@particle-academy/agent-integrations 0.29.0 → 0.30.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 +30 -1
- package/dist/bridges/files.d.cts +20 -1
- package/dist/bridges/files.d.ts +20 -1
- package/dist/bridges/forms.d.cts +22 -1
- package/dist/bridges/forms.d.ts +22 -1
- package/dist/bridges/terminal.d.cts +21 -1
- package/dist/bridges/terminal.d.ts +21 -1
- package/dist/bridges-artboard.cjs +1 -1
- package/dist/bridges-artboard.cjs.map +1 -1
- package/dist/bridges-artboard.js +1 -1
- package/dist/bridges-catalog.cjs +1 -1
- package/dist/bridges-catalog.cjs.map +1 -1
- package/dist/bridges-catalog.js +2 -2
- package/dist/bridges-charts.cjs +1 -1
- package/dist/bridges-charts.cjs.map +1 -1
- package/dist/bridges-charts.js +2 -2
- package/dist/bridges-cms.cjs +1 -1
- package/dist/bridges-cms.cjs.map +1 -1
- package/dist/bridges-cms.js +3 -3
- package/dist/bridges-code.cjs +38 -3
- package/dist/bridges-code.cjs.map +1 -1
- package/dist/bridges-code.js +2 -2
- package/dist/bridges-doc.cjs +1 -1
- package/dist/bridges-doc.cjs.map +1 -1
- package/dist/bridges-doc.js +2 -2
- package/dist/bridges-features.cjs +1 -1
- package/dist/bridges-features.cjs.map +1 -1
- package/dist/bridges-features.js +2 -2
- package/dist/bridges-files.cjs +29 -8
- package/dist/bridges-files.cjs.map +1 -1
- package/dist/bridges-files.js +2 -2
- package/dist/bridges-flow.cjs +1 -1
- package/dist/bridges-flow.cjs.map +1 -1
- package/dist/bridges-flow.js +1 -1
- package/dist/bridges-forms.cjs +12 -2
- package/dist/bridges-forms.cjs.map +1 -1
- package/dist/bridges-forms.js +2 -2
- package/dist/bridges-map.cjs +1 -1
- package/dist/bridges-map.cjs.map +1 -1
- package/dist/bridges-map.js +2 -2
- package/dist/bridges-scene.cjs +1 -1
- package/dist/bridges-scene.cjs.map +1 -1
- package/dist/bridges-scene.js +2 -2
- package/dist/bridges-screens.cjs +1 -1
- package/dist/bridges-screens.cjs.map +1 -1
- package/dist/bridges-screens.js +2 -2
- package/dist/bridges-sheets.cjs +1 -1
- package/dist/bridges-sheets.cjs.map +1 -1
- package/dist/bridges-sheets.js +2 -2
- package/dist/bridges-slides.cjs +1 -1
- package/dist/bridges-slides.cjs.map +1 -1
- package/dist/bridges-slides.js +2 -2
- package/dist/bridges-terminal.cjs +70 -45
- package/dist/bridges-terminal.cjs.map +1 -1
- package/dist/bridges-terminal.js +2 -2
- package/dist/bridges-tui.js +1 -1
- package/dist/bridges-whiteboard.cjs +1 -1
- package/dist/bridges-whiteboard.cjs.map +1 -1
- package/dist/bridges-whiteboard.js +2 -2
- package/dist/{chunk-OFCR6JY3.js → chunk-2U5JB6ZA.js} +3 -3
- package/dist/{chunk-OFCR6JY3.js.map → chunk-2U5JB6ZA.js.map} +1 -1
- package/dist/{chunk-ULJL53DL.js → chunk-2VENWGLZ.js} +3 -3
- package/dist/chunk-2VENWGLZ.js.map +1 -0
- package/dist/{chunk-BE2XS577.js → chunk-6YUBWQTS.js} +3 -3
- package/dist/{chunk-BE2XS577.js.map → chunk-6YUBWQTS.js.map} +1 -1
- package/dist/{chunk-OHUDEA6R.js → chunk-BHQJJ2YG.js} +3 -3
- package/dist/{chunk-OHUDEA6R.js.map → chunk-BHQJJ2YG.js.map} +1 -1
- package/dist/{chunk-LZJI7EKQ.js → chunk-CA7GIJJC.js} +3 -3
- package/dist/{chunk-LZJI7EKQ.js.map → chunk-CA7GIJJC.js.map} +1 -1
- package/dist/{chunk-7VBB2FJB.js → chunk-DKSR4R4O.js} +3 -3
- package/dist/{chunk-7VBB2FJB.js.map → chunk-DKSR4R4O.js.map} +1 -1
- package/dist/{chunk-2Y7Y5PTR.js → chunk-G5EZNWXJ.js} +3 -3
- package/dist/{chunk-2Y7Y5PTR.js.map → chunk-G5EZNWXJ.js.map} +1 -1
- package/dist/{chunk-OIX2ANFS.js → chunk-GRHPEK6M.js} +45 -9
- package/dist/chunk-GRHPEK6M.js.map +1 -0
- package/dist/{chunk-VMCG3PJD.js → chunk-HRHC5GM5.js} +31 -11
- package/dist/chunk-HRHC5GM5.js.map +1 -0
- package/dist/{chunk-5KLUYF4D.js → chunk-HSI5RVF4.js} +3 -3
- package/dist/{chunk-5KLUYF4D.js.map → chunk-HSI5RVF4.js.map} +1 -1
- package/dist/{chunk-AF454WRD.js → chunk-LQZKRBWR.js} +3 -3
- package/dist/{chunk-AF454WRD.js.map → chunk-LQZKRBWR.js.map} +1 -1
- package/dist/{chunk-KMSRG5KH.js → chunk-N6RLSLOF.js} +7 -7
- package/dist/chunk-N6RLSLOF.js.map +1 -0
- package/dist/{chunk-NSPL5TVF.js → chunk-OAFJ53X3.js} +3 -3
- package/dist/{chunk-NSPL5TVF.js.map → chunk-OAFJ53X3.js.map} +1 -1
- package/dist/{chunk-3PKHJ4IV.js → chunk-PR4SXFZH.js} +3 -3
- package/dist/{chunk-3PKHJ4IV.js.map → chunk-PR4SXFZH.js.map} +1 -1
- package/dist/{chunk-KYMEDV6E.js → chunk-RIXYCFDO.js} +41 -6
- package/dist/chunk-RIXYCFDO.js.map +1 -0
- package/dist/{chunk-TNU6AWUO.js → chunk-USQGA672.js} +14 -4
- package/dist/chunk-USQGA672.js.map +1 -0
- package/dist/{chunk-KKGC4ERF.js → chunk-UZHNKUER.js} +72 -47
- package/dist/chunk-UZHNKUER.js.map +1 -0
- package/dist/{chunk-UTUVN6HQ.js → chunk-WCKSDFTM.js} +3 -3
- package/dist/{chunk-UTUVN6HQ.js.map → chunk-WCKSDFTM.js.map} +1 -1
- package/dist/{chunk-HFLHLS2N.js → chunk-ZNGTC4CO.js} +3 -3
- package/dist/{chunk-HFLHLS2N.js.map → chunk-ZNGTC4CO.js.map} +1 -1
- package/dist/components/SharedWhiteboard/index.d.cts +1 -1
- package/dist/components/SharedWhiteboard/index.d.ts +1 -1
- package/dist/components-shared-whiteboard.cjs +2 -3
- package/dist/components-shared-whiteboard.cjs.map +1 -1
- package/dist/components-shared-whiteboard.js +4 -4
- package/dist/heuristics.js +1 -1
- package/dist/index.cjs +156 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +27 -21
- package/dist/index.js.map +1 -1
- package/dist/presence/index.d.cts +7 -0
- package/dist/presence/index.d.ts +7 -0
- package/dist/presence.cjs +1 -1
- package/dist/presence.cjs.map +1 -1
- package/dist/presence.js +1 -1
- package/dist/relay-server/index.d.cts +20 -0
- package/dist/relay-server/index.d.ts +20 -0
- package/dist/relay-server-cli.cjs +53 -9
- package/dist/relay-server-cli.cjs.map +1 -1
- package/dist/relay-server-cli.js +11 -3
- package/dist/relay-server-cli.js.map +1 -1
- package/dist/relay-server.cjs +43 -7
- package/dist/relay-server.cjs.map +1 -1
- package/dist/relay-server.js +1 -1
- package/dist/sharing/index.d.cts +1 -1
- package/dist/sharing/index.d.ts +1 -1
- package/dist/sharing.cjs +5 -5
- package/dist/sharing.cjs.map +1 -1
- package/dist/sharing.js +1 -1
- package/dist/{token-CrJF76oH.d.ts → token-BZ8doMam.d.cts} +15 -2
- package/dist/{token-CrJF76oH.d.cts → token-BZ8doMam.d.ts} +15 -2
- package/package.json +1 -1
- package/dist/chunk-KKGC4ERF.js.map +0 -1
- package/dist/chunk-KMSRG5KH.js.map +0 -1
- package/dist/chunk-KYMEDV6E.js.map +0 -1
- package/dist/chunk-OIX2ANFS.js.map +0 -1
- package/dist/chunk-TNU6AWUO.js.map +0 -1
- package/dist/chunk-ULJL53DL.js.map +0 -1
- package/dist/chunk-VMCG3PJD.js.map +0 -1
|
@@ -172,8 +172,37 @@ var RelayBroker = class {
|
|
|
172
172
|
sub.resolveNext?.(sub.queue.shift() ?? null);
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Validate that a peer-supplied payload is a well-formed JSON-RPC 2.0 frame
|
|
177
|
+
* (or batch) before it is fanned out. Previously this was a substring match on
|
|
178
|
+
* `"jsonrpc"`, which let any token holder inject spoofed frames — a forged
|
|
179
|
+
* `notifications/peer_joined` / `peer_left` (broker-reserved presence control),
|
|
180
|
+
* a fake `notifications/agent_activity {source:"user"}` ("human took control"),
|
|
181
|
+
* or a forged response with a matching id. We now parse + shape-check, and
|
|
182
|
+
* reject the broker-reserved control methods (the broker emits those itself,
|
|
183
|
+
* never a peer).
|
|
184
|
+
*/
|
|
175
185
|
isFrame(payload) {
|
|
176
|
-
|
|
186
|
+
if (payload.length === 0) return false;
|
|
187
|
+
let parsed;
|
|
188
|
+
try {
|
|
189
|
+
parsed = JSON.parse(payload);
|
|
190
|
+
} catch {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
const frames = Array.isArray(parsed) ? parsed : [parsed];
|
|
194
|
+
return frames.length > 0 && frames.every((f) => this.isValidFrame(f));
|
|
195
|
+
}
|
|
196
|
+
isValidFrame(f) {
|
|
197
|
+
if (!f || typeof f !== "object" || Array.isArray(f)) return false;
|
|
198
|
+
const o = f;
|
|
199
|
+
if (o.jsonrpc !== "2.0") return false;
|
|
200
|
+
const method = typeof o.method === "string" ? o.method : void 0;
|
|
201
|
+
if (method === "notifications/peer_joined" || method === "notifications/peer_left") return false;
|
|
202
|
+
if (method !== void 0) return true;
|
|
203
|
+
const hasResult = "result" in o;
|
|
204
|
+
const hasError = "error" in o;
|
|
205
|
+
return "id" in o && hasResult !== hasError;
|
|
177
206
|
}
|
|
178
207
|
reap() {
|
|
179
208
|
const cutoff = Date.now() - this.ttlMs;
|
|
@@ -202,10 +231,17 @@ function createNodeRelay(opts = {}) {
|
|
|
202
231
|
const broker = new RelayBroker(opts);
|
|
203
232
|
const prefix = (opts.pathPrefix ?? "").replace(/\/$/, "");
|
|
204
233
|
const cors = opts.corsAllowOrigin ?? "*";
|
|
205
|
-
|
|
206
|
-
|
|
234
|
+
const allowedOrigins = opts.allowedOrigins;
|
|
235
|
+
function setCorsHeaders(res, req) {
|
|
236
|
+
if (allowedOrigins && allowedOrigins.length) {
|
|
237
|
+
const origin = req?.headers.origin;
|
|
238
|
+
res.setHeader("access-control-allow-origin", origin && allowedOrigins.includes(origin) ? origin : "null");
|
|
239
|
+
res.setHeader("vary", "origin");
|
|
240
|
+
} else {
|
|
241
|
+
res.setHeader("access-control-allow-origin", cors);
|
|
242
|
+
}
|
|
207
243
|
res.setHeader("access-control-allow-methods", "GET, POST, OPTIONS");
|
|
208
|
-
res.setHeader("access-control-allow-headers", "content-type, x-csrf-token, accept");
|
|
244
|
+
res.setHeader("access-control-allow-headers", "content-type, x-csrf-token, accept, authorization");
|
|
209
245
|
res.setHeader("access-control-max-age", "86400");
|
|
210
246
|
}
|
|
211
247
|
function readBody(req) {
|
|
@@ -241,7 +277,7 @@ function createNodeRelay(opts = {}) {
|
|
|
241
277
|
return u.pathname;
|
|
242
278
|
}
|
|
243
279
|
const register = async (req, res) => {
|
|
244
|
-
setCorsHeaders(res);
|
|
280
|
+
setCorsHeaders(res, req);
|
|
245
281
|
if (req.method === "OPTIONS") {
|
|
246
282
|
res.statusCode = 204;
|
|
247
283
|
return res.end();
|
|
@@ -269,7 +305,7 @@ function createNodeRelay(opts = {}) {
|
|
|
269
305
|
};
|
|
270
306
|
function makeSessionHandler(direction) {
|
|
271
307
|
return async (req, res) => {
|
|
272
|
-
setCorsHeaders(res);
|
|
308
|
+
setCorsHeaders(res, req);
|
|
273
309
|
if (req.method === "OPTIONS") {
|
|
274
310
|
res.statusCode = 204;
|
|
275
311
|
return res.end();
|
|
@@ -296,7 +332,7 @@ function createNodeRelay(opts = {}) {
|
|
|
296
332
|
const outbox = makeSessionHandler("outbound");
|
|
297
333
|
const unregister = makeSessionHandler("unregister");
|
|
298
334
|
const events = async (req, res) => {
|
|
299
|
-
setCorsHeaders(res);
|
|
335
|
+
setCorsHeaders(res, req);
|
|
300
336
|
if (req.method === "OPTIONS") {
|
|
301
337
|
res.statusCode = 204;
|
|
302
338
|
return res.end();
|
|
@@ -390,7 +426,7 @@ function flush(res) {
|
|
|
390
426
|
async function main() {
|
|
391
427
|
const argv = process.argv.slice(2);
|
|
392
428
|
let port = Number(process.env.PORT ?? 8787);
|
|
393
|
-
let host = process.env.HOST ?? "
|
|
429
|
+
let host = process.env.HOST ?? "127.0.0.1";
|
|
394
430
|
let prefix = process.env.PREFIX ?? "";
|
|
395
431
|
let ttlMs = Number(process.env.TTL_MS ?? 4 * 60 * 60 * 1e3);
|
|
396
432
|
let cors = process.env.CORS_ALLOW_ORIGIN ?? "*";
|
|
@@ -438,6 +474,13 @@ async function main() {
|
|
|
438
474
|
}
|
|
439
475
|
relay.handler(req, res);
|
|
440
476
|
});
|
|
477
|
+
const isLoopback = host === "127.0.0.1" || host === "::1" || host === "localhost";
|
|
478
|
+
if (!isLoopback) {
|
|
479
|
+
process.stderr.write(
|
|
480
|
+
`[relay] WARNING: binding ${host} exposes the relay on non-loopback interfaces. register is unauthenticated and a leaked session token grants terminal_run. Only do this behind an authenticating proxy / trusted network.
|
|
481
|
+
`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
441
484
|
server.listen(port, host, () => {
|
|
442
485
|
process.stdout.write(
|
|
443
486
|
`[relay] listening on http://${host}:${port}${prefix || ""} (ttl=${Math.round(ttlMs / 1e3)}s, cors=${cors})
|
|
@@ -459,7 +502,8 @@ Usage: agent-integrations-relay [options]
|
|
|
459
502
|
|
|
460
503
|
Options:
|
|
461
504
|
--port <n> Listen port (env: PORT). Default 8787.
|
|
462
|
-
--host <addr> Bind address (env: HOST). Default
|
|
505
|
+
--host <addr> Bind address (env: HOST). Default 127.0.0.1 (loopback).
|
|
506
|
+
Pass 0.0.0.0 to expose on all interfaces (prints a warning).
|
|
463
507
|
--prefix <path> URL path prefix (env: PREFIX). Default "".
|
|
464
508
|
--ttl-ms <n> Session TTL ms (env: TTL_MS). Default 14_400_000.
|
|
465
509
|
--cors <origin> CORS Access-Control-Allow-Origin (env: CORS_ALLOW_ORIGIN). Default "*".
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/relay-server/core.ts","../src/relay-server/node.ts","../src/relay-server/cli.ts"],"names":["randomBytes","next","createHash","timingSafeEqual","URL","ok","createServer"],"mappings":"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","file":"relay-server-cli.cjs","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","#!/usr/bin/env node\nimport { createServer } from \"node:http\";\nimport { createNodeRelay } from \"./node\";\n\n/**\n * `agent-integrations-relay` — standalone Node HTTP server hosting the\n * SSE+POST relay broker. End users hit `Start share` on a /ui/demos\n * page, get a session URL pointing at this service, and paste it into\n * their MCP client. No state persists across restarts.\n *\n * Flags:\n * --port <n> Listen port. Default 8787.\n * --host <addr> Bind address. Default 0.0.0.0.\n * --prefix <path> URL path prefix (no trailing slash). Default \"\".\n * Useful when mounting behind a reverse proxy.\n * --ttl-ms <n> Session TTL ms. Default 14_400_000 (4h).\n * --cors <origin> Access-Control-Allow-Origin. Default \"*\".\n * -h, --help Show this help.\n *\n * Health: any request to `/` returns 200 OK so platform health checks\n * pass without authenticating.\n */\nasync function main() {\n const argv = process.argv.slice(2);\n let port = Number(process.env.PORT ?? 8787);\n let host = process.env.HOST ?? \"0.0.0.0\";\n let prefix = process.env.PREFIX ?? \"\";\n let ttlMs = Number(process.env.TTL_MS ?? 4 * 60 * 60 * 1000);\n let cors = process.env.CORS_ALLOW_ORIGIN ?? \"*\";\n\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n switch (a) {\n case \"--port\": port = Number(argv[++i]); break;\n case \"--host\": host = argv[++i]; break;\n case \"--prefix\": prefix = argv[++i]; break;\n case \"--ttl-ms\": ttlMs = Number(argv[++i]); break;\n case \"--cors\": cors = argv[++i]; break;\n case \"-h\":\n case \"--help\":\n process.stdout.write(HELP);\n process.exit(0);\n default:\n process.stderr.write(`[relay] unknown flag: ${a}\\n`);\n process.exit(2);\n }\n }\n\n if (!Number.isFinite(port) || port <= 0) {\n process.stderr.write(`[relay] invalid --port: ${port}\\n`);\n process.exit(2);\n }\n\n const relay = createNodeRelay({ pathPrefix: prefix, ttlMs, corsAllowOrigin: cors });\n\n const server = createServer((req, res) => {\n // Health: GET / always 200 so platform health checks succeed without auth.\n const url = req.url || \"/\";\n if ((url === \"/\" || url === \"/healthz\" || url === (prefix + \"/\")) && req.method === \"GET\") {\n res.statusCode = 200;\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify({ ok: true, service: \"agent-integrations-relay\" }));\n return;\n }\n relay.handler(req, res);\n });\n\n server.listen(port, host, () => {\n process.stdout.write(\n `[relay] listening on http://${host}:${port}${prefix || \"\"} ` +\n `(ttl=${Math.round(ttlMs / 1000)}s, cors=${cors})\\n`,\n );\n });\n\n const shutdown = () => {\n process.stdout.write(\"[relay] shutting down\\n\");\n relay.broker.dispose();\n server.close(() => process.exit(0));\n setTimeout(() => process.exit(0), 2000).unref();\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n}\n\nconst HELP = `agent-integrations-relay — Node HTTP server for the MCP relay broker.\n\nUsage: agent-integrations-relay [options]\n\nOptions:\n --port <n> Listen port (env: PORT). Default 8787.\n --host <addr> Bind address (env: HOST). Default 0.0.0.0.\n --prefix <path> URL path prefix (env: PREFIX). Default \"\".\n --ttl-ms <n> Session TTL ms (env: TTL_MS). Default 14_400_000.\n --cors <origin> CORS Access-Control-Allow-Origin (env: CORS_ALLOW_ORIGIN). Default \"*\".\n -h, --help Show this help.\n\nEndpoints (under --prefix):\n POST /register { session, token } → { ok: true }\n POST /<session>/inbox?token=... body: JSON-RPC frame\n POST /<session>/outbox?token=... body: JSON-RPC frame\n GET /<session>/events?token=...&direction=inbound|outbound\n Server-sent events stream\n POST /<session>/unregister?token=... Tear down session\n GET / Healthcheck → 200\n`;\n\nmain().catch((e) => {\n process.stderr.write(`[relay] fatal: ${e instanceof Error ? e.stack ?? e.message : String(e)}\\n`);\n process.exit(1);\n});\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/relay-server/core.ts","../src/relay-server/node.ts","../src/relay-server/cli.ts"],"names":["randomBytes","next","createHash","timingSafeEqual","URL","ok","createServer"],"mappings":"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","file":"relay-server-cli.cjs","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 /**\n * Validate that a peer-supplied payload is a well-formed JSON-RPC 2.0 frame\n * (or batch) before it is fanned out. Previously this was a substring match on\n * `\"jsonrpc\"`, which let any token holder inject spoofed frames — a forged\n * `notifications/peer_joined` / `peer_left` (broker-reserved presence control),\n * a fake `notifications/agent_activity {source:\"user\"}` (\"human took control\"),\n * or a forged response with a matching id. We now parse + shape-check, and\n * reject the broker-reserved control methods (the broker emits those itself,\n * never a peer).\n */\n private isFrame(payload: string): boolean {\n if (payload.length === 0) return false;\n let parsed: unknown;\n try {\n parsed = JSON.parse(payload);\n } catch {\n return false;\n }\n const frames = Array.isArray(parsed) ? parsed : [parsed];\n return frames.length > 0 && frames.every((f) => this.isValidFrame(f));\n }\n\n private isValidFrame(f: unknown): boolean {\n if (!f || typeof f !== \"object\" || Array.isArray(f)) return false;\n const o = f as Record<string, unknown>;\n if (o.jsonrpc !== \"2.0\") return false;\n const method = typeof o.method === \"string\" ? o.method : undefined;\n // Broker-reserved control frames may never arrive from a peer.\n if (method === \"notifications/peer_joined\" || method === \"notifications/peer_left\") return false;\n if (method !== undefined) return true; // request or notification\n // Response: needs an id and exactly one of result / error.\n const hasResult = \"result\" in o;\n const hasError = \"error\" in o;\n return \"id\" in o && hasResult !== hasError;\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 * Strict browser-origin allow-list. When set, only these Origins get a\n * matching `Access-Control-Allow-Origin` (the request's own Origin is\n * reflected, never `*`), which blunts DNS-rebinding + hostile cross-origin\n * pages from reading relay responses. Recommended for any browser-facing\n * deployment. When unset, `corsAllowOrigin` (default `*`) is used and the\n * session token is the only auth — pair that with a loopback bind.\n */\n allowedOrigins?: 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 const allowedOrigins = opts.allowedOrigins;\n\n function setCorsHeaders(res: ServerResponse, req?: IncomingMessage) {\n if (allowedOrigins && allowedOrigins.length) {\n // Strict mode: reflect the request Origin only when it's allow-listed;\n // otherwise emit an origin that no browser will match.\n const origin = req?.headers.origin;\n res.setHeader(\"access-control-allow-origin\", origin && allowedOrigins.includes(origin) ? origin : \"null\");\n res.setHeader(\"vary\", \"origin\");\n } else {\n res.setHeader(\"access-control-allow-origin\", cors);\n }\n res.setHeader(\"access-control-allow-methods\", \"GET, POST, OPTIONS\");\n // `authorization` is forward-compat for a header-borne session token.\n res.setHeader(\"access-control-allow-headers\", \"content-type, x-csrf-token, accept, authorization\");\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, req);\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, req);\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, req);\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","#!/usr/bin/env node\nimport { createServer } from \"node:http\";\nimport { createNodeRelay } from \"./node\";\n\n/**\n * `agent-integrations-relay` — standalone Node HTTP server hosting the\n * SSE+POST relay broker. End users hit `Start share` on a /ui/demos\n * page, get a session URL pointing at this service, and paste it into\n * their MCP client. No state persists across restarts.\n *\n * Flags:\n * --port <n> Listen port. Default 8787.\n * --host <addr> Bind address. Default 127.0.0.1 (loopback). Pass\n * 0.0.0.0 to expose on all interfaces (warns).\n * --prefix <path> URL path prefix (no trailing slash). Default \"\".\n * Useful when mounting behind a reverse proxy.\n * --ttl-ms <n> Session TTL ms. Default 14_400_000 (4h).\n * --cors <origin> Access-Control-Allow-Origin. Default \"*\".\n * -h, --help Show this help.\n *\n * Health: any request to `/` returns 200 OK so platform health checks\n * pass without authenticating.\n */\nasync function main() {\n const argv = process.argv.slice(2);\n let port = Number(process.env.PORT ?? 8787);\n // Loopback by default: this relay fronts terminal_run (RCE) and register is\n // unauthenticated, so it must not listen on all interfaces unless the operator\n // explicitly opts in with --host 0.0.0.0.\n let host = process.env.HOST ?? \"127.0.0.1\";\n let prefix = process.env.PREFIX ?? \"\";\n let ttlMs = Number(process.env.TTL_MS ?? 4 * 60 * 60 * 1000);\n let cors = process.env.CORS_ALLOW_ORIGIN ?? \"*\";\n\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n switch (a) {\n case \"--port\": port = Number(argv[++i]); break;\n case \"--host\": host = argv[++i]; break;\n case \"--prefix\": prefix = argv[++i]; break;\n case \"--ttl-ms\": ttlMs = Number(argv[++i]); break;\n case \"--cors\": cors = argv[++i]; break;\n case \"-h\":\n case \"--help\":\n process.stdout.write(HELP);\n process.exit(0);\n default:\n process.stderr.write(`[relay] unknown flag: ${a}\\n`);\n process.exit(2);\n }\n }\n\n if (!Number.isFinite(port) || port <= 0) {\n process.stderr.write(`[relay] invalid --port: ${port}\\n`);\n process.exit(2);\n }\n\n const relay = createNodeRelay({ pathPrefix: prefix, ttlMs, corsAllowOrigin: cors });\n\n const server = createServer((req, res) => {\n // Health: GET / always 200 so platform health checks succeed without auth.\n const url = req.url || \"/\";\n if ((url === \"/\" || url === \"/healthz\" || url === (prefix + \"/\")) && req.method === \"GET\") {\n res.statusCode = 200;\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify({ ok: true, service: \"agent-integrations-relay\" }));\n return;\n }\n relay.handler(req, res);\n });\n\n const isLoopback = host === \"127.0.0.1\" || host === \"::1\" || host === \"localhost\";\n if (!isLoopback) {\n process.stderr.write(\n `[relay] WARNING: binding ${host} exposes the relay on non-loopback interfaces. ` +\n `register is unauthenticated and a leaked session token grants terminal_run. ` +\n `Only do this behind an authenticating proxy / trusted network.\\n`,\n );\n }\n server.listen(port, host, () => {\n process.stdout.write(\n `[relay] listening on http://${host}:${port}${prefix || \"\"} ` +\n `(ttl=${Math.round(ttlMs / 1000)}s, cors=${cors})\\n`,\n );\n });\n\n const shutdown = () => {\n process.stdout.write(\"[relay] shutting down\\n\");\n relay.broker.dispose();\n server.close(() => process.exit(0));\n setTimeout(() => process.exit(0), 2000).unref();\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n}\n\nconst HELP = `agent-integrations-relay — Node HTTP server for the MCP relay broker.\n\nUsage: agent-integrations-relay [options]\n\nOptions:\n --port <n> Listen port (env: PORT). Default 8787.\n --host <addr> Bind address (env: HOST). Default 127.0.0.1 (loopback).\n Pass 0.0.0.0 to expose on all interfaces (prints a warning).\n --prefix <path> URL path prefix (env: PREFIX). Default \"\".\n --ttl-ms <n> Session TTL ms (env: TTL_MS). Default 14_400_000.\n --cors <origin> CORS Access-Control-Allow-Origin (env: CORS_ALLOW_ORIGIN). Default \"*\".\n -h, --help Show this help.\n\nEndpoints (under --prefix):\n POST /register { session, token } → { ok: true }\n POST /<session>/inbox?token=... body: JSON-RPC frame\n POST /<session>/outbox?token=... body: JSON-RPC frame\n GET /<session>/events?token=...&direction=inbound|outbound\n Server-sent events stream\n POST /<session>/unregister?token=... Tear down session\n GET / Healthcheck → 200\n`;\n\nmain().catch((e) => {\n process.stderr.write(`[relay] fatal: ${e instanceof Error ? e.stack ?? e.message : String(e)}\\n`);\n process.exit(1);\n});\n"]}
|
package/dist/relay-server-cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createNodeRelay } from './chunk-
|
|
2
|
+
import { createNodeRelay } from './chunk-GRHPEK6M.js';
|
|
3
3
|
import { createServer } from 'http';
|
|
4
4
|
|
|
5
5
|
async function main() {
|
|
6
6
|
const argv = process.argv.slice(2);
|
|
7
7
|
let port = Number(process.env.PORT ?? 8787);
|
|
8
|
-
let host = process.env.HOST ?? "
|
|
8
|
+
let host = process.env.HOST ?? "127.0.0.1";
|
|
9
9
|
let prefix = process.env.PREFIX ?? "";
|
|
10
10
|
let ttlMs = Number(process.env.TTL_MS ?? 4 * 60 * 60 * 1e3);
|
|
11
11
|
let cors = process.env.CORS_ALLOW_ORIGIN ?? "*";
|
|
@@ -53,6 +53,13 @@ async function main() {
|
|
|
53
53
|
}
|
|
54
54
|
relay.handler(req, res);
|
|
55
55
|
});
|
|
56
|
+
const isLoopback = host === "127.0.0.1" || host === "::1" || host === "localhost";
|
|
57
|
+
if (!isLoopback) {
|
|
58
|
+
process.stderr.write(
|
|
59
|
+
`[relay] WARNING: binding ${host} exposes the relay on non-loopback interfaces. register is unauthenticated and a leaked session token grants terminal_run. Only do this behind an authenticating proxy / trusted network.
|
|
60
|
+
`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
56
63
|
server.listen(port, host, () => {
|
|
57
64
|
process.stdout.write(
|
|
58
65
|
`[relay] listening on http://${host}:${port}${prefix || ""} (ttl=${Math.round(ttlMs / 1e3)}s, cors=${cors})
|
|
@@ -74,7 +81,8 @@ Usage: agent-integrations-relay [options]
|
|
|
74
81
|
|
|
75
82
|
Options:
|
|
76
83
|
--port <n> Listen port (env: PORT). Default 8787.
|
|
77
|
-
--host <addr> Bind address (env: HOST). Default
|
|
84
|
+
--host <addr> Bind address (env: HOST). Default 127.0.0.1 (loopback).
|
|
85
|
+
Pass 0.0.0.0 to expose on all interfaces (prints a warning).
|
|
78
86
|
--prefix <path> URL path prefix (env: PREFIX). Default "".
|
|
79
87
|
--ttl-ms <n> Session TTL ms (env: TTL_MS). Default 14_400_000.
|
|
80
88
|
--cors <origin> CORS Access-Control-Allow-Origin (env: CORS_ALLOW_ORIGIN). Default "*".
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/relay-server/cli.ts"],"names":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"sources":["../src/relay-server/cli.ts"],"names":[],"mappings":"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","file":"relay-server-cli.js","sourcesContent":["#!/usr/bin/env node\nimport { createServer } from \"node:http\";\nimport { createNodeRelay } from \"./node\";\n\n/**\n * `agent-integrations-relay` — standalone Node HTTP server hosting the\n * SSE+POST relay broker. End users hit `Start share` on a /ui/demos\n * page, get a session URL pointing at this service, and paste it into\n * their MCP client. No state persists across restarts.\n *\n * Flags:\n * --port <n> Listen port. Default 8787.\n * --host <addr> Bind address. Default 127.0.0.1 (loopback). Pass\n * 0.0.0.0 to expose on all interfaces (warns).\n * --prefix <path> URL path prefix (no trailing slash). Default \"\".\n * Useful when mounting behind a reverse proxy.\n * --ttl-ms <n> Session TTL ms. Default 14_400_000 (4h).\n * --cors <origin> Access-Control-Allow-Origin. Default \"*\".\n * -h, --help Show this help.\n *\n * Health: any request to `/` returns 200 OK so platform health checks\n * pass without authenticating.\n */\nasync function main() {\n const argv = process.argv.slice(2);\n let port = Number(process.env.PORT ?? 8787);\n // Loopback by default: this relay fronts terminal_run (RCE) and register is\n // unauthenticated, so it must not listen on all interfaces unless the operator\n // explicitly opts in with --host 0.0.0.0.\n let host = process.env.HOST ?? \"127.0.0.1\";\n let prefix = process.env.PREFIX ?? \"\";\n let ttlMs = Number(process.env.TTL_MS ?? 4 * 60 * 60 * 1000);\n let cors = process.env.CORS_ALLOW_ORIGIN ?? \"*\";\n\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n switch (a) {\n case \"--port\": port = Number(argv[++i]); break;\n case \"--host\": host = argv[++i]; break;\n case \"--prefix\": prefix = argv[++i]; break;\n case \"--ttl-ms\": ttlMs = Number(argv[++i]); break;\n case \"--cors\": cors = argv[++i]; break;\n case \"-h\":\n case \"--help\":\n process.stdout.write(HELP);\n process.exit(0);\n default:\n process.stderr.write(`[relay] unknown flag: ${a}\\n`);\n process.exit(2);\n }\n }\n\n if (!Number.isFinite(port) || port <= 0) {\n process.stderr.write(`[relay] invalid --port: ${port}\\n`);\n process.exit(2);\n }\n\n const relay = createNodeRelay({ pathPrefix: prefix, ttlMs, corsAllowOrigin: cors });\n\n const server = createServer((req, res) => {\n // Health: GET / always 200 so platform health checks succeed without auth.\n const url = req.url || \"/\";\n if ((url === \"/\" || url === \"/healthz\" || url === (prefix + \"/\")) && req.method === \"GET\") {\n res.statusCode = 200;\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify({ ok: true, service: \"agent-integrations-relay\" }));\n return;\n }\n relay.handler(req, res);\n });\n\n const isLoopback = host === \"127.0.0.1\" || host === \"::1\" || host === \"localhost\";\n if (!isLoopback) {\n process.stderr.write(\n `[relay] WARNING: binding ${host} exposes the relay on non-loopback interfaces. ` +\n `register is unauthenticated and a leaked session token grants terminal_run. ` +\n `Only do this behind an authenticating proxy / trusted network.\\n`,\n );\n }\n server.listen(port, host, () => {\n process.stdout.write(\n `[relay] listening on http://${host}:${port}${prefix || \"\"} ` +\n `(ttl=${Math.round(ttlMs / 1000)}s, cors=${cors})\\n`,\n );\n });\n\n const shutdown = () => {\n process.stdout.write(\"[relay] shutting down\\n\");\n relay.broker.dispose();\n server.close(() => process.exit(0));\n setTimeout(() => process.exit(0), 2000).unref();\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n}\n\nconst HELP = `agent-integrations-relay — Node HTTP server for the MCP relay broker.\n\nUsage: agent-integrations-relay [options]\n\nOptions:\n --port <n> Listen port (env: PORT). Default 8787.\n --host <addr> Bind address (env: HOST). Default 127.0.0.1 (loopback).\n Pass 0.0.0.0 to expose on all interfaces (prints a warning).\n --prefix <path> URL path prefix (env: PREFIX). Default \"\".\n --ttl-ms <n> Session TTL ms (env: TTL_MS). Default 14_400_000.\n --cors <origin> CORS Access-Control-Allow-Origin (env: CORS_ALLOW_ORIGIN). Default \"*\".\n -h, --help Show this help.\n\nEndpoints (under --prefix):\n POST /register { session, token } → { ok: true }\n POST /<session>/inbox?token=... body: JSON-RPC frame\n POST /<session>/outbox?token=... body: JSON-RPC frame\n GET /<session>/events?token=...&direction=inbound|outbound\n Server-sent events stream\n POST /<session>/unregister?token=... Tear down session\n GET / Healthcheck → 200\n`;\n\nmain().catch((e) => {\n process.stderr.write(`[relay] fatal: ${e instanceof Error ? e.stack ?? e.message : String(e)}\\n`);\n process.exit(1);\n});\n"]}
|
package/dist/relay-server.cjs
CHANGED
|
@@ -171,8 +171,37 @@ var RelayBroker = class {
|
|
|
171
171
|
sub.resolveNext?.(sub.queue.shift() ?? null);
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Validate that a peer-supplied payload is a well-formed JSON-RPC 2.0 frame
|
|
176
|
+
* (or batch) before it is fanned out. Previously this was a substring match on
|
|
177
|
+
* `"jsonrpc"`, which let any token holder inject spoofed frames — a forged
|
|
178
|
+
* `notifications/peer_joined` / `peer_left` (broker-reserved presence control),
|
|
179
|
+
* a fake `notifications/agent_activity {source:"user"}` ("human took control"),
|
|
180
|
+
* or a forged response with a matching id. We now parse + shape-check, and
|
|
181
|
+
* reject the broker-reserved control methods (the broker emits those itself,
|
|
182
|
+
* never a peer).
|
|
183
|
+
*/
|
|
174
184
|
isFrame(payload) {
|
|
175
|
-
|
|
185
|
+
if (payload.length === 0) return false;
|
|
186
|
+
let parsed;
|
|
187
|
+
try {
|
|
188
|
+
parsed = JSON.parse(payload);
|
|
189
|
+
} catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
const frames = Array.isArray(parsed) ? parsed : [parsed];
|
|
193
|
+
return frames.length > 0 && frames.every((f) => this.isValidFrame(f));
|
|
194
|
+
}
|
|
195
|
+
isValidFrame(f) {
|
|
196
|
+
if (!f || typeof f !== "object" || Array.isArray(f)) return false;
|
|
197
|
+
const o = f;
|
|
198
|
+
if (o.jsonrpc !== "2.0") return false;
|
|
199
|
+
const method = typeof o.method === "string" ? o.method : void 0;
|
|
200
|
+
if (method === "notifications/peer_joined" || method === "notifications/peer_left") return false;
|
|
201
|
+
if (method !== void 0) return true;
|
|
202
|
+
const hasResult = "result" in o;
|
|
203
|
+
const hasError = "error" in o;
|
|
204
|
+
return "id" in o && hasResult !== hasError;
|
|
176
205
|
}
|
|
177
206
|
reap() {
|
|
178
207
|
const cutoff = Date.now() - this.ttlMs;
|
|
@@ -199,10 +228,17 @@ function createNodeRelay(opts = {}) {
|
|
|
199
228
|
const broker = new RelayBroker(opts);
|
|
200
229
|
const prefix = (opts.pathPrefix ?? "").replace(/\/$/, "");
|
|
201
230
|
const cors = opts.corsAllowOrigin ?? "*";
|
|
202
|
-
|
|
203
|
-
|
|
231
|
+
const allowedOrigins = opts.allowedOrigins;
|
|
232
|
+
function setCorsHeaders(res, req) {
|
|
233
|
+
if (allowedOrigins && allowedOrigins.length) {
|
|
234
|
+
const origin = req?.headers.origin;
|
|
235
|
+
res.setHeader("access-control-allow-origin", origin && allowedOrigins.includes(origin) ? origin : "null");
|
|
236
|
+
res.setHeader("vary", "origin");
|
|
237
|
+
} else {
|
|
238
|
+
res.setHeader("access-control-allow-origin", cors);
|
|
239
|
+
}
|
|
204
240
|
res.setHeader("access-control-allow-methods", "GET, POST, OPTIONS");
|
|
205
|
-
res.setHeader("access-control-allow-headers", "content-type, x-csrf-token, accept");
|
|
241
|
+
res.setHeader("access-control-allow-headers", "content-type, x-csrf-token, accept, authorization");
|
|
206
242
|
res.setHeader("access-control-max-age", "86400");
|
|
207
243
|
}
|
|
208
244
|
function readBody(req) {
|
|
@@ -238,7 +274,7 @@ function createNodeRelay(opts = {}) {
|
|
|
238
274
|
return u.pathname;
|
|
239
275
|
}
|
|
240
276
|
const register = async (req, res) => {
|
|
241
|
-
setCorsHeaders(res);
|
|
277
|
+
setCorsHeaders(res, req);
|
|
242
278
|
if (req.method === "OPTIONS") {
|
|
243
279
|
res.statusCode = 204;
|
|
244
280
|
return res.end();
|
|
@@ -266,7 +302,7 @@ function createNodeRelay(opts = {}) {
|
|
|
266
302
|
};
|
|
267
303
|
function makeSessionHandler(direction) {
|
|
268
304
|
return async (req, res) => {
|
|
269
|
-
setCorsHeaders(res);
|
|
305
|
+
setCorsHeaders(res, req);
|
|
270
306
|
if (req.method === "OPTIONS") {
|
|
271
307
|
res.statusCode = 204;
|
|
272
308
|
return res.end();
|
|
@@ -293,7 +329,7 @@ function createNodeRelay(opts = {}) {
|
|
|
293
329
|
const outbox = makeSessionHandler("outbound");
|
|
294
330
|
const unregister = makeSessionHandler("unregister");
|
|
295
331
|
const events = async (req, res) => {
|
|
296
|
-
setCorsHeaders(res);
|
|
332
|
+
setCorsHeaders(res, req);
|
|
297
333
|
if (req.method === "OPTIONS") {
|
|
298
334
|
res.statusCode = 204;
|
|
299
335
|
return res.end();
|