diff --git a/autogpt_platform/backend/backend/api/features/mcp/routes.py b/autogpt_platform/backend/backend/api/features/mcp/routes.py index 4a190d0cf7..230eb517eb 100644 --- a/autogpt_platform/backend/backend/api/features/mcp/routes.py +++ b/autogpt_platform/backend/backend/api/features/mcp/routes.py @@ -120,7 +120,11 @@ async def discover_tools( ) for t in tools ], - server_name=init_result.get("serverInfo", {}).get("name"), + server_name=( + init_result.get("serverInfo", {}).get("name") + or urlparse(request.server_url).hostname + or "MCP" + ), protocol_version=init_result.get("protocolVersion"), ) except HTTPClientError as e: diff --git a/autogpt_platform/backend/backend/data/graph.py b/autogpt_platform/backend/backend/data/graph.py index 7f576db8dc..d9edeafe97 100644 --- a/autogpt_platform/backend/backend/data/graph.py +++ b/autogpt_platform/backend/backend/data/graph.py @@ -809,6 +809,19 @@ class GraphModel(Graph, GraphMeta): "'credentials' and `*_credentials` are reserved" ) + # Check custom block-level validation (e.g., MCP dynamic tool arguments). + # Blocks can override get_missing_input to report additional missing fields + # beyond the standard top-level required fields. + if for_run: + credential_fields = InputSchema.get_credentials_fields() + custom_missing = InputSchema.get_missing_input(node.input_default) + for field_name in custom_missing: + if ( + field_name not in provided_inputs + and field_name not in credential_fields + ): + node_errors[node.id][field_name] = "This field is required" + # Get input schema properties and check dependencies input_fields = InputSchema.model_fields diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeHeader.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeHeader.tsx index a37cda2159..9a3add62b6 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeHeader.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeHeader.tsx @@ -6,7 +6,6 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/atoms/Tooltip/BaseTooltip"; -import { SpecialBlockID } from "@/lib/autogpt-server-api"; import { beautifyString, cn } from "@/lib/utils"; import { useState } from "react"; import { CustomNodeData } from "../CustomNode"; @@ -21,31 +20,8 @@ type Props = { export const NodeHeader = ({ data, nodeId }: Props) => { const updateNodeData = useNodeStore((state) => state.updateNodeData); - const isMCPWithTool = - data.block_id === SpecialBlockID.MCP_TOOL && - !!data.hardcodedValues?.selected_tool; - // Derive MCP server label: prefer server_name, fall back to URL hostname. - let mcpServerLabel = "MCP"; - if (isMCPWithTool) { - mcpServerLabel = - data.hardcodedValues.server_name || - (() => { - try { - return new URL(data.hardcodedValues.server_url).hostname; - } catch { - return "MCP"; - } - })(); - } - - const title = - (data.metadata?.customized_name as string) || - (isMCPWithTool - ? `${mcpServerLabel}: ${beautifyString(data.hardcodedValues.selected_tool)}` - : null) || - data.hardcodedValues?.agent_name || - data.title; + const title = (data.metadata?.customized_name as string) || data.title; const [isEditingTitle, setIsEditingTitle] = useState(false); const [editedTitle, setEditedTitle] = useState(title); diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/NewBlockMenu/Block.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/NewBlockMenu/Block.tsx index dbcb2d1cb7..4c779380ac 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/NewBlockMenu/Block.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/NewBlockMenu/Block.tsx @@ -83,14 +83,15 @@ export const Block: BlockComponent = ({ available_tools: result.availableTools, credentials: result.credentials ?? undefined, }); - // Persist the MCP title as customized_name in metadata so it survives - // save/load even if server_name is pruned from input_default. - if (customNode && result.selectedTool) { - const title = `${serverLabel}: ${beautifyString(result.selectedTool)}`; + if (customNode) { + const title = result.selectedTool + ? `${serverLabel}: ${beautifyString(result.selectedTool)}` + : undefined; updateNodeData(customNode.id, { metadata: { ...customNode.data.metadata, - customized_name: title, + credentials_optional: true, + ...(title && { customized_name: title }), }, }); } @@ -104,7 +105,16 @@ export const Block: BlockComponent = ({ setMcpDialogOpen(true); return; } - addBlockAndCenter(blockData); + const customNode = addBlockAndCenter(blockData); + // Set customized_name for agent blocks so the agent's name persists + if (customNode && blockData.id === SpecialBlockID.AGENT) { + updateNodeData(customNode.id, { + metadata: { + ...customNode.data.metadata, + customized_name: blockData.name, + }, + }); + } }; const handleDragStart = (e: React.DragEvent) => { diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/Flow/Flow.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/Flow/Flow.tsx index f566143f40..6cd8721be5 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/Flow/Flow.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/Flow/Flow.tsx @@ -42,7 +42,11 @@ import { getV1GetSpecificGraph } from "@/app/api/__generated__/endpoints/graphs/ import { okData } from "@/app/api/helpers"; import { IncompatibilityInfo } from "../../../hooks/useSubAgentUpdate/types"; import { Key, storage } from "@/services/storage/local-storage"; -import { findNewlyAddedBlockCoordinates, getTypeColor } from "@/lib/utils"; +import { + beautifyString, + findNewlyAddedBlockCoordinates, + getTypeColor, +} from "@/lib/utils"; import { history } from "../history"; import { CustomEdge } from "../CustomEdge/CustomEdge"; import ConnectionLine from "../ConnectionLine"; @@ -748,9 +752,26 @@ const FlowEditor: React.FC<{ block_id: blockID, isOutputStatic: nodeSchema.staticOutput, uiType: nodeSchema.uiType, - // MCP blocks have optional credentials (public servers don't need auth) + // Set customized_name at creation so it persists through save/load ...(blockID === SpecialBlockID.MCP_TOOL && { - metadata: { credentials_optional: true }, + metadata: { + credentials_optional: true, + ...(finalHardcodedValues.selected_tool && { + customized_name: `${ + finalHardcodedValues.server_name || + (() => { + try { + return new URL(finalHardcodedValues.server_url).hostname; + } catch { + return "MCP"; + } + })() + }: ${beautifyString(finalHardcodedValues.selected_tool)}`, + }), + }, + }), + ...(blockID === SpecialBlockID.AGENT && { + metadata: { customized_name: blockName }, }), }, }; @@ -881,8 +902,6 @@ const FlowEditor: React.FC<{ return ( node.data.metadata?.customized_name || - (node.data.uiType == BlockUIType.AGENT && - node.data.hardcodedValues.agent_name) || node.data.blockType.replace(/Block$/, "") ); },