Documentation
¶
Overview ¶
Package mcp implements the codeiq stdio MCP server.
The server is created once per process by `codeiq mcp`, opens Kuzu in read-only mode, registers all 34 tools, and runs the JSON-RPC protocol loop via the official Anthropic Go SDK (github.com/modelcontextprotocol/go-sdk). Stdin is the JSON-RPC frame reader, stdout the writer, and stderr the log channel. There is no HTTP transport at this layer — codeiq's HTTP surface lives in `internal/api` and is independent.
SDK pin: github.com/modelcontextprotocol/go-sdk v1.6.0 (latest stable at phase 3 start). The plan was drafted against v0.4.0 — the public API moved between those versions:
- Plan was written against an older SDK shape that exposed `mcpsdk.NewStdioTransport(in, out)`. In v1.x the stdio transport is a zero-value `&mcpsdk.StdioTransport{}` hard-bound to os.Stdin and os.Stdout — there is no way to inject pipes. Tests use `mcpsdk.NewInMemoryTransports()` and call `Server.Run(ctx, transport)` for both sides; the CLI passes `&mcpsdk.StdioTransport{}`.
- Plan referenced `mcpsdk.ServerTool` (a {Tool, Handler} pair). v1.x replaces this with `Server.AddTool(t *Tool, h ToolHandler)` where ToolHandler is `func(ctx, *CallToolRequest) (*CallToolResult, error)`. The request's `Params.Arguments` is `json.RawMessage` per `CallToolParamsRaw`.
Our wrapper hides those differences: callers register `mcp.Tool` (name + description + JSON schema + handler) via `Server.Register`, and `Serve(ctx, transport)` delegates to the SDK's `Server.Run`.
Consolidated MCP tools — Plan Phase 2.
34 narrow tools collapsed into 6 mode-driven tools + 1 escape hatch (run_cypher, already in tools_graph.go) + 1 review tool (review_changes, added in Phase 3). Each consolidated tool takes a `mode` string parameter and DELEGATES TO THE EXISTING TOOL HANDLERS — this is a surface change, not a query-layer rewrite. The deprecated 34 stay wired for one release for back-compat with agents pinned to old names.
graph_summary → get_stats, get_detailed_stats, get_capabilities, get_artifact_metadata find_in_graph → query_nodes, query_edges, search_graph, find_node, find_component_by_file, find_related_endpoints inspect_node → get_node_neighbors, get_ego_graph, get_evidence_pack, read_file trace_relationships → find_callers, find_consumers, find_producers, find_dependencies, find_dependents, find_shortest_path analyze_impact → trace_impact, blast_radius, find_cycles, find_circular_deps, find_dead_code, find_dead_services, find_bottlenecks topology_view → get_topology, service_detail, service_dependencies, service_dependents, generate_flow run_cypher → (escape hatch, unchanged)
Tools wiring the architecture-flow MCP tool.
Single tool: generate_flow. Wraps the internal/flow engine; supports the five Java views (overview/ci/deploy/runtime/auth) and the four renderer formats (json/mermaid/dot/yaml). Mirrors Java McpTools .generateFlow but with two extra formats (dot, yaml) that the Java renderer also ships now.
Tools wiring the 20 graph-facing MCP tools per spec §8.
Each tool is a `func(ctx, raw json.RawMessage) (any, error)` that unmarshals its own typed params, applies the result/depth caps from Deps, and delegates to internal/query.Service / Stats / Topology / the graph.Store directly. Tools return either a structured payload (which the SDK marshals as text content) or an ErrorEnvelope when the input is bad. Returning a Go error short-circuits to the SDK's protocol- level error envelope — reserve that for genuine internal failures.
Tools wiring the four intelligence-facing MCP tools per spec §9.
find_node — fuzzy name lookup routed via the QueryPlanner. get_evidence_pack — assembles an EvidencePack via the Assembler. get_artifact_metadata — returns the most recent provenance snapshot. get_capabilities — returns the per-language capability matrix.
Tools wiring the 9 topology-facing MCP tools per spec §8.
Each tool delegates to internal/query.Topology — the Java side's getCachedData() 60s heap snapshot is NOT replicated; per spec §8 and the documented gotcha each topology operation runs targeted Cypher against the structural CONTAINS edges so peak memory stays flat regardless of graph size.
Index ¶
- Constants
- func CapDepth(requested, hardCap int) int
- func CapResults(requested, hardCap int) int
- func NewRequestID() string
- func RegisterConsolidated(srv *Server, d *Deps) error
- func RegisterFlow(srv *Server, d *Deps) error
- func RegisterGraph(srv *Server, d *Deps) error
- func RegisterGraphUserFacing(srv *Server, d *Deps) error
- func RegisterIntelligence(srv *Server, d *Deps) error
- func RegisterTopology(srv *Server, d *Deps) error
- func RequestID(ctx context.Context) string
- func WithRequestID(ctx context.Context, id string) context.Context
- type Deps
- type ErrorEnvelope
- type Handler
- type ReadFileRequest
- type ReadFileResponse
- type Registry
- type Server
- type ServerOptions
- type Tool
Constants ¶
const ( CodeInternalError = "INTERNAL_ERROR" CodeInvalidInput = "INVALID_INPUT" CodeFileReadFailed = "FILE_READ_FAILED" CodeSerializationFailed = "SERIALIZATION_FAILED" )
Error code constants. Mirror the four codes the Java McpTools.errorEnvelope emits today. New codes must be added on both sides — the legacy `error` field is kept verbatim for backwards compatibility with MCP clients that read `error` directly (see the McpTools envelope gotcha in CLAUDE.md).
const ( DefaultMaxResults = 500 DefaultMaxDepth = 10 DefaultQueryTimeout = 30 // seconds — DBMS-level wall-clock cap, mirrors Neo4jConfig )
Default per-call limits. Mirror McpLimitsConfig defaults in the Java side (ConfigDefaults.builtIn): perToolTimeoutMs=15000, maxResults=500, maxPayloadBytes=2_000_000, ratePerMinute=300, maxDepth=10. The Go side owns its own defaults today and will read codeiq.yml `mcp.limits.*` once the unified config port lands.
Variables ¶
This section is empty.
Functions ¶
func CapDepth ¶
CapDepth clamps a traversal-depth to [1, hardCap]. Mirrors Java `Math.min(depth, maxDepth)` with a positive floor. Phase 3 default hardCap is McpLimitsConfig.MaxDepth (10) loaded from codeiq.yml at server boot.
hardCap <= 0 falls back to DefaultMaxDepth.
func CapResults ¶
CapResults clamps a caller-supplied result-count to [1, hardCap]. Mirrors Java McpTools `Math.min(limit, maxResults)` with a positive floor. The cap is enforced in each tool's iteration loop (NOT injected as `LIMIT N` into Cypher), per the spec §8 gotcha.
hardCap <= 0 falls back to DefaultMaxResults so callers that haven't loaded a config yet still get sane behaviour.
func NewRequestID ¶
func NewRequestID() string
NewRequestID generates a fresh UUIDv4 request id. Server middleware calls this once per tool invocation before dispatch.
func RegisterConsolidated ¶
RegisterConsolidated appends the 6 new tools to srv. Wired alongside the deprecated 34 by the CLI's mcp boot path.
func RegisterFlow ¶
RegisterFlow appends every flow-facing tool to srv. Symmetric with RegisterGraph / RegisterTopology.
func RegisterGraph ¶
RegisterGraph appends every graph-facing tool to srv. Errors halt the loop so a duplicate name surfaces immediately during server boot.
func RegisterGraphUserFacing ¶
RegisterGraphUserFacing registers only the user-facing graph-tier tools (run_cypher + read_file). Used by production cli wiring — the 18 narrow tools were dropped from the user MCP surface in favor of the 6 consolidated mode-driven tools.
func RegisterIntelligence ¶
RegisterIntelligence appends every intelligence-facing tool to srv. Symmetric with RegisterGraph / RegisterTopology / RegisterFlow.
func RegisterTopology ¶
RegisterTopology appends every topology-facing tool to srv. Errors halt the loop so a duplicate name surfaces immediately at server boot. Symmetric with RegisterGraph; designed to be invoked once at startup.
Types ¶
type Deps ¶
type Deps struct {
// Store is the read-only Kuzu handle opened by `codeiq mcp` at
// server boot.
Store *graph.Store
// Query owns the high-level read service (consumers / producers /
// callers / dependencies / shortest path / cycles / dead code).
// Mirrors Java QueryService.
Query *query.Service
// Stats owns the StoreStatsService façade (rich categorized stats).
Stats *query.StoreStatsService
// Topology owns the service-topology projection (Topology /
// ServiceDetail / BlastRadius / FindPath / Bottlenecks / Circular
// / DeadServices).
Topology *query.Topology
// Flow owns the architecture-flow-diagram engine. Wired by `codeiq
// mcp` from a *graph.Store-backed Store. nil disables generate_flow.
Flow *flow.Engine
// Evidence owns the evidence-pack assembler for the
// `get_evidence_pack` tool. nil disables that tool (the handler
// returns the legacy "Evidence pack service unavailable. Run
// 'enrich' first." envelope to match the Java contract).
Evidence *evidence.Assembler
// QueryPlanner routes the find_node tool through GRAPH_FIRST /
// LEXICAL_FIRST / MERGED / DEGRADED. nil falls back to GRAPH_FIRST
// for every query (legacy behaviour pre-Planner).
QueryPlanner *iqquery.Planner
// ArtifactMeta is the most recent provenance snapshot bundled into
// `get_artifact_metadata` and every evidence pack. nil yields the
// legacy "Artifact metadata unavailable. Run 'enrich' first."
// envelope.
ArtifactMeta *evidence.ArtifactMetadata
// RootPath is the absolute repo root the read_file tool resolves
// caller paths against. Empty disables the read_file tool.
RootPath string
// MaxResults caps caller-supplied result counts (e.g. limit=1000
// hits CapResults(limit, MaxResults)). Defaults are filled in via
// CapResults if MaxResults <= 0.
MaxResults int
// MaxDepth caps caller-supplied traversal depths (e.g.
// trace_impact / ego_graph). Defaults via CapDepth.
MaxDepth int
}
Deps is the bundle of services every tool handler receives. Keep it small — adding fields here is a sign a tool wants to reach across layers. Prefer narrowing the interface in the tool registration site.
Phase 3 wired:
- Store + Query + Stats + Topology cover the 20 graph tools and 9 topology tools.
- Flow drives the generate_flow tool.
- Evidence + QueryPlanner + ArtifactMeta drive the four intelligence tools (find_node, get_evidence_pack, get_artifact_metadata, get_capabilities).
type ErrorEnvelope ¶
type ErrorEnvelope struct {
Code string `json:"code"`
Message string `json:"message"`
RequestID string `json:"request_id,omitempty"`
Error string `json:"error,omitempty"`
}
ErrorEnvelope is the structured failure shape returned by every MCP tool. The legacy `error` field is preserved as a mirror of `message` for backwards compatibility with tool clients reading `error` directly. Do not drop it without grepping downstream consumers first.
func NewErrorEnvelope ¶
func NewErrorEnvelope(code string, err error, requestID string) ErrorEnvelope
NewErrorEnvelope packages a code + error + request id into the standard shape. err.Error() is surfaced as both `message` and `error` (legacy mirror). A nil err is replaced with "(no message)" so the field is never empty in the wire payload — matches Java's McpTools.errorEnvelope behaviour exactly.
type Handler ¶
Handler runs a single tool invocation. params is the raw JSON object sent by the client; the handler unmarshals into its own typed struct. The returned value is JSON-marshaled and wrapped as a text-content CallToolResult. Returning an error short-circuits to the SDK error envelope — most tools should instead return an `ErrorEnvelope` value and a nil error so the result reaches the client as structured JSON.
type ReadFileRequest ¶
type ReadFileRequest struct {
Root string // Repo root (must be absolute or resolvable). Required.
Path string // Caller-supplied relative path under Root.
StartLine int // 1-based inclusive; 0 = read from line 1.
EndLine int // 1-based inclusive; 0 = read to EOF.
MaxBytes int64 // Byte cap; truncate beyond this.
}
ReadFileRequest is the typed input for the read_file tool / library surface. All fields are required except StartLine and EndLine; the caller is expected to default MaxBytes to McpLimitsConfig.maxPayloadBytes (2 MB by default) when forwarding from MCP.
type ReadFileResponse ¶
type ReadFileResponse struct {
Path string `json:"path"`
Content string `json:"content"`
Truncated bool `json:"truncated,omitempty"`
StartLine int `json:"start_line,omitempty"`
EndLine int `json:"end_line,omitempty"`
MimeType string `json:"mime_type,omitempty"`
}
ReadFileResponse is what the tool returns when the read succeeds.
func ReadRepoFile ¶
func ReadRepoFile(req ReadFileRequest) (*ReadFileResponse, error)
ReadRepoFile resolves req.Path under req.Root with symlink + traversal protection, validates the file is a permitted text MIME type, and returns at most MaxBytes of content (optionally line-sliced).
Mirrors Java SafeFileReader + GraphController.readFile exactly:
- Lexical traversal check before any FS access (rejects `..` segments).
- filepath.EvalSymlinks on both root and candidate; second-stage containment check after symlink resolution catches a symlink inside the repo that points to /etc/passwd.
- http.DetectContentType (RFC-2046-ish sniffing) against an allowlist.
- Byte cap enforced in the read loop; if MaxBytes <= 0 the cap defaults to 1 MiB to keep the function safe to call without a ConfigDefaults wired up.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry collects MCP tools. Insertion order is preserved so `tools/list` returns tools in a deterministic order (matches the Java side where Spring iterates beans in registration order).
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns an empty registry ready for Add calls.
func (*Registry) Add ¶
Add registers a tool. Duplicate names, empty names, and nil handlers all return an error rather than panicking — RegisterAll wires many tools at server boot and one bad entry should not abort the rest.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the stdio MCP server. One per `codeiq mcp` process. Tools are registered via Register, then Serve is called with a transport (StdioTransport in production, NewInMemoryTransports in tests).
func NewServer ¶
func NewServer(opts ServerOptions) (*Server, error)
NewServer constructs an unstarted Server. Tools are registered separately via Register before calling Serve. Returns an error when required options are missing — currently only Name is mandatory.
func (*Server) Register ¶
Register adds a Tool to the registry. Must be called before Serve. Concurrency-safe — the registry mutex serializes adds.
func (*Server) Registry ¶
Registry exposes the underlying Registry for read-only inspection (used by tests like TestRegisterGraphRegistersAllTwentyTools). Callers must not mutate the registry after Serve has been called.
func (*Server) Serve ¶
Serve runs the MCP protocol loop on the supplied transport. Blocks until the transport closes or ctx is cancelled. The transport choice determines stdin/stdout vs in-memory vs HTTP behaviour; see the package doc for the v0.8.0 SDK quirk re: StdioTransport's hard-coded os.Stdin/os.Stdout binding.
type ServerOptions ¶
type ServerOptions struct {
// Name is the protocol-level server name advertised in `initialize`.
// Matches the Java side `spring.ai.mcp.server.name` value: "CODE MCP".
Name string
// Version of the codeiq binary (build-info Version string).
Version string
}
ServerOptions configures the codeiq MCP server.
type Tool ¶
type Tool struct {
Name string
Description string
Schema json.RawMessage
Handler Handler
}
Tool is a single MCP tool: name, description, JSON-Schema for params, and a handler. Schemas are hand-written as `json.RawMessage` to mirror the Java side's `@McpToolParam` descriptions verbatim — the v0.8.0 SDK accepts any value that JSON-marshals to a valid schema in `InputSchema`.