Matrix logo

MCP Server

The MCP server exposes tachyon verbs as MCP tools over stdio newline-delimited JSON-RPC. This is the primary transport for LLM agent integration when the daemon runs as a subprocess.

Source files: pkg/mcp/server.go, pkg/mcp/tools.go

The MCP (Model Context Protocol) server exposes tachyon verbs as MCP tools over stdio newline-delimited JSON-RPC. This is the primary transport for LLM agent integration when the daemon runs as a subprocess.


Design decisions

Stdio NDJSON-RPC

MCP uses stdin/stdout with newline-delimited JSON-RPC 2.0 messages. The server reads one line at a time (1 MiB buffer), parses the request, dispatches to the engine, and writes the response. stdout is flushed after every message so clients block until the response is ready.

scanner := bufio.NewScanner(os.Stdin)
scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
for scanner.Scan() {
    line := scanner.Text()
    resp := handle(eng, req.Method, req.Params, req.ID)
    send(resp)  // marshal + write + sync
}

Log redirection

In MCP mode, logs are written to stderr so they do not corrupt the NDJSON-RPC stream on stdout. The daemon detects MCP mode via the --mcp flag and reconfigures the slog handler accordingly.

Tool registry

The MCP tool list is a static registry in tools.go. Nine tools are registered:

ToolDescription
tachyon_compileBuild Solidity contracts via forge
tachyon_testRun Forge tests with structured JSON results
tachyon_simulateDry-run eth_call without broadcasting
tachyon_deployIntent-based deploy with idempotency key
tachyon_callContract call (simulate_only or broadcast)
tachyon_chain_listList configured chain RPC profiles
tachyon_chain_registerRegister a custom chain profile
tachyon_artifact_getFetch cached ABI/bytecode by contract name
tachyon_registry_lookupResolve prior deployment by idempotency key

Each tool has a name, description, and input schema. The schema is permissive (additionalProperties: true) because the actual validation happens in the engine.

Selftest

The --selftest flag verifies that the tool registry matches the canonical list:

func Selftest() error {
    tools := Tools()
    if len(tools) != len(ToolNames) { ... }
    // verify every ToolNames entry exists in Tools()
}

This is used in CI to catch tool drift (adding a tool to the engine but not to the MCP registry).

Error formatting

MCP tool errors return isError: true with JSON text content:

{
  "content": [{"type": "text", "text": "{\"ok\":false,\"tool\":\"tachyon_deploy\",\"error\":{...}}"}],
  "isError": true
}

The FormatToolError helper builds this structure from a types.Error.

Method dispatch

MCP MethodHandler
initializeReturn protocol version (2024-11-05), server info (tachyon-tools + version), capabilities
tools/listReturn tool descriptors
tools/callParse tool name + arguments, dispatch to engine via rpc.DispatchForMCP
notifications/initializedNo-op (ack)
pingNo-op (ack)

The tools/call handler routes to the engine through the JSON-RPC dispatcher (pkg/rpc.DispatchForMCP), with tachyon_chain_list short-circuited directly to eng.ChainList().


Modifying the MCP server

What to changeWhere
Add MCP toolpkg/mcp/tools.go - add to ToolNames and Tools()
Change protocol versionpkg/mcp/server.go - initialize handler
Add MCP capabilitypkg/mcp/server.go - initialize capabilities map
Change error formatpkg/mcp/server.go - FormatToolError