Add execution field to base Tool class

The base Tool class now has an optional `execution` field for storing
task execution metadata (SEP-1686). This lets gateways/proxies preserve
execution info when forwarding tools from backends - previously this
metadata was lost because Tool had no way to store it.

FunctionTool continues to derive execution from task_config as before.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Chris Guidry 2025-12-08 17:07:49 -05:00
commit 17520ec5d5
2 changed files with 87 additions and 1 deletions

View file

@ -133,6 +133,10 @@ class Tool(FastMCPComponent):
ToolAnnotations | None,
Field(description="Additional annotations about the tool"),
] = None
execution: Annotated[
ToolExecution | None,
Field(description="Task execution configuration (SEP-1686)"),
] = None
serializer: Annotated[
ToolResultSerializerType | None,
Field(description="Optional custom serializer for tool results"),
@ -182,7 +186,7 @@ class Tool(FastMCPComponent):
outputSchema=overrides.get("outputSchema", self.output_schema),
icons=overrides.get("icons", self.icons),
annotations=overrides.get("annotations", self.annotations),
execution=overrides.get("execution"),
execution=overrides.get("execution", self.execution),
_meta=overrides.get(
"_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
),

View file

@ -12,6 +12,7 @@ from mcp.types import (
ResourceLink,
TextContent,
TextResourceContents,
ToolExecution,
)
from pydantic import AnyUrl, BaseModel, Field, TypeAdapter
from typing_extensions import TypedDict
@ -1813,3 +1814,84 @@ class TestToolNameValidation:
assert tool.parameters is not None
assert "a" in tool.parameters["properties"]
assert "b" in tool.parameters["properties"]
class TestToolExecutionField:
"""Tests for the execution field on the base Tool class."""
def test_tool_with_execution_field(self):
"""Test that Tool can store and return execution metadata."""
tool = Tool(
name="my_tool",
description="A tool with execution",
parameters={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport="optional"),
)
mcp_tool = tool.to_mcp_tool()
assert mcp_tool.execution is not None
assert mcp_tool.execution.taskSupport == "optional"
def test_tool_without_execution_field(self):
"""Test that Tool without execution returns None."""
tool = Tool(
name="my_tool",
description="A tool without execution",
parameters={"type": "object", "properties": {}},
)
mcp_tool = tool.to_mcp_tool()
assert mcp_tool.execution is None
def test_execution_override_takes_precedence(self):
"""Test that explicit override takes precedence over field value."""
tool = Tool(
name="my_tool",
description="A tool",
parameters={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport="optional"),
)
override_execution = ToolExecution(taskSupport="required")
mcp_tool = tool.to_mcp_tool(execution=override_execution)
assert mcp_tool.execution is not None
assert mcp_tool.execution.taskSupport == "required"
async def test_function_tool_task_config_still_works(self):
"""FunctionTool should still derive execution from task_config."""
async def my_fn() -> str:
return "hello"
tool = Tool.from_function(my_fn, task=True)
mcp_tool = tool.to_mcp_tool()
# FunctionTool sets execution from task_config
assert mcp_tool.execution is not None
assert mcp_tool.execution.taskSupport == "optional"
def test_tool_execution_required_mode(self):
"""Test that Tool can store required execution mode."""
tool = Tool(
name="my_tool",
description="A tool with required execution",
parameters={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport="required"),
)
mcp_tool = tool.to_mcp_tool()
assert mcp_tool.execution is not None
assert mcp_tool.execution.taskSupport == "required"
def test_tool_execution_forbidden_mode(self):
"""Test that Tool can store forbidden execution mode."""
tool = Tool(
name="my_tool",
description="A tool with forbidden execution",
parameters={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport="forbidden"),
)
mcp_tool = tool.to_mcp_tool()
assert mcp_tool.execution is not None
assert mcp_tool.execution.taskSupport == "forbidden"