rootly-mcp-server 2.0.8__py3-none-any.whl → 2.0.9__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/server.py +68 -13
- {rootly_mcp_server-2.0.8.dist-info → rootly_mcp_server-2.0.9.dist-info}/METADATA +2 -1
- {rootly_mcp_server-2.0.8.dist-info → rootly_mcp_server-2.0.9.dist-info}/RECORD +6 -6
- {rootly_mcp_server-2.0.8.dist-info → rootly_mcp_server-2.0.9.dist-info}/WHEEL +0 -0
- {rootly_mcp_server-2.0.8.dist-info → rootly_mcp_server-2.0.9.dist-info}/entry_points.txt +0 -0
- {rootly_mcp_server-2.0.8.dist-info → rootly_mcp_server-2.0.9.dist-info}/licenses/LICENSE +0 -0
rootly_mcp_server/server.py
CHANGED
|
@@ -69,7 +69,7 @@ class AuthenticatedHTTPXClient:
|
|
|
69
69
|
"""An HTTPX client wrapper that handles Rootly API authentication and parameter transformation."""
|
|
70
70
|
|
|
71
71
|
def __init__(self, base_url: str = "https://api.rootly.com", hosted: bool = False, parameter_mapping: Optional[Dict[str, str]] = None):
|
|
72
|
-
self.
|
|
72
|
+
self._base_url = base_url
|
|
73
73
|
self.hosted = hosted
|
|
74
74
|
self._api_token = None
|
|
75
75
|
self.parameter_mapping = parameter_mapping or {}
|
|
@@ -77,15 +77,22 @@ class AuthenticatedHTTPXClient:
|
|
|
77
77
|
if not self.hosted:
|
|
78
78
|
self._api_token = self._get_api_token()
|
|
79
79
|
|
|
80
|
-
# Create the HTTPX client
|
|
81
|
-
headers = {
|
|
80
|
+
# Create the HTTPX client
|
|
81
|
+
headers = {
|
|
82
|
+
"Content-Type": "application/vnd.api+json",
|
|
83
|
+
"Accept": "application/vnd.api+json"
|
|
84
|
+
# Let httpx handle Accept-Encoding automatically with all supported formats
|
|
85
|
+
}
|
|
82
86
|
if self._api_token:
|
|
83
87
|
headers["Authorization"] = f"Bearer {self._api_token}"
|
|
84
88
|
|
|
85
89
|
self.client = httpx.AsyncClient(
|
|
86
90
|
base_url=base_url,
|
|
87
91
|
headers=headers,
|
|
88
|
-
timeout=30.0
|
|
92
|
+
timeout=30.0,
|
|
93
|
+
follow_redirects=True,
|
|
94
|
+
# Ensure proper handling of compressed responses
|
|
95
|
+
limits=httpx.Limits(max_keepalive_connections=5, max_connections=10)
|
|
89
96
|
)
|
|
90
97
|
|
|
91
98
|
def _get_api_token(self) -> Optional[str]:
|
|
@@ -116,7 +123,7 @@ class AuthenticatedHTTPXClient:
|
|
|
116
123
|
if 'params' in kwargs:
|
|
117
124
|
kwargs['params'] = self._transform_params(kwargs['params'])
|
|
118
125
|
|
|
119
|
-
# Call the underlying client's request method
|
|
126
|
+
# Call the underlying client's request method and let it handle everything
|
|
120
127
|
return await self.client.request(method, url, **kwargs)
|
|
121
128
|
|
|
122
129
|
async def get(self, url: str, **kwargs):
|
|
@@ -146,8 +153,19 @@ class AuthenticatedHTTPXClient:
|
|
|
146
153
|
pass
|
|
147
154
|
|
|
148
155
|
def __getattr__(self, name):
|
|
149
|
-
# Delegate all other attributes to the underlying client
|
|
156
|
+
# Delegate all other attributes to the underlying client, except for request methods
|
|
157
|
+
if name in ['request', 'get', 'post', 'put', 'patch', 'delete']:
|
|
158
|
+
# Use our overridden methods instead
|
|
159
|
+
return getattr(self, name)
|
|
150
160
|
return getattr(self.client, name)
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def base_url(self):
|
|
164
|
+
return self._base_url
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def headers(self):
|
|
168
|
+
return self.client.headers
|
|
151
169
|
|
|
152
170
|
|
|
153
171
|
def create_rootly_mcp_server(
|
|
@@ -225,6 +243,43 @@ def create_rootly_mcp_server(
|
|
|
225
243
|
return PlainTextResponse("OK")
|
|
226
244
|
|
|
227
245
|
# Add some custom tools for enhanced functionality
|
|
246
|
+
@mcp.tool()
|
|
247
|
+
async def debug_incidents() -> dict:
|
|
248
|
+
"""Debug tool to inspect incidents endpoint response."""
|
|
249
|
+
try:
|
|
250
|
+
response = await make_authenticated_request("GET", "/v1/incidents", params={"page[size]": 1})
|
|
251
|
+
response.raise_for_status()
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
"status_code": response.status_code,
|
|
255
|
+
"headers": dict(response.headers),
|
|
256
|
+
"content_length": len(response.content) if response.content else 0,
|
|
257
|
+
"content_preview": response.content[:500].decode('utf-8', errors='ignore') if response.content else "No content",
|
|
258
|
+
"text_preview": response.text[:500] if hasattr(response, 'text') else "No text",
|
|
259
|
+
"encoding": response.encoding,
|
|
260
|
+
"content_type": response.headers.get('content-type', 'unknown')
|
|
261
|
+
}
|
|
262
|
+
except Exception as e:
|
|
263
|
+
return {"error": str(e), "error_type": type(e).__name__}
|
|
264
|
+
|
|
265
|
+
@mcp.tool()
|
|
266
|
+
async def debug_headers() -> dict:
|
|
267
|
+
"""Debug tool to inspect request/response headers for troubleshooting."""
|
|
268
|
+
try:
|
|
269
|
+
response = await make_authenticated_request("GET", "/v1/teams", params={"page[size]": 1})
|
|
270
|
+
response.raise_for_status()
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
"request_headers": dict(response.request.headers) if response.request else {},
|
|
274
|
+
"response_headers": dict(response.headers),
|
|
275
|
+
"status_code": response.status_code,
|
|
276
|
+
"content_type": response.headers.get('content-type', 'unknown'),
|
|
277
|
+
"encoding": response.encoding,
|
|
278
|
+
"content_preview": str(response.content[:200]) if response.content else "No content"
|
|
279
|
+
}
|
|
280
|
+
except Exception as e:
|
|
281
|
+
return {"error": str(e), "error_type": type(e).__name__}
|
|
282
|
+
|
|
228
283
|
@mcp.tool()
|
|
229
284
|
def list_endpoints() -> list:
|
|
230
285
|
"""List all available Rootly API endpoints with their descriptions."""
|
|
@@ -262,8 +317,8 @@ def create_rootly_mcp_server(
|
|
|
262
317
|
except Exception:
|
|
263
318
|
pass # Fallback to default client behavior
|
|
264
319
|
|
|
265
|
-
#
|
|
266
|
-
return await http_client.
|
|
320
|
+
# Use our custom client with proper error handling instead of bypassing it
|
|
321
|
+
return await http_client.request(method, url, **kwargs)
|
|
267
322
|
|
|
268
323
|
@mcp.tool()
|
|
269
324
|
async def search_incidents(
|
|
@@ -377,7 +432,7 @@ def _load_swagger_spec(swagger_path: Optional[str] = None) -> Dict[str, Any]:
|
|
|
377
432
|
logger.info(f"Using provided Swagger path: {swagger_path}")
|
|
378
433
|
if not os.path.isfile(swagger_path):
|
|
379
434
|
raise FileNotFoundError(f"Swagger file not found at {swagger_path}")
|
|
380
|
-
with open(swagger_path, "r") as f:
|
|
435
|
+
with open(swagger_path, "r", encoding="utf-8") as f:
|
|
381
436
|
return json.load(f)
|
|
382
437
|
else:
|
|
383
438
|
# First, check in the package data directory
|
|
@@ -385,7 +440,7 @@ def _load_swagger_spec(swagger_path: Optional[str] = None) -> Dict[str, Any]:
|
|
|
385
440
|
package_data_path = Path(__file__).parent / "data" / "swagger.json"
|
|
386
441
|
if package_data_path.is_file():
|
|
387
442
|
logger.info(f"Found Swagger file in package data: {package_data_path}")
|
|
388
|
-
with open(package_data_path, "r") as f:
|
|
443
|
+
with open(package_data_path, "r", encoding="utf-8") as f:
|
|
389
444
|
return json.load(f)
|
|
390
445
|
except Exception as e:
|
|
391
446
|
logger.debug(f"Could not load Swagger file from package data: {e}")
|
|
@@ -398,7 +453,7 @@ def _load_swagger_spec(swagger_path: Optional[str] = None) -> Dict[str, Any]:
|
|
|
398
453
|
swagger_path = current_dir / "swagger.json"
|
|
399
454
|
if swagger_path.is_file():
|
|
400
455
|
logger.info(f"Found Swagger file at {swagger_path}")
|
|
401
|
-
with open(swagger_path, "r") as f:
|
|
456
|
+
with open(swagger_path, "r", encoding="utf-8") as f:
|
|
402
457
|
return json.load(f)
|
|
403
458
|
|
|
404
459
|
# Check parent directories
|
|
@@ -406,7 +461,7 @@ def _load_swagger_spec(swagger_path: Optional[str] = None) -> Dict[str, Any]:
|
|
|
406
461
|
swagger_path = parent / "swagger.json"
|
|
407
462
|
if swagger_path.is_file():
|
|
408
463
|
logger.info(f"Found Swagger file at {swagger_path}")
|
|
409
|
-
with open(swagger_path, "r") as f:
|
|
464
|
+
with open(swagger_path, "r", encoding="utf-8") as f:
|
|
410
465
|
return json.load(f)
|
|
411
466
|
|
|
412
467
|
# If the file wasn't found, fetch it from the URL and save it
|
|
@@ -417,7 +472,7 @@ def _load_swagger_spec(swagger_path: Optional[str] = None) -> Dict[str, Any]:
|
|
|
417
472
|
swagger_path = current_dir / "swagger.json"
|
|
418
473
|
logger.info(f"Saving Swagger file to {swagger_path}")
|
|
419
474
|
try:
|
|
420
|
-
with open(swagger_path, "w") as f:
|
|
475
|
+
with open(swagger_path, "w", encoding="utf-8") as f:
|
|
421
476
|
json.dump(swagger_spec, f)
|
|
422
477
|
logger.info(f"Saved Swagger file to {swagger_path}")
|
|
423
478
|
except Exception as e:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rootly-mcp-server
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.9
|
|
4
4
|
Summary: A Model Context Protocol server for Rootly APIs using OpenAPI spec
|
|
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
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Topic :: Software Development :: Build Tools
|
|
16
16
|
Requires-Python: >=3.12
|
|
17
|
+
Requires-Dist: brotli>=1.0.0
|
|
17
18
|
Requires-Dist: fastmcp>=2.9.0
|
|
18
19
|
Requires-Dist: httpx>=0.24.0
|
|
19
20
|
Requires-Dist: pydantic>=2.0.0
|
|
@@ -2,12 +2,12 @@ rootly_mcp_server/__init__.py,sha256=6pLh19IFyqE-Cve9zergkD-X_yApEkInREKmRa73T6s
|
|
|
2
2
|
rootly_mcp_server/__main__.py,sha256=_F4p65_VjnN84RtmEdESVLLH0tO5tL9qBfb2Xdvbj2E,6480
|
|
3
3
|
rootly_mcp_server/client.py,sha256=diIBINJP_z4nnQIAC1b70vQSiHaNojEfUDARC2nrKHU,4681
|
|
4
4
|
rootly_mcp_server/routemap_server.py,sha256=0LfK2EzwkFQF9SpHNvGcca5ZaxkBC80gIdDojE0aUcs,6100
|
|
5
|
-
rootly_mcp_server/server.py,sha256=
|
|
5
|
+
rootly_mcp_server/server.py,sha256=8Jx_OQWYRGj3GkyNb5dNZ0JHGxGqXvDMJVZXdCEPyqU,25585
|
|
6
6
|
rootly_mcp_server/test_client.py,sha256=Ytd5ZP7vImm12CT97k3p9tlkY_JNcXHSzcGGnHCBqv0,5275
|
|
7
7
|
rootly_mcp_server/utils.py,sha256=NyxdcDiFGlV2a8eBO4lKgZg0D7Gxr6xUIB0YyJGgpPA,4165
|
|
8
8
|
rootly_mcp_server/data/__init__.py,sha256=fO8a0bQnRVEoRMHKvhFzj10bhoaw7VsI51czc2MsUm4,143
|
|
9
|
-
rootly_mcp_server-2.0.
|
|
10
|
-
rootly_mcp_server-2.0.
|
|
11
|
-
rootly_mcp_server-2.0.
|
|
12
|
-
rootly_mcp_server-2.0.
|
|
13
|
-
rootly_mcp_server-2.0.
|
|
9
|
+
rootly_mcp_server-2.0.9.dist-info/METADATA,sha256=d5e-QTrnK1MdkO5PeMtJwYLt9UjOXLmWiv6l19LoR4c,6188
|
|
10
|
+
rootly_mcp_server-2.0.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
rootly_mcp_server-2.0.9.dist-info/entry_points.txt,sha256=NE33b8VgigVPGBkboyo6pvN1Vz35HZtLybxMO4Q03PI,70
|
|
12
|
+
rootly_mcp_server-2.0.9.dist-info/licenses/LICENSE,sha256=c9w9ZZGl14r54tsP40oaq5adTVX_HMNHozPIH2ymzmw,11341
|
|
13
|
+
rootly_mcp_server-2.0.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|