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.
Files changed (86) hide show
  1. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.github/workflows/dependency-review.yml +1 -1
  2. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.gitignore +1 -1
  3. rootly_mcp_server-2.3.8/.mcp.json +10 -0
  4. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/CHANGELOG.md +94 -34
  5. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/Dockerfile +1 -0
  6. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/PKG-INFO +4 -4
  7. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/pyproject.toml +4 -4
  8. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/__main__.py +33 -6
  9. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/server.py +88 -10
  10. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/server_defaults.py +1 -0
  11. rootly_mcp_server-2.3.8/src/rootly_mcp_server/tools/alerts.py +87 -0
  12. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/tools/incidents.py +170 -8
  13. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/tools/oncall.py +280 -310
  14. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/tools/resources.py +12 -4
  15. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/transport.py +117 -9
  16. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/utils.py +52 -0
  17. rootly_mcp_server-2.3.8/tests/unit/test_alert_tools.py +135 -0
  18. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_main_transport.py +60 -2
  19. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_oncall_handoff.py +364 -0
  20. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_server.py +43 -0
  21. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_tools.py +437 -2
  22. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_transport_module.py +300 -0
  23. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/uv.lock +138 -121
  24. rootly_mcp_server-2.3.4/src/rootly_mcp_server/tools/alerts.py +0 -105
  25. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.dockerignore +0 -0
  26. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.gitattributes +0 -0
  27. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.github/dependabot.yml +0 -0
  28. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.github/openapi-audit-remote-baseline.json +0 -0
  29. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.github/workflows/ci.yml +0 -0
  30. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.github/workflows/openapi-audit.yml +0 -0
  31. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.github/workflows/pypi-release.yml +0 -0
  32. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.semaphore/deploy-staging.yml +0 -0
  33. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.semaphore/deploy.yml +0 -0
  34. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.semaphore/semaphore.yml +0 -0
  35. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/.semaphore/update-task-definition.sh +0 -0
  36. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/CONTRIBUTING.md +0 -0
  37. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/LICENSE +0 -0
  38. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/README.md +0 -0
  39. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/examples/skills/README.md +0 -0
  40. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/gemini-extension.json +0 -0
  41. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/rootly-mcp-server-demo.gif +0 -0
  42. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/rootly_openapi.json +0 -0
  43. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/scripts/audit_openapi.py +0 -0
  44. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/scripts/setup-hooks.sh +0 -0
  45. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/server.json +0 -0
  46. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/__init__.py +0 -0
  47. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/audit.py +0 -0
  48. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/client.py +0 -0
  49. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/code_mode.py +0 -0
  50. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/data/__init__.py +0 -0
  51. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/exceptions.py +0 -0
  52. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/legacy_server.py +0 -0
  53. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/mcp_error.py +0 -0
  54. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/monitoring.py +0 -0
  55. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/och_client.py +0 -0
  56. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/pagination.py +0 -0
  57. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/payload_stripping.py +0 -0
  58. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/security.py +0 -0
  59. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/smart_utils.py +0 -0
  60. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/spec_transform.py +0 -0
  61. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/tools/__init__.py +0 -0
  62. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/src/rootly_mcp_server/validators.py +0 -0
  63. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/README.md +0 -0
  64. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/conftest.py +0 -0
  65. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/integration/local/test_basic.py +0 -0
  66. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/integration/local/test_smart_tools.py +0 -0
  67. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/integration/local/test_tool_allowlists.py +0 -0
  68. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/integration/remote/test_essential.py +0 -0
  69. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/test_client.py +0 -0
  70. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_alert_stripping.py +0 -0
  71. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_authentication.py +0 -0
  72. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_code_mode_module.py +0 -0
  73. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_exceptions.py +0 -0
  74. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_http_headers.py +0 -0
  75. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_legacy_server_module.py +0 -0
  76. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_mcp_error_module.py +0 -0
  77. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_och_client.py +0 -0
  78. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_oncall_metrics.py +0 -0
  79. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_oncall_new_tools.py +0 -0
  80. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_oncall_severity_normalization.py +0 -0
  81. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_payload_stripping_module.py +0 -0
  82. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_security.py +0 -0
  83. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_server_defaults_module.py +0 -0
  84. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_smart_utils.py +0 -0
  85. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_utils.py +0 -0
  86. {rootly_mcp_server-2.3.4 → rootly_mcp_server-2.3.8}/tests/unit/test_validators.py +0 -0
@@ -12,4 +12,4 @@ jobs:
12
12
  runs-on: ubuntu-latest
13
13
  steps:
14
14
  - name: Dependency Review
15
- uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4
15
+ uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v4
@@ -199,4 +199,4 @@ test_output/
199
199
  !CONTRIBUTING.md
200
200
  !CHANGELOG.md
201
201
  .beads/
202
- .mcp.json
202
+ .claude/
@@ -0,0 +1,10 @@
1
+ {
2
+ "mcpServers": {
3
+ "rootly": {
4
+ "url": "https://mcp.rootly.com/mcp",
5
+ "headers": {
6
+ "Authorization": "Bearer ${ROOTLY_API_TOKEN}"
7
+ }
8
+ }
9
+ }
10
+ }
@@ -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.4] - Released 2026-04-30
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
- - **Incident Timeline Events Enabled by Default**: Exposed incident timeline event creation in the default MCP write surface so agents can add timeline entries without extra configuration
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.2] - Released 2026-04-23
73
+ ## [2.3.5] - Released 2026-05-07
19
74
 
20
75
  ### Fixed
21
76
 
22
- - **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"})`
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
- - **Request-Path Regression Coverage**: Added transport regression tests that assert write requests forward unwrapped JSON payloads and preserve already-correct non-envelope payloads
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.1] - Released 2026-04-23
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
- - **Create Operations Restored**: Restored missing create operations for workflows, schedules, and configuration resources by expanding `DEFAULT_WRITE_ALLOWED_PATHS` to include collection/base paths (not only `/{id}` update paths)
33
- - **Write Surface Completeness**: Re-enabled create + update parity across key families including workflows, workflow groups, schedules, escalation policies, services, teams, environments, dashboards, playbooks, monitoring, retrospectives, communications, causes, and catalogs
34
- - **Test Accuracy**: Corrected alert source tool-name assertions in unit tests (`createAlertSource` / `updateAlertSource`) to prevent false confidence
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
- ### Notes
103
+ ## [2.3.2] - Released 2026-04-23
37
104
 
38
- - `createWorkflow` remains the correct tool name (singular), not `createWorkflows`
39
- - Security exclusions for sensitive families remain unchanged
105
+ ### Fixed
40
106
 
41
- ## [2.3.0] - Released 2026-04-23
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
- ### Features
111
+ ### Testing
44
112
 
45
- - **Comprehensive API Coverage Expansion**: Expanded MCP tool surface from 124 to 150+ tools by updating to latest Rootly API specification with 95+ operations
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
- ### Enhanced
115
+ ## [2.3.1] - Released 2026-04-23
55
116
 
56
- - **Tool Descriptions**: Enhanced incident and on-call tool descriptions with specific usage context and best practices
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
- ### Documentation
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
- - **README Updates**: Updated tool counts, added workflow subset configurations, and enhanced setup guidance
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
- ### Security
125
+ ### Features
67
126
 
68
- - **Maintained Boundaries**: Preserved security exclusions for sensitive operations (API keys, user management, webhooks, financial data)
69
- - **Curated Approach**: Continued read-first philosophy with carefully selected write operations
70
- - **Input Validation**: Maintained existing security controls and validation patterns
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
 
@@ -28,6 +28,7 @@ EXPOSE 8000
28
28
  ENV PYTHONUNBUFFERED=1
29
29
  ENV FASTMCP_HOST=0.0.0.0
30
30
  ENV FASTMCP_PORT=8000
31
+ ENV FASTMCP_STATELESS_HTTP=true
31
32
  ENV ROOTLY_TRANSPORT=both
32
33
  ENV ROOTLY_LOG_LEVEL=INFO
33
34
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rootly-mcp-server
3
- Version: 2.3.4
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.0
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.12.5
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.33.1
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.4"
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.0", # Pin to patched version with Code Mode support
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.33.1", # Pin to patched version
45
+ "requests==2.34.0", # Pin to patched version
46
46
  "httpx==0.28.1", # Pin to specific version
47
- "pydantic==2.12.5", # Pin to specific version
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 = fastmcp.settings.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
- server.run(
468
- transport=normalized_transport,
469
- middleware=get_hosted_auth_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={"timeout_graceful_shutdown": 30},
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 sanitize_parameters_in_spec
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
- raw = (auth_header or "").strip()
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()
@@ -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
+ )