@hoyongjin/gitbook-mcp 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Toolset keys — the gating groups a user enables via `GITBOOK_TOOLSETS`. Every
3
+ * row in the generated manifest carries a `toolset` that is one of these keys.
4
+ *
5
+ * `core` + `change-requests` are enabled by DEFAULT (≈ the original 11 tools plus
6
+ * the change-request review/merge loop); everything else is opt-in. The `all`
7
+ * sentinel (handled in config) enables every key. Keeping the default surface
8
+ * small avoids flooding `tools/list` with hundreds of schemas and keeps the
9
+ * model's tool-selection sharp.
10
+ *
11
+ * This list is the source of truth for config validation; a conformance test
12
+ * asserts every manifest `toolset` is one of these keys (no orphans).
13
+ */
14
+ export declare const TOOLSET_KEYS: readonly ["core", "change-requests", "collections", "sites", "site-ai", "site-insights", "integrations", "data", "git", "public", "org-admin"];
15
+ export type ToolsetKey = (typeof TOOLSET_KEYS)[number];
16
+ /** Enabled when `GITBOOK_TOOLSETS` is unset/empty — keeps back-compat with the 11-tool server. */
17
+ export declare const DEFAULT_TOOLSETS: readonly ToolsetKey[];
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Toolset keys — the gating groups a user enables via `GITBOOK_TOOLSETS`. Every
3
+ * row in the generated manifest carries a `toolset` that is one of these keys.
4
+ *
5
+ * `core` + `change-requests` are enabled by DEFAULT (≈ the original 11 tools plus
6
+ * the change-request review/merge loop); everything else is opt-in. The `all`
7
+ * sentinel (handled in config) enables every key. Keeping the default surface
8
+ * small avoids flooding `tools/list` with hundreds of schemas and keeps the
9
+ * model's tool-selection sharp.
10
+ *
11
+ * This list is the source of truth for config validation; a conformance test
12
+ * asserts every manifest `toolset` is one of these keys (no orphans).
13
+ */
14
+ export const TOOLSET_KEYS = [
15
+ "core",
16
+ "change-requests",
17
+ "collections",
18
+ "sites",
19
+ "site-ai",
20
+ "site-insights",
21
+ "integrations",
22
+ "data",
23
+ "git",
24
+ "public",
25
+ "org-admin",
26
+ ];
27
+ /** Enabled when `GITBOOK_TOOLSETS` is unset/empty — keeps back-compat with the 11-tool server. */
28
+ export const DEFAULT_TOOLSETS = ["core", "change-requests"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoyongjin/gitbook-mcp",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Model Context Protocol server for GitBook — read content and drive a change-request write workflow over stdio or streamable HTTP.",
5
5
  "keywords": [
6
6
  "gitbook",
@@ -35,6 +35,7 @@
35
35
  ],
36
36
  "scripts": {
37
37
  "build": "shx rm -rf dist && tsc -p tsconfig.json && shx chmod +x dist/index.js",
38
+ "generate:manifest": "node scripts/generate-manifest.mjs",
38
39
  "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
39
40
  "lint": "eslint .",
40
41
  "format": "prettier --write .",
@@ -48,7 +49,7 @@
48
49
  "prepublishOnly": "npm run typecheck && npm run lint && npm test"
49
50
  },
50
51
  "dependencies": {
51
- "@gitbook/api": "^0.183.0",
52
+ "@gitbook/api": "0.183.0",
52
53
  "@modelcontextprotocol/sdk": "^1.29.0",
53
54
  "express": "^5.2.1",
54
55
  "zod": "^4.4.3"
@@ -67,6 +68,7 @@
67
68
  "vitest": "^4.1.8"
68
69
  },
69
70
  "publishConfig": {
70
- "access": "public"
71
+ "access": "public",
72
+ "provenance": true
71
73
  }
72
74
  }
@@ -1,4 +0,0 @@
1
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { type ToolContext } from "./shared.js";
3
- /** Register the read-only GitBook tools. Returns the registered tool names. */
4
- export declare function registerReadTools(server: McpServer, ctx: ToolContext): string[];
@@ -1,91 +0,0 @@
1
- import { z } from "zod";
2
- import { jsonResult, errorResult, guard, READ_ANNOTATIONS, ToolInputError, } from "./shared.js";
3
- const limit = z
4
- .number()
5
- .int()
6
- .min(1)
7
- .max(1000)
8
- .optional()
9
- .describe("Max results to return (server caps apply).");
10
- const cursor = z
11
- .string()
12
- .optional()
13
- .describe("Pagination cursor from a previous call's nextCursor.");
14
- /** Register the read-only GitBook tools. Returns the registered tool names. */
15
- export function registerReadTools(server, ctx) {
16
- server.registerTool("gitbook_whoami", {
17
- title: "Get authenticated user",
18
- description: "Return the GitBook user the configured token authenticates as (id, name, email).",
19
- inputSchema: {},
20
- annotations: READ_ANNOTATIONS,
21
- }, guard(ctx, "gitbook_whoami", async () => jsonResult(await ctx.gitbook.getAuthenticatedUser())));
22
- server.registerTool("gitbook_list_orgs", {
23
- title: "List organizations",
24
- description: "List organizations the authenticated user can access. Use the returned id as orgId for other tools.",
25
- inputSchema: { limit, cursor },
26
- annotations: READ_ANNOTATIONS,
27
- }, guard(ctx, "gitbook_list_orgs", async ({ limit, cursor }) => jsonResult(await ctx.gitbook.listOrganizations({ limit, cursor }))));
28
- server.registerTool("gitbook_list_spaces", {
29
- title: "List spaces in an organization",
30
- description: "List the spaces inside an organization. Get orgId from gitbook_list_orgs.",
31
- inputSchema: { orgId: z.string().describe("Organization id."), limit, cursor },
32
- annotations: READ_ANNOTATIONS,
33
- }, guard(ctx, "gitbook_list_spaces", async ({ orgId, limit, cursor }) => jsonResult(await ctx.gitbook.listSpaces(orgId, { limit, cursor }))));
34
- server.registerTool("gitbook_get_space", {
35
- title: "Get a space",
36
- description: "Fetch a single space by id (title, visibility, urls, default revision).",
37
- inputSchema: { spaceId: z.string().describe("Space id.") },
38
- annotations: READ_ANNOTATIONS,
39
- }, guard(ctx, "gitbook_get_space", async ({ spaceId }) => jsonResult(await ctx.gitbook.getSpace(spaceId))));
40
- server.registerTool("gitbook_list_pages", {
41
- title: "List pages in a space",
42
- description: "List the page tree of a space's current revision (page ids, titles, paths). Not paginated.",
43
- inputSchema: { spaceId: z.string().describe("Space id.") },
44
- annotations: READ_ANNOTATIONS,
45
- }, guard(ctx, "gitbook_list_pages", async ({ spaceId }) => jsonResult({ pages: await ctx.gitbook.listPages(spaceId) })));
46
- server.registerTool("gitbook_get_page", {
47
- title: "Get a page's content",
48
- description: "Read a page by id. format='markdown' (default) returns rendered markdown; format='document' returns GitBook's structured Document JSON.",
49
- inputSchema: {
50
- spaceId: z.string().describe("Space id."),
51
- pageId: z.string().describe("Page id (from gitbook_list_pages)."),
52
- format: z
53
- .enum(["markdown", "document"])
54
- .optional()
55
- .describe("Output format (default markdown)."),
56
- },
57
- annotations: READ_ANNOTATIONS,
58
- }, guard(ctx, "gitbook_get_page", async ({ spaceId, pageId, format }) => jsonResult(await ctx.gitbook.getPage(spaceId, pageId, format ?? "markdown"))));
59
- server.registerTool("gitbook_search", {
60
- title: "Search content",
61
- description: "Full-text search. Provide orgId to search an organization, or spaceId to search a single space (exactly one is required).",
62
- inputSchema: {
63
- query: z.string().min(1).max(512).describe("Search query."),
64
- orgId: z.string().optional().describe("Organization id (search org-wide)."),
65
- spaceId: z.string().optional().describe("Space id (search one space)."),
66
- limit,
67
- cursor,
68
- },
69
- annotations: READ_ANNOTATIONS,
70
- }, guard(ctx, "gitbook_search", async ({ query, orgId, spaceId, limit, cursor }) => {
71
- if (!orgId && !spaceId) {
72
- return errorResult(new ToolInputError("Provide either orgId or spaceId."), ctx.config.token);
73
- }
74
- if (orgId && spaceId) {
75
- return errorResult(new ToolInputError("Provide only one of orgId or spaceId, not both."), ctx.config.token);
76
- }
77
- const page = spaceId
78
- ? await ctx.gitbook.searchSpace(spaceId, query, { limit, cursor })
79
- : await ctx.gitbook.searchOrganization(orgId, query, { limit, cursor });
80
- return jsonResult(page);
81
- }));
82
- return [
83
- "gitbook_whoami",
84
- "gitbook_list_orgs",
85
- "gitbook_list_spaces",
86
- "gitbook_get_space",
87
- "gitbook_list_pages",
88
- "gitbook_get_page",
89
- "gitbook_search",
90
- ];
91
- }
@@ -1,8 +0,0 @@
1
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { type ToolContext } from "./shared.js";
3
- /**
4
- * Register the change-request write tools. Only call this when NOT in read-only
5
- * mode — gating registration (rather than rejecting at call time) keeps the
6
- * write tools out of tools/list entirely. Returns the registered tool names.
7
- */
8
- export declare function registerWriteTools(server: McpServer, ctx: ToolContext): string[];
@@ -1,88 +0,0 @@
1
- import { z } from "zod";
2
- import { jsonResult, guard, WRITE_ANNOTATIONS, DESTRUCTIVE_ANNOTATIONS, } from "./shared.js";
3
- import { isSafeImportUrl } from "../gitbook/import-url.js";
4
- /** Audit write/destructive tools at info level (invoke + ok lines). */
5
- const AUDIT = { audit: true };
6
- /**
7
- * Register the change-request write tools. Only call this when NOT in read-only
8
- * mode — gating registration (rather than rejecting at call time) keeps the
9
- * write tools out of tools/list entirely. Returns the registered tool names.
10
- */
11
- export function registerWriteTools(server, ctx) {
12
- server.registerTool("gitbook_create_change_request", {
13
- title: "Open a change request",
14
- description: "Create a change request (draft branch) on a space. Returns its id to target with gitbook_import_content / gitbook_comment_change_request, then gitbook_merge_change_request to publish.",
15
- inputSchema: {
16
- spaceId: z.string().describe("Space id."),
17
- subject: z.string().optional().describe("Title/subject of the change request."),
18
- },
19
- annotations: WRITE_ANNOTATIONS,
20
- }, guard(ctx, "gitbook_create_change_request", async ({ spaceId, subject }) => jsonResult(await ctx.gitbook.createChangeRequest(spaceId, subject)), AUDIT));
21
- server.registerTool("gitbook_import_content", {
22
- title: "Import content into a change request",
23
- description: "GitBook's content-write primitive: import a public web page (sourceUrl) into a space — scoped to a change request (and optionally a page), AI-enhanced by default. There is NO direct 'set page body' API; this is the supported write path. The import is asynchronous (returns a run id + status); review in GitBook, then gitbook_merge_change_request to publish.",
24
- inputSchema: {
25
- orgId: z.string().describe("Organization id that owns the space."),
26
- spaceId: z.string().describe("Target space id."),
27
- sourceUrl: z
28
- .string()
29
- .url()
30
- .refine(isSafeImportUrl, {
31
- message: "sourceUrl must be an http(s) URL with no embedded credentials.",
32
- })
33
- .describe("Public http(s) URL of the page to import as content (no credentials)."),
34
- changeRequestId: z
35
- .string()
36
- .optional()
37
- .describe("Target change request id (recommended — keeps the import off the live branch)."),
38
- pageId: z.string().optional().describe("Target page id to import into (optional)."),
39
- enhance: z.boolean().optional().describe("AI-enhance the imported content (default true)."),
40
- },
41
- annotations: WRITE_ANNOTATIONS,
42
- }, guard(ctx, "gitbook_import_content", async (args) => jsonResult(await ctx.gitbook.importContent(args)), AUDIT));
43
- server.registerTool("gitbook_comment_change_request", {
44
- title: "Comment on a change request",
45
- description: "Post a markdown comment on a change request (review feedback / notes).",
46
- inputSchema: {
47
- spaceId: z.string().describe("Space id."),
48
- changeRequestId: z.string().describe("Change request id."),
49
- body: z.string().min(1).describe("Comment text (markdown)."),
50
- page: z.string().optional().describe("Page id to attach the comment to (optional)."),
51
- },
52
- annotations: WRITE_ANNOTATIONS,
53
- }, guard(ctx, "gitbook_comment_change_request", async ({ spaceId, changeRequestId, body, page }) => jsonResult(await ctx.gitbook.commentOnChangeRequest(spaceId, changeRequestId, body, page)), AUDIT));
54
- server.registerTool("gitbook_merge_change_request", {
55
- title: "Merge (publish) a change request",
56
- description: "Publish a change request into the live space. THIS IS DESTRUCTIVE — it changes the live published docs. Only call after the change request has been reviewed.",
57
- inputSchema: {
58
- spaceId: z.string().describe("Space id."),
59
- changeRequestId: z.string().describe("Change request id to merge."),
60
- },
61
- annotations: DESTRUCTIVE_ANNOTATIONS,
62
- }, guard(ctx, "gitbook_merge_change_request", async ({ spaceId, changeRequestId }) => {
63
- const merge = await ctx.gitbook.mergeChangeRequest(spaceId, changeRequestId);
64
- // GitBook returns HTTP 200 with result:"conflicts" when it could NOT merge
65
- // — the change request is NOT published. Flag it as an error so the model
66
- // never mistakes this no-op for a successful publish of the live docs.
67
- if (merge.result === "conflicts") {
68
- return {
69
- content: [
70
- {
71
- type: "text",
72
- text: "Merge did NOT publish: the change request has conflicts and remains open. " +
73
- `Resolve them in GitBook, then retry.\n${JSON.stringify(merge, null, 2)}`,
74
- },
75
- ],
76
- structuredContent: merge,
77
- isError: true,
78
- };
79
- }
80
- return jsonResult(merge);
81
- }, AUDIT));
82
- return [
83
- "gitbook_create_change_request",
84
- "gitbook_import_content",
85
- "gitbook_comment_change_request",
86
- "gitbook_merge_change_request",
87
- ];
88
- }