rootly-mcp-server 2.0.10__py3-none-any.whl → 2.0.11__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/client.py +1 -1
- rootly_mcp_server/server.py +392 -18
- rootly_mcp_server/smart_utils.py +398 -0
- {rootly_mcp_server-2.0.10.dist-info → rootly_mcp_server-2.0.11.dist-info}/METADATA +52 -10
- rootly_mcp_server-2.0.11.dist-info/RECORD +12 -0
- rootly_mcp_server/routemap_server.py +0 -206
- rootly_mcp_server/test_client.py +0 -150
- rootly_mcp_server-2.0.10.dist-info/RECORD +0 -13
- {rootly_mcp_server-2.0.10.dist-info → rootly_mcp_server-2.0.11.dist-info}/WHEEL +0 -0
- {rootly_mcp_server-2.0.10.dist-info → rootly_mcp_server-2.0.11.dist-info}/entry_points.txt +0 -0
- {rootly_mcp_server-2.0.10.dist-info → rootly_mcp_server-2.0.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,206 +0,0 @@
|
|
|
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("✅ 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()
|
rootly_mcp_server/test_client.py
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Test client for the Rootly MCP Server
|
|
4
|
-
|
|
5
|
-
This script demonstrates how to use the Rootly MCP Server.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
import os
|
|
10
|
-
import sys
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
# Add the src directory to the Python path
|
|
14
|
-
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
15
|
-
|
|
16
|
-
from rootly_mcp_server.server import create_rootly_mcp_server
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
async def test_server():
|
|
20
|
-
"""Test the Rootly MCP server."""
|
|
21
|
-
print("Creating Rootly MCP Server...")
|
|
22
|
-
|
|
23
|
-
try:
|
|
24
|
-
# Create the server with a subset of endpoints for testing
|
|
25
|
-
server = create_rootly_mcp_server(
|
|
26
|
-
name="TestRootly",
|
|
27
|
-
allowed_paths=[
|
|
28
|
-
"/incidents",
|
|
29
|
-
"/alerts",
|
|
30
|
-
"/teams",
|
|
31
|
-
"/services"
|
|
32
|
-
],
|
|
33
|
-
hosted=False # Use local API token
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
print("✅ Server created successfully")
|
|
37
|
-
print(f"Server type: {type(server)}")
|
|
38
|
-
|
|
39
|
-
# Use the get_tools method to access tools
|
|
40
|
-
try:
|
|
41
|
-
tools = await server.get_tools()
|
|
42
|
-
print(f"Tools type: {type(tools)}")
|
|
43
|
-
print(f"Tools: {tools}")
|
|
44
|
-
|
|
45
|
-
# Handle both dict and list cases
|
|
46
|
-
if isinstance(tools, dict):
|
|
47
|
-
tools_list = list(tools.values())
|
|
48
|
-
tools_names = list(tools.keys())
|
|
49
|
-
tool_count = len(tools)
|
|
50
|
-
elif isinstance(tools, list):
|
|
51
|
-
tools_list = tools
|
|
52
|
-
tools_names = [getattr(tool, 'name', f'tool_{i}') for i, tool in enumerate(tools)]
|
|
53
|
-
tool_count = len(tools)
|
|
54
|
-
else:
|
|
55
|
-
tools_list = []
|
|
56
|
-
tools_names = []
|
|
57
|
-
tool_count = 0
|
|
58
|
-
|
|
59
|
-
print(f"Found {tool_count} tools via get_tools() method")
|
|
60
|
-
|
|
61
|
-
# List the registered tools
|
|
62
|
-
if tool_count > 0:
|
|
63
|
-
print(f"\n📋 Registered tools ({tool_count}):")
|
|
64
|
-
for i, tool in enumerate(tools_list):
|
|
65
|
-
if isinstance(tools, dict):
|
|
66
|
-
tool_name = tools_names[i]
|
|
67
|
-
else:
|
|
68
|
-
tool_name = getattr(tool, 'name', f'tool_{i}')
|
|
69
|
-
|
|
70
|
-
description = getattr(tool, 'description', 'No description')
|
|
71
|
-
print(f" • {tool_name}: {description[:100]}...")
|
|
72
|
-
print(f" Tool type: {type(tool)}")
|
|
73
|
-
print(f" Tool attributes: {[attr for attr in dir(tool) if not attr.startswith('_')][:10]}")
|
|
74
|
-
|
|
75
|
-
# Show parameter schema if available
|
|
76
|
-
if hasattr(tool, 'inputSchema') and tool.inputSchema:
|
|
77
|
-
props = tool.inputSchema.get('properties', {})
|
|
78
|
-
if props:
|
|
79
|
-
print(f" Parameters: {', '.join(props.keys())}")
|
|
80
|
-
else:
|
|
81
|
-
print("\n⚠️ No tools found")
|
|
82
|
-
|
|
83
|
-
# Test accessing a specific tool
|
|
84
|
-
if tool_count > 0:
|
|
85
|
-
print("\n🔍 Testing tool access...")
|
|
86
|
-
if isinstance(tools, dict):
|
|
87
|
-
first_tool_name = tools_names[0]
|
|
88
|
-
first_tool = tools[first_tool_name]
|
|
89
|
-
else:
|
|
90
|
-
first_tool = tools_list[0]
|
|
91
|
-
first_tool_name = getattr(first_tool, 'name', 'unknown')
|
|
92
|
-
|
|
93
|
-
print(f" ✅ First tool: {first_tool_name}")
|
|
94
|
-
print(f" Tool details: {first_tool}")
|
|
95
|
-
|
|
96
|
-
# Try to get tools and find the specific tool
|
|
97
|
-
try:
|
|
98
|
-
all_tools = await server.get_tools()
|
|
99
|
-
if first_tool_name in all_tools:
|
|
100
|
-
retrieved_tool = all_tools[first_tool_name]
|
|
101
|
-
print(f" ✅ Successfully retrieved tool by name: {first_tool_name}")
|
|
102
|
-
print(f" Retrieved tool type: {type(retrieved_tool)}")
|
|
103
|
-
else:
|
|
104
|
-
print(f" ❌ Could not find tool by name: {first_tool_name}")
|
|
105
|
-
except Exception as e:
|
|
106
|
-
print(f" ❌ Error retrieving tools: {e}")
|
|
107
|
-
|
|
108
|
-
except Exception as e:
|
|
109
|
-
print(f"❌ Error accessing tools: {e}")
|
|
110
|
-
import traceback
|
|
111
|
-
traceback.print_exc()
|
|
112
|
-
tool_count = 0
|
|
113
|
-
|
|
114
|
-
print("\n🎉 Test completed successfully!")
|
|
115
|
-
print(f"Total tools found: {tool_count}")
|
|
116
|
-
|
|
117
|
-
except Exception as e:
|
|
118
|
-
print(f"❌ Error creating server: {e}")
|
|
119
|
-
import traceback
|
|
120
|
-
traceback.print_exc()
|
|
121
|
-
return False
|
|
122
|
-
|
|
123
|
-
return True
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def main():
|
|
127
|
-
"""Main function."""
|
|
128
|
-
print("Rootly MCP Server Test")
|
|
129
|
-
print("=" * 50)
|
|
130
|
-
|
|
131
|
-
# Check for API token
|
|
132
|
-
api_token = os.getenv("ROOTLY_API_TOKEN")
|
|
133
|
-
if not api_token:
|
|
134
|
-
print("⚠️ Warning: ROOTLY_API_TOKEN not set. Server will use mock client.")
|
|
135
|
-
else:
|
|
136
|
-
print(f"✅ API token found: {api_token[:10]}...")
|
|
137
|
-
|
|
138
|
-
# Run the test
|
|
139
|
-
success = asyncio.run(test_server())
|
|
140
|
-
|
|
141
|
-
if success:
|
|
142
|
-
print("\n🎉 All tests passed!")
|
|
143
|
-
sys.exit(0)
|
|
144
|
-
else:
|
|
145
|
-
print("\n❌ Tests failed!")
|
|
146
|
-
sys.exit(1)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if __name__ == "__main__":
|
|
150
|
-
main()
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
rootly_mcp_server/__init__.py,sha256=6pLh19IFyqE-Cve9zergkD-X_yApEkInREKmRa73T6s,628
|
|
2
|
-
rootly_mcp_server/__main__.py,sha256=_F4p65_VjnN84RtmEdESVLLH0tO5tL9qBfb2Xdvbj2E,6480
|
|
3
|
-
rootly_mcp_server/client.py,sha256=diIBINJP_z4nnQIAC1b70vQSiHaNojEfUDARC2nrKHU,4681
|
|
4
|
-
rootly_mcp_server/routemap_server.py,sha256=0LfK2EzwkFQF9SpHNvGcca5ZaxkBC80gIdDojE0aUcs,6100
|
|
5
|
-
rootly_mcp_server/server.py,sha256=O31ByNNfKjBmmYY1y_ooPKL12b-PXaM5eNrekXzpURE,24346
|
|
6
|
-
rootly_mcp_server/test_client.py,sha256=Ytd5ZP7vImm12CT97k3p9tlkY_JNcXHSzcGGnHCBqv0,5275
|
|
7
|
-
rootly_mcp_server/utils.py,sha256=NyxdcDiFGlV2a8eBO4lKgZg0D7Gxr6xUIB0YyJGgpPA,4165
|
|
8
|
-
rootly_mcp_server/data/__init__.py,sha256=fO8a0bQnRVEoRMHKvhFzj10bhoaw7VsI51czc2MsUm4,143
|
|
9
|
-
rootly_mcp_server-2.0.10.dist-info/METADATA,sha256=SyvYvw80MbIJVhxXvAG_ai1F9FV9N3qPx9Ls4bj3mbg,6189
|
|
10
|
-
rootly_mcp_server-2.0.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
rootly_mcp_server-2.0.10.dist-info/entry_points.txt,sha256=NE33b8VgigVPGBkboyo6pvN1Vz35HZtLybxMO4Q03PI,70
|
|
12
|
-
rootly_mcp_server-2.0.10.dist-info/licenses/LICENSE,sha256=c9w9ZZGl14r54tsP40oaq5adTVX_HMNHozPIH2ymzmw,11341
|
|
13
|
-
rootly_mcp_server-2.0.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|