kubectl-mcp-server 1.19.3__py3-none-any.whl → 1.21.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.
@@ -0,0 +1,744 @@
1
+ """vind (vCluster in Docker) toolset for kubectl-mcp-server.
2
+
3
+ vind enables running Kubernetes clusters directly as Docker containers,
4
+ combining vCluster with Docker's simplicity. Uses the standard vCluster CLI.
5
+ """
6
+
7
+ import subprocess
8
+ import json
9
+ import re
10
+ from typing import Dict, Any, List, Optional
11
+
12
+ try:
13
+ from fastmcp import FastMCP
14
+ from fastmcp.tools import ToolAnnotations
15
+ except ImportError:
16
+ from mcp.server.fastmcp import FastMCP
17
+ from mcp.types import ToolAnnotations
18
+
19
+
20
+ def _vcluster_available() -> bool:
21
+ """Check if vcluster CLI is available."""
22
+ try:
23
+ result = subprocess.run(
24
+ ["vcluster", "version"],
25
+ capture_output=True,
26
+ timeout=10
27
+ )
28
+ return result.returncode == 0
29
+ except Exception:
30
+ return False
31
+
32
+
33
+ def _get_vcluster_version() -> Optional[str]:
34
+ """Get vcluster CLI version."""
35
+ try:
36
+ result = subprocess.run(
37
+ ["vcluster", "version"],
38
+ capture_output=True,
39
+ text=True,
40
+ timeout=10
41
+ )
42
+ if result.returncode == 0:
43
+ output = result.stdout.strip()
44
+ match = re.search(r'v?\d+\.\d+\.\d+', output)
45
+ if match:
46
+ return match.group(0)
47
+ return output
48
+ return None
49
+ except Exception:
50
+ return None
51
+
52
+
53
+ def _run_vcluster(
54
+ args: List[str],
55
+ timeout: int = 120,
56
+ json_output: bool = False
57
+ ) -> Dict[str, Any]:
58
+ """Run vcluster command and return result.
59
+
60
+ Args:
61
+ args: Command arguments (without 'vcluster' prefix)
62
+ timeout: Command timeout in seconds
63
+ json_output: Whether to add --output json flag
64
+
65
+ Returns:
66
+ Result dict with success status and output/error
67
+ """
68
+ if not _vcluster_available():
69
+ return {
70
+ "success": False,
71
+ "error": "vcluster CLI not available. Install from: https://www.vcluster.com/docs/getting-started/setup"
72
+ }
73
+
74
+ cmd = ["vcluster"] + args
75
+ if json_output and "--output" not in args:
76
+ cmd.extend(["--output", "json"])
77
+
78
+ try:
79
+ result = subprocess.run(
80
+ cmd,
81
+ capture_output=True,
82
+ text=True,
83
+ timeout=timeout
84
+ )
85
+ if result.returncode == 0:
86
+ output = result.stdout.strip()
87
+ if json_output and output:
88
+ try:
89
+ return {"success": True, "data": json.loads(output)}
90
+ except json.JSONDecodeError:
91
+ return {"success": True, "output": output}
92
+ return {"success": True, "output": output}
93
+ return {
94
+ "success": False,
95
+ "error": result.stderr.strip() or f"Command failed with exit code {result.returncode}"
96
+ }
97
+ except subprocess.TimeoutExpired:
98
+ return {"success": False, "error": f"Command timed out after {timeout} seconds"}
99
+ except Exception as e:
100
+ return {"success": False, "error": str(e)}
101
+
102
+
103
+ def vind_detect() -> Dict[str, Any]:
104
+ """Detect if vCluster CLI is installed and get version info.
105
+
106
+ Returns:
107
+ Detection results including CLI availability and version
108
+ """
109
+ available = _vcluster_available()
110
+ version = _get_vcluster_version() if available else None
111
+
112
+ return {
113
+ "installed": available,
114
+ "cli_available": available,
115
+ "version": version,
116
+ "install_instructions": "https://www.vcluster.com/docs/getting-started/setup" if not available else None
117
+ }
118
+
119
+
120
+ def vind_list_clusters() -> Dict[str, Any]:
121
+ """List all vCluster instances.
122
+
123
+ Returns:
124
+ List of vCluster instances with their status
125
+ """
126
+ result = _run_vcluster(["list"], json_output=True, timeout=30)
127
+
128
+ if not result["success"]:
129
+ return result
130
+
131
+ clusters = []
132
+ data = result.get("data") or result.get("output", "")
133
+
134
+ if isinstance(data, str) and data:
135
+ try:
136
+ data = json.loads(data)
137
+ except json.JSONDecodeError:
138
+ pass
139
+
140
+ if isinstance(data, list):
141
+ for cluster in data:
142
+ clusters.append({
143
+ "name": cluster.get("Name", cluster.get("name", "")),
144
+ "namespace": cluster.get("Namespace", cluster.get("namespace", "")),
145
+ "status": cluster.get("Status", cluster.get("status", "")),
146
+ "version": cluster.get("Version", cluster.get("version", "")),
147
+ "connected": cluster.get("Connected", cluster.get("connected", False)),
148
+ "created": cluster.get("Created", cluster.get("created", "")),
149
+ "age": cluster.get("Age", cluster.get("age", "")),
150
+ })
151
+ elif isinstance(data, str) and data:
152
+ return {"success": True, "output": data, "clusters": []}
153
+
154
+ return {
155
+ "success": True,
156
+ "total": len(clusters),
157
+ "clusters": clusters
158
+ }
159
+
160
+
161
+ def vind_status(name: str, namespace: str = "vcluster") -> Dict[str, Any]:
162
+ """Get detailed status of a vCluster instance.
163
+
164
+ Args:
165
+ name: Name of the vCluster instance
166
+ namespace: Namespace where vCluster is running (default: vcluster)
167
+
168
+ Returns:
169
+ Detailed status information
170
+ """
171
+ result = _run_vcluster(
172
+ ["list"],
173
+ timeout=30,
174
+ json_output=True,
175
+ )
176
+
177
+ if not result["success"]:
178
+ return result
179
+
180
+ data = result.get("data") or []
181
+ if isinstance(data, list):
182
+ for cluster in data:
183
+ cluster_name = cluster.get("Name", cluster.get("name", ""))
184
+ cluster_ns = cluster.get("Namespace", cluster.get("namespace", ""))
185
+ if cluster_name == name and (not namespace or cluster_ns == namespace):
186
+ return {
187
+ "success": True,
188
+ "cluster": {
189
+ "name": cluster_name,
190
+ "namespace": cluster_ns,
191
+ "status": cluster.get("Status", cluster.get("status", "")),
192
+ "version": cluster.get("Version", cluster.get("version", "")),
193
+ "connected": cluster.get("Connected", cluster.get("connected", False)),
194
+ "created": cluster.get("Created", cluster.get("created", "")),
195
+ "age": cluster.get("Age", cluster.get("age", "")),
196
+ "pro": cluster.get("Pro", cluster.get("pro", False)),
197
+ }
198
+ }
199
+
200
+ return {
201
+ "success": False,
202
+ "error": f"vCluster '{name}' not found in namespace '{namespace}'"
203
+ }
204
+
205
+
206
+ def vind_get_kubeconfig(
207
+ name: str,
208
+ namespace: str = "vcluster",
209
+ print_only: bool = True
210
+ ) -> Dict[str, Any]:
211
+ """Get kubeconfig for a vCluster instance.
212
+
213
+ Args:
214
+ name: Name of the vCluster instance
215
+ namespace: Namespace where vCluster is running
216
+ print_only: Only print kubeconfig without modifying local config
217
+
218
+ Returns:
219
+ Kubeconfig content or path
220
+ """
221
+ args = ["connect", name, "--namespace", namespace]
222
+ if print_only:
223
+ args.append("--print")
224
+
225
+ result = _run_vcluster(args, timeout=60)
226
+
227
+ if result["success"] and print_only:
228
+ return {
229
+ "success": True,
230
+ "kubeconfig": result.get("output", ""),
231
+ "message": f"Kubeconfig for vCluster '{name}'"
232
+ }
233
+
234
+ return result
235
+
236
+
237
+ def vind_logs(
238
+ name: str,
239
+ namespace: str = "vcluster",
240
+ follow: bool = False,
241
+ tail: int = 100
242
+ ) -> Dict[str, Any]:
243
+ """Get logs from a vCluster instance.
244
+
245
+ Args:
246
+ name: Name of the vCluster instance
247
+ namespace: Namespace where vCluster is running
248
+ follow: Follow log output (not recommended for API use)
249
+ tail: Number of lines to show
250
+
251
+ Returns:
252
+ Log output
253
+ """
254
+ args = ["logs", name, "--namespace", namespace]
255
+ if tail:
256
+ args.extend(["--tail", str(tail)])
257
+
258
+ result = _run_vcluster(args, timeout=60)
259
+ return result
260
+
261
+
262
+ def vind_create_cluster(
263
+ name: str,
264
+ namespace: str = "",
265
+ kubernetes_version: str = "",
266
+ values_file: str = "",
267
+ set_values: List[str] = None,
268
+ connect: bool = True,
269
+ upgrade: bool = False
270
+ ) -> Dict[str, Any]:
271
+ """Create a new vCluster instance.
272
+
273
+ Args:
274
+ name: Name for the new vCluster
275
+ namespace: Namespace to create vCluster in (default: vcluster-<name>)
276
+ kubernetes_version: Kubernetes version (e.g., "v1.29.0")
277
+ values_file: Path to values.yaml file
278
+ set_values: List of Helm-style value overrides (e.g., ["key=value"])
279
+ connect: Update kubeconfig and switch context after creation
280
+ upgrade: Upgrade existing vCluster instead of failing
281
+
282
+ Returns:
283
+ Creation result
284
+ """
285
+ args = ["create", name]
286
+
287
+ if namespace:
288
+ args.extend(["--namespace", namespace])
289
+
290
+ if kubernetes_version:
291
+ args.extend(["--kubernetes-version", kubernetes_version])
292
+
293
+ if values_file:
294
+ args.extend(["--values", values_file])
295
+
296
+ if set_values:
297
+ for val in set_values:
298
+ args.extend(["--set", val])
299
+
300
+ if connect:
301
+ args.append("--connect")
302
+ else:
303
+ args.append("--connect=false")
304
+
305
+ if upgrade:
306
+ args.append("--upgrade")
307
+
308
+ result = _run_vcluster(args, timeout=300)
309
+
310
+ if result["success"]:
311
+ return {
312
+ "success": True,
313
+ "message": f"vCluster '{name}' created successfully",
314
+ "output": result.get("output", ""),
315
+ "connected": connect
316
+ }
317
+
318
+ return result
319
+
320
+
321
+ def vind_delete_cluster(
322
+ name: str,
323
+ namespace: str = "",
324
+ delete_namespace: bool = False,
325
+ force: bool = False
326
+ ) -> Dict[str, Any]:
327
+ """Delete a vCluster instance.
328
+
329
+ Args:
330
+ name: Name of the vCluster to delete
331
+ namespace: Namespace of the vCluster
332
+ delete_namespace: Also delete the namespace
333
+ force: Force deletion
334
+
335
+ Returns:
336
+ Deletion result
337
+ """
338
+ args = ["delete", name]
339
+
340
+ if namespace:
341
+ args.extend(["--namespace", namespace])
342
+
343
+ if delete_namespace:
344
+ args.append("--delete-namespace")
345
+
346
+ if force:
347
+ args.append("--force")
348
+
349
+ result = _run_vcluster(args, timeout=120)
350
+
351
+ if result["success"]:
352
+ return {
353
+ "success": True,
354
+ "message": f"vCluster '{name}' deleted successfully",
355
+ "output": result.get("output", "")
356
+ }
357
+
358
+ return result
359
+
360
+
361
+ def vind_pause(name: str, namespace: str = "") -> Dict[str, Any]:
362
+ """Pause/sleep a vCluster instance to save resources.
363
+
364
+ Args:
365
+ name: Name of the vCluster to pause
366
+ namespace: Namespace of the vCluster
367
+
368
+ Returns:
369
+ Pause result
370
+ """
371
+ args = ["pause", name]
372
+
373
+ if namespace:
374
+ args.extend(["--namespace", namespace])
375
+
376
+ result = _run_vcluster(args, timeout=120)
377
+
378
+ if result["success"]:
379
+ return {
380
+ "success": True,
381
+ "message": f"vCluster '{name}' paused successfully",
382
+ "output": result.get("output", "")
383
+ }
384
+
385
+ return result
386
+
387
+
388
+ def vind_resume(name: str, namespace: str = "") -> Dict[str, Any]:
389
+ """Resume/wake a sleeping vCluster instance.
390
+
391
+ Args:
392
+ name: Name of the vCluster to resume
393
+ namespace: Namespace of the vCluster
394
+
395
+ Returns:
396
+ Resume result
397
+ """
398
+ args = ["resume", name]
399
+
400
+ if namespace:
401
+ args.extend(["--namespace", namespace])
402
+
403
+ result = _run_vcluster(args, timeout=120)
404
+
405
+ if result["success"]:
406
+ return {
407
+ "success": True,
408
+ "message": f"vCluster '{name}' resumed successfully",
409
+ "output": result.get("output", "")
410
+ }
411
+
412
+ return result
413
+
414
+
415
+ def vind_connect(
416
+ name: str,
417
+ namespace: str = "",
418
+ update_current: bool = True,
419
+ kube_config: str = "",
420
+ background_proxy: bool = True
421
+ ) -> Dict[str, Any]:
422
+ """Connect kubectl to a vCluster instance.
423
+
424
+ Args:
425
+ name: Name of the vCluster
426
+ namespace: Namespace of the vCluster
427
+ update_current: Update current kubeconfig context
428
+ kube_config: Path to kubeconfig file to update
429
+ background_proxy: Use background proxy to avoid blocking (default: True)
430
+
431
+ Returns:
432
+ Connection result
433
+ """
434
+ args = ["connect", name]
435
+
436
+ if namespace:
437
+ args.extend(["--namespace", namespace])
438
+
439
+ if not update_current:
440
+ args.append("--update-current=false")
441
+
442
+ if kube_config:
443
+ args.extend(["--kube-config", kube_config])
444
+
445
+ if background_proxy:
446
+ args.append("--background-proxy")
447
+
448
+ result = _run_vcluster(args, timeout=60)
449
+
450
+ if result["success"]:
451
+ return {
452
+ "success": True,
453
+ "message": f"Connected to vCluster '{name}'",
454
+ "output": result.get("output", "")
455
+ }
456
+
457
+ return result
458
+
459
+
460
+ def vind_disconnect(name: str, namespace: str = "") -> Dict[str, Any]:
461
+ """Disconnect from a vCluster instance.
462
+
463
+ Args:
464
+ name: Name of the vCluster
465
+ namespace: Namespace of the vCluster
466
+
467
+ Returns:
468
+ Disconnection result
469
+ """
470
+ args = ["disconnect"]
471
+
472
+ result = _run_vcluster(args, timeout=30)
473
+
474
+ if result["success"]:
475
+ return {
476
+ "success": True,
477
+ "message": "Disconnected from vCluster",
478
+ "output": result.get("output", "")
479
+ }
480
+
481
+ return result
482
+
483
+
484
+ def vind_upgrade(
485
+ name: str,
486
+ namespace: str = "",
487
+ kubernetes_version: str = "",
488
+ values_file: str = "",
489
+ set_values: List[str] = None
490
+ ) -> Dict[str, Any]:
491
+ """Upgrade a vCluster instance.
492
+
493
+ Args:
494
+ name: Name of the vCluster to upgrade
495
+ namespace: Namespace of the vCluster
496
+ kubernetes_version: New Kubernetes version
497
+ values_file: Path to values.yaml file
498
+ set_values: List of Helm-style value overrides
499
+
500
+ Returns:
501
+ Upgrade result
502
+ """
503
+ args = ["create", name, "--upgrade"]
504
+
505
+ if namespace:
506
+ args.extend(["--namespace", namespace])
507
+
508
+ if kubernetes_version:
509
+ args.extend(["--kubernetes-version", kubernetes_version])
510
+
511
+ if values_file:
512
+ args.extend(["--values", values_file])
513
+
514
+ if set_values:
515
+ for val in set_values:
516
+ args.extend(["--set", val])
517
+
518
+ result = _run_vcluster(args, timeout=300)
519
+
520
+ if result["success"]:
521
+ return {
522
+ "success": True,
523
+ "message": f"vCluster '{name}' upgraded successfully",
524
+ "output": result.get("output", "")
525
+ }
526
+
527
+ return result
528
+
529
+
530
+ def vind_describe(name: str, namespace: str = "") -> Dict[str, Any]:
531
+ """Describe a vCluster instance with detailed information.
532
+
533
+ Args:
534
+ name: Name of the vCluster
535
+ namespace: Namespace of the vCluster
536
+
537
+ Returns:
538
+ Detailed cluster information
539
+ """
540
+ args = ["describe", name]
541
+
542
+ if namespace:
543
+ args.extend(["--namespace", namespace])
544
+
545
+ result = _run_vcluster(args, timeout=60)
546
+ return result
547
+
548
+
549
+ def vind_platform_start(
550
+ host: str = "",
551
+ port: int = 0,
552
+ no_port_forwarding: bool = True
553
+ ) -> Dict[str, Any]:
554
+ """Start the vCluster Platform UI.
555
+
556
+ Args:
557
+ host: Host to bind to (default: localhost)
558
+ port: Port to bind to (default: 9898)
559
+ no_port_forwarding: Don't start port-forwarding (install only, default: True)
560
+
561
+ Returns:
562
+ Platform start result
563
+ """
564
+ args = ["platform", "start"]
565
+
566
+ if host:
567
+ args.extend(["--host", host])
568
+
569
+ if port:
570
+ args.extend(["--port", str(port)])
571
+
572
+ if no_port_forwarding:
573
+ args.append("--no-port-forwarding")
574
+
575
+ result = _run_vcluster(args, timeout=60)
576
+
577
+ if result["success"]:
578
+ return {
579
+ "success": True,
580
+ "message": "vCluster Platform started",
581
+ "output": result.get("output", "")
582
+ }
583
+
584
+ return result
585
+
586
+
587
+ def register_vind_tools(mcp: FastMCP, non_destructive: bool = False):
588
+ """Register vind (vCluster in Docker) tools with the MCP server."""
589
+
590
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
591
+ def vind_detect_tool() -> str:
592
+ """Detect if vCluster CLI is installed and get version info."""
593
+ return json.dumps(vind_detect(), indent=2)
594
+
595
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
596
+ def vind_list_clusters_tool() -> str:
597
+ """List all vCluster instances."""
598
+ return json.dumps(vind_list_clusters(), indent=2)
599
+
600
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
601
+ def vind_status_tool(
602
+ name: str,
603
+ namespace: str = "vcluster"
604
+ ) -> str:
605
+ """Get detailed status of a vCluster instance."""
606
+ return json.dumps(vind_status(name, namespace), indent=2)
607
+
608
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
609
+ def vind_get_kubeconfig_tool(
610
+ name: str,
611
+ namespace: str = "vcluster"
612
+ ) -> str:
613
+ """Get kubeconfig for a vCluster instance."""
614
+ return json.dumps(vind_get_kubeconfig(name, namespace, print_only=True), indent=2)
615
+
616
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
617
+ def vind_logs_tool(
618
+ name: str,
619
+ namespace: str = "vcluster",
620
+ tail: int = 100
621
+ ) -> str:
622
+ """Get logs from a vCluster instance."""
623
+ return json.dumps(vind_logs(name, namespace, follow=False, tail=tail), indent=2)
624
+
625
+ @mcp.tool()
626
+ def vind_create_cluster_tool(
627
+ name: str,
628
+ namespace: str = "",
629
+ kubernetes_version: str = "",
630
+ values_file: str = "",
631
+ set_values: str = "",
632
+ connect: bool = True,
633
+ upgrade: bool = False
634
+ ) -> str:
635
+ """Create a new vCluster instance.
636
+
637
+ Args:
638
+ name: Name for the new vCluster
639
+ namespace: Namespace to create vCluster in
640
+ kubernetes_version: Kubernetes version (e.g., "v1.29.0")
641
+ values_file: Path to values.yaml file
642
+ set_values: Comma-separated Helm-style value overrides
643
+ connect: Update kubeconfig after creation
644
+ upgrade: Upgrade existing vCluster instead of failing
645
+ """
646
+ if non_destructive:
647
+ return json.dumps({"success": False, "error": "Operation blocked: non-destructive mode"})
648
+
649
+ values_list = [v.strip() for v in set_values.split(",") if v.strip()] if set_values else None
650
+ return json.dumps(
651
+ vind_create_cluster(name, namespace, kubernetes_version, values_file, values_list, connect, upgrade),
652
+ indent=2
653
+ )
654
+
655
+ @mcp.tool()
656
+ def vind_delete_cluster_tool(
657
+ name: str,
658
+ namespace: str = "",
659
+ delete_namespace: bool = False,
660
+ force: bool = False
661
+ ) -> str:
662
+ """Delete a vCluster instance."""
663
+ if non_destructive:
664
+ return json.dumps({"success": False, "error": "Operation blocked: non-destructive mode"})
665
+ return json.dumps(vind_delete_cluster(name, namespace, delete_namespace, force), indent=2)
666
+
667
+ @mcp.tool()
668
+ def vind_pause_tool(
669
+ name: str,
670
+ namespace: str = ""
671
+ ) -> str:
672
+ """Pause/sleep a vCluster instance to save resources."""
673
+ if non_destructive:
674
+ return json.dumps({"success": False, "error": "Operation blocked: non-destructive mode"})
675
+ return json.dumps(vind_pause(name, namespace), indent=2)
676
+
677
+ @mcp.tool()
678
+ def vind_resume_tool(
679
+ name: str,
680
+ namespace: str = ""
681
+ ) -> str:
682
+ """Resume/wake a sleeping vCluster instance."""
683
+ if non_destructive:
684
+ return json.dumps({"success": False, "error": "Operation blocked: non-destructive mode"})
685
+ return json.dumps(vind_resume(name, namespace), indent=2)
686
+
687
+ @mcp.tool()
688
+ def vind_connect_tool(
689
+ name: str,
690
+ namespace: str = "",
691
+ kube_config: str = ""
692
+ ) -> str:
693
+ """Connect kubectl to a vCluster instance."""
694
+ if non_destructive:
695
+ return json.dumps({"success": False, "error": "Operation blocked: non-destructive mode"})
696
+ return json.dumps(vind_connect(name, namespace, True, kube_config), indent=2)
697
+
698
+ @mcp.tool()
699
+ def vind_disconnect_tool() -> str:
700
+ """Disconnect from a vCluster instance."""
701
+ if non_destructive:
702
+ return json.dumps({"success": False, "error": "Operation blocked: non-destructive mode"})
703
+ return json.dumps(vind_disconnect("", ""), indent=2)
704
+
705
+ @mcp.tool()
706
+ def vind_upgrade_tool(
707
+ name: str,
708
+ namespace: str = "",
709
+ kubernetes_version: str = "",
710
+ values_file: str = "",
711
+ set_values: str = ""
712
+ ) -> str:
713
+ """Upgrade a vCluster instance.
714
+
715
+ Args:
716
+ name: Name of the vCluster to upgrade
717
+ namespace: Namespace of the vCluster
718
+ kubernetes_version: New Kubernetes version
719
+ values_file: Path to values.yaml file
720
+ set_values: Comma-separated Helm-style value overrides
721
+ """
722
+ if non_destructive:
723
+ return json.dumps({"success": False, "error": "Operation blocked: non-destructive mode"})
724
+
725
+ values_list = [v.strip() for v in set_values.split(",") if v.strip()] if set_values else None
726
+ return json.dumps(vind_upgrade(name, namespace, kubernetes_version, values_file, values_list), indent=2)
727
+
728
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
729
+ def vind_describe_tool(
730
+ name: str,
731
+ namespace: str = ""
732
+ ) -> str:
733
+ """Describe a vCluster instance with detailed information."""
734
+ return json.dumps(vind_describe(name, namespace), indent=2)
735
+
736
+ @mcp.tool()
737
+ def vind_platform_start_tool(
738
+ host: str = "",
739
+ port: int = 0
740
+ ) -> str:
741
+ """Start the vCluster Platform UI."""
742
+ if non_destructive:
743
+ return json.dumps({"success": False, "error": "Operation blocked: non-destructive mode"})
744
+ return json.dumps(vind_platform_start(host, port), indent=2)