Allow CallToolResult to be returned directly to support _meta field for OpenAI Apps (#1459)
Co-authored-by: Max Isbey <224885523+maxisbey@users.noreply.github.com>
This commit is contained in:
parent
40acbc596c
commit
1200ba0082
3 changed files with 445 additions and 5 deletions
24
examples/fastmcp/direct_call_tool_result_return.py
Normal file
24
examples/fastmcp/direct_call_tool_result_return.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
"""
|
||||
FastMCP Echo Server with direct CallToolResult return
|
||||
"""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.types import CallToolResult, TextContent
|
||||
|
||||
mcp = FastMCP("Echo Server")
|
||||
|
||||
|
||||
class EchoResponse(BaseModel):
|
||||
text: str
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def echo(text: str) -> Annotated[CallToolResult, EchoResponse]:
|
||||
"""Echo the input text with structure and metadata"""
|
||||
return CallToolResult(
|
||||
content=[TextContent(type="text", text=text)], structuredContent={"text": text}, _meta={"some": "metadata"}
|
||||
)
|
||||
42
examples/snippets/servers/direct_call_tool_result.py
Normal file
42
examples/snippets/servers/direct_call_tool_result.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""Example showing direct CallToolResult return for advanced control."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.types import CallToolResult, TextContent
|
||||
|
||||
mcp = FastMCP("CallToolResult Example")
|
||||
|
||||
|
||||
class ValidationModel(BaseModel):
|
||||
"""Model for validating structured output."""
|
||||
|
||||
status: str
|
||||
data: dict[str, int]
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def advanced_tool() -> CallToolResult:
|
||||
"""Return CallToolResult directly for full control including _meta field."""
|
||||
return CallToolResult(
|
||||
content=[TextContent(type="text", text="Response visible to the model")],
|
||||
_meta={"hidden": "data for client applications only"},
|
||||
)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def validated_tool() -> Annotated[CallToolResult, ValidationModel]:
|
||||
"""Return CallToolResult with structured output validation."""
|
||||
return CallToolResult(
|
||||
content=[TextContent(type="text", text="Validated response")],
|
||||
structuredContent={"status": "success", "data": {"result": 42}},
|
||||
_meta={"internal": "metadata"},
|
||||
)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def empty_result_tool() -> CallToolResult:
|
||||
"""For empty results, return CallToolResult with empty content."""
|
||||
return CallToolResult(content=[])
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
"""
|
||||
Run from the repository root:
|
||||
uv run examples/snippets/servers/lowlevel/direct_call_tool_result.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
import mcp.server.stdio
|
||||
import mcp.types as types
|
||||
from mcp.server.lowlevel import NotificationOptions, Server
|
||||
from mcp.server.models import InitializationOptions
|
||||
|
||||
server = Server("example-server")
|
||||
|
||||
|
||||
@server.list_tools()
|
||||
async def list_tools() -> list[types.Tool]:
|
||||
"""List available tools."""
|
||||
return [
|
||||
types.Tool(
|
||||
name="advanced_tool",
|
||||
description="Tool with full control including _meta field",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {"message": {"type": "string"}},
|
||||
"required": ["message"],
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@server.call_tool()
|
||||
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> types.CallToolResult:
|
||||
"""Handle tool calls by returning CallToolResult directly."""
|
||||
if name == "advanced_tool":
|
||||
message = str(arguments.get("message", ""))
|
||||
return types.CallToolResult(
|
||||
content=[types.TextContent(type="text", text=f"Processed: {message}")],
|
||||
structuredContent={"result": "success", "message": message},
|
||||
_meta={"hidden": "data for client applications only"},
|
||||
)
|
||||
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
|
||||
|
||||
async def run():
|
||||
"""Run the server."""
|
||||
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
||||
await server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
InitializationOptions(
|
||||
server_name="example",
|
||||
server_version="0.1.0",
|
||||
capabilities=server.get_capabilities(
|
||||
notification_options=NotificationOptions(),
|
||||
experimental_capabilities={},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run())
|
||||
Loading…
Reference in a new issue