@particle-academy/agent-integrations 0.5.0 → 0.6.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.
@@ -0,0 +1,386 @@
1
+ import { randomBytes, createHash, timingSafeEqual } from 'crypto';
2
+ import { URL } from 'url';
3
+
4
+ // src/relay-server/core.ts
5
+ var MemoryStore = class {
6
+ constructor() {
7
+ this.sessions = /* @__PURE__ */ new Map();
8
+ }
9
+ putSession(s) {
10
+ this.sessions.set(s.id, s);
11
+ }
12
+ getSession(id) {
13
+ return this.sessions.get(id);
14
+ }
15
+ deleteSession(id) {
16
+ this.sessions.delete(id);
17
+ }
18
+ expiredSessionIds(cutoff) {
19
+ const out = [];
20
+ for (const [id, s] of this.sessions) if (s.lastSeen < cutoff) out.push(id);
21
+ return out;
22
+ }
23
+ };
24
+ var SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{4,64}$/;
25
+ var RelayBroker = class {
26
+ constructor(opts = {}) {
27
+ /** Per-session, per-direction subscriber list. */
28
+ this.subs = /* @__PURE__ */ new Map();
29
+ this.ttlMs = opts.ttlMs ?? 4 * 60 * 60 * 1e3;
30
+ this.store = opts.store ?? new MemoryStore();
31
+ const tick = opts.reapIntervalMs ?? 6e4;
32
+ if (tick > 0) {
33
+ this.reaper = setInterval(() => this.reap(), tick);
34
+ if (typeof this.reaper.unref === "function") {
35
+ this.reaper.unref();
36
+ }
37
+ }
38
+ }
39
+ dispose() {
40
+ if (this.reaper) clearInterval(this.reaper);
41
+ this.subs.clear();
42
+ }
43
+ /** Register a session id + token. Idempotent — same id+token re-registers,
44
+ * different token fails. */
45
+ register(id, token) {
46
+ if (!SESSION_ID_PATTERN.test(id)) return { ok: false, reason: "invalid_session_id" };
47
+ if (typeof token !== "string" || token.length < 16 || token.length > 128) {
48
+ return { ok: false, reason: "invalid_token" };
49
+ }
50
+ const existing = this.store.getSession(id);
51
+ const hash = sha256Hex(token);
52
+ if (existing) {
53
+ if (!timingSafeEqualHex(existing.tokenHash, hash)) return { ok: false, reason: "session_taken" };
54
+ existing.lastSeen = Date.now();
55
+ this.store.putSession(existing);
56
+ return { ok: true };
57
+ }
58
+ this.store.putSession({ id, tokenHash: hash, lastSeen: Date.now() });
59
+ return { ok: true };
60
+ }
61
+ unregister(id, token) {
62
+ if (!this.validate(id, token)) return false;
63
+ this.store.deleteSession(id);
64
+ this.subs.delete(id);
65
+ return true;
66
+ }
67
+ /** Validate an authenticated touch and slide the TTL forward. */
68
+ validate(id, token) {
69
+ if (!id || !token) return false;
70
+ const s = this.store.getSession(id);
71
+ if (!s) return false;
72
+ if (!timingSafeEqualHex(s.tokenHash, sha256Hex(token))) return false;
73
+ s.lastSeen = Date.now();
74
+ this.store.putSession(s);
75
+ return true;
76
+ }
77
+ /** Push a frame onto the inbound queue (external agent → browser). */
78
+ inbox(id, token, payload) {
79
+ if (!this.validate(id, token)) return false;
80
+ if (!this.isFrame(payload)) return false;
81
+ this.fanOut(id, "inbound", payload);
82
+ return true;
83
+ }
84
+ /** Push a frame onto the outbound queue (browser server → external agents). */
85
+ outbox(id, token, payload) {
86
+ if (!this.validate(id, token)) return false;
87
+ if (!this.isFrame(payload)) return false;
88
+ this.fanOut(id, "outbound", payload);
89
+ return true;
90
+ }
91
+ /**
92
+ * Subscribe to a session's queue for one direction. Returns an iterable
93
+ * the caller (an HTTP handler) pumps as SSE.
94
+ */
95
+ subscribe(id, token, direction) {
96
+ if (!this.validate(id, token)) return { ok: false, reason: "invalid_token" };
97
+ const subscriberId = randomBytes(8).toString("hex");
98
+ const subscriber = { id: subscriberId, direction, queue: [], resolveNext: null };
99
+ this.getDirSubs(id, direction).set(subscriberId, subscriber);
100
+ if (direction === "outbound") {
101
+ this.fanOut(
102
+ id,
103
+ "inbound",
104
+ JSON.stringify({
105
+ jsonrpc: "2.0",
106
+ method: "notifications/peer_joined",
107
+ params: { subscriberId, ts: Date.now() }
108
+ })
109
+ );
110
+ }
111
+ const unsubscribe = () => {
112
+ this.getDirSubs(id, direction).delete(subscriberId);
113
+ if (direction === "outbound") {
114
+ this.fanOut(
115
+ id,
116
+ "inbound",
117
+ JSON.stringify({
118
+ jsonrpc: "2.0",
119
+ method: "notifications/peer_left",
120
+ params: { subscriberId, ts: Date.now() }
121
+ })
122
+ );
123
+ }
124
+ };
125
+ const frames = async function* () {
126
+ while (true) {
127
+ if (this.queue.length > 0) {
128
+ const next2 = this.queue.shift();
129
+ if (next2 !== void 0) yield next2;
130
+ continue;
131
+ }
132
+ const next = await new Promise((resolve) => {
133
+ this.resolveNext = resolve;
134
+ });
135
+ this.resolveNext = null;
136
+ if (next === null) return;
137
+ yield next;
138
+ }
139
+ }.bind(subscriber);
140
+ return {
141
+ ok: true,
142
+ subscriberId,
143
+ frames: frames(),
144
+ unsubscribe: () => {
145
+ subscriber.resolveNext?.(null);
146
+ unsubscribe();
147
+ }
148
+ };
149
+ }
150
+ // ────────────────────────────────────────────────────────────── internals
151
+ getDirSubs(sessionId, direction) {
152
+ let bySession = this.subs.get(sessionId);
153
+ if (!bySession) {
154
+ bySession = /* @__PURE__ */ new Map();
155
+ this.subs.set(sessionId, bySession);
156
+ }
157
+ let byDir = bySession.get(direction);
158
+ if (!byDir) {
159
+ byDir = /* @__PURE__ */ new Map();
160
+ bySession.set(direction, byDir);
161
+ }
162
+ return byDir;
163
+ }
164
+ fanOut(sessionId, direction, payload) {
165
+ const dir = this.subs.get(sessionId)?.get(direction);
166
+ if (!dir) return;
167
+ for (const sub of dir.values()) {
168
+ sub.queue.push(payload);
169
+ sub.resolveNext?.(sub.queue.shift() ?? null);
170
+ }
171
+ }
172
+ isFrame(payload) {
173
+ return payload.length > 0 && payload.includes('"jsonrpc"');
174
+ }
175
+ reap() {
176
+ const cutoff = Date.now() - this.ttlMs;
177
+ for (const id of this.store.expiredSessionIds(cutoff)) {
178
+ const dirs = this.subs.get(id);
179
+ if (dirs) {
180
+ for (const dir of dirs.values()) {
181
+ for (const sub of dir.values()) sub.resolveNext?.(null);
182
+ }
183
+ }
184
+ this.subs.delete(id);
185
+ this.store.deleteSession(id);
186
+ }
187
+ }
188
+ };
189
+ function sha256Hex(input) {
190
+ return createHash("sha256").update(input).digest("hex");
191
+ }
192
+ function timingSafeEqualHex(a, b) {
193
+ if (a.length !== b.length) return false;
194
+ return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
195
+ }
196
+ function createNodeRelay(opts = {}) {
197
+ const broker = new RelayBroker(opts);
198
+ const prefix = (opts.pathPrefix ?? "").replace(/\/$/, "");
199
+ const cors = opts.corsAllowOrigin ?? "*";
200
+ function setCorsHeaders(res) {
201
+ res.setHeader("access-control-allow-origin", cors);
202
+ res.setHeader("access-control-allow-methods", "GET, POST, OPTIONS");
203
+ res.setHeader("access-control-allow-headers", "content-type, x-csrf-token, accept");
204
+ res.setHeader("access-control-max-age", "86400");
205
+ }
206
+ function readBody(req) {
207
+ return new Promise((resolve, reject) => {
208
+ const chunks = [];
209
+ let bytes = 0;
210
+ req.on("data", (chunk) => {
211
+ bytes += chunk.length;
212
+ if (bytes > 256 * 1024) {
213
+ reject(new Error("payload_too_large"));
214
+ req.destroy();
215
+ return;
216
+ }
217
+ chunks.push(chunk);
218
+ });
219
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
220
+ req.on("error", reject);
221
+ });
222
+ }
223
+ function json(res, status, body) {
224
+ res.statusCode = status;
225
+ res.setHeader("content-type", "application/json");
226
+ res.end(JSON.stringify(body));
227
+ }
228
+ function getQuery(req) {
229
+ const host = req.headers.host || "x";
230
+ const u = new URL(req.url || "/", `http://${host}`);
231
+ return u.searchParams;
232
+ }
233
+ function getPathname(req) {
234
+ const host = req.headers.host || "x";
235
+ const u = new URL(req.url || "/", `http://${host}`);
236
+ return u.pathname;
237
+ }
238
+ const register = async (req, res) => {
239
+ setCorsHeaders(res);
240
+ if (req.method === "OPTIONS") {
241
+ res.statusCode = 204;
242
+ return res.end();
243
+ }
244
+ if (req.method !== "POST") return json(res, 405, { error: "method_not_allowed" });
245
+ let body;
246
+ try {
247
+ body = await readBody(req);
248
+ } catch (e) {
249
+ return json(res, 413, { error: e instanceof Error ? e.message : "payload_error" });
250
+ }
251
+ let parsed;
252
+ try {
253
+ parsed = JSON.parse(body);
254
+ } catch {
255
+ return json(res, 400, { error: "invalid_json" });
256
+ }
257
+ const { session, token } = parsed ?? {};
258
+ if (typeof session !== "string" || typeof token !== "string") {
259
+ return json(res, 400, { error: "missing_fields" });
260
+ }
261
+ const result = broker.register(session, token);
262
+ if (!result.ok) return json(res, 401, { error: result.reason });
263
+ return json(res, 200, { ok: true });
264
+ };
265
+ function makeSessionHandler(direction) {
266
+ return async (req, res) => {
267
+ setCorsHeaders(res);
268
+ if (req.method === "OPTIONS") {
269
+ res.statusCode = 204;
270
+ return res.end();
271
+ }
272
+ if (req.method !== "POST") return json(res, 405, { error: "method_not_allowed" });
273
+ const session = extractSession(req, prefix);
274
+ if (!session) return json(res, 400, { error: "missing_session" });
275
+ const token = getQuery(req).get("token") ?? "";
276
+ if (direction === "unregister") {
277
+ const ok2 = broker.unregister(session, token);
278
+ return json(res, ok2 ? 200 : 401, ok2 ? { ok: true } : { error: "invalid_token" });
279
+ }
280
+ let body;
281
+ try {
282
+ body = await readBody(req);
283
+ } catch (e) {
284
+ return json(res, 413, { error: e instanceof Error ? e.message : "payload_error" });
285
+ }
286
+ const ok = direction === "inbound" ? broker.inbox(session, token, body) : broker.outbox(session, token, body);
287
+ return json(res, ok ? 200 : 401, ok ? { ok: true } : { error: "invalid_token_or_frame" });
288
+ };
289
+ }
290
+ const inbox = makeSessionHandler("inbound");
291
+ const outbox = makeSessionHandler("outbound");
292
+ const unregister = makeSessionHandler("unregister");
293
+ const events = async (req, res) => {
294
+ setCorsHeaders(res);
295
+ if (req.method === "OPTIONS") {
296
+ res.statusCode = 204;
297
+ return res.end();
298
+ }
299
+ if (req.method !== "GET") return json(res, 405, { error: "method_not_allowed" });
300
+ const session = extractSession(req, prefix);
301
+ if (!session) return json(res, 400, { error: "missing_session" });
302
+ const q = getQuery(req);
303
+ const token = q.get("token") ?? "";
304
+ const direction = q.get("direction") === "outbound" ? "outbound" : "inbound";
305
+ const sub = broker.subscribe(session, token, direction);
306
+ if (!sub.ok) {
307
+ res.statusCode = 401;
308
+ res.setHeader("content-type", "text/event-stream");
309
+ res.write(`event: error
310
+ data: ${sub.reason}
311
+
312
+ `);
313
+ return res.end();
314
+ }
315
+ res.statusCode = 200;
316
+ res.setHeader("content-type", "text/event-stream");
317
+ res.setHeader("cache-control", "no-cache");
318
+ res.setHeader("connection", "keep-alive");
319
+ res.setHeader("x-accel-buffering", "no");
320
+ res.write("retry: 2000\n\n");
321
+ flush(res);
322
+ let heartbeat = setInterval(() => {
323
+ res.write(": keepalive\n\n");
324
+ flush(res);
325
+ }, 15e3);
326
+ if (heartbeat && typeof heartbeat.unref === "function") {
327
+ heartbeat.unref();
328
+ }
329
+ const cleanup = () => {
330
+ if (heartbeat) {
331
+ clearInterval(heartbeat);
332
+ heartbeat = null;
333
+ }
334
+ sub.unsubscribe();
335
+ };
336
+ req.on("close", cleanup);
337
+ req.on("error", cleanup);
338
+ try {
339
+ for await (const frame of sub.frames) {
340
+ res.write(`event: mcp
341
+ data: ${frame}
342
+
343
+ `);
344
+ flush(res);
345
+ }
346
+ } catch {
347
+ } finally {
348
+ cleanup();
349
+ res.end();
350
+ }
351
+ };
352
+ const handler = async (req, res) => {
353
+ const pathname = getPathname(req);
354
+ if (!pathname.startsWith(prefix + "/")) {
355
+ return json(res, 404, { error: "not_found" });
356
+ }
357
+ const rest = pathname.slice(prefix.length);
358
+ if (rest === "/register") return register(req, res);
359
+ const m = /^\/([A-Za-z0-9_-]{4,64})\/(inbox|outbox|events|unregister)$/.exec(rest);
360
+ if (!m) return json(res, 404, { error: "not_found" });
361
+ const route = m[2];
362
+ if (route === "inbox") return inbox(req, res);
363
+ if (route === "outbox") return outbox(req, res);
364
+ if (route === "events") return events(req, res);
365
+ if (route === "unregister") return unregister(req, res);
366
+ return json(res, 404, { error: "not_found" });
367
+ };
368
+ return { broker, handler, register, inbox, outbox, events, unregister };
369
+ }
370
+ function extractSession(req, prefix) {
371
+ const host = req.headers.host || "x";
372
+ const u = new URL(req.url || "/", `http://${host}`);
373
+ const path = u.pathname;
374
+ if (prefix && !path.startsWith(prefix + "/")) return null;
375
+ const rest = prefix ? path.slice(prefix.length) : path;
376
+ const m = /^\/([A-Za-z0-9_-]{4,64})\//.exec(rest);
377
+ return m ? m[1] : null;
378
+ }
379
+ function flush(res) {
380
+ const r = res;
381
+ r.flush?.();
382
+ }
383
+
384
+ export { RelayBroker, createNodeRelay };
385
+ //# sourceMappingURL=chunk-OIX2ANFS.js.map
386
+ //# sourceMappingURL=chunk-OIX2ANFS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/relay-server/core.ts","../src/relay-server/node.ts"],"names":["next","ok"],"mappings":"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","file":"chunk-OIX2ANFS.js","sourcesContent":["import { createHash, randomBytes, timingSafeEqual } from \"node:crypto\";\n\n/**\n * RelayBroker — pure logic for the SSE+POST tunnel described in\n * docs/relay-protocol.md, hostable in any Node-compatible runtime\n * (Node, Bun, Deno-with-Node-compat, Cloudflare Workers via the Web\n * standards subset). No HTTP framework opinions; this class just\n * stores sessions, validates tokens, enqueues frames, and produces\n * SSE event payloads ready to flush.\n *\n * const broker = new RelayBroker();\n * const reg = broker.register(\"session-id\", \"token\"); // ok / error\n * broker.inbox(\"session-id\", \"token\", '{\"jsonrpc\":\"2.0\",…}'); // enqueue inbound\n * const sub = broker.subscribe(\"session-id\", \"token\", \"inbound\");\n * for await (const payload of sub.frames()) yield encodeSse(payload);\n *\n * Storage is an in-memory Map by default — fine for a single relay\n * process. To run multiple instances behind a load balancer, swap\n * `MemoryStore` for a Redis-backed equivalent (same Store interface).\n */\n\nexport type Direction = \"inbound\" | \"outbound\";\n\nexport type Session = {\n id: string;\n /** SHA-256 hex of the original token. Compared with timing-safe equals. */\n tokenHash: string;\n /** Last touched (ms since epoch). Used for TTL cleanup. */\n lastSeen: number;\n};\n\nexport type Subscriber = {\n id: string;\n direction: Direction;\n queue: string[];\n resolveNext: ((frame: string | null) => void) | null;\n};\n\nexport type RelayBrokerOptions = {\n /** Sessions auto-expire after this many ms of inactivity. Default 4h. */\n ttlMs?: number;\n /** Cleanup tick interval in ms. Default 60 000. */\n reapIntervalMs?: number;\n /** Bring-your-own storage layer (redis, etc.). Defaults to in-memory. */\n store?: Store;\n};\n\nexport interface Store {\n putSession(s: Session): void;\n getSession(id: string): Session | undefined;\n deleteSession(id: string): void;\n /** Used by the reap tick — return ids whose lastSeen < cutoff. */\n expiredSessionIds(cutoff: number): string[];\n}\n\nclass MemoryStore implements Store {\n private sessions = new Map<string, Session>();\n putSession(s: Session) { this.sessions.set(s.id, s); }\n getSession(id: string) { return this.sessions.get(id); }\n deleteSession(id: string) { this.sessions.delete(id); }\n expiredSessionIds(cutoff: number) {\n const out: string[] = [];\n for (const [id, s] of this.sessions) if (s.lastSeen < cutoff) out.push(id);\n return out;\n }\n}\n\nconst SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{4,64}$/;\n\nexport class RelayBroker {\n private readonly ttlMs: number;\n private readonly store: Store;\n /** Per-session, per-direction subscriber list. */\n private subs: Map<string, Map<string, Map<string, Subscriber>>> = new Map();\n private reaper?: ReturnType<typeof setInterval>;\n\n constructor(opts: RelayBrokerOptions = {}) {\n this.ttlMs = opts.ttlMs ?? 4 * 60 * 60 * 1000; // 4h\n this.store = opts.store ?? new MemoryStore();\n const tick = opts.reapIntervalMs ?? 60_000;\n if (tick > 0) {\n this.reaper = setInterval(() => this.reap(), tick);\n // Don't keep the process alive just for the reaper.\n if (typeof (this.reaper as { unref?: () => void }).unref === \"function\") {\n (this.reaper as { unref: () => void }).unref();\n }\n }\n }\n\n dispose() {\n if (this.reaper) clearInterval(this.reaper);\n this.subs.clear();\n }\n\n /** Register a session id + token. Idempotent — same id+token re-registers,\n * different token fails. */\n register(id: string, token: string): { ok: true } | { ok: false; reason: string } {\n if (!SESSION_ID_PATTERN.test(id)) return { ok: false, reason: \"invalid_session_id\" };\n if (typeof token !== \"string\" || token.length < 16 || token.length > 128) {\n return { ok: false, reason: \"invalid_token\" };\n }\n const existing = this.store.getSession(id);\n const hash = sha256Hex(token);\n if (existing) {\n if (!timingSafeEqualHex(existing.tokenHash, hash)) return { ok: false, reason: \"session_taken\" };\n existing.lastSeen = Date.now();\n this.store.putSession(existing);\n return { ok: true };\n }\n this.store.putSession({ id, tokenHash: hash, lastSeen: Date.now() });\n return { ok: true };\n }\n\n unregister(id: string, token: string): boolean {\n if (!this.validate(id, token)) return false;\n this.store.deleteSession(id);\n this.subs.delete(id);\n return true;\n }\n\n /** Validate an authenticated touch and slide the TTL forward. */\n validate(id: string, token: string): boolean {\n if (!id || !token) return false;\n const s = this.store.getSession(id);\n if (!s) return false;\n if (!timingSafeEqualHex(s.tokenHash, sha256Hex(token))) return false;\n s.lastSeen = Date.now();\n this.store.putSession(s);\n return true;\n }\n\n /** Push a frame onto the inbound queue (external agent → browser). */\n inbox(id: string, token: string, payload: string): boolean {\n if (!this.validate(id, token)) return false;\n if (!this.isFrame(payload)) return false;\n this.fanOut(id, \"inbound\", payload);\n return true;\n }\n\n /** Push a frame onto the outbound queue (browser server → external agents). */\n outbox(id: string, token: string, payload: string): boolean {\n if (!this.validate(id, token)) return false;\n if (!this.isFrame(payload)) return false;\n this.fanOut(id, \"outbound\", payload);\n return true;\n }\n\n /**\n * Subscribe to a session's queue for one direction. Returns an iterable\n * the caller (an HTTP handler) pumps as SSE.\n */\n subscribe(id: string, token: string, direction: Direction): SubscribeResult {\n if (!this.validate(id, token)) return { ok: false, reason: \"invalid_token\" };\n const subscriberId = randomBytes(8).toString(\"hex\");\n const subscriber: Subscriber = { id: subscriberId, direction, queue: [], resolveNext: null };\n this.getDirSubs(id, direction).set(subscriberId, subscriber);\n\n // Notify the inbound side (= browser) that an outbound subscriber\n // (= external agent) just connected.\n if (direction === \"outbound\") {\n this.fanOut(\n id,\n \"inbound\",\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"notifications/peer_joined\",\n params: { subscriberId, ts: Date.now() },\n }),\n );\n }\n\n const unsubscribe = () => {\n this.getDirSubs(id, direction).delete(subscriberId);\n if (direction === \"outbound\") {\n this.fanOut(\n id,\n \"inbound\",\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"notifications/peer_left\",\n params: { subscriberId, ts: Date.now() },\n }),\n );\n }\n };\n\n /** Async generator the HTTP handler drains. Yields raw frame payloads;\n * the handler is responsible for SSE framing (`event: mcp\\ndata: …`). */\n const frames = async function* (this: Subscriber): AsyncGenerator<string, void, void> {\n while (true) {\n if (this.queue.length > 0) {\n const next = this.queue.shift();\n if (next !== undefined) yield next;\n continue;\n }\n const next = await new Promise<string | null>((resolve) => {\n this.resolveNext = resolve;\n });\n this.resolveNext = null;\n if (next === null) return;\n yield next;\n }\n }.bind(subscriber);\n\n return {\n ok: true,\n subscriberId,\n frames: frames(),\n unsubscribe: () => {\n // Wake the generator and let it return cleanly.\n subscriber.resolveNext?.(null);\n unsubscribe();\n },\n };\n }\n\n // ────────────────────────────────────────────────────────────── internals\n\n private getDirSubs(sessionId: string, direction: Direction): Map<string, Subscriber> {\n let bySession = this.subs.get(sessionId);\n if (!bySession) {\n bySession = new Map();\n this.subs.set(sessionId, bySession);\n }\n let byDir = bySession.get(direction);\n if (!byDir) {\n byDir = new Map();\n bySession.set(direction, byDir);\n }\n return byDir;\n }\n\n private fanOut(sessionId: string, direction: Direction, payload: string) {\n const dir = this.subs.get(sessionId)?.get(direction);\n if (!dir) return;\n for (const sub of dir.values()) {\n sub.queue.push(payload);\n sub.resolveNext?.(sub.queue.shift() ?? null);\n }\n }\n\n private isFrame(payload: string): boolean {\n return payload.length > 0 && payload.includes('\"jsonrpc\"');\n }\n\n private reap() {\n const cutoff = Date.now() - this.ttlMs;\n for (const id of this.store.expiredSessionIds(cutoff)) {\n const dirs = this.subs.get(id);\n if (dirs) {\n for (const dir of dirs.values()) {\n for (const sub of dir.values()) sub.resolveNext?.(null);\n }\n }\n this.subs.delete(id);\n this.store.deleteSession(id);\n }\n }\n}\n\nexport type SubscribeResult =\n | { ok: false; reason: string }\n | {\n ok: true;\n subscriberId: string;\n frames: AsyncGenerator<string, void, void>;\n unsubscribe: () => void;\n };\n\nfunction sha256Hex(input: string): string {\n return createHash(\"sha256\").update(input).digest(\"hex\");\n}\n\nfunction timingSafeEqualHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n return timingSafeEqual(Buffer.from(a, \"hex\"), Buffer.from(b, \"hex\"));\n}\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { URL } from \"node:url\";\nimport { RelayBroker, type RelayBrokerOptions } from \"./core\";\n\n/**\n * Node HTTP adapter for {@link RelayBroker}. Returns a single request\n * handler plus per-route handlers, so you can either drop it into\n * `http.createServer(...)` directly or mount the individual handlers\n * onto your existing Node HTTP framework (Express, Hono w/ node-adapter,\n * native http).\n *\n * const relay = createNodeRelay({ pathPrefix: \"/mcp-relay\" });\n * http.createServer(relay.handler).listen(8787);\n *\n * // Or piecemeal:\n * app.post(\"/mcp-relay/register\", relay.register);\n * app.post(\"/mcp-relay/:s/inbox\", relay.inbox);\n * app.post(\"/mcp-relay/:s/outbox\", relay.outbox);\n * app.get (\"/mcp-relay/:s/events\", relay.events);\n * app.post(\"/mcp-relay/:s/unregister\", relay.unregister);\n */\n\nexport type NodeRelayOptions = RelayBrokerOptions & {\n /** URL path prefix (without trailing slash). Default `\"\"` — handlers\n * expect paths like `/register`, `/{id}/inbox`, etc. directly. */\n pathPrefix?: string;\n /** Comma-separated origins (or `*`) for CORS. Default `*` — relays\n * are typically called cross-origin from the demo host. */\n corsAllowOrigin?: string;\n};\n\nexport type NodeHandler = (req: IncomingMessage, res: ServerResponse) => unknown | Promise<unknown>;\n\nexport type NodeRelay = {\n broker: RelayBroker;\n /** Single-handler shape — routes internally based on method + URL. */\n handler: NodeHandler;\n /** Per-route handlers. Each handler ignores the URL prefix and\n * acts on the path remainder, so you can mount them under any\n * prefix in your existing app. */\n register: NodeHandler;\n inbox: NodeHandler;\n outbox: NodeHandler;\n events: NodeHandler;\n unregister: NodeHandler;\n};\n\nexport function createNodeRelay(opts: NodeRelayOptions = {}): NodeRelay {\n const broker = new RelayBroker(opts);\n const prefix = (opts.pathPrefix ?? \"\").replace(/\\/$/, \"\");\n const cors = opts.corsAllowOrigin ?? \"*\";\n\n function setCorsHeaders(res: ServerResponse) {\n res.setHeader(\"access-control-allow-origin\", cors);\n res.setHeader(\"access-control-allow-methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"access-control-allow-headers\", \"content-type, x-csrf-token, accept\");\n res.setHeader(\"access-control-max-age\", \"86400\");\n }\n\n function readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let bytes = 0;\n req.on(\"data\", (chunk: Buffer) => {\n bytes += chunk.length;\n // Cap individual frames at 256 KB — protect against runaway payloads.\n if (bytes > 256 * 1024) {\n reject(new Error(\"payload_too_large\"));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf8\")));\n req.on(\"error\", reject);\n });\n }\n\n function json(res: ServerResponse, status: number, body: unknown): void {\n res.statusCode = status;\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify(body));\n }\n\n function getQuery(req: IncomingMessage): URLSearchParams {\n const host = req.headers.host || \"x\";\n const u = new URL(req.url || \"/\", `http://${host}`);\n return u.searchParams;\n }\n\n function getPathname(req: IncomingMessage): string {\n const host = req.headers.host || \"x\";\n const u = new URL(req.url || \"/\", `http://${host}`);\n return u.pathname;\n }\n\n const register: NodeHandler = async (req, res) => {\n setCorsHeaders(res);\n if (req.method === \"OPTIONS\") { res.statusCode = 204; return res.end(); }\n if (req.method !== \"POST\") return json(res, 405, { error: \"method_not_allowed\" });\n let body: string;\n try { body = await readBody(req); } catch (e) {\n return json(res, 413, { error: e instanceof Error ? e.message : \"payload_error\" });\n }\n let parsed: unknown;\n try { parsed = JSON.parse(body); } catch { return json(res, 400, { error: \"invalid_json\" }); }\n const { session, token } = (parsed ?? {}) as { session?: string; token?: string };\n if (typeof session !== \"string\" || typeof token !== \"string\") {\n return json(res, 400, { error: \"missing_fields\" });\n }\n const result = broker.register(session, token);\n if (!result.ok) return json(res, 401, { error: result.reason });\n return json(res, 200, { ok: true });\n };\n\n /** Handler for endpoints with a `{session}` segment. The path matcher\n * caller passes the session id explicitly so this works mounted under\n * any route shape. */\n function makeSessionHandler(\n direction: Direction | \"unregister\",\n ): NodeHandler {\n return async (req, res) => {\n setCorsHeaders(res);\n if (req.method === \"OPTIONS\") { res.statusCode = 204; return res.end(); }\n if (req.method !== \"POST\") return json(res, 405, { error: \"method_not_allowed\" });\n const session = extractSession(req, prefix);\n if (!session) return json(res, 400, { error: \"missing_session\" });\n const token = getQuery(req).get(\"token\") ?? \"\";\n\n if (direction === \"unregister\") {\n const ok = broker.unregister(session, token);\n return json(res, ok ? 200 : 401, ok ? { ok: true } : { error: \"invalid_token\" });\n }\n\n let body: string;\n try { body = await readBody(req); } catch (e) {\n return json(res, 413, { error: e instanceof Error ? e.message : \"payload_error\" });\n }\n const ok = direction === \"inbound\"\n ? broker.inbox(session, token, body)\n : broker.outbox(session, token, body);\n return json(res, ok ? 200 : 401, ok ? { ok: true } : { error: \"invalid_token_or_frame\" });\n };\n }\n\n const inbox = makeSessionHandler(\"inbound\");\n const outbox = makeSessionHandler(\"outbound\");\n const unregister = makeSessionHandler(\"unregister\");\n\n const events: NodeHandler = async (req, res) => {\n setCorsHeaders(res);\n if (req.method === \"OPTIONS\") { res.statusCode = 204; return res.end(); }\n if (req.method !== \"GET\") return json(res, 405, { error: \"method_not_allowed\" });\n const session = extractSession(req, prefix);\n if (!session) return json(res, 400, { error: \"missing_session\" });\n const q = getQuery(req);\n const token = q.get(\"token\") ?? \"\";\n const direction = q.get(\"direction\") === \"outbound\" ? \"outbound\" : \"inbound\";\n\n const sub = broker.subscribe(session, token, direction);\n if (!sub.ok) {\n res.statusCode = 401;\n res.setHeader(\"content-type\", \"text/event-stream\");\n res.write(`event: error\\ndata: ${sub.reason}\\n\\n`);\n return res.end();\n }\n\n res.statusCode = 200;\n res.setHeader(\"content-type\", \"text/event-stream\");\n res.setHeader(\"cache-control\", \"no-cache\");\n res.setHeader(\"connection\", \"keep-alive\");\n res.setHeader(\"x-accel-buffering\", \"no\");\n res.write(\"retry: 2000\\n\\n\");\n flush(res);\n\n let heartbeat: ReturnType<typeof setInterval> | null = setInterval(() => {\n res.write(\": keepalive\\n\\n\");\n flush(res);\n }, 15_000);\n if (heartbeat && typeof (heartbeat as { unref?: () => void }).unref === \"function\") {\n (heartbeat as { unref: () => void }).unref();\n }\n\n const cleanup = () => {\n if (heartbeat) { clearInterval(heartbeat); heartbeat = null; }\n sub.unsubscribe();\n };\n req.on(\"close\", cleanup);\n req.on(\"error\", cleanup);\n\n try {\n for await (const frame of sub.frames) {\n res.write(`event: mcp\\ndata: ${frame}\\n\\n`);\n flush(res);\n }\n } catch {\n /* stream ended */\n } finally {\n cleanup();\n res.end();\n }\n };\n\n /**\n * Single handler — routes based on method + path. Useful for mounting\n * via `http.createServer(relay.handler)` without an Express layer.\n */\n const handler: NodeHandler = async (req, res) => {\n const pathname = getPathname(req);\n if (!pathname.startsWith(prefix + \"/\")) {\n return json(res, 404, { error: \"not_found\" });\n }\n const rest = pathname.slice(prefix.length); // \"/register\", \"/<id>/inbox\", etc.\n if (rest === \"/register\") return register(req, res);\n const m = /^\\/([A-Za-z0-9_-]{4,64})\\/(inbox|outbox|events|unregister)$/.exec(rest);\n if (!m) return json(res, 404, { error: \"not_found\" });\n const route = m[2];\n if (route === \"inbox\") return inbox(req, res);\n if (route === \"outbox\") return outbox(req, res);\n if (route === \"events\") return events(req, res);\n if (route === \"unregister\") return unregister(req, res);\n return json(res, 404, { error: \"not_found\" });\n };\n\n return { broker, handler, register, inbox, outbox, events, unregister };\n}\n\nfunction extractSession(req: IncomingMessage, prefix: string): string | null {\n const host = req.headers.host || \"x\";\n const u = new URL(req.url || \"/\", `http://${host}`);\n const path = u.pathname;\n if (prefix && !path.startsWith(prefix + \"/\")) return null;\n const rest = prefix ? path.slice(prefix.length) : path;\n const m = /^\\/([A-Za-z0-9_-]{4,64})\\//.exec(rest);\n return m ? m[1] : null;\n}\n\nfunction flush(res: ServerResponse) {\n // Node doesn't expose explicit flush, but write returns false when buffered;\n // explicit flushHeaders + write is enough for SSE in practice.\n const r = res as { flush?: () => void };\n r.flush?.();\n}\n\ntype Direction = \"inbound\" | \"outbound\";\n"]}
@@ -0,0 +1,134 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+
3
+ /**
4
+ * RelayBroker — pure logic for the SSE+POST tunnel described in
5
+ * docs/relay-protocol.md, hostable in any Node-compatible runtime
6
+ * (Node, Bun, Deno-with-Node-compat, Cloudflare Workers via the Web
7
+ * standards subset). No HTTP framework opinions; this class just
8
+ * stores sessions, validates tokens, enqueues frames, and produces
9
+ * SSE event payloads ready to flush.
10
+ *
11
+ * const broker = new RelayBroker();
12
+ * const reg = broker.register("session-id", "token"); // ok / error
13
+ * broker.inbox("session-id", "token", '{"jsonrpc":"2.0",…}'); // enqueue inbound
14
+ * const sub = broker.subscribe("session-id", "token", "inbound");
15
+ * for await (const payload of sub.frames()) yield encodeSse(payload);
16
+ *
17
+ * Storage is an in-memory Map by default — fine for a single relay
18
+ * process. To run multiple instances behind a load balancer, swap
19
+ * `MemoryStore` for a Redis-backed equivalent (same Store interface).
20
+ */
21
+ type Direction = "inbound" | "outbound";
22
+ type Session = {
23
+ id: string;
24
+ /** SHA-256 hex of the original token. Compared with timing-safe equals. */
25
+ tokenHash: string;
26
+ /** Last touched (ms since epoch). Used for TTL cleanup. */
27
+ lastSeen: number;
28
+ };
29
+ type Subscriber = {
30
+ id: string;
31
+ direction: Direction;
32
+ queue: string[];
33
+ resolveNext: ((frame: string | null) => void) | null;
34
+ };
35
+ type RelayBrokerOptions = {
36
+ /** Sessions auto-expire after this many ms of inactivity. Default 4h. */
37
+ ttlMs?: number;
38
+ /** Cleanup tick interval in ms. Default 60 000. */
39
+ reapIntervalMs?: number;
40
+ /** Bring-your-own storage layer (redis, etc.). Defaults to in-memory. */
41
+ store?: Store;
42
+ };
43
+ interface Store {
44
+ putSession(s: Session): void;
45
+ getSession(id: string): Session | undefined;
46
+ deleteSession(id: string): void;
47
+ /** Used by the reap tick — return ids whose lastSeen < cutoff. */
48
+ expiredSessionIds(cutoff: number): string[];
49
+ }
50
+ declare class RelayBroker {
51
+ private readonly ttlMs;
52
+ private readonly store;
53
+ /** Per-session, per-direction subscriber list. */
54
+ private subs;
55
+ private reaper?;
56
+ constructor(opts?: RelayBrokerOptions);
57
+ dispose(): void;
58
+ /** Register a session id + token. Idempotent — same id+token re-registers,
59
+ * different token fails. */
60
+ register(id: string, token: string): {
61
+ ok: true;
62
+ } | {
63
+ ok: false;
64
+ reason: string;
65
+ };
66
+ unregister(id: string, token: string): boolean;
67
+ /** Validate an authenticated touch and slide the TTL forward. */
68
+ validate(id: string, token: string): boolean;
69
+ /** Push a frame onto the inbound queue (external agent → browser). */
70
+ inbox(id: string, token: string, payload: string): boolean;
71
+ /** Push a frame onto the outbound queue (browser server → external agents). */
72
+ outbox(id: string, token: string, payload: string): boolean;
73
+ /**
74
+ * Subscribe to a session's queue for one direction. Returns an iterable
75
+ * the caller (an HTTP handler) pumps as SSE.
76
+ */
77
+ subscribe(id: string, token: string, direction: Direction): SubscribeResult;
78
+ private getDirSubs;
79
+ private fanOut;
80
+ private isFrame;
81
+ private reap;
82
+ }
83
+ type SubscribeResult = {
84
+ ok: false;
85
+ reason: string;
86
+ } | {
87
+ ok: true;
88
+ subscriberId: string;
89
+ frames: AsyncGenerator<string, void, void>;
90
+ unsubscribe: () => void;
91
+ };
92
+
93
+ /**
94
+ * Node HTTP adapter for {@link RelayBroker}. Returns a single request
95
+ * handler plus per-route handlers, so you can either drop it into
96
+ * `http.createServer(...)` directly or mount the individual handlers
97
+ * onto your existing Node HTTP framework (Express, Hono w/ node-adapter,
98
+ * native http).
99
+ *
100
+ * const relay = createNodeRelay({ pathPrefix: "/mcp-relay" });
101
+ * http.createServer(relay.handler).listen(8787);
102
+ *
103
+ * // Or piecemeal:
104
+ * app.post("/mcp-relay/register", relay.register);
105
+ * app.post("/mcp-relay/:s/inbox", relay.inbox);
106
+ * app.post("/mcp-relay/:s/outbox", relay.outbox);
107
+ * app.get ("/mcp-relay/:s/events", relay.events);
108
+ * app.post("/mcp-relay/:s/unregister", relay.unregister);
109
+ */
110
+ type NodeRelayOptions = RelayBrokerOptions & {
111
+ /** URL path prefix (without trailing slash). Default `""` — handlers
112
+ * expect paths like `/register`, `/{id}/inbox`, etc. directly. */
113
+ pathPrefix?: string;
114
+ /** Comma-separated origins (or `*`) for CORS. Default `*` — relays
115
+ * are typically called cross-origin from the demo host. */
116
+ corsAllowOrigin?: string;
117
+ };
118
+ type NodeHandler = (req: IncomingMessage, res: ServerResponse) => unknown | Promise<unknown>;
119
+ type NodeRelay = {
120
+ broker: RelayBroker;
121
+ /** Single-handler shape — routes internally based on method + URL. */
122
+ handler: NodeHandler;
123
+ /** Per-route handlers. Each handler ignores the URL prefix and
124
+ * acts on the path remainder, so you can mount them under any
125
+ * prefix in your existing app. */
126
+ register: NodeHandler;
127
+ inbox: NodeHandler;
128
+ outbox: NodeHandler;
129
+ events: NodeHandler;
130
+ unregister: NodeHandler;
131
+ };
132
+ declare function createNodeRelay(opts?: NodeRelayOptions): NodeRelay;
133
+
134
+ export { type Direction, type NodeHandler, type NodeRelay, type NodeRelayOptions, RelayBroker, type RelayBrokerOptions, type Session, type Store, type Subscriber, createNodeRelay };