rootly-mcp-server 2.0.5__py3-none-any.whl → 2.0.6__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/rootly_openapi_loader.py +19 -6
- rootly_mcp_server/routemap_server.py +185 -66
- {rootly_mcp_server-2.0.5.dist-info → rootly_mcp_server-2.0.6.dist-info}/METADATA +1 -1
- {rootly_mcp_server-2.0.5.dist-info → rootly_mcp_server-2.0.6.dist-info}/RECORD +7 -7
- {rootly_mcp_server-2.0.5.dist-info → rootly_mcp_server-2.0.6.dist-info}/WHEEL +0 -0
- {rootly_mcp_server-2.0.5.dist-info → rootly_mcp_server-2.0.6.dist-info}/entry_points.txt +0 -0
- {rootly_mcp_server-2.0.5.dist-info → rootly_mcp_server-2.0.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -18,10 +18,11 @@ def load_rootly_openapi_spec() -> dict:
|
|
|
18
18
|
Load Rootly OpenAPI spec with smart fallback logic.
|
|
19
19
|
|
|
20
20
|
Loading priority:
|
|
21
|
-
1. Check
|
|
22
|
-
2. Check
|
|
23
|
-
3. Check for
|
|
24
|
-
4.
|
|
21
|
+
1. Check for Claude-compatible swagger_claude_fixed.json
|
|
22
|
+
2. Check current directory for rootly_openapi.json
|
|
23
|
+
3. Check parent directories for rootly_openapi.json
|
|
24
|
+
4. Check for swagger.json files
|
|
25
|
+
5. Only as last resort, fetch from URL and cache locally
|
|
25
26
|
|
|
26
27
|
Returns:
|
|
27
28
|
dict: The OpenAPI specification
|
|
@@ -31,7 +32,19 @@ def load_rootly_openapi_spec() -> dict:
|
|
|
31
32
|
"""
|
|
32
33
|
current_dir = Path.cwd()
|
|
33
34
|
|
|
34
|
-
# Check for
|
|
35
|
+
# Priority 1: Check for Claude-compatible swagger file first
|
|
36
|
+
for check_dir in [current_dir] + list(current_dir.parents):
|
|
37
|
+
spec_file = check_dir / "swagger_claude_fixed.json"
|
|
38
|
+
if spec_file.is_file():
|
|
39
|
+
logger.info(f"Found Claude-compatible OpenAPI spec at {spec_file}")
|
|
40
|
+
try:
|
|
41
|
+
with open(spec_file, "r") as f:
|
|
42
|
+
return json.load(f)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
logger.warning(f"Failed to load {spec_file}: {e}")
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
# Priority 2: Check for rootly_openapi.json in current directory and parents
|
|
35
48
|
for check_dir in [current_dir] + list(current_dir.parents):
|
|
36
49
|
spec_file = check_dir / "rootly_openapi.json"
|
|
37
50
|
if spec_file.is_file():
|
|
@@ -43,7 +56,7 @@ def load_rootly_openapi_spec() -> dict:
|
|
|
43
56
|
logger.warning(f"Failed to load {spec_file}: {e}")
|
|
44
57
|
continue
|
|
45
58
|
|
|
46
|
-
# Check for swagger.json in current directory and parents
|
|
59
|
+
# Priority 3: Check for swagger.json in current directory and parents
|
|
47
60
|
for check_dir in [current_dir] + list(current_dir.parents):
|
|
48
61
|
spec_file = check_dir / "swagger.json"
|
|
49
62
|
if spec_file.is_file():
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Rootly FastMCP Server (RouteMap Version)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
instead of pre-filtering the OpenAPI spec.
|
|
5
|
+
Working implementation using FastMCP's RouteMap system with proper response handling.
|
|
7
6
|
"""
|
|
8
7
|
|
|
9
8
|
import httpx
|
|
@@ -11,7 +10,6 @@ from fastmcp import FastMCP
|
|
|
11
10
|
from fastmcp.server.openapi import RouteMap, MCPType
|
|
12
11
|
import os
|
|
13
12
|
import logging
|
|
14
|
-
from pathlib import Path
|
|
15
13
|
from typing import Optional, List
|
|
16
14
|
|
|
17
15
|
# Import the shared OpenAPI loader
|
|
@@ -36,8 +34,79 @@ def create_rootly_mcp_server(
|
|
|
36
34
|
raise ValueError("ROOTLY_API_TOKEN environment variable is required")
|
|
37
35
|
|
|
38
36
|
logger.info("Creating authenticated HTTP client...")
|
|
39
|
-
# Create
|
|
40
|
-
|
|
37
|
+
# Create a custom HTTP client wrapper that ensures string responses
|
|
38
|
+
class StringifyingClient:
|
|
39
|
+
def __init__(self, base_url: str, headers: dict, timeout: float):
|
|
40
|
+
self._client = httpx.AsyncClient(
|
|
41
|
+
base_url=base_url,
|
|
42
|
+
headers=headers,
|
|
43
|
+
timeout=timeout
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
async def __aenter__(self):
|
|
47
|
+
await self._client.__aenter__()
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
51
|
+
await self._client.__aexit__(exc_type, exc_val, exc_tb)
|
|
52
|
+
|
|
53
|
+
async def request(self, method: str, url: str, **kwargs):
|
|
54
|
+
"""Override request to return responses that FastMCP can handle."""
|
|
55
|
+
response = await self._client.request(method, url, **kwargs)
|
|
56
|
+
|
|
57
|
+
# Create a response that returns the raw text instead of structured JSON
|
|
58
|
+
class TextResponse:
|
|
59
|
+
def __init__(self, original_response):
|
|
60
|
+
self.status_code = original_response.status_code
|
|
61
|
+
self.headers = original_response.headers
|
|
62
|
+
self.url = original_response.url
|
|
63
|
+
self.request = original_response.request
|
|
64
|
+
self._original = original_response
|
|
65
|
+
|
|
66
|
+
# Pre-compute the text response
|
|
67
|
+
if original_response.status_code == 200:
|
|
68
|
+
try:
|
|
69
|
+
import json
|
|
70
|
+
data = original_response.json()
|
|
71
|
+
self._text_response = json.dumps(data, indent=2)
|
|
72
|
+
except Exception:
|
|
73
|
+
self._text_response = original_response.text or "No content"
|
|
74
|
+
else:
|
|
75
|
+
self._text_response = f"Error: HTTP {original_response.status_code} - {original_response.text}"
|
|
76
|
+
|
|
77
|
+
def json(self):
|
|
78
|
+
"""Return the original JSON data for structured_content."""
|
|
79
|
+
try:
|
|
80
|
+
# Return the original JSON data so FastMCP can handle structured_content
|
|
81
|
+
return self._original.json()
|
|
82
|
+
except Exception:
|
|
83
|
+
# If JSON parsing fails, return a wrapper structure
|
|
84
|
+
return {"result": self._text_response}
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def text(self):
|
|
88
|
+
"""Return the formatted JSON as text."""
|
|
89
|
+
return self._text_response
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def content(self):
|
|
93
|
+
return self._text_response.encode('utf-8')
|
|
94
|
+
|
|
95
|
+
def raise_for_status(self):
|
|
96
|
+
"""Delegate to original response."""
|
|
97
|
+
return self._original.raise_for_status()
|
|
98
|
+
|
|
99
|
+
def __getattr__(self, name):
|
|
100
|
+
"""Delegate any missing attributes to original response."""
|
|
101
|
+
return getattr(self._original, name)
|
|
102
|
+
|
|
103
|
+
return TextResponse(response)
|
|
104
|
+
|
|
105
|
+
def __getattr__(self, name):
|
|
106
|
+
return getattr(self._client, name)
|
|
107
|
+
|
|
108
|
+
# Create authenticated HTTP client with string conversion
|
|
109
|
+
client = StringifyingClient(
|
|
41
110
|
base_url=base_url or "https://api.rootly.com",
|
|
42
111
|
headers={
|
|
43
112
|
"Authorization": f"Bearer {ROOTLY_API_TOKEN}",
|
|
@@ -73,73 +142,125 @@ def create_rootly_mcp_server(
|
|
|
73
142
|
fix_array_types(openapi_spec)
|
|
74
143
|
logger.info("✅ Fixed OpenAPI spec compatibility issues")
|
|
75
144
|
|
|
76
|
-
logger.info("Creating FastMCP server with
|
|
145
|
+
logger.info("Creating FastMCP server with RouteMap filtering...")
|
|
77
146
|
|
|
78
|
-
# Define
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"
|
|
102
|
-
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
147
|
+
# Define custom route maps for filtering specific endpoints
|
|
148
|
+
route_maps = [
|
|
149
|
+
# Core incident management - list endpoints
|
|
150
|
+
RouteMap(
|
|
151
|
+
pattern=r"^/v1/incidents$",
|
|
152
|
+
mcp_type=MCPType.TOOL,
|
|
153
|
+
mcp_tags={"incidents", "core", "list"}
|
|
154
|
+
),
|
|
155
|
+
# Incident detail endpoints
|
|
156
|
+
RouteMap(
|
|
157
|
+
pattern=r"^/v1/incidents/\{.*\}$",
|
|
158
|
+
mcp_type=MCPType.TOOL,
|
|
159
|
+
mcp_tags={"incidents", "detail"}
|
|
160
|
+
),
|
|
161
|
+
# Incident relationships
|
|
162
|
+
RouteMap(
|
|
163
|
+
pattern=r"^/v1/incidents/\{.*\}/.*$",
|
|
164
|
+
mcp_type=MCPType.TOOL,
|
|
165
|
+
mcp_tags={"incidents", "relationships"}
|
|
166
|
+
),
|
|
167
|
+
|
|
168
|
+
# Alert management
|
|
169
|
+
RouteMap(
|
|
170
|
+
pattern=r"^/v1/alerts$",
|
|
171
|
+
mcp_type=MCPType.TOOL,
|
|
172
|
+
mcp_tags={"alerts", "core", "list"}
|
|
173
|
+
),
|
|
174
|
+
RouteMap(
|
|
175
|
+
pattern=r"^/v1/alerts/\{.*\}$",
|
|
176
|
+
mcp_type=MCPType.TOOL,
|
|
177
|
+
mcp_tags={"alerts", "detail"}
|
|
178
|
+
),
|
|
179
|
+
|
|
180
|
+
# Users - both list and detail
|
|
181
|
+
RouteMap(
|
|
182
|
+
pattern=r"^/v1/users$",
|
|
183
|
+
mcp_type=MCPType.TOOL,
|
|
184
|
+
mcp_tags={"users", "list"}
|
|
185
|
+
),
|
|
186
|
+
RouteMap(
|
|
187
|
+
pattern=r"^/v1/users/me$",
|
|
188
|
+
mcp_type=MCPType.TOOL,
|
|
189
|
+
mcp_tags={"users", "current"}
|
|
190
|
+
),
|
|
191
|
+
RouteMap(
|
|
192
|
+
pattern=r"^/v1/users/\{.*\}$",
|
|
193
|
+
mcp_type=MCPType.TOOL,
|
|
194
|
+
mcp_tags={"users", "detail"}
|
|
195
|
+
),
|
|
196
|
+
|
|
197
|
+
# Teams
|
|
198
|
+
RouteMap(
|
|
199
|
+
pattern=r"^/v1/teams$",
|
|
200
|
+
mcp_type=MCPType.TOOL,
|
|
201
|
+
mcp_tags={"teams", "list"}
|
|
202
|
+
),
|
|
203
|
+
RouteMap(
|
|
204
|
+
pattern=r"^/v1/teams/\{.*\}$",
|
|
205
|
+
mcp_type=MCPType.TOOL,
|
|
206
|
+
mcp_tags={"teams", "detail"}
|
|
207
|
+
),
|
|
208
|
+
|
|
209
|
+
# Services
|
|
210
|
+
RouteMap(
|
|
211
|
+
pattern=r"^/v1/services$",
|
|
212
|
+
mcp_type=MCPType.TOOL,
|
|
213
|
+
mcp_tags={"services", "list"}
|
|
214
|
+
),
|
|
215
|
+
RouteMap(
|
|
216
|
+
pattern=r"^/v1/services/\{.*\}$",
|
|
217
|
+
mcp_type=MCPType.TOOL,
|
|
218
|
+
mcp_tags={"services", "detail"}
|
|
219
|
+
),
|
|
220
|
+
|
|
221
|
+
# Configuration entities - list patterns
|
|
222
|
+
RouteMap(
|
|
223
|
+
pattern=r"^/v1/(severities|incident_types|environments)$",
|
|
224
|
+
mcp_type=MCPType.TOOL,
|
|
225
|
+
mcp_tags={"configuration", "list"}
|
|
226
|
+
),
|
|
227
|
+
# Configuration entities - detail patterns
|
|
228
|
+
RouteMap(
|
|
229
|
+
pattern=r"^/v1/(severities|incident_types|environments)/\{.*\}$",
|
|
230
|
+
mcp_type=MCPType.TOOL,
|
|
231
|
+
mcp_tags={"configuration", "detail"}
|
|
232
|
+
),
|
|
233
|
+
|
|
234
|
+
# Exclude everything else
|
|
235
|
+
RouteMap(
|
|
236
|
+
pattern=r".*",
|
|
237
|
+
mcp_type=MCPType.EXCLUDE
|
|
238
|
+
)
|
|
239
|
+
]
|
|
133
240
|
|
|
134
|
-
|
|
241
|
+
# Custom response handler to ensure proper MCP output format
|
|
242
|
+
def ensure_mcp_response(route, component):
|
|
243
|
+
"""Ensure all responses work with FastMCP's structured content system."""
|
|
244
|
+
# Set output schema to handle structured JSON data
|
|
245
|
+
component.output_schema = {
|
|
246
|
+
"type": "object",
|
|
247
|
+
"description": "Rootly API response data",
|
|
248
|
+
"additionalProperties": True
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
# Add description
|
|
252
|
+
if hasattr(component, 'description'):
|
|
253
|
+
component.description = f"🔧 {component.description or 'Rootly API endpoint'}"
|
|
135
254
|
|
|
136
|
-
# Create MCP server
|
|
255
|
+
# Create MCP server with custom route maps and response handling
|
|
137
256
|
mcp = FastMCP.from_openapi(
|
|
138
257
|
openapi_spec=openapi_spec,
|
|
139
258
|
client=client,
|
|
140
259
|
name=name,
|
|
141
260
|
timeout=30.0,
|
|
142
|
-
tags={"rootly", "incident-management", "evaluation"}
|
|
261
|
+
tags={"rootly", "incident-management", "evaluation"},
|
|
262
|
+
route_maps=route_maps,
|
|
263
|
+
mcp_component_fn=ensure_mcp_response
|
|
143
264
|
)
|
|
144
265
|
|
|
145
266
|
logger.info(f"✅ Created MCP server with RouteMap filtering successfully")
|
|
@@ -148,8 +269,6 @@ def create_rootly_mcp_server(
|
|
|
148
269
|
return mcp
|
|
149
270
|
|
|
150
271
|
|
|
151
|
-
|
|
152
|
-
|
|
153
272
|
def main():
|
|
154
273
|
"""Main entry point."""
|
|
155
274
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rootly-mcp-server
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.6
|
|
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
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
rootly_mcp_server/__init__.py,sha256=H3Dd4Od-PEff8VdbMgnblS6cCEBCb9XqYdI_-wmtQqk,628
|
|
2
2
|
rootly_mcp_server/__main__.py,sha256=fQLaI6b91ujqhgK9hiFN9xdGfFQC52a8S9Vk310bJXQ,6461
|
|
3
3
|
rootly_mcp_server/client.py,sha256=05TsHVJ3WtLH0k4R19Yzwwx4xcmjCKH6hS3uKcTMwRA,4678
|
|
4
|
-
rootly_mcp_server/rootly_openapi_loader.py,sha256=
|
|
5
|
-
rootly_mcp_server/routemap_server.py,sha256=
|
|
4
|
+
rootly_mcp_server/rootly_openapi_loader.py,sha256=oVD4jjWBPbuaTshIs_oxEgXQ21i0erONFy5P4yxr7c8,3782
|
|
5
|
+
rootly_mcp_server/routemap_server.py,sha256=LJGMkDJBpuujVdqsnO7aLVTjHIsJ4ojB3iYwrkPIxiw,10370
|
|
6
6
|
rootly_mcp_server/server.py,sha256=2paE39caYRVuQG4yWEEOqZewbvpFREmf-J01lPQ5elw,19756
|
|
7
7
|
rootly_mcp_server/test_client.py,sha256=8p1aJHrEt_Tj2NuJzTnTHw-ZeW816P99fJi5bhPidyc,5119
|
|
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.6.dist-info/METADATA,sha256=LNhgXwDvA7ee1rM-37ofYE41bE6-Xbx4SGCbMqcbRwE,6141
|
|
10
|
+
rootly_mcp_server-2.0.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
rootly_mcp_server-2.0.6.dist-info/entry_points.txt,sha256=NE33b8VgigVPGBkboyo6pvN1Vz35HZtLybxMO4Q03PI,70
|
|
12
|
+
rootly_mcp_server-2.0.6.dist-info/licenses/LICENSE,sha256=c9w9ZZGl14r54tsP40oaq5adTVX_HMNHozPIH2ymzmw,11341
|
|
13
|
+
rootly_mcp_server-2.0.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|