@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.
- package/README.fr.md +1 -0
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/dist/clients/openapi.js +3 -2
- package/dist/clients/openapi.js.map +1 -1
- package/dist/controllers/credentialBindingController.js +1 -1
- package/dist/controllers/credentialBindingController.js.map +1 -1
- package/dist/controllers/healthController.js +23 -1
- package/dist/controllers/healthController.js.map +1 -1
- package/dist/controllers/metricsController.js +9 -1
- package/dist/controllers/metricsController.js.map +1 -1
- package/dist/controllers/oauthDynamicRegistrationController.js +5 -5
- package/dist/controllers/oauthDynamicRegistrationController.js.map +1 -1
- package/dist/db/connection.js +6 -2
- package/dist/db/connection.js.map +1 -1
- package/dist/db/entities/OAuthState.js +43 -0
- package/dist/db/entities/OAuthState.js.map +1 -0
- package/dist/db/entities/index.js +3 -1
- package/dist/db/entities/index.js.map +1 -1
- package/dist/db/replicaSchema.js +40 -0
- package/dist/db/replicaSchema.js.map +1 -0
- package/dist/db/setup.js +25 -0
- package/dist/db/setup.js.map +1 -0
- package/dist/db/transactionContext.js +21 -0
- package/dist/db/transactionContext.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/middlewares/replicaConfig.js +134 -0
- package/dist/middlewares/replicaConfig.js.map +1 -0
- package/dist/middlewares/userContext.js +1 -1
- package/dist/middlewares/userContext.js.map +1 -1
- package/dist/models/OAuth.js +57 -7
- package/dist/models/OAuth.js.map +1 -1
- package/dist/routes/index.js +14 -1
- package/dist/routes/index.js.map +1 -1
- package/dist/server.js +13 -4
- package/dist/server.js.map +1 -1
- package/dist/services/credentialBindingService.js +5 -1
- package/dist/services/credentialBindingService.js.map +1 -1
- package/dist/services/mcpOAuthProvider.js +91 -5
- package/dist/services/mcpOAuthProvider.js.map +1 -1
- package/dist/services/mcpService.js +110 -7
- package/dist/services/mcpService.js.map +1 -1
- package/dist/services/metricsService.js +4 -0
- package/dist/services/metricsService.js.map +1 -1
- package/dist/services/oauthClientRegistration.js +21 -1
- package/dist/services/oauthClientRegistration.js.map +1 -1
- package/dist/services/oauthServerService.js +5 -6
- package/dist/services/oauthServerService.js.map +1 -1
- package/dist/services/oauthSettingsStore.js +21 -0
- package/dist/services/oauthSettingsStore.js.map +1 -1
- package/dist/services/personalOAuthService.js +40 -2
- package/dist/services/personalOAuthService.js.map +1 -1
- package/dist/services/replicaLock.js +30 -0
- package/dist/services/replicaLock.js.map +1 -0
- package/dist/services/replicaService.js +111 -0
- package/dist/services/replicaService.js.map +1 -0
- package/dist/services/sseService.js +3 -2
- package/dist/services/sseService.js.map +1 -1
- package/dist/utils/logger.js +7 -0
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/migration.js +3 -0
- package/dist/utils/migration.js.map +1 -1
- package/dist/utils/replicaLogContext.js +3 -0
- package/dist/utils/replicaLogContext.js.map +1 -0
- package/dist/utils/replicaMode.js +17 -0
- package/dist/utils/replicaMode.js.map +1 -0
- package/dist/utils/serverConfigPersistence.js +4 -0
- package/dist/utils/serverConfigPersistence.js.map +1 -1
- package/dist/utils/settingsSync.js +1 -0
- package/dist/utils/settingsSync.js.map +1 -1
- package/frontend/dist/assets/{ActivityPage-Bi-TjKez.js → ActivityPage-CSXEpl6y.js} +2 -2
- package/frontend/dist/assets/{ActivityPage-Bi-TjKez.js.map → ActivityPage-CSXEpl6y.js.map} +1 -1
- package/frontend/dist/assets/{CredentialsPage-C4Ucer3d.js → CredentialsPage-CPxOHQUZ.js} +2 -2
- package/frontend/dist/assets/{CredentialsPage-C4Ucer3d.js.map → CredentialsPage-CPxOHQUZ.js.map} +1 -1
- package/frontend/dist/assets/{Dashboard-C_2KkuoD.js → Dashboard-Da-YT_l_.js} +2 -2
- package/frontend/dist/assets/{Dashboard-C_2KkuoD.js.map → Dashboard-Da-YT_l_.js.map} +1 -1
- package/frontend/dist/assets/{EndpointCopy-DITVC2YQ.js → EndpointCopy-CZOjiFAH.js} +2 -2
- package/frontend/dist/assets/{EndpointCopy-DITVC2YQ.js.map → EndpointCopy-CZOjiFAH.js.map} +1 -1
- package/frontend/dist/assets/{GroupsPage-sE6YJyuK.js → GroupsPage-CSA5G9_H.js} +2 -2
- package/frontend/dist/assets/{GroupsPage-sE6YJyuK.js.map → GroupsPage-CSA5G9_H.js.map} +1 -1
- package/frontend/dist/assets/{LoginPage-CFsd_nY-.js → LoginPage-7RswUPtG.js} +2 -2
- package/frontend/dist/assets/{LoginPage-CFsd_nY-.js.map → LoginPage-7RswUPtG.js.map} +1 -1
- package/frontend/dist/assets/{LogsPage-BxZuGDuc.js → LogsPage-Dbef_68F.js} +2 -2
- package/frontend/dist/assets/{LogsPage-BxZuGDuc.js.map → LogsPage-Dbef_68F.js.map} +1 -1
- package/frontend/dist/assets/{MarketPage-CDBs4vMb.js → MarketPage-rSBdHZxo.js} +2 -2
- package/frontend/dist/assets/{MarketPage-CDBs4vMb.js.map → MarketPage-rSBdHZxo.js.map} +1 -1
- package/frontend/dist/assets/{OAuthConsentPage-BN_ucViM.js → OAuthConsentPage-BH9O7bk3.js} +2 -2
- package/frontend/dist/assets/{OAuthConsentPage-BN_ucViM.js.map → OAuthConsentPage-BH9O7bk3.js.map} +1 -1
- package/frontend/dist/assets/{PromptsPage-Bl3ySXGK.js → PromptsPage-B558MhVU.js} +2 -2
- package/frontend/dist/assets/{PromptsPage-Bl3ySXGK.js.map → PromptsPage-B558MhVU.js.map} +1 -1
- package/frontend/dist/assets/{ResourcesPage-BOzQkQ92.js → ResourcesPage-DQpy8B9L.js} +2 -2
- package/frontend/dist/assets/{ResourcesPage-BOzQkQ92.js.map → ResourcesPage-DQpy8B9L.js.map} +1 -1
- package/frontend/dist/assets/{ServersPage-DHYwQ5AM.js → ServersPage-DBcC8pG2.js} +4 -4
- package/frontend/dist/assets/ServersPage-DBcC8pG2.js.map +1 -0
- package/frontend/dist/assets/{SettingsPage-CSJ35jlM.js → SettingsPage-BcpeHFR3.js} +2 -2
- package/frontend/dist/assets/{SettingsPage-CSJ35jlM.js.map → SettingsPage-BcpeHFR3.js.map} +1 -1
- package/frontend/dist/assets/{StatusDot-NSjojGCy.js → StatusDot-CLxRrJ5l.js} +2 -2
- package/frontend/dist/assets/{StatusDot-NSjojGCy.js.map → StatusDot-CLxRrJ5l.js.map} +1 -1
- package/frontend/dist/assets/{ToggleGroup-BIZWpNzm.js → ToggleGroup-DuWJZU41.js} +2 -2
- package/frontend/dist/assets/{ToggleGroup-BIZWpNzm.js.map → ToggleGroup-DuWJZU41.js.map} +1 -1
- package/frontend/dist/assets/{UsersPage-DC51v2b8.js → UsersPage-B158Bofs.js} +2 -2
- package/frontend/dist/assets/{UsersPage-DC51v2b8.js.map → UsersPage-B158Bofs.js.map} +1 -1
- package/frontend/dist/assets/{clipboard-ik5YV95M.js → clipboard-BLr_gbf8.js} +2 -2
- package/frontend/dist/assets/{clipboard-ik5YV95M.js.map → clipboard-BLr_gbf8.js.map} +1 -1
- package/frontend/dist/assets/{costRevision-Bxov9_vH.js → costRevision-Dzec2k_b.js} +2 -2
- package/frontend/dist/assets/{costRevision-Bxov9_vH.js.map → costRevision-Dzec2k_b.js.map} +1 -1
- package/frontend/dist/assets/index-CGRjM_NC.js +3 -0
- package/frontend/dist/assets/index-CGRjM_NC.js.map +1 -0
- package/frontend/dist/assets/{resourceService-DMzCAZ0B.js → resourceService-BNH7uNar.js} +2 -2
- package/frontend/dist/assets/{resourceService-DMzCAZ0B.js.map → resourceService-BNH7uNar.js.map} +1 -1
- package/frontend/dist/assets/{useCostData-CcFSYViI.js → useCostData-B2d2eLCD.js} +2 -2
- package/frontend/dist/assets/{useCostData-CcFSYViI.js.map → useCostData-B2d2eLCD.js.map} +1 -1
- package/frontend/dist/assets/{variableDetection-CbYSa1ka.js → variableDetection-DRdE941j.js} +14 -14
- package/frontend/dist/assets/{variableDetection-CbYSa1ka.js.map → variableDetection-DRdE941j.js.map} +1 -1
- package/frontend/dist/index.html +1 -1
- package/package.json +3 -1
- package/frontend/dist/assets/ServersPage-DHYwQ5AM.js.map +0 -1
- package/frontend/dist/assets/index-6AyPtx0A.js +0 -3
- 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
|
|
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
|
-
|
|
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'), [
|