@annealessio647-toby/ai-agent-collab-mcp-lite
v0.10.3
Published
MCP Lite v0.4: manual-trigger MCP server (12 tools: 7 codex-side + 5 claude-side wrapping agent-collab.ps1 + Git + .agent/) plus collab-watch — a watcher daemon that orchestrates the Claude→Codex→Claude collaboration loop via opt-in auto-execute mode. Req
Maintainers
Readme
ai-agent-collab-mcp-lite
MCP Lite v0.4: a tiny MCP stdio server that wraps the existing agent-collab.ps1 + Git + .agent/ collaboration flow so both Claude (execution) and Codex (review) sides are end-to-end through MCP without PowerShell calls, plus a long-running watcher daemon (agent-collab-watch) that can orchestrate the full Claude → Codex → Claude collaboration loop in opt-in auto-execute mode.
Wraps. Does not replace. Source of truth stays in .agent/ and GitHub.
12 manual-trigger tools
Codex-side review (7 tools, v0.1+)
| Tool | Maps to | Side effects |
|---|---|---|
| status | agent-collab.ps1 status -Json | none (or git fetch if fetchFirst) |
| fetch | git fetch origin <branch> + conditional git pull --ff-only | remote ref always; local working tree only when branch matches |
| read_report | fs.readFile(.agent/CLAUDE_REPORT.md) | none |
| claim_review | agent-collab.ps1 codex-reviewing | one atomic git commit + push (lease-safe; never auto--Force) |
| write_verdict | write .agent/CODEX_VERDICT.md → validate -As codex | local file write only; does NOT commit |
| push_verdict | validate -As codex → codex-pass / codex-fail | one atomic git commit + push |
| close | agent-collab.ps1 off | one atomic git commit + push |
Claude-side execution (5 tools, v0.2+)
| Tool | Maps to | Side effects |
|---|---|---|
| open_phase | agent-collab.ps1 on -Phase <name> | creates collab/<phase> branch; one atomic commit + push |
| claim_work | agent-collab.ps1 claim -As claude | one atomic commit + push (lease-safe) |
| submit_report | validate -As claude → claude-done [-ExtraPath ...] | preValidate then one atomic commit + push; ExtraPaths validated against traversal / absolute / outside-repo / .agent/.lock / missing / symlink-escape |
| read_verdict | fs.readFile(.agent/CODEX_VERDICT.md) + parse | none |
| sync_status | fetch + status composition | git fetch + conditional ff-pull when branches match |
Every mutable tool runs the same two guards first: D-4 schema version check (schema_version != 2 → throw) and D-6 branch guard (git symbolic-ref --short HEAD != COLLAB_MODE.branch → throw, no side effects). open_phase skips the branch guard because it creates the collab branch; post-state verify (assertPostState) still catches silent no-ops. See the inline TypeScript types under src/types.ts and the design rationale in CHANGELOG.md.
collab-watch (v0.4) — watcher daemon with opt-in auto-orchestration
collab-watch is a long-running daemon that polls a collab-managed repo via the sync_status MCP tool and reacts to collab state machine transitions (e.g. waiting_for_codex → codex_reviewing → closed). It is shipped as a second binary, agent-collab-watch, installed alongside agent-collab-mcp-lite from the same npm package.
The watcher's default mode is notify-only: it polls, emits notifications (stdout / JSONL / Windows toast), and never mutates collab state. v0.4 adds an opt-in auto-execute mode (see §v0.4 auto-orchestration) where the watcher takes over the mechanical lease + push + close transitions for its configured role (role: claude or role: codex), invokes an operator-supplied trigger command to bring the local Claude / Codex session into the loop, and writes a request file (.agent/auto_requests/*.json) that the local session reads. Content-bearing verbs (open_phase, submit_report, write_verdict) remain operator-run — the loop is fully automatic only for the dispatch / state-transition / close steps.
Bin
agent-collab-watch <subcommand> --config <path-to-watch.yaml> [flags]Subcommands
| Subcommand | What it does |
|---|---|
| start | Acquire the single-instance lock, spawn the MCP child, and begin the poll loop. Foreground; logs to stdout + JSONL until killed. |
| status | Read <repo>/.agent/watch.state.json and print the current snapshot (last poll, last state, error counters) as JSON. Exit 0 even when no watcher is running — the absence of state is itself a valid status. |
| stop | Read the lock file, signal the live PID, and wait for graceful shutdown. No-op (exit 0) if no watcher is running. |
| tail | Stream new entries from <repo>/.agent/watch.log.jsonl to stdout, follow-mode. |
| test-notify | Emit one synthetic notification through every configured channel and exit. Used to confirm BurntToast / JSONL paths work before committing to a long-running start. |
| validate | Parse --config <path> against the v0.4 schema (incl. required role, optional safety.auto_execute, optional triggers) and exit 0 on success / non-zero with a diagnostic on failure. Does not touch the network, the MCP child, or any .agent/ file. |
watch.yaml — minimal example
The watcher is driven by a YAML config (single-repo; default notify-only with optional safety.auto_execute opt-in). Below is the smallest config that exercises all three notification channels in pure notify-only mode (no safety.auto_execute block ⇒ no MCP mutation). The full schema (every limits.* bound, every safety key, the role requirement, triggers.* shape, push_verdict_freshness.* 6 sub-checks, claim_timeout_sec, audit log layout) is documented in V04_PLAN.md and the example configs at docs/v04-example-configs/.
schema_version: 1
watch:
poll_interval_sec: 15 # 5..600
repo:
id: my-project
path: D:/projects/my-project # must be absolute, must contain .agent/
mcp:
transport: stdio
command: npx
args:
- -y
- "@annealessio647-toby/[email protected]"
startup_timeout_ms: 15000
notifications:
dedupe_window_sec: 60
channels:
- type: stdout
- type: jsonl
- type: windows_toast
title_prefix: "[collab]"
safety:
branch_guard:
on_mismatch: log_only # notify-only — never mutates
limits:
max_events_per_min: 60
log_rotate_bytes: 1048576
log_keep: 5
mcp_call_timeout_sec: 10
max_consecutive_errors: 5
mcp_restart_max: 3The schema is .strict(): unknown keys are rejected with a domain-specific message. repos: (plural / multi-repo) is explicitly rejected — v0.4 watches one repo per watcher process; multi-repo is deferred to a future release.
Notification channels
Three channels ship in v0.4, configured under notifications.channels[]. Multiple channels can be enabled at once; each receives every emitted event.
- stdout — plain text line per event on the watcher's stdout. Useful for
startforeground sessions and CI smoke tests. - jsonl — append-only JSON-Lines log at
<repo>/.agent/watch.log.jsonl. One event per line, rotated atlimits.log_rotate_bytesand kept up tolimits.log_keepfiles. This is the canonical machine-readable trail and the sourceagent-collab-watch tailreads from. - windows_toast — Windows toast notification via the BurntToast PowerShell module. The first emit probes for BurntToast with
Get-Module -ListAvailable -Name BurntToast; if BurntToast is missing or the platform is notwin32, the channel gracefully downgrades to stdout for the remainder of the process and logs a single one-time note (no crashes, no per-event spam).
Safety rails — notify-only by default, opt-in auto-execute
The watcher never calls a mutating MCP verb. Specifically, the following set is hard-rejected at the MCP client boundary by a FORBIDDEN_MVP_VERBS guard in src/watch/mcp_client.ts:
open_phase, claim_work, submit_report, claim_review, write_verdict, push_verdict, closeThe only verb the watcher is allowed to call is sync_status. This is defense-in-depth: even if a future bug, a stray config key, or a misbehaving channel tried to issue a mutating call, the guard short-circuits before the call leaves the process.
Q5=A invariant (v0.3 origin, preserved in v0.4): the three content-bearing MCP verbs — open_phase, submit_report, write_verdict — are FORBIDDEN from watcher auto-execution at runtime in mcp_client.ts (the McpForbiddenVerb guard). v0.4 reduces the FORBIDDEN list from 7 verbs (v0.3) to 3, lifting the ban on the four mechanical-state-transition verbs (claim_review, claim_work, push_verdict, close) so they can be opt-in auto-allowed via safety.auto_execute.allowlist. The original v0.3 approve-queue keys remain rejected: any config that tries to opt in via safety.approve, safety.mode, or AUTO_RUN_ALLOWLIST is rejected by rejectForbiddenKeys before zod validation runs.
Single-instance behavior
The watcher writes a PID file at <repo>/.agent/watch.state.json.lock and acquires it via atomic fs.open(path, 'wx') (exclusive create — race-free). If a second agent-collab-watch start is launched against the same repo:
- the second process throws
LockHeldByActiveProcesswith the PID and lock path, - exits non-zero,
- and the first process's loop is undisturbed.
If the lock file is stale (PID not alive — e.g. the previous watcher was kill -9'd), start reclaims it transparently. status and stop consult the lock file but never create one of their own.
For the wire-level event schema, the full polling state machine, and the rate-limit recovery rules, see V03_PLAN.md §1 in the planning workspace.
v0.4 auto-orchestration (opt-in)
v0.4 layers an optional auto-execute mode on top of the default notify-only
watcher. When enabled per-watcher via safety.auto_execute.enabled: true plus
a per-role allowlist, the watcher takes the lease for its own role
(claim_work for role: claude; claim_review / push_verdict /
close for role: codex) and invokes an operator-supplied trigger command
to bring the IDE on the local box into the loop. The watcher remains the
only process touching the MCP Lite server — the trigger command does the
content-bearing work that requires the human (or the IDE-hosted agent) in the
seat. A foreign-active lease blocks the claim at the watcher level (Codex r2
RC-2) so two watchers on one machine never collide.
Auto-execute is governed by a strict role-verb matrix (a claude watcher
literally cannot fire a codex verb), a per-phase loop cap, a per-call
freshness gate, and a kill switch file. The three reduced-set forbidden
verbs (open_phase, submit_report, write_verdict) are rejected at the
schema boundary AND at runtime. role: both is lab-only and requires an
explicit lab_mode_acknowledgement: true.
Trigger commands are dispatched via child_process.execFile with a fixed
args array, no shell interpolation, and stdout/stderr capped at 4 KB per
stream. Every dispatch is recorded in .agent/watch.audit.jsonl.
See docs/v04-windows-recipe.md for the
full two-machine Windows + VS Code runbook, the kill switch protocol,
and the troubleshooting playbook. Example configs and PowerShell trigger
templates live under docs/v04-example-configs/.
Install — VS Code Quick Start
Option A: npm global install (D-3 primary path)
npm install -g @annealessio647-toby/ai-agent-collab-mcp-liteThen in your project's .vscode/mcp.json:
{
"servers": {
"collab-mcp-lite": {
"command": "agent-collab-mcp-lite",
"args": [],
"env": {}
}
}
}Option B: git submodule (D-3 supported path)
git submodule add https://github.com/annealessio647-toby/ai-agent-collab-mcp-lite.git .agent-mcp-lite
cd .agent-mcp-lite
npm install
npm run buildThen in .vscode/mcp.json:
{
"servers": {
"collab-mcp-lite": {
"command": "node",
"args": ["${workspaceFolder}/.agent-mcp-lite/dist/server.js"],
"env": {}
}
}
}See examples/vscode-mcp.npm.json and examples/vscode-mcp.submodule.json for ready-to-copy snippets.
Windows note — PATHEXT and git
The @modelcontextprotocol/sdk StdioClientTransport on Windows uses a curated env (DEFAULT_INHERITED_ENV_VARS) that includes PATH but OMITS PATHEXT. Without PATHEXT, a spawned PowerShell cannot resolve extension-less command names (git, npm, …), and the FROZEN agent-collab.ps1 dies on its first git call with The term 'git' is not recognized — but PowerShell's Write-Error does NOT set a non-zero exit code by default, so the underlying verb returns "successfully" and disk state is never advanced.
This MCP server defends in two layers:
src/lib/ps_runner.ts buildPsEnv()— when running on Windows, the spawn env is composed so that if the parentprocess.envhas noPATHEXT, a canonical default (.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC) is supplied. Parent-setPATHEXT(in any casing) is preserved.src/lib/post_state_verify.ts assertPostState()— every mutating tool re-reads.agent/COLLAB_MODE.yamlafter the PS verb returns and asserts the on-disk state advanced to one of the expected values. If not, the tool throwsPostStateMismatchwith the actual state, exit code, and truncated stdout/stderr so the operator can investigate. This catches any silent no-op, not just thePATHEXTcause.
If you are a VS Code MCP / Codex CLI MCP user on Windows and want to be extra safe, you can still pass PATHEXT explicitly in the server's env:
{
"servers": {
"collab-mcp-lite": {
"command": "agent-collab-mcp-lite",
"args": [],
"env": {
"PATHEXT": ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC"
}
}
}
}This is no longer required (the server compensates internally), but it does no harm and makes the dependency explicit.
Prerequisites per project
The target project must contain a working .agent/ directory and a tools/agent-collab.ps1 stub (or .agent-collab/tools/agent-collab.ps1 submodule path). See ai-agent-collab-standard for the upstream protocol.
Specifically:
.agent/COLLAB_MODE.yamlexists withschema_version: 2.agent/policy.yamlexistsagent-collab.ps1is available via the path resolved fromrepoPathparameter on each tool call
Tool call shape
Every tool call requires repoPath (absolute path to the project repo containing .agent/). One MCP server instance can drive multiple projects; no global "current repo" state.
// example: write_verdict
{
"name": "write_verdict",
"arguments": {
"repoPath": "D:/projects/<project-root>",
"verdict": "PASS",
"accepted": "...",
"requiredChanges": "None.",
"risks": "None.",
"nextStep": "Run close to advance state.",
"expectedBranch": "collab/some-phase"
}
}Acceptance test (manual two-machine round-trip)
| Step | Where | Action |
|---|---|---|
| 1 | Machine A (Claude, no MCP needed) | agent-collab.ps1 on -Phase <p>, claim -As claude, fill report, claude-done |
| 2 | Machine B (Codex, using MCP Lite) | status({repoPath}) → state=waiting_for_codex |
| 3 | B | fetch({repoPath}) → ref-only + ff-pull (local matches) |
| 4 | B | read_report({repoPath}) |
| 5 | B | claim_review({repoPath}) → state=codex_reviewing |
| 6 | B | write_verdict({repoPath, verdict, ...}) → validate PASS |
| 7 | B | push_verdict({repoPath, verdict}) → commit + push |
| 8 | A or B | close({repoPath, reason}) → state=closed |
Versioning + compatibility
- Requires
schema_version: 2in the target project's.agent/COLLAB_MODE.yaml. Otherwise every tool throwsSchemaVersionMismatch. - v0.1.x line supports
ai-agent-collab-standardv2.0.0 schema. A future v0.2.x may track v3.
What this is NOT
- Not a webhook receiver.
- Not a background scheduler.
- Not an auto-PR-merger.
- Not a domain-aware service (no KiCad / DRC / firmware / report knowledge).
- Not a replacement for
agent-collab.ps1— it shells out to the canonical script.
License
MIT — see LICENSE.
See also
ai-agent-collab-standard— upstream protocol + canonicalagent-collab.ps1MCP_LITE_v1_PLAN.md(Rev 2) instandalone-v1collab phasemcp-lite-plan-review-r2— full spec this repo implements
