diff --git a/backend/generate_mcp_endpoints_tools/generate_mcp_tools.py b/backend/generate_mcp_endpoints_tools/generate_mcp_tools.py index b120a00d39..20ea1a0a6c 100644 --- a/backend/generate_mcp_endpoints_tools/generate_mcp_tools.py +++ b/backend/generate_mcp_endpoints_tools/generate_mcp_tools.py @@ -165,13 +165,14 @@ def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Opt conflicts = (path_keys & query_keys) | (path_keys & body_keys) | (query_keys & body_keys) - path_field_renames = {} query_field_renames = {} body_field_renames = {} for field in conflicts: + # The path parameter keeps the plain name: it identifies the item, is what a + # caller reaches for first, and matches the non-colliding endpoints (`getFlowByPath` + # takes `path`). Only the other locations carry a suffix. schemas_and_renames = [ - (path_params_schema, path_keys, '__path', path_field_renames), (query_params_schema, query_keys, '__query', query_field_renames), (body_schema, body_keys, '__body', body_field_renames), ] @@ -197,11 +198,26 @@ def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Opt # Store the reverse mapping: renamed -> original renames_map[new_name] = field + # A body field colliding with a same-named path parameter (`path` on the update + # endpoints) holds the new value and differs only when moving the item, so it must + # stay optional; the server defaults it from the URL path when the caller omits it. + if field in path_keys and field in body_keys and body_schema: + body_name = field + '__body' + if 'required' in body_schema: + body_schema['required'] = [r for r in body_schema['required'] if r != body_name] + prop = body_schema['properties'].get(body_name) + if isinstance(prop, dict): + existing_desc = prop.get('description', '').rstrip('. ') + prop['description'] = ( + f"{existing_desc}. Defaults to `{field}` when omitted; " + f"set it only to change the {field}." + ).lstrip('. ') + # Return None for empty schemas path_params_schema = path_params_schema if path_params_schema and path_params_schema.get('properties') else None query_params_schema = query_params_schema if query_params_schema and query_params_schema.get('properties') else None - return (path_params_schema, query_params_schema, body_schema, path_field_renames, query_field_renames, body_field_renames) + return (path_params_schema, query_params_schema, body_schema, query_field_renames, body_field_renames) # Cache for loaded external files _external_file_cache: Dict[str, Dict[str, Any]] = {} @@ -453,7 +469,7 @@ export const mcpEndpointTools: EndpointTool[] = []; method = tool['method'].upper() # Generate separate schemas - path_params_schema, query_params_schema, body_schema, path_field_renames, query_field_renames, body_field_renames = extract_separate_schemas( + path_params_schema, query_params_schema, body_schema, query_field_renames, body_field_renames = extract_separate_schemas( tool['parameters'], tool['requestBody'], spec, tool['required_fields'], base_path, tool.get('include_fields'), tool.get('opaque_fields'), tool.get('include_query_params') ) @@ -462,7 +478,6 @@ export const mcpEndpointTools: EndpointTool[] = []; path_params_ts = json.dumps(path_params_schema, indent=8) if path_params_schema else "undefined" query_params_ts = json.dumps(query_params_schema, indent=8) if query_params_schema else "undefined" body_schema_ts = json.dumps(body_schema, indent=8) if body_schema else "undefined" - path_field_renames_ts = json.dumps(path_field_renames, indent=8) if path_field_renames else "undefined" query_field_renames_ts = json.dumps(query_field_renames, indent=8) if query_field_renames else "undefined" body_field_renames_ts = json.dumps(body_field_renames, indent=8) if body_field_renames else "undefined" @@ -476,7 +491,6 @@ export const mcpEndpointTools: EndpointTool[] = []; pathParamsSchema: {path_params_ts}, queryParamsSchema: {query_params_ts}, bodySchema: {body_schema_ts}, - pathFieldRenames: {path_field_renames_ts}, queryFieldRenames: {query_field_renames_ts}, bodyFieldRenames: {body_field_renames_ts} }}""" @@ -497,7 +511,6 @@ export interface EndpointTool {{ pathParamsSchema?: object; queryParamsSchema?: object; bodySchema?: object; - pathFieldRenames?: Record; queryFieldRenames?: Record; bodyFieldRenames?: Record; }} @@ -529,7 +542,7 @@ pub fn all_tools() -> Vec {{ method = tool['method'].upper() # Generate separate schemas - path_params_schema, query_params_schema, body_schema, path_field_renames, query_field_renames, body_field_renames = extract_separate_schemas( + path_params_schema, query_params_schema, body_schema, query_field_renames, body_field_renames = extract_separate_schemas( tool['parameters'], tool['requestBody'], spec, tool['required_fields'], base_path, tool.get('include_fields'), tool.get('opaque_fields'), tool.get('include_query_params') ) @@ -537,7 +550,6 @@ pub fn all_tools() -> Vec {{ path_params_rust = schema_to_rust_value(path_params_schema) query_params_rust = schema_to_rust_value(query_params_schema) body_schema_rust = schema_to_rust_value(body_schema) - path_field_renames_rust = schema_to_rust_value(path_field_renames if path_field_renames else None) query_field_renames_rust = schema_to_rust_value(query_field_renames if query_field_renames else None) body_field_renames_rust = schema_to_rust_value(body_field_renames if body_field_renames else None) @@ -551,7 +563,6 @@ pub fn all_tools() -> Vec {{ path_params_schema: {path_params_rust}, query_params_schema: {query_params_rust}, body_schema: {body_schema_rust}, - path_field_renames: {path_field_renames_rust}, query_field_renames: {query_field_renames_rust}, body_field_renames: {body_field_renames_rust}, }}""" diff --git a/backend/windmill-api-flows/src/flows.rs b/backend/windmill-api-flows/src/flows.rs index a9376d02ed..ff555113c3 100644 --- a/backend/windmill-api-flows/src/flows.rs +++ b/backend/windmill-api-flows/src/flows.rs @@ -47,7 +47,7 @@ use windmill_common::HUB_BASE_URL; use windmill_common::{ db::UserDB, error::{self, to_anyhow, Error, JsonResult, Result}, - flows::{Flow, FlowWithStarred, ListFlowQuery, ListableFlow, NewFlow}, + flows::{EditFlow, Flow, FlowWithStarred, ListFlowQuery, ListableFlow, NewFlow}, jobs::JobPayload, schedule::Schedule, utils::{http_get_from_hub, not_found_if_none, paginate, Pagination, RunnableKind, StripPath}, @@ -1007,7 +1007,7 @@ async fn update_flow( Extension(db): Extension, Extension(webhook): Extension, Path((w_id, flow_path)): Path<(String, StripPath)>, - Json(nf): Json, + Json(ef): Json, ) -> Result { if authed.is_operator { return Err(Error::NotAuthorized( @@ -1015,6 +1015,8 @@ async fn update_flow( )); } let flow_path = flow_path.to_path(); + // The URL identifies the flow being updated; the body path is only needed to rename. + let nf = ef.into_new_flow(flow_path); check_scopes(&authed, || format!("flows:write:{}", flow_path))?; if let RuleCheckResult::Blocked(msg) = check_deploy_rules( diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 668cdae8cc..18a9120356 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -10775,7 +10775,7 @@ paths: application/json: schema: allOf: - - $ref: "#/components/schemas/OpenFlowWPath" + - $ref: "#/components/schemas/EditFlow" - type: object properties: deployment_message: @@ -29097,6 +29097,37 @@ components: type: string required: - path + # Like OpenFlowWPath but `path` is optional: on update the flow is identified by + # the URL, so the body path is only needed to rename it. Kept as a separate schema + # (rather than making OpenFlowWPath.path optional) so createFlow still requires path. + EditFlow: + allOf: + - $ref: "../../openflow.openapi.yaml#/components/schemas/OpenFlow" + - type: object + properties: + path: + type: string + tag: + type: string + ws_error_handler_muted: + type: boolean + priority: + type: integer + dedicated_worker: + type: boolean + timeout: + type: number + visible_to_runner_only: + type: boolean + on_behalf_of_email: + type: string + preserve_on_behalf_of: + type: boolean + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original on_behalf_of_email value instead of overwriting it." + labels: + type: array + items: + type: string FlowPreview: type: object diff --git a/backend/windmill-api/src/mcp/auto_generated_endpoints.rs b/backend/windmill-api/src/mcp/auto_generated_endpoints.rs index d44484f9db..fda5966e30 100644 --- a/backend/windmill-api/src/mcp/auto_generated_endpoints.rs +++ b/backend/windmill-api/src/mcp/auto_generated_endpoints.rs @@ -26,7 +26,6 @@ pub fn all_tools() -> Vec { ] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -54,7 +53,6 @@ pub fn all_tools() -> Vec { ] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -124,7 +122,6 @@ pub fn all_tools() -> Vec { "description" ] })), - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -147,7 +144,6 @@ pub fn all_tools() -> Vec { })), query_params_schema: None, body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -160,13 +156,12 @@ pub fn all_tools() -> Vec { path_params_schema: Some(serde_json::json!({ "type": "object", "properties": { - "path__path": { - "type": "string", - "description": "(path parameter)" + "path": { + "type": "string" } }, "required": [ - "path__path" + "path" ] })), query_params_schema: Some(serde_json::json!({ @@ -205,12 +200,9 @@ pub fn all_tools() -> Vec { }, "path__body": { "type": "string", - "description": "The path to the variable (body parameter)" + "description": "The path to the variable (body parameter). Defaults to `path` when omitted; set it only to change the path." } } -})), - path_field_renames: Some(serde_json::json!({ - "path__path": "path" })), query_field_renames: None, body_field_renames: Some(serde_json::json!({ @@ -253,7 +245,6 @@ pub fn all_tools() -> Vec { "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -307,7 +298,6 @@ pub fn all_tools() -> Vec { "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -362,7 +352,6 @@ pub fn all_tools() -> Vec { "resource_type" ] })), - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -385,7 +374,6 @@ pub fn all_tools() -> Vec { })), query_params_schema: None, body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -398,13 +386,12 @@ pub fn all_tools() -> Vec { path_params_schema: Some(serde_json::json!({ "type": "object", "properties": { - "path__path": { - "type": "string", - "description": "(path parameter)" + "path": { + "type": "string" } }, "required": [ - "path__path" + "path" ] })), query_params_schema: None, @@ -433,12 +420,9 @@ pub fn all_tools() -> Vec { }, "path__body": { "type": "string", - "description": "The path to the resource (body parameter)" + "description": "The path to the resource (body parameter). Defaults to `path` when omitted; set it only to change the path." } } -})), - path_field_renames: Some(serde_json::json!({ - "path__path": "path" })), query_field_renames: None, body_field_renames: Some(serde_json::json!({ @@ -473,7 +457,6 @@ pub fn all_tools() -> Vec { "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -535,7 +518,6 @@ pub fn all_tools() -> Vec { "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -548,7 +530,6 @@ pub fn all_tools() -> Vec { path_params_schema: None, query_params_schema: None, body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -646,7 +627,6 @@ pub fn all_tools() -> Vec { "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -699,7 +679,6 @@ Creates a new version of an existing script when called with the same path and t "language" ] })), - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -722,7 +701,6 @@ Creates a new version of an existing script when called with the same path and t })), query_params_schema: None, body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -754,7 +732,6 @@ Creates a new version of an existing script when called with the same path and t "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -789,7 +766,6 @@ Creates a new version of an existing script when called with the same path and t "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -816,7 +792,6 @@ Creates a new version of an existing script when called with the same path and t "description": "The arguments to pass to the script or flow", "additionalProperties": true })), - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -886,7 +861,6 @@ Creates a new version of an existing script when called with the same path and t "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -921,7 +895,6 @@ Creates a new version of an existing script when called with the same path and t "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -967,7 +940,6 @@ Creates a new version of an existing script when called with the same path and t ], "description": "Top-level flow definition containing metadata, configuration, and the flow structure" })), - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -980,13 +952,12 @@ Creates a new version of an existing script when called with the same path and t path_params_schema: Some(serde_json::json!({ "type": "object", "properties": { - "path__path": { - "type": "string", - "description": "(path parameter)" + "path": { + "type": "string" } }, "required": [ - "path__path" + "path" ] })), query_params_schema: None, @@ -1015,18 +986,14 @@ Creates a new version of an existing script when called with the same path and t }, "path__body": { "type": "string", - "description": "(body parameter)" + "description": "(body parameter). Defaults to `path` when omitted; set it only to change the path." } }, "required": [ "summary", - "value", - "path__body" + "value" ], "description": "Top-level flow definition containing metadata, configuration, and the flow structure" -})), - path_field_renames: Some(serde_json::json!({ - "path__path": "path" })), query_field_renames: None, body_field_renames: Some(serde_json::json!({ @@ -1061,7 +1028,6 @@ Creates a new version of an existing script when called with the same path and t "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -1099,7 +1065,6 @@ Creates a new version of an existing script when called with the same path and t "policy" ] })), - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -1112,13 +1077,12 @@ Creates a new version of an existing script when called with the same path and t path_params_schema: Some(serde_json::json!({ "type": "object", "properties": { - "path__path": { - "type": "string", - "description": "(path parameter)" + "path": { + "type": "string" } }, "required": [ - "path__path" + "path" ] })), query_params_schema: None, @@ -1139,12 +1103,9 @@ Creates a new version of an existing script when called with the same path and t }, "path__body": { "type": "string", - "description": "(body parameter)" + "description": "(body parameter). Defaults to `path` when omitted; set it only to change the path." } } -})), - path_field_renames: Some(serde_json::json!({ - "path__path": "path" })), query_field_renames: None, body_field_renames: Some(serde_json::json!({ @@ -1174,7 +1135,6 @@ Creates a new version of an existing script when called with the same path and t "description": "The arguments to pass to the script or flow", "additionalProperties": true })), - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -1269,7 +1229,6 @@ Creates a new version of an existing script when called with the same path and t "language" ] })), - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -1390,7 +1349,6 @@ Creates a new version of an existing script when called with the same path and t "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -1557,7 +1515,6 @@ Creates a new version of an existing script when called with the same path and t "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -1596,7 +1553,6 @@ Creates a new version of an existing script when called with the same path and t "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -1628,7 +1584,6 @@ Creates a new version of an existing script when called with the same path and t "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -1841,7 +1796,6 @@ You should get the schema of the script or flow before creating the schedule to "args" ] })), - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -2047,7 +2001,6 @@ You should get the schema of the script or flow before updating the schedule to "args" ] })), - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -2070,7 +2023,6 @@ You should get the schema of the script or flow before updating the schedule to })), query_params_schema: None, body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -2102,7 +2054,6 @@ You should get the schema of the script or flow before updating the schedule to "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -2168,7 +2119,6 @@ You should get the schema of the script or flow before updating the schedule to "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, }, @@ -2198,7 +2148,6 @@ You should get the schema of the script or flow before updating the schedule to "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, } diff --git a/backend/windmill-api/src/mcp/core.rs b/backend/windmill-api/src/mcp/core.rs index cdf1f0b3a3..5d78536d86 100644 --- a/backend/windmill-api/src/mcp/core.rs +++ b/backend/windmill-api/src/mcp/core.rs @@ -295,7 +295,6 @@ impl McpBackend for WindmillBackend { workspace_id, args_map, &endpoint_tool.path_params_schema, - &endpoint_tool.path_field_renames, )?; let query_string = build_query_string( args_map, diff --git a/backend/windmill-api/src/mcp/utils.rs b/backend/windmill-api/src/mcp/utils.rs index 70a4c6c1f4..7bca92dd57 100644 --- a/backend/windmill-api/src/mcp/utils.rs +++ b/backend/windmill-api/src/mcp/utils.rs @@ -295,7 +295,7 @@ pub async fn get_hub_script_schema(path: &str, db: &DB) -> Result // ============================================================================ /// Look up the original field name from a field_renames map. -/// field_renames maps renamed_key -> original_key (e.g. {"path__path": "path"}). +/// field_renames maps renamed_key -> original_key (e.g. {"path__body": "path"}). fn get_original_name(renamed_key: &str, field_renames: &Option) -> String { field_renames .as_ref() @@ -373,20 +373,17 @@ pub fn substitute_path_params( workspace_id: &str, args_map: &serde_json::Map, path_schema: &Option, - path_field_renames: &Option, ) -> BackendResult { let mut path_template = path.replace("{workspace}", workspace_id); if let Some(schema) = path_schema { if let Some(props) = schema.get("properties").and_then(|p| p.as_object()) { for (param_name, _) in props { - // param_name may be renamed (e.g. "path__path"), get original for URL placeholder - let original_name = get_original_name(param_name, path_field_renames); - let placeholder = format!("{{{}}}", original_name); + let placeholder = format!("{{{}}}", param_name); match args_map.get(param_name) { Some(param_value) => { if let Some(str_val) = param_value.as_str() { - validate_path_param_value(&original_name, str_val)?; + validate_path_param_value(param_name, str_val)?; path_template = path_template.replace(&placeholder, str_val); } } @@ -675,6 +672,60 @@ mod tests { ); } + // updateFlow-shaped: `path` is both a path parameter and a body field. The path + // parameter keeps the plain name; only the body side is mangled. + fn update_flow_schemas() -> (Option, Option, Option) { + ( + Some(json!({ + "type": "object", + "properties": { "path": { "type": "string" } }, + "required": ["path"] + })), + Some(json!({ + "type": "object", + "properties": { + "summary": { "type": "string" }, + "value": { "type": "object" }, + "path__body": { "type": "string" } + }, + "required": ["summary", "value"] + })), + Some(json!({ "path__body": "path" })), + ) + } + + #[test] + fn build_request_body_maps_body_path_alias_for_rename() { + // The mangled body field carries the *new* path when renaming; it must reach the + // API under its original name `path`. (An omitted `path__body` is intentionally + // absent from the body; the server defaults it from the URL path parameter.) + let (path_schema, body_schema, body_renames) = update_flow_schemas(); + let args: serde_json::Map = json!({ + "path": "f/team/my_flow", + "path__body": "f/team/renamed_flow", + "summary": "s", + "value": {} + }) + .as_object() + .unwrap() + .clone(); + + let body = build_request_body( + "POST", + &args, + &body_schema, + &body_renames, + &path_schema, + &None, + ) + .expect("body should be built"); + assert_eq!( + body.as_object().unwrap().get("path"), + Some(&json!("f/team/renamed_flow")), + "path__body must be sent as `path` so a rename takes effect" + ); + } + #[test] fn build_request_body_get_has_no_body() { let body_schema = Some(json!({ "type": "object", "additionalProperties": true })); @@ -745,7 +796,6 @@ mod tests { "dev", &args, &path_schema, - &None, ); assert!( result.is_err(), @@ -767,7 +817,6 @@ mod tests { "dev", &args, &path_schema, - &None, ) .expect("legitimate path should substitute"); assert_eq!(result, "/w/dev/scripts/get/p/u/alice/my_script"); diff --git a/backend/windmill-mcp/src/server/endpoints.rs b/backend/windmill-mcp/src/server/endpoints.rs index c3a50f9715..9e3adffc0b 100644 --- a/backend/windmill-mcp/src/server/endpoints.rs +++ b/backend/windmill-mcp/src/server/endpoints.rs @@ -21,7 +21,6 @@ pub struct EndpointTool { pub path_params_schema: Option, pub query_params_schema: Option, pub body_schema: Option, - pub path_field_renames: Option, pub query_field_renames: Option, pub body_field_renames: Option, } @@ -215,7 +214,6 @@ mod tests { "required": [] })), body_schema: None, - path_field_renames: None, query_field_renames: None, body_field_renames: None, } diff --git a/backend/windmill-types/src/flows.rs b/backend/windmill-types/src/flows.rs index 0c95b9612b..7e70dc5acd 100644 --- a/backend/windmill-types/src/flows.rs +++ b/backend/windmill-types/src/flows.rs @@ -145,6 +145,57 @@ impl NewFlow { } } +/// Body for updating an existing flow. Mirrors `NewFlow`, but `path` is optional: the +/// flow to update is identified by the URL, so the body only needs `path` to rename it. +/// This matches the `EditVariable` / `EditResource` / `EditApp` convention and lets a +/// caller update in place without restating the path. +#[derive(Debug, Deserialize)] +pub struct EditFlow { + #[serde(default)] + pub path: Option, + pub summary: String, + pub description: Option, + #[serde(deserialize_with = "validate_flow_value")] + pub value: Box, + pub schema: Option, + pub tag: Option, + pub dedicated_worker: Option, + pub timeout: Option, + pub deployment_message: Option, + pub visible_to_runner_only: Option, + pub on_behalf_of_email: Option, + pub preserve_on_behalf_of: Option, + pub ws_error_handler_muted: Option, + #[serde(default)] + pub labels: Option>, + #[serde(default)] + pub skip_draft_deletion: Option, +} + +impl EditFlow { + /// Resolve into a `NewFlow`, defaulting the target path to `current_path` (the flow's + /// URL path) when the body omits it. A body `path` that differs renames the flow. + pub fn into_new_flow(self, current_path: &str) -> NewFlow { + NewFlow { + path: self.path.unwrap_or_else(|| current_path.to_string()), + summary: self.summary, + description: self.description, + value: self.value, + schema: self.schema, + tag: self.tag, + dedicated_worker: self.dedicated_worker, + timeout: self.timeout, + deployment_message: self.deployment_message, + visible_to_runner_only: self.visible_to_runner_only, + on_behalf_of_email: self.on_behalf_of_email, + preserve_on_behalf_of: self.preserve_on_behalf_of, + ws_error_handler_muted: self.ws_error_handler_muted, + labels: self.labels, + skip_draft_deletion: self.skip_draft_deletion, + } + } +} + fn validate_retry(retry: &Retry, module_id: &str) -> anyhow::Result<()> { if retry.exponential.attempts > 0 && retry.exponential.seconds == 0 { return Err(anyhow::anyhow!( @@ -1233,6 +1284,20 @@ mod tests { use super::*; use serde_json::json; + #[test] + fn edit_flow_defaults_path_from_url_and_renames_when_given() { + // An omitted body path resolves to the URL path; an explicit body path renames. + let ef: EditFlow = + serde_json::from_value(json!({ "summary": "s", "value": { "modules": [] } })).unwrap(); + assert_eq!(ef.into_new_flow("f/team/my_flow").path, "f/team/my_flow"); + + let ef: EditFlow = serde_json::from_value( + json!({ "path": "f/team/renamed", "summary": "s", "value": { "modules": [] } }), + ) + .unwrap(); + assert_eq!(ef.into_new_flow("f/team/my_flow").path, "f/team/renamed"); + } + #[test] fn flow_value_ignores_notes_and_groups() { // FlowValue should parse successfully even when notes/groups are present — diff --git a/frontend/src/lib/mcpEndpointTools.ts b/frontend/src/lib/mcpEndpointTools.ts index 8a46163009..336c24cefc 100644 --- a/frontend/src/lib/mcpEndpointTools.ts +++ b/frontend/src/lib/mcpEndpointTools.ts @@ -10,7 +10,6 @@ export interface EndpointTool { pathParamsSchema?: object; queryParamsSchema?: object; bodySchema?: object; - pathFieldRenames?: Record; queryFieldRenames?: Record; bodyFieldRenames?: Record; } @@ -36,7 +35,6 @@ export const mcpEndpointTools: EndpointTool[] = [ ] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -64,7 +62,6 @@ export const mcpEndpointTools: EndpointTool[] = [ ] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -134,7 +131,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "description" ] }, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -157,7 +153,6 @@ export const mcpEndpointTools: EndpointTool[] = [ }, queryParamsSchema: undefined, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -170,13 +165,12 @@ export const mcpEndpointTools: EndpointTool[] = [ pathParamsSchema: { "type": "object", "properties": { - "path__path": { - "type": "string", - "description": "(path parameter)" + "path": { + "type": "string" } }, "required": [ - "path__path" + "path" ] }, queryParamsSchema: { @@ -215,12 +209,9 @@ export const mcpEndpointTools: EndpointTool[] = [ }, "path__body": { "type": "string", - "description": "The path to the variable (body parameter)" + "description": "The path to the variable (body parameter). Defaults to `path` when omitted; set it only to change the path." } } -}, - pathFieldRenames: { - "path__path": "path" }, queryFieldRenames: undefined, bodyFieldRenames: { @@ -263,7 +254,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -317,7 +307,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -372,7 +361,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "resource_type" ] }, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -395,7 +383,6 @@ export const mcpEndpointTools: EndpointTool[] = [ }, queryParamsSchema: undefined, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -408,13 +395,12 @@ export const mcpEndpointTools: EndpointTool[] = [ pathParamsSchema: { "type": "object", "properties": { - "path__path": { - "type": "string", - "description": "(path parameter)" + "path": { + "type": "string" } }, "required": [ - "path__path" + "path" ] }, queryParamsSchema: undefined, @@ -443,12 +429,9 @@ export const mcpEndpointTools: EndpointTool[] = [ }, "path__body": { "type": "string", - "description": "The path to the resource (body parameter)" + "description": "The path to the resource (body parameter). Defaults to `path` when omitted; set it only to change the path." } } -}, - pathFieldRenames: { - "path__path": "path" }, queryFieldRenames: undefined, bodyFieldRenames: { @@ -483,7 +466,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -545,7 +527,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -558,7 +539,6 @@ export const mcpEndpointTools: EndpointTool[] = [ pathParamsSchema: undefined, queryParamsSchema: undefined, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -656,7 +636,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -708,7 +687,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "language" ] }, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -731,7 +709,6 @@ export const mcpEndpointTools: EndpointTool[] = [ }, queryParamsSchema: undefined, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -763,7 +740,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -798,7 +774,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -825,7 +800,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "description": "The arguments to pass to the script or flow", "additionalProperties": true }, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -895,7 +869,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -930,7 +903,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -976,7 +948,6 @@ export const mcpEndpointTools: EndpointTool[] = [ ], "description": "Top-level flow definition containing metadata, configuration, and the flow structure" }, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -989,13 +960,12 @@ export const mcpEndpointTools: EndpointTool[] = [ pathParamsSchema: { "type": "object", "properties": { - "path__path": { - "type": "string", - "description": "(path parameter)" + "path": { + "type": "string" } }, "required": [ - "path__path" + "path" ] }, queryParamsSchema: undefined, @@ -1024,18 +994,14 @@ export const mcpEndpointTools: EndpointTool[] = [ }, "path__body": { "type": "string", - "description": "(body parameter)" + "description": "(body parameter). Defaults to `path` when omitted; set it only to change the path." } }, "required": [ "summary", - "value", - "path__body" + "value" ], "description": "Top-level flow definition containing metadata, configuration, and the flow structure" -}, - pathFieldRenames: { - "path__path": "path" }, queryFieldRenames: undefined, bodyFieldRenames: { @@ -1070,7 +1036,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -1108,7 +1073,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "policy" ] }, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -1121,13 +1085,12 @@ export const mcpEndpointTools: EndpointTool[] = [ pathParamsSchema: { "type": "object", "properties": { - "path__path": { - "type": "string", - "description": "(path parameter)" + "path": { + "type": "string" } }, "required": [ - "path__path" + "path" ] }, queryParamsSchema: undefined, @@ -1148,12 +1111,9 @@ export const mcpEndpointTools: EndpointTool[] = [ }, "path__body": { "type": "string", - "description": "(body parameter)" + "description": "(body parameter). Defaults to `path` when omitted; set it only to change the path." } } -}, - pathFieldRenames: { - "path__path": "path" }, queryFieldRenames: undefined, bodyFieldRenames: { @@ -1183,7 +1143,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "description": "The arguments to pass to the script or flow", "additionalProperties": true }, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -1278,7 +1237,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "language" ] }, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -1399,7 +1357,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -1566,7 +1523,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -1605,7 +1561,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -1637,7 +1592,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -1847,7 +1801,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "args" ] }, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -2050,7 +2003,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "args" ] }, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -2073,7 +2025,6 @@ export const mcpEndpointTools: EndpointTool[] = [ }, queryParamsSchema: undefined, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -2105,7 +2056,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -2171,7 +2121,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }, @@ -2201,7 +2150,6 @@ export const mcpEndpointTools: EndpointTool[] = [ "required": [] }, bodySchema: undefined, - pathFieldRenames: undefined, queryFieldRenames: undefined, bodyFieldRenames: undefined }