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.
- vibops_mcp-0.1.0/.gitignore +15 -0
- vibops_mcp-0.1.0/LICENSE +21 -0
- vibops_mcp-0.1.0/PKG-INFO +152 -0
- vibops_mcp-0.1.0/README.md +132 -0
- vibops_mcp-0.1.0/pyproject.toml +34 -0
- vibops_mcp-0.1.0/vibops_mcp/__init__.py +2 -0
- vibops_mcp-0.1.0/vibops_mcp/client.py +60 -0
- vibops_mcp-0.1.0/vibops_mcp/server.py +362 -0
- vibops_mcp-0.1.0/vibops_mcp/tools/__init__.py +1 -0
- vibops_mcp-0.1.0/vibops_mcp/tools/actions.py +157 -0
- vibops_mcp-0.1.0/vibops_mcp/tools/config.py +48 -0
- vibops_mcp-0.1.0/vibops_mcp/tools/observation.py +158 -0
vibops_mcp-0.1.0/LICENSE
ADDED
|
@@ -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,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")
|