@runpod/mcp-server 3.0.0 → 3.1.0
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.
- package/CHANGELOG.md +28 -0
- package/README.md +59 -319
- package/dist/{chunk-GTBCNGHE.mjs → chunk-OM6RHPQC.mjs} +183 -38
- package/dist/chunk-OM6RHPQC.mjs.map +1 -0
- package/dist/http.cjs +183 -38
- package/dist/http.cjs.map +1 -1
- package/dist/http.mjs +2 -2
- package/dist/stdio.cjs +183 -38
- package/dist/stdio.cjs.map +1 -1
- package/dist/stdio.mjs +183 -38
- package/dist/stdio.mjs.map +1 -1
- package/dist/tools.cjs +182 -37
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-GTBCNGHE.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @runpod/mcp-server
|
|
2
2
|
|
|
3
|
+
## 3.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 259241d: Enforce PKCE (S256) in every hosted OAuth flow. `/authorize` rejects missing, malformed, or non-S256 challenges before issuing a code and forwards a valid challenge to the flash backend. `/token` requires a valid, matching `code_verifier` before returning the minted key; missing PKCE state, `plain`, and non-matching verifiers are rejected.
|
|
8
|
+
|
|
9
|
+
Requires the backend `codeChallenge` / `codeChallengeMethod` fields on `createFlashAuthRequest` / `flashAuthRequestStatus` (runpod/RunPod, DR-1398) to be deployed.
|
|
10
|
+
|
|
11
|
+
- f6208cd: Diagnose stuck queued jobs instead of leaving them ambiguous. A job that stays IN_QUEUE has two very different causes that its status alone cannot distinguish: no host has the endpoint's GPU available (capacity), or a worker spun up and is crash-looping — the platform marks it UNHEALTHY while the job stays queued indefinitely, and agents reliably misread that as a capacity shortage. `get-job-status` now best-effort attaches the endpoint's worker summary (`workerHealth`) and a plain-language `hint` whenever the job is IN_QUEUE (v2 only; the extra lookup never fails the status call, and the diagnosis is cached ~15s per endpoint so polling loops do not amplify into repeated worker-list fetches): UNHEALTHY workers → crash-loop guidance naming the worker ids to inspect with `stream-worker-logs`; zero workers → capacity guidance; throttled/initializing → wait guidance. The `get-job-status`, `endpoint-health`, `list-endpoint-workers`, and `stream-worker-logs` descriptions now cross-reference each other so agents are routed from a stuck job to the worker logs without prior knowledge of the platform's queueing behavior.
|
|
12
|
+
- f36f191: Send the reshaped v2 Serverless write schema, and support load-balancing endpoints.
|
|
13
|
+
|
|
14
|
+
`/v2/serverless` changed its create/update contract: endpoint `type` (`QUEUE` or
|
|
15
|
+
`LOAD_BALANCER`) is now required on create, the autoscaling object moved from a flat
|
|
16
|
+
`{type, value, idleTimeout}` to a per-scaler `{type, queueDelay}` / `{type, requestCount}`,
|
|
17
|
+
and `idleTimeout` moved under `workers`. `create-endpoint` and `update-endpoint` now emit
|
|
18
|
+
that shape; previously they sent the older one and were rejected with a `422`.
|
|
19
|
+
|
|
20
|
+
`create-endpoint` gains `endpointType` for queue-based (default) versus load-balancing
|
|
21
|
+
request routing. `scalerType` defaults per endpoint type, `scalerValue` defaults to `4`, and
|
|
22
|
+
passing `scalerValue` alone on `update-endpoint` keeps the endpoint's current scaler, so
|
|
23
|
+
existing calls keep working unchanged. Responses carry `type` and `requestUrls`, and
|
|
24
|
+
`get-endpoint` / `list-endpoints` now point at `requestUrls` as the source for an endpoint's
|
|
25
|
+
URLs.
|
|
26
|
+
|
|
27
|
+
**This requires a Runpod API host serving the new schema.** Against a host still on the older
|
|
28
|
+
one, endpoint create/update return a `422` naming `queueDelay`/`requestCount`/`idleTimeout` as
|
|
29
|
+
not allowed. `RUNPOD_REST_VERSION=v1` remains available for the legacy template-based model.
|
|
30
|
+
|
|
3
31
|
## 3.0.0
|
|
4
32
|
|
|
5
33
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -2,32 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://smithery.ai/server/@runpod/runpod-mcp-ts)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The official Runpod Model Context Protocol (MCP) server. It lets MCP clients such as Claude Code, Claude Desktop, Cursor, Windsurf, and VS Code manage your Runpod Pods, Serverless endpoints, templates, network volumes, and more.
|
|
6
|
+
|
|
7
|
+
**We host it for you at [`https://mcp.getrunpod.io/`](https://mcp.getrunpod.io/)** — point your client at that URL and sign in with Runpod. Nothing to install, no API key on disk. Or run it locally from npm as [`@runpod/mcp-server`](https://www.npmjs.com/package/@runpod/mcp-server).
|
|
6
8
|
|
|
7
9
|
## Quick start
|
|
8
10
|
|
|
9
|
-
The
|
|
11
|
+
The guided installer detects the clients you have installed, asks which to configure, and writes the config for you:
|
|
10
12
|
|
|
11
13
|
```bash
|
|
12
14
|
npx @runpod/mcp-server@latest add
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
It
|
|
17
|
+
It offers two connection modes:
|
|
16
18
|
|
|
17
|
-
- **Hosted (recommended)
|
|
18
|
-
- **Local
|
|
19
|
+
- **Hosted (recommended)** — points the client at the hosted server and authenticates with "Sign in with Runpod" (OAuth). No API key is stored on disk.
|
|
20
|
+
- **Local** — runs the server through `npx` and stores a `RUNPOD_API_KEY` in the client's config.
|
|
19
21
|
|
|
20
|
-
To undo
|
|
22
|
+
To undo later:
|
|
21
23
|
|
|
22
24
|
```bash
|
|
23
25
|
npx @runpod/mcp-server@latest remove
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
###
|
|
28
|
+
### Or install the whole Runpod plugin
|
|
29
|
+
|
|
30
|
+
For the full agent setup, the [official plugin marketplace](https://github.com/runpod/runpod-plugins-official) bundles this server with a router skill plus five more — `runpod-mcp`, `runpodctl`, `flash`, `runpod-usage`, and `companion-clis`:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx skills add runpod/runpod-plugins-official
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
That works in Claude Code, Codex, Cursor, Copilot, Windsurf, Cline, Gemini, opencode, and 17+ other agents, and installs the skills — pair it with `npx @runpod/mcp-server@latest add` above for the control-plane tools.
|
|
27
37
|
|
|
28
|
-
|
|
38
|
+
In **Claude Code**, the native plugin route also wires up the hosted MCP server for you (OAuth included), so no separate setup is needed:
|
|
29
39
|
|
|
30
|
-
|
|
40
|
+
```
|
|
41
|
+
/plugin marketplace add runpod/runpod-plugins-official
|
|
42
|
+
/plugin install runpod@runpod
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Requirements
|
|
46
|
+
|
|
47
|
+
- Node.js 18 or higher.
|
|
48
|
+
- A Runpod account and [API key](https://www.runpod.io/console/user/settings).
|
|
49
|
+
|
|
50
|
+
## Connect to the hosted server
|
|
51
|
+
|
|
52
|
+
To configure a client by hand, point it at the hosted server over HTTP (no local process, no API key stored).
|
|
53
|
+
|
|
54
|
+
**Claude Code:**
|
|
31
55
|
|
|
32
56
|
```bash
|
|
33
57
|
claude mcp add --transport http runpod -s user https://mcp.getrunpod.io/
|
|
@@ -45,34 +69,29 @@ claude mcp add --transport http runpod -s user https://mcp.getrunpod.io/
|
|
|
45
69
|
}
|
|
46
70
|
```
|
|
47
71
|
|
|
48
|
-
|
|
72
|
+
An OAuth-capable client starts the "Sign in with Runpod" flow automatically on first connect: it opens a browser, you log in to the Runpod console and approve, and the server obtains a Runpod API key scoped to your session. Nothing is stored on disk.
|
|
49
73
|
|
|
50
|
-
> Prefer your own API key
|
|
51
|
-
|
|
52
|
-
### Requirements
|
|
53
|
-
|
|
54
|
-
- Node.js 18 or higher.
|
|
55
|
-
- A Runpod account and API key: https://www.runpod.io/console/user/settings
|
|
74
|
+
> Prefer your own API key over OAuth? Append `--header "Authorization: Bearer YOUR_API_KEY"` to the `claude mcp add` command (or add a `headers` block in the JSON). The server forwards that key to the Runpod API directly.
|
|
56
75
|
|
|
57
76
|
## Run locally with `npx`
|
|
58
77
|
|
|
59
|
-
|
|
78
|
+
Run the server as a local `stdio` process with your own API key:
|
|
60
79
|
|
|
61
80
|
```bash
|
|
62
81
|
RUNPOD_API_KEY=YOUR_API_KEY npx -y @runpod/mcp-server@latest
|
|
63
82
|
```
|
|
64
83
|
|
|
65
|
-
|
|
84
|
+
Or install via [Smithery](https://smithery.ai/server/@runpod/runpod-mcp-ts):
|
|
66
85
|
|
|
67
86
|
```bash
|
|
68
87
|
npx -y @smithery/cli install @runpod/runpod-mcp-ts --client claude
|
|
69
88
|
```
|
|
70
89
|
|
|
71
|
-
|
|
90
|
+
### Local client setup
|
|
72
91
|
|
|
73
|
-
Local
|
|
92
|
+
Local clients launch the `stdio` server and set `RUNPOD_API_KEY` in the environment.
|
|
74
93
|
|
|
75
|
-
|
|
94
|
+
**Claude Code:**
|
|
76
95
|
|
|
77
96
|
```bash
|
|
78
97
|
claude mcp add runpod -s user \
|
|
@@ -80,27 +99,9 @@ claude mcp add runpod -s user \
|
|
|
80
99
|
-- npx -y @runpod/mcp-server@latest
|
|
81
100
|
```
|
|
82
101
|
|
|
83
|
-
|
|
102
|
+
Use `-s project` for a project-local server. Verify with `claude mcp list`; in a session, `/mcp` reconnects.
|
|
84
103
|
|
|
85
|
-
|
|
86
|
-
claude mcp add runpod -s project \
|
|
87
|
-
-e RUNPOD_API_KEY=YOUR_API_KEY \
|
|
88
|
-
-- npx -y @runpod/mcp-server@latest
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Verify with `claude mcp list`. In an active session, use `/mcp` to reconnect.
|
|
92
|
-
|
|
93
|
-
### Claude Desktop
|
|
94
|
-
|
|
95
|
-
Local MCP servers in Claude Desktop still use `claude_desktop_config.json`.
|
|
96
|
-
|
|
97
|
-
macOS:
|
|
98
|
-
|
|
99
|
-
`~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
100
|
-
|
|
101
|
-
Windows:
|
|
102
|
-
|
|
103
|
-
`%APPDATA%\\Claude\\claude_desktop_config.json`
|
|
104
|
+
**Claude Desktop, Cursor, VS Code, Windsurf, and other clients** — use the same command in the client's MCP config:
|
|
104
105
|
|
|
105
106
|
```json
|
|
106
107
|
{
|
|
@@ -116,154 +117,38 @@ Windows:
|
|
|
116
117
|
}
|
|
117
118
|
```
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
For remote clients such as Claude's connector, use the hosted HTTP server instead (the [Quick start](#quick-start) above). The client then runs the OAuth "Sign in with Runpod" flow, and the resulting Runpod API key is forwarded to the Runpod API on each request.
|
|
122
|
-
|
|
123
|
-
### Cursor
|
|
124
|
-
|
|
125
|
-
Add this to `.cursor/mcp.json` in your project or `~/.cursor/mcp.json` globally:
|
|
126
|
-
|
|
127
|
-
```json
|
|
128
|
-
{
|
|
129
|
-
"mcpServers": {
|
|
130
|
-
"runpod": {
|
|
131
|
-
"command": "npx",
|
|
132
|
-
"args": ["-y", "@runpod/mcp-server@latest"],
|
|
133
|
-
"env": {
|
|
134
|
-
"RUNPOD_API_KEY": "YOUR_API_KEY"
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### VS Code, Windsurf, Cline, JetBrains, and other local clients
|
|
142
|
-
|
|
143
|
-
Use the same pattern:
|
|
144
|
-
|
|
145
|
-
- command: `npx`
|
|
146
|
-
- args: `["-y", "@runpod/mcp-server@latest"]`
|
|
147
|
-
- env: `RUNPOD_API_KEY=YOUR_API_KEY`
|
|
148
|
-
|
|
149
|
-
For a broader list of MCP clients, see https://modelcontextprotocol.io/clients
|
|
120
|
+
Claude Desktop's config lives at `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows); restart the app after saving. For a broader list of clients, see the [MCP clients directory](https://modelcontextprotocol.io/clients).
|
|
150
121
|
|
|
151
122
|
## Usage examples
|
|
152
123
|
|
|
153
|
-
|
|
124
|
+
Ask your MCP client in natural language:
|
|
154
125
|
|
|
155
126
|
```text
|
|
156
|
-
|
|
127
|
+
List all my Runpod Pods.
|
|
157
128
|
```
|
|
158
129
|
|
|
159
|
-
### Create a new Pod
|
|
160
|
-
|
|
161
130
|
```text
|
|
162
|
-
Create a
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
- GPU Type: NVIDIA GeForce RTX 4090
|
|
166
|
-
- GPU Count: 1
|
|
131
|
+
Create a Runpod Pod named test-pod with image
|
|
132
|
+
runpod/pytorch:2.1.0-py3.10-cuda11.8.0-devel-ubuntu22.04,
|
|
133
|
+
GPU type NVIDIA GeForce RTX 4090, 1 GPU.
|
|
167
134
|
```
|
|
168
135
|
|
|
169
|
-
### Create a Serverless endpoint
|
|
170
|
-
|
|
171
136
|
```text
|
|
172
|
-
Create a
|
|
173
|
-
-
|
|
174
|
-
- Image: runpod/test-output:0.0.1
|
|
175
|
-
- GPU pool: AMPERE_80 (a "pool" value from list-gpu-types)
|
|
176
|
-
- Minimum workers: 0
|
|
177
|
-
- Maximum workers: 3
|
|
137
|
+
Create a Serverless endpoint named my-endpoint with image
|
|
138
|
+
runpod/test-output:0.0.1, GPU pool AMPERE_80, 0 min workers, 3 max workers.
|
|
178
139
|
```
|
|
179
140
|
|
|
180
|
-
On the v2 API (the default) endpoints are image-based — pass an image and a GPU
|
|
181
|
-
pool, not a template. To use the legacy template-based model, pin
|
|
182
|
-
`RUNPOD_REST_VERSION=v1` and provide a `Template ID` instead.
|
|
183
|
-
|
|
184
|
-
## Hosted HTTP deployment
|
|
185
|
-
|
|
186
|
-
The package also exports a Streamable HTTP entrypoint at `@runpod/mcp-server/http`, and this repo includes a Vercel function in `api/index.ts`. This is what powers the hosted server at `https://mcp.getrunpod.io/`.
|
|
187
|
-
|
|
188
|
-
The hosted transport is stateless:
|
|
189
|
-
|
|
190
|
-
- Each request creates a fresh MCP server instance.
|
|
191
|
-
- The caller must send `Authorization: Bearer <token>`.
|
|
192
|
-
- No credentials are cached or shared server-side.
|
|
141
|
+
On the v2 API (the default), endpoints are image-based — pass an image and a GPU pool (a `pool` value from `list-gpu-types`), not a template.
|
|
193
142
|
|
|
194
|
-
|
|
143
|
+
See [`docs/configuration.md`](docs/configuration.md) for REST v1/v2 selection and the `templateId` migration note, private image pull (registry credentials vs ECR delegation), and large-output handling.
|
|
195
144
|
|
|
196
|
-
|
|
145
|
+
## Security
|
|
197
146
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
- a Runpod API key the caller configured manually, or
|
|
201
|
-
- a Runpod API key obtained through the OAuth "Sign in with Runpod" flow (see below).
|
|
202
|
-
|
|
203
|
-
An unauthenticated request receives a `401` with a `WWW-Authenticate` header pointing at the protected-resource metadata, which tells an OAuth-capable client (such as Claude) to start the sign-in flow.
|
|
204
|
-
|
|
205
|
-
A request whose bearer token is _rejected_ — a revoked key, whether OAuth-minted or supplied by the caller — gets the same `401`, plus `error="invalid_token"` to distinguish it from having sent no credential at all. The token is verified once and the verdict cached briefly. If a credential dies mid-session while a "valid" verdict is still cached, the first call afterward can still surface the upstream 401 as a `200` tool error; that call drops the cached verdict, so the next request re-checks and surfaces a real HTTP `401` that triggers re-authentication. Set `MCP_SKIP_CREDENTIAL_CHECK=true` to disable that verification; it also self-disables when the REST/Serverless hosts and the auth GraphQL host are pointed at different environments, since it would otherwise validate the key against the wrong backend.
|
|
206
|
-
|
|
207
|
-
### Sign in with Runpod (authorize flow)
|
|
208
|
-
|
|
209
|
-
In OAuth mode the hosted server is itself the authorization server for Claude's connector. It advertises itself in `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`, pointing `authorization_endpoint` at its own `GET /authorize`, `token_endpoint` at its own `POST /token`, and `registration_endpoint` at its own `POST /register`.
|
|
210
|
-
|
|
211
|
-
The flow reuses the Runpod flash auth backend and mints a real Runpod API key:
|
|
212
|
-
|
|
213
|
-
1. The client registers via `POST /register` (OAuth Dynamic Client Registration, RFC 7591) to obtain a `client_id`. This is a public client; no client secret is used.
|
|
214
|
-
2. `GET /authorize` calls the guest `createFlashAuthRequest` mutation to get a request id (which serves as the OAuth authorization code), then `302`-redirects the browser to the console handoff page (`/integrations/mcp/login`), carrying the request id plus the client's `redirect_uri` and `state`.
|
|
215
|
-
3. The user logs in and approves the request in the console. On approval the backend mints a Runpod API key for the request.
|
|
216
|
-
4. The console returns the browser to the client's `redirect_uri` with `code=<request id>`.
|
|
217
|
-
5. `POST /token` polls the guest `flashAuthRequestStatus` query for that id; once `APPROVED`, it returns the minted `apiKey` as the `access_token`. The client then sends that key as its bearer token on every MCP request, and the server forwards it to the Runpod API.
|
|
218
|
-
|
|
219
|
-
This flow uses these environment variables:
|
|
220
|
-
|
|
221
|
-
- `RUNPOD_GRAPHQL_URL`: flash auth backend endpoint (default `https://api.runpod.io/graphql`).
|
|
222
|
-
- `CONSOLE_BASE_URL`: base URL of the console that hosts the handoff login page (default `https://console.runpod.io`).
|
|
223
|
-
- `RUNPOD_REST_API_URL` / `RUNPOD_SERVERLESS_API_URL`: override the REST and Serverless API hosts so a deployment authenticating with non-production keys can target the matching environment.
|
|
224
|
-
- `RUNPOD_API_KEY_NAME`: name for the minted key as shown in the user's dashboard. Defaults to `runpod-mcp`. Set it to `""` to omit the name for a backend that does not support the `apiKeyName` argument (such backends reject the request when it is sent).
|
|
225
|
-
- `MCP_ALLOWED_REDIRECT_URIS`: comma-separated extra `redirect_uri` values to allow, in addition to the built-in Claude callbacks. Loopback addresses (`localhost`/`127.0.0.1`/`::1`, any port) are always allowed. `/authorize` and `/token` reject any `redirect_uri` not on this list, since the authorization code redeems into a real API key.
|
|
226
|
-
|
|
227
|
-
You can verify the entire flow end to end with `MCP_SERVER_URL=<deployment-url> npx tsx scripts/oauth-e2e.ts` (the harness uses a loopback callback).
|
|
228
|
-
|
|
229
|
-
Notes and current limitations:
|
|
230
|
-
|
|
231
|
-
- PKCE is not supported: `code_challenge` is neither advertised nor enforced (the flash flow has no place to bind it). Security relies on the `redirect_uri` allowlist and the single-use, short-lived flash approval.
|
|
232
|
-
- The minted key is named via `RUNPOD_API_KEY_NAME` (default `runpod-mcp`), which requires a flash backend that supports the `apiKeyName` argument. Against a backend without it, set `RUNPOD_API_KEY_NAME=""`.
|
|
233
|
-
|
|
234
|
-
### Vercel
|
|
235
|
-
|
|
236
|
-
This repo already contains `vercel.json` and the Vercel handler.
|
|
237
|
-
|
|
238
|
-
Deploy with the Vercel CLI:
|
|
239
|
-
|
|
240
|
-
```bash
|
|
241
|
-
vercel
|
|
242
|
-
vercel --prod
|
|
243
|
-
```
|
|
147
|
+
This server acts with the full permissions of the supplied API key.
|
|
244
148
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
curl -i https://YOUR-DEPLOYMENT.vercel.app/
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
Unauthenticated requests should return `401`.
|
|
252
|
-
|
|
253
|
-
### Hosted smoke test
|
|
254
|
-
|
|
255
|
-
```bash
|
|
256
|
-
MCP_SERVER_URL=https://YOUR-DEPLOYMENT.vercel.app \
|
|
257
|
-
RUNPOD_API_KEY=YOUR_API_KEY \
|
|
258
|
-
pnpm smoke:http
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
This validates:
|
|
262
|
-
|
|
263
|
-
- MCP initialization.
|
|
264
|
-
- `tools/list`.
|
|
265
|
-
- A public GraphQL-backed tool call.
|
|
266
|
-
- An authenticated REST-backed tool call.
|
|
149
|
+
- Never share your API key.
|
|
150
|
+
- Be deliberate with destructive tools.
|
|
151
|
+
- Each request authenticates with its own caller-supplied token, which is forwarded to the Runpod API and **never persisted server-side**. The server never holds a credential of its own and never shares one across users.
|
|
267
152
|
|
|
268
153
|
## Local development
|
|
269
154
|
|
|
@@ -280,161 +165,16 @@ Run the local build directly:
|
|
|
280
165
|
RUNPOD_API_KEY=YOUR_API_KEY node dist/stdio.mjs
|
|
281
166
|
```
|
|
282
167
|
|
|
283
|
-
Or point a client at your local build:
|
|
284
|
-
|
|
285
|
-
```json
|
|
286
|
-
{
|
|
287
|
-
"command": "node",
|
|
288
|
-
"args": ["/absolute/path/to/runpod-mcp/dist/stdio.mjs"],
|
|
289
|
-
"env": {
|
|
290
|
-
"RUNPOD_API_KEY": "YOUR_API_KEY"
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
### Local smoke test
|
|
296
|
-
|
|
297
|
-
```bash
|
|
298
|
-
RUNPOD_API_KEY=YOUR_API_KEY pnpm build
|
|
299
|
-
RUNPOD_API_KEY=YOUR_API_KEY pnpm smoke:stdio
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
## Contributing
|
|
303
|
-
|
|
304
|
-
The source is now split by responsibility:
|
|
305
|
-
|
|
306
|
-
- `src/stdio.ts`: local `stdio` entrypoint (runs the v2 probe at startup for `auto` mode).
|
|
307
|
-
- `src/http.ts`: bearer-token extraction and the per-request MCP session for the Streamable HTTP transport.
|
|
308
|
-
- `src/tools.ts`: thin orchestrator — builds the shared runtime and calls each per-resource registrar.
|
|
309
|
-
- `src/tools/<resource>.ts`: the tools for one resource (`pods`, `endpoints`, `jobs`, `templates`, `network-volumes`, `registries`, `catalog`), each with a description + MCP annotations.
|
|
310
|
-
- `src/tools/runtime.ts`: the shared per-server runtime (caller-tracking, the authenticated REST/Serverless/GraphQL clients, the v1/v2 backend resolver) threaded into every registrar.
|
|
311
|
-
- `src/server.ts`: shared server metadata and construction.
|
|
312
|
-
- `src/_shared/backend.ts`: v1/v2 routing adapter — version resolution, per-resource paths, list-envelope unwrap, and the v2 probe.
|
|
313
|
-
- `src/_shared/http.ts`: unified authenticated JSON client + `HttpError`.
|
|
314
|
-
- `src/_shared/tracking.ts`: caller-tracking header construction.
|
|
315
|
-
- `src/_shared/mappers.ts`: v1→v2 request-body mappers.
|
|
316
|
-
- `api/index.ts`: Vercel adapter and the OAuth authorization-server routes (`/.well-known/*`, `/register`, `/authorize`, `/token`).
|
|
317
|
-
|
|
318
168
|
After changes:
|
|
319
169
|
|
|
320
170
|
```bash
|
|
321
171
|
pnpm type-check
|
|
322
172
|
pnpm lint
|
|
323
|
-
pnpm test
|
|
173
|
+
pnpm test # offline unit suite — no network or API key required
|
|
324
174
|
pnpm build
|
|
325
175
|
```
|
|
326
176
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
### v2 spec parity gate
|
|
330
|
-
|
|
331
|
-
`tests/spec-parity.test.ts` walks every operation in the vendored v2 OpenAPI spec (`tests/fixtures/v2-openapi.yaml`) and fails if a v2 endpoint has no MCP tool covering it (and isn't explicitly allowlisted) — the drift gate that flags when the API grows an endpoint we don't expose yet. It also runs the reverse check (no tool is left unaccounted for). It's part of `pnpm test`, hermetic (parses the committed spec; no network, no API key).
|
|
332
|
-
|
|
333
|
-
Refresh the vendored spec when the v2 API changes:
|
|
334
|
-
|
|
335
|
-
```bash
|
|
336
|
-
pnpm tsx scripts/fetch-v2-spec.ts # re-fetch tests/fixtures/v2-openapi.yaml
|
|
337
|
-
pnpm test # parity test shows any newly-uncovered endpoint
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
The serverless runtime tools (`run-endpoint`, `get-job-status`, …) target `api.runpod.ai/v2`, which is a different service from the v2 REST control plane, so they are allowlisted as intentionally spec-unmapped. The spec has no logs/artifacts endpoints, so there are no such tools.
|
|
341
|
-
|
|
342
|
-
An optional live shape check (skipped by default) verifies the live endpoints still return the envelopes the list tools unwrap; run it with `MCP_LIVE_V2_KEY=<dev-key> pnpm test` (optionally `MCP_LIVE_V2_BASE`).
|
|
343
|
-
|
|
344
|
-
For transport validation:
|
|
345
|
-
|
|
346
|
-
```bash
|
|
347
|
-
pnpm smoke:stdio
|
|
348
|
-
pnpm smoke:http
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
To exercise a real create→get→delete lifecycle against a **dev account** (free resources only — templates and registry auths — with fail-closed teardown):
|
|
352
|
-
|
|
353
|
-
```bash
|
|
354
|
-
RUNPOD_API_KEY=YOUR_DEV_KEY \
|
|
355
|
-
RUNPOD_REST_V2_API_URL=https://v2-rest.runpod.dev/v2 \
|
|
356
|
-
pnpm smoke:crud v1 v2
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
This project uses [changesets](https://github.com/changesets/changesets) for versioning and npm publishing. Every PR with user-facing changes needs a changeset file at `.changeset/DESCRIPTIVE_NAME.md`.
|
|
360
|
-
|
|
361
|
-
See `CLAUDE.md` and `docs/context.md` for contributor guidance.
|
|
362
|
-
|
|
363
|
-
## Reference
|
|
364
|
-
|
|
365
|
-
### Deployment modes
|
|
366
|
-
|
|
367
|
-
The server supports two deployment modes:
|
|
368
|
-
|
|
369
|
-
- Local `stdio` for Claude Desktop, Claude Code, Cursor, VS Code, and other MCP clients that launch a local process. The caller sets `RUNPOD_API_KEY` in the environment.
|
|
370
|
-
- Hosted Streamable HTTP for Vercel or other HTTP-capable platforms. Each request carries its own `Authorization: Bearer <token>`, which the server forwards directly to the Runpod API. The token can be a Runpod API key or one obtained through the OAuth "Sign in with Runpod" flow.
|
|
371
|
-
|
|
372
|
-
The server never holds a credential of its own and never shares one across users.
|
|
373
|
-
|
|
374
|
-
### REST API version (v1 / v2)
|
|
375
|
-
|
|
376
|
-
The server can target either the v1 REST API (`rest.runpod.io/v1`) or the newer v2 REST API (`v2-rest.runpod.io/v2`). It **defaults to v2**; set `RUNPOD_REST_VERSION=v1` to pin the previous v1 behavior. These environment variables are read once at startup:
|
|
377
|
-
|
|
378
|
-
| Variable | Values | Default | Effect |
|
|
379
|
-
| -------------------------------- | ---------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
|
380
|
-
| `RUNPOD_REST_VERSION` | `v1` \| `v2` \| `auto` | `v2` | Version used for all resources. |
|
|
381
|
-
| `RUNPOD_REST_VERSION_<RESOURCE>` | `v1` \| `v2` \| `auto` | — | Per-resource override (e.g. `RUNPOD_REST_VERSION_PODS=v2`). Takes precedence over the global setting. |
|
|
382
|
-
| `RUNPOD_REST_V2_API_URL` | URL | `https://v2-rest.runpod.io/v2` | v2 base URL. Override to target a non-prod host. |
|
|
383
|
-
| `RUNPOD_REST_API_URL` | URL | `https://rest.runpod.io/v1` | v1 base URL. |
|
|
384
|
-
| `RUNPOD_SERVERLESS_API_URL` | URL | `https://api.runpod.ai/v2` | Serverless runtime base URL. |
|
|
385
|
-
|
|
386
|
-
> **⚠️ Migration note — `create-endpoint` / `update-endpoint` changed shape in v2.** Serverless endpoints now use the v2 API (`/v2/serverless`) with an **inline config** instead of a `templateId`. On v2, `create-endpoint` requires `imageName` + `gpuPoolIds` (GPU **pool** names from `list-gpu-types` — the `pool` field, e.g. `AMPERE_80`), plus optional `workersMin`/`workersMax`, `scalerType`/`scalerValue`/`idleTimeout`, `containerDiskInGb`, `env`, `flashboot`, etc. It no longer accepts `templateId`. **If you previously called `create-endpoint` with `{ templateId }` and have no `RUNPOD_REST_VERSION` set, that call now returns a clean `400` after upgrading** — switch to the inline fields, or pin `RUNPOD_REST_VERSION=v1` to keep the legacy template-based model.
|
|
387
|
-
|
|
388
|
-
Notes:
|
|
389
|
-
|
|
390
|
-
- **Default is v2** — with nothing set, every control-plane resource uses the v2 REST API. Set `RUNPOD_REST_VERSION=v1` to pin the previous v1 behavior.
|
|
391
|
-
- **Pinning a deployment** — the env var is read once at startup, so set `RUNPOD_REST_VERSION` (`v1` or `v2`) in the host's environment (e.g. a Vercel project env var) and redeploy; no code change. Hosted HTTP honors this default like any other transport.
|
|
392
|
-
- **What `auto` does:** it probes v2 once at startup and falls back to v1 — but **only on the `stdio` transport** (one process = one key). On hosted HTTP `auto` resolves to **v1**, because a warm instance serves many users and a cached probe verdict could leak across them. `auto` is a stdio-only convenience; on HTTP, rely on the default or pin the version explicitly.
|
|
393
|
-
- `jobs` (serverless runtime) always uses v1 regardless of the setting — it has no v2 REST home (it targets `api.runpod.ai/v2`, a different service).
|
|
394
|
-
- The v2-only tools (`list-cpu-types`, `get-gpu-type`, `restart-pod`, and the three `*-registry-delegation(s)` tools) return a clear "v2 only" notice when called under v1.
|
|
395
|
-
- `create-pod` for a **CPU pod** (`computeType: "CPU"`) on v2 is transparently served by the **v1** API — v2 has no CPU pods yet — and the reply is flagged `_servedBy: "v1"`. Because that fallback hits the **v1 base**, set `RUNPOD_REST_API_URL` to match your environment when running v2 against a non-prod host (otherwise CPU creates land on v1 **prod**). On v2 a create with neither `gpuTypeIds` nor `computeType` is rejected — absence is never silently turned into a CPU pod.
|
|
396
|
-
|
|
397
|
-
### Private image pull: credentials vs ECR delegation
|
|
398
|
-
|
|
399
|
-
Two ways to let Runpod pull a private image, and they are not interchangeable:
|
|
400
|
-
|
|
401
|
-
- **`create-container-registry-auth`** — stores a username + password/token. Works for any registry (Docker Hub, GHCR, Quay, a self-hosted one). Available on v1 and v2. Reference the resulting id from `create-pod` / `create-endpoint` via `containerRegistryAuthId`.
|
|
402
|
-
- **`create-registry-delegation`** — **AWS ECR only, v2-only, no credentials stored.** You register an ECR repository ARN and Runpod is granted scoped pull access to it; the reply carries a `dockerRegistryUri` plus the resolved repository, tag, and AWS region. Manage with `list-registry-delegations` and revoke with `delete-registry-delegation`.
|
|
403
|
-
|
|
404
|
-
Prefer the delegation for ECR — nothing long-lived is stored on Runpod's side.
|
|
405
|
-
|
|
406
|
-
Example (stdio client config) — v2 against prod:
|
|
407
|
-
|
|
408
|
-
```json
|
|
409
|
-
"env": {
|
|
410
|
-
"RUNPOD_API_KEY": "YOUR_API_KEY",
|
|
411
|
-
"RUNPOD_REST_VERSION": "v2"
|
|
412
|
-
}
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
Targeting a **non-prod (dev) environment** — point **both** bases at it (the v1 base matters even in v2 mode, for the CPU-pod fallback above):
|
|
416
|
-
|
|
417
|
-
```json
|
|
418
|
-
"env": {
|
|
419
|
-
"RUNPOD_API_KEY": "YOUR_DEV_KEY",
|
|
420
|
-
"RUNPOD_REST_VERSION": "v2",
|
|
421
|
-
"RUNPOD_REST_V2_API_URL": "https://v2-rest.runpod.dev/v2",
|
|
422
|
-
"RUNPOD_REST_API_URL": "https://rest.runpod.dev/v1"
|
|
423
|
-
}
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
### Large tool output
|
|
427
|
-
|
|
428
|
-
Resource **lists** are paginated (default 20 items, `nextCursor`), so a big account can't flood the agent's context. But **serverless job output** — `run-endpoint`, `runsync-endpoint`, `get-job-status`, and especially `stream-job` (which accumulates every chunk) — is returned **as-is and is not size-capped**. It's a single opaque payload, not a list, so there's no cursor to page. A very large or long-streaming result can exceed the context window. If output may be huge, have the agent **write it to a file** instead of returning it inline, or set `s3Config` on the job so large outputs go to object storage.
|
|
429
|
-
|
|
430
|
-
## Security considerations
|
|
431
|
-
|
|
432
|
-
This server acts with the full permissions of the supplied `RUNPOD_API_KEY`.
|
|
433
|
-
|
|
434
|
-
- Never share your API key.
|
|
435
|
-
- Be deliberate about destructive tools.
|
|
436
|
-
- Treat hosted deployments as sensitive infrastructure.
|
|
437
|
-
- Each request authenticates with its own caller-supplied token, which is forwarded to the Runpod API and never persisted server-side.
|
|
177
|
+
This project uses [changesets](https://github.com/changesets/changesets) for versioning and npm publishing; every PR with user-facing changes needs a changeset. See `CLAUDE.md` and `docs/context.md` for full contributor guidance, architecture, and the test suite.
|
|
438
178
|
|
|
439
179
|
## License
|
|
440
180
|
|