ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用MCP构建查Bug Agent:最小可用服务实战

用MCP构建查Bug Agent:最小可用服务实战 做开发这些年我越来越觉得查 Bug这件事的瓶颈不在脑子而在手。代码逻辑再复杂你坐下来一行行看总能看出个所以然真正让人崩溃的是从 IDE 切到终端、翻日志、查接口、跑测试、再切回来贴上下文——这个反复横跳的过程才是每天最磨人的地方。所以当 MCPModel Context Protocol这套东西开始火的时候我的第一反应不是又一个 AI 框架而是我能不能让 Agent 直接帮我把这些脏活干了。这个念头落地的结果就是一个不到几百行代码的查 BugMCP 服务Agent 可以自己读源码、搜关键词、翻日志、跑测试然后把结论连同证据一起甩给我。这篇文章不打算讲大道理就从一个最小可用的例子出发把 MCP 服务的角色、协议流程、工具设计和实操中的坑一次说清楚。适合所有想上手 Agent 开发、又不想被各种概念劝退的人。1. 先把概念拆开MCP 到底是什么凭什么能替你看 Bug1.1 没有 MCP 时Agent 看 Bug 有多费劲先回忆一下两年前我们是怎么让 AI 帮我们看 Bug 的把报错堆栈复制出来贴进聊天窗口再附上相关代码片段然后等 AI 给出一段看起来很有道理的分析。运气好它能指出问题运气不好它给你的建议完全建立在信息残缺之上比如对着一行KeyError就让你检查某个不存在的配置项。这里面的核心问题不是 AI 变笨了而是它的工作方式天然受限。你贴给它的代码是一个快照而 Bug 是活的同样的代码在不同的输入、不同的运行环境、不同的数据状态下表现可以完全不一样。你不可能把整个项目的源码、日志、配置、运行状态全部塞进上下文窗口更不可能让 AI 自己去验证它提出的假设。这就好比你请了一个经验丰富的专家却只让他隔着一扇玻璃门看工厂他只能凭你递给他的几张照片做判断想亲手拧一下阀门都不行。更麻烦的是 Agent 这类东西出现之后问题被放大了。Agent 的强项是自主行动但行动需要工具读文件、执行命令、查数据库、调接口。没有统一接口的时候每接一个工具就要写一套私有协议配置方式千奇百怪Agent 学得过来也维护不过来。于是大家开始意识到我们缺的不是更强的模型而是一个让模型和外部世界标准化连接的插座。MCP 就是在这样一个节点上出现的。1.2 MCP 的三方角色Host、Client、ServerMCP 的官方定义是模型上下文协议但我更愿意把它理解成AI 世界的 USB-C。回想一下 USB 接口统一之前的样子各种设备各用各的线桌面抽屉里永远缠成一团USB-C 出现之后一根线走天下接口标准统一了设备才真正开始百花齐放。MCP 做的事情本质上是一样的它把AI 与外部工具、数据源的连接方式统一成一套协议让模型和工具之间不再是一对一的私人订制而是即插即用的标准对接。在这个体系里有三个角色必须分清Host运行 Agent 的程序也就是持有模型的那一方比如 Claude Desktop、Cursor、IDE 插件或者你自己写的 Python 脚本。Host 负责调动模型能力也负责向模型展示 MCP 工具返回的结果。ClientHost 内部的连接器。每个 MCP Server 都会对应一个 Client 实例它负责维护连接、发送请求、接收响应。一个 Host 里可以同时挂多个 Client。Server提供能力的一方。它把文件系统、数据库、构建工具、日志系统包装成标准接口等着 Client 来调用。Server 对外暴露的是三类原语Tools、Resources、Prompts。Tools 是可执行的动作比如读文件跑测试Agent 会决定什么时候调用它Resources 是只读的数据比如一个配置文件、一段日志Agent 可以按 URI 读取Prompts 是可复用的提示词模板用来规范 Agent 在特定场景下的行为方式。后面我会用实际例子把这三样东西讲透。1.3 为什么调试场景适合做 MCP 的第一个实战项目坦白说MCP 有很多炫酷的玩法——接数据库、操作浏览器、控制设计软件但如果你只是想理解 MCP 的工作原理查 Bug 这个场景几乎是量身定做的入门项目。原因有三个。第一输入输出足够清晰。调试的核心操作是可枚举的看代码、搜关键字、看日志、跑测试。每一个操作都可以被封装成一个小工具逻辑简单不需要复杂的领域知识非常适合新手阅读和理解。第二安全边界好划。MCP 服务一旦接进来Agent 就拥有了执行能力如果一开始就做让 Agent 直接改代码、发请求这种高风险动作出问题都不知道从哪排查。而查 Bug 的第一版完全可以做成只读的能读、能搜、能跑测试但不能改任何文件。这个边界对新手极其友好也符合最小权限原则。第三价值反馈立竿见影。你花一个下午写几百行代码把服务挂进客户端然后对着一个真实的报错说一句帮我查一下看着 Agent 自己翻代码、定位、给出带证据链的分析那种这玩意儿真的能干活的体感比读十篇概念文章都强。所以接下来我们就按这个思路一步步把服务搭起来。2. 设计查 Bug服务先说边界再定工具2.1 安全边界能读、能搜、能跑但不能乱写动手写代码之前我建议先花十分钟想清楚一个问题你到底允许 Agent 碰哪些东西很多 MCP 教程上来就教你怎么写各种花哨工具却把权限设计一笔带过这是实战里最容易出事的地方。Agent 的能力越强它犯错造成的破坏就越大——它不是没有判断力而是它的判断力取决于你给它的工具描述和上下文约束一旦某个工具的边界含糊它就可能在错误的路径上执行写操作。我的建议是第一版把所有工具设计成只读 受限执行只读文件操作可以读项目内的文件但不能写、不能删、不能重命名。受限搜索只能在允许的目录范围内做正则搜索不能漫游到系统目录。可控命令执行如果允许 Agent 跑测试命令要写死模板比如只允许执行pytest 指定用例不允许让它构造任意 shell 命令。以我文章的示例服务为例我给它划定了根目录ROOT ~/work/demo-project所有文件读写都从这个根目录出发并且强制做路径解析校验——把用户传入的相对路径先拼到根目录下再调用resolve()解析出绝对路径最后检查这个绝对路径是否仍然位于根目录内。凡是越界的路径请求一律直接拒绝。这一步看着简单却能挡住绝大多数的路径穿越问题。另外一个隐藏边界是超时和资源限制。Agent 调用工具时可能会有意料之外的输入比如让它搜索一个耗时极长的正则或者读取一个几个 GB 的日志文件。设计工具时要设置合理的超时时间文件读取要有大小和行数上限。不要指望模型有分寸要把边界写在代码里。2.2 工具粒度怎么设计小而精确的工具才是好工具工具设计是 MCP 服务里最见功力的一环因为它直接决定了 Agent 能不能顺利完成任务。新手最常见的错误是设计一个超级工具——比如一个叫analyze_code的工具参数是文件路径返回值是分析结果。听起来很方便但这是把大量的判断逻辑藏进了工具内部Agent 对过程完全不可见它也学不会在什么情况下该用什么参数最后的结果就是你得到一个黑盒出了问题根本没法调试。我后来形成的习惯是把工具拆成原子操作一个工具只做一件事把组合和判断交给 Agent。比如查 Bug 服务第一版我定了这几个工具read_file(path, start_line, end_line)读取文件的指定行范围用于检查源码和配置。grep_search(pattern, directory)按正则搜索项目文件返回命中文件和行号。tail_log(path, num_lines)读取日志文件末尾 N 行用于抓取最新的异常堆栈。run_test(test_path)运行指定的单个测试用例返回通过/失败状态和输出。每个工具都小到不能再拆但组合起来Agent 就能完成看日志找异常 → 读源码定位 → 搜相关引用 → 跑测试验证的完整调试链路。更重要的是这套链路是 Agent 自己根据情况编排的它可以先看日志也可以先搜代码完全取决于它认为哪个线索更优先。这里有个容易被忽略的点工具的名字和描述是给模型看的不是给用户看的。模型不会读你的源码它只能通过工具名、参数 schema、description 来理解这个工具怎么用。所以描述要写清楚三件事这个工具是干什么的、参数的含义、以及什么时候该用它。比如tail_log的描述里我特意写了适合在收到异常报告后查看最新堆栈这就是在教 Agent 如何挑选工具。后面会有专门的篇幅聊参数描述的重要性这里先记住一句话工具描述写得越具体Agent 的表现越接近你想要的样子。2.3 Resources 和 Prompts 怎么配合 Tools 使用很多人在 MCP 里只用了 Tools把 Resources 和 Prompts 晾在一边这有点浪费。这两个原语在调试场景里其实很有用。Resources 适合暴露静态的、结构化的数据。比如你的项目有一个config.yaml或者一个已知的 issue 清单这些信息本就可以作为只读资源提供出来Agent 需要时直接按 URI 读取不必走工具调用。和工具的区别在于资源是数据工具是动作。数据让 Agent 获得背景知识动作让 Agent 改变外部状态。在设计上我习惯把那些每次调试都要看一眼的东西做成资源比如项目结构说明、错误码映射表、依赖清单这能让 Agent 在开场就建立起对项目的整体认知。Prompts 则适合暴露调试流程模板。你可以写一个debug-flow的 Prompt先让我tail_log查看最新日志再根据异常堆栈grep_search定位相关代码最后read_file确认根因并给出分析和修复建议。这个 Prompt 就像一份 SOPAgent 拿到它之后会按流程执行。不同项目的调试流程不一样有些团队喜欢先复现再分析有些喜欢先看最近变更把这些沉淀成 Prompt等于把团队的经验固化进了 Agent 的工作方式。不过要注意一条原则Resources 和 Prompts 解决的是上下文供给和行为规范真正的能力交付还是靠 Tools。三个原语缺一不可但别为了炫技去堆 Resources第一版有几个真正常用就够了。3. 协议级拆解一次替你看 Bug的完整调用链3.1 从握手到调用JSON-RPC 里发生了什么虽然你用 FastMCP 这种高层封装时协议细节基本不用手写但弄懂它在底层做了什么排查问题时会少走很多弯路。MCP 的消息格式基于 JSON-RPC 2.0一条请求包含jsonrpc、method、params、id四个字段响应则用id对应请求返回result或error。一次完整的调用链路是这样的Client 和 Server 建立连接后先发送initialize请求交换双方支持的协议版本、客户端信息、服务端能力列表服务端会回一个InitializeResult里面声明自己支持哪些功能比如是否支持工具、是否支持资源订阅。之后客户端发送notifications/initialized通知服务端我准备好了握手阶段结束。接着客户端会主动发送tools/list拿到 Server 端的工具清单——这一步是 Agent 被调度前客户端先把工具信息喂给模型参考。真正被调用时发送的是tools/call请求带上工具名和参数。下面是一个实际的tools/call请求报文{ jsonrpc: 2.0, id: 7, method: tools/call, params: { name: grep_search, arguments: { pattern: user_info.*role, directory: app } } }服务端如果成功执行会返回一个content数组里面的每一项是一个content对象可以是纯文本也可以是image或resource-link类型{ jsonrpc: 2.0, id: 7, result: { content: [ { type: text, text: app/auth.py:132 user_info[role] get_role(user_id) } ], isError: false } }值得一提的是isError字段。服务端如果希望把执行失败的详细信息交给模型分析和继续推理不应该把整个调用标记为协议错误而是把isError设为true并返回错误描述文本——这样模型仍能看到信息还能据此调整策略。比如run_test跑挂了这本身不是协议错误把 pytest 的输出放进content返回Agent 才能基于失败信息继续查。3.2 stdio 还是 HTTP传输方式怎么选MCP 支持多种传输方式目前最常用的是两种stdio和Streamable HTTP。选哪种基本取决于你的服务运行在哪里。stdio是最简单的方式Client 直接以子进程方式启动 Server两者通过标准输入输出通信。Host 配置里指定启动命令比如uv run bug_hunter.py剩下的都是管道的事。它特别适合本地工具链比如读取本地文件、跑本地测试、和 IDE 深度集成。配置简单、调试直观、不需要网络暴露是新手入门的最佳选择。实际操作时有个必须记住的坑stdio 模式下 Server 的输出通道被协议占用了print()这类写到标准输出的行为会污染协议流导致通信错乱。你要打印调试信息必须写到stderr。这个坑我一开始就踩过后面专门列了一节讲。Streamable HTTP则适合 Server 运行在另一个进程或另一台机器上的场景。Client 通过 HTTP 请求访问一个 URL服务端可以做鉴权、水平扩展、多客户端复用。比如你想让一个跑在云端容器里的 MCP 服务同时服务多个开发者的客户端或者把服务嵌入到一个 Web 应用里那就该用 HTTP。它的好处是可以远程调用坏处是配置复杂要做鉴权要管理会话状态还要处理防火墙、超时这些网络层问题。实际建议是第一版无脑选stdio先把功能跑通如果后续有远程协作、跨机器调用的需求再用Streamable HTTP迁移。别一开始就追求分布式很多团队的第一个 MCP 项目都是死在过度设计上。3.3 参数描述是给 Agent 看的说明书写不好就翻车工具的inputSchema用的是 JSON Schema 格式除了规定类型和必填项之外最重要的是给每个参数写清楚描述。我见过太多工具定义参数描述写着the path、the pattern这种等于没写的话模型只能靠猜。换成查 Bug 的服务视角来想read_file的path参数到底相对哪个根目录start_line是包含还是排除end_line不填代表读到哪这些不写清楚模型就会胡乱传参你得到的工具调用记录会充满 404 和越界错误。我这里放一个正反面对比看完你就明白差距在哪参数描述模型的理解实际效果path: 文件路径路径是相对当前工作目录还是绝对路径还是项目根目录经常传错服务端抛出 PermissionErrorpath: 相对项目根目录的路径例如 app/auth.py不允许绝对路径明确了基准点和格式约束一次通过错误率显著下降num_lines: 行数是最后 50 行还是从最后 50 开始的全部返回值忽多忽少num_lines: 读取日志末尾的行数最大 200默认 50边界和默认值一目了然行为稳定可预测另外一个细节是工具本身的description。很多 SDK 允许你在注解或者装饰器里写工具描述别浪费这个机会。好的工具描述应该包含触发时机也就是什么情况下该用这个工具。比如grep_search的描述我写成这样在项目内按正则搜索关键词返回命中的文件和行号。当需要确认某个变量或函数在哪里被引用时使用也适合在拿到异常关键词后快速定位相关代码。这样 Agent 在确认引用关系和根据报错搜代码两个场景下都会想到调用它。4. 从零实现一个能查 Bug 的 MCP 服务Python 版4.1 环境准备与项目结构技术选型上我用了 Anthropic 官方提供的 Python SDK因为它天然支持FastMCP这种高层封装写起来非常简洁一行输出一个工具适合快速原型。运行时我建议用uv它可以自动管理 Python 版本和依赖避免污染系统环境尤其在本地经常挂着多个项目时省心很多。先初始化项目并安装依赖mkdir bug-hunter-mcp cd bug-hunter-mcp uv init --bare uv add mcp[cli]这会生成一个最小项目。目录结构我习惯这样组织bug-hunter-mcp/ ├── pyproject.toml ├── bug_hunter_server.py # MCP Server 主入口 └── demo-project/ # 被调试的示例项目 ├── app/ │ ├── __init__.py │ └── auth.py └── logs/ └── app.log被调试的示例项目我建了一个极简的登录模块里面埋了一个真实的 Bug当用户信息里缺少某个字段时代码会直接抛KeyError导致登录接口 500。后面我们会让 Agent 亲手把这个 Bug 找出来。先把目录建好放一个带问题的auth.py再生成一份包含相关堆栈的app.log模拟线上环境。4.2 写一个最小可用的 Server现在写 MCP Server 主体。核心代码就几十行我先把完整版本放出来然后逐段解释from mcp.server.fastmcp import FastMCP from pathlib import Path import re import subprocess ROOT Path.home() / work / demo-project def safe_join(path: str) - Path: 将相对路径拼接到项目根目录并校验未越界。 p (ROOT / path).resolve() if not str(p).startswith(str(ROOT.resolve())): raise PermissionError(f路径越界: {path}) return p mcp FastMCP(namebug-hunter) mcp.tool() def read_file(path: str, start_line: int 1, end_line: int 200) - str: 读取项目内文本文件的指定行范围。 - path相对项目根目录的文件路径例如 app/auth.py不支持绝对路径 - start_line起始行号从 1 开始包含该行 - end_line结束行号包含该行最大不超过 500 适合查看源码、配置文件内容。 p safe_join(path) lines p.read_text(encodingutf-8).splitlines() if end_line 0 or end_line len(lines): end_line len(lines) if start_line 1: start_line 1 return \n.join(lines[start_line - 1:end_line]) mcp.tool() def grep_search(pattern: str, directory: str .) - str: 在项目内按正则搜索代码关键词返回命中的文件和行号。 - pattern正则表达式 - directory相对项目根目录的文件夹路径 当需要确认变量或函数被哪些位置引用或根据异常关键词定位代码时使用。 base safe_join(directory) hits [] rx re.compile(pattern) for path in base.rglob(*): if path.is_file() and path.suffix in {.py, .js, .ts, .yaml, .log, .json}: text path.read_text(encodingutf-8, errorsignore) for no, line in enumerate(text.splitlines(), 1): if rx.search(line): hits.append(f{path.relative_to(ROOT)}:{no} {line.strip()[:120]}) return \n.join(hits[:200]) or 没有命中任何记录注意到我把路径校验封装成了safe_join工具内部的逻辑都很直白read_file拆分文件为行、做截取grep_search递归匹配扩展名白名单。再做两个最有调试价值的工具mcp.tool() def tail_log(path: str, num_lines: int 50) - str: 读取日志文件末尾 num_lines 行默认 50最大 200。 当需要查看最近一次异常的堆栈信息时使用通常在接到报错反馈后优先调用。 p safe_join(path) if num_lines 200: num_lines 200 lines p.read_text(encodingutf-8, errorsignore).splitlines() return \n.join(lines[-num_lines:]) mcp.tool() def run_test(test_path: str ) - str: 运行 demo-project 下的 pytest 测试。 - test_path可空。为空则运行全部测试否则运行指定文件或用例例如 app/test_auth.py::test_login 执行失败只返回测试输出不代表协议错误。 base str(ROOT.resolve()) cmd [python, -m, pytest, test_path, --tbshort, -q] if test_path else \ [python, -m, pytest, --tbshort, -q] proc subprocess.run(cmd, cwdbase, capture_outputTrue, textTrue, timeout120) output proc.stdout proc.stderr return output[-3000:] if __name__ __main__: mcp.run(transportstdio)几个实现细节值得单独说一说。首先是路径安全tail_log和read_file都要经过safe_join日志文件放在项目内不允许 Agent 去读/etc/passwd。其次是run_test的命令做了模板化用固定参数列表拼接而不是让模型直接输入一串 shell 命令从根上堵住了命令注入。还有超时保护timeout120防止测试卡死返回输出尾部 3000 字符可以保证重要错误信息不丢失又不会让结果长得撑爆上下文。4.3 接入客户端并验证连通写好了 Server接下来要考虑的是怎么让 Host 认识它。如果你用 Claude Desktop可以在配置文件里加一个mcpServers字段claude mcp add bug-hunter -- uv run bug_hunter_server.py或者用命令行方式配置。如果你用的是 Codex、Cursor 这类支持 MCP 的客户端配置大同小异核心就是告诉它启动这个服务需要执行什么命令。以我的经验配置完成后第一件事不是马上问业务问题而是先做连通性验证让 Agent 直接说你有哪些工具看它能不能正确列出我们刚定义的四个工具claude -p 请列出你当前可用的 MCP 工具如果工具清单正常出现说明握手、tools/list、tools/call全链路已经通了。如果你看到的是一个空列表或者工具名对不上那大概率是配置命令不对、Python 环境路径有问题、或者进程启动失败——排查方向我们放到下一节统一讲。如果你想脱离现成客户端用脚本直接体验协议过程也可以写一个极简的 Python 客户端核心逻辑是建立 stdio 子进程、创建ClientSession、依次调用list_tools()和call_tool()from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client server_params StdioServerParameters( commanduv, args[run, bug_hunter_server.py], ) async def main(): async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: tools await session.list_tools() print([t.name for t in tools]) result await session.call_tool(tail_log, {path: logs/app.log}) print(result.content[0].text) if __name__ __main__: asyncio.run(main())这种方式虽然要多写几行但对理解协议流程特别有帮助你能亲眼看到一次调用从请求到响应的完整往返。4.4 跑个真实案例让 Agent 定位登录 500 的根因连通性验证通过之后就可以上真实场景了。我准备的auth.py里有一个典型 Bug登录时从数据库查到用户信息后直接访问user_info[role]字段但某些老用户的记录里没有这个字段于是抛KeyError接口 500。我给 Agent 下达的任务是帮我查一下登录接口为什么一直 500。Agent 的执行过程大致是这样的第一步它会调用tail_log查看最新日志拿到堆栈中关键的KeyError: role和app/auth.py:132这两条线索。这一步几乎是每个调试任务的起手式因为日志是最近的事实。第二步它会调用read_file读取app/auth.py第 120 到 140 行看到role user_info[role]这行代码并且很快意识到问题在于字典里不保证有这个键。第三步它会用grep_search搜索user_info在项目里的所有赋值位置确认这些数据来自数据库查询结果且没有做默认值填充。到这里根因已经浮出水面数据结构不兼容缺少容错处理。第四步它跑一遍run_test让回归测试失败用测试输出来证实自己的判断。最后它会给你一个结论auth.py:132直接对user_info做下标访问而来自旧账号的数据不包含role字段建议用user_info.get(role, member)替代或在写入时就补全默认值。整个过程中我没有贴过一行代码给它它靠的全是 MCP 工具拿到的第一手信息。你可以看到Agent 的推理链条比直接贴代码要扎实得多因为每一步都有可验证的证据。这也是 MCP 相对传统 Prompt 工程最有价值的地方它把阅读代码从用户的负担变成了 Agent 的能力。5. 实战中的坑连接、超时、权限与排查速查5.1 连接失败与握手超时先分清是进程问题还是协议问题MCP 服务接入过程中我遇到最多的问题不是代码逻辑错而是连不上。日志里常见的现象是客户端配置文件写好了启动后工具列表却是空的。这时候先别急着怀疑协议按照进程层 → 协议层 → 配置层的顺序排查。进程层的问题最容易发现如果 Server 启动命令里的可执行文件路径不对或者uv不在系统的 PATH 里子进程可能根本没有启动成功。这时候要把启动命令改成绝对路径或者先手动在终端跑一遍启动命令确认它能正常驻留而不是立刻报错退出。协议层的问题通常是输出污染——stdio 模式下 Server 的任何标准输出都会被当成协议消息解析如果你在代码里写了print(server started)握手阶段就会收到非法消息连接直接中断。我之前排查过一个诡异的超时问题最后发现是某次调试加了一行print在那之后所有的握手请求都石沉大海。所以再次强调stdio 服务的日志一律走stderr或者用专门的日志框架输出到文件。配置层的问题相对好查重点检查mcpServers的字段名和参数格式是否和客户端版本匹配。有些客户端版本更新后配置结构略有变化老教程里的配置不一定兼容报错信息里往往会提示 schema 校验失败仔细读一下就能定位。5.2 工具返回空或者结果被截断合理设计输出上限工具返回值空不一定是工具没执行很多时候是输出被静默截断了。我在grep_search里限制了返回前 200 行在tail_log里限制了最大 200 行在run_test里截取了输出末尾 3000 字符——这些限制是为了保护上下文窗口但代价是信息可能丢失。实战中有个很典型的场景Agent 搜索一个宽泛的关键词返回结果超过 200 条而真正相关的代码可能在 300 条之外它就会漏掉关键线索。解决思路不是无限放大截断值而是教会模型缩小范围再搜。比如在工具描述里引导它先限定directory参数或者用更精确的正则。你在设计工具时就要预设这种信息太多→引导收敛的循环而不是一把梭地返回所有内容。另一个常见问题是工具执行超时。模型调用run_test时如果测试套件很大120 秒超时可能不够。这里建议区分场景调试期的快速验证只跑单用例全量回归留给 CI。工具的超时值也要根据实际任务调整太小会误伤太大又会把整个会话拖死。5.3 权限失控的隐患工具能力必须可收敛权限设计不是一次性的而是需要持续审视的。我见过不少团队在 MCP 服务里快速加工具今天加一个执行任意命令明天加一个写文件等到出了问题才发现工具列表已经膨胀到模型难以判断边界的地步。模型虽然不是恶意的但它可能因为理解偏差而做出危险操作最终责任在工具设计者。实操中我有几条比较硬的规则默认只读写操作单独开工具并且要加确认环节。命令执行必须模板化禁止把自由字符串直接拼进 shell。文件路径统一走校验函数任何工具都不能绕过。每个工具都要有资源上限读文件不能无限大跑命令不能无限时。定期审计工具列表删掉不再使用的工具尤其是带副作用的。按照这些规则我给自己的服务扩展写补丁能力时也把权限收敛得很窄只能往指定 patches 目录写入文件只能通过git diff校验补丁格式不能修改任何现有源码文件。5.4 排查思路速查表把实战中频繁踩到的坑整理成一张速查表方便你在现场快速对照现象可能原因排查与解决工具列表为空进程启动失败、配置字段错、PATH 不含 uv/python先手动跑启动命令验证进程再看客户端配置 schema连接后一直无响应stdio 管道被 print 污染全局搜索print(改用 stderr 输出调试日志工具调用返回越界参数描述不清楚、路径解析不校验补全 JSON Schema 描述强制 safe_join 校验返回结果不完整截断上限太小提示模型缩小搜索范围或调整输出上限跑测试超时测试套件过大、单用例太重只跑指定用例调大 timeout配合 CI 使用结果有安全风险工具权限过宽走模板命令、默认只读、增加路径白名单5.5 新手最容易忽略的配置细节最后补三个新手几乎必踩的细节。第一个是编码问题Windows 上日志文件可能是 GBK 编码Python 默认按 UTF-8 读取会直接报错或者乱码读文件时最好带上errorsignore或者用charset自动探测。第二个是文件路径分隔符不同操作系统上相对路径的写法不同工具描述里尽量用正斜杠示例并且不要硬编码绝对路径。第三个是日志位置很多项目日志不在项目根目录而是写在系统临时目录或/var/log下这种情况下路径校验就不能只锁项目根目录要专门给日志目录开一个白名单防止 Agent 为了读日志把自己锁死。如果你经常和企业级项目打交道会发现 MCP 的实际接入方式比这里的示例复杂不少比如某些低代码平台直接把 MCP 功能合进了后端框架Spring AI 也提供了自己的 Agent 与 MCP 集成层编排逻辑更重。但这篇文章讲的最小链路始终是所有复杂集成的地基把这些细节吃透迁移到任何框架都不慌。6. 从看 Bug到修 BugMCP 还能往哪走6.1 加一个写权限的补丁工具只读服务跑通之后下一步很自然地就是让 Agent 不光看还能修。但要小心修复类工具的风险比查看类高一个量级所以我的做法是加一个辅助工具来生成补丁而不是让 Agent 直接改源码。具体来说可以定义propose_patch(description, file_path, content_behind)它接收 Agent 要修改的文件路径和代码片段在服务端生成一个git diff格式的补丁文件存放在单独的patches/目录并返回补丁内容。这个工具本身没有写源码的权限它只是把 Agent 的修改意图转化成可评审的补丁。真正应用补丁时由人在客户端 review 之后手动执行git apply。这样既发挥了 Agent 的代码生成能力又保留了一道人类审批的安全阀。补丁工具的价值在调试场景里非常明显Agent 定位到KeyError后可以直接给出修改后的代码行并生成补丁你确认无误后应用再让它重跑一遍测试做回归。整个定位 → 修复 → 验证的闭环就彻底打通了。6.2 接进 CI/CD做自动化回归MCP 服务不只是给开发者聊天用的它完全可以嵌进自动化链路。一个很实际的用法CI 构建失败后流水线把失败日志、测试报告、最新变更列表打包成一个只读 MCP Resource再启动一个 Agent 会话让它基于这些资源分析失败原因并生成一份带证据链的排查报告自动贴到项目群或者提一条 issue。这种集成方式的优点是失败信息不再是一堆让人头疼的日志粘贴而是 Agent 直接给结论和修复建议开发者只需要做裁决。我认识的一些团队已经把这条链路接进自己的发布流程构建失败后的平均定位时间从半小时降到几分钟。MCP 在这套体系里扮演的角色非常清晰——它就是让 AI 能读懂 CI 世界的那座桥。6.3 生态扫描MCP 已经从聊天工具走向专业工具链最后聊聊生态。MCP 在这两年已经远远超出给聊天机器人加插件的范畴各类专业软件都在主动接入。比如逆向工程领域IDA 和 x32dbg 都已经有了 MCP 插件分析恶意样本或者调试二进制时Agent 可以直接读取反汇编结果、设置断点、读取寄存器状态游戏开发领域Unreal Engine 5.8 集成了 MCP 能力可以用自然语言驱动编辑器操作和资源查询硬件设计领域Altium Designer 放出了 AI 接口的 MCP原理图、PCB 数据都能通过标准接口交给模型处理工业自动化领域也有 TIA 的 MCP 交付包PLC 工程师可以通过 Agent 辅助检查梯形图和变量表。我特别想举的一个例子是 WinSxS 目录膨胀这个经典系统问题。WinSxS 是 Windows 的组件存储目录随着系统更新它会不断增长很多人拿它没办法只能靠手工清理工具。如果有一个 MCP 服务把磁盘占用扫描、组件清单读取、清理建议整合成工具集Agent 就可以自己分析哪个组件占了多少空间、哪些可以安全清理给出可执行的方案。这个场景说明了一件事MCP 的想象力不在于聊天而在于让 Agent 真正摸到专业工具的数据和处理能力。6.4 一个务实的建议从三个工具起步如果你看完文章也想上手做自己的第一个 MCP 服务我的建议是想清楚三个只读工具就开工一个读文件的、一个搜代码的、一个看日志的。理由很简单这三个工具覆盖了 80% 的信息获取需求而且全部是只读操作风险和复杂度都控制在可控范围。把它跑通、挂进客户端、处理一个真实的报错你会立刻获得对 MCP 的完整直觉。之后你要做的不是急着重构而是在使用中观察 Agent 的行为它在哪些场景反复调用某个工具哪些参数它总是传错哪些描述它理解得比预期更好这些观察会告诉你下一个工具该加什么、描述该怎么改。MCP 服务是迭代出来的不是设计出来的。我个人在实际操作中的体会是MCP 最大的学习门槛其实不是协议而是以模型的视角设计接口——每写一个工具都想象一下一个只能看到你的描述、看不到你的代码的智能体会不会用对。把工具描述写得像给同事写的接口文档一样认真你的 Agent 表现会好得超乎预期。最后再分享一个小技巧把 Agent 每一次调用的记录打印到 stderr自己跑几个场景后翻一翻你会非常直观地看到工具设计的盲区这比任何教程都管用。
返回列表