构建STDIO MCP服务器
一份简明的端到端指南,用于在STDIO上实现模型上下文协议(MCP)服务器,重点介绍JSON-RPC 2.0消息框架和强大的生命周期管理。包括可运行的Node.js/TypeScript示例。
______________________________________________________________________
你将建造什么
- 通过STDIN/STDOUT与JSON-RPC 2.0通信的最小MCP服务器
- 使用适当的消息框架
Content-Length标头 - 能力和工具注册
- 请求/响应和通知处理
- 优雅的关机和健康检查
先决条件
- Node.js≥18
- npm或pnpm
协议概述(快速)
MCP服务器通常使用JSON-RPC 2.0,并使用类似HTTP的标头进行成帧。 典型的消息如下:
Content-Type: application/json
Content-Length: 123\r\n
\r\n
{"jsonrpc":"2.0","id":1,"method":"ping","params":{}}关键规则:
- 每条JSON消息前面都有标头(至少
Content-Length) - 空白行(
\r\n\r\n)将标头与JSON正文分开 - 车身长度匹配
Content-Length以字节为单位 - 双向:双方都可以发送请求和通知
工程脚手架
mkdir mcp-stdio-server && cd $_
npm init -y
npm i -D typescript ts-node @types/node
npx tsc --init --rootDir src --outDir dist --esModuleInterop true
mkdir src最小服务器(TypeScript)
创建 src/server.ts:
import { TextDecoder } from 'node:util';
interface JsonRpcRequest {
jsonrpc: '2.0';
id?: number | string;
method: string;
params?: unknown;
}
interface JsonRpcResponse {
jsonrpc: '2.0';
id: number | string | null;
result?: unknown;
error?: { code: number; message: string; data?: unknown };
}
const decoder = new TextDecoder('utf-8');
let buffer = Buffer.alloc(0);
process.stdin.on('data', (chunk) => {
buffer = Buffer.concat([buffer, chunk]);
parseFrames();
});
function parseFrames() {
while (true) {
const headerEnd = buffer.indexOf('\r\n\r\n');
if (headerEnd === -1) return;
const header = buffer.slice(0, headerEnd).toString('utf8');
const match = /Content-Length:\s*(\d+)/i.exec(header);
if (!match) {
// invalid frame, drop
buffer = buffer.slice(headerEnd + 4);
continue;
}
const length = Number(match[1]);
const total = headerEnd + 4 + length;
if (buffer.length graceful('SIGINT'));
process.on('SIGTERM', () => graceful('SIGTERM'));
function graceful(reason: string) {
// optional: send a shutdown notification before exiting
send({ jsonrpc: '2.0', id: null, method: 'shutdown' } as any);
setTimeout(() => process.exit(0), 50);
}将运行脚本添加到 package.json:
{
"type": "module",
"scripts": {
"dev": "ts-node src/server.ts",
"build": "tsc",
"start": "node dist/server.js"
}
}快速测试
在一个终端中,运行服务器:
npm run dev在另一个终端中,发送一个框架JSON-RPC请求:
node -e "const m=JSON.stringify({jsonrpc:'2.0',id:1,method:'ping'});process.stdout.write('Content-Length: '+Buffer.byteLength(m)+'\r\n\r\n'+m)" | npm run dev你应该得到一个框架式的回应 { pong: true }.
生产准备提示
- 验证标头并拒绝超大有效载荷
- 添加请求超时和心跳(周期性
ping) - 使用十六进制转储记录帧/解析错误
- 使用结构化日志记录;避免STDIO上的控制台噪音
- 如果双方都支持,请考虑使用MessagePack进行正文编码
- 对工具调用实施背压和并发限制
有用的参考资料
- JSON-RPC 2.0:https://www.jsonrpc.org/specification
- 语言服务器协议帧(类似标头):https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#headerPart
- MCP社区示例:在GitHub上搜索“MCP stdio服务器”
