[GH-ISSUE #87] MCP tool schema conversion drops top-level $defs while preserving $ref, producing invalid OpenAI tool schemas #41

Closed
opened 2026-10-18 14:02:18 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @hackadelic on GitHub (Jun 30, 2026).
Original GitHub issue: https://github.com/open-webui/computer/issues/87

Description

CPTR's MCP tool conversion drops top-level JSON Schema fields such as $defs while preserving properties that reference those definitions with $ref.

This produces invalid OpenAI-compatible tool schemas. Backends that resolve or compile tool schemas then reject the entire chat request before inference begins.

I encountered this with CPTR 0.7.1 using Crawl4AI MCP tools and an OpenAI-compatible LiteLLM → llama-swap → llama.cpp backend.

Environment

  • CPTR version: 0.7.1

  • CPTR host: Linux

  • MCP tools: Crawl4AI, among others

  • OpenAI-compatible route:

    • CPTR
    • LiteLLM
    • llama-swap
    • llama.cpp
  • Model: ls5090/unsloth/qwen3.6-27b-mtp

The model and llama-swap route are not the source of the malformed schemas. The failure occurs while the backend attempts to convert the tool JSON Schema into its tool-call parser, before model inference starts.

Actual error

litellm.BadRequestError: OpenAIException -
Unable to generate parser for this template.
Automatic parser generation failed:
JSON schema conversion failed:

Error resolving ref #/$defs/FilterType:
$defs not in {
  "type": "object",
  "properties": {
    "url": {
      "description": "Absolute http/https URL to fetch",
      "title": "Url",
      "type": "string"
    },
    "f": {
      "$ref": "#/$defs/FilterType",
      "default": "fit",
      "description": "Content-filter strategy: fit, raw, bm25, or llm"
    }
  },
  "required": ["url"]
}

Error resolving ref #/$defs/HookConfig:
$defs not in {
  "type": "object",
  "properties": {
    "urls": {
      "items": {"type": "string"},
      "maxItems": 100,
      "minItems": 1,
      "title": "Urls",
      "type": "array"
    },
    "hooks": {
      "anyOf": [
        {"$ref": "#/$defs/HookConfig"},
        {"type": "null"}
      ],
      "default": null,
      "description": "Optional user-provided hook functions"
    }
  },
  "required": ["urls"]
}

Malformed schemas sent by CPTR

For the Crawl4AI Markdown tool, CPTR sends a property containing:

{
  "f": {
    "$ref": "#/$defs/FilterType",
    "default": "fit",
    "description": "Content-filter strategy: fit, raw, bm25, or llm"
  }
}

However, the surrounding parameters schema contains no $defs object defining FilterType.

A second tool independently has the same problem:

{
  "hooks": {
    "anyOf": [
      {
        "$ref": "#/$defs/HookConfig"
      },
      {
        "type": "null"
      }
    ],
    "default": null,
    "description": "Optional user-provided hook functions"
  }
}

Again, the surrounding schema contains no $defs.HookConfig.

This shows that it is not one malformed MCP tool definition. Multiple MCP schemas lose their root definitions during CPTR's conversion.

Apparent root cause

In:

cptr/utils/mcp/client.py

MCPClient.list_tool_specs() currently creates a new reduced parameter schema:

spec = {
    "name": tool.name,
    "description": tool.description or "",
    "parameters": {
        "type": "object",
        "properties": {},
        "required": [],
    },
}

if tool.inputSchema:
    schema = tool.inputSchema
    spec["parameters"]["properties"] = schema.get("properties", {})
    spec["parameters"]["required"] = schema.get("required", [])

This preserves nested $ref values because they are inside properties, but discards the top-level $defs object those references depend on.

It also drops any other root-level JSON Schema keywords, including potentially:

  • $schema
  • $id
  • $defs
  • definitions
  • allOf
  • oneOf
  • anyOf
  • additionalProperties
  • patternProperties
  • dependentSchemas
  • unevaluatedProperties
  • root-level descriptions and constraints

An MCP inputSchema is already a JSON Schema document and should not be reconstructed by selecting only properties and required.

Expected behavior

CPTR should preserve the complete MCP tool.inputSchema when converting it to an OpenAI-compatible function definition.

A schema such as:

{
  "type": "object",
  "properties": {
    "f": {
      "$ref": "#/$defs/FilterType"
    }
  },
  "$defs": {
    "FilterType": {
      "type": "string",
      "enum": ["fit", "raw", "bm25", "llm"]
    }
  }
}

should retain both:

properties.f.$ref

and:

$defs.FilterType

Suggested fix

Preserve the complete input schema instead of copying only two fields.

For example:

from copy import deepcopy
async def list_tool_specs(self) -> list[dict]:
    """List tools from the connected server as OpenAI-compatible schemas."""
    if not self.session:
        raise RuntimeError("MCPClient is not connected")

    result = await self.session.list_tools()
    specs = []

    for tool in result.tools:
        if tool.inputSchema:
            parameters = deepcopy(tool.inputSchema)

            if not isinstance(parameters, dict):
                parameters = dict(parameters)

            parameters.setdefault("type", "object")
            parameters.setdefault("properties", {})
        else:
            parameters = {
                "type": "object",
                "properties": {},
            }

        specs.append(
            {
                "name": tool.name,
                "description": tool.description or "",
                "parameters": parameters,
            }
        )

    return specs

A smaller change would be:

if tool.inputSchema:
    spec["parameters"] = deepcopy(tool.inputSchema)

with suitable validation/default handling.

Minimal regression test

Create a mocked MCP tool with this inputSchema:

input_schema = {
    "type": "object",
    "properties": {
        "f": {
            "$ref": "#/$defs/FilterType",
        }
    },
    "$defs": {
        "FilterType": {
            "type": "string",
            "enum": ["fit", "raw"],
        }
    },
}

After list_tool_specs(), assert:

parameters = specs[0]["parameters"]

assert parameters["properties"]["f"]["$ref"] == "#/$defs/FilterType"
assert parameters["$defs"]["FilterType"]["type"] == "string"
assert parameters["$defs"]["FilterType"]["enum"] == ["fit", "raw"]

It would also be useful to verify preservation of another root keyword such as:

assert parameters["additionalProperties"] is False

Steps to reproduce

  1. Install CPTR 0.7.1 with MCP support.
  2. Configure an MCP server exposing a tool whose inputSchema uses $defs and a local $ref.
  3. Select an OpenAI-compatible backend that validates or compiles tool schemas.
  4. Start a CPTR chat with the MCP tool enabled.
  5. Inspect the outgoing OpenAI tool definition.
  6. Observe that the property still contains:
{"$ref": "#/$defs/SomeDefinition"}

but the parent parameters object no longer contains:

{"$defs": {"SomeDefinition": {}}}
  1. The backend rejects the request because the reference cannot be resolved.

Impact

Any MCP server using valid JSON Schema references can make all model requests fail before inference.

This is especially likely with schemas generated by Pydantic, which commonly place reusable models and enums under $defs.

The issue is backend-independent in principle: any OpenAI-compatible provider that validates, dereferences, or compiles the transmitted schemas may reject them.

Backends that silently ignore unresolved schema references might appear to work, which can hide the problem.

Workaround

Locally changing CPTR to preserve the complete tool.inputSchema resolves the malformed-schema portion of the request.

Removing the affected MCP tools or stripping $ref values is not a good permanent workaround because it discards valid schema information and weakens tool validation.

Security note

I have intentionally not attached the complete raw chat request because it includes private conversation content and credentials. The relevant sanitized tool-schema excerpts and backend error are included above.

Originally created by @hackadelic on GitHub (Jun 30, 2026). Original GitHub issue: https://github.com/open-webui/computer/issues/87 ## Description CPTR's MCP tool conversion drops top-level JSON Schema fields such as `$defs` while preserving properties that reference those definitions with `$ref`. This produces invalid OpenAI-compatible tool schemas. Backends that resolve or compile tool schemas then reject the entire chat request before inference begins. I encountered this with CPTR 0.7.1 using Crawl4AI MCP tools and an OpenAI-compatible LiteLLM → llama-swap → llama.cpp backend. ## Environment * CPTR version: `0.7.1` * CPTR host: Linux * MCP tools: Crawl4AI, among others * OpenAI-compatible route: * CPTR * LiteLLM * llama-swap * llama.cpp * Model: `ls5090/unsloth/qwen3.6-27b-mtp` The model and llama-swap route are not the source of the malformed schemas. The failure occurs while the backend attempts to convert the tool JSON Schema into its tool-call parser, before model inference starts. ## Actual error ```text litellm.BadRequestError: OpenAIException - Unable to generate parser for this template. Automatic parser generation failed: JSON schema conversion failed: Error resolving ref #/$defs/FilterType: $defs not in { "type": "object", "properties": { "url": { "description": "Absolute http/https URL to fetch", "title": "Url", "type": "string" }, "f": { "$ref": "#/$defs/FilterType", "default": "fit", "description": "Content-filter strategy: fit, raw, bm25, or llm" } }, "required": ["url"] } Error resolving ref #/$defs/HookConfig: $defs not in { "type": "object", "properties": { "urls": { "items": {"type": "string"}, "maxItems": 100, "minItems": 1, "title": "Urls", "type": "array" }, "hooks": { "anyOf": [ {"$ref": "#/$defs/HookConfig"}, {"type": "null"} ], "default": null, "description": "Optional user-provided hook functions" } }, "required": ["urls"] } ``` ## Malformed schemas sent by CPTR For the Crawl4AI Markdown tool, CPTR sends a property containing: ```json { "f": { "$ref": "#/$defs/FilterType", "default": "fit", "description": "Content-filter strategy: fit, raw, bm25, or llm" } } ``` However, the surrounding `parameters` schema contains no `$defs` object defining `FilterType`. A second tool independently has the same problem: ```json { "hooks": { "anyOf": [ { "$ref": "#/$defs/HookConfig" }, { "type": "null" } ], "default": null, "description": "Optional user-provided hook functions" } } ``` Again, the surrounding schema contains no `$defs.HookConfig`. This shows that it is not one malformed MCP tool definition. Multiple MCP schemas lose their root definitions during CPTR's conversion. ## Apparent root cause In: ```text cptr/utils/mcp/client.py ``` `MCPClient.list_tool_specs()` currently creates a new reduced parameter schema: ```python spec = { "name": tool.name, "description": tool.description or "", "parameters": { "type": "object", "properties": {}, "required": [], }, } if tool.inputSchema: schema = tool.inputSchema spec["parameters"]["properties"] = schema.get("properties", {}) spec["parameters"]["required"] = schema.get("required", []) ``` This preserves nested `$ref` values because they are inside `properties`, but discards the top-level `$defs` object those references depend on. It also drops any other root-level JSON Schema keywords, including potentially: * `$schema` * `$id` * `$defs` * `definitions` * `allOf` * `oneOf` * `anyOf` * `additionalProperties` * `patternProperties` * `dependentSchemas` * `unevaluatedProperties` * root-level descriptions and constraints An MCP `inputSchema` is already a JSON Schema document and should not be reconstructed by selecting only `properties` and `required`. ## Expected behavior CPTR should preserve the complete MCP `tool.inputSchema` when converting it to an OpenAI-compatible function definition. A schema such as: ```json { "type": "object", "properties": { "f": { "$ref": "#/$defs/FilterType" } }, "$defs": { "FilterType": { "type": "string", "enum": ["fit", "raw", "bm25", "llm"] } } } ``` should retain both: ```text properties.f.$ref ``` and: ```text $defs.FilterType ``` ## Suggested fix Preserve the complete input schema instead of copying only two fields. For example: ```python from copy import deepcopy ``` ```python async def list_tool_specs(self) -> list[dict]: """List tools from the connected server as OpenAI-compatible schemas.""" if not self.session: raise RuntimeError("MCPClient is not connected") result = await self.session.list_tools() specs = [] for tool in result.tools: if tool.inputSchema: parameters = deepcopy(tool.inputSchema) if not isinstance(parameters, dict): parameters = dict(parameters) parameters.setdefault("type", "object") parameters.setdefault("properties", {}) else: parameters = { "type": "object", "properties": {}, } specs.append( { "name": tool.name, "description": tool.description or "", "parameters": parameters, } ) return specs ``` A smaller change would be: ```python if tool.inputSchema: spec["parameters"] = deepcopy(tool.inputSchema) ``` with suitable validation/default handling. ## Minimal regression test Create a mocked MCP tool with this `inputSchema`: ```python input_schema = { "type": "object", "properties": { "f": { "$ref": "#/$defs/FilterType", } }, "$defs": { "FilterType": { "type": "string", "enum": ["fit", "raw"], } }, } ``` After `list_tool_specs()`, assert: ```python parameters = specs[0]["parameters"] assert parameters["properties"]["f"]["$ref"] == "#/$defs/FilterType" assert parameters["$defs"]["FilterType"]["type"] == "string" assert parameters["$defs"]["FilterType"]["enum"] == ["fit", "raw"] ``` It would also be useful to verify preservation of another root keyword such as: ```python assert parameters["additionalProperties"] is False ``` ## Steps to reproduce 1. Install CPTR 0.7.1 with MCP support. 2. Configure an MCP server exposing a tool whose `inputSchema` uses `$defs` and a local `$ref`. 3. Select an OpenAI-compatible backend that validates or compiles tool schemas. 4. Start a CPTR chat with the MCP tool enabled. 5. Inspect the outgoing OpenAI tool definition. 6. Observe that the property still contains: ```json {"$ref": "#/$defs/SomeDefinition"} ``` but the parent `parameters` object no longer contains: ```json {"$defs": {"SomeDefinition": {}}} ``` 7. The backend rejects the request because the reference cannot be resolved. ## Impact Any MCP server using valid JSON Schema references can make all model requests fail before inference. This is especially likely with schemas generated by Pydantic, which commonly place reusable models and enums under `$defs`. The issue is backend-independent in principle: any OpenAI-compatible provider that validates, dereferences, or compiles the transmitted schemas may reject them. Backends that silently ignore unresolved schema references might appear to work, which can hide the problem. ## Workaround Locally changing CPTR to preserve the complete `tool.inputSchema` resolves the malformed-schema portion of the request. Removing the affected MCP tools or stripping `$ref` values is not a good permanent workaround because it discards valid schema information and weakens tool validation. ## Security note I have intentionally not attached the complete raw chat request because it includes private conversation content and credentials. The relevant sanitized tool-schema excerpts and backend error are included above.
Author
Owner

@tjbck commented on GitHub (Jun 30, 2026):

Should be addressed!

<!-- gh-comment-id:4847863658 --> @tjbck commented on GitHub (Jun 30, 2026): Should be addressed!
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
open-webui/computer#41
No description provided.