Crea tu primer servidor MCP en Python: herramientas, recursos, comandos e inspeccionador
Construye un servidor MCP en Python con FastMCP: una herramienta práctica con herramientas, recursos y comandos, comparación entre stdio y HTTP, depuración con Inspector y una configuración para 3 clientes.
Imagínese tres archivos de Python casi idénticos, llamados Claude, Cursor y ChatGPT, cada uno de los cuales llama a la API de Slack, analiza la respuesta y la reorganiza para un cliente de IA específico. Ahora imagine reemplazar a los tres con un único archivo del cual cada uno de esos clientes pueda leer directamente. Cuando Slack modifica un campo, solo es necesario corregirlo una vez. Ese archivo único es un servidor MCP, y su creación tarda aproximadamente una tarde.
MCP en pocas palabras
El Protocolo de Contexto de Modelo es lo suficientemente sencillo como para describirse en tres mensajes. Un cliente se conecta a un servidor y pregunta qué puede hacer. El servidor responde con las herramientas que puede utilizar un modelo, los datos que puede obtener un host y los prompts que puede elegir un usuario. Luego el cliente llama a una herramienta con argumentos, el servidor la ejecuta y responde. Eso es todo en términos de comunicación.
Lo que hace que MCP valga la pena usar no es el formato de los mensajes, sino el acuerdo compartido. Claude Code, Cursor, Copilot de VS Code y otros realizan el mismo proceso de conexión, por lo que un archivo de Python escrito ahora puede adjuntarse a cualquiera de ellos más adelante.
Un servidor puede ofrecer uno de tres tipos de capacidades:
- Herramientas son funciones llamables que el modelo selecciona por nombre, como por ejemplo una que busca en una base de datos, envía una rama o condensa un registro.
- Recursos son materiales de solo lectura, como un archivo de configuración, un esquema o commits recientes, que el servidor obtiene cuando lo necesita en segundo plano.
- Plantillas de comandos son plantillas reutilizables que el usuario elige; estas aparecen como comandos con barra diagonal en Claude Code o como entradas en el menú en Claude Desktop.
En la práctica, la mayoría de los servidores incluyen herramientas, y los recursos y las instrucciones son secundarios. Si solo puedes aprender una de las tres cosas, aprende a usar las herramientas. Cómo se transmiten los mensajes y cómo funciona la autenticación son aspectos importantes más adelante. Por ahora, recuerda esto: un servidor, muchos clientes, tres elementos que exponer.
Por qué las integraciones solían multiplicarse
Antes de MCP, conectar una herramienta de IA a un backend significaba escribir un adaptador entre la estructura de tu backend y el formato de llamada a herramientas que requería ese cliente. Un cliente esperaba el esquema de herramientas de Anthropic, otro utilizaba su propio formato, y un tercero quería la estructura de llamadas a funciones de OpenAI. La búsqueda de Slack permanecía idéntica mientras alrededor de ella se creaban tres capas de enlace.
La aritmética se vuelve complicada rápidamente. Tres clientes y dos servidores backend significan seis adaptadores que mantener. Un cuarto cliente eleva la cifra a ocho, y un tercer servidor backend la lleva a doce, cada uno con su propia autenticación, mecanismos de reintentos y particularidades. MCP descompone ese conjunto. Los clientes y los servidores backend ya no se comunican entre sí; ambos hablan con el protocolo, por lo que adoptar un nuevo cliente basado en IA solo requiere una entrada de configuración en lugar de una nueva integración.
El diseño es deliberadamente desequilibrado. El cliente decide todo: qué servidores cargar, cuándo llamar a las herramientas y cómo mostrar los resultados. La función del servidor es mucho más limitada: simplemente declara sus herramientas y responde cuando se le solicita. Esa asimetría mantiene el protocolo lo suficientemente compacto como para que cualquier cliente importante pueda implementarlo rápidamente.
Un ayudante para reuniones diarias en un solo archivo
Un ayudante para standup es una herramienta interna clásica que los ingenieros siguen reescribiendo. Lee el registro local de git y resume lo que un desarrollador hizo ayer. Aquí se presentará como una herramienta, un recurso y una sugerencia en un único archivo de Python.
Necesitas el paquete FastMCP, la capa de Python de alto nivel sobre la cual se basan muchos servidores MCP.
pip install fastmcp
Cree standup_server.py. Comienza importando la biblioteca y definiendo la herramienta.
# standup_server.py
import subprocess
from typing import TypedDict
from fastmcp import FastMCP
mcp = FastMCP("standup-helper")
class StandupSummary(TypedDict):
branch: str
since: str
commit_count: int
commits: list[str]
@mcp.tool()
def summarize_standup(
branch: str = "main",
since: str = "yesterday",
) -> StandupSummary:
"""Summarize recent git activity for a standup.
Reads the local git log on the given branch since the
given time window. Returns commit count and one-line
subjects for each commit. Used by AI clients via MCP.
"""
try:
result = subprocess.run(
[
"git", "log",
f"--since={since}",
"--pretty=format:%h %s",
branch,
],
capture_output=True,
text=True,
timeout=5,
check=True,
)
except (subprocess.CalledProcessError,
subprocess.TimeoutExpired) as exc:
return {
"branch": branch,
"since": since,
"commit_count": 0,
"commits": [f"git error: {exc}"],
}
lines = [
line for line in result.stdout.splitlines() if line
]
return {
"branch": branch,
"since": since,
"commit_count": len(lines),
"commits": lines,
}
# resources and prompts come next
Detrás de escena, el decorador realiza cuatro tareas: lee las indicaciones de tipo para generar un esquema JSON, utiliza la documentación como descripción que se muestra al modelo, enumera la herramienta entre las capacidades del servidor durante el proceso de conexión y dirige las llamadas JSON-RPC entrantes a la función correspondiente.
Tenga en cuenta la llamada a un subproceso con un tiempo de espera estricto de cinco segundos. Si una orden git se queda atascada, la aplicación anfitriona que espera la respuesta de la herramienta también se bloqueará, por lo que toda llamada externa necesita un tiempo de espera. La rama de errores también devuelve la misma estructura de diccionario que la rama de éxito, de modo que el modelo siempre recibe una estructura predecible. Una herramienta más completa podría además llamar a la CLI de GitHub para solicitudes de pull abierto, añadiendo otra llamada a subproceso y adjuntando su salida al diccionario.
A continuación vienen el recurso y el prompt, que completan el archivo.
# standup_server.py (continued)
@mcp.resource("recent_commits://main")
def recent_commits_main() -> str:
"""Last 10 commits on the main branch, plain text.
Resources are pulled by the host opportunistically.
They are not invoked by the model the way tools are.
"""
result = subprocess.run(
[
"git", "log",
"-n", "10",
"--pretty=format:%h %ad %s",
"--date=short",
"main",
],
capture_output=True,
text=True,
timeout=5,
)
return result.stdout or "(no commits found)"
@mcp.prompt("standup_template")
def standup_template(focus: str = "shipping work") -> str:
"""Reusable standup question exposed as a prompt
template. Surfaces as a slash command in clients that
expose prompts (e.g. /standup_template in Claude Code).
"""
return (
f"Summarize what I worked on yesterday, focusing on "
f"{focus}. Use the summarize_standup tool to get the "
f"git log, then write a one-paragraph standup note."
)
if __name__ == "__main__":
mcp.run()
Un URI de recurso es simplemente una cadena de texto. No existe un esquema estricto que regule su estructura, aunque la convención consiste en un esquema seguido de una ruta, y FastMCP admite plantillas de URI cuando se necesitan parámetros. La instrucción es simplemente una función que devuelve texto formateado. Los clientes la presentan como una plantilla que el usuario invoca, la cual Claude Code expone como un comando con barra diagonal. Las instrucciones nunca se insertan automáticamente, por lo que el usuario siempre las elige.
La última línea inicia el servidor. No hay puerto, ni framework web ni bucle de eventos que gestionar, ya que FastMCP se encarga de los mecanismos de entrada/salida estándar.
Transportes y autenticación
El transporte determina cómo viajan los mensajes JSON-RPC entre el cliente y el servidor.
Use stdio para servidores locales. El host ejecuta su script como un proceso hijo. Stdio evita todo el stack de red: no hay puerto, ni TLS ni autenticación, ya que el proceso hijo hereda los límites de confianza del host.
Use Streamable HTTP cuando el servidor debe mantener el estado entre solicitudes o ser accesible desde varias máquinas. Un servidor remoto está expuesto a la red, por lo que necesita una autenticación estricta.
# bottom of standup_server.py
if __name__ == "__main__":
# Default transport is stdio. The host (Claude Code,
# Cursor, Claude Desktop, etc.) launches this script
# as a subprocess and talks to it over stdin/stdout.
# No port, no TLS, no auth. The trust boundary is
# whoever launched the host.
mcp.run()
# To expose the same server over the network instead,
# use Streamable HTTP. SSE was deprecated in the
# March 2025 spec update. Do not use it for new code.
#
# Production HTTP also needs an auth layer in front.
# OAuth 2.1 with Dynamic Client Registration is the
# current pattern. See Week 22 for the full flow.
#
# mcp.run(
# transport="streamable-http",
# host="0.0.0.0",
# port=8000,
# )
Llamar a run sin argumentos significa utilizar stdio, y el bloque comentado muestra cómo se ve la actualización HTTP. Esta versión mantiene stdio, que es la opción adecuada para la mayoría de las herramientas de desarrollo internas. Una herramienta que solo lee archivos locales o un repositorio local no debería estar detrás de una interfaz de red en absoluto.
El ciclo de desarrollo local
No pruebe un servidor nuevo conectándolo directamente a un cliente de IA. Los clientes están diseñados para ser resilientes: ignoran silenciosamente las incoherencias en los esquemas y las capacidades faltantes para mantener el funcionamiento del chat, y cuando una herramienta falla, el modelo simplemente se disculpa.
Utilice en su lugar el inspector oficial MCP, una interfaz de desarrollo basada en navegador que se comunica con su servidor a través de stdio.
npx @modelcontextprotocol/inspector python standup_server.py
La orden ejecuta tu script como un subproceso y abre un panel de control local. Puedes leer el intercambio de datos en bruto, ver con exactitud qué capacidades anunció tu servidor y utilizar herramientas y recursos en tiempo real.
Tres problemas suelen surgir en este ciclo:
- Desequilibrio en el esquema. El valor devuelto por la función no coincide con su indicación de tipo, y el Inspector lo señala de inmediato.
- Capacidad faltante. Llamas a algo que el servidor nunca anunció, y el panel de control muestra lo que realmente se registró durante la negociación.
- Contaminación de la salida. Cualquier llamada a
print()no deseada daña el flujo JSON-RPC, ya que stdio transporta los mensajes. Dirige los registros a error estándar con el módulo logging de Python, que el protocolo reserva para diagnósticos legibles.
FastMCP también ofrece un modo de desarrollo que reinicia el servidor cuando cambia el archivo, lo cual es útil mientras se realizan iteraciones en el Inspector.
Un servidor, varios clientes
Ahora el diseño da sus frutos. Existe un único archivo de Python, verificado en el Inspector, y puede asociarse a diferentes clientes de IA sin necesidad de código específico para cada cliente. Cada cliente lee un archivo de configuración que indica los servidores locales a ejecutar, y la estructura JSON es la misma en Claude Code, Cursor y Claude Desktop: un objeto mcpServers que contiene la orden a ejecutar y sus argumentos.
{
"mcpServers": {
"standup-helper": {
"command": "python",
"args": ["/Users/you/code/standup_server.py"]
}
}
}
{
"mcpServers": {
"standup-helper": {
"command": "python",
"args": ["/Users/you/code/standup_server.py"]
}
}
}
{
"mcpServers": {
"standup-helper": {
"command": "python",
"args": ["/Users/you/code/standup_server.py"]
}
}
}
La clave, la orden y los argumentos son idénticos en los tres casos. Solo la ubicación del archivo de configuración difiere según el cliente.
Abra cualquiera de ellos y solicite una reunión rápida. El cliente inicia el proceso de Python, negocia las capacidades a través de stdio, muestra la herramienta de resumen al modelo y redirige la llamada del modelo de vuelta a su script. Git se ejecuta, el diccionario devuelve los datos y el modelo escribe el informe de la reunión rápida, siguiendo siempre el mismo camino de código.
ChatGPT no está en esta lista porque sus conectores funcionan con servidores remotos a través de Streamable HTTP. Integrar este complemento en él requeriría una URL pública y una capa de autenticación, lo cual representa un perfil operativo muy diferente al de esta implementación estrictamente local.
Cuándo no usar MCP
El protocolo no es una herramienta universal. Hay algunas situaciones en las que no es adecuado:
- Rutas muy sensibles a la latencia. Cada llamada a una herramienta implica un viaje de ida y vuelta en JSON-RPC, por lo que las herramientas dentro de bucles muy cerrados deben utilizar una integración directa con el SDK.
- Salida interactiva personalizada. Si necesita un gráfico específico o un formulario personalizado, está creando una integración con el host, ya que el protocolo separa los datos de la presentación.
- Configuraciones que controla por completo. Si un mismo equipo desarrolla tanto el cliente de IA como el backend, conectarlos directamente puede ser más fácil de mantener que insertar MCP como intermediario.
Un protocolo pequeño, un gran cambio
Tres primitivas, dos mecanismos de transporte y un intercambio JSON-RPC componen todo el protocolo. Esa sencillez es su ventaja: separa a quienes desarrollan la inteligencia artificial de quienes proporcionan el contexto. Escribir el primer servidor solo lleva una tarde y rinde frutos cada vez que aparece otra herramienta de IA.