Vercel AI SDK
Overview
Build AI applications with Civic and the Vercel AI SDK. Your agents can access external tools via MCP — GitHub, Slack, Google Workspace, and more.
Framework: This guide is written for Next.js projects. The core MCP client setup works with any JavaScript framework, but the authentication examples below use Next.js-specific APIs.
Quick Start
The fastest way to get started is with the starter template:
Clone our Next.js + Vercel AI SDK template with Civic pre-configured. Includes authentication, streaming, and tool calling out of the box.
git clone https://github.com/civicteam/ai-chatbot.git
cd ai-chatbot
pnpm install
cp .env.example .env.local
# Edit .env.local with your CIVIC_AUTH_CLIENT_ID and AI provider keys
pnpm dev
Build From Scratch
Prerequisites
- Next.js 14+ project with App Router
- Civic account at app.civic.com with at least one MCP server connected
- Node.js 18+
Installation
pnpm install ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/react @modelcontextprotocol/sdk @civic/auth
Vercel AI SDK version: experimental_createMCPClient ships in the ai package for AI SDK v5. If you're on v6+, install @ai-sdk/mcp and import from there instead:
pnpm install @ai-sdk/mcp
// v6+
import { experimental_createMCPClient } from '@ai-sdk/mcp';
// v5
import { experimental_createMCPClient } from 'ai';
Authentication
Why user auth for tool calls? Civic needs to know which user's toolkit and permissions to use. For multi-user apps, each user gets their own access token tied to their Civic account.
Why Civic Auth? Civic needs to identify which user is accessing tools and authorize their permissions. Civic Auth provides the secure access token. (Support for additional identity providers coming soon.)
import { createCivicAuthPlugin } from "@civic/auth/nextjs"
import type { NextConfig } from "next";
const nextConfig: NextConfig = {};
const withCivicAuth = createCivicAuthPlugin({ clientId: "YOUR_CLIENT_ID" });
export default withCivicAuth(nextConfig)
File: src/app/api/auth/[...civicauth]/route.ts
import { handler } from "@civic/auth/nextjs"
export const GET = handler()
export const POST = handler()
File: src/middleware.ts
import { authMiddleware } from "@civic/auth/nextjs/middleware"
export default authMiddleware();
export const config = { matcher: ['/((?!_next|favicon.ico|.*\\.png).*)',] };
import { getTokens } from "@civic/auth/nextjs";
const { accessToken } = await getTokens();
// Use in headers:
headers: { Authorization: `Bearer ${accessToken}` }
Complete Next.js setup with frontend components, configuration options, and deployment details
Use Claude, ChatGPT, or other AI assistants to automatically set up Civic Auth
Get your Client ID at auth.civic.com
Environment Variables
# .env.local
CIVIC_AUTH_CLIENT_ID=your_client_id # from auth.civic.com
OPENAI_API_KEY=your_openai_key # or ANTHROPIC_API_KEY
Create Civic Tools Helper
// lib/ai/tools/civic.ts
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { getTokens } from "@civic/auth/nextjs";
import { experimental_createMCPClient as createMCPClient } from "ai"; // use @ai-sdk/mcp for v6+
export const getCivicTools = async () => {
const { accessToken } = (await getTokens()) ?? {};
// getTokens() exchanges the user's Civic Auth session for a hub access token
if (!accessToken) {
return {}; // Return empty tools if user isn't authenticated
}
try {
const transport = new StreamableHTTPClientTransport(
new URL('https://app.civic.com/hub/mcp'), {
requestInit: {
headers: {
Authorization: `Bearer ${accessToken}`
}
}
}
);
const mcpClient = await createMCPClient({ transport });
return mcpClient.tools();
} catch (error) {
console.warn('Failed to load Civic tools, continuing without them:', error);
return {};
}
}
App Router API Route
// app/api/chat/route.ts
import { convertToCoreMessages, streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { getCivicTools } from '@/lib/ai/tools/civic';
export async function POST(request: Request) {
const { messages } = await request.json();
const coreMessages = convertToCoreMessages(messages);
const civicTools = await getCivicTools();
const result = streamText({
model: openai('gpt-4o'),
messages: coreMessages,
tools: civicTools,
});
// toUIMessageStreamResponse() returns a Response compatible with @ai-sdk/react useChat()
return result.toUIMessageStreamResponse();
}
Using Anthropic/Claude? Replace the model:
import { anthropic } from '@ai-sdk/anthropic';
const result = streamText({
model: anthropic('claude-sonnet-4-5-20250929'),
messages: coreMessages,
tools: civicTools,
});
Next Steps
- 1Try the Starter Template
Clone ai-chatbot for a working example with UI, auth, and streaming
- 2Connect Your Services
Visit app.civic.com to connect GitHub, Slack, Notion, and other services
- 3Test Tool Calls
Run locally and ask your AI to "list my GitHub repos" or "search Slack messages"
- 4Deploy to Production
Deploy to Vercel — all environment variables are pre-configured in the starter template