Skip to content

Commit 68ad7c3

Browse files
committed
Implemented project configuration to disable certain MCP tools to spare tokens with the initial schema in the context
1 parent 7e617d8 commit 68ad7c3

8 files changed

Lines changed: 134 additions & 12 deletions

File tree

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,8 @@ When running as an MCP server, CodeGraph exposes these tools to Claude Code:
358358
| `codegraph_files` | Get indexed file structure (faster than filesystem scanning) |
359359
| `codegraph_status` | Check index health and statistics |
360360

361+
You can hide specific MCP tools per-project via `.codegraph/config.json` (see Configuration below). Hidden tools are removed from `tools/list` and are treated as unknown if called directly.
362+
361363
---
362364

363365
## Library Usage
@@ -396,7 +398,10 @@ The `.codegraph/config.json` file controls indexing:
396398
"frameworks": [],
397399
"maxFileSize": 1048576,
398400
"extractDocstrings": true,
399-
"trackCallSites": true
401+
"trackCallSites": true,
402+
"mcp": {
403+
"disabledTools": ["codegraph_explore"]
404+
}
400405
}
401406
```
402407

@@ -408,6 +413,13 @@ The `.codegraph/config.json` file controls indexing:
408413
| `maxFileSize` | Skip files larger than this (bytes) | `1048576` (1MB) |
409414
| `extractDocstrings` | Extract docstrings from code | `true` |
410415
| `trackCallSites` | Track call site locations | `true` |
416+
| `mcp.disabledTools` | MCP tool names to hide/disable for this project | `[]` |
417+
418+
Example disabled tool names: `codegraph_explore`, `codegraph_context`, `codegraph_files`.
419+
420+
Notes:
421+
- This setting is project-local only (no global override).
422+
- Unknown tool names are ignored (lenient behavior).
411423

412424
## Supported Languages
413425

__tests__/foundation.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,31 @@ describe('CodeGraph Foundation', () => {
205205
const config = loadConfig(tempDir);
206206
expect(config.maxFileSize).toBe(999999);
207207
});
208+
209+
it('should persist MCP disabled tools config', () => {
210+
const cg = CodeGraph.initSync(tempDir);
211+
212+
cg.updateConfig({
213+
mcp: { disabledTools: ['codegraph_explore'] },
214+
});
215+
216+
cg.close();
217+
218+
const config = loadConfig(tempDir);
219+
expect(config.mcp?.disabledTools).toEqual(['codegraph_explore']);
220+
});
221+
222+
it('should reject invalid mcp.disabledTools format', () => {
223+
const cg = CodeGraph.initSync(tempDir);
224+
cg.close();
225+
226+
const configPath = path.join(getCodeGraphDir(tempDir), 'config.json');
227+
const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as Record<string, unknown>;
228+
raw.mcp = { disabledTools: [123] };
229+
fs.writeFileSync(configPath, JSON.stringify(raw, null, 2));
230+
231+
expect(() => loadConfig(tempDir)).toThrow(/Invalid configuration format/i);
232+
});
208233
});
209234

210235
describe('Directory Management', () => {

__tests__/security.test.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import * as path from 'path';
1414
import * as os from 'os';
1515
import { FileLock } from '../src/utils';
1616
import CodeGraph from '../src/index';
17-
import { ToolHandler, tools } from '../src/mcp/tools';
17+
import { ToolHandler, tools, filterToolsByConfig } from '../src/mcp/tools';
1818
import { shouldIncludeFile, scanDirectory } from '../src/extraction';
1919
import { shouldIncludeFile as configShouldInclude } from '../src/config';
2020
import { CodeGraphConfig, DEFAULT_CONFIG } from '../src/types';
@@ -265,6 +265,37 @@ describe('MCP Input Validation', () => {
265265
const result = await handler.execute('codegraph_search', { query: 'example', limit: -5 });
266266
expect(result.isError).toBeFalsy();
267267
});
268+
269+
it('should hide disabled tools from tool list', async () => {
270+
const projectWithDisabled = createTempDir();
271+
const srcDir = path.join(projectWithDisabled, 'src');
272+
fs.mkdirSync(srcDir);
273+
fs.writeFileSync(path.join(srcDir, 'x.ts'), 'export const x = 1;\n');
274+
275+
const disabledCg = CodeGraph.initSync(projectWithDisabled, {
276+
config: {
277+
include: ['**/*.ts'],
278+
exclude: [],
279+
mcp: { disabledTools: ['codegraph_explore'] },
280+
},
281+
});
282+
283+
try {
284+
const disabledHandler = new ToolHandler(disabledCg);
285+
const toolNames = disabledHandler.getTools().map(t => t.name);
286+
expect(toolNames).not.toContain('codegraph_explore');
287+
} finally {
288+
disabledCg.close();
289+
cleanupTempDir(projectWithDisabled);
290+
}
291+
});
292+
293+
it('should ignore unknown disabled tool names (lenient)', () => {
294+
const filtered = filterToolsByConfig(tools, {
295+
mcp: { disabledTools: ['not_a_real_tool_name'] },
296+
});
297+
expect(filtered).toHaveLength(tools.length);
298+
});
268299
});
269300

270301
describe('Atomic Writes', () => {

package-lock.json

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ export function validateConfig(config: unknown): config is CodeGraphConfig {
6868
if (typeof c.extractDocstrings !== 'boolean') return false;
6969
if (typeof c.trackCallSites !== 'boolean') return false;
7070

71+
// Validate MCP config if present
72+
if (c.mcp !== undefined) {
73+
if (typeof c.mcp !== 'object' || c.mcp === null) return false;
74+
const mcp = c.mcp as Record<string, unknown>;
75+
if (mcp.disabledTools !== undefined) {
76+
if (!Array.isArray(mcp.disabledTools)) return false;
77+
if (!mcp.disabledTools.every((t) => typeof t === 'string')) return false;
78+
}
79+
}
80+
7181
// Validate include/exclude are string arrays
7282
if (!c.include.every((p) => typeof p === 'string')) return false;
7383
if (!c.exclude.every((p) => typeof p === 'string')) return false;
@@ -127,6 +137,9 @@ function mergeConfig(
127137
maxFileSize: overrides.maxFileSize ?? defaults.maxFileSize,
128138
extractDocstrings: overrides.extractDocstrings ?? defaults.extractDocstrings,
129139
trackCallSites: overrides.trackCallSites ?? defaults.trackCallSites,
140+
mcp: {
141+
disabledTools: overrides.mcp?.disabledTools ?? defaults.mcp?.disabledTools ?? [],
142+
},
130143
customPatterns: overrides.customPatterns ?? defaults.customPatterns,
131144
};
132145
}

src/mcp/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import * as path from 'path';
1919
import CodeGraph, { findNearestCodeGraphRoot } from '../index';
2020
import { StdioTransport, JsonRpcRequest, JsonRpcNotification, ErrorCodes } from './transport';
21-
import { tools, ToolHandler } from './tools';
21+
import { ToolHandler } from './tools';
2222
import { SERVER_INSTRUCTIONS } from './server-instructions';
2323

2424
/**
@@ -314,9 +314,11 @@ export class MCPServer {
314314
const toolName = params.name;
315315
const toolArgs = params.arguments || {};
316316

317-
// Validate tool exists
318-
const tool = tools.find(t => t.name === toolName);
319-
if (!tool) {
317+
// Validate tool is enabled for this project (disabled tools are hidden)
318+
const projectPath = typeof toolArgs.projectPath === 'string'
319+
? toolArgs.projectPath
320+
: undefined;
321+
if (!this.toolHandler.isToolEnabled(toolName, projectPath)) {
320322
this.transport.sendError(
321323
request.id,
322324
ErrorCodes.InvalidParams,

src/mcp/tools.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import CodeGraph, { findNearestCodeGraphRoot } from '../index';
8-
import type { Node, Edge, SearchResult, Subgraph, TaskContext, NodeKind } from '../types';
8+
import type { Node, Edge, SearchResult, Subgraph, TaskContext, NodeKind, CodeGraphConfig } from '../types';
99
import { createHash } from 'crypto';
1010
import { writeFileSync, readFileSync, existsSync } from 'fs';
1111
import { clamp, validatePathWithinRoot } from '../utils';
@@ -286,6 +286,19 @@ export const tools: ToolDefinition[] = [
286286
},
287287
];
288288

289+
/**
290+
* Filter tool definitions by project MCP config.
291+
* Unknown disabled tool names are ignored (lenient behavior).
292+
*/
293+
export function filterToolsByConfig(
294+
allTools: ToolDefinition[],
295+
config?: Pick<CodeGraphConfig, 'mcp'> | null
296+
): ToolDefinition[] {
297+
const disabled = new Set(config?.mcp?.disabledTools ?? []);
298+
if (disabled.size === 0) return allTools;
299+
return allTools.filter(tool => !disabled.has(tool.name));
300+
}
301+
289302
/**
290303
* Tool handler that executes tools against a CodeGraph instance
291304
*
@@ -317,14 +330,25 @@ export class ToolHandler {
317330
* The codegraph_explore tool description includes a budget recommendation
318331
* scaled to the number of indexed files.
319332
*/
320-
getTools(): ToolDefinition[] {
321-
if (!this.cg) return tools;
333+
getTools(projectPath?: string): ToolDefinition[] {
334+
let baseTools = tools;
335+
336+
try {
337+
const cg = projectPath ? this.getCodeGraph(projectPath) : this.cg;
338+
if (cg) {
339+
baseTools = filterToolsByConfig(baseTools, cg.getConfig());
340+
}
341+
} catch {
342+
// If project resolution fails here, keep fallback behavior and expose all tools.
343+
}
344+
345+
if (!this.cg) return baseTools;
322346

323347
try {
324348
const stats = this.cg.getStats();
325349
const budget = getExploreBudget(stats.fileCount);
326350

327-
return tools.map(tool => {
351+
return baseTools.map(tool => {
328352
if (tool.name === 'codegraph_explore') {
329353
return {
330354
...tool,
@@ -334,10 +358,17 @@ export class ToolHandler {
334358
return tool;
335359
});
336360
} catch {
337-
return tools;
361+
return baseTools;
338362
}
339363
}
340364

365+
/**
366+
* Check whether a tool is enabled for the target project.
367+
*/
368+
isToolEnabled(toolName: string, projectPath?: string): boolean {
369+
return this.getTools(projectPath).some(tool => tool.name === toolName);
370+
}
371+
341372
/**
342373
* Get CodeGraph instance for a project
343374
*

src/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,12 @@ export interface CodeGraphConfig {
478478
/** Whether to track call sites */
479479
trackCallSites: boolean;
480480

481+
/** MCP server configuration */
482+
mcp?: {
483+
/** Tool names to hide/disable from MCP clients */
484+
disabledTools?: string[];
485+
};
486+
481487
/** Custom symbol patterns to extract */
482488
customPatterns?: {
483489
/** Name for this pattern group */
@@ -693,6 +699,9 @@ export const DEFAULT_CONFIG: CodeGraphConfig = {
693699
maxFileSize: 1024 * 1024, // 1MB
694700
extractDocstrings: true,
695701
trackCallSites: true,
702+
mcp: {
703+
disabledTools: [],
704+
},
696705
};
697706

698707
// =============================================================================

0 commit comments

Comments
 (0)