Ir al contenido

Servidor MCP en Next.js en 2026: mcp-handler 2 y OAuth 2.1

Por Cassini · IA · 22 de abril de 2026 · 9 min de lectura · Actualizado el 8 de noviembre de 2026

A dark app window with a lit port on its right edge; a line from it passes a lit lock, with a stack of pills hung below, to a large lit tile; four dashed branches end in hollow rings

Esta guía te lleva desde cero a un servidor Model Context Protocol (MCP) listo para producción, corriendo dentro de un SaaS Next.js 16 existente. Al final tendrás una ruta que habla el protocolo stateless 2026-07-28, y el Streamable HTTP de los clientes de 2025, con Claude, ChatGPT, Cursor y VS Code, una herramienta tipada que lee de tu base de datos, OAuth 2.1 en la ruta caliente, un despliegue que aguanta en serverless sin store de sesión y un registro funcional que puedes probar hoy mismo.

Asumimos que el producto ya está en producción. Sin reescrituras. El servidor MCP se monta junto a tus rutas API existentes y reutiliza la auth, la base de datos y el pipeline de despliegue que ya tienes.

Lo que necesitas antes de empezar

  • Next.js 16.0 o posterior, App Router.
  • Node.js 22 o posterior. Node.js 20 llegó al final de su vida el 30 de abril de 2026 y Vercel lo deprecó el 1 de octubre de 2026.
  • Un proyecto en Vercel con Fluid Compute activado, o un servidor Node capaz de mantener abiertas peticiones de larga duración.
  • Redis solo si te quedas en mcp-handler 1.x. La versión 2 no guarda estado de sesión, así que no necesita store.
  • Un proveedor OAuth 2.1. Clerk, Better Auth, Auth.js y WorkOS entregan flujos compatibles con MCP; hacerlo en casa vale si ya tienes uno corriendo.

Paso 1. Instala los paquetes

El paquete mcp-handler, mantenido por Vercel, envuelve el SDK TypeScript oficial del Model Context Protocol para route handlers de Next.js. La versión 2.0, publicada el 30 de julio de 2026, se apoya en el SDK v2 y sirve la especificación 2026-07-28. Instálala junto al paquete server del SDK y a Zod 4 para validar los inputs de las herramientas.

npm install mcp-handler@^2 @modelcontextprotocol/server@^2 zod@^4

El SDK es la implementación canónica del protocolo; en la v2 @modelcontextprotocol/server sustituye al antiguo @modelcontextprotocol/sdk. mcp-handler es el adaptador fino que convierte la definición del servidor en un route handler Next.js, en un handler Nuxt o en un endpoint SvelteKit.

Paso 2. Crea la ruta del transporte

La especificación 2026-07-28 vuelve stateless el protocolo: desaparecen el handshake initialize y la cabecera Mcp-Session-Id, y cada petición lleva su versión del protocolo y las capacidades del cliente. mcp-handler 2.x la sirve de forma nativa y responde desde el mismo handler a los clientes Streamable HTTP de 2025. El antiguo transporte HTTP+SSE se ha eliminado: /sse y /message devuelven 410 Gone, así que un cliente que todavía necesite SSE te deja en la 1.x hasta que migre.

Crea src/app/api/mcp/route.ts:

import { createMcpHandler } from "mcp-handler"
import { z } from "zod"

const handler = createMcpHandler((server) => {
  server.registerTool(
    "ping",
    {
      description: "Health check, devuelve pong",
      inputSchema: z.object({})
    },
    async () => ({ content: [{ type: "text", text: "pong" }] })
  )
})

export { handler as GET, handler as POST }

Exporta GET y POST. Las llamadas a herramientas van por POST; las operaciones de sesión GET y DELETE de los clientes de 2025 responden 405, y es lo esperado ahora que nadie guarda una sesión. La ruta la eliges tú: el handler nunca la lee, así que a los clientes les das la URL completa de la ruta.

Paso 3. Expón una herramienta real con un esquema tipado

Las herramientas son los verbos que tu SaaS entrega al agente. Cada una declara un nombre, una descripción que el modelo leerá, un esquema Zod de objeto para los argumentos y una función async que corre en el servidor en cada llamada. En la v2 la llamada es registerTool; el antiguo server.tool(...) variádico ya no existe.

server.registerTool(
  "list-invoices",
  {
    description: "Devuelve las facturas más recientes del workspace actual. Úsala cuando el usuario pida ver, listar o revisar facturas.",
    inputSchema: z.object({
      limit: z.number().int().min(1).max(100).default(25)
    })
  },
  async ({ limit }, ctx) => {
    const workspaceId = ctx.http?.authInfo?.extra?.workspaceId
    const rows = await db
      .from("invoices")
      .select("id, number, total, issued_at")
      .eq("workspace_id", workspaceId)
      .order("issued_at", { ascending: false })
      .limit(limit)
    return { content: [{ type: "text", text: JSON.stringify(rows.data) }] }
  }
)

Escribe la descripción como la escribiría el usuario en un prompt. El modelo elige herramientas leyendo esas cadenas, no leyendo tu implementación. Cortas, específicas, guiadas por el verbo. Los esquemas declarativos dejan al cliente validar los argumentos antes de que toquen tu código, lo que reduce los failure modes a la mitad.

Paso 4. Conecta OAuth 2.1

La especificación MCP 2026-07-28 exige que los servidores remotos implementen OAuth 2.1 con PKCE (SHA-256) y publiquen un documento de Protected Resource Metadata (RFC 9728) en /.well-known/oauth-protected-resource. No lances un servidor MCP remoto sin auth; las auditorías de 2025 encontraron más de mil endpoints MCP públicos expuestos con auth débil o ausente.

Envuelve el handler en withMcpAuth y un verificador de tokens:

import type { AuthInfo } from "@modelcontextprotocol/server"
import { withMcpAuth } from "mcp-handler"
import { verifyToken } from "@/lib/auth"

const auth = async (_req: Request, token?: string): Promise<AuthInfo | undefined> => {
  if (!token) return undefined
  const session = await verifyToken(token)
  if (!session) return undefined
  return {
    token,
    clientId: session.clientId,
    scopes: session.scopes,
    extra: { workspaceId: session.workspaceId, userId: session.userId }
  }
}

const authHandler = withMcpAuth(handler, auth, {
  required: true,
  resourceMetadataPath: "/.well-known/oauth-protected-resource"
})

export { authHandler as GET, authHandler as POST }

Clerk entrega un adaptador withMcpAuth drop-in; Better Auth, Auth.js y WorkOS documentan cada uno un flujo MCP. Si lo haces en casa, las partes críticas son el documento de metadata well-known y la verificación PKCE SHA-256; la especificación prohíbe explícitamente PKCE plano. El protectedResourceHandler de mcp-handler sirve ese documento por ti. La especificación 2026-07-28 también depreca el Dynamic Client Registration en favor de los Client ID Metadata Documents, que debe soportar tu authorization server, no la ruta MCP.

Paso 5. Sobrevivir al serverless: Fluid Compute, y Redis solo en la 1.x

Una llamada a herramienta puede durar segundos o minutos mientras la respuesta sigue abierta. Una función con un límite corto la corta. En la v2 es el único problema de serverless que queda.

Activa Fluid Compute en el proyecto. Deja que una sola instancia sirva peticiones concurrentes, algo que MCP aprovecha mucho porque la mayoría de las llamadas son I/O-bound. Según los límites de funciones de Vercel, en Pro y Enterprise el valor por defecto es 300 segundos y se puede subir a 800 (1.800 en beta). Fija el límite en el archivo de la ruta:

Sin Redis en la v2. Sin sesión en el protocolo, cualquier petición puede llegar a cualquier instancia, y las opciones 1.x redisUrl, maxDuration y basePath se han eliminado. Si una herramienta necesita recordar algo entre llamadas, devuelve un identificador explícito (un id de tarea, un id de borrador) y deja que el modelo lo pase de vuelta como argumento.

// app/api/mcp/route.ts
export const maxDuration = 300 // seconds; Pro allows up to 800

Si te quedas en la 1.x por un cliente SSE, Redis sigue siendo necesario: pasa redisUrl a createMcpHandler y coloca la instancia Redis en la misma región que el despliegue, porque la 1.x reparte el tráfico SSE entre instancias con el pub/sub de Redis.

Paso 6. Registra el servidor con un cliente

Para Claude Code, añade el servidor al archivo .mcp.json del proyecto:

{
  "mcpServers": {
    "your-saas": {
      "type": "http",
      "url": "https://your-saas.com/api/mcp"
    }
  }
}

Claude hace el handshake OAuth en el primer uso y cachea el token. Cursor, los Custom Connectors de Claude.ai y los de ChatGPT aceptan el mismo formato de URL desde su UI de ajustes.

Verificar que funciona

  1. Envía POST /api/mcp con un Bearer token válido y una petición JSON-RPC tools/list. Deberías recibir la lista de tus herramientas.
  2. Ejecuta el MCP Inspector con npx @modelcontextprotocol/inspector. Conéctate a tu URL, autentícate, invoca cada herramienta.
  3. Desde Claude Code, escribe /mcp y confirma que tu servidor aparece en verde. Dispara una herramienta e inspecciona los argumentos que envió el modelo.
  4. Ponlo bajo carga con 20 sesiones concurrentes del Inspector. La latencia debe mantenerse plana; si sube, Fluid Compute está apagado o la base de datos está en otra región.

Fallos comunes y cómo arreglarlos

  • Stream ended unexpectedly. La herramienta dura más que el límite de la función, o Fluid Compute está desactivado. Sube maxDuration en el archivo de la ruta (hasta 800 segundos en Pro) y vuelve a desplegar.
  • Loops de auth en cada llamada. withMcpAuth devuelve undefined para un token válido. Suele ser que el audience o el issuer del token no coinciden con los Protected Resource Metadata que publicas. Loggéalos ambos y alinéalos.
  • Descripciones de herramientas ignoradas por el modelo. Están escritas como comentarios de código en vez de como prompts de usuario. Reescribe cada una como una frase corta en la forma "haz X cuando el usuario pida Y".
  • Sesiones que se reinician a mitad del flujo. En la 1.x falta REDIS_URL en alguno de tus entornos de despliegue; preview y production deben llevar la misma variable. En la 2.x no hay sesión que reiniciar: lleva el estado en un identificador que devuelva la herramienta.
  • 410 Gone en /sse. El cliente todavía usa el transporte HTTP+SSE que mcp-handler 2.x eliminó. Apúntalo a la URL Streamable HTTP, o deja ese cliente en un despliegue 1.x mientras migra.
  • Errores de validación Zod en el cliente. El esquema de la herramienta exige un campo que el modelo no sabe enviar. Añade un .default() o reestructura la descripción para que el campo requerido se deduzca del lenguaje natural.

Para ir más allá

Una vez el servidor esté en pie, el trabajo pasa del plumbing del protocolo al diseño de herramientas. El conjunto de herramientas que expones se vuelve parte de la superficie de tu producto, por eso la pregunta sobre la curación pesa más que la del transporte. Lee nuestro artículo sobre qué es MCP y cuándo un SaaS lo necesita para el argumento de producto, y nuestro árbol de decisión server versus client components para ver dónde encaja la ruta MCP en un codebase Next.js 16 que ya apoya sus datos en server components.

Actualizado 2026-08-09: la especificación MCP actual es ahora la 2026-07-28, que vuelve stateless el núcleo del protocolo (se eliminan el handshake initialize y la cabecera Mcp-Session-Id). Siguen vigentes OAuth 2.1 con PKCE SHA-256 y los Protected Resource Metadata.

Actualizado 2026-11-08: el código ahora usa mcp-handler 2.0 (publicado el 30 de julio de 2026), que sirve el protocolo stateless 2026-07-28, abandona el transporte SSE y Redis y usa registerTool; Node.js 22 sustituye a Node.js 20, ya fuera de soporte, y los límites de las funciones siguen los actuales de Vercel, de 300 a 800 segundos.

Preguntas frecuentes

Artículos relacionados

¿Prefieres que lo hagamos nosotros?

Diseñamos y desarrollamos este tipo de trabajo para clientes. Cuéntanos qué necesitas.