(
userId: string,
workspaceId: string,
forceRefresh = false
)
| 421 | } |
| 422 | |
| 423 | async discoverTools( |
| 424 | userId: string, |
| 425 | workspaceId: string, |
| 426 | forceRefresh = false |
| 427 | ): Promise<McpTool[]> { |
| 428 | const requestId = generateRequestId() |
| 429 | |
| 430 | try { |
| 431 | logger.info(`[${requestId}] Discovering MCP tools for workspace ${workspaceId}`) |
| 432 | |
| 433 | const servers = await this.getWorkspaceServers(workspaceId) |
| 434 | |
| 435 | if (servers.length === 0) { |
| 436 | logger.info(`[${requestId}] No servers found for workspace ${workspaceId}`) |
| 437 | return [] |
| 438 | } |
| 439 | |
| 440 | const outcomes = await Promise.all( |
| 441 | servers.map(async (config): Promise<DiscoveryOutcome> => { |
| 442 | const cacheKey = serverCacheKey(workspaceId, config.id) |
| 443 | |
| 444 | if (!forceRefresh) { |
| 445 | try { |
| 446 | const cached = await this.cacheAdapter.get(cacheKey) |
| 447 | if (cached) return { kind: 'cached', tools: cached.tools } |
| 448 | } catch (error) { |
| 449 | logger.warn( |
| 450 | `[${requestId}] Cache read failed for ${config.name}, proceeding with discovery:`, |
| 451 | error |
| 452 | ) |
| 453 | } |
| 454 | if (await this.isServerUnhealthy(workspaceId, config.id)) { |
| 455 | logger.info( |
| 456 | `[${requestId}] Skipping recently-failed server ${config.name} (negative-cache hit)` |
| 457 | ) |
| 458 | return { kind: 'unhealthy' } |
| 459 | } |
| 460 | } |
| 461 | |
| 462 | try { |
| 463 | const { config: resolvedConfig, resolvedIP } = await this.resolveConfigEnvVars( |
| 464 | config, |
| 465 | userId, |
| 466 | workspaceId |
| 467 | ) |
| 468 | const client = await this.createClient(resolvedConfig, resolvedIP, userId) |
| 469 | try { |
| 470 | const tools = await client.listTools() |
| 471 | logger.debug( |
| 472 | `[${requestId}] Discovered ${tools.length} tools from server ${config.name}` |
| 473 | ) |
| 474 | return { kind: 'fetched', tools, resolvedConfig, resolvedIP } |
| 475 | } finally { |
| 476 | await client.disconnect() |
| 477 | } |
| 478 | } catch (error) { |
| 479 | if ( |
| 480 | error instanceof McpOauthAuthorizationRequiredError || |
no test coverage detected