fix(mcp): keep codegraph_explore loaded in Claude Code and Copilot CLI (#1696)

Claude Code defers every MCP tool behind ToolSearch by default, so a fresh session sees only the tool name until the model searches for it. The explore tool now carries `_meta: { "anthropic/alwaysLoad": true }`, which exempts it on existing installs, and the Claude Code installer target writes `alwaysLoad: true` on the server entry (re-running install adds the key to an older entry). Copilot CLI tool search holds MCP tools back the same way once ~30 tools are connected, so its entry carries `deferTools: "never"`.
This commit is contained in:
Aaron Queen
2026-09-08 03:23:33 -06:00
parent cd4e65b59c
commit 097cd19ad2
7 changed files with 86 additions and 6 deletions
+14 -2
View File
@@ -41,6 +41,18 @@ import {
CODEGRAPH_SECTION_START,
} from '../instructions-template';
/**
* The shared stdio entry plus `alwaysLoad: true`, Claude Code's exemption from
* tool-search deferral (https://code.claude.com/docs/en/mcp#exempt-a-server-from-deferral).
* `codegraph_explore` carries the same flag in its `_meta`, which covers an
* entry written before this key; the entry-level key additionally makes
* Claude Code wait for this server's tools at startup, so they are in the
* first prompt rather than listed after the server connects in the background.
*/
function getClaudeMcpServerConfig() {
return { ...getMcpServerConfig(), alwaysLoad: true };
}
function configDir(loc: Location): string {
return loc === 'global'
? path.join(os.homedir(), '.claude')
@@ -211,7 +223,7 @@ class ClaudeCodeTarget implements AgentTarget {
printConfig(loc: Location): string {
const target = mcpJsonPath(loc);
const snippet = JSON.stringify({ mcpServers: { codegraph: getMcpServerConfig() } }, null, 2);
const snippet = JSON.stringify({ mcpServers: { codegraph: getClaudeMcpServerConfig() } }, null, 2);
return `# Add to ${target}\n\n${snippet}\n`;
}
@@ -231,7 +243,7 @@ export function writeMcpEntry(loc: Location): WriteResult['files'][number] {
const file = mcpJsonPath(loc);
const existing = readJsonFile(file);
const before = existing.mcpServers?.codegraph;
const after = getMcpServerConfig();
const after = getClaudeMcpServerConfig();
if (jsonDeepEqual(before, after)) {
// Already exactly what we'd write — preserve byte-identical file.
+8 -2
View File
@@ -95,9 +95,15 @@ function copilotOnPath(): boolean {
return false;
}
function buildCopilotMcpConfig(): { type: string; command: string; args: string[]; tools: string[] } {
/**
* The shared stdio entry, every tool allowed, and `deferTools: 'never'`: Copilot CLI's tool
* search (on by default from ~30 connected tools on Claude and GPT-5.4+ models) otherwise holds
* MCP tools back until the model searches for them, so `codegraph_explore` would be a name the
* model has to go looking for before it can follow the "call it instead of Read" instruction.
*/
function buildCopilotMcpConfig(): { type: string; command: string; args: string[]; tools: string[]; deferTools: 'never' } {
const base = getMcpServerConfig();
return { ...base, tools: ['*'] };
return { ...base, tools: ['*'], deferTools: 'never' };
}
class CopilotCliTarget implements AgentTarget {
+9
View File
@@ -942,6 +942,12 @@ export interface ToolDefinition {
};
/** Behavioral hints for clients (see {@link ToolAnnotations}). */
annotations?: ToolAnnotations;
/**
* MCP `_meta` on the tool definition. `anthropic/alwaysLoad: true` makes
* Claude Code load the tool at session start instead of deferring it behind
* its tool search (https://code.claude.com/docs/en/mcp#exempt-a-server-from-deferral).
*/
_meta?: Record<string, unknown>;
}
/**
@@ -1196,6 +1202,9 @@ export const tools: ToolDefinition[] = [
required: ['query'],
},
annotations: READ_ONLY_ANNOTATIONS,
// Loaded from the first prompt in Claude Code, which otherwise defers every
// MCP tool behind a ToolSearch step (#1696).
_meta: { 'anthropic/alwaysLoad': true },
},
{
name: 'codegraph_status',