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.
- {kubectl_mcp_server-1.19.3.dist-info → kubectl_mcp_server-1.21.0.dist-info}/METADATA +26 -18
- {kubectl_mcp_server-1.19.3.dist-info → kubectl_mcp_server-1.21.0.dist-info}/RECORD +13 -9
- kubectl_mcp_tool/__init__.py +1 -1
- kubectl_mcp_tool/mcp_server.py +5 -1
- kubectl_mcp_tool/tools/__init__.py +4 -0
- kubectl_mcp_tool/tools/kind.py +1723 -0
- kubectl_mcp_tool/tools/vind.py +744 -0
- tests/test_kind.py +1206 -0
- tests/test_vind.py +512 -0
- {kubectl_mcp_server-1.19.3.dist-info → kubectl_mcp_server-1.21.0.dist-info}/WHEEL +0 -0
- {kubectl_mcp_server-1.19.3.dist-info → kubectl_mcp_server-1.21.0.dist-info}/entry_points.txt +0 -0
- {kubectl_mcp_server-1.19.3.dist-info → kubectl_mcp_server-1.21.0.dist-info}/licenses/LICENSE +0 -0
- {kubectl_mcp_server-1.19.3.dist-info → kubectl_mcp_server-1.21.0.dist-info}/top_level.txt +0 -0
|
@@ -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)
|