mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-10 06:45:28 -05:00
fix(mcp): Handle optional credentials in graph save and execution validation
- _on_graph_activate: Clear stale credential references for optional
fields instead of blocking the save. Checks both node metadata
(credentials_optional) and block schema (field not in required_fields).
- _validate_node_input_credentials: Use block schema's required_fields
as fallback for credentials_optional check, so MCP blocks with
default={} credentials are properly treated as optional.
- Set credentials_optional metadata on new MCP nodes in the frontend.
This commit is contained in:
@@ -265,7 +265,13 @@ async def _validate_node_input_credentials(
|
||||
# Track if any credential field is missing for this node
|
||||
has_missing_credentials = False
|
||||
|
||||
# A credential field is optional if the node metadata says so, or if
|
||||
# the block schema declares a default for the field.
|
||||
required_fields = block.input_schema.get_required_fields()
|
||||
is_creds_optional = node.credentials_optional
|
||||
|
||||
for field_name, credentials_meta_type in credentials_fields.items():
|
||||
field_is_optional = is_creds_optional or field_name not in required_fields
|
||||
try:
|
||||
# Check nodes_input_masks first, then input_default
|
||||
field_value = None
|
||||
@@ -278,7 +284,7 @@ async def _validate_node_input_credentials(
|
||||
elif field_name in node.input_default:
|
||||
# For optional credentials, don't use input_default - treat as missing
|
||||
# This prevents stale credential IDs from failing validation
|
||||
if node.credentials_optional:
|
||||
if field_is_optional:
|
||||
field_value = None
|
||||
else:
|
||||
field_value = node.input_default[field_name]
|
||||
@@ -288,8 +294,8 @@ async def _validate_node_input_credentials(
|
||||
isinstance(field_value, dict) and not field_value.get("id")
|
||||
):
|
||||
has_missing_credentials = True
|
||||
# If node has credentials_optional flag, mark for skipping instead of error
|
||||
if node.credentials_optional:
|
||||
# If credential field is optional, skip instead of error
|
||||
if field_is_optional:
|
||||
continue # Don't add error, will be marked for skip after loop
|
||||
else:
|
||||
credential_errors[node.id][
|
||||
@@ -343,7 +349,7 @@ async def _validate_node_input_credentials(
|
||||
# The executor will pass credentials=None to the block's run().
|
||||
if (
|
||||
has_missing_credentials
|
||||
and node.credentials_optional
|
||||
and is_creds_optional
|
||||
and node.id not in credential_errors
|
||||
):
|
||||
logger.info(
|
||||
|
||||
@@ -50,6 +50,21 @@ async def _on_graph_activate(graph: "BaseGraph | GraphModel", user_id: str):
|
||||
if (
|
||||
creds_meta := new_node.input_default.get(creds_field_name)
|
||||
) and not await get_credentials(creds_meta["id"]):
|
||||
# If the credential field is optional (has a default in the
|
||||
# schema, or node metadata marks it optional), clear the stale
|
||||
# reference instead of blocking the save.
|
||||
creds_field_optional = (
|
||||
new_node.credentials_optional
|
||||
or creds_field_name not in block_input_schema.get_required_fields()
|
||||
)
|
||||
if creds_field_optional:
|
||||
new_node.input_default[creds_field_name] = {}
|
||||
logger.warning(
|
||||
f"Node #{new_node.id}: cleared stale optional "
|
||||
f"credentials #{creds_meta['id']} for "
|
||||
f"'{creds_field_name}'"
|
||||
)
|
||||
continue
|
||||
raise ValueError(
|
||||
f"Node #{new_node.id} input '{creds_field_name}' updated with "
|
||||
f"non-existent credentials #{creds_meta['id']}"
|
||||
|
||||
@@ -748,6 +748,10 @@ const FlowEditor: React.FC<{
|
||||
block_id: blockID,
|
||||
isOutputStatic: nodeSchema.staticOutput,
|
||||
uiType: nodeSchema.uiType,
|
||||
// MCP blocks have optional credentials (public servers don't need auth)
|
||||
...(blockID === SpecialBlockID.MCP_TOOL && {
|
||||
metadata: { credentials_optional: true },
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user