rootly-mcp-server 2.0.14__py3-none-any.whl → 2.1.0__py3-none-any.whl
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/__init__.py +9 -5
- rootly_mcp_server/__main__.py +44 -29
- rootly_mcp_server/client.py +98 -44
- rootly_mcp_server/data/__init__.py +1 -1
- rootly_mcp_server/exceptions.py +148 -0
- rootly_mcp_server/monitoring.py +378 -0
- rootly_mcp_server/pagination.py +98 -0
- rootly_mcp_server/security.py +404 -0
- rootly_mcp_server/server.py +1864 -343
- rootly_mcp_server/smart_utils.py +294 -209
- rootly_mcp_server/texttest.json +3178 -0
- rootly_mcp_server/utils.py +48 -33
- rootly_mcp_server/validators.py +147 -0
- {rootly_mcp_server-2.0.14.dist-info → rootly_mcp_server-2.1.0.dist-info}/METADATA +180 -50
- rootly_mcp_server-2.1.0.dist-info/RECORD +18 -0
- {rootly_mcp_server-2.0.14.dist-info → rootly_mcp_server-2.1.0.dist-info}/WHEEL +1 -1
- rootly_mcp_server-2.0.14.dist-info/RECORD +0 -12
- {rootly_mcp_server-2.0.14.dist-info → rootly_mcp_server-2.1.0.dist-info}/entry_points.txt +0 -0
- {rootly_mcp_server-2.0.14.dist-info → rootly_mcp_server-2.1.0.dist-info}/licenses/LICENSE +0 -0
rootly_mcp_server/utils.py
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Shared utilities for Rootly MCP Server.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import re
|
|
6
5
|
import logging
|
|
7
|
-
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger(__name__)
|
|
10
10
|
|
|
@@ -12,57 +12,57 @@ logger = logging.getLogger(__name__)
|
|
|
12
12
|
def sanitize_parameter_name(name: str) -> str:
|
|
13
13
|
"""
|
|
14
14
|
Sanitize parameter names to match MCP property key pattern ^[a-zA-Z0-9_.-]{1,64}$.
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
Args:
|
|
17
17
|
name: Original parameter name
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
Returns:
|
|
20
20
|
Sanitized parameter name
|
|
21
21
|
"""
|
|
22
22
|
# Replace square brackets with underscores: filter[kind] -> filter_kind
|
|
23
|
-
sanitized = re.sub(r
|
|
24
|
-
|
|
23
|
+
sanitized = re.sub(r"\[([^\]]+)\]", r"_\1", name)
|
|
24
|
+
|
|
25
25
|
# Replace any remaining invalid characters with underscores
|
|
26
|
-
sanitized = re.sub(r
|
|
27
|
-
|
|
26
|
+
sanitized = re.sub(r"[^a-zA-Z0-9_.-]", "_", sanitized)
|
|
27
|
+
|
|
28
28
|
# Remove multiple consecutive underscores
|
|
29
|
-
sanitized = re.sub(r
|
|
30
|
-
|
|
29
|
+
sanitized = re.sub(r"_{2,}", "_", sanitized)
|
|
30
|
+
|
|
31
31
|
# Remove leading/trailing underscores
|
|
32
|
-
sanitized = sanitized.strip(
|
|
33
|
-
|
|
32
|
+
sanitized = sanitized.strip("_")
|
|
33
|
+
|
|
34
34
|
# Ensure the name doesn't exceed 64 characters
|
|
35
35
|
if len(sanitized) > 64:
|
|
36
|
-
sanitized = sanitized[:64].rstrip(
|
|
37
|
-
|
|
36
|
+
sanitized = sanitized[:64].rstrip("_")
|
|
37
|
+
|
|
38
38
|
# Ensure the name is not empty and starts with a letter or underscore
|
|
39
39
|
if not sanitized or sanitized[0].isdigit():
|
|
40
40
|
sanitized = "param_" + sanitized if sanitized else "param"
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
return sanitized
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def sanitize_parameters_in_spec(spec:
|
|
45
|
+
def sanitize_parameters_in_spec(spec: dict[str, Any]) -> dict[str, str]:
|
|
46
46
|
"""
|
|
47
47
|
Sanitize all parameter names in an OpenAPI specification.
|
|
48
|
-
|
|
49
|
-
This function modifies the spec in-place and builds a mapping
|
|
48
|
+
|
|
49
|
+
This function modifies the spec in-place and builds a mapping
|
|
50
50
|
of sanitized names to original names.
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
Args:
|
|
53
53
|
spec: OpenAPI specification dictionary
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
Returns:
|
|
56
56
|
Dictionary mapping sanitized names to original names
|
|
57
57
|
"""
|
|
58
58
|
parameter_mapping = {}
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
# Sanitize parameters in paths
|
|
61
61
|
if "paths" in spec:
|
|
62
|
-
for
|
|
62
|
+
for _path, path_item in spec["paths"].items():
|
|
63
63
|
if not isinstance(path_item, dict):
|
|
64
64
|
continue
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
# Sanitize path-level parameters
|
|
67
67
|
if "parameters" in path_item:
|
|
68
68
|
for param in path_item["parameters"]:
|
|
@@ -70,36 +70,51 @@ def sanitize_parameters_in_spec(spec: Dict[str, Any]) -> Dict[str, str]:
|
|
|
70
70
|
original_name = param["name"]
|
|
71
71
|
sanitized_name = sanitize_parameter_name(original_name)
|
|
72
72
|
if sanitized_name != original_name:
|
|
73
|
-
logger.debug(
|
|
73
|
+
logger.debug(
|
|
74
|
+
f"Sanitized path-level parameter: '{original_name}' -> '{sanitized_name}'"
|
|
75
|
+
)
|
|
74
76
|
param["name"] = sanitized_name
|
|
75
77
|
parameter_mapping[sanitized_name] = original_name
|
|
76
|
-
|
|
78
|
+
|
|
77
79
|
# Sanitize operation-level parameters
|
|
78
80
|
for method, operation in path_item.items():
|
|
79
|
-
if method.lower() not in [
|
|
81
|
+
if method.lower() not in [
|
|
82
|
+
"get",
|
|
83
|
+
"post",
|
|
84
|
+
"put",
|
|
85
|
+
"delete",
|
|
86
|
+
"patch",
|
|
87
|
+
"options",
|
|
88
|
+
"head",
|
|
89
|
+
"trace",
|
|
90
|
+
]:
|
|
80
91
|
continue
|
|
81
92
|
if not isinstance(operation, dict):
|
|
82
93
|
continue
|
|
83
|
-
|
|
94
|
+
|
|
84
95
|
if "parameters" in operation:
|
|
85
96
|
for param in operation["parameters"]:
|
|
86
97
|
if "name" in param:
|
|
87
98
|
original_name = param["name"]
|
|
88
99
|
sanitized_name = sanitize_parameter_name(original_name)
|
|
89
100
|
if sanitized_name != original_name:
|
|
90
|
-
logger.debug(
|
|
101
|
+
logger.debug(
|
|
102
|
+
f"Sanitized operation parameter: '{original_name}' -> '{sanitized_name}'"
|
|
103
|
+
)
|
|
91
104
|
param["name"] = sanitized_name
|
|
92
105
|
parameter_mapping[sanitized_name] = original_name
|
|
93
|
-
|
|
106
|
+
|
|
94
107
|
# Sanitize parameters in components (OpenAPI 3.0)
|
|
95
108
|
if "components" in spec and "parameters" in spec["components"]:
|
|
96
|
-
for
|
|
109
|
+
for _param_name, param_def in spec["components"]["parameters"].items():
|
|
97
110
|
if isinstance(param_def, dict) and "name" in param_def:
|
|
98
111
|
original_name = param_def["name"]
|
|
99
112
|
sanitized_name = sanitize_parameter_name(original_name)
|
|
100
113
|
if sanitized_name != original_name:
|
|
101
|
-
logger.debug(
|
|
114
|
+
logger.debug(
|
|
115
|
+
f"Sanitized component parameter: '{original_name}' -> '{sanitized_name}'"
|
|
116
|
+
)
|
|
102
117
|
param_def["name"] = sanitized_name
|
|
103
118
|
parameter_mapping[sanitized_name] = original_name
|
|
104
|
-
|
|
105
|
-
return parameter_mapping
|
|
119
|
+
|
|
120
|
+
return parameter_mapping
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Input validation utilities for the Rootly MCP Server.
|
|
3
|
+
|
|
4
|
+
This module provides validation functions for API inputs, parameters,
|
|
5
|
+
and data structures.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .exceptions import RootlyValidationError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def validate_positive_integer(value: int, field_name: str, min_value: int = 1) -> int:
|
|
14
|
+
"""
|
|
15
|
+
Validate that a value is a positive integer.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
value: The value to validate
|
|
19
|
+
field_name: Name of the field for error messages
|
|
20
|
+
min_value: Minimum allowed value
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
The validated value
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
RootlyValidationError: If validation fails
|
|
27
|
+
"""
|
|
28
|
+
if not isinstance(value, int):
|
|
29
|
+
raise RootlyValidationError(f"{field_name} must be an integer, got {type(value).__name__}")
|
|
30
|
+
|
|
31
|
+
if value < min_value:
|
|
32
|
+
raise RootlyValidationError(f"{field_name} must be >= {min_value}, got {value}")
|
|
33
|
+
|
|
34
|
+
return value
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def validate_string(
|
|
38
|
+
value: str,
|
|
39
|
+
field_name: str,
|
|
40
|
+
min_length: int = 0,
|
|
41
|
+
max_length: int | None = None,
|
|
42
|
+
pattern: str | None = None,
|
|
43
|
+
) -> str:
|
|
44
|
+
"""
|
|
45
|
+
Validate a string value.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
value: The string to validate
|
|
49
|
+
field_name: Name of the field for error messages
|
|
50
|
+
min_length: Minimum allowed length
|
|
51
|
+
max_length: Maximum allowed length
|
|
52
|
+
pattern: Optional regex pattern to match
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The validated string
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
RootlyValidationError: If validation fails
|
|
59
|
+
"""
|
|
60
|
+
if not isinstance(value, str):
|
|
61
|
+
raise RootlyValidationError(f"{field_name} must be a string, got {type(value).__name__}")
|
|
62
|
+
|
|
63
|
+
if len(value) < min_length:
|
|
64
|
+
raise RootlyValidationError(f"{field_name} must be at least {min_length} characters")
|
|
65
|
+
|
|
66
|
+
if max_length and len(value) > max_length:
|
|
67
|
+
raise RootlyValidationError(f"{field_name} must be at most {max_length} characters")
|
|
68
|
+
|
|
69
|
+
if pattern:
|
|
70
|
+
import re
|
|
71
|
+
|
|
72
|
+
if not re.match(pattern, value):
|
|
73
|
+
raise RootlyValidationError(f"{field_name} does not match required pattern")
|
|
74
|
+
|
|
75
|
+
return value
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def validate_dict(value: dict, field_name: str, required_keys: list[str] | None = None) -> dict:
|
|
79
|
+
"""
|
|
80
|
+
Validate a dictionary value.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
value: The dict to validate
|
|
84
|
+
field_name: Name of the field for error messages
|
|
85
|
+
required_keys: Optional list of required keys
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The validated dict
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
RootlyValidationError: If validation fails
|
|
92
|
+
"""
|
|
93
|
+
if not isinstance(value, dict):
|
|
94
|
+
raise RootlyValidationError(f"{field_name} must be a dict, got {type(value).__name__}")
|
|
95
|
+
|
|
96
|
+
if required_keys:
|
|
97
|
+
missing_keys = set(required_keys) - set(value.keys())
|
|
98
|
+
if missing_keys:
|
|
99
|
+
raise RootlyValidationError(
|
|
100
|
+
f"{field_name} is missing required keys: {', '.join(missing_keys)}"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return value
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def validate_enum(value: Any, field_name: str, allowed_values: list[Any]) -> Any:
|
|
107
|
+
"""
|
|
108
|
+
Validate that a value is one of the allowed values.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
value: The value to validate
|
|
112
|
+
field_name: Name of the field for error messages
|
|
113
|
+
allowed_values: List of allowed values
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
The validated value
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
RootlyValidationError: If validation fails
|
|
120
|
+
"""
|
|
121
|
+
if value not in allowed_values:
|
|
122
|
+
raise RootlyValidationError(f"{field_name} must be one of {allowed_values}, got {value}")
|
|
123
|
+
|
|
124
|
+
return value
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def validate_page_params(page_size: int, page_number: int) -> tuple[int, int]:
|
|
128
|
+
"""
|
|
129
|
+
Validate pagination parameters.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
page_size: Number of items per page
|
|
133
|
+
page_number: Page number (0 for all, 1+ for specific page)
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Tuple of validated (page_size, page_number)
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
RootlyValidationError: If validation fails
|
|
140
|
+
"""
|
|
141
|
+
page_size = validate_positive_integer(page_size, "page_size", min_value=1)
|
|
142
|
+
page_number = validate_positive_integer(page_number, "page_number", min_value=0)
|
|
143
|
+
|
|
144
|
+
if page_size > 100:
|
|
145
|
+
raise RootlyValidationError("page_size cannot exceed 100")
|
|
146
|
+
|
|
147
|
+
return page_size, page_number
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rootly-mcp-server
|
|
3
|
-
Version: 2.0
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 2.1.0
|
|
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
|
|
7
7
|
Author-email: Rootly AI Labs <support@rootly.com>
|
|
8
8
|
License-Expression: Apache-2.0
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Keywords: automation,incidents,llm,mcp,rootly
|
|
10
|
+
Keywords: ai-sre,automation,devops,incident-management,incidents,llm,mcp,on-call,rate-limiting,rootly,security,sre
|
|
11
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
13
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Security
|
|
15
21
|
Classifier: Topic :: Software Development :: Build Tools
|
|
16
|
-
|
|
22
|
+
Classifier: Topic :: System :: Monitoring
|
|
23
|
+
Classifier: Topic :: System :: Systems Administration
|
|
24
|
+
Requires-Python: >=3.10
|
|
17
25
|
Requires-Dist: brotli>=1.0.0
|
|
18
26
|
Requires-Dist: fastmcp>=2.9.0
|
|
19
27
|
Requires-Dist: httpx>=0.24.0
|
|
@@ -22,8 +30,11 @@ Requires-Dist: pydantic>=2.0.0
|
|
|
22
30
|
Requires-Dist: requests>=2.28.0
|
|
23
31
|
Requires-Dist: scikit-learn>=1.3.0
|
|
24
32
|
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: bandit>=1.7.0; extra == 'dev'
|
|
25
34
|
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
26
35
|
Requires-Dist: isort>=5.0.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: safety>=2.0.0; extra == 'dev'
|
|
27
38
|
Description-Content-Type: text/markdown
|
|
28
39
|
|
|
29
40
|
# Rootly MCP Server
|
|
@@ -31,12 +42,11 @@ Description-Content-Type: text/markdown
|
|
|
31
42
|
[](https://pypi.org/project/rootly-mcp-server/)
|
|
32
43
|
[](https://pypi.org/project/rootly-mcp-server/)
|
|
33
44
|
[](https://pypi.org/project/rootly-mcp-server/)
|
|
45
|
+
[](https://cursor.com/install-mcp?name=rootly&config=eyJjb21tYW5kIjoibnB4IC15IG1jcC1yZW1vdGUgaHR0cHM6Ly9tY3Aucm9vdGx5LmNvbS9zc2UgLS1oZWFkZXIgQXV0aG9yaXphdGlvbjoke1JPT1RMWV9BVVRIX0hFQURFUn0iLCJlbnYiOnsiUk9PVExZX0FVVEhfSEVBREVSIjoiQmVhcmVyIDxZT1VSX1JPT1RMWV9BUElfVE9LRU4%2BIn19)
|
|
34
46
|
|
|
35
47
|
An MCP server for the [Rootly API](https://docs.rootly.com/api-reference/overview) that integrates seamlessly with MCP-compatible editors like Cursor, Windsurf, and Claude. Resolve production incidents in under a minute without leaving your IDE.
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-

|
|
49
|
+

|
|
40
50
|
|
|
41
51
|
## Prerequisites
|
|
42
52
|
|
|
@@ -45,7 +55,17 @@ An MCP server for the [Rootly API](https://docs.rootly.com/api-reference/overvie
|
|
|
45
55
|
```bash
|
|
46
56
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
47
57
|
```
|
|
48
|
-
- [Rootly API token](https://docs.rootly.com/api-reference/overview#how-to-generate-an-api-key%3F)
|
|
58
|
+
- [Rootly API token](https://docs.rootly.com/api-reference/overview#how-to-generate-an-api-key%3F) with appropriate permissions (see below)
|
|
59
|
+
|
|
60
|
+
### API Token Permissions
|
|
61
|
+
|
|
62
|
+
The MCP server requires a Rootly API token. Choose the appropriate token type based on your needs:
|
|
63
|
+
|
|
64
|
+
- **Global API Key** (Recommended): Full access to all entities across your Rootly instance. Required for organization-wide visibility across teams, schedules, and incidents.
|
|
65
|
+
- **Team API Key**: Team Admin permissions with full read/edit access to entities owned by that team. Suitable for team-specific workflows.
|
|
66
|
+
- **Personal API Key**: Inherits the permissions of the user who created it. Works for individual use cases but may have limited visibility.
|
|
67
|
+
|
|
68
|
+
For full functionality of tools like `get_oncall_handoff_summary`, `get_oncall_shift_metrics`, and organization-wide incident search, a **Global API Key** is recommended.
|
|
49
69
|
|
|
50
70
|
## Installation
|
|
51
71
|
|
|
@@ -63,7 +83,7 @@ Configure your MCP-compatible editor (tested with Cursor) with one of the config
|
|
|
63
83
|
"run",
|
|
64
84
|
"--from",
|
|
65
85
|
"rootly-mcp-server",
|
|
66
|
-
"rootly-mcp-server"
|
|
86
|
+
"rootly-mcp-server"
|
|
67
87
|
],
|
|
68
88
|
"env": {
|
|
69
89
|
"ROOTLY_API_TOKEN": "<YOUR_ROOTLY_API_TOKEN>"
|
|
@@ -83,7 +103,7 @@ Configure your MCP-compatible editor (tested with Cursor) with one of the config
|
|
|
83
103
|
"args": [
|
|
84
104
|
"--from",
|
|
85
105
|
"rootly-mcp-server",
|
|
86
|
-
"rootly-mcp-server"
|
|
106
|
+
"rootly-mcp-server"
|
|
87
107
|
],
|
|
88
108
|
"env": {
|
|
89
109
|
"ROOTLY_API_TOKEN": "<YOUR_ROOTLY_API_TOKEN>"
|
|
@@ -143,47 +163,88 @@ Alternatively, connect directly to our hosted MCP server:
|
|
|
143
163
|
- **Dynamic Tool Generation**: Automatically creates MCP resources from Rootly's OpenAPI (Swagger) specification
|
|
144
164
|
- **Smart Pagination**: Defaults to 10 items per request for incident endpoints to prevent context window overflow
|
|
145
165
|
- **API Filtering**: Limits exposed API endpoints for security and performance
|
|
146
|
-
- **
|
|
166
|
+
- **Intelligent Incident Analysis**: Smart tools that analyze historical incident data
|
|
147
167
|
- **`find_related_incidents`**: Uses TF-IDF similarity analysis to find historically similar incidents
|
|
148
168
|
- **`suggest_solutions`**: Mines past incident resolutions to recommend actionable solutions
|
|
149
169
|
- **MCP Resources**: Exposes incident and team data as structured resources for easy AI reference
|
|
150
170
|
- **Intelligent Pattern Recognition**: Automatically identifies services, error types, and resolution patterns
|
|
151
171
|
|
|
152
|
-
|
|
172
|
+
## Example Skills
|
|
153
173
|
|
|
154
|
-
|
|
174
|
+
Want to get started quickly? We provide pre-built Claude Code skills that showcase the full power of the Rootly MCP server:
|
|
155
175
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
/
|
|
171
|
-
/
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
/v1/workflows/{workflow_id}
|
|
176
|
-
/v1/workflow_runs
|
|
177
|
-
/v1/workflow_runs/{workflow_run_id}
|
|
178
|
-
/v1/environments
|
|
179
|
-
/v1/environments/{environment_id}
|
|
180
|
-
/v1/users
|
|
181
|
-
/v1/users/{user_id}
|
|
182
|
-
/v1/users/me
|
|
183
|
-
/v1/status_pages
|
|
184
|
-
/v1/status_pages/{status_page_id}
|
|
176
|
+
### 🚨 [Rootly Incident Responder](examples/skills/rootly-incident-responder.md)
|
|
177
|
+
|
|
178
|
+
An AI-powered incident response specialist that:
|
|
179
|
+
- Analyzes production incidents with full context
|
|
180
|
+
- Finds similar historical incidents using ML-based similarity matching
|
|
181
|
+
- Suggests solutions based on past successful resolutions
|
|
182
|
+
- Coordinates with on-call teams across timezones
|
|
183
|
+
- Correlates incidents with recent code changes and deployments
|
|
184
|
+
- Creates action items and remediation plans
|
|
185
|
+
- Provides confidence scores and time estimates
|
|
186
|
+
|
|
187
|
+
**Quick Start:**
|
|
188
|
+
```bash
|
|
189
|
+
# Copy the skill to your project
|
|
190
|
+
mkdir -p .claude/skills
|
|
191
|
+
cp examples/skills/rootly-incident-responder.md .claude/skills/
|
|
192
|
+
|
|
193
|
+
# Then in Claude Code, invoke it:
|
|
194
|
+
# @rootly-incident-responder analyze incident #12345
|
|
185
195
|
```
|
|
186
196
|
|
|
197
|
+
This skill demonstrates a complete incident response workflow using Rootly's intelligent tools combined with GitHub integration for code correlation.
|
|
198
|
+
|
|
199
|
+
### Available Tools
|
|
200
|
+
|
|
201
|
+
**Alerts**
|
|
202
|
+
- `listIncidentAlerts`
|
|
203
|
+
- `listAlerts`
|
|
204
|
+
- `attachAlert`
|
|
205
|
+
- `createAlert`
|
|
206
|
+
|
|
207
|
+
**Environments**
|
|
208
|
+
- `listEnvironments`
|
|
209
|
+
- `createEnvironment`
|
|
210
|
+
|
|
211
|
+
**Functionalities**
|
|
212
|
+
- `listFunctionalities`
|
|
213
|
+
- `createFunctionality`
|
|
214
|
+
|
|
215
|
+
**Workflows**
|
|
216
|
+
- `listWorkflows`
|
|
217
|
+
- `createWorkflow`
|
|
218
|
+
|
|
219
|
+
**Incidents**
|
|
220
|
+
- `listIncidentActionItems`
|
|
221
|
+
- `createIncidentActionItem`
|
|
222
|
+
- `listIncident_Types`
|
|
223
|
+
- `createIncidentType`
|
|
224
|
+
- `search_incidents`
|
|
225
|
+
- `find_related_incidents`
|
|
226
|
+
- `suggest_solutions`
|
|
227
|
+
|
|
228
|
+
**On-Call**
|
|
229
|
+
- `get_oncall_shift_metrics`
|
|
230
|
+
- `get_oncall_handoff_summary`
|
|
231
|
+
- `get_shift_incidents`
|
|
232
|
+
|
|
233
|
+
**Services & Severities**
|
|
234
|
+
- `listServices`
|
|
235
|
+
- `createService`
|
|
236
|
+
- `listSeverities`
|
|
237
|
+
- `createSeverity`
|
|
238
|
+
|
|
239
|
+
**Teams & Users**
|
|
240
|
+
- `listTeams`
|
|
241
|
+
- `createTeam`
|
|
242
|
+
- `listUsers`
|
|
243
|
+
- `getCurrentUser`
|
|
244
|
+
|
|
245
|
+
**Meta**
|
|
246
|
+
- `list_endpoints`
|
|
247
|
+
|
|
187
248
|
### Why Path Limiting?
|
|
188
249
|
|
|
189
250
|
We limit exposed API paths for two key reasons:
|
|
@@ -193,14 +254,14 @@ We limit exposed API paths for two key reasons:
|
|
|
193
254
|
|
|
194
255
|
To expose additional paths, modify the `allowed_paths` variable in `src/rootly_mcp_server/server.py`.
|
|
195
256
|
|
|
196
|
-
###
|
|
257
|
+
### Smart Analysis Tools
|
|
197
258
|
|
|
198
259
|
The MCP server includes intelligent tools that analyze historical incident data to provide actionable insights:
|
|
199
260
|
|
|
200
261
|
#### `find_related_incidents`
|
|
201
|
-
Finds historically similar incidents using
|
|
262
|
+
Finds historically similar incidents using text similarity analysis:
|
|
202
263
|
```
|
|
203
|
-
find_related_incidents(incident_id="12345", similarity_threshold=0.
|
|
264
|
+
find_related_incidents(incident_id="12345", similarity_threshold=0.15, max_results=5)
|
|
204
265
|
```
|
|
205
266
|
- **Input**: Incident ID, similarity threshold (0.0-1.0), max results
|
|
206
267
|
- **Output**: Similar incidents with confidence scores, matched services, and resolution times
|
|
@@ -215,7 +276,7 @@ suggest_solutions(incident_title="Payment API errors", incident_description="Use
|
|
|
215
276
|
```
|
|
216
277
|
- **Input**: Either incident ID OR title/description text
|
|
217
278
|
- **Output**: Actionable solution recommendations with confidence scores and time estimates
|
|
218
|
-
- **Use Case**: Get
|
|
279
|
+
- **Use Case**: Get intelligent suggestions based on successful past resolutions
|
|
219
280
|
|
|
220
281
|
#### How It Works
|
|
221
282
|
- **Text Similarity**: Uses TF-IDF vectorization and cosine similarity (scikit-learn)
|
|
@@ -232,10 +293,56 @@ For optimal results, ensure your Rootly incidents have descriptive:
|
|
|
232
293
|
|
|
233
294
|
Example good resolution summary: `"Restarted auth-service, cleared Redis cache, and increased connection pool from 10 to 50"`
|
|
234
295
|
|
|
235
|
-
|
|
296
|
+
### On-Call Shift Metrics
|
|
297
|
+
|
|
298
|
+
Get on-call shift metrics for any time period, grouped by user, team, or schedule. Includes primary/secondary role tracking, shift counts, hours, and days on-call.
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
get_oncall_shift_metrics(
|
|
302
|
+
start_date="2025-10-01",
|
|
303
|
+
end_date="2025-10-31",
|
|
304
|
+
group_by="user"
|
|
305
|
+
)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### On-Call Handoff Summary
|
|
309
|
+
|
|
310
|
+
Complete handoff: current/next on-call + incidents during shifts.
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
# All on-call (any timezone)
|
|
314
|
+
get_oncall_handoff_summary(
|
|
315
|
+
team_ids="team-1,team-2",
|
|
316
|
+
timezone="America/Los_Angeles"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# Regional filter - only show APAC on-call during APAC business hours
|
|
320
|
+
get_oncall_handoff_summary(
|
|
321
|
+
timezone="Asia/Tokyo",
|
|
322
|
+
filter_by_region=True
|
|
323
|
+
)
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Regional filtering shows only people on-call during business hours (9am-5pm) in the specified timezone.
|
|
327
|
+
|
|
328
|
+
Returns: `schedules` with `current_oncall`, `next_oncall`, and `shift_incidents`
|
|
329
|
+
|
|
330
|
+
### Shift Incidents
|
|
331
|
+
|
|
332
|
+
Incidents during a time period, with filtering by severity/status/tags.
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
get_shift_incidents(
|
|
336
|
+
start_time="2025-10-20T09:00:00Z",
|
|
337
|
+
end_time="2025-10-20T17:00:00Z",
|
|
338
|
+
severity="critical", # optional
|
|
339
|
+
status="resolved", # optional
|
|
340
|
+
tags="database,api" # optional
|
|
341
|
+
)
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Returns: `incidents` list + `summary` (counts, avg resolution time, grouping)
|
|
236
345
|
|
|
237
|
-
This project was developed by [Rootly AI Labs](https://labs.rootly.ai/), where we're building the future of system reliability and operational excellence. As an open-source incubator, we share ideas, experiment, and rapidly prototype solutions that benefit the entire community.
|
|
238
|
-

|
|
239
346
|
|
|
240
347
|
## Developer Setup & Troubleshooting
|
|
241
348
|
|
|
@@ -265,9 +372,32 @@ To add new dependencies during development:
|
|
|
265
372
|
uv pip install <package>
|
|
266
373
|
```
|
|
267
374
|
|
|
268
|
-
### 3.
|
|
375
|
+
### 3. Set Up Git Hooks (Recommended for Contributors)
|
|
376
|
+
|
|
377
|
+
Install pre-commit hooks to automatically run linting and tests before commits:
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
./scripts/setup-hooks.sh
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
This ensures code quality by running:
|
|
384
|
+
- Ruff linting
|
|
385
|
+
- Pyright type checking
|
|
386
|
+
- Unit tests
|
|
387
|
+
|
|
388
|
+
### 4. Verify Installation
|
|
269
389
|
|
|
270
390
|
The server should now be ready to use with your MCP-compatible editor.
|
|
271
391
|
|
|
272
392
|
**For developers:** Additional testing tools are available in the `tests/` directory.
|
|
273
393
|
|
|
394
|
+
## Play with it on Postman
|
|
395
|
+
[<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://god.gw.postman.com/run-collection/45004446-1074ba3c-44fe-40e3-a932-af7c071b96eb?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D45004446-1074ba3c-44fe-40e3-a932-af7c071b96eb%26entityType%3Dcollection%26workspaceId%3D4bec6e3c-50a0-4746-85f1-00a703c32f24)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
## About Rootly AI Labs
|
|
399
|
+
|
|
400
|
+
This project was developed by [Rootly AI Labs](https://labs.rootly.ai/), where we're building the future of system reliability and operational excellence. As an open-source incubator, we share ideas, experiment, and rapidly prototype solutions that benefit the entire community.
|
|
401
|
+

|
|
402
|
+
|
|
403
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
rootly_mcp_server/__init__.py,sha256=LAXe2qmP6Yw5MMx-02NAIObwY6l4lKDf8SegYJ0jUhs,873
|
|
2
|
+
rootly_mcp_server/__main__.py,sha256=mt74vaOpfHnX5rTO0CFAeulatR_9K3NBNCaLAhBLxlc,6886
|
|
3
|
+
rootly_mcp_server/client.py,sha256=Qca2R9cgBxXcyobQj4RHl8gdxLB4Jphq0RIr61DAVKw,6542
|
|
4
|
+
rootly_mcp_server/exceptions.py,sha256=67J_wlfOICg87eUipbkARzn_6u_Io82L-5cVnk2UPr0,4504
|
|
5
|
+
rootly_mcp_server/monitoring.py,sha256=k1X7vK65FOTrCrOsLUXrFm6AJxKpXt_a0PzL6xdPuVU,11681
|
|
6
|
+
rootly_mcp_server/pagination.py,sha256=2hZSO4DLUEJZbdF8oDfIt2_7X_XGBG1jIxN8VGmeJBE,2420
|
|
7
|
+
rootly_mcp_server/security.py,sha256=YkMoVALZ3XaKnMu3yF5kVf3SW_jdKHllSMwVLk1OlX0,11556
|
|
8
|
+
rootly_mcp_server/server.py,sha256=tJLRJgurdFlq-7m7jCsRYCeD1LFUbLff0YQ7yUQJWVc,115527
|
|
9
|
+
rootly_mcp_server/smart_utils.py,sha256=c7S-8H151GfmDw6dZBDdLH_cCmR1qiXkKEYSKc0WwUY,23481
|
|
10
|
+
rootly_mcp_server/texttest.json,sha256=KV9m13kWugmW1VEpU80Irp50uCcLgJtV1YT-JzMogQg,154182
|
|
11
|
+
rootly_mcp_server/utils.py,sha256=TWG1MaaFKrU1phRhU6FgHuZAEv91JOe_1w0L2OrPJMY,4406
|
|
12
|
+
rootly_mcp_server/validators.py,sha256=z1Lvel2SpOFLo1cPdQGSrX2ySt6zqR42w0R6QV9c2Cc,4092
|
|
13
|
+
rootly_mcp_server/data/__init__.py,sha256=KdWD6hiRssHXt0Ywgj3wjNHY1sx-XSPEqVHqrTArf54,143
|
|
14
|
+
rootly_mcp_server-2.1.0.dist-info/METADATA,sha256=0fXm7kY3z3npobc9BeEm-P0R6Et3yRGAisb-Wcr40vM,13560
|
|
15
|
+
rootly_mcp_server-2.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
16
|
+
rootly_mcp_server-2.1.0.dist-info/entry_points.txt,sha256=NE33b8VgigVPGBkboyo6pvN1Vz35HZtLybxMO4Q03PI,70
|
|
17
|
+
rootly_mcp_server-2.1.0.dist-info/licenses/LICENSE,sha256=c9w9ZZGl14r54tsP40oaq5adTVX_HMNHozPIH2ymzmw,11341
|
|
18
|
+
rootly_mcp_server-2.1.0.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
rootly_mcp_server/__init__.py,sha256=rvIuqIyuzgC7b9qSnylrdDP2zPO-7Ou9AoblR6re1co,629
|
|
2
|
-
rootly_mcp_server/__main__.py,sha256=_F4p65_VjnN84RtmEdESVLLH0tO5tL9qBfb2Xdvbj2E,6480
|
|
3
|
-
rootly_mcp_server/client.py,sha256=uit-YijR7OAJtysBoclqnublEDVkFfcb29wSzhpBv44,4686
|
|
4
|
-
rootly_mcp_server/server.py,sha256=dlqsLnD3QzRtSlUYzK_PUe5zR9rbQRvySRSjmKbCy5w,51476
|
|
5
|
-
rootly_mcp_server/smart_utils.py,sha256=lvGN9ITyJjBkm7ejpYagd8VWodLKnC6FmwECfCOcGwM,22973
|
|
6
|
-
rootly_mcp_server/utils.py,sha256=NyxdcDiFGlV2a8eBO4lKgZg0D7Gxr6xUIB0YyJGgpPA,4165
|
|
7
|
-
rootly_mcp_server/data/__init__.py,sha256=fO8a0bQnRVEoRMHKvhFzj10bhoaw7VsI51czc2MsUm4,143
|
|
8
|
-
rootly_mcp_server-2.0.14.dist-info/METADATA,sha256=XvbzBwNb-XkGeedNz2oZDRebwvBA1cUYSYSrPftB9L4,9078
|
|
9
|
-
rootly_mcp_server-2.0.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
10
|
-
rootly_mcp_server-2.0.14.dist-info/entry_points.txt,sha256=NE33b8VgigVPGBkboyo6pvN1Vz35HZtLybxMO4Q03PI,70
|
|
11
|
-
rootly_mcp_server-2.0.14.dist-info/licenses/LICENSE,sha256=c9w9ZZGl14r54tsP40oaq5adTVX_HMNHozPIH2ymzmw,11341
|
|
12
|
-
rootly_mcp_server-2.0.14.dist-info/RECORD,,
|
|
File without changes
|