第四课
MCP 从零实现教程(四)
第四课:实现 Tool Discovery(工具发现)
目标:实现 tools/list,让客户端能够自动发现服务器有哪些 Tool。
📚 本课目标
学完本课后,你将理解:
- 为什么 MCP 不需要客户端提前知道有哪些 Tool
- 什么是 Tool Discovery(工具发现)
- 为什么 Cursor、Claude Desktop 连接 MCP 后能自动显示工具
- 自己实现一个最小版
tools/list
这是我们第一次真正模拟 MCP 协议中的一个标准接口。
根据 MCP 官方规范,客户端通过发送 tools/list 请求来发现服务器支持的所有工具及其元数据。
📖 第一部分:为什么需要 Tool Discovery?
假设你写了一个天气 MCP Server。
里面有:
weather()
forecast()
air_quality()
客户端(例如 Claude Desktop)第一次连接你的 Server。
它怎么知道:
- 有哪些 Tool?
- Tool 叫什么?
- 每个 Tool 干什么?
- 每个 Tool 需要哪些参数?
总不能:
TOOLS = {
...
}
直接读取服务器内存吧?
当然不行。
所以:
客户端必须问服务器:
“你有哪些 Tool?”
📖 第二部分:第一次模拟 MCP 请求
客户端发送:
{
"method": "tools/list"
}
服务器收到以后:
开始遍历:
TOOLS
然后返回:
{
"tools": [
...
]
}
是不是很像 REST API?
其实就是。
💻 第三部分:准备 Registry
假设上一课我们已经有:
TOOLS = {
"add": {
"func": add,
"description": "两个数字相加",
"parameters": {
"a": int,
"b": int
}
},
"weather": {
"func": weather,
"description": "查询天气",
"parameters": {
"city": str
}
}
}
今天我们不调用 Tool。
我们只返回 Tool 信息。
💻 第四部分:实现
list_tools()
先写:
def list_tools():
tools = []
for name, info in TOOLS.items():
tools.append({
"name": name,
"description": info["description"],
"parameters": info["parameters"]
})
return tools
是不是很简单?
其实就是:
遍历字典
↓
取出信息
↓
组成列表
💻 第五部分:测试
打印:
print(list_tools())
输出:
[
{
"name": "add",
"description": "两个数字相加",
"parameters": {
"a": int,
"b": int
}
},
{
"name": "weather",
"description": "查询天气",
"parameters": {
"city": str
}
}
]
已经完成 Tool Discovery。
📖 第六部分:真正的 MCP 返回什么?
真正的 MCP 返回的是 JSON-RPC。
例如:
客户端发送:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
服务器返回:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "weather",
"description": "查询天气"
}
]
}
}
是不是和我们写的非常像?
区别只有:
多了:
jsonrpc
id
result
真正的 MCP 就是在我们代码外面包了一层 JSON-RPC 消息格式。
💻 第七部分:增加 Response
修改:
def list_tools():
tools = []
for name, info in TOOLS.items():
tools.append({
"name": name,
"description": info["description"],
"parameters": info["parameters"]
})
return {
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": tools
}
}
打印:
print(list_tools())
输出:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
...
]
}
}
已经开始越来越像真正 MCP。
💻 第八部分:统一 Dispatcher
上一课我们 Dispatcher 只能调用 Tool:
dispatch(request)
现在升级。
def dispatch(request):
method = request["method"]
if method == "tools/list":
return list_tools()
return {
"error": "未知方法"
}
测试:
request = {
"method": "tools/list"
}
print(dispatch(request))
输出:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
...
]
}
}
是不是已经像服务器了?
💻 第九部分:完整代码
TOOLS = {
"add": {
"description": "两个数字相加",
"parameters": {
"a": int,
"b": int
}
},
"weather": {
"description": "查询天气",
"parameters": {
"city": str
}
}
}
def list_tools():
result = []
for name, info in TOOLS.items():
result.append({
"name": name,
"description": info["description"],
"parameters": info["parameters"]
})
return {
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": result
}
}
def dispatch(request):
if request["method"] == "tools/list":
return list_tools()
return {
"error": "Unknown Method"
}
request = {
"method": "tools/list"
}
print(dispatch(request))
📖 第十部分:和真实 MCP 对比
我们的实现:
Client
↓
tools/list
↓
Server
↓
遍历 TOOLS
↓
返回列表
真实 MCP:
Claude Desktop
↓
tools/list
↓
MCP Server
↓
遍历 Registry
↓
生成 Tool Schema
↓
JSON-RPC 返回
除了:
- Schema 更完整
- JSON-RPC 更规范
- 支持分页(Pagination)
- 支持工具列表变化通知(
listChanged)
核心思想几乎一致。
🧪 本课练习
新增两个 Tool。
@tool
def hello(name: str):
"""打招呼"""
return f"你好,{name}"
@tool
def multiply(a: int, b: int):
"""乘法"""
return a * b
要求:
执行:
request = {
"method": "tools/list"
}
print(dispatch(request))
输出中必须包含:
- Tool 名称
- Tool 描述
- Tool 参数
🎯 本课总结
今天我们第一次实现了 MCP 标准协议中的一个真实接口。
目前已经拥有:
✅ Tool Registry(工具注册)
✅ Tool Metadata(工具元数据)
✅ Dispatcher(请求调度)
✅ Tool Discovery(工具发现)
现在,我们已经拥有了一个真正具有 MCP 雏形的 Server。
➡️ 第五课预告
下一课,我们将实现 真正的 tools/call。
客户端发送:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "add",
"arguments": {
"a": 10,
"b": 20
}
}
}
服务器自动:
解析 JSON-RPC
↓
找到 Tool
↓
执行 Python 函数
↓
返回标准 JSON-RPC 响应
到第五课结束,我们就不再是“模拟 MCP”,而是已经实现了 MCP 最核心的两个标准接口:tools/list 和 tools/call。

浙公网安备 33010602011771号