学习精选 · AI 工具
Claude 与 Skill

用 Python 从零写一个 MCP 文档搜索服务器:三个决定模型表现的工具设计细节

ninadpathak.com2026-10-22约 3 分钟读完AI 整理查看原文

作者用 MCP Python SDK 2.3.0 写了一个只读的 docs-search 服务器,并实测哪些工具设计会真正影响模型行为:返回带行号的文本让 Claude 引用正确行号的比例从 1/4 提升到 4/4。文章包含完整可运行的服务器代码、路径安全处理和不需要模型就能跑的测试脚本。

今天就能做

  • 写 MCP 工具时让返回值带行号或结构化标记,模型引用准确率差异巨大(实测 1/4 vs 4/4)
  • 所有来自模型的路径参数先 resolve() 再判断是否在允许目录内,别只查原始字符串
  • 用 SDK 的 Client 做内存内测试,不需要接真实模型就能验证工具行为和越界拒绝

项目搭建:uv + 固定 SDK 版本

  • 用 uv init --bare 创建单文件项目(Python 3.12),只生成 pyproject.toml,避免默认的 src/ 打包结构
  • 安装 mcp[cli]==2.3.0 并固定版本:SDK 2.x 把服务器类改名为 MCPServer,1.x 的 FastMCP 导入写法跑不起来
  • 准备一个 docs 文件夹放 Markdown 文档,再在文件夹外放一个 private.md 作为哨兵文件,专门用来验证越界访问会被拒绝

工具即函数:docstring 就是模型的说明书

  • 每个带类型注解的函数在 @mcp.tool() 下自动变成工具,输入 schema 来自类型注解,工具描述来自 docstring
  • docstring 相当于 agent 框架里的工具注册表,模型靠它决定调用哪个工具,所以要写清楚参数含义和返回内容
  • stdio 传输下绝不能 print():协议规定 stdout 只能写 MCP 消息,日志用 logging 写到 stderr

返回格式直接影响模型表现

  • 实测(Claude Code 2.1.308 + Haiku 5.5):read_doc 返回纯文本时,4 次运行只有 1 次引用对了行号
  • 改成每行带编号后,4 次全部引用正确——工具返回值是模型接口的一部分,格式决定模型能做什么
  • 原则:把模型需要引用的内容做成拿来即用的形式

路径安全:先 resolve 再检查

  • 工具参数是模型给的不可信字符串,guides/../../private.md 这种路径没有可疑前缀,直接查字符串会漏掉
  • inside_docs() 先调 resolve() 折叠 .. 段并跟随符号链接,再检查真实位置是否在 DOCS_DIR 内、后缀是否为 .md
  • docs/ 里名为 leak.md 的符号链接指向外部文件,不 resolve 就看不出来

拒绝方式与无模型测试

  • 拒绝越界路径时抛 ToolError:SDK 2.3.0 会把它作为错误结果发给客户端;其他异常只会变成笼统的 Error executing tool 加服务器日志里的堆栈
  • read_only_hint 注解只是提示不是安全机制,访问控制必须自己在代码里强制执行
  • 测试脚本用 SDK 的 Client 分别做内存内连接和 stdio 子进程两种方式跑同一套断言,包括四个越界路径和符号链接泄漏检查,最后输出 in-memory: ok 和 stdio: ok

原文:MCP Server Tutorial: Build a Docs Search Server in Python

来源:ninadpathak.com · 2026-10-22

查看原文

本文由 AI 阅读原文后整理,未经人工逐字核对,内容以原文为准。

登录后可以点赞、查看和发表评论