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:
centdix
2026-01-30 14:57:59 +01:00
committed by GitHub
parent 9d2785bece
commit 297aa23ed4
3 changed files with 77 additions and 9 deletions
+60
View File
@@ -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);
}
}
}
+4 -1
View File
@@ -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);
+13 -8
View File
@@ -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!(