MCP聊天
通过MCP(Model Context Protocol)可以利用外部工具的AI聊天UI。 以Anthropic Claude/OpenAI/Google Gemini为后端,具备流式响应线程管理认证的Next.js应用程序。
特性
- 支持多模型 —可切换Claude/GPT/Gemini
- MCP工具协作 —通过外部MCP服务器为AI提供工具(SSE/HTTP transport)
- Web 検索 —通过Tavily Remote MCP进行实时Web检索
- 音声入力 —通过Web Speech API进行麦克风输入(支持Chrome/Edge/Safari)
- 文件附件 —附上PDF/Excel/PPTX/文本文件,让AI分析(最多3个文件)
- 流响应 —实时显示标记
- 线程管理 —保存、浏览和切换对话
- Firebase认证 -通过邮件验证进行用户管理
技术栈
| 层 | 技术 |
|---|---|
| 框架 | Next.js 16(应用路由器) |
| AI/流媒体 | Vercel AI SDK v6(ai, @ai-sdk/react, @ai-sdk/anthropic, @ai-sdk/openai, @ai-sdk/google) |
| MCP客户端 | @ai-sdk/mcp (SSE/HTTP传输) |
客户端SDK+Admin SDK,Bearer令牌方式 |数据库|云Firestore| |形式|反应钩形+zod| |数据获取|TanStack查询| |UI |顺风CSS v4+shadcn/UI+Radix UI| |部署|Vercel|
建筑
┌─────────────┐ POST /api/chat ┌──────────────────┐
│ Browser │ ◄─── SSE Stream ──────► │ Next.js Server │
│ (useChat) │ │ │
└─────────────┘ │ ┌────────────┐ │
│ │ MCP Client │──┼──► External MCP Servers
│ └────────────┘ │ (tools)
│ ┌────────────┐ │
│ │ streamText │──┼──► Claude / GPT / Gemini API
│ └────────────┘ │
│ ┌────────────┐ │
│ │ Firestore │──┼──► Firebase (messages, threads)
│ └────────────┘ │
└──────────────────┘身份验证流程
不使用Cookie,采用无状态的认证方式,在Bearer头部发送Firebase Client SDK的ID令牌。
Firebase Auth (client)
│ onAuthStateChanged → user.getIdToken()
▼
Authorization: Bearer
│
▼
API Route → verifyToken() → Firebase Admin verifyIdToken()- 客户端:
useAuthhook监视认证状态authFetch自动为所有API请求添加标记 - 服务器:每个API路由
verifyToken()验证Bearer令牌(仅允许邮件验证用户)
项目结构
├── app/
│ ├── layout.tsx # Root layout (QueryProvider)
│ ├── page.tsx # → /chat リダイレクト
│ ├── (auth)/
│ │ ├── login/page.tsx # ログイン画面
│ │ ├── register/page.tsx # ユーザー登録画面
│ │ └── verify-email/page.tsx # メール認証確認画面
│ ├── api/
│ │ ├── chat/route.ts # Chat API: MCP + streamText + Firestore 保存
│ │ ├── parse-file/route.ts # ファイル解析 API (PDF/Excel/PPTX/テキスト)
│ │ └── threads/
│ │ ├── route.ts # スレッド一覧 GET
│ │ ├── create/route.ts # スレッド作成 POST
│ │ ├── delete/route.ts # スレッド削除 POST
│ │ ├── update-title/route.ts # タイトル更新 POST
│ │ └── [threadId]/route.ts # スレッド詳細 GET / メッセージ追記 PATCH
│ ├── actions/
│ │ └── generate-title.ts # Claude Haiku でタイトル自動生成
│ └── chat/
│ ├── layout.tsx # サイドバー + メインエリア
│ ├── page.tsx # 最新スレッドへリダイレクト
│ └── [threadId]/page.tsx # スレッド表示 (SSR → ChatInterface)
├── components/
│ ├── ChatInterface.tsx # useChat hook でストリーミング統合
│ ├── ChatMessages.tsx # メッセージ表示 + ツール実行表示
│ ├── ChatInput.tsx # 入力 UI (音声入力, ファイル添付, Enter 送信)
│ ├── ModelSelector.tsx # モデル切り替え (Claude / GPT / Gemini)
│ ├── ThreadSidebar.tsx # スレッド一覧・新規作成・削除
│ ├── providers/QueryProvider.tsx # TanStack Query Provider
│ └── ui/ # shadcn/ui コンポーネント
├── hooks/
│ ├── use-auth.ts # Firebase 認証状態管理 + authFetch
│ ├── use-speech-recognition.ts # Web Speech API 音声入力 hook
│ └── use-threads.ts # TanStack Query hooks (スレッド CRUD)
├── lib/
│ ├── firebase-admin.ts # Firebase Admin SDK 初期化 (server-only)
│ ├── firebase-client.ts # Firebase Client SDK 初期化
│ ├── verify-token.ts # Bearer トークン検証
│ ├── auth-chat-transport.ts # AI SDK transport に認証を付与
│ ├── file-parsers.ts # ファイルテキスト抽出 (PDF/Excel/PPTX)
│ ├── mcp-clients.ts # MCP Client factory (SSE/HTTP)
│ ├── threads.ts # Firestore スレッド操作
│ ├── types.ts # 型定義
│ └── utils.ts # cn() ヘルパー
├── types/
│ └── speech-recognition.d.ts # Web Speech API 型定義
├── firestore.indexes.json # Firestore 複合インデックス定義
├── firestore.rules # Firestore セキュリティルール
└── vercel.json # Chat API maxDuration: 60sFirestore 计划
threads/{threadId}
├── userId: string
├── title: string
├── createdAt: Timestamp
├── updatedAt: Timestamp
├── lastMessagePreview: string
├── model: string
│
└── messages/{messageId} (subcollection)
├── role: "user" | "assistant"
├── content: string
└── createdAt: Timestamp设置
先决条件
- Node.js 18+
- pnpm
- Firestore+Authentication启用
- 人类API密钥
- OpenAI API Key/Google Gemini API Key(任意,使用对应模型时)
1.安装依赖项
pnpm install2.配置环境变量
.env.local 创建并设置:
# Firebase Client (public, safe to expose)
NEXT_PUBLIC_FIREBASE_API_KEY=your-api-key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project.firebasestorage.app
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=000000000000
NEXT_PUBLIC_FIREBASE_APP_ID=1:000000000000:web:xxxxxxxxxxxx
# Firebase Admin (server-only, private)
FIREBASE_PROJECT_ID=your-project-id
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your-project.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
# AI Provider API Keys
ANTHROPIC_API_KEY=sk-ant-... # 必須
OPENAI_API_KEY=sk-proj-... # 任意
GOOGLE_GENERATIVE_AI_API_KEY=... # 任意
# MCP Server URLs (numbered, add as many as needed)
MCP_SERVER_URL_1=https://your-mcp-server.example.com/sse
MCP_SERVER_URL_2=http://localhost:3001/sse
# MCP_SERVER_HEADERS_1='{"Authorization":"Bearer xxx"}' # 認証が必要な場合
# MCP_SERVER_TRANSPORT_2=http # HTTP transport (default: sse)
# Tavily Web Search (Remote MCP)
MCP_SERVER_URL_3=https://mcp.tavily.com/mcp/?tavilyApiKey=your-key
MCP_SERVER_TRANSPORT_3=http3.部署Firestore索引和规则
firebase login
firebase init firestore
firebase deploy --only firestore:indexes,firestore:rules4.在本地运行
pnpm devhttp://localhost:4100的规格化距离的幂函数。第一次按照用户登录→邮件认证→聊天画面的顺序进行。
MCP服务器配置
连接MCP服务器 .env.local 添加URL:
# SSE transport (default)
MCP_SERVER_URL_1=https://your-mcp-server.vercel.app/sse
# HTTP transport (Streamable HTTP)
MCP_SERVER_URL_2=https://mcp.tavily.com/mcp/?tavilyApiKey=your-key
MCP_SERVER_TRANSPORT_2=http
# 認証が必要なサーバー
MCP_SERVER_URL_3=https://private-mcp-server.example.com/sse
MCP_SERVER_HEADERS_3='{"Authorization":"Bearer your-token"}'增加号码的话可以同时连接到多个服务器。 每个服务器的工具都会自动合并,聊天时可以使用AI。 传输波 MCP_SERVER_TRANSPORT_N 的 sse(默认)或 http 中所述修改相应参数的值。
不设定的情况下,作为没有工具的本源聊天动作。
MCP连接的结构
.env.local lib/mcp-clients.ts app/api/chat/route.ts
┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────────────┐
│ MCP_SERVER_URL_1 │───>│ getDefaultMCPServers()│───>│ createMCPTools() │
│ MCP_SERVER_URL_2 │ │ createMCPClient() │ │ streamText({ tools }) │
│ MCP_SERVER_URL_3 │ │ client.tools() │ │ → AI がツールを使用 │
└─────────────────┘ └──────────────────────┘ └─────────────────────────┘部署到Vercel
npx vercel单击功能区上的 .env.local 对较大场景进行渲染期间已观察到该故障。 FIREBASE_PRIVATE_KEY 在Vercel UI上粘贴原始密钥(不需要转义)。
