mcp-instana 0.1.0__py3-none-any.whl → 0.1.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.
- mcp_instana-0.1.1.dist-info/METADATA +908 -0
- mcp_instana-0.1.1.dist-info/RECORD +30 -0
- {mcp_instana-0.1.0.dist-info → mcp_instana-0.1.1.dist-info}/WHEEL +1 -1
- mcp_instana-0.1.1.dist-info/entry_points.txt +4 -0
- mcp_instana-0.1.0.dist-info/LICENSE → mcp_instana-0.1.1.dist-info/licenses/LICENSE.md +3 -3
- src/application/__init__.py +1 -0
- src/{client/application_alert_config_mcp_tools.py → application/application_alert_config.py} +251 -273
- src/application/application_analyze.py +415 -0
- src/application/application_catalog.py +153 -0
- src/{client/application_metrics_mcp_tools.py → application/application_metrics.py} +107 -129
- src/{client/application_resources_mcp_tools.py → application/application_resources.py} +128 -150
- src/application/application_settings.py +1135 -0
- src/application/application_topology.py +107 -0
- src/core/__init__.py +1 -0
- src/core/server.py +436 -0
- src/core/utils.py +213 -0
- src/event/__init__.py +1 -0
- src/{client/events_mcp_tools.py → event/events_tools.py} +128 -136
- src/infrastructure/__init__.py +1 -0
- src/{client/infrastructure_analyze_mcp_tools.py → infrastructure/infrastructure_analyze.py} +200 -203
- src/{client/infrastructure_catalog_mcp_tools.py → infrastructure/infrastructure_catalog.py} +194 -264
- src/infrastructure/infrastructure_metrics.py +167 -0
- src/{client/infrastructure_resources_mcp_tools.py → infrastructure/infrastructure_resources.py} +192 -223
- src/{client/infrastructure_topology_mcp_tools.py → infrastructure/infrastructure_topology.py} +105 -106
- src/log/__init__.py +1 -0
- src/log/log_alert_configuration.py +331 -0
- src/prompts/mcp_prompts.py +900 -0
- src/prompts/prompt_loader.py +29 -0
- src/prompts/prompt_registry.json +21 -0
- mcp_instana-0.1.0.dist-info/METADATA +0 -649
- mcp_instana-0.1.0.dist-info/RECORD +0 -19
- mcp_instana-0.1.0.dist-info/entry_points.txt +0 -3
- src/client/What is the sum of queue depth for all q +0 -55
- src/client/instana_client_base.py +0 -93
- src/client/log_alert_configuration_mcp_tools.py +0 -316
- src/client/show the top 5 services with the highest +0 -28
- src/mcp_server.py +0 -343
src/core/utils.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Instana API Client Module
|
|
3
|
+
|
|
4
|
+
This module provides the base client for interacting with the Instana API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from functools import wraps
|
|
9
|
+
from typing import Any, Callable, Dict, Union
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
# Registry to store all tools
|
|
14
|
+
MCP_TOOLS = {}
|
|
15
|
+
|
|
16
|
+
def register_as_tool(func):
|
|
17
|
+
"""Decorator to register a method as an MCP tool."""
|
|
18
|
+
MCP_TOOLS[func.__name__] = func
|
|
19
|
+
return func
|
|
20
|
+
|
|
21
|
+
def with_header_auth(api_class, allow_mock=False):
|
|
22
|
+
"""
|
|
23
|
+
Universal decorator for Instana MCP tools that provides flexible authentication.
|
|
24
|
+
|
|
25
|
+
This decorator automatically handles authentication for any Instana API tool method.
|
|
26
|
+
It supports both HTTP mode (using headers) and STDIO mode (using environment variables),
|
|
27
|
+
with strict mode separation to prevent cross-mode fallbacks.
|
|
28
|
+
|
|
29
|
+
Features:
|
|
30
|
+
- HTTP Mode: Extracts credentials from HTTP headers (fails if missing)
|
|
31
|
+
- STDIO Mode: Uses constructor-based authentication (fails if missing)
|
|
32
|
+
- Mock Mode: Allows injection of mock clients for testing (when allow_mock=True)
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
api_class: The Instana API class to instantiate (e.g., InfrastructureTopologyApi,
|
|
36
|
+
ApplicationMetricsApi, InfrastructureCatalogApi, etc.)
|
|
37
|
+
allow_mock: If True, allows mock clients to be passed directly (for testing)
|
|
38
|
+
|
|
39
|
+
Usage:
|
|
40
|
+
@with_header_auth(YourApiClass)
|
|
41
|
+
async def your_tool_method(self, param1, param2, ctx=None, api_client=None):
|
|
42
|
+
# The decorator automatically injects 'api_client' into the method
|
|
43
|
+
result = api_client.your_api_method(param1, param2)
|
|
44
|
+
return self._convert_to_dict(result)
|
|
45
|
+
|
|
46
|
+
Note: Always include 'api_client=None' in your method signature to receive the
|
|
47
|
+
injected API client from the decorator.
|
|
48
|
+
"""
|
|
49
|
+
def decorator(func: Callable) -> Callable:
|
|
50
|
+
@wraps(func)
|
|
51
|
+
async def wrapper(self, *args, **kwargs):
|
|
52
|
+
try:
|
|
53
|
+
# Check if a mock client is being passed (for testing)
|
|
54
|
+
if allow_mock and 'api_client' in kwargs and kwargs['api_client'] is not None:
|
|
55
|
+
print(" Using mock client for testing", file=sys.stderr)
|
|
56
|
+
# Call the original function with the mock client
|
|
57
|
+
return await func(self, *args, **kwargs)
|
|
58
|
+
|
|
59
|
+
# Try to get headers first to determine mode
|
|
60
|
+
try:
|
|
61
|
+
from fastmcp.server.dependencies import get_http_headers
|
|
62
|
+
headers = get_http_headers()
|
|
63
|
+
|
|
64
|
+
instana_token = headers.get("instana-api-token")
|
|
65
|
+
instana_base_url = headers.get("instana-base-url")
|
|
66
|
+
|
|
67
|
+
# Check if we're in HTTP mode (headers are present)
|
|
68
|
+
if instana_token or instana_base_url:
|
|
69
|
+
# HTTP mode detected - both headers must be present
|
|
70
|
+
if not instana_token or not instana_base_url:
|
|
71
|
+
missing = []
|
|
72
|
+
if not instana_token:
|
|
73
|
+
missing.append("instana-api-token")
|
|
74
|
+
if not instana_base_url:
|
|
75
|
+
missing.append("instana-base-url")
|
|
76
|
+
error_msg = f"HTTP mode detected but missing required headers: {', '.join(missing)}"
|
|
77
|
+
print(f" {error_msg}", file=sys.stderr)
|
|
78
|
+
return {"error": error_msg}
|
|
79
|
+
|
|
80
|
+
# Validate URL format
|
|
81
|
+
if not instana_base_url.startswith("http://") and not instana_base_url.startswith("https://"):
|
|
82
|
+
error_msg = "Instana base URL must start with http:// or https://"
|
|
83
|
+
print(f" {error_msg}", file=sys.stderr)
|
|
84
|
+
return {"error": error_msg}
|
|
85
|
+
|
|
86
|
+
print(" Using header-based authentication (HTTP mode)", file=sys.stderr)
|
|
87
|
+
print(" instana_base_url: ", instana_base_url)
|
|
88
|
+
|
|
89
|
+
# Import SDK components
|
|
90
|
+
from instana_client.api_client import ApiClient
|
|
91
|
+
from instana_client.configuration import Configuration
|
|
92
|
+
|
|
93
|
+
# Create API client from headers
|
|
94
|
+
configuration = Configuration()
|
|
95
|
+
configuration.host = instana_base_url
|
|
96
|
+
configuration.api_key['ApiKeyAuth'] = instana_token
|
|
97
|
+
configuration.api_key_prefix['ApiKeyAuth'] = 'apiToken'
|
|
98
|
+
configuration.default_headers = {"User-Agent": "MCP-server/0.1.0"}
|
|
99
|
+
|
|
100
|
+
api_client_instance = ApiClient(configuration=configuration)
|
|
101
|
+
api_instance = api_class(api_client=api_client_instance)
|
|
102
|
+
|
|
103
|
+
# Add the API instance to kwargs so the decorated function can use it
|
|
104
|
+
kwargs['api_client'] = api_instance
|
|
105
|
+
|
|
106
|
+
# Call the original function
|
|
107
|
+
return await func(self, *args, **kwargs)
|
|
108
|
+
|
|
109
|
+
except (ImportError, AttributeError) as e:
|
|
110
|
+
print(f"Header detection failed, using STDIO mode: {e}", file=sys.stderr)
|
|
111
|
+
|
|
112
|
+
# STDIO mode - use constructor-based authentication
|
|
113
|
+
print(" Using constructor-based authentication (STDIO mode)", file=sys.stderr)
|
|
114
|
+
print(f" self.base_url: {self.base_url}", file=sys.stderr)
|
|
115
|
+
|
|
116
|
+
# Validate constructor credentials before proceeding
|
|
117
|
+
if not self.read_token or not self.base_url:
|
|
118
|
+
error_msg = "Authentication failed: Missing credentials "
|
|
119
|
+
if not self.read_token:
|
|
120
|
+
error_msg += " - INSTANA_API_TOKEN is missing"
|
|
121
|
+
if not self.base_url:
|
|
122
|
+
error_msg += " - INSTANA_BASE_URL is missing"
|
|
123
|
+
print(f" {error_msg}", file=sys.stderr)
|
|
124
|
+
return {"error": error_msg}
|
|
125
|
+
|
|
126
|
+
# Check if the class has the expected API attribute
|
|
127
|
+
api_attr_name = None
|
|
128
|
+
for attr_name in dir(self):
|
|
129
|
+
if attr_name.endswith('_api'):
|
|
130
|
+
attr = getattr(self, attr_name)
|
|
131
|
+
if hasattr(attr, '__class__') and attr.__class__.__name__ == api_class.__name__:
|
|
132
|
+
api_attr_name = attr_name
|
|
133
|
+
print(f"🔐 Found existing API client: {attr_name}", file=sys.stderr)
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
if api_attr_name:
|
|
137
|
+
# Use the existing API client from constructor
|
|
138
|
+
api_instance = getattr(self, api_attr_name)
|
|
139
|
+
kwargs['api_client'] = api_instance
|
|
140
|
+
return await func(self, *args, **kwargs)
|
|
141
|
+
else:
|
|
142
|
+
# Create a new API client using constructor credentials
|
|
143
|
+
print(" Creating new API client with constructor credentials", file=sys.stderr)
|
|
144
|
+
from instana_client.api_client import ApiClient
|
|
145
|
+
from instana_client.configuration import Configuration
|
|
146
|
+
|
|
147
|
+
configuration = Configuration()
|
|
148
|
+
configuration.host = self.base_url
|
|
149
|
+
configuration.api_key['ApiKeyAuth'] = self.read_token
|
|
150
|
+
configuration.api_key_prefix['ApiKeyAuth'] = 'apiToken'
|
|
151
|
+
configuration.default_headers = {"User-Agent": "MCP-server/0.1.0"}
|
|
152
|
+
|
|
153
|
+
api_client_instance = ApiClient(configuration=configuration)
|
|
154
|
+
api_instance = api_class(api_client=api_client_instance)
|
|
155
|
+
|
|
156
|
+
kwargs['api_client'] = api_instance
|
|
157
|
+
return await func(self, *args, **kwargs)
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
print(f"Error in header auth decorator: {e}", file=sys.stderr)
|
|
161
|
+
import traceback
|
|
162
|
+
traceback.print_exc(file=sys.stderr)
|
|
163
|
+
return {"error": f"Authentication error: {e!s}"}
|
|
164
|
+
|
|
165
|
+
return wrapper
|
|
166
|
+
return decorator
|
|
167
|
+
|
|
168
|
+
class BaseInstanaClient:
|
|
169
|
+
"""Base client for Instana API with common functionality."""
|
|
170
|
+
|
|
171
|
+
def __init__(self, read_token: str, base_url: str):
|
|
172
|
+
self.read_token = read_token
|
|
173
|
+
self.base_url = base_url
|
|
174
|
+
|
|
175
|
+
def get_headers(self):
|
|
176
|
+
"""Get standard headers for Instana API requests."""
|
|
177
|
+
return {
|
|
178
|
+
"Authorization": f"apiToken {self.read_token}",
|
|
179
|
+
"Content-Type": "application/json",
|
|
180
|
+
"Accept": "application/json"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async def make_request(self, endpoint: str, params: Union[Dict[str, Any], None] = None, method: str = "GET", json: Union[Dict[str, Any], None] = None) -> Dict[str, Any]:
|
|
184
|
+
"""Make a request to the Instana API."""
|
|
185
|
+
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
186
|
+
headers = self.get_headers()
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
if method.upper() == "GET":
|
|
190
|
+
response = requests.get(url, headers=headers, params=params, verify=False)
|
|
191
|
+
elif method.upper() == "POST":
|
|
192
|
+
# Use the json parameter if provided, otherwise use params
|
|
193
|
+
data_to_send = json if json is not None else params
|
|
194
|
+
response = requests.post(url, headers=headers, json=data_to_send, verify=False)
|
|
195
|
+
elif method.upper() == "PUT":
|
|
196
|
+
data_to_send = json if json is not None else params
|
|
197
|
+
response = requests.put(url, headers=headers, json=data_to_send, verify=False)
|
|
198
|
+
elif method.upper() == "DELETE":
|
|
199
|
+
response = requests.delete(url, headers=headers, params=params, verify=False)
|
|
200
|
+
else:
|
|
201
|
+
return {"error": f"Unsupported HTTP method: {method}"}
|
|
202
|
+
|
|
203
|
+
response.raise_for_status()
|
|
204
|
+
return response.json()
|
|
205
|
+
except requests.exceptions.HTTPError as err:
|
|
206
|
+
print(f"HTTP Error: {err}", file=sys.stderr)
|
|
207
|
+
return {"error": f"HTTP Error: {err}"}
|
|
208
|
+
except requests.exceptions.RequestException as err:
|
|
209
|
+
print(f"Error: {err}", file=sys.stderr)
|
|
210
|
+
return {"error": f"Error: {err}"}
|
|
211
|
+
except Exception as e:
|
|
212
|
+
print(f"Unexpected error: {e!s}", file=sys.stderr)
|
|
213
|
+
return {"error": f"Unexpected error: {e!s}"}
|
src/event/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Event module for MCP Instana
|