草案 智能体设计模式 - 模型上下文协议 (MCP)
学习如何实现模型上下文协议 (MCP),在 Vercel 无服务器平台上的 Next.js 15 中创建标准化、可互操作的 AI 智能体,能够动态发现并与外部工具、服务和数据源交互。
思维模型:AI 智能体的通用适配器
将 MCP 想象成 AI 智能体的 USB-C 标准。正如 USB-C 提供了一个适用于任何兼容设备(手机、笔记本电脑、显示器)的通用连接器,MCP 为 LLM 创建了一个标准化协议,可以插入任何外部系统。在 TypeScript/Next.js 世界中,它就像一个通用中间件,自动为任何外部服务生成 TypeScript 接口,允许您的 AI 智能体在运行时发现和使用功能,而无需硬编码集成。@vercel/mcp-handler
包充当您的适配器,将 MCP 服务器转换为与 Vercel 基础设施无缝配合的无服务器友好端点。
基础示例:使用 Vercel 创建 MCP 服务器
让我们构建一个简单的 MCP 服务器,它公开任务管理工具,演示基本的客户端-服务器架构。
1. 安装 MCP 依赖项
npm install @vercel/mcp-handler @modelcontextprotocol/sdk zod es-toolkit
安装 Vercel 的 MCP 处理器以实现无服务器兼容性、MCP SDK 和验证工具。
2. 定义 MCP 服务器工具
// lib/mcp/task-tools.ts
import { z } from 'zod';
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { groupBy, sortBy } from 'es-toolkit';
// 定义模式以确保类型安全
const TaskSchema = z.object({
id: z.string(),
title: z.string(),
status: z.enum(['pending', 'in-progress', 'completed']),
priority: z.number().min(1).max(5),
createdAt: z.date(),
assignee: z.string().optional(),
});
type Task = z.infer<typeof TaskSchema>;
// 内存任务存储(生产环境中应替换为数据库)
const tasks = new Map<string, Task>();
// 定义具有清晰接口的 MCP 工具
export const taskTools: Tool[] = [
{
name: 'create_task',
description: '创建一个具有指定详细信息的新任务',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: '任务标题' },
priority: {
type: 'number',
minimum: 1,
maximum: 5,
description: '优先级 (1-5)'
},
assignee: {
type: 'string',
description: '可选的负责人姓名'
},
},
required: ['title', 'priority'],
},
},
{
name: 'list_tasks',
description: '列出所有任务并支持可选过滤',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
enum: ['pending', 'in-progress', 'completed'],
description: '按状态过滤'
},
assignee: {
type: 'string',
description: '按负责人过滤'
},
},
},
},
{
name: 'update_task_status',
description: '更新现有任务的状态',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string', description: '任务 ID' },
status: {
type: 'string',
enum: ['pending', 'in-progress', 'completed'],
description: '新状态'
},
},
required: ['id', 'status'],
},
},
];
// 工具实现
export async function executeTool(name: string, args: any) {
switch (name) {
case 'create_task': {
const task: Task = {
id: `task_${Date.now()}`,
title: args.title,
status: 'pending',
priority: args.priority,
createdAt: new Date(),
assignee: args.assignee,
};
tasks.set(task.id, task);
return { success: true, task };
}
case 'list_tasks': {
let taskList = Array.from(tasks.values());
// 使用 es-toolkit 应用过滤器
if (args.status) {
taskList = taskList.filter(t => t.status === args.status);
}
if (args.assignee) {
taskList = taskList.filter(t => t.assignee === args.assignee);
}
// 按优先级排序
taskList = sortBy(taskList, [(t) => -t.priority]);
return { tasks: taskList, count: taskList.length };
}
case 'update_task_status': {
const task = tasks.get(args.id);
if (!task) {
throw new Error(`任务 ${args.id} 未找到`);
}
task.status = args.status;
tasks.set(args.id, task);
return { success: true, task };
}
default:
throw new Error(`未知工具: ${name}`);
}
}
使用 JSON Schema 验证定义 MCP 工具,使用 es-toolkit 工具实现任务管理操作。
3. 创建 MCP 服务器处理器
// lib/mcp/server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { taskTools, executeTool } from './task-tools';
export async function createMCPServer() {
const server = new Server(
{
name: 'task-manager',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
// 注册工具发现
server.setRequestHandler('tools/list', async () => {
return { tools: taskTools };
});
// 注册工具执行
server.setRequestHandler('tools/call', async (request) => {
const { name, arguments: args } = CallToolRequestSchema.parse(request);
try {
const result = await executeTool(name, args);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `错误: ${error instanceof Error ? error.message : '未知错误'}`,
},
],
isError: true,
};
}
});
return server;
}
// 导出以供无服务器使用
export async function startMCPServer() {
const server = await createMCPServer();
const transport = new StdioServerTransport();
await server.connect(transport);
return server;
}
创建一个处理工具发现和执行的 MCP 服务器,并为无服务器环境提供适当的错误处理。
4. Vercel 无服务器 MCP 处理器
// app/api/mcp/route.ts
import { createMCPHandler } from '@vercel/mcp-handler';
import { createMCPServer } from '@/lib/mcp/server';
export const runtime = 'nodejs';
export const maxDuration = 60; // 允许复杂操作的更长执行时间
const handler = createMCPHandler({
createServer: createMCPServer,
// 为 Vercel 无服务器环境配置
options: {
keepAlive: false, // 为无服务器禁用
timeout: 55000, // 略低于 maxDuration
},
});
export const POST = handler;
创建一个 Vercel 兼容的 API 路由,通过无服务器端点公开 MCP 服务器。
5. 与 Langchain 的 MCP 客户端集成
// lib/agents/mcp-client-agent.ts
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { DynamicStructuredTool } from '@langchain/core/tools';
import { z } from 'zod';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js';
export class MCPClientAgent {
private model: ChatGoogleGenerativeAI;
private mcpClient: Client;
private tools: DynamicStructuredTool[] = [];
constructor() {
this.model = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-flash',
temperature: 0,
});
this.mcpClient = new Client(
{
name: 'langchain-agent',
version: '1.0.0',
},
{
capabilities: {},
}
);
}
async connect(serverUrl: string) {
// 连接到 MCP 服务器
const transport = new WebSocketClientTransport(
new URL(serverUrl)
);
await this.mcpClient.connect(transport);
// 发现可用工具
await this.discoverTools();
}
private async discoverTools() {
const response = await this.mcpClient.request(
{ method: 'tools/list' },
{}
);
// 将 MCP 工具转换为 Langchain 工具
this.tools = response.tools.map(tool =>
new DynamicStructuredTool({
name: tool.name,
description: tool.description,
schema: this.convertToZodSchema(tool.inputSchema),
func: async (input) => {
const result = await this.mcpClient.request(
{ method: 'tools/call' },
{ name: tool.name, arguments: input }
);
return result.content[0].text;
},
})
);
}
private convertToZodSchema(jsonSchema: any): z.ZodSchema {
// 简化转换 - 生产环境中需扩展
const shape: Record<string, z.ZodSchema> = {};
for (const [key, prop] of Object.entries(jsonSchema.properties || {})) {
const propSchema = prop as any;
if (propSchema.type === 'string') {
shape[key] = propSchema.enum
? z.enum(propSchema.enum)
: z.string();
} else if (propSchema.type === 'number') {
shape[key] = z.number();
}
// 添加可选处理
if (!jsonSchema.required?.includes(key)) {
shape[key] = shape[key].optional();
}
}
return z.object(shape);
}
async execute(input: string) {
// 使用发现的工具与模型一起使用
const response = await this.model.invoke(
input,
{ tools: this.tools }
);
return response;
}
}
创建一个从服务器发现工具并将其转换为 Langchain 兼容工具的 MCP 客户端。
高级示例:包含资源和提示的生产级 MCP 系统
让我们构建一个全面的 MCP 实现,包括资源(数据访问)、工具(操作)和提示(模板)。
1. 带资源的扩展 MCP 服务器
// lib/mcp/advanced-server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { Resource, Tool, Prompt } from '@modelcontextprotocol/sdk/types.js';
import { groupBy, chunk, debounce } from 'es-toolkit';
import { z } from 'zod';
// 定义综合数据模式
const DocumentSchema = z.object({
id: z.string(),
title: z.string(),
content: z.string(),
metadata: z.object({
author: z.string(),
createdAt: z.date(),
tags: z.array(z.string()),
version: z.number(),
}),
});
const AnalyticsEventSchema = z.object({
eventType: z.string(),
userId: z.string(),
timestamp: z.date(),
properties: z.record(z.unknown()),
});
type Document = z.infer<typeof DocumentSchema>;
type AnalyticsEvent = z.infer<typeof AnalyticsEventSchema>;
// 资源定义
export const resources: Resource[] = [
{
uri: 'documents://list',
name: '文档库',
description: '访问系统中的所有文档',
mimeType: 'application/json',
},
{
uri: 'analytics://events',
name: '分析事件',
description: '分析事件流',
mimeType: 'application/x-ndjson',
},
{
uri: 'config://settings',
name: '系统配置',
description: '当前系统设置和配置',
mimeType: 'application/json',
},
];
// 增强工具定义
export const advancedTools: Tool[] = [
{
name: 'analyze_documents',
description: '对文档执行语义分析',
inputSchema: {
type: 'object',
properties: {
documentIds: {
type: 'array',
items: { type: 'string' },
description: '要分析的文档 ID',
},
analysisType: {
type: 'string',
enum: ['sentiment', 'summary', 'keywords', 'entities'],
description: '要执行的分析类型',
},
options: {
type: 'object',
properties: {
maxLength: { type: 'number' },
threshold: { type: 'number' },
},
},
},
required: ['documentIds', 'analysisType'],
},
},
{
name: 'process_batch',
description: '并行批量处理多个项目',
inputSchema: {
type: 'object',
properties: {
items: {
type: 'array',
description: '要处理的项目',
},
batchSize: {
type: 'number',
default: 10,
description: '每批次的大小',
},
operation: {
type: 'string',
enum: ['transform', 'validate', 'enrich'],
},
},
required: ['items', 'operation'],
},
},
];
// 常用操作的提示模板
export const prompts: Prompt[] = [
{
name: 'document_qa',
description: '基于文档上下文的问答',
arguments: [
{
name: 'question',
description: '要回答的问题',
required: true,
},
{
name: 'context',
description: '用于回答的文档上下文',
required: true,
},
],
},
{
name: 'data_synthesis',
description: '从多个数据源综合见解',
arguments: [
{
name: 'sources',
description: '要综合的数据源',
required: true,
},
{
name: 'format',
description: '输出格式(报告、摘要、要点)',
required: false,
},
],
},
];
// 创建高级 MCP 服务器
export class AdvancedMCPServer {
private server: Server;
private documents = new Map<string, Document>();
private events: AnalyticsEvent[] = [];
// 防抖事件处理器
private processEvents = debounce(() => {
const grouped = groupBy(this.events, e => e.eventType);
console.log('处理事件批次:', Object.keys(grouped));
// 处理分组事件
}, 1000);
constructor() {
this.server = new Server(
{
name: 'advanced-mcp-server',
version: '2.0.0',
},
{
capabilities: {
tools: {},
resources: { subscribe: true },
prompts: {},
},
}
);
this.setupHandlers();
}
private setupHandlers() {
// 资源处理器
this.server.setRequestHandler('resources/list', async () => {
return { resources };
});
this.server.setRequestHandler('resources/read', async (request) => {
const { uri } = request;
switch (uri) {
case 'documents://list':
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(
Array.from(this.documents.values()),
null,
2
),
},
],
};
case 'analytics://events':
// 使用块流式传输事件
const eventChunks = chunk(this.events, 100);
return {
contents: eventChunks.map((chunk, idx) => ({
uri: `${uri}#chunk-${idx}`,
mimeType: 'application/x-ndjson',
text: chunk.map(e => JSON.stringify(e)).join('\n'),
})),
};
case 'config://settings':
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify({
maxBatchSize: 100,
timeout: 30000,
features: {
streaming: true,
batching: true,
caching: true,
},
}),
},
],
};
default:
throw new Error(`未知资源: ${uri}`);
}
});
// 带高级处理的工具处理器
this.server.setRequestHandler('tools/list', async () => {
return { tools: advancedTools };
});
this.server.setRequestHandler('tools/call', async (request) => {
const { name, arguments: args } = request;
switch (name) {
case 'analyze_documents':
return await this.analyzeDocuments(args);
case 'process_batch':
return await this.processBatch(args);
default:
throw new Error(`未知工具: ${name}`);
}
});
// 提示处理器
this.server.setRequestHandler('prompts/list', async () => {
return { prompts };
});
this.server.setRequestHandler('prompts/get', async (request) => {
const { name, arguments: args } = request;
const prompt = prompts.find(p => p.name === name);
if (!prompt) {
throw new Error(`未知提示: ${name}`);
}
// 基于模板和参数生成提示
return {
messages: [
{
role: 'system',
content: this.generatePromptContent(name, args),
},
],
};
});
}
private async analyzeDocuments(args: any) {
const { documentIds, analysisType, options = {} } = args;
// 获取文档
const docs = documentIds
.map((id: string) => this.documents.get(id))
.filter(Boolean);
// 使用 es-toolkit 工具执行分析
const results = await Promise.all(
docs.map(async (doc) => {
switch (analysisType) {
case 'summary':
return {
docId: doc!.id,
summary: doc!.content.substring(0, options.maxLength || 200),
};
case 'keywords':
// 提取关键词(简化版)
const words = doc!.content.toLowerCase().split(/\s+/);
const wordGroups = groupBy(words, w => w);
const keywords = Object.entries(wordGroups)
.sort((a, b) => b[1].length - a[1].length)
.slice(0, 10)
.map(([word]) => word);
return {
docId: doc!.id,
keywords,
};
default:
return { docId: doc!.id, result: '分析完成' };
}
})
);
return {
content: [
{
type: 'text',
text: JSON.stringify({ analysisType, results }, null, 2),
},
],
};
}
private async processBatch(args: any) {
const { items, batchSize = 10, operation } = args;
// 批量处理项目
const batches = chunk(items, batchSize);
const results = [];
for (const batch of batches) {
const batchResults = await Promise.all(
batch.map(async (item: any) => {
switch (operation) {
case 'transform':
return { ...item, transformed: true };
case 'validate':
return { item, valid: true };
case 'enrich':
return { ...item, enriched: { timestamp: new Date() } };
default:
return item;
}
})
);
results.push(...batchResults);
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
operation,
processedCount: results.length,
batchCount: batches.length,
results,
}, null, 2),
},
],
};
}
private generatePromptContent(name: string, args: any): string {
switch (name) {
case 'document_qa':
return `您正在分析文档以回答问题。
上下文: ${args.context}
请基于提供的上下文回答以下问题:
${args.question}
提供一个清晰、简洁且有上下文支持的答案。`;
case 'data_synthesis':
const format = args.format || '摘要';
return `从以下数据源综合见解:
${JSON.stringify(args.sources, null, 2)}
输出格式: ${format}
分析所有源之间的模式、相关性和关键见解。`;
default:
return '基于提供的参数处理请求。';
}
}
async connect(transport: any) {
await this.server.connect(transport);
}
}
实现一个全面的 MCP 服务器,具有用于数据访问的资源、高级工具和提示模板。
2. 带身份验证的 Vercel 处理器
// app/api/mcp/advanced/route.ts
import { NextRequest } from 'next/server';
import { createMCPHandler } from '@vercel/mcp-handler';
import { AdvancedMCPServer } from '@/lib/mcp/advanced-server';
import { verifyToken } from '@/lib/auth';
export const runtime = 'nodejs';
export const maxDuration = 300;
const handler = createMCPHandler({
createServer: async () => new AdvancedMCPServer(),
// 添加身份验证中间件
middleware: async (req: NextRequest) => {
const token = req.headers.get('authorization')?.replace('Bearer ', '');
if (!token) {
throw new Error('未授权: 未提供令牌');
}
const valid = await verifyToken(token);
if (!valid) {
throw new Error('未授权: 令牌无效');
}
return req;
},
options: {
keepAlive: false,
timeout: 295000, // 略低于 maxDuration
maxPayloadSize: 10 * 1024 * 1024, // 10MB
},
});
export const POST = handler;
为 MCP 服务器端点添加身份验证和安全性。
3. 带 MCP 集成的 React 前端
// components/MCPInterface.tsx
'use client';
import { useState, useEffect } from 'react';
import { useQuery, useMutation } from '@tanstack/react-query';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
interface Tool {
name: string;
description: string;
inputSchema: any;
}
interface Resource {
uri: string;
name: string;
description: string;
}
export default function MCPInterface() {
const [selectedTool, setSelectedTool] = useState<Tool | null>(null);
const [toolInput, setToolInput] = useState('{}');
const [mcpClient, setMcpClient] = useState<Client | null>(null);
// 初始化 MCP 客户端
useEffect(() => {
const initClient = async () => {
const client = new Client(
{
name: 'web-client',
version: '1.0.0',
},
{
capabilities: {},
}
);
// 通过 Vercel 端点连接到服务器
await client.connectToServer('/api/mcp/advanced');
setMcpClient(client);
};
initClient();
}, []);
// 发现可用工具
const { data: tools, isLoading: toolsLoading } = useQuery({
queryKey: ['mcp-tools'],
queryFn: async () => {
if (!mcpClient) return [];
const response = await mcpClient.request(
{ method: 'tools/list' },
{}
);
return response.tools as Tool[];
},
enabled: !!mcpClient,
});
// 发现可用资源
const { data: resources } = useQuery({
queryKey: ['mcp-resources'],
queryFn: async () => {
if (!mcpClient) return [];
const response = await mcpClient.request(
{ method: 'resources/list' },
{}
);
return response.resources as Resource[];
},
enabled: !!mcpClient,
});
// 执行工具变更
const executeTool = useMutation({
mutationFn: async ({ tool, input }: { tool: Tool; input: any }) => {
if (!mcpClient) throw new Error('MCP 客户端未初始化');
const response = await mcpClient.request(
{ method: 'tools/call' },
{
name: tool.name,
arguments: input,
}
);
return response;
},
});
// 读取资源变更
const readResource = useMutation({
mutationFn: async (uri: string) => {
if (!mcpClient) throw new Error('MCP 客户端未初始化');
const response = await mcpClient.request(
{ method: 'resources/read' },
{ uri }
);
return response;
},
});
const handleToolExecution = () => {
if (!selectedTool) return;
try {
const input = JSON.parse(toolInput);
executeTool.mutate({ tool: selectedTool, input });
} catch (error) {
console.error('无效的 JSON 输入:', error);
}
};
return (
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h2 className="card-title">MCP 控制面板</h2>
{/* 工具部分 */}
<div className="divider">可用工具</div>
{toolsLoading ? (
<div className="loading loading-spinner"></div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{tools?.map((tool) => (
<div
key={tool.name}
className={`card bg-base-200 cursor-pointer hover:bg-base-300 ${
selectedTool?.name === tool.name ? 'ring-2 ring-primary' : ''
}`}
onClick={() => setSelectedTool(tool)}
>
<div className="card-body compact">
<h3 className="font-bold">{tool.name}</h3>
<p className="text-sm opacity-70">{tool.description}</p>
</div>
</div>
))}
</div>
)}
{/* 工具执行 */}
{selectedTool && (
<div className="mt-6">
<h3 className="text-lg font-bold mb-2">
执行: {selectedTool.name}
</h3>
<div className="form-control">
<label className="label">
<span className="label-text">输入 (JSON)</span>
</label>
<textarea
className="textarea textarea-bordered h-32 font-mono text-sm"
value={toolInput}
onChange={(e) => setToolInput(e.target.value)}
placeholder={JSON.stringify(
selectedTool.inputSchema.properties,
null,
2
)}
/>
</div>
<button
className="btn btn-primary mt-4"
onClick={handleToolExecution}
disabled={executeTool.isPending}
>
{executeTool.isPending ? (
<>
<span className="loading loading-spinner"></span>
执行中...
</>
) : (
'执行工具'
)}
</button>
{executeTool.data && (
<div className="alert alert-success mt-4">
<pre className="text-xs overflow-auto">
{JSON.stringify(executeTool.data, null, 2)}
</pre>
</div>
)}
{executeTool.isError && (
<div className="alert alert-error mt-4">
<span>执行失败。请检查您的输入。</span>
</div>
)}
</div>
)}
{/* 资源部分 */}
<div className="divider">可用资源</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{resources?.map((resource) => (
<div
key={resource.uri}
className="card bg-base-200 cursor-pointer hover:bg-base-300"
onClick={() => readResource.mutate(resource.uri)}
>
<div className="card-body compact">
<h3 className="font-bold">{resource.name}</h3>
<p className="text-sm opacity-70">{resource.description}</p>
<p className="text-xs font-mono">{resource.uri}</p>
</div>
</div>
))}
</div>
{readResource.data && (
<div className="modal modal-open">
<div className="modal-box max-w-3xl">
<h3 className="font-bold text-lg">资源内容</h3>
<pre className="text-xs overflow-auto mt-4">
{JSON.stringify(readResource.data, null, 2)}
</pre>
<div className="modal-action">
<button
className="btn"
onClick={() => readResource.reset()}
>
关闭
</button>
</div>
</div>
</div>
)}
</div>
</div>
);
}
创建一个全面的 React 界面,用于与 MCP 服务器交互、发现功能和执行工具。
4. 带动态 MCP 发现的 Langchain 智能体
// lib/agents/dynamic-mcp-agent.ts
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { createReactAgent } from '@langchain/langgraph/prebuilt';
import { DynamicStructuredTool } from '@langchain/core/tools';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { z } from 'zod';
import { memoize, throttle } from 'es-toolkit';
export class DynamicMCPAgent {
private model: ChatGoogleGenerativeAI;
private mcpClients: Map<string, Client> = new Map();
private agent: any;
// 记忆化工具发现
private discoverTools = memoize(
async (serverUrl: string) => {
const client = await this.connectToServer(serverUrl);
const response = await client.request(
{ method: 'tools/list' },
{}
);
return response.tools;
},
{
getCacheKey: (serverUrl) => serverUrl,
ttl: 300000, // 缓存 5 分钟
}
);
// 节流资源读取
private readResource = throttle(
async (client: Client, uri: string) => {
const response = await client.request(
{ method: 'resources/read' },
{ uri }
);
return response;
},
1000 // 每个资源最多每秒一次
);
constructor() {
this.model = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-pro',
temperature: 0,
streaming: true,
});
}
async connectToServer(serverUrl: string): Promise<Client> {
if (this.mcpClients.has(serverUrl)) {
return this.mcpClients.get(serverUrl)!;
}
const client = new Client(
{
name: 'dynamic-agent',
version: '1.0.0',
},
{
capabilities: {},
}
);
// 根据协议连接
if (serverUrl.startsWith('ws://') || serverUrl.startsWith('wss://')) {
// WebSocket 连接
const { WebSocketClientTransport } = await import(
'@modelcontextprotocol/sdk/client/websocket.js'
);
const transport = new WebSocketClientTransport(new URL(serverUrl));
await client.connect(transport);
} else {
// 通过 Vercel 的 HTTP 连接
await client.connectToServer(serverUrl);
}
this.mcpClients.set(serverUrl, client);
return client;
}
async addMCPServers(serverUrls: string[]) {
const allTools: DynamicStructuredTool[] = [];
for (const serverUrl of serverUrls) {
try {
// 从每个服务器发现工具
const tools = await this.discoverTools(serverUrl);
const client = this.mcpClients.get(serverUrl)!;
// 转换为 Langchain 工具
for (const tool of tools) {
const langchainTool = new DynamicStructuredTool({
name: `${serverUrl}_${tool.name}`.replace(/[^a-zA-Z0-9_]/g, '_'),
description: `[${serverUrl}] ${tool.description}`,
schema: this.convertMCPSchemaToZod(tool.inputSchema),
func: async (input) => {
const result = await client.request(
{ method: 'tools/call' },
{
name: tool.name,
arguments: input,
}
);
return result.content[0].text;
},
});
allTools.push(langchainTool);
}
// 同时为读取资源创建工具
const resourcesResponse = await client.request(
{ method: 'resources/list' },
{}
);
for (const resource of resourcesResponse.resources || []) {
const resourceTool = new DynamicStructuredTool({
name: `read_${resource.uri}`.replace(/[^a-zA-Z0-9_]/g, '_'),
description: `读取资源: ${resource.description}`,
schema: z.object({}),
func: async () => {
const result = await this.readResource(client, resource.uri);
return JSON.stringify(result.contents[0], null, 2);
},
});
allTools.push(resourceTool);
}
} catch (error) {
console.error(`连接到 ${serverUrl} 失败:`, error);
}
}
// 使用所有发现的工具创建智能体
this.agent = createReactAgent({
llm: this.model,
tools: allTools,
});
return allTools.length;
}
private convertMCPSchemaToZod(schema: any): z.ZodSchema {
if (!schema || !schema.properties) {
return z.object({});
}
const shape: Record<string, z.ZodSchema> = {};
for (const [key, prop] of Object.entries(schema.properties)) {
const propSchema = prop as any;
let zodSchema: z.ZodSchema;
switch (propSchema.type) {
case 'string':
zodSchema = propSchema.enum
? z.enum(propSchema.enum as [string, ...string[]])
: z.string();
if (propSchema.description) {
zodSchema = zodSchema.describe(propSchema.description);
}
break;
case 'number':
zodSchema = z.number();
if (propSchema.minimum !== undefined) {
zodSchema = (zodSchema as z.ZodNumber).min(propSchema.minimum);
}
if (propSchema.maximum !== undefined) {
zodSchema = (zodSchema as z.ZodNumber).max(propSchema.maximum);
}
break;
case 'boolean':
zodSchema = z.boolean();
break;
case 'array':
if (propSchema.items) {
zodSchema = z.array(this.convertMCPSchemaToZod(propSchema.items));
} else {
zodSchema = z.array(z.unknown());
}
break;
case 'object':
zodSchema = propSchema.properties
? this.convertMCPSchemaToZod(propSchema)
: z.record(z.unknown());
break;
default:
zodSchema = z.unknown();
}
// 处理必需字段
if (!schema.required?.includes(key)) {
zodSchema = zodSchema.optional();
}
shape[key] = zodSchema;
}
return z.object(shape);
}
async execute(input: string) {
if (!this.agent) {
throw new Error('未连接 MCP 服务器。请先调用 addMCPServers。');
}
const response = await this.agent.stream({
messages: [{ role: 'user', content: input }],
});
const results = [];
for await (const chunk of response) {
results.push(chunk);
}
return results;
}
}
// 在 API 路由中的使用
export async function POST(req: Request) {
const { message, mcpServers } = await req.json();
const agent = new DynamicMCPAgent();
// 连接到多个 MCP 服务器
const toolCount = await agent.addMCPServers(
mcpServers || [
'/api/mcp/advanced',
'ws://localhost:3001/mcp',
'https://external-mcp-server.com/mcp',
]
);
console.log(`连接到 ${toolCount} 个跨 MCP 服务器的工具`);
const result = await agent.execute(message);
return new Response(
JSON.stringify({ result }),
{ headers: { 'Content-Type': 'application/json' } }
);
}
实现一个动态 MCP 智能体,可以同时发现和使用来自多个 MCP 服务器的工具。
结论
模型上下文协议 (MCP) 通过为工具、资源和提示提供标准化、可发现的接口,改变了 AI 智能体与外部系统的交互方式。通过在 Vercel 上的 Next.js 应用程序中实现 MCP,您可以创建能够动态适应新功能而无需代码更改的智能体。@vercel/mcp-handler
与 Langchain 和 es-toolkit 的结合为构建生产就绪的无服务器 AI 系统提供了强大的基础,这些系统可以与任何符合 MCP 的服务无缝集成。这种协议优先的方法确保您的智能体在 AI 工具生态系统持续发展的过程中保持互操作性和面向未来。