Skip to content

Commit a91546e

Browse files
committed
fix(mcp): address second-round bugbot review
- useMcpToolsQuery serverIds: filter on enabled + workspaceId match. Disabled rows no longer trigger discover calls that get negative-cached, and keepPreviousData on useMcpServers no longer races a workspace switch into cross-workspace discover requests. - Aggregate skips per-server data when that server's latest refetch errored, so a broken server's last-known tools no longer linger in the workspace view while its card shows an error. - discoverServerTools failure path drops the positive cache alongside writing the negative-cache marker. A cache-respecting follow-up now fails fast via cooldown instead of returning stale tools from a now-broken server. - useMcpTools.refreshTools drops the dead forceRefresh param — the per-server queryFn always sends refresh=false, so the flag was never effective. Callers wanting cache-bypass should use useForceRefreshMcpTools.
1 parent aa2d756 commit a91546e

3 files changed

Lines changed: 43 additions & 19 deletions

File tree

apps/sim/hooks/mcp/use-mcp-tools.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export interface UseMcpToolsResult {
3232
mcpTools: McpToolForUI[]
3333
isLoading: boolean
3434
error: string | null
35-
refreshTools: (forceRefresh?: boolean) => Promise<void>
35+
refreshTools: () => Promise<void>
3636
getToolsByServer: (serverId: string) => McpToolForUI[]
3737
}
3838

@@ -55,22 +55,20 @@ export function useMcpTools(workspaceId: string): UseMcpToolsResult {
5555
}))
5656
}, [mcpToolsData])
5757

58-
const refreshTools = useCallback(
59-
async (forceRefresh = false) => {
60-
if (!workspaceId) {
61-
logger.warn('Cannot refresh tools: no workspaceId provided')
62-
return
63-
}
58+
// Soft refresh: invalidate per-server query entries so React Query refetches
59+
// them, but let the server-side cache decide whether to go live (the per-server
60+
// queryFn always sends refresh=false). For an explicit cache-bypass refresh,
61+
// use `useForceRefreshMcpTools` instead.
62+
const refreshTools = useCallback(async () => {
63+
if (!workspaceId) {
64+
logger.warn('Cannot refresh tools: no workspaceId provided')
65+
return
66+
}
6467

65-
logger.info('Refreshing MCP tools', { forceRefresh, workspaceId })
66-
67-
await queryClient.invalidateQueries({
68-
queryKey: mcpKeys.serverToolsWorkspace(workspaceId),
69-
refetchType: forceRefresh ? 'active' : 'all',
70-
})
71-
},
72-
[workspaceId, queryClient]
73-
)
68+
await queryClient.invalidateQueries({
69+
queryKey: mcpKeys.serverToolsWorkspace(workspaceId),
70+
})
71+
}, [workspaceId, queryClient])
7472

7573
const getToolsByServer = useCallback(
7674
(serverId: string): McpToolForUI[] => {

apps/sim/hooks/queries/mcp.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,20 @@ export function useMcpServerTools(workspaceId: string, serverId?: string) {
140140
export function useMcpToolsQuery(workspaceId: string) {
141141
const { data: servers, isLoading: serversLoading } = useMcpServers(workspaceId)
142142

143-
const serverIds = useMemo(() => (servers ? servers.map((s) => s.id).sort() : []), [servers])
143+
// Filter on (a) enabled (disabled servers would 404 in discover and pollute the
144+
// negative cache), and (b) workspaceId match (useMcpServers keeps the previous
145+
// workspace's data via keepPreviousData, so during a workspace switch we'd
146+
// otherwise spawn discover calls for the wrong workspace's IDs).
147+
const serverIds = useMemo(
148+
() =>
149+
servers
150+
? servers
151+
.filter((s) => s.enabled && s.workspaceId === workspaceId)
152+
.map((s) => s.id)
153+
.sort()
154+
: [],
155+
[servers, workspaceId]
156+
)
144157

145158
const results = useQueries({
146159
queries: serverIds.map((serverId) => ({
@@ -160,7 +173,10 @@ export function useMcpToolsQuery(workspaceId: string) {
160173
let anyServerLoading = false
161174
let firstError: Error | null = null
162175
for (const result of results) {
163-
if (result.data) {
176+
// Drop stale data when the latest refetch failed — otherwise React Query's
177+
// stale-while-revalidate behavior would surface broken-server tools in the
178+
// aggregate while the per-server card shows an error.
179+
if (result.data && !result.isError) {
164180
tools.push(...result.data)
165181
hasData = true
166182
}

apps/sim/lib/mcp/service.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,17 @@ class McpService {
723723
await sleep(100)
724724
continue
725725
}
726-
await this.markServerUnhealthy(workspaceId, serverId, error)
726+
// Drop any stale positive cache so a follow-up cache-respecting call
727+
// doesn't keep returning old tools from a now-broken server. The
728+
// negative cache marker then makes that follow-up fail fast.
729+
await Promise.allSettled([
730+
this.cacheAdapter
731+
.delete(serverCacheKey(workspaceId, serverId))
732+
.catch((err) =>
733+
logger.warn(`[${requestId}] Cache delete failed for ${serverId}:`, err)
734+
),
735+
this.markServerUnhealthy(workspaceId, serverId, error),
736+
])
727737
throw error
728738
}
729739
}

0 commit comments

Comments
 (0)