ai-coord-mcp
A shared coordination layer for multiple AI coding assistants, exposing a persistent task queue, per-task message threads, and progress tracking via JSON files on disk.
README
ai-coord-mcp
A shared coordination layer between multiple AI coding assistants — not an orchestrator.
This MCP server never launches, manages, or talks to any AI process. It only exposes a task queue, a per-task message thread, and progress reporting, all persisted as human-readable JSON on disk. Claude Code and OpenCode (or any other MCP-capable client) are launched manually, in separate terminals, and both point their MCP config at this server. Claude typically plays coordinator (creates/reviews tasks); OpenCode plays implementer (claims/updates/completes them) — but the server has no opinion about that, it just stores state.
Claude Code (Sonnet) OpenCode (DeepSeek/Qwen/etc.)
│ │
│ create_task, list_tasks, │ claim_task, update_progress,
│ approve_task, request_revision │ complete_task, post_message
▼ ▼
ai-coord-mcp (this server)
│
▼
.ai/coordination/*.json (on disk)
Design
- Transport: stdio. Each client spawns its own copy of this server as a subprocess.
"The same server" means the same storage directory (
COORDINATION_DIR), not one process. Every tool call re-reads the relevant JSON file from disk before mutating it; writes go through temp-file-then-rename (writeJsonAtomic) so a reader never sees a half-written file; a cross-process mutex (withLock,src/storage/lock.ts) built onmkdirguards concurrent writers, with stale-lock reclaim after 10s and an ownership token per holder. - IDs: sequential
task-0001,task-0002, … assigned under a short-lived lock. Validated against/^task-\d{4,}$/before being joined into any filesystem path. - Optimistic concurrency: every task carries a
versioncounter. Passexpected_versionto any mutating tool to get aConflictErrorinstead of a silent overwrite. - Not implemented: binary attachments (the
filesfield is path references only, not stored content), archiving (thearchive/directory exists but nothing writes to it), typedbranch/commitfields (use themetadataconvention below instead), structured Definition-of-Done /verify_command(useexpected_outputinstead), task relationships /parent_id/blocks(usetagsto group instead), MCP Resources /resources/subscribe(usewait_for_taskinstead).
Status model:
pending --claim_task--> claimed --update_progress--> in_progress
^ | |
| | both claimed and in_progress can complete_task--> completed
| | |
| └──────────────────────cancel_task─────────────────┐ approve_task│request_revision
| | | |
└───────────────reopen_task (from cancelled/completed/approved)◄────────────┘ ▼ ▼
approved revision_requested
(loops back through
update_progress →
in_progress again)
update_progress auto-advances claimed or revision_requested to in_progress on its
first call in that cycle — there's no separate "start_task" tool.
Storage layout
.ai/coordination/
tasks/task-0001.json one file per task (includes its own audit history[])
messages/task-0001.json array of {id, taskId, author, timestamp, content}
progress/task-0001.json latest snapshot + full history of updates
archive/ reserved, unused
.locks/ transient lock directories, safe to delete if empty
Everything is plain JSON — cat .ai/coordination/tasks/task-0001.json works fine while
the server is running.
Setup
cd ai-coord-mcp
pnpm install
pnpm run build
Both Claude Code and OpenCode need to point at the same COORDINATION_DIR (an absolute
path) so they share state regardless of which directory each was launched from.
Claude Code — add to .mcp.json (project) or via claude mcp add:
{
"mcpServers": {
"ai-coord": {
"command": "node",
"args": ["/absolute/path/to/ai-coord-mcp/dist/index.js"],
"env": {
"COORDINATION_DIR": "/absolute/path/to/shared/.ai/coordination"
}
}
}
}
OpenCode — add the equivalent entry to its MCP config (opencode.json /
~/.config/opencode/config.json, depending on your OpenCode version):
{
"mcp": {
"ai-coord": {
"type": "local",
"command": ["node", "/absolute/path/to/ai-coord-mcp/dist/index.js"],
"environment": {
"COORDINATION_DIR": "/absolute/path/to/shared/.ai/coordination"
}
}
}
}
If COORDINATION_DIR is unset, the server defaults to <cwd>/.ai/coordination — only
safe if you know both clients will be launched from the same working directory.
Getting started
- Build the server and wire up
COORDINATION_DIRfor both clients (Setup above). - Launch Claude Code and OpenCode in separate terminals, both in the shared repo.
- In Claude Code, create a task:
Create a task: title "Sort inventory list", description "Implement stable sort by SKU for the inventory panel, keep the existing filter hooks working", expected_output "inventory.ts updated, existing tests still pass, new test for empty list". → calls
create_task, returnstask-0001(statuspending). - In OpenCode:
Check ai-coord for any pending tasks. → calls
claim_next_task, works the task, callsupdate_progressa few times, thencomplete_task. - Back in Claude Code:
Review task-0001 and approve or request revision. → reviews the diff, calls
approve_taskorrequest_revision(the latter posts feedback to the thread; OpenCode reads it viaget_messagesand repeats fromupdate_progress).
Tools
| Tool | Purpose |
|---|---|
create_task |
Create a task (status pending) |
list_tasks |
List task summaries (no history[], includes computed idleSeconds), filter by status / assignee / creator / tag |
get_task |
Fetch one task by id, full record including history[] |
search_tasks |
Substring search over title/description/tags, same summary shape as list_tasks |
claim_task |
pending → claimed |
claim_next_task |
Atomically claim the first eligible pending task (optionally by tag) instead of a list_tasks + claim_task round trip; returns null if none available |
wait_for_task |
Block (polling, lock-free) until a task's status changes or timeout_seconds elapses, instead of looping get_task yourself |
complete_task |
claimed/in_progress/revision_requested → completed |
approve_task |
completed → approved |
request_revision |
completed → revision_requested (also posts the feedback as a message) |
cancel_task |
any open status → cancelled |
reopen_task |
cancelled/completed/approved → pending, clears assignee |
post_message |
Append to a task's conversation thread |
get_messages |
Read a task's thread, optionally only messages since an ISO timestamp |
update_progress |
Report percent (0-100) / current_step / notes; auto-advances status to in_progress |
get_progress |
Latest progress snapshot + history |
All mutating tools accept an optional expected_version — pass the task's current
version to get a ConflictError instead of clobbering a concurrent change.
Every status-changing tool above (claim_task through reopen_task) returns the same
summary shape as list_tasks — no history[] — so an implementer with a small context
window (a local model, say) isn't handed the whole growing transition/note log back on
every call. Same for update_progress: it returns the current snapshot, not the growing
history[]. get_task/get_progress remain the only tools that return full history.
Workflow conventions
- Waiting for work: use
wait_for_taskover pollingget_task/list_tasks— one call instead of N, blocks server-side without holding a lock, returnstimed_out: trueas a normal result. - Picking up work: use
claim_next_taskoverlist_tasks({status:"pending"})+claim_task— same two steps, atomic, retries automatically if another claimer wins the race. - Git state: no typed
branch/commitfield. Convention:create_task'smetadatacarries{ baseCommit, branch }; both agents read/write those same keys.expected_outputplus this README is the Definition-of-Done contract. - Implementer loop:
claim_next_task(orclaim_taskon a specific id) → work → a fewupdate_progresscalls →complete_task. If revision is requested,get_messagesfor the feedback, then repeat fromupdate_progress. - Coordinator loop:
create_task→wait_for_task(or check back later) → oncompleted, review the diff atworking_directory→approve_taskorrequest_revision.
Tests
pnpm test
Runs node:test (via tsx) over every src/**/*.test.ts file: the storage layer
(atomic writes, cross-process lock semantics including the stale-lock/ownership-token
edge case, id generation, message/progress persistence) and the full tool surface
(state-machine transitions, TASK_ID schema rejecting malformed/traversal ids, version
conflicts) driven through the real registerXxx() functions against a fresh temp
COORDINATION_DIR per test file.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。