agent-project-mcp
by X33834
README.md
# agent-project-mcp
跨 agent 项目管理 MCP Server:任何支持 MCP 的 agent(Claude Code、Cursor、Windsurf、Codex、Gemini CLI 等)共用同一套工具维护项目状态,解决对话中断、各 agent 格式各异、文件散落的问题。
除了 MCP,还能直接用 CLI(`apm`)操作,人和 agent 走同一套读写逻辑。
## 数据存放
每个被管理的项目根目录下自动生成:
```
<项目>/.apm/
├── PROJECT.md # 目标、创建时间
├── DECISIONS.md # 决策日志(时间/agent/标题/决定/理由)
├── TASKS.md # 唯一任务清单(todo/doing/done/blocked)
├── handoff/ # 每次会话结束必须写的交接记录
├── .gitignore # 内容为 .lock,避免把残留锁文件提交进 Git
└── .lock # 写操作时的进程锁(自动创建并删除,可安全忽略)
```
纯 Markdown,可直接进 Git 版本管理,无数据库、无外部服务。
`TASKS.md` 里有一行 `<!-- next-id: N -->` 记录任务 id 高水位,`get_briefing` 会自动隐藏它。
## 从旧版升级
旧版(单文件 `server.py`)写出的 `.apm/` 目录可直接继续用,无需迁移:老任务行原样保留,任务 id 从最大 id 往后接,第一次写入时自动补上 `<!-- next-id: -->` 标记。
## 安装
核心(CLI + 可视化界面)**零第三方依赖**,只用标准库:
```bash
pip install -e . # apm init / briefing / task-* / decide / handoff / insights / ui
pip install -e ".[mcp]" # 额外装上 fastmcp,才能用 apm serve 跑 MCP server
pip install -e ".[dev]" # 再加 pytest + ruff
```
`apm serve` 是唯一需要 `fastmcp` 的命令;其余命令在没装 fastmcp 的环境里照常可用。
## MCP 工具
| 工具 | 参数 | 用途 |
|---|---|---|
| `init_project` | `project_root, name, goal, force=false` | 创建 `.apm/` 结构,已存在时不覆盖 |
| `get_briefing` | `project_root, decisions=20` | 会话开场:目标 + 任务 + 最近决策 + 最新交接 |
| `log_decision` | `project_root, title, decision, reason, agent` | 追加一条决策记录 |
| `add_task` | `project_root, title, agent=""` | 新增任务,返回新 id |
| `update_task` | `project_root, task_id, status` | 改状态,仅接受 `todo\|doing\|done\|blocked` |
| `remove_task` | `project_root, task_id` | 删除任务,id 不复用 |
| `write_handoff` | `project_root, agent, done, next_steps, blockers="none"` | 写会话交接记录 |
| `get_insights` | `project_root, now_iso=""` | 检查项目:过期任务、长期 doing、阻塞漂移等,每条带出处 |
## CLI
```bash
apm -C <项目路径> init "my-app" "把登录页做完"
apm -C <项目路径> task-add "接入 SSO" --agent codex
apm -C <项目路径> task-update T001 doing
apm -C <项目路径> decide "选数据库" "sqlite" "部署简单" --agent codex
apm -C <项目路径> briefing
apm -C <项目路径> handoff --agent codex --done "登录页" --next "接 SSO" --blockers none
apm -C <项目路径> task-remove T001
apm -C <项目路径> insights # 检查项目有没有真实问题
apm serve # 以 stdio 方式启动 MCP server
apm ui # 打开浏览器看板
```
## 检测项(真正的检测,不是猜测)
`apm insights` 或 MCP 工具 `get_insights` 会从 `.apm/` 里算出下面这些事实。**每一条都带「出处」**,指向造成它的那一行或那个文件;没有出处的结论不会输出。全部阈值可用参数覆盖。
| code | 级别 | 触发条件 | 默认阈值 |
|---|---|---|---|
| `stale_open_task` | 中/高 | todo/doing 任务最后更新距今过久 | 7 天(超 2 倍升为高) |
| `long_doing` | 中 | 任务挂在 doing 太久 | 3 天 |
| `blocked_too_long` | 中 | blocked 任务创建至今太久 | 5 天 |
| `blocked_task_unreported` | 高 | 有 blocked 任务,但最新交接写 `Blockers: none` | — |
| `blocker_without_task` | 高 | 最新交接说有阻塞,但任务表里没有 blocked | — |
| `stale_handoff` | 中 | 还有未完成任务,但最新交接距今太久 | 3 天 |
| `unowned_task` | 低 | 任务没有任何 agent 归属 | — |
| `handoff_references_unknown_task` | 低 | 交接里引用的任务 id 已不存在 | — |
| `removed_task_churn` | 低 | id 高水位远高于现存任务数(删过很多) | 差 4 个 |
| `empty_state` | 低 | 什么都没有 | — |
设计约束:**不猜意图**。唯一的自由文本检查是把交接里的 `T###` 当字面 id 去比对,匹配不上就报「引用了不存在的任务」,不会去猜它想说什么。也不打分、不预测、不给建议。
```bash
apm insights # 人类可读,有高危项时退出码 1(可直接进 CI)
apm insights --json # 机器可读
apm insights --stale-days 3 # 覆盖阈值
```
MCP 侧:`get_insights(project_root, now_iso="")` 返回同样的 `summary` + `findings`。`now_iso` 可注入分析时间,便于做可复现的检查。
## 看板:默认一眼看懂,展开才看细节
- **顶部一行结论**:严重度配色 + 「高 N · 中 N · 低 N」+ 最需要处理的那条。
- **只把高危项直接摊开**,中低危项收在「逐条展开」里,每条带出处。
- **任务卡片默认折叠**:一行 `T002 · codex · 今天 · 标题`,点击才展开创建/更新时间和状态按钮(拖拽卡片仍然直接改状态)。
- **决策和历史交接按天分组**,每组一个默认折叠的 `<details>`,组内最多渲染 8 条,其余点「展开其余 N 条」——几百条交接也不会卡。
## 可视化看板
```bash
apm ui # 打开浏览器看板(自动挑空闲端口)
apm ui --port 8787 # 指定端口
apm ui --no-browser # 不自动开浏览器
```
看板把 `.apm/` 渲染成:顶部一行检测结论、任务看板(可拖拽改状态)、决策时间线、最新/历史交接、增删改任务与写决策/交接的表单。**所有改动都走同一套 `apm.core` 写接口**,所以和 CLI、MCP 工具共享同一份文件与校验;看板、CLI、其它 agent 可以同时在线。
默认视图刻意做得很短:一行结论 + 折叠的任务卡片 + 按天分组的折叠历史。**想看细节时再点开**,长列表分块加载。
**实时更新**:走 SSE(`/api/events`)。服务端监听 `.apm/` 下文件的 mtime+size 变化,另一个进程里的 agent 或你手敲的 `apm task-add` 一落盘,页面 ~350ms 内自动更新,不用刷新。右上角显示「实时」还是「轮询」——流建立失败会自动退回 5 秒轮询,功能不减。
前端是 vendored 进仓的 preact + htm(约 16KB,**零构建、零 npm 依赖、零远程请求**),时间线是内联 SVG。
安全边界(本地 HTTP 写接口,务必了解):
- 只绑 `127.0.0.1`,不监听任何外部地址。
- 每次启动生成一次性 token,**每个请求都必须带**(URL 上的 `?t=` 或 `X-APM-Token` 头),token 用常量时间比较。
- `Host` 头必须是回环地址,否则 403 —— 这挡住 DNS rebinding。
- 不发任何 `Access-Control-*` 头,网页无法驱动它。
- CSP 用**每响应随机 nonce**,且不含任何远程域名:页面引用的是仓内 vendored 的 preact/htm,时间线是内联 SVG,**断网也完整可用**。
`apm ui` 是前台进程,`Ctrl-C` 停止。
## 接入各 agent
Claude Code / Cursor / Windsurf 的 MCP 配置里加:
```json
{
"mcpServers": {
"agent-project-manager": {
"command": "python",
"args": ["-m", "apm", "serve"],
"cwd": "C:\\path\\to\\agent-project-mcp"
}
}
}
```
仓库根目录的 `server.py` 是等价的兼容入口,老配置指向它仍可用:
```json
"args": ["C:\\path\\to\\agent-project-mcp\\server.py"]
```
注意:`server.py` 只在用**源码 checkout**(或 `pip install -e .`)时存在。用 `pip install` 装 wheel 时该文件不进包,MCP 配置应改用 `"args": ["-m", "apm", "serve"]` 或直接调用 `apm serve`。
本地调试用 inspector:
```bash
fastmcp dev apm/mcp_server.py
```
## 给 agent 的硬性规则(写进各自的 AGENTS.md / 系统提示)
1. 开工前先调 `get_briefing`,不基于对话记忆假设项目状态。
2. 改变方向前先 `log_decision`,再动手。
3. 任务状态变化立即 `add_task` / `update_task` / `remove_task`。
4. 会话结束必须 `write_handoff`(done / next / blockers)。
5. 产物文件一律按既有目录结构存放,不自造新目录。
## 写入安全
- 写操作走 `.apm/.lock` 进程锁,多个 agent 同时写不会互相覆盖;读取(`get_briefing`)也取同一把锁,避免 Windows 上读句柄让并发写失败。
- 先写临时文件再 `os.replace` 原子替换,中途失败保留原文件;`PermissionError`(Windows 共享冲突)自动重试。
- 任务 id 单调递增(`TASKS.md` 里的 `<!-- next-id: N -->` 高水位),删除后不复用。
- 标题里的 `|` 和 `&` 会被转义,不会破坏 Markdown 表格。
- 锁等不到(进程被强杀留下残留锁)时报 `TimeoutError`,CLI 输出 `apm: ...` 并返回退出码 1,不会挂死;删掉 `.apm/.lock` 即可恢复。
## 测试
```bash
pip install -e ".[dev]"
pytest
ruff check apm server.py tests
```
`dev` 依赖里含 `pytest` 与 `ruff`。裸 `pytest` 也行,因为 `pyproject.toml` 的 `pythonpath = ["."]` 让测试不依赖已安装。This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues