@samanhappy/mcphub 1.1.0 → 1.1.2

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 (125) hide show
  1. package/dist/betterAuth.js +5 -0
  2. package/dist/betterAuth.js.map +1 -1
  3. package/dist/clients/legacyMcpClient.js +1 -1
  4. package/dist/clients/legacyMcpClient.js.map +1 -1
  5. package/dist/clients/mcpSdkClient.js +28359 -0
  6. package/dist/clients/mcpSdkClient.js.map +7 -0
  7. package/dist/config/index.js +13 -7
  8. package/dist/config/index.js.map +1 -1
  9. package/dist/constants/smartRoutingDefaults.js +23 -0
  10. package/dist/constants/smartRoutingDefaults.js.map +1 -0
  11. package/dist/controllers/authController.js +5 -0
  12. package/dist/controllers/authController.js.map +1 -1
  13. package/dist/controllers/betterAuthController.js +3 -1
  14. package/dist/controllers/betterAuthController.js.map +1 -1
  15. package/dist/controllers/serverController.js +99 -18
  16. package/dist/controllers/serverController.js.map +1 -1
  17. package/dist/dao/ServerDao.js.map +1 -1
  18. package/dist/dao/ServerDaoDbImpl.js +13 -1
  19. package/dist/dao/ServerDaoDbImpl.js.map +1 -1
  20. package/dist/dao/UserDao.js +57 -34
  21. package/dist/dao/UserDao.js.map +1 -1
  22. package/dist/dao/base/JsonFileBaseDao.js +11 -6
  23. package/dist/dao/base/JsonFileBaseDao.js.map +1 -1
  24. package/dist/db/entities/Server.js +4 -0
  25. package/dist/db/entities/Server.js.map +1 -1
  26. package/dist/middlewares/auth.js +17 -0
  27. package/dist/middlewares/auth.js.map +1 -1
  28. package/dist/routes/index.js +3 -5
  29. package/dist/routes/index.js.map +1 -1
  30. package/dist/services/betterAuthConfig.js +18 -95
  31. package/dist/services/betterAuthConfig.js.map +1 -1
  32. package/dist/services/betterAuthSession.js +19 -3
  33. package/dist/services/betterAuthSession.js.map +1 -1
  34. package/dist/services/credentialBindingService.js +42 -0
  35. package/dist/services/credentialBindingService.js.map +1 -1
  36. package/dist/services/forwardAuthService.js +277 -0
  37. package/dist/services/forwardAuthService.js.map +1 -0
  38. package/dist/services/keepAliveService.js +1 -1
  39. package/dist/services/keepAliveService.js.map +1 -1
  40. package/dist/services/mcpOAuthProvider.js +24 -0
  41. package/dist/services/mcpOAuthProvider.js.map +1 -1
  42. package/dist/services/mcpService.js +51 -15
  43. package/dist/services/mcpService.js.map +1 -1
  44. package/dist/services/oauthSettingsStore.js +1 -1
  45. package/dist/services/oauthSettingsStore.js.map +1 -1
  46. package/dist/services/smartRoutingService.js +54 -52
  47. package/dist/services/smartRoutingService.js.map +1 -1
  48. package/dist/services/sseService.js +20 -0
  49. package/dist/services/sseService.js.map +1 -1
  50. package/dist/services/templateService.js +10 -9
  51. package/dist/services/templateService.js.map +1 -1
  52. package/dist/utils/migration.js +8 -6
  53. package/dist/utils/migration.js.map +1 -1
  54. package/dist/utils/serverAvailability.js +9 -0
  55. package/dist/utils/serverAvailability.js.map +1 -0
  56. package/dist/utils/serverConfigPersistence.js +12 -1
  57. package/dist/utils/serverConfigPersistence.js.map +1 -1
  58. package/dist/utils/serverConfigValidation.js +5 -1
  59. package/dist/utils/serverConfigValidation.js.map +1 -1
  60. package/dist/utils/settingResolvers.js +97 -0
  61. package/dist/utils/settingResolvers.js.map +1 -0
  62. package/dist/utils/settingsSync.js +198 -0
  63. package/dist/utils/settingsSync.js.map +1 -0
  64. package/dist/utils/smartRouting.js +36 -0
  65. package/dist/utils/smartRouting.js.map +1 -1
  66. package/dist/utils/ssrf.js +1 -1
  67. package/dist/utils/ssrf.js.map +1 -1
  68. package/dist/utils/stdioOptions.js +11 -0
  69. package/dist/utils/stdioOptions.js.map +1 -0
  70. package/frontend/dist/assets/{ActivityPage-Dp0qfFJ7.js → ActivityPage-DFLMe7o2.js} +2 -2
  71. package/frontend/dist/assets/{ActivityPage-Dp0qfFJ7.js.map → ActivityPage-DFLMe7o2.js.map} +1 -1
  72. package/frontend/dist/assets/{CopyClientConfigDialog-Bj0FEskD.js → CopyClientConfigDialog-DukyyXVp.js} +2 -2
  73. package/frontend/dist/assets/{CopyClientConfigDialog-Bj0FEskD.js.map → CopyClientConfigDialog-DukyyXVp.js.map} +1 -1
  74. package/frontend/dist/assets/{CredentialsPage-C-yB8aQU.js → CredentialsPage-BpBCEmEE.js} +2 -2
  75. package/frontend/dist/assets/{CredentialsPage-C-yB8aQU.js.map → CredentialsPage-BpBCEmEE.js.map} +1 -1
  76. package/frontend/dist/assets/{Dashboard-cbalSWB1.js → Dashboard-DjZkbAPt.js} +2 -2
  77. package/frontend/dist/assets/{Dashboard-cbalSWB1.js.map → Dashboard-DjZkbAPt.js.map} +1 -1
  78. package/frontend/dist/assets/{EndpointCopy-V8MoW7SG.js → EndpointCopy-Bv-_FPmR.js} +2 -2
  79. package/frontend/dist/assets/{EndpointCopy-V8MoW7SG.js.map → EndpointCopy-Bv-_FPmR.js.map} +1 -1
  80. package/frontend/dist/assets/{GroupsPage-DTXuiuqc.js → GroupsPage-B8BWRIWv.js} +2 -2
  81. package/frontend/dist/assets/{GroupsPage-DTXuiuqc.js.map → GroupsPage-B8BWRIWv.js.map} +1 -1
  82. package/frontend/dist/assets/LoginPage-OH73zcbT.js +2 -0
  83. package/frontend/dist/assets/LoginPage-OH73zcbT.js.map +1 -0
  84. package/frontend/dist/assets/{LogsPage-Bwv0shjb.js → LogsPage-B6P_uQvL.js} +2 -2
  85. package/frontend/dist/assets/{LogsPage-Bwv0shjb.js.map → LogsPage-B6P_uQvL.js.map} +1 -1
  86. package/frontend/dist/assets/{MarketPage-3jMYsH4H.js → MarketPage-CKjkgZpe.js} +2 -2
  87. package/frontend/dist/assets/{MarketPage-3jMYsH4H.js.map → MarketPage-CKjkgZpe.js.map} +1 -1
  88. package/frontend/dist/assets/{OAuthConsentPage-DI-IQ2gj.js → OAuthConsentPage-DjvtbLnm.js} +2 -2
  89. package/frontend/dist/assets/{OAuthConsentPage-DI-IQ2gj.js.map → OAuthConsentPage-DjvtbLnm.js.map} +1 -1
  90. package/frontend/dist/assets/{PromptsPage-BuVTifDV.js → PromptsPage-CCMb9OH2.js} +2 -2
  91. package/frontend/dist/assets/{PromptsPage-BuVTifDV.js.map → PromptsPage-CCMb9OH2.js.map} +1 -1
  92. package/frontend/dist/assets/{ResourcesPage-COSY92pD.js → ResourcesPage-BERaKn0d.js} +2 -2
  93. package/frontend/dist/assets/{ResourcesPage-COSY92pD.js.map → ResourcesPage-BERaKn0d.js.map} +1 -1
  94. package/frontend/dist/assets/{ServersPage-BlZvDfZE.js → ServersPage-BNsFiZez.js} +4 -4
  95. package/frontend/dist/assets/{ServersPage-BlZvDfZE.js.map → ServersPage-BNsFiZez.js.map} +1 -1
  96. package/frontend/dist/assets/SettingsPage-PiL7o9b9.js +12 -0
  97. package/frontend/dist/assets/SettingsPage-PiL7o9b9.js.map +1 -0
  98. package/frontend/dist/assets/{StatusDot-BHfWZAic.js → StatusDot-IWYCjv_t.js} +2 -2
  99. package/frontend/dist/assets/{StatusDot-BHfWZAic.js.map → StatusDot-IWYCjv_t.js.map} +1 -1
  100. package/frontend/dist/assets/{ToggleGroup-C33UH_Ob.js → ToggleGroup-B_NGGU8p.js} +2 -2
  101. package/frontend/dist/assets/{ToggleGroup-C33UH_Ob.js.map → ToggleGroup-B_NGGU8p.js.map} +1 -1
  102. package/frontend/dist/assets/{UsersPage-BXjVPFoF.js → UsersPage-D9QdH-ma.js} +2 -2
  103. package/frontend/dist/assets/{UsersPage-BXjVPFoF.js.map → UsersPage-D9QdH-ma.js.map} +1 -1
  104. package/frontend/dist/assets/{clipboard-r7a9vj44.js → clipboard-CFeeSZ4D.js} +2 -2
  105. package/frontend/dist/assets/{clipboard-r7a9vj44.js.map → clipboard-CFeeSZ4D.js.map} +1 -1
  106. package/frontend/dist/assets/index--NhF_yyy.css +1 -0
  107. package/frontend/dist/assets/index-RTKBtgGw.js +3 -0
  108. package/frontend/dist/assets/index-RTKBtgGw.js.map +1 -0
  109. package/frontend/dist/assets/{resourceService-5J2x7MrD.js → resourceService-BoJqsssR.js} +2 -2
  110. package/frontend/dist/assets/{resourceService-5J2x7MrD.js.map → resourceService-BoJqsssR.js.map} +1 -1
  111. package/frontend/dist/assets/{useCostData-CjmWUnd9.js → useCostData-Dld-MisV.js} +2 -2
  112. package/frontend/dist/assets/{useCostData-CjmWUnd9.js.map → useCostData-Dld-MisV.js.map} +1 -1
  113. package/frontend/dist/assets/variableDetection-C1abuc-p.js +16 -0
  114. package/frontend/dist/assets/variableDetection-C1abuc-p.js.map +1 -0
  115. package/frontend/dist/index.html +2 -2
  116. package/package.json +5 -3
  117. package/frontend/dist/assets/LoginPage-kfbiuyWa.js +0 -2
  118. package/frontend/dist/assets/LoginPage-kfbiuyWa.js.map +0 -1
  119. package/frontend/dist/assets/SettingsPage-V0TrX4k8.js +0 -12
  120. package/frontend/dist/assets/SettingsPage-V0TrX4k8.js.map +0 -1
  121. package/frontend/dist/assets/index-D-J2WUmj.css +0 -1
  122. package/frontend/dist/assets/index-pouDG4uY.js +0 -3
  123. package/frontend/dist/assets/index-pouDG4uY.js.map +0 -1
  124. package/frontend/dist/assets/variableDetection-CAv2g3qV.js +0 -16
  125. package/frontend/dist/assets/variableDetection-CAv2g3qV.js.map +0 -1
@@ -5,6 +5,7 @@ import { LegacyMcpClient } from '../clients/legacyMcpClient.js';
5
5
  import { LEGACY_PROTOCOL_VERSIONS } from '../utils/mcpProtocol.js';
6
6
  import { AsyncLocalStorage } from 'node:async_hooks';
7
7
  import { canAccessGroupRoute } from '../utils/groupAccess.js';
8
+ import { canServeToolRequests } from '../utils/serverAvailability.js';
8
9
  import { PrincipalRuntimeService } from './principalRuntimeService.js';
9
10
  import { UserContextService } from './userContextService.js';
10
11
  import { deleteCredentialBindings, missingCredentialError } from './credentialBindingService.js';
@@ -15,7 +16,7 @@ import fs from 'fs';
15
16
  import treeKill from 'tree-kill';
16
17
  import { isProcessTreeKillAvailable } from '../utils/processTree.js';
17
18
  import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
18
- import { SSEClientTransport, StreamableHTTPClientTransport, } from '@modelcontextprotocol/client';
19
+ import { SSEClientTransport, StreamableHTTPClientTransport, } from '../clients/mcpSdkClient.js';
19
20
  import { Server } from '@modelcontextprotocol/server';
20
21
  import { createFetchWithProxy, getProxyConfigFromEnv } from './proxy.js';
21
22
  import { assertSafeUrl, createRedirectValidatingFetch } from '../utils/ssrf.js';
@@ -24,6 +25,7 @@ import { ResilientJsonSchemaValidator } from '../utils/jsonSchemaValidator.js';
24
25
  import { getUserDao } from '../dao/index.js';
25
26
  import { expandEnvVars, replaceEnvVars, getNameSeparator } from '../config/index.js';
26
27
  import config from '../config/index.js';
28
+ import { validateMaxBufferSize } from '../utils/stdioOptions.js';
27
29
  import { validateServerName } from '../utils/serverNameValidation.js';
28
30
  import { getGroup } from './sseService.js';
29
31
  import { getServerConfigInGroup, normalizeGroupServers } from './groupService.js';
@@ -1293,6 +1295,7 @@ export const createTransportFromConfig = async (name, conf) => {
1293
1295
  args: finalArgs,
1294
1296
  env: env,
1295
1297
  stderr: 'pipe',
1298
+ maxBufferSize: validateMaxBufferSize(conf.options?.maxBufferSize),
1296
1299
  });
1297
1300
  if (transport.stderr && hasCredentialTemplate(conf)) {
1298
1301
  transport.stderr.on('data', () => undefined); // Personal upstream diagnostics can contain raw credentials.
@@ -1390,6 +1393,7 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
1390
1393
  serverInfo.client = newClient;
1391
1394
  serverInfo.transport = newTransport;
1392
1395
  serverInfo.status = 'connected';
1396
+ setupServerKeepAlive(serverInfo, server);
1393
1397
  }
1394
1398
  // Point the local refs at the new connection for the next attempt.
1395
1399
  client = newClient;
@@ -2333,6 +2337,7 @@ const classifyUnavailableReason = (qualifiedName) => {
2333
2337
  };
2334
2338
  // Add new server
2335
2339
  export const addServer = async (name, config) => {
2340
+ validateMaxBufferSize(config.options?.maxBufferSize);
2336
2341
  validateCredentialTemplate(config);
2337
2342
  const server = { name, ...config };
2338
2343
  const result = await getServerDao().create(server);
@@ -2367,6 +2372,7 @@ export const removeServer = async (name) => {
2367
2372
  // Add or update server (supports overriding existing servers for MCPB)
2368
2373
  export const addOrUpdateServer = async (name, config, allowOverride = false) => {
2369
2374
  try {
2375
+ validateMaxBufferSize(config.options?.maxBufferSize);
2370
2376
  validateCredentialTemplate(config);
2371
2377
  const exists = await getServerDao().exists(name);
2372
2378
  if (exists && !allowOverride) {
@@ -2664,12 +2670,19 @@ const primeOnDemandServers = (gate) => {
2664
2670
  const run = gate ? gate.run : (start) => start();
2665
2671
  return Promise.allSettled(targets.map(async (si) => {
2666
2672
  try {
2667
- await run(() => ensureServerReady(si));
2673
+ await run(() => ensureServerReady(si),
2674
+ // A reload may replace this runtime or finish its discovery while it
2675
+ // waits for a slot. Neither needs another child from the old queue.
2676
+ () => serverInfos.includes(si) && si.enabled !== false && si.tools.length === 0);
2668
2677
  // Sleep the child unless a tool call is using it; keep the cached tool list.
2669
2678
  shutdownOnDemandServer(si);
2670
2679
  logger.log('On-demand server tool cache primed');
2671
2680
  }
2672
2681
  catch (error) {
2682
+ if (error instanceof QueuedConnectCancelledError) {
2683
+ logger.log('Cancelled queued on-demand discovery', { serverName: si.name });
2684
+ return;
2685
+ }
2673
2686
  logger.warn('Failed to prime on-demand server', {
2674
2687
  error: summarizeErrorForLogging(error),
2675
2688
  });
@@ -2837,9 +2850,21 @@ const getMcpAppsRouteContext = async (sessionId, group) => {
2837
2850
  : sessionId
2838
2851
  ? servers[sessionId]?.getClientCapabilities()
2839
2852
  : undefined;
2840
- if (isSmartRoutingGroup(group) || !hasMcpAppsCapability(clientCapabilities)) {
2853
+ if (!hasMcpAppsCapability(clientCapabilities)) {
2841
2854
  return { enabled: false };
2842
2855
  }
2856
+ if (isSmartRoutingGroup(group)) {
2857
+ // Only pinned tools are listed under their own descriptor, so only the servers
2858
+ // that pin something can own a widget here. Always the multi-server shape:
2859
+ // names stay qualified and calls resolve through the group, so a pin never
2860
+ // widens what the group allows.
2861
+ const pinned = await resolvePinnedSmartRoutingServers(group);
2862
+ // An idle on-demand server still advertises its cached tools, so it keeps its
2863
+ // widget link: dropping it here would hand the host a descriptor without one
2864
+ // that the next call, which wakes the server, can no longer correct.
2865
+ const eligible = (pinned?.pinnedServerInfos ?? []).filter(canServeToolRequests);
2866
+ return eligible.length > 0 ? { enabled: true, serverInfos: eligible } : { enabled: false };
2867
+ }
2843
2868
  const { filteredServerInfos } = await getFilteredServerInfosForGroup(group);
2844
2869
  const connectedServerInfos = filteredServerInfos.filter((serverInfo) => serverInfo.status === 'connected' && !!serverInfo.client);
2845
2870
  if (connectedServerInfos.length === 0) {
@@ -2938,11 +2963,8 @@ const resolveToolInGroup = async (group, toolName) => {
2938
2963
  serverConfigsByName.size <= 1 &&
2939
2964
  filteredServerInfos.length === 1;
2940
2965
  for (const serverInfo of filteredServerInfos) {
2941
- // A disconnected on-demand server may still have a cached tool list from a
2942
- // previous wake-up; allow it through so it can be selected and woken by the
2943
- // caller. Other disconnected servers cannot serve the request. See #1029.
2944
- if ((serverInfo.status !== 'connected' && !serverInfo.config?.startOnDemand) ||
2945
- serverInfo.enabled === false) {
2966
+ // An idle on-demand server is selected here and woken by the caller (#1029)
2967
+ if (!canServeToolRequests(serverInfo)) {
2946
2968
  continue;
2947
2969
  }
2948
2970
  const serverConfig = serverConfigsByName.get(serverInfo.name);
@@ -3038,13 +3060,12 @@ const listGroupTools = async (group, filteredServerInfos, serverConfigsByName, a
3038
3060
  // Names the call handler always routes to a meta-tool on a Smart Routing session,
3039
3061
  // whichever of them the current mode lists (see handleCallToolRequestImpl)
3040
3062
  const SMART_ROUTING_META_TOOL_NAMES = new Set(['search_tools', 'describe_tool', 'call_tool']);
3041
- // Tools a $smart/<group> lists next to its meta-tools: each member's pinnedTools,
3042
- // narrowed to its tools selection and then run through the same filtering and
3043
- // projection as a direct group listing.
3044
- const getPinnedSmartRoutingTools = async (group) => {
3063
+ // Each member's pinnedTools, narrowed to its tools selection. Empty when the
3064
+ // route is not a $smart/<group> or the group pins nothing.
3065
+ const resolvePinnedSmartRoutingServers = async (group) => {
3045
3066
  const lookupGroup = getGroupLookupName(group);
3046
3067
  if (!lookupGroup) {
3047
- return [];
3068
+ return undefined;
3048
3069
  }
3049
3070
  const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(lookupGroup);
3050
3071
  const pinnedConfigsByName = new Map();
@@ -3056,9 +3077,23 @@ const getPinnedSmartRoutingTools = async (group) => {
3056
3077
  }
3057
3078
  }
3058
3079
  if (pinnedConfigsByName.size === 0) {
3080
+ return undefined;
3081
+ }
3082
+ return {
3083
+ lookupGroup,
3084
+ pinnedServerInfos: filteredServerInfos.filter((serverInfo) => pinnedConfigsByName.has(serverInfo.name)),
3085
+ pinnedConfigsByName,
3086
+ };
3087
+ };
3088
+ // Tools a $smart/<group> lists next to its meta-tools: each member's pinnedTools,
3089
+ // narrowed to its tools selection and then run through the same filtering and
3090
+ // projection as a direct group listing.
3091
+ const getPinnedSmartRoutingTools = async (group, appsRouteContext = { enabled: false }) => {
3092
+ const pinned = await resolvePinnedSmartRoutingServers(group);
3093
+ if (!pinned) {
3059
3094
  return [];
3060
3095
  }
3061
- return listGroupTools(lookupGroup, filteredServerInfos.filter((serverInfo) => pinnedConfigsByName.has(serverInfo.name)), pinnedConfigsByName, { enabled: false });
3096
+ return listGroupTools(pinned.lookupGroup, pinned.pinnedServerInfos, pinned.pinnedConfigsByName, appsRouteContext);
3062
3097
  };
3063
3098
  const handleListToolsRequestImpl = async (_, extra) => {
3064
3099
  const sessionId = extra.sessionId || '';
@@ -3070,10 +3105,11 @@ const handleListToolsRequestImpl = async (_, extra) => {
3070
3105
  const smartRoutingTools = await getSmartRoutingTools(group);
3071
3106
  let pinnedTools = [];
3072
3107
  try {
3108
+ const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
3073
3109
  // A pin named like a meta-tool (e.g. alias "describe" + separator "_" +
3074
3110
  // tool "tool") would be intercepted by it on call, even in a mode that
3075
3111
  // does not list that meta-tool, so it is not listed at all
3076
- pinnedTools = (await getPinnedSmartRoutingTools(group)).filter((tool) => !SMART_ROUTING_META_TOOL_NAMES.has(tool.name));
3112
+ pinnedTools = (await getPinnedSmartRoutingTools(group, appsRouteContext)).filter((tool) => !SMART_ROUTING_META_TOOL_NAMES.has(tool.name));
3077
3113
  }
3078
3114
  catch (error) {
3079
3115
  // Pins are an optimisation: failing to resolve them must not cost the meta-tools