mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 00:02:03 +00:00
fix: add schema compatibility layer for MCP clients like n8n (#7747)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,3 +39,63 @@ pub fn extract_resource_types_from_schema(schema: &SchemaType) -> HashSet<String
|
||||
}
|
||||
resource_types
|
||||
}
|
||||
|
||||
/// Transform a JSON schema for maximum MCP client compatibility.
|
||||
///
|
||||
/// Some MCP clients (e.g., n8n) have limited JSON Schema support:
|
||||
/// - `integer` type is not supported (convert to `number`)
|
||||
pub fn make_schema_compatible(schema: &mut Value) {
|
||||
let Value::Object(obj) = schema else { return };
|
||||
|
||||
// 1. Convert integer to number
|
||||
if let Some(type_val) = obj.get_mut("type") {
|
||||
match type_val {
|
||||
Value::String(s) if s == "integer" => *s = "number".to_string(),
|
||||
Value::Array(arr) => {
|
||||
for item in arr.iter_mut() {
|
||||
if let Value::String(s) = item {
|
||||
if s == "integer" {
|
||||
*s = "number".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively process nested schemas
|
||||
if let Some(Value::Object(props)) = obj.get_mut("properties") {
|
||||
for value in props.values_mut() {
|
||||
make_schema_compatible(value);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(items) = obj.get_mut("items") {
|
||||
make_schema_compatible(items);
|
||||
}
|
||||
|
||||
if let Some(additional) = obj.get_mut("additionalProperties") {
|
||||
if additional.is_object() {
|
||||
make_schema_compatible(additional);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Value::Array(all_of)) = obj.get_mut("allOf") {
|
||||
for s in all_of.iter_mut() {
|
||||
make_schema_compatible(s);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Value::Array(one_of)) = obj.get_mut("oneOf") {
|
||||
for s in one_of.iter_mut() {
|
||||
make_schema_compatible(s);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Value::Array(any_of)) = obj.get_mut("anyOf") {
|
||||
for s in any_of.iter_mut() {
|
||||
make_schema_compatible(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::common::schema::make_schema_compatible;
|
||||
|
||||
/// Represents an auto-generated endpoint tool from OpenAPI specification
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct EndpointTool {
|
||||
@@ -37,11 +39,12 @@ pub fn endpoint_tool_to_mcp_tool(tool: &EndpointTool) -> Tool {
|
||||
merge_schema_into(&mut combined_properties, &mut combined_required, schema);
|
||||
}
|
||||
|
||||
let combined_schema = serde_json::json!({
|
||||
let mut combined_schema = serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": combined_properties,
|
||||
"required": combined_required
|
||||
});
|
||||
make_schema_compatible(&mut combined_schema);
|
||||
|
||||
let description = format!("{}. {}", tool.description, tool.instructions);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::common::schema::convert_schema_to_schema_type;
|
||||
use crate::common::schema::{convert_schema_to_schema_type, make_schema_compatible};
|
||||
use crate::common::transform::transform_path;
|
||||
use crate::common::types::{
|
||||
FlowInfo, HubScriptInfo, ResourceInfo, ResourceType, SchemaType, ScriptInfo, ToolableItem,
|
||||
@@ -147,13 +147,18 @@ pub fn create_tool_from_item<T: ToolableItem, B: McpBackend>(
|
||||
backend.transform_schema_for_resources(&schema, resources_cache, resources_types);
|
||||
|
||||
let input_schema_map = match serde_json::to_value(schema_obj) {
|
||||
Ok(serde_json::Value::Object(map)) => map,
|
||||
Ok(_) => {
|
||||
tracing::warn!(
|
||||
"Schema object for tool '{}' did not serialize to a JSON object, using empty schema.",
|
||||
path
|
||||
);
|
||||
serde_json::Map::new()
|
||||
Ok(mut value) => {
|
||||
make_schema_compatible(&mut value);
|
||||
match value {
|
||||
serde_json::Value::Object(map) => map,
|
||||
_ => {
|
||||
tracing::warn!(
|
||||
"Schema object for tool '{}' did not serialize to a JSON object, using empty schema.",
|
||||
path
|
||||
);
|
||||
serde_json::Map::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
|
||||
Reference in New Issue
Block a user