Patrones de autenticación MCP: OAuth, tokens y cuándo usar cada uno

Patrones de autenticación MCP: OAuth, tokens y cuándo usar cada uno

Última verificación: 22 de septiembre de 2026
10 min de lectura
Guía
500+ proyectos WP
Integración IA

#Patrones de autenticación MCP: OAuth, tokens y cuándo usar cada uno

La especificación Model Context Protocol (modelcontextprotocol.io) define transportes y primitivas. No define la autenticación. Con razón, porque la autenticación es cosa del transporte y del despliegue, no del protocolo. También es el origen del error de producción más habitual que veo: un servidor MCP expuesto a Internet sin autenticación, con herramientas que modifican estado. Cuando hay datos personales de por medio, se suma el artículo 32 del RGPD, que obliga a aplicar medidas técnicas apropiadas.

Este artículo forma parte del pilar sobre desarrollo de servidores MCP.

#TL;DR

  • La lectura de datos públicos con rate limiting es el único caso legítimo de acceso anónimo.
  • Los tokens de API con scopes, guardados con hash y con TTL corto, cubren B2B y headless.
  • OAuth 2.1 con PKCE cubre los asistentes de consumo que actúan en nombre de un usuario autenticado.
  • El rate limiting se asocia al principal; las herramientas que modifican estado reciben un bucket más estrecho.
  • Cada evento de autenticación se registra: emitido, usado, revocado, rechazado.

#Cómo elegir el método de autenticación MCP

Antes de elegir un patrón de autenticación, repaso siempre las mismas cinco preguntas:

  1. ¿Alguna herramienta modifica estado? Si es así, el acceso anónimo queda descartado.
  2. ¿El agente actúa en nombre de un usuario humano concreto? Si es así, OAuth.
  3. ¿El agente actúa como integración B2B sin usuario humano? Si es así, token de API con scopes.
  4. ¿La superficie de datos es accesible desde la Internet pública? Si es así, el rate limiting es obligatorio.
  5. ¿Se mezclan en un mismo servidor herramientas que modifican estado y herramientas de solo lectura? Si es así, scope de autenticación por herramienta, no por servidor.

El árbol se traduce en tres patrones:

PatrónCuándo usarloImplementación
Anónimo + rate limit por IPCatálogo público, solo lectura, carga acotadaEl Worker solo comprueba el bucket de la IP
Token de API con scopesIntegración B2B, runtime de agente headless, sin usuario humanoJWT con claims, almacenamiento con hash, rotación
OAuth 2.1 + PKCEAgente de consumo para un usuario autenticadoAuthorization code flow estándar

Los patrones no se excluyen. Un servidor de producción real suele ejecutar los tres, y cada herramienta lleva la etiqueta del scope que requiere.

#Acceso MCP anónimo de solo lectura con rate limiting

Una herramienta de navegación del catálogo que envuelve /wp-json/wc/v3/products?stock_status=instock expone los mismos datos que el sitio público ya muestra. Poner autenticación delante no mejora la postura de seguridad; solo reduce la disponibilidad. La amenaza real es la carga: un bucle de un agente o un scrape de la competencia puede tumbar el origen de WooCommerce.

Implementación:

async function checkAnonymousRateLimit(request: Request, env: Env): Promise<boolean> {
  const ip = request.headers.get("CF-Connecting-IP") ?? "unknown";
  const key = `rl:anon:${ip}`;
  const count = Number((await env.RATE_LIMIT.get(key)) ?? "0");
  if (count >= 60) return false;
  await env.RATE_LIMIT.put(key, String(count + 1), { expirationTtl: 60 });
  return true;
}

60 peticiones por minuto por IP es mi valor por defecto. Un contador basado en KV es aproximado (las escrituras en KV son eventually consistent); para límites estrictos, Durable Objects o el binding nativo de Rate Limiting de Cloudflare son la herramienta adecuada.

Lo que este patrón no cubre: ninguna herramienta que devuelva datos específicos de un usuario, ninguna que modifique estado, ninguna que revele el stock de productos no públicos. Esas requieren un principal real.

#Tokens de API con scopes para integraciones B2B con MCP

El caso B2B: la integración de un socio aporta un agente que llama a tu servidor MCP en nombre de la organización del socio, no de un usuario humano concreto. Ejemplos: un agente de sincronización de stock en un mayorista, una integración de marketplace en un agregador, un agente de analítica que informa de tendencias de pedidos al BI.

El flujo:

  1. Un administrador en el backend de WordPress crea un token con un nombre, un conjunto de scopes (catalogue:read, inventory:read, orders:write) y una fecha de caducidad (por defecto, 90 días).
  2. El token se muestra al administrador una sola vez y después se guarda como hash SHA-256 más el conjunto de scopes más la caducidad.
  3. El socio configura su cliente MCP con el token en la cabecera Authorization: Bearer <token>.
  4. El Worker calcula el hash del token entrante y lo busca en KV. Si el hash coincide, la caducidad está en el futuro y el scope que exige la herramienta está en el conjunto del token, la llamada continúa.
async function verifyApiToken(authHeader: string | null, env: Env): Promise<TokenContext | null> {
  if (!authHeader?.startsWith("Bearer ")) return null;
  const token = authHeader.slice(7);
  const hash = await sha256(token);
  const record = await env.TOKENS.get(`tok:${hash}`, "json") as TokenRecord | null;
  if (!record) return null;
  if (record.expiresAt < Date.now()) return null;
  return { tokenId: record.id, scopes: record.scopes, principal: record.principal };
}

function requireScope(ctx: TokenContext, scope: string): void {
  if (!ctx.scopes.includes(scope)) {
    throw new McpError("forbidden", `Tool requires scope ${scope}`);
  }
}

Dos hábitos operativos mantienen el patrón honesto:

Rotación, no eternidad. Un token que nunca caduca es un token que dentro de seis meses acaba en un repositorio público de GitHub. 90 días por defecto, con un flujo de renovación en el que el token antiguo y el nuevo conviven durante 7 días, es el patrón que sobrevive a socios reales.

Almacenamiento con hash, no en texto plano. Si tu KV se filtra, los hashes sin el token original no sirven para nada. Si guardaste los tokens en texto plano, todas las integraciones de socios necesitan una rotación inmediata. La diferencia de coste en el momento de la emisión es una llamada a sha256.

#OAuth 2.1 con PKCE en un servidor MCP

El caso de consumo: el usuario abre Claude Desktop, conecta su cuenta de tu tienda mediante OAuth y le pide al agente “muéstrame mi último pedido”. El agente tiene que llamar ahora a tu servidor MCP con credenciales que digan “actúo en nombre del usuario 4231”.

OAuth 2.1 (draft-ietf-oauth-v2-1) es el perfil consolidado. PKCE (RFC 7636) es obligatorio para clientes públicos (aquí entran los asistentes de escritorio sin una parte de servidor confidencial).

El flujo en cinco pasos:

  1. El host MCP abre el navegador en tu endpoint de autorización con response_type=code, code_challenge=<hash S256 del verifier> y los scopes solicitados.
  2. El usuario inicia sesión en tu sitio WordPress (o en tu proveedor de autenticación) y acepta los scopes.
  3. Tu endpoint de autorización redirige al host con un código de autorización de un solo uso.
  4. El host intercambia el código más el code_verifier original en tu endpoint de tokens por un access token (TTL corto, 1 hora) y un refresh token (TTL más largo, 30 días).
  5. El host llama al servidor MCP con Authorization: Bearer <access_token>. El Worker verifica la firma del token, la caducidad y el scope frente a la herramienta invocada.

El access token es un JWT firmado. El Worker lo verifica con la Web Crypto API (documentación de Cloudflare Workers) sin biblioteca externa:

async function verifyJwt(token: string, env: Env): Promise<JwtClaims | null> {
  const [headerB64, payloadB64, sigB64] = token.split(".");
  const data = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
  const sig = base64UrlDecode(sigB64);
  const valid = await crypto.subtle.verify("RS256", env.PUBLIC_KEY, sig, data);
  if (!valid) return null;
  const payload = JSON.parse(new TextDecoder().decode(base64UrlDecode(payloadB64)));
  if (payload.exp * 1000 < Date.now()) return null;
  return payload as JwtClaims;
}

Los scopes del JWT reflejan los del patrón de token con scopes: catalogue:read, orders:read, orders:write. El ID del usuario de WordPress va en el claim sub, así que una llamada orders:read devuelve solo los pedidos de ese usuario.

#Cómo combinar OAuth, tokens y acceso anónimo en un servidor MCP

Un servidor MCP real para WooCommerce suele exponer:

  • catalogue.list y product.detail: anónimo + rate limit por IP.
  • inventory.check (para socios): token con scope inventory:read.
  • order.status (para el usuario autenticado): OAuth con orders:read.
  • order.intent (para el usuario autenticado): OAuth con orders:write, más un rate limit más estrecho.

La lógica de pre-dispatch en el Worker recorre los patrones:

async function authenticate(request: Request, toolName: string, env: Env): Promise<Principal> {
  const requirement = TOOL_AUTH_REQUIREMENTS[toolName];
  if (requirement === "anonymous") {
    if (!await checkAnonymousRateLimit(request, env)) throw new McpError("rate_limit");
    return { kind: "anonymous" };
  }
  const auth = request.headers.get("Authorization");
  if (requirement === "api_token") {
    const ctx = await verifyApiToken(auth, env);
    if (!ctx) throw new McpError("unauthorized");
    return { kind: "api_token", ctx };
  }
  if (requirement === "oauth") {
    const claims = auth?.startsWith("Bearer ") ? await verifyJwt(auth.slice(7), env) : null;
    if (!claims) throw new McpError("unauthorized");
    return { kind: "oauth", claims };
  }
  throw new Error(`Unknown auth requirement for ${toolName}`);
}

El mapa TOOL_AUTH_REQUIREMENTS es la única fuente de verdad sobre qué herramienta necesita qué modo de autenticación. Ninguna herramienta se añade sin una entrada explícita.

#Límites de peticiones MCP por IP, token y usuario

El tráfico anónimo recibe un bucket por IP. El tráfico con token recibe un bucket por token. El tráfico OAuth recibe un bucket por usuario. Las herramientas que modifican estado reciben un techo más bajo sea cual sea el principal.

Para un escenario WooCommerce, mis buckets por defecto son:

Categoría de herramientaAnónimoTokenOAuth
catalogue.* (lectura)60 / minuto / IP600 / minuto / token120 / minuto / usuario
inventory.* (lectura)no permitido300 / minuto / tokenno permitido
order.status (lectura)no permitido60 / minuto / token60 / minuto / usuario
order.intent (escritura)no permitido30 / minuto / token10 / minuto / usuario

Las cifras son un punto de partida; los valores correctos salen de observar tráfico real durante dos semanas y ajustar. Lo que cuenta es la estructura.

#Qué eventos de autenticación MCP registrar

Cada evento relevante para la autenticación acaba en el registro:

  • Token emitido. Administrador, principal de destino, scopes, caducidad.
  • Token usado. ID del token, nombre de la herramienta, principal, latencia, éxito/fallo.
  • Token revocado. ID del token, quién lo revocó, por qué.
  • Token rechazado. Motivo (caducado, falta scope, el hash no coincide), IP, user agent.
  • Intercambio de código OAuth. ID del usuario, scopes concedidos, refresh token emitido.
  • Refresh de OAuth. ID del usuario, nuevo access token, token antiguo sustituido.

Los registros van por Cloudflare Logpush a un almacenamiento a largo plazo. Una consulta de dashboard que sigue los “tokens usados en las últimas 24 horas que no se habían usado en los 90 días anteriores” detecta un probable robo de token. Una consulta de “verificaciones de token fallidas por IP” detecta intentos de credential stuffing.

#Temas relacionados

Este artículo trata la autenticación. El refuerzo más amplio de WordPress está en la guía de seguridad de WordPress. La página del servicio es desarrollo de servidores MCP.

Presupuesto individual, porque el alcance de la autenticación depende de los patrones que exige tu entorno; un servidor de solo lectura en modo anónimo es un proyecto distinto de una superficie completa que emite OAuth.

Siguiente paso

Transforma el artículo en una implementación real

Este bloque refuerza el enlazado interno y lleva al lector al siguiente paso más útil dentro de la arquitectura del sitio.

Cluster relacionado

Explora otros servicios WordPress y base de conocimiento

Refuerza tu negocio con soporte técnico profesional en áreas clave del ecosistema WordPress.

FAQ del artículo

Preguntas frecuentes

Respuestas prácticas para aplicar el tema en la ejecución real.

SEO-readyGEO-readyAEO-ready5 Q&A
¿La especificación MCP exige autenticación?#
No. La especificación Model Context Protocol deja la autenticación a la capa de transporte. Los transportes stdio pueden considerarse de confianza por el mero hecho de ejecutarse en local. Los transportes HTTP expuestos a Internet necesitan una capa de autenticación que el SDK no incluye por defecto.
¿Cuándo es aceptable un servidor MCP anónimo?#
Cuando todas las herramientas expuestas son de solo lectura sobre datos públicos y el impacto de la carga está acotado por un rate limit por IP. Una herramienta de navegación del catálogo que envuelve /wp-json/wc/v3/products?stock_status=instock es aceptable. Una herramienta de consulta de pedidos no lo es.
¿Por qué precisamente OAuth 2.1?#
OAuth 2.1 consolida las pautas actuales de OAuth 2.0 más PKCE, elimina los flujos obsoletos implicit y resource-owner-password y encaja con lo que Claude Desktop y otros hosts MCP admiten de forma nativa para el acceso delegado de agentes.
¿Dónde se guardan los tokens con scopes?#
Los tokens se emiten desde un área de administración del lado de WordPress, se guardan como valor con hash más scope más fecha de caducidad y se verifican en cada petición MCP. Guardarlos en texto plano es el mismo error que guardar contraseñas en texto plano.
¿Cómo encaja el rate limiting con la autenticación?#
Los límites se asocian al principal: un token autenticado tiene su propio bucket, el tráfico anónimo comparte un bucket por IP. Las herramientas que modifican estado reciben un bucket más estrecho sea cual sea el principal, para que un token comprometido no pueda ejecutar order.intent en bucle.

¿Necesitas un FAQ adaptado a tu sector y mercado? Preparamos una versión alineada con tus objetivos de negocio.

Hablemos

Artículos Relacionados