2025年MCP协议经历了两次重大升级——6月的2025-06-18版本引入结构化工具输出、Tool Annotations和Elicitation机制;11月的2025-11-25版本确立了异步Tasks、增强OAuth和协议扩展。本教程从零构建一个完整的MCP服务器,覆盖全部五大现代特性。适合有TypeScript基础、已完成MCP基础学习的开发者,预计教学时长4小时。
| 项目 | 要求 |
|---|---|
| Node.js | ≥ 18 |
| TypeScript | ≥ 5.0 |
| MCP基础 | 了解Tools/Resources/Prompts三原语 |
| 工具 | npm/npx、git |
MCP协议自2024年11月发布以来,经历了三个关键版本:
timeline
title MCP协议演进时间线
2024-11 : 初始版本发布\nstdio + SSE双传输\nTools/Resources/Prompts三原语
2025-03 : 2025-03-26版本\nStreamable HTTP传输替代SSE\n协议基础架构定型
2025-06 : 2025-06-18版本\n结构化工具输出\nTool Annotations\nElicitation\nResource订阅
2025-11 : 2025-11-25版本\n异步Tasks\n增强OAuth/OIDC\n协议扩展机制
本教程聚焦2025-06-18和2025-11-25两个版本引入的核心新特性,它们代表了MCP从"能用"到"好用"的关键跨越。
| 特性 | 版本 | 解决什么问题 |
|---|---|---|
| Structured Tool Output | 2025-06-18 | 工具返回强类型结构化数据,替代纯文本 |
| Tool Annotations | 2025-06-18 | 声明工具副作用属性,辅助客户端权限决策 |
| Elicitation | 2025-06-18 | 服务器主动请求用户输入,实现人在回路 |
| Resource Templates + Completions | 2025-06-18 | 参数化资源URI模板 + 自动补全 |
| Async Tasks | 2025-11-25 | 长时间运行操作的异步任务管理 |
mkdir modern-mcp-server && cd modern-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zody
npm install -D typescript @types/node tsx
注意:
@modelcontextprotocol/sdk从1.12.0版本开始支持2025-06-18规范特性,安装时确认版本。
npx tsc --init
修改tsconfig.json关键配置:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
modern-mcp-server/
├── src/
│ ├── index.ts # 服务器入口
│ ├── tools/
│ │ ├── query-db.ts # 结构化输出示例
│ │ ├── deploy.ts # Tool Annotations示例
│ │ └── migrate.ts # Elicitation示例
│ ├── resources/
│ │ └── templates.ts # 资源模板+补全
│ └── utils/
│ └── db.ts # 模拟数据库
├── package.json
└── tsconfig.json
传统MCP工具返回纯文本内容,客户端需要自行解析。2025-06-18版本引入了outputSchema,允许工具声明其输出的JSON Schema结构。
sequenceDiagram
participant Client as MCP客户端
participant Server as MCP服务器
Client->>Server: tools/call (query_users)
Note over Server: 执行查询逻辑
Server->>Client: structuredContent<br/>{users: [...], total: 42}
Note over Client: 按schema解析<br/>无需文本解析
Server->>Client: content (文本回退)<br/>"Found 42 users"
关键区别:
- content:向后兼容的文本输出
- structuredContent:符合outputSchema的结构化JSON数据
创建src/utils/db.ts——模拟数据库层:
// src/utils/db.ts
export interface User {
id: number;
name: string;
email: string;
role: "admin" | "editor" | "viewer";
created_at: string;
}
const mockUsers: User[] = [
{ id: 1, name: "Alice Chen", email: "[email protected]", role: "admin", created_at: "2024-01-15" },
{ id: 2, name: "Bob Wang", email: "[email protected]", role: "editor", created_at: "2024-03-22" },
{ id: 3, name: "Carol Li", email: "[email protected]", role: "viewer", created_at: "2024-06-10" },
{ id: 4, name: "David Zhang", email: "[email protected]", role: "editor", created_at: "2024-08-01" },
{ id: 5, name: "Eva Liu", email: "[email protected]", role: "admin", created_at: "2025-01-20" },
];
export function queryUsers(role?: string, limit = 10): { users: User[]; total: number } {
let filtered = mockUsers;
if (role) {
filtered = filtered.filter(u => u.role === role);
}
return {
users: filtered.slice(0, limit),
total: filtered.length,
};
}
创建src/tools/query-db.ts:
// src/tools/query-db.ts
import { z } from "zod";
import { queryUsers } from "../utils/db.js";
const QueryUsersSchema = z.object({
role: z.enum(["admin", "editor", "viewer"]).optional()
.describe("Filter by user role"),
limit: z.number().min(1).max(100).default(10)
.describe("Maximum number of users to return"),
});
// 定义输出schema —— 这是结构化输出的关键
const outputSchema = {
type: "object" as const,
properties: {
users: {
type: "array" as const,
items: {
type: "object" as const,
properties: {
id: { type: "number" },
name: { type: "string" },
email: { type: "string" },
role: { type: "string", enum: ["admin", "editor", "viewer"] },
created_at: { type: "string" },
},
required: ["id", "name", "email", "role", "created_at"],
},
},
total: { type: "number" },
},
required: ["users", "total"],
};
export function registerQueryUsersTool(server: any) {
server.tool(
"query_users",
"Query users from the database with optional role filter. Returns structured user data.",
{ role: QueryUsersSchema.shape.role, limit: QueryUsersSchema.shape.limit },
outputSchema, // ← 声明输出schema
async (args: z.infer<typeof QueryUsersSchema>) => {
const result = queryUsers(args.role, args.limit);
return {
// 结构化输出 —— 客户端可以按schema直接解析
structuredContent: result,
// 文本回退 —— 不支持structuredContent的客户端仍可使用
content: [{
type: "text",
text: `Found ${result.total} users${args.role ? ` with role '${args.role}'` : ""}.\n` +
result.users.map(u => ` - ${u.name} (${u.email}) [${u.role}]`).join("\n"),
}],
};
}
);
}
| 场景 | 纯文本输出 | 结构化输出 |
|---|---|---|
| 客户端解析 | 需要正则/解析器 | 直接JSON反序列化 |
| 类型安全 | 无 | 有schema验证 |
| 多语言客户端 | 各自实现解析 | 统一schema |
| 调试 | 难以验证格式 | schema自动验证 |
常见问题:旧版客户端不支持structuredContent怎么办?
解决方案:始终同时提供content(文本回退)和structuredContent。MCP规范要求客户端优先使用structuredContent,回退到content。
Tool Annotations为每个工具声明行为属性,帮助MCP客户端(如Claude Code)做出权限决策。
graph LR
subgraph "Tool Annotations"
A[readOnlyHint<br/>是否只读] --> D{客户端决策}
B[destructiveHint<br/>是否破坏性] --> D
C[idempotentHint<br/>是否幂等] --> D
E[openWorldHint<br/>是否访问外部] --> D
F[title<br/>显示标题] --> G{UI展示}
end
D --> |autoApprove| H[自动执行]
D --> |requireConfirm| I[用户确认]
| 注解 | 类型 | 含义 | 典型场景 |
|---|---|---|---|
readOnlyHint |
boolean | 工具不修改任何状态 | 查询数据库、读取文件 |
destructiveHint |
boolean | 工具可能造成不可逆操作 | 删除记录、DROP TABLE |
idempotentHint |
boolean | 多次调用结果一致 | 设置配置(非切换) |
openWorldHint |
boolean | 访问外部网络/系统 | 调用API、访问URL |
title |
string | 人类可读的显示标题 | UI展示 |
创建src/tools/deploy.ts:
// src/tools/deploy.ts
import { z } from "zod";
const DeploySchema = z.object({
service: z.string().describe("Service name to deploy"),
environment: z.enum(["staging", "production"]).describe("Target environment"),
version: z.string().describe("Version tag to deploy"),
force: z.boolean().default(false).describe("Force deploy even with warnings"),
});
export function registerDeployTool(server: any) {
server.tool(
"deploy_service",
"Deploy a service to the specified environment",
{
service: DeploySchema.shape.service,
environment: DeploySchema.shape.environment,
version: DeploySchema.shape.version,
force: DeploySchema.shape.force,
},
{
// 关键注解声明
title: "Deploy Service", // UI显示标题
readOnlyHint: false, // 会修改状态
destructiveHint: true, // 可能覆盖现有部署
idempotentHint: true, // 部署同一版本幂等
openWorldHint: true, // 访问部署系统
},
async (args: z.infer<typeof DeploySchema>) => {
// 模拟部署逻辑
const deployId = `deploy-${Date.now()}`;
const warning = args.environment === "production" && !args.force
? "⚠️ Production deploy requires force flag"
: "";
return {
content: [{
type: "text",
text: JSON.stringify({
status: "deployed",
deploy_id: deployId,
service: args.service,
environment: args.environment,
version: args.version,
timestamp: new Date().toISOString(),
warning,
}, null, 2),
}],
};
}
);
// 注册一个只读的部署状态查询工具
server.tool(
"check_deploy_status",
"Check the deployment status of a service",
{
service: z.string().describe("Service name"),
environment: z.enum(["staging", "production"]).describe("Environment"),
},
{
title: "Check Deploy Status",
readOnlyHint: true, // 只读,客户端可自动批准
destructiveHint: false,
idempotentHint: true,
openWorldHint: true, // 需要访问部署系统查询
},
async (args) => {
return {
content: [{
type: "text",
text: JSON.stringify({
service: args.service,
environment: args.environment,
status: "running",
version: "v2.3.1",
uptime: "3d 14h 22m",
last_deploy: "2025-05-20T08:30:00Z",
}, null, 2),
}],
};
}
);
}
Claude Code根据注解决定工具调用策略:
readOnlyHint: true + destructiveHint: false
→ 自动批准,无需用户确认
destructiveHint: true
→ 强制要求用户确认
openWorldHint: true
→ 沙箱模式下可能被阻止
实际配置示例(.claude/settings.json):
{
"permissions": {
"allow": [
"mcp__modern-mcp__check_deploy_status"
],
"deny": [
"mcp__modern-mcp__deploy_service"
]
}
}
Elicitation是2025-06-18版本最重要的新特性之一,它允许MCP服务器在工具执行过程中主动向用户请求输入。
sequenceDiagram
participant Client as MCP客户端<br/>(Claude Code)
participant Server as MCP服务器
participant User as 用户
Client->>Server: tools/call (migrate_database)
Note over Server: 检测到破坏性操作
Server->>Client: elicitation/request<br/>"确认要删除旧表吗?"<br/>action: confirm
Client->>User: 展示确认对话框
User->>Client: 确认/取消
Client->>Server: elicitation/response<br/>{action: "accept", content: {confirmed: true}}
Note over Server: 继续执行迁移
Server->>Client: 工具结果
创建src/tools/migrate.ts:
// src/tools/migrate.ts
import { z } from "zod";
const MigrateSchema = z.object({
migration_file: z.string().describe("Path to the migration SQL file"),
target_db: z.string().describe("Target database name"),
dry_run: z.boolean().default(true).describe("Run in dry-run mode first"),
});
export function registerMigrateTool(server: any) {
server.tool(
"migrate_database",
"Execute a database migration. Will request user confirmation before destructive operations.",
{
migration_file: MigrateSchema.shape.migration_file,
target_db: MigrateSchema.shape.target_db,
dry_run: MigrateSchema.shape.dry_run,
},
{
title: "Database Migration",
readOnlyHint: false,
destructiveHint: true,
idempotentHint: false,
openWorldHint: true,
},
async (args: z.infer<typeof MigrateSchema>, extra: any) => {
// 第一步:Dry run检查
const dryRunResult = analyzeMigration(args.migration_file);
if (args.dry_run) {
return {
content: [{
type: "text",
text: `Dry run result:\n${dryRunResult.summary}\n\nSet dry_run=false to execute for real.`,
}],
};
}
// 第二步:如果有破坏性操作,通过Elicitation请求用户确认
if (dryRunResult.hasDestructiveOps) {
const confirmation = await extra.elicitInput({
message: `Migration "${args.migration_file}" contains destructive operations:\n` +
dryRunResult.destructiveOps.map(op => ` • ${op}`).join("\n") +
`\n\nTarget: ${args.target_db}`,
requestedSchema: {
type: "object",
properties: {
confirmed: {
type: "boolean",
title: "I understand and accept the risks",
},
backup_first: {
type: "boolean",
title: "Create backup before migration",
default: true,
},
},
required: ["confirmed"],
},
});
// 用户拒绝或未确认
if (confirmation.action !== "accept" || !confirmation.content?.confirmed) {
return {
content: [{
type: "text",
text: "Migration cancelled by user.",
}],
isError: true,
};
}
// 用户要求先备份
if (confirmation.content.backup_first) {
await performBackup(args.target_db);
}
}
// 第三步:执行迁移
const result = await executeMigration(args.migration_file, args.target_db);
return {
content: [{
type: "text",
text: JSON.stringify({
status: "success",
migration: args.migration_file,
database: args.target_db,
applied_at: new Date().toISOString(),
changes: result.changes,
}, null, 2),
}],
};
}
);
}
// 辅助函数(模拟实现)
function analyzeMigration(file: string) {
return {
summary: "3 tables will be modified, 1 index will be dropped",
hasDestructiveOps: true,
destructiveOps: [
"DROP INDEX idx_users_email",
"ALTER TABLE orders DROP COLUMN legacy_id",
"TRUNCATE TABLE temp_cache",
],
};
}
async function performBackup(db: string) {
// 模拟备份
return { backup_file: `backup_${db}_${Date.now()}.sql` };
}
async function executeMigration(file: string, db: string) {
return {
changes: [
"Applied: create_users_v2_table",
"Applied: migrate_users_data",
"Applied: drop_users_v1_table",
],
};
}
| 动作 | 含义 | 典型用途 |
|---|---|---|
accept |
用户确认并提供输入 | 继续执行 |
decline |
用户明确拒绝 | 取消操作 |
cancel |
用户关闭对话框 | 取消操作 |
设计原则:Elicitation适用于"运行时决策"——你在开发时不知道用户会选什么。对于固定规则(如"生产环境必须审批"),应使用Tool Annotations + 客户端权限配置。
Resource Templates是参数化的资源URI模式,Completions则为模板参数提供自动补全。
db://{database}/tables/{table}
users://{user_id}/profile
logs://{service}/{date}
创建src/resources/templates.ts:
// src/resources/templates.ts
import { z } from "zod";
export function registerResourceTemplates(server: any) {
// 资源模板:获取数据库表结构
server.resource(
"database-schema", // 资源名称
"db://{database}/tables/{table}/schema", // URI模板
{
description: "Get the schema of a specific database table",
mimeType: "application/json",
},
async (uri: URL, variables: { database: string; table: string }) => {
// 根据变量动态生成资源内容
const schema = getTableSchema(variables.database, variables.table);
return {
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(schema, null, 2),
}],
};
}
);
// 资源模板:用户配置
server.resource(
"user-profile",
"users://{user_id}/profile",
{
description: "Get user profile information",
mimeType: "application/json",
},
async (uri: URL, variables: { user_id: string }) => {
const profile = getUserProfile(variables.user_id);
return {
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(profile, null, 2),
}],
};
}
);
// 注册Completions —— 为模板参数提供自动补全
server.completion("database", async (context: any) => {
// 根据上下文返回可用的数据库名列表
return {
values: ["production_db", "staging_db", "analytics_db"],
total: 3,
hasMore: false,
};
});
server.completion("table", async (context: any) => {
// 根据已选的database参数,返回该数据库的表列表
const database = context?.arguments?.database;
const tables = getTablesForDatabase(database || "");
return {
values: tables,
total: tables.length,
hasMore: false,
};
});
server.completion("user_id", async (context: any) => {
// 返回用户ID列表
return {
values: ["1", "2", "3", "4", "5"],
total: 5,
hasMore: false,
};
});
}
// 模拟数据函数
function getTableSchema(database: string, table: string) {
const schemas: Record<string, Record<string, any>> = {
production_db: {
users: {
columns: [
{ name: "id", type: "INTEGER", nullable: false, primary_key: true },
{ name: "name", type: "VARCHAR(255)", nullable: false },
{ name: "email", type: "VARCHAR(255)", nullable: false, unique: true },
{ name: "role", type: "ENUM('admin','editor','viewer')", nullable: false },
{ name: "created_at", type: "TIMESTAMP", nullable: false },
],
indexes: ["idx_users_email", "idx_users_role"],
row_count: 15420,
},
orders: {
columns: [
{ name: "id", type: "INTEGER", nullable: false, primary_key: true },
{ name: "user_id", type: "INTEGER", nullable: false, foreign_key: "users.id" },
{ name: "amount", type: "DECIMAL(10,2)", nullable: false },
{ name: "status", type: "ENUM('pending','completed','cancelled')", nullable: false },
{ name: "created_at", type: "TIMESTAMP", nullable: false },
],
indexes: ["idx_orders_user_id", "idx_orders_status"],
row_count: 89230,
},
},
};
return schemas[database]?.[table] || { error: "Table not found" };
}
function getTablesForDatabase(database: string): string[] {
const dbTables: Record<string, string[]> = {
production_db: ["users", "orders", "products", "payments"],
staging_db: ["users", "orders", "test_data"],
analytics_db: ["events", "page_views", "conversions", "sessions"],
};
return dbTables[database] || [];
}
function getUserProfile(userId: string) {
const users: Record<string, any> = {
"1": { id: 1, name: "Alice Chen", role: "admin", team: "Platform" },
"2": { id: 2, name: "Bob Wang", role: "editor", team: "Content" },
"3": { id: 3, name: "Carol Li", role: "viewer", team: "Marketing" },
};
return users[userId] || { error: "User not found" };
}
sequenceDiagram
participant Client as MCP客户端
participant Server as MCP服务器
Client->>Server: resources/templates/list
Server->>Client: [db://{database}/tables/{table}/schema, ...]
Client->>Server: completion/complete<br/>ref: {uri_template, name: "database"}
Server->>Client: {values: ["production_db", "staging_db"]}
Note over Client: 用户选择 "production_db"
Client->>Server: completion/complete<br/>ref: {...}, context: {database: "production_db"}<br/>name: "table"
Server->>Client: {values: ["users", "orders", "products"]}
Note over Client: 用户选择 "users"
Client->>Server: resources/read<br/>uri: db://production_db/tables/users/schema
Server->>Client: {contents: [{text: "{columns: [...]}"}]}
异步Tasks是MCP 2025-11-25版本最重要的新增功能,解决了长时间运行操作超时的问题。
传统MCP工具是同步的——工具必须在HTTP请求超时内返回结果。对于以下场景无法满足:
stateDiagram-v2
[*] --> Submitted: 客户端调用工具
Submitted --> Running: 服务器开始执行
Running --> Running: 发送进度通知
Running --> Completed: 执行成功
Running --> Failed: 执行失败
Running --> Cancelled: 客户端取消
Completed --> [*]
Failed --> [*]
Cancelled --> [*]
由于异步Tasks是2025-11-25规范特性,SDK支持仍在完善中。以下是使用当前SDK实现长时间运行操作的模式:
// src/tools/batch-process.ts
import { z } from "zod";
const BatchProcessSchema = z.object({
operation: z.enum(["export", "reindex", "cleanup"]).describe("Batch operation type"),
target: z.string().describe("Target resource identifier"),
options: z.record(z.any()).optional().describe("Operation-specific options"),
});
export function registerBatchProcessTool(server: any) {
server.tool(
"batch_process",
"Execute a long-running batch operation with progress tracking",
{
operation: BatchProcessSchema.shape.operation,
target: BatchProcessSchema.shape.target,
options: BatchProcessSchema.shape.options,
},
{
title: "Batch Process",
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
},
async (args: z.infer<typeof BatchProcessSchema>, extra: any) => {
const totalSteps = 100;
const progressToken = extra._meta?.progressToken;
// 模拟长时间运行操作,定期发送进度通知
for (let step = 0; step <= totalSteps; step++) {
// 检查取消信号
if (extra.signal?.aborted) {
return {
content: [{
type: "text",
text: JSON.stringify({
status: "cancelled",
operation: args.operation,
completed_steps: step,
total_steps: totalSteps,
}, null, 2),
}],
isError: true,
};
}
// 发送进度通知
if (progressToken && extra.sendNotification) {
await extra.sendNotification({
method: "notifications/progress",
params: {
progressToken,
progress: step,
total: totalSteps,
},
});
}
// 模拟实际工作
await new Promise(resolve => setTimeout(resolve, 100));
}
return {
content: [{
type: "text",
text: JSON.stringify({
status: "completed",
operation: args.operation,
target: args.target,
steps_completed: totalSteps,
duration_seconds: 10,
result: {
processed: 15420,
succeeded: 15398,
failed: 22,
},
}, null, 2),
}],
};
}
);
}
创建src/index.ts:
// src/index.ts
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
// 创建MCP服务器实例
const server = new McpServer({
name: "modern-mcp-server",
version: "1.0.0",
}, {
capabilities: {
tools: { listChanged: true },
resources: { subscribe: true, listChanged: true },
prompts: { listChanged: true },
logging: {},
},
});
// 注册所有工具
import { registerQueryUsersTool } from "./tools/query-db.js";
import { registerDeployTool } from "./tools/deploy.js";
import { registerMigrateTool } from "./tools/migrate.js";
import { registerBatchProcessTool } from "./tools/batch-process.js";
import { registerResourceTemplates } from "./resources/templates.js";
registerQueryUsersTool(server);
registerDeployTool(server);
registerMigrateTool(server);
registerBatchProcessTool(server);
registerResourceTemplates(server);
// 注册一个Prompt示例
server.prompt(
"analyze_database",
"Generate a database analysis prompt with current state",
{
database: z.string().describe("Database name to analyze"),
focus: z.enum(["performance", "schema", "data_quality"]).optional()
.describe("Analysis focus area"),
},
async (args) => {
return {
messages: [{
role: "user",
content: {
type: "text",
text: `Please analyze the database "${args.database}" with focus on ${args.focus || "general health"}.\n\n` +
`Steps:\n` +
`1. First, use the query_users tool to check user data\n` +
`2. Then read the resource db://${args.database}/tables/users/schema\n` +
`3. Provide recommendations based on findings`,
},
}],
};
}
);
// 启动服务器
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Modern MCP Server running on stdio");
}
main().catch(console.error);
在package.json中添加:
{
"type": "module",
"scripts": {
"build": "tsc",
"dev": "tsx src/index.ts",
"start": "node dist/index.js",
"inspect": "npx @modelcontextprotocol/inspector tsx src/index.ts"
}
}
MCP Inspector是官方调试工具,提供可视化界面测试所有原语:
npm run inspect
浏览器自动打开Inspector界面后:
query_users工具
- 输入参数{"role": "admin"}
- 观察返回的structuredContent和contentdb://production_db/tables/users/schema
- 查看返回的JSON内容analyze_database
- 输入{"database": "production_db", "focus": "performance"}
- 查看生成的prompt消息在项目目录创建.mcp.json:
{
"mcpServers": {
"modern-mcp": {
"command": "tsx",
"args": ["src/index.ts"],
"cwd": "/path/to/modern-mcp-server"
}
}
}
启动Claude Code后,服务器自动连接。验证:
> /mcp
查看已连接的MCP服务器列表和可用工具。
在Claude Code中触发Elicitation:
> 运行数据库迁移 tools/migration_001.sql 到 production_db,不用dry run模式
预期行为:
1. Claude Code调用migrate_database工具
2. 服务器检测到破坏性操作
3. 弹出Elicitation确认对话框
4. 用户确认或取消
5. 根据用户选择继续执行或返回取消信息
将以上所有特性组合为一个完整的DevOps运维管理服务器。
graph TB
subgraph "MCP客户端 (Claude Code)"
A[Claude Code CLI]
end
subgraph "Modern MCP Server"
B[McpServer Core]
subgraph "工具层 Tools"
C1[query_users<br/>结构化输出]
C2[deploy_service<br/>Tool Annotations]
C3[check_deploy_status<br/>只读Annotation]
C4[migrate_database<br/>Elicitation]
C5[batch_process<br/>进度通知]
end
subgraph "资源层 Resources"
D1[db://.../schema<br/>资源模板]
D2[users://.../profile<br/>资源模板]
end
subgraph "补全层 Completions"
E1[database补全]
E2[table补全]
E3[user_id补全]
end
subgraph "Prompt层"
F1[analyze_database<br/>结构化Prompt]
end
end
A -->|stdio| B
B --> C1 & C2 & C3 & C4 & C5
B --> D1 & D2
B --> E1 & E2 & E3
B --> F1
# 构建
npm run build
# 开发模式运行
npm run dev
# Inspector调试
npm run inspect
# 生产运行
npm start
在Claude Code中执行以下任务,验证全部五大特性:
场景一:结构化输出
查询数据库中有多少admin角色的用户
预期:调用query_users,返回结构化的用户数据。
场景二:Tool Annotations
检查production环境中web-api服务的部署状态
预期:调用check_deploy_status(readOnlyHint=true),自动批准。
场景三:Elicitation
将web-api v3.0.0部署到production环境
预期:调用deploy_service(destructiveHint=true),Claude Code要求确认。
场景四:资源模板
读取production_db中users表的schema
预期:读取db://production_db/tables/users/schema资源。
场景五:进度通知
对production_db执行cleanup批处理操作
预期:调用batch_process,实时显示进度百分比。
在选择使用哪个特性时,参考以下决策树:
graph TD
Start[构建MCP工具] --> Q1{工具是否返回结构化数据?}
Q1 -->|是| F1[使用 Structured Output<br/>定义outputSchema]
Q1 -->|否| Q2{工具有副作用吗?}
F1 --> Q2
Q2 -->|只读| A1[readOnlyHint: true<br/>destructiveHint: false]
Q2 -->|有修改| A2[destructiveHint: true/false<br/>根据实际情况]
A1 & A2 --> Q3{执行中需要用户决策?}
Q3 -->|是| F3[使用 Elicitation<br/>动态请求确认]
Q3 -->|否| Q4{执行超过30秒?}
Q4 -->|是| F5[实现进度通知<br/>考虑Async Tasks]
Q4 -->|否| Done[完成]
F3 --> Done
F5 --> Done
style F1 fill:#4CAF50,color:white
style F3 fill:#FF9800,color:white
style F5 fill:#2196F3,color:white
| 工具类型 | 推荐组合 | 示例 |
|---|---|---|
| 查询类 | Structured Output + readOnlyHint | 查询用户、检索日志 |
| 修改类 | Annotations + Elicitation | 数据库迁移、配置变更 |
| 长任务 | Annotations + Progress | 批量处理、大规模部署 |
| 浏览类 | Resource Templates + Completions | 浏览数据库、文件系统 |
当服务器需要远程访问时,从stdio传输切换到Streamable HTTP:
// src/index-http.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamablehttp.js";
import { createServer } from "http";
import { randomUUID } from "crypto";
// ... 同样的注册逻辑 ...
const httpServer = createServer(async (req, res) => {
if (req.method === "POST") {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: randomUUID,
});
await server.connect(transport);
await transport.handleRequest(req, res);
}
});
httpServer.listen(3000, () => {
console.error("MCP Server listening on http://localhost:3000");
});
{
"mcpServers": {
"modern-mcp-remote": {
"url": "http://localhost:3000/mcp",
"transport": "streamable-http"
}
}
}
| 问题 | 原因 | 解决方案 |
|---|---|---|
structuredContent为空 |
SDK版本过低 | 确保@modelcontextprotocol/sdk≥1.12.0 |
| Tool Annotations不生效 | 注解位置错误 | 注解应在工具名和handler之间 |
| Elicitation无响应 | 客户端不支持 | Claude Code 1.0.18+才支持 |
| 资源模板404 | URI格式不匹配 | 检查大括号{}参数是否正确 |
| 进度通知未显示 | 未传递progressToken | 客户端需在_meta中传递token |
| stdio连接失败 | 入口文件路径错误 | 使用绝对路径或正确的cwd |
在服务器中添加日志输出(输出到stderr,不影响MCP协议通信):
server.server.setRequestHandler("ping", async () => {
console.error("[DEBUG] Ping received");
return {};
});
| 维度 | 评估 |
|---|---|
| 适合训练营 | MCP高级开发训练营、AI工具链工程师训练营 |
| 教学时长 | 4-5小时(含实操) |
| 学员基础 | 需掌握TypeScript、MCP三原语 |
| 实操比重 | 70%(代码实现)+ 30%(概念讲解) |
| 分组建议 | 2人一组,一人负责Tools层,一人负责Resources层 |
| 考核方式 | 独立完成一个包含全部五大特性的MCP服务器 |
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
