feat(registry): claude_stream + mcp_server_stdio para chat con tool-use
- claude_stream_go_core: lanza claude -p --output-format stream-json --verbose, decodifica NDJSON y emite eventos sinteticos (text_delta, tool_use, tool_result, result, error) por canal Go. 10 tests con fake claude bash. - mcp_server_stdio_go_infra: scaffold de MCP server JSON-RPC 2.0 sobre stdio (initialize, tools/list, tools/call, ping). Usuario registra tool defs y handler unico. 9 tests. Usadas por apps/kanban backend para reemplazar el chat HTTP one-shot con XML actions por WebSocket streaming + tool-use nativa. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
---
|
||||
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.
|
||||
Reference in New Issue
Block a user