1. 工具是什么? #

工具(Tools) 是 MCP 服务器暴露的可执行函数,供大模型根据对话内容自动调用。

对比 资源(Resources) 工具(Tools)
作用 读数据 做事情
谁决定用 用户 / 应用 大模型
类比 打开文件看内容 按一下按钮执行操作

典型例子:查天气、搜数据库、发 HTTP 请求、创建 GitHub Issue。

你需要知道的两个协议方法:

方法 作用 客户端 SDK 写法
tools/list 列出所有工具 await session.list_tools()
tools/call 执行指定工具 await session.call_tool(name, args)

2. 从用户提问到工具执行 #

用户问「杭州天气怎么样」时,完整链路如下:

sequenceDiagram participant U as 用户 participant App as AI应用 participant LLM as 大模型 participant S as MCP服务器 App->>S: tools/list S-->>App: [get_weather, ...] U->>App: 杭州天气怎么样? App->>LLM: 问题 + 工具描述 LLM-->>App: 调用 get_weather(city=杭州) App->>S: tools/call S-->>App: {"temp":"22°C",...} App->>LLM: 工具结果 LLM-->>App: 自然语言回答 App-->>U: 杭州今天 22°C,晴

在 Cursor 里,「App」就是编辑器内置的 MCP 客户端,工具列表获取和调用大多自动完成;你自己写服务器时,重点是把工具注册好、描述写清楚。

3. 工具长什么样? #

每个工具对外暴露三个核心字段(FastMCP 会根据函数自动生成):

字段 含义 举例
name 唯一标识,调用时用 get_weather
description 功能说明,帮模型决定何时调用 查询指定城市的天气
inputSchema 参数的 JSON Schema {city: string}

3.1 list 响应示例(Inspector 里可见) #

{
  "tools": [
    {
      "name": "get_weather",
      "description": "查询指定城市的天气",
      "inputSchema": {
        "type": "object",
        "properties": {
          "city": { "type": "string", "description": "城市名" }
        },
        "required": ["city"]
      }
    }
  ]
}

3.2 call 请求与响应示例 #

请求:

{
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": { "city": "杭州" }
  }
}

响应:

{
  "content": [
    { "type": "text", "text": "{\"city\": \"杭州\", \"temp\": \"22°C\"}" }
  ],
  "isError": false
}

4. 服务器端:注册工具 #

用 FastMCP 时,把 Python 函数用 `@mcp.tool()` 装饰,就是注册工具。无需手写 tools/list / tools/call。

4.1 最简工具(无参数) #

tool_server.py

# 导入 FastMCP 库,提供构建多工具服务器的能力
from mcp.server.fastmcp import FastMCP

# 创建一个 FastMCP 服务器实例,服务器名称为 "hello-server"
mcp = FastMCP(name="hello-server")

# 使用 @mcp.tool() 装饰器注册 greet 工具,工具名默认为函数名
@mcp.tool()
# 定义 greet 函数,接收可选参数 name,默认为 "World",返回问候字符串
def greet(name: str = "World") -> str:
    # 返回格式化的问候字符串
    return f"Hello, {name}!"

# 判断是否作为主模块运行
if __name__ == "__main__":
    # 启动服务器,使用 stdio 作为传输方式
    mcp.run(transport="stdio")

4.2 带参数的工具 #

函数参数会自动变成 inputSchema。类型注解和 docstring 会帮助生成更准确的描述:

# 导入json库,用于进行JSON格式的数据编码和解码
import json
# 从mcp.server.fastmcp模块导入FastMCP类,用于创建MCP服务器
from mcp.server.fastmcp import FastMCP

# 创建一个FastMCP服务器实例,服务器名称为"weather-server"
mcp = FastMCP(name="weather-server")

# 使用mcp.tool装饰器注册天气查询工具
@mcp.tool()
# 定义get_weather函数,入参为城市名,返回类型为字符串
def get_weather(city: str) -> str:
    # 函数文档字符串,描述该工具的用途
    """查询指定城市的当前天气。"""
    # 构建包含城市、温度和天气状况的字典数据
    data = {"city": city, "temp": "22°C", "condition": "晴"}
    # 将字典数据转换为JSON字符串并返回,确保中文字符正常显示
    return json.dumps(data, ensure_ascii=False)

# 如果当前模块作为主程序运行,则启动服务器
if __name__ == "__main__":
    # 启动MCP服务器,指定传输方式为stdio(标准输入输出)
    mcp.run(transport="stdio")

4.3 自定义工具名和描述 #

# 导入 json 库,用于数据的 JSON 编解码
import json
# 从 mcp.server.fastmcp 模块导入 FastMCP,用来创建 MCP 服务器实例
from mcp.server.fastmcp import FastMCP

# 创建 FastMCP 服务器实例,命名为 "city-weather-server"
mcp = FastMCP(name="city-weather-server")

# 使用 mcp.tool 装饰器注册工具,指定工具名为 "search_city_weather",描述为"根据城市名查询实时天气"
@mcp.tool(name="search_city_weather", description="根据城市名查询实时天气")
# 定义 get_weather 函数,接收一个城市名参数 city,返回字符串类型
def get_weather(city: str) -> str:
    # 为函数添加文档字符串,描述函数作用
    """根据城市名查询实时天气。"""
    # 定义包含城市、温度和天气状况的字典,模拟返回结果
    data = {
        "city": city,
        "temp": "25°C",
        "condition": "多云"
    }
    # 将字典数据编码为 json 字符串并返回,ensure_ascii=False 保证中文正常显示
    return json.dumps(data, ensure_ascii=False)

# 判断当前模块是否为主模块(直接运行而非被导入)
if __name__ == "__main__":
    # 启动 MCP 服务器,指定传输方式为 stdio(标准输入输出)
    mcp.run(transport="stdio")

4.4 返回值怎么处理? #

你返回的类型 客户端看到什么
str content 里一条 type: text
dict / list FastMCP 会序列化成文本
抛异常 isError: true,错误信息在 content 里

4.5 启动服务器 #

npx @modelcontextprotocol/inspector uv run tool_server.py

5. 客户端:列出并调用工具 #

初始化完成后,才能 list_tools 和 call_tool。

5.1 示例 #

# 异步客户端
# 导入异步IO模块
import asyncio
# 导入os模块用于路径操作
import os
# 导入sys模块以便使用解释器路径
import sys

# 从mcp包导入ClientSession和StdioServerParameters类
from mcp import ClientSession, StdioServerParameters
# 从mcp.client.stdio模块导入stdio_client函数
from mcp.client.stdio import stdio_client

# 定义主异步函数
async def main() -> None:
    # 获取当前文件的绝对路径所在目录
    base_dir = os.path.dirname(os.path.abspath(__file__))
    # 构造server端脚本的绝对路径
    server_path = os.path.join(base_dir, "tool_server.py")

    # 创建StdioServerParameters对象,配置服务器执行命令和参数
    server_params = StdioServerParameters(
        command=sys.executable,  # Python可执行文件
        args=[server_path],      # 指定要运行的服务端脚本
    )

    # 使用stdio_client启动异步通信,获取读写流
    async with stdio_client(server_params) as (read, write):
        # 创建客户端会话
        async with ClientSession(read, write) as session:
            # ① 必须先初始化
            # 初始化会话
            await session.initialize()

            # ② 列出所有工具
            # 列出所有可用工具
            tools = await session.list_tools()
            # 打印工具名称列表
            print("工具列表:", [t.name for t in tools.tools])

            # 打印某个工具的参数 schema(可选,调试用)
            # 遍历所有工具,打印名称、描述和入参schema
            for t in tools.tools:
                print(f"  - {t.name}: {t.description}")
                print(f"    参数: {t.inputSchema}")

            # ③ 调用工具
            # 调用名为greet的工具并传递参数
            result = await session.call_tool("greet", {"name": "MCP"})

            # ④ 读取返回内容
            # 遍历返回内容中的所有block
            for block in result.content:
                # 如果block对象具有text属性
                if hasattr(block, "text"):
                    # 打印调用结果文本
                    print("调用结果:", block.text)

            # 若工具执行失败,检查 isError
            # 如果调用结果有错误,打印提示信息
            if result.isError:
                print("工具执行出错")

# 如果当前脚本是主程序入口
if __name__ == "__main__":
    # 运行异步主函数
    asyncio.run(main())

5.2 调用带参数的工具 #

tool_client.py

# 导入操作系统相关模块
import os
# 导入与Python解释器交互的sys模块
import sys
# 导入异步IO模块
import asyncio

# 从mcp.rpc模块导入StdioServerParameters、stdio_client和ClientSession类
from mcp.rpc import StdioServerParameters, stdio_client, ClientSession

# 定义主异步函数
async def main():
    # 获取当前文件所在的目录路径
    base_dir = os.path.dirname(os.path.abspath(__file__))
    # 拼接得到服务端脚本的完整路径
    server_path = os.path.join(base_dir, "02_full_server.py")
    # 创建StdioServerParameters对象,指定Python解释器和服务端脚本路径
    server_params = StdioServerParameters(
        command=sys.executable,
        args=[server_path],
    )

    # 使用stdio_client建立与服务端的异步通信,获取读写流
    async with stdio_client(server_params) as (read, write):
        # 创建客户端会话对象
        async with ClientSession(read, write) as session:
            # 初始化会话(必须先调用)
            await session.initialize()
            # 异步调用名为"get_weather"的工具,传入参数{"city": "杭州"}
            result = await session.call_tool("get_weather", {"city": "杭州"})
            # 遍历返回结果内容中的每一个block
            for block in result.content:
                # 判断block对象是否有"text"属性
                if hasattr(block, "text"):
                    # 打印每个结果文本
                    print(block.text)
            # 调用get_weather示例返回: {"city": "杭州", "temp": "22°C", "condition": "晴"}

# 判断当前脚本是否作为主模块运行
if __name__ == "__main__":
    # 运行异步主函数
    asyncio.run(main())

5.3 运行 #

uv run  tool_client.py

期望输出:

工具列表: ['greet']
  - greet:
    参数: {'properties': {'name': {'default': 'World', 'title': 'Name', 'type': 'string'}}, 'title': 'greetArguments', 'type': 'object'}
调用结果: Hello, MCP!

5.4 list_tools() 返回什么? #

属性 含义
tools.tools 工具对象列表
tool.name 工具名
tool.description 描述
tool.inputSchema 参数 JSON Schema

5.5 call_tool() 参数说明 #

# 第一个参数:工具名(字符串,与 @mcp.tool 注册名一致)
# 第二个参数:参数字典(键名与函数参数名一致)
result = await session.call_tool("get_weather", {"city": "上海"})
常见错误 原因
Unknown tool 工具名拼错,或服务器未注册该工具
参数无效 缺少必填参数,或类型不对
未 initialize 忘了先调用 await session.initialize()

6. 用 Inspector 测试工具 #

不写客户端代码,也能快速验证工具是否注册正确。

6.1 启动 #

npx @modelcontextprotocol/inspector uv run tool_server.py

6.2 操作步骤 #

步骤 操作
1 浏览器打开 Inspector,确认 Connected
2 切到 Tools 标签
3 选中工具(如 greet 或 get_weather)
4 在参数表单里填值
5 点击执行,查看返回 JSON

6.3 用 Inspector 排查问题 #

现象 可能原因
Tools 列表为空 没有 `@mcp.tool()`,或服务器启动失败
执行报错 参数缺失、函数内部异常
连接失败 命令路径错误,先终端手动跑同样命令

7. 写好工具的三条建议 #

工具描述直接影响模型会不会、能不能正确调用。

建议 好的做法 差的做法
名称清晰 get_weather、search_flights tool1、doStuff
描述具体 查询指定城市的实时温度和天气状况 天气工具
参数有说明 用类型注解 + docstring 参数名 a、b

函数 docstring 示例:

@mcp.tool()
def search_flights(origin: str, destination: str, date: str) -> str:
    """搜索两个城市之间的可用航班。

    Args:
        origin: 出发城市,如「北京」
        destination: 到达城市,如「上海」
        date: 出发日期,格式 YYYY-MM-DD
    """
    ...