forked from openai/openai-agents-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhelpers.py
More file actions
160 lines (128 loc) · 5.27 KB
/
Copy pathhelpers.py
File metadata and controls
160 lines (128 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
from __future__ import annotations
import asyncio
import json
import shutil
from typing import Any
from mcp import Tool as MCPTool
from mcp.types import (
CallToolResult,
Content,
GetPromptResult,
ListPromptsResult,
ListResourcesResult,
ListResourceTemplatesResult,
PromptMessage,
ReadResourceResult,
TextContent,
)
from agents.mcp import MCPServer
from agents.mcp.server import _UNSET, _MCPServerWithClientSession, _UnsetType
from agents.mcp.util import MCPToolMetaResolver, ToolFilter
from agents.tool import ToolErrorFunction
tee = shutil.which("tee") or ""
assert tee, "tee not found"
# Added dummy stream classes for patching stdio_client to avoid real I/O during tests
class DummyStream:
async def send(self, msg):
pass
async def receive(self):
raise Exception("Dummy receive not implemented")
class DummyStreamsContextManager:
async def __aenter__(self):
return (DummyStream(), DummyStream())
async def __aexit__(self, exc_type, exc_val, exc_tb):
pass
class _TestFilterServer(_MCPServerWithClientSession):
"""Minimal implementation of _MCPServerWithClientSession for testing tool filtering"""
def __init__(self, tool_filter: ToolFilter, server_name: str):
# Initialize parent class properly to avoid type errors
super().__init__(
cache_tools_list=False,
client_session_timeout_seconds=None,
tool_filter=tool_filter,
)
self._server_name: str = server_name
# Override some attributes for test isolation
self.session = None
self._cleanup_lock = asyncio.Lock()
def create_streams(self):
raise NotImplementedError("Not needed for filtering tests")
@property
def name(self) -> str:
return self._server_name
class FakeMCPServer(MCPServer):
def __init__(
self,
tools: list[MCPTool] | None = None,
tool_filter: ToolFilter = None,
server_name: str = "fake_mcp_server",
require_approval: object | None = None,
failure_error_function: ToolErrorFunction | None | _UnsetType = _UNSET,
tool_meta_resolver: MCPToolMetaResolver | None = None,
):
super().__init__(
use_structured_content=False,
require_approval=require_approval, # type: ignore[arg-type]
failure_error_function=failure_error_function,
tool_meta_resolver=tool_meta_resolver,
)
self.tools: list[MCPTool] = tools or []
self.tool_calls: list[str] = []
self.tool_results: list[str] = []
self.tool_metas: list[dict[str, Any] | None] = []
self.tool_filter = tool_filter
self._server_name = server_name
self._custom_content: list[Content] | None = None
def add_tool(self, name: str, input_schema: dict[str, Any]):
self.tools.append(MCPTool(name=name, inputSchema=input_schema))
async def connect(self):
pass
async def cleanup(self):
pass
async def list_tools(self, run_context=None, agent=None):
tools = self.tools
# Apply tool filtering using the REAL implementation
if self.tool_filter is not None:
# Use the real _MCPServerWithClientSession filtering logic
filter_server = _TestFilterServer(self.tool_filter, self.name)
tools = await filter_server._apply_tool_filter(tools, run_context, agent)
return tools
async def call_tool(
self,
tool_name: str,
arguments: dict[str, Any] | None,
meta: dict[str, Any] | None = None,
) -> CallToolResult:
self.tool_calls.append(tool_name)
self.tool_results.append(f"result_{tool_name}_{json.dumps(arguments)}")
self.tool_metas.append(meta)
# Allow testing custom content scenarios
if self._custom_content is not None:
return CallToolResult(content=self._custom_content)
return CallToolResult(
content=[TextContent(text=self.tool_results[-1], type="text")],
)
async def list_prompts(self, run_context=None, agent=None) -> ListPromptsResult:
"""Return empty list of prompts for fake server"""
return ListPromptsResult(prompts=[])
async def get_prompt(
self, name: str, arguments: dict[str, Any] | None = None
) -> GetPromptResult:
"""Return a simple prompt result for fake server"""
content = f"Fake prompt content for {name}"
message = PromptMessage(role="user", content=TextContent(type="text", text=content))
return GetPromptResult(description=f"Fake prompt: {name}", messages=[message])
async def list_resources(self, cursor: str | None = None) -> ListResourcesResult:
"""Return empty list of resources for fake server."""
return ListResourcesResult(resources=[])
async def list_resource_templates(
self, cursor: str | None = None
) -> ListResourceTemplatesResult:
"""Return empty list of resource templates for fake server."""
return ListResourceTemplatesResult(resourceTemplates=[])
async def read_resource(self, uri: str) -> ReadResourceResult:
"""Return empty resource contents for fake server."""
return ReadResourceResult(contents=[])
@property
def name(self) -> str:
return self._server_name