From a18c65ecde0f29989bff22502465270742f15faa Mon Sep 17 00:00:00 2001 From: Csaba Kertesz Date: Mon, 27 Apr 2026 22:19:02 +0200 Subject: [PATCH 1/6] feat(mcp): selectively disable tools to reduce initial schema size --- src/infrastructure/config.ts | 1 + src/mcp/server.ts | 13 +++++- src/mcp/tool-registry.ts | 25 ++++++++--- src/types.ts | 1 + tests/unit/config.test.ts | 1 + tests/unit/mcp.test.ts | 87 ++++++++++++++++++++++++++++++++++++ 6 files changed, 121 insertions(+), 7 deletions(-) diff --git a/src/infrastructure/config.ts b/src/infrastructure/config.ts index 523c16af..46efb1a6 100644 --- a/src/infrastructure/config.ts +++ b/src/infrastructure/config.ts @@ -147,6 +147,7 @@ export const DEFAULTS = { implementations: 50, interfaces: 50, }, + disabledTools: [] as string[], }, } satisfies CodegraphConfig; diff --git a/src/mcp/server.ts b/src/mcp/server.ts index ae44c001..2fe5fd7a 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -163,11 +163,17 @@ function createCallToolHandler( customDbPath: string | undefined, allowedRepos: string[] | undefined, getQueries: () => Promise, + enabledToolNames: Set, ) { return async (request: any) => { const { name, arguments: args } = request.params; try { validateMultiRepoAccess(multiRepo, name, args); + + if (!enabledToolNames.has(name)) { + return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }; + } + const dbPath = await resolveDbPath(customDbPath, args, allowedRepos); const toolEntry = TOOL_HANDLERS.get(name); @@ -209,6 +215,9 @@ export async function startMCPServer( // Apply config-based MCP page-size overrides const config = options.config || loadConfig(); initMcpDefaults(config.mcp?.defaults ? { ...config.mcp.defaults } : undefined); + const disabledTools = config.mcp?.disabledTools ? [...config.mcp.disabledTools] : undefined; + const enabledTools = buildToolList(multiRepo, disabledTools); + const enabledToolNames = new Set(enabledTools.map((tool) => tool.name)); const { Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema } = await loadMCPSdk(); @@ -225,12 +234,12 @@ export async function startMCPServer( ); server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools: buildToolList(multiRepo), + tools: enabledTools, })); server.setRequestHandler( CallToolRequestSchema, - createCallToolHandler(multiRepo, customDbPath, allowedRepos, getQueries), + createCallToolHandler(multiRepo, customDbPath, allowedRepos, getQueries, enabledToolNames), ); const transport = new (StdioServerTransport as any)(); diff --git a/src/mcp/tool-registry.ts b/src/mcp/tool-registry.ts index 07b71541..3f102e26 100644 --- a/src/mcp/tool-registry.ts +++ b/src/mcp/tool-registry.ts @@ -29,6 +29,14 @@ const PAGINATION_PROPS: Record = { offset: { type: 'number', description: 'Skip this many results (pagination, default: 0)' }, }; +function normalizeToolName(name: string): string { + return name.trim().toLowerCase().replace(/^codegraph\d+_/, ''); +} + +function buildDisabledToolSet(disabledTools?: string[]): Set { + return new Set((disabledTools || []).map((name) => normalizeToolName(name)).filter(Boolean)); +} + const BASE_TOOLS: ToolSchema[] = [ { name: 'query', @@ -849,18 +857,25 @@ const LIST_REPOS_TOOL: ToolSchema = { /** * Build the tool list based on multi-repo mode. */ -export function buildToolList(multiRepo: boolean): ToolSchema[] { - if (!multiRepo) return BASE_TOOLS; - return [ - ...BASE_TOOLS.map((tool) => ({ +export function buildToolList(multiRepo: boolean, disabledTools?: string[]): ToolSchema[] { + const disabled = buildDisabledToolSet(disabledTools); + const includeTool = (tool: ToolSchema): boolean => !disabled.has(normalizeToolName(tool.name)); + const baseTools = BASE_TOOLS.filter(includeTool); + + if (!multiRepo) return baseTools; + + const tools: ToolSchema[] = [ + ...baseTools.map((tool) => ({ ...tool, inputSchema: { ...tool.inputSchema, properties: { ...tool.inputSchema.properties, ...REPO_PROP }, }, })), - LIST_REPOS_TOOL, ]; + + if (includeTool(LIST_REPOS_TOOL)) tools.push(LIST_REPOS_TOOL); + return tools; } // Backward-compatible export: full multi-repo tool list diff --git a/src/types.ts b/src/types.ts index b5614c73..ebfe1368 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1201,6 +1201,7 @@ export interface CodegraphConfig { mcp: { defaults: McpDefaults; + disabledTools: string[]; }; } diff --git a/tests/unit/config.test.ts b/tests/unit/config.test.ts index a2b50401..ebb87be4 100644 --- a/tests/unit/config.test.ts +++ b/tests/unit/config.test.ts @@ -137,6 +137,7 @@ describe('DEFAULTS', () => { it('has mcp defaults', () => { expect(DEFAULTS.mcp.defaults.list_functions).toBe(100); expect(DEFAULTS.mcp.defaults.fn_impact).toBe(5); + expect(DEFAULTS.mcp.disabledTools).toEqual([]); }); }); diff --git a/tests/unit/mcp.test.ts b/tests/unit/mcp.test.ts index c3a1a147..752c6935 100644 --- a/tests/unit/mcp.test.ts +++ b/tests/unit/mcp.test.ts @@ -255,6 +255,20 @@ describe('buildToolList', () => { expect(tool.inputSchema.properties.repo.type).toBe('string'); } }); + + it('removes disabled tools from schema in single-repo mode', () => { + const tools = buildToolList(false, ['execution_flow', 'module_map']); + const names = tools.map((t) => t.name); + expect(names).not.toContain('execution_flow'); + expect(names).not.toContain('module_map'); + }); + + it('supports prefixed disabled tool names and can disable list_repos', () => { + const tools = buildToolList(true, ['codegraph2_module_map', 'list_repos']); + const names = tools.map((t) => t.name); + expect(names).not.toContain('module_map'); + expect(names).not.toContain('list_repos'); + }); }); // ─── startMCPServer handler logic ──────────────────────────────────── @@ -335,6 +349,79 @@ describe('startMCPServer handler dispatch', () => { expect(unknownResult.content[0].text).toContain('Unknown tool'); }); + it('applies config.mcp.disabledTools to list and call handlers', async () => { + const handlers = {}; + + vi.doMock('@modelcontextprotocol/sdk/server/index.js', () => ({ + Server: class MockServer { + setRequestHandler(name, handler) { + handlers[name] = handler; + } + async connect() {} + }, + })); + vi.doMock('@modelcontextprotocol/sdk/server/stdio.js', () => ({ + StdioServerTransport: class MockTransport {}, + })); + vi.doMock('@modelcontextprotocol/sdk/types.js', () => ({ + ListToolsRequestSchema: 'tools/list', + CallToolRequestSchema: 'tools/call', + })); + + vi.doMock('../../src/infrastructure/config.js', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: vi.fn(() => ({ + ...actual.DEFAULTS, + mcp: { + ...actual.DEFAULTS.mcp, + disabledTools: ['module_map'], + }, + })), + }; + }); + + vi.doMock('../../src/domain/queries.js', () => ({ + EVERY_SYMBOL_KIND: [], + EVERY_EDGE_KIND: [], + VALID_ROLES: [], + diffImpactMermaid: vi.fn(), + impactAnalysisData: vi.fn(() => ({ file: 'test', sources: [] })), + moduleMapData: vi.fn(() => ({ topNodes: [], stats: {} })), + fileDepsData: vi.fn(() => ({ file: 'test', results: [] })), + fnDepsData: vi.fn(() => ({ name: 'test', results: [] })), + fnImpactData: vi.fn(() => ({ name: 'test', results: [] })), + contextData: vi.fn(() => ({ name: 'test', results: [] })), + childrenData: vi.fn(() => ({ name: 'test', results: [] })), + explainData: vi.fn(() => ({ target: 'test', kind: 'function', results: [] })), + exportsData: vi.fn(() => ({ + file: 'test', + results: [], + reexports: [], + totalExported: 0, + totalInternal: 0, + })), + whereData: vi.fn(() => ({ target: 'test', mode: 'symbol', results: [] })), + diffImpactData: vi.fn(() => ({ changedFiles: 0, affectedFunctions: [] })), + listFunctionsData: vi.fn(() => ({ count: 0, functions: [] })), + rolesData: vi.fn(() => ({ count: 0, summary: {}, symbols: [] })), + pathData: vi.fn(() => ({ from: 'a', to: 'b', found: false })), + })); + + const { startMCPServer } = await import('../../src/mcp/index.js'); + await startMCPServer('/tmp/test.db'); + + const toolsList = await handlers['tools/list'](); + expect(toolsList.tools.map((t) => t.name)).not.toContain('module_map'); + + const result = await handlers['tools/call']({ + params: { name: 'module_map', arguments: {} }, + }); + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('Unknown tool: module_map'); + }); + it('dispatches query deps mode to fnDepsData with options', async () => { const handlers = {}; From 6f46e8f2cc899bc0a3adbb7ba58a976a7c61936c Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Wed, 29 Apr 2026 01:54:36 -0600 Subject: [PATCH 2/6] fix(mcp): address greptile review feedback - Reorder createCallToolHandler so the disabled-tool check runs before validateMultiRepoAccess, giving config-disabled tools a consistent "Unknown tool" response regardless of multi-repo state. - Mark CodegraphConfig.mcp.disabledTools optional to match the runtime guard (config.mcp?.disabledTools) and tolerate partial configs. - Update list_repos single-repo test to reflect the unified "Unknown tool" path. - Apply biome formatting to normalizeToolName. --- src/mcp/server.ts | 4 ++-- src/mcp/tool-registry.ts | 5 ++++- src/types.ts | 2 +- tests/unit/mcp.test.ts | 3 +-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 2fe5fd7a..1d454ccd 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -168,12 +168,12 @@ function createCallToolHandler( return async (request: any) => { const { name, arguments: args } = request.params; try { - validateMultiRepoAccess(multiRepo, name, args); - if (!enabledToolNames.has(name)) { return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }; } + validateMultiRepoAccess(multiRepo, name, args); + const dbPath = await resolveDbPath(customDbPath, args, allowedRepos); const toolEntry = TOOL_HANDLERS.get(name); diff --git a/src/mcp/tool-registry.ts b/src/mcp/tool-registry.ts index 3f102e26..d5d9dac7 100644 --- a/src/mcp/tool-registry.ts +++ b/src/mcp/tool-registry.ts @@ -30,7 +30,10 @@ const PAGINATION_PROPS: Record = { }; function normalizeToolName(name: string): string { - return name.trim().toLowerCase().replace(/^codegraph\d+_/, ''); + return name + .trim() + .toLowerCase() + .replace(/^codegraph\d+_/, ''); } function buildDisabledToolSet(disabledTools?: string[]): Set { diff --git a/src/types.ts b/src/types.ts index ebfe1368..4eff9287 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1201,7 +1201,7 @@ export interface CodegraphConfig { mcp: { defaults: McpDefaults; - disabledTools: string[]; + disabledTools?: string[]; }; } diff --git a/tests/unit/mcp.test.ts b/tests/unit/mcp.test.ts index 752c6935..d5990b80 100644 --- a/tests/unit/mcp.test.ts +++ b/tests/unit/mcp.test.ts @@ -1101,8 +1101,7 @@ describe('startMCPServer handler dispatch', () => { params: { name: 'list_repos', arguments: {} }, }); expect(result.isError).toBe(true); - expect(result.content[0].text).toContain('Multi-repo access is disabled'); - expect(result.content[0].text).toContain('--multi-repo'); + expect(result.content[0].text).toContain('Unknown tool: list_repos'); }); it('tools/list in single-repo mode has no repo property and no list_repos', async () => { From fd9ee61ab973ccc6f58fb5f6c9ddba76d4807470 Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Wed, 29 Apr 2026 01:59:00 -0600 Subject: [PATCH 3/6] docs(mcp): document mcp.disabledTools config - README: short subsection under Configuration with example and link. - docs/guides/mcp-tool-filtering.md: extensive guide covering motivation, name normalization, runtime behavior, full tool catalog, and recipes. --- README.md | 14 +++ docs/guides/mcp-tool-filtering.md | 199 ++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 docs/guides/mcp-tool-filtering.md diff --git a/README.md b/README.md index 4d4f2d80..a93d0020 100644 --- a/README.md +++ b/README.md @@ -833,6 +833,20 @@ The command is split on whitespace and executed with `execFileSync` (no shell in Works with any secret manager: 1Password CLI (`op`), Bitwarden (`bw`), `pass`, HashiCorp Vault, macOS Keychain (`security`), AWS Secrets Manager, etc. +### MCP tool filtering + +Codegraph's MCP server exposes 30+ tools by default. For models with a small context window, you can shrink the schema by disabling tools you don't use: + +```json +{ + "mcp": { + "disabledTools": ["execution_flow", "sequence", "communities", "co_changes"] + } +} +``` + +Names are matched case-insensitively and a leading `codegraph_` prefix (e.g. `codegraph2_module_map`) is stripped before comparison. Disabled tools are removed from `tools/list` and any `tools/call` invocation returns `Unknown tool: `. See **[docs/guides/mcp-tool-filtering.md](docs/guides/mcp-tool-filtering.md)** for the full tool catalog and recipes. + ## 📖 Programmatic API Codegraph also exports a full API for use in your own tools: diff --git a/docs/guides/mcp-tool-filtering.md b/docs/guides/mcp-tool-filtering.md new file mode 100644 index 00000000..d54af73a --- /dev/null +++ b/docs/guides/mcp-tool-filtering.md @@ -0,0 +1,199 @@ +# MCP Tool Filtering + +The codegraph MCP server exposes 30+ tools that AI agents can call. Every tool's input schema is sent to the model on the first turn, and large schemas eat tokens that could go toward actual work. This guide shows how to trim the toolset to the slice you actually use. + +--- + +## When to filter + +Disable tools when: + +- **You're using a small-context model.** Loading 30+ tool schemas can consume thousands of tokens before the model sees a single user message. Cutting the list to the 5–10 you actually call is the easiest single optimization. +- **Your workflow only uses a subset.** A code-review agent may only need `diff_impact`, `fn_impact`, and `context`; a refactor agent may only need `query`, `where`, and `audit`. Hiding the rest reduces noise in tool selection. +- **You want to lock down capabilities.** Disabling `export_graph` or `semantic_search` can keep agents on the rails for narrow tasks. +- **You want consistent behavior across modes.** Tools that are filtered (by config or by single-repo mode) all return the same `Unknown tool: ` response, so prompts that handle that response work everywhere. + +Don't filter when you don't have a token-budget problem — a missing tool is a silent capability gap. Start permissive, then trim once you've measured. + +--- + +## Configuration + +Add `mcp.disabledTools` to your `.codegraphrc.json`: + +```json +{ + "mcp": { + "disabledTools": [ + "execution_flow", + "sequence", + "communities", + "co_changes", + "code_owners" + ] + } +} +``` + +Reload your MCP client (or restart `codegraph mcp`) for changes to take effect. The list is read once when the server starts. + +### Name normalization + +Names are matched after the following normalization, applied to both the config entries and the registered tool names: + +1. Trim surrounding whitespace. +2. Lowercase. +3. Strip a leading `codegraph_` prefix (e.g. `codegraph2_module_map` → `module_map`). + +This means all of the following entries are equivalent and disable the same tool: + +```json +["module_map", "Module_Map", " module_map ", "codegraph2_module_map"] +``` + +The `codegraph_` prefix exists because some MCP clients namespace tools per-server when multiple servers are connected — copying a tool name straight from the client UI still works. + +### Behavior at runtime + +- **`tools/list`** — disabled tools do not appear in the response. The model never sees their schemas. +- **`tools/call`** — invoking a disabled tool returns `{ isError: true, content: [{ type: 'text', text: 'Unknown tool: ' }] }`. This is the same response a truly unknown tool name produces. +- **`list_repos` in single-repo mode** — `list_repos` is only registered when the server is started with `--multi-repo` or `--repos`. In single-repo mode it is filtered out by default and produces the same `Unknown tool: list_repos` response, regardless of `disabledTools`. + +--- + +## Tool catalog + +All tool names below match the values you put in `disabledTools`. Group descriptions are advisory — disable in any combination you like. + +### Navigation & lookup + +| Tool | Purpose | +|------|---------| +| `query` | Callers/callees with transitive chain, or shortest path between two symbols. | +| `path` | Shortest call path between two symbols. | +| `where` | Locate where a symbol lives. | +| `file_deps` | Per-file dependency listing. | +| `file_exports` | Per-symbol export consumers. | +| `brief` | Short summary of a symbol. | +| `context` | Source + dependencies + callers for a symbol. | +| `symbol_children` | Sub-declarations (parameters, properties, constants). | +| `list_functions` | Enumerate functions in the graph. | + +### Impact & change analysis + +| Tool | Purpose | +|------|---------| +| `impact_analysis` | Blast radius for a file. | +| `fn_impact` | Blast radius for a function. | +| `diff_impact` | Impact of a diff (staged, branch, or refs). | +| `branch_compare` | Structural diff between two refs. | + +### Structure & health + +| Tool | Purpose | +|------|---------| +| `module_map` | Most-connected files / module overview. | +| `structure` | Directory tree with cohesion scores. | +| `find_cycles` | Circular dependency detection. | +| `node_roles` | Classify symbols (core, dead, hub, etc.). | +| `complexity` | Per-function complexity metrics. | +| `audit` | Composite report: structure + impact + health. | +| `triage` | Risk-ranked audit priority queue. | +| `check` | CI gate predicates (cycles, complexity, boundaries). | +| `communities` | Louvain community detection. | +| `co_changes` | Files that historically change together. | +| `code_owners` | CODEOWNERS-based ownership queries. | + +### Visualization & export + +| Tool | Purpose | +|------|---------| +| `export_graph` | Export the graph (DOT, Mermaid, GraphML, Neo4j). | +| `sequence` | Sequence diagram data (BFS traversal). | +| `execution_flow` | Execution flow data for a function. | +| `cfg` | Control-flow graph for a function. | +| `dataflow` | Intraprocedural dataflow analysis. | + +### Search & types + +| Tool | Purpose | +|------|---------| +| `semantic_search` | Embedding-backed semantic search (requires `codegraph embed`). | +| `ast_query` | Query the raw AST by node kind. | +| `implementations` | Find implementations of an interface/trait. | +| `interfaces` | Find interfaces/traits a class/struct satisfies. | + +### Batching + +| Tool | Purpose | +|------|---------| +| `batch_query` | Run multiple queries in one call. | + +### Multi-repo only + +| Tool | Purpose | +|------|---------| +| `list_repos` | List repositories in the registry. Available only when the server is started with `--multi-repo` or `--repos`. | + +--- + +## Recipes + +### Minimal "code-review" toolset + +Disable everything except impact and context: + +```json +{ + "mcp": { + "disabledTools": [ + "query", "path", "file_deps", "file_exports", "brief", + "module_map", "structure", "find_cycles", "node_roles", + "complexity", "audit", "triage", "check", + "communities", "co_changes", "code_owners", + "export_graph", "sequence", "execution_flow", "cfg", "dataflow", + "semantic_search", "ast_query", "implementations", "interfaces", + "batch_query", "list_functions", "symbol_children", "branch_compare" + ] + } +} +``` + +The remaining tools — `diff_impact`, `fn_impact`, `impact_analysis`, `context`, `where` — give an agent everything it needs to assess a PR. + +### Drop the heavy visualizers + +If your agent never produces diagrams, the visualization tools are pure schema overhead: + +```json +{ + "mcp": { + "disabledTools": ["export_graph", "sequence", "execution_flow", "cfg", "dataflow"] + } +} +``` + +### Drop semantic search if you haven't run `codegraph embed` + +`semantic_search` is wired up unconditionally but only works after embeddings have been built. Disabling it removes a tool that would otherwise return errors: + +```json +{ + "mcp": { + "disabledTools": ["semantic_search"] + } +} +``` + +--- + +## Verifying + +After editing `.codegraphrc.json`, restart the server and ask the client to refresh its tool list. Then call `tools/list` from your MCP client and confirm the disabled names are absent — that response is the source of truth for what the model will see. + +--- + +## See also + +- [`mcp.disabledTools` in the README](../../README.md#mcp-tool-filtering) — short reference. +- [`src/mcp/tool-registry.ts`](../../src/mcp/tool-registry.ts) — source of truth for tool names and schemas. From eee2b527a9c3f940a77b7d7697a2125143a26017 Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Wed, 29 Apr 2026 02:50:29 -0600 Subject: [PATCH 4/6] docs(config): replace mcp-only guide with full config reference Per review feedback, the previous guide was too narrowly scoped. Replace with docs/guides/configuration.md covering every key in DEFAULTS: file selection, build, query, embeddings, llm, search, ci, manifesto, check, coChange, analysis, community, structure, risk, display, mcp. README points to the new guide and keeps the short MCP tool-filtering example for the PR's headline feature. --- README.md | 4 +- docs/guides/configuration.md | 409 ++++++++++++++++++++++++++++++ docs/guides/mcp-tool-filtering.md | 199 --------------- 3 files changed, 411 insertions(+), 201 deletions(-) create mode 100644 docs/guides/configuration.md delete mode 100644 docs/guides/mcp-tool-filtering.md diff --git a/README.md b/README.md index a93d0020..e190318f 100644 --- a/README.md +++ b/README.md @@ -773,7 +773,7 @@ Copy `.github/workflows/codegraph-impact.yml` to your repo, and every PR will ge ## 🛠️ Configuration -Create a `.codegraphrc.json` in your project root to customize behavior: +Create a `.codegraphrc.json` in your project root to customize behavior. The snippets below cover the most-used keys — see **[docs/guides/configuration.md](docs/guides/configuration.md)** for the full reference (every group, every key, every default). ```json { @@ -845,7 +845,7 @@ Codegraph's MCP server exposes 30+ tools by default. For models with a small con } ``` -Names are matched case-insensitively and a leading `codegraph_` prefix (e.g. `codegraph2_module_map`) is stripped before comparison. Disabled tools are removed from `tools/list` and any `tools/call` invocation returns `Unknown tool: `. See **[docs/guides/mcp-tool-filtering.md](docs/guides/mcp-tool-filtering.md)** for the full tool catalog and recipes. +Names are matched case-insensitively and a leading `codegraph_` prefix (e.g. `codegraph2_module_map`) is stripped before comparison. Disabled tools are removed from `tools/list` and any `tools/call` invocation returns `Unknown tool: `. See **[docs/guides/configuration.md#mcp-tool-filtering](docs/guides/configuration.md#mcp-tool-filtering)** for the full tool catalog, and the rest of that guide for every other config option. ## 📖 Programmatic API diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md new file mode 100644 index 00000000..e97d5ee5 --- /dev/null +++ b/docs/guides/configuration.md @@ -0,0 +1,409 @@ +# Configuration Reference + +All codegraph behavior is configured through a single file at the project root. This guide covers every option, grouped by concern. The authoritative source is `DEFAULTS` in [`src/infrastructure/config.ts`](../../src/infrastructure/config.ts) — if anything here drifts, the code wins. + +## File location + +Codegraph looks for, in order: + +1. `.codegraphrc.json` +2. `.codegraphrc` +3. `codegraph.config.json` + +The first one found is used. All values are optional — anything you don't set falls back to the default. + +## Merge semantics + +Config is deep-merged with defaults. Partial overrides preserve sibling keys: + +```json +{ "build": { "incremental": false } } +``` + +Leaves `build.dbPath`, `build.driftThreshold`, etc. at their defaults. Arrays are replaced wholesale, not concatenated. + +## Top-level shorthand + +`excludeTests` may be set at the top level as a shorthand for `query.excludeTests`. If both are present, `query.excludeTests` wins. + +```json +{ "excludeTests": true } +``` + +--- + +## File selection + +Controls which files codegraph parses. + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `include` | `string[]` | `[]` | Glob patterns to include. Empty means "everything not excluded". | +| `exclude` | `string[]` | `[]` | Glob patterns to skip. | +| `ignoreDirs` | `string[]` | `[]` | Directory names to skip entirely (added to a built-in list of `node_modules`, `.git`, `dist`, etc.). | +| `extensions` | `string[]` | `[]` | File extensions to parse. Empty means "use the language registry's default extensions". | +| `aliases` | `Record` | `{}` | Path alias map for import resolution (e.g. `"@/": "./src/"`). | + +```json +{ + "include": ["src/**", "lib/**"], + "exclude": ["**/*.test.js", "**/__mocks__/**"], + "ignoreDirs": ["fixtures", "vendor"], + "aliases": { "@/": "./src/", "@utils/": "./src/utils/" } +} +``` + +--- + +## Build (`build`) + +Controls graph construction. + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `incremental` | `boolean` | `true` | Reuse cached file hashes; only re-parse changed files. Set `false` to force a full rebuild. | +| `dbPath` | `string` | `".codegraph/graph.db"` | Path to the SQLite database, relative to the project root. | +| `driftThreshold` | `number` | `0.2` | Fraction (0–1). If incremental rebuild changes node or edge counts by more than this, codegraph warns and suggests `--no-incremental`. | +| `smallFilesThreshold` | `number` | `5` | When ≤ this many files change in an incremental build, codegraph takes faster code paths (skips full rebuilds of structure metrics, scoped barrel re-parsing, JS fallback for inserts). | + +--- + +## Query defaults (`query`) + +Defaults applied to graph queries when the CLI flag is omitted. + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `defaultDepth` | `number` | `3` | Default transitive depth for callers/callees queries. | +| `defaultLimit` | `number` | `20` | Default max results per query. | +| `excludeTests` | `boolean` | `false` | When `true`, commands exclude test/spec files by default. Pass `--include-tests` on any command to override per-invocation. | + +--- + +## Embeddings (`embeddings`) + +Controls the local embedding model used by `codegraph embed` and `codegraph search`. + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `model` | `string` | `"nomic-v1.5"` | Model registry key (see `src/domain/search/models.ts`). Common options: `"nomic-v1.5"`, `"bge-large"`, `"nomic"`. | +| `llmProvider` | `string \| null` | `null` | Optional LLM provider for query expansion. `null` disables it. | + +--- + +## LLM credentials (`llm`) + +Used by features that call out to a chat-completion API (e.g. query expansion). Codegraph never hardcodes a provider — you pick one. + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `provider` | `string \| null` | `null` | Provider name (e.g. `"openai"`, `"anthropic"`). | +| `model` | `string \| null` | `null` | Model identifier passed to the provider. | +| `baseUrl` | `string \| null` | `null` | Override the provider's base URL (for compatible proxies, local servers, etc.). | +| `apiKey` | `string \| null` | `null` | Plaintext API key. Prefer `apiKeyCommand` or env vars over this. | +| `apiKeyCommand` | `string \| null` | `null` | Shell-out command that prints the key to stdout. Split on whitespace and run via `execFileSync` (no shell — `$(...)`, pipes, globs, and variable expansion are not supported). 10s timeout, 64 KB max output. | + +Resolution order (first non-empty wins): `apiKeyCommand` output → `CODEGRAPH_LLM_API_KEY` env var → `apiKey` field. + +```json +{ + "llm": { + "provider": "openai", + "model": "gpt-4o-mini", + "apiKeyCommand": "op read op://vault/openai/api-key" + } +} +``` + +Works with any single-binary secret manager: 1Password CLI (`op`), Bitwarden (`bw`), `pass`, Vault (`vault`), macOS Keychain (`security`), AWS Secrets Manager (`aws secretsmanager get-secret-value ...`), etc. + +### Environment overrides + +These env vars override the corresponding `llm.*` fields when set: + +- `CODEGRAPH_LLM_PROVIDER` → `llm.provider` +- `CODEGRAPH_LLM_MODEL` → `llm.model` +- `CODEGRAPH_LLM_API_KEY` → `llm.apiKey` + +--- + +## Semantic search (`search`) + +Knobs for `codegraph search` and the `semantic_search` MCP tool. + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `defaultMinScore` | `number` | `0.2` | Minimum cosine similarity for a match to be returned. | +| `rrfK` | `number` | `60` | Reciprocal-rank-fusion constant for multi-query search. Higher values dampen the influence of top-ranked results. | +| `topK` | `number` | `15` | Default number of results returned per query. | +| `similarityWarnThreshold` | `number` | `0.85` | When two queries in a multi-query search have similarity above this, codegraph warns about redundancy. | + +--- + +## CI quick gates (`ci`) + +Coarse, single-flag CI checks. + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `failOnCycles` | `boolean` | `false` | If `true`, `codegraph build` exits non-zero when cycles are detected. | +| `impactThreshold` | `number \| null` | `null` | If set, `diff-impact` exits non-zero when a changed function's blast radius exceeds this number of callers. | + +For richer CI gating use `manifesto` and `check`. + +--- + +## Manifesto rules (`manifesto`) + +Pass/fail thresholds for `codegraph check` (manifesto mode). Each rule has `warn` (soft, exit 0) and optional `fail` (hard, exit 1) thresholds. `null` disables the rule. + +| Rule | Direction | Default `warn` | Default `fail` | Notes | +|------|-----------|----------------|----------------|-------| +| `cognitive` | upper bound | `15` | none | Cognitive complexity per function. | +| `cyclomatic` | upper bound | `10` | none | McCabe cyclomatic complexity per function. | +| `maxNesting` | upper bound | `4` | none | Max nesting depth per function. | +| `maintainabilityIndex` | lower bound | `20` | `null` | Microsoft MI score per function. Lower is worse. | +| `importCount` | upper bound | `null` | `null` | Imports per file. | +| `exportCount` | upper bound | `null` | `null` | Exports per file. | +| `lineCount` | upper bound | `null` | `null` | Lines per function or file. | +| `fanIn` | upper bound | `null` | `null` | Number of inbound dependencies. | +| `fanOut` | upper bound | `null` | `null` | Number of outbound dependencies. | +| `noCycles` | upper bound | `null` | `null` | Allowed cycle count (set `fail: 0` to forbid cycles). | +| `boundaries` | upper bound | `null` | `null` | Boundary violations against `manifesto.boundaries`. | + +```json +{ + "manifesto": { + "rules": { + "cognitive": { "warn": 15, "fail": 30 }, + "cyclomatic": { "warn": 10, "fail": 20 }, + "maintainabilityIndex": { "warn": 40, "fail": 20 }, + "noCycles": { "fail": 0 } + } + } +} +``` + +### Boundaries (`manifesto.boundaries`) + +Architecture constraints between layers. Accepts a free-form rule object or one of the built-in presets (e.g. `"onion"` — see [`src/features/boundaries.ts`](../../src/features/boundaries.ts)). Set to `null` (default) to disable. + +--- + +## Check predicates (`check`) + +Toggles for the lightweight `codegraph check` command (separate from manifesto). + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `cycles` | `boolean` | `true` | Fail if cycles exist. | +| `blastRadius` | `number \| null` | `null` | Fail if any function's caller count exceeds this. | +| `signatures` | `boolean` | `true` | Warn on signature changes in the diff. | +| `boundaries` | `boolean` | `true` | Honor the `manifesto.boundaries` rules. | +| `depth` | `number` | `3` | Transitive depth for blast-radius calculation. | + +--- + +## Co-change analysis (`coChange`) + +Configures `codegraph co-changes` (files that historically change together based on git history). + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `since` | `string` | `"1 year ago"` | Git revision range start (anything `git log --since` accepts). | +| `minSupport` | `number` | `3` | Minimum number of co-occurring commits before a pair is reported. | +| `minJaccard` | `number` | `0.3` | Minimum Jaccard similarity (`|A∩B| / |A∪B|`) for a pair. | +| `maxFilesPerCommit` | `number` | `50` | Skip commits touching more than this many files (avoids noise from large refactors / merges). | + +--- + +## Analysis depth & sampling (`analysis`) + +Defaults for transitive traversal across analysis commands. + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `impactDepth` | `number` | `3` | Default depth for `impact-analysis` (file-level). | +| `fnImpactDepth` | `number` | `5` | Default depth for `fn-impact` (function-level). | +| `auditDepth` | `number` | `3` | Default depth for `audit`. | +| `sequenceDepth` | `number` | `10` | Max BFS depth for `sequence` diagram generation. | +| `falsePositiveCallers` | `number` | `20` | Threshold above which a caller count is flagged as a likely false-positive name collision in `module_map`/quality reports. | +| `briefCallerDepth` | `number` | `5` | Caller depth for `brief`. | +| `briefImporterDepth` | `number` | `5` | Importer depth for `brief`. | +| `briefHighRiskCallers` | `number` | `10` | Number of high-risk callers to surface in `brief`. | +| `briefMediumRiskCallers` | `number` | `3` | Number of medium-risk callers to surface in `brief`. | + +--- + +## Community detection (`community`) + +Parameters for the Leiden/Louvain community detector used by `codegraph communities`. + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `resolution` | `number` | `1.0` | Resolution parameter — higher values produce more, smaller communities. | +| `maxLevels` | `number` | `50` | Max number of multi-level passes. | +| `maxLocalPasses` | `number` | `20` | Max local-moving passes per level. | +| `refinementTheta` | `number` | `1.0` | Leiden refinement temperature. | + +--- + +## Structure (`structure`) + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `cohesionThreshold` | `number` | `0.3` | Cohesion score below which a directory is flagged as low-cohesion in `codegraph structure`. | + +--- + +## Risk scoring (`risk`) + +Weights for the per-symbol risk score (used by `triage` and `audit`). + +### Component weights (`risk.weights`) + +Sum doesn't have to equal 1; codegraph normalizes internally. + +| Key | Default | Component | +|-----|---------|-----------| +| `fanIn` | `0.25` | Number of callers. | +| `complexity` | `0.30` | Cognitive/cyclomatic blend. | +| `churn` | `0.20` | Git history churn. | +| `role` | `0.15` | Symbol role weight (see below). | +| `mi` | `0.10` | Maintainability Index. | + +### Role weights (`risk.roleWeights`) + +Multiplier per classified role. + +| Role | Default | +|------|---------| +| `core` | `1.0` | +| `utility` | `0.9` | +| `entry` | `0.8` | +| `adapter` | `0.5` | +| `leaf` | `0.2` | +| `test-only` | `0.1` | +| `dead` | `0.1` | +| `dead-leaf` | `0.0` | +| `dead-entry` | `0.3` | +| `dead-ffi` | `0.05` | +| `dead-unresolved` | `0.15` | + +### Fallback (`risk.defaultRoleWeight`) + +| Key | Default | Purpose | +|-----|---------|---------| +| `defaultRoleWeight` | `0.5` | Weight for any role not explicitly listed in `roleWeights`. | + +--- + +## Display (`display`) + +CLI-output formatting. + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `maxColWidth` | `number` | `40` | Max width per column in tabular output. | +| `excerptLines` | `number` | `50` | Lines of source returned when no explicit end-line is given. | +| `summaryMaxChars` | `number` | `100` | Max length of extracted JSDoc/comment summaries. | +| `jsdocEndScanLines` | `number` | `10` | How far back to scan for the closing `*/` of a JSDoc above a symbol. | +| `jsdocOpenScanLines` | `number` | `20` | How far back to scan for the opening `/**` once the close is found. | +| `signatureGatherLines` | `number` | `5` | How many lines to gather to reconstruct multi-line function signatures. | + +--- + +## MCP server (`mcp`) + +Configures the Model Context Protocol server (`codegraph mcp`). + +### Per-tool page-size defaults (`mcp.defaults`) + +Default `limit` value applied when an MCP client calls a tool without specifying one. Override any subset: + +```json +{ + "mcp": { + "defaults": { + "list_functions": 50, + "module_map": 100 + } + } +} +``` + +| Key | Default | Tool | +|-----|---------|------| +| `list_functions` | `100` | `list_functions` | +| `query` | `10` | `query` | +| `where` | `50` | `where` | +| `node_roles` | `100` | `node_roles` | +| `export_graph` | `500` | `export_graph` | +| `fn_impact` | `5` | `fn_impact` | +| `context` | `5` | `context` | +| `explain` | `10` | Used internally by `audit`. | +| `file_deps` | `20` | `file_deps` | +| `file_exports` | `20` | `file_exports` | +| `diff_impact` | `30` | `diff_impact` | +| `impact_analysis` | `20` | `impact_analysis` | +| `semantic_search` | `20` | `semantic_search` | +| `execution_flow` | `50` | `execution_flow` | +| `hotspots` | `20` | Used internally by `triage`. | +| `co_changes` | `20` | `co_changes` | +| `complexity` | `30` | `complexity` | +| `manifesto` | `50` | Used internally by `check`. | +| `communities` | `20` | `communities` | +| `structure` | `30` | `structure` | +| `triage` | `20` | `triage` | +| `ast_query` | `50` | `ast_query` | +| `implementations` | `50` | `implementations` | +| `interfaces` | `50` | `interfaces` | + +### Tool filtering (`mcp.disabledTools`) + +Hide MCP tools from `tools/list` (and reject them from `tools/call`) to shrink the schema for small-context models or lock down agent capabilities. + +```json +{ + "mcp": { + "disabledTools": ["execution_flow", "sequence", "communities", "co_changes"] + } +} +``` + +**Name normalization.** Each entry is trimmed, lowercased, and has a leading `codegraph_` prefix stripped before comparison. All of these match the same tool: + +``` +"module_map", "Module_Map", " module_map ", "codegraph2_module_map" +``` + +The `codegraph_` prefix exists because some MCP clients namespace tools per-server when multiple servers are connected. + +**Runtime behavior.** Disabled tools are removed from `tools/list`. A `tools/call` invocation against a disabled name returns: + +```json +{ "isError": true, "content": [{ "type": "text", "text": "Unknown tool: " }] } +``` + +This is the same response a truly unknown name produces — config-disabled, unknown, and mode-disabled (e.g. `list_repos` in single-repo mode) tools all behave the same. + +**Available tool names** (one per line — copy into `disabledTools`): + +``` +query, path, where, file_deps, brief, file_exports, context, symbol_children, +list_functions, impact_analysis, fn_impact, diff_impact, branch_compare, +module_map, structure, find_cycles, node_roles, complexity, audit, triage, +check, communities, co_changes, code_owners, export_graph, sequence, +execution_flow, cfg, dataflow, semantic_search, ast_query, implementations, +interfaces, batch_query, list_repos +``` + +`list_repos` is only registered when the server is started with `--multi-repo` or `--repos`. + +--- + +## Where new options should live + +If you contribute a new behavioral constant, add it to `DEFAULTS` in `src/infrastructure/config.ts` under the appropriate group, mirror the type in `CodegraphConfig` (`src/types.ts`), and update this guide. Don't introduce hardcoded magic numbers in individual modules — `DEFAULTS` is the single source of truth. + +The only exception is **Category F values**: safety boundaries, standard formulas, and platform constraints (e.g. POSIX path separators, SQLite `BUSY_TIMEOUT`, IEEE 754 epsilons). Those stay inline. diff --git a/docs/guides/mcp-tool-filtering.md b/docs/guides/mcp-tool-filtering.md deleted file mode 100644 index d54af73a..00000000 --- a/docs/guides/mcp-tool-filtering.md +++ /dev/null @@ -1,199 +0,0 @@ -# MCP Tool Filtering - -The codegraph MCP server exposes 30+ tools that AI agents can call. Every tool's input schema is sent to the model on the first turn, and large schemas eat tokens that could go toward actual work. This guide shows how to trim the toolset to the slice you actually use. - ---- - -## When to filter - -Disable tools when: - -- **You're using a small-context model.** Loading 30+ tool schemas can consume thousands of tokens before the model sees a single user message. Cutting the list to the 5–10 you actually call is the easiest single optimization. -- **Your workflow only uses a subset.** A code-review agent may only need `diff_impact`, `fn_impact`, and `context`; a refactor agent may only need `query`, `where`, and `audit`. Hiding the rest reduces noise in tool selection. -- **You want to lock down capabilities.** Disabling `export_graph` or `semantic_search` can keep agents on the rails for narrow tasks. -- **You want consistent behavior across modes.** Tools that are filtered (by config or by single-repo mode) all return the same `Unknown tool: ` response, so prompts that handle that response work everywhere. - -Don't filter when you don't have a token-budget problem — a missing tool is a silent capability gap. Start permissive, then trim once you've measured. - ---- - -## Configuration - -Add `mcp.disabledTools` to your `.codegraphrc.json`: - -```json -{ - "mcp": { - "disabledTools": [ - "execution_flow", - "sequence", - "communities", - "co_changes", - "code_owners" - ] - } -} -``` - -Reload your MCP client (or restart `codegraph mcp`) for changes to take effect. The list is read once when the server starts. - -### Name normalization - -Names are matched after the following normalization, applied to both the config entries and the registered tool names: - -1. Trim surrounding whitespace. -2. Lowercase. -3. Strip a leading `codegraph_` prefix (e.g. `codegraph2_module_map` → `module_map`). - -This means all of the following entries are equivalent and disable the same tool: - -```json -["module_map", "Module_Map", " module_map ", "codegraph2_module_map"] -``` - -The `codegraph_` prefix exists because some MCP clients namespace tools per-server when multiple servers are connected — copying a tool name straight from the client UI still works. - -### Behavior at runtime - -- **`tools/list`** — disabled tools do not appear in the response. The model never sees their schemas. -- **`tools/call`** — invoking a disabled tool returns `{ isError: true, content: [{ type: 'text', text: 'Unknown tool: ' }] }`. This is the same response a truly unknown tool name produces. -- **`list_repos` in single-repo mode** — `list_repos` is only registered when the server is started with `--multi-repo` or `--repos`. In single-repo mode it is filtered out by default and produces the same `Unknown tool: list_repos` response, regardless of `disabledTools`. - ---- - -## Tool catalog - -All tool names below match the values you put in `disabledTools`. Group descriptions are advisory — disable in any combination you like. - -### Navigation & lookup - -| Tool | Purpose | -|------|---------| -| `query` | Callers/callees with transitive chain, or shortest path between two symbols. | -| `path` | Shortest call path between two symbols. | -| `where` | Locate where a symbol lives. | -| `file_deps` | Per-file dependency listing. | -| `file_exports` | Per-symbol export consumers. | -| `brief` | Short summary of a symbol. | -| `context` | Source + dependencies + callers for a symbol. | -| `symbol_children` | Sub-declarations (parameters, properties, constants). | -| `list_functions` | Enumerate functions in the graph. | - -### Impact & change analysis - -| Tool | Purpose | -|------|---------| -| `impact_analysis` | Blast radius for a file. | -| `fn_impact` | Blast radius for a function. | -| `diff_impact` | Impact of a diff (staged, branch, or refs). | -| `branch_compare` | Structural diff between two refs. | - -### Structure & health - -| Tool | Purpose | -|------|---------| -| `module_map` | Most-connected files / module overview. | -| `structure` | Directory tree with cohesion scores. | -| `find_cycles` | Circular dependency detection. | -| `node_roles` | Classify symbols (core, dead, hub, etc.). | -| `complexity` | Per-function complexity metrics. | -| `audit` | Composite report: structure + impact + health. | -| `triage` | Risk-ranked audit priority queue. | -| `check` | CI gate predicates (cycles, complexity, boundaries). | -| `communities` | Louvain community detection. | -| `co_changes` | Files that historically change together. | -| `code_owners` | CODEOWNERS-based ownership queries. | - -### Visualization & export - -| Tool | Purpose | -|------|---------| -| `export_graph` | Export the graph (DOT, Mermaid, GraphML, Neo4j). | -| `sequence` | Sequence diagram data (BFS traversal). | -| `execution_flow` | Execution flow data for a function. | -| `cfg` | Control-flow graph for a function. | -| `dataflow` | Intraprocedural dataflow analysis. | - -### Search & types - -| Tool | Purpose | -|------|---------| -| `semantic_search` | Embedding-backed semantic search (requires `codegraph embed`). | -| `ast_query` | Query the raw AST by node kind. | -| `implementations` | Find implementations of an interface/trait. | -| `interfaces` | Find interfaces/traits a class/struct satisfies. | - -### Batching - -| Tool | Purpose | -|------|---------| -| `batch_query` | Run multiple queries in one call. | - -### Multi-repo only - -| Tool | Purpose | -|------|---------| -| `list_repos` | List repositories in the registry. Available only when the server is started with `--multi-repo` or `--repos`. | - ---- - -## Recipes - -### Minimal "code-review" toolset - -Disable everything except impact and context: - -```json -{ - "mcp": { - "disabledTools": [ - "query", "path", "file_deps", "file_exports", "brief", - "module_map", "structure", "find_cycles", "node_roles", - "complexity", "audit", "triage", "check", - "communities", "co_changes", "code_owners", - "export_graph", "sequence", "execution_flow", "cfg", "dataflow", - "semantic_search", "ast_query", "implementations", "interfaces", - "batch_query", "list_functions", "symbol_children", "branch_compare" - ] - } -} -``` - -The remaining tools — `diff_impact`, `fn_impact`, `impact_analysis`, `context`, `where` — give an agent everything it needs to assess a PR. - -### Drop the heavy visualizers - -If your agent never produces diagrams, the visualization tools are pure schema overhead: - -```json -{ - "mcp": { - "disabledTools": ["export_graph", "sequence", "execution_flow", "cfg", "dataflow"] - } -} -``` - -### Drop semantic search if you haven't run `codegraph embed` - -`semantic_search` is wired up unconditionally but only works after embeddings have been built. Disabling it removes a tool that would otherwise return errors: - -```json -{ - "mcp": { - "disabledTools": ["semantic_search"] - } -} -``` - ---- - -## Verifying - -After editing `.codegraphrc.json`, restart the server and ask the client to refresh its tool list. Then call `tools/list` from your MCP client and confirm the disabled names are absent — that response is the source of truth for what the model will see. - ---- - -## See also - -- [`mcp.disabledTools` in the README](../../README.md#mcp-tool-filtering) — short reference. -- [`src/mcp/tool-registry.ts`](../../src/mcp/tool-registry.ts) — source of truth for tool names and schemas. From 4fc8b2005709360649e36840ca4ededaa83fd853 Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Thu, 30 Apr 2026 04:08:23 -0600 Subject: [PATCH 5/6] fix(mcp): simplify disabledTools to always be an array (#1035) Impact: 1 functions changed, 1 affected --- src/mcp/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 1d454ccd..e0331f8c 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -215,7 +215,7 @@ export async function startMCPServer( // Apply config-based MCP page-size overrides const config = options.config || loadConfig(); initMcpDefaults(config.mcp?.defaults ? { ...config.mcp.defaults } : undefined); - const disabledTools = config.mcp?.disabledTools ? [...config.mcp.disabledTools] : undefined; + const disabledTools = [...(config.mcp?.disabledTools ?? [])]; const enabledTools = buildToolList(multiRepo, disabledTools); const enabledToolNames = new Set(enabledTools.map((tool) => tool.name)); From 8b785156c33db03e1f6ff919e3fb1929f80f935e Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Thu, 30 Apr 2026 15:31:13 -0600 Subject: [PATCH 6/6] refactor(mcp): remove unreachable list_repos branch (#1035) The 'list_repos' tool is excluded from 'enabledToolNames' when 'multiRepo' is false, so 'createCallToolHandler' rejects single-repo 'list_repos' calls with 'Unknown tool' before they ever reach 'validateMultiRepoAccess'. That made the 'name === "list_repos"' branch unreachable. Drop the dead branch and the now-unused 'name' parameter; existing test 'rejects list_repos in single-repo mode' covers the early-rejection path. --- src/mcp/server.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/mcp/server.ts b/src/mcp/server.ts index e0331f8c..c3deac1b 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -98,17 +98,15 @@ async function resolveDbPath( return dbPath; } -function validateMultiRepoAccess(multiRepo: boolean, name: string, args: { repo?: string }): void { +function validateMultiRepoAccess(multiRepo: boolean, args: { repo?: string }): void { if (!multiRepo && args.repo) { throw new ConfigError( 'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to access other repositories.', ); } - if (!multiRepo && name === 'list_repos') { - throw new ConfigError( - 'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to list repositories.', - ); - } + // Note: the `list_repos` tool is excluded from `enabledToolNames` when + // `multiRepo` is false (see `buildToolList`), so any call to it is rejected + // earlier in `createCallToolHandler` with an "Unknown tool" error. } /** @@ -172,7 +170,7 @@ function createCallToolHandler( return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }; } - validateMultiRepoAccess(multiRepo, name, args); + validateMultiRepoAccess(multiRepo, args); const dbPath = await resolveDbPath(customDbPath, args, allowedRepos);