Spaces:
Sleeping
Sleeping
| """Focused compatibility tests for ACE's generic MCP server surface.""" | |
| from __future__ import annotations | |
| import json | |
| import re | |
| from unittest.mock import MagicMock, patch | |
| import pytest | |
| from ace.integrations.mcp.adapters import _MCP_INSTALL_HINT as ADAPTERS_INSTALL_HINT | |
| from ace.integrations.mcp.adapters import _mcp_schema, register_tools | |
| from ace.integrations.mcp.config import MCPServerConfig | |
| from ace.integrations.mcp.errors import ( | |
| ForbiddenInSafeModeError, | |
| SessionNotFoundError, | |
| ValidationError as ACEValidationError, | |
| map_error_to_mcp, | |
| ) | |
| from ace.integrations.mcp.handlers import MCPHandlers | |
| from ace.integrations.mcp.models import AskRequest, LearnSampleRequest | |
| from ace.integrations.mcp.registry import SessionRegistry | |
| from ace.integrations.mcp.server import _MCP_INSTALL_HINT as SERVER_INSTALL_HINT | |
| _CLIENT_PATTERN = re.compile( | |
| r"(vs\s*code|vscode|visual\s*studio\s*code|cursor|windsurf)", | |
| re.IGNORECASE, | |
| ) | |
| def _require_mcp(): | |
| pytest.importorskip("mcp.server") | |
| pytest.importorskip("mcp.types") | |
| from mcp.server import Server | |
| from mcp.types import CallToolRequest, ListToolsRequest | |
| return Server, CallToolRequest, ListToolsRequest | |
| def test_ask_request_schema_is_inlined(): | |
| schema = _mcp_schema(AskRequest) | |
| schema_str = json.dumps(schema) | |
| assert "$ref" not in schema_str | |
| assert "$defs" not in schema_str | |
| assert "session_id" in schema.get("properties", {}) | |
| assert "question" in schema.get("properties", {}) | |
| def test_nested_schema_is_inlined(): | |
| schema = _mcp_schema(LearnSampleRequest) | |
| schema_str = json.dumps(schema) | |
| assert "$ref" not in schema_str | |
| assert "$defs" not in schema_str | |
| assert "samples" in schema.get("properties", {}) | |
| def test_install_hints_are_client_agnostic(): | |
| assert not _CLIENT_PATTERN.search(SERVER_INSTALL_HINT) | |
| assert not _CLIENT_PATTERN.search(ADAPTERS_INSTALL_HINT) | |
| def test_error_messages_are_client_agnostic(): | |
| for err in ( | |
| SessionNotFoundError("session-1"), | |
| ForbiddenInSafeModeError("ace.learn.sample"), | |
| ACEValidationError("prompt too long", details={"field": "question"}), | |
| RuntimeError("boom"), | |
| ): | |
| mapped = map_error_to_mcp(err) | |
| assert not _CLIENT_PATTERN.search(mapped["message"]) | |
| def wired_server(): | |
| Server, _, _ = _require_mcp() | |
| config = MCPServerConfig(safe_mode=False) | |
| registry = SessionRegistry(config) | |
| handlers = MCPHandlers(registry, config) | |
| server = Server("ace-mcp-server") | |
| register_tools(server, handlers) | |
| return server, registry | |
| async def test_published_tool_schemas_are_inlined(wired_server): | |
| server, _ = wired_server | |
| _, _, ListToolsRequest = _require_mcp() | |
| handler = server.request_handlers.get(ListToolsRequest) | |
| assert handler is not None | |
| result = await handler(MagicMock()) | |
| for tool in result.root.tools: | |
| schema_str = json.dumps(tool.inputSchema) | |
| assert "$ref" not in schema_str | |
| assert "$defs" not in schema_str | |
| assert not _CLIENT_PATTERN.search(tool.description or "") | |
| async def test_call_tool_ace_ask_returns_json_payload(wired_server): | |
| server, _ = wired_server | |
| _, CallToolRequest, _ = _require_mcp() | |
| with patch("ace.integrations.mcp.registry.ACELiteLLM") as mock_runner_cls: | |
| runner = MagicMock() | |
| runner.ask.return_value = "The answer is 42." | |
| runner.skillbook.skills.return_value = [] | |
| mock_runner_cls.from_model.return_value = runner | |
| handler = server.request_handlers.get(CallToolRequest) | |
| assert handler is not None | |
| req = MagicMock() | |
| req.params.name = "ace.ask" | |
| req.params.arguments = { | |
| "session_id": "generic-client-1", | |
| "question": "What is the meaning of life?", | |
| } | |
| result = await handler(req) | |
| assert not result.root.isError | |
| payload = json.loads(result.root.content[0].text) | |
| assert payload["answer"] == "The answer is 42." | |
| assert payload["session_id"] == "generic-client-1" | |
| async def test_call_tool_unknown_tool_returns_structured_error(wired_server): | |
| server, _ = wired_server | |
| _, CallToolRequest, _ = _require_mcp() | |
| handler = server.request_handlers.get(CallToolRequest) | |
| assert handler is not None | |
| req = MagicMock() | |
| req.params.name = "nonexistent.tool" | |
| req.params.arguments = {} | |
| result = await handler(req) | |
| assert result.root.isError | |
| payload = json.loads(result.root.content[0].text) | |
| assert payload["code"] == "ACE_MCP_INTERNAL_ERROR" | |
| assert "Unknown tool" in payload["message"] | |
| async def test_session_ids_are_opaque_strings(): | |
| config = MCPServerConfig() | |
| registry = SessionRegistry(config) | |
| with patch("ace.integrations.mcp.registry.ACELiteLLM") as mock_runner_cls: | |
| mock_runner_cls.from_model.side_effect = lambda *a, **kw: MagicMock() | |
| ids = [ | |
| "simple-id", | |
| "uuid-550e8400-e29b-41d4-a716-446655440000", | |
| "cursor/project/session-1", | |
| "claude-code:workspace:12345", | |
| ] | |
| sessions = [await registry.get_or_create(session_id) for session_id in ids] | |
| assert [session.session_id for session in sessions] == ids | |
| assert len({id(session.runner) for session in sessions}) == len(ids) | |