从 0 手写一个 MCP Server:让 Copilot 调用 Tool 完成四则运算

本文从「MCP 是什么」讲起,然后手写一个只做加减乘除的 MCP Server(Node.js 实现),最后把它接到 VS Code 的 GitHub Copilot Chat 里,用自然语言让 AI 调用我们自己的工具。

一、MCP 是什么

MCP(Model Context Protocol,模型上下文协议) 是一套开放协议,用来标准化 AI 应用外部工具 / 数据 / 服务 之间的连接方式。

一句话理解:

MCP 就像是 AI 世界的 USB-C 接口。以前每接一个工具就要写一套专用适配代码,现在大家都按同一个协议说话,插上就能用。

在 MCP 出现之前,如果你有 N 个 AI 应用 (VS Code、Claude Desktop、Cursor、自研 Agent...)和 M 个工具 (GitHub、数据库、文件系统、内部 API...),理论上需要写 N × M 份适配代码:

text 复制代码
        AI 应用            工具
      ┌─────────┐      ┌─────────┐
      │ VS Code │──────│ GitHub  │
      ├─────────┤      ├─────────┤
      │ Claude  │──────│ MySQL   │
      ├─────────┤      ├─────────┤
      │ Cursor  │──────│ 内部 API │
      └─────────┘      └─────────┘
        N 个               M 个
        连线条数 = N × M

有了 MCP 之后,工具只需按 MCP 协议暴露一次能力,所有支持 MCP 的 AI 应用都能接入,连线条数从 N × M 变成 N + M

text 复制代码
        AI 应用        MCP 协议       MCP Server
      ┌─────────┐                   ┌─────────┐
      │ VS Code │──┐             ┌──│ GitHub  │
      ├─────────┤  │             │  ├─────────┤
      │ Claude  │──┼── MCP ──────┼──│ MySQL   │
      ├─────────┤  │             │  ├─────────┤
      │ Cursor  │──┘             └──│ 内部 API |
      └─────────┘                   └─────────┘
                连线条数 = N + M

MCP 的适用范围

MCP 只专注于「上下文交换」这一件事:

  • 它规定 AI 应用和 MCP Server 之间如何通信
  • 规定 AI 应用该怎么使用大模型;
  • 规定 AI 应用该如何管理拿到的上下文。

二、MCP 的核心概念

2.1 三个参与者

MCP 采用 客户端-服务器(Client-Server)架构

  • MCP Host(宿主):AI 应用本身,比如 VS Code、Claude Desktop、Claude Code。它负责协调管理一个或多个 MCP Client。
  • MCP Client(客户端) :Host 内部为每一个 MCP Server 创建的连接对象,负责维护与某个 Server 的专用连接
  • MCP Server(服务器):真正提供上下文与能力的程序,可以跑在本地,也可以跑在远端。

注意:MCP Server 指的是「提供上下文数据的程序」,和它跑在哪里无关

  • 跑在本机、通过 stdio 通信的,叫 本地 MCP Server
  • 跑在云上、通过 Streamable HTTP 通信的,叫 远程 MCP Server

stdio 是 standard input/output(标准输入/输出)的缩写,也就是键盘输入、控制台输出。可以实现跨进程通信。

2.2 服务器能提供的三类能力

MCP Server 可以对外提供三种基础能力(Primitives):

能力 说明 典型用途
Tools AI 可调用的可执行函数(需要用户授权) 查数据库、调 API、做计算、写文件
Resources 只读的上下文数据源 文件内容、数据库记录、接口返回
Prompts 预置的提示词模板 代码审查模板、周报生成模板

本文的实战只用到 Tools,因为计算器就是典型的「可执行函数」。

2.3 两个协议层

MCP 把协议分成两层,理解这两层基本就理解了 MCP 的全貌:

text 复制代码
MCP
├── 数据层(Data Layer)      → 「传什么」
│   └── 基于 JSON-RPC 2.0:能力发现、版本协商、Tools / Resources / Prompts / 通知
│
└── 传输层(Transport Layer)  → 「怎么传」
    ├── stdio:标准输入输出,进程间通信,本地部署,延迟最低
    └── Streamable HTTP:HTTP POST(可选 SSE 流式),本地或远程部署,支持 OAuth 等鉴权

无论用哪种传输方式,消息本身都统一是 JSON-RPC 2.0 格式。SDK 已经帮我们把这一层封装好了,日常开发基本只需要关心「注册了什么工具」。

JSON-RPC 是一种基于 JSON 格式的远程过程调用协议,让客户端可以通过发送 JSON 请求来调用远程服务器上的方法并获取结果。

2.4 MCP 和 Function Calling 有什么区别

很多人会把两者搞混,实际关系是:

维度 Function Calling MCP
是什么 大模型的一种能力(输出「调用哪个函数」) 一套协议(规定 AI 应用如何连接外部工具/数据)
解决问题 模型如何决定调用工具 工具如何被标准化地接入各种各样的 AI 应用
代码写在哪 写在 AI 应用内部 写在独立的 Server 进程里,可复用
关系 MCP 是"工具的插座",Function Calling 是"模型伸出手去插"的那个动作

简单说:MCP 让工具变成可复用的标准件,Function Calling 让模型有能力去使用这些标准件。

三、环境准备

构建 MCP 需要用到官方提供的 SDK 。本文使用 TypeScript 语言版本的 SDK 。需要 Node.js >= 20

官方 TypeScript/Node SDK(v2)要求 Node.js >= 20

关于包名,需要注意版本差异:

版本 包名 说明
v1 @modelcontextprotocol/sdk 早期单体包,网上大部分教程用的是它
v2 @modelcontextprotocol/server 当前稳定版,实现 2026-07-28 版 MCP 规范

本文使用 v2 的 @modelcontextprotocol/server (服务端包已经拆成独立包,客户端是 @modelcontextprotocol/client)。

四、用 Node.js 实现加减乘除 MCP Server

4.1 初始化项目

bash 复制代码
mkdir mcp-calculator
cd mcp-calculator
npm init -y

# 安装依赖:MCP 服务端 SDK + zod(用于声明工具入参的类型)
npm install @modelcontextprotocol/server zod

修改 package.json,加上 ESM 支持(SDK 是纯 ESM 包):

json 复制代码
{
  "name": "mcp-calculator",
  "version": "1.0.0",
  "type": "module",
  "main": "calculator-server.js",
  "scripts": {
    "start": "node calculator-server.js"
  }
}

4.2 编写 Server 代码

新建 calculator-server.js

js 复制代码
import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { z } from "zod";

// 1. 创建 MCP Server 实例,name/version 会通过 initialize 响应暴露给客户端
const server = new McpServer({ name: "calculator", version: "1.0.0" });

// 2. 抽一个公共的入参 schema:a、b 都是数字
const binaryInput = z.object({
  a: z.number().describe("第一个操作数"),
  b: z.number().describe("第二个操作数"),
});

// 3. 抽一个统一的返回格式:MCP 工具必须返回 content 数组
const text = (t) => ({ content: [{ type: "text", text: String(t) }] });

// 4. 注册加法工具
server.registerTool(
  "add",
  {
    title: "加法",
    description: "计算两个数字的和 a + b",
    inputSchema: binaryInput,
  },
  async ({ a, b }) => text(a + b)
);

// 5. 注册减法工具
server.registerTool(
  "subtract",
  {
    title: "减法",
    description: "计算两个数字的差 a - b",
    inputSchema: binaryInput,
  },
  async ({ a, b }) => text(a - b)
);

// 6. 注册乘法工具
server.registerTool(
  "multiply",
  {
    title: "乘法",
    description: "计算两个数字的积 a * b",
    inputSchema: binaryInput,
  },
  async ({ a, b }) => text(a * b)
);

// 7. 注册除法工具(需要处理除零)
server.registerTool(
  "divide",
  {
    title: "除法",
    description: "计算两个数字的商 a / b,b 不能为 0",
    inputSchema: binaryInput,
  },
  async ({ a, b }) => {
    if (b === 0) {
      return {
        content: [{ type: "text", text: "错误:除数不能为 0" }],
        isError: true,
      };
    }
    return text(a / b);
  }
);

// 8. 使用 stdio 传输启动服务
const transport = new StdioServerTransport();
await server.connect(transport);

// 注意:stdio 场景下日志必须走 stderr,绝不能 console.log
console.error("Calculator MCP Server running on stdio");

4.3 关键点说明

1)registerTool(name, config, callback) 的签名

js 复制代码
server.registerTool(
  "工具名(模型看到的函数名)",
  {
    title: "给用户看的标题",
    description: "给模型看的说明 ------ 这段文字直接决定模型会不会用、用对不用对",
    inputSchema: z.object({ ... }),   // 入参类型,SDK 会转成 JSON Schema
    outputSchema: z.object({ ... }),  // 可选:声明返回值结构
  },
  async (args, ctx) => {
    return { content: [{ type: "text", text: "..." }] };
  },
);

2)description 比代码更重要

模型只能通过 name + description + inputSchema 来判断该不该调用这个工具。所以:

  • description: "计算" ------ 模型不知道算加还是减;
  • description: "计算两个数字的商 a / b,b 不能为 0" ------ 意图清晰,参数含义明确。

3)stdio 场景下绝对不能 console.log

stdio 传输是用 标准输出 来传 JSON-RPC 消息的。你 console.log 一句日志,就会把 JSON-RPC 报文污染掉,连接直接断开。

js 复制代码
console.log("server started"); // ❌ 会破坏 stdio 通信
console.error("server started"); // ✅ 写 stderr,安全

4)错误要用 isError 标记,而不是抛异常

抛异常会让整个请求失败,而 isError: true 会把错误信息作为工具结果交给模型,模型可以据此自我修正(比如「除数不能为 0」它会解释给用户听)。

4.4 运行一下

bash 复制代码
node calculator-server.js

因为 stdio 服务在等 JSON-RPC 报文,终端里看起来「卡住不动」是正常的。要真正验证,用官方调试工具 MCP Inspector:

bash 复制代码
npx @modelcontextprotocol/inspector node $(pwd)/calculator-server.js

打开它给出的地址后,可以看到 Web 界面:

  1. Connect
  2. 切到 Tools 标签,点 List Tools ,能看到 add / subtract / multiply / divide 四个工具;
  3. 输入 a=6, b=7 运行 multiply,返回 42

五、在 VS Code GitHub Copilot 中使用该 Server

步骤 1:配置 mcp.json

VS Code 通过 mcp.json 管理 MCP Server,有两个位置:

  • 工作区级.vscode/mcp.json(推荐,可以提交到 git 共享给团队)
  • 用户级 :命令面板执行 MCP: Open User Configuration(所有工作区可用)

mcp-calculator 目录下新建 .vscode/mcp.json

json 复制代码
{
  "servers": {
    "calculator": {
      "type": "stdio",
      "command": "node",
      "args": ["${workspaceFolder}/calculator-server.js"]
    }
  }
}

字段说明(stdio 类型):

字段 必填 说明
type stdio(本地)/ httpsse(远程)
command 启动命令,必须在 PATH 中或写全路径
args 传给命令的参数数组

${workspaceFolder} 要输入真实的文件目录路径。

步骤 2:启动 Server

配置保存后,mcp.json 里对应 server 上方会出现 启动 的 CodeLens,点击 启动

启动后,VS Code 会连接进程并发现该 Server 暴露的工具

也可以用命令面板:MCP: List Servers → 选中 calculator → 启动服务器。

步骤 3:在 Copilot Chat 中启用工具

  1. 打开 Chat 视图(⌃⌘I);
  2. 把对话模式切换到 Agent
  3. 点击输入框的 Configure Tools(配置工具) 按钮,展开后能看到 calculator 这个 MCP Server 下的四个工具;
  1. 勾选(或全部勾选)add / subtract / multiply / divide

步骤 4:用自然语言验证

在 Agent 模式下依次输入:

text 复制代码
帮我算一下 (128 * 37) - 456 等于多少?

上述 mcp server 的源码已上传 github 仓库:github.com/Panda-plus5...

六、小结

  1. MCP 是一套协议 ,解决的是 AI 应用与外部工具/数据之间的标准化连接问题,把 N × M 的适配爆炸变成 N + M
  2. Host / Client / Server 三个角色分工明确;Server 提供 Tools / Resources / Prompts 三类能力;传输层可选 stdio (本地)或 Streamable HTTP(远程)。
  3. 用 v2 的 @modelcontextprotocol/server 写一个 MCP Server 只需要三步:建实例 → registerTool 注册工具 → connect(StdioServerTransport)
  4. 想要模型「用对」工具,重点在于打磨 工具名 + description + inputSchema,而不是工具内部逻辑多复杂。
  5. 在 VS Code 中接入只需一份 .vscode/mcp.json:配置 → 启动 → Agent 模式下勾选工具 → 用自然语言提问。

七、参考链接

相关推荐
尾善爱看海10 小时前
Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)
前端·javascript·vue.js·面试·vue
FungLeo14 小时前
成为全栈·Node 后端篇·点赞系统:幂等点赞与计数原子增减
node.js·成为全栈·点赞系统·幂等点赞·计数原子增减
李明卫杭州15 小时前
React 复合事件系统对比解析
前端·javascript·react.js
sarasuki16 小时前
MCP 客户端接入:一行注册一个 GitHub 工具
人工智能·agent·mcp
故作春风16 小时前
elpis-core 核心从入门到理解
后端·架构·node.js
万敏16 小时前
Vue3 全栈实战第九周:Node.js + Express 后端从零搭建实战记录
vue.js·node.js·全栈
LEE16 小时前
前端转型全栈 01:数据建模,前端最大的盲区
前端·javascript·后端
雪芽蓝域zzs17 小时前
第四十七节:驾驶舱大屏 ECharts 图表集成
前端·javascript·vue.js
梦醒沉醉18 小时前
7、表达日期和时间(前朝遗老Date)
javascript