Comprehensive MCP server management with kubectl-style CLI. Key features in this release: - Declarative YAML apply/get round-trip with project cloning support - Gated sessions with prompt intelligence for Claude - Interactive MCP console with traffic inspector - Persistent STDIO connections for containerized servers - RBAC with name-scoped bindings - Shell completions (fish + bash) auto-generated - Rate-limit retry with exponential backoff in apply - Project-scoped prompt management - Credential scrubbing from git history Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
107 lines
3.8 KiB
Diff
107 lines
3.8 KiB
Diff
diff --git a/src/index.ts b/src/index.ts
|
|
index 83c251d..852ee0e 100644
|
|
--- a/src/index.ts
|
|
+++ b/src/index.ts
|
|
@@ -1,4 +1,4 @@
|
|
-import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
+import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
import FormData from "form-data";
|
|
import axios, { AxiosInstance } from "axios";
|
|
@@ -130,10 +130,18 @@ class DocmostClient {
|
|
return groups.map((group) => filterGroup(group));
|
|
}
|
|
|
|
- async listPages(spaceId?: string) {
|
|
- const payload = spaceId ? { spaceId } : {};
|
|
- const pages = await this.paginateAll("/pages/recent", payload);
|
|
- return pages.map((page) => filterPage(page));
|
|
+ async listPages(spaceId?: string, page: number = 1, limit: number = 50) {
|
|
+ await this.ensureAuthenticated();
|
|
+ const clampedLimit = Math.max(1, Math.min(100, limit));
|
|
+ const payload: Record<string, any> = { page, limit: clampedLimit };
|
|
+ if (spaceId) payload.spaceId = spaceId;
|
|
+ const response = await this.client.post("/pages/recent", payload);
|
|
+ const data = response.data;
|
|
+ const items = data.data?.items || data.items || [];
|
|
+ return {
|
|
+ pages: items.map((p: any) => filterPage(p)),
|
|
+ meta: data.data?.meta || data.meta || {},
|
|
+ };
|
|
}
|
|
|
|
async listSidebarPages(spaceId: string, pageId: string) {
|
|
@@ -283,8 +291,9 @@ class DocmostClient {
|
|
spaceId,
|
|
});
|
|
|
|
- // Filter search results (data is directly an array)
|
|
- const items = response.data?.data || [];
|
|
+ // Handle both array and {items: [...]} response formats
|
|
+ const rawData = response.data?.data;
|
|
+ const items = Array.isArray(rawData) ? rawData : (rawData?.items || []);
|
|
const filteredItems = items.map((item: any) => filterSearchResult(item));
|
|
|
|
return {
|
|
@@ -384,13 +393,15 @@ server.registerTool(
|
|
server.registerTool(
|
|
"list_pages",
|
|
{
|
|
- description: "List pages in a space ordered by updatedAt (descending).",
|
|
+ description: "List pages in a space ordered by updatedAt (descending). Returns one page of results.",
|
|
inputSchema: {
|
|
spaceId: z.string().optional(),
|
|
+ page: z.number().optional().describe("Page number (default: 1)"),
|
|
+ limit: z.number().optional().describe("Items per page, 1-100 (default: 50)"),
|
|
},
|
|
},
|
|
- async ({ spaceId }) => {
|
|
- const result = await docmostClient.listPages(spaceId);
|
|
+ async ({ spaceId, page, limit }) => {
|
|
+ const result = await docmostClient.listPages(spaceId, page, limit);
|
|
return jsonContent(result);
|
|
},
|
|
);
|
|
@@ -544,6 +555,41 @@ server.registerTool(
|
|
},
|
|
);
|
|
|
|
+// Resource template: docmost://pages/{pageId}
|
|
+// Allows MCP clients to read page content as resources
|
|
+server.resource(
|
|
+ "page",
|
|
+ new ResourceTemplate("docmost://pages/{pageId}", {
|
|
+ list: async () => {
|
|
+ // List recent pages as browsable resources
|
|
+ try {
|
|
+ const result = await docmostClient.listPages(undefined, 1, 100);
|
|
+ return result.pages.map((page: any) => ({
|
|
+ uri: `docmost://pages/${page.id}`,
|
|
+ name: page.title || page.id,
|
|
+ mimeType: "text/markdown",
|
|
+ }));
|
|
+ } catch {
|
|
+ return [];
|
|
+ }
|
|
+ },
|
|
+ }),
|
|
+ { description: "A Docmost wiki page", mimeType: "text/markdown" },
|
|
+ async (uri: URL, variables: Record<string, string | string[]>) => {
|
|
+ const pageId = Array.isArray(variables.pageId) ? variables.pageId[0]! : variables.pageId!;
|
|
+ const page = await docmostClient.getPage(pageId);
|
|
+ return {
|
|
+ contents: [
|
|
+ {
|
|
+ uri: uri.href,
|
|
+ text: page.data.content || `# ${page.data.title || "Untitled"}\n\n(No content)`,
|
|
+ mimeType: "text/markdown",
|
|
+ },
|
|
+ ],
|
|
+ };
|
|
+ },
|
|
+);
|
|
+
|
|
async function run() {
|
|
const transport = new StdioServerTransport();
|
|
await server.connect(transport);
|