@creatornader/agent-bridge 0.7.3 → 0.7.7

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/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@ All notable changes to agent-bridge are documented in this file.
4
4
 
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [Unreleased]
8
+
9
+ ## [0.7.7] - 2026-07-30
10
+
11
+ ### Fixed
12
+
13
+ - Publish the packed package before checking npm for immutable release recovery.
14
+
15
+ ## [0.7.6] - 2026-07-30
16
+
17
+ ### Fixed
18
+
19
+ - Read release package metadata directly from the verified tarball before checking
20
+ the immutable npm version during recovery.
21
+
22
+ ## [0.7.5] - 2026-07-30
23
+
24
+ ### Fixed
25
+
26
+ - Create a canonical immutable GitHub Release only after the tagged gateway and npm
27
+ publication gates succeed. Release pages now use the same tag and `Agent Bridge
28
+ v<version>` title for every new version.
29
+ - Make release recovery verify an already published package from the packed artifact
30
+ instead of failing on an immutable npm version.
31
+
32
+ ## [0.7.4] - 2026-07-30
33
+
34
+ ### Fixed
35
+
36
+ - Resolve npm-managed executable symlinks before checking whether the loopback host
37
+ or stdio proxy is the invoked module. Packed-install smoke coverage now invokes
38
+ both public executables.
39
+
7
40
  ## [0.7.3] - 2026-07-30
8
41
 
9
42
  ### Added
@@ -520,7 +553,14 @@ First tagged release. Marks the point where agent-bridge has shipped its initial
520
553
  - Narrative-leak detection in CI + on commit via `creatornader/textleaks@v0.2.0` (renamed from leakguard).
521
554
 
522
555
  [0.1.0]: https://github.com/creatornader/agent-bridge/releases/tag/v0.1.0
523
- [Unreleased]: https://github.com/creatornader/agent-bridge/compare/v0.6.4...HEAD
556
+ [Unreleased]: https://github.com/creatornader/agent-bridge/compare/v0.7.7...HEAD
557
+ [0.7.7]: https://github.com/creatornader/agent-bridge/compare/v0.7.6...v0.7.7
558
+ [0.7.6]: https://github.com/creatornader/agent-bridge/compare/v0.7.5...v0.7.6
559
+ [0.7.5]: https://github.com/creatornader/agent-bridge/compare/v0.7.4...v0.7.5
560
+ [0.7.4]: https://github.com/creatornader/agent-bridge/compare/v0.7.3...v0.7.4
561
+ [0.7.3]: https://github.com/creatornader/agent-bridge/compare/v0.7.2...v0.7.3
562
+ [0.7.2]: https://github.com/creatornader/agent-bridge/compare/v0.7.1...v0.7.2
563
+ [0.7.1]: https://github.com/creatornader/agent-bridge/compare/v0.7.0...v0.7.1
524
564
  [0.6.4]: https://github.com/creatornader/agent-bridge/compare/v0.6.3...v0.6.4
525
565
  [0.6.3]: https://github.com/creatornader/agent-bridge/compare/v0.6.2...v0.6.3
526
566
  [0.6.2]: https://github.com/creatornader/agent-bridge/compare/v0.6.1...v0.6.2
package/README.md CHANGED
@@ -150,7 +150,9 @@ The authenticated check compares the running package and Git revision with the
150
150
  intended release. It also requires protocol 2.1, request authority, and row isolation.
151
151
  It prints no credential. A missing or different revision stops the rollout.
152
152
 
153
- Release tags run this deployment gate before npm publication. The release environment
153
+ Release tags run this deployment gate before npm publication. After the package is
154
+ published, the workflow creates the immutable GitHub Release for the same tag with
155
+ the canonical title `Agent Bridge v<version>`. The release environment
154
156
  must define `AGENT_BRIDGE_FLY_APP` and `AGENT_BRIDGE_GATEWAY_ORIGIN`, and keep
155
157
  `FLY_API_TOKEN` plus `RELEASE_VERIFY_TOKEN` as protected secrets. The manual `gateway
156
158
  production proof` workflow tests the deployed gateway after this gate passes. GitHub
@@ -19,7 +19,7 @@ import "./chunk-BZLMF5O5.js";
19
19
 
20
20
  // src/loopback-host.ts
21
21
  import { randomUUID } from "crypto";
22
- import { chmodSync, existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
22
+ import { chmodSync, existsSync, mkdirSync, realpathSync, rmSync, writeFileSync } from "fs";
23
23
  import { createServer } from "http";
24
24
  import { connect } from "net";
25
25
  import { homedir } from "os";
@@ -349,7 +349,15 @@ async function uninstallLaunchdHost(serviceName, port, host = DEFAULT_HOST, home
349
349
  await waitForPortClosed(host, port);
350
350
  return { label, plistPath };
351
351
  }
352
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
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()) {
353
361
  const args = process.argv.slice(2);
354
362
  if (args.includes("--help") || args.includes("-h")) process.stdout.write(usage());
355
363
  else if (args.includes("--install-launchd")) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/loopback-host.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { chmodSync, existsSync, mkdirSync, 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\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\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"]}
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"]}
@@ -9,6 +9,7 @@ import {
9
9
  } from "./chunk-7B3XRRNI.js";
10
10
 
11
11
  // src/stdio-http-proxy.ts
12
+ import { realpathSync } from "fs";
12
13
  import { pathToFileURL } from "url";
13
14
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
14
15
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -45,7 +46,15 @@ async function startStdioHttpProxy(endpoint) {
45
46
  function usage() {
46
47
  return "Usage: agent-bridge-stdio-http-proxy --endpoint http://127.0.0.1:<port>/mcp\n";
47
48
  }
48
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
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()) {
49
58
  const args = process.argv.slice(2);
50
59
  if (args.includes("--help") || args.includes("-h")) process.stdout.write(usage());
51
60
  else {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/stdio-http-proxy.ts"],"sourcesContent":["import { 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\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\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,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,IAAI,QAAQ,KAAK,CAAC,KAAK,YAAY,QAAQ,cAAc,QAAQ,KAAK,CAAC,CAAC,EAAE,MAAM;AAC9E,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":[]}
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":[]}
@@ -271,7 +271,9 @@ messages before choosing one authority. If schema or data recovery is required,
271
271
  restore the verified native DR bundle into a fresh target and switch authority once.
272
272
  Do not run the source and restored database as active authorities at the same time.
273
273
 
274
- Release tags deploy the exact tagged revision before npm publication. The release job
274
+ Release tags deploy the exact tagged revision before npm publication. After successful
275
+ package publication, the release job creates the immutable GitHub Release for that
276
+ same tag. The release job
275
277
  uses the approval-protected `agent-bridge-production-proof` environment. Configure
276
278
  `AGENT_BRIDGE_FLY_APP` and `AGENT_BRIDGE_GATEWAY_ORIGIN` as environment variables, and
277
279
  configure `PROOF_SENDER_TOKEN`, `PROOF_RECEIVER_TOKEN`, `PROOF_HOST_SALT`, and
@@ -280,6 +282,12 @@ for the release job. It runs the read-only Fly preflight, deploys the image with
280
282
  checked-out revision, requires `/readyz`, and verifies the running package version and
281
283
  revision. A failed deployment or identity check stops npm publication.
282
284
 
285
+ To recover a release workflow, dispatch it from the release tag itself and provide the
286
+ same tag as the input. The workflow verifies an already published package by its name
287
+ and version from the packed artifact, then leaves that immutable npm version intact.
288
+ It also leaves an existing GitHub Release intact. A recovery run cannot publish a
289
+ different package under the tag.
290
+
283
291
  The manual `gateway production proof` GitHub Actions workflow runs its acceptance
284
292
  proof against that deployed HTTPS gateway. GitHub restricts it to `main`, and every job
285
293
  uses the same approval-protected environment. Use a release-specific proof workspace
@@ -2,17 +2,19 @@
2
2
 
3
3
  ## Status
4
4
 
5
- Resolved for basic Codex, Claude Code, and Claude Desktop availability.
5
+ Resolved for Codex, Claude Code, and Claude Desktop availability.
6
6
 
7
- Codex, Claude Code, and Claude Desktop now launch the source repo MCP server
8
- directly from its built `dist/index.js` entrypoint with a pinned Node binary.
9
- The direct server reads credentials from `~/.agent-bridge/config`, so client
10
- configs no longer need to duplicate the Supabase URL or anon key.
7
+ The public package now owns the local MCP path. Each client has a public
8
+ `agent-bridge-loopback-host` launchd service that binds only its assigned
9
+ loopback port and serves `/mcp/health` plus Streamable HTTP MCP. Claude Desktop
10
+ uses the public `agent-bridge-stdio-http-proxy` to reach its local host. The
11
+ client services read owner-private backend files, so registrations do not need
12
+ to duplicate gateway credentials.
11
13
 
12
14
  The broken Codex, Claude Code, and Claude Desktop signed-wrapper launchd jobs
13
15
  were removed after their labels stayed loaded while `/mcp/health` refused
14
- connections. The wrapper code still exists as an optional signed attribution
15
- layer.
16
+ connections. The wrapper remains an optional signed attribution layer outside
17
+ the public Agent Bridge runtime.
16
18
 
17
19
  ## Impact
18
20
 
@@ -40,43 +42,56 @@ it required `AGENT_BRIDGE_URL` and `AGENT_BRIDGE_KEY` in process env. The CLI
40
42
  already read `~/.agent-bridge/config`, so the two source-repo entrypoints had
41
43
  drifted.
42
44
 
45
+ The first public-host release also exposed a package-distribution defect. npm
46
+ executes package bins through a symlink, while the host and proxy used a source
47
+ file entrypoint check that compared the unresolved `argv[1]` path. Their
48
+ install commands therefore returned success without starting a service.
49
+
50
+ The release workflow published valid npm versions but did not create matching
51
+ GitHub Release objects. This made the release page incomplete and left older
52
+ titles inconsistent.
53
+
43
54
  During recovery, the wrapper installer also selected `node` from ambient
44
55
  `PATH`. That allowed restarts to drift to a local Node shim instead of the
45
56
  Homebrew Node binary that had previously run the service.
46
57
 
47
58
  ## Fixes
48
59
 
49
- - `src/server.ts` now reads `~/.agent-bridge/config` when env credentials are
50
- absent. Env values still take precedence.
51
- - `test/server_factory.test.ts` covers config-file fallback, env precedence,
52
- quoted config values, explicit config paths, and missing credentials.
53
- - Local Codex, Claude Code, and Claude Desktop configs now launch the source
54
- MCP server directly with `/opt/homebrew/bin/node` and only set
55
- `AGENT_BRIDGE_AGENT`.
56
- - The dead Codex, Claude Code, and Claude Desktop wrapper launchd jobs were
60
+ - The public package provides a long-lived loopback host, health endpoint, and
61
+ Claude Desktop stdio proxy. It installs launchd services only after health
62
+ proves the socket is listening, and uninstalls them only after the socket
63
+ closes.
64
+ - The host and proxy resolve the invoked file before deciding whether to run as
65
+ a CLI. Their global npm bins now work through npm's symlink layout.
66
+ - Package-install smoke invokes both public binaries. This catches a silent
67
+ entrypoint failure before publication.
68
+ - Local Codex, Claude Code, and Claude Desktop registrations now point to the
69
+ public client-owned loopback services. The dead private wrapper jobs were
57
70
  uninstalled.
58
- - The internal wrapper installer now prefers `/opt/homebrew/bin/node` by default
59
- while still honoring `NODE_BIN`.
71
+ - Tagged releases now create one immutable GitHub Release after the gateway and
72
+ npm gates pass. New release pages use `Agent Bridge v<version>`. A tag-based
73
+ recovery verifies the immutable package already in npm before continuing.
60
74
 
61
75
  ## Wrapper Policy
62
76
 
63
- Keep the signed wrapper for attribution receipts and local-substrate metadata.
64
- Do not make it the only Agent Bridge path for Codex, Claude Code, or Claude
65
- Desktop.
77
+ Agent Bridge must run fully from its public package. Keep the signed wrapper
78
+ for attribution receipts and local-substrate metadata only. Do not make it an
79
+ availability boundary for Codex, Claude Code, Claude Desktop, or another MCP
80
+ client.
66
81
 
67
- Only reinstall those wrapper jobs as active client targets after the wrapper has
68
- health-based supervision. A loaded launchd label is not proof that Agent Bridge
69
- MCP is available. The proof is a successful `/mcp/health` probe and a real MCP
70
- tool call.
82
+ An active launchd label is not proof that Agent Bridge MCP is available. The
83
+ proof is a successful public `/mcp/health` probe and a real MCP tool call. New
84
+ versions use an immutable `v<version>` tag and the GitHub Release title `Agent
85
+ Bridge v<version>`.
71
86
 
72
87
  ## Verification
73
88
 
74
- - `npm test`: 24 tests passed.
75
- - `npm run build`: passed.
76
- - Direct stdio MCP smoke: listed `post_context`, `get_context`, and
77
- `ack_context`, then read live context.
78
- - CLI status: returned `{"status":"ok","message":"Agent Bridge is connected to Supabase"}`.
79
- - Claude Desktop launch proof: fresh Desktop log showed `initialize` and
80
- `tools/list` succeeding against the direct source MCP entrypoint.
81
- - Wrapper cleanup: Codex, Claude Code, and Claude Desktop wrapper labels are
82
- absent, and their launchd plist files are absent.
89
+ - A global-package launchd install bound a disposable loopback port, passed
90
+ `/mcp/health`, then unbound the port after uninstall.
91
+ - Each production Codex, Claude Code, and Claude Desktop public host passed
92
+ `/mcp/health`, completed an MCP handshake, listed 17 tools, and accepted a
93
+ `post_context` write.
94
+ - Each client registration and its identity, instance, and private backend file
95
+ passed managed-client inspection.
96
+ - The three private wrapper labels are absent. The three public launchd labels
97
+ are running and own ports 8794, 8793, and 8791.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@creatornader/agent-bridge",
3
- "version": "0.7.3",
3
+ "version": "0.7.7",
4
4
  "description": "Let AI agents message each other and hand off work across tools, sessions, and machines.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",