rootly-mcp-server 2.3.1__tar.gz → 2.3.2__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.3.1 → rootly_mcp_server-2.3.2}/CHANGELOG.md +10 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/PKG-INFO +1 -1
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/pyproject.toml +1 -1
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/transport.py +19 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_transport_module.py +71 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.dockerignore +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.gitattributes +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.github/dependabot.yml +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.github/openapi-audit-remote-baseline.json +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.github/workflows/ci.yml +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.github/workflows/dependency-review.yml +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.github/workflows/openapi-audit.yml +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.github/workflows/pypi-release.yml +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.gitignore +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.semaphore/deploy-staging.yml +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.semaphore/deploy.yml +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.semaphore/semaphore.yml +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.semaphore/update-task-definition.sh +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/CONTRIBUTING.md +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/Dockerfile +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/LICENSE +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/README.md +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/examples/skills/README.md +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/gemini-extension.json +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/rootly-mcp-server-demo.gif +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/rootly_openapi.json +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/scripts/audit_openapi.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/scripts/setup-hooks.sh +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/server.json +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/__init__.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/__main__.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/audit.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/client.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/code_mode.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/data/__init__.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/exceptions.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/legacy_server.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/mcp_error.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/monitoring.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/och_client.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/pagination.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/payload_stripping.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/security.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/server.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/server_defaults.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/smart_utils.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/spec_transform.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/tools/__init__.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/tools/alerts.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/tools/incidents.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/tools/oncall.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/tools/resources.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/utils.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/validators.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/README.md +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/conftest.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/integration/local/test_basic.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/integration/local/test_smart_tools.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/integration/local/test_tool_allowlists.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/integration/remote/test_essential.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/test_client.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_alert_stripping.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_authentication.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_code_mode_module.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_exceptions.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_http_headers.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_legacy_server_module.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_main_transport.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_mcp_error_module.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_och_client.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_oncall_handoff.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_oncall_metrics.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_oncall_new_tools.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_oncall_severity_normalization.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_payload_stripping_module.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_security.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_server.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_server_defaults_module.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_smart_utils.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_tools.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_utils.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_validators.py +0 -0
- {rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/uv.lock +0 -0
|
@@ -5,6 +5,16 @@ 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.3.2] - Released 2026-04-23
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Generated Write Payload Forwarding**: Normalized generated OpenAPI write-tool payloads to unwrap top-level `body` envelopes before forwarding to the Rootly API, fixing failures such as `createWorkflow` returning `Invalid payload ({data: "Expected hash"})`
|
|
13
|
+
|
|
14
|
+
### Testing
|
|
15
|
+
|
|
16
|
+
- **Request-Path Regression Coverage**: Added transport regression tests that assert write requests forward unwrapped JSON payloads and preserve already-correct non-envelope payloads
|
|
17
|
+
|
|
8
18
|
## [2.3.1] - Released 2026-04-23
|
|
9
19
|
|
|
10
20
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rootly-mcp-server
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.2
|
|
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.3.
|
|
3
|
+
version = "2.3.2"
|
|
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"
|
|
@@ -651,6 +651,8 @@ class AuthenticatedHTTPXClient:
|
|
|
651
651
|
# Transform query parameters
|
|
652
652
|
if "params" in kwargs:
|
|
653
653
|
kwargs["params"] = self._transform_params(kwargs["params"])
|
|
654
|
+
if "json" in kwargs:
|
|
655
|
+
kwargs["json"] = self._normalize_request_json_payload(method, kwargs["json"])
|
|
654
656
|
|
|
655
657
|
# Log incoming headers for debugging (before transformation)
|
|
656
658
|
incoming_headers = kwargs.get("headers", {})
|
|
@@ -723,6 +725,23 @@ class AuthenticatedHTTPXClient:
|
|
|
723
725
|
|
|
724
726
|
return response
|
|
725
727
|
|
|
728
|
+
@staticmethod
|
|
729
|
+
def _normalize_request_json_payload(method: str, payload: Any) -> Any:
|
|
730
|
+
"""Normalize JSON payloads forwarded by generated tools.
|
|
731
|
+
|
|
732
|
+
Some generated write tools expose a top-level `body` parameter, which can
|
|
733
|
+
result in JSON payloads of shape `{"body": {...}}`. Rootly API write
|
|
734
|
+
endpoints expect the inner object as the actual request body.
|
|
735
|
+
"""
|
|
736
|
+
if method.upper() not in {"POST", "PUT", "PATCH"}:
|
|
737
|
+
return payload
|
|
738
|
+
if not isinstance(payload, dict):
|
|
739
|
+
return payload
|
|
740
|
+
body = payload.get("body")
|
|
741
|
+
if len(payload) == 1 and isinstance(body, dict):
|
|
742
|
+
return body
|
|
743
|
+
return payload
|
|
744
|
+
|
|
726
745
|
@staticmethod
|
|
727
746
|
def _is_alert_endpoint(url: str) -> bool:
|
|
728
747
|
"""Check if a URL is an alert endpoint (but not alert sub-resources like events)."""
|
|
@@ -523,6 +523,54 @@ class TestTransportModule:
|
|
|
523
523
|
assert error_context["upstream_path"] == "/v1/alerts"
|
|
524
524
|
assert error_context["upstream_log_level"] == "error"
|
|
525
525
|
|
|
526
|
+
@pytest.mark.asyncio
|
|
527
|
+
async def test_authenticated_client_unwraps_body_envelope_on_write_requests(self):
|
|
528
|
+
response = httpx.Response(
|
|
529
|
+
200,
|
|
530
|
+
request=httpx.Request("POST", "https://api.rootly.com/v1/workflows"),
|
|
531
|
+
content=b'{"data":{"id":"wf-1"}}',
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
with patch.object(
|
|
535
|
+
transport.AuthenticatedHTTPXClient, "_get_api_token", return_value="token"
|
|
536
|
+
):
|
|
537
|
+
client = transport.AuthenticatedHTTPXClient(hosted=False, transport="stdio")
|
|
538
|
+
client.client.request = AsyncMock(return_value=response)
|
|
539
|
+
|
|
540
|
+
await client.request(
|
|
541
|
+
"POST",
|
|
542
|
+
"/v1/workflows",
|
|
543
|
+
json={"body": {"genius_workflow": {"name": "MCP verification workflow"}}},
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
_, kwargs = client.client.request.call_args
|
|
547
|
+
assert kwargs["json"] == {"genius_workflow": {"name": "MCP verification workflow"}}
|
|
548
|
+
|
|
549
|
+
@pytest.mark.asyncio
|
|
550
|
+
async def test_authenticated_client_preserves_non_envelope_payload_on_update_requests(self):
|
|
551
|
+
response = httpx.Response(
|
|
552
|
+
200,
|
|
553
|
+
request=httpx.Request("PATCH", "https://api.rootly.com/v1/workflows/wf-1"),
|
|
554
|
+
content=b'{"data":{"id":"wf-1"}}',
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
with patch.object(
|
|
558
|
+
transport.AuthenticatedHTTPXClient, "_get_api_token", return_value="token"
|
|
559
|
+
):
|
|
560
|
+
client = transport.AuthenticatedHTTPXClient(hosted=False, transport="stdio")
|
|
561
|
+
client.client.request = AsyncMock(return_value=response)
|
|
562
|
+
|
|
563
|
+
await client.request(
|
|
564
|
+
"PATCH",
|
|
565
|
+
"/v1/workflows/wf-1",
|
|
566
|
+
json={"data": {"type": "workflows", "attributes": {"name": "Updated"}}},
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
_, kwargs = client.client.request.call_args
|
|
570
|
+
assert kwargs["json"] == {
|
|
571
|
+
"data": {"type": "workflows", "attributes": {"name": "Updated"}}
|
|
572
|
+
}
|
|
573
|
+
|
|
526
574
|
def test_sanitize_log_excerpt_redacts_tokens_and_paths(self):
|
|
527
575
|
excerpt = transport._sanitize_log_excerpt(
|
|
528
576
|
'Bearer rootly_1234567890 File "/Users/spencercheng/app.py" failed'
|
|
@@ -560,6 +608,29 @@ class TestTransportModule:
|
|
|
560
608
|
assert result["data"][0]["relationships"]["alerts"] == {"count": 2}
|
|
561
609
|
assert "included" not in result
|
|
562
610
|
|
|
611
|
+
def test_normalize_request_json_payload_unwraps_body_for_write_methods(self):
|
|
612
|
+
payload = {"body": {"genius_workflow": {"name": "workflow-name"}}}
|
|
613
|
+
|
|
614
|
+
result = transport.AuthenticatedHTTPXClient._normalize_request_json_payload("POST", payload)
|
|
615
|
+
|
|
616
|
+
assert result == {"genius_workflow": {"name": "workflow-name"}}
|
|
617
|
+
|
|
618
|
+
def test_normalize_request_json_payload_does_not_unwrap_for_get(self):
|
|
619
|
+
payload = {"body": {"query": "database timeout"}}
|
|
620
|
+
|
|
621
|
+
result = transport.AuthenticatedHTTPXClient._normalize_request_json_payload("GET", payload)
|
|
622
|
+
|
|
623
|
+
assert result == payload
|
|
624
|
+
|
|
625
|
+
def test_normalize_request_json_payload_keeps_non_envelope_dict(self):
|
|
626
|
+
payload = {"data": {"type": "incidents"}, "body": {"ignored": True}}
|
|
627
|
+
|
|
628
|
+
result = transport.AuthenticatedHTTPXClient._normalize_request_json_payload(
|
|
629
|
+
"PATCH", payload
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
assert result == payload
|
|
633
|
+
|
|
563
634
|
def test_strip_heavy_user_data_keeps_profile_essentials(self):
|
|
564
635
|
data = {
|
|
565
636
|
"data": [
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/.github/openapi-audit-remote-baseline.json
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
|
|
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.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/payload_stripping.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/server_defaults.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/src/rootly_mcp_server/tools/incidents.py
RENAMED
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/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.3.1 → rootly_mcp_server-2.3.2}/tests/integration/local/test_smart_tools.py
RENAMED
|
File without changes
|
{rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/integration/local/test_tool_allowlists.py
RENAMED
|
File without changes
|
{rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/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
|
|
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.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_oncall_severity_normalization.py
RENAMED
|
File without changes
|
{rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_payload_stripping_module.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.3.1 → rootly_mcp_server-2.3.2}/tests/unit/test_server_defaults_module.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|