Skip to content

Commit f553f20

Browse files
committed
refactor: remove Agent-level tool filtering logic
1 parent 9b2248e commit f553f20

File tree

4 files changed

+7
-211
lines changed

4 files changed

+7
-211
lines changed

docs/mcp.md

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,7 @@ agent=Agent(
4343

4444
## Tool filtering
4545

46-
You can filter which tools are available to your Agent in two ways:
47-
48-
### Server-level filtering
49-
50-
Each MCP server instance can be configured with `allowed_tools` and `excluded_tools` parameters to control which tools it exposes:
46+
You can filter which tools are available to your Agent using server-level filtering:
5147

5248
```python
5349
# Only expose specific tools from this server
@@ -69,29 +65,6 @@ server = MCPServerStdio(
6965
)
7066
```
7167

72-
### Agent-level filtering
73-
74-
You can also filter tools at the Agent level using the `mcp_config` parameter. This allows you to control which tools are available across all MCP servers:
75-
76-
```python
77-
agent = Agent(
78-
name="Assistant",
79-
instructions="Use the tools to achieve the task",
80-
mcp_servers=[server1, server2, server3],
81-
mcp_config={
82-
"allowed_tools": {
83-
"server1": ["read_file", "write_file"], # Only these tools from server1
84-
"server2": ["search"], # Only search tool from server2
85-
},
86-
"excluded_tools": {
87-
"server3": ["dangerous_tool"], # Exclude this tool from server3
88-
}
89-
}
90-
)
91-
```
92-
93-
**Filtering priority**: Server-level filtering is applied first, then Agent-level filtering. This allows for fine-grained control where servers can limit their exposed tools, and Agents can further restrict which tools they use.
94-
9568
## Caching
9669

9770
Every time an Agent runs, it calls `list_tools()` on the MCP server. This can be a latency hit, especially if the server is a remote server. To automatically cache the list of tools, you can pass `cache_tools_list=True` to [`MCPServerStdio`][agents.mcp.server.MCPServerStdio], [`MCPServerSse`][agents.mcp.server.MCPServerSse], and [`MCPServerStreamableHttp`][agents.mcp.server.MCPServerStreamableHttp]. You should only do this if you're certain the tool list will not change.

src/agents/agent.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,6 @@ class MCPConfig(TypedDict):
6262
"""If True, we will attempt to convert the MCP schemas to strict-mode schemas. This is a
6363
best-effort conversion, so some schemas may not be convertible. Defaults to False.
6464
"""
65-
allowed_tools: NotRequired[dict[str, list[str]]]
66-
"""Optional: server_name -> allowed tool names (whitelist)"""
67-
excluded_tools: NotRequired[dict[str, list[str]]]
68-
"""Optional: server_name -> excluded tool names (blacklist)"""
6965

7066

7167
@dataclass
@@ -249,14 +245,7 @@ async def get_system_prompt(self, run_context: RunContextWrapper[TContext]) -> s
249245
async def get_mcp_tools(self) -> list[Tool]:
250246
"""Fetches the available tools from the MCP servers."""
251247
convert_schemas_to_strict = self.mcp_config.get("convert_schemas_to_strict", False)
252-
allowed_tools_map = self.mcp_config.get("allowed_tools", {})
253-
excluded_tools_map = self.mcp_config.get("excluded_tools", {})
254-
return await MCPUtil.get_all_function_tools(
255-
self.mcp_servers,
256-
convert_schemas_to_strict,
257-
allowed_tools_map,
258-
excluded_tools_map,
259-
)
248+
return await MCPUtil.get_all_function_tools(self.mcp_servers, convert_schemas_to_strict)
260249

261250
async def get_all_tools(self, run_context: RunContextWrapper[Any]) -> list[Tool]:
262251
"""All agent tools, including MCP tools and function tools."""

src/agents/mcp/util.py

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import functools
22
import json
3-
from typing import TYPE_CHECKING, Any, Optional
3+
from typing import TYPE_CHECKING, Any
44

55
from agents.strict_schema import ensure_strict_json_schema
66

@@ -22,23 +22,13 @@ class MCPUtil:
2222

2323
@classmethod
2424
async def get_all_function_tools(
25-
cls,
26-
servers: list["MCPServer"],
27-
convert_schemas_to_strict: bool,
28-
allowed_tools_map: Optional[dict[str, list[str]]] = None,
29-
excluded_tools_map: Optional[dict[str, list[str]]] = None,
25+
cls, servers: list["MCPServer"], convert_schemas_to_strict: bool
3026
) -> list[Tool]:
3127
"""Get all function tools from a list of MCP servers."""
3228
tools = []
3329
tool_names: set[str] = set()
34-
allowed_tools_map = allowed_tools_map or {}
35-
excluded_tools_map = excluded_tools_map or {}
3630
for server in servers:
37-
allowed = allowed_tools_map.get(server.name)
38-
excluded = excluded_tools_map.get(server.name)
39-
server_tools = await cls.get_function_tools(
40-
server, convert_schemas_to_strict, allowed, excluded
41-
)
31+
server_tools = await cls.get_function_tools(server, convert_schemas_to_strict)
4232
server_tool_names = {tool.name for tool in server_tools}
4333
if len(server_tool_names & tool_names) > 0:
4434
raise UserError(
@@ -52,29 +42,15 @@ async def get_all_function_tools(
5242

5343
@classmethod
5444
async def get_function_tools(
55-
cls,
56-
server: "MCPServer",
57-
convert_schemas_to_strict: bool,
58-
allowed_tools: Optional[list[str]] = None,
59-
excluded_tools: Optional[list[str]] = None,
45+
cls, server: "MCPServer", convert_schemas_to_strict: bool
6046
) -> list[Tool]:
6147
"""Get all function tools from a single MCP server."""
6248

6349
with mcp_tools_span(server=server.name) as span:
6450
tools = await server.list_tools()
6551
span.span_data.result = [tool.name for tool in tools]
6652

67-
# Apply Agent-level filtering (additional filtering on top of server-level filtering)
68-
filtered_tools = tools
69-
if allowed_tools is not None:
70-
filtered_tools = [t for t in filtered_tools if t.name in allowed_tools]
71-
if excluded_tools is not None:
72-
filtered_tools = [t for t in filtered_tools if t.name not in excluded_tools]
73-
74-
return [
75-
cls.to_function_tool(tool, server, convert_schemas_to_strict)
76-
for tool in filtered_tools
77-
]
53+
return [cls.to_function_tool(tool, server, convert_schemas_to_strict) for tool in tools]
7854

7955
@classmethod
8056
def to_function_tool(

tests/mcp/test_tool_filtering.py

Lines changed: 0 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import pytest
22

3-
from agents import Agent
4-
from agents.mcp import MCPUtil
5-
63
from .helpers import FakeMCPServer
74

85

@@ -82,142 +79,3 @@ async def test_server_both_filters():
8279
tools = await server.list_tools()
8380
assert len(tools) == 2
8481
assert {t.name for t in tools} == {"tool1", "tool2"}
85-
86-
87-
@pytest.mark.asyncio
88-
async def test_agent_allowed_tools():
89-
"""Test that agent-level allowed_tools filters tools correctly"""
90-
server1 = FilterableFakeMCPServer(server_name="server1")
91-
server1.add_tool("tool1", {})
92-
server1.add_tool("tool2", {})
93-
94-
server2 = FilterableFakeMCPServer(server_name="server2")
95-
server2.add_tool("tool3", {})
96-
server2.add_tool("tool4", {})
97-
98-
# Create agent with allowed_tools in mcp_config
99-
agent = Agent(
100-
name="test_agent",
101-
mcp_servers=[server1, server2],
102-
mcp_config={
103-
"allowed_tools": {
104-
"server1": ["tool1"],
105-
"server2": ["tool3"],
106-
}
107-
}
108-
)
109-
110-
# Get tools and verify filtering
111-
tools = await agent.get_mcp_tools()
112-
assert len(tools) == 2
113-
assert {t.name for t in tools} == {"tool1", "tool3"}
114-
115-
116-
@pytest.mark.asyncio
117-
async def test_agent_excluded_tools():
118-
"""Test that agent-level excluded_tools filters tools correctly"""
119-
server1 = FilterableFakeMCPServer(server_name="server1")
120-
server1.add_tool("tool1", {})
121-
server1.add_tool("tool2", {})
122-
123-
server2 = FilterableFakeMCPServer(server_name="server2")
124-
server2.add_tool("tool3", {})
125-
server2.add_tool("tool4", {})
126-
127-
# Create agent with excluded_tools in mcp_config
128-
agent = Agent(
129-
name="test_agent",
130-
mcp_servers=[server1, server2],
131-
mcp_config={
132-
"excluded_tools": {
133-
"server1": ["tool2"],
134-
"server2": ["tool4"],
135-
}
136-
}
137-
)
138-
139-
# Get tools and verify filtering
140-
tools = await agent.get_mcp_tools()
141-
assert len(tools) == 2
142-
assert {t.name for t in tools} == {"tool1", "tool3"}
143-
144-
145-
@pytest.mark.asyncio
146-
async def test_combined_filtering():
147-
"""Test that server-level and agent-level filtering work together correctly"""
148-
# Server with its own filtering
149-
server = FilterableFakeMCPServer(server_name="test_server")
150-
server.add_tool("tool1", {})
151-
server.add_tool("tool2", {})
152-
server.add_tool("tool3", {})
153-
server.add_tool("tool4", {})
154-
server.allowed_tools = ["tool1", "tool2", "tool3"] # Server only exposes these
155-
156-
# Agent with additional filtering
157-
agent = Agent(
158-
name="test_agent",
159-
mcp_servers=[server],
160-
mcp_config={
161-
"excluded_tools": {
162-
"test_server": ["tool3"], # Agent excludes this one
163-
}
164-
}
165-
)
166-
167-
# Get tools and verify filtering
168-
tools = await agent.get_mcp_tools()
169-
assert len(tools) == 2
170-
assert {t.name for t in tools} == {"tool1", "tool2"}
171-
172-
173-
@pytest.mark.asyncio
174-
async def test_util_direct_filtering():
175-
"""Test MCPUtil.get_all_function_tools with filtering parameters"""
176-
server1 = FilterableFakeMCPServer(server_name="server1")
177-
server1.add_tool("tool1", {})
178-
server1.add_tool("tool2", {})
179-
180-
server2 = FilterableFakeMCPServer(server_name="server2")
181-
server2.add_tool("tool3", {})
182-
server2.add_tool("tool4", {})
183-
184-
# Test direct filtering through MCPUtil
185-
allowed_tools_map = {"server1": ["tool1"]}
186-
excluded_tools_map = {"server2": ["tool4"]}
187-
188-
tools = await MCPUtil.get_all_function_tools(
189-
[server1, server2],
190-
convert_schemas_to_strict=False,
191-
allowed_tools_map=allowed_tools_map,
192-
excluded_tools_map=excluded_tools_map
193-
)
194-
195-
assert len(tools) == 2
196-
assert {t.name for t in tools} == {"tool1", "tool3"}
197-
198-
199-
@pytest.mark.asyncio
200-
async def test_filtering_priority():
201-
"""Test that server-level filtering takes priority over agent-level filtering"""
202-
# Server only exposes tool1 and tool2
203-
server = FilterableFakeMCPServer(server_name="test_server")
204-
server.add_tool("tool1", {})
205-
server.add_tool("tool2", {})
206-
server.add_tool("tool3", {})
207-
server.allowed_tools = ["tool1", "tool2"]
208-
209-
# Agent tries to allow tool3 (which server doesn't expose)
210-
agent = Agent(
211-
name="test_agent",
212-
mcp_servers=[server],
213-
mcp_config={
214-
"allowed_tools": {
215-
"test_server": ["tool2", "tool3"], # tool3 isn't available from server
216-
}
217-
}
218-
)
219-
220-
# Get tools and verify filtering
221-
tools = await agent.get_mcp_tools()
222-
assert len(tools) == 1
223-
assert tools[0].name == "tool2" # Only tool2 passes both filters

0 commit comments

Comments
 (0)