diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx index 5ca7f92a03..cc3225b9c2 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx @@ -21,7 +21,6 @@ import { getIssueBadgeLabel, getIssueBadgeVariant, isToolUnavailable, - getMcpServerIssue as validateMcpServer, getMcpToolIssue as validateMcpTool, } from '@/lib/mcp/tool-validation' import type { McpToolSchema } from '@/lib/mcp/types' @@ -42,7 +41,6 @@ import { ToolSubBlockRenderer } from '@/app/workspace/[workspaceId]/w/[workflowI import type { StoredTool } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/types' import { isCustomToolAlreadySelected, - isMcpServerAlreadySelected, isMcpToolAlreadySelected, isWorkflowAlreadySelected, } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/utils' @@ -483,6 +481,7 @@ export const ToolInput = memo(function ToolInput({ const [draggedIndex, setDraggedIndex] = useState(null) const [dragOverIndex, setDragOverIndex] = useState(null) const [usageControlPopoverIndex, setUsageControlPopoverIndex] = useState(null) + const [mcpRemovePopoverIndex, setMcpRemovePopoverIndex] = useState(null) const [mcpServerDrilldown, setMcpServerDrilldown] = useState(null) const canonicalModeOverrides = useWorkflowStore( @@ -525,9 +524,7 @@ export const ToolInput = memo(function ToolInput({ ) const hasRefreshedRef = useRef(false) - const hasMcpTools = selectedTools.some( - (tool) => tool.type === 'mcp' || tool.type === 'mcp-server' - ) + const hasMcpTools = selectedTools.some((tool) => tool.type === 'mcp') useEffect(() => { if (isPreview) return @@ -544,25 +541,16 @@ export const ToolInput = memo(function ToolInput({ */ const getMcpToolIssue = useCallback( (tool: StoredTool) => { - if (tool.type !== 'mcp' && tool.type !== 'mcp-server') return null + if (tool.type !== 'mcp') return null const serverId = tool.params?.serverId as string + const toolName = tool.params?.toolName as string const serverStates = mcpServers.map((s) => ({ id: s.id, url: s.url, connectionStatus: s.connectionStatus, lastError: s.lastError ?? undefined, })) - - if (tool.type === 'mcp-server') { - return validateMcpServer( - serverId, - tool.params?.serverUrl as string | undefined, - serverStates - ) - } - - const toolName = tool.params?.toolName as string const discoveredTools = mcpTools.map((t) => ({ serverId: t.serverId, name: t.name, @@ -878,6 +866,16 @@ export const ToolInput = memo(function ToolInput({ [isPreview, disabled, selectedTools, setStoreValue] ) + const handleRemoveAllFromServer = useCallback( + (serverId: string | undefined) => { + if (isPreview || disabled || !serverId) return + setStoreValue( + selectedTools.filter((t) => !(t.type === 'mcp' && t.params?.serverId === serverId)) + ) + }, + [isPreview, disabled, selectedTools, setStoreValue] + ) + const handleDeleteTool = useCallback( (toolId: string) => { const updatedTools = selectedTools.filter((tool) => { @@ -1272,8 +1270,13 @@ export const ToolInput = memo(function ToolInput({ if (tools && tools.length > 0) { const server = mcpServers.find((s) => s.id === mcpServerDrilldown) const serverName = tools[0]?.serverName || server?.name || 'Unknown Server' - const serverAlreadySelected = isMcpServerAlreadySelected(selectedTools, mcpServerDrilldown) const toolCount = tools.length + const selectedToolIdsForServer = new Set( + selectedTools + .filter((t) => t.type === 'mcp' && t.params?.serverId === mcpServerDrilldown) + .map((t) => t.toolId) + ) + const allAlreadySelected = tools.every((t) => selectedToolIdsForServer.has(t.id)) const serverToolItems: ComboboxOption[] = [] // Back navigation @@ -1287,43 +1290,45 @@ export const ToolInput = memo(function ToolInput({ keepOpen: true, }) - // "Use all tools" option + // "Use all tools" option — adds each tool individually serverToolItems.push({ label: `Use all ${toolCount} tools`, value: `mcp-server-all-${mcpServerDrilldown}`, iconElement: createToolIcon('#6366F1', ServerIcon), onSelect: () => { - if (serverAlreadySelected) return + if (allAlreadySelected) return + // Remove existing individual tools from this server to avoid duplicates const filteredTools = selectedTools.filter( - (tool) => !(tool.type === 'mcp' && tool.params?.serverId === mcpServerDrilldown) + (t) => !(t.type === 'mcp' && t.params?.serverId === mcpServerDrilldown) ) - const newTool: StoredTool = { - type: 'mcp-server', - title: `${serverName} (all tools)`, - toolId: `mcp-server-${mcpServerDrilldown}`, + // Add all tools individually + const newTools: StoredTool[] = tools.map((mcpTool) => ({ + type: 'mcp' as const, + title: mcpTool.name, + toolId: mcpTool.id, params: { - serverId: mcpServerDrilldown, + serverId: mcpTool.serverId, ...(server?.url && { serverUrl: server.url }), - serverName, - toolCount: String(toolCount), + toolName: mcpTool.name, + serverName: mcpTool.serverName, }, isExpanded: false, - usageControl: 'auto', - } - setStoreValue([ - ...filteredTools.map((tool) => ({ ...tool, isExpanded: false })), - newTool, - ]) + usageControl: 'auto' as const, + schema: { + ...mcpTool.inputSchema, + description: mcpTool.description, + }, + })) + setStoreValue([...filteredTools.map((t) => ({ ...t, isExpanded: false })), ...newTools]) setMcpServerDrilldown(null) setOpen(false) }, - disabled: isPreview || disabled || serverAlreadySelected, + disabled: isPreview || disabled || allAlreadySelected, }) // Individual tools for (const mcpTool of tools) { - const alreadySelected = - isMcpToolAlreadySelected(selectedTools, mcpTool.id) || serverAlreadySelected + const alreadySelected = isMcpToolAlreadySelected(selectedTools, mcpTool.id) serverToolItems.push({ label: mcpTool.name, value: `mcp-${mcpTool.id}`, @@ -1557,20 +1562,19 @@ export const ToolInput = memo(function ToolInput({ selectedTools.map((tool, toolIndex) => { const isCustomTool = tool.type === 'custom-tool' const isMcpTool = tool.type === 'mcp' - const isMcpServer = tool.type === 'mcp-server' const isWorkflowTool = tool.type === 'workflow' const toolBlock = - !isCustomTool && !isMcpTool && !isMcpServer + !isCustomTool && !isMcpTool ? toolBlocks.find((block) => block.type === tool.type) : null const currentToolId = - !isCustomTool && !isMcpTool && !isMcpServer + !isCustomTool && !isMcpTool ? getToolIdForOperation(tool.type, tool.operation) || tool.toolId || '' : tool.toolId || '' const toolParams = - !isCustomTool && !isMcpTool && !isMcpServer && currentToolId + !isCustomTool && !isMcpTool && currentToolId ? getToolParametersConfig(currentToolId, tool.type, { operation: tool.operation, ...tool.params, @@ -1580,7 +1584,7 @@ export const ToolInput = memo(function ToolInput({ const toolScopedOverrides = scopeCanonicalOverrides(canonicalModeOverrides, tool.type) const subBlocksResult: SubBlocksForToolInput | null = - !isCustomTool && !isMcpTool && !isMcpServer && currentToolId + !isCustomTool && !isMcpTool && currentToolId ? getSubBlocksForToolInput( currentToolId, tool.type, @@ -1643,26 +1647,21 @@ export const ToolInput = memo(function ToolInput({ ) : [] - const useSubBlocks = - !isCustomTool && !isMcpTool && !isMcpServer && subBlocksResult?.subBlocks?.length + const useSubBlocks = !isCustomTool && !isMcpTool && subBlocksResult?.subBlocks?.length const displayParams: ToolParameterConfig[] = isCustomTool ? customToolParams : isMcpTool ? mcpToolParams - : isMcpServer - ? [] // MCP servers have no user-configurable params - : toolParams?.userInputParameters || [] + : toolParams?.userInputParameters || [] const displaySubBlocks: BlockSubBlockConfig[] = useSubBlocks ? subBlocksResult!.subBlocks : [] - const hasOperations = - !isCustomTool && !isMcpTool && !isMcpServer && hasMultipleOperations(tool.type) + const hasOperations = !isCustomTool && !isMcpTool && hasMultipleOperations(tool.type) const hasParams = useSubBlocks ? displaySubBlocks.length > 0 : displayParams.filter((param) => evaluateParameterCondition(param, tool)).length > 0 - // MCP servers are expandable to show tool list - const hasToolBody = isMcpServer ? true : hasOperations || hasParams + const hasToolBody = hasOperations || hasParams const isExpandedForDisplay = hasToolBody ? isPreview @@ -1670,11 +1669,6 @@ export const ToolInput = memo(function ToolInput({ : !!tool.isExpanded : false - // For MCP servers, get the list of tools for display - const mcpServerTools = isMcpServer - ? availableMcpTools.filter((t) => t.serverId === tool.params?.serverId) - : [] - return (
{isCustomTool ? ( ) : isMcpTool ? ( - ) : isMcpServer ? ( - ) : isWorkflowTool ? ( ) : ( @@ -1738,12 +1728,7 @@ export const ToolInput = memo(function ToolInput({ {isCustomTool ? customToolTitle : tool.title} - {isMcpServer && ( - - {tool.params?.toolCount || mcpServerTools.length} tools - - )} - {(isMcpTool || isMcpServer) && + {isMcpTool && !mcpDataLoading && (() => { const issue = getMcpToolIssue(tool) @@ -1778,127 +1763,156 @@ export const ToolInput = memo(function ToolInput({ )}
- {supportsToolControl && - !((isMcpTool || isMcpServer) && isMcpToolUnavailable(tool)) && ( - - setUsageControlPopoverIndex(open ? toolIndex : null) - } - > - - - - setUsageControlPopoverIndex(open ? toolIndex : null)} + > + + + {tool.usageControl === 'auto' && 'Auto'} + {tool.usageControl === 'force' && 'Force'} + {tool.usageControl === 'none' && 'None'} + {!tool.usageControl && 'Auto'} + + + e.stopPropagation()} + className='gap-[2px]' + border + > + { + handleUsageControlChange(toolIndex, 'auto') + setUsageControlPopoverIndex(null) + }} + > + Auto (model decides) + + { + handleUsageControlChange(toolIndex, 'force') + setUsageControlPopoverIndex(null) + }} + > + Force (always use) + + { + handleUsageControlChange(toolIndex, 'none') + setUsageControlPopoverIndex(null) + }} + > + None + + + + )} + {isMcpTool && + selectedTools.filter( + (t) => t.type === 'mcp' && t.params?.serverId === tool.params?.serverId + ).length > 1 ? ( + { + if (!isOpen) setMcpRemovePopoverIndex(null) + }} + > + + + + e.stopPropagation()} + className='gap-[2px]' + border + > + { + handleRemoveTool(toolIndex) + setMcpRemovePopoverIndex(null) + }} + > + Remove + + { + handleRemoveAllFromServer(tool.params?.serverId) + setMcpRemovePopoverIndex(null) + }} + > + Remove all from {tool.params?.serverName || 'server'} + + + + ) : ( + + )}
{!isCustomTool && isExpandedForDisplay && (
- {/* MCP Server tool list (read-only) */} - {isMcpServer && mcpServerTools.length > 0 && ( -
-
- Available tools: -
-
- {mcpServerTools.map((serverTool) => ( - - {serverTool.name} - - ))} -
-
- )} - {/* Operation dropdown for tools with multiple operations */} - {!isMcpServer && - (() => { - const hasOperations = hasMultipleOperations(tool.type) - const operationOptions = hasOperations ? getOperationOptions(tool.type) : [] - - return hasOperations && operationOptions.length > 0 ? ( -
-
- Operation -
- option.id !== '') - .map((option) => ({ - label: option.label, - value: option.id, - }))} - value={tool.operation || operationOptions[0].id} - onChange={(value) => handleOperationChange(toolIndex, value)} - placeholder='Select operation' - disabled={disabled} - /> + {(() => { + const hasOperations = hasMultipleOperations(tool.type) + const operationOptions = hasOperations ? getOperationOptions(tool.type) : [] + + return hasOperations && operationOptions.length > 0 ? ( +
+
+ Operation
- ) : null - })()} + option.id !== '') + .map((option) => ({ + label: option.label, + value: option.id, + }))} + value={tool.operation || operationOptions[0].id} + onChange={(value) => handleOperationChange(toolIndex, value)} + placeholder='Select operation' + disabled={disabled} + /> +
+ ) : null + })()} {(() => { const renderedElements: React.ReactNode[] = [] diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/types.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/types.ts index d87661afa5..c46bb97c18 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/types.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/types.ts @@ -6,29 +6,19 @@ * - Standard block types (e.g., 'api', 'search', 'function') * - 'custom-tool': User-defined tools with custom code * - 'mcp': Individual MCP tool from a connected server - * - 'mcp-server': All tools from an MCP server (agent discovery mode). - * At execution time, this expands into individual tool definitions for - * all tools available on the server. * * For custom tools (new format), we only store: type, customToolId, usageControl, isExpanded. * Everything else (title, schema, code) is loaded dynamically from the database. * Legacy custom tools with inline schema/code are still supported for backwards compatibility. */ export interface StoredTool { - /** - * Block type identifier. - * 'mcp-server' enables server-level selection where all tools from - * the server are made available to the LLM at execution time. - */ + /** Block type identifier */ type: string /** Display title for the tool (optional for new custom tool format) */ title?: string /** Direct tool ID for execution (optional for new custom tool format) */ toolId?: string - /** - * Parameter values configured by the user. - * For 'mcp-server' type, includes: serverId, serverUrl, serverName, toolCount - */ + /** Parameter values configured by the user */ params?: Record /** Whether the tool details are expanded in UI */ isExpanded?: boolean diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/utils.ts index d47a745bdb..1110a5808b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/utils.ts @@ -7,15 +7,6 @@ export function isMcpToolAlreadySelected(selectedTools: StoredTool[], mcpToolId: return selectedTools.some((tool) => tool.type === 'mcp' && tool.toolId === mcpToolId) } -/** - * Checks if an MCP server is already selected (all tools mode). - */ -export function isMcpServerAlreadySelected(selectedTools: StoredTool[], serverId: string): boolean { - return selectedTools.some( - (tool) => tool.type === 'mcp-server' && tool.params?.serverId === serverId - ) -} - /** * Checks if a custom tool is already selected. */ diff --git a/apps/sim/executor/handlers/agent/agent-handler.ts b/apps/sim/executor/handlers/agent/agent-handler.ts index ee07f276b5..db8b3d0b06 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.ts @@ -164,7 +164,7 @@ export class AgentBlockHandler implements BlockHandler { private async validateToolPermissions(ctx: ExecutionContext, tools: ToolInput[]): Promise { if (!Array.isArray(tools) || tools.length === 0) return - const hasMcpTools = tools.some((t) => t.type === 'mcp' || t.type === 'mcp-server') + const hasMcpTools = tools.some((t) => t.type === 'mcp') const hasCustomTools = tools.some((t) => t.type === 'custom-tool') if (hasMcpTools) { @@ -182,7 +182,7 @@ export class AgentBlockHandler implements BlockHandler { ): Promise { if (!Array.isArray(tools) || tools.length === 0) return tools - const mcpTools = tools.filter((t) => t.type === 'mcp' || t.type === 'mcp-server') + const mcpTools = tools.filter((t) => t.type === 'mcp') if (mcpTools.length === 0) return tools const serverIds = [...new Set(mcpTools.map((t) => t.params?.serverId).filter(Boolean))] @@ -216,7 +216,7 @@ export class AgentBlockHandler implements BlockHandler { } return tools.filter((tool) => { - if (tool.type !== 'mcp' && tool.type !== 'mcp-server') return true + if (tool.type !== 'mcp') return true const serverId = tool.params?.serverId if (!serverId) return false return availableServerIds.has(serverId) @@ -236,14 +236,11 @@ export class AgentBlockHandler implements BlockHandler { }) const mcpTools: ToolInput[] = [] - const mcpServerSelections: ToolInput[] = [] const otherTools: ToolInput[] = [] for (const tool of filtered) { if (tool.type === 'mcp') { mcpTools.push(tool) - } else if (tool.type === 'mcp-server') { - mcpServerSelections.push(tool) } else { otherTools.push(tool) } @@ -268,86 +265,12 @@ export class AgentBlockHandler implements BlockHandler { const mcpResults = await this.processMcpToolsBatched(ctx, mcpTools) - // Process MCP server selections (all tools from server mode) - const mcpServerResults = await this.processMcpServerSelections(ctx, mcpServerSelections) - - const allTools = [...otherResults, ...mcpResults, ...mcpServerResults] + const allTools = [...otherResults, ...mcpResults] return allTools.filter( (tool): tool is NonNullable => tool !== null && tool !== undefined ) } - /** - * Process MCP server selections by discovering and formatting all tools from each server. - * This enables "agent discovery" mode where the LLM can call any tool from the server. - */ - private async processMcpServerSelections( - ctx: ExecutionContext, - mcpServerSelections: ToolInput[] - ): Promise { - if (mcpServerSelections.length === 0) return [] - - const results = await Promise.all( - mcpServerSelections.map(async (serverSelection) => { - const serverId = serverSelection.params?.serverId - const serverName = serverSelection.params?.serverName - const usageControl = serverSelection.usageControl || 'auto' - - if (!serverId) { - logger.error('MCP server selection missing serverId:', serverSelection) - return [] - } - - try { - const discoveredTools = await this.discoverMcpToolsForServer(ctx, serverId) - const createdTools = await Promise.all( - discoveredTools.map((mcpTool) => - this.createMcpToolFromDiscoveredServerTool( - mcpTool, - serverId, - serverName || serverId, - usageControl - ) - ) - ) - logger.info( - `[AgentHandler] Expanded MCP server ${serverName} into ${discoveredTools.length} tools` - ) - return createdTools.filter(Boolean) - } catch (error) { - logger.error(`[AgentHandler] Failed to process MCP server selection:`, { - serverId, - error, - }) - return [] - } - }) - ) - - return results.flat() - } - - /** - * Create an MCP tool from server discovery for the "all tools" mode. - * Delegates to buildMcpTool so server-discovered tools use the same - * execution pipeline as individually-selected MCP tools. - */ - private async createMcpToolFromDiscoveredServerTool( - mcpTool: any, - serverId: string, - serverName: string, - usageControl: string - ): Promise { - return this.buildMcpTool({ - serverId, - toolName: mcpTool.name, - description: mcpTool.description || `MCP tool ${mcpTool.name} from ${serverName}`, - schema: mcpTool.inputSchema || { type: 'object', properties: {} }, - userProvidedParams: {}, - usageControl, - }) - } - private async createCustomTool(ctx: ExecutionContext, tool: ToolInput): Promise { const userProvidedParams = tool.params || {} diff --git a/apps/sim/executor/handlers/agent/types.ts b/apps/sim/executor/handlers/agent/types.ts index 3c69548fa2..7f4a185bac 100644 --- a/apps/sim/executor/handlers/agent/types.ts +++ b/apps/sim/executor/handlers/agent/types.ts @@ -47,28 +47,14 @@ export interface AgentInputs { * - Standard block types (e.g., 'api', 'search', 'function') * - 'custom-tool': User-defined tools with custom code * - 'mcp': Individual MCP tool from a connected server - * - 'mcp-server': All tools from an MCP server (agent discovery mode). - * At execution time, this is expanded into individual tool definitions - * for all tools available on the server. This enables dynamic capability - * discovery where the LLM can call any tool from the server. */ export interface ToolInput { - /** - * Tool type identifier. - * 'mcp-server' enables server-level selection where all tools from - * the server are made available to the LLM at execution time. - */ + /** Tool type identifier */ type?: string schema?: any title?: string code?: string - /** - * Tool parameters. For 'mcp-server' type, includes: - * - serverId: The MCP server ID - * - serverUrl: The server URL (optional) - * - serverName: Human-readable server name - * - toolCount: Number of tools available (for display) - */ + /** Tool parameters */ params?: Record timeout?: number usageControl?: 'auto' | 'force' | 'none'