rootly-mcp-server 2.2.22__tar.gz → 2.2.24__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.2.22 → rootly_mcp_server-2.2.24}/CHANGELOG.md +26 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/PKG-INFO +1 -1
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/pyproject.toml +1 -1
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/transport.py +92 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_server.py +13 -7
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_transport_module.py +169 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/uv.lock +1 -1
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.dockerignore +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.gitattributes +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.github/dependabot.yml +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.github/openapi-audit-remote-baseline.json +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.github/workflows/ci.yml +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.github/workflows/dependency-review.yml +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.github/workflows/openapi-audit.yml +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.github/workflows/pypi-release.yml +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.gitignore +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.semaphore/deploy-staging.yml +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.semaphore/deploy.yml +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.semaphore/semaphore.yml +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.semaphore/update-task-definition.sh +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/CONTRIBUTING.md +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/Dockerfile +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/LICENSE +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/README.md +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/examples/skills/README.md +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/gemini-extension.json +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/rootly-mcp-server-demo.gif +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/rootly_openapi.json +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/scripts/audit_openapi.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/scripts/setup-hooks.sh +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/server.json +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/__init__.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/__main__.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/audit.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/client.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/code_mode.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/data/__init__.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/exceptions.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/legacy_server.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/mcp_error.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/monitoring.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/och_client.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/pagination.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/payload_stripping.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/security.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/server.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/server_defaults.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/smart_utils.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/spec_transform.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/tools/__init__.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/tools/alerts.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/tools/incidents.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/tools/oncall.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/tools/resources.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/utils.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/validators.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/README.md +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/conftest.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/integration/local/test_basic.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/integration/local/test_smart_tools.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/integration/local/test_tool_allowlists.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/integration/remote/test_essential.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/test_client.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_alert_stripping.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_authentication.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_code_mode_module.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_exceptions.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_http_headers.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_legacy_server_module.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_main_transport.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_mcp_error_module.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_och_client.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_oncall_handoff.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_oncall_metrics.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_oncall_new_tools.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_oncall_severity_normalization.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_payload_stripping_module.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_security.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_server_defaults_module.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_smart_utils.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_tools.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_utils.py +0 -0
- {rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_validators.py +0 -0
|
@@ -5,6 +5,32 @@ All notable changes to the Rootly MCP Server will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.2.24] - Released 2026-04-22
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Incident Form Field Selection Responses**: Normalized text and textarea form field selection responses so MCP clients receive the primary `value` plus `selected_*_ids`, without the redundant `selected_*` value objects repeated across unrelated resource types
|
|
13
|
+
|
|
14
|
+
### Testing
|
|
15
|
+
|
|
16
|
+
- **Response Normalization Coverage**: Added focused transport tests for single-item and list incident form field selection payloads, including a guard to leave select-style fields unchanged
|
|
17
|
+
|
|
18
|
+
## [2.2.23] - Released 2026-04-22
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- **Self-Hosted Tool Allowlists**: Added `ROOTLY_MCP_ENABLED_TOOLS` and `--enabled-tools` so self-hosted deployments can expose only an exact allowlist of MCP tool names
|
|
23
|
+
- **Tool Discovery Command**: Added `--list-tools` so self-hosted users can print the effective tool names for their current configuration before narrowing the MCP surface
|
|
24
|
+
- **Code Mode Alignment**: Applied the same allowlist behavior to the self-hosted Code Mode surface so discovery and enforcement stay consistent
|
|
25
|
+
|
|
26
|
+
### Testing
|
|
27
|
+
|
|
28
|
+
- **Live MCP Integration Coverage**: Added subprocess integration tests that boot the server, connect over streamable HTTP, call `tools/list`, and verify the live tool payload matches the configured allowlist
|
|
29
|
+
|
|
30
|
+
### Documentation
|
|
31
|
+
|
|
32
|
+
- **Self-Hosted Setup Guidance**: Documented the new allowlist and discovery workflow in the README, including smoke-test examples for read-only and write-enabled self-hosted setups
|
|
33
|
+
|
|
8
34
|
## [2.2.20] - Released 2026-04-21
|
|
9
35
|
|
|
10
36
|
### Security
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rootly-mcp-server
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.24
|
|
4
4
|
Summary: Secure Model Context Protocol server for Rootly APIs with AI SRE capabilities, comprehensive error handling, and input validation
|
|
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.2.
|
|
3
|
+
version = "2.2.24"
|
|
4
4
|
description = "Secure Model Context Protocol server for Rootly APIs with AI SRE capabilities, comprehensive error handling, and input validation"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -717,6 +717,9 @@ class AuthenticatedHTTPXClient:
|
|
|
717
717
|
# there is no other interception point for auto-generated tools.
|
|
718
718
|
response = self._maybe_strip_alert_response(method, url, response)
|
|
719
719
|
response = self._maybe_strip_collection_response(method, url, response)
|
|
720
|
+
response = self._maybe_normalize_incident_form_field_selection_response(
|
|
721
|
+
method, url, response
|
|
722
|
+
)
|
|
720
723
|
|
|
721
724
|
return response
|
|
722
725
|
|
|
@@ -739,6 +742,13 @@ class AuthenticatedHTTPXClient:
|
|
|
739
742
|
except Exception:
|
|
740
743
|
return str(url).split("?", 1)[0]
|
|
741
744
|
|
|
745
|
+
@staticmethod
|
|
746
|
+
def _is_incident_form_field_selection_endpoint(path: str) -> bool:
|
|
747
|
+
"""Return whether a path targets incident form field selection resources."""
|
|
748
|
+
return path.startswith("/v1/incident_form_field_selections/") or (
|
|
749
|
+
path.startswith("/v1/incidents/") and path.endswith("/form_field_selections")
|
|
750
|
+
)
|
|
751
|
+
|
|
742
752
|
@staticmethod
|
|
743
753
|
def _maybe_strip_alert_response(
|
|
744
754
|
method: str, url: str, response: httpx.Response
|
|
@@ -783,6 +793,85 @@ class AuthenticatedHTTPXClient:
|
|
|
783
793
|
logger.debug(f"Could not strip collection response for {url}", exc_info=True)
|
|
784
794
|
return response
|
|
785
795
|
|
|
796
|
+
@staticmethod
|
|
797
|
+
def _normalize_incident_form_field_selection_item(item: Any) -> Any:
|
|
798
|
+
"""Prune redundant selected_* objects for text-like form field selections."""
|
|
799
|
+
if not isinstance(item, dict):
|
|
800
|
+
return item
|
|
801
|
+
|
|
802
|
+
attributes = item.get("attributes")
|
|
803
|
+
if not isinstance(attributes, dict):
|
|
804
|
+
return item
|
|
805
|
+
|
|
806
|
+
form_field = attributes.get("form_field")
|
|
807
|
+
if not isinstance(form_field, dict):
|
|
808
|
+
return item
|
|
809
|
+
|
|
810
|
+
if form_field.get("input_kind") not in {"text", "textarea"}:
|
|
811
|
+
return item
|
|
812
|
+
|
|
813
|
+
normalized_attributes = dict(attributes)
|
|
814
|
+
for key in (
|
|
815
|
+
"selected_groups",
|
|
816
|
+
"selected_options",
|
|
817
|
+
"selected_services",
|
|
818
|
+
"selected_functionalities",
|
|
819
|
+
"selected_catalog_entities",
|
|
820
|
+
"selected_users",
|
|
821
|
+
"selected_environments",
|
|
822
|
+
"selected_causes",
|
|
823
|
+
"selected_incident_types",
|
|
824
|
+
):
|
|
825
|
+
normalized_attributes.pop(key, None)
|
|
826
|
+
|
|
827
|
+
normalized_item = dict(item)
|
|
828
|
+
normalized_item["attributes"] = normalized_attributes
|
|
829
|
+
return normalized_item
|
|
830
|
+
|
|
831
|
+
@classmethod
|
|
832
|
+
def _normalize_incident_form_field_selection_payload(cls, payload: Any) -> Any:
|
|
833
|
+
"""Normalize form field selection payloads that may contain one item or many."""
|
|
834
|
+
if not isinstance(payload, dict):
|
|
835
|
+
return payload
|
|
836
|
+
|
|
837
|
+
data = payload.get("data")
|
|
838
|
+
if isinstance(data, dict):
|
|
839
|
+
normalized_payload = dict(payload)
|
|
840
|
+
normalized_payload["data"] = cls._normalize_incident_form_field_selection_item(data)
|
|
841
|
+
return normalized_payload
|
|
842
|
+
|
|
843
|
+
if isinstance(data, list):
|
|
844
|
+
normalized_payload = dict(payload)
|
|
845
|
+
normalized_payload["data"] = [
|
|
846
|
+
cls._normalize_incident_form_field_selection_item(item) for item in data
|
|
847
|
+
]
|
|
848
|
+
return normalized_payload
|
|
849
|
+
|
|
850
|
+
return payload
|
|
851
|
+
|
|
852
|
+
@classmethod
|
|
853
|
+
def _maybe_normalize_incident_form_field_selection_response(
|
|
854
|
+
cls, method: str, url: str, response: httpx.Response
|
|
855
|
+
) -> httpx.Response:
|
|
856
|
+
"""Normalize noisy incident form field selection responses."""
|
|
857
|
+
if method.upper() not in {"GET", "POST", "PUT", "PATCH"} or not response.is_success:
|
|
858
|
+
return response
|
|
859
|
+
|
|
860
|
+
path = cls._path_for_url(url)
|
|
861
|
+
if not cls._is_incident_form_field_selection_endpoint(path):
|
|
862
|
+
return response
|
|
863
|
+
|
|
864
|
+
try:
|
|
865
|
+
payload = response.json()
|
|
866
|
+
normalized = cls._normalize_incident_form_field_selection_payload(payload)
|
|
867
|
+
response._content = json.dumps(normalized).encode() # noqa: SLF001
|
|
868
|
+
except Exception:
|
|
869
|
+
logger.debug(
|
|
870
|
+
f"Could not normalize incident form field selection response for {url}",
|
|
871
|
+
exc_info=True,
|
|
872
|
+
)
|
|
873
|
+
return response
|
|
874
|
+
|
|
786
875
|
async def get(self, url: str, **kwargs):
|
|
787
876
|
"""Proxy to request with GET method."""
|
|
788
877
|
return await self.request("GET", url, **kwargs)
|
|
@@ -858,6 +947,9 @@ class AuthenticatedHTTPXClient:
|
|
|
858
947
|
|
|
859
948
|
response = self._maybe_strip_alert_response(request.method, str(request.url), response)
|
|
860
949
|
response = self._maybe_strip_collection_response(request.method, str(request.url), response)
|
|
950
|
+
response = self._maybe_normalize_incident_form_field_selection_response(
|
|
951
|
+
request.method, str(request.url), response
|
|
952
|
+
)
|
|
861
953
|
return response
|
|
862
954
|
|
|
863
955
|
async def __aenter__(self):
|
|
@@ -289,32 +289,38 @@ class TestServerCreation:
|
|
|
289
289
|
class TestBundledIncidentFormFieldSelectionTools:
|
|
290
290
|
"""Verify the bundled swagger exposes the intended incident custom field tools."""
|
|
291
291
|
|
|
292
|
-
async def
|
|
292
|
+
async def test_default_server_shows_generated_write_tools(self, mock_environment_token):
|
|
293
293
|
server = create_rootly_mcp_server(hosted=False)
|
|
294
294
|
|
|
295
295
|
tools = await server.list_tools()
|
|
296
296
|
tool_names = {tool.name for tool in tools}
|
|
297
297
|
|
|
298
|
+
# Read tools should be present
|
|
298
299
|
assert "listIncidentActionItems" in tool_names
|
|
299
300
|
assert "listIncidentFormFieldSelections" in tool_names
|
|
300
301
|
assert "getIncidentFormFieldSelection" in tool_names
|
|
301
302
|
|
|
302
|
-
|
|
303
|
-
assert "
|
|
304
|
-
assert "
|
|
303
|
+
# Write tools should also be present by default
|
|
304
|
+
assert "createIncidentActionItem" in tool_names
|
|
305
|
+
assert "createIncidentFormFieldSelection" in tool_names
|
|
306
|
+
assert "updateIncidentFormFieldSelection" in tool_names
|
|
307
|
+
# Delete operations are still restricted
|
|
305
308
|
assert "deleteIncidentFormFieldSelection" not in tool_names
|
|
306
309
|
|
|
307
|
-
async def
|
|
310
|
+
async def test_default_server_shows_workflow_write_tools(self, mock_environment_token):
|
|
308
311
|
server = create_rootly_mcp_server(hosted=False)
|
|
309
312
|
|
|
310
313
|
tools = await server.list_tools()
|
|
311
314
|
tool_names = {tool.name for tool in tools}
|
|
312
315
|
|
|
316
|
+
# Read tools should be present
|
|
313
317
|
assert "listWorkflowTasks" in tool_names
|
|
314
318
|
assert "getWorkflowTask" in tool_names
|
|
315
319
|
|
|
316
|
-
|
|
317
|
-
assert "
|
|
320
|
+
# Write tools should also be present by default
|
|
321
|
+
assert "createWorkflowTask" in tool_names
|
|
322
|
+
assert "updateWorkflowTask" in tool_names
|
|
323
|
+
# Delete operations are still restricted
|
|
318
324
|
assert "deleteWorkflowTask" not in tool_names
|
|
319
325
|
|
|
320
326
|
async def test_enable_write_tools_exposes_curated_generated_write_tools(
|
|
@@ -267,6 +267,175 @@ class TestTransportModule:
|
|
|
267
267
|
== ""
|
|
268
268
|
)
|
|
269
269
|
|
|
270
|
+
def test_normalize_incident_form_field_selection_payload_textarea(self):
|
|
271
|
+
payload = {
|
|
272
|
+
"data": {
|
|
273
|
+
"id": "selection-1",
|
|
274
|
+
"type": "incident_form_field_selections",
|
|
275
|
+
"attributes": {
|
|
276
|
+
"value": "Decagon Chat API degraded",
|
|
277
|
+
"selected_group_ids": [],
|
|
278
|
+
"selected_option_ids": [],
|
|
279
|
+
"selected_service_ids": [],
|
|
280
|
+
"selected_functionality_ids": [],
|
|
281
|
+
"selected_catalog_entity_ids": [],
|
|
282
|
+
"selected_user_ids": [],
|
|
283
|
+
"selected_groups": {"id": None, "value": "Decagon Chat API degraded"},
|
|
284
|
+
"selected_options": {"id": None, "value": "Decagon Chat API degraded"},
|
|
285
|
+
"selected_services": {"id": None, "value": "Decagon Chat API degraded"},
|
|
286
|
+
"selected_functionalities": {
|
|
287
|
+
"id": None,
|
|
288
|
+
"value": "Decagon Chat API degraded",
|
|
289
|
+
},
|
|
290
|
+
"selected_catalog_entities": {
|
|
291
|
+
"id": None,
|
|
292
|
+
"value": "Decagon Chat API degraded",
|
|
293
|
+
},
|
|
294
|
+
"selected_users": {"id": None, "value": "Decagon Chat API degraded"},
|
|
295
|
+
"selected_environments": {
|
|
296
|
+
"id": None,
|
|
297
|
+
"value": "Decagon Chat API degraded",
|
|
298
|
+
},
|
|
299
|
+
"selected_causes": {"id": None, "value": "Decagon Chat API degraded"},
|
|
300
|
+
"selected_incident_types": {
|
|
301
|
+
"id": None,
|
|
302
|
+
"value": "Decagon Chat API degraded",
|
|
303
|
+
},
|
|
304
|
+
"form_field": {"input_kind": "textarea"},
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
normalized = (
|
|
310
|
+
transport.AuthenticatedHTTPXClient._normalize_incident_form_field_selection_payload(
|
|
311
|
+
payload
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
attributes = normalized["data"]["attributes"]
|
|
315
|
+
|
|
316
|
+
assert attributes["value"] == "Decagon Chat API degraded"
|
|
317
|
+
assert attributes["selected_group_ids"] == []
|
|
318
|
+
assert "selected_groups" not in attributes
|
|
319
|
+
assert "selected_options" not in attributes
|
|
320
|
+
assert "selected_services" not in attributes
|
|
321
|
+
assert "selected_functionalities" not in attributes
|
|
322
|
+
assert "selected_catalog_entities" not in attributes
|
|
323
|
+
assert "selected_users" not in attributes
|
|
324
|
+
assert "selected_environments" not in attributes
|
|
325
|
+
assert "selected_causes" not in attributes
|
|
326
|
+
assert "selected_incident_types" not in attributes
|
|
327
|
+
|
|
328
|
+
def test_normalize_incident_form_field_selection_payload_select_unchanged(self):
|
|
329
|
+
payload = {
|
|
330
|
+
"data": {
|
|
331
|
+
"id": "selection-2",
|
|
332
|
+
"type": "incident_form_field_selections",
|
|
333
|
+
"attributes": {
|
|
334
|
+
"selected_option_ids": ["opt-1"],
|
|
335
|
+
"selected_options": {"id": "opt-1", "value": "Database"},
|
|
336
|
+
"form_field": {"input_kind": "select"},
|
|
337
|
+
},
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
normalized = (
|
|
342
|
+
transport.AuthenticatedHTTPXClient._normalize_incident_form_field_selection_payload(
|
|
343
|
+
payload
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
assert normalized == payload
|
|
348
|
+
|
|
349
|
+
def test_normalize_incident_form_field_selection_list_payload(self):
|
|
350
|
+
payload = {
|
|
351
|
+
"data": [
|
|
352
|
+
{
|
|
353
|
+
"id": "selection-3",
|
|
354
|
+
"type": "incident_form_field_selections",
|
|
355
|
+
"attributes": {
|
|
356
|
+
"value": "External user impact only",
|
|
357
|
+
"selected_groups": {"id": None, "value": "External user impact only"},
|
|
358
|
+
"selected_group_ids": [],
|
|
359
|
+
"form_field": {"input_kind": "textarea"},
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"id": "selection-4",
|
|
364
|
+
"type": "incident_form_field_selections",
|
|
365
|
+
"attributes": {
|
|
366
|
+
"selected_options": {"id": "opt-1", "value": "Database"},
|
|
367
|
+
"selected_option_ids": ["opt-1"],
|
|
368
|
+
"form_field": {"input_kind": "select"},
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
]
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
normalized = (
|
|
375
|
+
transport.AuthenticatedHTTPXClient._normalize_incident_form_field_selection_payload(
|
|
376
|
+
payload
|
|
377
|
+
)
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
assert "selected_groups" not in normalized["data"][0]["attributes"]
|
|
381
|
+
assert normalized["data"][1]["attributes"]["selected_options"] == {
|
|
382
|
+
"id": "opt-1",
|
|
383
|
+
"value": "Database",
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
def test_maybe_normalize_incident_form_field_selection_response(self):
|
|
387
|
+
response = httpx.Response(
|
|
388
|
+
200,
|
|
389
|
+
json={
|
|
390
|
+
"data": {
|
|
391
|
+
"id": "selection-5",
|
|
392
|
+
"type": "incident_form_field_selections",
|
|
393
|
+
"attributes": {
|
|
394
|
+
"value": "External user impact only",
|
|
395
|
+
"selected_groups": {"id": None, "value": "External user impact only"},
|
|
396
|
+
"selected_group_ids": [],
|
|
397
|
+
"form_field": {"input_kind": "textarea"},
|
|
398
|
+
},
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
result = transport.AuthenticatedHTTPXClient._maybe_normalize_incident_form_field_selection_response(
|
|
404
|
+
"PUT", "/v1/incident_form_field_selections/selection-5", response
|
|
405
|
+
)
|
|
406
|
+
parsed = result.json()
|
|
407
|
+
|
|
408
|
+
assert parsed["data"]["attributes"]["value"] == "External user impact only"
|
|
409
|
+
assert parsed["data"]["attributes"]["selected_group_ids"] == []
|
|
410
|
+
assert "selected_groups" not in parsed["data"]["attributes"]
|
|
411
|
+
|
|
412
|
+
def test_maybe_normalize_incident_form_field_selection_list_response(self):
|
|
413
|
+
response = httpx.Response(
|
|
414
|
+
200,
|
|
415
|
+
json={
|
|
416
|
+
"data": [
|
|
417
|
+
{
|
|
418
|
+
"id": "selection-6",
|
|
419
|
+
"type": "incident_form_field_selections",
|
|
420
|
+
"attributes": {
|
|
421
|
+
"value": "External user impact only",
|
|
422
|
+
"selected_groups": {"id": None, "value": "External user impact only"},
|
|
423
|
+
"selected_group_ids": [],
|
|
424
|
+
"form_field": {"input_kind": "textarea"},
|
|
425
|
+
},
|
|
426
|
+
}
|
|
427
|
+
]
|
|
428
|
+
},
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
result = transport.AuthenticatedHTTPXClient._maybe_normalize_incident_form_field_selection_response(
|
|
432
|
+
"GET", "/v1/incidents/inc-123/form_field_selections", response
|
|
433
|
+
)
|
|
434
|
+
parsed = result.json()
|
|
435
|
+
|
|
436
|
+
assert parsed["data"][0]["attributes"]["selected_group_ids"] == []
|
|
437
|
+
assert "selected_groups" not in parsed["data"][0]["attributes"]
|
|
438
|
+
|
|
270
439
|
def test_authenticated_client_user_agent_contains_mode(self):
|
|
271
440
|
with patch.object(
|
|
272
441
|
transport.AuthenticatedHTTPXClient, "_get_api_token", return_value="token"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.github/openapi-audit-remote-baseline.json
RENAMED
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/.github/workflows/dependency-review.yml
RENAMED
|
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
|
|
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.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/data/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/legacy_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/payload_stripping.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/server_defaults.py
RENAMED
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/spec_transform.py
RENAMED
|
File without changes
|
{rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/tools/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/tools/incidents.py
RENAMED
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/src/rootly_mcp_server/tools/resources.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/integration/local/test_smart_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/integration/remote/test_essential.py
RENAMED
|
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.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_legacy_server_module.py
RENAMED
|
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.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_payload_stripping_module.py
RENAMED
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.2.22 → rootly_mcp_server-2.2.24}/tests/unit/test_server_defaults_module.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|