From dcf70fec4ca6817b199ece9fbf5e088e904b50d0 Mon Sep 17 00:00:00 2001 From: Stepan Filonov Date: Mon, 9 Feb 2026 04:56:37 +0800 Subject: [PATCH] Feat: strict definition openai --- mcphero/adapters/openai.py | 21 ++++++++++++++++++--- tests/test_openai_adapter.py | 20 +++++++++++++++++++- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/mcphero/adapters/openai.py b/mcphero/adapters/openai.py index f96fdab..7406f90 100644 --- a/mcphero/adapters/openai.py +++ b/mcphero/adapters/openai.py @@ -37,8 +37,23 @@ class MCPToolAdapterOpenAI( """ @staticmethod - def _to_openai_tool(tool: MCPToolDefinition) -> ChatCompletionToolParam: + def _to_openai_tool( + tool: MCPToolDefinition, *, strict: bool = True + ) -> ChatCompletionToolParam: """Convert MCPToolDefinition to OpenAI format.""" + if strict: + return { + "type": "function", + "function": { + "name": tool.name, + "description": tool.description, + "strict": True, + "parameters": { + **tool.input_schema, + "additionalProperties": False, + }, + }, + } return { "type": "function", "function": { @@ -49,11 +64,11 @@ def _to_openai_tool(tool: MCPToolDefinition) -> ChatCompletionToolParam: } async def get_tool_definitions( - self, *, parallel: bool = True + self, *, parallel: bool = True, strict: bool = True ) -> list[ChatCompletionToolParam]: """Fetch tools and return OpenAI-compatible definitions.""" tools = await self.discover_tools(parallel=parallel) - return [self._to_openai_tool(t) for t in tools] + return [self._to_openai_tool(t, strict=strict) for t in tools] async def _execute_single_tool_call( self, tool_call: ChatCompletionMessageToolCall, *, return_errors: bool diff --git a/tests/test_openai_adapter.py b/tests/test_openai_adapter.py index 636d647..076a7ba 100644 --- a/tests/test_openai_adapter.py +++ b/tests/test_openai_adapter.py @@ -32,7 +32,11 @@ async def test_converts_mcp_tools_to_openai_format( tools[0]["function"]["description"] == "Get the current weather for a location" ) - assert tools[0]["function"]["parameters"] == sample_mcp_tools[0]["inputSchema"] + assert tools[0]["function"]["strict"] is True + assert tools[0]["function"]["parameters"] == { + **sample_mcp_tools[0]["inputSchema"], + "additionalProperties": False, + } @respx.mock async def test_handles_missing_input_schema( @@ -51,8 +55,22 @@ async def test_handles_missing_input_schema( assert tools[0]["function"]["parameters"] == { "type": "object", "properties": {}, + "additionalProperties": False, } + @respx.mock + async def test_strict_false_omits_strict_fields(self, base_url, sample_mcp_tools): + respx.post(base_url).mock( + return_value=httpx.Response(200, json=_tools_response(sample_mcp_tools)) + ) + + adapter = MCPToolAdapterOpenAI(MCPServerConfig(url=base_url, init_mode="none")) + tools = await adapter.get_tool_definitions(strict=False) + + assert tools[0]["function"]["parameters"] == sample_mcp_tools[0]["inputSchema"] + assert "strict" not in tools[0]["function"] + assert "additionalProperties" not in tools[0]["function"]["parameters"] + @respx.mock async def test_handles_empty_tools(self, base_url): respx.post(base_url).mock(