--- name: mcp_server_stdio kind: function lang: go domain: infra version: "1.0.0" purity: impure signature: "func ServeMCP(ctx context.Context, opts MCPServerOpts) error" description: "Ejecuta un servidor MCP (Model Context Protocol) sobre stdio implementando JSON-RPC 2.0. Lee de opts.In linea a linea, despacha initialize/tools/list/tools/call/ping al handler del usuario, y escribe respuestas a opts.Out. Retorna nil al cerrar stdin o al cancelar ctx." tags: [mcp, stdio, json-rpc, claude, tools, server, protocol] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: - bufio - context - encoding/json - fmt - io - os - sync tested: true tests: - "initialize retorna serverInfo con Name y Version correctos" - "tools/list retorna las tools registradas con su schema" - "tools/call con tool valida invoca handler y retorna content con isError false" - "tools/call cuando handler retorna error genera respuesta error -32603" - "tools/call cuando handler retorna isError=true usa result.isError=true no error JSON-RPC" - "metodo desconocido retorna error -32601" - "notification sin id no produce respuesta en el buffer" - "json invalido retorna error -32700 con id null" - "ctx cancel detiene ServeMCP y retorna nil sin error" test_file_path: "functions/infra/mcp_server_stdio_test.go" file_path: "functions/infra/mcp_server_stdio.go" params: - name: ctx desc: "Contexto de cancelacion. Cuando se cancela, el bucle de lectura termina limpiamente y la funcion retorna nil." - name: opts desc: "Configuracion del servidor: nombre y version del servidor, lista de tools (MCPToolDef con nombre, descripcion y JSON Schema del input), handler unico que despacha todas las tools (recibe name + arguments JSON crudo, retorna result + isError + err), reader de entrada (default os.Stdin), writer de salida (default os.Stdout), y writer opcional de log (default descartado)." output: "nil cuando stdin se cierra o ctx se cancela. Error si ocurre un fallo irrecuperable de escritura en Out." --- ## Ejemplo ```go package main import ( "context" "encoding/json" "fmt" "os" "fn-registry/functions/infra" ) func main() { tools := []infra.MCPToolDef{ { Name: "echo", Description: "Echoes the input message back", InputSchema: json.RawMessage(`{ "type": "object", "properties": { "msg": {"type": "string", "description": "message to echo"} }, "required": ["msg"] }`), }, } handler := func(ctx context.Context, name string, input json.RawMessage) (any, bool, error) { switch name { case "echo": var args struct{ Msg string `json:"msg"` } if err := json.Unmarshal(input, &args); err != nil { return nil, false, err } return map[string]string{"result": args.Msg}, false, nil default: return nil, true, fmt.Errorf("unknown tool: %s", name) } } err := infra.ServeMCP(context.Background(), infra.MCPServerOpts{ Name: "my-app-mcp", Version: "1.0.0", Tools: tools, Handler: handler, Logger: os.Stderr, }) if err != nil { fmt.Fprintln(os.Stderr, "mcp error:", err) os.Exit(1) } } ``` Para usar como MCP server en Claude Desktop / `claude -p`, registrar el binario en `.mcp.json`: ```json { "mcpServers": { "my-app": { "command": "/path/to/my-app-binary", "args": ["--mcp"] } } } ``` El binario detecta `--mcp` y llama `ServeMCP` en lugar del modo interactivo normal. ## Notas **Protocolo soportado:** MCP 2024-11-05, subset minimo suficiente para exponer tools a `claude -p` y Claude Desktop. **Metodos implementados:** - `initialize` — handshake inicial; responde con protocolVersion, capabilities y serverInfo. - `initialized` — notification enviada por el cliente tras el handshake; se ignora sin respuesta. - `tools/list` — devuelve la lista de tools registradas. - `tools/call` — invoca el Handler. Si handler.err != nil → JSON-RPC error -32603. Si isError=true → result.isError=true (error logico de la tool, no error de protocolo). - `ping` — responde con `{}`. - Cualquier otro metodo → JSON-RPC error -32601 (method not found). - Notifications (mensajes sin campo `id`) → nunca se responden, ni siquiera con error. **Buffer del scanner:** 4 MB para admitir schemas JSON grandes o resultados voluminosos. **Concurrencia:** el bucle es secuencial hoy; los writes estan protegidos por mutex para que sea seguro si en el futuro se paraleliza el dispatch del handler. **Distincion notification vs request:** la presencia del campo `id` en el JSON crudo (incluso si es null) indica request. La ausencia indica notification. Esto sigue la spec JSON-RPC 2.0.