ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python MCP SDK 工具开发指南:用 `@mcp.tool()` 声明模型可调用的函数

Python MCP SDK 工具开发指南:用 `@mcp.tool()` 声明模型可调用的函数 Python MCP SDK 工具开发指南用mcp.tool()声明模型可调用的函数【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdk在 Model Context ProtocolMCP中工具tool就是模型可以调用的函数。本文基于 pythonsd/python-sdk 官方仓库的文档与源码完整讲解如何用mcp.tool()装饰器把一个普通 Python 函数变成 MCP 工具从函数名、docstring、类型提示自动推导出工具名称、描述与 JSON Schema 输入契约再到可选参数、Field约束、Pydantic 模型参数、async def异步工具以及title与ToolAnnotations行为提示。读完本文你将能独立编写一个可被任意 MCP 客户端发现并调用的工具服务并用 MCP Inspector 完成端到端验证。你的第一个工具在 MCP 中声明一个工具的 API 极其简洁把mcp.tool()装饰器贴在一个普通 Python 函数上即可。没有手写 schema、没有 JSON、没有协议细节只有函数本身。以下代码摘自仓库教程 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}).SDK 从这段函数中读取三样东西工具的名称来自函数名search_books模型看到的描述来自 docstringSearch the catalog by title or author.模型可以传入的参数来自类型提示query: str与limit: int。输入 schemaThe input schemaSDK 会根据类型提示生成 JSON Schema并在tools/list握手阶段发送给客户端。上面函数的输入 schema 如下{ type: object, properties: { query: {title: Query, type: string}, limit: {title: Limit, type: integer} }, required: [query, limit], title: search_booksArguments }两个参数都没有默认值所以都出现在required中这个马上就会改掉。其中的title键是 Pydantic 生成的附带产物真正构成“契约”的是属性、属性类型与required列表。另外注意这里没有$schema键。MCP 把不带该键的 schema 一律视为JSON Schema 2020-12而 Pydantic 生成的正是这一方言所以在 低级 Server 上手工编写 schema 之前你不需要做任何选择。!!! tip 在这里类型提示不是文档而是契约本身。如果客户端发送limit: tenSDK 会在你的函数运行之前就拒绝该请求。从源码看MCPServer的add_tool方法见 src/mcp/server/mcpserver/server.py会把函数连同name、title、description、annotations、icons、meta、structured_output等配置一并交给ToolManager注册schema 的生成与参数校验正是发生在这一注册与后续调用链路上。模型拿回什么用{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 对象SDK 就会自动做正确的事。这个主题在结构化输出页面有完整讲解。动手试一试Try it用 MCP Inspector 启动服务器uv run mcp dev server.py打开命令打印出的 URL进入Tools标签页然后调用search_books。Inspector 会根据你的类型提示渲染一个表单必填的query文本字段 必填的limit数字字段。所有其他 MCP 客户端都会做同样的事情——表单本身就是从类型提示推导出来的。可选参数Optional arguments给参数一个默认值它就不再是必填项。仅此而已这就是普通 Python 语义。代码见 docs_src/tools/tutorial002.pyfrom 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 默认参数行为完全一致。用Field构建更丰富的 schema类型提示能做的事情很多但有时你希望对参数做描述或约束。把类型包进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]枚举类型模型只能从中选一个。!!! check 约束不是装饰品。用limit999调用工具SDK 会在函数执行之前就返回一个工具错误text Input should be less than or equal to 50 这个错误会作为工具结果回到模型那里模型读到错误后会换一个合法值重试。你只写了一次 le50就免费得到了一个会自我纠正的 Agent。!!! info 如果你用过 FastAPI 或 Pydantic这些你都已了然于胸同一个Field、同一个Annotated、同一套校验。这里没有任何 MCP 特有的新知识。用模型作为参数A model as a parameter当工具的参数超过两三个时把它们打包进一个 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属性。组合方式非常自由普通参数与模型参数并列、模型嵌套模型、接收模型列表全部可行——自底向上都是 Pydantic。async def异步工具如果工具要做 I/O调用 API、读文件、查询数据库就把它声明为async def并在函数内部使用await。SDK 会负责 await 它mcp.tool() async def fetch_price(symbol: str) - str: price await market_api.get(symbol) # 异步 I/O return f{symbol}: {price}普通的def工具同样可以工作SDK 会在线程中运行它因此不会阻塞服务器的事件循环。这里没有任何额外配置项仅此而已。从 src/mcp/server/mcpserver/server.py 的装饰器实现看同步与异步函数都走同一条注册路径调用时由 SDK 统一调度执行。名称、标题与注解Names, titles, and annotationsSDK 推断出来的一切都可以在装饰器中覆盖。代码见 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面向 UI 的人性化名称。客户端会显示Search the catalog而不是search_booksannotations面向客户端的行为提示hintsread_only_hintTrue该工具不会修改任何东西open_world_hintFalse它作用于一个封闭集合这个书目而非开放网络另外两个destructive_hint与idempotent_hint描述的是写入型工具它是否可能删除某些内容调用两次与调用一次结果是否相同规范只为非只读工具定义这两个字段因此把它们挂在search_books上没有任何意义。在仓库的 src/mcp-types/mcp_types/_types.py 中ToolAnnotations共定义了五个可选字段title、read_only_hint默认false、destructive_hint仅在read_only_hint false时有意义默认true、idempotent_hint同样仅对非只读工具有意义默认false、open_world_hint默认true。其源码注释明确强调这些属性都是提示hints并不保证对工具行为的忠实描述客户端不应基于来自不受信任服务器的ToolAnnotations做工具使用决策。一个行为良好的客户端会依据这些提示做出判断例如运行这个工具之前需要先询问用户吗。但它们只是提示不是安全机制——永远不要指望客户端会遵守它们。!!! tip 如果你不想从函数名和 docstring 推导mcp.tool()也接受name与description参数。大多数时候直接用默认推导即可。回顾Recap给函数加mcp.tool()装饰器它就成了工具名称取自函数名描述取自 docstring类型提示就是输入 schema有默认值的参数自动变为可选Annotated[..., Field(...)]添加描述与约束Literal添加枚举Pydantic 模型参数是接收结构化请求体的方式非法参数会被自动拒绝并返回一个模型能读懂、能自我恢复的错误I/O 用async def其余情况用普通def。关于return返回值的去向请继续阅读结构化输出一文。【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表