Skip to content
Docs

TanStack AI with Vercel Connect

Vercel Connect is available on all plans and is subject to the Vercel Connect terms

Connect a TanStack AI chat route to Linear's Model Context Protocol (MCP) server. Vercel Connect supplies OAuth tokens and handles account consent, while TanStack AI discovers and calls the server's tools using a model served through AI Gateway.

  • A Next.js App Router project for the route example below
  • A Vercel project linked to your local directory
  • A Linear connector with the UID oauth/linear, linked to the project's Development environment
  • A stable ID for each signed-in user

Follow the Vercel Connect quickstart to create and link the connector. The Connect adapter also works in other server frameworks that support TanStack AI.

  1. Link your directory and pull a development OpenID Connect (OIDC) token:

    Terminal
    vercel link
    vercel env pull

    The second command writes VERCEL_OIDC_TOKEN to .env.local. Next.js loads this file during local development. Vercel Connect and the TanStack AI Gateway adapter read the token automatically. Run vercel env pull again when the development token expires. Vercel provides and rotates the token automatically after deployment.

    AI Gateway authenticates model requests, while Vercel Connect authenticates requests to Linear. If you set AI_GATEWAY_API_KEY, the AI Gateway adapter uses that key instead of the OIDC token. See TanStack AI with AI Gateway for authentication options. When deploying, link the connector to the deployment environment.

  2. Install Vercel Connect, TanStack AI, its MCP client, and the AI Gateway adapter:

    pnpm add @vercel/connect @tanstack/ai @tanstack/ai-mcp @tanstack/ai-vercel-gateway

    These packages provide the imports used below. You don't need @ai-sdk/mcp to use @vercel/connect/tanstack-ai.

  3. Use connectMCPTransport to attach Connect authentication to the MCP transport. Pass the connected client to chat() through mcp.clients:

    app/api/chat/route.ts
    import { chat, toServerSentEventsResponse } from '@tanstack/ai';
    import { createMCPClient, type MCPClient } from '@tanstack/ai-mcp';
    import { vercelGatewayText } from '@tanstack/ai-vercel-gateway';
    import {
      connectMCPTransport,
      getConsentChallenge,
    } from '@vercel/connect/tanstack-ai';
     
    export const maxDuration = 60;
     
    export async function POST(request: Request) {
      const { messages } = await request.json();
     
      if (!Array.isArray(messages)) {
        return Response.json({ error: 'messages must be an array' }, { status: 400 });
      }
     
      // Replace this demo value with the ID from your authenticated session.
      const userId = 'user_demo_123';
      let mcpClient: MCPClient | undefined;
     
      try {
        const adapter = vercelGatewayText('anthropic/claude-opus-5');
     
        mcpClient = await createMCPClient({
          transport: connectMCPTransport(
            { type: 'http', url: 'https://mcp.linear.app/mcp' },
            'oauth/linear',
            {
              subject: { type: 'user', id: userId },
              scopes: ['read'],
            },
            {
              redirectUrl: new URL('/', request.url).toString(),
            },
          ),
        });
     
        const stream = chat({
          adapter,
          messages,
          mcp: { clients: [mcpClient] },
        });
     
        return toServerSentEventsResponse(stream);
      } catch (error) {
        await mcpClient?.close();
     
        const challenge = getConsentChallenge(error);
        if (challenge) {
          return Response.redirect(challenge.url, 303);
        }
     
        throw error;
      }
    }

    The transport requests a current token from Connect before each MCP request. You don't need to store a Linear token or copy one into an Authorization header.

    TanStack AI's managed MCP integration discovers tools when the stream is consumed and closes the client when the run ends, including on error or abort. Keep the client open until streaming finishes. The catch block closes it if route setup fails before returning the stream.

    Derive the user subject ID from your authenticated session. Do not accept an arbitrary user ID from the request body. Keep MCP clients and Connect token requests on the server.

  4. Start your app:

    Terminal
    pnpm dev

    In another terminal, send a chat message:

    Terminal
    curl -i -N \
      -X POST http://localhost:3000/api/chat \
      -H "Content-Type: application/json" \
      -d '{"messages":[{"role":"user","content":"List my assigned Linear issues."}]}'

    If user_demo_123 hasn't granted Linear access, the route returns a 303 response. Open the Location URL in a browser and approve access. Connect returns you to the route's configured redirectUrl. Send the chat request again to receive the model's response as server-sent events.

For Streamable HTTP, connectMCPTransport defaults to eager consent. When Connect reports a missing grant, the provider throws ConsentRequiredError during createMCPClient(), before the chat stream starts.

TanStack wraps connection failures in MCPConnectionError. Use getConsentChallenge(error) to find a consent challenge through the error's cause chain. A direct instanceof ConsentRequiredError check on the outer error can miss the challenge.

Handle consent at the route boundary and start a new chat turn after the user grants access. Tool discovery happens during streaming, so later discovery errors surface in the stream. A route-level catch can't turn those errors into a redirect after the response starts.

A fetch() request doesn't navigate the browser when the server returns a redirect. For a chat UI, replace the redirect in the route with a JSON response that your client handles before reading the event stream:

if (challenge) {
  return Response.json({ consentUrl: challenge.url }, { status: 409 });
}

Have your client show a Connect Linear link or navigate to consentUrl. Preserve the conversation and submit a new request after consent. Send only the consent URL to the client; keep the challenge's request, verifier, and tokens on the server.

Pass consent options as the fourth argument to connectMCPTransport:

OptionBehavior
redirectUrlReturns the user to this URL after consent. If omitted, Connect uses the connector's registered redirect.
consentDefaults to 'eager' for HTTP and 'transport' for SSE. Keep these defaults for the corresponding transport.
onConsentRequiredReceives the consent challenge. In eager mode, the provider still throws after calling this callback so the unauthenticated MCP request stops.

connectMCPTransport accepts a transport config with type, url, and optional headers and fetch properties:

TransporttypeDefault consent mode
Streamable HTTP'http''eager': raises a missing-grant challenge during connection setup
Server-sent events (SSE)'sse''transport': raises the challenge through the transport's OAuth flow

Use Streamable HTTP when the server supports it. For an SSE server, use type: 'sse' and the server's SSE endpoint. Don't force eager consent on SSE: its EventSource transport can treat a consent error as a network failure and repeatedly reconnect.

The helper prevents MCP requests from following HTTP redirects by default. If your trusted MCP endpoint requires redirects, pass { redirect: 'follow' } in the fourth argument. A custom fetch is wrapped rather than replaced.

For a hand-built transport, @vercel/connect/tanstack-ai also exports connectAuthProvider. Its consent mode defaults to 'transport'; use 'eager' only with Streamable HTTP. Prefer connectMCPTransport to apply both the transport-specific consent default and redirect handling.

Use createMCPClients from @tanstack/ai-mcp to create a pool. Configure each entry with its own connectMCPTransport call, connector UID, and MCP URL, then pass the pool in mcp.clients. TanStack prefixes tool names with the pool's configuration keys.

Create clients and pools inside each request when using per-user subjects. A shared pool retains its subject and can cause requests from different users to act through the same account. Keep the default connection: 'close' lifecycle for this pattern.

For a long-lived provider with an explicit vercelToken, supply a callback that returns the current token instead of capturing an OIDC token string. See Authentication for deployment credentials and Tokens for subjects and scopes.

Connect's OAuth consent grants access to a provider account. TanStack AI's needsApproval setting on tool definitions controls whether a user must approve a specific action.

For tools that create, update, delete, send, or spend, configure TanStack AI tool approval separately. Granting Linear access doesn't approve every tool call. The Connect adapter doesn't turn OAuth consent into a TanStack interrupt automatically.

Last updated September 18, 2026

Was this helpful?

supported.