emceepee 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/emceepee-http.js +2 -2
- package/dist/emceepee.js +2 -2
- package/package.json +1 -1
package/dist/emceepee-http.js
CHANGED
|
@@ -59,7 +59,7 @@ data:
|
|
|
59
59
|
`;break;case"id":H=O.includes("\x00")?void 0:O;break;case"retry":/^\d+$/.test(O)?W(parseInt(O,10)):Y(new QG(`Invalid \`retry\` value: "${O}"`,{type:"invalid-retry",value:O,line:j}));break;default:Y(new QG(`Unknown field "${D.length>20?`${D.slice(0,20)}…`:D}"`,{type:"unknown-field",field:D,value:O,line:j}));break}}function A(){B.length>0&&X({id:H,event:z||void 0,data:B.endsWith(`
|
|
60
60
|
`)?B.slice(0,-1):B}),H=void 0,B="",z=""}function L(D={}){J&&D.consume&&V(J),G=!0,H=void 0,B="",z="",J=""}return{feed:K,reset:L}}function nf(Q){let X=[],Y="",W=0;for(;W<Q.length;){let $=Q.indexOf("\r",W),J=Q.indexOf(`
|
|
61
61
|
`,W),G=-1;if($!==-1&&J!==-1?G=Math.min($,J):$!==-1?$===Q.length-1?G=-1:G=$:J!==-1&&(G=J),G===-1){Y=Q.slice(W);break}else{let H=Q.slice(W,G);X.push(H),W=G+1,Q[W-1]==="\r"&&Q[W]===`
|
|
62
|
-
`&&W++}}return[X,Y]}class XG extends TransformStream{constructor({onError:Q,onRetry:X,onComment:Y}={}){let W;super({start($){W=Pj({onEvent:(J)=>{$.enqueue(J)},onError(J){Q==="terminate"?$.error(J):typeof Q=="function"&&Q(J)},onRetry:X,onComment:Y})},transform($){W.feed($)}})}}var of={initialReconnectionDelay:1000,maxReconnectionDelay:30000,reconnectionDelayGrowFactor:1.5,maxRetries:2};class L9 extends Error{constructor(Q,X){super(`Streamable HTTP error: ${X}`);this.code=Q}}class gY{constructor(Q,X){this._hasCompletedAuthFlow=!1,this._url=Q,this._resourceMetadataUrl=void 0,this._scope=void 0,this._requestInit=X?.requestInit,this._authProvider=X?.authProvider,this._fetch=X?.fetch,this._fetchWithInit=Vj(X?.fetch,X?.requestInit),this._sessionId=X?.sessionId,this._reconnectionOptions=X?.reconnectionOptions??of}async _authThenStart(){if(!this._authProvider)throw new g6("No auth provider");let Q;try{Q=await t8(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})}catch(X){throw this.onerror?.(X),X}if(Q!=="AUTHORIZED")throw new g6;return await this._startOrAuthSse({resumptionToken:void 0})}async _commonHeaders(){let Q={};if(this._authProvider){let Y=await this._authProvider.tokens();if(Y)Q.Authorization=`Bearer ${Y.access_token}`}if(this._sessionId)Q["mcp-session-id"]=this._sessionId;if(this._protocolVersion)Q["mcp-protocol-version"]=this._protocolVersion;let X=ZY(this._requestInit?.headers);return new Headers({...Q,...X})}async _startOrAuthSse(Q){let{resumptionToken:X}=Q;try{let Y=await this._commonHeaders();if(Y.set("Accept","text/event-stream"),X)Y.set("last-event-id",X);let W=await(this._fetch??fetch)(this._url,{method:"GET",headers:Y,signal:this._abortController?.signal});if(!W.ok){if(await W.body?.cancel(),W.status===401&&this._authProvider)return await this._authThenStart();if(W.status===405)return;throw new L9(W.status,`Failed to open SSE stream: ${W.statusText}`)}this._handleSseStream(W.body,Q,!0)}catch(Y){throw this.onerror?.(Y),Y}}_getNextReconnectionDelay(Q){if(this._serverRetryMs!==void 0)return this._serverRetryMs;let X=this._reconnectionOptions.initialReconnectionDelay,Y=this._reconnectionOptions.reconnectionDelayGrowFactor,W=this._reconnectionOptions.maxReconnectionDelay;return Math.min(X*Math.pow(Y,Q),W)}_scheduleReconnection(Q,X=0){let Y=this._reconnectionOptions.maxRetries;if(X>=Y){this.onerror?.(Error(`Maximum reconnection attempts (${Y}) exceeded.`));return}let W=this._getNextReconnectionDelay(X);this._reconnectionTimeout=setTimeout(()=>{this._startOrAuthSse(Q).catch(($)=>{this.onerror?.(Error(`Failed to reconnect SSE stream: ${$ instanceof Error?$.message:String($)}`)),this._scheduleReconnection(Q,X+1)})},W)}_handleSseStream(Q,X,Y){if(!Q)return;let{onresumptiontoken:W,replayMessageId:$}=X,J,G=!1,H=!1;(async()=>{try{let z=Q.pipeThrough(new TextDecoderStream).pipeThrough(new XG({onRetry:(F)=>{this._serverRetryMs=F}})).getReader();while(!0){let{value:F,done:A}=await z.read();if(A)break;if(F.id)J=F.id,G=!0,W?.(F.id);if(!F.data)continue;if(!F.event||F.event==="message")try{let L=e6.parse(JSON.parse(F.data));if(P1(L)){if(H=!0,$!==void 0)L.id=$}this.onmessage?.(L)}catch(L){this.onerror?.(L)}}if((Y||G)&&!H&&this._abortController&&!this._abortController.signal.aborted)this._scheduleReconnection({resumptionToken:J,onresumptiontoken:W,replayMessageId:$},0)}catch(z){if(this.onerror?.(Error(`SSE stream disconnected: ${z}`)),(Y||G)&&!H&&this._abortController&&!this._abortController.signal.aborted)try{this._scheduleReconnection({resumptionToken:J,onresumptiontoken:W,replayMessageId:$},0)}catch(F){this.onerror?.(Error(`Failed to reconnect: ${F instanceof Error?F.message:String(F)}`))}}})()}async start(){if(this._abortController)throw Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");this._abortController=new AbortController}async finishAuth(Q){if(!this._authProvider)throw new g6("No auth provider");if(await t8(this._authProvider,{serverUrl:this._url,authorizationCode:Q,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})!=="AUTHORIZED")throw new g6("Failed to authorize")}async close(){if(this._reconnectionTimeout)clearTimeout(this._reconnectionTimeout),this._reconnectionTimeout=void 0;this._abortController?.abort(),this.onclose?.()}async send(Q,X){try{let{resumptionToken:Y,onresumptiontoken:W}=X||{};if(Y){this._startOrAuthSse({resumptionToken:Y,replayMessageId:p1(Q)?Q.id:void 0}).catch((V)=>this.onerror?.(V));return}let $=await this._commonHeaders();$.set("content-type","application/json"),$.set("accept","application/json, text/event-stream");let J={...this._requestInit,method:"POST",headers:$,body:JSON.stringify(Q),signal:this._abortController?.signal},G=await(this._fetch??fetch)(this._url,J),H=G.headers.get("mcp-session-id");if(H)this._sessionId=H;if(!G.ok){let V=await G.text().catch(()=>null);if(G.status===401&&this._authProvider){if(this._hasCompletedAuthFlow)throw new L9(401,"Server returned 401 after successful authentication");let{resourceMetadataUrl:F,scope:A}=aJ(G);if(this._resourceMetadataUrl=F,this._scope=A,await t8(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})!=="AUTHORIZED")throw new g6;return this._hasCompletedAuthFlow=!0,this.send(Q)}if(G.status===403&&this._authProvider){let{resourceMetadataUrl:F,scope:A,error:L}=aJ(G);if(L==="insufficient_scope"){let D=G.headers.get("WWW-Authenticate");if(this._lastUpscopingHeader===D)throw new L9(403,"Server returned 403 after trying upscoping");if(A)this._scope=A;if(F)this._resourceMetadataUrl=F;if(this._lastUpscopingHeader=D??void 0,await t8(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetch})!=="AUTHORIZED")throw new g6;return this.send(Q)}}throw new L9(G.status,`Error POSTing to endpoint: ${V}`)}if(this._hasCompletedAuthFlow=!1,this._lastUpscopingHeader=void 0,G.status===202){if(await G.body?.cancel(),P3(Q))this._startOrAuthSse({resumptionToken:void 0}).catch((V)=>this.onerror?.(V));return}let z=(Array.isArray(Q)?Q:[Q]).filter((V)=>("method"in V)&&("id"in V)&&V.id!==void 0).length>0,K=G.headers.get("content-type");if(z)if(K?.includes("text/event-stream"))this._handleSseStream(G.body,{onresumptiontoken:W},!1);else if(K?.includes("application/json")){let V=await G.json(),F=Array.isArray(V)?V.map((A)=>e6.parse(A)):[e6.parse(V)];for(let A of F)this.onmessage?.(A)}else throw await G.body?.cancel(),new L9(-1,`Unexpected content type: ${K}`);else await G.body?.cancel()}catch(Y){throw this.onerror?.(Y),Y}}get sessionId(){return this._sessionId}async terminateSession(){if(!this._sessionId)return;try{let Q=await this._commonHeaders(),X={...this._requestInit,method:"DELETE",headers:Q,signal:this._abortController?.signal},Y=await(this._fetch??fetch)(this._url,X);if(await Y.body?.cancel(),!Y.ok&&Y.status!==405)throw new L9(Y.status,`Failed to terminate session: ${Y.statusText}`);this._sessionId=void 0}catch(Q){throw this.onerror?.(Q),Q}}setProtocolVersion(Q){this._protocolVersion=Q}get protocolVersion(){return this._protocolVersion}async resumeStream(Q,X){await this._startOrAuthSse({resumptionToken:Q,onresumptiontoken:X?.onresumptiontoken})}}var rf=1000,tf=180000,af=2,sf=0.1,Zj=120000,ef=0.1,Qg=60000,Xg=3;class YG{name;url;onStatusChange;onNotification;onLog;onSamplingRequest;onElicitationRequest;onReconnecting;onReconnected;onHealthDegraded;onHealthRestored;client=null;transport=null;status="disconnected";errorMessage;capabilities;isClosing=!1;reconnectAttempt=0;reconnectTimeoutHandle=null;nextRetryMs=0;isReconnecting=!1;healthCheckIntervalHandle=null;consecutiveHealthFailures=0;healthStatus="healthy";constructor(Q){this.name=Q.name,this.url=Q.url,this.onStatusChange=Q.onStatusChange,this.onNotification=Q.onNotification,this.onLog=Q.onLog,this.onSamplingRequest=Q.onSamplingRequest,this.onElicitationRequest=Q.onElicitationRequest,this.onReconnecting=Q.onReconnecting,this.onReconnected=Q.onReconnected,this.onHealthDegraded=Q.onHealthDegraded,this.onHealthRestored=Q.onHealthRestored}getInfo(){let Q={name:this.name,url:this.url,status:this.status};if(this.errorMessage!==void 0)Q.error=this.errorMessage;if(this.capabilities!==void 0)Q.capabilities=this.capabilities;return Q}getName(){return this.name}getStatus(){return this.status}isConnected(){return this.status==="connected"}getReconnectionState(){if(this.status!=="reconnecting")return null;return{attempt:this.reconnectAttempt,nextRetryMs:this.nextRetryMs}}getHealthStatus(){return this.healthStatus}getConsecutiveHealthFailures(){return this.consecutiveHealthFailures}async connect(){if(this.status==="connected"||this.status==="connecting")return;this.setStatus("connecting");try{this.transport=new gY(new URL(this.url)),this.client=new PY({name:"emceepee",version:"0.1.0"},{capabilities:{sampling:{},elicitation:{form:{}}}}),this.setupNotificationHandler(),this.transport.onclose=()=>{if(!this.isClosing)this.handleUnexpectedDisconnect()},this.transport.onerror=(X)=>{if((X.name==="AbortError"||X.message.includes("AbortError"))&&(this.isClosing||this.status==="disconnected"))return;this.errorMessage=X.message,this.handleUnexpectedDisconnect()},await this.client.connect(this.transport);let Q=this.client.getServerCapabilities();this.capabilities={tools:Q?.tools!==void 0,resources:Q?.resources!==void 0,prompts:Q?.prompts!==void 0,resourceTemplates:Q?.resources!==void 0},this.setStatus("connected"),this.startHealthChecks()}catch(Q){let X=Q instanceof Error?Q.message:String(Q);throw this.setStatus("error",X),Q}}async disconnect(){if(this.isClosing=!0,this.stopHealthChecks(),this.cancelReconnection(),this.transport)try{await this.transport.close()}catch{}this.client=null,this.transport=null,this.isClosing=!1,this.setStatus("disconnected")}async forceReconnect(){if(this.stopHealthChecks(),this.cancelReconnection(),this.transport){this.isClosing=!0;try{await this.transport.close()}catch{}this.isClosing=!1}this.client=null,this.transport=null,this.reconnectAttempt=0,this.nextRetryMs=0,this.consecutiveHealthFailures=0,this.healthStatus="healthy",await this.connect()}cancelReconnection(){if(this.reconnectTimeoutHandle)clearTimeout(this.reconnectTimeoutHandle),this.reconnectTimeoutHandle=null;this.isReconnecting=!1}async listTools(){let Q=this.getConnectedClient();if(!this.capabilities?.tools)return[];return(await Q.listTools()).tools}async callTool(Q,X={}){return await this.getConnectedClient().callTool({name:Q,arguments:X})}async listResources(){let Q=this.getConnectedClient();if(!this.capabilities?.resources)return[];return(await Q.listResources()).resources}async listResourceTemplates(){let Q=this.getConnectedClient();if(!this.capabilities?.resourceTemplates)return[];return(await Q.listResourceTemplates()).resourceTemplates}async readResource(Q){return await this.getConnectedClient().readResource({uri:Q})}async listPrompts(){let Q=this.getConnectedClient();if(!this.capabilities?.prompts)return[];return(await Q.listPrompts()).prompts}async getPrompt(Q,X={}){return await this.getConnectedClient().getPrompt({name:Q,arguments:X})}setupNotificationHandler(){if(!this.client)return;this.client.setNotificationHandler(o4,()=>{this.emitNotification("notifications/tools/list_changed")}),this.client.setNotificationHandler(i4,()=>{this.emitNotification("notifications/resources/list_changed")}),this.client.setNotificationHandler(A5,(Q)=>{this.emitNotification("notifications/resources/updated",Q.params)}),this.client.setNotificationHandler(n4,()=>{this.emitNotification("notifications/prompts/list_changed")}),this.client.setNotificationHandler(M5,(Q)=>{if(this.onLog)this.onLog({server:this.name,timestamp:new Date,level:Q.params.level,logger:Q.params.logger,data:Q.params.data})}),this.client.setRequestHandler(t4,(Q)=>{return new Promise((X,Y)=>{if(this.onSamplingRequest)this.onSamplingRequest({id:crypto.randomUUID(),server:this.name,timestamp:new Date,params:Q.params,resolve:X,reject:Y});else Y(Error("Sampling not supported: no handler registered"))})}),this.client.setRequestHandler(s4,(Q)=>{return new Promise((X,Y)=>{if(this.onElicitationRequest)this.onElicitationRequest({id:crypto.randomUUID(),server:this.name,timestamp:new Date,params:Q.params,resolve:X,reject:Y});else Y(Error("Elicitation not supported: no handler registered"))})})}emitNotification(Q,X){if(this.onNotification!==void 0)this.onNotification({server:this.name,timestamp:new Date,method:Q,params:X})}handleUnexpectedDisconnect(){if(this.isReconnecting||this.isClosing)return;this.stopHealthChecks(),this.client=null,this.transport=null,this.isReconnecting=!0,this.reconnectAttempt=0,this.setStatus("reconnecting",this.errorMessage),this.scheduleReconnection()}scheduleReconnection(){if(this.isClosing)return;this.reconnectAttempt++;let Q=this.calculateBackoff(this.reconnectAttempt);if(this.nextRetryMs=Q,this.onReconnecting)this.onReconnecting(this.reconnectAttempt,Q);this.reconnectTimeoutHandle=setTimeout(()=>{this.attemptReconnection()},Q)}async attemptReconnection(){if(this.isClosing)return;try{this.transport=new gY(new URL(this.url)),this.client=new PY({name:"emceepee",version:"0.1.0"},{capabilities:{sampling:{},elicitation:{form:{}}}}),this.setupNotificationHandler(),this.transport.onclose=()=>{if(!this.isClosing)this.handleUnexpectedDisconnect()},this.transport.onerror=(Y)=>{if((Y.name==="AbortError"||Y.message.includes("AbortError"))&&(this.isClosing||this.status==="disconnected"))return;this.errorMessage=Y.message,this.handleUnexpectedDisconnect()},await this.client.connect(this.transport);let Q=this.client.getServerCapabilities();this.capabilities={tools:Q?.tools!==void 0,resources:Q?.resources!==void 0,prompts:Q?.prompts!==void 0,resourceTemplates:Q?.resources!==void 0};let X=this.reconnectAttempt;if(this.reconnectAttempt=0,this.nextRetryMs=0,this.isReconnecting=!1,this.consecutiveHealthFailures=0,this.healthStatus="healthy",this.setStatus("connected"),this.onReconnected)this.onReconnected(X);this.startHealthChecks()}catch(Q){this.client=null,this.transport=null,this.errorMessage=Q instanceof Error?Q.message:String(Q),this.scheduleReconnection()}}calculateBackoff(Q){let X=rf*Math.pow(af,Q-1),Y=Math.min(X,tf),W=Y*sf*(Math.random()*2-1);return Math.round(Y+W)}startHealthChecks(){this.stopHealthChecks();let Q=()=>{let X=Zj*ef*(Math.random()*2-1),Y=Math.round(Zj+X);this.healthCheckIntervalHandle=setTimeout(()=>{this.performHealthCheck(),Q()},Y)};Q()}stopHealthChecks(){if(this.healthCheckIntervalHandle)clearTimeout(this.healthCheckIntervalHandle),this.healthCheckIntervalHandle=null}async performHealthCheck(){if(!this.client||!this.isConnected())return;try{let Q=new AbortController,X=setTimeout(()=>{Q.abort()},Qg);try{if(await this.client.listTools(),clearTimeout(X),this.consecutiveHealthFailures>0){let Y=this.healthStatus==="degraded";if(this.consecutiveHealthFailures=0,this.healthStatus="healthy",Y&&this.onHealthRestored)this.onHealthRestored()}}catch(Y){throw clearTimeout(X),Y}}catch(Q){this.consecutiveHealthFailures++;let X=Q instanceof Error?Q.message:String(Q);if(this.consecutiveHealthFailures>=Xg&&this.healthStatus!=="degraded"){if(this.healthStatus="degraded",this.onHealthDegraded)this.onHealthDegraded(this.consecutiveHealthFailures,X)}}}setStatus(Q,X){if(this.status=Q,Q==="error"&&X!==void 0)this.errorMessage=X;else this.errorMessage=void 0;if(this.onStatusChange!==void 0)this.onStatusChange(Q,X)}getConnectedClient(){if(!this.isConnected()||!this.client)throw Error(`Client '${this.name}' is not connected`);return this.client}}var Yg={sessionTimeoutMs:1800000,cleanupIntervalMs:300000};class WG{sessions=new Map;serverConfigs;config;logger;cleanupIntervalHandle=null;constructor(Q={}){this.config={...Yg,...Q},this.logger=Q.logger;let X={logger:Q.logger};this.serverConfigs=new fJ(X),this.startCleanupInterval()}async createSession(){let Q=K1(),X=new mJ(Q,this.config.sessionStateConfig,this.logger);this.sessions.set(Q,X),this.logger?.info("session_created",{sessionId:Q});let Y=this.serverConfigs.listConfigs();for(let W of Y)try{await this.connectSessionToServer(X,W)}catch($){this.logger?.warn("session_auto_connect_failed",{sessionId:Q,server:W.name,error:$ instanceof Error?$.message:String($)})}return X}getSession(Q){return this.sessions.get(Q)}touchSession(Q){this.sessions.get(Q)?.touch()}async destroySession(Q){let X=this.sessions.get(Q);if(!X)return;this.logger?.info("session_destroying",{sessionId:Q}),await X.cleanup(),this.sessions.delete(Q),this.logger?.info("session_destroyed",{sessionId:Q})}async addServer(Q,X,Y){let W=this.sessions.get(Q);if(!W)throw Error(`Session '${Q}' not found`);let $=this.serverConfigs.addConfig(X,Y,Q),J=this.serverConfigs.getConfig(X);if(!J)throw Error(`Failed to add server config for '${X}'`);let G=await this.connectSessionToServer(W,J);if($){for(let[H,B]of this.sessions)if(H!==Q)B.eventSystem.addEvent("server_added",X,{name:X,url:Y,addedBy:Q})}return G}async removeServer(Q,X){if(this.logger?.debug("removeServer_start",{sessionId:Q,serverName:X}),!this.serverConfigs.removeConfig(X))throw this.logger?.debug("removeServer_not_found",{sessionId:Q,serverName:X}),Error(`Server '${X}' not found`);for(let[W,$]of this.sessions)this.logger?.debug("removeServer_disconnecting_session",{sessionId:W,serverName:X,hasConnection:$.getConnection(X)!==void 0}),await this.disconnectSessionFromServer($,X),$.eventSystem.addEvent("server_removed",X,{name:X,removedBy:Q});this.logger?.debug("removeServer_complete",{sessionId:Q,serverName:X})}listServers(Q){let X=this.sessions.get(Q);return this.serverConfigs.listConfigs().map((W)=>{let $=X?.getConnection(W.name),J={name:W.name,url:W.url,connected:$?.status==="connected",status:$?.status??"not_connected",connectedAt:$?.connectedAt,lastError:$?.lastError};if($?.status==="reconnecting"){let G=$.client.getReconnectionState();if(G)J.reconnectAttempt=G.attempt,J.nextRetryMs=G.nextRetryMs}if($?.status==="connected"){J.healthStatus=$.client.getHealthStatus();let G=$.client.getConsecutiveHealthFailures();if(G>0)J.consecutiveHealthFailures=G}return J})}async reconnectServer(Q,X){let Y=this.sessions.get(Q);if(!Y)throw Error(`Session '${Q}' not found`);let W=this.serverConfigs.getConfig(X);if(!W)throw Error(`Server '${X}' not found`);let $=Y.getConnection(X);if(!$){await this.connectSessionToServer(Y,W);return}this.handleBackendDisconnect(Y,X),await $.client.forceReconnect(),Y.setConnectionStatus(X,"connected"),Y.eventSystem.addEvent("server_reconnected",X,{name:X,attemptsTaken:0,forced:!0}),this.logger?.info("session_server_force_reconnected",{sessionId:Y.sessionId,server:X})}async getOrCreateConnection(Q,X){let Y=this.sessions.get(Q);if(!Y)throw Error(`Session '${Q}' not found`);let W=Y.getConnection(X);if(W?.status==="connected")return W.client;let $=this.serverConfigs.getConfig(X);if(!$)throw Error(`Server '${X}' not found`);return(await this.connectSessionToServer(Y,$)).client}getConnectedClient(Q,X){let Y=this.sessions.get(Q);if(!Y)return;let W=Y.getConnection(X);if(W?.status==="connected")return W.client;return}async connectSessionToServer(Q,X){let Y=Q.backendConnections.get(X.name);if(Y?.status==="connected")return Y;if(Y?.status==="connecting")throw Error(`Already connecting to '${X.name}'`);let W=new YG({name:X.name,url:X.url,onStatusChange:($,J)=>{Q.setConnectionStatus(X.name,$,J)},onNotification:($)=>{Q.bufferManager.addNotification($)},onLog:($)=>{Q.bufferManager.addLog($)},onSamplingRequest:($)=>{Q.pendingRequests.addSamplingRequest($.server,$.params,$.resolve,$.reject)},onElicitationRequest:($)=>{Q.pendingRequests.addElicitationRequest($.server,$.params,$.resolve,$.reject)},onReconnecting:($,J)=>{if($===1)this.handleBackendDisconnect(Q,X.name);Q.eventSystem.addEvent("server_reconnecting",X.name,{name:X.name,attempt:$,nextRetryMs:J}),this.logger?.debug("session_server_reconnecting",{sessionId:Q.sessionId,server:X.name,attempt:$,nextRetryMs:J})},onReconnected:($)=>{Q.setConnectionStatus(X.name,"connected"),Q.eventSystem.addEvent("server_reconnected",X.name,{name:X.name,attemptsTaken:$}),this.logger?.info("session_server_reconnected",{sessionId:Q.sessionId,server:X.name,attemptsTaken:$})},onHealthDegraded:($,J)=>{Q.eventSystem.addEvent("server_health_degraded",X.name,{name:X.name,consecutiveFailures:$,lastError:J}),this.logger?.warn("session_server_health_degraded",{sessionId:Q.sessionId,server:X.name,consecutiveFailures:$,lastError:J})},onHealthRestored:()=>{Q.eventSystem.addEvent("server_health_restored",X.name,{name:X.name}),this.logger?.info("session_server_health_restored",{sessionId:Q.sessionId,server:X.name})}});Q.addConnection(X.name,W);try{await W.connect(),Q.setConnectionStatus(X.name,"connected"),Q.eventSystem.addEvent("server_connected",X.name,{name:X.name,capabilities:W.getInfo().capabilities}),this.logger?.info("session_server_connected",{sessionId:Q.sessionId,server:X.name});let $=Q.getConnection(X.name);if(!$)throw Error("Connection not found after connect");return $}catch($){let J=$ instanceof Error?$.message:String($);throw Q.setConnectionStatus(X.name,"error",J),this.logger?.warn("session_server_connect_failed",{sessionId:Q.sessionId,server:X.name,error:J}),$}}async disconnectSessionFromServer(Q,X){let Y=Q.getConnection(X);if(!Y){this.logger?.debug("disconnectSessionFromServer_no_connection",{sessionId:Q.sessionId,serverName:X});return}this.logger?.debug("disconnectSessionFromServer_start",{sessionId:Q.sessionId,serverName:X,connectionStatus:Y.status});let W=Q.taskManager.getTasksForServer(X);for(let $ of W)if($.status==="working")Q.taskManager.failTask($.taskId,"Server removed");Q.pendingRequests.rejectRequestsForServer(X,"Server removed");try{this.logger?.debug("disconnectSessionFromServer_calling_disconnect",{sessionId:Q.sessionId,serverName:X}),await Y.client.disconnect(),this.logger?.debug("disconnectSessionFromServer_disconnect_complete",{sessionId:Q.sessionId,serverName:X})}catch($){this.logger?.debug("disconnectSessionFromServer_disconnect_error",{sessionId:Q.sessionId,serverName:X,error:$ instanceof Error?$.message:String($)})}Q.removeConnection(X),this.logger?.debug("disconnectSessionFromServer_complete",{sessionId:Q.sessionId,serverName:X})}handleBackendDisconnect(Q,X){let Y=Q.taskManager.getTasksForServer(X);for(let W of Y)if(W.status==="working")Q.taskManager.failTask(W.taskId,"Server disconnected");Q.pendingRequests.rejectRequestsForServer(X,"Server disconnected"),Q.eventSystem.addEvent("server_disconnected",X,{name:X}),this.logger?.info("session_server_disconnected",{sessionId:Q.sessionId,server:X})}startCleanupInterval(){this.cleanupIntervalHandle=setInterval(()=>{this.runSessionCleanup()},this.config.cleanupIntervalMs)}runSessionCleanup(){let Q=Date.now(),X=[];for(let[Y,W]of this.sessions)if(Q-W.lastActivityAt.getTime()>=this.config.sessionTimeoutMs)X.push(Y);for(let Y of X)this.logger?.info("session_timeout_cleanup",{sessionId:Y}),this.destroySession(Y)}async shutdown(){if(this.logger?.info("session_manager_shutdown_start",{sessionCount:this.sessions.size}),this.cleanupIntervalHandle)clearInterval(this.cleanupIntervalHandle),this.cleanupIntervalHandle=null;for(let Q of Array.from(this.sessions.keys()))await this.destroySession(Q);this.logger?.info("session_manager_shutdown_complete",{})}getServerConfigs(){return this.serverConfigs}listSessionIds(){return Array.from(this.sessions.keys())}get sessionCount(){return this.sessions.size}}var Wg={maxEvents:1000,retentionMs:300000};class $G{events=new Map;config;logger;constructor(Q={}){this.config={...Wg,...Q},this.logger=Q.logger}storeEvent(Q,X){let Y=K1();return this.events.set(Y,{streamId:Q,message:X,createdAt:Date.now()}),this.logger?.debug("sse_event_stored",{eventId:Y,streamId:Q,messageId:X.id}),this.enforceLimit(),this.cleanupOldEvents(),Promise.resolve(Y)}getStreamIdForEventId(Q){return Promise.resolve(this.events.get(Q)?.streamId)}async replayEventsAfter(Q,{send:X}){this.logger?.debug("sse_replay_requested",{lastEventId:Q,totalEvents:this.events.size});let Y=this.events.get(Q);if(!Y)return this.logger?.debug("sse_replay_event_not_found",{lastEventId:Q}),"";let W=Y.streamId,$=!1,J=0,G=[...this.events.keys()].sort();this.logger?.debug("sse_replay_scanning",{streamId:W,sortedIdsCount:G.length});for(let H of G){if(H===Q){$=!0;continue}if($){let B=this.events.get(H);if(B?.streamId===W)this.logger?.debug("sse_replaying_event",{eventId:H,streamId:W}),await X(H,B.message),J++;else this.logger?.debug("sse_skip_different_stream",{eventId:H,eventStreamId:B?.streamId,targetStreamId:W})}}return this.logger?.debug("sse_replay_complete",{streamId:W,replayedCount:J}),W}enforceLimit(){if(this.events.size<=this.config.maxEvents)return;let X=[...this.events.keys()].sort().slice(0,this.events.size-this.config.maxEvents);for(let Y of X)this.events.delete(Y)}cleanupOldEvents(){let Q=Date.now()-this.config.retentionMs;for(let[X,Y]of this.events)if(Y.createdAt<Q)this.events.delete(X)}getEventCount(){return this.events.size}clear(){this.events.clear()}}var Ej={debug:0,info:1,warn:2,error:3};function Rj(Q="info"){let X=Ej[Q],Y=(W,$,J)=>{if(Ej[W]<X)return;let G=new Date().toISOString(),H=J?` ${JSON.stringify(J)}`:"";switch(W){case"debug":console.debug(`[${G}] DEBUG ${$}${H}`);break;case"info":console.info(`[${G}] INFO ${$}${H}`);break;case"warn":console.warn(`[${G}] WARN ${$}${H}`);break;case"error":console.error(`[${G}] ERROR ${$}${H}`);break}};return{debug:(W,$)=>{Y("debug",W,$)},info:(W,$)=>{Y("info",W,$)},warn:(W,$)=>{Y("warn",W,$)},error:(W,$)=>{Y("error",W,$)}}}var $g={maxRequestsPerSession:100,maxTotalRequests:1000};class JG{requests=new Map;sessionRequests=new Map;config;logger;constructor(Q={}){this.config={...$g,...Q},this.logger=Q.logger}startRequest(Q,X,Y,W={}){let $=K1(),J={requestId:$,sessionId:Q,type:X,name:Y,server:W.server,args:W.args,startedAt:new Date,status:"pending"};this.requests.set($,J);let G=this.sessionRequests.get(Q)??[];return G.push($),this.sessionRequests.set(Q,G),this.logger?.debug("request_started",{requestId:$,sessionId:Q,type:X,name:Y,server:W.server}),this.enforceSessionLimit(Q),this.enforceTotalLimit(),$}completeRequest(Q,X){let Y=this.requests.get(Q);if(!Y)return;Y.status="completed",Y.endedAt=new Date,Y.durationMs=Y.endedAt.getTime()-Y.startedAt.getTime(),Y.resultSummary=X,this.logger?.debug("request_completed",{requestId:Q,sessionId:Y.sessionId,type:Y.type,name:Y.name,server:Y.server,durationMs:Y.durationMs})}failRequest(Q,X){let Y=this.requests.get(Q);if(!Y)return;Y.status="failed",Y.endedAt=new Date,Y.durationMs=Y.endedAt.getTime()-Y.startedAt.getTime(),Y.error=X,this.logger?.debug("request_failed",{requestId:Q,sessionId:Y.sessionId,type:Y.type,name:Y.name,server:Y.server,durationMs:Y.durationMs,error:X})}timeoutRequest(Q){let X=this.requests.get(Q);if(!X)return;X.status="timeout",X.endedAt=new Date,X.durationMs=X.endedAt.getTime()-X.startedAt.getTime(),this.logger?.debug("request_timeout",{requestId:Q,sessionId:X.sessionId,type:X.type,name:X.name,server:X.server,durationMs:X.durationMs})}getSessionRequests(Q){return(this.sessionRequests.get(Q)??[]).map((Y)=>this.requests.get(Y)).filter((Y)=>Y!==void 0)}getAllRequests(){return Array.from(this.requests.values())}getRequestsBySession(){let Q=new Map;for(let[X]of this.sessionRequests)Q.set(X,this.getSessionRequests(X));return Q}clearSession(Q){let X=this.sessionRequests.get(Q)??[];for(let Y of X)this.requests.delete(Y);this.sessionRequests.delete(Q)}getStats(){let Q={pending:0,completed:0,failed:0,timeout:0},X={};for(let Y of this.requests.values())Q[Y.status]++,X[Y.type]=(X[Y.type]??0)+1;return{totalRequests:this.requests.size,sessionCount:this.sessionRequests.size,byStatus:Q,byType:X}}enforceSessionLimit(Q){let X=this.sessionRequests.get(Q);if(!X||X.length<=this.config.maxRequestsPerSession)return;let Y=X.length-this.config.maxRequestsPerSession,W=X.splice(0,Y);for(let $ of W)this.requests.delete($)}enforceTotalLimit(){if(this.requests.size<=this.config.maxTotalRequests)return;let Q=Array.from(this.requests.values()).sort((Y,W)=>Y.startedAt.getTime()-W.startedAt.getTime()),X=this.requests.size-this.config.maxTotalRequests;for(let Y=0;Y<X;Y++){let W=Q[Y];if(W){this.requests.delete(W.requestId);let $=this.sessionRequests.get(W.sessionId);if($){let J=$.indexOf(W.requestId);if(J>=0)$.splice(J,1)}}}}}function Ij(Q){let X=Q.getRequestsBySession(),Y=Q.getStats(),W=1/0,$=0;for(let G of X.values())for(let H of G){W=Math.min(W,H.startedAt.getTime());let B=H.endedAt?.getTime()??Date.now();$=Math.max($,B)}if(W===1/0)W=Date.now()-60000,$=Date.now();let J=Math.max($-W,1000);return`<!DOCTYPE html>
|
|
62
|
+
`&&W++}}return[X,Y]}class XG extends TransformStream{constructor({onError:Q,onRetry:X,onComment:Y}={}){let W;super({start($){W=Pj({onEvent:(J)=>{$.enqueue(J)},onError(J){Q==="terminate"?$.error(J):typeof Q=="function"&&Q(J)},onRetry:X,onComment:Y})},transform($){W.feed($)}})}}var of={initialReconnectionDelay:1000,maxReconnectionDelay:30000,reconnectionDelayGrowFactor:1.5,maxRetries:2};class L9 extends Error{constructor(Q,X){super(`Streamable HTTP error: ${X}`);this.code=Q}}class gY{constructor(Q,X){this._hasCompletedAuthFlow=!1,this._url=Q,this._resourceMetadataUrl=void 0,this._scope=void 0,this._requestInit=X?.requestInit,this._authProvider=X?.authProvider,this._fetch=X?.fetch,this._fetchWithInit=Vj(X?.fetch,X?.requestInit),this._sessionId=X?.sessionId,this._reconnectionOptions=X?.reconnectionOptions??of}async _authThenStart(){if(!this._authProvider)throw new g6("No auth provider");let Q;try{Q=await t8(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})}catch(X){throw this.onerror?.(X),X}if(Q!=="AUTHORIZED")throw new g6;return await this._startOrAuthSse({resumptionToken:void 0})}async _commonHeaders(){let Q={};if(this._authProvider){let Y=await this._authProvider.tokens();if(Y)Q.Authorization=`Bearer ${Y.access_token}`}if(this._sessionId)Q["mcp-session-id"]=this._sessionId;if(this._protocolVersion)Q["mcp-protocol-version"]=this._protocolVersion;let X=ZY(this._requestInit?.headers);return new Headers({...Q,...X})}async _startOrAuthSse(Q){let{resumptionToken:X}=Q;try{let Y=await this._commonHeaders();if(Y.set("Accept","text/event-stream"),X)Y.set("last-event-id",X);let W=await(this._fetch??fetch)(this._url,{method:"GET",headers:Y,signal:this._abortController?.signal});if(!W.ok){if(await W.body?.cancel(),W.status===401&&this._authProvider)return await this._authThenStart();if(W.status===405)return;throw new L9(W.status,`Failed to open SSE stream: ${W.statusText}`)}this._handleSseStream(W.body,Q,!0)}catch(Y){throw this.onerror?.(Y),Y}}_getNextReconnectionDelay(Q){if(this._serverRetryMs!==void 0)return this._serverRetryMs;let X=this._reconnectionOptions.initialReconnectionDelay,Y=this._reconnectionOptions.reconnectionDelayGrowFactor,W=this._reconnectionOptions.maxReconnectionDelay;return Math.min(X*Math.pow(Y,Q),W)}_scheduleReconnection(Q,X=0){let Y=this._reconnectionOptions.maxRetries;if(X>=Y){this.onerror?.(Error(`Maximum reconnection attempts (${Y}) exceeded.`));return}let W=this._getNextReconnectionDelay(X);this._reconnectionTimeout=setTimeout(()=>{this._startOrAuthSse(Q).catch(($)=>{this.onerror?.(Error(`Failed to reconnect SSE stream: ${$ instanceof Error?$.message:String($)}`)),this._scheduleReconnection(Q,X+1)})},W)}_handleSseStream(Q,X,Y){if(!Q)return;let{onresumptiontoken:W,replayMessageId:$}=X,J,G=!1,H=!1;(async()=>{try{let z=Q.pipeThrough(new TextDecoderStream).pipeThrough(new XG({onRetry:(F)=>{this._serverRetryMs=F}})).getReader();while(!0){let{value:F,done:A}=await z.read();if(A)break;if(F.id)J=F.id,G=!0,W?.(F.id);if(!F.data)continue;if(!F.event||F.event==="message")try{let L=e6.parse(JSON.parse(F.data));if(P1(L)){if(H=!0,$!==void 0)L.id=$}this.onmessage?.(L)}catch(L){this.onerror?.(L)}}if((Y||G)&&!H&&this._abortController&&!this._abortController.signal.aborted)this._scheduleReconnection({resumptionToken:J,onresumptiontoken:W,replayMessageId:$},0)}catch(z){if(this.onerror?.(Error(`SSE stream disconnected: ${z}`)),(Y||G)&&!H&&this._abortController&&!this._abortController.signal.aborted)try{this._scheduleReconnection({resumptionToken:J,onresumptiontoken:W,replayMessageId:$},0)}catch(F){this.onerror?.(Error(`Failed to reconnect: ${F instanceof Error?F.message:String(F)}`))}}})()}async start(){if(this._abortController)throw Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");this._abortController=new AbortController}async finishAuth(Q){if(!this._authProvider)throw new g6("No auth provider");if(await t8(this._authProvider,{serverUrl:this._url,authorizationCode:Q,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})!=="AUTHORIZED")throw new g6("Failed to authorize")}async close(){if(this._reconnectionTimeout)clearTimeout(this._reconnectionTimeout),this._reconnectionTimeout=void 0;this._abortController?.abort(),this.onclose?.()}async send(Q,X){try{let{resumptionToken:Y,onresumptiontoken:W}=X||{};if(Y){this._startOrAuthSse({resumptionToken:Y,replayMessageId:p1(Q)?Q.id:void 0}).catch((V)=>this.onerror?.(V));return}let $=await this._commonHeaders();$.set("content-type","application/json"),$.set("accept","application/json, text/event-stream");let J={...this._requestInit,method:"POST",headers:$,body:JSON.stringify(Q),signal:this._abortController?.signal},G=await(this._fetch??fetch)(this._url,J),H=G.headers.get("mcp-session-id");if(H)this._sessionId=H;if(!G.ok){let V=await G.text().catch(()=>null);if(G.status===401&&this._authProvider){if(this._hasCompletedAuthFlow)throw new L9(401,"Server returned 401 after successful authentication");let{resourceMetadataUrl:F,scope:A}=aJ(G);if(this._resourceMetadataUrl=F,this._scope=A,await t8(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})!=="AUTHORIZED")throw new g6;return this._hasCompletedAuthFlow=!0,this.send(Q)}if(G.status===403&&this._authProvider){let{resourceMetadataUrl:F,scope:A,error:L}=aJ(G);if(L==="insufficient_scope"){let D=G.headers.get("WWW-Authenticate");if(this._lastUpscopingHeader===D)throw new L9(403,"Server returned 403 after trying upscoping");if(A)this._scope=A;if(F)this._resourceMetadataUrl=F;if(this._lastUpscopingHeader=D??void 0,await t8(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetch})!=="AUTHORIZED")throw new g6;return this.send(Q)}}throw new L9(G.status,`Error POSTing to endpoint: ${V}`)}if(this._hasCompletedAuthFlow=!1,this._lastUpscopingHeader=void 0,G.status===202){if(await G.body?.cancel(),P3(Q))this._startOrAuthSse({resumptionToken:void 0}).catch((V)=>this.onerror?.(V));return}let z=(Array.isArray(Q)?Q:[Q]).filter((V)=>("method"in V)&&("id"in V)&&V.id!==void 0).length>0,K=G.headers.get("content-type");if(z)if(K?.includes("text/event-stream"))this._handleSseStream(G.body,{onresumptiontoken:W},!1);else if(K?.includes("application/json")){let V=await G.json(),F=Array.isArray(V)?V.map((A)=>e6.parse(A)):[e6.parse(V)];for(let A of F)this.onmessage?.(A)}else throw await G.body?.cancel(),new L9(-1,`Unexpected content type: ${K}`);else await G.body?.cancel()}catch(Y){throw this.onerror?.(Y),Y}}get sessionId(){return this._sessionId}async terminateSession(){if(!this._sessionId)return;try{let Q=await this._commonHeaders(),X={...this._requestInit,method:"DELETE",headers:Q,signal:this._abortController?.signal},Y=await(this._fetch??fetch)(this._url,X);if(await Y.body?.cancel(),!Y.ok&&Y.status!==405)throw new L9(Y.status,`Failed to terminate session: ${Y.statusText}`);this._sessionId=void 0}catch(Q){throw this.onerror?.(Q),Q}}setProtocolVersion(Q){this._protocolVersion=Q}get protocolVersion(){return this._protocolVersion}async resumeStream(Q,X){await this._startOrAuthSse({resumptionToken:Q,onresumptiontoken:X?.onresumptiontoken})}}var rf=1000,tf=180000,af=2,sf=0.1,Zj=120000,ef=0.1,Qg=60000,Xg=3;class YG{name;url;onStatusChange;onNotification;onLog;onSamplingRequest;onElicitationRequest;onReconnecting;onReconnected;onHealthDegraded;onHealthRestored;client=null;transport=null;status="disconnected";errorMessage;capabilities;isClosing=!1;reconnectAttempt=0;reconnectTimeoutHandle=null;nextRetryMs=0;isReconnecting=!1;healthCheckIntervalHandle=null;consecutiveHealthFailures=0;healthStatus="healthy";constructor(Q){this.name=Q.name,this.url=Q.url,this.onStatusChange=Q.onStatusChange,this.onNotification=Q.onNotification,this.onLog=Q.onLog,this.onSamplingRequest=Q.onSamplingRequest,this.onElicitationRequest=Q.onElicitationRequest,this.onReconnecting=Q.onReconnecting,this.onReconnected=Q.onReconnected,this.onHealthDegraded=Q.onHealthDegraded,this.onHealthRestored=Q.onHealthRestored}getInfo(){let Q={name:this.name,url:this.url,status:this.status};if(this.errorMessage!==void 0)Q.error=this.errorMessage;if(this.capabilities!==void 0)Q.capabilities=this.capabilities;return Q}getName(){return this.name}getStatus(){return this.status}isConnected(){return this.status==="connected"}getReconnectionState(){if(this.status!=="reconnecting")return null;return{attempt:this.reconnectAttempt,nextRetryMs:this.nextRetryMs}}getHealthStatus(){return this.healthStatus}getConsecutiveHealthFailures(){return this.consecutiveHealthFailures}async connect(){if(this.status==="connected"||this.status==="connecting")return;this.setStatus("connecting");try{this.transport=new gY(new URL(this.url)),this.client=new PY({name:"emceepee",version:"0.1.0"},{capabilities:{sampling:{},elicitation:{form:{}}}}),this.setupNotificationHandler(),this.transport.onclose=()=>{if(!this.isClosing)this.handleUnexpectedDisconnect()},this.transport.onerror=(X)=>{if((X.name==="AbortError"||X.message.includes("AbortError"))&&(this.isClosing||this.status==="disconnected"))return;this.errorMessage=X.message,this.handleUnexpectedDisconnect()},await this.client.connect(this.transport);let Q=this.client.getServerCapabilities();this.capabilities={tools:Q?.tools!==void 0,resources:Q?.resources!==void 0,prompts:Q?.prompts!==void 0,resourceTemplates:Q?.resources!==void 0},this.setStatus("connected"),this.startHealthChecks()}catch(Q){let X=Q instanceof Error?Q.message:String(Q);throw this.setStatus("error",X),Q}}async disconnect(){if(this.isClosing=!0,this.stopHealthChecks(),this.cancelReconnection(),this.transport)try{await this.transport.close()}catch{}this.client=null,this.transport=null,this.isClosing=!1,this.setStatus("disconnected")}async forceReconnect(){if(this.stopHealthChecks(),this.cancelReconnection(),this.transport){this.isClosing=!0;try{await this.transport.close()}catch{}this.isClosing=!1}this.client=null,this.transport=null,this.reconnectAttempt=0,this.nextRetryMs=0,this.consecutiveHealthFailures=0,this.healthStatus="healthy",await this.connect()}cancelReconnection(){if(this.reconnectTimeoutHandle)clearTimeout(this.reconnectTimeoutHandle),this.reconnectTimeoutHandle=null;this.isReconnecting=!1}async listTools(){let Q=this.getConnectedClient();if(!this.capabilities?.tools)return[];return(await Q.listTools()).tools}async callTool(Q,X={}){return await this.getConnectedClient().callTool({name:Q,arguments:X})}async listResources(){let Q=this.getConnectedClient();if(!this.capabilities?.resources)return[];return(await Q.listResources()).resources}async listResourceTemplates(){let Q=this.getConnectedClient();if(!this.capabilities?.resourceTemplates)return[];return(await Q.listResourceTemplates()).resourceTemplates}async readResource(Q){return await this.getConnectedClient().readResource({uri:Q})}async listPrompts(){let Q=this.getConnectedClient();if(!this.capabilities?.prompts)return[];return(await Q.listPrompts()).prompts}async getPrompt(Q,X={}){return await this.getConnectedClient().getPrompt({name:Q,arguments:X})}setupNotificationHandler(){if(!this.client)return;this.client.setNotificationHandler(o4,()=>{this.emitNotification("notifications/tools/list_changed")}),this.client.setNotificationHandler(i4,()=>{this.emitNotification("notifications/resources/list_changed")}),this.client.setNotificationHandler(A5,(Q)=>{this.emitNotification("notifications/resources/updated",Q.params)}),this.client.setNotificationHandler(n4,()=>{this.emitNotification("notifications/prompts/list_changed")}),this.client.setNotificationHandler(M5,(Q)=>{if(this.onLog)this.onLog({server:this.name,timestamp:new Date,level:Q.params.level,logger:Q.params.logger,data:Q.params.data})}),this.client.setRequestHandler(t4,(Q)=>{return new Promise((X,Y)=>{if(this.onSamplingRequest)this.onSamplingRequest({id:crypto.randomUUID(),server:this.name,timestamp:new Date,params:Q.params,resolve:X,reject:Y});else Y(Error("Sampling not supported: no handler registered"))})}),this.client.setRequestHandler(s4,(Q)=>{return new Promise((X,Y)=>{if(this.onElicitationRequest)this.onElicitationRequest({id:crypto.randomUUID(),server:this.name,timestamp:new Date,params:Q.params,resolve:X,reject:Y});else Y(Error("Elicitation not supported: no handler registered"))})})}emitNotification(Q,X){if(this.onNotification!==void 0)this.onNotification({server:this.name,timestamp:new Date,method:Q,params:X})}handleUnexpectedDisconnect(){if(this.isReconnecting||this.isClosing)return;this.stopHealthChecks(),this.client=null,this.transport=null,this.isReconnecting=!0,this.reconnectAttempt=0,this.setStatus("reconnecting",this.errorMessage),this.scheduleReconnection()}scheduleReconnection(){if(this.isClosing)return;this.reconnectAttempt++;let Q=this.calculateBackoff(this.reconnectAttempt);if(this.nextRetryMs=Q,this.onReconnecting)this.onReconnecting(this.reconnectAttempt,Q);this.reconnectTimeoutHandle=setTimeout(()=>{this.attemptReconnection()},Q)}async attemptReconnection(){if(this.isClosing)return;try{this.transport=new gY(new URL(this.url)),this.client=new PY({name:"emceepee",version:"0.1.0"},{capabilities:{sampling:{},elicitation:{form:{}}}}),this.setupNotificationHandler(),this.transport.onclose=()=>{if(!this.isClosing)this.handleUnexpectedDisconnect()},this.transport.onerror=(Y)=>{if((Y.name==="AbortError"||Y.message.includes("AbortError"))&&(this.isClosing||this.status==="disconnected"))return;this.errorMessage=Y.message,this.handleUnexpectedDisconnect()},await this.client.connect(this.transport);let Q=this.client.getServerCapabilities();this.capabilities={tools:Q?.tools!==void 0,resources:Q?.resources!==void 0,prompts:Q?.prompts!==void 0,resourceTemplates:Q?.resources!==void 0};let X=this.reconnectAttempt;if(this.reconnectAttempt=0,this.nextRetryMs=0,this.isReconnecting=!1,this.consecutiveHealthFailures=0,this.healthStatus="healthy",this.setStatus("connected"),this.onReconnected)this.onReconnected(X);this.startHealthChecks()}catch(Q){this.client=null,this.transport=null,this.errorMessage=Q instanceof Error?Q.message:String(Q),this.scheduleReconnection()}}calculateBackoff(Q){let X=rf*Math.pow(af,Q-1),Y=Math.min(X,tf),W=Y*sf*(Math.random()*2-1);return Math.round(Y+W)}startHealthChecks(){this.stopHealthChecks();let Q=()=>{let X=Zj*ef*(Math.random()*2-1),Y=Math.round(Zj+X);this.healthCheckIntervalHandle=setTimeout(()=>{this.performHealthCheck(),Q()},Y)};Q()}stopHealthChecks(){if(this.healthCheckIntervalHandle)clearTimeout(this.healthCheckIntervalHandle),this.healthCheckIntervalHandle=null}async performHealthCheck(){if(!this.client||!this.isConnected())return;try{let Q=new AbortController,X=setTimeout(()=>{Q.abort()},Qg);try{if(await this.client.listTools(),clearTimeout(X),this.consecutiveHealthFailures>0){let Y=this.healthStatus==="degraded";if(this.consecutiveHealthFailures=0,this.healthStatus="healthy",Y&&this.onHealthRestored)this.onHealthRestored()}}catch(Y){throw clearTimeout(X),Y}}catch(Q){this.consecutiveHealthFailures++;let X=Q instanceof Error?Q.message:String(Q);if(this.consecutiveHealthFailures>=Xg&&this.healthStatus!=="degraded"){if(this.healthStatus="degraded",this.onHealthDegraded)this.onHealthDegraded(this.consecutiveHealthFailures,X)}}}setStatus(Q,X){if(this.status=Q,Q==="error"&&X!==void 0)this.errorMessage=X;else this.errorMessage=void 0;if(this.onStatusChange!==void 0)this.onStatusChange(Q,X)}getConnectedClient(){if(!this.isConnected()||!this.client)throw Error(`Client '${this.name}' is not connected`);return this.client}}var Yg={sessionTimeoutMs:86400000,cleanupIntervalMs:300000};class WG{sessions=new Map;serverConfigs;config;logger;cleanupIntervalHandle=null;constructor(Q={}){this.config={...Yg,...Q},this.logger=Q.logger;let X={logger:Q.logger};this.serverConfigs=new fJ(X),this.startCleanupInterval()}async createSession(){let Q=K1(),X=new mJ(Q,this.config.sessionStateConfig,this.logger);this.sessions.set(Q,X),this.logger?.info("session_created",{sessionId:Q});let Y=this.serverConfigs.listConfigs();for(let W of Y)try{await this.connectSessionToServer(X,W)}catch($){this.logger?.warn("session_auto_connect_failed",{sessionId:Q,server:W.name,error:$ instanceof Error?$.message:String($)})}return X}getSession(Q){return this.sessions.get(Q)}touchSession(Q){this.sessions.get(Q)?.touch()}async destroySession(Q){let X=this.sessions.get(Q);if(!X)return;this.logger?.info("session_destroying",{sessionId:Q}),await X.cleanup(),this.sessions.delete(Q),this.logger?.info("session_destroyed",{sessionId:Q})}async addServer(Q,X,Y){let W=this.sessions.get(Q);if(!W)throw Error(`Session '${Q}' not found`);let $=this.serverConfigs.addConfig(X,Y,Q),J=this.serverConfigs.getConfig(X);if(!J)throw Error(`Failed to add server config for '${X}'`);let G=await this.connectSessionToServer(W,J);if($){for(let[H,B]of this.sessions)if(H!==Q)B.eventSystem.addEvent("server_added",X,{name:X,url:Y,addedBy:Q})}return G}async removeServer(Q,X){if(this.logger?.debug("removeServer_start",{sessionId:Q,serverName:X}),!this.serverConfigs.removeConfig(X))throw this.logger?.debug("removeServer_not_found",{sessionId:Q,serverName:X}),Error(`Server '${X}' not found`);for(let[W,$]of this.sessions)this.logger?.debug("removeServer_disconnecting_session",{sessionId:W,serverName:X,hasConnection:$.getConnection(X)!==void 0}),await this.disconnectSessionFromServer($,X),$.eventSystem.addEvent("server_removed",X,{name:X,removedBy:Q});this.logger?.debug("removeServer_complete",{sessionId:Q,serverName:X})}listServers(Q){let X=this.sessions.get(Q);return this.serverConfigs.listConfigs().map((W)=>{let $=X?.getConnection(W.name),J={name:W.name,url:W.url,connected:$?.status==="connected",status:$?.status??"not_connected",connectedAt:$?.connectedAt,lastError:$?.lastError};if($?.status==="reconnecting"){let G=$.client.getReconnectionState();if(G)J.reconnectAttempt=G.attempt,J.nextRetryMs=G.nextRetryMs}if($?.status==="connected"){J.healthStatus=$.client.getHealthStatus();let G=$.client.getConsecutiveHealthFailures();if(G>0)J.consecutiveHealthFailures=G}return J})}async reconnectServer(Q,X){let Y=this.sessions.get(Q);if(!Y)throw Error(`Session '${Q}' not found`);let W=this.serverConfigs.getConfig(X);if(!W)throw Error(`Server '${X}' not found`);let $=Y.getConnection(X);if(!$){await this.connectSessionToServer(Y,W);return}this.handleBackendDisconnect(Y,X),await $.client.forceReconnect(),Y.setConnectionStatus(X,"connected"),Y.eventSystem.addEvent("server_reconnected",X,{name:X,attemptsTaken:0,forced:!0}),this.logger?.info("session_server_force_reconnected",{sessionId:Y.sessionId,server:X})}async getOrCreateConnection(Q,X){let Y=this.sessions.get(Q);if(!Y)throw Error(`Session '${Q}' not found`);let W=Y.getConnection(X);if(W?.status==="connected")return W.client;let $=this.serverConfigs.getConfig(X);if(!$)throw Error(`Server '${X}' not found`);return(await this.connectSessionToServer(Y,$)).client}getConnectedClient(Q,X){let Y=this.sessions.get(Q);if(!Y)return;let W=Y.getConnection(X);if(W?.status==="connected")return W.client;return}async connectSessionToServer(Q,X){let Y=Q.backendConnections.get(X.name);if(Y?.status==="connected")return Y;if(Y?.status==="connecting")throw Error(`Already connecting to '${X.name}'`);let W=new YG({name:X.name,url:X.url,onStatusChange:($,J)=>{Q.setConnectionStatus(X.name,$,J)},onNotification:($)=>{Q.bufferManager.addNotification($)},onLog:($)=>{Q.bufferManager.addLog($)},onSamplingRequest:($)=>{Q.pendingRequests.addSamplingRequest($.server,$.params,$.resolve,$.reject)},onElicitationRequest:($)=>{Q.pendingRequests.addElicitationRequest($.server,$.params,$.resolve,$.reject)},onReconnecting:($,J)=>{if($===1)this.handleBackendDisconnect(Q,X.name);Q.eventSystem.addEvent("server_reconnecting",X.name,{name:X.name,attempt:$,nextRetryMs:J}),this.logger?.debug("session_server_reconnecting",{sessionId:Q.sessionId,server:X.name,attempt:$,nextRetryMs:J})},onReconnected:($)=>{Q.setConnectionStatus(X.name,"connected"),Q.eventSystem.addEvent("server_reconnected",X.name,{name:X.name,attemptsTaken:$}),this.logger?.info("session_server_reconnected",{sessionId:Q.sessionId,server:X.name,attemptsTaken:$})},onHealthDegraded:($,J)=>{Q.eventSystem.addEvent("server_health_degraded",X.name,{name:X.name,consecutiveFailures:$,lastError:J}),this.logger?.warn("session_server_health_degraded",{sessionId:Q.sessionId,server:X.name,consecutiveFailures:$,lastError:J})},onHealthRestored:()=>{Q.eventSystem.addEvent("server_health_restored",X.name,{name:X.name}),this.logger?.info("session_server_health_restored",{sessionId:Q.sessionId,server:X.name})}});Q.addConnection(X.name,W);try{await W.connect(),Q.setConnectionStatus(X.name,"connected"),Q.eventSystem.addEvent("server_connected",X.name,{name:X.name,capabilities:W.getInfo().capabilities}),this.logger?.info("session_server_connected",{sessionId:Q.sessionId,server:X.name});let $=Q.getConnection(X.name);if(!$)throw Error("Connection not found after connect");return $}catch($){let J=$ instanceof Error?$.message:String($);throw Q.setConnectionStatus(X.name,"error",J),this.logger?.warn("session_server_connect_failed",{sessionId:Q.sessionId,server:X.name,error:J}),$}}async disconnectSessionFromServer(Q,X){let Y=Q.getConnection(X);if(!Y){this.logger?.debug("disconnectSessionFromServer_no_connection",{sessionId:Q.sessionId,serverName:X});return}this.logger?.debug("disconnectSessionFromServer_start",{sessionId:Q.sessionId,serverName:X,connectionStatus:Y.status});let W=Q.taskManager.getTasksForServer(X);for(let $ of W)if($.status==="working")Q.taskManager.failTask($.taskId,"Server removed");Q.pendingRequests.rejectRequestsForServer(X,"Server removed");try{this.logger?.debug("disconnectSessionFromServer_calling_disconnect",{sessionId:Q.sessionId,serverName:X}),await Y.client.disconnect(),this.logger?.debug("disconnectSessionFromServer_disconnect_complete",{sessionId:Q.sessionId,serverName:X})}catch($){this.logger?.debug("disconnectSessionFromServer_disconnect_error",{sessionId:Q.sessionId,serverName:X,error:$ instanceof Error?$.message:String($)})}Q.removeConnection(X),this.logger?.debug("disconnectSessionFromServer_complete",{sessionId:Q.sessionId,serverName:X})}handleBackendDisconnect(Q,X){let Y=Q.taskManager.getTasksForServer(X);for(let W of Y)if(W.status==="working")Q.taskManager.failTask(W.taskId,"Server disconnected");Q.pendingRequests.rejectRequestsForServer(X,"Server disconnected"),Q.eventSystem.addEvent("server_disconnected",X,{name:X}),this.logger?.info("session_server_disconnected",{sessionId:Q.sessionId,server:X})}startCleanupInterval(){this.cleanupIntervalHandle=setInterval(()=>{this.runSessionCleanup()},this.config.cleanupIntervalMs)}runSessionCleanup(){let Q=Date.now(),X=[];for(let[Y,W]of this.sessions)if(Q-W.lastActivityAt.getTime()>=this.config.sessionTimeoutMs)X.push(Y);for(let Y of X)this.logger?.info("session_timeout_cleanup",{sessionId:Y}),this.destroySession(Y)}async shutdown(){if(this.logger?.info("session_manager_shutdown_start",{sessionCount:this.sessions.size}),this.cleanupIntervalHandle)clearInterval(this.cleanupIntervalHandle),this.cleanupIntervalHandle=null;for(let Q of Array.from(this.sessions.keys()))await this.destroySession(Q);this.logger?.info("session_manager_shutdown_complete",{})}getServerConfigs(){return this.serverConfigs}listSessionIds(){return Array.from(this.sessions.keys())}get sessionCount(){return this.sessions.size}}var Wg={maxEvents:1000,retentionMs:300000};class $G{events=new Map;config;logger;constructor(Q={}){this.config={...Wg,...Q},this.logger=Q.logger}storeEvent(Q,X){let Y=K1();return this.events.set(Y,{streamId:Q,message:X,createdAt:Date.now()}),this.logger?.debug("sse_event_stored",{eventId:Y,streamId:Q,messageId:X.id}),this.enforceLimit(),this.cleanupOldEvents(),Promise.resolve(Y)}getStreamIdForEventId(Q){return Promise.resolve(this.events.get(Q)?.streamId)}async replayEventsAfter(Q,{send:X}){this.logger?.debug("sse_replay_requested",{lastEventId:Q,totalEvents:this.events.size});let Y=this.events.get(Q);if(!Y)return this.logger?.debug("sse_replay_event_not_found",{lastEventId:Q}),"";let W=Y.streamId,$=!1,J=0,G=[...this.events.keys()].sort();this.logger?.debug("sse_replay_scanning",{streamId:W,sortedIdsCount:G.length});for(let H of G){if(H===Q){$=!0;continue}if($){let B=this.events.get(H);if(B?.streamId===W)this.logger?.debug("sse_replaying_event",{eventId:H,streamId:W}),await X(H,B.message),J++;else this.logger?.debug("sse_skip_different_stream",{eventId:H,eventStreamId:B?.streamId,targetStreamId:W})}}return this.logger?.debug("sse_replay_complete",{streamId:W,replayedCount:J}),W}enforceLimit(){if(this.events.size<=this.config.maxEvents)return;let X=[...this.events.keys()].sort().slice(0,this.events.size-this.config.maxEvents);for(let Y of X)this.events.delete(Y)}cleanupOldEvents(){let Q=Date.now()-this.config.retentionMs;for(let[X,Y]of this.events)if(Y.createdAt<Q)this.events.delete(X)}getEventCount(){return this.events.size}clear(){this.events.clear()}}var Ej={debug:0,info:1,warn:2,error:3};function Rj(Q="info"){let X=Ej[Q],Y=(W,$,J)=>{if(Ej[W]<X)return;let G=new Date().toISOString(),H=J?` ${JSON.stringify(J)}`:"";switch(W){case"debug":console.debug(`[${G}] DEBUG ${$}${H}`);break;case"info":console.info(`[${G}] INFO ${$}${H}`);break;case"warn":console.warn(`[${G}] WARN ${$}${H}`);break;case"error":console.error(`[${G}] ERROR ${$}${H}`);break}};return{debug:(W,$)=>{Y("debug",W,$)},info:(W,$)=>{Y("info",W,$)},warn:(W,$)=>{Y("warn",W,$)},error:(W,$)=>{Y("error",W,$)}}}var $g={maxRequestsPerSession:100,maxTotalRequests:1000};class JG{requests=new Map;sessionRequests=new Map;config;logger;constructor(Q={}){this.config={...$g,...Q},this.logger=Q.logger}startRequest(Q,X,Y,W={}){let $=K1(),J={requestId:$,sessionId:Q,type:X,name:Y,server:W.server,args:W.args,startedAt:new Date,status:"pending"};this.requests.set($,J);let G=this.sessionRequests.get(Q)??[];return G.push($),this.sessionRequests.set(Q,G),this.logger?.debug("request_started",{requestId:$,sessionId:Q,type:X,name:Y,server:W.server}),this.enforceSessionLimit(Q),this.enforceTotalLimit(),$}completeRequest(Q,X){let Y=this.requests.get(Q);if(!Y)return;Y.status="completed",Y.endedAt=new Date,Y.durationMs=Y.endedAt.getTime()-Y.startedAt.getTime(),Y.resultSummary=X,this.logger?.debug("request_completed",{requestId:Q,sessionId:Y.sessionId,type:Y.type,name:Y.name,server:Y.server,durationMs:Y.durationMs})}failRequest(Q,X){let Y=this.requests.get(Q);if(!Y)return;Y.status="failed",Y.endedAt=new Date,Y.durationMs=Y.endedAt.getTime()-Y.startedAt.getTime(),Y.error=X,this.logger?.debug("request_failed",{requestId:Q,sessionId:Y.sessionId,type:Y.type,name:Y.name,server:Y.server,durationMs:Y.durationMs,error:X})}timeoutRequest(Q){let X=this.requests.get(Q);if(!X)return;X.status="timeout",X.endedAt=new Date,X.durationMs=X.endedAt.getTime()-X.startedAt.getTime(),this.logger?.debug("request_timeout",{requestId:Q,sessionId:X.sessionId,type:X.type,name:X.name,server:X.server,durationMs:X.durationMs})}getSessionRequests(Q){return(this.sessionRequests.get(Q)??[]).map((Y)=>this.requests.get(Y)).filter((Y)=>Y!==void 0)}getAllRequests(){return Array.from(this.requests.values())}getRequestsBySession(){let Q=new Map;for(let[X]of this.sessionRequests)Q.set(X,this.getSessionRequests(X));return Q}clearSession(Q){let X=this.sessionRequests.get(Q)??[];for(let Y of X)this.requests.delete(Y);this.sessionRequests.delete(Q)}getStats(){let Q={pending:0,completed:0,failed:0,timeout:0},X={};for(let Y of this.requests.values())Q[Y.status]++,X[Y.type]=(X[Y.type]??0)+1;return{totalRequests:this.requests.size,sessionCount:this.sessionRequests.size,byStatus:Q,byType:X}}enforceSessionLimit(Q){let X=this.sessionRequests.get(Q);if(!X||X.length<=this.config.maxRequestsPerSession)return;let Y=X.length-this.config.maxRequestsPerSession,W=X.splice(0,Y);for(let $ of W)this.requests.delete($)}enforceTotalLimit(){if(this.requests.size<=this.config.maxTotalRequests)return;let Q=Array.from(this.requests.values()).sort((Y,W)=>Y.startedAt.getTime()-W.startedAt.getTime()),X=this.requests.size-this.config.maxTotalRequests;for(let Y=0;Y<X;Y++){let W=Q[Y];if(W){this.requests.delete(W.requestId);let $=this.sessionRequests.get(W.sessionId);if($){let J=$.indexOf(W.requestId);if(J>=0)$.splice(J,1)}}}}}function Ij(Q){let X=Q.getRequestsBySession(),Y=Q.getStats(),W=1/0,$=0;for(let G of X.values())for(let H of G){W=Math.min(W,H.startedAt.getTime());let B=H.endedAt?.getTime()??Date.now();$=Math.max($,B)}if(W===1/0)W=Date.now()-60000,$=Date.now();let J=Math.max($-W,1000);return`<!DOCTYPE html>
|
|
63
63
|
<html>
|
|
64
64
|
<head>
|
|
65
65
|
<title>MCP Proxy - Request Waterfall</title>
|
|
@@ -281,5 +281,5 @@ data:
|
|
|
281
281
|
</div>
|
|
282
282
|
</div>
|
|
283
283
|
`}function Hg(Q){switch(Q){case"tool_call":return"TOOL";case"resource_read":return"RES";case"prompt_get":return"PROMPT";case"backend_tool":return"B:TOOL";case"backend_resource":return"B:RES";case"backend_prompt":return"B:PROMPT";default:return Q.toUpperCase()}}function Sj(Q){return Q.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function Cj(Q){let X=Q.getRequestsBySession(),Y=Q.getStats(),W={};for(let[$,J]of X)W[$]=J;return{stats:Y,sessions:W,generatedAt:new Date().toISOString()}}function Kg(){let Q=process.argv.slice(2),X,Y=Number(process.env.PORT)||8080,W="info";for(let $=0;$<Q.length;$++){let J=Q[$];if(J==="--config"&&Q[$+1])X=Q[$+1],$++;else if(J?.startsWith("--config="))X=J.slice(9);else if(J==="--port"&&Q[$+1])Y=Number(Q[$+1]),$++;else if(J?.startsWith("--port="))Y=Number(J.slice(7));else if(J==="--log-level"&&Q[$+1])W=Q[$+1],$++;else if(J?.startsWith("--log-level="))W=J.slice(12)}return{configPath:X,port:Y,logLevel:W}}function Vg(Q){let X=zg(Q,"utf-8");return JSON.parse(X)}function E0(Q,X,Y){let W=X.sessionId;if(!W)return;let $=Y.get(W);if(!$)return;return Q.getSession($)}function a(Q){return{content:[{type:"text",text:Q}],isError:!0}}function l0(Q){return{content:[{type:"text",text:Q}]}}function m0(Q){return{content:[{type:"text",text:JSON.stringify(Q,null,2)}]}}function Fg(Q,X,Y,W){Q.registerTool("add_server",{description:"Connect to a backend MCP server. The server will be added to the shared configuration and this session will connect to it.",inputSchema:{name:c.string().describe("Unique name for this server"),url:c.string().url().describe("HTTP URL of the MCP server endpoint")}},async({name:$,url:J},G)=>{let H=E0(X,G,Y);if(!H)return a("Session not found");try{let z=(await X.addServer(H.sessionId,$,J)).client.getInfo().capabilities;return l0(`Connected to server '${$}' at ${J}
|
|
284
|
-
Capabilities: ${JSON.stringify(z)}`)}catch(B){let z=B instanceof Error?B.message:String(B);return a(`Failed to add server: ${z}`)}}),Q.registerTool("remove_server",{description:"Disconnect from a backend MCP server and remove it from the configuration. This disconnects ALL sessions from the server.",inputSchema:{name:c.string().describe("Name of the server to remove")}},async({name:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");try{return await X.removeServer(G.sessionId,$),l0(`Disconnected from server '${$}'`)}catch(H){let B=H instanceof Error?H.message:String(H);return a(`Failed to remove server: ${B}`)}}),Q.registerTool("reconnect_server",{description:"Force reconnection to a backend MCP server. Works on connected, disconnected, or reconnecting servers. Cancels any pending reconnection and immediately attempts a fresh connection.",inputSchema:{name:c.string().describe("Name of the server to reconnect")}},async({name:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");try{return await X.reconnectServer(G.sessionId,$),l0(`Reconnected to server '${$}'`)}catch(H){let B=H instanceof Error?H.message:String(H);return a(`Failed to reconnect: ${B}`)}}),Q.registerTool("list_servers",{description:"List all configured backend MCP servers with their connection status for this session",inputSchema:{}},($,J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");let H=X.listServers(G.sessionId);if(H.length===0)return l0("No servers configured");let B=H.map((z)=>{let K={name:z.name,url:z.url,status:z.status,connected:z.connected,connectedAt:z.connectedAt?.toISOString(),lastError:z.lastError};if(z.status==="reconnecting")K.reconnectAttempt=z.reconnectAttempt,K.nextRetryMs=z.nextRetryMs;if(z.status==="connected"){if(K.healthStatus=z.healthStatus,z.consecutiveHealthFailures!==void 0&&z.consecutiveHealthFailures>0)K.consecutiveHealthFailures=z.consecutiveHealthFailures}return K});return m0(B)}),Q.registerTool("list_tools",{description:"List tools available from backend servers",inputSchema:{server:c.string().default(".*").describe("Regex pattern to match server names (default: .*)"),tool:c.string().default(".*").describe("Regex pattern to match tool names (default: .*)")}},async({server:$,tool:J},G)=>{let H=E0(X,G,Y);if(!H)return a("Session not found");try{let B=new RegExp($),z=new RegExp(J),K=[];for(let V of H.listConnectedServers()){if(!B.test(V))continue;let F=H.getConnection(V);if(F?.status!=="connected")continue;try{let A=await F.client.listTools();for(let L of A)if(z.test(L.name))K.push({server:V,name:L.name,description:L.description,inputSchema:L.inputSchema})}catch{}}if(K.length===0)return l0("No tools available");return m0(K)}catch(B){let z=B instanceof Error?B.message:String(B);return a(`Failed to list tools: ${z}`)}}),Q.registerTool("execute_tool",{description:"Execute a tool on a specific backend server",inputSchema:{server:c.string().describe("Name of the backend server"),tool:c.string().describe("Name of the tool to execute"),args:c.record(c.unknown()).optional().describe("Arguments to pass to the tool")}},async({server:$,tool:J,args:G},H)=>{let B=E0(X,H,Y);if(!B)return a("Session not found");let z=B.getConnection($);if(z?.status==="reconnecting"){let V=z.client.getReconnectionState(),F=V?` (attempt ${String(V.attempt)}, next retry in ${String(V.nextRetryMs)}ms)`:"";return a(`Server '${$}' is reconnecting${F}. Use reconnect_server to force immediate reconnection or await_activity to wait for reconnection.`)}let K=W.startRequest(B.sessionId,"backend_tool",J,{server:$,args:G});try{let F=await(await X.getOrCreateConnection(B.sessionId,$)).callTool(J,G??{}),A=F.isError?"error":"ok";return W.completeRequest(K,A),{content:F.content,isError:F.isError}}catch(V){let F=V instanceof Error?V.message:String(V);return W.failRequest(K,F),a(`Failed to execute tool: ${F}`)}}),Q.registerTool("list_resources",{description:"List resources available from backend servers",inputSchema:{server:c.string().optional().describe("Server name to filter by (omit for all servers)")}},async({server:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");try{let H=[],B=$?[$]:G.listConnectedServers();for(let z of B){let K=G.getConnection(z);if(K?.status!=="connected")continue;try{let V=await K.client.listResources();for(let F of V)H.push({server:z,uri:F.uri,name:F.name,description:F.description,mimeType:F.mimeType})}catch{}}if(H.length===0)return l0("No resources available");return m0(H)}catch(H){let B=H instanceof Error?H.message:String(H);return a(`Failed to list resources: ${B}`)}}),Q.registerTool("list_resource_templates",{description:"List resource templates available from backend servers. Templates define parameterized resources that can be read with specific arguments.",inputSchema:{server:c.string().optional().describe("Server name to filter by (omit for all servers)")}},async({server:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");try{let H=[],B=$?[$]:G.listConnectedServers();for(let z of B){let K=G.getConnection(z);if(K?.status!=="connected")continue;try{let V=await K.client.listResourceTemplates();for(let F of V)H.push({server:z,uriTemplate:F.uriTemplate,name:F.name,description:F.description,mimeType:F.mimeType})}catch{}}if(H.length===0)return l0("No resource templates available");return m0(H)}catch(H){let B=H instanceof Error?H.message:String(H);return a(`Failed to list resource templates: ${B}`)}}),Q.registerTool("read_resource",{description:"Read a specific resource from a backend server",inputSchema:{server:c.string().describe("Name of the backend server"),uri:c.string().describe("URI of the resource to read")}},async({server:$,uri:J},G)=>{let H=E0(X,G,Y);if(!H)return a("Session not found");let B=W.startRequest(H.sessionId,"backend_resource",J,{server:$});try{let V=(await(await X.getOrCreateConnection(H.sessionId,$)).readResource(J)).contents.map((F)=>{if("text"in F&&typeof F.text==="string")return{uri:F.uri,mimeType:F.mimeType,text:F.text};else if("blob"in F&&typeof F.blob==="string")return{uri:F.uri,mimeType:F.mimeType,blob:`[base64 data, ${String(F.blob.length)} chars]`};return F});return W.completeRequest(B,`${String(V.length)} content(s)`),m0({contents:V})}catch(z){let K=z instanceof Error?z.message:String(z);return W.failRequest(B,K),a(`Failed to read resource: ${K}`)}}),Q.registerTool("list_prompts",{description:"List prompts available from backend servers",inputSchema:{server:c.string().optional().describe("Server name to filter by (omit for all servers)")}},async({server:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");try{let H=[],B=$?[$]:G.listConnectedServers();for(let z of B){let K=G.getConnection(z);if(K?.status!=="connected")continue;try{let V=await K.client.listPrompts();for(let F of V)H.push({server:z,name:F.name,description:F.description,arguments:F.arguments})}catch{}}if(H.length===0)return l0("No prompts available");return m0(H)}catch(H){let B=H instanceof Error?H.message:String(H);return a(`Failed to list prompts: ${B}`)}}),Q.registerTool("get_prompt",{description:"Get a specific prompt from a backend server",inputSchema:{server:c.string().describe("Name of the backend server"),name:c.string().describe("Name of the prompt to get"),arguments:c.record(c.string()).optional().describe("Arguments to pass to the prompt")}},async({server:$,name:J,arguments:G},H)=>{let B=E0(X,H,Y);if(!B)return a("Session not found");try{let K=await(await X.getOrCreateConnection(B.sessionId,$)).getPrompt(J,G??{});return m0(K)}catch(z){let K=z instanceof Error?z.message:String(z);return a(`Failed to get prompt: ${K}`)}}),Q.registerTool("get_notifications",{description:"Get and clear buffered notifications from backend servers for this session",inputSchema:{}},($,J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");let H=G.bufferManager.getAndClearNotifications();if(H.length===0)return l0("No notifications");let B=H.map((z)=>({server:z.server,method:z.method,timestamp:z.timestamp.toISOString(),params:z.params}));return m0(B)}),Q.registerTool("get_logs",{description:"Get and clear buffered log messages from backend servers for this session",inputSchema:{}},($,J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");let H=G.bufferManager.getAndClearLogs();if(H.length===0)return l0("No log messages");let B=H.map((z)=>({server:z.server,level:z.level,logger:z.logger,timestamp:z.timestamp.toISOString(),data:z.data}));return m0(B)}),Q.registerTool("get_sampling_requests",{description:"Get pending sampling requests from backend servers that need LLM responses. These are requests from MCP servers asking for LLM completions.",inputSchema:{}},($,J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");let H=G.pendingRequests.getPendingSamplingRequests();if(H.length===0)return l0("No pending sampling requests");let B=H.map((z)=>({requestId:z.requestId,server:z.server,timestamp:z.timestamp.toISOString(),maxTokens:z.params.maxTokens,systemPrompt:z.params.systemPrompt,temperature:z.params.temperature,messages:z.params.messages,modelPreferences:z.params.modelPreferences,tools:z.params.tools?.map((K)=>({name:K.name,description:K.description}))}));return m0(B)}),Q.registerTool("respond_to_sampling",{description:"Respond to a pending sampling request with an LLM result",inputSchema:{request_id:c.string().describe("The ID of the sampling request to respond to"),role:c.enum(["user","assistant"]).describe("The role of the message"),content:c.string().describe("The text content of the response"),model:c.string().describe("The model that generated the response"),stop_reason:c.enum(["endTurn","maxTokens","stopSequence","toolUse"]).optional().describe("Why the model stopped generating")}},({request_id:$,role:J,content:G,model:H,stop_reason:B},z)=>{let K=E0(X,z,Y);if(!K)return a("Session not found");try{return K.pendingRequests.respondToSampling($,{role:J,content:{type:"text",text:G},model:H,stopReason:B}),l0(`Responded to sampling request '${$}'`)}catch(V){let F=V instanceof Error?V.message:String(V);return a(`Failed to respond: ${F}`)}}),Q.registerTool("get_elicitations",{description:"Get pending elicitation requests from backend servers that need user input. These are requests from MCP servers asking for form data or user confirmation.",inputSchema:{}},($,J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");let H=G.pendingRequests.getPendingElicitationRequests();if(H.length===0)return l0("No pending elicitation requests");let B=H.map((z)=>({requestId:z.requestId,server:z.server,timestamp:z.timestamp.toISOString(),mode:"mode"in z.params?z.params.mode:"form",message:z.params.message,requestedSchema:"requestedSchema"in z.params?z.params.requestedSchema:void 0,elicitationId:"elicitationId"in z.params?z.params.elicitationId:void 0,url:"url"in z.params?z.params.url:void 0}));return m0(B)}),Q.registerTool("respond_to_elicitation",{description:"Respond to a pending elicitation request with user input",inputSchema:{request_id:c.string().describe("The ID of the elicitation request to respond to"),action:c.enum(["accept","decline","cancel"]).describe("The user's action: accept (with content), decline, or cancel"),content:c.record(c.union([c.string(),c.number(),c.boolean(),c.array(c.string())])).optional().describe("The form field values (required if action is 'accept' for form mode)")}},({request_id:$,action:J,content:G},H)=>{let B=E0(X,H,Y);if(!B)return a("Session not found");try{return B.pendingRequests.respondToElicitation($,{action:J,content:G}),l0(`Responded to elicitation request '${$}' with action '${J}'`)}catch(z){let K=z instanceof Error?z.message:String(z);return a(`Failed to respond: ${K}`)}}),Q.registerTool("await_activity",{description:"Wait for activity (events, pending requests) or timeout. Use this to poll for changes efficiently instead of repeatedly calling get_* tools.",inputSchema:{timeout_ms:c.number().min(100).max(60000).default(30000).describe("Maximum time to wait in milliseconds (100-60000, default: 30000)"),last_event_id:c.string().optional().describe("Only return events after this ID (for pagination/continuation)")}},async({timeout_ms:$,last_event_id:J},G)=>{let H=E0(X,G,Y);if(!H)return a("Session not found");let B=J?H.eventSystem.getEventsAfter(J):H.eventSystem.getNewEvents();if(B.length>0)return m0({triggered_by:"existing_events",events:B.map((K)=>({id:K.id,type:K.type,server:K.server,createdAt:K.createdAt.toISOString(),data:K.data})),pending_server:{tasks:H.taskManager.getAllTasks().map((K)=>({taskId:K.taskId,server:K.server,toolName:K.toolName,status:K.status}))},pending_client:{sampling:H.pendingRequests.getPendingSamplingRequests().length,elicitation:H.pendingRequests.getPendingElicitationRequests().length}});let z=await H.eventSystem.waitForActivity($);if(z){let K=H.eventSystem.getNewEvents();return m0({triggered_by:z.type,events:K.map((V)=>({id:V.id,type:V.type,server:V.server,createdAt:V.createdAt.toISOString(),data:V.data})),pending_server:{tasks:H.taskManager.getAllTasks().map((V)=>({taskId:V.taskId,server:V.server,toolName:V.toolName,status:V.status}))},pending_client:{sampling:H.pendingRequests.getPendingSamplingRequests().length,elicitation:H.pendingRequests.getPendingElicitationRequests().length}})}return m0({triggered_by:"timeout",events:[],pending_server:{tasks:H.taskManager.getAllTasks().map((K)=>({taskId:K.taskId,server:K.server,toolName:K.toolName,status:K.status}))},pending_client:{sampling:H.pendingRequests.getPendingSamplingRequests().length,elicitation:H.pendingRequests.getPendingElicitationRequests().length}})}),Q.registerTool("list_tasks",{description:"List proxy tasks (background tool executions that exceeded timeout)",inputSchema:{include_completed:c.boolean().default(!1).describe("Include completed/failed/cancelled tasks (default: false, only working tasks)"),server:c.string().optional().describe("Filter by server name")}},({include_completed:$,server:J},G)=>{let H=E0(X,G,Y);if(!H)return a("Session not found");let B=H.taskManager.getAllTasks($);if(J)B=B.filter((K)=>K.server===J);if(B.length===0)return l0("No tasks");let z=B.map((K)=>({taskId:K.taskId,server:K.server,toolName:K.toolName,status:K.status,createdAt:K.createdAt.toISOString(),lastUpdatedAt:K.lastUpdatedAt.toISOString(),error:K.error,hasResult:K.result!==void 0}));return m0(z)}),Q.registerTool("get_task",{description:"Get details of a specific task including its result if completed",inputSchema:{task_id:c.string().describe("The task ID to retrieve")}},({task_id:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");let H=G.taskManager.getTask($);if(!H)return a(`Task '${$}' not found`);return m0({taskId:H.taskId,server:H.server,toolName:H.toolName,args:H.args,status:H.status,createdAt:H.createdAt.toISOString(),lastUpdatedAt:H.lastUpdatedAt.toISOString(),ttl:H.ttl,error:H.error,result:H.result})}),Q.registerTool("cancel_task",{description:"Cancel a working task",inputSchema:{task_id:c.string().describe("The task ID to cancel")}},({task_id:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");if(G.taskManager.cancelTask($))return l0(`Task '${$}' cancelled`);else return a(`Task '${$}' not found or not in working state`)})}function Ag(){let{configPath:Q,port:X,logLevel:Y}=Kg(),W=Rj(Y);W.info("Starting MCP Proxy Server",{port:X,configPath:Q});let $=new WG({logger:W,sessionTimeoutMs:1800000,cleanupIntervalMs:300000});if(Q)try{let V=Vg(Q);W.info("Loading servers from config",{count:V.servers.length,path:Q});for(let F of V.servers)W.info("Adding server config",{name:F.name,url:F.url}),$.getServerConfigs().addConfig(F.name,F.url)}catch(V){W.error("Failed to load config",{error:V instanceof Error?V.message:String(V)}),process.exit(1)}let J=new JG({logger:W}),G=new IJ({name:"emceepee",version:"0.2.0"}),H=new Map,B=new Map;Fg(G,$,H,J);let z=Bg((V,F)=>{let A=V.headers.host??"localhost",L=new URL(V.url??"/",`http://${A}`);if(L.pathname==="/waterfall"||L.pathname==="/waterfall/"){F.writeHead(200,{"Content-Type":"text/html"}),F.end(Ij(J));return}if(L.pathname==="/waterfall/json"){F.writeHead(200,{"Content-Type":"application/json"}),F.end(JSON.stringify(Cj(J),null,2));return}if(L.pathname!=="/mcp"){F.writeHead(404,{"Content-Type":"application/json"}),F.end(JSON.stringify({error:"Not found. Available endpoints: /mcp, /waterfall, /waterfall/json"}));return}let D=V.headers["mcp-session-id"];if(V.method==="POST"){let O=D?B.get(D):void 0;if(!O){if(D)W.warn("transport_not_found_for_session",{transportSessionId:D,transportCount:B.size,knownTransports:Array.from(B.keys())});let j=new $G({logger:W}),q=new yJ({sessionIdGenerator:()=>crypto.randomUUID(),eventStore:j,onsessioninitialized:(M)=>{B.set(M,q),$.createSession().then((S)=>{H.set(M,S.sessionId),W.info("Session created",{transportSessionId:M,sessionId:S.sessionId})})}});O=q,G.connect(q)}else{W.debug("using_existing_transport",{transportSessionId:D});let j=H.get(D??"");if(j)$.touchSession(j)}O.handleRequest(V,F)}else if(V.method==="GET"){if(!D){F.writeHead(400,{"Content-Type":"application/json"}),F.end(JSON.stringify({error:"Missing mcp-session-id header"}));return}let O=B.get(D);if(!O){F.writeHead(404,{"Content-Type":"application/json"}),F.end(JSON.stringify({error:"Session not found"}));return}F.on("close",()=>{W.info("sse_connection_closed",{transportSessionId:D});let q=B.get(D);if(q){q.close(),B.delete(D);let M=H.get(D);if(M)$.destroySession(M),H.delete(D),W.info("session_closed_via_sse",{transportSessionId:D,sessionId:M})}});let j=V.headers["last-event-id"]!==void 0;if(O.handleRequest(V,F),!j)setTimeout(()=>{W.debug("sending_sse_priming_notification",{transportSessionId:D}),G.sendToolListChanged()},100)}else if(V.method==="DELETE"){if(D&&B.has(D)){B.get(D)?.close(),B.delete(D);let j=H.get(D);if(j)$.destroySession(j),H.delete(D);W.info("Session closed",{transportSessionId:D})}F.writeHead(200),F.end()}else F.writeHead(405,{"Content-Type":"application/json"}),F.end(JSON.stringify({error:"Method not allowed"}))}),K=async()=>{W.info("Shutting down...",{});for(let V of B.values())V.close();await $.shutdown(),z.close(),process.exit(0)};process.on("SIGINT",()=>void K()),process.on("SIGTERM",()=>void K()),z.listen(X,()=>{W.info("Server started",{url:`http://localhost:${String(X)}/mcp`}),console.log(`MCP Proxy Server running at http://localhost:${String(X)}/mcp`),console.log(`Waterfall UI available at http://localhost:${String(X)}/waterfall`),console.log(`
|
|
284
|
+
Capabilities: ${JSON.stringify(z)}`)}catch(B){let z=B instanceof Error?B.message:String(B);return a(`Failed to add server: ${z}`)}}),Q.registerTool("remove_server",{description:"Disconnect from a backend MCP server and remove it from the configuration. This disconnects ALL sessions from the server.",inputSchema:{name:c.string().describe("Name of the server to remove")}},async({name:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");try{return await X.removeServer(G.sessionId,$),l0(`Disconnected from server '${$}'`)}catch(H){let B=H instanceof Error?H.message:String(H);return a(`Failed to remove server: ${B}`)}}),Q.registerTool("reconnect_server",{description:"Force reconnection to a backend MCP server. Works on connected, disconnected, or reconnecting servers. Cancels any pending reconnection and immediately attempts a fresh connection.",inputSchema:{name:c.string().describe("Name of the server to reconnect")}},async({name:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");try{return await X.reconnectServer(G.sessionId,$),l0(`Reconnected to server '${$}'`)}catch(H){let B=H instanceof Error?H.message:String(H);return a(`Failed to reconnect: ${B}`)}}),Q.registerTool("list_servers",{description:"List all configured backend MCP servers with their connection status for this session",inputSchema:{}},($,J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");let H=X.listServers(G.sessionId);if(H.length===0)return l0("No servers configured");let B=H.map((z)=>{let K={name:z.name,url:z.url,status:z.status,connected:z.connected,connectedAt:z.connectedAt?.toISOString(),lastError:z.lastError};if(z.status==="reconnecting")K.reconnectAttempt=z.reconnectAttempt,K.nextRetryMs=z.nextRetryMs;if(z.status==="connected"){if(K.healthStatus=z.healthStatus,z.consecutiveHealthFailures!==void 0&&z.consecutiveHealthFailures>0)K.consecutiveHealthFailures=z.consecutiveHealthFailures}return K});return m0(B)}),Q.registerTool("list_tools",{description:"List tools available from backend servers",inputSchema:{server:c.string().default(".*").describe("Regex pattern to match server names (default: .*)"),tool:c.string().default(".*").describe("Regex pattern to match tool names (default: .*)")}},async({server:$,tool:J},G)=>{let H=E0(X,G,Y);if(!H)return a("Session not found");try{let B=new RegExp($),z=new RegExp(J),K=[];for(let V of H.listConnectedServers()){if(!B.test(V))continue;let F=H.getConnection(V);if(F?.status!=="connected")continue;try{let A=await F.client.listTools();for(let L of A)if(z.test(L.name))K.push({server:V,name:L.name,description:L.description,inputSchema:L.inputSchema})}catch{}}if(K.length===0)return l0("No tools available");return m0(K)}catch(B){let z=B instanceof Error?B.message:String(B);return a(`Failed to list tools: ${z}`)}}),Q.registerTool("execute_tool",{description:"Execute a tool on a specific backend server",inputSchema:{server:c.string().describe("Name of the backend server"),tool:c.string().describe("Name of the tool to execute"),args:c.record(c.unknown()).optional().describe("Arguments to pass to the tool")}},async({server:$,tool:J,args:G},H)=>{let B=E0(X,H,Y);if(!B)return a("Session not found");let z=B.getConnection($);if(z?.status==="reconnecting"){let V=z.client.getReconnectionState(),F=V?` (attempt ${String(V.attempt)}, next retry in ${String(V.nextRetryMs)}ms)`:"";return a(`Server '${$}' is reconnecting${F}. Use reconnect_server to force immediate reconnection or await_activity to wait for reconnection.`)}let K=W.startRequest(B.sessionId,"backend_tool",J,{server:$,args:G});try{let F=await(await X.getOrCreateConnection(B.sessionId,$)).callTool(J,G??{}),A=F.isError?"error":"ok";return W.completeRequest(K,A),{content:F.content,isError:F.isError}}catch(V){let F=V instanceof Error?V.message:String(V);return W.failRequest(K,F),a(`Failed to execute tool: ${F}`)}}),Q.registerTool("list_resources",{description:"List resources available from backend servers",inputSchema:{server:c.string().optional().describe("Server name to filter by (omit for all servers)")}},async({server:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");try{let H=[],B=$?[$]:G.listConnectedServers();for(let z of B){let K=G.getConnection(z);if(K?.status!=="connected")continue;try{let V=await K.client.listResources();for(let F of V)H.push({server:z,uri:F.uri,name:F.name,description:F.description,mimeType:F.mimeType})}catch{}}if(H.length===0)return l0("No resources available");return m0(H)}catch(H){let B=H instanceof Error?H.message:String(H);return a(`Failed to list resources: ${B}`)}}),Q.registerTool("list_resource_templates",{description:"List resource templates available from backend servers. Templates define parameterized resources that can be read with specific arguments.",inputSchema:{server:c.string().optional().describe("Server name to filter by (omit for all servers)")}},async({server:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");try{let H=[],B=$?[$]:G.listConnectedServers();for(let z of B){let K=G.getConnection(z);if(K?.status!=="connected")continue;try{let V=await K.client.listResourceTemplates();for(let F of V)H.push({server:z,uriTemplate:F.uriTemplate,name:F.name,description:F.description,mimeType:F.mimeType})}catch{}}if(H.length===0)return l0("No resource templates available");return m0(H)}catch(H){let B=H instanceof Error?H.message:String(H);return a(`Failed to list resource templates: ${B}`)}}),Q.registerTool("read_resource",{description:"Read a specific resource from a backend server",inputSchema:{server:c.string().describe("Name of the backend server"),uri:c.string().describe("URI of the resource to read")}},async({server:$,uri:J},G)=>{let H=E0(X,G,Y);if(!H)return a("Session not found");let B=W.startRequest(H.sessionId,"backend_resource",J,{server:$});try{let V=(await(await X.getOrCreateConnection(H.sessionId,$)).readResource(J)).contents.map((F)=>{if("text"in F&&typeof F.text==="string")return{uri:F.uri,mimeType:F.mimeType,text:F.text};else if("blob"in F&&typeof F.blob==="string")return{uri:F.uri,mimeType:F.mimeType,blob:`[base64 data, ${String(F.blob.length)} chars]`};return F});return W.completeRequest(B,`${String(V.length)} content(s)`),m0({contents:V})}catch(z){let K=z instanceof Error?z.message:String(z);return W.failRequest(B,K),a(`Failed to read resource: ${K}`)}}),Q.registerTool("list_prompts",{description:"List prompts available from backend servers",inputSchema:{server:c.string().optional().describe("Server name to filter by (omit for all servers)")}},async({server:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");try{let H=[],B=$?[$]:G.listConnectedServers();for(let z of B){let K=G.getConnection(z);if(K?.status!=="connected")continue;try{let V=await K.client.listPrompts();for(let F of V)H.push({server:z,name:F.name,description:F.description,arguments:F.arguments})}catch{}}if(H.length===0)return l0("No prompts available");return m0(H)}catch(H){let B=H instanceof Error?H.message:String(H);return a(`Failed to list prompts: ${B}`)}}),Q.registerTool("get_prompt",{description:"Get a specific prompt from a backend server",inputSchema:{server:c.string().describe("Name of the backend server"),name:c.string().describe("Name of the prompt to get"),arguments:c.record(c.string()).optional().describe("Arguments to pass to the prompt")}},async({server:$,name:J,arguments:G},H)=>{let B=E0(X,H,Y);if(!B)return a("Session not found");try{let K=await(await X.getOrCreateConnection(B.sessionId,$)).getPrompt(J,G??{});return m0(K)}catch(z){let K=z instanceof Error?z.message:String(z);return a(`Failed to get prompt: ${K}`)}}),Q.registerTool("get_notifications",{description:"Get and clear buffered notifications from backend servers for this session",inputSchema:{}},($,J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");let H=G.bufferManager.getAndClearNotifications();if(H.length===0)return l0("No notifications");let B=H.map((z)=>({server:z.server,method:z.method,timestamp:z.timestamp.toISOString(),params:z.params}));return m0(B)}),Q.registerTool("get_logs",{description:"Get and clear buffered log messages from backend servers for this session",inputSchema:{}},($,J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");let H=G.bufferManager.getAndClearLogs();if(H.length===0)return l0("No log messages");let B=H.map((z)=>({server:z.server,level:z.level,logger:z.logger,timestamp:z.timestamp.toISOString(),data:z.data}));return m0(B)}),Q.registerTool("get_sampling_requests",{description:"Get pending sampling requests from backend servers that need LLM responses. These are requests from MCP servers asking for LLM completions. URGENT: Backend servers are blocked waiting for these responses. You MUST respond promptly using respond_to_sampling.",inputSchema:{}},($,J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");let H=G.pendingRequests.getPendingSamplingRequests();if(H.length===0)return l0("No pending sampling requests");let B=H.map((z)=>({requestId:z.requestId,server:z.server,timestamp:z.timestamp.toISOString(),maxTokens:z.params.maxTokens,systemPrompt:z.params.systemPrompt,temperature:z.params.temperature,messages:z.params.messages,modelPreferences:z.params.modelPreferences,tools:z.params.tools?.map((K)=>({name:K.name,description:K.description}))}));return m0(B)}),Q.registerTool("respond_to_sampling",{description:"Respond to a pending sampling request with an LLM result",inputSchema:{request_id:c.string().describe("The ID of the sampling request to respond to"),role:c.enum(["user","assistant"]).describe("The role of the message"),content:c.string().describe("The text content of the response"),model:c.string().describe("The model that generated the response"),stop_reason:c.enum(["endTurn","maxTokens","stopSequence","toolUse"]).optional().describe("Why the model stopped generating")}},({request_id:$,role:J,content:G,model:H,stop_reason:B},z)=>{let K=E0(X,z,Y);if(!K)return a("Session not found");try{return K.pendingRequests.respondToSampling($,{role:J,content:{type:"text",text:G},model:H,stopReason:B}),l0(`Responded to sampling request '${$}'`)}catch(V){let F=V instanceof Error?V.message:String(V);return a(`Failed to respond: ${F}`)}}),Q.registerTool("get_elicitations",{description:"Get pending elicitation requests from backend servers that need user input. These are requests from MCP servers asking for form data or user confirmation. URGENT: Backend servers are blocked waiting for user responses. You MUST respond promptly using respond_to_elicitation or the request may time out.",inputSchema:{}},($,J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");let H=G.pendingRequests.getPendingElicitationRequests();if(H.length===0)return l0("No pending elicitation requests");let B=H.map((z)=>({requestId:z.requestId,server:z.server,timestamp:z.timestamp.toISOString(),mode:"mode"in z.params?z.params.mode:"form",message:z.params.message,requestedSchema:"requestedSchema"in z.params?z.params.requestedSchema:void 0,elicitationId:"elicitationId"in z.params?z.params.elicitationId:void 0,url:"url"in z.params?z.params.url:void 0}));return m0(B)}),Q.registerTool("respond_to_elicitation",{description:"Respond to a pending elicitation request with user input",inputSchema:{request_id:c.string().describe("The ID of the elicitation request to respond to"),action:c.enum(["accept","decline","cancel"]).describe("The user's action: accept (with content), decline, or cancel"),content:c.record(c.union([c.string(),c.number(),c.boolean(),c.array(c.string())])).optional().describe("The form field values (required if action is 'accept' for form mode)")}},({request_id:$,action:J,content:G},H)=>{let B=E0(X,H,Y);if(!B)return a("Session not found");try{return B.pendingRequests.respondToElicitation($,{action:J,content:G}),l0(`Responded to elicitation request '${$}' with action '${J}'`)}catch(z){let K=z instanceof Error?z.message:String(z);return a(`Failed to respond: ${K}`)}}),Q.registerTool("await_activity",{description:"Wait for activity (events, pending requests) or timeout. Use this to poll for changes efficiently instead of repeatedly calling get_* tools. IMPORTANT: If pending_client.sampling or pending_client.elicitation counts are non-zero, you MUST respond promptly - backend servers are blocked waiting.",inputSchema:{timeout_ms:c.number().min(100).max(60000).default(30000).describe("Maximum time to wait in milliseconds (100-60000, default: 30000)"),last_event_id:c.string().optional().describe("Only return events after this ID (for pagination/continuation)")}},async({timeout_ms:$,last_event_id:J},G)=>{let H=E0(X,G,Y);if(!H)return a("Session not found");let B=J?H.eventSystem.getEventsAfter(J):H.eventSystem.getNewEvents();if(B.length>0)return m0({triggered_by:"existing_events",events:B.map((K)=>({id:K.id,type:K.type,server:K.server,createdAt:K.createdAt.toISOString(),data:K.data})),pending_server:{tasks:H.taskManager.getAllTasks().map((K)=>({taskId:K.taskId,server:K.server,toolName:K.toolName,status:K.status}))},pending_client:{sampling:H.pendingRequests.getPendingSamplingRequests().length,elicitation:H.pendingRequests.getPendingElicitationRequests().length}});let z=await H.eventSystem.waitForActivity($);if(z){let K=H.eventSystem.getNewEvents();return m0({triggered_by:z.type,events:K.map((V)=>({id:V.id,type:V.type,server:V.server,createdAt:V.createdAt.toISOString(),data:V.data})),pending_server:{tasks:H.taskManager.getAllTasks().map((V)=>({taskId:V.taskId,server:V.server,toolName:V.toolName,status:V.status}))},pending_client:{sampling:H.pendingRequests.getPendingSamplingRequests().length,elicitation:H.pendingRequests.getPendingElicitationRequests().length}})}return m0({triggered_by:"timeout",events:[],pending_server:{tasks:H.taskManager.getAllTasks().map((K)=>({taskId:K.taskId,server:K.server,toolName:K.toolName,status:K.status}))},pending_client:{sampling:H.pendingRequests.getPendingSamplingRequests().length,elicitation:H.pendingRequests.getPendingElicitationRequests().length}})}),Q.registerTool("list_tasks",{description:"List proxy tasks (background tool executions that exceeded timeout)",inputSchema:{include_completed:c.boolean().default(!1).describe("Include completed/failed/cancelled tasks (default: false, only working tasks)"),server:c.string().optional().describe("Filter by server name")}},({include_completed:$,server:J},G)=>{let H=E0(X,G,Y);if(!H)return a("Session not found");let B=H.taskManager.getAllTasks($);if(J)B=B.filter((K)=>K.server===J);if(B.length===0)return l0("No tasks");let z=B.map((K)=>({taskId:K.taskId,server:K.server,toolName:K.toolName,status:K.status,createdAt:K.createdAt.toISOString(),lastUpdatedAt:K.lastUpdatedAt.toISOString(),error:K.error,hasResult:K.result!==void 0}));return m0(z)}),Q.registerTool("get_task",{description:"Get details of a specific task including its result if completed",inputSchema:{task_id:c.string().describe("The task ID to retrieve")}},({task_id:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");let H=G.taskManager.getTask($);if(!H)return a(`Task '${$}' not found`);return m0({taskId:H.taskId,server:H.server,toolName:H.toolName,args:H.args,status:H.status,createdAt:H.createdAt.toISOString(),lastUpdatedAt:H.lastUpdatedAt.toISOString(),ttl:H.ttl,error:H.error,result:H.result})}),Q.registerTool("cancel_task",{description:"Cancel a working task",inputSchema:{task_id:c.string().describe("The task ID to cancel")}},({task_id:$},J)=>{let G=E0(X,J,Y);if(!G)return a("Session not found");if(G.taskManager.cancelTask($))return l0(`Task '${$}' cancelled`);else return a(`Task '${$}' not found or not in working state`)})}function Ag(){let{configPath:Q,port:X,logLevel:Y}=Kg(),W=Rj(Y);W.info("Starting MCP Proxy Server",{port:X,configPath:Q});let $=new WG({logger:W,sessionTimeoutMs:86400000,cleanupIntervalMs:3600000});if(Q)try{let V=Vg(Q);W.info("Loading servers from config",{count:V.servers.length,path:Q});for(let F of V.servers)W.info("Adding server config",{name:F.name,url:F.url}),$.getServerConfigs().addConfig(F.name,F.url)}catch(V){W.error("Failed to load config",{error:V instanceof Error?V.message:String(V)}),process.exit(1)}let J=new JG({logger:W}),G=new IJ({name:"emceepee",version:"0.2.0"}),H=new Map,B=new Map;Fg(G,$,H,J);let z=Bg((V,F)=>{let A=V.headers.host??"localhost",L=new URL(V.url??"/",`http://${A}`);if(L.pathname==="/waterfall"||L.pathname==="/waterfall/"){F.writeHead(200,{"Content-Type":"text/html"}),F.end(Ij(J));return}if(L.pathname==="/waterfall/json"){F.writeHead(200,{"Content-Type":"application/json"}),F.end(JSON.stringify(Cj(J),null,2));return}if(L.pathname!=="/mcp"){F.writeHead(404,{"Content-Type":"application/json"}),F.end(JSON.stringify({error:"Not found. Available endpoints: /mcp, /waterfall, /waterfall/json"}));return}let D=V.headers["mcp-session-id"];if(V.method==="POST"){let O=D?B.get(D):void 0;if(!O){if(D)W.warn("transport_not_found_for_session",{transportSessionId:D,transportCount:B.size,knownTransports:Array.from(B.keys())});let j=new $G({logger:W}),q=new yJ({sessionIdGenerator:()=>crypto.randomUUID(),eventStore:j,onsessioninitialized:(M)=>{B.set(M,q),$.createSession().then((S)=>{H.set(M,S.sessionId),W.info("Session created",{transportSessionId:M,sessionId:S.sessionId})})}});O=q,G.connect(q)}else{W.debug("using_existing_transport",{transportSessionId:D});let j=H.get(D??"");if(j)$.touchSession(j)}O.handleRequest(V,F)}else if(V.method==="GET"){if(!D){F.writeHead(400,{"Content-Type":"application/json"}),F.end(JSON.stringify({error:"Missing mcp-session-id header"}));return}let O=B.get(D);if(!O){F.writeHead(404,{"Content-Type":"application/json"}),F.end(JSON.stringify({error:"Session not found"}));return}F.on("close",()=>{W.info("sse_connection_closed",{transportSessionId:D});let q=B.get(D);if(q){q.close(),B.delete(D);let M=H.get(D);if(M)$.destroySession(M),H.delete(D),W.info("session_closed_via_sse",{transportSessionId:D,sessionId:M})}});let j=V.headers["last-event-id"]!==void 0;if(O.handleRequest(V,F),!j)setTimeout(()=>{W.debug("sending_sse_priming_notification",{transportSessionId:D}),G.sendToolListChanged()},100)}else if(V.method==="DELETE"){if(D&&B.has(D)){B.get(D)?.close(),B.delete(D);let j=H.get(D);if(j)$.destroySession(j),H.delete(D);W.info("Session closed",{transportSessionId:D})}F.writeHead(200),F.end()}else F.writeHead(405,{"Content-Type":"application/json"}),F.end(JSON.stringify({error:"Method not allowed"}))}),K=async()=>{W.info("Shutting down...",{});for(let V of B.values())V.close();await $.shutdown(),z.close(),process.exit(0)};process.on("SIGINT",()=>void K()),process.on("SIGTERM",()=>void K()),z.listen(X,()=>{W.info("Server started",{url:`http://localhost:${String(X)}/mcp`}),console.log(`MCP Proxy Server running at http://localhost:${String(X)}/mcp`),console.log(`Waterfall UI available at http://localhost:${String(X)}/waterfall`),console.log(`
|
|
285
285
|
Available tools:`),console.log(" Server management: add_server, remove_server, reconnect_server, list_servers"),console.log(" Tools: list_tools, execute_tool"),console.log(" Resources: list_resources, read_resource"),console.log(" Prompts: list_prompts, get_prompt"),console.log(" Notifications: get_notifications, get_logs"),console.log(" Sampling: get_sampling_requests, respond_to_sampling"),console.log(" Elicitation: get_elicitations, respond_to_elicitation"),console.log(" Activity: await_activity"),console.log(" Tasks: list_tasks, get_task, cancel_task")})}Ag();
|
package/dist/emceepee.js
CHANGED
|
@@ -50,7 +50,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
50
50
|
`;break;case"id":H=L.includes("\x00")?void 0:L;break;case"retry":/^\d+$/.test(L)?W(parseInt(L,10)):Y(new fJ(`Invalid \`retry\` value: "${L}"`,{type:"invalid-retry",value:L,line:O}));break;default:Y(new fJ(`Unknown field "${A.length>20?`${A.slice(0,20)}…`:A}"`,{type:"unknown-field",field:A,value:L,line:O}));break}}function F(){B.length>0&&X({id:H,event:z||void 0,data:B.endsWith(`
|
|
51
51
|
`)?B.slice(0,-1):B}),H=void 0,B="",z=""}function q(A={}){J&&A.consume&&V(J),G=!0,H=void 0,B="",z="",J=""}return{feed:K,reset:q}}function Wg(Q){let X=[],Y="",W=0;for(;W<Q.length;){let $=Q.indexOf("\r",W),J=Q.indexOf(`
|
|
52
52
|
`,W),G=-1;if($!==-1&&J!==-1?G=Math.min($,J):$!==-1?$===Q.length-1?G=-1:G=$:J!==-1&&(G=J),G===-1){Y=Q.slice(W);break}else{let H=Q.slice(W,G);X.push(H),W=G+1,Q[W-1]==="\r"&&Q[W]===`
|
|
53
|
-
`&&W++}}return[X,Y]}class hJ extends TransformStream{constructor({onError:Q,onRetry:X,onComment:Y}={}){let W;super({start($){W=QO({onEvent:(J)=>{$.enqueue(J)},onError(J){Q==="terminate"?$.error(J):typeof Q=="function"&&Q(J)},onRetry:X,onComment:Y})},transform($){W.feed($)}})}}var $g={initialReconnectionDelay:1000,maxReconnectionDelay:30000,reconnectionDelayGrowFactor:1.5,maxRetries:2};class K9 extends Error{constructor(Q,X){super(`Streamable HTTP error: ${X}`);this.code=Q}}class SY{constructor(Q,X){this._hasCompletedAuthFlow=!1,this._url=Q,this._resourceMetadataUrl=void 0,this._scope=void 0,this._requestInit=X?.requestInit,this._authProvider=X?.authProvider,this._fetch=X?.fetch,this._fetchWithInit=lL(X?.fetch,X?.requestInit),this._sessionId=X?.sessionId,this._reconnectionOptions=X?.reconnectionOptions??$g}async _authThenStart(){if(!this._authProvider)throw new y6("No auth provider");let Q;try{Q=await m8(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})}catch(X){throw this.onerror?.(X),X}if(Q!=="AUTHORIZED")throw new y6;return await this._startOrAuthSse({resumptionToken:void 0})}async _commonHeaders(){let Q={};if(this._authProvider){let Y=await this._authProvider.tokens();if(Y)Q.Authorization=`Bearer ${Y.access_token}`}if(this._sessionId)Q["mcp-session-id"]=this._sessionId;if(this._protocolVersion)Q["mcp-protocol-version"]=this._protocolVersion;let X=DY(this._requestInit?.headers);return new Headers({...Q,...X})}async _startOrAuthSse(Q){let{resumptionToken:X}=Q;try{let Y=await this._commonHeaders();if(Y.set("Accept","text/event-stream"),X)Y.set("last-event-id",X);let W=await(this._fetch??fetch)(this._url,{method:"GET",headers:Y,signal:this._abortController?.signal});if(!W.ok){if(await W.body?.cancel(),W.status===401&&this._authProvider)return await this._authThenStart();if(W.status===405)return;throw new K9(W.status,`Failed to open SSE stream: ${W.statusText}`)}this._handleSseStream(W.body,Q,!0)}catch(Y){throw this.onerror?.(Y),Y}}_getNextReconnectionDelay(Q){if(this._serverRetryMs!==void 0)return this._serverRetryMs;let X=this._reconnectionOptions.initialReconnectionDelay,Y=this._reconnectionOptions.reconnectionDelayGrowFactor,W=this._reconnectionOptions.maxReconnectionDelay;return Math.min(X*Math.pow(Y,Q),W)}_scheduleReconnection(Q,X=0){let Y=this._reconnectionOptions.maxRetries;if(X>=Y){this.onerror?.(Error(`Maximum reconnection attempts (${Y}) exceeded.`));return}let W=this._getNextReconnectionDelay(X);this._reconnectionTimeout=setTimeout(()=>{this._startOrAuthSse(Q).catch(($)=>{this.onerror?.(Error(`Failed to reconnect SSE stream: ${$ instanceof Error?$.message:String($)}`)),this._scheduleReconnection(Q,X+1)})},W)}_handleSseStream(Q,X,Y){if(!Q)return;let{onresumptiontoken:W,replayMessageId:$}=X,J,G=!1,H=!1;(async()=>{try{let z=Q.pipeThrough(new TextDecoderStream).pipeThrough(new hJ({onRetry:(D)=>{this._serverRetryMs=D}})).getReader();while(!0){let{value:D,done:F}=await z.read();if(F)break;if(D.id)J=D.id,G=!0,W?.(D.id);if(!D.data)continue;if(!D.event||D.event==="message")try{let q=v9.parse(JSON.parse(D.data));if(t6(q)){if(H=!0,$!==void 0)q.id=$}this.onmessage?.(q)}catch(q){this.onerror?.(q)}}if((Y||G)&&!H&&this._abortController&&!this._abortController.signal.aborted)this._scheduleReconnection({resumptionToken:J,onresumptiontoken:W,replayMessageId:$},0)}catch(z){if(this.onerror?.(Error(`SSE stream disconnected: ${z}`)),(Y||G)&&!H&&this._abortController&&!this._abortController.signal.aborted)try{this._scheduleReconnection({resumptionToken:J,onresumptiontoken:W,replayMessageId:$},0)}catch(D){this.onerror?.(Error(`Failed to reconnect: ${D instanceof Error?D.message:String(D)}`))}}})()}async start(){if(this._abortController)throw Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");this._abortController=new AbortController}async finishAuth(Q){if(!this._authProvider)throw new y6("No auth provider");if(await m8(this._authProvider,{serverUrl:this._url,authorizationCode:Q,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})!=="AUTHORIZED")throw new y6("Failed to authorize")}async close(){if(this._reconnectionTimeout)clearTimeout(this._reconnectionTimeout),this._reconnectionTimeout=void 0;this._abortController?.abort(),this.onclose?.()}async send(Q,X){try{let{resumptionToken:Y,onresumptiontoken:W}=X||{};if(Y){this._startOrAuthSse({resumptionToken:Y,replayMessageId:v4(Q)?Q.id:void 0}).catch((V)=>this.onerror?.(V));return}let $=await this._commonHeaders();$.set("content-type","application/json"),$.set("accept","application/json, text/event-stream");let J={...this._requestInit,method:"POST",headers:$,body:JSON.stringify(Q),signal:this._abortController?.signal},G=await(this._fetch??fetch)(this._url,J),H=G.headers.get("mcp-session-id");if(H)this._sessionId=H;if(!G.ok){let V=await G.text().catch(()=>null);if(G.status===401&&this._authProvider){if(this._hasCompletedAuthFlow)throw new K9(401,"Server returned 401 after successful authentication");let{resourceMetadataUrl:D,scope:F}=xJ(G);if(this._resourceMetadataUrl=D,this._scope=F,await m8(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})!=="AUTHORIZED")throw new y6;return this._hasCompletedAuthFlow=!0,this.send(Q)}if(G.status===403&&this._authProvider){let{resourceMetadataUrl:D,scope:F,error:q}=xJ(G);if(q==="insufficient_scope"){let A=G.headers.get("WWW-Authenticate");if(this._lastUpscopingHeader===A)throw new K9(403,"Server returned 403 after trying upscoping");if(F)this._scope=F;if(D)this._resourceMetadataUrl=D;if(this._lastUpscopingHeader=A??void 0,await m8(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetch})!=="AUTHORIZED")throw new y6;return this.send(Q)}}throw new K9(G.status,`Error POSTing to endpoint: ${V}`)}if(this._hasCompletedAuthFlow=!1,this._lastUpscopingHeader=void 0,G.status===202){if(await G.body?.cancel(),G3(Q))this._startOrAuthSse({resumptionToken:void 0}).catch((V)=>this.onerror?.(V));return}let z=(Array.isArray(Q)?Q:[Q]).filter((V)=>("method"in V)&&("id"in V)&&V.id!==void 0).length>0,K=G.headers.get("content-type");if(z)if(K?.includes("text/event-stream"))this._handleSseStream(G.body,{onresumptiontoken:W},!1);else if(K?.includes("application/json")){let V=await G.json(),D=Array.isArray(V)?V.map((F)=>v9.parse(F)):[v9.parse(V)];for(let F of D)this.onmessage?.(F)}else throw await G.body?.cancel(),new K9(-1,`Unexpected content type: ${K}`);else await G.body?.cancel()}catch(Y){throw this.onerror?.(Y),Y}}get sessionId(){return this._sessionId}async terminateSession(){if(!this._sessionId)return;try{let Q=await this._commonHeaders(),X={...this._requestInit,method:"DELETE",headers:Q,signal:this._abortController?.signal},Y=await(this._fetch??fetch)(this._url,X);if(await Y.body?.cancel(),!Y.ok&&Y.status!==405)throw new K9(Y.status,`Failed to terminate session: ${Y.statusText}`);this._sessionId=void 0}catch(Q){throw this.onerror?.(Q),Q}}setProtocolVersion(Q){this._protocolVersion=Q}get protocolVersion(){return this._protocolVersion}async resumeStream(Q,X){await this._startOrAuthSse({resumptionToken:Q,onresumptiontoken:X?.onresumptiontoken})}}var Jg=1000,Gg=180000,Hg=2,Bg=0.1,XO=120000,zg=0.1,Kg=60000,Fg=3;class uJ{name;url;onStatusChange;onNotification;onLog;onSamplingRequest;onElicitationRequest;onReconnecting;onReconnected;onHealthDegraded;onHealthRestored;client=null;transport=null;status="disconnected";errorMessage;capabilities;isClosing=!1;reconnectAttempt=0;reconnectTimeoutHandle=null;nextRetryMs=0;isReconnecting=!1;healthCheckIntervalHandle=null;consecutiveHealthFailures=0;healthStatus="healthy";constructor(Q){this.name=Q.name,this.url=Q.url,this.onStatusChange=Q.onStatusChange,this.onNotification=Q.onNotification,this.onLog=Q.onLog,this.onSamplingRequest=Q.onSamplingRequest,this.onElicitationRequest=Q.onElicitationRequest,this.onReconnecting=Q.onReconnecting,this.onReconnected=Q.onReconnected,this.onHealthDegraded=Q.onHealthDegraded,this.onHealthRestored=Q.onHealthRestored}getInfo(){let Q={name:this.name,url:this.url,status:this.status};if(this.errorMessage!==void 0)Q.error=this.errorMessage;if(this.capabilities!==void 0)Q.capabilities=this.capabilities;return Q}getName(){return this.name}getStatus(){return this.status}isConnected(){return this.status==="connected"}getReconnectionState(){if(this.status!=="reconnecting")return null;return{attempt:this.reconnectAttempt,nextRetryMs:this.nextRetryMs}}getHealthStatus(){return this.healthStatus}getConsecutiveHealthFailures(){return this.consecutiveHealthFailures}async connect(){if(this.status==="connected"||this.status==="connecting")return;this.setStatus("connecting");try{this.transport=new SY(new URL(this.url)),this.client=new AY({name:"emceepee",version:"0.1.0"},{capabilities:{sampling:{},elicitation:{form:{}}}}),this.setupNotificationHandler(),this.transport.onclose=()=>{if(!this.isClosing)this.handleUnexpectedDisconnect()},this.transport.onerror=(X)=>{if((X.name==="AbortError"||X.message.includes("AbortError"))&&(this.isClosing||this.status==="disconnected"))return;this.errorMessage=X.message,this.handleUnexpectedDisconnect()},await this.client.connect(this.transport);let Q=this.client.getServerCapabilities();this.capabilities={tools:Q?.tools!==void 0,resources:Q?.resources!==void 0,prompts:Q?.prompts!==void 0,resourceTemplates:Q?.resources!==void 0},this.setStatus("connected"),this.startHealthChecks()}catch(Q){let X=Q instanceof Error?Q.message:String(Q);throw this.setStatus("error",X),Q}}async disconnect(){if(this.isClosing=!0,this.stopHealthChecks(),this.cancelReconnection(),this.transport)try{await this.transport.close()}catch{}this.client=null,this.transport=null,this.isClosing=!1,this.setStatus("disconnected")}async forceReconnect(){if(this.stopHealthChecks(),this.cancelReconnection(),this.transport){this.isClosing=!0;try{await this.transport.close()}catch{}this.isClosing=!1}this.client=null,this.transport=null,this.reconnectAttempt=0,this.nextRetryMs=0,this.consecutiveHealthFailures=0,this.healthStatus="healthy",await this.connect()}cancelReconnection(){if(this.reconnectTimeoutHandle)clearTimeout(this.reconnectTimeoutHandle),this.reconnectTimeoutHandle=null;this.isReconnecting=!1}async listTools(){let Q=this.getConnectedClient();if(!this.capabilities?.tools)return[];return(await Q.listTools()).tools}async callTool(Q,X={}){return await this.getConnectedClient().callTool({name:Q,arguments:X})}async listResources(){let Q=this.getConnectedClient();if(!this.capabilities?.resources)return[];return(await Q.listResources()).resources}async listResourceTemplates(){let Q=this.getConnectedClient();if(!this.capabilities?.resourceTemplates)return[];return(await Q.listResourceTemplates()).resourceTemplates}async readResource(Q){return await this.getConnectedClient().readResource({uri:Q})}async listPrompts(){let Q=this.getConnectedClient();if(!this.capabilities?.prompts)return[];return(await Q.listPrompts()).prompts}async getPrompt(Q,X={}){return await this.getConnectedClient().getPrompt({name:Q,arguments:X})}setupNotificationHandler(){if(!this.client)return;this.client.setNotificationHandler(m4,()=>{this.emitNotification("notifications/tools/list_changed")}),this.client.setNotificationHandler(u4,()=>{this.emitNotification("notifications/resources/list_changed")}),this.client.setNotificationHandler(Y5,(Q)=>{this.emitNotification("notifications/resources/updated",Q.params)}),this.client.setNotificationHandler(l4,()=>{this.emitNotification("notifications/prompts/list_changed")}),this.client.setNotificationHandler(F5,(Q)=>{if(this.onLog)this.onLog({server:this.name,timestamp:new Date,level:Q.params.level,logger:Q.params.logger,data:Q.params.data})}),this.client.setRequestHandler(p4,(Q)=>{return new Promise((X,Y)=>{if(this.onSamplingRequest)this.onSamplingRequest({id:crypto.randomUUID(),server:this.name,timestamp:new Date,params:Q.params,resolve:X,reject:Y});else Y(Error("Sampling not supported: no handler registered"))})}),this.client.setRequestHandler(i4,(Q)=>{return new Promise((X,Y)=>{if(this.onElicitationRequest)this.onElicitationRequest({id:crypto.randomUUID(),server:this.name,timestamp:new Date,params:Q.params,resolve:X,reject:Y});else Y(Error("Elicitation not supported: no handler registered"))})})}emitNotification(Q,X){if(this.onNotification!==void 0)this.onNotification({server:this.name,timestamp:new Date,method:Q,params:X})}handleUnexpectedDisconnect(){if(this.isReconnecting||this.isClosing)return;this.stopHealthChecks(),this.client=null,this.transport=null,this.isReconnecting=!0,this.reconnectAttempt=0,this.setStatus("reconnecting",this.errorMessage),this.scheduleReconnection()}scheduleReconnection(){if(this.isClosing)return;this.reconnectAttempt++;let Q=this.calculateBackoff(this.reconnectAttempt);if(this.nextRetryMs=Q,this.onReconnecting)this.onReconnecting(this.reconnectAttempt,Q);this.reconnectTimeoutHandle=setTimeout(()=>{this.attemptReconnection()},Q)}async attemptReconnection(){if(this.isClosing)return;try{this.transport=new SY(new URL(this.url)),this.client=new AY({name:"emceepee",version:"0.1.0"},{capabilities:{sampling:{},elicitation:{form:{}}}}),this.setupNotificationHandler(),this.transport.onclose=()=>{if(!this.isClosing)this.handleUnexpectedDisconnect()},this.transport.onerror=(Y)=>{if((Y.name==="AbortError"||Y.message.includes("AbortError"))&&(this.isClosing||this.status==="disconnected"))return;this.errorMessage=Y.message,this.handleUnexpectedDisconnect()},await this.client.connect(this.transport);let Q=this.client.getServerCapabilities();this.capabilities={tools:Q?.tools!==void 0,resources:Q?.resources!==void 0,prompts:Q?.prompts!==void 0,resourceTemplates:Q?.resources!==void 0};let X=this.reconnectAttempt;if(this.reconnectAttempt=0,this.nextRetryMs=0,this.isReconnecting=!1,this.consecutiveHealthFailures=0,this.healthStatus="healthy",this.setStatus("connected"),this.onReconnected)this.onReconnected(X);this.startHealthChecks()}catch(Q){this.client=null,this.transport=null,this.errorMessage=Q instanceof Error?Q.message:String(Q),this.scheduleReconnection()}}calculateBackoff(Q){let X=Jg*Math.pow(Hg,Q-1),Y=Math.min(X,Gg),W=Y*Bg*(Math.random()*2-1);return Math.round(Y+W)}startHealthChecks(){this.stopHealthChecks();let Q=()=>{let X=XO*zg*(Math.random()*2-1),Y=Math.round(XO+X);this.healthCheckIntervalHandle=setTimeout(()=>{this.performHealthCheck(),Q()},Y)};Q()}stopHealthChecks(){if(this.healthCheckIntervalHandle)clearTimeout(this.healthCheckIntervalHandle),this.healthCheckIntervalHandle=null}async performHealthCheck(){if(!this.client||!this.isConnected())return;try{let Q=new AbortController,X=setTimeout(()=>{Q.abort()},Kg);try{if(await this.client.listTools(),clearTimeout(X),this.consecutiveHealthFailures>0){let Y=this.healthStatus==="degraded";if(this.consecutiveHealthFailures=0,this.healthStatus="healthy",Y&&this.onHealthRestored)this.onHealthRestored()}}catch(Y){throw clearTimeout(X),Y}}catch(Q){this.consecutiveHealthFailures++;let X=Q instanceof Error?Q.message:String(Q);if(this.consecutiveHealthFailures>=Fg&&this.healthStatus!=="degraded"){if(this.healthStatus="degraded",this.onHealthDegraded)this.onHealthDegraded(this.consecutiveHealthFailures,X)}}}setStatus(Q,X){if(this.status=Q,Q==="error"&&X!==void 0)this.errorMessage=X;else this.errorMessage=void 0;if(this.onStatusChange!==void 0)this.onStatusChange(Q,X)}getConnectedClient(){if(!this.isConnected()||!this.client)throw Error(`Client '${this.name}' is not connected`);return this.client}}var Vg={sessionTimeoutMs:1800000,cleanupIntervalMs:300000};class lJ{sessions=new Map;serverConfigs;config;logger;cleanupIntervalHandle=null;constructor(Q={}){this.config={...Vg,...Q},this.logger=Q.logger;let X={logger:Q.logger};this.serverConfigs=new MJ(X),this.startCleanupInterval()}async createSession(){let Q=B6(),X=new RJ(Q,this.config.sessionStateConfig,this.logger);this.sessions.set(Q,X),this.logger?.info("session_created",{sessionId:Q});let Y=this.serverConfigs.listConfigs();for(let W of Y)try{await this.connectSessionToServer(X,W)}catch($){this.logger?.warn("session_auto_connect_failed",{sessionId:Q,server:W.name,error:$ instanceof Error?$.message:String($)})}return X}getSession(Q){return this.sessions.get(Q)}touchSession(Q){this.sessions.get(Q)?.touch()}async destroySession(Q){let X=this.sessions.get(Q);if(!X)return;this.logger?.info("session_destroying",{sessionId:Q}),await X.cleanup(),this.sessions.delete(Q),this.logger?.info("session_destroyed",{sessionId:Q})}async addServer(Q,X,Y){let W=this.sessions.get(Q);if(!W)throw Error(`Session '${Q}' not found`);let $=this.serverConfigs.addConfig(X,Y,Q),J=this.serverConfigs.getConfig(X);if(!J)throw Error(`Failed to add server config for '${X}'`);let G=await this.connectSessionToServer(W,J);if($){for(let[H,B]of this.sessions)if(H!==Q)B.eventSystem.addEvent("server_added",X,{name:X,url:Y,addedBy:Q})}return G}async removeServer(Q,X){if(this.logger?.debug("removeServer_start",{sessionId:Q,serverName:X}),!this.serverConfigs.removeConfig(X))throw this.logger?.debug("removeServer_not_found",{sessionId:Q,serverName:X}),Error(`Server '${X}' not found`);for(let[W,$]of this.sessions)this.logger?.debug("removeServer_disconnecting_session",{sessionId:W,serverName:X,hasConnection:$.getConnection(X)!==void 0}),await this.disconnectSessionFromServer($,X),$.eventSystem.addEvent("server_removed",X,{name:X,removedBy:Q});this.logger?.debug("removeServer_complete",{sessionId:Q,serverName:X})}listServers(Q){let X=this.sessions.get(Q);return this.serverConfigs.listConfigs().map((W)=>{let $=X?.getConnection(W.name),J={name:W.name,url:W.url,connected:$?.status==="connected",status:$?.status??"not_connected",connectedAt:$?.connectedAt,lastError:$?.lastError};if($?.status==="reconnecting"){let G=$.client.getReconnectionState();if(G)J.reconnectAttempt=G.attempt,J.nextRetryMs=G.nextRetryMs}if($?.status==="connected"){J.healthStatus=$.client.getHealthStatus();let G=$.client.getConsecutiveHealthFailures();if(G>0)J.consecutiveHealthFailures=G}return J})}async reconnectServer(Q,X){let Y=this.sessions.get(Q);if(!Y)throw Error(`Session '${Q}' not found`);let W=this.serverConfigs.getConfig(X);if(!W)throw Error(`Server '${X}' not found`);let $=Y.getConnection(X);if(!$){await this.connectSessionToServer(Y,W);return}this.handleBackendDisconnect(Y,X),await $.client.forceReconnect(),Y.setConnectionStatus(X,"connected"),Y.eventSystem.addEvent("server_reconnected",X,{name:X,attemptsTaken:0,forced:!0}),this.logger?.info("session_server_force_reconnected",{sessionId:Y.sessionId,server:X})}async getOrCreateConnection(Q,X){let Y=this.sessions.get(Q);if(!Y)throw Error(`Session '${Q}' not found`);let W=Y.getConnection(X);if(W?.status==="connected")return W.client;let $=this.serverConfigs.getConfig(X);if(!$)throw Error(`Server '${X}' not found`);return(await this.connectSessionToServer(Y,$)).client}getConnectedClient(Q,X){let Y=this.sessions.get(Q);if(!Y)return;let W=Y.getConnection(X);if(W?.status==="connected")return W.client;return}async connectSessionToServer(Q,X){let Y=Q.backendConnections.get(X.name);if(Y?.status==="connected")return Y;if(Y?.status==="connecting")throw Error(`Already connecting to '${X.name}'`);let W=new uJ({name:X.name,url:X.url,onStatusChange:($,J)=>{Q.setConnectionStatus(X.name,$,J)},onNotification:($)=>{Q.bufferManager.addNotification($)},onLog:($)=>{Q.bufferManager.addLog($)},onSamplingRequest:($)=>{Q.pendingRequests.addSamplingRequest($.server,$.params,$.resolve,$.reject)},onElicitationRequest:($)=>{Q.pendingRequests.addElicitationRequest($.server,$.params,$.resolve,$.reject)},onReconnecting:($,J)=>{if($===1)this.handleBackendDisconnect(Q,X.name);Q.eventSystem.addEvent("server_reconnecting",X.name,{name:X.name,attempt:$,nextRetryMs:J}),this.logger?.debug("session_server_reconnecting",{sessionId:Q.sessionId,server:X.name,attempt:$,nextRetryMs:J})},onReconnected:($)=>{Q.setConnectionStatus(X.name,"connected"),Q.eventSystem.addEvent("server_reconnected",X.name,{name:X.name,attemptsTaken:$}),this.logger?.info("session_server_reconnected",{sessionId:Q.sessionId,server:X.name,attemptsTaken:$})},onHealthDegraded:($,J)=>{Q.eventSystem.addEvent("server_health_degraded",X.name,{name:X.name,consecutiveFailures:$,lastError:J}),this.logger?.warn("session_server_health_degraded",{sessionId:Q.sessionId,server:X.name,consecutiveFailures:$,lastError:J})},onHealthRestored:()=>{Q.eventSystem.addEvent("server_health_restored",X.name,{name:X.name}),this.logger?.info("session_server_health_restored",{sessionId:Q.sessionId,server:X.name})}});Q.addConnection(X.name,W);try{await W.connect(),Q.setConnectionStatus(X.name,"connected"),Q.eventSystem.addEvent("server_connected",X.name,{name:X.name,capabilities:W.getInfo().capabilities}),this.logger?.info("session_server_connected",{sessionId:Q.sessionId,server:X.name});let $=Q.getConnection(X.name);if(!$)throw Error("Connection not found after connect");return $}catch($){let J=$ instanceof Error?$.message:String($);throw Q.setConnectionStatus(X.name,"error",J),this.logger?.warn("session_server_connect_failed",{sessionId:Q.sessionId,server:X.name,error:J}),$}}async disconnectSessionFromServer(Q,X){let Y=Q.getConnection(X);if(!Y){this.logger?.debug("disconnectSessionFromServer_no_connection",{sessionId:Q.sessionId,serverName:X});return}this.logger?.debug("disconnectSessionFromServer_start",{sessionId:Q.sessionId,serverName:X,connectionStatus:Y.status});let W=Q.taskManager.getTasksForServer(X);for(let $ of W)if($.status==="working")Q.taskManager.failTask($.taskId,"Server removed");Q.pendingRequests.rejectRequestsForServer(X,"Server removed");try{this.logger?.debug("disconnectSessionFromServer_calling_disconnect",{sessionId:Q.sessionId,serverName:X}),await Y.client.disconnect(),this.logger?.debug("disconnectSessionFromServer_disconnect_complete",{sessionId:Q.sessionId,serverName:X})}catch($){this.logger?.debug("disconnectSessionFromServer_disconnect_error",{sessionId:Q.sessionId,serverName:X,error:$ instanceof Error?$.message:String($)})}Q.removeConnection(X),this.logger?.debug("disconnectSessionFromServer_complete",{sessionId:Q.sessionId,serverName:X})}handleBackendDisconnect(Q,X){let Y=Q.taskManager.getTasksForServer(X);for(let W of Y)if(W.status==="working")Q.taskManager.failTask(W.taskId,"Server disconnected");Q.pendingRequests.rejectRequestsForServer(X,"Server disconnected"),Q.eventSystem.addEvent("server_disconnected",X,{name:X}),this.logger?.info("session_server_disconnected",{sessionId:Q.sessionId,server:X})}startCleanupInterval(){this.cleanupIntervalHandle=setInterval(()=>{this.runSessionCleanup()},this.config.cleanupIntervalMs)}runSessionCleanup(){let Q=Date.now(),X=[];for(let[Y,W]of this.sessions)if(Q-W.lastActivityAt.getTime()>=this.config.sessionTimeoutMs)X.push(Y);for(let Y of X)this.logger?.info("session_timeout_cleanup",{sessionId:Y}),this.destroySession(Y)}async shutdown(){if(this.logger?.info("session_manager_shutdown_start",{sessionCount:this.sessions.size}),this.cleanupIntervalHandle)clearInterval(this.cleanupIntervalHandle),this.cleanupIntervalHandle=null;for(let Q of Array.from(this.sessions.keys()))await this.destroySession(Q);this.logger?.info("session_manager_shutdown_complete",{})}getServerConfigs(){return this.serverConfigs}listSessionIds(){return Array.from(this.sessions.keys())}get sessionCount(){return this.sessions.size}}var YO={debug:0,info:1,warn:2,error:3};function WO(){let Q=()=>{};return{debug:Q,info:Q,warn:Q,error:Q}}function $O(Q="info",X){let Y=YO[Q],W=p8("fs"),$=(J,G,H)=>{if(YO[J]<Y)return;let B=new Date().toISOString(),z=H?` ${JSON.stringify(H)}`:"",K=`[${B}] ${J.toUpperCase()} ${G}${z}
|
|
53
|
+
`&&W++}}return[X,Y]}class hJ extends TransformStream{constructor({onError:Q,onRetry:X,onComment:Y}={}){let W;super({start($){W=QO({onEvent:(J)=>{$.enqueue(J)},onError(J){Q==="terminate"?$.error(J):typeof Q=="function"&&Q(J)},onRetry:X,onComment:Y})},transform($){W.feed($)}})}}var $g={initialReconnectionDelay:1000,maxReconnectionDelay:30000,reconnectionDelayGrowFactor:1.5,maxRetries:2};class K9 extends Error{constructor(Q,X){super(`Streamable HTTP error: ${X}`);this.code=Q}}class SY{constructor(Q,X){this._hasCompletedAuthFlow=!1,this._url=Q,this._resourceMetadataUrl=void 0,this._scope=void 0,this._requestInit=X?.requestInit,this._authProvider=X?.authProvider,this._fetch=X?.fetch,this._fetchWithInit=lL(X?.fetch,X?.requestInit),this._sessionId=X?.sessionId,this._reconnectionOptions=X?.reconnectionOptions??$g}async _authThenStart(){if(!this._authProvider)throw new y6("No auth provider");let Q;try{Q=await m8(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})}catch(X){throw this.onerror?.(X),X}if(Q!=="AUTHORIZED")throw new y6;return await this._startOrAuthSse({resumptionToken:void 0})}async _commonHeaders(){let Q={};if(this._authProvider){let Y=await this._authProvider.tokens();if(Y)Q.Authorization=`Bearer ${Y.access_token}`}if(this._sessionId)Q["mcp-session-id"]=this._sessionId;if(this._protocolVersion)Q["mcp-protocol-version"]=this._protocolVersion;let X=DY(this._requestInit?.headers);return new Headers({...Q,...X})}async _startOrAuthSse(Q){let{resumptionToken:X}=Q;try{let Y=await this._commonHeaders();if(Y.set("Accept","text/event-stream"),X)Y.set("last-event-id",X);let W=await(this._fetch??fetch)(this._url,{method:"GET",headers:Y,signal:this._abortController?.signal});if(!W.ok){if(await W.body?.cancel(),W.status===401&&this._authProvider)return await this._authThenStart();if(W.status===405)return;throw new K9(W.status,`Failed to open SSE stream: ${W.statusText}`)}this._handleSseStream(W.body,Q,!0)}catch(Y){throw this.onerror?.(Y),Y}}_getNextReconnectionDelay(Q){if(this._serverRetryMs!==void 0)return this._serverRetryMs;let X=this._reconnectionOptions.initialReconnectionDelay,Y=this._reconnectionOptions.reconnectionDelayGrowFactor,W=this._reconnectionOptions.maxReconnectionDelay;return Math.min(X*Math.pow(Y,Q),W)}_scheduleReconnection(Q,X=0){let Y=this._reconnectionOptions.maxRetries;if(X>=Y){this.onerror?.(Error(`Maximum reconnection attempts (${Y}) exceeded.`));return}let W=this._getNextReconnectionDelay(X);this._reconnectionTimeout=setTimeout(()=>{this._startOrAuthSse(Q).catch(($)=>{this.onerror?.(Error(`Failed to reconnect SSE stream: ${$ instanceof Error?$.message:String($)}`)),this._scheduleReconnection(Q,X+1)})},W)}_handleSseStream(Q,X,Y){if(!Q)return;let{onresumptiontoken:W,replayMessageId:$}=X,J,G=!1,H=!1;(async()=>{try{let z=Q.pipeThrough(new TextDecoderStream).pipeThrough(new hJ({onRetry:(D)=>{this._serverRetryMs=D}})).getReader();while(!0){let{value:D,done:F}=await z.read();if(F)break;if(D.id)J=D.id,G=!0,W?.(D.id);if(!D.data)continue;if(!D.event||D.event==="message")try{let q=v9.parse(JSON.parse(D.data));if(t6(q)){if(H=!0,$!==void 0)q.id=$}this.onmessage?.(q)}catch(q){this.onerror?.(q)}}if((Y||G)&&!H&&this._abortController&&!this._abortController.signal.aborted)this._scheduleReconnection({resumptionToken:J,onresumptiontoken:W,replayMessageId:$},0)}catch(z){if(this.onerror?.(Error(`SSE stream disconnected: ${z}`)),(Y||G)&&!H&&this._abortController&&!this._abortController.signal.aborted)try{this._scheduleReconnection({resumptionToken:J,onresumptiontoken:W,replayMessageId:$},0)}catch(D){this.onerror?.(Error(`Failed to reconnect: ${D instanceof Error?D.message:String(D)}`))}}})()}async start(){if(this._abortController)throw Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");this._abortController=new AbortController}async finishAuth(Q){if(!this._authProvider)throw new y6("No auth provider");if(await m8(this._authProvider,{serverUrl:this._url,authorizationCode:Q,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})!=="AUTHORIZED")throw new y6("Failed to authorize")}async close(){if(this._reconnectionTimeout)clearTimeout(this._reconnectionTimeout),this._reconnectionTimeout=void 0;this._abortController?.abort(),this.onclose?.()}async send(Q,X){try{let{resumptionToken:Y,onresumptiontoken:W}=X||{};if(Y){this._startOrAuthSse({resumptionToken:Y,replayMessageId:v4(Q)?Q.id:void 0}).catch((V)=>this.onerror?.(V));return}let $=await this._commonHeaders();$.set("content-type","application/json"),$.set("accept","application/json, text/event-stream");let J={...this._requestInit,method:"POST",headers:$,body:JSON.stringify(Q),signal:this._abortController?.signal},G=await(this._fetch??fetch)(this._url,J),H=G.headers.get("mcp-session-id");if(H)this._sessionId=H;if(!G.ok){let V=await G.text().catch(()=>null);if(G.status===401&&this._authProvider){if(this._hasCompletedAuthFlow)throw new K9(401,"Server returned 401 after successful authentication");let{resourceMetadataUrl:D,scope:F}=xJ(G);if(this._resourceMetadataUrl=D,this._scope=F,await m8(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})!=="AUTHORIZED")throw new y6;return this._hasCompletedAuthFlow=!0,this.send(Q)}if(G.status===403&&this._authProvider){let{resourceMetadataUrl:D,scope:F,error:q}=xJ(G);if(q==="insufficient_scope"){let A=G.headers.get("WWW-Authenticate");if(this._lastUpscopingHeader===A)throw new K9(403,"Server returned 403 after trying upscoping");if(F)this._scope=F;if(D)this._resourceMetadataUrl=D;if(this._lastUpscopingHeader=A??void 0,await m8(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetch})!=="AUTHORIZED")throw new y6;return this.send(Q)}}throw new K9(G.status,`Error POSTing to endpoint: ${V}`)}if(this._hasCompletedAuthFlow=!1,this._lastUpscopingHeader=void 0,G.status===202){if(await G.body?.cancel(),G3(Q))this._startOrAuthSse({resumptionToken:void 0}).catch((V)=>this.onerror?.(V));return}let z=(Array.isArray(Q)?Q:[Q]).filter((V)=>("method"in V)&&("id"in V)&&V.id!==void 0).length>0,K=G.headers.get("content-type");if(z)if(K?.includes("text/event-stream"))this._handleSseStream(G.body,{onresumptiontoken:W},!1);else if(K?.includes("application/json")){let V=await G.json(),D=Array.isArray(V)?V.map((F)=>v9.parse(F)):[v9.parse(V)];for(let F of D)this.onmessage?.(F)}else throw await G.body?.cancel(),new K9(-1,`Unexpected content type: ${K}`);else await G.body?.cancel()}catch(Y){throw this.onerror?.(Y),Y}}get sessionId(){return this._sessionId}async terminateSession(){if(!this._sessionId)return;try{let Q=await this._commonHeaders(),X={...this._requestInit,method:"DELETE",headers:Q,signal:this._abortController?.signal},Y=await(this._fetch??fetch)(this._url,X);if(await Y.body?.cancel(),!Y.ok&&Y.status!==405)throw new K9(Y.status,`Failed to terminate session: ${Y.statusText}`);this._sessionId=void 0}catch(Q){throw this.onerror?.(Q),Q}}setProtocolVersion(Q){this._protocolVersion=Q}get protocolVersion(){return this._protocolVersion}async resumeStream(Q,X){await this._startOrAuthSse({resumptionToken:Q,onresumptiontoken:X?.onresumptiontoken})}}var Jg=1000,Gg=180000,Hg=2,Bg=0.1,XO=120000,zg=0.1,Kg=60000,Fg=3;class uJ{name;url;onStatusChange;onNotification;onLog;onSamplingRequest;onElicitationRequest;onReconnecting;onReconnected;onHealthDegraded;onHealthRestored;client=null;transport=null;status="disconnected";errorMessage;capabilities;isClosing=!1;reconnectAttempt=0;reconnectTimeoutHandle=null;nextRetryMs=0;isReconnecting=!1;healthCheckIntervalHandle=null;consecutiveHealthFailures=0;healthStatus="healthy";constructor(Q){this.name=Q.name,this.url=Q.url,this.onStatusChange=Q.onStatusChange,this.onNotification=Q.onNotification,this.onLog=Q.onLog,this.onSamplingRequest=Q.onSamplingRequest,this.onElicitationRequest=Q.onElicitationRequest,this.onReconnecting=Q.onReconnecting,this.onReconnected=Q.onReconnected,this.onHealthDegraded=Q.onHealthDegraded,this.onHealthRestored=Q.onHealthRestored}getInfo(){let Q={name:this.name,url:this.url,status:this.status};if(this.errorMessage!==void 0)Q.error=this.errorMessage;if(this.capabilities!==void 0)Q.capabilities=this.capabilities;return Q}getName(){return this.name}getStatus(){return this.status}isConnected(){return this.status==="connected"}getReconnectionState(){if(this.status!=="reconnecting")return null;return{attempt:this.reconnectAttempt,nextRetryMs:this.nextRetryMs}}getHealthStatus(){return this.healthStatus}getConsecutiveHealthFailures(){return this.consecutiveHealthFailures}async connect(){if(this.status==="connected"||this.status==="connecting")return;this.setStatus("connecting");try{this.transport=new SY(new URL(this.url)),this.client=new AY({name:"emceepee",version:"0.1.0"},{capabilities:{sampling:{},elicitation:{form:{}}}}),this.setupNotificationHandler(),this.transport.onclose=()=>{if(!this.isClosing)this.handleUnexpectedDisconnect()},this.transport.onerror=(X)=>{if((X.name==="AbortError"||X.message.includes("AbortError"))&&(this.isClosing||this.status==="disconnected"))return;this.errorMessage=X.message,this.handleUnexpectedDisconnect()},await this.client.connect(this.transport);let Q=this.client.getServerCapabilities();this.capabilities={tools:Q?.tools!==void 0,resources:Q?.resources!==void 0,prompts:Q?.prompts!==void 0,resourceTemplates:Q?.resources!==void 0},this.setStatus("connected"),this.startHealthChecks()}catch(Q){let X=Q instanceof Error?Q.message:String(Q);throw this.setStatus("error",X),Q}}async disconnect(){if(this.isClosing=!0,this.stopHealthChecks(),this.cancelReconnection(),this.transport)try{await this.transport.close()}catch{}this.client=null,this.transport=null,this.isClosing=!1,this.setStatus("disconnected")}async forceReconnect(){if(this.stopHealthChecks(),this.cancelReconnection(),this.transport){this.isClosing=!0;try{await this.transport.close()}catch{}this.isClosing=!1}this.client=null,this.transport=null,this.reconnectAttempt=0,this.nextRetryMs=0,this.consecutiveHealthFailures=0,this.healthStatus="healthy",await this.connect()}cancelReconnection(){if(this.reconnectTimeoutHandle)clearTimeout(this.reconnectTimeoutHandle),this.reconnectTimeoutHandle=null;this.isReconnecting=!1}async listTools(){let Q=this.getConnectedClient();if(!this.capabilities?.tools)return[];return(await Q.listTools()).tools}async callTool(Q,X={}){return await this.getConnectedClient().callTool({name:Q,arguments:X})}async listResources(){let Q=this.getConnectedClient();if(!this.capabilities?.resources)return[];return(await Q.listResources()).resources}async listResourceTemplates(){let Q=this.getConnectedClient();if(!this.capabilities?.resourceTemplates)return[];return(await Q.listResourceTemplates()).resourceTemplates}async readResource(Q){return await this.getConnectedClient().readResource({uri:Q})}async listPrompts(){let Q=this.getConnectedClient();if(!this.capabilities?.prompts)return[];return(await Q.listPrompts()).prompts}async getPrompt(Q,X={}){return await this.getConnectedClient().getPrompt({name:Q,arguments:X})}setupNotificationHandler(){if(!this.client)return;this.client.setNotificationHandler(m4,()=>{this.emitNotification("notifications/tools/list_changed")}),this.client.setNotificationHandler(u4,()=>{this.emitNotification("notifications/resources/list_changed")}),this.client.setNotificationHandler(Y5,(Q)=>{this.emitNotification("notifications/resources/updated",Q.params)}),this.client.setNotificationHandler(l4,()=>{this.emitNotification("notifications/prompts/list_changed")}),this.client.setNotificationHandler(F5,(Q)=>{if(this.onLog)this.onLog({server:this.name,timestamp:new Date,level:Q.params.level,logger:Q.params.logger,data:Q.params.data})}),this.client.setRequestHandler(p4,(Q)=>{return new Promise((X,Y)=>{if(this.onSamplingRequest)this.onSamplingRequest({id:crypto.randomUUID(),server:this.name,timestamp:new Date,params:Q.params,resolve:X,reject:Y});else Y(Error("Sampling not supported: no handler registered"))})}),this.client.setRequestHandler(i4,(Q)=>{return new Promise((X,Y)=>{if(this.onElicitationRequest)this.onElicitationRequest({id:crypto.randomUUID(),server:this.name,timestamp:new Date,params:Q.params,resolve:X,reject:Y});else Y(Error("Elicitation not supported: no handler registered"))})})}emitNotification(Q,X){if(this.onNotification!==void 0)this.onNotification({server:this.name,timestamp:new Date,method:Q,params:X})}handleUnexpectedDisconnect(){if(this.isReconnecting||this.isClosing)return;this.stopHealthChecks(),this.client=null,this.transport=null,this.isReconnecting=!0,this.reconnectAttempt=0,this.setStatus("reconnecting",this.errorMessage),this.scheduleReconnection()}scheduleReconnection(){if(this.isClosing)return;this.reconnectAttempt++;let Q=this.calculateBackoff(this.reconnectAttempt);if(this.nextRetryMs=Q,this.onReconnecting)this.onReconnecting(this.reconnectAttempt,Q);this.reconnectTimeoutHandle=setTimeout(()=>{this.attemptReconnection()},Q)}async attemptReconnection(){if(this.isClosing)return;try{this.transport=new SY(new URL(this.url)),this.client=new AY({name:"emceepee",version:"0.1.0"},{capabilities:{sampling:{},elicitation:{form:{}}}}),this.setupNotificationHandler(),this.transport.onclose=()=>{if(!this.isClosing)this.handleUnexpectedDisconnect()},this.transport.onerror=(Y)=>{if((Y.name==="AbortError"||Y.message.includes("AbortError"))&&(this.isClosing||this.status==="disconnected"))return;this.errorMessage=Y.message,this.handleUnexpectedDisconnect()},await this.client.connect(this.transport);let Q=this.client.getServerCapabilities();this.capabilities={tools:Q?.tools!==void 0,resources:Q?.resources!==void 0,prompts:Q?.prompts!==void 0,resourceTemplates:Q?.resources!==void 0};let X=this.reconnectAttempt;if(this.reconnectAttempt=0,this.nextRetryMs=0,this.isReconnecting=!1,this.consecutiveHealthFailures=0,this.healthStatus="healthy",this.setStatus("connected"),this.onReconnected)this.onReconnected(X);this.startHealthChecks()}catch(Q){this.client=null,this.transport=null,this.errorMessage=Q instanceof Error?Q.message:String(Q),this.scheduleReconnection()}}calculateBackoff(Q){let X=Jg*Math.pow(Hg,Q-1),Y=Math.min(X,Gg),W=Y*Bg*(Math.random()*2-1);return Math.round(Y+W)}startHealthChecks(){this.stopHealthChecks();let Q=()=>{let X=XO*zg*(Math.random()*2-1),Y=Math.round(XO+X);this.healthCheckIntervalHandle=setTimeout(()=>{this.performHealthCheck(),Q()},Y)};Q()}stopHealthChecks(){if(this.healthCheckIntervalHandle)clearTimeout(this.healthCheckIntervalHandle),this.healthCheckIntervalHandle=null}async performHealthCheck(){if(!this.client||!this.isConnected())return;try{let Q=new AbortController,X=setTimeout(()=>{Q.abort()},Kg);try{if(await this.client.listTools(),clearTimeout(X),this.consecutiveHealthFailures>0){let Y=this.healthStatus==="degraded";if(this.consecutiveHealthFailures=0,this.healthStatus="healthy",Y&&this.onHealthRestored)this.onHealthRestored()}}catch(Y){throw clearTimeout(X),Y}}catch(Q){this.consecutiveHealthFailures++;let X=Q instanceof Error?Q.message:String(Q);if(this.consecutiveHealthFailures>=Fg&&this.healthStatus!=="degraded"){if(this.healthStatus="degraded",this.onHealthDegraded)this.onHealthDegraded(this.consecutiveHealthFailures,X)}}}setStatus(Q,X){if(this.status=Q,Q==="error"&&X!==void 0)this.errorMessage=X;else this.errorMessage=void 0;if(this.onStatusChange!==void 0)this.onStatusChange(Q,X)}getConnectedClient(){if(!this.isConnected()||!this.client)throw Error(`Client '${this.name}' is not connected`);return this.client}}var Vg={sessionTimeoutMs:86400000,cleanupIntervalMs:300000};class lJ{sessions=new Map;serverConfigs;config;logger;cleanupIntervalHandle=null;constructor(Q={}){this.config={...Vg,...Q},this.logger=Q.logger;let X={logger:Q.logger};this.serverConfigs=new MJ(X),this.startCleanupInterval()}async createSession(){let Q=B6(),X=new RJ(Q,this.config.sessionStateConfig,this.logger);this.sessions.set(Q,X),this.logger?.info("session_created",{sessionId:Q});let Y=this.serverConfigs.listConfigs();for(let W of Y)try{await this.connectSessionToServer(X,W)}catch($){this.logger?.warn("session_auto_connect_failed",{sessionId:Q,server:W.name,error:$ instanceof Error?$.message:String($)})}return X}getSession(Q){return this.sessions.get(Q)}touchSession(Q){this.sessions.get(Q)?.touch()}async destroySession(Q){let X=this.sessions.get(Q);if(!X)return;this.logger?.info("session_destroying",{sessionId:Q}),await X.cleanup(),this.sessions.delete(Q),this.logger?.info("session_destroyed",{sessionId:Q})}async addServer(Q,X,Y){let W=this.sessions.get(Q);if(!W)throw Error(`Session '${Q}' not found`);let $=this.serverConfigs.addConfig(X,Y,Q),J=this.serverConfigs.getConfig(X);if(!J)throw Error(`Failed to add server config for '${X}'`);let G=await this.connectSessionToServer(W,J);if($){for(let[H,B]of this.sessions)if(H!==Q)B.eventSystem.addEvent("server_added",X,{name:X,url:Y,addedBy:Q})}return G}async removeServer(Q,X){if(this.logger?.debug("removeServer_start",{sessionId:Q,serverName:X}),!this.serverConfigs.removeConfig(X))throw this.logger?.debug("removeServer_not_found",{sessionId:Q,serverName:X}),Error(`Server '${X}' not found`);for(let[W,$]of this.sessions)this.logger?.debug("removeServer_disconnecting_session",{sessionId:W,serverName:X,hasConnection:$.getConnection(X)!==void 0}),await this.disconnectSessionFromServer($,X),$.eventSystem.addEvent("server_removed",X,{name:X,removedBy:Q});this.logger?.debug("removeServer_complete",{sessionId:Q,serverName:X})}listServers(Q){let X=this.sessions.get(Q);return this.serverConfigs.listConfigs().map((W)=>{let $=X?.getConnection(W.name),J={name:W.name,url:W.url,connected:$?.status==="connected",status:$?.status??"not_connected",connectedAt:$?.connectedAt,lastError:$?.lastError};if($?.status==="reconnecting"){let G=$.client.getReconnectionState();if(G)J.reconnectAttempt=G.attempt,J.nextRetryMs=G.nextRetryMs}if($?.status==="connected"){J.healthStatus=$.client.getHealthStatus();let G=$.client.getConsecutiveHealthFailures();if(G>0)J.consecutiveHealthFailures=G}return J})}async reconnectServer(Q,X){let Y=this.sessions.get(Q);if(!Y)throw Error(`Session '${Q}' not found`);let W=this.serverConfigs.getConfig(X);if(!W)throw Error(`Server '${X}' not found`);let $=Y.getConnection(X);if(!$){await this.connectSessionToServer(Y,W);return}this.handleBackendDisconnect(Y,X),await $.client.forceReconnect(),Y.setConnectionStatus(X,"connected"),Y.eventSystem.addEvent("server_reconnected",X,{name:X,attemptsTaken:0,forced:!0}),this.logger?.info("session_server_force_reconnected",{sessionId:Y.sessionId,server:X})}async getOrCreateConnection(Q,X){let Y=this.sessions.get(Q);if(!Y)throw Error(`Session '${Q}' not found`);let W=Y.getConnection(X);if(W?.status==="connected")return W.client;let $=this.serverConfigs.getConfig(X);if(!$)throw Error(`Server '${X}' not found`);return(await this.connectSessionToServer(Y,$)).client}getConnectedClient(Q,X){let Y=this.sessions.get(Q);if(!Y)return;let W=Y.getConnection(X);if(W?.status==="connected")return W.client;return}async connectSessionToServer(Q,X){let Y=Q.backendConnections.get(X.name);if(Y?.status==="connected")return Y;if(Y?.status==="connecting")throw Error(`Already connecting to '${X.name}'`);let W=new uJ({name:X.name,url:X.url,onStatusChange:($,J)=>{Q.setConnectionStatus(X.name,$,J)},onNotification:($)=>{Q.bufferManager.addNotification($)},onLog:($)=>{Q.bufferManager.addLog($)},onSamplingRequest:($)=>{Q.pendingRequests.addSamplingRequest($.server,$.params,$.resolve,$.reject)},onElicitationRequest:($)=>{Q.pendingRequests.addElicitationRequest($.server,$.params,$.resolve,$.reject)},onReconnecting:($,J)=>{if($===1)this.handleBackendDisconnect(Q,X.name);Q.eventSystem.addEvent("server_reconnecting",X.name,{name:X.name,attempt:$,nextRetryMs:J}),this.logger?.debug("session_server_reconnecting",{sessionId:Q.sessionId,server:X.name,attempt:$,nextRetryMs:J})},onReconnected:($)=>{Q.setConnectionStatus(X.name,"connected"),Q.eventSystem.addEvent("server_reconnected",X.name,{name:X.name,attemptsTaken:$}),this.logger?.info("session_server_reconnected",{sessionId:Q.sessionId,server:X.name,attemptsTaken:$})},onHealthDegraded:($,J)=>{Q.eventSystem.addEvent("server_health_degraded",X.name,{name:X.name,consecutiveFailures:$,lastError:J}),this.logger?.warn("session_server_health_degraded",{sessionId:Q.sessionId,server:X.name,consecutiveFailures:$,lastError:J})},onHealthRestored:()=>{Q.eventSystem.addEvent("server_health_restored",X.name,{name:X.name}),this.logger?.info("session_server_health_restored",{sessionId:Q.sessionId,server:X.name})}});Q.addConnection(X.name,W);try{await W.connect(),Q.setConnectionStatus(X.name,"connected"),Q.eventSystem.addEvent("server_connected",X.name,{name:X.name,capabilities:W.getInfo().capabilities}),this.logger?.info("session_server_connected",{sessionId:Q.sessionId,server:X.name});let $=Q.getConnection(X.name);if(!$)throw Error("Connection not found after connect");return $}catch($){let J=$ instanceof Error?$.message:String($);throw Q.setConnectionStatus(X.name,"error",J),this.logger?.warn("session_server_connect_failed",{sessionId:Q.sessionId,server:X.name,error:J}),$}}async disconnectSessionFromServer(Q,X){let Y=Q.getConnection(X);if(!Y){this.logger?.debug("disconnectSessionFromServer_no_connection",{sessionId:Q.sessionId,serverName:X});return}this.logger?.debug("disconnectSessionFromServer_start",{sessionId:Q.sessionId,serverName:X,connectionStatus:Y.status});let W=Q.taskManager.getTasksForServer(X);for(let $ of W)if($.status==="working")Q.taskManager.failTask($.taskId,"Server removed");Q.pendingRequests.rejectRequestsForServer(X,"Server removed");try{this.logger?.debug("disconnectSessionFromServer_calling_disconnect",{sessionId:Q.sessionId,serverName:X}),await Y.client.disconnect(),this.logger?.debug("disconnectSessionFromServer_disconnect_complete",{sessionId:Q.sessionId,serverName:X})}catch($){this.logger?.debug("disconnectSessionFromServer_disconnect_error",{sessionId:Q.sessionId,serverName:X,error:$ instanceof Error?$.message:String($)})}Q.removeConnection(X),this.logger?.debug("disconnectSessionFromServer_complete",{sessionId:Q.sessionId,serverName:X})}handleBackendDisconnect(Q,X){let Y=Q.taskManager.getTasksForServer(X);for(let W of Y)if(W.status==="working")Q.taskManager.failTask(W.taskId,"Server disconnected");Q.pendingRequests.rejectRequestsForServer(X,"Server disconnected"),Q.eventSystem.addEvent("server_disconnected",X,{name:X}),this.logger?.info("session_server_disconnected",{sessionId:Q.sessionId,server:X})}startCleanupInterval(){this.cleanupIntervalHandle=setInterval(()=>{this.runSessionCleanup()},this.config.cleanupIntervalMs)}runSessionCleanup(){let Q=Date.now(),X=[];for(let[Y,W]of this.sessions)if(Q-W.lastActivityAt.getTime()>=this.config.sessionTimeoutMs)X.push(Y);for(let Y of X)this.logger?.info("session_timeout_cleanup",{sessionId:Y}),this.destroySession(Y)}async shutdown(){if(this.logger?.info("session_manager_shutdown_start",{sessionCount:this.sessions.size}),this.cleanupIntervalHandle)clearInterval(this.cleanupIntervalHandle),this.cleanupIntervalHandle=null;for(let Q of Array.from(this.sessions.keys()))await this.destroySession(Q);this.logger?.info("session_manager_shutdown_complete",{})}getServerConfigs(){return this.serverConfigs}listSessionIds(){return Array.from(this.sessions.keys())}get sessionCount(){return this.sessions.size}}var YO={debug:0,info:1,warn:2,error:3};function WO(){let Q=()=>{};return{debug:Q,info:Q,warn:Q,error:Q}}function $O(Q="info",X){let Y=YO[Q],W=p8("fs"),$=(J,G,H)=>{if(YO[J]<Y)return;let B=new Date().toISOString(),z=H?` ${JSON.stringify(H)}`:"",K=`[${B}] ${J.toUpperCase()} ${G}${z}
|
|
54
54
|
`;try{W.appendFileSync(X,K)}catch(V){process.stderr.write(`Failed to write to log file: ${String(V)}
|
|
55
55
|
`),process.stderr.write(K)}};return{debug:(J,G)=>{$("debug",J,G)},info:(J,G)=>{$("info",J,G)},warn:(J,G)=>{$("warn",J,G)},error:(J,G)=>{$("error",J,G)}}}function Ag(Q,X){return{start:async()=>{return X.debug("transport_start",{}),Q.start()},send:async(W,$)=>{return X.debug("transport_send",{message:JSON.stringify(W),hasRelatedRequestId:$?.relatedRequestId!==void 0}),Q.send(W,$)},close:async()=>{return X.debug("transport_close",{}),Q.close()},get sessionId(){return Q.sessionId},set onclose(W){Q.onclose=W?()=>{X.debug("transport_onclose",{}),W()}:void 0},get onclose(){return Q.onclose},set onerror(W){Q.onerror=W?($)=>{X.debug("transport_onerror",{error:$.message}),W($)}:void 0},get onerror(){return Q.onerror},set onmessage(W){Q.onmessage=W?($)=>{X.debug("transport_onmessage",{message:JSON.stringify($)}),W($)}:void 0},get onmessage(){return Q.onmessage},setProtocolVersion:Q.setProtocolVersion?.bind(Q)}}function Dg(){let Q=process.argv.slice(2),X,Y="info",W;for(let $=0;$<Q.length;$++){let J=Q[$];if(J==="--config"&&Q[$+1])X=Q[$+1],$++;else if(J?.startsWith("--config="))X=J.slice(9);else if(J==="--log-level"&&Q[$+1])Y=Q[$+1],$++;else if(J?.startsWith("--log-level="))Y=J.slice(12);else if(J==="--log-file"&&Q[$+1])W=Q[$+1],$++;else if(J?.startsWith("--log-file="))W=J.slice(11)}return{configPath:X,logLevel:Y,logFile:W}}function Lg(Q){let X=qg(Q,"utf-8");return JSON.parse(X)}function Og(Q){return JSON.parse(Q)}function E0(Q){return Q}function $0(Q){return{content:[{type:"text",text:Q}],isError:!0}}function o0(Q){return{content:[{type:"text",text:Q}]}}function l0(Q){return{content:[{type:"text",text:JSON.stringify(Q,null,2)}]}}function Ug(Q,X,Y){Q.registerTool("add_server",{description:"Connect to a backend MCP server. The server will be added to the shared configuration and this session will connect to it.",inputSchema:{name:c.string().describe("Unique name for this server"),url:c.string().url().describe("HTTP URL of the MCP server endpoint")}},async({name:W,url:$})=>{let J=E0(Y());if(!J)return $0("Session not initialized");try{let H=(await X.addServer(J.sessionId,W,$)).client.getInfo().capabilities;return o0(`Connected to server '${W}' at ${$}
|
|
56
|
-
Capabilities: ${JSON.stringify(H)}`)}catch(G){let H=G instanceof Error?G.message:String(G);return $0(`Failed to add server: ${H}`)}}),Q.registerTool("remove_server",{description:"Disconnect from a backend MCP server and remove it from the configuration. This disconnects ALL sessions from the server.",inputSchema:{name:c.string().describe("Name of the server to remove")}},async({name:W})=>{let $=E0(Y());if(!$)return $0("Session not initialized");try{return await X.removeServer($.sessionId,W),o0(`Disconnected from server '${W}'`)}catch(J){let G=J instanceof Error?J.message:String(J);return $0(`Failed to remove server: ${G}`)}}),Q.registerTool("list_servers",{description:"List all configured backend MCP servers with their connection status for this session",inputSchema:{}},()=>{let W=E0(Y());if(!W)return $0("Session not initialized");let $=X.listServers(W.sessionId);if($.length===0)return o0("No servers configured");let J=$.map((G)=>({name:G.name,url:G.url,status:G.status,connected:G.connected,connectedAt:G.connectedAt?.toISOString(),lastError:G.lastError}));return l0(J)}),Q.registerTool("list_tools",{description:"List tools available from backend servers",inputSchema:{server:c.string().default(".*").describe("Regex pattern to match server names (default: .*)"),tool:c.string().default(".*").describe("Regex pattern to match tool names (default: .*)")}},async({server:W,tool:$})=>{let J=E0(Y());if(!J)return $0("Session not initialized");try{let G=new RegExp(W),H=new RegExp($),B=[];for(let z of J.listConnectedServers()){if(!G.test(z))continue;let K=J.getConnection(z);if(K?.status!=="connected")continue;try{let V=await K.client.listTools();for(let D of V)if(H.test(D.name))B.push({server:z,name:D.name,description:D.description,inputSchema:D.inputSchema})}catch{}}if(B.length===0)return o0("No tools available");return l0(B)}catch(G){let H=G instanceof Error?G.message:String(G);return $0(`Failed to list tools: ${H}`)}}),Q.registerTool("execute_tool",{description:"Execute a tool on a specific backend server",inputSchema:{server:c.string().describe("Name of the backend server"),tool:c.string().describe("Name of the tool to execute"),args:c.record(c.unknown()).optional().describe("Arguments to pass to the tool")}},async({server:W,tool:$,args:J})=>{let G=E0(Y());if(!G)return $0("Session not initialized");try{let B=await(await X.getOrCreateConnection(G.sessionId,W)).callTool($,J??{});return{content:B.content,isError:B.isError}}catch(H){let B=H instanceof Error?H.message:String(H);return $0(`Failed to execute tool: ${B}`)}}),Q.registerTool("list_resources",{description:"List resources available from backend servers",inputSchema:{server:c.string().optional().describe("Server name to filter by (omit for all servers)")}},async({server:W})=>{let $=E0(Y());if(!$)return $0("Session not initialized");try{let J=[],G=W?[W]:$.listConnectedServers();for(let H of G){let B=$.getConnection(H);if(B?.status!=="connected")continue;try{let z=await B.client.listResources();for(let K of z)J.push({server:H,uri:K.uri,name:K.name,description:K.description,mimeType:K.mimeType})}catch{}}if(J.length===0)return o0("No resources available");return l0(J)}catch(J){let G=J instanceof Error?J.message:String(J);return $0(`Failed to list resources: ${G}`)}}),Q.registerTool("list_resource_templates",{description:"List resource templates available from backend servers. Templates define parameterized resources that can be read with specific arguments.",inputSchema:{server:c.string().optional().describe("Server name to filter by (omit for all servers)")}},async({server:W})=>{let $=E0(Y());if(!$)return $0("Session not initialized");try{let J=[],G=W?[W]:$.listConnectedServers();for(let H of G){let B=$.getConnection(H);if(B?.status!=="connected")continue;try{let z=await B.client.listResourceTemplates();for(let K of z)J.push({server:H,uriTemplate:K.uriTemplate,name:K.name,description:K.description,mimeType:K.mimeType})}catch{}}if(J.length===0)return o0("No resource templates available");return l0(J)}catch(J){let G=J instanceof Error?J.message:String(J);return $0(`Failed to list resource templates: ${G}`)}}),Q.registerTool("read_resource",{description:"Read a specific resource from a backend server",inputSchema:{server:c.string().describe("Name of the backend server"),uri:c.string().describe("URI of the resource to read")}},async({server:W,uri:$})=>{let J=E0(Y());if(!J)return $0("Session not initialized");try{let B=(await(await X.getOrCreateConnection(J.sessionId,W)).readResource($)).contents.map((z)=>{if("text"in z&&typeof z.text==="string")return{uri:z.uri,mimeType:z.mimeType,text:z.text};else if("blob"in z&&typeof z.blob==="string")return{uri:z.uri,mimeType:z.mimeType,blob:`[base64 data, ${String(z.blob.length)} chars]`};return z});return l0({contents:B})}catch(G){let H=G instanceof Error?G.message:String(G);return $0(`Failed to read resource: ${H}`)}}),Q.registerTool("list_prompts",{description:"List prompts available from backend servers",inputSchema:{server:c.string().optional().describe("Server name to filter by (omit for all servers)")}},async({server:W})=>{let $=E0(Y());if(!$)return $0("Session not initialized");try{let J=[],G=W?[W]:$.listConnectedServers();for(let H of G){let B=$.getConnection(H);if(B?.status!=="connected")continue;try{let z=await B.client.listPrompts();for(let K of z)J.push({server:H,name:K.name,description:K.description,arguments:K.arguments})}catch{}}if(J.length===0)return o0("No prompts available");return l0(J)}catch(J){let G=J instanceof Error?J.message:String(J);return $0(`Failed to list prompts: ${G}`)}}),Q.registerTool("get_prompt",{description:"Get a specific prompt from a backend server",inputSchema:{server:c.string().describe("Name of the backend server"),name:c.string().describe("Name of the prompt to get"),arguments:c.record(c.string()).optional().describe("Arguments to pass to the prompt")}},async({server:W,name:$,arguments:J})=>{let G=E0(Y());if(!G)return $0("Session not initialized");try{let B=await(await X.getOrCreateConnection(G.sessionId,W)).getPrompt($,J??{});return l0(B)}catch(H){let B=H instanceof Error?H.message:String(H);return $0(`Failed to get prompt: ${B}`)}}),Q.registerTool("get_notifications",{description:"Get and clear buffered notifications from backend servers for this session",inputSchema:{}},()=>{let W=E0(Y());if(!W)return $0("Session not initialized");let $=W.bufferManager.getAndClearNotifications();if($.length===0)return o0("No notifications");let J=$.map((G)=>({server:G.server,method:G.method,timestamp:G.timestamp.toISOString(),params:G.params}));return l0(J)}),Q.registerTool("get_logs",{description:"Get and clear buffered log messages from backend servers for this session",inputSchema:{}},()=>{let W=E0(Y());if(!W)return $0("Session not initialized");let $=W.bufferManager.getAndClearLogs();if($.length===0)return o0("No log messages");let J=$.map((G)=>({server:G.server,level:G.level,logger:G.logger,timestamp:G.timestamp.toISOString(),data:G.data}));return l0(J)}),Q.registerTool("get_sampling_requests",{description:"Get pending sampling requests from backend servers that need LLM responses. These are requests from MCP servers asking for LLM completions.",inputSchema:{}},()=>{let W=E0(Y());if(!W)return $0("Session not initialized");let $=W.pendingRequests.getPendingSamplingRequests();if($.length===0)return o0("No pending sampling requests");let J=$.map((G)=>({requestId:G.requestId,server:G.server,timestamp:G.timestamp.toISOString(),maxTokens:G.params.maxTokens,systemPrompt:G.params.systemPrompt,temperature:G.params.temperature,messages:G.params.messages,modelPreferences:G.params.modelPreferences,tools:G.params.tools?.map((H)=>({name:H.name,description:H.description}))}));return l0(J)}),Q.registerTool("respond_to_sampling",{description:"Respond to a pending sampling request with an LLM result",inputSchema:{request_id:c.string().describe("The ID of the sampling request to respond to"),role:c.enum(["user","assistant"]).describe("The role of the message"),content:c.string().describe("The text content of the response"),model:c.string().describe("The model that generated the response"),stop_reason:c.enum(["endTurn","maxTokens","stopSequence","toolUse"]).optional().describe("Why the model stopped generating")}},({request_id:W,role:$,content:J,model:G,stop_reason:H})=>{let B=E0(Y());if(!B)return $0("Session not initialized");try{return B.pendingRequests.respondToSampling(W,{role:$,content:{type:"text",text:J},model:G,stopReason:H}),o0(`Responded to sampling request '${W}'`)}catch(z){let K=z instanceof Error?z.message:String(z);return $0(`Failed to respond: ${K}`)}}),Q.registerTool("get_elicitations",{description:"Get pending elicitation requests from backend servers that need user input. These are requests from MCP servers asking for form data or user confirmation.",inputSchema:{}},()=>{let W=E0(Y());if(!W)return $0("Session not initialized");let $=W.pendingRequests.getPendingElicitationRequests();if($.length===0)return o0("No pending elicitation requests");let J=$.map((G)=>({requestId:G.requestId,server:G.server,timestamp:G.timestamp.toISOString(),mode:"mode"in G.params?G.params.mode:"form",message:G.params.message,requestedSchema:"requestedSchema"in G.params?G.params.requestedSchema:void 0,elicitationId:"elicitationId"in G.params?G.params.elicitationId:void 0,url:"url"in G.params?G.params.url:void 0}));return l0(J)}),Q.registerTool("respond_to_elicitation",{description:"Respond to a pending elicitation request with user input",inputSchema:{request_id:c.string().describe("The ID of the elicitation request to respond to"),action:c.enum(["accept","decline","cancel"]).describe("The user's action: accept (with content), decline, or cancel"),content:c.record(c.union([c.string(),c.number(),c.boolean(),c.array(c.string())])).optional().describe("The form field values (required if action is 'accept' for form mode)")}},({request_id:W,action:$,content:J})=>{let G=E0(Y());if(!G)return $0("Session not initialized");try{return G.pendingRequests.respondToElicitation(W,{action:$,content:J}),o0(`Responded to elicitation request '${W}' with action '${$}'`)}catch(H){let B=H instanceof Error?H.message:String(H);return $0(`Failed to respond: ${B}`)}}),Q.registerTool("await_activity",{description:"Wait for activity (events, pending requests) or timeout. Use this to poll for changes efficiently instead of repeatedly calling get_* tools.",inputSchema:{timeout_ms:c.number().min(100).max(60000).default(30000).describe("Maximum time to wait in milliseconds (100-60000, default: 30000)"),last_event_id:c.string().optional().describe("Only return events after this ID (for pagination/continuation)")}},async({timeout_ms:W,last_event_id:$})=>{let J=E0(Y());if(!J)return $0("Session not initialized");let G=$?J.eventSystem.getEventsAfter($):J.eventSystem.getNewEvents();if(G.length>0)return l0({triggered_by:"existing_events",events:G.map((B)=>({id:B.id,type:B.type,server:B.server,createdAt:B.createdAt.toISOString(),data:B.data})),pending_server:{tasks:J.taskManager.getAllTasks().map((B)=>({taskId:B.taskId,server:B.server,toolName:B.toolName,status:B.status}))},pending_client:{sampling:J.pendingRequests.getPendingSamplingRequests().length,elicitation:J.pendingRequests.getPendingElicitationRequests().length}});let H=await J.eventSystem.waitForActivity(W);if(H){let B=J.eventSystem.getNewEvents();return l0({triggered_by:H.type,events:B.map((z)=>({id:z.id,type:z.type,server:z.server,createdAt:z.createdAt.toISOString(),data:z.data})),pending_server:{tasks:J.taskManager.getAllTasks().map((z)=>({taskId:z.taskId,server:z.server,toolName:z.toolName,status:z.status}))},pending_client:{sampling:J.pendingRequests.getPendingSamplingRequests().length,elicitation:J.pendingRequests.getPendingElicitationRequests().length}})}return l0({triggered_by:"timeout",events:[],pending_server:{tasks:J.taskManager.getAllTasks().map((B)=>({taskId:B.taskId,server:B.server,toolName:B.toolName,status:B.status}))},pending_client:{sampling:J.pendingRequests.getPendingSamplingRequests().length,elicitation:J.pendingRequests.getPendingElicitationRequests().length}})}),Q.registerTool("list_tasks",{description:"List proxy tasks (background tool executions that exceeded timeout)",inputSchema:{include_completed:c.boolean().default(!1).describe("Include completed/failed/cancelled tasks (default: false, only working tasks)"),server:c.string().optional().describe("Filter by server name")}},({include_completed:W,server:$})=>{let J=E0(Y());if(!J)return $0("Session not initialized");let G=J.taskManager.getAllTasks(W);if($)G=G.filter((B)=>B.server===$);if(G.length===0)return o0("No tasks");let H=G.map((B)=>({taskId:B.taskId,server:B.server,toolName:B.toolName,status:B.status,createdAt:B.createdAt.toISOString(),lastUpdatedAt:B.lastUpdatedAt.toISOString(),error:B.error,hasResult:B.result!==void 0}));return l0(H)}),Q.registerTool("get_task",{description:"Get details of a specific task including its result if completed",inputSchema:{task_id:c.string().describe("The task ID to retrieve")}},({task_id:W})=>{let $=E0(Y());if(!$)return $0("Session not initialized");let J=$.taskManager.getTask(W);if(!J)return $0(`Task '${W}' not found`);return l0({taskId:J.taskId,server:J.server,toolName:J.toolName,args:J.args,status:J.status,createdAt:J.createdAt.toISOString(),lastUpdatedAt:J.lastUpdatedAt.toISOString(),ttl:J.ttl,error:J.error,result:J.result})}),Q.registerTool("cancel_task",{description:"Cancel a working task",inputSchema:{task_id:c.string().describe("The task ID to cancel")}},({task_id:W})=>{let $=E0(Y());if(!$)return $0("Session not initialized");if($.taskManager.cancelTask(W))return o0(`Task '${W}' cancelled`);else return $0(`Task '${W}' not found or not in working state`)})}async function jg(){let{configPath:Q,logLevel:X,logFile:Y}=Dg(),W,$=Y,J=process.env.EMCEEPEE_LOG_DIR;if(!$&&J){let F=await import("fs"),q=await import("path"),A=J.startsWith("~")?q.join(process.env.HOME??"",J.slice(1)):J;try{F.mkdirSync(A,{recursive:!0})}catch{}let L=new Date().toISOString().replace(/[:.]/g,"-");$=q.join(A,`emceepee-${L}-${String(process.pid)}.log`)}if($){let F=Y?X:J?"debug":X;W=$O(F,$),W.info("Logging initialized",{logFile:$,logLevel:F})}else W=WO();W.info("Starting MCP Proxy Server (stdio mode)",{configPath:Q});let G=new lJ({logger:W,sessionTimeoutMs:1800000,cleanupIntervalMs:300000}),H=process.env.EMCEEPEE_CONFIG;if(H)try{let F=Og(H);W.info("Loading servers from config",{count:F.servers.length,source:"EMCEEPEE_CONFIG env var"});for(let q of F.servers)W.info("Adding server config",{name:q.name,url:q.url}),G.getServerConfigs().addConfig(q.name,q.url)}catch(F){W.error("Failed to load config from EMCEEPEE_CONFIG",{error:F instanceof Error?F.message:String(F)}),process.exit(1)}else if(Q)try{let F=Lg(Q);W.info("Loading servers from config",{count:F.servers.length,path:Q});for(let q of F.servers)W.info("Adding server config",{name:q.name,url:q.url}),G.getServerConfigs().addConfig(q.name,q.url)}catch(F){W.error("Failed to load config",{error:F instanceof Error?F.message:String(F)}),process.exit(1)}let B=new UJ({name:"emceepee",version:"0.2.0"}),z=await G.createSession();W.info("Session created",{sessionId:z.sessionId}),Ug(B,G,()=>z);let K=new wJ,V=$?Ag(K,W):K,D=async()=>{W.info("Shutting down...",{}),await K.close(),await G.shutdown(),process.exit(0)};process.on("SIGINT",()=>void D()),process.on("SIGTERM",()=>void D()),await B.connect(V),W.info("Server started (stdio mode)",{})}jg().catch((Q)=>{console.error("Fatal error:",Q),process.exit(1)});
|
|
56
|
+
Capabilities: ${JSON.stringify(H)}`)}catch(G){let H=G instanceof Error?G.message:String(G);return $0(`Failed to add server: ${H}`)}}),Q.registerTool("remove_server",{description:"Disconnect from a backend MCP server and remove it from the configuration. This disconnects ALL sessions from the server.",inputSchema:{name:c.string().describe("Name of the server to remove")}},async({name:W})=>{let $=E0(Y());if(!$)return $0("Session not initialized");try{return await X.removeServer($.sessionId,W),o0(`Disconnected from server '${W}'`)}catch(J){let G=J instanceof Error?J.message:String(J);return $0(`Failed to remove server: ${G}`)}}),Q.registerTool("list_servers",{description:"List all configured backend MCP servers with their connection status for this session",inputSchema:{}},()=>{let W=E0(Y());if(!W)return $0("Session not initialized");let $=X.listServers(W.sessionId);if($.length===0)return o0("No servers configured");let J=$.map((G)=>({name:G.name,url:G.url,status:G.status,connected:G.connected,connectedAt:G.connectedAt?.toISOString(),lastError:G.lastError}));return l0(J)}),Q.registerTool("list_tools",{description:"List tools available from backend servers",inputSchema:{server:c.string().default(".*").describe("Regex pattern to match server names (default: .*)"),tool:c.string().default(".*").describe("Regex pattern to match tool names (default: .*)")}},async({server:W,tool:$})=>{let J=E0(Y());if(!J)return $0("Session not initialized");try{let G=new RegExp(W),H=new RegExp($),B=[];for(let z of J.listConnectedServers()){if(!G.test(z))continue;let K=J.getConnection(z);if(K?.status!=="connected")continue;try{let V=await K.client.listTools();for(let D of V)if(H.test(D.name))B.push({server:z,name:D.name,description:D.description,inputSchema:D.inputSchema})}catch{}}if(B.length===0)return o0("No tools available");return l0(B)}catch(G){let H=G instanceof Error?G.message:String(G);return $0(`Failed to list tools: ${H}`)}}),Q.registerTool("execute_tool",{description:"Execute a tool on a specific backend server",inputSchema:{server:c.string().describe("Name of the backend server"),tool:c.string().describe("Name of the tool to execute"),args:c.record(c.unknown()).optional().describe("Arguments to pass to the tool")}},async({server:W,tool:$,args:J})=>{let G=E0(Y());if(!G)return $0("Session not initialized");try{let B=await(await X.getOrCreateConnection(G.sessionId,W)).callTool($,J??{});return{content:B.content,isError:B.isError}}catch(H){let B=H instanceof Error?H.message:String(H);return $0(`Failed to execute tool: ${B}`)}}),Q.registerTool("list_resources",{description:"List resources available from backend servers",inputSchema:{server:c.string().optional().describe("Server name to filter by (omit for all servers)")}},async({server:W})=>{let $=E0(Y());if(!$)return $0("Session not initialized");try{let J=[],G=W?[W]:$.listConnectedServers();for(let H of G){let B=$.getConnection(H);if(B?.status!=="connected")continue;try{let z=await B.client.listResources();for(let K of z)J.push({server:H,uri:K.uri,name:K.name,description:K.description,mimeType:K.mimeType})}catch{}}if(J.length===0)return o0("No resources available");return l0(J)}catch(J){let G=J instanceof Error?J.message:String(J);return $0(`Failed to list resources: ${G}`)}}),Q.registerTool("list_resource_templates",{description:"List resource templates available from backend servers. Templates define parameterized resources that can be read with specific arguments.",inputSchema:{server:c.string().optional().describe("Server name to filter by (omit for all servers)")}},async({server:W})=>{let $=E0(Y());if(!$)return $0("Session not initialized");try{let J=[],G=W?[W]:$.listConnectedServers();for(let H of G){let B=$.getConnection(H);if(B?.status!=="connected")continue;try{let z=await B.client.listResourceTemplates();for(let K of z)J.push({server:H,uriTemplate:K.uriTemplate,name:K.name,description:K.description,mimeType:K.mimeType})}catch{}}if(J.length===0)return o0("No resource templates available");return l0(J)}catch(J){let G=J instanceof Error?J.message:String(J);return $0(`Failed to list resource templates: ${G}`)}}),Q.registerTool("read_resource",{description:"Read a specific resource from a backend server",inputSchema:{server:c.string().describe("Name of the backend server"),uri:c.string().describe("URI of the resource to read")}},async({server:W,uri:$})=>{let J=E0(Y());if(!J)return $0("Session not initialized");try{let B=(await(await X.getOrCreateConnection(J.sessionId,W)).readResource($)).contents.map((z)=>{if("text"in z&&typeof z.text==="string")return{uri:z.uri,mimeType:z.mimeType,text:z.text};else if("blob"in z&&typeof z.blob==="string")return{uri:z.uri,mimeType:z.mimeType,blob:`[base64 data, ${String(z.blob.length)} chars]`};return z});return l0({contents:B})}catch(G){let H=G instanceof Error?G.message:String(G);return $0(`Failed to read resource: ${H}`)}}),Q.registerTool("list_prompts",{description:"List prompts available from backend servers",inputSchema:{server:c.string().optional().describe("Server name to filter by (omit for all servers)")}},async({server:W})=>{let $=E0(Y());if(!$)return $0("Session not initialized");try{let J=[],G=W?[W]:$.listConnectedServers();for(let H of G){let B=$.getConnection(H);if(B?.status!=="connected")continue;try{let z=await B.client.listPrompts();for(let K of z)J.push({server:H,name:K.name,description:K.description,arguments:K.arguments})}catch{}}if(J.length===0)return o0("No prompts available");return l0(J)}catch(J){let G=J instanceof Error?J.message:String(J);return $0(`Failed to list prompts: ${G}`)}}),Q.registerTool("get_prompt",{description:"Get a specific prompt from a backend server",inputSchema:{server:c.string().describe("Name of the backend server"),name:c.string().describe("Name of the prompt to get"),arguments:c.record(c.string()).optional().describe("Arguments to pass to the prompt")}},async({server:W,name:$,arguments:J})=>{let G=E0(Y());if(!G)return $0("Session not initialized");try{let B=await(await X.getOrCreateConnection(G.sessionId,W)).getPrompt($,J??{});return l0(B)}catch(H){let B=H instanceof Error?H.message:String(H);return $0(`Failed to get prompt: ${B}`)}}),Q.registerTool("get_notifications",{description:"Get and clear buffered notifications from backend servers for this session",inputSchema:{}},()=>{let W=E0(Y());if(!W)return $0("Session not initialized");let $=W.bufferManager.getAndClearNotifications();if($.length===0)return o0("No notifications");let J=$.map((G)=>({server:G.server,method:G.method,timestamp:G.timestamp.toISOString(),params:G.params}));return l0(J)}),Q.registerTool("get_logs",{description:"Get and clear buffered log messages from backend servers for this session",inputSchema:{}},()=>{let W=E0(Y());if(!W)return $0("Session not initialized");let $=W.bufferManager.getAndClearLogs();if($.length===0)return o0("No log messages");let J=$.map((G)=>({server:G.server,level:G.level,logger:G.logger,timestamp:G.timestamp.toISOString(),data:G.data}));return l0(J)}),Q.registerTool("get_sampling_requests",{description:"Get pending sampling requests from backend servers that need LLM responses. These are requests from MCP servers asking for LLM completions.",inputSchema:{}},()=>{let W=E0(Y());if(!W)return $0("Session not initialized");let $=W.pendingRequests.getPendingSamplingRequests();if($.length===0)return o0("No pending sampling requests");let J=$.map((G)=>({requestId:G.requestId,server:G.server,timestamp:G.timestamp.toISOString(),maxTokens:G.params.maxTokens,systemPrompt:G.params.systemPrompt,temperature:G.params.temperature,messages:G.params.messages,modelPreferences:G.params.modelPreferences,tools:G.params.tools?.map((H)=>({name:H.name,description:H.description}))}));return l0(J)}),Q.registerTool("respond_to_sampling",{description:"Respond to a pending sampling request with an LLM result",inputSchema:{request_id:c.string().describe("The ID of the sampling request to respond to"),role:c.enum(["user","assistant"]).describe("The role of the message"),content:c.string().describe("The text content of the response"),model:c.string().describe("The model that generated the response"),stop_reason:c.enum(["endTurn","maxTokens","stopSequence","toolUse"]).optional().describe("Why the model stopped generating")}},({request_id:W,role:$,content:J,model:G,stop_reason:H})=>{let B=E0(Y());if(!B)return $0("Session not initialized");try{return B.pendingRequests.respondToSampling(W,{role:$,content:{type:"text",text:J},model:G,stopReason:H}),o0(`Responded to sampling request '${W}'`)}catch(z){let K=z instanceof Error?z.message:String(z);return $0(`Failed to respond: ${K}`)}}),Q.registerTool("get_elicitations",{description:"Get pending elicitation requests from backend servers that need user input. These are requests from MCP servers asking for form data or user confirmation.",inputSchema:{}},()=>{let W=E0(Y());if(!W)return $0("Session not initialized");let $=W.pendingRequests.getPendingElicitationRequests();if($.length===0)return o0("No pending elicitation requests");let J=$.map((G)=>({requestId:G.requestId,server:G.server,timestamp:G.timestamp.toISOString(),mode:"mode"in G.params?G.params.mode:"form",message:G.params.message,requestedSchema:"requestedSchema"in G.params?G.params.requestedSchema:void 0,elicitationId:"elicitationId"in G.params?G.params.elicitationId:void 0,url:"url"in G.params?G.params.url:void 0}));return l0(J)}),Q.registerTool("respond_to_elicitation",{description:"Respond to a pending elicitation request with user input",inputSchema:{request_id:c.string().describe("The ID of the elicitation request to respond to"),action:c.enum(["accept","decline","cancel"]).describe("The user's action: accept (with content), decline, or cancel"),content:c.record(c.union([c.string(),c.number(),c.boolean(),c.array(c.string())])).optional().describe("The form field values (required if action is 'accept' for form mode)")}},({request_id:W,action:$,content:J})=>{let G=E0(Y());if(!G)return $0("Session not initialized");try{return G.pendingRequests.respondToElicitation(W,{action:$,content:J}),o0(`Responded to elicitation request '${W}' with action '${$}'`)}catch(H){let B=H instanceof Error?H.message:String(H);return $0(`Failed to respond: ${B}`)}}),Q.registerTool("await_activity",{description:"Wait for activity (events, pending requests) or timeout. Use this to poll for changes efficiently instead of repeatedly calling get_* tools.",inputSchema:{timeout_ms:c.number().min(100).max(60000).default(30000).describe("Maximum time to wait in milliseconds (100-60000, default: 30000)"),last_event_id:c.string().optional().describe("Only return events after this ID (for pagination/continuation)")}},async({timeout_ms:W,last_event_id:$})=>{let J=E0(Y());if(!J)return $0("Session not initialized");let G=$?J.eventSystem.getEventsAfter($):J.eventSystem.getNewEvents();if(G.length>0)return l0({triggered_by:"existing_events",events:G.map((B)=>({id:B.id,type:B.type,server:B.server,createdAt:B.createdAt.toISOString(),data:B.data})),pending_server:{tasks:J.taskManager.getAllTasks().map((B)=>({taskId:B.taskId,server:B.server,toolName:B.toolName,status:B.status}))},pending_client:{sampling:J.pendingRequests.getPendingSamplingRequests().length,elicitation:J.pendingRequests.getPendingElicitationRequests().length}});let H=await J.eventSystem.waitForActivity(W);if(H){let B=J.eventSystem.getNewEvents();return l0({triggered_by:H.type,events:B.map((z)=>({id:z.id,type:z.type,server:z.server,createdAt:z.createdAt.toISOString(),data:z.data})),pending_server:{tasks:J.taskManager.getAllTasks().map((z)=>({taskId:z.taskId,server:z.server,toolName:z.toolName,status:z.status}))},pending_client:{sampling:J.pendingRequests.getPendingSamplingRequests().length,elicitation:J.pendingRequests.getPendingElicitationRequests().length}})}return l0({triggered_by:"timeout",events:[],pending_server:{tasks:J.taskManager.getAllTasks().map((B)=>({taskId:B.taskId,server:B.server,toolName:B.toolName,status:B.status}))},pending_client:{sampling:J.pendingRequests.getPendingSamplingRequests().length,elicitation:J.pendingRequests.getPendingElicitationRequests().length}})}),Q.registerTool("list_tasks",{description:"List proxy tasks (background tool executions that exceeded timeout)",inputSchema:{include_completed:c.boolean().default(!1).describe("Include completed/failed/cancelled tasks (default: false, only working tasks)"),server:c.string().optional().describe("Filter by server name")}},({include_completed:W,server:$})=>{let J=E0(Y());if(!J)return $0("Session not initialized");let G=J.taskManager.getAllTasks(W);if($)G=G.filter((B)=>B.server===$);if(G.length===0)return o0("No tasks");let H=G.map((B)=>({taskId:B.taskId,server:B.server,toolName:B.toolName,status:B.status,createdAt:B.createdAt.toISOString(),lastUpdatedAt:B.lastUpdatedAt.toISOString(),error:B.error,hasResult:B.result!==void 0}));return l0(H)}),Q.registerTool("get_task",{description:"Get details of a specific task including its result if completed",inputSchema:{task_id:c.string().describe("The task ID to retrieve")}},({task_id:W})=>{let $=E0(Y());if(!$)return $0("Session not initialized");let J=$.taskManager.getTask(W);if(!J)return $0(`Task '${W}' not found`);return l0({taskId:J.taskId,server:J.server,toolName:J.toolName,args:J.args,status:J.status,createdAt:J.createdAt.toISOString(),lastUpdatedAt:J.lastUpdatedAt.toISOString(),ttl:J.ttl,error:J.error,result:J.result})}),Q.registerTool("cancel_task",{description:"Cancel a working task",inputSchema:{task_id:c.string().describe("The task ID to cancel")}},({task_id:W})=>{let $=E0(Y());if(!$)return $0("Session not initialized");if($.taskManager.cancelTask(W))return o0(`Task '${W}' cancelled`);else return $0(`Task '${W}' not found or not in working state`)})}async function jg(){let{configPath:Q,logLevel:X,logFile:Y}=Dg(),W,$=Y,J=process.env.EMCEEPEE_LOG_DIR;if(!$&&J){let F=await import("fs"),q=await import("path"),A=J.startsWith("~")?q.join(process.env.HOME??"",J.slice(1)):J;try{F.mkdirSync(A,{recursive:!0})}catch{}let L=new Date().toISOString().replace(/[:.]/g,"-");$=q.join(A,`emceepee-${L}-${String(process.pid)}.log`)}if($){let F=Y?X:J?"debug":X;W=$O(F,$),W.info("Logging initialized",{logFile:$,logLevel:F})}else W=WO();W.info("Starting MCP Proxy Server (stdio mode)",{configPath:Q});let G=new lJ({logger:W,sessionTimeoutMs:86400000,cleanupIntervalMs:300000}),H=process.env.EMCEEPEE_CONFIG;if(H)try{let F=Og(H);W.info("Loading servers from config",{count:F.servers.length,source:"EMCEEPEE_CONFIG env var"});for(let q of F.servers)W.info("Adding server config",{name:q.name,url:q.url}),G.getServerConfigs().addConfig(q.name,q.url)}catch(F){W.error("Failed to load config from EMCEEPEE_CONFIG",{error:F instanceof Error?F.message:String(F)}),process.exit(1)}else if(Q)try{let F=Lg(Q);W.info("Loading servers from config",{count:F.servers.length,path:Q});for(let q of F.servers)W.info("Adding server config",{name:q.name,url:q.url}),G.getServerConfigs().addConfig(q.name,q.url)}catch(F){W.error("Failed to load config",{error:F instanceof Error?F.message:String(F)}),process.exit(1)}let B=new UJ({name:"emceepee",version:"0.2.0"}),z=await G.createSession();W.info("Session created",{sessionId:z.sessionId}),Ug(B,G,()=>z);let K=new wJ,V=$?Ag(K,W):K,D=async()=>{W.info("Shutting down...",{}),await K.close(),await G.shutdown(),process.exit(0)};process.on("SIGINT",()=>void D()),process.on("SIGTERM",()=>void D()),await B.connect(V),W.info("Server started (stdio mode)",{})}jg().catch((Q)=>{console.error("Fatal error:",Q),process.exit(1)});
|