@onyxsecurity/mcp-gateway 1.0.60 → 1.0.61

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.
@@ -0,0 +1,2 @@
1
+ import{SSEClientTransport as e,StreamableHTTPClientTransport as t,discoverAuthorizationServerMetadata as n,discoverOAuthProtectedResourceMetadata as r,logger as i}from"./streamableHttp-DM0LA7pR.js";var a=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 o(e,t={}){let{logger:i,signal:o}=t,c=o?s(o):void 0;i?.debug(`Discovering OAuth protected resource metadata...`);let l;try{l=c?await r(e,void 0,c):await r(e),i?.info(`Discovered protected resource metadata`,{authorizationServers:l?.authorization_servers,resource:l?.resource,scopesSupported:l?.scopes_supported})}catch(e){i?.warn(`Could not discover protected resource metadata, will try direct discovery`,{error:String(e)})}let u;l?.authorization_servers?.length?(u=new URL(l.authorization_servers[0]),i?.debug(`Using authorization server from resource metadata`,{authServerUrl:u.toString()})):(u=e,i?.debug(`Using server URL as authorization server`,{authServerUrl:u.toString()})),i?.debug(`Discovering authorization server metadata...`);let d;try{d=c?await n(u,{fetchFn:c}):await n(u)}catch(e){throw new a(u,e)}return d&&i?.info(`Discovered authorization server metadata`,{authorizationEndpoint:d.authorization_endpoint,issuer:d.issuer,registrationEndpoint:d.registration_endpoint,tokenEndpoint:d.token_endpoint}),{authMetadata:d,authServerUrl:u,resourceMetadata:l}}function s(e){return(t,n)=>{let r=n?.signal,i=r?AbortSignal.any([e,r]):e;return fetch(t,{...n,signal:i})}}let c=[];function l(e){let t=typeof e==`string`?new URL(e):e,n=t.hostname.toLowerCase();for(let e of c)for(let t of e.urlPatterns){let r=t.toLowerCase();if(f(n,r))return i.debug(`Found matching OAuth provider for URL`,{hostname:n,pattern:t,providerId:e.id,providerName:e.name}),d(e)}i.debug(`No pre-configured OAuth provider found for URL`,{hostname:n,serverUrl:t.toString()})}function u(e){c=e.map(d)}function d(e){return{...e,scopes:e.scopes?[...e.scopes]:void 0,urlPatterns:[...e.urlPatterns]}}function f(e,t){return!!(e===t||e.endsWith(`.${t}`))}const p=[`streamable-http`,`sse`];async function m(e,t,n){let{result:r,transportType:i}=await _(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 g(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 _(e){let t=[];for(let n of p)try{return{result:await e(n),transportType:n}}catch(e){if(g(e)){i.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{a as AuthServerDiscoveryError,m as connectWithTransportFallback,o as discoverAuthServer,l as findProviderForUrl,u as setOAuthProviders,_ as walkTransports};
2
+ //# sourceMappingURL=transportDetection-DquHtdNu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transportDetection-DquHtdNu.js","names":["authServerUrl: URL","cause: unknown","resourceMetadata: OAuthProtectedResourceMetadata | undefined","authMetadata: Awaited<ReturnType<typeof discoverAuthorizationServerMetadata>>","oauthProviders: OAuthProviderConfig[]","TRANSPORT_PRIORITY: TransportType[]","client","transportType","failures: Array<{ err: unknown; transport: TransportType }>"],"sources":["../src/auth/discovery.ts","../src/auth/providers.ts","../src/lib/transportDetection.ts"],"sourcesContent":["import {\n discoverAuthorizationServerMetadata,\n discoverOAuthProtectedResourceMetadata,\n} from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { OAuthProtectedResourceMetadata } from \"@modelcontextprotocol/sdk/shared/auth.js\";\n\nexport interface DiscoverAuthServerOptions {\n /**\n * Optional logger. When provided, emits the same debug/info/warn lines that\n * `performOAuthFlow` used to emit inline. When omitted, discovery runs\n * silently.\n */\n logger?: DiscoveryLogger;\n /**\n * Optional AbortSignal, threaded into both SDK discovery calls via a fetch\n * wrapper. When omitted, the SDK calls are made with no fetch override —\n * preserving the existing `performOAuthFlow` call shape exactly.\n */\n signal?: AbortSignal;\n}\n\n/**\n * Minimal logging surface used by the discovery helper. Matches the shape of\n * `../lib/logger.ts`'s singleton so callers can pass it directly. Omit to run\n * discovery silently (the probe's call pattern).\n */\nexport interface DiscoveryLogger {\n debug(message: string, meta?: Record<string, unknown>): void;\n info(message: string, meta?: Record<string, unknown>): void;\n warn(message: string, meta?: Record<string, unknown>): void;\n}\n\nexport interface DiscoveryResult {\n /** Auth-server metadata. May be undefined — caller decides how to respond. */\n authMetadata: Awaited<ReturnType<typeof discoverAuthorizationServerMetadata>>;\n /**\n * The chosen auth server URL: the first entry of `resourceMetadata.authorization_servers`\n * when present, otherwise `serverUrl` itself.\n */\n authServerUrl: URL;\n /** Protected-resource metadata. Undefined when the document is missing or lookup soft-failed. */\n resourceMetadata: OAuthProtectedResourceMetadata | undefined;\n}\n\n/**\n * Thrown when `discoverAuthorizationServerMetadata` fails. Preserves the\n * computed `authServerUrl` so callers can include it in error messages /\n * probe results — the pre-refactor inline code had it in scope naturally.\n */\nexport class AuthServerDiscoveryError extends Error {\n constructor(\n public readonly authServerUrl: URL,\n public readonly cause: unknown\n ) {\n const msg = cause instanceof Error ? cause.message : String(cause);\n super(`Auth server metadata discovery failed for ${authServerUrl}: ${msg}`);\n this.name = \"AuthServerDiscoveryError\";\n }\n}\n\n/**\n * Runs the OAuth metadata discovery sequence shared by the full OAuth flow and\n * the install-time DCR probe:\n *\n * 1. Look up the protected-resource metadata (soft-fails on error).\n * 2. Pick the auth server URL — the first `authorization_servers` entry, or\n * `serverUrl` as a fallback.\n * 3. Look up the auth-server metadata. Throws iff the SDK call itself throws;\n * does NOT throw when the SDK returns undefined (caller decides).\n *\n * No policy decisions baked in — both callers (`performOAuthFlow` and `probeOne`)\n * apply their own handling to the result.\n */\nexport async function discoverAuthServer(\n serverUrl: URL,\n options: DiscoverAuthServerOptions = {}\n): Promise<DiscoveryResult> {\n const { logger, signal } = options;\n const fetchFn = signal ? signaledFetch(signal) : undefined;\n\n logger?.debug(\"Discovering OAuth protected resource metadata...\");\n let resourceMetadata: OAuthProtectedResourceMetadata | undefined;\n try {\n // Preserve the exact SDK call arity used by performOAuthFlow when no\n // signal is provided (single-arg form) — some SDKs treat an explicit\n // `undefined` differently from an omitted arg.\n resourceMetadata = fetchFn\n ? await discoverOAuthProtectedResourceMetadata(serverUrl, undefined, fetchFn)\n : await discoverOAuthProtectedResourceMetadata(serverUrl);\n logger?.info(\"Discovered protected resource metadata\", {\n authorizationServers: resourceMetadata?.authorization_servers,\n resource: resourceMetadata?.resource,\n scopesSupported: resourceMetadata?.scopes_supported,\n });\n } catch (err) {\n logger?.warn(\"Could not discover protected resource metadata, will try direct discovery\", {\n error: String(err),\n });\n }\n\n let authServerUrl: URL;\n if (resourceMetadata?.authorization_servers?.length) {\n authServerUrl = new URL(resourceMetadata.authorization_servers[0]);\n logger?.debug(\"Using authorization server from resource metadata\", {\n authServerUrl: authServerUrl.toString(),\n });\n } else {\n authServerUrl = serverUrl;\n logger?.debug(\"Using server URL as authorization server\", {\n authServerUrl: authServerUrl.toString(),\n });\n }\n\n logger?.debug(\"Discovering authorization server metadata...\");\n let authMetadata: Awaited<ReturnType<typeof discoverAuthorizationServerMetadata>>;\n try {\n authMetadata = fetchFn\n ? await discoverAuthorizationServerMetadata(authServerUrl, { fetchFn })\n : await discoverAuthorizationServerMetadata(authServerUrl);\n } catch (err) {\n throw new AuthServerDiscoveryError(authServerUrl, err);\n }\n\n if (authMetadata) {\n logger?.info(\"Discovered authorization server metadata\", {\n authorizationEndpoint: authMetadata.authorization_endpoint,\n issuer: authMetadata.issuer,\n registrationEndpoint: authMetadata.registration_endpoint,\n tokenEndpoint: authMetadata.token_endpoint,\n });\n }\n\n return { authMetadata, authServerUrl, resourceMetadata };\n}\n\n/** Wraps global fetch so the SDK inherits a caller-supplied AbortSignal. */\nfunction signaledFetch(probeSignal: AbortSignal): typeof fetch {\n return (input, init) => {\n const sdkSignal = init?.signal;\n const combined =\n sdkSignal ? AbortSignal.any([probeSignal, sdkSignal]) : probeSignal;\n return fetch(input, { ...init, signal: combined });\n };\n}\n","import type { OAuthProviderConfig } from \"../config/config.js\";\n\nimport { logger } from \"../lib/logger.js\";\n\n// Mutable store. Populated by setOAuthProviders() during startup once the\n// gateway has fetched provider config from the web extension service.\n// Pre-startup or when the fetch fails, the array stays empty and OAuth flows\n// fall through to Dynamic Client Registration via oauthFlow.ts.\nlet oauthProviders: OAuthProviderConfig[] = [];\n\n/**\n * Finds a pre-configured OAuth provider that matches the given server URL.\n *\n * @param serverUrl The MCP server URL to match against provider URL patterns\n * @returns The matching provider config, or undefined if no match found\n */\nexport function findProviderForUrl(serverUrl: string | URL): OAuthProviderConfig | undefined {\n const serverUrlObj = typeof serverUrl === \"string\" ? new URL(serverUrl) : serverUrl;\n const hostname = serverUrlObj.hostname.toLowerCase();\n\n for (const provider of oauthProviders) {\n for (const pattern of provider.urlPatterns) {\n // Pattern can be a hostname or a hostname with path prefix\n const patternLower = pattern.toLowerCase();\n\n // Check if pattern matches hostname (with or without subdomains)\n if (matchesHostname(hostname, patternLower)) {\n logger.debug(\"Found matching OAuth provider for URL\", {\n hostname,\n pattern,\n providerId: provider.id,\n providerName: provider.name,\n });\n // Return a defensive clone — callers in oauthFlow.ts must not be\n // able to corrupt the in-memory store via the returned reference.\n return cloneProvider(provider);\n }\n }\n }\n\n logger.debug(\"No pre-configured OAuth provider found for URL\", {\n hostname,\n serverUrl: serverUrlObj.toString(),\n });\n return undefined;\n}\n\n/**\n * Gets a provider by its ID.\n *\n * @param providerId The provider ID (e.g., \"github\")\n * @returns The provider config, or undefined if not found\n */\nexport function getProviderById(providerId: string): OAuthProviderConfig | undefined {\n const found = oauthProviders.find((p) => p.id === providerId);\n return found ? cloneProvider(found) : undefined;\n}\n\n/**\n * Lists all configured OAuth providers.\n * Useful for debugging and admin interfaces.\n *\n * @returns Array of provider summaries (without secrets)\n */\nexport function listProviders(): Array<{\n hasClientSecret: boolean;\n id: string;\n name: string;\n urlPatterns: string[];\n}> {\n return oauthProviders.map((p) => ({\n hasClientSecret: !!p.clientSecret,\n id: p.id,\n name: p.name,\n urlPatterns: [...p.urlPatterns],\n }));\n}\n\n/**\n * Replaces the in-memory OAuth provider list. Called once at gateway startup\n * after fetching from the web extension service. Pass an empty array to clear.\n *\n * Stores defensive shallow copies of each provider so that callers cannot\n * mutate the in-memory store via the references they passed in. The\n * urlPatterns array is also cloned for the same reason.\n */\nexport function setOAuthProviders(providers: OAuthProviderConfig[]): void {\n oauthProviders = providers.map(cloneProvider);\n}\n\n// cloneProvider returns a defensive shallow copy with array fields cloned,\n// so callers can read and even mutate the result without corrupting the\n// in-memory store. Used on every read path (findProviderForUrl,\n// getProviderById) to match the write-side contract in setOAuthProviders.\nfunction cloneProvider(p: OAuthProviderConfig): OAuthProviderConfig {\n return {\n ...p,\n scopes: p.scopes ? [...p.scopes] : undefined,\n urlPatterns: [...p.urlPatterns],\n };\n}\n\n/**\n * Checks if a hostname matches a pattern.\n * Supports exact matches and subdomain matches.\n *\n * @param hostname The hostname to check (e.g., \"api.github.com\")\n * @param pattern The pattern to match against (e.g., \"github.com\")\n * @returns true if the hostname matches the pattern\n */\nfunction matchesHostname(hostname: string, pattern: string): boolean {\n // Exact match\n if (hostname === pattern) {\n return true;\n }\n\n // Subdomain match: hostname ends with \".pattern\"\n // e.g., \"api.github.com\" matches \"github.com\"\n if (hostname.endsWith(`.${pattern}`)) {\n return true;\n }\n\n return false;\n}\n","/**\n * MCP Transport Utilities\n *\n * Simple transport creation for remote MCP servers.\n * Transport type is determined by trying Streamable HTTP first,\n * falling back to SSE if the endpoint doesn't support it.\n */\n\nimport type { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\n\nimport { logger } from \"./logger.js\";\n\nexport type TransportType = \"sse\" | \"streamable-http\";\n\n/** Order in which transports are tried (per MCP spec: Streamable HTTP first, SSE fallback) */\nconst TRANSPORT_PRIORITY: TransportType[] = [\"streamable-http\", \"sse\"];\n\n/** Result of a successful transport connection */\nexport interface TransportConnectionResult {\n /** The connected client instance */\n client: Client;\n /** The transport type that successfully connected */\n transportType: TransportType;\n}\n\n/**\n * Connects to a remote MCP server, trying transports in order.\n * Per MCP spec: tries Streamable HTTP first, falls back to SSE if not supported (404/405).\n *\n * Creates a fresh client for each transport attempt to avoid state issues.\n * The MCP SDK Client maintains internal connection state that does not properly\n * reset after a failed connect attempt, so reusing the same client instance\n * across transport fallbacks can cause \"already connected\" or similar errors.\n *\n * @param createClient - Factory function to create a new Client instance\n * @param url - The URL of the remote MCP server\n * @param requestInit - Optional request init for the transport (headers, etc.)\n * @returns The connected client and transport type\n * @throws The error from the last transport attempt if none succeed,\n * or immediately if an error is not a \"transport not supported\" error\n */\nexport async function connectWithTransportFallback(\n createClient: () => Client,\n url: string,\n requestInit?: RequestInit\n): Promise<TransportConnectionResult> {\n const { result: client, transportType } = await walkTransports(async (transportType) => {\n // Create a fresh client for each transport attempt to avoid state issues\n const client = createClient();\n const transport = createTransport(url, transportType, requestInit);\n await client.connect(transport);\n return client;\n });\n return { client, transportType };\n}\n\n/**\n * Creates a transport instance for the given type.\n */\nexport function createTransport(\n url: string,\n type: TransportType,\n requestInit?: RequestInit\n): SSEClientTransport | StreamableHTTPClientTransport {\n const parsedUrl = new URL(url);\n\n if (type === \"streamable-http\") {\n return new StreamableHTTPClientTransport(parsedUrl, { requestInit });\n }\n\n return new SSEClientTransport(parsedUrl, { requestInit });\n}\n\n/**\n * Checks if an error indicates the transport/endpoint is not supported (404/405).\n * This means we should try a different transport type.\n */\nexport function isTransportNotSupportedError(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n\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\n // Check status codes that indicate endpoint doesn't support this transport\n if (status === 404 || status === 405 || code === 404 || code === 405) {\n return true;\n }\n\n // Check error message for these status codes\n if (message && /404|not found|405|method not allowed/i.test(String(message))) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Walks transports in priority order, calling `attempt` for each. Returns the\n * first successful result. Continues to the next transport on\n * `isTransportNotSupportedError` (404/405); propagates any other error.\n *\n * Shared primitive for both connect-style attempts (the runtime proxy via\n * `connectWithTransportFallback`) and probe-style attempts (the install-time\n * scan in `src/probe`). Callers supply the per-transport request shape; this\n * helper owns only the priority-order + fallback-on-404/405 logic.\n *\n * When every transport fails with a not-supported error, throws an aggregate\n * error whose message lists each transport's status — so callers can tell\n * \"URL is dead\" from \"URL only speaks one transport\". The original errors are\n * available via `error.cause` (an array of `{ transport, err }`).\n *\n * @param attempt - Per-transport function. Throw an error matching\n * `isTransportNotSupportedError` to trigger fallback; throw anything else\n * (or any one transport's non-fallback error) to propagate.\n * @returns The first transport that succeeded and its result.\n * @throws The propagated non-fallback error, or an aggregate of every\n * transport's not-supported error when all transports fail.\n */\nexport async function walkTransports<T>(\n attempt: (transportType: TransportType) => Promise<T>\n): Promise<{ result: T; transportType: TransportType }> {\n const failures: Array<{ err: unknown; transport: TransportType }> = [];\n\n for (const transportType of TRANSPORT_PRIORITY) {\n try {\n const result = await attempt(transportType);\n return { result, transportType };\n } catch (err) {\n if (isTransportNotSupportedError(err)) {\n logger.info(`${transportType} not supported, trying next transport...`);\n failures.push({ err, transport: transportType });\n continue;\n }\n\n // Non-transport error (e.g., 401, network error) - propagate immediately\n throw err;\n }\n }\n\n // All transports failed with \"not supported\" errors. Build a message that\n // lists every transport's status (or message), so callers don't see only\n // the last transport's failure and assume the others were never tried.\n const summary = failures\n .map(({ err, transport }) => {\n const status = (err as { status?: number } | null)?.status;\n if (status !== undefined) return `${transport}: ${status}`;\n const message = err instanceof Error ? err.message : String(err);\n return `${transport}: ${message}`;\n })\n .join(\", \");\n const aggregate =\n failures.length > 0\n ? new Error(`all transports returned not-supported (${summary})`)\n : new Error(\"No compatible transport found\");\n // Preserve original errors for callers that need to introspect them.\n (aggregate as { cause?: unknown } & Error).cause = failures;\n throw aggregate;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onyxsecurity/mcp-gateway",
3
- "version": "1.0.60",
3
+ "version": "1.0.61",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "prebuild": "npx tsx scripts/generate-config.ts",
@@ -1,142 +0,0 @@
1
- import{AccessControlBlockError as e,BUILD_TIME_CONFIG as t,Client as n,ConfigurationError as r,ProxyConnectionError as i,ReadBuffer as a,SSEClientTransport as o,Server as s,StdioServerTransport as c,StreamableHTTPClientTransport as l,UnauthorizedError as u,chownToRealUser as d,compressClientInfo as f,config as p,createParser as m,discoverAuthorizationServerMetadata as h,discoverOAuthProtectedResourceMetadata as g,exchangeAuthorization as _,extractTraceIdFromHeaders as ee,flushLogs as v,getSessionData as y,initializeTrafficMirror as b,logRequestCounts as te,logger as x,normalizeUrl as S,proxyServer as ne,refreshAuthorization as C,registerClient as w,serializeMessage as T,startAuthorization as re,startHTTPServer as ie}from"./normalizeUrl-Cni_Rjv3.js";import{emitProxySetupLog as ae,hideBin as oe,yargs_default as se}from"./helpers-Cwd-UJrA.js";import"./pkce-ANRIC6ce.js";import{createHash as E}from"node:crypto";import{join as D}from"node:path";import{platform as O}from"node:os";import{chmod as k,mkdir as ce,readFile as le,rm as ue,writeFile as de}from"node:fs/promises";import{createServer as fe}from"node:http";import{StringDecoder as pe}from"node:string_decoder";import{setTimeout as me}from"node:timers";import he from"node:util";import{execFile as ge,execSync as _e,spawn as ve,spawnSync as ye}from"node:child_process";import{URL as be}from"node:url";import{PassThrough as xe,Transform as Se}from"node:stream";var Ce=class extends Event{constructor(e,t){super(e),this.code=t?.code??void 0,this.message=t?.message??void 0}[Symbol.for(`nodejs.util.inspect.custom`)](e,t,n){return n(Te(this),t)}[Symbol.for(`Deno.customInspect`)](e,t){return e(Te(this),t)}};function we(e){let t=globalThis.DOMException;return typeof t==`function`?new t(e,`SyntaxError`):SyntaxError(e)}function A(e){return e instanceof Error?`errors`in e&&Array.isArray(e.errors)?e.errors.map(A).join(`, `):`cause`in e&&e.cause instanceof Error?`${e}: ${A(e.cause)}`:e.message:`${e}`}function Te(e){return{type:e.type,message:e.message,code:e.code,defaultPrevented:e.defaultPrevented,cancelable:e.cancelable,timeStamp:e.timeStamp}}var Ee=e=>{throw TypeError(e)},j=(e,t,n)=>t.has(e)||Ee(`Cannot `+n),M=(e,t,n)=>(j(e,t,`read from private field`),n?n.call(e):t.get(e)),N=(e,t,n)=>t.has(e)?Ee(`Cannot add the same private member more than once`):t instanceof WeakSet?t.add(e):t.set(e,n),P=(e,t,n,r)=>(j(e,t,`write to private field`),t.set(e,n),n),F=(e,t,n)=>(j(e,t,`access private method`),n),I,L,R,z,B,V,H,U,W,G,K,q,J,Y,X,Z,De,Oe,ke,Ae,Q,je,Me,$=class extends EventTarget{constructor(e,t){super(),N(this,Y),this.CONNECTING=0,this.OPEN=1,this.CLOSED=2,N(this,I),N(this,L),N(this,R),N(this,z),N(this,B),N(this,V),N(this,H),N(this,U,null),N(this,W),N(this,G),N(this,K,null),N(this,q,null),N(this,J,null),N(this,Z,async e=>{var t;M(this,G).reset();let{body:n,redirected:r,status:i,headers:a}=e;if(i===204){F(this,Y,Q).call(this,`Server sent HTTP 204, not reconnecting`,204),this.close();return}if(r?P(this,R,new URL(e.url)):P(this,R,void 0),i!==200){F(this,Y,Q).call(this,`Non-200 status code (${i})`,i);return}if(!(a.get(`content-type`)||``).startsWith(`text/event-stream`)){F(this,Y,Q).call(this,`Invalid content type, expected "text/event-stream"`,i);return}if(M(this,I)===this.CLOSED)return;P(this,I,this.OPEN);let o=new Event(`open`);if((t=M(this,J))==null||t.call(this,o),this.dispatchEvent(o),typeof n!=`object`||!n||!(`getReader`in n)){F(this,Y,Q).call(this,`Invalid response body, expected a web ReadableStream`,i),this.close();return}let s=new TextDecoder,c=n.getReader(),l=!0;do{let{done:e,value:t}=await c.read();t&&M(this,G).feed(s.decode(t,{stream:!e})),e&&(l=!1,M(this,G).reset(),F(this,Y,je).call(this))}while(l)}),N(this,De,e=>{P(this,W,void 0),!(e.name===`AbortError`||e.type===`aborted`)&&F(this,Y,je).call(this,A(e))}),N(this,ke,e=>{typeof e.id==`string`&&P(this,U,e.id);let t=new MessageEvent(e.event||`message`,{data:e.data,origin:M(this,R)?M(this,R).origin:M(this,L).origin,lastEventId:e.id||``});M(this,q)&&(!e.event||e.event===`message`)&&M(this,q).call(this,t),this.dispatchEvent(t)}),N(this,Ae,e=>{P(this,V,e)}),N(this,Me,()=>{P(this,H,void 0),M(this,I)===this.CONNECTING&&F(this,Y,X).call(this)});try{if(e instanceof URL)P(this,L,e);else if(typeof e==`string`)P(this,L,new URL(e,Ne()));else throw Error(`Invalid URL`)}catch{throw we(`An invalid or illegal string was specified`)}P(this,G,m({onEvent:M(this,ke),onRetry:M(this,Ae)})),P(this,I,this.CONNECTING),P(this,V,3e3),P(this,B,t?.fetch??globalThis.fetch),P(this,z,t?.withCredentials??!1),F(this,Y,X).call(this)}get readyState(){return M(this,I)}get url(){return M(this,L).href}get withCredentials(){return M(this,z)}get onerror(){return M(this,K)}set onerror(e){P(this,K,e)}get onmessage(){return M(this,q)}set onmessage(e){P(this,q,e)}get onopen(){return M(this,J)}set onopen(e){P(this,J,e)}addEventListener(e,t,n){let r=t;super.addEventListener(e,r,n)}removeEventListener(e,t,n){let r=t;super.removeEventListener(e,r,n)}close(){M(this,H)&&clearTimeout(M(this,H)),M(this,I)!==this.CLOSED&&(M(this,W)&&M(this,W).abort(),P(this,I,this.CLOSED),P(this,W,void 0))}};I=new WeakMap,L=new WeakMap,R=new WeakMap,z=new WeakMap,B=new WeakMap,V=new WeakMap,H=new WeakMap,U=new WeakMap,W=new WeakMap,G=new WeakMap,K=new WeakMap,q=new WeakMap,J=new WeakMap,Y=new WeakSet,X=function(){P(this,I,this.CONNECTING),P(this,W,new AbortController),M(this,B)(M(this,L),F(this,Y,Oe).call(this)).then(M(this,Z)).catch(M(this,De))},Z=new WeakMap,De=new WeakMap,Oe=function(){let e={mode:`cors`,redirect:`follow`,headers:{Accept:`text/event-stream`,...M(this,U)?{"Last-Event-ID":M(this,U)}:void 0},cache:`no-store`,signal:M(this,W)?.signal};return`window`in globalThis&&(e.credentials=this.withCredentials?`include`:`same-origin`),e},ke=new WeakMap,Ae=new WeakMap,Q=function(e,t){var n;M(this,I)!==this.CLOSED&&P(this,I,this.CLOSED);let r=new Ce(`error`,{code:t,message:e});(n=M(this,K))==null||n.call(this,r),this.dispatchEvent(r)},je=function(e,t){var n;if(M(this,I)===this.CLOSED)return;P(this,I,this.CONNECTING);let r=new Ce(`error`,{code:t,message:e});(n=M(this,K))==null||n.call(this,r),this.dispatchEvent(r),P(this,H,setTimeout(M(this,Me),M(this,V)))},Me=new WeakMap,$.CONNECTING=0,$.OPEN=1,$.CLOSED=2;function Ne(){let e=`document`in globalThis?globalThis.document:void 0;return e&&typeof e==`object`&&`baseURI`in e&&typeof e.baseURI==`string`?e.baseURI:void 0}var Pe=class{client;enabled;lastBlockReason;serverName;constructor(e){this.enabled=e.enabled,this.client=e.client,this.serverName=e.serverName,x.info(`AccessControlAuthorizer initialized`,{enabled:this.enabled,hasClient:!!this.client,serverName:this.serverName})}getBlockReason(){return this.lastBlockReason?this.lastBlockReason:`MCP server${this.serverName?` '${this.serverName}'`:``} is not authorized for use in your organization and has been blocked by Onyx.`}async isAllowed(){if(!this.enabled)return!0;if(this.client)try{let e=await this.client.authorize();return e.action===`block`?(e.reason?this.lastBlockReason=e.reason:this.lastBlockReason=`MCP server${this.serverName?` '${this.serverName}'`:``} is not authorized for use in your organization and has been blocked by Onyx.`,!1):!0}catch(e){return x.error(`Access control authorization failed with unexpected error`,{error:String(e)}),!0}return x.warn(`No access control client configured, allowing by default`),!0}},Fe=class{clientInfoBase64;config;constructor(e){this.config=e,this.clientInfoBase64=this.getClientInfoBase64(),x.info(`AccessControlClient initialized`,{timeoutMs:e.timeoutMs,url:e.url})}async authorize(){try{let e=await this.sendAuthorizeRequest();return x.debug(`Access control check successful`,{action:e.action}),e}catch(e){return x.warn(`Access control check failed, failing open (allowing by default)`,{error:String(e)}),{action:`allow`}}}getClientInfoBase64(){return f(this.config.sessionData)}async sendAuthorizeRequest(){let e=new AbortController,t=setTimeout(()=>e.abort(),this.config.timeoutMs);try{let t=`${this.config.url}/${this.config.apiKey}/mcp/${this.clientInfoBase64}`,n=await fetch(t,{headers:{...this.config.headers},method:`POST`,signal:e.signal});if(!n.ok)throw Error(`Access control service returned ${n.status}: ${n.statusText}`);let r=await n.json();if(!r.action||![`allow`,`block`].includes(r.action))throw Error(`Invalid access control response format: action="${r.action}"`);return r}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Access control check timed out after ${this.config.timeoutMs}ms`):e}finally{clearTimeout(t)}}};async function Ie(e){let t=e.toString(),n=O(),r=Be(t);return new Promise((e,i)=>{let a,o;if(n===`win32`){let e=`Start-Process ${Re(t)}`,n=Le(e);a=ze(),o=[`-NoProfile`,`-NonInteractive`,`-ExecutionPolicy`,`Bypass`,`-EncodedCommand`,n]}else n===`darwin`?(a=`open`,o=[t]):(a=`xdg-open`,o=[t]);ge(a,o,(t,o,s)=>{if(t){let e={command:a,os:n,stderr:s,url:r};x.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 x.debug(`Browser opened successfully`,{url:r}),e()})})}function Le(e){return Buffer.from(e,`utf16le`).toString(`base64`)}function Re(e){return`'${e.replaceAll(`'`,`''`)}'`}function ze(){return`${process.env.SYSTEMROOT||process.env.windir||`C:\\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`}function Be(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 Ve=49152;function He(){return Math.floor(Math.random()*(65535-Ve+1))+Ve}function Ue(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 We(e={}){let{maxAttempts:t=5,preferredPort:n,timeoutMs:r=3e5}=e,i,a,o={},s=new Promise((e,t)=>{i=e,a=t}),c,l=fe((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 be(e.url,`http://localhost:${c}`),r=n.searchParams.get(`code`),s=n.searchParams.get(`error`),u=n.searchParams.get(`error_description`),d=n.searchParams.get(`state`);if(s){x.error(`OAuth authorization error`,{error:s,errorDescription:u}),t.writeHead(400,{"Content-Type":`text/html`}),t.end(Ke(s,u||void 0)),clearTimeout(o.id),a(Error(`OAuth authorization failed: ${s}${u?` - ${u}`:``}`));return}if(!r){x.error(`OAuth callback missing authorization code`),t.writeHead(400,{"Content-Type":`text/html`}),t.end(Ke(`missing_code`,`No authorization code was provided`)),clearTimeout(o.id),a(Error(`OAuth callback missing authorization code`));return}x.info(`OAuth authorization code received`,{codePrefix:`${r.substring(0,10)}...`,hasState:!!d}),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(o.id),i({code:r,state:d||void 0}),setTimeout(()=>l.close(),1e3)}catch(e){x.error(`Error processing OAuth callback`,{error:String(e)}),t.writeHead(500),t.end(`Internal Server Error`),clearTimeout(o.id),a(e instanceof Error?e:Error(String(e)))}});o.id=setTimeout(()=>{a(Error(`OAuth callback timeout after ${r}ms`)),l.close()},r);let u=[],d;for(;u.length<t;){let e;if(u.length===0&&n!==void 0)e=n;else for(e=He();u.includes(e);)e=He();u.push(e);try{await Ue(l,e),c=e;let t=`http://localhost:${c}/callback`;return x.info(`OAuth callback server started`,{attempt:u.length,callbackUrl:t,port:c}),{callbackUrl:t,close:()=>new Promise(e=>{clearTimeout(o.id),l.close(()=>e())}),port:c,waitForCallback:()=>s}}catch(n){if(n.code===`EADDRINUSE`)x.debug(`Port in use, trying another port`,{attempt:u.length,maxAttempts:t,port:e}),d=Error(`Port ${e} is already in use`);else throw clearTimeout(o.id),l.close(),n}}throw clearTimeout(o.id),l.close(),Error(`Failed to start OAuth callback server after ${t} attempts. Tried ports: ${u.join(`, `)}. Last error: ${d?.message||`unknown`}`)}function Ge(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&#039;`)}function Ke(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">${Ge(e)}</p>
132
- ${t?`<p>${Ge(t)}</p>`:``}
133
- </div>
134
- </body>
135
- </html>`}var qe=class{storageDir;constructor(e){this.storageDir=e.storageDir}async delete(e){let t=S(e),n=this.getFilePath(t);try{await ue(n),x.debug(`Deleted stored credentials`,{filePath:n,serverUrl:t})}catch(e){e.code!==`ENOENT`&&x.warn(`Failed to delete stored credentials`,{error:String(e),filePath:n,serverUrl:t})}}hasRefreshToken(e){return!!e.tokens?.refresh_token}hasValidAccessToken(e){return!!e.tokens?.access_token&&!this.isAccessTokenExpired(e)}isAccessTokenExpired(e){if(!e.tokens||!e.tokensObtainedAt)return!0;let{expires_in:t}=e.tokens;if(!t)return!1;let n=new Date(e.tokensObtainedAt).getTime()+t*1e3,r=Date.now(),i=r>=n-3e4;return x.debug(`Checked access token expiration`,{expiresAt:new Date(n).toISOString(),expiresIn:t,isExpired:i,now:new Date(r).toISOString(),obtainedAt:e.tokensObtainedAt}),i}async load(e){let t=S(e),n=this.getFilePath(t);try{let e=await le(n,`utf-8`),r=JSON.parse(e);if(S(r.serverUrl)!==t){x.warn(`Credential file server URL mismatch`,{expected:t,filePath:n,found:r.serverUrl});return}return x.debug(`Loaded stored credentials`,{clientId:r.clientInfo?.client_id,hasRefreshToken:!!r.tokens?.refresh_token,hasTokens:!!r.tokens,serverUrl:t}),r}catch(e){if(e.code===`ENOENT`){x.debug(`No stored credentials found`,{serverUrl:t});return}x.warn(`Failed to load stored credentials`,{error:String(e),filePath:n,serverUrl:t});return}}async save(e){await this.ensureStorageDir();let t=this.getFilePath(e.serverUrl),n=JSON.stringify(e,null,2);try{await de(t,n,{encoding:`utf-8`,mode:384}),await k(t,384),x.debug(`Saved credentials to disk`,{clientId:e.clientInfo?.client_id,filePath:t,hasTokens:!!e.tokens,serverUrl:e.serverUrl})}catch(n){throw x.error(`Failed to save credentials`,{error:String(n),filePath:t,serverUrl:e.serverUrl}),n}}async update(e,t){let n=S(e),r=await this.load(n),i=new Date().toISOString(),a=r?{...r,...t,serverUrl:n,updatedAt:i}:{createdAt:i,serverUrl:n,updatedAt:i,...t};return await this.save(a),a}async ensureStorageDir(){try{await ce(this.storageDir,{mode:448,recursive:!0})}catch(e){if(e.code!==`EEXIST`)throw e}try{await d(this.storageDir)}catch(e){x.warn(`Failed to fix OAuth storage directory ownership`,{error:String(e),storageDir:this.storageDir})}}generateKey(e){return E(`sha256`).update(e).digest(`hex`).substring(0,16)}getFilePath(e){let t=this.generateKey(e);return D(this.storageDir,`${t}.json`)}};const Je=t.OAUTH_PROVIDERS;function Ye(e){let t=typeof e==`string`?new URL(e):e,n=t.hostname.toLowerCase();for(let e of Je)for(let t of e.urlPatterns){let r=t.toLowerCase();if(Xe(n,r))return x.debug(`Found matching OAuth provider for URL`,{hostname:n,pattern:t,providerId:e.id,providerName:e.name}),e}x.debug(`No pre-configured OAuth provider found for URL`,{hostname:n,serverUrl:t.toString()})}function Xe(e,t){return!!(e===t||e.endsWith(`.${t}`))}async function Ze(e,t={}){let{credentialStore:n,scope:r,timeoutMs:i}=t,a=S(e),o=new URL(a);if(x.info(`Starting OAuth flow`,{serverUrl:a}),n){let e=await et(o,n);if(e)return x.info(`Using existing OAuth credentials`,{clientId:e.clientInfo.client_id,serverUrl:a}),e}x.debug(`Starting OAuth callback server...`);let s=await We({timeoutMs:i}),c=s.callbackUrl;x.info(`OAuth callback server started`,{callbackUrl:c,port:s.port});try{let e=Ye(o);if(e)return x.info(`Using pre-configured OAuth provider`,{providerId:e.id,providerName:e.name}),await $e(o,e,s,n,r);x.debug(`Discovering OAuth protected resource metadata...`);let i;try{i=await g(o),x.info(`Discovered protected resource metadata`,{authorizationServers:i?.authorization_servers,resource:i?.resource,scopesSupported:i?.scopes_supported})}catch(e){x.warn(`Could not discover protected resource metadata, will try direct discovery`,{error:String(e)})}let l;i?.authorization_servers?.length?(l=new URL(i.authorization_servers[0]),x.debug(`Using authorization server from resource metadata`,{authServerUrl:l.toString()})):(l=o,x.debug(`Using server URL as authorization server`,{authServerUrl:l.toString()})),x.debug(`Discovering authorization server metadata...`);let u=await h(l);if(!u)throw Error(`Could not discover OAuth metadata from ${l}. The server may not support OAuth 2.0 or the metadata endpoint is not accessible.`);x.info(`Discovered authorization server metadata`,{authorizationEndpoint:u.authorization_endpoint,issuer:u.issuer,registrationEndpoint:u.registration_endpoint,tokenEndpoint:u.token_endpoint});let d,f=n?await n.load(a):void 0,p=f?.clientInfo?.redirect_uris||[];if(f?.clientInfo&&p.includes(c)&&f?.clientInfo)x.info(`Reusing existing DCR client registration`,{clientId:f.clientInfo.client_id,clientName:f.clientInfo.client_name,redirectUri:c}),d=f.clientInfo;else{f?.clientInfo&&x.info(`Existing DCR client has different redirect_uri, re-registering`,{existingRedirectUris:p,newRedirectUri:c});let e={client_name:t.clientMetadata?.client_name||`MCP Gateway`,grant_types:t.clientMetadata?.grant_types||[`authorization_code`,`refresh_token`],redirect_uris:[c],response_types:t.clientMetadata?.response_types||[`code`],token_endpoint_auth_method:t.clientMetadata?.token_endpoint_auth_method||`none`,...r&&{scope:r}};if(x.debug(`Client metadata for DCR`,{clientMetadata:e}),!u.registration_endpoint)throw Error(`Authorization server does not support Dynamic Client Registration. No registration_endpoint found in metadata.`);x.info(`Registering client via DCR...`,{registrationEndpoint:u.registration_endpoint}),d=await w(l,{clientMetadata:e,metadata:u}),x.info(`Client registered successfully via DCR`,{clientId:d.client_id,clientName:d.client_name,clientSecretExpiresAt:d.client_secret_expires_at}),n&&await n.update(a,{clientInfo:d})}let m=crypto.randomUUID();x.debug(`Starting authorization flow...`);let{authorizationUrl:ee,codeVerifier:v}=await re(l,{clientInformation:d,metadata:u,redirectUrl:c,scope:r,state:m});await Ie(ee),x.debug(`Waiting for OAuth callback...`);let y=await s.waitForCallback();if(y.state!==m)throw Error(`OAuth state mismatch - possible CSRF attack`);x.info(`Authorization code received`,{codePrefix:`${y.code.substring(0,10)}...`}),x.debug(`Exchanging authorization code for tokens...`);let b=await _(l,{authorizationCode:y.code,clientInformation:d,codeVerifier:v,metadata:u,redirectUri:c});if(x.info(`OAuth tokens obtained successfully`,{expiresIn:b.expires_in,hasRefreshToken:!!b.refresh_token,tokenType:b.token_type}),n){let e=new Date().toISOString();await n.update(a,{tokens:b,tokensObtainedAt:e}),x.info(`OAuth credentials persisted to disk`,{serverUrl:a})}return{clientInfo:d,fromCache:!1,tokens:b}}finally{await s.close()}}async function Qe(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),x.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 x.error(`Token exchange failed`,{error:t,providerId:e.id,status:c.status}),Error(`Token exchange failed: ${c.status} ${t}`)}let l=c.headers.get(`content-type`)||``,u;if(l.includes(`application/json`))u=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);u=Object.fromEntries(t.entries())}else{let e=await c.text();try{u=JSON.parse(e)}catch{let t=new URLSearchParams(e);u=Object.fromEntries(t.entries())}}return typeof u.expires_in==`string`&&(u.expires_in=parseInt(u.expires_in,10)),{access_token:u.access_token,expires_in:u.expires_in,refresh_token:u.refresh_token,scope:u.scope,token_type:u.token_type||`bearer`}}async function $e(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`)};x.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,u,d,f;if(l){let{generateCodeChallenge:e,generateCodeVerifier:t}=await import(`./pkce-dymop6yt.js`);u=t(),d=await e(u),f=`S256`}let p=new URL(t.authorizationEndpoint);p.searchParams.set(`client_id`,t.clientId),p.searchParams.set(`redirect_uri`,o),p.searchParams.set(`response_type`,`code`),c&&p.searchParams.set(`scope`,c);let m=crypto.randomUUID();p.searchParams.set(`state`,m),d&&f&&(p.searchParams.set(`code_challenge`,d),p.searchParams.set(`code_challenge_method`,f)),await Ie(p),x.debug(`Waiting for OAuth callback...`);let h=await n.waitForCallback();if(h.state!==m)throw Error(`OAuth state mismatch - possible CSRF attack`);x.info(`Authorization code received`,{codePrefix:`${h.code.substring(0,10)}...`,providerId:t.id}),x.debug(`Exchanging authorization code for tokens...`);let g=await Qe(t,h.code,o,u);if(x.info(`OAuth tokens obtained successfully`,{expiresIn:g.expires_in,hasRefreshToken:!!g.refresh_token,providerId:t.id,tokenType:g.token_type}),r){let e=new Date().toISOString();await r.update(a,{clientInfo:s,tokens:g,tokensObtainedAt:e}),x.info(`OAuth credentials persisted to disk`,{providerId:t.id,serverUrl:a})}return{clientInfo:s,fromCache:!1,tokens:g}}async function et(e,t){let n=await t.load(e.toString());if(!n){x.debug(`No stored credentials found`,{serverUrl:e.toString()});return}if(!n.clientInfo){x.debug(`Stored credentials have no client info`,{serverUrl:e.toString()});return}if(t.hasValidAccessToken(n)&&n.tokens)return x.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 tt(e,n,t);if(r)return r}x.debug(`Stored credentials are expired and no valid refresh token`,{serverUrl:e.toString()})}async function tt(e,t,n){if(!(!t.clientInfo||!t.tokens?.refresh_token)){x.info(`Attempting to refresh expired access token`,{clientId:t.clientInfo.client_id,serverUrl:e.toString()});try{let r;try{r=await g(e)}catch{}let i;i=r?.authorization_servers?.length?new URL(r.authorization_servers[0]):e;let a=await h(i),o=await C(i,{clientInformation:t.clientInfo,metadata:a,refreshToken:t.tokens.refresh_token});x.info(`Successfully refreshed access token`,{clientId:t.clientInfo.client_id,expiresIn:o.expires_in,hasNewRefreshToken:!!o.refresh_token});let s=new Date().toISOString();return await n.update(e.toString(),{tokens:o,tokensObtainedAt:s}),{clientInfo:t.clientInfo,fromCache:!0,tokens:o}}catch(t){x.warn(`Failed to refresh access token, will require re-authorization`,{error:String(t),serverUrl:e.toString()}),await n.update(e.toString(),{tokens:void 0,tokensObtainedAt:void 0});return}}}const nt=[`streamable-http`,`sse`];async function rt(e,t,n){let r;for(let i of nt){let a=e();try{let e=it(t,i,n);return await a.connect(e),{client:a,transportType:i}}catch(e){if(r=e,at(e)){x.info(`${i} not supported, trying next transport...`);continue}throw e}}throw r??Error(`No compatible transport found`)}function it(e,t,n){let r=new URL(e);return t===`streamable-http`?new l(r,{requestInit:n}):new o(r,{requestInit:n})}function at(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)))}var ot=class extends Se{buffer=``;droppedLinesLogged=0;constructor(){super({objectMode:!1})}_flush(e){this.buffer.trim().startsWith(`{`)?e(null,Buffer.from(this.buffer)):(this.buffer.trim().length>0&&this.logDroppedLine(this.buffer),e(null,null))}_transform(e,t,n){this.buffer+=e.toString();let r=this.buffer.split(`
136
- `);this.buffer=r.pop()||``;let i=[];for(let e of r)e.trim().startsWith(`{`)?i.push(e):e.trim().length>0&&this.logDroppedLine(e);if(i.length>0){let e=`${i.join(`
137
- `)}\n`;n(null,Buffer.from(e))}else n(null,null)}logDroppedLine(e){if(!(this.droppedLinesLogged>50)){if(this.droppedLinesLogged===50){this.droppedLinesLogged+=1,x.info(`Suppressing further non-JSON stdout lines`,{eventType:`mcp_server_stdout_dropped_suppressed`,loggedSoFar:50});return}this.droppedLinesLogged+=1,x.info(`MCP server stdout (non-JSON, dropped)`,{eventType:`mcp_server_stdout_dropped`,line:e.length>1024?`${e.slice(0,1024)}…`:e})}}},st=class{onclose;onerror;onmessage;get pid(){return this._process?.pid??null}get stderr(){return this._stderrStream?this._stderrStream:this._process?.stderr??null}_abortController=new AbortController;_process;_readBuffer=new a;_serverParams;_stderrStream=null;onEvent;constructor(e){this._serverParams=e,(e.stderr===`pipe`||e.stderr===`overlapped`)&&(this._stderrStream=new xe),this.onEvent=e.onEvent}async close(){if(this.onEvent?.({type:`close`}),this._process?.pid)try{if(process.platform===`win32`){let e=ye(`taskkill`,[`/PID`,String(this._process.pid),`/T`,`/F`],{stdio:`ignore`});if(e.error)throw e.error;if(e.status!==0)throw Error(`taskkill exited with status ${e.status}`)}else process.kill(-this._process.pid,`SIGTERM`)}catch(e){x.debug(`Process tree cleanup failed (process may already be dead)`,{error:e.message,pid:this._process.pid})}this._abortController.abort(),this._process=void 0,this._readBuffer.clear()}send(e){return new Promise(t=>{if(!this._process?.stdin)throw Error(`Not connected`);let n=T(e);this._process.stdin.write(n)?t():this._process.stdin.once(`drain`,t)})}async start(){if(this._process)throw Error(`StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.`);return new Promise((e,t)=>{this._process=ve(this._serverParams.command,this._serverParams.args??[],{cwd:this._serverParams.cwd,detached:process.platform!==`win32`,env:this._serverParams.env,shell:this._serverParams.shell??!1,signal:this._abortController.signal,stdio:[`pipe`,`pipe`,this._serverParams.stderr??`inherit`]}),this._process.on(`error`,e=>{if(e.name===`AbortError`){this.onclose?.();return}x.error(`Process spawn error`,{args:this._serverParams.args,command:this._serverParams.command,cwd:this._serverParams.cwd,error:e.message,errorCode:e.code,errorErrno:e.errno,errorSyscall:e.syscall,operation:`spawn`,shell:this._serverParams.shell,transportType:`stdio`}),t(e),this.onerror?.(e)});let n,r;this._process.on(`spawn`,()=>{n=Date.now(),r=this._process?.pid,x.info(`MCP server process spawned`,{argCount:this._serverParams.args?.length,command:this._serverParams.command,eventType:`mcp_server_process_spawned`,pid:r}),e()}),this._process.on(`close`,(e,t)=>{let i=n===void 0?void 0:Date.now()-n;x[e===0&&t===null?`info`:`warn`](`MCP server process closed`,{argCount:this._serverParams.args?.length,code:e,command:this._serverParams.command,eventType:`mcp_server_process_closed`,pid:r,signal:t,uptimeMs:i}),this.onEvent?.({type:`close`}),this._process=void 0,this.onclose?.()}),this._process.stdin?.on(`error`,e=>{x.error(`Stdin error`,{command:this._serverParams.command,error:e.message,operation:`write`,pid:this._process?.pid,transportType:`stdio`}),this.onEvent?.({error:e,type:`error`}),this.onerror?.(e)});let i=new ot;this._process.stdout?.pipe(i),i.on(`data`,e=>{this.onEvent?.({chunk:e.toString(),type:`data`}),this._readBuffer.append(e),this.processReadBuffer()}),i.on(`error`,e=>{x.error(`JSON filter error`,{command:this._serverParams.command,error:e.message,operation:`parse`,pid:this._process?.pid,transportType:`stdio`}),this.onEvent?.({error:e,type:`error`}),this.onerror?.(e)}),this._stderrStream&&this._process.stderr&&this._process.stderr.pipe(this._stderrStream)})}processReadBuffer(){for(;;)try{let e=this._readBuffer.readMessage();if(e===null)break;this.onEvent?.({message:e,type:`message`}),this.onmessage?.(e)}catch(e){let t=`unknown`;try{let e=this._readBuffer.readMessage();e&&(t=typeof e)}catch{}x.error(`Message processing error`,{command:this._serverParams.command,error:e.message,messageType:t,operation:`process`,pid:this._process?.pid,transportType:`stdio`}),this.onEvent?.({error:e,type:`error`}),this.onerror?.(e)}}};function ct(e){let t=e.toLowerCase().split(/[/\\]/).pop()||``;return t===`docker`||t===`docker.exe`?`docker`:t===`podman`||t===`podman.exe`?`podman`:t===`nerdctl`||t===`nerdctl.exe`?`nerdctl`:t===`finch`||t===`finch.exe`?`finch`:`unknown`}function lt(e){let t=e.indexOf(`run`);if(t===-1)return;let n=e.slice(t+1),r=new Set(`--add-host.--annotation.--arch.--attach.--blkio-weight.--blkio-weight-device.--cap-add.--cap-drop.--cgroup-parent.--cgroupns.--cidfile.--cosign-key.--cpu-period.--cpu-quota.--cpu-rt-period.--cpu-rt-runtime.--cpu-shares.--cpus.--cpuset-cpus.--cpuset-mems.--decryption-key.--device.--device-cgroup-rule.--device-read-bps.--device-read-iops.--device-write-bps.--device-write-iops.--dns.--dns-option.--dns-search.--domainname.--entrypoint.--env.--env-file.--env-host.--expose.--gidmap.--gpus.--group-add.--health-cmd.--health-interval.--health-retries.--health-start-period.--health-timeout.--hostname.--image-volume.--ip.--ip6.--ipc.--isolation.--kernel-memory.--label.--label-file.--link.--link-local-ip.--log-driver.--log-opt.--mac-address.--memory.--memory-reservation.--memory-swap.--memory-swappiness.--mount.--name.--network.--network-alias.--oom-kill-disable.--oom-score-adj.--os.--pid.--pidfile.--pids-limit.--platform.--pod.--privileged.--publish.--publish-all.--pull.--requires.--restart.--rootfs.--runtime.--secret.--security-opt.--shm-size.--stop-signal.--stop-timeout.--storage-opt.--subgidname.--subuidname.--sysctl.--systemd.--timeout.--tmpfs.--tz.--uidmap.--ulimit.--user.--userns.--uts.--variant.--volume.--volume-driver.--volumes-from.--workdir.-a.-c.-e.-h.-l.-m.-p.-P.-u.-v.-w`.split(`.`)),i=0;for(;i<n.length;){let e=n[i];if(e.startsWith(`-`)){if(e.includes(`=`)){i++;continue}if(r.has(e)){i+=2;continue}i++;continue}return e}}function ut(e,t){try{let n=_e(`${t===`unknown`?`docker`:t} image inspect --format "{{json .RepoDigests}}" ${JSON.stringify(e)}`,{encoding:`utf-8`,stdio:[`pipe`,`pipe`,`pipe`],timeout:5e3}).trim(),r;try{r=JSON.parse(n)}catch{return x.debug(`Could not parse RepoDigests JSON`,{imageName:e,result:n,runtime:t}),[]}let i=[];for(let e of r){let t=e.match(/sha256:[a-f0-9]{64}/i);t?i.push(t[0]):/^sha256:[a-f0-9]{64}$/i.test(e)&&i.push(e)}return i.length>0?(x.debug(`Container image digests retrieved`,{digests:i,imageName:e,runtime:t}),i):(x.debug(`No digests available for image (may be locally built)`,{imageName:e,runtime:t}),[])}catch(n){return x.debug(`Could not get container image digests`,{error:n instanceof Error?n.message:String(n),imageName:e,runtime:t}),[]}}function dt(e,t){let n=ct(e);if(n===`unknown`)return{imageDigests:[],imageName:``,isContainerMCP:!1,runtime:`unknown`};if(!t||!t.includes(`run`))return{imageDigests:[],imageName:``,isContainerMCP:!1,runtime:n};let r=lt(t);return r?{imageDigests:ut(r,n),imageName:r,isContainerMCP:!0,runtime:n}:(x.debug(`Container command detected but could not extract image name`,{args:t,command:e,runtime:n}),{imageDigests:[],imageName:``,isContainerMCP:!0,runtime:n})}he.inspect.defaultOptions.depth=8,`EventSource`in global||(global.EventSource=$);const ft=async()=>{ae(x);let t=await se(oe(process.argv)).scriptName(`mcp-gateway`).command(`stdio [command] [args...]`,`Run a local command with stdio transport (default)`,e=>e.positional(`command`,{describe:`The command to run`,type:`string`}).positional(`args`,{array:!0,describe:`The arguments to pass to the command`,type:`string`})).command(`remote`,`Connect to a remote MCP server`,e=>e.options({"bearer-token-env":{describe:`Environment variable name containing a bearer token for remote authentication`,type:`string`},"env-http-headers":{describe:`HTTP headers resolved from environment variables (format: HeaderName:ENV_VAR_NAME)`,string:!0,type:`array`},headers:{deprecated:`Use --env-http-headers with env var names instead. --headers exposes values in process listings.`,describe:`Legacy: headers with literal values (format: key:value)`,string:!0,type:`array`},"remote-transport":{choices:[`sse`,`stream`],deprecated:`Transport is now auto-detected per MCP spec. This option is ignored.`,describe:`(Deprecated) The transport type - now auto-detected`,type:`string`},url:{demandOption:!0,describe:`The URL of the remote MCP server`,type:`string`}})).command(`$0 [command] [args...]`,`Run a command with MCP arguments (backward compatible)`,e=>e.positional(`command`,{describe:`The command to run`,type:`string`}).positional(`args`,{array:!0,describe:`The arguments to pass to the command`,type:`string`})).env(`MCP_GATEWAY`).parserConfiguration({"populate--":!0,"unknown-options-as-args":!0}).options({"access-control-url":{describe:`URL of the access control service (overrides MCP_GATEWAY_ACCESS_CONTROL_URL)`,type:`string`},debug:{default:!1,describe:`Enable debug logging`,type:`boolean`},env:{describe:`Environment variables to pass to the command (format: KEY=value)`,string:!0,type:`array`},gracefulShutdownTimeout:{default:5e3,describe:`The timeout (in milliseconds) for graceful shutdown`,type:`number`},port:{describe:`Run as HTTP server on the specified port instead of stdio`,type:`number`},"scanner-api-key":{describe:`API key for scanner authentication (overrides MCP_GATEWAY_SCANNER_API_KEY)`,type:`string`},"scanner-fail-open":{describe:`Allow requests when scanner is unavailable (overrides MCP_GATEWAY_SCANNER_FAIL_OPEN)`,type:`boolean`},"scanner-timeout-ms":{describe:`Scanner request timeout in milliseconds (overrides MCP_GATEWAY_SCANNER_TIMEOUT_MS)`,type:`number`},"scanner-url":{describe:`URL of the scanner/evaluator service (overrides MCP_GATEWAY_SCANNER_URL)`,type:`string`},shell:{default:process.platform===`win32`,describe:`Spawn the server via the user's shell (defaults to true on Windows)`,type:`boolean`}}).help().parseAsync(),a,o,l,d,f,m={},h={};if(t.env)for(let e of t.env){let[t,...n]=e.split(`=`);t&&n.length>0&&(h[t.trim()]=n.join(`=`).trim())}let g=e=>{if(e.includes("${")){let t,n=e.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,(e,n)=>process.env[n]||(t=n,``));return t?{missingVar:t,resolved:``}:{resolved:n}}let t=process.env[e];return t?{resolved:t}:{missingVar:e,resolved:``}};if(t.headers)for(let e of t.headers){let[t,...n]=e.split(`:`);t&&n.length>0&&(m[t.trim()]=n.join(`:`).trim())}if(t.bearerTokenEnv){let e=t.bearerTokenEnv,{missingVar:n,resolved:r}=g(e);n&&(x.error(`Bearer token env var specified but not set`,{envVar:n,eventType:`mcp_auth_bearer_env_missing`}),process.exit(1)),m.Authorization=`Bearer ${r}`,x.info(`Bearer token loaded from environment variable`,{authType:`bearer_token`,envVar:e,eventType:`mcp_auth_bearer_env`,remoteUrl:t.url})}if(t.envHttpHeaders)for(let e of t.envHttpHeaders){let[n,...r]=e.split(`:`),i=r.join(`:`).trim();if(n&&i){let e=n.trim(),{missingVar:r,resolved:a}=g(i);r&&(x.error(`Env var for HTTP header not set`,{envVar:r,eventType:`mcp_auth_env_header_missing`,header:e}),process.exit(1)),m[e]=a,x.info(`HTTP header loaded from environment`,{eventType:`mcp_auth_env_header`,header:e,remoteUrl:t.url})}}let _=ee(m);if(_&&x.setTraceId(_),m.Authorization){let e=m.Authorization.startsWith(`Bearer `)?`bearer_token`:m.Authorization.startsWith(`Basic `)?`basic`:`api_key`;x.info(`Authorization header provided`,{authType:e,eventType:`mcp_auth_header_provided`,remoteUrl:t.url})}let C={...p.scanner.headers};if(x.debug(`Initializing traffic scanner`,{failOpen:p.scanner.failOpen,hasApiKey:!!p.scanner.apiKey,scannerEnabled:p.scanner.enabled,scannerUrl:p.scanner.url,timeoutMs:p.scanner.timeoutMs}),t._[0]===`remote`){a=`remote`,t[`remote-transport`]&&x.warn(`The --remote-transport option is deprecated and will be ignored. Transport is now auto-detected per MCP spec (Streamable HTTP with SSE fallback).`,{providedTransport:t[`remote-transport`]});let e=t.url;if(!e)throw new r(`Remote URL is required for remote proxy type`,`url`);d=S(e),d!==e&&x.debug(`Normalized remote URL`,{normalizedUrl:d,originalUrl:e})}else{if(a=`stdio`,t[`--`]&&t[`--`].length>0){let e=t[`--`];o=e[0],l=e.slice(1).map(String)}else o=t.command,l=(t.args||[]).map(String);if(!o)throw new r(`No command specified for stdio proxy`,`command`)}let w;a===`stdio`&&o&&(w=dt(o,l),w.isContainerMCP&&x.info(`Container MCP detected`,{imageDigests:w.imageDigests,imageName:w.imageName,runtime:w.runtime}));let T;if(p.accessControl.url&&p.scanner.apiKey){let e=await y(),t=a===`stdio`?{...e,proxyType:`stdio`,stdioCliArgs:{args:l,command:o,dockerImageDigests:w?.imageDigests,dockerImageName:w?.imageName,proxyType:`stdio`}}:{...e,proxyType:`remote`,remoteCliArgs:{proxyType:`remote`,url:d}};T=new Fe({apiKey:p.scanner.apiKey,headers:p.accessControl.headers,sessionData:t,timeoutMs:p.accessControl.timeoutMs,url:p.accessControl.url})}let re=a===`remote`?d:o,E=new Pe({client:T,enabled:p.accessControl.enabled,serverName:re}),D=new qe({storageDir:p.oauthDir}),O=()=>new n({name:`mcp-gateway`,version:p.appVersion},{capabilities:{}}),k=async n=>{if(!await E.isAllowed()){let t=E.getBlockReason();throw x.warn(`MCP server blocked by access control`,{proxyType:a,reason:t}),new e(t,`policy_block`)}if(x.info(`MCP server allowed by access control`),a===`stdio`){let e={...process.env,...h},r=new st({args:l,command:o,env:e,onEvent:e=>{t.debug&&x.debug(`Transport event`,{event:e})},shell:t.shell,stderr:`pipe`}),s=1024;s*8;let c=e=>e.length>s?`${e.slice(0,s)}…`:e,u=``,d=[],f=e=>{let t=c(e);d.push(t),d.length>100&&d.shift(),x.info(`MCP server stderr`,{command:o,eventType:`mcp_server_stderr`,line:t})},p=()=>{u.length!==0&&(f(u),u=``)},m=new pe(`utf8`),g=!1,_=()=>{if(g)return;g=!0;let e=m.end();e&&(u+=e),p()};r.stderr?.on(`data`,e=>{process.stderr.write(e),u+=m.write(e);let t=u.split(`
138
- `);u=t.pop()??``;for(let e of t){if(e.length===0)continue;f(e)}u.length>8192&&p()}),r.stderr?.once(`end`,_),r.stderr?.once(`close`,_);try{return await n.connect(r),n}catch(e){throw _(),x.warn(`Failed original server access`,{command:o,error:e instanceof Error?e.message:String(e),eventType:`mcp_server_access_failed`,proxyType:a,stderrTail:d.slice(-20)}),new i(`Failed to connect to stdio process: ${e}`,void 0,`stdio`,{argCount:l?.length,command:o})}}else if(a===`remote`){let e=d,t=e=>{let t={...m};return e&&(t.Authorization=`Bearer ${e}`),Object.keys(t).length>0?{headers:t}:void 0},n=e=>{if(e instanceof u)return!0;if(e&&typeof e==`object`){let t=e.status,n=e.code,r=e.message;if(t===401||n===401||r&&/unauthorized|401/i.test(String(r)))return!0}return!1},r=async()=>{let t=await Ze(e,{credentialStore:D});return x.info(`Authentication performed`,{authType:`oauth`,eventType:`mcp_auth_performed`,fromCache:t.fromCache,remoteUrl:e}),t.tokens.access_token},o;try{let i=await D.load(e),a=i&&D.hasValidAccessToken(i)?i.tokens?.access_token:void 0,s=!1;try{let n=await rt(O,e,t(a));o=n.client,f=n.transportType,x.info(`Connected to remote server`,{hasAuth:!!a,transport:f})}catch(i){if(n(i)){if(s)throw i;a?(x.info(`Cached OAuth token rejected (possibly revoked), invalidating and re-authorizing...`),await D.update(e,{tokens:void 0,tokensObtainedAt:void 0})):x.info(`Server requires OAuth, starting authentication flow...`),a=await r(),s=!0;let n=await rt(O,e,t(a));o=n.client,f=n.transportType,x.info(`Connected to remote server with OAuth`,{transport:f})}else throw i}return o}catch(t){throw x.warn(`Failed original server access`,{error:t instanceof Error?t.message:String(t),eventType:`mcp_server_access_failed`,proxyType:a,remoteUrl:e}),t instanceof i?t:new i(`Failed to connect to remote server: ${t instanceof Error?t.message:String(t)}`,e,f,{headerCount:Object.keys(m).length})}}return n},ce=async()=>{if(t.port){let e=a===`stdio`?{args:l,command:o,dockerImageDigests:w?.imageDigests,dockerImageName:w?.imageName,proxyType:a}:{proxyType:a,url:d};await b({cliArgs:e,scanApiKey:p.scanner.apiKey,scanEnabled:p.scanner.enabled,scanFailOpen:p.scanner.failOpen,scanHeaders:C,scanTimeoutMs:p.scanner.timeoutMs,scanUrl:p.scanner.url||``});let n,r=await ie({createServer:async()=>{let e=O();n=await k(e);let t=n.getServerVersion(),r=n.getServerCapabilities(),i=new s(t,{capabilities:r});return ne({authorizer:E,client:n,server:i,serverCapabilities:r}),i},onClose:async e=>{n&&await n.close(),await e.close()},port:t.port});return x.info(`MCP gateway started`,{eventType:`mcp_gateway_started`,mode:`http`,port:t.port,proxyType:a}),a===`stdio`?x.info(`MCP server configuration`,{args:l?.join(` `)||``,command:o,eventType:`mcp_server_config`,proxyType:a}):x.info(`MCP server configuration`,{eventType:`mcp_server_config`,proxyType:a,remoteUrl:d}),{close:()=>r.close()}}let e=O(),n=await k(e),r=n.getServerVersion(),i=n.getServerCapabilities(),u=new s(r,{capabilities:i});ne({authorizer:E,client:n,server:u,serverCapabilities:i});let m=new c,h=a===`stdio`?{args:l,command:o,dockerImageDigests:w?.imageDigests,dockerImageName:w?.imageName,proxyType:a}:{proxyType:a,url:d};return await b({cliArgs:h,scanApiKey:p.scanner.apiKey,scanEnabled:p.scanner.enabled,scanFailOpen:p.scanner.failOpen,scanHeaders:C,scanTimeoutMs:p.scanner.timeoutMs,scanUrl:p.scanner.url||``}),await u.connect(m),x.info(`MCP gateway started`,{eventType:`mcp_gateway_started`,mode:`stdio`,proxyType:a}),x.info(`MCP server configuration`,{capabilities:Object.keys(i||{}),eventType:`mcp_server_config`,proxyType:a,serverName:r?.name,serverVersion:r?.version,...a===`stdio`?{args:l?.join(` `)||``,command:o}:{remoteUrl:d,transport:f}}),{close:()=>n.close()}},le=({server:e,timeout:t})=>{let n=!1,r=async r=>{if(!n){n=!0,x.info(`MCP gateway stopping`,{eventType:`mcp_gateway_stopping`,signal:r});try{await e.close(),te(),await v(2e3),process.exit(0)}catch(e){let n=`Error during shutdown: ${e}`;x.error(`Error during shutdown`,{error:n,eventType:`mcp_gateway_stopped`,shutdownTimeout:t}),await v(2e3),process.exit(1)}}},i=e=>async()=>{me(()=>{x.error(`Graceful shutdown timeout exceeded, forcing exit`),process.exit(1)},t).unref(),await r(e)};return process.once(`SIGTERM`,i(`SIGTERM`)),process.once(`SIGINT`,i(`SIGINT`)),process.stdin.once(`end`,i(`stdin-end`)),()=>e.close()};x.info(`MCP gateway starting`,{command:o,eventType:`mcp_gateway_starting`,proxyType:a,transport:a===`remote`?`auto-detect`:`stdio`,url:d});try{let e=await ce();le({server:e,timeout:t.gracefulShutdownTimeout})}catch(t){t instanceof e&&(console.error(`
139
- ❌ ACCESS DENIED`),console.error(`━`.repeat(60)),console.error(`
140
- This 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.
141
- `),x.error(`MCP server blocked by access control policy`,{error:t.message,reason:t.reason}),await v(2e3),process.exit(1));let n=`Could not start the proxy: ${t}`;x.error(`MCP server failed to start`,{args:l,command:o,error:n,eventType:`mcp_server_start_failed`,proxyType:a,remoteUrl:d,transport:f}),await v(2e3),process.exit(1)}};export{ft as main};
142
- //# sourceMappingURL=main-Ct3dHxDT.js.map