1. MCP协议简介
Model Context Protocol (MCP)是Anthropic在2024年推出的开放标准,旨在统一大型语言模型(LLM)与外部数据源和工具之间的通信。它解决了AI模型因数据孤岛限制而无法充分发挥潜力的问题,使AI应用能够访问和操作本地及远程数据。
MCP 是让不同 AI 模型和外部工具通过统一接口协作的通信协议,类似给所有设备统一用USB-C 接口
2. 三层架构设计
MCP遵循清晰的三层架构模式:
Host层
● 与用户直接交互的应用程序(如Claude Desktop、Cursor、Cline等)
● 提供用户界面,处理用户查询到大模型,大模型通过Client路由到指定server
Client层
● 实现MCP的客户端标准,作为大模型和Server的桥梁
● 业界主流方案是一个Client与多个MCP Server建立连接
● 当大模型需要外部工具时,构造请求并将响应传回模型
Server层
● 实现MCP服务端标准,将不同数据源格式统一为MCP标准化结构,支持本地部署与远程连接
● 提供核心功能:定义API和Description,表示服务可提供的功能
● 处理Client的请求并返回结果
3. 通信机制与工作流程
通信协议
● 本地通讯:通过标准输入输出(stdin/stdout)传输,子进程方式通信
● 远程通讯:使用SSE或WebSockets
● 数据格式:采用JSON-RPC 2.0格式
注册与初始化过程
MCP服务器注册通过手动配置完成,目前不存在自动化的服务发现机制:
-
服务器创建与配置:开发者创建符合MCP协议的服务器程序,准备连接信息
-
在Host中添加配置:
bash
"mcpServers": {
"dev-tools": {
"command": "python",
"args": ["/absolute/path/to/my-mcp-server/server.py"]
},
"weather": {
"transport": "sse",
"url": "https://weather-mcp.example.com/api/mcp",
"description": "Weather data service",
"auth": {
"type": "api-key",
"headerName": "X-API-Key",
"key": "${WEATHER_API_KEY}"
}
}
}
- 初始化与功能协商:Host启动时初始化MCP Client组件
Client连接所有MCP Server并获取它们提供的Tools能力进行存储,用户每次提问时Host将这些能力信息作为系统提示词传到大模型的上下文中
实际工作流程
-
用户发起查询:Host调用大模型,携带Server工具信息
-
需求分析:大模型识别需要外部工具时,基于Server描述决定调用哪个Server和方法
-
数据交换:Client向Server发送请求,Server处理后返回数据
-
结果整合:Client将数据返回给大模型,模型完成最终响应
4. 实现方案
Client和Server连接模式
● 基本模式:一个Client对应一个Server
● 高级模式(LangChain / LangGraph的实现):一个Client管理多个Server连接
实现选项
-
使用开源Client:如Open MCP Client (open-mcp-client.vercel.app/)
-
使用官方SDK:Anthropic提供的@modelcontextprotocol/sdk自行实现
4. 智能运维助手场景
需求分析
智能运维助手需要访问服务器日志数据,辅助AI进行分析和诊断。可以根据MCP架构实现以下组件:
-
MCP Server (必须实现):访问服务器日志、系统状态和监控数据
-
MCP Client (自行实现/社区实现):大模型与Server的桥梁,有开源库,比如Langchain等库已经实现好了
-
MCP Host (可选实现):提供用户交互界面
Client的实现方案
- 使用现有开源Client, 一个client管理多个server:
Open MCP Client (open-mcp-client.vercel.app/)
- 使用官方SDK自行实现:
Anthropic提供的@modelcontextprotocol/sdk
5. 代码实现示例
5.1 MCP Server实现
typescript
// mcp_server.ts
import express from 'express';
import bodyParser from 'body-parser';
import { exec } from 'child_process';
import fs from 'fs/promises';
import path from 'path';
import childProcess from 'child_process';
const app = express();
app.use(bodyParser.json());
// 工具定义
const TOOLS = [
{
name: "diagnose_service",
description: "Diagnose a service status",
parameters: {
type: "object",
properties: {
service_name: {
type: "string",
description: "Name of the service to diagnose"
}
},
required: ["service_name"]
}
}
];
// MCP协议处理函数
async function handleMcpRequest(request: any) {
const method = request.method;
const params = request.params || {};
const requestId = request.id;
console.log(`Received MCP request: ${method}`, params);
// 列出工具
if (method === 'list_tools') {
return {
jsonrpc: '2.0',
result: {
tools: TOOLS
},
id: requestId
};
}
// 调用工具
if (method === 'call_tool') {
const toolName = params.name;
const toolArgs = params.arguments || {};
console.log(`Calling tool: ${toolName} with arguments:`, toolArgs);
// 服务诊断工具
if (toolName === 'diagnose_service') {
const serviceName = toolArgs.service_name;
return new Promise((resolve) => {
exec(`systemctl status ${serviceName}`, (error, stdout, stderr) => {
const result = {
jsonrpc: '2.0',
result: {
content: [
{
type: "text",
text: error ? `Error: ${stderr}` : stdout
}
]
},
id: requestId
};
resolve(result);
});
});
}
}
// 方法不存在
console.error(`Method not found: ${method}`);
return {
jsonrpc: '2.0',
error: { code: -32601, message: 'Method not found' },
id: requestId
};
}
// 标准输入/输出模式的处理函数
async function handleStdioMode() {
process.stdin.setEncoding('utf-8');
process.stdin.on('data', async (data) => {
const lines = data.toString().split('\n');
for (const line of lines) {
if (!line.trim()) continue;
try {
const request = JSON.parse(line);
const response = await handleMcpRequest(request);
// 发送响应到标准输出
process.stdout.write(JSON.stringify(response) + '\n');
} catch (error) {
console.error('Error handling request:', error);
const errorResponse = {
jsonrpc: '2.0',
error: { code: -32603, message: `Internal error: ${(error as Error).message}` },
id: null
};
process.stdout.write(JSON.stringify(errorResponse) + '\n');
}
}
});
console.error('MCP Server running in stdio mode');
}
// HTTP接口
app.post('/mcp-server', async (req, res) => {
try {
const response = await handleMcpRequest(req.body);
res.json(response);
} catch (error) {
console.error('Error handling request:', error);
res.status(500).json({
jsonrpc: '2.0',
error: { code: -32603, message: 'Internal error' },
id: req.body.id
});
}
});
// 启动服务器 - 支持HTTP和stdio两种模式
if (process.argv.includes('--stdio')) {
handleStdioMode();
} else {
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`MCP Server running on port ${PORT}`);
});
}
export default app;
5.2 一个MCP Client管理多个MCP Server的实现
typescript
// multi_server_mcp_client.ts
import { spawn, ChildProcess } from 'child_process';
import fetch from 'node-fetch';
import { EventEmitter } from 'events';
import * as path from 'path';
// 定义连接类型
type StdioConnection = {
command: string;
args: string[];
transport: 'stdio';
};
type SSEConnection = {
url: string;
transport: 'sse';
};
// MCP配置类型
type MCPConfig = Record<string, StdioConnection | SSEConnection>;
// 工具类型定义
type ToolParameter = {
type: string;
description?: string;
properties?: Record<string, any>;
required?: string[];
};
type Tool = {
name: string;
description: string;
parameters: ToolParameter;
returns?: any;
};
// 传输层基类
abstract class Transport extends EventEmitter {
abstract connect(): Promise<void>;
abstract send(request: any): Promise<any>;
abstract disconnect(): Promise<void>;
}
// Stdio传输层实现
class StdioTransport extends Transport {
private process: ChildProcess | null = null;
private responseQueue: Array<{
resolve: (value: any) => void;
reject: (reason: any) => void;
id: string;
}> = [];
private buffer: string = '';
constructor(private command: string, private args: string[]) {
super();
}
async connect(): Promise<void> {
return new Promise((resolve, reject) => {
try {
this.process = spawn(this.command, this.args);
this.process.stdout?.on('data', (data: Buffer) => {
this.buffer += data.toString();
this.processBuffer();
});
this.process.stderr?.on('data', (data: Buffer) => {
console.error(`[StdioTransport] Error: ${data.toString()}`);
});
this.process.on('error', (error) => {
console.error('[StdioTransport] Process error:', error);
reject(error);
});
this.process.on('exit', (code) => {
console.log(`[StdioTransport] Process exited with code ${code}`);
if (code !== 0) {
this.rejectAllPending(`Process exited with code ${code}`);
}
});
// 给进程一些启动时间
setTimeout(resolve, 100);
} catch (error) {
reject(error);
}
});
}
private processBuffer() {
const lines = this.buffer.split('\n');
// 保留最后一行,可能不完整
this.buffer = lines.pop() || '';
for (const line of lines) {
if (!line.trim()) continue;
try {
const response = JSON.parse(line);
const pending = this.responseQueue.find(p => p.id === response.id);
if (pending) {
const index = this.responseQueue.indexOf(pending);
this.responseQueue.splice(index, 1);
if (response.error) {
pending.reject(response.error);
} else {
pending.resolve(response);
}
} else {
console.warn('[StdioTransport] Received response with no matching request:', response);
}
} catch (error) {
console.error('[StdioTransport] Error parsing response:', error, line);
}
}
}
async send(request: any): Promise<any> {
if (!this.process || this.process.killed) {
throw new Error('Process not running');
}
return new Promise((resolve, reject) => {
this.responseQueue.push({
resolve,
reject,
id: request.id
});
const requestStr = JSON.stringify(request) + '\n';
this.process!.stdin!.write(requestStr);
});
}
private rejectAllPending(reason: string) {
for (const pending of this.responseQueue) {
pending.reject(new Error(reason));
}
this.responseQueue = [];
}
async disconnect(): Promise<void> {
if (this.process && !this.process.killed) {
this.process.kill();
}
}
}
// HTTP/SSE传输层实现
class HttpTransport extends Transport {
constructor(private url: string) {
super();
}
async connect(): Promise<void> {
// HTTP传输不需要特别的连接逻辑
}
async send(request: any): Promise<any> {
const response = await fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
}
return await response.json();
}
async disconnect(): Promise<void> {
// HTTP传输不需要特别的断开逻辑
}
}
// 多服务器MCP客户端
class MultiServerMCPClient {
private services: Map<string, {
transport: Transport,
tools: Tool[]
}> = new Map();
private isConnected: boolean = false;
constructor(private config: MCPConfig) {}
async connect(): Promise<void> {
// 创建和连接所有服务器客户端
for (const [serviceName, serviceConfig] of Object.entries(this.config)) {
try {
// 创建传输层
let transport: Transport;
if (serviceConfig.transport === 'stdio') {
transport = new StdioTransport(serviceConfig.command, serviceConfig.args);
} else if (serviceConfig.transport === 'sse') {
transport = new HttpTransport(serviceConfig.url);
} else {
throw new Error(`Unsupported transport: ${(serviceConfig as any).transport}`);
}
// 连接到服务
await transport.connect();
// 初始化服务
const response = await transport.send({
jsonrpc: '2.0',
method: 'list_tools',
params: {},
id: `init_${serviceName}_${Date.now()}`
});
if (response.error) {
throw new Error(`Initialization error: ${response.error.message}`);
}
// 保存服务信息和工具
this.services.set(serviceName, {
transport,
tools: response.result?.tools || []
});
console.log(`Connected to MCP service: ${serviceName} with ${response.result?.tools?.length || 0} tools`);
} catch (error) {
console.error(`Failed to connect to service ${serviceName}:`, error);
// 继续连接其他服务,不因一个服务失败而终止
}
}
this.isConnected = true;
console.log(`Connected to ${this.services.size} MCP services`);
}
getTools(): Tool[] {
if (!this.isConnected) {
throw new Error('Not connected to MCP services');
}
// 收集所有服务的工具,添加服务名前缀
const allTools: Tool[] = [];
for (const [serviceName, service] of this.services.entries()) {
for (const tool of service.tools) {
allTools.push({
...tool,
name: `${serviceName}.${tool.name}`,
description: `[${serviceName}] ${tool.description}`
});
}
}
return allTools;
}
async callTool(fullToolName: string, parameters: any): Promise<any> {
if (!this.isConnected) {
throw new Error('Not connected to MCP services');
}
// 解析服务名和工具名
const [serviceName, toolName] = fullToolName.split('.');
if (!serviceName || !toolName) {
throw new Error(`Invalid tool name format: ${fullToolName}. Expected: service.tool`);
}
const service = this.services.get(serviceName);
if (!service) {
throw new Error(`Service not found: ${serviceName}`);
}
// 调用工具
const response = await service.transport.send({
jsonrpc: '2.0',
method: 'call_tool',
params: {
name: toolName,
arguments: parameters
},
id: `call_${serviceName}_${toolName}_${Date.now()}`
});
if (response.error) {
throw new Error(`Tool call error: ${response.error.message}`);
}
return response.result;
}
async disconnect(): Promise<void> {
// 断开所有服务连接
for (const [serviceName, service] of this.services.entries()) {
try {
await service.transport.disconnect();
console.log(`Disconnected from service: ${serviceName}`);
} catch (error) {
console.error(`Error disconnecting from ${serviceName}:`, error);
}
}
this.services.clear();
this.isConnected = false;
console.log('Disconnected from all MCP services');
}
}
export { MultiServerMCPClient, MCPConfig, Tool, using };
5.3 简易MCP Host实现
typescript
import express from 'express';
import path from 'path';
import { MultiServerMCPClient, MCPConfig } from './multi_server_mcp_client';
const app = express();
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
// 模拟LLM接口
class ChatModel {
private config: { apiKey: string, model: string };
constructor(config: { apiKey: string, model: string }) {
this.config = config;
}
async chat(options: { messages: any[] }): Promise<any> {
console.log('Sending chat request to LLM');
console.log('Messages:', JSON.stringify(options.messages, null, 2));
// 检查是否需要调用工具 - 实际应该由LLM API返回
const lastMessage = options.messages[options.messages.length - 1];
if (lastMessage.role === 'user') {
const content = lastMessage.content.toLowerCase();
if (content.includes('service') || content.includes('status')) {
// 模拟LLM决定使用服务诊断工具
return {
id: 'resp_' + Date.now(),
content: JSON.stringify({
thoughts: "用户询问服务状态。我应该使用服务诊断工具获取信息。",
tool: "logs.diagnose_service",
parameters: {
service_name: "nginx"
}
}),
model: this.config.model
};
}
}
// 普通回复
return {
id: 'resp_' + Date.now(),
content: '我已经处理了您的请求。您可以尝试询问关于日志、服务状态或文件内容的问题,我可以使用工具来帮助您。',
model: this.config.model
};
}
}
// MCP服务配置
const MCP_CONFIG: MCPConfig = {
"logs": {
url: "http://localhost:3000/mcp",
transport: "sse",
},
"filesystem": {
command: "node",
args: [path.join(__dirname, "mcp_server.js"), "--stdio"],
transport: "stdio",
}
};
// 初始化客户端
const mcpClient = new MultiServerMCPClient(MCP_CONFIG);
// 初始化模型
const model = new ChatModel({
apiKey: process.env.AI_API_KEY || 'dummy-key',
model: 'claude-3.7-sonnet'
});
let toolsMetadata: any[] = [];
// 连接到服务器
(async () => {
try {
await mcpClient.connect();
toolsMetadata = mcpClient.getTools();
// 在这种模式下,工具名称会添加服务器前缀(例如logs.search_logs)以避免命名冲突
console.log('MCP Client connected successfully');
console.log('Available tools:', JSON.stringify(toolsMetadata, null, 2));
} catch (error) {
console.error('Failed to connect MCP Client:', error);
}
})();
// 处理聊天请求
app.post('/api/chat', async (req, res) => {
try {
const { message, conversation_id } = req.body;
const newConversationId = conversation_id || Date.now().toString();
// 从数据库或内存中获取历史消息
let conversationHistory = await getConversationHistory(newConversationId) || [];
// 构建带有工具调用能力的提示
const systemPrompt = `You are an AI operations assistant with many tools.
When you need to use external tools, use the appropriate tool.
Available Tools: ${JSON.stringify(toolsMetadata)}
When you want to use external tool, respond with a JSON object containing:
{
"thoughts": "your reasoning",
"tool": "service.tool_name",
"parameters": { param1: value1, ... }
}`;
// 创建完整的消息历史,包括系统提示和新消息
const messages = [
{ role: 'system', content: systemPrompt },
...conversationHistory,
{ role: 'user', content: message }
];
// 开始agent循环,允许多次工具调用
let maxIterations = 5; // 防止无限循环
let finalResponse = null;
let toolUsed = null;
for (let i = 0; i < maxIterations; i++) {
// 调用LLM
const llmResponse = await model.chat({ messages });
try {
// 尝试解析JSON工具调用
const toolCall = JSON.parse(llmResponse.content);
if (toolCall.tool && toolCall.parameters) {
console.log(`LLM requested tool (iteration ${i+1}): ${toolCall.tool}`);
toolUsed = toolCall.tool;
// 执行工具调用
const toolResult = await mcpClient.callTool(
toolCall.tool,
toolCall.parameters
);
// 提取工具返回的文本内容
let resultText = '';
if (toolResult.content && Array.isArray(toolResult.content)) {
resultText = toolResult.content
.filter((item) => item.type === 'text')
.map((item) => item.text)
.join('\n');
} else {
resultText = JSON.stringify(toolResult);
}
// 将LLM响应和工具结果添加到历史中
messages.push({ role: 'assistant', content: llmResponse.content });
messages.push({ role: 'system', content: `Tool result: ${resultText}` });
// 继续循环,让LLM处理工具结果
continue;
}
// 如果没有工具调用,这是最终响应
finalResponse = llmResponse.content;
break;
} catch (parseError) {
// 不是JSON,是普通回复,保存为最终响应
finalResponse = llmResponse.content;
break;
}
}
// 保存对话历史
const updatedHistory = [
...conversationHistory,
{ role: 'user', content: message },
{ role: 'assistant', content: finalResponse }
];
await saveConversationHistory(newConversationId, updatedHistory);
// 返回最终结果
res.json({
message: finalResponse,
conversation_id: newConversationId,
tool_used: toolUsed
});
} catch (error) {
console.error('Chat error:', error);
res.status(500).json({ error: 'Failed to process chat request' });
}
});
// 辅助函数用于管理对话历史
async function getConversationHistory(conversationId) {
// 实现从数据库或内存中获取对话历史
// 简单实现可以使用内存缓存或Redis
// 这里假设有一个全局对象存储对话历史
return global.conversations[conversationId] || [];
}
async function saveConversationHistory(conversationId, history) {
// 保存对话历史到数据库或内存
if (!global.conversations) global.conversations = {};
global.conversations[conversationId] = history;
}
// 获取工具列表API
app.get('/api/tools', (req, res) => {
res.json({ tools: toolsMetadata });
});
// 启动服务器
const PORT = process.env.HOST_PORT || 8080;
app.listen(PORT, () => {
console.log(`MCP Host running on port ${PORT}`);
});
// 确保在进程退出时断开MCP连接
process.on('exit', async () => {
await mcpClient.disconnect();
});
process.on('SIGINT', async () => {
await mcpClient.disconnect();
process.exit();
});
export default app;
总结
● 对开发者:MCP减少 70% 的重复接口开发工作, 增强AI能力
● 对公司:快速连接现有系统与 AI 能力