@@ -42,7 +42,7 @@ Let's build your first MCP server in Python! We'll create a weather server that
42
42
43
43
<Step title = " Install additional dependencies" >
44
44
``` bash
45
- uv add requests python-dotenv
45
+ uv add httpx python-dotenv
46
46
```
47
47
</Step >
48
48
@@ -69,7 +69,7 @@ Let's build your first MCP server in Python! We'll create a weather server that
69
69
from functools import lru_cache
70
70
from typing import Any
71
71
72
- import requests
72
+ import httpx
73
73
import asyncio
74
74
from dotenv import load_dotenv
75
75
from mcp.server import Server
@@ -108,20 +108,20 @@ Let's build your first MCP server in Python! We'll create a weather server that
108
108
Add this functionality:
109
109
110
110
``` python
111
- # Create reusable session
112
- http = requests.Session()
113
- http.params = {
111
+ # Create reusable params
112
+ http_params = {
114
113
" appid" : API_KEY ,
115
114
" units" : " metric"
116
115
}
117
116
118
117
async def fetch_weather (city : str ) -> dict[str , Any]:
119
- response = http.get(
120
- f " { API_BASE_URL } /weather " ,
121
- params = {" q" : city}
122
- )
123
- response.raise_for_status()
124
- data = response.json()
118
+ async with httpx.AsyncClient() as client:
119
+ response = await client.get(
120
+ f " { API_BASE_URL } /weather " ,
121
+ params = {" q" : city, ** http_params}
122
+ )
123
+ response.raise_for_status()
124
+ data = response.json()
125
125
126
126
return {
127
127
" temperature" : data[" main" ][" temp" ],
@@ -167,7 +167,7 @@ Let's build your first MCP server in Python! We'll create a weather server that
167
167
try :
168
168
weather_data = await fetch_weather(city)
169
169
return json.dumps(weather_data, indent = 2 )
170
- except requests.RequestException as e:
170
+ except httpx.HTTPError as e:
171
171
raise RuntimeError (f " Weather API error: { str (e)} " )
172
172
173
173
```
@@ -220,15 +220,17 @@ Let's build your first MCP server in Python! We'll create a weather server that
220
220
days = min (int (arguments.get(" days" , 3 )), 5 )
221
221
222
222
try :
223
- response = http.get(
224
- f " { API_BASE_URL } / { FORECAST_ENDPOINT } " ,
225
- params = {
226
- " q" : city,
227
- " cnt" : days * 8 # API returns 3-hour intervals
228
- }
229
- )
230
- response.raise_for_status()
231
- data = response.json()
223
+ async with httpx.AsyncClient() as client:
224
+ response = await client.get(
225
+ f " { API_BASE_URL } / { FORECAST_ENDPOINT } " ,
226
+ params = {
227
+ " q" : city,
228
+ " cnt" : days * 8 , # API returns 3-hour intervals
229
+ ** http_params,
230
+ }
231
+ )
232
+ response.raise_for_status()
233
+ data = response.json()
232
234
233
235
forecasts = []
234
236
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
245
247
text = json.dumps(forecasts, indent = 2 )
246
248
)
247
249
]
248
- except requests.RequestException as e:
250
+ except requests.HTTPError as e:
249
251
logger.error(f " Weather API error: { str (e)} " )
250
252
raise RuntimeError (f " Weather API error: { str (e)} " )
251
253
```
@@ -443,9 +445,10 @@ Let's build your first MCP server in Python! We'll create a weather server that
443
445
<Card title = " Error Handling" icon = " shield" >
444
446
``` python
445
447
try :
446
- response = self .http.get(... )
447
- response.raise_for_status()
448
- except requests.RequestException as e:
448
+ async with httpx.AsyncClient() as client:
449
+ response = await client.get(... , params = {... , ** http_params})
450
+ response.raise_for_status()
451
+ except requests.HTTPError as e:
449
452
raise McpError(
450
453
ErrorCode.INTERNAL_ERROR ,
451
454
f " API error: { str (e)} "
@@ -565,12 +568,13 @@ uvicorn.run(app, host="0.0.0.0", port=8000)
565
568
last_cache_time is None or
566
569
now - last_cache_time > cache_timeout):
567
570
568
- response = http.get(
569
- f " { API_BASE_URL } / { CURRENT_WEATHER_ENDPOINT } " ,
570
- params = {" q" : city}
571
- )
572
- response.raise_for_status()
573
- data = response.json()
571
+ async with httpx.AsyncClient() as client:
572
+ response = await client.get(
573
+ f " { API_BASE_URL } / { CURRENT_WEATHER_ENDPOINT } " ,
574
+ params = {" q" : city, ** http_params}
575
+ )
576
+ response.raise_for_status()
577
+ data = response.json()
574
578
575
579
cached_weather = {
576
580
" temperature" : data[" main" ][" temp" ],
@@ -674,6 +678,10 @@ uvicorn.run(app, host="0.0.0.0", port=8000)
674
678
DEFAULT_CITY
675
679
)
676
680
681
+ @pytest.fixture
682
+ def anyio_backend ():
683
+ return " asyncio"
684
+
677
685
@pytest.fixture
678
686
def mock_weather_response ():
679
687
return {
@@ -706,7 +714,7 @@ uvicorn.run(app, host="0.0.0.0", port=8000)
706
714
]
707
715
}
708
716
709
- @pytest.mark.asyncio
717
+ @pytest.mark.anyio
710
718
async def test_fetch_weather (mock_weather_response ):
711
719
with patch(' requests.Session.get' ) as mock_get:
712
720
mock_get.return_value.json.return_value = mock_weather_response
@@ -720,7 +728,7 @@ uvicorn.run(app, host="0.0.0.0", port=8000)
720
728
assert weather[" wind_speed" ] == 3.6
721
729
assert " timestamp" in weather
722
730
723
- @pytest.mark.asyncio
731
+ @pytest.mark.anyio
724
732
async def test_read_resource ():
725
733
with patch(' weather_service.server.fetch_weather' ) as mock_fetch:
726
734
mock_fetch.return_value = {
@@ -736,12 +744,26 @@ uvicorn.run(app, host="0.0.0.0", port=8000)
736
744
assert " temperature" in result
737
745
assert " clear sky" in result
738
746
739
- @pytest.mark.asyncio
747
+ @pytest.mark.anyio
740
748
async def test_call_tool (mock_forecast_response ):
741
- with patch(' weather_service.server.http.get' ) as mock_get:
742
- mock_get.return_value.json.return_value = mock_forecast_response
743
- mock_get.return_value.raise_for_status = Mock()
749
+ class Response ():
750
+ def raise_for_status (self ):
751
+ pass
752
+
753
+ def json (self ):
754
+ return nock_forecast_response
755
+
756
+ class AsyncClient ():
757
+ def __aenter__ (self ):
758
+ return self
759
+
760
+ async def __aexit__ (self , * exc_info ):
761
+ pass
762
+
763
+ async def get (self , * args , ** kwargs ):
764
+ return Response()
744
765
766
+ with patch(' httpx.AsyncClient' , new = AsyncClient) as mock_client:
745
767
result = await call_tool(" get_forecast" , {" city" : " London" , " days" : 2 })
746
768
747
769
assert len (result) == 1
@@ -751,14 +773,14 @@ uvicorn.run(app, host="0.0.0.0", port=8000)
751
773
assert forecast_data[0 ][" temperature" ] == 18.5
752
774
assert forecast_data[0 ][" conditions" ] == " sunny"
753
775
754
- @pytest.mark.asyncio
776
+ @pytest.mark.anyio
755
777
async def test_list_resources ():
756
778
resources = await list_resources()
757
779
assert len (resources) == 1
758
780
assert resources[0 ].name == f " Current weather in { DEFAULT_CITY } "
759
781
assert resources[0 ].mimeType == " application/json"
760
782
761
- @pytest.mark.asyncio
783
+ @pytest.mark.anyio
762
784
async def test_list_tools ():
763
785
tools = await list_tools()
764
786
assert len (tools) == 1
@@ -768,7 +790,7 @@ uvicorn.run(app, host="0.0.0.0", port=8000)
768
790
</Step >
769
791
<Step title = " Run tests" >
770
792
``` bash
771
- uv add --dev pytest pytest-asyncio
793
+ uv add --dev pytest
772
794
uv run pytest
773
795
```
774
796
</Step >
0 commit comments