前言
说实话,MCP 协议从去年底爆火到现在,已经成了 AI 开发圈绕不开的话题。但你真要动手写一个 Server,很多教程要么讲得太浅,要么跳过了关键细节。
今天就手把手带大家写一个文件搜索工具 MCP Server,功能很简单:让 AI 能通过 MCP 协议搜索本地文件。但麻雀虽小五脏俱全,整个过程你能完整理解 MCP 的工作原理。
MCP 是什么?一句话说清楚
MCP (Model Context Protocol) 是 Anthropic 去年推出的一种开源协议,专门解决 AI 模型和外部工具之间的通信问题。
通俗点说:MCP 就是 AI 世界的 USB 接口。以前你给 AI 加功能,每个 AI 有自己的一套插件系统,像不同品牌的充电器不通用。MCP 统一了接口标准,写一次工具,任何支持 MCP 的 AI 客户端都能用。
环境准备
首先确保你的环境满足以下条件:
# Python 3.10+python--version# 安装 MCP 开发包pipinstallmcp我们用 Python 实现,因为生态最成熟。如果你不会 Python,用 TypeScript 也行,官方支持两种语言。
第一步:定义工具
MCP Server 的核心是暴露工具(Tool)给 AI 调用。每个工具需要定义:
- 名称— AI 调用时用的标识符
- 参数描述— 告诉 AI 需要什么参数
- 实现逻辑— 实际干活的代码
frommcp.serverimportServerfrommcp.typesimportTool,TextContentfromtypingimportAnyimportosimportfnmatch# 创建 Server 实例server=Server("file-search-server")# 注册工具@server.list_tools()asyncdeflist_tools()->list[Tool]:return[Tool(name="search_files",description="搜索本地文件,支持通配符模式",inputSchema={"type":"object","properties":{"pattern":{"type":"string","description":"文件搜索模式,例如 *.py 或 data/*.csv"},"root_dir":{"type":"string","description":"搜索根目录,默认为当前目录"},"max_results":{"type":"integer","description":"最大返回结果数,默认 20","default":20}},"required":["pattern"]})]第二步:实现工具逻辑
工具定义好了,接下来实现 AI 发起调用时实际执行的代码:
@server.call_tool()asyncdefcall_tool(name:str,arguments:dict)->list[TextContent]:ifname=="search_files":pattern=arguments["pattern"]root_dir=arguments.get("root_dir",".")max_results=arguments.get("max_results",20)results=[]forroot,dirs,filesinos.walk(root_dir):# 跳过隐藏目录dirs[:]=[dfordindirsifnotd.startswith('.')]# 跳过 node_modules 等大目录dirs[:]=[dfordindirsifdnotin('node_modules','__pycache__','.git','venv')]forfilenameinfiles:iffnmatch.fnmatch(filename,pattern):filepath=os.path.join(root,filename)try:size=os.path.getsize(filepath)results.append({"path":filepath,"size":size,"size_str":format_size(size)})exceptOSError:continue# 按大小排序,最大的在前results.sort(key=lambdax:x["size"],reverse=True)results=results[:max_results]return[TextContent(type="text",text=format_results(results,pattern))]else:raiseValueError(f"Unknown tool:{name}")第三步:传输层配置
MCP 支持两种传输方式:标准输入输出(stdio)和 SSE(Server-Sent Events)。本地开发用 stdio 最简单:
defformat_size(size:int)->str:"""格式化文件大小"""forunitin['B','KB','MB','GB']:ifsize<1024:returnf"{size:.1f}{unit}"size/=1024returnf"{size:.1f}TB"defformat_results(results:list[dict],pattern:str)->str:"""格式化搜索结果"""ifnotresults:returnf"没有找到匹配 `{pattern}` 的文件"lines=[f"找到{len(results)}个匹配 `{pattern}` 的文件:\n"]forrinresults:lines.append(f"-{r['path']}({r['size_str']})")return"\n".join(lines)if__name__=="__main__":frommcp.server.stdioimportstdio_serverimportasyncioprint("启动 MCP File Search Server...")asyncio.run(stdio_server(server))第四步:配置客户端
Server 写好了,怎么让 AI 用起来?以 Claude Desktop 为例:
{"mcpServers":{"file-search":{"command":"python","args":["path/to/search_server.py"]}}}配置完成后重启 Claude Desktop,AI 就能自动发现并使用你的文件搜索工具了。
踩坑指南
写 MCP Server 最常遇到的几个坑:
1. 参数描述不够详细
AI 模型依赖参数描述来理解怎么用。如果描述太模糊,AI 可能传错参数。建议每个参数都写清楚「这个参数干什么用的」「什么格式」。
2. 超时处理
MCP 默认有超时时间,如果你的工具执行时间太长(比如扫描几百万个文件),AI 会超时。建议加入超时限制和进度反馈。
3. 工具返回值太长
AI 模型的上下文窗口有限,一次性返回太多结果会被截断。建议加 max_results 限制,或者分页返回。
# 加入超时控制的改进版本importasyncioimportsignalasyncdefsearch_with_timeout(pattern,root_dir,max_results,timeout=30):try:result=awaitasyncio.wait_for(search_files_async(pattern,root_dir,max_results),timeout=timeout)returnresultexceptasyncio.TimeoutError:return[{"error":"搜索超时,请缩小搜索范围"}]进阶玩法
写完了基础版,你还可以扩展更多功能:
- 文件内容搜索:结合 grep 模式搜索文件内容
- 实时文件监控:用 watchfiles 监听文件变化
- 多 Agent 协作:让多个 MCP Server 协同工作
总结
MCP 协议的价值不在于技术有多复杂,而在于它定义了一个通用的接口标准。以前的 AI 工具链像一个个孤岛,MCP 就是连接这些孤岛的桥梁。
写完这个 demo 你会发现,MCP 本身并不难,真正的难点在于设计好的工具接口。好的工具接口 = 清晰的参数描述 + 合理的错误处理 + 可预期的行为。
下一步推荐你试试:
- 把搜索工具改成异步实现(asyncio)
- 加上文件内容预览功能
- 试试用 SSE 模式部署成远程服务
有什么问题欢迎在评论区讨论 🔧