clawborrator-mcp 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # clawborrator-mcp (channel_v1)
2
+
3
+ MCP server that connects each running Claude Code instance to a
4
+ [`hub_v1`](https://github.com/clawborrator/hub_v1) over WebSocket.
5
+ Companion to the hub. Designed to be invoked by Claude Code via
6
+ `.mcp.json`; runs as both a long-lived stdio MCP server AND a
7
+ short-lived hook spawn (selected by the `--hook=<HookName>` CLI flag).
8
+
9
+ > **Status: dev-mode-only.** Connect to a local hub at `ws://localhost:8787`.
10
+ > Production deployment is a future concern.
11
+ >
12
+ > Design context: [`hub/design/IMPL-PLAN-1-CHANNEL-V1-FRESH-START.md`](https://github.com/clawborrator/hub/blob/main/design/IMPL-PLAN-1-CHANNEL-V1-FRESH-START.md).
13
+
14
+ ---
15
+
16
+ ## Configuration
17
+
18
+ Set in your project's `.mcp.json`:
19
+
20
+ ```json
21
+ {
22
+ "mcpServers": {
23
+ "clawborrator": {
24
+ "command": "npx",
25
+ "args": ["-y", "clawborrator-mcp"],
26
+ "env": {
27
+ "CLAWBORRATOR_HUB_URL": "ws://localhost:8787",
28
+ "CLAWBORRATOR_TOKEN": "ck_live_…"
29
+ }
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ | Env var | Required | Notes |
36
+ |---|---|---|
37
+ | `CLAWBORRATOR_HUB_URL` | yes | `ws://…` or `wss://…`; no trailing slash |
38
+ | `CLAWBORRATOR_TOKEN` | yes | Channel token (`ck_live_…`) minted via `claw token mint --kind=channel` |
39
+ | `CLAWBORRATOR_REUSE_SESSION_ID` | no | Opt-in: reconnect rebinds to a known session id rather than creating a fresh one |
40
+ | `CLAWBORRATOR_LOG_LEVEL` | no | `debug`, `info`, `warn`, `error`; default `info` |
41
+
42
+ Get the snippet pre-filled with the right URL + token via:
43
+
44
+ ```bash
45
+ claw token mint --kind=channel --name=mbp --mcp-snippet
46
+ ```
47
+
48
+ ---
49
+
50
+ ## What it does
51
+
52
+ **Long-lived MCP path** (default invocation):
53
+ 1. Reads env config; loads channel token.
54
+ 2. Opens WSS to `<HUB_URL>/channel` with `Authorization: Bearer <CHANNEL_TOKEN>`.
55
+ 3. Sends `register` with host / cwd / pid / version; receives `welcome` with sessionId + routingName.
56
+ 4. Writes `<cwd>/.claude/clawborrator.session.json` (mode 0600) so per-event hook spawns can find the active session.
57
+ 5. Maintains the WS with heartbeat ping/pong; reconnects with exponential backoff (1s/2s/5s/15s/30s/60s).
58
+ 6. Listens for hub-side messages: `prompt` (cross-session route), `permission_response`, `peers_update`, `bye`, `error`.
59
+ 7. On clean shutdown (SIGINT/SIGTERM/exit), deletes the sidecar.
60
+
61
+ **Short-lived hook path** (`--hook=<HookName>` flag):
62
+ 1. Reads JSON payload from stdin (Claude Code's hook protocol).
63
+ 2. Locates the active sidecar (walks up from cwd looking for `.claude/clawborrator.session.json`).
64
+ 3. Maps the hook name to a clawborrator event (e.g. `PreToolUse` → `tail/PreToolUse`, `UserPromptSubmit` → `chat/prompt`).
65
+ 4. POSTs to `<HUB_URL>/api/channel/event` with the channel token from the sidecar.
66
+ 5. Echoes stdin to stdout so Claude's hook chain stays intact.
67
+ 6. Exits cleanly even if the hub is unreachable — never breaks the operator's actual Claude flow.
68
+
69
+ Hooks are installed with `claw session init` from inside a project — see hub_v1 README.
70
+
71
+ ---
72
+
73
+ ## Tools exposed to Claude
74
+
75
+ v1 ships an empty MCP tool list. The hooks-based event firehose
76
+ covers chat + tail event capture without any Claude-side tool calls.
77
+
78
+ Phase D adds these:
79
+ - `reply({ chat_id, text })` — Claude posts a tagged final reply
80
+ - `list_peers()` — Claude discovers other operator sessions
81
+ - `route_to_peer({ peer, prompt, mode })` — Claude routes a question
82
+ - `probe_peers({ prompt, peers? })` — Claude fan-out probes
83
+
84
+ For now, operators initiate routing via `claw route` / `claw probe`.
85
+
86
+ ---
87
+
88
+ ## Phases
89
+
90
+ - **Phase A** (✓): connect / register / heartbeat / reconnect
91
+ - **Phase B** (✓): hooks + event forwarding via sidecar
92
+ - **Phase C** (✓): bidirectional permission relay protocol (channel
93
+ → hub → operator → back). Hook IPC for actually delivering decisions
94
+ into a blocked PreToolUse hook is upcoming.
95
+ - **Phase D**: MCP tools (above)
96
+
97
+ ---
98
+
99
+ ## Local dev (linked to a sibling hub_v1 checkout)
100
+
101
+ ```bash
102
+ npm install
103
+ npm run build
104
+ npm link
105
+
106
+ # verify the binary is on PATH
107
+ clawborrator-mcp --hook=PreToolUse < /dev/null # exits cleanly with no sidecar
108
+ ```
109
+
110
+ When `claude` runs in a folder whose `.mcp.json` references `clawborrator-mcp`, npm/npx resolves it to your linked build.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/index.js').catch((err) => {
3
+ console.error(err);
4
+ process.exit(1);
5
+ });
@@ -0,0 +1,6 @@
1
+ export interface ChannelConfig {
2
+ hubUrl: string;
3
+ token: string;
4
+ reuseSessionId: string | null;
5
+ }
6
+ export declare function loadConfig(): ChannelConfig;
package/dist/config.js ADDED
@@ -0,0 +1,22 @@
1
+ // Read + validate the env-var configuration the MCP needs to connect.
2
+ // Mirrors the shape documented in the channel_v1 design doc §3.
3
+ export function loadConfig() {
4
+ const hubUrl = process.env.CLAWBORRATOR_HUB_URL?.trim();
5
+ const token = process.env.CLAWBORRATOR_TOKEN?.trim();
6
+ if (!hubUrl)
7
+ throw new Error('CLAWBORRATOR_HUB_URL is required');
8
+ if (!token)
9
+ throw new Error('CLAWBORRATOR_TOKEN is required');
10
+ if (!hubUrl.startsWith('ws://') && !hubUrl.startsWith('wss://')) {
11
+ throw new Error(`CLAWBORRATOR_HUB_URL must be ws:// or wss://, got: ${hubUrl}`);
12
+ }
13
+ if (!token.startsWith('ck_live_')) {
14
+ throw new Error(`CLAWBORRATOR_TOKEN must start with ck_live_`);
15
+ }
16
+ return {
17
+ hubUrl: hubUrl.replace(/\/$/, ''),
18
+ token,
19
+ reuseSessionId: process.env.CLAWBORRATOR_REUSE_SESSION_ID?.trim() || null,
20
+ };
21
+ }
22
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,gEAAgE;AAQhE,MAAM,UAAU,UAAU;IACxB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,IAAI,EAAE,CAAC;IACxD,MAAM,KAAK,GAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE,CAAC;IACtD,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACjE,IAAI,CAAC,KAAK;QAAG,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC/D,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,sDAAsD,MAAM,EAAE,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACjE,CAAC;IACD,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;QACjC,KAAK;QACL,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC,6BAA6B,EAAE,IAAI,EAAE,IAAI,IAAI;KAC1E,CAAC;AACJ,CAAC"}
package/dist/hook.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function runHook(hookName: string): Promise<void>;
package/dist/hook.js ADDED
@@ -0,0 +1,250 @@
1
+ // Hook entry point. Spawned by Claude Code per `.claude/settings.json`
2
+ // like:
3
+ //
4
+ // { "type": "command", "command": "npx clawborrator-mcp --hook=PreToolUse" }
5
+ //
6
+ // The hook receives the JSON payload on stdin. We:
7
+ // 1. Read stdin to EOF
8
+ // 2. Locate the sidecar in the cwd (or walk up parents)
9
+ // 3. Map the hook name to a clawborrator event kind+type
10
+ // 4. Enrich Stop / SubagentStop with assistant_text (final answer)
11
+ // from the transcript JSONL
12
+ // 5. For PreToolUse, ship 0..N chat/assistant_text events for any
13
+ // "let me check X" running commentary Claude wrote in the same
14
+ // assistant message before this tool_use, plus a placeholder
15
+ // when extended thinking happened but the plaintext was stripped
16
+ // from disk
17
+ // 6. POST /api/channel/event with the channel token from the sidecar
18
+ // 7. Echo stdin to stdout so Claude's hook chain stays intact
19
+ import { findSidecar } from './sidecar.js';
20
+ import { log } from './log.js';
21
+ import { readTranscriptMessages, messageContainsToolUse, extractTextBlocksBeforeToolUse, hasThinkingBlocksBeforeToolUse, extractFromLastAssistantMessage, extractFinalAnswerFromTranscript, DEFAULT_TAIL_BYTES, } from './transcript.js';
22
+ // Mirrors the install-hooks set from the old clawborrator-channel
23
+ // package so the remote viewer has the same coverage of Claude Code's
24
+ // hook surface.
25
+ const HOOK_TO_EVENT = {
26
+ UserPromptSubmit: { kind: 'chat', type: 'prompt' },
27
+ PreToolUse: { kind: 'tail', type: 'PreToolUse' },
28
+ PostToolUse: { kind: 'tail', type: 'PostToolUse' },
29
+ PostToolUseFailure: { kind: 'tail', type: 'PostToolUseFailure' },
30
+ Stop: { kind: 'tail', type: 'Stop' },
31
+ Notification: { kind: 'tail', type: 'Notification' },
32
+ SessionStart: { kind: 'tail', type: 'SessionStart' },
33
+ SessionEnd: { kind: 'tail', type: 'SessionEnd' },
34
+ TaskCreated: { kind: 'tail', type: 'TaskCreated' },
35
+ SubagentStart: { kind: 'tail', type: 'SubagentStart' },
36
+ SubagentStop: { kind: 'tail', type: 'SubagentStop' },
37
+ TaskCompleted: { kind: 'tail', type: 'TaskCompleted' },
38
+ };
39
+ // Tools whose pre-text we DON'T ship as AssistantText. The clawborrator
40
+ // reply tool already routes the user-facing answer over the WS — pre-
41
+ // reply text from the model is nearly identical and would render as a
42
+ // duplicate row in chat.
43
+ const TOOLS_SKIPPED_FOR_PRE_TEXT = new Set([
44
+ 'mcp__clawborrator__reply',
45
+ ]);
46
+ async function readStdin() {
47
+ return new Promise((res) => {
48
+ let buf = '';
49
+ process.stdin.setEncoding('utf8');
50
+ process.stdin.on('data', (chunk) => { buf += chunk; });
51
+ process.stdin.on('end', () => res(buf));
52
+ process.stdin.on('close', () => res(buf));
53
+ });
54
+ }
55
+ async function postEvent(sidecar, body, timeoutMs) {
56
+ const ctl = new AbortController();
57
+ const timer = setTimeout(() => ctl.abort(), timeoutMs);
58
+ try {
59
+ const res = await fetch(`${sidecar.hubUrl}/api/channel/event`, {
60
+ method: 'POST',
61
+ headers: {
62
+ 'Authorization': `Bearer ${sidecar.channelToken}`,
63
+ 'Content-Type': 'application/json',
64
+ },
65
+ body: JSON.stringify(body),
66
+ signal: ctl.signal,
67
+ });
68
+ if (!res.ok) {
69
+ const text = await res.text().catch(() => '');
70
+ log.warn('hub rejected event', { kind: body.kind, type: body.type, status: res.status, body: text.slice(0, 240) });
71
+ return false;
72
+ }
73
+ return true;
74
+ }
75
+ catch (e) {
76
+ log.warn('event POST failed', { kind: body.kind, type: body.type, error: e?.message ?? String(e) });
77
+ return false;
78
+ }
79
+ finally {
80
+ clearTimeout(timer);
81
+ }
82
+ }
83
+ // Stop / SubagentStop: try to populate payload.assistant_text from
84
+ // (in order) last_assistant_message → existing payload fields →
85
+ // transcript tail. Mutates payload in place.
86
+ async function enrichStopAssistantText(payload, hookName) {
87
+ if (typeof payload.assistant_text === 'string' && payload.assistant_text.trim())
88
+ return;
89
+ const fromLAM = extractFromLastAssistantMessage(payload.last_assistant_message);
90
+ if (fromLAM) {
91
+ payload.assistant_text = fromLAM;
92
+ log.debug('stop: assistant_text from last_assistant_message', { chars: fromLAM.length });
93
+ return;
94
+ }
95
+ if (typeof payload.text === 'string' && payload.text.trim() ||
96
+ typeof payload.response === 'string' && payload.response.trim()) {
97
+ return; // a string-shaped variant is already there; let downstream use it
98
+ }
99
+ const transcriptPath = typeof payload.transcript_path === 'string' ? payload.transcript_path : '';
100
+ if (!transcriptPath) {
101
+ log.debug('stop: no transcript_path on payload, no assistant_text recoverable', { hookName });
102
+ return;
103
+ }
104
+ // Sleep so CC has time to flush the just-finished assistant message
105
+ // to disk before we tail-read. Without this, on some CC versions the
106
+ // read returns the PRIOR turn's text, which would render the wrong
107
+ // reply on attached operators' TUI.
108
+ await new Promise((r) => setTimeout(r, 500));
109
+ const text = extractFinalAnswerFromTranscript(transcriptPath, DEFAULT_TAIL_BYTES);
110
+ if (text) {
111
+ payload.assistant_text = text;
112
+ log.info('stop: assistant_text extracted from transcript', { chars: text.length });
113
+ }
114
+ else {
115
+ log.debug('stop: transcript had no assistant-text block in tail window', { hookName, path: transcriptPath });
116
+ }
117
+ }
118
+ // PreToolUse: ship every text block Claude wrote in the same
119
+ // assistant message BEFORE this tool_use as its own chat/assistant_text
120
+ // event. If no text blocks but extended-thinking blocks were present
121
+ // (CC strips thinking plaintext on disk; only the signature survives),
122
+ // ship a single placeholder so the operator sees a "claude was
123
+ // thinking here" marker.
124
+ async function shipPreToolAssistantText(sidecar, payload) {
125
+ const transcriptPath = typeof payload.transcript_path === 'string' ? payload.transcript_path : '';
126
+ const toolUseId = String(payload.tool_use_id ?? payload.toolUseId ?? '');
127
+ const toolName = String(payload.tool_name ?? payload.toolName ?? '');
128
+ if (!transcriptPath || !toolUseId)
129
+ return;
130
+ if (TOOLS_SKIPPED_FOR_PRE_TEXT.has(toolName))
131
+ return;
132
+ // Race-retry: CC writes the assistant message that DECIDED the
133
+ // tool_use to the transcript ASYNC, so a fresh read right after the
134
+ // hook fires can miss it. Retry up to 4 times with 80ms delays.
135
+ let messages = readTranscriptMessages(transcriptPath, DEFAULT_TAIL_BYTES);
136
+ let foundTarget = messageContainsToolUse(messages, toolUseId);
137
+ let retries = 0;
138
+ while (!foundTarget && retries < 4) {
139
+ await new Promise((r) => setTimeout(r, 80));
140
+ messages = readTranscriptMessages(transcriptPath, DEFAULT_TAIL_BYTES);
141
+ foundTarget = messageContainsToolUse(messages, toolUseId);
142
+ retries++;
143
+ }
144
+ const blocks = extractTextBlocksBeforeToolUse(messages, toolUseId);
145
+ const hasThinking = blocks.length === 0 ? hasThinkingBlocksBeforeToolUse(messages, toolUseId) : false;
146
+ log.debug('PreToolUse extract', {
147
+ toolUseId: toolUseId.slice(0, 24),
148
+ messages: messages.length,
149
+ targetFound: foundTarget,
150
+ retries,
151
+ blocks: blocks.length,
152
+ placeholder: hasThinking,
153
+ });
154
+ // Monotonic timestamps so events sort chronologically on the hub
155
+ // (chat_events orders by ts; ties broken by id, but ts ordering is
156
+ // the human signal). We use ms epoch then convert to ISO.
157
+ const baseMs = Date.now();
158
+ const tsAt = (i) => new Date(baseMs + i).toISOString();
159
+ if (blocks.length > 0) {
160
+ // Fire all AssistantText POSTs in parallel — sequential with 600ms
161
+ // each could exceed CC's hook timeout on multi-block turns.
162
+ await Promise.all(blocks.map((text, i) => postEvent(sidecar, {
163
+ sessionId: sidecar.sessionId,
164
+ kind: 'chat',
165
+ type: 'assistant_text',
166
+ payload: { text, toolUseId },
167
+ ts: tsAt(i),
168
+ }, 800)));
169
+ }
170
+ else if (hasThinking) {
171
+ await postEvent(sidecar, {
172
+ sessionId: sidecar.sessionId,
173
+ kind: 'chat',
174
+ type: 'assistant_text',
175
+ payload: {
176
+ text: '(extended thinking — content not in transcript)',
177
+ toolUseId,
178
+ placeholder: true,
179
+ },
180
+ ts: tsAt(0),
181
+ }, 800);
182
+ }
183
+ }
184
+ export async function runHook(hookName) {
185
+ const map = HOOK_TO_EVENT[hookName];
186
+ if (!map) {
187
+ log.warn('unknown hook name; skipping', { hookName });
188
+ process.exit(0);
189
+ }
190
+ const stdinRaw = await readStdin();
191
+ // Always echo stdin so Claude's hook chain sees the original payload.
192
+ if (stdinRaw)
193
+ process.stdout.write(stdinRaw);
194
+ let payload = {};
195
+ try {
196
+ if (stdinRaw.trim())
197
+ payload = JSON.parse(stdinRaw);
198
+ }
199
+ catch {
200
+ payload = { rawStdin: stdinRaw.slice(0, 2000) };
201
+ }
202
+ const sidecar = findSidecar(process.cwd());
203
+ if (!sidecar) {
204
+ log.warn('hook fired but no sidecar found — channel must not be running', { cwd: process.cwd() });
205
+ process.exit(0);
206
+ }
207
+ try {
208
+ if (hookName === 'Stop') {
209
+ // Real turn end — extract final answer + ship chat/reply so
210
+ // attached operators see Claude's response in the chat lane.
211
+ // The tail Stop event below marks the boundary.
212
+ await enrichStopAssistantText(payload, hookName);
213
+ const reply = typeof payload.assistant_text === 'string' ? payload.assistant_text.trim() : '';
214
+ if (reply) {
215
+ await postEvent(sidecar, {
216
+ sessionId: sidecar.sessionId,
217
+ kind: 'chat',
218
+ type: 'reply',
219
+ payload: { text: reply },
220
+ ts: new Date().toISOString(),
221
+ }, 2000);
222
+ }
223
+ }
224
+ else if (hookName === 'SubagentStop') {
225
+ // A Task-tool subagent finished. NOT a final-answer event — the
226
+ // parent agent is still running and will fire its own Stop later.
227
+ // Some CC versions populate `last_assistant_message` with the
228
+ // subagent's INPUT prompt rather than its reply, which previously
229
+ // surfaced as a misleading chat/reply row. We still record the
230
+ // tail event below for activity-lane visibility, but skip the
231
+ // chat/reply ship entirely.
232
+ }
233
+ else if (hookName === 'PreToolUse') {
234
+ await shipPreToolAssistantText(sidecar, payload);
235
+ }
236
+ }
237
+ catch (e) {
238
+ log.warn('pre-event enrichment threw', { error: e?.message ?? String(e), hookName });
239
+ }
240
+ // Main hook event POST.
241
+ await postEvent(sidecar, {
242
+ sessionId: sidecar.sessionId,
243
+ kind: map.kind,
244
+ type: map.type,
245
+ payload,
246
+ ts: new Date().toISOString(),
247
+ }, 5000);
248
+ process.exit(0);
249
+ }
250
+ //# sourceMappingURL=hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ interface InboxEntry {
2
+ chatId: string;
3
+ text: string;
4
+ }
5
+ export declare function enqueueRoutedPrompt(entry: InboxEntry): void;
6
+ export declare function tryDequeue(): InboxEntry | null;
7
+ export declare function awaitDequeue(maxWaitMs: number): Promise<InboxEntry | null>;
8
+ export declare function inboxSize(): number;
9
+ export {};
package/dist/inbox.js ADDED
@@ -0,0 +1,55 @@
1
+ // In-memory inbox for routed prompts arriving from the hub. The
2
+ // `await_routed_prompt` MCP tool reads from here.
3
+ //
4
+ // Why a queue rather than callback-driven: Claude Code can only see
5
+ // "messages" the operator types (or that the MCP returns from a tool
6
+ // call). To inject a routed prompt, the only mechanism we have today
7
+ // is for Claude to CALL a tool that returns the pending prompt as
8
+ // its result — Claude then treats the result as the next thing to
9
+ // answer.
10
+ //
11
+ // The convention in CLAUDE.md / the tool description is: at the
12
+ // start of each turn, Claude calls `await_routed_prompt({maxWaitMs:0})`.
13
+ // If the result has a non-null prompt, Claude answers by calling
14
+ // `reply({chat_id, text})` and skips its normal user-facing reply.
15
+ const queue = [];
16
+ let waiters = [];
17
+ export function enqueueRoutedPrompt(entry) {
18
+ // If a waiter is parked, hand off directly.
19
+ const w = waiters.shift();
20
+ if (w) {
21
+ w(entry);
22
+ return;
23
+ }
24
+ queue.push(entry);
25
+ }
26
+ export function tryDequeue() {
27
+ return queue.shift() ?? null;
28
+ }
29
+ export function awaitDequeue(maxWaitMs) {
30
+ const immediate = tryDequeue();
31
+ if (immediate || maxWaitMs <= 0) {
32
+ return Promise.resolve(immediate);
33
+ }
34
+ return new Promise((resolve) => {
35
+ let resolved = false;
36
+ let timer = null;
37
+ const settle = (entry) => {
38
+ if (resolved)
39
+ return;
40
+ resolved = true;
41
+ const i = waiters.indexOf(settle);
42
+ if (i >= 0)
43
+ waiters.splice(i, 1);
44
+ if (timer)
45
+ clearTimeout(timer);
46
+ resolve(entry);
47
+ };
48
+ waiters.push(settle);
49
+ timer = setTimeout(() => settle(null), maxWaitMs);
50
+ });
51
+ }
52
+ export function inboxSize() {
53
+ return queue.length;
54
+ }
55
+ //# sourceMappingURL=inbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox.js","sourceRoot":"","sources":["../src/inbox.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,kDAAkD;AAClD,EAAE;AACF,oEAAoE;AACpE,qEAAqE;AACrE,qEAAqE;AACrE,kEAAkE;AAClE,kEAAkE;AAClE,UAAU;AACV,EAAE;AACF,gEAAgE;AAChE,yEAAyE;AACzE,iEAAiE;AACjE,mEAAmE;AAOnE,MAAM,KAAK,GAAiB,EAAE,CAAC;AAC/B,IAAI,OAAO,GAA2C,EAAE,CAAC;AAEzD,MAAM,UAAU,mBAAmB,CAAC,KAAiB;IACnD,4CAA4C;IAC5C,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE,CAAC;QAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAAC,OAAO;IAAC,CAAC;IAC5B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,KAAK,CAAC,KAAK,EAAE,IAAI,IAAI,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,MAAM,SAAS,GAAG,UAAU,EAAE,CAAC;IAC/B,IAAI,SAAS,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,KAAK,GAA0B,IAAI,CAAC;QACxC,MAAM,MAAM,GAAG,CAAC,KAAwB,EAAE,EAAE;YAC1C,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACjC,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;YAC/B,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,140 @@
1
+ // MCP entrypoint. Phase A: open the WS to hub, register, log every
2
+ // inbound message to stderr. Subsequent phases will:
3
+ // - install hooks (Phase B) that turn into chat_event/tail_event sends
4
+ // - wire permission relay (Phase C)
5
+ // - register MCP tools (reply, list_peers, route_to_peer, probe_peers)
6
+ // for Claude to call (Phase D)
7
+ //
8
+ // In Phase A we still need the MCP transport open so Claude Code's
9
+ // MCP-discovery handshake succeeds; otherwise the user sees "MCP
10
+ // server failed to start." We register a no-op tool list — Claude
11
+ // gets an empty tools/list response and the WS work proceeds in the
12
+ // background.
13
+ import { hostname } from 'node:os';
14
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
15
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
16
+ import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
17
+ import { loadConfig } from './config.js';
18
+ import { ChannelClient } from './ws-client.js';
19
+ import { log } from './log.js';
20
+ import { runHook } from './hook.js';
21
+ import { writeSidecar, deleteSidecar } from './sidecar.js';
22
+ import { TOOL_DEFINITIONS, callTool } from './tools/index.js';
23
+ import { enqueueRoutedPrompt } from './inbox.js';
24
+ // Dispatch on --hook=<HookName> first; that's the short-lived spawn
25
+ // path Claude Code's hook system uses. Without it, fall through to
26
+ // the long-lived MCP stdio server.
27
+ const hookFlag = process.argv.find((a) => a.startsWith('--hook='));
28
+ if (hookFlag) {
29
+ const name = hookFlag.slice('--hook='.length);
30
+ runHook(name).catch((err) => {
31
+ log.error('hook fatal', { error: String(err) });
32
+ process.exit(0); // never fail the operator's actual Claude flow
33
+ });
34
+ }
35
+ else {
36
+ main().catch((err) => {
37
+ log.error('fatal', { error: String(err), stack: err?.stack });
38
+ process.exit(1);
39
+ });
40
+ }
41
+ async function main() {
42
+ let config;
43
+ try {
44
+ config = loadConfig();
45
+ }
46
+ catch (e) {
47
+ log.error('config invalid', { error: String(e) });
48
+ process.exit(2);
49
+ }
50
+ log.info('clawborrator-mcp starting', { hubUrl: config.hubUrl });
51
+ const cwd = process.cwd();
52
+ const host = hostname();
53
+ // Open the channel-side WS to hub.
54
+ const client = new ChannelClient(config, {
55
+ onWelcome: (m) => {
56
+ log.info('session ready', {
57
+ sessionId: m.sessionId,
58
+ routingName: m.routingName,
59
+ channelToken: m.channelTokenName,
60
+ });
61
+ // Sidecar — Phase B hooks read this. The hub URL on the sidecar
62
+ // is the HTTP form (hooks POST over HTTPS, not WS).
63
+ const httpHubUrl = config.hubUrl.replace(/^ws/i, 'http');
64
+ writeSidecar({
65
+ sessionId: m.sessionId,
66
+ routingName: m.routingName,
67
+ hubUrl: httpHubUrl,
68
+ channelToken: config.token,
69
+ host,
70
+ cwd,
71
+ writtenAt: new Date().toISOString(),
72
+ });
73
+ },
74
+ onPrompt: (m) => {
75
+ // A peer session routed a prompt to us. Push to the inbox so
76
+ // the next `await_routed_prompt` tool call can pick it up.
77
+ log.info('prompt received', { chatId: m.chatId, text: m.text });
78
+ enqueueRoutedPrompt({ chatId: m.chatId, text: m.text });
79
+ },
80
+ onPermissionResponse: (m) => {
81
+ // Phase C: the operator (or auto-expire) resolved a permission.
82
+ // We surface it in the log so operators running with
83
+ // CLAWBORRATOR_LOG_LEVEL=info can see decisions land. Real
84
+ // hook integration (where this decision routes back to a
85
+ // pending hook spawn) is a follow-on once we wire IPC between
86
+ // the long-lived MCP and the short-lived hook process.
87
+ log.info('permission resolved', {
88
+ requestId: m.requestId,
89
+ decision: m.decision,
90
+ message: m.message ?? null,
91
+ });
92
+ },
93
+ onError: (m) => {
94
+ log.error('hub rejected', { code: m.code, message: m.message });
95
+ },
96
+ });
97
+ client.connect();
98
+ // MCP stdio transport. Phase D ships the four routing tools.
99
+ const server = new Server({
100
+ name: 'clawborrator',
101
+ version: '0.0.1',
102
+ }, {
103
+ capabilities: {
104
+ tools: {},
105
+ },
106
+ });
107
+ // Track our session id so tools (and future use) can reference it.
108
+ // Set by the onWelcome handler above.
109
+ let toolCtxSessionId = null;
110
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
111
+ tools: TOOL_DEFINITIONS.map((t) => ({
112
+ name: t.name,
113
+ description: t.description,
114
+ inputSchema: t.inputSchema,
115
+ })),
116
+ }));
117
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
118
+ const args = (req.params.arguments ?? {});
119
+ return await callTool({ client, sessionId: toolCtxSessionId }, req.params.name, args);
120
+ });
121
+ void toolCtxSessionId;
122
+ const transport = new StdioServerTransport();
123
+ await server.connect(transport);
124
+ log.info('mcp transport connected');
125
+ // Clean shutdown on common signals so the WS goes away cleanly +
126
+ // sidecar gets removed so a stale file doesn't mislead future hooks.
127
+ for (const sig of ['SIGINT', 'SIGTERM']) {
128
+ process.on(sig, () => {
129
+ log.info('shutting down', { signal: sig });
130
+ client.stop();
131
+ deleteSidecar(cwd);
132
+ transport.close().catch(() => { });
133
+ setTimeout(() => process.exit(0), 200);
134
+ });
135
+ }
136
+ // Last-ditch sidecar cleanup. Even on uncaught exceptions, we'd
137
+ // rather not leave a stale file pointing at a dead WS.
138
+ process.on('exit', () => deleteSidecar(cwd));
139
+ }
140
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,qDAAqD;AACrD,yEAAyE;AACzE,sCAAsC;AACtC,yEAAyE;AACzE,mCAAmC;AACnC,EAAE;AACF,mEAAmE;AACnE,iEAAiE;AACjE,kEAAkE;AAClE,oEAAoE;AACpE,cAAc;AAEd,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AACnG,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,oEAAoE;AACpE,mEAAmE;AACnE,mCAAmC;AACnC,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;AACnE,IAAI,QAAQ,EAAE,CAAC;IACb,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QAC1B,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAG,+CAA+C;IACpE,CAAC,CAAC,CAAC;AACL,CAAC;KAAM,CAAC;IACN,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACnB,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,UAAU,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,GAAG,CAAC,KAAK,CAAC,gBAAgB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,GAAG,CAAC,IAAI,CAAC,2BAA2B,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAEjE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;IAExB,mCAAmC;IACnC,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE;QACvC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;YACf,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE;gBACxB,SAAS,EAAK,CAAC,CAAC,SAAS;gBACzB,WAAW,EAAG,CAAC,CAAC,WAAW;gBAC3B,YAAY,EAAE,CAAC,CAAC,gBAAgB;aACjC,CAAC,CAAC;YACH,gEAAgE;YAChE,oDAAoD;YACpD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACzD,YAAY,CAAC;gBACX,SAAS,EAAK,CAAC,CAAC,SAAS;gBACzB,WAAW,EAAG,CAAC,CAAC,WAAW;gBAC3B,MAAM,EAAQ,UAAU;gBACxB,YAAY,EAAE,MAAM,CAAC,KAAK;gBAC1B,IAAI;gBACJ,GAAG;gBACH,SAAS,EAAK,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACvC,CAAC,CAAC;QACL,CAAC;QACD,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;YACd,6DAA6D;YAC7D,2DAA2D;YAC3D,GAAG,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAChE,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1D,CAAC;QACD,oBAAoB,EAAE,CAAC,CAAC,EAAE,EAAE;YAC1B,gEAAgE;YAChE,qDAAqD;YACrD,2DAA2D;YAC3D,yDAAyD;YACzD,8DAA8D;YAC9D,uDAAuD;YACvD,GAAG,CAAC,IAAI,CAAC,qBAAqB,EAAE;gBAC9B,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,QAAQ,EAAG,CAAC,CAAC,QAAQ;gBACrB,OAAO,EAAI,CAAC,CAAC,OAAO,IAAI,IAAI;aAC7B,CAAC,CAAC;QACL,CAAC;QACD,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;YACb,GAAG,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAClE,CAAC;KACF,CAAC,CAAC;IACH,MAAM,CAAC,OAAO,EAAE,CAAC;IAEjB,6DAA6D;IAC7D,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;QACE,IAAI,EAAK,cAAc;QACvB,OAAO,EAAE,OAAO;KACjB,EACD;QACE,YAAY,EAAE;YACZ,KAAK,EAAE,EAAE;SACV;KACF,CACF,CAAC;IAEF,mEAAmE;IACnE,sCAAsC;IACtC,IAAI,gBAAgB,GAAkB,IAAI,CAAC;IAE3C,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAClC,IAAI,EAAS,CAAC,CAAC,IAAI;YACnB,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;SAC3B,CAAC,CAAC;KACJ,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAC5D,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAA4B,CAAC;QACrE,OAAO,MAAM,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IACH,KAAK,gBAAgB,CAAC;IAEtB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IAEpC,iEAAiE;IACjE,qEAAqE;IACrE,KAAK,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAU,EAAE,CAAC;QACjD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE;YACnB,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YAC3C,MAAM,CAAC,IAAI,EAAE,CAAC;YACd,aAAa,CAAC,GAAG,CAAC,CAAC;YACnB,SAAS,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAClC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC;IACD,gEAAgE;IAChE,uDAAuD;IACvD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/C,CAAC"}
package/dist/log.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export declare const log: {
2
+ debug: (msg: string, fields?: Record<string, unknown>) => void;
3
+ info: (msg: string, fields?: Record<string, unknown>) => void;
4
+ warn: (msg: string, fields?: Record<string, unknown>) => void;
5
+ error: (msg: string, fields?: Record<string, unknown>) => void;
6
+ };