用Python构建你的第一个MCP服务器:让Claude访问你的笔记
Model Context Protocol (MCP) 使得 AI 模型能通过标准接口调用外部工具和数据。本文以约60行 Python 代码,使用
uv和官方 SDK 构建一个 MCP 服务器,通过 stdio 暴露两个工具,并演示如何一条命令接入 Claude Code。文章还揭示了 stdout 块缓冲带来的隐蔽陷阱。
背景:一次集成,处处调用
作者在个人网站上积累了48篇笔记,覆盖 Netplan、ufw 等频繁遇到的配置问题。这些笔记原本只为解决自己的“重复搜索”痛点,但当与 Claude 对话时,模型并不知道这些定制化的最佳实践。每次都需要手动粘贴,且只在当前会话有效——这不是系统化的方案。
理想的方式是让模型自己“敲门”:需要时主动读取笔记。MCP 提供了这个门——真正的价值不在于协议本身,而在于你只需要编写一次集成代码。
如果没有 MCP,要为每个客户端(Claude Code、桌面版、未来工具)分别做适配。有了 MCP,一个服务器即可服务所有支持该协议的客户端,服务器无需关心谁在调用。
快速上手:TL;DR
uv add "mcp[cli]"
在 Python 文件中用 @mcp.tool() 装饰两个函数,最后调用 mcp.run(transport="stdio")。然后注册到 Claude:
claude mcp add notes -- uv run --directory /abs/path server.py
关键注意事项:
- 必须使用绝对路径,否则连接失败。
- 永远不要用 print() 输出到 stdout——stdout 是传输通道,任何额外输出都会破坏协议。
实现概要
整个服务器约60行 Python 代码,核心思路:
1. 用 uv 初始化项目并添加 mcp[cli] 依赖。
2. 导入 MCP 类,实例化。
3. 定义两个工具函数(如搜索笔记、获取单篇笔记),通过 @mcp.tool() 注册。
4. 以 stdio 模式运行服务器。
示例脚手架:
from mcp import MCPServer
server = MCPServer("notes")
@server.tool()
def search(query: str) -> str:
# 实现笔记搜索逻辑
return results
@server.tool()
def get_note(path: str) -> str:
# 返回笔记内容
return content
server.run(transport="stdio")
MCP 会处理 stdio 上的 JSON-RPC 消息,Claude 端自动发现工具并调用。
为什么 stdio 是陷阱
mcp.run(transport="stdio") 会将 stdin/stdout 用于协议通信。任何多余的 print() 或日志输出到 stdout 都会破坏 JSON 流,导致连接断开且无错误提示。调试时必须用 stderr 或日志文件。
关键要点
- 使用
uv add "mcp[cli]"一键安装依赖和 CLI 工具。 - 工具函数用
@mcp.tool()装饰,返回值必须是字符串(或 MCP 支持的类型)。 - 服务器注册时使用绝对路径,避免
claude mcp add找不到可执行文件。 - 绝对不要向 stdout 输出任何内容——stdout 是协议通道,用
print(..., file=sys.stderr)进行调试。 - 一次编写 MCP 服务器即可在多个客户端(Claude Code、Claude Desktop 等)中使用,无需重复集成。
本文介绍如何仅用
uv和Claude Code构建一个本地 MCP 服务器,让 Claude 能够搜索和读取你的 Markdown 笔记。无需部署服务器、Docker 或开放端口,MCP 通过stdio子进程通信,整个过程简单直接。
环境依赖
你只需要两样东西:uv 和 Claude Code。不需要部署服务器、不需要 Docker、不需要打开任何端口。一个本地 MCP 服务器本质上就是一个程序,由 Claude Code 作为子进程启动,并通过标准输入输出(stdin/stdout)与其通信。
这一点值得停下来理解,因为它正是人们以为会很复杂但实际并不复杂的地方。MCP 定义了两种传输方式:
- Streamable HTTP:用于远程服务器,多个客户端通过网络连接,涉及完整的认证流程。
- stdio:用于本地服务器,本质上是一个管道。你的进程、客户端的进程,通过 JSON 来回通信。没有任何端口在监听,没有任何东西暴露在外。
本教程中所有内容都基于 stdio。它是学习的正确起点,对于一个只读取你本地笔记本电脑上文件的服务器来说,也可能是正确的长期方案。
构建目标
我们将构建两个工具:
search_notes(query):查找包含某个短语的笔记,返回文章 slug 和标题。get_note(slug):返回一篇完整笔记内容。
这两个工具的分工比看起来更重要。search_notes 返回一个简短列表,让模型可以选择;然后模型只获取它真正需要的内容。如果 search_notes 直接返回完整文章内容,一个三个词的查询可能会向上下文窗口塞入 4 万字,模型还没开始处理就被淹没了。
本教程将目录指向笔者的博客(因为手头正好有),但你只需将 NOTES 变量指向任意 Markdown 文件夹,就能获得完全相同的效果。这正是整个想法的核心。
项目设置
mkdir mcp-notes && cd mcp-notes
uv init .
uv add "mcp[cli]"
mcp[cli] 是官方的 Python SDK。cli 扩展引入了开发工具,稍后你会用到。