mcp

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 14, 2026 License: MIT Imports: 20 Imported by: 0

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

View Source
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).

View Source
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

func CapDepth(requested, hardCap int) int

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

func CapResults(requested, hardCap int) int

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

func RegisterConsolidated(srv *Server, d *Deps) error

RegisterConsolidated appends the 6 new tools to srv. Wired alongside the deprecated 34 by the CLI's mcp boot path.

func RegisterFlow

func RegisterFlow(srv *Server, d *Deps) error

RegisterFlow appends every flow-facing tool to srv. Symmetric with RegisterGraph / RegisterTopology.

func RegisterGraph

func RegisterGraph(srv *Server, d *Deps) error

RegisterGraph appends every graph-facing tool to srv. Errors halt the loop so a duplicate name surfaces immediately during server boot.

func RegisterGraphUserFacing

func RegisterGraphUserFacing(srv *Server, d *Deps) error

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

func RegisterIntelligence(srv *Server, d *Deps) error

RegisterIntelligence appends every intelligence-facing tool to srv. Symmetric with RegisterGraph / RegisterTopology / RegisterFlow.

func RegisterTopology

func RegisterTopology(srv *Server, d *Deps) error

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.

func RequestID

func RequestID(ctx context.Context) string

RequestID returns the request id stored on ctx, or "" if unset. Tool handlers call this to populate the `request_id` field on the envelope when they return an error.

func WithRequestID

func WithRequestID(ctx context.Context, id string) context.Context

WithRequestID returns ctx augmented with a request id. Used by tool dispatch wrappers; tests may also call this directly.

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

type Handler func(ctx context.Context, params json.RawMessage) (any, error)

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

func (r *Registry) Add(t Tool) error

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.

func (*Registry) All

func (r *Registry) All() []Tool

All returns a defensive copy of the registered tools in registration order. Mutating the returned slice does not affect the registry.

func (*Registry) Names

func (r *Registry) Names() []string

Names returns the registered tool names in registration order. Used by `TestRegisterGraphRegistersAllTwentyTools` (and the topology/flow analogues) to assert the wiring without inspecting handlers.

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

func (s *Server) Register(t Tool) error

Register adds a Tool to the registry. Must be called before Serve. Concurrency-safe — the registry mutex serializes adds.

func (*Server) Registry

func (s *Server) Registry() *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

func (s *Server) Serve(ctx context.Context, transport mcpsdk.Transport) error

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`.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL