Empezar aquí
Índice de la sección

MCP

Referencia del Model Context Protocol, con arquitectura host, cliente y servidor, primitivas, transportes y cómo escribir y conectar un servidor.

Publicado 3 min de lectura

El Model Context Protocol (MCP) es un estándar abierto para conectar aplicaciones de IA con herramientas y datos. En lugar de programar cada integración dentro de cada aplicación, alguien escribe un servidor MCP una vez (para GitHub, una base de datos, tu sistema interno) y cualquier aplicación compatible puede usarlo. La especificación vive en modelcontextprotocol.io.

MCP no cambia cómo razona el modelo. Por debajo, las herramientas de un servidor MCP acaban siendo herramientas normales con su ciclo tool_use → tool_result (Uso de herramientas). Lo que estandariza es cómo se descubren, describen e invocan.

Para aprenderlo paso a paso: MCP: conectar un servidor de herramientas y el curso MCP: de cero a tu propio servidor.

Arquitectura

flowchart LR
  H[Host: tu app o IDE] --> C1[Cliente MCP]
  H --> C2[Cliente MCP]
  C1 <--> S1[Servidor: base de datos]
  C2 <--> S2[Servidor: tickets]
  H <--> M[Modelo]
PiezaQué esEjemplo
HostLa aplicación que usa el modelo y decide qué servidores conectaUn asistente de escritorio, un IDE, tu agente
ClienteComponente dentro del host que mantiene una conexión con un servidorUna instancia de ClientSession
ServidorProceso que expone capacidades a través del protocoloUn servidor de solo lectura sobre tu base de datos

Primitivas de un servidor

PrimitivaQuién decide usarlaQué esEjemplo
ToolsEl modeloFunciones invocables con argumentosbuscar_cliente(email)
ResourcesLa aplicaciónDatos de solo lectura identificados por URIlibros://catalogo
PromptsLa persona usuariaPlantillas de prompt reutilizables«Resume esta incidencia»

La mayoría de integraciones empiezan solo con tools. Añade resources cuando quieras que la aplicación cargue contexto sin pasar por una decisión del modelo.

Transportes

TransporteCuándoCómo funciona
stdioServidor local en la misma máquinaEl host lanza el servidor como subproceso y se comunican por entrada y salida estándar.
Streamable HTTPServidor remoto, compartido por varios usuariosEl servidor escucha en una URL; admite respuestas en streaming.

Ejemplo completo: un servidor

Un servidor con una herramienta y un recurso. SDK de Python: paquete mcp (serie 2.x; en la 1.x la clase MCPServer se llamaba FastMCP). SDK de TypeScript: @modelcontextprotocol/sdk.

from mcp.server.mcpserver import MCPServer

mcp = MCPServer("biblioteca")

CATALOGO = {
    "978-84-376-0494-7": {"titulo": "Cien años de soledad", "disponible": True},
    "978-84-206-3311-2": {"titulo": "La Regenta", "disponible": False},
}

@mcp.tool()
def buscar_libro(texto: str) -> str:
    """Busca libros cuyo título contenga el texto. Devuelve ISBN, título y disponibilidad."""
    hallados = [
        f"{isbn} | {d['titulo']} | {'disponible' if d['disponible'] else 'prestado'}"
        for isbn, d in CATALOGO.items()
        if texto.lower() in d["titulo"].lower()
    ]
    return "\n".join(hallados) or "Sin resultados."

@mcp.resource("libros://catalogo")
def catalogo() -> str:
    """Lista completa del catálogo."""
    return "\n".join(f"{isbn} | {d['titulo']}" for isbn, d in CATALOGO.items())

if __name__ == "__main__":
    mcp.run()  # stdio por defecto
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const CATALOGO: Record<string, { titulo: string; disponible: boolean }> = {
  "978-84-376-0494-7": { titulo: "Cien años de soledad", disponible: true },
  "978-84-206-3311-2": { titulo: "La Regenta", disponible: false },
};

const server = new McpServer({ name: "biblioteca", version: "1.0.0" });

server.registerTool(
  "buscar_libro",
  {
    description: "Busca libros cuyo título contenga el texto. Devuelve ISBN, título y disponibilidad.",
    inputSchema: { texto: z.string() },
  },
  async ({ texto }) => {
    const hallados = Object.entries(CATALOGO)
      .filter(([, d]) => d.titulo.toLowerCase().includes(texto.toLowerCase()))
      .map(([isbn, d]) => `${isbn} | ${d.titulo} | ${d.disponible ? "disponible" : "prestado"}`);
    return { content: [{ type: "text", text: hallados.join("\n") || "Sin resultados." }] };
  },
);

await server.connect(new StdioServerTransport());

En Python, MCPServer genera el esquema de entrada a partir de las anotaciones de tipo y la descripción a partir del docstring. Escríbelo con el mismo cuidado que la descripción de cualquier herramienta: Diseño de herramientas.

Ejemplo completo: conectar el servidor a tu agente

El cliente lista las herramientas del servidor, se las pasa al modelo en tools y reenvía cada tool_use al servidor.

import asyncio
import anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

MODEL = "claude-sonnet-5-5"
MAX_PASOS = 8
client = anthropic.AsyncAnthropic()
servidor = StdioServerParameters(command="python", args=["servidor.py"])

async def main(pregunta: str) -> str:
    async with stdio_client(servidor) as (leer, escribir):
        async with ClientSession(leer, escribir) as sesion:
            await sesion.initialize()
            lista = await sesion.list_tools()
            tools = [
                {"name": t.name, "description": t.description or "", "input_schema": t.input_schema}
                for t in lista.tools
            ]
            mensajes = [{"role": "user", "content": pregunta}]
            for _ in range(MAX_PASOS):
                resp = await client.messages.create(
                    model=MODEL, max_tokens=1024, tools=tools, messages=mensajes
                )
                mensajes.append({"role": "assistant", "content": resp.content})
                if resp.stop_reason != "tool_use":
                    return "".join(b.text for b in resp.content if b.type == "text")
                resultados = []
                for b in resp.content:
                    if b.type == "tool_use":
                        r = await sesion.call_tool(b.name, b.input)
                        texto = "".join(c.text for c in r.content if c.type == "text")
                        resultados.append({
                            "type": "tool_result", "tool_use_id": b.id,
                            "content": texto, "is_error": bool(r.is_error),
                        })
                mensajes.append({"role": "user", "content": resultados})
            return "[límite de pasos]"

print(asyncio.run(main("¿Está disponible La Regenta?")))

Algunas aplicaciones y la propia API del proveedor pueden conectarse a servidores MCP remotos sin que escribas este puente. Consulta la documentación oficial para las opciones vigentes.

Casos límite

SituaciónQué hacer
Varios servidores con herramientas del mismo nombreAñade un prefijo por servidor (tickets_buscar, db_buscar) al construir tools.
Servidor con decenas de herramientasExpón solo las que necesita esta tarea; muchas herramientas parecidas confunden al modelo y ocupan contexto.
Resultado con imágenes u otros tiposr.content puede traer bloques no textuales; conviértelos al bloque equivalente o descártalos explícitamente.
Servidor caídocall_tool lanza excepción: devuélvela como tool_result con is_error.

Errores frecuentes

  • Instalar servidores de terceros sin revisarlos. Un servidor ejecuta código en tu máquina y sus descripciones y resultados entran en el contexto del modelo, donde pueden traer inyecciones. Ver Seguridad y despliegue de servidores.
  • Dar permisos de escritura por defecto. Empieza por solo lectura: Servidor MCP de solo lectura.
  • Herramientas que traducen la API interna una a una. Diseña pensando en las tareas del modelo, no en tus endpoints.
  • Logs por la salida estándar con stdio.

Ver también