Wire TanStack AI agents to OAuth-protected MCP via Vercel Connect

If you already run TanStack AI agents that call MCP tools, the boring failure mode is not that the model is dumb. It is that you pasted a long-lived MCP token into env, shipped it with the deployment, and now every rotate is a redeploy plus a hope that no screenshot of $CONNECT_* ever leaked. Vercel’s changelog for Vercel Connect + TanStack AI (2026-09-24) is aimed at that boundary: install @vercel/connect/tanstack-ai, send MCP traffic through connectMCPTransport, and when the server returns a consent challenge, catch getConsentChallenge and redirect the user. Tokens are issued at runtime at an owned route. They do not live in env for you to rotate.
This post is the keep-path PSEO for that wiring. It is not the EAS/Claude connector path in Expo MCP connector for Claude. Different runtime, different consent surface, different place the secret is allowed to exist.
What Connect is doing at the agent boundary
TanStack AI already knows how to call tools. MCP already knows how to expose tools behind OAuth. The missing piece on a Vercel deployment is a first-party transport that refuses to treat “bearer in $CONNECT_TOKEN” as the integration.
connectMCPTransport is that transport. You point the agent at MCP the same way you would any other tool host, except the bytes do not leave your app with a static secret. When the MCP server needs a user (or org) to grant access, Connect surfaces a consent challenge. Your route handles it with getConsentChallenge and redirects. After consent, a runtime token is bound to that session at the route you own.
That is the product claim worth keeping: owned-route MCP auth. The agent never becomes a secret store. Env never becomes a token locker. Rotation is “consent again,” not “edit production env and pray.”
If you still think in gateway terms — one billed path for model calls, one for tools — pair this with AI Gateway delegation. Gateway is the model hop. Connect is the OAuth hop for MCP. Do not collapse them.
Install and the only import that matters
From the changelog: install @vercel/connect/tanstack-ai. That package is the TanStack-specific adapter. Do not cargo-cult a mobile EAS connector, a generic MCP SDK auth helper, or a “put the token in $CONNECT_MCP_TOKEN” snippet from an older thread.
Keep Connect configuration in env as connection config — whatever $CONNECT_* names your dashboard emits — not as the user’s MCP access token. Path-only on the app side: your route owns consent and the agent endpoint.
Honest skeleton from the changelog: TanStack AI (chat, createMCPClient) with MCP attached through connectMCPTransport — not a raw fetch with Authorization from .env. The same HTTP handler catches getConsentChallenge and redirects; it does not retry the model with a guessed token. Storing the resulting token in env to “make CI simpler” undoes the feature.
11 production screens. Login, database, payments — all wired.
The SaaS Dashboard Kit ships everything already connected. Nothing to set up. Live demo at saas.otf-kit.dev.
connectMCPTransport: keep the agent ignorant of OAuth
The agent’s job is tool names, arguments, and when to stop. OAuth is not a tool. If you shove authorize URLs into system prompts, you will get a model that pastes tokens into logs.
connectMCPTransport sits under the tool layer. The provider is called before every MCP request, so the token is always fresh. Failed auth is not a chat message; it is a control-flow exception your route understands. That is why getConsentChallenge exists as a catchable object rather than a string the model might echo.
Trace transport errors and redirects, not bearer prefixes. Treat consent timeouts as user-wait. Multiple MCP servers can share the Connect consent pattern — not one shared env token.
getConsentChallenge and the redirect you actually ship
Consent is a browser problem. When getConsentChallenge fires on the agent route the UI posts to, that route should:
- Stop the current agent turn. Do not half-apply tool results.
- Redirect the user to the consent URL Connect gave you (changelog uses a 303).
- Land back on a path you own after the user accepts or denies.
- Resume the agent only after a runtime token exists for that session.
“Owned route” is load-bearing. If consent returns to a third-party page you do not control, someone else’s cookie becomes your security model. Keep the callback on your deployment — path-only, like /connect/consent next to the agent route.
Denied consent is first-class. Show that MCP tools are unavailable. Do not fall back to a pasted env token “just this once.”
Catch the challenge before the model runs. The changelog is explicit: if a consent error is raised inside a tool call instead, it reaches the model as an error string rather than the user as a redirect.
Runtime tokens vs env tokens

| Pasted MCP token in env | Connect consent at the route | |
|---|---|---|
| Where the secret lives | Deployment env, often copied into previews | Issued at runtime after getConsentChallenge redirect |
| Rotate | Change env, redeploy, invalidate every replica | Re-consent; old runtime token dies with the session |
| Blast radius | Anyone with env or a leaked preview | Bound to the user/session that consented |
| Agent code | Tempted to log headers | Transport handles OAuth; agent sees tools |
| Fits TanStack on Vercel | Works until the first leak | What @vercel/connect/tanstack-ai is for |
Env is fine for which Connect app you are, $CONNECT_* identifiers, and public MCP URLs. Env is not fine for the OAuth access token that talks to a customer’s MCP. If your runbook still says “rotate MCP_TOKEN quarterly,” you adopted a rename, not Connect.
Sequence you can keep in your head

- UI hits your owned agent route.
- TanStack AI starts a turn and needs an MCP tool.
connectMCPTransporttalks to the MCP server without a long-lived env bearer.- Server demands OAuth. Transport yields a consent challenge.
- Route catches
getConsentChallenge, redirects the browser. - User consents. Connect issues a runtime token at your callback path.
- Agent retries the tool call. MCP sees a valid OAuth token. Env never held it.
If step 5 is “stuff the token into $CONNECT_MCP_TOKEN so we skip redirects in staging,” staging will ship to production.
Hard split from the Claude MCP connector
We already covered Expo MCP connector for Claude: EAS builds, Claude, a connector aimed at that editor/runtime pair. This Vercel Connect + TanStack path is a different product surface — different package (@vercel/connect/tanstack-ai), host (Vercel route + TanStack), auth UX (getConsentChallenge on your web route), and secret home (runtime token at the Vercel boundary). Copy-pasting those snippets into a TanStack Vercel app gives you two half-wired OAuth stacks.
Failure modes that are on you
Redirect loops mean the callback did not persist enough session state — fix the session, not the transport. Ignoring getConsentChallenge so the model “tries another tool” leaks partial work; abort the turn. Log that a challenge happened, not URL query params that may carry one-time codes. Use connectMCPTransport in local and preview too, or those environments invent env tokens again. Keep AI Gateway credentials (model inference) separate from MCP OAuth (tools) — see AI Gateway model picks.
A minimal implementation checklist
Grounded in the changelog — not invented API:
import { chat, toServerSentEventsResponse } from '@tanstack/ai';
import { createMCPClient } from '@tanstack/ai-mcp';
import { vercelGatewayText } from '@tanstack/ai-vercel-gateway';
import {
connectMCPTransport,
getConsentChallenge,
} from '@vercel/connect/tanstack-ai';
export async function POST(request: Request) {
const { messages } = await request.json();
const userId = await getUserId(request);
try {
const linear = await createMCPClient({
transport: connectMCPTransport(
{ type: 'http', url: process.env.CONNECT_MCP_URL! },
'oauth/linear',
{ subject: { type: 'user', id: userId } },
),
});
const stream = chat({
adapter: vercelGatewayText('anthropic/claude-opus-5'),
messages,
mcp: { clients: [linear] },
});
return toServerSentEventsResponse(stream);
} catch (err) {
const challenge = getConsentChallenge(err);
if (challenge) return Response.redirect(challenge.url, 303);
throw err;
}
}Checklist: @vercel/connect/tanstack-ai adapter; connectMCPTransport; catch getConsentChallenge on the agent route; owned-path consent callback; no MCP access token in env; deny disables tools (no static fallback); logs omit tokens. If the changelog is silent on a step, do not invent it — read the entry.
Owned-route auth is the pattern we want when an agent can spend money or read a private repo: the browser user is in the loop, the serverless function is the boundary, and the model is a guest. Install @vercel/connect/tanstack-ai, send tools through connectMCPTransport, catch getConsentChallenge and redirect, let tokens issue at runtime. Leave env for $CONNECT_* config. Keep the Claude connector in its own post. For a durable product surface under the agent churn, start from the OTF templates you own — Connect still owns the OAuth hop.
Sources
- Vercel Connect + TanStack AI changelog
- AI Gateway delegation (OtF)
- Expo MCP connector for Claude (OtF)
Ship the product, not the setup.
- 11 production screens — auth, billing, team, analytics, settings
- Real database, payments, and login — all wired on day 1
- AI configs pre-tuned so your agent extends instead of regenerates