rootly-mcp-server 2.1.0__tar.gz → 2.1.1__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.1.0 → rootly_mcp_server-2.1.1}/#Dockerfile# +1 -1
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/beads.db +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/beads.db-shm +0 -0
- rootly_mcp_server-2.1.1/.beads/beads.db-wal +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.claude/settings.local.json +8 -1
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.github/workflows/ci.yml +13 -13
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.github/workflows/lint.yml +2 -2
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/PKG-INFO +1 -1
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/pyproject.toml +1 -1
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/server.py +8 -130
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/unit/test_authentication.py +49 -0
- rootly_mcp_server-2.1.0/.beads/beads.db-wal +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/.gitignore +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/.local_version +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/.sync.lock +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/README.md +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/config.yaml +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/daemon.lock +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/daemon.pid +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/interactions.jsonl +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/issues.jsonl +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/last-touched +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/metadata.json +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.beads/sync_base.jsonl +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.gitattributes +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.github/dependabot.yml +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.github/workflows/pypi-release.yml +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.github/workflows/test.yml +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.gitignore +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.mcp.json +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.semaphore/deploy.yml +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.semaphore/semaphore.yml +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/.semaphore/update-task-definition.sh +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/2026-01-27-MCP.txt +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/2026-01-27-claude-code-v2120.txt +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/Dockerfile +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/LICENSE +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/README.md +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/examples/skills/README.md +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/rootly-mcp-server-demo.gif +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/rootly_openapi.json +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/scripts/setup-hooks.sh +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/__init__.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/__main__.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/client.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/data/__init__.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/exceptions.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/monitoring.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/pagination.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/security.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/smart_utils.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/texttest.json +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/utils.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/src/rootly_mcp_server/validators.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/README.md +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/conftest.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/integration/local/test_basic.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/integration/local/test_smart_tools.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/integration/remote/test_essential.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/test_client.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/unit/test_exceptions.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/unit/test_oncall_handoff.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/unit/test_oncall_metrics.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/unit/test_security.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/unit/test_server.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/unit/test_smart_utils.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/unit/test_tools.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/unit/test_utils.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/unit/test_validators.py +0 -0
- {rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/uv.lock +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -63,7 +63,14 @@
|
|
|
63
63
|
"Bash(git rm:*)",
|
|
64
64
|
"Bash(black:*)",
|
|
65
65
|
"Bash(isort:*)",
|
|
66
|
-
"Bash(python -m isort:*)"
|
|
66
|
+
"Bash(python -m isort:*)",
|
|
67
|
+
"Bash(pip install:*)",
|
|
68
|
+
"Bash(gh pr close:*)",
|
|
69
|
+
"Bash(git fetch:*)",
|
|
70
|
+
"Bash(git cherry-pick:*)",
|
|
71
|
+
"Bash(git revert:*)",
|
|
72
|
+
"mcp__rootly__search_incidents",
|
|
73
|
+
"Bash(uv build:*)"
|
|
67
74
|
],
|
|
68
75
|
"deny": []
|
|
69
76
|
}
|
|
@@ -16,15 +16,15 @@ jobs:
|
|
|
16
16
|
|
|
17
17
|
steps:
|
|
18
18
|
- name: Checkout code
|
|
19
|
-
uses: actions/checkout@
|
|
19
|
+
uses: actions/checkout@v6
|
|
20
20
|
|
|
21
21
|
- name: Set up Python ${{ matrix.python-version }}
|
|
22
|
-
uses: actions/setup-python@
|
|
22
|
+
uses: actions/setup-python@v6
|
|
23
23
|
with:
|
|
24
24
|
python-version: ${{ matrix.python-version }}
|
|
25
25
|
|
|
26
26
|
- name: Install uv
|
|
27
|
-
uses: astral-sh/setup-uv@
|
|
27
|
+
uses: astral-sh/setup-uv@v7
|
|
28
28
|
|
|
29
29
|
- name: Install dependencies
|
|
30
30
|
run: |
|
|
@@ -36,7 +36,7 @@ jobs:
|
|
|
36
36
|
uv run pytest tests/ -v --cov=src/rootly_mcp_server --cov-report=term --cov-report=xml
|
|
37
37
|
|
|
38
38
|
- name: Upload coverage to Codecov
|
|
39
|
-
uses: codecov/codecov-action@
|
|
39
|
+
uses: codecov/codecov-action@v5
|
|
40
40
|
with:
|
|
41
41
|
file: ./coverage.xml
|
|
42
42
|
flags: unittests
|
|
@@ -48,15 +48,15 @@ jobs:
|
|
|
48
48
|
|
|
49
49
|
steps:
|
|
50
50
|
- name: Checkout code
|
|
51
|
-
uses: actions/checkout@
|
|
51
|
+
uses: actions/checkout@v6
|
|
52
52
|
|
|
53
53
|
- name: Set up Python
|
|
54
|
-
uses: actions/setup-python@
|
|
54
|
+
uses: actions/setup-python@v6
|
|
55
55
|
with:
|
|
56
56
|
python-version: "3.12"
|
|
57
57
|
|
|
58
58
|
- name: Install uv
|
|
59
|
-
uses: astral-sh/setup-uv@
|
|
59
|
+
uses: astral-sh/setup-uv@v7
|
|
60
60
|
|
|
61
61
|
- name: Install dependencies
|
|
62
62
|
run: |
|
|
@@ -85,10 +85,10 @@ jobs:
|
|
|
85
85
|
|
|
86
86
|
steps:
|
|
87
87
|
- name: Checkout code
|
|
88
|
-
uses: actions/checkout@
|
|
88
|
+
uses: actions/checkout@v6
|
|
89
89
|
|
|
90
90
|
- name: Set up Python
|
|
91
|
-
uses: actions/setup-python@
|
|
91
|
+
uses: actions/setup-python@v6
|
|
92
92
|
with:
|
|
93
93
|
python-version: "3.12"
|
|
94
94
|
|
|
@@ -113,22 +113,22 @@ jobs:
|
|
|
113
113
|
|
|
114
114
|
steps:
|
|
115
115
|
- name: Checkout code
|
|
116
|
-
uses: actions/checkout@
|
|
116
|
+
uses: actions/checkout@v6
|
|
117
117
|
|
|
118
118
|
- name: Set up Python
|
|
119
|
-
uses: actions/setup-python@
|
|
119
|
+
uses: actions/setup-python@v6
|
|
120
120
|
with:
|
|
121
121
|
python-version: "3.12"
|
|
122
122
|
|
|
123
123
|
- name: Install uv
|
|
124
|
-
uses: astral-sh/setup-uv@
|
|
124
|
+
uses: astral-sh/setup-uv@v7
|
|
125
125
|
|
|
126
126
|
- name: Build package
|
|
127
127
|
run: |
|
|
128
128
|
uv build
|
|
129
129
|
|
|
130
130
|
- name: Upload build artifacts
|
|
131
|
-
uses: actions/upload-artifact@
|
|
131
|
+
uses: actions/upload-artifact@v6
|
|
132
132
|
with:
|
|
133
133
|
name: dist
|
|
134
134
|
path: dist/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rootly-mcp-server
|
|
3
|
-
Version: 2.1.
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "rootly-mcp-server"
|
|
3
|
-
version = "2.1.
|
|
3
|
+
version = "2.1.1"
|
|
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.10"
|
|
@@ -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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
495
|
+
client=http_client, # type: ignore[arg-type]
|
|
618
496
|
name=name,
|
|
619
497
|
timeout=30.0,
|
|
620
498
|
tags={"rootly", "incident-management"},
|
|
@@ -238,3 +238,52 @@ class TestAuthenticationModeComparison:
|
|
|
238
238
|
|
|
239
239
|
assert local_client.hosted is False
|
|
240
240
|
assert hosted_client.hosted is True
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class TestParameterTransformation:
|
|
244
|
+
"""Test suite for parameter transformation functionality."""
|
|
245
|
+
|
|
246
|
+
def test_transform_params_with_mapping(self):
|
|
247
|
+
"""Test that parameters are transformed according to mapping."""
|
|
248
|
+
mapping = {
|
|
249
|
+
"filter_status": "filter[status]",
|
|
250
|
+
"filter_services": "filter[services]",
|
|
251
|
+
}
|
|
252
|
+
client = AuthenticatedHTTPXClient(parameter_mapping=mapping)
|
|
253
|
+
|
|
254
|
+
params = {"filter_status": "active", "filter_services": "api,web", "page": 1}
|
|
255
|
+
result = client._transform_params(params)
|
|
256
|
+
|
|
257
|
+
assert result is not None
|
|
258
|
+
assert result["filter[status]"] == "active"
|
|
259
|
+
assert result["filter[services]"] == "api,web"
|
|
260
|
+
assert result["page"] == 1
|
|
261
|
+
assert "filter_status" not in result
|
|
262
|
+
assert "filter_services" not in result
|
|
263
|
+
|
|
264
|
+
def test_transform_params_without_mapping(self):
|
|
265
|
+
"""Test that params pass through unchanged when no mapping exists."""
|
|
266
|
+
client = AuthenticatedHTTPXClient(parameter_mapping={})
|
|
267
|
+
|
|
268
|
+
params = {"filter_status": "active", "page": 1}
|
|
269
|
+
result = client._transform_params(params)
|
|
270
|
+
|
|
271
|
+
assert result is not None
|
|
272
|
+
assert result["filter_status"] == "active"
|
|
273
|
+
assert result["page"] == 1
|
|
274
|
+
|
|
275
|
+
def test_transform_params_with_none(self):
|
|
276
|
+
"""Test that None params return None."""
|
|
277
|
+
client = AuthenticatedHTTPXClient(parameter_mapping={"foo": "bar"})
|
|
278
|
+
|
|
279
|
+
result = client._transform_params(None)
|
|
280
|
+
|
|
281
|
+
assert result is None
|
|
282
|
+
|
|
283
|
+
def test_transform_params_with_empty_dict(self):
|
|
284
|
+
"""Test that empty dict returns empty dict."""
|
|
285
|
+
client = AuthenticatedHTTPXClient(parameter_mapping={"foo": "bar"})
|
|
286
|
+
|
|
287
|
+
result = client._transform_params({})
|
|
288
|
+
|
|
289
|
+
assert result == {}
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/integration/local/test_smart_tools.py
RENAMED
|
File without changes
|
{rootly_mcp_server-2.1.0 → rootly_mcp_server-2.1.1}/tests/integration/remote/test_essential.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|