Tutoriales de VPS
MCP Server autoalojado en un VPS (2026): guía de despliegue seguro con Streamable HTTP, OAuth, Docker y Caddy
¿Quieres poner un MCP Server en un VPS para que Claude, Cursor y otros clientes lo usen en remoto? Este artículo despliega un servicio Streamable HTTP con FastMCP, Docker Compose y Caddy, y explica OAuth, la validación de Origin, el mínimo privilegio, los logs y los errores habituales.

MCP Server autoalojado en un VPS (2026): guía de despliegue seguro con Streamable HTTP, OAuth, Docker y Caddy
La primera vez que mucha gente prueba MCP, ejecuta stdio en local: Claude Desktop, Claude Code o Cursor lanzan un proceso hijo y, si las herramientas funcionan, se da por exitoso.
Pero en cuanto te encuentras con alguna de estas situaciones, el stdio local ya no basta:
- Quieres compartir un mismo conjunto de herramientas entre un portátil, un equipo de escritorio y una máquina de desarrollo remota
- Quieres dar las herramientas a miembros del equipo o a varios clientes a la vez
- Necesitas un punto de entrada remoto que esté siempre en línea, sea auditable y se pueda actualizar
Es entonces cuando entra en juego el MCP Server remoto en un VPS.
Pongamos la conclusión por delante: para nuevos despliegues remotos de MCP en 2026, la línea principal debe ser Streamable HTTP, no el antiguo HTTP+SSE. stdio sigue siendo la opción por defecto para la integración local; solo merece la pena llevarlo a un VPS cuando realmente necesitas acceso remoto, HTTPS, autenticación, proxy inverso y reutilización por varios clientes.
Nota sobre el alcance: este artículo se basa en la especificación publicada de MCP
2025-11-25y también te recuerda que durante la inicialización se negocia la versión del protocolo. Es decir, el cliente, el SDK y el servidor no tienen por qué quedarse todos en el mismo número de versión; en escenarios HTTP hay que mirar el resultado de la negociación yMCP-Protocol-Version, y no fijar por escrito que "todos los clientes solo admiten una versión concreta".
Si tus herramientas solo se usan en la máquina local —leer un repositorio de código local, llamar a una base de datos local, ejecutar scripts que solo usas tú—, stdio sigue siendo lo más cómodo.
Los límites de stdio son claros:
- El cliente lanza el proceso hijo por sí mismo
- Los permisos heredan básicamente el entorno de ejecución local
- No necesita dominio, proxy inverso, HTTPS ni OAuth
- Tampoco es adecuado para que otros dispositivos se conecten en remoto directamente
El MCP remoto encaja mejor en estos escenarios:
- Quieres exponer herramientas de negocio de solo lectura detrás de una entrada HTTPS pública
- Quieres que Claude, Cursor y una consola de herramientas interna compartan un mismo servicio
- Quieres centralizar logs, cuotas, actualizaciones y acceso a las API de bajada
Si todavía estás valorando cómo elegir un VPS, empieza por la guía de selección de configuración de VPS.
| Método | Para qué sirve | Cómo usarlo ahora |
|---|---|---|
stdio | Clientes locales que lanzan herramientas locales | Sigue usándose; opción por defecto en local |
Antiguo HTTP+SSE | Compatibilidad con clientes antiguos | Consérvalo solo cuando debas ser compatible con implementaciones antiguas |
Streamable HTTP | La nueva línea principal para despliegue remoto | VPS, autoalojamiento, HTTPS y autenticación pasan por él |
Hay tres puntos clave:
stdiono es un protocolo de despliegue remoto. Es la forma en que "el cliente inicia un proceso hijo local".- Streamable HTTP usa un único endpoint MCP. El cliente envía JSON-RPC mediante POST y la petición puede devolver JSON o un flujo SSE; si hace falta, el cliente también puede enviar un GET al mismo endpoint para establecer un flujo de mensajes del servidor.
- El antiguo HTTP+SSE ha quedado sustituido. El viejo patrón de "doble endpoint" solo sirve para la compatibilidad con clientes heredados y ya no debería ser la opción por defecto en proyectos nuevos.
Así que, si ahora vas a crear un servicio nuevo en un VPS, el enfoque más fiable es:
- Para clientes nuevos: prioriza Streamable HTTP
- Para clientes antiguos: toma una decisión de compatibilidad explícita y, si hace falta, mantén aparte el endpoint antiguo
- No hagas "degradación automática a un modo antiguo más laxo y menos seguro"
El propio MCP Server a menudo no es la parte que más recursos consume; lo que realmente los consume suele ser:
- Las bases de datos de bajada
- La automatización de navegador
- La búsqueda vectorial
- Las llamadas a la API del modelo
- La conversión de archivos, el OCR, los rastreadores
Si el ejemplo de este artículo es solo una herramienta de solo lectura, 1C1G o 2C2G pueden servir como punto de partida para "la escala del ejemplo de este artículo". Pero esto no es un requisito mínimo oficial de MCP ni una promesa general. Al final debes dimensionar según la carga de tus propias herramientas.
En la práctica conviene fijarse más en:
- Si tus herramientas llaman a las API de bajada de forma concurrente
- Si necesitas caché de estado o almacenamiento de sesiones
- Qué volumen de logs, de copias de seguridad y de reintentos por fallo tienes
- Si vas a compartir el mismo VPS con otras aplicaciones
Si vas a alojar varios servicios con Compose, consulta también la guía de health checks y límites de recursos de Docker Compose en producción.
Este artículo usa una arquitectura deliberadamente contenida:
Claude / Cursor / 其他 MCP Client
↓ HTTPS
Caddy
↓ Docker 内网
FastMCP Streamable HTTP
↓ allowlist / 只读访问
固定的数据源或内部 API
Aquí hay que separar los requisitos del protocolo de las decisiones operativas:
- A nivel del protocolo MCP: la autorización es una capacidad opcional para todo el ecosistema MCP; no todas las implementaciones tienen que usar OAuth.
stdioen escenarios locales suele heredar el entorno de ejecución local; pero esta arquitectura HTTP de producción orientada a internet y accesible por clientes remotos de este artículo debe tener autenticación, y en cuanto implementes MCP HTTP auth deberías seguir la especificación de autorización de MCP para los metadatos del recurso, la cabecera Bearer, la validación de audience/resource del token y HTTPS. - Decisiones operativas de este artículo: FastMCP, Docker Compose, Caddy, exponer solo 80/443, un endpoint de health independiente, rotación de logs, límites de recursos
Es decir, Caddy y Compose no son requisitos obligatorios del protocolo MCP; simplemente son cómodos y fáciles de mantener para esta implementación.
En el ejemplo evito a propósito el shell arbitrario, las rutas arbitrarias, la captura de URL arbitrarias y cualquier "herramienta de consulta universal". Lo que más teme el MCP remoto no es tener pocas funciones, sino una entrada demasiado amplia, parámetros demasiado libres y que al final el modelo pueda probarlo todo.
A continuación tienes un app.py completo que encaja con el Dockerfile, el Compose y el Caddy que vienen después. Coloca la herramienta de solo lectura, JWTVerifier, la ruta no sensible /health y app = mcp.http_app(path="/mcp") en el mismo archivo; la protección de Host / Origin se sigue dejando a la configuración oficial de variables de entorno que viene después:
import os
from typing import Literal
from fastmcp import FastMCP
from fastmcp.server.auth.providers.jwt import JWTVerifier
from starlette.requests import Request
from starlette.responses import PlainTextResponse
auth = JWTVerifier(
jwks_uri=os.environ["AUTH_JWKS_URI"],
issuer=os.environ["AUTH_ISSUER"],
audience=os.environ["AUTH_AUDIENCE"],
)
mcp = FastMCP("ops-readonly", auth=auth)
SERVICE_STATE = {
"api": {"status": "ok", "latency_ms": 32},
"worker": {"status": "degraded", "latency_ms": 85},
"billing": {"status": "ok", "latency_ms": 41},
}
@mcp.tool()
def get_service_status(service: Literal["api", "worker", "billing"]) -> dict:
"""Return a tiny, read-only status snapshot for one allowed service."""
item = SERVICE_STATE[service]
return {
"service": service,
"status": item["status"],
"latency_ms": item["latency_ms"],
}
@mcp.custom_route("/health", methods=["GET"])
async def health(_: Request) -> PlainTextResponse:
return PlainTextResponse("OK")
app = mcp.http_app(
path="/mcp",
)
El límite de seguridad de este ejemplo es bastante claro:
- La entrada no es una cadena arbitraria, sino una allowlist fija
- La salida es JSON pequeño, sin tokens, contraseñas ni logs en bruto
- No tiene efectos secundarios: no escribe archivos, no borra datos, no hace peticiones externas
/healthexiste por separado, así que la comprobación de vida operativa no necesita llamar de verdad a una herramienta de alto privilegio- La autenticación se engancha a
FastMCP(..., auth=auth)desde el principio, así que nunca se convierte en un/mcppúblico sin autenticación app = mcp.http_app(path="/mcp")y los posterioresuvicorn app:app, Compose y Caddy apuntan a la misma entrada- La protección de Host / Origin no se "mete a la fuerza" en
http_app(...)con parámetros personalizados, sino que la proporcionan de forma unificada los posterioresFASTMCP_HTTP_HOST_ORIGIN_PROTECTION,FASTMCP_HTTP_ALLOWED_HOSTSyFASTMCP_HTTP_ALLOWED_ORIGINS
Si quieres conectar herramientas a una base de datos, un sistema de tickets o una API interna, te recomiendo mantener esta idea: una herramienta hace una sola cosa, el rango de parámetros se reduce en lo posible y se devuelven los menos campos posibles.
6. Puesta en producción con Docker Compose: fija versiones, limita privilegios y no expongas el puerto de la aplicación directamente
El error más habitual en producción es, por comodidad, exponer ports: ["8000:8000"] directamente al exterior y luego ejecutar pip install de forma temporal al arrancar el contenedor, de modo que ni las versiones de las dependencias ni el contenido de la imagen son reproducibles. Esto funciona a corto plazo, pero a largo plazo deja agujeros con facilidad.
Un enfoque más estable: las dependencias se instalan en la fase de construcción de la imagen, la aplicación solo sirve el 8000 en la red interna de Docker y la entrada pública se da únicamente a Caddy.
Primero fija las dependencias en requirements.txt:
fastmcp==3.2.4
uvicorn==0.35.0
Luego usa un Dockerfile explícito:
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt \
&& useradd --create-home --uid 10001 appuser
COPY app.py /app/app.py
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
El sentido de este app.py no es "todos los MCP deben copiarlo", sino: cuando pones un MCP HTTP remoto en un entorno de producción público, al menos debe tener la autenticación activada desde el primer día, en lugar de ser un endpoint público sin autenticación. Si después conectas un proveedor como Keycloak, Scalekit o Azure, sigue la documentación oficial de cada proveedor; no inventes tu propio código OAuth.
services:
mcp-app:
build:
context: .
dockerfile: Dockerfile
image: mcp-server:2026-07
expose:
- "8000"
environment:
FASTMCP_HTTP_HOST_ORIGIN_PROTECTION: "true"
FASTMCP_HTTP_ALLOWED_HOSTS: '["mcp.example.com"]'
FASTMCP_HTTP_ALLOWED_ORIGINS: '["https://app.example.com"]'
AUTH_JWKS_URI: "https://auth.example.com/.well-known/jwks.json"
AUTH_ISSUER: "https://auth.example.com"
AUTH_AUDIENCE: "https://mcp.example.com/mcp"
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2).read()"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s
restart: unless-stopped
read_only: true
tmpfs:
- /tmp
user: "10001:10001"
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
mem_limit: 512m
cpus: 1.0
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks:
- edge
caddy:
image: caddy:2.10
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
depends_on:
mcp-app:
condition: service_healthy
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks:
- edge
networks:
edge:
volumes:
caddy-data:
caddy-config:
Hay varios puntos en este Compose que se añaden a propósito:
- Versiones fijadas: el ejemplo fija
python:3.12-slim,fastmcp==3.2.4,uvicorn==0.35.0ycaddy:2.10; como mínimo no useslatest - Dependencias instaladas en la construcción: ya no se ejecuta
pip installde forma temporal al arrancar el contenedor, y el contenido de la imagen es reproducible - Puerto de la aplicación no expuesto directamente: solo Caddy expone 80/443 a internet
- Comprobación de vida independiente: el healthcheck pasa por
/healthy no ejecuta de verdad herramientas de negocio - Mínimo privilegio:
read_only,cap_drop: [ALL], no root,no-new-privileges - Límites de recursos: para que una petición anómala no tumbe toda la máquina
- Protección de Host / Origin: con las oficiales
FASTMCP_HTTP_ALLOWED_HOSTS,FASTMCP_HTTP_ALLOWED_ORIGINSyFASTMCP_HTTP_HOST_ORIGIN_PROTECTION - Inyección de credenciales: ajustes como
AUTH_JWKS_URIpueden ir por variables de entorno; si tu proveedor además necesita un client secret, cámbialo a inyección por secret file, no lo escribas en la imagen ni en Git - Rotación de logs: al menos mantén controlado el tamaño de los logs del contenedor
Para ejecuciones realmente prolongadas, conviene fijar también el digest de la imagen base y pasar antes por un entorno de preproducción antes de actualizar.
mcp.example.com {
encode zstd gzip
@health path /health
handle @health {
reverse_proxy mcp-app:8000
}
handle /mcp* {
reverse_proxy mcp-app:8000 {
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host}
transport http {
read_timeout 30s
write_timeout 30s
}
}
}
request_body {
max_size 2MB
}
log {
output stdout
format json
}
}
Lo importante aquí no es que "Caddy sea más avanzado que otro", sino que encaja bien en unas cuantas cosas:
- HTTPS automático
- Una entrada pública unificada
- Mantener el contenedor de la aplicación solo en la red interna
- Añadir tiempos de espera, límites del cuerpo de la petición y logs de acceso en la capa de proxy
Aquí he elegido que Caddy escriba en stdout y que la rotación de logs la gestione el driver json-file de Docker. Hay que dejar claro el límite: Caddy se encarga de producir logs de acceso estructurados; quien realmente rota según 10m * 3 es el runtime del contenedor, no el rotador de archivos propio de Caddy.
También hay un detalle que se pasa por alto con facilidad: no mezcles /mcp y /mcp/. Si algún proxy o cliente primero recibe una redirección 301/308 y luego reenvía la petición, la cabecera Authorization puede perderse. Lo más fiable es escribir la misma URL exacta en app.py, en Caddy y en la configuración del cliente; por ejemplo, que todos escriban https://mcp.example.com/mcp, y no confiar en que la redirección lo corrija automáticamente.
Si aún no dominas esta capa, empieza por la guía de proxy inverso y HTTPS automático con Caddy.
Este apartado es especialmente fácil de escribir mal.
En la cabecera HTTP Authorization:
Authorization: Bearer your-access-token
No lo pongas en la query string, no lo pongas en la barra de direcciones del navegador y no hagas un "token fijo en la URL" por comodidad. La especificación dice claramente que el access token de las peticiones HTTP no debe ir en la URI.
Como mínimo:
- Si el token lo emitió el servidor de autorización correspondiente
- Si el audience / resource apunta al recurso MCP actual
- Si ha caducado y si el scope es insuficiente
Esto es especialmente importante porque el token passthrough es un antipatrón. Es decir, no puedes reenviar a una API de bajada el token que trae el cliente sin validar el audience.
Principalmente defiende frente a problemas del tipo DNS rebinding. Para Streamable HTTP, si Origin existe y no es válido, el servidor debe devolver directamente 403 Forbidden. No es un "punto extra", sino una de las defensas básicas del MCP remoto.
No.
La sesión solo correlaciona un conjunto de interacciones, para que el servidor pueda mantener estado, reanudar flujos SSE e identificar el ciclo de vida de la sesión. No puede sustituir a la autenticación. La autenticación real sigue dependiendo del Bearer token de cada petición HTTP.
La especificación también lo dice claramente: si el servidor termina una sesión y el cliente después hace peticiones con ese session ID, el servidor debe devolver 404 Not Found, y el cliente debe volver a inicializar en lugar de tratar la sesión antigua como un pase permanente.
Una nota más directamente relacionada con la forma de despliegue: un VPS de una sola máquina y un solo proceso puede seguir usando sesiones con estado. Pero si después vas a pasar a varios workers, varios contenedores o escalado horizontal, deberías seguir el diseño stateless_http=True de la documentación de FastMCP y tratar de antemano el estado compartido, la recuperación de sesiones y la consistencia del almacenamiento externo.
La documentación de FastMCP ya da indicaciones sobre HTTP auth, StaticTokenVerifier, JWTVerifier y distintos proveedores. Hay que verlo en dos niveles:
- Nivel de especificación MCP: HTTP auth es una capacidad opcional, pero en cuanto la implementes, hazlo según la especificación de autorización de MCP
- Este despliegue público del artículo: la autenticación es obligatoria por defecto, así que no puedes exponer
/mcpcomo una entrada pública sin autenticación
Si quieres un ejemplo que ya aparezca en la documentación oficial y con sintaxis estable, JWTVerifier es más fiable que "que yo te escriba a mano el código de conexión de un proveedor OAuth". Si no has confirmado del todo la API de proveedores de la rama estable actual, no copies en producción un fragmento OAuth de un blog que "parece que funciona".
Un enfoque más fiable es fijar primero los límites:
- El servicio remoto solo expone el transporte HTTP
- La autenticación pasa por un proveedor estándar de OAuth / JWT
- Los metadatos del recurso y el descubrimiento del servidor de autorización se exponen según la especificación
- Caddy se encarga de HTTPS; la aplicación se encarga de la validación Bearer y de la validación de Origin
- La sesión hace de sesión, no de auth
Una vez que un MCP remoto está en producción, no basta con que "se conecte"; deben darse todos estos resultados:
- Con un Bearer token válido, completar initialize / list-tools / tool call: éxito
- Sin token: 401
- Origin fuera de la allowlist: 403
- Sesión caducada o desconocida: 404, y luego el cliente vuelve a inicializar
- El servidor no admite algún método: 405
- Content-Type incorrecto o tipo de medio no admitido: 415
Si además necesitas ser compatible con clientes antiguos, deja la política clara:
- Clientes nuevos: conectar directamente a Streamable HTTP
/mcp - Clientes antiguos: tú decides si ofreces además una entrada de compatibilidad con el antiguo HTTP+SSE
- No desactives la autenticación, ni la validación de Origin, ni abras un "endpoint temporal sin autenticación" solo por ser compatible con clientes antiguos
Ser compatible es una cosa; mantener el mismo límite de seguridad al ser compatible es otra. Lo segundo es lo que determina si has puesto en producción un servicio real o solo has trasladado una demo a internet.
10. Lista de seguridad en producción: el verdadero peligro es la inyección de prompts y la fuga de datos
El mayor error de concepto sobre el MCP remoto es poner todo el riesgo en la "exposición pública". De hecho, aunque ya tengas HTTPS y OAuth activados, los siguientes problemas te siguen afectando:
- Inyección de prompts: el modelo recibe contexto malicioso y se le induce a llamar a herramientas que no debería
- Data exfiltration / fuga de datos: las herramientas devuelven demasiados campos y sacan datos internos fuera del límite
- Fallo del mínimo privilegio: solo querías leer el estado y al final la herramienta puede leer toda la base de datos, ejecutar shell, escanear directorios
- Fuga en los logs: tokens, parámetros completos y respuestas en bruto de bajada acaban en los logs
Te recomiendo revisar tu servicio con esta lista:
- ¿Cada tool tiene el mínimo privilegio?
- ¿Hay entradas de alto riesgo como shell arbitrario, archivos arbitrarios o URL arbitrarias?
- ¿Se recortan los campos de la salida?
- ¿Las API keys de bajada se inyectan por variables de entorno o secret file?
- ¿El acceso saliente está en allowlist o al menos controlado?
- ¿Los logs están desensibilizados?
- ¿Antes de actualizar has mirado la spec, el SDK y las release notes de FastMCP?
- ¿Haces copia de seguridad del código fuente, el Compose, la configuración de Caddy y el estado necesario, en lugar de confiar en que "el contenedor en sí es la copia de seguridad"?
Añado tres recomendaciones operativas especialmente prácticas.
Primera: los logs deben permitir diagnosticar, pero no completar el contexto al atacante. Un enfoque más fiable es registrar el request ID, el nombre del tool, el tiempo de ejecución, el estado HTTP y si la llamada de bajada ha expirado; no vuelques a disco el Bearer token, la entrada completa del usuario ni la respuesta completa del upstream.
Segunda: antes de actualizar, mira tres cosas: los cambios de la especificación MCP, las release notes de FastMCP/el SDK oficial y la versión del cliente que estás usando. Muchos problemas de "ayer conectaba y hoy da 401 / 415" no son porque el código se haya roto de repente, sino porque han cambiado el transporte, las cabeceras o las convenciones de auth.
Tercera: mantén los pasos de recuperación como un activo de producción. Al menos deja escrito el cambio de dominio, la recuperación del directorio de datos de Caddy, el origen del secret file, la redistribución de la allowlist de bajada y los comandos de rollback de la imagen antigua. Cuando el MCP remoto falla de verdad, lo más valioso no es cuántos fragmentos de configuración tengas, sino si puedes recuperar /mcp en 15 minutos.
Si tu MCP además se conecta a una pasarela de IA autoalojada, conviene dibujar aparte el límite de permisos; no mezcles sin cuidado un MCP Server que puede leer datos con una pasarela de IA que puede cambiar el modelo upstream. Para ideas relacionadas, consulta la guía de autoalojamiento de una pasarela de API de IA.
Tampoco escatimes en copias de seguridad: al menos ensaya el proceso de recuperación del código fuente, la configuración, la gestión de secretos y el estado necesario. En un incidente real, importa más si lo has ensayado que "cuántos paquetes tar has copiado"; consulta también la guía de simulacros de copia de seguridad y recuperación.
Normalmente:
- No se envía Bearer token
- El token ha caducado
- El token no se emitió para el recurso MCP actual
- La cadena de descubrimiento del servidor de autorización está mal configurada
Mira primero WWW-Authenticate y la dirección de metadatos del recurso, y luego confirma si resource / audience coinciden.
Las dos direcciones más habituales:
Originno es válido- Scope insuficiente
Si es lo segundo, la especificación permite que el servidor indique al cliente en WWW-Authenticate qué scopes faltan. No des todos los scopes de golpe; escalar poco a poco según el mínimo privilegio es más fiable.
En MCP remoto, 404 no tiene por qué ser una ruta incorrecta; también puede ser una sesión invalidada. Si la petición lleva un MCP-Session-Id antiguo, el servidor puede devolver 404 directamente, y el cliente debe volver a inicializar.
Se ve a menudo cuando:
- El cliente envía GET a un endpoint que no admite flujos SSE
- Has configurado mal las reglas del proxy y algún método queda bloqueado
- El servidor no permite DELETE para terminar la sesión
Normalmente las cabeceras de la petición están mal, por ejemplo un POST JSON-RPC sin el Content-Type o el Accept correctos según lo requerido. Estos problemas a menudo no son un error de lógica del servidor, sino que el cliente o la capa de proxy han estropeado las cabeceras.
Si vas a crear un servicio remoto nuevo, FastMCP encaja bien para una primera versión, por una razón sencilla: ejemplos cortos, material de despliegue HTTP completo y límites de herramientas de solo lectura fáciles de controlar.
Si eres un equipo de Node.js, o necesitas controlar con más detalle el middleware, el transporte HTTP y los helpers de OAuth, el SDK oficial de TypeScript es más flexible.
Si ya tienes un lote de herramientas locales stdio y no quieres reescribirlas, entonces plantéate soluciones puente como mcp-proxy. Pero eso es más una ruta de migración que la forma por defecto recomendada en este artículo para proyectos nuevos.
Otro punto en el que es fácil tropezar: las ramas main de los repositorios del SDK oficial de Python y del SDK oficial de TypeScript están impulsando ahora la beta de v2. Lo que de verdad es estable en producción sigue siendo la línea v1.x.
- SDK de Python: el README de
maindice explícitamente que v2 es pre-release y que en producción conviene mirarv1.x - SDK de TypeScript:
maintambién dice explícitamente v2 beta, y en producción se sigue usandov1.x
Así que, si hoy pones algo en producción, no copies directamente los ejemplos nuevos de la rama main. Primero confirma qué línea estable, qué documentación y qué versión del protocolo estás siguiendo realmente.
Si solo usas herramientas en local, sigue con stdio; si de verdad quieres ponerlo en un VPS para que varios clientes accedan en remoto, Streamable HTTP + HTTPS + OAuth/Bearer estándar + validación de Origin + herramientas de mínimo privilegio es la línea principal más fiable.
FastMCP, Docker Compose y Caddy son buenas opciones de implementación, pero resuelven "cómo desplegar y cómo operar", no "el protocolo puede ignorar la seguridad". Lo que de verdad determina si este MCP remoto puede funcionar a largo plazo siguen siendo esas viejas cuestiones prácticas:
- Si los límites de las herramientas son suficientemente estrechos
- Si se valida el audience del token
- Si se ha confundido una sesión con la autenticación
- Si los logs, las copias de seguridad y las actualizaciones están institucionalizados
Hazlo bien y el MCP remoto se comportará como un servicio a largo plazo, no como una demo que de momento funciona.
- MCP Transports (2025-11-25)
https://modelcontextprotocol.io/specification/2025-11-25/basic/transports - MCP Authorization (2025-11-25)
https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization - MCP Security Best Practices (2025-11-25)
https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices - Build MCP Server
https://modelcontextprotocol.io/docs/develop/build-server - Connect Remote Servers
https://modelcontextprotocol.io/docs/develop/connect-remote-servers - Official Python SDK Repository
https://github.com/modelcontextprotocol/python-sdk - Official TypeScript SDK Repository
https://github.com/modelcontextprotocol/typescript-sdk - FastMCP HTTP Deployment
https://gofastmcp.com/deployment/http - FastMCP Authentication
https://gofastmcp.com/servers/auth/