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.
@@ -2,9 +2,9 @@
2
2
  Shared utilities for Rootly MCP Server.
3
3
  """
4
4
 
5
- import re
6
5
  import logging
7
- from typing import Dict, Any
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'\[([^\]]+)\]', r'_\1', name)
24
-
23
+ sanitized = re.sub(r"\[([^\]]+)\]", r"_\1", name)
24
+
25
25
  # Replace any remaining invalid characters with underscores
26
- sanitized = re.sub(r'[^a-zA-Z0-9_.-]', '_', sanitized)
27
-
26
+ sanitized = re.sub(r"[^a-zA-Z0-9_.-]", "_", sanitized)
27
+
28
28
  # Remove multiple consecutive underscores
29
- sanitized = re.sub(r'_{2,}', '_', sanitized)
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: Dict[str, Any]) -> Dict[str, str]:
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 path, path_item in spec["paths"].items():
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(f"Sanitized path-level parameter: '{original_name}' -> '{sanitized_name}'")
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 ["get", "post", "put", "delete", "patch", "options", "head", "trace"]:
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(f"Sanitized operation parameter: '{original_name}' -> '{sanitized_name}'")
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 param_name, param_def in spec["components"]["parameters"].items():
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(f"Sanitized component parameter: '{original_name}' -> '{sanitized_name}'")
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.14
4
- Summary: A Model Context Protocol server for Rootly APIs using OpenAPI spec
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
- Requires-Python: >=3.12
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
  [![PyPI version](https://badge.fury.io/py/rootly-mcp-server.svg)](https://pypi.org/project/rootly-mcp-server/)
32
43
  [![PyPI - Downloads](https://img.shields.io/pypi/dm/rootly-mcp-server)](https://pypi.org/project/rootly-mcp-server/)
33
44
  [![Python Version](https://img.shields.io/pypi/pyversions/rootly-mcp-server.svg)](https://pypi.org/project/rootly-mcp-server/)
45
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](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
- [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=rootly&config=eyJjb21tYW5kIjoibnB4IC15IG1jcC1yZW1vdGUgaHR0cHM6Ly9tY3Aucm9vdGx5LmNvbS9zc2UgLS1oZWFkZXIgQXV0aG9yaXphdGlvbjoke1JPT1RMWV9BVVRIX0hFQURFUn0iLCJlbnYiOnsiUk9PVExZX0FVVEhfSEVBREVSIjoiQmVhcmVyIDxZT1VSX1JPT1RMWV9BUElfVE9LRU4%2BIn19)
38
-
39
- ![Demo GIF](rootly-mcp-server-demo.gif)
49
+ ![Demo GIF](https://raw.githubusercontent.com/Rootly-AI-Labs/Rootly-MCP-server/refs/heads/main/rootly-mcp-server-demo.gif)
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
- - **AI-Powered Incident Analysis**: Smart tools that learn from historical incident data
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
- ### Whitelisted Endpoints
172
+ ## Example Skills
153
173
 
154
- By default, the following Rootly API endpoints are exposed to the AI agent (see `allowed_paths` in `src/rootly_mcp_server/server.py`):
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
- /v1/incidents
158
- /v1/incidents/{incident_id}/alerts
159
- /v1/alerts
160
- /v1/alerts/{alert_id}
161
- /v1/severities
162
- /v1/severities/{severity_id}
163
- /v1/teams
164
- /v1/teams/{team_id}
165
- /v1/services
166
- /v1/services/{service_id}
167
- /v1/functionalities
168
- /v1/functionalities/{functionality_id}
169
- /v1/incident_types
170
- /v1/incident_types/{incident_type_id}
171
- /v1/incident_action_items
172
- /v1/incident_action_items/{incident_action_item_id}
173
- /v1/incidents/{incident_id}/action_items
174
- /v1/workflows
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
- ### AI-Powered Smart Tools
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 machine learning text analysis:
262
+ Finds historically similar incidents using text similarity analysis:
202
263
  ```
203
- find_related_incidents(incident_id="12345", similarity_threshold=0.3, max_results=5)
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 AI-powered suggestions based on successful past resolutions
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
- ## About Rootly AI Labs
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
- ![Rootly AI logo](https://github.com/Rootly-AI-Labs/EventOrOutage/raw/main/rootly-ai.png)
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. Verify Installation
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
+ ![Rootly AI logo](https://github.com/Rootly-AI-Labs/EventOrOutage/raw/main/rootly-ai.png)
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,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,