A methodology for systematically designing, implementing, testing, and deploying Model Context Protocol servers.
1. Core Protocol Concepts
MCP defines three primitives:
- Tools: functions actively invoked by AI assistants, with side effects. Such as search, create, and delete operations.
- Resources: read-only data sources for AI assistants, identified by URI. Such as
users://{id}/profile。 - Prompts: predefined interaction templates that guide users to trigger workflows.
Selection principles: Perform operations → Tool | Read data → Resource | Guide interaction → Prompt
2. Project Structure Standards
TypeScript
my-mcp-server/
├── src/
│ ├── index.ts # 入口,注册 tools/resources
│ ├── tools/ # 按功能拆分
│ ├── resources/
│ └── lib/ # 客户端封装、校验逻辑
├── tests/
├── package.json
└── tsconfig.json
Key dependencies:@modelcontextprotocol/sdk + zod
Python
my-mcp-server/
├── src/my_mcp_server/
│ ├── server.py
│ ├── tools/
│ └── lib/
├── tests/
└── pyproject.toml
Key dependencies:mcp + pydantic
3. Tool Design Principles
Naming
snake_caseformat, starting with a verb:search_users、create_issue、delete_file- Names should be self-explanatory; AI assistants select tools by name, and vague naming leads to incorrect invocation
Parameters
- Each parameter has a type constraint and
.describe()description - Optional parameters should have default values to reduce the AI's decision burden
- Use enums instead of boolean switches
server.tool("search_issues", {
query: z.string().describe("搜索关键词"),
status: z.enum(["open", "closed", "all"]).default("open").describe("状态筛选"),
limit: z.number().min(1).max(100).default(20).describe("返回上限"),
}, async ({ query, status, limit }) => { /* ... */ });
Description
Explainpurpose + return content + limitations, which is the key basis for the AI to select tools:
server.tool("search_users",
"根据姓名或邮箱搜索用户。返回 ID、姓名、邮箱列表。模糊匹配,最多 50 条。",
schema, handler);
Output
- Structured data → JSON, human-readable content → Markdown
- Always use
content: [{ type: "text", text: "..." }]format for returns
4. Input Validation and Error Handling
Use Zod/Pydantic for schema-level validation, and place business-level validation at the beginning of the handler:
server.tool("get_user", { id: z.string() }, async ({ id }) => {
try {
const user = await db.getUser(id);
if (!user) {
return {
content: [{ type: "text", text: `用户 ${id} 不存在,请检查 ID。` }],
isError: true,
};
}
return { content: [{ type: "text", text: JSON.stringify(user, null, 2) }] };
} catch (err) {
return {
content: [{ type: "text", text: `查询失败:${err.message}` }],
isError: true,
};
}
});
Four principles of error handling:
- Never let the server crash — wrap all external calls in try/catch
- Return actionable error information — tell the AI what the problem is and what can be done
- Use
isError: true— let the AI know the call failed - Distinguish error types — parameter errors, insufficient permissions, resource not found, service unavailable
5. Resource Management and Lifecycle
// 资源注册
server.resource("user-profile", "users://{userId}/profile", async (uri) => {
const profile = await db.getProfile(extractId(uri));
return { contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(profile) }] };
});
// 生命周期:先初始化 → 再 connect → 监听关闭信号
const db = await Database.connect(config.dbUrl);
await server.connect(new StdioServerTransport());
process.on("SIGINT", async () => { await db.disconnect(); await server.close(); process.exit(0); });
Key points: use connection pools, set timeouts for all external calls, and gracefully shut down to clean up resources.
6. Testing Strategy
Unit testing — separate business logic from MCP registration
// tools/search.ts 导出纯函数
export async function searchUsers(query: string, limit: number) { /* ... */ }
// search.test.ts 独立测试
test("返回匹配结果", async () => {
const results = await searchUsers("alice", 10);
expect(results[0].name).toContain("Alice");
});
Integration testing — use the SDK Client for end-to-end verification
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
const client = new Client({ name: "test", version: "1.0.0" });
await client.connect(clientTransport);
const result = await client.callTool("search_users", { query: "test" });
expect(result.isError).toBeFalsy();
MCP Inspector — interactive debugging
npx @modelcontextprotocol/inspector node dist/index.js
View all tools/resources in the browser, call them manually, and inspect the results.
Testing key points: Each Tool should cover normal + exception paths, boundary values, and external service failure simulation.
7. Security Considerations
Permission control:
- Principle of least privilege, separate read and write Tools
- Dangerous operations require confirmation parameters (such as
confirm: true)
Input security:
- SQL injection → parameterized queries, never concatenate
- Path traversal → validate paths, prohibit
../ - Command injection → use
execFileinstead ofexec
Sensitive data:
- Secrets are passed in through environment variables, not hardcoded
- Logs do not print complete sensitive information
- Returned data is masked
Sandbox: Restrict file operation directories, whitelist network requests, and set resource quotas.
8. Deployment and Distribution
npm Publishing
{ "bin": { "mcp-server-myservice": "dist/index.js" }, "files": ["dist"] }
User configuration:
{ "mcpServers": { "myservice": { "command": "npx", "args": ["@yourorg/mcp-server-myservice"], "env": { "API_KEY": "xxx" } } } }
pip Publishing
[project.scripts]
mcp-server-myservice = "my_mcp_server.server:main"
Docker — suitable for complex dependencies or isolation scenarios
FROM node:20-slim
WORKDIR /app
COPY package*.json ./ && RUN npm ci --production
COPY dist ./dist
ENTRYPOINT ["node", "dist/index.js"]
9. Debugging Techniques
Key: MCP uses stdio for communication and cannot use console.log, as it will break the protocol stream.
// 错误
console.log("debug");
// 正确
console.error("[DEBUG]", info);
// 更好
server.sendLoggingMessage({ level: "info", data: "处理中" });
Common issues:
| Symptom | Cause | Solution |
|---|---|---|
| No response on startup | transport not connected | Check server.connect() |
| Tool does not appear | registered after connect | register first, then connect |
| AI does not call the Tool | unclear description | improve the name and description |
| parameters always wrong | Schema unclear | Add .describe() |
| call timeout | external service slow | add timeout and caching |
Debugging workflow: Inspector verifies basic functionality → manually call to confirm input and output → connect to a real AI client to observe call patterns → adjust the design based on actual behavior.
10. Build Checklist
Design
- Clarify the division of labor among Tools vs Resources vs Prompts
- Tool naming
动词_名词, with descriptions explaining purpose and return content - Concise parameters, with reasonable default values for optional parameters
Implementation
- Validate input with Zod/Pydantic
- External calls have try/catch and timeouts
- Error returns
isError: truewith actionable information - Do not use
console.log(use stderr or SDK logging) - Sensitive data goes through environment variables
Testing
- Core logic has unit tests
- Integration tests verify MCP protocol interaction
- Manually verified with MCP Inspector
- Tested with a real AI client
Deployment
- README includes installation and configuration instructions
- Provide a client configuration JSON example
- Follow semver, no hard-coded secrets