@samanhappy/mcphub 1.0.42 → 1.0.43

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 (94) hide show
  1. package/dist/config/index.js +19 -9
  2. package/dist/config/index.js.map +1 -1
  3. package/dist/controllers/groupController.js +14 -0
  4. package/dist/controllers/groupController.js.map +1 -1
  5. package/dist/controllers/serverController.js +102 -3
  6. package/dist/controllers/serverController.js.map +1 -1
  7. package/dist/controllers/smartRoutingController.js +15 -5
  8. package/dist/controllers/smartRoutingController.js.map +1 -1
  9. package/dist/db/entities/Group.js.map +1 -1
  10. package/dist/db/repositories/VectorEmbeddingRepository.js +21 -1
  11. package/dist/db/repositories/VectorEmbeddingRepository.js.map +1 -1
  12. package/dist/server.js +3 -2
  13. package/dist/server.js.map +1 -1
  14. package/dist/services/contextCostService.js +11 -4
  15. package/dist/services/contextCostService.js.map +1 -1
  16. package/dist/services/groupService.js +19 -2
  17. package/dist/services/groupService.js.map +1 -1
  18. package/dist/services/mcpOAuthProvider.js +26 -10
  19. package/dist/services/mcpOAuthProvider.js.map +1 -1
  20. package/dist/services/mcpService.js +138 -34
  21. package/dist/services/mcpService.js.map +1 -1
  22. package/dist/services/oauthClientRegistration.js +53 -8
  23. package/dist/services/oauthClientRegistration.js.map +1 -1
  24. package/dist/services/oauthSettingsStore.js +5 -3
  25. package/dist/services/oauthSettingsStore.js.map +1 -1
  26. package/dist/services/smartRoutingService.js +109 -50
  27. package/dist/services/smartRoutingService.js.map +1 -1
  28. package/dist/services/sseService.js +49 -2
  29. package/dist/services/sseService.js.map +1 -1
  30. package/dist/services/vectorSearchService.js +72 -18
  31. package/dist/services/vectorSearchService.js.map +1 -1
  32. package/dist/utils/mcpRequestGroup.js +8 -0
  33. package/dist/utils/mcpRequestGroup.js.map +1 -0
  34. package/dist/utils/serverConfigPersistence.js +13 -2
  35. package/dist/utils/serverConfigPersistence.js.map +1 -1
  36. package/dist/utils/smartRouting.js +32 -0
  37. package/dist/utils/smartRouting.js.map +1 -1
  38. package/frontend/dist/assets/{ActivityPage-DTouw-He.js → ActivityPage-Y8gIRxkd.js} +2 -2
  39. package/frontend/dist/assets/{ActivityPage-DTouw-He.js.map → ActivityPage-Y8gIRxkd.js.map} +1 -1
  40. package/frontend/dist/assets/{CopyClientConfigDialog-BtO0YcVK.js → CopyClientConfigDialog-BHijFbyw.js} +2 -2
  41. package/frontend/dist/assets/{CopyClientConfigDialog-BtO0YcVK.js.map → CopyClientConfigDialog-BHijFbyw.js.map} +1 -1
  42. package/frontend/dist/assets/{CredentialsPage-BeSMd9m4.js → CredentialsPage-1UqVqEMQ.js} +2 -2
  43. package/frontend/dist/assets/{CredentialsPage-BeSMd9m4.js.map → CredentialsPage-1UqVqEMQ.js.map} +1 -1
  44. package/frontend/dist/assets/{Dashboard-DuaKaJAK.js → Dashboard-CswKNreX.js} +2 -2
  45. package/frontend/dist/assets/{Dashboard-DuaKaJAK.js.map → Dashboard-CswKNreX.js.map} +1 -1
  46. package/frontend/dist/assets/{EndpointCopy-Bos3ZWUc.js → EndpointCopy-B0yIEBjO.js} +2 -2
  47. package/frontend/dist/assets/{EndpointCopy-Bos3ZWUc.js.map → EndpointCopy-B0yIEBjO.js.map} +1 -1
  48. package/frontend/dist/assets/GroupsPage-KJJsAprG.js +33 -0
  49. package/frontend/dist/assets/GroupsPage-KJJsAprG.js.map +1 -0
  50. package/frontend/dist/assets/{LoginPage-DUnjHB-q.js → LoginPage-B1heSsJ6.js} +2 -2
  51. package/frontend/dist/assets/{LoginPage-DUnjHB-q.js.map → LoginPage-B1heSsJ6.js.map} +1 -1
  52. package/frontend/dist/assets/{LogsPage-BxjYh1MS.js → LogsPage-C4BOgOSW.js} +2 -2
  53. package/frontend/dist/assets/{LogsPage-BxjYh1MS.js.map → LogsPage-C4BOgOSW.js.map} +1 -1
  54. package/frontend/dist/assets/{MarketPage-BnxAofuO.js → MarketPage-D0RXyf6g.js} +2 -2
  55. package/frontend/dist/assets/{MarketPage-BnxAofuO.js.map → MarketPage-D0RXyf6g.js.map} +1 -1
  56. package/frontend/dist/assets/{OAuthConsentPage-rAx5ELdp.js → OAuthConsentPage-DLveAKh7.js} +2 -2
  57. package/frontend/dist/assets/{OAuthConsentPage-rAx5ELdp.js.map → OAuthConsentPage-DLveAKh7.js.map} +1 -1
  58. package/frontend/dist/assets/{PromptsPage-Bh4Woe2Q.js → PromptsPage-cPHf4CM9.js} +2 -2
  59. package/frontend/dist/assets/{PromptsPage-Bh4Woe2Q.js.map → PromptsPage-cPHf4CM9.js.map} +1 -1
  60. package/frontend/dist/assets/{ResourcesPage-BREUhWYG.js → ResourcesPage-DcXEwXg9.js} +2 -2
  61. package/frontend/dist/assets/{ResourcesPage-BREUhWYG.js.map → ResourcesPage-DcXEwXg9.js.map} +1 -1
  62. package/frontend/dist/assets/{ServersPage-DCqew7Dq.js → ServersPage-CIPZUbqT.js} +4 -4
  63. package/frontend/dist/assets/{ServersPage-DCqew7Dq.js.map → ServersPage-CIPZUbqT.js.map} +1 -1
  64. package/frontend/dist/assets/SettingsPage-vMxO3bOc.js +12 -0
  65. package/frontend/dist/assets/SettingsPage-vMxO3bOc.js.map +1 -0
  66. package/frontend/dist/assets/{StatusDot-CYPIH-Iz.js → StatusDot-LGEKnAGN.js} +2 -2
  67. package/frontend/dist/assets/{StatusDot-CYPIH-Iz.js.map → StatusDot-LGEKnAGN.js.map} +1 -1
  68. package/frontend/dist/assets/{ToggleGroup-B4Lt01jM.js → ToggleGroup-Ad_I2spO.js} +2 -2
  69. package/frontend/dist/assets/{ToggleGroup-B4Lt01jM.js.map → ToggleGroup-Ad_I2spO.js.map} +1 -1
  70. package/frontend/dist/assets/{UsersPage-LvOfLsJc.js → UsersPage-CbfajFCc.js} +2 -2
  71. package/frontend/dist/assets/{UsersPage-LvOfLsJc.js.map → UsersPage-CbfajFCc.js.map} +1 -1
  72. package/frontend/dist/assets/{clipboard--jmjkvaW.js → clipboard-CjtPOmJs.js} +2 -2
  73. package/frontend/dist/assets/{clipboard--jmjkvaW.js.map → clipboard-CjtPOmJs.js.map} +1 -1
  74. package/frontend/dist/assets/{icons-vendor-CNEaJWqW.js → icons-vendor-CvmEvkQ-.js} +69 -64
  75. package/frontend/dist/assets/icons-vendor-CvmEvkQ-.js.map +1 -0
  76. package/frontend/dist/assets/index-Cla8rRxG.css +1 -0
  77. package/frontend/dist/assets/index-DJUr-vo3.js +3 -0
  78. package/frontend/dist/assets/index-DJUr-vo3.js.map +1 -0
  79. package/frontend/dist/assets/{resourceService-DkIvmxRC.js → resourceService-D4dgqjzY.js} +2 -2
  80. package/frontend/dist/assets/{resourceService-DkIvmxRC.js.map → resourceService-D4dgqjzY.js.map} +1 -1
  81. package/frontend/dist/assets/{useCostData-B-uPi7i8.js → useCostData-CT2iwvzx.js} +2 -2
  82. package/frontend/dist/assets/{useCostData-B-uPi7i8.js.map → useCostData-CT2iwvzx.js.map} +1 -1
  83. package/frontend/dist/assets/{variableDetection-RlrBM33Q.js → variableDetection-BHrhKDaj.js} +2 -2
  84. package/frontend/dist/assets/{variableDetection-RlrBM33Q.js.map → variableDetection-BHrhKDaj.js.map} +1 -1
  85. package/frontend/dist/index.html +3 -3
  86. package/package.json +1 -1
  87. package/frontend/dist/assets/GroupsPage-Cv3lZSCh.js +0 -33
  88. package/frontend/dist/assets/GroupsPage-Cv3lZSCh.js.map +0 -1
  89. package/frontend/dist/assets/SettingsPage-DGAB-Jy8.js +0 -12
  90. package/frontend/dist/assets/SettingsPage-DGAB-Jy8.js.map +0 -1
  91. package/frontend/dist/assets/icons-vendor-CNEaJWqW.js.map +0 -1
  92. package/frontend/dist/assets/index-Cv7we3z-.css +0 -1
  93. package/frontend/dist/assets/index-qPkrYLbl.js +0 -3
  94. package/frontend/dist/assets/index-qPkrYLbl.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { getMcpRequestGroup } from '../utils/mcpRequestGroup.js';
1
2
  import { LegacyMcpClient } from '../clients/legacyMcpClient.js';
2
3
  import { LEGACY_PROTOCOL_VERSIONS } from '../utils/mcpProtocol.js';
3
4
  import { AsyncLocalStorage } from 'node:async_hooks';
@@ -24,7 +25,7 @@ import config from '../config/index.js';
24
25
  import { validateServerName } from '../utils/serverNameValidation.js';
25
26
  import { getGroup } from './sseService.js';
26
27
  import { getServerConfigInGroup, normalizeGroupServers } from './groupService.js';
27
- import { removeServerToolEmbeddings, saveToolsAsVectorEmbeddings } from './vectorSearchService.js';
28
+ import { removeServerToolEmbeddings, removeToolEmbeddings, saveToolsAsVectorEmbeddings, } from './vectorSearchService.js';
28
29
  import { OpenAPIClient } from '../clients/openapi.js';
29
30
  import { RequestContextService } from './requestContextService.js';
30
31
  import { getDataService } from './services.js';
@@ -517,7 +518,24 @@ const applyDescriptionOverridesForEmbedding = async (serverName, tools) => {
517
518
  : tool;
518
519
  });
519
520
  };
520
- const syncToolsAsVectorEmbeddings = async (serverName, tools, options) => {
521
+ // Tail of the embedding tasks queued per server. A per-user connect and a
522
+ // reindex, or two users connecting at once, often race on the same tool set;
523
+ // queued behind the first run, the second one hits the skip check in
524
+ // saveToolsAsVectorEmbeddings instead of embedding every tool a second time.
525
+ const embeddingSyncTails = new Map();
526
+ // Run `task` after every embedding task already queued for the server. A task
527
+ // must not wait for another queued task of the same server, or both stall.
528
+ const enqueueEmbeddingTask = (serverName, task) => {
529
+ const run = (embeddingSyncTails.get(serverName) ?? Promise.resolve()).then(task);
530
+ const tail = run.catch(() => undefined);
531
+ embeddingSyncTails.set(serverName, tail);
532
+ void tail.then(() => {
533
+ if (embeddingSyncTails.get(serverName) === tail)
534
+ embeddingSyncTails.delete(serverName);
535
+ });
536
+ return run;
537
+ };
538
+ export const syncToolsAsVectorEmbeddings = (serverName, tools, options) => enqueueEmbeddingTask(serverName, async () => {
521
539
  const toolsWithOverrides = await applyDescriptionOverridesForEmbedding(serverName, tools);
522
540
  const modelVisibleTools = filterModelVisibleTools(toolsWithOverrides);
523
541
  if (modelVisibleTools.length === 0) {
@@ -527,6 +545,38 @@ const syncToolsAsVectorEmbeddings = async (serverName, tools, options) => {
527
545
  return;
528
546
  }
529
547
  await saveToolsAsVectorEmbeddings(serverName, modelVisibleTools, options);
548
+ });
549
+ /**
550
+ * Embed the tools of a credential server, which never connects globally.
551
+ * Tools disabled in the server's tools config are never embedded: search
552
+ * filters them out of the hits anyway, but only after they have taken result
553
+ * slots. A toggle reaches the index without extra wiring: the tools config is
554
+ * part of the runtime revision, so the next acquire reconnects and re-syncs.
555
+ *
556
+ * Two callers, two scopes:
557
+ * - `partial: true` (every per-user connect): the list is what one user's
558
+ * credentials show, and users may see different tools. The rows of the
559
+ * listed, enabled tools are added or refreshed and nothing else is pruned,
560
+ * so tools only other users see stay indexed. The listed tools that are
561
+ * disabled are removed by name, queued before the sync.
562
+ * - otherwise (the reindex endpoint, which passes the union of every binding's
563
+ * tools): the list is complete, so rows of tools outside it are pruned.
564
+ *
565
+ * Resolves to the number of tools handed to the vector store.
566
+ */
567
+ export const syncCredentialServerToolEmbeddings = async (serverName, tools, options) => {
568
+ const enabledTools = await filterToolsByConfig(serverName, tools);
569
+ if (options?.partial) {
570
+ const enabledNames = new Set(enabledTools.map((tool) => tool.name));
571
+ const disabledNames = tools
572
+ .map((tool) => tool.name)
573
+ .filter((toolName) => !enabledNames.has(toolName));
574
+ if (disabledNames.length > 0) {
575
+ void enqueueEmbeddingTask(serverName, () => removeToolEmbeddings(serverName, disabledNames)).catch(() => undefined);
576
+ }
577
+ }
578
+ await syncToolsAsVectorEmbeddings(serverName, enabledTools, options);
579
+ return enabledTools.length;
530
580
  };
531
581
  // Normalize prompt payload to satisfy MCP ListPrompts response schema
532
582
  const normalizePromptForList = (prompt) => {
@@ -2880,17 +2930,7 @@ const projectToolForDownstream = (serverName, tool, appsRouteContext, serverConf
2880
2930
  name: projectNameForGroup(projectedTool.name, serverName, serverConfig),
2881
2931
  };
2882
2932
  };
2883
- const handleListToolsRequestImpl = async (_, extra) => {
2884
- const sessionId = extra.sessionId || '';
2885
- const group = getGroup(sessionId);
2886
- logger.log(`Handling ListToolsRequest for group: ${group}`);
2887
- // Special handling for $smart group to return smart routing tools
2888
- // Support both $smart and $smart/{group} patterns
2889
- if (isSmartRoutingGroup(group)) {
2890
- return getSmartRoutingTools(group);
2891
- }
2892
- const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
2893
- const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
2933
+ const listGroupTools = async (group, filteredServerInfos, serverConfigsByName, appsRouteContext) => {
2894
2934
  // If the startup prime of an on-demand server is still in flight, wait for it
2895
2935
  // so this list reflects the freshly cached tools instead of returning empty.
2896
2936
  // No wake is triggered from list itself; the prime handles that. See #1029.
@@ -2920,8 +2960,60 @@ const handleListToolsRequestImpl = async (_, extra) => {
2920
2960
  }));
2921
2961
  }
2922
2962
  }
2963
+ return allTools;
2964
+ };
2965
+ // Names the call handler always routes to a meta-tool on a Smart Routing session,
2966
+ // whichever of them the current mode lists (see handleCallToolRequestImpl)
2967
+ const SMART_ROUTING_META_TOOL_NAMES = new Set(['search_tools', 'describe_tool', 'call_tool']);
2968
+ // Tools a $smart/<group> lists next to its meta-tools: each member's pinnedTools,
2969
+ // narrowed to its tools selection and then run through the same filtering and
2970
+ // projection as a direct group listing.
2971
+ const getPinnedSmartRoutingTools = async (group) => {
2972
+ const lookupGroup = getGroupLookupName(group);
2973
+ if (!lookupGroup) {
2974
+ return [];
2975
+ }
2976
+ const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(lookupGroup);
2977
+ const pinnedConfigsByName = new Map();
2978
+ for (const [serverName, serverConfig] of serverConfigsByName) {
2979
+ const selection = serverConfig.tools;
2980
+ const pinnedTools = (serverConfig.pinnedTools ?? []).filter((toolName) => !Array.isArray(selection) || selection.includes(toolName));
2981
+ if (pinnedTools.length > 0) {
2982
+ pinnedConfigsByName.set(serverName, { ...serverConfig, tools: pinnedTools });
2983
+ }
2984
+ }
2985
+ if (pinnedConfigsByName.size === 0) {
2986
+ return [];
2987
+ }
2988
+ return listGroupTools(lookupGroup, filteredServerInfos.filter((serverInfo) => pinnedConfigsByName.has(serverInfo.name)), pinnedConfigsByName, { enabled: false });
2989
+ };
2990
+ const handleListToolsRequestImpl = async (_, extra) => {
2991
+ const sessionId = extra.sessionId || '';
2992
+ const group = getMcpRequestGroup(extra);
2993
+ logger.log(`Handling ListToolsRequest for group: ${group}`);
2994
+ // Special handling for $smart group to return smart routing tools
2995
+ // Support both $smart and $smart/{group} patterns
2996
+ if (isSmartRoutingGroup(group)) {
2997
+ const smartRoutingTools = await getSmartRoutingTools(group);
2998
+ let pinnedTools = [];
2999
+ try {
3000
+ // A pin named like a meta-tool (e.g. alias "describe" + separator "_" +
3001
+ // tool "tool") would be intercepted by it on call, even in a mode that
3002
+ // does not list that meta-tool, so it is not listed at all
3003
+ pinnedTools = (await getPinnedSmartRoutingTools(group)).filter((tool) => !SMART_ROUTING_META_TOOL_NAMES.has(tool.name));
3004
+ }
3005
+ catch (error) {
3006
+ // Pins are an optimisation: failing to resolve them must not cost the meta-tools
3007
+ logger.warn(`Failed to list pinned tools for ${group}; returning meta-tools only`, error);
3008
+ }
3009
+ return pinnedTools.length > 0
3010
+ ? { tools: [...smartRoutingTools.tools, ...pinnedTools] }
3011
+ : smartRoutingTools;
3012
+ }
3013
+ const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
3014
+ const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
2923
3015
  return {
2924
- tools: allTools,
3016
+ tools: await listGroupTools(group, filteredServerInfos, serverConfigsByName, appsRouteContext),
2925
3017
  };
2926
3018
  };
2927
3019
  const handleCallToolRequestImpl = async (request, extra) => {
@@ -2946,7 +3038,7 @@ const handleCallToolRequestImpl = async (request, extra) => {
2946
3038
  ].find((id) => typeof id === 'string' && id.length > 0 && !isSyntheticSessionFallback(id));
2947
3039
  // Extract group and key info from request context (set by SSE/HTTP handlers)
2948
3040
  // Fallback to extra for backward compatibility (e.g., direct API calls)
2949
- const group = requestContextService.getGroupContext() || extra?.group || getGroup(sessionId) || undefined;
3041
+ const group = getMcpRequestGroup(extra);
2950
3042
  const username = requestContextService.getUsernameContext() ||
2951
3043
  extra?.username ||
2952
3044
  (requestContextService.getKeyKindContext() === 'system' ? 'system' : undefined) ||
@@ -2977,12 +3069,12 @@ const handleCallToolRequestImpl = async (request, extra) => {
2977
3069
  // Special handling for smart routing tools
2978
3070
  if (request.params.name === 'search_tools') {
2979
3071
  const { query, limit = 10 } = request.params.arguments || {};
2980
- return await handleSearchToolsRequest(query, limit, sessionId);
3072
+ return await handleSearchToolsRequest(query, limit, sessionId, group);
2981
3073
  }
2982
3074
  // Special handling for describe_tool (progressive disclosure mode)
2983
3075
  if (request.params.name === 'describe_tool') {
2984
3076
  const { toolName } = request.params.arguments || {};
2985
- return await handleDescribeToolRequest(toolName, sessionId);
3077
+ return await handleDescribeToolRequest(toolName, sessionId, group);
2986
3078
  }
2987
3079
  // Special handling for call_tool
2988
3080
  if (request.params.name === 'call_tool') {
@@ -3080,6 +3172,9 @@ const handleCallToolRequestImpl = async (request, extra) => {
3080
3172
  }
3081
3173
  }
3082
3174
  }
3175
+ if (targetServerInfo.config?.openapi?.cookieSession && !cookieSessionId) {
3176
+ throw new Error(`OpenAPI server '${targetServerInfo.name}' requires cookie-session state and is not yet available on stateless MCP requests`);
3177
+ }
3083
3178
  await reserveHostedIfNeeded(targetServerInfo.name, cleanToolName);
3084
3179
  const result = await openApiClient.callTool(cleanToolName, finalArgs, passthroughHeaders, false, cookieSessionId);
3085
3180
  await settleHostedIfNeeded({
@@ -3125,8 +3220,10 @@ const handleCallToolRequestImpl = async (request, extra) => {
3125
3220
  // For servers with perSessionClient: true, use a per-session dedicated client
3126
3221
  let isolatedCtx;
3127
3222
  if (targetServerInfo.config?.perSessionClient &&
3128
- !hasCredentialTemplate(targetServerInfo.config) &&
3129
- sessionId) {
3223
+ !hasCredentialTemplate(targetServerInfo.config)) {
3224
+ if (!sessionId) {
3225
+ throw new Error(`Server '${targetServerInfo.name}' requires per-session client isolation and is not yet available on stateless MCP requests`);
3226
+ }
3130
3227
  const isolated = await getOrCreateIsolatedClient(sessionId, targetServerInfo);
3131
3228
  isolatedCtx = { sessionId, client: isolated.client, transport: isolated.transport };
3132
3229
  }
@@ -3246,6 +3343,9 @@ const handleCallToolRequestImpl = async (request, extra) => {
3246
3343
  }
3247
3344
  }
3248
3345
  const finalArgs = request.params.arguments || {};
3346
+ if (serverInfo.config?.openapi?.cookieSession && !cookieSessionId) {
3347
+ throw new Error(`OpenAPI server '${serverInfo.name}' requires cookie-session state and is not yet available on stateless MCP requests`);
3348
+ }
3249
3349
  await reserveHostedIfNeeded(serverInfo.name, cleanToolName);
3250
3350
  const result = await openApiClient.callTool(cleanToolName, finalArgs, passthroughHeaders, false, cookieSessionId);
3251
3351
  await settleHostedIfNeeded({
@@ -3290,9 +3390,10 @@ const handleCallToolRequestImpl = async (request, extra) => {
3290
3390
  // Handle MCP servers
3291
3391
  // For servers with perSessionClient: true, use a per-session dedicated client
3292
3392
  let isolatedCtx;
3293
- if (serverInfo.config?.perSessionClient &&
3294
- !hasCredentialTemplate(serverInfo.config) &&
3295
- sessionId) {
3393
+ if (serverInfo.config?.perSessionClient && !hasCredentialTemplate(serverInfo.config)) {
3394
+ if (!sessionId) {
3395
+ throw new Error(`Server '${serverInfo.name}' requires per-session client isolation and is not yet available on stateless MCP requests`);
3396
+ }
3296
3397
  const isolated = await getOrCreateIsolatedClient(sessionId, serverInfo);
3297
3398
  isolatedCtx = { sessionId, client: isolated.client, transport: isolated.transport };
3298
3399
  }
@@ -3392,8 +3493,7 @@ const handleCallToolRequestImpl = async (request, extra) => {
3392
3493
  const handleGetPromptRequestImpl = async (request, extra) => {
3393
3494
  try {
3394
3495
  const { name, arguments: promptArgs } = request.params;
3395
- const sessionId = extra?.sessionId || '';
3396
- const group = extra?.group || getGroup(sessionId) || undefined;
3496
+ const group = getMcpRequestGroup(extra);
3397
3497
  // Check built-in prompts first
3398
3498
  const builtinPrompt = await getBuiltinPromptDao().findByName(name);
3399
3499
  if (builtinPrompt && builtinPrompt.enabled !== false) {
@@ -3479,8 +3579,7 @@ const handleGetPromptRequestImpl = async (request, extra) => {
3479
3579
  }
3480
3580
  };
3481
3581
  const handleListPromptsRequestImpl = async (_, extra) => {
3482
- const sessionId = extra.sessionId || '';
3483
- const group = getGroup(sessionId);
3582
+ const group = getMcpRequestGroup(extra);
3484
3583
  const lookupGroup = getGroupLookupName(group);
3485
3584
  logger.log(`Handling ListPromptsRequest for group: ${group}`);
3486
3585
  // Start with built-in prompts (only enabled ones)
@@ -3524,7 +3623,7 @@ const handleListPromptsRequestImpl = async (_, extra) => {
3524
3623
  };
3525
3624
  const handleListResourcesRequestImpl = async (_, extra) => {
3526
3625
  const sessionId = extra.sessionId || '';
3527
- const group = getGroup(sessionId);
3626
+ const group = getMcpRequestGroup(extra);
3528
3627
  const lookupGroup = getGroupLookupName(group);
3529
3628
  logger.log(`Handling ListResourcesRequest for group: ${group}`);
3530
3629
  const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
@@ -3569,7 +3668,7 @@ const handleListResourcesRequestImpl = async (_, extra) => {
3569
3668
  };
3570
3669
  const handleListResourceTemplatesRequestImpl = async (_, extra) => {
3571
3670
  const sessionId = extra.sessionId || '';
3572
- const group = getGroup(sessionId);
3671
+ const group = getMcpRequestGroup(extra);
3573
3672
  const lookupGroup = getGroupLookupName(group);
3574
3673
  logger.log(`Handling ListResourceTemplatesRequest for group: ${group}`);
3575
3674
  const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
@@ -3594,7 +3693,7 @@ const handleReadResourceRequestImpl = async (request, extra) => {
3594
3693
  try {
3595
3694
  const { uri } = request.params;
3596
3695
  const sessionId = extra.sessionId || '';
3597
- const group = getGroup(sessionId);
3696
+ const group = getMcpRequestGroup(extra);
3598
3697
  const lookupGroup = getGroupLookupName(group);
3599
3698
  const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
3600
3699
  // Check built-in resources first
@@ -3687,7 +3786,9 @@ const handleReadResourceRequestImpl = async (request, extra) => {
3687
3786
  };
3688
3787
  }
3689
3788
  };
3690
- // Personal runtimes never enter serverInfos, config exports, or shared embedding caches.
3789
+ // Personal runtimes never enter serverInfos or config exports. Their tool list
3790
+ // is added to the rows under the server name, the index the reindex endpoint
3791
+ // rebuilds from every binding.
3691
3792
  const createPrincipalRuntime = async (name, resolvedConfig) => {
3692
3793
  const info = {
3693
3794
  name,
@@ -3769,6 +3870,12 @@ const createPrincipalRuntime = async (name, resolvedConfig) => {
3769
3870
  };
3770
3871
  }
3771
3872
  info.status = 'connected';
3873
+ // A credential server never connects globally, so this is the only place
3874
+ // its tools are seen outside a reindex. One user's list is not the whole
3875
+ // server's, so this only adds and refreshes rows; unchanged ones are skipped.
3876
+ syncCredentialServerToolEmbeddings(name, info.tools, { partial: true }).catch(() => {
3877
+ logger.warn(`[EMBED_SYNC_ERROR] Failed to sync tool embeddings for "${name}"`);
3878
+ });
3772
3879
  return info;
3773
3880
  }
3774
3881
  catch (error) {
@@ -3815,10 +3922,7 @@ const withPrincipalServers = (handler, operation) => async (request, extra) => {
3815
3922
  if (builtin && builtin.enabled !== false)
3816
3923
  return handler(request, extra);
3817
3924
  }
3818
- const group = RequestContextService.getInstance().getGroupContext() ||
3819
- extra?.group ||
3820
- getGroup(extra?.sessionId || '') ||
3821
- undefined;
3925
+ const group = getMcpRequestGroup(extra);
3822
3926
  const { filteredServerInfos: candidates, serverConfigsByName } = await getFilteredServerInfosForGroup(getGroupLookupName(group));
3823
3927
  const requestedName = ['call_tool', 'describe_tool'].includes(request?.params?.name)
3824
3928
  ? request.params.arguments?.toolName