rootly-mcp-server 2.1.0__py3-none-any.whl → 2.1.1__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.
@@ -163,131 +163,9 @@ def strip_heavy_nested_data(data: dict[str, Any]) -> dict[str, Any]:
163
163
  # Replace with just count
164
164
  rels[rel_key] = {"count": len(rels[rel_key]["data"])}
165
165
 
166
- # Process "included" section (common in shifts/alerts with user data)
167
- if "included" in data and isinstance(data["included"], list):
168
- for item in data["included"]:
169
- if item.get("type") == "users":
170
- # Keep only essential user fields
171
- if "attributes" in item:
172
- attrs = item["attributes"]
173
- keep_fields = {"name", "email", "phone", "time_zone", "full_name"}
174
- item["attributes"] = {k: v for k, v in attrs.items() if k in keep_fields}
175
- # Strip heavy relationships
176
- if "relationships" in item:
177
- for rel_key in [
178
- "schedules",
179
- "notification_rules",
180
- "teams",
181
- "devices",
182
- "email_addresses",
183
- "phone_numbers",
184
- ]:
185
- if rel_key in item["relationships"]:
186
- rel_data = item["relationships"][rel_key]
187
- if isinstance(rel_data, dict) and "data" in rel_data:
188
- data_list = rel_data.get("data", [])
189
- if isinstance(data_list, list):
190
- item["relationships"][rel_key] = {"count": len(data_list)}
191
-
192
- # Process alerts in data list
193
- if "data" in data and isinstance(data["data"], list):
194
- for item in data["data"]:
195
- if item.get("type") == "alerts":
196
- # Strip heavy attributes from alerts
197
- if "attributes" in item:
198
- attrs = item["attributes"]
199
- # Remove heavy fields - raw data, embedded objects, integration fields
200
- heavy_fields = [
201
- "data", # Raw alert payload from source - very large
202
- "labels",
203
- "external_url",
204
- "pagerduty_incident_id",
205
- "pagerduty_incident_url",
206
- "opsgenie_alert_id",
207
- "opsgenie_alert_url",
208
- "deduplication_key",
209
- ]
210
- for field in heavy_fields:
211
- attrs.pop(field, None)
212
-
213
- # Simplify embedded objects to just IDs/counts
214
- # groups - keep only group_ids
215
- if "groups" in attrs:
216
- attrs.pop("groups", None)
217
- # environments - keep only environment_ids
218
- if "environments" in attrs:
219
- attrs.pop("environments", None)
220
- # services - keep only service_ids
221
- if "services" in attrs:
222
- attrs.pop("services", None)
223
- # incidents - embedded incident objects
224
- if "incidents" in attrs:
225
- attrs.pop("incidents", None)
226
- # responders - embedded responder objects
227
- if "responders" in attrs:
228
- attrs.pop("responders", None)
229
- # notified_users - embedded user objects
230
- if "notified_users" in attrs:
231
- attrs.pop("notified_users", None)
232
- # alerting_targets - embedded target objects
233
- if "alerting_targets" in attrs:
234
- attrs.pop("alerting_targets", None)
235
- # alert_urgency - keep only alert_urgency_id
236
- if "alert_urgency" in attrs:
237
- attrs.pop("alert_urgency", None)
238
- # alert_field_values - embedded custom field values
239
- if "alert_field_values" in attrs:
240
- attrs.pop("alert_field_values", None)
241
-
242
- # Strip heavy relationships
243
- if "relationships" in item:
244
- rels = item["relationships"]
245
- for rel_key in ["events", "subscribers", "alerts"]:
246
- if (
247
- rel_key in rels
248
- and isinstance(rels[rel_key], dict)
249
- and "data" in rels[rel_key]
250
- ):
251
- data_list = rels[rel_key].get("data", [])
252
- if isinstance(data_list, list):
253
- rels[rel_key] = {"count": len(data_list)}
254
-
255
166
  return data
256
167
 
257
168
 
258
- class ProcessedResponse:
259
- """Wrapper around httpx.Response that processes JSON to reduce payload size."""
260
-
261
- def __init__(self, response: httpx.Response):
262
- self._response = response
263
- self._processed_json = None
264
-
265
- def json(self, **kwargs):
266
- """Parse JSON and strip heavy nested data."""
267
- if self._processed_json is None:
268
- raw_data = self._response.json(**kwargs)
269
- self._processed_json = strip_heavy_nested_data(raw_data)
270
- return self._processed_json
271
-
272
- def __getattr__(self, name):
273
- """Delegate all other attributes to the wrapped response."""
274
- return getattr(self._response, name)
275
-
276
-
277
- class ResponseProcessingClient(httpx.AsyncClient):
278
- """AsyncClient subclass that wraps responses to reduce payload size.
279
-
280
- This is necessary because FastMCP.from_openapi() uses the client directly,
281
- bypassing any wrapper class. By subclassing httpx.AsyncClient, we ensure
282
- all responses go through our processing.
283
- """
284
-
285
- async def request(self, method, url, **kwargs):
286
- """Override request to wrap response with ProcessedResponse."""
287
- response = await super().request(method, url, **kwargs)
288
- return ProcessedResponse(response)
289
-
290
-
291
169
  class MCPError:
292
170
  """Enhanced error handling for MCP protocol compliance."""
293
171
 
@@ -468,7 +346,7 @@ class AuthenticatedHTTPXClient:
468
346
  if self._api_token:
469
347
  headers["Authorization"] = f"Bearer {self._api_token}"
470
348
 
471
- self.client = ResponseProcessingClient(
349
+ self.client = httpx.AsyncClient(
472
350
  base_url=base_url,
473
351
  headers=headers,
474
352
  timeout=30.0,
@@ -500,16 +378,13 @@ class AuthenticatedHTTPXClient:
500
378
  return transformed
501
379
 
502
380
  async def request(self, method: str, url: str, **kwargs):
503
- """Override request to transform parameters and wrap response for payload reduction."""
381
+ """Override request to transform parameters."""
504
382
  # Transform query parameters
505
383
  if "params" in kwargs:
506
384
  kwargs["params"] = self._transform_params(kwargs["params"])
507
385
 
508
- # Call the underlying client's request method
509
- response = await self.client.request(method, url, **kwargs)
510
-
511
- # Wrap response to process JSON and reduce payload size
512
- return ProcessedResponse(response)
386
+ # Call the underlying client's request method and let it handle everything
387
+ return await self.client.request(method, url, **kwargs)
513
388
 
514
389
  async def get(self, url: str, **kwargs):
515
390
  """Proxy to request with GET method."""
@@ -612,9 +487,12 @@ def create_rootly_mcp_server(
612
487
 
613
488
  # Create the MCP server using OpenAPI integration
614
489
  # By default, all routes become tools which is what we want
490
+ # NOTE: We pass http_client (the wrapper) instead of http_client.client (the inner httpx client)
491
+ # so that parameter transformation (e.g., filter_status -> filter[status]) is applied.
492
+ # The wrapper implements the same interface as httpx.AsyncClient (duck typing).
615
493
  mcp = FastMCP.from_openapi(
616
494
  openapi_spec=filtered_spec,
617
- client=http_client.client,
495
+ client=http_client, # type: ignore[arg-type]
618
496
  name=name,
619
497
  timeout=30.0,
620
498
  tags={"rootly", "incident-management"},
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rootly-mcp-server
3
- Version: 2.1.0
3
+ Version: 2.1.1
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
@@ -5,14 +5,14 @@ rootly_mcp_server/exceptions.py,sha256=67J_wlfOICg87eUipbkARzn_6u_Io82L-5cVnk2UP
5
5
  rootly_mcp_server/monitoring.py,sha256=k1X7vK65FOTrCrOsLUXrFm6AJxKpXt_a0PzL6xdPuVU,11681
6
6
  rootly_mcp_server/pagination.py,sha256=2hZSO4DLUEJZbdF8oDfIt2_7X_XGBG1jIxN8VGmeJBE,2420
7
7
  rootly_mcp_server/security.py,sha256=YkMoVALZ3XaKnMu3yF5kVf3SW_jdKHllSMwVLk1OlX0,11556
8
- rootly_mcp_server/server.py,sha256=tJLRJgurdFlq-7m7jCsRYCeD1LFUbLff0YQ7yUQJWVc,115527
8
+ rootly_mcp_server/server.py,sha256=IQ2lZNZKckLRFr2ALjRL9AAjGWTsfQq4jkhlh0-BUig,109962
9
9
  rootly_mcp_server/smart_utils.py,sha256=c7S-8H151GfmDw6dZBDdLH_cCmR1qiXkKEYSKc0WwUY,23481
10
10
  rootly_mcp_server/texttest.json,sha256=KV9m13kWugmW1VEpU80Irp50uCcLgJtV1YT-JzMogQg,154182
11
11
  rootly_mcp_server/utils.py,sha256=TWG1MaaFKrU1phRhU6FgHuZAEv91JOe_1w0L2OrPJMY,4406
12
12
  rootly_mcp_server/validators.py,sha256=z1Lvel2SpOFLo1cPdQGSrX2ySt6zqR42w0R6QV9c2Cc,4092
13
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,,
14
+ rootly_mcp_server-2.1.1.dist-info/METADATA,sha256=iaXmvsZRLzSyYHpERE-xdn0Rd6rrrCXbO57lWZeIBHs,13560
15
+ rootly_mcp_server-2.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
+ rootly_mcp_server-2.1.1.dist-info/entry_points.txt,sha256=NE33b8VgigVPGBkboyo6pvN1Vz35HZtLybxMO4Q03PI,70
17
+ rootly_mcp_server-2.1.1.dist-info/licenses/LICENSE,sha256=c9w9ZZGl14r54tsP40oaq5adTVX_HMNHozPIH2ymzmw,11341
18
+ rootly_mcp_server-2.1.1.dist-info/RECORD,,