ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python MCP SDK 服务端工具(Tools)开发指南:从 `@mcp.tool()` 装饰器到结构化工具定义

Python MCP SDK 服务端工具(Tools)开发指南:从 `@mcp.tool()` 装饰器到结构化工具定义 Python MCP SDK 服务端工具Tools开发指南从mcp.tool()装饰器到结构化工具定义【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdk导读本文面向使用官方 Python SDKpython-sdk构建 Model Context ProtocolMCP服务端的开发者围绕服务端**工具Tool**的声明、输入校验、元数据与执行模型展开完整讲解。读完本文你将掌握如何用一段纯 Python 函数 mcp.tool()装饰器声明一个可被模型调用的工具理解类型注解如何自动生成 JSON Schema 输入契约、默认值与Field如何约束参数、Pydantic 模型参数如何聚合复杂入参、同步/异步函数各自的执行方式以及title与ToolAnnotations等行为提示hints的语义与边界。工具的本质模型可调用的一个函数在 MCP 协议中**工具tool**是服务端暴露给模型LLM调用的一段能力。SDK 对工具的声明做了极致的简化——在普通 Python 函数上放置mcp.tool()装饰器即可无需手写任何 JSON Schema、协议报文或注册代码。这与 MCP 将工具划分为Tools能力域通过tools/list、tools/call方法交互的协议设计相对应而 SDK 的目标就是把这套协议细节完全封装起来。第一个工具签名即定义最简工具的完整代码见 docs_src/tools/tutorial001.pyfrom 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 或协议代码SDK 只从这个函数读取三样东西工具名name即函数名search_books模型可见的描述description即 docstringSearch the catalog by title or author.模型可传入的参数arguments来自类型注解query: str与limit: int。输入 Schema类型注解即契约基于这些类型注解SDK 生成 JSON Schema并在tools/list握手时下发给客户端{ 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、类型与required列表。Schema 中没有$schema键。MCP 协议将不带该键的 Schema 按JSON Schema 2020-12方言处理而 Pydantic 生成的正是这一方言因此只要不用手写 Schema就无需关心方言选择问题手工编写 Schema 的低层服务器场景见 docs/advanced/low-level-server.md。需要特别强调这里的类型注解不是文档而是契约。如果客户端发送limit: tenSDK 会在你的函数体运行之前就将其拒绝。源码层面这一校验由 src/mcp/server/mcpserver/utilities/func_metadata.py 的validate_arguments()完成——它先对参数做预处理再交给由签名构建的 Pydanticarg_model做model_validate()任何类型不符都会抛出ValidationError在 src/mcp/server/mcpserver/tools/base.py 中被转换为ToolError返回给客户端。模型拿到的返回值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。这里可以先不纠结structured_content工具返回真实 Python 对象即可得到正确的行为其完整机制在 Structured Output结构化输出 中有专门论述。测试 tests/docs_src/test_tools.py 直接验证了这一行为——result.content为单个TextContent且is_error为False。立即试跑MCP Inspector用 MCP Inspector 启动服务器uv run mcp dev server.py打开终端打印的 URL切到Tools标签页调用search_books。Inspector 会根据类型注解渲染出一个必填文本字段query和一个必填数字字段limit——其他所有 MCP 客户端都会以同样的方式消费这个 Schema。可选参数给默认值即可给参数一个默认值它就不再必填仅此而已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}).对应的 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 验证了省略limit调用时返回(showing up to 10)。更丰富的 SchemaAnnotatedField类型注解能表达大多数情况但有时你需要描述一个参数或对参数施加约束。把类型包进Annotated并追加一个 PydanticField即可完整代码见 docs_src/tools/tutorial003.pyfrom 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}).三处新东西全部作用于参数Field(description...)为单个参数提供描述模型会结合 docstring 一起阅读Field(ge1, le50)数值上下界落到 Schema 中即为minimum: 1, maximum: 50Literal[fiction, non-fiction, poetry]枚举模型只能从中取值。测试 tests/docs_src/test_tools.py 确认了limit的 Schema 中包含minimum、maximum、description与default而genre以anyOf[0].enum形式呈现。约束不是装饰越界参数在函数体执行前被拒绝以limit999调用该工具SDK 会在函数执行前以工具错误tool error回应Input should be less than or equal to 50这条错误以工具结果的形式回到模型手中模型读到后会用合法值重试。你只写了le50一次就免费获得了会自我纠错的 Agent。测试 tests/docs_src/test_tools.py 验证了该调用返回is_errorTrue且错误文本确实包含less than or equal to 50。如果你用过 FastAPI 或 Pydantic以上内容对你而言是零学习成本——这是同一个Field、同一个Annotated、同一套校验没有任何 MCP 特有的知识需要额外掌握。用 Pydantic 模型作为参数结构化请求体当工具参数超过两三个时把它们聚合进一个 Pydantic 模型完整代码见 docs_src/tools/tutorial004.pyfrom 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}).Book的 Schema 会以$defs引用的形式嵌套进工具输入 Schema客户端按 JSON 对象填充它而你的函数收到的是一个真正、已经过校验的Book实例可直接访问.title、.author、.year属性。测试 tests/docs_src/test_tools.py 验证了$defs[Book][required] [title, author, year]并以{book: {title: Dune, author: Frank Herbert, year: 1965}}调用成功。你可以自由组合普通参数与模型参数并列、模型嵌套模型、模型列表等全程都是 Pydantic。async defI/O 工具的推荐写法如果工具涉及 I/O调用 API、读文件、查数据库把它声明为async def并在内部awaitSDK 会负责等待它mcp.tool() async def fetch_weather(city: str) - str: data await weather_api.get(city) return f{city}: {data[temp]}°C普通def工具同样可用——SDK 会把同步函数放到 worker 线程执行因此它永远不会阻塞服务器。这一点在源码中有明确实现工具的执行入口 src/mcp/server/mcpserver/tools/base.py 调用fn_metadata.call_fn而 src/mcp/server/mcpserver/utilities/func_metadata.py 中异步函数被直接await fn(**kwargs)同步函数则通过anyio.to_thread.run_sync()在独立线程中运行。工具注册时即通过is_async_callable()判定函数是否异步见 src/mcp/shared/_callable_inspection.py 与 src/mcp/server/mcpserver/tools/base.py。除此之外无需任何额外配置。覆盖推断结果title、annotations与显式name/descriptionSDK 从函数推断出的一切都可以在装饰器中覆盖完整代码见 docs_src/tools/tutorial005.pyfrom 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}.title是面向用户界面的人类可读名称客户端会展示Search the catalog而非search_booksannotations是面向客户端的行为提示hintsread_only_hintTrue该工具不会修改任何东西open_world_hintFalse它作用于封闭的事物集合这个目录而非开放的互联网另外两个destructive_hint与idempotent_hint用于描述写入型工具它是否可能删除内容重复调用两次与调用一次效果是否相同规范只为非只读工具定义这两个提示因此放在search_books上不产生任何语义。行为良好的客户端会用这些提示来决策例如执行前是否需要征求用户同意。但要清醒地认识到它们是提示hints不是安全机制永远不要依赖某个客户端会遵守它们。这一点在类型定义 src/mcp-types/mcp_types/_types.py 的文档字符串中写得很明确——ToolAnnotations的所有属性都是提示不被信任的服务端提供的注释不应作为工具使用决策的依据四个 hint 字段read_only_hint、destructive_hint、idempotent_hint、open_world_hint均为可选布尔值并带有默认语义。此外装饰器还接受显式的name与description当你不希望从函数名和 docstring 推导时可以直接传入——在多数场景下这正是你想要的。完整参数列表可见 src/mcp/server/mcpserver/server.py 中tool()的签名还支持icons、meta与structured_output测试 tests/docs_src/test_tools.py 验证了title与annotations会原样出现在tools/list返回的工具定义中。深入源码注册、查重与执行链路理解装饰器背后的机制有助于排障。MCPServer.tool()src/mcp/server/mcpserver/server.py是一个工厂装饰器内部通过ToolManager.add_tool()src/mcp/server/mcpserver/tools/tool_manager.py注册工具Tool.from_function()依据签名构建输入模型与输出模型若同名工具已存在默认打印Tool already exists: name警告并保留原工具该行为可由MCPServer的warn_on_duplicate_tools设置关闭见 src/mcp/server/mcpserver/server.py。tools/list时ToolManager.list_tools()tool_manager.py返回全部注册工具。一次tools/call的完整执行链路为参数校验validate_arguments→ 错误转换为ToolError/UnexpectedToolErrortools/base.py→ 执行函数体call_fn同步函数走 worker 线程→ 可选的结果结构化转换。整个文档对应的行为均由 tests/docs_src/test_tools.py 逐条锁定可作为文档声称的每一条都能被真实 SDK 证明的对照实验。小结mcp.tool()作用于函数即使其成为工具名称来自函数名描述来自 docstring类型注解就是输入 Schema默认值使参数变为可选Annotated[..., Field(...)]为参数添加描述与约束Literal添加枚举Pydantic 模型参数是接收结构化请求体的方式非法参数会被自动拒绝并返回一条模型可读、可据此恢复的错误I/O 用async def其余情况用普通def同步函数在 worker 线程中执行。关于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),仅供参考
返回列表