iflow-mcp_drdroidlab-grafana-mcp-server 0.1.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.
File without changes
@@ -0,0 +1,19 @@
1
+ # Environment variables take precedence over this file
2
+
3
+ grafana:
4
+ # Grafana instance URL (required)
5
+ host: <host>
6
+
7
+ api_key: <api_key>
8
+ ssl_verify: <ssl_verify>
9
+
10
+ server:
11
+ # Port to run the MCP server on
12
+ port: 8000
13
+
14
+ # Debug mode
15
+ debug: true
16
+
17
+ #this is for testing purposes only
18
+ openai:
19
+ api_key: "your openai api key"
@@ -0,0 +1,643 @@
1
+ import datetime
2
+ import json
3
+ import logging
4
+ import os
5
+ import sys
6
+
7
+ import yaml
8
+ from flask import Flask, current_app, jsonify, request
9
+
10
+ try:
11
+ from src.grafana_mcp_server.processor.grafana_processor import GrafanaApiProcessor
12
+ from src.grafana_mcp_server.stdio_server import run_stdio_server
13
+ except ImportError:
14
+ # Fallback for when running the file directly
15
+ from processor.grafana_processor import GrafanaApiProcessor
16
+ from stdio_server import run_stdio_server
17
+
18
+ app = Flask(__name__)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Configure logging
22
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
23
+
24
+
25
+ # Load configuration from environment variables, then YAML as fallback
26
+ def load_config():
27
+ # Try multiple possible config file locations
28
+ possible_config_paths = [
29
+ os.path.join(os.path.dirname(__file__), "config.yaml"), # Same directory as this file
30
+ "/app/config.yaml", # Docker container path
31
+ "config.yaml", # Current working directory
32
+ ]
33
+
34
+ config = {}
35
+ config_loaded = False
36
+
37
+ for config_path in possible_config_paths:
38
+ try:
39
+ with open(config_path) as file:
40
+ config = yaml.safe_load(file)
41
+ logger.info(f"Successfully loaded config from: {config_path}")
42
+ config_loaded = True
43
+ break
44
+ except FileNotFoundError:
45
+ logger.debug(f"Config file not found at: {config_path}")
46
+ continue
47
+ except yaml.YAMLError as e:
48
+ logger.error(f"Error parsing YAML configuration from {config_path}: {e}")
49
+ continue
50
+
51
+ if not config_loaded:
52
+ logger.warning("No config file found, using environment variables only")
53
+ config = {}
54
+
55
+ # Environment variable overrides (preferred method)
56
+ grafana_host = os.environ.get("GRAFANA_HOST") or (config.get("grafana", {}).get("host") if config.get("grafana") else None)
57
+ grafana_api_key = os.environ.get("GRAFANA_API_KEY") or (config.get("grafana", {}).get("api_key") if config.get("grafana") else None)
58
+ grafana_ssl_verify = os.environ.get("GRAFANA_SSL_VERIFY") or (
59
+ config.get("grafana", {}).get("ssl_verify", "true") if config.get("grafana") else "true"
60
+ )
61
+
62
+ server_port = int(os.environ.get("MCP_SERVER_PORT") or (config.get("server", {}).get("port", 8000) if config.get("server") else 8000))
63
+ server_debug = os.environ.get("MCP_SERVER_DEBUG")
64
+ if server_debug is not None:
65
+ server_debug = server_debug.lower() in ["1", "true", "yes"]
66
+ else:
67
+ server_debug = config.get("server", {}).get("debug", True) if config.get("server") else True
68
+
69
+ logger.info(f"Loaded configuration - Host: {grafana_host}, API Key: {'***' if grafana_api_key else 'None'}, Port: {server_port}")
70
+
71
+ return {
72
+ "grafana": {
73
+ "host": grafana_host,
74
+ "api_key": grafana_api_key,
75
+ "ssl_verify": grafana_ssl_verify,
76
+ },
77
+ "server": {"port": server_port, "debug": server_debug},
78
+ }
79
+
80
+
81
+ # Initialize configuration and processor at app startup
82
+ with app.app_context():
83
+ config = load_config()
84
+ app.config["GRAFANA_CONFIG"] = config.get("grafana", {})
85
+ app.config["SERVER_CONFIG"] = config.get("server", {})
86
+
87
+ # Initialize Grafana processor
88
+ try:
89
+ app.config["grafana_processor"] = GrafanaApiProcessor(
90
+ grafana_host=app.config["GRAFANA_CONFIG"].get("host"),
91
+ grafana_api_key=app.config["GRAFANA_CONFIG"].get("api_key"),
92
+ ssl_verify=app.config["GRAFANA_CONFIG"].get("ssl_verify", "true"),
93
+ )
94
+ logger.info("Grafana processor initialized successfully")
95
+ except Exception as e:
96
+ logger.error(f"Failed to initialize Grafana processor: {e}")
97
+ app.config["grafana_processor"] = None
98
+
99
+ # Server info
100
+ SERVER_INFO = {"name": "grafana-mcp-server", "version": "1.0.0"}
101
+
102
+ # Server capabilities
103
+ SERVER_CAPABILITIES = {"tools": {}}
104
+
105
+ # Protocol version
106
+ PROTOCOL_VERSION = "2025-06-18"
107
+
108
+
109
+ def get_current_time_iso():
110
+ """Get current time in ISO format"""
111
+ return datetime.datetime.now(datetime.timezone.utc).isoformat()
112
+
113
+
114
+ # Available tools - Grafana MCP Server Tools
115
+ TOOLS_LIST = [
116
+ {
117
+ "name": "test_connection",
118
+ "description": "Test connection to Grafana API to verify configuration and connectivity. Requires API key or open Grafana instance.",
119
+ "inputSchema": {"type": "object", "properties": {}, "required": []},
120
+ },
121
+ {
122
+ "name": "grafana_promql_query",
123
+ "description": "Executes PromQL queries against Grafana's Prometheus datasource. "
124
+ "Fetches metrics data using PromQL expressions, optimizes time series responses to reduce token size.",
125
+ "inputSchema": {
126
+ "type": "object",
127
+ "properties": {
128
+ "datasource_uid": {
129
+ "type": "string",
130
+ "description": "Prometheus datasource UID",
131
+ },
132
+ "query": {"type": "string", "description": "PromQL query string"},
133
+ "start_time": {
134
+ "type": "string",
135
+ "description": "Start time in RFC3339 or relative string (e.g., 'now-2h', '2023-01-01T00:00:00Z')",
136
+ },
137
+ "end_time": {
138
+ "type": "string",
139
+ "description": "End time in RFC3339 or relative string (e.g., 'now-2h', '2023-01-01T00:00:00Z')",
140
+ },
141
+ "duration": {
142
+ "type": "string",
143
+ "description": "Duration string for the time window (e.g., '2h', '90m')",
144
+ },
145
+ },
146
+ "required": ["datasource_uid", "query"],
147
+ },
148
+ },
149
+ {
150
+ "name": "grafana_loki_query",
151
+ "description": "Queries Grafana Loki for log data. Fetches logs for a specified duration "
152
+ "(e.g., '5m', '1h', '2d'), converts relative time to absolute timestamps. "
153
+ "Note: Loki queries require at least one non-empty matcher. Use patterns like '{job=~\".+\"}' "
154
+ "instead of '{job=~\".*\"}' or '{}' to avoid syntax errors.",
155
+ "inputSchema": {
156
+ "type": "object",
157
+ "properties": {
158
+ "datasource_uid": {
159
+ "type": "string",
160
+ "description": "Loki datasource UID",
161
+ },
162
+ "query": {"type": "string", "description": "Loki query string (e.g., '{job=~\".+\"}' or '{app=\"myapp\"}')"},
163
+ "duration": {
164
+ "type": "string",
165
+ "description": "Time duration (e.g., '5m', '1h', '2d') - overrides start_time/end_time if provided",
166
+ },
167
+ "start_time": {
168
+ "type": "string",
169
+ "description": "Start time in RFC3339 or relative string (e.g., 'now-2h', '2023-01-01T00:00:00Z')",
170
+ },
171
+ "end_time": {
172
+ "type": "string",
173
+ "description": "End time in RFC3339 or relative string (e.g., 'now-2h', '2023-01-01T00:00:00Z')",
174
+ },
175
+ "limit": {
176
+ "type": "integer",
177
+ "description": "Maximum number of log entries to return",
178
+ "default": 100,
179
+ },
180
+ },
181
+ "required": ["datasource_uid", "query"],
182
+ },
183
+ },
184
+ {
185
+ "name": "grafana_get_dashboard_config",
186
+ "description": "Retrieves dashboard configuration details from the database. "
187
+ "Queries the connectors_connectormetadatamodelstore table for dashboard metadata.",
188
+ "inputSchema": {
189
+ "type": "object",
190
+ "properties": {"dashboard_uid": {"type": "string", "description": "Dashboard UID"}},
191
+ "required": ["dashboard_uid"],
192
+ },
193
+ },
194
+ {
195
+ "name": "grafana_query_dashboard_panels",
196
+ "description": "Executes queries for specific dashboard panels. Can query up to 4 panels at once, "
197
+ "supports template variables, optimizes metrics data.",
198
+ "inputSchema": {
199
+ "type": "object",
200
+ "properties": {
201
+ "dashboard_uid": {"type": "string", "description": "Dashboard UID"},
202
+ "panel_ids": {
203
+ "type": "array",
204
+ "items": {"type": "integer"},
205
+ "description": "List of panel IDs to query (max 4)",
206
+ },
207
+ "template_variables": {
208
+ "type": "object",
209
+ "description": "Template variables for the dashboard",
210
+ },
211
+ },
212
+ "required": ["dashboard_uid", "panel_ids"],
213
+ },
214
+ },
215
+ {
216
+ "name": "grafana_fetch_label_values",
217
+ "description": "Fetches label values for dashboard variables from Prometheus datasource. "
218
+ "Retrieves available values for specific labels (e.g., 'instance', 'job').",
219
+ "inputSchema": {
220
+ "type": "object",
221
+ "properties": {
222
+ "datasource_uid": {
223
+ "type": "string",
224
+ "description": "Prometheus datasource UID",
225
+ },
226
+ "label_name": {
227
+ "type": "string",
228
+ "description": "Label name to fetch values for (e.g., 'instance', 'job')",
229
+ },
230
+ "metric_match_filter": {
231
+ "type": "string",
232
+ "description": "Optional metric name filter (e.g., 'up', 'node_cpu_seconds_total')",
233
+ },
234
+ },
235
+ "required": ["datasource_uid", "label_name"],
236
+ },
237
+ },
238
+ {
239
+ "name": "grafana_fetch_dashboard_variables",
240
+ "description": "Fetches all variables and their values from a Grafana dashboard. "
241
+ "Retrieves dashboard template variables and their current values.",
242
+ "inputSchema": {
243
+ "type": "object",
244
+ "properties": {"dashboard_uid": {"type": "string", "description": "Dashboard UID"}},
245
+ "required": ["dashboard_uid"],
246
+ },
247
+ },
248
+ {
249
+ "name": "grafana_fetch_all_dashboards",
250
+ "description": "Fetches all dashboards from Grafana with basic information like title, UID, folder, tags, etc.",
251
+ "inputSchema": {
252
+ "type": "object",
253
+ "properties": {
254
+ "limit": {
255
+ "type": "integer",
256
+ "description": "Maximum number of dashboards to return",
257
+ "default": 100,
258
+ }
259
+ },
260
+ "required": [],
261
+ },
262
+ },
263
+ {
264
+ "name": "grafana_fetch_datasources",
265
+ "description": "Fetches all datasources from Grafana with their configuration details.",
266
+ "inputSchema": {"type": "object", "properties": {}, "required": []},
267
+ },
268
+ {
269
+ "name": "grafana_fetch_folders",
270
+ "description": "Fetches all folders from Grafana with their metadata and permissions.",
271
+ "inputSchema": {"type": "object", "properties": {}, "required": []},
272
+ },
273
+ ]
274
+
275
+
276
+ # Tool implementations
277
+ def test_grafana_connection():
278
+ """Test connection to Grafana API"""
279
+ try:
280
+ grafana_processor = current_app.config.get("grafana_processor")
281
+ if not grafana_processor:
282
+ return {
283
+ "status": "error",
284
+ "message": "Grafana processor not initialized. Check configuration.",
285
+ }
286
+
287
+ result = grafana_processor.test_connection()
288
+
289
+ if result:
290
+ connection_details = grafana_processor.get_connection()
291
+ return {
292
+ "status": "success",
293
+ "message": "Successfully connected to Grafana API",
294
+ "auth_method": connection_details.get("auth_method"),
295
+ "host": connection_details.get("host"),
296
+ }
297
+ else:
298
+ return {"status": "failed", "message": "Failed to connect to Grafana API"}
299
+ except Exception as e:
300
+ logger.error(f"Error testing Grafana connection: {e!s}")
301
+ return {"status": "error", "message": f"Failed to connect to Grafana: {e!s}"}
302
+
303
+
304
+ def grafana_promql_query(datasource_uid, query, start_time=None, end_time=None, duration=None):
305
+ """Execute PromQL query against Grafana's Prometheus datasource"""
306
+ try:
307
+ grafana_processor = current_app.config.get("grafana_processor")
308
+ if not grafana_processor:
309
+ return {
310
+ "status": "error",
311
+ "message": "Grafana processor not initialized. Check configuration.",
312
+ }
313
+
314
+ result = grafana_processor.grafana_promql_query(datasource_uid, query, start_time, end_time, duration)
315
+ return result
316
+ except Exception as e:
317
+ logger.error(f"Error executing PromQL query: {e!s}")
318
+ return {"status": "error", "message": f"PromQL query failed: {e!s}"}
319
+
320
+
321
+ def grafana_loki_query(datasource_uid, query, duration=None, start_time=None, end_time=None, limit=100):
322
+ """Query Grafana Loki for log data"""
323
+ try:
324
+ grafana_processor = current_app.config.get("grafana_processor")
325
+ if not grafana_processor:
326
+ return {
327
+ "status": "error",
328
+ "message": "Grafana processor not initialized. Check configuration.",
329
+ }
330
+
331
+ result = grafana_processor.grafana_loki_query(datasource_uid, query, duration, start_time, end_time, limit)
332
+ return result
333
+ except Exception as e:
334
+ logger.error(f"Error executing Loki query: {e!s}")
335
+ return {"status": "error", "message": f"Loki query failed: {e!s}"}
336
+
337
+
338
+ def grafana_get_dashboard_config(dashboard_uid):
339
+ """Get dashboard configuration details"""
340
+ try:
341
+ grafana_processor = current_app.config.get("grafana_processor")
342
+ if not grafana_processor:
343
+ return {
344
+ "status": "error",
345
+ "message": "Grafana processor not initialized. Check configuration.",
346
+ }
347
+
348
+ result = grafana_processor.grafana_get_dashboard_config_details(dashboard_uid)
349
+ return result
350
+ except Exception as e:
351
+ logger.error(f"Error fetching dashboard config: {e!s}")
352
+ return {
353
+ "status": "error",
354
+ "message": f"Failed to fetch dashboard config: {e!s}",
355
+ }
356
+
357
+
358
+ def grafana_query_dashboard_panels(dashboard_uid, panel_ids, template_variables=None):
359
+ """Execute queries for specific dashboard panels"""
360
+ try:
361
+ grafana_processor = current_app.config.get("grafana_processor")
362
+ if not grafana_processor:
363
+ return {
364
+ "status": "error",
365
+ "message": "Grafana processor not initialized. Check configuration.",
366
+ }
367
+
368
+ result = grafana_processor.grafana_query_dashboard_panels(dashboard_uid, panel_ids, template_variables)
369
+ return result
370
+ except Exception as e:
371
+ logger.error(f"Error querying dashboard panels: {e!s}")
372
+ return {
373
+ "status": "error",
374
+ "message": f"Failed to query dashboard panels: {e!s}",
375
+ }
376
+
377
+
378
+ def grafana_fetch_label_values(datasource_uid, label_name, metric_match_filter=None):
379
+ """Fetch label values for dashboard variables from Prometheus datasource"""
380
+ try:
381
+ grafana_processor = current_app.config.get("grafana_processor")
382
+ if not grafana_processor:
383
+ return {
384
+ "status": "error",
385
+ "message": "Grafana processor not initialized. Check configuration.",
386
+ }
387
+
388
+ result = grafana_processor.grafana_fetch_dashboard_variable_label_values(datasource_uid, label_name, metric_match_filter)
389
+ return result
390
+ except Exception as e:
391
+ logger.error(f"Error fetching label values: {e!s}")
392
+ return {"status": "error", "message": f"Failed to fetch label values: {e!s}"}
393
+
394
+
395
+ def grafana_fetch_dashboard_variables(dashboard_uid):
396
+ """Fetch all variables and their values from a Grafana dashboard"""
397
+ try:
398
+ grafana_processor = current_app.config.get("grafana_processor")
399
+ if not grafana_processor:
400
+ return {
401
+ "status": "error",
402
+ "message": "Grafana processor not initialized. Check configuration.",
403
+ }
404
+
405
+ result = grafana_processor.grafana_fetch_dashboard_variables(dashboard_uid)
406
+ return result
407
+ except Exception as e:
408
+ logger.error(f"Error fetching dashboard variables: {e!s}")
409
+ return {
410
+ "status": "error",
411
+ "message": f"Failed to fetch dashboard variables: {e!s}",
412
+ }
413
+
414
+
415
+ def grafana_fetch_all_dashboards(limit=100):
416
+ """Fetch all dashboards from Grafana"""
417
+ try:
418
+ grafana_processor = current_app.config.get("grafana_processor")
419
+ if not grafana_processor:
420
+ return {
421
+ "status": "error",
422
+ "message": "Grafana processor not initialized. Check configuration.",
423
+ }
424
+
425
+ result = grafana_processor.grafana_fetch_all_dashboards(limit)
426
+ return result
427
+ except Exception as e:
428
+ logger.error(f"Error fetching dashboards: {e!s}")
429
+ return {"status": "error", "message": f"Failed to fetch dashboards: {e!s}"}
430
+
431
+
432
+ def grafana_fetch_datasources():
433
+ """Fetch all datasources from Grafana"""
434
+ try:
435
+ grafana_processor = current_app.config.get("grafana_processor")
436
+ if not grafana_processor:
437
+ return {
438
+ "status": "error",
439
+ "message": "Grafana processor not initialized. Check configuration.",
440
+ }
441
+
442
+ result = grafana_processor.grafana_fetch_datasources()
443
+ return result
444
+ except Exception as e:
445
+ logger.error(f"Error fetching datasources: {e!s}")
446
+ return {"status": "error", "message": f"Failed to fetch datasources: {e!s}"}
447
+
448
+
449
+ def grafana_fetch_folders():
450
+ """Fetch all folders from Grafana"""
451
+ try:
452
+ grafana_processor = current_app.config.get("grafana_processor")
453
+ if not grafana_processor:
454
+ return {
455
+ "status": "error",
456
+ "message": "Grafana processor not initialized. Check configuration.",
457
+ }
458
+
459
+ result = grafana_processor.grafana_fetch_folders()
460
+ return result
461
+ except Exception as e:
462
+ logger.error(f"Error fetching folders: {e!s}")
463
+ return {"status": "error", "message": f"Failed to fetch folders: {e!s}"}
464
+
465
+
466
+ # Function mapping
467
+ FUNCTION_MAPPING = {
468
+ "test_connection": test_grafana_connection,
469
+ "grafana_promql_query": grafana_promql_query,
470
+ "grafana_loki_query": grafana_loki_query,
471
+ "grafana_get_dashboard_config": grafana_get_dashboard_config,
472
+ "grafana_query_dashboard_panels": grafana_query_dashboard_panels,
473
+ "grafana_fetch_label_values": grafana_fetch_label_values,
474
+ "grafana_fetch_dashboard_variables": grafana_fetch_dashboard_variables,
475
+ "grafana_fetch_all_dashboards": grafana_fetch_all_dashboards,
476
+ "grafana_fetch_datasources": grafana_fetch_datasources,
477
+ "grafana_fetch_folders": grafana_fetch_folders,
478
+ }
479
+
480
+
481
+ def handle_jsonrpc_request(data):
482
+ """Handle JSON-RPC 2.0 requests"""
483
+ request_id = data.get("id")
484
+ method = data.get("method")
485
+ params = data.get("params", {})
486
+
487
+ logger.info(f"Handling JSON-RPC request: {method}")
488
+
489
+ # Handle JSON-RPC notifications (no id field or method starts with 'notifications/')
490
+ if method and method.startswith("notifications/"):
491
+ logger.info(f"Received notification: {method}")
492
+ return {"jsonrpc": "2.0", "result": {}, "id": request_id}
493
+
494
+ # Handle initialization
495
+ if method == "initialize":
496
+ client_protocol_version = params.get("protocolVersion")
497
+ # Accept any protocol version that starts with '2025-'
498
+ if not (isinstance(client_protocol_version, str) and client_protocol_version.startswith("2024-") or client_protocol_version.startswith("2025-")):
499
+ return {
500
+ "jsonrpc": "2.0",
501
+ "error": {
502
+ "code": -32602,
503
+ "message": f"Unsupported protocol version: {client_protocol_version}",
504
+ },
505
+ "id": request_id,
506
+ }
507
+
508
+ return {
509
+ "jsonrpc": "2.0",
510
+ "result": {
511
+ "protocolVersion": PROTOCOL_VERSION,
512
+ "capabilities": SERVER_CAPABILITIES,
513
+ "serverInfo": SERVER_INFO,
514
+ },
515
+ "id": request_id,
516
+ }
517
+
518
+ # Handle tools/list
519
+ elif method == "tools/list":
520
+ return {
521
+ "jsonrpc": "2.0",
522
+ "result": {"tools": TOOLS_LIST},
523
+ "id": request_id,
524
+ }
525
+
526
+ # Handle tools/call
527
+ elif method == "tools/call":
528
+ tool_name = params.get("name")
529
+ arguments = params.get("arguments", {})
530
+
531
+ if tool_name not in FUNCTION_MAPPING:
532
+ return {
533
+ "jsonrpc": "2.0",
534
+ "error": {"code": -32601, "message": f"Unknown tool: {tool_name}"},
535
+ "id": request_id,
536
+ }
537
+
538
+ try:
539
+ # Execute the tool function
540
+ result = FUNCTION_MAPPING[tool_name](**arguments)
541
+
542
+ return {
543
+ "jsonrpc": "2.0",
544
+ "result": {
545
+ "content": [{"type": "text", "text": json.dumps(result, indent=2)}],
546
+ "isError": False,
547
+ },
548
+ "id": request_id,
549
+ }
550
+ except Exception as e:
551
+ logger.error(f"Error executing tool {tool_name}: {e!s}")
552
+ return {
553
+ "jsonrpc": "2.0",
554
+ "error": {
555
+ "code": -32603,
556
+ "message": f"Tool execution failed: {e!s}",
557
+ },
558
+ "id": request_id,
559
+ }
560
+
561
+ # Unknown method
562
+ else:
563
+ return {
564
+ "jsonrpc": "2.0",
565
+ "error": {"code": -32601, "message": f"Method not found: {method}"},
566
+ "id": request_id,
567
+ }
568
+
569
+
570
+ @app.route("/mcp", methods=["POST"])
571
+ def mcp_endpoint():
572
+ """Main MCP endpoint for JSON-RPC requests"""
573
+ if request.method != "POST":
574
+ return jsonify({"error": "Only POST method is supported. Use POST with application/json."}), 405
575
+
576
+ data = request.get_json()
577
+ logger.info(f"Received MCP request: {data}")
578
+
579
+ if not data:
580
+ return jsonify(
581
+ {
582
+ "jsonrpc": "2.0",
583
+ "error": {"code": -32700, "message": "Parse error"},
584
+ "id": None,
585
+ }
586
+ ), 400
587
+
588
+ response = handle_jsonrpc_request(data)
589
+ status_code = 200
590
+
591
+ if "error" in response:
592
+ # Map error codes to HTTP status codes
593
+ code = response["error"].get("code", -32000)
594
+ if code == -32700 or code == -32600 or code == -32602:
595
+ status_code = 400
596
+ elif code == -32601:
597
+ status_code = 404
598
+ else:
599
+ status_code = 500
600
+
601
+ return jsonify(response), status_code
602
+
603
+
604
+ @app.route("/health", methods=["GET"])
605
+ def health_check():
606
+ """Health check endpoint"""
607
+ return jsonify({"status": "ok", "timestamp": get_current_time_iso()})
608
+
609
+
610
+ @app.route("/", methods=["GET"])
611
+ def root():
612
+ """Root endpoint with server info"""
613
+ return jsonify(
614
+ {
615
+ "name": "Grafana MCP Server",
616
+ "version": "1.0.0",
617
+ "status": "running",
618
+ "endpoints": {"mcp": "/mcp", "health": "/health"},
619
+ }
620
+ )
621
+
622
+
623
+ def main():
624
+ """Main entry point"""
625
+ transport = os.environ.get("MCP_TRANSPORT", "http")
626
+
627
+ if ("-t" in sys.argv and "stdio" in sys.argv) or ("--transport" in sys.argv and "stdio" in sys.argv) or (transport == "stdio"):
628
+
629
+ def stdio_handler(data):
630
+ with app.app_context():
631
+ return handle_jsonrpc_request(data)
632
+
633
+ run_stdio_server(stdio_handler)
634
+ else:
635
+ # HTTP mode
636
+ port = app.config["SERVER_CONFIG"].get("port", 8000)
637
+ debug = app.config["SERVER_CONFIG"].get("debug", True)
638
+ logger.info(f"Starting Grafana MCP Server on port {port}")
639
+ app.run(host="0.0.0.0", port=port, debug=debug)
640
+
641
+
642
+ if __name__ == "__main__":
643
+ main()
File without changes