GuidesMCP
构建可以连接 MCP Server 的 Client
用 Python 做一个能连接 MCP server 的聊天客户端:发现工具、执行调用、把结果回传模型。
基于 Model Context Protocol Docs 整理 · 官方资料 ↗
写完 Server 之后,还缺一环:谁去连接它、发现工具、在模型要求时真正发起 call_tool?这就是 MCP Client。
官方 Python 教程(MCP SDK 2.0.0+)用 uv、mcp、anthropic、python-dotenv 做一个命令行聊天客户端。建议先完成 构建你的第一个 MCP Server,再用同一个 weather server 验证端到端。
Client 在 MCP 里到底负责什么
Host 是用户看到的应用壳;Server 暴露 Tool;Client 夹在中间,按 MCP 协议说话。
在官方示例里,Client 的职责很具体:启动并连接 Server 子进程、list_tools、把工具 schema 交给 Claude、在模型返回 tool_use 时 call_tool、把结果写回对话,再让模型生成最终自然语言回答。
没有可用的 Server 时,Client 教程无法单独验证工具环。准备好 weather.py 或任意 .py / .js Server 脚本再继续。
连接一个 Server
先按官方要求初始化项目。需要 Mac 或 Windows、最新 Python、已安装 uv,以及 Python MCP SDK 2.0.0+;对话侧使用 Anthropic API Key。
官方 macOS / Linux 初始化
uv init mcp-client
cd mcp-client
uv venv
source .venv/bin/activate
uv add mcp anthropic python-dotenv
rm main.py
touch client.pyStdioServerParameters 只是配置,不是连接本身。它描述「用什么命令启动哪个 Server 脚本」。
from mcp import Client, StdioServerParameters
from mcp.client.stdio import stdio_client
def server_params(server_script_path: str) -> StdioServerParameters:
if server_script_path.endswith(".py"):
command = "python"
elif server_script_path.endswith(".js"):
command = "node"
else:
raise ValueError("Server script must be a .py or .js file")
return StdioServerParameters(command=command, args=[server_script_path])建立 Session
stdio_client() 把参数变成 stdio 传输;Client 在进入 async with 时打开传输、拉起 Server,并完成协议版本协商。
官方导入与入口形态
import asyncio
import sys
from mcp import Client, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp_types import TextContent
from anthropic import Anthropic
from dotenv import load_dotenv
load_dotenv()
MODEL = "claude-opus-5"
anthropic = Anthropic()
async def main() -> None:
if len(sys.argv) < 2:
print("Usage: python client.py <path_to_server_script>")
sys.exit(1)
async with Client(stdio_client(server_params(sys.argv[1]))) as client:
tool_list = await client.list_tools()
tool_names = [tool.name for tool in tool_list.tools]
print("\nConnected to server with tools:", tool_names)
# ... chat_loop(client)
if __name__ == "__main__":
asyncio.run(main())官方写明:async with 就是整个连接生命周期。进入时连接,离开时断开并关掉子进程,没有单独的 connect/close 要对。
列出 Server 暴露的 Tools
连接成功后,第一件事通常是 await client.list_tools()。把 name、description、input_schema 转成 Anthropic tools 参数,模型才能决定是否调用。
tool_list = await client.list_tools()
available_tools = [{
"name": tool.name,
"description": tool.description,
"input_schema": tool.input_schema,
} for tool in tool_list.tools]调用一个 Tool
用户提问后,用 MODEL = "claude-opus-5" 调用 anthropic.messages.create,并传入 available_tools。若响应里出现 type 为 tool_use 的内容块,就从中取出 tool_name 与 tool_args,再执行 await client.call_tool(tool_name, tool_args)。
response = anthropic.messages.create(
model=MODEL,
max_tokens=1000,
messages=messages,
tools=available_tools,
)
for content in response.content:
if content.type == "tool_use":
tool_name = content.name
tool_args = content.input
result = await client.call_tool(tool_name, tool_args)读取结果
call_tool 返回 CallToolResult。content 是一组内容块;官方示例只抽取 TextContent 的 .text。工具失败通常不会在这里抛异常,而是 result.is_error 为真——把该标志一并回传,方便模型换策略。
tool_results = [{
"type": "tool_result",
"tool_use_id": content.id,
"content": "\n".join(
block.text
for block in result.content
if isinstance(block, TextContent)
),
"is_error": result.is_error,
}]
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
response = anthropic.messages.create(
model=MODEL,
max_tokens=1000,
messages=messages,
tools=available_tools,
)正确关闭连接
不要手写一对 connect/close。让 async with Client(...) 拥有连接:离开代码块时会断开并关闭 Server 子进程。
聊天循环里输入 quit,或遇到 EOF,应干净退出 main;单次查询失败应打印错误并继续会话,而不是默默结束整个 Client。
对接 weather server 运行
uv run client.py path/to/weather.pyServer / Client / Host 最容易混淆在哪里
Server 只暴露能力,不负责对话 UI,也不直接调用 Claude。Client 负责协议与工具调度。Host 是承载体验的应用;在这篇教程里,你的命令行程序既是 Host,也内嵌了 Client。
Claude for Desktop 是另一个 Host:它内置 Client,去连你写的 Server。你在这篇写的 Client,则是自己充当 Host,去连同一个 Server。两边连的是同一类 Server,角色不要对调理解。
若还没有 Server,先回到 构建你的第一个 MCP Server。
最容易出错的地方
连不上 Server
检查命令行传入的脚本路径(相对或绝对),确认扩展名是 .py 或 .js;Windows 注意斜杠写法。
Anthropic 鉴权失败
在 .env 中设置 ANTHROPIC_API_KEY,并用 python-dotenv 的 load_dotenv() 加载;不要把 Key 提交进仓库。
工具调用参数不对
打印模型返回的 tool input,对照 Server 工具的 docstring 与类型提示;list_tools 里的 schema 才是模型看到的真相。
工具失败却当成异常崩溃
按官方说明检查 result.is_error,把错误内容回传给模型,而不是假设 call_tool 一定会抛异常。
官方资料
Model Context Protocol Docs
Build an MCP client ↗