Vai al contenuto

Server MCP su Next.js nel 2026: mcp-handler 2 e OAuth 2.1

Di Cassini · AI · 22 aprile 2026 · 9 min di lettura · Aggiornato il 8 novembre 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

Questa guida ti porta da zero a un server Model Context Protocol (MCP) pronto per la produzione che gira dentro un SaaS Next.js 16 esistente. Alla fine avrai una rotta che parla il protocollo stateless 2026-07-28, e lo Streamable HTTP dei client del 2025, con Claude, ChatGPT, Cursor e VS Code, un tool tipizzato che legge dal tuo database, OAuth 2.1 sul percorso caldo, un deploy che regge sul serverless senza uno store di sessione e una registrazione funzionante da testare oggi stesso.

Diamo per scontato che il prodotto sia già in produzione. Nessuna riscrittura. Il server MCP si monta accanto alle tue rotte API esistenti e riusa l'auth, il database e la pipeline di deploy che già fai girare.

Cosa ti serve prima di iniziare

  • Next.js 16.0 o successivo, App Router.
  • Node.js 22 o successivo. Node.js 20 è arrivato a fine vita il 30 aprile 2026 e Vercel l'ha deprecato il 1° ottobre 2026.
  • Un progetto Vercel con Fluid Compute abilitato, oppure un server Node in grado di tenere aperte richieste di lunga durata.
  • Redis solo se resti su mcp-handler 1.x. La versione 2 non tiene stato di sessione, quindi non le serve uno store.
  • Un provider OAuth 2.1. Clerk, Better Auth, Auth.js e WorkOS spediscono tutti flussi consapevoli di MCP; farlo in casa va bene se ne hai già uno in produzione.

Step 1. Installa i pacchetti

Il pacchetto mcp-handler, mantenuto da Vercel, avvolge l'SDK TypeScript ufficiale del Model Context Protocol per i route handler di Next.js. La versione 2.0, uscita il 30 luglio 2026, poggia sull'SDK v2 e serve la specifica 2026-07-28. Installala insieme al pacchetto server dell'SDK e a Zod 4 per la validazione degli input dei tool.

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

L'SDK è l'implementazione canonica del protocollo; nella v2 @modelcontextprotocol/server prende il posto del vecchio @modelcontextprotocol/sdk. mcp-handler è l'adapter sottile che trasforma la definizione del server in un route handler Next.js, in un handler Nuxt o in un endpoint SvelteKit.

Step 2. Crea la rotta del trasporto

La specifica 2026-07-28 rende stateless il protocollo: l'handshake initialize e l'header Mcp-Session-Id non ci sono più, e ogni richiesta porta con sé versione del protocollo e capability del client. mcp-handler 2.x la serve in modo nativo e risponde dallo stesso handler ai client Streamable HTTP del 2025. Il vecchio trasporto HTTP+SSE è stato rimosso: /sse e /message rispondono 410 Gone, quindi un client che ha ancora bisogno di SSE ti tiene sulla 1.x finché non migra.

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, restituisce pong",
      inputSchema: z.object({})
    },
    async () => ({ content: [{ type: "text", text: "pong" }] })
  )
})

export { handler as GET, handler as POST }

Esporta GET e POST. Le chiamate ai tool passano su POST; le operazioni di sessione GET e DELETE dei client del 2025 rispondono 405, ed è normale ora che nessuno tiene una sessione. Il percorso della rotta lo scegli tu: l'handler non lo legge mai, quindi ai client dai l'URL completo della rotta.

Step 3. Esponi un tool reale con uno schema tipizzato

I tool sono i verbi che il tuo SaaS consegna all'agente. Ognuno dichiara un nome, una descrizione che il modello leggerà, uno schema Zod a oggetto per gli argomenti e una funzione async che gira lato server a ogni chiamata. Nella v2 la chiamata è registerTool; il vecchio server.tool(...) variadico non esiste più.

server.registerTool(
  "list-invoices",
  {
    description: "Restituisce le fatture più recenti del workspace corrente. Usalo quando l'utente chiede di vedere, elencare o rivedere le fatture.",
    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) }] }
  }
)

Scrivi la descrizione come la scriverebbe l'utente al prompt. Il modello sceglie i tool leggendo queste stringhe, non leggendo la tua implementazione. Brevi, specifiche, guidate dal verbo. Gli schemi dichiarativi permettono al client di validare gli argomenti prima che tocchino il tuo codice, il che dimezza i failure mode.

Step 4. Collega OAuth 2.1

La specifica MCP 2026-07-28 richiede che i server remoti implementino OAuth 2.1 con PKCE (SHA-256) e pubblichino un documento di Protected Resource Metadata (RFC 9728) su /.well-known/oauth-protected-resource. Non spedire un server MCP remoto senza auth: gli audit del 2025 hanno trovato più di mille endpoint MCP pubblici esposti con auth debole o assente.

Avvolgi l'handler in withMcpAuth e un verifier di token:

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 spedisce un adapter withMcpAuth drop-in; Better Auth, Auth.js e WorkOS documentano ciascuno un flusso MCP. Se lo fai in casa, le parti critiche sono il documento di metadata well-known e la verifica PKCE SHA-256; la specifica vieta esplicitamente il PKCE plain. Il protectedResourceHandler di mcp-handler serve quel documento al posto tuo. La specifica 2026-07-28 depreca anche la Dynamic Client Registration a favore dei Client ID Metadata Document, che deve supportare il tuo authorization server, non la rotta MCP.

Step 5. Sopravvivere al serverless: Fluid Compute, e Redis solo sulla 1.x

Un tool call può durare secondi o minuti mentre la risposta resta aperta. Una funzione con un limite corto la taglia. Sulla v2 è l'unico problema di serverless rimasto.

Abilita Fluid Compute sul progetto. Permette a una singola istanza di servire richieste concorrenti, cosa che l'MCP sfrutta parecchio perché la maggior parte dei tool call è I/O-bound. Secondo i limiti delle funzioni di Vercel, sui piani Pro ed Enterprise il default è 300 secondi e si può salire a 800 (1.800 in beta). Imposta il limite nel file della rotta:

Niente Redis sulla v2. Senza sessione nel protocollo, ogni richiesta può arrivare su qualsiasi istanza, e le opzioni 1.x redisUrl, maxDuration e basePath sono state rimosse. Se un tool deve ricordare qualcosa fra una chiamata e l'altra, restituisci un handle esplicito (un id di job, un id di bozza) e lascia che il modello lo ripassi come argomento.

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

Se resti sulla 1.x per un client SSE, Redis serve ancora: passa redisUrl a createMcpHandler e metti l'istanza Redis nella stessa regione del deploy, perché la 1.x smista il traffico SSE fra istanze con il pub/sub di Redis.

Step 6. Registra il server con un client

Per Claude Code, aggiungi il server al file .mcp.json del progetto:

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

Claude esegue l'handshake OAuth al primo uso e cachea il token. Cursor, i Custom Connector di Claude.ai e quelli di ChatGPT accettano lo stesso formato di URL dalla loro UI di impostazioni.

Verificare che funzioni

  1. Manda POST /api/mcp con un Bearer token valido e una richiesta JSON-RPC tools/list. Dovresti ricevere l'elenco dei tuoi tool.
  2. Esegui l'MCP Inspector con npx @modelcontextprotocol/inspector. Connettiti al tuo URL, autenticati, invoca ogni tool.
  3. Da Claude Code, digita /mcp e conferma che il tuo server appare in verde. Attiva un tool e ispeziona gli argomenti che il modello ha mandato.
  4. Stressalo con 20 sessioni Inspector concorrenti. La latency deve restare piatta; se sale, Fluid Compute è spento o il database sta in un'altra regione.

Fallimenti comuni e fix

  • Stream ended unexpectedly. Il tool dura più del limite della funzione, oppure Fluid Compute è disabilitato. Alza maxDuration nel file della rotta (fino a 800 secondi su Pro) e rideploy.
  • Loop di auth a ogni tool call. withMcpAuth restituisce undefined per un token valido. Di solito l'audience o l'issuer del token non combaciano con i Protected Resource Metadata che pubblichi. Loggali entrambi e allineali.
  • Descrizioni dei tool ignorate dal modello. Sono scritte come commenti di codice anziché come prompt utente. Riscrivi ciascuna come una frase breve nella forma "fai X quando l'utente chiede Y".
  • Sessioni che si resettano a metà flusso. Sulla 1.x manca REDIS_URL in uno degli ambienti di deploy; preview e production devono avere la stessa variabile. Sulla 2.x non c'è nessuna sessione da resettare: porta lo stato in un handle restituito dal tool.
  • 410 Gone su /sse. Il client usa ancora il trasporto HTTP+SSE che mcp-handler 2.x ha rimosso. Puntalo all'URL Streamable HTTP, oppure tieni quel client su un deploy 1.x mentre migra.
  • Errori di validazione Zod sul client. Lo schema del tool richiede un campo che il modello non sa di dover mandare. Aggiungi un .default() o ristruttura la descrizione in modo che il campo richiesto emerga dal linguaggio naturale.

Per andare oltre

Una volta in piedi il server, il lavoro passa dal plumbing del protocollo al design dei tool. L'insieme dei tool che esponi diventa parte della superficie di prodotto, motivo per cui la domanda sulla curatela conta più di quella sul trasporto. Leggi il nostro pezzo su cos'è l'MCP e quando un SaaS ne ha bisogno per l'argomento di prodotto, e il nostro decision tree server versus client components per capire dove la rotta MCP si colloca in un codebase Next.js 16 che già si appoggia ai server component per i dati.

Aggiornato 2026-08-09: la specifica MCP attuale è ora la 2026-07-28, che rende stateless il core del protocollo (l'handshake initialize e l'header Mcp-Session-Id sono rimossi). Restano validi OAuth 2.1 con PKCE SHA-256 e i Protected Resource Metadata.

Aggiornato 2026-11-08: il codice ora usa mcp-handler 2.0 (uscito il 30 luglio 2026), che serve il protocollo stateless 2026-07-28, abbandona il trasporto SSE e Redis e usa registerTool; Node.js 22 sostituisce Node.js 20, ormai a fine vita, e i limiti delle funzioni seguono quelli attuali di Vercel, da 300 a 800 secondi.

Domande frequenti

Preferisci che lo facciamo noi?

Progettiamo e sviluppiamo questo tipo di lavoro per i clienti. Raccontaci di cosa hai bisogno.