AI 终于有了"插件系统"——MCP 完全指南

0 阅读5分钟

写给想搞懂 MCP 的开发者,不讲废话,直接上干货


先说一个让人头疼的问题

你有没有遇到过这种情况:

你想让 AI 帮你查 GitHub 上的 issue,它说:“我没有访问 GitHub 的权限。”

你想让 AI 帮你操作数据库,它说:“我无法连接到您的数据库。”

你想让 AI 读取本地文件,它说:“我没有文件系统访问权限。”

每次都要自己写一堆胶水代码,把 AI 和各种工具连起来。写完这个项目,下个项目又得重新写一遍。

这就是 MCP 要解决的问题。


MCP 到底是什么?

MCP 全称是 Model Context Protocol(模型上下文协议),是 Anthropic 在 2024 年底推出的一个开放标准。

一句话解释:

MCP 是 AI 的"USB 接口"——定义了一套标准,让任何 AI 应用都能用同一种方式接入任何外部工具和数据源。

在 MCP 出现之前,每个 AI 应用想接入外部工具,都要自己造轮子,写各种适配代码。就像早年电脑接外设,每个厂商接口都不一样,乱得一塌糊涂。

MCP 就是那个统一的 USB 标准——工具只需要实现一次,所有支持 MCP 的 AI 应用都能直接用。


MCP 的三个核心角色

理解 MCP,先搞清楚三个角色:

┌─────────────────────────────────────────┐
│           MCP Host(宿主)               │
│   Claude Desktop / Cursor / 你的应用    │
└──────────────────┬──────────────────────┘
                   │ MCP 协议
        ┌──────────┴──────────┐
        ↓                     ↓
┌──────────────┐    ┌──────────────────┐
│  MCP Server  │    │   MCP Server     │
│  (文件系统)  │    │   (数据库)       │
└──────────────┘    └──────────────────┘
  • MCP Host(宿主) :使用 AI 的应用,比如 Claude Desktop、Cursor、你自己写的 AI 应用
  • MCP Server(服务端) :提供具体能力的服务,比如文件读写、数据库查询、GitHub 操作
  • MCP Client(客户端) :内嵌在 Host 里,负责和 Server 通信

你只需要写一个 MCP Server,所有支持 MCP 的 Host 都能直接用你的工具。


MCP Server 能提供什么?

MCP Server 可以暴露三种东西:

🔧 Tools(工具)

AI 可以调用的函数,类似 Function Calling。

查询数据库、发送邮件、操作文件、调用 API...

📄 Resources(资源)

AI 可以读取的数据,类似文件系统。

本地文件、数据库记录、API 返回的数据...

💬 Prompts(提示词模板)

预定义的提示词,可以复用。

代码审查模板、文档生成模板、分析报告模板...

来一个真实的代码示例(JavaScript)

用 @modelcontextprotocol/sdk 写一个最简单的 MCP Server:

安装依赖

npm install @modelcontextprotocol/sdk

写一个天气查询 MCP Server

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

// 创建 MCP Server
const server = new McpServer({
  name: 'weather-server',
  version: '1.0.0',
});

// 注册一个 Tool:查询天气
server.tool(
  'get_weather',
  '获取指定城市的实时天气信息',
  {
    city: z.string().describe('城市名称,例如:北京、上海、广州'),
  },
  async ({ city }) => {
    // 实际项目里这里调用真实天气 API
    const weatherData = {
      北京: { temperature: 22, condition: '晴', humidity: 40 },
      上海: { temperature: 25, condition: '多云', humidity: 65 },
      广州: { temperature: 30, condition: '阵雨', humidity: 80 },
    };

    const weather = weatherData[city];
    if (!weather) {
      return {
        content: [{ type: 'text', text: `未找到城市:${city}` }],
      };
    }

    return {
      content: [
        {
          type: 'text',
          text: `${city}天气:${weather.condition},气温 ${weather.temperature}°C,湿度 ${weather.humidity}%`,
        },
      ],
    };
  }
);

// 注册一个 Resource:读取本地文件
server.resource(
  'local-file',
  'file://{path}',
  async (uri) => {
    const path = uri.pathname;
    const fs = await import('fs/promises');

    try {
      const content = await fs.readFile(path, 'utf-8');
      return {
        contents: [{ uri: uri.href, mimeType: 'text/plain', text: content }],
      };
    } catch (error) {
      throw new Error(`读取文件失败:${error.message}`);
    }
  }
);

// 启动 Server(通过标准输入输出通信)
const transport = new StdioServerTransport();
await server.connect(transport);

console.error('Weather MCP Server 已启动');

就这么几十行,一个 MCP Server 就写好了。

在 Claude Desktop 里使用

在 Claude Desktop 的配置文件里加上你的 Server:

// ~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": ["/path/to/your/weather-server.js"]
    }
  }
}

重启 Claude Desktop,它就能直接调用你的天气查询工具了。


一个更完整的例子:数据库查询 Server

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import Database from 'better-sqlite3';

const server = new McpServer({
  name: 'sqlite-server',
  version: '1.0.0',
});

const db = new Database('./mydata.db');

// Tool:执行 SQL 查询
server.tool(
  'query_database',
  '执行 SQL 查询并返回结果,只支持 SELECT 语句',
  {
    sql: z.string().describe('要执行的 SQL 查询语句,只允许 SELECT'),
  },
  async ({ sql }) => {
    // 安全检查:只允许 SELECT
    if (!sql.trim().toUpperCase().startsWith('SELECT')) {
      return {
        content: [{ type: 'text', text: '错误:只允许执行 SELECT 查询' }],
        isError: true,
      };
    }

    try {
      const rows = db.prepare(sql).all();
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(rows, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [{ type: 'text', text: `查询失败:${error.message}` }],
        isError: true,
      };
    }
  }
);

// Tool:获取表结构
server.tool(
  'get_table_schema',
  '获取数据库中指定表的结构信息',
  {
    table_name: z.string().describe('表名'),
  },
  async ({ table_name }) => {
    try {
      const schema = db
        .prepare(`PRAGMA table_info(${table_name})`)
        .all();

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(schema, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [{ type: 'text', text: `获取表结构失败:${error.message}` }],
        isError: true,
      };
    }
  }
);

// Resource:暴露数据库表列表
server.resource(
  'database-tables',
  'db://tables',
  async () => {
    const tables = db
      .prepare(`SELECT name FROM sqlite_master WHERE type='table'`)
      .all();

    return {
      contents: [
        {
          uri: 'db://tables',
          mimeType: 'application/json',
          text: JSON.stringify(tables, null, 2),
        },
      ],
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

配置好之后,你就可以直接对 Claude 说:“帮我查一下 users 表里上个月注册的用户有多少”,它会自己去查数据库,给你真实的结果。


MCP 的通信方式

MCP Server 和 Host 之间有两种通信方式:

Stdio(标准输入输出)

适合本地工具,Server 作为子进程运行:

const transport = new StdioServerTransport();
await server.connect(transport);

HTTP + SSE(服务端推送)

适合远程服务,Server 作为独立的 HTTP 服务运行:

import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express from 'express';

const app = express();
const transport = new SSEServerTransport('/messages', res);
await server.connect(transport);

app.listen(3000);

MCP vs Function Calling,傻傻分不清楚?

很多人会问:MCP 和 Function Calling 有什么区别?

对比项MCPFunction Calling
定位标准协议,跨应用复用单次对话内的工具调用
复用性写一次,所有 Host 都能用每个应用自己实现
适合场景工具生态、持久化服务单个应用内的功能扩展
部署方式独立进程 / 远程服务内嵌在应用代码里
标准化有统一协议规范各家 API 格式不同

简单来说:

  • Function Calling 是"这个应用里我能调用哪些函数"
  • MCP 是"我写了一个工具,所有 AI 应用都能用"

两者不是替代关系,很多 MCP Server 内部就是用 Function Calling 的思路实现的。


现成的 MCP Server,拿来就用

不想自己写?社区已经有大量现成的 MCP Server:

Server功能地址
@modelcontextprotocol/server-filesystem本地文件读写官方
@modelcontextprotocol/server-githubGitHub 操作官方
@modelcontextprotocol/server-postgresPostgreSQL 查询官方
@modelcontextprotocol/server-brave-searchBrave 搜索官方
@modelcontextprotocol/server-slackSlack 消息官方
mcp-server-sqliteSQLite 查询社区

安装官方文件系统 Server:

npx @modelcontextprotocol/server-filesystem /your/directory

加到 Claude Desktop 配置:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "@modelcontextprotocol/server-filesystem",
        "/Users/yourname/Documents"
      ]
    }
  }
}

配置完重启,Claude 就能直接读写你的文档目录了。


几个容易踩的坑

坑 1:忘了处理错误返回

MCP Tool 的返回里有个 isError 字段,出错时要设置为 true,这样 AI 才知道调用失败了,会尝试其他方式。

// ❌ 错误做法:直接 throw
throw new Error('查询失败');

// ✅ 正确做法:返回错误信息
return {
  content: [{ type: 'text', text: `查询失败:${error.message}` }],
  isError: true,
};

坑 2:Tool 描述不够清晰

和 Function Calling 一样,Tool 的描述越详细,AI 越知道什么时候该用它。

坑 3:没有做权限控制

MCP Server 有访问本地资源的能力,一定要做好权限控制,别让 AI 随便删文件、执行危险 SQL。

// 数据库操作只允许 SELECT
if (!sql.trim().toUpperCase().startsWith('SELECT')) {
  return { content: [{ type: 'text', text: '只允许 SELECT 查询' }], isError: true };
}

// 文件操作限制在指定目录
if (!filePath.startsWith(ALLOWED_DIR)) {
  return { content: [{ type: 'text', text: '不允许访问该目录' }], isError: true };
}

坑 4:Stdio 模式下别用 console.log

Stdio 模式下,标准输出是用来和 Host 通信的,你的日志要用 console.error 输出到标准错误,否则会破坏通信协议。

// ❌ 会破坏通信
console.log('Server 启动了');

// ✅ 正确做法
console.error('Server 启动了');

进阶:给你的 AI 应用接入 MCP

不只是 Claude Desktop,你自己写的 AI 应用也可以作为 MCP Host:

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

// 连接到 MCP Server
const transport = new StdioClientTransport({
  command: 'node',
  args: ['./weather-server.js'],
});

const client = new Client({ name: 'my-app', version: '1.0.0' }, {});
await client.connect(transport);

// 列出所有可用工具
const { tools } = await client.listTools();
console.log('可用工具:', tools.map(t => t.name));

// 调用工具
const result = await client.callTool({
  name: 'get_weather',
  arguments: { city: '北京' },
});

console.log('结果:', result.content[0].text);

把这些工具列表传给 OpenAI 或其他大模型,就实现了完整的 MCP Host。


总结

MCP 的核心思想就一句话:

定义一套标准,让 AI 工具的开发和使用彻底解耦——工具只需要写一次,所有 AI 应用都能用。

它解决的是 AI 生态里的"碎片化"问题,让工具开发者和 AI 应用开发者都能专注自己的事。

对于开发者来说,现在是入场 MCP 生态的好时机:

  1. 用现成的 Server:直接用官方和社区的 MCP Server,快速扩展 AI 能力
  2. 写自己的 Server:把你的业务系统包装成 MCP Server,让 AI 直接操作
  3. 做 MCP Host:在你的 AI 应用里支持 MCP,接入整个工具生态

推荐学习路径:

  1. 在 Claude Desktop 里配置一个官方 MCP Server(比如文件系统)
  2. 跑通上面的天气查询示例
  3. 把你自己项目里的一个接口包装成 MCP Server

跑通了,你就真正理解 MCP 了。