Documentation
¶
Overview ¶
Package mcphandlers exposes ckg's MCP tool handlers as a public, reusable surface so sister repos (code-knowledge-system / cks, code-knowledge-vector / ckv) can wire their own mcp-go servers to the same eight-tool set without re-implementing the bodies.
The handlers live here rather than under internal/mcp/ because the Go internal/ rule blocks external import of anything inside it. Moving them to pkg/ is the T-14 public-surface promotion described in eval/stablenet/HANDOFF.md.
Tool catalogue ¶
find_symbol resolve a qname / name to nodes find_callers reverse call graph over calls + invokes edges find_callees forward call graph over calls + invokes edges get_subgraph bidirectional BFS rooted at qname search_text FTS5 search with AND/OR mode + language pushdown impact_of_change reverse-dependency closure (broader than callers) get_context_for_task smart 1-shot: BM25 -> 1-hop -> score-fuse -> pack evidence_for_intent H3 EvidencePack assembler over commit/hunk corpus
H3 retrieval boundary ¶
Every handler operates on a wrapped Reader (see NewLLMSafeReader) that filters AMBIGUOUS Hunk + Commit nodes — the §11.3 unreachable-history track — out of every result set before it reaches an LLM. RegisterAll applies the wrapper for the entire tool set; individual Register* callers must wrap their reader themselves if they want the same guarantee.
Minimal cks-side wiring ¶
import (
server "github.com/mark3labs/mcp-go/server"
"github.com/0xmhha/code-knowledge-graph/pkg/mcphandlers"
"github.com/0xmhha/code-knowledge-graph/pkg/store"
)
r, err := store.OpenReadOnly("/tmp/ckg-graph")
if err != nil { /* ... */ }
defer r.Close()
s := server.NewMCPServer("cks-mcp", "0.1.0")
mcphandlers.RegisterAll(s, r)
_ = server.ServeStdio(s)
Stability ¶
The Register* function signatures and the tool schema definitions follow semantic versioning once the cks/ckv extractions land. Adding a new optional argument to a tool is a non-breaking change; removing or renaming an existing argument is breaking.
Index ¶
- func NewLLMSafeReader(r store.Reader) store.Reader
- func RegisterAll(s *server.MCPServer, reader store.Reader)
- func RegisterChangeHistory(s *server.MCPServer, reader store.Reader)
- func RegisterConcurrencyImpact(s *server.MCPServer, reader store.Reader)
- func RegisterEvidenceForIntent(s *server.MCPServer, reader store.Reader, cache *evidence.Cache)
- func RegisterFindCallees(s *server.MCPServer, reader store.Reader)
- func RegisterFindCallers(s *server.MCPServer, reader store.Reader)
- func RegisterFindSymbol(s *server.MCPServer, reader store.Reader)
- func RegisterGetContextForTask(s *server.MCPServer, reader store.Reader)
- func RegisterGetSubgraph(s *server.MCPServer, reader store.Reader)
- func RegisterImpactOfChange(s *server.MCPServer, reader store.Reader)
- func RegisterSearchText(s *server.MCPServer, reader store.Reader)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewLLMSafeReader ¶
NewLLMSafeReader wraps r so AMBIGUOUS Hunk/Commit nodes are stripped from every read result. Every Register* handler applies this boundary itself via [safeReader], so the §11.3 H3 boundary holds no matter how tools are mounted; this constructor stays exported for callers that want the wrapped reader for their own use.
func RegisterAll ¶
RegisterAll wires every ckg tool to s in one call. Equivalent to invoking each Register* function individually plus RegisterEvidenceForIntent with a fresh evidence.NewCache.
The §11.3 H3 retrieval boundary is enforced inside every Register* (each wraps its reader via safeReader, idempotently), so it holds for the whole tool set here and for any individual handler mounted on its own — callers can no longer forget to wrap. Callers that need a different lifecycle (one cache across multiple servers, a partial tool subset) can still compose the individual Register* calls directly.
Existing callers within this module use this to keep server.go down to ~10 lines; sister-repo wiring (cks / ckv) is recommended to use this too unless there's a specific reason not to.
func RegisterChangeHistory ¶
RegisterChangeHistory wires the change_history tool: the merged pull requests that touched a symbol (number, title, summary, merged-at), newest first. It exposes the graph's existing per-node PR breadcrumbs (Reader.GetNodePRs) over MCP so an agent can answer "when/why did this code change" from a single HEAD graph — no pre-fix re-index or git fallback.
func RegisterConcurrencyImpact ¶
RegisterConcurrencyImpact wires the concurrency_impact tool — the concurrency blast radius of a symbol over the five contract edge types (spawns, sends_to, recvs_from, acquires_lock, accessed_under_lock; R1' contract C1, S1). The algorithm body lives in pkg/concurrency so cks consumes it in-process; this file is the MCP request envelope only (the dev-only ckg server, 00 §7), mirroring RegisterImpactOfChange.
func RegisterEvidenceForIntent ¶
RegisterEvidenceForIntent wires the H3 EvidencePack assembler (docs/design/hunk-graph.md §5). Returns an EvidencePack JSON for an intent string + optional seed_qname / issue_id, ranked by BM25 over the (commit subject || patch || modifies-qnames) virtual document and grouped by parent commit.
The cache parameter is shared across the lifetime of one Run invocation so the BM25 corpus is built once and reused across queries — sub-second latency on graphs that take ~4s for a cold rebuild. The cache invalidates itself when the underlying graph.db's manifest drifts (a `ckg build` while the server is running). RegisterAll passes a fresh evidence.NewCache so callers get the standard lifetime without having to thread it themselves; if you need to share one cache across multiple servers, call this function directly.
§11.3 retrieval boundary is enforced two ways:
- When wired via RegisterAll, reader is already wrapped by NewLLMSafeReader, so AllNodes/AllEdges/GetBlob filter AMBIGUOUS Hunks/Commits at the read layer.
- pkg/evidence.indexCorpus also filters confidence='EXTRACTED' as defence in depth — even if a future caller bypasses the wrapper, the EvidencePack assembler still hides the unreachable-history track from LLM consumers.
func RegisterFindCallees ¶
RegisterFindCallees walks the forward call graph from the seed. Same edge-type filter as RegisterFindCallers for symmetry.
func RegisterFindCallers ¶
RegisterFindCallers walks the reverse call graph from the seed. Filters to calls/invokes edges so the BFS only follows real invocation links (see callEdgeTypes for the rationale). Default depth=2 — see docs/ckg5-depth-sweep-report-2026-05-20.md for the latency justification.
func RegisterFindSymbol ¶
RegisterFindSymbol resolves an exact-or-suffix qname / name to nodes.
Description text was rewritten 2026-05-11 (go-stablenet VERIFICATION_REPORT §3.1 B2): the prior phrasing implied a bare name worked with exact=true, but FindSymbol always matches qualified_name — bare names work only with exact=false (suffix match). The schema spells that contract out so LLM agents don't false-empty on `{"name":"NewBlockChain","exact":true}`.
func RegisterGetContextForTask ¶
RegisterGetContextForTask wires the smart 1-shot retrieval tool: BM25 retrieve -> 1-hop expand -> score-fuse -> diversify -> pack within token budget. The body lives in pkg/smartctx so the eval δ baseline measures exactly the same algorithm the LLM consumer runs at request time.
1-shot enrichment (P0 #2, docs/PROJECT-BLUEPRINT-ALIGNMENT.md §4.2): opt-in flags include_recent_prs and include_impact fold the PR breadcrumb and reverse-deps closures into the same response, so a coding-agent can answer "what is this, why did it change, what depends on it?" in a single tool call.
func RegisterGetSubgraph ¶
RegisterGetSubgraph returns the BFS bidirectional subgraph rooted at qname. Unlike find_callers / find_callees this DOES follow every edge type — the caller asked for a neighbourhood, not a call graph.
func RegisterImpactOfChange ¶
RegisterImpactOfChange wires the impact_of_change tool. Either seed_qname or seed_file must be set; if both are set, seed_qname wins (less ambiguous, and the qname path returns a single seed node for the response envelope).
The algorithm body lives in pkg/impact so the same code path is shared with internal/server's HTTP /api/impact handler — mirrors the smartctx pattern (pkg/smartctx is shared by get_context_for_task and the eval δ baseline). This file is the MCP request envelope only.
func RegisterSearchText ¶
RegisterSearchText runs the smart Search router (FTS5 with auto-prefix for ASCII, LIKE substring fallback for CJK). Routes through attachBlobs so the response shape matches find_symbol / find_callers / get_subgraph — LLM clients can parse one schema across the toolbox.
mode = "or" (default) ORs multi-token queries via rewriteFTSQuery (any token match surfaces the candidate, BM25 + PageRank + usage rerank). mode = "and" requires every token to appear in the hit's FTS-indexed columns; useful for precise multi-keyword retrieval. language pushes a `WHERE language = ?` filter into the SQL when non-empty (CKG-2).
node_kinds is the optional whitelist of node types to return. The default (omitted / empty) applies a symbol-only filter that strips statement-level rows (IfStmt/LoopStmt/CallSite/ReturnStmt/SwitchStmt/ AwaitPoint), meta rows (Commit/Hunk), and path-only rows (Import/Export). Pass an explicit array — typically the full set produced by types.AllNodeTypes — to disable the default narrowing and surface every FTS match.
Types ¶
This section is empty.