@chinchillaenterprises/mcp-claude-channel 0.1.0
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 +117 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +315 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @chinchillaenterprises/mcp-claude-channel
|
|
2
|
+
|
|
3
|
+
**Lane-to-lane Claude Code side-door channel server (v1).** Pushes a **nudge**
|
|
4
|
+
from a peer lane **into a live Claude Code session** via the official **Channels**
|
|
5
|
+
notification (`notifications/claude/channel`) and exposes a `send_to_lane` tool to
|
|
6
|
+
nudge the next lane. **v1 is session↔session only — no Slack.**
|
|
7
|
+
|
|
8
|
+
> The doorbell, not the payload. The **SQLite Lane Board** stays the source of truth.
|
|
9
|
+
|
|
10
|
+
## What it's for
|
|
11
|
+
|
|
12
|
+
Echelon's "fused line": the PEP lanes (R&D · Arch · Dev · Deploy · Test) are
|
|
13
|
+
Claude Code sessions in separate tabs. This server automates the **baton handoff
|
|
14
|
+
between tabs** — replacing the human walking it lane-to-lane.
|
|
15
|
+
|
|
16
|
+
## How it's different from every other ChillMCP server
|
|
17
|
+
|
|
18
|
+
The other servers are request/response stdio (`capabilities:{tools:{}}`). This one
|
|
19
|
+
adds two patterns net-new to the repo:
|
|
20
|
+
|
|
21
|
+
1. **Server-initiated notifications** — emits `notifications/claude/channel`, a
|
|
22
|
+
server→client push Claude Code injects into the running session as a new
|
|
23
|
+
`<channel ...>` turn.
|
|
24
|
+
2. **A resident file-inbox tailer** — watches this lane's inbox and turns each
|
|
25
|
+
appended line into a push. Peer lanes write to it via `send_to_lane`.
|
|
26
|
+
|
|
27
|
+
The process stays alive on the stdio transport + the inbox poll loop. No HTTP, no
|
|
28
|
+
Slack — this is a stdio child of Claude Code.
|
|
29
|
+
|
|
30
|
+
## Wire model (pure files, no shared daemon)
|
|
31
|
+
|
|
32
|
+
- **Inbox:** `<LANE_INBOX_DIR>/chan-<LANE_NAME>-in.jsonl` — one JSON object per
|
|
33
|
+
line: `{"content": "...", "meta": {...}}`.
|
|
34
|
+
- This server **tails its own inbox** and pushes each new line.
|
|
35
|
+
- `send_to_lane(target, text, meta?)` **appends a line to the TARGET's inbox**,
|
|
36
|
+
stamping `meta.from_lane` with our own callsign.
|
|
37
|
+
|
|
38
|
+
Mirrors the proven `labs/sidedoor-ref/lane-channel.py`.
|
|
39
|
+
|
|
40
|
+
## The principle — the push is a NUDGE, not the payload
|
|
41
|
+
|
|
42
|
+
Content is a short doorbell: *"you're next — round R7, baton at `<path>`, check the
|
|
43
|
+
Board."* The receiving lane reads the **real baton from the SQLite Lane Board**, not
|
|
44
|
+
from the push. This kills dedup / replay / double-process.
|
|
45
|
+
|
|
46
|
+
## Protocol contract
|
|
47
|
+
|
|
48
|
+
- **Handshake:** `initialize` advertises `capabilities.experimental["claude/channel"] = {}`
|
|
49
|
+
(the badge that arms the session's notification listener).
|
|
50
|
+
- **Inbound (peer lane → session):** each allowed inbox line →
|
|
51
|
+
`notifications/claude/channel` with `params.content` (the nudge) and `params.meta`.
|
|
52
|
+
Every `meta` key becomes a `<channel ...>` attribute. We carry `from_lane`,
|
|
53
|
+
`to_lane`, `round_id`, `stage`, `baton_path`, plus `source` (this lane).
|
|
54
|
+
- **Outbound (session → peer lane):** the `send_to_lane` tool appends to the
|
|
55
|
+
target lane's inbox. (Replaces v1's would-be `reply_to_channel`-to-Slack.)
|
|
56
|
+
|
|
57
|
+
## Security — allowlist on the sending lane
|
|
58
|
+
|
|
59
|
+
Access is gated by `LANE_ALLOWED_SENDERS` against **`meta.from_lane`** — the
|
|
60
|
+
**sending lane callsign**, not message content. Only the prev lane (forward) /
|
|
61
|
+
Test+Deploy (fail-back) / the human may inject into a given lane's door. A line
|
|
62
|
+
from a disallowed lane is **dropped silently** (logged to stderr). An empty
|
|
63
|
+
allowlist accepts any sender (dev only — set it in production).
|
|
64
|
+
|
|
65
|
+
Claude Code also treats injected `<channel>` content as untrusted **data, not
|
|
66
|
+
commands** (won't auto-obey), and gates outbound. For unattended `send_to_lane`,
|
|
67
|
+
add `mcp__claude-channel__send_to_lane` to the session's `allowedTools`.
|
|
68
|
+
|
|
69
|
+
## Configuration (env-first, NO tokens)
|
|
70
|
+
|
|
71
|
+
| Env var | Default | Notes |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| `LANE_NAME` | `sidedoor` | This lane's callsign. Also names its inbox file. |
|
|
74
|
+
| `LANE_INBOX_DIR` | `/tmp` | Directory holding the `chan-<lane>-in.jsonl` inboxes. |
|
|
75
|
+
| `LANE_ALLOWED_SENDERS` | _(empty = open)_ | Comma/space-separated callsigns allowed to inject (matched on `meta.from_lane`). |
|
|
76
|
+
|
|
77
|
+
## Launch (consumer side, per tab)
|
|
78
|
+
|
|
79
|
+
Custom/dev channel servers require the dangerous flag (plain `--channels` is
|
|
80
|
+
marketplace-only), with `channelsEnabled: true` in `~/.claude/settings.json`:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
claude --dangerously-load-development-channels server:claude-channel
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`.mcp.json` (one per lane tab — note the per-lane `LANE_NAME`):
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"mcpServers": {
|
|
91
|
+
"claude-channel": {
|
|
92
|
+
"command": "npx",
|
|
93
|
+
"args": ["-y", "@chinchillaenterprises/mcp-claude-channel"],
|
|
94
|
+
"env": {
|
|
95
|
+
"LANE_NAME": "bravo",
|
|
96
|
+
"LANE_ALLOWED_SENDERS": "alpha,human"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Build
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm install
|
|
107
|
+
npm run build # tsc → dist/
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Published via the repo's OIDC `publish.yml`. Bump the version in **both**
|
|
111
|
+
`package.json` and `src/index.ts` (`VERSION`).
|
|
112
|
+
|
|
113
|
+
## Roadmap
|
|
114
|
+
|
|
115
|
+
- **v1 (this):** lane-to-lane nudges, file-inbox transport, no Slack.
|
|
116
|
+
- **v2 (deferred, not deleted):** the Slack-bridge half — one app posting a remote
|
|
117
|
+
readout when the human walks away. See ChillMCP issues #8 / #13.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* mcp-claude-channel — lane-to-lane Claude Code side-door channel server (v1).
|
|
4
|
+
*
|
|
5
|
+
* Echelon's "fused line": each PEP lane (R&D · Arch · Dev · Deploy · Test) is a
|
|
6
|
+
* Claude Code session in its own tab. This server automates the baton HANDOFF
|
|
7
|
+
* between tabs — replacing the human walking it lane-to-lane.
|
|
8
|
+
*
|
|
9
|
+
* Net-new to ChillMCP vs. the request/response servers:
|
|
10
|
+
* 1. Server-initiated notifications — emits `notifications/claude/channel`, a
|
|
11
|
+
* server→client push Claude Code injects into the live session as a new
|
|
12
|
+
* `<channel ...>` turn (the "doorbell").
|
|
13
|
+
* 2. A resident file-inbox tailer — watches this lane's inbox and turns each
|
|
14
|
+
* appended line into a push. Peer lanes write to it via `send_to_lane`.
|
|
15
|
+
*
|
|
16
|
+
* Wire model (no shared daemon, pure files — easy to reason about, mirrors the
|
|
17
|
+
* proven `labs/sidedoor-ref/lane-channel.py`):
|
|
18
|
+
* - INBOX = <LANE_INBOX_DIR>/chan-<LANE_NAME>-in.jsonl
|
|
19
|
+
* - this server tails its own INBOX and pushes each new line.
|
|
20
|
+
* - `send_to_lane(target, text, meta?)` appends a line to the TARGET's inbox.
|
|
21
|
+
*
|
|
22
|
+
* THE PRINCIPLE — the push is a NUDGE, not the payload. Content is a short
|
|
23
|
+
* doorbell ("you're next — round R<n>, baton at <path>, check the Board"). The
|
|
24
|
+
* SQLite Lane Board is the source of truth; the receiving lane reads the real
|
|
25
|
+
* baton from the Board, not from the push. (Kills dedup/replay/double-process.)
|
|
26
|
+
*
|
|
27
|
+
* Security: allowlist on the SENDING LANE callsign (`meta.from_lane`), via
|
|
28
|
+
* LANE_ALLOWED_SENDERS. Only the prev lane / fail-back lanes / the human may
|
|
29
|
+
* inject into a given lane's door. Provenance over content.
|
|
30
|
+
*
|
|
31
|
+
* v1 is Slack-free: NO Slack tokens are required to start.
|
|
32
|
+
*/
|
|
33
|
+
declare class ClaudeChannelServer {
|
|
34
|
+
private server;
|
|
35
|
+
private transport;
|
|
36
|
+
private config;
|
|
37
|
+
private offset;
|
|
38
|
+
private buffer;
|
|
39
|
+
private pollTimer;
|
|
40
|
+
constructor();
|
|
41
|
+
/**
|
|
42
|
+
* Emit a `notifications/claude/channel` push. Each `meta` key becomes an
|
|
43
|
+
* attribute on the `<channel ...>` tag the session sees. Try the SDK's typed
|
|
44
|
+
* `server.notification()` first; because `notifications/claude/channel` is not
|
|
45
|
+
* a method the SDK knows, that may fail schema validation, so fall back to
|
|
46
|
+
* writing the raw JSON-RPC line straight to the transport (proven shape).
|
|
47
|
+
*/
|
|
48
|
+
private pushToChannel;
|
|
49
|
+
/** Process one raw inbox line: parse, enforce the sender allowlist, push. */
|
|
50
|
+
private handleInboxLine;
|
|
51
|
+
/** Tail our own inbox file; push each newly-appended line. */
|
|
52
|
+
private startInboxTailer;
|
|
53
|
+
private sendToLane;
|
|
54
|
+
private setupHandlers;
|
|
55
|
+
run(): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
export { ClaudeChannelServer };
|
|
58
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAqEH,cAAM,mBAAmB;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,SAAS,CAA+C;;IAqBhE;;;;;;OAMG;YACW,aAAa;IAqB3B,6EAA6E;YAC/D,eAAe;IA8B7B,8DAA8D;IAC9D,OAAO,CAAC,gBAAgB;YAsDV,UAAU;IAwCxB,OAAO,CAAC,aAAa;IAgDf,GAAG;CAgBV;AAmBD,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* mcp-claude-channel — lane-to-lane Claude Code side-door channel server (v1).
|
|
4
|
+
*
|
|
5
|
+
* Echelon's "fused line": each PEP lane (R&D · Arch · Dev · Deploy · Test) is a
|
|
6
|
+
* Claude Code session in its own tab. This server automates the baton HANDOFF
|
|
7
|
+
* between tabs — replacing the human walking it lane-to-lane.
|
|
8
|
+
*
|
|
9
|
+
* Net-new to ChillMCP vs. the request/response servers:
|
|
10
|
+
* 1. Server-initiated notifications — emits `notifications/claude/channel`, a
|
|
11
|
+
* server→client push Claude Code injects into the live session as a new
|
|
12
|
+
* `<channel ...>` turn (the "doorbell").
|
|
13
|
+
* 2. A resident file-inbox tailer — watches this lane's inbox and turns each
|
|
14
|
+
* appended line into a push. Peer lanes write to it via `send_to_lane`.
|
|
15
|
+
*
|
|
16
|
+
* Wire model (no shared daemon, pure files — easy to reason about, mirrors the
|
|
17
|
+
* proven `labs/sidedoor-ref/lane-channel.py`):
|
|
18
|
+
* - INBOX = <LANE_INBOX_DIR>/chan-<LANE_NAME>-in.jsonl
|
|
19
|
+
* - this server tails its own INBOX and pushes each new line.
|
|
20
|
+
* - `send_to_lane(target, text, meta?)` appends a line to the TARGET's inbox.
|
|
21
|
+
*
|
|
22
|
+
* THE PRINCIPLE — the push is a NUDGE, not the payload. Content is a short
|
|
23
|
+
* doorbell ("you're next — round R<n>, baton at <path>, check the Board"). The
|
|
24
|
+
* SQLite Lane Board is the source of truth; the receiving lane reads the real
|
|
25
|
+
* baton from the Board, not from the push. (Kills dedup/replay/double-process.)
|
|
26
|
+
*
|
|
27
|
+
* Security: allowlist on the SENDING LANE callsign (`meta.from_lane`), via
|
|
28
|
+
* LANE_ALLOWED_SENDERS. Only the prev lane / fail-back lanes / the human may
|
|
29
|
+
* inject into a given lane's door. Provenance over content.
|
|
30
|
+
*
|
|
31
|
+
* v1 is Slack-free: NO Slack tokens are required to start.
|
|
32
|
+
*/
|
|
33
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
34
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
35
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
36
|
+
import { z } from "zod";
|
|
37
|
+
import { fileURLToPath } from "url";
|
|
38
|
+
import { realpathSync, existsSync, closeSync, openSync, statSync, readSync, writeSync } from "fs";
|
|
39
|
+
import { join } from "path";
|
|
40
|
+
const VERSION = "0.1.0";
|
|
41
|
+
function inboxPathFor(dir, lane) {
|
|
42
|
+
return join(dir, `chan-${lane}-in.jsonl`);
|
|
43
|
+
}
|
|
44
|
+
function loadConfigFromEnv() {
|
|
45
|
+
const lane = (process.env.LANE_NAME || "sidedoor").trim();
|
|
46
|
+
const inboxDir = process.env.LANE_INBOX_DIR || "/tmp";
|
|
47
|
+
// Comma/space separated sending-lane callsigns allowed to inject into THIS
|
|
48
|
+
// lane's door (matched against meta.from_lane). Empty = open (dev) + warning.
|
|
49
|
+
const allowedSenders = new Set((process.env.LANE_ALLOWED_SENDERS || "")
|
|
50
|
+
.split(/[,\s]+/)
|
|
51
|
+
.map((s) => s.trim())
|
|
52
|
+
.filter(Boolean));
|
|
53
|
+
return { lane, inboxDir, inboxPath: inboxPathFor(inboxDir, lane), allowedSenders };
|
|
54
|
+
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Tool schema
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
const SendToLaneArgsSchema = z.object({
|
|
59
|
+
target: z.string().min(1).describe("Callsign of the lane to nudge (e.g., bravo, arch, test)"),
|
|
60
|
+
text: z
|
|
61
|
+
.string()
|
|
62
|
+
.min(1)
|
|
63
|
+
.describe("The nudge — a SHORT doorbell, not the payload. e.g. 'you're next — round R7, baton at <path>, check the Board.'"),
|
|
64
|
+
meta: z
|
|
65
|
+
.record(z.any())
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Structured routing payload: round_id, stage, baton_path, to_lane, etc. from_lane is stamped automatically."),
|
|
68
|
+
});
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Server
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
class ClaudeChannelServer {
|
|
73
|
+
server;
|
|
74
|
+
transport = null;
|
|
75
|
+
config;
|
|
76
|
+
offset = 0;
|
|
77
|
+
buffer = "";
|
|
78
|
+
pollTimer = null;
|
|
79
|
+
constructor() {
|
|
80
|
+
this.server = new Server({ name: "mcp-claude-channel", version: VERSION }, {
|
|
81
|
+
capabilities: {
|
|
82
|
+
// The empty `claude/channel` object is the handshake badge that arms
|
|
83
|
+
// the receiving session's notification listener — THE thing that makes
|
|
84
|
+
// the side door work.
|
|
85
|
+
experimental: { "claude/channel": {} },
|
|
86
|
+
tools: {},
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
this.config = loadConfigFromEnv();
|
|
90
|
+
this.setupHandlers();
|
|
91
|
+
}
|
|
92
|
+
// --- inbound: push a peer-lane nudge into the live session ------------------
|
|
93
|
+
/**
|
|
94
|
+
* Emit a `notifications/claude/channel` push. Each `meta` key becomes an
|
|
95
|
+
* attribute on the `<channel ...>` tag the session sees. Try the SDK's typed
|
|
96
|
+
* `server.notification()` first; because `notifications/claude/channel` is not
|
|
97
|
+
* a method the SDK knows, that may fail schema validation, so fall back to
|
|
98
|
+
* writing the raw JSON-RPC line straight to the transport (proven shape).
|
|
99
|
+
*/
|
|
100
|
+
async pushToChannel(content, meta) {
|
|
101
|
+
const merged = { ...meta, source: this.config.lane };
|
|
102
|
+
const params = { content, meta: merged };
|
|
103
|
+
try {
|
|
104
|
+
await this.server.notification({
|
|
105
|
+
method: "notifications/claude/channel",
|
|
106
|
+
params,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
if (this.transport) {
|
|
111
|
+
await this.transport.send({
|
|
112
|
+
jsonrpc: "2.0",
|
|
113
|
+
method: "notifications/claude/channel",
|
|
114
|
+
params,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.error("[ClaudeChannel] No transport to push notification.");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Process one raw inbox line: parse, enforce the sender allowlist, push. */
|
|
123
|
+
async handleInboxLine(line) {
|
|
124
|
+
const trimmed = line.trim();
|
|
125
|
+
if (!trimmed)
|
|
126
|
+
return;
|
|
127
|
+
let content;
|
|
128
|
+
let meta;
|
|
129
|
+
try {
|
|
130
|
+
const obj = JSON.parse(trimmed);
|
|
131
|
+
content = typeof obj.content === "string" ? obj.content : trimmed;
|
|
132
|
+
meta = obj.meta && typeof obj.meta === "object" ? obj.meta : {};
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Tolerate a plain-text line (no JSON) — push as-is with empty meta.
|
|
136
|
+
content = trimmed;
|
|
137
|
+
meta = {};
|
|
138
|
+
}
|
|
139
|
+
// THE GATE: allowlist on the SENDING LANE callsign (meta.from_lane).
|
|
140
|
+
const fromLane = typeof meta.from_lane === "string" ? meta.from_lane : "";
|
|
141
|
+
if (this.config.allowedSenders.size > 0 && !this.config.allowedSenders.has(fromLane)) {
|
|
142
|
+
console.error(`[ClaudeChannel] dropped inbound from disallowed lane '${fromLane || "unknown"}' (allowed: ${[
|
|
143
|
+
...this.config.allowedSenders,
|
|
144
|
+
].join(",")})`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
await this.pushToChannel(content, meta);
|
|
148
|
+
}
|
|
149
|
+
/** Tail our own inbox file; push each newly-appended line. */
|
|
150
|
+
startInboxTailer() {
|
|
151
|
+
const path = this.config.inboxPath;
|
|
152
|
+
// Ensure the inbox exists, then start reading only NEW lines from here on.
|
|
153
|
+
if (!existsSync(path))
|
|
154
|
+
closeSync(openSync(path, "a"));
|
|
155
|
+
try {
|
|
156
|
+
this.offset = statSync(path).size;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
this.offset = 0;
|
|
160
|
+
}
|
|
161
|
+
const poll = () => {
|
|
162
|
+
let size;
|
|
163
|
+
try {
|
|
164
|
+
size = statSync(path).size;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return; // file vanished mid-flight; try again next tick
|
|
168
|
+
}
|
|
169
|
+
if (size < this.offset) {
|
|
170
|
+
// Truncated/rotated — start over from the top.
|
|
171
|
+
this.offset = 0;
|
|
172
|
+
this.buffer = "";
|
|
173
|
+
}
|
|
174
|
+
if (size === this.offset)
|
|
175
|
+
return;
|
|
176
|
+
const fd = openSync(path, "r");
|
|
177
|
+
try {
|
|
178
|
+
const len = size - this.offset;
|
|
179
|
+
const buf = Buffer.alloc(len);
|
|
180
|
+
const read = readSync(fd, buf, 0, len, this.offset);
|
|
181
|
+
this.offset += read;
|
|
182
|
+
this.buffer += buf.toString("utf8", 0, read);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
closeSync(fd);
|
|
186
|
+
}
|
|
187
|
+
let nl;
|
|
188
|
+
while ((nl = this.buffer.indexOf("\n")) !== -1) {
|
|
189
|
+
const line = this.buffer.slice(0, nl);
|
|
190
|
+
this.buffer = this.buffer.slice(nl + 1);
|
|
191
|
+
void this.handleInboxLine(line);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
this.pollTimer = setInterval(poll, 500);
|
|
195
|
+
console.error(`[ClaudeChannel] lane '${this.config.lane}' tailing inbox ${path}` +
|
|
196
|
+
(this.config.allowedSenders.size > 0
|
|
197
|
+
? ` — allowed senders: ${[...this.config.allowedSenders].join(",")}`
|
|
198
|
+
: " — WARNING: no LANE_ALLOWED_SENDERS set, accepting any sender"));
|
|
199
|
+
}
|
|
200
|
+
// --- outbound: nudge another lane ------------------------------------------
|
|
201
|
+
async sendToLane(args) {
|
|
202
|
+
const validated = SendToLaneArgsSchema.parse(args);
|
|
203
|
+
const targetPath = inboxPathFor(this.config.inboxDir, validated.target);
|
|
204
|
+
// Stamp provenance: from_lane is OURS (the gate on the receiving side keys on
|
|
205
|
+
// this), to_lane is the target. Caller-supplied meta wins for everything else.
|
|
206
|
+
const meta = {
|
|
207
|
+
to_lane: validated.target,
|
|
208
|
+
...(validated.meta || {}),
|
|
209
|
+
from_lane: this.config.lane,
|
|
210
|
+
};
|
|
211
|
+
const record = JSON.stringify({ content: validated.text, meta });
|
|
212
|
+
try {
|
|
213
|
+
const fd = openSync(targetPath, "a");
|
|
214
|
+
try {
|
|
215
|
+
writeSync(fd, record + "\n");
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
closeSync(fd);
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
content: [
|
|
222
|
+
{
|
|
223
|
+
type: "text",
|
|
224
|
+
text: JSON.stringify({ ok: true, delivered_to: validated.target, inbox: targetPath, from_lane: this.config.lane }, null, 2),
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
return {
|
|
231
|
+
content: [{ type: "text", text: `Error delivering to lane '${validated.target}': ${err?.message || err}` }],
|
|
232
|
+
isError: true,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// --- handlers ---------------------------------------------------------------
|
|
237
|
+
setupHandlers() {
|
|
238
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
239
|
+
const tools = [
|
|
240
|
+
{
|
|
241
|
+
name: "send_to_lane",
|
|
242
|
+
description: "Nudge ANOTHER lane's Claude Code session via the side door — the session→session handoff. Send a SHORT doorbell ('you're next — round R<n>, baton at <path>, check the Board'), NOT the payload: the SQLite Lane Board is the source of truth, the receiving lane reads the real baton from there. Carry routing in meta (round_id, stage, baton_path, to_lane). from_lane is stamped automatically for the receiver's allowlist.",
|
|
243
|
+
inputSchema: {
|
|
244
|
+
type: "object",
|
|
245
|
+
properties: {
|
|
246
|
+
target: {
|
|
247
|
+
type: "string",
|
|
248
|
+
description: "Callsign of the lane to nudge (e.g., bravo, arch, test)",
|
|
249
|
+
},
|
|
250
|
+
text: {
|
|
251
|
+
type: "string",
|
|
252
|
+
description: "The nudge — a short doorbell pointing at the Board, not the baton payload",
|
|
253
|
+
},
|
|
254
|
+
meta: {
|
|
255
|
+
type: "object",
|
|
256
|
+
description: "Structured routing: round_id, stage, baton_path, to_lane. from_lane is added automatically.",
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
required: ["target", "text"],
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
];
|
|
263
|
+
return { tools };
|
|
264
|
+
});
|
|
265
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
266
|
+
try {
|
|
267
|
+
const { name, arguments: args } = request.params;
|
|
268
|
+
switch (name) {
|
|
269
|
+
case "send_to_lane":
|
|
270
|
+
return await this.sendToLane(args);
|
|
271
|
+
default:
|
|
272
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
277
|
+
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
// --- boot -------------------------------------------------------------------
|
|
282
|
+
async run() {
|
|
283
|
+
this.transport = new StdioServerTransport();
|
|
284
|
+
await this.server.connect(this.transport);
|
|
285
|
+
console.error(`[ClaudeChannel] mcp-claude-channel v${VERSION} running on stdio — lane '${this.config.lane}'`);
|
|
286
|
+
// Start the tailer AFTER the transport is connected so the first push lands.
|
|
287
|
+
this.startInboxTailer();
|
|
288
|
+
const shutdown = () => {
|
|
289
|
+
if (this.pollTimer)
|
|
290
|
+
clearInterval(this.pollTimer);
|
|
291
|
+
process.exit(0);
|
|
292
|
+
};
|
|
293
|
+
process.once("SIGINT", shutdown);
|
|
294
|
+
process.once("SIGTERM", shutdown);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Boot guard — only run when invoked directly (mirrors mcp-slack).
|
|
298
|
+
const isMain = (() => {
|
|
299
|
+
const entry = process.argv[1];
|
|
300
|
+
if (!entry)
|
|
301
|
+
return false;
|
|
302
|
+
const self = fileURLToPath(import.meta.url);
|
|
303
|
+
try {
|
|
304
|
+
return realpathSync(entry) === realpathSync(self);
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
return entry === self;
|
|
308
|
+
}
|
|
309
|
+
})();
|
|
310
|
+
if (isMain) {
|
|
311
|
+
const server = new ClaudeChannelServer();
|
|
312
|
+
server.run().catch(console.error);
|
|
313
|
+
}
|
|
314
|
+
export { ClaudeChannelServer };
|
|
315
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUNBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E4Qkc7QUFFSCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sMkNBQTJDLENBQUM7QUFDbkUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sMkNBQTJDLENBQUM7QUFDakYsT0FBTyxFQUNMLHNCQUFzQixFQUN0QixxQkFBcUIsR0FFdEIsTUFBTSxvQ0FBb0MsQ0FBQztBQUM1QyxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBQ3hCLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFDcEMsT0FBTyxFQUFFLFlBQVksRUFBRSxVQUFVLEVBQUUsU0FBUyxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxNQUFNLElBQUksQ0FBQztBQUNsRyxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBRTVCLE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQztBQWF4QixTQUFTLFlBQVksQ0FBQyxHQUFXLEVBQUUsSUFBWTtJQUM3QyxPQUFPLElBQUksQ0FBQyxHQUFHLEVBQUUsUUFBUSxJQUFJLFdBQVcsQ0FBQyxDQUFDO0FBQzVDLENBQUM7QUFFRCxTQUFTLGlCQUFpQjtJQUN4QixNQUFNLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBUyxJQUFJLFVBQVUsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0lBQzFELE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsY0FBYyxJQUFJLE1BQU0sQ0FBQztJQUV0RCwyRUFBMkU7SUFDM0UsOEVBQThFO0lBQzlFLE1BQU0sY0FBYyxHQUFHLElBQUksR0FBRyxDQUM1QixDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsb0JB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
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chinchillaenterprises/mcp-claude-channel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lane-to-lane Claude Code side-door channel server. Pushes a NUDGE from a peer lane INTO a live Claude Code session via the official Channels notification (notifications/claude/channel) and exposes a send_to_lane tool to nudge the next lane. v1: session↔session only, no Slack. The SQLite Lane Board stays the source of truth; the push is a doorbell, not the payload.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-claude-channel": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"prepare": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"channels",
|
|
23
|
+
"lane-to-lane",
|
|
24
|
+
"side-door",
|
|
25
|
+
"notifications"
|
|
26
|
+
],
|
|
27
|
+
"author": "Chinchilla Enterprises",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.12.3",
|
|
34
|
+
"zod": "^3.25.64"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^24.0.1",
|
|
38
|
+
"typescript": "^5.8.3"
|
|
39
|
+
}
|
|
40
|
+
}
|