@samanhappy/mcphub 1.0.18 → 1.0.20

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 (71) hide show
  1. package/dist/clients/openapi.js +28 -0
  2. package/dist/clients/openapi.js.map +1 -1
  3. package/dist/controllers/serverController.js +22 -4
  4. package/dist/controllers/serverController.js.map +1 -1
  5. package/dist/controllers/userController.js +10 -1
  6. package/dist/controllers/userController.js.map +1 -1
  7. package/dist/dao/SystemConfigDaoDbImpl.js +3 -0
  8. package/dist/dao/SystemConfigDaoDbImpl.js.map +1 -1
  9. package/dist/db/entities/SystemConfig.js +4 -0
  10. package/dist/db/entities/SystemConfig.js.map +1 -1
  11. package/dist/services/activityLoggingService.js +36 -6
  12. package/dist/services/activityLoggingService.js.map +1 -1
  13. package/dist/services/mcpService.js +54 -17
  14. package/dist/services/mcpService.js.map +1 -1
  15. package/dist/services/requestContextService.js +15 -0
  16. package/dist/services/requestContextService.js.map +1 -1
  17. package/dist/services/sseService.js +5 -2
  18. package/dist/services/sseService.js.map +1 -1
  19. package/dist/services/userService.js +18 -0
  20. package/dist/services/userService.js.map +1 -1
  21. package/dist/utils/serialization.js +85 -12
  22. package/dist/utils/serialization.js.map +1 -1
  23. package/dist/utils/ssrf.js +155 -0
  24. package/dist/utils/ssrf.js.map +1 -0
  25. package/dist/utils/tokenTruncation.js +14 -0
  26. package/dist/utils/tokenTruncation.js.map +1 -1
  27. package/frontend/dist/assets/{ActivityPage-C36IBbnj.js → ActivityPage-CmDatV8h.js} +2 -2
  28. package/frontend/dist/assets/{ActivityPage-C36IBbnj.js.map → ActivityPage-CmDatV8h.js.map} +1 -1
  29. package/frontend/dist/assets/{Dashboard-BsUNNsqb.js → Dashboard-D4mJrnD5.js} +2 -2
  30. package/frontend/dist/assets/{Dashboard-BsUNNsqb.js.map → Dashboard-D4mJrnD5.js.map} +1 -1
  31. package/frontend/dist/assets/{EndpointCopy-eF3xFbaZ.js → EndpointCopy-YSgbA8oS.js} +2 -2
  32. package/frontend/dist/assets/{EndpointCopy-eF3xFbaZ.js.map → EndpointCopy-YSgbA8oS.js.map} +1 -1
  33. package/frontend/dist/assets/{GroupsPage-Dp-NU5wZ.js → GroupsPage-Dbsjqynx.js} +2 -2
  34. package/frontend/dist/assets/{GroupsPage-Dp-NU5wZ.js.map → GroupsPage-Dbsjqynx.js.map} +1 -1
  35. package/frontend/dist/assets/{LoginPage-9PIMlvGe.js → LoginPage-BJsFWBRX.js} +2 -2
  36. package/frontend/dist/assets/{LoginPage-9PIMlvGe.js.map → LoginPage-BJsFWBRX.js.map} +1 -1
  37. package/frontend/dist/assets/{LogsPage-C8WS43Dw.js → LogsPage-sKpr_d-I.js} +2 -2
  38. package/frontend/dist/assets/{LogsPage-C8WS43Dw.js.map → LogsPage-sKpr_d-I.js.map} +1 -1
  39. package/frontend/dist/assets/{MarketPage-Cyl5VvwI.js → MarketPage-Dl66sBVk.js} +2 -2
  40. package/frontend/dist/assets/{MarketPage-Cyl5VvwI.js.map → MarketPage-Dl66sBVk.js.map} +1 -1
  41. package/frontend/dist/assets/{PromptsPage-CqxKWAR9.js → PromptsPage-BIJz-1mn.js} +2 -2
  42. package/frontend/dist/assets/{PromptsPage-CqxKWAR9.js.map → PromptsPage-BIJz-1mn.js.map} +1 -1
  43. package/frontend/dist/assets/{ResourcesPage-Dq7Grza0.js → ResourcesPage-CVZLtp7s.js} +2 -2
  44. package/frontend/dist/assets/{ResourcesPage-Dq7Grza0.js.map → ResourcesPage-CVZLtp7s.js.map} +1 -1
  45. package/frontend/dist/assets/ServersPage-CJQ-LCYS.js +37 -0
  46. package/frontend/dist/assets/ServersPage-CJQ-LCYS.js.map +1 -0
  47. package/frontend/dist/assets/SettingsPage-CzbexjdA.js +12 -0
  48. package/frontend/dist/assets/SettingsPage-CzbexjdA.js.map +1 -0
  49. package/frontend/dist/assets/{StatusDot-DAStUyI3.js → StatusDot-9Gg6xqv-.js} +2 -2
  50. package/frontend/dist/assets/{StatusDot-DAStUyI3.js.map → StatusDot-9Gg6xqv-.js.map} +1 -1
  51. package/frontend/dist/assets/{ToggleGroup-CQvqGQLA.js → ToggleGroup-C6qyrY2I.js} +2 -2
  52. package/frontend/dist/assets/{ToggleGroup-CQvqGQLA.js.map → ToggleGroup-C6qyrY2I.js.map} +1 -1
  53. package/frontend/dist/assets/{UsersPage-Bsa2NGcJ.js → UsersPage-D4jwwoib.js} +2 -2
  54. package/frontend/dist/assets/{UsersPage-Bsa2NGcJ.js.map → UsersPage-D4jwwoib.js.map} +1 -1
  55. package/frontend/dist/assets/{contextCost-DrQqHXcP.js → contextCost-nh7hHroj.js} +2 -2
  56. package/frontend/dist/assets/{contextCost-DrQqHXcP.js.map → contextCost-nh7hHroj.js.map} +1 -1
  57. package/frontend/dist/assets/index-CGgjgCxq.js +3 -0
  58. package/frontend/dist/assets/index-CGgjgCxq.js.map +1 -0
  59. package/frontend/dist/assets/{resourceService-Bg941cnv.js → resourceService-BJBWJ8vG.js} +2 -2
  60. package/frontend/dist/assets/{resourceService-Bg941cnv.js.map → resourceService-BJBWJ8vG.js.map} +1 -1
  61. package/frontend/dist/assets/useSettingsData-ClaXqU1-.js +2 -0
  62. package/frontend/dist/assets/{useSettingsData-BwKohDXD.js.map → useSettingsData-ClaXqU1-.js.map} +1 -1
  63. package/frontend/dist/index.html +1 -1
  64. package/package.json +1 -1
  65. package/frontend/dist/assets/ServersPage-xt7QkwjZ.js +0 -37
  66. package/frontend/dist/assets/ServersPage-xt7QkwjZ.js.map +0 -1
  67. package/frontend/dist/assets/SettingsPage-Cn5Krvgb.js +0 -12
  68. package/frontend/dist/assets/SettingsPage-Cn5Krvgb.js.map +0 -1
  69. package/frontend/dist/assets/index-BsgjLwhT.js +0 -3
  70. package/frontend/dist/assets/index-BsgjLwhT.js.map +0 -1
  71. package/frontend/dist/assets/useSettingsData-BwKohDXD.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"SystemConfig.js","sourceRoot":"","sources":["../../../src/db/entities/SystemConfig.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE5F;;;GAGG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAY;CA0CxB,CAAA;AAxCC;IADC,aAAa,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;;wCACxD;AAGX;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;6CAClB;AAG9B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;6CAClB;AAG9B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;kDACb;AAGnC;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2DACJ;AAG5C;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CAChB;AAGhC;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mDACjC;AAGvB;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACpB;AAG5B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;iDACd;AAGlC;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0CACrB;AAG3B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0DACb;AAG/B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CAChB;AAGhC;IADC,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;8BACjD,IAAI;+CAAC;AAGhB;IADC,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;8BACjD,IAAI;+CAAC;AAzCL,YAAY;IADxB,MAAM,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;GACrB,YAAY,CA0CxB;;AAED,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"SystemConfig.js","sourceRoot":"","sources":["../../../src/db/entities/SystemConfig.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE5F;;;GAGG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAY;CA6CxB,CAAA;AA3CC;IADC,aAAa,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;;wCACxD;AAGX;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;6CAClB;AAG9B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;6CAClB;AAG9B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;kDACb;AAGnC;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2DACJ;AAG5C;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CAChB;AAGhC;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mDACjC;AAGvB;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACpB;AAG5B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;iDACd;AAGlC;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0CACrB;AAG3B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0DACb;AAG/B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CAChB;AAGhC;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;iDACd;AAGlC;IADC,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;8BACjD,IAAI;+CAAC;AAGhB;IADC,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;8BACjD,IAAI;+CAAC;AA5CL,YAAY;IADxB,MAAM,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;GACrB,YAAY,CA6CxB;;AAED,eAAe,YAAY,CAAC"}
@@ -1,5 +1,10 @@
1
1
  import { getActivityDao, isActivityLoggingEnabled } from '../dao/DaoFactory.js';
2
- import { safeStringify as safeJsonStringify, sanitizeStringForLogging, } from '../utils/serialization.js';
2
+ import { stringifyWithoutRedaction, sanitizeStringForLogging, } from '../utils/serialization.js';
3
+ import { getCachedSystemConfig } from '../utils/systemConfigCache.js';
4
+ const PAYLOAD_OMITTED = JSON.stringify({
5
+ _omitted: true,
6
+ _reason: 'activityLog.storeToolPayload is disabled',
7
+ });
3
8
  /**
4
9
  * Service for logging tool call activities
5
10
  * Only logs when in database mode
@@ -32,14 +37,15 @@ export class ActivityLoggingService {
32
37
  return;
33
38
  }
34
39
  try {
40
+ const storePayload = this.shouldStoreToolPayload();
35
41
  const activity = {
36
42
  timestamp: new Date(),
37
43
  server: params.server,
38
44
  tool: params.tool,
39
45
  duration: params.duration,
40
46
  status: params.status,
41
- input: params.input ? this.safeStringify(params.input) : undefined,
42
- output: params.output ? this.safeStringify(params.output) : undefined,
47
+ input: this.serializePayload(params.input, storePayload),
48
+ output: this.serializePayload(params.output, storePayload),
43
49
  group: params.group,
44
50
  username: params.username,
45
51
  keyId: params.keyId,
@@ -57,12 +63,36 @@ export class ActivityLoggingService {
57
63
  }
58
64
  }
59
65
  /**
60
- * Safely stringify an object, handling circular references
66
+ * Whether to persist full tool call payloads. Defaults to true; deployments
67
+ * that treat tool arguments as sensitive can opt out via system config.
68
+ */
69
+ shouldStoreToolPayload() {
70
+ return getCachedSystemConfig()?.activityLog?.storeToolPayload !== false;
71
+ }
72
+ /**
73
+ * Serialize a tool call payload for storage.
74
+ *
75
+ * Payloads are stored verbatim (no field-level redaction): heuristic
76
+ * redaction corrupts the audit record on false positives and gives false
77
+ * assurance on false negatives, so the decision is whether to store at all.
78
+ */
79
+ serializePayload(payload, storePayload) {
80
+ if (payload === undefined || payload === null) {
81
+ return undefined;
82
+ }
83
+ if (!storePayload) {
84
+ return PAYLOAD_OMITTED;
85
+ }
86
+ return this.safeStringify(payload);
87
+ }
88
+ /**
89
+ * Stringify an object without redaction, handling circular references and
90
+ * capping the stored size.
61
91
  */
62
92
  safeStringify(obj) {
63
93
  try {
64
94
  // Limit the size of the stringified data
65
- const str = safeJsonStringify(obj, 2);
95
+ const str = stringifyWithoutRedaction(obj, 2);
66
96
  // Limit to 100KB
67
97
  if (str.length > 100000) {
68
98
  return JSON.stringify({
@@ -73,7 +103,7 @@ export class ActivityLoggingService {
73
103
  }
74
104
  return str;
75
105
  }
76
- catch (error) {
106
+ catch (_error) {
77
107
  return JSON.stringify({
78
108
  _error: 'Failed to stringify data',
79
109
  _type: typeof obj,
@@ -1 +1 @@
1
- {"version":3,"file":"activityLoggingService.js","sourceRoot":"","sources":["../../src/services/activityLoggingService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"activityLoggingService.js","sourceRoot":"","sources":["../../src/services/activityLoggingService.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,8 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
10
10
  import { StreamableHTTPClientTransport, } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
11
11
  import { normalizeHeaders } from '@modelcontextprotocol/sdk/shared/transport.js';
12
12
  import { createFetchWithProxy, getProxyConfigFromEnv } from './proxy.js';
13
+ import { assertSafeUrl, createRedirectValidatingFetch } from '../utils/ssrf.js';
14
+ import { getUserDao } from '../dao/index.js';
13
15
  import { expandEnvVars, replaceEnvVars, getNameSeparator } from '../config/index.js';
14
16
  import config from '../config/index.js';
15
17
  import { getGroup } from './sseService.js';
@@ -698,11 +700,23 @@ export const createTransportFromConfig = async (name, conf) => {
698
700
  ...process.env,
699
701
  ...replaceEnvVars(conf.env || {}),
700
702
  };
703
+ // SSRF guard: block URL/streamable-http transports from reaching
704
+ // loopback / RFC1918 / link-local targets (e.g. cloud metadata service).
705
+ // Admin-owned servers may legitimately target internal services, so they
706
+ // skip the internal-IP blocklist. allowInternal also governs per-hop
707
+ // redirect validation in createRedirectValidatingFetch below.
708
+ const ownerUser = conf.owner
709
+ ? await getUserDao().findByUsername(conf.owner)
710
+ : null;
711
+ const allowInternal = !!ownerUser?.isAdmin;
712
+ if (conf.url) {
713
+ await assertSafeUrl(conf.url, { allowInternal });
714
+ }
701
715
  if (conf.type === 'streamable-http') {
702
716
  const options = {};
703
717
  const headers = conf.headers ? replaceEnvVars(conf.headers, env) : {};
704
718
  const baseFetch = createFetchWithProxy(getProxyConfigFromEnv(env));
705
- const requestAwareFetch = createRequestContextAwareFetch(baseFetch, conf.passthroughHeaders);
719
+ const requestAwareFetch = createRedirectValidatingFetch(createRequestContextAwareFetch(baseFetch, conf.passthroughHeaders), allowInternal);
706
720
  if (Object.keys(headers).length > 0) {
707
721
  options.requestInit = {
708
722
  headers,
@@ -722,7 +736,7 @@ export const createTransportFromConfig = async (name, conf) => {
722
736
  const options = {};
723
737
  const headers = conf.headers ? replaceEnvVars(conf.headers, env) : {};
724
738
  const baseFetch = createFetchWithProxy(getProxyConfigFromEnv(env));
725
- const requestAwareFetch = createRequestContextAwareFetch(baseFetch, conf.passthroughHeaders);
739
+ const requestAwareFetch = createRedirectValidatingFetch(createRequestContextAwareFetch(baseFetch, conf.passthroughHeaders), allowInternal);
726
740
  if (Object.keys(headers).length > 0) {
727
741
  options.eventSourceInit = {
728
742
  headers,
@@ -989,6 +1003,9 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
989
1003
  serverInfo.tools = mcpTools;
990
1004
  serverInfo.openApiClient = openApiClient;
991
1005
  console.log(`Successfully initialized OpenAPI server: ${name} with ${mcpTools.length} tools`);
1006
+ // Broadcast now that tools are loaded. OpenAPI servers expose tools
1007
+ // only (no prompts/resources). See the standard-path note above.
1008
+ broadcastToolListChanged();
992
1009
  // Save tools as vector embeddings for search
993
1010
  syncToolsAsVectorEmbeddings(name, mcpTools, {
994
1011
  reportProgress: options?.reportEmbeddingProgress === true && serverName === name,
@@ -1077,6 +1094,11 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
1077
1094
  updateServerToolsCache(serverInfo, tools.tools, {
1078
1095
  reportEmbeddingProgress: options?.reportEmbeddingProgress === true && serverName === name,
1079
1096
  });
1097
+ // Broadcast only after tools are actually loaded into the cache.
1098
+ // The connection completes asynchronously, so callers (e.g. enabling
1099
+ // a server) cannot broadcast a correct tool list themselves — doing so
1100
+ // would race ahead of this point and push a stale (empty) list.
1101
+ broadcastToolListChanged();
1080
1102
  })
1081
1103
  .catch((error) => {
1082
1104
  console.error('Failed to list tools for server', {
@@ -1092,6 +1114,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
1092
1114
  .then((prompts) => {
1093
1115
  console.log(`Successfully listed ${prompts.prompts.length} prompts for server: ${name}`);
1094
1116
  updateServerPromptsCache(serverInfo, prompts.prompts);
1117
+ broadcastPromptListChanged();
1095
1118
  })
1096
1119
  .catch((error) => {
1097
1120
  console.error('Failed to list prompts for server', {
@@ -1107,6 +1130,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
1107
1130
  .then((resources) => {
1108
1131
  console.log(`Successfully listed ${resources.resources.length} resources for server: ${name}`);
1109
1132
  updateServerResourcesCache(serverInfo, resources.resources);
1133
+ broadcastResourceListChanged();
1110
1134
  })
1111
1135
  .catch((error) => {
1112
1136
  console.error('Failed to list resources for server', {
@@ -1607,6 +1631,9 @@ const normalizeToolNameForServer = (serverName, toolName) => {
1607
1631
  return toolName.startsWith(prefix) ? toolName.substring(prefix.length) : toolName;
1608
1632
  };
1609
1633
  const getGroupLookupName = (group) => {
1634
+ if (group === '$smart') {
1635
+ return undefined;
1636
+ }
1610
1637
  if (group?.startsWith('$smart/')) {
1611
1638
  return group.substring(7) || undefined;
1612
1639
  }
@@ -1749,7 +1776,10 @@ export const handleCallToolRequest = async (request, extra) => {
1749
1776
  // Extract group and key info from request context (set by SSE/HTTP handlers)
1750
1777
  // Fallback to extra for backward compatibility (e.g., direct API calls)
1751
1778
  const group = requestContextService.getGroupContext() || extra?.group || getGroup(sessionId) || undefined;
1752
- const username = requestContextService.getUsernameContext() || extra?.username || undefined;
1779
+ const username = requestContextService.getUsernameContext() ||
1780
+ extra?.username ||
1781
+ (requestContextService.getKeyKindContext() === 'system' ? 'system' : undefined) ||
1782
+ undefined;
1753
1783
  let appsRouteContext = { enabled: false };
1754
1784
  const keyId = bearerKeyContext.keyId || extra?.keyId || undefined;
1755
1785
  const keyName = bearerKeyContext.keyName || extra?.keyName || undefined;
@@ -1798,7 +1828,7 @@ export const handleCallToolRequest = async (request, extra) => {
1798
1828
  else if (extra && extra.server) {
1799
1829
  targetServerInfo = getServerByName(extra.server);
1800
1830
  }
1801
- else if (group) {
1831
+ else if (getGroupLookupName(group)) {
1802
1832
  const groupTool = await resolveToolInGroup(group, toolName, appsRouteContext.enabled);
1803
1833
  if (groupTool) {
1804
1834
  targetServerInfo = groupTool.serverInfo;
@@ -1949,12 +1979,14 @@ export const handleCallToolRequest = async (request, extra) => {
1949
1979
  });
1950
1980
  }
1951
1981
  // Regular tool handling
1952
- const groupTool = !appsRouteContext.enabled && group
1953
- ? await resolveToolInGroup(group, request.params.name, appsRouteContext.enabled)
1982
+ const lookupGroup = getGroupLookupName(group);
1983
+ const groupTool = !appsRouteContext.enabled && lookupGroup
1984
+ ? await resolveToolInGroup(lookupGroup, request.params.name, appsRouteContext.enabled)
1954
1985
  : undefined;
1955
1986
  const serverInfo = appsRouteContext.enabled
1956
1987
  ? appsRouteContext.serverInfo
1957
- : (groupTool?.serverInfo ?? (group ? undefined : getServerByTool(request.params.name)));
1988
+ : (groupTool?.serverInfo ??
1989
+ (lookupGroup ? undefined : getServerByTool(request.params.name)));
1958
1990
  const routeToolName = groupTool?.toolName ?? request.params.name;
1959
1991
  const tool = groupTool?.tool ??
1960
1992
  (serverInfo
@@ -2144,11 +2176,12 @@ export const handleGetPromptRequest = async (request, extra) => {
2144
2176
  }
2145
2177
  let server;
2146
2178
  let promptNameForServer = name;
2179
+ const lookupGroup = getGroupLookupName(group);
2147
2180
  if (extra && extra.server) {
2148
2181
  server = getServerByName(extra.server);
2149
2182
  }
2150
- else if (group) {
2151
- const groupPrompt = await resolvePromptInGroup(group, name);
2183
+ else if (lookupGroup) {
2184
+ const groupPrompt = await resolvePromptInGroup(lookupGroup, name);
2152
2185
  if (groupPrompt) {
2153
2186
  server = groupPrompt.serverInfo;
2154
2187
  promptNameForServer = groupPrompt.promptName;
@@ -2202,6 +2235,7 @@ export const handleGetPromptRequest = async (request, extra) => {
2202
2235
  export const handleListPromptsRequest = async (_, extra) => {
2203
2236
  const sessionId = extra.sessionId || '';
2204
2237
  const group = getGroup(sessionId);
2238
+ const lookupGroup = getGroupLookupName(group);
2205
2239
  console.log(`Handling ListPromptsRequest for group: ${group}`);
2206
2240
  // Start with built-in prompts (only enabled ones)
2207
2241
  const builtinPrompts = await getBuiltinPromptDao().findEnabled();
@@ -2211,7 +2245,7 @@ export const handleListPromptsRequest = async (_, extra) => {
2211
2245
  description: bp.description,
2212
2246
  arguments: bp.arguments,
2213
2247
  }));
2214
- const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
2248
+ const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(lookupGroup);
2215
2249
  for (const serverInfo of filteredServerInfos) {
2216
2250
  if (serverInfo.prompts && serverInfo.prompts.length > 0) {
2217
2251
  const groupServerConfig = serverConfigsByName.get(serverInfo.name);
@@ -2225,7 +2259,7 @@ export const handleListPromptsRequest = async (_, extra) => {
2225
2259
  return promptConfig?.enabled !== false;
2226
2260
  });
2227
2261
  }
2228
- enabledPrompts = await filterPromptsByGroup(group, serverInfo.name, enabledPrompts, groupServerConfig);
2262
+ enabledPrompts = await filterPromptsByGroup(lookupGroup, serverInfo.name, enabledPrompts, groupServerConfig);
2229
2263
  // Apply custom descriptions from server configuration
2230
2264
  const promptsWithCustomDescriptions = enabledPrompts.map((prompt) => {
2231
2265
  const promptConfig = serverConfig?.prompts?.[prompt.name];
@@ -2245,6 +2279,7 @@ export const handleListPromptsRequest = async (_, extra) => {
2245
2279
  export const handleListResourcesRequest = async (_, extra) => {
2246
2280
  const sessionId = extra.sessionId || '';
2247
2281
  const group = getGroup(sessionId);
2282
+ const lookupGroup = getGroupLookupName(group);
2248
2283
  console.log(`Handling ListResourcesRequest for group: ${group}`);
2249
2284
  const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
2250
2285
  // Start with built-in resources (only enabled ones)
@@ -2255,7 +2290,7 @@ export const handleListResourcesRequest = async (_, extra) => {
2255
2290
  description: br.description,
2256
2291
  mimeType: br.mimeType,
2257
2292
  }));
2258
- const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
2293
+ const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(lookupGroup);
2259
2294
  for (const serverInfo of filteredServerInfos) {
2260
2295
  if (serverInfo.resources && serverInfo.resources.length > 0) {
2261
2296
  // Filter resources based on server configuration
@@ -2267,7 +2302,7 @@ export const handleListResourcesRequest = async (_, extra) => {
2267
2302
  return resourceConfig?.enabled !== false;
2268
2303
  });
2269
2304
  }
2270
- enabledResources = await filterResourcesByGroup(group, serverInfo.name, enabledResources, serverConfigsByName.get(serverInfo.name));
2305
+ enabledResources = await filterResourcesByGroup(lookupGroup, serverInfo.name, enabledResources, serverConfigsByName.get(serverInfo.name));
2271
2306
  // Apply custom descriptions from server configuration
2272
2307
  const resourcesWithCustomDescriptions = enabledResources.map((resource) => {
2273
2308
  const resourceConfig = serverConfig?.resources?.[resource.uri];
@@ -2289,9 +2324,10 @@ export const handleListResourcesRequest = async (_, extra) => {
2289
2324
  export const handleListResourceTemplatesRequest = async (_, extra) => {
2290
2325
  const sessionId = extra.sessionId || '';
2291
2326
  const group = getGroup(sessionId);
2327
+ const lookupGroup = getGroupLookupName(group);
2292
2328
  console.log(`Handling ListResourceTemplatesRequest for group: ${group}`);
2293
2329
  const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
2294
- const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group, {
2330
+ const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(lookupGroup, {
2295
2331
  requireClient: true,
2296
2332
  });
2297
2333
  const results = await Promise.allSettled(filteredServerInfos.map(async (serverInfo) => {
@@ -2299,7 +2335,7 @@ export const handleListResourceTemplatesRequest = async (_, extra) => {
2299
2335
  return [];
2300
2336
  }
2301
2337
  const templates = await serverInfo.client.listResourceTemplates({}, serverInfo.options || {});
2302
- const filteredTemplates = await filterResourceTemplatesByGroup(group, serverInfo.name, templates.resourceTemplates || [], serverConfigsByName.get(serverInfo.name));
2338
+ const filteredTemplates = await filterResourceTemplatesByGroup(lookupGroup, serverInfo.name, templates.resourceTemplates || [], serverConfigsByName.get(serverInfo.name));
2303
2339
  return appsRouteContext.enabled
2304
2340
  ? filteredTemplates
2305
2341
  : filteredTemplates.map((template) => stripMcpAppsMetadata(template));
@@ -2313,6 +2349,7 @@ export const handleReadResourceRequest = async (request, extra) => {
2313
2349
  const { uri } = request.params;
2314
2350
  const sessionId = extra.sessionId || '';
2315
2351
  const group = getGroup(sessionId);
2352
+ const lookupGroup = getGroupLookupName(group);
2316
2353
  const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
2317
2354
  // Check built-in resources first
2318
2355
  const builtinResource = await getBuiltinResourceDao().findByUri(uri);
@@ -2327,7 +2364,7 @@ export const handleReadResourceRequest = async (request, extra) => {
2327
2364
  ],
2328
2365
  };
2329
2366
  }
2330
- const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
2367
+ const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(lookupGroup);
2331
2368
  let server;
2332
2369
  for (const serverInfo of filteredServerInfos) {
2333
2370
  if (serverInfo.status !== 'connected') {
@@ -2338,7 +2375,7 @@ export const handleReadResourceRequest = async (request, extra) => {
2338
2375
  if (serverConfig?.resources) {
2339
2376
  enabledResources = enabledResources.filter((resource) => serverConfig.resources?.[resource.uri]?.enabled !== false);
2340
2377
  }
2341
- enabledResources = await filterResourcesByGroup(group, serverInfo.name, enabledResources, serverConfigsByName.get(serverInfo.name));
2378
+ enabledResources = await filterResourcesByGroup(lookupGroup, serverInfo.name, enabledResources, serverConfigsByName.get(serverInfo.name));
2342
2379
  if (enabledResources.some((resource) => resource.uri === uri)) {
2343
2380
  server = serverInfo;
2344
2381
  break;