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