@zereight/mcp-gitlab 2.1.69 → 2.2.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.
Files changed (155) hide show
  1. package/README.ko.md +10 -4
  2. package/README.md +238 -233
  3. package/README.pt-BR.md +4 -4
  4. package/README.zh-CN.md +6 -4
  5. package/build/config.js +12 -4
  6. package/build/deprecated-env.js +47 -0
  7. package/build/index.js +456 -237
  8. package/build/oauth-device-flow.js +80 -0
  9. package/build/oauth.js +135 -18
  10. package/build/schemas.js +1 -1
  11. package/build/server/remote-http-stack.js +39 -0
  12. package/build/tools/discover-activation.js +38 -0
  13. package/build/tools/exposed-tools.js +35 -0
  14. package/build/tools/listed-tools.js +76 -0
  15. package/build/tools/registry.js +226 -57
  16. package/build/tools/tool-profile.js +66 -0
  17. package/build/utils/compact-tool-allowlist.js +14 -0
  18. package/build/utils/logger.js +34 -12
  19. package/build/utils/url.js +15 -0
  20. package/package.json +3 -3
  21. package/build/scripts/check-skill-sync.js +0 -143
  22. package/build/scripts/generate-server-card.js +0 -39
  23. package/build/scripts/generate-tool-coverage.js +0 -58
  24. package/build/scripts/generate-tool-docs.js +0 -430
  25. package/build/scripts/tool-coverage/coverage.js +0 -341
  26. package/build/scripts/tool-coverage/coverage.test.js +0 -142
  27. package/build/test/callback-proxy-tests.js +0 -321
  28. package/build/test/cli/args.test.js +0 -175
  29. package/build/test/cli/boolean-flags.test.js +0 -106
  30. package/build/test/cli/compact-mcp-result.test.js +0 -93
  31. package/build/test/cli/exposure.test.js +0 -104
  32. package/build/test/cli/load-config-snapshot.js +0 -5
  33. package/build/test/cli/output.test.js +0 -74
  34. package/build/test/cli/replay-command.test.js +0 -37
  35. package/build/test/cli/router.test.js +0 -183
  36. package/build/test/client-pool-test.js +0 -109
  37. package/build/test/clients/client.js +0 -34
  38. package/build/test/clients/custom-header-client.js +0 -122
  39. package/build/test/clients/sse-client.js +0 -95
  40. package/build/test/clients/stdio-client.js +0 -113
  41. package/build/test/clients/streamable-http-client.js +0 -95
  42. package/build/test/config-allowed-groups.test.js +0 -97
  43. package/build/test/create-commit-actions.test.js +0 -102
  44. package/build/test/draft-note-schema.test.js +0 -44
  45. package/build/test/dynamic-api-url-allowlist.test.js +0 -121
  46. package/build/test/dynamic-api-url-test.js +0 -304
  47. package/build/test/dynamic-routing-tests.js +0 -495
  48. package/build/test/get-merge-request-discussion-schema.test.js +0 -30
  49. package/build/test/gitlab-artifact-entry-schema.test.js +0 -44
  50. package/build/test/gitlab-diff-schema.test.js +0 -36
  51. package/build/test/gitlab-pipeline-schedule-schema.test.js +0 -32
  52. package/build/test/gitlab-tag-schema.test.js +0 -51
  53. package/build/test/group-milestone-schema.test.js +0 -45
  54. package/build/test/mcp-oauth-tests.js +0 -905
  55. package/build/test/mcp-server-name.test.js +0 -87
  56. package/build/test/merge-merge-request-schema.test.js +0 -75
  57. package/build/test/multi-server-test.js +0 -190
  58. package/build/test/no-proxy-integration-test.js +0 -183
  59. package/build/test/no-proxy-test.js +0 -149
  60. package/build/test/nullable-gitlab-response-fields.test.js +0 -37
  61. package/build/test/nullish-tool-arguments-schema.test.js +0 -118
  62. package/build/test/oauth-device-flow-tests.js +0 -450
  63. package/build/test/oauth-startup.test.js +0 -204
  64. package/build/test/oauth-tests.js +0 -428
  65. package/build/test/path-segment-encoding.test.js +0 -98
  66. package/build/test/remote-auth-simple-test.js +0 -277
  67. package/build/test/remote-auth-tests.js +0 -314
  68. package/build/test/response-masking.test.js +0 -445
  69. package/build/test/schema-tests.js +0 -1679
  70. package/build/test/server/metrics.test.js +0 -47
  71. package/build/test/server/request-helpers.test.js +0 -48
  72. package/build/test/server/version.test.js +0 -13
  73. package/build/test/sse-auth-guard.test.js +0 -96
  74. package/build/test/sse-session-limits.test.js +0 -273
  75. package/build/test/stateless/callback-proxy.test.js +0 -510
  76. package/build/test/stateless/client-id.test.js +0 -177
  77. package/build/test/stateless/codec.test.js +0 -328
  78. package/build/test/stateless/config-ttl.test.js +0 -149
  79. package/build/test/stateless/consumed-proxy-code-cache.test.js +0 -150
  80. package/build/test/stateless/session-id-integration.test.js +0 -675
  81. package/build/test/stateless/session-id.test.js +0 -157
  82. package/build/test/streamable-http-concurrent-session.test.js +0 -171
  83. package/build/test/streamable-http-dns-rebinding.test.js +0 -185
  84. package/build/test/streamable-http-sse-stream.test.js +0 -350
  85. package/build/test/streamable-http-static-token-auth.test.js +0 -157
  86. package/build/test/streamable-http-unauthenticated-discovery.test.js +0 -207
  87. package/build/test/test-all-transport-server.js +0 -255
  88. package/build/test/test-auth-retry.js +0 -195
  89. package/build/test/test-blank-filters.js +0 -156
  90. package/build/test/test-ci-catalog.js +0 -177
  91. package/build/test/test-ci-lint.js +0 -191
  92. package/build/test/test-ci-variables.js +0 -309
  93. package/build/test/test-create-repository.js +0 -120
  94. package/build/test/test-dependency-proxy.js +0 -195
  95. package/build/test/test-deployment-tools.js +0 -463
  96. package/build/test/test-download-attachment.js +0 -190
  97. package/build/test/test-dynamic-project-scope.js +0 -524
  98. package/build/test/test-empty-toolsets.js +0 -535
  99. package/build/test/test-get-file-blame.js +0 -145
  100. package/build/test/test-get-file-contents.js +0 -128
  101. package/build/test/test-geteffectiveprojectid.js +0 -812
  102. package/build/test/test-health-check.js +0 -115
  103. package/build/test/test-issue-description-patch.js +0 -256
  104. package/build/test/test-job-artifacts.js +0 -275
  105. package/build/test/test-json-schema.js +0 -166
  106. package/build/test/test-list-group-members.js +0 -150
  107. package/build/test/test-list-group-merge-requests.js +0 -165
  108. package/build/test/test-list-issues.js +0 -170
  109. package/build/test/test-list-merge-requests.js +0 -201
  110. package/build/test/test-list-milestones.js +0 -145
  111. package/build/test/test-list-project-members.js +0 -132
  112. package/build/test/test-merge-request-approval-state-tools.js +0 -213
  113. package/build/test/test-merge-request-approvals.js +0 -187
  114. package/build/test/test-merge-request-pipelines.js +0 -106
  115. package/build/test/test-mr-diffs-filter.js +0 -132
  116. package/build/test/test-mr-file-diffs.js +0 -251
  117. package/build/test/test-oauth-proxy-rate-limit.js +0 -133
  118. package/build/test/test-orbit.js +0 -155
  119. package/build/test/test-permission-mode.js +0 -424
  120. package/build/test/test-pipeline-schedules.js +0 -482
  121. package/build/test/test-protected-branches.js +0 -174
  122. package/build/test/test-remote-downloads.js +0 -510
  123. package/build/test/test-search-code.js +0 -275
  124. package/build/test/test-snippets.js +0 -735
  125. package/build/test/test-tags.js +0 -220
  126. package/build/test/test-todos.js +0 -195
  127. package/build/test/test-token-optimizations.js +0 -691
  128. package/build/test/test-toolset-filtering.js +0 -499
  129. package/build/test/test-update-issue-slim.js +0 -141
  130. package/build/test/test-update-project.js +0 -112
  131. package/build/test/test-upload-markdown.js +0 -206
  132. package/build/test/test-vulnerabilities.js +0 -399
  133. package/build/test/test-webhooks.js +0 -218
  134. package/build/test/tool-description-quality.test.js +0 -50
  135. package/build/test/utils/bulk-publish-options.test.js +0 -32
  136. package/build/test/utils/download-token.test.js +0 -35
  137. package/build/test/utils/forwarded-public-base-url.test.js +0 -46
  138. package/build/test/utils/gitlab-instance-version.test.js +0 -32
  139. package/build/test/utils/gitlab-version-gate.test.js +0 -74
  140. package/build/test/utils/graphql-query.test.js +0 -168
  141. package/build/test/utils/helpers-path.test.js +0 -104
  142. package/build/test/utils/jmespath-tool-result.test.js +0 -91
  143. package/build/test/utils/masking.test.js +0 -384
  144. package/build/test/utils/merge-request-position.test.js +0 -57
  145. package/build/test/utils/mock-gitlab-server.js +0 -614
  146. package/build/test/utils/proxy-client-ip.test.js +0 -28
  147. package/build/test/utils/redact-sensitive.test.js +0 -32
  148. package/build/test/utils/safe-redirect-fetch.test.js +0 -463
  149. package/build/test/utils/server-launcher.js +0 -231
  150. package/build/test/utils/tool-args.test.js +0 -210
  151. package/build/test/utils/url.test.js +0 -128
  152. package/build/test/utils/version-check.test.js +0 -239
  153. package/build/test/utils/wiki-title.test.js +0 -21
  154. package/build/test-note.js +0 -54
  155. package/build/test-resolve-issue-note.js +0 -127
@@ -1,143 +0,0 @@
1
- #!/usr/bin/env tsx
2
- /**
3
- * Verify skills/gitlab-mcp/ stays in sync with tools/registry.ts.
4
- *
5
- * Run with: npm run check:skill-sync
6
- */
7
- import { readFileSync, readdirSync } from "node:fs";
8
- import { dirname, join } from "node:path";
9
- import { fileURLToPath } from "node:url";
10
- import { TOOLSET_DEFINITIONS, allTools, destructiveTools, DEFAULT_TOOLSET_IDS, } from "../tools/registry.js";
11
- const __dirname = dirname(fileURLToPath(import.meta.url));
12
- const REPO_ROOT = join(__dirname, "..");
13
- const SKILL_PATH = join(REPO_ROOT, "skills", "gitlab-mcp", "SKILL.md");
14
- const REF_DIR = join(REPO_ROOT, "skills", "gitlab-mcp", "reference");
15
- // Response field names in reference docs — not MCP tools.
16
- const REFERENCE_FIELD_ALLOWLIST = new Set([
17
- "after",
18
- "allow_multiple",
19
- "deleted_file",
20
- "excluded_file_patterns",
21
- "group_id",
22
- "new_file",
23
- "new_path",
24
- "old_path",
25
- "name",
26
- "page",
27
- "per_page",
28
- "pipeline_id",
29
- "job_inputs",
30
- "project_id",
31
- "renamed_file",
32
- ]);
33
- function fail(message) {
34
- console.error(`::error::${message}`);
35
- process.exit(1);
36
- }
37
- function parseToolsetTable(skill) {
38
- const rows = new Map();
39
- const rowRe = /^\| ([a-z_]+) \((\d+) tools?\) \| (yes|no) \|/gm;
40
- let match;
41
- while ((match = rowRe.exec(skill)) !== null) {
42
- rows.set(match[1], {
43
- count: Number(match[2]),
44
- isDefault: match[3] === "yes",
45
- });
46
- }
47
- return rows;
48
- }
49
- function parseIntroCounts(skill) {
50
- const match = skill.match(/providing (\d+) tools: (\d+) tools across (\d+) toolsets/);
51
- if (!match) {
52
- fail("skills/gitlab-mcp/SKILL.md intro line is missing or malformed");
53
- }
54
- return {
55
- totalTools: Number(match[1]),
56
- uniqueInToolsets: Number(match[2]),
57
- toolsetCount: Number(match[3]),
58
- };
59
- }
60
- function parseDestructiveTools(skill) {
61
- const section = skill.match(/## Destructive Tools[^\n]*\n\n(`[^`]+`(?:, `[^`]+`)*)$/m);
62
- if (!section) {
63
- fail("skills/gitlab-mcp/SKILL.md destructive tools section is missing or malformed");
64
- }
65
- return new Set([...section[1].matchAll(/`([a-z_]+)`/g)].map((m) => m[1]));
66
- }
67
- function collectReferenceToolNames() {
68
- const toolNames = new Set();
69
- for (const file of readdirSync(REF_DIR).filter((name) => name.endsWith(".md"))) {
70
- const text = readFileSync(join(REF_DIR, file), "utf8");
71
- for (const match of text.matchAll(/`([a-z][a-z0-9_]*)`/g)) {
72
- const name = match[1];
73
- if (!REFERENCE_FIELD_ALLOWLIST.has(name)) {
74
- toolNames.add(name);
75
- }
76
- }
77
- }
78
- return toolNames;
79
- }
80
- const skill = readFileSync(SKILL_PATH, "utf8");
81
- const errors = [];
82
- const registryCounts = Object.fromEntries(TOOLSET_DEFINITIONS.map((definition) => [definition.id, definition.tools.size]));
83
- const skillRows = parseToolsetTable(skill);
84
- if (skillRows.size !== TOOLSET_DEFINITIONS.length) {
85
- errors.push(`toolset table has ${skillRows.size} rows; registry has ${TOOLSET_DEFINITIONS.length}`);
86
- }
87
- for (const definition of TOOLSET_DEFINITIONS) {
88
- const row = skillRows.get(definition.id);
89
- if (!row) {
90
- errors.push(`missing toolset row for ${definition.id}`);
91
- continue;
92
- }
93
- const actualCount = registryCounts[definition.id];
94
- if (row.count !== actualCount) {
95
- errors.push(`${definition.id}: skill says ${row.count}, registry has ${actualCount}`);
96
- }
97
- const expectedDefault = DEFAULT_TOOLSET_IDS.has(definition.id);
98
- if (row.isDefault !== expectedDefault) {
99
- errors.push(`${definition.id}: skill default=${row.isDefault}, registry default=${expectedDefault}`);
100
- }
101
- }
102
- for (const id of skillRows.keys()) {
103
- if (!registryCounts[id]) {
104
- errors.push(`unknown toolset in skill table: ${id}`);
105
- }
106
- }
107
- const uniqueInToolsets = new Set(TOOLSET_DEFINITIONS.flatMap((definition) => [...definition.tools]))
108
- .size;
109
- const intro = parseIntroCounts(skill);
110
- if (intro.totalTools !== allTools.length) {
111
- errors.push(`intro total tools: skill says ${intro.totalTools}, registry has ${allTools.length}`);
112
- }
113
- if (intro.uniqueInToolsets !== uniqueInToolsets) {
114
- errors.push(`intro unique toolset tools: skill says ${intro.uniqueInToolsets}, registry has ${uniqueInToolsets}`);
115
- }
116
- if (intro.toolsetCount !== TOOLSET_DEFINITIONS.length) {
117
- errors.push(`intro toolset count: skill says ${intro.toolsetCount}, registry has ${TOOLSET_DEFINITIONS.length}`);
118
- }
119
- const skillDestructive = parseDestructiveTools(skill);
120
- for (const tool of skillDestructive) {
121
- if (!destructiveTools.has(tool)) {
122
- errors.push(`destructive list includes non-destructive tool: ${tool}`);
123
- }
124
- }
125
- for (const tool of destructiveTools) {
126
- if (!skillDestructive.has(tool)) {
127
- errors.push(`destructive list missing registry tool: ${tool}`);
128
- }
129
- }
130
- const registryToolNames = new Set(allTools.map((tool) => tool.name));
131
- for (const tool of collectReferenceToolNames()) {
132
- if (!registryToolNames.has(tool)) {
133
- errors.push(`reference mentions unknown tool: ${tool}`);
134
- }
135
- }
136
- if (errors.length > 0) {
137
- console.error("skills/gitlab-mcp/ is out of sync with tools/registry.ts:");
138
- for (const error of errors) {
139
- console.error(`- ${error}`);
140
- }
141
- process.exit(1);
142
- }
143
- console.log("skills/gitlab-mcp/ is in sync with tools/registry.ts");
@@ -1,39 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { allTools } from "../tools/registry.js";
4
- async function main() {
5
- const pkgPath = path.resolve(process.cwd(), "package.json");
6
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
7
- const serverCard = {
8
- serverInfo: {
9
- name: pkg.mcpName || pkg.name,
10
- version: pkg.version,
11
- },
12
- authentication: {
13
- required: true,
14
- schemes: ["bearer", "oauth2"],
15
- },
16
- tools: allTools.map(t => {
17
- const tool = { ...t };
18
- if (tool.inputSchema &&
19
- typeof tool.inputSchema === "object" &&
20
- "$schema" in tool.inputSchema) {
21
- const schema = { ...tool.inputSchema };
22
- delete schema["$schema"];
23
- tool.inputSchema = schema;
24
- }
25
- return tool;
26
- }),
27
- resources: [],
28
- prompts: [],
29
- };
30
- const outDir = path.resolve(process.cwd(), ".well-known", "mcp");
31
- fs.mkdirSync(outDir, { recursive: true });
32
- const outFile = path.join(outDir, "server-card.json");
33
- fs.writeFileSync(outFile, JSON.stringify(serverCard, null, 2), "utf8");
34
- console.log(`Successfully generated ${outFile} (${allTools.length} tools)`);
35
- }
36
- main().catch(err => {
37
- console.error("Failed to generate server card:", err);
38
- process.exit(1);
39
- });
@@ -1,58 +0,0 @@
1
- #!/usr/bin/env tsx
2
- /**
3
- * Generate docs/reference/tool-coverage.md from tools/registry.ts + test/ MCP calls.
4
- *
5
- * Detection rules live in ./tool-coverage/coverage.ts (AST-scoped): a tool is
6
- * invoked when test source sends that name as a `callTool*` argument, as the
7
- * `name` of a first-argument params object (`client.callTool({ name })`), or as
8
- * the `name` of a `params` object inside a `tools/call` payload literal.
9
- *
10
- * Name-only mentions (toolset tables, description-quality) do not count.
11
- * callTool(variable) loops are missed unless the name is also a literal in that call.
12
- *
13
- * Run with: npm run docs:tools
14
- */
15
- import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
16
- import { dirname, join, relative } from "node:path";
17
- import { fileURLToPath } from "node:url";
18
- import { allTools, TOOLSET_DEFINITIONS } from "../tools/registry.js";
19
- import { assertDetectorHealthy, buildCoverageMarkdown, collectInvocations, countInvoked, groupToolsByToolset, percent, SKIP_FILES, } from "./tool-coverage/coverage.js";
20
- const __dirname = dirname(fileURLToPath(import.meta.url));
21
- const REPO_ROOT = join(__dirname, "..");
22
- const TEST_DIR = join(REPO_ROOT, "test");
23
- const OUT_PATH = join(REPO_ROOT, "docs", "reference", "tool-coverage.md");
24
- function walkTestFiles(dir) {
25
- const out = [];
26
- for (const name of readdirSync(dir)) {
27
- const full = join(dir, name);
28
- const stat = statSync(full);
29
- if (stat.isDirectory()) {
30
- out.push(...walkTestFiles(full));
31
- continue;
32
- }
33
- if (/\.(ts|js)$/.test(name) && !SKIP_FILES.has(name)) {
34
- out.push(full);
35
- }
36
- }
37
- return out;
38
- }
39
- function repoPath(abs) {
40
- return relative(REPO_ROOT, abs).split("\\").join("/");
41
- }
42
- function readScannedFiles() {
43
- return walkTestFiles(TEST_DIR).map(abs => ({
44
- path: repoPath(abs),
45
- text: readFileSync(abs, "utf8"),
46
- }));
47
- }
48
- function main() {
49
- const toolNames = allTools.map(tool => tool.name);
50
- const toolNameSet = new Set(toolNames);
51
- const hits = collectInvocations(readScannedFiles(), toolNameSet);
52
- const invokedCount = countInvoked(toolNames, hits);
53
- assertDetectorHealthy(hits, invokedCount);
54
- const grouped = groupToolsByToolset(toolNames, TOOLSET_DEFINITIONS);
55
- writeFileSync(OUT_PATH, buildCoverageMarkdown(toolNames, hits, grouped));
56
- console.log(`generated docs/reference/tool-coverage.md — ${invokedCount}/${toolNames.length} (${percent(invokedCount, toolNames.length)})`);
57
- }
58
- main();
@@ -1,430 +0,0 @@
1
- #!/usr/bin/env tsx
2
- /**
3
- * Generate per-group tool reference pages under docs/tools/ from the
4
- * authoritative tool registry (tools/registry.ts).
5
- *
6
- * Run with: npx tsx scripts/generate-tool-docs.ts
7
- * Or via: make tools-docs
8
- */
9
- import { writeFileSync, mkdirSync, readdirSync, unlinkSync, statSync } from "node:fs";
10
- import { join, dirname } from "node:path";
11
- import { fileURLToPath } from "node:url";
12
- import { allTools, readOnlyTools, TOOLSET_DEFINITIONS } from "../tools/registry.js";
13
- const __dirname = dirname(fileURLToPath(import.meta.url));
14
- const REPO_ROOT = join(__dirname, "..");
15
- const OUT_DIR = join(REPO_ROOT, "docs", "tools");
16
- // Legacy single-group env flags that pre-date GITLAB_TOOLSETS.
17
- // Only three groups have these for backward compatibility; everything else
18
- // opt-in is configured via GITLAB_TOOLSETS / GITLAB_TOOLS / discover_tools.
19
- const LEGACY_TOGGLE_ENV = {
20
- pipelines: "USE_PIPELINE",
21
- milestones: "USE_MILESTONE",
22
- wiki: "USE_GITLAB_WIKI",
23
- };
24
- function isDefaultToolset(id) {
25
- return TOOLSET_DEFINITIONS.find(d => d.id === id)?.isDefault ?? false;
26
- }
27
- /** Returns the availability note for a toolset group, or undefined when it is on by default. */
28
- function computeToggleNote(id) {
29
- if (isDefaultToolset(id))
30
- return undefined;
31
- // Synthetic group for tools not in any TOOLSET_DEFINITIONS entry.
32
- // discover_tools is always exposed; execute_graphql is opt-in via GITLAB_TOOLS.
33
- if (id === "meta") {
34
- return "Mixed availability. `discover_tools` is always exposed (the server re-adds it after every toolset filter). `execute_graphql` is not part of any toolset — enable it explicitly with `GITLAB_TOOLS=execute_graphql`. Tools excluded from the list (toolset / `GITLAB_TOOLS` / `GITLAB_DENIED_TOOLS_REGEX`) are also rejected on direct calls; `GITLAB_TOOL_POLICY_HIDDEN` tools stay callable unless they match `GITLAB_DENIED_TOOLS_REGEX`.";
35
- }
36
- const legacy = LEGACY_TOGGLE_ENV[id];
37
- if (legacy) {
38
- return `Opt-in. Enable via \`GITLAB_TOOLSETS=${id}\` (or \`GITLAB_TOOLSETS=all\`), or use the legacy \`${legacy}=true\` flag for backward compatibility.`;
39
- }
40
- return `Opt-in. Enable via \`GITLAB_TOOLSETS=${id}\` (or \`GITLAB_TOOLSETS=all\`), list individual tools in \`GITLAB_TOOLS=\`, or activate at runtime with the \`discover_tools\` MCP tool.`;
41
- }
42
- const GROUP_META = {
43
- merge_requests: {
44
- title: "Merge Requests",
45
- blurb: "MR lifecycle — create, update, merge, approve, plus diff/conflict inspection and the full discussion/note/draft API.",
46
- },
47
- issues: {
48
- title: "Issues",
49
- blurb: "Issue CRUD, links, discussions and notes, todos, and emoji reactions.",
50
- },
51
- repositories: {
52
- title: "Projects & Files",
53
- blurb: "Project search/creation/fork plus the Files API for reading and writing repository content without shelling out to git.",
54
- },
55
- branches: {
56
- title: "Branches & Commits",
57
- blurb: "Branch management, commit listing/inspection, file blame, and CI commit-status manipulation.",
58
- },
59
- projects: {
60
- title: "Projects & Namespaces",
61
- blurb: "Project/namespace listing, member queries, group iterations, and server health.",
62
- },
63
- labels: {
64
- title: "Labels",
65
- blurb: "Project label CRUD.",
66
- },
67
- ci: {
68
- title: "CI Lint",
69
- blurb: "Validate `.gitlab-ci.yml` snippets and project pipeline configs.",
70
- },
71
- groups: {
72
- title: "Groups",
73
- blurb: "Create new groups and subgroups.",
74
- },
75
- pipelines: {
76
- title: "Pipelines, Jobs & Deployments",
77
- blurb: "Pipeline + job control (trigger, retry, cancel, play manual jobs, fetch logs/artifacts), and the deployments/environments view.",
78
- },
79
- milestones: {
80
- title: "Milestones",
81
- blurb: "Project and group milestone CRUD plus associated issues/MRs and burndown events.",
82
- },
83
- wiki: {
84
- title: "Wiki",
85
- blurb: "Project and group wiki page CRUD. Attachment uploads where supported.",
86
- },
87
- releases: {
88
- title: "Releases",
89
- blurb: "Release lifecycle, release evidence, and asset download.",
90
- },
91
- tags: {
92
- title: "Tags",
93
- blurb: "Tag listing, creation, deletion, and signature inspection.",
94
- },
95
- users: {
96
- title: "Users & Events",
97
- blurb: "User lookup, the authenticated user (`whoami`), event streams, and markdown attachment upload/download.",
98
- },
99
- workitems: {
100
- title: "Work Items",
101
- blurb: "Modern unified API for issues, tasks, incidents, and other typed work items — including notes, emoji reactions, and incident timeline events.",
102
- },
103
- webhooks: {
104
- title: "Webhooks",
105
- blurb: "Create, update, and delete project or group webhooks, and inspect recent webhook events.",
106
- },
107
- search: {
108
- title: "Search",
109
- blurb: "Code search across all visible projects, a specific project, or a specific group.",
110
- },
111
- variables: {
112
- title: "Variables",
113
- blurb: "Project and group CI/CD variable CRUD.",
114
- },
115
- dependency_proxy: {
116
- title: "Dependency Proxy",
117
- blurb: "Inspect and manage the GitLab dependency proxy cache settings, blob storage, and purge operations.",
118
- },
119
- vulnerabilities: {
120
- title: "Vulnerabilities",
121
- blurb: "AI-assisted vulnerability triage — list findings, inspect details, dismiss with reason, or confirm for remediation. Backed by the GitLab GraphQL API; requires GitLab Ultimate.",
122
- },
123
- orbit: {
124
- title: "GitLab Orbit",
125
- blurb: "Query the Orbit SDLC knowledge graph (Beta; Premium/Ultimate). Graph queries consume GitLab credits; schema, status, and tool listing are free.",
126
- },
127
- snippets: {
128
- title: "Snippets",
129
- blurb: "Snippet CRUD — list, get (with optional file content), create, update, and delete personal or project snippets.",
130
- },
131
- };
132
- const GROUP_ORDER = [
133
- "projects",
134
- "repositories",
135
- "branches",
136
- "groups",
137
- "merge_requests",
138
- "issues",
139
- "labels",
140
- "workitems",
141
- "ci",
142
- "pipelines",
143
- "milestones",
144
- "wiki",
145
- "releases",
146
- "tags",
147
- "users",
148
- "variables",
149
- "webhooks",
150
- "search",
151
- "dependency_proxy",
152
- "vulnerabilities",
153
- "orbit",
154
- "snippets",
155
- ];
156
- // Authoritative classification — uses the `readOnlyTools` set from
157
- // tools/registry.ts. That set is what the server itself consults to decide
158
- // which tools survive `GITLAB_READ_ONLY_MODE=true`, so the badges here
159
- // match real runtime behavior exactly (no prefix heuristics).
160
- function classify(name) {
161
- return readOnlyTools.has(name) ? "read" : "write";
162
- }
163
- function rwBadge(name) {
164
- return classify(name) === "read" ? "📖 Read-only" : "✏️ Writes";
165
- }
166
- function describeType(prop) {
167
- if (prop.enum) {
168
- return `enum (\`${prop.enum.map(String).join("` \\| `")}\`)`;
169
- }
170
- if (prop.anyOf || prop.oneOf) {
171
- const variants = (prop.anyOf || prop.oneOf || []).map(describeType).filter(Boolean);
172
- return variants.join(" \\| ") || "any";
173
- }
174
- if (Array.isArray(prop.type)) {
175
- return prop.type.join(" \\| ");
176
- }
177
- if (prop.type === "array") {
178
- return `array<${prop.items ? describeType(prop.items) : "any"}>`;
179
- }
180
- if (prop.format && prop.type) {
181
- return `${prop.type} (${prop.format})`;
182
- }
183
- return prop.type || "any";
184
- }
185
- function escapePipe(text) {
186
- return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
187
- }
188
- function paramTable(schema) {
189
- if (!schema || !schema.properties || Object.keys(schema.properties).length === 0) {
190
- return "_No parameters._";
191
- }
192
- const required = new Set(schema.required || []);
193
- const rows = ["| Parameter | Type | Required | Description |", "|---|---|:-:|---|"];
194
- for (const [name, prop] of Object.entries(schema.properties)) {
195
- const type = describeType(prop);
196
- const req = required.has(name) ? "✓" : "";
197
- const desc = escapePipe(prop.description || "");
198
- rows.push(`| \`${name}\` | ${type} | ${req} | ${desc} |`);
199
- }
200
- return rows.join("\n");
201
- }
202
- function toolSection(name, description, schema) {
203
- return [
204
- `### \`${name}\``,
205
- "",
206
- `*${rwBadge(name)}*`,
207
- "",
208
- description,
209
- "",
210
- "**Parameters**",
211
- "",
212
- paramTable(schema),
213
- "",
214
- ].join("\n");
215
- }
216
- function buildGroupPage(id, toolNames) {
217
- const meta = GROUP_META[id];
218
- const lines = [`# ${meta.title}`, "", meta.blurb, ""];
219
- const toggle = computeToggleNote(id);
220
- if (toggle) {
221
- lines.push(`!!! note "Feature toggle"`);
222
- lines.push(` ${toggle}`);
223
- lines.push("");
224
- }
225
- // Quick index
226
- lines.push("## Tools in this group");
227
- lines.push("");
228
- for (const name of toolNames) {
229
- lines.push(`- [\`${name}\`](#${name}) — ${rwBadge(name)}`);
230
- }
231
- lines.push("");
232
- lines.push("---");
233
- lines.push("");
234
- for (const name of toolNames) {
235
- const tool = allTools.find(t => t.name === name);
236
- if (!tool) {
237
- throw new Error(`Tool '${name}' referenced in toolset '${id}' but missing from allTools registry`);
238
- }
239
- lines.push(toolSection(name, tool.description, tool.inputSchema));
240
- }
241
- return lines.join("\n");
242
- }
243
- function buildToggleSection(groupedToolsList) {
244
- const grouped = groupedToolsList.filter(([id]) => GROUP_META[id]);
245
- const defaults = grouped.filter(([id]) => isDefaultToolset(id));
246
- const optins = grouped.filter(([id]) => !isDefaultToolset(id));
247
- const formatList = (items) => items
248
- .map(([id]) => {
249
- const slug = id.replace(/_/g, "-");
250
- const legacy = LEGACY_TOGGLE_ENV[id];
251
- const suffix = legacy ? ` (also \`${legacy}=true\`)` : "";
252
- return `[${GROUP_META[id].title}](${slug}.md)${suffix}`;
253
- })
254
- .join(", ");
255
- return [
256
- "| Status | Groups |",
257
- "|---|---|",
258
- `| **Default** — always exposed | ${formatList(defaults)} |`,
259
- `| **Opt-in** — must be enabled | ${formatList(optins)} |`,
260
- "",
261
- "**How to enable opt-in groups** (any one is sufficient):",
262
- "",
263
- "- `GITLAB_TOOLSETS=<group,…>` — comma-separated toolset IDs.",
264
- "- `GITLAB_TOOLSETS=all` — enables every group.",
265
- "- `GITLAB_TOOLS=<tool,…>` — enables individual tools regardless of group.",
266
- "- `USE_PIPELINE=true` / `USE_MILESTONE=true` / `USE_GITLAB_WIKI=true` —" +
267
- " legacy single-group flags (Pipelines, Milestones, Wiki only).",
268
- "- Call the `discover_tools` MCP tool at runtime to activate categories" +
269
- " for the current session.",
270
- ];
271
- }
272
- function buildIndexPage(groupedToolsList) {
273
- const lines = [
274
- "# Tools Reference",
275
- "",
276
- "Complete catalog of every tool the GitLab MCP server exposes.",
277
- "",
278
- "Which of these have an MCP `tools/call` test is tracked in",
279
- "[Tool invocation coverage](../reference/tool-coverage.md).",
280
- "",
281
- "> **Setup first** — if you haven't connected your Personal Access Token or",
282
- "> OAuth credentials yet, follow one of the [client setup guides](../clients/claude-code.md)",
283
- "> or read [Getting Started](../getting-started/index.md). Tools listed below",
284
- "> will be unavailable until the server is authenticated.",
285
- "",
286
- "## Feature toggles",
287
- "",
288
- "Toolsets are split into a **default** set (exposed automatically) and an",
289
- "**opt-in** set (must be explicitly enabled). The lists below are derived",
290
- "directly from `TOOLSET_DEFINITIONS` in",
291
- "[`tools/registry.ts`](https://github.com/zereight/gitlab-mcp/blob/main/tools/registry.ts).",
292
- "",
293
- ...buildToggleSection(groupedToolsList),
294
- "",
295
- "Permission modes control which tools are exposed:",
296
- "",
297
- "- `GITLAB_PERMISSION_MODE=readonly` — hides every write tool regardless of toggles.",
298
- "- `GITLAB_PERMISSION_MODE=modify` — allows create/update but blocks delete and teardown tools: every `delete_*` tool, `erase_pipeline_job`, `purge_dependency_proxy_cache`, the destructive teardown verbs `cancel_pipeline`, `cancel_pipeline_job`, `stop_environment`, `stop_stale_environments`, `unprotect_branch`, plus `push_files` `delete`/`move` actions.",
299
- "- `GITLAB_READ_ONLY_MODE=true` (deprecated) — same as `readonly`; prefer `GITLAB_PERMISSION_MODE=readonly`.",
300
- "",
301
- "The `modify` guard applies to typed tools (`tools/list` and `tools/call`) and to destructive mutations sent through `execute_graphql`: any top-level mutation field whose name contains a deletion verb (`delete`, `destroy`, `remove`, `prune`, `purge`, `erase`) or a teardown verb (`revoke`, `cancel`, `stop`, `terminate`, `unprotect`, `disable`, `deactivate`, `drop`, `unschedule`). See [Environment Variables](../configuration/environment-variables.md#gitlab_permission_mode).",
302
- "",
303
- "See [Environment Variables](../configuration/environment-variables.md)",
304
- "and [CLI Arguments](../getting-started/cli-arguments.md) for the full list.",
305
- "",
306
- "## Legend",
307
- "",
308
- "| Marker | Meaning |",
309
- "|---|---|",
310
- "| 📖 | **Read-only** — fetches data, does not modify GitLab state. Safe to invoke freely. |",
311
- "| ✏️ | **Writes** — creates, updates, or deletes data on GitLab. Confirm intent before running. |",
312
- "",
313
- "## Browse by group",
314
- "",
315
- "Each group has its own page with full parameter tables — click any tool name to jump to its details, or click the group title for the per-group view.",
316
- "",
317
- ];
318
- for (const [id, tools] of groupedToolsList) {
319
- const meta = GROUP_META[id];
320
- const slug = id.replace(/_/g, "-");
321
- lines.push(`### [${meta.title}](${slug}.md)`);
322
- lines.push("");
323
- lines.push(`${meta.blurb} *(${tools.length} tools)*`);
324
- lines.push("");
325
- const toggle = computeToggleNote(id);
326
- if (toggle) {
327
- lines.push(`> ${toggle}`);
328
- lines.push("");
329
- }
330
- lines.push("| Tool | What it does | R/W |");
331
- lines.push("|---|---|:-:|");
332
- for (const name of tools) {
333
- const tool = allTools.find(t => t.name === name);
334
- if (!tool)
335
- continue;
336
- const desc = escapePipe(tool.description);
337
- const marker = classify(name) === "read" ? "📖" : "✏️";
338
- lines.push(`| [\`${name}\`](${slug}.md#${name}) | ${desc} | ${marker} |`);
339
- }
340
- lines.push("");
341
- }
342
- lines.push("---");
343
- lines.push("");
344
- lines.push("## Argument schemas");
345
- lines.push("");
346
- lines.push("Each group page includes a parameter table per tool, generated from");
347
- lines.push("the authoritative Zod schemas in");
348
- lines.push("[`schemas.ts`](https://github.com/zereight/gitlab-mcp/blob/main/schemas.ts).");
349
- lines.push("For runtime schema inspection from a connected MCP client, call the");
350
- lines.push("`discover_tools` tool.");
351
- return lines.join("\n");
352
- }
353
- // --- Main -----------------------------------------------------------------
354
- function main() {
355
- mkdirSync(OUT_DIR, { recursive: true });
356
- // Clean previously generated per-group files (keep index.md until last)
357
- for (const file of readdirSync(OUT_DIR)) {
358
- const full = join(OUT_DIR, file);
359
- if (statSync(full).isFile() && file.endsWith(".md")) {
360
- unlinkSync(full);
361
- }
362
- }
363
- const grouped = [];
364
- const allCategorizedNames = new Set();
365
- for (const id of GROUP_ORDER) {
366
- const def = TOOLSET_DEFINITIONS.find(d => d.id === id);
367
- if (!def) {
368
- throw new Error(`Toolset '${id}' listed in GROUP_ORDER but missing from TOOLSET_DEFINITIONS — keep them in sync`);
369
- }
370
- // Preserve insertion order from TOOLSET_DEFINITIONS
371
- const tools = [...def.tools];
372
- for (const t of tools)
373
- allCategorizedNames.add(t);
374
- grouped.push([id, tools]);
375
- const page = buildGroupPage(id, tools);
376
- const slug = id.replace(/_/g, "-");
377
- writeFileSync(join(OUT_DIR, `${slug}.md`), page);
378
- console.log(`generated docs/tools/${slug}.md — ${tools.length} tools`);
379
- }
380
- // Tools NOT in any toolset (e.g., execute_graphql, discover_tools)
381
- const uncategorized = allTools.map(t => t.name).filter(n => !allCategorizedNames.has(n));
382
- if (uncategorized.length > 0) {
383
- const metaToggle = computeToggleNote("meta");
384
- const lines = [
385
- `# Meta & GraphQL`,
386
- "",
387
- "Tools the MCP exposes that aren't tied to a specific GitLab feature group — server diagnostics and the GraphQL escape hatch.",
388
- "",
389
- ];
390
- if (metaToggle) {
391
- lines.push(`!!! note "Feature toggle"`);
392
- lines.push(` ${metaToggle}`);
393
- lines.push("");
394
- }
395
- lines.push("## Tools in this group");
396
- lines.push("");
397
- for (const name of uncategorized) {
398
- lines.push(`- [\`${name}\`](#${name}) — ${rwBadge(name)}`);
399
- }
400
- lines.push("");
401
- lines.push("---");
402
- lines.push("");
403
- for (const name of uncategorized) {
404
- const tool = allTools.find(t => t.name === name);
405
- if (!tool) {
406
- throw new Error(`Uncategorized tool '${name}' missing from allTools registry`);
407
- }
408
- lines.push(toolSection(name, tool.description, tool.inputSchema));
409
- }
410
- writeFileSync(join(OUT_DIR, "meta.md"), lines.join("\n"));
411
- console.log(`generated docs/tools/meta.md — ${uncategorized.length} tools`);
412
- grouped.push(["meta", uncategorized]);
413
- GROUP_META["meta"] = {
414
- title: "Meta & GraphQL",
415
- blurb: "Server diagnostics, tool discovery, and the GraphQL escape hatch.",
416
- };
417
- }
418
- writeFileSync(join(OUT_DIR, "index.md"), buildIndexPage(grouped));
419
- console.log(`generated docs/tools/index.md`);
420
- // Emit nav fragment so you can paste it into mkdocs.yml if needed
421
- console.log("\n--- mkdocs.yml nav fragment ---");
422
- console.log(" - Tools:");
423
- console.log(" - Overview: tools/index.md");
424
- for (const [id] of grouped) {
425
- const meta = GROUP_META[id];
426
- const slug = id.replace(/_/g, "-");
427
- console.log(` - ${meta.title}: tools/${slug}.md`);
428
- }
429
- }
430
- main();