第二课
很好,从这一课开始,我们统一采用 Markdown 教程 的形式。
以后每一课都会包含:
📚 本课目标
📖 本课知识
💻 动手实践
🧪 运行结果
📝 课后练习
🎯 本课总结
➡️ 下一课预告
尽量做到:
- 代码 80%
- 概念 20%
- 每节课都可以直接复制运行
- 每节课学完都能理解 MCP 底层原理,而不是学 SDK
⸻
MCP 从零实现教程(二)
第二课:自己实现 @tool 装饰器
目标:自己实现 MCP Tool 注册机制。
⸻
📚 本课目标
学完本课后,你应该能够理解:
- 什么是 Tool Registry(工具注册中心)
- Python 装饰器到底做了什么
- 为什么 @mcp.tool() 一点都不神奇
- 如何自己实现一个工具注册器
今天仍然不使用任何 MCP SDK。
官方 MCP SDK 的 @mcp.tool() 底层也是围绕 Python 函数、装饰器和反射来组织工具信息,只是在此基础上增加了参数 Schema、协议通信等能力。
⸻
📖 第一部分:为什么需要 Tool Registry?
上一课,我们写的是:
def add(a, b):
return a + b
def sub(a, b):
return a - b
调用方式:
print(add(1, 2))
如果工具越来越多:
add()
sub()
weather()
search()
translate()
send_email()
...
难道要写:
if tool == "add":
...
elif tool == "sub":
...
elif tool == "weather":
...
当然不行。
所以需要一个统一管理工具的地方。
例如:
TOOLS = {}
以后所有工具都放进去。
⸻
📖 第二部分:创建 Tool Registry
新建一个文件:
registry.py
写入:
TOOLS = {}
目前什么都没有。
打印:
print(TOOLS)
输出:
{}
⸻
📖 第三部分:自己实现 @tool
继续写:
TOOLS = {}
def tool(func):
print("注册工具:", func.name)
TOOLS[func.name] = func
return func
是不是很简单?
这里只有三件事情:
收到函数
↓
保存函数
↓
返回函数
⸻
📖 第四部分:使用装饰器
下面开始注册工具。
@tool
def add(a, b):
return a + b
@tool
def sub(a, b):
return a - b
运行程序。
输出:
注册工具: add
注册工具: sub
再打印:
print(TOOLS)
输出:
{
"add":
"sub":
}
到这里,
工具已经完成自动注册。
⸻
📖 第五部分:装饰器到底是什么?
很多初学者都会觉得:
@tool
特别神秘。
其实不是。
Python 会自动转换成:
def add(a, b):
return a + b
add = tool(add)
也就是说:
@tool
只是下面这句话的语法糖:
tool(add)
所以:
@tool
def add():
...
等价于:
def add():
...
add = tool(add)
以后看到任何 Python 装饰器,都可以先脑补成这种写法。
⸻
💻 第六部分:调用 Tool
现在工具已经注册完成。
增加下面代码:
tool_name = input("请输入工具名称:")
a = int(input("请输入 a:"))
b = int(input("请输入 b:"))
result = TOOLS[tool_name](a, b)
print(result)
运行:
请输入工具名称:
add
请输入 a:
10
请输入 b:
20
输出:
30
如果输入:
sub
输出:
-10
是不是已经开始有 MCP Tool 的感觉了?
⸻
📖 第七部分:Tool 不仅保存函数
真正的 MCP Server 保存的不只是函数。
还会保存:
- Tool 名称
- Tool 描述
- Tool 参数
- Tool Schema
- 返回值 Schema
所以修改一下 Registry。
TOOLS = {}
def tool(func):
TOOLS[func.name] = {
"func": func,
"description": func.doc
}
return func
然后:
@tool
def add(a, b):
"""两个数字相加"""
return a + b
打印:
print(TOOLS)
输出:
{
"add": {
"func":
"description": "两个数字相加"
}
}
是不是已经开始有数据库的感觉了?
⸻
📖 第八部分:Python 如何知道参数?
这是今天最重要的一步。
Python 自带一个库:
import inspect
例如:
import inspect
def add(a: int, b: int):
return a + b
print(inspect.signature(add))
输出:
(a: int, b: int)
继续:
sig = inspect.signature(add)
for name, param in sig.parameters.items():
print(name)
print(param.annotation)
输出:
a
<class 'int'>
b
<class 'int'>
Python 已经知道了:
- 参数名称
- 参数类型
所以:
def add(a: int, b: int):
这些信息不是给编辑器看的。
运行时 Python 也能读取。
官方 MCP SDK 正是利用这一能力,在注册工具时自动读取函数签名、类型注解和文档字符串,再生成工具元数据和输入 Schema。
⸻
💻 第九部分:最终版 Registry
下面是目前最完整的版本。
import inspect
TOOLS = {}
def tool(func):
sig = inspect.signature(func)
params = {}
for name, param in sig.parameters.items():
params[name] = param.annotation
TOOLS[func.name] = {
"func": func,
"description": func.doc,
"parameters": params
}
return func
@tool
def weather(city: str, days: int = 1):
"""查询天气"""
return f"{city} 天气晴"
打印:
print(TOOLS)
输出类似:
{
"weather": {
"func":
"description": "查询天气",
"parameters": {
"city": str,
"days": int
}
}
}
⸻
🧪 本课练习
请自己完成下面三个工具。
@tool
def add(a: int, b: int):
"""两个数字相加"""
pass
@tool
def multiply(a: int, b: int):
"""两个数字相乘"""
pass
@tool
def hello(name: str):
"""打招呼"""
pass
要求:
运行:
print(TOOLS)
输出每个工具:
- 名称
- 描述
- 参数
- 函数对象
⸻
🎯 本课总结
今天我们已经实现了一个最小版 Tool Registry。
目前已经具备:
✅ Tool 注册
✅ Tool 自动发现
✅ Tool 描述
✅ Tool 参数读取
虽然我们还没有写任何 MCP 协议代码,但已经完成了 MCP Server 中非常重要的一部分:工具注册与元数据收集。
⸻
➡️ 第三课预告
下一课我们继续升级。
我们会实现:
让一个 JSON 自动调用 Tool。
例如:
输入:
{
"tool": "add",
"args": {
"a": 10,
"b": 20
}
}
程序自动执行:
add(10, 20)
并返回:
{
"result": 30
}
到第三课结束时,你会发现:一个最小可运行的 Mini MCP Server 已经初具雏形。

浙公网安备 33010602011771号