@samanhappy/mcphub 1.0.45 → 1.1.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/dist/clients/legacyMcpClient.js +30 -4
- package/dist/clients/legacyMcpClient.js.map +1 -1
- package/dist/clients/openapi.js +4 -1
- package/dist/clients/openapi.js.map +1 -1
- package/dist/controllers/oauthCallbackController.js +2 -2
- package/dist/controllers/oauthCallbackController.js.map +1 -1
- package/dist/services/mcpService.js +137 -28
- package/dist/services/mcpService.js.map +1 -1
- package/dist/services/principalRuntimeService.js +2 -2
- package/dist/services/principalRuntimeService.js.map +1 -1
- package/dist/services/proxy.js +4 -0
- package/dist/services/proxy.js.map +1 -1
- package/dist/services/serverConfigEnvironment.js +23 -0
- package/dist/services/serverConfigEnvironment.js.map +1 -0
- package/dist/services/sseService.js +11 -8
- package/dist/services/sseService.js.map +1 -1
- package/dist/utils/listFreshness.js +44 -0
- package/dist/utils/listFreshness.js.map +1 -0
- package/dist/utils/ssrf.js +49 -2
- package/dist/utils/ssrf.js.map +1 -1
- package/frontend/dist/assets/{ActivityPage-DPlbWRuE.js → ActivityPage-Dp0qfFJ7.js} +2 -2
- package/frontend/dist/assets/{ActivityPage-DPlbWRuE.js.map → ActivityPage-Dp0qfFJ7.js.map} +1 -1
- package/frontend/dist/assets/{CopyClientConfigDialog-BmrvVqKm.js → CopyClientConfigDialog-Bj0FEskD.js} +2 -2
- package/frontend/dist/assets/{CopyClientConfigDialog-BmrvVqKm.js.map → CopyClientConfigDialog-Bj0FEskD.js.map} +1 -1
- package/frontend/dist/assets/{CredentialsPage-DUa5COar.js → CredentialsPage-C-yB8aQU.js} +2 -2
- package/frontend/dist/assets/{CredentialsPage-DUa5COar.js.map → CredentialsPage-C-yB8aQU.js.map} +1 -1
- package/frontend/dist/assets/{Dashboard-DaTYK_nx.js → Dashboard-cbalSWB1.js} +2 -2
- package/frontend/dist/assets/{Dashboard-DaTYK_nx.js.map → Dashboard-cbalSWB1.js.map} +1 -1
- package/frontend/dist/assets/{EndpointCopy-BCfqwDEK.js → EndpointCopy-V8MoW7SG.js} +2 -2
- package/frontend/dist/assets/{EndpointCopy-BCfqwDEK.js.map → EndpointCopy-V8MoW7SG.js.map} +1 -1
- package/frontend/dist/assets/{GroupsPage-Cuxz5FyS.js → GroupsPage-DTXuiuqc.js} +4 -4
- package/frontend/dist/assets/{GroupsPage-Cuxz5FyS.js.map → GroupsPage-DTXuiuqc.js.map} +1 -1
- package/frontend/dist/assets/{LoginPage-yTpHn5Tx.js → LoginPage-kfbiuyWa.js} +2 -2
- package/frontend/dist/assets/{LoginPage-yTpHn5Tx.js.map → LoginPage-kfbiuyWa.js.map} +1 -1
- package/frontend/dist/assets/{LogsPage-BbSlC06Q.js → LogsPage-Bwv0shjb.js} +2 -2
- package/frontend/dist/assets/{LogsPage-BbSlC06Q.js.map → LogsPage-Bwv0shjb.js.map} +1 -1
- package/frontend/dist/assets/MarketPage-3jMYsH4H.js +2 -0
- package/frontend/dist/assets/MarketPage-3jMYsH4H.js.map +1 -0
- package/frontend/dist/assets/{OAuthConsentPage-W05BBBpL.js → OAuthConsentPage-DI-IQ2gj.js} +2 -2
- package/frontend/dist/assets/{OAuthConsentPage-W05BBBpL.js.map → OAuthConsentPage-DI-IQ2gj.js.map} +1 -1
- package/frontend/dist/assets/{PromptsPage-Bq7xptCw.js → PromptsPage-BuVTifDV.js} +2 -2
- package/frontend/dist/assets/{PromptsPage-Bq7xptCw.js.map → PromptsPage-BuVTifDV.js.map} +1 -1
- package/frontend/dist/assets/{ResourcesPage-CsL39aff.js → ResourcesPage-COSY92pD.js} +2 -2
- package/frontend/dist/assets/{ResourcesPage-CsL39aff.js.map → ResourcesPage-COSY92pD.js.map} +1 -1
- package/frontend/dist/assets/ServersPage-BlZvDfZE.js +39 -0
- package/frontend/dist/assets/ServersPage-BlZvDfZE.js.map +1 -0
- package/frontend/dist/assets/SettingsPage-V0TrX4k8.js +12 -0
- package/frontend/dist/assets/SettingsPage-V0TrX4k8.js.map +1 -0
- package/frontend/dist/assets/{StatusDot-DnCf86-T.js → StatusDot-BHfWZAic.js} +2 -2
- package/frontend/dist/assets/{StatusDot-DnCf86-T.js.map → StatusDot-BHfWZAic.js.map} +1 -1
- package/frontend/dist/assets/{ToggleGroup-DFjmIz4f.js → ToggleGroup-C33UH_Ob.js} +2 -2
- package/frontend/dist/assets/{ToggleGroup-DFjmIz4f.js.map → ToggleGroup-C33UH_Ob.js.map} +1 -1
- package/frontend/dist/assets/{UsersPage-g_A6Nis1.js → UsersPage-BXjVPFoF.js} +2 -2
- package/frontend/dist/assets/{UsersPage-g_A6Nis1.js.map → UsersPage-BXjVPFoF.js.map} +1 -1
- package/frontend/dist/assets/{clipboard-C6pDz_7o.js → clipboard-r7a9vj44.js} +2 -2
- package/frontend/dist/assets/{clipboard-C6pDz_7o.js.map → clipboard-r7a9vj44.js.map} +1 -1
- package/frontend/dist/assets/index-D-J2WUmj.css +1 -0
- package/frontend/dist/assets/index-pouDG4uY.js +3 -0
- package/frontend/dist/assets/index-pouDG4uY.js.map +1 -0
- package/frontend/dist/assets/{resourceService-CAxgCzyV.js → resourceService-5J2x7MrD.js} +2 -2
- package/frontend/dist/assets/{resourceService-CAxgCzyV.js.map → resourceService-5J2x7MrD.js.map} +1 -1
- package/frontend/dist/assets/{useCostData-wcUc6g7_.js → useCostData-CjmWUnd9.js} +2 -2
- package/frontend/dist/assets/{useCostData-wcUc6g7_.js.map → useCostData-CjmWUnd9.js.map} +1 -1
- package/frontend/dist/assets/variableDetection-CAv2g3qV.js +16 -0
- package/frontend/dist/assets/variableDetection-CAv2g3qV.js.map +1 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/MarketPage-nAF8uFkv.js +0 -2
- package/frontend/dist/assets/MarketPage-nAF8uFkv.js.map +0 -1
- package/frontend/dist/assets/ServersPage-p9LgWUzS.js +0 -39
- package/frontend/dist/assets/ServersPage-p9LgWUzS.js.map +0 -1
- package/frontend/dist/assets/SettingsPage-DobkN2nZ.js +0 -12
- package/frontend/dist/assets/SettingsPage-DobkN2nZ.js.map +0 -1
- package/frontend/dist/assets/index-BlEZfPQl.js +0 -3
- package/frontend/dist/assets/index-BlEZfPQl.js.map +0 -1
- package/frontend/dist/assets/index-Cla8rRxG.css +0 -1
- package/frontend/dist/assets/variableDetection-DxVZhKpb.js +0 -16
- package/frontend/dist/assets/variableDetection-DxVZhKpb.js.map +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { preserveListFreshness, combineListFreshness, remainingListTtl, } from '../utils/listFreshness.js';
|
|
2
|
+
import { expandServerConfig, getServerEnvironment } from './serverConfigEnvironment.js';
|
|
1
3
|
import { getMcpRequestGroup } from '../utils/mcpRequestGroup.js';
|
|
2
4
|
import { LegacyMcpClient } from '../clients/legacyMcpClient.js';
|
|
3
5
|
import { LEGACY_PROTOCOL_VERSIONS } from '../utils/mcpProtocol.js';
|
|
@@ -29,7 +31,7 @@ import { removeServerToolEmbeddings, removeToolEmbeddings, saveToolsAsVectorEmbe
|
|
|
29
31
|
import { OpenAPIClient } from '../clients/openapi.js';
|
|
30
32
|
import { RequestContextService } from './requestContextService.js';
|
|
31
33
|
import { getDataService } from './services.js';
|
|
32
|
-
import { getServerDao, getGroupDao, getSystemConfigDao, getBuiltinPromptDao, getBuiltinResourceDao, } from '../dao/index.js';
|
|
34
|
+
import { getBearerKeyDao, getServerDao, getGroupDao, getSystemConfigDao, getBuiltinPromptDao, getBuiltinResourceDao, } from '../dao/index.js';
|
|
33
35
|
import { initializeAllOAuthClients } from './oauthService.js';
|
|
34
36
|
import { createOAuthProvider } from './mcpOAuthProvider.js';
|
|
35
37
|
import { initSmartRoutingService, getSmartRoutingTools, handleSearchToolsRequest, handleDescribeToolRequest, isSmartRoutingGroup, } from './smartRoutingService.js';
|
|
@@ -781,7 +783,7 @@ export const setServerInfosForTest = (infos) => {
|
|
|
781
783
|
serverInfos = infos;
|
|
782
784
|
};
|
|
783
785
|
export const updateServerToolsCache = (serverInfo, tools, options) => {
|
|
784
|
-
serverInfo.tools = tools.map((tool) => normalizeToolForCache(serverInfo.name, tool));
|
|
786
|
+
serverInfo.tools = preserveListFreshness(tools, tools.map((tool) => normalizeToolForCache(serverInfo.name, tool)));
|
|
785
787
|
syncToolsAsVectorEmbeddings(serverInfo.name, serverInfo.tools, {
|
|
786
788
|
reportProgress: options?.reportEmbeddingProgress === true,
|
|
787
789
|
}).catch(() => {
|
|
@@ -789,10 +791,10 @@ export const updateServerToolsCache = (serverInfo, tools, options) => {
|
|
|
789
791
|
});
|
|
790
792
|
};
|
|
791
793
|
const updateServerPromptsCache = (serverInfo, prompts) => {
|
|
792
|
-
serverInfo.prompts = prompts.map((prompt) => normalizePromptForCache(serverInfo.name, prompt));
|
|
794
|
+
serverInfo.prompts = preserveListFreshness(prompts, prompts.map((prompt) => normalizePromptForCache(serverInfo.name, prompt)));
|
|
793
795
|
};
|
|
794
796
|
const updateServerResourcesCache = (serverInfo, resources) => {
|
|
795
|
-
serverInfo.resources = resources.map(normalizeResourceForCache);
|
|
797
|
+
serverInfo.resources = preserveListFreshness(resources, resources.map(normalizeResourceForCache));
|
|
796
798
|
};
|
|
797
799
|
const logListChangedRefreshError = (listType) => {
|
|
798
800
|
logger.warn(`Failed to refresh ${listType} list after upstream notification`);
|
|
@@ -806,6 +808,7 @@ const createUpstreamMcpClient = (name, getServerInfo) => {
|
|
|
806
808
|
jsonSchemaValidator: new ResilientJsonSchemaValidator(),
|
|
807
809
|
listChanged: {
|
|
808
810
|
tools: {
|
|
811
|
+
debounceMs: 0,
|
|
809
812
|
onChanged: (error, tools) => {
|
|
810
813
|
const serverInfo = getServerInfo();
|
|
811
814
|
if (error) {
|
|
@@ -820,6 +823,7 @@ const createUpstreamMcpClient = (name, getServerInfo) => {
|
|
|
820
823
|
},
|
|
821
824
|
},
|
|
822
825
|
prompts: {
|
|
826
|
+
debounceMs: 0,
|
|
823
827
|
onChanged: (error, prompts) => {
|
|
824
828
|
const serverInfo = getServerInfo();
|
|
825
829
|
if (error) {
|
|
@@ -834,6 +838,7 @@ const createUpstreamMcpClient = (name, getServerInfo) => {
|
|
|
834
838
|
},
|
|
835
839
|
},
|
|
836
840
|
resources: {
|
|
841
|
+
debounceMs: 0,
|
|
837
842
|
onChanged: (error, resources) => {
|
|
838
843
|
const serverInfo = getServerInfo();
|
|
839
844
|
if (error) {
|
|
@@ -1171,10 +1176,7 @@ const stripAuthorizationHeader = (headers) => {
|
|
|
1171
1176
|
};
|
|
1172
1177
|
export const createTransportFromConfig = async (name, conf) => {
|
|
1173
1178
|
let transport;
|
|
1174
|
-
const env =
|
|
1175
|
-
...process.env,
|
|
1176
|
-
...(hasCredentialTemplate(conf) ? conf.env : replaceEnvVars(conf.env || {})),
|
|
1177
|
-
};
|
|
1179
|
+
const env = await getServerEnvironment(conf);
|
|
1178
1180
|
// The encryption key belongs only to the hub, never to a child process.
|
|
1179
1181
|
delete env.MCPHUB_CREDENTIAL_ENCRYPTION_KEY;
|
|
1180
1182
|
// SSRF guard: block URL/streamable-http transports from reaching
|
|
@@ -1275,7 +1277,7 @@ export const createTransportFromConfig = async (name, conf) => {
|
|
|
1275
1277
|
env['npm_config_registry'] = systemConfig.install.npmRegistry;
|
|
1276
1278
|
}
|
|
1277
1279
|
// Apply proxychains4 wrapper if proxy is configured (Linux/macOS only)
|
|
1278
|
-
let resolvedArgs = replaceEnvVars(conf.args ?? []);
|
|
1280
|
+
let resolvedArgs = replaceEnvVars(conf.args ?? [], env);
|
|
1279
1281
|
// If this server is pending a reinstall, inject cache-busting flags (uvx only).
|
|
1280
1282
|
// For npx, the cache directory was already cleared before reconnect.
|
|
1281
1283
|
if (pendingReinstalls.has(name)) {
|
|
@@ -1344,7 +1346,7 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
|
|
|
1344
1346
|
});
|
|
1345
1347
|
}
|
|
1346
1348
|
// Match initial connection expansion before URL validation and transport creation.
|
|
1347
|
-
const server =
|
|
1349
|
+
const server = await expandServerConfig(rawConfig);
|
|
1348
1350
|
const newTransport = await createTransportFromConfig(serverInfo.name, server);
|
|
1349
1351
|
const newClient = createUpstreamMcpClient(serverInfo.name, () => serverInfo);
|
|
1350
1352
|
// Reconnect with new transport
|
|
@@ -1476,7 +1478,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
|
|
|
1476
1478
|
}
|
|
1477
1479
|
}
|
|
1478
1480
|
// Expand environment variables in all configuration values
|
|
1479
|
-
const expandedConf =
|
|
1481
|
+
const expandedConf = await expandServerConfig(conf);
|
|
1480
1482
|
// Skip disabled servers
|
|
1481
1483
|
if (expandedConf.enabled === false) {
|
|
1482
1484
|
logger.log(`Skipping disabled server: ${name}`);
|
|
@@ -2542,7 +2544,7 @@ const ensureServerReady = async (serverInfo) => {
|
|
|
2542
2544
|
if (rawConfig.enabled === false) {
|
|
2543
2545
|
throw new Error(`Cannot start disabled on-demand server: ${rawConfig.name}`);
|
|
2544
2546
|
}
|
|
2545
|
-
const expandedConf =
|
|
2547
|
+
const expandedConf = await expandServerConfig(rawConfig);
|
|
2546
2548
|
// startOnDemand is a stdio-only optimisation; OpenAPI/HTTP servers have no
|
|
2547
2549
|
// deferred process to spawn, so there is nothing to wake.
|
|
2548
2550
|
if (expandedConf.type === 'openapi') {
|
|
@@ -3766,19 +3768,24 @@ const handleListResourceTemplatesRequestImpl = async (_, extra) => {
|
|
|
3766
3768
|
const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(lookupGroup, {
|
|
3767
3769
|
requireClient: true,
|
|
3768
3770
|
});
|
|
3771
|
+
const snapshots = [];
|
|
3769
3772
|
const results = await Promise.allSettled(filteredServerInfos.map(async (serverInfo) => {
|
|
3770
3773
|
if (!serverInfo.client?.listResourceTemplates) {
|
|
3771
3774
|
return [];
|
|
3772
3775
|
}
|
|
3773
3776
|
const templates = await serverInfo.client.listResourceTemplates({}, serverInfo.options || {});
|
|
3777
|
+
snapshots.push(templates.resourceTemplates);
|
|
3774
3778
|
const filteredTemplates = await filterResourceTemplatesByGroup(lookupGroup, serverInfo.name, templates.resourceTemplates || [], serverConfigsByName.get(serverInfo.name));
|
|
3775
3779
|
return appsRouteContext.enabled
|
|
3776
3780
|
? filteredTemplates
|
|
3777
3781
|
: filteredTemplates.map((template) => stripMcpAppsMetadata(template));
|
|
3778
3782
|
}));
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3783
|
+
const resourceTemplates = results.flatMap((result) => result.status === 'fulfilled' ? result.value : []);
|
|
3784
|
+
if (results.every((result) => result.status === 'fulfilled') &&
|
|
3785
|
+
snapshots.length === filteredServerInfos.length) {
|
|
3786
|
+
combineListFreshness(snapshots, resourceTemplates);
|
|
3787
|
+
}
|
|
3788
|
+
return { resourceTemplates };
|
|
3782
3789
|
};
|
|
3783
3790
|
const handleReadResourceRequestImpl = async (request, extra) => {
|
|
3784
3791
|
try {
|
|
@@ -3818,8 +3825,15 @@ const handleReadResourceRequestImpl = async (request, extra) => {
|
|
|
3818
3825
|
}
|
|
3819
3826
|
}
|
|
3820
3827
|
let result;
|
|
3828
|
+
// A cached SDK body carries its original TTL, not the remaining lifetime.
|
|
3829
|
+
// Fetch modern reads afresh so forwarding the TTL cannot renew stale content.
|
|
3830
|
+
const readOptions = RequestContextService.getInstance().getRequestContext()?.stateless
|
|
3831
|
+
? { cacheMode: 'bypass' }
|
|
3832
|
+
: undefined;
|
|
3821
3833
|
if (server?.client) {
|
|
3822
|
-
result =
|
|
3834
|
+
result = readOptions
|
|
3835
|
+
? await server.client.readResource({ uri }, readOptions)
|
|
3836
|
+
: await server.client.readResource({ uri });
|
|
3823
3837
|
if (!result || !Array.isArray(result.contents)) {
|
|
3824
3838
|
throw new Error(`Failed to read resource: ${uri}`);
|
|
3825
3839
|
}
|
|
@@ -3839,7 +3853,9 @@ const handleReadResourceRequestImpl = async (request, extra) => {
|
|
|
3839
3853
|
continue;
|
|
3840
3854
|
}
|
|
3841
3855
|
try {
|
|
3842
|
-
const candidateResult =
|
|
3856
|
+
const candidateResult = readOptions
|
|
3857
|
+
? await candidate.client.readResource({ uri }, readOptions)
|
|
3858
|
+
: await candidate.client.readResource({ uri });
|
|
3843
3859
|
if (candidateResult && Array.isArray(candidateResult.contents)) {
|
|
3844
3860
|
result = candidateResult;
|
|
3845
3861
|
break;
|
|
@@ -3947,12 +3963,18 @@ const createPrincipalRuntime = async (name, resolvedConfig) => {
|
|
|
3947
3963
|
info.client = protectClient(client);
|
|
3948
3964
|
await client.connect(transport, resolvedConfig.options || {});
|
|
3949
3965
|
const capabilities = client.getServerCapabilities();
|
|
3950
|
-
if (capabilities?.tools)
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
if (capabilities?.
|
|
3955
|
-
|
|
3966
|
+
if (capabilities?.tools) {
|
|
3967
|
+
const { tools } = await client.listTools({}, resolvedConfig.options);
|
|
3968
|
+
info.tools = preserveListFreshness(tools, tools.map((tool) => normalizeToolForCache(name, tool)));
|
|
3969
|
+
}
|
|
3970
|
+
if (capabilities?.prompts) {
|
|
3971
|
+
const { prompts } = await client.listPrompts({}, resolvedConfig.options);
|
|
3972
|
+
info.prompts = preserveListFreshness(prompts, prompts.map((prompt) => normalizePromptForCache(name, prompt)));
|
|
3973
|
+
}
|
|
3974
|
+
if (capabilities?.resources) {
|
|
3975
|
+
const { resources } = await client.listResources({}, resolvedConfig.options);
|
|
3976
|
+
info.resources = preserveListFreshness(resources, resources.map(normalizeResourceForCache));
|
|
3977
|
+
}
|
|
3956
3978
|
info.version = client.getServerVersion()?.version;
|
|
3957
3979
|
// Record the resolved npx/uvx package version backing this server.
|
|
3958
3980
|
applyResolvedPackageVersion(info, resolvedConfig);
|
|
@@ -4104,13 +4126,100 @@ const withPrincipalServers = (handler, operation) => async (request, extra) => {
|
|
|
4104
4126
|
release();
|
|
4105
4127
|
}
|
|
4106
4128
|
};
|
|
4107
|
-
|
|
4129
|
+
// Re-read authoritative inputs across awaits; no projected response is cached.
|
|
4130
|
+
const listProjectionInputs = async (group, field) => {
|
|
4131
|
+
const user = UserContextService.getInstance().getCurrentUser();
|
|
4132
|
+
const keyId = RequestContextService.getInstance().getBearerKeyContext().keyId;
|
|
4133
|
+
const liveUser = user?.username ? await getUserDao().findByUsername(user.username) : undefined;
|
|
4134
|
+
const key = keyId ? await getBearerKeyDao().findById(keyId) : undefined;
|
|
4135
|
+
if ((user?.username && (!liveUser || Boolean(user.isAdmin) !== Boolean(liveUser.isAdmin))) ||
|
|
4136
|
+
(keyId && !key?.enabled))
|
|
4137
|
+
return undefined;
|
|
4138
|
+
const lookupGroup = getGroupLookupName(group);
|
|
4139
|
+
const groupDao = getGroupDao();
|
|
4140
|
+
let routeGroup = lookupGroup ? await groupDao.findByName(lookupGroup) : undefined;
|
|
4141
|
+
if (!routeGroup &&
|
|
4142
|
+
lookupGroup &&
|
|
4143
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(lookupGroup)) {
|
|
4144
|
+
routeGroup = await groupDao.findById(lookupGroup);
|
|
4145
|
+
}
|
|
4146
|
+
return JSON.stringify([
|
|
4147
|
+
await getServerDao().findAll(),
|
|
4148
|
+
routeGroup,
|
|
4149
|
+
await getSystemConfigDao().get(),
|
|
4150
|
+
field === 'prompts' ? await getBuiltinPromptDao().findEnabled() : undefined,
|
|
4151
|
+
field === 'resources' ? await getBuiltinResourceDao().findEnabled() : undefined,
|
|
4152
|
+
liveUser,
|
|
4153
|
+
key,
|
|
4154
|
+
RequestContextService.getInstance().getRequestContext()?.clientCapabilities,
|
|
4155
|
+
getVisibleServerInfos().map((info) => ({
|
|
4156
|
+
name: info.name,
|
|
4157
|
+
status: info.status,
|
|
4158
|
+
enabled: info.enabled,
|
|
4159
|
+
owner: info.owner,
|
|
4160
|
+
visibility: info.visibility,
|
|
4161
|
+
sharedWithUsers: info.sharedWithUsers,
|
|
4162
|
+
config: info.config,
|
|
4163
|
+
})),
|
|
4164
|
+
]);
|
|
4165
|
+
};
|
|
4166
|
+
// Lists combine cached discovery with live gateway configuration and authorization.
|
|
4167
|
+
// Upstream freshness alone cannot guarantee freshness of the resulting projection.
|
|
4168
|
+
const withCacheHints = (handler, passthroughTtl = false, listField) => async (request, extra) => {
|
|
4169
|
+
if (!RequestContextService.getInstance().getRequestContext()?.stateless) {
|
|
4170
|
+
return handler(request, extra);
|
|
4171
|
+
}
|
|
4172
|
+
const startedAt = performance.now();
|
|
4173
|
+
const group = getMcpRequestGroup(extra);
|
|
4174
|
+
const inputs = listField ? await listProjectionInputs(group, listField) : undefined;
|
|
4175
|
+
const before = listField
|
|
4176
|
+
? await getFilteredServerInfosForGroup(getGroupLookupName(group))
|
|
4177
|
+
: undefined;
|
|
4178
|
+
const snapshots = before && listField && listField !== 'resourceTemplates'
|
|
4179
|
+
? before.filteredServerInfos.map((info) => info[listField])
|
|
4180
|
+
: [];
|
|
4181
|
+
const clients = before?.filteredServerInfos.map((info) => info.client);
|
|
4182
|
+
const result = await handler(request, extra);
|
|
4183
|
+
if (listField) {
|
|
4184
|
+
const after = await getFilteredServerInfosForGroup(getGroupLookupName(group));
|
|
4185
|
+
const stable = typeof inputs === 'string' &&
|
|
4186
|
+
inputs === (await listProjectionInputs(group, listField)) &&
|
|
4187
|
+
before?.filteredServerInfos.length === after.filteredServerInfos.length &&
|
|
4188
|
+
before.filteredServerInfos.every((info, index) => info === after.filteredServerInfos[index] &&
|
|
4189
|
+
info.client === clients?.[index] &&
|
|
4190
|
+
info.status === 'connected' &&
|
|
4191
|
+
(listField === 'resourceTemplates' || info[listField] === snapshots[index]));
|
|
4192
|
+
const generated = (listField === 'tools' && isSmartRoutingGroup(group)) ||
|
|
4193
|
+
(listField === 'prompts' && (await getBuiltinPromptDao().findEnabled()).length > 0) ||
|
|
4194
|
+
(listField === 'resources' && (await getBuiltinResourceDao().findEnabled()).length > 0);
|
|
4195
|
+
const unknownPermissions = RequestContextService.getInstance().getHostedAuthContext();
|
|
4196
|
+
const dependencies = listField === 'resourceTemplates' ? [result.resourceTemplates] : snapshots;
|
|
4197
|
+
return {
|
|
4198
|
+
...result,
|
|
4199
|
+
ttlMs: stable && !generated && !unknownPermissions
|
|
4200
|
+
? remainingListTtl(dependencies, startedAt, performance.now())
|
|
4201
|
+
: 0,
|
|
4202
|
+
cacheScope: 'private',
|
|
4203
|
+
};
|
|
4204
|
+
}
|
|
4205
|
+
const upstreamTtl = result.ttlMs;
|
|
4206
|
+
const ttlMs = passthroughTtl &&
|
|
4207
|
+
typeof upstreamTtl === 'number' &&
|
|
4208
|
+
Number.isSafeInteger(upstreamTtl) &&
|
|
4209
|
+
upstreamTtl >= 0
|
|
4210
|
+
? Math.max(0, upstreamTtl - Math.ceil(performance.now() - startedAt))
|
|
4211
|
+
: 0;
|
|
4212
|
+
// Even public upstream content is projected through caller-specific permissions
|
|
4213
|
+
// and capabilities. Shared caches must not reuse the gateway result across users.
|
|
4214
|
+
return { ...result, ttlMs, cacheScope: 'private' };
|
|
4215
|
+
};
|
|
4216
|
+
export const handleListToolsRequest = withPrincipalServers(withCacheHints(handleListToolsRequestImpl, false, 'tools'), 'list');
|
|
4108
4217
|
export const handleCallToolRequest = withPrincipalServers(handleCallToolRequestImpl, 'tool');
|
|
4109
4218
|
export const handleGetPromptRequest = withPrincipalServers(handleGetPromptRequestImpl, 'prompt');
|
|
4110
|
-
export const handleListPromptsRequest = withPrincipalServers(handleListPromptsRequestImpl, 'list');
|
|
4111
|
-
export const handleListResourcesRequest = withPrincipalServers(handleListResourcesRequestImpl, 'list');
|
|
4112
|
-
export const handleListResourceTemplatesRequest = withPrincipalServers(handleListResourceTemplatesRequestImpl, 'list');
|
|
4113
|
-
export const handleReadResourceRequest = withPrincipalServers(handleReadResourceRequestImpl, 'resource');
|
|
4219
|
+
export const handleListPromptsRequest = withPrincipalServers(withCacheHints(handleListPromptsRequestImpl, false, 'prompts'), 'list');
|
|
4220
|
+
export const handleListResourcesRequest = withPrincipalServers(withCacheHints(handleListResourcesRequestImpl, false, 'resources'), 'list');
|
|
4221
|
+
export const handleListResourceTemplatesRequest = withPrincipalServers(withCacheHints(handleListResourceTemplatesRequestImpl, false, 'resourceTemplates'), 'list');
|
|
4222
|
+
export const handleReadResourceRequest = withPrincipalServers(withCacheHints(handleReadResourceRequestImpl, true), 'resource');
|
|
4114
4223
|
export const createMcpServer = (name, version, options) => {
|
|
4115
4224
|
const normalizedOptions = typeof options === 'string' ? { group: options } : (options ?? {});
|
|
4116
4225
|
// Determine server name based on routing type
|