Ensure MCP works when inputSchema.properties is missing (#454)
Resolves #449 - TLDR, [OpenAI's API](https://platform.openai.com/docs/api-reference/responses/create) expects the properties field to be present, whereas the MCP schema explicitly allows omitting the properties field. [MCP Spec](https://github.com/modelcontextprotocol/specification/blob/main/schema/2025-03-26/schema.json)
This commit is contained in:
parent
064e25b01b
commit
50bbfdd8be
2 changed files with 41 additions and 7 deletions
|
|
@ -59,6 +59,11 @@ class MCPUtil:
|
||||||
"""Convert an MCP tool to an Agents SDK function tool."""
|
"""Convert an MCP tool to an Agents SDK function tool."""
|
||||||
invoke_func = functools.partial(cls.invoke_mcp_tool, server, tool)
|
invoke_func = functools.partial(cls.invoke_mcp_tool, server, tool)
|
||||||
schema, is_strict = tool.inputSchema, False
|
schema, is_strict = tool.inputSchema, False
|
||||||
|
|
||||||
|
# MCP spec doesn't require the inputSchema to have `properties`, but OpenAI spec does.
|
||||||
|
if "properties" not in schema:
|
||||||
|
schema["properties"] = {}
|
||||||
|
|
||||||
if convert_schemas_to_strict:
|
if convert_schemas_to_strict:
|
||||||
try:
|
try:
|
||||||
schema = ensure_strict_json_schema(schema)
|
schema = ensure_strict_json_schema(schema)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,10 @@ async def test_get_all_function_tools():
|
||||||
|
|
||||||
for idx, tool in enumerate(tools):
|
for idx, tool in enumerate(tools):
|
||||||
assert isinstance(tool, FunctionTool)
|
assert isinstance(tool, FunctionTool)
|
||||||
assert tool.params_json_schema == schemas[idx]
|
if schemas[idx] == {}:
|
||||||
|
assert tool.params_json_schema == snapshot({"properties": {}})
|
||||||
|
else:
|
||||||
|
assert tool.params_json_schema == schemas[idx]
|
||||||
assert tool.name == names[idx]
|
assert tool.name == names[idx]
|
||||||
|
|
||||||
# Also make sure it works with strict schemas
|
# Also make sure it works with strict schemas
|
||||||
|
|
@ -167,10 +170,7 @@ async def test_agent_convert_schemas_true():
|
||||||
|
|
||||||
# Checks that additionalProperties is set to False
|
# Checks that additionalProperties is set to False
|
||||||
assert bar_tool.params_json_schema == snapshot(
|
assert bar_tool.params_json_schema == snapshot(
|
||||||
{
|
{"type": "object", "additionalProperties": {"type": "string"}, "properties": {}}
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {"type": "string"},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
assert bar_tool.strict_json_schema is False, "bar_tool should not be strict"
|
assert bar_tool.strict_json_schema is False, "bar_tool should not be strict"
|
||||||
|
|
||||||
|
|
@ -220,7 +220,9 @@ async def test_agent_convert_schemas_false():
|
||||||
assert foo_tool.params_json_schema == strict_schema
|
assert foo_tool.params_json_schema == strict_schema
|
||||||
assert foo_tool.strict_json_schema is False, "Shouldn't be converted unless specified"
|
assert foo_tool.strict_json_schema is False, "Shouldn't be converted unless specified"
|
||||||
|
|
||||||
assert bar_tool.params_json_schema == non_strict_schema
|
assert bar_tool.params_json_schema == snapshot(
|
||||||
|
{"type": "object", "additionalProperties": {"type": "string"}, "properties": {}}
|
||||||
|
)
|
||||||
assert bar_tool.strict_json_schema is False
|
assert bar_tool.strict_json_schema is False
|
||||||
|
|
||||||
assert baz_tool.params_json_schema == possible_to_convert_schema
|
assert baz_tool.params_json_schema == possible_to_convert_schema
|
||||||
|
|
@ -255,8 +257,35 @@ async def test_agent_convert_schemas_unset():
|
||||||
assert foo_tool.params_json_schema == strict_schema
|
assert foo_tool.params_json_schema == strict_schema
|
||||||
assert foo_tool.strict_json_schema is False, "Shouldn't be converted unless specified"
|
assert foo_tool.strict_json_schema is False, "Shouldn't be converted unless specified"
|
||||||
|
|
||||||
assert bar_tool.params_json_schema == non_strict_schema
|
assert bar_tool.params_json_schema == snapshot(
|
||||||
|
{"type": "object", "additionalProperties": {"type": "string"}, "properties": {}}
|
||||||
|
)
|
||||||
assert bar_tool.strict_json_schema is False
|
assert bar_tool.strict_json_schema is False
|
||||||
|
|
||||||
assert baz_tool.params_json_schema == possible_to_convert_schema
|
assert baz_tool.params_json_schema == possible_to_convert_schema
|
||||||
assert baz_tool.strict_json_schema is False, "Shouldn't be converted unless specified"
|
assert baz_tool.strict_json_schema is False, "Shouldn't be converted unless specified"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_util_adds_properties():
|
||||||
|
"""The MCP spec doesn't require the inputSchema to have `properties`, so we need to add it
|
||||||
|
if it's missing.
|
||||||
|
"""
|
||||||
|
schema = {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Test tool",
|
||||||
|
}
|
||||||
|
|
||||||
|
server = FakeMCPServer()
|
||||||
|
server.add_tool("test_tool", schema)
|
||||||
|
|
||||||
|
tools = await MCPUtil.get_all_function_tools([server], convert_schemas_to_strict=False)
|
||||||
|
tool = next(tool for tool in tools if tool.name == "test_tool")
|
||||||
|
|
||||||
|
assert isinstance(tool, FunctionTool)
|
||||||
|
assert "properties" in tool.params_json_schema
|
||||||
|
assert tool.params_json_schema["properties"] == {}
|
||||||
|
|
||||||
|
assert tool.params_json_schema == snapshot(
|
||||||
|
{"type": "object", "description": "Test tool", "properties": {}}
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue