Skip to content

Commit e5eb35b

Browse files
committed
feat!: make run_context and agent required parameters for MCP tools listing
BREAKING CHANGE: Agent.get_mcp_tools() and MCPServer.list_tools() now require run_context and agent parameters for API consistency and type safety
1 parent 02f0f27 commit e5eb35b

File tree

8 files changed

+98
-94
lines changed

8 files changed

+98
-94
lines changed

docs/ja/mcp.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,20 @@ Agents SDK は MCP をサポートしており、これにより幅広い MCP
2323
たとえば、[公式 MCP filesystem サーバー](https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem)を利用する場合は次のようになります。
2424

2525
```python
26+
from agents.run_context import RunContextWrapper
27+
2628
async with MCPServerStdio(
2729
params={
2830
"command": "npx",
2931
"args": ["-y", "@modelcontextprotocol/server-filesystem", samples_dir],
3032
}
3133
) as server:
32-
tools = await server.list_tools()
34+
# 注意:実際には通常は MCP サーバーをエージェントに追加し、
35+
# フレームワークがツール一覧の取得を自動的に処理するようにします。
36+
# list_tools() への直接呼び出しには run_context と agent パラメータが必要です。
37+
run_context = RunContextWrapper(context=None)
38+
agent = Agent(name="test", instructions="test")
39+
tools = await server.list_tools(run_context, agent)
3340
```
3441

3542
## MCP サーバーの利用

docs/mcp.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,20 @@ You can use the [`MCPServerStdio`][agents.mcp.server.MCPServerStdio], [`MCPServe
1919
For example, this is how you'd use the [official MCP filesystem server](https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem).
2020

2121
```python
22+
from agents.run_context import RunContextWrapper
23+
2224
async with MCPServerStdio(
2325
params={
2426
"command": "npx",
2527
"args": ["-y", "@modelcontextprotocol/server-filesystem", samples_dir],
2628
}
2729
) as server:
28-
tools = await server.list_tools()
30+
# Note: In practice, you typically add the server to an Agent
31+
# and let the framework handle tool listing automatically.
32+
# Direct calls to list_tools() require run_context and agent parameters.
33+
run_context = RunContextWrapper(context=None)
34+
agent = Agent(name="test", instructions="test")
35+
tools = await server.list_tools(run_context, agent)
2936
```
3037

3138
## Using MCP servers

src/agents/mcp/server.py

Lines changed: 16 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ async def cleanup(self):
5252
@abc.abstractmethod
5353
async def list_tools(
5454
self,
55-
run_context: RunContextWrapper[Any] | None = None,
56-
agent: Agent[Any] | None = None,
55+
run_context: RunContextWrapper[Any],
56+
agent: Agent[Any],
5757
) -> list[MCPTool]:
5858
"""List the tools available on the server."""
5959
pass
@@ -102,8 +102,8 @@ def __init__(
102102
async def _apply_tool_filter(
103103
self,
104104
tools: list[MCPTool],
105-
run_context: RunContextWrapper[Any] | None,
106-
agent: Agent[Any] | None,
105+
run_context: RunContextWrapper[Any],
106+
agent: Agent[Any],
107107
) -> list[MCPTool]:
108108
"""Apply the tool filter to the list of tools."""
109109
if self.tool_filter is None:
@@ -140,8 +140,8 @@ def _apply_static_tool_filter(
140140
async def _apply_dynamic_tool_filter(
141141
self,
142142
tools: list[MCPTool],
143-
run_context: RunContextWrapper[Any] | None,
144-
agent: Agent[Any] | None,
143+
run_context: RunContextWrapper[Any],
144+
agent: Agent[Any],
145145
) -> list[MCPTool]:
146146
"""Apply dynamic tool filtering using a callable filter function."""
147147

@@ -150,35 +150,18 @@ async def _apply_dynamic_tool_filter(
150150
raise ValueError("Tool filter must be callable for dynamic filtering")
151151
tool_filter_func = cast(ToolFilterCallable, self.tool_filter)
152152

153-
# Create filter context - it may be None if run_context or agent is None
154-
filter_context = None
155-
if run_context is not None and agent is not None:
156-
filter_context = ToolFilterContext(
157-
run_context=run_context,
158-
agent=agent,
159-
server_name=self.name,
160-
)
153+
# Create filter context
154+
filter_context = ToolFilterContext(
155+
run_context=run_context,
156+
agent=agent,
157+
server_name=self.name,
158+
)
161159

162160
filtered_tools = []
163161
for tool in tools:
164162
try:
165-
# Try to call the filter function
166-
if filter_context is not None:
167-
# We have full context, call with context
168-
result = tool_filter_func(filter_context, tool)
169-
else:
170-
# Try to call without context first to see if it works
171-
try:
172-
# Some filters might not need context parameters at all
173-
result = tool_filter_func(None, tool)
174-
except (TypeError, AttributeError) as e:
175-
# If the filter tries to access context attributes, raise a helpful error
176-
raise UserError(
177-
"Dynamic tool filters require both run_context and agent when the "
178-
"filter function accesses context information. This typically happens "
179-
"when calling list_tools() directly without these parameters. Either "
180-
"provide both parameters or use a static tool filter instead."
181-
) from e
163+
# Call the filter function with context
164+
result = tool_filter_func(filter_context, tool)
182165

183166
if inspect.isawaitable(result):
184167
should_include = await result
@@ -187,9 +170,6 @@ async def _apply_dynamic_tool_filter(
187170

188171
if should_include:
189172
filtered_tools.append(tool)
190-
except UserError:
191-
# Re-raise UserError as-is (this includes our context requirement error)
192-
raise
193173
except Exception as e:
194174
logger.error(
195175
f"Error applying tool filter to tool '{tool.name}' on server '{self.name}': {e}"
@@ -251,8 +231,8 @@ async def connect(self):
251231

252232
async def list_tools(
253233
self,
254-
run_context: RunContextWrapper[Any] | None = None,
255-
agent: Agent[Any] | None = None,
234+
run_context: RunContextWrapper[Any],
235+
agent: Agent[Any],
256236
) -> list[MCPTool]:
257237
"""List the tools available on the server."""
258238
if not self.session:

src/agents/mcp/util.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,11 @@ class ToolFilterContext:
3636
"""The name of the MCP server."""
3737

3838

39-
ToolFilterCallable = Callable[["ToolFilterContext | None", "MCPTool"], MaybeAwaitable[bool]]
39+
ToolFilterCallable = Callable[["ToolFilterContext", "MCPTool"], MaybeAwaitable[bool]]
4040
"""A function that determines whether a tool should be available.
4141
4242
Args:
4343
context: The context information including run context, agent, and server name.
44-
Can be None if run_context or agent is not available.
4544
tool: The MCP tool to filter.
4645
4746
Returns:
@@ -100,8 +99,8 @@ async def get_all_function_tools(
10099
cls,
101100
servers: list["MCPServer"],
102101
convert_schemas_to_strict: bool,
103-
run_context: RunContextWrapper[Any] | None = None,
104-
agent: "Agent[Any] | None" = None,
102+
run_context: RunContextWrapper[Any],
103+
agent: "Agent[Any]",
105104
) -> list[Tool]:
106105
"""Get all function tools from a list of MCP servers."""
107106
tools = []
@@ -126,8 +125,8 @@ async def get_function_tools(
126125
cls,
127126
server: "MCPServer",
128127
convert_schemas_to_strict: bool,
129-
run_context: RunContextWrapper[Any] | None = None,
130-
agent: "Agent[Any] | None" = None,
128+
run_context: RunContextWrapper[Any],
129+
agent: "Agent[Any]",
131130
) -> list[Tool]:
132131
"""Get all function tools from a single MCP server."""
133132

tests/mcp/test_caching.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import pytest
44
from mcp.types import ListToolsResult, Tool as MCPTool
55

6+
from agents import Agent
67
from agents.mcp import MCPServerStdio
8+
from agents.run_context import RunContextWrapper
79

810
from .helpers import DummyStreamsContextManager, tee
911

@@ -33,25 +35,29 @@ async def test_server_caching_works(
3335
mock_list_tools.return_value = ListToolsResult(tools=tools)
3436

3537
async with server:
38+
# Create test context and agent
39+
run_context = RunContextWrapper(context=None)
40+
agent = Agent(name="test_agent", instructions="Test agent")
41+
3642
# Call list_tools() multiple times
37-
tools = await server.list_tools()
38-
assert tools == tools
43+
result_tools = await server.list_tools(run_context, agent)
44+
assert result_tools == tools
3945

4046
assert mock_list_tools.call_count == 1, "list_tools() should have been called once"
4147

4248
# Call list_tools() again, should return the cached value
43-
tools = await server.list_tools()
44-
assert tools == tools
49+
result_tools = await server.list_tools(run_context, agent)
50+
assert result_tools == tools
4551

4652
assert mock_list_tools.call_count == 1, "list_tools() should not have been called again"
4753

4854
# Invalidate the cache and call list_tools() again
4955
server.invalidate_tools_cache()
50-
tools = await server.list_tools()
51-
assert tools == tools
56+
result_tools = await server.list_tools(run_context, agent)
57+
assert result_tools == tools
5258

5359
assert mock_list_tools.call_count == 2, "list_tools() should be called again"
5460

5561
# Without invalidating the cache, calling list_tools() again should return the cached value
56-
tools = await server.list_tools()
57-
assert tools == tools
62+
result_tools = await server.list_tools(run_context, agent)
63+
assert result_tools == tools

tests/mcp/test_mcp_util.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ async def test_get_all_function_tools():
5757
server3.add_tool(names[4], schemas[4])
5858

5959
servers: list[MCPServer] = [server1, server2, server3]
60-
tools = await MCPUtil.get_all_function_tools(servers, convert_schemas_to_strict=False)
60+
run_context = RunContextWrapper(context=None)
61+
agent = Agent(name="test_agent", instructions="Test agent")
62+
63+
tools = await MCPUtil.get_all_function_tools(servers, False, run_context, agent)
6164
assert len(tools) == 5
6265
assert all(tool.name in names for tool in tools)
6366

@@ -70,7 +73,7 @@ async def test_get_all_function_tools():
7073
assert tool.name == names[idx]
7174

7275
# Also make sure it works with strict schemas
73-
tools = await MCPUtil.get_all_function_tools(servers, convert_schemas_to_strict=True)
76+
tools = await MCPUtil.get_all_function_tools(servers, True, run_context, agent)
7477
assert len(tools) == 5
7578
assert all(tool.name in names for tool in tools)
7679

@@ -282,7 +285,9 @@ async def test_util_adds_properties():
282285
server = FakeMCPServer()
283286
server.add_tool("test_tool", schema)
284287

285-
tools = await MCPUtil.get_all_function_tools([server], convert_schemas_to_strict=False)
288+
run_context = RunContextWrapper(context=None)
289+
agent = Agent(name="test_agent", instructions="Test agent")
290+
tools = await MCPUtil.get_all_function_tools([server], False, run_context, agent)
286291
tool = next(tool for tool in tools if tool.name == "test_tool")
287292

288293
assert isinstance(tool, FunctionTool)

tests/mcp/test_server_errors.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import pytest
22

3+
from agents import Agent
34
from agents.exceptions import UserError
45
from agents.mcp.server import _MCPServerWithClientSession
6+
from agents.run_context import RunContextWrapper
57

68

79
class CrashingClientSessionServer(_MCPServerWithClientSession):
@@ -35,8 +37,11 @@ async def test_server_errors_cause_error_and_cleanup_called():
3537
async def test_not_calling_connect_causes_error():
3638
server = CrashingClientSessionServer()
3739

40+
run_context = RunContextWrapper(context=None)
41+
agent = Agent(name="test_agent", instructions="Test agent")
42+
3843
with pytest.raises(UserError):
39-
await server.list_tools()
44+
await server.list_tools(run_context, agent)
4045

4146
with pytest.raises(UserError):
4247
await server.call_tool("foo", {})

0 commit comments

Comments
 (0)