mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix(mcp): keep inlined body schema containers JSON native
The MCP server validates the raw inputSchema against the JSON Schema metaschema on every tool call, where a tuple is not an array, so tools with a required body field became uncallable. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
b387ae2737
commit
7e59527359
2 changed files with 39 additions and 5 deletions
|
|
@ -266,6 +266,8 @@ def resolve_operation_params(
|
|||
|
||||
_SCHEMA_REF_PREFIX: Final = "#/components/schemas/"
|
||||
_EMPTY_SCHEMA: Final[Mapping[str, Any]] = MappingProxyType({})
|
||||
_EMPTY_JSON_OBJECT: Final[Mapping[str, Any]] = {} # mutable-ok: MCP validates the raw schema; only real dicts pass
|
||||
_EMPTY_JSON_ARRAY: Final[Sequence[str]] = [] # mutable-ok: MCP validates the raw schema; only real lists pass
|
||||
|
||||
|
||||
def _inline_schema_refs(
|
||||
|
|
@ -300,7 +302,8 @@ def _inlined_schema_value(
|
|||
if isinstance(value, Mapping):
|
||||
return _inline_schema_refs(value, component_schemas, seen)
|
||||
if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
|
||||
return tuple(_inlined_schema_value(item, component_schemas, seen) for item in value)
|
||||
# mutable-ok: MCP validates the raw schema against the JSON Schema metaschema, where a tuple is not an array
|
||||
return [_inlined_schema_value(item, component_schemas, seen) for item in value]
|
||||
return value
|
||||
|
||||
|
||||
|
|
@ -368,8 +371,8 @@ def build_input_schema(operation: Mapping[str, Any], components: _OpenAPICompone
|
|||
properties["body"] = {
|
||||
"type": "object",
|
||||
"description": request_body.get("description", "Request body"),
|
||||
"properties": schema.get("properties", _EMPTY_SCHEMA),
|
||||
"required": tuple(schema.get("required", ())),
|
||||
"properties": schema.get("properties", _EMPTY_JSON_OBJECT),
|
||||
"required": schema.get("required", _EMPTY_JSON_ARRAY),
|
||||
}
|
||||
if request_body.get("required", False):
|
||||
required.append("body")
|
||||
|
|
|
|||
|
|
@ -452,7 +452,7 @@ class TestBuildInputSchema:
|
|||
assert set(body["properties"]) == {"resourceType", "name", "namespace"}
|
||||
assert body["properties"]["resourceType"]["description"] == "Type of resource to get"
|
||||
assert body["properties"]["namespace"]["default"] == "default"
|
||||
assert body["required"] == ("resourceType",)
|
||||
assert body["required"] == ["resourceType"]
|
||||
assert schema["required"] == ["body"]
|
||||
|
||||
def test_request_body_nested_refs_are_dereferenced(self):
|
||||
|
|
@ -503,6 +503,37 @@ class TestBuildInputSchema:
|
|||
assert body["properties"]["maybe_inner"]["anyOf"][0]["properties"] == inner_props
|
||||
assert "$ref" not in json.dumps(body)
|
||||
|
||||
def test_request_body_schema_uses_json_native_containers(self):
|
||||
"""The MCP server validates the raw inputSchema against the JSON Schema
|
||||
metaschema on every tool call, so a tuple or a mapping proxy in place of
|
||||
an array or object makes the tool uncallable.
|
||||
"""
|
||||
operation = {
|
||||
"requestBody": {
|
||||
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Payload"}}},
|
||||
},
|
||||
}
|
||||
components = {
|
||||
"schemas": {
|
||||
"Payload": {
|
||||
"type": "object",
|
||||
"required": ["mode"],
|
||||
"properties": {
|
||||
"mode": {"type": "string", "enum": ["fast", "slow"]},
|
||||
"name": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
schema = build_input_schema(operation, components)
|
||||
|
||||
assert json.loads(json.dumps(schema)) == schema
|
||||
|
||||
empty_body = build_input_schema({"requestBody": {"content": {"application/json": {}}}})["properties"]["body"]
|
||||
assert isinstance(empty_body["properties"], dict)
|
||||
assert isinstance(empty_body["required"], list)
|
||||
|
||||
def test_recursive_request_body_ref_terminates(self):
|
||||
"""A self-referencing schema must not recurse forever."""
|
||||
operation = {
|
||||
|
|
@ -566,7 +597,7 @@ class TestBuildInputSchema:
|
|||
body = schema["properties"]["body"]
|
||||
assert body["description"] == "The payload"
|
||||
assert body["properties"] == {"name": {"type": "string"}}
|
||||
assert body["required"] == ("name",)
|
||||
assert body["required"] == ["name"]
|
||||
assert schema["required"] == ["body"]
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue