MCP Python SDK 服务端工具(Tools)开发指南:用 `@mcp.tool()` 把普通 Python 函数变成模型可调用的工具

发布时间:2026/9/20 10:03:08
MCP Python SDK 服务端工具(Tools)开发指南:用 `@mcp.tool()` 把普通 Python 函数变成模型可调用的工具 MCP Python SDK 服务端工具Tools开发指南用mcp.tool()把普通 Python 函数变成模型可调用的工具【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdk本指南基于 python-sdk 官方文档的 Tools 章节及其中文译文 i18n/es/pages/servers/tools.md系统讲解 Model Context ProtocolMCP服务端工具的声明、入参契约与元数据配置。读完本文你将掌握用装饰器声明工具、让类型注解自动生成 JSON Schema、用 Pydantic 校验与约束参数以及通过ToolAnnotations向客户端传递行为提示的完整实战方法。第一把工具整个 API 就是一个装饰器在 MCP 中工具tool就是模型可以调用的函数。而 python-sdk 声明工具的方式极其精简把一个装饰器放在普通 Python 函数上即可mcp.tool()就是全部 API。看 docs_src/tools/tutorial001.py 中的完整示例from mcp.server import MCPServer mcp MCPServer(Bookshop) mcp.tool() def search_books(query: str, limit: int) - str: Search the catalog by title or author. return fFound 3 books matching {query!r} (showing up to {limit}).注意这里没有任何 Schema、JSON 或协议样板只是一段朴素的 Python 代码。SDK 会从函数中读取三样东西自动构成工具定义工具名称取函数名search_books模型看到的描述取 docstringSearch the catalog by title or author.模型可以传入的参数取自类型注解query: str和limit: int。这一点在测试 tests/docs_src/test_tools.py 中被逐字验证list_tools返回的工具name、description与input_schema完全由上述三要素推导而来。输入 Schema类型注解自动生成 JSON Schema基于这些类型注解SDK 会在客户端执行tools/list时生成并下发一份 JSON Schema内容如下{ type: object, properties: { query: {title: Query, type: string}, limit: {title: Limit, type: integer} }, required: [query, limit], title: search_booksArguments }由于query和limit都没有默认值两者都出现在required列表中稍后你会看到如何把参数变为可选。这里的title键是 Pydantic 生成的工件真正构成契约的是properties、各属性的type以及required。同时注意 Schema 中没有$schema键MCP 会把缺少该键的 Schema 视为JSON Schema 2020-12方言而这正是 Pydantic 默认生成的方言因此除非你在底层 Server 中手写 Schema否则无需关心方言选择问题。提示在这里类型注解不是文档注释而是契约本身。如果客户端发送limit: ten这种类型错误的值SDK 会在你的函数真正执行之前就将其拒绝。模型拿到什么content与structured_content用{query: dune, limit: 5}调用该工具返回结果包含两部分result.content # [TextContent(textFound 3 books matching dune (showing up to 5).)] result.structured_content # {result: Found 3 books matching dune (showing up to 5).}content是模型阅读的文本内容structured_content是供客户端应用消费的类型化数据它之所以存在是因为你把返回类型声明为- str。测试 tests/docs_src/test_tools.py 精确复现了这一行为调用后result.content与result.structured_content与上述两个值完全一致且result.is_error为False。暂时不必纠结structured_content只要从工具中返回真实的 Python 对象SDK 会自动做出正确的事这正是结构化输出Structured Output页面专门讲解的主题。用 MCP Inspector 实测你的工具启动服务端并接入 MCP Inspector 非常直接uv run mcp dev server.py打开命令打印出的 URL进入Tools标签页调用search_books。Inspector 会根据你的类型注解渲染出一个表单必填的文本字段query和必填的数字字段limit。这个表单完全是由注解自动构建的任何其他 MCP 客户端都会以同样的方式解析并展示你的工具。参数可选化给一个默认值就够了想让参数变得可选只需给参数一个默认值——这就是 Python 本身的语义from mcp.server import MCPServer mcp MCPServer(Bookshop) mcp.tool() def search_books(query: str, limit: int 10) - str: Search the catalog by title or author. return fFound 3 books matching {query!r} (showing up to {limit}).对应的完整示例见 docs_src/tools/tutorial002.py。生成的 Schema 会同步反映这一变化{ type: object, properties: { query: {title: Query, type: string}, limit: {default: 10, title: Limit, type: integer} }, required: [query], title: search_booksArguments }limit从required中消失同时获得default: 10。客户端省略该参数时函数会收到10与 Python 的默认参数行为完全一致。测试 tests/docs_src/test_tools.py 用{query: dune}调用后断言structured_content中的结果为showing up to 10验证了默认值的透传。用Field构建更丰富的 Schema类型注解能做的事情很多但有时你还需要描述参数或对参数加以约束。方法是用Annotated包裹类型并附加一个 Pydantic 的Fieldfrom typing import Annotated, Literal from pydantic import Field from mcp.server import MCPServer mcp MCPServer(Bookshop) mcp.tool() def search_books( query: Annotated[str, Field(descriptionTitle or author to search for.)], limit: Annotated[int, Field(ge1, le50, descriptionMaximum number of results.)] 10, genre: Literal[fiction, non-fiction, poetry] | None None, ) - str: Search the catalog by title or author. where f in {genre} if genre else return fFound 3 books matching {query!r}{where} (showing up to {limit}).完整示例见 docs_src/tools/tutorial003.py。这里有三处新东西全部写在参数上Field(description...)为单个参数提供描述模型会连同 docstring 一起阅读Field(ge1, le50)数值上下界会落到 Schema 的minimum: 1, maximum: 50Literal[fiction, non-fiction, poetry]枚举约束模型只能从这三个值中选一个。测试 tests/docs_src/test_tools.py 验证了这些元数据确实进入了 Schemaprops[limit]中包含minimum、maximum、description与default而genre的anyOf[0].enum正是三个枚举值。约束不是装饰模型可以自己纠错这些约束并不仅仅是文档装饰。以limit999调用工具SDK 会在你的函数执行前直接返回一个工具错误Input should be less than or equal to 50该错误会作为工具结果回到模型手里result.is_error为True见 tests/docs_src/test_tools.py模型读到后会自动改用合法值重试。你只写了一次le50就免费得到了一个会自我纠错的 Agent 行为。说明如果你用过 FastAPI 或 Pydantic这里没有需要新学的东西——同一个Field、同一个Annotated、同一套校验机制没有任何 MCP 特有的额外知识。参数较多时聚合为 Pydantic 模型当工具的参数超过一两对时把它们聚合进一个 Pydantic 模型是更好的做法from pydantic import BaseModel, Field from mcp.server import MCPServer mcp MCPServer(Bookshop) class Book(BaseModel): title: str author: str year: int Field(ge1450, descriptionYear of first publication.) mcp.tool() def add_book(book: Book) - str: Add a book to the catalog. return fAdded {book.title!r} by {book.author} ({book.year}).完整示例见 docs_src/tools/tutorial004.py。此时Book的 Schema 会嵌套进工具的输入 Schema以$defs引用形式存在测试在 tests/docs_src/test_tools.py 中断言input_schema[$defs][Book][required] [title, author, year]。客户端把模型参数当作一个 JSON 对象填充而你的函数收到的是一个已经过校验的真实Book实例可以直接访问.title、.author、.year属性——模型内部的Field(ge1450)约束同样生效。你可以自由组合普通参数与模型参数混用、模型嵌套模型、模型列表……底层全是 Pydantic。async def为 I/O 而生如果工具要做 I/O调用 API、读文件、查数据库把它声明为async def并在内部使用awaitSDK 会负责等待协程完成。而普通的def工具同样可用SDK 会在线程中执行它确保它永远不会阻塞服务器。除此之外没有任何额外配置。mcp.tool()装饰器还支持传入Context类型注解的参数让工具获得日志、进度上报、资源访问等 MCP 能力装饰器文档中的示例src/mcp/server/mcpserver/server.py演示了ctx.info(...)与context.report_progress(...)的用法。名称、标题与注解覆盖 SDK 的所有推断SDK 自动推断的一切都可以在装饰器中显式覆盖from mcp.server import MCPServer from mcp.types import ToolAnnotations mcp MCPServer(Bookshop) mcp.tool( titleSearch the catalog, annotationsToolAnnotations(read_only_hintTrue, open_world_hintFalse), ) def search_books(query: str) - str: Search the catalog by title or author. return fFound 3 books matching {query!r}.完整示例见 docs_src/tools/tutorial005.py其元数据在 tests/docs_src/test_tools.py 中被断言为tool.title Search the catalog且annotations与ToolAnnotations(read_only_hintTrue, open_world_hintFalse)相等。title面向用户界面的人性化名称。客户端会显示Search the catalog而不是search_booksannotations给客户端的行为提示read_only_hintTrue该工具不会改变任何状态open_world_hintFalse它作用于一个封闭的对象集合例如本地目录而不是开放的互联网另外两个destructive_hint与idempotent_hint用于描述写操作类工具它是否会删除某些东西调用两次是否等价于调用一次规范只为非只读工具定义了这两个提示所以在search_books这种只读工具上它们没有任何意义。行为良好的客户端会依据这些提示做出诸如执行前是否需要询问用户的决策。但要牢记它们是提示不是安全机制永远不要假定客户端一定会遵守。提示如果不想从函数名和 docstring 推导mcp.tool()也接受name与description参数。不过在大多数情况下让 SDK 自动推导正是你想要的行为。装饰器的源码级全貌从源码看MCPServer.tool()的完整签名位于 src/mcp/server/mcpserver/server.py它接受以下关键字参数参数作用name工具名默认取函数名title面向 UI 的人性化标题description工具描述默认取 docstringannotationsToolAnnotations行为提示icons工具的图标列表meta附加元数据字典structured_output控制输出是否结构化None时按函数返回类型注解自动探测True强制创建结构化工具需返回类型允许False强制非结构化实现细节中还有一个值得注意的防护如果把装饰器写成tool忘记加括号代码会抛出TypeError提示信息明确写道The tool decorator was used incorrectly. Did you forget to call it? Use tool() instead of toolsrc/mcp/server/mcpserver/server.py帮助开发者第一时间发现误用。小结在函数上加mcp.tool()即可把它变成工具名称取自函数名描述取自 docstring类型注解就是输入 Schema默认值让参数变为可选Annotated[..., Field(...)]添加描述与约束Literal添加枚举Pydantic 模型参数是接收结构化请求体的标准方式非法参数由 SDK 替你拦截返回的报错模型可读、可据此恢复I/O 用async def其余用普通def即可。关于return返回值的去向——它会被包装成结构化输出这正是结构化输出Structured Output章节的主题可以继续深入阅读。【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考