@samanhappy/mcphub 1.0.9 → 1.0.11
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.md +2 -0
- package/dist/cli/commands/tools.js +4 -0
- package/dist/cli/commands/tools.js.map +1 -1
- package/dist/controllers/contextCostController.js +24 -0
- package/dist/controllers/contextCostController.js.map +1 -0
- package/dist/controllers/discoveryController.js +203 -0
- package/dist/controllers/discoveryController.js.map +1 -0
- package/dist/controllers/oauthCallbackController.js +3 -8
- package/dist/controllers/oauthCallbackController.js.map +1 -1
- package/dist/dao/SystemConfigDaoDbImpl.js +3 -0
- package/dist/dao/SystemConfigDaoDbImpl.js.map +1 -1
- package/dist/db/entities/SystemConfig.js +4 -0
- package/dist/db/entities/SystemConfig.js.map +1 -1
- package/dist/db/repositories/SystemConfigRepository.js +1 -0
- package/dist/db/repositories/SystemConfigRepository.js.map +1 -1
- package/dist/routes/index.js +16 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/services/contextCostService.js +101 -0
- package/dist/services/contextCostService.js.map +1 -0
- package/dist/services/groupService.js +1 -1
- package/dist/services/groupService.js.map +1 -1
- package/dist/services/hostedRuntimeCatalogService.js +2 -1
- package/dist/services/hostedRuntimeCatalogService.js.map +1 -1
- package/dist/services/mcpService.js +243 -98
- package/dist/services/mcpService.js.map +1 -1
- package/dist/services/openApiGeneratorService.js +5 -4
- package/dist/services/openApiGeneratorService.js.map +1 -1
- package/dist/services/smartRoutingService.js +53 -30
- package/dist/services/smartRoutingService.js.map +1 -1
- package/dist/services/vectorSearchService.js +3 -1
- package/dist/services/vectorSearchService.js.map +1 -1
- package/dist/utils/mcpApps.js +43 -0
- package/dist/utils/mcpApps.js.map +1 -0
- package/dist/utils/migration.js +1 -0
- package/dist/utils/migration.js.map +1 -1
- package/dist/utils/serialization.js +17 -1
- package/dist/utils/serialization.js.map +1 -1
- package/dist/utils/tokenCost.js +71 -0
- package/dist/utils/tokenCost.js.map +1 -0
- package/frontend/dist/assets/{ActivityPage-DrwHdeQ1.js → ActivityPage-DY-oWiYy.js} +2 -2
- package/frontend/dist/assets/{ActivityPage-DrwHdeQ1.js.map → ActivityPage-DY-oWiYy.js.map} +1 -1
- package/frontend/dist/assets/{ConfirmDialog-CxlizGia.js → ConfirmDialog-Cag_haxr.js} +2 -2
- package/frontend/dist/assets/{ConfirmDialog-CxlizGia.js.map → ConfirmDialog-Cag_haxr.js.map} +1 -1
- package/frontend/dist/assets/Dashboard-DraIR5Hs.js +2 -0
- package/frontend/dist/assets/Dashboard-DraIR5Hs.js.map +1 -0
- package/frontend/dist/assets/{DeleteDialog-DRbWonMu.js → DeleteDialog-BBfJpiiD.js} +2 -2
- package/frontend/dist/assets/{DeleteDialog-DRbWonMu.js.map → DeleteDialog-BBfJpiiD.js.map} +1 -1
- package/frontend/dist/assets/{EndpointCopy-Doi0lGZI.js → EndpointCopy-Bkkaa0MC.js} +2 -2
- package/frontend/dist/assets/{EndpointCopy-Doi0lGZI.js.map → EndpointCopy-Bkkaa0MC.js.map} +1 -1
- package/frontend/dist/assets/GroupsPage-CTFtynaX.js +33 -0
- package/frontend/dist/assets/GroupsPage-CTFtynaX.js.map +1 -0
- package/frontend/dist/assets/{LoginPage-C3p_yHJd.js → LoginPage-BeZ9wg33.js} +2 -2
- package/frontend/dist/assets/{LoginPage-C3p_yHJd.js.map → LoginPage-BeZ9wg33.js.map} +1 -1
- package/frontend/dist/assets/{LogsPage-Z8Iueeij.js → LogsPage-D5LqNWlN.js} +2 -2
- package/frontend/dist/assets/LogsPage-D5LqNWlN.js.map +1 -0
- package/frontend/dist/assets/{MarketPage-BvEPL_eq.js → MarketPage-DmtOnQVN.js} +2 -2
- package/frontend/dist/assets/{MarketPage-BvEPL_eq.js.map → MarketPage-DmtOnQVN.js.map} +1 -1
- package/frontend/dist/assets/{Pagination-BFi-X7qY.js → Pagination-DBAu79mv.js} +2 -2
- package/frontend/dist/assets/{Pagination-BFi-X7qY.js.map → Pagination-DBAu79mv.js.map} +1 -1
- package/frontend/dist/assets/{PromptsPage-BYoNsubQ.js → PromptsPage-blyfeDf6.js} +2 -2
- package/frontend/dist/assets/{PromptsPage-BYoNsubQ.js.map → PromptsPage-blyfeDf6.js.map} +1 -1
- package/frontend/dist/assets/{ResourcesPage-BipHAbTQ.js → ResourcesPage-3gvaBRAN.js} +2 -2
- package/frontend/dist/assets/{ResourcesPage-BipHAbTQ.js.map → ResourcesPage-3gvaBRAN.js.map} +1 -1
- package/frontend/dist/assets/ServersPage-WhPsI-wN.js +37 -0
- package/frontend/dist/assets/ServersPage-WhPsI-wN.js.map +1 -0
- package/frontend/dist/assets/SettingsPage-Dg2-dPPZ.js +12 -0
- package/frontend/dist/assets/SettingsPage-Dg2-dPPZ.js.map +1 -0
- package/frontend/dist/assets/{StatusDot-C-kTn-AA.js → StatusDot-D3_AIDzm.js} +2 -2
- package/frontend/dist/assets/{StatusDot-C-kTn-AA.js.map → StatusDot-D3_AIDzm.js.map} +1 -1
- package/frontend/dist/assets/{ToggleGroup-B4fP-TVG.js → ToggleGroup-DU1_5iss.js} +2 -2
- package/frontend/dist/assets/{ToggleGroup-B4fP-TVG.js.map → ToggleGroup-DU1_5iss.js.map} +1 -1
- package/frontend/dist/assets/{UsersPage-MDtzW9-F.js → UsersPage-Cwi4XfTK.js} +2 -2
- package/frontend/dist/assets/{UsersPage-MDtzW9-F.js.map → UsersPage-Cwi4XfTK.js.map} +1 -1
- package/frontend/dist/assets/contextCost-qVdQkBuO.js +2 -0
- package/frontend/dist/assets/contextCost-qVdQkBuO.js.map +1 -0
- package/frontend/dist/assets/{framework-vendor-BUhDPOUZ.js → framework-vendor-DeqnZ0v6.js} +5 -5
- package/frontend/dist/assets/{framework-vendor-BUhDPOUZ.js.map → framework-vendor-DeqnZ0v6.js.map} +1 -1
- package/frontend/dist/assets/i18n-vendor-DP1IRITP.js +10 -0
- package/frontend/dist/assets/i18n-vendor-DP1IRITP.js.map +1 -0
- package/frontend/dist/assets/{icons-vendor-CKgJB3SC.js → icons-vendor-IYX_ppgH.js} +2 -2
- package/frontend/dist/assets/{icons-vendor-CKgJB3SC.js.map → icons-vendor-IYX_ppgH.js.map} +1 -1
- package/frontend/dist/assets/index-B-q7kaxJ.js +3 -0
- package/frontend/dist/assets/{index-otG7s0Ro.js.map → index-B-q7kaxJ.js.map} +1 -1
- package/frontend/dist/assets/index-D8V2fxz_.css +1 -0
- package/frontend/dist/assets/{resourceService-b5_G4mIW.js → resourceService-DvnBlx1V.js} +2 -2
- package/frontend/dist/assets/{resourceService-b5_G4mIW.js.map → resourceService-DvnBlx1V.js.map} +1 -1
- package/frontend/dist/assets/useSettingsData-Dc1fQY2w.js +2 -0
- package/frontend/dist/assets/{useSettingsData-BE6ZnRFg.js.map → useSettingsData-Dc1fQY2w.js.map} +1 -1
- package/frontend/dist/assets/{variableDetection-GTKqOf1F.js → variableDetection-Dnh8qFpO.js} +2 -2
- package/frontend/dist/assets/{variableDetection-GTKqOf1F.js.map → variableDetection-Dnh8qFpO.js.map} +1 -1
- package/frontend/dist/index.html +5 -5
- package/package.json +7 -7
- package/frontend/dist/assets/Dashboard-C6l7But8.js +0 -2
- package/frontend/dist/assets/Dashboard-C6l7But8.js.map +0 -1
- package/frontend/dist/assets/GroupsPage-Bx3Yi-md.js +0 -33
- package/frontend/dist/assets/GroupsPage-Bx3Yi-md.js.map +0 -1
- package/frontend/dist/assets/LogsPage-Z8Iueeij.js.map +0 -1
- package/frontend/dist/assets/ServersPage-Da24Dkfs.js +0 -37
- package/frontend/dist/assets/ServersPage-Da24Dkfs.js.map +0 -1
- package/frontend/dist/assets/SettingsPage-BiTd0EBn.js +0 -12
- package/frontend/dist/assets/SettingsPage-BiTd0EBn.js.map +0 -1
- package/frontend/dist/assets/i18n-vendor-Kbr87Ofu.js +0 -2
- package/frontend/dist/assets/i18n-vendor-Kbr87Ofu.js.map +0 -1
- package/frontend/dist/assets/index-aFFPxLwH.css +0 -1
- package/frontend/dist/assets/index-otG7s0Ro.js +0 -3
- package/frontend/dist/assets/useServerData-B_sSO_jj.js +0 -2
- package/frontend/dist/assets/useServerData-B_sSO_jj.js.map +0 -1
- package/frontend/dist/assets/useSettingsData-BE6ZnRFg.js +0 -2
|
@@ -24,6 +24,7 @@ import { initSmartRoutingService, getSmartRoutingTools, handleSearchToolsRequest
|
|
|
24
24
|
import { getActivityLoggingService } from './activityLoggingService.js';
|
|
25
25
|
import { assertHostedToolAllowed, filterHostedTools, reserveHostedToolCall, settleHostedToolCall, } from './hostedAuthService.js';
|
|
26
26
|
import { formatErrorForLogging, sanitizeStringForLogging, summarizeErrorForLogging, } from '../utils/serialization.js';
|
|
27
|
+
import { MCP_APPS_CAPABILITIES, filterModelVisibleTools, hasMcpAppsCapability, isAppOnlyTool, stripMcpAppsMetadata, } from '../utils/mcpApps.js';
|
|
27
28
|
const servers = {};
|
|
28
29
|
import { setupClientKeepAlive } from './keepAliveService.js';
|
|
29
30
|
/**
|
|
@@ -194,18 +195,26 @@ export const notifyToolChanged = async (name, options) => {
|
|
|
194
195
|
await registerAllTools(false, name, options);
|
|
195
196
|
broadcastToolListChanged();
|
|
196
197
|
};
|
|
197
|
-
|
|
198
|
+
const broadcastListChanged = (listType, sendNotification) => {
|
|
198
199
|
Object.values(servers).forEach((server) => {
|
|
199
|
-
server
|
|
200
|
-
.sendToolListChanged()
|
|
201
|
-
.catch((error) => {
|
|
202
|
-
console.warn('Failed to send tool list changed notification:', error.message);
|
|
203
|
-
})
|
|
200
|
+
sendNotification(server)
|
|
204
201
|
.then(() => {
|
|
205
|
-
console.log(
|
|
202
|
+
console.log(`${listType} list changed notification sent successfully`);
|
|
203
|
+
})
|
|
204
|
+
.catch((error) => {
|
|
205
|
+
console.warn(`Failed to send ${listType} list changed notification:`, error.message);
|
|
206
206
|
});
|
|
207
207
|
});
|
|
208
208
|
};
|
|
209
|
+
export const broadcastToolListChanged = () => {
|
|
210
|
+
broadcastListChanged('tool', (server) => server.sendToolListChanged());
|
|
211
|
+
};
|
|
212
|
+
export const broadcastResourceListChanged = () => {
|
|
213
|
+
broadcastListChanged('resource', (server) => server.sendResourceListChanged());
|
|
214
|
+
};
|
|
215
|
+
export const broadcastPromptListChanged = () => {
|
|
216
|
+
broadcastListChanged('prompt', (server) => server.sendPromptListChanged());
|
|
217
|
+
};
|
|
209
218
|
export const updateServerInfoVisibility = (serverName, visibility) => {
|
|
210
219
|
const serverInfo = getServerByName(serverName);
|
|
211
220
|
if (!serverInfo) {
|
|
@@ -230,8 +239,11 @@ export const syncToolEmbedding = async (serverName, toolName) => {
|
|
|
230
239
|
console.warn(`Tool not found: ${toolName} on server: ${serverName}`);
|
|
231
240
|
return;
|
|
232
241
|
}
|
|
242
|
+
if (isAppOnlyTool(tool)) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
233
245
|
// Save tool as vector embedding for search
|
|
234
|
-
|
|
246
|
+
syncToolsAsVectorEmbeddings(serverName, [tool]).catch((error) => {
|
|
235
247
|
console.warn(`[EMBED_SYNC_ERROR] Failed to sync embedding for tool "${toolName}" on server "${serverName}"`);
|
|
236
248
|
console.error('Error syncing single tool embedding', { serverName, toolName, error });
|
|
237
249
|
});
|
|
@@ -245,26 +257,122 @@ const cleanInputSchema = (schema) => {
|
|
|
245
257
|
delete cleanedSchema.$schema;
|
|
246
258
|
return cleanedSchema;
|
|
247
259
|
};
|
|
260
|
+
export const normalizeToolForCache = (serverName, tool) => {
|
|
261
|
+
return {
|
|
262
|
+
...tool,
|
|
263
|
+
name: `${serverName}${getNameSeparator()}${tool.name}`,
|
|
264
|
+
description: tool.description || '',
|
|
265
|
+
inputSchema: cleanInputSchema(tool.inputSchema || {}),
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
const syncToolsAsVectorEmbeddings = async (serverName, tools, options) => {
|
|
269
|
+
const modelVisibleTools = filterModelVisibleTools(tools);
|
|
270
|
+
if (modelVisibleTools.length === 0) {
|
|
271
|
+
await removeServerToolEmbeddings(serverName);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
await saveToolsAsVectorEmbeddings(serverName, modelVisibleTools, options);
|
|
275
|
+
};
|
|
248
276
|
// Normalize prompt payload to satisfy MCP ListPrompts response schema
|
|
249
277
|
const normalizePromptForList = (prompt) => {
|
|
250
278
|
return {
|
|
279
|
+
...prompt,
|
|
251
280
|
name: prompt.name,
|
|
252
281
|
title: prompt.title || prompt.name,
|
|
253
282
|
description: prompt.description || '',
|
|
254
283
|
arguments: Array.isArray(prompt.arguments) ? prompt.arguments : [],
|
|
255
284
|
};
|
|
256
285
|
};
|
|
286
|
+
const normalizePromptForCache = (serverName, prompt) => {
|
|
287
|
+
return normalizePromptForList({
|
|
288
|
+
...prompt,
|
|
289
|
+
name: `${serverName}${getNameSeparator()}${prompt.name}`,
|
|
290
|
+
});
|
|
291
|
+
};
|
|
257
292
|
// Normalize resource payload to avoid nullable DB fields violating MCP schema
|
|
258
293
|
const normalizeResourceForList = (resource) => {
|
|
259
294
|
return {
|
|
295
|
+
...resource,
|
|
260
296
|
uri: resource.uri,
|
|
261
297
|
name: resource.name || '',
|
|
262
298
|
description: resource.description || '',
|
|
263
299
|
mimeType: resource.mimeType || '',
|
|
264
300
|
};
|
|
265
301
|
};
|
|
302
|
+
const normalizeResourceForCache = (resource) => {
|
|
303
|
+
return normalizeResourceForList(resource);
|
|
304
|
+
};
|
|
266
305
|
// Store all server information
|
|
267
306
|
let serverInfos = [];
|
|
307
|
+
export const updateServerToolsCache = (serverInfo, tools, options) => {
|
|
308
|
+
serverInfo.tools = tools.map((tool) => normalizeToolForCache(serverInfo.name, tool));
|
|
309
|
+
syncToolsAsVectorEmbeddings(serverInfo.name, serverInfo.tools, {
|
|
310
|
+
reportProgress: options?.reportEmbeddingProgress === true,
|
|
311
|
+
}).catch(() => {
|
|
312
|
+
console.warn('[EMBED_SYNC_ERROR] Failed to sync tool embeddings');
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
const updateServerPromptsCache = (serverInfo, prompts) => {
|
|
316
|
+
serverInfo.prompts = prompts.map((prompt) => normalizePromptForCache(serverInfo.name, prompt));
|
|
317
|
+
};
|
|
318
|
+
const updateServerResourcesCache = (serverInfo, resources) => {
|
|
319
|
+
serverInfo.resources = resources.map(normalizeResourceForCache);
|
|
320
|
+
};
|
|
321
|
+
const logListChangedRefreshError = (listType) => {
|
|
322
|
+
console.warn(`Failed to refresh ${listType} list after upstream notification`);
|
|
323
|
+
};
|
|
324
|
+
const createUpstreamMcpClient = (name, getServerInfo) => {
|
|
325
|
+
return new Client({
|
|
326
|
+
name: `mcp-client-${name}`,
|
|
327
|
+
version: '1.0.0',
|
|
328
|
+
}, {
|
|
329
|
+
capabilities: MCP_APPS_CAPABILITIES,
|
|
330
|
+
listChanged: {
|
|
331
|
+
tools: {
|
|
332
|
+
onChanged: (error, tools) => {
|
|
333
|
+
const serverInfo = getServerInfo();
|
|
334
|
+
if (error) {
|
|
335
|
+
logListChangedRefreshError('tool');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (!serverInfo || !tools) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
updateServerToolsCache(serverInfo, tools);
|
|
342
|
+
broadcastToolListChanged();
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
prompts: {
|
|
346
|
+
onChanged: (error, prompts) => {
|
|
347
|
+
const serverInfo = getServerInfo();
|
|
348
|
+
if (error) {
|
|
349
|
+
logListChangedRefreshError('prompt');
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (!serverInfo || !prompts) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
updateServerPromptsCache(serverInfo, prompts);
|
|
356
|
+
broadcastPromptListChanged();
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
resources: {
|
|
360
|
+
onChanged: (error, resources) => {
|
|
361
|
+
const serverInfo = getServerInfo();
|
|
362
|
+
if (error) {
|
|
363
|
+
logListChangedRefreshError('resource');
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (!serverInfo || !resources) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
updateServerResourcesCache(serverInfo, resources);
|
|
370
|
+
broadcastResourceListChanged();
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
};
|
|
268
376
|
// Normalize and infer server type for safe client display
|
|
269
377
|
const normalizeServerType = (type) => {
|
|
270
378
|
if (!type)
|
|
@@ -679,12 +787,7 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
|
|
|
679
787
|
// Recreate transport using helper function
|
|
680
788
|
const newTransport = await createTransportFromConfig(serverInfo.name, server);
|
|
681
789
|
// Create new client
|
|
682
|
-
const client =
|
|
683
|
-
name: `mcp-client-${serverInfo.name}`,
|
|
684
|
-
version: '1.0.0',
|
|
685
|
-
}, {
|
|
686
|
-
capabilities: {},
|
|
687
|
-
});
|
|
790
|
+
const client = createUpstreamMcpClient(serverInfo.name, () => serverInfo);
|
|
688
791
|
// Reconnect with new transport
|
|
689
792
|
await client.connect(newTransport, serverInfo.options || {});
|
|
690
793
|
// Update server info with new client and transport
|
|
@@ -694,19 +797,7 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
|
|
|
694
797
|
// Reload tools list after reconnection
|
|
695
798
|
try {
|
|
696
799
|
const tools = await client.listTools({}, serverInfo.options || {});
|
|
697
|
-
serverInfo
|
|
698
|
-
name: `${serverInfo.name}${getNameSeparator()}${tool.name}`,
|
|
699
|
-
description: tool.description || '',
|
|
700
|
-
inputSchema: cleanInputSchema(tool.inputSchema || {}),
|
|
701
|
-
}));
|
|
702
|
-
// Save tools as vector embeddings for search
|
|
703
|
-
saveToolsAsVectorEmbeddings(serverInfo.name, serverInfo.tools).catch((error) => {
|
|
704
|
-
console.warn(`[EMBED_SYNC_ERROR] Failed to sync tool embeddings after reconnect for server "${serverInfo.name}"`);
|
|
705
|
-
console.error('Error syncing tool embeddings after reconnect', {
|
|
706
|
-
serverName: serverInfo.name,
|
|
707
|
-
error: summarizeErrorForLogging(error),
|
|
708
|
-
});
|
|
709
|
-
});
|
|
800
|
+
updateServerToolsCache(serverInfo, tools.tools);
|
|
710
801
|
}
|
|
711
802
|
catch (listToolsError) {
|
|
712
803
|
console.warn('Failed to reload tools after reconnection', {
|
|
@@ -850,7 +941,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
|
|
|
850
941
|
serverInfo.openApiClient = openApiClient;
|
|
851
942
|
console.log(`Successfully initialized OpenAPI server: ${name} with ${mcpTools.length} tools`);
|
|
852
943
|
// Save tools as vector embeddings for search
|
|
853
|
-
|
|
944
|
+
syncToolsAsVectorEmbeddings(name, mcpTools, {
|
|
854
945
|
reportProgress: options?.reportEmbeddingProgress === true && serverName === name,
|
|
855
946
|
}).catch((error) => {
|
|
856
947
|
console.warn(`[EMBED_SYNC_ERROR] Failed to sync OpenAPI embeddings for server "${name}"`);
|
|
@@ -875,12 +966,8 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
|
|
|
875
966
|
else {
|
|
876
967
|
transport = await createTransportFromConfig(name, expandedConf);
|
|
877
968
|
}
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
version: '1.0.0',
|
|
881
|
-
}, {
|
|
882
|
-
capabilities: {},
|
|
883
|
-
});
|
|
969
|
+
const serverInfoRef = {};
|
|
970
|
+
const client = createUpstreamMcpClient(name, () => serverInfoRef.current);
|
|
884
971
|
// Get request options from server configuration, with fallbacks
|
|
885
972
|
const serverRequestOptions = expandedConf.options || {};
|
|
886
973
|
const defaultRequestTimeout = Number(process.env.DEFAULT_REQUEST_TIMEOUT) || 60000;
|
|
@@ -911,6 +998,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
|
|
|
911
998
|
createTime: Date.now(),
|
|
912
999
|
config: expandedConf, // Store reference to expanded config
|
|
913
1000
|
};
|
|
1001
|
+
serverInfoRef.current = serverInfo;
|
|
914
1002
|
const pendingAuth = expandedConf.oauth?.pendingAuthorization;
|
|
915
1003
|
if (pendingAuth) {
|
|
916
1004
|
serverInfo.status = 'oauth_required';
|
|
@@ -937,20 +1025,8 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
|
|
|
937
1025
|
.listTools({}, initRequestOptions || requestOptions)
|
|
938
1026
|
.then((tools) => {
|
|
939
1027
|
console.log(`Successfully listed ${tools.tools.length} tools for server: ${name}`);
|
|
940
|
-
serverInfo
|
|
941
|
-
|
|
942
|
-
description: tool.description || '',
|
|
943
|
-
inputSchema: cleanInputSchema(tool.inputSchema || {}),
|
|
944
|
-
}));
|
|
945
|
-
// Save tools as vector embeddings for search
|
|
946
|
-
saveToolsAsVectorEmbeddings(name, serverInfo.tools, {
|
|
947
|
-
reportProgress: options?.reportEmbeddingProgress === true && serverName === name,
|
|
948
|
-
}).catch((embeddingError) => {
|
|
949
|
-
console.warn(`[EMBED_SYNC_ERROR] Failed to sync tool embeddings for connected server "${name}"`);
|
|
950
|
-
console.error('Error syncing tool embeddings for connected server', {
|
|
951
|
-
serverName: name,
|
|
952
|
-
error: summarizeErrorForLogging(embeddingError),
|
|
953
|
-
});
|
|
1028
|
+
updateServerToolsCache(serverInfo, tools.tools, {
|
|
1029
|
+
reportEmbeddingProgress: options?.reportEmbeddingProgress === true && serverName === name,
|
|
954
1030
|
});
|
|
955
1031
|
})
|
|
956
1032
|
.catch((error) => {
|
|
@@ -966,12 +1042,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
|
|
|
966
1042
|
.listPrompts({}, initRequestOptions || requestOptions)
|
|
967
1043
|
.then((prompts) => {
|
|
968
1044
|
console.log(`Successfully listed ${prompts.prompts.length} prompts for server: ${name}`);
|
|
969
|
-
serverInfo
|
|
970
|
-
name: `${name}${getNameSeparator()}${prompt.name}`,
|
|
971
|
-
title: prompt.title,
|
|
972
|
-
description: prompt.description,
|
|
973
|
-
arguments: prompt.arguments,
|
|
974
|
-
}));
|
|
1045
|
+
updateServerPromptsCache(serverInfo, prompts.prompts);
|
|
975
1046
|
})
|
|
976
1047
|
.catch((error) => {
|
|
977
1048
|
console.error('Failed to list prompts for server', {
|
|
@@ -986,12 +1057,7 @@ export const initializeClientsFromSettings = async (isInit, serverName, options)
|
|
|
986
1057
|
.listResources({}, initRequestOptions || requestOptions)
|
|
987
1058
|
.then((resources) => {
|
|
988
1059
|
console.log(`Successfully listed ${resources.resources.length} resources for server: ${name}`);
|
|
989
|
-
serverInfo
|
|
990
|
-
uri: resource.uri,
|
|
991
|
-
name: resource.name,
|
|
992
|
-
description: resource.description,
|
|
993
|
-
mimeType: resource.mimeType,
|
|
994
|
-
}));
|
|
1060
|
+
updateServerResourcesCache(serverInfo, resources.resources);
|
|
995
1061
|
})
|
|
996
1062
|
.catch((error) => {
|
|
997
1063
|
console.error('Failed to list resources for server', {
|
|
@@ -1360,6 +1426,48 @@ export const toggleServerStatus = async (name, enabled) => {
|
|
|
1360
1426
|
return { success: false, message: 'Failed to toggle server status' };
|
|
1361
1427
|
}
|
|
1362
1428
|
};
|
|
1429
|
+
const getMcpAppsRouteContext = async (sessionId, group) => {
|
|
1430
|
+
if (!sessionId ||
|
|
1431
|
+
isSmartRoutingGroup(group) ||
|
|
1432
|
+
!hasMcpAppsCapability(servers[sessionId]?.getClientCapabilities())) {
|
|
1433
|
+
return { enabled: false };
|
|
1434
|
+
}
|
|
1435
|
+
const { filteredServerInfos } = await getFilteredServerInfosForGroup(group);
|
|
1436
|
+
if (filteredServerInfos.length !== 1 ||
|
|
1437
|
+
filteredServerInfos[0].status !== 'connected' ||
|
|
1438
|
+
!filteredServerInfos[0].client) {
|
|
1439
|
+
return { enabled: false };
|
|
1440
|
+
}
|
|
1441
|
+
return {
|
|
1442
|
+
enabled: true,
|
|
1443
|
+
serverInfo: filteredServerInfos[0],
|
|
1444
|
+
};
|
|
1445
|
+
};
|
|
1446
|
+
const normalizeToolNameForServer = (serverName, toolName) => {
|
|
1447
|
+
const prefix = `${serverName}${getNameSeparator()}`;
|
|
1448
|
+
return toolName.startsWith(prefix) ? toolName.substring(prefix.length) : toolName;
|
|
1449
|
+
};
|
|
1450
|
+
const findToolOnServer = (serverInfo, toolName, allowRawName) => {
|
|
1451
|
+
return serverInfo.tools.find((tool) => tool.name === toolName ||
|
|
1452
|
+
(allowRawName && normalizeToolNameForServer(serverInfo.name, tool.name) === toolName));
|
|
1453
|
+
};
|
|
1454
|
+
const assertToolAvailableForRoute = (tool, appsRouteContext) => {
|
|
1455
|
+
if (isAppOnlyTool(tool) && !appsRouteContext.enabled) {
|
|
1456
|
+
throw new Error(`Tool '${tool.name}' is only available to MCP Apps`);
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
const projectToolForDownstream = (serverName, tool, appsRouteContext) => {
|
|
1460
|
+
if (!appsRouteContext.enabled && isAppOnlyTool(tool)) {
|
|
1461
|
+
return undefined;
|
|
1462
|
+
}
|
|
1463
|
+
const projectedTool = appsRouteContext.enabled ? tool : stripMcpAppsMetadata(tool);
|
|
1464
|
+
return {
|
|
1465
|
+
...projectedTool,
|
|
1466
|
+
name: appsRouteContext.enabled
|
|
1467
|
+
? normalizeToolNameForServer(serverName, projectedTool.name)
|
|
1468
|
+
: projectedTool.name,
|
|
1469
|
+
};
|
|
1470
|
+
};
|
|
1363
1471
|
export const handleListToolsRequest = async (_, extra) => {
|
|
1364
1472
|
const sessionId = extra.sessionId || '';
|
|
1365
1473
|
const group = getGroup(sessionId);
|
|
@@ -1370,6 +1478,7 @@ export const handleListToolsRequest = async (_, extra) => {
|
|
|
1370
1478
|
return getSmartRoutingTools(group);
|
|
1371
1479
|
}
|
|
1372
1480
|
const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
|
|
1481
|
+
const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
|
|
1373
1482
|
const allTools = [];
|
|
1374
1483
|
for (const serverInfo of filteredServerInfos) {
|
|
1375
1484
|
if (serverInfo.tools && serverInfo.tools.length > 0) {
|
|
@@ -1386,7 +1495,10 @@ export const handleListToolsRequest = async (_, extra) => {
|
|
|
1386
1495
|
description: toolConfig?.description || tool.description, // Use custom description if available
|
|
1387
1496
|
};
|
|
1388
1497
|
});
|
|
1389
|
-
allTools.push(...toolsWithCustomDescriptions)
|
|
1498
|
+
allTools.push(...toolsWithCustomDescriptions.flatMap((tool) => {
|
|
1499
|
+
const projectedTool = projectToolForDownstream(serverInfo.name, tool, appsRouteContext);
|
|
1500
|
+
return projectedTool ? [projectedTool] : [];
|
|
1501
|
+
}));
|
|
1390
1502
|
}
|
|
1391
1503
|
}
|
|
1392
1504
|
return {
|
|
@@ -1405,6 +1517,7 @@ export const handleCallToolRequest = async (request, extra) => {
|
|
|
1405
1517
|
// Fallback to extra for backward compatibility (e.g., direct API calls)
|
|
1406
1518
|
const group = requestContextService.getGroupContext() || extra?.group || getGroup(sessionId) || undefined;
|
|
1407
1519
|
const username = requestContextService.getUsernameContext() || extra?.username || undefined;
|
|
1520
|
+
let appsRouteContext = { enabled: false };
|
|
1408
1521
|
const keyId = bearerKeyContext.keyId || extra?.keyId || undefined;
|
|
1409
1522
|
const keyName = bearerKeyContext.keyName || extra?.keyName || undefined;
|
|
1410
1523
|
const sourceIp = requestContextService.getRequestContext()?.remoteAddress || undefined;
|
|
@@ -1425,6 +1538,7 @@ export const handleCallToolRequest = async (request, extra) => {
|
|
|
1425
1538
|
});
|
|
1426
1539
|
};
|
|
1427
1540
|
try {
|
|
1541
|
+
appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
|
|
1428
1542
|
// Special handling for smart routing tools
|
|
1429
1543
|
if (request.params.name === 'search_tools') {
|
|
1430
1544
|
const { query, limit = 10 } = request.params.arguments || {};
|
|
@@ -1443,7 +1557,10 @@ export const handleCallToolRequest = async (request, extra) => {
|
|
|
1443
1557
|
}
|
|
1444
1558
|
const { arguments: toolArgs } = request.params.arguments || {};
|
|
1445
1559
|
let targetServerInfo;
|
|
1446
|
-
if (
|
|
1560
|
+
if (appsRouteContext.enabled) {
|
|
1561
|
+
targetServerInfo = appsRouteContext.serverInfo;
|
|
1562
|
+
}
|
|
1563
|
+
else if (extra && extra.server) {
|
|
1447
1564
|
targetServerInfo = getServerByName(extra.server);
|
|
1448
1565
|
}
|
|
1449
1566
|
else {
|
|
@@ -1456,10 +1573,11 @@ export const handleCallToolRequest = async (request, extra) => {
|
|
|
1456
1573
|
throw new Error(`No available servers found with tool: ${toolName}`);
|
|
1457
1574
|
}
|
|
1458
1575
|
// Check if the tool exists on the server
|
|
1459
|
-
const
|
|
1460
|
-
if (!
|
|
1576
|
+
const tool = findToolOnServer(targetServerInfo, toolName, appsRouteContext.enabled);
|
|
1577
|
+
if (!tool) {
|
|
1461
1578
|
throw new Error(`Tool '${toolName}' not found on server '${targetServerInfo.name}'`);
|
|
1462
1579
|
}
|
|
1580
|
+
assertToolAvailableForRoute(tool, appsRouteContext);
|
|
1463
1581
|
// Handle OpenAPI servers differently
|
|
1464
1582
|
if (targetServerInfo.openApiClient) {
|
|
1465
1583
|
// For OpenAPI servers, use the OpenAPI client
|
|
@@ -1472,11 +1590,7 @@ export const handleCallToolRequest = async (request, extra) => {
|
|
|
1472
1590
|
arguments: summarizeArgumentsForLogging(finalArgs),
|
|
1473
1591
|
});
|
|
1474
1592
|
// Remove server prefix from tool name if present
|
|
1475
|
-
const
|
|
1476
|
-
const prefix = `${targetServerInfo.name}${separator}`;
|
|
1477
|
-
const cleanToolName = toolName.startsWith(prefix)
|
|
1478
|
-
? toolName.substring(prefix.length)
|
|
1479
|
-
: toolName;
|
|
1593
|
+
const cleanToolName = normalizeToolNameForServer(targetServerInfo.name, toolName);
|
|
1480
1594
|
// Extract passthrough headers from extra or request context
|
|
1481
1595
|
let passthroughHeaders;
|
|
1482
1596
|
let requestHeaders = null;
|
|
@@ -1549,11 +1663,7 @@ export const handleCallToolRequest = async (request, extra) => {
|
|
|
1549
1663
|
serverName: targetServerInfo.name,
|
|
1550
1664
|
arguments: summarizeArgumentsForLogging(finalArgs),
|
|
1551
1665
|
});
|
|
1552
|
-
const
|
|
1553
|
-
const prefix = `${targetServerInfo.name}${separator}`;
|
|
1554
|
-
const cleanToolName = toolName.startsWith(prefix)
|
|
1555
|
-
? toolName.substring(prefix.length)
|
|
1556
|
-
: toolName;
|
|
1666
|
+
const cleanToolName = normalizeToolNameForServer(targetServerInfo.name, toolName);
|
|
1557
1667
|
await reserveHostedIfNeeded(targetServerInfo.name, cleanToolName);
|
|
1558
1668
|
const result = await callToolWithReconnect(targetServerInfo, {
|
|
1559
1669
|
name: cleanToolName,
|
|
@@ -1588,20 +1698,22 @@ export const handleCallToolRequest = async (request, extra) => {
|
|
|
1588
1698
|
return result;
|
|
1589
1699
|
}
|
|
1590
1700
|
// Regular tool handling
|
|
1591
|
-
const serverInfo =
|
|
1592
|
-
|
|
1701
|
+
const serverInfo = appsRouteContext.enabled
|
|
1702
|
+
? appsRouteContext.serverInfo
|
|
1703
|
+
: getServerByTool(request.params.name);
|
|
1704
|
+
const tool = serverInfo
|
|
1705
|
+
? findToolOnServer(serverInfo, request.params.name, appsRouteContext.enabled)
|
|
1706
|
+
: undefined;
|
|
1707
|
+
if (!serverInfo || !tool) {
|
|
1593
1708
|
throw new Error(`Server not found: ${request.params.name}`);
|
|
1594
1709
|
}
|
|
1710
|
+
assertToolAvailableForRoute(tool, appsRouteContext);
|
|
1595
1711
|
// Handle OpenAPI servers differently
|
|
1596
1712
|
if (serverInfo.openApiClient) {
|
|
1597
1713
|
// For OpenAPI servers, use the OpenAPI client
|
|
1598
1714
|
const openApiClient = serverInfo.openApiClient;
|
|
1599
1715
|
// Remove server prefix from tool name if present
|
|
1600
|
-
const
|
|
1601
|
-
const prefix = `${serverInfo.name}${separator}`;
|
|
1602
|
-
const cleanToolName = request.params.name.startsWith(prefix)
|
|
1603
|
-
? request.params.name.substring(prefix.length)
|
|
1604
|
-
: request.params.name;
|
|
1716
|
+
const cleanToolName = normalizeToolNameForServer(serverInfo.name, request.params.name);
|
|
1605
1717
|
console.log('Invoking OpenAPI tool', {
|
|
1606
1718
|
toolName: cleanToolName,
|
|
1607
1719
|
serverName: serverInfo.name,
|
|
@@ -1673,11 +1785,7 @@ export const handleCallToolRequest = async (request, extra) => {
|
|
|
1673
1785
|
if (!client) {
|
|
1674
1786
|
throw new Error(`Client not found for server: ${serverInfo.name}`);
|
|
1675
1787
|
}
|
|
1676
|
-
const
|
|
1677
|
-
const prefix = `${serverInfo.name}${separator}`;
|
|
1678
|
-
const cleanToolName = request.params.name.startsWith(prefix)
|
|
1679
|
-
? request.params.name.substring(prefix.length)
|
|
1680
|
-
: request.params.name;
|
|
1788
|
+
const cleanToolName = normalizeToolNameForServer(serverInfo.name, request.params.name);
|
|
1681
1789
|
await reserveHostedIfNeeded(serverInfo.name, cleanToolName);
|
|
1682
1790
|
const result = await callToolWithReconnect(serverInfo, { ...request.params, name: cleanToolName }, serverInfo.options || {});
|
|
1683
1791
|
await settleHostedIfNeeded({
|
|
@@ -1860,6 +1968,7 @@ export const handleListResourcesRequest = async (_, extra) => {
|
|
|
1860
1968
|
const sessionId = extra.sessionId || '';
|
|
1861
1969
|
const group = getGroup(sessionId);
|
|
1862
1970
|
console.log(`Handling ListResourcesRequest for group: ${group}`);
|
|
1971
|
+
const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
|
|
1863
1972
|
// Start with built-in resources (only enabled ones)
|
|
1864
1973
|
const builtinResources = await getBuiltinResourceDao().findEnabled();
|
|
1865
1974
|
const allResources = builtinResources.map((br) => normalizeResourceForList({
|
|
@@ -1884,10 +1993,13 @@ export const handleListResourcesRequest = async (_, extra) => {
|
|
|
1884
1993
|
// Apply custom descriptions from server configuration
|
|
1885
1994
|
const resourcesWithCustomDescriptions = enabledResources.map((resource) => {
|
|
1886
1995
|
const resourceConfig = serverConfig?.resources?.[resource.uri];
|
|
1887
|
-
|
|
1996
|
+
const normalizedResource = normalizeResourceForList({
|
|
1888
1997
|
...resource,
|
|
1889
1998
|
description: resourceConfig?.description || resource.description,
|
|
1890
1999
|
});
|
|
2000
|
+
return appsRouteContext.enabled
|
|
2001
|
+
? normalizedResource
|
|
2002
|
+
: stripMcpAppsMetadata(normalizedResource);
|
|
1891
2003
|
});
|
|
1892
2004
|
allResources.push(...resourcesWithCustomDescriptions);
|
|
1893
2005
|
}
|
|
@@ -1900,6 +2012,7 @@ export const handleListResourceTemplatesRequest = async (_, extra) => {
|
|
|
1900
2012
|
const sessionId = extra.sessionId || '';
|
|
1901
2013
|
const group = getGroup(sessionId);
|
|
1902
2014
|
console.log(`Handling ListResourceTemplatesRequest for group: ${group}`);
|
|
2015
|
+
const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
|
|
1903
2016
|
const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group, {
|
|
1904
2017
|
requireClient: true,
|
|
1905
2018
|
});
|
|
@@ -1908,15 +2021,21 @@ export const handleListResourceTemplatesRequest = async (_, extra) => {
|
|
|
1908
2021
|
return [];
|
|
1909
2022
|
}
|
|
1910
2023
|
const templates = await serverInfo.client.listResourceTemplates({}, serverInfo.options || {});
|
|
1911
|
-
|
|
2024
|
+
const filteredTemplates = await filterResourceTemplatesByGroup(group, serverInfo.name, templates.resourceTemplates || [], serverConfigsByName.get(serverInfo.name));
|
|
2025
|
+
return appsRouteContext.enabled
|
|
2026
|
+
? filteredTemplates
|
|
2027
|
+
: filteredTemplates.map((template) => stripMcpAppsMetadata(template));
|
|
1912
2028
|
}));
|
|
1913
2029
|
return {
|
|
1914
2030
|
resourceTemplates: results.flatMap((result) => result.status === 'fulfilled' ? result.value : []),
|
|
1915
2031
|
};
|
|
1916
2032
|
};
|
|
1917
|
-
export const handleReadResourceRequest = async (request,
|
|
2033
|
+
export const handleReadResourceRequest = async (request, extra) => {
|
|
1918
2034
|
try {
|
|
1919
2035
|
const { uri } = request.params;
|
|
2036
|
+
const sessionId = extra.sessionId || '';
|
|
2037
|
+
const group = getGroup(sessionId);
|
|
2038
|
+
const appsRouteContext = await getMcpAppsRouteContext(sessionId, group);
|
|
1920
2039
|
// Check built-in resources first
|
|
1921
2040
|
const builtinResource = await getBuiltinResourceDao().findByUri(uri);
|
|
1922
2041
|
if (builtinResource && builtinResource.enabled !== false) {
|
|
@@ -1930,18 +2049,39 @@ export const handleReadResourceRequest = async (request, _extra) => {
|
|
|
1930
2049
|
],
|
|
1931
2050
|
};
|
|
1932
2051
|
}
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
serverInfo.
|
|
1937
|
-
|
|
2052
|
+
const { filteredServerInfos, serverConfigsByName } = await getFilteredServerInfosForGroup(group);
|
|
2053
|
+
let server;
|
|
2054
|
+
for (const serverInfo of filteredServerInfos) {
|
|
2055
|
+
if (serverInfo.status !== 'connected') {
|
|
2056
|
+
continue;
|
|
2057
|
+
}
|
|
2058
|
+
const serverConfig = await getServerDao().findById(serverInfo.name);
|
|
2059
|
+
let enabledResources = serverInfo.resources;
|
|
2060
|
+
if (serverConfig?.resources) {
|
|
2061
|
+
enabledResources = enabledResources.filter((resource) => serverConfig.resources?.[resource.uri]?.enabled !== false);
|
|
2062
|
+
}
|
|
2063
|
+
enabledResources = await filterResourcesByGroup(group, serverInfo.name, enabledResources, serverConfigsByName.get(serverInfo.name));
|
|
2064
|
+
if (enabledResources.some((resource) => resource.uri === uri)) {
|
|
2065
|
+
server = serverInfo;
|
|
2066
|
+
break;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
if (!server && appsRouteContext.enabled && uri.startsWith('ui://')) {
|
|
2070
|
+
server = appsRouteContext.serverInfo;
|
|
2071
|
+
}
|
|
2072
|
+
if (!server?.client) {
|
|
1938
2073
|
throw new Error(`Resource not found: ${uri}`);
|
|
1939
2074
|
}
|
|
1940
|
-
const result = await server.client
|
|
1941
|
-
if (!result) {
|
|
2075
|
+
const result = await server.client.readResource({ uri });
|
|
2076
|
+
if (!result || !Array.isArray(result.contents)) {
|
|
1942
2077
|
throw new Error(`Failed to read resource: ${uri}`);
|
|
1943
2078
|
}
|
|
1944
|
-
return
|
|
2079
|
+
return appsRouteContext.enabled
|
|
2080
|
+
? result
|
|
2081
|
+
: {
|
|
2082
|
+
...result,
|
|
2083
|
+
contents: result.contents.map((content) => stripMcpAppsMetadata(content)),
|
|
2084
|
+
};
|
|
1945
2085
|
}
|
|
1946
2086
|
catch (error) {
|
|
1947
2087
|
console.error('Error handling ReadResourceRequest', summarizeErrorForLogging(error));
|
|
@@ -1968,7 +2108,12 @@ export const createMcpServer = (name, version, options) => {
|
|
|
1968
2108
|
}
|
|
1969
2109
|
// If no group, use default name (global routing)
|
|
1970
2110
|
const server = new Server({ name: serverName, version }, {
|
|
1971
|
-
capabilities: {
|
|
2111
|
+
capabilities: {
|
|
2112
|
+
tools: { listChanged: true },
|
|
2113
|
+
prompts: { listChanged: true },
|
|
2114
|
+
resources: { listChanged: true },
|
|
2115
|
+
...MCP_APPS_CAPABILITIES,
|
|
2116
|
+
},
|
|
1972
2117
|
...(normalizedOptions.instructions !== undefined
|
|
1973
2118
|
? { instructions: normalizedOptions.instructions }
|
|
1974
2119
|
: {}),
|