内容提要
Vercel Connect 推出 TanStack AI 集成,智能体可调用受 OAuth 保护的 MCP 服务器,无需自行存储或轮换凭证。新增子路径导出 connectMCPTransport,在每次 MCP 请求前刷新令牌。若用户未授权,createMCPClient 会抛出同意挑战,可用 getConsentChallenge 捕获并重定向至授权页,避免错误直接传给模型。
延伸解读
凭证管理负担的消除
Vercel Connect 为 TanStack AI 智能体提供 OAuth 保护 MCP 服务器的调用能力,开发者无需自行存储或轮换凭证。新增的 connectMCPTransport 子路径导出,接收 TanStack 传输配置并附加 Connect 支持的认证提供者,该提供者在每次 MCP 请求前被调用,确保令牌始终新鲜。这降低了凭证泄露风险,并简化了维护流程。
用户授权流程的处理
当用户未授权时,createMCPClient 会在模型运行前抛出同意挑战。开发者需用 getConsentChallenge 捕获该挑战,并将用户重定向至 Connect 的同意 URL。若不处理,工具调用中引发的同意错误会以错误字符串形式传递给模型,而非将用户重定向至授权页面,导致交互失败。
代码集成示例解析
示例展示了如何为每个用户创建 Linear MCP 客户端:通过 connectMCPTransport 指定 HTTP 传输和 OAuth 路径,并传入用户主体信息。随后使用 chat 函数和 vercelGatewayText 适配器,将 MCP 客户端集成到聊天流中。捕获同意挑战后,返回 303 重定向响应,引导用户完成授权。
Q&A
Vercel Connect 现在支持 TanStack AI 了吗?具体能做什么?
是的,Vercel Connect 现已支持 TanStack AI。使用 TanStack AI 构建的智能体现在可以通过 Vercel Connect 调用受 OAuth 保护的 MCP 服务器,并且你无需自行存储或轮换凭证。
如何在 TanStack AI 中接入 Vercel Connect 的 MCP 传输?
使用新增的 @vercel/connect/tanstack-ai 子路径导出的 connectMCPTransport。它接收 TanStack 传输配置并附加一个由 Connect 支持的认证提供者。示例:connectMCPTransport({ type: 'http', url: 'https://mcp.linear.app/mcp' }, 'oauth/linear', { subject: { type: 'user', id: userId } })。
connectMCPTransport 如何保证令牌始终有效?
认证提供者会在每次 MCP 请求之前被调用,因此令牌始终是新鲜的,无需手动刷新或轮换。
如果用户没有授权,createMCPClient 会怎样?该如何处理?
如果用户尚未授权,createMCPClient 会在模型运行之前失败并抛出同意挑战(consent challenge)。你可以用 getConsentChallenge 捕获它,并重定向到 Connect 的同意 URL(例如 Response.redirect(challenge.url, 303))。
为什么必须捕获同意挑战并重定向,而不是让错误直接抛出?
如果不捕获,工具调用中引发的同意错误会作为错误字符串传递给模型,而不是将用户重定向到授权页面。因此需要捕获并重定向,以确保用户完成授权流程。
能否提供一个完整的代码示例,展示如何用 TanStack AI 和 Vercel Connect 调用 Linear MCP?
可以。示例代码: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: '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 (err) { const challenge = getConsentChallenge(err); if (challenge) return Response.redirect(challenge.url, 303); throw err; } }