awslabs.cloudwatch-mcp-server 0.0.8__tar.gz → 0.0.11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/Dockerfile +2 -2
  2. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/PKG-INFO +30 -1
  3. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/README.md +29 -0
  4. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/awslabs/cloudwatch_mcp_server/cloudwatch_logs/tools.py +59 -10
  5. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/pyproject.toml +1 -1
  6. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/cloudwatch_logs/test_logs_error_handling.py +72 -17
  7. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/cloudwatch_logs/test_logs_server.py +15 -11
  8. awslabs_cloudwatch_mcp_server-0.0.11/uv-requirements.txt +24 -0
  9. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/uv.lock +695 -695
  10. awslabs_cloudwatch_mcp_server-0.0.8/uv-requirements.txt +0 -26
  11. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/.gitignore +0 -0
  12. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/.python-version +0 -0
  13. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/CHANGELOG.md +0 -0
  14. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/LICENSE +0 -0
  15. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/NOTICE +0 -0
  16. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/awslabs/__init__.py +0 -0
  17. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/awslabs/cloudwatch_mcp_server/__init__.py +0 -0
  18. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/awslabs/cloudwatch_mcp_server/cloudwatch_alarms/models.py +0 -0
  19. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/awslabs/cloudwatch_mcp_server/cloudwatch_alarms/tools.py +0 -0
  20. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/awslabs/cloudwatch_mcp_server/cloudwatch_logs/models.py +0 -0
  21. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/awslabs/cloudwatch_mcp_server/cloudwatch_metrics/data/metric_metadata.json +0 -0
  22. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/awslabs/cloudwatch_mcp_server/cloudwatch_metrics/models.py +0 -0
  23. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/awslabs/cloudwatch_mcp_server/cloudwatch_metrics/tools.py +0 -0
  24. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/awslabs/cloudwatch_mcp_server/common.py +0 -0
  25. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/awslabs/cloudwatch_mcp_server/server.py +0 -0
  26. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/docker-healthcheck.sh +0 -0
  27. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/cloudwatch_alarms/test_active_alarms.py +0 -0
  28. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/cloudwatch_alarms/test_alarm_history.py +0 -0
  29. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/cloudwatch_alarms/test_alarm_history_integration.py +0 -0
  30. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/cloudwatch_alarms/test_alarms_error_handling.py +0 -0
  31. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/cloudwatch_logs/test_logs_models.py +0 -0
  32. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/cloudwatch_metrics/test_metrics_error_handling.py +0 -0
  33. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/cloudwatch_metrics/test_metrics_models.py +0 -0
  34. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/cloudwatch_metrics/test_metrics_server.py +0 -0
  35. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/cloudwatch_metrics/test_validation_error.py +0 -0
  36. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/test_common_and_server.py +0 -0
  37. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/test_init.py +0 -0
  38. {awslabs_cloudwatch_mcp_server-0.0.8 → awslabs_cloudwatch_mcp_server-0.0.11}/tests/test_main.py +0 -0
@@ -13,7 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
  # dependabot should continue to update this to the latest hash.
16
- FROM public.ecr.aws/docker/library/python:3.13.5-alpine3.21@sha256:c9a09c45a4bcc618c7f7128585b8dd0d41d0c31a8a107db4c8255ffe0b69375d AS uv
16
+ FROM public.ecr.aws/docker/library/python:3.13-alpine@sha256:070342a0cc1011532c0e69972cce2bbc6cc633eba294bae1d12abea8bd05303b AS uv
17
17
 
18
18
  # Install the project into `/app`
19
19
  WORKDIR /app
@@ -61,7 +61,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
61
61
  # Make the directory just in case it doesn't exist
62
62
  RUN mkdir -p /root/.local
63
63
 
64
- FROM public.ecr.aws/docker/library/python:3.13.5-alpine3.21@sha256:c9a09c45a4bcc618c7f7128585b8dd0d41d0c31a8a107db4c8255ffe0b69375d
64
+ FROM public.ecr.aws/docker/library/python:3.13-alpine@sha256:070342a0cc1011532c0e69972cce2bbc6cc633eba294bae1d12abea8bd05303b
65
65
 
66
66
  # Place executables in the environment at the front of the path and include other binaries
67
67
  ENV PATH="/app/.venv/bin:$PATH" \
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awslabs.cloudwatch-mcp-server
3
- Version: 0.0.8
3
+ Version: 0.0.11
4
4
  Summary: An AWS Labs Model Context Protocol (MCP) server for cloudwatch
5
5
  Project-URL: homepage, https://awslabs.github.io/mcp/
6
6
  Project-URL: docs, https://awslabs.github.io/mcp/servers/cloudwatch-mcp-server/
@@ -119,6 +119,35 @@ Alarm Recommendations - Suggests recommended alarm configurations for CloudWatch
119
119
  }
120
120
  }
121
121
  ```
122
+ ### Windows Installation
123
+
124
+ For Windows users, the MCP server configuration format is slightly different:
125
+
126
+ ```json
127
+ {
128
+ "mcpServers": {
129
+ "awslabs.cloudwatch-mcp-server": {
130
+ "disabled": false,
131
+ "timeout": 60,
132
+ "type": "stdio",
133
+ "command": "uv",
134
+ "args": [
135
+ "tool",
136
+ "run",
137
+ "--from",
138
+ "awslabs.cloudwatch-mcp-server@latest",
139
+ "awslabs.cloudwatch-mcp-server.exe"
140
+ ],
141
+ "env": {
142
+ "FASTMCP_LOG_LEVEL": "ERROR",
143
+ "AWS_PROFILE": "your-aws-profile",
144
+ "AWS_REGION": "us-east-1"
145
+ }
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
122
151
 
123
152
  Please reference [AWS documentation](https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html) to create and manage your credentials profile
124
153
 
@@ -90,6 +90,35 @@ Alarm Recommendations - Suggests recommended alarm configurations for CloudWatch
90
90
  }
91
91
  }
92
92
  ```
93
+ ### Windows Installation
94
+
95
+ For Windows users, the MCP server configuration format is slightly different:
96
+
97
+ ```json
98
+ {
99
+ "mcpServers": {
100
+ "awslabs.cloudwatch-mcp-server": {
101
+ "disabled": false,
102
+ "timeout": 60,
103
+ "type": "stdio",
104
+ "command": "uv",
105
+ "args": [
106
+ "tool",
107
+ "run",
108
+ "--from",
109
+ "awslabs.cloudwatch-mcp-server@latest",
110
+ "awslabs.cloudwatch-mcp-server.exe"
111
+ ],
112
+ "env": {
113
+ "FASTMCP_LOG_LEVEL": "ERROR",
114
+ "AWS_PROFILE": "your-aws-profile",
115
+ "AWS_REGION": "us-east-1"
116
+ }
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
93
122
 
94
123
  Please reference [AWS documentation](https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html) to create and manage your credentials profile
95
124
 
@@ -168,12 +168,37 @@ class CloudWatchLogsTools:
168
168
  """
169
169
  poll_start = timer()
170
170
  while poll_start + max_timeout > timer():
171
- response = logs_client.get_query_results(queryId=query_id)
172
- status = response['status']
171
+ try:
172
+ response = logs_client.get_query_results(queryId=query_id)
173
+ status = response['status']
174
+
175
+ logger.debug(f'Query {query_id} status: {status}')
176
+
177
+ if status in {'Complete', 'Failed', 'Cancelled'}:
178
+ logger.info(f'Query {query_id} finished with status {status}')
179
+ result = self._process_query_results(response, query_id)
180
+
181
+ # Handle case where query completed but returned no results
182
+ if status == 'Complete' and not result.get('results'):
183
+ logger.info(f'Query {query_id} completed but returned no results')
184
+ result['results'] = []
185
+
186
+ return result
187
+
188
+ # Handle unexpected status states
189
+ if status not in {'Scheduled', 'Running'}:
190
+ logger.warning(f'Query {query_id} has unexpected status: {status}')
191
+ return self._process_query_results(response, query_id)
173
192
 
174
- if status in {'Complete', 'Failed', 'Cancelled'}:
175
- logger.info(f'Query {query_id} finished with status {status}')
176
- return self._process_query_results(response, query_id)
193
+ except Exception as e:
194
+ logger.error(f'Error polling for query {query_id} completion: {str(e)}')
195
+ await ctx.error(f'Error during query polling: {str(e)}')
196
+ return {
197
+ 'queryId': query_id,
198
+ 'status': 'Error',
199
+ 'message': f'Error occurred while polling: {str(e)}',
200
+ 'results': [],
201
+ }
177
202
 
178
203
  await asyncio.sleep(1)
179
204
 
@@ -184,6 +209,7 @@ class CloudWatchLogsTools:
184
209
  'queryId': query_id,
185
210
  'status': 'Polling Timeout',
186
211
  'message': msg,
212
+ 'results': [],
187
213
  }
188
214
 
189
215
  def register(self, mcp):
@@ -576,8 +602,16 @@ class CloudWatchLogsTools:
576
602
 
577
603
  except Exception as e:
578
604
  logger.error(f'Error in execute_log_insights_query_tool: {str(e)}')
579
- await ctx.error(f'Error executing CloudWatch Logs Insights query: {str(e)}')
580
- raise
605
+ error_msg = f'Error executing CloudWatch Logs Insights query: {str(e)}'
606
+ await ctx.error(error_msg)
607
+
608
+ # Instead of raising, return a consistent error result
609
+ return {
610
+ 'queryId': '',
611
+ 'status': 'Error',
612
+ 'message': error_msg,
613
+ 'results': [],
614
+ }
581
615
 
582
616
  async def get_logs_insight_query_results(
583
617
  self,
@@ -612,11 +646,26 @@ class CloudWatchLogsTools:
612
646
 
613
647
  logger.info(f'Retrieved results for query ID {query_id}')
614
648
 
615
- return self._process_query_results(response, query_id)
649
+ result = self._process_query_results(response, query_id)
650
+
651
+ # Ensure results is always an array, even if empty
652
+ if not result.get('results'):
653
+ result['results'] = []
654
+
655
+ return result
656
+
616
657
  except Exception as e:
617
658
  logger.error(f'Error in get_query_results_tool: {str(e)}')
618
- await ctx.error(f'Error retrieving CloudWatch Logs Insights query results: {str(e)}')
619
- raise
659
+ error_msg = f'Error retrieving CloudWatch Logs Insights query results: {str(e)}'
660
+ await ctx.error(error_msg)
661
+
662
+ # Return consistent error structure instead of raising
663
+ return {
664
+ 'queryId': query_id,
665
+ 'status': 'Error',
666
+ 'message': error_msg,
667
+ 'results': [],
668
+ }
620
669
 
621
670
  async def cancel_logs_insight_query(
622
671
  self,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "awslabs.cloudwatch-mcp-server"
3
- version = "0.0.8"
3
+ version = "0.0.11"
4
4
  description = "An AWS Labs Model Context Protocol (MCP) server for cloudwatch"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -185,19 +185,22 @@ class TestErrorHandling:
185
185
 
186
186
  tools = CloudWatchLogsTools()
187
187
 
188
- with pytest.raises(Exception) as exc_info:
189
- await tools.execute_log_insights_query(
190
- mock_context,
191
- log_group_names=['test-group'],
192
- log_group_identifiers=None,
193
- start_time='2023-01-01T00:00:00+00:00',
194
- end_time='2023-01-01T01:00:00+00:00',
195
- query_string='fields @message',
196
- limit=10,
197
- max_timeout=30,
198
- )
188
+ result = await tools.execute_log_insights_query(
189
+ mock_context,
190
+ log_group_names=['test-group'],
191
+ log_group_identifiers=None,
192
+ start_time='2023-01-01T00:00:00+00:00',
193
+ end_time='2023-01-01T01:00:00+00:00',
194
+ query_string='fields @message',
195
+ limit=10,
196
+ max_timeout=30,
197
+ )
199
198
 
200
- assert 'Query API Error' in str(exc_info.value)
199
+ # Verify error response structure instead of exception
200
+ assert result['status'] == 'Error'
201
+ assert result['results'] == []
202
+ assert 'Query API Error' in result['message']
203
+ assert result['queryId'] == ''
201
204
  mock_context.error.assert_called_once()
202
205
 
203
206
  @pytest.mark.asyncio
@@ -212,10 +215,15 @@ class TestErrorHandling:
212
215
 
213
216
  tools = CloudWatchLogsTools()
214
217
 
215
- with pytest.raises(Exception) as exc_info:
216
- await tools.get_logs_insight_query_results(mock_context, query_id='test-query-id')
218
+ result = await tools.get_logs_insight_query_results(
219
+ mock_context, query_id='test-query-id'
220
+ )
217
221
 
218
- assert 'Query Results API Error' in str(exc_info.value)
222
+ # Verify error response structure instead of exception
223
+ assert result['status'] == 'Error'
224
+ assert result['results'] == []
225
+ assert 'Query Results API Error' in result['message']
226
+ assert result['queryId'] == 'test-query-id'
219
227
  mock_context.error.assert_called_once()
220
228
 
221
229
  @pytest.mark.asyncio
@@ -296,6 +304,54 @@ class TestErrorHandling:
296
304
  assert result['queryId'] == 'test-query-id'
297
305
  assert result['status'] == 'Cancelled'
298
306
 
307
+ @pytest.mark.asyncio
308
+ async def test_poll_for_query_completion_unexpected_status(self, mock_context):
309
+ """Test polling with unexpected query status."""
310
+ with patch(
311
+ 'awslabs.cloudwatch_mcp_server.cloudwatch_logs.tools.boto3.Session'
312
+ ) as mock_session:
313
+ mock_client = Mock()
314
+ mock_client.get_query_results.return_value = {
315
+ 'queryId': 'test-query-id',
316
+ 'status': 'UnknownStatus', # Unexpected status
317
+ 'results': [],
318
+ }
319
+ mock_session.return_value.client.return_value = mock_client
320
+ tools = CloudWatchLogsTools()
321
+ result = await tools._poll_for_query_completion(
322
+ mock_client, 'test-query-id', 30, mock_context
323
+ )
324
+ assert result['queryId'] == 'test-query-id'
325
+ assert result['status'] == 'UnknownStatus'
326
+ assert result['results'] == []
327
+
328
+ @pytest.mark.asyncio
329
+ async def test_poll_for_query_completion_polling_exception(self, mock_context):
330
+ """Test polling with exception during get_query_results."""
331
+ with patch(
332
+ 'awslabs.cloudwatch_mcp_server.cloudwatch_logs.tools.boto3.Session'
333
+ ) as mock_session:
334
+ mock_client = Mock()
335
+ mock_client.get_query_results.side_effect = Exception('Network timeout during polling')
336
+ mock_session.return_value.client.return_value = mock_client
337
+ tools = CloudWatchLogsTools()
338
+
339
+ result = await tools._poll_for_query_completion(
340
+ mock_client, 'test-query-id', 30, mock_context
341
+ )
342
+
343
+ # Verify error response structure
344
+ assert result['queryId'] == 'test-query-id'
345
+ assert result['status'] == 'Error'
346
+ assert 'Network timeout during polling' in result['message']
347
+ assert result['results'] == []
348
+
349
+ # Verify context error was called
350
+ mock_context.error.assert_called_once()
351
+ error_call_args = mock_context.error.call_args[0][0]
352
+ assert 'Error during query polling' in error_call_args
353
+ assert 'Network timeout during polling' in error_call_args
354
+
299
355
 
300
356
  class TestEdgeCases:
301
357
  """Test edge cases and boundary conditions."""
@@ -308,14 +364,13 @@ class TestEdgeCases:
308
364
  # Response with minimal fields
309
365
  raw_response = {
310
366
  'status': 'Complete',
311
- # Missing queryId, statistics, results
367
+ # Missing queryId, results
312
368
  }
313
369
 
314
370
  processed = tools._process_query_results(raw_response, 'fallback-id')
315
371
 
316
372
  assert processed['queryId'] == 'fallback-id'
317
373
  assert processed['status'] == 'Complete'
318
- assert processed['statistics'] == {}
319
374
  assert processed['results'] == []
320
375
 
321
376
  def test_aws_profile_initialization(self):
@@ -265,17 +265,21 @@ class TestExecuteLogInsightsQuery:
265
265
 
266
266
  async def test_invalid_parameters(self, ctx, cloudwatch_tools):
267
267
  """Test invalid parameter handling."""
268
- with pytest.raises(Exception):
269
- await cloudwatch_tools.execute_log_insights_query(
270
- ctx,
271
- log_group_names=['/aws/test/group1'],
272
- log_group_identifiers=['/aws/test/group1'], # Both provided - should fail
273
- start_time='2023-01-01T00:00:00+00:00',
274
- end_time='2023-01-01T01:00:00+00:00',
275
- query_string='fields @timestamp, @message | limit 10',
276
- limit=10,
277
- max_timeout=30,
278
- )
268
+ result = await cloudwatch_tools.execute_log_insights_query(
269
+ ctx,
270
+ log_group_names=['/aws/test/group1'],
271
+ log_group_identifiers=['/aws/test/group1'], # Both provided - should fail
272
+ start_time='2023-01-01T00:00:00+00:00',
273
+ end_time='2023-01-01T01:00:00+00:00',
274
+ query_string='fields @timestamp, @message | limit 10',
275
+ limit=10,
276
+ max_timeout=30,
277
+ )
278
+
279
+ # Verify error response structure instead of exception
280
+ assert result['status'] == 'Error'
281
+ assert result['results'] == []
282
+ assert 'Error executing CloudWatch Logs Insights query' in result['message']
279
283
 
280
284
 
281
285
  @pytest.mark.asyncio
@@ -0,0 +1,24 @@
1
+ # This file was autogenerated by uv via the following command:
2
+ # echo "uv==0.8.10" > uv-requirements.in
3
+ # uv pip compile --generate-hashes --output-file=uv-requirements.txt --strip-extras --python=3.10 uv-requirements.in
4
+ uv==0.8.10 \
5
+ --hash=sha256:31e4fc37ee94b94c032384a0957ad32ba7dce4ce6c04b4880fd3e31e25e51a82 \
6
+ --hash=sha256:36a5ce708d52388c37043e7335f9eb3fea5a19a56166a2cc6adb365179a1cd77 \
7
+ --hash=sha256:38286d230daad82388469c8dc7a1d2f5dc279c11178319c886d1a88d7938e513 \
8
+ --hash=sha256:3e190cee3bb2b4f574a419eef87ae8e33f713e9cd6f856b83277ece70ad9ca9b \
9
+ --hash=sha256:3fdf89fc40af9902141c39ed943bcfca15664623363335eb032a44f22001e2b4 \
10
+ --hash=sha256:4cc190d403a89e46d13cec83b6f8e8d7d07aaf1e5a996eac9a3f0c2a8cd92537 \
11
+ --hash=sha256:57b71dc79eff25a5419d3fe4a563d3b9397f55d789f685ef27f43f033b31f482 \
12
+ --hash=sha256:86fe044c2be43977566a0d184a487edd7aace2febb757fd95927684b629ef50b \
13
+ --hash=sha256:88df34c32555064fae459cce665757619fd1af7deb2dc393352b15d909d2d131 \
14
+ --hash=sha256:9ad21eeaa4156a1bf5ed85903f80db06e2c02badd3a587ba98d3171517960555 \
15
+ --hash=sha256:a5495b5a6e3111c03cf5e4dbdd598bc8fd1da887e3920d58cd5a2d4c8bc9a473 \
16
+ --hash=sha256:ab072cd3bf2f9dc264659a1ff48ad91a910ac4830bcfe965e2d3f89c86646f46 \
17
+ --hash=sha256:af8a5526b0e331775a264fa0dbccfd53c183cb974f269a208af136d7561f9eb2 \
18
+ --hash=sha256:b00637c63d5dfc9f879281c5c91db2bb909ab1f9ab275dab015e7fb6cac6be5b \
19
+ --hash=sha256:b3ff3c451fcd23ea78356d8c18e802d0e423cbe655273601e3ec039a51b33286 \
20
+ --hash=sha256:c4a493cd4b15b3aef11523531aff96a77a586666a63e842fa437966b7b7ee62d \
21
+ --hash=sha256:defc50bb319be2d58be74a680710cd4b7697e88d5f79974eacd354df95f0b6b0 \
22
+ --hash=sha256:e0a02bcec766eb0862b7082ab746b204add7d9fcaa62322502d159b5a7ccc54a \
23
+ --hash=sha256:eb79a46d8099f563ef58237bf4e9009f876a40145e757ea883a92b24b724d01e
24
+ # via -r uv-requirements.in