MCP 协议实战:给你的 AI 装上「工具手」(含自建 Server 完整代码)

MCP 协议实战:给你的 AI 装上「工具手」(含自建 Server 完整代码)

大模型本身只会说话。它不知道你数据库里有什么、不能读你的私有文档、也发不出一封邮件。要让它真正干活,就得给它接工具。

过去每家 AI 产品都有自己的一套「插件 / Function Calling」格式,接一个工具要为每个平台重写一遍。MCP(Model Context Protocol)就是来终结这件事的——它定义了一套统一的协议,工具写一次,任何支持 MCP 的客户端都能用。

到 2026 年,Claude Desktop、Claude Code、Cursor、Codex 以及一批国产工具都已支持 MCP。这篇讲清楚它是什么、怎么接现成的、以及怎么自己写一个。

一、MCP 到底是什么

一句话:MCP 之于 AI 工具,就像 USB 之于外设

它规定了三方之间怎么通信:

┌─────────────┐   MCP 协议   ┌──────────────┐
│  MCP Client │ ◄──────────► │  MCP Server  │
│ (Claude/    │              │ (你写的工具)  │
│  Cursor...) │              │              │
└─────────────┘              └──────┬───────┘
                                    │
                             ┌──────▼───────┐
                             │ 数据库/API/   │
                             │ 文件系统...   │
                             └──────────────┘

Server 向 Client 暴露三类能力:

类型 作用 例子
Tools AI 可以调用的函数 查数据库、发邮件、创建工单
Resources AI 可以读取的数据 文件内容、API 返回、配置
Prompts 预置的提示词模板 「代码审查」「周报生成」模板

日常用得最多的是 Tools,这篇重点讲它。

二、先用起来:接一个现成的 Server

社区已经有大量现成的 MCP Server。最快的体验方式是接一个文件系统 Server。

以 Claude Desktop 为例,编辑配置文件:

  • macOS:~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows:%APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/you/Documents"
      ]
    }
  }
}

重启客户端,你就能对它说:

读一下 Documents 里的 notes.md,总结成三条要点

它会通过 MCP 调用文件系统工具去读。

几个常用的官方 Server

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxx" }
    },
    "sqlite": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-sqlite", "--db", "./app.db"]
    }
  }
}

配置改完必须重启客户端才生效,这是新手最常卡住的地方。

三、自己写一个:给博客加个「文章查询」工具

现成的不够用,就自己写。下面用 Node.js 写一个能查询 SQLite 文章库的 MCP Server,完整可跑。

1. 初始化

mkdir blog-mcp && cd blog-mcp
npm init -y
npm install @modelcontextprotocol/sdk

package.json 里加上:

{
  "type": "module"
}

2. 写 Server

新建 index.js

#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  ListToolsRequestSchema,
  CallToolRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { DatabaseSync } from 'node:sqlite';

const DB_PATH = process.argv[2] || './data/site.db';
const db = new DatabaseSync(DB_PATH);

const server = new Server(
  { name: 'blog-mcp', version: '1.0.0' },
  { capabilities: { tools: {} } }
);

// ---- 1. 声明有哪些工具 ----
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'search_posts',
      description: '按关键词搜索博客文章,返回标题、日期、摘要',
      inputSchema: {
        type: 'object',
        properties: {
          keyword: { type: 'string', description: '搜索关键词' },
          limit: { type: 'number', description: '返回条数,默认 5' },
        },
        required: ['keyword'],
      },
    },
    {
      name: 'get_post',
      description: '按 slug 获取一篇文章的完整内容',
      inputSchema: {
        type: 'object',
        properties: {
          slug: { type: 'string', description: '文章 slug' },
        },
        required: ['slug'],
      },
    },
  ],
}));

// ---- 2. 实现工具逻辑 ----
server.setRequestHandler(CallToolRequestSchema, async (req) => {
  const { name, arguments: args } = req.params;

  if (name === 'search_posts') {
    const kw = `%${args.keyword}%`;
    const limit = args.limit ?? 5;
    const rows = db
      .prepare(
        `SELECT slug, title, date, excerpt FROM posts
         WHERE title LIKE ? OR content LIKE ?
         ORDER BY date DESC LIMIT ?`
      )
      .all(kw, kw, limit);

    return {
      content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }],
    };
  }

  if (name === 'get_post') {
    const row = db
      .prepare('SELECT * FROM posts WHERE slug = ?')
      .get(args.slug);
    if (!row) {
      return {
        content: [{ type: 'text', text: `未找到 slug=${args.slug} 的文章` }],
        isError: true,
      };
    }
    return {
      content: [{ type: 'text', text: row.content }],
    };
  }

  throw new Error(`未知工具: ${name}`);
});

// ---- 3. 启动 ----
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('blog-mcp server started'); // 注意:日志必须走 stderr

3. 接进客户端

{
  "mcpServers": {
    "blog": {
      "command": "node",
      "args": [
        "/absolute/path/to/blog-mcp/index.js",
        "/absolute/path/to/data/site.db"
      ]
    }
  }
}

重启客户端,然后就可以:

搜一下我博客里关于 SQLite 的文章,把最相关的那篇总结成三段

AI 会自动调 search_posts,拿到结果后再调 get_post,最后给你总结。整个过程你不用写任何胶水代码

四、写 Server 的几条关键规则

规则 1:日志必须走 stderr

stdio 传输模式下,stdout 是协议通道。你 console.log 一句话,整个连接就崩了。调试信息一律用 console.error

规则 2:description 是给 AI 看的,要写清楚

工具的 description 直接决定 AI 会不会正确调用它。

差的写法:

description: '查询文章'

好的写法:

description: '按关键词搜索博客文章。在标题和正文中做模糊匹配,返回 slug、标题、日期、摘要。适合用户想找某个主题的文章时使用。不返回全文,需要全文请再调 get_post。'

把「什么时候用」「返回什么」「有什么限制」都写进去。这是提升调用准确率最有效的一招。

规则 3:错误要返回,不要抛

工具执行失败时,返回带 isError: true 的结果,让 AI 知道发生了什么、可以怎么补救。直接抛异常会让整个调用链断掉。

规则 4:控制返回体积

返回 5000 行数据 = 烧掉一大把 token。加上 limit 参数,默认值给小一点,让 AI 需要更多时自己加。

规则 5:危险操作要有护栏

写操作(删除、修改、发送)尽量做成需要二次确认,或者在 Server 层面加白名单。别让 AI 一句话就能把生产库删了。

五、调试:MCP Inspector

官方提供了调试工具,不用反复重启客户端:

npx @modelcontextprotocol/inspector node index.js ./data/site.db

它会起一个网页界面,你可以:

  • 看到 Server 暴露了哪些工具
  • 手动填参数调用,看返回结果
  • 看完整的协议往返消息

开发流程建议:先用 Inspector 把工具调通,再接进真实客户端。能省掉大量「改一行重启一次」的时间。

六、常见问题排查

Server 没出现在客户端里

  1. 配置文件 JSON 语法有没有错(少个逗号就全废)
  2. 路径必须是绝对路径
  3. 客户端重启了吗(真重启,不是关窗口)
  4. 手动跑一下 node index.js,看有没有直接报错

工具存在但 AI 不调用
description 写得太模糊。改清楚,或者在对话里直接点名:「用 search_posts 工具查一下」。

调用了但返回乱码 / 报错
检查是不是有 console.log 污染了 stdout。

性能很慢
每次调用都新建数据库连接会很慢。连接对象放在模块顶层复用(像上面代码那样)。

七、MCP 值得投入吗

值得的场景

  • 你有私有数据要给 AI 用(内部文档、业务数据库、私有 API)
  • 你希望一次开发多处复用(同一个工具,Claude、Cursor、Codex 都能用)
  • 你要做团队级的 AI 工具(统一分发一套 MCP Server 给全组)

不值得的场景

  • 一次性的小需求 —— 直接让 AI 写个脚本跑一下更快
  • 已经有成熟 SaaS 集成的 —— 别重复造轮子
  • 纯读公开信息 —— 联网搜索就够了

小结

MCP 的意义在于把「AI 能做什么」这个问题,从模型厂商手里交回到了你手里

模型能力是给定的,但它能接触到什么数据、能调用什么系统,取决于你写了什么 Server。在企业场景里,后者往往比前者更决定成败——一个接上了内部 CRM 和工单系统的中等模型,比一个什么都碰不到的顶级模型有用得多。

写一个 MCP Server 的成本,大概就是上面那一百行代码。这个投入产出比,值得试。

MCP 规范与 SDK 仍在演进,具体 API 请以 modelcontextprotocol.io 官方文档为准。本文基于 2026 年 8 月的版本整理。

← 返回博客列表