【智能体开发】用Python开发一个最小MCP服务器:提供只读产品查询工具

📅 发布时间:2026/10/14 18:32:18
【智能体开发】用Python开发一个最小MCP服务器:提供只读产品查询工具
用Python开发一个最小MCP服务器提供只读产品查询工具一、问题与目标假设你有一个小型的商品目录存储在本地的一个JSON文件里包含几十条虚构的产品记录。你希望让Claude Desktop或其他支持MCP的AI客户端能够直接查询这些商品信息——比如“查一下P-1002这个商品还有没有货”或者“列出所有价格低于200元的商品”。当前的问题是AI客户端无法直接访问你的本地文件你每次都要手动复制粘贴数据既低效又容易出错。MCPModel Context Protocol是Anthropic在2024年11月推出的开放协议旨在用一套标准化的接口解决AI模型与外部数据源之间的“N×M”集成问题。完成本文后你将拥有一个可运行的Python MCP服务器它暴露一个只读的产品查询工具能够被任何兼容MCP的客户端通过stdio传输层调用。整个示例在一个隔离目录中完成不触碰真实数据。适用环境Python 3.10macOS或Linux命令使用bash语法MCP Python SDK 1.x。如果你使用Windows请将bash命令替换为对应的PowerShell等价写法。二、前置条件与案例数据你需要Python 3.10或更高版本Node.js 22.19.0或更高版本仅用于MCP Inspector测试工具基本的Python类型注解知识虚构案例数据创建文件products.json包含5条产品记录。所有数据均为虚构与任何真实商品无关。{products:[{id:P-1001,name:机械键盘,price:299.0,stock:12,category:外设},{id:P-1002,name:无线鼠标,price:89.0,stock:0,category:外设},{id:P-1003,name:27寸显示器,price:1599.0,stock:3,category:显示设备},{id:P-1004,name:USB-C扩展坞,price:199.0,stock:25,category:配件},{id:P-1005,name:降噪耳机,price:699.0,stock:7,category:音频}]}字段含义id是唯一产品标识price单位为人民币元stock为当前库存数量0表示缺货。目录结构mcp-demo/ ├── products.json ├── server.py └── requirements.txt三、为什么选择FastMCP和stdioMCP Python SDK是官方维护的Tier 1 SDK提供了两个服务器实现层次低层Server类需要手动处理工具列表和分发适合对协议有精细控制需求的场景高层FastMCP类通过装饰器注册工具自动处理输入模式生成和参数验证代码量减少约60%。本文的核心需求是“暴露一个简单的只读查询工具”不需要底层控制。FastMCP的设计正好匹配一个装饰器函数加上类型注解SDK自动生成JSON Schema并注册工具。传输层选择stdio标准输入输出因为这是本地开发测试最简单的方案。MCP客户端以子进程方式启动服务器通过stdin/stdout交换JSON-RPC消息。如果你后续需要远程部署可以切换到Streamable HTTP传输但本文不覆盖。四、完整实现文件清单文件用途server.pyMCP服务器主程序包含数据加载和工具定义products.json虚构产品数据源requirements.txtPython依赖声明requirements.txtmcp1.0.0执行安装在项目根目录bashpython3-mvenv .venvsource.venv/bin/activate pipinstall-rrequirements.txt说明本文在Python 3.11.9环境中使用mcp 1.9.4完成验证。如果你安装的mcp版本不同FastMCP的导入路径和装饰器行为应当兼容因为FastMCP是SDK的稳定高层API。server.py最小MCP服务器只读产品查询工具。importjsonimportosfrommcp.server.fastmcpimportFastMCP# 数据文件路径从环境变量读取默认同目录下的 products.jsonDATA_FILEos.environ.get(PRODUCTS_FILE,products.json)defload_products()-list[dict]:加载产品数据。每次调用重新读取确保工具结果反映文件当前状态。withopen(DATA_FILE,r,encodingutf-8)asf:datajson.load(f)returndata[products]deffind_product(product_id:str)-dict|None:按ID查找产品。ID比较不区分大小写并去除首尾空白。normalizedproduct_id.strip().upper()forpinload_products():ifp[id].upper()normalized:returnpreturnNone# 创建FastMCP实例名称会成为工具命名空间的一部分mcpFastMCP(product-catalog)mcp.tool()defquery_product(product_id:str)-dict:按产品ID查询详细信息。 参数: product_id: 产品唯一标识例如 P-1001。 返回: 包含 id、name、price、stock、category 的字典。 如果产品不存在返回包含 error 字段的字典。 productfind_product(product_id)ifproductisNone:return{error:f未找到ID为 {product_id} 的产品,hint:可用ID: P-1001 至 P-1005,}return{id:product[id],name:product[name],price:product[price],stock:product[stock],in_stock:product[stock]0,category:product[category],}mcp.tool()deflist_products(max_price:float|NoneNone,in_stock_only:boolFalse,)-list[dict]:列出产品支持按价格上限和库存状态过滤。 参数: max_price: 可选价格上限含。不传则不过滤。 in_stock_only: 为 True 时只返回库存大于0的产品。 返回: 符合条件的精简产品列表。 productsload_products()ifmax_priceisnotNone:products[pforpinproductsifp[price]max_price]ifin_stock_only:products[pforpinproductsifp[stock]0]return[{id:p[id],name:p[name],price:p[price],stock:p[stock],}forpinproducts]if__name____main__:mcp.run(transportstdio)关键设计说明为什么返回字典而不是抛异常MCP工具通过返回值传递信息。当产品不存在时返回一个包含error字段的字典让调用方AI客户端能够以结构化方式处理失败而不是让异常穿透到协议层。这在只读查询场景中是更稳妥的模式。为什么load_products每次调用都重新读文件这是一个有意的简化。它确保你对products.json的修改会立即反映在下次工具调用中方便测试。在生产场景中你可能会在启动时加载一次并缓存。本文选择“每次读取”是为了让边界测试修改文件后重新查询更加直观。返回类型注解的作用FastMCP根据函数的返回类型注解自动生成工具的outputSchema使返回数据可被客户端验证。dict和list[dict]都是支持的返回类型。五、运行与测试方式一用MCP Inspector推荐MCP Inspector是官方提供的测试调试工具通过npx运行无需安装。它提供一个交互式Web界面让你在浏览器中直接调用工具。确保server.py和products.json在同一目录然后执行npx modelcontextprotocol/inspector python server.py终端会输出一个带token的本地URL。在浏览器中打开它你应该看到工具列表包含query_product和list_products。在工具调用面板中选择query_product输入{product_id: P-1001}点击执行。正常场景的预期输出Inspector界面或JSON响应中{id:P-1001,name:机械键盘,price:299.0,stock:12,in_stock:true,category:外设}方式二用CLI快速验证如果你只想在终端中验证工具注册是否成功可以使用Inspector的CLI模式npx modelcontextprotocol/inspector--clipython server.py--methodtools/list预期输出是一个JSON数组包含两个工具的定义。六、验收测试验收场景总览#测试目的输入/操作预期结果判定方法1正常查询存在的产品query_product参数{product_id: P-1001}返回name为“机械键盘”、price299.0、stock12、in_stocktrue检查返回字典中id、name、price、stock字段值与products.json一致2边界产品存在但缺货query_product参数{product_id: P-1002}返回stock0、in_stockfalseerror字段不存在检查in_stock为false确认没有error字段3失败不存在的产品IDquery_product参数{product_id: P-9999}返回包含error的字典错误信息包含“未找到”检查返回字典有error键且没有正常产品字段4过滤价格上限仅库存list_products参数{max_price: 200, in_stock_only: true}只返回USB-C扩展坞199.0库存25无线鼠标(89.0)和降噪耳机(699.0)被过滤检查返回列表长度为1id为P-10045边界价格恰好等于上限list_products参数{max_price: 299.0}包含机械键盘299.0和USB-C扩展坞199.0无线鼠标(89.0)也包含检查返回列表中包含idP-1001和P-10026失败文件被移动或删除将products.json重命名后调用query_product工具调用返回错误进程可能崩溃或返回异常信息检查是否抛出FileNotFoundError或返回错误信息测试4的细节说明数据中价格≤200且在库存0的产品只有P-1004199.0stock25。P-1002价格89.0但stock0被in_stock_only过滤。P-1001价格299.0超过上限。所以预期结果列表长度为1。测试5的细节说明max_price299.0包含price 299.0所以P-1001299.0和P-1004199.0符合P-100289.0也符合价格条件但in_stock_only未传默认False所以P-1002stock0也会返回。最终列表包含P-1001、P-1002、P-1004共3条。七、常见故障与定位工具没有出现在Inspector中。首先确认命令在包含server.py的目录中执行。然后检查python命令是否指向正确的Python版本python --version应显示3.10。如果pip安装的mcp在venv中而python命令指向系统PythonInspector会启动一个没有安装mcp的进程并立即失败。调用工具时长时间无响应。stdio服务器等待stdin输入。如果用python server.py直接运行而不通过MCP客户端连接进程会静默等待。这是预期行为不是故障。请通过Inspector或真实MCP客户端连接。ModuleNotFoundError: No module named mcp。确认venv已激活或使用venv中的Python全路径启动npx modelcontextprotocol/inspector /path/to/.venv/bin/python server.py。修改products.json后结果未更新。load_products每次调用重新读取文件所以修改应立即生效。如果结果未变检查修改是否保存到了server.py同目录下的products.json或者PRODUCTS_FILE环境变量是否指向了其他路径。八、局限与边界本文实现的是一个只读、单机、无认证的最小服务器。以下内容不在覆盖范围内写入操作没有添加、修改或删除产品的工具。生产环境中写入操作需要权限检查、输入验证和重复执行防护。数据库后端使用JSON文件读取。如果产品数量达到数千条或需要并发访问应替换为参数化的SQLite或外部数据库查询。远程部署使用stdio传输仅适用于本地进程间通信。远程场景需要Streamable HTTP传输和认证机制。安全边界服务器没有区分调用方身份。任何能够启动该进程的客户端都可以查询所有产品。如果产品数据需要访问控制应在工具函数内部实现身份校验。关于“只读”的实际含义本文的“只读”指服务器不提供任何写入工具而非在文件系统层面强制只读。load_products以只读模式打开文件但Python代码没有阻止你在未来添加写入逻辑。真正的只读保证需要文件权限或沙箱机制。验证状态已完成的核验MCP Python SDK 1.9.4的FastMCP导入路径和mcp.tool()装饰器行为已在隔离Python 3.11.9环境中通过静态导入检查。产品数据逻辑过滤条件、边界值已用本示例数据进行手工复算测试4和测试5的预期结果可复核。FastMCP的dict和list[dict]返回类型注解支持已对照SDK文档确认。未执行的验证未在真实Claude Desktop或其他MCP客户端中完成端到端连接测试需要客户端安装和配置权限。未使用MCP Inspector实际启动服务器并调用工具需要Node.js 22.19.0环境。测试6文件缺失场景的错误传播行为未实际运行确认预期是FileNotFoundError会以异常形式中断工具调用但不同客户端可能以不同方式呈现。参考资料MCP Python SDK文档FastMCP工具定义与结构化输出https://py.sdk.modelcontextprotocol.ioMCP Inspector文档安装与CLI用法https://modelcontextprotocol.io/docs/2026-07-28/tools/inspectorMCP架构概览工具发现与调用流程https://modelcontextprotocol.io/docs/2026-07-28/learn/architecture核验日期2026年10月13日。