@loop_ouroboros/mcp-hub-lite 1.2.6 → 1.2.8

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 (101) hide show
  1. package/CHANGELOG.md +666 -640
  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/web/health.d.ts.map +1 -1
  33. package/dist/server/src/api/web/health.js +5 -0
  34. package/dist/server/src/api/ws/ws-handler.d.ts.map +1 -1
  35. package/dist/server/src/api/ws/ws-handler.js +4 -3
  36. package/dist/server/src/app.d.ts +7 -0
  37. package/dist/server/src/app.d.ts.map +1 -1
  38. package/dist/server/src/app.js +87 -0
  39. package/dist/server/src/models/event.model.d.ts +0 -13
  40. package/dist/server/src/models/event.model.d.ts.map +1 -1
  41. package/dist/server/src/models/server.model.d.ts +0 -29
  42. package/dist/server/src/models/server.model.d.ts.map +1 -1
  43. package/dist/server/src/models/types.d.ts +1 -70
  44. package/dist/server/src/models/types.d.ts.map +1 -1
  45. package/dist/server/src/models/types.js +1 -67
  46. package/dist/server/src/server/dev-server.js +24 -6
  47. package/dist/server/src/services/connection/connection-manager.d.ts +13 -0
  48. package/dist/server/src/services/connection/connection-manager.d.ts.map +1 -1
  49. package/dist/server/src/services/connection/connection-manager.js +122 -5
  50. package/dist/server/src/services/event-bus.service.d.ts +0 -4
  51. package/dist/server/src/services/event-bus.service.d.ts.map +1 -1
  52. package/dist/server/src/services/event-bus.service.js +1 -6
  53. package/dist/server/src/services/gateway/request-handlers/initialize-handler.d.ts.map +1 -1
  54. package/dist/server/src/services/gateway/request-handlers/initialize-handler.js +1 -0
  55. package/dist/server/src/services/mcp-oauth/index.d.ts +5 -0
  56. package/dist/server/src/services/mcp-oauth/index.d.ts.map +1 -0
  57. package/dist/server/src/services/mcp-oauth/index.js +3 -0
  58. package/dist/server/src/services/mcp-oauth/oauth-callback-server.d.ts +19 -0
  59. package/dist/server/src/services/mcp-oauth/oauth-callback-server.d.ts.map +1 -0
  60. package/dist/server/src/services/mcp-oauth/oauth-callback-server.js +100 -0
  61. package/dist/server/src/services/mcp-oauth/oauth-provider.d.ts +42 -0
  62. package/dist/server/src/services/mcp-oauth/oauth-provider.d.ts.map +1 -0
  63. package/dist/server/src/services/mcp-oauth/oauth-provider.js +121 -0
  64. package/dist/server/src/services/mcp-oauth/oauth-token-storage.d.ts +23 -0
  65. package/dist/server/src/services/mcp-oauth/oauth-token-storage.d.ts.map +1 -0
  66. package/dist/server/src/services/mcp-oauth/oauth-token-storage.js +92 -0
  67. package/dist/server/src/services/mcp-oauth/oauth-types.d.ts +21 -0
  68. package/dist/server/src/services/mcp-oauth/oauth-types.d.ts.map +1 -0
  69. package/dist/server/src/services/mcp-oauth/oauth-types.js +4 -0
  70. package/dist/server/src/utils/network-security.d.ts +33 -0
  71. package/dist/server/src/utils/network-security.d.ts.map +1 -0
  72. package/dist/server/src/utils/network-security.js +83 -0
  73. package/dist/server/src/utils/transports/streamable-http-transport.d.ts +12 -1
  74. package/dist/server/src/utils/transports/streamable-http-transport.d.ts.map +1 -1
  75. package/dist/server/src/utils/transports/streamable-http-transport.js +20 -1
  76. package/dist/server/src/utils/transports/transport-factory.d.ts +2 -0
  77. package/dist/server/src/utils/transports/transport-factory.d.ts.map +1 -1
  78. package/dist/server/src/utils/transports/transport-factory.js +17 -2
  79. package/dist/server/src/utils/transports/transport.interface.d.ts +2 -0
  80. package/dist/server/src/utils/transports/transport.interface.d.ts.map +1 -1
  81. package/dist/server/tests/unit/utils/network-security.test.d.ts +2 -0
  82. package/dist/server/tests/unit/utils/network-security.test.d.ts.map +1 -0
  83. package/dist/server/tests/unit/utils/network-security.test.js +123 -0
  84. package/dist/server/vite.config.js +1 -1
  85. package/package.json +111 -111
  86. package/dist/client/assets/HomeView-BnO4yIT9.js +0 -1
  87. package/dist/client/assets/ResourcesView-B5Xg0ynh.js +0 -1
  88. package/dist/client/assets/ServerDashboard-DYAVrrUk.js +0 -1
  89. package/dist/client/assets/ServerDetail-q94ZFfjL.js +0 -2
  90. package/dist/client/assets/SettingsView-BM6P5yrT.js +0 -1
  91. package/dist/client/assets/ToolCallDialog-BoAGxlB5.js +0 -1
  92. package/dist/client/assets/ToolsView-lqFhr7Bk.js +0 -1
  93. package/dist/client/assets/el-input-DkJq57wP.js +0 -1
  94. package/dist/client/assets/el-loading-C3v6a9xV.js +0 -1
  95. package/dist/client/assets/el-radio-group-C9QUL5mm.js +0 -1
  96. package/dist/client/assets/el-skeleton-item-Bbmpc0Xz.js +0 -1
  97. package/dist/client/assets/el-tab-pane-YsYuBcem.js +0 -1
  98. package/dist/client/assets/index-5tzIwwtS.js +0 -1
  99. package/dist/client/assets/index-MqHvQjDP.js +0 -2
  100. package/dist/client/assets/omit-CB4hTeTH.js +0 -1
  101. 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
  }
@@ -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
  };
@@ -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
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * MCP OAuth Client Provider implementing the SDK's OAuthClientProvider interface.
3
+ *
4
+ * Handles the full OAuth 2.0 authorization code flow with PKCE for MCP servers:
5
+ * - Persists tokens and client info to JSON files
6
+ * - Opens system browser for user authorization
7
+ * - Manages a local HTTP callback server for auth code capture
8
+ * - Supports token refresh and credential invalidation
9
+ */
10
+ import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
11
+ import type { OAuthClientInformationMixed, OAuthTokens, OAuthClientMetadata } from '@modelcontextprotocol/sdk/shared/auth.js';
12
+ import { OAuthCallbackServer } from './oauth-callback-server.js';
13
+ import type { McpOAuthProviderOptions } from './oauth-types.js';
14
+ export declare class McpOAuthClientProvider implements OAuthClientProvider {
15
+ private _callbackServer;
16
+ private storage;
17
+ private _configDir;
18
+ private _callbackPort;
19
+ private _serverUrlHash;
20
+ constructor(options: McpOAuthProviderOptions);
21
+ get redirectUrl(): string;
22
+ get clientMetadata(): OAuthClientMetadata;
23
+ clientInformation(): OAuthClientInformationMixed | undefined;
24
+ saveClientInformation(info: OAuthClientInformationMixed): void;
25
+ tokens(): OAuthTokens | undefined;
26
+ saveTokens(tokens: OAuthTokens): void;
27
+ redirectToAuthorization(authorizationUrl: URL): Promise<void>;
28
+ saveCodeVerifier(codeVerifier: string): void;
29
+ codeVerifier(): string;
30
+ invalidateCredentials(scope: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'): Promise<void>;
31
+ /**
32
+ * Starts the local callback server for auth code capture.
33
+ * Idempotent — returns existing server if already running.
34
+ */
35
+ startCallbackServer(): Promise<OAuthCallbackServer>;
36
+ /**
37
+ * Stops the callback server after auth flow completes.
38
+ */
39
+ stopCallbackServer(): Promise<void>;
40
+ getCallbackServer(): OAuthCallbackServer | null;
41
+ }
42
+ //# sourceMappingURL=oauth-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-provider.d.ts","sourceRoot":"","sources":["../../../../../src/services/mcp-oauth/oauth-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AACpF,OAAO,KAAK,EACV,2BAA2B,EAC3B,WAAW,EACX,mBAAmB,EACpB,MAAM,0CAA0C,CAAC;AAElD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAMhE,qBAAa,sBAAuB,YAAW,mBAAmB;IAChE,OAAO,CAAC,eAAe,CAAoC;IAC3D,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAAS;gBAEnB,OAAO,EAAE,uBAAuB;IAO5C,IAAI,WAAW,IAAI,MAAM,CAGxB;IAED,IAAI,cAAc,IAAI,mBAAmB,CAQxC;IAED,iBAAiB,IAAI,2BAA2B,GAAG,SAAS;IAI5D,qBAAqB,CAAC,IAAI,EAAE,2BAA2B,GAAG,IAAI;IAI9D,MAAM,IAAI,WAAW,GAAG,SAAS;IAIjC,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAI/B,uBAAuB,CAAC,gBAAgB,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BnE,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAI5C,YAAY,IAAI,MAAM;IAIhB,qBAAqB,CACzB,KAAK,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,GAC5D,OAAO,CAAC,IAAI,CAAC;IAKhB;;;OAGG;IACG,mBAAmB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IASzD;;OAEG;IACG,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAOzC,iBAAiB,IAAI,mBAAmB,GAAG,IAAI;CAGhD"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * MCP OAuth Client Provider implementing the SDK's OAuthClientProvider interface.
3
+ *
4
+ * Handles the full OAuth 2.0 authorization code flow with PKCE for MCP servers:
5
+ * - Persists tokens and client info to JSON files
6
+ * - Opens system browser for user authorization
7
+ * - Manages a local HTTP callback server for auth code capture
8
+ * - Supports token refresh and credential invalidation
9
+ */
10
+ import { exec } from 'node:child_process';
11
+ import { promisify } from 'node:util';
12
+ import path from 'node:path';
13
+ import os from 'node:os';
14
+ import { OAuthTokenStorage } from './oauth-token-storage.js';
15
+ import { OAuthCallbackServer } from './oauth-callback-server.js';
16
+ const execAsync = promisify(exec);
17
+ const DEFAULT_CONFIG_DIR = path.join(os.homedir(), '.mcp-hub-lite', 'oauth');
18
+ export class McpOAuthClientProvider {
19
+ _callbackServer = null;
20
+ storage;
21
+ _configDir;
22
+ _callbackPort;
23
+ _serverUrlHash;
24
+ constructor(options) {
25
+ this._serverUrlHash = options.serverUrlHash;
26
+ this._configDir = options.configDir || DEFAULT_CONFIG_DIR;
27
+ this._callbackPort = options.callbackPort || 0;
28
+ this.storage = new OAuthTokenStorage(this._configDir, this._serverUrlHash);
29
+ }
30
+ get redirectUrl() {
31
+ if (!this._callbackServer)
32
+ return '';
33
+ return this._callbackServer.redirectUrl;
34
+ }
35
+ get clientMetadata() {
36
+ return {
37
+ redirect_uris: [this.redirectUrl],
38
+ token_endpoint_auth_method: 'none',
39
+ grant_types: ['authorization_code', 'refresh_token'],
40
+ response_types: ['code'],
41
+ client_name: 'MCP Hub Lite'
42
+ };
43
+ }
44
+ clientInformation() {
45
+ return this.storage.getClientInfo();
46
+ }
47
+ saveClientInformation(info) {
48
+ this.storage.saveClientInfo(info);
49
+ }
50
+ tokens() {
51
+ return this.storage.getTokens();
52
+ }
53
+ saveTokens(tokens) {
54
+ this.storage.saveTokens(tokens);
55
+ }
56
+ async redirectToAuthorization(authorizationUrl) {
57
+ const urlStr = authorizationUrl.toString();
58
+ const platform = process.platform;
59
+ let command;
60
+ if (platform === 'win32') {
61
+ command = `start "" "${urlStr}"`;
62
+ }
63
+ else if (platform === 'darwin') {
64
+ command = `open "${urlStr}"`;
65
+ }
66
+ else {
67
+ command = `xdg-open "${urlStr}"`;
68
+ }
69
+ try {
70
+ await execAsync(command);
71
+ }
72
+ catch {
73
+ // Fallback: spawn detached
74
+ const { spawn } = await import('node:child_process');
75
+ if (platform === 'win32') {
76
+ spawn('cmd', ['/c', 'start', '', urlStr], { detached: true, stdio: 'ignore' }).unref();
77
+ }
78
+ else if (platform === 'darwin') {
79
+ spawn('open', [urlStr], { detached: true, stdio: 'ignore' }).unref();
80
+ }
81
+ else {
82
+ spawn('xdg-open', [urlStr], { detached: true, stdio: 'ignore' }).unref();
83
+ }
84
+ }
85
+ }
86
+ saveCodeVerifier(codeVerifier) {
87
+ this.storage.saveCodeVerifier(codeVerifier);
88
+ }
89
+ codeVerifier() {
90
+ return this.storage.getCodeVerifier() || '';
91
+ }
92
+ async invalidateCredentials(scope) {
93
+ if (scope === 'discovery')
94
+ return; // Not persisted
95
+ this.storage.clear(scope);
96
+ }
97
+ /**
98
+ * Starts the local callback server for auth code capture.
99
+ * Idempotent — returns existing server if already running.
100
+ */
101
+ async startCallbackServer() {
102
+ if (this._callbackServer) {
103
+ return this._callbackServer;
104
+ }
105
+ this._callbackServer = new OAuthCallbackServer(this._callbackPort);
106
+ await this._callbackServer.start();
107
+ return this._callbackServer;
108
+ }
109
+ /**
110
+ * Stops the callback server after auth flow completes.
111
+ */
112
+ async stopCallbackServer() {
113
+ if (this._callbackServer) {
114
+ await this._callbackServer.stop();
115
+ this._callbackServer = null;
116
+ }
117
+ }
118
+ getCallbackServer() {
119
+ return this._callbackServer;
120
+ }
121
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * JSON file-based OAuth token persistence.
3
+ *
4
+ * Stores OAuth credentials (client info, tokens, code verifier) as JSON files
5
+ * in the config directory. Uses atomic writes (tmp + rename) to prevent corruption.
6
+ */
7
+ import type { OAuthClientInformationMixed, OAuthTokens } from './oauth-types.js';
8
+ export declare class OAuthTokenStorage {
9
+ private cache;
10
+ private filePath;
11
+ constructor(configDir: string, serverUrlHash: string);
12
+ private ensureDir;
13
+ private loadFromFile;
14
+ private saveToFile;
15
+ getClientInfo(): OAuthClientInformationMixed | undefined;
16
+ saveClientInfo(info: OAuthClientInformationMixed): void;
17
+ getTokens(): OAuthTokens | undefined;
18
+ saveTokens(tokens: OAuthTokens): void;
19
+ getCodeVerifier(): string | undefined;
20
+ saveCodeVerifier(verifier: string): void;
21
+ clear(scope: 'all' | 'client' | 'tokens' | 'verifier'): void;
22
+ }
23
+ //# sourceMappingURL=oauth-token-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-token-storage.d.ts","sourceRoot":"","sources":["../../../../../src/services/mcp-oauth/oauth-token-storage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EACV,2BAA2B,EAC3B,WAAW,EAEZ,MAAM,kBAAkB,CAAC;AAE1B,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,QAAQ,CAAS;gBAEb,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM;IAIpD,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,UAAU;IAclB,aAAa,IAAI,2BAA2B,GAAG,SAAS;IAOxD,cAAc,CAAC,IAAI,EAAE,2BAA2B,GAAG,IAAI;IAKvD,SAAS,IAAI,WAAW,GAAG,SAAS;IAOpC,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAKrC,eAAe,IAAI,MAAM,GAAG,SAAS;IAOrC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKxC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,IAAI;CAiB7D"}