rootly-mcp-server 1.0.0__py3-none-any.whl → 2.0.1__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/__init__.py +1 -1
- rootly_mcp_server/__main__.py +78 -10
- rootly_mcp_server/client.py +40 -26
- rootly_mcp_server/routemap_server.py +206 -0
- rootly_mcp_server/server.py +439 -365
- rootly_mcp_server/test_client.py +128 -47
- rootly_mcp_server-2.0.1.dist-info/METADATA +225 -0
- rootly_mcp_server-2.0.1.dist-info/RECORD +12 -0
- rootly_mcp_server/data/swagger.json +0 -1
- rootly_mcp_server-1.0.0.dist-info/METADATA +0 -128
- rootly_mcp_server-1.0.0.dist-info/RECORD +0 -12
- {rootly_mcp_server-1.0.0.dist-info → rootly_mcp_server-2.0.1.dist-info}/WHEEL +0 -0
- {rootly_mcp_server-1.0.0.dist-info → rootly_mcp_server-2.0.1.dist-info}/entry_points.txt +0 -0
- {rootly_mcp_server-1.0.0.dist-info → rootly_mcp_server-2.0.1.dist-info}/licenses/LICENSE +0 -0
rootly_mcp_server/__init__.py
CHANGED
rootly_mcp_server/__main__.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
1
2
|
"""
|
|
2
|
-
|
|
3
|
+
Rootly MCP Server - Main entry point
|
|
4
|
+
|
|
5
|
+
This module provides the main entry point for the Rootly MCP Server.
|
|
3
6
|
"""
|
|
4
7
|
|
|
5
8
|
import argparse
|
|
@@ -7,8 +10,9 @@ import logging
|
|
|
7
10
|
import os
|
|
8
11
|
import sys
|
|
9
12
|
from pathlib import Path
|
|
13
|
+
from typing import Optional, List
|
|
10
14
|
|
|
11
|
-
from .
|
|
15
|
+
from .routemap_server import create_rootly_mcp_server
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
def parse_args():
|
|
@@ -46,6 +50,27 @@ def parse_args():
|
|
|
46
50
|
action="store_true",
|
|
47
51
|
help="Enable debug mode (equivalent to --log-level DEBUG)",
|
|
48
52
|
)
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
"--base-url",
|
|
55
|
+
type=str,
|
|
56
|
+
help="Base URL for the Rootly API. Default: https://api.rootly.com",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--allowed-paths",
|
|
60
|
+
type=str,
|
|
61
|
+
help="Comma-separated list of allowed API paths to include",
|
|
62
|
+
)
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"--hosted",
|
|
65
|
+
action="store_true",
|
|
66
|
+
help="Enable hosted mode for remote MCP server",
|
|
67
|
+
)
|
|
68
|
+
# Backward compatibility: support deprecated --host argument
|
|
69
|
+
parser.add_argument(
|
|
70
|
+
"--host",
|
|
71
|
+
action="store_true",
|
|
72
|
+
help="(Deprecated) Use --hosted instead. Enable hosted mode for remote MCP server",
|
|
73
|
+
)
|
|
49
74
|
return parser.parse_args()
|
|
50
75
|
|
|
51
76
|
|
|
@@ -67,11 +92,7 @@ def setup_logging(log_level, debug=False):
|
|
|
67
92
|
|
|
68
93
|
# Set specific logger levels
|
|
69
94
|
logging.getLogger("rootly_mcp_server").setLevel(numeric_level)
|
|
70
|
-
logging.getLogger("
|
|
71
|
-
|
|
72
|
-
# Always set MCP logger to ERROR level to fix Cline UI issue
|
|
73
|
-
# This prevents INFO logs from causing problems with Cline tool display
|
|
74
|
-
logging.getLogger("mcp").setLevel(logging.ERROR)
|
|
95
|
+
logging.getLogger("mcp").setLevel(numeric_level)
|
|
75
96
|
|
|
76
97
|
# Log the configuration
|
|
77
98
|
logger = logging.getLogger(__name__)
|
|
@@ -97,22 +118,69 @@ def check_api_token():
|
|
|
97
118
|
logger.debug(f"Token starts with: {api_token[:5]}...")
|
|
98
119
|
|
|
99
120
|
|
|
121
|
+
# Create the server instance for FastMCP CLI (follows quickstart pattern)
|
|
122
|
+
def get_server():
|
|
123
|
+
"""Get a configured Rootly MCP server instance."""
|
|
124
|
+
# Get configuration from environment variables
|
|
125
|
+
swagger_path = os.getenv("ROOTLY_SWAGGER_PATH")
|
|
126
|
+
server_name = os.getenv("ROOTLY_SERVER_NAME", "Rootly")
|
|
127
|
+
hosted = os.getenv("ROOTLY_HOSTED", "false").lower() in ("true", "1", "yes")
|
|
128
|
+
base_url = os.getenv("ROOTLY_BASE_URL")
|
|
129
|
+
|
|
130
|
+
# Parse allowed paths from environment variable
|
|
131
|
+
allowed_paths = None
|
|
132
|
+
allowed_paths_env = os.getenv("ROOTLY_ALLOWED_PATHS")
|
|
133
|
+
if allowed_paths_env:
|
|
134
|
+
allowed_paths = [path.strip() for path in allowed_paths_env.split(",")]
|
|
135
|
+
|
|
136
|
+
# Create and return the server
|
|
137
|
+
return create_rootly_mcp_server(
|
|
138
|
+
swagger_path=swagger_path,
|
|
139
|
+
name=server_name,
|
|
140
|
+
allowed_paths=allowed_paths,
|
|
141
|
+
hosted=hosted,
|
|
142
|
+
base_url=base_url,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# Create the server instance for FastMCP CLI (follows quickstart pattern)
|
|
147
|
+
mcp = get_server()
|
|
148
|
+
|
|
149
|
+
|
|
100
150
|
def main():
|
|
101
|
-
"""
|
|
151
|
+
"""Main entry point for the Rootly MCP Server."""
|
|
102
152
|
args = parse_args()
|
|
103
153
|
setup_logging(args.log_level, args.debug)
|
|
104
154
|
|
|
105
155
|
logger = logging.getLogger(__name__)
|
|
106
156
|
logger.info("Starting Rootly MCP Server")
|
|
107
157
|
|
|
158
|
+
# Handle backward compatibility for --host argument
|
|
159
|
+
hosted_mode = args.hosted
|
|
160
|
+
if args.host:
|
|
161
|
+
logger.warning("--host argument is deprecated, use --hosted instead")
|
|
162
|
+
hosted_mode = True
|
|
163
|
+
|
|
108
164
|
check_api_token()
|
|
109
165
|
|
|
110
166
|
try:
|
|
167
|
+
# Parse allowed paths from command line argument
|
|
168
|
+
allowed_paths = None
|
|
169
|
+
if args.allowed_paths:
|
|
170
|
+
allowed_paths = [path.strip() for path in args.allowed_paths.split(",")]
|
|
171
|
+
|
|
111
172
|
logger.info(f"Initializing server with name: {args.name}")
|
|
112
|
-
server =
|
|
173
|
+
server = create_rootly_mcp_server(
|
|
174
|
+
swagger_path=args.swagger_path,
|
|
175
|
+
name=args.name,
|
|
176
|
+
allowed_paths=allowed_paths,
|
|
177
|
+
hosted=hosted_mode,
|
|
178
|
+
base_url=args.base_url,
|
|
179
|
+
)
|
|
113
180
|
|
|
114
181
|
logger.info(f"Running server with transport: {args.transport}...")
|
|
115
182
|
server.run(transport=args.transport)
|
|
183
|
+
|
|
116
184
|
except FileNotFoundError as e:
|
|
117
185
|
logger.error(f"File not found: {e}")
|
|
118
186
|
print(f"Error: {e}", file=sys.stderr)
|
|
@@ -124,4 +192,4 @@ def main():
|
|
|
124
192
|
|
|
125
193
|
|
|
126
194
|
if __name__ == "__main__":
|
|
127
|
-
main()
|
|
195
|
+
main()
|
rootly_mcp_server/client.py
CHANGED
|
@@ -11,10 +11,13 @@ from typing import Optional, Dict, Any, Union
|
|
|
11
11
|
# Set up logger
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
class RootlyClient:
|
|
15
|
-
def __init__(self, base_url: Optional[str] = None):
|
|
16
|
+
def __init__(self, base_url: Optional[str] = None, hosted: bool = False):
|
|
16
17
|
self.base_url = base_url or "https://api.rootly.com"
|
|
17
|
-
self.
|
|
18
|
+
self.hosted = hosted
|
|
19
|
+
if not self.hosted:
|
|
20
|
+
self._api_token = self._get_api_token()
|
|
18
21
|
logger.debug(f"Initialized RootlyClient with base_url: {self.base_url}")
|
|
19
22
|
|
|
20
23
|
def _get_api_token(self) -> str:
|
|
@@ -24,25 +27,39 @@ class RootlyClient:
|
|
|
24
27
|
raise ValueError("ROOTLY_API_TOKEN environment variable is not set")
|
|
25
28
|
return api_token
|
|
26
29
|
|
|
27
|
-
def make_request(
|
|
30
|
+
def make_request(
|
|
31
|
+
self,
|
|
32
|
+
method: str,
|
|
33
|
+
path: str,
|
|
34
|
+
query_params: Optional[Dict[str, Any]] = None,
|
|
35
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
36
|
+
json_api_type: Optional[str] = None,
|
|
37
|
+
api_token: Optional[str] = None,
|
|
38
|
+
) -> str:
|
|
28
39
|
"""
|
|
29
40
|
Make an authenticated request to the Rootly API.
|
|
30
|
-
|
|
41
|
+
|
|
31
42
|
Args:
|
|
32
43
|
method: The HTTP method to use.
|
|
33
44
|
path: The API path.
|
|
34
45
|
query_params: Query parameters for the request.
|
|
35
46
|
json_data: JSON data for the request body.
|
|
36
47
|
json_api_type: If set, use JSON-API format and this type value.
|
|
37
|
-
|
|
48
|
+
|
|
38
49
|
Returns:
|
|
39
50
|
The API response as a JSON string.
|
|
40
51
|
"""
|
|
52
|
+
if self.hosted:
|
|
53
|
+
if not api_token:
|
|
54
|
+
return json.dumps({"error": "No API token provided"})
|
|
55
|
+
else:
|
|
56
|
+
api_token = self._api_token
|
|
57
|
+
|
|
41
58
|
# Default headers
|
|
42
59
|
headers = {
|
|
43
|
-
"Authorization": f"Bearer {
|
|
60
|
+
"Authorization": f"Bearer {api_token}",
|
|
44
61
|
"Content-Type": "application/json",
|
|
45
|
-
"Accept": "application/json"
|
|
62
|
+
"Accept": "application/json",
|
|
46
63
|
}
|
|
47
64
|
|
|
48
65
|
# If JSON-API, update headers and wrap payload
|
|
@@ -50,30 +67,25 @@ class RootlyClient:
|
|
|
50
67
|
headers["Content-Type"] = "application/vnd.api+json"
|
|
51
68
|
headers["Accept"] = "application/vnd.api+json"
|
|
52
69
|
if json_data:
|
|
53
|
-
json_data = {
|
|
54
|
-
"data": {
|
|
55
|
-
"type": json_api_type,
|
|
56
|
-
"attributes": json_data
|
|
57
|
-
}
|
|
58
|
-
}
|
|
70
|
+
json_data = {"data": {"type": json_api_type, "attributes": json_data}}
|
|
59
71
|
else:
|
|
60
72
|
json_data = None
|
|
61
73
|
|
|
62
74
|
# Ensure path starts with a slash
|
|
63
75
|
if not path.startswith("/"):
|
|
64
76
|
path = f"/{path}"
|
|
65
|
-
|
|
77
|
+
|
|
66
78
|
# Ensure path starts with /v1 if not already
|
|
67
79
|
if not path.startswith("/v1"):
|
|
68
80
|
path = f"/v1{path}"
|
|
69
|
-
|
|
81
|
+
|
|
70
82
|
url = f"{self.base_url}{path}"
|
|
71
|
-
|
|
83
|
+
|
|
72
84
|
logger.debug(f"Making {method} request to {url}")
|
|
73
85
|
logger.debug(f"Headers: {headers}")
|
|
74
86
|
logger.debug(f"Query params: {query_params}")
|
|
75
87
|
logger.debug(f"JSON data: {json_data}")
|
|
76
|
-
|
|
88
|
+
|
|
77
89
|
try:
|
|
78
90
|
response = requests.request(
|
|
79
91
|
method=method.upper(),
|
|
@@ -81,17 +93,19 @@ class RootlyClient:
|
|
|
81
93
|
headers=headers,
|
|
82
94
|
params=query_params,
|
|
83
95
|
json=json_data,
|
|
84
|
-
timeout=30 # Add a timeout to prevent hanging
|
|
96
|
+
timeout=30, # Add a timeout to prevent hanging
|
|
85
97
|
)
|
|
86
|
-
|
|
98
|
+
|
|
87
99
|
# Log the response status and headers
|
|
88
100
|
logger.debug(f"Response status: {response.status_code}")
|
|
89
101
|
logger.debug(f"Response headers: {response.headers}")
|
|
90
|
-
|
|
102
|
+
|
|
91
103
|
# Try to parse the response as JSON
|
|
92
104
|
try:
|
|
93
105
|
response_json = response.json()
|
|
94
|
-
logger.debug(
|
|
106
|
+
logger.debug(
|
|
107
|
+
f"Response parsed as JSON: {json.dumps(response_json)[:200]}..."
|
|
108
|
+
)
|
|
95
109
|
response.raise_for_status()
|
|
96
110
|
return json.dumps(response_json, indent=2)
|
|
97
111
|
except ValueError:
|
|
@@ -99,20 +113,20 @@ class RootlyClient:
|
|
|
99
113
|
logger.debug(f"Response is not JSON: {response.text[:200]}...")
|
|
100
114
|
response.raise_for_status()
|
|
101
115
|
return json.dumps({"text": response.text}, indent=2)
|
|
102
|
-
|
|
116
|
+
|
|
103
117
|
except requests.exceptions.RequestException as e:
|
|
104
118
|
logger.error(f"Request failed: {e}")
|
|
105
119
|
error_response = {"error": str(e)}
|
|
106
|
-
|
|
120
|
+
|
|
107
121
|
# Add response details if available
|
|
108
|
-
if hasattr(e,
|
|
122
|
+
if hasattr(e, "response") and e.response is not None:
|
|
109
123
|
try:
|
|
110
124
|
error_response["status_code"] = e.response.status_code
|
|
111
125
|
error_response["response_text"] = e.response.text
|
|
112
126
|
except:
|
|
113
127
|
pass
|
|
114
|
-
|
|
128
|
+
|
|
115
129
|
return json.dumps(error_response, indent=2)
|
|
116
130
|
except Exception as e:
|
|
117
131
|
logger.error(f"Unexpected error: {e}")
|
|
118
|
-
return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2)
|
|
132
|
+
return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Rootly FastMCP Server (RouteMap Version)
|
|
4
|
+
|
|
5
|
+
Alternative implementation using FastMCP's RouteMap system for filtering
|
|
6
|
+
instead of pre-filtering the OpenAPI spec.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from fastmcp import FastMCP
|
|
11
|
+
from fastmcp.server.openapi import RouteMap, MCPType
|
|
12
|
+
import os
|
|
13
|
+
import logging
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional, List
|
|
17
|
+
|
|
18
|
+
# 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
|
|
21
|
+
|
|
22
|
+
# Configure logging
|
|
23
|
+
logging.basicConfig(level=logging.INFO)
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
def create_rootly_mcp_server(
|
|
27
|
+
swagger_path: Optional[str] = None,
|
|
28
|
+
name: str = "Rootly API Server (RouteMap Filtered)",
|
|
29
|
+
allowed_paths: Optional[List[str]] = None,
|
|
30
|
+
hosted: bool = False,
|
|
31
|
+
base_url: Optional[str] = None,
|
|
32
|
+
):
|
|
33
|
+
"""Create and configure the Rootly MCP server using RouteMap filtering."""
|
|
34
|
+
|
|
35
|
+
# Get Rootly API token from environment
|
|
36
|
+
ROOTLY_API_TOKEN = os.getenv("ROOTLY_API_TOKEN")
|
|
37
|
+
if not ROOTLY_API_TOKEN:
|
|
38
|
+
raise ValueError("ROOTLY_API_TOKEN environment variable is required")
|
|
39
|
+
|
|
40
|
+
logger.info("Creating authenticated HTTP client...")
|
|
41
|
+
# Create authenticated HTTP client
|
|
42
|
+
client = httpx.AsyncClient(
|
|
43
|
+
base_url=base_url or "https://api.rootly.com",
|
|
44
|
+
headers={
|
|
45
|
+
"Authorization": f"Bearer {ROOTLY_API_TOKEN}",
|
|
46
|
+
"Content-Type": "application/vnd.api+json",
|
|
47
|
+
"User-Agent": "Rootly-FastMCP-Server/1.0"
|
|
48
|
+
},
|
|
49
|
+
timeout=30.0
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
logger.info("Loading OpenAPI specification...")
|
|
53
|
+
# Load OpenAPI spec with smart fallback logic
|
|
54
|
+
openapi_spec = load_rootly_openapi_spec()
|
|
55
|
+
logger.info("✅ Successfully loaded OpenAPI specification")
|
|
56
|
+
|
|
57
|
+
logger.info("Fixing OpenAPI spec for FastMCP compatibility...")
|
|
58
|
+
# Fix array types for FastMCP compatibility
|
|
59
|
+
def fix_array_types(obj):
|
|
60
|
+
if isinstance(obj, dict):
|
|
61
|
+
keys_to_process = list(obj.keys())
|
|
62
|
+
for key in keys_to_process:
|
|
63
|
+
value = obj[key]
|
|
64
|
+
if key == 'type' and isinstance(value, list):
|
|
65
|
+
non_null_types = [t for t in value if t != 'null']
|
|
66
|
+
if len(non_null_types) >= 1:
|
|
67
|
+
obj[key] = non_null_types[0]
|
|
68
|
+
obj['nullable'] = True
|
|
69
|
+
else:
|
|
70
|
+
fix_array_types(value)
|
|
71
|
+
elif isinstance(obj, list):
|
|
72
|
+
for item in obj:
|
|
73
|
+
fix_array_types(item)
|
|
74
|
+
|
|
75
|
+
fix_array_types(openapi_spec)
|
|
76
|
+
logger.info("✅ Fixed OpenAPI spec compatibility issues")
|
|
77
|
+
|
|
78
|
+
logger.info("Creating FastMCP server with RouteMap filtering...")
|
|
79
|
+
|
|
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
|
+
]
|
|
168
|
+
|
|
169
|
+
# Create MCP server with custom route maps
|
|
170
|
+
mcp = FastMCP.from_openapi(
|
|
171
|
+
openapi_spec=openapi_spec,
|
|
172
|
+
client=client,
|
|
173
|
+
name=name,
|
|
174
|
+
timeout=30.0,
|
|
175
|
+
tags={"rootly", "incident-management", "evaluation"},
|
|
176
|
+
route_maps=route_maps
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
logger.info(f"✅ Created MCP server with RouteMap filtering successfully")
|
|
180
|
+
logger.info("🚀 Selected Rootly API endpoints are now available as MCP tools")
|
|
181
|
+
|
|
182
|
+
return mcp
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def main():
|
|
188
|
+
"""Main entry point."""
|
|
189
|
+
try:
|
|
190
|
+
logger.info("🚀 Starting Rootly FastMCP Server (RouteMap Version)...")
|
|
191
|
+
mcp = create_rootly_mcp_server()
|
|
192
|
+
|
|
193
|
+
logger.info("🌐 Server starting on stdio transport...")
|
|
194
|
+
logger.info("Ready for MCP client connections!")
|
|
195
|
+
|
|
196
|
+
# Run the MCP server
|
|
197
|
+
mcp.run()
|
|
198
|
+
|
|
199
|
+
except KeyboardInterrupt:
|
|
200
|
+
logger.info("🛑 Server stopped by user")
|
|
201
|
+
except Exception as e:
|
|
202
|
+
logger.error(f"❌ Server error: {e}")
|
|
203
|
+
raise
|
|
204
|
+
|
|
205
|
+
if __name__ == "__main__":
|
|
206
|
+
main()
|