rootly-mcp-server 2.0.2__py3-none-any.whl → 2.0.3__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 +96 -0
- rootly_mcp_server/routemap_server.py +62 -96
- {rootly_mcp_server-2.0.2.dist-info → rootly_mcp_server-2.0.3.dist-info}/METADATA +1 -1
- {rootly_mcp_server-2.0.2.dist-info → rootly_mcp_server-2.0.3.dist-info}/RECORD +7 -6
- {rootly_mcp_server-2.0.2.dist-info → rootly_mcp_server-2.0.3.dist-info}/WHEEL +0 -0
- {rootly_mcp_server-2.0.2.dist-info → rootly_mcp_server-2.0.3.dist-info}/entry_points.txt +0 -0
- {rootly_mcp_server-2.0.2.dist-info → rootly_mcp_server-2.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Rootly OpenAPI Loader Utility
|
|
4
|
+
|
|
5
|
+
Shared utility for loading Rootly's OpenAPI specification with smart fallback logic.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_rootly_openapi_spec() -> dict:
|
|
17
|
+
"""
|
|
18
|
+
Load Rootly OpenAPI spec with smart fallback logic.
|
|
19
|
+
|
|
20
|
+
Loading priority:
|
|
21
|
+
1. Check current directory for rootly_openapi.json
|
|
22
|
+
2. Check parent directories for rootly_openapi.json
|
|
23
|
+
3. Check for swagger.json files
|
|
24
|
+
4. Only as last resort, fetch from URL and cache locally
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
dict: The OpenAPI specification
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
RuntimeError: If the specification cannot be loaded
|
|
31
|
+
"""
|
|
32
|
+
current_dir = Path.cwd()
|
|
33
|
+
|
|
34
|
+
# Check for rootly_openapi.json in current directory and parents
|
|
35
|
+
for check_dir in [current_dir] + list(current_dir.parents):
|
|
36
|
+
spec_file = check_dir / "rootly_openapi.json"
|
|
37
|
+
if spec_file.is_file():
|
|
38
|
+
logger.info(f"Found OpenAPI spec at {spec_file}")
|
|
39
|
+
try:
|
|
40
|
+
with open(spec_file, "r") as f:
|
|
41
|
+
return json.load(f)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.warning(f"Failed to load {spec_file}: {e}")
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
# Check for swagger.json in current directory and parents
|
|
47
|
+
for check_dir in [current_dir] + list(current_dir.parents):
|
|
48
|
+
spec_file = check_dir / "swagger.json"
|
|
49
|
+
if spec_file.is_file():
|
|
50
|
+
logger.info(f"Found Swagger spec at {spec_file}")
|
|
51
|
+
try:
|
|
52
|
+
with open(spec_file, "r") as f:
|
|
53
|
+
return json.load(f)
|
|
54
|
+
except Exception as e:
|
|
55
|
+
logger.warning(f"Failed to load {spec_file}: {e}")
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
# Last resort: fetch from URL and cache
|
|
59
|
+
logger.warning("OpenAPI spec not found locally, fetching from URL (this should only happen once)")
|
|
60
|
+
return _fetch_and_cache_spec()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _fetch_and_cache_spec() -> dict:
|
|
64
|
+
"""
|
|
65
|
+
Fetch OpenAPI spec from Rootly's URL and cache it locally.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
dict: The OpenAPI specification
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
RuntimeError: If the specification cannot be fetched
|
|
72
|
+
"""
|
|
73
|
+
SWAGGER_URL = "https://rootly-heroku.s3.amazonaws.com/swagger/v1/swagger.json"
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
logger.info(f"Fetching OpenAPI spec from {SWAGGER_URL}")
|
|
77
|
+
response = httpx.get(SWAGGER_URL, timeout=30.0)
|
|
78
|
+
response.raise_for_status()
|
|
79
|
+
spec_data = response.json()
|
|
80
|
+
|
|
81
|
+
# Cache the spec for next time
|
|
82
|
+
current_dir = Path.cwd()
|
|
83
|
+
cache_file = current_dir / "rootly_openapi.json"
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
with open(cache_file, "w") as f:
|
|
87
|
+
json.dump(spec_data, f, indent=2)
|
|
88
|
+
logger.info(f"Cached OpenAPI spec to {cache_file} for future use")
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logger.warning(f"Failed to cache OpenAPI spec: {e}")
|
|
91
|
+
|
|
92
|
+
return spec_data
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(f"Failed to fetch OpenAPI spec: {e}")
|
|
96
|
+
raise RuntimeError(f"Could not load OpenAPI specification: {e}")
|
|
@@ -11,13 +11,11 @@ from fastmcp import FastMCP
|
|
|
11
11
|
from fastmcp.server.openapi import RouteMap, MCPType
|
|
12
12
|
import os
|
|
13
13
|
import logging
|
|
14
|
-
import sys
|
|
15
14
|
from pathlib import Path
|
|
16
15
|
from typing import Optional, List
|
|
17
16
|
|
|
18
17
|
# Import the shared OpenAPI loader
|
|
19
|
-
|
|
20
|
-
from rootly_openapi_loader import load_rootly_openapi_spec
|
|
18
|
+
from .rootly_openapi_loader import load_rootly_openapi_spec
|
|
21
19
|
|
|
22
20
|
# Configure logging
|
|
23
21
|
logging.basicConfig(level=logging.INFO)
|
|
@@ -26,7 +24,7 @@ logger = logging.getLogger(__name__)
|
|
|
26
24
|
def create_rootly_mcp_server(
|
|
27
25
|
swagger_path: Optional[str] = None,
|
|
28
26
|
name: str = "Rootly API Server (RouteMap Filtered)",
|
|
29
|
-
|
|
27
|
+
custom_allowed_paths: Optional[List[str]] = None,
|
|
30
28
|
hosted: bool = False,
|
|
31
29
|
base_url: Optional[str] = None,
|
|
32
30
|
):
|
|
@@ -75,105 +73,73 @@ def create_rootly_mcp_server(
|
|
|
75
73
|
fix_array_types(openapi_spec)
|
|
76
74
|
logger.info("✅ Fixed OpenAPI spec compatibility issues")
|
|
77
75
|
|
|
78
|
-
logger.info("Creating FastMCP server with
|
|
76
|
+
logger.info("Creating FastMCP server with pre-filtered OpenAPI spec...")
|
|
79
77
|
|
|
80
|
-
# Define
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
mcp_type=MCPType.TOOL
|
|
138
|
-
),
|
|
139
|
-
|
|
140
|
-
# Action items
|
|
141
|
-
RouteMap(
|
|
142
|
-
pattern=r"^/v1/incident_action_items(\{id\})?$",
|
|
143
|
-
mcp_type=MCPType.TOOL
|
|
144
|
-
),
|
|
145
|
-
|
|
146
|
-
# Workflows
|
|
147
|
-
RouteMap(
|
|
148
|
-
pattern=r"^/v1/workflows(\{id\})?$",
|
|
149
|
-
mcp_type=MCPType.TOOL
|
|
150
|
-
),
|
|
151
|
-
RouteMap(
|
|
152
|
-
pattern=r"^/v1/workflow_runs(\{id\})?$",
|
|
153
|
-
mcp_type=MCPType.TOOL
|
|
154
|
-
),
|
|
155
|
-
|
|
156
|
-
# Status pages
|
|
157
|
-
RouteMap(
|
|
158
|
-
pattern=r"^/v1/status_pages(\{id\})?$",
|
|
159
|
-
mcp_type=MCPType.TOOL
|
|
160
|
-
),
|
|
161
|
-
|
|
162
|
-
# Exclude everything else
|
|
163
|
-
RouteMap(
|
|
164
|
-
pattern=r".*",
|
|
165
|
-
mcp_type=MCPType.EXCLUDE
|
|
166
|
-
)
|
|
167
|
-
]
|
|
78
|
+
# Define the specific endpoints we want to include
|
|
79
|
+
if custom_allowed_paths:
|
|
80
|
+
allowed_paths = set(custom_allowed_paths)
|
|
81
|
+
else:
|
|
82
|
+
allowed_paths = {
|
|
83
|
+
# Core incident management
|
|
84
|
+
"/v1/incidents",
|
|
85
|
+
"/v1/incidents/{incident_id}/alerts",
|
|
86
|
+
"/v1/incidents/{incident_id}/action_items",
|
|
87
|
+
|
|
88
|
+
# Alert management
|
|
89
|
+
"/v1/alerts",
|
|
90
|
+
"/v1/alerts/{id}",
|
|
91
|
+
|
|
92
|
+
# Configuration entities
|
|
93
|
+
"/v1/severities",
|
|
94
|
+
"/v1/severities/{id}",
|
|
95
|
+
"/v1/incident_types",
|
|
96
|
+
"/v1/incident_types/{id}",
|
|
97
|
+
"/v1/functionalities",
|
|
98
|
+
"/v1/functionalities/{id}",
|
|
99
|
+
|
|
100
|
+
# Organization
|
|
101
|
+
"/v1/teams",
|
|
102
|
+
"/v1/teams/{id}",
|
|
103
|
+
"/v1/users",
|
|
104
|
+
"/v1/users/me",
|
|
105
|
+
"/v1/users/{id}",
|
|
106
|
+
|
|
107
|
+
# Infrastructure
|
|
108
|
+
"/v1/services",
|
|
109
|
+
"/v1/services/{id}",
|
|
110
|
+
"/v1/environments",
|
|
111
|
+
"/v1/environments/{id}",
|
|
112
|
+
|
|
113
|
+
# Action items
|
|
114
|
+
"/v1/action_items",
|
|
115
|
+
"/v1/action_items/{id}",
|
|
116
|
+
|
|
117
|
+
# Workflows
|
|
118
|
+
"/v1/workflows",
|
|
119
|
+
"/v1/workflows/{id}",
|
|
120
|
+
|
|
121
|
+
# Status pages
|
|
122
|
+
"/v1/status-pages",
|
|
123
|
+
"/v1/status-pages/{id}"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Filter the OpenAPI spec to only include allowed paths
|
|
127
|
+
original_paths = openapi_spec.get("paths", {})
|
|
128
|
+
filtered_paths = {path: spec for path, spec in original_paths.items() if path in allowed_paths}
|
|
129
|
+
|
|
130
|
+
logger.info(f"📊 Filtered OpenAPI spec from {len(original_paths)} paths to {len(filtered_paths)} paths")
|
|
131
|
+
logger.info(f"🔍 Allowed paths: {sorted(allowed_paths)}")
|
|
132
|
+
logger.info(f"✅ Filtered paths: {sorted(filtered_paths.keys())}")
|
|
133
|
+
|
|
134
|
+
openapi_spec["paths"] = filtered_paths
|
|
168
135
|
|
|
169
|
-
# Create MCP server
|
|
136
|
+
# Create MCP server without route maps for now to test basic functionality
|
|
170
137
|
mcp = FastMCP.from_openapi(
|
|
171
138
|
openapi_spec=openapi_spec,
|
|
172
139
|
client=client,
|
|
173
140
|
name=name,
|
|
174
141
|
timeout=30.0,
|
|
175
|
-
tags={"rootly", "incident-management", "evaluation"}
|
|
176
|
-
route_maps=route_maps
|
|
142
|
+
tags={"rootly", "incident-management", "evaluation"}
|
|
177
143
|
)
|
|
178
144
|
|
|
179
145
|
logger.info(f"✅ Created MCP server with RouteMap filtering successfully")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rootly-mcp-server
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
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,12 +1,13 @@
|
|
|
1
1
|
rootly_mcp_server/__init__.py,sha256=H3Dd4Od-PEff8VdbMgnblS6cCEBCb9XqYdI_-wmtQqk,628
|
|
2
2
|
rootly_mcp_server/__main__.py,sha256=yOBEaQWEMZvsC7zT5no7yo1ISIvfeq7NlIPflBMoAFI,6447
|
|
3
3
|
rootly_mcp_server/client.py,sha256=05TsHVJ3WtLH0k4R19Yzwwx4xcmjCKH6hS3uKcTMwRA,4678
|
|
4
|
-
rootly_mcp_server/
|
|
4
|
+
rootly_mcp_server/rootly_openapi_loader.py,sha256=GebIDHzEMV6XCuPox3SoBOG42lyaAb3RFlhWoCdIkeE,3159
|
|
5
|
+
rootly_mcp_server/routemap_server.py,sha256=DDcuY68_B4uqg8aOuDmUp0zhrlpIxQtw0h5265ZYULk,5631
|
|
5
6
|
rootly_mcp_server/server.py,sha256=2paE39caYRVuQG4yWEEOqZewbvpFREmf-J01lPQ5elw,19756
|
|
6
7
|
rootly_mcp_server/test_client.py,sha256=8p1aJHrEt_Tj2NuJzTnTHw-ZeW816P99fJi5bhPidyc,5119
|
|
7
8
|
rootly_mcp_server/data/__init__.py,sha256=fO8a0bQnRVEoRMHKvhFzj10bhoaw7VsI51czc2MsUm4,143
|
|
8
|
-
rootly_mcp_server-2.0.
|
|
9
|
-
rootly_mcp_server-2.0.
|
|
10
|
-
rootly_mcp_server-2.0.
|
|
11
|
-
rootly_mcp_server-2.0.
|
|
12
|
-
rootly_mcp_server-2.0.
|
|
9
|
+
rootly_mcp_server-2.0.3.dist-info/METADATA,sha256=x79WLJxzQUZ_A9oYtW3qBsRz6V4n7Qb_sXRVaJa0e1g,6141
|
|
10
|
+
rootly_mcp_server-2.0.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
rootly_mcp_server-2.0.3.dist-info/entry_points.txt,sha256=NE33b8VgigVPGBkboyo6pvN1Vz35HZtLybxMO4Q03PI,70
|
|
12
|
+
rootly_mcp_server-2.0.3.dist-info/licenses/LICENSE,sha256=c9w9ZZGl14r54tsP40oaq5adTVX_HMNHozPIH2ymzmw,11341
|
|
13
|
+
rootly_mcp_server-2.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|