@samanhappy/mcphub 1.2.2 → 1.2.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/dist/clients/legacyMcpClient.js +27 -5
- package/dist/clients/legacyMcpClient.js.map +1 -1
- package/dist/clients/openapi.js +2 -1
- package/dist/clients/openapi.js.map +1 -1
- package/dist/controllers/bearerKeyController.js +50 -2
- package/dist/controllers/bearerKeyController.js.map +1 -1
- package/dist/db/connection.js +22 -10
- package/dist/db/connection.js.map +1 -1
- package/dist/middlewares/index.js +3 -0
- package/dist/middlewares/index.js.map +1 -1
- package/dist/services/mcpService.js +131 -5
- package/dist/services/mcpService.js.map +1 -1
- package/dist/services/personalOAuthService.js +3 -1
- package/dist/services/personalOAuthService.js.map +1 -1
- package/dist/services/sseService.js +7 -5
- package/dist/services/sseService.js.map +1 -1
- package/dist/utils/openApiSchema.js +24 -2
- package/dist/utils/openApiSchema.js.map +1 -1
- package/dist/utils/resourceTemplateMatch.js +229 -0
- package/dist/utils/resourceTemplateMatch.js.map +1 -0
- package/dist/utils/upstreamError.js +10 -1
- package/dist/utils/upstreamError.js.map +1 -1
- package/frontend/dist/assets/{ActivityPage-CB41zWix.js → ActivityPage-B-5D-enT.js} +2 -2
- package/frontend/dist/assets/{ActivityPage-CB41zWix.js.map → ActivityPage-B-5D-enT.js.map} +1 -1
- package/frontend/dist/assets/{CredentialsPage-BecFRfro.js → CredentialsPage-C3YstYEp.js} +2 -2
- package/frontend/dist/assets/{CredentialsPage-BecFRfro.js.map → CredentialsPage-C3YstYEp.js.map} +1 -1
- package/frontend/dist/assets/{Dashboard-CwifBwm4.js → Dashboard-DKUuRSCZ.js} +2 -2
- package/frontend/dist/assets/{Dashboard-CwifBwm4.js.map → Dashboard-DKUuRSCZ.js.map} +1 -1
- package/frontend/dist/assets/{EndpointCopy-BeLf_ow_.js → EndpointCopy-C99H0jkU.js} +2 -2
- package/frontend/dist/assets/{EndpointCopy-BeLf_ow_.js.map → EndpointCopy-C99H0jkU.js.map} +1 -1
- package/frontend/dist/assets/{GroupsPage-DLL1tFj8.js → GroupsPage-Bn2gDfFV.js} +2 -2
- package/frontend/dist/assets/{GroupsPage-DLL1tFj8.js.map → GroupsPage-Bn2gDfFV.js.map} +1 -1
- package/frontend/dist/assets/{LoginPage-DvHL1HNV.js → LoginPage-B1WWybhi.js} +2 -2
- package/frontend/dist/assets/{LoginPage-DvHL1HNV.js.map → LoginPage-B1WWybhi.js.map} +1 -1
- package/frontend/dist/assets/{LogsPage-Bva6Y11K.js → LogsPage-DVwiH4ho.js} +2 -2
- package/frontend/dist/assets/{LogsPage-Bva6Y11K.js.map → LogsPage-DVwiH4ho.js.map} +1 -1
- package/frontend/dist/assets/{MarketPage-DGxNuifk.js → MarketPage-Cf1OqMnz.js} +2 -2
- package/frontend/dist/assets/{MarketPage-DGxNuifk.js.map → MarketPage-Cf1OqMnz.js.map} +1 -1
- package/frontend/dist/assets/{OAuthApprovalsPage-TzVNIJ27.js → OAuthApprovalsPage-BLoVtvRl.js} +2 -2
- package/frontend/dist/assets/{OAuthApprovalsPage-TzVNIJ27.js.map → OAuthApprovalsPage-BLoVtvRl.js.map} +1 -1
- package/frontend/dist/assets/{OAuthConsentPage-yQSgIbZ8.js → OAuthConsentPage-7_LBmL5u.js} +2 -2
- package/frontend/dist/assets/{OAuthConsentPage-yQSgIbZ8.js.map → OAuthConsentPage-7_LBmL5u.js.map} +1 -1
- package/frontend/dist/assets/{PromptsPage-CIAVpo9B.js → PromptsPage-Bq4knOKi.js} +2 -2
- package/frontend/dist/assets/{PromptsPage-CIAVpo9B.js.map → PromptsPage-Bq4knOKi.js.map} +1 -1
- package/frontend/dist/assets/{ResourcesPage-C5g-98tB.js → ResourcesPage-ez2BUPiH.js} +2 -2
- package/frontend/dist/assets/{ResourcesPage-C5g-98tB.js.map → ResourcesPage-ez2BUPiH.js.map} +1 -1
- package/frontend/dist/assets/{ServersPage-CJEgBQr-.js → ServersPage-CxRwhpMG.js} +4 -4
- package/frontend/dist/assets/{ServersPage-CJEgBQr-.js.map → ServersPage-CxRwhpMG.js.map} +1 -1
- package/frontend/dist/assets/SettingsPage-BmO2EBiV.js +14 -0
- package/frontend/dist/assets/SettingsPage-BmO2EBiV.js.map +1 -0
- package/frontend/dist/assets/{StatusDot-B81gkUBo.js → StatusDot-DXos2bR8.js} +2 -2
- package/frontend/dist/assets/{StatusDot-B81gkUBo.js.map → StatusDot-DXos2bR8.js.map} +1 -1
- package/frontend/dist/assets/{ToggleGroup-DZ39486g.js → ToggleGroup-B0y_76Jq.js} +2 -2
- package/frontend/dist/assets/{ToggleGroup-DZ39486g.js.map → ToggleGroup-B0y_76Jq.js.map} +1 -1
- package/frontend/dist/assets/{UsersPage-pdkVxTsL.js → UsersPage-Cr44mPCW.js} +2 -2
- package/frontend/dist/assets/{UsersPage-pdkVxTsL.js.map → UsersPage-Cr44mPCW.js.map} +1 -1
- package/frontend/dist/assets/{clipboard-BYlVdxdy.js → clipboard-ydtgZGEU.js} +2 -2
- package/frontend/dist/assets/{clipboard-BYlVdxdy.js.map → clipboard-ydtgZGEU.js.map} +1 -1
- package/frontend/dist/assets/{costRevision-D-WY0B73.js → costRevision-Ba21UTQz.js} +2 -2
- package/frontend/dist/assets/{costRevision-D-WY0B73.js.map → costRevision-Ba21UTQz.js.map} +1 -1
- package/frontend/dist/assets/index-BP8nhViQ.js +3 -0
- package/frontend/dist/assets/{index-DKX_BExz.js.map → index-BP8nhViQ.js.map} +1 -1
- package/frontend/dist/assets/{resourceService-CKAaXBkJ.js → resourceService-WakoRIC8.js} +2 -2
- package/frontend/dist/assets/{resourceService-CKAaXBkJ.js.map → resourceService-WakoRIC8.js.map} +1 -1
- package/frontend/dist/assets/{useCostData-DzjFGnw4.js → useCostData-Dn8-UX9u.js} +2 -2
- package/frontend/dist/assets/{useCostData-DzjFGnw4.js.map → useCostData-Dn8-UX9u.js.map} +1 -1
- package/frontend/dist/assets/{variableDetection-D75LR7nZ.js → variableDetection-D5HFb-qE.js} +2 -2
- package/frontend/dist/assets/{variableDetection-D75LR7nZ.js.map → variableDetection-D5HFb-qE.js.map} +1 -1
- package/frontend/dist/index.html +1 -1
- package/package.json +2 -2
- package/frontend/dist/assets/SettingsPage-Bub-g0mD.js +0 -14
- package/frontend/dist/assets/SettingsPage-Bub-g0mD.js.map +0 -1
- package/frontend/dist/assets/index-DKX_BExz.js +0 -3
|
@@ -5,7 +5,7 @@ import { withReplicaLock } from './replicaLock.js';
|
|
|
5
5
|
import { projectGroupToolName, assertValidGroupToolNames } from '../utils/groupToolNames.js';
|
|
6
6
|
import { isSafePassthroughHeader } from '../utils/passthroughHeaders.js';
|
|
7
7
|
import { observeToolCall, setMetricsServer } from './metricsService.js';
|
|
8
|
-
import { hasPersonalOAuth, PersonalOAuthContext, personalOAuthContexts, personalAuthorizationRequired, } from './personalOAuthService.js';
|
|
8
|
+
import { hasPersonalOAuth, PersonalOAuthContext, personalOAuthContexts, personalAuthorizationRequired, PersonalAuthorizationRequiredError, } from './personalOAuthService.js';
|
|
9
9
|
import { preserveListFreshness, combineListFreshness, remainingListTtl, } from '../utils/listFreshness.js';
|
|
10
10
|
import { expandServerConfig, getServerEnvironment } from './serverConfigEnvironment.js';
|
|
11
11
|
import { getMcpRequestGroup } from '../utils/mcpRequestGroup.js';
|
|
@@ -17,6 +17,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
|
|
|
17
17
|
import { createHash } from 'node:crypto';
|
|
18
18
|
import { canAccessGroupRoute } from '../utils/groupAccess.js';
|
|
19
19
|
import { canServeToolRequests } from '../utils/serverAvailability.js';
|
|
20
|
+
import { uriMatchesResourceTemplate } from '../utils/resourceTemplateMatch.js';
|
|
20
21
|
import { assertToolCallArgumentsSafe, isToolCallGuardEnabled, ToolCallBlockedError, } from '../middlewares/toolCallGuard.js';
|
|
21
22
|
import { applyHiddenParams, findToolConfig, removeHiddenArguments, } from '../utils/toolConfig.js';
|
|
22
23
|
import { prepareCapabilitySnapshot, recordCapabilitySnapshot, flushCapabilitySnapshot, } from './capabilitySnapshotService.js';
|
|
@@ -31,7 +32,7 @@ import treeKill from 'tree-kill';
|
|
|
31
32
|
import { isProcessTreeKillAvailable } from '../utils/processTree.js';
|
|
32
33
|
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
|
|
33
34
|
import { SSEClientTransport, StreamableHTTPClientTransport, } from '../clients/mcpSdkClient.js';
|
|
34
|
-
import { Server } from '@modelcontextprotocol/server';
|
|
35
|
+
import { ProtocolError, Server } from '@modelcontextprotocol/server';
|
|
35
36
|
import { createFetchWithProxy, getProxyConfigFromEnv } from './proxy.js';
|
|
36
37
|
import { assertSafeUrl, createRedirectValidatingFetch } from '../utils/ssrf.js';
|
|
37
38
|
import { createAbortIsolatingFetch } from '../utils/abortIsolatingFetch.js';
|
|
@@ -862,6 +863,29 @@ const updateServerPromptsCache = (serverInfo, prompts) => {
|
|
|
862
863
|
const updateServerResourcesCache = (serverInfo, resources) => {
|
|
863
864
|
serverInfo.resources = preserveListFreshness(resources, resources.map(normalizeResourceForCache));
|
|
864
865
|
};
|
|
866
|
+
const updateServerResourceTemplatesCache = (serverInfo, resourceTemplates) => {
|
|
867
|
+
serverInfo.resourceTemplates = Array.isArray(resourceTemplates) ? resourceTemplates : [];
|
|
868
|
+
};
|
|
869
|
+
// Best effort: many servers do not implement resources/templates/list.
|
|
870
|
+
const refreshServerResourceTemplatesCache = async (getTarget, client, options, owner = client) => {
|
|
871
|
+
try {
|
|
872
|
+
if (!client.getServerCapabilities?.()?.resources)
|
|
873
|
+
return undefined;
|
|
874
|
+
const { resourceTemplates } = await client.listResourceTemplates({}, options || {});
|
|
875
|
+
const target = getTarget();
|
|
876
|
+
if (target && target.client === owner) {
|
|
877
|
+
updateServerResourceTemplatesCache(target, resourceTemplates);
|
|
878
|
+
}
|
|
879
|
+
return resourceTemplates;
|
|
880
|
+
}
|
|
881
|
+
catch (error) {
|
|
882
|
+
logger.debug('Failed to list resource templates for server', {
|
|
883
|
+
serverName: getTarget()?.name,
|
|
884
|
+
error: summarizeErrorForLogging(error),
|
|
885
|
+
});
|
|
886
|
+
return undefined;
|
|
887
|
+
}
|
|
888
|
+
};
|
|
865
889
|
// Modern HTTP has no list-changed push stream. Refresh only the lists needed
|
|
866
890
|
// by the current request; TTL and in-flight work are scoped to the live client.
|
|
867
891
|
const refreshModernServerLists = async (info, fields) => {
|
|
@@ -983,6 +1007,10 @@ const createUpstreamMcpClient = (name, getServerInfo, serverConfig, downstream)
|
|
|
983
1007
|
}
|
|
984
1008
|
updateServerResourcesCache(serverInfo, resources);
|
|
985
1009
|
broadcastResourceListChanged();
|
|
1010
|
+
// Templates have no list-changed notification of their own.
|
|
1011
|
+
if (serverInfo.client) {
|
|
1012
|
+
void refreshServerResourceTemplatesCache(getServerInfo, serverInfo.client, serverInfo.options);
|
|
1013
|
+
}
|
|
986
1014
|
},
|
|
987
1015
|
},
|
|
988
1016
|
},
|
|
@@ -2111,6 +2139,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
|
|
|
2111
2139
|
});
|
|
2112
2140
|
}
|
|
2113
2141
|
if (capabilities?.resources) {
|
|
2142
|
+
void refreshServerResourceTemplatesCache(liveServerInfo, client, initRequestOptions || requestOptions);
|
|
2114
2143
|
client
|
|
2115
2144
|
.listResources({}, initRequestOptions || requestOptions)
|
|
2116
2145
|
.then((resources) => {
|
|
@@ -2910,6 +2939,7 @@ const ensureServerReady = async (serverInfo) => {
|
|
|
2910
2939
|
}
|
|
2911
2940
|
}
|
|
2912
2941
|
if (capabilities?.resources) {
|
|
2942
|
+
void refreshServerResourceTemplatesCache(() => serverInfo, client, requestOptions);
|
|
2913
2943
|
try {
|
|
2914
2944
|
const resources = await client.listResources({}, requestOptions);
|
|
2915
2945
|
resourceCount = resources.resources.length;
|
|
@@ -3004,6 +3034,7 @@ export const resetServerOAuthConnection = (name) => {
|
|
|
3004
3034
|
serverInfo.tools = [];
|
|
3005
3035
|
serverInfo.prompts = [];
|
|
3006
3036
|
serverInfo.resources = [];
|
|
3037
|
+
serverInfo.resourceTemplates = undefined;
|
|
3007
3038
|
serverInfo.oauth = undefined;
|
|
3008
3039
|
broadcastToolListChanged();
|
|
3009
3040
|
broadcastPromptListChanged();
|
|
@@ -4220,8 +4251,12 @@ const handleListResourceTemplatesRequestImpl = async (_, extra) => {
|
|
|
4220
4251
|
if (!serverInfo.client?.listResourceTemplates) {
|
|
4221
4252
|
return [];
|
|
4222
4253
|
}
|
|
4223
|
-
const
|
|
4254
|
+
const client = serverInfo.client;
|
|
4255
|
+
const templates = await client.listResourceTemplates({}, serverInfo.options || {});
|
|
4224
4256
|
snapshots.push(templates.resourceTemplates);
|
|
4257
|
+
if (serverInfo.client === client) {
|
|
4258
|
+
updateServerResourceTemplatesCache(serverInfo, templates.resourceTemplates);
|
|
4259
|
+
}
|
|
4225
4260
|
const filteredTemplates = await filterResourceTemplatesByGroup(lookupGroup, serverInfo.name, templates.resourceTemplates || [], serverConfigsByName.get(serverInfo.name));
|
|
4226
4261
|
return appsRouteContext.enabled
|
|
4227
4262
|
? filteredTemplates
|
|
@@ -4234,6 +4269,60 @@ const handleListResourceTemplatesRequestImpl = async (_, extra) => {
|
|
|
4234
4269
|
}
|
|
4235
4270
|
return { resourceTemplates };
|
|
4236
4271
|
};
|
|
4272
|
+
const TEMPLATE_LIST_RETRY_MS = 60000;
|
|
4273
|
+
const templateListFailedAt = new WeakMap();
|
|
4274
|
+
const getReadableResourceTemplates = async (serverInfo, uri, lookupGroup, groupServerConfig) => {
|
|
4275
|
+
const serverConfig = await getServerDao().findById(serverInfo.name);
|
|
4276
|
+
const groupConfig = await getGroupServerConfig(lookupGroup, serverInfo.name, groupServerConfig);
|
|
4277
|
+
if (serverConfig?.resources?.[uri]?.enabled === false ||
|
|
4278
|
+
(Array.isArray(groupConfig?.resources) && groupConfig.resources.length === 0)) {
|
|
4279
|
+
return [];
|
|
4280
|
+
}
|
|
4281
|
+
const client = serverInfo.client;
|
|
4282
|
+
// Modern upstreams have no list-changed stream, so honor the templates' own TTL.
|
|
4283
|
+
const modern = client?.getProtocolEra?.() === 'modern';
|
|
4284
|
+
if ((serverInfo.resourceTemplates === undefined || modern) &&
|
|
4285
|
+
serverInfo.status === 'connected' &&
|
|
4286
|
+
client &&
|
|
4287
|
+
(templateListFailedAt.get(client) ?? -Infinity) < Date.now() - TEMPLATE_LIST_RETRY_MS) {
|
|
4288
|
+
const refresh = async () => {
|
|
4289
|
+
const listed = await refreshServerResourceTemplatesCache(() => serverInfo, client, serverInfo.options);
|
|
4290
|
+
if (listed === undefined) {
|
|
4291
|
+
templateListFailedAt.set(client, Date.now());
|
|
4292
|
+
}
|
|
4293
|
+
};
|
|
4294
|
+
await (modern ? revalidateUpstreamList(client, 'templates', refresh) : refresh());
|
|
4295
|
+
}
|
|
4296
|
+
const templates = serverInfo.resourceTemplates;
|
|
4297
|
+
if (!templates?.length) {
|
|
4298
|
+
return [];
|
|
4299
|
+
}
|
|
4300
|
+
const enabled = templates.filter((template) => serverConfig?.resources?.[template.uriTemplate]?.enabled !== false);
|
|
4301
|
+
if (!groupConfig ||
|
|
4302
|
+
!Array.isArray(groupConfig.resources) ||
|
|
4303
|
+
groupConfig.resources.includes(uri)) {
|
|
4304
|
+
return enabled;
|
|
4305
|
+
}
|
|
4306
|
+
// Listing shows templates under any selected prefix; reading needs the template itself selected.
|
|
4307
|
+
const selected = new Set(groupConfig.resources);
|
|
4308
|
+
return enabled.filter((template) => selected.has(template.uriTemplate));
|
|
4309
|
+
};
|
|
4310
|
+
const findResourceTemplateServer = async (uri, lookupGroup, filteredServerInfos, serverConfigsByName) => {
|
|
4311
|
+
for (const serverInfo of filteredServerInfos) {
|
|
4312
|
+
// Idle on-demand servers keep cached templates; the read wakes them.
|
|
4313
|
+
const idleOnDemand = serverInfo.config?.startOnDemand === true &&
|
|
4314
|
+
serverInfo.status === 'disconnected' &&
|
|
4315
|
+
!serverInfo.client;
|
|
4316
|
+
if (serverInfo.status !== 'connected' && !idleOnDemand) {
|
|
4317
|
+
continue;
|
|
4318
|
+
}
|
|
4319
|
+
const templates = await getReadableResourceTemplates(serverInfo, uri, lookupGroup, serverConfigsByName.get(serverInfo.name));
|
|
4320
|
+
if (templates.some((template) => uriMatchesResourceTemplate(template.uriTemplate, uri))) {
|
|
4321
|
+
return serverInfo;
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
return undefined;
|
|
4325
|
+
};
|
|
4237
4326
|
const handleReadResourceRequestImpl = async (request, extra) => {
|
|
4238
4327
|
try {
|
|
4239
4328
|
const { uri } = request.params;
|
|
@@ -4349,7 +4438,32 @@ const handleReadResourceRequestImpl = async (request, extra) => {
|
|
|
4349
4438
|
}
|
|
4350
4439
|
}
|
|
4351
4440
|
else {
|
|
4352
|
-
|
|
4441
|
+
const templateServer = await findResourceTemplateServer(uri, lookupGroup, filteredServerInfos, serverConfigsByName);
|
|
4442
|
+
if (!templateServer) {
|
|
4443
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
4444
|
+
}
|
|
4445
|
+
const releaseOnDemandRead = beginOnDemandToolCall(templateServer);
|
|
4446
|
+
try {
|
|
4447
|
+
if (templateServer.config?.startOnDemand) {
|
|
4448
|
+
if (!templateServer.client) {
|
|
4449
|
+
await ensureServerReady(templateServer);
|
|
4450
|
+
}
|
|
4451
|
+
templateServer.lastUsedAt = Date.now();
|
|
4452
|
+
}
|
|
4453
|
+
const client = templateServer.client;
|
|
4454
|
+
if (!client) {
|
|
4455
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
4456
|
+
}
|
|
4457
|
+
result = readOptions
|
|
4458
|
+
? await client.readResource({ uri }, readOptions)
|
|
4459
|
+
: await client.readResource({ uri });
|
|
4460
|
+
}
|
|
4461
|
+
finally {
|
|
4462
|
+
releaseOnDemandRead?.();
|
|
4463
|
+
}
|
|
4464
|
+
if (!result || !Array.isArray(result.contents)) {
|
|
4465
|
+
throw new Error(`Failed to read resource: ${uri}`);
|
|
4466
|
+
}
|
|
4353
4467
|
}
|
|
4354
4468
|
return appsRouteContext.enabled
|
|
4355
4469
|
? result
|
|
@@ -4497,6 +4611,8 @@ const createPrincipalRuntime = async (name, resolvedConfig, scope, toolsOnly = f
|
|
|
4497
4611
|
info.prompts = preserveListFreshness(prompts, prompts.map((prompt) => normalizePromptForCache(name, prompt)));
|
|
4498
4612
|
}
|
|
4499
4613
|
if (capabilities?.resources) {
|
|
4614
|
+
// The raw client: the protected one holds the replica credential lock while listing.
|
|
4615
|
+
void refreshServerResourceTemplatesCache(() => info, client, resolvedConfig.options, info.client);
|
|
4500
4616
|
const { resources } = await client.listResources({}, resolvedConfig.options);
|
|
4501
4617
|
info.resources = preserveListFreshness(resources, resources.map(normalizeResourceForCache));
|
|
4502
4618
|
}
|
|
@@ -4755,7 +4871,17 @@ const withPrincipalServers = (handler, operation) => async (request, extra) => {
|
|
|
4755
4871
|
: { ...request, params: { ...request.params, name: `${prefix}${requestedName}` } };
|
|
4756
4872
|
}
|
|
4757
4873
|
}
|
|
4758
|
-
|
|
4874
|
+
const result = await principalServerScope.run([...scoped.values()], () => handler(resolvedRequest, extra));
|
|
4875
|
+
if (operation === 'tools-list' && result.tools?.length === 0) {
|
|
4876
|
+
const authorizationFailures = [...failures].filter(([, error]) => error instanceof PersonalAuthorizationRequiredError);
|
|
4877
|
+
if (authorizationFailures.length) {
|
|
4878
|
+
throw new ProtocolError(-32000, authorizationFailures.map(([, error]) => error.message).join(' '), {
|
|
4879
|
+
code: 'PERSONAL_AUTHORIZATION_REQUIRED',
|
|
4880
|
+
servers: authorizationFailures.map(([name]) => name),
|
|
4881
|
+
});
|
|
4882
|
+
}
|
|
4883
|
+
}
|
|
4884
|
+
return result;
|
|
4759
4885
|
}
|
|
4760
4886
|
finally {
|
|
4761
4887
|
for (const release of releases)
|