rootly-mcp-server 2.3.4__tar.gz → 2.3.8__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.4 → rootly_mcp_server-2.3.8}/.github/workflows/dependency-review.yml +1 -1
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.gitignore +1 -1
- rootly_mcp_server-2.3.8/.mcp.json +10 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/CHANGELOG.md +94 -34
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/Dockerfile +1 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/PKG-INFO +4 -4
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/pyproject.toml +4 -4
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/__main__.py +33 -6
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/server.py +88 -10
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/server_defaults.py +1 -0
- rootly_mcp_server-2.3.8/src/rootly_mcp_server/tools/alerts.py +87 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/tools/incidents.py +170 -8
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/tools/oncall.py +280 -310
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/tools/resources.py +12 -4
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/transport.py +117 -9
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/utils.py +52 -0
- rootly_mcp_server-2.3.8/tests/unit/test_alert_tools.py +135 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_main_transport.py +60 -2
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_oncall_handoff.py +364 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_server.py +43 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_tools.py +437 -2
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_transport_module.py +300 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/uv.lock +138 -121
- rootly_mcp_server-2.3.4/src/rootly_mcp_server/tools/alerts.py +0 -105
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.dockerignore +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.gitattributes +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.github/dependabot.yml +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.github/openapi-audit-remote-baseline.json +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.github/workflows/ci.yml +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.github/workflows/openapi-audit.yml +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.github/workflows/pypi-release.yml +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.semaphore/deploy-staging.yml +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.semaphore/deploy.yml +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.semaphore/semaphore.yml +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.semaphore/update-task-definition.sh +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/CONTRIBUTING.md +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/LICENSE +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/README.md +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/examples/skills/README.md +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/gemini-extension.json +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/rootly-mcp-server-demo.gif +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/rootly_openapi.json +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/scripts/audit_openapi.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/scripts/setup-hooks.sh +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/server.json +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/__init__.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/audit.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/client.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/code_mode.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/data/__init__.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/exceptions.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/legacy_server.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/mcp_error.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/monitoring.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/och_client.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/pagination.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/payload_stripping.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/security.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/smart_utils.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/spec_transform.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/tools/__init__.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/validators.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/README.md +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/conftest.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/integration/local/test_basic.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/integration/local/test_smart_tools.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/integration/local/test_tool_allowlists.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/integration/remote/test_essential.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/test_client.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_alert_stripping.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_authentication.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_code_mode_module.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_exceptions.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_http_headers.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_legacy_server_module.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_mcp_error_module.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_och_client.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_oncall_metrics.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_oncall_new_tools.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_oncall_severity_normalization.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_payload_stripping_module.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_security.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_server_defaults_module.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_smart_utils.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_utils.py +0 -0
- {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_validators.py +0 -0
|
@@ -5,69 +5,129 @@ 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.
|
|
8
|
+
## [2.3.8] - Released 2026-05-19
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
|
|
12
|
+
- **Bearer Auth Enforcement on Hosted Transports**: Required Bearer authentication on MCP transport paths in hosted mode so unauthenticated requests are rejected before any tool dispatch
|
|
13
|
+
- **Upstream Token Validation**: Validated Bearer tokens against the upstream Rootly API before granting MCP access, surfacing malformed or expired credentials at the edge with clearer errors
|
|
14
|
+
- **Dependency Advisories**: Bumped `urllib3` (≥2.7.0), `python-multipart` (≥0.0.27), and `pip` (≥26.1) to resolve five open Dependabot advisories (urllib3 CVE-2026-44431/44432, python-multipart CVE-2026-42561, pip CVE-2026-6357/3219)
|
|
15
|
+
|
|
16
|
+
### Performance
|
|
17
|
+
|
|
18
|
+
- **`get_alert_by_short_id` Now O(1)**: Replaced the up-to-20-page sequential scan with a single direct `GET /v1/alerts/{short_id}` point lookup. P95 spikes of 41s on this tool collapse to ~200ms; not-found responses no longer cost 10+ seconds; workspaces with >2,000 alerts no longer silently fail
|
|
19
|
+
- **`get_oncall_handoff_summary` Fan-Out Parallelized**: Replaced sequential per-schedule shift and incident fetches with `asyncio.gather` capped by a `Semaphore(10)`. Worst-case p95 of ~15s on populated workspaces drops to ~1–2s
|
|
20
|
+
- **Lookup-Maps Helper Parallelized**: `_fetch_users_and_schedules_maps` (used by `list_shifts`, `check_responder_availability`, `check_oncall_health_risk`, `create_override_recommendation`, `find_related_incidents`) now runs the three independent resource fetches concurrently with intra-resource page fan-out. Cold-cache worst case drops from ~15s to ~1s — meaningfully more impactful since v2.3.7 made sessions stateless and the in-memory cache is repaid more often
|
|
21
|
+
- **Shared `_fetch_all_pages` Helper Across Five Shift Tools**: `list_shifts`, `get_oncall_schedule_summary`, `check_responder_availability`, `create_override_recommendation`, and `check_oncall_health_risk` now all route through the same paginated, concurrency-capped helper instead of duplicating sequential pagination loops. Each tool saves 2–5s worst case under load
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **`get_oncall_shift_metrics` Silent Data Loss**: The function previously fetched only page 1 of schedules and teams (no pagination loop), causing team-id filtering to silently drop schedules whose teams lived past page 1. Now uses the shared paginated, cached helper so all schedules are considered
|
|
26
|
+
- **OAuth Authorization Server Discovery**: Derived the OAuth authorization server URL from the API base URL so discovery works against staging and self-hosted Rootly instances
|
|
27
|
+
- **OAuth Protected Resource Metadata**: Removed `scopes_supported` from the protected resource metadata, advertised only the granular scopes the resource actually accepts, and included resource-specific scopes for clients that key off them
|
|
28
|
+
- **Pagination Resilience**: `asyncio.gather` callsites that fan out pages now use `return_exceptions=True` with an `isinstance(_, BaseException)` guard so a single transient httpx error on one page no longer aborts the entire handler
|
|
29
|
+
- **Missing `total_pages` Metadata Fallback**: When upstream omits `meta.total_pages`, the helper now falls back to fetching up to `max_pages` (preserving the legacy "keep going until a short page" semantics) instead of silently truncating to page 1
|
|
30
|
+
|
|
31
|
+
### Dependencies
|
|
32
|
+
|
|
33
|
+
- **Routine Refresh**: Pulled in the latest minor and patch dependency updates from Dependabot
|
|
34
|
+
- **CI Action Bump**: Updated `actions/dependency-review-action` from 4.9.0 to 5.0.0
|
|
35
|
+
|
|
36
|
+
### Repo
|
|
37
|
+
|
|
38
|
+
- **`.claude/` Ignored**: Stopped tracking transient `.claude/worktrees/` directories and added `.claude/` to `.gitignore` so local Claude Code state no longer pollutes diffs
|
|
39
|
+
|
|
40
|
+
### Testing
|
|
41
|
+
|
|
42
|
+
- New unit tests for the parallelized fan-out paths, the missing-`total_pages` fallback, exception-tolerance in `asyncio.gather`, the `get_oncall_shift_metrics` pagination regression, and the direct-GET path for `get_alert_by_short_id`
|
|
43
|
+
|
|
44
|
+
## [2.3.7] - Released 2026-05-08
|
|
9
45
|
|
|
10
46
|
### Features
|
|
11
47
|
|
|
12
|
-
- **
|
|
48
|
+
- **MCP OAuth2 Authorization Discovery**: Added hosted-only OAuth protected resource metadata and `WWW-Authenticate` discovery headers so compatible MCP clients can discover Rootly's authorization server automatically
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- **Hosted Streamable HTTP Session Accumulation**: Defaulted hosted streamable HTTP to stateless mode so the server no longer accumulates long-lived MCP sessions when clients do not explicitly terminate them
|
|
53
|
+
- **Full Hosted HTTP Coverage**: Applied the stateless hosted default to both dual-transport deployments and direct hosted `streamable-http` mode for consistent leak mitigation
|
|
54
|
+
|
|
55
|
+
### Configuration
|
|
56
|
+
|
|
57
|
+
- **Hosted Docker Default**: Set `FASTMCP_STATELESS_HTTP=true` in the Docker image so standard hosted deployments use the safer stateless configuration by default while still allowing explicit operator override
|
|
58
|
+
|
|
59
|
+
### Testing
|
|
60
|
+
|
|
61
|
+
- **Hosted Transport Coverage**: Added focused tests for hosted stateless defaults and explicit `FASTMCP_STATELESS_HTTP` override behavior across both transport entry paths
|
|
62
|
+
|
|
63
|
+
## [2.3.6] - Released 2026-05-07
|
|
64
|
+
|
|
65
|
+
### Features
|
|
66
|
+
|
|
67
|
+
- **Open Plugins Discovery Support**: Added a root `.mcp.json` so plugin catalogs like cursor.directory can auto-detect the hosted Rootly MCP configuration and generate one-click install flows
|
|
13
68
|
|
|
14
69
|
### Dependencies
|
|
15
70
|
|
|
16
71
|
- **Routine Dependency Refresh**: Pulled in the latest minor and patch dependency updates from Dependabot
|
|
17
72
|
|
|
18
|
-
## [2.3.
|
|
73
|
+
## [2.3.5] - Released 2026-05-07
|
|
19
74
|
|
|
20
75
|
### Fixed
|
|
21
76
|
|
|
22
|
-
- **
|
|
77
|
+
- **Sequential Incident Reference Resolution**: Added server-side resolution for incident references like `4460`, `#4460`, and `INC-4460`, so callers no longer need to convert sequential incident numbers to UUIDs themselves
|
|
78
|
+
- **Broader Incident Tool Support**: Applied the same reference resolution behavior across `getIncident`, `updateIncident`, `find_related_incidents`, `suggest_solutions`, and the `incident://{incident_id}` resource for a more consistent incident workflow
|
|
23
79
|
|
|
24
80
|
### Testing
|
|
25
81
|
|
|
26
|
-
- **
|
|
82
|
+
- **Incident Reference Coverage**: Added focused test coverage for UUID and sequential incident references, not-found handling, bounded lookup behavior, and the expanded incident tool/resource paths
|
|
27
83
|
|
|
28
|
-
## [2.3.
|
|
84
|
+
## [2.3.4] - Released 2026-04-30
|
|
85
|
+
|
|
86
|
+
### Features
|
|
87
|
+
|
|
88
|
+
- **Incident Timeline Events Enabled by Default**: Enabled creating incident timeline events by default, so timeline entries can now be created through MCP without extra configuration
|
|
89
|
+
|
|
90
|
+
### Dependencies
|
|
91
|
+
|
|
92
|
+
- **Routine Dependency Refresh**: Pulled in the latest minor and patch dependency updates from Dependabot
|
|
93
|
+
|
|
94
|
+
## [2.3.3] - Released 2026-04-23
|
|
29
95
|
|
|
30
96
|
### Fixed
|
|
31
97
|
|
|
32
|
-
- **
|
|
33
|
-
- **Write
|
|
34
|
-
- **
|
|
98
|
+
- **Schema and Path Interpretation**: Improved how the MCP interprets API schemas and path patterns so more tools can be exposed correctly
|
|
99
|
+
- **Write Path Coverage**: Fixed write-path coverage gaps so generated tools line up more reliably with endpoints that support write actions
|
|
100
|
+
- **Write Availability Guidance**: Improved the guidance returned when a write action is not available for the current endpoint or configuration
|
|
101
|
+
- **Hyphenated Resource Matching**: Tightened path matching so hyphenated resources like `status-pages` are handled correctly
|
|
35
102
|
|
|
36
|
-
|
|
103
|
+
## [2.3.2] - Released 2026-04-23
|
|
37
104
|
|
|
38
|
-
|
|
39
|
-
- Security exclusions for sensitive families remain unchanged
|
|
105
|
+
### Fixed
|
|
40
106
|
|
|
41
|
-
|
|
107
|
+
- **Generated Write Request Shape**: Fixed how generated MCP tools send create and update requests for API-backed endpoints
|
|
108
|
+
- **Affected Endpoints**: This affected tools tied to endpoints like `/v1/workflows`, `/v1/workflow_groups`, `/v1/schedules`, and other generated write operations that expected a specific request shape
|
|
109
|
+
- **Create Workflow Reliability**: As a result, tools like `createWorkflow` can complete successfully instead of stopping during request submission
|
|
42
110
|
|
|
43
|
-
###
|
|
111
|
+
### Testing
|
|
44
112
|
|
|
45
|
-
- **
|
|
46
|
-
- **Enhanced Tool Discoverability**: Added contextual "WHEN TO USE" guidance to tool descriptions for improved AI agent decision-making
|
|
47
|
-
- **Workflow-Focused Tool Subsets**: Introduced 5 predefined tool configurations optimized for specific use cases (Incident Response, Schedule Management, Monitoring Setup, Retrospectives, Administration)
|
|
48
|
-
- **Operational Context Resources**: Added new MCP resources for real-time operational awareness:
|
|
49
|
-
- `rootly://oncall-status` - Live on-call status across teams for incident response context
|
|
50
|
-
- `rootly://workflow-guide` - Step-by-step workflow guidance for common Rootly operations
|
|
51
|
-
- **Expanded Write Operations**: Added 54 non-destructive write operations while maintaining security boundaries
|
|
52
|
-
- **API Path Coverage**: Increased DEFAULT_ALLOWED_PATHS from ~80 to 151 endpoints covering workflows, dashboards, communications, playbooks, and monitoring
|
|
113
|
+
- **Request-Path Regression Coverage**: Added transport regression tests that assert write requests forward unwrapped JSON payloads and preserve already-correct non-envelope payloads
|
|
53
114
|
|
|
54
|
-
|
|
115
|
+
## [2.3.1] - Released 2026-04-23
|
|
55
116
|
|
|
56
|
-
|
|
57
|
-
- **Environment Configuration**: Improved environment-based tool subset configuration following MCP best practices
|
|
58
|
-
- **Error Handling**: Robust error handling in new resource implementations with proper categorization
|
|
117
|
+
### Fixed
|
|
59
118
|
|
|
60
|
-
|
|
119
|
+
- **Restored Create Actions**: Restored missing create actions for key configuration endpoints, including workflows, workflow groups, schedules, schedule rotations, escalation policies, escalation paths, services, teams, and environments
|
|
120
|
+
- **Create and Update Parity**: Closed the gap where users could list or update those records but could not create new ones through MCP
|
|
121
|
+
- **Test Accuracy**: Corrected alert source tool-name assertions in unit tests (`createAlertSource` / `updateAlertSource`)
|
|
61
122
|
|
|
62
|
-
|
|
63
|
-
- **Configuration Examples**: Added 5 workflow-focused environment configurations for optimal AI performance
|
|
64
|
-
- **API Coverage**: Documented comprehensive endpoint coverage while highlighting security exclusions
|
|
123
|
+
## [2.3.0] - Released 2026-04-23
|
|
65
124
|
|
|
66
|
-
###
|
|
125
|
+
### Features
|
|
67
126
|
|
|
68
|
-
- **
|
|
69
|
-
- **
|
|
70
|
-
- **
|
|
127
|
+
- **Broader API Coverage**: Expanded the MCP to cover many more Rootly API areas, including workflows, workflow groups, workflow tasks, schedules, schedule rotations, escalation policies, escalation paths, services, teams, environments, dashboards, playbooks, retrospectives, and monitoring-related resources
|
|
128
|
+
- **More Write Actions**: Added more write actions across those areas, especially update actions, so the MCP can do more than just read data
|
|
129
|
+
- **Wider Operational Use**: Made the MCP useful for more operational and configuration work, not just incident search and on-call lookups
|
|
130
|
+
- **Workflow-Focused Tooling**: Introduced workflow-focused tool subsets and supporting resources for common MCP use cases
|
|
71
131
|
|
|
72
132
|
### Testing
|
|
73
133
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rootly-mcp-server
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.8
|
|
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
|
|
@@ -24,14 +24,14 @@ Requires-Python: >=3.12
|
|
|
24
24
|
Requires-Dist: authlib>=1.6.11
|
|
25
25
|
Requires-Dist: brotli>=1.2.0
|
|
26
26
|
Requires-Dist: cryptography>=46.0.7
|
|
27
|
-
Requires-Dist: fastmcp[code-mode]==3.2.
|
|
27
|
+
Requires-Dist: fastmcp[code-mode]==3.2.4
|
|
28
28
|
Requires-Dist: httpx==0.28.1
|
|
29
29
|
Requires-Dist: numpy>=1.24.0
|
|
30
|
-
Requires-Dist: pydantic==2.
|
|
30
|
+
Requires-Dist: pydantic==2.13.4
|
|
31
31
|
Requires-Dist: pygments>=2.20.0
|
|
32
32
|
Requires-Dist: python-dotenv>=1.2.2
|
|
33
33
|
Requires-Dist: python-multipart>=0.0.26
|
|
34
|
-
Requires-Dist: requests==2.
|
|
34
|
+
Requires-Dist: requests==2.34.0
|
|
35
35
|
Requires-Dist: scikit-learn>=1.3.0
|
|
36
36
|
Provides-Extra: dev
|
|
37
37
|
Requires-Dist: bandit>=1.7.0; extra == 'dev'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "rootly-mcp-server"
|
|
3
|
-
version = "2.3.
|
|
3
|
+
version = "2.3.8"
|
|
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"
|
|
@@ -36,15 +36,15 @@ classifiers = [
|
|
|
36
36
|
"License :: OSI Approved :: Apache Software License",
|
|
37
37
|
]
|
|
38
38
|
dependencies = [
|
|
39
|
-
"fastmcp[code-mode]==3.2.
|
|
39
|
+
"fastmcp[code-mode]==3.2.4", # Pin to patched version with Code Mode support
|
|
40
40
|
"authlib>=1.6.11", # Override vulnerable transitive authlib (GHSA-jj8c-mmj3-mmgv)
|
|
41
41
|
"python-dotenv>=1.2.2", # Override vulnerable transitive python-dotenv (GHSA-m8f7-34r5-grfg)
|
|
42
42
|
"python-multipart>=0.0.26", # Override vulnerable transitive python-multipart (CVE-2026-40347)
|
|
43
43
|
"cryptography>=46.0.7", # Override vulnerable transitive cryptography
|
|
44
44
|
"Pygments>=2.20.0", # Override vulnerable transitive Pygments
|
|
45
|
-
"requests==2.
|
|
45
|
+
"requests==2.34.0", # Pin to patched version
|
|
46
46
|
"httpx==0.28.1", # Pin to specific version
|
|
47
|
-
"pydantic==2.
|
|
47
|
+
"pydantic==2.13.4", # Pin to specific version
|
|
48
48
|
"brotli>=1.2.0", # For Brotli compression support in httpx
|
|
49
49
|
"scikit-learn>=1.3.0", # For text similarity and ML utilities
|
|
50
50
|
"numpy>=1.24.0", # For numerical operations
|
|
@@ -63,6 +63,20 @@ def normalize_transport_or_default(value: str, default: TransportName = "stdio")
|
|
|
63
63
|
return default
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
def streamable_http_stateless_enabled(*, hosted: bool, fastmcp_stateless_http: bool) -> bool:
|
|
67
|
+
"""Choose streamable HTTP session mode with a safe hosted default.
|
|
68
|
+
|
|
69
|
+
Hosted streamable HTTP traffic is high-churn and most clients do not send
|
|
70
|
+
DELETE to close MCP sessions. On current MCP SDK versions that leaks
|
|
71
|
+
stateful session transports until process restart. We therefore default
|
|
72
|
+
hosted deployments to stateless mode unless the operator explicitly sets
|
|
73
|
+
``FASTMCP_STATELESS_HTTP``.
|
|
74
|
+
"""
|
|
75
|
+
if "FASTMCP_STATELESS_HTTP" in os.environ:
|
|
76
|
+
return fastmcp_stateless_http
|
|
77
|
+
return hosted
|
|
78
|
+
|
|
79
|
+
|
|
66
80
|
def parse_args():
|
|
67
81
|
"""Parse command-line arguments."""
|
|
68
82
|
parser = argparse.ArgumentParser(description="Start the Rootly MCP server for API integration.")
|
|
@@ -259,7 +273,12 @@ def run_dual_http_server(
|
|
|
259
273
|
sse_path = fastmcp.settings.sse_path
|
|
260
274
|
streamable_path = fastmcp.settings.streamable_http_path
|
|
261
275
|
message_path = fastmcp.settings.message_path
|
|
262
|
-
stateless_http =
|
|
276
|
+
stateless_http = streamable_http_stateless_enabled(
|
|
277
|
+
hosted=True, fastmcp_stateless_http=fastmcp.settings.stateless_http
|
|
278
|
+
)
|
|
279
|
+
logger.info(
|
|
280
|
+
"Streamable HTTP configured in %s mode", "stateless" if stateless_http else "stateful"
|
|
281
|
+
)
|
|
263
282
|
|
|
264
283
|
sse_transport = SseServerTransport(message_path)
|
|
265
284
|
|
|
@@ -453,6 +472,11 @@ def main():
|
|
|
453
472
|
logger.info("Code Mode enabled at path: %s", code_mode_path)
|
|
454
473
|
|
|
455
474
|
logger.info(f"Running server with transport: {normalized_transport}...")
|
|
475
|
+
direct_streamable_stateless_http = streamable_http_stateless_enabled(
|
|
476
|
+
hosted=hosted_mode,
|
|
477
|
+
fastmcp_stateless_http=os.getenv("FASTMCP_STATELESS_HTTP", "").lower()
|
|
478
|
+
in ("true", "1", "yes"),
|
|
479
|
+
)
|
|
456
480
|
if normalized_transport == "both":
|
|
457
481
|
run_dual_http_server(
|
|
458
482
|
server=server,
|
|
@@ -464,13 +488,16 @@ def main():
|
|
|
464
488
|
elif normalized_transport == "stdio":
|
|
465
489
|
server.run(transport=normalized_transport)
|
|
466
490
|
else:
|
|
467
|
-
|
|
468
|
-
transport
|
|
469
|
-
middleware
|
|
491
|
+
run_kwargs = {
|
|
492
|
+
"transport": normalized_transport,
|
|
493
|
+
"middleware": get_hosted_auth_middleware(),
|
|
470
494
|
# Override FastMCP's default of 0s to allow active SSE connections
|
|
471
495
|
# to finish gracefully during deployments (avoids 502s).
|
|
472
|
-
uvicorn_config
|
|
473
|
-
|
|
496
|
+
"uvicorn_config": {"timeout_graceful_shutdown": 30},
|
|
497
|
+
}
|
|
498
|
+
if normalized_transport == "streamable-http":
|
|
499
|
+
run_kwargs["stateless_http"] = direct_streamable_stateless_http
|
|
500
|
+
server.run(**run_kwargs)
|
|
474
501
|
|
|
475
502
|
except FileNotFoundError as e:
|
|
476
503
|
logger.error(f"File not found: {e}")
|
|
@@ -25,7 +25,14 @@ from .tools.alerts import register_alert_tools
|
|
|
25
25
|
from .tools.incidents import register_incident_tools
|
|
26
26
|
from .tools.oncall import register_oncall_tools
|
|
27
27
|
from .tools.resources import register_resource_handlers
|
|
28
|
-
from .utils import
|
|
28
|
+
from .utils import (
|
|
29
|
+
OAUTH_PROTECTED_RESOURCE_PATH,
|
|
30
|
+
auth_header_state,
|
|
31
|
+
derive_oauth_server_url,
|
|
32
|
+
is_mcp_server_url_static,
|
|
33
|
+
resolve_mcp_server_url,
|
|
34
|
+
sanitize_parameters_in_spec,
|
|
35
|
+
)
|
|
29
36
|
|
|
30
37
|
# Set up logger
|
|
31
38
|
logger = logging.getLogger(__name__)
|
|
@@ -84,15 +91,7 @@ def _fingerprint_auth_header(auth_header: str) -> str:
|
|
|
84
91
|
|
|
85
92
|
def _auth_header_state(auth_header: str) -> str:
|
|
86
93
|
"""Classify Authorization header shape without exposing token contents."""
|
|
87
|
-
|
|
88
|
-
if not raw:
|
|
89
|
-
return "missing"
|
|
90
|
-
parts = raw.split(None, 1)
|
|
91
|
-
if not parts or parts[0].lower() != "bearer":
|
|
92
|
-
return "invalid_format"
|
|
93
|
-
if len(parts) == 1 or not parts[1].strip():
|
|
94
|
-
return "missing_token"
|
|
95
|
-
return "bearer"
|
|
94
|
+
return auth_header_state(auth_header)
|
|
96
95
|
|
|
97
96
|
|
|
98
97
|
def _validate_bearer_auth_header(auth_header: str) -> str:
|
|
@@ -541,6 +540,85 @@ def create_rootly_mcp_server(
|
|
|
541
540
|
|
|
542
541
|
return PlainTextResponse("OK")
|
|
543
542
|
|
|
543
|
+
# OAuth 2.0 Protected Resource Metadata (RFC 9728)
|
|
544
|
+
# MCP clients fetch this to discover which authorization server to use.
|
|
545
|
+
if hosted:
|
|
546
|
+
from starlette.responses import JSONResponse
|
|
547
|
+
|
|
548
|
+
async def _oauth_protected_resource_handler(request):
|
|
549
|
+
mcp_server_url = resolve_mcp_server_url(request)
|
|
550
|
+
|
|
551
|
+
cache = "max-age=3600" if is_mcp_server_url_static() else "no-store"
|
|
552
|
+
return JSONResponse(
|
|
553
|
+
{
|
|
554
|
+
"resource": mcp_server_url,
|
|
555
|
+
"authorization_servers": [derive_oauth_server_url(base_url)],
|
|
556
|
+
"scopes_supported": [
|
|
557
|
+
"openid",
|
|
558
|
+
"profile",
|
|
559
|
+
"email",
|
|
560
|
+
"ir.incidents:read",
|
|
561
|
+
"ir.incidents:write",
|
|
562
|
+
"ir.services:read",
|
|
563
|
+
"ir.services:write",
|
|
564
|
+
"ir.environments:read",
|
|
565
|
+
"ir.environments:write",
|
|
566
|
+
"ir.functionalities:read",
|
|
567
|
+
"ir.functionalities:write",
|
|
568
|
+
"ir.severities:read",
|
|
569
|
+
"ir.severities:write",
|
|
570
|
+
"ir.incident_types:read",
|
|
571
|
+
"ir.incident_types:write",
|
|
572
|
+
"ir.incident_roles:read",
|
|
573
|
+
"ir.incident_roles:write",
|
|
574
|
+
"ir.workflows:read",
|
|
575
|
+
"ir.workflows:write",
|
|
576
|
+
"ir.catalogs:read",
|
|
577
|
+
"ir.catalogs:write",
|
|
578
|
+
"ir.groups:read",
|
|
579
|
+
"ir.groups:write",
|
|
580
|
+
"ir.playbooks:read",
|
|
581
|
+
"ir.playbooks:write",
|
|
582
|
+
"ir.retrospectives:read",
|
|
583
|
+
"ir.retrospectives:write",
|
|
584
|
+
"ir.status_pages:read",
|
|
585
|
+
"ir.status_pages:write",
|
|
586
|
+
"ir.form_fields:read",
|
|
587
|
+
"ir.form_fields:write",
|
|
588
|
+
"ir.pulses:read",
|
|
589
|
+
"ir.pulses:write",
|
|
590
|
+
"oc.alerts:read",
|
|
591
|
+
"oc.alerts:write",
|
|
592
|
+
"oc.schedules:read",
|
|
593
|
+
"oc.schedules:write",
|
|
594
|
+
"oc.escalation_policies:read",
|
|
595
|
+
"oc.escalation_policies:write",
|
|
596
|
+
"oc.alert_routing_rules:read",
|
|
597
|
+
"oc.alert_routing_rules:write",
|
|
598
|
+
"oc.heartbeats:read",
|
|
599
|
+
"oc.heartbeats:write",
|
|
600
|
+
"oc.alert_sources:read",
|
|
601
|
+
"oc.alert_sources:write",
|
|
602
|
+
"oc.live_call_routing:read",
|
|
603
|
+
"oc.live_call_routing:write",
|
|
604
|
+
"oc.shift_overrides:read",
|
|
605
|
+
"oc.shift_overrides:write",
|
|
606
|
+
],
|
|
607
|
+
"bearer_methods_supported": ["header"],
|
|
608
|
+
},
|
|
609
|
+
headers={"Cache-Control": cache},
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
# RFC 9728 §5: clients may request the path-suffixed variant first
|
|
613
|
+
# (e.g. /.well-known/oauth-protected-resource/mcp for a resource at /mcp).
|
|
614
|
+
@mcp.custom_route(OAUTH_PROTECTED_RESOURCE_PATH + "/{path:path}", methods=["GET"])
|
|
615
|
+
async def oauth_protected_resource_suffixed(request):
|
|
616
|
+
return await _oauth_protected_resource_handler(request)
|
|
617
|
+
|
|
618
|
+
@mcp.custom_route(OAUTH_PROTECTED_RESOURCE_PATH, methods=["GET"])
|
|
619
|
+
async def oauth_protected_resource(request):
|
|
620
|
+
return await _oauth_protected_resource_handler(request)
|
|
621
|
+
|
|
544
622
|
# Add some custom tools for enhanced functionality
|
|
545
623
|
|
|
546
624
|
@mcp.tool()
|
{rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/server_defaults.py
RENAMED
|
@@ -349,6 +349,7 @@ DEFAULT_WRITE_ALLOWED_PATHS = [
|
|
|
349
349
|
# Incident mutations
|
|
350
350
|
"/alert_events/{id}",
|
|
351
351
|
"/incidents/{incident_id}/action_items",
|
|
352
|
+
"/incidents/{incident_id}/events",
|
|
352
353
|
"/incidents/{incident_id}/form_field_selections",
|
|
353
354
|
"/incident_form_field_selections/{id}",
|
|
354
355
|
# On-call schedules - create + update
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Alert tool registration for Rootly MCP server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from typing import Annotated, Any, Protocol
|
|
7
|
+
from urllib.parse import quote
|
|
8
|
+
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
|
|
11
|
+
JsonDict = dict[str, Any]
|
|
12
|
+
MakeAuthenticatedRequest = Callable[..., Awaitable[Any]]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MCPErrorLike(Protocol):
|
|
16
|
+
"""Protocol for MCP error helpers used by alert tools."""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def tool_error(
|
|
20
|
+
error_message: str,
|
|
21
|
+
error_type: str = "execution_error",
|
|
22
|
+
details: dict[str, Any] | None = None,
|
|
23
|
+
) -> JsonDict: ...
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def categorize_error(exception: Exception) -> tuple[str, str]: ...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def register_alert_tools(
|
|
30
|
+
mcp: Any,
|
|
31
|
+
make_authenticated_request: MakeAuthenticatedRequest,
|
|
32
|
+
mcp_error: MCPErrorLike,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Register alert tools on the MCP server."""
|
|
35
|
+
|
|
36
|
+
@mcp.tool()
|
|
37
|
+
async def get_alert_by_short_id(
|
|
38
|
+
short_id: Annotated[
|
|
39
|
+
str,
|
|
40
|
+
Field(
|
|
41
|
+
description="The alert short_id (e.g., 'PhIQtP') or full alert URL (e.g., 'https://rootly.com/account/alerts/PhIQtP')"
|
|
42
|
+
),
|
|
43
|
+
],
|
|
44
|
+
) -> JsonDict:
|
|
45
|
+
"""Get alert details by short_id or alert URL. Use this when a user pastes an alert URL or short_id from a pager notification and wants to investigate the alert."""
|
|
46
|
+
try:
|
|
47
|
+
alert_short_id = short_id.strip()
|
|
48
|
+
if "/" in alert_short_id:
|
|
49
|
+
alert_short_id = alert_short_id.rstrip("/").split("/")[-1]
|
|
50
|
+
|
|
51
|
+
if not alert_short_id:
|
|
52
|
+
return mcp_error.tool_error("short_id is required", "validation_error")
|
|
53
|
+
|
|
54
|
+
# GET /v1/alerts/{id} accepts the short_id as well as the UUID
|
|
55
|
+
# (undocumented but supported), so a single point lookup avoids
|
|
56
|
+
# listing/filtering altogether.
|
|
57
|
+
response = await make_authenticated_request(
|
|
58
|
+
"GET", f"/v1/alerts/{quote(alert_short_id, safe='')}"
|
|
59
|
+
)
|
|
60
|
+
if response.status_code == 404:
|
|
61
|
+
return mcp_error.tool_error(
|
|
62
|
+
f"Alert with short_id '{alert_short_id}' not found",
|
|
63
|
+
"not_found",
|
|
64
|
+
)
|
|
65
|
+
response.raise_for_status()
|
|
66
|
+
|
|
67
|
+
data = response.json().get("data", {})
|
|
68
|
+
attrs = data.get("attributes", {})
|
|
69
|
+
return {
|
|
70
|
+
"id": data.get("id"),
|
|
71
|
+
"short_id": attrs.get("short_id"),
|
|
72
|
+
"summary": attrs.get("summary"),
|
|
73
|
+
"status": attrs.get("status"),
|
|
74
|
+
"source": attrs.get("source"),
|
|
75
|
+
"description": attrs.get("description"),
|
|
76
|
+
"started_at": attrs.get("started_at"),
|
|
77
|
+
"ended_at": attrs.get("ended_at"),
|
|
78
|
+
"noise": attrs.get("noise"),
|
|
79
|
+
"url": attrs.get("url"),
|
|
80
|
+
"created_at": attrs.get("created_at"),
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
error_type, error_message = mcp_error.categorize_error(e)
|
|
85
|
+
return mcp_error.tool_error(
|
|
86
|
+
f"Failed to get alert by short_id: {error_message}", error_type
|
|
87
|
+
)
|