ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MCP Python SDK v2 实战:从 0 做一个本地笔记服务器

2026/9/11 19:41:43 拓冰建站 浏览量
MCP Python SDK v2 实战:从 0 做一个本地笔记服务器 摘要用官方 MCP Python SDK v2 写一个只读服务器让支持 MCP 的 AI 客户端能搜索和读取本地 Markdown 笔记。全程不需要 API Key。普通聊天模型不知道你电脑上的《部署手册》写了什么。最粗暴的做法是每次手工粘贴文档但这种做法没有可重用的调用边界。MCP 服务器做的事就是把这条边界定义清楚AI 可以搜索哪个目录参数是什么结果是什么哪些路径绝对不允许读。最终效果我们会暴露两个能力search_notes按关键词搜索 Markdown 笔记返回文件名和命中片段note://{name}在已知笔记名时读取完整文本。在 MCP 中前者是 Tool适合让模型根据问题决定何时调用后者是 Resource更像一个可定位的文件。1. 创建项目MCP Python SDK v2 要求 Python 3.10 或更高版本本文用 3.12。uv init--app--baremcp-notescdmcp-notes uv python pin3.12uvaddmcp[cli]2.0.0mkdirnotestouchserver.py在notes/部署手册.md放一份测试笔记# 内部部署手册 测试环境由 main 分支自动部署。生产环境需要创建带 v 前缀的版本标签例如 v1.4.0。 若健康检查连续失败三次先查看应用日志不要直接删除数据卷。2. 写服务器将以下完整代码写入server.pyimportosfrompathlibimportPathfrommcp.serverimportMCPServer mcpMCPServer(local-notes)default_notes_dirPath(__file__).parent/notesnotes_dirPath(os.environ.get(NOTES_DIR,str(default_notes_dir))).expanduser().resolve()defmarkdown_files()-list[Path]:ifnotnotes_dir.is_dir():return[]returnsorted(notes_dir.rglob(*.md))defsafe_note_path(name:str)-Path:Resolve a note name while keeping reads inside notes_dir.relativePath(name)ifrelative.is_absolute()or..inrelative.parts:raiseValueError(笔记路径不合法)ifrelative.suffix!.md:relativerelative.with_suffix(.md)target(notes_dir/relative).resolve()iftarget!notes_dirandnotes_dirnotintarget.parents:raiseValueError(笔记路径超出了允许的目录)returntargetmcp.tool()defsearch_notes(keyword:str,limit:int5)-list[dict[str,str]]:Search Markdown notes by keyword and return short matching snippets. Args: keyword: Text to search for. Matching is case-insensitive. limit: Maximum number of results, from 1 to 20. keywordkeyword.strip()ifnotkeyword:raiseValueError(keyword 不能为空)limitmax(1,min(limit,20))results:list[dict[str,str]][]needlekeyword.casefold()forpathinmarkdown_files():contentpath.read_text(encodingutf-8)positioncontent.casefold().find(needle)ifposition-1:continuestartmax(0,position-80)endmin(len(content),positionlen(keyword)120)results.append({name:str(path.relative_to(notes_dir)),snippet:content[start:end].replace(\n, ).strip(),})iflen(results)limit:breakreturnresultsmcp.resource(note://{name})defread_note(name:str)-str:Read one Markdown note by its relative name.pathsafe_note_path(name)ifnotpath.is_file():raiseValueError(f笔记不存在:{name})returnpath.read_text(encodingutf-8)if__name____main__:mcp.run(transportstdio)mcp.tool()会读取函数签名、类型标注和 docstring生成客户端能理解的工具定义。所以keyword: str不只是给 IDE 看的它还参与了调用参数的校验。safe_note_path()则是不能省的一层。如果直接写(notes_dir / name).read_text()那么../../some-file这类输入就有机会离开notes目录。MCP 定义了“怎么调用”但具体能读什么仍然要由服务器代码限制。3. 用 Inspector 真正调一次官方 CLI 可以启动 MCP Inspectoruv run mcp dev server.pyInspector 是 Node.js 应用因此本机还需要能执行npx。打开命令输出的地址连接成功后进入 Tools选择search_notes输入{keyword:生产环境,limit:5}应当看到类似结果[{name:部署手册.md,snippet:# 内部部署手册 测试环境由 main 分支自动部署。生产环境需要创建带 v 前缀的版本标签...}]这一步很值得做。先在 Inspector 中确认参数 Schema 和返回值能把“服务器有问题”与“AI 客户端配置有问题”分开。4. 连接支持 MCP 的客户端大多数支持 stdio MCP 的客户端都需要类似下面的配置{mcpServers:{local-notes:{command:uv,args:[--directory,/ABSOLUTE/PATH/TO/mcp-notes,run,server.py]}}}请把/ABSOLUTE/PATH/TO/mcp-notes改成项目的绝对路径。macOS/Linux 可在项目目录执行pwdWindows 可在 CMD 中执行cd查看。如果客户端找不到uv再用which uv或where uv取得它的绝对路径。重启客户端后可以问搜索我的本地笔记生产环境是怎么部署的客户端是否会自动调用工具以及调用前是否弹出确认取决于客端自身的策略。但服务器端始终只暴露notes_dir中的 Markdown 文件。最容易踩的坑stdio 不是普通终端输出这个服务器用标准输入输出传输 JSON-RPC 消息。因此不要在服务器里随手写print(开始搜索)它会把普通文本插进协议数据流导致客户端解析失败。需要记录日志时使用 Pythonlogging模块让日志写到 stderrimportlogging loggerlogging.getLogger(__name__)logger.info(search started)当 Inspector 能用keyword生产环境返回《部署手册》的片段时你已经完成了一个真正的 MCP 闭环不是让模型“知道更多”而是给它一个边界清楚、可校验的读取动作。参考资料MCP Python SDK v2 官方文档Model Context ProtocolBuild an MCP server