@creatornader/agent-bridge 0.7.2 → 0.7.4

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/dist/dr.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  verifyNativeDrBundle,
40
40
  verifyPostgresNativeDrArtifacts,
41
41
  writeNativeDrBundle
42
- } from "./chunk-SHJGICQ5.js";
42
+ } from "./chunk-KT2NNGDR.js";
43
43
  import {
44
44
  EDGE_SQLITE_APPLICATION_ID,
45
45
  EDGE_SQLITE_SCHEMA_CONTRACTS,
@@ -0,0 +1,32 @@
1
+ interface LoopbackHostOptions {
2
+ host?: string;
3
+ port?: number;
4
+ path?: string;
5
+ sessionIdleMs?: number;
6
+ }
7
+ interface LoopbackHost {
8
+ endpoint: string;
9
+ healthEndpoint: string;
10
+ close(): Promise<void>;
11
+ }
12
+ interface LaunchdInstallOptions extends Required<LoopbackHostOptions> {
13
+ serviceName: string;
14
+ configPath: string;
15
+ agent: string;
16
+ instance: string;
17
+ home?: string;
18
+ executablePath?: string;
19
+ }
20
+ declare function startLoopbackHost(options?: LoopbackHostOptions): Promise<LoopbackHost>;
21
+ declare function launchdPlist(options: LaunchdInstallOptions): string;
22
+ declare function installLaunchdHost(options: LaunchdInstallOptions): Promise<{
23
+ label: string;
24
+ plistPath: string;
25
+ healthEndpoint: string;
26
+ }>;
27
+ declare function uninstallLaunchdHost(serviceName: string, port: number, host?: string, home?: string): Promise<{
28
+ label: string;
29
+ plistPath: string;
30
+ }>;
31
+
32
+ export { type LaunchdInstallOptions, type LoopbackHost, type LoopbackHostOptions, installLaunchdHost, launchdPlist, startLoopbackHost, uninstallLaunchdHost };
@@ -0,0 +1,421 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ configFromEnv,
4
+ createAgentBridgeServer
5
+ } from "./chunk-W2TBIICO.js";
6
+ import "./chunk-23UDQZMT.js";
7
+ import "./chunk-UKFRCP6X.js";
8
+ import "./chunk-CQZHYEXW.js";
9
+ import "./chunk-6ZOLE3E3.js";
10
+ import "./chunk-7EQXQVPD.js";
11
+ import "./chunk-SBVZVXGK.js";
12
+ import {
13
+ verifyPrivatePathAccess
14
+ } from "./chunk-GTK4KOED.js";
15
+ import "./chunk-VOQIDTCJ.js";
16
+ import "./chunk-4V2PFJEF.js";
17
+ import "./chunk-63JKCBC6.js";
18
+ import "./chunk-BZLMF5O5.js";
19
+
20
+ // src/loopback-host.ts
21
+ import { randomUUID } from "crypto";
22
+ import { chmodSync, existsSync, mkdirSync, realpathSync, rmSync, writeFileSync } from "fs";
23
+ import { createServer } from "http";
24
+ import { connect } from "net";
25
+ import { homedir } from "os";
26
+ import { isAbsolute, join } from "path";
27
+ import { spawnSync } from "child_process";
28
+ import { fileURLToPath, pathToFileURL } from "url";
29
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
30
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
31
+ var DEFAULT_HOST = "127.0.0.1";
32
+ var DEFAULT_PORT = 8794;
33
+ var DEFAULT_PATH = "/mcp";
34
+ var DEFAULT_SESSION_IDLE_MS = 12 * 60 * 60 * 1e3;
35
+ var MAX_BODY_BYTES = 1024 * 1024;
36
+ function normalizePath(raw) {
37
+ const path = raw.startsWith("/") ? raw : `/${raw}`;
38
+ return path.length > 1 ? path.replace(/\/+$/, "") : path;
39
+ }
40
+ function isLoopback(host) {
41
+ return host === "127.0.0.1" || host === "::1" || host === "localhost";
42
+ }
43
+ function endpoint(host, port, path) {
44
+ return `http://${host.includes(":") ? `[${host}]` : host}:${port}${path}`;
45
+ }
46
+ function requestPath(req) {
47
+ try {
48
+ return new URL(req.url ?? "/", "http://localhost").pathname;
49
+ } catch {
50
+ return "/";
51
+ }
52
+ }
53
+ function sessionId(req) {
54
+ const value = req.headers["mcp-session-id"];
55
+ return Array.isArray(value) ? value[0] : value;
56
+ }
57
+ function reply(res, status, body) {
58
+ const bytes = Buffer.from(JSON.stringify(body));
59
+ res.writeHead(status, {
60
+ "content-type": "application/json; charset=utf-8",
61
+ "content-length": bytes.length
62
+ });
63
+ res.end(bytes);
64
+ }
65
+ function jsonRpcError(res, status, code, message) {
66
+ reply(res, status, { jsonrpc: "2.0", error: { code, message }, id: null });
67
+ }
68
+ async function readBody(req) {
69
+ const chunks = [];
70
+ let length = 0;
71
+ for await (const chunk of req) {
72
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
73
+ length += bytes.length;
74
+ if (length > MAX_BODY_BYTES) throw new Error(`request body exceeds ${MAX_BODY_BYTES} bytes`);
75
+ chunks.push(bytes);
76
+ }
77
+ const value = Buffer.concat(chunks).toString("utf8").trim();
78
+ return value ? JSON.parse(value) : void 0;
79
+ }
80
+ function listen(server, host, port) {
81
+ return new Promise((resolve, reject) => {
82
+ const onError = (error) => {
83
+ server.off("listening", onListening);
84
+ reject(error);
85
+ };
86
+ const onListening = () => {
87
+ server.off("error", onError);
88
+ resolve();
89
+ };
90
+ server.once("error", onError);
91
+ server.once("listening", onListening);
92
+ server.listen(port, host);
93
+ });
94
+ }
95
+ function closeServer(server) {
96
+ return new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
97
+ }
98
+ function boundPort(server) {
99
+ const address = server.address();
100
+ if (!address || typeof address === "string") throw new Error("loopback host did not bind a TCP port");
101
+ return address.port;
102
+ }
103
+ async function startLoopbackHost(options = {}) {
104
+ const host = options.host ?? DEFAULT_HOST;
105
+ const port = options.port ?? DEFAULT_PORT;
106
+ const mcpPath = normalizePath(options.path ?? DEFAULT_PATH);
107
+ const healthPath = `${mcpPath}/health`;
108
+ const sessionIdleMs = options.sessionIdleMs ?? DEFAULT_SESSION_IDLE_MS;
109
+ if (!isLoopback(host)) throw new Error("--host must be 127.0.0.1, ::1, or localhost");
110
+ if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error("--port must be an integer from 0 to 65535");
111
+ if (!Number.isInteger(sessionIdleMs) || sessionIdleMs <= 0) throw new Error("--session-idle-ms must be a positive integer");
112
+ const sessions = /* @__PURE__ */ new Map();
113
+ let openedSessions = 0;
114
+ let closedSessions = 0;
115
+ let closing = false;
116
+ const closeSession = async (session) => {
117
+ if (session.closing) return;
118
+ session.closing = true;
119
+ if (session.id) sessions.delete(session.id);
120
+ closedSessions += 1;
121
+ await Promise.allSettled([session.transport.close(), session.bridge.close()]);
122
+ };
123
+ const sweep = () => {
124
+ const cutoff = Date.now() - sessionIdleMs;
125
+ for (const session of sessions.values()) {
126
+ if (session.lastSeenAt < cutoff) void closeSession(session);
127
+ }
128
+ };
129
+ const sweepTimer = setInterval(sweep, Math.min(sessionIdleMs, 6e4));
130
+ sweepTimer.unref();
131
+ const server = createServer(async (req, res) => {
132
+ const path = requestPath(req);
133
+ if (path === healthPath || path === "/health") {
134
+ reply(res, 200, {
135
+ status: "healthy",
136
+ report: {
137
+ bridgeRuntime: "agent-bridge",
138
+ transport: "streamable-http",
139
+ sessions: { active: sessions.size, opened: openedSessions, closed: closedSessions, idleTimeoutMs: sessionIdleMs }
140
+ }
141
+ });
142
+ return;
143
+ }
144
+ if (path !== mcpPath) return jsonRpcError(res, 404, -32e3, "Not Found");
145
+ if (!["POST", "GET", "DELETE"].includes(req.method ?? "")) {
146
+ return jsonRpcError(res, 405, -32e3, "Method Not Allowed");
147
+ }
148
+ sweep();
149
+ try {
150
+ const body = req.method === "POST" ? await readBody(req) : void 0;
151
+ const existingId = sessionId(req);
152
+ if (existingId) {
153
+ const session2 = sessions.get(existingId);
154
+ if (!session2) return jsonRpcError(res, 404, -32e3, "Session not found");
155
+ session2.lastSeenAt = Date.now();
156
+ await session2.transport.handleRequest(req, res, body);
157
+ return;
158
+ }
159
+ if (req.method !== "POST" || !isInitializeRequest(body)) {
160
+ return jsonRpcError(res, 400, -32e3, "initialize first or provide mcp-session-id");
161
+ }
162
+ let session;
163
+ try {
164
+ const bridge = createAgentBridgeServer(configFromEnv());
165
+ session = {
166
+ bridge,
167
+ transport: new StreamableHTTPServerTransport({
168
+ sessionIdGenerator: randomUUID,
169
+ onsessioninitialized: (id) => {
170
+ if (!session) return;
171
+ session.id = id;
172
+ session.lastSeenAt = Date.now();
173
+ sessions.set(id, session);
174
+ }
175
+ }),
176
+ lastSeenAt: Date.now(),
177
+ closing: false
178
+ };
179
+ session.transport.onclose = () => {
180
+ if (session) void closeSession(session);
181
+ };
182
+ await bridge.connect(session.transport);
183
+ openedSessions += 1;
184
+ await session.transport.handleRequest(req, res, body);
185
+ } finally {
186
+ if (session && !session.id && !session.closing) await closeSession(session);
187
+ }
188
+ } catch (error) {
189
+ if (!res.headersSent) {
190
+ const message = error instanceof Error ? error.message : String(error);
191
+ jsonRpcError(res, 500, -32603, `Internal server error: ${message}`);
192
+ }
193
+ }
194
+ });
195
+ await listen(server, host, port);
196
+ const actualPort = boundPort(server);
197
+ const mcpEndpoint = endpoint(host, actualPort, mcpPath);
198
+ const healthEndpoint = endpoint(host, actualPort, healthPath);
199
+ process.stdout.write(`${JSON.stringify({ status: "ready", endpoint: mcpEndpoint, healthEndpoint })}
200
+ `);
201
+ return {
202
+ endpoint: mcpEndpoint,
203
+ healthEndpoint,
204
+ close: async () => {
205
+ if (closing) return;
206
+ closing = true;
207
+ clearInterval(sweepTimer);
208
+ await Promise.allSettled([...sessions.values()].map(closeSession));
209
+ await closeServer(server);
210
+ }
211
+ };
212
+ }
213
+ function option(args, name, fallback) {
214
+ const index = args.indexOf(name);
215
+ if (index < 0) return fallback;
216
+ return args[index + 1] ?? (() => {
217
+ throw new Error(`${name} requires a value`);
218
+ })();
219
+ }
220
+ function usage() {
221
+ return `Usage:
222
+ agent-bridge-loopback-host [--host 127.0.0.1] [--port 8794] [--path /mcp] [--session-idle-ms 43200000]
223
+ agent-bridge-loopback-host --install-launchd --service-name <name> --config <private-backend> --agent <identity> --instance <stable-key> [host options]
224
+ agent-bridge-loopback-host --uninstall-launchd --service-name <name> --port <port>
225
+ `;
226
+ }
227
+ function required(args, name) {
228
+ const value = option(args, name, "");
229
+ if (!value) throw new Error(`${name} requires a value`);
230
+ return value;
231
+ }
232
+ function serviceLabel(name) {
233
+ if (!/^[A-Za-z0-9_-]+$/.test(name)) throw new Error("--service-name must contain only letters, numbers, _ or -");
234
+ return `com.creatornader.agent-bridge.${name}`;
235
+ }
236
+ function launchctl(args) {
237
+ const result = spawnSync("launchctl", args, { encoding: "utf8" });
238
+ if (result.status !== 0 && result.status !== 3 && result.status !== 113) {
239
+ throw new Error(`launchctl ${args[0]} failed: ${(result.stderr || result.stdout || "unknown error").trim()}`);
240
+ }
241
+ }
242
+ function currentUserId() {
243
+ if (!process.getuid) throw new Error("launchd requires a POSIX user identity");
244
+ return process.getuid();
245
+ }
246
+ function launchdPlist(options) {
247
+ const executable = options.executablePath ?? fileURLToPath(import.meta.url);
248
+ const label = serviceLabel(options.serviceName);
249
+ const environment = {
250
+ AGENT_BRIDGE_CONFIG: options.configPath,
251
+ AGENT_BRIDGE_AGENT: options.agent,
252
+ AGENT_BRIDGE_INSTANCE: options.instance
253
+ };
254
+ const encode = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
255
+ const strings = (values) => values.map((value) => ` <string>${encode(value)}</string>`).join("\n");
256
+ const env = Object.entries(environment).map(([key, value]) => ` <key>${key}</key>
257
+ <string>${encode(value)}</string>`).join("\n");
258
+ return `<?xml version="1.0" encoding="UTF-8"?>
259
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
260
+ <plist version="1.0">
261
+ <dict>
262
+ <key>Label</key>
263
+ <string>${label}</string>
264
+ <key>ProgramArguments</key>
265
+ <array>
266
+ ${strings([process.execPath, executable, "--host", options.host, "--port", String(options.port), "--path", options.path, "--session-idle-ms", String(options.sessionIdleMs)])}
267
+ </array>
268
+ <key>EnvironmentVariables</key>
269
+ <dict>
270
+ ${env}
271
+ </dict>
272
+ <key>RunAtLoad</key>
273
+ <true/>
274
+ <key>KeepAlive</key>
275
+ <true/>
276
+ <key>StandardOutPath</key>
277
+ <string>${encode(join(options.home ?? homedir(), ".agent-bridge", "logs", `${options.serviceName}.out.log`))}</string>
278
+ <key>StandardErrorPath</key>
279
+ <string>${encode(join(options.home ?? homedir(), ".agent-bridge", "logs", `${options.serviceName}.err.log`))}</string>
280
+ </dict>
281
+ </plist>
282
+ `;
283
+ }
284
+ async function waitForHealth(healthEndpoint) {
285
+ let lastError = "no response";
286
+ for (let attempt = 0; attempt < 30; attempt += 1) {
287
+ try {
288
+ const response = await fetch(healthEndpoint, { signal: AbortSignal.timeout(1e3) });
289
+ const body = await response.json();
290
+ if (response.ok && body.status === "healthy") return;
291
+ lastError = `HTTP ${response.status}`;
292
+ } catch (error) {
293
+ lastError = error instanceof Error ? error.message : String(error);
294
+ }
295
+ await new Promise((resolve) => setTimeout(resolve, 200));
296
+ }
297
+ throw new Error(`launchd started but ${healthEndpoint} did not become healthy: ${lastError}`);
298
+ }
299
+ async function installLaunchdHost(options) {
300
+ if (process.platform !== "darwin") throw new Error("--install-launchd is supported only on macOS");
301
+ if (!isLoopback(options.host)) throw new Error("--host must be 127.0.0.1, ::1, or localhost");
302
+ if (!Number.isInteger(options.port) || options.port < 1 || options.port > 65535) throw new Error("--port must be an integer from 1 to 65535 for launchd");
303
+ if (!isAbsolute(options.configPath) || !existsSync(options.configPath)) throw new Error(`--config must be an existing absolute path: ${options.configPath}`);
304
+ verifyPrivatePathAccess(options.configPath, "file");
305
+ const home = options.home ?? homedir();
306
+ const label = serviceLabel(options.serviceName);
307
+ const agents = join(home, "Library", "LaunchAgents");
308
+ const logs = join(home, ".agent-bridge", "logs");
309
+ const plistPath = join(agents, `${label}.plist`);
310
+ mkdirSync(agents, { recursive: true, mode: 448 });
311
+ mkdirSync(logs, { recursive: true, mode: 448 });
312
+ chmodSync(agents, 448);
313
+ chmodSync(logs, 448);
314
+ launchctl(["bootout", `gui/${currentUserId()}/${label}`]);
315
+ writeFileSync(plistPath, launchdPlist({ ...options, home }), { mode: 384 });
316
+ chmodSync(plistPath, 384);
317
+ launchctl(["bootstrap", `gui/${currentUserId()}`, plistPath]);
318
+ const healthEndpoint = endpoint(options.host, options.port, `${normalizePath(options.path)}/health`);
319
+ await waitForHealth(healthEndpoint);
320
+ return { label, plistPath, healthEndpoint };
321
+ }
322
+ async function portIsOpen(host, port) {
323
+ return await new Promise((resolve) => {
324
+ const socket = connect({ host, port });
325
+ const finish = (open) => {
326
+ socket.destroy();
327
+ resolve(open);
328
+ };
329
+ socket.once("connect", () => finish(true));
330
+ socket.once("error", () => finish(false));
331
+ socket.setTimeout(1e3, () => finish(false));
332
+ });
333
+ }
334
+ async function waitForPortClosed(host, port) {
335
+ for (let attempt = 0; attempt < 30; attempt += 1) {
336
+ if (!await portIsOpen(host, port)) return;
337
+ await new Promise((resolve) => setTimeout(resolve, 200));
338
+ }
339
+ throw new Error(`launchd unloaded but ${host}:${port} is still accepting connections`);
340
+ }
341
+ async function uninstallLaunchdHost(serviceName, port, host = DEFAULT_HOST, home = homedir()) {
342
+ if (process.platform !== "darwin") throw new Error("--uninstall-launchd is supported only on macOS");
343
+ if (!isLoopback(host)) throw new Error("--host must be 127.0.0.1, ::1, or localhost");
344
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("--port must be an integer from 1 to 65535 for launchd");
345
+ const label = serviceLabel(serviceName);
346
+ const plistPath = join(home, "Library", "LaunchAgents", `${label}.plist`);
347
+ launchctl(["bootout", `gui/${currentUserId()}/${label}`]);
348
+ rmSync(plistPath, { force: true });
349
+ await waitForPortClosed(host, port);
350
+ return { label, plistPath };
351
+ }
352
+ function isMainModule() {
353
+ if (!process.argv[1]) return false;
354
+ try {
355
+ return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
356
+ } catch {
357
+ return false;
358
+ }
359
+ }
360
+ if (isMainModule()) {
361
+ const args = process.argv.slice(2);
362
+ if (args.includes("--help") || args.includes("-h")) process.stdout.write(usage());
363
+ else if (args.includes("--install-launchd")) {
364
+ void (async () => {
365
+ try {
366
+ const result = await installLaunchdHost({
367
+ serviceName: required(args, "--service-name"),
368
+ configPath: required(args, "--config"),
369
+ agent: required(args, "--agent"),
370
+ instance: required(args, "--instance"),
371
+ host: option(args, "--host", DEFAULT_HOST),
372
+ port: Number(option(args, "--port", String(DEFAULT_PORT))),
373
+ path: option(args, "--path", DEFAULT_PATH),
374
+ sessionIdleMs: Number(option(args, "--session-idle-ms", String(DEFAULT_SESSION_IDLE_MS)))
375
+ });
376
+ process.stdout.write(`${JSON.stringify({ status: "installed", ...result })}
377
+ `);
378
+ } catch (error) {
379
+ process.stderr.write(`agent-bridge-loopback-host: ${error instanceof Error ? error.message : String(error)}
380
+ `);
381
+ process.exitCode = 1;
382
+ }
383
+ })();
384
+ } else if (args.includes("--uninstall-launchd")) {
385
+ void (async () => {
386
+ try {
387
+ const result = await uninstallLaunchdHost(
388
+ required(args, "--service-name"),
389
+ Number(required(args, "--port")),
390
+ option(args, "--host", DEFAULT_HOST)
391
+ );
392
+ process.stdout.write(`${JSON.stringify({ status: "uninstalled", ...result })}
393
+ `);
394
+ } catch (error) {
395
+ process.stderr.write(`agent-bridge-loopback-host: ${error instanceof Error ? error.message : String(error)}
396
+ `);
397
+ process.exitCode = 1;
398
+ }
399
+ })();
400
+ } else void startLoopbackHost({
401
+ host: option(args, "--host", DEFAULT_HOST),
402
+ port: Number(option(args, "--port", String(DEFAULT_PORT))),
403
+ path: option(args, "--path", DEFAULT_PATH),
404
+ sessionIdleMs: Number(option(args, "--session-idle-ms", String(DEFAULT_SESSION_IDLE_MS)))
405
+ }).then((host) => {
406
+ const shutdown = () => void host.close().finally(() => process.exit(0));
407
+ process.once("SIGINT", shutdown);
408
+ process.once("SIGTERM", shutdown);
409
+ }).catch((error) => {
410
+ process.stderr.write(`agent-bridge-loopback-host: ${error instanceof Error ? error.message : String(error)}
411
+ `);
412
+ process.exitCode = 1;
413
+ });
414
+ }
415
+ export {
416
+ installLaunchdHost,
417
+ launchdPlist,
418
+ startLoopbackHost,
419
+ uninstallLaunchdHost
420
+ };
421
+ //# sourceMappingURL=loopback-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/loopback-host.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { chmodSync, existsSync, mkdirSync, realpathSync, rmSync, writeFileSync } from \"node:fs\";\nimport { createServer, type IncomingMessage, type Server as HttpServer, type ServerResponse } from \"node:http\";\nimport { connect, type AddressInfo } from \"node:net\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, join } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { isInitializeRequest } from \"@modelcontextprotocol/sdk/types.js\";\nimport { configFromEnv, createAgentBridgeServer } from \"./server.js\";\nimport { verifyPrivatePathAccess } from \"./private-path.js\";\n\nconst DEFAULT_HOST = \"127.0.0.1\";\nconst DEFAULT_PORT = 8794;\nconst DEFAULT_PATH = \"/mcp\";\nconst DEFAULT_SESSION_IDLE_MS = 12 * 60 * 60 * 1000;\nconst MAX_BODY_BYTES = 1024 * 1024;\n\nexport interface LoopbackHostOptions {\n host?: string;\n port?: number;\n path?: string;\n sessionIdleMs?: number;\n}\n\nexport interface LoopbackHost {\n endpoint: string;\n healthEndpoint: string;\n close(): Promise<void>;\n}\n\nexport interface LaunchdInstallOptions extends Required<LoopbackHostOptions> {\n serviceName: string;\n configPath: string;\n agent: string;\n instance: string;\n home?: string;\n executablePath?: string;\n}\n\ninterface Session {\n bridge: ReturnType<typeof createAgentBridgeServer>;\n transport: StreamableHTTPServerTransport;\n id?: string;\n lastSeenAt: number;\n closing: boolean;\n}\n\nfunction normalizePath(raw: string): string {\n const path = raw.startsWith(\"/\") ? raw : `/${raw}`;\n return path.length > 1 ? path.replace(/\\/+$/, \"\") : path;\n}\n\nfunction isLoopback(host: string): boolean {\n return host === \"127.0.0.1\" || host === \"::1\" || host === \"localhost\";\n}\n\nfunction endpoint(host: string, port: number, path: string): string {\n return `http://${host.includes(\":\") ? `[${host}]` : host}:${port}${path}`;\n}\n\nfunction requestPath(req: IncomingMessage): string {\n try {\n return new URL(req.url ?? \"/\", \"http://localhost\").pathname;\n } catch {\n return \"/\";\n }\n}\n\nfunction sessionId(req: IncomingMessage): string | undefined {\n const value = req.headers[\"mcp-session-id\"];\n return Array.isArray(value) ? value[0] : value;\n}\n\nfunction reply(res: ServerResponse, status: number, body: unknown): void {\n const bytes = Buffer.from(JSON.stringify(body));\n res.writeHead(status, {\n \"content-type\": \"application/json; charset=utf-8\",\n \"content-length\": bytes.length,\n });\n res.end(bytes);\n}\n\nfunction jsonRpcError(res: ServerResponse, status: number, code: number, message: string): void {\n reply(res, status, { jsonrpc: \"2.0\", error: { code, message }, id: null });\n}\n\nasync function readBody(req: IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = [];\n let length = 0;\n for await (const chunk of req) {\n const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n length += bytes.length;\n if (length > MAX_BODY_BYTES) throw new Error(`request body exceeds ${MAX_BODY_BYTES} bytes`);\n chunks.push(bytes);\n }\n const value = Buffer.concat(chunks).toString(\"utf8\").trim();\n return value ? JSON.parse(value) : undefined;\n}\n\nfunction listen(server: HttpServer, host: string, port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const onError = (error: Error) => {\n server.off(\"listening\", onListening);\n reject(error);\n };\n const onListening = () => {\n server.off(\"error\", onError);\n resolve();\n };\n server.once(\"error\", onError);\n server.once(\"listening\", onListening);\n server.listen(port, host);\n });\n}\n\nfunction closeServer(server: HttpServer): Promise<void> {\n return new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));\n}\n\nfunction boundPort(server: HttpServer): number {\n const address = server.address();\n if (!address || typeof address === \"string\") throw new Error(\"loopback host did not bind a TCP port\");\n return (address as AddressInfo).port;\n}\n\nexport async function startLoopbackHost(options: LoopbackHostOptions = {}): Promise<LoopbackHost> {\n const host = options.host ?? DEFAULT_HOST;\n const port = options.port ?? DEFAULT_PORT;\n const mcpPath = normalizePath(options.path ?? DEFAULT_PATH);\n const healthPath = `${mcpPath}/health`;\n const sessionIdleMs = options.sessionIdleMs ?? DEFAULT_SESSION_IDLE_MS;\n if (!isLoopback(host)) throw new Error(\"--host must be 127.0.0.1, ::1, or localhost\");\n if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error(\"--port must be an integer from 0 to 65535\");\n if (!Number.isInteger(sessionIdleMs) || sessionIdleMs <= 0) throw new Error(\"--session-idle-ms must be a positive integer\");\n\n const sessions = new Map<string, Session>();\n let openedSessions = 0;\n let closedSessions = 0;\n let closing = false;\n const closeSession = async (session: Session): Promise<void> => {\n if (session.closing) return;\n session.closing = true;\n if (session.id) sessions.delete(session.id);\n closedSessions += 1;\n await Promise.allSettled([session.transport.close(), session.bridge.close()]);\n };\n const sweep = (): void => {\n const cutoff = Date.now() - sessionIdleMs;\n for (const session of sessions.values()) {\n if (session.lastSeenAt < cutoff) void closeSession(session);\n }\n };\n const sweepTimer = setInterval(sweep, Math.min(sessionIdleMs, 60_000));\n sweepTimer.unref();\n\n const server = createServer(async (req, res) => {\n const path = requestPath(req);\n if (path === healthPath || path === \"/health\") {\n reply(res, 200, {\n status: \"healthy\",\n report: {\n bridgeRuntime: \"agent-bridge\",\n transport: \"streamable-http\",\n sessions: { active: sessions.size, opened: openedSessions, closed: closedSessions, idleTimeoutMs: sessionIdleMs },\n },\n });\n return;\n }\n if (path !== mcpPath) return jsonRpcError(res, 404, -32000, \"Not Found\");\n if (![\"POST\", \"GET\", \"DELETE\"].includes(req.method ?? \"\")) {\n return jsonRpcError(res, 405, -32000, \"Method Not Allowed\");\n }\n sweep();\n try {\n const body = req.method === \"POST\" ? await readBody(req) : undefined;\n const existingId = sessionId(req);\n if (existingId) {\n const session = sessions.get(existingId);\n if (!session) return jsonRpcError(res, 404, -32000, \"Session not found\");\n session.lastSeenAt = Date.now();\n await session.transport.handleRequest(req, res, body);\n return;\n }\n if (req.method !== \"POST\" || !isInitializeRequest(body)) {\n return jsonRpcError(res, 400, -32000, \"initialize first or provide mcp-session-id\");\n }\n let session: Session | undefined;\n try {\n const bridge = createAgentBridgeServer(configFromEnv());\n session = {\n bridge,\n transport: new StreamableHTTPServerTransport({\n sessionIdGenerator: randomUUID,\n onsessioninitialized: (id) => {\n if (!session) return;\n session.id = id;\n session.lastSeenAt = Date.now();\n sessions.set(id, session);\n },\n }),\n lastSeenAt: Date.now(),\n closing: false,\n };\n session.transport.onclose = () => { if (session) void closeSession(session); };\n await bridge.connect(session.transport);\n openedSessions += 1;\n await session.transport.handleRequest(req, res, body);\n } finally {\n if (session && !session.id && !session.closing) await closeSession(session);\n }\n } catch (error) {\n if (!res.headersSent) {\n const message = error instanceof Error ? error.message : String(error);\n jsonRpcError(res, 500, -32603, `Internal server error: ${message}`);\n }\n }\n });\n await listen(server, host, port);\n const actualPort = boundPort(server);\n const mcpEndpoint = endpoint(host, actualPort, mcpPath);\n const healthEndpoint = endpoint(host, actualPort, healthPath);\n process.stdout.write(`${JSON.stringify({ status: \"ready\", endpoint: mcpEndpoint, healthEndpoint })}\\n`);\n\n return {\n endpoint: mcpEndpoint,\n healthEndpoint,\n close: async () => {\n if (closing) return;\n closing = true;\n clearInterval(sweepTimer);\n await Promise.allSettled([...sessions.values()].map(closeSession));\n await closeServer(server);\n },\n };\n}\n\nfunction option(args: string[], name: string, fallback: string): string {\n const index = args.indexOf(name);\n if (index < 0) return fallback;\n return args[index + 1] ?? (() => { throw new Error(`${name} requires a value`); })();\n}\n\nfunction usage(): string {\n return `Usage:\n agent-bridge-loopback-host [--host 127.0.0.1] [--port 8794] [--path /mcp] [--session-idle-ms 43200000]\n agent-bridge-loopback-host --install-launchd --service-name <name> --config <private-backend> --agent <identity> --instance <stable-key> [host options]\n agent-bridge-loopback-host --uninstall-launchd --service-name <name> --port <port>\n`;\n}\n\nfunction required(args: string[], name: string): string {\n const value = option(args, name, \"\");\n if (!value) throw new Error(`${name} requires a value`);\n return value;\n}\n\nfunction serviceLabel(name: string): string {\n if (!/^[A-Za-z0-9_-]+$/.test(name)) throw new Error(\"--service-name must contain only letters, numbers, _ or -\");\n return `com.creatornader.agent-bridge.${name}`;\n}\n\nfunction launchctl(args: string[]): void {\n const result = spawnSync(\"launchctl\", args, { encoding: \"utf8\" });\n if (result.status !== 0 && result.status !== 3 && result.status !== 113) {\n throw new Error(`launchctl ${args[0]} failed: ${(result.stderr || result.stdout || \"unknown error\").trim()}`);\n }\n}\n\nfunction currentUserId(): number {\n if (!process.getuid) throw new Error(\"launchd requires a POSIX user identity\");\n return process.getuid();\n}\n\nexport function launchdPlist(options: LaunchdInstallOptions): string {\n const executable = options.executablePath ?? fileURLToPath(import.meta.url);\n const label = serviceLabel(options.serviceName);\n const environment = {\n AGENT_BRIDGE_CONFIG: options.configPath,\n AGENT_BRIDGE_AGENT: options.agent,\n AGENT_BRIDGE_INSTANCE: options.instance,\n };\n const encode = (value: string): string => value.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n const strings = (values: string[]): string => values.map((value) => ` <string>${encode(value)}</string>`).join(\"\\n\");\n const env = Object.entries(environment).map(([key, value]) => ` <key>${key}</key>\\n <string>${encode(value)}</string>`).join(\"\\n\");\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${label}</string>\n <key>ProgramArguments</key>\n <array>\n${strings([process.execPath, executable, \"--host\", options.host, \"--port\", String(options.port), \"--path\", options.path, \"--session-idle-ms\", String(options.sessionIdleMs)])}\n </array>\n <key>EnvironmentVariables</key>\n <dict>\n${env}\n </dict>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>StandardOutPath</key>\n <string>${encode(join(options.home ?? homedir(), \".agent-bridge\", \"logs\", `${options.serviceName}.out.log`))}</string>\n <key>StandardErrorPath</key>\n <string>${encode(join(options.home ?? homedir(), \".agent-bridge\", \"logs\", `${options.serviceName}.err.log`))}</string>\n</dict>\n</plist>\n`;\n}\n\nasync function waitForHealth(healthEndpoint: string): Promise<void> {\n let lastError = \"no response\";\n for (let attempt = 0; attempt < 30; attempt += 1) {\n try {\n const response = await fetch(healthEndpoint, { signal: AbortSignal.timeout(1_000) });\n const body = await response.json() as { status?: unknown };\n if (response.ok && body.status === \"healthy\") return;\n lastError = `HTTP ${response.status}`;\n } catch (error) {\n lastError = error instanceof Error ? error.message : String(error);\n }\n await new Promise((resolve) => setTimeout(resolve, 200));\n }\n throw new Error(`launchd started but ${healthEndpoint} did not become healthy: ${lastError}`);\n}\n\nexport async function installLaunchdHost(options: LaunchdInstallOptions): Promise<{ label: string; plistPath: string; healthEndpoint: string }> {\n if (process.platform !== \"darwin\") throw new Error(\"--install-launchd is supported only on macOS\");\n if (!isLoopback(options.host)) throw new Error(\"--host must be 127.0.0.1, ::1, or localhost\");\n if (!Number.isInteger(options.port) || options.port < 1 || options.port > 65535) throw new Error(\"--port must be an integer from 1 to 65535 for launchd\");\n if (!isAbsolute(options.configPath) || !existsSync(options.configPath)) throw new Error(`--config must be an existing absolute path: ${options.configPath}`);\n verifyPrivatePathAccess(options.configPath, \"file\");\n const home = options.home ?? homedir();\n const label = serviceLabel(options.serviceName);\n const agents = join(home, \"Library\", \"LaunchAgents\");\n const logs = join(home, \".agent-bridge\", \"logs\");\n const plistPath = join(agents, `${label}.plist`);\n mkdirSync(agents, { recursive: true, mode: 0o700 });\n mkdirSync(logs, { recursive: true, mode: 0o700 });\n chmodSync(agents, 0o700);\n chmodSync(logs, 0o700);\n launchctl([\"bootout\", `gui/${currentUserId()}/${label}`]);\n writeFileSync(plistPath, launchdPlist({ ...options, home }), { mode: 0o600 });\n chmodSync(plistPath, 0o600);\n launchctl([\"bootstrap\", `gui/${currentUserId()}`, plistPath]);\n const healthEndpoint = endpoint(options.host, options.port, `${normalizePath(options.path)}/health`);\n await waitForHealth(healthEndpoint);\n return { label, plistPath, healthEndpoint };\n}\n\nasync function portIsOpen(host: string, port: number): Promise<boolean> {\n return await new Promise((resolve) => {\n const socket = connect({ host, port });\n const finish = (open: boolean) => { socket.destroy(); resolve(open); };\n socket.once(\"connect\", () => finish(true));\n socket.once(\"error\", () => finish(false));\n socket.setTimeout(1_000, () => finish(false));\n });\n}\n\nasync function waitForPortClosed(host: string, port: number): Promise<void> {\n for (let attempt = 0; attempt < 30; attempt += 1) {\n if (!await portIsOpen(host, port)) return;\n await new Promise((resolve) => setTimeout(resolve, 200));\n }\n throw new Error(`launchd unloaded but ${host}:${port} is still accepting connections`);\n}\n\nexport async function uninstallLaunchdHost(\n serviceName: string,\n port: number,\n host = DEFAULT_HOST,\n home = homedir(),\n): Promise<{ label: string; plistPath: string }> {\n if (process.platform !== \"darwin\") throw new Error(\"--uninstall-launchd is supported only on macOS\");\n if (!isLoopback(host)) throw new Error(\"--host must be 127.0.0.1, ::1, or localhost\");\n if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(\"--port must be an integer from 1 to 65535 for launchd\");\n const label = serviceLabel(serviceName);\n const plistPath = join(home, \"Library\", \"LaunchAgents\", `${label}.plist`);\n launchctl([\"bootout\", `gui/${currentUserId()}/${label}`]);\n rmSync(plistPath, { force: true });\n await waitForPortClosed(host, port);\n return { label, plistPath };\n}\n\nfunction isMainModule(): boolean {\n if (!process.argv[1]) return false;\n try {\n return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;\n } catch {\n return false;\n }\n}\n\nif (isMainModule()) {\n const args = process.argv.slice(2);\n if (args.includes(\"--help\") || args.includes(\"-h\")) process.stdout.write(usage());\n else if (args.includes(\"--install-launchd\")) {\n void (async () => {\n try {\n const result = await installLaunchdHost({\n serviceName: required(args, \"--service-name\"),\n configPath: required(args, \"--config\"),\n agent: required(args, \"--agent\"),\n instance: required(args, \"--instance\"),\n host: option(args, \"--host\", DEFAULT_HOST),\n port: Number(option(args, \"--port\", String(DEFAULT_PORT))),\n path: option(args, \"--path\", DEFAULT_PATH),\n sessionIdleMs: Number(option(args, \"--session-idle-ms\", String(DEFAULT_SESSION_IDLE_MS))),\n });\n process.stdout.write(`${JSON.stringify({ status: \"installed\", ...result })}\\n`);\n } catch (error) {\n process.stderr.write(`agent-bridge-loopback-host: ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n }\n })();\n } else if (args.includes(\"--uninstall-launchd\")) {\n void (async () => {\n try {\n const result = await uninstallLaunchdHost(\n required(args, \"--service-name\"),\n Number(required(args, \"--port\")),\n option(args, \"--host\", DEFAULT_HOST),\n );\n process.stdout.write(`${JSON.stringify({ status: \"uninstalled\", ...result })}\\n`);\n } catch (error) {\n process.stderr.write(`agent-bridge-loopback-host: ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n }\n })();\n }\n else void startLoopbackHost({\n host: option(args, \"--host\", DEFAULT_HOST),\n port: Number(option(args, \"--port\", String(DEFAULT_PORT))),\n path: option(args, \"--path\", DEFAULT_PATH),\n sessionIdleMs: Number(option(args, \"--session-idle-ms\", String(DEFAULT_SESSION_IDLE_MS))),\n }).then((host) => {\n const shutdown = () => void host.close().finally(() => process.exit(0));\n process.once(\"SIGINT\", shutdown);\n process.once(\"SIGTERM\", shutdown);\n }).catch((error) => {\n process.stderr.write(`agent-bridge-loopback-host: ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n });\n}\n"],"mappings":"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","names":["session"]}
@@ -0,0 +1,10 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+
3
+ interface StdioHttpProxyRuntime {
4
+ server: Server;
5
+ close(): Promise<void>;
6
+ }
7
+ declare function createStdioHttpProxyRuntime(endpoint: string): Promise<StdioHttpProxyRuntime>;
8
+ declare function startStdioHttpProxy(endpoint: string): Promise<void>;
9
+
10
+ export { type StdioHttpProxyRuntime, createStdioHttpProxyRuntime, startStdioHttpProxy };
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ validateLoopbackMcpUrl
4
+ } from "./chunk-JKKIGIO5.js";
5
+ import "./chunk-UKFRCP6X.js";
6
+ import "./chunk-GTK4KOED.js";
7
+ import {
8
+ packageVersion
9
+ } from "./chunk-7B3XRRNI.js";
10
+
11
+ // src/stdio-http-proxy.ts
12
+ import { realpathSync } from "fs";
13
+ import { pathToFileURL } from "url";
14
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
15
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
16
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
17
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
19
+ async function createStdioHttpProxyRuntime(endpoint) {
20
+ const url = validateLoopbackMcpUrl(endpoint, "--endpoint");
21
+ const upstreamTransport = new StreamableHTTPClientTransport(new URL(url));
22
+ const upstream = new Client({ name: "agent-bridge-stdio-http-proxy", version: packageVersion() });
23
+ await upstream.connect(upstreamTransport);
24
+ const listed = await upstream.listTools();
25
+ const server = new Server(
26
+ { name: "agent-bridge-stdio-http-proxy", version: packageVersion() },
27
+ { capabilities: { tools: {} } }
28
+ );
29
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: listed.tools }));
30
+ server.setRequestHandler(CallToolRequestSchema, async (request) => upstream.callTool(request.params));
31
+ return {
32
+ server,
33
+ close: async () => {
34
+ await Promise.allSettled([server.close(), upstream.close(), upstreamTransport.close()]);
35
+ }
36
+ };
37
+ }
38
+ async function startStdioHttpProxy(endpoint) {
39
+ const runtime = await createStdioHttpProxyRuntime(endpoint);
40
+ const transport = new StdioServerTransport();
41
+ const shutdown = () => void Promise.allSettled([transport.close(), runtime.close()]).finally(() => process.exit(0));
42
+ process.once("SIGINT", shutdown);
43
+ process.once("SIGTERM", shutdown);
44
+ await runtime.server.connect(transport);
45
+ }
46
+ function usage() {
47
+ return "Usage: agent-bridge-stdio-http-proxy --endpoint http://127.0.0.1:<port>/mcp\n";
48
+ }
49
+ function isMainModule() {
50
+ if (!process.argv[1]) return false;
51
+ try {
52
+ return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+ if (isMainModule()) {
58
+ const args = process.argv.slice(2);
59
+ if (args.includes("--help") || args.includes("-h")) process.stdout.write(usage());
60
+ else {
61
+ const index = args.indexOf("--endpoint");
62
+ const endpoint = index < 0 ? void 0 : args[index + 1];
63
+ if (!endpoint || args.length !== 2) {
64
+ process.stderr.write(`agent-bridge-stdio-http-proxy: ${usage()}`);
65
+ process.exitCode = 1;
66
+ } else {
67
+ void startStdioHttpProxy(endpoint).catch((error) => {
68
+ process.stderr.write(`agent-bridge-stdio-http-proxy: ${error instanceof Error ? error.message : String(error)}
69
+ `);
70
+ process.exitCode = 1;
71
+ });
72
+ }
73
+ }
74
+ }
75
+ export {
76
+ createStdioHttpProxyRuntime,
77
+ startStdioHttpProxy
78
+ };
79
+ //# sourceMappingURL=stdio-http-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stdio-http-proxy.ts"],"sourcesContent":["import { realpathSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CallToolRequestSchema, ListToolsRequestSchema, type CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { validateLoopbackMcpUrl } from \"./client-lifecycle.js\";\nimport { packageVersion } from \"./package-metadata.js\";\n\nexport interface StdioHttpProxyRuntime {\n server: Server;\n close(): Promise<void>;\n}\n\nexport async function createStdioHttpProxyRuntime(endpoint: string): Promise<StdioHttpProxyRuntime> {\n const url = validateLoopbackMcpUrl(endpoint, \"--endpoint\");\n const upstreamTransport = new StreamableHTTPClientTransport(new URL(url));\n const upstream = new Client({ name: \"agent-bridge-stdio-http-proxy\", version: packageVersion() });\n await upstream.connect(upstreamTransport);\n const listed = await upstream.listTools();\n const server = new Server(\n { name: \"agent-bridge-stdio-http-proxy\", version: packageVersion() },\n { capabilities: { tools: {} } },\n );\n server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: listed.tools }));\n server.setRequestHandler(CallToolRequestSchema, async (request) =>\n upstream.callTool(request.params) as Promise<CallToolResult>);\n return {\n server,\n close: async () => {\n await Promise.allSettled([server.close(), upstream.close(), upstreamTransport.close()]);\n },\n };\n}\n\nexport async function startStdioHttpProxy(endpoint: string): Promise<void> {\n const runtime = await createStdioHttpProxyRuntime(endpoint);\n const transport = new StdioServerTransport();\n const shutdown = () => void Promise.allSettled([transport.close(), runtime.close()]).finally(() => process.exit(0));\n process.once(\"SIGINT\", shutdown);\n process.once(\"SIGTERM\", shutdown);\n await runtime.server.connect(transport);\n}\n\nfunction usage(): string {\n return \"Usage: agent-bridge-stdio-http-proxy --endpoint http://127.0.0.1:<port>/mcp\\n\";\n}\n\nfunction isMainModule(): boolean {\n if (!process.argv[1]) return false;\n try {\n return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;\n } catch {\n return false;\n }\n}\n\nif (isMainModule()) {\n const args = process.argv.slice(2);\n if (args.includes(\"--help\") || args.includes(\"-h\")) process.stdout.write(usage());\n else {\n const index = args.indexOf(\"--endpoint\");\n const endpoint = index < 0 ? undefined : args[index + 1];\n if (!endpoint || args.length !== 2) {\n process.stderr.write(`agent-bridge-stdio-http-proxy: ${usage()}`);\n process.exitCode = 1;\n } else {\n void startStdioHttpProxy(endpoint).catch((error) => {\n process.stderr.write(`agent-bridge-stdio-http-proxy: ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n });\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AAAA,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,cAAc;AACvB,SAAS,qCAAqC;AAC9C,SAAS,cAAc;AACvB,SAAS,4BAA4B;AACrC,SAAS,uBAAuB,8BAAmD;AASnF,eAAsB,4BAA4B,UAAkD;AAClG,QAAM,MAAM,uBAAuB,UAAU,YAAY;AACzD,QAAM,oBAAoB,IAAI,8BAA8B,IAAI,IAAI,GAAG,CAAC;AACxE,QAAM,WAAW,IAAI,OAAO,EAAE,MAAM,iCAAiC,SAAS,eAAe,EAAE,CAAC;AAChG,QAAM,SAAS,QAAQ,iBAAiB;AACxC,QAAM,SAAS,MAAM,SAAS,UAAU;AACxC,QAAM,SAAS,IAAI;AAAA,IACjB,EAAE,MAAM,iCAAiC,SAAS,eAAe,EAAE;AAAA,IACnE,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,EAAE;AAAA,EAChC;AACA,SAAO,kBAAkB,wBAAwB,aAAa,EAAE,OAAO,OAAO,MAAM,EAAE;AACtF,SAAO,kBAAkB,uBAAuB,OAAO,YACrD,SAAS,SAAS,QAAQ,MAAM,CAA4B;AAC9D,SAAO;AAAA,IACL;AAAA,IACA,OAAO,YAAY;AACjB,YAAM,QAAQ,WAAW,CAAC,OAAO,MAAM,GAAG,SAAS,MAAM,GAAG,kBAAkB,MAAM,CAAC,CAAC;AAAA,IACxF;AAAA,EACF;AACF;AAEA,eAAsB,oBAAoB,UAAiC;AACzE,QAAM,UAAU,MAAM,4BAA4B,QAAQ;AAC1D,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,WAAW,MAAM,KAAK,QAAQ,WAAW,CAAC,UAAU,MAAM,GAAG,QAAQ,MAAM,CAAC,CAAC,EAAE,QAAQ,MAAM,QAAQ,KAAK,CAAC,CAAC;AAClH,UAAQ,KAAK,UAAU,QAAQ;AAC/B,UAAQ,KAAK,WAAW,QAAQ;AAChC,QAAM,QAAQ,OAAO,QAAQ,SAAS;AACxC;AAEA,SAAS,QAAgB;AACvB,SAAO;AACT;AAEA,SAAS,eAAwB;AAC/B,MAAI,CAAC,QAAQ,KAAK,CAAC,EAAG,QAAO;AAC7B,MAAI;AACF,WAAO,YAAY,QAAQ,cAAc,aAAa,QAAQ,KAAK,CAAC,CAAC,CAAC,EAAE;AAAA,EAC1E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAI,aAAa,GAAG;AAClB,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,MAAI,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,IAAI,EAAG,SAAQ,OAAO,MAAM,MAAM,CAAC;AAAA,OAC3E;AACH,UAAM,QAAQ,KAAK,QAAQ,YAAY;AACvC,UAAM,WAAW,QAAQ,IAAI,SAAY,KAAK,QAAQ,CAAC;AACvD,QAAI,CAAC,YAAY,KAAK,WAAW,GAAG;AAClC,cAAQ,OAAO,MAAM,kCAAkC,MAAM,CAAC,EAAE;AAChE,cAAQ,WAAW;AAAA,IACrB,OAAO;AACL,WAAK,oBAAoB,QAAQ,EAAE,MAAM,CAAC,UAAU;AAClD,gBAAQ,OAAO,MAAM,kCAAkC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,CAAI;AACjH,gBAAQ,WAAW;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
@@ -405,6 +405,17 @@ explicit loopback HTTP URL with a port and `/mcp` path, without user information
405
405
  query, fragment, bearer-token setting, or custom header. Backend credentials remain in
406
406
  their existing private file and do not enter the native HTTP registration.
407
407
 
408
+ The public loopback host is an optional client-side process boundary for native HTTP
409
+ targets. One host process owns one exact client identity, private backend file, stable
410
+ instance, and local edge store. It binds only an explicit loopback address and exposes
411
+ `/mcp/health` as socket-backed liveness. It creates an MCP server per transport session
412
+ and closes that server when the session closes or becomes idle. The host does not share
413
+ one credential across client identities, hold SQLite's write lease during gateway I/O,
414
+ or replace the durable edge outbox and inbox semantics. On macOS, its public launchd
415
+ installer avoids scheduler priority hints and requires a successful health request
416
+ before a client registration may depend on the endpoint. Optional wrappers belong above
417
+ this host boundary and cannot be required for client availability.
418
+
408
419
  Claude Desktop represents an endpoint-backed target as an exact stdio proxy launch.
409
420
  The proxy command must be an existing absolute executable. Its bounded arguments must
410
421
  contain the exact loopback endpoint and no credential-like flags or values. Claude