mcp-instana 0.1.0__py3-none-any.whl → 0.2.0__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.
Files changed (67) hide show
  1. mcp_instana-0.2.0.dist-info/METADATA +1229 -0
  2. mcp_instana-0.2.0.dist-info/RECORD +59 -0
  3. {mcp_instana-0.1.0.dist-info → mcp_instana-0.2.0.dist-info}/WHEEL +1 -1
  4. mcp_instana-0.2.0.dist-info/entry_points.txt +4 -0
  5. mcp_instana-0.1.0.dist-info/LICENSE → mcp_instana-0.2.0.dist-info/licenses/LICENSE.md +3 -3
  6. src/application/__init__.py +1 -0
  7. src/{client/application_alert_config_mcp_tools.py → application/application_alert_config.py} +251 -273
  8. src/application/application_analyze.py +628 -0
  9. src/application/application_catalog.py +155 -0
  10. src/application/application_global_alert_config.py +653 -0
  11. src/{client/application_metrics_mcp_tools.py → application/application_metrics.py} +113 -131
  12. src/{client/application_resources_mcp_tools.py → application/application_resources.py} +131 -151
  13. src/application/application_settings.py +1731 -0
  14. src/application/application_topology.py +111 -0
  15. src/automation/action_catalog.py +416 -0
  16. src/automation/action_history.py +338 -0
  17. src/core/__init__.py +1 -0
  18. src/core/server.py +586 -0
  19. src/core/utils.py +213 -0
  20. src/event/__init__.py +1 -0
  21. src/event/events_tools.py +850 -0
  22. src/infrastructure/__init__.py +1 -0
  23. src/{client/infrastructure_analyze_mcp_tools.py → infrastructure/infrastructure_analyze.py} +207 -206
  24. src/{client/infrastructure_catalog_mcp_tools.py → infrastructure/infrastructure_catalog.py} +197 -265
  25. src/infrastructure/infrastructure_metrics.py +171 -0
  26. src/{client/infrastructure_resources_mcp_tools.py → infrastructure/infrastructure_resources.py} +198 -227
  27. src/{client/infrastructure_topology_mcp_tools.py → infrastructure/infrastructure_topology.py} +110 -109
  28. src/log/__init__.py +1 -0
  29. src/log/log_alert_configuration.py +331 -0
  30. src/prompts/__init__.py +16 -0
  31. src/prompts/application/__init__.py +1 -0
  32. src/prompts/application/application_alerts.py +54 -0
  33. src/prompts/application/application_catalog.py +26 -0
  34. src/prompts/application/application_metrics.py +57 -0
  35. src/prompts/application/application_resources.py +26 -0
  36. src/prompts/application/application_settings.py +75 -0
  37. src/prompts/application/application_topology.py +30 -0
  38. src/prompts/events/__init__.py +1 -0
  39. src/prompts/events/events_tools.py +161 -0
  40. src/prompts/infrastructure/infrastructure_analyze.py +72 -0
  41. src/prompts/infrastructure/infrastructure_catalog.py +53 -0
  42. src/prompts/infrastructure/infrastructure_metrics.py +45 -0
  43. src/prompts/infrastructure/infrastructure_resources.py +74 -0
  44. src/prompts/infrastructure/infrastructure_topology.py +38 -0
  45. src/prompts/settings/__init__.py +0 -0
  46. src/prompts/settings/custom_dashboard.py +157 -0
  47. src/prompts/website/__init__.py +1 -0
  48. src/prompts/website/website_analyze.py +35 -0
  49. src/prompts/website/website_catalog.py +40 -0
  50. src/prompts/website/website_configuration.py +105 -0
  51. src/prompts/website/website_metrics.py +34 -0
  52. src/settings/__init__.py +1 -0
  53. src/settings/custom_dashboard_tools.py +417 -0
  54. src/website/__init__.py +0 -0
  55. src/website/website_analyze.py +433 -0
  56. src/website/website_catalog.py +171 -0
  57. src/website/website_configuration.py +770 -0
  58. src/website/website_metrics.py +241 -0
  59. mcp_instana-0.1.0.dist-info/METADATA +0 -649
  60. mcp_instana-0.1.0.dist-info/RECORD +0 -19
  61. mcp_instana-0.1.0.dist-info/entry_points.txt +0 -3
  62. src/client/What is the sum of queue depth for all q +0 -55
  63. src/client/events_mcp_tools.py +0 -531
  64. src/client/instana_client_base.py +0 -93
  65. src/client/log_alert_configuration_mcp_tools.py +0 -316
  66. src/client/show the top 5 services with the highest +0 -28
  67. 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=True):
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). Defaults to True.
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