Skip to content

Commit 405ec43

Browse files
cpsievertclaude
andcommitted
fix: use strict=False for MCP tools to preserve optional params
MCP tools use standard JSON Schema conventions where optional params are simply not in the required array. Setting strict=False for OpenAI allows this to work properly, so the LLM won't be forced to provide values for all parameters. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 5184419 commit 405ec43

File tree

1 file changed

+18
-28
lines changed

1 file changed

+18
-28
lines changed

chatlas/_tools.py

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,26 @@ def __init__(
6666
description: str,
6767
parameters: dict[str, Any],
6868
annotations: "Optional[ToolAnnotations]" = None,
69+
strict: Optional[bool] = None,
6970
):
7071
self.name = name
7172
self.func = func
7273
self.annotations = annotations
7374
self._is_async = _utils.is_async_callable(func)
74-
self.schema: "ChatCompletionToolParam" = {
75-
"type": "function",
76-
"function": {
77-
"name": name,
78-
"description": description,
79-
"parameters": parameters,
80-
},
75+
func_schema: dict[str, Any] = {
76+
"name": name,
77+
"description": description,
78+
"parameters": parameters,
8179
}
80+
if strict is not None:
81+
func_schema["strict"] = strict
82+
self.schema: "ChatCompletionToolParam" = cast(
83+
"ChatCompletionToolParam",
84+
{
85+
"type": "function",
86+
"function": func_schema,
87+
},
88+
)
8289

8390
@classmethod
8491
def from_func(
@@ -230,6 +237,9 @@ async def _call(**args: Any) -> AsyncGenerator[ContentToolResult, None]:
230237
description=mcp_tool.description or "",
231238
parameters=params,
232239
annotations=annotations,
240+
# MCP tools use standard JSON Schema conventions for optional params
241+
# (not in required array), which requires strict=False for OpenAI
242+
strict=False,
233243
)
234244

235245

@@ -406,8 +416,6 @@ def sanitize_schema(
406416
- `title`: Pydantic includes titles at model/field level, but they're not needed
407417
- `format`: JSON Schema format hints (e.g., "uri", "date-time") that some
408418
providers like OpenAI reject
409-
- `required`: OpenAI requires all properties to be in required array, with
410-
optional params using anyOf with null type
411419
"""
412420
if "title" in params:
413421
del params["title"]
@@ -416,28 +424,10 @@ def sanitize_schema(
416424
del params["format"]
417425

418426
if "properties" in params and isinstance(params["properties"], dict):
419-
required_keys = set(params.get("required", []))
420-
421-
for key, prop in params["properties"].items():
427+
for prop in params["properties"].values():
422428
if isinstance(prop, dict):
423429
sanitize_schema(prop)
424430

425-
# For optional properties (not in required), make them nullable
426-
# OpenAI requires all props in required array, using anyOf with
427-
# null type to indicate optionality
428-
if key not in required_keys:
429-
prop_type = prop.get("type")
430-
if prop_type and prop_type != "null":
431-
# Convert to anyOf with null
432-
prop["anyOf"] = [{"type": prop_type}, {"type": "null"}]
433-
del prop["type"]
434-
elif "anyOf" not in prop and "oneOf" not in prop:
435-
# No type specified, just allow null
436-
prop["anyOf"] = [prop.copy(), {"type": "null"}]
437-
438-
# OpenAI requires all properties to be in required array
439-
params["required"] = list(params["properties"].keys())
440-
441431
return params
442432

443433

0 commit comments

Comments
 (0)