Skip to main content
Glama
test-template.j211.3 kB
# Test Template for MCP Migration Fixes # Generates pytest tests for each fix """Tests for {{ fix_name }}.""" import pytest {% if fix_id in ["fix-3-error-messages", "fix-4-response-formats", "imp-3-character-limit"] %} from mcp_stdio_server import ( {% if fix_id == "fix-3-error-messages" %} create_error_response, create_http_error_response, {% endif %} {% if fix_id == "fix-4-response-formats" %} format_property_markdown, format_properties_list_markdown, format_booking_markdown, {% endif %} {% if fix_id == "imp-3-character-limit" %} truncate_response, CHARACTER_LIMIT, {% endif %} ) {% endif %} {# Fix 1: Service Prefixes #} {% if fix_id == "fix-1-service-prefixes" %} def test_tool_names_have_hostaway_prefix(): """Verify all tool names have 'hostaway_' prefix.""" from mcp_stdio_server import list_tools import asyncio tools = asyncio.run(list_tools()) for tool in tools: assert tool.name.startswith("hostaway_"), f"Tool {tool.name} missing prefix" expected_names = [ "hostaway_list_properties", "hostaway_get_property_details", "hostaway_check_availability", "hostaway_search_bookings", "hostaway_get_booking_details", "hostaway_get_guest_info", "hostaway_get_financial_reports" ] actual_names = [tool.name for tool in tools] assert actual_names == expected_names def test_tool_routing_uses_prefixed_names(): """Verify call_tool function handles prefixed names.""" from mcp_stdio_server import call_tool import asyncio # Test with valid prefixed name (mock API call) # This would need proper mocking in real test pass {% endif %} {# Fix 2: Tool Annotations #} {% if fix_id == "fix-2-tool-annotations" %} def test_all_tools_have_annotations(): """Verify all tools have required annotations.""" from mcp_stdio_server import list_tools import asyncio tools = asyncio.run(list_tools()) required_annotations = ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"] for tool in tools: assert tool.annotations is not None, f"Tool {tool.name} missing annotations" for annotation in required_annotations: assert annotation in tool.annotations, f"Tool {tool.name} missing {annotation}" def test_read_only_annotations(): """Verify all tools are marked as read-only.""" from mcp_stdio_server import list_tools import asyncio tools = asyncio.run(list_tools()) for tool in tools: assert tool.annotations["readOnlyHint"] is True, f"Tool {tool.name} should be read-only" assert tool.annotations["destructiveHint"] is False, f"Tool {tool.name} should not be destructive" def test_open_world_annotations(): """Verify all tools are marked as open-world (external API).""" from mcp_stdio_server import list_tools import asyncio tools = asyncio.run(list_tools()) for tool in tools: assert tool.annotations["openWorldHint"] is True, f"Tool {tool.name} should be open-world" def test_financial_reports_not_idempotent(): """Verify financial reports marked as non-idempotent.""" from mcp_stdio_server import list_tools import asyncio tools = asyncio.run(list_tools()) financial_tool = next(t for t in tools if t.name == "hostaway_get_financial_reports") assert financial_tool.annotations["idempotentHint"] is False, "Financial reports should not be idempotent" {% endif %} {# Fix 3: Error Messages #} {% if fix_id == "fix-3-error-messages" %} def test_create_error_response(): """Test error response formatting.""" response = create_error_response("Test error") assert len(response) == 1 assert response[0].type == "text" assert "ERROR:" in response[0].text assert "Test error" in response[0].text def test_create_http_error_401(): """Test 401 error handling.""" import httpx # Mock 401 error mock_response = httpx.Response(401, request=httpx.Request("GET", "http://test")) error = httpx.HTTPStatusError("Unauthorized", request=mock_response.request, response=mock_response) response = create_http_error_response(error, "testing authentication") assert len(response) == 1 assert "Authentication failed" in response[0].text assert "REMOTE_MCP_API_KEY" in response[0].text def test_create_http_error_404(): """Test 404 error handling.""" import httpx mock_response = httpx.Response(404, request=httpx.Request("GET", "http://test")) error = httpx.HTTPStatusError("Not Found", request=mock_response.request, response=mock_response) response = create_http_error_response(error, "fetching property") assert len(response) == 1 assert "Resource not found" in response[0].text assert "hostaway_list_properties" in response[0].text # Suggests alternative def test_create_http_error_429(): """Test rate limit error handling.""" import httpx mock_response = httpx.Response(429, request=httpx.Request("GET", "http://test")) error = httpx.HTTPStatusError("Too Many Requests", request=mock_response.request, response=mock_response) response = create_http_error_response(error, "listing properties") assert len(response) == 1 assert "Rate limit exceeded" in response[0].text assert "Wait 10 seconds" in response[0].text def test_create_http_error_timeout(): """Test timeout error handling.""" import httpx error = httpx.TimeoutException("Connection timeout") response = create_http_error_response(error, "fetching data") assert len(response) == 1 assert "timeout" in response[0].text.lower() assert "30 seconds" in response[0].text {% endif %} {# Fix 4: Response Formats #} {% if fix_id == "fix-4-response-formats" %} def test_format_property_markdown(): """Test property markdown formatting.""" property_data = { "id": 12345, "name": "Test Villa", "city": "Ubud", "country": "Indonesia", "bedrooms": 3, "status": "Available" } markdown = format_property_markdown(property_data) assert "Test Villa" in markdown assert "12345" in markdown assert "Ubud, Indonesia" in markdown assert "3" in markdown assert "Available" in markdown def test_format_properties_list_markdown(): """Test properties list markdown formatting.""" data = { "items": [ {"id": 1, "name": "Villa 1", "city": "Ubud", "country": "Indonesia", "bedrooms": 3, "status": "Available"}, {"id": 2, "name": "Villa 2", "city": "Seminyak", "country": "Indonesia", "bedrooms": 4, "status": "Available"} ], "meta": {"hasMore": True}, "nextCursor": "abc123" } markdown = format_properties_list_markdown(data) assert "# Properties (2 results)" in markdown assert "Villa 1" in markdown assert "Villa 2" in markdown assert "More results available" in markdown def test_response_format_parameter(): """Test that tools accept response_format parameter.""" from mcp_stdio_server import list_tools import asyncio tools = asyncio.run(list_tools()) for tool in tools: if tool.name in ["hostaway_list_properties", "hostaway_search_bookings"]: schema = tool.inputSchema assert "response_format" in schema["properties"] assert schema["properties"]["response_format"]["type"] == "string" assert "markdown" in schema["properties"]["response_format"]["enum"] assert "json" in schema["properties"]["response_format"]["enum"] {% endif %} {# Improvement 1: Tool Descriptions #} {% if fix_id == "imp-1-tool-descriptions" %} def test_tool_descriptions_have_usage_examples(): """Verify tool descriptions include usage examples.""" from mcp_stdio_server import list_tools import asyncio tools = asyncio.run(list_tools()) for tool in tools: description = tool.description assert len(description) > 100, f"Tool {tool.name} description too short" assert "When to use" in description or "Usage examples" in description def test_tool_descriptions_have_when_not_to_use(): """Verify descriptions guide users on when NOT to use tool.""" from mcp_stdio_server import list_tools import asyncio tools = asyncio.run(list_tools()) # Check list tools have "when NOT to use" for tool in tools: if "list" in tool.name or "search" in tool.name: assert "NOT to use" in tool.description {% endif %} {# Improvement 2: Input Validation #} {% if fix_id == "imp-2-input-validation" %} def test_input_schemas_have_constraints(): """Verify input schemas have min/max constraints.""" from mcp_stdio_server import list_tools import asyncio tools = asyncio.run(list_tools()) for tool in tools: schema = tool.inputSchema properties = schema.get("properties", {}) # Check numeric fields have constraints for prop_name, prop_schema in properties.items(): if prop_schema.get("type") == "integer": assert "minimum" in prop_schema, f"{tool.name}.{prop_name} missing minimum" if prop_name == "limit": assert "maximum" in prop_schema, f"{tool.name}.{prop_name} missing maximum" def test_input_schemas_have_examples(): """Verify input schemas include examples.""" from mcp_stdio_server import list_tools import asyncio tools = asyncio.run(list_tools()) for tool in tools: schema = tool.inputSchema properties = schema.get("properties", {}) # At least one property should have examples has_examples = any("examples" in prop_schema for prop_schema in properties.values()) assert has_examples, f"Tool {tool.name} has no examples in schema" def test_schemas_reject_additional_properties(): """Verify schemas set additionalProperties: false.""" from mcp_stdio_server import list_tools import asyncio tools = asyncio.run(list_tools()) for tool in tools: schema = tool.inputSchema assert schema.get("additionalProperties") is False, f"{tool.name} allows additional properties" {% endif %} {# Improvement 3: CHARACTER_LIMIT #} {% if fix_id == "imp-3-character-limit" %} def test_character_limit_constant(): """Verify CHARACTER_LIMIT is set to 25000.""" assert CHARACTER_LIMIT == 25000 def test_truncate_response_under_limit(): """Test truncation with text under limit.""" text = "Short text" result = truncate_response(text, limit=1000) assert result == text assert "Truncated" not in result def test_truncate_response_over_limit(): """Test truncation with text over limit.""" text = "a" * 30000 result = truncate_response(text, limit=25000) assert len(result) <= 25000 assert "Response Truncated" in result assert "To see more results" in result def test_truncate_response_includes_guidance(): """Verify truncation includes helpful guidance.""" text = "x" * 30000 result = truncate_response(text, limit=25000) assert "Reduce the `limit` parameter" in result assert "Add filters" in result assert "Use cursor pagination" in result {% endif %}

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/darrentmorgan/hostaway-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server