mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-23 05:54:19 +02:00
Add output schema support for OpenAPI tools (#1073)
* Add output schema support for OpenAPI tools Implement automatic output schema extraction from OpenAPI responses for FastMCP tools, addressing issue #1070. Tools generated from OpenAPI specs now have meaningful output schemas instead of null, improving agent efficiency when working with structured API responses. - Add extract_output_schema_from_responses() function to parse response schemas - Enhance OpenAPITool to accept and use output schemas with proper wrapping - Automatically wrap non-object responses to comply with MCP requirements - Include schema definitions and compress unused ones - Add comprehensive test suite covering object, array, and primitive responses - Update existing tests to handle structured output with proper typing - Maintain backward compatibility for specs without response schemas 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Move output schema tests to dedicated test file Reorganize output schema tests into test_openapi_output_schemas.py to keep the main test_openapi.py file focused and smaller, as requested. Added two additional tests for schema definitions handling. - Move all extract_output_schema_from_responses() tests to new file - Add tests for schema definitions inclusion - Remove output schema imports from main test file --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
73d6e0d838
commit
6c9263001b
4 changed files with 391 additions and 18 deletions
|
|
@ -109,7 +109,16 @@ class TestTools:
|
|||
},
|
||||
"required": ["name", "active"],
|
||||
},
|
||||
outputSchema=None,
|
||||
outputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "integer", "title": "Id"},
|
||||
"name": {"type": "string", "title": "Name"},
|
||||
"active": {"type": "boolean", "title": "Active"},
|
||||
},
|
||||
"required": ["id", "name", "active"],
|
||||
"title": "User",
|
||||
},
|
||||
)
|
||||
assert tools[1].model_dump() == dict(
|
||||
name="update_user_name_users",
|
||||
|
|
@ -127,7 +136,16 @@ class TestTools:
|
|||
},
|
||||
"required": ["user_id", "name"],
|
||||
},
|
||||
outputSchema=None,
|
||||
outputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "integer", "title": "Id"},
|
||||
"name": {"type": "string", "title": "Name"},
|
||||
"active": {"type": "boolean", "title": "Active"},
|
||||
},
|
||||
"required": ["id", "name", "active"],
|
||||
"title": "User",
|
||||
},
|
||||
)
|
||||
|
||||
async def test_call_create_user_tool(
|
||||
|
|
@ -143,8 +161,11 @@ class TestTools:
|
|||
"create_user_users_post", {"name": "David", "active": False}
|
||||
)
|
||||
|
||||
expected_user = User(id=4, name="David", active=False).model_dump()
|
||||
assert tool_response.data == expected_user
|
||||
expected_user = User(id=4, name="David", active=False)
|
||||
# Compare the data content since MCP client creates different class instances
|
||||
assert tool_response.data.id == expected_user.id
|
||||
assert tool_response.data.name == expected_user.name
|
||||
assert tool_response.data.active == expected_user.active
|
||||
|
||||
# Check that the user was created via API
|
||||
response = await api_client.get("/users")
|
||||
|
|
@ -155,7 +176,7 @@ class TestTools:
|
|||
user_response = await client.read_resource("resource://get_user_users/4")
|
||||
response_text = user_response[0].text # type: ignore[attr-defined]
|
||||
user = json.loads(response_text)
|
||||
assert user == expected_user
|
||||
assert user == expected_user.model_dump()
|
||||
|
||||
async def test_call_update_user_name_tool(
|
||||
self,
|
||||
|
|
@ -171,19 +192,22 @@ class TestTools:
|
|||
{"user_id": 1, "name": "XYZ"},
|
||||
)
|
||||
|
||||
expected_data = dict(id=1, name="XYZ", active=True)
|
||||
assert tool_response.data == expected_data
|
||||
expected_user = User(id=1, name="XYZ", active=True)
|
||||
# Compare the data content since MCP client creates different class instances
|
||||
assert tool_response.data.id == expected_user.id
|
||||
assert tool_response.data.name == expected_user.name
|
||||
assert tool_response.data.active == expected_user.active
|
||||
|
||||
# Check that the user was updated via API
|
||||
response = await api_client.get("/users")
|
||||
assert expected_data in response.json()
|
||||
assert expected_user.model_dump() in response.json()
|
||||
|
||||
# Check that the user was updated via MCP
|
||||
async with Client(fastmcp_openapi_server) as client:
|
||||
user_response = await client.read_resource("resource://get_user_users/1")
|
||||
response_text = user_response[0].text # type: ignore[attr-defined]
|
||||
user = json.loads(response_text)
|
||||
assert user == expected_data
|
||||
assert user == expected_user.model_dump()
|
||||
|
||||
async def test_call_tool_return_list(
|
||||
self,
|
||||
|
|
@ -204,12 +228,11 @@ class TestTools:
|
|||
)
|
||||
async with Client(mcp_server) as client:
|
||||
tool_response = await client.call_tool("get_users_users_get", {})
|
||||
assert tool_response.data == {
|
||||
"result": [
|
||||
user.model_dump()
|
||||
for user in sorted(users_db.values(), key=lambda x: x.id)
|
||||
]
|
||||
}
|
||||
# The tool response should now be unwrapped since we have output schema
|
||||
assert tool_response.data == [
|
||||
user.model_dump()
|
||||
for user in sorted(users_db.values(), key=lambda x: x.id)
|
||||
]
|
||||
|
||||
|
||||
class TestResources:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue