第四次作业

第四次作业:基于 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 工具完成操作,不需要背命令格式。

  1. 添加待办:用户描述任务,智能体新增一条待办,并补全截止时间、重要程度、所属课程
  2. 查看待办:列出当前全部待办及序号、重要程度、课程、截止时间
  3. 删除待办:按序号删除指定待办

约束:

  • 使用 MCP 协议:大模型只负责理解和选择工具,工具执行逻辑放在 MCP 服务端;
  • 命令行交互,模型走 OpenAI 兼容接口;
  • 运行只用 Python 标准库,不装第三方包。

二、业务流描述

一次完整交互:

  1. 用户在终端输入自然语言,例如"帮我记一下,写软件工程实验报告";
  2. 客户端把用户输入和工具清单一起发给大模型;
  3. 模型返回要调用的工具名和参数,例如 add_todo({"text": "写软件工程实验报告", "due": "2026-10-30", "priority": "high", "course": "软件工程"});
  4. 客户端通过 MCP(stdio)把调用请求发给 todo_agent.py --server 子进程;
  5. 服务端执行工具并返回结果,结果回填给模型;
  6. 模型生成自然语言回复,程序打印 [回答],等待下一条输入。

流程图一句话:用户输入 → 大模型选工具 → 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"这条定下来,改起来省了很多沟通。

参考

posted @ 2026-10-25 23:57  youmu514  阅读(7)  评论(0)    收藏  举报