Ejemplos de servidores MCP en Python y TypeScript (código de GitHub) que funcionan con los SDK actuales
Ejemplos de servidores MCP en Python y TypeScript listos para copiar, basados en los repositorios oficiales de los SDK en GitHub. Cada ejemplo funciona por stdio o Streamable HTTP, y los dos últimos envuelven una API de imagen y video para que un asistente pueda generar contenido multimedia desde una ventana de chat.
Un servidor MCP es un programa pequeño que entrega a un asistente de IA una lista de herramientas que puede invocar, y la forma más rápida de entender cómo funciona uno es leer algunos que ya están en marcha. Este artículo reúne ejemplos de servidores MCP en Python y TypeScript, escritos según los repositorios oficiales de los SDK en GitHub, para que puedas pegar un archivo, iniciarlo y ver cómo un asistente lo usa en cuestión de minutos.
Todo lo que sigue se basa en la documentación de los SDK vigente en octubre de 2026, lo que importa porque ambos SDK han alcanzado hace poco una segunda versión mayor. En Python, FastMCP pasó a ser MCPServer. En TypeScript, el código del servidor se trasladó a su propio paquete @modelcontextprotocol/server. La primera mitad del artículo construye un servidor sencillo en cada lenguaje. La segunda mitad añade herramientas que generan imágenes y video, que es donde MCP deja de ser una demo y empieza a ahorrar trabajo real.
Qué hace un servidor MCP
El Model Context Protocol (MCP) es un estándar abierto que permite a un cliente de IA, como una aplicación de chat, un IDE o un agente, invocar código que tú has escrito. El cliente abre una conexión, pregunta a tu servidor qué ofrece y deja que el modelo decida cuándo usar cada elemento. Los mensajes viajan como JSON-RPC, y tu servidor nunca llama al modelo por sí mismo. Espera a que le pidan algo, ejecuta la función y devuelve un resultado.
Herramientas, recursos y prompts
Cada servidor se construye con tres tipos de bloques:
Herramientas son funciones que el modelo puede invocar, como add o generate_image. Pueden tener efectos secundarios, así que son la parte que hay que diseñar con más cuidado.
Recursos son datos de solo lectura direccionados por una URI, como greeting://alice o una ruta de archivo. El cliente los lee para dar contexto al modelo.
Prompts son plantillas de mensajes reutilizables que una persona elige de un menú, por ejemplo una solicitud de revisión de código.
💡 Consejo: Empieza por las herramientas. La mayoría de los servidores en GitHub solo exponen herramientas, y un modelo elige la correcta con más fiabilidad a partir de una lista corta de herramientas con nombres claros que de una lista larga de nombres vagos.
Qué línea del SDK instalar
Ambos SDK oficiales tienen ahora una línea actual y una línea de mantenimiento. Elige la correcta antes de copiar código, porque las importaciones son distintas.
Python
TypeScript
Actual (v2)
pip install "mcp[cli]", clase MCPServer
npm install @modelcontextprotocol/server
Mantenimiento (v1.x)
pip install "mcp[cli]<2", clase FastMCP
npm install @modelcontextprotocol/sdk zod
Repositorio en GitHub
modelcontextprotocol/python-sdk
modelcontextprotocol/typescript-sdk
La línea v1 de Python solo recibe ahora correcciones de seguridad, así que los proyectos nuevos deberían empezar con v2, y los ejemplos de Python de abajo lo hacen. Si mantienes un servidor Python antiguo, migrar significa cambiar from mcp.server.fastmcp import FastMCP por from mcp.server.mcpserver import MCPServer y renombrar la llamada al constructor. Los decoradores siguen igual.
En TypeScript, los ejemplos completos usan el paquete v1.x que hoy importan la mayoría de los servidores existentes. Después aparece una versión corta en v2 del mismo servidor, para que veas exactamente qué cambia.
Ejemplo en Python con MCPServer
Python ofrece el camino más corto desde cero hasta un servidor en marcha, porque las anotaciones de tipo y los docstrings se convierten en el esquema de la herramienta. No hay que escribir a mano ningún JSON Schema.
El archivo del servidor
Instala con uv add "mcp[cli]" (o pip install "mcp[cli]") y luego guarda esto como server.py:
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
@mcp.prompt()
def review_code(code: str) -> str:
"""Ask for a code review that lists bugs first."""
return f"Please review this code and list bugs first:\n\n{code}"
if __name__ == "__main__":
mcp.run(transport="stdio")
La función add se convierte en una herramienta cuyo esquema de entrada se genera a partir de a: int, b: int. El docstring se convierte en la descripción que el modelo lee al decidir si la invoca. greeting es una plantilla de recurso: un cliente que lee greeting://Ada recibe Hello, Ada!.
Ejecútalo e inspecciónalo
uv run mcp dev server.py # opens the MCP Inspector in your browser
uv run mcp run server.py # plain stdio, for a client to launch
uv run mcp run server.py --transport streamable-http # HTTP instead
Usa primero el Inspector. Muestra todas las herramientas, te da un formulario para los argumentos y enseña el tráfico JSON-RPC sin procesar, que es la forma más rápida de detectar un esquema defectuoso. Cuando funcione, registra el servidor en un cliente. En Claude Code eso es una sola línea:
claude mcp add demo -- uv run mcp run server.py
Ejemplo en TypeScript con Zod
TypeScript exige un poco más de ceremonia, porque describes las entradas con Zod en lugar de con anotaciones de tipo. A cambio obtienes validación en tiempo de ejecución y argumentos tipados en tu handler.
El archivo del servidor en v1.x
Instala el paquete con npm install @modelcontextprotocol/sdk zod y define "type": "module" en package.json. Escribe el servidor como una función para que ambos transportes puedan reutilizarla. Guarda esto como src/build-server.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export function buildServer(): McpServer {
const server = new McpServer({ name: "demo", version: "1.0.0" });
server.registerTool(
"add",
{
title: "Add numbers",
description: "Add two numbers",
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
})
);
return server;
}
Después, un punto de entrada de tres líneas para stdio, en src/stdio.ts:
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { buildServer } from "./build-server.js";
await buildServer().connect(new StdioServerTransport());
Compila con tsc y luego compruébalo con npx @modelcontextprotocol/inspector node dist/stdio.js. Los recursos y los prompts usan la misma forma a través de registerResource y registerPrompt. El repositorio v1.x también incluye src/examples/server/simpleStreamableHttp.ts, un ejemplo completo con herramientas, recursos, prompts, registro de eventos y OAuth opcional, que merece la pena leer una vez que tu servidor deje de ser un juguete.
El mismo servidor en v2
import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";
const server = new McpServer({ name: "demo", version: "1.0.0" });
server.registerTool(
"add",
{
description: "Add two numbers",
inputSchema: z.object({ a: z.number(), b: z.number() }),
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
})
);
await server.connect(new StdioServerTransport());
Tres cosas cambian: el nombre del paquete, la importación del subpath /stdio y el esquema, que pasa a ser un z.object(...) completo creado con zod/v4 en lugar de un objeto plano de campos. El cuerpo del handler es idéntico. Servir por HTTP en v2 pasa por paquetes adaptadores pequeños como @modelcontextprotocol/express, así que lee el README de ese paquete antes de portar un servidor HTTP.
Stdio o Streamable HTTP
El transporte es la única decisión que cambia la forma de desplegar. El código de las herramientas sigue siendo idéntico.
stdio
Streamable HTTP
Quién inicia el servidor
El cliente lo lanza como proceso hijo
Lo ejecutas tú y los clientes se conectan por URL
Ideal para
Herramientas locales, IDE, aplicaciones de escritorio
Servidores compartidos o remotos, equipos
Autenticación
Hereda tu usuario y entorno
La añades tú (tokens o OAuth)
Registro de eventos
Solo stderr
La salida normal está bien
Escalado
Un proceso por cliente
Escalado web habitual
Proceso local por stdio
Un servidor stdio es la opción por defecto correcta para cualquier cosa que toque tu propio equipo, como archivos, una base de datos local o un script. El cliente lo lanza, se comunica por su stdin y su stdout, y lo detiene cuando termina la sesión. No hay ningún puerto que proteger.
Servidor remoto por HTTP
Streamable HTTP permite que un mismo servidor en ejecución responda a muchos clientes. Esta versión sin estado con Express crea un servidor nuevo para cada solicitud, lo que evita compartir estado de sesión. Guárdala como src/http.ts:
import express from "express";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { buildServer } from "./build-server.js";
const app = express();
app.use(express.json());
app.post("/mcp", async (req, res) => {
const server = buildServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
res.on("close", () => {
transport.close();
server.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.listen(3000, "127.0.0.1");
Regístralo con claude mcp add --transport http demo http://127.0.0.1:3000/mcp. En Python el equivalente es una sola línea: mcp.run(transport="streamable-http", host="127.0.0.1", port=9000). Vincúlalo a localhost a menos que algo delante del servidor gestione la autenticación, porque un puerto MCP abierto es una puerta abierta a todas las herramientas que registraste.
Ejemplo: herramientas que generan imágenes
Las herramientas se vuelven más interesantes cuando el resultado no es un número. La generación de imágenes y de video son buenos casos didácticos porque son lentas, asíncronas y devuelven una URL en lugar de texto. Los ejemplos siguientes llaman a la API de PicassoIA, que sigue un estilo similar al de Replicate: creas una predicción, la consultas y lees el resultado.
URL base y autenticación:https://api.picassoia.com/v1 con la cabecera Authorization: Bearer pia_sk_...
Crear:POST /models/{owner}/{name}/predictions con el cuerpo {"input": {...}}
Consultar:GET /predictions/{id} hasta que el estado sea succeeded, failed o canceled
Tiempos: la respuesta de creación incluye eta.next_poll_in_seconds, un intervalo de consulta que conviene respetar
Herramienta de Python con sondeo
import asyncio
import os
import httpx
from mcp.server.mcpserver import MCPServer
API = "https://api.picassoia.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PICASSOIA_API_TOKEN']}"}
mcp = MCPServer("picassoia-media")
slots = asyncio.Semaphore(5) # the account allows 5 concurrent predictions
async def run_prediction(model: str, payload: dict, timeout_s: int = 600) -> list[str]:
async with slots, httpx.AsyncClient(headers=HEADERS, timeout=30) as http:
created = await http.post(
f"{API}/models/{model}/predictions", json={"input": payload}
)
created.raise_for_status()
prediction = created.json()
waited = 0
while prediction["status"] in ("starting", "processing"):
if waited >= timeout_s:
raise TimeoutError(f"Prediction {prediction['id']} is still running")
delay = (prediction.get("eta") or {}).get("next_poll_in_seconds", 3)
await asyncio.sleep(delay)
waited += delay
polled = await http.get(f"{API}/predictions/{prediction['id']}")
polled.raise_for_status()
prediction = polled.json()
if prediction["status"] != "succeeded":
raise RuntimeError(f"Prediction {prediction['status']}: {prediction.get('error')}")
output = prediction["output"]
return output if isinstance(output, list) else [output]
@mcp.tool()
async def generate_image(prompt: str, aspect_ratio: str = "16:9") -> str:
"""Generate one image from a text prompt and return its URL."""
urls = await run_prediction(
"picassoia/picassoia-image",
{"prompt": prompt, "aspect_ratio": aspect_ratio, "num_outputs": 1},
)
return urls[0]
if __name__ == "__main__":
mcp.run(transport="stdio")
Hay dos detalles importantes aquí. El bucle usa asyncio.sleep, así que el servidor sigue respondiendo a otras solicitudes mientras un trabajo se ejecuta, y sigue el intervalo que sugiere la API en lugar de bombardearla. El semáforo te mantiene por debajo del límite de concurrencia de la cuenta. El modelo PicassoIA Image acepta prompt, aspect_ratio, seed, num_outputs (1 o 2), output_format y output_quality. Para las ediciones, apunta el mismo helper a PicassoIA Image Editor Pro.
Herramienta de TypeScript para video
El mismo patrón funciona en TypeScript. Esta versión añade una herramienta de video sobre la función buildServer de antes:
const API = "https://api.picassoia.com/v1";
const headers = {
Authorization: `Bearer ${process.env.PICASSOIA_API_TOKEN}`,
"Content-Type": "application/json",
};
async function runPrediction(model: string, input: Record<string, unknown>) {
const created = await fetch(`${API}/models/${model}/predictions`, {
method: "POST",
headers,
body: JSON.stringify({ input }),
});
if (!created.ok) throw new Error(`Create failed: ${created.status}`);
let prediction = await created.json();
while (["starting", "processing"].includes(prediction.status)) {
const delay = prediction.eta?.next_poll_in_seconds ?? 5;
await new Promise((resolve) => setTimeout(resolve, delay * 1000));
const polled = await fetch(`${API}/predictions/${prediction.id}`, { headers });
prediction = await polled.json();
}
if (prediction.status !== "succeeded") {
throw new Error(`Prediction ${prediction.status}`);
}
return [prediction.output].flat() as string[];
}
server.registerTool(
"generate_video",
{
title: "Generate video",
description: "Make a short clip with synchronized audio from a text prompt",
inputSchema: {
prompt: z.string().max(4000),
duration: z.union([z.literal(5), z.literal(10)]).default(5),
resolution: z.enum(["480p", "720p"]).default("720p"),
},
},
async ({ prompt, duration, resolution }) => {
const [url] = await runPrediction("picassoia/seedance-2.5-lite", {
prompt,
duration,
resolution,
});
return { content: [{ type: "text", text: url }] };
}
);
El video es más lento. La página del modelo Seedance 2.5 Lite muestra ejemplos de ejecuciones de unos 100 a 190 segundos, un tiempo lo bastante largo como para que algunos clientes abandonen una sola llamada a herramienta. Un diseño más seguro divide el trabajo en dos: start_video devuelve de inmediato la predicción id, y check_video toma ese id y devuelve el estado o la URL final.
Así se comporta el conector de PicassoIA para claude.ai. Sus herramientas de generación devuelven un predict_id y una espera sugerida, y get_generation se llama hasta que el trabajo termina con éxito o con error. El mismo modelo también acepta un image opcional como primer fotograma, un seed, un aspect_ratio y una marca save_audio para clips sin sonido.
💡 Consejo: Mantén pequeño el resultado de la herramienta. Devuelve la URL y un resumen de una línea, no el archivo. El asistente solo necesita un enlace para mostrarlo o pasarlo.
Cómo usar PicassoIA con MCP
Hay dos formas de llegar a PicassoIA desde un asistente: el conector ya hecho o tu propio servidor envuelto alrededor de la API, como en los ejemplos anteriores.
Configuración paso a paso
Elige la vía. Para un cliente de chat, añade el conector de PicassoIA en los ajustes de claude.ai. Expone generate_image, edit_image, generate_video_picassoia, generate_video_seedance, get_generation, cancel_generation, list_models, get_account y list_generations. Para el código de tu propio agente, usa la vía de la API.
Crea un token de API. Inicia sesión en la página de la API de PicassoIA y crea uno. Empieza por pia_sk_, y una cuenta tiene como máximo dos. Revisa la página de precios para ver qué plan incluye acceso a la API antes de construir sobre ella.
Guárdalo en el entorno. Ejecuta export PICASSOIA_API_TOKEN=pia_sk_... en tu shell. Mantenlo fuera de los archivos de código y de cualquier configuración de cliente que subas al repositorio.
Elige un modelo de la tabla de abajo.
Envuelve y prueba. Pega una herramienta de los ejemplos, ejecútala en el Inspector con un prompt sencillo y luego regístrala en tu cliente.
Texto o imagen a video con audio sincronizado, 5 o 10 segundos
¿Qué modelo decide cuándo llamar a tus herramientas? Cualquier LLM capaz de invocar herramientas. El catálogo de modelos de lenguaje de PicassoIA incluye Claude Sonnet 5, GPT 5.6 Sol, Kimi K2.6 y Gemini 3.5 Flash, entre muchos otros. Prueba más de uno con el mismo servidor y compara con qué fiabilidad elige cada uno la herramienta correcta.
Límites que conviene conocer
5 predicciones simultáneas por cuenta, compartidas entre tokens y conexiones MCP
10 MB como tamaño máximo del cuerpo de la solicitud
4.000 caracteres como máximo por prompt
3 horas antes de que una predicción agote el tiempo
2 tokens de API por cuenta
Errores que rompen los servidores MCP
La mayoría de las primeras ejecuciones fallidas se deben a uno de tres problemas. Cada uno es fácil de evitar en cuanto sabes dónde mirar.
Escribir registros en stdout
Con stdio, la salida estándar es el canal del protocolo. Un print() o un console.log() suelto corrompe el flujo JSON-RPC, y el cliente informa de un error de análisis o de un servidor que nunca se conectó. Envía los registros a la salida de error estándar:
En TypeScript, usa console.error("polling prediction").
Llamadas bloqueantes y nombres vagos
Un time.sleep(30) dentro de una herramienta asíncrona congela todas las demás solicitudes del mismo servidor. Usa await asyncio.sleep en Python y un temporizador con await en TypeScript, como hacen los ejemplos. Los nombres importan igual de mucho: una herramienta llamada do_task no da al modelo nada con qué elegir, mientras que generate_image con un docstring claro le indica exactamente cuándo recurrir a ella.
Secretos y cargas grandes
Lee los tokens desde variables de entorno, nunca desde archivos de código, y nunca los muestres en un resultado de herramienta. Para la salida, devuelve URL en lugar de base64. Una sola imagen en base64 puede ocupar megabytes e inunda la ventana de contexto del modelo, y además la API de PicassoIA limita los cuerpos de solicitud a 10 MB.
Pruébalo hoy en PicassoIA
Copia server.py o el par de TypeScript, ejecútalo en el Inspector y tendrás un servidor MCP funcionando en menos de diez minutos. Luego dale algo visual que hacer. Los servidores de referencia oficiales en GitHub son un buen lugar para leer más patrones, y ambos repositorios de SDK incluyen una carpeta de ejemplos.
Abre PicassoIA Image y escribe un prompt propio, edita una toma con Image Editor Pro o anima un fotograma con Seedance 2.5 Lite. Lo que construyas, el ciclo es el mismo: describe, envía, consulta, revisa.
Prueba a crear tus propias imágenes con PicassoIA, y cuando un prompt funcione, envuélvelo como herramienta para que tu asistente pueda repetirlo cuando quiera.