|
4 | 4 | from typing import Any, TypedDict |
5 | 5 |
|
6 | 6 | import pytest |
7 | | -from pydantic import BaseModel |
| 7 | +from pydantic import BaseModel, Field |
8 | 8 |
|
9 | 9 | from mcp.server.fastmcp import Context, FastMCP |
10 | 10 | from mcp.server.fastmcp.exceptions import ToolError |
@@ -577,13 +577,97 @@ def get_user() -> UserOutput: |
577 | 577 |
|
578 | 578 | # Test that output_schema is populated |
579 | 579 | expected_schema = { |
580 | | - "properties": {"name": {"type": "string", "title": "Name"}, "age": {"type": "integer", "title": "Age"}}, |
| 580 | + "properties": { |
| 581 | + "name": {"type": "string", "title": "Name"}, |
| 582 | + "age": {"type": "integer", "title": "Age"}, |
| 583 | + }, |
581 | 584 | "required": ["name", "age"], |
582 | 585 | "title": "UserOutput", |
583 | 586 | "type": "object", |
584 | 587 | } |
585 | 588 | assert tool.output_schema == expected_schema |
586 | 589 |
|
| 590 | + def test_add_output_schema_override(self): |
| 591 | + """Test registering a tool with an explicit output schema.""" |
| 592 | + |
| 593 | + # For the ChatGPT App SDK, the tool output should be structured like: |
| 594 | + # { |
| 595 | + # "structuredOutput": { ... }, |
| 596 | + # "content": [ { "type": "text", "text": "..." }, ... ], |
| 597 | + # "_meta": { ... } |
| 598 | + # } |
| 599 | + # and the tool output schema should reflect the structure of "structuredOutput" |
| 600 | + class UserOutput(BaseModel): |
| 601 | + name: str |
| 602 | + age: int |
| 603 | + |
| 604 | + # Output structure expected by ChatGPT App SDK |
| 605 | + class ToolOutput(BaseModel): |
| 606 | + structuredOutput: UserOutput |
| 607 | + content: list[dict[str, str]] |
| 608 | + meta: dict[str, Any] = Field(alias="_meta") |
| 609 | + |
| 610 | + def get_user(user_id: int) -> ToolOutput: |
| 611 | + """Get user by ID.""" |
| 612 | + return ToolOutput( |
| 613 | + structuredOutput=UserOutput(name="John", age=30), |
| 614 | + content=[{"type": "text", "text": "User found"}], |
| 615 | + _meta={"request_id": "12345"}, |
| 616 | + ) |
| 617 | + |
| 618 | + manager = ToolManager() |
| 619 | + tool = manager.add_tool(get_user, output_schema=UserOutput.model_json_schema()) |
| 620 | + |
| 621 | + expected_schema = { |
| 622 | + "properties": { |
| 623 | + "name": {"type": "string", "title": "Name"}, |
| 624 | + "age": {"type": "integer", "title": "Age"}, |
| 625 | + }, |
| 626 | + "required": ["name", "age"], |
| 627 | + "title": "UserOutput", |
| 628 | + "type": "object", |
| 629 | + } |
| 630 | + assert tool.output_schema == expected_schema |
| 631 | + assert tool.fn_metadata.output_model == ToolOutput |
| 632 | + |
| 633 | + @pytest.mark.anyio |
| 634 | + async def test_call_tool_with_output_schema_override(self): |
| 635 | + # For the ChatGPT App SDK, the tool output should be structured like: |
| 636 | + # { |
| 637 | + # "structuredOutput": { ... }, |
| 638 | + # "content": [ { "type": "text", "text": "..." }, ... ], |
| 639 | + # "_meta": { ... } |
| 640 | + # } |
| 641 | + # and the tool output schema should reflect the structure of "structuredOutput" |
| 642 | + class UserOutput(BaseModel): |
| 643 | + name: str |
| 644 | + age: int |
| 645 | + |
| 646 | + # Output structure expected by ChatGPT App SDK |
| 647 | + class ToolOutput(BaseModel): |
| 648 | + structuredOutput: UserOutput |
| 649 | + content: list[dict[str, str]] |
| 650 | + meta: dict[str, Any] = Field(alias="_meta") |
| 651 | + |
| 652 | + def get_user(user_id: int) -> ToolOutput: |
| 653 | + """Get user by ID.""" |
| 654 | + return ToolOutput( |
| 655 | + structuredOutput=UserOutput(name="John", age=30), |
| 656 | + content=[{"type": "some more information about the output data"}], |
| 657 | + _meta={"request_id": "12345"}, |
| 658 | + ) |
| 659 | + |
| 660 | + manager = ToolManager() |
| 661 | + manager.add_tool(get_user, output_schema=UserOutput.model_json_schema()) |
| 662 | + result = await manager.call_tool("get_user", {"user_id": 1}, convert_result=True) |
| 663 | + |
| 664 | + expected_result = { |
| 665 | + "structuredOutput": {"name": "John", "age": 30}, |
| 666 | + "content": [{"type": "some more information about the output data"}], |
| 667 | + "_meta": {"request_id": "12345"}, |
| 668 | + } |
| 669 | + assert len(result) == 2 and result[1] == expected_result |
| 670 | + |
587 | 671 | @pytest.mark.anyio |
588 | 672 | async def test_tool_with_dict_str_any_output(self): |
589 | 673 | """Test tool with dict[str, Any] return type.""" |
|
0 commit comments