@samanhappy/mcphub 0.9.16 → 0.10.1
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 +67 -0
- package/README.zh.md +65 -0
- package/dist/config/index.js +17 -4
- package/dist/config/index.js.map +1 -1
- package/dist/controllers/authController.js +16 -0
- package/dist/controllers/authController.js.map +1 -1
- package/dist/controllers/cloudController.js +5 -2
- package/dist/controllers/cloudController.js.map +1 -1
- package/dist/controllers/oauthCallbackController.js +276 -0
- package/dist/controllers/oauthCallbackController.js.map +1 -0
- package/dist/controllers/openApiController.js +5 -64
- package/dist/controllers/openApiController.js.map +1 -1
- package/dist/controllers/promptController.js +3 -1
- package/dist/controllers/promptController.js.map +1 -1
- package/dist/controllers/serverController.js +13 -5
- package/dist/controllers/serverController.js.map +1 -1
- package/dist/controllers/toolController.js +18 -3
- package/dist/controllers/toolController.js.map +1 -1
- package/dist/controllers/userController.js +23 -1
- package/dist/controllers/userController.js.map +1 -1
- package/dist/routes/index.js +3 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/server.js +18 -8
- package/dist/server.js.map +1 -1
- package/dist/services/mcpOAuthProvider.js +472 -0
- package/dist/services/mcpOAuthProvider.js.map +1 -0
- package/dist/services/mcpService.js +321 -191
- package/dist/services/mcpService.js.map +1 -1
- package/dist/services/oauthClientRegistration.js +444 -0
- package/dist/services/oauthClientRegistration.js.map +1 -0
- package/dist/services/oauthService.js +216 -0
- package/dist/services/oauthService.js.map +1 -0
- package/dist/services/oauthSettingsStore.js +106 -0
- package/dist/services/oauthSettingsStore.js.map +1 -0
- package/dist/services/openApiGeneratorService.js +10 -2
- package/dist/services/openApiGeneratorService.js.map +1 -1
- package/dist/utils/parameterConversion.js +87 -0
- package/dist/utils/parameterConversion.js.map +1 -0
- package/dist/utils/passwordValidation.js +38 -0
- package/dist/utils/passwordValidation.js.map +1 -0
- package/dist/utils/path.js.map +1 -1
- package/frontend/dist/assets/index-C0eRMKUo.js +251 -0
- package/frontend/dist/assets/index-C0eRMKUo.js.map +1 -0
- package/frontend/dist/assets/index-C_58ZhSt.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +3 -2
- package/frontend/dist/assets/index-DDUK8zl_.js +0 -217
- package/frontend/dist/assets/index-DDUK8zl_.js.map +0 -1
- package/frontend/dist/assets/index-DcVhHcn9.css +0 -1
|
@@ -4,7 +4,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema
|
|
|
4
4
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
5
5
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
6
6
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
7
|
-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
7
|
+
import { StreamableHTTPClientTransport, } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
8
8
|
import { loadSettings, expandEnvVars, replaceEnvVars, getNameSeparator } from '../config/index.js';
|
|
9
9
|
import config from '../config/index.js';
|
|
10
10
|
import { getGroup } from './sseService.js';
|
|
@@ -14,6 +14,8 @@ import { OpenAPIClient } from '../clients/openapi.js';
|
|
|
14
14
|
import { RequestContextService } from './requestContextService.js';
|
|
15
15
|
import { getDataService } from './services.js';
|
|
16
16
|
import { getServerDao } from '../dao/index.js';
|
|
17
|
+
import { initializeAllOAuthClients } from './oauthService.js';
|
|
18
|
+
import { createOAuthProvider } from './mcpOAuthProvider.js';
|
|
17
19
|
const servers = {};
|
|
18
20
|
const serverDao = getServerDao();
|
|
19
21
|
// Helper function to set up keep-alive ping for SSE connections
|
|
@@ -43,6 +45,9 @@ const setupKeepAlive = (serverInfo, serverConfig) => {
|
|
|
43
45
|
console.log(`Keep-alive ping set up for server ${serverInfo.name} with interval ${interval / 1000} seconds`);
|
|
44
46
|
};
|
|
45
47
|
export const initUpstreamServers = async () => {
|
|
48
|
+
// Initialize OAuth clients for servers with dynamic registration
|
|
49
|
+
await initializeAllOAuthClients();
|
|
50
|
+
// Register all tools from upstream servers
|
|
46
51
|
await registerAllTools(true);
|
|
47
52
|
};
|
|
48
53
|
export const getMcpServer = (sessionId, group) => {
|
|
@@ -128,28 +133,42 @@ export const cleanupAllServers = () => {
|
|
|
128
133
|
});
|
|
129
134
|
};
|
|
130
135
|
// Helper function to create transport based on server configuration
|
|
131
|
-
const createTransportFromConfig = (name, conf) => {
|
|
136
|
+
export const createTransportFromConfig = async (name, conf) => {
|
|
132
137
|
let transport;
|
|
133
138
|
if (conf.type === 'streamable-http') {
|
|
134
139
|
const options = {};
|
|
135
|
-
|
|
140
|
+
const headers = conf.headers ? replaceEnvVars(conf.headers) : {};
|
|
141
|
+
if (Object.keys(headers).length > 0) {
|
|
136
142
|
options.requestInit = {
|
|
137
|
-
headers
|
|
143
|
+
headers,
|
|
138
144
|
};
|
|
139
145
|
}
|
|
146
|
+
// Create OAuth provider if configured - SDK will handle authentication automatically
|
|
147
|
+
const authProvider = await createOAuthProvider(name, conf);
|
|
148
|
+
if (authProvider) {
|
|
149
|
+
options.authProvider = authProvider;
|
|
150
|
+
console.log(`OAuth provider configured for server: ${name}`);
|
|
151
|
+
}
|
|
140
152
|
transport = new StreamableHTTPClientTransport(new URL(conf.url || ''), options);
|
|
141
153
|
}
|
|
142
154
|
else if (conf.url) {
|
|
143
155
|
// SSE transport
|
|
144
156
|
const options = {};
|
|
145
|
-
|
|
157
|
+
const headers = conf.headers ? replaceEnvVars(conf.headers) : {};
|
|
158
|
+
if (Object.keys(headers).length > 0) {
|
|
146
159
|
options.eventSourceInit = {
|
|
147
|
-
headers
|
|
160
|
+
headers,
|
|
148
161
|
};
|
|
149
162
|
options.requestInit = {
|
|
150
|
-
headers
|
|
163
|
+
headers,
|
|
151
164
|
};
|
|
152
165
|
}
|
|
166
|
+
// Create OAuth provider if configured - SDK will handle authentication automatically
|
|
167
|
+
const authProvider = await createOAuthProvider(name, conf);
|
|
168
|
+
if (authProvider) {
|
|
169
|
+
options.authProvider = authProvider;
|
|
170
|
+
console.log(`OAuth provider configured for server: ${name}`);
|
|
171
|
+
}
|
|
153
172
|
transport = new SSEClientTransport(new URL(conf.url), options);
|
|
154
173
|
}
|
|
155
174
|
else if (conf.command && conf.args) {
|
|
@@ -173,6 +192,7 @@ const createTransportFromConfig = (name, conf) => {
|
|
|
173
192
|
conf.command === 'node')) {
|
|
174
193
|
env['npm_config_registry'] = settings.systemConfig.install.npmRegistry;
|
|
175
194
|
}
|
|
195
|
+
// Expand environment variables in command
|
|
176
196
|
transport = new StdioClientTransport({
|
|
177
197
|
cwd: os.homedir(),
|
|
178
198
|
command: conf.command,
|
|
@@ -222,7 +242,7 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
|
|
|
222
242
|
throw new Error(`Server configuration not found for: ${serverInfo.name}`);
|
|
223
243
|
}
|
|
224
244
|
// Recreate transport using helper function
|
|
225
|
-
const newTransport = createTransportFromConfig(serverInfo.name, server);
|
|
245
|
+
const newTransport = await createTransportFromConfig(serverInfo.name, server);
|
|
226
246
|
// Create new client
|
|
227
247
|
const client = new Client({
|
|
228
248
|
name: `mcp-client-${serverInfo.name}`,
|
|
@@ -282,194 +302,230 @@ const callToolWithReconnect = async (serverInfo, toolParams, options, maxRetries
|
|
|
282
302
|
export const initializeClientsFromSettings = async (isInit, serverName) => {
|
|
283
303
|
const allServers = await serverDao.findAll();
|
|
284
304
|
const existingServerInfos = serverInfos;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
error: null,
|
|
296
|
-
tools: [],
|
|
297
|
-
prompts: [],
|
|
298
|
-
createTime: Date.now(),
|
|
299
|
-
enabled: false,
|
|
300
|
-
});
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
// Check if server is already connected
|
|
304
|
-
const existingServer = existingServerInfos.find((s) => s.name === name && s.status === 'connected');
|
|
305
|
-
if (existingServer && (!serverName || serverName !== name)) {
|
|
306
|
-
serverInfos.push({
|
|
307
|
-
...existingServer,
|
|
308
|
-
enabled: conf.enabled === undefined ? true : conf.enabled,
|
|
309
|
-
});
|
|
310
|
-
console.log(`Server '${name}' is already connected.`);
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
let transport;
|
|
314
|
-
let openApiClient;
|
|
315
|
-
if (conf.type === 'openapi') {
|
|
316
|
-
// Handle OpenAPI type servers
|
|
317
|
-
if (!conf.openapi?.url && !conf.openapi?.schema) {
|
|
318
|
-
console.warn(`Skipping OpenAPI server '${name}': missing OpenAPI specification URL or schema`);
|
|
319
|
-
serverInfos.push({
|
|
305
|
+
const nextServerInfos = [];
|
|
306
|
+
try {
|
|
307
|
+
for (const conf of allServers) {
|
|
308
|
+
const { name } = conf;
|
|
309
|
+
// Expand environment variables in all configuration values
|
|
310
|
+
const expandedConf = replaceEnvVars(conf);
|
|
311
|
+
// Skip disabled servers
|
|
312
|
+
if (expandedConf.enabled === false) {
|
|
313
|
+
console.log(`Skipping disabled server: ${name}`);
|
|
314
|
+
nextServerInfos.push({
|
|
320
315
|
name,
|
|
321
|
-
owner:
|
|
316
|
+
owner: expandedConf.owner,
|
|
322
317
|
status: 'disconnected',
|
|
323
|
-
error:
|
|
318
|
+
error: null,
|
|
324
319
|
tools: [],
|
|
325
320
|
prompts: [],
|
|
326
321
|
createTime: Date.now(),
|
|
322
|
+
enabled: false,
|
|
323
|
+
});
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
// Check if server is already connected
|
|
327
|
+
const existingServer = existingServerInfos.find((s) => s.name === name && s.status === 'connected');
|
|
328
|
+
if (existingServer && (!serverName || serverName !== name)) {
|
|
329
|
+
nextServerInfos.push({
|
|
330
|
+
...existingServer,
|
|
331
|
+
enabled: expandedConf.enabled === undefined ? true : expandedConf.enabled,
|
|
327
332
|
});
|
|
333
|
+
console.log(`Server '${name}' is already connected.`);
|
|
328
334
|
continue;
|
|
329
335
|
}
|
|
336
|
+
let transport;
|
|
337
|
+
let openApiClient;
|
|
338
|
+
if (expandedConf.type === 'openapi') {
|
|
339
|
+
// Handle OpenAPI type servers
|
|
340
|
+
if (!expandedConf.openapi?.url && !expandedConf.openapi?.schema) {
|
|
341
|
+
console.warn(`Skipping OpenAPI server '${name}': missing OpenAPI specification URL or schema`);
|
|
342
|
+
nextServerInfos.push({
|
|
343
|
+
name,
|
|
344
|
+
owner: expandedConf.owner,
|
|
345
|
+
status: 'disconnected',
|
|
346
|
+
error: 'Missing OpenAPI specification URL or schema',
|
|
347
|
+
tools: [],
|
|
348
|
+
prompts: [],
|
|
349
|
+
createTime: Date.now(),
|
|
350
|
+
});
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
// Create server info first and keep reference to it
|
|
354
|
+
const serverInfo = {
|
|
355
|
+
name,
|
|
356
|
+
owner: expandedConf.owner,
|
|
357
|
+
status: 'connecting',
|
|
358
|
+
error: null,
|
|
359
|
+
tools: [],
|
|
360
|
+
prompts: [],
|
|
361
|
+
createTime: Date.now(),
|
|
362
|
+
enabled: expandedConf.enabled === undefined ? true : expandedConf.enabled,
|
|
363
|
+
config: expandedConf, // Store reference to expanded config for OpenAPI passthrough headers
|
|
364
|
+
};
|
|
365
|
+
nextServerInfos.push(serverInfo);
|
|
366
|
+
try {
|
|
367
|
+
// Create OpenAPI client instance
|
|
368
|
+
openApiClient = new OpenAPIClient(expandedConf);
|
|
369
|
+
console.log(`Initializing OpenAPI server: ${name}...`);
|
|
370
|
+
// Perform async initialization
|
|
371
|
+
await openApiClient.initialize();
|
|
372
|
+
// Convert OpenAPI tools to MCP tool format
|
|
373
|
+
const openApiTools = openApiClient.getTools();
|
|
374
|
+
const mcpTools = openApiTools.map((tool) => ({
|
|
375
|
+
name: `${name}${getNameSeparator()}${tool.name}`,
|
|
376
|
+
description: tool.description,
|
|
377
|
+
inputSchema: cleanInputSchema(tool.inputSchema),
|
|
378
|
+
}));
|
|
379
|
+
// Update server info with successful initialization
|
|
380
|
+
serverInfo.status = 'connected';
|
|
381
|
+
serverInfo.tools = mcpTools;
|
|
382
|
+
serverInfo.openApiClient = openApiClient;
|
|
383
|
+
console.log(`Successfully initialized OpenAPI server: ${name} with ${mcpTools.length} tools`);
|
|
384
|
+
// Save tools as vector embeddings for search
|
|
385
|
+
saveToolsAsVectorEmbeddings(name, mcpTools);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
console.error(`Failed to initialize OpenAPI server ${name}:`, error);
|
|
390
|
+
// Update the already pushed server info with error status
|
|
391
|
+
serverInfo.status = 'disconnected';
|
|
392
|
+
serverInfo.error = `Failed to initialize OpenAPI server: ${error}`;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
transport = await createTransportFromConfig(name, expandedConf);
|
|
398
|
+
}
|
|
399
|
+
const client = new Client({
|
|
400
|
+
name: `mcp-client-${name}`,
|
|
401
|
+
version: '1.0.0',
|
|
402
|
+
}, {
|
|
403
|
+
capabilities: {
|
|
404
|
+
prompts: {},
|
|
405
|
+
resources: {},
|
|
406
|
+
tools: {},
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
const initRequestOptions = isInit
|
|
410
|
+
? {
|
|
411
|
+
timeout: Number(config.initTimeout) || 60000,
|
|
412
|
+
}
|
|
413
|
+
: undefined;
|
|
414
|
+
// Get request options from server configuration, with fallbacks
|
|
415
|
+
const serverRequestOptions = expandedConf.options || {};
|
|
416
|
+
const requestOptions = {
|
|
417
|
+
timeout: serverRequestOptions.timeout || 60000,
|
|
418
|
+
resetTimeoutOnProgress: serverRequestOptions.resetTimeoutOnProgress || false,
|
|
419
|
+
maxTotalTimeout: serverRequestOptions.maxTotalTimeout,
|
|
420
|
+
};
|
|
330
421
|
// Create server info first and keep reference to it
|
|
331
422
|
const serverInfo = {
|
|
332
423
|
name,
|
|
333
|
-
owner:
|
|
424
|
+
owner: expandedConf.owner,
|
|
334
425
|
status: 'connecting',
|
|
335
426
|
error: null,
|
|
336
427
|
tools: [],
|
|
337
428
|
prompts: [],
|
|
429
|
+
client,
|
|
430
|
+
transport,
|
|
431
|
+
options: requestOptions,
|
|
338
432
|
createTime: Date.now(),
|
|
339
|
-
|
|
340
|
-
config: conf, // Store reference to original config for OpenAPI passthrough headers
|
|
433
|
+
config: expandedConf, // Store reference to expanded config
|
|
341
434
|
};
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
openApiClient = new OpenAPIClient(conf);
|
|
346
|
-
console.log(`Initializing OpenAPI server: ${name}...`);
|
|
347
|
-
// Perform async initialization
|
|
348
|
-
await openApiClient.initialize();
|
|
349
|
-
// Convert OpenAPI tools to MCP tool format
|
|
350
|
-
const openApiTools = openApiClient.getTools();
|
|
351
|
-
const mcpTools = openApiTools.map((tool) => ({
|
|
352
|
-
name: `${name}${getNameSeparator()}${tool.name}`,
|
|
353
|
-
description: tool.description,
|
|
354
|
-
inputSchema: cleanInputSchema(tool.inputSchema),
|
|
355
|
-
}));
|
|
356
|
-
// Update server info with successful initialization
|
|
357
|
-
serverInfo.status = 'connected';
|
|
358
|
-
serverInfo.tools = mcpTools;
|
|
359
|
-
serverInfo.openApiClient = openApiClient;
|
|
360
|
-
console.log(`Successfully initialized OpenAPI server: ${name} with ${mcpTools.length} tools`);
|
|
361
|
-
// Save tools as vector embeddings for search
|
|
362
|
-
saveToolsAsVectorEmbeddings(name, mcpTools);
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
catch (error) {
|
|
366
|
-
console.error(`Failed to initialize OpenAPI server ${name}:`, error);
|
|
367
|
-
// Update the already pushed server info with error status
|
|
368
|
-
serverInfo.status = 'disconnected';
|
|
369
|
-
serverInfo.error = `Failed to initialize OpenAPI server: ${error}`;
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
transport = createTransportFromConfig(name, conf);
|
|
375
|
-
}
|
|
376
|
-
const client = new Client({
|
|
377
|
-
name: `mcp-client-${name}`,
|
|
378
|
-
version: '1.0.0',
|
|
379
|
-
}, {
|
|
380
|
-
capabilities: {
|
|
381
|
-
prompts: {},
|
|
382
|
-
resources: {},
|
|
383
|
-
tools: {},
|
|
384
|
-
},
|
|
385
|
-
});
|
|
386
|
-
const initRequestOptions = isInit
|
|
387
|
-
? {
|
|
388
|
-
timeout: Number(config.initTimeout) || 60000,
|
|
389
|
-
}
|
|
390
|
-
: undefined;
|
|
391
|
-
// Get request options from server configuration, with fallbacks
|
|
392
|
-
const serverRequestOptions = conf.options || {};
|
|
393
|
-
const requestOptions = {
|
|
394
|
-
timeout: serverRequestOptions.timeout || 60000,
|
|
395
|
-
resetTimeoutOnProgress: serverRequestOptions.resetTimeoutOnProgress || false,
|
|
396
|
-
maxTotalTimeout: serverRequestOptions.maxTotalTimeout,
|
|
397
|
-
};
|
|
398
|
-
// Create server info first and keep reference to it
|
|
399
|
-
const serverInfo = {
|
|
400
|
-
name,
|
|
401
|
-
owner: conf.owner,
|
|
402
|
-
status: 'connecting',
|
|
403
|
-
error: null,
|
|
404
|
-
tools: [],
|
|
405
|
-
prompts: [],
|
|
406
|
-
client,
|
|
407
|
-
transport,
|
|
408
|
-
options: requestOptions,
|
|
409
|
-
createTime: Date.now(),
|
|
410
|
-
config: conf, // Store reference to original config
|
|
411
|
-
};
|
|
412
|
-
serverInfos.push(serverInfo);
|
|
413
|
-
client
|
|
414
|
-
.connect(transport, initRequestOptions || requestOptions)
|
|
415
|
-
.then(() => {
|
|
416
|
-
console.log(`Successfully connected client for server: ${name}`);
|
|
417
|
-
const capabilities = client.getServerCapabilities();
|
|
418
|
-
console.log(`Server capabilities: ${JSON.stringify(capabilities)}`);
|
|
419
|
-
let dataError = null;
|
|
420
|
-
if (capabilities?.tools) {
|
|
421
|
-
client
|
|
422
|
-
.listTools({}, initRequestOptions || requestOptions)
|
|
423
|
-
.then((tools) => {
|
|
424
|
-
console.log(`Successfully listed ${tools.tools.length} tools for server: ${name}`);
|
|
425
|
-
serverInfo.tools = tools.tools.map((tool) => ({
|
|
426
|
-
name: `${name}${getNameSeparator()}${tool.name}`,
|
|
427
|
-
description: tool.description || '',
|
|
428
|
-
inputSchema: cleanInputSchema(tool.inputSchema || {}),
|
|
429
|
-
}));
|
|
430
|
-
// Save tools as vector embeddings for search
|
|
431
|
-
saveToolsAsVectorEmbeddings(name, serverInfo.tools);
|
|
432
|
-
})
|
|
433
|
-
.catch((error) => {
|
|
434
|
-
console.error(`Failed to list tools for server ${name} by error: ${error} with stack: ${error.stack}`);
|
|
435
|
-
dataError = error;
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
if (capabilities?.prompts) {
|
|
439
|
-
client
|
|
440
|
-
.listPrompts({}, initRequestOptions || requestOptions)
|
|
441
|
-
.then((prompts) => {
|
|
442
|
-
console.log(`Successfully listed ${prompts.prompts.length} prompts for server: ${name}`);
|
|
443
|
-
serverInfo.prompts = prompts.prompts.map((prompt) => ({
|
|
444
|
-
name: `${name}${getNameSeparator()}${prompt.name}`,
|
|
445
|
-
title: prompt.title,
|
|
446
|
-
description: prompt.description,
|
|
447
|
-
arguments: prompt.arguments,
|
|
448
|
-
}));
|
|
449
|
-
})
|
|
450
|
-
.catch((error) => {
|
|
451
|
-
console.error(`Failed to list prompts for server ${name} by error: ${error} with stack: ${error.stack}`);
|
|
452
|
-
dataError = error;
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
if (!dataError) {
|
|
456
|
-
serverInfo.status = 'connected';
|
|
435
|
+
const pendingAuth = expandedConf.oauth?.pendingAuthorization;
|
|
436
|
+
if (pendingAuth) {
|
|
437
|
+
serverInfo.status = 'oauth_required';
|
|
457
438
|
serverInfo.error = null;
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
serverInfo.error = `Failed to list data: ${dataError} `;
|
|
439
|
+
serverInfo.oauth = {
|
|
440
|
+
authorizationUrl: pendingAuth.authorizationUrl,
|
|
441
|
+
state: pendingAuth.state,
|
|
442
|
+
codeVerifier: pendingAuth.codeVerifier,
|
|
443
|
+
};
|
|
464
444
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
445
|
+
nextServerInfos.push(serverInfo);
|
|
446
|
+
client
|
|
447
|
+
.connect(transport, initRequestOptions || requestOptions)
|
|
448
|
+
.then(() => {
|
|
449
|
+
console.log(`Successfully connected client for server: ${name}`);
|
|
450
|
+
const capabilities = client.getServerCapabilities();
|
|
451
|
+
console.log(`Server capabilities: ${JSON.stringify(capabilities)}`);
|
|
452
|
+
let dataError = null;
|
|
453
|
+
if (capabilities?.tools) {
|
|
454
|
+
client
|
|
455
|
+
.listTools({}, initRequestOptions || requestOptions)
|
|
456
|
+
.then((tools) => {
|
|
457
|
+
console.log(`Successfully listed ${tools.tools.length} tools for server: ${name}`);
|
|
458
|
+
serverInfo.tools = tools.tools.map((tool) => ({
|
|
459
|
+
name: `${name}${getNameSeparator()}${tool.name}`,
|
|
460
|
+
description: tool.description || '',
|
|
461
|
+
inputSchema: cleanInputSchema(tool.inputSchema || {}),
|
|
462
|
+
}));
|
|
463
|
+
// Save tools as vector embeddings for search
|
|
464
|
+
saveToolsAsVectorEmbeddings(name, serverInfo.tools);
|
|
465
|
+
})
|
|
466
|
+
.catch((error) => {
|
|
467
|
+
console.error(`Failed to list tools for server ${name} by error: ${error} with stack: ${error.stack}`);
|
|
468
|
+
dataError = error;
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
if (capabilities?.prompts) {
|
|
472
|
+
client
|
|
473
|
+
.listPrompts({}, initRequestOptions || requestOptions)
|
|
474
|
+
.then((prompts) => {
|
|
475
|
+
console.log(`Successfully listed ${prompts.prompts.length} prompts for server: ${name}`);
|
|
476
|
+
serverInfo.prompts = prompts.prompts.map((prompt) => ({
|
|
477
|
+
name: `${name}${getNameSeparator()}${prompt.name}`,
|
|
478
|
+
title: prompt.title,
|
|
479
|
+
description: prompt.description,
|
|
480
|
+
arguments: prompt.arguments,
|
|
481
|
+
}));
|
|
482
|
+
})
|
|
483
|
+
.catch((error) => {
|
|
484
|
+
console.error(`Failed to list prompts for server ${name} by error: ${error} with stack: ${error.stack}`);
|
|
485
|
+
dataError = error;
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
if (!dataError) {
|
|
489
|
+
serverInfo.status = 'connected';
|
|
490
|
+
serverInfo.error = null;
|
|
491
|
+
// Set up keep-alive ping for SSE connections
|
|
492
|
+
setupKeepAlive(serverInfo, expandedConf);
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
serverInfo.status = 'disconnected';
|
|
496
|
+
serverInfo.error = `Failed to list data: ${dataError} `;
|
|
497
|
+
}
|
|
498
|
+
})
|
|
499
|
+
.catch(async (error) => {
|
|
500
|
+
// Check if this is an OAuth authorization error
|
|
501
|
+
const isOAuthError = error?.message?.includes('OAuth authorization required') ||
|
|
502
|
+
error?.message?.includes('Authorization required');
|
|
503
|
+
if (isOAuthError) {
|
|
504
|
+
// OAuth provider should have already set the status to 'oauth_required'
|
|
505
|
+
// and stored the authorization URL in serverInfo.oauth
|
|
506
|
+
console.log(`OAuth authorization required for server ${name}. Status should be set to 'oauth_required'.`);
|
|
507
|
+
// Make sure status is set correctly
|
|
508
|
+
if (serverInfo.status !== 'oauth_required') {
|
|
509
|
+
serverInfo.status = 'oauth_required';
|
|
510
|
+
}
|
|
511
|
+
serverInfo.error = null;
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
console.error(`Failed to connect client for server ${name} by error: ${error} with stack: ${error.stack}`);
|
|
515
|
+
// Other connection errors
|
|
516
|
+
serverInfo.status = 'disconnected';
|
|
517
|
+
serverInfo.error = `Failed to connect: ${error.stack} `;
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
console.log(`Initialized client for server: ${name}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
// Restore previous state if initialization fails to avoid exposing an empty server list
|
|
525
|
+
serverInfos = existingServerInfos;
|
|
526
|
+
throw error;
|
|
472
527
|
}
|
|
528
|
+
serverInfos = nextServerInfos;
|
|
473
529
|
return serverInfos;
|
|
474
530
|
};
|
|
475
531
|
// Register all MCP tools
|
|
@@ -483,7 +539,7 @@ export const getServersInfo = async () => {
|
|
|
483
539
|
const filterServerInfos = dataService.filterData
|
|
484
540
|
? dataService.filterData(serverInfos)
|
|
485
541
|
: serverInfos;
|
|
486
|
-
const infos = filterServerInfos.map(({ name, status, tools, prompts, createTime, error }) => {
|
|
542
|
+
const infos = filterServerInfos.map(({ name, status, tools, prompts, createTime, error, oauth }) => {
|
|
487
543
|
const serverConfig = allServers.find((server) => server.name === name);
|
|
488
544
|
const enabled = serverConfig ? serverConfig.enabled !== false : true;
|
|
489
545
|
// Add enabled status and custom description to each tool
|
|
@@ -511,6 +567,13 @@ export const getServersInfo = async () => {
|
|
|
511
567
|
prompts: promptsWithEnabled,
|
|
512
568
|
createTime,
|
|
513
569
|
enabled,
|
|
570
|
+
oauth: oauth
|
|
571
|
+
? {
|
|
572
|
+
authorizationUrl: oauth.authorizationUrl,
|
|
573
|
+
state: oauth.state,
|
|
574
|
+
// Don't expose codeVerifier to frontend for security
|
|
575
|
+
}
|
|
576
|
+
: undefined,
|
|
514
577
|
};
|
|
515
578
|
});
|
|
516
579
|
infos.sort((a, b) => {
|
|
@@ -524,6 +587,45 @@ export const getServersInfo = async () => {
|
|
|
524
587
|
export const getServerByName = (name) => {
|
|
525
588
|
return serverInfos.find((serverInfo) => serverInfo.name === name);
|
|
526
589
|
};
|
|
590
|
+
// Get server by OAuth state parameter
|
|
591
|
+
export const getServerByOAuthState = (state) => {
|
|
592
|
+
return serverInfos.find((serverInfo) => serverInfo.oauth?.state === state);
|
|
593
|
+
};
|
|
594
|
+
/**
|
|
595
|
+
* Reconnect a server after OAuth authorization or configuration change
|
|
596
|
+
* This will close the existing connection and reinitialize the server
|
|
597
|
+
*/
|
|
598
|
+
export const reconnectServer = async (serverName) => {
|
|
599
|
+
console.log(`Reconnecting server: ${serverName}`);
|
|
600
|
+
const serverInfo = getServerByName(serverName);
|
|
601
|
+
if (!serverInfo) {
|
|
602
|
+
throw new Error(`Server not found: ${serverName}`);
|
|
603
|
+
}
|
|
604
|
+
// Close existing connection if any
|
|
605
|
+
if (serverInfo.client) {
|
|
606
|
+
try {
|
|
607
|
+
serverInfo.client.close();
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
console.warn(`Error closing client for server ${serverName}:`, error);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (serverInfo.transport) {
|
|
614
|
+
try {
|
|
615
|
+
serverInfo.transport.close();
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
console.warn(`Error closing transport for server ${serverName}:`, error);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (serverInfo.keepAliveIntervalId) {
|
|
622
|
+
clearInterval(serverInfo.keepAliveIntervalId);
|
|
623
|
+
serverInfo.keepAliveIntervalId = undefined;
|
|
624
|
+
}
|
|
625
|
+
// Reinitialize the server
|
|
626
|
+
await initializeClientsFromSettings(false, serverName);
|
|
627
|
+
console.log(`Successfully reconnected server: ${serverName}`);
|
|
628
|
+
};
|
|
527
629
|
// Filter tools by server configuration
|
|
528
630
|
const filterToolsByConfig = async (serverName, tools) => {
|
|
529
631
|
const serverConfig = await serverDao.findById(serverName);
|
|
@@ -635,28 +737,39 @@ export const handleListToolsRequest = async (_, extra) => {
|
|
|
635
737
|
const group = getGroup(sessionId);
|
|
636
738
|
console.log(`Handling ListToolsRequest for group: ${group}`);
|
|
637
739
|
// Special handling for $smart group to return special tools
|
|
638
|
-
|
|
740
|
+
// Support both $smart and $smart/{group} patterns
|
|
741
|
+
if (group === '$smart' || group?.startsWith('$smart/')) {
|
|
742
|
+
// Extract target group if pattern is $smart/{group}
|
|
743
|
+
const targetGroup = group?.startsWith('$smart/') ? group.substring(7) : undefined;
|
|
744
|
+
// Get info about available servers, filtered by target group if specified
|
|
745
|
+
let availableServers = serverInfos.filter((server) => server.status === 'connected' && server.enabled !== false);
|
|
746
|
+
// If a target group is specified, filter servers to only those in the group
|
|
747
|
+
if (targetGroup) {
|
|
748
|
+
const serversInGroup = getServersInGroup(targetGroup);
|
|
749
|
+
if (serversInGroup && serversInGroup.length > 0) {
|
|
750
|
+
availableServers = availableServers.filter((server) => serversInGroup.includes(server.name));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// Create simple server information with only server names
|
|
754
|
+
const serversList = availableServers
|
|
755
|
+
.map((server) => {
|
|
756
|
+
return `${server.name}`;
|
|
757
|
+
})
|
|
758
|
+
.join(', ');
|
|
759
|
+
const scopeDescription = targetGroup
|
|
760
|
+
? `servers in the "${targetGroup}" group`
|
|
761
|
+
: 'all available servers';
|
|
639
762
|
return {
|
|
640
763
|
tools: [
|
|
641
764
|
{
|
|
642
765
|
name: 'search_tools',
|
|
643
|
-
description:
|
|
644
|
-
// Get info about available servers
|
|
645
|
-
const availableServers = serverInfos.filter((server) => server.status === 'connected' && server.enabled !== false);
|
|
646
|
-
// Create simple server information with only server names
|
|
647
|
-
const serversList = availableServers
|
|
648
|
-
.map((server) => {
|
|
649
|
-
return `${server.name}`;
|
|
650
|
-
})
|
|
651
|
-
.join(', ');
|
|
652
|
-
return `STEP 1 of 2: Use this tool FIRST to discover and search for relevant tools across all available servers. This tool and call_tool work together as a two-step process: 1) search_tools to find what you need, 2) call_tool to execute it.
|
|
766
|
+
description: `STEP 1 of 2: Use this tool FIRST to discover and search for relevant tools across ${scopeDescription}. This tool and call_tool work together as a two-step process: 1) search_tools to find what you need, 2) call_tool to execute it.
|
|
653
767
|
|
|
654
768
|
For optimal results, use specific queries matching your exact needs. Call this tool multiple times with different queries for different parts of complex tasks. Example queries: "image generation tools", "code review tools", "data analysis", "translation capabilities", etc. Results are sorted by relevance using vector similarity.
|
|
655
769
|
|
|
656
770
|
After finding relevant tools, you MUST use the call_tool to actually execute them. The search_tools only finds tools - it doesn't execute them.
|
|
657
771
|
|
|
658
|
-
Available servers: ${serversList}
|
|
659
|
-
})(),
|
|
772
|
+
Available servers: ${serversList}`,
|
|
660
773
|
inputSchema: {
|
|
661
774
|
type: 'object',
|
|
662
775
|
properties: {
|
|
@@ -757,7 +870,24 @@ export const handleCallToolRequest = async (request, extra) => {
|
|
|
757
870
|
thresholdNum = 0.4;
|
|
758
871
|
}
|
|
759
872
|
console.log(`Using similarity threshold: ${thresholdNum} for query: "${query}"`);
|
|
760
|
-
|
|
873
|
+
// Determine server filtering based on group
|
|
874
|
+
const sessionId = extra.sessionId || '';
|
|
875
|
+
const group = getGroup(sessionId);
|
|
876
|
+
let servers = undefined; // No server filtering by default
|
|
877
|
+
// If group is in format $smart/{group}, filter servers to that group
|
|
878
|
+
if (group?.startsWith('$smart/')) {
|
|
879
|
+
const targetGroup = group.substring(7);
|
|
880
|
+
const serversInGroup = getServersInGroup(targetGroup);
|
|
881
|
+
if (serversInGroup !== undefined && serversInGroup !== null) {
|
|
882
|
+
servers = serversInGroup;
|
|
883
|
+
if (servers.length > 0) {
|
|
884
|
+
console.log(`Filtering search to servers in group "${targetGroup}": ${servers.join(', ')}`);
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
console.log(`Group "${targetGroup}" has no servers, search will return no results`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
761
891
|
const searchResults = await searchToolsByVector(query, limitNum, thresholdNum, servers);
|
|
762
892
|
console.log(`Search results: ${JSON.stringify(searchResults)}`);
|
|
763
893
|
// Find actual tool information from serverInfos by serverName and toolName
|