feat(mcp): emit server-level instructions in initialize response#121
Closed
andreinknv wants to merge 3 commits into
Closed
feat(mcp): emit server-level instructions in initialize response#121andreinknv wants to merge 3 commits into
andreinknv wants to merge 3 commits into
Conversation
…flicts
Today every PR adding an MCP tool conflicts on the same two
shared lists in src/mcp/tools.ts: the tools[] array (the
list_tools surface) and the case switch in execute(). After this
refactor:
Adding a new MCP tool:
1. Drop a file at src/mcp/tools/<name>.ts exporting a
<NAME>_TOOL: ToolModule (definition + handlerKey).
2. Add one import line and one array entry to
src/mcp/tools/registry.ts.
3. Implement handle<Name>(args) on ToolHandler in tools.ts and
add the new key to HandlerKey in tools/types.ts.
Step 3 is the only remaining "shared method on a single class"
conflict surface. Extracting handler bodies into per-tool files
(making step 3 also a single-file addition) is left as a
follow-up — the cost/benefit favors landing this incremental win
now and finishing the body extraction once language and migration
refactors land.
## What's new
- **src/mcp/tool-types.ts** — extracted ToolDefinition, ToolResult,
PropertySchema, projectPathProperty into a shared module so
per-tool files can import without circular dependency.
- **src/mcp/tools/types.ts** — ToolModule interface, HandlerKey
string union, and ToolHandlerLike (a structural type that
ToolHandler now `implements`, providing compile-time guarantee
that every HandlerKey maps to a real method).
- **src/mcp/tools/<name>.ts × 9** — one file per existing tool
(callees, callers, context, explore, files, impact, node, search,
status). Each ~25-30 lines: import + definition literal +
handlerKey reference.
- **src/mcp/tools/registry.ts** — static-import barrel, sorted
alphabetically. Exports getToolModules(), getToolModule(name),
and the derived `tools[]` array.
- **src/mcp/tools.ts** — ~200 lines deleted from the top
(inline types + tools[] array + projectPathProperty).
execute()'s case-switch replaced with a registry lookup +
type-safe `this[mod.handlerKey](args)` dispatch (now compile-
time-checked thanks to `implements ToolHandlerLike`).
All `private async handle*` methods now public to match the
interface. errorResult/textResult also public for the same reason.
- **src/mcp/index.ts** — MCPServer's tool-existence check switched
from a linear `tools.find()` scan to the O(1) `getToolModule()`
Map lookup, eliminating two parallel lookup paths.
## Tests
387/387 pass. **7 new tests** in __tests__/mcp-tool-registry.test.ts:
- Definitions are well-formed (name shape, description length).
- handlerKey shape (`handle<UpperCase>`).
- Every registered handlerKey resolves to a real method on
ToolHandler.
- Exported `tools[]` exactly mirrors the registry.
- Canonical 9 main-line tools regression guard.
- execute() unknown-tool error path.
- **End-to-end dispatch smoke test**: execute('codegraph_status', {})
reaches the real handler body (no broken `this` binding) — would
fail loudly if the dynamic dispatch chain ever breaks.
## Reviewer pass
Independent reviewer ran once. 2 REQUEST_CHANGES + 2 INFO addressed:
1. ToolHandlerLike was defined but never enforced —
ToolHandler now `implements ToolHandlerLike`. Eliminates the
`(this as unknown as Record<...>)` cast in execute(); dispatch
is fully compile-time-checked.
2. No end-to-end dispatch test — added one (see Tests above).
3. MCPServer.handleToolsCall used a linear `tools.find()` scan
while execute() used Map lookup — switched to getToolModule()
for parity.
4. Removed redundant .slice() in registry.ts (map() already
returns a fresh array).
## Backward compat
src/mcp/tools.ts still re-exports ToolDefinition, ToolResult, the
mutable `tools[]` array, ToolHandler, and getExploreBudget. Every
existing consumer (`import { ToolDefinition, ToolResult, tools,
ToolHandler } from './tools'`) keeps working unchanged.
## Affected open PRs
- colbymchenry#110 (review-context): rebases to 1 new file in tools/ + 2
lines in registry.ts + 1 method on ToolHandler + 1 line in
HandlerKey.
- colbymchenry#112 (centrality+churn): same shape for the codegraph_hotspots
tool.
- colbymchenry#114 (config-refs): same shape for codegraph_config.
- colbymchenry#115 (sql-refs): same shape for codegraph_sql.
Each goes from 4-way conflict (tools[] + case + handler + helpers)
down to 1-way conflict (HandlerKey + handler method on ToolHandler,
both in tools.ts).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 28, 2026
The MCP `initialize` response can include an `instructions` field that clients (Claude Code, Cursor, opencode, LangChain, OpenAI Agent SDK, etc.) surface in the agent's system prompt automatically. Today codegraph emits an empty initialize response — agents only see individual tool descriptions, no overall guidance on how to compose them. This adds the missing playbook: - **Tool selection by intent** — quick map from "what is X" / "how does X work" / "what would changing X break" to the right tool. - **Common chains** — onboarding (context first), PR review (review_context), refactor planning (search → callers → impact), debugging a regression. - **Tier discipline** — start at the cheap deterministic tier (search, context, callers, callees, impact, node, explore, files, status), escalate to conditional tools only when their data exists, and only reach for LLM-mediated tools when the cheap path doesn't suffice. - **Agent-bridge tier** — explicit recipe for projects without a local LLM where the agent itself summarizes via codegraph_pending_summaries + codegraph_save_summaries. - **Anti-patterns** — don't grep when search exists, don't chain search+node when context covers it, don't query the index immediately after a write. Lives in src/mcp/server-instructions.ts so it's easy to update without touching the JSON-RPC dispatch in src/mcp/index.ts. Single-file, no schema changes, no migrations, no test changes needed. References tools that exist on `main` today; doesn't presume any of the in-flight feature PRs (colbymchenry#110, colbymchenry#112-115, colbymchenry#111) have landed. After those merge, the relevant sections of this guidance start applying without needing a follow-up edit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…kers Two new tools landed in colbymchenry#124 and colbymchenry#125 that this playbook should route the agent to instead of falling back to "read the source": - codegraph_biomarkers (PR colbymchenry#125): structured static-analysis signals (Code Health, cyclomatic, nesting, length) so an agent can ask "is this function risky to change?" without reading the source. - codegraph_coverage (PR colbymchenry#124): per-symbol coverage from lcov so an agent can ask "is this function tested?" with a structured answer. Updates: - "When to use which tool" map gains two entries. - Refactor-planning chain expanded to call both tools before callers/impact -- and points at the killer cross-tool query (high-centrality + warning-severity findings). - Tier table places biomarkers in tier 1 (always available after colbymchenry#125 lands) and coverage in tier 2 (conditional on a prior `codegraph coverage <lcov>` ingestion). Both references are forward-compatible: agents that try to call a not-yet-merged tool get a graceful "unknown tool" error, same pattern the existing playbook already uses for colbymchenry#110, colbymchenry#111, etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
c4a3336 to
7ce63d2
Compare
Owner
|
Closing — same situation as #122. The PR is also stacked on #117's tool-registry refactor (the per-tool The core idea is good — telling agents to reach for |
2 tasks
colbymchenry
added a commit
that referenced
this pull request
May 8, 2026
Adds a universal tool-selection playbook surfaced by MCP clients (Claude Code, Cursor, opencode, LangChain, OpenAI Agent SDK) in the agent's system prompt automatically. Without this, agents have to infer tool composition from individual tool descriptions and tend to walk callers manually instead of reaching for codegraph_impact, etc. Scoped tight: only the 9 tools that exist on main today (search/context/callers/callees/impact/node/explore/files/status), no "(when present)" references to unmerged tools, no per-language guidance. ~40 lines of useful guidance. Salvaged from #121, which bundled the instructions with #117's MCP tool-registry refactor and referenced many tools that don't exist on main. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The MCP
initializeresponse can include aninstructionsfield that clients (Claude Code, Cursor, opencode, LangChain, OpenAI Agent SDK, …) surface in the agent's system prompt automatically. Today codegraph emits an empty initialize response — agents only see individual tool descriptions, with no overall guidance on how to compose them.This adds the missing playbook in a new
src/mcp/server-instructions.tsmodule, wired into theinitializehandler.Empirical validation (A/B test)
Tested in-session by running the same task two ways and counting tool calls:
Task: "Predict the blast radius of changing
extractFromSourcein the codegraph codebase."codegraph_search→codegraph_callers→ ~5 more recursive walkscodegraph_impact("extractFromSource")The playbook's mapping
"What would changing this break?" → codegraph_impactsaved ~6 redundant tool calls and produced a more complete answer. The benefit isn't theoretical — without the meta-guidance, the natural agent instinct is to start withcodegraph_search(the most general-sounding tool) and walk the call graph manually. Tool descriptions alone don't redirect that instinct.What the instructions teach the agent
codegraph_pending_summaries+codegraph_save_summaries.Why MCP-level vs CLAUDE.md
CLAUDE.md is a Claude-Code-only convention. The MCP
instructionsprotocol field reaches every client. Both can coexist — the existing CLAUDE.md template still covers the Claude-Code-specific Explore-agent pattern. This PR adds the universal playbook on top.Why a separate PR
Originally considered as part of #111 (LLM tools), but pulled out because:
maintoday; they don't presume feat(mcp): codegraph_review_context — structured PR-review context from a diff #110/feat(graph): PageRank centrality + per-file churn metrics #112-feat(graph): sql_refs — SQL string-literal call sites (pairs with #95) #115/feat: LLM symbol summaries, semantic search, RAG Q&A, dir/role/dead-code/naming + agent-as-LLM bridge #111 have landed. After those merge, the relevant sections of the guidance simply start applying.Per-language guidance — intentionally not included
Considered (and explicitly rejected): per-language sections like "in Python, callers includes decorators." Reasons:
codegraph_sql,codegraph_config), the tool description already self-documents.If we ever want this, the principled implementation is dynamic per-project tailoring at initialize time (only emit the SQL section if the project has SQL nodes, etc.). Out of scope here.
Test plan
npx tsc --noEmitcleannpx vitest runclean (no test changes — the JSON-RPC initialize response is structurally compatible)Files changed
src/mcp/server-instructions.tssrc/mcp/index.ts🤖 Generated with Claude Code