vibops-mcp 0.1.0__tar.gz

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,15 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .env
4
+ .venv/
5
+ venv/
6
+ *.egg-info/
7
+ .DS_Store
8
+ agent/data/
9
+ dist/
10
+ .connect-env
11
+ charts/*/charts/
12
+ charts/*/*.tgz
13
+
14
+ # Licence key generator — contains RSA private key, vendor-only
15
+ scripts/gen_licence.py
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VibOps
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: vibops-mcp
3
+ Version: 0.1.0
4
+ Summary: VibOps MCP server — control your GPU infrastructure from Claude Desktop, Cursor, or any MCP client
5
+ Project-URL: Homepage, https://vibops.io
6
+ Project-URL: Repository, https://github.com/vibops/vibops-mcp
7
+ Project-URL: Issues, https://github.com/vibops/vibops-mcp/issues
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: ai-infrastructure,gpu,kubernetes,mcp,vibops
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: System :: Systems Administration
16
+ Requires-Python: >=3.11
17
+ Requires-Dist: httpx>=0.27.0
18
+ Requires-Dist: mcp[cli]>=1.0.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # vibops-mcp
22
+
23
+ Control your GPU infrastructure with Claude, Cursor, or any MCP-compatible AI assistant.
24
+
25
+ ## What it does
26
+
27
+ `vibops-mcp` exposes 26 tools that let an AI assistant:
28
+
29
+ - **Observe** — GPU utilisation, workload breakdown, MTTR, cost estimates, live K8s deployments
30
+ - **Act** — deploy models, scale clusters, run Helm/kubectl, trigger pipelines
31
+ - **Configure** — set cost rates, manage gateways, store secrets
32
+
33
+ All operations go through your VibOps instance and are recorded in the audit log.
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install vibops-mcp
39
+ ```
40
+
41
+ ## Configuration
42
+
43
+ You need two environment variables:
44
+
45
+ | Variable | Description |
46
+ |----------|-------------|
47
+ | `VIBOPS_URL` | Base URL of your VibOps instance, e.g. `https://vibops.example.com` |
48
+ | `VIBOPS_TOKEN` | API token — create one in VibOps → Settings → API Tokens |
49
+
50
+ ## Claude Desktop
51
+
52
+ Add to `~/.config/claude/claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`):
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "vibops": {
58
+ "command": "vibops-mcp",
59
+ "env": {
60
+ "VIBOPS_URL": "https://vibops.example.com",
61
+ "VIBOPS_TOKEN": "your-token-here"
62
+ }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ ## Cursor
69
+
70
+ Add to `.cursor/mcp.json` in your project root, or to the global config:
71
+
72
+ ```json
73
+ {
74
+ "mcpServers": {
75
+ "vibops": {
76
+ "command": "vibops-mcp",
77
+ "env": {
78
+ "VIBOPS_URL": "https://vibops.example.com",
79
+ "VIBOPS_TOKEN": "your-token-here"
80
+ }
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ ## Claude Code (CLI)
87
+
88
+ ```bash
89
+ claude mcp add vibops vibops-mcp \
90
+ -e VIBOPS_URL=https://vibops.example.com \
91
+ -e VIBOPS_TOKEN=your-token-here
92
+ ```
93
+
94
+ ## Available tools
95
+
96
+ ### Observation (read-only)
97
+
98
+ | Tool | Description |
99
+ |------|-------------|
100
+ | `list_clusters` | List clusters and GPU utilisation |
101
+ | `list_kubectl_contexts` | List available kubectl contexts |
102
+ | `get_cluster_deployments` | Live K8s deployment status for a cluster |
103
+ | `get_cluster_rate` | Get configured GPU cost rate for a cluster |
104
+ | `list_jobs` | List recent jobs with optional filters |
105
+ | `get_job` | Get job details and result |
106
+ | `get_gpu_metrics` | Hourly GPU utilisation time-series |
107
+ | `get_workload_breakdown` | Job count by workload type |
108
+ | `get_mttr` | Mean Time To Resolve GPU alerts |
109
+ | `get_cost_estimate` | Estimated GPU spend |
110
+ | `list_gateways` | List registered gateways and status |
111
+ | `list_alerts` | List GPU alerts (open or resolved) |
112
+ | `list_secrets` | List secrets (names only, never values) |
113
+ | `list_providers` | List configured AI/GPU cloud providers |
114
+ | `list_pipelines` | List automation pipelines |
115
+
116
+ ### Actions (write)
117
+
118
+ | Tool | Description |
119
+ |------|-------------|
120
+ | `scale_cluster` | Scale a K8s deployment or node pool |
121
+ | `deploy_model` | Deploy an AI model onto a GPU cluster |
122
+ | `helm_upgrade` | Run helm upgrade --install |
123
+ | `helm_uninstall` | Uninstall a Helm release |
124
+ | `run_kubectl` | Run an arbitrary kubectl command |
125
+ | `git_clone` | Clone a git repository |
126
+ | `create_secret` | Store an encrypted secret |
127
+ | `trigger_pipeline` | Manually trigger an automation pipeline |
128
+
129
+ ### Configuration
130
+
131
+ | Tool | Description |
132
+ |------|-------------|
133
+ | `set_cluster_rate` | Set GPU cost rate for a cluster (admin only) |
134
+ | `register_gateway` | Register a new gateway (returns one-time token) |
135
+ | `delete_gateway` | Revoke a gateway |
136
+
137
+ ## Example prompts
138
+
139
+ ```
140
+ "What's our GPU utilisation trend over the last 7 days?"
141
+ "Show me the cost breakdown per cluster this week."
142
+ "Deploy llama3:8b on vibops-dev with 2 replicas."
143
+ "Which clusters have open critical GPU alerts?"
144
+ "Scale the inference namespace to 4 replicas on prod-cluster."
145
+ "What's our MTTR for critical alerts?"
146
+ ```
147
+
148
+ ## License
149
+
150
+ MIT — free to use, modify, and distribute. See [LICENSE](LICENSE).
151
+
152
+ Built on [FastMCP](https://github.com/anthropics/mcp) and the [VibOps](https://vibops.io) platform.
@@ -0,0 +1,132 @@
1
+ # vibops-mcp
2
+
3
+ Control your GPU infrastructure with Claude, Cursor, or any MCP-compatible AI assistant.
4
+
5
+ ## What it does
6
+
7
+ `vibops-mcp` exposes 26 tools that let an AI assistant:
8
+
9
+ - **Observe** — GPU utilisation, workload breakdown, MTTR, cost estimates, live K8s deployments
10
+ - **Act** — deploy models, scale clusters, run Helm/kubectl, trigger pipelines
11
+ - **Configure** — set cost rates, manage gateways, store secrets
12
+
13
+ All operations go through your VibOps instance and are recorded in the audit log.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install vibops-mcp
19
+ ```
20
+
21
+ ## Configuration
22
+
23
+ You need two environment variables:
24
+
25
+ | Variable | Description |
26
+ |----------|-------------|
27
+ | `VIBOPS_URL` | Base URL of your VibOps instance, e.g. `https://vibops.example.com` |
28
+ | `VIBOPS_TOKEN` | API token — create one in VibOps → Settings → API Tokens |
29
+
30
+ ## Claude Desktop
31
+
32
+ Add to `~/.config/claude/claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`):
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "vibops": {
38
+ "command": "vibops-mcp",
39
+ "env": {
40
+ "VIBOPS_URL": "https://vibops.example.com",
41
+ "VIBOPS_TOKEN": "your-token-here"
42
+ }
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ ## Cursor
49
+
50
+ Add to `.cursor/mcp.json` in your project root, or to the global config:
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "vibops": {
56
+ "command": "vibops-mcp",
57
+ "env": {
58
+ "VIBOPS_URL": "https://vibops.example.com",
59
+ "VIBOPS_TOKEN": "your-token-here"
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ## Claude Code (CLI)
67
+
68
+ ```bash
69
+ claude mcp add vibops vibops-mcp \
70
+ -e VIBOPS_URL=https://vibops.example.com \
71
+ -e VIBOPS_TOKEN=your-token-here
72
+ ```
73
+
74
+ ## Available tools
75
+
76
+ ### Observation (read-only)
77
+
78
+ | Tool | Description |
79
+ |------|-------------|
80
+ | `list_clusters` | List clusters and GPU utilisation |
81
+ | `list_kubectl_contexts` | List available kubectl contexts |
82
+ | `get_cluster_deployments` | Live K8s deployment status for a cluster |
83
+ | `get_cluster_rate` | Get configured GPU cost rate for a cluster |
84
+ | `list_jobs` | List recent jobs with optional filters |
85
+ | `get_job` | Get job details and result |
86
+ | `get_gpu_metrics` | Hourly GPU utilisation time-series |
87
+ | `get_workload_breakdown` | Job count by workload type |
88
+ | `get_mttr` | Mean Time To Resolve GPU alerts |
89
+ | `get_cost_estimate` | Estimated GPU spend |
90
+ | `list_gateways` | List registered gateways and status |
91
+ | `list_alerts` | List GPU alerts (open or resolved) |
92
+ | `list_secrets` | List secrets (names only, never values) |
93
+ | `list_providers` | List configured AI/GPU cloud providers |
94
+ | `list_pipelines` | List automation pipelines |
95
+
96
+ ### Actions (write)
97
+
98
+ | Tool | Description |
99
+ |------|-------------|
100
+ | `scale_cluster` | Scale a K8s deployment or node pool |
101
+ | `deploy_model` | Deploy an AI model onto a GPU cluster |
102
+ | `helm_upgrade` | Run helm upgrade --install |
103
+ | `helm_uninstall` | Uninstall a Helm release |
104
+ | `run_kubectl` | Run an arbitrary kubectl command |
105
+ | `git_clone` | Clone a git repository |
106
+ | `create_secret` | Store an encrypted secret |
107
+ | `trigger_pipeline` | Manually trigger an automation pipeline |
108
+
109
+ ### Configuration
110
+
111
+ | Tool | Description |
112
+ |------|-------------|
113
+ | `set_cluster_rate` | Set GPU cost rate for a cluster (admin only) |
114
+ | `register_gateway` | Register a new gateway (returns one-time token) |
115
+ | `delete_gateway` | Revoke a gateway |
116
+
117
+ ## Example prompts
118
+
119
+ ```
120
+ "What's our GPU utilisation trend over the last 7 days?"
121
+ "Show me the cost breakdown per cluster this week."
122
+ "Deploy llama3:8b on vibops-dev with 2 replicas."
123
+ "Which clusters have open critical GPU alerts?"
124
+ "Scale the inference namespace to 4 replicas on prod-cluster."
125
+ "What's our MTTR for critical alerts?"
126
+ ```
127
+
128
+ ## License
129
+
130
+ MIT — free to use, modify, and distribute. See [LICENSE](LICENSE).
131
+
132
+ Built on [FastMCP](https://github.com/anthropics/mcp) and the [VibOps](https://vibops.io) platform.
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "vibops-mcp"
7
+ version = "0.1.0"
8
+ description = "VibOps MCP server — control your GPU infrastructure from Claude Desktop, Cursor, or any MCP client"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.11"
12
+ keywords = ["mcp", "gpu", "kubernetes", "ai-infrastructure", "vibops"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Topic :: System :: Systems Administration",
19
+ ]
20
+ dependencies = [
21
+ "mcp[cli]>=1.0.0",
22
+ "httpx>=0.27.0",
23
+ ]
24
+
25
+ [project.scripts]
26
+ vibops-mcp = "vibops_mcp.server:main"
27
+
28
+ [project.urls]
29
+ Homepage = "https://vibops.io"
30
+ Repository = "https://github.com/vibops/vibops-mcp"
31
+ Issues = "https://github.com/vibops/vibops-mcp/issues"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["vibops_mcp"]
@@ -0,0 +1,2 @@
1
+ """VibOps MCP server — Apache 2.0"""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,60 @@
1
+ """
2
+ Async HTTP client wrapping the VibOps REST API.
3
+
4
+ Configuration (env vars):
5
+ VIBOPS_URL — base URL of your VibOps instance, e.g. https://vibops.example.com
6
+ VIBOPS_TOKEN — API token (create one in VibOps → Settings → API Tokens)
7
+ """
8
+ import os
9
+ import httpx
10
+
11
+ _TIMEOUT = 60.0
12
+
13
+
14
+ def _base_url() -> str:
15
+ url = os.environ.get("VIBOPS_URL", "").rstrip("/")
16
+ if not url:
17
+ raise RuntimeError(
18
+ "VIBOPS_URL environment variable is not set. "
19
+ "Set it to your VibOps instance URL, e.g. https://vibops.example.com"
20
+ )
21
+ return url
22
+
23
+
24
+ def _token() -> str:
25
+ tok = os.environ.get("VIBOPS_TOKEN", "")
26
+ if not tok:
27
+ raise RuntimeError(
28
+ "VIBOPS_TOKEN environment variable is not set. "
29
+ "Create an API token in VibOps → Settings → API Tokens."
30
+ )
31
+ return tok
32
+
33
+
34
+ def _client() -> httpx.AsyncClient:
35
+ return httpx.AsyncClient(
36
+ base_url=_base_url(),
37
+ headers={"Authorization": f"Bearer {_token()}"},
38
+ timeout=_TIMEOUT,
39
+ )
40
+
41
+
42
+ async def get(path: str, params: dict | None = None) -> dict:
43
+ async with _client() as c:
44
+ r = await c.get(path, params=params)
45
+ r.raise_for_status()
46
+ return r.json()
47
+
48
+
49
+ async def post(path: str, body: dict | None = None) -> dict:
50
+ async with _client() as c:
51
+ r = await c.post(path, json=body or {})
52
+ r.raise_for_status()
53
+ return r.json()
54
+
55
+
56
+ async def delete(path: str) -> dict:
57
+ async with _client() as c:
58
+ r = await c.delete(path)
59
+ r.raise_for_status()
60
+ return r.json()
@@ -0,0 +1,362 @@
1
+ """
2
+ VibOps MCP server — entry point.
3
+
4
+ Run via:
5
+ vibops-mcp (after pip install vibops-mcp)
6
+ python -m vibops_mcp (from source)
7
+
8
+ Required environment variables:
9
+ VIBOPS_URL — base URL of your VibOps instance
10
+ VIBOPS_TOKEN — API token (VibOps → Settings → API Tokens)
11
+
12
+ Claude Desktop configuration (~/.config/claude/claude_desktop_config.json):
13
+ {
14
+ "mcpServers": {
15
+ "vibops": {
16
+ "command": "vibops-mcp",
17
+ "env": {
18
+ "VIBOPS_URL": "https://vibops.example.com",
19
+ "VIBOPS_TOKEN": "your-token-here"
20
+ }
21
+ }
22
+ }
23
+ }
24
+ """
25
+ from mcp.server.fastmcp import FastMCP
26
+
27
+ from vibops_mcp.tools import observation, actions, config
28
+
29
+ mcp = FastMCP(
30
+ name="vibops",
31
+ instructions=(
32
+ "You are connected to a VibOps instance that manages GPU infrastructure. "
33
+ "Use the observation tools to inspect cluster state, GPU metrics, jobs, and cost. "
34
+ "Use the action tools to deploy models, scale workloads, and run Helm or kubectl operations. "
35
+ "Use the configuration tools to set cost rates and manage gateways. "
36
+ "All write operations are recorded in the VibOps audit log."
37
+ ),
38
+ )
39
+
40
+
41
+ # ── Observation tools (15) ─────────────────────────────────────────────────────
42
+
43
+ @mcp.tool()
44
+ async def list_clusters() -> dict:
45
+ """List all Kubernetes clusters and their GPU utilisation."""
46
+ return await observation.list_clusters()
47
+
48
+
49
+ @mcp.tool()
50
+ async def get_cluster_deployments(cluster_name: str, namespace: str | None = None) -> dict:
51
+ """
52
+ Return live Kubernetes deployment status for a cluster.
53
+ Polls the cluster via the VibOps gateway (up to 20 s).
54
+
55
+ Args:
56
+ cluster_name: Name of the kubectl context / Kind cluster.
57
+ namespace: Filter to a specific namespace (optional).
58
+ """
59
+ return await observation.get_cluster_deployments(cluster_name, namespace)
60
+
61
+
62
+ @mcp.tool()
63
+ async def list_jobs(
64
+ status: str | None = None,
65
+ action: str | None = None,
66
+ limit: int = 20,
67
+ ) -> dict:
68
+ """
69
+ List recent jobs (operations executed on the infrastructure).
70
+
71
+ Args:
72
+ status: Filter by status — pending | running | success | failed.
73
+ action: Filter by action name, e.g. scale_cluster, deploy_model.
74
+ limit: Maximum number of jobs to return (default 20, max 100).
75
+ """
76
+ return await observation.list_jobs(status, action, limit)
77
+
78
+
79
+ @mcp.tool()
80
+ async def get_job(job_id: str) -> dict:
81
+ """
82
+ Get the details and result of a specific job.
83
+
84
+ Args:
85
+ job_id: UUID of the job.
86
+ """
87
+ return await observation.get_job(job_id)
88
+
89
+
90
+ @mcp.tool()
91
+ async def get_gpu_metrics(hours: int = 24) -> dict:
92
+ """
93
+ Return hourly GPU utilisation time-series for the last N hours.
94
+
95
+ Args:
96
+ hours: Look-back window in hours (default 24, max 168).
97
+ """
98
+ return await observation.get_gpu_metrics(hours)
99
+
100
+
101
+ @mcp.tool()
102
+ async def get_workload_breakdown(hours: int = 24) -> dict:
103
+ """
104
+ Return the breakdown of jobs by workload type for the last N hours.
105
+ Types: inference | training | observation | operations | gitops | maintenance | other.
106
+
107
+ Args:
108
+ hours: Look-back window in hours (default 24).
109
+ """
110
+ return await observation.get_workload_breakdown(hours)
111
+
112
+
113
+ @mcp.tool()
114
+ async def get_mttr(hours: int = 168) -> dict:
115
+ """
116
+ Return Mean Time To Resolve GPU alerts per cluster and severity.
117
+
118
+ Args:
119
+ hours: Look-back window in hours (default 168 = 7 days).
120
+ """
121
+ return await observation.get_mttr(hours)
122
+
123
+
124
+ @mcp.tool()
125
+ async def get_cost_estimate(hours: int = 24) -> dict:
126
+ """
127
+ Return estimated GPU spend for the last N hours.
128
+ Requires GPU cost rates to be configured per cluster (use set_cluster_rate).
129
+
130
+ Args:
131
+ hours: Look-back window in hours (default 24).
132
+ """
133
+ return await observation.get_cost_estimate(hours)
134
+
135
+
136
+ @mcp.tool()
137
+ async def list_gateways() -> dict:
138
+ """List all registered VibOps gateways (remote agents) and their status."""
139
+ return await observation.list_gateways()
140
+
141
+
142
+ @mcp.tool()
143
+ async def list_alerts(severity: str | None = None, resolved: bool | None = None) -> dict:
144
+ """
145
+ List GPU alerts.
146
+
147
+ Args:
148
+ severity: Filter by severity — warning | critical.
149
+ resolved: True for resolved alerts, False for open alerts.
150
+ """
151
+ return await observation.list_alerts(severity, resolved)
152
+
153
+
154
+ @mcp.tool()
155
+ async def list_secrets(search: str | None = None) -> dict:
156
+ """
157
+ List available secrets (names and metadata only, never values).
158
+
159
+ Args:
160
+ search: Filter secrets by name (optional).
161
+ """
162
+ return await observation.list_secrets(search)
163
+
164
+
165
+ @mcp.tool()
166
+ async def list_providers() -> dict:
167
+ """List configured custom AI/GPU cloud providers."""
168
+ return await observation.list_providers()
169
+
170
+
171
+ @mcp.tool()
172
+ async def list_pipelines() -> dict:
173
+ """List automation pipelines (sequences of jobs)."""
174
+ return await observation.list_pipelines()
175
+
176
+
177
+ @mcp.tool()
178
+ async def get_cluster_rate(cluster_name: str) -> dict:
179
+ """
180
+ Get the configured GPU cost rate for a cluster.
181
+
182
+ Args:
183
+ cluster_name: Name of the cluster.
184
+ """
185
+ return await observation.get_cluster_rate(cluster_name)
186
+
187
+
188
+ @mcp.tool()
189
+ async def list_kubectl_contexts() -> dict:
190
+ """List available kubectl contexts from the kubeconfig."""
191
+ return await observation.list_kubectl_contexts()
192
+
193
+
194
+ # ── Action tools (8) ──────────────────────────────────────────────────────────
195
+
196
+ @mcp.tool()
197
+ async def scale_cluster(cluster_name: str, replicas: int, namespace: str | None = None) -> dict:
198
+ """
199
+ Scale a Kubernetes deployment or node pool.
200
+
201
+ Args:
202
+ cluster_name: Target cluster name.
203
+ replicas: Desired replica count.
204
+ namespace: Kubernetes namespace (optional).
205
+ """
206
+ return await actions.scale_cluster(cluster_name, replicas, namespace)
207
+
208
+
209
+ @mcp.tool()
210
+ async def deploy_model(
211
+ cluster_name: str,
212
+ model_name: str,
213
+ namespace: str | None = None,
214
+ replicas: int = 1,
215
+ extra: dict | None = None,
216
+ ) -> dict:
217
+ """
218
+ Deploy an AI model onto a GPU cluster.
219
+
220
+ Args:
221
+ cluster_name: Target cluster name.
222
+ model_name: Model identifier (e.g. llama3:8b, mistral:7b).
223
+ namespace: Kubernetes namespace (optional).
224
+ replicas: Number of replicas (default 1).
225
+ extra: Additional deployment parameters (image, resources, env vars…).
226
+ """
227
+ return await actions.deploy_model(cluster_name, model_name, namespace, replicas, extra)
228
+
229
+
230
+ @mcp.tool()
231
+ async def helm_upgrade(
232
+ cluster_name: str,
233
+ release_name: str,
234
+ chart: str,
235
+ namespace: str = "default",
236
+ values: dict | None = None,
237
+ ) -> dict:
238
+ """
239
+ Run helm upgrade --install on a cluster.
240
+
241
+ Args:
242
+ cluster_name: Target cluster.
243
+ release_name: Helm release name.
244
+ chart: Helm chart reference (e.g. bitnami/nginx or ./charts/myapp).
245
+ namespace: Kubernetes namespace (default: 'default').
246
+ values: Helm values to override (dict, optional).
247
+ """
248
+ return await actions.helm_upgrade(cluster_name, release_name, chart, namespace, values)
249
+
250
+
251
+ @mcp.tool()
252
+ async def helm_uninstall(cluster_name: str, release_name: str, namespace: str = "default") -> dict:
253
+ """
254
+ Uninstall a Helm release from a cluster.
255
+
256
+ Args:
257
+ cluster_name: Target cluster.
258
+ release_name: Helm release to remove.
259
+ namespace: Kubernetes namespace (default: 'default').
260
+ """
261
+ return await actions.helm_uninstall(cluster_name, release_name, namespace)
262
+
263
+
264
+ @mcp.tool()
265
+ async def run_kubectl(cluster_name: str, command: list[str]) -> dict:
266
+ """
267
+ Run an arbitrary kubectl command on a cluster.
268
+ Example command: ["get", "pods", "-n", "default"]
269
+
270
+ Args:
271
+ cluster_name: Target cluster.
272
+ command: kubectl arguments as a list (without 'kubectl' prefix).
273
+ """
274
+ return await actions.run_kubectl(cluster_name, command)
275
+
276
+
277
+ @mcp.tool()
278
+ async def git_clone(repo_url: str, branch: str = "main", cluster_name: str | None = None) -> dict:
279
+ """
280
+ Clone a git repository (pull manifests or model configs).
281
+
282
+ Args:
283
+ repo_url: Git repository URL.
284
+ branch: Branch to clone (default: main).
285
+ cluster_name: Target cluster if repo contains K8s manifests to apply.
286
+ """
287
+ return await actions.git_clone(repo_url, branch, cluster_name)
288
+
289
+
290
+ @mcp.tool()
291
+ async def create_secret(name: str, value: str, description: str | None = None) -> dict:
292
+ """
293
+ Store an encrypted secret in the VibOps vault.
294
+
295
+ Args:
296
+ name: Secret name (used to reference it in jobs).
297
+ value: Secret value (stored encrypted, never logged).
298
+ description: Optional human-readable description.
299
+ """
300
+ return await actions.create_secret(name, value, description)
301
+
302
+
303
+ @mcp.tool()
304
+ async def trigger_pipeline(pipeline_id: str, payload: dict | None = None) -> dict:
305
+ """
306
+ Manually trigger an automation pipeline.
307
+
308
+ Args:
309
+ pipeline_id: UUID of the pipeline to trigger.
310
+ payload: Optional input payload for the pipeline.
311
+ """
312
+ return await actions.trigger_pipeline(pipeline_id, payload)
313
+
314
+
315
+ # ── Configuration tools (3) ───────────────────────────────────────────────────
316
+
317
+ @mcp.tool()
318
+ async def set_cluster_rate(cluster_name: str, rate_per_gpu_hour: float, currency: str = "USD") -> dict:
319
+ """
320
+ Set the GPU cost rate for a cluster (organisation admin only).
321
+
322
+ Args:
323
+ cluster_name: Name of the cluster.
324
+ rate_per_gpu_hour: Cost per GPU per hour (e.g. 2.50).
325
+ currency: Currency code (default: USD).
326
+ """
327
+ return await config.set_cluster_rate(cluster_name, rate_per_gpu_hour, currency)
328
+
329
+
330
+ @mcp.tool()
331
+ async def register_gateway(name: str, description: str | None = None, clusters: list[str] | None = None) -> dict:
332
+ """
333
+ Register a new VibOps gateway (remote agent).
334
+ Returns a one-time token to configure the gateway with.
335
+
336
+ Args:
337
+ name: Human-readable name for the gateway.
338
+ description: Optional description (location, purpose…).
339
+ clusters: List of cluster names this gateway manages.
340
+ """
341
+ return await config.register_gateway(name, description, clusters)
342
+
343
+
344
+ @mcp.tool()
345
+ async def delete_gateway(gateway_id: str) -> dict:
346
+ """
347
+ Revoke a VibOps gateway and its token.
348
+
349
+ Args:
350
+ gateway_id: UUID of the gateway to delete.
351
+ """
352
+ return await config.delete_gateway(gateway_id)
353
+
354
+
355
+ # ── Entry point ───────────────────────────────────────────────────────────────
356
+
357
+ def main():
358
+ mcp.run(transport="stdio")
359
+
360
+
361
+ if __name__ == "__main__":
362
+ main()
@@ -0,0 +1 @@
1
+ # tools package
@@ -0,0 +1,157 @@
1
+ """
2
+ Action tools (write operations) — 8 tools.
3
+
4
+ These tools trigger infrastructure changes via VibOps jobs.
5
+ All operations are recorded in the audit log.
6
+ """
7
+ from vibops_mcp import client
8
+
9
+
10
+ async def _run_job(action: str, payload: dict) -> dict:
11
+ """Submit a job and return its initial state (async — poll with get_job)."""
12
+ return await client.post("/api/v1/jobs", body={"action": action, "payload": payload})
13
+
14
+
15
+ async def scale_cluster(cluster_name: str, replicas: int, namespace: str | None = None) -> dict:
16
+ """
17
+ Scale a Kubernetes deployment or node pool.
18
+
19
+ Args:
20
+ cluster_name: Target cluster name.
21
+ replicas: Desired replica count.
22
+ namespace: Kubernetes namespace (optional, defaults to 'default').
23
+ """
24
+ payload: dict = {"cluster": cluster_name, "replicas": replicas}
25
+ if namespace:
26
+ payload["namespace"] = namespace
27
+ return await _run_job("scale_cluster", payload)
28
+
29
+
30
+ async def deploy_model(
31
+ cluster_name: str,
32
+ model_name: str,
33
+ namespace: str | None = None,
34
+ replicas: int = 1,
35
+ extra: dict | None = None,
36
+ ) -> dict:
37
+ """
38
+ Deploy an AI model onto a GPU cluster.
39
+
40
+ Args:
41
+ cluster_name: Target cluster name.
42
+ model_name: Model identifier (e.g. llama3:8b, mistral:7b).
43
+ namespace: Kubernetes namespace (optional).
44
+ replicas: Number of replicas (default 1).
45
+ extra: Additional deployment parameters (image, resources, env vars…).
46
+ """
47
+ payload: dict = {
48
+ "cluster": cluster_name,
49
+ "model": model_name,
50
+ "replicas": replicas,
51
+ }
52
+ if namespace:
53
+ payload["namespace"] = namespace
54
+ if extra:
55
+ payload.update(extra)
56
+ return await _run_job("deploy_model", payload)
57
+
58
+
59
+ async def helm_upgrade(
60
+ cluster_name: str,
61
+ release_name: str,
62
+ chart: str,
63
+ namespace: str = "default",
64
+ values: dict | None = None,
65
+ ) -> dict:
66
+ """
67
+ Run helm upgrade --install on a cluster.
68
+
69
+ Args:
70
+ cluster_name: Target cluster.
71
+ release_name: Helm release name.
72
+ chart: Helm chart reference (e.g. bitnami/nginx or ./charts/myapp).
73
+ namespace: Kubernetes namespace (default: 'default').
74
+ values: Helm values to override (dict, optional).
75
+ """
76
+ payload: dict = {
77
+ "cluster": cluster_name,
78
+ "release": release_name,
79
+ "chart": chart,
80
+ "namespace": namespace,
81
+ }
82
+ if values:
83
+ payload["values"] = values
84
+ return await _run_job("helm_upgrade", payload)
85
+
86
+
87
+ async def helm_uninstall(cluster_name: str, release_name: str, namespace: str = "default") -> dict:
88
+ """
89
+ Uninstall a Helm release from a cluster.
90
+
91
+ Args:
92
+ cluster_name: Target cluster.
93
+ release_name: Helm release to remove.
94
+ namespace: Kubernetes namespace (default: 'default').
95
+ """
96
+ return await _run_job("helm_uninstall", {
97
+ "cluster": cluster_name,
98
+ "release": release_name,
99
+ "namespace": namespace,
100
+ })
101
+
102
+
103
+ async def run_kubectl(cluster_name: str, command: list[str]) -> dict:
104
+ """
105
+ Run an arbitrary kubectl command on a cluster.
106
+ The command is a list of arguments (without 'kubectl' prefix).
107
+ Example: ["get", "pods", "-n", "default"]
108
+
109
+ Args:
110
+ cluster_name: Target cluster.
111
+ command: kubectl arguments as a list.
112
+ """
113
+ return await _run_job("kubectl_exec", {
114
+ "cluster": cluster_name,
115
+ "command": command,
116
+ })
117
+
118
+
119
+ async def git_clone(repo_url: str, branch: str = "main", cluster_name: str | None = None) -> dict:
120
+ """
121
+ Clone a git repository (used to pull manifests or model configs).
122
+
123
+ Args:
124
+ repo_url: Git repository URL.
125
+ branch: Branch to clone (default: main).
126
+ cluster_name: Target cluster if repo contains K8s manifests to apply.
127
+ """
128
+ payload: dict = {"repo": repo_url, "branch": branch}
129
+ if cluster_name:
130
+ payload["cluster"] = cluster_name
131
+ return await _run_job("git_clone", payload)
132
+
133
+
134
+ async def create_secret(name: str, value: str, description: str | None = None) -> dict:
135
+ """
136
+ Store an encrypted secret in the VibOps vault.
137
+
138
+ Args:
139
+ name: Secret name (used to reference it in jobs).
140
+ value: Secret value (stored encrypted, never logged).
141
+ description: Optional human-readable description.
142
+ """
143
+ body: dict = {"name": name, "value": value}
144
+ if description:
145
+ body["description"] = description
146
+ return await client.post("/api/v1/secrets", body=body)
147
+
148
+
149
+ async def trigger_pipeline(pipeline_id: str, payload: dict | None = None) -> dict:
150
+ """
151
+ Manually trigger an automation pipeline.
152
+
153
+ Args:
154
+ pipeline_id: UUID of the pipeline to trigger.
155
+ payload: Optional input payload for the pipeline.
156
+ """
157
+ return await client.post(f"/api/v1/pipelines/{pipeline_id}/trigger", body=payload or {})
@@ -0,0 +1,48 @@
1
+ """
2
+ Configuration tools — 3 tools.
3
+
4
+ These tools let the LLM read and update VibOps configuration.
5
+ """
6
+ from vibops_mcp import client
7
+
8
+
9
+ async def set_cluster_rate(cluster_name: str, rate_per_gpu_hour: float, currency: str = "USD") -> dict:
10
+ """
11
+ Set the GPU cost rate for a cluster (organisation admin only).
12
+ Used to calculate cost estimates in get_cost_estimate.
13
+
14
+ Args:
15
+ cluster_name: Name of the cluster.
16
+ rate_per_gpu_hour: Cost per GPU per hour (e.g. 2.50).
17
+ currency: Currency code (default: USD).
18
+ """
19
+ return await client.post(f"/api/v1/clusters/{cluster_name}/rate", body={
20
+ "rate_per_gpu_hour": rate_per_gpu_hour,
21
+ "currency": currency.upper(),
22
+ })
23
+
24
+
25
+ async def register_gateway(name: str, description: str | None = None, clusters: list[str] | None = None) -> dict:
26
+ """
27
+ Register a new VibOps gateway (remote agent).
28
+ Returns a one-time token to configure the gateway with.
29
+
30
+ Args:
31
+ name: Human-readable name for the gateway.
32
+ description: Optional description (location, purpose…).
33
+ clusters: List of cluster names this gateway manages.
34
+ """
35
+ body: dict = {"name": name, "clusters": clusters or []}
36
+ if description:
37
+ body["description"] = description
38
+ return await client.post("/api/v1/gateways", body=body)
39
+
40
+
41
+ async def delete_gateway(gateway_id: str) -> dict:
42
+ """
43
+ Revoke a VibOps gateway and its token.
44
+
45
+ Args:
46
+ gateway_id: UUID of the gateway to delete.
47
+ """
48
+ return await client.delete(f"/api/v1/gateways/{gateway_id}")
@@ -0,0 +1,158 @@
1
+ """
2
+ Observation tools (read-only) — 15 tools.
3
+
4
+ These tools let the LLM inspect the state of the infrastructure
5
+ without triggering any side effects.
6
+ """
7
+ from vibops_mcp import client
8
+
9
+
10
+ async def list_clusters() -> dict:
11
+ """List all Kubernetes clusters and their GPU utilisation."""
12
+ return await client.get("/api/v1/gateways")
13
+
14
+
15
+ async def get_cluster_deployments(cluster_name: str, namespace: str | None = None) -> dict:
16
+ """
17
+ Return live Kubernetes deployment status for a cluster.
18
+ Polls the cluster via the VibOps gateway (up to 20 s).
19
+
20
+ Args:
21
+ cluster_name: Name of the kubectl context / Kind cluster.
22
+ namespace: Filter to a specific namespace (optional).
23
+ """
24
+ params = {}
25
+ if namespace:
26
+ params["namespace"] = namespace
27
+ return await client.get(f"/api/v1/clusters/{cluster_name}/deployments", params=params)
28
+
29
+
30
+ async def list_jobs(
31
+ status: str | None = None,
32
+ action: str | None = None,
33
+ limit: int = 20,
34
+ ) -> dict:
35
+ """
36
+ List recent jobs (operations executed on the infrastructure).
37
+
38
+ Args:
39
+ status: Filter by status — pending | running | success | failed.
40
+ action: Filter by action name, e.g. scale_cluster, deploy_model.
41
+ limit: Maximum number of jobs to return (default 20, max 100).
42
+ """
43
+ params: dict = {"limit": min(limit, 100)}
44
+ if status:
45
+ params["status"] = status
46
+ if action:
47
+ params["action"] = action
48
+ return await client.get("/api/v1/jobs", params=params)
49
+
50
+
51
+ async def get_job(job_id: str) -> dict:
52
+ """
53
+ Get the details and result of a specific job.
54
+
55
+ Args:
56
+ job_id: UUID of the job.
57
+ """
58
+ return await client.get(f"/api/v1/jobs/{job_id}")
59
+
60
+
61
+ async def get_gpu_metrics(hours: int = 24) -> dict:
62
+ """
63
+ Return hourly GPU utilisation time-series for the last N hours.
64
+ Aggregated across all clusters for the organisation.
65
+
66
+ Args:
67
+ hours: Look-back window in hours (default 24, max 168).
68
+ """
69
+ return await client.get("/api/v1/metrics/gpu", params={"hours": hours})
70
+
71
+
72
+ async def get_workload_breakdown(hours: int = 24) -> dict:
73
+ """
74
+ Return the breakdown of jobs by workload type for the last N hours.
75
+ Types: inference | training | observation | operations | gitops | maintenance | other.
76
+
77
+ Args:
78
+ hours: Look-back window in hours (default 24, max 168).
79
+ """
80
+ return await client.get("/api/v1/metrics/workloads", params={"hours": hours})
81
+
82
+
83
+ async def get_mttr(hours: int = 168) -> dict:
84
+ """
85
+ Return Mean Time To Resolve GPU alerts per cluster and severity.
86
+
87
+ Args:
88
+ hours: Look-back window in hours (default 168 = 7 days).
89
+ """
90
+ return await client.get("/api/v1/metrics/mttr", params={"hours": hours})
91
+
92
+
93
+ async def get_cost_estimate(hours: int = 24) -> dict:
94
+ """
95
+ Return estimated GPU spend for the last N hours.
96
+ Requires GPU cost rates to be configured per cluster.
97
+
98
+ Args:
99
+ hours: Look-back window in hours (default 24).
100
+ """
101
+ return await client.get("/api/v1/metrics/cost", params={"hours": hours})
102
+
103
+
104
+ async def list_gateways() -> dict:
105
+ """List all registered VibOps gateways (remote agents) and their status."""
106
+ return await client.get("/api/v1/gateways")
107
+
108
+
109
+ async def list_alerts(severity: str | None = None, resolved: bool | None = None) -> dict:
110
+ """
111
+ List GPU alerts.
112
+
113
+ Args:
114
+ severity: Filter by severity — warning | critical.
115
+ resolved: True to show only resolved alerts, False for open alerts.
116
+ """
117
+ params: dict = {}
118
+ if severity:
119
+ params["severity"] = severity
120
+ if resolved is not None:
121
+ params["resolved"] = str(resolved).lower()
122
+ return await client.get("/api/v1/alerts", params=params)
123
+
124
+
125
+ async def list_secrets(search: str | None = None) -> dict:
126
+ """
127
+ List available secrets (names and metadata only, never values).
128
+
129
+ Args:
130
+ search: Filter secrets by name (optional).
131
+ """
132
+ params = {"search": search} if search else {}
133
+ return await client.get("/api/v1/secrets", params=params)
134
+
135
+
136
+ async def list_providers() -> dict:
137
+ """List configured custom AI/GPU cloud providers."""
138
+ return await client.get("/api/v1/providers")
139
+
140
+
141
+ async def list_pipelines() -> dict:
142
+ """List automation pipelines (sequences of jobs)."""
143
+ return await client.get("/api/v1/pipelines")
144
+
145
+
146
+ async def get_cluster_rate(cluster_name: str) -> dict:
147
+ """
148
+ Get the configured GPU cost rate for a cluster.
149
+
150
+ Args:
151
+ cluster_name: Name of the cluster.
152
+ """
153
+ return await client.get(f"/api/v1/clusters/{cluster_name}/rate")
154
+
155
+
156
+ async def list_kubectl_contexts() -> dict:
157
+ """List available kubectl contexts from the kubeconfig."""
158
+ return await client.get("/api/v1/clusters/contexts")