@runpod/mcp-server 3.2.0 → 3.3.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 CHANGED
@@ -1,5 +1,71 @@
1
1
  # @runpod/mcp-server
2
2
 
3
+ ## 3.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a62a663: `list-gpu-types` and `get-gpu-type` now always send `product` with their `include=AVAILABILITY` catalog requests, via a new optional `product` parameter (`POD` | `CLUSTER` | `SERVERLESS`, default `POD`). Availability is product-specific — the same GPU can be scarce for Pods and plentiful for Serverless — and the next v2 API release makes `product` required with availability (400 without it), so this keeps the availability lookups working and lets agents ask for the context they actually deploy to: pass `SERVERLESS` when picking a GPU for an endpoint, `CLUSTER` for Instant Clusters. When `includeAvailability` is false, `product` is not sent (the API also rejects `product` without `include=AVAILABILITY`). The CPU catalog tools never request availability, so they are unaffected.
8
+ - 7074c87: Add an `sshPublicKey` parameter to `create-pod` for full SSH access (#73), and fix v2 GPU pod creation failing without an explicit `gpuCount`.
9
+
10
+ The REST API has no SSH switch yet (the console and runpodctl set it via GraphQL `startSsh`), so Pods created through the MCP got a valid-looking port-22 mapping but no authorized key — direct SSH, SCP, SFTP, and rsync all failed. Passing `sshPublicKey` now merges the key into the `PUBLIC_KEY` environment variable and ensures `22/tcp` is exposed, which any image honoring the `PUBLIC_KEY` convention (all runpod/\* official images) turns into a running sshd with the key installed. Template deploys extend the template's ports and env rather than replacing them, and an existing `PUBLIC_KEY` is appended to rather than overwritten.
11
+
12
+ The key is validated before the Pod is created, because an unusable value would otherwise produce a Pod that looks SSH-ready and is not: `22/tcp` exposed, junk in `PUBLIC_KEY`, and a reply reporting SSH as configured. Rejected with a 400: a private key in any format (PEM in any case, or a PuTTY `.ppk`, which contains neither the words "private key" nor a public key line), a file path or SHA256 fingerprint passed instead of the `.pub` file's contents, a bare base64 blob with no key type, and an empty or whitespace-only value (omit the parameter to create a Pod without SSH). Accepted keys are normalized to one key per line with CRLF stripped, since a stray carriage return corrupts the `authorized_keys` entry it lands on. Error messages never echo the rejected value, which may itself be secret.
13
+
14
+ `create-pod` also now rejects a `gpuCount` below 1 or with a fractional part, which the v2 API answers with an opaque 422.
15
+
16
+ Separately, the v2 pod-create mapper omitted `gpu.count` when the caller passed no `gpuCount`, trusting the spec's documented server-side default of 1. In practice the scheduler matches zero machines without it and every create fails with a misleading "no instances available" error (verified live against both v2 hosts). The mapper now always emits `count`, defaulting to 1.
17
+
18
+ - 9176c78: Clamp long-poll tool budgets on the hosted HTTP server, which runs behind a 60-second Vercel function limit. `runsync-endpoint` waited 90 seconds by default (300 via `wait`) and `stream-job` polled for up to 5 minutes, so for a slow job the function was reaped mid-flight: the caller got a bare 504 and every chunk collected so far was discarded.
19
+
20
+ Over HTTP, `runsync-endpoint` now sends a `wait` capped at 45000 ms — a job that outlives it comes back as a job ID plus a non-terminal status to poll with `get-job-status`. `stream-job` stops after 45 seconds over HTTP and returns what it has with `pollingTimedOut: true`; `/stream` drains what it hands out, so calling again resumes rather than replaying. Its HTTP polls also send `wait=1000`, since the server otherwise holds an empty response for 10 seconds and the budget is only checked between polls. Both tool descriptions are now written per transport, so a caller is told the one budget that applies to them and where to go for more — `run-endpoint` + `get-job-status`, or the runtime API directly. The stdio server has no deadline and is unchanged.
21
+
22
+ - f20d987: Give every outbound request a client-side deadline — every tool call, the OAuth handshake on the hosted server, and the install wizard's key check. node-fetch applies no timeout of its own, so a Runpod host that accepted the connection and then went silent — a wedged worker, a load balancer holding the socket — left a tool call pending forever, and on the hosted server that ended as a bare 504 when Vercel reaped the function at its 60s limit.
23
+
24
+ Requests now abort after 30 seconds with a named `RequestTimeoutError` naming the API that went quiet, the deadline it was given, and what to do next. `runsync-endpoint` is the one call that legitimately asks the server to hold a connection open, so it derives its deadline from the `wait` it requested (the server's own 90-second default when `wait` is omitted) rather than being truncated. Successful tool output is unchanged.
25
+
26
+ On the hosted transport the cap is a single budget for the whole invocation rather than a fresh allowance per request, because several tools make more than one call — `get-job-status` adds a queued-job diagnosis, `deploy-hub-repo` and `set-endpoint-gpus` read before they write, `update-endpoint` reads the current scaler before patching it — and two full deadlines back to back outlived the platform even with each one bounded. Each request is clamped to what is left, so a stall anywhere in a handler surfaces as the named error instead of a 504. The queued-job diagnosis, which only decorates a status that is already in hand, is bounded at 5 seconds so it cannot spend a budget the reply itself needs.
27
+
28
+ `stream-job` bounds each poll by the wait the server was asked to hold rather than by its whole budget. A deadline set to the budget meant one wedged socket consumed the entire run in a single attempt — 45 seconds on the hosted server, five minutes on stdio — so the loop's retry path never ran and a stall returned nothing at all. Each poll now gets the hold plus a round trip, which both clears a reply already in flight and leaves the budget room to reconnect; a run that ends on the budget reports `pollingTimedOut` with the last error rather than discarding it.
29
+
30
+ The hosted OAuth routes are bounded too. `/token` polls the flash backend for an approval, and one silent socket there hung the whole handshake until the platform reaped it — the worst place for a blank error, since the user has no credential yet to retry with. Each backend call now has its own 10-second deadline (override with `MCP_FLASH_TIMEOUT_MS`), never exceeding what is left of a 45-second poll budget, and a stall is reported as a named error naming the operation, the host and the deadline. The `runpod-mcp install` wizard's key verification gets the same treatment, so it can no longer sit at "Verifying…" indefinitely.
31
+
32
+ A timed-out GraphQL read is also no longer described as a possible write. The advice keys off the HTTP method and GraphQL is always POST on the wire, so `list-gpu-types` timing out used to tell the agent the call may have landed and to "check with the matching list-/get- tool first" — which is the tool that just failed. Only actual mutations carry that warning now.
33
+
34
+ ### Patch Changes
35
+
36
+ - 2b6480d: Refuse the standalone GET SSE stream with 405. The HTTP server is stateless and never sends server-initiated messages, so the GET "listen" stream has nothing to carry — but the SDK accepted it anyway and held the response open with nothing to send until the platform's maxDuration killed it, at which point every connected client immediately re-opened it. On the hosted deployment that loop was ~1.1M hung requests per day (90% of all traffic), each ending in a 60-second timeout, and it dominated the serverless bill. GET now gets the spec's answer for a server without an SSE stream: 405 Method Not Allowed with an `Allow: POST, DELETE` header and a JSON-RPC error body, before any auth work — a 401 there would send OAuth clients into a pointless re-auth flow. Clients per the MCP spec treat the 405 as "no server-initiated messages offered" and continue POST-only; tool calls are unaffected.
37
+ - 674fa2c: Fix Claude Code detection and registration in the install wizard on Windows (#56).
38
+
39
+ Detection used the POSIX-only `command -v claude` plus three POSIX install paths, so
40
+ Claude Code was undetectable on a standard Windows install. Windows now probes
41
+ `%USERPROFILE%\.local\bin\claude.exe` (native installer) and `%APPDATA%\npm\claude.cmd`
42
+ (global npm), and registration resolves the real entrypoint from the installed package
43
+ manifest instead of running the `.cmd` shim through `cmd.exe`. Candidates are
44
+ canonicalized and confined to their standard install roots, a PATH result resolving into
45
+ the current project is rejected, and Claude Code is spawned with `PATH`, `COMSPEC`,
46
+ `PATHEXT`, `NODE_OPTIONS`, and `NODE_PATH` sanitized, so nothing a project directory can
47
+ plant receives your API key. Custom npm prefixes and redirected roaming profiles are
48
+ still not auto-detected; when Claude Code is not found, the wizard now prints the exact
49
+ `claude mcp` command to register or remove the entry yourself.
50
+
51
+ Claude Code add and remove are now verified against its own `.claude.json` rather than
52
+ the CLI exit code, which reports success for writes it never made. Re-running the wizard
53
+ over an existing entry now says the entry was left unchanged instead of printing a bare
54
+ success, since the CLI does not update it and a rotated API key would otherwise look
55
+ applied. An entry shadowed by a local-scope one is called out with the command to clear
56
+ it.
57
+
58
+ For the clients that own a JSON config, an edit that would leave the file unparseable —
59
+ or land in a duplicated server block the client does not read — now fails with the
60
+ original file untouched instead of reporting success, and validity is judged by the
61
+ parser each client actually ships (Claude Desktop is strict JSON, Cursor and VS Code
62
+ accept JSONC, Windsurf's tolerance is unverified and disclosed rather than assumed).
63
+ Claude Desktop on Linux and VS Code now honour `XDG_CONFIG_HOME` instead of writing
64
+ where those clients never read, an empty or relative `APPDATA`/`XDG_CONFIG_HOME` no
65
+ longer resolves a config path against the current directory, configs this wizard creates
66
+ are `0600` because they hold a plaintext API key, and a key taken from the environment is
67
+ trimmed.
68
+
3
69
  ## 3.2.0
4
70
 
5
71
  ### Minor Changes