diff --git a/packages/cli/src/ui/commands/mcpCommand.test.ts b/packages/cli/src/ui/commands/mcpCommand.test.ts index 2a6401b3..ad04cb69 100644 --- a/packages/cli/src/ui/commands/mcpCommand.test.ts +++ b/packages/cli/src/ui/commands/mcpCommand.test.ts @@ -212,9 +212,9 @@ describe('mcpCommand', () => { ); expect(message).toContain('server2_tool1'); - // Server 3 - Disconnected + // Server 3 - Disconnected but with cached tools, so shows as Ready expect(message).toContain( - '🔴 \u001b[1mserver3\u001b[0m - Disconnected (1 tools cached)', + '🟢 \u001b[1mserver3\u001b[0m - Ready (1 tool)', ); expect(message).toContain('server3_tool1'); diff --git a/packages/cli/src/ui/commands/mcpCommand.ts b/packages/cli/src/ui/commands/mcpCommand.ts index dc5442cc..11c71f1a 100644 --- a/packages/cli/src/ui/commands/mcpCommand.ts +++ b/packages/cli/src/ui/commands/mcpCommand.ts @@ -94,7 +94,15 @@ const getMcpStatus = async ( const promptRegistry = await config.getPromptRegistry(); const serverPrompts = promptRegistry.getPromptsByServer(serverName) || []; - const status = getMCPServerStatus(serverName); + const originalStatus = getMCPServerStatus(serverName); + const hasCachedItems = serverTools.length > 0 || serverPrompts.length > 0; + + // If the server is "disconnected" but has prompts or cached tools, display it as Ready + // by using CONNECTED as the display status. + const status = + originalStatus === MCPServerStatus.DISCONNECTED && hasCachedItems + ? MCPServerStatus.CONNECTED + : originalStatus; // Add status indicator with descriptive text let statusIndicator = ''; @@ -260,11 +268,14 @@ const getMcpStatus = async ( message += ' No tools or prompts available\n'; } else if (serverTools.length === 0) { message += ' No tools available'; - if (status === MCPServerStatus.DISCONNECTED && needsAuthHint) { + if (originalStatus === MCPServerStatus.DISCONNECTED && needsAuthHint) { message += ` ${COLOR_GREY}(type: "/mcp auth ${serverName}" to authenticate this server)${RESET_COLOR}`; } message += '\n'; - } else if (status === MCPServerStatus.DISCONNECTED && needsAuthHint) { + } else if ( + originalStatus === MCPServerStatus.DISCONNECTED && + needsAuthHint + ) { // This case is for when serverTools.length > 0 message += ` ${COLOR_GREY}(type: "/mcp auth ${serverName}" to authenticate this server)${RESET_COLOR}\n`; } diff --git a/packages/core/src/tools/mcp-client.ts b/packages/core/src/tools/mcp-client.ts index f9ccc380..00f2197a 100644 --- a/packages/core/src/tools/mcp-client.ts +++ b/packages/core/src/tools/mcp-client.ts @@ -366,33 +366,47 @@ export async function connectAndDiscover( ): Promise { updateMCPServerStatus(mcpServerName, MCPServerStatus.CONNECTING); + let mcpClient: Client | undefined; try { - const mcpClient = await connectToMcpServer( + mcpClient = await connectToMcpServer( mcpServerName, mcpServerConfig, debugMode, ); - try { - updateMCPServerStatus(mcpServerName, MCPServerStatus.CONNECTED); - mcpClient.onerror = (error) => { - console.error(`MCP ERROR (${mcpServerName}):`, error.toString()); - updateMCPServerStatus(mcpServerName, MCPServerStatus.DISCONNECTED); - }; - await discoverPrompts(mcpServerName, mcpClient, promptRegistry); - const tools = await discoverTools( - mcpServerName, - mcpServerConfig, - mcpClient, - ); - for (const tool of tools) { - toolRegistry.registerTool(tool); - } - } catch (error) { - mcpClient.close(); - throw error; + mcpClient.onerror = (error) => { + console.error(`MCP ERROR (${mcpServerName}):`, error.toString()); + updateMCPServerStatus(mcpServerName, MCPServerStatus.DISCONNECTED); + }; + + // Attempt to discover both prompts and tools + const prompts = await discoverPrompts( + mcpServerName, + mcpClient, + promptRegistry, + ); + const tools = await discoverTools( + mcpServerName, + mcpServerConfig, + mcpClient, + ); + + // If we have neither prompts nor tools, it's a failed discovery + if (prompts.length === 0 && tools.length === 0) { + throw new Error('No prompts or tools found on the server.'); + } + + // If we found anything, the server is connected + updateMCPServerStatus(mcpServerName, MCPServerStatus.CONNECTED); + + // Register any discovered tools + for (const tool of tools) { + toolRegistry.registerTool(tool); } } catch (error) { + if (mcpClient) { + mcpClient.close(); + } console.error( `Error connecting to MCP server '${mcpServerName}': ${getErrorMessage( error, @@ -423,7 +437,8 @@ export async function discoverTools( const tool = await mcpCallableTool.tool(); if (!Array.isArray(tool.functionDeclarations)) { - throw new Error(`Server did not return valid function declarations.`); + // This is a valid case for a prompt-only server + return []; } const discoveredTools: DiscoveredMCPTool[] = []; @@ -454,7 +469,17 @@ export async function discoverTools( } return discoveredTools; } catch (error) { - throw new Error(`Error discovering tools: ${error}`); + if ( + error instanceof Error && + !error.message?.includes('Method not found') + ) { + console.error( + `Error discovering tools from ${mcpServerName}: ${getErrorMessage( + error, + )}`, + ); + } + return []; } } @@ -469,7 +494,7 @@ export async function discoverPrompts( mcpServerName: string, mcpClient: Client, promptRegistry: PromptRegistry, -): Promise { +): Promise { try { const response = await mcpClient.request( { method: 'prompts/list', params: {} }, @@ -484,6 +509,7 @@ export async function discoverPrompts( invokeMcpPrompt(mcpServerName, mcpClient, prompt.name, params), }); } + return response.prompts; } catch (error) { // It's okay if this fails, not all servers will have prompts. // Don't log an error if the method is not found, which is a common case. @@ -497,6 +523,7 @@ export async function discoverPrompts( )}`, ); } + return []; } }