@samanhappy/mcphub 1.1.3 → 1.1.4
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 +2 -0
- package/README.md +2 -0
- package/README.zh.md +2 -0
- package/dist/controllers/mcpbController.js +6 -2
- package/dist/controllers/mcpbController.js.map +1 -1
- package/dist/controllers/oauthCallbackController.js +11 -16
- package/dist/controllers/oauthCallbackController.js.map +1 -1
- package/dist/controllers/oauthServerController.js +10 -7
- package/dist/controllers/oauthServerController.js.map +1 -1
- package/dist/controllers/personalOAuthController.js +119 -0
- package/dist/controllers/personalOAuthController.js.map +1 -0
- package/dist/controllers/smartRoutingController.js +4 -4
- package/dist/controllers/smartRoutingController.js.map +1 -1
- package/dist/dao/CredentialBindingDao.js +8 -0
- package/dist/dao/CredentialBindingDao.js.map +1 -1
- package/dist/dao/CredentialBindingDaoDbImpl.js +14 -0
- package/dist/dao/CredentialBindingDaoDbImpl.js.map +1 -1
- package/dist/routes/index.js +8 -1
- package/dist/routes/index.js.map +1 -1
- package/dist/services/credentialBindingService.js +14 -8
- package/dist/services/credentialBindingService.js.map +1 -1
- package/dist/services/mcpOAuthProvider.js +40 -24
- package/dist/services/mcpOAuthProvider.js.map +1 -1
- package/dist/services/mcpService.js +66 -35
- package/dist/services/mcpService.js.map +1 -1
- package/dist/services/oauthService.js +3 -1
- package/dist/services/oauthService.js.map +1 -1
- package/dist/services/personalOAuthService.js +235 -0
- package/dist/services/personalOAuthService.js.map +1 -0
- package/dist/services/principalRuntimeService.js +67 -45
- package/dist/services/principalRuntimeService.js.map +1 -1
- package/dist/services/serverConfigPresenter.js +11 -1
- package/dist/services/serverConfigPresenter.js.map +1 -1
- package/dist/services/sseService.js +8 -28
- package/dist/services/sseService.js.map +1 -1
- package/dist/services/upstreamOAuthDisconnectService.js +2 -2
- package/dist/services/upstreamOAuthDisconnectService.js.map +1 -1
- package/dist/utils/credentialTemplate.js +37 -6
- package/dist/utils/credentialTemplate.js.map +1 -1
- package/dist/utils/protectedResource.js +44 -0
- package/dist/utils/protectedResource.js.map +1 -0
- package/dist/utils/serverConfigPersistence.js +13 -0
- package/dist/utils/serverConfigPersistence.js.map +1 -1
- package/dist/utils/upstreamError.js +8 -1
- package/dist/utils/upstreamError.js.map +1 -1
- package/frontend/dist/assets/{ActivityPage-Bs0YrAhj.js → ActivityPage-Cmpa6PNN.js} +2 -2
- package/frontend/dist/assets/{ActivityPage-Bs0YrAhj.js.map → ActivityPage-Cmpa6PNN.js.map} +1 -1
- package/frontend/dist/assets/{CopyClientConfigDialog-TcVaJHaV.js → CopyClientConfigDialog-CAqbh1Uh.js} +3 -3
- package/frontend/dist/assets/{CopyClientConfigDialog-TcVaJHaV.js.map → CopyClientConfigDialog-CAqbh1Uh.js.map} +1 -1
- package/frontend/dist/assets/{CredentialsPage-Cnd6MF-3.js → CredentialsPage-Cwr2rAVa.js} +2 -2
- package/frontend/dist/assets/{CredentialsPage-Cnd6MF-3.js.map → CredentialsPage-Cwr2rAVa.js.map} +1 -1
- package/frontend/dist/assets/{Dashboard--skdgzau.js → Dashboard-7XIOjzwF.js} +2 -2
- package/frontend/dist/assets/{Dashboard--skdgzau.js.map → Dashboard-7XIOjzwF.js.map} +1 -1
- package/frontend/dist/assets/EndpointCopy-CZVjSaZJ.js +2 -0
- package/frontend/dist/assets/{EndpointCopy-Dr-iLvMr.js.map → EndpointCopy-CZVjSaZJ.js.map} +1 -1
- package/frontend/dist/assets/{GroupsPage-CSIfAW7V.js → GroupsPage-BpHUw2qs.js} +3 -3
- package/frontend/dist/assets/{GroupsPage-CSIfAW7V.js.map → GroupsPage-BpHUw2qs.js.map} +1 -1
- package/frontend/dist/assets/{LoginPage-DwnZEV5T.js → LoginPage-ClSBXFLy.js} +2 -2
- package/frontend/dist/assets/{LoginPage-DwnZEV5T.js.map → LoginPage-ClSBXFLy.js.map} +1 -1
- package/frontend/dist/assets/{LogsPage-CTstXCQT.js → LogsPage-CVuaA5oX.js} +2 -2
- package/frontend/dist/assets/{LogsPage-CTstXCQT.js.map → LogsPage-CVuaA5oX.js.map} +1 -1
- package/frontend/dist/assets/{MarketPage-IbsIGvoI.js → MarketPage-BveXMx84.js} +2 -2
- package/frontend/dist/assets/{MarketPage-IbsIGvoI.js.map → MarketPage-BveXMx84.js.map} +1 -1
- package/frontend/dist/assets/{OAuthConsentPage-DawUB5r6.js → OAuthConsentPage-B7kOLjXw.js} +2 -2
- package/frontend/dist/assets/{OAuthConsentPage-DawUB5r6.js.map → OAuthConsentPage-B7kOLjXw.js.map} +1 -1
- package/frontend/dist/assets/{PromptsPage-Dim3CdKg.js → PromptsPage-DFH6bLuP.js} +2 -2
- package/frontend/dist/assets/{PromptsPage-Dim3CdKg.js.map → PromptsPage-DFH6bLuP.js.map} +1 -1
- package/frontend/dist/assets/{ResourcesPage-zcP5M9pv.js → ResourcesPage-Dsjlzp3t.js} +2 -2
- package/frontend/dist/assets/{ResourcesPage-zcP5M9pv.js.map → ResourcesPage-Dsjlzp3t.js.map} +1 -1
- package/frontend/dist/assets/ServersPage-BXEDxyLa.js +39 -0
- package/frontend/dist/assets/ServersPage-BXEDxyLa.js.map +1 -0
- package/frontend/dist/assets/{SettingsPage-CoAt9Wha.js → SettingsPage-B1zU66rQ.js} +2 -2
- package/frontend/dist/assets/{SettingsPage-CoAt9Wha.js.map → SettingsPage-B1zU66rQ.js.map} +1 -1
- package/frontend/dist/assets/{StatusDot-nh6JXzfm.js → StatusDot-Cp0G2KUZ.js} +2 -2
- package/frontend/dist/assets/{StatusDot-nh6JXzfm.js.map → StatusDot-Cp0G2KUZ.js.map} +1 -1
- package/frontend/dist/assets/{ToggleGroup-CeiSM3PL.js → ToggleGroup-BoQ324GW.js} +2 -2
- package/frontend/dist/assets/{ToggleGroup-CeiSM3PL.js.map → ToggleGroup-BoQ324GW.js.map} +1 -1
- package/frontend/dist/assets/{UsersPage-DLq5cgUz.js → UsersPage-B27T44p9.js} +2 -2
- package/frontend/dist/assets/{UsersPage-DLq5cgUz.js.map → UsersPage-B27T44p9.js.map} +1 -1
- package/frontend/dist/assets/{clipboard-CmkeMDp6.js → clipboard-ChKqfVYn.js} +2 -2
- package/frontend/dist/assets/{clipboard-CmkeMDp6.js.map → clipboard-ChKqfVYn.js.map} +1 -1
- package/frontend/dist/assets/{icons-vendor-CEwDZ1lN.js → icons-vendor-CkcU1LMA.js} +2 -2
- package/frontend/dist/assets/{icons-vendor-CEwDZ1lN.js.map → icons-vendor-CkcU1LMA.js.map} +1 -1
- package/frontend/dist/assets/index-dQlYMTl9.js +3 -0
- package/frontend/dist/assets/{index-dacgBASO.js.map → index-dQlYMTl9.js.map} +1 -1
- package/frontend/dist/assets/{resourceService-Nk5qQbQ0.js → resourceService-CmNa5REv.js} +2 -2
- package/frontend/dist/assets/{resourceService-Nk5qQbQ0.js.map → resourceService-CmNa5REv.js.map} +1 -1
- package/frontend/dist/assets/{useCostData-BgXlKVml.js → useCostData-BrV9Dqf7.js} +2 -2
- package/frontend/dist/assets/{useCostData-BgXlKVml.js.map → useCostData-BrV9Dqf7.js.map} +1 -1
- package/frontend/dist/assets/variableDetection-sA1AjKyF.js +16 -0
- package/frontend/dist/assets/variableDetection-sA1AjKyF.js.map +1 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/EndpointCopy-Dr-iLvMr.js +0 -2
- package/frontend/dist/assets/ServersPage-CxucQkR-.js +0 -39
- package/frontend/dist/assets/ServersPage-CxucQkR-.js.map +0 -1
- package/frontend/dist/assets/index-dacgBASO.js +0 -3
- package/frontend/dist/assets/variableDetection-CUnJ5xdC.js +0 -16
- package/frontend/dist/assets/variableDetection-CUnJ5xdC.js.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasPersonalOAuth, PersonalOAuthContext, personalOAuthContexts, personalAuthorizationRequired, } from './personalOAuthService.js';
|
|
1
2
|
import { preserveListFreshness, combineListFreshness, remainingListTtl, } from '../utils/listFreshness.js';
|
|
2
3
|
import { expandServerConfig, getServerEnvironment } from './serverConfigEnvironment.js';
|
|
3
4
|
import { getMcpRequestGroup } from '../utils/mcpRequestGroup.js';
|
|
@@ -10,7 +11,7 @@ import { canServeToolRequests } from '../utils/serverAvailability.js';
|
|
|
10
11
|
import { PrincipalRuntimeService } from './principalRuntimeService.js';
|
|
11
12
|
import { UserContextService } from './userContextService.js';
|
|
12
13
|
import { deleteCredentialBindings, missingCredentialError } from './credentialBindingService.js';
|
|
13
|
-
import { CredentialBindingError, hasCredentialTemplate, validateCredentialTemplate, } from '../utils/credentialTemplate.js';
|
|
14
|
+
import { CredentialBindingError, hasCredentialTemplate, hasPersonalAuthentication, resolveCredentialArguments, validateCredentialTemplate, } from '../utils/credentialTemplate.js';
|
|
14
15
|
import os from 'os';
|
|
15
16
|
import path from 'path';
|
|
16
17
|
import fs from 'fs';
|
|
@@ -172,13 +173,13 @@ const sanitizeArgs = (args) => {
|
|
|
172
173
|
return args.map((arg) => {
|
|
173
174
|
// Remove shell metacharacters that could be used for injection
|
|
174
175
|
// Allow common flags (-f, --flag, -vvv) and paths
|
|
175
|
-
if (/^[a-zA-Z0-9._/\\:@=+,-]+$/.test(arg)) {
|
|
176
|
+
if (/^[a-zA-Z0-9._/\\:@=+,-]+$/.test(arg.replace(/\{\{args\.[A-Za-z_][A-Za-z0-9_]*\}\}/g, 'slot'))) {
|
|
176
177
|
return arg;
|
|
177
178
|
}
|
|
178
179
|
// For arguments with special characters, log a warning
|
|
179
180
|
// Also block redirection operators (>, <, >>) and newlines
|
|
180
181
|
logger.warn(`[proxychains] Potentially unsafe argument blocked: ${arg}`);
|
|
181
|
-
return arg.replace(
|
|
182
|
+
return arg.replace(/\{\{args\.[A-Za-z_][A-Za-z0-9_]*\}\}|[;&|`$(){}[\]!><\n\r]/g, (match) => match.startsWith('{{args.') ? match : '');
|
|
182
183
|
});
|
|
183
184
|
};
|
|
184
185
|
/**
|
|
@@ -908,7 +909,7 @@ export const summarizeServerConnections = (infos) => {
|
|
|
908
909
|
};
|
|
909
910
|
export const getServerConnectionStats = () => {
|
|
910
911
|
// Personal definitions have no shared connection whose readiness can be checked.
|
|
911
|
-
return summarizeServerConnections(serverInfos.filter((info) => !
|
|
912
|
+
return summarizeServerConnections(serverInfos.filter((info) => !hasPersonalAuthentication(info.config)));
|
|
912
913
|
};
|
|
913
914
|
// Returns true if all enabled servers are connected
|
|
914
915
|
export const connected = () => {
|
|
@@ -1207,7 +1208,7 @@ export const createTransportFromConfig = async (name, conf) => {
|
|
|
1207
1208
|
if (conf.type === 'streamable-http') {
|
|
1208
1209
|
const options = {};
|
|
1209
1210
|
let headers = conf.headers
|
|
1210
|
-
?
|
|
1211
|
+
? hasPersonalAuthentication(conf)
|
|
1211
1212
|
? conf.headers
|
|
1212
1213
|
: replaceEnvVars(conf.headers, env)
|
|
1213
1214
|
: {};
|
|
@@ -1233,14 +1234,15 @@ export const createTransportFromConfig = async (name, conf) => {
|
|
|
1233
1234
|
headers,
|
|
1234
1235
|
};
|
|
1235
1236
|
}
|
|
1236
|
-
options.fetch =
|
|
1237
|
+
options.fetch =
|
|
1238
|
+
personalOAuthContexts.get(conf)?.wrapFetch(requestAwareFetch) || requestAwareFetch;
|
|
1237
1239
|
transport = new StreamableHTTPClientTransport(new URL(conf.url || ''), options);
|
|
1238
1240
|
}
|
|
1239
1241
|
else if (conf.url) {
|
|
1240
1242
|
// SSE transport
|
|
1241
1243
|
const options = {};
|
|
1242
1244
|
let headers = conf.headers
|
|
1243
|
-
?
|
|
1245
|
+
? hasPersonalAuthentication(conf)
|
|
1244
1246
|
? conf.headers
|
|
1245
1247
|
: replaceEnvVars(conf.headers, env)
|
|
1246
1248
|
: {};
|
|
@@ -1259,7 +1261,7 @@ export const createTransportFromConfig = async (name, conf) => {
|
|
|
1259
1261
|
if (Object.keys(headers).length > 0) {
|
|
1260
1262
|
options.eventSourceInit = {
|
|
1261
1263
|
headers,
|
|
1262
|
-
fetch: requestAwareFetch,
|
|
1264
|
+
fetch: personalOAuthContexts.get(conf)?.wrapFetch(requestAwareFetch) || requestAwareFetch,
|
|
1263
1265
|
};
|
|
1264
1266
|
options.requestInit = {
|
|
1265
1267
|
headers,
|
|
@@ -1267,10 +1269,11 @@ export const createTransportFromConfig = async (name, conf) => {
|
|
|
1267
1269
|
}
|
|
1268
1270
|
else {
|
|
1269
1271
|
options.eventSourceInit = {
|
|
1270
|
-
fetch: requestAwareFetch,
|
|
1272
|
+
fetch: personalOAuthContexts.get(conf)?.wrapFetch(requestAwareFetch) || requestAwareFetch,
|
|
1271
1273
|
};
|
|
1272
1274
|
}
|
|
1273
|
-
options.fetch =
|
|
1275
|
+
options.fetch =
|
|
1276
|
+
personalOAuthContexts.get(conf)?.wrapFetch(requestAwareFetch) || requestAwareFetch;
|
|
1274
1277
|
transport = new SSEClientTransport(new URL(conf.url), options);
|
|
1275
1278
|
}
|
|
1276
1279
|
else if (conf.command) {
|
|
@@ -1305,12 +1308,13 @@ export const createTransportFromConfig = async (name, conf) => {
|
|
|
1305
1308
|
transport = new StdioClientTransport({
|
|
1306
1309
|
cwd: process.cwd(),
|
|
1307
1310
|
command: finalCommand,
|
|
1308
|
-
|
|
1311
|
+
// Substitute after expansion and proxy argument cleaning so secrets stay literal.
|
|
1312
|
+
args: resolveCredentialArguments(conf, finalArgs),
|
|
1309
1313
|
env: env,
|
|
1310
1314
|
stderr: 'pipe',
|
|
1311
1315
|
maxBufferSize: validateMaxBufferSize(conf.options?.maxBufferSize),
|
|
1312
1316
|
});
|
|
1313
|
-
if (transport.stderr &&
|
|
1317
|
+
if (transport.stderr && hasPersonalAuthentication(conf)) {
|
|
1314
1318
|
transport.stderr.on('data', () => undefined); // Personal upstream diagnostics can contain raw credentials.
|
|
1315
1319
|
}
|
|
1316
1320
|
else if (transport.stderr) {
|
|
@@ -1337,7 +1341,7 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
|
|
|
1337
1341
|
if (!client) {
|
|
1338
1342
|
throw new Error(`Client not found for server: ${serverInfo.name}`);
|
|
1339
1343
|
}
|
|
1340
|
-
if (
|
|
1344
|
+
if (hasPersonalAuthentication(serverInfo.config)) {
|
|
1341
1345
|
return client.callTool(toolParams, options || {});
|
|
1342
1346
|
}
|
|
1343
1347
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
@@ -1514,7 +1518,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
|
|
|
1514
1518
|
});
|
|
1515
1519
|
continue;
|
|
1516
1520
|
}
|
|
1517
|
-
if (
|
|
1521
|
+
if (hasPersonalAuthentication(expandedConf)) {
|
|
1518
1522
|
validateCredentialTemplate(expandedConf);
|
|
1519
1523
|
nextServerInfos.push({
|
|
1520
1524
|
name,
|
|
@@ -2023,15 +2027,26 @@ export const getServersInfo = async (page, limit, user) => {
|
|
|
2023
2027
|
const principal = (user ||
|
|
2024
2028
|
UserContextService.getInstance().getCurrentUser());
|
|
2025
2029
|
const presentedInfos = await Promise.all(filterServerInfos.map(async (info) => {
|
|
2026
|
-
if (!
|
|
2030
|
+
if (!hasPersonalAuthentication(info.config) ||
|
|
2027
2031
|
info.enabled === false ||
|
|
2028
2032
|
!principal?.username ||
|
|
2029
2033
|
principal.credentialEligible === false)
|
|
2030
2034
|
return info;
|
|
2035
|
+
let ownOAuth;
|
|
2031
2036
|
try {
|
|
2037
|
+
const personal = hasPersonalOAuth(info.config)
|
|
2038
|
+
? await PersonalOAuthContext.load(info.name, principal.username, (await getServerDao().findById(info.name)))
|
|
2039
|
+
: undefined;
|
|
2040
|
+
ownOAuth = personal
|
|
2041
|
+
? {
|
|
2042
|
+
identityMode: 'personal',
|
|
2043
|
+
connected: Boolean(personal.config.oauth.accessToken),
|
|
2044
|
+
authorizationUrl: personal.authorizationUrl,
|
|
2045
|
+
}
|
|
2046
|
+
: undefined;
|
|
2032
2047
|
const lease = await principalRuntimes.acquire(info.name, principal);
|
|
2033
2048
|
try {
|
|
2034
|
-
return { ...lease.info, config: info.config };
|
|
2049
|
+
return { ...lease.info, config: info.config, ...(ownOAuth ? { oauth: ownOAuth } : {}) };
|
|
2035
2050
|
}
|
|
2036
2051
|
finally {
|
|
2037
2052
|
lease.release();
|
|
@@ -2040,6 +2055,7 @@ export const getServersInfo = async (page, limit, user) => {
|
|
|
2040
2055
|
catch (error) {
|
|
2041
2056
|
return {
|
|
2042
2057
|
...info,
|
|
2058
|
+
...(ownOAuth ? { status: 'oauth_required', oauth: ownOAuth } : {}),
|
|
2043
2059
|
error: error instanceof CredentialBindingError || error instanceof UpstreamRequestError
|
|
2044
2060
|
? error.message
|
|
2045
2061
|
: 'Unable to prepare upstream server',
|
|
@@ -2102,6 +2118,8 @@ export const getServersInfo = async (page, limit, user) => {
|
|
|
2102
2118
|
? {
|
|
2103
2119
|
...(oauth
|
|
2104
2120
|
? {
|
|
2121
|
+
...(oauth.identityMode ? { identityMode: oauth.identityMode } : {}),
|
|
2122
|
+
...(oauth.connected ? { connected: true } : {}),
|
|
2105
2123
|
authorizationUrl: oauth.authorizationUrl,
|
|
2106
2124
|
state: oauth.state,
|
|
2107
2125
|
...(oauth.clientIdConfigured ? { clientIdConfigured: true } : {}),
|
|
@@ -2114,9 +2132,12 @@ export const getServersInfo = async (page, limit, user) => {
|
|
|
2114
2132
|
config: resolvedType || serverConfig?.description || serverConfig?.command
|
|
2115
2133
|
? {
|
|
2116
2134
|
...(resolvedType ? { type: resolvedType } : {}),
|
|
2117
|
-
...(
|
|
2135
|
+
...(hasPersonalAuthentication(serverConfig)
|
|
2118
2136
|
? { credentialTemplate: validateCredentialTemplate(serverConfig) }
|
|
2119
2137
|
: {}),
|
|
2138
|
+
...(hasPersonalOAuth(serverConfig)
|
|
2139
|
+
? { oauth: { identityMode: 'personal' } }
|
|
2140
|
+
: {}),
|
|
2120
2141
|
...(serverConfig?.description ? { description: serverConfig.description } : {}),
|
|
2121
2142
|
// Expose command so the frontend can determine if reinstall is
|
|
2122
2143
|
// supported (npx/uvx only). This is not a secret — it's the
|
|
@@ -2506,7 +2527,7 @@ const scheduleIdleShutdown = (serverInfo) => {
|
|
|
2506
2527
|
clearIdleShutdown(serverInfo);
|
|
2507
2528
|
if (!serverInfo.config?.startOnDemand ||
|
|
2508
2529
|
!isStdioServer(serverInfo.config) ||
|
|
2509
|
-
|
|
2530
|
+
hasPersonalAuthentication(serverInfo.config) ||
|
|
2510
2531
|
serverInfo.status !== 'connected' ||
|
|
2511
2532
|
!serverInfo.client ||
|
|
2512
2533
|
serverInfo.activeToolCalls) {
|
|
@@ -2522,7 +2543,7 @@ const scheduleIdleShutdown = (serverInfo) => {
|
|
|
2522
2543
|
const beginOnDemandToolCall = (serverInfo) => {
|
|
2523
2544
|
if (!serverInfo.config?.startOnDemand ||
|
|
2524
2545
|
!isStdioServer(serverInfo.config) ||
|
|
2525
|
-
|
|
2546
|
+
hasPersonalAuthentication(serverInfo.config)) {
|
|
2526
2547
|
return undefined;
|
|
2527
2548
|
}
|
|
2528
2549
|
clearIdleShutdown(serverInfo);
|
|
@@ -2670,7 +2691,7 @@ const isStdioServer = (conf) => !!conf && (conf.type === 'stdio' || (!conf.type
|
|
|
2670
2691
|
*/
|
|
2671
2692
|
const primeOnDemandServers = (gate) => {
|
|
2672
2693
|
const targets = serverInfos.filter((si) => si.config?.startOnDemand === true &&
|
|
2673
|
-
!
|
|
2694
|
+
!hasPersonalAuthentication(si.config) &&
|
|
2674
2695
|
isStdioServer(si.config) &&
|
|
2675
2696
|
si.tools.length === 0 &&
|
|
2676
2697
|
si.enabled !== false);
|
|
@@ -2878,12 +2899,17 @@ const getMcpAppsRouteContext = async (sessionId, group) => {
|
|
|
2878
2899
|
const eligible = (pinned?.pinnedServerInfos ?? []).filter(canServeToolRequests);
|
|
2879
2900
|
return eligible.length > 0 ? { enabled: true, serverInfos: eligible } : { enabled: false };
|
|
2880
2901
|
}
|
|
2881
|
-
const { filteredServerInfos } = await getFilteredServerInfosForGroup(group);
|
|
2902
|
+
const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
|
|
2882
2903
|
const connectedServerInfos = filteredServerInfos.filter((serverInfo) => serverInfo.status === 'connected' && !!serverInfo.client);
|
|
2883
2904
|
if (connectedServerInfos.length === 0) {
|
|
2884
2905
|
return { enabled: false };
|
|
2885
2906
|
}
|
|
2886
|
-
|
|
2907
|
+
// Connection availability must not turn an aggregate route into a raw-name
|
|
2908
|
+
// route: OpenAPI uses openApiClient, and tool calls acquire only the target
|
|
2909
|
+
// personal runtime. Match resolveToolInGroup's configured-route ambiguity check.
|
|
2910
|
+
if (connectedServerInfos.length === 1 &&
|
|
2911
|
+
filteredServerInfos.length === 1 &&
|
|
2912
|
+
serverConfigsByName.size <= 1) {
|
|
2887
2913
|
return {
|
|
2888
2914
|
enabled: true,
|
|
2889
2915
|
serverInfo: connectedServerInfos[0],
|
|
@@ -3023,7 +3049,7 @@ const projectToolForDownstream = (serverName, tool, appsRouteContext, serverConf
|
|
|
3023
3049
|
return undefined;
|
|
3024
3050
|
}
|
|
3025
3051
|
const projectedTool = appsRouteContext.enabled ? tool : stripMcpAppsMetadata(tool);
|
|
3026
|
-
// Single
|
|
3052
|
+
// Single configured server: keep the existing raw (unqualified) name so
|
|
3027
3053
|
// widgets that call back with the bare tool name continue to work.
|
|
3028
3054
|
if (appsRouteContext.serverInfo) {
|
|
3029
3055
|
return {
|
|
@@ -3031,7 +3057,7 @@ const projectToolForDownstream = (serverName, tool, appsRouteContext, serverConf
|
|
|
3031
3057
|
name: normalizeToolNameForServer(serverName, projectedTool.name),
|
|
3032
3058
|
};
|
|
3033
3059
|
}
|
|
3034
|
-
//
|
|
3060
|
+
// Aggregate routes (Apps-enabled) or Apps disabled: qualify the
|
|
3035
3061
|
// name so it can still be routed to the right server unambiguously.
|
|
3036
3062
|
return {
|
|
3037
3063
|
...projectedTool,
|
|
@@ -3333,7 +3359,7 @@ const handleCallToolRequestImpl = async (request, extra) => {
|
|
|
3333
3359
|
keyId,
|
|
3334
3360
|
keyName,
|
|
3335
3361
|
sourceIp,
|
|
3336
|
-
hasCredentialTemplate:
|
|
3362
|
+
hasCredentialTemplate: hasPersonalAuthentication(targetServerInfo.config),
|
|
3337
3363
|
});
|
|
3338
3364
|
return await maybeCompressToolResult({
|
|
3339
3365
|
content: [
|
|
@@ -3352,7 +3378,7 @@ const handleCallToolRequestImpl = async (request, extra) => {
|
|
|
3352
3378
|
// For servers with perSessionClient: true, use a per-session dedicated client
|
|
3353
3379
|
let isolatedCtx;
|
|
3354
3380
|
if (targetServerInfo.config?.perSessionClient &&
|
|
3355
|
-
!
|
|
3381
|
+
!hasPersonalAuthentication(targetServerInfo.config)) {
|
|
3356
3382
|
const stateId = getToolStateId(targetServerInfo, sessionId);
|
|
3357
3383
|
if (!stateId) {
|
|
3358
3384
|
throw new Error(`Server '${targetServerInfo.name}' requires per-session client isolation and is not yet available on stateless MCP requests`);
|
|
@@ -3405,7 +3431,7 @@ const handleCallToolRequestImpl = async (request, extra) => {
|
|
|
3405
3431
|
keyId,
|
|
3406
3432
|
keyName,
|
|
3407
3433
|
sourceIp,
|
|
3408
|
-
hasCredentialTemplate:
|
|
3434
|
+
hasCredentialTemplate: hasPersonalAuthentication(targetServerInfo.config),
|
|
3409
3435
|
errorMessage: result.isError ? 'Tool returned error response' : undefined,
|
|
3410
3436
|
});
|
|
3411
3437
|
return await maybeCompressToolResult(result, {
|
|
@@ -3512,7 +3538,7 @@ const handleCallToolRequestImpl = async (request, extra) => {
|
|
|
3512
3538
|
keyId,
|
|
3513
3539
|
keyName,
|
|
3514
3540
|
sourceIp,
|
|
3515
|
-
hasCredentialTemplate:
|
|
3541
|
+
hasCredentialTemplate: hasPersonalAuthentication(serverInfo.config),
|
|
3516
3542
|
});
|
|
3517
3543
|
return await maybeCompressToolResult({
|
|
3518
3544
|
content: [
|
|
@@ -3530,7 +3556,7 @@ const handleCallToolRequestImpl = async (request, extra) => {
|
|
|
3530
3556
|
// Handle MCP servers
|
|
3531
3557
|
// For servers with perSessionClient: true, use a per-session dedicated client
|
|
3532
3558
|
let isolatedCtx;
|
|
3533
|
-
if (serverInfo.config?.perSessionClient && !
|
|
3559
|
+
if (serverInfo.config?.perSessionClient && !hasPersonalAuthentication(serverInfo.config)) {
|
|
3534
3560
|
const stateId = getToolStateId(serverInfo, sessionId);
|
|
3535
3561
|
if (!stateId) {
|
|
3536
3562
|
throw new Error(`Server '${serverInfo.name}' requires per-session client isolation and is not yet available on stateless MCP requests`);
|
|
@@ -3568,7 +3594,7 @@ const handleCallToolRequestImpl = async (request, extra) => {
|
|
|
3568
3594
|
keyId,
|
|
3569
3595
|
keyName,
|
|
3570
3596
|
sourceIp,
|
|
3571
|
-
hasCredentialTemplate:
|
|
3597
|
+
hasCredentialTemplate: hasPersonalAuthentication(serverInfo.config),
|
|
3572
3598
|
errorMessage: result.isError ? 'Tool returned error response' : undefined,
|
|
3573
3599
|
});
|
|
3574
3600
|
return await maybeCompressToolResult(result, {
|
|
@@ -3607,7 +3633,7 @@ const handleCallToolRequestImpl = async (request, extra) => {
|
|
|
3607
3633
|
keyId,
|
|
3608
3634
|
keyName,
|
|
3609
3635
|
sourceIp,
|
|
3610
|
-
hasCredentialTemplate:
|
|
3636
|
+
hasCredentialTemplate: hasPersonalAuthentication(serverInfo?.config),
|
|
3611
3637
|
// The reason is an internal diagnostic; the caller-facing message stays
|
|
3612
3638
|
// identical for hidden and nonexistent targets.
|
|
3613
3639
|
errorMessage: unavailable
|
|
@@ -4068,6 +4094,9 @@ const createPrincipalRuntime = async (name, resolvedConfig) => {
|
|
|
4068
4094
|
}
|
|
4069
4095
|
catch (error) {
|
|
4070
4096
|
closeServerRuntime(info);
|
|
4097
|
+
if (hasPersonalOAuth(resolvedConfig) &&
|
|
4098
|
+
personalOAuthContexts.get(resolvedConfig)?.config.oauth.pendingAuthorization)
|
|
4099
|
+
throw personalAuthorizationRequired(name);
|
|
4071
4100
|
const failure = createUpstreamRequestError(name, 'connect', error, resolvedConfig);
|
|
4072
4101
|
logger.error(failure.message);
|
|
4073
4102
|
throw failure;
|
|
@@ -4097,7 +4126,7 @@ export const getServerToolsForPrincipal = async (serverName, principal) => {
|
|
|
4097
4126
|
}
|
|
4098
4127
|
};
|
|
4099
4128
|
const withPrincipalServers = (handler, operation) => async (request, extra) => {
|
|
4100
|
-
if (!serverInfos.some((info) =>
|
|
4129
|
+
if (!serverInfos.some((info) => hasPersonalAuthentication(info.config)))
|
|
4101
4130
|
return handler(request, extra);
|
|
4102
4131
|
// Built-in content does not require an upstream credential.
|
|
4103
4132
|
if (operation === 'prompt') {
|
|
@@ -4134,7 +4163,7 @@ const withPrincipalServers = (handler, operation) => async (request, extra) => {
|
|
|
4134
4163
|
}
|
|
4135
4164
|
// A call to a known target must not spawn or keep unrelated personal children alive.
|
|
4136
4165
|
const selected = target && (operation === 'tool' || operation === 'prompt') ? [target] : candidates;
|
|
4137
|
-
const templated = selected.filter((info) =>
|
|
4166
|
+
const templated = selected.filter((info) => hasPersonalAuthentication(info.config));
|
|
4138
4167
|
if (!templated.length)
|
|
4139
4168
|
return handler(request, extra);
|
|
4140
4169
|
const user = UserContextService.getInstance().getCurrentUser();
|
|
@@ -4147,7 +4176,9 @@ const withPrincipalServers = (handler, operation) => async (request, extra) => {
|
|
|
4147
4176
|
if (!user?.username ||
|
|
4148
4177
|
user.credentialEligible === false ||
|
|
4149
4178
|
RequestContextService.getInstance().getKeyKindContext() === 'system')
|
|
4150
|
-
throw
|
|
4179
|
+
throw hasPersonalOAuth(info.config)
|
|
4180
|
+
? personalAuthorizationRequired(info.name)
|
|
4181
|
+
: missingCredentialError(info.name);
|
|
4151
4182
|
const lease = await principalRuntimes.acquire(info.name, user);
|
|
4152
4183
|
releases.push(lease.release);
|
|
4153
4184
|
scoped.set(info.name, lease.info);
|
|
@@ -4176,7 +4207,7 @@ const withPrincipalServers = (handler, operation) => async (request, extra) => {
|
|
|
4176
4207
|
let resolvedRequest = request;
|
|
4177
4208
|
if (operation === 'tool' &&
|
|
4178
4209
|
extra?.server &&
|
|
4179
|
-
|
|
4210
|
+
hasPersonalAuthentication(scoped.get(extra.server)?.config)) {
|
|
4180
4211
|
const prefix = `${extra.server}${getNameSeparator()}`;
|
|
4181
4212
|
if (typeof requestedName === 'string' && !requestedName.startsWith(prefix)) {
|
|
4182
4213
|
resolvedRequest =
|