refactor: remove request_ctx ContextVar, thread Context explicitly (#2203)

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
This commit is contained in:
Max Isbey 2026-03-04 13:23:02 +00:00 • committed by GitHub
commit cc22bf5464
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -188,7 +188,7 @@ class TestCallTools:
manager = ToolManager()
manager.add_tool(sum)
result = await manager.call_tool("sum", {"a": 1, "b": 2})
result = await manager.call_tool("sum", {"a": 1, "b": 2}, Context())
assert result == 3
@pytest.mark.anyio
@ -199,7 +199,7 @@ class TestCallTools:
manager = ToolManager()
manager.add_tool(double)
result = await manager.call_tool("double", {"n": 5})
result = await manager.call_tool("double", {"n": 5}, Context())
assert result == 10
@pytest.mark.anyio
@ -213,7 +213,7 @@ class TestCallTools:
manager = ToolManager()
tool = manager.add_tool(MyTool())
result = await tool.run({"x": 5})
result = await tool.run({"x": 5}, Context())
assert result == 10
@pytest.mark.anyio
@ -227,7 +227,7 @@ class TestCallTools:
manager = ToolManager()
tool = manager.add_tool(MyAsyncTool())
result = await tool.run({"x": 5})
result = await tool.run({"x": 5}, Context())
assert result == 10
@pytest.mark.anyio
@ -238,7 +238,7 @@ class TestCallTools:
manager = ToolManager()
manager.add_tool(sum)
result = await manager.call_tool("sum", {"a": 1})
result = await manager.call_tool("sum", {"a": 1}, Context())
assert result == 2
@pytest.mark.anyio
@ -250,13 +250,13 @@ class TestCallTools:
manager = ToolManager()
manager.add_tool(sum)
with pytest.raises(ToolError):
await manager.call_tool("sum", {"a": 1})
await manager.call_tool("sum", {"a": 1}, Context())
@pytest.mark.anyio
async def test_call_unknown_tool(self):
manager = ToolManager()
with pytest.raises(ToolError):
await manager.call_tool("unknown", {"a": 1})
await manager.call_tool("unknown", {"a": 1}, Context())
@pytest.mark.anyio
async def test_call_tool_with_list_int_input(self):
@ -266,9 +266,9 @@ class TestCallTools:
manager = ToolManager()
manager.add_tool(sum_vals)
# Try both with plain list and with JSON list
result = await manager.call_tool("sum_vals", {"vals": "[1, 2, 3]"})
result = await manager.call_tool("sum_vals", {"vals": "[1, 2, 3]"}, Context())
assert result == 6
result = await manager.call_tool("sum_vals", {"vals": [1, 2, 3]})
result = await manager.call_tool("sum_vals", {"vals": [1, 2, 3]}, Context())
assert result == 6
@pytest.mark.anyio
@ -279,13 +279,13 @@ class TestCallTools:
manager = ToolManager()
manager.add_tool(concat_strs)
# Try both with plain python object and with JSON list
result = await manager.call_tool("concat_strs", {"vals": ["a", "b", "c"]})
result = await manager.call_tool("concat_strs", {"vals": ["a", "b", "c"]}, Context())
assert result == "abc"
result = await manager.call_tool("concat_strs", {"vals": '["a", "b", "c"]'})
result = await manager.call_tool("concat_strs", {"vals": '["a", "b", "c"]'}, Context())
assert result == "abc"
result = await manager.call_tool("concat_strs", {"vals": "a"})
result = await manager.call_tool("concat_strs", {"vals": "a"}, Context())
assert result == "a"
result = await manager.call_tool("concat_strs", {"vals": '"a"'})
result = await manager.call_tool("concat_strs", {"vals": '"a"'}, Context())
assert result == '"a"'
@pytest.mark.anyio
@ -297,7 +297,7 @@ class TestCallTools:
shrimp: list[Shrimp]
x: None
def name_shrimp(tank: MyShrimpTank, ctx: Context[ServerSessionT, None]) -> list[str]:
def name_shrimp(tank: MyShrimpTank) -> list[str]:
return [x.name for x in tank.shrimp]
manager = ToolManager()
@ -305,11 +305,13 @@ class TestCallTools:
result = await manager.call_tool(
"name_shrimp",
{"tank": {"x": None, "shrimp": [{"name": "rex"}, {"name": "gertrude"}]}},
Context(),
)
assert result == ["rex", "gertrude"]
result = await manager.call_tool(
"name_shrimp",
{"tank": '{"x": null, "shrimp": [{"name": "rex"}, {"name": "gertrude"}]}'},
Context(),
)
assert result == ["rex", "gertrude"]
@ -364,9 +366,7 @@ class TestContextHandling:
manager = ToolManager()
manager.add_tool(tool_with_context)
mcp = MCPServer()
ctx = mcp.get_context()
result = await manager.call_tool("tool_with_context", {"x": 42}, context=ctx)
result = await manager.call_tool("tool_with_context", {"x": 42}, context=Context())
assert result == "42"
@pytest.mark.anyio
@ -380,22 +380,7 @@ class TestContextHandling:
manager = ToolManager()
manager.add_tool(async_tool)
mcp = MCPServer()
ctx = mcp.get_context()
result = await manager.call_tool("async_tool", {"x": 42}, context=ctx)
assert result == "42"
@pytest.mark.anyio
async def test_context_optional(self):
"""Test that context is optional when calling tools."""
def tool_with_context(x: int, ctx: Context[ServerSessionT, None] | None = None) -> str:
return str(x)
manager = ToolManager()
manager.add_tool(tool_with_context)
# Should not raise an error when context is not provided
result = await manager.call_tool("tool_with_context", {"x": 42})
result = await manager.call_tool("async_tool", {"x": 42}, context=Context())
assert result == "42"
@pytest.mark.anyio
@ -408,10 +393,8 @@ class TestContextHandling:
manager = ToolManager()
manager.add_tool(tool_with_context)
mcp = MCPServer()
ctx = mcp.get_context()
with pytest.raises(ToolError, match="Error executing tool tool_with_context"):
await manager.call_tool("tool_with_context", {"x": 42}, context=ctx)
await manager.call_tool("tool_with_context", {"x": 42}, context=Context())
class TestToolAnnotations:
@ -471,7 +454,7 @@ class TestStructuredOutput:
manager = ToolManager()
manager.add_tool(get_user)
result = await manager.call_tool("get_user", {"user_id": 1}, convert_result=True)
result = await manager.call_tool("get_user", {"user_id": 1}, Context(), convert_result=True)
# don't test unstructured output here, just the structured conversion
assert len(result) == 2 and result[1] == {"name": "John", "age": 30}
@ -485,9 +468,9 @@ class TestStructuredOutput:
manager = ToolManager()
manager.add_tool(double_number)
result = await manager.call_tool("double_number", {"n": 5})
result = await manager.call_tool("double_number", {"n": 5}, Context())
assert result == 10
result = await manager.call_tool("double_number", {"n": 5}, convert_result=True)
result = await manager.call_tool("double_number", {"n": 5}, Context(), convert_result=True)
assert isinstance(result[0][0], TextContent) and result[1] == {"result": 10}
@pytest.mark.anyio
@ -506,7 +489,7 @@ class TestStructuredOutput:
manager = ToolManager()
manager.add_tool(get_user_dict)
result = await manager.call_tool("get_user_dict", {"user_id": 1})
result = await manager.call_tool("get_user_dict", {"user_id": 1}, Context())
assert result == expected_output
@pytest.mark.anyio
@ -526,7 +509,7 @@ class TestStructuredOutput:
manager = ToolManager()
manager.add_tool(get_person)
result = await manager.call_tool("get_person", {}, convert_result=True)
result = await manager.call_tool("get_person", {}, Context(), convert_result=True)
# don't test unstructured output here, just the structured conversion
assert len(result) == 2 and result[1] == expected_output
@ -543,9 +526,9 @@ class TestStructuredOutput:
manager = ToolManager()
manager.add_tool(get_numbers)
result = await manager.call_tool("get_numbers", {})
result = await manager.call_tool("get_numbers", {}, Context())
assert result == expected_list
result = await manager.call_tool("get_numbers", {}, convert_result=True)
result = await manager.call_tool("get_numbers", {}, Context(), convert_result=True)
assert isinstance(result[0][0], TextContent) and result[1] == expected_output
@pytest.mark.anyio
@ -558,7 +541,7 @@ class TestStructuredOutput:
manager = ToolManager()
manager.add_tool(get_dict, structured_output=False)
result = await manager.call_tool("get_dict", {})
result = await manager.call_tool("get_dict", {}, Context())
assert isinstance(result, dict)
assert result == {"key": "value"}
@ -601,12 +584,12 @@ class TestStructuredOutput:
assert "properties" not in tool.output_schema # dict[str, Any] has no constraints
# Test raw result
result = await manager.call_tool("get_config", {})
result = await manager.call_tool("get_config", {}, Context())
expected = {"debug": True, "port": 8080, "features": ["auth", "logging"]}
assert result == expected
# Test converted result
result = await manager.call_tool("get_config", {})
result = await manager.call_tool("get_config", {}, Context())
assert result == expected
@pytest.mark.anyio
@ -626,12 +609,12 @@ class TestStructuredOutput:
assert tool.output_schema["additionalProperties"]["type"] == "integer"
# Test raw result
result = await manager.call_tool("get_scores", {})
result = await manager.call_tool("get_scores", {}, Context())
expected = {"alice": 100, "bob": 85, "charlie": 92}
assert result == expected
# Test converted result
result = await manager.call_tool("get_scores", {})
result = await manager.call_tool("get_scores", {}, Context())
assert result == expected
@ -885,7 +868,7 @@ class TestRemoveTools:
manager.add_tool(greet)
# Verify tool works before removal
result = await manager.call_tool("greet", {"name": "World"})
result = await manager.call_tool("greet", {"name": "World"}, Context())
assert result == "Hello, World!"
# Remove the tool
@ -893,7 +876,7 @@ class TestRemoveTools:
# Verify calling removed tool raises error
with pytest.raises(ToolError, match="Unknown tool: greet"):
await manager.call_tool("greet", {"name": "World"})
await manager.call_tool("greet", {"name": "World"}, Context())
def test_remove_tool_case_sensitive(self):
"""Test that tool removal is case-sensitive."""