@onyxsecurity/mcp-gateway 1.0.63 → 1.0.65

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.
Files changed (30) hide show
  1. package/dist/bin/mcp-gateway.js +1 -1
  2. package/dist/cli-CKYhylKw.js +3 -0
  3. package/dist/cli-CKYhylKw.js.map +1 -0
  4. package/dist/{env-JHfIqf7O.js → env-BWJ8ud_g.js} +2 -2
  5. package/dist/{env-JHfIqf7O.js.map → env-BWJ8ud_g.js.map} +1 -1
  6. package/dist/{env-Bz3l4xR7.js → env-BhLIGX1J.js} +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/{logger-CxuXnoy8.js → logger-CZbmmqeM.js} +2 -2
  9. package/dist/{logger-CxuXnoy8.js.map → logger-CZbmmqeM.js.map} +1 -1
  10. package/dist/main-27K-iFJ4.js +8 -0
  11. package/dist/main-27K-iFJ4.js.map +1 -0
  12. package/dist/{normalizeUrl-u9rsNRmG.js → normalizeUrl-fkFv5Mfb.js} +2 -2
  13. package/dist/{normalizeUrl-u9rsNRmG.js.map → normalizeUrl-fkFv5Mfb.js.map} +1 -1
  14. package/dist/{oauthProvidersClient-CA99rXX6.js → oauthProvidersClient-DJfrkdEi.js} +2 -2
  15. package/dist/{oauthProvidersClient-CA99rXX6.js.map → oauthProvidersClient-DJfrkdEi.js.map} +1 -1
  16. package/dist/oauthProvidersClient-DP3TZ44j.js +1 -0
  17. package/dist/{providers-DlG5fsS1.js → providers-Cn2j18Ew.js} +2 -2
  18. package/dist/{providers-DlG5fsS1.js.map → providers-Cn2j18Ew.js.map} +1 -1
  19. package/dist/{stdio-DPNPM5VR.js → stdio-N_9V53d5.js} +2 -2
  20. package/dist/{stdio-DPNPM5VR.js.map → stdio-N_9V53d5.js.map} +1 -1
  21. package/dist/transportDetection-eBY0es0F.js +136 -0
  22. package/dist/transportDetection-eBY0es0F.js.map +1 -0
  23. package/package.json +1 -1
  24. package/dist/cli-D7NTuJNr.js +0 -3
  25. package/dist/cli-D7NTuJNr.js.map +0 -1
  26. package/dist/main-BP88lLlU.js +0 -8
  27. package/dist/main-BP88lLlU.js.map +0 -1
  28. package/dist/oauthProvidersClient-DXFLCVgt.js +0 -1
  29. package/dist/transportDetection-DOLigM8S.js +0 -136
  30. package/dist/transportDetection-DOLigM8S.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"main-BP88lLlU.js","names":["config","config","credentials: StoredCredentials","jsonLines: string[]","spawnedAt: number | undefined","spawnedPid: number | undefined","messageType: string","repoDigests: string[]","digests: string[]","yargs","proxyType: ProxyType","finalCommand: string | undefined","finalArgs: string[] | undefined","remoteUrl: string | undefined","usedTransport: TransportType | undefined","remoteHeaders: Record<string, string>","customEnv: Record<string, string>","missingVar: string | undefined","val","containerMCPInfo: ReturnType<typeof getContainerMCPInfo> | undefined","accessControlClient: AccessControlClient | undefined","recentStderr: string[]","headers: Record<string, string>","connectedClient: Client","cliArgs","client: Client | undefined","initialClient","serverVersion","client","serverCapabilities","server"],"sources":["../node_modules/eventsource/dist/index.js","../src/accessControl/authorizer.ts","../src/accessControl/client.ts","../src/auth/OAuthCredentialStore.ts","../src/JSONFilterTransform.ts","../src/StdioClientTransport.ts","../src/utils/dockerImageDigest.ts","../src/bin/main.ts"],"sourcesContent":["import { createParser } from \"eventsource-parser\";\nclass ErrorEvent extends Event {\n /**\n * Constructs a new `ErrorEvent` instance. This is typically not called directly,\n * but rather emitted by the `EventSource` object when an error occurs.\n *\n * @param type - The type of the event (should be \"error\")\n * @param errorEventInitDict - Optional properties to include in the error event\n */\n constructor(type, errorEventInitDict) {\n var _a, _b;\n super(type), this.code = (_a = errorEventInitDict == null ? void 0 : errorEventInitDict.code) != null ? _a : void 0, this.message = (_b = errorEventInitDict == null ? void 0 : errorEventInitDict.message) != null ? _b : void 0;\n }\n /**\n * Node.js \"hides\" the `message` and `code` properties of the `ErrorEvent` instance,\n * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,\n * we explicitly include the properties in the `inspect` method.\n *\n * This is automatically called by Node.js when you `console.log` an instance of this class.\n *\n * @param _depth - The current depth\n * @param options - The options passed to `util.inspect`\n * @param inspect - The inspect function to use (prevents having to import it from `util`)\n * @returns A string representation of the error\n */\n [Symbol.for(\"nodejs.util.inspect.custom\")](_depth, options, inspect) {\n return inspect(inspectableError(this), options);\n }\n /**\n * Deno \"hides\" the `message` and `code` properties of the `ErrorEvent` instance,\n * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,\n * we explicitly include the properties in the `inspect` method.\n *\n * This is automatically called by Deno when you `console.log` an instance of this class.\n *\n * @param inspect - The inspect function to use (prevents having to import it from `util`)\n * @param options - The options passed to `Deno.inspect`\n * @returns A string representation of the error\n */\n [Symbol.for(\"Deno.customInspect\")](inspect, options) {\n return inspect(inspectableError(this), options);\n }\n}\nfunction syntaxError(message) {\n const DomException = globalThis.DOMException;\n return typeof DomException == \"function\" ? new DomException(message, \"SyntaxError\") : new SyntaxError(message);\n}\nfunction flattenError(err) {\n return err instanceof Error ? \"errors\" in err && Array.isArray(err.errors) ? err.errors.map(flattenError).join(\", \") : \"cause\" in err && err.cause instanceof Error ? `${err}: ${flattenError(err.cause)}` : err.message : `${err}`;\n}\nfunction inspectableError(err) {\n return {\n type: err.type,\n message: err.message,\n code: err.code,\n defaultPrevented: err.defaultPrevented,\n cancelable: err.cancelable,\n timeStamp: err.timeStamp\n };\n}\nvar __typeError = (msg) => {\n throw TypeError(msg);\n}, __accessCheck = (obj, member, msg) => member.has(obj) || __typeError(\"Cannot \" + msg), __privateGet = (obj, member, getter) => (__accessCheck(obj, member, \"read from private field\"), getter ? getter.call(obj) : member.get(obj)), __privateAdd = (obj, member, value) => member.has(obj) ? __typeError(\"Cannot add the same private member more than once\") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value), __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, \"write to private field\"), member.set(obj, value), value), __privateMethod = (obj, member, method) => (__accessCheck(obj, member, \"access private method\"), method), _readyState, _url, _redirectUrl, _withCredentials, _fetch, _reconnectInterval, _reconnectTimer, _lastEventId, _controller, _parser, _onError, _onMessage, _onOpen, _EventSource_instances, connect_fn, _onFetchResponse, _onFetchError, getRequestOptions_fn, _onEvent, _onRetryChange, failConnection_fn, scheduleReconnect_fn, _reconnect;\nclass EventSource extends EventTarget {\n constructor(url, eventSourceInitDict) {\n var _a, _b;\n super(), __privateAdd(this, _EventSource_instances), this.CONNECTING = 0, this.OPEN = 1, this.CLOSED = 2, __privateAdd(this, _readyState), __privateAdd(this, _url), __privateAdd(this, _redirectUrl), __privateAdd(this, _withCredentials), __privateAdd(this, _fetch), __privateAdd(this, _reconnectInterval), __privateAdd(this, _reconnectTimer), __privateAdd(this, _lastEventId, null), __privateAdd(this, _controller), __privateAdd(this, _parser), __privateAdd(this, _onError, null), __privateAdd(this, _onMessage, null), __privateAdd(this, _onOpen, null), __privateAdd(this, _onFetchResponse, async (response) => {\n var _a2;\n __privateGet(this, _parser).reset();\n const { body, redirected, status, headers } = response;\n if (status === 204) {\n __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, \"Server sent HTTP 204, not reconnecting\", 204), this.close();\n return;\n }\n if (redirected ? __privateSet(this, _redirectUrl, new URL(response.url)) : __privateSet(this, _redirectUrl, void 0), status !== 200) {\n __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, `Non-200 status code (${status})`, status);\n return;\n }\n if (!(headers.get(\"content-type\") || \"\").startsWith(\"text/event-stream\")) {\n __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, 'Invalid content type, expected \"text/event-stream\"', status);\n return;\n }\n if (__privateGet(this, _readyState) === this.CLOSED)\n return;\n __privateSet(this, _readyState, this.OPEN);\n const openEvent = new Event(\"open\");\n if ((_a2 = __privateGet(this, _onOpen)) == null || _a2.call(this, openEvent), this.dispatchEvent(openEvent), typeof body != \"object\" || !body || !(\"getReader\" in body)) {\n __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, \"Invalid response body, expected a web ReadableStream\", status), this.close();\n return;\n }\n const decoder = new TextDecoder(), reader = body.getReader();\n let open = !0;\n do {\n const { done, value } = await reader.read();\n value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open = !1, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this));\n } while (open);\n }), __privateAdd(this, _onFetchError, (err) => {\n __privateSet(this, _controller, void 0), !(err.name === \"AbortError\" || err.type === \"aborted\") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err));\n }), __privateAdd(this, _onEvent, (event) => {\n typeof event.id == \"string\" && __privateSet(this, _lastEventId, event.id);\n const messageEvent = new MessageEvent(event.event || \"message\", {\n data: event.data,\n origin: __privateGet(this, _redirectUrl) ? __privateGet(this, _redirectUrl).origin : __privateGet(this, _url).origin,\n lastEventId: event.id || \"\"\n });\n __privateGet(this, _onMessage) && (!event.event || event.event === \"message\") && __privateGet(this, _onMessage).call(this, messageEvent), this.dispatchEvent(messageEvent);\n }), __privateAdd(this, _onRetryChange, (value) => {\n __privateSet(this, _reconnectInterval, value);\n }), __privateAdd(this, _reconnect, () => {\n __privateSet(this, _reconnectTimer, void 0), __privateGet(this, _readyState) === this.CONNECTING && __privateMethod(this, _EventSource_instances, connect_fn).call(this);\n });\n try {\n if (url instanceof URL)\n __privateSet(this, _url, url);\n else if (typeof url == \"string\")\n __privateSet(this, _url, new URL(url, getBaseURL()));\n else\n throw new Error(\"Invalid URL\");\n } catch {\n throw syntaxError(\"An invalid or illegal string was specified\");\n }\n __privateSet(this, _parser, createParser({\n onEvent: __privateGet(this, _onEvent),\n onRetry: __privateGet(this, _onRetryChange)\n })), __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _reconnectInterval, 3e3), __privateSet(this, _fetch, (_a = eventSourceInitDict == null ? void 0 : eventSourceInitDict.fetch) != null ? _a : globalThis.fetch), __privateSet(this, _withCredentials, (_b = eventSourceInitDict == null ? void 0 : eventSourceInitDict.withCredentials) != null ? _b : !1), __privateMethod(this, _EventSource_instances, connect_fn).call(this);\n }\n /**\n * Returns the state of this EventSource object's connection. It can have the values described below.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)\n *\n * Note: typed as `number` instead of `0 | 1 | 2` for compatibility with the `EventSource` interface,\n * defined in the TypeScript `dom` library.\n *\n * @public\n */\n get readyState() {\n return __privateGet(this, _readyState);\n }\n /**\n * Returns the URL providing the event stream.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)\n *\n * @public\n */\n get url() {\n return __privateGet(this, _url).href;\n }\n /**\n * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to \"include\", and false otherwise.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)\n */\n get withCredentials() {\n return __privateGet(this, _withCredentials);\n }\n /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */\n get onerror() {\n return __privateGet(this, _onError);\n }\n set onerror(value) {\n __privateSet(this, _onError, value);\n }\n /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */\n get onmessage() {\n return __privateGet(this, _onMessage);\n }\n set onmessage(value) {\n __privateSet(this, _onMessage, value);\n }\n /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */\n get onopen() {\n return __privateGet(this, _onOpen);\n }\n set onopen(value) {\n __privateSet(this, _onOpen, value);\n }\n addEventListener(type, listener, options) {\n const listen = listener;\n super.addEventListener(type, listen, options);\n }\n removeEventListener(type, listener, options) {\n const listen = listener;\n super.removeEventListener(type, listen, options);\n }\n /**\n * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)\n *\n * @public\n */\n close() {\n __privateGet(this, _reconnectTimer) && clearTimeout(__privateGet(this, _reconnectTimer)), __privateGet(this, _readyState) !== this.CLOSED && (__privateGet(this, _controller) && __privateGet(this, _controller).abort(), __privateSet(this, _readyState, this.CLOSED), __privateSet(this, _controller, void 0));\n }\n}\n_readyState = /* @__PURE__ */ new WeakMap(), _url = /* @__PURE__ */ new WeakMap(), _redirectUrl = /* @__PURE__ */ new WeakMap(), _withCredentials = /* @__PURE__ */ new WeakMap(), _fetch = /* @__PURE__ */ new WeakMap(), _reconnectInterval = /* @__PURE__ */ new WeakMap(), _reconnectTimer = /* @__PURE__ */ new WeakMap(), _lastEventId = /* @__PURE__ */ new WeakMap(), _controller = /* @__PURE__ */ new WeakMap(), _parser = /* @__PURE__ */ new WeakMap(), _onError = /* @__PURE__ */ new WeakMap(), _onMessage = /* @__PURE__ */ new WeakMap(), _onOpen = /* @__PURE__ */ new WeakMap(), _EventSource_instances = /* @__PURE__ */ new WeakSet(), /**\n* Connect to the given URL and start receiving events\n*\n* @internal\n*/\nconnect_fn = function() {\n __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _controller, new AbortController()), __privateGet(this, _fetch)(__privateGet(this, _url), __privateMethod(this, _EventSource_instances, getRequestOptions_fn).call(this)).then(__privateGet(this, _onFetchResponse)).catch(__privateGet(this, _onFetchError));\n}, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), /**\n* Get request options for the `fetch()` request\n*\n* @returns The request options\n* @internal\n*/\ngetRequestOptions_fn = function() {\n var _a;\n const init = {\n // [spec] Let `corsAttributeState` be `Anonymous`…\n // [spec] …will have their mode set to \"cors\"…\n mode: \"cors\",\n redirect: \"follow\",\n headers: { Accept: \"text/event-stream\", ...__privateGet(this, _lastEventId) ? { \"Last-Event-ID\": __privateGet(this, _lastEventId) } : void 0 },\n cache: \"no-store\",\n signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal\n };\n return \"window\" in globalThis && (init.credentials = this.withCredentials ? \"include\" : \"same-origin\"), init;\n}, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), /**\n* Handles the process referred to in the EventSource specification as \"failing a connection\".\n*\n* @param error - The error causing the connection to fail\n* @param code - The HTTP status code, if available\n* @internal\n*/\nfailConnection_fn = function(message, code) {\n var _a;\n __privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED);\n const errorEvent = new ErrorEvent(\"error\", { code, message });\n (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent);\n}, /**\n* Schedules a reconnection attempt against the EventSource endpoint.\n*\n* @param message - The error causing the connection to fail\n* @param code - The HTTP status code, if available\n* @internal\n*/\nscheduleReconnect_fn = function(message, code) {\n var _a;\n if (__privateGet(this, _readyState) === this.CLOSED)\n return;\n __privateSet(this, _readyState, this.CONNECTING);\n const errorEvent = new ErrorEvent(\"error\", { code, message });\n (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval)));\n}, _reconnect = /* @__PURE__ */ new WeakMap(), /**\n* ReadyState representing an EventSource currently trying to connect\n*\n* @public\n*/\nEventSource.CONNECTING = 0, /**\n* ReadyState representing an EventSource connection that is open (eg connected)\n*\n* @public\n*/\nEventSource.OPEN = 1, /**\n* ReadyState representing an EventSource connection that is closed (eg disconnected)\n*\n* @public\n*/\nEventSource.CLOSED = 2;\nfunction getBaseURL() {\n const doc = \"document\" in globalThis ? globalThis.document : void 0;\n return doc && typeof doc == \"object\" && \"baseURI\" in doc && typeof doc.baseURI == \"string\" ? doc.baseURI : void 0;\n}\nexport {\n ErrorEvent,\n EventSource\n};\n//# sourceMappingURL=index.js.map\n","import type { AccessControlClient } from \"./client.js\";\n\nimport { logger } from \"../lib/logger.js\";\n\nexport interface AccessControlConfig {\n client?: AccessControlClient;\n enabled: boolean;\n serverName?: string;\n}\n\n/**\n * Authorizes MCP servers based on access control configuration.\n * Queries backend with full client info for authorization decisions.\n */\nexport class AccessControlAuthorizer {\n private client?: AccessControlClient;\n private enabled: boolean;\n private lastBlockReason?: string;\n private serverName?: string;\n\n constructor(config: AccessControlConfig) {\n this.enabled = config.enabled;\n this.client = config.client;\n this.serverName = config.serverName;\n\n logger.info(\"AccessControlAuthorizer initialized\", {\n enabled: this.enabled,\n hasClient: !!this.client,\n serverName: this.serverName,\n });\n }\n\n /**\n * Get human-readable reason for why a server was blocked.\n * Returns the reason from the last authorization check, or a default message.\n *\n * @returns Reason message\n */\n getBlockReason(): string {\n if (this.lastBlockReason) {\n return this.lastBlockReason;\n }\n // Default message with server name if available\n const serverPart = this.serverName ? ` '${this.serverName}'` : \"\";\n return `MCP server${serverPart} is not authorized for use in your organization and has been blocked by Onyx.`;\n }\n\n /**\n * Check if access is allowed by querying the backend.\n * Always fails open (allows) if backend is unavailable or not configured.\n * Stores the block reason from the backend for later retrieval.\n *\n * @returns Promise resolving to true if allowed, false if blocked\n */\n async isAllowed(): Promise<boolean> {\n // If access control not enabled, allow everything\n if (!this.enabled) {\n return true;\n }\n\n // Check with backend if client is available\n if (this.client) {\n try {\n const response = await this.client.authorize();\n if (response.action === \"block\") {\n // Store the reason from backend for user-facing error messages, or use default with server name\n if (response.reason) {\n this.lastBlockReason = response.reason;\n } else {\n const serverPart = this.serverName ? ` '${this.serverName}'` : \"\";\n this.lastBlockReason = `MCP server${serverPart} is not authorized for use in your organization and has been blocked by Onyx.`;\n }\n return false;\n }\n return true;\n } catch (error) {\n logger.error(\"Access control authorization failed with unexpected error\", {\n error: String(error),\n });\n // DEFENSIVE: client.authorize() handles all errors internally and always\n // returns an AccessControlResponse, so this catch is normally unreachable.\n // However, we keep it as defensive fail-open behavior for unexpected errors.\n return true;\n }\n }\n\n // No client configured - allow by default (shouldn't happen if enabled=true with URL)\n logger.warn(\"No access control client configured, allowing by default\");\n return true;\n }\n}\n","import type { SessionData } from \"../utils/getUserData.js\";\n\nimport { compressClientInfo } from \"../lib/compress-client-info.js\";\nimport { logger } from \"../lib/logger.js\";\n\nexport interface AccessControlClientConfig {\n apiKey: string;\n headers: Record<string, string>;\n sessionData: SessionData;\n timeoutMs: number;\n url: string;\n}\n\nexport interface AccessControlResponse {\n action: \"allow\" | \"block\";\n reason?: string;\n}\n\n/**\n * Client for checking MCP server access control with backend API.\n * Always fails open (allows) when backend is unreachable.\n */\nexport class AccessControlClient {\n private clientInfoBase64: string;\n private config: AccessControlClientConfig;\n\n constructor(config: AccessControlClientConfig) {\n this.config = config;\n this.clientInfoBase64 = this.getClientInfoBase64();\n logger.info(\"AccessControlClient initialized\", {\n timeoutMs: config.timeoutMs,\n url: config.url,\n });\n }\n\n /**\n * Authorize by querying the backend with full client info.\n * Always fails open (allows) on any error.\n *\n * @returns Access control response\n */\n async authorize(): Promise<AccessControlResponse> {\n try {\n // Try to get decision from backend\n const response = await this.sendAuthorizeRequest();\n\n logger.debug(\"Access control check successful\", {\n action: response.action,\n });\n\n return response;\n } catch (error) {\n logger.warn(\"Access control check failed, failing open (allowing by default)\", {\n error: String(error),\n });\n\n // Always fail open (allow by default for better UX)\n return { action: \"allow\" };\n }\n }\n\n /**\n * Compress and base64-encode client info for URL.\n * Matches scanner pattern.\n */\n private getClientInfoBase64(): string {\n return compressClientInfo(this.config.sessionData);\n }\n\n /**\n * Send authorization request to backend with compressed client info in URL.\n * Matches scanner URL pattern: ${url}/${apiKey}/mcp/${clientInfoBase64}\n *\n * @returns Access control response\n * @throws Error if request fails\n */\n private async sendAuthorizeRequest(): Promise<AccessControlResponse> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n // Build URL with API key and compressed client info (matching scanner pattern)\n const checkUrl = `${this.config.url}/${this.config.apiKey}/mcp/${this.clientInfoBase64}`;\n\n const response = await fetch(checkUrl, {\n headers: {\n ...this.config.headers,\n },\n method: \"POST\",\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Access control service returned ${response.status}: ${response.statusText}`);\n }\n\n const result = (await response.json()) as AccessControlResponse;\n\n // Validate response format\n if (!result.action || ![\"allow\", \"block\"].includes(result.action)) {\n throw new Error(`Invalid access control response format: action=\"${result.action}\"`);\n }\n\n return result;\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new Error(`Access control check timed out after ${this.config.timeoutMs}ms`);\n }\n throw error;\n } finally {\n clearTimeout(timeout);\n }\n }\n\n}\n","import type { OAuthClientInformationFull, OAuthTokens } from \"@modelcontextprotocol/sdk/shared/auth.js\";\n\nimport { createHash } from \"node:crypto\";\nimport { chmod, mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { logger } from \"../lib/logger.js\";\nimport { chownToRealUser } from \"../utils/chownToRealUser.js\";\nimport { normalizeUrl } from \"../utils/normalizeUrl.js\";\n\nexport interface OAuthCredentialStoreOptions {\n /**\n * Directory to store OAuth credentials.\n * Defaults to ~/.onyx/mcp-gateway/oauth/\n */\n storageDir: string;\n}\n\n/**\n * Stored credentials structure persisted to disk.\n */\nexport interface StoredCredentials {\n /**\n * DCR client registration information.\n */\n clientInfo?: OAuthClientInformationFull;\n /**\n * Timestamp when the credentials were stored (ISO string).\n */\n createdAt: string;\n /**\n * The original server URL these credentials are for.\n */\n serverUrl: string;\n /**\n * OAuth tokens (access_token, refresh_token, etc.).\n */\n tokens?: OAuthTokens;\n /**\n * Timestamp when tokens were obtained (ISO string).\n * Used to calculate token expiration.\n */\n tokensObtainedAt?: string;\n /**\n * Last update timestamp (ISO string).\n */\n updatedAt: string;\n}\n\n/**\n * File-based credential store for OAuth credentials.\n * Stores credentials in JSON files keyed by server URL hash.\n */\nexport class OAuthCredentialStore {\n private readonly storageDir: string;\n\n constructor(options: OAuthCredentialStoreOptions) {\n this.storageDir = options.storageDir;\n }\n\n /**\n * Deletes stored credentials for a server URL.\n */\n async delete(serverUrl: string): Promise<void> {\n // Normalize URL to ensure consistent key generation\n const normalizedUrl = normalizeUrl(serverUrl);\n const filePath = this.getFilePath(normalizedUrl);\n\n try {\n await rm(filePath);\n logger.debug(\"Deleted stored credentials\", { filePath, serverUrl: normalizedUrl });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n logger.warn(\"Failed to delete stored credentials\", {\n error: String(error),\n filePath,\n serverUrl: normalizedUrl,\n });\n }\n }\n }\n\n /**\n * Checks if stored credentials have a refresh token.\n */\n hasRefreshToken(credentials: StoredCredentials): boolean {\n return !!credentials.tokens?.refresh_token;\n }\n\n /**\n * Checks if stored credentials have a valid (non-expired) access token.\n */\n hasValidAccessToken(credentials: StoredCredentials): boolean {\n return !!credentials.tokens?.access_token && !this.isAccessTokenExpired(credentials);\n }\n\n /**\n * Checks if the stored access token is expired.\n * Returns true if expired or if expiration cannot be determined.\n */\n isAccessTokenExpired(credentials: StoredCredentials): boolean {\n if (!credentials.tokens || !credentials.tokensObtainedAt) {\n return true;\n }\n\n const { expires_in } = credentials.tokens;\n if (!expires_in) {\n // No expiration info - assume not expired\n return false;\n }\n\n const obtainedAt = new Date(credentials.tokensObtainedAt).getTime();\n const expiresAt = obtainedAt + expires_in * 1000;\n const now = Date.now();\n\n // Add 30 second buffer to avoid edge cases\n const isExpired = now >= expiresAt - 30000;\n\n logger.debug(\"Checked access token expiration\", {\n expiresAt: new Date(expiresAt).toISOString(),\n expiresIn: expires_in,\n isExpired,\n now: new Date(now).toISOString(),\n obtainedAt: credentials.tokensObtainedAt,\n });\n\n return isExpired;\n }\n\n /**\n * Loads stored credentials for a server URL.\n * Returns undefined if no credentials exist or if they're invalid.\n */\n async load(serverUrl: string): Promise<StoredCredentials | undefined> {\n // Normalize URL to ensure consistent key generation\n const normalizedUrl = normalizeUrl(serverUrl);\n const filePath = this.getFilePath(normalizedUrl);\n\n try {\n const content = await readFile(filePath, \"utf-8\");\n const credentials = JSON.parse(content) as StoredCredentials;\n\n // Validate the stored credentials match the requested server (using normalized URL)\n // Also accept credentials stored with the original URL format for backward compatibility\n const storedUrlNormalized = normalizeUrl(credentials.serverUrl);\n if (storedUrlNormalized !== normalizedUrl) {\n logger.warn(\"Credential file server URL mismatch\", {\n expected: normalizedUrl,\n filePath,\n found: credentials.serverUrl,\n });\n return undefined;\n }\n\n logger.debug(\"Loaded stored credentials\", {\n clientId: credentials.clientInfo?.client_id,\n hasRefreshToken: !!credentials.tokens?.refresh_token,\n hasTokens: !!credentials.tokens,\n serverUrl: normalizedUrl,\n });\n\n return credentials;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n // File doesn't exist - no stored credentials\n logger.debug(\"No stored credentials found\", { serverUrl: normalizedUrl });\n return undefined;\n }\n\n logger.warn(\"Failed to load stored credentials\", {\n error: String(error),\n filePath,\n serverUrl: normalizedUrl,\n });\n return undefined;\n }\n }\n\n /**\n * Saves credentials to disk.\n */\n async save(credentials: StoredCredentials): Promise<void> {\n await this.ensureStorageDir();\n\n const filePath = this.getFilePath(credentials.serverUrl);\n const content = JSON.stringify(credentials, null, 2);\n\n try {\n await writeFile(filePath, content, { encoding: \"utf-8\", mode: 0o600 });\n // Ensure permissions are correct even if file existed\n await chmod(filePath, 0o600);\n\n logger.debug(\"Saved credentials to disk\", {\n clientId: credentials.clientInfo?.client_id,\n filePath,\n hasTokens: !!credentials.tokens,\n serverUrl: credentials.serverUrl,\n });\n } catch (error) {\n logger.error(\"Failed to save credentials\", {\n error: String(error),\n filePath,\n serverUrl: credentials.serverUrl,\n });\n throw error;\n }\n }\n\n /**\n * Updates specific fields in stored credentials.\n * Creates new credentials if none exist.\n */\n async update(\n serverUrl: string,\n updates: Partial<Pick<StoredCredentials, \"clientInfo\" | \"tokens\" | \"tokensObtainedAt\">>\n ): Promise<StoredCredentials> {\n // Normalize URL to ensure consistent key generation and storage\n const normalizedUrl = normalizeUrl(serverUrl);\n const existing = await this.load(normalizedUrl);\n const now = new Date().toISOString();\n\n const credentials: StoredCredentials = existing\n ? {\n ...existing,\n ...updates,\n // Update serverUrl to normalized form for consistency\n serverUrl: normalizedUrl,\n updatedAt: now,\n }\n : {\n createdAt: now,\n serverUrl: normalizedUrl,\n updatedAt: now,\n ...updates,\n };\n\n await this.save(credentials);\n return credentials;\n }\n\n /**\n * Ensures the storage directory exists with proper permissions.\n */\n private async ensureStorageDir(): Promise<void> {\n try {\n await mkdir(this.storageDir, { mode: 0o700, recursive: true });\n } catch (error) {\n // Directory may already exist\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") {\n throw error;\n }\n }\n\n // Best-effort ownership repair; should not block auth flows\n try {\n await chownToRealUser(this.storageDir);\n } catch (error) {\n logger.warn(\"Failed to fix OAuth storage directory ownership\", {\n error: String(error),\n storageDir: this.storageDir,\n });\n }\n }\n\n /**\n * Generates a URL-safe filename from a server URL.\n * Uses SHA-256 hash truncated to 16 chars for brevity.\n */\n private generateKey(serverUrl: string): string {\n const hash = createHash(\"sha256\").update(serverUrl).digest(\"hex\");\n return hash.substring(0, 16);\n }\n\n /**\n * Gets the file path for a server's credentials.\n */\n private getFilePath(serverUrl: string): string {\n const key = this.generateKey(serverUrl);\n return join(this.storageDir, `${key}.json`);\n }\n}\n\n","import { Transform } from \"node:stream\";\n\nimport { logger } from \"./lib/logger.js\";\n\nconst MAX_DROPPED_LINES_LOGGED = 50;\n\n/**\n * Filters out lines that do not start with '{' from the input stream.\n * We use this to drop anything that is obviously not a JSON-RPC message.\n */\nexport class JSONFilterTransform extends Transform {\n private buffer = \"\";\n private droppedLinesLogged = 0;\n\n constructor() {\n super({ objectMode: false });\n }\n\n _flush(callback: (error: Error | null, chunk: Buffer | null) => void) {\n // Handle any remaining data in buffer\n if (this.buffer.trim().startsWith(\"{\")) {\n callback(null, Buffer.from(this.buffer));\n } else {\n if (this.buffer.trim().length > 0) {\n this.logDroppedLine(this.buffer);\n }\n callback(null, null);\n }\n }\n\n _transform(chunk: Buffer, _encoding: string, callback: (error: Error | null, chunk: Buffer | null) => void) {\n this.buffer += chunk.toString();\n const lines = this.buffer.split(\"\\n\");\n\n // Keep the last incomplete line in the buffer\n this.buffer = lines.pop() || \"\";\n\n const jsonLines: string[] = [];\n for (const line of lines) {\n if (line.trim().startsWith(\"{\")) {\n jsonLines.push(line);\n } else if (line.trim().length > 0) {\n this.logDroppedLine(line);\n }\n }\n\n if (jsonLines.length > 0) {\n // Send filtered lines with newlines\n const output = `${jsonLines.join(\"\\n\")}\\n`;\n\n callback(null, Buffer.from(output));\n } else {\n callback(null, null);\n }\n }\n\n private logDroppedLine(line: string) {\n // Already past the suppression sentinel — stay silent.\n if (this.droppedLinesLogged > MAX_DROPPED_LINES_LOGGED) return;\n\n // First line past the cap: emit the sentinel exactly once. The increment\n // moves us into the `>` branch above, so subsequent calls fall through.\n if (this.droppedLinesLogged === MAX_DROPPED_LINES_LOGGED) {\n this.droppedLinesLogged += 1;\n logger.info(\"Suppressing further non-JSON stdout lines\", {\n eventType: \"mcp_server_stdout_dropped_suppressed\",\n loggedSoFar: MAX_DROPPED_LINES_LOGGED,\n });\n return;\n }\n\n this.droppedLinesLogged += 1;\n logger.info(\"MCP server stdout (non-JSON, dropped)\", {\n eventType: \"mcp_server_stdout_dropped\",\n line: line.length > 1024 ? `${line.slice(0, 1024)}…` : line,\n });\n }\n}\n","/**\n * Forked from https://github.com/modelcontextprotocol/typescript-sdk/blob/a1608a6513d18eb965266286904760f830de96fe/src/client/stdio.ts\n */\n\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\n\nimport {\n ReadBuffer,\n serializeMessage,\n} from \"@modelcontextprotocol/sdk/shared/stdio.js\";\nimport { type ChildProcess, type IOType, spawn, spawnSync } from \"node:child_process\";\nimport { PassThrough, type Stream } from \"node:stream\";\n\nimport { JSONFilterTransform } from \"./JSONFilterTransform.js\";\nimport { logger } from \"./lib/logger.js\";\n\nexport type StdioServerParameters = {\n\t/**\n\t * Command line arguments to pass to the executable.\n\t */\n\targs?: string[];\n\n\t/**\n\t * The executable to run to start the server.\n\t */\n\tcommand: string;\n\n\t/**\n\t * The working directory to use when spawning the process.\n\t *\n\t * If not specified, the current working directory will be inherited.\n\t */\n\tcwd?: string;\n\n\t/**\n\t * The environment to use when spawning the process.\n\t *\n\t * If not specified, the result of getDefaultEnvironment() will be used.\n\t */\n\tenv: Record<string, string>;\n\n\t/**\n\t * A function to call when an event occurs.\n\t */\n\tonEvent?: (event: TransportEvent) => void;\n\n\t/**\n\t * When true, spawn the child process using the user's shell.\n\t */\n\tshell?: boolean;\n\n\t/**\n\t * How to handle stderr of the child process. This matches the semantics of Node's `child_process.spawn`.\n\t *\n\t * The default is \"inherit\", meaning messages to stderr will be printed to the parent process's stderr.\n\t */\n\tstderr?: IOType | number | Stream;\n};\n\ntype TransportEvent =\n\t| {\n\t\t\tchunk: string;\n\t\t\ttype: \"data\";\n\t }\n\t| {\n\t\t\terror: Error;\n\t\t\ttype: \"error\";\n\t }\n\t| {\n\t\t\tmessage: JSONRPCMessage;\n\t\t\ttype: \"message\";\n\t }\n\t| {\n\t\t\ttype: \"close\";\n\t };\n\n/**\n * Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout.\n *\n * This transport is only available in Node.js environments.\n */\nexport class StdioClientTransport implements Transport {\n\tonclose?: () => void;\n\n\tonerror?: (error: Error) => void;\n\tonmessage?: (message: JSONRPCMessage) => void;\n\t/**\n\t * The child process pid spawned by this transport.\n\t *\n\t * This is only available after the transport has been started.\n\t */\n\tget pid(): null | number {\n\t\treturn this._process?.pid ?? null;\n\t}\n\t/**\n\t * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to \"pipe\" or \"overlapped\".\n\t *\n\t * If stderr piping was requested, a PassThrough stream is returned _immediately_, allowing callers to\n\t * attach listeners before the start method is invoked. This prevents loss of any early\n\t * error output emitted by the child process.\n\t */\n\tget stderr(): null | Stream {\n\t\tif (this._stderrStream) {\n\t\t\treturn this._stderrStream;\n\t\t}\n\n\t\treturn this._process?.stderr ?? null;\n\t}\n\tprivate _abortController: AbortController = new AbortController();\n\tprivate _process?: ChildProcess;\n\tprivate _readBuffer: ReadBuffer = new ReadBuffer();\n\tprivate _serverParams: StdioServerParameters;\n\tprivate _stderrStream: null | PassThrough = null;\n\n\tprivate onEvent?: (event: TransportEvent) => void;\n\n\tconstructor(server: StdioServerParameters) {\n\t\tthis._serverParams = server;\n\t\tif (server.stderr === \"pipe\" || server.stderr === \"overlapped\") {\n\t\t\tthis._stderrStream = new PassThrough();\n\t\t}\n\t\tthis.onEvent = server.onEvent;\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.onEvent?.({\n\t\t\ttype: \"close\",\n\t\t});\n\n\t\t// Kill the entire process tree to avoid orphaned grandchildren.\n\t\t// AbortController only kills the direct child, but MCP servers\n\t\t// may spawn subprocesses (workers, file watchers, etc.) that\n\t\t// would otherwise be left running as orphans.\n\t\tif (this._process?.pid) {\n\t\t\ttry {\n\t\t\t\tif (process.platform === \"win32\") {\n\t\t\t\t\t// Windows doesn't support negative-PID process group kills.\n\t\t\t\t\t// Use taskkill /T to terminate the entire process tree.\n\t\t\t\t\tconst result = spawnSync(\"taskkill\", [\"/PID\", String(this._process.pid), \"/T\", \"/F\"], {\n\t\t\t\t\t\tstdio: \"ignore\",\n\t\t\t\t\t});\n\t\t\t\t\tif (result.error) {\n\t\t\t\t\t\tthrow result.error;\n\t\t\t\t\t}\n\t\t\t\t\tif (result.status !== 0) {\n\t\t\t\t\t\tthrow new Error(`taskkill exited with status ${result.status}`);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tprocess.kill(-this._process.pid, \"SIGTERM\");\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tlogger.debug(\"Process tree cleanup failed (process may already be dead)\", {\n\t\t\t\t\terror: (error as Error).message,\n\t\t\t\t\tpid: this._process.pid,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tthis._abortController.abort();\n\t\tthis._process = undefined;\n\t\tthis._readBuffer.clear();\n\t}\n\n\tsend(message: JSONRPCMessage): Promise<void> {\n\t\treturn new Promise((resolve) => {\n\t\t\tif (!this._process?.stdin) {\n\t\t\t\tthrow new Error(\"Not connected\");\n\t\t\t}\n\n\t\t\tconst json = serializeMessage(message);\n\t\t\tif (this._process.stdin.write(json)) {\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\tthis._process.stdin.once(\"drain\", resolve);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Starts the server process and prepares to communicate with it.\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this._process) {\n\t\t\tthrow new Error(\n\t\t\t\t\"StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.\",\n\t\t\t);\n\t\t}\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis._process = spawn(\n\t\t\t\tthis._serverParams.command,\n\t\t\t\tthis._serverParams.args ?? [],\n\t\t\t\t{\n\t\t\t\t\tcwd: this._serverParams.cwd,\n\t\t\t\t\t// On POSIX, detached creates a new process group (setsid) so we\n\t\t\t\t\t// can kill the entire tree with kill(-pid). On Windows, detached\n\t\t\t\t\t// breaks the child out of the parent's job object, which would\n\t\t\t\t\t// prevent automatic cleanup if the parent crashes — so we skip it.\n\t\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\t\tenv: this._serverParams.env,\n\t\t\t\t\tshell: this._serverParams.shell ?? false,\n\t\t\t\t\tsignal: this._abortController.signal,\n\t\t\t\t\tstdio: [\"pipe\", \"pipe\", this._serverParams.stderr ?? \"inherit\"],\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tthis._process.on(\"error\", (error) => {\n\t\t\t\tif (error.name === \"AbortError\") {\n\t\t\t\t\t// Expected when close() is called.\n\t\t\t\t\tthis.onclose?.();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlogger.error(\"Process spawn error\", {\n\t\t\t\t\targs: this._serverParams.args,\n\t\t\t\t\tcommand: this._serverParams.command,\n\t\t\t\t\tcwd: this._serverParams.cwd,\n\t\t\t\t\terror: error.message,\n\t\t\t\t\terrorCode: (error as NodeJS.ErrnoException).code,\n\t\t\t\t\terrorErrno: (error as NodeJS.ErrnoException).errno,\n\t\t\t\t\terrorSyscall: (error as NodeJS.ErrnoException).syscall,\n\t\t\t\t\toperation: \"spawn\",\n\t\t\t\t\tshell: this._serverParams.shell,\n\t\t\t\t\ttransportType: \"stdio\",\n\t\t\t\t});\n\n\t\t\t\treject(error);\n\t\t\t\tthis.onerror?.(error);\n\t\t\t});\n\n\t\t\t// Capture pid + spawn time at \"spawn\" time so they remain available in\n\t\t\t// the \"close\" handler. close() nulls `this._process` synchronously\n\t\t\t// before the OS fires the close event, so reading `this._process?.pid`\n\t\t\t// from the close handler would always return undefined on that path.\n\t\t\tlet spawnedAt: number | undefined;\n\t\t\tlet spawnedPid: number | undefined;\n\t\t\tthis._process.on(\"spawn\", () => {\n\t\t\t\tspawnedAt = Date.now();\n\t\t\t\tspawnedPid = this._process?.pid;\n\t\t\t\t// Log argCount, not args — MCP servers commonly take secrets as\n\t\t\t\t// CLI args (--api-key, --token, etc.) and these logs land in the\n\t\t\t\t// file transport and remote ingest endpoint.\n\t\t\t\tlogger.info(\"MCP server process spawned\", {\n\t\t\t\t\targCount: this._serverParams.args?.length,\n\t\t\t\t\tcommand: this._serverParams.command,\n\t\t\t\t\teventType: \"mcp_server_process_spawned\",\n\t\t\t\t\tpid: spawnedPid,\n\t\t\t\t});\n\t\t\t\tresolve();\n\t\t\t});\n\n\t\t\tthis._process.on(\"close\", (code, signal) => {\n\t\t\t\tconst uptimeMs = spawnedAt === undefined ? undefined : Date.now() - spawnedAt;\n\t\t\t\tconst cleanExit = code === 0 && signal === null;\n\t\t\t\tconst logLevel = cleanExit ? \"info\" : \"warn\";\n\t\t\t\tlogger[logLevel](\"MCP server process closed\", {\n\t\t\t\t\targCount: this._serverParams.args?.length,\n\t\t\t\t\tcode,\n\t\t\t\t\tcommand: this._serverParams.command,\n\t\t\t\t\teventType: \"mcp_server_process_closed\",\n\t\t\t\t\tpid: spawnedPid,\n\t\t\t\t\tsignal,\n\t\t\t\t\tuptimeMs,\n\t\t\t\t});\n\n\t\t\t\tthis.onEvent?.({\n\t\t\t\t\ttype: \"close\",\n\t\t\t\t});\n\n\t\t\t\tthis._process = undefined;\n\t\t\t\tthis.onclose?.();\n\t\t\t});\n\n\t\t\tthis._process.stdin?.on(\"error\", (error) => {\n\t\t\t\tlogger.error(\"Stdin error\", {\n\t\t\t\t\tcommand: this._serverParams.command,\n\t\t\t\t\terror: error.message,\n\t\t\t\t\toperation: \"write\",\n\t\t\t\t\tpid: this._process?.pid,\n\t\t\t\t\ttransportType: \"stdio\",\n\t\t\t\t});\n\n\t\t\t\tthis.onEvent?.({\n\t\t\t\t\terror,\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t});\n\n\t\t\t\tthis.onerror?.(error);\n\t\t\t});\n\n\t\t\tconst jsonFilterTransform = new JSONFilterTransform();\n\n\t\t\tthis._process.stdout?.pipe(jsonFilterTransform);\n\n\t\t\tjsonFilterTransform.on(\"data\", (chunk) => {\n\t\t\t\tthis.onEvent?.({\n\t\t\t\t\tchunk: chunk.toString(),\n\t\t\t\t\ttype: \"data\",\n\t\t\t\t});\n\n\t\t\t\tthis._readBuffer.append(chunk);\n\t\t\t\tthis.processReadBuffer();\n\t\t\t});\n\n\t\t\tjsonFilterTransform.on(\"error\", (error) => {\n\t\t\t\tlogger.error(\"JSON filter error\", {\n\t\t\t\t\tcommand: this._serverParams.command,\n\t\t\t\t\terror: error.message,\n\t\t\t\t\toperation: \"parse\",\n\t\t\t\t\tpid: this._process?.pid,\n\t\t\t\t\ttransportType: \"stdio\",\n\t\t\t\t});\n\n\t\t\t\tthis.onEvent?.({\n\t\t\t\t\terror,\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t});\n\n\t\t\t\tthis.onerror?.(error);\n\t\t\t});\n\n\t\t\tif (this._stderrStream && this._process.stderr) {\n\t\t\t\tthis._process.stderr.pipe(this._stderrStream);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate processReadBuffer() {\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tconst message = this._readBuffer.readMessage();\n\n\t\t\t\tif (message === null) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tthis.onEvent?.({\n\t\t\t\t\tmessage,\n\t\t\t\t\ttype: \"message\",\n\t\t\t\t});\n\n\t\t\t\tthis.onmessage?.(message);\n\t\t\t} catch (error) {\n\t\t\t\t// Try to read message for context (may fail)\n\t\t\t\tlet messageType: string = \"unknown\";\n\t\t\t\ttry {\n\t\t\t\t\tconst msg = this._readBuffer.readMessage();\n\t\t\t\t\tif (msg) {\n\t\t\t\t\t\tmessageType = typeof msg;\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore error when trying to read message for context\n\t\t\t\t}\n\n\t\t\t\tlogger.error(\"Message processing error\", {\n\t\t\t\t\tcommand: this._serverParams.command,\n\t\t\t\t\terror: (error as Error).message,\n\t\t\t\t\tmessageType,\n\t\t\t\t\toperation: \"process\",\n\t\t\t\t\tpid: this._process?.pid,\n\t\t\t\t\ttransportType: \"stdio\",\n\t\t\t\t});\n\n\t\t\t\tthis.onEvent?.({\n\t\t\t\t\terror: error as Error,\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t});\n\n\t\t\t\tthis.onerror?.(error as Error);\n\t\t\t}\n\t\t}\n\t}\n}\n","import { execSync } from \"node:child_process\";\n\nimport { logger } from \"../lib/logger.js\";\n\n/**\n * Information about a container-based MCP command\n */\nexport interface ContainerMCPInfo {\n /**\n * The container image digests (sha256 hashes of the manifest)\n * These are consistent across registries and identify specific image versions.\n * Format: sha256:abc123...\n * An image may have multiple digests if it exists in multiple registries.\n */\n imageDigests: string[];\n /**\n * The image name/tag used in the command\n */\n imageName: string;\n /**\n * Whether the command is a container MCP (docker, podman, etc.)\n */\n isContainerMCP: boolean;\n /**\n * The detected container runtime (docker, podman, nerdctl, finch, etc.)\n */\n runtime: ContainerRuntime;\n}\n\n/**\n * Supported container runtimes\n */\nexport type ContainerRuntime = \"docker\" | \"finch\" | \"nerdctl\" | \"podman\" | \"unknown\";\n\n/**\n * Detect the container runtime from a command string.\n *\n * Supports:\n * - docker / docker.exe\n * - podman / podman.exe\n * - nerdctl / nerdctl.exe\n * - finch / finch.exe\n *\n * @param command - The executable command\n * @returns The detected container runtime or \"unknown\"\n */\nexport function detectContainerRuntime(command: string): ContainerRuntime {\n // Normalize command to lowercase for comparison\n const normalizedCommand = command.toLowerCase();\n\n // Get the base command name (without path)\n const baseName = normalizedCommand.split(/[/\\\\]/).pop() || \"\";\n\n // Check for known container runtimes\n if (baseName === \"docker\" || baseName === \"docker.exe\") {\n return \"docker\";\n }\n if (baseName === \"podman\" || baseName === \"podman.exe\") {\n return \"podman\";\n }\n if (baseName === \"nerdctl\" || baseName === \"nerdctl.exe\") {\n return \"nerdctl\";\n }\n if (baseName === \"finch\" || baseName === \"finch.exe\") {\n return \"finch\";\n }\n\n return \"unknown\";\n}\n\n/**\n * Extract the image name from container run arguments.\n *\n * Docker/Podman/nerdctl run syntax: <runtime> run [OPTIONS] IMAGE [COMMAND] [ARG...]\n *\n * The image is the first argument after all options (which start with - or --).\n * Some options take values: -e, --env, -v, --volume, -p, --publish, etc.\n *\n * @param args - The run arguments (after \"<runtime> run\")\n * @returns The image name or undefined if not found\n */\nexport function extractContainerImageName(args: string[]): string | undefined {\n // Find the index of \"run\" in args\n const runIndex = args.indexOf(\"run\");\n if (runIndex === -1) {\n return undefined;\n }\n\n // Args after \"run\"\n const runArgs = args.slice(runIndex + 1);\n\n // Options that take a value (the next arg is not the image)\n // These are common across Docker, Podman, nerdctl, and finch\n // Sorted alphabetically for linter\n const optionsWithValue = new Set([\n \"--add-host\",\n \"--annotation\",\n \"--arch\",\n \"--attach\",\n \"--blkio-weight\",\n \"--blkio-weight-device\",\n \"--cap-add\",\n \"--cap-drop\",\n \"--cgroup-parent\",\n \"--cgroupns\",\n \"--cidfile\",\n \"--cosign-key\",\n \"--cpu-period\",\n \"--cpu-quota\",\n \"--cpu-rt-period\",\n \"--cpu-rt-runtime\",\n \"--cpu-shares\",\n \"--cpus\",\n \"--cpuset-cpus\",\n \"--cpuset-mems\",\n \"--decryption-key\",\n \"--device\",\n \"--device-cgroup-rule\",\n \"--device-read-bps\",\n \"--device-read-iops\",\n \"--device-write-bps\",\n \"--device-write-iops\",\n \"--dns\",\n \"--dns-option\",\n \"--dns-search\",\n \"--domainname\",\n \"--entrypoint\",\n \"--env\",\n \"--env-file\",\n \"--env-host\",\n \"--expose\",\n \"--gidmap\",\n \"--gpus\",\n \"--group-add\",\n \"--health-cmd\",\n \"--health-interval\",\n \"--health-retries\",\n \"--health-start-period\",\n \"--health-timeout\",\n \"--hostname\",\n \"--image-volume\",\n \"--ip\",\n \"--ip6\",\n \"--ipc\",\n \"--isolation\",\n \"--kernel-memory\",\n \"--label\",\n \"--label-file\",\n \"--link\",\n \"--link-local-ip\",\n \"--log-driver\",\n \"--log-opt\",\n \"--mac-address\",\n \"--memory\",\n \"--memory-reservation\",\n \"--memory-swap\",\n \"--memory-swappiness\",\n \"--mount\",\n \"--name\",\n \"--network\",\n \"--network-alias\",\n \"--oom-kill-disable\",\n \"--oom-score-adj\",\n \"--os\",\n \"--pid\",\n \"--pidfile\",\n \"--pids-limit\",\n \"--platform\",\n \"--pod\",\n \"--privileged\",\n \"--publish\",\n \"--publish-all\",\n \"--pull\",\n \"--requires\",\n \"--restart\",\n \"--rootfs\",\n \"--runtime\",\n \"--secret\",\n \"--security-opt\",\n \"--shm-size\",\n \"--stop-signal\",\n \"--stop-timeout\",\n \"--storage-opt\",\n \"--subgidname\",\n \"--subuidname\",\n \"--sysctl\",\n \"--systemd\",\n \"--timeout\",\n \"--tmpfs\",\n \"--tz\",\n \"--uidmap\",\n \"--ulimit\",\n \"--user\",\n \"--userns\",\n \"--uts\",\n \"--variant\",\n \"--volume\",\n \"--volume-driver\",\n \"--volumes-from\",\n \"--workdir\",\n \"-a\",\n \"-c\",\n \"-e\",\n \"-h\",\n \"-l\",\n \"-m\",\n \"-p\",\n \"-P\",\n \"-u\",\n \"-v\",\n \"-w\",\n ]);\n\n let i = 0;\n while (i < runArgs.length) {\n const arg = runArgs[i];\n\n // Skip options\n if (arg.startsWith(\"-\")) {\n // Check if this option takes a value\n // Handle --option=value format\n if (arg.includes(\"=\")) {\n i++;\n continue;\n }\n\n // Check if the option (without leading dashes) is in our set\n if (optionsWithValue.has(arg)) {\n // Skip this option and its value\n i += 2;\n continue;\n }\n\n // Boolean option, just skip it\n i++;\n continue;\n }\n\n // This is the image name\n return arg;\n }\n\n return undefined;\n}\n\n/**\n * Get all container image digests using the appropriate runtime's inspect command.\n *\n * The digests are sha256 hashes of the image manifest, which are consistent across\n * registries and uniquely identify specific image versions.\n * An image may have multiple digests if it exists in multiple registries.\n *\n * @param imageName - The image name/tag to inspect\n * @param runtime - The container runtime to use\n * @returns Array of image digests (sha256 hashes), empty if not found or locally built\n */\nexport function getContainerImageDigests(imageName: string, runtime: ContainerRuntime): string[] {\n try {\n // All supported runtimes use the same inspect syntax\n const runtimeCommand = runtime === \"unknown\" ? \"docker\" : runtime;\n\n // Get RepoDigests as JSON array - this contains the manifest digests\n // Format: [\"repo@sha256:abc123...\", \"other-repo@sha256:def456...\"] or [] if image was built locally\n const result = execSync(\n `${runtimeCommand} image inspect --format \"{{json .RepoDigests}}\" ${JSON.stringify(imageName)}`,\n {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n timeout: 5000, // 5 second timeout\n }\n );\n\n const trimmedResult = result.trim();\n\n // Parse the JSON array of digests\n let repoDigests: string[];\n try {\n repoDigests = JSON.parse(trimmedResult) as string[];\n } catch {\n logger.debug(\"Could not parse RepoDigests JSON\", { imageName, result: trimmedResult, runtime });\n return [];\n }\n\n // Extract sha256 digests from all entries\n // Format is typically: \"repo@sha256:abc123...\" or just \"sha256:...\"\n const digests: string[] = [];\n for (const repoDigest of repoDigests) {\n // Extract just the sha256:... portion\n const digestMatch = repoDigest.match(/sha256:[a-f0-9]{64}/i);\n if (digestMatch) {\n digests.push(digestMatch[0]);\n } else if (/^sha256:[a-f0-9]{64}$/i.test(repoDigest)) {\n // If the entire string is already a valid digest\n digests.push(repoDigest);\n }\n }\n\n if (digests.length > 0) {\n logger.debug(\"Container image digests retrieved\", { digests, imageName, runtime });\n return digests;\n }\n\n // No digest available (likely a locally built image that was never pushed)\n logger.debug(\"No digests available for image (may be locally built)\", { imageName, runtime });\n return [];\n } catch (error) {\n // Image not found locally, or runtime not available\n logger.debug(\"Could not get container image digests\", {\n error: error instanceof Error ? error.message : String(error),\n imageName,\n runtime,\n });\n return [];\n }\n}\n\n/**\n * Detect if a command is running a container (Docker, Podman, nerdctl, finch) and retrieve the image digests.\n *\n * @param command - The executable command\n * @param args - Command arguments\n * @returns Container MCP information including image digests if available\n */\nexport function getContainerMCPInfo(command: string, args?: string[]): ContainerMCPInfo {\n const runtime = detectContainerRuntime(command);\n\n if (runtime === \"unknown\") {\n return {\n imageDigests: [],\n imageName: \"\",\n isContainerMCP: false,\n runtime: \"unknown\",\n };\n }\n\n // Check if there's a \"run\" subcommand in the args\n if (!args || !args.includes(\"run\")) {\n return {\n imageDigests: [],\n imageName: \"\",\n isContainerMCP: false,\n runtime,\n };\n }\n\n const imageName = extractContainerImageName(args);\n\n if (!imageName) {\n logger.debug(\"Container command detected but could not extract image name\", {\n args,\n command,\n runtime,\n });\n return {\n imageDigests: [],\n imageName: \"\",\n isContainerMCP: true,\n runtime,\n };\n }\n\n const imageDigests = getContainerImageDigests(imageName, runtime);\n\n return {\n imageDigests,\n imageName,\n isContainerMCP: true,\n runtime,\n };\n}\n","import { StringDecoder } from \"node:string_decoder\";\nimport { setTimeout } from \"node:timers\";\nimport util from \"node:util\";\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { EventSource } from \"eventsource\";\nimport yargs from \"yargs\";\nimport { hideBin } from \"yargs/helpers\";\n\nimport { AccessControlAuthorizer } from \"../accessControl/authorizer.js\";\nimport { AccessControlClient } from \"../accessControl/client.js\";\nimport { OAuthCredentialStore, performOAuthFlow } from \"../auth/index.js\";\nimport { initializeOAuthProviders } from \"../auth/oauthProvidersClient.js\";\nimport { config } from \"../config/env.js\";\nimport { AccessControlBlockError, ConfigurationError, ProxyConnectionError } from \"../lib/errors.js\";\nimport { extractTraceIdFromHeaders, flushLogs, logger } from \"../lib/logger.js\";\nimport { emitProxySetupLog } from \"../lib/proxySetup.js\";\nimport { initializeTrafficMirror } from \"../lib/trafficMirror.js\";\nimport { connectWithTransportFallback, type TransportType } from \"../lib/transportDetection.js\";\nimport { logRequestCounts, proxyServer } from \"../proxyServer.js\";\nimport { StdioClientTransport } from \"../StdioClientTransport.js\";\nimport { startHTTPServer } from \"../startHTTPServer.js\";\nimport { getContainerMCPInfo } from \"../utils/dockerImageDigest.js\";\nimport { getSessionData, type RemoteSessionCliArgs, type StdioSessionCliArgs } from \"../utils/getUserData.js\";\nimport { normalizeUrl } from \"../utils/normalizeUrl.js\";\n\nutil.inspect.defaultOptions.depth = 8;\n\nif (!(\"EventSource\" in global)) {\n // @ts-expect-error - figure out how to use --experimental-eventsource with vitest\n global.EventSource = EventSource;\n}\n\n// Export ProxyType for use by other modules\nexport type ProxyType = \"remote\" | \"stdio\";\n\nexport const main = async () => {\n // Emit the proxy-dispatcher install/failure record captured during phase 0\n // (see lib/proxySetup.ts). Done here, not at the install site, so it goes\n // through the structured logger (file + ingest) instead of stdio.\n emitProxySetupLog(logger);\n\n const argv = await yargs(hideBin(process.argv))\n .scriptName(\"mcp-gateway\")\n .command(\"stdio [command] [args...]\", \"Run a local command with stdio transport (default)\", (yargs) => {\n return yargs\n .positional(\"command\", {\n describe: \"The command to run\",\n type: \"string\",\n })\n .positional(\"args\", {\n array: true,\n describe: \"The arguments to pass to the command\",\n type: \"string\",\n });\n })\n .command(\"remote\", \"Connect to a remote MCP server\", (yargs) => {\n return yargs.options({\n \"bearer-token-env\": {\n describe:\n \"Environment variable name containing a bearer token for remote authentication\",\n type: \"string\",\n },\n \"env-http-headers\": {\n describe:\n \"HTTP headers resolved from environment variables (format: HeaderName:ENV_VAR_NAME)\",\n string: true,\n type: \"array\",\n },\n headers: {\n deprecated: \"Use --env-http-headers with env var names instead. --headers exposes values in process listings.\",\n describe: \"Legacy: headers with literal values (format: key:value)\",\n string: true,\n type: \"array\",\n },\n \"remote-transport\": {\n choices: [\"sse\", \"stream\"],\n deprecated: \"Transport is now auto-detected per MCP spec. This option is ignored.\",\n describe: \"(Deprecated) The transport type - now auto-detected\",\n type: \"string\",\n },\n url: {\n demandOption: true,\n describe: \"The URL of the remote MCP server\",\n type: \"string\",\n },\n });\n })\n .command(\"$0 [command] [args...]\", \"Run a command with MCP arguments (backward compatible)\", (yargs) => {\n return yargs\n .positional(\"command\", {\n describe: \"The command to run\",\n type: \"string\",\n })\n .positional(\"args\", {\n array: true,\n describe: \"The arguments to pass to the command\",\n type: \"string\",\n });\n })\n .env(\"MCP_GATEWAY\")\n .parserConfiguration({\n \"populate--\": true,\n \"unknown-options-as-args\": true,\n })\n .options({\n // NEW: Scanner/Gateway configuration CLI args (these override env vars)\n // Note: These are pre-parsed in mcp-gateway.ts and set as env vars before this file loads\n \"access-control-url\": {\n describe: \"URL of the access control service (overrides MCP_GATEWAY_ACCESS_CONTROL_URL)\",\n type: \"string\",\n },\n debug: {\n default: false,\n describe: \"Enable debug logging\",\n type: \"boolean\",\n },\n env: {\n describe: \"Environment variables to pass to the command (format: KEY=value)\",\n string: true,\n type: \"array\",\n },\n gracefulShutdownTimeout: {\n default: 5000,\n describe: \"The timeout (in milliseconds) for graceful shutdown\",\n type: \"number\",\n },\n \"oauth-providers-url\": {\n describe: \"URL of the OAuth providers config endpoint (overrides MCP_GATEWAY_OAUTH_PROVIDERS_URL)\",\n type: \"string\",\n },\n port: {\n describe: \"Run as HTTP server on the specified port instead of stdio\",\n type: \"number\",\n },\n \"scanner-api-key\": {\n describe: \"API key for scanner authentication (overrides MCP_GATEWAY_SCANNER_API_KEY)\",\n type: \"string\",\n },\n \"scanner-fail-open\": {\n describe: \"Allow requests when scanner is unavailable (overrides MCP_GATEWAY_SCANNER_FAIL_OPEN)\",\n type: \"boolean\",\n },\n \"scanner-timeout-ms\": {\n describe: \"Scanner request timeout in milliseconds (overrides MCP_GATEWAY_SCANNER_TIMEOUT_MS)\",\n type: \"number\",\n },\n \"scanner-url\": {\n describe: \"URL of the scanner/evaluator service (overrides MCP_GATEWAY_SCANNER_URL)\",\n type: \"string\",\n },\n shell: {\n default: process.platform === \"win32\",\n describe: \"Spawn the server via the user's shell (defaults to true on Windows)\",\n type: \"boolean\",\n },\n })\n .help()\n .parseAsync();\n\n // Fetch OAuth provider config from the web extension service before any\n // OAuth flow can run. Fail-safe: on error, the in-memory provider list\n // stays empty and OAuth requests fall through to Dynamic Client\n // Registration. See auth/oauthProvidersClient.ts.\n //\n // Placed AFTER yargs parsing so commands like `--help` / `--version` exit\n // immediately without paying the OAuth fetch latency cost.\n await initializeOAuthProviders(config.oauthProviders);\n\n // Determine the proxy type based on the command\n let proxyType: ProxyType;\n let finalCommand: string | undefined;\n let finalArgs: string[] | undefined;\n let remoteUrl: string | undefined;\n // Transport type used for connection (for logging)\n let usedTransport: TransportType | undefined;\n const remoteHeaders: Record<string, string> = {};\n\n // Parse --env KEY=VALUE args for stdio child processes (general-purpose flag).\n const customEnv: Record<string, string> = {};\n if (argv.env) {\n for (const envVar of argv.env as string[]) {\n const [key, ...valueParts] = envVar.split(\"=\");\n if (key && valueParts.length > 0) {\n customEnv[key.trim()] = valueParts.join(\"=\").trim();\n }\n }\n }\n\n // Resolve an env var reference from process.env. Handles plain env var names\n // (used by --env-http-headers and --bearer-token-env) and ${VAR} template\n // patterns (defense-in-depth, not used by current scanner output).\n // Returns the resolved string, or the name of the first missing/empty var.\n const resolveEnvValue = (value: string): { missingVar?: string; resolved: string } => {\n if (value.includes(\"${\")) {\n let missingVar: string | undefined;\n const resolved = value.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_, varName: string) => {\n const val = process.env[varName];\n if (!val) {\n missingVar = varName;\n return \"\";\n }\n return val;\n });\n return missingVar ? { missingVar, resolved: \"\" } : { resolved };\n }\n // Plain env var name\n const val = process.env[value];\n if (!val) {\n return { missingVar: value, resolved: \"\" };\n }\n return { resolved: val };\n };\n\n // Legacy: --headers with literal values (old installs). New installs use\n // --env-http-headers with synthetic env var names instead. Passthrough only.\n if (argv.headers) {\n for (const header of argv.headers as string[]) {\n const [key, ...valueParts] = header.split(\":\");\n if (key && valueParts.length > 0) {\n remoteHeaders[key.trim()] = valueParts.join(\":\").trim();\n }\n }\n }\n\n // Resolve bearer token from environment variable if specified\n if (argv.bearerTokenEnv) {\n const envVarName = argv.bearerTokenEnv as string;\n const { missingVar, resolved: token } = resolveEnvValue(envVarName);\n if (missingVar) {\n logger.error(\"Bearer token env var specified but not set\", {\n envVar: missingVar,\n eventType: \"mcp_auth_bearer_env_missing\",\n });\n process.exit(1);\n }\n remoteHeaders[\"Authorization\"] = `Bearer ${token}`;\n logger.info(\"Bearer token loaded from environment variable\", {\n authType: \"bearer_token\",\n envVar: envVarName,\n eventType: \"mcp_auth_bearer_env\",\n remoteUrl: argv.url as string,\n });\n }\n\n // Resolve env-http-headers: each entry is either:\n // \"HeaderName:ENV_VAR_NAME\" — full value from a single env var\n // \"HeaderName:Bearer ${ENV_VAR_NAME}\" — template with ${VAR} references resolved from env\n if (argv.envHttpHeaders) {\n for (const entry of argv.envHttpHeaders as string[]) {\n const [headerName, ...valueParts] = entry.split(\":\");\n const valueOrTemplate = valueParts.join(\":\").trim();\n if (headerName && valueOrTemplate) {\n const trimmedHeader = headerName.trim();\n const { missingVar, resolved } = resolveEnvValue(valueOrTemplate);\n\n if (missingVar) {\n logger.error(\"Env var for HTTP header not set\", {\n envVar: missingVar,\n eventType: \"mcp_auth_env_header_missing\",\n header: trimmedHeader,\n });\n process.exit(1);\n }\n\n remoteHeaders[trimmedHeader] = resolved;\n logger.info(\"HTTP header loaded from environment\", {\n eventType: \"mcp_auth_env_header\",\n header: trimmedHeader,\n remoteUrl: argv.url as string,\n });\n }\n }\n }\n\n // Extract and set trace ID from final resolved headers (legacy + env-backed)\n const headerTraceId = extractTraceIdFromHeaders(remoteHeaders);\n if (headerTraceId) {\n logger.setTraceId(headerTraceId);\n }\n\n if (remoteHeaders.Authorization) {\n const authType = remoteHeaders.Authorization.startsWith(\"Bearer \")\n ? \"bearer_token\"\n : remoteHeaders.Authorization.startsWith(\"Basic \")\n ? \"basic\"\n : \"api_key\";\n\n logger.info(\"Authorization header provided\", {\n authType,\n eventType: \"mcp_auth_header_provided\",\n remoteUrl: argv.url as string,\n });\n }\n\n // Initialize traffic scanner from config\n const scannerHeaders = { ...config.scanner.headers };\n\n logger.debug(\"Initializing traffic scanner\", {\n failOpen: config.scanner.failOpen,\n hasApiKey: !!config.scanner.apiKey,\n scannerEnabled: config.scanner.enabled,\n scannerUrl: config.scanner.url,\n timeoutMs: config.scanner.timeoutMs,\n });\n\n // Determine proxy type based on command\n if (argv._[0] === \"remote\") {\n proxyType = \"remote\";\n\n // Warn if deprecated remote-transport argument is used\n if (argv[\"remote-transport\"]) {\n logger.warn(\n \"The --remote-transport option is deprecated and will be ignored. \" +\n \"Transport is now auto-detected per MCP spec (Streamable HTTP with SSE fallback).\",\n { providedTransport: argv[\"remote-transport\"] }\n );\n }\n\n const rawUrl = argv.url as string;\n if (!rawUrl) {\n throw new ConfigurationError(\"Remote URL is required for remote proxy type\", \"url\");\n }\n // Normalize URL to avoid redirect issues on Windows\n // Windows may convert POST to GET on 301/302 redirects (e.g., trailing slash differences)\n remoteUrl = normalizeUrl(rawUrl);\n if (remoteUrl !== rawUrl) {\n logger.debug(\"Normalized remote URL\", { normalizedUrl: remoteUrl, originalUrl: rawUrl });\n }\n } else {\n // Default to stdio for backward compatibility\n proxyType = \"stdio\";\n\n // If -- separator was used, first item in argv[\"--\"] is the command\n if (argv[\"--\"] && (argv[\"--\"] as string[]).length > 0) {\n const dashDashArgs = argv[\"--\"] as string[];\n finalCommand = dashDashArgs[0];\n // Ensure all args are strings (yargs may parse numbers as numeric types)\n finalArgs = dashDashArgs.slice(1).map(String);\n } else {\n // Use positional arguments\n finalCommand = argv.command as string;\n // Ensure all args are strings (yargs may parse numbers as numeric types)\n finalArgs = ((argv.args as unknown[]) || []).map(String);\n }\n\n if (!finalCommand) {\n throw new ConfigurationError(\"No command specified for stdio proxy\", \"command\");\n }\n }\n\n // Detect if this is a container-based MCP (Docker, Podman, nerdctl, finch) and get image info\n let containerMCPInfo: ReturnType<typeof getContainerMCPInfo> | undefined;\n if (proxyType === \"stdio\" && finalCommand) {\n containerMCPInfo = getContainerMCPInfo(finalCommand, finalArgs);\n if (containerMCPInfo.isContainerMCP) {\n logger.info(\"Container MCP detected\", {\n imageDigests: containerMCPInfo.imageDigests,\n imageName: containerMCPInfo.imageName,\n runtime: containerMCPInfo.runtime,\n });\n }\n }\n\n // Build session data and initialize access control client if configured\n let accessControlClient: AccessControlClient | undefined;\n if (config.accessControl.url && config.scanner.apiKey) {\n const sessionDataBase = await getSessionData();\n\n const sessionData =\n proxyType === \"stdio\"\n ? {\n ...sessionDataBase,\n proxyType: \"stdio\" as const,\n stdioCliArgs: {\n args: finalArgs as string[],\n command: finalCommand as string,\n dockerImageDigests: containerMCPInfo?.imageDigests,\n dockerImageName: containerMCPInfo?.imageName,\n proxyType: \"stdio\" as const,\n },\n }\n : {\n ...sessionDataBase,\n proxyType: \"remote\" as const,\n remoteCliArgs: {\n proxyType: \"remote\" as const,\n url: remoteUrl as string,\n },\n };\n\n accessControlClient = new AccessControlClient({\n apiKey: config.scanner.apiKey,\n headers: config.accessControl.headers,\n sessionData,\n timeoutMs: config.accessControl.timeoutMs,\n url: config.accessControl.url,\n });\n }\n\n // Determine server name for error messages\n const serverName = proxyType === \"remote\" ? remoteUrl : finalCommand;\n\n // Initialize access control authorizer\n const accessControlAuthorizer = new AccessControlAuthorizer({\n client: accessControlClient,\n enabled: config.accessControl.enabled,\n serverName,\n });\n\n // Initialize OAuth credential store for remote connections\n const oauthCredentialStore = new OAuthCredentialStore({\n storageDir: config.oauthDir,\n });\n\n // Factory function to create a new Client instance\n const createClient = (): Client =>\n new Client(\n {\n name: \"mcp-gateway\",\n version: config.appVersion,\n },\n {\n capabilities: {},\n }\n );\n\n const connect = async (client: Client): Promise<Client> => {\n // Check access control (async - may query backend)\n const isAllowed = await accessControlAuthorizer.isAllowed();\n if (!isAllowed) {\n const reason = accessControlAuthorizer.getBlockReason();\n logger.warn(\"MCP server blocked by access control\", {\n proxyType,\n reason,\n });\n\n throw new AccessControlBlockError(reason, \"policy_block\");\n }\n\n logger.info(\"MCP server allowed by access control\");\n\n if (proxyType === \"stdio\") {\n // Merge custom CLI environment variables with process.env for child processes\n // Note: This is for MCP server processes, not the proxy's internal configuration\n const mergedEnv = {\n ...process.env,\n ...customEnv,\n } as Record<string, string>;\n\n const transport = new StdioClientTransport({\n args: finalArgs,\n command: finalCommand as string,\n env: mergedEnv,\n onEvent: (event) => {\n if (argv.debug) {\n logger.debug(\"Transport event\", { event });\n }\n },\n shell: argv.shell,\n // Pipe stderr so we can both forward to the parent agent client (preserving\n // the previous \"inherit\" behavior) AND log each line into the gateway's\n // structured log. Without this we have no record of why a spawned MCP\n // server crashed during startup.\n stderr: \"pipe\",\n });\n\n // Cap each captured stderr line so a misbehaving server emitting multi-MB\n // stack dumps or base64 blobs to stderr can't balloon log entries or the\n // in-memory tail buffer. Matches JSONFilterTransform.logDroppedLine.\n const STDERR_LINE_MAX = 1024;\n // If the child never emits a newline we still need an upper bound on the\n // pending line buffer — otherwise a binary blob to stderr would grow\n // memory unbounded and defeat the per-line cap.\n const STDERR_BUFFER_MAX = STDERR_LINE_MAX * 8;\n const truncate = (line: string): string =>\n line.length > STDERR_LINE_MAX ? `${line.slice(0, STDERR_LINE_MAX)}…` : line;\n\n let stderrLineBuffer = \"\";\n const recentStderr: string[] = [];\n const logStderrLine = (line: string) => {\n const truncated = truncate(line);\n recentStderr.push(truncated);\n if (recentStderr.length > 100) recentStderr.shift();\n logger.info(\"MCP server stderr\", {\n command: finalCommand,\n eventType: \"mcp_server_stderr\",\n line: truncated,\n });\n };\n const flushBufferAsLine = () => {\n if (stderrLineBuffer.length === 0) return;\n logStderrLine(stderrLineBuffer);\n stderrLineBuffer = \"\";\n };\n // Decoder buffers incomplete multi-byte UTF-8 sequences across chunk\n // boundaries — without it `chunk.toString()` would emit replacement\n // characters when a codepoint straddles a chunk.\n const stderrDecoder = new StringDecoder(\"utf8\");\n // Drain any pending decoder bytes + remaining line buffer. Idempotent —\n // both `end` and `close` may fire, and the connect-failure catch below\n // also drains explicitly so we don't depend on the SDK ordering.\n let stderrTailDrained = false;\n const drainStderrTail = () => {\n if (stderrTailDrained) return;\n stderrTailDrained = true;\n const decoderTail = stderrDecoder.end();\n if (decoderTail) {\n stderrLineBuffer += decoderTail;\n }\n flushBufferAsLine();\n };\n transport.stderr?.on(\"data\", (chunk: Buffer) => {\n // Forward raw bytes to the parent's stderr to preserve the byte-fidelity\n // the previous `stderr: \"inherit\"` provided. The structured-log path\n // below uses StringDecoder for proper UTF-8 handling.\n process.stderr.write(chunk);\n stderrLineBuffer += stderrDecoder.write(chunk);\n const lines = stderrLineBuffer.split(\"\\n\");\n stderrLineBuffer = lines.pop() ?? \"\";\n for (const line of lines) {\n if (line.length === 0) continue;\n logStderrLine(line);\n }\n // Cap residual partial line — only after extracting any complete lines\n // above, otherwise a chunk containing newlines + an oversize tail would\n // silently discard the parseable lines together with the tail.\n if (stderrLineBuffer.length > STDERR_BUFFER_MAX) {\n flushBufferAsLine();\n }\n });\n transport.stderr?.once(\"end\", drainStderrTail);\n transport.stderr?.once(\"close\", drainStderrTail);\n\n try {\n await client.connect(transport);\n return client;\n } catch (error) {\n drainStderrTail();\n logger.warn(\"Failed original server access\", {\n command: finalCommand,\n error: error instanceof Error ? error.message : String(error),\n eventType: \"mcp_server_access_failed\",\n proxyType,\n stderrTail: recentStderr.slice(-20),\n });\n throw new ProxyConnectionError(`Failed to connect to stdio process: ${error}`, undefined, \"stdio\", {\n argCount: finalArgs?.length,\n command: finalCommand,\n });\n }\n } else if (proxyType === \"remote\") {\n // Connect to remote server\n // Strategy: Try Streamable HTTP first, fall back to SSE if not supported (404/405)\n // Handle OAuth (401) separately - if auth fails, get token and retry the whole process\n\n const url = remoteUrl as string;\n\n // Build request init with headers and optional auth token\n const buildRequestInit = (authToken?: string): RequestInit | undefined => {\n const headers: Record<string, string> = { ...remoteHeaders };\n if (authToken) {\n headers.Authorization = `Bearer ${authToken}`;\n }\n return Object.keys(headers).length > 0 ? { headers } : undefined;\n };\n\n // Check if error is a 401 (requires OAuth)\n const is401Error = (err: unknown): boolean => {\n if (err instanceof UnauthorizedError) return true;\n if (err && typeof err === \"object\") {\n const status = (err as { status?: number }).status;\n const code = (err as { code?: number }).code;\n const message = (err as { message?: string }).message;\n if (status === 401 || code === 401) return true;\n if (message && /unauthorized|401/i.test(String(message))) return true;\n }\n return false;\n };\n\n // Get OAuth token (from cache or via flow)\n const getOAuthToken = async (): Promise<string> => {\n const oauthResult = await performOAuthFlow(url, { credentialStore: oauthCredentialStore });\n logger.info(\"Authentication performed\", {\n authType: \"oauth\",\n eventType: \"mcp_auth_performed\",\n fromCache: oauthResult.fromCache,\n remoteUrl: url,\n });\n return oauthResult.tokens.access_token;\n };\n\n let connectedClient: Client;\n try {\n // Check for cached OAuth token\n const cached = await oauthCredentialStore.load(url);\n let authToken =\n cached && oauthCredentialStore.hasValidAccessToken(cached) ? cached.tokens?.access_token : undefined;\n let usedFreshOAuth = false; // Track if we've already tried with a fresh token\n\n try {\n // Try connecting with transport fallback (Streamable HTTP → SSE)\n // Uses a client factory to create fresh clients for each transport attempt\n const result = await connectWithTransportFallback(createClient, url, buildRequestInit(authToken));\n connectedClient = result.client;\n usedTransport = result.transportType;\n logger.info(\"Connected to remote server\", { hasAuth: !!authToken, transport: usedTransport });\n } catch (err) {\n // Handle 401 errors - need OAuth authentication or token refresh\n if (is401Error(err)) {\n // If we already tried with a fresh OAuth token, don't retry again (avoid infinite loop)\n if (usedFreshOAuth) {\n throw err;\n }\n\n // If cached token was rejected, invalidate it so performOAuthFlow doesn't return it again\n if (authToken) {\n logger.info(\"Cached OAuth token rejected (possibly revoked), invalidating and re-authorizing...\");\n await oauthCredentialStore.update(url, {\n tokens: undefined,\n tokensObtainedAt: undefined,\n });\n } else {\n logger.info(\"Server requires OAuth, starting authentication flow...\");\n }\n\n authToken = await getOAuthToken();\n usedFreshOAuth = true;\n const result = await connectWithTransportFallback(createClient, url, buildRequestInit(authToken));\n connectedClient = result.client;\n usedTransport = result.transportType;\n logger.info(\"Connected to remote server with OAuth\", { transport: usedTransport });\n } else {\n throw err;\n }\n }\n return connectedClient;\n } catch (error) {\n logger.warn(\"Failed original server access\", {\n error: error instanceof Error ? error.message : String(error),\n eventType: \"mcp_server_access_failed\",\n proxyType,\n remoteUrl: url,\n });\n if (error instanceof ProxyConnectionError) throw error;\n throw new ProxyConnectionError(\n `Failed to connect to remote server: ${error instanceof Error ? error.message : String(error)}`,\n url,\n usedTransport,\n { headerCount: Object.keys(remoteHeaders).length }\n );\n }\n }\n\n // Should never reach here, but satisfy TypeScript\n return client;\n };\n\n const proxy = async () => {\n // If port is specified, start HTTP server mode\n if (argv.port) {\n const cliArgs =\n proxyType === \"stdio\"\n ? ({\n args: finalArgs as string[],\n command: finalCommand as string,\n dockerImageDigests: containerMCPInfo?.imageDigests,\n dockerImageName: containerMCPInfo?.imageName,\n proxyType,\n } as StdioSessionCliArgs)\n : ({\n proxyType,\n url: remoteUrl as string,\n } as RemoteSessionCliArgs);\n\n // Initialize traffic evaluator with scanner config once for HTTP server mode\n await initializeTrafficMirror({\n cliArgs,\n scanApiKey: config.scanner.apiKey,\n // Scanner config from env vars\n scanEnabled: config.scanner.enabled,\n scanFailOpen: config.scanner.failOpen,\n scanHeaders: scannerHeaders,\n scanTimeoutMs: config.scanner.timeoutMs,\n scanUrl: config.scanner.url || \"\",\n });\n\n let client: Client | undefined;\n const httpServer = await startHTTPServer({\n createServer: async () => {\n // Create initial client, but connect() may return a different instance for remote connections\n const initialClient = createClient();\n client = await connect(initialClient);\n\n const serverVersion = client.getServerVersion() as {\n name: string;\n version: string;\n };\n\n const serverCapabilities = client.getServerCapabilities() as {\n capabilities: Record<string, unknown>;\n };\n\n const server = new Server(serverVersion, {\n capabilities: serverCapabilities,\n });\n\n proxyServer({\n authorizer: accessControlAuthorizer,\n client,\n server,\n serverCapabilities,\n });\n\n return server;\n },\n onClose: async (server) => {\n if (client) {\n await client.close();\n }\n await server.close();\n },\n port: argv.port,\n });\n\n logger.info(\"MCP gateway started\", {\n eventType: \"mcp_gateway_started\",\n mode: \"http\",\n port: argv.port,\n proxyType,\n });\n if (proxyType === \"stdio\") {\n logger.info(\"MCP server configuration\", {\n args: finalArgs?.join(\" \") || \"\",\n command: finalCommand,\n eventType: \"mcp_server_config\",\n proxyType,\n });\n } else {\n logger.info(\"MCP server configuration\", {\n eventType: \"mcp_server_config\",\n proxyType,\n remoteUrl,\n });\n }\n\n return {\n close: () => {\n return httpServer.close();\n },\n };\n }\n\n // Original stdio mode\n // Create initial client, but connect() may return a different instance for remote connections\n const initialClient = createClient();\n const client = await connect(initialClient);\n\n const serverVersion = client.getServerVersion() as {\n name: string;\n version: string;\n };\n\n const serverCapabilities = client.getServerCapabilities() as {\n capabilities: Record<string, unknown>;\n };\n\n // Always create a stdio server for client-facing interface\n const server = new Server(serverVersion, {\n capabilities: serverCapabilities,\n });\n\n proxyServer({\n authorizer: accessControlAuthorizer,\n client,\n server,\n serverCapabilities,\n });\n\n const stdioTransport = new StdioServerTransport();\n\n const cliArgs =\n proxyType === \"stdio\"\n ? ({\n args: finalArgs as string[],\n command: finalCommand as string,\n dockerImageDigests: containerMCPInfo?.imageDigests,\n dockerImageName: containerMCPInfo?.imageName,\n proxyType,\n } as StdioSessionCliArgs)\n : ({\n proxyType,\n url: remoteUrl as string,\n } as RemoteSessionCliArgs);\n\n // Initialize traffic evaluator with scanner config\n await initializeTrafficMirror({\n cliArgs,\n scanApiKey: config.scanner.apiKey,\n // Scanner config from env vars\n scanEnabled: config.scanner.enabled,\n scanFailOpen: config.scanner.failOpen,\n scanHeaders: scannerHeaders,\n scanTimeoutMs: config.scanner.timeoutMs,\n scanUrl: config.scanner.url || \"\",\n });\n\n await server.connect(stdioTransport);\n\n logger.info(\"MCP gateway started\", {\n eventType: \"mcp_gateway_started\",\n mode: \"stdio\",\n proxyType,\n });\n\n // Log MCP server configuration\n logger.info(\"MCP server configuration\", {\n capabilities: Object.keys(serverCapabilities || {}),\n eventType: \"mcp_server_config\",\n proxyType,\n serverName: serverVersion?.name,\n serverVersion: serverVersion?.version,\n ...(proxyType === \"stdio\"\n ? { args: finalArgs?.join(\" \") || \"\", command: finalCommand }\n : { remoteUrl, transport: usedTransport }),\n });\n\n return {\n close: () => {\n // Close the client connection\n return client.close();\n },\n };\n };\n\n const createGracefulShutdown = ({\n server,\n timeout,\n }: {\n server: { close: () => Promise<void> | void };\n timeout: number;\n }) => {\n let shutdownTriggered = false;\n\n const gracefulShutdown = async (trigger: string) => {\n if (shutdownTriggered) {\n return;\n }\n shutdownTriggered = true;\n\n logger.info(\"MCP gateway stopping\", {\n eventType: \"mcp_gateway_stopping\",\n signal: trigger,\n });\n\n try {\n await server.close();\n // Log final request counts before shutdown\n logRequestCounts();\n // Flush logs to ingest endpoint before exit\n await flushLogs(2000);\n process.exit(0);\n } catch (error) {\n const errorMessage = `Error during shutdown: ${error}`;\n logger.error(\"Error during shutdown\", {\n error: errorMessage,\n eventType: \"mcp_gateway_stopped\",\n shutdownTimeout: timeout,\n });\n // Flush logs to ingest endpoint before exit\n await flushLogs(2000);\n process.exit(1);\n }\n };\n\n const timeoutHandler = (trigger: string) => async () => {\n setTimeout(() => {\n logger.error(\"Graceful shutdown timeout exceeded, forcing exit\");\n process.exit(1);\n }, timeout).unref();\n\n await gracefulShutdown(trigger);\n };\n\n process.once(\"SIGTERM\", timeoutHandler(\"SIGTERM\"));\n process.once(\"SIGINT\", timeoutHandler(\"SIGINT\"));\n\n // Stdin EOF — fires when the parent (e.g. claude) closes our stdio pipe.\n // On Windows, parents typically TerminateProcess without graceful signals,\n // but the MCP SDK closes stdio first; this gives us a window to reap our\n // child process tree before being killed. On POSIX it provides a backup\n // path when SIGHUP/SIGTERM doesn't reach us.\n process.stdin.once(\"end\", timeoutHandler(\"stdin-end\"));\n\n return () => {\n return server.close();\n };\n };\n\n // Log startup before attempting to connect\n logger.info(\"MCP gateway starting\", {\n command: finalCommand,\n eventType: \"mcp_gateway_starting\",\n proxyType,\n transport: proxyType === \"remote\" ? \"auto-detect\" : \"stdio\",\n url: remoteUrl,\n });\n\n try {\n const server = await proxy();\n\n createGracefulShutdown({\n server,\n timeout: argv.gracefulShutdownTimeout,\n });\n } catch (error) {\n // Handle access control blocks gracefully\n if (error instanceof AccessControlBlockError) {\n // Show user-visible error message\n console.error(\"\\n❌ ACCESS DENIED\");\n console.error(\"━\".repeat(60));\n console.error(\n \"\\nThis MCP server has been blocked by Onyx because it is not authorized by your organization's access control policy. Please contact your administrator if you believe this is an error.\\n\"\n );\n\n // Also log for debugging\n logger.error(\"MCP server blocked by access control policy\", {\n error: error.message,\n reason: error.reason,\n });\n\n // Flush logs to ingest endpoint before exit\n await flushLogs(2000);\n process.exit(1);\n }\n\n // Handle unexpected errors\n const errorMessage = `Could not start the proxy: ${error}`;\n logger.error(\"MCP server failed to start\", {\n args: finalArgs,\n command: finalCommand,\n error: errorMessage,\n eventType: \"mcp_server_start_failed\",\n proxyType,\n remoteUrl,\n transport: usedTransport,\n });\n\n // Flush logs to ingest endpoint before exit\n await flushLogs(2000);\n process.exit(1);\n }\n};\n"],"x_google_ignoreList":[0],"mappings":"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"}
@@ -1 +0,0 @@
1
- import"./env-JHfIqf7O.js";import"./logger-CxuXnoy8.js";import"./providers-DlG5fsS1.js";import{fetchOAuthProviders as e,initializeOAuthProviders as t}from"./oauthProvidersClient-CA99rXX6.js";export{t as initializeOAuthProviders};
@@ -1,136 +0,0 @@
1
- import{SSEClientTransport as e,StreamableHTTPClientTransport as t,discoverAuthorizationServerMetadata as n,discoverOAuthProtectedResourceMetadata as r,exchangeAuthorization as i,normalizeUrl as a,refreshAuthorization as o,registerClient as s,startAuthorization as c}from"./normalizeUrl-u9rsNRmG.js";import{config as l}from"./env-JHfIqf7O.js";import{logger as u}from"./logger-CxuXnoy8.js";import{findProviderForUrl as d}from"./providers-DlG5fsS1.js";import{platform as f}from"node:os";import{createServer as p}from"node:http";import{execFile as m}from"node:child_process";import{URL as h}from"node:url";async function g(e){let t=e.toString(),n=f(),r=b(t);return new Promise((e,i)=>{let a,o;if(n===`win32`){let e=`Start-Process ${v(t)}`,n=_(e);a=y(),o=[`-NoProfile`,`-NonInteractive`,`-ExecutionPolicy`,`Bypass`,`-EncodedCommand`,n]}else n===`darwin`?(a=`open`,o=[t]):(a=`xdg-open`,o=[t]);m(a,o,(t,o,s)=>{if(t){let e={command:a,os:n,stderr:s,url:r};u.warn(`Failed to open browser automatically`,{...e,error:t.message});let o=Error(`Failed to open browser: ${t.message} (command: ${a}, os: ${n}, url: ${r})`);o.cause=t,i(o)}else u.debug(`Browser opened successfully`,{url:r}),e()})})}function _(e){return Buffer.from(e,`utf16le`).toString(`base64`)}function v(e){return`'${e.replaceAll(`'`,`''`)}'`}function y(){return`${process.env.SYSTEMROOT||process.env.windir||`C:\\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`}function b(e){try{let t=new URL(e);return t.search?`${t.protocol}//${t.host}${t.pathname}?[REDACTED]`:`${t.protocol}//${t.host}${t.pathname}`}catch{return`[INVALID_URL]`}}const x={CALLBACK_FAILED:`mcp_auth_callback_failed`,DCR_FAILED:`mcp_auth_dcr_failed`,REFRESH_FAILED:`mcp_auth_refresh_failed`,SERVER_METADATA_FAILED:`mcp_auth_server_metadata_failed`,STATE_MISMATCH:`mcp_auth_state_mismatch`,TOKEN_EXCHANGE_FAILED:`mcp_auth_token_exchange_failed`},S=49152;function C(){return Math.floor(Math.random()*(65535-S+1))+S}function w(e,t){return new Promise((n,r)=>{let i=t=>{e.removeListener(`listening`,a),r(t)},a=()=>{e.removeListener(`error`,i),n()};e.once(`error`,i),e.once(`listening`,a),e.listen(t,`127.0.0.1`)})}async function T(e={}){let{maxAttempts:t=5,preferredPort:n,serverUrl:r,timeoutMs:i=3e5}=e,a=r?O(r):void 0,o,s,c={},l=new Promise((e,t)=>{o=e,s=t}),d,f=p((e,t)=>{if(e.url===`/favicon.ico`){t.writeHead(404),t.end();return}if(!e.url?.startsWith(`/callback`)){t.writeHead(404),t.end(`Not Found`);return}try{let n=new h(e.url,`http://localhost:${d}`),r=n.searchParams.get(`code`),i=n.searchParams.get(`error`),l=n.searchParams.get(`error_description`),p=n.searchParams.get(`state`);if(i){u.error(`OAuth authorization error`,{error:i,errorDescription:l,eventType:x.CALLBACK_FAILED,...a?{serverUrl:a}:{}}),t.writeHead(400,{"Content-Type":`text/html`}),t.end(D(i,l||void 0)),clearTimeout(c.id),s(Error(`OAuth authorization failed: ${i}${l?` - ${l}`:``}`));return}if(!r){u.error(`OAuth callback missing authorization code`,{eventType:x.CALLBACK_FAILED,reason:`missing_code`,...a?{serverUrl:a}:{}}),t.writeHead(400,{"Content-Type":`text/html`}),t.end(D(`missing_code`,`No authorization code was provided`)),clearTimeout(c.id),s(Error(`OAuth callback missing authorization code`));return}u.info(`OAuth authorization code received`,{codePrefix:`${r.substring(0,10)}...`,hasState:!!p}),t.writeHead(200,{"Content-Type":`text/html`}),t.end(`<!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Authorization Successful</title>
7
- <style>
8
- body {
9
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
- display: flex;
11
- justify-content: center;
12
- align-items: center;
13
- min-height: 100vh;
14
- margin: 0;
15
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
16
- color: #fff;
17
- }
18
- .container {
19
- text-align: center;
20
- padding: 2rem;
21
- }
22
- .checkmark {
23
- width: 80px;
24
- height: 80px;
25
- border-radius: 50%;
26
- background: #10b981;
27
- display: flex;
28
- align-items: center;
29
- justify-content: center;
30
- margin: 0 auto 1.5rem;
31
- animation: pop 0.3s ease-out;
32
- }
33
- .checkmark svg {
34
- width: 40px;
35
- height: 40px;
36
- stroke: white;
37
- stroke-width: 3;
38
- }
39
- h1 {
40
- margin: 0 0 0.5rem;
41
- font-size: 1.75rem;
42
- font-weight: 600;
43
- }
44
- p {
45
- margin: 0;
46
- color: #a0aec0;
47
- font-size: 1rem;
48
- }
49
- @keyframes pop {
50
- 0% { transform: scale(0); }
51
- 50% { transform: scale(1.1); }
52
- 100% { transform: scale(1); }
53
- }
54
- </style>
55
- </head>
56
- <body>
57
- <div class="container">
58
- <div class="checkmark">
59
- <svg viewBox="0 0 24 24" fill="none">
60
- <path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round"/>
61
- </svg>
62
- </div>
63
- <h1>Authorization Successful</h1>
64
- <p>You can close this window and return to the terminal.</p>
65
- </div>
66
- <script>setTimeout(() => window.close(), 3000);<\/script>
67
- </body>
68
- </html>`),clearTimeout(c.id),o({code:r,state:p||void 0}),setTimeout(()=>f.close(),1e3)}catch(e){u.error(`Error processing OAuth callback`,{error:String(e),eventType:x.CALLBACK_FAILED,reason:`callback_processing_error`,...a?{serverUrl:a}:{}}),t.writeHead(500),t.end(`Internal Server Error`),clearTimeout(c.id),s(e instanceof Error?e:Error(String(e)))}});c.id=setTimeout(()=>{s(Error(`OAuth callback timeout after ${i}ms`)),f.close()},i);let m=[],g;for(;m.length<t;){let e;if(m.length===0&&n!==void 0)e=n;else for(e=C();m.includes(e);)e=C();m.push(e);try{await w(f,e),d=e;let t=`http://localhost:${d}/callback`;return u.info(`OAuth callback server started`,{attempt:m.length,callbackUrl:t,port:d}),{callbackUrl:t,close:()=>new Promise(e=>{clearTimeout(c.id),f.close(()=>e()),f.closeAllConnections()}),port:d,waitForCallback:()=>l}}catch(n){if(n.code===`EADDRINUSE`)u.debug(`Port in use, trying another port`,{attempt:m.length,maxAttempts:t,port:e}),g=Error(`Port ${e} is already in use`);else throw clearTimeout(c.id),f.close(),n}}throw clearTimeout(c.id),f.close(),Error(`Failed to start OAuth callback server after ${t} attempts. Tried ports: ${m.join(`, `)}. Last error: ${g?.message||`unknown`}`)}function E(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&#039;`)}function D(e,t){return`<!DOCTYPE html>
69
- <html lang="en">
70
- <head>
71
- <meta charset="UTF-8">
72
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
73
- <title>Authorization Failed</title>
74
- <style>
75
- body {
76
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
77
- display: flex;
78
- justify-content: center;
79
- align-items: center;
80
- min-height: 100vh;
81
- margin: 0;
82
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
83
- color: #fff;
84
- }
85
- .container {
86
- text-align: center;
87
- padding: 2rem;
88
- max-width: 400px;
89
- }
90
- .error-icon {
91
- width: 80px;
92
- height: 80px;
93
- border-radius: 50%;
94
- background: #ef4444;
95
- display: flex;
96
- align-items: center;
97
- justify-content: center;
98
- margin: 0 auto 1.5rem;
99
- }
100
- .error-icon svg {
101
- width: 40px;
102
- height: 40px;
103
- stroke: white;
104
- stroke-width: 3;
105
- }
106
- h1 {
107
- margin: 0 0 0.5rem;
108
- font-size: 1.75rem;
109
- font-weight: 600;
110
- }
111
- .error-code {
112
- color: #f87171;
113
- font-family: monospace;
114
- margin-bottom: 0.5rem;
115
- }
116
- p {
117
- margin: 0;
118
- color: #a0aec0;
119
- font-size: 1rem;
120
- }
121
- </style>
122
- </head>
123
- <body>
124
- <div class="container">
125
- <div class="error-icon">
126
- <svg viewBox="0 0 24 24" fill="none">
127
- <path d="M6 18L18 6M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/>
128
- </svg>
129
- </div>
130
- <h1>Authorization Failed</h1>
131
- <p class="error-code">${E(e)}</p>
132
- ${t?`<p>${E(t)}</p>`:``}
133
- </div>
134
- </body>
135
- </html>`}function O(e){try{let t=new h(e);return t.username=``,t.password=``,t.search=``,t.hash=``,t.toString()}catch{return e}}var k=class extends Error{constructor(e,t){let n=t instanceof Error?t.message:String(t);super(`Auth server metadata discovery failed for ${e}: ${n}`),this.authServerUrl=e,this.cause=t,this.name=`AuthServerDiscoveryError`}};async function A(e,t={}){let{logger:i,signal:a}=t,o=a?j(a):void 0;i?.debug(`Discovering OAuth protected resource metadata...`);let s;try{s=o?await r(e,void 0,o):await r(e),i?.info(`Discovered protected resource metadata`,{authorizationServers:s?.authorization_servers,resource:s?.resource,scopesSupported:s?.scopes_supported})}catch(e){i?.warn(`Could not discover protected resource metadata, will try direct discovery`,{error:String(e)})}let c;s?.authorization_servers?.length?(c=new URL(s.authorization_servers[0]),i?.debug(`Using authorization server from resource metadata`,{authServerUrl:c.toString()})):(c=e,i?.debug(`Using server URL as authorization server`,{authServerUrl:c.toString()})),i?.debug(`Discovering authorization server metadata...`);let l;try{l=o?await n(c,{fetchFn:o}):await n(c)}catch(e){throw new k(c,e)}return l&&i?.info(`Discovered authorization server metadata`,{authorizationEndpoint:l.authorization_endpoint,issuer:l.issuer,registrationEndpoint:l.registration_endpoint,tokenEndpoint:l.token_endpoint}),{authMetadata:l,authServerUrl:c,resourceMetadata:s}}function j(e){return(t,n)=>{let r=n?.signal,i=r?AbortSignal.any([e,r]):e;return fetch(t,{...n,signal:i})}}const M={"claude-code":`Claude Code (mcp-gateway)`,codex:`Codex`,copilot:`Visual Studio Code`,cursor:`Cursor`,opencode:`OpenCode`};function N(){return F(l.clientAppName)}async function P(e,t={}){let{credentialStore:n,scope:r,timeoutMs:o}=t,l=a(e),f=new URL(l);if(u.info(`Starting OAuth flow`,{serverUrl:l}),n){let e=await R(f,n);if(e)return u.info(`Using existing OAuth credentials`,{clientId:e.clientInfo.client_id,serverUrl:l}),e}u.debug(`Starting OAuth callback server...`);let p=await T({serverUrl:l,timeoutMs:o}),m=p.callbackUrl;u.info(`OAuth callback server started`,{callbackUrl:m,port:p.port});try{let e=d(f);if(e)return u.info(`Using pre-configured OAuth provider`,{providerId:e.id,providerName:e.name}),await L(f,e,p,n,r);let a,o;try{({authMetadata:a,authServerUrl:o}=await A(f,{logger:u}))}catch(e){throw u.error(`Auth server metadata discovery failed`,{error:String(e),eventType:x.SERVER_METADATA_FAILED,serverUrl:l,...e instanceof k?{authServer:e.authServerUrl.toString()}:{}}),e}if(!a)throw u.error(`Auth server metadata absent (server may not support OAuth 2.0)`,{authServer:o.toString(),eventType:x.SERVER_METADATA_FAILED,reason:`metadata_absent`,serverUrl:l}),Error(`Could not discover OAuth metadata from ${o}. The server may not support OAuth 2.0 or the metadata endpoint is not accessible.`);let h,_=n?await n.load(l):void 0,v=_?.clientInfo?.redirect_uris||[];if(_?.clientInfo&&v.includes(m)&&_?.clientInfo)u.info(`Reusing existing DCR client registration`,{clientId:_.clientInfo.client_id,clientName:_.clientInfo.client_name,redirectUri:m}),h=_.clientInfo;else{_?.clientInfo&&u.info(`Existing DCR client has different redirect_uri, re-registering`,{existingRedirectUris:v,newRedirectUri:m});let e={client_name:t.clientMetadata?.client_name||N(),grant_types:t.clientMetadata?.grant_types||[`authorization_code`,`refresh_token`],redirect_uris:[m],response_types:t.clientMetadata?.response_types||[`code`],token_endpoint_auth_method:t.clientMetadata?.token_endpoint_auth_method||`none`,...r&&{scope:r}};if(u.debug(`Client metadata for DCR`,{clientMetadata:e}),!a.registration_endpoint)throw u.error(`DCR registration unsupported by auth server`,{authServer:o.toString(),eventType:x.DCR_FAILED,reason:`no_registration_endpoint`,serverUrl:l}),Error(`Authorization server does not support Dynamic Client Registration. No registration_endpoint found in metadata.`);u.info(`Registering client via DCR...`,{registrationEndpoint:a.registration_endpoint});try{h=await s(o,{clientMetadata:e,metadata:a})}catch(e){throw u.error(`DCR registration failed`,{authServer:o.toString(),error:String(e),eventType:x.DCR_FAILED,registrationEndpoint:a.registration_endpoint,serverUrl:l}),e}u.info(`Client registered successfully via DCR`,{clientId:h.client_id,clientName:h.client_name,clientSecretExpiresAt:h.client_secret_expires_at}),n&&await n.update(l,{clientInfo:h})}let y=crypto.randomUUID();u.debug(`Starting authorization flow...`);let{authorizationUrl:b,codeVerifier:S}=await c(o,{clientInformation:h,metadata:a,redirectUrl:m,scope:r,state:y});await g(b),u.debug(`Waiting for OAuth callback...`);let C=await p.waitForCallback();if(C.state!==y)throw u.error(`OAuth state mismatch — possible CSRF attack`,{authServer:o.toString(),eventType:x.STATE_MISMATCH,serverUrl:l}),Error(`OAuth state mismatch - possible CSRF attack`);u.info(`Authorization code received`,{codePrefix:`${C.code.substring(0,10)}...`}),u.debug(`Exchanging authorization code for tokens...`);let w;try{w=await i(o,{authorizationCode:C.code,clientInformation:h,codeVerifier:S,metadata:a,redirectUri:m})}catch(e){throw u.error(`Token exchange failed`,{authServer:o.toString(),clientId:h.client_id,error:String(e),eventType:x.TOKEN_EXCHANGE_FAILED,grantType:`authorization_code`,serverUrl:l}),e}if(u.info(`OAuth tokens obtained successfully`,{expiresIn:w.expires_in,hasRefreshToken:!!w.refresh_token,tokenType:w.token_type}),n){let e=new Date().toISOString();await n.update(l,{tokens:w,tokensObtainedAt:e}),u.info(`OAuth credentials persisted to disk`,{serverUrl:l})}return{clientInfo:h,fromCache:!1,tokens:w}}finally{await p.close()}}function F(e){let t=e?.trim().toLowerCase();return t?M[t]??`MCP Gateway`:`MCP Gateway`}async function I(e,t,n,r){let i=e.tokenEndpoint,a=e.tokenEndpointAuthMethod||(e.clientSecret?`client_secret_post`:`none`),o=new URLSearchParams;o.set(`grant_type`,`authorization_code`),o.set(`code`,t),o.set(`redirect_uri`,n),r&&o.set(`code_verifier`,r);let s={Accept:`application/json`,"Content-Type":`application/x-www-form-urlencoded`};a===`client_secret_basic`&&e.clientSecret?s.Authorization=`Basic ${Buffer.from(`${e.clientId}:${e.clientSecret}`).toString(`base64`)}`:a===`client_secret_post`&&e.clientSecret?(o.set(`client_id`,e.clientId),o.set(`client_secret`,e.clientSecret)):o.set(`client_id`,e.clientId),u.debug(`Sending token request`,{authMethod:a,providerId:e.id,tokenEndpoint:i});let c=await fetch(i,{body:o.toString(),headers:s,method:`POST`});if(!c.ok){let t=await c.text();throw u.error(`Token exchange failed`,{authMethod:a,error:t,eventType:x.TOKEN_EXCHANGE_FAILED,grantType:`authorization_code`,httpStatus:c.status,providerId:e.id,tokenEndpoint:i}),Error(`Token exchange failed: ${c.status} ${t}`)}let l=c.headers.get(`content-type`)||``,d;if(l.includes(`application/json`))d=await c.json();else if(l.includes(`application/x-www-form-urlencoded`)||l.includes(`text/plain`)){let e=await c.text(),t=new URLSearchParams(e);d=Object.fromEntries(t.entries())}else{let e=await c.text();try{d=JSON.parse(e)}catch{let t=new URLSearchParams(e);d=Object.fromEntries(t.entries())}}return typeof d.expires_in==`string`&&(d.expires_in=parseInt(d.expires_in,10)),{access_token:d.access_token,expires_in:d.expires_in,refresh_token:d.refresh_token,scope:d.scope,token_type:d.token_type||`bearer`}}async function L(e,t,n,r,i){let a=e.toString(),o=n.callbackUrl,s={client_id:t.clientId,client_name:t.name,client_secret:t.clientSecret,redirect_uris:[o],token_endpoint_auth_method:t.tokenEndpointAuthMethod||(t.clientSecret?`client_secret_post`:`none`)};u.info(`Using pre-configured OAuth client`,{clientId:t.clientId,hasClientSecret:!!t.clientSecret,providerId:t.id,tokenEndpointAuthMethod:s.token_endpoint_auth_method});let c=i||t.scopes?.join(` `),l=t.usePkce!==!1,d,f,p;if(l){let{generateCodeChallenge:e,generateCodeVerifier:t}=await import(`./pkce-CYE2WffE.js`);d=t(),f=await e(d),p=`S256`}let m=new URL(t.authorizationEndpoint);m.searchParams.set(`client_id`,t.clientId),m.searchParams.set(`redirect_uri`,o),m.searchParams.set(`response_type`,`code`),c&&m.searchParams.set(`scope`,c);let h=crypto.randomUUID();m.searchParams.set(`state`,h),f&&p&&(m.searchParams.set(`code_challenge`,f),m.searchParams.set(`code_challenge_method`,p)),await g(m),u.debug(`Waiting for OAuth callback...`);let _=await n.waitForCallback();if(_.state!==h)throw u.error(`OAuth state mismatch — possible CSRF attack`,{eventType:x.STATE_MISMATCH,providerId:t.id,serverUrl:a}),Error(`OAuth state mismatch - possible CSRF attack`);u.info(`Authorization code received`,{codePrefix:`${_.code.substring(0,10)}...`,providerId:t.id}),u.debug(`Exchanging authorization code for tokens...`);let v=await I(t,_.code,o,d);if(u.info(`OAuth tokens obtained successfully`,{expiresIn:v.expires_in,hasRefreshToken:!!v.refresh_token,providerId:t.id,tokenType:v.token_type}),r){let e=new Date().toISOString();await r.update(a,{clientInfo:s,tokens:v,tokensObtainedAt:e}),u.info(`OAuth credentials persisted to disk`,{providerId:t.id,serverUrl:a})}return{clientInfo:s,fromCache:!1,tokens:v}}async function R(e,t){let n=await t.load(e.toString());if(!n){u.debug(`No stored credentials found`,{serverUrl:e.toString()});return}if(!n.clientInfo){u.debug(`Stored credentials have no client info`,{serverUrl:e.toString()});return}if(t.hasValidAccessToken(n)&&n.tokens)return u.info(`Using cached OAuth credentials with valid access token`,{clientId:n.clientInfo.client_id,serverUrl:e.toString()}),{clientInfo:n.clientInfo,fromCache:!0,tokens:n.tokens};if(t.hasRefreshToken(n)){let r=await z(e,n,t);if(r)return r}u.debug(`Stored credentials are expired and no valid refresh token`,{serverUrl:e.toString()})}async function z(e,t,i){if(!(!t.clientInfo||!t.tokens?.refresh_token)){u.info(`Attempting to refresh expired access token`,{clientId:t.clientInfo.client_id,serverUrl:e.toString()});try{let a;try{a=await r(e)}catch{}let s;s=a?.authorization_servers?.length?new URL(a.authorization_servers[0]):e;let c=await n(s),l=await o(s,{clientInformation:t.clientInfo,metadata:c,refreshToken:t.tokens.refresh_token});u.info(`Successfully refreshed access token`,{clientId:t.clientInfo.client_id,expiresIn:l.expires_in,hasNewRefreshToken:!!l.refresh_token});let d=new Date().toISOString();return await i.update(e.toString(),{tokens:l,tokensObtainedAt:d}),{clientInfo:t.clientInfo,fromCache:!0,tokens:l}}catch(n){u.warn(`Failed to refresh access token, will require re-authorization`,{clientId:t.clientInfo.client_id,error:String(n),eventType:x.REFRESH_FAILED,serverUrl:e.toString()}),await i.update(e.toString(),{tokens:void 0,tokensObtainedAt:void 0});return}}}const B=[`streamable-http`,`sse`];async function V(e,t,n){let{result:r,transportType:i}=await W(async r=>{let i=e(),a=H(t,r,n);return await i.connect(a),i});return{client:r,transportType:i}}function H(n,r,i){let a=new URL(n);return r===`streamable-http`?new t(a,{requestInit:i}):new e(a,{requestInit:i})}function U(e){if(!e||typeof e!=`object`)return!1;let t=e.status,n=e.code,r=e.message;return!!(t===404||t===405||n===404||n===405||r&&/404|not found|405|method not allowed/i.test(String(r)))}async function W(e){let t=[];for(let n of B)try{return{result:await e(n),transportType:n}}catch(e){if(U(e)){u.info(`${n} not supported, trying next transport...`),t.push({err:e,transport:n});continue}throw e}let n=t.map(({err:e,transport:t})=>{let n=e?.status;if(n!==void 0)return`${t}: ${n}`;let r=e instanceof Error?e.message:String(e);return`${t}: ${r}`}).join(`, `),r=t.length>0?Error(`all transports returned not-supported (${n})`):Error(`No compatible transport found`);throw r.cause=t,r}export{k as AuthServerDiscoveryError,V as connectWithTransportFallback,A as discoverAuthServer,N as getDcrClientName,P as performOAuthFlow,W as walkTransports};
136
- //# sourceMappingURL=transportDetection-DOLigM8S.js.map