第四课

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/listtools/call

posted @ 2026-07-31 17:45  zwx901323  阅读(4)  评论(0)    收藏  举报