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.
@@ -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
- sys.path.append(str(Path(__file__).parent.parent.parent))
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
- allowed_paths: Optional[List[str]] = None,
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 RouteMap filtering...")
76
+ logger.info("Creating FastMCP server with pre-filtered OpenAPI spec...")
79
77
 
80
- # Define custom route maps for filtering specific endpoints
81
- route_maps = [
82
- # Core incident management
83
- RouteMap(
84
- pattern=r"^/v1/incidents$",
85
- mcp_type=MCPType.TOOL
86
- ),
87
- RouteMap(
88
- pattern=r"^/v1/incidents/\{incident_id\}/alerts$",
89
- mcp_type=MCPType.TOOL
90
- ),
91
- RouteMap(
92
- pattern=r"^/v1/incidents/\{incident_id\}/action_items$",
93
- mcp_type=MCPType.TOOL
94
- ),
95
-
96
- # Alert management
97
- RouteMap(
98
- pattern=r"^/v1/alerts$",
99
- mcp_type=MCPType.TOOL
100
- ),
101
- RouteMap(
102
- pattern=r"^/v1/alerts/\{id\}$",
103
- mcp_type=MCPType.TOOL
104
- ),
105
-
106
- # Configuration entities
107
- RouteMap(
108
- pattern=r"^/v1/severities(\{id\})?$",
109
- mcp_type=MCPType.TOOL
110
- ),
111
- RouteMap(
112
- pattern=r"^/v1/incident_types(\{id\})?$",
113
- mcp_type=MCPType.TOOL
114
- ),
115
- RouteMap(
116
- pattern=r"^/v1/functionalities(\{id\})?$",
117
- mcp_type=MCPType.TOOL
118
- ),
119
-
120
- # Organization
121
- RouteMap(
122
- pattern=r"^/v1/teams(\{id\})?$",
123
- mcp_type=MCPType.TOOL
124
- ),
125
- RouteMap(
126
- pattern=r"^/v1/users(\{id\}|/me)?$",
127
- mcp_type=MCPType.TOOL
128
- ),
129
-
130
- # Infrastructure
131
- RouteMap(
132
- pattern=r"^/v1/services(\{id\})?$",
133
- mcp_type=MCPType.TOOL
134
- ),
135
- RouteMap(
136
- pattern=r"^/v1/environments(\{id\})?$",
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 with custom route maps
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.2
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/routemap_server.py,sha256=SloHu4ZTaFBusoIlhM2h7jfB8kFtMQrT3M_n7JAekug,6101
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.2.dist-info/METADATA,sha256=gVQHYX9Z8KlzftzQZluRXsJ45K3kEkzxkxTpyHSdyCM,6141
9
- rootly_mcp_server-2.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- rootly_mcp_server-2.0.2.dist-info/entry_points.txt,sha256=NE33b8VgigVPGBkboyo6pvN1Vz35HZtLybxMO4Q03PI,70
11
- rootly_mcp_server-2.0.2.dist-info/licenses/LICENSE,sha256=c9w9ZZGl14r54tsP40oaq5adTVX_HMNHozPIH2ymzmw,11341
12
- rootly_mcp_server-2.0.2.dist-info/RECORD,,
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,,