From 73e287d0c7f8f30375f62456dc4d3e461c32497a Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 21 Jul 2026 13:33:52 -0700 Subject: [PATCH 1/2] fix(mcp): don't blank the MCP tools page when tool discovery fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A tool-discovery error (one slow/failing server, e.g. a stalled transport) replaced the entire server list with an error banner. Gate the full-list replacement on serversError (the list genuinely failing to load) only; when the servers loaded, always render the list — each row already surfaces its own discovery state via toolsStateByServer, with a non-blocking notice above the list. Restores graceful degradation so one bad server can't hide the others. --- .../[workspaceId]/settings/components/mcp/mcp.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx index 9708b1b1ff9..0e3626d1457 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx @@ -386,7 +386,10 @@ export function MCP() { return issues } - const error = toolsError || serversError + // Only a failure to load the server LIST replaces the list. A tool-discovery failure + // (`toolsError`) must not blank the page — the servers still render, each row surfacing its + // own discovery state via `toolsStateByServer`, with a non-blocking notice above the list. + const listError = serversError const hasServers = servers && servers.length > 0 const showNoResults = searchTerm.trim() && filteredServers.length === 0 && servers.length > 0 @@ -646,10 +649,10 @@ export function MCP() { : [] } > - {error ? ( + {listError ? (

- {getErrorMessage(error, 'Failed to load MCP servers')} + {getErrorMessage(listError, 'Failed to load MCP servers')}

) : serversLoading ? null : !hasServers ? ( @@ -658,6 +661,11 @@ export function MCP() { ) : (
+ {toolsError && ( +

+ {getErrorMessage(toolsError, 'Some tools could not be discovered')} +

+ )} {filteredServers.map((server) => { if (!server?.id) return null const tools = toolsByServer[server.id] || [] From 8ad1c31f22d27649af73ecc72624b94629b91e1c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 21 Jul 2026 13:42:55 -0700 Subject: [PATCH 2/2] fix(mcp): surface partial discovery failures per-row and in the notice When one server succeeds and another fails, the aggregate toolsError is suppressed (data exists), so the failure was hidden. Now: the notice renders on ANY per-server discovery error (not just all-fail), and each failed row surfaces its live discovery error instead of reading as '0 tools' before its stored status catches up. --- .../settings/components/mcp/mcp.tsx | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx index 0e3626d1457..97d64680a7d 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx @@ -71,6 +71,7 @@ interface ServerListItemProps { isConnecting: boolean isLoadingTools?: boolean isRefreshing?: boolean + discoveryError?: string | null onRemove: () => void onViewDetails: () => void onAuthorize: () => void @@ -84,6 +85,7 @@ function ServerListItem({ isConnecting, isLoadingTools = false, isRefreshing = false, + discoveryError = null, onRemove, onViewDetails, onAuthorize, @@ -95,8 +97,17 @@ function ServerListItem({ server.lastError, server.authType ) + // A live discovery failure whose stored status hasn't caught up yet would otherwise read as + // "0 tools"; surface it directly so a failed row reads as failed, not empty. + const showDiscoveryError = + Boolean(discoveryError) && + tools.length === 0 && + server.connectionStatus !== 'error' && + server.connectionStatus !== 'disconnected' const hasConnectionIssue = - server.connectionStatus === 'error' || server.connectionStatus === 'disconnected' + server.connectionStatus === 'error' || + server.connectionStatus === 'disconnected' || + showDiscoveryError return (
@@ -117,7 +128,9 @@ function ServerListItem({ ? 'Refreshing...' : isLoadingTools && tools.length === 0 ? 'Loading...' - : toolsLabel} + : showDiscoveryError + ? discoveryError + : toolsLabel}

@@ -390,6 +403,11 @@ export function MCP() { // (`toolsError`) must not blank the page — the servers still render, each row surfacing its // own discovery state via `toolsStateByServer`, with a non-blocking notice above the list. const listError = serversError + // Any per-server discovery failure — even a partial one where other servers succeeded (which + // suppresses the aggregate `toolsError`) — so the notice below still surfaces it. + const hasDiscoveryError = + Boolean(toolsError) || + Array.from(toolsStateByServer.values()).some((state) => state.error != null) const hasServers = servers && servers.length > 0 const showNoResults = searchTerm.trim() && filteredServers.length === 0 && servers.length > 0 @@ -661,7 +679,7 @@ export function MCP() { ) : (
- {toolsError && ( + {hasDiscoveryError && (

{getErrorMessage(toolsError, 'Some tools could not be discovered')}

@@ -687,6 +705,9 @@ export function MCP() { refreshServerMutation.isPending && refreshServerMutation.variables?.serverId === server.id } + discoveryError={ + serverToolsState?.error ? getErrorMessage(serverToolsState.error) : null + } onRemove={() => handleRemoveServer(server.id)} onViewDetails={() => handleViewDetails(server.id)} onAuthorize={() => startOauthForServer(server.id)}