Skip to main content
Glama

MCP 任务助手

这是一个教学项目,以单个 Docker Compose 服务运行,将用户消息发送给 Groq 上的 LLM,让 LLM 使用五个 MCP 工具(list_tasks、list_tasks_by_priority、create_task、update_task、delete_task)来管理一个内存中的任务列表。每个任务都有一个 priority(优先级:低/中/高)字段。

它做什么?

你向 POST /chat 端点发送一条自然语言消息(例如"将 Docker 任务标记为已完成")。聊天服务器将该消息连同其拥有的 5 个 MCP 工具的 schema 一起发送给 Groq。模型可以连续调用工具(例如先调用 list_tasks 来查找 id);每次调用都会根据 JSON Schema 进行验证,通过真实的 MCP 服务器执行,并将结果再次展示给模型。最后,自然语言回复和整个过程的可见追踪记录(trace)会一起返回。

Related MCP server: MCP Project Manager

架构

两个独立的 Node.js 进程,在同一个容器内,通过 stdio 上的 JSON-RPC 进行通信:

[app sureci]                          [mcp-server sureci]
Express (/chat)                       (child process, stdio ile baslatiliyor)
  |- groq/           --HTTP-->  Groq API
  `- mcp-client/     --stdio/JSON-RPC-->  mcp-server/  -->  task-store/

文件

职责

src/task-store

任务 CRUD,基于 Map 的内存存储,种子数据

src/mcp-server

将 task-store 转换为带 JSON Schema 的 5 个 MCP 工具,监听 stdio+JSON-RPC

src/mcp-client

以子进程方式启动 mcp-server,保持单一(单例)连接

src/groq

向 Groq 发送请求,将 MCP schema 转换为 Groq 工具格式

src/app

/chat 端点、工具调用循环、ajv 验证、追踪记录生成

安装与运行

1) 获取 Groq API 密钥

  1. 访问 https://console.groq.com/keys 并登录。

  2. 点击"Create API Key"创建新密钥,名称可随意填写(例如 mcp-gorev-asistani)。

  3. 复制显示的密钥(gsk_...)——它不会再次显示。

2) 创建 .env 文件

cp .env.example .env

打开 .env 文件,将你的密钥粘贴到 GROQ_API_KEY= 行的末尾。

注意:GROQ_MODEL 的值可能会随时间变化——Groq 会不时移除和添加模型。要查看最新列表: curl -s https://api.groq.com/openai/v1/models -H "Authorization: Bearer $GROQ_API_KEY"

3) 使用 Docker Compose 运行

docker compose up --build -d

查看日志:

docker compose logs -f

看到 Chat sunucusu http://localhost:3000 adresinde calisiyor. 这一行就表示就绪了(容器内端口为 3000,通过 compose.yaml 对外映射为 3001——如果你本机的 3000 端口被占用,可以修改 compose.yaml 中的 ports 行)。

停止运行:

docker compose down

测试消息

curl -X POST http://localhost:3001/chat -H "Content-Type: application/json" \
  -d '{"message": "Hangi görevlerim var?"}'

curl -X POST http://localhost:3001/chat -H "Content-Type: application/json" \
  -d '{"message": "JSON Schema öğrenmek için bir görev ekle."}'

curl -X POST http://localhost:3001/chat -H "Content-Type: application/json" \
  -d '{"message": "Docker görevini tamamlandı olarak işaretle."}'

curl -X POST http://localhost:3001/chat -H "Content-Type: application/json" \
  -d '{"message": "Tamamlanan görevi sil."}'

示例回复(第 3 条消息——注意 id 是先通过 list_tasks 找到,然后传给 update_task 的):

{
  "answer": "\"Docker Compose kur\" görevi tamamlandı olarak işaretlendi.",
  "trace": [
    { "tool": "list_tasks", "arguments": {}, "validation": "passed",
      "result": { "tasks": [ { "id": 1, "title": "MCP sartnamesini oku", "completed": false },
        { "id": 2, "title": "Docker Compose kur", "completed": false },
        { "id": 3, "title": "Groq API anahtarini al", "completed": true } ] } },
    { "tool": "update_task", "arguments": { "completed": true, "id": 2 }, "validation": "passed",
      "result": { "id": 2, "title": "Docker Compose kur", "completed": true } }
  ]
}

第 4 条消息("删除已完成的任务。")在测试过程中表现出一个有趣的行为:由于种子数据中已经有一个已完成的任务(id=3),并且在第 5 条消息之后又产生了一个已完成的任务(id=2),模型在两个选项之间犹豫,没有猜测而是询问用户指的是哪一个——没有调用任何工具。这是项目预期/期望的行为(不删除错误的任务),而不是错误。

常见问题

为什么添加一个新工具(例如 list_tasks_by_priority)只需要修改 2 个文件? 因为 appmcp-clientgroq 层完全没有硬编码工具——app 在每次请求时通过 listMcpTools() 向 mcp-server 询问"你有哪些工具",并将返回的列表原样传递给 Groq。也就是说,要定义一个新工具,只需 (1) 在 task-store 中添加逻辑,(2) 在 mcp-server 中添加 schema——其余一切都会自动流转。这是第 1 步中"分离职责"决策的具体体现。

为什么没有数据库,而是使用内存数据? 规格说明有意如此要求:该项目旨在教授 MCP 协议和工具调用流程,持久化存储是另一个话题,会带来不必要的复杂性。Map + 种子数据免费提供了"每次启动都从干净状态开始"的行为。

为什么用 Docker Compose,单个 node 命令不够吗? Docker 消除了"在我机器上能跑"的问题,保证项目在任何机器上都能以相同方式运行。Compose 则让服务(即使这里只有一个服务)能够以标准、单命令的方式启动——更接近真实世界的部署练习。

为什么需要 JSON Schema 验证,信任 Groq 不够吗? LLM 的输出不是确定性的——模型有时会生成缺失或类型错误的参数。如果不通过 ajv 验证就直接进入 task-store,可能会导致意外错误或数据不一致。验证是"不要信任,要验证"原则的代码体现。

为什么工具定义放在 tools 字段而不是系统消息中? tools 字段是 Groq/OpenAI API 中的结构化契约——模型将其视为真实、可调用的函数,并以结构化的 tool_calls 格式生成响应。如果我们以纯文本形式写在系统消息中,模型只会将其视为上下文,不会有调用保证或结构。

为什么 mcp-client 不在每次请求时重启 mcp-server? task-store 存在于 mcp-server 进程的 RAM 中。如果每次请求都启动新进程,数据每次都会重置为种子状态——之前消息中做的修改就会丢失。因此,只要 app 进程存活,mcp-client 就保持单一的 mcp-server 连接(单例)。

为什么一次 Groq 调用不够,需要循环? 当用户说"标记 Docker 任务"时,模型不知道它的 id——它需要先调用 list_tasks 找到正确的 id,然后用该 id 调用执行实际操作的工具。这意味着在单个请求中需要多次连续的工具调用;固定的"提问-执行-回答"流程无法支持这一点,需要真正的循环。

已知缺陷 / 尚未达到生产就绪的方面

  • 无持久化:如果容器重启(或崩溃/重新部署),所有任务数据都会丢失。实际使用中需要数据库(Postgres、SQLite 等)。

  • 无多用户/会话隔离:所有用户共享同一个 task-store;没有用户间隔离(多租户)。

  • 无对话记忆:每次 /chat 请求都是独立开始的。用户无法引用之前的消息(如"把那个也删了")——只有同一请求内的工具循环期间才保持上下文。

  • 单次并发工具调用:即使模型在同一轮中请求多个工具(并行 tool_calls),也只处理第一个。

  • 无身份验证/授权/chat 端点对所有人开放,没有任何访问控制。

  • 无输入大小/速率限制:恶意或有缺陷的客户端可以无限发送请求,Groq 账单会相应膨胀。

  • Ajv schema 在每次请求时重新编译ajv.compile(...) 本可以缓存以提升性能(在小规模下差别不大)。

  • 模型名称可能随时间过时:Groq 的模型目录会变化(在项目期间 llama-3.3-70b-versatile 已被移除)——应定期检查 GROQ_MODEL

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    A simple, powerful Todo list manager for Claude Desktop and other MCP-compatible AI assistants. Organize your tasks across different projects with priorities and never lose track of what needs to be done!
    15 npm
    2
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    A task manager MCP server that demonstrates all three MCP primitives (tools, resources, prompts). Enables users to manage tasks, read task summaries and details, and run structured planning/review prompts through natural language.
    -