The MCP server gated tool availability on whether the server root had a .codegraph/ index, so in a monorepo where only sub-projects are indexed the agent saw zero tools — and couldn't reach an indexed sub-project even by projectPath. A session started before `codegraph init` also never surfaced the tools afterward. The Claude front-load hook had the mirror gap: it only walked UP for an index, so it stayed silent at a monorepo root. MCP server: - Always expose the tool surface; when the root isn't indexed, send a per-project instructions variant (pass projectPath) instead of the "inactive" note. Safety comes from response SHAPE (success-shaped guidance, never isError), not from hiding tools. - Reword the no-default-project guidance to be per-project, not per-session, and sharpen the projectPath schema description. Front-load hook (UserPromptSubmit): - Scan DOWN (bounded depth, workspace-root-gated) for indexed sub-projects and shape the injection by topology: front-load the one the prompt names, nudge about the rest, or list them when ambiguous. Verified: full suite (1703 passed); a live two-package monorepo run confirms the hook front-loads the correct sub-project with no cross-package leakage. The front-load's net speed effect is the existing multi-file-vs-single-file tradeoff, unchanged by this work. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0a91d0f512
commit
85a8f32fd9
@@ -70,22 +70,34 @@ calls; a grep/read exploration is dozens.
|
||||
`;
|
||||
|
||||
/**
|
||||
* Instructions variant sent when the workspace has NO codegraph index.
|
||||
* Instructions variant sent when the server's own root has NO codegraph index.
|
||||
*
|
||||
* Sending the full playbook ("lean on codegraph for everything") into a
|
||||
* session where every call would fail wastes the agent's calls and — worse —
|
||||
* the failures teach it codegraph is broken. The unindexed variant is a
|
||||
* short, unambiguous "inactive this session" note; `tools/list` is gated to
|
||||
* empty in the same state, so the agent has nothing to mis-call. Indexing is
|
||||
* deliberately left to the user: the agent is told NOT to run init itself.
|
||||
* The tools are still exposed (gating tool availability on whether `./` has an
|
||||
* index is the bug behind #964: it breaks monorepos where only sub-projects are
|
||||
* indexed, and a server that started before `codegraph init` never surfaces the
|
||||
* tools afterward). Instead of an "inactive" note, this variant tells the agent
|
||||
* codegraph works **per project**: there's no default project to query, so pass
|
||||
* a `projectPath` to any project that HAS a `.codegraph/`. The full single-
|
||||
* project playbook ({@link SERVER_INSTRUCTIONS}) is sent instead when the root
|
||||
* IS indexed, so the common case stays tight.
|
||||
*/
|
||||
export const SERVER_INSTRUCTIONS_UNINDEXED = `# Codegraph — inactive (workspace not indexed)
|
||||
export const SERVER_INSTRUCTIONS_NO_ROOT_INDEX = `# Codegraph — available (per-project; pass projectPath)
|
||||
|
||||
This workspace has no codegraph index (no \`.codegraph/\` directory), so no
|
||||
codegraph tools are available this session. Work with your built-in tools as
|
||||
usual.
|
||||
Codegraph is a SQLite knowledge graph of a codebase's symbols, edges, and
|
||||
files: one \`codegraph_explore\` call returns the verbatim, line-numbered source
|
||||
of the relevant symbols PLUS the call paths between them and a blast-radius
|
||||
summary — replacing a grep + Read loop with one round-trip.
|
||||
|
||||
Indexing is the user's decision — do not run it yourself. If the user asks
|
||||
about codegraph, they can enable it by running \`codegraph init\` in the
|
||||
project root and starting a new session.
|
||||
This server started somewhere with no \`.codegraph/\` of its own, so there is no
|
||||
default project — but the tools are available and work **per project**:
|
||||
|
||||
- To query a project that HAS a \`.codegraph/\` index (e.g. a service inside a
|
||||
monorepo, or a second repo), pass its path as \`projectPath\` to
|
||||
\`codegraph_explore\` (and any other codegraph tool). Codegraph resolves the
|
||||
nearest \`.codegraph/\` at or above that path and answers from it — for as many
|
||||
projects as you like in one session.
|
||||
- For a project with no \`.codegraph/\`, use your built-in tools (Read/Grep/Glob)
|
||||
for that project. Indexing is the user's decision — don't run it yourself, but
|
||||
if it comes up they can run \`codegraph init\` in a project to enable codegraph
|
||||
there (a new index is picked up live, no restart).
|
||||
`;
|
||||
|
||||
+24
-19
@@ -16,7 +16,7 @@ import * as path from 'path';
|
||||
import { JsonRpcRequest, JsonRpcNotification, JsonRpcTransport, ErrorCodes } from './transport';
|
||||
import { MCPEngine } from './engine';
|
||||
import { tools } from './tools';
|
||||
import { SERVER_INSTRUCTIONS, SERVER_INSTRUCTIONS_UNINDEXED } from './server-instructions';
|
||||
import { SERVER_INSTRUCTIONS, SERVER_INSTRUCTIONS_NO_ROOT_INDEX } from './server-instructions';
|
||||
import { CodeGraphPackageVersion } from './version';
|
||||
import { findNearestCodeGraphRoot } from '../directory';
|
||||
import { getTelemetry, ClientInfo } from '../telemetry';
|
||||
@@ -189,16 +189,17 @@ export class MCPSession {
|
||||
explicitPath = this.explicitProjectPath;
|
||||
}
|
||||
|
||||
// Pick the instructions variant by the workspace's index state — a cheap
|
||||
// Pick the instructions variant by the root's index state — a cheap
|
||||
// synchronous walk-up (existsSync loop only, no DB open, so the #172
|
||||
// respond-fast contract holds). An unindexed workspace gets the short
|
||||
// "inactive this session" note instead of the full playbook: the playbook
|
||||
// tells the agent to lean on tools that would all fail, and early failures
|
||||
// teach the agent to abandon codegraph entirely. `tools/list` is gated the
|
||||
// same way (empty list when unindexed). When no explicit path is known yet
|
||||
// (roots/list dance pending), cwd is the best predictor of where the
|
||||
// default project will resolve — and on a mismatch the worst case is the
|
||||
// optimistic full playbook backstopped by the empty tool list.
|
||||
// respond-fast contract holds). When the root IS indexed, send the full
|
||||
// single-project playbook. When it ISN'T, send the per-project variant
|
||||
// (tools are still exposed — see handleToolsList): it tells the agent there
|
||||
// is no default project and to pass `projectPath` to any project that has a
|
||||
// `.codegraph/`. Gating tool AVAILABILITY on whether `./` is indexed was the
|
||||
// #964 bug — it broke monorepos (only sub-projects indexed) and never
|
||||
// surfaced the tools after a mid-session `codegraph init`. When no explicit
|
||||
// path is known yet (roots/list dance pending), cwd is the best predictor of
|
||||
// where the default project will resolve.
|
||||
const indexed = findNearestCodeGraphRoot(explicitPath ?? process.cwd()) !== null;
|
||||
|
||||
// Respond to the handshake BEFORE doing any heavy init — see issue #172.
|
||||
@@ -206,7 +207,7 @@ export class MCPSession {
|
||||
protocolVersion: PROTOCOL_VERSION,
|
||||
capabilities: { tools: {} },
|
||||
serverInfo: SERVER_INFO,
|
||||
instructions: indexed ? SERVER_INSTRUCTIONS : SERVER_INSTRUCTIONS_UNINDEXED,
|
||||
instructions: indexed ? SERVER_INSTRUCTIONS : SERVER_INSTRUCTIONS_NO_ROOT_INDEX,
|
||||
});
|
||||
|
||||
if (explicitPath) {
|
||||
@@ -219,15 +220,19 @@ export class MCPSession {
|
||||
|
||||
private async handleToolsList(request: JsonRpcRequest): Promise<void> {
|
||||
await this.retryInitIfNeeded();
|
||||
// An unindexed workspace serves an EMPTY tool list: absence is the one
|
||||
// signal an agent can't misread. Listing 8 tools that all fail wastes the
|
||||
// agent's calls and teaches it codegraph is broken (observed: one or two
|
||||
// early isError responses and the agent stops calling codegraph for the
|
||||
// whole session). A `codegraph init` run after the server started is
|
||||
// picked up on the next tools/list — retryInitIfNeeded re-walks — though
|
||||
// most hosts only request the list once per connection.
|
||||
// Always expose the tools — even when the server root has no index. Gating
|
||||
// availability on whether `./` is indexed (the old behavior) breaks the
|
||||
// monorepo case where only sub-projects carry a `.codegraph/` (the agent
|
||||
// saw zero tools and couldn't even reach an indexed sub-project by
|
||||
// `projectPath`), and it hides the tools from a session that started before
|
||||
// the user ran `codegraph init` (most hosts request the list once, so the
|
||||
// freshly-built index never surfaces). #964. The not-indexed case is still
|
||||
// safe: a call against an un-indexed path returns SUCCESS-shaped guidance
|
||||
// ("pass projectPath / run codegraph init"), never `isError`, so it can't
|
||||
// teach the agent to abandon codegraph. `getTools()` returns the default
|
||||
// surface even before a project is open.
|
||||
this.transport.sendResult(request.id, {
|
||||
tools: this.engine.hasDefaultCodeGraph() ? this.engine.getToolHandler().getTools() : [],
|
||||
tools: this.engine.getToolHandler().getTools(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+10
-7
@@ -438,7 +438,7 @@ export interface ToolResult {
|
||||
*/
|
||||
const projectPathProperty: PropertySchema = {
|
||||
type: 'string',
|
||||
description: 'Path to a different project with .codegraph/ initialized. If omitted, uses current project. Use this to query other codebases.',
|
||||
description: 'Absolute path to the project to query (or any directory inside it) — codegraph uses the nearest .codegraph/ index at or above that path. Omit to use this session\'s default project. Pass it to query a second codebase, or when the server root has no index of its own (e.g. a monorepo where only sub-projects are indexed, so there is no default project).',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -888,13 +888,16 @@ export class ToolHandler {
|
||||
throw new NotIndexedError(
|
||||
'No CodeGraph project is loaded for this session.\n' +
|
||||
`Searched for a .codegraph/ directory starting from: ${searched}\n` +
|
||||
'If this project IS indexed, this is a working-directory detection issue: ' +
|
||||
"the MCP client launched the server outside your project and didn't report the " +
|
||||
'workspace root. Fix it either way:\n' +
|
||||
' • Pass projectPath to the tool call, e.g. projectPath: "/absolute/path/to/your/project"\n' +
|
||||
'Either the server root has no index of its own (e.g. a monorepo where only ' +
|
||||
"sub-projects are indexed), or the MCP client launched the server outside your " +
|
||||
'project without reporting the workspace root. Either way, target the project ' +
|
||||
'explicitly:\n' +
|
||||
' • Pass projectPath to the tool call, e.g. projectPath: "/absolute/path/to/your/project" ' +
|
||||
'(any project that has a .codegraph/ — including a sub-project of a monorepo)\n' +
|
||||
' • Or add --path to the server\'s MCP config args: ["serve", "--mcp", "--path", "/absolute/path/to/your/project"]\n' +
|
||||
'If the project simply has no index, continue with your built-in tools (Read/Grep/Glob) ' +
|
||||
"and don't call codegraph again this session — the user can run 'codegraph init' to enable it."
|
||||
'If a project simply has no index, use your built-in tools (Read/Grep/Glob) for THAT ' +
|
||||
"project (the user can run 'codegraph init' there to enable it) — you can still query " +
|
||||
'other indexed projects by projectPath in the same session.'
|
||||
);
|
||||
}
|
||||
return this.freshen(this.cg);
|
||||
|
||||
Reference in New Issue
Block a user