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工具链,让大模型真正具备与外部世界交互的能力。
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐