spotgpu-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.
- spotgpu_mcp-0.1.0/PKG-INFO +132 -0
- spotgpu_mcp-0.1.0/README.md +109 -0
- spotgpu_mcp-0.1.0/pyproject.toml +39 -0
- spotgpu_mcp-0.1.0/setup.cfg +4 -0
- spotgpu_mcp-0.1.0/src/spotgpu_mcp/__init__.py +3 -0
- spotgpu_mcp-0.1.0/src/spotgpu_mcp/client.py +140 -0
- spotgpu_mcp-0.1.0/src/spotgpu_mcp/server.py +61 -0
- spotgpu_mcp-0.1.0/src/spotgpu_mcp.egg-info/PKG-INFO +132 -0
- spotgpu_mcp-0.1.0/src/spotgpu_mcp.egg-info/SOURCES.txt +11 -0
- spotgpu_mcp-0.1.0/src/spotgpu_mcp.egg-info/dependency_links.txt +1 -0
- spotgpu_mcp-0.1.0/src/spotgpu_mcp.egg-info/entry_points.txt +2 -0
- spotgpu_mcp-0.1.0/src/spotgpu_mcp.egg-info/requires.txt +2 -0
- spotgpu_mcp-0.1.0/src/spotgpu_mcp.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spotgpu-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SpotGPU MCP server — spot GPU rental prices via GET /v1/spot
|
|
5
|
+
Author: SpotGPU
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://spotgpu-api.fly.dev
|
|
8
|
+
Project-URL: Documentation, https://spotgpu-api.fly.dev/docs
|
|
9
|
+
Keywords: mcp,gpu,spot,pricing,vast,runpod
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: mcp<3,>=2
|
|
22
|
+
Requires-Dist: httpx>=0.27.0
|
|
23
|
+
|
|
24
|
+
# spotgpu-mcp
|
|
25
|
+
|
|
26
|
+
MCP (Model Context Protocol) server for [SpotGPU](https://spotgpu-api.fly.dev) spot GPU rental prices.
|
|
27
|
+
|
|
28
|
+
Exposes one tool: **`spot_price`** → `GET /v1/spot` (Vast + RunPod best + alts).
|
|
29
|
+
|
|
30
|
+
> **Credits ≠ rental.** SpotGPU API credits pay for price lookups.
|
|
31
|
+
> **`usd_per_hr` is the upstream provider rental price**, not a SpotGPU fee.
|
|
32
|
+
|
|
33
|
+
<!-- mcp-name: io.github.flowtestpro-svg/spotgpu-mcp -->
|
|
34
|
+
|
|
35
|
+
## Install / run
|
|
36
|
+
|
|
37
|
+
Requires Python 3.10+ and a SpotGPU API key (`sk_…` from https://spotgpu-api.fly.dev).
|
|
38
|
+
|
|
39
|
+
### One-shot (after PyPI publish)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uvx spotgpu-mcp
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Local (this monorepo package)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd packages/mcp-spotgpu
|
|
49
|
+
uv sync # or: pip install -e .
|
|
50
|
+
export SPOTGPU_API_KEY=sk_…
|
|
51
|
+
spotgpu-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Env:
|
|
55
|
+
|
|
56
|
+
| Variable | Required | Default |
|
|
57
|
+
|----------|----------|---------|
|
|
58
|
+
| `SPOTGPU_API_KEY` | **yes** | — |
|
|
59
|
+
| `SPOTGPU_BASE_URL` | no | `https://spotgpu-api.fly.dev` |
|
|
60
|
+
|
|
61
|
+
Never hardcode keys. The server speaks MCP on **stdio** only (do not print to stdout).
|
|
62
|
+
|
|
63
|
+
## Claude Desktop
|
|
64
|
+
|
|
65
|
+
Add to `claude_desktop_config.json`:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"spotgpu": {
|
|
71
|
+
"command": "uvx",
|
|
72
|
+
"args": ["spotgpu-mcp"],
|
|
73
|
+
"env": {
|
|
74
|
+
"SPOTGPU_API_KEY": "sk_…"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Local editable install instead of `uvx`:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"spotgpu": {
|
|
87
|
+
"command": "uv",
|
|
88
|
+
"args": ["run", "--directory", "/path/to/spotgpu/packages/mcp-spotgpu", "spotgpu-mcp"],
|
|
89
|
+
"env": {
|
|
90
|
+
"SPOTGPU_API_KEY": "sk_…"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Cursor
|
|
98
|
+
|
|
99
|
+
Add to `.cursor/mcp.json` (or user MCP config):
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"mcpServers": {
|
|
104
|
+
"spotgpu": {
|
|
105
|
+
"command": "uvx",
|
|
106
|
+
"args": ["spotgpu-mcp"],
|
|
107
|
+
"env": {
|
|
108
|
+
"SPOTGPU_API_KEY": "sk_…"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Tool: `spot_price`
|
|
116
|
+
|
|
117
|
+
| Param | Type | Default | Notes |
|
|
118
|
+
|-------|------|---------|-------|
|
|
119
|
+
| `gpu` | string | required | e.g. `RTX_4090` |
|
|
120
|
+
| `qty` | int | `1` | ≥ 1 |
|
|
121
|
+
| `markets` | string | `"vast,runpod"` | comma-separated |
|
|
122
|
+
| `fresh` | bool | `false` | maps to `fresh=1` |
|
|
123
|
+
|
|
124
|
+
On success returns JSON text of the API body (`best.usd_per_hr`, `alts`, …).
|
|
125
|
+
|
|
126
|
+
## Attribution
|
|
127
|
+
|
|
128
|
+
Requests send `X-SpotGPU-Client: mcp-spotgpu`.
|
|
129
|
+
|
|
130
|
+
## Not published yet
|
|
131
|
+
|
|
132
|
+
PyPI / Official MCP Registry / Smithery / Glama publish is deferred until Biz Bot approval. `server.json` and `glama.json` stubs are in this package for later.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# spotgpu-mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for [SpotGPU](https://spotgpu-api.fly.dev) spot GPU rental prices.
|
|
4
|
+
|
|
5
|
+
Exposes one tool: **`spot_price`** → `GET /v1/spot` (Vast + RunPod best + alts).
|
|
6
|
+
|
|
7
|
+
> **Credits ≠ rental.** SpotGPU API credits pay for price lookups.
|
|
8
|
+
> **`usd_per_hr` is the upstream provider rental price**, not a SpotGPU fee.
|
|
9
|
+
|
|
10
|
+
<!-- mcp-name: io.github.flowtestpro-svg/spotgpu-mcp -->
|
|
11
|
+
|
|
12
|
+
## Install / run
|
|
13
|
+
|
|
14
|
+
Requires Python 3.10+ and a SpotGPU API key (`sk_…` from https://spotgpu-api.fly.dev).
|
|
15
|
+
|
|
16
|
+
### One-shot (after PyPI publish)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uvx spotgpu-mcp
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Local (this monorepo package)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd packages/mcp-spotgpu
|
|
26
|
+
uv sync # or: pip install -e .
|
|
27
|
+
export SPOTGPU_API_KEY=sk_…
|
|
28
|
+
spotgpu-mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Env:
|
|
32
|
+
|
|
33
|
+
| Variable | Required | Default |
|
|
34
|
+
|----------|----------|---------|
|
|
35
|
+
| `SPOTGPU_API_KEY` | **yes** | — |
|
|
36
|
+
| `SPOTGPU_BASE_URL` | no | `https://spotgpu-api.fly.dev` |
|
|
37
|
+
|
|
38
|
+
Never hardcode keys. The server speaks MCP on **stdio** only (do not print to stdout).
|
|
39
|
+
|
|
40
|
+
## Claude Desktop
|
|
41
|
+
|
|
42
|
+
Add to `claude_desktop_config.json`:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mcpServers": {
|
|
47
|
+
"spotgpu": {
|
|
48
|
+
"command": "uvx",
|
|
49
|
+
"args": ["spotgpu-mcp"],
|
|
50
|
+
"env": {
|
|
51
|
+
"SPOTGPU_API_KEY": "sk_…"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Local editable install instead of `uvx`:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"spotgpu": {
|
|
64
|
+
"command": "uv",
|
|
65
|
+
"args": ["run", "--directory", "/path/to/spotgpu/packages/mcp-spotgpu", "spotgpu-mcp"],
|
|
66
|
+
"env": {
|
|
67
|
+
"SPOTGPU_API_KEY": "sk_…"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Cursor
|
|
75
|
+
|
|
76
|
+
Add to `.cursor/mcp.json` (or user MCP config):
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mcpServers": {
|
|
81
|
+
"spotgpu": {
|
|
82
|
+
"command": "uvx",
|
|
83
|
+
"args": ["spotgpu-mcp"],
|
|
84
|
+
"env": {
|
|
85
|
+
"SPOTGPU_API_KEY": "sk_…"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Tool: `spot_price`
|
|
93
|
+
|
|
94
|
+
| Param | Type | Default | Notes |
|
|
95
|
+
|-------|------|---------|-------|
|
|
96
|
+
| `gpu` | string | required | e.g. `RTX_4090` |
|
|
97
|
+
| `qty` | int | `1` | ≥ 1 |
|
|
98
|
+
| `markets` | string | `"vast,runpod"` | comma-separated |
|
|
99
|
+
| `fresh` | bool | `false` | maps to `fresh=1` |
|
|
100
|
+
|
|
101
|
+
On success returns JSON text of the API body (`best.usd_per_hr`, `alts`, …).
|
|
102
|
+
|
|
103
|
+
## Attribution
|
|
104
|
+
|
|
105
|
+
Requests send `X-SpotGPU-Client: mcp-spotgpu`.
|
|
106
|
+
|
|
107
|
+
## Not published yet
|
|
108
|
+
|
|
109
|
+
PyPI / Official MCP Registry / Smithery / Glama publish is deferred until Biz Bot approval. `server.json` and `glama.json` stubs are in this package for later.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "spotgpu-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "SpotGPU MCP server — spot GPU rental prices via GET /v1/spot"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "SpotGPU" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: Software Development :: Libraries",
|
|
22
|
+
"Typing :: Typed",
|
|
23
|
+
]
|
|
24
|
+
keywords = ["mcp", "gpu", "spot", "pricing", "vast", "runpod"]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"mcp>=2,<3",
|
|
27
|
+
"httpx>=0.27.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://spotgpu-api.fly.dev"
|
|
32
|
+
Documentation = "https://spotgpu-api.fly.dev/docs"
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
spotgpu-mcp = "spotgpu_mcp.server:main"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["src"]
|
|
39
|
+
include = ["spotgpu_mcp*"]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Thin httpx client for SpotGPU GET /v1/spot. No app-internal imports."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
DEFAULT_BASE_URL = "https://spotgpu-api.fly.dev"
|
|
12
|
+
CLIENT_HEADER = "mcp-spotgpu"
|
|
13
|
+
TIMEOUT_S = 60.0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SpotGPUError(Exception):
|
|
17
|
+
"""Raised for API/config failures; message is safe for MCP tool errors."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, message: str) -> None:
|
|
20
|
+
super().__init__(message)
|
|
21
|
+
self.message = message
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _base_url() -> str:
|
|
25
|
+
return (os.environ.get("SPOTGPU_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _api_key() -> str:
|
|
29
|
+
key = os.environ.get("SPOTGPU_API_KEY")
|
|
30
|
+
if not key or not key.strip():
|
|
31
|
+
raise SpotGPUError(
|
|
32
|
+
"Server misconfigured: SPOTGPU_API_KEY not set. "
|
|
33
|
+
"Set it in your MCP client env (Claude Desktop / Cursor)."
|
|
34
|
+
)
|
|
35
|
+
return key.strip()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _detail_snippet(body: Any) -> str:
|
|
39
|
+
"""Extract a short human detail without leaking secrets."""
|
|
40
|
+
if body is None:
|
|
41
|
+
return ""
|
|
42
|
+
if isinstance(body, str):
|
|
43
|
+
text = body.strip()
|
|
44
|
+
return f" — {text[:300]}" if text else ""
|
|
45
|
+
if isinstance(body, dict):
|
|
46
|
+
for k in ("message", "detail", "error"):
|
|
47
|
+
v = body.get(k)
|
|
48
|
+
if isinstance(v, str) and v.strip():
|
|
49
|
+
return f" — {v.strip()[:300]}"
|
|
50
|
+
if isinstance(v, list) and v:
|
|
51
|
+
return f" — {json.dumps(v)[:300]}"
|
|
52
|
+
# FastAPI validation often nests under detail
|
|
53
|
+
detail = body.get("detail")
|
|
54
|
+
if detail is not None:
|
|
55
|
+
try:
|
|
56
|
+
return f" — {json.dumps(detail)[:300]}"
|
|
57
|
+
except (TypeError, ValueError):
|
|
58
|
+
return f" — {str(detail)[:300]}"
|
|
59
|
+
try:
|
|
60
|
+
return f" — {json.dumps(body)[:300]}"
|
|
61
|
+
except (TypeError, ValueError):
|
|
62
|
+
return ""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _map_http_error(status: int, body: Any, base: str) -> SpotGPUError:
|
|
66
|
+
if status == 401:
|
|
67
|
+
return SpotGPUError(
|
|
68
|
+
"Invalid or missing API key — check SPOTGPU_API_KEY"
|
|
69
|
+
+ _detail_snippet(body)
|
|
70
|
+
)
|
|
71
|
+
if status == 402:
|
|
72
|
+
return SpotGPUError(
|
|
73
|
+
f"Insufficient credits — top up at {base}/topup or POST /v1/checkout"
|
|
74
|
+
+ _detail_snippet(body)
|
|
75
|
+
)
|
|
76
|
+
if status == 404:
|
|
77
|
+
return SpotGPUError(
|
|
78
|
+
"No offers / unknown GPU" + _detail_snippet(body)
|
|
79
|
+
)
|
|
80
|
+
if status == 422:
|
|
81
|
+
return SpotGPUError("Bad params" + _detail_snippet(body))
|
|
82
|
+
if status in (502, 503):
|
|
83
|
+
return SpotGPUError(
|
|
84
|
+
"Upstream markets unavailable — retry later" + _detail_snippet(body)
|
|
85
|
+
)
|
|
86
|
+
return SpotGPUError(f"SpotGPU API HTTP {status}" + _detail_snippet(body))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def fetch_spot(
|
|
90
|
+
*,
|
|
91
|
+
gpu: str,
|
|
92
|
+
qty: int = 1,
|
|
93
|
+
markets: str = "vast,runpod",
|
|
94
|
+
fresh: bool = False,
|
|
95
|
+
) -> dict[str, Any]:
|
|
96
|
+
"""
|
|
97
|
+
GET {BASE}/v1/spot with Bearer auth.
|
|
98
|
+
|
|
99
|
+
Maps fresh=True → query fresh=1. Returns parsed JSON body on 200.
|
|
100
|
+
"""
|
|
101
|
+
base = _base_url()
|
|
102
|
+
key = _api_key()
|
|
103
|
+
params: dict[str, str | int] = {
|
|
104
|
+
"gpu": gpu,
|
|
105
|
+
"qty": qty,
|
|
106
|
+
"markets": markets,
|
|
107
|
+
}
|
|
108
|
+
if fresh:
|
|
109
|
+
params["fresh"] = 1
|
|
110
|
+
|
|
111
|
+
headers = {
|
|
112
|
+
"Authorization": f"Bearer {key}",
|
|
113
|
+
"Accept": "application/json",
|
|
114
|
+
"X-SpotGPU-Client": CLIENT_HEADER,
|
|
115
|
+
}
|
|
116
|
+
url = f"{base}/v1/spot"
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
with httpx.Client(timeout=TIMEOUT_S) as client:
|
|
120
|
+
resp = client.get(url, params=params, headers=headers)
|
|
121
|
+
except httpx.TimeoutException as exc:
|
|
122
|
+
raise SpotGPUError("Connection failed to SpotGPU API (timeout)") from exc
|
|
123
|
+
except httpx.RequestError as exc:
|
|
124
|
+
raise SpotGPUError("Connection failed to SpotGPU API") from exc
|
|
125
|
+
|
|
126
|
+
if resp.status_code == 200:
|
|
127
|
+
try:
|
|
128
|
+
data = resp.json()
|
|
129
|
+
except json.JSONDecodeError as exc:
|
|
130
|
+
raise SpotGPUError("SpotGPU API returned non-JSON body") from exc
|
|
131
|
+
if not isinstance(data, dict):
|
|
132
|
+
raise SpotGPUError("SpotGPU API returned unexpected JSON shape")
|
|
133
|
+
return data
|
|
134
|
+
|
|
135
|
+
body: Any
|
|
136
|
+
try:
|
|
137
|
+
body = resp.json()
|
|
138
|
+
except json.JSONDecodeError:
|
|
139
|
+
body = resp.text
|
|
140
|
+
raise _map_http_error(resp.status_code, body, base)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""SpotGPU MCP server (stdio only). Never write non-protocol bytes to stdout."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from mcp.server import MCPServer
|
|
10
|
+
|
|
11
|
+
from spotgpu_mcp.client import SpotGPUError, fetch_spot
|
|
12
|
+
|
|
13
|
+
# Stdio MCP: logging must go to stderr only
|
|
14
|
+
logging.basicConfig(
|
|
15
|
+
level=logging.INFO,
|
|
16
|
+
format="%(levelname)s %(name)s: %(message)s",
|
|
17
|
+
stream=sys.stderr,
|
|
18
|
+
)
|
|
19
|
+
log = logging.getLogger("spotgpu_mcp")
|
|
20
|
+
|
|
21
|
+
mcp = MCPServer("spotgpu")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@mcp.tool()
|
|
25
|
+
def spot_price(
|
|
26
|
+
gpu: str,
|
|
27
|
+
qty: int = 1,
|
|
28
|
+
markets: str = "vast,runpod",
|
|
29
|
+
fresh: bool = False,
|
|
30
|
+
) -> str:
|
|
31
|
+
"""Get SpotGPU spot rental prices (best + alts) for a GPU type.
|
|
32
|
+
|
|
33
|
+
Returns JSON from GET /v1/spot including best.usd_per_hr (upstream
|
|
34
|
+
provider rental price, not a SpotGPU fee). Credits are for API calls,
|
|
35
|
+
not GPU rental.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
gpu: GPU type, e.g. RTX_4090, RTX_3090, RTX_A6000
|
|
39
|
+
qty: Number of GPUs (>= 1). Default 1.
|
|
40
|
+
markets: Comma-separated markets. Default "vast,runpod".
|
|
41
|
+
fresh: If true, bypass cache (query fresh=1). Default false.
|
|
42
|
+
"""
|
|
43
|
+
if qty < 1:
|
|
44
|
+
raise SpotGPUError("Bad params — qty must be >= 1")
|
|
45
|
+
try:
|
|
46
|
+
data = fetch_spot(gpu=gpu, qty=qty, markets=markets, fresh=fresh)
|
|
47
|
+
except SpotGPUError:
|
|
48
|
+
raise
|
|
49
|
+
except Exception as exc: # noqa: BLE001 — surface unexpected as tool error
|
|
50
|
+
log.exception("spot_price unexpected failure")
|
|
51
|
+
raise SpotGPUError(f"SpotGPU client error: {exc}") from exc
|
|
52
|
+
return json.dumps(data, indent=2)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def main() -> None:
|
|
56
|
+
"""CLI entry: spotgpu-mcp — stdio transport only."""
|
|
57
|
+
mcp.run(transport="stdio")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
main()
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spotgpu-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SpotGPU MCP server — spot GPU rental prices via GET /v1/spot
|
|
5
|
+
Author: SpotGPU
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://spotgpu-api.fly.dev
|
|
8
|
+
Project-URL: Documentation, https://spotgpu-api.fly.dev/docs
|
|
9
|
+
Keywords: mcp,gpu,spot,pricing,vast,runpod
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: mcp<3,>=2
|
|
22
|
+
Requires-Dist: httpx>=0.27.0
|
|
23
|
+
|
|
24
|
+
# spotgpu-mcp
|
|
25
|
+
|
|
26
|
+
MCP (Model Context Protocol) server for [SpotGPU](https://spotgpu-api.fly.dev) spot GPU rental prices.
|
|
27
|
+
|
|
28
|
+
Exposes one tool: **`spot_price`** → `GET /v1/spot` (Vast + RunPod best + alts).
|
|
29
|
+
|
|
30
|
+
> **Credits ≠ rental.** SpotGPU API credits pay for price lookups.
|
|
31
|
+
> **`usd_per_hr` is the upstream provider rental price**, not a SpotGPU fee.
|
|
32
|
+
|
|
33
|
+
<!-- mcp-name: io.github.flowtestpro-svg/spotgpu-mcp -->
|
|
34
|
+
|
|
35
|
+
## Install / run
|
|
36
|
+
|
|
37
|
+
Requires Python 3.10+ and a SpotGPU API key (`sk_…` from https://spotgpu-api.fly.dev).
|
|
38
|
+
|
|
39
|
+
### One-shot (after PyPI publish)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uvx spotgpu-mcp
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Local (this monorepo package)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd packages/mcp-spotgpu
|
|
49
|
+
uv sync # or: pip install -e .
|
|
50
|
+
export SPOTGPU_API_KEY=sk_…
|
|
51
|
+
spotgpu-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Env:
|
|
55
|
+
|
|
56
|
+
| Variable | Required | Default |
|
|
57
|
+
|----------|----------|---------|
|
|
58
|
+
| `SPOTGPU_API_KEY` | **yes** | — |
|
|
59
|
+
| `SPOTGPU_BASE_URL` | no | `https://spotgpu-api.fly.dev` |
|
|
60
|
+
|
|
61
|
+
Never hardcode keys. The server speaks MCP on **stdio** only (do not print to stdout).
|
|
62
|
+
|
|
63
|
+
## Claude Desktop
|
|
64
|
+
|
|
65
|
+
Add to `claude_desktop_config.json`:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"spotgpu": {
|
|
71
|
+
"command": "uvx",
|
|
72
|
+
"args": ["spotgpu-mcp"],
|
|
73
|
+
"env": {
|
|
74
|
+
"SPOTGPU_API_KEY": "sk_…"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Local editable install instead of `uvx`:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"spotgpu": {
|
|
87
|
+
"command": "uv",
|
|
88
|
+
"args": ["run", "--directory", "/path/to/spotgpu/packages/mcp-spotgpu", "spotgpu-mcp"],
|
|
89
|
+
"env": {
|
|
90
|
+
"SPOTGPU_API_KEY": "sk_…"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Cursor
|
|
98
|
+
|
|
99
|
+
Add to `.cursor/mcp.json` (or user MCP config):
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"mcpServers": {
|
|
104
|
+
"spotgpu": {
|
|
105
|
+
"command": "uvx",
|
|
106
|
+
"args": ["spotgpu-mcp"],
|
|
107
|
+
"env": {
|
|
108
|
+
"SPOTGPU_API_KEY": "sk_…"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Tool: `spot_price`
|
|
116
|
+
|
|
117
|
+
| Param | Type | Default | Notes |
|
|
118
|
+
|-------|------|---------|-------|
|
|
119
|
+
| `gpu` | string | required | e.g. `RTX_4090` |
|
|
120
|
+
| `qty` | int | `1` | ≥ 1 |
|
|
121
|
+
| `markets` | string | `"vast,runpod"` | comma-separated |
|
|
122
|
+
| `fresh` | bool | `false` | maps to `fresh=1` |
|
|
123
|
+
|
|
124
|
+
On success returns JSON text of the API body (`best.usd_per_hr`, `alts`, …).
|
|
125
|
+
|
|
126
|
+
## Attribution
|
|
127
|
+
|
|
128
|
+
Requests send `X-SpotGPU-Client: mcp-spotgpu`.
|
|
129
|
+
|
|
130
|
+
## Not published yet
|
|
131
|
+
|
|
132
|
+
PyPI / Official MCP Registry / Smithery / Glama publish is deferred until Biz Bot approval. `server.json` and `glama.json` stubs are in this package for later.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/spotgpu_mcp/__init__.py
|
|
4
|
+
src/spotgpu_mcp/client.py
|
|
5
|
+
src/spotgpu_mcp/server.py
|
|
6
|
+
src/spotgpu_mcp.egg-info/PKG-INFO
|
|
7
|
+
src/spotgpu_mcp.egg-info/SOURCES.txt
|
|
8
|
+
src/spotgpu_mcp.egg-info/dependency_links.txt
|
|
9
|
+
src/spotgpu_mcp.egg-info/entry_points.txt
|
|
10
|
+
src/spotgpu_mcp.egg-info/requires.txt
|
|
11
|
+
src/spotgpu_mcp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
spotgpu_mcp
|