Add real Monty sandbox e2e coverage for CodeMode call_tool (#4274)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
This commit is contained in:
Alex LaGuardia 2026-06-24 11:52:27 -04:00 committed by GitHub
commit 27a0720928
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,5 +1,6 @@
import asyncio
import importlib
import importlib.util
import json
from typing import Any
@ -20,6 +21,30 @@ from fastmcp.experimental.transforms.code_mode import (
from fastmcp.server.context import Context
from fastmcp.tools.base import Tool, ToolResult
requires_monty = pytest.mark.skipif(
importlib.util.find_spec("pydantic_monty") is None,
reason="pydantic-monty is required for the real Monty sandbox provider",
)
# test_code_mode_monty_bare_call_returns_empty deliberately runs
# `print(call_tool(...))` without awaiting, which orphans a `Provider.get_tool`
# coroutine. CPython reports it as "never awaited" only when the coroutine is
# garbage-collected, which happens asynchronously — often partway through a
# *later* test — so a per-test filter can't reliably catch it. The suite
# promotes that warning (and its unraisable-teardown variant) to an error via
# `filterwarnings` in pyproject.toml, so scope the suppression to the module
# but pin it to that exact coroutine; genuine "never awaited" leaks elsewhere
# still surface as errors.
pytestmark = [
pytest.mark.filterwarnings(
"ignore:coroutine 'Provider.get_tool' was never awaited:RuntimeWarning"
),
pytest.mark.filterwarnings(
"ignore:Exception ignored in.*Provider.get_tool:"
"pytest.PytestUnraisableExceptionWarning"
),
]
def _unwrap_result(result: ToolResult) -> Any:
"""Extract the logical return value from a ToolResult."""
@ -684,6 +709,116 @@ async def test_code_mode_sandbox_error_surfaces_as_tool_error() -> None:
await _run_tool(mcp, "execute", {"code": "raise ValueError('boom')"})
# ---------------------------------------------------------------------------
# Real Monty sandbox end-to-end (issue #4263)
#
# Every execute test above runs through the exec-based
# _UnsafeTestSandboxProvider, so the real MontySandboxProvider + call_tool
# path is otherwise uncovered. These tests drive call_tool through the actual
# pydantic-monty sandbox, the path #4263 reported as broken.
# ---------------------------------------------------------------------------
@requires_monty
@pytest.mark.parametrize("async_tool", [False, True])
async def test_code_mode_monty_execute_call_tool(async_tool: bool) -> None:
"""call_tool resolves and returns through the real Monty sandbox.
The reported failure ("empty result") could not be reproduced on a
directly-registered tool: ``return await call_tool(...)`` returns the
value for both sync and async backend tools.
"""
mcp = FastMCP("CodeMode Monty Execute")
if async_tool:
@mcp.tool
async def add(x: int, y: int) -> int:
return x + y
else:
@mcp.tool
def add(x: int, y: int) -> int:
return x + y
mcp.add_transform(CodeMode(sandbox_provider=MontySandboxProvider()))
result = await _run_tool(
mcp, "execute", {"code": "return await call_tool('add', {'x': 2, 'y': 3})"}
)
assert _unwrap_result(result) == {"result": 5}
@requires_monty
async def test_code_mode_monty_execute_string_result() -> None:
"""A string-returning tool round-trips through the real Monty sandbox."""
mcp = FastMCP("CodeMode Monty String")
@mcp.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
mcp.add_transform(CodeMode(sandbox_provider=MontySandboxProvider()))
result = await _run_tool(
mcp, "execute", {"code": "return await call_tool('greet', {'name': 'World'})"}
)
assert _unwrap_string_result(result) == "Hello, World!"
@requires_monty
async def test_code_mode_monty_execute_chaining() -> None:
"""Multiple sequential call_tool() calls chain through the real sandbox."""
mcp = FastMCP("CodeMode Monty Chaining")
@mcp.tool
def add(x: int, y: int) -> int:
return x + y
mcp.add_transform(CodeMode(sandbox_provider=MontySandboxProvider()))
result = await _run_tool(
mcp,
"execute",
{
"code": (
"a = await call_tool('add', {'x': 1, 'y': 2})\n"
"b = await call_tool('add', {'x': a['result'], 'y': 10})\n"
"return b"
)
},
)
assert _unwrap_result(result) == {"result": 13}
@requires_monty
async def test_code_mode_monty_bare_call_returns_empty() -> None:
"""Pins the reported #4263 symptom as a usage error, not a sandbox bug.
The report's code called ``call_tool`` bare — ``print(call_tool(...))``
without ``await`` or ``return``. ``call_tool`` is async, so a bare call
hands back an unawaited coroutine, and ``print`` returns ``None``; the
block therefore returns nothing and ``execute`` yields an empty result.
The accompanying "coroutine ... was never awaited" RuntimeWarning is a
cascading effect of that unawaited coroutine being garbage-collected, not
a separate defect (see the module-level ``filterwarnings`` note for why it
is suppressed rather than asserted).
"""
mcp = FastMCP("CodeMode Monty Bare Call")
@mcp.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
mcp.add_transform(CodeMode(sandbox_provider=MontySandboxProvider()))
result = await _run_tool(
mcp, "execute", {"code": "print(call_tool('greet', {'name': 'World'}))"}
)
assert result.content == []
assert result.structured_content is None
# ---------------------------------------------------------------------------
# Sandbox provider tests
# ---------------------------------------------------------------------------