mcp-server-development
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseThis skill provides comprehensive guidance for building robust MCP servers, with specific focus on the unity-mcp-server architecture and patterns.
本技能为构建稳定的MCP服务器提供全面指导,重点关注unity-mcp-server的架构与模式。
Core Philosophy
核心设计理念
MCP servers bridge AI assistants to external systems. They must be:
- Reliable: Handle errors gracefully, never crash unexpectedly
- Discoverable: Tools should have clear, self-documenting schemas
- Performant: Minimize latency, especially for stdio transport
- Protocol-compliant: Follow JSON-RPC 2.0 and MCP spec exactly
MCP服务器是AI助手与外部系统之间的桥梁。它们必须具备以下特性:
- 可靠性:优雅处理错误,避免意外崩溃
- 可发现性:工具应具备清晰、自文档化的schema
- 高性能:尽可能降低延迟,尤其是对于stdio传输
- 协议合规性:严格遵循JSON-RPC 2.0和MCP规范
Architecture Patterns
架构模式
Handler-Based Design
基于处理器的设计
ALWAYS use a handler class per tool. Each handler encapsulates:
- Input validation (Zod schema)
- Business logic execution
- Error handling and response formatting
javascript
// 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 };
}
}务必为每个工具单独使用一个处理器类。每个处理器封装以下内容:
- 输入验证(Zod schema)
- 业务逻辑执行
- 错误处理与响应格式化
javascript
// 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 Contract
BaseToolHandler 契约要求
All handlers MUST:
- Call with tool metadata
super() - Implement method
execute(params) - Return plain objects (framework handles JSON-RPC wrapping)
- Throw errors with descriptive messages
所有处理器必须满足以下要求:
- 传入工具元数据调用
super() - 实现方法
execute(params) - 返回普通对象(框架会处理JSON-RPC封装)
- 抛出带有描述性信息的错误
Input Validation with Zod
使用Zod进行输入验证
ALWAYS validate inputs before processing:
javascript
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);
}务必在处理前验证输入:
javascript
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);
}Transport Layer
传输层
Content-Length Framing (Standard)
Content-Length 帧格式(标准)
MCP uses LSP-style Content-Length framing for stdio:
Content-Length: 123\r\n
\r\n
{"jsonrpc":"2.0","id":1,"method":"tools/call"...}ALWAYS use Content-Length for output. NEVER mix framing formats in a session.
MCP针对stdio采用LSP风格的Content-Length帧格式:
Content-Length: 123\r\n
\r\n
{"jsonrpc":"2.0","id":1,"method":"tools/call"...}务必在输出时使用Content-Length。绝对不要在一个会话中混合使用多种帧格式。
Hybrid Input (Compatibility)
混合输入(兼容性)
Accept both Content-Length and NDJSON input for client compatibility:
- Claude Desktop: Content-Length
- Some CLI tools: NDJSON (newline-delimited)
为了兼容客户端,同时支持Content-Length和NDJSON输入:
- Claude Desktop:使用Content-Length
- 部分CLI工具:使用NDJSON(换行分隔)
Error Handling
错误处理
Structured Errors
结构化错误
Use MCP error codes from the spec:
javascript
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'
}));使用规范中定义的MCP错误码:
javascript
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'
}));Error Response Format
错误响应格式
javascript
{
jsonrpc: '2.0',
id: requestId,
error: {
code: -32602,
message: 'Invalid params',
data: { field: 'name', issue: 'required' }
}
}javascript
{
jsonrpc: '2.0',
id: requestId,
error: {
code: -32602,
message: 'Invalid params',
data: { field: 'name', issue: 'required' }
}
}Unity-Specific Patterns
Unity专属模式
Command Protocol
命令协议
Unity communication uses a simple request/response pattern:
javascript
const result = await this.unityConnection.send({
command: 'gameobject_create',
params: { name: 'Cube', primitiveType: 'cube' }
});与Unity的通信采用简单的请求/响应模式:
javascript
const result = await this.unityConnection.send({
command: 'gameobject_create',
params: { name: 'Cube', primitiveType: 'cube' }
});Workspace Root Resolution
工作区根目录解析
ALWAYS include in commands requiring file paths:
workspaceRootjavascript
execute(params) {
return this.unityConnection.send({
command: 'screenshot_capture',
workspaceRoot: config.workspaceRoot,
...params
});
}务必在需要文件路径的命令中包含:
workspaceRootjavascript
execute(params) {
return this.unityConnection.send({
command: 'screenshot_capture',
workspaceRoot: config.workspaceRoot,
...params
});
}Testing Patterns
测试模式
TDD for Handlers
处理器的测试驱动开发(TDD)
- Contract test first: Define expected input/output schema
- Mock Unity connection: Isolate handler logic
- Test error paths: Invalid input, connection failures
- Test happy path last: After contracts are verified
javascript
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/
);
});
});- 先做契约测试:定义预期的输入/输出schema
- Mock Unity连接:隔离处理器逻辑
- 测试错误路径:无效输入、连接失败等场景
- 最后测试正常路径:在契约验证通过后进行
javascript
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/
);
});
});Integration Testing
集成测试
Test full request/response cycle including JSON-RPC framing:
javascript
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测试包含JSON-RPC帧格式的完整请求/响应周期:
javascript
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"}');
// 断言stdout包含Content-Length格式的响应Common Mistakes
常见错误
Mixing framing formats:
- NEVER: Output NDJSON after receiving Content-Length input
- ALWAYS: Output Content-Length regardless of input format
Swallowing errors:
- NEVER:
catch (e) { return null; } - ALWAYS: Propagate errors with context
Missing validation:
- NEVER: Trust raw input from clients
- ALWAYS: Validate with Zod before processing
Blocking stdio:
- NEVER: Synchronous operations on transport
- ALWAYS: Use async/await throughout
混合帧格式:
- 禁止:在接收Content-Length格式的输入后输出NDJSON
- 务必:无论输入格式如何,输出都使用Content-Length
吞掉错误:
- 禁止:
catch (e) { return null; } - 务必:携带上下文传播错误
缺少验证:
- 禁止:信任客户端的原始输入
- 务必:处理前使用Zod进行验证
阻塞stdio:
- 禁止:在传输层使用同步操作
- 务必:全程使用async/await
Handler Registration
处理器注册
Register all handlers in a central index:
javascript
// src/handlers/index.js
export function createHandlers(unityConnection) {
return [
new SystemPingToolHandler(unityConnection),
new CreateGameObjectHandler(unityConnection),
new ScreenshotHandler(unityConnection),
// ...
];
}在中央索引文件中注册所有处理器:
javascript
// src/handlers/index.js
export function createHandlers(unityConnection) {
return [
new SystemPingToolHandler(unityConnection),
new CreateGameObjectHandler(unityConnection),
new ScreenshotHandler(unityConnection),
// ...
];
}Remember
注意事项
- Content-Length always: Output framing must be consistent
- Validate everything: Never trust client input
- One handler, one tool: Keep handlers focused
- Test error paths: Most bugs hide in error handling
- Protocol compliance: Follow JSON-RPC 2.0 exactly
- 始终使用Content-Length:输出的帧格式必须保持一致
- 验证所有输入:永远不要信任客户端输入
- 一个处理器对应一个工具:保持处理器职责单一
- 测试错误路径:大多数bug隐藏在错误处理逻辑中
- 遵循协议规范:严格遵守JSON-RPC 2.0