MCP

https://modelcontextprotocol.io/

核心概念

MCP(模型上下文协议) 是一个由 Anthropic 创建的标准化通信协议。它的主要作用是让 AI 模型能够安全、规范地连接和使用外部工具与数据源

想象一下:当你想让 AI 帮你创建文件、查询数据库或访问网络时,AI 模型本身并不能直接操作你的电脑或外部系统。这个时候需要一些外部工具来实现这些操作,这些外部工具是由编程语言编写的辅助工具,或者某些功能需要我们程序员自己来实现编写,那么这个时候ai模型需要与这些外部工具通信,就需要一个“桥梁”——而 MCP 就是这个桥梁

把 MCP 想象成 AI 的“USB 接口”:

  • AI 模型本身就像一台没有外设的电脑
  • MCP 提供了标准的“接口规范”
  • 外部工具(文件系统、数据库、API)就像各种“USB 设备”
  • 通过这个标准接口,AI 就能安全地使用各种扩展功能

为什么使用mcp

  • 当ai模型自身能力不足以满足用户需求时,能让ai模型连接到外部工具来辅助实现用户的需求,比如创建文件写入文件内容。ai模型自身并不能实现,所以需要借助外力来实现,那么ai在和外部工具通信时需要有一个标准的通信协议,从而确保能够正常交互。这个标准的通信协议就是mcp。
  • mcp通信支持两种通信方式。
    1. stdio : 推荐,高效/简洁/基于本地化的json RPC
    2. http: 可远程
  • 总结:能够扩充ai能力,让 AI 可以执行原本无法完成的任务,如:
    • 读取文件系统、数据库
    • 访问网络资源
    • 执行代码
    • 调用 API等等

什么是mcp服务(MCP Server)

  • mcp服务也就是ai模型所需要扩充的工具,本质是一个可执行程序,通过ai来调用,执行后返回结果,调用和返回的参数格式也被就是mcp通信协议所约束的规范,如果不统一规范,ai不知道执行工具时需要传递什么参数,乱传递参数,工具侧也不认识这些参数,工具侧执行结果返回如果不统一格式那么ai它也不认识。

mcp的简单实现和工作原理(stdio)

  • 因为mcp通信的本质就是进程之间标准的输入和输出,加上统一规范的输入输出参数来达到安全正确的数据交互,所以大部分的编程语言都可以编写mcp服务,这里以node为例,先简单实现一个标准的输入输出
// 标准输出 由终端(客户端)捕获得到
// process.stdout.write('Hello World');
// 标准输入 监控来自终端(客户端)的输入
console.log('请输入内容:')
process.stdin.on('data', function (data) {
    console.log(data.toString());
    // 需要手动退出程序,除非你需要一直监听输入并返回处理结果
    process.exit();
})
  • 标准的输入输出其实很简单。其实就算是一个console.log,由父进程启动子进程,两个进程之间就可以通信

简单的实现一个mcp服务

mcp服务代码,
  • 调试时不能使用console.log 因为log算是标准的输出,用log会导致mcp服务不能正常运作,可以使用error作为调试信息,进程通信除了有标准的输入输出以外还是标准的错误输出
// simple-mcp-server.js
const fs = require('fs');
const path = require('path');

class SimpleMCPServer {
  constructor() {
    this.initialized = false; // 标记是否已完成初始化
    this.setupServer();
  }

  // 工具1:求和
  addNumbers(a, b) {
    console.error('[MCP] 调用求和工具');
    if (typeof a !== 'number' || typeof b !== 'number') {
      return {
        success: false,
        error: '参数必须是数字',
        message: `参数类型错误: a=${typeof a}, b=${typeof b}`
      };
    }
    const result = a + b;
    console.error(`[MCP] 计算结果: ${a} + ${b} = ${result}`);
    return {
      success: true,
      result: `计算结果:${result}`,
      message: `${a} + ${b} = ${result}`
    };
  }

  // 工具2:创建文件
  createFile(filePath, fileName, content) {
    console.error('[MCP] 调用创建文件工具');
    try {
      if (typeof filePath !== 'string' || typeof fileName !== 'string' || typeof content !== 'string') {
        throw new Error('所有参数必须是字符串');
      }

      // 清理路径
      const cleanPath = filePath.replace(/\.\./g, ''); // 防止路径遍历
      const fullPath = path.join(cleanPath, fileName);

      // 确保目录存在
      if (!fs.existsSync(cleanPath)) {
        fs.mkdirSync(cleanPath, { recursive: true });
      }

      // 写入文件
      fs.writeFileSync(fullPath, content, 'utf8');
      console.error(`[MCP] 文件创建成功: ${fullPath}`);

      return {
        success: true,
        message: `文件创建成功: ${fileName}`,
        path: fullPath,
        size: content.length
      };
    } catch (error) {
      console.error(`[MCP] 文件创建失败: ${error.message}`);
      return {
        success: false,
        error: error.message,
        message: `文件创建失败: ${error.message}`
      };
    }
  }

  // 处理初始化请求(握手)
  handleInitialize(params) {
    console.error('[MCP] 收到初始化请求');

    // 检查协议版本
    if (params.protocolVersion !== '2025-06-18') {
      return {
        jsonrpc: "2.0",
        id: params.requestId,
        error: {
          code: -32602,
          message: "不支持的协议版本"
        }
      };
    }

    this.initialized = true;
    console.error('[MCP] 初始化完成,服务器已就绪');

    return {
      jsonrpc: "2.0",
      id: params.requestId,
      result: {
        protocolVersion: "2025-06-18",
        capabilities: {
          tools: {},
          prompts: {},
          resources: {}
        },
        serverInfo: {
          name: "simple-mcp-server",
          version: "1.0.0"
        }
      }
    };
  }

  // 处理 JSON-RPC 请求
  handleRequest(request) {
    try {
      const data = JSON.parse(request);
      console.error(`[MCP] 收到请求: ${data.method || 'unknown'}, ID: ${data.id}`);

      // 1. 初始化请求(必须在其他请求之前)
      if (data.method === 'initialize') {
        return JSON.stringify(this.handleInitialize({
          requestId: data.id,
          protocolVersion: data.params?.protocolVersion,
          capabilities: data.params?.capabilities
        }));
      }



      // 2. 通知请求(不需要响应)
      if (data.method === 'notify/initialized') {
        console.error('[MCP] 客户端已就绪通知');
        return null; // 通知不需要响应
      }

      // 3. 列出可用工具
      if (data.method === 'tools/list') {
        const response = {
          jsonrpc: "2.0",
          id: data.id,
          result: {
            tools: [
              {
                name: "add",
                description: "计算两个数字的和",
                inputSchema: {
                  type: "object",
                  properties: {
                    a: {
                      type: "number",
                      description: "第一个数字"
                    },
                    b: {
                      type: "number",
                      description: "第二个数字"
                    }
                  },
                  required: ["a", "b"],
                  additionalProperties: false
                }
              },
              {
                name: "create_file",
                description: "创建文件并写入内容",
                inputSchema: {
                  type: "object",
                  properties: {
                    filePath: {
                      type: "string",
                      description: "文件路径(相对路径)"
                    },
                    fileName: {
                      type: "string",
                      description: "文件名"
                    },
                    content: {
                      type: "string",
                      description: "文件内容"
                    }
                  },
                  required: ["filePath", "fileName", "content"],
                  additionalProperties: false
                }
              }
            ]
          }
        };
        return JSON.stringify(response);
      }

      // 4. 调用工具
      if (data.method === 'tools/call') {
        const { name, arguments: args } = data.params;
        let result;

        if (name === 'add') {
          result = this.addNumbers(args.a, args.b);
        } else if (name === 'create_file') {
          result = this.createFile(args.filePath, args.fileName, args.content);
        } else {
          result = { success: false, error: `未知的工具: ${name}` };
        }

        const response = {
          jsonrpc: "2.0",
          id: data.id,
          result: {
            content: [
              {
                type: "text",
                text: JSON.stringify(result, null, 2)
              }
            ]
          }
        };
        return JSON.stringify(response);
      }

      // 5. 关闭请求
      if (data.method === 'shutdown') {
        console.error('[MCP] 收到关闭请求');
        const response = {
          jsonrpc: "2.0",
          id: data.id,
          result: null
        };

        // 延迟退出,让客户端有时间处理响应
        setTimeout(() => {
          console.error('[MCP] 服务器关闭');
          process.exit(0);
        }, 100);

        return JSON.stringify(response);
      }

      // 6. 其他请求返回错误
      return JSON.stringify({
        jsonrpc: "2.0",
        id: data.id,
        error: {
          code: -32601,
          message: "方法不存在"
        }
      });

    } catch (error) {
      console.error(`[MCP] 处理请求出错: ${error.message}`);
      return JSON.stringify({
        jsonrpc: "2.0",
        id: null,
        error: {
          code: -32700,
          message: "解析错误",
          data: error.message
        }
      });
    }
  }

  setupServer() {
    console.error('======================================');
    console.error('  简单 MCP 服务器 (纯 JavaScript)');
    console.error('  按 Ctrl+C 退出');
    console.error('======================================\n');

    // 监听标准输入
    process.stdin.setEncoding('utf8');

    let buffer = '';
    process.stdin.on('data', (chunk) => {
      buffer += chunk;

      // 检查是否收到完整请求(以换行符分隔)
      const requests = buffer.split('\n');
      buffer = requests.pop() || '';

      requests.forEach(request => {
        if (request.trim()) {
          try {
            const response = this.handleRequest(request);
            if (response) {
              process.stdout.write(response + '\n');
            }
          } catch (error) {
            console.error(`[MCP] 处理失败: ${error.message}`);
          }
        }
      });
    });

    // 错误处理
    process.stdin.on('error', (error) => {
      console.error(`[MCP] 输入流错误: ${error.message}`);
    });

    process.on('SIGINT', () => {
      console.error('\n[MCP] 服务器关闭');
      process.exit(0);
    });
  }
}

// 启动服务器
new SimpleMCPServer();

mcp服务测试(手动测试)
  • 这个手动调试写的可能有点抽象,需要先启动脚本程序,然后在终端输入指定格式的json, 有点麻烦,你可以直接看后面的可视化调试。
初始化阶段
  • 初始化测试, 向mcp服务输入 固定格式
{
    "jsonrpc": "2.0", // json rpc版本
    "id": 1, // id 用于多个请求时能够靳准对应上响应结果
    "method": "initialize", // 初始化 ,固定值
    "params": { // 请求参数
        "protocolVersion": "2025-06-18", // mcp版本
        "capabilities": {
            "tools": {

            },
            "prompts": {

            },
            "resources": {

            }
        },
        "clientInfo": { // 向服务器介绍自身信息
            "name": "test-client",
            "version": "1.0.0"
        }
    }
}
  • 服务器响应 如果支持mcp服务
{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "protocolVersion": "2025-06-18",
        "capabilities": { // 表示服务器有哪些功能
            "tools": {

            },
            "prompts": {

            },
            "resources": {

            }
        },
        "serverInfo": { // 介绍服务器情况
            "name": "simple-mcp-server",
            "version": "1.0.0"
        }
    }
}
  • 查询服务器是否准备就绪
{
  "jsonrpc": "2.0",
  "method": "notify/initialized",
  "params": {}
}
列出mcp服务有哪些工具

当ai知道这个mcp服务器有哪些工具可用

  • 输入固定格式
{  "jsonrpc": "2.0",  "id": 2,  "method": "tools/list"}
  • 响应内容
{
    "jsonrpc": "2.0",
    "id": 2,
    "result": {
        "tools": [ // 工具列表
            {
                "name": "add", // 工具名称
                "description": "计算两个数字的和", // 作用,用自然语言描述,方便ai理解
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "a": {
                            "type": "number",
                            "description": "第一个数字"
                        },
                        "b": {
                            "type": "number",
                            "description": "第二个数字"
                        }
                    },
                    "required": [// 必填参数
                        "a",
                        "b"
                    ],
                    "additionalProperties": false
                }
            },
            {
                "name": "create_file",
                "description": "创建文件并写入内容",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "filePath": {
                            "type": "string",
                            "description": "文件路径(相对路径)"
                        },
                        "fileName": {
                            "type": "string",
                            "description": "文件名"
                        },
                        "content": {
                            "type": "string",
                            "description": "文件内容"
                        }
                    },
                    "required": [
                        "filePath",
                        "fileName",
                        "content"
                    ],
                    "additionalProperties": false
                }
            }
        ]
    }
}
工具测试
  • 输入参数
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "add", // 调用工具名称
    "arguments": { // 参数
      "a": 15,
      "b": 27
    }
  }
}
  • 输出结果
{
    "jsonrpc": "2.0",
    "id": 3,
    "result": {
        "content": [
            {
                "type": "text",
                "text": "{\n  \"success\": true,\n  \"result\": 42,\n  \"message\": \"15 + 27 = 42\"\n}"
            }
        ]
    }
}
mcp服务测试(可视化工具)
  • 通过npx安装
npx @modelcontextprotocol/inspector
# 注意:命令需要在mcp服务所在的目录下执行,不然等会启动不了服务
  • 命令执行后会在终端有一个可以访问网页地址 http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=…打开即可

在这里插入图片描述

  • 填写你的通信方式,启动命令及文件

在这里插入图片描述

可依次点击并测试工具

在这里插入图片描述

基于mcp sdk实现mcp服务

npm install @modelcontextprotocol/sdk
  • 代码, 测试流程和上面一样,如果可视化面板没反应可重启一下可视化服务
// simple-mcp-server-sdk.js
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 fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

// 获取当前文件目录
const __dirname = path.dirname(fileURLToPath(import.meta.url));

class SimpleMCPServerSDK {
  constructor() {
    // 创建 MCP 服务器
    this.server = new Server(
      {
        name: 'simple-mcp-server',
        version: '1.0.0'
      },
      {
        capabilities: {
          tools: {},
        }
      }
    );
    
    this.setupHandlers();
  }
  
  // 工具1:求和
  addNumbers(a, b) {
    console.error('[MCP SDK] 调用求和工具');
    
    // 参数验证
    if (typeof a !== 'number' || typeof b !== 'number') {
      return {
        success: false,
        error: '参数必须是数字',
        message: `参数类型错误: a=${typeof a}, b=${typeof b}`
      };
    }
    
    const result = a + b;
    console.error(`[MCP SDK] 计算结果: ${a} + ${b} = ${result}`);
    
    return {
      success: true,
      result: `计算结果:${result}`,
      message: `${a} + ${b} = ${result}`
    };
  }
  
  // 工具2:创建文件
  createFile(filePath, fileName, content) {
    console.error('[MCP SDK] 调用创建文件工具');
    
    try {
      // 参数验证
      if (typeof filePath !== 'string' || typeof fileName !== 'string' || typeof content !== 'string') {
        throw new Error('所有参数必须是字符串');
      }
      
      // 清理路径
      const cleanPath = filePath.replace(/\.\./g, ''); // 防止路径遍历
      const fullPath = path.join(cleanPath, fileName);
      
      // 确保目录存在
      if (!fs.existsSync(cleanPath)) {
        fs.mkdirSync(cleanPath, { recursive: true });
      }
      
      // 写入文件
      fs.writeFileSync(fullPath, content, 'utf8');
      console.error(`[MCP SDK] 文件创建成功: ${fullPath}`);
      
      return {
        success: true,
        message: `文件创建成功: ${fileName}`,
        path: fullPath,
        size: content.length
      };
    } catch (error) {
      console.error(`[MCP SDK] 文件创建失败: ${error.message}`);
      return {
        success: false,
        error: error.message,
        message: `文件创建失败: ${error.message}`
      };
    }
  }
  
  // 设置请求处理器
  setupHandlers() {
    // 处理列出工具请求
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      console.error('[MCP SDK] 收到列出工具请求');
      
      return {
        tools: [
          {
            name: 'add',
            description: '计算两个数字的和',
            inputSchema: {
              type: 'object',
              properties: {
                a: {
                  type: 'number',
                  description: '第一个数字'
                },
                b: {
                  type: 'number',
                  description: '第二个数字'
                }
              },
              required: ['a', 'b'],
              additionalProperties: false
            }
          },
          {
            name: 'create_file',
            description: '创建文件并写入内容',
            inputSchema: {
              type: 'object',
              properties: {
                filePath: {
                  type: 'string',
                  description: '文件路径(相对路径)'
                },
                fileName: {
                  type: 'string',
                  description: '文件名'
                },
                content: {
                  type: 'string',
                  description: '文件内容'
                }
              },
              required: ['filePath', 'fileName', 'content'],
              additionalProperties: false
            }
          }
        ]
      };
    });
    
    // 处理工具调用请求
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;
      console.error(`[MCP SDK] 调用工具: ${name}`, args);
      
      try {
        let result;
        
        switch (name) {
          case 'add':
            // 验证参数
            if (args?.a === undefined || args?.b === undefined) {
              throw new Error('缺少必要参数: a 和 b');
            }
            if (typeof args.a !== 'number' || typeof args.b !== 'number') {
              throw new Error('参数 a 和 b 必须是数字');
            }
            
            result = this.addNumbers(args.a, args.b);
            break;
            
          case 'create_file':
            // 验证参数
            if (!args?.filePath || !args?.fileName || args?.content === undefined) {
              throw new Error('缺少必要参数: filePath, fileName, content');
            }
            if (typeof args.filePath !== 'string' || 
                typeof args.fileName !== 'string' || 
                typeof args.content !== 'string') {
              throw new Error('所有参数必须是字符串');
            }
            
            result = this.createFile(args.filePath, args.fileName, args.content);
            break;
            
          default:
            throw new Error(`未知的工具: ${name}`);
        }
        
        // 返回结果
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(result, null, 2)
            }
          ]
        };
        
      } catch (error) {
        console.error(`[MCP SDK] 工具调用错误: ${error.message}`);
        
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                success: false,
                error: error.message,
                message: `工具调用失败: ${error.message}`
              }, null, 2)
            }
          ],
          isError: true
        };
      }
    });
  }
  
  // 启动服务器
  async start() {
    try {
      console.error('======================================');
      console.error('  MCP 服务器 (使用官方SDK)');
      console.error('  可用工具:');
      console.error('  1. add - 计算两个数字的和');
      console.error('  2. create_file - 创建文件并写入内容');
      console.error('  按 Ctrl+C 退出');
      console.error('======================================\n');
      
      // 使用 stdio 传输
      const transport = new StdioServerTransport();
      
      // 连接传输层
      await this.server.connect(transport);
      
      console.error('[MCP SDK] 服务器已启动,等待连接...');
      
      // 设置优雅关闭
      this.setupGracefulShutdown();
      
    } catch (error) {
      console.error('[MCP SDK] 启动失败:', error);
      process.exit(1);
    }
  }
  
  // 设置优雅关闭
  setupGracefulShutdown() {
    const shutdown = async (signal) => {
      console.error(`\n[MCP SDK] 收到 ${signal} 信号,正在关闭...`);
      
      try {
        await this.server.close();
        console.error('[MCP SDK] 服务器已关闭');
        process.exit(0);
      } catch (error) {
        console.error('[MCP SDK] 关闭时出错:', error);
        process.exit(1);
      }
    };
    
    process.on('SIGINT', () => shutdown('SIGINT'));
    process.on('SIGTERM', () => shutdown('SIGTERM'));
  }
}

// 启动服务器
async function main() {
  const server = new SimpleMCPServerSDK();
  await server.start();
}

// 捕获未处理的Promise异常
process.on('unhandledRejection', (reason, promise) => {
  console.error('[MCP SDK] 未处理的Promise拒绝:', reason);
});

// 捕获未捕获的异常
process.on('uncaughtException', (error) => {
  console.error('[MCP SDK] 未捕获的异常:', error);
  process.exit(1);
});

main().catch((error) => {
  console.error('[MCP SDK] 启动失败:', error);
  process.exit(1);
});

将我们自己写的mcp服务连接到ai模型上去使用

  • 这里以Trae来实验,打开Trae,找到 设置>mcp>t添加
{
  "mcpServers": {
 	... ... /// 每个mcp就是一个配置对象
    "My MCP Server": {
      "command": "node", // 这里是启动命令
      "args": [
        "D:/web/MCP/test.js" // mcp服务脚本的所在目录,绝对路径
      ]
    }
  }
}

在这里插入图片描述

可以看到,模型调用我们添加的mcp服务中的求和函数,mcp服务开发完成后可以将项目上传到npm上,通过bpx的形式将服务添加到模型的工具箱。

  • mcp host和mcp client mcp host是ai本身或者ai工具/编辑器用于调用mcp服务,mcp client是用于管理mcp服务,可以添加多个mcp服务

在这里插入图片描述

  • ai模型于mcp之间的调用关系图

在这里插入图片描述

挂载第三方mcp服务

终结

  • 当前ai模型不能实现的功能可自行编写工具进行辅助
  • mcp服务开发完成后可发行到npm上供团队或开源使用
  • 以上的内容均为个人心得,有错误的地方欢迎指出
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐