Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,8 @@ When running as an MCP server, CodeGraph exposes these tools to Claude Code:
| `codegraph_files` | Get indexed file structure (faster than filesystem scanning) |
| `codegraph_status` | Check index health and statistics |

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.

---

## Library Usage
Expand Down Expand Up @@ -396,7 +398,10 @@ The `.codegraph/config.json` file controls indexing:
"frameworks": [],
"maxFileSize": 1048576,
"extractDocstrings": true,
"trackCallSites": true
"trackCallSites": true,
"mcp": {
"disabledTools": ["codegraph_explore"]
}
}
```

Expand All @@ -408,6 +413,13 @@ The `.codegraph/config.json` file controls indexing:
| `maxFileSize` | Skip files larger than this (bytes) | `1048576` (1MB) |
| `extractDocstrings` | Extract docstrings from code | `true` |
| `trackCallSites` | Track call site locations | `true` |
| `mcp.disabledTools` | MCP tool names to hide/disable for this project | `[]` |

Example disabled tool names: `codegraph_explore`, `codegraph_context`, `codegraph_files`.

Notes:
- This setting is project-local only (no global override).
- Unknown tool names are ignored (lenient behavior).

## Supported Languages

Expand Down
25 changes: 25 additions & 0 deletions __tests__/foundation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,31 @@ describe('CodeGraph Foundation', () => {
const config = loadConfig(tempDir);
expect(config.maxFileSize).toBe(999999);
});

it('should persist MCP disabled tools config', () => {
const cg = CodeGraph.initSync(tempDir);

cg.updateConfig({
mcp: { disabledTools: ['codegraph_explore'] },
});

cg.close();

const config = loadConfig(tempDir);
expect(config.mcp?.disabledTools).toEqual(['codegraph_explore']);
});

it('should reject invalid mcp.disabledTools format', () => {
const cg = CodeGraph.initSync(tempDir);
cg.close();

const configPath = path.join(getCodeGraphDir(tempDir), 'config.json');
const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as Record<string, unknown>;
raw.mcp = { disabledTools: [123] };
fs.writeFileSync(configPath, JSON.stringify(raw, null, 2));

expect(() => loadConfig(tempDir)).toThrow(/Invalid configuration format/i);
});
});

describe('Directory Management', () => {
Expand Down
33 changes: 32 additions & 1 deletion __tests__/security.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import * as path from 'path';
import * as os from 'os';
import { FileLock } from '../src/utils';
import CodeGraph from '../src/index';
import { ToolHandler, tools } from '../src/mcp/tools';
import { ToolHandler, tools, filterToolsByConfig } from '../src/mcp/tools';
import { shouldIncludeFile, scanDirectory } from '../src/extraction';
import { shouldIncludeFile as configShouldInclude } from '../src/config';
import { CodeGraphConfig, DEFAULT_CONFIG } from '../src/types';
Expand Down Expand Up @@ -265,6 +265,37 @@ describe('MCP Input Validation', () => {
const result = await handler.execute('codegraph_search', { query: 'example', limit: -5 });
expect(result.isError).toBeFalsy();
});

it('should hide disabled tools from tool list', async () => {
const projectWithDisabled = createTempDir();
const srcDir = path.join(projectWithDisabled, 'src');
fs.mkdirSync(srcDir);
fs.writeFileSync(path.join(srcDir, 'x.ts'), 'export const x = 1;\n');

const disabledCg = CodeGraph.initSync(projectWithDisabled, {
config: {
include: ['**/*.ts'],
exclude: [],
mcp: { disabledTools: ['codegraph_explore'] },
},
});

try {
const disabledHandler = new ToolHandler(disabledCg);
const toolNames = disabledHandler.getTools().map(t => t.name);
expect(toolNames).not.toContain('codegraph_explore');
} finally {
disabledCg.close();
cleanupTempDir(projectWithDisabled);
}
});

it('should ignore unknown disabled tool names (lenient)', () => {
const filtered = filterToolsByConfig(tools, {
mcp: { disabledTools: ['not_a_real_tool_name'] },
});
expect(filtered).toHaveLength(tools.length);
});
});

describe('Atomic Writes', () => {
Expand Down
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ export function validateConfig(config: unknown): config is CodeGraphConfig {
if (typeof c.extractDocstrings !== 'boolean') return false;
if (typeof c.trackCallSites !== 'boolean') return false;

// Validate MCP config if present
if (c.mcp !== undefined) {
if (typeof c.mcp !== 'object' || c.mcp === null) return false;
const mcp = c.mcp as Record<string, unknown>;
if (mcp.disabledTools !== undefined) {
if (!Array.isArray(mcp.disabledTools)) return false;
if (!mcp.disabledTools.every((t) => typeof t === 'string')) return false;
}
}

// Validate include/exclude are string arrays
if (!c.include.every((p) => typeof p === 'string')) return false;
if (!c.exclude.every((p) => typeof p === 'string')) return false;
Expand Down Expand Up @@ -127,6 +137,9 @@ function mergeConfig(
maxFileSize: overrides.maxFileSize ?? defaults.maxFileSize,
extractDocstrings: overrides.extractDocstrings ?? defaults.extractDocstrings,
trackCallSites: overrides.trackCallSites ?? defaults.trackCallSites,
mcp: {
disabledTools: overrides.mcp?.disabledTools ?? defaults.mcp?.disabledTools ?? [],
},
customPatterns: overrides.customPatterns ?? defaults.customPatterns,
};
}
Expand Down
10 changes: 6 additions & 4 deletions src/mcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import * as path from 'path';
import CodeGraph, { findNearestCodeGraphRoot } from '../index';
import { StdioTransport, JsonRpcRequest, JsonRpcNotification, ErrorCodes } from './transport';
import { tools, ToolHandler } from './tools';
import { ToolHandler } from './tools';
import { SERVER_INSTRUCTIONS } from './server-instructions';

/**
Expand Down Expand Up @@ -314,9 +314,11 @@ export class MCPServer {
const toolName = params.name;
const toolArgs = params.arguments || {};

// Validate tool exists
const tool = tools.find(t => t.name === toolName);
if (!tool) {
// Validate tool is enabled for this project (disabled tools are hidden)
const projectPath = typeof toolArgs.projectPath === 'string'
? toolArgs.projectPath
: undefined;
if (!this.toolHandler.isToolEnabled(toolName, projectPath)) {
this.transport.sendError(
request.id,
ErrorCodes.InvalidParams,
Expand Down
41 changes: 36 additions & 5 deletions src/mcp/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

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

/**
* Filter tool definitions by project MCP config.
* Unknown disabled tool names are ignored (lenient behavior).
*/
export function filterToolsByConfig(
allTools: ToolDefinition[],
config?: Pick<CodeGraphConfig, 'mcp'> | null
): ToolDefinition[] {
const disabled = new Set(config?.mcp?.disabledTools ?? []);
if (disabled.size === 0) return allTools;
return allTools.filter(tool => !disabled.has(tool.name));
}

/**
* Tool handler that executes tools against a CodeGraph instance
*
Expand Down Expand Up @@ -317,14 +330,25 @@ export class ToolHandler {
* The codegraph_explore tool description includes a budget recommendation
* scaled to the number of indexed files.
*/
getTools(): ToolDefinition[] {
if (!this.cg) return tools;
getTools(projectPath?: string): ToolDefinition[] {
let baseTools = tools;

try {
const cg = projectPath ? this.getCodeGraph(projectPath) : this.cg;
if (cg) {
baseTools = filterToolsByConfig(baseTools, cg.getConfig());
}
} catch {
// If project resolution fails here, keep fallback behavior and expose all tools.
}

if (!this.cg) return baseTools;

try {
const stats = this.cg.getStats();
const budget = getExploreBudget(stats.fileCount);

return tools.map(tool => {
return baseTools.map(tool => {
if (tool.name === 'codegraph_explore') {
return {
...tool,
Expand All @@ -334,10 +358,17 @@ export class ToolHandler {
return tool;
});
} catch {
return tools;
return baseTools;
}
}

/**
* Check whether a tool is enabled for the target project.
*/
isToolEnabled(toolName: string, projectPath?: string): boolean {
return this.getTools(projectPath).some(tool => tool.name === toolName);
}

/**
* Get CodeGraph instance for a project
*
Expand Down
9 changes: 9 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,12 @@ export interface CodeGraphConfig {
/** Whether to track call sites */
trackCallSites: boolean;

/** MCP server configuration */
mcp?: {
/** Tool names to hide/disable from MCP clients */
disabledTools?: string[];
};

/** Custom symbol patterns to extract */
customPatterns?: {
/** Name for this pattern group */
Expand Down Expand Up @@ -693,6 +699,9 @@ export const DEFAULT_CONFIG: CodeGraphConfig = {
maxFileSize: 1024 * 1024, // 1MB
extractDocstrings: true,
trackCallSites: true,
mcp: {
disabledTools: [],
},
};

// =============================================================================
Expand Down