La mayoría de las guías sobre Model Context Protocol dan por supuesto que estás construyendo un servidor delante de una tienda o una base de datos, algo con pedidos e inventario. Nosotros hicimos lo menos obvio: convertimos un sitio de marketing estático en un servidor MCP en vivo y de solo lectura. Cualquier cliente MCP puede ahora hacer POST a un único endpoint y preguntar qué servicios existen o cómo iniciar una consulta, en JSON-RPC tipado, sin analizar una sola página de HTML.
Este es un relato práctico de esa implementación. Es deliberadamente acotado: unas pocas herramientas de solo lectura, sin base de datos, sin escrituras. Esa estrechez es el punto, y de ella surgen la mayoría de las decisiones interesantes. Lo que sigue explica por qué un sitio de contenido se beneficia de un endpoint MCP en absoluto, por qué el endpoint es una Cloudflare Pages Function en lugar de una ruta de framework, por qué escribimos JSON-RPC a mano en lugar de tirar del SDK, y el único fallo de la barra final que rompe en silencio cada POST si lo pasas por alto.
Por qué poner un servidor MCP en un sitio de marketing
Una tienda expone MCP porque un agente necesita realizar una acción: comprobar stock, montar un carrito, hacer un pedido. Un sitio de marketing no tiene carrito. Entonces, ¿para qué sirve la superficie de herramientas?
La respuesta honesta es el descubrimiento por agentes. Nuestro sitio ya ejecutaba una capa de descubrimiento por agentes: un llms.txt, un conjunto de documentos .well-known y una tarjeta de servidor MCP en /.well-known/mcp/server-card.json. Esa tarjeta describía un servidor, su transporte y sus capacidades. Incluso declaraba una capacidad tools. Había un problema: nada vivía detrás de ella. La tarjeta anunciaba un servidor que no existía. Un agente que seguía la tarjeta e intentaba conectarse no obtenía nada.
Ese es un estado común para estos archivos. Se generan, se anuncian en una cabecera Link y después apuntan a un recurso que nunca se construyó. La tarjeta es una promesa sin implementación.
Así que el objetivo no era inventar una nueva superficie. Era hacer real una superficie existente y ya anunciada. Las herramientas se derivan directamente de para qué sirve un sitio de marketing:
check_servicesdevuelve el catálogo de servicios: id, nombre, descripción, categoría y URL canónica.request_quotedevuelve la URL de contacto localizada y las instrucciones para enviar una consulta.
La primera versión salió con esas dos. Después llegaron tres herramientas más de solo lectura, cada una como una entrada nueva en el array TOOLS y una rama nueva en callTool, sin tocar el transporte:
check_tech_stackdevuelve el stack tecnológico con el que trabajamos, agrupado por categorías y con un filtro de categoría opcional.get_case_studiesdevuelve los casos de estudio con su stack, filtrados por un término de búsqueda opcional.search_blog_postsbusca en el blog por título, descripción, categorías, etiquetas o slug, con filtros opcionales de idioma y categoría y un límite de resultados.
Cinco herramientas, todas de solo lectura. Un agente puede ahora enumerar lo que hacemos y dar al usuario el siguiente paso correcto, de forma determinista, en lugar de adivinar a partir de la prosa.
Cómo ejecutar un servidor MCP como Cloudflare Pages Function
El sitio está construido con Astro en modo de salida estática. No hay adaptador SSR. Cada página se prerenderiza a HTML en el momento de la compilación, y los archivos de estilo API que sirve (el JSON del catálogo de servicios, el perfil del agente) son archivos estáticos bajo public/, no rutas de servidor.
Ese único hecho determina todo el diseño. Un endpoint dinámico que lee un cuerpo de petición y devuelve una respuesta calculada no puede ser una ruta de Astro aquí, porque no hay servidor que la ejecute. Añadir uno significaría cambiar el modo de salida a híbrido o de servidor y asociar un adaptador, lo cual es un cambio grande y arriesgado en una compilación que ya produce miles de páginas.
El sitio, sin embargo, ya ejecuta Cloudflare Pages Functions escritas a mano: un middleware que gestiona redirecciones y unos pocos manejadores API pequeños. Esa es la costura. Un endpoint MCP dinámico es solo una Pages Function más, functions/mcp.ts, situada junto a las demás. Se despliega con la compilación normal, no necesita adaptador y no cambia nada de cómo se renderizan las páginas.
La lección se generaliza. Cuando quieras añadir un endpoint dinámico a un sitio por lo demás estático, no recurras al modo de servidor del framework. Recurre al primitivo de función edge que tu proveedor ya te ofrece. En Cloudflare Pages eso es una Function. El coste es un archivo, no una reconstrucción de tu modelo de renderizado.
Cómo crear un servidor MCP sin el SDK oficial
El reflejo cuando lees “servidor MCP” es instalar @modelcontextprotocol/sdk. Para un servidor stdio con muchas herramientas, recursos y prompts, ese reflejo es correcto. Para unas pocas herramientas de solo lectura en una función edge, no lo es.
El protocolo de transporte de MCP es JSON-RPC 2.0. Un servidor que responde a un cliente MCP necesita gestionar un pequeño conjunto de métodos:
initialize, donde el servidor devuelve su versión de protocolo, sus capacidades y su identidad.tools/list, donde devuelve las definiciones de herramientas con su JSON Schema.tools/call, donde ejecuta una herramienta con nombre y devuelve el resultado envuelto en contenido MCP.- notificaciones como
notifications/initialized, que no llevan id y no esperan respuesta.
Esa es toda la superficie para un servidor de solo lectura. Implementarla a mano son aproximadamente 120 líneas. El despacho es un simple switch:
switch (method) {
case "initialize":
return ok({ protocolVersion, capabilities: { tools: { listChanged: false } }, serverInfo });
case "tools/list":
return ok({ tools: TOOLS });
case "tools/call":
return ok({ content: [{ type: "text", text: callTool(name, args, services).text }] });
default:
return err(-32601, `Method not found: ${method}`);
}Compara eso con el SDK. El SDK está diseñado para stdio y para el protocolo completo. En un runtime edge heredas sus supuestos de empaquetado y su maquinaria de transporte para funciones que no estás usando. Para unas pocas herramientas de lectura, una dependencia sobre la que tienes que razonar es un peor trato que 120 líneas que controlas por completo. Esta es la misma contención de solo lectura primero aplicada a las dependencias: expón el mínimo, controla el mínimo.
Lo único que merece la pena hacer con cuidado a mano es el contrato de errores. JSON-RPC tiene códigos de error definidos, y los clientes MCP los esperan: -32700 para un error de parseo, -32601 para un método desconocido, -32602 para parámetros incorrectos. Acertar con esos es lo que hace que un servidor escrito a mano se comporte como uno real ante un cliente estricto.
Por qué un servidor MCP público debe ser de solo lectura
La tentadora herramienta siguiente es una que capta un lead: recoger un nombre, un correo y un mensaje, y enviárnoslo por email. No la construyas como una herramienta MCP abierta.
Un endpoint público con una herramienta escribible es un sumidero de spam. Cualquiera en internet puede llamar a tools/call con submit_quote y una carga falsa, y ahora tu bandeja de entrada, tu CRM o tu base de datos es un objetivo sin fricción y sin ningún humano en el proceso. En el momento en que una herramienta escribe, el endpoint necesita autenticación, limitación de velocidad y gestión de abusos, lo cual es una superficie grande para que un sitio de marketing la defienda.
Así que request_quote no escribe nada. Devuelve la URL de contacto localizada y una guía estructurada:
if (name === "request_quote") {
const lang = LOCALES.includes(String(args?.lang)) ? String(args.lang) : "en";
return { text: JSON.stringify({
contact_url: CONTACT_URLS[lang],
method: "web-form",
note: "Read-only endpoint. Submit the inquiry through the contact form; this tool does not send it for you.",
reply_time: "within one working day",
}, null, 2) };
}El agente obtiene todo lo que necesita para hacer avanzar al usuario: la página de contacto correcta para el idioma del usuario, y una declaración clara de que el envío se realiza a través del formulario. El humano permanece en el proceso. El endpoint no tiene nada de lo que abusar.
Este no es un diseño más débil forzado por la cautela. Es la misma postura que recomendamos a los clientes que construyen MCP para sus propios sistemas: solo lectura primero, añade escrituras solo detrás de autenticación cuando haya una razón concreta. Un servidor de solo lectura es honesto sobre lo que es, y es seguro dejarlo abierto.
| Elección de diseño | Herramienta abierta de lectura y escritura | Traspaso de solo lectura |
|---|---|---|
| Autenticación requerida | Sí | No |
| Superficie de spam | Alta | Ninguna |
| Humano en el proceso | Opcional | Siempre |
| Código que defender | Límite de velocidad, validación, gestión de abusos | Ninguno |
| Idoneidad para un sitio de marketing público | Mala | Buena |
Por qué la redirección por barra final pierde el cuerpo POST
Esta costó tiempo real de depuración, así que merece la pena decirlo con claridad.
Nuestro sitio canonicaliza las URLs con una barra final. El middleware redirige con un 301 cualquier ruta sin ella a la versión con barra. Eso está bien para las páginas. Es un desastre silencioso para un endpoint JSON-RPC.
Un POST /mcp choca con la redirección y devuelve un 301 a /mcp/. Muchos clientes HTTP, al seguir la redirección, no reenvían el cuerpo del POST, o degradan el método. El cliente MCP ve una respuesta vacía o fallida y concluye que el servidor está roto. El servidor está bien. La petición nunca llegó con su cuerpo.
La solución no es luchar contra el middleware. Es anunciar la URL que el sitio realmente sirve. La tarjeta del servidor MCP y todas las referencias de descubrimiento apuntan a /mcp/, con la barra final, de modo que un cliente que se comporte bien haga POST directamente a la URL canónica y nunca toque la redirección. Si tu framework o proveedor normaliza las barras, decide qué forma es canónica y anuncia exactamente esa forma en todas partes.
Puedes verificar el endpoint en vivo de la misma forma que nosotros:
curl -s -X POST https://wppoland.com/mcp/ \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Un POST sin barra al mismo host te mostrará el 301 en su lugar.
Cómo ayuda un servidor MCP al GEO y a los agentes de IA
La generative engine optimization consiste sobre todo en ser legible y verificable para los modelos. Datos estructurados, entidades claras, afirmaciones consistentes entre tus propias superficies y las externas. Un endpoint MCP en vivo es una versión fuerte de esa legibilidad: no es una pista sobre tu contenido, es una interfaz invocable a él.
Dos cosas hacen que valga el pequeño esfuerzo incluso mientras la adopción es temprana. Primero, cierra la brecha entre lo que la capa de descubrimiento anuncia y lo que existe. Una tarjeta que apunta a un servidor funcional es una señal coherente; una tarjeta que apunta a nada es una señal rota, y las señales rotas son peores que las ausentes. Segundo, para cualquiera que venda esta capacidad, el endpoint es la prueba. Construimos servidores MCP para clientes, y la demostración más creíble de ello es uno público, de solo lectura, corriendo en nuestro propio dominio, junto al servidor MCP de WooCommerce de código abierto que publicamos.
Ningún proveedor importante de IA se compromete formalmente hoy a leer tarjetas de servidor MCP o llms.txt. Esa es una advertencia justa y la exponemos con claridad. El endpoint es barato de ejecutar, hace verdadera una promesa anunciada, y es un artefacto que funciona en lugar de una afirmación. Esos tres juntos superan el listón.
Telemetría MCP de otro sitio, octubre de 2026: Amir Pournasserian contó el tráfico del servidor MCP de un sitio personal durante tres días y medio (del 14 al 18 de octubre de 2026). Los clientes abrieron unas 300 conexiones (308 initialize, 281 tools/list) e hicieron 29 llamadas a herramientas, 15 búsquedas y 14 descargas. Muchos se presentaban como directorios y clasificaciones de servidores MCP o de agentes. También pidieron una docena de direcciones de descubrimiento que ese servidor no tiene, entre ellas /.well-known/mcp/server-card.json, que el autor atribuye a borradores anteriores de Server Cards. Es la dirección que usa nuestra tarjeta, enlazada desde nuestro /.well-known/ai-catalog.json. La propuesta MCP Server Cards, SEP-2127, ahora con estado Final, admite tarjetas en cualquier dirección no reservada a la que apunte un AI Catalog y recomienda <streamable-http-url>/server-card. El autor no encontró ningún cliente extendido que siga un catálogo por sí solo. Claude Code, Claude.ai, VS Code y Cursor toman la URL del servidor de quien lo usa o de una galería propia. Un sitio personal y tres días y medio de datos. La mayoría de los clientes pidió la lista de herramientas y pocos llamaron a alguna.
Qué funciones MCP puede omitir un servidor pequeño
Una lista breve, porque saber qué omite una implementación es tan útil como saber qué incluye.
- Ninguna herramienta por adelantado.
get_case_studiesno estaba en la primera versión, y los agentes leían los casos de estudio desdellms.txty las páginas enlazadas hasta que llegó la herramienta. Cada herramienta de la lista responde hoy a una pregunta que los agentes hacen de verdad. - Sin capacidad
resourcesniprompts. El servidor declara solotools, porque eso es todo lo que implementa. Declarar una capacidad que no sirves es peor que omitirla: un cliente que llama aresources/listobtiene un error en lugar de un limpio “no soportado”. - Sin SDK, como se cubrió arriba.
- Sin escrituras, como se cubrió arriba.
Cada omisión es una decisión, no un descuido. El endpoint hace exactamente lo que requieren sus herramientas de solo lectura y nada más, por lo que es lo bastante pequeño para razonar sobre él de una sentada y lo bastante seguro para dejarlo abierto a internet.
Qué exige la especificación MCP a un endpoint Streamable HTTP
El transporte detrás de POST /mcp/ es Streamable HTTP. La especificación de transportes de MCP (versión 2025-06-18) lo define como el sustituto del transporte HTTP+SSE de la versión 2024-11-05. La mayoría de sus reglas tratan del streaming, y un servidor de solo lectura las cumple sin transmitir nada en streaming.
- Cada mensaje JSON-RPC del cliente es un POST HTTP nuevo a una única ruta de endpoint. El cliente debe enviar una cabecera
Acceptque incluya tantoapplication/jsoncomotext/event-stream. - Cuando el cuerpo del POST es una notificación o una respuesta, el servidor debe contestar
202 Acceptedsin cuerpo. Nuestra función lo hace connotifications/initialized. - Cuando el cuerpo es una petición, el servidor elige: abrir un stream SSE o devolver un único objeto JSON con
Content-Type: application/json. El cliente debe aceptar ambos casos, así que un servidor sin nada que transmitir siempre puede responder con JSON simple. - Las sesiones son opcionales. Un servidor puede asignar una cabecera
Mcp-Session-Iden la respuesta ainitialize, pero nada le obliga. Sin identificador de sesión no hay almacén de sesiones que mantener, algo que encaja con una función edge que no guarda estado entre peticiones. - Tras la inicialización, el cliente debe enviar la cabecera
MCP-Protocol-Versionen cada petición. Si falta y el servidor no tiene otra forma de saber la versión, debería asumir2025-03-26. Un valor no válido o no soportado debe recibir400 Bad Request.
La misma página incluye una advertencia de seguridad para este transporte. Los servidores deben validar la cabecera Origin en todas las conexiones entrantes, los servidores que se ejecutan en local deberían escuchar solo en 127.0.0.1 y no en todas las interfaces de red, y los servidores deberían autenticar las conexiones. El ataque que nombra la especificación es el DNS rebinding: un sitio web remoto usa el navegador del visitante para llegar a un servidor MCP en su propia máquina.
Cómo funciona la negociación de versión del protocolo MCP
La negociación de versión ocurre dentro de initialize, y la especificación del ciclo de vida la resume en pocas reglas. El cliente debe enviar una versión del protocolo que soporte, y debería ser la más reciente que soporte. Si el servidor soporta esa versión, debe responder con la misma. Si no, debe responder con otra versión que sí soporte, de nuevo preferiblemente la más reciente. Un cliente que no soporte la versión de la respuesta debería desconectarse.
Las versiones del protocolo son cadenas de fecha como 2024-11-05, 2025-03-26 y 2025-06-18. Nuestra función usa 2025-06-18 como valor por defecto. En un servidor escrito a mano la regla se reduce a una decisión: la lista de versiones para las que respondes. La página del ciclo de vida muestra la forma del error ante una incompatibilidad: código -32602, el mensaje “Unsupported protocol version” y un objeto data con las versiones soportadas y la solicitada.
Una vez negociada, ambas partes deben respetar la versión acordada y usar solo las capacidades negociadas. Esa es la razón formal por la que el servidor descrito arriba declara solo tools: una capacidad en la respuesta a initialize es una promesa sobre la que el cliente tiene derecho a actuar.
Cómo marcar una herramienta MCP como de solo lectura
El esquema de MCP da a cada herramienta un objeto opcional annotations con cuatro indicaciones de comportamiento:
| Indicación | Significado | Por defecto |
|---|---|---|
readOnlyHint | la herramienta no modifica su entorno | false |
destructiveHint | la herramienta puede hacer cambios destructivos; solo cuenta cuando readOnlyHint es false | true |
idempotentHint | repetir la llamada con los mismos argumentos no tiene efecto adicional; solo cuenta cuando readOnlyHint es false | false |
openWorldHint | la herramienta puede interactuar con un mundo abierto de entidades externas | true |
Los valores por defecto son pesimistas. Una herramienta sin anotaciones queda descrita como una que puede escribir, puede destruir datos y puede salir de su propio dominio. Las herramientas de esta implementación no llevan anotaciones, así que las indicaciones por sí solas las describirían de ese modo. Añadir readOnlyHint: true a la definición de cada herramienta es un cambio de una línea.
El esquema también dice que estas propiedades son indicaciones y no una descripción fiel del comportamiento, y la especificación de herramientas establece que los clientes deben tratar las anotaciones como no fiables salvo que vengan de servidores de confianza. Una indicación nunca sustituye al control. La propiedad de solo lectura de este endpoint vive en el código, donde ninguna herramienta tiene una ruta de escritura. La anotación solo describe esa propiedad a un cliente que decida confiar en ella.
La especificación de herramientas impone tres obligaciones a todo servidor: validar todas las entradas de las herramientas, aplicar controles de acceso adecuados y limitar la frecuencia de las llamadas. Nuestra función cubre la primera rechazando los argumentos mal formados con -32602. La especificación separa además dos tipos de fallo: los errores de protocolo, como una herramienta desconocida, vuelven como errores JSON-RPC, mientras que una herramienta que se ejecutó y falló devuelve un resultado normal con isError: true, para que el modelo vea el texto del fallo y pueda reaccionar.
Más guías para crear servidores MCP
Para una guía operativa paso a paso sobre configuración, validación de esquemas JSON-RPC y conexión de Claude Desktop a nuestro endpoint de producción, consulta nuestra guía del servidor MCP en vivo de WPPoland. Si quieres la versión más completa de estas decisiones, el grupo de contenidos en torno a este artículo cubre el terreno adyacente: construir un servidor MCP para WooCommerce para el caso con estado y orientado a tienda, MCP frente a REST: cuándo gana cada uno para saber si necesitas MCP en absoluto, y patrones de autenticación MCP para el momento en que añades una herramienta escribible y necesitas autenticación. La versión comercial de este trabajo vive en la página de servicio de desarrollo de servidores MCP.
La versión corta cabe en una frase: un sitio de marketing puede ser un servidor MCP, el endpoint es una función edge, mantén cada herramienta como solo lectura, y anuncia la URL que tu proveedor realmente sirve.






