From d4834b6e13c4207ee73ac16679914573c0b5ad83 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 19 Nov 2025 16:03:52 -0800 Subject: [PATCH 1/6] fix(mcp-preview): server and tool name fetch to use tanstack --- .../workflow-block/workflow-block.tsx | 29 ++++ .../settings-modal/components/mcp/mcp.tsx | 20 +-- apps/sim/blocks/types.ts | 2 + apps/sim/hooks/queries/mcp.ts | 2 + apps/sim/hooks/use-mcp-tools.ts | 145 +++++------------- 5 files changed, 78 insertions(+), 120 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx index abc49cd704..4c5032fe0b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx @@ -5,6 +5,7 @@ import { Badge } from '@/components/emcn/components/badge/badge' import { Tooltip } from '@/components/emcn/components/tooltip/tooltip' import { getEnv, isTruthy } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { createMcpToolId } from '@/lib/mcp/utils' import { cn } from '@/lib/utils' import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import { useBlockCore } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks' @@ -13,6 +14,7 @@ import { useBlockDimensions, } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-dimensions' import { SELECTOR_TYPES_HYDRATION_REQUIRED, type SubBlockConfig } from '@/blocks/types' +import { useMcpServers, useMcpToolsQuery } from '@/hooks/queries/mcp' import { useCredentialName } from '@/hooks/queries/oauth-credentials' import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow' import { useKnowledgeBaseName } from '@/hooks/use-knowledge-base-name' @@ -313,6 +315,31 @@ const SubBlockRow = ({ ? (workflowMap[rawValue]?.name ?? null) : null + // Hydrate MCP server ID to name using TanStack Query + const { data: mcpServers = [] } = useMcpServers(workspaceId || '') + const mcpServerDisplayName = useMemo(() => { + if (subBlock?.type !== 'mcp-server-selector' || typeof rawValue !== 'string') { + return null + } + const server = mcpServers.find((s) => s.id === rawValue) + return server?.name ?? null + }, [subBlock?.type, rawValue, mcpServers]) + + // Hydrate MCP tool ID to name using TanStack Query + const { data: mcpToolsData = [] } = useMcpToolsQuery(workspaceId || '') + const mcpToolDisplayName = useMemo(() => { + if (subBlock?.type !== 'mcp-tool-selector' || typeof rawValue !== 'string') { + return null + } + // The rawValue is a composite ID like "serverId-toolName" + // We need to find the tool by matching the composite ID + const tool = mcpToolsData.find((t) => { + const toolId = createMcpToolId(t.serverId, t.name) + return toolId === rawValue + }) + return tool?.name ?? null + }, [subBlock?.type, rawValue, mcpToolsData]) + // Subscribe to variables store to reactively update when variables change const allVariables = useVariablesStore((state) => state.variables) @@ -354,6 +381,8 @@ const SubBlockRow = ({ variablesDisplayValue || knowledgeBaseDisplayName || workflowSelectionName || + mcpServerDisplayName || + mcpToolDisplayName || selectorDisplayName const displayValue = maskedValue || hydratedName || (isSelectorType && value ? '-' : value) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/mcp/mcp.tsx index 6af0e082fa..05647d8aa6 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/mcp/mcp.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/mcp/mcp.tsx @@ -14,7 +14,6 @@ import { useMcpToolsQuery, } from '@/hooks/queries/mcp' import { useMcpServerTest } from '@/hooks/use-mcp-server-test' -import { useMcpTools } from '@/hooks/use-mcp-tools' import { AddServerForm } from './components/add-server-form' import type { McpServerFormData } from './types' @@ -34,9 +33,6 @@ export function MCP() { const createServerMutation = useCreateMcpServer() const deleteServerMutation = useDeleteMcpServer() - // Keep the old hook for backward compatibility with other features that use it - const { refreshTools } = useMcpTools(workspaceId) - const [showAddForm, setShowAddForm] = useState(false) const [searchTerm, setSearchTerm] = useState('') const [deletingServers, setDeletingServers] = useState>(new Set()) @@ -198,21 +194,13 @@ export function MCP() { setActiveHeaderIndex(null) clearTestResult() - refreshTools(true) // Force refresh after adding server + // TanStack Query mutations automatically invalidate and refetch tools } catch (error) { logger.error('Failed to add MCP server:', error) } finally { setIsAddingServer(false) } - }, [ - formData, - testResult, - testConnection, - createServerMutation, - refreshTools, - clearTestResult, - workspaceId, - ]) + }, [formData, testResult, testConnection, createServerMutation, clearTestResult, workspaceId]) const handleRemoveServer = useCallback( async (serverId: string) => { @@ -220,7 +208,7 @@ export function MCP() { try { await deleteServerMutation.mutateAsync({ workspaceId, serverId }) - await refreshTools(true) + // TanStack Query mutations automatically invalidate and refetch tools logger.info(`Removed MCP server: ${serverId}`) } catch (error) { @@ -238,7 +226,7 @@ export function MCP() { }) } }, - [deleteServerMutation, refreshTools, workspaceId] + [deleteServerMutation, workspaceId] ) const toolsByServer = (mcpToolsData || []).reduce( diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index 9822d749da..4799199481 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -87,6 +87,8 @@ export const SELECTOR_TYPES_HYDRATION_REQUIRED: SubBlockType[] = [ 'knowledge-base-selector', 'document-selector', 'variables-input', + 'mcp-server-selector', + 'mcp-tool-selector', ] as const export type ExtractToolOutput = T extends ToolResponse ? T['output'] : never diff --git a/apps/sim/hooks/queries/mcp.ts b/apps/sim/hooks/queries/mcp.ts index 08bc09b0ae..04945de57c 100644 --- a/apps/sim/hooks/queries/mcp.ts +++ b/apps/sim/hooks/queries/mcp.ts @@ -45,8 +45,10 @@ export interface McpServerConfig { export interface McpTool { id: string serverId: string + serverName: string name: string description?: string + inputSchema?: any } /** diff --git a/apps/sim/hooks/use-mcp-tools.ts b/apps/sim/hooks/use-mcp-tools.ts index 7d5e0d7858..ffe699c20d 100644 --- a/apps/sim/hooks/use-mcp-tools.ts +++ b/apps/sim/hooks/use-mcp-tools.ts @@ -2,16 +2,16 @@ * Hook for discovering and managing MCP tools * * This hook provides a unified interface for accessing MCP tools - * alongside regular platform tools in the tool-input component + * using TanStack Query for optimal caching and performance */ import type React from 'react' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useMemo } from 'react' +import { useQueryClient } from '@tanstack/react-query' import { WrenchIcon } from 'lucide-react' import { createLogger } from '@/lib/logs/console/logger' -import type { McpTool } from '@/lib/mcp/types' import { createMcpToolId } from '@/lib/mcp/utils' -import { useMcpServers } from '@/hooks/queries/mcp' +import { mcpKeys, useMcpToolsQuery } from '@/hooks/queries/mcp' const logger = createLogger('useMcpTools') @@ -36,84 +36,51 @@ export interface UseMcpToolsResult { getToolsByServer: (serverId: string) => McpToolForUI[] } +/** + * Hook for accessing MCP tools with TanStack Query + * Provides backward-compatible API with the old useState-based implementation + */ export function useMcpTools(workspaceId: string): UseMcpToolsResult { - const [mcpTools, setMcpTools] = useState([]) - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState(null) - - const { data: servers = [] } = useMcpServers(workspaceId) - - // Track the last fingerprint - const lastProcessedFingerprintRef = useRef('') - - // Create a stable server fingerprint - const serversFingerprint = useMemo(() => { - return servers - .filter((s) => s.enabled && !s.deletedAt) - .map((s) => `${s.id}-${s.enabled}-${s.updatedAt}`) - .sort() - .join('|') - }, [servers]) - + const queryClient = useQueryClient() + + // Use TanStack Query hook for data fetching with caching + const { data: mcpToolsData = [], isLoading, error: queryError } = useMcpToolsQuery(workspaceId) + + // Transform raw tool data to UI-friendly format with memoization + const mcpTools = useMemo(() => { + return mcpToolsData.map((tool) => ({ + id: createMcpToolId(tool.serverId, tool.name), + name: tool.name, + description: tool.description, + serverId: tool.serverId, + serverName: tool.serverName, + type: 'mcp' as const, + inputSchema: tool.inputSchema, + bgColor: '#6366F1', + icon: WrenchIcon, + })) + }, [mcpToolsData]) + + // Refresh tools by invalidating the query cache const refreshTools = useCallback( async (forceRefresh = false) => { - // Skip if no workspaceId (e.g., on template preview pages) if (!workspaceId) { - setMcpTools([]) - setIsLoading(false) + logger.warn('Cannot refresh tools: no workspaceId provided') return } - setIsLoading(true) - setError(null) - - try { - logger.info('Discovering MCP tools', { forceRefresh, workspaceId }) - - const response = await fetch( - `/api/mcp/tools/discover?workspaceId=${workspaceId}&refresh=${forceRefresh}` - ) - - if (!response.ok) { - throw new Error(`Failed to discover MCP tools: ${response.status} ${response.statusText}`) - } - - const data = await response.json() - - if (!data.success) { - throw new Error(data.error || 'Failed to discover MCP tools') - } - - const tools = data.data.tools || [] - const transformedTools = tools.map((tool: McpTool) => ({ - id: createMcpToolId(tool.serverId, tool.name), - name: tool.name, - description: tool.description, - serverId: tool.serverId, - serverName: tool.serverName, - type: 'mcp' as const, - inputSchema: tool.inputSchema, - bgColor: '#6366F1', - icon: WrenchIcon, - })) - - setMcpTools(transformedTools) - - logger.info( - `Discovered ${transformedTools.length} MCP tools from ${data.data.byServer ? Object.keys(data.data.byServer).length : 0} servers` - ) - } catch (err) { - const errorMessage = err instanceof Error ? err.message : 'Failed to discover MCP tools' - logger.error('Error discovering MCP tools:', err) - setError(errorMessage) - setMcpTools([]) - } finally { - setIsLoading(false) - } + logger.info('Refreshing MCP tools', { forceRefresh, workspaceId }) + + // Invalidate the query to trigger a refetch + await queryClient.invalidateQueries({ + queryKey: mcpKeys.tools(workspaceId), + refetchType: forceRefresh ? 'active' : 'all', + }) }, - [workspaceId] + [workspaceId, queryClient] ) + // Get tool by ID const getToolById = useCallback( (toolId: string): McpToolForUI | undefined => { return mcpTools.find((tool) => tool.id === toolId) @@ -121,6 +88,7 @@ export function useMcpTools(workspaceId: string): UseMcpToolsResult { [mcpTools] ) + // Get all tools for a specific server const getToolsByServer = useCallback( (serverId: string): McpToolForUI[] => { return mcpTools.filter((tool) => tool.serverId === serverId) @@ -128,41 +96,10 @@ export function useMcpTools(workspaceId: string): UseMcpToolsResult { [mcpTools] ) - useEffect(() => { - refreshTools() - }, [refreshTools]) - - // Refresh tools when servers change - useEffect(() => { - if (!serversFingerprint || serversFingerprint === lastProcessedFingerprintRef.current) return - - logger.info('Active servers changed, refreshing MCP tools', { - serverCount: servers.filter((s) => s.enabled && !s.deletedAt).length, - fingerprint: serversFingerprint, - }) - - lastProcessedFingerprintRef.current = serversFingerprint - refreshTools() - }, [serversFingerprint, refreshTools]) - - // Auto-refresh every 5 minutes - useEffect(() => { - const interval = setInterval( - () => { - if (!isLoading) { - refreshTools() - } - }, - 5 * 60 * 1000 - ) - - return () => clearInterval(interval) - }, [refreshTools]) - return { mcpTools, isLoading, - error, + error: queryError instanceof Error ? queryError.message : null, refreshTools, getToolById, getToolsByServer, From 248d6248b007851f44ac6ceff0ec31778d26aafa Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 19 Nov 2025 16:11:25 -0800 Subject: [PATCH 2/6] remove comments --- .../components/workflow-block/workflow-block.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx index 4c5032fe0b..cdced8dd36 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx @@ -325,14 +325,12 @@ const SubBlockRow = ({ return server?.name ?? null }, [subBlock?.type, rawValue, mcpServers]) - // Hydrate MCP tool ID to name using TanStack Query const { data: mcpToolsData = [] } = useMcpToolsQuery(workspaceId || '') const mcpToolDisplayName = useMemo(() => { if (subBlock?.type !== 'mcp-tool-selector' || typeof rawValue !== 'string') { return null } - // The rawValue is a composite ID like "serverId-toolName" - // We need to find the tool by matching the composite ID + const tool = mcpToolsData.find((t) => { const toolId = createMcpToolId(t.serverId, t.name) return toolId === rawValue @@ -340,10 +338,8 @@ const SubBlockRow = ({ return tool?.name ?? null }, [subBlock?.type, rawValue, mcpToolsData]) - // Subscribe to variables store to reactively update when variables change const allVariables = useVariablesStore((state) => state.variables) - // Special handling for variables-input to hydrate variable IDs to names from variables store const variablesDisplayValue = useMemo(() => { if (subBlock?.type !== 'variables-input' || !isVariableAssignmentsArray(rawValue)) { return null From dbec1438036e62e1a91340a65601c4d025a5d372 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 19 Nov 2025 16:13:17 -0800 Subject: [PATCH 3/6] fix mcp tool interface --- apps/sim/hooks/queries/mcp.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/sim/hooks/queries/mcp.ts b/apps/sim/hooks/queries/mcp.ts index 04945de57c..421287a64f 100644 --- a/apps/sim/hooks/queries/mcp.ts +++ b/apps/sim/hooks/queries/mcp.ts @@ -43,7 +43,6 @@ export interface McpServerConfig { } export interface McpTool { - id: string serverId: string serverName: string name: string From 67c3c628a880bd41f73ebe174662d5dec9b2d2cf Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 19 Nov 2025 16:15:28 -0800 Subject: [PATCH 4/6] change incorrect reference --- .../components-new/settings-modal/components/mcp/mcp.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/mcp/mcp.tsx index 05647d8aa6..56de0cb8da 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/mcp/mcp.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/mcp/mcp.tsx @@ -6,6 +6,7 @@ import { useParams } from 'next/navigation' import { Button } from '@/components/emcn' import { Alert, AlertDescription, Input, Skeleton } from '@/components/ui' import { createLogger } from '@/lib/logs/console/logger' +import { createMcpToolId } from '@/lib/mcp/utils' import { checkEnvVarTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/env-var-dropdown' import { useCreateMcpServer, @@ -380,7 +381,7 @@ export function MCP() {
{tools.map((tool) => ( {tool.name} From 31cb65dc0d19ad36939f6dca302ea69962d84f8d Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 19 Nov 2025 16:16:02 -0800 Subject: [PATCH 5/6] fix --- .../components-new/settings-modal/components/mcp/mcp.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/mcp/mcp.tsx index 56de0cb8da..a2b9cd4efb 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/mcp/mcp.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/mcp/mcp.tsx @@ -194,8 +194,6 @@ export function MCP() { setActiveInputField(null) setActiveHeaderIndex(null) clearTestResult() - - // TanStack Query mutations automatically invalidate and refetch tools } catch (error) { logger.error('Failed to add MCP server:', error) } finally { From c3657ee86a7d8615665cf236b6da973681af1292 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 19 Nov 2025 16:17:06 -0800 Subject: [PATCH 6/6] remove comments --- apps/sim/hooks/use-mcp-tools.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/apps/sim/hooks/use-mcp-tools.ts b/apps/sim/hooks/use-mcp-tools.ts index ffe699c20d..4960bedcfa 100644 --- a/apps/sim/hooks/use-mcp-tools.ts +++ b/apps/sim/hooks/use-mcp-tools.ts @@ -36,17 +36,11 @@ export interface UseMcpToolsResult { getToolsByServer: (serverId: string) => McpToolForUI[] } -/** - * Hook for accessing MCP tools with TanStack Query - * Provides backward-compatible API with the old useState-based implementation - */ export function useMcpTools(workspaceId: string): UseMcpToolsResult { const queryClient = useQueryClient() - // Use TanStack Query hook for data fetching with caching const { data: mcpToolsData = [], isLoading, error: queryError } = useMcpToolsQuery(workspaceId) - // Transform raw tool data to UI-friendly format with memoization const mcpTools = useMemo(() => { return mcpToolsData.map((tool) => ({ id: createMcpToolId(tool.serverId, tool.name), @@ -61,7 +55,6 @@ export function useMcpTools(workspaceId: string): UseMcpToolsResult { })) }, [mcpToolsData]) - // Refresh tools by invalidating the query cache const refreshTools = useCallback( async (forceRefresh = false) => { if (!workspaceId) { @@ -71,7 +64,6 @@ export function useMcpTools(workspaceId: string): UseMcpToolsResult { logger.info('Refreshing MCP tools', { forceRefresh, workspaceId }) - // Invalidate the query to trigger a refetch await queryClient.invalidateQueries({ queryKey: mcpKeys.tools(workspaceId), refetchType: forceRefresh ? 'active' : 'all', @@ -80,7 +72,6 @@ export function useMcpTools(workspaceId: string): UseMcpToolsResult { [workspaceId, queryClient] ) - // Get tool by ID const getToolById = useCallback( (toolId: string): McpToolForUI | undefined => { return mcpTools.find((tool) => tool.id === toolId) @@ -88,7 +79,6 @@ export function useMcpTools(workspaceId: string): UseMcpToolsResult { [mcpTools] ) - // Get all tools for a specific server const getToolsByServer = useCallback( (serverId: string): McpToolForUI[] => { return mcpTools.filter((tool) => tool.serverId === serverId)