Phase 13 - Lesson 07
Construyendo un Servidor MCP — SDKs de Python + TypeScript
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
La mayoría de los tutoriales de MCP solo muestran hello-worlds de stdio. Un servidor real expone herramientas, recursos y prompts, maneja la negociación de capacidades, emite errores estructurados y funciona igual en todos los SDK. Esta lección construye un servidor de notas de principio a fin: transporte stdio de la biblioteca estándar (stdlib), despacho JSON-RPC, las tres primitivas de servidor y un estilo de función pura que se puede portar directamente a FastMCP de la SDK de Python o a la SDK de TypeScript cuando decidas avanzar.
Tipo: Build Idiomas: Python (servidor MCP stdio con stdlib) Requisitos previos: Phase 13 · 06 (MCP fundamentals) Tiempo: ~75 minutos
Objetivos de Aprendizaje
- Implementar los métodos
initialize,tools/list,tools/call,resources/list,resources/read,prompts/listyprompts/get. - Escribir un bucle de despacho (dispatch loop) que lea mensajes JSON-RPC de
stdiny escriba respuestas enstdout. - Emitir respuestas de error estructuradas según la especificación JSON-RPC 2.0 y los códigos adicionales de MCP.
- Migrar una implementación con stdlib a FastMCP (SDK de Python) o al SDK de TypeScript sin reescribir la lógica de las herramientas.
El Problema
Antes de poder usar un transporte remoto (Phase 13 · 09) o una capa de autenticación (Phase 13 · 16), necesitas un servidor local limpio. Local significa stdio: el cliente inicia el servidor como un proceso hijo, y los mensajes fluyen delimitados por saltos de línea a través de stdin/stdout.
La especificación del 25-11-2025 prescribe que los mensajes stdio se codifiquen como objetos JSON con un separador \n explícito. Nada de SSE aquí; SSE era el antiguo modo remoto y se eliminará a mediados de 2026 (el servidor MCP Rovo de Atlassian lo depreció el 30 de junio de 2026; Keboola el 1 de abril de 2026). Para stdio, un objeto JSON por línea es todo el formato de transmisión.
Un servidor de notas es un buen modelo porque ejercita las tres primitivas del servidor. Las herramientas realizan mutaciones (notes_create). Los recursos exponen datos (notes://{id}). Los prompts entregan plantillas (review_note). El diseño de esta lección se puede generalizar a cualquier dominio.
El Concepto
Bucle de despacho
loop:
line = stdin.readline()
msg = json.loads(line)
if has id:
handle request -> write response
else:
handle notification -> no response
Tres reglas:
- No imprimas nada en stdout que no sea un sobre JSON-RPC. Los registros de depuración (debug) van a stderr.
- Toda solicitud DEBE tener una respuesta correspondiente que contenga el mismo
id. - Las notificaciones NO DEBEN ser respondidas.
Implementando initialize
def initialize(params):
return {
"protocolVersion": "2025-11-25",
"capabilities": {
"tools": {"listChanged": True},
"resources": {"listChanged": True, "subscribe": False},
"prompts": {"listChanged": False},
},
"serverInfo": {"name": "notes", "version": "1.0.0"},
}
Declara solo lo que soportes. El cliente depende del conjunto de capacidades (capabilities) para habilitar o restringir funcionalidades.
Implementando tools/list y tools/call
tools/list devuelve {tools: [...]} con cada entrada conteniendo name, description e inputSchema. tools/call recibe {name, arguments} y devuelve {content: [blocks], isError: bool}.
Los bloques de contenido (content blocks) son tipados. Los más comunes:
{"type": "text", "text": "Found 2 notes"}
{"type": "resource", "resource": {"uri": "notes://14", "text": "..."}}
{"type": "image", "data": "<base64>", "mimeType": "image/png"}
Los errores de herramientas se presentan en dos formas. Los errores a nivel de protocolo (método desconocido, parámetros inválidos) son errores JSON-RPC. Los errores a nivel de herramienta (llamada válida, pero el funcionamiento falló) se devuelven como {content: [...], isError: true}. Esto permite que el modelo vea el fallo en su contexto y pueda actuar en consecuencia.
Implementando recursos
Los recursos son de solo lectura por diseño. resources/list devuelve un manifiesto; resources/read devuelve el contenido. Las URIs pueden ser file://..., http://... o un esquema personalizado como notes://.
Cuando expones datos como un recurso en lugar de una herramienta:
- El modelo no lo "llama"; el cliente puede inyectarlo en el contexto a petición del usuario.
- Las suscripciones (subscriptions) permiten al servidor enviar actualizaciones cuando el recurso cambia (Phase 13 · 10).
- La Phase 13 · 14 extiende esto con
ui://para recursos interactivos.
Implementando prompts
Los prompts son plantillas con argumentos nombrados. El host los muestra como comandos de barra diagonal (slash-commands). Un prompt review_note puede recibir un argumento note_id y producir una plantilla de prompt con múltiples mensajes que el cliente entrega a su modelo.
Sutilezas del transporte stdio
- JSON delimitado por saltos de línea. Sin marcos prefijados por longitud (length-prefixed framing).
- No uses búfer. Llama a
sys.stdout.flush()después de cada escritura. - El cliente controla el tiempo de vida. Cuando
stdinse cierra (EOF), sal de manera limpia. - No manejes SIGPIPE de forma silenciosa; registra el log y sal.
Anotaciones
Cada herramienta puede llevar annotations (anotaciones) que describen propiedades de seguridad:
readOnlyHint: true— lectura pura, segura para reintentar.destructiveHint: true— efectos secundarios irreversibles; el cliente debe confirmar.idempotentHint: true— las mismas entradas producen las mismas salidas.openWorldHint: true— interactúa con sistemas externos.
El cliente usa esto para decidir la experiencia de usuario UX (diálogos de confirmación, indicadores de estado) y el enrutamiento (Phase 13 · 17).
Ruta de transición (Graduation path)
El servidor stdlib en code/main.py tiene alrededor de 180 líneas. FastMCP (Python) simplifica esta misma lógica en un estilo basado en decoradores:
from fastmcp import FastMCP
app = FastMCP("notes")
@app.tool()
def notes_search(query: str, limit: int = 10) -> list[dict]:
...
El SDK de TypeScript tiene un diseño equivalente. La ruta de transición es directa cuando estés listo; los conceptos (capacidades, despacho, bloques de contenido) son los mismos.
Cómo Usarlo
code/main.py es un servidor MCP de notas completo sobre stdio, usando solo la biblioteca estándar (stdlib). Maneja initialize, tools/list, tools/call para tres herramientas (notes_list, notes_search, notes_create), resources/list y resources/read para cada nota, y un prompt review_note. Puedes ejecutarlo enviando mensajes JSON-RPC a través de una tubería (pipe):
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | python main.py
Qué observar:
- El despachador (dispatcher) es un
dict[str, Callable]que tiene el nombre del método como clave. - Cada ejecutor de herramienta devuelve una lista de bloques de contenido (content blocks), no una cadena de texto simple.
isError: truese establece cuando el ejecutor lanza una excepción.
Entrégalo
Esta lección produce outputs/skill-mcp-server-scaffolder.md. Dado un dominio (notas, tickets, archivos, base de datos), la skill genera la estructura (scaffolds) de un servidor MCP con la división correcta de herramientas / recursos / prompts y la ruta de transición de SDK correspondiente.
Ejercicios
Ejecuta
code/main.pye interactúa con él usando mensajes JSON-RPC creados manualmente. Pruebanotes_createy luegoresources/readpara recuperar la nueva nota.Agrega una herramienta
notes_deleteconannotations: {destructiveHint: true}. Verifica si el cliente mostraría un diálogo de confirmación (esto requiere un host real; Claude Desktop funciona).Implementa
resources/subscribepara que el servidor envíe notificaciones deresources/updatedcada vez que se modifique una nota. Agrega una tarea de mantenimiento de conexión (keepalive).Porta el servidor a FastMCP. El archivo de Python debería reducirse a menos de 80 líneas. El comportamiento de red debe ser idéntico; verifícalo con la misma herramienta de pruebas de JSON-RPC.
Lee la sección
server/toolsde la especificación e identifica un campo de definición de herramienta no implementado en el servidor de esta lección. (Consejo: hay varios; elige uno y agrégalo).
Términos Clave
| Término | Lo que la gente dice | Lo que realmente significa |
|---|---|---|
| Servidor MCP | "La cosa que expone herramientas" | Proceso que habla MCP JSON-RPC a través de stdio o HTTP |
| transporte stdio | "Modelo de proceso hijo" | El servidor es iniciado por el cliente; se comunica a través de stdin/stdout |
| Despachador (Dispatcher) | "Enrutador de métodos" | Mapeo del nombre del método JSON-RPC a la función manejadora |
| Bloque de contenido (Content block) | "Fragmento de resultado de herramienta" | Elemento tipado en el arreglo content de una respuesta de herramienta |
isError |
"Fallo a nivel de herramienta" | Señala que la herramienta falló; se distingue de un error de JSON-RPC |
| Annotations | "Indicaciones de seguridad" | Flags readOnly / destructive / idempotent / openWorld |
| FastMCP | "SDK de Python" | Framework de alto nivel basado en decoradores sobre el protocolo MCP |
| URI del Recurso (Resource URI) | "Datos direccionables" | file://, db:// o esquema personalizado que identifica un recurso |
| Plantilla de Prompt (Prompt template) | "Resumen de comando de barra" | Plantilla proporcionada por el servidor con espacios para argumentos para interfaces de hosts |
| Declaración de capacidades (Capability declaration) | "Alternancia de funciones" | Flags por primitiva declaradas en initialize |
Lecturas Adicionales
- Model Context Protocol — Python SDK — la implementación de referencia en Python
- Model Context Protocol — TypeScript SDK — implementación paralela en TS
- FastMCP — server framework — API de Python en estilo de decoradores para servidores MCP
- MCP — Quickstart server guide — tutorial de principio a fin utilizando cualquiera de los dos SDK
- MCP — Server tools spec — referencia completa para mensajes de
tools/*