关于 mcp-server-development
mcp-server-development 是一项指导如何构建可靠 MCP 服务的技能,重点介绍 unity-mcp-server 的架构与模式。它适用于 MCP 服务开发者,涵盖工具处理器、输入验证、传输、错误处理和测试。
主要功能
- 01按工具拆分处理器
- 02使用 Zod 验证输入
- 03处理 Content-Length 与 NDJSON 输入
- 04遵循 JSON-RPC 错误格式
- 05测试处理器与传输流程
- 06说明 Unity 命令与工作区路径
适用场景
- 01设计 MCP 工具处理器结构
- 02实现标准输入输出传输
- 03排查协议与错误处理问题
- 04为 Unity 集成编写测试
MCP 相关
该技能提供 MCP 服务构建指导,涵盖工具定义、JSON-RPC 传输及协议合规。其架构示例侧重 unity-mcp-server。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ akiojin/ llmlb --skill mcp-server-development安装后在 Claude Code 中输入/mcp-server-development确认已加载。
详细介绍
此技能提供构建稳健 MCP 服务的全面指导,重点介绍 unity-mcp-server 的架构和模式。
核心理念
MCP 服务连接 AI 助手与外部系统。它们必须:
- 可靠:妥善处理错误,绝不意外崩溃
- 易于发现:工具应具有清晰且自描述的架构
- 高性能:尽量降低延迟,尤其是对 stdio 传输而言
- 符合协议规范:严格遵循 JSON-RPC 2.0 和 MCP 规范
架构模式
基于处理器的设计
始终为每个工具使用一个处理器类。每个处理器封装:
- 输入验证(Zod 架构)
- 业务逻辑执行
- 错误处理和响应格式化
// Pattern: One handler per tool
export class SystemPingToolHandler extends BaseToolHandler {
constructor(unityConnection) {
super({
name: 'system_ping',
description: 'Check Unity Editor connectivity',
inputSchema: { type: 'object', properties: {} }
});
this.unityConnection = unityConnection;
}
async execute(params) {
const result = await this.unityConnection.send({ command: 'ping' });
return { status: 'ok', ...result };
}
}
BaseToolHandler 契约
所有处理器都必须:
- 使用工具元数据调用
super() - 实现
execute(params)方法 - 返回普通对象(由框架处理 JSON-RPC 封装)
- 抛出包含描述性消息的错误
使用 Zod 进行输入验证
始终在处理前验证输入:
import { z } from 'zod';
const inputSchema = z.object({
name: z.string().min(1).describe('GameObject name'),
primitiveType: z.enum(['cube', 'sphere', 'cylinder']).optional()
});
validate(input) {
return inputSchema.parse(input);
}
传输层
Content-Length 分帧(标准)
MCP 对 stdio 使用 LSP 风格的 Content-Length 分帧:
Content-Length: 123\r\n
\r\n
{"jsonrpc":"2.0","id":1,"method":"tools/call"...}
始终对输出使用 Content-Length。绝不在同一会话中混用分帧格式。
混合输入(兼容性)
为兼容客户端,同时接受 Content-Length 和 NDJSON 输入:
- Claude Desktop:Content-Length
- 部分 CLI 工具:NDJSON(以换行符分隔)
错误处理
结构化错误
使用规范中的 MCP 错误代码:
const McpError = {
ParseError: -32700,
InvalidRequest: -32600,
MethodNotFound: -32601,
InvalidParams: -32602,
InternalError: -32603
};
throw new Error(JSON.stringify({
code: McpError.InvalidParams,
message: 'primitiveType must be one of: cube, sphere, cylinder'
}));
错误响应格式
{
jsonrpc: '2.0',
id: requestId,
error: {
code: -32602,
message: 'Invalid params',
data: { field: 'name', issue: 'required' }
}
}
Unity 特定模式
命令协议
Unity 通信使用简单的请求/响应模式:
const result = await this.unityConnection.send({
command: 'gameobject_create',
params: { name: 'Cube', primitiveType: 'cube' }
});
工作区根路径解析
对于需要文件路径的命令,始终包含 workspaceRoot:
execute(params) {
return this.unityConnection.send({
command: 'screenshot_capture',
workspaceRoot: config.workspaceRoot,
...params
});
}
测试模式
处理器的 TDD
- 先编写契约测试:定义预期的输入/输出架构
- 模拟 Unity 连接:隔离处理器逻辑
- 测试错误路径:无效输入、连接失败
- 最后测试正常路径:验证契约后再进行
describe('CreateGameObjectHandler', () => {
it('validates primitiveType enum', async () => {
const handler = new CreateGameObjectHandler(mockConnection);
await assert.rejects(
() => handler.handle({ name: 'Cube', primitiveType: 'invalid' }),
/primitiveType must be one of/
);
});
});
集成测试
测试完整的请求/响应周期,包括 JSON-RPC 分帧:
const stdin = new PassThrough();
const stdout = new PassThrough();
const transport = new HybridStdioServerTransport(stdin, stdout);
stdin.write('Content-Length: 50\r\n\r\n{"jsonrpc":"2.0","id":1,"method":"ping"}');
// Assert stdout contains Content-Length response
常见错误
混用分帧格式:
- 绝不:收到 Content-Length 输入后输出 NDJSON
- 始终:无论输入格式如何,都输出 Content-Length
吞掉错误:
- 绝不:
catch (e) { return null; } - 始终:传播错误并附带上下文
缺少验证:
- 绝不:信任来自客户端的原始输入
- 始终:在处理前使用 Zod 验证
阻塞 stdio:
- 绝不:在传输过程中执行同步操作
- 始终:全程使用 async/await
处理器注册
在中央索引中注册所有处理器:
// src/handlers/index.js
export function createHandlers(unityConnection) {
return [
new SystemPingToolHandler(unityConnection),
new CreateGameObjectHandler(unityConnection),
new ScreenshotHandler(unityConnection),
// ...
];
}
请记住
- 始终使用 Content-Length:输出分帧必须保持一致
- 验证所有内容:绝不信任客户端输入
- 一个处理器对应一个工具:保持处理器职责专一
- 测试错误路径:大多数错误都隐藏在错误处理中
- 符合协议规范:严格遵循 JSON-RPC 2.0
评论
支持 Markdown