MCP协议深度实战
MCP协议深度实战:构建AI Agent的标准化工具接口作者:Crown_22 | Hermes Agent 桌面程序开发者前言2024年11月,Anthropic发布了Model Context Protocol(MCP),一个开放标准,旨在统一大语言模型与外部工具、数据源之间的通信方式。作为一个长期开发AI Agent的工程师,我在第一次接触MCP时就被它的设计哲学吸引——它不是又一个"框架",而是一个"协议",就像HTTP之于Web、LSP之于IDE一样。但真正动手开发MCP Server之后,我踩了不少坑。文档里的示例看起来简单得像Hello World,但到了生产环境,错误处理、并发控制、资源管理、安全边界这些问题全都冒出来了。这篇文章记录了我从零构建MCP Server到生产部署的完整经验,包含大量踩坑记录和对比分析。一、MCP到底解决了什么问题1.1 没有MCP之前的世界在MCP出现之前,每个AI Agent框架都有自己的工具定义方式:# LangChain的方式fromlangchain.toolsimporttool@tooldefsearch_web(query:str)-str:"""搜索网页"""returnrequests.get(f"https://api.search.com?q={query}").text# OpenAI Function Calling的方式tools=[{"type":"function","function":{"name":"search_web","description":"搜索网页","parameters":{"type":"object","properties":{"query":{"type":"string","description":"搜索关键词"}},"required":["query"]}}}]# 自定义Agent框架的方式classSearchTool(BaseTool):name="search_web"description="搜索网页"def_run(self,query:str)-str:returnrequests.get(f"https://api.search.com?q={query}").text每种方式都互不兼容。你在LangChain上写的工具,换到AutoGen就不能直接用。更痛苦的是,每次换模型提供商(OpenAI→Claude→Gemini),工具定义的格式都要重新适配。1.2 MCP的解决方案MCP提出了一个优雅的抽象:工具(Tool)、资源(Resource)、提示(Prompt)是三个独立的原语,由MCP Server提供,任何MCP Client都可以消费。┌─────────────────┐ JSON-RPC 2.0 ┌─────────────────┐ │ MCP Client │ ◄──────────────────► │ MCP Server │ │ (Claude, etc) │ stdio/SSE │ (你的代码) │ └─────────────────┘ └─────────────────┘这个架构的核心价值是解耦:工具提供者只需要实现MCP Server工具消费者只需要实现MCP Client双方通过标准协议通信,互不依赖1.3 三个原语详解Tool(工具):让模型执行操作。类似函数调用,有输入参数和返回值。# Tool定义{"name":"read_file","description":"读取文件内容","inputSchema":{"type":"object","properties":{"path":{"type":"string","description":"文件路径"}},"required":["path"]}}Resource(资源):让模型获取数据。类似REST API的GET端点,是只读的。# Resource定义{"uri":"file:///etc/config.yaml","name":"应用配置","description":"当前应用的YAML配置文件","mimeType":"text/yaml"}Prompt(提示):预定义的提示模板。让Client可以获取Server提供的上下文。# Prompt定义{"name":"code_review","description":"代码审查提示","arguments":[{"name":"language","description":"编程语言","required":True},{"name":"code","description":"待审查代码","required":True}]}二、从零构建一个MCP Server2.1 环境搭建# 安装MCP Python SDKpipinstallmcp# 验证安装python-c"import mcp; print(mcp.__version__)"2.2 最小MCP Server# server.py - 最简单的MCP Serverfrommcp.server.fastmcpimportFastMCP mcp=FastMCP("my-first-server")@mcp.tool()defadd(a:int,b:int)-int:"""两数相加"""returna+b@mcp.tool()defgreet(name:str)-str:"""打招呼"""returnf"你好,{name}!欢迎使用MCP Server。"if__name__=="__main__":mcp.run(transport="stdio")看起来很简单对吧?但接下来的坑就开始了。2.3 踩坑1:stdio模式下的输出污染问题:stdio模式下,MCP Server通过stdin/stdout进行JSON-RPC通信。如果你的代码里有任何print()语句,就会污染通信管道,导致协议解析失败。# ❌ 错误写法@mcp.tool()defprocess_data(data:str)-str:print(f"处理数据:{data}")# 这会污染stdout!return"处理完成"# ✅ 正确写法importlogging logger=logging.getLogger(__name__)@mcp.tool()defprocess_data(data:str)-str:logger.info(f"处理数据:{data}")# 用logging,输出到stderrreturn"处理完成"调试技巧:在开发阶段,把日志输出到文件而不是stderr:importlogging logging.basicConfig(filename="/tmp/mcp-server.log"