kubectl-mcp-server 1.14.0__py3-none-any.whl → 1.16.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 (37) hide show
  1. kubectl_mcp_server-1.16.0.dist-info/METADATA +1047 -0
  2. kubectl_mcp_server-1.16.0.dist-info/RECORD +61 -0
  3. kubectl_mcp_tool/__init__.py +1 -1
  4. kubectl_mcp_tool/crd_detector.py +247 -0
  5. kubectl_mcp_tool/k8s_config.py +304 -63
  6. kubectl_mcp_tool/mcp_server.py +27 -0
  7. kubectl_mcp_tool/tools/__init__.py +20 -0
  8. kubectl_mcp_tool/tools/backup.py +881 -0
  9. kubectl_mcp_tool/tools/capi.py +727 -0
  10. kubectl_mcp_tool/tools/certs.py +709 -0
  11. kubectl_mcp_tool/tools/cilium.py +582 -0
  12. kubectl_mcp_tool/tools/cluster.py +395 -121
  13. kubectl_mcp_tool/tools/core.py +157 -60
  14. kubectl_mcp_tool/tools/cost.py +97 -41
  15. kubectl_mcp_tool/tools/deployments.py +173 -56
  16. kubectl_mcp_tool/tools/diagnostics.py +40 -13
  17. kubectl_mcp_tool/tools/gitops.py +552 -0
  18. kubectl_mcp_tool/tools/helm.py +133 -46
  19. kubectl_mcp_tool/tools/keda.py +464 -0
  20. kubectl_mcp_tool/tools/kiali.py +652 -0
  21. kubectl_mcp_tool/tools/kubevirt.py +803 -0
  22. kubectl_mcp_tool/tools/networking.py +106 -32
  23. kubectl_mcp_tool/tools/operations.py +176 -50
  24. kubectl_mcp_tool/tools/pods.py +162 -50
  25. kubectl_mcp_tool/tools/policy.py +554 -0
  26. kubectl_mcp_tool/tools/rollouts.py +790 -0
  27. kubectl_mcp_tool/tools/security.py +89 -36
  28. kubectl_mcp_tool/tools/storage.py +35 -16
  29. tests/test_browser.py +2 -2
  30. tests/test_ecosystem.py +331 -0
  31. tests/test_tools.py +73 -10
  32. kubectl_mcp_server-1.14.0.dist-info/METADATA +0 -780
  33. kubectl_mcp_server-1.14.0.dist-info/RECORD +0 -49
  34. {kubectl_mcp_server-1.14.0.dist-info → kubectl_mcp_server-1.16.0.dist-info}/WHEEL +0 -0
  35. {kubectl_mcp_server-1.14.0.dist-info → kubectl_mcp_server-1.16.0.dist-info}/entry_points.txt +0 -0
  36. {kubectl_mcp_server-1.14.0.dist-info → kubectl_mcp_server-1.16.0.dist-info}/licenses/LICENSE +0 -0
  37. {kubectl_mcp_server-1.14.0.dist-info → kubectl_mcp_server-1.16.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,652 @@
1
+ """Kiali/Istio service mesh observability toolset for kubectl-mcp-server.
2
+
3
+ Provides tools for service mesh visualization and Istio configuration inspection.
4
+ """
5
+
6
+ import subprocess
7
+ import json
8
+ import os
9
+ from typing import Dict, Any, List, Optional
10
+
11
+ try:
12
+ from fastmcp import FastMCP
13
+ from fastmcp.tools import ToolAnnotations
14
+ except ImportError:
15
+ from mcp.server.fastmcp import FastMCP
16
+ from mcp.types import ToolAnnotations
17
+
18
+ from ..k8s_config import _get_kubectl_context_args
19
+ from ..crd_detector import crd_exists
20
+
21
+
22
+ # Istio CRDs
23
+ VIRTUALSERVICE_CRD = "virtualservices.networking.istio.io"
24
+ DESTINATIONRULE_CRD = "destinationrules.networking.istio.io"
25
+ GATEWAY_CRD = "gateways.networking.istio.io"
26
+ SERVICEENTRY_CRD = "serviceentries.networking.istio.io"
27
+ SIDECAR_CRD = "sidecars.networking.istio.io"
28
+ PEERAUTHENTICATION_CRD = "peerauthentications.security.istio.io"
29
+ AUTHORIZATIONPOLICY_CRD = "authorizationpolicies.security.istio.io"
30
+ REQUESTAUTHENTICATION_CRD = "requestauthentications.security.istio.io"
31
+
32
+
33
+ def _run_kubectl(args: List[str], context: str = "") -> Dict[str, Any]:
34
+ """Run kubectl command and return result."""
35
+ cmd = ["kubectl"] + _get_kubectl_context_args(context) + args
36
+ try:
37
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
38
+ if result.returncode == 0:
39
+ return {"success": True, "output": result.stdout}
40
+ return {"success": False, "error": result.stderr}
41
+ except subprocess.TimeoutExpired:
42
+ return {"success": False, "error": "Command timed out"}
43
+ except Exception as e:
44
+ return {"success": False, "error": str(e)}
45
+
46
+
47
+ def _get_resources(kind: str, namespace: str = "", context: str = "", label_selector: str = "") -> List[Dict]:
48
+ """Get Kubernetes resources of a specific kind."""
49
+ args = ["get", kind, "-o", "json"]
50
+ if namespace:
51
+ args.extend(["-n", namespace])
52
+ else:
53
+ args.append("-A")
54
+ if label_selector:
55
+ args.extend(["-l", label_selector])
56
+
57
+ result = _run_kubectl(args, context)
58
+ if result["success"]:
59
+ try:
60
+ data = json.loads(result["output"])
61
+ return data.get("items", [])
62
+ except json.JSONDecodeError:
63
+ return []
64
+ return []
65
+
66
+
67
+ def _istioctl_available() -> bool:
68
+ """Check if istioctl CLI is available."""
69
+ try:
70
+ result = subprocess.run(["istioctl", "version", "--remote=false"],
71
+ capture_output=True, timeout=5)
72
+ return result.returncode == 0
73
+ except Exception:
74
+ return False
75
+
76
+
77
+ def _get_kiali_config() -> Dict[str, str]:
78
+ """Get Kiali connection configuration from environment."""
79
+ return {
80
+ "url": os.environ.get("KIALI_URL", ""),
81
+ "token": os.environ.get("KIALI_TOKEN", ""),
82
+ "username": os.environ.get("KIALI_USERNAME", ""),
83
+ "password": os.environ.get("KIALI_PASSWORD", ""),
84
+ }
85
+
86
+
87
+ # ============== Istio Resource Functions ==============
88
+
89
+ def istio_virtualservices_list(
90
+ namespace: str = "",
91
+ context: str = "",
92
+ label_selector: str = ""
93
+ ) -> Dict[str, Any]:
94
+ """List Istio VirtualServices.
95
+
96
+ Args:
97
+ namespace: Filter by namespace (empty for all namespaces)
98
+ context: Kubernetes context to use (optional)
99
+ label_selector: Label selector to filter
100
+
101
+ Returns:
102
+ List of VirtualServices with their configuration
103
+ """
104
+ if not crd_exists(VIRTUALSERVICE_CRD, context):
105
+ return {
106
+ "success": False,
107
+ "error": "Istio is not installed (virtualservices.networking.istio.io CRD not found)"
108
+ }
109
+
110
+ virtualservices = []
111
+ for item in _get_resources("virtualservices.networking.istio.io", namespace, context, label_selector):
112
+ spec = item.get("spec", {})
113
+ hosts = spec.get("hosts", [])
114
+ gateways = spec.get("gateways", [])
115
+ http_routes = spec.get("http", [])
116
+ tcp_routes = spec.get("tcp", [])
117
+ tls_routes = spec.get("tls", [])
118
+
119
+ virtualservices.append({
120
+ "name": item["metadata"]["name"],
121
+ "namespace": item["metadata"]["namespace"],
122
+ "hosts": hosts,
123
+ "gateways": gateways,
124
+ "http_routes_count": len(http_routes),
125
+ "tcp_routes_count": len(tcp_routes),
126
+ "tls_routes_count": len(tls_routes),
127
+ "total_routes": len(http_routes) + len(tcp_routes) + len(tls_routes),
128
+ })
129
+
130
+ return {
131
+ "context": context or "current",
132
+ "total": len(virtualservices),
133
+ "virtualservices": virtualservices,
134
+ }
135
+
136
+
137
+ def istio_virtualservice_get(
138
+ name: str,
139
+ namespace: str,
140
+ context: str = ""
141
+ ) -> Dict[str, Any]:
142
+ """Get detailed information about a VirtualService.
143
+
144
+ Args:
145
+ name: Name of the VirtualService
146
+ namespace: Namespace of the VirtualService
147
+ context: Kubernetes context to use (optional)
148
+
149
+ Returns:
150
+ Detailed VirtualService information
151
+ """
152
+ if not crd_exists(VIRTUALSERVICE_CRD, context):
153
+ return {"success": False, "error": "Istio is not installed"}
154
+
155
+ args = ["get", "virtualservices.networking.istio.io", name, "-n", namespace, "-o", "json"]
156
+ result = _run_kubectl(args, context)
157
+
158
+ if result["success"]:
159
+ try:
160
+ data = json.loads(result["output"])
161
+ return {
162
+ "success": True,
163
+ "context": context or "current",
164
+ "virtualservice": data,
165
+ }
166
+ except json.JSONDecodeError:
167
+ return {"success": False, "error": "Failed to parse response"}
168
+
169
+ return {"success": False, "error": result.get("error", "Unknown error")}
170
+
171
+
172
+ def istio_destinationrules_list(
173
+ namespace: str = "",
174
+ context: str = "",
175
+ label_selector: str = ""
176
+ ) -> Dict[str, Any]:
177
+ """List Istio DestinationRules.
178
+
179
+ Args:
180
+ namespace: Filter by namespace (empty for all namespaces)
181
+ context: Kubernetes context to use (optional)
182
+ label_selector: Label selector to filter
183
+
184
+ Returns:
185
+ List of DestinationRules
186
+ """
187
+ if not crd_exists(DESTINATIONRULE_CRD, context):
188
+ return {
189
+ "success": False,
190
+ "error": "Istio DestinationRules CRD not found"
191
+ }
192
+
193
+ rules = []
194
+ for item in _get_resources("destinationrules.networking.istio.io", namespace, context, label_selector):
195
+ spec = item.get("spec", {})
196
+ traffic_policy = spec.get("trafficPolicy", {})
197
+ subsets = spec.get("subsets", [])
198
+
199
+ rules.append({
200
+ "name": item["metadata"]["name"],
201
+ "namespace": item["metadata"]["namespace"],
202
+ "host": spec.get("host", ""),
203
+ "subsets_count": len(subsets),
204
+ "subsets": [s.get("name") for s in subsets],
205
+ "has_traffic_policy": bool(traffic_policy),
206
+ "load_balancer": traffic_policy.get("loadBalancer", {}).get("simple"),
207
+ "connection_pool": bool(traffic_policy.get("connectionPool")),
208
+ "outlier_detection": bool(traffic_policy.get("outlierDetection")),
209
+ "tls_mode": traffic_policy.get("tls", {}).get("mode"),
210
+ })
211
+
212
+ return {
213
+ "context": context or "current",
214
+ "total": len(rules),
215
+ "destinationrules": rules,
216
+ }
217
+
218
+
219
+ def istio_gateways_list(
220
+ namespace: str = "",
221
+ context: str = "",
222
+ label_selector: str = ""
223
+ ) -> Dict[str, Any]:
224
+ """List Istio Gateways.
225
+
226
+ Args:
227
+ namespace: Filter by namespace (empty for all namespaces)
228
+ context: Kubernetes context to use (optional)
229
+ label_selector: Label selector to filter
230
+
231
+ Returns:
232
+ List of Gateways
233
+ """
234
+ if not crd_exists(GATEWAY_CRD, context):
235
+ return {
236
+ "success": False,
237
+ "error": "Istio Gateways CRD not found"
238
+ }
239
+
240
+ gateways = []
241
+ for item in _get_resources("gateways.networking.istio.io", namespace, context, label_selector):
242
+ spec = item.get("spec", {})
243
+ selector = spec.get("selector", {})
244
+ servers = spec.get("servers", [])
245
+
246
+ # Extract hosts and ports from servers
247
+ all_hosts = []
248
+ all_ports = []
249
+ for server in servers:
250
+ all_hosts.extend(server.get("hosts", []))
251
+ port = server.get("port", {})
252
+ if port:
253
+ all_ports.append({
254
+ "number": port.get("number"),
255
+ "name": port.get("name"),
256
+ "protocol": port.get("protocol"),
257
+ })
258
+
259
+ gateways.append({
260
+ "name": item["metadata"]["name"],
261
+ "namespace": item["metadata"]["namespace"],
262
+ "selector": selector,
263
+ "servers_count": len(servers),
264
+ "hosts": list(set(all_hosts)),
265
+ "ports": all_ports,
266
+ })
267
+
268
+ return {
269
+ "context": context or "current",
270
+ "total": len(gateways),
271
+ "gateways": gateways,
272
+ }
273
+
274
+
275
+ def istio_peerauthentications_list(
276
+ namespace: str = "",
277
+ context: str = "",
278
+ label_selector: str = ""
279
+ ) -> Dict[str, Any]:
280
+ """List Istio PeerAuthentication policies.
281
+
282
+ Args:
283
+ namespace: Filter by namespace (empty for all namespaces)
284
+ context: Kubernetes context to use (optional)
285
+ label_selector: Label selector to filter
286
+
287
+ Returns:
288
+ List of PeerAuthentication policies
289
+ """
290
+ if not crd_exists(PEERAUTHENTICATION_CRD, context):
291
+ return {
292
+ "success": False,
293
+ "error": "Istio PeerAuthentication CRD not found"
294
+ }
295
+
296
+ policies = []
297
+ for item in _get_resources("peerauthentications.security.istio.io", namespace, context, label_selector):
298
+ spec = item.get("spec", {})
299
+ selector = spec.get("selector", {})
300
+ mtls = spec.get("mtls", {})
301
+ port_level_mtls = spec.get("portLevelMtls", {})
302
+
303
+ policies.append({
304
+ "name": item["metadata"]["name"],
305
+ "namespace": item["metadata"]["namespace"],
306
+ "selector": selector.get("matchLabels", {}),
307
+ "mtls_mode": mtls.get("mode", "UNSET"),
308
+ "port_level_mtls_count": len(port_level_mtls),
309
+ })
310
+
311
+ return {
312
+ "context": context or "current",
313
+ "total": len(policies),
314
+ "peerauthentications": policies,
315
+ }
316
+
317
+
318
+ def istio_authorizationpolicies_list(
319
+ namespace: str = "",
320
+ context: str = "",
321
+ label_selector: str = ""
322
+ ) -> Dict[str, Any]:
323
+ """List Istio AuthorizationPolicies.
324
+
325
+ Args:
326
+ namespace: Filter by namespace (empty for all namespaces)
327
+ context: Kubernetes context to use (optional)
328
+ label_selector: Label selector to filter
329
+
330
+ Returns:
331
+ List of AuthorizationPolicies
332
+ """
333
+ if not crd_exists(AUTHORIZATIONPOLICY_CRD, context):
334
+ return {
335
+ "success": False,
336
+ "error": "Istio AuthorizationPolicy CRD not found"
337
+ }
338
+
339
+ policies = []
340
+ for item in _get_resources("authorizationpolicies.security.istio.io", namespace, context, label_selector):
341
+ spec = item.get("spec", {})
342
+ selector = spec.get("selector", {})
343
+ rules = spec.get("rules", [])
344
+ action = spec.get("action", "ALLOW")
345
+
346
+ policies.append({
347
+ "name": item["metadata"]["name"],
348
+ "namespace": item["metadata"]["namespace"],
349
+ "selector": selector.get("matchLabels", {}),
350
+ "action": action,
351
+ "rules_count": len(rules),
352
+ })
353
+
354
+ return {
355
+ "context": context or "current",
356
+ "total": len(policies),
357
+ "authorizationpolicies": policies,
358
+ }
359
+
360
+
361
+ def istio_proxy_status(context: str = "") -> Dict[str, Any]:
362
+ """Get Istio proxy (Envoy) synchronization status.
363
+
364
+ Args:
365
+ context: Kubernetes context to use (optional)
366
+
367
+ Returns:
368
+ Proxy sync status for all workloads
369
+ """
370
+ if not _istioctl_available():
371
+ return {
372
+ "success": False,
373
+ "error": "istioctl CLI not available"
374
+ }
375
+
376
+ cmd = ["istioctl", "proxy-status", "-o", "json"]
377
+ if context:
378
+ cmd.extend(["--context", context])
379
+
380
+ try:
381
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
382
+ if result.returncode == 0:
383
+ try:
384
+ data = json.loads(result.stdout)
385
+ proxies = []
386
+ for proxy in data:
387
+ proxies.append({
388
+ "name": proxy.get("proxy", ""),
389
+ "cluster_id": proxy.get("cluster_id", ""),
390
+ "istiod": proxy.get("istiod", ""),
391
+ "cds": proxy.get("cluster_status", ""),
392
+ "lds": proxy.get("listener_status", ""),
393
+ "eds": proxy.get("endpoint_status", ""),
394
+ "rds": proxy.get("route_status", ""),
395
+ "ecds": proxy.get("extension_config_status", ""),
396
+ })
397
+
398
+ synced = sum(1 for p in proxies if all(
399
+ p.get(s) == "SYNCED" for s in ["cds", "lds", "eds", "rds"]
400
+ ))
401
+
402
+ return {
403
+ "success": True,
404
+ "context": context or "current",
405
+ "total": len(proxies),
406
+ "synced": synced,
407
+ "proxies": proxies,
408
+ }
409
+ except json.JSONDecodeError:
410
+ return {"success": False, "error": "Failed to parse response"}
411
+
412
+ return {"success": False, "error": result.stderr}
413
+ except Exception as e:
414
+ return {"success": False, "error": str(e)}
415
+
416
+
417
+ def istio_analyze(
418
+ namespace: str = "",
419
+ context: str = ""
420
+ ) -> Dict[str, Any]:
421
+ """Analyze Istio configuration for potential issues.
422
+
423
+ Args:
424
+ namespace: Namespace to analyze (empty for all)
425
+ context: Kubernetes context to use (optional)
426
+
427
+ Returns:
428
+ Analysis results with warnings and errors
429
+ """
430
+ if not _istioctl_available():
431
+ return {
432
+ "success": False,
433
+ "error": "istioctl CLI not available"
434
+ }
435
+
436
+ cmd = ["istioctl", "analyze", "-o", "json"]
437
+ if namespace:
438
+ cmd.extend(["-n", namespace])
439
+ else:
440
+ cmd.append("-A")
441
+ if context:
442
+ cmd.extend(["--context", context])
443
+
444
+ try:
445
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
446
+ # istioctl analyze returns non-zero if issues found, but still outputs valid JSON
447
+ try:
448
+ data = json.loads(result.stdout) if result.stdout else []
449
+
450
+ messages = []
451
+ for msg in data:
452
+ messages.append({
453
+ "code": msg.get("code", ""),
454
+ "level": msg.get("level", ""),
455
+ "message": msg.get("message", ""),
456
+ "origin": msg.get("origin", ""),
457
+ "documentation_url": msg.get("documentationUrl", ""),
458
+ })
459
+
460
+ errors = sum(1 for m in messages if m["level"] == "Error")
461
+ warnings = sum(1 for m in messages if m["level"] == "Warning")
462
+ info = sum(1 for m in messages if m["level"] == "Info")
463
+
464
+ return {
465
+ "success": True,
466
+ "context": context or "current",
467
+ "namespace": namespace or "all",
468
+ "total_issues": len(messages),
469
+ "errors": errors,
470
+ "warnings": warnings,
471
+ "info": info,
472
+ "messages": messages,
473
+ }
474
+ except json.JSONDecodeError:
475
+ # If not JSON, return the text output
476
+ return {
477
+ "success": True,
478
+ "context": context or "current",
479
+ "namespace": namespace or "all",
480
+ "output": result.stdout,
481
+ }
482
+
483
+ except Exception as e:
484
+ return {"success": False, "error": str(e)}
485
+
486
+
487
+ def istio_sidecar_status(
488
+ namespace: str = "",
489
+ context: str = ""
490
+ ) -> Dict[str, Any]:
491
+ """Get sidecar injection status for pods.
492
+
493
+ Args:
494
+ namespace: Namespace to check (empty for all)
495
+ context: Kubernetes context to use (optional)
496
+
497
+ Returns:
498
+ Pods with their sidecar injection status
499
+ """
500
+ args = ["get", "pods", "-o", "json"]
501
+ if namespace:
502
+ args.extend(["-n", namespace])
503
+ else:
504
+ args.append("-A")
505
+
506
+ result = _run_kubectl(args, context)
507
+ if not result["success"]:
508
+ return {"success": False, "error": result.get("error", "Failed to list pods")}
509
+
510
+ try:
511
+ data = json.loads(result["output"])
512
+ pods = data.get("items", [])
513
+ except json.JSONDecodeError:
514
+ return {"success": False, "error": "Failed to parse response"}
515
+
516
+ pod_status = []
517
+ for pod in pods:
518
+ containers = pod.get("spec", {}).get("containers", [])
519
+ container_names = [c.get("name") for c in containers]
520
+
521
+ has_sidecar = "istio-proxy" in container_names
522
+ annotations = pod.get("metadata", {}).get("annotations", {})
523
+ inject_status = annotations.get("sidecar.istio.io/status")
524
+
525
+ pod_status.append({
526
+ "name": pod["metadata"]["name"],
527
+ "namespace": pod["metadata"]["namespace"],
528
+ "has_sidecar": has_sidecar,
529
+ "inject_annotation": annotations.get("sidecar.istio.io/inject"),
530
+ "sidecar_status": "injected" if has_sidecar else "not_injected",
531
+ })
532
+
533
+ injected = sum(1 for p in pod_status if p["has_sidecar"])
534
+
535
+ return {
536
+ "context": context or "current",
537
+ "total": len(pod_status),
538
+ "injected": injected,
539
+ "not_injected": len(pod_status) - injected,
540
+ "pods": pod_status,
541
+ }
542
+
543
+
544
+ def istio_detect(context: str = "") -> Dict[str, Any]:
545
+ """Detect if Istio is installed and its components.
546
+
547
+ Args:
548
+ context: Kubernetes context to use (optional)
549
+
550
+ Returns:
551
+ Detection results for Istio
552
+ """
553
+ return {
554
+ "context": context or "current",
555
+ "installed": crd_exists(VIRTUALSERVICE_CRD, context),
556
+ "cli_available": _istioctl_available(),
557
+ "kiali_configured": bool(_get_kiali_config().get("url")),
558
+ "crds": {
559
+ "virtualservices": crd_exists(VIRTUALSERVICE_CRD, context),
560
+ "destinationrules": crd_exists(DESTINATIONRULE_CRD, context),
561
+ "gateways": crd_exists(GATEWAY_CRD, context),
562
+ "serviceentries": crd_exists(SERVICEENTRY_CRD, context),
563
+ "sidecars": crd_exists(SIDECAR_CRD, context),
564
+ "peerauthentications": crd_exists(PEERAUTHENTICATION_CRD, context),
565
+ "authorizationpolicies": crd_exists(AUTHORIZATIONPOLICY_CRD, context),
566
+ "requestauthentications": crd_exists(REQUESTAUTHENTICATION_CRD, context),
567
+ },
568
+ }
569
+
570
+
571
+ def register_istio_tools(mcp: FastMCP, non_destructive: bool = False):
572
+ """Register Istio/Kiali tools with the MCP server."""
573
+
574
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
575
+ def istio_virtualservices_list_tool(
576
+ namespace: str = "",
577
+ context: str = "",
578
+ label_selector: str = ""
579
+ ) -> str:
580
+ """List Istio VirtualServices."""
581
+ return json.dumps(istio_virtualservices_list(namespace, context, label_selector), indent=2)
582
+
583
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
584
+ def istio_virtualservice_get_tool(
585
+ name: str,
586
+ namespace: str,
587
+ context: str = ""
588
+ ) -> str:
589
+ """Get detailed information about a VirtualService."""
590
+ return json.dumps(istio_virtualservice_get(name, namespace, context), indent=2)
591
+
592
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
593
+ def istio_destinationrules_list_tool(
594
+ namespace: str = "",
595
+ context: str = "",
596
+ label_selector: str = ""
597
+ ) -> str:
598
+ """List Istio DestinationRules."""
599
+ return json.dumps(istio_destinationrules_list(namespace, context, label_selector), indent=2)
600
+
601
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
602
+ def istio_gateways_list_tool(
603
+ namespace: str = "",
604
+ context: str = "",
605
+ label_selector: str = ""
606
+ ) -> str:
607
+ """List Istio Gateways."""
608
+ return json.dumps(istio_gateways_list(namespace, context, label_selector), indent=2)
609
+
610
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
611
+ def istio_peerauthentications_list_tool(
612
+ namespace: str = "",
613
+ context: str = "",
614
+ label_selector: str = ""
615
+ ) -> str:
616
+ """List Istio PeerAuthentication policies."""
617
+ return json.dumps(istio_peerauthentications_list(namespace, context, label_selector), indent=2)
618
+
619
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
620
+ def istio_authorizationpolicies_list_tool(
621
+ namespace: str = "",
622
+ context: str = "",
623
+ label_selector: str = ""
624
+ ) -> str:
625
+ """List Istio AuthorizationPolicies."""
626
+ return json.dumps(istio_authorizationpolicies_list(namespace, context, label_selector), indent=2)
627
+
628
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
629
+ def istio_proxy_status_tool(context: str = "") -> str:
630
+ """Get Istio proxy synchronization status."""
631
+ return json.dumps(istio_proxy_status(context), indent=2)
632
+
633
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
634
+ def istio_analyze_tool(
635
+ namespace: str = "",
636
+ context: str = ""
637
+ ) -> str:
638
+ """Analyze Istio configuration for potential issues."""
639
+ return json.dumps(istio_analyze(namespace, context), indent=2)
640
+
641
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
642
+ def istio_sidecar_status_tool(
643
+ namespace: str = "",
644
+ context: str = ""
645
+ ) -> str:
646
+ """Get sidecar injection status for pods."""
647
+ return json.dumps(istio_sidecar_status(namespace, context), indent=2)
648
+
649
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
650
+ def istio_detect_tool(context: str = "") -> str:
651
+ """Detect if Istio is installed and its components."""
652
+ return json.dumps(istio_detect(context), indent=2)