Cómo crear un servidor MCP: ejemplos en Python y TypeScriptServidores MCP: qué son y cómo conectar uno

Cómo crear un servidor MCP: ejemplo en Python y TypeScript

En corto: un servidor MCP es un programa pequeño escrito con un SDK oficial que declara herramientas: nombre, descripción y parámetros. En Python, un servidor funcional ocupa una decena de líneas con la librería FastMCP; en TypeScript, algo más. Se empieza con un servidor local (stdio), se comprueba en MCP Inspector, se conecta a Claude Code y, cuando hay que entregárselo a otras personas, se pasa a HTTP. Abajo está todo el camino y los tropiezos que nos llevamos al operar 26 servidores remotos en producción.

Primero decide si necesitas un servidor propio

Para los servicios populares —GitHub, Figma, Notion, un navegador, la búsqueda— ya existen servidores hechos; ver los mejores servidores MCP. Uno propio se escribe cuando el modelo necesita acceso a lo tuyo: una API interna, una base de datos, un CRM, los scripts del equipo. Si todavía estás averiguando qué es MCP, empieza por el repaso de los servidores MCP.

Ejemplo en Python (FastMCP)

Instala el SDK; lo más cómodo es con uv:

uv init my-mcp && cd my-mcp
uv add "mcp[cli]"

El archivo server.py:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("orders")

@mcp.tool()
def order_status(order_id: str) -> str:
    """Estado de un pedido por su número. Úsalo cuando el usuario pregunte dónde está su pedido."""
    # aquí va la llamada a tu API o a tu base de datos
    return f"Pedido {order_id}: entregado al transportista"

if __name__ == "__main__":
    mcp.run()

Ése es el servidor entero. FastMCP convierte la firma de la función en un esquema de parámetros y el docstring en la descripción de la herramienta. La descripción es justo lo que lee el modelo al decidir si llamarla, así que no escribas «qué hace la función», sino cuándo usarla.

Ejemplo en TypeScript

npm init -y
npm install @modelcontextprotocol/sdk zod

El archivo server.ts (en package.json hace falta "type": "module"):

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

const server = new McpServer({ name: 'orders', version: '1.0.0' });

server.registerTool(
  'order_status',
  {
    description:
      'Estado de un pedido por su número. Úsalo cuando el usuario pregunte dónde está su pedido.',
    inputSchema: { orderId: z.string().describe('Número de pedido') },
  },
  async ({ orderId }) => ({
    content: [{ type: 'text', text: `Pedido ${orderId}: entregado al transportista` }],
  }),
);

await server.connect(new StdioServerTransport());

En un servidor stdio no se puede escribir en stdout

Un servidor local habla con el cliente por la salida estándar, y cualquier print() o console.log() rompe el protocolo: el cliente recibirá basura en lugar de JSON. Escribe los logs en stderr (console.error; el logging de Python va ahí por defecto).

Comprobación en MCP Inspector

Antes de conectar el servidor a un cliente, compruébalo en Inspector, el depurador oficial con interfaz web: muestra la lista de herramientas y sus esquemas, y permite llamar a cada una a mano.

# Python
uv run mcp dev server.py

# TypeScript y cualquier otro servidor
npx @modelcontextprotocol/inspector npx tsx server.ts

Conexión a Claude Code y otros clientes

Un servidor local se conecta con su comando de arranque. En Claude Code, todo lo que va después de -- es ese comando:

claude mcp add orders -- uv run --directory /path/to/my-mcp server.py
claude mcp add orders-ts -- npx tsx /path/to/server.ts

En los clientes con mcp.json —Cursor, LM Studio y otros— el mismo comando se escribe con los campos command y args. Dónde guarda cada uno su configuración: Claude, Cursor, LM Studio, Codex, OpenCode.

De local a remoto: HTTP

Un servidor local sólo funciona para quien lo arrancó. Para conectarse por una URL —desde claude.ai, desde otra máquina, con todo el equipo— el servidor pasa al transporte Streamable HTTP. En FastMCP es una línea:

mcp.run(transport="streamable-http")

En TypeScript, StreamableHTTPServerTransport sustituye a StdioServerTransport dentro de tu servidor HTTP (Express, Fastify). Con HTTP aparecen preguntas que el servidor local no tenía:

La preguntaQué resolver
Quién llamaEl acceso: OAuth 2.1 como describe la especificación de MCP, o una clave en la cabecera Authorization. Claude.ai y Cursor saben OAuth; LM Studio y CI son más cómodos con clave, así que lo sensato es admitir ambos
El estadoUn servidor sin sesiones (stateless) es más fácil de escalar: cada petición se basta a sí misma
Las llamadas largasLas operaciones de minutos mantienen la conexión abierta: hacen falta defensas contra los timeouts (ver abajo)
El gastoSi una herramienta cuesta dinero, reserva el importe antes de llamar a la API externa, no después

Tropiezos de producción

Mantenemos en marcha 26 servidores MCP remotos: búsqueda y generación de imágenes, vídeo, música y voz. Esto es lo que las guías breves no cuentan y a nosotros nos costó incidentes.

1. El nombre de una herramienta es más largo de lo que parece

Muchos clientes y modelos pegan el nombre del servidor y el de la herramienta; por ejemplo happyhorse-video-generation_happyhorse-reference-to-video. Las API compatibles con OpenAI validan ese nombre contra ^[a-zA-Z0-9_-]{1,64}$ y, si se incumple, rechazan la petición entera, no sólo esa herramienta. Usa nombres cortos, en alfabeto latino, sin puntos ni espacios, y comprueba la longitud contando el prefijo del servidor.

2. Más de cinco minutos de silencio cortan la llamada

La generación de vídeo no envía nada hasta tener el resultado. Los clientes HTTP habituales de Node.js traen timeouts de cabeceras y cuerpo de 300 segundos por defecto, y a los cinco minutos justos el cliente corta la conexión, mientras el servidor sigue trabajando, guarda el archivo y cobra por él. Se cura con comentarios keepalive en el flujo SSE (una línea tipo : keepalive cada 20 segundos): el analizador de eventos los descarta, pero los temporizadores de la conexión se reinician.

3. Sin un 401 correcto, el cliente no encuentra OAuth

Para que Claude o Cursor ofrezcan el acceso por su cuenta, una petición sin token debe responderse con 401 y la cabecera WWW-Authenticate: Bearer resource_metadata="…" apuntando a /.well-known/oauth-protected-resource, que a su vez apunta a un servidor de autorización con registro dinámico de clientes y PKCE. Si falta la cabecera, el cliente se limitará a mostrar «error de conexión».

4. La descripción es un prompt

El modelo elige la herramienta sólo por su descripción. Un difuso «trabaja con pedidos» lleva o a llamadas de más, o a que no la llame nunca. Escribe: cuándo llamarla, cuándo no llamarla, qué devuelve y qué hacer ante un error. Y devuelve los errores como texto con una pista: el modelo lo leerá y corregirá los parámetros solo.

Si quieres un servidor propio sólo para buscar o generar

Para eso no hace falta escribir un servidor desde cero: los servidores remotos ya hechos se conectan con una URL; ver MCP para buscar y la generación de imágenes y vídeo. Y cómo armar un agente a partir de servidores está en el capítulo agentes de IA y MCP.

Preguntas frecuentes

¿Cómo creo mi propio servidor MCP?
Coge un SDK oficial —Python (FastMCP) o TypeScript— y declara tus herramientas: nombre, descripción y parámetros. En Python basta una función con el decorador @mcp.tool() y una llamada a mcp.run(). Comprueba el servidor en MCP Inspector (mcp dev server.py) y conéctalo a un cliente, por ejemplo con claude mcp add nombre -- uv run server.py.
¿En qué lenguaje escribo un servidor MCP?
Hay SDK oficiales para Python, TypeScript, Java, Kotlin, C#, Go y otros lenguajes. Para arrancar rápido es más cómodo Python con FastMCP; si el servidor va a formar parte de un backend en Node.js, TypeScript. El protocolo es el mismo y al cliente le da igual en qué esté escrito el servidor.
¿En qué se diferencia un servidor MCP local de uno remoto?
El local lo arranca el cliente como un proceso en tu ordenador y se comunica por stdio: puede llegar a tus archivos, pero sólo funciona para ti. El remoto funciona por HTTP (Streamable HTTP), se conecta con una URL desde cualquier dispositivo y desde Claude web, pero necesita alojamiento y autorización.
¿Cómo depuro un servidor MCP?
Con MCP Inspector: npx @modelcontextprotocol/inspector más el comando de arranque del servidor, o mcp dev server.py en Python. Inspector muestra las herramientas y sus esquemas y permite llamar a cada una a mano. En un servidor local, escribe los logs en stderr: stdout es del protocolo.
¿Cómo hago la autorización en un servidor MCP remoto?
La especificación de MCP describe OAuth 2.1: ante una petición sin token, el servidor responde 401 con una cabecera WWW-Authenticate que apunta a los metadatos del recurso protegido, y el servidor de autorización admite PKCE y registro dinámico de clientes. Para clientes sin OAuth conviene aceptar además una clave en la cabecera Authorization: Bearer.

Pruébalo tú mismo

Todo lo de esta guía funciona en Twelver

Un chat para texto, imágenes, vídeo, música y voz — sin servicios ni suscripciones aparte.

Abrir el chat de Twelver
Califica tu experiencia