@vercel/connect
v2.3.2
Published
SDK for obtaining scoped tokens for third-party services on behalf of apps or users. Authenticates the calling Vercel project via [`@vercel/oidc`](https://www.npmjs.com/package/@vercel/oidc) and exchanges the OIDC token for a Vercel Connect-issued credent
Keywords
Readme
@vercel/connect
SDK for obtaining scoped tokens for third-party services on behalf of apps or users. Authenticates the calling Vercel project via @vercel/oidc and exchanges the OIDC token for a Vercel Connect-issued credential.
Eight entrypoints, all ESM:
@vercel/connect— core token / authorization SDK@vercel/connect/chat— adapter helpers for the Chat SDK (chat):connectSlackAdapter,connectDiscordAdapter,connectGitHubAdapter,connectLinearAdapter,connectNotionAdapter,connectTelegramAdapter,connectSendblueAdapter,connectTeamsAdapter(no Chat SDK dependency — returns structural config)@vercel/connect/ai-sdk— Vercel AI SDK glue: re-exportsconnectAuthProviderfor MCP transports (optional peers:ai,@ai-sdk/mcp)@vercel/connect/mcp— canonical MCP-specOAuthClientProviderfor any MCP client (no SDK dependency; returns a self-containedConnectOAuthClientProviderassignable to@ai-sdk/mcpand@modelcontextprotocol/sdk)@vercel/connect/tanstack-ai— TanStack AI glue:connectMCPTransportbuilds a@tanstack/ai-mcptransport config with a ConnectauthProvider(eager consent on Streamable HTTP, spec 401 flow on SSE), plusgetConsentChallenge(no TanStack, AI SDK, or MCP SDK dependency)@vercel/connect/eve— adapter helpers for Eve connections (optional peer:eve)@vercel/connect/betterauth— Better AuthgenericOAuthprovider (optional peer:better-auth)@vercel/connect/authjs— Auth.jsOAuth2Configprovider (optional peer:@auth/core)
Install
pnpm add @vercel/connectUsage
Core SDK
import { getToken } from '@vercel/connect';
const token = await getToken(process.env.CONNECTOR_LINEAR!, {
subject: { type: 'user', id: 'user_123' },
});To start an authorization request for a user, use startAuthorization:
import { startAuthorization } from '@vercel/connect';
const { url } = await startAuthorization(
process.env.CONNECTOR_LINEAR!,
{ subject: { type: 'user', id: 'user_123' } },
{ callbackUrl: 'https://example.com/settings/integrations' }
);Chat SDK
Spread the helper into the matching create*Adapter factory. Each helper
wires outbound app-scoped tokens; trigger-capable providers also receive
inbound Connect webhook verification via Vercel OIDC.
import { createSlackAdapter } from '@chat-adapter/slack';
import { connectSlackAdapter } from '@vercel/connect/chat';
createSlackAdapter({
...connectSlackAdapter('slack/acme-slack'),
userName: 'my-bot',
});connectDiscordAdapter (botToken and applicationId),
connectGitHubAdapter (installationToken), and connectLinearAdapter
(accessToken) follow the same shape. connectSendblueAdapter supplies a
lazy Sendblue accessToken and Connect OIDC webhook verifier.
connectTeamsAdapter supplies a lazy Microsoft Teams appId, a scope-aware
token callback for Bot Framework and Microsoft Graph, and Connect OIDC webhook
verification. Use it with a Teams adapter release that supports lazy appId
and webhookVerifier:
import { createTeamsAdapter } from '@chat-adapter/teams';
import { connectTeamsAdapter } from '@vercel/connect/chat';
createTeamsAdapter({
...connectTeamsAdapter('microsoft-teams/my-bot'),
});connectNotionAdapter supplies only the outbound token; native Notion
webhooks still require NOTION_VERIFICATION_TOKEN. connectTelegramAdapter
supplies only botToken; Telegram retains native webhook verification or
polling. See the
Chat SDK integration guide
for connector setup, trigger forwarding, and per-platform examples.
Vercel AI SDK + MCP
import { createMCPClient } from '@ai-sdk/mcp';
import { streamText } from 'ai';
import {
connectAuthProvider,
ConsentRequiredError,
} from '@vercel/connect/ai-sdk';
const mcp = await createMCPClient({
transport: {
type: 'http',
url: 'https://mcp.linear.app',
authProvider: connectAuthProvider('oauth/linear', {
subject: { type: 'user', id: 'user_123' },
}),
},
});
try {
const result = await streamText({
model: 'openai/gpt-5.4',
tools: await mcp.tools(),
prompt,
});
return result.toUIMessageStreamResponse();
} catch (err) {
if (err instanceof ConsentRequiredError) return Response.redirect(err.url);
throw err;
}Tool-call approval (Human-in-the-Loop) is independent of Connect — use the AI
SDK's toolApproval option or wrapMcpTools from @ai-sdk/policy-opa.
Non-AI-SDK MCP clients (the official MCP TypeScript SDK, Mastra, etc.)
can import the same connectAuthProvider from @vercel/connect/mcp.
TanStack AI has its own subpath, @vercel/connect/tanstack-ai, described
below.
TanStack AI + MCP
@tanstack/ai-mcp's createMCPClient passes transport.authProvider
straight to the official @modelcontextprotocol/sdk transports.
connectMCPTransport builds that transport config with a Connect-backed
authProvider and picks the consent mode per transport: on Streamable HTTP
(type: 'http') a missing grant fails eagerly inside createMCPClient, at
the route boundary, instead of mid-run inside a tool call where TanStack
would feed the error back to the model as text; on SSE it keeps the MCP-spec
401 flow, because the SSE EventSource would turn an eager throw into a
reconnect loop. TanStack wraps connect failures in MCPConnectionError;
getConsentChallenge looks through the cause chain for you. The adapter
also supplies a fixed discovery state so the official SDK never fetches OAuth
metadata from URLs the MCP server names in WWW-Authenticate, and the
transport refuses HTTP redirects by default (redirect: 'error', as in
@ai-sdk/mcp).
import { chat, toServerSentEventsResponse } from '@tanstack/ai';
import { createMCPClient } from '@tanstack/ai-mcp';
import {
connectMCPTransport,
getConsentChallenge,
} from '@vercel/connect/tanstack-ai';
export async function POST(request: Request) {
try {
const linear = await createMCPClient({
transport: connectMCPTransport(
{ type: 'http', url: 'https://mcp.linear.app/mcp' },
'oauth/linear',
{ subject: { type: 'user', id: userId } },
{ redirectUrl: 'https://app.example.com/integrations' }
),
});
const stream = chat({ adapter, messages, mcp: { clients: [linear] } });
return toServerSentEventsResponse(stream);
} catch (err) {
const challenge = getConsentChallenge(err);
if (challenge) return Response.redirect(challenge.url);
throw err;
}
}createMCPClients pools take the same per-entry transport config, so one
connectMCPTransport per connector drops in. connectAuthProvider is also
re-exported for hand-built transports; only pass consent: 'eager' with
type: 'http'. Tool-call approval remains independent of Connect: use
TanStack's needsApproval on tool definitions. See the
TanStack AI integration guide
for the eager-consent rationale, the SSE caveat, pool caveats, and the
redirectUrl fix for official-SDK transports.
Eve
Use channel-specific credential helpers with Eve's native channels.
connectSendblueCredentials resolves the app-scoped bearer token and managed
sending line together, and includes Vercel OIDC webhook verification. When the
connector has multiple lines, pass fromNumber to choose one.
import { connectSendblueCredentials } from '@vercel/connect/eve';
import { sendblueChannel } from 'eve/channels/sendblue';
export default sendblueChannel({
credentials: connectSendblueCredentials('sendblue/my-agent'),
});For managed Microsoft Teams bots, connectTeamsCredentials resolves the
public bot application id and short-lived Bot Framework token without exposing
the managed app's federated credential. It also verifies Connect-forwarded
activities with Vercel OIDC.
import { connectTeamsCredentials } from '@vercel/connect/eve';
import { teamsChannel } from 'eve/channels/teams';
export default teamsChannel({
credentials: connectTeamsCredentials('microsoft-teams/my-agent'),
});import { defineMcpClientConnection } from 'eve/connections';
import { connect } from '@vercel/connect/eve';
export default defineMcpClientConnection({
url: 'https://mcp.linear.app/sse',
auth: connect({ connector: 'linear', autoProvision: true }),
});By default, connect() only uses connectors already linked to the project and
does not provision or modify connectors at runtime. Pass autoProvision: true
to opt in. When enabled, connect() first tries the token or authorization
request; if the connector is missing or not linked, it provisions or links the
connector and retries the request once.
Better Auth
import { genericOAuth } from 'better-auth/plugins';
import { connect } from '@vercel/connect/betterauth';
genericOAuth({ config: [connect({ connector: 'linear' })] });Auth.js
import { connect } from '@vercel/connect/authjs';
const providers = [connect({ connector: 'linear' })];See the source under src/ for the full API (additional helpers like revokeToken, getTokenResponse, startAuthorization, experimental_startInstallation, typed error classes, and per-adapter options).
