@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 +41 -1
- package/README.md +3 -1
- package/dist/loopback-host.js +10 -2
- package/dist/loopback-host.js.map +1 -1
- package/dist/stdio-http-proxy.js +10 -1
- package/dist/stdio-http-proxy.js.map +1 -1
- package/docs/deployment.md +9 -1
- package/docs/postmortems/2026-07-08-wrapper-source-drift.md +48 -33
- package/package.json +1 -1
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.
|
|
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.
|
|
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
|
package/dist/loopback-host.js
CHANGED
|
@@ -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
|
-
|
|
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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\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":";;;;;;;;;;;;;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,WAAW,YAAY,WAAW,cAAc,QAAQ,qBAAqB;AACtF,SAAS,oBAA0F;AACnG,SAAS,eAAiC;AAC1C,SAAS,eAAe;AACxB,SAAkB,YAAY,YAAY;AAC1C,SAAS,iBAAiB;AAC1B,SAAS,eAAe,qBAAqB;AAC7C,SAAS,qCAAqC;AAC9C,SAAS,2BAA2B;AAIpC,IAAM,eAAe;AACrB,IAAM,eAAe;AACrB,IAAM,eAAe;AACrB,IAAM,0BAA0B,KAAK,KAAK,KAAK;AAC/C,IAAM,iBAAiB,OAAO;AAgC9B,SAAS,cAAc,KAAqB;AAC1C,QAAM,OAAO,IAAI,WAAW,GAAG,IAAI,MAAM,IAAI,GAAG;AAChD,SAAO,KAAK,SAAS,IAAI,KAAK,QAAQ,QAAQ,EAAE,IAAI;AACtD;AAEA,SAAS,WAAW,MAAuB;AACzC,SAAO,SAAS,eAAe,SAAS,SAAS,SAAS;AAC5D;AAEA,SAAS,SAAS,MAAc,MAAc,MAAsB;AAClE,SAAO,UAAU,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,GAAG,IAAI;AACzE;AAEA,SAAS,YAAY,KAA8B;AACjD,MAAI;AACF,WAAO,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB,EAAE;AAAA,EACrD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,UAAU,KAA0C;AAC3D,QAAM,QAAQ,IAAI,QAAQ,gBAAgB;AAC1C,SAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI;AAC3C;AAEA,SAAS,MAAM,KAAqB,QAAgB,MAAqB;AACvE,QAAM,QAAQ,OAAO,KAAK,KAAK,UAAU,IAAI,CAAC;AAC9C,MAAI,UAAU,QAAQ;AAAA,IACpB,gBAAgB;AAAA,IAChB,kBAAkB,MAAM;AAAA,EAC1B,CAAC;AACD,MAAI,IAAI,KAAK;AACf;AAEA,SAAS,aAAa,KAAqB,QAAgB,MAAc,SAAuB;AAC9F,QAAM,KAAK,QAAQ,EAAE,SAAS,OAAO,OAAO,EAAE,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC;AAC3E;AAEA,eAAe,SAAS,KAAwC;AAC9D,QAAM,SAAmB,CAAC;AAC1B,MAAI,SAAS;AACb,mBAAiB,SAAS,KAAK;AAC7B,UAAM,QAAQ,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,KAAK,KAAK;AAChE,cAAU,MAAM;AAChB,QAAI,SAAS,eAAgB,OAAM,IAAI,MAAM,wBAAwB,cAAc,QAAQ;AAC3F,WAAO,KAAK,KAAK;AAAA,EACnB;AACA,QAAM,QAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,EAAE,KAAK;AAC1D,SAAO,QAAQ,KAAK,MAAM,KAAK,IAAI;AACrC;AAEA,SAAS,OAAO,QAAoB,MAAc,MAA6B;AAC7E,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,UAAU,CAAC,UAAiB;AAChC,aAAO,IAAI,aAAa,WAAW;AACnC,aAAO,KAAK;AAAA,IACd;AACA,UAAM,cAAc,MAAM;AACxB,aAAO,IAAI,SAAS,OAAO;AAC3B,cAAQ;AAAA,IACV;AACA,WAAO,KAAK,SAAS,OAAO;AAC5B,WAAO,KAAK,aAAa,WAAW;AACpC,WAAO,OAAO,MAAM,IAAI;AAAA,EAC1B,CAAC;AACH;AAEA,SAAS,YAAY,QAAmC;AACtD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW,OAAO,MAAM,CAAC,UAAU,QAAQ,OAAO,KAAK,IAAI,QAAQ,CAAC,CAAC;AACpG;AAEA,SAAS,UAAU,QAA4B;AAC7C,QAAM,UAAU,OAAO,QAAQ;AAC/B,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,OAAM,IAAI,MAAM,uCAAuC;AACpG,SAAQ,QAAwB;AAClC;AAEA,eAAsB,kBAAkB,UAA+B,CAAC,GAA0B;AAChG,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,UAAU,cAAc,QAAQ,QAAQ,YAAY;AAC1D,QAAM,aAAa,GAAG,OAAO;AAC7B,QAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,MAAI,CAAC,WAAW,IAAI,EAAG,OAAM,IAAI,MAAM,6CAA6C;AACpF,MAAI,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO,MAAO,OAAM,IAAI,MAAM,2CAA2C;AACpH,MAAI,CAAC,OAAO,UAAU,aAAa,KAAK,iBAAiB,EAAG,OAAM,IAAI,MAAM,8CAA8C;AAE1H,QAAM,WAAW,oBAAI,IAAqB;AAC1C,MAAI,iBAAiB;AACrB,MAAI,iBAAiB;AACrB,MAAI,UAAU;AACd,QAAM,eAAe,OAAO,YAAoC;AAC9D,QAAI,QAAQ,QAAS;AACrB,YAAQ,UAAU;AAClB,QAAI,QAAQ,GAAI,UAAS,OAAO,QAAQ,EAAE;AAC1C,sBAAkB;AAClB,UAAM,QAAQ,WAAW,CAAC,QAAQ,UAAU,MAAM,GAAG,QAAQ,OAAO,MAAM,CAAC,CAAC;AAAA,EAC9E;AACA,QAAM,QAAQ,MAAY;AACxB,UAAM,SAAS,KAAK,IAAI,IAAI;AAC5B,eAAW,WAAW,SAAS,OAAO,GAAG;AACvC,UAAI,QAAQ,aAAa,OAAQ,MAAK,aAAa,OAAO;AAAA,IAC5D;AAAA,EACF;AACA,QAAM,aAAa,YAAY,OAAO,KAAK,IAAI,eAAe,GAAM,CAAC;AACrE,aAAW,MAAM;AAEjB,QAAM,SAAS,aAAa,OAAO,KAAK,QAAQ;AAC9C,UAAM,OAAO,YAAY,GAAG;AAC5B,QAAI,SAAS,cAAc,SAAS,WAAW;AAC7C,YAAM,KAAK,KAAK;AAAA,QACd,QAAQ;AAAA,QACR,QAAQ;AAAA,UACN,eAAe;AAAA,UACf,WAAW;AAAA,UACX,UAAU,EAAE,QAAQ,SAAS,MAAM,QAAQ,gBAAgB,QAAQ,gBAAgB,eAAe,cAAc;AAAA,QAClH;AAAA,MACF,CAAC;AACD;AAAA,IACF;AACA,QAAI,SAAS,QAAS,QAAO,aAAa,KAAK,KAAK,OAAQ,WAAW;AACvE,QAAI,CAAC,CAAC,QAAQ,OAAO,QAAQ,EAAE,SAAS,IAAI,UAAU,EAAE,GAAG;AACzD,aAAO,aAAa,KAAK,KAAK,OAAQ,oBAAoB;AAAA,IAC5D;AACA,UAAM;AACN,QAAI;AACF,YAAM,OAAO,IAAI,WAAW,SAAS,MAAM,SAAS,GAAG,IAAI;AAC3D,YAAM,aAAa,UAAU,GAAG;AAChC,UAAI,YAAY;AACd,cAAMA,WAAU,SAAS,IAAI,UAAU;AACvC,YAAI,CAACA,SAAS,QAAO,aAAa,KAAK,KAAK,OAAQ,mBAAmB;AACvE,QAAAA,SAAQ,aAAa,KAAK,IAAI;AAC9B,cAAMA,SAAQ,UAAU,cAAc,KAAK,KAAK,IAAI;AACpD;AAAA,MACF;AACA,UAAI,IAAI,WAAW,UAAU,CAAC,oBAAoB,IAAI,GAAG;AACvD,eAAO,aAAa,KAAK,KAAK,OAAQ,4CAA4C;AAAA,MACpF;AACA,UAAI;AACJ,UAAI;AACF,cAAM,SAAS,wBAAwB,cAAc,CAAC;AACtD,kBAAU;AAAA,UACR;AAAA,UACA,WAAW,IAAI,8BAA8B;AAAA,YAC3C,oBAAoB;AAAA,YACpB,sBAAsB,CAAC,OAAO;AAC5B,kBAAI,CAAC,QAAS;AACd,sBAAQ,KAAK;AACb,sBAAQ,aAAa,KAAK,IAAI;AAC9B,uBAAS,IAAI,IAAI,OAAO;AAAA,YAC1B;AAAA,UACF,CAAC;AAAA,UACD,YAAY,KAAK,IAAI;AAAA,UACrB,SAAS;AAAA,QACX;AACA,gBAAQ,UAAU,UAAU,MAAM;AAAE,cAAI,QAAS,MAAK,aAAa,OAAO;AAAA,QAAG;AAC7E,cAAM,OAAO,QAAQ,QAAQ,SAAS;AACtC,0BAAkB;AAClB,cAAM,QAAQ,UAAU,cAAc,KAAK,KAAK,IAAI;AAAA,MACtD,UAAE;AACA,YAAI,WAAW,CAAC,QAAQ,MAAM,CAAC,QAAQ,QAAS,OAAM,aAAa,OAAO;AAAA,MAC5E;AAAA,IACF,SAAS,OAAO;AACd,UAAI,CAAC,IAAI,aAAa;AACpB,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,qBAAa,KAAK,KAAK,QAAQ,0BAA0B,OAAO,EAAE;AAAA,MACpE;AAAA,IACF;AAAA,EACF,CAAC;AACD,QAAM,OAAO,QAAQ,MAAM,IAAI;AAC/B,QAAM,aAAa,UAAU,MAAM;AACnC,QAAM,cAAc,SAAS,MAAM,YAAY,OAAO;AACtD,QAAM,iBAAiB,SAAS,MAAM,YAAY,UAAU;AAC5D,UAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,QAAQ,SAAS,UAAU,aAAa,eAAe,CAAC,CAAC;AAAA,CAAI;AAEtG,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA,OAAO,YAAY;AACjB,UAAI,QAAS;AACb,gBAAU;AACV,oBAAc,UAAU;AACxB,YAAM,QAAQ,WAAW,CAAC,GAAG,SAAS,OAAO,CAAC,EAAE,IAAI,YAAY,CAAC;AACjE,YAAM,YAAY,MAAM;AAAA,IAC1B;AAAA,EACF;AACF;AAEA,SAAS,OAAO,MAAgB,MAAc,UAA0B;AACtE,QAAM,QAAQ,KAAK,QAAQ,IAAI;AAC/B,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO,KAAK,QAAQ,CAAC,MAAM,MAAM;AAAE,UAAM,IAAI,MAAM,GAAG,IAAI,mBAAmB;AAAA,EAAG,GAAG;AACrF;AAEA,SAAS,QAAgB;AACvB,SAAO;AAAA;AAAA;AAAA;AAAA;AAKT;AAEA,SAAS,SAAS,MAAgB,MAAsB;AACtD,QAAM,QAAQ,OAAO,MAAM,MAAM,EAAE;AACnC,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,GAAG,IAAI,mBAAmB;AACtD,SAAO;AACT;AAEA,SAAS,aAAa,MAAsB;AAC1C,MAAI,CAAC,mBAAmB,KAAK,IAAI,EAAG,OAAM,IAAI,MAAM,2DAA2D;AAC/G,SAAO,iCAAiC,IAAI;AAC9C;AAEA,SAAS,UAAU,MAAsB;AACvC,QAAM,SAAS,UAAU,aAAa,MAAM,EAAE,UAAU,OAAO,CAAC;AAChE,MAAI,OAAO,WAAW,KAAK,OAAO,WAAW,KAAK,OAAO,WAAW,KAAK;AACvE,UAAM,IAAI,MAAM,aAAa,KAAK,CAAC,CAAC,aAAa,OAAO,UAAU,OAAO,UAAU,iBAAiB,KAAK,CAAC,EAAE;AAAA,EAC9G;AACF;AAEA,SAAS,gBAAwB;AAC/B,MAAI,CAAC,QAAQ,OAAQ,OAAM,IAAI,MAAM,wCAAwC;AAC7E,SAAO,QAAQ,OAAO;AACxB;AAEO,SAAS,aAAa,SAAwC;AACnE,QAAM,aAAa,QAAQ,kBAAkB,cAAc,YAAY,GAAG;AAC1E,QAAM,QAAQ,aAAa,QAAQ,WAAW;AAC9C,QAAM,cAAc;AAAA,IAClB,qBAAqB,QAAQ;AAAA,IAC7B,oBAAoB,QAAQ;AAAA,IAC5B,uBAAuB,QAAQ;AAAA,EACjC;AACA,QAAM,SAAS,CAAC,UAA0B,MAAM,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,MAAM;AACjH,QAAM,UAAU,CAAC,WAA6B,OAAO,IAAI,CAAC,UAAU,eAAe,OAAO,KAAK,CAAC,WAAW,EAAE,KAAK,IAAI;AACtH,QAAM,MAAM,OAAO,QAAQ,WAAW,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,YAAY,GAAG;AAAA,cAAuB,OAAO,KAAK,CAAC,WAAW,EAAE,KAAK,IAAI;AACvI,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,YAKG,KAAK;AAAA;AAAA;AAAA,EAGf,QAAQ,CAAC,QAAQ,UAAU,YAAY,UAAU,QAAQ,MAAM,UAAU,OAAO,QAAQ,IAAI,GAAG,UAAU,QAAQ,MAAM,qBAAqB,OAAO,QAAQ,aAAa,CAAC,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA,EAI3K,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAOO,OAAO,KAAK,QAAQ,QAAQ,QAAQ,GAAG,iBAAiB,QAAQ,GAAG,QAAQ,WAAW,UAAU,CAAC,CAAC;AAAA;AAAA,YAElG,OAAO,KAAK,QAAQ,QAAQ,QAAQ,GAAG,iBAAiB,QAAQ,GAAG,QAAQ,WAAW,UAAU,CAAC,CAAC;AAAA;AAAA;AAAA;AAI9G;AAEA,eAAe,cAAc,gBAAuC;AAClE,MAAI,YAAY;AAChB,WAAS,UAAU,GAAG,UAAU,IAAI,WAAW,GAAG;AAChD,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,gBAAgB,EAAE,QAAQ,YAAY,QAAQ,GAAK,EAAE,CAAC;AACnF,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAI,SAAS,MAAM,KAAK,WAAW,UAAW;AAC9C,kBAAY,QAAQ,SAAS,MAAM;AAAA,IACrC,SAAS,OAAO;AACd,kBAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IACnE;AACA,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAG,CAAC;AAAA,EACzD;AACA,QAAM,IAAI,MAAM,uBAAuB,cAAc,4BAA4B,SAAS,EAAE;AAC9F;AAEA,eAAsB,mBAAmB,SAAuG;AAC9I,MAAI,QAAQ,aAAa,SAAU,OAAM,IAAI,MAAM,8CAA8C;AACjG,MAAI,CAAC,WAAW,QAAQ,IAAI,EAAG,OAAM,IAAI,MAAM,6CAA6C;AAC5F,MAAI,CAAC,OAAO,UAAU,QAAQ,IAAI,KAAK,QAAQ,OAAO,KAAK,QAAQ,OAAO,MAAO,OAAM,IAAI,MAAM,uDAAuD;AACxJ,MAAI,CAAC,WAAW,QAAQ,UAAU,KAAK,CAAC,WAAW,QAAQ,UAAU,EAAG,OAAM,IAAI,MAAM,+CAA+C,QAAQ,UAAU,EAAE;AAC3J,0BAAwB,QAAQ,YAAY,MAAM;AAClD,QAAM,OAAO,QAAQ,QAAQ,QAAQ;AACrC,QAAM,QAAQ,aAAa,QAAQ,WAAW;AAC9C,QAAM,SAAS,KAAK,MAAM,WAAW,cAAc;AACnD,QAAM,OAAO,KAAK,MAAM,iBAAiB,MAAM;AAC/C,QAAM,YAAY,KAAK,QAAQ,GAAG,KAAK,QAAQ;AAC/C,YAAU,QAAQ,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAClD,YAAU,MAAM,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAChD,YAAU,QAAQ,GAAK;AACvB,YAAU,MAAM,GAAK;AACrB,YAAU,CAAC,WAAW,OAAO,cAAc,CAAC,IAAI,KAAK,EAAE,CAAC;AACxD,gBAAc,WAAW,aAAa,EAAE,GAAG,SAAS,KAAK,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AAC5E,YAAU,WAAW,GAAK;AAC1B,YAAU,CAAC,aAAa,OAAO,cAAc,CAAC,IAAI,SAAS,CAAC;AAC5D,QAAM,iBAAiB,SAAS,QAAQ,MAAM,QAAQ,MAAM,GAAG,cAAc,QAAQ,IAAI,CAAC,SAAS;AACnG,QAAM,cAAc,cAAc;AAClC,SAAO,EAAE,OAAO,WAAW,eAAe;AAC5C;AAEA,eAAe,WAAW,MAAc,MAAgC;AACtE,SAAO,MAAM,IAAI,QAAQ,CAAC,YAAY;AACpC,UAAM,SAAS,QAAQ,EAAE,MAAM,KAAK,CAAC;AACrC,UAAM,SAAS,CAAC,SAAkB;AAAE,aAAO,QAAQ;AAAG,cAAQ,IAAI;AAAA,IAAG;AACrE,WAAO,KAAK,WAAW,MAAM,OAAO,IAAI,CAAC;AACzC,WAAO,KAAK,SAAS,MAAM,OAAO,KAAK,CAAC;AACxC,WAAO,WAAW,KAAO,MAAM,OAAO,KAAK,CAAC;AAAA,EAC9C,CAAC;AACH;AAEA,eAAe,kBAAkB,MAAc,MAA6B;AAC1E,WAAS,UAAU,GAAG,UAAU,IAAI,WAAW,GAAG;AAChD,QAAI,CAAC,MAAM,WAAW,MAAM,IAAI,EAAG;AACnC,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAG,CAAC;AAAA,EACzD;AACA,QAAM,IAAI,MAAM,wBAAwB,IAAI,IAAI,IAAI,iCAAiC;AACvF;AAEA,eAAsB,qBACpB,aACA,MACA,OAAO,cACP,OAAO,QAAQ,GACgC;AAC/C,MAAI,QAAQ,aAAa,SAAU,OAAM,IAAI,MAAM,gDAAgD;AACnG,MAAI,CAAC,WAAW,IAAI,EAAG,OAAM,IAAI,MAAM,6CAA6C;AACpF,MAAI,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO,MAAO,OAAM,IAAI,MAAM,uDAAuD;AAChI,QAAM,QAAQ,aAAa,WAAW;AACtC,QAAM,YAAY,KAAK,MAAM,WAAW,gBAAgB,GAAG,KAAK,QAAQ;AACxE,YAAU,CAAC,WAAW,OAAO,cAAc,CAAC,IAAI,KAAK,EAAE,CAAC;AACxD,SAAO,WAAW,EAAE,OAAO,KAAK,CAAC;AACjC,QAAM,kBAAkB,MAAM,IAAI;AAClC,SAAO,EAAE,OAAO,UAAU;AAC5B;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,WACvE,KAAK,SAAS,mBAAmB,GAAG;AAC3C,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,SAAS,MAAM,mBAAmB;AAAA,UACtC,aAAa,SAAS,MAAM,gBAAgB;AAAA,UAC5C,YAAY,SAAS,MAAM,UAAU;AAAA,UACrC,OAAO,SAAS,MAAM,SAAS;AAAA,UAC/B,UAAU,SAAS,MAAM,YAAY;AAAA,UACrC,MAAM,OAAO,MAAM,UAAU,YAAY;AAAA,UACzC,MAAM,OAAO,OAAO,MAAM,UAAU,OAAO,YAAY,CAAC,CAAC;AAAA,UACzD,MAAM,OAAO,MAAM,UAAU,YAAY;AAAA,UACzC,eAAe,OAAO,OAAO,MAAM,qBAAqB,OAAO,uBAAuB,CAAC,CAAC;AAAA,QAC1F,CAAC;AACD,gBAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,QAAQ,aAAa,GAAG,OAAO,CAAC,CAAC;AAAA,CAAI;AAAA,MAChF,SAAS,OAAO;AACd,gBAAQ,OAAO,MAAM,+BAA+B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,CAAI;AAC9G,gBAAQ,WAAW;AAAA,MACrB;AAAA,IACF,GAAG;AAAA,EACL,WAAW,KAAK,SAAS,qBAAqB,GAAG;AAC/C,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,SAAS,MAAM;AAAA,UACnB,SAAS,MAAM,gBAAgB;AAAA,UAC/B,OAAO,SAAS,MAAM,QAAQ,CAAC;AAAA,UAC/B,OAAO,MAAM,UAAU,YAAY;AAAA,QACrC;AACA,gBAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,QAAQ,eAAe,GAAG,OAAO,CAAC,CAAC;AAAA,CAAI;AAAA,MAClF,SAAS,OAAO;AACd,gBAAQ,OAAO,MAAM,+BAA+B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,CAAI;AAC9G,gBAAQ,WAAW;AAAA,MACrB;AAAA,IACF,GAAG;AAAA,EACL,MACK,MAAK,kBAAkB;AAAA,IAC1B,MAAM,OAAO,MAAM,UAAU,YAAY;AAAA,IACzC,MAAM,OAAO,OAAO,MAAM,UAAU,OAAO,YAAY,CAAC,CAAC;AAAA,IACzD,MAAM,OAAO,MAAM,UAAU,YAAY;AAAA,IACzC,eAAe,OAAO,OAAO,MAAM,qBAAqB,OAAO,uBAAuB,CAAC,CAAC;AAAA,EAC1F,CAAC,EAAE,KAAK,CAAC,SAAS;AAChB,UAAM,WAAW,MAAM,KAAK,KAAK,MAAM,EAAE,QAAQ,MAAM,QAAQ,KAAK,CAAC,CAAC;AACtE,YAAQ,KAAK,UAAU,QAAQ;AAC/B,YAAQ,KAAK,WAAW,QAAQ;AAAA,EAClC,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,YAAQ,OAAO,MAAM,+BAA+B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,CAAI;AAC9G,YAAQ,WAAW;AAAA,EACrB,CAAC;AACH;","names":["session"]}
|
package/dist/stdio-http-proxy.js
CHANGED
|
@@ -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
|
-
|
|
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\
|
|
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":[]}
|
package/docs/deployment.md
CHANGED
|
@@ -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.
|
|
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
|
|
5
|
+
Resolved for Codex, Claude Code, and Claude Desktop availability.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
15
|
-
|
|
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
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
-
|
|
59
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
|
|
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