@samanhappy/mcphub 1.0.38 → 1.0.40
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/dist/betterAuth.js +23 -0
- package/dist/betterAuth.js.map +1 -1
- package/dist/controllers/smartRoutingController.js +408 -0
- package/dist/controllers/smartRoutingController.js.map +1 -0
- package/dist/dao/CredentialBindingDao.js +5 -0
- package/dist/dao/CredentialBindingDao.js.map +1 -1
- package/dist/dao/CredentialBindingDaoDbImpl.js +7 -0
- package/dist/dao/CredentialBindingDaoDbImpl.js.map +1 -1
- package/dist/db/connection.js +38 -11
- package/dist/db/connection.js.map +1 -1
- package/dist/db/groupSchema.js +2 -2
- package/dist/db/groupSchema.js.map +1 -1
- package/dist/db/repositories/ActivityRepository.js +2 -2
- package/dist/db/repositories/ActivityRepository.js.map +1 -1
- package/dist/db/repositories/BaseRepository.js +4 -1
- package/dist/db/repositories/BaseRepository.js.map +1 -1
- package/dist/db/repositories/BearerKeyRepository.js +3 -1
- package/dist/db/repositories/BearerKeyRepository.js.map +1 -1
- package/dist/db/repositories/BuiltinPromptRepository.js +2 -2
- package/dist/db/repositories/BuiltinPromptRepository.js.map +1 -1
- package/dist/db/repositories/BuiltinResourceRepository.js +2 -2
- package/dist/db/repositories/BuiltinResourceRepository.js.map +1 -1
- package/dist/db/repositories/GroupRepository.js +2 -2
- package/dist/db/repositories/GroupRepository.js.map +1 -1
- package/dist/db/repositories/OAuthClientRepository.js +2 -2
- package/dist/db/repositories/OAuthClientRepository.js.map +1 -1
- package/dist/db/repositories/OAuthTokenRepository.js +3 -1
- package/dist/db/repositories/OAuthTokenRepository.js.map +1 -1
- package/dist/db/repositories/ServerRepository.js +2 -2
- package/dist/db/repositories/ServerRepository.js.map +1 -1
- package/dist/db/repositories/UserConfigRepository.js +2 -2
- package/dist/db/repositories/UserConfigRepository.js.map +1 -1
- package/dist/db/repositories/UserRepository.js +3 -1
- package/dist/db/repositories/UserRepository.js.map +1 -1
- package/dist/db/repositories/VectorEmbeddingRepository.js +35 -3
- package/dist/db/repositories/VectorEmbeddingRepository.js.map +1 -1
- package/dist/middlewares/auth.js +1 -1
- package/dist/middlewares/auth.js.map +1 -1
- package/dist/middlewares/index.js +8 -1
- package/dist/middlewares/index.js.map +1 -1
- package/dist/routes/index.js +4 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/services/mcpOAuthProvider.js +7 -0
- package/dist/services/mcpOAuthProvider.js.map +1 -1
- package/dist/services/mcpService.js +178 -20
- package/dist/services/mcpService.js.map +1 -1
- package/dist/services/principalRuntimeService.js +1 -3
- package/dist/services/principalRuntimeService.js.map +1 -1
- package/dist/services/vectorSearchService.js +29 -10
- package/dist/services/vectorSearchService.js.map +1 -1
- package/dist/utils/cacheUtils.js +345 -1
- package/dist/utils/cacheUtils.js.map +1 -1
- package/dist/utils/packageUpdate.js +242 -0
- package/dist/utils/packageUpdate.js.map +1 -0
- package/dist/utils/serialization.js +14 -2
- package/dist/utils/serialization.js.map +1 -1
- package/dist/utils/serverConfigPersistence.js +18 -0
- package/dist/utils/serverConfigPersistence.js.map +1 -1
- package/dist/utils/upstreamError.js +60 -0
- package/dist/utils/upstreamError.js.map +1 -0
- package/dist/utils/withTimeout.js +25 -0
- package/dist/utils/withTimeout.js.map +1 -0
- package/frontend/dist/assets/{ActivityPage-BZNq-eGE.js → ActivityPage-B5DanW8q.js} +2 -2
- package/frontend/dist/assets/{ActivityPage-BZNq-eGE.js.map → ActivityPage-B5DanW8q.js.map} +1 -1
- package/frontend/dist/assets/CopyClientConfigDialog-C6U6_eyy.js +4 -0
- package/frontend/dist/assets/CopyClientConfigDialog-C6U6_eyy.js.map +1 -0
- package/frontend/dist/assets/{CredentialsPage-7kyDJLsX.js → CredentialsPage-CXK8Xkao.js} +2 -2
- package/frontend/dist/assets/{CredentialsPage-7kyDJLsX.js.map → CredentialsPage-CXK8Xkao.js.map} +1 -1
- package/frontend/dist/assets/Dashboard-LIC6BqIA.js +2 -0
- package/frontend/dist/assets/{Dashboard-CElNx820.js.map → Dashboard-LIC6BqIA.js.map} +1 -1
- package/frontend/dist/assets/EndpointCopy-CeZbEsF2.js +2 -0
- package/frontend/dist/assets/EndpointCopy-CeZbEsF2.js.map +1 -0
- package/frontend/dist/assets/GroupsPage-DwVaRIap.js +33 -0
- package/frontend/dist/assets/GroupsPage-DwVaRIap.js.map +1 -0
- package/frontend/dist/assets/{LoginPage-D23s7oM0.js → LoginPage-CPqSMwWO.js} +2 -2
- package/frontend/dist/assets/{LoginPage-D23s7oM0.js.map → LoginPage-CPqSMwWO.js.map} +1 -1
- package/frontend/dist/assets/{LogsPage-BuQMkmV-.js → LogsPage-eCHGem9T.js} +2 -2
- package/frontend/dist/assets/{LogsPage-BuQMkmV-.js.map → LogsPage-eCHGem9T.js.map} +1 -1
- package/frontend/dist/assets/{MarketPage-g9vEYzIq.js → MarketPage-RqD2-rxq.js} +2 -2
- package/frontend/dist/assets/{MarketPage-g9vEYzIq.js.map → MarketPage-RqD2-rxq.js.map} +1 -1
- package/frontend/dist/assets/{OAuthConsentPage-BiX1lpJR.js → OAuthConsentPage-DJITYpCM.js} +2 -2
- package/frontend/dist/assets/{OAuthConsentPage-BiX1lpJR.js.map → OAuthConsentPage-DJITYpCM.js.map} +1 -1
- package/frontend/dist/assets/{PromptsPage-g8SRFY6s.js → PromptsPage-DQ02iDOJ.js} +2 -2
- package/frontend/dist/assets/{PromptsPage-g8SRFY6s.js.map → PromptsPage-DQ02iDOJ.js.map} +1 -1
- package/frontend/dist/assets/{ResourcesPage-Ddhxi9Sf.js → ResourcesPage-mUU0DVus.js} +2 -2
- package/frontend/dist/assets/{ResourcesPage-Ddhxi9Sf.js.map → ResourcesPage-mUU0DVus.js.map} +1 -1
- package/frontend/dist/assets/ServersPage-DsS8j6tl.js +39 -0
- package/frontend/dist/assets/ServersPage-DsS8j6tl.js.map +1 -0
- package/frontend/dist/assets/SettingsPage-CIeF1sDE.js +12 -0
- package/frontend/dist/assets/SettingsPage-CIeF1sDE.js.map +1 -0
- package/frontend/dist/assets/{StatusDot-CdzMqjTi.js → StatusDot-Bcv2BwIL.js} +2 -2
- package/frontend/dist/assets/{StatusDot-CdzMqjTi.js.map → StatusDot-Bcv2BwIL.js.map} +1 -1
- package/frontend/dist/assets/{ToggleGroup-CJd8X_31.js → ToggleGroup-C_7sDUlr.js} +2 -2
- package/frontend/dist/assets/{ToggleGroup-CJd8X_31.js.map → ToggleGroup-C_7sDUlr.js.map} +1 -1
- package/frontend/dist/assets/{UsersPage-CkP_x9Rt.js → UsersPage-ufaG2-Ag.js} +2 -2
- package/frontend/dist/assets/{UsersPage-CkP_x9Rt.js.map → UsersPage-ufaG2-Ag.js.map} +1 -1
- package/frontend/dist/assets/clipboard-BukcCPLt.js +2 -0
- package/frontend/dist/assets/clipboard-BukcCPLt.js.map +1 -0
- package/frontend/dist/assets/{icons-vendor-VW-UwEhw.js → icons-vendor-CNEaJWqW.js} +79 -59
- package/frontend/dist/assets/icons-vendor-CNEaJWqW.js.map +1 -0
- package/frontend/dist/assets/index-C8aAOWR7.js +3 -0
- package/frontend/dist/assets/{index-Bu4XuAH4.js.map → index-C8aAOWR7.js.map} +1 -1
- package/frontend/dist/assets/index-D4Vy78bV.css +1 -0
- package/frontend/dist/assets/{resourceService-w-cZ31FD.js → resourceService-CN5cMjQ0.js} +2 -2
- package/frontend/dist/assets/{resourceService-w-cZ31FD.js.map → resourceService-CN5cMjQ0.js.map} +1 -1
- package/frontend/dist/assets/{useCostData-C72NC0PQ.js → useCostData-B9XzWVsV.js} +2 -2
- package/frontend/dist/assets/{useCostData-C72NC0PQ.js.map → useCostData-B9XzWVsV.js.map} +1 -1
- package/frontend/dist/assets/useSettingsData-J_hwKZA6.js +2 -0
- package/frontend/dist/assets/{useSettingsData-JTEMu74D.js.map → useSettingsData-J_hwKZA6.js.map} +1 -1
- package/frontend/dist/assets/variableDetection-Dxw0g2ON.js +16 -0
- package/frontend/dist/assets/variableDetection-Dxw0g2ON.js.map +1 -0
- package/frontend/dist/index.html +3 -3
- package/package.json +2 -1
- package/frontend/dist/assets/Dashboard-CElNx820.js +0 -2
- package/frontend/dist/assets/EndpointCopy-8jW5Xtka.js +0 -2
- package/frontend/dist/assets/EndpointCopy-8jW5Xtka.js.map +0 -1
- package/frontend/dist/assets/GroupsPage-dUkvX_iB.js +0 -33
- package/frontend/dist/assets/GroupsPage-dUkvX_iB.js.map +0 -1
- package/frontend/dist/assets/ServersPage-DAmKvrPV.js +0 -39
- package/frontend/dist/assets/ServersPage-DAmKvrPV.js.map +0 -1
- package/frontend/dist/assets/SettingsPage-DZXjQZXS.js +0 -12
- package/frontend/dist/assets/SettingsPage-DZXjQZXS.js.map +0 -1
- package/frontend/dist/assets/icons-vendor-VW-UwEhw.js.map +0 -1
- package/frontend/dist/assets/index-B3OqAhGz.css +0 -1
- package/frontend/dist/assets/index-Bu4XuAH4.js +0 -3
- package/frontend/dist/assets/toolDescription-Dw3sa28K.js +0 -2
- package/frontend/dist/assets/toolDescription-Dw3sa28K.js.map +0 -1
- package/frontend/dist/assets/useSettingsData-JTEMu74D.js +0 -2
- package/frontend/dist/assets/variableDetection-DfhNyFy-.js +0 -16
- package/frontend/dist/assets/variableDetection-DfhNyFy-.js.map +0 -1
|
@@ -40,9 +40,11 @@ import { assertHostedToolAllowed, filterHostedTools, reserveHostedToolCall, sett
|
|
|
40
40
|
import { formatErrorForLogging, sanitizeStringForLogging, summarizeErrorForLogging, } from '../utils/serialization.js';
|
|
41
41
|
import { addStdioErrorContext, observeStdioStderr } from '../utils/stdioDiagnostics.js';
|
|
42
42
|
import { MCP_APPS_CAPABILITIES, filterModelVisibleTools, hasMcpAppsCapability, isAppOnlyTool, stripMcpAppsMetadata, } from '../utils/mcpApps.js';
|
|
43
|
-
import { supportsCacheRefresh, injectRefreshFlag, clearRunnerCache } from '../utils/cacheUtils.js';
|
|
43
|
+
import { supportsCacheRefresh, injectRefreshFlag, clearRunnerCache, resolveRunnerPackageVersion } from '../utils/cacheUtils.js';
|
|
44
|
+
import { checkPackageUpdate } from '../utils/packageUpdate.js';
|
|
44
45
|
const servers = {};
|
|
45
46
|
import { setupClientKeepAlive } from './keepAliveService.js';
|
|
47
|
+
import { createUpstreamRequestError, isUpstreamConnectionFailure, UpstreamRequestError, } from '../utils/upstreamError.js';
|
|
46
48
|
import { logger } from '../utils/logger.js';
|
|
47
49
|
/**
|
|
48
50
|
* Check if proxychains4 is available on the system (Linux/macOS only).
|
|
@@ -439,7 +441,12 @@ export const syncToolEmbedding = async (serverName, toolName) => {
|
|
|
439
441
|
logger.warn(`Server not found: ${serverName}`);
|
|
440
442
|
return;
|
|
441
443
|
}
|
|
442
|
-
|
|
444
|
+
// The runtime cache names tools with the server prefix, but a direct API call
|
|
445
|
+
// (or a client that only knows the upstream name) may pass the bare name.
|
|
446
|
+
// Match both, mirroring the dual lookup used for the disabled-tool check, so a
|
|
447
|
+
// description update always reaches the right tool (see #1198).
|
|
448
|
+
const tool = serverInfo.tools.find((t) => t.name === toolName) ??
|
|
449
|
+
serverInfo.tools.find((t) => normalizeToolNameForServer(serverInfo.name, t.name) === toolName);
|
|
443
450
|
if (!tool) {
|
|
444
451
|
logger.warn(`Tool not found: ${toolName} on server: ${serverName}`);
|
|
445
452
|
return;
|
|
@@ -448,7 +455,7 @@ export const syncToolEmbedding = async (serverName, toolName) => {
|
|
|
448
455
|
return;
|
|
449
456
|
}
|
|
450
457
|
// Save tool as vector embedding for search
|
|
451
|
-
syncToolsAsVectorEmbeddings(serverName, [tool]).catch((error) => {
|
|
458
|
+
syncToolsAsVectorEmbeddings(serverName, [tool], { partial: true }).catch((error) => {
|
|
452
459
|
logger.warn(`[EMBED_SYNC_ERROR] Failed to sync embedding for tool "${toolName}" on server "${serverName}"`);
|
|
453
460
|
logger.error('Error syncing single tool embedding', { serverName, toolName, error });
|
|
454
461
|
});
|
|
@@ -486,9 +493,38 @@ const buildToolWithDescriptionMetadata = (tool, toolConfig) => {
|
|
|
486
493
|
enabled: toolConfig?.enabled !== false,
|
|
487
494
|
};
|
|
488
495
|
};
|
|
496
|
+
// Apply any per-tool description override before the tool text is embedded for
|
|
497
|
+
// Smart Routing search. Deliberately narrower than buildToolWithDescriptionMetadata:
|
|
498
|
+
// only `description` (plus the hasDescriptionOverride marker used by the
|
|
499
|
+
// tool-set hash) is touched, so serverInfo.tools (built by
|
|
500
|
+
// normalizeToolForCache/updateServerToolsCache) still never carries `enabled` --
|
|
501
|
+
// preserving the existing invariant that execution gating reads the persisted
|
|
502
|
+
// config directly, not this projection. Without this step, overrides only
|
|
503
|
+
// affected client-facing output (tools/list, describe_tool, dashboard) while
|
|
504
|
+
// search_tools embeddings kept ranking against the raw upstream description (#1198).
|
|
505
|
+
const applyDescriptionOverridesForEmbedding = async (serverName, tools) => {
|
|
506
|
+
const serverConfig = await getServerDao().findById(serverName);
|
|
507
|
+
if (!serverConfig?.tools) {
|
|
508
|
+
return tools;
|
|
509
|
+
}
|
|
510
|
+
return tools.map((tool) => {
|
|
511
|
+
const bareToolName = normalizeToolNameForServer(serverName, tool.name);
|
|
512
|
+
const toolConfig = serverConfig.tools?.[tool.name] ?? serverConfig.tools?.[bareToolName];
|
|
513
|
+
return hasDescriptionOverride(toolConfig)
|
|
514
|
+
? {
|
|
515
|
+
...tool,
|
|
516
|
+
description: resolveDescriptionOverride(tool.description, toolConfig),
|
|
517
|
+
hasDescriptionOverride: true,
|
|
518
|
+
}
|
|
519
|
+
: tool;
|
|
520
|
+
});
|
|
521
|
+
};
|
|
489
522
|
const syncToolsAsVectorEmbeddings = async (serverName, tools, options) => {
|
|
490
|
-
const
|
|
523
|
+
const toolsWithOverrides = await applyDescriptionOverridesForEmbedding(serverName, tools);
|
|
524
|
+
const modelVisibleTools = filterModelVisibleTools(toolsWithOverrides);
|
|
491
525
|
if (modelVisibleTools.length === 0) {
|
|
526
|
+
if (options?.partial)
|
|
527
|
+
return;
|
|
492
528
|
await removeServerToolEmbeddings(serverName);
|
|
493
529
|
return;
|
|
494
530
|
}
|
|
@@ -754,6 +790,7 @@ export const connected = () => {
|
|
|
754
790
|
};
|
|
755
791
|
// Global cleanup function to close all connections
|
|
756
792
|
export const cleanupAllServers = () => {
|
|
793
|
+
stopPackageUpdateSweep();
|
|
757
794
|
principalRuntimes.invalidate();
|
|
758
795
|
for (const serverInfo of serverInfos) {
|
|
759
796
|
try {
|
|
@@ -1625,6 +1662,8 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
|
|
|
1625
1662
|
const serverVersion = client.getServerVersion?.();
|
|
1626
1663
|
info.version = serverVersion?.version;
|
|
1627
1664
|
info.instructions = client.getInstructions?.();
|
|
1665
|
+
// Record the resolved npx/uvx package version backing this server.
|
|
1666
|
+
applyResolvedPackageVersion(info, expandedConf);
|
|
1628
1667
|
const capabilities = client.getServerCapabilities();
|
|
1629
1668
|
logger.log('Server capabilities', JSON.stringify(capabilities));
|
|
1630
1669
|
let dataError = null;
|
|
@@ -1839,15 +1878,15 @@ export const getServersInfo = async (page, limit, user) => {
|
|
|
1839
1878
|
catch (error) {
|
|
1840
1879
|
return {
|
|
1841
1880
|
...info,
|
|
1842
|
-
error: error instanceof CredentialBindingError
|
|
1881
|
+
error: error instanceof CredentialBindingError || error instanceof UpstreamRequestError
|
|
1843
1882
|
? error.message
|
|
1844
|
-
: 'Unable to
|
|
1883
|
+
: 'Unable to prepare upstream server',
|
|
1845
1884
|
};
|
|
1846
1885
|
}
|
|
1847
1886
|
}));
|
|
1848
1887
|
const infos = presentedInfos
|
|
1849
1888
|
.filter((info) => requestedServerNames.has(info.name)) // Only include requested servers
|
|
1850
|
-
.map(({ name, version, instructions, owner, visibility, status, tools, prompts, resources, createTime, error, oauth, }) => {
|
|
1889
|
+
.map(({ name, version, packageVersion, latestVersion, updateAvailable, instructions, owner, visibility, status, tools, prompts, resources, createTime, error, oauth, }) => {
|
|
1851
1890
|
const serverConfig = allServers.find((server) => server.name === name);
|
|
1852
1891
|
const enabled = serverConfig ? serverConfig.enabled !== false : true;
|
|
1853
1892
|
const resolvedType = inferServerType(serverConfig);
|
|
@@ -1891,6 +1930,12 @@ export const getServersInfo = async (page, limit, user) => {
|
|
|
1891
1930
|
resources: resourcesWithEnabled,
|
|
1892
1931
|
createTime,
|
|
1893
1932
|
enabled,
|
|
1933
|
+
// Resolved npx/uvx package version + registry update hint (#1166).
|
|
1934
|
+
// Runtime state, kept at the top level like `version` so it can never
|
|
1935
|
+
// be round-tripped into a persisted ServerConfig.
|
|
1936
|
+
...(packageVersion ? { packageVersion } : {}),
|
|
1937
|
+
...(latestVersion ? { latestVersion } : {}),
|
|
1938
|
+
...(updateAvailable !== undefined ? { updateAvailable } : {}),
|
|
1894
1939
|
oauth: oauth || oauthConnected
|
|
1895
1940
|
? {
|
|
1896
1941
|
...(oauth
|
|
@@ -1969,6 +2014,75 @@ export const reconnectServer = async (serverName) => {
|
|
|
1969
2014
|
await initializeClientsFromSettings(false, serverName);
|
|
1970
2015
|
logger.log(`Successfully reconnected server: ${serverName}`);
|
|
1971
2016
|
};
|
|
2017
|
+
/**
|
|
2018
|
+
* Resolve the installed package version for an npx/uvx stdio server and record
|
|
2019
|
+
* it on the live ServerInfo, so the dashboard can answer "what version is
|
|
2020
|
+
* actually running" (see #1166). Also checks the configured registry for a
|
|
2021
|
+
* newer version and marks updateAvailable. Fire-and-forget: best-effort
|
|
2022
|
+
* display data — a resolution failure must never break or delay a connection.
|
|
2023
|
+
*
|
|
2024
|
+
* On any failure the runtime fields are cleared so the UI never shows a stale
|
|
2025
|
+
* version or a stale "update available" hint from an earlier, healthier check.
|
|
2026
|
+
*/
|
|
2027
|
+
const applyResolvedPackageVersion = (info, config) => {
|
|
2028
|
+
const command = config?.command;
|
|
2029
|
+
if (!command || !supportsCacheRefresh(command))
|
|
2030
|
+
return;
|
|
2031
|
+
void (async () => {
|
|
2032
|
+
try {
|
|
2033
|
+
const args = config.args ?? [];
|
|
2034
|
+
const version = await resolveRunnerPackageVersion(command, args);
|
|
2035
|
+
if (!version) {
|
|
2036
|
+
info.packageVersion = undefined;
|
|
2037
|
+
info.latestVersion = undefined;
|
|
2038
|
+
info.updateAvailable = undefined;
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
info.packageVersion = version;
|
|
2042
|
+
const systemConfig = await getSystemConfigDao().get();
|
|
2043
|
+
const update = await checkPackageUpdate(command, args, version, {
|
|
2044
|
+
npmRegistry: systemConfig?.install?.npmRegistry,
|
|
2045
|
+
pythonIndexUrl: systemConfig?.install?.pythonIndexUrl,
|
|
2046
|
+
});
|
|
2047
|
+
if (update?.latestVersion) {
|
|
2048
|
+
info.latestVersion = update.latestVersion;
|
|
2049
|
+
info.updateAvailable = update.updateAvailable;
|
|
2050
|
+
}
|
|
2051
|
+
else {
|
|
2052
|
+
// Registry unreachable or no metadata: hide the hint rather than keep
|
|
2053
|
+
// a stale "update available" from a previous successful check.
|
|
2054
|
+
info.latestVersion = undefined;
|
|
2055
|
+
info.updateAvailable = undefined;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
catch (error) {
|
|
2059
|
+
logger.warn(`Failed to resolve package update for ${info.name}`, { error });
|
|
2060
|
+
info.latestVersion = undefined;
|
|
2061
|
+
info.updateAvailable = undefined;
|
|
2062
|
+
}
|
|
2063
|
+
})();
|
|
2064
|
+
};
|
|
2065
|
+
// Periodically refresh the "update available" flag for npx/uvx servers (#1166).
|
|
2066
|
+
// Each check respects the TTL cache inside checkPackageUpdate, so this only
|
|
2067
|
+
// re-queries packages whose last registry lookup has gone stale. The timer is
|
|
2068
|
+
// unref'd so it never keeps the process alive by itself.
|
|
2069
|
+
//
|
|
2070
|
+
// Note: personal-runtime servers (credential-template bindings) are not part of
|
|
2071
|
+
// `serverInfos` and are therefore not swept here — their flags refresh when a
|
|
2072
|
+
// new per-user connection is created in createPrincipalRuntime instead.
|
|
2073
|
+
const PACKAGE_UPDATE_SWEEP_MS = 6 * 60 * 60 * 1000;
|
|
2074
|
+
const packageUpdateSweepTimer = setInterval(() => {
|
|
2075
|
+
for (const info of serverInfos) {
|
|
2076
|
+
if (!info.config?.command)
|
|
2077
|
+
continue;
|
|
2078
|
+
applyResolvedPackageVersion(info, info.config);
|
|
2079
|
+
}
|
|
2080
|
+
}, PACKAGE_UPDATE_SWEEP_MS);
|
|
2081
|
+
packageUpdateSweepTimer.unref?.();
|
|
2082
|
+
/** Stop the periodic package-update sweep (called on shutdown). */
|
|
2083
|
+
export const stopPackageUpdateSweep = () => {
|
|
2084
|
+
clearInterval(packageUpdateSweepTimer);
|
|
2085
|
+
};
|
|
1972
2086
|
// Reinstall server: clear package cache and reconnect.
|
|
1973
2087
|
// For npx: deletes ~/.npm/_npx before reconnect (--ignore-existing removed in npm 7+).
|
|
1974
2088
|
// For uvx: schedules --refresh flag injection on next spawn via pendingReinstalls Set.
|
|
@@ -2303,6 +2417,8 @@ const ensureServerReady = async (serverInfo) => {
|
|
|
2303
2417
|
serverInfo.version = client.getServerVersion?.()?.version;
|
|
2304
2418
|
serverInfo.instructions = client.getInstructions?.();
|
|
2305
2419
|
serverInfo.config = expandedConf;
|
|
2420
|
+
// Record the resolved npx/uvx package version backing this server.
|
|
2421
|
+
applyResolvedPackageVersion(serverInfo, expandedConf);
|
|
2306
2422
|
serverInfo.status = 'connected';
|
|
2307
2423
|
serverInfo.error = null;
|
|
2308
2424
|
let toolCount = 0;
|
|
@@ -3569,9 +3685,12 @@ const createPrincipalRuntime = async (name, resolvedConfig) => {
|
|
|
3569
3685
|
try {
|
|
3570
3686
|
return await value.apply(target, args);
|
|
3571
3687
|
}
|
|
3572
|
-
catch {
|
|
3573
|
-
|
|
3574
|
-
|
|
3688
|
+
catch (error) {
|
|
3689
|
+
if (isUpstreamConnectionFailure(error))
|
|
3690
|
+
info.status = 'disconnected';
|
|
3691
|
+
const failure = createUpstreamRequestError(name, String(property), error, resolvedConfig);
|
|
3692
|
+
logger.error(failure.message);
|
|
3693
|
+
throw failure;
|
|
3575
3694
|
}
|
|
3576
3695
|
};
|
|
3577
3696
|
},
|
|
@@ -3606,6 +3725,8 @@ const createPrincipalRuntime = async (name, resolvedConfig) => {
|
|
|
3606
3725
|
if (capabilities?.resources)
|
|
3607
3726
|
info.resources = (await client.listResources({}, resolvedConfig.options)).resources.map(normalizeResourceForCache);
|
|
3608
3727
|
info.version = client.getServerVersion()?.version;
|
|
3728
|
+
// Record the resolved npx/uvx package version backing this server.
|
|
3729
|
+
applyResolvedPackageVersion(info, resolvedConfig);
|
|
3609
3730
|
client.onclose = () => {
|
|
3610
3731
|
info.status = 'disconnected';
|
|
3611
3732
|
};
|
|
@@ -3613,12 +3734,36 @@ const createPrincipalRuntime = async (name, resolvedConfig) => {
|
|
|
3613
3734
|
info.status = 'connected';
|
|
3614
3735
|
return info;
|
|
3615
3736
|
}
|
|
3616
|
-
catch {
|
|
3737
|
+
catch (error) {
|
|
3617
3738
|
closeServerRuntime(info);
|
|
3618
|
-
|
|
3739
|
+
const failure = createUpstreamRequestError(name, 'connect', error, resolvedConfig);
|
|
3740
|
+
logger.error(failure.message);
|
|
3741
|
+
throw failure;
|
|
3619
3742
|
}
|
|
3620
3743
|
};
|
|
3621
3744
|
const principalRuntimes = new PrincipalRuntimeService(createPrincipalRuntime, closeServerRuntime);
|
|
3745
|
+
/**
|
|
3746
|
+
* Resolve the tools of a personal-credential server on behalf of an explicit
|
|
3747
|
+
* principal, outside any request context.
|
|
3748
|
+
*
|
|
3749
|
+
* Servers that declare a credential template are never connected globally (see
|
|
3750
|
+
* the `hasCredentialTemplate` branch in `initializeClientsFromSettings`); their
|
|
3751
|
+
* runtime only exists for the principals who bound their own credentials. Any
|
|
3752
|
+
* caller that needs the tool list of such a server — smart routing reindex, for
|
|
3753
|
+
* instance — must therefore borrow one of those principals instead of relying
|
|
3754
|
+
* on the ambient request user, which may hold no binding at all.
|
|
3755
|
+
*
|
|
3756
|
+
* The returned list is a snapshot: the lease is released before returning.
|
|
3757
|
+
*/
|
|
3758
|
+
export const getServerToolsForPrincipal = async (serverName, principal) => {
|
|
3759
|
+
const lease = await principalRuntimes.acquire(serverName, principal);
|
|
3760
|
+
try {
|
|
3761
|
+
return lease.info.tools ?? [];
|
|
3762
|
+
}
|
|
3763
|
+
finally {
|
|
3764
|
+
lease.release();
|
|
3765
|
+
}
|
|
3766
|
+
};
|
|
3622
3767
|
const withPrincipalServers = (handler, operation) => async (request, extra) => {
|
|
3623
3768
|
if (!serverInfos.some((info) => hasCredentialTemplate(info.config)))
|
|
3624
3769
|
return handler(request, extra);
|
|
@@ -3641,12 +3786,25 @@ const withPrincipalServers = (handler, operation) => async (request, extra) => {
|
|
|
3641
3786
|
const requestedName = ['call_tool', 'describe_tool'].includes(request?.params?.name)
|
|
3642
3787
|
? request.params.arguments?.toolName
|
|
3643
3788
|
: request?.params?.name;
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3789
|
+
// An explicit REST target takes precedence over every qualified-name prefix.
|
|
3790
|
+
// Otherwise prefer the longest exposed prefix, including group aliases.
|
|
3791
|
+
let target = extra?.server
|
|
3792
|
+
? candidates.find((info) => info.name === extra.server)
|
|
3793
|
+
: undefined;
|
|
3794
|
+
if (extra?.server && !target && (operation === 'tool' || operation === 'prompt')) {
|
|
3795
|
+
throw new ToolUnavailableError(`Tool not available: ${requestedName}`, 'server-or-name-not-found');
|
|
3796
|
+
}
|
|
3797
|
+
if (!extra?.server && typeof requestedName === 'string') {
|
|
3798
|
+
let longestPrefix = 0;
|
|
3799
|
+
for (const info of candidates) {
|
|
3800
|
+
const exposedName = getExposedServerName(info.name, serverConfigsByName.get(info.name));
|
|
3801
|
+
const prefix = `${exposedName}${getNameSeparator()}`;
|
|
3802
|
+
if (prefix.length > longestPrefix && requestedName.startsWith(prefix)) {
|
|
3803
|
+
target = info;
|
|
3804
|
+
longestPrefix = prefix.length;
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3650
3808
|
// A call to a known target must not spawn or keep unrelated personal children alive.
|
|
3651
3809
|
const selected = target && (operation === 'tool' || operation === 'prompt') ? [target] : candidates;
|
|
3652
3810
|
const templated = selected.filter((info) => hasCredentialTemplate(info.config));
|
|
@@ -3668,9 +3826,9 @@ const withPrincipalServers = (handler, operation) => async (request, extra) => {
|
|
|
3668
3826
|
scoped.set(info.name, lease.info);
|
|
3669
3827
|
}
|
|
3670
3828
|
catch (error) {
|
|
3671
|
-
failures.set(info.name, error instanceof CredentialBindingError
|
|
3829
|
+
failures.set(info.name, error instanceof CredentialBindingError || error instanceof UpstreamRequestError
|
|
3672
3830
|
? error
|
|
3673
|
-
: new
|
|
3831
|
+
: new Error(`Unable to prepare upstream '${info.name}'`));
|
|
3674
3832
|
}
|
|
3675
3833
|
}));
|
|
3676
3834
|
const requiredTarget = target ||
|