@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.
- package/dist/clients/openapi.js +20 -4
- package/dist/clients/openapi.js.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/controllers/oauthCallbackController.js +1 -1
- package/dist/controllers/oauthCallbackController.js.map +1 -1
- package/dist/controllers/toolController.js +2 -1
- package/dist/controllers/toolController.js.map +1 -1
- package/dist/middlewares/auth.js +1 -1
- package/dist/middlewares/auth.js.map +1 -1
- package/dist/services/mcpOAuthProvider.js +25 -0
- package/dist/services/mcpOAuthProvider.js.map +1 -1
- package/dist/services/mcpService.js +106 -15
- package/dist/services/mcpService.js.map +1 -1
- package/dist/services/oauthClientRegistration.js +1 -1
- package/dist/services/oauthClientRegistration.js.map +1 -1
- package/dist/services/oauthSettingsStore.js +5 -0
- package/dist/services/oauthSettingsStore.js.map +1 -1
- package/dist/services/proxy.js +1 -1
- package/dist/services/proxy.js.map +1 -1
- package/dist/services/requestContextService.js.map +1 -1
- package/dist/services/sseService.js +14 -1
- package/dist/services/sseService.js.map +1 -1
- package/dist/services/vectorSearchService.js +3 -1
- package/dist/services/vectorSearchService.js.map +1 -1
- package/dist/utils/oauthConsentResource.js +1 -1
- package/dist/utils/oauthConsentResource.js.map +1 -1
- package/frontend/dist/assets/{ActivityPage-Y8gIRxkd.js → ActivityPage-DPlbWRuE.js} +2 -2
- package/frontend/dist/assets/{ActivityPage-Y8gIRxkd.js.map → ActivityPage-DPlbWRuE.js.map} +1 -1
- package/frontend/dist/assets/{ConfirmDialog-uWBwm8J1.js → ConfirmDialog-BYdfb115.js} +2 -2
- package/frontend/dist/assets/{ConfirmDialog-uWBwm8J1.js.map → ConfirmDialog-BYdfb115.js.map} +1 -1
- package/frontend/dist/assets/{CopyClientConfigDialog-BHijFbyw.js → CopyClientConfigDialog-BmrvVqKm.js} +2 -2
- package/frontend/dist/assets/{CopyClientConfigDialog-BHijFbyw.js.map → CopyClientConfigDialog-BmrvVqKm.js.map} +1 -1
- package/frontend/dist/assets/{CredentialsPage-1UqVqEMQ.js → CredentialsPage-DUa5COar.js} +2 -2
- package/frontend/dist/assets/{CredentialsPage-1UqVqEMQ.js.map → CredentialsPage-DUa5COar.js.map} +1 -1
- package/frontend/dist/assets/{Dashboard-CswKNreX.js → Dashboard-DaTYK_nx.js} +2 -2
- package/frontend/dist/assets/{Dashboard-CswKNreX.js.map → Dashboard-DaTYK_nx.js.map} +1 -1
- package/frontend/dist/assets/{DeleteDialog-BYhrMGZo.js → DeleteDialog-BaCsEkJA.js} +2 -2
- package/frontend/dist/assets/{DeleteDialog-BYhrMGZo.js.map → DeleteDialog-BaCsEkJA.js.map} +1 -1
- package/frontend/dist/assets/{EndpointCopy-B0yIEBjO.js → EndpointCopy-BCfqwDEK.js} +2 -2
- package/frontend/dist/assets/{EndpointCopy-B0yIEBjO.js.map → EndpointCopy-BCfqwDEK.js.map} +1 -1
- package/frontend/dist/assets/{GroupsPage-KJJsAprG.js → GroupsPage-Cuxz5FyS.js} +2 -2
- package/frontend/dist/assets/{GroupsPage-KJJsAprG.js.map → GroupsPage-Cuxz5FyS.js.map} +1 -1
- package/frontend/dist/assets/{LoginPage-B1heSsJ6.js → LoginPage-yTpHn5Tx.js} +2 -2
- package/frontend/dist/assets/{LoginPage-B1heSsJ6.js.map → LoginPage-yTpHn5Tx.js.map} +1 -1
- package/frontend/dist/assets/LogsPage-BbSlC06Q.js +2 -0
- package/frontend/dist/assets/LogsPage-BbSlC06Q.js.map +1 -0
- package/frontend/dist/assets/{MarketPage-D0RXyf6g.js → MarketPage-nAF8uFkv.js} +2 -2
- package/frontend/dist/assets/{MarketPage-D0RXyf6g.js.map → MarketPage-nAF8uFkv.js.map} +1 -1
- package/frontend/dist/assets/{OAuthConsentPage-DLveAKh7.js → OAuthConsentPage-W05BBBpL.js} +2 -2
- package/frontend/dist/assets/{OAuthConsentPage-DLveAKh7.js.map → OAuthConsentPage-W05BBBpL.js.map} +1 -1
- package/frontend/dist/assets/{Pagination-CLUvn47x.js → Pagination-BRqqU5Ct.js} +2 -2
- package/frontend/dist/assets/{Pagination-CLUvn47x.js.map → Pagination-BRqqU5Ct.js.map} +1 -1
- package/frontend/dist/assets/PromptsPage-Bq7xptCw.js +2 -0
- package/frontend/dist/assets/{PromptsPage-cPHf4CM9.js.map → PromptsPage-Bq7xptCw.js.map} +1 -1
- package/frontend/dist/assets/ResourcesPage-CsL39aff.js +2 -0
- package/frontend/dist/assets/{ResourcesPage-DcXEwXg9.js.map → ResourcesPage-CsL39aff.js.map} +1 -1
- package/frontend/dist/assets/{ServersPage-CIPZUbqT.js → ServersPage-p9LgWUzS.js} +4 -4
- package/frontend/dist/assets/{ServersPage-CIPZUbqT.js.map → ServersPage-p9LgWUzS.js.map} +1 -1
- package/frontend/dist/assets/{SettingsPage-vMxO3bOc.js → SettingsPage-DobkN2nZ.js} +2 -2
- package/frontend/dist/assets/{SettingsPage-vMxO3bOc.js.map → SettingsPage-DobkN2nZ.js.map} +1 -1
- package/frontend/dist/assets/{StatusDot-LGEKnAGN.js → StatusDot-DnCf86-T.js} +2 -2
- package/frontend/dist/assets/{StatusDot-LGEKnAGN.js.map → StatusDot-DnCf86-T.js.map} +1 -1
- package/frontend/dist/assets/{ToggleGroup-Ad_I2spO.js → ToggleGroup-DFjmIz4f.js} +2 -2
- package/frontend/dist/assets/{ToggleGroup-Ad_I2spO.js.map → ToggleGroup-DFjmIz4f.js.map} +1 -1
- package/frontend/dist/assets/{UsersPage-CbfajFCc.js → UsersPage-g_A6Nis1.js} +2 -2
- package/frontend/dist/assets/{UsersPage-CbfajFCc.js.map → UsersPage-g_A6Nis1.js.map} +1 -1
- package/frontend/dist/assets/{clipboard-CjtPOmJs.js → clipboard-C6pDz_7o.js} +2 -2
- package/frontend/dist/assets/{clipboard-CjtPOmJs.js.map → clipboard-C6pDz_7o.js.map} +1 -1
- package/frontend/dist/assets/{framework-vendor-_YL27Pfc.js → framework-vendor-CQFm9vKd.js} +12 -12
- package/frontend/dist/assets/{framework-vendor-_YL27Pfc.js.map → framework-vendor-CQFm9vKd.js.map} +1 -1
- package/frontend/dist/assets/{i18n-vendor-BMaj5qV8.js → i18n-vendor-DVmjrzgW.js} +2 -2
- package/frontend/dist/assets/{i18n-vendor-BMaj5qV8.js.map → i18n-vendor-DVmjrzgW.js.map} +1 -1
- package/frontend/dist/assets/icons-vendor-CEwDZ1lN.js +362 -0
- package/frontend/dist/assets/icons-vendor-CEwDZ1lN.js.map +1 -0
- package/frontend/dist/assets/{index-DJUr-vo3.js → index-BlEZfPQl.js} +3 -3
- package/frontend/dist/assets/{index-DJUr-vo3.js.map → index-BlEZfPQl.js.map} +1 -1
- package/frontend/dist/assets/{resourceService-D4dgqjzY.js → resourceService-CAxgCzyV.js} +2 -2
- package/frontend/dist/assets/{resourceService-D4dgqjzY.js.map → resourceService-CAxgCzyV.js.map} +1 -1
- package/frontend/dist/assets/{useCostData-CT2iwvzx.js → useCostData-wcUc6g7_.js} +2 -2
- package/frontend/dist/assets/{useCostData-CT2iwvzx.js.map → useCostData-wcUc6g7_.js.map} +1 -1
- package/frontend/dist/assets/{variableDetection-BHrhKDaj.js → variableDetection-DxVZhKpb.js} +2 -2
- package/frontend/dist/assets/{variableDetection-BHrhKDaj.js.map → variableDetection-DxVZhKpb.js.map} +1 -1
- package/frontend/dist/index.html +4 -4
- package/package.json +5 -5
- package/frontend/dist/assets/LogsPage-C4BOgOSW.js +0 -2
- package/frontend/dist/assets/LogsPage-C4BOgOSW.js.map +0 -1
- package/frontend/dist/assets/PromptsPage-cPHf4CM9.js +0 -2
- package/frontend/dist/assets/ResourcesPage-DcXEwXg9.js +0 -2
- package/frontend/dist/assets/icons-vendor-CvmEvkQ-.js +0 -337
- 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
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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(
|
|
3228
|
-
isolatedCtx = {
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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(
|
|
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
|
};
|