Files
egutierrez dce725e69f feat(infra): auto-commit con 8 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:38:16 +02:00

6.5 KiB

name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path, params, output
name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports tested tests test_file_path file_path params output
mcp_server_http function go infra 1.0.0 impure func MCPHTTPHandler(opts MCPHTTPOpts) http.Handler Devuelve un http.Handler que implementa el Streamable HTTP transport del protocolo MCP (spec 2025-03-26). Acepta POST con un mensaje JSON-RPC 2.0 unico y despacha initialize/tools/list/tools/call/ping al handler del usuario. Soporta auth opcional via MCPHTTPAuthFunc que enriquece el context antes de invocar el handler de tools.
mcp
http
rpc
json-rpc
tools
server
protocol
claude
backends
false error_go_core
context
encoding/json
fmt
io
net/http
true
Initialize retorna serverInfo con Name y Version correctos
ToolsList retorna las tools registradas
ToolsCall invoca handler y retorna content[0].text con el resultado
BadAuth retorna 401 cuando opts.Auth devuelve error
BodyTooLarge retorna 413 cuando el body supera 1 MiB
ParseError retorna HTTP 200 con error JSON-RPC -32700 para body invalido
Notification retorna 202 Accepted sin body cuando falta el campo id
MethodNotAllowed retorna 405 para GET y DELETE
functions/infra/mcp_server_http_test.go functions/infra/mcp_server_http.go
name desc
opts.Name Nombre del servidor reportado al cliente en la respuesta de initialize (serverInfo.name).
name desc
opts.Version Version del servidor reportada al cliente en initialize (serverInfo.version).
name desc
opts.Tools Lista de MCPToolDef (nombre, descripcion, JSON Schema del input) que el servidor expone. Mismo tipo que MCPServerOpts de mcp_server_stdio_go_infra.
name desc
opts.Handler Dispatcher unico para todas las tools. Recibe ctx (posiblemente enriquecido por Auth), nombre de la tool y arguments JSON crudo. Devuelve result, isError y err.
name desc
opts.Auth Funcion opcional de autenticacion. Recibe el *http.Request, devuelve un context enriquecido (p.ej. con user_id) o un error. Si devuelve error, el handler responde 401 sin invocar Handler. Si es nil no se hace auth.
name desc
opts.Logger Writer opcional para log de debug (p.ej. os.Stderr). Si es nil los mensajes se descartan.
http.Handler listo para montarse en un mux. El handler es seguro para uso concurrente (no tiene estado mutable compartido).

Ejemplo

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strings"

    "fn-registry/functions/infra"
)

func main() {
    tools := []infra.MCPToolDef{
        {
            Name:        "echo",
            Description: "Devuelve el mensaje tal cual",
            InputSchema: json.RawMessage(`{
                "type": "object",
                "properties": {"msg": {"type": "string"}},
                "required": ["msg"]
            }`),
        },
    }

    toolHandler := func(ctx context.Context, name string, input json.RawMessage) (any, bool, error) {
        if name == "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
        }
        return nil, true, fmt.Errorf("unknown tool: %s", name)
    }

    // AuthFunc Bearer simple: extrae el token, busca en DB, inyecta user_id en ctx.
    // El kanban lo implementa asi, buscando en la tabla mcp_tokens.
    authFn := func(r *http.Request) (context.Context, error) {
        token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
        if token == "" {
            return nil, fmt.Errorf("missing token")
        }
        // ... validar token en DB ...
        ctx := context.WithValue(r.Context(), "user_id", "u_123")
        return ctx, nil
    }

    mcpH := infra.MCPHTTPHandler(infra.MCPHTTPOpts{
        Name:    "my-app-mcp",
        Version: "1.0.0",
        Tools:   tools,
        Handler: toolHandler,
        Auth:    authFn,
        Logger:  os.Stderr,
    })

    mux := http.NewServeMux()
    mux.Handle("/mcp", mcpH)

    fmt.Println("MCP HTTP server en :8300/mcp")
    if err := http.ListenAndServe(":8300", mux); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

Para un cliente MCP HTTP, la configuracion en .mcp.json usa type: http:

{
  "mcpServers": {
    "my-app": {
      "type": "http",
      "url": "http://localhost:8300/mcp",
      "headers": {
        "Authorization": "Bearer <token>"
      }
    }
  }
}

Cuando usarla

Cuando necesites exponer tools MCP a Claude o a un agente via HTTP en lugar de via stdio — es decir, cuando el servidor MCP es un proceso separado (no un subproceso del cliente) o cuando varios clientes deben compartir el mismo servidor. Tipico en apps con backend Go ya existente (kanban, sqlite_api, registry_api) que quieren aceptar llamadas MCP sin lanzar un subproceso nuevo por sesion.

Usa mcp_server_stdio_go_infra si el cliente lanza el servidor como subproceso (Claude Desktop, claude -p). Usa esta funcion si el servidor ya esta corriendo y el cliente se conecta via URL.

Gotchas

  • Sin sesiones MCP (Mcp-Session-Id): la spec 2025-03-26 define un header Mcp-Session-Id para multiplexar sesiones sobre el mismo endpoint. Esta implementacion no lo emite ni lo exige — cada POST es independiente. TODO: implementar sesiones cuando se necesiten mas de un cliente concurrente con estado de sesion separado.
  • GET (SSE server→client) no implementado: POST cubre el 100% de los casos de uso actuales (tools call). El canal SSE (server-initiated notifications) se puede anadir montando un segundo handler GET en el mismo path. Devuelve 405 hasta entonces.
  • DELETE (close session) no implementado: 405. Sin sesiones, no hay nada que cerrar.
  • Body limit 1 MiB: requests mas grandes reciben 413. Si tus tools reciben inputs grandes (imagenes en base64, JSONs voluminosos), ajusta mcpHTTPBodyLimit en el .go o recibe los datos por referencia (URL/path) en vez de inline.
  • CORS no incluido: monta http_cors_middleware_go_infra en el mux antes del handler si el cliente MCP es un frontend web o viene de origen diferente.
  • Errores de protocolo usan HTTP 200: segun el spec MCP, los errores JSON-RPC se devuelven con HTTP 200 para que el cliente pueda parsear el objeto error. Solo 401 y 413 son codigos HTTP de error.