Secure TanStack AI MCP OAuth Flows with Vercel Connect

TanStack AI agents can call OAuth-protected MCP servers through Vercel Connect without storing provider tokens in application code. The integration wraps a TanStack MCP transport with a Connect-backed authentication provider, scopes the connection to a subject such as the signed-in user, and retrieves a fresh token before each MCP request.
The important part is where consent happens. Create the authenticated MCP client before starting the model stream. If the user has not authorized the provider, catch the consent challenge at the HTTP route and return a 303 redirect. If authorization is first attempted inside a model tool call, the user may see a tool error while the model receives an error string it cannot repair.
The Integration in One Request Flow
Vercel's 24 September 2026 launch example shows the complete shape: authenticate the application user, create an MCP client with connectMCPTransport, start chat, and handle consent around client creation.
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 userId = await requireUserId(request)
const { messages } = await request.json()
try {
const linear = await createMCPClient({
transport: connectMCPTransport(
{ type: 'http', url: 'https://mcp.linear.app/mcp' },
'oauth/linear',
{ subject: { type: 'user', id: userId } },
),
})
const stream = chat({
adapter: vercelGatewayText('anthropic/claude-opus-5'),
messages,
mcp: { clients: [linear] },
})
return toServerSentEventsResponse(stream)
} catch (error) {
const challenge = getConsentChallenge(error)
if (challenge) return Response.redirect(challenge.url, 303)
throw error
}
}
Authentication comes first
requireUserId is application code, not a Vercel helper shown by the integration. It must validate the signed-in session and return a stable internal user identifier. Do not accept userId from the JSON body, query string, or a model-generated argument.
The Connect subject selects whose authorization should be used. A caller-controlled subject becomes an account-confusion vulnerability because one user could request another user's stored connection.
MCP client creation comes before chat
createMCPClient performs the initial MCP interaction needed to discover and initialize the server. Vercel says a missing authorization grant raises a consent challenge at this point, before the model runs.
The route can turn that challenge into a browser navigation. No model tokens are spent, no tool plan begins, and the consent state remains an ordinary web authentication concern.
Chat receives an already authenticated client
Once client creation succeeds, TanStack AI receives a ready MCP client in mcp.clients. Tool discovery and calls use that client. The model does not need to know how tokens are stored or refreshed.
This boundary keeps authentication out of prompts and tool schemas. The application controls identity and consent, while the model decides when an available tool is useful.
What Vercel Connect Manages
Vercel Connect is a credential connection layer for OAuth, MCP, API keys, and managed integrations. For OAuth, it centralizes authorization and token exchange, then lets server-side code request credentials for a subject at runtime.
Tokens stay out of application storage
The Connect OAuth documentation positions the service as a way to keep access tokens out of prompts, source code, client-side bundles, and application-managed token tables. The runtime authenticates to Vercel and requests the credential associated with a configured connector and subject.
This reduces the amount of refresh-token logic and encrypted storage an application must build. It does not remove the application's responsibility to authenticate users or authorize which connector each user may access.
The connector identifies the provider configuration
The second argument to connectMCPTransport is a connector identifier such as oauth/linear. It refers to the OAuth integration configured for the Vercel project and environment.
Keep connector names in server configuration. A user should choose among product-approved integrations, not submit an arbitrary connector ID that the server forwards to Connect.
Tokens are requested for every MCP call
Vercel says the Connect-backed provider is called before every MCP request so the token is always fresh. The transport attaches the resulting authorization to the outbound MCP request.
That is useful for rotation and refresh. It also means Connect availability and latency sit on the tool-call path, so timeouts, retry policy, and observability need to distinguish credential retrieval from MCP server execution.
What TanStack AI Manages
TanStack AI's MCP client supports Streamable HTTP, legacy SSE, stdio, in-memory, and custom transports. For remote HTTP servers, it can accept an OAuth provider compatible with the official MCP SDK.
Transport and auth remain replaceable
Without Vercel Connect, an application can pass an OAuthClientProvider in the HTTP transport configuration. The underlying MCP SDK handles token attachment, refresh, and retry after a 401 when the provider supplies valid or refreshable token state.
Vercel's helper supplies that provider using Connect. The MCP client remains a TanStack AI component, which keeps the model and tool integration separate from credential storage.
Custom transport is the escape hatch
TanStack's documentation notes that interactive authorization can require retaining a StreamableHTTPClientTransport reference and calling finishAuth(code) after the OAuth callback. Because createMCPClient normally constructs the transport internally, advanced custom flows build the transport first and pass it to the client.
The Connect adapter exists to avoid rebuilding that lifecycle in the common hosted case. Use a custom transport only when the provider or product flow genuinely needs behavior the adapter does not expose.
MCP errors still need application semantics
TanStack AI can surface MCP connection and tool errors, but it cannot decide whether an error means "ask the user for consent," "retry later," or "the requested action is forbidden." The route and tool layer should classify those cases before raw text reaches the model.
Authentication errors are control flow. Treating them as model context wastes tokens and can provoke repeated tool calls.
Use a Stable Per-User Subject
The subject binds an OAuth connection to the principal acting through the agent.
const subject = {
type: 'user' as const,
id: session.user.id,
}
Derive it from a verified session
Resolve the subject after validating a secure cookie, bearer session, or trusted identity-provider assertion. Use the internal application ID rather than an email address that may change or be reassigned.
If the application supports organizations, keep organization membership checks alongside user authentication. A valid user token does not automatically authorize work in every tenant.
Keep subjects tenant-aware in your own policy
The example subject is a user. Your application may need to verify that the user can access the requested workspace, project, or account before exposing its MCP tools.
Do not encode all authorization into a clever subject string. Keep a normal policy check in application code, and use the subject to select the correct connection after access is approved.
Avoid service-wide shared connections for user actions
A single shared OAuth grant makes every tool call look like the same external identity. That weakens audit trails and can let one user exercise another user's external permissions.
Use a service connection only for an explicitly service-owned workflow. User-initiated actions should use the user's authorization and scopes whenever the provider supports it.
Handle Consent at the Route Boundary
The consent branch is part of the route's response contract. Design the frontend for it instead of treating a redirect as an unexpected fetch error.
Why Vercel uses 303
The chat request is a POST. A 303 response tells the client to retrieve the consent URL with GET, avoiding a replay of the chat body at the authorization endpoint.
Browser navigation and programmatic fetch handle redirects differently. If the chat library follows redirects inside an XHR-style request, the consent page may not replace the current page. Test the actual client behavior and return a structured authorization response when full-page navigation is required.
Preserve the user's intent safely
The user expects to continue after consent. Store a short-lived server-side continuation record containing the conversation ID, intended connector, and safe return path. Send only an opaque nonce through the redirect state.
Do not put the full prompt, model messages, token, or arbitrary return URL in OAuth state. Validate the callback destination against an allowlist and bind the state to the same signed-in user.
Make denial a normal outcome
Users can deny access. The callback should return them to the application with a clear status and leave the conversation usable without that connector.
Do not immediately send the model into the same failing tool path. Mark the connection unavailable until the user explicitly tries to connect again.
Keep OAuth Errors Out of the Agent Loop
Vercel's launch note calls out a subtle failure: if consent is raised during a tool call, the model receives it as an error string instead of the user receiving a redirect.
Preflight required connectors
If the route knows the conversation requires a specific MCP server, create that client before starting the stream. This catches missing consent early.
For optional tools, you can initialize available clients at request start and omit any connector that needs consent. The UI can show a Connect control without forcing authorization for a tool the model may never use.
Classify errors by audience
An OAuth consent challenge is for the user interface. A rate limit may be retryable by the application. A validation error may be useful to the model so it can correct tool arguments. A forbidden operation should remain a policy denial.
Create typed error categories rather than flattening every failure into text:
type ToolBoundaryError =
| { kind: 'consent_required'; connector: string; url: string }
| { kind: 'temporarily_unavailable'; retryAfterMs?: number }
| { kind: 'forbidden'; policy: string }
| { kind: 'tool_validation'; message: string }
Only the final category normally belongs in model-visible correction context.
Cap repeated authentication attempts
A revoked grant or provider outage can cause repeated 401 responses. Limit automatic retries and record the connector, subject hash, MCP host, status class, and request correlation ID.
Never log access tokens or authorization headers. Repeated failure should end in a user-facing reconnect action or an operator-visible incident, not an infinite tool loop.
Configure the OAuth Connector
Vercel documents a CLI setup for an OAuth or OIDC provider:
vercel link
vercel connect create your-service.com --name my-app
vercel env pull
For a supported integration such as Linear, use the provider-specific connector path and follow its required redirect URI and scope configuration.
Separate environments
Development, preview, and production should use separate connector configuration or an explicit environment binding. A preview deployment must not retrieve a production user's credentials by accident.
Register redirect URIs for each approved environment. Avoid wildcard callbacks when the provider offers stricter configuration.
Request minimum scopes
Start with the read scopes needed for tool discovery and retrieval. Add write scopes only for tools that change external state, and display those capabilities clearly during consent.
Scope changes often require reauthorization. Version the connector policy and tell the user why a new grant is being requested.
Bind Connect access to the Vercel project
Vercel says Connect can bind connections to projects and environments while Vercel OIDC authenticates runtime requests. Keep production access limited to the production project and deployment identity.
Review who can modify connector configuration in Vercel. A secure runtime can still be redirected to an unsafe provider if administrative access is too broad.
Harden the MCP Transport
OAuth authenticates the request, but transport security and server trust still matter.
Allowlist MCP origins
Keep the MCP URL in server-owned configuration. Do not let a browser or model provide an arbitrary URL to connectMCPTransport, because that can turn the server into an authenticated request proxy.
Validate HTTPS, exact hostname, path, and expected connector pairing. A Linear token should never be attached to a host chosen from user input.
Set timeouts and cancellation
Chat requests are long lived, while OAuth retrieval and MCP initialization should have bounded latency. Propagate request cancellation to the MCP client and close transports when the stream ends.
Separate timeout messages for Connect and the MCP server. Operators need to know whether credentials could not be fetched or the remote tool service did not respond.
Validate discovered tools
An authenticated MCP server can publish tool descriptions and schemas that influence the model. Pin or allowlist expected tool names for sensitive integrations, and require human confirmation for high-impact actions.
OAuth proves which account is acting. It does not prove every tool call is safe or intended.
Authorize Every Tool Call
The external provider enforces its token scopes, while the application should enforce product and tenant policy before a tool runs.
Separate connection from permission
A connected Linear account may allow issue deletion, but your product may expose only search and comment tools. Filter the available tool set and add server-side checks for project ownership and action type.
Do not rely on model instructions such as "never delete." A tool that must not be used should be absent or denied in code.
Require confirmation for mutations
Read operations can often run automatically under narrow scopes. Creating, editing, deleting, sending, paying, or deploying should require a confirmation that names the target and effect.
Bind approval to the exact arguments and a short expiry. If the model changes the target after approval, request a new confirmation.
Preserve external audit identity
Per-user OAuth lets external systems record the actual account behind a change. Include your conversation and tool-call correlation IDs in permitted metadata where the provider supports it.
Keep an application audit record with the user, connector, MCP server, tool, arguments hash, approval, result status, and timestamps. Exclude tokens and sensitive response bodies.
Manage Token Freshness and Revocation
Calling the Connect provider before each request helps with refresh and rotation, but applications still need predictable behavior when a grant expires or is revoked.
Do not cache raw tokens in application memory
Let the Connect-backed provider retrieve the appropriate credential. A process-level token cache can outlive revocation, mix subjects through a bad key, or create inconsistent refresh behavior across instances.
If performance requires caching, use only a library-supported mechanism with expiry and exact subject, connector, environment, and scope keys. The default should be no application-owned token cache.
Reconnect after revocation
When a provider rejects a previously valid grant, stop the tool request and re-enter the consent flow at the route boundary. Tell the user that the connection expired or was revoked.
Do not ask the model to reinterpret a 401. The model cannot refresh a user's authorization and should not be given token details.
Disconnect cleanly
Provide a product control to disconnect an integration. Revoke the provider grant when supported, remove or disable the Connect relationship, clear application connection metadata, and stop presenting those tools to later chats.
Audit the disconnect without retaining the credential. A user should be able to see which integrations remain connected.
Protect the Browser Boundary
The example runs on the server. Keep it there.
Never expose connector credentials to client code
The browser sends messages and receives chat events. It should not receive access tokens, refresh tokens, GH_TOKEN-style environment values, or internal Connect credentials.
The consent URL is the exception because the browser must navigate to it. Validate that it comes from the expected challenge extractor and avoid logging its full query string.
Defend against CSRF and login confusion
Bind the chat POST and OAuth callback to the authenticated session. Use same-site cookies where appropriate, CSRF protection for state-changing endpoints, and a one-time OAuth state value.
If the user signs out or changes accounts during consent, invalidate the continuation and ask them to start again. Never attach the returned grant to whichever session happens to be active.
Limit message body size
Authentication happens before model execution, but parsing an unbounded request can still consume memory. Enforce content type, body size, message count, and allowed message structure before initializing external connections.
This also reduces the chance that tool or system fields supplied by an untrusted client bypass the intended server configuration.
Test the Full Consent Lifecycle
Unit tests around getConsentChallenge are useful, but browser and integration tests catch redirect and session problems.
First-time user
Start with no stored grant. POST a chat request, assert that no model call begins, receive the consent response, complete authorization, return to the application, and retry the original intent through a safe continuation.
Verify that the external provider shows the correct scopes and that the resulting tool call uses the signed-in user's account.
Returning user
With a valid grant, create the MCP client and stream a response without redirect. Confirm that credentials never appear in client events, application logs, model messages, or tool arguments.
Exercise several MCP requests so token retrieval and connection reuse behave as expected.
Denial, expiry, and revocation
Test a user who denies consent, a token that expires between calls, a revoked grant, a provider 401, and a Connect outage. Each case should produce a bounded, user-readable outcome.
Assert that the model does not receive consent URLs or raw authentication errors as tool content.
Cross-user isolation
Create grants for two users and request the same MCP tool concurrently. Verify that each external call uses the matching account and that changing a body field cannot select the other subject.
This is the most important multitenant test. A working happy path says nothing about subject isolation.
Observe Without Logging Secrets
Production debugging needs enough structure to locate failures without turning logs into a token store.
Useful fields
Record request ID, user subject hash, tenant ID, connector name, MCP hostname, lifecycle stage, tool name, status category, retry count, and duration. Hash or map the user identifier according to your logging policy.
Track consent-required rate, consent completion rate, Connect latency, MCP initialization failures, 401 frequency, tool latency, and repeated-call loops.
Fields to exclude
Do not log authorization headers, access tokens, refresh tokens, consent query strings, cookies, full OAuth callback URLs, or unrestricted MCP response bodies.
Prompt and tool arguments can also contain customer data. Apply field-level redaction and retention limits rather than assuming application logs are harmless.
Trace route, client, and tool stages
Use one correlation ID from the chat POST through Connect retrieval, MCP initialization, model stream, tool call, and response. Separate spans make latency and failure ownership visible.
When a tool fails, operators should be able to tell whether the cause was application authentication, missing consent, token retrieval, transport, provider authorization, tool validation, or the external service.
A Production Route Pattern
Factor identity, connector configuration, and error classification into testable functions:
const MCP_SERVERS = {
linear: {
url: 'https://mcp.linear.app/mcp',
connector: 'oauth/linear',
},
} as const
async function createUserMCPClient(
name: keyof typeof MCP_SERVERS,
userId: string,
) {
const server = MCP_SERVERS[name]
return createMCPClient({
transport: connectMCPTransport(
{ type: 'http', url: server.url },
server.connector,
{ subject: { type: 'user', id: userId } },
),
})
}
The route authenticates once, authorizes the requested integration, creates the client, handles a consent challenge, and only then starts chat. A finally block should close any client or transport resources exposed by the library version you use.
For the broader MCP security model, see Implementing Model Context Protocol for Secure Agents. Vercel AI SDK vs TanStack AI covers framework selection, while Building Agentic-Native APIs covers API boundaries beyond MCP.
Security Checklist
Before enabling an OAuth-protected MCP tool:
- Authenticate the application user before selecting a Connect subject.
- Derive the subject from the server session, never the request body.
- Keep connector IDs and MCP URLs in server-owned configuration.
- Allowlist exact HTTPS MCP origins and expected connector pairings.
- Create required MCP clients before model execution.
- Extract consent challenges at the route boundary.
- Use a safe redirect or structured client navigation flow.
- Store only an opaque, user-bound continuation state.
- Keep tokens out of prompts, logs, tool arguments, and browser bundles.
- Request minimum provider scopes and separate environments.
- Filter tools and enforce application authorization for every call.
- Require argument-bound approval for external mutations.
- Cap retries for 401s, outages, and repeated tool calls.
- Test denial, expiry, revocation, and cross-user isolation.
- Provide a visible disconnect and revocation path.
The decisive architectural move is simple: authorization belongs before inference. Once the route hands TanStack AI an authenticated MCP client, the model can focus on tool selection. When the route cannot create that client, the user sees a normal consent flow instead of an agent trying to reason its way through an OAuth error.
Official Sources
- Vercel changelog: Connect now supports TanStack AI
- Vercel Connect OAuth documentation
- Vercel Connect
- TanStack AI MCP tools documentation
- TanStack AI documentation
- Model Context Protocol authorization specification
- OAuth 2.1 draft
- OAuth 2.0 security best current practice
- Vercel Model Context Protocol documentation
- Vercel MCP server documentation
FAQs
What does connectMCPTransport do?
The @vercel/connect/tanstack-ai helper wraps a TanStack MCP transport configuration with a Vercel Connect-backed authentication provider. Vercel says the provider is called before every MCP request so the token is fresh.
Why should MCP consent happen before the model runs?
Creating the MCP client first lets the route catch a consent challenge and redirect the user. If consent is discovered only during a tool call, the failure may reach the model as an error string instead of reaching the user as an authorization step.
How is OAuth access scoped to a user?
Pass a stable application user ID as a Vercel Connect subject with type user. The server must derive that ID from an authenticated session and must never trust a user ID supplied directly by the browser.
Should an MCP access token be sent to the model?
No. Vercel Connect retrieves credentials in server-side code and the transport attaches them to MCP requests. Keep tokens out of prompts, model-visible tool arguments, logs, and client-side bundles.
What HTTP status should an OAuth consent redirect use?
Vercel's TanStack AI example uses a 303 redirect. That converts the original POST flow into a GET to the consent URL and avoids replaying the chat request body at the authorization destination.
Can TanStack AI use OAuth without Vercel Connect?
Yes. TanStack AI accepts an OAuthClientProvider from the official MCP SDK, or a custom StreamableHTTPClientTransport for interactive authorization. Vercel Connect manages token storage and consent lifecycle for the integration described here.
How should an app handle expired or revoked MCP consent?
Recreate or revalidate the MCP client at the route boundary, extract a new consent challenge when authorization is missing, and redirect the user. Treat repeated revocation as an authentication event, not a prompt for the model to solve.
Work with us
Let's build something together
We build fast, modern websites and applications using Next.js, React, WordPress, Rust, and more. If you have a project in mind or just want to talk through an idea, we'd love to hear from you.
Related Articles
Engineering • 7 min
Implementing Model Context Protocol for Secure Agent Communication
Learn how Model Context Protocol standardizes AI agent communication, solving MxN integration crises and enabling secure, scalable enterprise architectures.
5/3/2026
TypeScript • 15 min
Vercel AI SDK vs TanStack AI: 2026 Guide
Compare Vercel AI SDK and TanStack AI across agents, streaming, tools, approvals, MCP, persistence, sandboxes, frameworks, and production trade-offs now.
9/18/2026
Engineering • 21 min
Vercel Sandbox Drives for Persistent Agent Workspaces
Design persistent Vercel Sandbox workspaces with safe single-writer mounts, read-only snapshots, regional placement, lifecycle controls, and cost limits.
9/24/2026