@samanhappy/mcphub 1.1.15 → 1.2.0

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 (120) hide show
  1. package/README.fr.md +1 -0
  2. package/README.md +1 -0
  3. package/README.zh.md +1 -0
  4. package/dist/clients/openapi.js +3 -2
  5. package/dist/clients/openapi.js.map +1 -1
  6. package/dist/controllers/credentialBindingController.js +1 -1
  7. package/dist/controllers/credentialBindingController.js.map +1 -1
  8. package/dist/controllers/healthController.js +23 -1
  9. package/dist/controllers/healthController.js.map +1 -1
  10. package/dist/controllers/metricsController.js +9 -1
  11. package/dist/controllers/metricsController.js.map +1 -1
  12. package/dist/controllers/oauthDynamicRegistrationController.js +5 -5
  13. package/dist/controllers/oauthDynamicRegistrationController.js.map +1 -1
  14. package/dist/db/connection.js +6 -2
  15. package/dist/db/connection.js.map +1 -1
  16. package/dist/db/entities/OAuthState.js +43 -0
  17. package/dist/db/entities/OAuthState.js.map +1 -0
  18. package/dist/db/entities/index.js +3 -1
  19. package/dist/db/entities/index.js.map +1 -1
  20. package/dist/db/replicaSchema.js +40 -0
  21. package/dist/db/replicaSchema.js.map +1 -0
  22. package/dist/db/setup.js +25 -0
  23. package/dist/db/setup.js.map +1 -0
  24. package/dist/db/transactionContext.js +21 -0
  25. package/dist/db/transactionContext.js.map +1 -0
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/middlewares/replicaConfig.js +134 -0
  29. package/dist/middlewares/replicaConfig.js.map +1 -0
  30. package/dist/middlewares/userContext.js +1 -1
  31. package/dist/middlewares/userContext.js.map +1 -1
  32. package/dist/models/OAuth.js +57 -7
  33. package/dist/models/OAuth.js.map +1 -1
  34. package/dist/routes/index.js +14 -1
  35. package/dist/routes/index.js.map +1 -1
  36. package/dist/server.js +13 -4
  37. package/dist/server.js.map +1 -1
  38. package/dist/services/credentialBindingService.js +5 -1
  39. package/dist/services/credentialBindingService.js.map +1 -1
  40. package/dist/services/mcpOAuthProvider.js +91 -5
  41. package/dist/services/mcpOAuthProvider.js.map +1 -1
  42. package/dist/services/mcpService.js +110 -7
  43. package/dist/services/mcpService.js.map +1 -1
  44. package/dist/services/metricsService.js +4 -0
  45. package/dist/services/metricsService.js.map +1 -1
  46. package/dist/services/oauthClientRegistration.js +21 -1
  47. package/dist/services/oauthClientRegistration.js.map +1 -1
  48. package/dist/services/oauthServerService.js +5 -6
  49. package/dist/services/oauthServerService.js.map +1 -1
  50. package/dist/services/oauthSettingsStore.js +21 -0
  51. package/dist/services/oauthSettingsStore.js.map +1 -1
  52. package/dist/services/personalOAuthService.js +40 -2
  53. package/dist/services/personalOAuthService.js.map +1 -1
  54. package/dist/services/replicaLock.js +30 -0
  55. package/dist/services/replicaLock.js.map +1 -0
  56. package/dist/services/replicaService.js +111 -0
  57. package/dist/services/replicaService.js.map +1 -0
  58. package/dist/services/sseService.js +3 -2
  59. package/dist/services/sseService.js.map +1 -1
  60. package/dist/utils/logger.js +7 -0
  61. package/dist/utils/logger.js.map +1 -1
  62. package/dist/utils/migration.js +3 -0
  63. package/dist/utils/migration.js.map +1 -1
  64. package/dist/utils/replicaLogContext.js +3 -0
  65. package/dist/utils/replicaLogContext.js.map +1 -0
  66. package/dist/utils/replicaMode.js +17 -0
  67. package/dist/utils/replicaMode.js.map +1 -0
  68. package/dist/utils/serverConfigPersistence.js +4 -0
  69. package/dist/utils/serverConfigPersistence.js.map +1 -1
  70. package/dist/utils/settingsSync.js +1 -0
  71. package/dist/utils/settingsSync.js.map +1 -1
  72. package/frontend/dist/assets/{ActivityPage-Bi-TjKez.js → ActivityPage-CSXEpl6y.js} +2 -2
  73. package/frontend/dist/assets/{ActivityPage-Bi-TjKez.js.map → ActivityPage-CSXEpl6y.js.map} +1 -1
  74. package/frontend/dist/assets/{CredentialsPage-C4Ucer3d.js → CredentialsPage-CPxOHQUZ.js} +2 -2
  75. package/frontend/dist/assets/{CredentialsPage-C4Ucer3d.js.map → CredentialsPage-CPxOHQUZ.js.map} +1 -1
  76. package/frontend/dist/assets/{Dashboard-C_2KkuoD.js → Dashboard-Da-YT_l_.js} +2 -2
  77. package/frontend/dist/assets/{Dashboard-C_2KkuoD.js.map → Dashboard-Da-YT_l_.js.map} +1 -1
  78. package/frontend/dist/assets/{EndpointCopy-DITVC2YQ.js → EndpointCopy-CZOjiFAH.js} +2 -2
  79. package/frontend/dist/assets/{EndpointCopy-DITVC2YQ.js.map → EndpointCopy-CZOjiFAH.js.map} +1 -1
  80. package/frontend/dist/assets/{GroupsPage-sE6YJyuK.js → GroupsPage-CSA5G9_H.js} +2 -2
  81. package/frontend/dist/assets/{GroupsPage-sE6YJyuK.js.map → GroupsPage-CSA5G9_H.js.map} +1 -1
  82. package/frontend/dist/assets/{LoginPage-CFsd_nY-.js → LoginPage-7RswUPtG.js} +2 -2
  83. package/frontend/dist/assets/{LoginPage-CFsd_nY-.js.map → LoginPage-7RswUPtG.js.map} +1 -1
  84. package/frontend/dist/assets/{LogsPage-BxZuGDuc.js → LogsPage-Dbef_68F.js} +2 -2
  85. package/frontend/dist/assets/{LogsPage-BxZuGDuc.js.map → LogsPage-Dbef_68F.js.map} +1 -1
  86. package/frontend/dist/assets/{MarketPage-CDBs4vMb.js → MarketPage-rSBdHZxo.js} +2 -2
  87. package/frontend/dist/assets/{MarketPage-CDBs4vMb.js.map → MarketPage-rSBdHZxo.js.map} +1 -1
  88. package/frontend/dist/assets/{OAuthConsentPage-BN_ucViM.js → OAuthConsentPage-BH9O7bk3.js} +2 -2
  89. package/frontend/dist/assets/{OAuthConsentPage-BN_ucViM.js.map → OAuthConsentPage-BH9O7bk3.js.map} +1 -1
  90. package/frontend/dist/assets/{PromptsPage-Bl3ySXGK.js → PromptsPage-B558MhVU.js} +2 -2
  91. package/frontend/dist/assets/{PromptsPage-Bl3ySXGK.js.map → PromptsPage-B558MhVU.js.map} +1 -1
  92. package/frontend/dist/assets/{ResourcesPage-BOzQkQ92.js → ResourcesPage-DQpy8B9L.js} +2 -2
  93. package/frontend/dist/assets/{ResourcesPage-BOzQkQ92.js.map → ResourcesPage-DQpy8B9L.js.map} +1 -1
  94. package/frontend/dist/assets/{ServersPage-DHYwQ5AM.js → ServersPage-DBcC8pG2.js} +4 -4
  95. package/frontend/dist/assets/ServersPage-DBcC8pG2.js.map +1 -0
  96. package/frontend/dist/assets/{SettingsPage-CSJ35jlM.js → SettingsPage-BcpeHFR3.js} +2 -2
  97. package/frontend/dist/assets/{SettingsPage-CSJ35jlM.js.map → SettingsPage-BcpeHFR3.js.map} +1 -1
  98. package/frontend/dist/assets/{StatusDot-NSjojGCy.js → StatusDot-CLxRrJ5l.js} +2 -2
  99. package/frontend/dist/assets/{StatusDot-NSjojGCy.js.map → StatusDot-CLxRrJ5l.js.map} +1 -1
  100. package/frontend/dist/assets/{ToggleGroup-BIZWpNzm.js → ToggleGroup-DuWJZU41.js} +2 -2
  101. package/frontend/dist/assets/{ToggleGroup-BIZWpNzm.js.map → ToggleGroup-DuWJZU41.js.map} +1 -1
  102. package/frontend/dist/assets/{UsersPage-DC51v2b8.js → UsersPage-B158Bofs.js} +2 -2
  103. package/frontend/dist/assets/{UsersPage-DC51v2b8.js.map → UsersPage-B158Bofs.js.map} +1 -1
  104. package/frontend/dist/assets/{clipboard-ik5YV95M.js → clipboard-BLr_gbf8.js} +2 -2
  105. package/frontend/dist/assets/{clipboard-ik5YV95M.js.map → clipboard-BLr_gbf8.js.map} +1 -1
  106. package/frontend/dist/assets/{costRevision-Bxov9_vH.js → costRevision-Dzec2k_b.js} +2 -2
  107. package/frontend/dist/assets/{costRevision-Bxov9_vH.js.map → costRevision-Dzec2k_b.js.map} +1 -1
  108. package/frontend/dist/assets/index-CGRjM_NC.js +3 -0
  109. package/frontend/dist/assets/index-CGRjM_NC.js.map +1 -0
  110. package/frontend/dist/assets/{resourceService-DMzCAZ0B.js → resourceService-BNH7uNar.js} +2 -2
  111. package/frontend/dist/assets/{resourceService-DMzCAZ0B.js.map → resourceService-BNH7uNar.js.map} +1 -1
  112. package/frontend/dist/assets/{useCostData-CcFSYViI.js → useCostData-B2d2eLCD.js} +2 -2
  113. package/frontend/dist/assets/{useCostData-CcFSYViI.js.map → useCostData-B2d2eLCD.js.map} +1 -1
  114. package/frontend/dist/assets/{variableDetection-CbYSa1ka.js → variableDetection-DRdE941j.js} +14 -14
  115. package/frontend/dist/assets/{variableDetection-CbYSa1ka.js.map → variableDetection-DRdE941j.js.map} +1 -1
  116. package/frontend/dist/index.html +1 -1
  117. package/package.json +3 -1
  118. package/frontend/dist/assets/ServersPage-DHYwQ5AM.js.map +0 -1
  119. package/frontend/dist/assets/index-6AyPtx0A.js +0 -3
  120. package/frontend/dist/assets/index-6AyPtx0A.js.map +0 -1
@@ -1,4 +1,7 @@
1
1
  import { getToolPolicy } from './toolPolicyService.js';
2
+ import { isDeepStrictEqual } from 'node:util';
3
+ import { isReplicaMode } from '../utils/replicaMode.js';
4
+ import { withReplicaLock } from './replicaLock.js';
2
5
  import { projectGroupToolName, assertValidGroupToolNames } from '../utils/groupToolNames.js';
3
6
  import { isSafePassthroughHeader } from '../utils/passthroughHeaders.js';
4
7
  import { observeToolCall, setMetricsServer } from './metricsService.js';
@@ -45,7 +48,7 @@ import { removeServerToolEmbeddings, removeToolEmbeddings, saveToolsAsVectorEmbe
45
48
  import { OpenAPIClient } from '../clients/openapi.js';
46
49
  import { RequestContextService } from './requestContextService.js';
47
50
  import { getDataService } from './services.js';
48
- import { getBearerKeyDao, getServerDao, getGroupDao, getSystemConfigDao, getBuiltinPromptDao, getBuiltinResourceDao, } from '../dao/index.js';
51
+ import { getBearerKeyDao, getServerDao, getGroupDao, getSystemConfigDao, getBuiltinPromptDao, getBuiltinResourceDao, getCredentialBindingDao, } from '../dao/index.js';
49
52
  import { initializeAllOAuthClients } from './oauthService.js';
50
53
  import { createOAuthProvider } from './mcpOAuthProvider.js';
51
54
  import { initSmartRoutingService, getSmartRoutingTools, handleSearchToolsRequest, handleDescribeToolRequest, isSmartRoutingGroup, } from './smartRoutingService.js';
@@ -986,7 +989,7 @@ const createUpstreamMcpClient = (name, getServerInfo, serverConfig, downstream)
986
989
  });
987
990
  if (elicitation && downstream)
988
991
  installElicitationRelay(client, downstream);
989
- return client;
992
+ return protectReplicaOAuthClient(client, name, serverConfig);
990
993
  };
991
994
  // Normalize and infer server type for safe client display
992
995
  const normalizeServerType = (type) => {
@@ -1484,6 +1487,9 @@ const refreshCapabilitiesAfterFailedCall = async (info) => {
1484
1487
  };
1485
1488
  // Helper function to handle client.callTool with reconnection logic
1486
1489
  const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries = 1, isolated, extra) => {
1490
+ // A lost response does not prove a side effect failed; replica failover never replays it.
1491
+ if (isReplicaMode())
1492
+ maxRetries = 0;
1487
1493
  // Local, reassignable refs so a reconnect can swap in the new connection for
1488
1494
  // the next retry attempt. For isolated calls these track the per-session
1489
1495
  // client/transport; otherwise they mirror the shared serverInfo connection.
@@ -1678,7 +1684,7 @@ const setupServerKeepAlive = (serverInfo, serverConfig) => {
1678
1684
  };
1679
1685
  // Initialize MCP server clients
1680
1686
  export const initializeClientsFromSettings = async (isInit, serverName, options) => {
1681
- const allServers = await getServerDao().findAll();
1687
+ const allServers = options?.declarations ?? await getServerDao().findAll();
1682
1688
  const existingServerInfos = serverInfos;
1683
1689
  const nextServerInfos = [];
1684
1690
  const generation = ++initGeneration;
@@ -1705,6 +1711,13 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
1705
1711
  }
1706
1712
  // Expand environment variables in all configuration values
1707
1713
  const expandedConf = await expandServerConfig(conf);
1714
+ if (options?.preserveExisting) {
1715
+ const existing = existingServerInfos.find((info) => info.name === name);
1716
+ if (existing) {
1717
+ nextServerInfos.push(existing);
1718
+ continue;
1719
+ }
1720
+ }
1708
1721
  // Skip disabled servers
1709
1722
  if (expandedConf.enabled === false) {
1710
1723
  logger.log(`Skipping disabled server: ${name}`);
@@ -2190,6 +2203,24 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
2190
2203
  }
2191
2204
  return serverInfos;
2192
2205
  };
2206
+ export const reconcileReplicaServers = async (declarations) => {
2207
+ const expanded = new Map();
2208
+ for (const declaration of declarations) {
2209
+ const resolved = await expandServerConfig(declaration);
2210
+ validateCredentialTemplate(resolved);
2211
+ validateMaxBufferSize(resolved.options?.maxBufferSize);
2212
+ expanded.set(declaration.name, resolved);
2213
+ }
2214
+ const changed = serverInfos.filter((info) => !isDeepStrictEqual(info.config, expanded.get(info.name)) ||
2215
+ (info.enabled !== false) !== (expanded.get(info.name)?.enabled !== false));
2216
+ for (const info of changed)
2217
+ closeServer(info.name);
2218
+ serverInfos = serverInfos.filter((info) => !changed.includes(info));
2219
+ await initializeClientsFromSettings(false, undefined, { declarations, preserveExisting: true });
2220
+ broadcastToolListChanged();
2221
+ broadcastResourceListChanged();
2222
+ broadcastPromptListChanged();
2223
+ };
2193
2224
  // Register all MCP tools
2194
2225
  export const registerAllTools = async (isInit, serverName, options) => {
2195
2226
  await initializeClientsFromSettings(isInit, serverName, options);
@@ -4343,6 +4374,7 @@ const handleReadResourceRequestImpl = async (request, extra) => {
4343
4374
  };
4344
4375
  // Preserve credential-safe errors for personal clients in every discovery scope.
4345
4376
  const personalClientMethods = new Set([
4377
+ 'connect',
4346
4378
  'callTool',
4347
4379
  'getPrompt',
4348
4380
  'readResource',
@@ -4351,7 +4383,33 @@ const personalClientMethods = new Set([
4351
4383
  'listResources',
4352
4384
  'listResourceTemplates',
4353
4385
  ]);
4354
- const protectPersonalClient = (client, info, resolvedConfig) => new Proxy(client, {
4386
+ const protectReplicaOAuthClient = (client, name, definition) => {
4387
+ if (!isReplicaMode() || !definition.url || !definition.oauth)
4388
+ return client;
4389
+ return new Proxy(client, {
4390
+ get(target, property) {
4391
+ const value = Reflect.get(target, property);
4392
+ if (typeof value !== 'function')
4393
+ return value;
4394
+ if (!personalClientMethods.has(String(property)))
4395
+ return value.bind(target);
4396
+ return (...args) => {
4397
+ const personal = personalOAuthContexts.get(definition);
4398
+ const scope = JSON.stringify(['oauth', name, personal?.username || 'shared']);
4399
+ return withReplicaLock(scope, async () => {
4400
+ if (personal) {
4401
+ await personal.assertCurrent();
4402
+ const binding = await getCredentialBindingDao().get(name, personal.username);
4403
+ if (binding?.encryptedValues !== personal.version)
4404
+ throw new Error('Personal authorization changed; retry with current credentials');
4405
+ }
4406
+ return value.apply(target, args);
4407
+ });
4408
+ };
4409
+ },
4410
+ });
4411
+ };
4412
+ const protectPersonalClient = (client, info, resolvedConfig) => new Proxy(protectReplicaOAuthClient(client, info.name, resolvedConfig), {
4355
4413
  get(target, property) {
4356
4414
  const value = Reflect.get(target, property);
4357
4415
  if (typeof value !== 'function')
@@ -4423,10 +4481,10 @@ const createPrincipalRuntime = async (name, resolvedConfig, scope, toolsOnly = f
4423
4481
  });
4424
4482
  info.transport = transport;
4425
4483
  info.client = protectPersonalClient(client, info, resolvedConfig);
4426
- await client.connect(transport, resolvedConfig.options || {});
4484
+ await info.client.connect(transport, resolvedConfig.options || {});
4427
4485
  const capabilities = client.getServerCapabilities();
4428
4486
  if (capabilities?.tools) {
4429
- const { tools } = await client.listTools({}, resolvedConfig.options);
4487
+ const { tools } = await info.client.listTools({}, resolvedConfig.options);
4430
4488
  info.tools = preserveListFreshness(tools, tools.map((tool) => normalizeToolForCache(name, tool)));
4431
4489
  recordCapabilitySnapshot(info, tools);
4432
4490
  }
@@ -4894,7 +4952,52 @@ const withToolPolicy = async (handler, request, extra, invocation) => {
4894
4952
  return principalServerScope.run(eligible, () => handler(request, extra));
4895
4953
  };
4896
4954
  export const handleListToolsRequest = (request, extra) => withToolPolicy(listToolsWithPrincipal, request, extra, false);
4897
- export const handleCallToolRequest = (request, extra) => observeToolCall(() => withToolPolicy(callToolWithPrincipal, request, extra, true));
4955
+ // Polling refreshes connections; authorization always reads the current shared records.
4956
+ const withReplicaServerScope = async (action) => {
4957
+ if (!isReplicaMode())
4958
+ return action();
4959
+ const context = UserContextService.getInstance();
4960
+ const current = context.getCurrentUser();
4961
+ const externalPrincipal = current?.credentialEligible === false ||
4962
+ RequestContextService.getInstance().getHostedAuthContext();
4963
+ const user = current ? externalPrincipal ? current : await getUserDao().findByUsername(current.username) : null;
4964
+ if (current && !user)
4965
+ throw new Error('User no longer exists');
4966
+ const definitions = getDataService().filterData(await getServerDao().findAll(), user ?? undefined);
4967
+ const byName = new Map(definitions.map((server) => [server.name, server]));
4968
+ const visible = (principalServerScope.getStore() ?? serverInfos).flatMap((info) => {
4969
+ const live = byName.get(info.name);
4970
+ if (!live || live.enabled === false)
4971
+ return [];
4972
+ if (hasPersonalAuthentication(live) !== hasPersonalAuthentication(info.config))
4973
+ return [];
4974
+ // Project request authorization while keeping all lifecycle reads/writes on the runtime.
4975
+ return [
4976
+ new Proxy(info, {
4977
+ get(target, property) {
4978
+ switch (property) {
4979
+ case 'owner':
4980
+ case 'visibility':
4981
+ case 'sharedWithUsers':
4982
+ case 'sharedWithUserGroups':
4983
+ return live[property];
4984
+ case 'config':
4985
+ return {
4986
+ ...target.config,
4987
+ tools: live.tools,
4988
+ prompts: live.prompts,
4989
+ resources: live.resources,
4990
+ };
4991
+ default:
4992
+ return Reflect.get(target, property);
4993
+ }
4994
+ },
4995
+ }),
4996
+ ];
4997
+ });
4998
+ return context.runWithContext(() => principalServerScope.run(visible, action), user ?? null);
4999
+ };
5000
+ export const handleCallToolRequest = (request, extra) => observeToolCall(() => withReplicaServerScope(() => withToolPolicy(callToolWithPrincipal, request, extra, true)));
4898
5001
  export const handleGetPromptRequest = withPrincipalServers(withModernLists(handleGetPromptRequestImpl, ['prompts']), 'prompt');
4899
5002
  export const handleListPromptsRequest = withPrincipalServers(withModernLists(withCacheHints(handleListPromptsRequestImpl, false, 'prompts'), ['prompts']), 'list');
4900
5003
  export const handleListResourcesRequest = withPrincipalServers(withModernLists(withCacheHints(handleListResourcesRequestImpl, false, 'resources'), [