Layer 2 MCP Watcher v0 scaffold
Per spec/agent-watcher.md §4. TypeScript/Node implementation living in
mcp-watcher/ subdirectory, parallel to Layer 1 Collector at repo root.
What lands:
- Core MCP server (src/server.ts) with experimental['claude/channel']:{}
+ tools:{} capability declarations, stdio transport, channel-event
notifier wired through the inbox watcher.
- Identity resolution mirroring agent-ping's layered model
(PING_AGENT_IDENTITY env, $CLAUDE_HOME/ping-agent, ~/.ping-agent).
- Inbox reader with HWM tracking, sentinel deferral (warn-after-3),
atomic HWM writes via tmp+rename.
- chokidar-backed file watcher with coalesced drain, urgent-first
ordering, recentEvents map for tool sender lookup.
- Three reply tools (ack / respond / mark_handled) with cross-host
write discipline (writes to local inbox files; Syncthing replicates).
- Sentinel file (.<agent>.watcher-active) for hook coexistence per
spec §4.3 — agent-ping hook stands down when the watcher is in
charge of delivery on this host. Sentinel + hwm in .stignore.
- 35 unit tests passing (vitest): inbox parsing, HWM round-trip,
sentinel deferral semantics, identity layers, tool I/O, watcher
drain + ordering + restart-from-hwm.
- install.sh (Angus-executed, rule-2 compliant) installs deps,
builds, symlinks ~/.local/bin/agent-watcher-mcp, prints mcp.json
registration snippet for paste.
- README documents launch flag, sandbox CLAUDE_HOME pattern,
hook coexistence, observability, v2 limitations.
Not yet:
- Integration test against a real Claude Code session — gated on
Angus spinning up a sandbox CC session on the VPS with
CLAUDE_HOME=~/.claude-sandbox.
- agent-ping hook update to read the sentinel and stand down.
Separate small PR against agent-ping.
Interface contract with Layer 1: the inbox JSONL line shape from
inbox.ts::PingEvent matches inbox.Event in the Collector — bit-
identical reads regardless of source.
This commit is contained in:
parent
3eda72df28
commit
c22558c67a
18 changed files with 4786 additions and 0 deletions
98
mcp-watcher/README.md
Normal file
98
mcp-watcher/README.md
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# agent-watcher-mcp (Layer 2)
|
||||
|
||||
The MCP Watcher: a Claude Code stdio MCP server that surfaces ping-inbox
|
||||
events into your Claude Code session via Channels, and exposes reply tools
|
||||
(`ack` / `respond` / `mark_handled`) so Claude can react.
|
||||
|
||||
Spec: `../spec/agent-watcher.md` §4.
|
||||
|
||||
## What it does
|
||||
|
||||
1. Resolves identity (env / `$CLAUDE_HOME/ping-agent` / `~/.ping-agent`).
|
||||
2. Writes `pings/.<agent>.watcher-active` so the existing agent-ping
|
||||
`UserPromptSubmit` hook stands down on this host.
|
||||
3. Watches `pings/<agent>.inbox` via inotify (chokidar).
|
||||
4. On change, drains unread pings (since HWM), applies sentinel
|
||||
deferral (warn-after-3), and emits each as a
|
||||
`notifications/claude/channel` event.
|
||||
5. Exposes three MCP tools so Claude can ack, respond, or mark a ping
|
||||
handled.
|
||||
6. Removes the sentinel on graceful shutdown.
|
||||
|
||||
## Install
|
||||
|
||||
Per CLAUDE.md rule #2, the agent does not install on itself. A human
|
||||
runs:
|
||||
|
||||
```bash
|
||||
cd /path/to/agent-watcher/mcp-watcher
|
||||
./install.sh
|
||||
```
|
||||
|
||||
This installs deps, builds, symlinks the binary into `~/.local/bin/`,
|
||||
adds `.stignore` patterns, and prints the `mcp.json` snippet to paste
|
||||
into Claude Code's config.
|
||||
|
||||
## Launch
|
||||
|
||||
Channels is in research preview. Start Claude Code with:
|
||||
|
||||
```bash
|
||||
claude --dangerously-load-development-channels server:agent-watcher
|
||||
```
|
||||
|
||||
## Sandbox testing
|
||||
|
||||
To run a separate Claude Code session with a different identity (so it
|
||||
won't compete with the default `~/.ping-agent` for inbox reads):
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.claude-sandbox
|
||||
echo sandbox > ~/.claude-sandbox/ping-agent
|
||||
CLAUDE_HOME=~/.claude-sandbox \
|
||||
claude --dangerously-load-development-channels server:agent-watcher
|
||||
```
|
||||
|
||||
The sandbox session reads `pings/sandbox.inbox` and writes
|
||||
`pings/.sandbox.watcher-active` — fully isolated from prod.
|
||||
|
||||
## How it interacts with agent-ping
|
||||
|
||||
The `agent-ping` UserPromptSubmit hook checks for the sentinel file
|
||||
`pings/.<agent>.watcher-active` at startup. If the file is present and
|
||||
the PID inside is alive, the hook stands down — the watcher is the
|
||||
delivery primitive. If the watcher exits (graceful or crash with stale
|
||||
sentinel cleared by the hook's age check), the hook resumes
|
||||
async-on-next-prompt delivery.
|
||||
|
||||
This means **you can run with the watcher OR the hook OR both
|
||||
configured** — the sentinel arbitrates.
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
npm test # unit tests via vitest (35 currently passing)
|
||||
npm run typecheck # tsc --noEmit
|
||||
npm run build # tsc → dist/
|
||||
```
|
||||
|
||||
## Observability
|
||||
|
||||
- Logs to stderr (visible via `claude --debug` or the Claude Code MCP
|
||||
debug log at `~/.claude/debug/<session-id>.txt`).
|
||||
- Sentinel file content includes PID + start time for the hook's
|
||||
age/liveness check.
|
||||
|
||||
## Limitations / v2
|
||||
|
||||
- Sentinel hook coexistence requires the agent-ping hook to know how
|
||||
to read the sentinel. PR pending against `agent-ping` to add the
|
||||
check.
|
||||
- No reconnection / restart on Claude Code session restart — Claude
|
||||
Code spawns the subprocess anew each session, so the watcher
|
||||
re-drains from HWM cleanly.
|
||||
- One watcher process per agent identity. Two sessions with the same
|
||||
identity contending on the same inbox is undefined behaviour
|
||||
(use `CLAUDE_HOME` to scope identities).
|
||||
- Channels is research preview; if the API changes, expect to update
|
||||
the meta-key sanitization or notification shape.
|
||||
Loading…
Add table
Add a link
Reference in a new issue