Cómo crear tu propio servidor MCP en Python (FastMCP, 2026): expón tus herramientas a cualquier cliente — Cesar Ayala
← Todos los artículos

Cómo crear tu propio servidor MCP en Python (FastMCP, 2026): expón tus herramientas a cualquier cliente

Instala el SDK oficial de Python con pip install mcp y usa FastMCP: crea mcp = FastMCP("nombre"), decora una función tipada con @mcp.tool() para que sus type hints y docstring generen el schema, expón datos con @mcp.resource() y arráncalo con mcp.run(transport="stdio"). Conéctalo con claude mcp add, la config de escritorio o el mcp_servers del Agent SDK.

¿Cómo crear tu propio servidor MCP en Python?

Para crear un servidor MCP, instala el SDK oficial con pip install mcp y usa FastMCP: defines una función tipada, la decoras y arrancas el servidor. Lo escribes una vez y cualquier cliente MCP lo usa. En código: mcp = FastMCP("nombre"), decoras con @mcp.tool() (los type hints y el docstring generan el schema), expones datos con @mcp.resource(), arrancas con mcp.run(transport="stdio") y lo conectas con claude mcp add, la config de escritorio o mcp_servers del Agent SDK.

¿Qué es MCP y por qué construir tu propio servidor?

MCP (Model Context Protocol) es un estándar abierto que separa dos cosas que antes vivían mezcladas: proveer contexto y hablar con el LLM. Escribes un servidor una sola vez que expone tus herramientas y tus datos, y cualquier cliente MCP (Claude Code, Claude for Desktop, el Agent SDK y otros) puede consumirlo. No re-escribes la integración por cada cliente; la escribes una vez y el resto la descubre.

Ese es el argumento práctico para crear un servidor MCP propio. Si tienes una API interna, una base de datos de facturas o un catálogo de productos, un servidor MCP los vuelve alcanzables para cualquier agente sin acoplarte a un vendor. Es lo contrario de conectar un agente a datos ya existentes con servidores de terceros: aquí tú eres quien provee la herramienta.

La otra cara: un servidor MCP ejecuta código real y toca datos reales. Lo tratamos como cualquier superficie de API desde el primer commit, no como un juguete de demo.

Las tres capacidades que expone un servidor: Tools, Resources, Prompts

Un servidor MCP puede exponer tres tipos de capacidad, y conviene tenerlos claros antes de escribir una línea:

  • Tools — funciones que el modelo puede llamar, con aprobación del usuario. Es la capacidad activa: buscar, calcular, escribir en un sistema.
  • Resources — datos legibles tipo archivo: respuestas de una API, contenido de un archivo, un registro de base de datos. El modelo los lee, no los ejecuta.
  • Prompts — plantillas reutilizables que el cliente puede ofrecer al usuario.

La distinción entre Tools y Resources es la que más se confunde. Regla mental: si tiene efectos o hace trabajo, es una Tool; si solo entrega contenido para leer, es un Resource.

Las tres capacidades de un servidor MCP

ToolsFunciones que el modelo llama (con aprobación) — buscar, calcular, escribir
ResourcesDatos legibles tipo archivo — respuestas de API, archivos, registros
PromptsPlantillas reutilizables que el cliente ofrece al usuario
Un servidor puede exponer cualquier combinación. Casi todos arrancan con una o dos Tools.

Instala el SDK y levanta FastMCP: pip install mcp

El SDK oficial de Python se instala con un comando:

pip install mcp

Dentro del SDK vive FastMCP, la API de alto nivel y pythónica. Su gracia es que usa tus type hints y tus docstrings para auto-generar el schema de cada herramienta, así que escribes una función normal y bien tipada, y el schema sale gratis. (El SDK de TypeScript, por si lo necesitas del lado JS, es @modelcontextprotocol/sdk.)

El esqueleto de un servidor son dos líneas:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")

Ese FastMCP("weather") es el nombre con el que el servidor se anuncia al cliente. A partir de esta instancia mcp cuelgas todo lo demás con decoradores.

Define una herramienta con @mcp.tool() — type hints + docstring son el schema

Aquí está el corazón del asunto. Decoras una función tipada con @mcp.tool() y FastMCP hace el trabajo pesado: el docstring se vuelve la descripción de la herramienta y los parámetros tipados se vuelven el input schema. No escribes JSON Schema a mano.

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")

@mcp.tool()
def get_forecast(city: str, days: int = 3) -> str:
    """Get the weather forecast for a city.

    Args:
        city: Name of the city, e.g. "Guadalajara".
        days: Number of days to forecast (1-7).
    """
    # Call your real weather API here.
    return f"Forecast for {city}: sunny for the next {days} days."

Fíjate en lo que hiciste sin darte cuenta: city: str y days: int = 3 le dicen al cliente que city es un string obligatorio y days un entero opcional con default. El docstring es lo que el modelo lee para decidir cuándo usar la herramienta. Por eso el docstring no es decoración: es parte del contrato. Escríbelo pensando en que un modelo lo va a leer para decidir si te llama.

Este es el mismo instinto que aplica cuando defines herramientas a mano en un tool loop con la Messages API — la diferencia es que FastMCP te ahorra escribir el schema tú mismo.

De función a herramienta MCP

  1. Escribes una función tipadaParámetros con type hints y valores por defecto
  2. Le pones @mcp.tool()El decorador la registra como herramienta
  3. El docstring se vuelve la descripciónEs lo que el modelo lee para decidir si te llama
  4. Los type hints se vuelven el schemaFastMCP genera el input schema por ti
Una función tipada es la única fuente de verdad, tanto para el schema como para la implementación.

Expón tus datos con @mcp.resource()

Si lo que quieres es entregar datos legibles y no una acción, usas @mcp.resource("scheme://path"). El decorador recibe un URI con esquema propio, y la función devuelve el contenido:

@mcp.resource("config://app-settings")
def app_settings() -> str:
    """The current application settings as JSON."""
    return '{"region": "MX", "currency": "MXN", "timezone": "America/Mexico_City"}'

Puedes parametrizar el URI con plantillas, igual que una ruta:

@mcp.resource("customer://{rfc}")
def customer_profile(rfc: str) -> str:
    """Return the profile for a customer by their RFC."""
    # Look up the customer in your database by rfc.
    return f"Profile for customer with RFC {rfc}"

El placeholder {rfc} del URI se vuelve el parámetro de la función: el cliente pide customer://XAXX010101000 y FastMCP lo enruta a tu función con rfc ya lleno. La regla de diseño: un Resource lee, una Tool actúa. Si el agente necesita el saldo de un cliente para razonar, es un Resource. Si necesita emitir un reembolso, es una Tool — y una que va detrás de aprobación humana, como verás más abajo.

Córrelo: mcp.run(transport=“stdio”) y stdio vs HTTP

Con las herramientas y recursos declarados, arrancas el servidor con una línea al final del archivo:

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

El parámetro transport es la decisión de arquitectura. Tienes dos caminos:

  • stdio — para servidores locales. El cliente lanza tu servidor como un subproceso y hablan por entrada/salida estándar. Es lo más simple y lo que quieres para empezar: sin puertos, sin red, sin auth de red.
  • HTTP (SSE / Streamable HTTP) — para servidores remotos, donde el servidor vive en otra máquina y varios clientes se conectan por red.

Para tu primer servidor, quédate con stdio. Es cero configuración de red y encaja directo con cómo los clientes de escritorio y CLI lanzan procesos hijos.

stdio vs HTTP: qué transporte elegir

stdio (local)

  • El cliente lanza el servidor como subproceso
  • Hablan por entrada/salida estándar
  • Sin puertos ni red — cero config
  • Ideal para empezar y para uso personal

HTTP (remoto)

  • El servidor vive en otra máquina
  • SSE / Streamable HTTP por red
  • Varios clientes se conectan al mismo servidor
  • Requiere auth de red y apuntar al spec vigente
Elige stdio para desarrollo local; los transportes HTTP para servidores remotos, en red y multi-cliente.

Conéctalo a un cliente: Claude Code, Claude for Desktop, el Agent SDK

El servidor no sirve de nada hasta que un cliente lo descubre. Hay tres destinos comunes, y en los tres el cliente encuentra tus herramientas automáticamente tras reiniciar.

Claude Code — un solo comando desde tu terminal:

claude mcp add weather -- python /path/to/weather_server.py

Claude for Desktop — agregas el comando y sus argumentos al archivo de configuración claude_desktop_config.json:

{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["/path/to/weather_server.py"]
    }
  }
}

Claude Agent SDK — lo registras vía la opción mcp_servers, para que tus agentes programáticos vean las mismas herramientas. Si estás construyendo agentes con el SDK, el patrón encaja con levantar agentes con el Claude Agent SDK.

En los tres casos, después de conectar reinicias el cliente y tus herramientas aparecen listas para llamarse. Si trabajas dentro de Claude Code a diario, revisa también qué es Claude Code y cómo usarlo para el flujo completo.

Del código al cliente que lo usa

Escribes el servidorFastMCP + @mcp.tool() + @mcp.resource()
Lo arrancasmcp.run(transport="stdio")
Lo conectasclaude mcp add, config de escritorio o mcp_servers
Reinicias el clienteDescubre tus herramientas automáticamente
El agente las llamaCon aprobación del usuario
Todos los clientes siguen los mismos pasos: apúntalos al comando que lanza tu servidor y descubren las herramientas por ti.

¿Servidor remoto? La nota del spec 2026-07-28

Si tu servidor va a ser remoto por HTTP y no local por stdio, hay una fecha que importa: el spec de MCP tuvo un release el 2026-07-28, un rework del SDK (v2) que empuja hacia el spec HTTP stateless. La consecuencia práctica es que, para un servidor remoto nuevo, debes apuntar al spec vigente y no copiar un ejemplo viejo de SSE.

No lo cubro a fondo aquí para no desviar el tutorial local, pero si vas por remoto o vienes migrando, tengo la guía dedicada: migrar tu servidor MCP al spec stateless 2026-07-28. Empieza local con stdio, valida tus herramientas, y solo entonces piensa en remoto.

Seguridad: ejecuta código real, trátalo como cualquier API

Repito lo que dije al inicio porque es lo que separa un demo de algo que pones en producción: tu servidor MCP ejecuta código real y alcanza datos reales. Trátalo como cualquier superficie de API.

En concreto, tres reglas que no negocio:

  • Autentica y aplica privilegio mínimo. No expongas una herramienta con más scope del que su tarea necesita. Una tool de solo lectura no debería poder escribir.
  • Valida las entradas. Los parámetros llegan de un modelo que a su vez puede leer contenido no confiable; no confíes ciegamente en lo que te pasan.
  • No sobre-expongas. Cada Tool adicional es superficie de ataque. Si una acción toca pagos o datos fiscales, va detrás de aprobación humana, punto.

Esto es un resumen, no el tratado completo. El detalle operativo —las familias de ataque y el checklist de blindaje— está en cómo asegurar un servidor MCP. Léelo antes de darle a tu servidor cualquier scope sensible.

Construir vs. reusar: cuándo escribir tu propio servidor

No todo merece un servidor propio. Antes de escribir el tuyo, la pregunta honesta es si ya existe uno que haga el trabajo.

Reusa cuando la integración es común y hay un servidor mantenido: sistemas de archivos, GitHub, bases de datos populares. Reinventarlo solo te da más código que auditar.

Construye cuando lo que expones es tuyo y nadie más lo tiene: tu API interna, tu lógica de negocio, tu base de datos de facturas o tu catálogo. Ahí el servidor propio es la jugada correcta, porque escribes la integración una vez y todos tus clientes —Claude Code, el escritorio, tus agentes del SDK— la consumen sin duplicar esfuerzo.

Mi criterio de ingeniero: construye el servidor cuando el valor está en tu dominio y quieres que cualquier cliente MCP lo alcance sin acoplarte a un vendor. Si es una utilidad genérica que alguien ya mantiene bien, reúsala y dedica tu tiempo a lo que sí es tuyo.

Con eso tienes el camino completo: pip install mcp, una función tipada con @mcp.tool(), datos con @mcp.resource(), mcp.run(transport="stdio") y un claude mcp add. Si quieres seguir por la vía de agentes, tengo más guías del hub de agentes de IA con este mismo enfoque de ponerlo en producción.

Fuentes oficiales: Build an MCP server (modelcontextprotocol.io) y el SDK de Python en GitHub.