@samanhappy/mcphub 1.0.46 → 1.1.1

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 (109) hide show
  1. package/dist/betterAuth.js +5 -0
  2. package/dist/betterAuth.js.map +1 -1
  3. package/dist/clients/legacyMcpClient.js +30 -4
  4. package/dist/clients/legacyMcpClient.js.map +1 -1
  5. package/dist/clients/openapi.js +4 -1
  6. package/dist/clients/openapi.js.map +1 -1
  7. package/dist/config/index.js +13 -7
  8. package/dist/config/index.js.map +1 -1
  9. package/dist/constants/smartRoutingDefaults.js +23 -0
  10. package/dist/constants/smartRoutingDefaults.js.map +1 -0
  11. package/dist/controllers/authController.js +5 -0
  12. package/dist/controllers/authController.js.map +1 -1
  13. package/dist/controllers/serverController.js +49 -9
  14. package/dist/controllers/serverController.js.map +1 -1
  15. package/dist/dao/ServerDao.js.map +1 -1
  16. package/dist/dao/ServerDaoDbImpl.js +13 -1
  17. package/dist/dao/ServerDaoDbImpl.js.map +1 -1
  18. package/dist/dao/base/JsonFileBaseDao.js +11 -6
  19. package/dist/dao/base/JsonFileBaseDao.js.map +1 -1
  20. package/dist/db/entities/Server.js +4 -0
  21. package/dist/db/entities/Server.js.map +1 -1
  22. package/dist/routes/index.js +3 -5
  23. package/dist/routes/index.js.map +1 -1
  24. package/dist/services/betterAuthConfig.js +17 -0
  25. package/dist/services/betterAuthConfig.js.map +1 -1
  26. package/dist/services/mcpOAuthProvider.js +11 -0
  27. package/dist/services/mcpOAuthProvider.js.map +1 -1
  28. package/dist/services/mcpService.js +109 -23
  29. package/dist/services/mcpService.js.map +1 -1
  30. package/dist/services/oauthSettingsStore.js +1 -1
  31. package/dist/services/oauthSettingsStore.js.map +1 -1
  32. package/dist/services/proxy.js +4 -0
  33. package/dist/services/proxy.js.map +1 -1
  34. package/dist/services/smartRoutingService.js +54 -52
  35. package/dist/services/smartRoutingService.js.map +1 -1
  36. package/dist/services/sseService.js +11 -8
  37. package/dist/services/sseService.js.map +1 -1
  38. package/dist/utils/listFreshness.js +44 -0
  39. package/dist/utils/listFreshness.js.map +1 -0
  40. package/dist/utils/migration.js +8 -6
  41. package/dist/utils/migration.js.map +1 -1
  42. package/dist/utils/serverAvailability.js +9 -0
  43. package/dist/utils/serverAvailability.js.map +1 -0
  44. package/dist/utils/serverConfigPersistence.js +4 -0
  45. package/dist/utils/serverConfigPersistence.js.map +1 -1
  46. package/dist/utils/settingsSync.js +198 -0
  47. package/dist/utils/settingsSync.js.map +1 -0
  48. package/dist/utils/smartRouting.js +36 -0
  49. package/dist/utils/smartRouting.js.map +1 -1
  50. package/dist/utils/ssrf.js +49 -2
  51. package/dist/utils/ssrf.js.map +1 -1
  52. package/dist/utils/stdioOptions.js +11 -0
  53. package/dist/utils/stdioOptions.js.map +1 -0
  54. package/frontend/dist/assets/{ActivityPage-C4vGn8kC.js → ActivityPage-D3Oto1RE.js} +2 -2
  55. package/frontend/dist/assets/{ActivityPage-C4vGn8kC.js.map → ActivityPage-D3Oto1RE.js.map} +1 -1
  56. package/frontend/dist/assets/{CopyClientConfigDialog-gqu8M16p.js → CopyClientConfigDialog-kX5ewtFp.js} +2 -2
  57. package/frontend/dist/assets/{CopyClientConfigDialog-gqu8M16p.js.map → CopyClientConfigDialog-kX5ewtFp.js.map} +1 -1
  58. package/frontend/dist/assets/{CredentialsPage-EJYe8sf_.js → CredentialsPage-C9dHU6Wf.js} +2 -2
  59. package/frontend/dist/assets/{CredentialsPage-EJYe8sf_.js.map → CredentialsPage-C9dHU6Wf.js.map} +1 -1
  60. package/frontend/dist/assets/{Dashboard-CqqzEeM3.js → Dashboard-DFyfT22A.js} +2 -2
  61. package/frontend/dist/assets/{Dashboard-CqqzEeM3.js.map → Dashboard-DFyfT22A.js.map} +1 -1
  62. package/frontend/dist/assets/{EndpointCopy-sH8M0Zl-.js → EndpointCopy-6sqlZ15W.js} +2 -2
  63. package/frontend/dist/assets/{EndpointCopy-sH8M0Zl-.js.map → EndpointCopy-6sqlZ15W.js.map} +1 -1
  64. package/frontend/dist/assets/{GroupsPage-BAiER86x.js → GroupsPage-joJmAW7F.js} +2 -2
  65. package/frontend/dist/assets/{GroupsPage-BAiER86x.js.map → GroupsPage-joJmAW7F.js.map} +1 -1
  66. package/frontend/dist/assets/LoginPage-10tXreQ_.js +2 -0
  67. package/frontend/dist/assets/LoginPage-10tXreQ_.js.map +1 -0
  68. package/frontend/dist/assets/{LogsPage-Ci0H5Hbz.js → LogsPage-yBj3gqOU.js} +2 -2
  69. package/frontend/dist/assets/{LogsPage-Ci0H5Hbz.js.map → LogsPage-yBj3gqOU.js.map} +1 -1
  70. package/frontend/dist/assets/{MarketPage-B0bU64Fu.js → MarketPage-CuAl4IXf.js} +2 -2
  71. package/frontend/dist/assets/{MarketPage-B0bU64Fu.js.map → MarketPage-CuAl4IXf.js.map} +1 -1
  72. package/frontend/dist/assets/{OAuthConsentPage-COPngtNn.js → OAuthConsentPage-BuvpRon8.js} +2 -2
  73. package/frontend/dist/assets/{OAuthConsentPage-COPngtNn.js.map → OAuthConsentPage-BuvpRon8.js.map} +1 -1
  74. package/frontend/dist/assets/{PromptsPage-DkRz0aOM.js → PromptsPage-DsYU0Kgm.js} +2 -2
  75. package/frontend/dist/assets/{PromptsPage-DkRz0aOM.js.map → PromptsPage-DsYU0Kgm.js.map} +1 -1
  76. package/frontend/dist/assets/{ResourcesPage-D9UvvBJO.js → ResourcesPage-DVxFnDJE.js} +2 -2
  77. package/frontend/dist/assets/{ResourcesPage-D9UvvBJO.js.map → ResourcesPage-DVxFnDJE.js.map} +1 -1
  78. package/frontend/dist/assets/{ServersPage-D9DDRTwG.js → ServersPage-DWq6jqvv.js} +4 -4
  79. package/frontend/dist/assets/{ServersPage-D9DDRTwG.js.map → ServersPage-DWq6jqvv.js.map} +1 -1
  80. package/frontend/dist/assets/SettingsPage-Db0jJzi0.js +12 -0
  81. package/frontend/dist/assets/SettingsPage-Db0jJzi0.js.map +1 -0
  82. package/frontend/dist/assets/{StatusDot-rU2z4UIB.js → StatusDot-DdZTtyw1.js} +2 -2
  83. package/frontend/dist/assets/{StatusDot-rU2z4UIB.js.map → StatusDot-DdZTtyw1.js.map} +1 -1
  84. package/frontend/dist/assets/{ToggleGroup-B-m_CA-c.js → ToggleGroup-tJbhgavL.js} +2 -2
  85. package/frontend/dist/assets/{ToggleGroup-B-m_CA-c.js.map → ToggleGroup-tJbhgavL.js.map} +1 -1
  86. package/frontend/dist/assets/{UsersPage-BtCcY8iT.js → UsersPage-BqM1rGSo.js} +2 -2
  87. package/frontend/dist/assets/{UsersPage-BtCcY8iT.js.map → UsersPage-BqM1rGSo.js.map} +1 -1
  88. package/frontend/dist/assets/{clipboard-ZgOiZZfc.js → clipboard-CakNzStU.js} +2 -2
  89. package/frontend/dist/assets/{clipboard-ZgOiZZfc.js.map → clipboard-CakNzStU.js.map} +1 -1
  90. package/frontend/dist/assets/index--NhF_yyy.css +1 -0
  91. package/frontend/dist/assets/index-DE5u_PAU.js +3 -0
  92. package/frontend/dist/assets/index-DE5u_PAU.js.map +1 -0
  93. package/frontend/dist/assets/{resourceService-B_JEhB8X.js → resourceService-BkO_Mq1D.js} +2 -2
  94. package/frontend/dist/assets/{resourceService-B_JEhB8X.js.map → resourceService-BkO_Mq1D.js.map} +1 -1
  95. package/frontend/dist/assets/{useCostData-B9tFvSJs.js → useCostData-inPDjb84.js} +2 -2
  96. package/frontend/dist/assets/{useCostData-B9tFvSJs.js.map → useCostData-inPDjb84.js.map} +1 -1
  97. package/frontend/dist/assets/variableDetection-Dt-yEyj8.js +16 -0
  98. package/frontend/dist/assets/variableDetection-Dt-yEyj8.js.map +1 -0
  99. package/frontend/dist/index.html +2 -2
  100. package/package.json +1 -1
  101. package/frontend/dist/assets/LoginPage-BPhnf62f.js +0 -2
  102. package/frontend/dist/assets/LoginPage-BPhnf62f.js.map +0 -1
  103. package/frontend/dist/assets/SettingsPage-p0RqcQ3b.js +0 -12
  104. package/frontend/dist/assets/SettingsPage-p0RqcQ3b.js.map +0 -1
  105. package/frontend/dist/assets/index-D-J2WUmj.css +0 -1
  106. package/frontend/dist/assets/index-DeDkxr8P.js +0 -3
  107. package/frontend/dist/assets/index-DeDkxr8P.js.map +0 -1
  108. package/frontend/dist/assets/variableDetection-DSNGipcp.js +0 -16
  109. package/frontend/dist/assets/variableDetection-DSNGipcp.js.map +0 -1
@@ -1,9 +1,11 @@
1
+ import { preserveListFreshness, combineListFreshness, remainingListTtl, } from '../utils/listFreshness.js';
1
2
  import { expandServerConfig, getServerEnvironment } from './serverConfigEnvironment.js';
2
3
  import { getMcpRequestGroup } from '../utils/mcpRequestGroup.js';
3
4
  import { LegacyMcpClient } from '../clients/legacyMcpClient.js';
4
5
  import { LEGACY_PROTOCOL_VERSIONS } from '../utils/mcpProtocol.js';
5
6
  import { AsyncLocalStorage } from 'node:async_hooks';
6
7
  import { canAccessGroupRoute } from '../utils/groupAccess.js';
8
+ import { canServeToolRequests } from '../utils/serverAvailability.js';
7
9
  import { PrincipalRuntimeService } from './principalRuntimeService.js';
8
10
  import { UserContextService } from './userContextService.js';
9
11
  import { deleteCredentialBindings, missingCredentialError } from './credentialBindingService.js';
@@ -23,6 +25,7 @@ import { ResilientJsonSchemaValidator } from '../utils/jsonSchemaValidator.js';
23
25
  import { getUserDao } from '../dao/index.js';
24
26
  import { expandEnvVars, replaceEnvVars, getNameSeparator } from '../config/index.js';
25
27
  import config from '../config/index.js';
28
+ import { validateMaxBufferSize } from '../utils/stdioOptions.js';
26
29
  import { validateServerName } from '../utils/serverNameValidation.js';
27
30
  import { getGroup } from './sseService.js';
28
31
  import { getServerConfigInGroup, normalizeGroupServers } from './groupService.js';
@@ -30,7 +33,7 @@ import { removeServerToolEmbeddings, removeToolEmbeddings, saveToolsAsVectorEmbe
30
33
  import { OpenAPIClient } from '../clients/openapi.js';
31
34
  import { RequestContextService } from './requestContextService.js';
32
35
  import { getDataService } from './services.js';
33
- import { getServerDao, getGroupDao, getSystemConfigDao, getBuiltinPromptDao, getBuiltinResourceDao, } from '../dao/index.js';
36
+ import { getBearerKeyDao, getServerDao, getGroupDao, getSystemConfigDao, getBuiltinPromptDao, getBuiltinResourceDao, } from '../dao/index.js';
34
37
  import { initializeAllOAuthClients } from './oauthService.js';
35
38
  import { createOAuthProvider } from './mcpOAuthProvider.js';
36
39
  import { initSmartRoutingService, getSmartRoutingTools, handleSearchToolsRequest, handleDescribeToolRequest, isSmartRoutingGroup, } from './smartRoutingService.js';
@@ -782,7 +785,7 @@ export const setServerInfosForTest = (infos) => {
782
785
  serverInfos = infos;
783
786
  };
784
787
  export const updateServerToolsCache = (serverInfo, tools, options) => {
785
- serverInfo.tools = tools.map((tool) => normalizeToolForCache(serverInfo.name, tool));
788
+ serverInfo.tools = preserveListFreshness(tools, tools.map((tool) => normalizeToolForCache(serverInfo.name, tool)));
786
789
  syncToolsAsVectorEmbeddings(serverInfo.name, serverInfo.tools, {
787
790
  reportProgress: options?.reportEmbeddingProgress === true,
788
791
  }).catch(() => {
@@ -790,10 +793,10 @@ export const updateServerToolsCache = (serverInfo, tools, options) => {
790
793
  });
791
794
  };
792
795
  const updateServerPromptsCache = (serverInfo, prompts) => {
793
- serverInfo.prompts = prompts.map((prompt) => normalizePromptForCache(serverInfo.name, prompt));
796
+ serverInfo.prompts = preserveListFreshness(prompts, prompts.map((prompt) => normalizePromptForCache(serverInfo.name, prompt)));
794
797
  };
795
798
  const updateServerResourcesCache = (serverInfo, resources) => {
796
- serverInfo.resources = resources.map(normalizeResourceForCache);
799
+ serverInfo.resources = preserveListFreshness(resources, resources.map(normalizeResourceForCache));
797
800
  };
798
801
  const logListChangedRefreshError = (listType) => {
799
802
  logger.warn(`Failed to refresh ${listType} list after upstream notification`);
@@ -807,6 +810,7 @@ const createUpstreamMcpClient = (name, getServerInfo) => {
807
810
  jsonSchemaValidator: new ResilientJsonSchemaValidator(),
808
811
  listChanged: {
809
812
  tools: {
813
+ debounceMs: 0,
810
814
  onChanged: (error, tools) => {
811
815
  const serverInfo = getServerInfo();
812
816
  if (error) {
@@ -821,6 +825,7 @@ const createUpstreamMcpClient = (name, getServerInfo) => {
821
825
  },
822
826
  },
823
827
  prompts: {
828
+ debounceMs: 0,
824
829
  onChanged: (error, prompts) => {
825
830
  const serverInfo = getServerInfo();
826
831
  if (error) {
@@ -835,6 +840,7 @@ const createUpstreamMcpClient = (name, getServerInfo) => {
835
840
  },
836
841
  },
837
842
  resources: {
843
+ debounceMs: 0,
838
844
  onChanged: (error, resources) => {
839
845
  const serverInfo = getServerInfo();
840
846
  if (error) {
@@ -1289,6 +1295,7 @@ export const createTransportFromConfig = async (name, conf) => {
1289
1295
  args: finalArgs,
1290
1296
  env: env,
1291
1297
  stderr: 'pipe',
1298
+ maxBufferSize: validateMaxBufferSize(conf.options?.maxBufferSize),
1292
1299
  });
1293
1300
  if (transport.stderr && hasCredentialTemplate(conf)) {
1294
1301
  transport.stderr.on('data', () => undefined); // Personal upstream diagnostics can contain raw credentials.
@@ -1386,6 +1393,7 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
1386
1393
  serverInfo.client = newClient;
1387
1394
  serverInfo.transport = newTransport;
1388
1395
  serverInfo.status = 'connected';
1396
+ setupServerKeepAlive(serverInfo, server);
1389
1397
  }
1390
1398
  // Point the local refs at the new connection for the next attempt.
1391
1399
  client = newClient;
@@ -2329,6 +2337,7 @@ const classifyUnavailableReason = (qualifiedName) => {
2329
2337
  };
2330
2338
  // Add new server
2331
2339
  export const addServer = async (name, config) => {
2340
+ validateMaxBufferSize(config.options?.maxBufferSize);
2332
2341
  validateCredentialTemplate(config);
2333
2342
  const server = { name, ...config };
2334
2343
  const result = await getServerDao().create(server);
@@ -2363,6 +2372,7 @@ export const removeServer = async (name) => {
2363
2372
  // Add or update server (supports overriding existing servers for MCPB)
2364
2373
  export const addOrUpdateServer = async (name, config, allowOverride = false) => {
2365
2374
  try {
2375
+ validateMaxBufferSize(config.options?.maxBufferSize);
2366
2376
  validateCredentialTemplate(config);
2367
2377
  const exists = await getServerDao().exists(name);
2368
2378
  if (exists && !allowOverride) {
@@ -2934,11 +2944,8 @@ const resolveToolInGroup = async (group, toolName) => {
2934
2944
  serverConfigsByName.size <= 1 &&
2935
2945
  filteredServerInfos.length === 1;
2936
2946
  for (const serverInfo of filteredServerInfos) {
2937
- // A disconnected on-demand server may still have a cached tool list from a
2938
- // previous wake-up; allow it through so it can be selected and woken by the
2939
- // caller. Other disconnected servers cannot serve the request. See #1029.
2940
- if ((serverInfo.status !== 'connected' && !serverInfo.config?.startOnDemand) ||
2941
- serverInfo.enabled === false) {
2947
+ // An idle on-demand server is selected here and woken by the caller (#1029)
2948
+ if (!canServeToolRequests(serverInfo)) {
2942
2949
  continue;
2943
2950
  }
2944
2951
  const serverConfig = serverConfigsByName.get(serverInfo.name);
@@ -3764,19 +3771,24 @@ const handleListResourceTemplatesRequestImpl = async (_, extra) => {
3764
3771
  const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(lookupGroup, {
3765
3772
  requireClient: true,
3766
3773
  });
3774
+ const snapshots = [];
3767
3775
  const results = await Promise.allSettled(filteredServerInfos.map(async (serverInfo) => {
3768
3776
  if (!serverInfo.client?.listResourceTemplates) {
3769
3777
  return [];
3770
3778
  }
3771
3779
  const templates = await serverInfo.client.listResourceTemplates({}, serverInfo.options || {});
3780
+ snapshots.push(templates.resourceTemplates);
3772
3781
  const filteredTemplates = await filterResourceTemplatesByGroup(lookupGroup, serverInfo.name, templates.resourceTemplates || [], serverConfigsByName.get(serverInfo.name));
3773
3782
  return appsRouteContext.enabled
3774
3783
  ? filteredTemplates
3775
3784
  : filteredTemplates.map((template) => stripMcpAppsMetadata(template));
3776
3785
  }));
3777
- return {
3778
- resourceTemplates: results.flatMap((result) => result.status === 'fulfilled' ? result.value : []),
3779
- };
3786
+ const resourceTemplates = results.flatMap((result) => result.status === 'fulfilled' ? result.value : []);
3787
+ if (results.every((result) => result.status === 'fulfilled') &&
3788
+ snapshots.length === filteredServerInfos.length) {
3789
+ combineListFreshness(snapshots, resourceTemplates);
3790
+ }
3791
+ return { resourceTemplates };
3780
3792
  };
3781
3793
  const handleReadResourceRequestImpl = async (request, extra) => {
3782
3794
  try {
@@ -3954,12 +3966,18 @@ const createPrincipalRuntime = async (name, resolvedConfig) => {
3954
3966
  info.client = protectClient(client);
3955
3967
  await client.connect(transport, resolvedConfig.options || {});
3956
3968
  const capabilities = client.getServerCapabilities();
3957
- if (capabilities?.tools)
3958
- info.tools = (await client.listTools({}, resolvedConfig.options)).tools.map((tool) => normalizeToolForCache(name, tool));
3959
- if (capabilities?.prompts)
3960
- info.prompts = (await client.listPrompts({}, resolvedConfig.options)).prompts.map((prompt) => normalizePromptForCache(name, prompt));
3961
- if (capabilities?.resources)
3962
- info.resources = (await client.listResources({}, resolvedConfig.options)).resources.map(normalizeResourceForCache);
3969
+ if (capabilities?.tools) {
3970
+ const { tools } = await client.listTools({}, resolvedConfig.options);
3971
+ info.tools = preserveListFreshness(tools, tools.map((tool) => normalizeToolForCache(name, tool)));
3972
+ }
3973
+ if (capabilities?.prompts) {
3974
+ const { prompts } = await client.listPrompts({}, resolvedConfig.options);
3975
+ info.prompts = preserveListFreshness(prompts, prompts.map((prompt) => normalizePromptForCache(name, prompt)));
3976
+ }
3977
+ if (capabilities?.resources) {
3978
+ const { resources } = await client.listResources({}, resolvedConfig.options);
3979
+ info.resources = preserveListFreshness(resources, resources.map(normalizeResourceForCache));
3980
+ }
3963
3981
  info.version = client.getServerVersion()?.version;
3964
3982
  // Record the resolved npx/uvx package version backing this server.
3965
3983
  applyResolvedPackageVersion(info, resolvedConfig);
@@ -4111,14 +4129,82 @@ const withPrincipalServers = (handler, operation) => async (request, extra) => {
4111
4129
  release();
4112
4130
  }
4113
4131
  };
4132
+ // Re-read authoritative inputs across awaits; no projected response is cached.
4133
+ const listProjectionInputs = async (group, field) => {
4134
+ const user = UserContextService.getInstance().getCurrentUser();
4135
+ const keyId = RequestContextService.getInstance().getBearerKeyContext().keyId;
4136
+ const liveUser = user?.username ? await getUserDao().findByUsername(user.username) : undefined;
4137
+ const key = keyId ? await getBearerKeyDao().findById(keyId) : undefined;
4138
+ if ((user?.username && (!liveUser || Boolean(user.isAdmin) !== Boolean(liveUser.isAdmin))) ||
4139
+ (keyId && !key?.enabled))
4140
+ return undefined;
4141
+ const lookupGroup = getGroupLookupName(group);
4142
+ const groupDao = getGroupDao();
4143
+ let routeGroup = lookupGroup ? await groupDao.findByName(lookupGroup) : undefined;
4144
+ if (!routeGroup &&
4145
+ lookupGroup &&
4146
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(lookupGroup)) {
4147
+ routeGroup = await groupDao.findById(lookupGroup);
4148
+ }
4149
+ return JSON.stringify([
4150
+ await getServerDao().findAll(),
4151
+ routeGroup,
4152
+ await getSystemConfigDao().get(),
4153
+ field === 'prompts' ? await getBuiltinPromptDao().findEnabled() : undefined,
4154
+ field === 'resources' ? await getBuiltinResourceDao().findEnabled() : undefined,
4155
+ liveUser,
4156
+ key,
4157
+ RequestContextService.getInstance().getRequestContext()?.clientCapabilities,
4158
+ getVisibleServerInfos().map((info) => ({
4159
+ name: info.name,
4160
+ status: info.status,
4161
+ enabled: info.enabled,
4162
+ owner: info.owner,
4163
+ visibility: info.visibility,
4164
+ sharedWithUsers: info.sharedWithUsers,
4165
+ config: info.config,
4166
+ })),
4167
+ ]);
4168
+ };
4114
4169
  // Lists combine cached discovery with live gateway configuration and authorization.
4115
4170
  // Upstream freshness alone cannot guarantee freshness of the resulting projection.
4116
- const withCacheHints = (handler, passthroughTtl = false) => async (request, extra) => {
4171
+ const withCacheHints = (handler, passthroughTtl = false, listField) => async (request, extra) => {
4117
4172
  if (!RequestContextService.getInstance().getRequestContext()?.stateless) {
4118
4173
  return handler(request, extra);
4119
4174
  }
4120
4175
  const startedAt = performance.now();
4176
+ const group = getMcpRequestGroup(extra);
4177
+ const inputs = listField ? await listProjectionInputs(group, listField) : undefined;
4178
+ const before = listField
4179
+ ? await getFilteredServerInfosForGroup(getGroupLookupName(group))
4180
+ : undefined;
4181
+ const snapshots = before && listField && listField !== 'resourceTemplates'
4182
+ ? before.filteredServerInfos.map((info) => info[listField])
4183
+ : [];
4184
+ const clients = before?.filteredServerInfos.map((info) => info.client);
4121
4185
  const result = await handler(request, extra);
4186
+ if (listField) {
4187
+ const after = await getFilteredServerInfosForGroup(getGroupLookupName(group));
4188
+ const stable = typeof inputs === 'string' &&
4189
+ inputs === (await listProjectionInputs(group, listField)) &&
4190
+ before?.filteredServerInfos.length === after.filteredServerInfos.length &&
4191
+ before.filteredServerInfos.every((info, index) => info === after.filteredServerInfos[index] &&
4192
+ info.client === clients?.[index] &&
4193
+ info.status === 'connected' &&
4194
+ (listField === 'resourceTemplates' || info[listField] === snapshots[index]));
4195
+ const generated = (listField === 'tools' && isSmartRoutingGroup(group)) ||
4196
+ (listField === 'prompts' && (await getBuiltinPromptDao().findEnabled()).length > 0) ||
4197
+ (listField === 'resources' && (await getBuiltinResourceDao().findEnabled()).length > 0);
4198
+ const unknownPermissions = RequestContextService.getInstance().getHostedAuthContext();
4199
+ const dependencies = listField === 'resourceTemplates' ? [result.resourceTemplates] : snapshots;
4200
+ return {
4201
+ ...result,
4202
+ ttlMs: stable && !generated && !unknownPermissions
4203
+ ? remainingListTtl(dependencies, startedAt, performance.now())
4204
+ : 0,
4205
+ cacheScope: 'private',
4206
+ };
4207
+ }
4122
4208
  const upstreamTtl = result.ttlMs;
4123
4209
  const ttlMs = passthroughTtl &&
4124
4210
  typeof upstreamTtl === 'number' &&
@@ -4130,12 +4216,12 @@ const withCacheHints = (handler, passthroughTtl = false) => async (request, extr
4130
4216
  // and capabilities. Shared caches must not reuse the gateway result across users.
4131
4217
  return { ...result, ttlMs, cacheScope: 'private' };
4132
4218
  };
4133
- export const handleListToolsRequest = withPrincipalServers(withCacheHints(handleListToolsRequestImpl), 'list');
4219
+ export const handleListToolsRequest = withPrincipalServers(withCacheHints(handleListToolsRequestImpl, false, 'tools'), 'list');
4134
4220
  export const handleCallToolRequest = withPrincipalServers(handleCallToolRequestImpl, 'tool');
4135
4221
  export const handleGetPromptRequest = withPrincipalServers(handleGetPromptRequestImpl, 'prompt');
4136
- export const handleListPromptsRequest = withPrincipalServers(withCacheHints(handleListPromptsRequestImpl), 'list');
4137
- export const handleListResourcesRequest = withPrincipalServers(withCacheHints(handleListResourcesRequestImpl), 'list');
4138
- export const handleListResourceTemplatesRequest = withPrincipalServers(withCacheHints(handleListResourceTemplatesRequestImpl), 'list');
4222
+ export const handleListPromptsRequest = withPrincipalServers(withCacheHints(handleListPromptsRequestImpl, false, 'prompts'), 'list');
4223
+ export const handleListResourcesRequest = withPrincipalServers(withCacheHints(handleListResourcesRequestImpl, false, 'resources'), 'list');
4224
+ export const handleListResourceTemplatesRequest = withPrincipalServers(withCacheHints(handleListResourceTemplatesRequestImpl, false, 'resourceTemplates'), 'list');
4139
4225
  export const handleReadResourceRequest = withPrincipalServers(withCacheHints(handleReadResourceRequestImpl, true), 'resource');
4140
4226
  export const createMcpServer = (name, version, options) => {
4141
4227
  const normalizedOptions = typeof options === 'string' ? { group: options } : (options ?? {});