@samanhappy/mcphub 0.12.11 → 0.12.13

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 (69) hide show
  1. package/README.md +2 -1
  2. package/dist/clients/openapi.js +3 -1
  3. package/dist/clients/openapi.js.map +1 -1
  4. package/dist/controllers/groupController.js +11 -9
  5. package/dist/controllers/groupController.js.map +1 -1
  6. package/dist/controllers/mcpbController.js +32 -3
  7. package/dist/controllers/mcpbController.js.map +1 -1
  8. package/dist/controllers/serverController.js +83 -45
  9. package/dist/controllers/serverController.js.map +1 -1
  10. package/dist/dao/BearerKeyDao.js +2 -1
  11. package/dist/dao/BearerKeyDao.js.map +1 -1
  12. package/dist/dao/ServerDaoDbImpl.js +36 -21
  13. package/dist/dao/ServerDaoDbImpl.js.map +1 -1
  14. package/dist/db/repositories/VectorEmbeddingRepository.js +9 -3
  15. package/dist/db/repositories/VectorEmbeddingRepository.js.map +1 -1
  16. package/dist/middlewares/auth.js +16 -14
  17. package/dist/middlewares/auth.js.map +1 -1
  18. package/dist/middlewares/index.js +11 -2
  19. package/dist/middlewares/index.js.map +1 -1
  20. package/dist/middlewares/userContext.js +4 -5
  21. package/dist/middlewares/userContext.js.map +1 -1
  22. package/dist/routes/index.js +3 -12
  23. package/dist/routes/index.js.map +1 -1
  24. package/dist/server.js +11 -10
  25. package/dist/server.js.map +1 -1
  26. package/dist/services/activityLoggingService.js +4 -2
  27. package/dist/services/activityLoggingService.js.map +1 -1
  28. package/dist/services/groupService.js +10 -5
  29. package/dist/services/groupService.js.map +1 -1
  30. package/dist/services/logService.js +6 -5
  31. package/dist/services/logService.js.map +1 -1
  32. package/dist/services/mcpOAuthProvider.js +4 -56
  33. package/dist/services/mcpOAuthProvider.js.map +1 -1
  34. package/dist/services/mcpService.js +382 -127
  35. package/dist/services/mcpService.js.map +1 -1
  36. package/dist/services/oauthClientRegistration.js +5 -1
  37. package/dist/services/oauthClientRegistration.js.map +1 -1
  38. package/dist/services/oauthServerService.js +3 -2
  39. package/dist/services/oauthServerService.js.map +1 -1
  40. package/dist/services/sseService.js +7 -5
  41. package/dist/services/sseService.js.map +1 -1
  42. package/dist/services/templateService.js +8 -1
  43. package/dist/services/templateService.js.map +1 -1
  44. package/dist/services/vectorSearchService.js +120 -74
  45. package/dist/services/vectorSearchService.js.map +1 -1
  46. package/dist/utils/bearerAuth.js +41 -0
  47. package/dist/utils/bearerAuth.js.map +1 -0
  48. package/dist/utils/oauthBearer.js +11 -2
  49. package/dist/utils/oauthBearer.js.map +1 -1
  50. package/dist/utils/oauthRedirectUri.js +65 -0
  51. package/dist/utils/oauthRedirectUri.js.map +1 -0
  52. package/dist/utils/rateLimit.js +23 -0
  53. package/dist/utils/rateLimit.js.map +1 -0
  54. package/dist/utils/safeCompare.js +16 -0
  55. package/dist/utils/safeCompare.js.map +1 -0
  56. package/dist/utils/serialization.js +156 -1
  57. package/dist/utils/serialization.js.map +1 -1
  58. package/dist/utils/serverConfigPersistence.js +175 -0
  59. package/dist/utils/serverConfigPersistence.js.map +1 -0
  60. package/frontend/dist/assets/index-BFEyMCq8.css +1 -0
  61. package/frontend/dist/assets/index-BhBoIoYG.js +323 -0
  62. package/frontend/dist/assets/index-BhBoIoYG.js.map +1 -0
  63. package/frontend/dist/assets/{resourceService-OPtBdW1q.js → resourceService-D25G2-Ta.js} +2 -2
  64. package/frontend/dist/assets/{resourceService-OPtBdW1q.js.map → resourceService-D25G2-Ta.js.map} +1 -1
  65. package/frontend/dist/index.html +2 -2
  66. package/package.json +2 -2
  67. package/frontend/dist/assets/index-DHhMZeA_.js +0 -319
  68. package/frontend/dist/assets/index-DHhMZeA_.js.map +0 -1
  69. package/frontend/dist/assets/index-lGQBQ_t6.css +0 -1
@@ -2,7 +2,7 @@ import os from 'os';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
4
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
- import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
6
6
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
7
7
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
8
8
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
@@ -12,7 +12,7 @@ import { createFetchWithProxy, getProxyConfigFromEnv } from './proxy.js';
12
12
  import { expandEnvVars, replaceEnvVars, getNameSeparator } from '../config/index.js';
13
13
  import config from '../config/index.js';
14
14
  import { getGroup } from './sseService.js';
15
- import { getServersInGroup, getServerConfigInGroup } from './groupService.js';
15
+ import { getServerConfigsInGroup, getServerConfigInGroup } from './groupService.js';
16
16
  import { removeServerToolEmbeddings, saveToolsAsVectorEmbeddings } from './vectorSearchService.js';
17
17
  import { OpenAPIClient } from '../clients/openapi.js';
18
18
  import { RequestContextService } from './requestContextService.js';
@@ -22,6 +22,7 @@ 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 { formatErrorForLogging, sanitizeStringForLogging, summarizeErrorForLogging, } from '../utils/serialization.js';
25
26
  const servers = {};
26
27
  import { setupClientKeepAlive } from './keepAliveService.js';
27
28
  /**
@@ -309,6 +310,149 @@ const getHeaderValue = (headers, name) => {
309
310
  }
310
311
  return undefined;
311
312
  };
313
+ const LOG_SUMMARY_LIMIT = 8;
314
+ const getValueTypeForLogging = (value) => {
315
+ if (Array.isArray(value)) {
316
+ return `array(${value.length})`;
317
+ }
318
+ if (value === null) {
319
+ return 'null';
320
+ }
321
+ if (value && typeof value === 'object') {
322
+ return `object(${Object.keys(value).length} keys)`;
323
+ }
324
+ return typeof value;
325
+ };
326
+ const summarizeObjectShapeForLogging = (value) => {
327
+ const entries = Object.entries(value);
328
+ return {
329
+ keyCount: entries.length,
330
+ keys: entries.slice(0, LOG_SUMMARY_LIMIT).map(([key]) => key),
331
+ valueTypes: Object.fromEntries(entries
332
+ .slice(0, LOG_SUMMARY_LIMIT)
333
+ .map(([key, entryValue]) => [key, getValueTypeForLogging(entryValue)])),
334
+ truncated: entries.length > LOG_SUMMARY_LIMIT || undefined,
335
+ };
336
+ };
337
+ export const summarizeArgumentsForLogging = (value) => {
338
+ if (value === undefined) {
339
+ return { present: false };
340
+ }
341
+ if (Array.isArray(value)) {
342
+ return {
343
+ present: true,
344
+ type: 'array',
345
+ length: value.length,
346
+ itemTypes: Array.from(new Set(value.slice(0, LOG_SUMMARY_LIMIT).map(getValueTypeForLogging))),
347
+ truncated: value.length > LOG_SUMMARY_LIMIT || undefined,
348
+ };
349
+ }
350
+ if (value && typeof value === 'object') {
351
+ return {
352
+ present: true,
353
+ type: 'object',
354
+ ...summarizeObjectShapeForLogging(value),
355
+ };
356
+ }
357
+ return {
358
+ present: true,
359
+ type: getValueTypeForLogging(value),
360
+ };
361
+ };
362
+ const summarizeTextPayloadForLogging = (text) => ({
363
+ textLength: text.length,
364
+ looksLikeJson: /^[[{]/.test(text.trim()) || undefined,
365
+ wasSanitized: sanitizeStringForLogging(text) !== text || undefined,
366
+ });
367
+ const getHttpErrorStatusCode = (error) => {
368
+ if (!error || typeof error !== 'object') {
369
+ return undefined;
370
+ }
371
+ const err = error;
372
+ const code = err.status ?? err.response?.status ?? err.code;
373
+ if (typeof code === 'number' && Number.isFinite(code)) {
374
+ return code;
375
+ }
376
+ if (typeof code === 'string') {
377
+ const parsedCode = Number.parseInt(code, 10);
378
+ return Number.isFinite(parsedCode) ? parsedCode : undefined;
379
+ }
380
+ return undefined;
381
+ };
382
+ const isRecoverableHttp4xxError = (error) => {
383
+ const statusCode = getHttpErrorStatusCode(error);
384
+ if (statusCode !== undefined) {
385
+ return statusCode === 401 || statusCode === 404;
386
+ }
387
+ const message = typeof error?.message === 'string'
388
+ ? error.message
389
+ : '';
390
+ return /Error POSTing to endpoint \(HTTP 40[14]/.test(message);
391
+ };
392
+ const summarizeContentItemForLogging = (item) => {
393
+ if (!item || typeof item !== 'object') {
394
+ return { type: getValueTypeForLogging(item) };
395
+ }
396
+ const record = item;
397
+ return {
398
+ type: typeof record.type === 'string' ? record.type : 'object',
399
+ keys: Object.keys(record).slice(0, LOG_SUMMARY_LIMIT),
400
+ text: typeof record.text === 'string' ? summarizeTextPayloadForLogging(record.text) : undefined,
401
+ truncated: Object.keys(record).length > LOG_SUMMARY_LIMIT || undefined,
402
+ };
403
+ };
404
+ export const summarizeToolResultForLogging = (value) => {
405
+ if (!value || typeof value !== 'object') {
406
+ return { type: getValueTypeForLogging(value) };
407
+ }
408
+ const record = value;
409
+ const summary = {
410
+ type: 'object',
411
+ ...summarizeObjectShapeForLogging(record),
412
+ };
413
+ if (typeof record.isError === 'boolean') {
414
+ summary.isError = record.isError;
415
+ }
416
+ if (Array.isArray(record.content)) {
417
+ summary.contentCount = record.content.length;
418
+ summary.content = record.content
419
+ .slice(0, LOG_SUMMARY_LIMIT)
420
+ .map((item) => summarizeContentItemForLogging(item));
421
+ summary.contentTruncated = record.content.length > LOG_SUMMARY_LIMIT || undefined;
422
+ }
423
+ return summary;
424
+ };
425
+ const summarizeToolRequestForLogging = (params) => ({
426
+ name: typeof params?.name === 'string' ? params.name : 'unknown',
427
+ arguments: summarizeArgumentsForLogging(params?.arguments),
428
+ });
429
+ const summarizePromptForLogging = (prompt) => {
430
+ if (!prompt || typeof prompt !== 'object') {
431
+ return { type: getValueTypeForLogging(prompt) };
432
+ }
433
+ const record = prompt;
434
+ const summary = {
435
+ type: 'object',
436
+ ...summarizeObjectShapeForLogging(record),
437
+ };
438
+ if (Array.isArray(record.messages)) {
439
+ const messages = record.messages;
440
+ summary.messageCount = messages.length;
441
+ summary.messages = messages.slice(0, LOG_SUMMARY_LIMIT).map((message) => ({
442
+ role: typeof message.role === 'string' ? message.role : undefined,
443
+ contentType: message.content && typeof message.content === 'object'
444
+ ? message.content.type
445
+ : getValueTypeForLogging(message.content),
446
+ text: message.content &&
447
+ typeof message.content === 'object' &&
448
+ typeof message.content.text === 'string'
449
+ ? summarizeTextPayloadForLogging(String(message.content.text))
450
+ : undefined,
451
+ }));
452
+ summary.messagesTruncated = messages.length > LOG_SUMMARY_LIMIT || undefined;
453
+ }
454
+ return summary;
455
+ };
312
456
  export const collectPassthroughHeaders = (requestHeaders, passthroughHeaderNames) => {
313
457
  if (!requestHeaders || !Array.isArray(passthroughHeaderNames) || passthroughHeaderNames.length === 0) {
314
458
  return {};
@@ -422,7 +566,7 @@ export const createTransportFromConfig = async (name, conf) => {
422
566
  const { command: finalCommand, args: finalArgs } = wrapWithProxychains(name, conf.command, replaceEnvVars(conf.args), conf.proxy);
423
567
  // Create STDIO transport with potentially wrapped command
424
568
  transport = new StdioClientTransport({
425
- cwd: os.homedir(),
569
+ cwd: process.cwd(),
426
570
  command: finalCommand,
427
571
  args: finalArgs,
428
572
  env: env,
@@ -450,8 +594,7 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
450
594
  return result;
451
595
  }
452
596
  catch (error) {
453
- // Check if error message starts with "Error POSTing to endpoint (HTTP 40"
454
- const isHttp40xError = error?.message?.startsWith?.('Error POSTing to endpoint (HTTP 40');
597
+ const isHttp40xError = isRecoverableHttp4xxError(error);
455
598
  // Only retry for StreamableHTTPClientTransport
456
599
  const isStreamableHttp = serverInfo.transport instanceof StreamableHTTPClientTransport;
457
600
  const isSSE = serverInfo.transport instanceof SSEClientTransport;
@@ -499,12 +642,15 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
499
642
  console.warn(`[EMBED_SYNC_ERROR] Failed to sync tool embeddings after reconnect for server "${serverInfo.name}"`);
500
643
  console.error('Error syncing tool embeddings after reconnect', {
501
644
  serverName: serverInfo.name,
502
- error,
645
+ error: summarizeErrorForLogging(error),
503
646
  });
504
647
  });
505
648
  }
506
649
  catch (listToolsError) {
507
- console.warn(`Failed to reload tools after reconnection for server ${serverInfo.name}:`, listToolsError);
650
+ console.warn('Failed to reload tools after reconnection', {
651
+ serverName: serverInfo.name,
652
+ error: summarizeErrorForLogging(listToolsError),
653
+ });
508
654
  // Continue anyway, as the connection might still work for the current tool
509
655
  }
510
656
  console.log(`Successfully reconnected to server: ${serverInfo.name}`);
@@ -514,10 +660,10 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
514
660
  catch (reconnectError) {
515
661
  console.error('Failed to reconnect to server', {
516
662
  serverName: serverInfo.name,
517
- error: reconnectError,
663
+ error: summarizeErrorForLogging(reconnectError),
518
664
  });
519
665
  serverInfo.status = 'disconnected';
520
- serverInfo.error = `Failed to reconnect: ${reconnectError}`;
666
+ serverInfo.error = `Failed to reconnect: ${formatErrorForLogging(reconnectError)}`;
521
667
  // If this was the last attempt, throw the original error
522
668
  if (attempt === maxRetries) {
523
669
  throw error;
@@ -624,15 +770,21 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
624
770
  reportProgress: options?.reportEmbeddingProgress === true && serverName === name,
625
771
  }).catch((error) => {
626
772
  console.warn(`[EMBED_SYNC_ERROR] Failed to sync OpenAPI embeddings for server "${name}"`);
627
- console.error('Error syncing OpenAPI tool embeddings', { serverName: name, error });
773
+ console.error('Error syncing OpenAPI tool embeddings', {
774
+ serverName: name,
775
+ error: summarizeErrorForLogging(error),
776
+ });
628
777
  });
629
778
  continue;
630
779
  }
631
780
  catch (error) {
632
- console.error('Failed to initialize OpenAPI server', { serverName: name, error });
781
+ console.error('Failed to initialize OpenAPI server', {
782
+ serverName: name,
783
+ error: summarizeErrorForLogging(error),
784
+ });
633
785
  // Update the already pushed server info with error status
634
786
  serverInfo.status = 'disconnected';
635
- serverInfo.error = `Failed to initialize OpenAPI server: ${error}`;
787
+ serverInfo.error = `Failed to initialize OpenAPI server: ${formatErrorForLogging(error)}`;
636
788
  continue;
637
789
  }
638
790
  }
@@ -688,7 +840,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
688
840
  .then(() => {
689
841
  console.log(`Successfully connected client for server: ${name}`);
690
842
  const capabilities = client.getServerCapabilities();
691
- console.log(`Server capabilities: ${JSON.stringify(capabilities)}`);
843
+ console.log('Server capabilities', JSON.stringify(capabilities));
692
844
  let dataError = null;
693
845
  if (capabilities?.tools) {
694
846
  client
@@ -707,12 +859,15 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
707
859
  console.warn(`[EMBED_SYNC_ERROR] Failed to sync tool embeddings for connected server "${name}"`);
708
860
  console.error('Error syncing tool embeddings for connected server', {
709
861
  serverName: name,
710
- error: embeddingError,
862
+ error: summarizeErrorForLogging(embeddingError),
711
863
  });
712
864
  });
713
865
  })
714
866
  .catch((error) => {
715
- console.error(`Failed to list tools for server ${name} by error: ${error} with stack: ${error.stack}`);
867
+ console.error('Failed to list tools for server', {
868
+ serverName: name,
869
+ error: summarizeErrorForLogging(error),
870
+ });
716
871
  dataError = error;
717
872
  });
718
873
  }
@@ -729,7 +884,10 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
729
884
  }));
730
885
  })
731
886
  .catch((error) => {
732
- console.error(`Failed to list prompts for server ${name} by error: ${error} with stack: ${error.stack}`);
887
+ console.error('Failed to list prompts for server', {
888
+ serverName: name,
889
+ error: summarizeErrorForLogging(error),
890
+ });
733
891
  dataError = error;
734
892
  });
735
893
  }
@@ -746,7 +904,10 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
746
904
  }));
747
905
  })
748
906
  .catch((error) => {
749
- console.error(`Failed to list resources for server ${name} by error: ${error} with stack: ${error.stack}`);
907
+ console.error('Failed to list resources for server', {
908
+ serverName: name,
909
+ error: summarizeErrorForLogging(error),
910
+ });
750
911
  dataError = error;
751
912
  });
752
913
  }
@@ -754,11 +915,14 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
754
915
  serverInfo.status = 'connected';
755
916
  serverInfo.error = null;
756
917
  // Set up keep-alive ping for SSE connections via shared service
757
- setupClientKeepAlive(serverInfo, expandedConf).catch((e) => console.warn('Keepalive setup failed', { serverName: name, error: e }));
918
+ setupClientKeepAlive(serverInfo, expandedConf).catch((e) => console.warn('Keepalive setup failed', {
919
+ serverName: name,
920
+ error: summarizeErrorForLogging(e),
921
+ }));
758
922
  }
759
923
  else {
760
924
  serverInfo.status = 'disconnected';
761
- serverInfo.error = `Failed to list data: ${dataError} `;
925
+ serverInfo.error = `Failed to list data: ${formatErrorForLogging(dataError)}`;
762
926
  }
763
927
  })
764
928
  .catch(async (error) => {
@@ -776,10 +940,13 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
776
940
  serverInfo.error = null;
777
941
  }
778
942
  else {
779
- console.error(`Failed to connect client for server ${name} by error: ${error} with stack: ${error.stack}`);
943
+ console.error('Failed to connect client for server', {
944
+ serverName: name,
945
+ error: summarizeErrorForLogging(error),
946
+ });
780
947
  // Other connection errors
781
948
  serverInfo.status = 'disconnected';
782
- serverInfo.error = `Failed to connect: ${error.stack} `;
949
+ serverInfo.error = `Failed to connect: ${formatErrorForLogging(error)}`;
783
950
  }
784
951
  });
785
952
  console.log(`Initialized client for server: ${name}`);
@@ -1067,7 +1234,7 @@ export const toggleServerStatus = async (name, enabled) => {
1067
1234
  catch (embeddingError) {
1068
1235
  console.warn('Failed to remove embeddings for server', {
1069
1236
  serverName: name,
1070
- error: embeddingError,
1237
+ error: summarizeErrorForLogging(embeddingError),
1071
1238
  });
1072
1239
  }
1073
1240
  }
@@ -1080,7 +1247,7 @@ export const toggleServerStatus = async (name, enabled) => {
1080
1247
  catch (reconnectError) {
1081
1248
  console.warn('Failed to reconnect server during enable', {
1082
1249
  serverName: name,
1083
- error: reconnectError,
1250
+ error: summarizeErrorForLogging(reconnectError),
1084
1251
  });
1085
1252
  }
1086
1253
  }
@@ -1100,32 +1267,14 @@ export const handleListToolsRequest = async (_, extra) => {
1100
1267
  if (isSmartRoutingGroup(group)) {
1101
1268
  return getSmartRoutingTools(group);
1102
1269
  }
1103
- // Need to filter servers based on group asynchronously
1104
- const filteredServerInfos = [];
1105
- for (const serverInfo of getDataService().filterData(serverInfos)) {
1106
- if (serverInfo.enabled === false)
1107
- continue;
1108
- if (!group) {
1109
- filteredServerInfos.push(serverInfo);
1110
- continue;
1111
- }
1112
- const serversInGroup = await getServersInGroup(group);
1113
- if (!serversInGroup || serversInGroup.length === 0) {
1114
- if (serverInfo.name === group)
1115
- filteredServerInfos.push(serverInfo);
1116
- continue;
1117
- }
1118
- if (serversInGroup.includes(serverInfo.name)) {
1119
- filteredServerInfos.push(serverInfo);
1120
- }
1121
- }
1270
+ const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
1122
1271
  const allTools = [];
1123
1272
  for (const serverInfo of filteredServerInfos) {
1124
1273
  if (serverInfo.tools && serverInfo.tools.length > 0) {
1125
1274
  // Filter tools based on server configuration
1126
1275
  let tools = await filterToolsByConfig(serverInfo.name, serverInfo.tools);
1127
1276
  // If this is a group request, apply group-level tool filtering
1128
- tools = await filterToolsByGroup(group, serverInfo.name, tools);
1277
+ tools = await filterToolsByGroup(group, serverInfo.name, tools, serverConfigsByName.get(serverInfo.name));
1129
1278
  // Apply custom descriptions from server configuration
1130
1279
  const serverConfig = await getServerDao().findById(serverInfo.name);
1131
1280
  const toolsWithCustomDescriptions = tools.map((tool) => {
@@ -1143,7 +1292,7 @@ export const handleListToolsRequest = async (_, extra) => {
1143
1292
  };
1144
1293
  };
1145
1294
  export const handleCallToolRequest = async (request, extra) => {
1146
- console.log(`Handling CallToolRequest for tool: ${JSON.stringify(request.params)}`);
1295
+ console.log('Handling CallToolRequest for tool', summarizeToolRequestForLogging(request.params));
1147
1296
  const startTime = Date.now();
1148
1297
  const activityLogger = getActivityLoggingService();
1149
1298
  // Get request context for activity logging
@@ -1197,7 +1346,11 @@ export const handleCallToolRequest = async (request, extra) => {
1197
1346
  const openApiClient = targetServerInfo.openApiClient;
1198
1347
  // Use toolArgs if it has properties, otherwise fallback to request.params.arguments
1199
1348
  const finalArgs = toolArgs && typeof toolArgs === 'object' ? toolArgs : {};
1200
- console.log(`Invoking OpenAPI tool '${toolName}' on server '${targetServerInfo.name}' with arguments: ${JSON.stringify(finalArgs)}`);
1349
+ console.log('Invoking OpenAPI tool', {
1350
+ toolName,
1351
+ serverName: targetServerInfo.name,
1352
+ arguments: summarizeArgumentsForLogging(finalArgs),
1353
+ });
1201
1354
  // Remove server prefix from tool name if present
1202
1355
  const separator = getNameSeparator();
1203
1356
  const prefix = `${targetServerInfo.name}${separator}`;
@@ -1229,7 +1382,11 @@ export const handleCallToolRequest = async (request, extra) => {
1229
1382
  }
1230
1383
  }
1231
1384
  const result = await openApiClient.callTool(cleanToolName, finalArgs, passthroughHeaders);
1232
- console.log(`OpenAPI tool invocation result: ${JSON.stringify(result)}`);
1385
+ console.log('OpenAPI tool invocation result', {
1386
+ serverName: targetServerInfo.name,
1387
+ toolName: cleanToolName,
1388
+ result: summarizeToolResultForLogging(result),
1389
+ });
1233
1390
  // Log successful activity
1234
1391
  const duration = Date.now() - startTime;
1235
1392
  await activityLogger.logToolCall({
@@ -1237,8 +1394,8 @@ export const handleCallToolRequest = async (request, extra) => {
1237
1394
  tool: cleanToolName,
1238
1395
  duration,
1239
1396
  status: 'success',
1240
- input: finalArgs,
1241
- output: result,
1397
+ input: summarizeArgumentsForLogging(finalArgs),
1398
+ output: summarizeToolResultForLogging(result),
1242
1399
  group,
1243
1400
  keyId,
1244
1401
  keyName,
@@ -1259,7 +1416,11 @@ export const handleCallToolRequest = async (request, extra) => {
1259
1416
  }
1260
1417
  // Use toolArgs if it has properties, otherwise fallback to request.params.arguments
1261
1418
  const finalArgs = toolArgs && typeof toolArgs === 'object' ? toolArgs : {};
1262
- console.log(`Invoking tool '${toolName}' on server '${targetServerInfo.name}' with arguments: ${JSON.stringify(finalArgs)}`);
1419
+ console.log('Invoking tool', {
1420
+ toolName,
1421
+ serverName: targetServerInfo.name,
1422
+ arguments: summarizeArgumentsForLogging(finalArgs),
1423
+ });
1263
1424
  const separator = getNameSeparator();
1264
1425
  const prefix = `${targetServerInfo.name}${separator}`;
1265
1426
  const cleanToolName = toolName.startsWith(prefix)
@@ -1269,7 +1430,11 @@ export const handleCallToolRequest = async (request, extra) => {
1269
1430
  name: cleanToolName,
1270
1431
  arguments: finalArgs,
1271
1432
  }, targetServerInfo.options || {});
1272
- console.log(`Tool invocation result: ${JSON.stringify(result)}`);
1433
+ console.log('Tool invocation result', {
1434
+ serverName: targetServerInfo.name,
1435
+ toolName: cleanToolName,
1436
+ result: summarizeToolResultForLogging(result),
1437
+ });
1273
1438
  // Log successful activity
1274
1439
  const duration = Date.now() - startTime;
1275
1440
  await activityLogger.logToolCall({
@@ -1277,14 +1442,12 @@ export const handleCallToolRequest = async (request, extra) => {
1277
1442
  tool: cleanToolName,
1278
1443
  duration,
1279
1444
  status: result.isError ? 'error' : 'success',
1280
- input: finalArgs,
1281
- output: result,
1445
+ input: summarizeArgumentsForLogging(finalArgs),
1446
+ output: summarizeToolResultForLogging(result),
1282
1447
  group,
1283
1448
  keyId,
1284
1449
  keyName,
1285
- errorMessage: result.isError
1286
- ? String(result.content?.[0]?.text || 'Unknown error')
1287
- : undefined,
1450
+ errorMessage: result.isError ? 'Tool returned error response' : undefined,
1288
1451
  });
1289
1452
  return result;
1290
1453
  }
@@ -1303,7 +1466,11 @@ export const handleCallToolRequest = async (request, extra) => {
1303
1466
  const cleanToolName = request.params.name.startsWith(prefix)
1304
1467
  ? request.params.name.substring(prefix.length)
1305
1468
  : request.params.name;
1306
- console.log(`Invoking OpenAPI tool '${cleanToolName}' on server '${serverInfo.name}' with arguments: ${JSON.stringify(request.params.arguments)}`);
1469
+ console.log('Invoking OpenAPI tool', {
1470
+ toolName: cleanToolName,
1471
+ serverName: serverInfo.name,
1472
+ arguments: summarizeArgumentsForLogging(request.params.arguments),
1473
+ });
1307
1474
  // Extract passthrough headers from extra or request context
1308
1475
  let passthroughHeaders;
1309
1476
  let requestHeaders = null;
@@ -1329,7 +1496,11 @@ export const handleCallToolRequest = async (request, extra) => {
1329
1496
  }
1330
1497
  }
1331
1498
  const result = await openApiClient.callTool(cleanToolName, request.params.arguments || {}, passthroughHeaders);
1332
- console.log(`OpenAPI tool invocation result: ${JSON.stringify(result)}`);
1499
+ console.log('OpenAPI tool invocation result', {
1500
+ serverName: serverInfo.name,
1501
+ toolName: cleanToolName,
1502
+ result: summarizeToolResultForLogging(result),
1503
+ });
1333
1504
  // Log successful activity
1334
1505
  const duration = Date.now() - startTime;
1335
1506
  await activityLogger.logToolCall({
@@ -1337,8 +1508,8 @@ export const handleCallToolRequest = async (request, extra) => {
1337
1508
  tool: cleanToolName,
1338
1509
  duration,
1339
1510
  status: 'success',
1340
- input: request.params.arguments,
1341
- output: result,
1511
+ input: summarizeArgumentsForLogging(request.params.arguments),
1512
+ output: summarizeToolResultForLogging(result),
1342
1513
  group,
1343
1514
  keyId,
1344
1515
  keyName,
@@ -1363,7 +1534,11 @@ export const handleCallToolRequest = async (request, extra) => {
1363
1534
  ? request.params.name.substring(prefix.length)
1364
1535
  : request.params.name;
1365
1536
  const result = await callToolWithReconnect(serverInfo, { ...request.params, name: cleanToolName }, serverInfo.options || {});
1366
- console.log(`Tool call result: ${JSON.stringify(result)}`);
1537
+ console.log('Tool call result', {
1538
+ serverName: serverInfo.name,
1539
+ toolName: cleanToolName,
1540
+ result: summarizeToolResultForLogging(result),
1541
+ });
1367
1542
  // Log successful activity
1368
1543
  const duration = Date.now() - startTime;
1369
1544
  await activityLogger.logToolCall({
@@ -1371,19 +1546,17 @@ export const handleCallToolRequest = async (request, extra) => {
1371
1546
  tool: cleanToolName,
1372
1547
  duration,
1373
1548
  status: result.isError ? 'error' : 'success',
1374
- input: request.params.arguments,
1375
- output: result,
1549
+ input: summarizeArgumentsForLogging(request.params.arguments),
1550
+ output: summarizeToolResultForLogging(result),
1376
1551
  group,
1377
1552
  keyId,
1378
1553
  keyName,
1379
- errorMessage: result.isError
1380
- ? String(result.content?.[0]?.text || 'Unknown error')
1381
- : undefined,
1554
+ errorMessage: result.isError ? 'Tool returned error response' : undefined,
1382
1555
  });
1383
1556
  return result;
1384
1557
  }
1385
1558
  catch (error) {
1386
- console.error(`Error handling CallToolRequest: ${error}`);
1559
+ console.error('Error handling CallToolRequest', summarizeErrorForLogging(error));
1387
1560
  // Log error activity
1388
1561
  const duration = Date.now() - startTime;
1389
1562
  const toolName = request.params?.name || 'unknown';
@@ -1393,17 +1566,18 @@ export const handleCallToolRequest = async (request, extra) => {
1393
1566
  tool: toolName,
1394
1567
  duration,
1395
1568
  status: 'error',
1396
- input: request.params?.arguments,
1569
+ input: summarizeArgumentsForLogging(request.params?.arguments),
1397
1570
  group,
1398
1571
  keyId,
1399
1572
  keyName,
1400
- errorMessage: String(error),
1573
+ errorMessage: formatErrorForLogging(error),
1401
1574
  });
1575
+ const safeErrorText = formatErrorForLogging(error);
1402
1576
  return {
1403
1577
  content: [
1404
1578
  {
1405
1579
  type: 'text',
1406
- text: `Error: ${error}`,
1580
+ text: `Error: ${safeErrorText}`,
1407
1581
  },
1408
1582
  ],
1409
1583
  isError: true,
@@ -1454,21 +1628,25 @@ export const handleGetPromptRequest = async (request, extra) => {
1454
1628
  arguments: promptArgs,
1455
1629
  };
1456
1630
  // Log the final promptParams
1457
- console.log(`Calling getPrompt with params: ${JSON.stringify(promptParams)}`);
1631
+ console.log('Calling getPrompt with params', {
1632
+ name: cleanPromptName || '',
1633
+ arguments: summarizeArgumentsForLogging(promptArgs),
1634
+ });
1458
1635
  const prompt = await server.client?.getPrompt(promptParams);
1459
- console.log(`Received prompt: ${JSON.stringify(prompt)}`);
1636
+ console.log('Received prompt', summarizePromptForLogging(prompt));
1460
1637
  if (!prompt) {
1461
1638
  throw new Error(`Prompt not found: ${cleanPromptName}`);
1462
1639
  }
1463
1640
  return prompt;
1464
1641
  }
1465
1642
  catch (error) {
1466
- console.error(`Error handling GetPromptRequest: ${error}`);
1643
+ console.error('Error handling GetPromptRequest', summarizeErrorForLogging(error));
1644
+ const safeErrorText = formatErrorForLogging(error);
1467
1645
  return {
1468
1646
  content: [
1469
1647
  {
1470
1648
  type: 'text',
1471
- text: `Error: ${error}`,
1649
+ text: `Error: ${safeErrorText}`,
1472
1650
  },
1473
1651
  ],
1474
1652
  isError: true,
@@ -1487,25 +1665,7 @@ export const handleListPromptsRequest = async (_, extra) => {
1487
1665
  description: bp.description,
1488
1666
  arguments: bp.arguments,
1489
1667
  }));
1490
- // Need to filter servers based on group asynchronously
1491
- const filteredServerInfos = [];
1492
- for (const serverInfo of getDataService().filterData(serverInfos)) {
1493
- if (serverInfo.enabled === false)
1494
- continue;
1495
- if (!group) {
1496
- filteredServerInfos.push(serverInfo);
1497
- continue;
1498
- }
1499
- const serversInGroup = await getServersInGroup(group);
1500
- if (!serversInGroup || serversInGroup.length === 0) {
1501
- if (serverInfo.name === group)
1502
- filteredServerInfos.push(serverInfo);
1503
- continue;
1504
- }
1505
- if (serversInGroup.includes(serverInfo.name)) {
1506
- filteredServerInfos.push(serverInfo);
1507
- }
1508
- }
1668
+ const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
1509
1669
  for (const serverInfo of filteredServerInfos) {
1510
1670
  if (serverInfo.prompts && serverInfo.prompts.length > 0) {
1511
1671
  // Filter prompts based on server configuration
@@ -1518,16 +1678,7 @@ export const handleListPromptsRequest = async (_, extra) => {
1518
1678
  return promptConfig?.enabled !== false;
1519
1679
  });
1520
1680
  }
1521
- // If this is a group request, apply group-level prompt filtering
1522
- if (group) {
1523
- const serverConfigInGroup = await getServerConfigInGroup(group, serverInfo.name);
1524
- if (serverConfigInGroup &&
1525
- serverConfigInGroup.tools !== 'all' &&
1526
- Array.isArray(serverConfigInGroup.tools)) {
1527
- // Note: Group config uses 'tools' field but we're filtering prompts here
1528
- // This might be a design decision to control access at the server level
1529
- }
1530
- }
1681
+ enabledPrompts = await filterPromptsByGroup(group, serverInfo.name, enabledPrompts, serverConfigsByName.get(serverInfo.name));
1531
1682
  // Apply custom descriptions from server configuration
1532
1683
  const promptsWithCustomDescriptions = enabledPrompts.map((prompt) => {
1533
1684
  const promptConfig = serverConfig?.prompts?.[prompt.name];
@@ -1555,25 +1706,7 @@ export const handleListResourcesRequest = async (_, extra) => {
1555
1706
  description: br.description,
1556
1707
  mimeType: br.mimeType,
1557
1708
  }));
1558
- // Add resources from connected MCP servers
1559
- const filteredServerInfos = [];
1560
- for (const serverInfo of getDataService().filterData(serverInfos)) {
1561
- if (serverInfo.enabled === false)
1562
- continue;
1563
- if (!group) {
1564
- filteredServerInfos.push(serverInfo);
1565
- continue;
1566
- }
1567
- const serversInGroup = await getServersInGroup(group);
1568
- if (!serversInGroup || serversInGroup.length === 0) {
1569
- if (serverInfo.name === group)
1570
- filteredServerInfos.push(serverInfo);
1571
- continue;
1572
- }
1573
- if (serversInGroup.includes(serverInfo.name)) {
1574
- filteredServerInfos.push(serverInfo);
1575
- }
1576
- }
1709
+ const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
1577
1710
  for (const serverInfo of filteredServerInfos) {
1578
1711
  if (serverInfo.resources && serverInfo.resources.length > 0) {
1579
1712
  // Filter resources based on server configuration
@@ -1585,6 +1718,7 @@ export const handleListResourcesRequest = async (_, extra) => {
1585
1718
  return resourceConfig?.enabled !== false;
1586
1719
  });
1587
1720
  }
1721
+ enabledResources = await filterResourcesByGroup(group, serverInfo.name, enabledResources, serverConfigsByName.get(serverInfo.name));
1588
1722
  // Apply custom descriptions from server configuration
1589
1723
  const resourcesWithCustomDescriptions = enabledResources.map((resource) => {
1590
1724
  const resourceConfig = serverConfig?.resources?.[resource.uri];
@@ -1600,6 +1734,24 @@ export const handleListResourcesRequest = async (_, extra) => {
1600
1734
  resources: allResources,
1601
1735
  };
1602
1736
  };
1737
+ export const handleListResourceTemplatesRequest = async (_, extra) => {
1738
+ const sessionId = extra.sessionId || '';
1739
+ const group = getGroup(sessionId);
1740
+ console.log(`Handling ListResourceTemplatesRequest for group: ${group}`);
1741
+ const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group, {
1742
+ requireClient: true,
1743
+ });
1744
+ const results = await Promise.allSettled(filteredServerInfos.map(async (serverInfo) => {
1745
+ if (!serverInfo.client?.listResourceTemplates) {
1746
+ return [];
1747
+ }
1748
+ const templates = await serverInfo.client.listResourceTemplates({}, serverInfo.options || {});
1749
+ return filterResourceTemplatesByGroup(group, serverInfo.name, templates.resourceTemplates || [], serverConfigsByName.get(serverInfo.name));
1750
+ }));
1751
+ return {
1752
+ resourceTemplates: results.flatMap((result) => result.status === 'fulfilled' ? result.value : []),
1753
+ };
1754
+ };
1603
1755
  export const handleReadResourceRequest = async (request, _extra) => {
1604
1756
  try {
1605
1757
  const { uri } = request.params;
@@ -1630,13 +1782,14 @@ export const handleReadResourceRequest = async (request, _extra) => {
1630
1782
  return result;
1631
1783
  }
1632
1784
  catch (error) {
1633
- console.error(`Error handling ReadResourceRequest: ${error}`);
1785
+ console.error('Error handling ReadResourceRequest', summarizeErrorForLogging(error));
1786
+ const safeErrorText = formatErrorForLogging(error);
1634
1787
  return {
1635
1788
  contents: [
1636
1789
  {
1637
1790
  uri: request.params?.uri || '',
1638
1791
  mimeType: 'text/plain',
1639
- text: `Error: ${error}`,
1792
+ text: `Error: ${safeErrorText}`,
1640
1793
  },
1641
1794
  ],
1642
1795
  };
@@ -1658,19 +1811,121 @@ export const createMcpServer = (name, version, group) => {
1658
1811
  server.setRequestHandler(GetPromptRequestSchema, handleGetPromptRequest);
1659
1812
  server.setRequestHandler(ListPromptsRequestSchema, handleListPromptsRequest);
1660
1813
  server.setRequestHandler(ListResourcesRequestSchema, handleListResourcesRequest);
1814
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, handleListResourceTemplatesRequest);
1661
1815
  server.setRequestHandler(ReadResourceRequestSchema, handleReadResourceRequest);
1662
1816
  return server;
1663
1817
  };
1818
+ const getFilteredServerInfosForGroup = async (group, options) => {
1819
+ const serverConfigs = group ? await getServerConfigsInGroup(group) : [];
1820
+ const serverNamesInGroup = new Set(serverConfigs.map((serverConfig) => serverConfig.name));
1821
+ const serverConfigsByName = new Map(serverConfigs.map((serverConfig) => [serverConfig.name, serverConfig]));
1822
+ const filteredServerInfos = [];
1823
+ for (const serverInfo of getDataService().filterData(serverInfos)) {
1824
+ if (serverInfo.enabled === false)
1825
+ continue;
1826
+ if (options?.requireClient && !serverInfo.client)
1827
+ continue;
1828
+ if (!group) {
1829
+ filteredServerInfos.push(serverInfo);
1830
+ continue;
1831
+ }
1832
+ if (serverNamesInGroup.size === 0) {
1833
+ if (serverInfo.name === group) {
1834
+ filteredServerInfos.push(serverInfo);
1835
+ }
1836
+ continue;
1837
+ }
1838
+ if (serverNamesInGroup.has(serverInfo.name)) {
1839
+ filteredServerInfos.push(serverInfo);
1840
+ }
1841
+ }
1842
+ return { filteredServerInfos, serverConfigsByName };
1843
+ };
1844
+ const getGroupServerConfig = async (group, serverName, serverConfig) => {
1845
+ if (!group) {
1846
+ return undefined;
1847
+ }
1848
+ return serverConfig ?? getServerConfigInGroup(group, serverName);
1849
+ };
1664
1850
  // Filter tools based on group configuration
1665
- async function filterToolsByGroup(group, serverName, tools) {
1851
+ async function filterToolsByGroup(group, serverName, tools, serverConfig) {
1666
1852
  if (group) {
1667
- const serverConfig = await getServerConfigInGroup(group, serverName);
1668
- if (serverConfig && serverConfig.tools !== 'all' && Array.isArray(serverConfig.tools)) {
1853
+ const resolvedServerConfig = await getGroupServerConfig(group, serverName, serverConfig);
1854
+ if (resolvedServerConfig &&
1855
+ resolvedServerConfig.tools !== 'all' &&
1856
+ Array.isArray(resolvedServerConfig.tools)) {
1669
1857
  // Filter tools based on group configuration
1670
- const allowedToolNames = serverConfig.tools.map((toolName) => `${serverName}${getNameSeparator()}${toolName}`);
1858
+ const allowedToolNames = resolvedServerConfig.tools.map((toolName) => `${serverName}${getNameSeparator()}${toolName}`);
1671
1859
  tools = tools.filter((tool) => allowedToolNames.includes(tool.name));
1672
1860
  }
1673
1861
  }
1674
1862
  return tools;
1675
1863
  }
1864
+ const normalizePromptNameForGroup = (serverName, promptName) => {
1865
+ const prefix = `${serverName}${getNameSeparator()}`;
1866
+ return promptName.startsWith(prefix) ? promptName.substring(prefix.length) : promptName;
1867
+ };
1868
+ export async function filterPromptsByGroup(group, serverName, prompts, serverConfig) {
1869
+ if (group) {
1870
+ const resolvedServerConfig = await getGroupServerConfig(group, serverName, serverConfig);
1871
+ if (resolvedServerConfig &&
1872
+ resolvedServerConfig.prompts !== 'all' &&
1873
+ Array.isArray(resolvedServerConfig.prompts)) {
1874
+ const allowedPromptNames = new Set(resolvedServerConfig.prompts);
1875
+ return prompts.filter((prompt) => allowedPromptNames.has(normalizePromptNameForGroup(serverName, prompt.name)));
1876
+ }
1877
+ }
1878
+ return prompts;
1879
+ }
1880
+ export async function filterResourcesByGroup(group, serverName, resources, serverConfig) {
1881
+ if (group) {
1882
+ const resolvedServerConfig = await getGroupServerConfig(group, serverName, serverConfig);
1883
+ if (resolvedServerConfig &&
1884
+ resolvedServerConfig.resources !== 'all' &&
1885
+ Array.isArray(resolvedServerConfig.resources)) {
1886
+ const allowedResources = new Set(resolvedServerConfig.resources);
1887
+ return resources.filter((resource) => allowedResources.has(resource.uri));
1888
+ }
1889
+ }
1890
+ return resources;
1891
+ }
1892
+ const resourceTemplateMatchesSelection = (uriTemplate, allowedResources) => {
1893
+ if (allowedResources.has(uriTemplate)) {
1894
+ return true;
1895
+ }
1896
+ const dynamicSegmentIndex = uriTemplate.search(/[{*]/);
1897
+ if (dynamicSegmentIndex === -1) {
1898
+ return false;
1899
+ }
1900
+ const staticPrefix = uriTemplate.slice(0, dynamicSegmentIndex);
1901
+ if (!staticPrefix) {
1902
+ return false;
1903
+ }
1904
+ for (const resourceUri of allowedResources) {
1905
+ if (resourceUri.startsWith(staticPrefix)) {
1906
+ return true;
1907
+ }
1908
+ }
1909
+ return false;
1910
+ };
1911
+ export async function filterResourceTemplatesByGroup(group, serverName, resourceTemplates, serverConfig) {
1912
+ if (group) {
1913
+ const resolvedServerConfig = await getGroupServerConfig(group, serverName, serverConfig);
1914
+ if (resolvedServerConfig &&
1915
+ resolvedServerConfig.resources !== 'all' &&
1916
+ Array.isArray(resolvedServerConfig.resources)) {
1917
+ if (resolvedServerConfig.resources.length === 0) {
1918
+ return [];
1919
+ }
1920
+ const allowedResources = new Set(resolvedServerConfig.resources);
1921
+ return resourceTemplates.filter((resourceTemplate) => {
1922
+ if (typeof resourceTemplate.uriTemplate !== 'string') {
1923
+ return false;
1924
+ }
1925
+ return resourceTemplateMatchesSelection(resourceTemplate.uriTemplate, allowedResources);
1926
+ });
1927
+ }
1928
+ }
1929
+ return resourceTemplates;
1930
+ }
1676
1931
  //# sourceMappingURL=mcpService.js.map