1. 引言:为什么需要MCP协议?

  • AI Agent的“工具困境”:传统Agent开发中,工具集成繁琐、协议不统一、扩展性差。
  • MCP(Model Context Protocol)的诞生:由Anthropic提出,旨在标准化AI模型与外部工具/数据源之间的通信。
  • 本文目标:带领读者从零开始,基于MCP协议搭建一个可扩展、易维护的AI Agent工具链。

2. MCP协议核心概念解析

  • 2.1 协议定位:连接AI模型(客户端)与工具/数据源(服务器)的标准化桥梁。
  • 2.2 核心组件
    • Server(服务器):封装具体能力(如搜索、数据库查询、API调用),向客户端宣告可用工具(Tools)和资源(Resources)。
    • Client(客户端):通常是AI模型或应用,发现并调用服务器提供的工具和资源。
    • Transport(传输层):支持Stdio(标准输入输出)和SSE(Server-Sent Events)两种通信方式。
  • 2.3 关键能力
    • Tools(工具):可执行的操作,包含名称、描述、参数schema。
    • Resources(资源):可读取的静态或动态数据源(如文件、数据库表视图)。
    • Prompts(提示词模板):可复用的提示词片段,供客户端组合使用。

3. 开发环境与工具准备

  • 3.1 环境要求:Node.js (>=18) / Python 3.10+,代码编辑器。
  • 3.2 核心工具库
    • 官方SDK@modelcontextprotocol/sdk (Node.js) / mcp (Python)。
    • 开发辅助:MCP Inspector(调试工具)、Claude Desktop(客户端测试环境)。
  • 3.3 项目初始化:创建项目目录,安装依赖。

4. 实战一:构建你的第一个MCP服务器(天气查询)

  • 4.1 项目结构设计

  • 4.2 实现Server基类:初始化、能力声明、请求路由。

  • 4.3 定义并实现Tool:创建一个get_weather工具,定义输入参数(城市名),集成第三方天气API。

    下面是一个完整的 Node.js 版 get_weather 工具实现示例:

    // weatherTool.js
    const { Tool } = require('@modelcontextprotocol/sdk/server/tools');
    const { z } = require('zod'); // 用于参数验证
    
    /**
     * 天气查询工具
     * 该工具模拟调用第三方天气API,返回指定城市的天气信息
     */
    class GetWeatherTool extends Tool {
      constructor() {
        super({
          name: 'get_weather',
          description: '查询指定城市的当前天气情况',
          inputSchema: {
            type: 'object',
            properties: {
              city: {
                type: 'string',
                description: '城市名称,例如:北京、上海、New York',
              },
              units: {
                type: 'string',
                enum: ['metric', 'imperial'],
                description: '温度单位:metric(摄氏度) 或 imperial(华氏度)',
                default: 'metric',
              },
            },
            required: ['city'],
          },
        });
      }
    
      /**
       * 执行天气查询
       * @param {Object} params - 工具参数
       * @param {string} params.city - 城市名称
       * @param {string} [params.units='metric'] - 温度单位
       * @returns {Promise<Object>} 天气信息
       */
      async execute(params) {
        const { city, units = 'metric' } = params;
        
        // 1. 参数验证
        if (!city || typeof city !== 'string') {
          throw new Error('城市名称不能为空且必须是字符串');
        }
    
        // 2. 模拟调用天气API(实际项目中替换为真实的API调用)
        // 这里使用模拟数据,实际开发中可集成OpenWeatherMap、和风天气等API
        const mockWeatherData = {
          '北京': { temp: 22, condition: '晴朗', humidity: 45 },
          '上海': { temp: 25, condition: '多云', humidity: 65 },
          '广州': { temp: 28, condition: '阵雨', humidity: 80 },
          'New York': { temp: 68, condition: 'Partly Cloudy', humidity: 60 },
        };
    
        // 3. 查找城市天气数据
        const cityKey = Object.keys(mockWeatherData).find(
          key => key.toLowerCase() === city.toLowerCase()
        );
    
        if (!cityKey) {
          throw new Error(`未找到城市 "${city}" 的天气数据`);
        }
    
        const weather = mockWeatherData[cityKey];
        
        // 4. 单位转换(模拟)
        let temperature = weather.temp;
        let unitSymbol = '°C';
        
        if (units === 'imperial') {
          temperature = Math.round((temperature * 9/5) + 32);
          unitSymbol = '°F';
        }
    
        // 5. 返回结构化结果
        return {
          city: cityKey,
          temperature: {
            value: temperature,
            unit: unitSymbol,
            original_unit: units === 'metric' ? 'celsius' : 'fahrenheit',
          },
          condition: weather.condition,
          humidity: `${weather.humidity}%`,
          last_updated: new Date().toISOString(),
          source: 'mock-weather-api',
          units: units,
          // 附加建议信息
          recommendations: this._generateRecommendations(weather.condition, temperature),
        };
      }
    
      /**
       * 根据天气条件生成建议
       * @private
       */
      _generateRecommendations(condition, temperature) {
        const recommendations = [];
        
        if (condition.includes('雨')) {
          recommendations.push('建议携带雨具');
        }
        
        if (temperature > 30) {
          recommendations.push('天气炎热,注意防暑降温');
        } else if (temperature < 10) {
          recommendations.push('天气寒冷,注意保暖');
        }
        
        if (condition.includes('晴朗') || condition.includes('Sunny')) {
          recommendations.push('适合户外活动,请注意防晒');
        }
        
        return recommendations.length > 0 ? recommendations : ['天气适宜,享受美好的一天'];
      }
    }
    
    // 导出工具实例
    module.exports = new GetWeatherTool();
    

    代码说明:

    1. 工具类定义:继承自 Tool 基类,定义工具名称、描述和参数schema
    2. 参数验证:使用Zod schema定义参数类型和约束,确保输入安全
    3. API集成:示例中使用模拟数据,实际项目可替换为真实天气API调用
    4. 错误处理:对无效城市名、参数错误等情况进行友好提示
    5. 结构化返回:返回包含温度、天气状况、湿度等信息的结构化JSON
    6. 单位转换:支持公制(摄氏度)和英制(华氏度)单位
    7. 附加建议:根据天气条件自动生成实用建议

    在MCP服务器中注册该工具:

    // server.js
    const { Server } = require('@modelcontextprotocol/sdk/server');
    const weatherTool = require('./weatherTool');
    
    const server = new Server(
      'weather-server',
      { version: '1.0.0' }
    );
    
    // 注册天气查询工具
    server.setRequestHandler('tools/list', async () => ({
      tools: [weatherTool.getDefinition()],
    }));
    
    server.setRequestHandler('tools/call', async (request) => {
      if (request.params.name === 'get_weather') {
        return await weatherTool.execute(request.params.arguments);
      }
      throw new Error(`Unknown tool: ${request.params.name}`);
    });
    
    // 启动服务器
    server.start().catch(console.error);
    
  • 4.4 运行与测试:通过Stdio启动服务器,使用MCP Inspector或Claude Desktop进行工具调用测试。

5. 实战二:扩展服务器能力(资源与提示词)

  • 5.1 添加Resource:暴露一个本地配置文件或API文档作为可读资源。

    下面是一个读取本地配置文件(config.json)并暴露为MCP资源的完整示例:

    // configResource.js
    const { Resource } = require('@modelcontextprotocol/sdk/server/resources');
    const fs = require('fs').promises;
    const path = require('path');
    
    /**
     * 配置文件资源
     * 将本地配置文件暴露为只读资源,客户端可以读取配置信息
     */
    class ConfigResource extends Resource {
      constructor(configPath) {
        super({
          uri: 'file:///config.json',
          name: 'server-config',
          description: '服务器配置文件,包含API密钥、服务端点等设置',
          mimeType: 'application/json',
        });
        this.configPath = configPath;
      }
    
      /**
       * 读取配置文件内容
       * @returns {Promise<Object>} 配置对象
       */
      async read() {
        try {
          const configContent = await fs.readFile(this.configPath, 'utf-8');
          const config = JSON.parse(configContent);
          
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(config, null, 2),
              },
            ],
            metadata: {
              lastModified: new Date().toISOString(),
              fileSize: Buffer.byteLength(configContent),
              format: 'json',
            },
          };
        } catch (error) {
          throw new Error(`读取配置文件失败: ${error.message}`);
        }
      }
    
      /**
       * 获取资源定义(用于注册到服务器)
       */
      getDefinition() {
        return {
          uri: this.uri,
          name: this.name,
          description: this.description,
          mimeType: this.mimeType,
        };
      }
    }
    
    // 示例配置文件 config.json
    const exampleConfig = {
      "server": {
        "name": "weather-mcp-server",
        "version": "1.0.0",
        "port": 3000
      },
      "weatherApi": {
        "provider": "openweathermap",
        "apiKey": "YOUR_API_KEY_HERE",
        "baseUrl": "https://api.openweathermap.org/data/2.5",
        "cacheDuration": 300 // 缓存时间(秒)
      },
      "logging": {
        "level": "info",
        "file": "logs/server.log"
      },
      "features": {
        "enableCaching": true,
        "enableMetrics": true,
        "maxConcurrentRequests": 10
      }
    };
    
    // 创建配置文件(开发环境)
    async function createExampleConfig() {
      const configPath = path.join(__dirname, 'config.json');
      await fs.writeFile(configPath, JSON.stringify(exampleConfig, null, 2));
      console.log(`示例配置文件已创建: ${configPath}`);
      return configPath;
    }
    
    // 导出资源实例
    module.exports = {
      ConfigResource,
      createExampleConfig,
      exampleConfig,
    };
    

    在MCP服务器中注册资源:

    // server.js - 资源注册部分
    const { ConfigResource, createExampleConfig } = require('./configResource');
    
    // 创建配置文件(首次运行)
    const configPath = await createExampleConfig();
    
    // 创建资源实例
    const configResource = new ConfigResource(configPath);
    
    // 注册资源到服务器
    server.setRequestHandler('resources/list', async () => ({
      resources: [configResource.getDefinition()],
    }));
    
    server.setRequestHandler('resources/read', async (request) => {
      if (request.params.uri === configResource.uri) {
        return await configResource.read();
      }
      throw new Error(`资源不存在: ${request.params.uri}`);
    });
    

    资源使用场景:

    • 客户端可以读取服务器配置,了解可用功能和限制
    • 暴露API文档、使用说明等静态内容
    • 提供动态数据源(如数据库查询结果、实时监控数据)
  • 5.2 添加Prompt:创建一个用于优化天气查询结果的提示词模板。

    下面是一个优化天气查询结果的提示词模板示例,包含变量占位符和用法说明:

    // weatherPrompt.js
    const { Prompt } = require('@modelcontextprotocol/sdk/server/prompts');
    
    /**
     * 天气查询优化提示词
     * 该提示词模板帮助客户端更好地理解和展示天气查询结果
     */
    class WeatherOptimizationPrompt extends Prompt {
      constructor() {
        super({
          name: 'weather_result_optimizer',
          description: '优化天气查询结果的提示词模板,生成更友好的天气报告',
          arguments: [
            {
              name: 'city',
              description: '城市名称',
              required: true,
            },
            {
              name: 'temperature',
              description: '温度数值',
              required: true,
            },
            {
              name: 'unit',
              description: '温度单位(°C或°F)',
              required: true,
            },
            {
              name: 'condition',
              description: '天气状况(如晴朗、多云、下雨等)',
              required: true,
            },
            {
              name: 'humidity',
              description: '湿度百分比',
              required: true,
            },
            {
              name: 'recommendations',
              description: '天气建议数组',
              required: false,
            },
            {
              name: 'time_of_day',
              description: '查询时间(早晨、中午、傍晚、夜晚)',
              required: false,
            },
          ],
        });
      }
    
      /**
       * 生成优化后的提示词
       * @param {Object} args - 提示词参数
       * @returns {string} 格式化后的提示词
       */
      generate(args) {
        const {
          city,
          temperature,
          unit,
          condition,
          humidity,
          recommendations = [],
          time_of_day = '当前',
        } = args;
    
        // 构建基础提示词
        let prompt = `## ${city}天气报告\n\n`;
        prompt += `**查询时间**:${time_of_day}\n\n`;
        prompt += `### 天气概况\n`;
        prompt += `- **温度**:${temperature}${unit}\n`;
        prompt += `- **天气状况**:${condition}\n`;
        prompt += `- **湿度**:${humidity}\n\n`;
    
        // 添加天气解读
        prompt += `### 天气解读\n`;
        
        // 根据温度给出解读
        const tempNum = parseFloat(temperature);
        if (unit === '°C') {
          if (tempNum < 0) {
            prompt += `❄️ 气温极低,请注意防寒保暖,路面可能结冰。\n`;
          } else if (tempNum < 10) {
            prompt += `🧥 天气较冷,建议穿着外套。\n`;
          } else if (tempNum < 25) {
            prompt += `😊 气温舒适,适合户外活动。\n`;
          } else if (tempNum < 35) {
            prompt += `☀️ 天气较热,注意补充水分。\n`;
          } else {
            prompt += `🔥 高温天气,避免长时间户外活动。\n`;
          }
        }
    
        // 根据天气状况给出解读
        if (condition.includes('雨')) {
          prompt += `🌧️ 有降雨,出行请携带雨具。\n`;
        } else if (condition.includes('雪')) {
          prompt += `❄️ 有降雪,注意道路安全。\n`;
        } else if (condition.includes('晴') || condition.includes('Sunny')) {
          prompt += `☀️ 天气晴朗,适合户外活动,请注意防晒。\n`;
        } else if (condition.includes('云') || condition.includes('Cloudy')) {
          prompt += `☁️ 多云天气,紫外线较弱。\n`;
        } else if (condition.includes('雾')) {
          prompt += `🌫️ 有雾,能见度较低,出行请注意安全。\n`;
        }
    
        // 添加湿度解读
        const humidityNum = parseInt(humidity);
        if (humidityNum > 80) {
          prompt += `💦 湿度较高,体感可能闷热。\n`;
        } else if (humidityNum < 30) {
          prompt += `🏜️ 湿度较低,注意皮肤保湿。\n`;
        }
    
        // 添加自定义建议
        if (recommendations.length > 0) {
          prompt += `\n### 出行建议\n`;
          recommendations.forEach((rec, index) => {
            prompt += `${index + 1}. ${rec}\n`;
          });
        }
    
        // 添加穿衣建议
        prompt += `\n### 穿衣指南\n`;
        if (tempNum < 5) {
          prompt += `👕 内搭保暖内衣 + 毛衣 + 羽绒服/厚外套\n`;
          prompt += `👖 加厚裤子 + 保暖鞋袜\n`;
        } else if (tempNum < 15) {
          prompt += `👕 长袖T恤 + 外套/夹克\n`;
          prompt += `👖 长裤 + 普通鞋袜\n`;
        } else if (tempNum < 25) {
          prompt += `👕 短袖/长袖T恤 + 薄外套\n`;
          prompt += `👖 长裤/休闲裤\n`;
        } else {
          prompt += `👕 短袖/背心\n`;
          prompt += `👖 短裤/薄长裤\n`;
        }
    
        // 添加活动建议
        prompt += `\n### 活动推荐\n`;
        if (condition.includes('雨') || condition.includes('雪')) {
          prompt += `🏠 室内活动:阅读、看电影、室内健身\n`;
          prompt += `☕ 适合去咖啡馆、图书馆等室内场所\n`;
        } else {
          prompt += `🚶 户外活动:散步、慢跑、骑行\n`;
          prompt += `🌳 适合公园游玩、郊游、户外运动\n`;
        }
    
        prompt += `\n---\n*数据更新时间:{{current_time}}*\n`;
        prompt += `*提示:天气数据仅供参考,实际天气可能有所变化。*`;
    
        return prompt;
      }
    
      /**
       * 获取提示词定义(用于注册到服务器)
       */
      getDefinition() {
        return {
          name: this.name,
          description: this.description,
          arguments: this.arguments,
        };
      }
    }
    
    // 导出提示词实例
    module.exports = new WeatherOptimizationPrompt();
    

    在MCP服务器中注册提示词:

    // server.js - 提示词注册部分
    const weatherPrompt = require('./weatherPrompt');
    
    // 注册提示词到服务器
    server.setRequestHandler('prompts/list', async () => ({
      prompts: [weatherPrompt.getDefinition()],
    }));
    
    server.setRequestHandler('prompts/get', async (request) => {
      if (request.params.name === 'weather_result_optimizer') {
        const args = request.params.arguments || {};
        // 添加当前时间变量
        args.current_time = new Date().toLocaleString('zh-CN');
        
        const promptText = weatherPrompt.generate(args);
        return {
          prompt: {
            messages: [
              {
                role: 'user',
                content: promptText,
              },
            ],
          },
        };
      }
      throw new Error(`提示词不存在: ${request.params.name}`);
    });
    

    提示词使用示例(客户端调用):

    // 客户端调用提示词
    async function useWeatherPrompt(client, weatherData) {
      const promptArgs = {
        city: weatherData.city,
        temperature: weatherData.temperature.value,
        unit: weatherData.temperature.unit,
        condition: weatherData.condition,
        humidity: weatherData.humidity,
        recommendations: weatherData.recommendations,
        time_of_day: '下午',
      };
    
      const promptResult = await client.request('prompts/get', {
        name: 'weather_result_optimizer',
        arguments: promptArgs,
      });
    
      console.log('优化后的天气报告:');
      console.log(promptResult.prompt.messages[0].content);
      
      // 可以将优化后的报告发送给AI模型进一步处理
      return promptResult;
    }
    

    提示词模板特点:

    1. 结构化参数:定义清晰的输入参数和类型约束
    2. 动态生成:根据天气数据动态生成个性化的天气报告
    3. 多维度解读:从温度、湿度、天气状况等多个维度提供解读
    4. 实用建议:包含穿衣指南、活动推荐等实用信息
    5. 可扩展性:可以轻松添加更多解读维度和建议类型
  • 5.3 能力组合演示:展示Client如何联合使用Tool、Resource和Prompt完成复杂任务。

    // 客户端组合使用示例
    async function completeWeatherTask(client) {
      // 1. 读取服务器配置资源
      const config = await client.request('resources/read', {
        uri: 'file:///config.json',
      });
      console.log('服务器配置:', config.content[0].text);
    
      // 2. 调用天气查询工具
      const weatherResult = await client.request('tools/call', {
        name: 'get_weather',
        arguments: { city: '北京', units: 'metric' },
      });
    
      // 3. 使用提示词优化天气结果
      const optimizedReport = await client.request('prompts/get', {
        name: 'weather_result_optimizer',
        arguments: {
          city: weatherResult.city,
          temperature: weatherResult.temperature.value,
          unit: weatherResult.temperature.unit,
          condition: weatherResult.condition,
          humidity: weatherResult.humidity,
          recommendations: weatherResult.recommendations,
          time_of_day: '当前',
        },
      });
    
      // 4. 将优化后的报告发送给AI模型进行进一步处理
      const finalResponse = await aiModel.generateResponse(
        `基于以下天气报告,为用户提供出行建议:\n\n${optimizedReport.prompt.messages[0].content}`
      );
    
      return {
        rawWeather: weatherResult,
        optimizedReport: optimizedReport.prompt.messages[0].content,
        finalAdvice: finalResponse,
      };
    }
    

    组合使用优势:

    1. 资源提供上下文:客户端先读取配置,了解服务器能力
    2. 工具执行操作:调用具体的天气查询功能
    3. 提示词优化展示:将原始数据转化为用户友好的报告
    4. 端到端自动化:整个流程可以完全自动化,无需人工干预

6. 实战三:打造自定义MCP客户端(Agent核心)

  • 6.1 客户端职责:服务发现、工具列表获取、调用决策与执行。
  • 6.2 连接多个服务器:实现一个客户端同时连接天气、日历、邮件等多个MCP服务器。
  • 6.3 工具调用逻辑:解析模型请求,匹配并调用正确的工具,处理返回结果。
  • 6.4 错误处理与降级:网络异常、工具调用

7. 实战四:集成与构建完整Agent工具链

  • 7.1 架构设计:绘制基于MCP的AI Agent系统架构图(Mermaid)。

    下面是一个基于 MCP 协议的 AI Agent 系统架构图,展示了客户端、多个 MCP 服务器以及外部数据源之间的交互关系:

    外部 API/数据源

    MCP 服务器层

    AI Agent 层

    用户界面/应用

    决策与编排引擎

    自然语言请求
    e.g. '明天北京天气如何?'

    格式化响应
    e.g. '明天北京晴朗,22°C'

    MCP 协议
    (Stdio/SSE)

    MCP 协议

    MCP 协议

    MCP 协议

    HTTP 请求

    API 调用

    API 调用

    读取配置

    读写数据

    读取模板

    JSON 响应

    JSON 响应

    发送状态

    配置/数据

    日历数据

    邮件模板

    结构化天气数据

    日历事件列表

    发送状态

    其他结果

    其他服务服务器

    Tool: 其他工具

    Resource: 其他资源

    邮件通知服务器

    Tool: send_email

    Resource: 邮件模板

    Prompt: 邮件内容生成

    日历管理服务器

    Tool: create_event

    Tool: list_events

    Resource: 日历数据

    天气查询服务器

    Tool: get_weather

    Resource: 天气配置

    Prompt: 天气报告优化

    用户/应用

    MCP 客户端 (Agent 核心)

    意图理解

    工具选择

    流程编排

    天气 API
    (OpenWeatherMap)

    日历 API
    (Google Calendar)

    邮件服务 API
    (SMTP/SendGrid)

    数据库/文件系统

    架构图说明:

    1. 用户界面/应用层:用户通过自然语言与系统交互,提出需求如"明天北京天气如何?安排会议并邮件通知"。

    2. AI Agent 层(MCP 客户端)

      • MCP 客户端:作为 Agent 核心,负责连接和管理多个 MCP 服务器。
      • 决策与编排引擎
        • 意图理解:解析用户请求,识别需要调用的工具和服务。
        • 工具选择:根据意图从可用工具中选择最合适的组合。
        • 流程编排:确定工具调用顺序和依赖关系。
    3. MCP 服务器层

      • 天气查询服务器:提供天气查询工具、配置资源和天气报告优化提示词。
      • 日历管理服务器:提供日历事件创建、查询工具和日历数据资源。
      • 邮件通知服务器:提供邮件发送工具、邮件模板资源和内容生成提示词。
      • 其他服务服务器:可扩展的其他 MCP 服务器,如文件操作、数据库查询等。
    4. 外部 API/数据源层

      • 天气 API:如 OpenWeatherMap、和风天气等第三方服务。
      • 日历 API:如 Google Calendar、Outlook 等日历服务。
      • 邮件服务 API:如 SMTP、SendGrid、AWS SES 等邮件发送服务。
      • 数据库/文件系统:本地或远程的数据存储,用于配置、模板等资源。
    5. 数据流向

      • 请求流:用户请求 → Agent → MCP 服务器 → 外部 API。
      • 响应流:外部 API → MCP 服务器 → Agent → 用户。
      • 协议:Agent 与 MCP 服务器之间使用 MCP 协议(Stdio/SSE),服务器与外部 API 之间使用相应的 API 协议(HTTP/REST 等)。
    6. 关键设计特点

      • 松耦合架构:每个 MCP 服务器独立开发、部署和扩展。
      • 标准化接口:所有服务器通过统一的 MCP 协议暴露能力。
      • 能力组合:Agent 可以灵活组合不同服务器的工具、资源和提示词。
      • 可扩展性:新增服务器只需实现 MCP 协议,即可被现有 Agent 发现和使用。
  • 7.2 工具链编排:如何将多个独立的MCP服务器(数据查询、代码执行、内容生成)通过一个智能客户端(Agent)协同工作。

  • 7.3 实战案例:旅行规划Agent

    • 服务器1:航班/酒店查询工具。
    • 服务器2:本地景点与天气资源。
    • 服务器3:邮件/DingTalk消息发送工具。
    • 客户端(Agent):理解用户需求,按顺序调用工具,生成旅行计划并通知。
      序调用工具,生成旅行计划并通知。

8. 高级主题与最佳实践

  • 8.1 性能优化:工具调用的异步处理、结果缓存、连接池管理。
  • 8.2 安全考量:工具权限控制、输入验证、敏感信息过滤。
  • 8.3 测试策略:单元测试(工具逻辑)、集成测试(客户端-服务器通信)、E2E测试(完整工作流)。
  • 8.4 部署与监控:容器化部署、日志记录、健康检查与指标收集。

9. 总结与展望

  • MCP协议的价值总结:标准化带来的开发效率提升、生态互操作性。
  • 当前生态与工具:介绍社区中优秀的开源MCP服务器(如文件系统、数据库、Git)。
  • 未来展望:协议演进方向、在AI应用开发中的更广泛应用。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐