从零开始学习MCP 第3部分:将客户端连接到服务器
构建一个MCP标准输入输出客户端,用于启动hr_server.py、调用search_employee函数并返回结果——无需手动启动服务器。
上一部分留下了一个仅承担单一职责的MCP服务器:提供人力资源搜索功能。
HR MCP Server
↓
search_employee
整个流程始于:
python hr_server.py
还有一个问题尚未解决:究竟是谁与服务器进行交互?这个角色由客户端承担。
Client
↓
MCP
↓
HR MCP Server
简单理解这些概念即可:服务器提供功能,客户端则连接并使用这些功能。
让我们创建客户端
在hr_server.py旁边,再添加一个模块:
hr_client.py
项目的结构变为:
mcp-hr
│
├── hr_server.py
│
└── hr_client.py
既然服务器部分已经编写完成,现在重点就转向客户端。
连接到我们的服务器
将以下内容放入hr_client.py中:
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
server = StdioServerParameters(
command="python",
args=["hr_server.py"]
)
async with stdio_client(server) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
asyncio.run(main())
乍看之下这段代码似乎很复杂,但关键部分在于:
server = StdioServerParameters(
command="python",
args=["hr_server.py"]
)
这些参数用于指示客户端启动并连接到hr_server.py。客户端会启动服务器进程,并与其建立标准输入输出连接。
现在让我们调用我们的工具
第二部分定义了一个名为:
search_employee
客户端可以这样搜索John:
await session.initialize()
result = await session.call_tool(
"search_employee",
{"name": "John"}
)
print(result.content[0].text)
用通俗的话来说,客户端会请求该连接运行名为John的search_employee函数。完整的客户端脚本如下:
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
server = StdioServerParameters(
command="python3",
args=["hr_server.py"]
)
async with stdio_client(server) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(
"search_employee",
{"name": "John"}
)
print(result.content[0].text)
asyncio.run(main())
该文件就是演示中客户端的全部代码。
让我们运行它
在终端中执行:
python hr_client.py
一个有用的细节是:hr_server.py无需手动单独启动。客户端会通过以下方式来启动它:
command="python",
args=["hr_server.py"]
然后连接到正在运行的进程。
会发生什么?
客户端会发送一个类似如下的工具请求:
Tool:
search_employee
Name:
John
服务器接收到该请求后执行以下操作:
search_employee("John")
它进行解析:
John → Finance
然后将结果返回给调用方。整个流程的路径如下:
hr_client.py
│
│ search_employee("John")
↓
hr_server.py
│
↓
Search employee list
│
↓
John → Finance
│
↓
hr_client.py
协议的双方现在都完成了真正的往返通信。
回到我们的USB示例
将键盘连接到计算机是一个很有用的类比。
Keyboard
↓
USB
↓
Computer
两端都遵循相同的电缆通信协议。在这里,客户端使用MCP,服务器也使用MCP。正是这份共同的协议规范使得它们能够协同工作。
但它真的选择了合适的工具吗?
有一个容易被忽视的局限性。再来看看调用场景:
session.call_tool(
"search_employee",
{"name": "John"}
)
谁选择了search_employee?是应用程序的开发者,他通过硬编码工具名称来决定使用哪个功能。客户端从未读取过诸如“约翰在财务部门工作吗?”这样的自然语言问题来决定调用哪个功能。接下来缺失的环节就是自动工具选择功能。
下一步是什么?
假设服务器上增加了多个工具:
search_employee
create_employee
get_leave_balance
list_departments
当用户询问约翰属于哪个部门时,必须有机制来判断应该使用哪个功能:
search_employee
而不是其他类似的功能。这一判断取决于工具名称、描述以及展示的目录信息。接下来的内容将介绍当存在多个MCP工具时,应用程序如何进行选择。
在此之前,这一步骤带来的重要启示在于其机制:stdio客户端即可启动服务器、初始化会话、带参数调用指定工具,并输出结构化回复——无需手动启动独立的服务器进程。