fix(mcp): Set customized_name at block creation and add pre-run validation

- Set customized_name in metadata when MCP and Agent blocks are created
  (both legacy and new builder) so titles persist through save/load
- Remove convoluted agent_name fallback from NodeHeader and getNodeTitle
- Add custom block-level validation in graph pre-run checks so MCP tool
  arguments are validated before execution
- Fix server_name fallback to URL hostname in discover_tools endpoint
This commit is contained in:
Zamil Majdy
2026-02-10 16:34:19 +04:00
parent 4364a771d4
commit 84809f4b94
5 changed files with 59 additions and 37 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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);

View File

@@ -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<HTMLButtonElement>) => {

View File

@@ -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$/, "")
);
},