Skip to content

Commit c3aa01c

Browse files
committed
chore: only keep the $ref when corner case happens
1 parent 83da00f commit c3aa01c

File tree

3 files changed

+214
-116
lines changed

3 files changed

+214
-116
lines changed

docs/integrations/claude-desktop.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ Enable dereferencing in your server code:
108108
from fastmcp import FastMCP
109109
import fastmcp
110110

111-
fastmcp.settings.dereference_json_schema = True
111+
fastmcp.settings.dereference_json_schemas = True
112112

113113
mcp = FastMCP(name="Compatible Test")
114114

src/fastmcp/utilities/json_schema.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,33 +47,44 @@ def find_refs_in_value(value: Any, parent_def: str) -> bool:
4747

4848
def dereference_json_schema(schema: dict, max_depth: int = 5) -> dict:
4949
"""
50-
Dereference a JSON schema by resolving $ref references while preserving $defs.
50+
Dereference a JSON schema by resolving $ref references while preserving $defs only when corner cases occur.
5151
5252
This function flattens schema properties by:
53-
1. Check for self-reference - if found, return original schema
53+
1. Check for self-reference - if found, return original schema with $defs
5454
2. When encountering $refs in properties, resolve them on-demand
5555
3. Track visited definitions globally to prevent circular expansion
56-
4. Preserve original $defs in the final result
56+
4. Only preserve original $defs if corner cases are encountered:
57+
- Self-reference detected
58+
- Circular references between definitions
59+
- Reference depth exceeds max_depth
60+
- Reference not found in $defs
5761
5862
Args:
5963
schema: The JSON schema to flatten
6064
max_depth: Maximum depth for resolving references (default: 5)
6165
6266
Returns:
63-
Schema with references resolved in properties, keeping original $defs
67+
Schema with references resolved in properties, keeping $defs only when corner cases occur
6468
"""
6569
# Step 1: Check for self-reference
6670
if _detect_self_reference(schema):
67-
# Self-referencing detected, return original schema
71+
# Self-referencing detected, return original schema with $defs
6872
return schema
6973

7074
# Make a deep copy to work with
7175
result = deepcopy(schema)
7276

73-
# Keep original $defs for the final result
77+
# Keep original $defs for potential corner cases
7478
defs = deepcopy(schema.get("$defs", {}))
7579

76-
# Step 2: Define resolution function that tracks visits globally
80+
# Track corner cases that require preserving $defs
81+
corner_cases_detected = {
82+
"circular_ref": False,
83+
"max_depth_reached": False,
84+
"ref_not_found": False,
85+
}
86+
87+
# Step 2: Define resolution function that tracks visits globally and corner cases
7788
def resolve_refs_in_value(value: Any, depth: int, visiting: set[str]) -> Any:
7889
"""
7990
Recursively resolve $refs in a value.
@@ -84,9 +95,10 @@ def resolve_refs_in_value(value: Any, depth: int, visiting: set[str]) -> Any:
8495
visiting: Set of definitions currently being resolved (for cycle detection)
8596
8697
Returns:
87-
Value with $refs resolved (or kept if max depth reached)
98+
Value with $refs resolved (or kept if corner cases occur)
8899
"""
89100
if depth >= max_depth:
101+
corner_cases_detected["max_depth_reached"] = True
90102
return value
91103

92104
if isinstance(value, dict):
@@ -100,6 +112,7 @@ def resolve_refs_in_value(value: Any, depth: int, visiting: set[str]) -> Any:
100112
# Check for circular reference
101113
if def_name in visiting:
102114
# Circular reference detected, keep the $ref
115+
corner_cases_detected["circular_ref"] = True
103116
return value
104117

105118
if def_name in defs:
@@ -123,6 +136,7 @@ def resolve_refs_in_value(value: Any, depth: int, visiting: set[str]) -> Any:
123136
return resolved
124137
else:
125138
# Definition not found, keep the $ref
139+
corner_cases_detected["ref_not_found"] = True
126140
return value
127141
else:
128142
# External ref or other type - keep as is
@@ -147,9 +161,14 @@ def resolve_refs_in_value(value: Any, depth: int, visiting: set[str]) -> Any:
147161
# This allows the same definition to be used in different contexts
148162
result[key] = resolve_refs_in_value(value, 0, set())
149163

150-
# Step 4: Preserve original $defs
151-
if "$defs" in result:
152-
result["$defs"] = defs
164+
# Step 4: Conditionally preserve $defs based on corner cases
165+
if any(corner_cases_detected.values()):
166+
# Corner case detected, preserve original $defs
167+
if "$defs" in schema: # Only add if original schema had $defs
168+
result["$defs"] = defs
169+
else:
170+
# No corner cases, remove $defs if it exists
171+
result.pop("$defs", None)
153172

154173
return result
155174

0 commit comments

Comments
 (0)