New in version: 2.0.0

fastmcp.Client 提供了一个高级异步接口,用于与任何模型上下文协议(MCP)服务器进行交互,无论该服务器是使用FastMCP还是其他实现构建的。它通过处理协议细节和连接管理来简化通信。

FastMCP客户端

FastMCP客户端架构将协议逻辑(Client)与连接机制(Transport)分离。

  • Client: 负责发送MCP请求(如tools/callresources/read),接收响应并管理回调。
  • Transport: 负责建立和维护与服务器的连接(例如通过WebSockets、SSE、Stdio或内存方式)。

传输方式

客户端必须使用transport进行初始化。您既可以提供一个已经实例化的传输对象,也可以提供一个传输源,让FastMCP尝试推断应使用的正确传输方式。

以下推理规则用于根据输入类型确定合适的ClientTransport

  1. ClientTransport 实例: 如果提供了已实例化的传输对象,则直接使用该对象。
  2. FastMCP 实例: 创建一个FastMCPTransport用于高效的内存通信(非常适合测试场景)。
  3. Path or str pointing to an existing file:
    • 如果以.py结尾:创建一个PythonStdioTransport来使用python运行该脚本。
    • 如果以.js结尾:创建一个NodeStdioTransport来使用node运行该脚本。
  4. AnyUrl or str pointing to a URL that begins with http:// or https://:
    • 创建一个StreamableHttpTransport
  5. 其他: 如果无法推断类型,则引发ValueError
import asyncio
from fastmcp import Client, FastMCP

# Example transports (more details in Transports page)
server_instance = FastMCP(name="TestServer") # In-memory server
http_url = "https://example.com/mcp"        # HTTP server URL
ws_url = "ws://localhost:9000"             # WebSocket server URL
server_script = "my_mcp_server.py"         # Path to a Python server file

# Client automatically infers the transport type
client_in_memory = Client(server_instance)
client_http = Client(http_url)
client_ws = Client(ws_url)
client_stdio = Client(server_script)

print(client_in_memory.transport)
print(client_http.transport)
print(client_ws.transport)
print(client_stdio.transport)

# Expected Output (types may vary slightly based on environment):
# <FastMCP(server='TestServer')>
# <StreamableHttp(url='https://example.com/mcp')>
# <WebSocket(url='ws://localhost:9000')>
# <PythonStdioTransport(command='python', args=['/path/to/your/my_mcp_server.py'])>

如需更精细地控制连接细节(例如SSE的请求头、Stdio的环境变量),您可以自行实例化特定的ClientTransport类并将其传递给Client。详情请参阅传输方式页面。

客户端使用指南

连接生命周期

客户端以异步方式运行,必须在async with代码块中使用。该上下文管理器负责建立连接、初始化MCP会话以及在退出时清理资源。

import asyncio
from fastmcp import Client

client = Client("my_mcp_server.py") # Assumes my_mcp_server.py exists

async def main():
    # Connection is established here
    async with client:
        print(f"Client connected: {client.is_connected()}")

        # Make MCP calls within the context
        tools = await client.list_tools()
        print(f"Available tools: {tools}")

        if any(tool.name == "greet" for tool in tools):
            result = await client.call_tool("greet", {"name": "World"})
            print(f"Greet result: {result}")

    # Connection is closed automatically here
    print(f"Client connected: {client.is_connected()}")

if __name__ == "__main__":
    asyncio.run(main())

您可以在同一个async with代码块中利用已建立的会话多次调用服务器。

客户端方法

Client 提供了与标准 MCP 请求对应的方法:

标准客户端方法返回用户友好的表示形式,这些表示可能会随着协议演变而改变。如需一致访问完整数据结构,请使用后文描述的*_mcp方法。

工具操作

  • list_tools(): 获取服务器上可用的工具列表。
    tools = await client.list_tools()
    # tools -> list[mcp.types.Tool]
    
  • call_tool(name: str, arguments: dict[str, Any] | None = None, timeout: float | None = None): Executes a tool on the server.
    result = await client.call_tool("add", {"a": 5, "b": 3})
    # result -> list[mcp.types.TextContent | mcp.types.ImageContent | ...]
    print(result[0].text) # Assuming TextContent, e.g., '8'
    
    # With timeout (aborts if execution takes longer than 2 seconds)
    result = await client.call_tool("long_running_task", {"param": "value"}, timeout=2.0)
    
    • 参数以字典形式传递。如果需要处理复杂类型,FastMCP服务器会自动进行JSON字符串解析。
    • 返回一个内容对象列表(通常是TextContentImageContent)。
    • 可选的 timeout 参数用于限制此特定调用的最长执行时间(以秒为单位),将覆盖客户端级别的超时设置。

资源操作

  • list_resources(): 获取静态资源列表
    resources = await client.list_resources()
    # resources -> list[mcp.types.Resource]
    
  • list_resource_templates(): 获取资源模板列表
    templates = await client.list_resource_templates()
    # templates -> 类型为mcp.types.ResourceTemplate的列表
    
  • read_resource(uri: str | AnyUrl): 读取资源或已解析模板的内容。
    # 读取静态资源
    readme_content = await client.read_resource("file:///path/to/README.md")
    # readme_content -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]
    print(readme_content[0].text) # 假设是文本
    
    # 读取从模板生成的资源
    weather_content = await client.read_resource("data://weather/london")
    print(weather_content[0].text) # 假设是JSON文本
    

提示词操作

  • list_prompts(): 获取可用的提示模板。
  • get_prompt(name: str, arguments: dict[str, Any] | None = None): 获取渲染后的提示消息列表。

原始MCP协议对象

New in version: 2.2.7

FastMCP客户端旨在为MCP协议提供一个"友好"的界面,但有时您可能需要直接访问原始的MCP协议对象。每个返回数据的主要客户端方法都有一个对应的*_mcp方法,该方法直接返回原始的MCP协议对象。

标准客户端方法(不带_mcp后缀)返回对用户友好的MCP数据表示形式,而*_mcp方法始终返回完整的MCP协议对象。随着协议的发展,这些用户友好的表示形式可能会发生变化,并可能造成破坏性影响。如果您需要一致、稳定地访问完整的数据结构,建议优先使用*_mcp方法。

# Standard method - returns just the list of tools
tools = await client.list_tools()
# tools -> list[mcp.types.Tool]

# Raw MCP method - returns the full protocol object
result = await client.list_tools_mcp()
# result -> mcp.types.ListToolsResult
tools = result.tools

可用的原始MCP方法:

  • list_tools_mcp(): 返回 mcp.types.ListToolsResult
  • call_tool_mcp(name, arguments): 返回 mcp.types.CallToolResult
  • list_resources_mcp(): 返回 mcp.types.ListResourcesResult
  • list_resource_templates_mcp(): 返回 mcp.types.ListResourceTemplatesResult
  • read_resource_mcp(uri): 返回 mcp.types.ReadResourceResult
  • list_prompts_mcp(): 返回 mcp.types.ListPromptsResult
  • get_prompt_mcp(name, arguments): 返回 mcp.types.GetPromptResult
  • complete_mcp(ref, argument): 返回 mcp.types.CompleteResult

这些方法在调试或需要访问简化方法未公开的元数据或字段时特别有用。

高级功能

MCP允许服务器与客户端交互以提供额外功能。Client构造函数接受额外配置来处理这些服务器请求。

超时控制

New in version: 2.3.4

您可以在客户端级别和单个请求级别控制请求超时:

from fastmcp import Client
from fastmcp.exceptions import McpError

# Client with a global 5-second timeout for all requests
client = Client(
    my_mcp_server,
    timeout=5.0  # Default timeout in seconds
)

async with client:
    # This uses the global 5-second timeout
    result1 = await client.call_tool("quick_task", {"param": "value"})
    
    # This specifies a 10-second timeout for this specific call
    result2 = await client.call_tool("slow_task", {"param": "value"}, timeout=10.0)
    
    try:
        # This will likely timeout
        result3 = await client.call_tool("medium_task", {"param": "value"}, timeout=0.01)
    except McpError as e:
        # Handle timeout error
        print(f"The task timed out: {e}")

不同传输类型的超时行为有所不同:

  • 使用SSE传输时,每个请求(工具调用)的超时时间始终优先,无论哪个更低。
  • 使用HTTP传输时,两个超时时间(客户端或工具调用)中较短的那个优先。

为了在所有传输方式中保持行为一致,我们建议在需要时在单个工具调用级别显式设置超时,而不是依赖客户端级别的超时。

大语言模型采样

MCP服务器可以向客户端请求LLM补全结果。客户端可以提供sampling_handler来处理这些请求。采样处理器会从服务器接收消息列表和其他参数,并应返回一个字符串形式的补全结果。

以下示例使用marvin库生成补全内容:

import marvin
from fastmcp import Client
from fastmcp.client.sampling import (
    SamplingMessage,
    SamplingParams,
    RequestContext,
)

async def sampling_handler(
    messages: list[SamplingMessage],
    params: SamplingParams,
    context: RequestContext
) -> str:
    return await marvin.say_async(
        message=[m.content.text for m in messages],
        instructions=params.systemPrompt,
    )

client = Client(
    ...,
    sampling_handler=sampling_handler,
)

日志记录

MCP服务器可以向客户端发送日志。客户端可以设置一个日志回调函数来接收这些日志。

from fastmcp import Client
from fastmcp.client.logging import LogHandler, LogMessage

async def my_log_handler(params: LogMessage):
    print(f"[Server Log - {params.level.upper()}] {params.logger or 'default'}: {params.data}")

client_with_logging = Client(
    ...,
    log_handler=my_log_handler,
)

根节点

Roots是客户端向服务器告知其可访问资源或访问权限边界的一种方式。服务器可以利用此信息调整行为或提供更准确的响应。

服务器可以向客户端请求根节点,当客户端的根节点发生变化时,客户端可以通知服务器。

在创建客户端时设置根路径,用户既可以提供一个根路径列表(可以是字符串列表),也可以提供一个返回根路径列表的异步函数。

from fastmcp import Client

client = Client(
    ..., 
    roots=["/path/to/root1", "/path/to/root2"],
)

实用方法

  • ping(): 向服务器发送ping请求以验证连接性。
    async def check_connection():
        async with client:
            await client.ping()
            print("Server is reachable")
    

错误处理

call_tool请求在服务器端发生错误时(例如工具函数抛出异常),client.call_tool()方法将抛出fastmcp.client.ClientError错误。

async def safe_call_tool():
    async with client:
        try:
            # Assume 'divide' tool exists and might raise ZeroDivisionError
            result = await client.call_tool("divide", {"a": 10, "b": 0})
            print(f"Result: {result}")
        except ClientError as e:
            print(f"Tool call failed: {e}")
        except ConnectionError as e:
            print(f"Connection failed: {e}")
        except Exception as e:
            print(f"An unexpected error occurred: {e}")

# Example Output if division by zero occurs:
# Tool call failed: Division by zero is not allowed.

其他错误,如连接失败,将引发标准Python异常(例如ConnectionErrorTimeoutError)。

客户端传输通常有自己的错误处理机制,因此您无法始终捕获类似call_toolasync with块外部引发的错误。相反,您可以使用call_tool_mcp()获取原始的mcp.types.CallToolResult对象,并通过检查其isError属性来自行处理错误。