rootly-mcp-server 2.0.12__tar.gz → 2.0.14__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.
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/PKG-INFO +5 -1
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/README.md +4 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/pyproject.toml +1 -1
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/src/rootly_mcp_server/server.py +194 -4
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/tests/unit/test_server.py +90 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/uv.lock +1 -1
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/.github/workflows/pypi-release.yml +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/.github/workflows/test.yml +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/.gitignore +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/.semaphore/deploy.yml +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/.semaphore/semaphore.yml +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/.semaphore/update-task-definition.sh +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/Dockerfile +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/LICENSE +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/rootly-mcp-server-demo.gif +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/rootly_openapi.json +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/src/rootly_mcp_server/__init__.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/src/rootly_mcp_server/__main__.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/src/rootly_mcp_server/client.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/src/rootly_mcp_server/data/__init__.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/src/rootly_mcp_server/smart_utils.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/src/rootly_mcp_server/utils.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/tests/README.md +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/tests/conftest.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/tests/integration/local/test_basic.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/tests/integration/local/test_smart_tools.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/tests/integration/remote/test_essential.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/tests/test_client.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/tests/unit/test_authentication.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/tests/unit/test_smart_utils.py +0 -0
- {rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/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.
|
|
3
|
+
Version: 2.0.14
|
|
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
|
|
@@ -28,6 +28,10 @@ Description-Content-Type: text/markdown
|
|
|
28
28
|
|
|
29
29
|
# Rootly MCP Server
|
|
30
30
|
|
|
31
|
+
[](https://pypi.org/project/rootly-mcp-server/)
|
|
32
|
+
[](https://pypi.org/project/rootly-mcp-server/)
|
|
33
|
+
[](https://pypi.org/project/rootly-mcp-server/)
|
|
34
|
+
|
|
31
35
|
An MCP server for the [Rootly API](https://docs.rootly.com/api-reference/overview) that integrates seamlessly with MCP-compatible editors like Cursor, Windsurf, and Claude. Resolve production incidents in under a minute without leaving your IDE.
|
|
32
36
|
|
|
33
37
|
[](https://cursor.com/install-mcp?name=rootly&config=eyJjb21tYW5kIjoibnB4IC15IG1jcC1yZW1vdGUgaHR0cHM6Ly9tY3Aucm9vdGx5LmNvbS9zc2UgLS1oZWFkZXIgQXV0aG9yaXphdGlvbjoke1JPT1RMWV9BVVRIX0hFQURFUn0iLCJlbnYiOnsiUk9PVExZX0FVVEhfSEVBREVSIjoiQmVhcmVyIDxZT1VSX1JPT1RMWV9BUElfVE9LRU4%2BIn19)
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Rootly MCP Server
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/rootly-mcp-server/)
|
|
4
|
+
[](https://pypi.org/project/rootly-mcp-server/)
|
|
5
|
+
[](https://pypi.org/project/rootly-mcp-server/)
|
|
6
|
+
|
|
3
7
|
An MCP server for the [Rootly API](https://docs.rootly.com/api-reference/overview) that integrates seamlessly with MCP-compatible editors like Cursor, Windsurf, and Claude. Resolve production incidents in under a minute without leaving your IDE.
|
|
4
8
|
|
|
5
9
|
[](https://cursor.com/install-mcp?name=rootly&config=eyJjb21tYW5kIjoibnB4IC15IG1jcC1yZW1vdGUgaHR0cHM6Ly9tY3Aucm9vdGx5LmNvbS9zc2UgLS1oZWFkZXIgQXV0aG9yaXphdGlvbjoke1JPT1RMWV9BVVRIX0hFQURFUn0iLCJlbnYiOnsiUk9PVExZX0FVVEhfSEVBREVSIjoiQmVhcmVyIDxZT1VSX1JPT1RMWV9BUElfVE9LRU4%2BIn19)
|
|
@@ -391,9 +391,10 @@ def create_rootly_mcp_server(
|
|
|
391
391
|
# Single page mode
|
|
392
392
|
if page_number > 0:
|
|
393
393
|
params = {
|
|
394
|
-
"page[size]":
|
|
394
|
+
"page[size]": page_size, # Use requested page size (already limited to max 20)
|
|
395
395
|
"page[number]": page_number,
|
|
396
396
|
"include": "",
|
|
397
|
+
"fields[incidents]": "id,title,summary,status,severity,created_at,updated_at,url,started_at",
|
|
397
398
|
}
|
|
398
399
|
if query:
|
|
399
400
|
params["filter[search]"] = query
|
|
@@ -409,7 +410,7 @@ def create_rootly_mcp_server(
|
|
|
409
410
|
# Multi-page mode (page_number = 0)
|
|
410
411
|
all_incidents = []
|
|
411
412
|
current_page = 1
|
|
412
|
-
effective_page_size =
|
|
413
|
+
effective_page_size = page_size # Use requested page size (already limited to max 20)
|
|
413
414
|
max_pages = 10 # Safety limit to prevent infinite loops
|
|
414
415
|
|
|
415
416
|
try:
|
|
@@ -418,6 +419,7 @@ def create_rootly_mcp_server(
|
|
|
418
419
|
"page[size]": effective_page_size,
|
|
419
420
|
"page[number]": current_page,
|
|
420
421
|
"include": "",
|
|
422
|
+
"fields[incidents]": "id,title,summary,status,severity,created_at,updated_at,url,started_at",
|
|
421
423
|
}
|
|
422
424
|
if query:
|
|
423
425
|
params["filter[search]"] = query
|
|
@@ -922,6 +924,133 @@ def _filter_openapi_spec(spec: Dict[str, Any], allowed_paths: List[str]) -> Dict
|
|
|
922
924
|
"description": param.get("description", "Parameter value")
|
|
923
925
|
}
|
|
924
926
|
|
|
927
|
+
# Add/modify pagination limits to alerts and incident-related endpoints to prevent infinite loops
|
|
928
|
+
if method.lower() == "get" and ("alerts" in path.lower() or "incident" in path.lower()):
|
|
929
|
+
if "parameters" not in operation:
|
|
930
|
+
operation["parameters"] = []
|
|
931
|
+
|
|
932
|
+
# Find existing pagination parameters and update them with limits
|
|
933
|
+
page_size_param = None
|
|
934
|
+
page_number_param = None
|
|
935
|
+
|
|
936
|
+
for param in operation["parameters"]:
|
|
937
|
+
if param.get("name") == "page[size]":
|
|
938
|
+
page_size_param = param
|
|
939
|
+
elif param.get("name") == "page[number]":
|
|
940
|
+
page_number_param = param
|
|
941
|
+
|
|
942
|
+
# Update or add page[size] parameter with limits
|
|
943
|
+
if page_size_param:
|
|
944
|
+
# Update existing parameter with limits
|
|
945
|
+
if "schema" not in page_size_param:
|
|
946
|
+
page_size_param["schema"] = {}
|
|
947
|
+
page_size_param["schema"].update({
|
|
948
|
+
"type": "integer",
|
|
949
|
+
"default": 10,
|
|
950
|
+
"minimum": 1,
|
|
951
|
+
"maximum": 20,
|
|
952
|
+
"description": "Number of results per page (max: 20)"
|
|
953
|
+
})
|
|
954
|
+
else:
|
|
955
|
+
# Add new parameter
|
|
956
|
+
operation["parameters"].append({
|
|
957
|
+
"name": "page[size]",
|
|
958
|
+
"in": "query",
|
|
959
|
+
"required": False,
|
|
960
|
+
"schema": {
|
|
961
|
+
"type": "integer",
|
|
962
|
+
"default": 10,
|
|
963
|
+
"minimum": 1,
|
|
964
|
+
"maximum": 20,
|
|
965
|
+
"description": "Number of results per page (max: 20)"
|
|
966
|
+
}
|
|
967
|
+
})
|
|
968
|
+
|
|
969
|
+
# Update or add page[number] parameter with defaults
|
|
970
|
+
if page_number_param:
|
|
971
|
+
# Update existing parameter
|
|
972
|
+
if "schema" not in page_number_param:
|
|
973
|
+
page_number_param["schema"] = {}
|
|
974
|
+
page_number_param["schema"].update({
|
|
975
|
+
"type": "integer",
|
|
976
|
+
"default": 1,
|
|
977
|
+
"minimum": 1,
|
|
978
|
+
"description": "Page number to retrieve"
|
|
979
|
+
})
|
|
980
|
+
else:
|
|
981
|
+
# Add new parameter
|
|
982
|
+
operation["parameters"].append({
|
|
983
|
+
"name": "page[number]",
|
|
984
|
+
"in": "query",
|
|
985
|
+
"required": False,
|
|
986
|
+
"schema": {
|
|
987
|
+
"type": "integer",
|
|
988
|
+
"default": 1,
|
|
989
|
+
"minimum": 1,
|
|
990
|
+
"description": "Page number to retrieve"
|
|
991
|
+
}
|
|
992
|
+
})
|
|
993
|
+
|
|
994
|
+
# Add sparse fieldsets for alerts endpoints to reduce payload size
|
|
995
|
+
if "alert" in path.lower():
|
|
996
|
+
# Add fields[alerts] parameter with essential fields only - make it required with default
|
|
997
|
+
operation["parameters"].append({
|
|
998
|
+
"name": "fields[alerts]",
|
|
999
|
+
"in": "query",
|
|
1000
|
+
"required": True,
|
|
1001
|
+
"schema": {
|
|
1002
|
+
"type": "string",
|
|
1003
|
+
"default": "id,summary,status,started_at,ended_at,short_id,alert_urgency_id,source,noise",
|
|
1004
|
+
"description": "Comma-separated list of alert fields to include (reduces payload size)"
|
|
1005
|
+
}
|
|
1006
|
+
})
|
|
1007
|
+
|
|
1008
|
+
# Add include parameter for alerts endpoints to minimize relationships
|
|
1009
|
+
if "alert" in path.lower():
|
|
1010
|
+
# Check if include parameter already exists
|
|
1011
|
+
include_param_exists = any(param.get("name") == "include" for param in operation["parameters"])
|
|
1012
|
+
if not include_param_exists:
|
|
1013
|
+
operation["parameters"].append({
|
|
1014
|
+
"name": "include",
|
|
1015
|
+
"in": "query",
|
|
1016
|
+
"required": True,
|
|
1017
|
+
"schema": {
|
|
1018
|
+
"type": "string",
|
|
1019
|
+
"default": "",
|
|
1020
|
+
"description": "Related resources to include (empty for minimal payload)"
|
|
1021
|
+
}
|
|
1022
|
+
})
|
|
1023
|
+
|
|
1024
|
+
# Add sparse fieldsets for incidents endpoints to reduce payload size
|
|
1025
|
+
if "incident" in path.lower():
|
|
1026
|
+
# Add fields[incidents] parameter with essential fields only - make it required with default
|
|
1027
|
+
operation["parameters"].append({
|
|
1028
|
+
"name": "fields[incidents]",
|
|
1029
|
+
"in": "query",
|
|
1030
|
+
"required": True,
|
|
1031
|
+
"schema": {
|
|
1032
|
+
"type": "string",
|
|
1033
|
+
"default": "id,title,summary,status,severity,created_at,updated_at,url,started_at",
|
|
1034
|
+
"description": "Comma-separated list of incident fields to include (reduces payload size)"
|
|
1035
|
+
}
|
|
1036
|
+
})
|
|
1037
|
+
|
|
1038
|
+
# Add include parameter for incidents endpoints to minimize relationships
|
|
1039
|
+
if "incident" in path.lower():
|
|
1040
|
+
# Check if include parameter already exists
|
|
1041
|
+
include_param_exists = any(param.get("name") == "include" for param in operation["parameters"])
|
|
1042
|
+
if not include_param_exists:
|
|
1043
|
+
operation["parameters"].append({
|
|
1044
|
+
"name": "include",
|
|
1045
|
+
"in": "query",
|
|
1046
|
+
"required": True,
|
|
1047
|
+
"schema": {
|
|
1048
|
+
"type": "string",
|
|
1049
|
+
"default": "",
|
|
1050
|
+
"description": "Related resources to include (empty for minimal payload)"
|
|
1051
|
+
}
|
|
1052
|
+
})
|
|
1053
|
+
|
|
925
1054
|
# Also clean up any remaining broken references in components
|
|
926
1055
|
if "components" in filtered_spec and "schemas" in filtered_spec["components"]:
|
|
927
1056
|
schemas = filtered_spec["components"]["schemas"]
|
|
@@ -935,6 +1064,52 @@ def _filter_openapi_spec(spec: Dict[str, Any], allowed_paths: List[str]) -> Dict
|
|
|
935
1064
|
logger.warning(f"Removing schema with broken references: {schema_name}")
|
|
936
1065
|
del schemas[schema_name]
|
|
937
1066
|
|
|
1067
|
+
# Clean up any operation-level references to removed schemas
|
|
1068
|
+
removed_schemas = set()
|
|
1069
|
+
if "components" in filtered_spec and "schemas" in filtered_spec["components"]:
|
|
1070
|
+
removed_schemas = {"new_workflow", "update_workflow", "workflow", "workflow_task",
|
|
1071
|
+
"workflow_response", "workflow_list", "new_workflow_task",
|
|
1072
|
+
"update_workflow_task", "workflow_task_response", "workflow_task_list"}
|
|
1073
|
+
|
|
1074
|
+
for path, path_item in filtered_spec.get("paths", {}).items():
|
|
1075
|
+
for method, operation in path_item.items():
|
|
1076
|
+
if method.lower() not in ["get", "post", "put", "delete", "patch"]:
|
|
1077
|
+
continue
|
|
1078
|
+
|
|
1079
|
+
# Clean request body references
|
|
1080
|
+
if "requestBody" in operation:
|
|
1081
|
+
request_body = operation["requestBody"]
|
|
1082
|
+
if "content" in request_body:
|
|
1083
|
+
for content_type, content_info in request_body["content"].items():
|
|
1084
|
+
if "schema" in content_info and "$ref" in content_info["schema"]:
|
|
1085
|
+
ref_path = content_info["schema"]["$ref"]
|
|
1086
|
+
schema_name = ref_path.split("/")[-1]
|
|
1087
|
+
if schema_name in removed_schemas:
|
|
1088
|
+
# Replace with generic object schema
|
|
1089
|
+
content_info["schema"] = {
|
|
1090
|
+
"type": "object",
|
|
1091
|
+
"description": "Request data for this endpoint",
|
|
1092
|
+
"additionalProperties": True
|
|
1093
|
+
}
|
|
1094
|
+
logger.debug(f"Cleaned broken reference in {method.upper()} {path} request body: {ref_path}")
|
|
1095
|
+
|
|
1096
|
+
# Clean response references
|
|
1097
|
+
if "responses" in operation:
|
|
1098
|
+
for status_code, response in operation["responses"].items():
|
|
1099
|
+
if "content" in response:
|
|
1100
|
+
for content_type, content_info in response["content"].items():
|
|
1101
|
+
if "schema" in content_info and "$ref" in content_info["schema"]:
|
|
1102
|
+
ref_path = content_info["schema"]["$ref"]
|
|
1103
|
+
schema_name = ref_path.split("/")[-1]
|
|
1104
|
+
if schema_name in removed_schemas:
|
|
1105
|
+
# Replace with generic object schema
|
|
1106
|
+
content_info["schema"] = {
|
|
1107
|
+
"type": "object",
|
|
1108
|
+
"description": "Response data from this endpoint",
|
|
1109
|
+
"additionalProperties": True
|
|
1110
|
+
}
|
|
1111
|
+
logger.debug(f"Cleaned broken reference in {method.upper()} {path} response: {ref_path}")
|
|
1112
|
+
|
|
938
1113
|
return filtered_spec
|
|
939
1114
|
|
|
940
1115
|
|
|
@@ -946,8 +1121,23 @@ def _has_broken_references(schema_def: Dict[str, Any]) -> bool:
|
|
|
946
1121
|
broken_refs = [
|
|
947
1122
|
"incident_trigger_params",
|
|
948
1123
|
"new_workflow",
|
|
949
|
-
"update_workflow",
|
|
950
|
-
"workflow"
|
|
1124
|
+
"update_workflow",
|
|
1125
|
+
"workflow",
|
|
1126
|
+
"new_workflow_task",
|
|
1127
|
+
"update_workflow_task",
|
|
1128
|
+
"workflow_task",
|
|
1129
|
+
"workflow_task_response",
|
|
1130
|
+
"workflow_task_list",
|
|
1131
|
+
"workflow_response",
|
|
1132
|
+
"workflow_list",
|
|
1133
|
+
"workflow_custom_field_selection_response",
|
|
1134
|
+
"workflow_custom_field_selection_list",
|
|
1135
|
+
"workflow_form_field_condition_response",
|
|
1136
|
+
"workflow_form_field_condition_list",
|
|
1137
|
+
"workflow_group_response",
|
|
1138
|
+
"workflow_group_list",
|
|
1139
|
+
"workflow_run_response",
|
|
1140
|
+
"workflow_runs_list"
|
|
951
1141
|
]
|
|
952
1142
|
if any(broken_ref in ref_path for broken_ref in broken_refs):
|
|
953
1143
|
return True
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/src/rootly_mcp_server/data/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/tests/integration/local/test_smart_tools.py
RENAMED
|
File without changes
|
{rootly_mcp_server-2.0.12 → rootly_mcp_server-2.0.14}/tests/integration/remote/test_essential.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|