@samanhappy/mcphub 1.0.10 → 1.0.12

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 (116) hide show
  1. package/README.fr.md +1 -0
  2. package/README.md +3 -0
  3. package/README.zh.md +1 -0
  4. package/dist/cli/commands/tools.js +4 -0
  5. package/dist/cli/commands/tools.js.map +1 -1
  6. package/dist/controllers/contextCostController.js +24 -0
  7. package/dist/controllers/contextCostController.js.map +1 -0
  8. package/dist/controllers/discoveryController.js +203 -0
  9. package/dist/controllers/discoveryController.js.map +1 -0
  10. package/dist/controllers/oauthCallbackController.js +3 -8
  11. package/dist/controllers/oauthCallbackController.js.map +1 -1
  12. package/dist/controllers/serverController.js +51 -1
  13. package/dist/controllers/serverController.js.map +1 -1
  14. package/dist/dao/SystemConfigDaoDbImpl.js +6 -0
  15. package/dist/dao/SystemConfigDaoDbImpl.js.map +1 -1
  16. package/dist/db/entities/SystemConfig.js +8 -0
  17. package/dist/db/entities/SystemConfig.js.map +1 -1
  18. package/dist/db/repositories/SystemConfigRepository.js +2 -0
  19. package/dist/db/repositories/SystemConfigRepository.js.map +1 -1
  20. package/dist/routes/index.js +16 -0
  21. package/dist/routes/index.js.map +1 -1
  22. package/dist/services/contextCostService.js +101 -0
  23. package/dist/services/contextCostService.js.map +1 -0
  24. package/dist/services/groupService.js +1 -1
  25. package/dist/services/groupService.js.map +1 -1
  26. package/dist/services/hostedRuntimeCatalogService.js +2 -1
  27. package/dist/services/hostedRuntimeCatalogService.js.map +1 -1
  28. package/dist/services/mcpService.js +266 -104
  29. package/dist/services/mcpService.js.map +1 -1
  30. package/dist/services/openApiGeneratorService.js +5 -4
  31. package/dist/services/openApiGeneratorService.js.map +1 -1
  32. package/dist/services/smartRoutingService.js +63 -30
  33. package/dist/services/smartRoutingService.js.map +1 -1
  34. package/dist/services/toolResultCompressionService.js +360 -0
  35. package/dist/services/toolResultCompressionService.js.map +1 -0
  36. package/dist/services/vectorSearchService.js +3 -1
  37. package/dist/services/vectorSearchService.js.map +1 -1
  38. package/dist/utils/mcpApps.js +43 -0
  39. package/dist/utils/mcpApps.js.map +1 -0
  40. package/dist/utils/migration.js +2 -0
  41. package/dist/utils/migration.js.map +1 -1
  42. package/dist/utils/serialization.js +17 -1
  43. package/dist/utils/serialization.js.map +1 -1
  44. package/dist/utils/smartRouting.js +6 -0
  45. package/dist/utils/smartRouting.js.map +1 -1
  46. package/dist/utils/tokenCost.js +71 -0
  47. package/dist/utils/tokenCost.js.map +1 -0
  48. package/frontend/dist/assets/{ActivityPage-RTpGLfzM.js → ActivityPage-BPfkCkM3.js} +2 -2
  49. package/frontend/dist/assets/{ActivityPage-RTpGLfzM.js.map → ActivityPage-BPfkCkM3.js.map} +1 -1
  50. package/frontend/dist/assets/{ConfirmDialog-CxlizGia.js → ConfirmDialog-Cag_haxr.js} +2 -2
  51. package/frontend/dist/assets/{ConfirmDialog-CxlizGia.js.map → ConfirmDialog-Cag_haxr.js.map} +1 -1
  52. package/frontend/dist/assets/Dashboard-BvEkS4H1.js +2 -0
  53. package/frontend/dist/assets/Dashboard-BvEkS4H1.js.map +1 -0
  54. package/frontend/dist/assets/{DeleteDialog-DRbWonMu.js → DeleteDialog-BBfJpiiD.js} +2 -2
  55. package/frontend/dist/assets/{DeleteDialog-DRbWonMu.js.map → DeleteDialog-BBfJpiiD.js.map} +1 -1
  56. package/frontend/dist/assets/{EndpointCopy-BtPORuga.js → EndpointCopy-KhSo4Y6F.js} +2 -2
  57. package/frontend/dist/assets/{EndpointCopy-BtPORuga.js.map → EndpointCopy-KhSo4Y6F.js.map} +1 -1
  58. package/frontend/dist/assets/GroupsPage-CLkFTxqd.js +33 -0
  59. package/frontend/dist/assets/GroupsPage-CLkFTxqd.js.map +1 -0
  60. package/frontend/dist/assets/{LoginPage-Nks21Yj0.js → LoginPage-lM-z_O3X.js} +2 -2
  61. package/frontend/dist/assets/{LoginPage-Nks21Yj0.js.map → LoginPage-lM-z_O3X.js.map} +1 -1
  62. package/frontend/dist/assets/{LogsPage-CoupcpK1.js → LogsPage-CS9OFma4.js} +2 -2
  63. package/frontend/dist/assets/LogsPage-CS9OFma4.js.map +1 -0
  64. package/frontend/dist/assets/{MarketPage-PwkvXlle.js → MarketPage-9LSDiVvR.js} +2 -2
  65. package/frontend/dist/assets/{MarketPage-PwkvXlle.js.map → MarketPage-9LSDiVvR.js.map} +1 -1
  66. package/frontend/dist/assets/{Pagination-BFi-X7qY.js → Pagination-DBAu79mv.js} +2 -2
  67. package/frontend/dist/assets/{Pagination-BFi-X7qY.js.map → Pagination-DBAu79mv.js.map} +1 -1
  68. package/frontend/dist/assets/{PromptsPage-DQiX0bAK.js → PromptsPage-CPFRMMgV.js} +2 -2
  69. package/frontend/dist/assets/{PromptsPage-DQiX0bAK.js.map → PromptsPage-CPFRMMgV.js.map} +1 -1
  70. package/frontend/dist/assets/{ResourcesPage-Dltmu_Ze.js → ResourcesPage-DJ_7k1MF.js} +2 -2
  71. package/frontend/dist/assets/{ResourcesPage-Dltmu_Ze.js.map → ResourcesPage-DJ_7k1MF.js.map} +1 -1
  72. package/frontend/dist/assets/ServersPage-BtPOdp4X.js +37 -0
  73. package/frontend/dist/assets/ServersPage-BtPOdp4X.js.map +1 -0
  74. package/frontend/dist/assets/SettingsPage-CyYl7pb8.js +12 -0
  75. package/frontend/dist/assets/SettingsPage-CyYl7pb8.js.map +1 -0
  76. package/frontend/dist/assets/{StatusDot-BRb7WUcz.js → StatusDot-CZZGigtR.js} +2 -2
  77. package/frontend/dist/assets/{StatusDot-BRb7WUcz.js.map → StatusDot-CZZGigtR.js.map} +1 -1
  78. package/frontend/dist/assets/{ToggleGroup-tIqxkae6.js → ToggleGroup-C24CNPSz.js} +2 -2
  79. package/frontend/dist/assets/{ToggleGroup-tIqxkae6.js.map → ToggleGroup-C24CNPSz.js.map} +1 -1
  80. package/frontend/dist/assets/{UsersPage-DNMaUcUn.js → UsersPage-CLXhNpvg.js} +2 -2
  81. package/frontend/dist/assets/{UsersPage-DNMaUcUn.js.map → UsersPage-CLXhNpvg.js.map} +1 -1
  82. package/frontend/dist/assets/contextCost-mvvSAnFv.js +2 -0
  83. package/frontend/dist/assets/contextCost-mvvSAnFv.js.map +1 -0
  84. package/frontend/dist/assets/{framework-vendor-BUhDPOUZ.js → framework-vendor-DeqnZ0v6.js} +5 -5
  85. package/frontend/dist/assets/{framework-vendor-BUhDPOUZ.js.map → framework-vendor-DeqnZ0v6.js.map} +1 -1
  86. package/frontend/dist/assets/i18n-vendor-DP1IRITP.js +10 -0
  87. package/frontend/dist/assets/i18n-vendor-DP1IRITP.js.map +1 -0
  88. package/frontend/dist/assets/{icons-vendor-CKgJB3SC.js → icons-vendor-IYX_ppgH.js} +2 -2
  89. package/frontend/dist/assets/{icons-vendor-CKgJB3SC.js.map → icons-vendor-IYX_ppgH.js.map} +1 -1
  90. package/frontend/dist/assets/{index-B9E3ygVt.css → index-D8V2fxz_.css} +1 -1
  91. package/frontend/dist/assets/index-DRcTlhkb.js +3 -0
  92. package/frontend/dist/assets/index-DRcTlhkb.js.map +1 -0
  93. package/frontend/dist/assets/{resourceService-BuhqcwTn.js → resourceService-jx5dJGUy.js} +2 -2
  94. package/frontend/dist/assets/{resourceService-BuhqcwTn.js.map → resourceService-jx5dJGUy.js.map} +1 -1
  95. package/frontend/dist/assets/useSettingsData-B4hMPWdK.js +2 -0
  96. package/frontend/dist/assets/{useSettingsData-iQpBXEjX.js.map → useSettingsData-B4hMPWdK.js.map} +1 -1
  97. package/frontend/dist/assets/{variableDetection-GTKqOf1F.js → variableDetection-Dnh8qFpO.js} +2 -2
  98. package/frontend/dist/assets/{variableDetection-GTKqOf1F.js.map → variableDetection-Dnh8qFpO.js.map} +1 -1
  99. package/frontend/dist/index.html +5 -5
  100. package/package.json +8 -8
  101. package/frontend/dist/assets/Dashboard-KV-zezVb.js +0 -2
  102. package/frontend/dist/assets/Dashboard-KV-zezVb.js.map +0 -1
  103. package/frontend/dist/assets/GroupsPage-BhNYw74w.js +0 -33
  104. package/frontend/dist/assets/GroupsPage-BhNYw74w.js.map +0 -1
  105. package/frontend/dist/assets/LogsPage-CoupcpK1.js.map +0 -1
  106. package/frontend/dist/assets/ServersPage-CcbF8dGi.js +0 -37
  107. package/frontend/dist/assets/ServersPage-CcbF8dGi.js.map +0 -1
  108. package/frontend/dist/assets/SettingsPage-BbRB2fCw.js +0 -12
  109. package/frontend/dist/assets/SettingsPage-BbRB2fCw.js.map +0 -1
  110. package/frontend/dist/assets/i18n-vendor-Kbr87Ofu.js +0 -2
  111. package/frontend/dist/assets/i18n-vendor-Kbr87Ofu.js.map +0 -1
  112. package/frontend/dist/assets/index-C6LZBP3G.js +0 -3
  113. package/frontend/dist/assets/index-C6LZBP3G.js.map +0 -1
  114. package/frontend/dist/assets/useServerData-C9R1u5V4.js +0 -2
  115. package/frontend/dist/assets/useServerData-C9R1u5V4.js.map +0 -1
  116. package/frontend/dist/assets/useSettingsData-iQpBXEjX.js +0 -2
@@ -22,8 +22,10 @@ import { initializeAllOAuthClients } from './oauthService.js';
22
22
  import { createOAuthProvider } from './mcpOAuthProvider.js';
23
23
  import { initSmartRoutingService, getSmartRoutingTools, handleSearchToolsRequest, handleDescribeToolRequest, isSmartRoutingGroup, } from './smartRoutingService.js';
24
24
  import { getActivityLoggingService } from './activityLoggingService.js';
25
+ import { maybeCompressToolResult } from './toolResultCompressionService.js';
25
26
  import { assertHostedToolAllowed, filterHostedTools, reserveHostedToolCall, settleHostedToolCall, } from './hostedAuthService.js';
26
27
  import { formatErrorForLogging, sanitizeStringForLogging, summarizeErrorForLogging, } from '../utils/serialization.js';
28
+ import { MCP_APPS_CAPABILITIES, filterModelVisibleTools, hasMcpAppsCapability, isAppOnlyTool, stripMcpAppsMetadata, } from '../utils/mcpApps.js';
27
29
  const servers = {};
28
30
  import { setupClientKeepAlive } from './keepAliveService.js';
29
31
  /**
@@ -194,18 +196,26 @@ export const notifyToolChanged = async (name, options) => {
194
196
  await registerAllTools(false, name, options);
195
197
  broadcastToolListChanged();
196
198
  };
197
- export const broadcastToolListChanged = () => {
199
+ const broadcastListChanged = (listType, sendNotification) => {
198
200
  Object.values(servers).forEach((server) => {
199
- server
200
- .sendToolListChanged()
201
- .catch((error) => {
202
- console.warn('Failed to send tool list changed notification:', error.message);
203
- })
201
+ sendNotification(server)
204
202
  .then(() => {
205
- console.log('Tool list changed notification sent successfully');
203
+ console.log(`${listType} list changed notification sent successfully`);
204
+ })
205
+ .catch((error) => {
206
+ console.warn(`Failed to send ${listType} list changed notification:`, error.message);
206
207
  });
207
208
  });
208
209
  };
210
+ export const broadcastToolListChanged = () => {
211
+ broadcastListChanged('tool', (server) => server.sendToolListChanged());
212
+ };
213
+ export const broadcastResourceListChanged = () => {
214
+ broadcastListChanged('resource', (server) => server.sendResourceListChanged());
215
+ };
216
+ export const broadcastPromptListChanged = () => {
217
+ broadcastListChanged('prompt', (server) => server.sendPromptListChanged());
218
+ };
209
219
  export const updateServerInfoVisibility = (serverName, visibility) => {
210
220
  const serverInfo = getServerByName(serverName);
211
221
  if (!serverInfo) {
@@ -230,8 +240,11 @@ export const syncToolEmbedding = async (serverName, toolName) => {
230
240
  console.warn(`Tool not found: ${toolName} on server: ${serverName}`);
231
241
  return;
232
242
  }
243
+ if (isAppOnlyTool(tool)) {
244
+ return;
245
+ }
233
246
  // Save tool as vector embedding for search
234
- saveToolsAsVectorEmbeddings(serverName, [tool]).catch((error) => {
247
+ syncToolsAsVectorEmbeddings(serverName, [tool]).catch((error) => {
235
248
  console.warn(`[EMBED_SYNC_ERROR] Failed to sync embedding for tool "${toolName}" on server "${serverName}"`);
236
249
  console.error('Error syncing single tool embedding', { serverName, toolName, error });
237
250
  });
@@ -245,26 +258,122 @@ const cleanInputSchema = (schema) => {
245
258
  delete cleanedSchema.$schema;
246
259
  return cleanedSchema;
247
260
  };
261
+ export const normalizeToolForCache = (serverName, tool) => {
262
+ return {
263
+ ...tool,
264
+ name: `${serverName}${getNameSeparator()}${tool.name}`,
265
+ description: tool.description || '',
266
+ inputSchema: cleanInputSchema(tool.inputSchema || {}),
267
+ };
268
+ };
269
+ const syncToolsAsVectorEmbeddings = async (serverName, tools, options) => {
270
+ const modelVisibleTools = filterModelVisibleTools(tools);
271
+ if (modelVisibleTools.length === 0) {
272
+ await removeServerToolEmbeddings(serverName);
273
+ return;
274
+ }
275
+ await saveToolsAsVectorEmbeddings(serverName, modelVisibleTools, options);
276
+ };
248
277
  // Normalize prompt payload to satisfy MCP ListPrompts response schema
249
278
  const normalizePromptForList = (prompt) => {
250
279
  return {
280
+ ...prompt,
251
281
  name: prompt.name,
252
282
  title: prompt.title || prompt.name,
253
283
  description: prompt.description || '',
254
284
  arguments: Array.isArray(prompt.arguments) ? prompt.arguments : [],
255
285
  };
256
286
  };
287
+ const normalizePromptForCache = (serverName, prompt) => {
288
+ return normalizePromptForList({
289
+ ...prompt,
290
+ name: `${serverName}${getNameSeparator()}${prompt.name}`,
291
+ });
292
+ };
257
293
  // Normalize resource payload to avoid nullable DB fields violating MCP schema
258
294
  const normalizeResourceForList = (resource) => {
259
295
  return {
296
+ ...resource,
260
297
  uri: resource.uri,
261
298
  name: resource.name || '',
262
299
  description: resource.description || '',
263
300
  mimeType: resource.mimeType || '',
264
301
  };
265
302
  };
303
+ const normalizeResourceForCache = (resource) => {
304
+ return normalizeResourceForList(resource);
305
+ };
266
306
  // Store all server information
267
307
  let serverInfos = [];
308
+ export const updateServerToolsCache = (serverInfo, tools, options) => {
309
+ serverInfo.tools = tools.map((tool) => normalizeToolForCache(serverInfo.name, tool));
310
+ syncToolsAsVectorEmbeddings(serverInfo.name, serverInfo.tools, {
311
+ reportProgress: options?.reportEmbeddingProgress === true,
312
+ }).catch(() => {
313
+ console.warn('[EMBED_SYNC_ERROR] Failed to sync tool embeddings');
314
+ });
315
+ };
316
+ const updateServerPromptsCache = (serverInfo, prompts) => {
317
+ serverInfo.prompts = prompts.map((prompt) => normalizePromptForCache(serverInfo.name, prompt));
318
+ };
319
+ const updateServerResourcesCache = (serverInfo, resources) => {
320
+ serverInfo.resources = resources.map(normalizeResourceForCache);
321
+ };
322
+ const logListChangedRefreshError = (listType) => {
323
+ console.warn(`Failed to refresh ${listType} list after upstream notification`);
324
+ };
325
+ const createUpstreamMcpClient = (name, getServerInfo) => {
326
+ return new Client({
327
+ name: `mcp-client-${name}`,
328
+ version: '1.0.0',
329
+ }, {
330
+ capabilities: MCP_APPS_CAPABILITIES,
331
+ listChanged: {
332
+ tools: {
333
+ onChanged: (error, tools) => {
334
+ const serverInfo = getServerInfo();
335
+ if (error) {
336
+ logListChangedRefreshError('tool');
337
+ return;
338
+ }
339
+ if (!serverInfo || !tools) {
340
+ return;
341
+ }
342
+ updateServerToolsCache(serverInfo, tools);
343
+ broadcastToolListChanged();
344
+ },
345
+ },
346
+ prompts: {
347
+ onChanged: (error, prompts) => {
348
+ const serverInfo = getServerInfo();
349
+ if (error) {
350
+ logListChangedRefreshError('prompt');
351
+ return;
352
+ }
353
+ if (!serverInfo || !prompts) {
354
+ return;
355
+ }
356
+ updateServerPromptsCache(serverInfo, prompts);
357
+ broadcastPromptListChanged();
358
+ },
359
+ },
360
+ resources: {
361
+ onChanged: (error, resources) => {
362
+ const serverInfo = getServerInfo();
363
+ if (error) {
364
+ logListChangedRefreshError('resource');
365
+ return;
366
+ }
367
+ if (!serverInfo || !resources) {
368
+ return;
369
+ }
370
+ updateServerResourcesCache(serverInfo, resources);
371
+ broadcastResourceListChanged();
372
+ },
373
+ },
374
+ },
375
+ });
376
+ };
268
377
  // Normalize and infer server type for safe client display
269
378
  const normalizeServerType = (type) => {
270
379
  if (!type)
@@ -679,12 +788,7 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
679
788
  // Recreate transport using helper function
680
789
  const newTransport = await createTransportFromConfig(serverInfo.name, server);
681
790
  // Create new client
682
- const client = new Client({
683
- name: `mcp-client-${serverInfo.name}`,
684
- version: '1.0.0',
685
- }, {
686
- capabilities: {},
687
- });
791
+ const client = createUpstreamMcpClient(serverInfo.name, () => serverInfo);
688
792
  // Reconnect with new transport
689
793
  await client.connect(newTransport, serverInfo.options || {});
690
794
  // Update server info with new client and transport
@@ -694,19 +798,7 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
694
798
  // Reload tools list after reconnection
695
799
  try {
696
800
  const tools = await client.listTools({}, serverInfo.options || {});
697
- serverInfo.tools = tools.tools.map((tool) => ({
698
- name: `${serverInfo.name}${getNameSeparator()}${tool.name}`,
699
- description: tool.description || '',
700
- inputSchema: cleanInputSchema(tool.inputSchema || {}),
701
- }));
702
- // Save tools as vector embeddings for search
703
- saveToolsAsVectorEmbeddings(serverInfo.name, serverInfo.tools).catch((error) => {
704
- console.warn(`[EMBED_SYNC_ERROR] Failed to sync tool embeddings after reconnect for server "${serverInfo.name}"`);
705
- console.error('Error syncing tool embeddings after reconnect', {
706
- serverName: serverInfo.name,
707
- error: summarizeErrorForLogging(error),
708
- });
709
- });
801
+ updateServerToolsCache(serverInfo, tools.tools);
710
802
  }
711
803
  catch (listToolsError) {
712
804
  console.warn('Failed to reload tools after reconnection', {
@@ -850,7 +942,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
850
942
  serverInfo.openApiClient = openApiClient;
851
943
  console.log(`Successfully initialized OpenAPI server: ${name} with ${mcpTools.length} tools`);
852
944
  // Save tools as vector embeddings for search
853
- saveToolsAsVectorEmbeddings(name, mcpTools, {
945
+ syncToolsAsVectorEmbeddings(name, mcpTools, {
854
946
  reportProgress: options?.reportEmbeddingProgress === true && serverName === name,
855
947
  }).catch((error) => {
856
948
  console.warn(`[EMBED_SYNC_ERROR] Failed to sync OpenAPI embeddings for server "${name}"`);
@@ -875,12 +967,8 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
875
967
  else {
876
968
  transport = await createTransportFromConfig(name, expandedConf);
877
969
  }
878
- const client = new Client({
879
- name: `mcp-client-${name}`,
880
- version: '1.0.0',
881
- }, {
882
- capabilities: {},
883
- });
970
+ const serverInfoRef = {};
971
+ const client = createUpstreamMcpClient(name, () => serverInfoRef.current);
884
972
  // Get request options from server configuration, with fallbacks
885
973
  const serverRequestOptions = expandedConf.options || {};
886
974
  const defaultRequestTimeout = Number(process.env.DEFAULT_REQUEST_TIMEOUT) || 60000;
@@ -911,6 +999,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
911
999
  createTime: Date.now(),
912
1000
  config: expandedConf, // Store reference to expanded config
913
1001
  };
1002
+ serverInfoRef.current = serverInfo;
914
1003
  const pendingAuth = expandedConf.oauth?.pendingAuthorization;
915
1004
  if (pendingAuth) {
916
1005
  serverInfo.status = 'oauth_required';
@@ -937,20 +1026,8 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
937
1026
  .listTools({}, initRequestOptions || requestOptions)
938
1027
  .then((tools) => {
939
1028
  console.log(`Successfully listed ${tools.tools.length} tools for server: ${name}`);
940
- serverInfo.tools = tools.tools.map((tool) => ({
941
- name: `${name}${getNameSeparator()}${tool.name}`,
942
- description: tool.description || '',
943
- inputSchema: cleanInputSchema(tool.inputSchema || {}),
944
- }));
945
- // Save tools as vector embeddings for search
946
- saveToolsAsVectorEmbeddings(name, serverInfo.tools, {
947
- reportProgress: options?.reportEmbeddingProgress === true && serverName === name,
948
- }).catch((embeddingError) => {
949
- console.warn(`[EMBED_SYNC_ERROR] Failed to sync tool embeddings for connected server "${name}"`);
950
- console.error('Error syncing tool embeddings for connected server', {
951
- serverName: name,
952
- error: summarizeErrorForLogging(embeddingError),
953
- });
1029
+ updateServerToolsCache(serverInfo, tools.tools, {
1030
+ reportEmbeddingProgress: options?.reportEmbeddingProgress === true && serverName === name,
954
1031
  });
955
1032
  })
956
1033
  .catch((error) => {
@@ -966,12 +1043,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
966
1043
  .listPrompts({}, initRequestOptions || requestOptions)
967
1044
  .then((prompts) => {
968
1045
  console.log(`Successfully listed ${prompts.prompts.length} prompts for server: ${name}`);
969
- serverInfo.prompts = prompts.prompts.map((prompt) => normalizePromptForList({
970
- name: `${name}${getNameSeparator()}${prompt.name}`,
971
- title: prompt.title,
972
- description: prompt.description,
973
- arguments: prompt.arguments,
974
- }));
1046
+ updateServerPromptsCache(serverInfo, prompts.prompts);
975
1047
  })
976
1048
  .catch((error) => {
977
1049
  console.error('Failed to list prompts for server', {
@@ -986,12 +1058,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
986
1058
  .listResources({}, initRequestOptions || requestOptions)
987
1059
  .then((resources) => {
988
1060
  console.log(`Successfully listed ${resources.resources.length} resources for server: ${name}`);
989
- serverInfo.resources = resources.resources.map((resource) => normalizeResourceForList({
990
- uri: resource.uri,
991
- name: resource.name,
992
- description: resource.description,
993
- mimeType: resource.mimeType,
994
- }));
1061
+ updateServerResourcesCache(serverInfo, resources.resources);
995
1062
  })
996
1063
  .catch((error) => {
997
1064
  console.error('Failed to list resources for server', {
@@ -1360,6 +1427,48 @@ export const toggleServerStatus = async (name, enabled) => {
1360
1427
  return { success: false, message: 'Failed to toggle server status' };
1361
1428
  }
1362
1429
  };
1430
+ const getMcpAppsRouteContext = async (sessionId, group) => {
1431
+ if (!sessionId ||
1432
+ isSmartRoutingGroup(group) ||
1433
+ !hasMcpAppsCapability(servers[sessionId]?.getClientCapabilities())) {
1434
+ return { enabled: false };
1435
+ }
1436
+ const { filteredServerInfos } = await getFilteredServerInfosForGroup(group);
1437
+ if (filteredServerInfos.length !== 1 ||
1438
+ filteredServerInfos[0].status !== 'connected' ||
1439
+ !filteredServerInfos[0].client) {
1440
+ return { enabled: false };
1441
+ }
1442
+ return {
1443
+ enabled: true,
1444
+ serverInfo: filteredServerInfos[0],
1445
+ };
1446
+ };
1447
+ const normalizeToolNameForServer = (serverName, toolName) => {
1448
+ const prefix = `${serverName}${getNameSeparator()}`;
1449
+ return toolName.startsWith(prefix) ? toolName.substring(prefix.length) : toolName;
1450
+ };
1451
+ const findToolOnServer = (serverInfo, toolName, allowRawName) => {
1452
+ return serverInfo.tools.find((tool) => tool.name === toolName ||
1453
+ (allowRawName && normalizeToolNameForServer(serverInfo.name, tool.name) === toolName));
1454
+ };
1455
+ const assertToolAvailableForRoute = (tool, appsRouteContext) => {
1456
+ if (isAppOnlyTool(tool) && !appsRouteContext.enabled) {
1457
+ throw new Error(`Tool '${tool.name}' is only available to MCP Apps`);
1458
+ }
1459
+ };
1460
+ const projectToolForDownstream = (serverName, tool, appsRouteContext) => {
1461
+ if (!appsRouteContext.enabled && isAppOnlyTool(tool)) {
1462
+ return undefined;
1463
+ }
1464
+ const projectedTool = appsRouteContext.enabled ? tool : stripMcpAppsMetadata(tool);
1465
+ return {
1466
+ ...projectedTool,
1467
+ name: appsRouteContext.enabled
1468
+ ? normalizeToolNameForServer(serverName, projectedTool.name)
1469
+ : projectedTool.name,
1470
+ };
1471
+ };
1363
1472
  export const handleListToolsRequest = async (_, extra) => {
1364
1473
  const sessionId = extra.sessionId || '';
1365
1474
  const group = getGroup(sessionId);
@@ -1370,6 +1479,7 @@ export const handleListToolsRequest = async (_, extra) => {
1370
1479
  return getSmartRoutingTools(group);
1371
1480
  }
1372
1481
  const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
1482
+ const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
1373
1483
  const allTools = [];
1374
1484
  for (const serverInfo of filteredServerInfos) {
1375
1485
  if (serverInfo.tools && serverInfo.tools.length > 0) {
@@ -1386,7 +1496,10 @@ export const handleListToolsRequest = async (_, extra) => {
1386
1496
  description: toolConfig?.description || tool.description, // Use custom description if available
1387
1497
  };
1388
1498
  });
1389
- allTools.push(...toolsWithCustomDescriptions);
1499
+ allTools.push(...toolsWithCustomDescriptions.flatMap((tool) => {
1500
+ const projectedTool = projectToolForDownstream(serverInfo.name, tool, appsRouteContext);
1501
+ return projectedTool ? [projectedTool] : [];
1502
+ }));
1390
1503
  }
1391
1504
  }
1392
1505
  return {
@@ -1405,6 +1518,7 @@ export const handleCallToolRequest = async (request, extra) => {
1405
1518
  // Fallback to extra for backward compatibility (e.g., direct API calls)
1406
1519
  const group = requestContextService.getGroupContext() || extra?.group || getGroup(sessionId) || undefined;
1407
1520
  const username = requestContextService.getUsernameContext() || extra?.username || undefined;
1521
+ let appsRouteContext = { enabled: false };
1408
1522
  const keyId = bearerKeyContext.keyId || extra?.keyId || undefined;
1409
1523
  const keyName = bearerKeyContext.keyName || extra?.keyName || undefined;
1410
1524
  const sourceIp = requestContextService.getRequestContext()?.remoteAddress || undefined;
@@ -1425,6 +1539,7 @@ export const handleCallToolRequest = async (request, extra) => {
1425
1539
  });
1426
1540
  };
1427
1541
  try {
1542
+ appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
1428
1543
  // Special handling for smart routing tools
1429
1544
  if (request.params.name === 'search_tools') {
1430
1545
  const { query, limit = 10 } = request.params.arguments || {};
@@ -1443,7 +1558,10 @@ export const handleCallToolRequest = async (request, extra) => {
1443
1558
  }
1444
1559
  const { arguments: toolArgs } = request.params.arguments || {};
1445
1560
  let targetServerInfo;
1446
- if (extra && extra.server) {
1561
+ if (appsRouteContext.enabled) {
1562
+ targetServerInfo = appsRouteContext.serverInfo;
1563
+ }
1564
+ else if (extra && extra.server) {
1447
1565
  targetServerInfo = getServerByName(extra.server);
1448
1566
  }
1449
1567
  else {
@@ -1456,10 +1574,11 @@ export const handleCallToolRequest = async (request, extra) => {
1456
1574
  throw new Error(`No available servers found with tool: ${toolName}`);
1457
1575
  }
1458
1576
  // Check if the tool exists on the server
1459
- const toolExists = targetServerInfo.tools.some((tool) => tool.name === toolName);
1460
- if (!toolExists) {
1577
+ const tool = findToolOnServer(targetServerInfo, toolName, appsRouteContext.enabled);
1578
+ if (!tool) {
1461
1579
  throw new Error(`Tool '${toolName}' not found on server '${targetServerInfo.name}'`);
1462
1580
  }
1581
+ assertToolAvailableForRoute(tool, appsRouteContext);
1463
1582
  // Handle OpenAPI servers differently
1464
1583
  if (targetServerInfo.openApiClient) {
1465
1584
  // For OpenAPI servers, use the OpenAPI client
@@ -1472,11 +1591,7 @@ export const handleCallToolRequest = async (request, extra) => {
1472
1591
  arguments: summarizeArgumentsForLogging(finalArgs),
1473
1592
  });
1474
1593
  // Remove server prefix from tool name if present
1475
- const separator = getNameSeparator();
1476
- const prefix = `${targetServerInfo.name}${separator}`;
1477
- const cleanToolName = toolName.startsWith(prefix)
1478
- ? toolName.substring(prefix.length)
1479
- : toolName;
1594
+ const cleanToolName = normalizeToolNameForServer(targetServerInfo.name, toolName);
1480
1595
  // Extract passthrough headers from extra or request context
1481
1596
  let passthroughHeaders;
1482
1597
  let requestHeaders = null;
@@ -1528,14 +1643,18 @@ export const handleCallToolRequest = async (request, extra) => {
1528
1643
  keyName,
1529
1644
  sourceIp,
1530
1645
  });
1531
- return {
1646
+ return await maybeCompressToolResult({
1532
1647
  content: [
1533
1648
  {
1534
1649
  type: 'text',
1535
1650
  text: JSON.stringify(result),
1536
1651
  },
1537
1652
  ],
1538
- };
1653
+ }, {
1654
+ serverName: targetServerInfo.name,
1655
+ toolName: cleanToolName,
1656
+ group,
1657
+ });
1539
1658
  }
1540
1659
  // Call the tool on the target server (MCP servers)
1541
1660
  const client = targetServerInfo.client;
@@ -1549,11 +1668,7 @@ export const handleCallToolRequest = async (request, extra) => {
1549
1668
  serverName: targetServerInfo.name,
1550
1669
  arguments: summarizeArgumentsForLogging(finalArgs),
1551
1670
  });
1552
- const separator = getNameSeparator();
1553
- const prefix = `${targetServerInfo.name}${separator}`;
1554
- const cleanToolName = toolName.startsWith(prefix)
1555
- ? toolName.substring(prefix.length)
1556
- : toolName;
1671
+ const cleanToolName = normalizeToolNameForServer(targetServerInfo.name, toolName);
1557
1672
  await reserveHostedIfNeeded(targetServerInfo.name, cleanToolName);
1558
1673
  const result = await callToolWithReconnect(targetServerInfo, {
1559
1674
  name: cleanToolName,
@@ -1585,23 +1700,29 @@ export const handleCallToolRequest = async (request, extra) => {
1585
1700
  sourceIp,
1586
1701
  errorMessage: result.isError ? 'Tool returned error response' : undefined,
1587
1702
  });
1588
- return result;
1703
+ return await maybeCompressToolResult(result, {
1704
+ serverName: targetServerInfo.name,
1705
+ toolName: cleanToolName,
1706
+ group,
1707
+ });
1589
1708
  }
1590
1709
  // Regular tool handling
1591
- const serverInfo = getServerByTool(request.params.name);
1592
- if (!serverInfo) {
1710
+ const serverInfo = appsRouteContext.enabled
1711
+ ? appsRouteContext.serverInfo
1712
+ : getServerByTool(request.params.name);
1713
+ const tool = serverInfo
1714
+ ? findToolOnServer(serverInfo, request.params.name, appsRouteContext.enabled)
1715
+ : undefined;
1716
+ if (!serverInfo || !tool) {
1593
1717
  throw new Error(`Server not found: ${request.params.name}`);
1594
1718
  }
1719
+ assertToolAvailableForRoute(tool, appsRouteContext);
1595
1720
  // Handle OpenAPI servers differently
1596
1721
  if (serverInfo.openApiClient) {
1597
1722
  // For OpenAPI servers, use the OpenAPI client
1598
1723
  const openApiClient = serverInfo.openApiClient;
1599
1724
  // Remove server prefix from tool name if present
1600
- const separator = getNameSeparator();
1601
- const prefix = `${serverInfo.name}${separator}`;
1602
- const cleanToolName = request.params.name.startsWith(prefix)
1603
- ? request.params.name.substring(prefix.length)
1604
- : request.params.name;
1725
+ const cleanToolName = normalizeToolNameForServer(serverInfo.name, request.params.name);
1605
1726
  console.log('Invoking OpenAPI tool', {
1606
1727
  toolName: cleanToolName,
1607
1728
  serverName: serverInfo.name,
@@ -1659,25 +1780,25 @@ export const handleCallToolRequest = async (request, extra) => {
1659
1780
  keyName,
1660
1781
  sourceIp,
1661
1782
  });
1662
- return {
1783
+ return await maybeCompressToolResult({
1663
1784
  content: [
1664
1785
  {
1665
1786
  type: 'text',
1666
1787
  text: JSON.stringify(result),
1667
1788
  },
1668
1789
  ],
1669
- };
1790
+ }, {
1791
+ serverName: serverInfo.name,
1792
+ toolName: cleanToolName,
1793
+ group,
1794
+ });
1670
1795
  }
1671
1796
  // Handle MCP servers
1672
1797
  const client = serverInfo.client;
1673
1798
  if (!client) {
1674
1799
  throw new Error(`Client not found for server: ${serverInfo.name}`);
1675
1800
  }
1676
- const separator = getNameSeparator();
1677
- const prefix = `${serverInfo.name}${separator}`;
1678
- const cleanToolName = request.params.name.startsWith(prefix)
1679
- ? request.params.name.substring(prefix.length)
1680
- : request.params.name;
1801
+ const cleanToolName = normalizeToolNameForServer(serverInfo.name, request.params.name);
1681
1802
  await reserveHostedIfNeeded(serverInfo.name, cleanToolName);
1682
1803
  const result = await callToolWithReconnect(serverInfo, { ...request.params, name: cleanToolName }, serverInfo.options || {});
1683
1804
  await settleHostedIfNeeded({
@@ -1706,7 +1827,11 @@ export const handleCallToolRequest = async (request, extra) => {
1706
1827
  sourceIp,
1707
1828
  errorMessage: result.isError ? 'Tool returned error response' : undefined,
1708
1829
  });
1709
- return result;
1830
+ return await maybeCompressToolResult(result, {
1831
+ serverName: serverInfo.name,
1832
+ toolName: cleanToolName,
1833
+ group,
1834
+ });
1710
1835
  }
1711
1836
  catch (error) {
1712
1837
  console.error('Error handling CallToolRequest', summarizeErrorForLogging(error));
@@ -1860,6 +1985,7 @@ export const handleListResourcesRequest = async (_, extra) => {
1860
1985
  const sessionId = extra.sessionId || '';
1861
1986
  const group = getGroup(sessionId);
1862
1987
  console.log(`Handling ListResourcesRequest for group: ${group}`);
1988
+ const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
1863
1989
  // Start with built-in resources (only enabled ones)
1864
1990
  const builtinResources = await getBuiltinResourceDao().findEnabled();
1865
1991
  const allResources = builtinResources.map((br) => normalizeResourceForList({
@@ -1884,10 +2010,13 @@ export const handleListResourcesRequest = async (_, extra) => {
1884
2010
  // Apply custom descriptions from server configuration
1885
2011
  const resourcesWithCustomDescriptions = enabledResources.map((resource) => {
1886
2012
  const resourceConfig = serverConfig?.resources?.[resource.uri];
1887
- return normalizeResourceForList({
2013
+ const normalizedResource = normalizeResourceForList({
1888
2014
  ...resource,
1889
2015
  description: resourceConfig?.description || resource.description,
1890
2016
  });
2017
+ return appsRouteContext.enabled
2018
+ ? normalizedResource
2019
+ : stripMcpAppsMetadata(normalizedResource);
1891
2020
  });
1892
2021
  allResources.push(...resourcesWithCustomDescriptions);
1893
2022
  }
@@ -1900,6 +2029,7 @@ export const handleListResourceTemplatesRequest = async (_, extra) => {
1900
2029
  const sessionId = extra.sessionId || '';
1901
2030
  const group = getGroup(sessionId);
1902
2031
  console.log(`Handling ListResourceTemplatesRequest for group: ${group}`);
2032
+ const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
1903
2033
  const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group, {
1904
2034
  requireClient: true,
1905
2035
  });
@@ -1908,15 +2038,21 @@ export const handleListResourceTemplatesRequest = async (_, extra) => {
1908
2038
  return [];
1909
2039
  }
1910
2040
  const templates = await serverInfo.client.listResourceTemplates({}, serverInfo.options || {});
1911
- return filterResourceTemplatesByGroup(group, serverInfo.name, templates.resourceTemplates || [], serverConfigsByName.get(serverInfo.name));
2041
+ const filteredTemplates = await filterResourceTemplatesByGroup(group, serverInfo.name, templates.resourceTemplates || [], serverConfigsByName.get(serverInfo.name));
2042
+ return appsRouteContext.enabled
2043
+ ? filteredTemplates
2044
+ : filteredTemplates.map((template) => stripMcpAppsMetadata(template));
1912
2045
  }));
1913
2046
  return {
1914
2047
  resourceTemplates: results.flatMap((result) => result.status === 'fulfilled' ? result.value : []),
1915
2048
  };
1916
2049
  };
1917
- export const handleReadResourceRequest = async (request, _extra) => {
2050
+ export const handleReadResourceRequest = async (request, extra) => {
1918
2051
  try {
1919
2052
  const { uri } = request.params;
2053
+ const sessionId = extra.sessionId || '';
2054
+ const group = getGroup(sessionId);
2055
+ const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
1920
2056
  // Check built-in resources first
1921
2057
  const builtinResource = await getBuiltinResourceDao().findByUri(uri);
1922
2058
  if (builtinResource && builtinResource.enabled !== false) {
@@ -1930,18 +2066,39 @@ export const handleReadResourceRequest = async (request, _extra) => {
1930
2066
  ],
1931
2067
  };
1932
2068
  }
1933
- // Find the server that owns this resource
1934
- const server = serverInfos.find((serverInfo) => serverInfo.status === 'connected' &&
1935
- serverInfo.enabled !== false &&
1936
- serverInfo.resources?.find((resource) => resource.uri === uri));
1937
- if (!server) {
2069
+ const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
2070
+ let server;
2071
+ for (const serverInfo of filteredServerInfos) {
2072
+ if (serverInfo.status !== 'connected') {
2073
+ continue;
2074
+ }
2075
+ const serverConfig = await getServerDao().findById(serverInfo.name);
2076
+ let enabledResources = serverInfo.resources;
2077
+ if (serverConfig?.resources) {
2078
+ enabledResources = enabledResources.filter((resource) => serverConfig.resources?.[resource.uri]?.enabled !== false);
2079
+ }
2080
+ enabledResources = await filterResourcesByGroup(group, serverInfo.name, enabledResources, serverConfigsByName.get(serverInfo.name));
2081
+ if (enabledResources.some((resource) => resource.uri === uri)) {
2082
+ server = serverInfo;
2083
+ break;
2084
+ }
2085
+ }
2086
+ if (!server && appsRouteContext.enabled && uri.startsWith('ui://')) {
2087
+ server = appsRouteContext.serverInfo;
2088
+ }
2089
+ if (!server?.client) {
1938
2090
  throw new Error(`Resource not found: ${uri}`);
1939
2091
  }
1940
- const result = await server.client?.readResource({ uri });
1941
- if (!result) {
2092
+ const result = await server.client.readResource({ uri });
2093
+ if (!result || !Array.isArray(result.contents)) {
1942
2094
  throw new Error(`Failed to read resource: ${uri}`);
1943
2095
  }
1944
- return result;
2096
+ return appsRouteContext.enabled
2097
+ ? result
2098
+ : {
2099
+ ...result,
2100
+ contents: result.contents.map((content) => stripMcpAppsMetadata(content)),
2101
+ };
1945
2102
  }
1946
2103
  catch (error) {
1947
2104
  console.error('Error handling ReadResourceRequest', summarizeErrorForLogging(error));
@@ -1968,7 +2125,12 @@ export const createMcpServer = (name, version, options) => {
1968
2125
  }
1969
2126
  // If no group, use default name (global routing)
1970
2127
  const server = new Server({ name: serverName, version }, {
1971
- capabilities: { tools: {}, prompts: {}, resources: {} },
2128
+ capabilities: {
2129
+ tools: { listChanged: true },
2130
+ prompts: { listChanged: true },
2131
+ resources: { listChanged: true },
2132
+ ...MCP_APPS_CAPABILITIES,
2133
+ },
1972
2134
  ...(normalizedOptions.instructions !== undefined
1973
2135
  ? { instructions: normalizedOptions.instructions }
1974
2136
  : {}),