@samanhappy/mcphub 1.0.43 → 1.0.45

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 (91) hide show
  1. package/dist/clients/openapi.js +20 -4
  2. package/dist/clients/openapi.js.map +1 -1
  3. package/dist/config/index.js +1 -1
  4. package/dist/config/index.js.map +1 -1
  5. package/dist/controllers/oauthCallbackController.js +1 -1
  6. package/dist/controllers/oauthCallbackController.js.map +1 -1
  7. package/dist/controllers/toolController.js +2 -1
  8. package/dist/controllers/toolController.js.map +1 -1
  9. package/dist/middlewares/auth.js +1 -1
  10. package/dist/middlewares/auth.js.map +1 -1
  11. package/dist/services/mcpOAuthProvider.js +25 -0
  12. package/dist/services/mcpOAuthProvider.js.map +1 -1
  13. package/dist/services/mcpService.js +106 -15
  14. package/dist/services/mcpService.js.map +1 -1
  15. package/dist/services/oauthClientRegistration.js +1 -1
  16. package/dist/services/oauthClientRegistration.js.map +1 -1
  17. package/dist/services/oauthSettingsStore.js +5 -0
  18. package/dist/services/oauthSettingsStore.js.map +1 -1
  19. package/dist/services/proxy.js +1 -1
  20. package/dist/services/proxy.js.map +1 -1
  21. package/dist/services/requestContextService.js.map +1 -1
  22. package/dist/services/sseService.js +14 -1
  23. package/dist/services/sseService.js.map +1 -1
  24. package/dist/services/vectorSearchService.js +3 -1
  25. package/dist/services/vectorSearchService.js.map +1 -1
  26. package/dist/utils/oauthConsentResource.js +1 -1
  27. package/dist/utils/oauthConsentResource.js.map +1 -1
  28. package/frontend/dist/assets/{ActivityPage-Y8gIRxkd.js → ActivityPage-DPlbWRuE.js} +2 -2
  29. package/frontend/dist/assets/{ActivityPage-Y8gIRxkd.js.map → ActivityPage-DPlbWRuE.js.map} +1 -1
  30. package/frontend/dist/assets/{ConfirmDialog-uWBwm8J1.js → ConfirmDialog-BYdfb115.js} +2 -2
  31. package/frontend/dist/assets/{ConfirmDialog-uWBwm8J1.js.map → ConfirmDialog-BYdfb115.js.map} +1 -1
  32. package/frontend/dist/assets/{CopyClientConfigDialog-BHijFbyw.js → CopyClientConfigDialog-BmrvVqKm.js} +2 -2
  33. package/frontend/dist/assets/{CopyClientConfigDialog-BHijFbyw.js.map → CopyClientConfigDialog-BmrvVqKm.js.map} +1 -1
  34. package/frontend/dist/assets/{CredentialsPage-1UqVqEMQ.js → CredentialsPage-DUa5COar.js} +2 -2
  35. package/frontend/dist/assets/{CredentialsPage-1UqVqEMQ.js.map → CredentialsPage-DUa5COar.js.map} +1 -1
  36. package/frontend/dist/assets/{Dashboard-CswKNreX.js → Dashboard-DaTYK_nx.js} +2 -2
  37. package/frontend/dist/assets/{Dashboard-CswKNreX.js.map → Dashboard-DaTYK_nx.js.map} +1 -1
  38. package/frontend/dist/assets/{DeleteDialog-BYhrMGZo.js → DeleteDialog-BaCsEkJA.js} +2 -2
  39. package/frontend/dist/assets/{DeleteDialog-BYhrMGZo.js.map → DeleteDialog-BaCsEkJA.js.map} +1 -1
  40. package/frontend/dist/assets/{EndpointCopy-B0yIEBjO.js → EndpointCopy-BCfqwDEK.js} +2 -2
  41. package/frontend/dist/assets/{EndpointCopy-B0yIEBjO.js.map → EndpointCopy-BCfqwDEK.js.map} +1 -1
  42. package/frontend/dist/assets/{GroupsPage-KJJsAprG.js → GroupsPage-Cuxz5FyS.js} +2 -2
  43. package/frontend/dist/assets/{GroupsPage-KJJsAprG.js.map → GroupsPage-Cuxz5FyS.js.map} +1 -1
  44. package/frontend/dist/assets/{LoginPage-B1heSsJ6.js → LoginPage-yTpHn5Tx.js} +2 -2
  45. package/frontend/dist/assets/{LoginPage-B1heSsJ6.js.map → LoginPage-yTpHn5Tx.js.map} +1 -1
  46. package/frontend/dist/assets/LogsPage-BbSlC06Q.js +2 -0
  47. package/frontend/dist/assets/LogsPage-BbSlC06Q.js.map +1 -0
  48. package/frontend/dist/assets/{MarketPage-D0RXyf6g.js → MarketPage-nAF8uFkv.js} +2 -2
  49. package/frontend/dist/assets/{MarketPage-D0RXyf6g.js.map → MarketPage-nAF8uFkv.js.map} +1 -1
  50. package/frontend/dist/assets/{OAuthConsentPage-DLveAKh7.js → OAuthConsentPage-W05BBBpL.js} +2 -2
  51. package/frontend/dist/assets/{OAuthConsentPage-DLveAKh7.js.map → OAuthConsentPage-W05BBBpL.js.map} +1 -1
  52. package/frontend/dist/assets/{Pagination-CLUvn47x.js → Pagination-BRqqU5Ct.js} +2 -2
  53. package/frontend/dist/assets/{Pagination-CLUvn47x.js.map → Pagination-BRqqU5Ct.js.map} +1 -1
  54. package/frontend/dist/assets/PromptsPage-Bq7xptCw.js +2 -0
  55. package/frontend/dist/assets/{PromptsPage-cPHf4CM9.js.map → PromptsPage-Bq7xptCw.js.map} +1 -1
  56. package/frontend/dist/assets/ResourcesPage-CsL39aff.js +2 -0
  57. package/frontend/dist/assets/{ResourcesPage-DcXEwXg9.js.map → ResourcesPage-CsL39aff.js.map} +1 -1
  58. package/frontend/dist/assets/{ServersPage-CIPZUbqT.js → ServersPage-p9LgWUzS.js} +4 -4
  59. package/frontend/dist/assets/{ServersPage-CIPZUbqT.js.map → ServersPage-p9LgWUzS.js.map} +1 -1
  60. package/frontend/dist/assets/{SettingsPage-vMxO3bOc.js → SettingsPage-DobkN2nZ.js} +2 -2
  61. package/frontend/dist/assets/{SettingsPage-vMxO3bOc.js.map → SettingsPage-DobkN2nZ.js.map} +1 -1
  62. package/frontend/dist/assets/{StatusDot-LGEKnAGN.js → StatusDot-DnCf86-T.js} +2 -2
  63. package/frontend/dist/assets/{StatusDot-LGEKnAGN.js.map → StatusDot-DnCf86-T.js.map} +1 -1
  64. package/frontend/dist/assets/{ToggleGroup-Ad_I2spO.js → ToggleGroup-DFjmIz4f.js} +2 -2
  65. package/frontend/dist/assets/{ToggleGroup-Ad_I2spO.js.map → ToggleGroup-DFjmIz4f.js.map} +1 -1
  66. package/frontend/dist/assets/{UsersPage-CbfajFCc.js → UsersPage-g_A6Nis1.js} +2 -2
  67. package/frontend/dist/assets/{UsersPage-CbfajFCc.js.map → UsersPage-g_A6Nis1.js.map} +1 -1
  68. package/frontend/dist/assets/{clipboard-CjtPOmJs.js → clipboard-C6pDz_7o.js} +2 -2
  69. package/frontend/dist/assets/{clipboard-CjtPOmJs.js.map → clipboard-C6pDz_7o.js.map} +1 -1
  70. package/frontend/dist/assets/{framework-vendor-_YL27Pfc.js → framework-vendor-CQFm9vKd.js} +12 -12
  71. package/frontend/dist/assets/{framework-vendor-_YL27Pfc.js.map → framework-vendor-CQFm9vKd.js.map} +1 -1
  72. package/frontend/dist/assets/{i18n-vendor-BMaj5qV8.js → i18n-vendor-DVmjrzgW.js} +2 -2
  73. package/frontend/dist/assets/{i18n-vendor-BMaj5qV8.js.map → i18n-vendor-DVmjrzgW.js.map} +1 -1
  74. package/frontend/dist/assets/icons-vendor-CEwDZ1lN.js +362 -0
  75. package/frontend/dist/assets/icons-vendor-CEwDZ1lN.js.map +1 -0
  76. package/frontend/dist/assets/{index-DJUr-vo3.js → index-BlEZfPQl.js} +3 -3
  77. package/frontend/dist/assets/{index-DJUr-vo3.js.map → index-BlEZfPQl.js.map} +1 -1
  78. package/frontend/dist/assets/{resourceService-D4dgqjzY.js → resourceService-CAxgCzyV.js} +2 -2
  79. package/frontend/dist/assets/{resourceService-D4dgqjzY.js.map → resourceService-CAxgCzyV.js.map} +1 -1
  80. package/frontend/dist/assets/{useCostData-CT2iwvzx.js → useCostData-wcUc6g7_.js} +2 -2
  81. package/frontend/dist/assets/{useCostData-CT2iwvzx.js.map → useCostData-wcUc6g7_.js.map} +1 -1
  82. package/frontend/dist/assets/{variableDetection-BHrhKDaj.js → variableDetection-DxVZhKpb.js} +2 -2
  83. package/frontend/dist/assets/{variableDetection-BHrhKDaj.js.map → variableDetection-DxVZhKpb.js.map} +1 -1
  84. package/frontend/dist/index.html +4 -4
  85. package/package.json +5 -5
  86. package/frontend/dist/assets/LogsPage-C4BOgOSW.js +0 -2
  87. package/frontend/dist/assets/LogsPage-C4BOgOSW.js.map +0 -1
  88. package/frontend/dist/assets/PromptsPage-cPHf4CM9.js +0 -2
  89. package/frontend/dist/assets/ResourcesPage-DcXEwXg9.js +0 -2
  90. package/frontend/dist/assets/icons-vendor-CvmEvkQ-.js +0 -337
  91. package/frontend/dist/assets/icons-vendor-CvmEvkQ-.js.map +0 -1
@@ -702,6 +702,65 @@ const getVisibleServerByName = (name) => {
702
702
  const sessionIsolatedClients = new Map();
703
703
  // Locks to prevent concurrent creation of the same isolated client
704
704
  const isolatedClientCreationLocks = new Map();
705
+ // Explicit application state for modern clients, independent of MCP transports.
706
+ // Active calls pin entries; idle state expires and admission is bounded.
707
+ const CLIENT_STATE_IDLE_MS = 30 * 60 * 1000;
708
+ const MAX_CLIENT_STATES = 1000;
709
+ const clientStates = new Map();
710
+ let clientStateSweep;
711
+ const removeClientState = (id) => {
712
+ const state = clientStates.get(id);
713
+ if (!state)
714
+ return;
715
+ clientStates.delete(id);
716
+ cleanupIsolatedSession(id);
717
+ for (const client of state.cookieClients)
718
+ client.clearSessionCookies(id);
719
+ };
720
+ const acquireClientState = (serverInfo) => {
721
+ const contextService = RequestContextService.getInstance();
722
+ const scope = contextService.getRequestContext()?.clientStateScope;
723
+ const handle = contextService.getHeader('x-mcphub-state-id');
724
+ if (!scope ||
725
+ typeof handle !== 'string' ||
726
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(handle)) {
727
+ throw new Error('Stateful tools on stateless MCP require authenticated bearer access and X-MCPHub-State-Id (UUID v4)');
728
+ }
729
+ const id = `client-state:${scope}:${handle.toLowerCase()}`;
730
+ let state = clientStates.get(id);
731
+ if (state && state.active === 0 && Date.now() - state.lastUsed >= CLIENT_STATE_IDLE_MS) {
732
+ removeClientState(id);
733
+ state = undefined;
734
+ }
735
+ if (!state) {
736
+ if (clientStates.size >= MAX_CLIENT_STATES) {
737
+ throw new Error('Client state capacity reached; retry after idle state expires');
738
+ }
739
+ state = { active: 0, lastUsed: Date.now(), cookieClients: new Set() };
740
+ clientStates.set(id, state);
741
+ }
742
+ if (!clientStateSweep) {
743
+ clientStateSweep = setInterval(() => {
744
+ for (const [key, entry] of clientStates) {
745
+ if (entry.active === 0 && Date.now() - entry.lastUsed >= CLIENT_STATE_IDLE_MS) {
746
+ removeClientState(key);
747
+ }
748
+ }
749
+ }, 60 * 1000);
750
+ clientStateSweep.unref();
751
+ }
752
+ if (serverInfo.openApiClient)
753
+ state.cookieClients.add(serverInfo.openApiClient);
754
+ state.active++;
755
+ const entry = state;
756
+ return {
757
+ id,
758
+ release: () => {
759
+ entry.active--;
760
+ entry.lastUsed = Date.now();
761
+ },
762
+ };
763
+ };
705
764
  export const connectClientWithDiagnostics = async (client, transport, options) => {
706
765
  try {
707
766
  await client.connect(transport, options);
@@ -839,6 +898,11 @@ export const connected = () => {
839
898
  // Global cleanup function to close all connections
840
899
  export const cleanupAllServers = () => {
841
900
  stopPackageUpdateSweep();
901
+ if (clientStateSweep)
902
+ clearInterval(clientStateSweep);
903
+ clientStateSweep = undefined;
904
+ for (const id of clientStates.keys())
905
+ removeClientState(id);
842
906
  principalRuntimes.invalidate();
843
907
  for (const serverInfo of serverInfos) {
844
908
  try {
@@ -1275,7 +1339,9 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
1275
1339
  try {
1276
1340
  const rawConfig = await getServerDao().findById(serverInfo.name);
1277
1341
  if (!rawConfig) {
1278
- throw new Error(`Server configuration not found for: ${serverInfo.name}`);
1342
+ throw new Error(`Server configuration not found for: ${serverInfo.name}`, {
1343
+ cause: error,
1344
+ });
1279
1345
  }
1280
1346
  // Match initial connection expansion before URL validation and transport creation.
1281
1347
  const server = replaceEnvVars(rawConfig);
@@ -2762,9 +2828,14 @@ export const toggleServerStatus = async (name, enabled) => {
2762
2828
  }
2763
2829
  };
2764
2830
  const getMcpAppsRouteContext = async (sessionId, group) => {
2765
- if (!sessionId ||
2766
- isSmartRoutingGroup(group) ||
2767
- !hasMcpAppsCapability(servers[sessionId]?.getClientCapabilities())) {
2831
+ // Stateless (2026-07-28) requests have no session; capabilities arrive per request.
2832
+ const requestContext = RequestContextService.getInstance().getRequestContext();
2833
+ const clientCapabilities = requestContext?.stateless
2834
+ ? requestContext.clientCapabilities
2835
+ : sessionId
2836
+ ? servers[sessionId]?.getClientCapabilities()
2837
+ : undefined;
2838
+ if (isSmartRoutingGroup(group) || !hasMcpAppsCapability(clientCapabilities)) {
2768
2839
  return { enabled: false };
2769
2840
  }
2770
2841
  const { filteredServerInfos } = await getFilteredServerInfosForGroup(group);
@@ -3043,12 +3114,19 @@ const handleCallToolRequestImpl = async (request, extra) => {
3043
3114
  extra?.username ||
3044
3115
  (requestContextService.getKeyKindContext() === 'system' ? 'system' : undefined) ||
3045
3116
  undefined;
3046
- let appsRouteContext = { enabled: false };
3117
+ let appsRouteContext;
3047
3118
  const keyId = bearerKeyContext.keyId || extra?.keyId || undefined;
3048
3119
  const keyName = bearerKeyContext.keyName || extra?.keyName || undefined;
3049
3120
  const sourceIp = requestContextService.getRequestContext()?.remoteAddress || undefined;
3050
3121
  let hostedReservation = null;
3051
3122
  let releaseOnDemandToolCall;
3123
+ let clientStateLease;
3124
+ const getToolStateId = (info, legacyId) => {
3125
+ if (!requestContextService.getRequestContext()?.stateless)
3126
+ return legacyId;
3127
+ clientStateLease ?? (clientStateLease = acquireClientState(info));
3128
+ return clientStateLease.id;
3129
+ };
3052
3130
  const reserveHostedIfNeeded = async (serverName, toolName) => {
3053
3131
  const hostedAuth = requestContextService.getHostedAuthContext();
3054
3132
  assertHostedToolAllowed(hostedAuth, serverName, toolName);
@@ -3172,11 +3250,14 @@ const handleCallToolRequestImpl = async (request, extra) => {
3172
3250
  }
3173
3251
  }
3174
3252
  }
3175
- if (targetServerInfo.config?.openapi?.cookieSession && !cookieSessionId) {
3253
+ const stateId = targetServerInfo.config?.openapi?.cookieSession
3254
+ ? getToolStateId(targetServerInfo, cookieSessionId)
3255
+ : cookieSessionId;
3256
+ if (targetServerInfo.config?.openapi?.cookieSession && !stateId) {
3176
3257
  throw new Error(`OpenAPI server '${targetServerInfo.name}' requires cookie-session state and is not yet available on stateless MCP requests`);
3177
3258
  }
3178
3259
  await reserveHostedIfNeeded(targetServerInfo.name, cleanToolName);
3179
- const result = await openApiClient.callTool(cleanToolName, finalArgs, passthroughHeaders, false, cookieSessionId);
3260
+ const result = await openApiClient.callTool(cleanToolName, finalArgs, passthroughHeaders, false, stateId);
3180
3261
  await settleHostedIfNeeded({
3181
3262
  success: true,
3182
3263
  requestContent: finalArgs,
@@ -3221,11 +3302,16 @@ const handleCallToolRequestImpl = async (request, extra) => {
3221
3302
  let isolatedCtx;
3222
3303
  if (targetServerInfo.config?.perSessionClient &&
3223
3304
  !hasCredentialTemplate(targetServerInfo.config)) {
3224
- if (!sessionId) {
3305
+ const stateId = getToolStateId(targetServerInfo, sessionId);
3306
+ if (!stateId) {
3225
3307
  throw new Error(`Server '${targetServerInfo.name}' requires per-session client isolation and is not yet available on stateless MCP requests`);
3226
3308
  }
3227
- const isolated = await getOrCreateIsolatedClient(sessionId, targetServerInfo);
3228
- isolatedCtx = { sessionId, client: isolated.client, transport: isolated.transport };
3309
+ const isolated = await getOrCreateIsolatedClient(stateId, targetServerInfo);
3310
+ isolatedCtx = {
3311
+ sessionId: stateId,
3312
+ client: isolated.client,
3313
+ transport: isolated.transport,
3314
+ };
3229
3315
  }
3230
3316
  else if (!targetServerInfo.client) {
3231
3317
  throw new Error(`Client not found for server: ${targetServerInfo.name}`);
@@ -3343,11 +3429,14 @@ const handleCallToolRequestImpl = async (request, extra) => {
3343
3429
  }
3344
3430
  }
3345
3431
  const finalArgs = request.params.arguments || {};
3346
- if (serverInfo.config?.openapi?.cookieSession && !cookieSessionId) {
3432
+ const stateId = serverInfo.config?.openapi?.cookieSession
3433
+ ? getToolStateId(serverInfo, cookieSessionId)
3434
+ : cookieSessionId;
3435
+ if (serverInfo.config?.openapi?.cookieSession && !stateId) {
3347
3436
  throw new Error(`OpenAPI server '${serverInfo.name}' requires cookie-session state and is not yet available on stateless MCP requests`);
3348
3437
  }
3349
3438
  await reserveHostedIfNeeded(serverInfo.name, cleanToolName);
3350
- const result = await openApiClient.callTool(cleanToolName, finalArgs, passthroughHeaders, false, cookieSessionId);
3439
+ const result = await openApiClient.callTool(cleanToolName, finalArgs, passthroughHeaders, false, stateId);
3351
3440
  await settleHostedIfNeeded({
3352
3441
  success: true,
3353
3442
  requestContent: finalArgs,
@@ -3391,11 +3480,12 @@ const handleCallToolRequestImpl = async (request, extra) => {
3391
3480
  // For servers with perSessionClient: true, use a per-session dedicated client
3392
3481
  let isolatedCtx;
3393
3482
  if (serverInfo.config?.perSessionClient && !hasCredentialTemplate(serverInfo.config)) {
3394
- if (!sessionId) {
3483
+ const stateId = getToolStateId(serverInfo, sessionId);
3484
+ if (!stateId) {
3395
3485
  throw new Error(`Server '${serverInfo.name}' requires per-session client isolation and is not yet available on stateless MCP requests`);
3396
3486
  }
3397
- const isolated = await getOrCreateIsolatedClient(sessionId, serverInfo);
3398
- isolatedCtx = { sessionId, client: isolated.client, transport: isolated.transport };
3487
+ const isolated = await getOrCreateIsolatedClient(stateId, serverInfo);
3488
+ isolatedCtx = { sessionId: stateId, client: isolated.client, transport: isolated.transport };
3399
3489
  }
3400
3490
  else if (!serverInfo.client) {
3401
3491
  throw new Error(`Client not found for server: ${serverInfo.name}`);
@@ -3487,6 +3577,7 @@ const handleCallToolRequestImpl = async (request, extra) => {
3487
3577
  };
3488
3578
  }
3489
3579
  finally {
3580
+ clientStateLease?.release();
3490
3581
  releaseOnDemandToolCall?.();
3491
3582
  }
3492
3583
  };