MCP Server 开发实战 & Skill 生命周期管理

2026-06-10MCPSkill开发实战架构设计

第一部分:MCP Server 从零开发

为什么需要自己写 MCP Server

社区有大量预建的 MCP Server(文件系统、GitHub、Slack、数据库等),但企业场景中的内部 API、专有数据源、定制业务流程仍然需要自建 Server。MCP 的价值恰恰在于让你用统一协议封装任何后端能力。

MCP Server 的最小实现

一个 MCP Server 本质上是一个进程,通过 stdio 或 HTTP SSE 与 Host 通信,暴露 Tools、Resources、Prompts 三种能力:

# Python MCP Server 最小实现
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationCapabilities
from mcp.server.stdio import stdio_server
import mcp.types as types

# 1. 创建 Server 实例
server = Server("my-knowledge-server")

# 2. 注册工具(Tool)
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="search_knowledge",
            description="搜索内部知识库。",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "搜索关键词"
                    },
                    "top_k": {
                        "type": "integer",
                        "default": 5,
                        "minimum": 1,
                        "maximum": 20
                    }
                },
                "required": ["query"]
            }
        )
    ]

@server.call_tool()
async def handle_call_tool(
    name: str, arguments: dict
) -> list[types.TextContent | types.ImageContent]:
    if name == "search_knowledge":
        query = arguments["query"]
        top_k = arguments.get("top_k", 5)
        results = await search_kb(query, top_k)
        return [types.TextContent(
            type="text",
            text=json.dumps(results, ensure_ascii=False)
        )]
    raise ValueError(f"Unknown tool: {name}")

# 3. 注册资源(Resource)
@server.list_resources()
async def handle_list_resources() -> list[types.Resource]:
    return [
        types.Resource(
            uri="knowledge://stats",
            name="知识库统计",
            description="知识库的文档数量、分类分布等统计信息",
            mimeType="application/json"
        )
    ]

@server.read_resource()
async def handle_read_resource(uri: str) -> str:
    if uri == "knowledge://stats":
        stats = await get_kb_stats()
        return json.dumps(stats, ensure_ascii=False)
    raise ValueError(f"Unknown resource: {uri}")

# 4. 启动 Server
async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream, write_stream,
            InitializationCapabilities(
                sampling={},
                experimental={},
            )
        )

if __name__ == "__main__":
    asyncio.run(main())

MCP 配置(Host 端)

Server 写好后,需要在 Host(如 Claude Desktop)中配置:

{
  "mcpServers": {
    "my-knowledge-server": {
      "command": "python",
      "args": ["-m", "my_knowledge_server"],
      "env": {
        "API_KEY": "${MY_API_KEY}",
        "DATABASE_URL": "postgresql://..."
      }
    }
  }
}

stdio vs SSE:何时用哪个

维度stdioSSE (HTTP)
适用场景本地工具、单用户远程服务、多用户、微服务架构
启动方式Host 启动子进程独立部署的 HTTP 服务
部署复杂度低(跟随 Host)中(需要独立的服务部署)
多客户端不支持天然支持
认证不需要(本地进程)需要(API Key / OAuth)
网络要求需要 HTTP 可达

MCP Server 的错误处理

@server.call_tool()
async def handle_call_tool(name: str, arguments: dict):
    try:
        if name == "search_knowledge":
            return await handle_search(arguments)
        elif name == "query_database":
            return await handle_query(arguments)
    except ValidationError as e:
        return [types.TextContent(
            type="text",
            text=f"参数错误:{str(e)}\n正确格式请查看工具定义。"
        )]
    except TimeoutError:
        return [types.TextContent(
            type="text",
            text="操作超时,请尝试缩小查询范围或稍后重试。"
        )]
    except Exception as e:
        # 不暴露内部错误细节给 LLM
        logger.error(f"Tool {name} failed: {e}", exc_info=True)
        return [types.TextContent(
            type="text",
            text=f"执行 {name} 时发生内部错误,已记录日志。"
        )]

MCP 的四个高阶模式

1. MCP Server 链(Chaining)

一个 MCP Server 可以作为另一个 Server 的 Client:

# 编排 Server:聚合多个子 Server 的能力
KnowledgeServer → 知识库搜索
  └─ 调用 → GitHubServer → 获取代码仓库信息
  └─ 调用 → SlackServer → 获取团队讨论记录
  └─ 返回 → 综合结果:"关于 X 主题,知识库有3篇文档,
              GitHub有2个相关PR,Slack有5条讨论"

2. MCP 动态工具注册

某些场景下工具列表不是静态的(如 SaaS 平台的按租户定制的工具),可以用 list_tools 的动态实现:根据请求上下文(通过 HTTP Header 传递的 API Key)返回不同的工具列表。

3. MCP 资源流式传输

对于大文件或实时数据流,用 resources/subscribenotifications/resources/updated 实现推送更新。

4. MCP Sampling(Server 请求 LLM)

Server 可以主动请求 Host 调用 LLM,用于 Agent 间的交叉验证或自省:

# Server 请求 LLM 验证自己的输出
result = await server.create_message(
    messages=[{
        "role": "user",
        "content": {
            "type": "text",
            "text": f"请验证以下搜索结果是否与查询'{query}'相关:{results}"
        }
    }],
    max_tokens=200
)

第二部分:Skill 系统架构与生命周期

Skill 系统的三层架构

一个成熟的 Agent 平台通常有三层能力组织:

┌─────────────────────────────────────────────┐
│  Layer 3: Skill(技能)                       │
│  完整的业务能力单元                            │
│  例:旅行规划、文档生成、数据分析               │
│  特点:多步骤、有状态、有降级策略               │
├─────────────────────────────────────────────┤
│  Layer 2: Tool(工具)                        │
│  可被 Agent 直接调用的单一功能                  │
│  例:搜索文档、发送邮件、查询数据库              │
│  特点:无状态、原子性、独立执行                  │
├─────────────────────────────────────────────┤
│  Layer 1: MCP Server / API(基础能力)         │
│  底层数据源和服务接口                           │
│  例:文件系统、数据库、第三方API                │
│  特点:与 Agent 解耦、标准化协议                 │
└─────────────────────────────────────────────┘

Skill 的生命周期管理

Skill 生命周期:
  注册 → 发现 → 激活 → 执行 → 结果回传 → (更新/卸载)
   │      │      │      │        │
   │      │      │      │        └─ 输出结果 + 状态持久化
   │      │      │      └─ Agent 驱动:ReAct 循环中调用工具
   │      │      └─ 上下文注入:SKILL.md 内容进入 System Prompt
   │      └─ 触发匹配:用户输入命中触发条件
   └─ 静态注册:文件系统中的 SKILL.md 被扫描加载

  热更新流程:
  1. 文件系统监控 SKILL.md 变化
  2. 变更的 Skill 重新解析 → 更新内存中的 Skill 注册表
  3. 新会话使用新版本,已有会话保持旧版本(避免状态不一致)

SKILL.md 触发词设计的工程实践

SKILL.md 的触发条件设计直接影响 Skill 的召回率:

  • 关键词触发:覆盖常见表达。例:旅行 Skill → "旅行、旅游、出行、攻略、去哪玩、订机票、订酒店"
  • 语义触发:用 Embedding 做意图匹配。"帮我规划一个三天两夜的行程"→ 语义相似度匹配旅行 Skill
  • 排除规则:明确不触发的场景。"单纯的天气查询不要触发旅行 Skill"
  • 优先级:多个 Skill 同时被触发时的仲裁规则。按 specificity(特异度)排序,更具体的优先

Skill 之间的协作模式

模式说明示例
顺序协作Skill A 的输出 → Skill B 的输入数据分析 Skill → 报告生成 Skill
并行协作多个 Skill 同时独立执行同时查天气、查酒店、查景点
聚合协作多个 Skill 的结果汇总旅行 Skill 汇总天气+交通+景点
回退协作Skill A 失败时降级到 Skill B实时API失败 → 用预置数据

Skill 的版本管理与灰度发布

# Skill 版本策略
skill_registry = {
    "travel-planner": {
        "versions": {
            "v1.0.0": {"path": "skills/travel/v1", "status": "deprecated"},
            "v1.1.0": {"path": "skills/travel/v1.1", "status": "stable"},
            "v2.0.0-beta": {"path": "skills/travel/v2", "status": "beta"},
        },
        "routing": {
            "strategy": "weighted",  # 加权路由
            "weights": {"v1.1.0": 0.9, "v2.0.0-beta": 0.1}  # 10% 流量到 beta
        }
    }
}

第三部分:Agent 后端架构模式精华

后端 Agent 的五个横切关注点

无论 Agent 的业务逻辑是什么,这五个横切关注点必须被正确设计:

1. 会话管理

# 会话状态机
[IDLE] → 用户发消息 → [RUNNING] → 工具调用 → [WAITING_TOOL]
                                            ↓
                                    工具返回 → [RUNNING] → 完成 → [IDLE]

# 关键设计决策
- 会话超时:空闲 30 分钟自动清理
- 并发言论:同一会话的多个请求必须串行化
- 中断恢复:用户中断后如何优雅停止工具执行
- 会话迁移:WebSocket 断线重连后恢复状态

2. 流式响应的背压处理

SSE 流式输出时,如果前端消费慢于后端生产(移动网络、性能差的设备),需要背压机制:

  • 后端用有界队列缓冲事件(如最多 100 个待发送事件)
  • 队列满时丢弃旧的中间状态事件(thinking、tool_start),但保留 token 和最终结果
  • 或使用 TCP backpressure 原生机制(asyncio 的 write() 返回时会等待)

3. 多模型路由

class ModelRouter:
    """根据任务类型路由到最优模型"""

    ROUTING_RULES = {
        "classification": "gpt-4o-mini",      # 便宜够用
        "summarization": "claude-haiku",       # 快速总结
        "complex_reasoning": "claude-sonnet",  # 强推理
        "code_generation": "deepseek-coder",   # 代码专长
        "creative_writing": "gpt-4o",          # 创意写作
        "tool_selection": "gpt-4o-mini",       # 工具选择不需要最强模型
        "safety_check": "gpt-4o",             # 安全审查需要强模型
    }

    async def route(self, task_type: str, budget: float):
        primary = self.ROUTING_RULES.get(task_type, "gpt-4o-mini")
        # 根据预算和可用性做 fallback
        return self.select_with_fallback(primary, budget)

4. 多租户隔离

隔离级别方式适用场景
逻辑隔离同数据库不同 schema / 租户ID过滤SaaS,成本低
资源池隔离每个租户独立的数据库/Redis 实例高安全要求
环境隔离每个租户独立的部署实例金融/医疗等强合规

5. 成本控制

  • 按会话预算:每个会话最多消耗 $X,超出后降级到便宜模型
  • 按用户配额:免费用户每天 50 次调用,付费用户 500 次
  • 智能缓存:相同/相似问题直接返回缓存答案
  • 模型降级链:Sonnet → Haiku → gpt-4o-mini → 本地小模型
  • Token 预算:每次 Agent 推理限制总 Token 消耗(含工具调用往返)

Agent 后端的三个反模式

反模式症状正确做法
单一巨型 Prompt把所有指令、工具、记忆全塞进 System Prompt分层注入:核心指令常驻,工具按需注入,记忆动态加载
同步阻塞式工具调用工具执行期间 Agent 完全阻塞并行执行无依赖的工具,用异步非阻塞模式
无状态 Agent每次请求重新初始化所有上下文会话级状态管理,预热公共资源(如 Embedding 模型)

Agent 开发的问题排查速查表

问题最快定位方法最常见原因
Agent 不调用工具检查 System Prompt 中工具定义是否正确注入工具描述太模糊、JSON Schema 格式错误
调用了错误的工具检查该工具与其他工具的 description 是否有歧义相近工具描述缺少区分说明
工具参数错误在工具执行层加参数校验日志JSON Schema 缺少 enum/format 约束
ReAct 死循环检查最近 N 步是否重复相同 Action工具返回空值/格式异常导致 LLM 重试
答案幻觉(有 RAG)对比 LLM 输出和检索文档,检查一致性检索结果不相关或 chunk 截断丢失关键信息
答案幻觉(无 RAG)要求 LLM 在回答中引用来源Prompt 缺少"不知道就说不知道"的指令
响应延迟高用 Trace 定位每个环节的耗时Embedding 服务慢、向量检索慢、工具超时
Token 消耗异常高统计每步 Token 用量,找出膨胀点工具结果太长、System Prompt 过大、死循环
未标记