Coverage for agentos/tests/test_mcp.py: 0%
133 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-08 21:26 +0800
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-08 21:26 +0800
1"""Tests for MCP client and tool adapter."""
3import pytest
5from agentos.mcp import (
6 MCPClient,
7 MCPError,
8 MCPPromptInfo,
9 MCPResourceInfo,
10 MCPServerConfig,
11 MCPToolInfo,
12)
13from agentos.mcp.adapter import MCPToolAdapter, MCPToolRegistry
14from agentos.tools.base import PermissionLevel
17class TestMCPServerConfig:
18 """Server configuration tests."""
20 def test_defaults(self):
21 config = MCPServerConfig(name="test")
22 assert config.name == "test"
23 assert config.transport == "stdio"
24 assert config.args == []
25 assert config.timeout == 30
27 def test_custom(self):
28 config = MCPServerConfig(
29 name="github",
30 transport="sse",
31 url="http://localhost:8080",
32 timeout=60,
33 )
34 assert config.transport == "sse"
35 assert config.url == "http://localhost:8080"
36 assert config.timeout == 60
39class TestMCPClientLifecycle:
40 """Client init and teardown tests (no real server needed)."""
42 @pytest.mark.asyncio
43 async def test_init_empty(self):
44 client = MCPClient()
45 assert client.connected_servers == []
46 assert client.list_tools() == []
47 assert client.list_resources() == []
48 assert client.list_prompts() == []
50 @pytest.mark.asyncio
51 async def test_context_manager(self):
52 async with MCPClient() as client:
53 assert client.connected_servers == []
55 @pytest.mark.asyncio
56 async def test_connect_unknown_transport(self):
57 client = MCPClient()
58 config = MCPServerConfig(name="bad", transport="grpc")
59 with pytest.raises(MCPError, match="Unknown transport"):
60 await client.connect_server(config)
62 @pytest.mark.asyncio
63 async def test_sse_requires_url(self):
64 client = MCPClient()
65 config = MCPServerConfig(name="bad", transport="sse")
66 with pytest.raises(MCPError, match="URL required"):
67 await client.connect_server(config)
70class TestMCPToolAdapter:
71 """Tool adapter wrapping tests."""
73 def test_adapt_tool_basic(self):
74 client = MCPClient()
75 tool = MCPToolInfo(
76 name="read",
77 description="Read a file",
78 server_name="fs",
79 input_schema={
80 "type": "object",
81 "properties": {"path": {"type": "string"}},
82 "required": ["path"],
83 },
84 )
85 adapter = MCPToolAdapter(client=client, tool_info=tool)
86 assert adapter.name == "mcp__fs__read"
87 assert adapter.description == "Read a file"
88 assert "path" in adapter.parameters()["properties"]
90 def test_to_openai_schema(self):
91 client = MCPClient()
92 tool = MCPToolInfo(
93 name="search",
94 description="Search docs",
95 server_name="docs",
96 input_schema={"type": "object", "properties": {"q": {"type": "string"}}},
97 )
98 adapter = MCPToolAdapter(client=client, tool_info=tool)
99 schema = adapter.to_openai_schema()
100 assert schema["type"] == "function"
101 assert schema["function"]["name"] == "mcp__docs__search"
102 assert "q" in schema["function"]["parameters"]["properties"]
104 def test_to_anthropic_schema(self):
105 client = MCPClient()
106 tool = MCPToolInfo(name="run", description="Run command", server_name="shell")
107 adapter = MCPToolAdapter(client=client, tool_info=tool)
108 schema = adapter.to_anthropic_schema()
109 assert schema["name"] == "mcp__shell__run"
111 def test_write_operation_detection(self):
112 client = MCPClient()
113 tool = MCPToolInfo(name="write_file", server_name="fs")
114 adapter = MCPToolAdapter(client=client, tool_info=tool)
115 assert adapter.is_write_operation({"path": "/tmp/x"})
116 assert not adapter.is_read_operation({"path": "/tmp/x"})
118 def test_read_operation_detection(self):
119 client = MCPClient()
120 tool = MCPToolInfo(name="read_file", server_name="fs")
121 adapter = MCPToolAdapter(client=client, tool_info=tool)
122 assert not adapter.is_write_operation({"path": "/tmp/x"})
123 assert adapter.is_read_operation({"path": "/tmp/x"})
125 def test_extract_target_path(self):
126 client = MCPClient()
127 tool = MCPToolInfo(name="tool", server_name="s")
128 adapter = MCPToolAdapter(client=client, tool_info=tool)
129 assert adapter.extract_target_path({"path": "/a/b"}) == "/a/b"
130 assert adapter.extract_target_path({"uri": "file:///x"}) == "file:///x"
132 def test_permission_default(self):
133 client = MCPClient()
134 tool = MCPToolInfo(name="t", server_name="s")
135 adapter = MCPToolAdapter(client=client, tool_info=tool)
136 assert adapter.permission_level == PermissionLevel.MODERATE
138 def test_permission_custom(self):
139 client = MCPClient()
140 tool = MCPToolInfo(name="t", server_name="s")
141 adapter = MCPToolAdapter(
142 client=client,
143 tool_info=tool,
144 permission_level=PermissionLevel.SAFE,
145 )
146 assert adapter.permission_level == PermissionLevel.SAFE
149class TestMCPToolRegistry:
150 """Tool registry tests."""
152 def test_empty_registry(self):
153 client = MCPClient()
154 registry = MCPToolRegistry(client)
155 assert registry.get_all_tools() == {}
156 assert registry.get_tool("nonexistent") is None
158 def test_refresh(self):
159 client = MCPClient()
160 registry = MCPToolRegistry(client)
161 registry.refresh() # Should not raise
164class TestMCPDataModels:
165 """Data model tests."""
167 def test_tool_info_minimal(self):
168 info = MCPToolInfo(name="t", server_name="s")
169 assert info.description == ""
170 assert info.input_schema == {}
172 def test_resource_info(self):
173 info = MCPResourceInfo(
174 uri="file:///data",
175 name="config",
176 mime_type="application/json",
177 server_name="s",
178 )
179 assert info.uri == "file:///data"
180 assert info.mime_type == "application/json"
182 def test_prompt_info(self):
183 info = MCPPromptInfo(
184 name="greet",
185 description="Generate greeting",
186 arguments=[{"name": "style", "required": True}],
187 server_name="s",
188 )
189 assert len(info.arguments) == 1
190 assert info.arguments[0]["required"]
193class TestMCPError:
194 """Error handling tests."""
196 def test_error_basic(self):
197 err = MCPError(-32602, "Invalid params")
198 assert err.code == -32602
199 assert "Invalid params" in str(err)
201 def test_error_with_data(self):
202 err = MCPError(-1, "custom", data={"detail": "xyz"})
203 assert err.data == {"detail": "xyz"}
206class TestMCPToolAdapterEdgeCases:
207 """Edge case tests for adapter behavior."""
209 def test_adapter_empty_schema(self):
210 client = MCPClient()
211 tool = MCPToolInfo(name="empty", server_name="s")
212 adapter = MCPToolAdapter(client=client, tool_info=tool)
213 schema = adapter.to_openai_schema()
214 assert "properties" in schema["function"]["parameters"]
216 def test_adapter_no_description(self):
217 client = MCPClient()
218 tool = MCPToolInfo(name="t", server_name="s")
219 adapter = MCPToolAdapter(client=client, tool_info=tool)
220 assert "mcp" in adapter.description.lower()