MCP - Implementing a simple mcp server
Record how to implement a simple two-number sum server, client joint debugging and key information description
最近MCP一词处处都出现在我的视野里,看了不少关于MCP的文章,脑海里总结出来的就是个应用间的一种协议充当着类似USB接口的角色。但究竟是什么样子,还是实践一下才知道。翻阅了好几遍 MCP 文档,终于整明白了MCP是怎么个事, 同时自己实现一个最最最简单的MCP Server。本篇文章带着大家快速理解 MCP。
什么是 MCP ?
文档中有着相关的定义,用简单通俗的理解就是:
让 客户端和服务端通过标准输入/输出(或者可流式传输的 HTTP),以 JSON-RPC 协议进行通讯。
服务端(MCP Server)暴露能力(tools、resources 等, 客户端基于这些能力完成任务。
什么是标准输入/输出?
stdio 是程序与计算机系统之间进行基本数据交互的三个标准通信通道:输入(stdin)、输出(stdout)和错误(stderr)。
观看概念有点不太清楚,写过 node 服务的同学再熟悉不过了这不就是 process.stdio,当然每一种语言都有对应的标准输入输出写,本文以 node 的 stdio 展开。
什么是 JSON-RPC 协议
JSON-RPC 是一种使用 JSON 编码消息的 RPC 协议。本质还是 JSON, 只不过遵循特定的格式。举一个例子:
//请求
{
jsonrpc: "2.0";
id: string | number;
method: string;
params?: {
[key: string]: unknown;
};
}
//响应
{
jsonrpc: "2.0";
id: string | number;
result?: {
[key: string]: unknown;
}
error?: {
code: number;
message: string;
data?: unknown;
}
}
这就是我们客户端和服务端之间的通信格式。
更多细节请阅读 JSON-RPC 2.0 规范
最小可用 MCP 服务器的组成
可以参考官方提供的生命周期图
一个基础的 MCP Server 有以下几部分组成。
初始化相关
initialize
:客户端告知协议版本/能力;服务端返回自身能力与信息。initialized
:通知,表示客户端已完成初始化(通知无id
,无需响应)。
JSON-RPC 请求的 method 的值就是 initialize,initialized。后面讲到的方法同理。
json
{
jsonrpc: "2.0";
id: 1;
method: 'initialize';
params?: {
[key: string]: unknown;
};
}
工具相关
tools/list
:返回可用工具列表(名称、描述、输入参数 schema)。tools/call
:执行工具,返回规范化的result.content
。- 其他常见方法
ping
:健康检查。shutdown
/exit
:退出(可选但常用)。
协议注意点
- 遵循 JSON-RPC 2.0;只有带
id
的请求才需要响应。 - 错误响应必须只含
error
,不可同时含result
。
两数求和的 MCP Server 的实现
在了解了标准输入输出,JSON-RPC 和最小 MCP Server 构成后,我们就可以实现我们的 MCP Server 了。
目录与文件
新建项目, 创建两个文件:
index.js
:主循环(stdin 读消息 → 分发 → stdout 回应)
utils.js
:工具定义与实现(本文示例为 sum
)
交互主循环:读、分发、写
核心是监听 stdin
,解析为请求,根据 method
分发,构造 JSON-RPC 响应并写回 stdout
。
javascript
// index.js
// MCP服务器状态
let serverState = {
initialized: false,
clientInfo: null
};
// 处理MCP请求
process.stdin.on('data', data => {
try {
const req = JSON.parse(data);
// 处理initialized通知 - 这是通知,不需要响应
if (req.method === 'initialized') {
return;
}
// 处理exit通知 - 直接退出,不发送响应
if (req.method === 'exit') {
process.exit(0);
}
// 只有有id的请求才需要响应
if (req.id === undefined || req.id === null) {
return;
}
let res = {
jsonrpc: '2.0',
id: req.id,
result: null
};
// 处理initialize请求
if (req.method === 'initialize') {
}
// 处理tools/list请求
else if (req.method === 'tools/list') {
}
// 处理tools/call请求
else if (req.method === 'tools/call') {
}
// 处理ping请求
else if (req.method === 'ping') {
}
// 处理shutdown请求
else if (req.method === 'shutdown') {
}
// 未知方法
else {
}
// 发送响应
process.stdout.write(JSON.stringify(res) + '
');
} catch (error) {
// 处理JSON解析错误
const errorRes = {
jsonrpc: '2.0',
id: null,
error: {
code: -32700,
message: "解析错误: " + error.message
}
};
process.stdout.write(JSON.stringify(errorRes) + '
');
}
});
我们根据 最小可用 MCP 服务器的组成 我们搭出了 server 的架子。
serverState 来维护当前连接状态和客户端信息。
process.stdin.on 监听并读取客户端的数据。
process.stdout.write 将服务端生成的 JSON-RPC 写入,响应客户端。
我们只要实现依据不同的 req.method 就好了。
initialize实现
js
let res = {
jsonrpc: '2.0',
id: req.id,
result: null
};
if (req.method === 'initialize') {
if (serverState.initialized) {
res.error = {
code: -32602,
message: "服务器已经初始化"
};
delete res.result;
} else {
serverState.clientInfo = req.params;
serverState.initialized = true;
res.result = {
protocolVersion: "2024-11-05",
capabilities: {
tools: {
listChanged: false
}
},
serverInfo: {
name: "MCP求和服务器",
version: "1.0.0"
}
};
}
}
if 块
我们先判断了连接是否已经初始化过了,如果初始化过了,返回给客户端 error, error code 以及后面的 error code 请参考JSON-RPC 2.0 规范
同时删除了 result (错误响应里不能包含 result 字段)。
else 块
将客户端请求的参数保存起来, 一般的 initialize 请求如下:
JSON
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"elicitation": {}
},
"clientInfo": {
"name": "example-client",
"version": "1.0.0"
}
}
}
protocolVersion 表示客户端希望使用日期戳为 2024-11-05 的协议规范版本, 不同的协议版本可
能会不兼容。
capabilities 用于描述客户端支持哪些功能和特性。客户端通过这个对象告诉服务器:“我具备这些能力,你可以向我发送这些类型的通知或请求,我可以处理这种格式的参数。例子中 elicitation 功能对象({}) 内部是空的,通常意味着客户端支持该功能的所有默认行为。
clientInfo 提供一些客户端的基本信息。
然后设置result, 包含的服务器端信息基本与请求的信息格式一致,重点说一下 listChanged
js
capabilities: {
tools: {
listChanged: false
}
}
listChanged 是一种通知机制,用于告知服务器:当服务器所管理的“工具列表”发生变化时,是否需要主动通知客户端。如果设置为true,那么当服务器端tools(后面会将) 列表变动时,服务器端会向客户端发送一个 listChanged 的通知。客户端收到通知后通过调用 tools/list 获取最新的 tool list来与服务器端保持同步。更多信息请移步 lifecycle
组装好jsonrpc后将其用标准输出流返回客户端。这样我们的 initialize 部分就实现了, 初始化完成之后,客户端就可能发起 tools/list 请求 查看一下 服务端都有什么工具供我调遣。
tools/list 实现
在实现这部分的逻辑之前我们需要了解一下什么tools。
一个工具需要“元信息 + 实现”两部分。
tools/list
会把tools
返回给客户端,客户端据此生成 UI。tools/call
按name
分发到实现(这里是sum
)。
工具定义包括:
name
:工具的唯一标识符
title
:用于显示目的的可选的、人类可读的工具名称。
description
:人类可读的功能描述
inputSchema
:定义预期参数的 JSON Schema
outputSchema
:可选 JSON Schema 定义预期输出结构
annotations
:描述工具行为的可选属性
光看这些属性可能有点懵。结合代码理解就容易的多
js
//utils.js
export const sum = ({ a, b }) => {
return a + b;
}
// MCP工具定义
export const tools = [
{
name: "sum",
description: "calculate the sum of two numbers",
inputSchema: {
type: "object",
properties: {
a: {
type: "number",
description: "the first number"
},
b: {
type: "number",
description: "the second number"
}
},
required: ["a", "b"]
}
}
];
在tools数组里我们增加了一个对象,而对象的属性满足以上我们列出的属性。整个对象基本以自然语言的形式描述了tool的功能,参数以及参数的类型。通俗理解就是告诉客户端这个工具可以做什么,要怎么用。更多细节移步 tools
而在主逻辑里我们只需要将tools数组(tools 列出了 MCP Server 的所有能力)返回给客户端就可以了。
js
import { tools } from './utils.js';
else if (req.method === 'tools/list') {
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
delete res.result;
} else {
res.result = {
tools: tools
};
}
}
客户端收到了服务端的信息后,知道了服务端给它提供了一个两数求和的工具。那就来用一下吧,于是乎按照tool的格式填好信息发起了tools/call, 希望利用tool的功能帮我计算出一个结果。
tools/call 实现
js
import { sum } from './utils.js';
else if (req.method === 'tools/call') {
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
delete res.result;
} else {
const { name, arguments: args } = req.params;
if (name === 'sum') {
try {
// 参数验证
if (typeof args.a !== 'number' || typeof args.b !== 'number') {
throw new Error('参数a和b必须是数字');
}
const result = sum(args);
res.result = {
content: [
{
type: "text",
text: `计算结果: ${args.a} + ${args.b} = ${result}`
}
]
};
} catch (error) {
res.error = {
code: -32603,
message: "内部错误: " + error.message
};
delete res.result;
}
} else {
res.error = {
code: -32601,
message: "工具未找到: " + name
};
delete res.result;
}
}
}
通过结构从req.params 中获取请求调用的工具名称name以及参数arguments,我们需要对参数做校验(一般采用ts + zod),这很重要!校验通过后,调用我们的sum函数执行结果采用test格式加入到result中。这样客户端便通过调用我们的工具实现了两数求和。
ping请求实现
ping请求是客户端用来检测连接状态的。这里我们返回一个空对象就可以,没有采用返回体result.content: [{ type, text }]
这样的结构是因为在做测试的时候报错了,后面会讲到。
js
else if (req.method === 'ping') {
res.result = {};
}
shutdown 实现
js
else if (req.method === 'shutdown') {
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
delete res.result;
} else {
res.result = null;
// 延迟退出,让客户端收到响应
setTimeout(() => {
process.exit(0);
}, 100);
}
}
需要注意的是我们需要等到客户端接收到响应后再关掉我们的node进程。
完整代码
js
import { tools, sum } from './utils.js';
// MCP服务器状态
let serverState = {
initialized: false,
clientInfo: null
};
// 处理MCP请求
process.stdin.on('data', data => {
try {
const req = JSON.parse(data);
// 处理initialized通知 - 这是通知,不需要响应
if (req.method === 'initialized') {
return;
}
// 处理exit通知 - 直接退出,不发送响应
if (req.method === 'exit') {
process.exit(0);
}
// 只有有id的请求才需要响应
if (req.id === undefined || req.id === null) {
return;
}
let res = {
jsonrpc: '2.0',
id: req.id,
result: null
};
// 处理initialize请求
if (req.method === 'initialize') {
if (serverState.initialized) {
res.error = {
code: -32602,
message: "服务器已经初始化"
};
delete res.result;
} else {
serverState.clientInfo = req.params;
serverState.initialized = true;
res.result = {
protocolVersion: "2024-11-05",
capabilities: {
tools: {
listChanged: false
},
resources: {
subscribe: false
}
},
serverInfo: {
name: "MCP求和服务器",
version: "1.0.0"
}
};
}
}
// 处理tools/list请求
else if (req.method === 'tools/list') {
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
delete res.result;
} else {
res.result = {
tools: tools
};
}
}
// 处理tools/call请求
else if (req.method === 'tools/call') {
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
delete res.result;
} else {
const { name, arguments: args } = req.params;
if (name === 'sum') {
try {
// 参数验证
if (typeof args.a !== 'number' || typeof args.b !== 'number') {
throw new Error('参数a和b必须是数字');
}
const result = sum(args);
res.result = {
content: [
{
type: "text",
text: `计算结果: ${args.a} + ${args.b} = ${result}`
}
]
};
} catch (error) {
res.error = {
code: -32603,
message: "内部错误: " + error.message
};
delete res.result;
}
} else {
res.error = {
code: -32601,
message: "工具未找到: " + name
};
delete res.result;
}
}
}
// 处理ping请求
else if (req.method === 'ping') {
res.result = {};
}
// 处理shutdown请求
else if (req.method === 'shutdown') {
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
delete res.result;
} else {
res.result = null;
// 延迟退出,让客户端收到响应
setTimeout(() => {
process.exit(0);
}, 100);
}
}
// 未知方法
else {
res.error = {
code: -32601,
message: "方法未找到: " + req.method
};
delete res.result;
}
// 发送响应
process.stdout.write(JSON.stringify(res) + '
');
} catch (error) {
// 处理JSON解析错误
console.error('JSON解析错误:', error.message);
}
});
// 输出启动信息
console.error('MCP求和服务器已启动,等待初始化...');
值得注意的一点是,服务器启动的log是用 error 打印的,这样写的目的是方便调试。
总结一下以上实现要点:
- 通知(
initialized
、exit
)不需要响应。 - 只有带
id
的请求才写回响应。 - 错误响应只能有
error
,不能同时带result
。 tools/call
的成功返回体必须是result.content: [{ type, text }]
这样的结构。
至此,我们的两数求和MCP Server就实现了。接下来是测试环节。
端到端调用流程
既然我们已经准备好了MCP Server, 那我们还需要一个Client来调用我们的服务,这样才能完成完整的测试流程。
那我们的客户端到底应该是怎样的呢?
支持标准输入输出,实现 JSON-RPC 的程序理论上都可以做客户端。以下分别介绍三个客户端:
命令行工具
命令行工具支持标准的输入输出流,只要我们按照JSON—RPC的格式进行通信,那么就可以与我们的MCP服务通信。
打开一个新的命令行窗口,cd到我们的项目目录下,执行node index.js。启动我们的服务后,在命令行输入我们的初始化数据, 需要注意的是在命令行输入不要有换行。
json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"elicitation": {}
},
"clientInfo": {
"name": "teminal-client",
"version": "1.0.0"
}
}
}
可以看到成功获取到了服务器的初始化响应,并获取到了server info.
接着测试我们的tool/list, 继续输入tool/list请求
json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
同样的我们获取到了服务端的tools响应。
最后我们测试tools/call, 继续命令行输入
json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "sum",
"arguments": {
"a": 3,
"b": 4
}
}
}
我们成功调用了server tool 中sum的功能帮我实现了两数求和。
当然大家可能觉得这样测试不太正规。接着我们来使用官方提供的测试工具 MCP Inspector 。
MCP Inspector
首先一九四打开命令行窗口,cd到我们的项目目录下,执行以下命令会帮我们创建一个MCP Inspector Client
bash
npx @modelcontextprotocol/inspector
MCP Inspector界面如下
在连接我们的MCP Server之前我们需要做一些配置。
- Transport Type 选择 STDIO ,因为我们是本地的传输。
Command 填写node 因为我们的服务是基于node实现的
Arguments 就是我们的启动文件路径。由于我们的Inspector本身就在项目的路径下启动的,这里我们直接添index.js就好了。
配置完成后点击connect, Inspector 会用node 执行Arguments下的脚本,相当于本地执行了 node index.js.
发现左下角打印出了服务器启动的log, 这里解释了为什么用console.error便于调试。连接成功后,Inspector 头部菜单栏展示出了一系列工具,这里就重点讲tools 和 ping。
如图操作:
实际上我们在图中已经实现了对 tool/list 和 tool/call的测试。可以说是很优雅了。
点击ping 按钮 可以收到服务端的响应,注意面板的下方有History记录,记录了客户端的所有操作行为。如果把ping的result 采用 result.content: [{ type, text }]
形式,你会发现客户端报错了,说明 Inspector 不希望以这种标准输出的格式作为ping的返回结果。感兴趣可以试试。
至此,关于Inspector的基本使用以及测试也告一段落了。接下来我们要介绍一个大人物 claude code desktop
claude desktop
首先我们需要下载安装 claude desktop, 还需要注册一个账号(需要点魔法,不懂可以cc我)。
安装登录好我们的claude 桌面端后,我们需要选择setting, 点击developer, 选择edit Config.
由于我已经添加过了,这里列出了我添加的server。
编辑 claude_desktop_config_fixed.json
文件。
json
{
"mcpServers": {
"sum-server": {
"command": "node",
"args": ["a:\\b\\c\\index.js"],
"env": {}
}
}
}
看配置有没有觉得很熟悉? 这我们刚刚不才给MCP Inspector 配置过吗。
需要注意一点的是,server的命名格式 example-server。
保存更改后,我们关掉 claude desktop并重新启动。
发现我们已经成功添加了我们的sum-server, 但是是无法勾选的。
检查log文件,发现原来是MCP服务器在处理输入时遇到了以下问题:
-
JSON解析错误。我们需要改造一下原来的代码,使得我们的服务支持多行JSON数据和数据分片。
-
'notifications/cancelled'
,'prompts/list'
,'resources/list'
请求没有响应。
说明claude 默认发送了这些请求,但我们服务端没有对应的处理,我们需要在服务端处理对应的请求。
处理JSON解析问题:
js
process.stdin.on('data', data => {
try {
// 将新数据添加到缓冲区
inputBuffer += data.toString();
// 处理缓冲区中的所有完整JSON消息
let lines = inputBuffer.split('
');
// 保留最后一个可能不完整的行
inputBuffer = lines.pop() || '';
// 处理所有完整的行
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine) {
processMessage(trimmedLine);
}
}
} catch (error) {
console.error('处理stdin数据时出错:', error);
inputBuffer = '';
}
});
我们通过使用了一个inputBuffer 来处理分片的JSON数据按行分割数据,确保每次处理完整的JSON消息。
新请求的处理:
js
case 'prompts/list':
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
} else {
res.result = {
prompts: []
};
}
break;
case 'resources/list':
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
} else {
res.result = {
resources: []
};
}
break;
由于我们mcp不支持prompts和resourses, 而且在返回tools信息的时候已经配置了prompts和 resources的相关信息,这里我们直接返回空数组。
改造后的完整代码
js
import { tools, sum } from './utils.js';
// MCP服务器状态
let serverState = {
initialized: false,
clientInfo: null
};
let inputBuffer = '';
function sendResponse(response) {
const responseStr = JSON.stringify(response);
process.stdout.write(responseStr + '
');
}
function processMessage(messageStr) {
try {
const req = JSON.parse(messageStr);
if (req.method === 'notifications/initialized' ||
req.method === 'notifications/cancelled') {
console.error(`收到通知: ${req.method}`);
return;
}
if (req.method === 'exit') {
console.error('收到exit通知,退出...');
process.exit(0);
}
if (req.id === undefined || req.id === null) {
console.error('请求没有id,跳过');
return;
}
let res = {
jsonrpc: '2.0',
id: req.id
};
// 处理不同的方法
switch (req.method) {
case 'initialize':
if (!serverState.initialized) {
serverState.clientInfo = req.params;
serverState.initialized = true;
console.error('服务器已初始化');
}
res.result = {
protocolVersion: "2024-11-05",
capabilities: {
tools: {
listChanged: false
},
prompts: {
listChanged: false
},
resources: {
subscribe: false,
listChanged: false
}
},
serverInfo: {
name: "MCP求和服务器",
version: "1.0.0"
}
};
break;
case 'tools/list':
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
} else {
res.result = {
tools: tools
};
}
break;
case 'prompts/list':
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
} else {
res.result = {
prompts: []
};
}
break;
case 'resources/list':
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
} else {
res.result = {
resources: []
};
}
break;
case 'tools/call':
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
} else {
const { name, arguments: args } = req.params;
if (name === 'sum') {
try {
if (typeof args.a !== 'number' || typeof args.b !== 'number') {
throw new Error('参数a和b必须是数字');
}
const result = sum(args);
res.result = {
content: [
{
type: "text",
text: `计算结果: ${args.a} + ${args.b} = ${result}`
}
]
};
} catch (error) {
res.error = {
code: -32603,
message: "内部错误: " + error.message
};
}
} else {
res.error = {
code: -32601,
message: "工具未找到: " + name
};
}
}
break;
case 'ping':
res.result = {};
break;
case 'shutdown':
if (!serverState.initialized) {
res.error = {
code: -32002,
message: "服务器未初始化"
};
} else {
res.result = null;
sendResponse(res);
setTimeout(() => {
console.error('关闭服务器...');
process.exit(0);
}, 100);
return;
}
break;
default:
res.error = {
code: -32601,
message: "方法未找到: " + req.method
};
break;
}
sendResponse(res);
} catch (error) {
// 尝试解析部分请求以获取ID
try {
const partialReq = JSON.parse(messageStr);
if (partialReq.id !== undefined) {
sendResponse({
jsonrpc: '2.0',
id: partialReq.id,
error: {
code: -32700,
message: "解析错误: " + error.message
}
});
}
} catch (e) {
console.error('无法发送错误响应:', e);
}
}
}
// 处理stdin数据 - 使用缓冲区处理分片数据
process.stdin.on('data', data => {
try {
// 将新数据添加到缓冲区
inputBuffer += data.toString();
// 处理缓冲区中的所有完整JSON消息
let lines = inputBuffer.split('
');
// 保留最后一个可能不完整的行
inputBuffer = lines.pop() || '';
// 处理所有完整的行
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine) {
processMessage(trimmedLine);
}
}
} catch (error) {
console.error('处理stdin数据时出错:', error);
inputBuffer = '';
}
});
process.stdin.setEncoding('utf8');
process.on('SIGINT', () => {
console.error('收到SIGINT信号,正在关闭服务器...');
process.exit(0);
});
process.on('SIGTERM', () => {
console.error('收到SIGTERM信号,正在关闭服务器...');
process.exit(0);
});
process.on('uncaughtException', (error) => {
console.error('未捕获的异常:', error);
setTimeout(() => {
process.exit(1);
}, 1000);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的Promise拒绝:', reason);
setTimeout(() => {
process.exit(1);
}, 1000);
});
process.stdout.setDefaultEncoding('utf8');
console.error('MCP求和服务器已启动,等待初始化...');
console.error('Process ID:', process.pid);
console.error('Node.js版本:', process.version);
保存文件后重启claude,我们可以看到sum-server被默认选中了。同时点击sum-server还可以看到我们server提供的tool工具。
接着我们就可以借助mcp-server的能力做一些事情。如图
可以看到在claude中我们成功使用了sum-server计算两数求和的能力。
常见坑与排查
总结了我们实现sum-server时遇到的一些问题:
- 对“通知”误回响应 → 导致校验失败。
- 错误响应同时带了
result
→ 违反 JSON-RPC 规范。 tools/call
返回体未包含result.content
→ 客户端无法展示内容。- 未初始化就调用工具 → 应返回「服务器未初始化」的标准错误。
- Windows 路径在配置中要使用双反斜杠转义。
- 支持多行JSON数据和数据分片。
- 更多请求的支持如
resouses/list
Summary
我们实现了简单Sum-server的开发和与客户端的联调的整个流程。
总结最小 MCP Server 的核心:
- 1.要严格遵循 JSON-RPC/MCP 响应结构。
- 2.清晰声明能力(
tools/list
)并标准化返回结果(tools/call
→result.content
)。
阅读过官方文档的同学可能会看到官方提供了@modelcontextprotocol/sdk
, sdk 帮我们封装好了很多功能,便于我们做复杂的MCP Server开发。
