---
slug: 42-mcp
title: Servidores MCP
description: Construí servidores MCP (Model Context Protocol) en Synsema — exponé tools, datos y APIs existentes a cualquier agente de IA (Claude, Cursor…) por JSON-RPC, con sandbox seguro.
example_ids: [mcp-server]
---

# Servidores MCP

**MCP** (Model Context Protocol) le permite a un agente de IA — Claude, Cursor, cualquier cliente MCP — llamar **tools** y leer **datos** que vos exponés. Es **JSON-RPC**, y el `serve` + JSON nativos de Synsema lo vuelven un server de **una sola ruta**.

```synsema
-- Doc example: a minimal MCP server (Model Context Protocol) in Synsema. MCP is JSON-RPC;
-- the `serve` wiring is one route (shown in the prose). Here the handler is a pure function,
-- so it's doctestable — press Test.
intent: "doc example: MCP server handler"

task rpc(id, result)
    give {"jsonrpc": "2.0", "id": id, "result": result}

-- one tool: greet(name)
task tool_greet(args)
    give {"content": [{"type": "text", "text": "Hello, " + args["name"] + "!"}]}

-- the JSON-RPC dispatcher: initialize / tools/list / tools/call
task mcp(req)
    let id be req["id"]
    when req["method"] == "initialize"
        give rpc(id, {"protocolVersion": "2025-06-18", "serverInfo": {"name": "demo", "version": "1.0"}, "capabilities": {"tools": {}}})
    otherwise when req["method"] == "tools/list"
        give rpc(id, {"tools": [{"name": "greet", "description": "Greet someone", "inputSchema": {"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}}]})
    otherwise when req["method"] == "tools/call"
        let p be req["params"]
        when p["name"] == "greet"
            give rpc(id, tool_greet(p["arguments"]))
        otherwise
            give rpc(id, {"content": [{"type": "text", "text": "unknown tool"}], "isError": true})
    otherwise
        give rpc(id, {})

test "initialize returns serverInfo + protocol version"
    let r be mcp({"id": 1, "method": "initialize", "params": {}})
    assert_eq((r["result"])["protocolVersion"], "2025-06-18")

test "tools/call greet returns text content"
    let r be mcp({"id": 2, "method": "tools/call", "params": {"name": "greet", "arguments": {"name": "Ada"}}})
    assert_eq((((r["result"])["content"])[0])["text"], "Hello, Ada!")
```

## Cablealo a `serve` (una ruta)

```synsema
require serve(8080)
serve on 8080
    route "POST /mcp"
        give mcp(json of request)
```

Eso es un servidor MCP completo: cualquier cliente MCP que hable HTTP se conecta a `http://…/mcp`. Los métodos centrales son `initialize`, `tools/list` y `tools/call`.

## Tools que hacen trabajo real

Una tool es solo una task. Dale un cuerpo real — consultar una DB, calcular, llamar una API — y devolvé `{content: [...]}`:

```synsema
task tool_orders(args)
    require db("./store.db")
    let rows be sql("SELECT id, total FROM orders WHERE customer = ?", [args["customer"]])
    give {"content": [{"type": "text", "text": json_encode(rows)}]}
```

Agregala a `tools/list` (con un `inputSchema`) y despachala en `tools/call`.

## Convertí una API vieja en MCP (un conversor)

Envolvé una API REST existente como una tool MCP — el agente recibe una tool limpia y tipada; tu API queda intacta:

```synsema
require net("api.legacy.com")
require secret("LEGACY_KEY")

task tool_weather(args)
    let r be http_get("https://api.legacy.com/v1/weather",
        {"x-api-key": secret("LEGACY_KEY")}, {"city": args["city"]})
    give {"content": [{"type": "text", "text": body of r}]}
```

Registrala, despachala — tu API legacy ahora es MCP-native, y el secreto queda redactado (ver **[Secretos](/es/0.6.x/21-secrets)**).

## Seguridad — ejecutar input no confiable en sandbox

Si tu servidor MCP corre código o input que no confiás, poné un **[techo del host](/es/0.6.x/20-capabilities)** sobre la ejecución (`--cap-set`). El propio endpoint MCP de este sitio (`/mcp`) expone tools `run_synsema`/`test_synsema` que corren snippets en exactamente ese sandbox — así un agente puede **verificar** el Synsema que escribe, de forma segura.
