rootly-mcp-server 2.2.10__tar.gz → 2.2.12__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.10 → rootly_mcp_server-2.2.12}/.github/workflows/ci.yml +3 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/.github/workflows/pypi-release.yml +4 -1
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/.gitignore +2 -1
- rootly_mcp_server-2.2.12/CHANGELOG.md +153 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/PKG-INFO +27 -3
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/README.md +25 -1
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/pyproject.toml +2 -2
- rootly_mcp_server-2.2.12/src/rootly_mcp_server/code_mode.py +165 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/tools/incidents.py +100 -2
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/tools/oncall.py +117 -21
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/transport.py +228 -0
- rootly_mcp_server-2.2.12/tests/unit/test_code_mode_module.py +159 -0
- rootly_mcp_server-2.2.12/tests/unit/test_oncall_handoff.py +475 -0
- rootly_mcp_server-2.2.12/tests/unit/test_tools.py +286 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_transport_module.py +126 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/uv.lock +56 -19
- rootly_mcp_server-2.2.10/.beads/issues.jsonl +0 -6
- rootly_mcp_server-2.2.10/src/rootly_mcp_server/code_mode.py +0 -91
- rootly_mcp_server-2.2.10/src/rootly_mcp_server/texttest.json +0 -3178
- rootly_mcp_server-2.2.10/tests/unit/test_code_mode_module.py +0 -74
- rootly_mcp_server-2.2.10/tests/unit/test_oncall_handoff.py +0 -69
- rootly_mcp_server-2.2.10/tests/unit/test_tools.py +0 -93
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/.dockerignore +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/.gitattributes +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/.github/dependabot.yml +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/.github/workflows/dependency-review.yml +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/.semaphore/deploy-staging.yml +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/.semaphore/deploy.yml +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/.semaphore/semaphore.yml +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/.semaphore/update-task-definition.sh +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/CONTRIBUTING.md +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/Dockerfile +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/LICENSE +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/examples/skills/README.md +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/gemini-extension.json +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/rootly-mcp-server-demo.gif +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/rootly_openapi.json +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/scripts/setup-hooks.sh +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/server.json +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/__init__.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/__main__.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/client.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/data/__init__.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/exceptions.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/legacy_server.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/mcp_error.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/monitoring.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/och_client.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/pagination.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/payload_stripping.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/security.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/server.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/server_defaults.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/smart_utils.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/spec_transform.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/tools/__init__.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/tools/alerts.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/tools/resources.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/utils.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/validators.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/README.md +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/conftest.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/integration/local/test_basic.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/integration/local/test_smart_tools.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/integration/remote/test_essential.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/test_client.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_alert_stripping.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_authentication.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_exceptions.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_http_headers.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_legacy_server_module.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_main_transport.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_mcp_error_module.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_och_client.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_oncall_metrics.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_oncall_new_tools.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_oncall_severity_normalization.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_payload_stripping_module.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_security.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_server.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_server_defaults_module.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_smart_utils.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_utils.py +0 -0
- {rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/tests/unit/test_validators.py +0 -0
|
@@ -5,6 +5,9 @@ on:
|
|
|
5
5
|
tags:
|
|
6
6
|
- '*'
|
|
7
7
|
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
8
11
|
jobs:
|
|
9
12
|
build-and-publish:
|
|
10
13
|
runs-on: ubuntu-latest
|
|
@@ -31,4 +34,4 @@ jobs:
|
|
|
31
34
|
TWINE_USERNAME: __token__
|
|
32
35
|
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
33
36
|
run: |
|
|
34
|
-
twine upload dist/*
|
|
37
|
+
twine upload dist/*
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the Rootly MCP Server will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Gemini CLI extension support via `gemini-extension.json` manifest
|
|
12
|
+
- Gemini CLI installation instructions in README
|
|
13
|
+
|
|
14
|
+
## [2.1.1] - 2026-02-04
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Fixed parameter transformation bug where filter parameters (e.g., `filter_status`, `filter_services`) were not being transformed back to their API format (`filter[status]`, `filter[services]`) when making requests to the Rootly API
|
|
18
|
+
- Root cause: The inner httpx client was being passed to FastMCP instead of the AuthenticatedHTTPXClient wrapper, bypassing the `_transform_params` method
|
|
19
|
+
- Thanks to @smoya for reporting this issue in PR #29
|
|
20
|
+
|
|
21
|
+
## [2.1.0] - 2026-01-27
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
#### Security Improvements
|
|
26
|
+
- Comprehensive security module (`security.py`) with:
|
|
27
|
+
- API token validation (prevents invalid/short tokens)
|
|
28
|
+
- HTTPS enforcement for all API calls (rejects HTTP URLs)
|
|
29
|
+
- Input sanitization (SQL injection and XSS prevention)
|
|
30
|
+
- Rate limiting using token bucket algorithm (default: 100 req/min)
|
|
31
|
+
- Error message sanitization (removes stack traces and file paths)
|
|
32
|
+
- Sensitive data masking for logs (tokens, passwords, secrets)
|
|
33
|
+
- URL validation with allowed domain checking
|
|
34
|
+
|
|
35
|
+
#### Exception Handling
|
|
36
|
+
- Custom exception hierarchy (`exceptions.py`) with 11 specific exception types:
|
|
37
|
+
- `RootlyAuthenticationError` - 401 authentication failures
|
|
38
|
+
- `RootlyAuthorizationError` - 403 access denied
|
|
39
|
+
- `RootlyNetworkError` - Network/connection issues
|
|
40
|
+
- `RootlyTimeoutError` - Request timeouts
|
|
41
|
+
- `RootlyValidationError` - Input validation failures
|
|
42
|
+
- `RootlyRateLimitError` - Rate limit exceeded (with retry_after)
|
|
43
|
+
- `RootlyAPIError` - Generic API errors
|
|
44
|
+
- `RootlyServerError` - 5xx server errors
|
|
45
|
+
- `RootlyClientError` - 4xx client errors
|
|
46
|
+
- `RootlyConfigurationError` - Missing/invalid configuration
|
|
47
|
+
- `RootlyResourceNotFoundError` - 404 not found
|
|
48
|
+
- Automatic exception categorization with `categorize_exception()`
|
|
49
|
+
|
|
50
|
+
#### Input Validation
|
|
51
|
+
- Input validation utilities (`validators.py`) with:
|
|
52
|
+
- Positive integer validation
|
|
53
|
+
- String validation with length and pattern checks
|
|
54
|
+
- Dictionary validation with required keys
|
|
55
|
+
- Enum value validation
|
|
56
|
+
- Pagination parameter validation
|
|
57
|
+
|
|
58
|
+
#### Monitoring & Observability
|
|
59
|
+
- Structured JSON logging with correlation IDs (`monitoring.py`)
|
|
60
|
+
- Request metrics tracking:
|
|
61
|
+
- Request counts by endpoint and status code
|
|
62
|
+
- Response latency percentiles (p50, p95, p99)
|
|
63
|
+
- Error rate tracking by type
|
|
64
|
+
- Active connection monitoring
|
|
65
|
+
- Health check support with `get_health_status()`
|
|
66
|
+
- Request/response logging decorator (automatically sanitizes sensitive data)
|
|
67
|
+
- Context manager for tracking request metrics
|
|
68
|
+
|
|
69
|
+
#### Helper Utilities
|
|
70
|
+
- Pagination helpers (`pagination.py`):
|
|
71
|
+
- Async pagination across multiple pages
|
|
72
|
+
- Pagination parameter building for Rootly API
|
|
73
|
+
- Pagination metadata extraction
|
|
74
|
+
|
|
75
|
+
#### Testing Infrastructure
|
|
76
|
+
- 66 comprehensive unit tests (100% passing)
|
|
77
|
+
- Test coverage >90% for all new modules
|
|
78
|
+
- Security-focused tests:
|
|
79
|
+
- SQL injection prevention
|
|
80
|
+
- XSS prevention
|
|
81
|
+
- Rate limiting behavior
|
|
82
|
+
- Token validation
|
|
83
|
+
- HTTPS enforcement
|
|
84
|
+
- Error message sanitization
|
|
85
|
+
|
|
86
|
+
#### CI/CD Pipeline
|
|
87
|
+
- GitHub Actions workflow (`.github/workflows/ci.yml`) with:
|
|
88
|
+
- Automated testing on Python 3.10, 3.11, 3.12
|
|
89
|
+
- Code coverage reporting (Codecov integration)
|
|
90
|
+
- Automated linting (ruff, black, isort, mypy)
|
|
91
|
+
- Security scanning (bandit, safety)
|
|
92
|
+
- Automated package building
|
|
93
|
+
- Runs on every push and pull request
|
|
94
|
+
|
|
95
|
+
### Changed
|
|
96
|
+
|
|
97
|
+
#### Security Enhancements
|
|
98
|
+
- **BREAKING SECURITY FIX**: Removed all API token logging from `__main__.py` (line 100, 116)
|
|
99
|
+
- Changed from: `logger.debug(f"Token starts with: {api_token[:5]}...")`
|
|
100
|
+
- Changed to: `logger.info("ROOTLY_API_TOKEN is configured")`
|
|
101
|
+
- **SECURITY**: Updated `client.py` to use structured logging without exposing tokens
|
|
102
|
+
- **SECURITY**: All error messages now sanitized to remove stack traces
|
|
103
|
+
- Replaced generic `except Exception` with specific exception types in:
|
|
104
|
+
- `__main__.py` - Now catches `RootlyConfigurationError`, `RootlyMCPError`
|
|
105
|
+
- `client.py` - Now catches specific HTTP errors and categorizes them
|
|
106
|
+
|
|
107
|
+
#### API Client Improvements
|
|
108
|
+
- `RootlyClient.make_request()` now raises specific exceptions instead of returning JSON errors
|
|
109
|
+
- Added HTTPS enforcement to base URL validation
|
|
110
|
+
- Added 30-second timeout to all requests (already existed, now enforced everywhere)
|
|
111
|
+
- Better error categorization for HTTP status codes (401, 403, 404, 429, 4xx, 5xx)
|
|
112
|
+
|
|
113
|
+
#### Configuration Validation
|
|
114
|
+
- API token now validated on startup with `validate_api_token()`
|
|
115
|
+
- Better error messages for missing or invalid configuration
|
|
116
|
+
|
|
117
|
+
### Fixed
|
|
118
|
+
|
|
119
|
+
- Security vulnerability: API tokens no longer logged (even partially)
|
|
120
|
+
- Security vulnerability: Stack traces no longer exposed in error responses
|
|
121
|
+
- Security vulnerability: HTTP URLs now rejected (HTTPS enforced)
|
|
122
|
+
- Generic exception handling replaced with specific exception types
|
|
123
|
+
- Error messages now user-friendly (sanitized of internal details)
|
|
124
|
+
|
|
125
|
+
### Documentation
|
|
126
|
+
|
|
127
|
+
- Added `IMPLEMENTATION_REPORT.md` - Detailed implementation summary
|
|
128
|
+
- Added `GPT4O_REVIEW.md` - External review of improvements
|
|
129
|
+
- Added `IMPLEMENTATION_CHECKLIST.md` - Implementation progress tracking
|
|
130
|
+
- Updated `IMPROVEMENT_PLAN.md` with GPT-4o recommendations
|
|
131
|
+
- All new modules have comprehensive docstrings
|
|
132
|
+
- Updated package docstring with new features
|
|
133
|
+
|
|
134
|
+
### Technical Details
|
|
135
|
+
|
|
136
|
+
- **Lines of Code Added**: ~1,500 lines production code, ~500 lines test code
|
|
137
|
+
- **Test Coverage**: >90% for new modules
|
|
138
|
+
- **Tests Passing**: 66/66 (100%)
|
|
139
|
+
- **Security Issues Fixed**: 6 critical vulnerabilities
|
|
140
|
+
- **Breaking Changes**: 0 (fully backward compatible)
|
|
141
|
+
|
|
142
|
+
### Backward Compatibility
|
|
143
|
+
|
|
144
|
+
All changes are backward compatible:
|
|
145
|
+
- Existing API unchanged
|
|
146
|
+
- New modules are additive
|
|
147
|
+
- Exception hierarchy maintains base `Exception` compatibility
|
|
148
|
+
- Client behavior unchanged from external perspective
|
|
149
|
+
- No migration required for existing users
|
|
150
|
+
|
|
151
|
+
## [2.0.15] - Previous Release
|
|
152
|
+
|
|
153
|
+
(Previous changelog entries would go here)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rootly-mcp-server
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.12
|
|
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
|
|
@@ -30,7 +30,7 @@ Requires-Dist: requests==2.32.5
|
|
|
30
30
|
Requires-Dist: scikit-learn>=1.3.0
|
|
31
31
|
Provides-Extra: dev
|
|
32
32
|
Requires-Dist: bandit>=1.7.0; extra == 'dev'
|
|
33
|
-
Requires-Dist: black>=
|
|
33
|
+
Requires-Dist: black>=26.3.1; extra == 'dev'
|
|
34
34
|
Requires-Dist: isort>=5.0.0; extra == 'dev'
|
|
35
35
|
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
36
36
|
Requires-Dist: pip-audit>=2.7.0; extra == 'dev'
|
|
@@ -55,6 +55,8 @@ The fastest way to get started is to connect to our hosted MCP server — no ins
|
|
|
55
55
|
|
|
56
56
|
### Claude Code
|
|
57
57
|
|
|
58
|
+
**Hosted Server (Recommended)**
|
|
59
|
+
|
|
58
60
|
```bash
|
|
59
61
|
claude mcp add --transport http rootly https://mcp.rootly.com/mcp \
|
|
60
62
|
--header "Authorization: Bearer YOUR_ROOTLY_API_TOKEN"
|
|
@@ -74,6 +76,26 @@ claude mcp add --transport sse rootly-sse https://mcp.rootly.com/sse \
|
|
|
74
76
|
--header "Authorization: Bearer YOUR_ROOTLY_API_TOKEN"
|
|
75
77
|
```
|
|
76
78
|
|
|
79
|
+
**Manual Configuration**
|
|
80
|
+
|
|
81
|
+
Alternatively, create `.mcp.json` in your project root:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"rootly": {
|
|
87
|
+
"type": "sse",
|
|
88
|
+
"url": "https://mcp.rootly.com/sse",
|
|
89
|
+
"headers": {
|
|
90
|
+
"Authorization": "Bearer YOUR_ROOTLY_API_TOKEN"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Then restart Claude Code so it reloads the updated configuration.
|
|
98
|
+
|
|
77
99
|
### Gemini CLI
|
|
78
100
|
|
|
79
101
|
Install the extension:
|
|
@@ -295,7 +317,7 @@ docker run -p 8000:8000 \
|
|
|
295
317
|
|
|
296
318
|
## Supported Tools
|
|
297
319
|
|
|
298
|
-
The default server configuration currently exposes **
|
|
320
|
+
The default server configuration currently exposes **101 tools** (including custom agentic tools and OpenAPI-generated tools).
|
|
299
321
|
|
|
300
322
|
### Custom Agentic Tools
|
|
301
323
|
|
|
@@ -303,6 +325,7 @@ The default server configuration currently exposes **99 tools** (including custo
|
|
|
303
325
|
- `check_responder_availability`
|
|
304
326
|
- `create_override_recommendation`
|
|
305
327
|
- `find_related_incidents`
|
|
328
|
+
- `getIncident` - retrieve a single incident for direct verification, including PIR-related fields
|
|
306
329
|
- `get_alert_by_short_id`
|
|
307
330
|
- `get_oncall_handoff_summary`
|
|
308
331
|
- `get_oncall_schedule_summary`
|
|
@@ -313,6 +336,7 @@ The default server configuration currently exposes **99 tools** (including custo
|
|
|
313
336
|
- `list_shifts`
|
|
314
337
|
- `search_incidents`
|
|
315
338
|
- `suggest_solutions`
|
|
339
|
+
- `updateIncident` - scoped incident update tool for `summary` and `retrospective_progress_status`
|
|
316
340
|
|
|
317
341
|
### OpenAPI-Generated Tools
|
|
318
342
|
|
|
@@ -16,6 +16,8 @@ The fastest way to get started is to connect to our hosted MCP server — no ins
|
|
|
16
16
|
|
|
17
17
|
### Claude Code
|
|
18
18
|
|
|
19
|
+
**Hosted Server (Recommended)**
|
|
20
|
+
|
|
19
21
|
```bash
|
|
20
22
|
claude mcp add --transport http rootly https://mcp.rootly.com/mcp \
|
|
21
23
|
--header "Authorization: Bearer YOUR_ROOTLY_API_TOKEN"
|
|
@@ -35,6 +37,26 @@ claude mcp add --transport sse rootly-sse https://mcp.rootly.com/sse \
|
|
|
35
37
|
--header "Authorization: Bearer YOUR_ROOTLY_API_TOKEN"
|
|
36
38
|
```
|
|
37
39
|
|
|
40
|
+
**Manual Configuration**
|
|
41
|
+
|
|
42
|
+
Alternatively, create `.mcp.json` in your project root:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mcpServers": {
|
|
47
|
+
"rootly": {
|
|
48
|
+
"type": "sse",
|
|
49
|
+
"url": "https://mcp.rootly.com/sse",
|
|
50
|
+
"headers": {
|
|
51
|
+
"Authorization": "Bearer YOUR_ROOTLY_API_TOKEN"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then restart Claude Code so it reloads the updated configuration.
|
|
59
|
+
|
|
38
60
|
### Gemini CLI
|
|
39
61
|
|
|
40
62
|
Install the extension:
|
|
@@ -256,7 +278,7 @@ docker run -p 8000:8000 \
|
|
|
256
278
|
|
|
257
279
|
## Supported Tools
|
|
258
280
|
|
|
259
|
-
The default server configuration currently exposes **
|
|
281
|
+
The default server configuration currently exposes **101 tools** (including custom agentic tools and OpenAPI-generated tools).
|
|
260
282
|
|
|
261
283
|
### Custom Agentic Tools
|
|
262
284
|
|
|
@@ -264,6 +286,7 @@ The default server configuration currently exposes **99 tools** (including custo
|
|
|
264
286
|
- `check_responder_availability`
|
|
265
287
|
- `create_override_recommendation`
|
|
266
288
|
- `find_related_incidents`
|
|
289
|
+
- `getIncident` - retrieve a single incident for direct verification, including PIR-related fields
|
|
267
290
|
- `get_alert_by_short_id`
|
|
268
291
|
- `get_oncall_handoff_summary`
|
|
269
292
|
- `get_oncall_schedule_summary`
|
|
@@ -274,6 +297,7 @@ The default server configuration currently exposes **99 tools** (including custo
|
|
|
274
297
|
- `list_shifts`
|
|
275
298
|
- `search_incidents`
|
|
276
299
|
- `suggest_solutions`
|
|
300
|
+
- `updateIncident` - scoped incident update tool for `summary` and `retrospective_progress_status`
|
|
277
301
|
|
|
278
302
|
### OpenAPI-Generated Tools
|
|
279
303
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "rootly-mcp-server"
|
|
3
|
-
version = "2.2.
|
|
3
|
+
version = "2.2.12"
|
|
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"
|
|
@@ -70,7 +70,7 @@ rootly-mcp-server = "rootly_mcp_server.__main__:main"
|
|
|
70
70
|
|
|
71
71
|
[project.optional-dependencies]
|
|
72
72
|
dev = [
|
|
73
|
-
"black>=
|
|
73
|
+
"black>=26.3.1",
|
|
74
74
|
"isort>=5.0.0",
|
|
75
75
|
"mypy>=1.0.0",
|
|
76
76
|
"types-requests>=2.32.0",
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Code Mode helpers for exposing a third MCP endpoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import os
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from fastmcp.experimental.transforms.code_mode import (
|
|
11
|
+
CodeMode,
|
|
12
|
+
GetSchemas,
|
|
13
|
+
GetTags,
|
|
14
|
+
ListTools,
|
|
15
|
+
MontySandboxProvider,
|
|
16
|
+
Search,
|
|
17
|
+
_ensure_async,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from .server import create_rootly_mcp_server
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from fastmcp import FastMCP
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
DEFAULT_CODE_MODE_PATH = "/mcp-codemode"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _normalize_http_path(path: str) -> str:
|
|
30
|
+
"""Normalize hosted HTTP path values for reliable comparisons."""
|
|
31
|
+
if not path:
|
|
32
|
+
return "/"
|
|
33
|
+
normalized = path if path.startswith("/") else f"/{path}"
|
|
34
|
+
if len(normalized) > 1:
|
|
35
|
+
normalized = normalized.rstrip("/")
|
|
36
|
+
return normalized
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def normalize_code_mode_path(path: str) -> str:
|
|
40
|
+
"""Normalize a hosted Code Mode path value."""
|
|
41
|
+
return _normalize_http_path(path)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def code_mode_enabled_from_env(default: bool = True) -> bool:
|
|
45
|
+
"""Return whether hosted Code Mode exposure is enabled.
|
|
46
|
+
|
|
47
|
+
Code Mode defaults on for hosted dual-transport deployments unless explicitly disabled.
|
|
48
|
+
"""
|
|
49
|
+
raw = os.getenv("ROOTLY_CODE_MODE_ENABLED")
|
|
50
|
+
if raw is None:
|
|
51
|
+
return default
|
|
52
|
+
return raw.lower() in ("1", "true", "yes")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def code_mode_path_from_env() -> str:
|
|
56
|
+
"""Return the configured hosted Code Mode path."""
|
|
57
|
+
return normalize_code_mode_path(os.getenv("ROOTLY_CODE_MODE_PATH", DEFAULT_CODE_MODE_PATH))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class CompatibleMontySandboxProvider(MontySandboxProvider):
|
|
61
|
+
"""Monty sandbox provider that tolerates older constructor signatures.
|
|
62
|
+
|
|
63
|
+
Some deployed environments can end up with a Monty runtime that supports
|
|
64
|
+
``run_monty_async(..., external_functions=...)`` but still rejects the
|
|
65
|
+
newer ``Monty(..., external_functions=[...])`` constructor argument. This
|
|
66
|
+
provider falls back to the older constructor form so Code Mode execution
|
|
67
|
+
continues to work during mixed-version rollouts.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def _build_monty_runner(
|
|
72
|
+
pydantic_monty: Any,
|
|
73
|
+
code: str,
|
|
74
|
+
*,
|
|
75
|
+
input_names: list[str],
|
|
76
|
+
external_function_names: list[str],
|
|
77
|
+
) -> Any:
|
|
78
|
+
try:
|
|
79
|
+
return pydantic_monty.Monty(
|
|
80
|
+
code,
|
|
81
|
+
inputs=input_names,
|
|
82
|
+
external_functions=external_function_names,
|
|
83
|
+
)
|
|
84
|
+
except TypeError as exc:
|
|
85
|
+
if "external_functions" not in str(exc):
|
|
86
|
+
raise
|
|
87
|
+
return pydantic_monty.Monty(code, inputs=input_names)
|
|
88
|
+
|
|
89
|
+
async def run(
|
|
90
|
+
self,
|
|
91
|
+
code: str,
|
|
92
|
+
*,
|
|
93
|
+
inputs: dict[str, Any] | None = None,
|
|
94
|
+
external_functions: dict[str, Callable[..., Any]] | None = None,
|
|
95
|
+
) -> Any:
|
|
96
|
+
try:
|
|
97
|
+
pydantic_monty = importlib.import_module("pydantic_monty")
|
|
98
|
+
except ModuleNotFoundError as exc:
|
|
99
|
+
raise ImportError(
|
|
100
|
+
"CodeMode requires pydantic-monty for the Monty sandbox provider. "
|
|
101
|
+
"Install it with `fastmcp[code-mode]` or pass a custom SandboxProvider."
|
|
102
|
+
) from exc
|
|
103
|
+
|
|
104
|
+
inputs = inputs or {}
|
|
105
|
+
async_functions = {
|
|
106
|
+
key: _ensure_async(value)
|
|
107
|
+
for key, value in (external_functions or {}).items()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
monty = self._build_monty_runner(
|
|
111
|
+
pydantic_monty,
|
|
112
|
+
code,
|
|
113
|
+
input_names=list(inputs.keys()),
|
|
114
|
+
external_function_names=list(async_functions.keys()),
|
|
115
|
+
)
|
|
116
|
+
run_kwargs: dict[str, Any] = {"external_functions": async_functions}
|
|
117
|
+
if inputs:
|
|
118
|
+
run_kwargs["inputs"] = inputs
|
|
119
|
+
if self.limits is not None:
|
|
120
|
+
run_kwargs["limits"] = self.limits
|
|
121
|
+
return await pydantic_monty.run_monty_async(monty, **run_kwargs)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def build_code_mode_transform() -> CodeMode:
|
|
125
|
+
"""Build the shared Code Mode transform used by hosted deployments."""
|
|
126
|
+
return CodeMode(
|
|
127
|
+
sandbox_provider=CompatibleMontySandboxProvider(),
|
|
128
|
+
discovery_tools=[
|
|
129
|
+
ListTools(default_detail="brief"),
|
|
130
|
+
Search(name="tool_search", default_detail="detailed", default_limit=12),
|
|
131
|
+
GetSchemas(default_detail="detailed"),
|
|
132
|
+
GetTags(default_detail="brief"),
|
|
133
|
+
],
|
|
134
|
+
execute_description=(
|
|
135
|
+
"Write a short async Python block and chain await call_tool(name, params) calls "
|
|
136
|
+
"to complete a Rootly workflow. Use tool_search only to discover tools and their "
|
|
137
|
+
"schemas, then call the actual Rootly tool with the exact parameter names shown in "
|
|
138
|
+
"the schema. For paginated data tools, pass pagination arguments like page_size, "
|
|
139
|
+
"page_number, and max_results exactly as documented by that tool instead of inventing "
|
|
140
|
+
"alternatives like per_page. Prefer Rootly's higher-level custom tools when they fit "
|
|
141
|
+
"the task, then fall back to lower-level API tools as needed. Example: "
|
|
142
|
+
"await call_tool('search_incidents', {'query': '', 'page_size': 1, 'page_number': 1, "
|
|
143
|
+
"'max_results': 1}). Use return to emit the final result."
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def create_rootly_codemode_server(
|
|
149
|
+
swagger_path: str | None = None,
|
|
150
|
+
name: str = "Rootly Code Mode",
|
|
151
|
+
allowed_paths: list[str] | None = None,
|
|
152
|
+
hosted: bool = False,
|
|
153
|
+
base_url: str | None = None,
|
|
154
|
+
) -> FastMCP:
|
|
155
|
+
"""Create a Rootly MCP server instance wrapped with Code Mode."""
|
|
156
|
+
mcp: FastMCP = create_rootly_mcp_server(
|
|
157
|
+
swagger_path=swagger_path,
|
|
158
|
+
name=name,
|
|
159
|
+
allowed_paths=allowed_paths,
|
|
160
|
+
hosted=hosted,
|
|
161
|
+
base_url=base_url,
|
|
162
|
+
transport="streamable-http",
|
|
163
|
+
)
|
|
164
|
+
mcp.add_transform(build_code_mode_transform())
|
|
165
|
+
return mcp
|
{rootly_mcp_server-2.2.10 → rootly_mcp_server-2.2.12}/src/rootly_mcp_server/tools/incidents.py
RENAMED
|
@@ -14,6 +14,11 @@ MakeAuthenticatedRequest = Callable[..., Awaitable[Any]]
|
|
|
14
14
|
StripHeavyNestedData = Callable[[JsonDict], JsonDict]
|
|
15
15
|
GenerateRecommendation = Callable[[JsonDict], str]
|
|
16
16
|
|
|
17
|
+
RETROSPECTIVE_PROGRESS_STATUSES = ("not_started", "active", "completed", "skipped")
|
|
18
|
+
INCIDENT_SEARCH_FIELDS = (
|
|
19
|
+
"id,title,summary,status,created_at,updated_at,url,started_at,retrospective_progress_status"
|
|
20
|
+
)
|
|
21
|
+
|
|
17
22
|
|
|
18
23
|
def register_incident_tools(
|
|
19
24
|
mcp: Any,
|
|
@@ -60,7 +65,7 @@ def register_incident_tools(
|
|
|
60
65
|
"page[size]": page_size, # Use requested page size (already limited to max 20)
|
|
61
66
|
"page[number]": page_number,
|
|
62
67
|
"include": "",
|
|
63
|
-
"fields[incidents]":
|
|
68
|
+
"fields[incidents]": INCIDENT_SEARCH_FIELDS,
|
|
64
69
|
}
|
|
65
70
|
if query:
|
|
66
71
|
params["filter[search]"] = query
|
|
@@ -85,7 +90,7 @@ def register_incident_tools(
|
|
|
85
90
|
"page[size]": effective_page_size,
|
|
86
91
|
"page[number]": current_page,
|
|
87
92
|
"include": "",
|
|
88
|
-
"fields[incidents]":
|
|
93
|
+
"fields[incidents]": INCIDENT_SEARCH_FIELDS,
|
|
89
94
|
}
|
|
90
95
|
if query:
|
|
91
96
|
params["filter[search]"] = query
|
|
@@ -155,6 +160,99 @@ def register_incident_tools(
|
|
|
155
160
|
error_type, error_message = mcp_error.categorize_error(e)
|
|
156
161
|
return cast(JsonDict, mcp_error.tool_error(error_message, error_type))
|
|
157
162
|
|
|
163
|
+
@mcp.tool(name="getIncident")
|
|
164
|
+
async def get_incident(
|
|
165
|
+
incident_id: Annotated[str, Field(description="Incident ID to retrieve")],
|
|
166
|
+
) -> JsonDict:
|
|
167
|
+
"""Retrieve a single incident with PIR-related fields for direct verification."""
|
|
168
|
+
try:
|
|
169
|
+
response = await make_authenticated_request("GET", f"/v1/incidents/{incident_id}")
|
|
170
|
+
response.raise_for_status()
|
|
171
|
+
|
|
172
|
+
response_data = response.json()
|
|
173
|
+
if isinstance(response_data.get("data"), dict):
|
|
174
|
+
stripped = strip_heavy_nested_data({"data": [response_data["data"]]})
|
|
175
|
+
response_data["data"] = stripped["data"][0]
|
|
176
|
+
return cast(JsonDict, response_data)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
error_type, error_message = mcp_error.categorize_error(e)
|
|
179
|
+
return cast(
|
|
180
|
+
JsonDict,
|
|
181
|
+
mcp_error.tool_error(
|
|
182
|
+
f"Failed to retrieve incident: {error_message}",
|
|
183
|
+
error_type,
|
|
184
|
+
),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
@mcp.tool(name="updateIncident")
|
|
188
|
+
async def update_incident(
|
|
189
|
+
incident_id: Annotated[str, Field(description="Incident ID to update")],
|
|
190
|
+
retrospective_progress_status: Annotated[
|
|
191
|
+
str | None,
|
|
192
|
+
Field(
|
|
193
|
+
description="Retrospective/PIR status: one of not_started, active, completed, skipped"
|
|
194
|
+
),
|
|
195
|
+
] = None,
|
|
196
|
+
summary: Annotated[
|
|
197
|
+
str | None,
|
|
198
|
+
Field(description="Updated incident summary"),
|
|
199
|
+
] = None,
|
|
200
|
+
) -> JsonDict:
|
|
201
|
+
"""Update scoped incident fields for PIR lifecycle automation."""
|
|
202
|
+
attributes: dict[str, Any] = {}
|
|
203
|
+
|
|
204
|
+
if retrospective_progress_status is not None:
|
|
205
|
+
if retrospective_progress_status not in RETROSPECTIVE_PROGRESS_STATUSES:
|
|
206
|
+
allowed = ", ".join(RETROSPECTIVE_PROGRESS_STATUSES)
|
|
207
|
+
return cast(
|
|
208
|
+
JsonDict,
|
|
209
|
+
mcp_error.tool_error(
|
|
210
|
+
f"retrospective_progress_status must be one of: {allowed}",
|
|
211
|
+
"validation_error",
|
|
212
|
+
),
|
|
213
|
+
)
|
|
214
|
+
attributes["retrospective_progress_status"] = retrospective_progress_status
|
|
215
|
+
|
|
216
|
+
if summary is not None:
|
|
217
|
+
attributes["summary"] = summary
|
|
218
|
+
|
|
219
|
+
if not attributes:
|
|
220
|
+
return cast(
|
|
221
|
+
JsonDict,
|
|
222
|
+
mcp_error.tool_error(
|
|
223
|
+
"Must provide at least one of retrospective_progress_status or summary",
|
|
224
|
+
"validation_error",
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
payload = {
|
|
229
|
+
"data": {
|
|
230
|
+
"type": "incidents",
|
|
231
|
+
"attributes": attributes,
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
response = await make_authenticated_request(
|
|
237
|
+
"PUT", f"/v1/incidents/{incident_id}", json=payload
|
|
238
|
+
)
|
|
239
|
+
response.raise_for_status()
|
|
240
|
+
|
|
241
|
+
response_data = response.json()
|
|
242
|
+
if isinstance(response_data.get("data"), dict):
|
|
243
|
+
stripped = strip_heavy_nested_data({"data": [response_data["data"]]})
|
|
244
|
+
response_data["data"] = stripped["data"][0]
|
|
245
|
+
return cast(JsonDict, response_data)
|
|
246
|
+
except Exception as e:
|
|
247
|
+
error_type, error_message = mcp_error.categorize_error(e)
|
|
248
|
+
return cast(
|
|
249
|
+
JsonDict,
|
|
250
|
+
mcp_error.tool_error(
|
|
251
|
+
f"Failed to update incident: {error_message}",
|
|
252
|
+
error_type,
|
|
253
|
+
),
|
|
254
|
+
)
|
|
255
|
+
|
|
158
256
|
@mcp.tool()
|
|
159
257
|
async def find_related_incidents(
|
|
160
258
|
incident_id: str = "",
|