// Package mcp is Maven's Model Context Protocol CLIENT. She is a host: she // connects OUT to MCP servers, discovers the tools and resources they offer, // and hands them to the parts of her that already exist for this — the tool // allowlist in the store, the confirm turn for anything that mutates, the // stage-3 gate that makes an uncertain act ask instead of run. // // She is not an MCP server. Nothing here exposes her own capabilities to an // outside caller; docs/plans/06-mcp-support.md asks for the host direction only. // // Boundaries, in code rather than in prose: // // - OFF unless configured. No mcp_servers block ⇒ no manager, no goroutine, // no socket. // - A remote server is reached through internal/webfetch, so the SSRF guard, // the size cap, the redirect cap and the per-host rate limit all apply to // an MCP endpoint exactly as they do to a news feed. Reaching a loopback // or LAN server means explicitly setting allow_private on THAT server — // a different trust level, spelled out per server rather than globally. // - Only the tool name and the arguments the router produced are sent. This // package never sees his notes, facts, history or the persona block, and // has no API through which a caller could pass them. // - Discovery proposes, it does not enable. A discovered tool lands as a // 'proposed' row; a human enables it on the authed surface. package mcp import ( "context" "encoding/json" "fmt" ) // ProtocolVersion — the spec revision we ask for in the initialize handshake. // A server that answers with a different one is accepted (the spec says the // client may proceed if it can support what came back); we only refuse when it // answers with no version at all, which means it is not an MCP server. const ProtocolVersion = "2025-06-18" // rpcRequest / rpcResponse — JSON-RPC 2.0. Deliberately hand-rolled: the wire // format is four fields, and the repo vendors its dependencies, so pulling a // library in for this would cost more than it saves. type rpcRequest struct { JSONRPC string `json:"jsonrpc"` ID int64 `json:"id,omitempty"` Method string `json:"method"` Params any `json:"params,omitempty"` } type rpcResponse struct { JSONRPC string `json:"jsonrpc"` ID *int64 `json:"id"` Result json.RawMessage `json:"result,omitempty"` Error *rpcError `json:"error,omitempty"` } type rpcError struct { Code int `json:"code"` Message string `json:"message"` } func (e *rpcError) Error() string { return fmt.Sprintf("mcp: rpc error %d: %s", e.Code, e.Message) } // transport carries one JSON-RPC conversation. Implementations: stdioTransport // (a subprocess on this box) and httpTransport (streamable HTTP, guarded by // webfetch). Both must be safe for concurrent use by the Client. type transport interface { // Call sends a request and returns the matching response. Call(ctx context.Context, req *rpcRequest) (*rpcResponse, error) // Notify sends a notification (no id, no reply expected). Notify(ctx context.Context, method string, params any) error // Close releases the transport (kills the subprocess, drops the session). Close() error }