第四次作业
第四次作业:基于 MCP 的待办智能体(Python)
| 这个作业属于哪个课程 | Class56-Grade2024-CS |
|---|---|
| 这个作业要求在哪里 | 作业要求 |
| 这个作业的目标 | 实现一个基于 MCP 的待办智能体:大模型负责理解意图、选择工具,程序负责真正执行增/查/删 |
| 学号 | 梁峻豪3124004063,王劲松3123004583 |
项目:课代表 Agent(Todo MCP Agent)
技术栈:Python 3.12、MCP(stdio / JSON-RPC 2.0)、OpenAI 兼容大模型(DeepSeek)
仓库:https://github.com/Liangjunhao528/Software-engineering-2026.git
一、需求描述
本项目是一个待办事项助手。用户用自然语言下指令,智能体识别意图后调用 MCP 工具完成操作,不需要背命令格式。
- 添加待办:用户描述任务,智能体新增一条待办,并补全截止时间、重要程度、所属课程
- 查看待办:列出当前全部待办及序号、重要程度、课程、截止时间
- 删除待办:按序号删除指定待办
约束:
- 使用 MCP 协议:大模型只负责理解和选择工具,工具执行逻辑放在 MCP 服务端;
- 命令行交互,模型走 OpenAI 兼容接口;
- 运行只用 Python 标准库,不装第三方包。
二、业务流描述
一次完整交互:
- 用户在终端输入自然语言,例如"帮我记一下,写软件工程实验报告";
- 客户端把用户输入和工具清单一起发给大模型;
- 模型返回要调用的工具名和参数,例如
add_todo({"text": "写软件工程实验报告", "due": "2026-10-30", "priority": "high", "course": "软件工程"}); - 客户端通过 MCP(stdio)把调用请求发给
todo_agent.py --server子进程; - 服务端执行工具并返回结果,结果回填给模型;
- 模型生成自然语言回复,程序打印
[回答],等待下一条输入。
流程图一句话:用户输入 → 大模型选工具 → MCP 调用 → 工具执行 → 结果回填 → 最终回答。
三、实现说明
开发环境
- Python 3.12,Windows 11
- 大模型:DeepSeek(
deepseek-chat,OpenAI 兼容接口) - 协议:MCP 2024-11-05,stdio 传输
- 无第三方依赖,
pytest只用于测试
模块划分
整个项目只有一个文件 todo_agent.py,靠启动参数区分两种身份:
| 运行方式 | 身份 | 职责 |
|---|---|---|
python todo_agent.py |
客户端 | 命令行、大模型调用、ReAct 循环、MCP 客户端 |
python todo_agent.py --server |
MCP 服务端 | stdio 上收发 JSON-RPC,执行 3 个工具 |
客户端启动时会用 subprocess 拉起自己的 --server 模式,所以虽然只有一个源文件,运行时仍是两个进程、走真正的 MCP 协议:
def main() -> int:
if "--server" in sys.argv[1:]:
server_main() # 服务端:只读写 stdio,不打印
return 0
return client_main() # 客户端:对话 + 调模型
tests/test_todo_agent.py 里有 6 个测试。
工具定义
每个工具就是一个普通函数,再用 description 和 inputSchema 告诉模型怎么用。add_todo 有四个字段,description 里写清"下周三要换算成日期":
{
"name": "add_todo",
"description": "新增一条待办:due 是截止日期 YYYY-MM-DD(把「下周三」换算好),"
"priority 是重要程度 high/medium/low,course 是课程名。",
"inputSchema": {
"type": "object",
"properties": {
"text": {"type": "string", "description": "待办内容"},
"due": {"type": "string", "description": "截止日期 YYYY-MM-DD,可空"},
"priority": {"type": "string", "enum": ["high", "medium", "low"]},
"course": {"type": "string", "description": "课程名,可空"},
},
"required": ["text"],
},
}
MCP 的 inputSchema 和 OpenAI function calling 的 parameters 是同一套 JSON Schema,客户端原样传给模型即可,不用写两遍。
ReAct 循环
for _ in range(6):
message = model.chat(messages, tools)
calls = message.get("tool_calls") or []
if not calls:
return message["content"]
messages.append({"role": "assistant", "tool_calls": calls})
for call in calls:
name = call["function"]["name"]
args = json.loads(call["function"]["arguments"])
ok, result = mcp.call(name, args)
messages.append({"role": "tool", "tool_call_id": call["id"], "content": result})
工具出错时返回 isError=true 和一句人话,模型可以据此改正参数再试。
四、使用示例(功能演示)
安装与启动
copy .env.example .env # 编辑 .env,填入 LLM_API_KEY
python todo_agent.py
没有填 Key 时,程序会直接提示:
未找到 LLM_API_KEY,程序无法调用大模型。
请复制 .env.example 为 .env,并填入你的 API Key:
LLM_API_KEY=sk-xxxxxxxx
(DeepSeek / 通义千问 / 智谱 GLM 等 OpenAI 兼容接口都可以)
示例一:添加待办(自动补全信息)
输入:周五交软件工程实验报告,挺急的
你 > 周五交软件工程实验报告,挺急的
[工具] add_todo({"text": "写软件工程实验报告", "due": "2026-10-30", "priority": "high", "course": "软件工程"})
[结果] 已添加 #1:写软件工程实验报告(软件工程,高优先级,截止 2026-10-30(周五,还剩 5 天))。
[回答] 已记下:写软件工程实验报告,截止 2026-10-30(本周五,还剩 5 天),重要程度高,课程软件工程。
模型没填的字段,服务端会兜底:只传 text 时,它也会从原话里解析"明天""下周三""三天后",以及"急/重要/不急"这类说法。
示例二:查看待办
输入:看看我还有哪些待办
你 > 看看我还有哪些待办
[工具] list_todos({})
[结果] 当前待办:
#1 [高] 写软件工程实验报告 · 软件工程 · 截止 2026-10-30(周五,还剩 5 天)
[回答] 你目前只有 1 条待办:写软件工程实验报告,周五截止,高优先级。
示例三:删除待办
输入:把第一条删掉
你 > 把第一条删掉
[工具] delete_todo({"index": 1})
[结果] 已删除 #1:写软件工程实验报告
[回答] 已删除第 1 条待办,现在没有待办了。
说明:以上是 DeepSeek 的真实运行记录(输入行为便于阅读补回),
[回答]是模型原文的精简版,措辞可能略有不同。
五、测试结果
$ python -m pytest tests -q
6 passed
| 测试 | 内容 |
|---|---|
test_add_with_details |
内容、截止时间、重要程度、课程四个字段正确入库 |
test_auto_derive_fields |
没给日期/重要程度时,从"下周三""挺急的""不急"里自动推导 |
test_invalid_input |
空内容、非法优先级、不存在的日期和序号 |
test_list_and_delete |
列表展示详细信息、按序号删除 |
test_protocol |
MCP 握手、tools/list、未知方法、isError |
test_agent_loop_roundtrip |
真起子进程,跑通 MCP 通信 + Agent 循环 |
另外用 DeepSeek 实机跑通了添加、查看、删除三个场景(见第四节)。
六、GitHub 仓库
https://github.com/Liangjunhao528/Software-engineering-2026.git
七、小组分工
| 成员 | 学号 | 负责内容 |
|---|---|---|
| 梁峻豪 | 3124004063 | 需求梳理;MCP 服务端(handle / server_main、JSON-RPC 收发);三个工具与字段校验;日期、重要程度、课程的自动推导;测试 test_add_with_details、test_auto_derive_fields、test_invalid_input、test_list_and_delete |
| 王劲松 | 3123004583 | 客户端(MCPClient、LLMClient、ReAct 循环、命令行);系统提示词与工具描述;--server 子进程通信联调;测试 test_protocol、test_agent_loop_roundtrip;README 与演示 |
协作方式:两人共用一个仓库,改完各自跑一遍 python -m pytest tests -q 再合并;接口以 TOOLS 里的 inputSchema 为准,客户端不直接改工具层。
八、项目总结
完成了一个最小可用的 MCP 待办智能体:模型只负责理解意图和选工具,真正的增删查在 MCP 服务端执行。记录时会补全截止时间、重要程度和课程,列表里能直接看到"还剩几天"。整个项目只有一个 todo_agent.py(约 300 行),同一文件既是客户端也是 MCP 服务端。
不足:待办存在内存里,程序退出就清空;可以改成写 JSON 文件或 SQLite。工具也只有三个,后续可以加"标记完成""按关键词搜索"。
九、心得体会
梁峻豪
第一次按 MCP 协议自己写服务端,发现它没有想象中复杂:stdio 上就是一行一条 JSON-RPC,实现 initialize、tools/list、tools/call 三个方法就能和客户端配合。真正花时间的是字段推导——"下周三""挺急的"要变成日期和优先级,还得处理"不急"这种反例,以及模型漏填时的兜底。
王劲松
我主要负责客户端。体会是"不要相信任何一层":模型换算的日期可能有偏差,服务端就再校验一遍 YYYY-MM-DD;模型漏填字段时,服务端会从原话里补。工具描述同样重要:list_todos 写明"删除前先确认序号",模型才会先查再删。两个人联调时把"客户端只认 inputSchema"这条定下来,改起来省了很多沟通。

浙公网安备 33010602011771号