MCP又是个什么东西?

摘要:了解 MCP 与 LLM 的交互方式

打个小广告,如果希望 claude 可以获取网页信息,可以尝试一下我之前开发的: mcp-web-content-pick。本地服务,隐私无忧,开箱即用。

引言:从孤岛到桥梁

想象一下,你有一个超级智能的外星朋友,他知道关于宇宙的一切知识,但有个致命问题:他被锁在一个没有窗户的房间里,只能通过纸条与外界交流。你想让他帮你查询今天的天气,但他无法上网;你想让他帮你计算复杂的数学问题,但他没有计算器;你想让他帮你控制智能家居,但他无法连接到你的设备。

这就是当前大语言模型(LLMs)的处境。它们拥有惊人的能力,但被限制在自己的”文本世界”里,无法直接获取或操作外部资源。这就是为什么我们需要一种标准化的方式,让这些模型能够与外部世界交互——这正是 Model Context Protocol (MCP) 的使命。

MCP 是什么:给 LLM 装上”超能力插座”

Model Context Protocol(模型上下文协议)是一个开放标准,旨在规范大语言模型(LLM)与外部系统之间的通信。如果把 LLM 比作一个超级大脑,那么 MCP 就像是一个标准化的”插座”,让各种”能力插头”都能轻松接入这个大脑。

MCP 的核心功能

  1. 资源访问:允许 LLM 读取各种数据源,就像给盲人配上了眼睛
  2. 工具使用:让 LLM 能够调用外部功能,就像给瘫痪的人装上了机械臂
  3. 提示模板:定义标准化的交互模式,就像是给外星人准备了地球生存指南

把 MCP 比作城市交通系统可能更容易理解:

  • 资源 (Resources) 是图书馆和数据中心,LLM 可以去那里查阅信息
  • 工具 (Tools) 是专业服务,LLM 可以委托这些服务完成特定任务
  • 提示 (Prompts) 是标准的互动剧本,让交流更高效

为什么需要 MCP:解决”魔法黑盒”问题

现在,你可能会问:“我们已经有了一堆让 LLM 访问外部世界的方法,为什么还需要 MCP?”

想象你在组装一套家具,但每个零件都来自不同制造商,螺丝和螺孔大小各异,说明书语言不同。这就是当前 LLM 集成的状况——每个应用都在发明自己的轮子。

没有 MCP 的世界:

  • 碎片化接口:每个应用都有自己的方式与 LLM 交互,就像每个国家都有自己的电源插座标准
  • 重复开发:开发者不断”重新发明轮子”,浪费时间和资源
  • 复杂集成:将 LLM 与现有系统集成需要大量定制工作
  • 安全隐患:没有标准化的安全措施,容易引入漏洞

有了 MCP 的世界:

  • 统一标准:一套协议连接所有模型和应用,就像 HTTP 连接了所有网站
  • 即插即用:新功能可以轻松添加到任何支持 MCP 的系统
  • 专注核心:开发者可以专注于创造价值,而不是搭建基础设施
  • 安全可控:标准化的安全措施保护模型和数据

如何使用 MCP:从 Hello World 开始

让我们动手创建一个简单的 MCP 服务器,体验一下这个协议的强大之处。我们将使用 TypeScript 和官方的 SDK。

首先,安装 MCP SDK:

npm install @modelcontextprotocol/sdk

基本示例:计算器服务器

这个例子创建了一个简单的 MCP 服务器,提供基本的计算功能:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";  // 用于参数验证

// 创建 MCP 服务器
const server = new Server({ 
  name: "计算器服务", 
  version: "1.0.0" 
});

// 添加加法工具
server.tool(
  "add",  // 工具名称
  { a: z.number(), b: z.number() },  // 参数定义
  async ({ a, b }) => {
    // 执行加法并返回结果
    return { 
      content: [{ 
        type: "text", 
        text: String(a + b) 
      }] 
    };
  }
);

// 添加数学常数资源
server.resource(
  "constants",  // 资源名称
  "math://constants",  // 资源 URI
  async (uri) => {
    // 返回常用数学常数
    return { 
      contents: [{ 
        uri: uri.href, 
        text: JSON.stringify({
          PI: Math.PI,
          E: Math.E,
          GOLDEN_RATIO: 1.618033988749895,
          SQRT2: Math.SQRT2
        }, null, 2)
      }] 
    };
  }
);

// 启动服务器(使用标准输入/输出作为通信通道)
const transport = new StdioServerTransport();
await server.connect(transport);

这段代码做了什么?它创建了一个小型 MCP 服务器,提供了:

  1. 一个加法工具:LLM 可以用它计算两个数字的和
  2. 一个数学常数资源:LLM 可以查询常用的数学常数

使用 MCP 就像是给 LLM 安装了计算器和数学参考手册,让它能够进行之前做不到的计算。

更复杂的例子:天气查询服务

让我们再来看一个更实用的例子——天气查询服务:

import { Server, ResourceTemplate } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// 创建 MCP 服务器
const server = new Server({ 
  name: "天气助手", 
  version: "1.0.0" 
});

// 添加城市天气资源(带参数的动态资源)
server.resource(
  "cityWeather",
  new ResourceTemplate("weather://city/{cityName}", { list: undefined }),
  async (uri, { cityName }) => {
    // 在实际应用中,这里会调用天气 API
    // 这里简化为返回模拟数据
    return { 
      contents: [{ 
        uri: uri.href, 
        text: `${cityName}的天气信息:晴天,温度25°C,湿度60%` 
      }] 
    };
  }
);

// 添加天气预报工具
server.tool(
  "forecast",
  { city: z.string(), days: z.number().min(1).max(7) },
  async ({ city, days }) => {
    // 在实际应用中,这里会调用天气 API 获取预报
    const forecast = Array(days).fill(0).map((_, i) => 
      `第${i+1}天: ${Math.random() > 0.5 ? '晴天' : '多云'}, ${20 + Math.floor(Math.random() * 10)}°C`
    ).join('\n');
    
    return { 
      content: [{ 
        type: "text", 
        text: `${city}未来${days}天的天气预报:\n${forecast}` 
      }] 
    };
  }
);

// 添加天气咨询提示模板
server.prompt(
  "weatherAdvice",
  { city: z.string() },
  ({ city }) => ({
    messages: [{
      role: "user",
      content: { 
        type: "text", 
        text: `请根据${city}的天气情况,给我适合的着装和活动建议。` 
      }
    }]
  })
);

// 启动服务器
const transport = new StdioServerTransport();
await server.connect(transport);

这个更完整的例子展示了 MCP 的三个核心组件:

  1. 资源:城市天气信息,LLM 可以查询特定城市的当前天气
  2. 工具:天气预报功能,LLM 可以获取未来几天的天气预测
  3. 提示模板:天气咨询模板,提供标准化的咨询方式

MCP 的工作原理:幕后解析

你可能好奇 MCP 到底是如何工作的。想象一下餐厅里的点餐系统:顾客(LLM)通过菜单(定义好的接口)向服务员(MCP 协议)点餐,服务员将订单传递给厨房(外部系统),然后将美食(数据/结果)送回给顾客。

MCP 的通信流程

  1. 连接建立:LLM 应用与 MCP 服务器建立连接
  2. 能力发现:应用了解服务器提供哪些资源、工具和提示
  3. 请求执行:应用发送请求(查询资源、调用工具、使用提示)
  4. 响应处理:服务器处理请求并返回结果
  5. 上下文整合:LLM 将获取的信息整合到对话上下文中

MCP 使用 JSON 格式的消息进行通信,类似于这样:

// 工具调用请求
{
  "type": "call_tool_request",
  "id": "req-123",
  "params": {
    "name": "forecast",
    "arguments": {
      "city": "北京",
      "days": 3
    }
  }
}

// 工具调用响应
{
  "type": "call_tool_response",
  "id": "resp-123",
  "request_id": "req-123",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "北京未来3天的天气预报:\n第1天: 晴天, 25°C\n第2天: 多云, 23°C\n第3天: 晴天, 26°C"
      }
    ]
  }
}

通信方式

MCP 支持多种通信方式,适应不同的应用场景:

  1. 标准输入/输出 (stdio):适用于命令行工具和本地应用
  2. HTTP + Server-Sent Events (SSE):适用于远程服务和网络应用
  3. WebSockets:提供双向实时通信

MCP 的进阶应用:解锁更多可能

掌握了基础知识后,让我们探索 MCP 的一些进阶应用场景。

数据库集成

将 MCP 与数据库结合,让 LLM 能够查询和分析数据:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import sqlite3 from "sqlite3";
import { promisify } from "util";

// 创建 MCP 服务器
const server = new Server({ 
  name: "数据库助手", 
  version: "1.0.0" 
});

// 辅助函数:创建数据库连接
const getDb = () => {
  const db = new sqlite3.Database("company.db");
  return {
    all: promisify(db.all.bind(db)),
    close: promisify(db.close.bind(db))
  };
};

// 暴露数据库架构作为资源
server.resource(
  "schema",
  "db://schema",
  async (uri) => {
    const db = getDb();
    try {
      const tables = await db.all(
        "SELECT name, sql FROM sqlite_master WHERE type='table'"
      );
      
      return {
        contents: [{
          uri: uri.href,
          text: tables.map(t => t.sql).join("\n\n")
        }]
      };
    } finally {
      await db.close();
    }
  }
);

// 添加 SQL 查询工具
server.tool(
  "query",
  { sql: z.string() },
  async ({ sql }) => {
    const db = getDb();
    try {
      // 安全检查(实际应用中需要更严格的检查)
      if (sql.toLowerCase().includes("drop") || 
          sql.toLowerCase().includes("delete")) {
        return {
          content: [{ type: "text", text: "禁止执行破坏性操作" }],
          isError: true
        };
      }
      
      const results = await db.all(sql);
      
      return {
        content: [{
          type: "text",
          text: JSON.stringify(results, null, 2)
        }]
      };
    } catch (err) {
      return {
        content: [{ type: "text", text: `错误: ${err.message}` }],
        isError: true
      };
    } finally {
      await db.close();
    }
  }
);

// 启动服务器
const transport = new StdioServerTransport();
await server.connect(transport);

这个例子创建了一个数据库助手,它可以:

  1. 提供数据库表结构信息(资源)
  2. 执行 SQL 查询并返回结果(工具)

这样,LLM 就能够了解数据库结构,并根据用户需求执行查询,实现类似”数据分析师”的功能。

文件系统访问

让 LLM 能够读取和(有限制地)写入文件:

import { Server, ResourceTemplate } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fs from "fs/promises";
import path from "path";

// 创建 MCP 服务器
const server = new Server({ 
  name: "文件助手", 
  version: "1.0.0" 
});

// 设置安全的根目录
const ROOT_DIR = "./safe_files";

// 文件读取资源
server.resource(
  "file",
  new ResourceTemplate("file://{filePath*}", { list: async (uri) => {
    // 列出目录内容
    const dirPath = path.join(ROOT_DIR, uri.pathname.slice(1) || "");
    
    try {
      const entries = await fs.readdir(dirPath, { withFileTypes: true });
      return entries.map(entry => {
        const entryPath = path.join(uri.pathname.slice(1) || "", entry.name);
        return { uri: `file:/${entryPath}` };
      });
    } catch (err) {
      return [];
    }
  }}),
  async (uri, { filePath }) => {
    // 安全检查:确保路径在允许范围内
    const normalizedPath = path.normalize(filePath);
    if (normalizedPath.startsWith("..")) {
      return {
        contents: [{
          uri: uri.href,
          text: "访问被拒绝:路径超出允许范围"
        }]
      };
    }
    
    // 读取文件内容
    const fullPath = path.join(ROOT_DIR, normalizedPath);
    
    try {
      const stat = await fs.stat(fullPath);
      
      if (stat.isDirectory()) {
        // 如果是目录,列出内容
        const entries = await fs.readdir(fullPath);
        return {
          contents: [{
            uri: uri.href,
            text: `目录 ${normalizedPath}:\n${entries.join("\n")}`
          }]
        };
      } else {
        // 读取文件内容
        const content = await fs.readFile(fullPath, "utf-8");
        return {
          contents: [{
            uri: uri.href,
            text: content
          }]
        };
      }
    } catch (err) {
      return {
        contents: [{
          uri: uri.href,
          text: `错误: ${err.message}`
        }]
      };
    }
  }
);

// 文件写入工具
server.tool(
  "writeFile",
  { 
    path: z.string(),
    content: z.string(),
    createDirectories: z.boolean().optional().default(false)
  },
  async ({ path: filePath, content, createDirectories }) => {
    // 安全检查
    const normalizedPath = path.normalize(filePath);
    if (normalizedPath.startsWith("..")) {
      return {
        content: [{ type: "text", text: "访问被拒绝:路径超出允许范围" }],
        isError: true
      };
    }
    
    const fullPath = path.join(ROOT_DIR, normalizedPath);
    
    try {
      // 如果需要创建目录
      if (createDirectories) {
        await fs.mkdir(path.dirname(fullPath), { recursive: true });
      }
      
      // 写入文件
      await fs.writeFile(fullPath, content);
      
      return {
        content: [{
          type: "text",
          text: `文件已成功写入到 ${normalizedPath}`
        }]
      };
    } catch (err) {
      return {
        content: [{ type: "text", text: `错误: ${err.message}` }],
        isError: true
      };
    }
  }
);

// 启动服务器
const transport = new StdioServerTransport();
await server.connect(transport);

这个文件助手允许 LLM:

  1. 浏览和读取文件系统中的文件(资源)
  2. 在安全的范围内创建和写入文件(工具)

这非常适合用于文档管理、代码生成等场景。

MCP 的安全与隐私:守护大门

给 LLM 提供访问外部世界的能力是把双刃剑——方便是一方面,安全风险是另一方面。就像给一个强大的机器人提供了活动能力,我们需要确保它不会因为误解指令而造成伤害。

MCP 的安全特性

  1. 访问控制:服务器可以严格控制 LLM 能够访问哪些资源和工具
  2. 参数验证:使用类型系统(如 Zod)确保输入符合预期
  3. 沙箱执行:在受限环境中执行操作,防止恶意代码
  4. 审计日志:记录所有请求和操作,便于追踪和分析

安全最佳实践

在实现 MCP 服务器时,应遵循以下安全原则:

// 安全最佳实践示例

// 1. 严格验证所有输入
server.tool(
  "secureOperation",
  { 
    input: z.string().min(1).max(100), // 限制输入长度
    options: z.object({                 // 验证选项结构
      mode: z.enum(["safe", "normal"]), // 限制为预定义选项
      depth: z.number().int().min(1).max(10) // 限制数值范围
    })
  },
  async ({ input, options }) => {
    // 实现...
  }
);

// 2. 限制资源访问范围
server.resource(
  "secureResource",
  new ResourceTemplate("secure://{path*}", {}),
  async (uri, { path }) => {
    // 安全检查:路径规范化和验证
    const normalizedPath = normalizePath(path);
    if (!isPathAllowed(normalizedPath)) {
      return { contents: [{ uri: uri.href, text: "访问被拒绝" }] };
    }
    
    // 实现...
  }
);

// 3. 错误处理和信息隐藏
try {
  // 执行操作...
} catch (err) {
  // 隐藏敏感的错误细节
  return {
    content: [{ 
      type: "text", 
      text: "操作失败,请联系管理员" // 不暴露具体错误
    }],
    isError: true
  };
}

// 4. 实现速率限制
const requestCounter = new Map();

function checkRateLimit(clientId) {
  const now = Date.now();
  const recentRequests = requestCounter.get(clientId) || [];
  
  // 清理旧请求记录
  const recentWindow = recentRequests.filter(time => now - time < 60000);
  
  // 检查是否超过限制(例如每分钟 100 个请求)
  if (recentWindow.length >= 100) {
    return false;
  }
  
  // 更新请求记录
  recentWindow.push(now);
  requestCounter.set(clientId, recentWindow);
  return true;
}

MCP 的未来:展望

MCP 仍处于发展的早期阶段,但它的潜力是巨大的。就像 HTTP 成为了网络通信的基础协议,MCP 有望成为 AI 模型与外部世界交互的标准协议。

未来发展方向

  1. 更丰富的媒体类型:支持图像、音频、视频等多模态交互
  2. 更智能的缓存机制:优化性能和响应速度
  3. 分布式协作:多个 MCP 服务器协同工作,形成能力网络
  4. 身份与权限管理:更精细的访问控制和用户身份验证
  5. 跨模型标准化:不同模型之间的无缝协作

实际应用场景

MCP 的应用前景广阔,可以赋能各种场景:

  • 企业知识库:LLM 可以访问公司内部文档、规章制度、产品信息
  • 个人助理:管理日程、电子邮件、文件,执行日常任务
  • 开发辅助:查询代码库、API 文档,生成和测试代码
  • 数据分析:直接查询和分析大型数据集,生成报告和可视化
  • 物联网控制:安全地管理和控制智能家居和其他联网设备

结语:迈向 AI 新纪元

Model Context Protocol 不仅仅是一个技术规范,它代表了 AI 发展的一个重要里程碑。就像互联网协议栈将计算机连接成网络一样,MCP 有潜力将 AI 模型与外部世界无缝连接,创造全新的可能性。

想象一下将来的场景:你的 AI 助手不再只是一个聊天工具,而是能够为你预订机票、分析财务报表、控制智能家居、编写并运行代码、搜索和整理大量文档——所有这些都通过标准化的 MCP 协议实现。

作为开发者,现在正是探索和参与这一革命性技术的最佳时机。无论你是构建 LLM 应用、创建 MCP 服务器,还是贡献协议本身,都有机会在这个新兴领域留下你的印记。

MCP 就像是给 LLM 装上了”超能力插座”,而你,则是创造这些”超能力”的人。未来已来,就看我们如何塑造它。


资源链接

开始你的 MCP 之旅吧,让我们一起解锁 AI 的无限可能!