@loop_ouroboros/mcp-hub-lite 1.2.5 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +659 -626
  2. package/dist/client/assets/HomeView-CgEri1kD.js +1 -0
  3. package/dist/client/assets/{ResourceDetailView-BGBtmsyc.js → ResourceDetailView-B8Qo1_jK.js} +1 -1
  4. package/dist/client/assets/ResourcesView-B12FzUdo.js +1 -0
  5. package/dist/client/assets/ServerDashboard-B3O-crvv.js +1 -0
  6. package/dist/client/assets/ServerDetail-Bz5_9yOY.js +2 -0
  7. package/dist/client/assets/{ServerDetail-CtnNKJGx.css → ServerDetail-CXg8rI3q.css} +1 -1
  8. package/dist/client/assets/{ServerListView-C7kcd4GC.js → ServerListView-SlZN8ppC.js} +2 -2
  9. package/dist/client/assets/{ServerStatusTags.vue_vue_type_script_setup_true_lang-BHhwEuGe.js → ServerStatusTags.vue_vue_type_script_setup_true_lang-DmGg4uuV.js} +1 -1
  10. package/dist/client/assets/SettingsView-D8fiOG0O.js +1 -0
  11. package/dist/client/assets/ToolCallDialog-DYEdhnCk.js +1 -0
  12. package/dist/client/assets/ToolsView-BreAl-yn.js +1 -0
  13. package/dist/client/assets/{_baseClone-kbJbcBJT.js → _baseClone-BYxCbA_9.js} +1 -1
  14. package/dist/client/assets/{el-form-item-iQ0G8e97.js → el-form-item-ySymCPMr.js} +2 -2
  15. package/dist/client/assets/el-input-C85p6Nqj.js +1 -0
  16. package/dist/client/assets/el-loading-DIjKEx81.js +1 -0
  17. package/dist/client/assets/{el-overlay-Cy5xg31y.js → el-overlay-B_CxiSem.js} +1 -1
  18. package/dist/client/assets/el-radio-group-BjkTCPRf.js +1 -0
  19. package/dist/client/assets/el-skeleton-item-CupTKK6n.js +1 -0
  20. package/dist/client/assets/{el-switch-KpjV93lm.js → el-switch-BosIJ9jf.js} +1 -1
  21. package/dist/client/assets/el-tab-pane-BydxdJoc.js +1 -0
  22. package/dist/client/assets/{el-table-column-fofd_2n-.js → el-table-column-DV5TZOUW.js} +1 -1
  23. package/dist/client/assets/index-kC4mf0Vo.js +2 -0
  24. package/dist/client/assets/{index-DpH6ZSbs.css → index-xJkq2euk.css} +1 -1
  25. package/dist/client/assets/omit-DxDGRttI.js +1 -0
  26. package/dist/client/assets/{raf-MWAHt9ca.js → raf-Y9AoxecD.js} +1 -1
  27. package/dist/client/assets/{vue-vendor-CbgVSHIh.js → vue-vendor-Dwcr0jep.js} +1 -1
  28. package/dist/client/index.html +3 -3
  29. package/dist/server/shared/types/websocket.types.d.ts +28 -19
  30. package/dist/server/shared/types/websocket.types.d.ts.map +1 -1
  31. package/dist/server/shared/types/websocket.types.js +3 -2
  32. package/dist/server/src/api/ws/ws-handler.d.ts.map +1 -1
  33. package/dist/server/src/api/ws/ws-handler.js +4 -3
  34. package/dist/server/src/app.d.ts.map +1 -1
  35. package/dist/server/src/app.js +46 -0
  36. package/dist/server/src/models/event.model.d.ts +0 -13
  37. package/dist/server/src/models/event.model.d.ts.map +1 -1
  38. package/dist/server/src/models/server.model.d.ts +0 -29
  39. package/dist/server/src/models/server.model.d.ts.map +1 -1
  40. package/dist/server/src/models/types.d.ts +1 -70
  41. package/dist/server/src/models/types.d.ts.map +1 -1
  42. package/dist/server/src/models/types.js +1 -67
  43. package/dist/server/src/server/dev-server.js +24 -6
  44. package/dist/server/src/services/connection/connection-manager.d.ts +19 -0
  45. package/dist/server/src/services/connection/connection-manager.d.ts.map +1 -1
  46. package/dist/server/src/services/connection/connection-manager.js +145 -5
  47. package/dist/server/src/services/event-bus.service.d.ts +0 -4
  48. package/dist/server/src/services/event-bus.service.d.ts.map +1 -1
  49. package/dist/server/src/services/event-bus.service.js +1 -6
  50. package/dist/server/src/services/gateway/request-handlers/initialize-handler.d.ts.map +1 -1
  51. package/dist/server/src/services/gateway/request-handlers/initialize-handler.js +1 -0
  52. package/dist/server/src/services/hub-tools/instance-selector.d.ts +8 -1
  53. package/dist/server/src/services/hub-tools/instance-selector.d.ts.map +1 -1
  54. package/dist/server/src/services/hub-tools/instance-selector.js +24 -10
  55. package/dist/server/src/services/hub-tools/resource-generator.d.ts.map +1 -1
  56. package/dist/server/src/services/hub-tools/resource-generator.js +4 -19
  57. package/dist/server/src/services/hub-tools.service.d.ts.map +1 -1
  58. package/dist/server/src/services/hub-tools.service.js +22 -5
  59. package/dist/server/src/services/mcp-oauth/index.d.ts +5 -0
  60. package/dist/server/src/services/mcp-oauth/index.d.ts.map +1 -0
  61. package/dist/server/src/services/mcp-oauth/index.js +3 -0
  62. package/dist/server/src/services/mcp-oauth/oauth-callback-server.d.ts +19 -0
  63. package/dist/server/src/services/mcp-oauth/oauth-callback-server.d.ts.map +1 -0
  64. package/dist/server/src/services/mcp-oauth/oauth-callback-server.js +100 -0
  65. package/dist/server/src/services/mcp-oauth/oauth-provider.d.ts +42 -0
  66. package/dist/server/src/services/mcp-oauth/oauth-provider.d.ts.map +1 -0
  67. package/dist/server/src/services/mcp-oauth/oauth-provider.js +121 -0
  68. package/dist/server/src/services/mcp-oauth/oauth-token-storage.d.ts +23 -0
  69. package/dist/server/src/services/mcp-oauth/oauth-token-storage.d.ts.map +1 -0
  70. package/dist/server/src/services/mcp-oauth/oauth-token-storage.js +92 -0
  71. package/dist/server/src/services/mcp-oauth/oauth-types.d.ts +21 -0
  72. package/dist/server/src/services/mcp-oauth/oauth-types.d.ts.map +1 -0
  73. package/dist/server/src/services/mcp-oauth/oauth-types.js +4 -0
  74. package/dist/server/src/utils/network-security.d.ts +33 -0
  75. package/dist/server/src/utils/network-security.d.ts.map +1 -0
  76. package/dist/server/src/utils/network-security.js +83 -0
  77. package/dist/server/src/utils/transports/streamable-http-transport.d.ts +12 -1
  78. package/dist/server/src/utils/transports/streamable-http-transport.d.ts.map +1 -1
  79. package/dist/server/src/utils/transports/streamable-http-transport.js +20 -1
  80. package/dist/server/src/utils/transports/transport-factory.d.ts +2 -0
  81. package/dist/server/src/utils/transports/transport-factory.d.ts.map +1 -1
  82. package/dist/server/src/utils/transports/transport-factory.js +17 -2
  83. package/dist/server/src/utils/transports/transport.interface.d.ts +2 -0
  84. package/dist/server/src/utils/transports/transport.interface.d.ts.map +1 -1
  85. package/dist/server/tests/unit/services/hub-tools/instance-selector.test.js +21 -16
  86. package/dist/server/tests/unit/services/hub-tools.service.test.js +36 -35
  87. package/dist/server/tests/unit/utils/network-security.test.d.ts +2 -0
  88. package/dist/server/tests/unit/utils/network-security.test.d.ts.map +1 -0
  89. package/dist/server/tests/unit/utils/network-security.test.js +123 -0
  90. package/dist/server/vite.config.js +1 -1
  91. package/package.json +111 -111
  92. package/dist/client/assets/HomeView-BnO4yIT9.js +0 -1
  93. package/dist/client/assets/ResourcesView-B5Xg0ynh.js +0 -1
  94. package/dist/client/assets/ServerDashboard-DYAVrrUk.js +0 -1
  95. package/dist/client/assets/ServerDetail-q94ZFfjL.js +0 -2
  96. package/dist/client/assets/SettingsView-BM6P5yrT.js +0 -1
  97. package/dist/client/assets/ToolCallDialog-BoAGxlB5.js +0 -1
  98. package/dist/client/assets/ToolsView-lqFhr7Bk.js +0 -1
  99. package/dist/client/assets/el-input-DkJq57wP.js +0 -1
  100. package/dist/client/assets/el-loading-C3v6a9xV.js +0 -1
  101. package/dist/client/assets/el-radio-group-C9QUL5mm.js +0 -1
  102. package/dist/client/assets/el-skeleton-item-Bbmpc0Xz.js +0 -1
  103. package/dist/client/assets/el-tab-pane-YsYuBcem.js +0 -1
  104. package/dist/client/assets/index-5tzIwwtS.js +0 -1
  105. package/dist/client/assets/index-MqHvQjDP.js +0 -2
  106. package/dist/client/assets/omit-CB4hTeTH.js +0 -1
  107. package/dist/client/assets/typescript-Bp3YSIOJ.js +0 -1
@@ -1,5 +1,7 @@
1
1
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
2
  import { TransportFactory } from '../../utils/transports/transport-factory.js';
3
+ import { UnauthorizedError, auth } from '@modelcontextprotocol/sdk/client/auth.js';
4
+ import { StreamableHttpTransport } from '../../utils/transports/streamable-http-transport.js';
3
5
  import { logger, LOG_MODULES, formatMcpMessageForLogging, logNotificationMessage } from '../../utils/logger.js';
4
6
  import { getAppVersion } from '../../utils/version.js';
5
7
  import { getMcpCommDebugSetting } from '../../utils/json-utils.js';
@@ -98,6 +100,10 @@ export class McpConnectionManager {
98
100
  const serverId = server.id || 'unknown';
99
101
  const compositeKey = getCompositeKey(serverName, serverIndex);
100
102
  const { maxRetries, baseRetryDelay } = this.getRetryConfig();
103
+ // Create OAuth provider once per-connect (shared across retries)
104
+ const isStreamableHttp = server.type === 'streamable-http' || server.type === 'http';
105
+ let oauthProvider = null;
106
+ let oauthStarted = false;
101
107
  // Retry loop for connection attempts
102
108
  let lastError;
103
109
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
@@ -111,7 +117,21 @@ export class McpConnectionManager {
111
117
  // 4. Get server info
112
118
  const serverInfo = this.getServerInfo(serverId);
113
119
  // 5. Create transport and set up callbacks
114
- const { transport, pid } = this.initializeTransport(server, serverInfo, compositeKey, serverName, serverIndex);
120
+ const { transport, pid } = this.initializeTransport(server, serverInfo, compositeKey, serverName, serverIndex, oauthProvider ?? undefined);
121
+ // Capture OAuth provider from the transport (reuse across retries)
122
+ if (!oauthProvider && transport instanceof StreamableHttpTransport) {
123
+ const provider = transport.getOAuthProvider();
124
+ if (provider) {
125
+ oauthProvider =
126
+ provider;
127
+ }
128
+ }
129
+ // Start OAuth callback server once before connecting
130
+ if (oauthProvider && !oauthStarted) {
131
+ await oauthProvider.startCallbackServer();
132
+ oauthStarted = true;
133
+ logger.info(`OAuth callback server started for [${compositeKey}]`, LOG_MODULES.CONNECTION_MANAGER);
134
+ }
115
135
  // 6. Establish client connection
116
136
  const client = await this.establishClientConnection(transport);
117
137
  // 7. Register connection
@@ -122,12 +142,30 @@ export class McpConnectionManager {
122
142
  this.publishConnectionEvents(serverName, serverIndex);
123
143
  // 10. Refresh resources
124
144
  await this.refreshServerResources(serverName, serverIndex, server.type);
145
+ // 11. Request log notifications from downstream server
146
+ await this.requestLoggingFromServer(compositeKey, client, server.type);
125
147
  return true;
126
148
  }
127
149
  catch (error) {
150
+ // Handle OAuth flow for Streamable HTTP servers (only on first attempt)
151
+ if (isStreamableHttp &&
152
+ oauthProvider &&
153
+ error instanceof UnauthorizedError &&
154
+ attempt === 1) {
155
+ logger.info(`OAuth required for server [${compositeKey}], waiting for browser auth...`, LOG_MODULES.CONNECTION_MANAGER);
156
+ const oauthHandled = await this.handleOAuthFlow(oauthProvider, server.url || '', compositeKey);
157
+ if (oauthHandled) {
158
+ logger.info(`OAuth flow completed for [${compositeKey}], retrying connection...`, LOG_MODULES.CONNECTION_MANAGER);
159
+ continue; // Retry connection — authProvider now has tokens
160
+ }
161
+ }
128
162
  lastError = await this.handleConnectionError(error, compositeKey, attempt, maxRetries, baseRetryDelay);
129
163
  }
130
164
  }
165
+ // Clean up callback server
166
+ if (oauthProvider) {
167
+ await oauthProvider.stopCallbackServer();
168
+ }
131
169
  // All retries failed
132
170
  this.handleFinalFailure(compositeKey, serverName, serverIndex, lastError);
133
171
  return false;
@@ -186,7 +224,7 @@ export class McpConnectionManager {
186
224
  /**
187
225
  * Creates transport and sets up all callbacks.
188
226
  */
189
- initializeTransport(server, serverInfo, compositeKey, serverName, serverIndex) {
227
+ initializeTransport(server, serverInfo, compositeKey, serverName, serverIndex, authProvider) {
190
228
  const readyPatterns = server.type === 'stdio' ? (serverInfo.config.template.readyPatterns ?? []) : undefined;
191
229
  const readyTimeout = configManager.getConfig().system.startup?.readyTimeout ?? 120000;
192
230
  const transport = TransportFactory.createTransport({
@@ -194,7 +232,8 @@ export class McpConnectionManager {
194
232
  name: serverName
195
233
  }, compositeKey, {
196
234
  readyPatterns,
197
- readyTimeout
235
+ readyTimeout,
236
+ authProvider
198
237
  });
199
238
  // Set up message handler for notifications/message
200
239
  transport.onmessage = (message) => {
@@ -357,6 +396,76 @@ export class McpConnectionManager {
357
396
  resources
358
397
  });
359
398
  }
399
+ /**
400
+ * Sends logging/setLevel request to downstream server to start receiving log notifications.
401
+ * This is a best-effort request — servers that don't support logging will silently ignore it.
402
+ */
403
+ async requestLoggingFromServer(compositeKey, client, serverType) {
404
+ // SSE is unidirectional — cannot send requests to the server
405
+ if (serverType === 'sse')
406
+ return;
407
+ try {
408
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
409
+ await client.request({ method: 'logging/setLevel', params: { level: 'info' } }, { timeout: 5000 });
410
+ logger.info(`Sent logging/setLevel to server [${compositeKey}]`, LOG_MODULES.CONNECTION_MANAGER);
411
+ }
412
+ catch {
413
+ // Server may not support logging — not an error condition
414
+ logger.debug(`Server [${compositeKey}] does not support logging/setLevel`, LOG_MODULES.CONNECTION_MANAGER);
415
+ }
416
+ }
417
+ /**
418
+ * Handles the OAuth authorization flow for Streamable HTTP servers.
419
+ * Starts a local callback server, waits for the user to complete auth in the browser,
420
+ * then finishes the auth on the transport.
421
+ *
422
+ * @returns true if OAuth was handled successfully, false otherwise
423
+ */
424
+ async handleOAuthFlow(provider, serverUrl, compositeKey) {
425
+ try {
426
+ const callbackServer = provider.getCallbackServer();
427
+ if (!callbackServer) {
428
+ logger.warn(`No callback server for [${compositeKey}]`, LOG_MODULES.CONNECTION_MANAGER);
429
+ return false;
430
+ }
431
+ // SDK has already called redirectToAuthorization via authProvider
432
+ // Wait for the auth code from the callback server
433
+ const authCode = await new Promise((resolve, reject) => {
434
+ const timeout = setTimeout(() => {
435
+ callbackServer.removeAllListeners('auth-code-received');
436
+ resolve(null);
437
+ }, 5 * 60 * 1000);
438
+ callbackServer.once('auth-code-received', (code) => {
439
+ clearTimeout(timeout);
440
+ resolve(code);
441
+ });
442
+ callbackServer.once('error', (err) => {
443
+ clearTimeout(timeout);
444
+ reject(err);
445
+ });
446
+ });
447
+ if (!authCode) {
448
+ logger.warn(`OAuth flow timed out for server [${compositeKey}]`, LOG_MODULES.CONNECTION_MANAGER);
449
+ return false;
450
+ }
451
+ // Exchange the authorization code for tokens via SDK
452
+ logger.info(`Exchanging OAuth code for tokens for [${compositeKey}]...`, LOG_MODULES.CONNECTION_MANAGER);
453
+ const result = await auth(provider, {
454
+ serverUrl,
455
+ authorizationCode: authCode
456
+ });
457
+ if (result === 'AUTHORIZED') {
458
+ logger.info(`OAuth token exchange succeeded for [${compositeKey}]`, LOG_MODULES.CONNECTION_MANAGER);
459
+ return true;
460
+ }
461
+ logger.warn(`OAuth token exchange returned ${result} for [${compositeKey}]`, LOG_MODULES.CONNECTION_MANAGER);
462
+ return false;
463
+ }
464
+ catch (error) {
465
+ logger.error(`OAuth flow error for server [${compositeKey}]: ${error instanceof Error ? error.message : String(error)}`, LOG_MODULES.CONNECTION_MANAGER);
466
+ return false;
467
+ }
468
+ }
360
469
  /**
361
470
  * Handles connection error with logging and retry delay.
362
471
  */
@@ -375,10 +484,18 @@ export class McpConnectionManager {
375
484
  */
376
485
  handleFinalFailure(compositeKey, serverName, serverIndex, lastError) {
377
486
  const errorMessage = lastError?.message || 'Connection failed after all retries';
487
+ // Fetch recent error logs (stderr) to help diagnose startup failures
488
+ const recentErrorLogs = logStorage.getLogs(compositeKey, { level: 'error', limit: 10 });
489
+ const logDetail = recentErrorLogs.length > 0
490
+ ? '\n\n--- Recent stderr output ---\n' + recentErrorLogs.map((l) => l.message).join('\n')
491
+ : '';
492
+ // Write connection error to logStorage so it appears in the log viewer
493
+ // This ensures errors from all transport types (stdio, streamable-http, sse) are visible
494
+ logStorage.append(compositeKey, 'error', `[CONNECTION] ${errorMessage}`);
378
495
  logger.error(`Failed to connect to server ${compositeKey} after retries:`, lastError, LOG_MODULES.CONNECTION_MANAGER);
379
496
  this.serverStatus.set(compositeKey, {
380
497
  connected: false,
381
- error: errorMessage,
498
+ error: errorMessage + logDetail,
382
499
  lastCheck: Date.now(),
383
500
  toolsCount: 0,
384
501
  resourcesCount: 0
@@ -387,7 +504,7 @@ export class McpConnectionManager {
387
504
  serverName,
388
505
  serverIndex,
389
506
  status: 'error',
390
- error: errorMessage,
507
+ error: errorMessage + logDetail,
391
508
  timestamp: Date.now()
392
509
  });
393
510
  }
@@ -995,6 +1112,29 @@ export class McpConnectionManager {
995
1112
  }
996
1113
  return undefined;
997
1114
  }
1115
+ /**
1116
+ * Gets all connected instance indexes for a server.
1117
+ * @param serverName - Server name
1118
+ * @returns Array of connected instance indexes, empty if none connected
1119
+ */
1120
+ getConnectedIndexes(serverName) {
1121
+ // Directly iterate instances via hubManager, check each via getStatus
1122
+ // This bypasses serverNameToCompositeKeys which may have stale/missing entries
1123
+ // Note: We do NOT filter by instance.enabled here because:
1124
+ // - enabled=false means "do not auto-start" (config), not "cannot be connected" (runtime)
1125
+ // - A user can manually start an enabled=false instance, which should still appear in list_servers
1126
+ const instances = hubManager.getServerInstancesByName(serverName);
1127
+ const connectedIndexes = [];
1128
+ for (const instance of instances) {
1129
+ if (instance.index !== undefined) {
1130
+ const status = this.getStatus(serverName, instance.index);
1131
+ if (status?.connected) {
1132
+ connectedIndexes.push(instance.index);
1133
+ }
1134
+ }
1135
+ }
1136
+ return connectedIndexes;
1137
+ }
998
1138
  /**
999
1139
  * Gets the composite key of the first connected instance for a server name.
1000
1140
  * This is a backward compatibility method for code that expects getServerIdByName.
@@ -159,11 +159,7 @@ export declare const EventTypes: {
159
159
  readonly TOOL_CALL_ERROR: "tool-call-error";
160
160
  readonly RESOURCES_UPDATED: "resources";
161
161
  readonly LOG_ENTRY: "log";
162
- readonly LOGS_CLEARED: "logs-cleared";
163
- readonly SYSTEM_HEALTH: "system-health";
164
162
  readonly CONFIGURATION_UPDATED: "configuration-updated";
165
- readonly CLIENT_CONNECTED: "client-connected";
166
- readonly CLIENT_DISCONNECTED: "client-disconnected";
167
163
  };
168
164
  export declare const eventBus: EventBusService;
169
165
  //# sourceMappingURL=event-bus.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"event-bus.service.d.ts","sourceRoot":"","sources":["../../../../src/services/event-bus.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,SAAS,CAAqD;IAEtE;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAajD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,GAAG,MAAM,IAAI;IAU7E;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,WAAW;IAWnB;;;;;;;;;;;;;;OAcG;IACH,cAAc,IAAI,IAAI;IAItB;;;;;;;;;;;;;;OAcG;IACH,aAAa,IAAI,MAAM,EAAE;IAIzB;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAI5C;AAGD,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;CAkCb,CAAC;AAGX,eAAO,MAAM,QAAQ,iBAAwB,CAAC"}
1
+ {"version":3,"file":"event-bus.service.d.ts","sourceRoot":"","sources":["../../../../src/services/event-bus.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,SAAS,CAAqD;IAEtE;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAajD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,GAAG,MAAM,IAAI;IAU7E;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,WAAW;IAWnB;;;;;;;;;;;;;;OAcG;IACH,cAAc,IAAI,IAAI;IAItB;;;;;;;;;;;;;;OAcG;IACH,aAAa,IAAI,MAAM,EAAE;IAIzB;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAI5C;AAGD,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;CA4Bb,CAAC;AAGX,eAAO,MAAM,QAAQ,iBAAwB,CAAC"}
@@ -188,13 +188,8 @@ export const EventTypes = {
188
188
  RESOURCES_UPDATED: 'resources',
189
189
  // Log related events
190
190
  LOG_ENTRY: 'log',
191
- LOGS_CLEARED: 'logs-cleared',
192
191
  // System related events
193
- SYSTEM_HEALTH: 'system-health',
194
- CONFIGURATION_UPDATED: 'configuration-updated',
195
- // Client related events
196
- CLIENT_CONNECTED: 'client-connected',
197
- CLIENT_DISCONNECTED: 'client-disconnected'
192
+ CONFIGURATION_UPDATED: 'configuration-updated'
198
193
  };
199
194
  // Create global event bus instance
200
195
  export const eventBus = new EventBusService();
@@ -1 +1 @@
1
- {"version":3,"file":"initialize-handler.d.ts","sourceRoot":"","sources":["../../../../../../src/services/gateway/request-handlers/initialize-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYpE;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAqElE"}
1
+ {"version":3,"file":"initialize-handler.d.ts","sourceRoot":"","sources":["../../../../../../src/services/gateway/request-handlers/initialize-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYpE;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsElE"}
@@ -36,6 +36,7 @@ export function registerInitializeHandlers(server) {
36
36
  list: true,
37
37
  read: true
38
38
  },
39
+ logging: {},
39
40
  experimental: {}
40
41
  }
41
42
  };
@@ -17,11 +17,18 @@ export declare class InstanceSelector {
17
17
  private static roundRobinCounters;
18
18
  /**
19
19
  * Select best instance based on configured strategy
20
+ *
21
+ * @param serverName - Name of the server
22
+ * @param serverConfig - Server configuration
23
+ * @param requestOptions - Optional request options for instance selection
24
+ * @param statusChecker - Optional function to check instance connection status (for testing)
20
25
  */
21
26
  static selectInstance(serverName: string, serverConfig: ServerConfig, requestOptions?: {
22
27
  sessionId?: string;
23
28
  tags?: Record<string, string>;
24
- }): ServerInstance | undefined;
29
+ }, statusChecker?: (serverName: string, index: number) => {
30
+ connected?: boolean;
31
+ } | undefined): ServerInstance | undefined;
25
32
  private static selectRandomInstance;
26
33
  private static selectRoundRobinInstance;
27
34
  private static selectTagMatchUniqueInstance;
@@ -1 +1 @@
1
- {"version":3,"file":"instance-selector.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/instance-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAGnF;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,aAAa,EAAE,MAAM;aACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;aACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAF3D,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA,EACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,YAAA;CAmB9E;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAA6B;IAE9D;;OAEG;IACH,MAAM,CAAC,cAAc,CACnB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,YAAY,EAC1B,cAAc,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GACrE,cAAc,GAAG,SAAS;IA+B7B,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAKnC,OAAO,CAAC,MAAM,CAAC,wBAAwB;IAUvC,OAAO,CAAC,MAAM,CAAC,4BAA4B;CAiC5C"}
1
+ {"version":3,"file":"instance-selector.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/instance-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAInF;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,aAAa,EAAE,MAAM;aACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;aACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAF3D,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA,EACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,YAAA;CAmB9E;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAA6B;IAE9D;;;;;;;OAOG;IACH,MAAM,CAAC,cAAc,CACnB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,YAAY,EAC1B,cAAc,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,EACtE,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,GACzF,cAAc,GAAG,SAAS;IAwC7B,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAKnC,OAAO,CAAC,MAAM,CAAC,wBAAwB;IAUvC,OAAO,CAAC,MAAM,CAAC,4BAA4B;CAiC5C"}
@@ -1,4 +1,5 @@
1
1
  import { InstanceSelectionStrategy } from '../../models/server.model.js';
2
+ import { mcpConnectionManager } from '../mcp-connection-manager.js';
2
3
  /**
3
4
  * Error thrown when tag-match-unique instance selection fails.
4
5
  * Passes raw data so the error class itself can format the message.
@@ -39,28 +40,41 @@ export class InstanceSelector {
39
40
  static roundRobinCounters = new Map();
40
41
  /**
41
42
  * Select best instance based on configured strategy
43
+ *
44
+ * @param serverName - Name of the server
45
+ * @param serverConfig - Server configuration
46
+ * @param requestOptions - Optional request options for instance selection
47
+ * @param statusChecker - Optional function to check instance connection status (for testing)
42
48
  */
43
- static selectInstance(serverName, serverConfig, requestOptions) {
49
+ static selectInstance(serverName, serverConfig, requestOptions, statusChecker) {
44
50
  const { instances } = serverConfig;
45
51
  const instanceSelectionStrategy = serverConfig.template.instanceSelectionStrategy || InstanceSelectionStrategy.RANDOM;
46
- // Filter instances - only use enabled instances
47
- const enabledInstances = instances.filter((instance) => instance.enabled !== false);
48
- if (enabledInstances.length === 0) {
52
+ // Use provided statusChecker or default to mcpConnectionManager.getStatus
53
+ const checkStatus = statusChecker || ((name, idx) => mcpConnectionManager.getStatus(name, idx));
54
+ // Filter instances - use runtime connected status, NOT config enabled flag
55
+ // enabled=false means "do not auto-start" but user can manually start it
56
+ const connectedInstances = instances.filter((instance) => {
57
+ if (instance.index === undefined)
58
+ return false;
59
+ const status = checkStatus(serverName, instance.index);
60
+ return status?.connected;
61
+ });
62
+ if (connectedInstances.length === 0) {
49
63
  return undefined;
50
64
  }
51
65
  // Single instance case - return directly
52
- if (enabledInstances.length === 1) {
53
- return enabledInstances[0];
66
+ if (connectedInstances.length === 1) {
67
+ return connectedInstances[0];
54
68
  }
55
69
  switch (instanceSelectionStrategy) {
56
70
  case InstanceSelectionStrategy.RANDOM:
57
- return this.selectRandomInstance(enabledInstances);
71
+ return this.selectRandomInstance(connectedInstances);
58
72
  case InstanceSelectionStrategy.ROUND_ROBIN:
59
- return this.selectRoundRobinInstance(serverName, enabledInstances);
73
+ return this.selectRoundRobinInstance(serverName, connectedInstances);
60
74
  case InstanceSelectionStrategy.TAG_MATCH_UNIQUE:
61
- return this.selectTagMatchUniqueInstance(enabledInstances, requestOptions?.tags);
75
+ return this.selectTagMatchUniqueInstance(connectedInstances, requestOptions?.tags);
62
76
  default:
63
- return enabledInstances[0]; // Fallback to first instance
77
+ return connectedInstances[0]; // Fallback to first instance
64
78
  }
65
79
  }
66
80
  static selectRandomInstance(instances) {
@@ -1 +1 @@
1
- {"version":3,"file":"resource-generator.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/resource-generator.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAUlE;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AA6ID;;GAEG;AACH,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAE/C;;GAEG;AACH,eAAO,MAAM,cAAc,2BAA2B,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,qBAAqB,uEACoC,CAAC;AAEvE;;GAEG;AACH,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,IAAI,QAAQ,EAAE,CAuFrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,GAAG,QAAQ,EAAE,GAAG,MAAM,GAAG,OAAO,CAAC,CAyGzD"}
1
+ {"version":3,"file":"resource-generator.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/resource-generator.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAUlE;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AA6ID;;GAEG;AACH,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAE/C;;GAEG;AACH,eAAO,MAAM,cAAc,2BAA2B,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,qBAAqB,uEACoC,CAAC;AAEvE;;GAEG;AACH,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,IAAI,QAAQ,EAAE,CAqErD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,GAAG,QAAQ,EAAE,GAAG,MAAM,GAAG,OAAO,CAAC,CAyGzD"}
@@ -178,24 +178,9 @@ export function generateDynamicResources() {
178
178
  if (!hasValidId(server)) {
179
179
  continue;
180
180
  }
181
- let hasAnyConnectedInstance = false;
182
- let firstConnectedInstanceIndex;
183
- // First pass: check which instances are connected (consistent with /web/mcp/status API)
184
- for (const instance of server.config.instances) {
185
- const idx = instance.index;
186
- if (idx === undefined) {
187
- continue;
188
- }
189
- const instanceStatus = mcpConnectionManager.getStatus(server.name, idx);
190
- if (instanceStatus?.connected) {
191
- hasAnyConnectedInstance = true;
192
- if (firstConnectedInstanceIndex === undefined) {
193
- firstConnectedInstanceIndex = idx;
194
- }
195
- }
196
- }
197
- // Skip server if no instances are connected
198
- if (!hasAnyConnectedInstance) {
181
+ // Check if any instances are connected using getConnectedIndexes
182
+ const connectedIndexes = mcpConnectionManager.getConnectedIndexes(server.name);
183
+ if (connectedIndexes.length === 0) {
199
184
  continue;
200
185
  }
201
186
  // Second pass: generate resources for each connected instance
@@ -210,7 +195,7 @@ export function generateDynamicResources() {
210
195
  }
211
196
  // Server metadata resource (one per server, using first connected instance's index)
212
197
  // Only generate once when we hit the first connected instance
213
- if (idx === firstConnectedInstanceIndex) {
198
+ if (idx === connectedIndexes[0]) {
214
199
  resources.push({
215
200
  uri: `hub://servers/${server.name}`,
216
201
  name: `Server: ${server.name}`,
@@ -1 +1 @@
1
- {"version":3,"file":"hub-tools.service.d.ts","sourceRoot":"","sources":["../../../../src/services/hub-tools.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hub-tools.service.d.ts","sourceRoot":"","sources":["../../../../src/services/hub-tools.service.ts"],"names":[],"mappings":"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"}
@@ -91,9 +91,9 @@ export class HubToolsService {
91
91
  const servers = hubManager.getAllServers();
92
92
  const result = {};
93
93
  for (const server of servers.filter(hasValidId)) {
94
- // Check if server is actually connected
95
- const status = mcpConnectionManager.getStatusByName(server.name);
96
- if (!status?.connected) {
94
+ // Use getConnectedIndexes for reliable multi-instance support
95
+ const indexes = mcpConnectionManager.getConnectedIndexes(server.name);
96
+ if (indexes.length === 0) {
97
97
  continue;
98
98
  }
99
99
  // Use non-strict mode for management operations to avoid tag-match-unique errors
@@ -139,6 +139,11 @@ export class HubToolsService {
139
139
  tools: toolSummaries
140
140
  };
141
141
  }
142
+ // Check if server has any connected instances before trying to get tools
143
+ const indexes = mcpConnectionManager.getConnectedIndexes(args.serverName);
144
+ if (indexes.length === 0) {
145
+ throw new Error(`Server not found: ${args.serverName}`);
146
+ }
142
147
  // Use server name level cache to get tools directly without triggering instance selection
143
148
  // This avoids tag-match-unique errors for multi-instance servers when listing tools
144
149
  const tools = mcpConnectionManager.getToolsByServerName(args.serverName);
@@ -180,6 +185,11 @@ export class HubToolsService {
180
185
  }
181
186
  return undefined;
182
187
  }
188
+ // Check if server has any connected instances before trying to get tools
189
+ const indexes = mcpConnectionManager.getConnectedIndexes(args.serverName);
190
+ if (indexes.length === 0) {
191
+ throw new Error(`Server not found: ${args.serverName}`);
192
+ }
183
193
  // Use server name level cache to get tools directly without triggering instance selection
184
194
  // This avoids tag-match-unique errors for multi-instance servers when getting tool details
185
195
  const tools = mcpConnectionManager.getToolsByServerName(args.serverName);
@@ -405,8 +415,15 @@ export class HubToolsService {
405
415
  logger.debug(`selectBestInstance returned undefined for ${serverName}, trying direct instance selection with RANDOM strategy`, LOG_MODULES.HUB_TOOLS);
406
416
  const serverConfig = hubManager.getServerByName(serverName);
407
417
  if (serverConfig && serverConfig.instances.length > 0) {
408
- const enabledInstances = serverConfig.instances.filter((instance) => instance.enabled !== false);
409
- if (enabledInstances.length > 0) {
418
+ // Filter: use runtime connected status, NOT config enabled flag
419
+ // enabled=false means "do not auto-start" but user can manually start it
420
+ const connectedInstances = serverConfig.instances.filter((instance) => {
421
+ if (instance.index === undefined)
422
+ return false;
423
+ const status = mcpConnectionManager.getStatus(serverName, instance.index);
424
+ return status?.connected;
425
+ });
426
+ if (connectedInstances.length > 0) {
410
427
  // Use RANDOM strategy regardless of server's configured strategy
411
428
  const selectedInstance = InstanceSelector.selectInstance(serverName, {
412
429
  ...serverConfig,
@@ -0,0 +1,5 @@
1
+ export { McpOAuthClientProvider } from './oauth-provider.js';
2
+ export { OAuthCallbackServer } from './oauth-callback-server.js';
3
+ export { OAuthTokenStorage } from './oauth-token-storage.js';
4
+ export type { McpOAuthConfig, McpOAuthProviderOptions } from './oauth-types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/services/mcp-oauth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { McpOAuthClientProvider } from './oauth-provider.js';
2
+ export { OAuthCallbackServer } from './oauth-callback-server.js';
3
+ export { OAuthTokenStorage } from './oauth-token-storage.js';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Local HTTP server for OAuth callback handling.
3
+ *
4
+ * Listens on 127.0.0.1 with a dynamic port, captures the authorization code
5
+ * from the OAuth redirect, displays a success page to the user, and emits
6
+ * the code via EventEmitter. Auto-closes after 5 minutes of inactivity.
7
+ */
8
+ import { EventEmitter } from 'node:events';
9
+ export declare class OAuthCallbackServer extends EventEmitter {
10
+ private server;
11
+ private port;
12
+ private timeout;
13
+ constructor(port?: number);
14
+ get callbackUrl(): string;
15
+ get redirectUrl(): string;
16
+ start(): Promise<void>;
17
+ stop(): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=oauth-callback-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-callback-server.d.ts","sourceRoot":"","sources":["../../../../../src/services/mcp-oauth/oauth-callback-server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA2B3C,qBAAa,mBAAoB,SAAQ,YAAY;IACnD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,OAAO,CAA+B;gBAElC,IAAI,GAAE,MAAU;IAK5B,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA6CtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAkB5B"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Local HTTP server for OAuth callback handling.
3
+ *
4
+ * Listens on 127.0.0.1 with a dynamic port, captures the authorization code
5
+ * from the OAuth redirect, displays a success page to the user, and emits
6
+ * the code via EventEmitter. Auto-closes after 5 minutes of inactivity.
7
+ */
8
+ import http from 'node:http';
9
+ import { EventEmitter } from 'node:events';
10
+ import { logger, LOG_MODULES } from '../../utils/logger/index.js';
11
+ const CALLBACK_PATH = '/oauth/callback';
12
+ const CALLBACK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
13
+ const SUCCESS_HTML = `<!DOCTYPE html>
14
+ <html lang="en">
15
+ <head>
16
+ <meta charset="UTF-8">
17
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
18
+ <title>Authentication Successful</title>
19
+ <style>
20
+ body { font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #f5f5f5; }
21
+ .card { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); text-align: center; max-width: 400px; }
22
+ h1 { color: #22c55e; margin: 0 0 0.5rem; }
23
+ p { color: #666; margin: 0; }
24
+ </style>
25
+ </head>
26
+ <body>
27
+ <div class="card">
28
+ <h1>Authentication Successful</h1>
29
+ <p>You can close this page and return to MCP Hub Lite.</p>
30
+ </div>
31
+ </body>
32
+ </html>`;
33
+ export class OAuthCallbackServer extends EventEmitter {
34
+ server = null;
35
+ port;
36
+ timeout = null;
37
+ constructor(port = 0) {
38
+ super();
39
+ this.port = port;
40
+ }
41
+ get callbackUrl() {
42
+ return `http://127.0.0.1:${this.port}${CALLBACK_PATH}`;
43
+ }
44
+ get redirectUrl() {
45
+ return this.callbackUrl;
46
+ }
47
+ async start() {
48
+ return new Promise((resolve, reject) => {
49
+ this.server = http.createServer((req, res) => {
50
+ if (!req.url)
51
+ return;
52
+ const url = new URL(req.url, `http://127.0.0.1:${this.port}`);
53
+ if (url.pathname === CALLBACK_PATH) {
54
+ const code = url.searchParams.get('code');
55
+ if (code) {
56
+ logger.info('OAuth callback received authorization code', LOG_MODULES.dynamic('oauth'));
57
+ this.emit('auth-code-received', code);
58
+ }
59
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
60
+ res.end(SUCCESS_HTML);
61
+ }
62
+ else {
63
+ res.writeHead(404);
64
+ res.end('Not found');
65
+ }
66
+ });
67
+ this.server.on('error', (err) => {
68
+ logger.error(`OAuth callback server error: ${err.message}`, LOG_MODULES.dynamic('oauth'));
69
+ reject(err);
70
+ });
71
+ this.server.listen(this.port, '127.0.0.1', () => {
72
+ const addr = this.server.address();
73
+ if (addr && typeof addr === 'object') {
74
+ this.port = addr.port;
75
+ }
76
+ logger.info(`OAuth callback server started on ${this.callbackUrl}`, LOG_MODULES.dynamic('oauth'));
77
+ this.timeout = setTimeout(() => this.stop(), CALLBACK_TIMEOUT);
78
+ resolve();
79
+ });
80
+ });
81
+ }
82
+ async stop() {
83
+ if (this.timeout) {
84
+ clearTimeout(this.timeout);
85
+ this.timeout = null;
86
+ }
87
+ return new Promise((resolve) => {
88
+ if (this.server) {
89
+ this.server.close(() => {
90
+ logger.info('OAuth callback server stopped', LOG_MODULES.dynamic('oauth'));
91
+ this.server = null;
92
+ resolve();
93
+ });
94
+ }
95
+ else {
96
+ resolve();
97
+ }
98
+ });
99
+ }
100
+ }