MCP协议开发实战:从零搭建AI Agent工具链
·
1. 引言
随着大语言模型(LLM)能力的不断提升,如何让AI Agent与外部工具、数据源进行高效交互成为关键挑战。MCP(Model Context Protocol)协议应运而生,它为AI模型与外部工具之间提供了一套标准化的通信协议。本文将带你从零开始,通过实战代码搭建一套完整的MCP工具链。
2. MCP协议核心概念
MCP协议定义了三个核心角色:
- Host:运行LLM的主机环境,负责发起请求和处理响应
- Client:与MCP Server通信的客户端,封装了协议细节
- Server:提供具体工具能力的服务端,暴露标准接口
通信采用JSON-RPC 2.0协议,通过标准输入输出(stdio)或HTTP进行数据传输。
3. 环境准备
首先,我们需要准备开发环境:
# 创建项目目录
mkdir mcp-toolchain && cd mcp-toolchain
初始化Node.js项目
npm init -y
安装核心依赖
npm install @modelcontextprotocol/sdk zod
安装开发依赖
npm install -D typescript @types/node ts-node
4. 基础MCP Server实现
下面我们实现一个基础的MCP Server,提供文件读取和天气查询两个工具:
// src/server.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
// 创建Server实例
const server = new Server(
{
name: "mcp-toolchain-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// 定义工具列表
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "read_file",
description: "读取指定路径的文件内容",
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "文件路径" },
},
required: ["path"],
},
},
{
name: "get_weather",
description: "查询指定城市的天气信息",
inputSchema: {
type: "object",
properties: {
city: { type: "string", description: "城市名称" },
},
required: ["city"],
},
},
],
}));
// 实现工具调用逻辑
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "read_file": {
const { path } = args as { path: string };
const fs = await import("fs/promises");
const content = await fs.readFile(path, "utf-8");
return {
content: [{ type: "text", text: content }],
};
}
case "get_weather": {
const { city } = args as { city: string };
// 模拟天气查询
return {
content: [
{
type: "text",
text: ${city}天气:晴,温度25°C,湿度60%,
},
],
};
}
default:
throw new Error(未知工具: ${name});
}
});
// 启动Server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Server running on stdio");
}
main().catch(console.error);
5. MCP Client实现
接下来实现客户端,用于与MCP Server通信:
// src/client.ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { spawn } from "child_process";
class MCPClient {
private client: Client;
private transport: StdioClientTransport | null = null;
constructor() {
this.client = new Client(
{
name: "mcp-toolchain-client",
version: "1.0.0",
},
{
capabilities: {},
}
);
}
async connect(serverScript: string) {
// 启动Server进程
const serverProcess = spawn("node", [serverScript], {
stdio: ["pipe", "pipe", process.stderr],
});
this.transport = new StdioClientTransport({
stdin: serverProcess.stdin,
stdout: serverProcess.stdout,
});
await this.client.connect(this.transport);
console.log("已连接到MCP Server");
}
async listTools() {
const result = await this.client.listTools();
console.log("可用工具:", result.tools.map(t => t.name));
return result.tools;
}
async callTool(name: string, args: Record<string, unknown>) {
const result = await this.client.callTool({
name,
arguments: args,
});
return result;
}
async disconnect() {
await this.client.close();
if (this.transport) {
await this.transport.close();
}
}
}
// 使用示例
async function main() {
const client = new MCPClient();
try {
await client.connect("./dist/server.js");
// 列出可用工具
const tools = await client.listTools();
// 调用文件读取工具
const fileResult = await client.callTool("read_file", {
path: "./package.json",
});
console.log("文件内容:", fileResult.content[0].text);
// 调用天气查询工具
const weatherResult = await client.callTool("get_weather", {
city: "北京",
});
console.log("天气信息:", weatherResult.content[0].text);
} finally {
await client.disconnect();
}
}
main().catch(console.error);
6. 集成AI Agent
将MCP Client集成到AI Agent中,实现智能工具调用:
// src/agent.ts
import OpenAI from "openai";
import { MCPClient } from "./client.js";
class AIAgent {
private openai: OpenAI;
private mcpClient: MCPClient;
private tools: any[] = [];
constructor(apiKey: string) {
this.openai = new OpenAI({ apiKey });
this.mcpClient = new MCPClient();
}
async initialize(serverScript: string) {
await this.mcpClient.connect(serverScript);
this.tools = await this.mcpClient.listTools();
}
async processQuery(userQuery: string) {
const messages: any[] = [
{ role: "user", content: userQuery }
];
// 将MCP工具转换为OpenAI工具格式
const openaiTools = this.tools.map(tool => ({
type: "function" as const,
function: {
name: tool.name,
description: tool.description,
parameters: tool.inputSchema,
},
}));
const response = await this.openai.chat.completions.create({
model: "gpt-4",
messages,
tools: openaiTools,
tool_choice: "auto",
});
const message = response.choices[0].message;
// 处理工具调用
if (message.tool_calls) {
for (const toolCall of message.tool_calls) {
const args = JSON.parse(toolCall.function.arguments);
const result = await this.mcpClient.callTool(
toolCall.function.name,
args
);
messages.push(message);
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: result.content[0].text,
});
}
// 获取最终回复
const finalResponse = await this.openai.chat.completions.create({
model: "gpt-4",
messages,
});
return finalResponse.choices[0].message.content;
}
return message.content;
}
async cleanup() {
await this.mcpClient.disconnect();
}
}
// 使用示例
async function main() {
const agent = new AIAgent(process.env.OPENAI_API_KEY!);
try {
await agent.initialize("./dist/server.js");
const result = await agent.processQuery(
"请读取package.json文件,并查询北京的天气"
);
console.log("AI回复:", result);
} finally {
await agent.cleanup();
}
}
main().catch(console.error);
7. 项目构建与运行
配置TypeScript编译和运行脚本:
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
// package.json 添加脚本
{
"scripts": {
"build": "tsc",
"start:server": "node dist/server.js",
"start:client": "node dist/client.js",
"start:agent": "node dist/agent.js"
}
}
# 构建并运行
npm run build
启动Server(终端1)
npm run start:server
启动Client(终端2)
npm run start:client
或启动AI Agent(终端3)
export OPENAI_API_KEY=your-api-key
npm run start:agent
8. 架构图
flowchart TD
A[用户] -->|自然语言查询| B[AI Agent]
B -->|工具调用请求| C[MCP Client]
C -->|JSON-RPC| D[MCP Server]
D -->|读取文件| E[文件系统]
D -->|查询天气| F[天气API]
E -->|文件内容| D
F -->|天气数据| D
D -->|工具响应| C
C -->|结果| B
B -->|最终回复| A
subgraph MCP协议层
C
D
end
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#bbf,stroke:#333,stroke-width:2px
style C fill:#bfb,stroke:#333,stroke-width:2px
style D fill:#fbb,stroke:#333,stroke-width:2px</code></pre>
9. 进阶:动态工具注册
实现一个动态工具注册机制,让Server可以热加载新工具:
// src/dynamic-server.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
interface ToolDefinition {
name: string;
description: string;
handler: (args: any) => Promise<any>;
inputSchema: Record<string, any>;
}
class DynamicMCPServer {
private server: Server;
private tools: Map<string, ToolDefinition> = new Map();
constructor() {
this.server = new Server(
{
name: "dynamic-mcp-server",
version: "1.0.0",
},
{
capabilities: { tools: {} },
}
);
this.setupHandlers();
}
private setupHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: Array.from(this.tools.values()).map(t => ({
name: t.name,
description: t.description,
inputSchema: t.inputSchema,
})),
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const tool = this.tools.get(request.params.name);
if (!tool) {
throw new Error(工具 ${request.params.name} 未注册);
}
const result = await tool.handler(request.params.arguments);
return { content: [{ type: "text", text: JSON.stringify(result) }] };
});
}
registerTool(definition: ToolDefinition) {
this.tools.set(definition.name, definition);
console.log(工具已注册: ${definition.name});
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
}
}
// 使用示例
const dynamicServer = new DynamicMCPServer();
// 动态注册计算器工具
dynamicServer.registerTool({
name: "calculator",
description: "执行数学计算",
inputSchema: {
type: "object",
properties: {
expression: { type: "string", description: "数学表达式" },
},
},
handler: async (args) => {
const result = eval(args.expression);
return { expression: args.expression, result };
},
});
// 动态注册数据库查询工具
dynamicServer.registerTool({
name: "query_database",
description: "执行SQL查询",
inputSchema: {
type: "object",
properties: {
sql: { type: "string", description: "SQL语句" },
},
},
handler: async (args) => {
// 模拟数据库查询
return { sql: args.sql, rows: [], affected: 0 };
},
});
dynamicServer.start().catch(console.error);
10. 总结与最佳实践
本文从零开始搭建了一套完整的MCP工具链,涵盖了Server、Client和AI Agent三个核心组件。在实际开发中,建议遵循以下最佳实践:
错误处理:为每个工具调用添加完善的错误处理和超时机制
安全验证:对工具输入进行严格的参数校验,防止注入攻击
日志监控:记录所有工具调用日志,便于调试和审计
性能优化:对频繁调用的工具添加缓存机制
版本管理:使用语义化版本管理MCP Server的API变更
通过MCP协议,我们可以构建出功能强大、可扩展的AI Agent工具链,让大模型真正具备与外部世界交互的能力。更多推荐


所有评论(0)