rootly-mcp-server 2.0.12__tar.gz → 2.0.13__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 (31) hide show
  1. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/PKG-INFO +1 -1
  2. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/pyproject.toml +1 -1
  3. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/src/rootly_mcp_server/server.py +69 -2
  4. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/tests/unit/test_server.py +90 -0
  5. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/uv.lock +1 -1
  6. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/.github/workflows/pypi-release.yml +0 -0
  7. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/.github/workflows/test.yml +0 -0
  8. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/.gitignore +0 -0
  9. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/.semaphore/deploy.yml +0 -0
  10. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/.semaphore/semaphore.yml +0 -0
  11. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/.semaphore/update-task-definition.sh +0 -0
  12. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/Dockerfile +0 -0
  13. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/LICENSE +0 -0
  14. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/README.md +0 -0
  15. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/rootly-mcp-server-demo.gif +0 -0
  16. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/rootly_openapi.json +0 -0
  17. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/src/rootly_mcp_server/__init__.py +0 -0
  18. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/src/rootly_mcp_server/__main__.py +0 -0
  19. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/src/rootly_mcp_server/client.py +0 -0
  20. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/src/rootly_mcp_server/data/__init__.py +0 -0
  21. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/src/rootly_mcp_server/smart_utils.py +0 -0
  22. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/src/rootly_mcp_server/utils.py +0 -0
  23. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/tests/README.md +0 -0
  24. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/tests/conftest.py +0 -0
  25. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/tests/integration/local/test_basic.py +0 -0
  26. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/tests/integration/local/test_smart_tools.py +0 -0
  27. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/tests/integration/remote/test_essential.py +0 -0
  28. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/tests/test_client.py +0 -0
  29. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/tests/unit/test_authentication.py +0 -0
  30. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/tests/unit/test_smart_utils.py +0 -0
  31. {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.13}/tests/unit/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rootly-mcp-server
3
- Version: 2.0.12
3
+ Version: 2.0.13
4
4
  Summary: A Model Context Protocol server for Rootly APIs using OpenAPI spec
5
5
  Project-URL: Homepage, https://github.com/Rootly-AI-Labs/Rootly-MCP-server
6
6
  Project-URL: Issues, https://github.com/Rootly-AI-Labs/Rootly-MCP-server/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rootly-mcp-server"
3
- version = "2.0.12"
3
+ version = "2.0.13"
4
4
  description = "A Model Context Protocol server for Rootly APIs using OpenAPI spec"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -391,7 +391,7 @@ def create_rootly_mcp_server(
391
391
  # Single page mode
392
392
  if page_number > 0:
393
393
  params = {
394
- "page[size]": min(page_size, 5), # Keep responses very small to avoid errors
394
+ "page[size]": page_size, # Use requested page size (already limited to max 20)
395
395
  "page[number]": page_number,
396
396
  "include": "",
397
397
  }
@@ -409,7 +409,7 @@ def create_rootly_mcp_server(
409
409
  # Multi-page mode (page_number = 0)
410
410
  all_incidents = []
411
411
  current_page = 1
412
- effective_page_size = min(page_size, 5) # Keep responses very small to avoid errors
412
+ effective_page_size = page_size # Use requested page size (already limited to max 20)
413
413
  max_pages = 10 # Safety limit to prevent infinite loops
414
414
 
415
415
  try:
@@ -922,6 +922,73 @@ def _filter_openapi_spec(spec: Dict[str, Any], allowed_paths: List[str]) -> Dict
922
922
  "description": param.get("description", "Parameter value")
923
923
  }
924
924
 
925
+ # Add/modify pagination limits to alerts and incident-related endpoints to prevent infinite loops
926
+ if method.lower() == "get" and ("alerts" in path.lower() or "incident" in path.lower()):
927
+ if "parameters" not in operation:
928
+ operation["parameters"] = []
929
+
930
+ # Find existing pagination parameters and update them with limits
931
+ page_size_param = None
932
+ page_number_param = None
933
+
934
+ for param in operation["parameters"]:
935
+ if param.get("name") == "page[size]":
936
+ page_size_param = param
937
+ elif param.get("name") == "page[number]":
938
+ page_number_param = param
939
+
940
+ # Update or add page[size] parameter with limits
941
+ if page_size_param:
942
+ # Update existing parameter with limits
943
+ if "schema" not in page_size_param:
944
+ page_size_param["schema"] = {}
945
+ page_size_param["schema"].update({
946
+ "type": "integer",
947
+ "default": 10,
948
+ "minimum": 1,
949
+ "maximum": 20,
950
+ "description": "Number of results per page (max: 20)"
951
+ })
952
+ else:
953
+ # Add new parameter
954
+ operation["parameters"].append({
955
+ "name": "page[size]",
956
+ "in": "query",
957
+ "required": False,
958
+ "schema": {
959
+ "type": "integer",
960
+ "default": 10,
961
+ "minimum": 1,
962
+ "maximum": 20,
963
+ "description": "Number of results per page (max: 20)"
964
+ }
965
+ })
966
+
967
+ # Update or add page[number] parameter with defaults
968
+ if page_number_param:
969
+ # Update existing parameter
970
+ if "schema" not in page_number_param:
971
+ page_number_param["schema"] = {}
972
+ page_number_param["schema"].update({
973
+ "type": "integer",
974
+ "default": 1,
975
+ "minimum": 1,
976
+ "description": "Page number to retrieve"
977
+ })
978
+ else:
979
+ # Add new parameter
980
+ operation["parameters"].append({
981
+ "name": "page[number]",
982
+ "in": "query",
983
+ "required": False,
984
+ "schema": {
985
+ "type": "integer",
986
+ "default": 1,
987
+ "minimum": 1,
988
+ "description": "Page number to retrieve"
989
+ }
990
+ })
991
+
925
992
  # Also clean up any remaining broken references in components
926
993
  if "components" in filtered_spec and "schemas" in filtered_spec["components"]:
927
994
  schemas = filtered_spec["components"]["schemas"]
@@ -237,6 +237,19 @@ class TestOpenAPISpecFiltering:
237
237
  assert "/teams" in filtered_spec["paths"]
238
238
  assert "/forbidden" not in filtered_spec["paths"]
239
239
 
240
+ # Verify pagination parameters were added to /incidents endpoint
241
+ incidents_get = filtered_spec["paths"]["/incidents"]["get"]
242
+ assert "parameters" in incidents_get
243
+ param_names = [p["name"] for p in incidents_get["parameters"]]
244
+ assert "page[size]" in param_names
245
+ assert "page[number]" in param_names
246
+
247
+ # Verify /teams endpoint does not get pagination (doesn't contain "incidents" or "alerts")
248
+ teams_get = filtered_spec["paths"]["/teams"]["get"]
249
+ if "parameters" in teams_get:
250
+ param_names = [p["name"] for p in teams_get["parameters"]]
251
+ assert "page[size]" not in param_names
252
+
240
253
  # Verify other properties are preserved
241
254
  assert filtered_spec["openapi"] == original_spec["openapi"]
242
255
  assert filtered_spec["info"] == original_spec["info"]
@@ -276,6 +289,83 @@ class TestOpenAPISpecFiltering:
276
289
  assert "servers" in filtered_spec
277
290
  assert "components" in filtered_spec
278
291
  assert filtered_spec["servers"] == original_spec["servers"]
292
+
293
+ # Verify pagination parameters were added to /incidents endpoint
294
+ incidents_get = filtered_spec["paths"]["/incidents"]["get"]
295
+ assert "parameters" in incidents_get
296
+ param_names = [p["name"] for p in incidents_get["parameters"]]
297
+ assert "page[size]" in param_names
298
+ assert "page[number]" in param_names
299
+
300
+ def test_filter_spec_adds_pagination_to_alerts(self):
301
+ """Test that pagination parameters are added to alerts endpoints."""
302
+ original_spec = {
303
+ "openapi": "3.0.0",
304
+ "info": {"title": "Test API", "version": "1.0.0"},
305
+ "paths": {
306
+ "/alerts": {"get": {"operationId": "listAlerts"}},
307
+ "/incidents/123/alerts": {"get": {"operationId": "listIncidentAlerts"}},
308
+ "/users": {"get": {"operationId": "listUsers"}},
309
+ },
310
+ "components": {"schemas": {}}
311
+ }
312
+
313
+ allowed_paths = ["/alerts", "/incidents/123/alerts", "/users"]
314
+ filtered_spec = _filter_openapi_spec(original_spec, allowed_paths)
315
+
316
+ # Verify pagination was added to alerts endpoints
317
+ alerts_get = filtered_spec["paths"]["/alerts"]["get"]
318
+ assert "parameters" in alerts_get
319
+ param_names = [p["name"] for p in alerts_get["parameters"]]
320
+ assert "page[size]" in param_names
321
+ assert "page[number]" in param_names
322
+
323
+ incident_alerts_get = filtered_spec["paths"]["/incidents/123/alerts"]["get"]
324
+ assert "parameters" in incident_alerts_get
325
+ param_names = [p["name"] for p in incident_alerts_get["parameters"]]
326
+ assert "page[size]" in param_names
327
+ assert "page[number]" in param_names
328
+
329
+ # Verify pagination was NOT added to /users (no "incident" or "alerts" in path)
330
+ users_get = filtered_spec["paths"]["/users"]["get"]
331
+ if "parameters" in users_get:
332
+ param_names = [p["name"] for p in users_get["parameters"]]
333
+ assert "page[size]" not in param_names
334
+
335
+ def test_filter_spec_adds_pagination_to_incident_types(self):
336
+ """Test that pagination parameters are added to incident-related endpoints."""
337
+ original_spec = {
338
+ "openapi": "3.0.0",
339
+ "info": {"title": "Test API", "version": "1.0.0"},
340
+ "paths": {
341
+ "/incident_types": {"get": {"operationId": "listIncidentTypes"}},
342
+ "/incident_action_items": {"get": {"operationId": "listIncidentActionItems"}},
343
+ "/services": {"get": {"operationId": "listServices"}},
344
+ },
345
+ "components": {"schemas": {}}
346
+ }
347
+
348
+ allowed_paths = ["/incident_types", "/incident_action_items", "/services"]
349
+ filtered_spec = _filter_openapi_spec(original_spec, allowed_paths)
350
+
351
+ # Verify pagination was added to incident-related endpoints
352
+ incident_types_get = filtered_spec["paths"]["/incident_types"]["get"]
353
+ assert "parameters" in incident_types_get
354
+ param_names = [p["name"] for p in incident_types_get["parameters"]]
355
+ assert "page[size]" in param_names
356
+ assert "page[number]" in param_names
357
+
358
+ incident_action_items_get = filtered_spec["paths"]["/incident_action_items"]["get"]
359
+ assert "parameters" in incident_action_items_get
360
+ param_names = [p["name"] for p in incident_action_items_get["parameters"]]
361
+ assert "page[size]" in param_names
362
+ assert "page[number]" in param_names
363
+
364
+ # Verify pagination was NOT added to /services (no "incident" or "alerts" in path)
365
+ services_get = filtered_spec["paths"]["/services"]["get"]
366
+ if "parameters" in services_get:
367
+ param_names = [p["name"] for p in services_get["parameters"]]
368
+ assert "page[size]" not in param_names
279
369
 
280
370
 
281
371
  @pytest.mark.unit
@@ -873,7 +873,7 @@ wheels = [
873
873
 
874
874
  [[package]]
875
875
  name = "rootly-mcp-server"
876
- version = "2.0.10"
876
+ version = "2.0.12"
877
877
  source = { editable = "." }
878
878
  dependencies = [
879
879
  { name = "brotli" },