
前段时间把一个内部 Agent 的十几个工具函数从硬编码改挂到 MCP 上之后我最大的感受是接入方式终于不像在补窟窿了。过去每加一个能力就要改主流程、改参数解析、改权限判断现在能力的定义、注册、路由全被收拢到 MCP 这一层。用一句话概括就是——我相当于给 Agent 单独建了一个控制平面然后用 MCP 把技能一个个引进去。这篇文章想把这条链路讲透为什么需要控制平面、MCP 在这里面到底扮演什么角色、一个“技能”从定义到被 AI 调用中间经历了什么以及我实际落地时踩过的坑。适合正在做 AI Agent 开发、想要规范化管理工具能力、或者对 MCP 协议只听过没用过的同学看完就能照着搭一套。1. 先别急着写工具函数控制平面到底在控制什么1.1 用转发平面做对比理解控制平面“控制平面”这个词最早是从网络领域来的。路由器、交换机内部被拆成两个逻辑层一个是控制平面负责路由计算、策略下发、设备配置说白了就是“大脑”另一个是转发平面也叫数据平面负责真正把数据包从一个端口搬到另一个端口讲究的是速度和稳定性。这两个平面分离的好处是控制平面可以慢慢算、全局看、随时改策略而转发平面只需要听指令干活不用关心“为什么走这条路”。所以在网络设备里控制平面挂了会导致全网路由震荡转发平面挂了只会影响个别流量转发。咱们做 Agent 的时候其实也天然存在这两个平面模型决策、工具选择、参数生成、权限判断这是控制平面而真正执行一个技能、调用一个 API、操作一个文件这是转发平面。可惜大多数人一开始根本没分这么细所有逻辑全糊在一坨代码里模型要调工具就直接从主流程里拿到函数引用参数也不校验权限也没有结果就是能力越来越多系统越来越脆。理解了这个对比再看“通过 MCP 控制平面引入技能”这个标题思路就清晰了MCP 不是简简单单帮你暴露几个函数给模型调它是在 Agent 的架构里抽出一层标准的控制平面然后把技能作为“可被发现的资源”注册上去模型要什么能力控制平面动态响应什么能力。1.2 Agent 里的控制平面缺失症我见过不少团队的工具集成方式是这样的在代码里写一个超级大的execute_tool(tool_name, params)里面用 if-else 或 switch 堆了几十个工具分支。今天加一个“查天气”明天加一个“读数据库”后天再加一个“操作 Figma”。表面上看很灵活但用一段时间就会出问题。问题一是能力不可发现。模型只能通过提示词知道你有哪些工具一旦工具数量超过几十个提示词里就塞不下了。问题二是权限没法精细控制。所有工具在一个进程里要么全能用要么全不能用风险极高。问题三是工具的实现和协议强耦合。比如某个工具原来是 REST API改成 gRPC 后主流程代码又要动一遍。问题四是没法热更新。想加一个新技能必须重新发布整个 Agent这在线上的迭代效率低到难以接受。这些问题本质上都是“控制平面缺失症”。也就是说缺少一个统一的、可持续演进的层去管理技能的注册、发现、路由、校验、审计。而 MCP 的架构恰好补上了这一层。MCP Server 可以理解为一个技能容器MCP Host 可以理解为一个技能调度中心模型只需要说“我要做什么”Host 通过 MCP 协议去控制平面里找“谁可以做”然后把请求路由过去再把结果拿回来。2. MCP 为什么适合当这层控制平面2.1 MCP 的三层模型和核心原语MCPModel Context Protocol本质上是一套基于 JSON-RPC 2.0 的通信协议定义了 AI 应用Host、协议客户端Client、能力服务端Server三者之间的互动方式。你可以把 Host 理解为 AI 应用本身比如 Claude Desktop、Cursor、自研的 AgentClient 是 Host 里面负责跟外部 Server 通信的组件一个 Host 可以连接多个 Client而 Server 就是具体能力提供方每个 Server 专注于一类技能。这套架构最核心的价值是标准化了三个原语Tools、Resources、Prompts。Tools 是可执行的动作比如“搜索代码库”“生成图片”“读取数据库”Resources 是可读取的数据源比如本地文件、工程配置、文档内容Prompts 是可复用的提示词模板算是技能使用时的“配方”。其中 Tools 最接近我们常说的“技能”也是控制平面里最关键的编排对象。协议层面的交互流程也不复杂Client 启动时向 Server 发送initialize请求做能力协商然后调用tools/list拉取技能清单模型决定要用哪个技能时Client 再发tools/call并把参数传过去。整个过程基于 JSON-RPC 2.0消息格式统一任何语言都能实现。这里有个容易被忽视的点MCP 之所以适合做控制平面不是因为它能传输多少数据而是它把“能力发现”和“能力调用”做成了协议标准。Agent 不再需要提前知道每个工具的长相只要连上 Server就能通过tools/list动态发现当前有哪些技能可用。这在动态扩展场景里价值非常大。2.2 与普通 API、Agent Skill 的区别有的人会觉得MCP 不就是把 API 包装了一下吗我用 OpenAPI 定义接口再写个 function calling 的适配器效果也差不多。这个说法对了一半。普通 API 解决的是“怎么调”MCP 解决的是“怎么让 AI 知道可以调、什么时候调、调完怎么处理”。MCP 在协议层把 JSON Schema 参数校验、能力列表广播、传输方式抽象全做了你不需要自己造一套工具注册中心。那 Agent Skill 又是什么现在不少平台提出了 Skill 概念比如 Claude 的 Agent Skills 或 Codex 的 skills。它跟 MCP 的区别我打个比方Skill 更像是一本“操作手册”它告诉模型“遇到这类任务时应该按什么步骤做”本质是提示词和知识注入不一定会调用外部程序而 MCP Tool 更像一个“动力机械臂”它直接让模型在真实环境里执行动作、拿到结果。实际项目里两者经常配合使用先用 Agent Skill 把任务拆解思路写清楚让模型知道“遇到 SQL 注入要先枚举字段再尝试绕过”具体执行时再用 MCP 去调 sqlmap 或者其他扫描工具。也就是说Skill 管方法论MCP 管执行力。控制平面既需要方法论模板也需要能力接入点MCP 负责的是后者但好的实现平台往往两个都支持。3. 设计一套“技能引入”方案从注册到调用3.1 先定义清楚“技能”在系统里的形态开始写代码之前一定要先把技能这个概念在系统里的形态定下来。我的做法是每个技能 一个独立的可调用能力 一段描述性元数据 一组参数约束 一套权限标签。这四个部分缺一不可。能力本体很好理解就是具体函数或命令。元数据是给模型看的包括技能名称、功能描述、适用场景、返回值格式。参数约束是给校验器看的用 JSON Schema 描述每个参数的类型、必填性、取值范围。权限标签是给控制平面看的标记这个技能谁可以用、是否需要敏感操作确认、是否只读等。我踩过不少坑之后才意识到很多 MCP Server 技能调用的失败根源不在功能实现而是“描述写得不够好”。比如一个技能 function 叫search描述只写“搜索”模型根本不知道它搜的是代码、文件还是数据库。正确的描述应该长这样“在指定 Git 仓库中搜索包含关键词的代码返回文件路径和行号适用于排查代码逻辑或定位报错来源。”模型读完之后匹配准确率会明显提升。3.2 核心流程拆解注册、发现、路由、调用、反馈把技能引入 MCP 控制平面后一次完整调用会走过五个环节我逐个拆一下。注册是起点。技能不是写进代码就算注册而是要在 MCP Server 启动时组件式声明。用 Python SDK 的话可能是装饰器mcp.tool()用 TypeScript SDK 的话是在 server 实例上调用server.tool()。注册时同时把元数据带上这样tools/list返回时模型才能看到。发现是模型侧的行为。模型在生成本次任务的执行计划时会先查看当前连接的所有 Server 的能力清单。这个过程看起来是“想一下”实际是 Host 先把tools/list的结果拼进上下文或者按需向 Server 拉取。也就是说MCP 控制平面要保证清单是新鲜的增删技能后客户端要能感知。路由是控制平面最核心的决策环节。假设你挂了五个 Server每个 Server 里又有三五个工具那么“当前任务该调用哪个工具”这个决策是由模型根据描述和上下文做的。控制平面要做的是把这个决策转化为具体连接也就是找到持有该技能的 Server把请求发过去而不是自己写死路由表。调用是执行环节。Server 收到tools/call后解析参数、执行函数、返回结果。这个环节最容易出问题的是参数类型不匹配模型给的是字符串你的函数要整数所以 Server 端一定要做类型转换和校验。反馈是收尾但也很关键。执行结果要按 MCP 协议包装成结构化内容返回失败时要返回带错误码的响应不能直接抛异常。这样 Host 才能把异常信息交给模型做下一步决策。比如重试、换一种调用方式、或者直接告诉用户工具不可用。3.3 一个可落地的目录结构设计到目录层我会推荐用“一个 Server 负责一个领域”的粒度。技术类 Agent 常见的拆分方式是这样的skillhub/ ├── servers/ │ ├── code_search/ # 代码搜索技能 │ │ ├── server.py │ │ └── config.json │ ├── design_tool/ # 设计稿读取技能比如 Figma │ │ ├── server.py │ │ └── config.json │ ├── db_ops/ # 数据库操作技能 │ │ ├── server.py │ │ └── config.json │ └── security_scanner/ # 安全扫描技能 │ ├── server.py │ └── config.json ├── clients/ │ ├── claude_config.json │ └── cursor_mcp.json └── shared/ ├── auth.py └── logger.py不是所有场景都要拆得这么细。如果你只是本地个人项目一个 Server 就够但如果你想把它做成团队基础设施建议按照领域隔离这样权限好控制、故障不会互相影响、更新也能独立发布。控制平面本来就是用来收敛复杂度的如果自己先搞成一个大而全的 Server那等于没分。4. 实操把一套技能真正挂上 MCP4.1 用 FastMCP 写一个技能服务器现在到动手环节。我用 Python 官方 SDK 的 FastMCP 封装来示范它比原生 Server 类简洁很多适合快速把技能暴露出去。先装基础依赖pip install mcp然后创建一个server.py我定义为skillhub-dev里面先放两个典型技能一个本地代码搜索一个模拟的设计稿信息读取。from mcp.server.fastmcp import FastMCP import os import glob mcp FastMCP(skillhub-dev) mcp.tool() def search_code(keyword: str, path: str .) - list[dict]: 在指定目录下递归搜索包含关键词的代码文件。 用于定位报错来源、查找函数定义、梳理调用关系。 results [] for root, _, files in os.walk(path): # 忽略常见依赖目录避免把 node_modules 里的大文件都扫进来 if any(part in root for part in [node_modules, .git, venv, __pycache__]): continue for f in files: if not f.endswith((.py, .js, .ts, .go, .java, .md)): continue full_path os.path.join(root, f) try: with open(full_path, r, encodingutf-8, errorsignore) as fp: lines fp.readlines() except Exception: continue for idx, line in enumerate(lines, 1): if keyword in line: results.append({file: full_path, line: idx, content: line.strip()}) break return results[:50] mcp.tool() def get_design_annotation(file_key: str, node_id: str) - dict: 读取设计稿中某个节点的标注信息类似 Figma 的标注能力。 输入文件 key 和节点 id输出该节点的尺寸、颜色、字体等样式信息。 # 这里原本应调用真实 Figma API示例环境里直接返回模拟数据 # 实际部署时把 token 放到环境变量 FIGMA_ACCESS_TOKEN并从请求头带过去 if not file_key or not node_id: return {error: file_key 和 node_id 不能为空} figma_token os.getenv(FIGMA_ACCESS_TOKEN, ) if not figma_token: return {error: 缺少 FIGMA_ACCESS_TOKEN 环境变量} # 伪装一段标注数据 return { node_id: node_id, name: Primary Button, width: 120, height: 40, background: #2563EB, font_size: 14, font_family: Inter, text_color: #FFFFFF, source: figma-mock } if __name__ __main__: mcp.run()这段代码里的细节值得多说几句。第一描述信息要写成“给模型看的说明书”而不是给开发者看的注释。第二search_code里忽略依赖目录是非常实用的习惯不然模型一次可能会检索出几千个结果直接把上下文撞爆。第三get_design_annotation里先校验环境变量能显著降低因为配置缺失导致的调用失败率。4.2 配置客户端Claude Desktop / Cursor / 自研 Host技能服务器写好了下一步就是让客户端连上来。我用 Claude Desktop 举例配置文件通常在~/Library/Application Support/Claude/claude_desktop_config.jsonmacOS或对应系统目录。核心是声明mcpServers{ mcpServers: { skillhub-dev: { command: python, args: [/absolute/path/to/skillhub/server.py], env: { FIGMA_ACCESS_TOKEN: 你的figma_token } } } }Cursor 里面的配置略有不同是在项目根目录或全局配置里维护一个mcp.json{ mcpServers: { skillhub-dev: { type: stdio, command: python, args: [/absolute/path/to/skillhub/server.py], env: { FIGMA_ACCESS_TOKEN: 你的figma_token } } } }如果你的 Server 部署在远程服务器上客户端配置就不是command args而是url headers。比如使用 Streamable HTTP 传输时是这样{ mcpServers: { skillhub-remote: { type: http, url: https://mcp.example.com/mcp, headers: { Authorization: Bearer your_token } } } }初次接入的人常犯一个错误改了配置后不重启客户端然后抱怨工具不生效。MCP 的握手和工具发现基本都在启动阶段完成配置变更后一定要完整重启。后面排查章节我会再细说。4.3 验证技能引入链路服务器启动、客户端配置好之后怎么确认技能真的被引入成功我不会只凭界面上看到“MCP connected”就放心而是会做一次全链路验证。第一步直接在终端启动 server确认没有报错python /absolute/path/to/skillhub/server.py正常能看到 FastMCP 的日志输出监听在 stdio 或某个端口上。第二步使用 MCP Inspector 这类调试工具连接。可以直接跑npx modelcontextprotocol/inspector python /absolute/path/to/skillhub/server.py然后浏览器打开 Inspector 面板点List Tools能看到注册出来的search_code和get_design_annotation。第三步在面板里手动调用一次search_code传一个你确定存在的关键词观察返回结果。这一步能验证参数传递和函数执行是否正常。第四步回到 Claude Desktop 或者 Cursor新建一个会话用自然语言说“帮我搜一下代码里哪里有 xxx”看模型是否自动选择了search_code这个技能并且正确填参数。如果四步全通说明技能已经成功进入 MCP 控制平面Agent 能在合适的任务里主动使用它。整个过程的关键逻辑是先确认 Server 本身没问题再确认协议层能发现技能最后才测试模型会不会用这个顺序能帮你快速定位问题在哪一段。5. 典型问题排查与避坑清单5.1 接入过程中最常见的 6 个问题技能挂上 MCP 之后日常维护不会永远一帆风顺。我把这段时间遇到的问题整理成一张速查表按出现频率排序现象常见原因处理办法客户端提示连接失败配置里的 command 路径错误或 python 环境不对在终端手动执行配置里的命令先确认能跑起来工具列表里看不到新技能Server 没正确注册或客户端没重启用 Inspector 连上看 tools/list确认后有就重启客户端模型明明可用却一直不调工具技能描述太模糊模型不知道什么时候该用重写描述明确“适用于什么任务、能拿到什么结果”调用时参数报错JSON Schema 里类型和实际不一致在 Server 端做类型转换别依赖模型一次传对超时严重技能函数本身耗时长比如扫描整个仓库把耗时任务异步化或增加 MCP 请求超时时间权限失效token 放在配置里但没正确注入环境变量在 Server 里加启动即校验缺失就直接抛配置错误还有一个我特别想强调的stdio 传输出问题是比较隐蔽的一种。当你在配置里用command: python去启动某个虚拟环境的脚本时如果客户端进程里python指向的是系统 Python 而不是虚拟环境 Python就会出现“命令行能跑、配置里跑不了”的诡异情况。解决方法是 args 里写绝对路径的 Python 解释器例如/Users/me/.venv/bin/python。5.2 独家避坑经验排查问题多了就总结出几条别人文档里不会写但实战很管用的经验。经验一技能名称千万不要起得太普通。我之前有个 Server 里放了个工具叫query结果模型在拿不准的时候老把它当成万能数据库查询器传了一堆它处理不了的参数。后来改成query_sales_summary_by_date_range并在描述里写明“只用于销售汇总不做明细查询”误用率立刻降了下来。技能名实质上也是控制平面里路由决策的一部分起名要带着“边界”意识。经验二不要把多个 Server 塞进同一个进程。有人为了省事在一个 Python 进程里偷偷启了多个 MCP Server 实例结果一个技能崩溃整个进程一起退出所有技能全挂。控制平面本来就是要做故障隔离的你别自己又绕回去。经验三日志要输出到 stderr不要输出到 stdout。MCP 的 stdio 传输模式里stdout 承担着协议数据的传输职责如果你在函数里写print(hello)这行文字会混进 JSON-RPC 消息流导致客户端解析失败。这一点踩中的人非常多我自己也掉过一次调试了半天才发现是日志污染了协议通道。正确做法是用logging模块输出到 stderr或者直接重定向到文件。经验四MCP 配置里的敏感信息不要写死。环境变量注入是标准做法但很多人图方便直接写在 JSON 配置文件里然后不小心把配置提交到代码仓库token 就泄露了。建议开发环境用.env加载生产环境用密钥管理服务配置文件里只保留变量名。6. 生态现状与我的落地体会6.1 值得关注的 MCP 技能生态现在 MCP 的技能生态已经相当丰富了官方和社区都有大量现成 Server 可以直接引入没必要什么都自己写。设计协作类的有 Figma MCP可以在对话里直接读取设计稿节点信息、生成标注、对照 UI 给出修改建议对于做前端还原的团队很实用三维创作类的有 Blender MCP模型可以操控 Blender 场景里的物体、材质、动画省掉不少在 DCC 软件里手点的操作。开发工具类里Codex MCP、Chrome MCP Server 也都很成熟适合做浏览器自动化、网页信息抓取、代码生成等场景。安全攻防方向的工具链像 BurpSuite MCP、Kali MCP、Yakit MCP也被不少人接入到 AI Agent 里做辅助分析。这类场景尤其能体现 MCP 控制平面的价值安全工具的命令参数通常很长、协作链路复杂把技能封装好后模型可以直接按流程调用减少人工抄写命令的失误。还有一类我觉得很有意思就是把“技能树”这个思路跟 MCP 结合。比如 CTF 比赛、运维技能图谱、车控测试等领域如果把练习步骤做成 Prompt 技能Agent Skill把实操工具做成 MCP 工具再让 Agent 当“陪练教练”就能把学习场景变成“讲解、示范、练习、反馈”的闭环。这算是我看到的比较长远的方向MCP 不只是生产环境里的工具接入层也会成为技能培训、人才培养的基础设施。6.2 最后再分享一个小技巧所有技能都挂到 MCP 之后我最后做了一件事建了一个技能说明审查脚本用自动化检查每个工具的描述是否包含“适用场景、参数边界、返回结果”三要素。模型判断要不要调用工具本质上就是一个基于文本的匹配过程描述写得好不好直接决定技能的利用率。很多技能挂上了没人用不是能力不行而是描述写得像天书。把描述当成产品文案来写价值立刻不一样。踩过不少坑之后我现在接手新项目第一件事就是问两句话控制平面在哪技能是怎么注册和发现的如果答案含糊那这个 Agent 迟早要在工具管理上翻车。MCP 给了一套不错的答案剩下的就是看你怎么把它用好。