diff --git a/docs/first-server/python.mdx b/docs/first-server/python.mdx index 1e8b966..54c1305 100644 --- a/docs/first-server/python.mdx +++ b/docs/first-server/python.mdx @@ -42,7 +42,7 @@ Let's build your first MCP server in Python! We'll create a weather server that ```bash - uv add requests python-dotenv + uv add httpx python-dotenv ``` @@ -69,8 +69,8 @@ Let's build your first MCP server in Python! We'll create a weather server that from functools import lru_cache from typing import Any - import requests - import asyncio + import httpx + import anyio from dotenv import load_dotenv from mcp.server import Server from mcp.types import ( @@ -108,20 +108,20 @@ Let's build your first MCP server in Python! We'll create a weather server that Add this functionality: ```python - # Create reusable session - http = requests.Session() - http.params = { + # Create reusable params + http_params = { "appid": API_KEY, "units": "metric" } async def fetch_weather(city: str) -> dict[str, Any]: - response = http.get( - f"{API_BASE_URL}/weather", - params={"q": city} - ) - response.raise_for_status() - data = response.json() + async with httpx.AsyncClient() as client: + response = await client.get( + f"{API_BASE_URL}/weather", + params={"q": city, **http_params} + ) + response.raise_for_status() + data = response.json() return { "temperature": data["main"]["temp"], @@ -167,7 +167,7 @@ Let's build your first MCP server in Python! We'll create a weather server that try: weather_data = await fetch_weather(city) return json.dumps(weather_data, indent=2) - except requests.RequestException as e: + except httpx.HTTPError as e: raise RuntimeError(f"Weather API error: {str(e)}") ``` @@ -220,15 +220,17 @@ Let's build your first MCP server in Python! We'll create a weather server that days = min(int(arguments.get("days", 3)), 5) try: - response = http.get( - f"{API_BASE_URL}/{FORECAST_ENDPOINT}", - params={ - "q": city, - "cnt": days * 8 # API returns 3-hour intervals - } - ) - response.raise_for_status() - data = response.json() + async with httpx.AsyncClient() as client: + response = await client.get( + f"{API_BASE_URL}/{FORECAST_ENDPOINT}", + params={ + "q": city, + "cnt": days * 8, # API returns 3-hour intervals + **http_params, + } + ) + response.raise_for_status() + data = response.json() forecasts = [] for i in range(0, len(data["list"]), 8): @@ -245,7 +247,7 @@ Let's build your first MCP server in Python! We'll create a weather server that text=json.dumps(forecasts, indent=2) ) ] - except requests.RequestException as e: + except requests.HTTPError as e: logger.error(f"Weather API error: {str(e)}") raise RuntimeError(f"Weather API error: {str(e)}") ``` @@ -273,11 +275,11 @@ Let's build your first MCP server in Python! We'll create a weather server that ```python from . import server - import asyncio + import anyio def main(): """Main entry point for the package.""" - asyncio.run(server.main()) + anyio.run(server.main) # Optionally expose other important items at package level __all__ = ['main', 'server'] @@ -443,9 +445,10 @@ Let's build your first MCP server in Python! We'll create a weather server that ```python try: - response = self.http.get(...) - response.raise_for_status() - except requests.RequestException as e: + async with httpx.AsyncClient() as client: + response = await client.get(..., params={..., **http_params}) + response.raise_for_status() + except requests.HTTPError as e: raise McpError( ErrorCode.INTERNAL_ERROR, f"API error: {str(e)}" @@ -565,12 +568,13 @@ uvicorn.run(app, host="0.0.0.0", port=8000) last_cache_time is None or now - last_cache_time > cache_timeout): - response = http.get( - f"{API_BASE_URL}/{CURRENT_WEATHER_ENDPOINT}", - params={"q": city} - ) - response.raise_for_status() - data = response.json() + async with httpx.AsyncClient() as client: + response = await client.get( + f"{API_BASE_URL}/{CURRENT_WEATHER_ENDPOINT}", + params={"q": city, **http_params} + ) + response.raise_for_status() + data = response.json() cached_weather = { "temperature": data["main"]["temp"], @@ -674,6 +678,10 @@ uvicorn.run(app, host="0.0.0.0", port=8000) DEFAULT_CITY ) + @pytest.fixture + def anyio_backend(): + return "asyncio" + @pytest.fixture def mock_weather_response(): return { @@ -706,7 +714,7 @@ uvicorn.run(app, host="0.0.0.0", port=8000) ] } - @pytest.mark.asyncio + @pytest.mark.anyio async def test_fetch_weather(mock_weather_response): with patch('requests.Session.get') as mock_get: mock_get.return_value.json.return_value = mock_weather_response @@ -720,7 +728,7 @@ uvicorn.run(app, host="0.0.0.0", port=8000) assert weather["wind_speed"] == 3.6 assert "timestamp" in weather - @pytest.mark.asyncio + @pytest.mark.anyio async def test_read_resource(): with patch('weather_service.server.fetch_weather') as mock_fetch: mock_fetch.return_value = { @@ -736,12 +744,26 @@ uvicorn.run(app, host="0.0.0.0", port=8000) assert "temperature" in result assert "clear sky" in result - @pytest.mark.asyncio + @pytest.mark.anyio async def test_call_tool(mock_forecast_response): - with patch('weather_service.server.http.get') as mock_get: - mock_get.return_value.json.return_value = mock_forecast_response - mock_get.return_value.raise_for_status = Mock() + class Response(): + def raise_for_status(self): + pass + def json(self): + return nock_forecast_response + + class AsyncClient(): + def __aenter__(self): + return self + + async def __aexit__(self, *exc_info): + pass + + async def get(self, *args, **kwargs): + return Response() + + with patch('httpx.AsyncClient', new=AsyncClient) as mock_client: result = await call_tool("get_forecast", {"city": "London", "days": 2}) assert len(result) == 1 @@ -751,14 +773,14 @@ uvicorn.run(app, host="0.0.0.0", port=8000) assert forecast_data[0]["temperature"] == 18.5 assert forecast_data[0]["conditions"] == "sunny" - @pytest.mark.asyncio + @pytest.mark.anyio async def test_list_resources(): resources = await list_resources() assert len(resources) == 1 assert resources[0].name == f"Current weather in {DEFAULT_CITY}" assert resources[0].mimeType == "application/json" - @pytest.mark.asyncio + @pytest.mark.anyio async def test_list_tools(): tools = await list_tools() assert len(tools) == 1 @@ -768,7 +790,7 @@ uvicorn.run(app, host="0.0.0.0", port=8000) ```bash - uv add --dev pytest pytest-asyncio + uv add --dev pytest uv run pytest ```