@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.
- package/README.ko.md +10 -4
- package/README.md +238 -233
- package/README.pt-BR.md +4 -4
- package/README.zh-CN.md +6 -4
- package/build/config.js +12 -4
- package/build/deprecated-env.js +47 -0
- package/build/index.js +456 -237
- package/build/oauth-device-flow.js +80 -0
- package/build/oauth.js +135 -18
- package/build/schemas.js +1 -1
- package/build/server/remote-http-stack.js +39 -0
- package/build/tools/discover-activation.js +38 -0
- package/build/tools/exposed-tools.js +35 -0
- package/build/tools/listed-tools.js +76 -0
- package/build/tools/registry.js +226 -57
- package/build/tools/tool-profile.js +66 -0
- package/build/utils/compact-tool-allowlist.js +14 -0
- package/build/utils/logger.js +34 -12
- package/build/utils/url.js +15 -0
- package/package.json +3 -3
- package/build/scripts/check-skill-sync.js +0 -143
- package/build/scripts/generate-server-card.js +0 -39
- package/build/scripts/generate-tool-coverage.js +0 -58
- package/build/scripts/generate-tool-docs.js +0 -430
- package/build/scripts/tool-coverage/coverage.js +0 -341
- package/build/scripts/tool-coverage/coverage.test.js +0 -142
- package/build/test/callback-proxy-tests.js +0 -321
- package/build/test/cli/args.test.js +0 -175
- package/build/test/cli/boolean-flags.test.js +0 -106
- package/build/test/cli/compact-mcp-result.test.js +0 -93
- package/build/test/cli/exposure.test.js +0 -104
- package/build/test/cli/load-config-snapshot.js +0 -5
- package/build/test/cli/output.test.js +0 -74
- package/build/test/cli/replay-command.test.js +0 -37
- package/build/test/cli/router.test.js +0 -183
- package/build/test/client-pool-test.js +0 -109
- package/build/test/clients/client.js +0 -34
- package/build/test/clients/custom-header-client.js +0 -122
- package/build/test/clients/sse-client.js +0 -95
- package/build/test/clients/stdio-client.js +0 -113
- package/build/test/clients/streamable-http-client.js +0 -95
- package/build/test/config-allowed-groups.test.js +0 -97
- package/build/test/create-commit-actions.test.js +0 -102
- package/build/test/draft-note-schema.test.js +0 -44
- package/build/test/dynamic-api-url-allowlist.test.js +0 -121
- package/build/test/dynamic-api-url-test.js +0 -304
- package/build/test/dynamic-routing-tests.js +0 -495
- package/build/test/get-merge-request-discussion-schema.test.js +0 -30
- package/build/test/gitlab-artifact-entry-schema.test.js +0 -44
- package/build/test/gitlab-diff-schema.test.js +0 -36
- package/build/test/gitlab-pipeline-schedule-schema.test.js +0 -32
- package/build/test/gitlab-tag-schema.test.js +0 -51
- package/build/test/group-milestone-schema.test.js +0 -45
- package/build/test/mcp-oauth-tests.js +0 -905
- package/build/test/mcp-server-name.test.js +0 -87
- package/build/test/merge-merge-request-schema.test.js +0 -75
- package/build/test/multi-server-test.js +0 -190
- package/build/test/no-proxy-integration-test.js +0 -183
- package/build/test/no-proxy-test.js +0 -149
- package/build/test/nullable-gitlab-response-fields.test.js +0 -37
- package/build/test/nullish-tool-arguments-schema.test.js +0 -118
- package/build/test/oauth-device-flow-tests.js +0 -450
- package/build/test/oauth-startup.test.js +0 -204
- package/build/test/oauth-tests.js +0 -428
- package/build/test/path-segment-encoding.test.js +0 -98
- package/build/test/remote-auth-simple-test.js +0 -277
- package/build/test/remote-auth-tests.js +0 -314
- package/build/test/response-masking.test.js +0 -445
- package/build/test/schema-tests.js +0 -1679
- package/build/test/server/metrics.test.js +0 -47
- package/build/test/server/request-helpers.test.js +0 -48
- package/build/test/server/version.test.js +0 -13
- package/build/test/sse-auth-guard.test.js +0 -96
- package/build/test/sse-session-limits.test.js +0 -273
- package/build/test/stateless/callback-proxy.test.js +0 -510
- package/build/test/stateless/client-id.test.js +0 -177
- package/build/test/stateless/codec.test.js +0 -328
- package/build/test/stateless/config-ttl.test.js +0 -149
- package/build/test/stateless/consumed-proxy-code-cache.test.js +0 -150
- package/build/test/stateless/session-id-integration.test.js +0 -675
- package/build/test/stateless/session-id.test.js +0 -157
- package/build/test/streamable-http-concurrent-session.test.js +0 -171
- package/build/test/streamable-http-dns-rebinding.test.js +0 -185
- package/build/test/streamable-http-sse-stream.test.js +0 -350
- package/build/test/streamable-http-static-token-auth.test.js +0 -157
- package/build/test/streamable-http-unauthenticated-discovery.test.js +0 -207
- package/build/test/test-all-transport-server.js +0 -255
- package/build/test/test-auth-retry.js +0 -195
- package/build/test/test-blank-filters.js +0 -156
- package/build/test/test-ci-catalog.js +0 -177
- package/build/test/test-ci-lint.js +0 -191
- package/build/test/test-ci-variables.js +0 -309
- package/build/test/test-create-repository.js +0 -120
- package/build/test/test-dependency-proxy.js +0 -195
- package/build/test/test-deployment-tools.js +0 -463
- package/build/test/test-download-attachment.js +0 -190
- package/build/test/test-dynamic-project-scope.js +0 -524
- package/build/test/test-empty-toolsets.js +0 -535
- package/build/test/test-get-file-blame.js +0 -145
- package/build/test/test-get-file-contents.js +0 -128
- package/build/test/test-geteffectiveprojectid.js +0 -812
- package/build/test/test-health-check.js +0 -115
- package/build/test/test-issue-description-patch.js +0 -256
- package/build/test/test-job-artifacts.js +0 -275
- package/build/test/test-json-schema.js +0 -166
- package/build/test/test-list-group-members.js +0 -150
- package/build/test/test-list-group-merge-requests.js +0 -165
- package/build/test/test-list-issues.js +0 -170
- package/build/test/test-list-merge-requests.js +0 -201
- package/build/test/test-list-milestones.js +0 -145
- package/build/test/test-list-project-members.js +0 -132
- package/build/test/test-merge-request-approval-state-tools.js +0 -213
- package/build/test/test-merge-request-approvals.js +0 -187
- package/build/test/test-merge-request-pipelines.js +0 -106
- package/build/test/test-mr-diffs-filter.js +0 -132
- package/build/test/test-mr-file-diffs.js +0 -251
- package/build/test/test-oauth-proxy-rate-limit.js +0 -133
- package/build/test/test-orbit.js +0 -155
- package/build/test/test-permission-mode.js +0 -424
- package/build/test/test-pipeline-schedules.js +0 -482
- package/build/test/test-protected-branches.js +0 -174
- package/build/test/test-remote-downloads.js +0 -510
- package/build/test/test-search-code.js +0 -275
- package/build/test/test-snippets.js +0 -735
- package/build/test/test-tags.js +0 -220
- package/build/test/test-todos.js +0 -195
- package/build/test/test-token-optimizations.js +0 -691
- package/build/test/test-toolset-filtering.js +0 -499
- package/build/test/test-update-issue-slim.js +0 -141
- package/build/test/test-update-project.js +0 -112
- package/build/test/test-upload-markdown.js +0 -206
- package/build/test/test-vulnerabilities.js +0 -399
- package/build/test/test-webhooks.js +0 -218
- package/build/test/tool-description-quality.test.js +0 -50
- package/build/test/utils/bulk-publish-options.test.js +0 -32
- package/build/test/utils/download-token.test.js +0 -35
- package/build/test/utils/forwarded-public-base-url.test.js +0 -46
- package/build/test/utils/gitlab-instance-version.test.js +0 -32
- package/build/test/utils/gitlab-version-gate.test.js +0 -74
- package/build/test/utils/graphql-query.test.js +0 -168
- package/build/test/utils/helpers-path.test.js +0 -104
- package/build/test/utils/jmespath-tool-result.test.js +0 -91
- package/build/test/utils/masking.test.js +0 -384
- package/build/test/utils/merge-request-position.test.js +0 -57
- package/build/test/utils/mock-gitlab-server.js +0 -614
- package/build/test/utils/proxy-client-ip.test.js +0 -28
- package/build/test/utils/redact-sensitive.test.js +0 -32
- package/build/test/utils/safe-redirect-fetch.test.js +0 -463
- package/build/test/utils/server-launcher.js +0 -231
- package/build/test/utils/tool-args.test.js +0 -210
- package/build/test/utils/url.test.js +0 -128
- package/build/test/utils/version-check.test.js +0 -239
- package/build/test/utils/wiki-title.test.js +0 -21
- package/build/test-note.js +0 -54
- 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();
|