@runpod/mcp-server 3.4.0 → 4.0.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 +25 -1
- package/LICENSE +1 -1
- package/README.md +42 -11
- package/THIRD_PARTY_NOTICES.txt +73 -0
- package/dist/chunk-DDX42UTT.mjs +26 -0
- package/dist/chunk-DDX42UTT.mjs.map +1 -0
- package/dist/http.cjs +7195 -3392
- package/dist/http.cjs.map +1 -1
- package/dist/http.d.cts +8 -8
- package/dist/http.d.ts +8 -8
- package/dist/http.mjs +7635 -55
- package/dist/http.mjs.map +1 -1
- package/dist/stdio.cjs +7246 -3407
- package/dist/stdio.cjs.map +1 -1
- package/dist/stdio.mjs +7257 -3435
- package/dist/stdio.mjs.map +1 -1
- package/dist/{wizard-FBLOZ6YM.mjs → wizard-YYU3OXFW.mjs} +5 -3
- package/dist/{wizard-FBLOZ6YM.mjs.map → wizard-YYU3OXFW.mjs.map} +1 -1
- package/package.json +16 -11
- package/dist/chunk-G4URDODF.mjs +0 -3776
- package/dist/chunk-G4URDODF.mjs.map +0 -1
- package/dist/tools.cjs +0 -3799
- package/dist/tools.cjs.map +0 -1
- package/dist/tools.d.cts +0 -95
- package/dist/tools.d.ts +0 -95
- package/dist/tools.mjs +0 -7
- package/dist/tools.mjs.map +0 -1
package/dist/tools.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tools.ts","../src/tools/runtime.ts","../src/_shared/rate-limit.ts","../src/_shared/http.ts","../src/_shared/tracking.ts","../src/tools/logs.ts","../src/_shared/mappers.ts","../src/_shared/backend.ts","../src/tools/catalog.ts","../src/pagination.ts","../src/tools/hub.ts","../src/tools/public-endpoints.ts","../src/tools/endpoint-gpus.ts","../src/tools/pods.ts","../src/tools/endpoints.ts","../src/tools/jobs.ts","../src/tools/templates.ts","../src/tools/network-volumes.ts","../src/tools/registries.ts","../src/tools/billing.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport {\n createToolRuntime,\n type ToolContext,\n type ToolDeps,\n} from './tools/runtime.js';\nimport { registerCatalogTools } from './tools/catalog.js';\nimport { registerHubTools } from './tools/hub.js';\nimport { registerPublicEndpointTools } from './tools/public-endpoints.js';\nimport { registerEndpointGpuTools } from './tools/endpoint-gpus.js';\nimport { registerPodTools } from './tools/pods.js';\nimport { registerEndpointTools } from './tools/endpoints.js';\nimport { registerJobTools } from './tools/jobs.js';\nimport { registerTemplateTools } from './tools/templates.js';\nimport { registerNetworkVolumeTools } from './tools/network-volumes.js';\nimport { registerRegistryTools } from './tools/registries.js';\nimport { registerBillingTools } from './tools/billing.js';\n\n// Re-export so existing importers (entrypoints, tests) keep their import path.\nexport type { ToolContext } from './tools/runtime.js';\n\n/**\n * Register all Runpod tools on the given MCP server instance.\n *\n * The tool surface is split by resource under `src/tools/<resource>.ts`; this\n * function builds the shared runtime (caller-tracking, authenticated REST/\n * Serverless/GraphQL clients, the v1/v2 backend resolver) once and threads it\n * into each per-resource registrar.\n *\n * @param ctx supplies the API key the request helpers authenticate with.\n * @param deps optional test seam (inject a fake fetch / the resolved v2-probe\n * verdict). Production passes nothing.\n */\nexport function registerTools(\n server: McpServer,\n ctx: ToolContext,\n deps: ToolDeps = {}\n): void {\n const rt = createToolRuntime(server, ctx, deps);\n\n registerCatalogTools(server, rt);\n registerHubTools(server, rt);\n registerPublicEndpointTools(server, rt);\n registerPodTools(server, rt);\n registerEndpointTools(server, rt);\n registerEndpointGpuTools(server, rt);\n registerJobTools(server, rt);\n registerTemplateTools(server, rt);\n registerNetworkVolumeTools(server, rt);\n registerRegistryTools(server, rt);\n registerBillingTools(server, rt);\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport fetch from 'node-fetch';\nimport { randomUUID } from 'node:crypto';\nimport {\n clampTimeout,\n createHttpClient,\n EXPIRED_CREDENTIAL_HINT,\n HttpError,\n withRequestTimeout,\n type RequestOptions,\n type TimeoutCeiling,\n} from '../_shared/http.js';\nimport { rateLimitHint } from '../_shared/rate-limit.js';\nimport { buildTrackingHeaders } from '../_shared/tracking.js';\nimport { readSseSnapshot, type SseFetch } from './logs.js';\nimport {\n resolveBackend,\n restV1Base,\n serverlessBase,\n publicGraphqlBase,\n authedGraphqlBase,\n type Env,\n type Backend,\n type Resource,\n} from '../_shared/backend.js';\n\n// ============== TOOL RUNTIME (shared per-server wiring) ==============\n// The per-resource tool modules (src/tools/<resource>.ts) are pure registration\n// functions: each takes the MCP server + this runtime and calls server.tool(...).\n// Everything that depends on the live server instance or the caller's API key —\n// caller-tracking, the authenticated request clients, the v1/v2 backend resolver\n// — is built ONCE here by createToolRuntime and threaded in, so the resource\n// modules stay free of wiring and are individually testable.\n\n// Base URLs are resolved LIVE per call (via restV1Base/serverlessBase/\n// publicGraphqlBase reading process.env), not frozen at module import — so they\n// match callRestUrl's behavior and a test/env change takes effect without a\n// module reload. The defaults live in _shared/backend.ts. Three distinct\n// GraphQL hosts: publicGraphqlBase (credential-free), authedGraphqlBase (the\n// only one this module sends the caller's API key to), and RUNPOD_GRAPHQL_URL\n// (flash OAuth flow in api/index.ts only).\n\n// ============== CALLER TRACKING ==============\n// Adds structured caller identification to every outbound API call so the\n// Runpod platform can attribute traffic to a specific MCP client (Claude Code,\n// Cursor, Codex, Gemini CLI, etc.) and count distinct agent sessions. Pure\n// observability — no tool behavior changes.\n//\n// `__PACKAGE_VERSION__` is replaced at build time by tsup's `define` with the\n// current `version` from package.json. Falls back to `'dev'` when the\n// substitution doesn't happen (e.g. `pnpm dev` via tsx).\ndeclare const __PACKAGE_VERSION__: string;\nconst MCP_SERVER_VERSION =\n typeof __PACKAGE_VERSION__ !== 'undefined' ? __PACKAGE_VERSION__ : 'dev';\n\n// One session ID per server process. Anonymous; lets the data side count\n// distinct agent sessions without per-user attribution.\nconst SESSION_ID = randomUUID();\n\n/**\n * Context passed to every tool handler. Contains the API key used to\n * authenticate against the Runpod REST and Serverless APIs, the transport the\n * server is running under, and (for stateless HTTP) the inbound client\n * User-Agent — all used for outbound caller-tracking headers.\n */\nexport interface ToolContext {\n apiKey: string;\n transport: 'stdio' | 'http';\n clientUserAgent?: string;\n serverVersion?: string;\n // Called when an outbound call answers 401, i.e. the credential just died.\n // The hosted server uses it to drop the cached pre-flight verdict so the very\n // next request re-checks and can emit the 401 + WWW-Authenticate that makes a\n // client re-authenticate, instead of waiting out the cache TTL.\n onUnauthorized?: () => void;\n}\n\n/**\n * Builds the headers that identify the calling MCP client and session on every\n * outbound HTTP call. Client identity comes from the `initialize` handshake's\n * `clientInfo` (exposed by the SDK via `server.server.getClientVersion()`),\n * which works for the long-lived stdio server. In stateless HTTP that handshake\n * is on a different request, so we fall back to the inbound HTTP User-Agent.\n *\n * Resolution of clientInfo → fallback happens here (the SDK touch); the pure\n * string formatting lives in `_shared/tracking.ts`.\n */\nfunction trackingHeaders(\n server: McpServer,\n ctx: ToolContext\n): Record<string, string> {\n const info = server.server.getClientVersion();\n return buildTrackingHeaders({\n clientName: info?.name || ctx.clientUserAgent || 'unknown',\n clientVersion: info?.version || 'unknown',\n transport: ctx.transport,\n // Prefer a server version supplied by the caller (the hosted entrypoint reads\n // it from package.json at runtime, since tsup's build-time define does not\n // run when Vercel compiles api/index.ts). Fall back to the build-time value.\n serverVersion: ctx.serverVersion || MCP_SERVER_VERSION,\n sessionId: SESSION_ID,\n });\n}\n\n// A BACKSTOP for a wedged socket, not a budget. It has to clear the wait\n// runsync asks the server to hold (or we abort a reply in flight) and still\n// fire before the platform — measured from the INVOCATION, which starts with\n// the 4s credential pre-flight, not from our request. That is what makes 55s\n// wrong. Both bounds asserted in tests/http.test.ts.\n//\n// 0s invocation starts\n// 4s credential pre-flight, worst case, before any tool request\n// 54s this backstop fires\n// 60s vercel.json maxDuration\nexport const HTTP_TRANSPORT_BUDGET_MS = 50_000;\n\n// Spent across the whole invocation, not per request: a handler that issues two\n// calls (get-job-status, deploy-hub-repo, update-endpoint) would otherwise get\n// two full deadlines and outlive the platform anyway. Safe to anchor at runtime\n// construction because on http the server, and so this runtime, is built per\n// request and disposed with the response (src/http.ts).\n// Exported for the test that pins the reset: if registerTools were ever hoisted\n// out of the http request handler to save cold-start time, this window would\n// keep decaying across requests and every request after the first would run on\n// the floor — a silent, load-shaped failure rather than an obvious one.\nexport function invocationBudget(totalMs: number): () => number {\n const startedAt = Date.now();\n return () => totalMs - (Date.now() - startedAt);\n}\n\n// The fetch implementation the unified client uses. Defaults to node-fetch;\n// tests inject a fake to capture outbound requests offline (the A4 seam).\nexport type HttpFetch = Parameters<typeof createHttpClient>[0]['fetch'];\nconst defaultFetch = fetch as HttpFetch;\n\n// Wrap a unified-client call with the legacy `console.error(label) + re-throw`\n// the pre-adapter runpodRequest/serverlessRequest helpers had. Centralizes the\n// identical try/catch blocks into one place.\nasync function withApiErrorLog<T>(\n label: string,\n call: () => Promise<T>\n): Promise<T> {\n try {\n return await call();\n } catch (error) {\n console.error(label, error);\n throw error;\n }\n}\n\n// The standard MCP tool reply: a single text block holding pretty-printed JSON.\n// Every tool returns this shape.\nexport function jsonReply(result: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n}\n\n// Shared MCP tool-annotation presets (advisory hints clients use to label tools\n// and gate confirmations). `openWorldHint` is true on all of these — every tool\n// talks to the external Runpod API. READ_ONLY = pure reads; WRITE = creates/\n// mutations; DESTRUCTIVE = deletes (also idempotent: re-deleting a gone resource\n// is a no-op). Spread a preset and add a per-tool `title`.\nexport const READ_ONLY = { readOnlyHint: true, openWorldHint: true };\nexport const WRITE = { readOnlyHint: false, openWorldHint: true };\nexport const DESTRUCTIVE = {\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: true,\n};\n\n// Optional test seam: inject a fake fetch to capture outbound requests offline.\n// Production passes nothing → node-fetch. `v2Available` is the already-resolved\n// stdio `auto`-probe verdict (see backend.resolveVersion); omitted → `auto`\n// resolves to v1.\nexport interface ToolDeps {\n fetch?: HttpFetch;\n v2Available?: boolean;\n // Test seam for the SSE log stream (stream-pod-logs). Production builds a\n // node-fetch reader; tests inject a fake returning canned event-stream text.\n streamSse?: StreamSse;\n // Lower-level seam: the fetch the SSE reader uses. `streamSse` replaces the\n // whole reader (and so bypasses the 401 observer); this replaces only its\n // transport, which is what lets a test drive an SSE 401 through the observer.\n sseFetch?: SseFetch;\n // Test seam: shrink the deadline so a suite need not wait out the real one.\n defaultTimeoutMs?: number;\n // Ceiling on every request's deadline, including one a tool asks to lengthen\n // (stream-job's per-poll hold, runsync's ?wait=). The hosted transport\n // installs the remaining invocation budget here; a test pins it low so a\n // stalled socket aborts in milliseconds instead of seconds.\n maxTimeoutMs?: TimeoutCeiling;\n}\n\n// A bounded Server-Sent-Events read. Returns the raw accumulated stream text\n// (caller parses frames) plus whether the byte cap truncated it. Time-bounded\n// by maxWaitMs (the stream may stay open for live tail), so a clean timeout is\n// a normal end, not an error.\nexport interface StreamSseResult {\n raw: string;\n truncated: boolean;\n}\nexport type StreamSse = (\n url: string,\n opts: { maxWaitMs: number; maxBytes: number }\n) => Promise<StreamSseResult>;\n\n/**\n * The shared, server-instance-bound runtime threaded into every per-resource\n * registrar. Built once by createToolRuntime.\n */\nexport interface ToolRuntime {\n // Pretty-printed JSON reply helper (re-exported for convenience).\n jsonReply: typeof jsonReply;\n // Public, no-auth GraphQL query against the Runpod GraphQL endpoint.\n graphql: <T>(query: string) => Promise<T>;\n // Authenticated GraphQL operation (API-key Bearer) against the same endpoint,\n // with variables support — the authenticated path accepts them (the public,\n // unauthenticated path does not). Used for console-only operations that have\n // no REST home yet (e.g. deploying a Hub release via saveEndpoint).\n graphqlAuthed: <T>(\n query: string,\n variables?: Record<string, unknown>\n ) => Promise<T>;\n // Authenticated v1 REST call, path-relative to the v1 base (e.g. `/endpoints`).\n\n runpodRequest: (\n endpoint: string,\n method?: string,\n body?: Record<string, unknown>,\n options?: RequestOptions\n ) => Promise<unknown>;\n // Authenticated Serverless runtime call (api.runpod.ai/v2/{endpointId}{path}).\n serverlessRequest: (\n endpointId: string,\n path: string,\n method?: string,\n body?: Record<string, unknown>,\n options?: RequestOptions\n ) => Promise<unknown>;\n // Authenticated REST call to a fully-resolved URL (the adapter builds the URL).\n callRestUrl: (\n url: string,\n method?: string,\n body?: Record<string, unknown>,\n options?: RequestOptions\n ) => Promise<unknown>;\n // Resolve a resource's v1/v2 backend descriptor for the current env/transport.\n backendFor: (resource: Resource) => Backend;\n // Bounded SSE reader (stream-pod-logs). Authenticated + tracked; reads a\n // text/event-stream until the byte cap or the time bound, returning the raw\n // accumulated text. Throws HttpError on a non-OK response.\n streamSse: StreamSse;\n // Pod state transition (v1 subpaths vs v2 unified /action).\n podAction: (\n podId: string,\n action: 'start' | 'stop' | 'restart'\n ) => Promise<unknown>;\n // The process env, exposed for the few handlers that resolve base URLs\n // directly (e.g. create-pod's CPU → v1 fallback).\n env: Env;\n // Which transport this server runs under. The hosted HTTP server lives inside\n // a serverless function with a hard platform deadline (Vercel maxDuration),\n // so long-poll tools clamp their wait budgets on 'http' (see jobs.ts) instead\n // of planning more seconds than the platform will give them.\n transport: 'stdio' | 'http';\n}\n\n// Helper to make GraphQL requests to Runpod (public, no auth required). Bound to\n// the per-request tracking headers AND the injected fetch by createToolRuntime —\n// it must use the same `httpFetch` seam as the REST clients, NOT the module-level\n// node-fetch, or test fakes wouldn't intercept the v1 catalog (list-gpu-types /\n// list-data-centers) GraphQL calls and those \"offline\" goldens would hit the net.\nasync function graphqlRequest<T>(\n query: string,\n tracking: () => Record<string, string>,\n fetchImpl: HttpFetch,\n url: string,\n options?: {\n variables?: Record<string, unknown>;\n apiKey?: string;\n // See http.ts.\n timeoutMs?: number;\n // Remaining invocation budget, same thunk the REST clients are capped by.\n // deploy-hub-repo spends two GraphQL calls in one invocation.\n maxTimeoutMs?: TimeoutCeiling;\n }\n): Promise<T> {\n // Builds its own request rather than going through createHttpClient, so the\n // deadline is wired separately — otherwise list-gpu-types / get-capacity\n // against a wedged host hang the way the REST calls used to.\n return withRequestTimeout(\n 'Runpod GraphQL Error',\n clampTimeout(options?.timeoutMs, options?.maxTimeoutMs),\n // GraphQL is POST on the wire whatever it carries, but the retry advice keys\n // off the method, and telling a caller its list-gpu-types \"may have\n // SUCCEEDED upstream — check with the matching list-/get- tool first\" points\n // it back at the tool that just failed. The operation keyword is what\n // actually says whether anything could have been written.\n isGraphqlMutation(query) ? 'POST' : 'GET',\n (signal) => runGraphql<T>(query, tracking, fetchImpl, url, signal, options)\n );\n}\n\n// Anonymous operations (`{ gpuTypes { ... } }`) and the explicit `query` keyword\n// are both reads; only `mutation` writes. Leading whitespace and comments are\n// the shapes that actually occur in this file's template literals.\nexport function isGraphqlMutation(query: string): boolean {\n const firstMeaningful = query\n .split('\\n')\n .map((line) => line.trim())\n .find((line) => line.length > 0 && !line.startsWith('#'));\n return /^mutation\\b/.test(firstMeaningful ?? '');\n}\n\nasync function runGraphql<T>(\n query: string,\n tracking: () => Record<string, string>,\n fetchImpl: HttpFetch,\n url: string,\n signal: AbortSignal,\n options?: {\n variables?: Record<string, unknown>;\n apiKey?: string;\n }\n): Promise<T> {\n const response = await fetchImpl(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(options?.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {}),\n ...tracking(),\n },\n body: JSON.stringify({\n query,\n ...(options?.variables ? { variables: options.variables } : {}),\n }),\n signal,\n });\n\n // HTTP-level failures used to fall through to response.json() and surface\n // as an opaque parse error (\"Unexpected token '<'\" for a 429/5xx HTML\n // body). Handle them like the REST client (http.ts): status + body + a\n // wait hint on 429. A non-OK response whose body still carries a GraphQL\n // errors array keeps the readable GraphQL message (servers commonly return\n // 400 for malformed queries), with the status attached.\n if (!response.ok) {\n const bodyText = await response.text();\n let gqlErrors: Array<{ message: string }> | undefined;\n try {\n gqlErrors = (\n JSON.parse(bodyText) as { errors?: Array<{ message: string }> }\n ).errors;\n } catch {\n // Not JSON (HTML error page, empty body) — the HttpError below carries\n // the raw body instead.\n }\n // Array.isArray, not truthiness: proxies/WAFs emit shapes like\n // {\"errors\":\"internal failure\"}, where a non-empty string passes a\n // .length check and then .map() throws — a worse error than the parse\n // error this handling exists to eliminate. 401/429 fall through to\n // the hinted HttpError below, so the re-auth and rate-limit hints are\n // never suppressed by a prettier message. Still an HttpError (with the\n // extracted messages as the body) so `.status` stays machine-readable.\n if (\n Array.isArray(gqlErrors) &&\n gqlErrors.length > 0 &&\n response.status !== 401 &&\n response.status !== 429\n ) {\n throw new HttpError(\n 'Runpod GraphQL Error',\n response.status,\n gqlErrors.map((e) => String(e?.message ?? JSON.stringify(e))).join(', ')\n );\n }\n throw new HttpError(\n 'Runpod GraphQL Error',\n response.status,\n bodyText,\n response.status === 429\n ? rateLimitHint(\n response.headers.get('ratelimit'),\n response.headers.get('retry-after')\n )\n : // Only when this request actually carried the caller's API key: the\n // public path sends no Authorization header, so a 401 from that\n // host (WAF, misconfigured RUNPOD_PUBLIC_GRAPHQL_URL) says nothing\n // about the credential — same rationale as observeUnauthorized.\n response.status === 401 && options?.apiKey\n ? EXPIRED_CREDENTIAL_HINT\n : undefined\n );\n }\n\n const result = (await response.json()) as {\n data?: T;\n errors?: Array<{ message: string }>;\n };\n\n if (result.errors && result.errors.length > 0) {\n throw new Error(\n `GraphQL Error: ${result.errors.map((e) => e.message).join(', ')}`\n );\n }\n\n if (!result.data) {\n throw new Error('No data returned from GraphQL query');\n }\n\n return result.data;\n}\n\n/**\n * Build the shared runtime once per registerTools call. Constructs the\n * caller-tracking closure, the authenticated REST/Serverless/GraphQL clients,\n * and the version-aware backend resolver — all from the live server + context.\n */\nexport function createToolRuntime(\n server: McpServer,\n ctx: ToolContext,\n deps: ToolDeps = {}\n): ToolRuntime {\n const tracking = () => trackingHeaders(server, ctx);\n\n // Report a 401 once, at the transport boundary, instead of at each of the ~30\n // call sites. Applied to BOTH outbound fetch seams: the JSON clients (REST\n // v1/v2, serverless, GraphQL) and the SSE reader, which binds its own fetch\n // and would otherwise let stream-pod-logs / stream-worker-logs 401s go\n // unreported. Pass-through when no observer is registered (stdio).\n function observeUnauthorized<\n A extends unknown[],\n R extends { status: number },\n >(fn: (...args: A) => Promise<R>): (...args: A) => Promise<R> {\n const notify = ctx.onUnauthorized;\n if (!notify) return fn;\n return async (...args: A) => {\n const response = await fn(...args);\n if (response.status === 401) notify();\n return response;\n };\n }\n\n // Only the AUTHENTICATED seams are observed. The public GraphQL query sends no\n // Authorization header at all, so a 401 from that host (a WAF block, say) says\n // nothing about the caller's credential — observing it would drop a good verdict\n // and force every caller back through the pre-flight.\n const rawFetch = deps.fetch ?? defaultFetch;\n const httpFetch = observeUnauthorized(rawFetch);\n\n // Deadline policy for all three JSON clients. One budget object shared by all\n // of them, so a handler that crosses clients (get-job-status: serverless, then\n // REST for the diagnosis) draws down a single allowance.\n const remainingHttpBudgetMs = invocationBudget(HTTP_TRANSPORT_BUDGET_MS);\n const timeouts = {\n defaultTimeoutMs: deps.defaultTimeoutMs,\n maxTimeoutMs:\n deps.maxTimeoutMs ??\n (ctx.transport === 'http' ? remainingHttpBudgetMs : undefined),\n };\n\n // v1 REST client (path-relative to the v1 base).\n const v1Client = createHttpClient({\n apiKey: ctx.apiKey,\n fetch: httpFetch,\n tracking,\n errorPrefix: 'Runpod API Error',\n ...timeouts,\n });\n const runpodRequest = (\n endpoint: string,\n method: string = 'GET',\n body?: Record<string, unknown>,\n options?: RequestOptions\n ) =>\n withApiErrorLog('Error calling Runpod API:', () =>\n v1Client(\n `${restV1Base(process.env as Env)}${endpoint}`,\n method,\n body,\n options\n )\n );\n\n // Serverless runtime client (endpointId + path against the serverless base).\n const serverlessClient = createHttpClient({\n apiKey: ctx.apiKey,\n fetch: httpFetch,\n tracking,\n errorPrefix: 'Runpod Serverless API Error',\n ...timeouts,\n });\n const serverlessRequest = (\n endpointId: string,\n path: string,\n method: string = 'GET',\n body?: Record<string, unknown>,\n options?: RequestOptions\n ) =>\n withApiErrorLog('Error calling Runpod Serverless API:', () =>\n serverlessClient(\n `${serverlessBase(process.env as Env)}/${endpointId}${path}`,\n method,\n body,\n options\n )\n );\n\n // v2-aware REST routing (keystone): a full-URL REST client (the adapter\n // resolves base+path per version) used by all adapter-routed resources.\n const restClient = createHttpClient({\n apiKey: ctx.apiKey,\n fetch: httpFetch,\n tracking,\n errorPrefix: 'Runpod API Error',\n ...timeouts,\n });\n const callRestUrl = (\n url: string,\n method: string = 'GET',\n body?: Record<string, unknown>,\n options?: RequestOptions\n ): Promise<unknown> =>\n withApiErrorLog('Error calling Runpod API:', () =>\n restClient(url, method, body, options)\n );\n\n // Bounded SSE reader for stream-pod-logs / stream-worker-logs. Uses node-fetch\n // directly (not the JSON client) so the response body is read incrementally and\n // stopped on the byte cap or the time bound. The read loop itself lives in\n // logs.ts (readSseSnapshot) so it is unit-testable against a mock Response; here\n // we just bind it to node-fetch + the authenticated headers. Overridable via\n // deps for offline handler tests.\n const sseFetch = observeUnauthorized(\n deps.sseFetch ?? (fetch as unknown as SseFetch)\n );\n const streamSse: StreamSse =\n deps.streamSse ??\n ((url, opts) =>\n readSseSnapshot(\n sseFetch,\n url,\n {\n Authorization: `Bearer ${ctx.apiKey}`,\n Accept: 'text/event-stream',\n ...tracking(),\n },\n opts\n ));\n\n const backendFor = (resource: Resource): Backend =>\n resolveBackend({\n resource,\n env: process.env as Env,\n ctx: { transport: ctx.transport },\n v2Available: deps.v2Available,\n });\n\n // B4: pod state transition. v1 has dedicated subpaths (/start, /stop); v2\n // unifies them under POST /pods/{id}/action with an `{action}` body.\n const podAction = async (\n podId: string,\n action: 'start' | 'stop' | 'restart'\n ): Promise<unknown> => {\n const backend = backendFor('pods');\n const podPath = backend.get!(podId);\n if (backend.version === 'v2') {\n return callRestUrl(`${backend.base}${podPath}/action`, 'POST', {\n action,\n });\n }\n return callRestUrl(`${backend.base}${podPath}/${action}`, 'POST');\n };\n\n return {\n jsonReply,\n // No GraphQL caller overrides a deadline, but both are still capped by the\n // shared budget: deploy-hub-repo spends one of each back to back.\n graphql: <T>(query: string) =>\n graphqlRequest<T>(\n query,\n tracking,\n // rawFetch, not httpFetch: this call carries no credential (see above).\n rawFetch,\n publicGraphqlBase(process.env as Env),\n {\n timeoutMs: deps.defaultTimeoutMs,\n maxTimeoutMs: timeouts.maxTimeoutMs,\n }\n ),\n graphqlAuthed: <T>(query: string, variables?: Record<string, unknown>) =>\n graphqlRequest<T>(\n query,\n tracking,\n httpFetch,\n // NOT publicGraphqlBase: this call carries the caller's API key.\n authedGraphqlBase(process.env as Env),\n {\n variables,\n apiKey: ctx.apiKey,\n timeoutMs: deps.defaultTimeoutMs,\n maxTimeoutMs: timeouts.maxTimeoutMs,\n }\n ),\n runpodRequest,\n serverlessRequest,\n callRestUrl,\n backendFor,\n streamSse,\n podAction,\n env: process.env as Env,\n transport: ctx.transport,\n };\n}\n","// ============== 429 RATE-LIMIT HINT ==============\n// A 429's bare \"rate limit exceeded\" invites the worst possible agent\n// response: retrying immediately. This turns one into a wait instruction,\n// reading the IETF `RateLimit` field and `Retry-After`. Kept out of the HTTP\n// client because it shares nothing with it — two strings in, one string out —\n// and because both the client and the SSE log reader need it.\n\n// No window names or numbers here: this same client serves the serverless host\n// (api.runpod.ai), whose quota shape this repo doesn't document, so naming\n// minute/hour/day windows would assert a REST fact somewhere it may not hold.\nconst GENERIC_RATE_LIMIT_HINT =\n 'rate limited — back off before retrying, and pace bulk operations';\n\n// The longest window this API documents is a day; the slack covers an\n// undocumented serverless policy without printing \"~1157407408 days\" for a\n// broken or hostile header, which the digit count alone would not bound.\n// Rejecting is cheap either way: a discarded reset never shortens the wait —\n// it hedges it when another source still answers, and otherwise leaves the\n// \"no usable reset time\" clause to say so outright.\nconst MAX_PLAUSIBLE_WAIT_S = 7 * 86400;\n\n// One window reporting zero requests left. `window` is null when the name was\n// unreadable — the reset is independently trustworthy, and dropping the item\n// would throw away a correct number to avoid printing a doubtful name.\n// `resetS` is null when the window sent no readable `t`: the parameter is\n// optional, and a value that is unparseable (`t=1.5`), implausible (past the\n// cap) or valueless (`t` alone) all read the same way — absent. The member\n// still counts, because its `r` is what said the window is out.\ninterface ExhaustedWindow {\n window: string | null;\n resetS: number | null;\n}\n\n// A count of remaining requests. Only ever compared against zero, so its\n// magnitude doesn't matter — but blank must not pass: `Number('')` is 0, which\n// would read `\"hour\";r=;t=` as a real exhausted window.\nfunction remainingRequests(raw: string | undefined): number | null {\n const value = raw?.trim();\n return value && /^\\d+$/.test(value) ? Number(value) : null;\n}\n\n// A number of seconds to wait, from `t` or from `Retry-After`. Both are plain\n// digit strings — `1.5`, `-30`, `0x10` and `1e3` are not, and `Number()` would\n// happily coerce every one of them.\nfunction waitSeconds(raw: string | null | undefined): number | null {\n const value = raw?.trim();\n if (!value || !/^\\d+$/.test(value)) return null;\n const seconds = Number(value);\n return seconds <= MAX_PLAUSIBLE_WAIT_S ? seconds : null;\n}\n\n// A window name, or null if it isn't one. The name lands verbatim in an error\n// message an agent reads, so validate rather than sanitize — including its\n// length. Truncating would print a name that isn't in the header, which is the\n// same defect as stripping the parts that don't belong: `\"a,b\"` reported as\n// \"the b quota\". A quoted name takes any UNESCAPED string character, so\n// `\"gpu hour\"` is named rather than dropped; a `\\`-escape or a display string\n// is legal but unsupported, and dropping the name beats printing one we\n// haven't unescaped. Bare names follow the token grammar.\nconst WINDOW_NAME_RE =\n /^(?:\"([\\x20-\\x21\\x23-\\x5b\\x5d-\\x7e]{1,32})\"|([A-Za-z*][A-Za-z0-9!#$%&'*+.^_`|~:/-]{0,31}))$/;\nfunction windowName(raw: string): string | null {\n const match = WINDOW_NAME_RE.exec(raw.trim());\n return match ? (match[1] ?? match[2]) : null;\n}\n\n// A parameter value that is a complete quoted string and nothing else. Testing\n// only the first and last character would pass `pk=\"x\" \"minute\"`, where a\n// second item was glued on after a legal value — the same merge as below, one\n// quote further along.\nconst QUOTED_VALUE_RE = /^\"(?:[\\x20-\\x21\\x23-\\x5b\\x5d-\\x7e]|\\\\[\"\\\\])*\"$/;\n// A display string is a legal parameter value too (`%\"caf%c3%a9\"`). Reading it\n// as malformed would silently collapse every hint on the day the API adds one,\n// which is the failure this file exists to avoid — so match it, still whole.\nconst DISPLAY_VALUE_RE =\n /^%\"(?:[\\x20-\\x21\\x23-\\x24\\x26-\\x5b\\x5d-\\x7e]|\\\\|%[0-9a-f]{2})*\"$/;\n\n// Whether a `;`-separated chunk is a well-formed parameter. A dropped comma\n// glues the next item onto the previous parameter's VALUE — `t=3600 \"minute\"`,\n// or `t=3600 minute` in the token form — and last-wins then merges the two\n// items under the FIRST one's name and the SECOND one's reset, which is how a\n// 3600s lockout gets answered with \"wait ~10s\". A bare value can hold no\n// whitespace (every unquoted item type excludes it) and no quote, so either\n// means two items ran together. The draft says a malformed field MUST be\n// ignored wholesale; dropping just the item keeps more signal and still never\n// over-reports, because `dropped` forces the \"at least\".\nfunction isWellFormedParam(param: string): boolean {\n const eq = param.indexOf('=');\n const key = (eq === -1 ? param : param.slice(0, eq)).trim();\n if (key.includes('\"')) return false;\n if (eq === -1) return true;\n const value = param.slice(eq + 1).trim();\n if (value.startsWith('\"')) return QUOTED_VALUE_RE.test(value);\n if (value.startsWith('%\"')) return DISPLAY_VALUE_RE.test(value);\n return !/[\\s\"]/.test(value);\n}\n\n// Split on `sep` only where it isn't inside a quoted string. A plain\n// `String.split` cuts `\"a,b\";r=0;t=1812` — a legal window name — in half,\n// losing that window's r/t entirely, and if a shorter window survives alongside\n// it the hint confidently reports the shorter wait. Same for a vendor parameter\n// the draft leaves untyped (`acme-scope=\"a,b\"`). Strings are the only construct\n// that can hide a delimiter: `pk` is a byte sequence (`pk=:QXBwLTk5OQ==:`),\n// whose base64 alphabet holds neither. An unterminated quote yields one chunk,\n// which then fails validation — as a name if the quote opened in the name\n// position, as a parameter otherwise. Malformed in, generic out.\nfunction splitOutsideStrings(input: string, sep: ',' | ';'): string[] {\n const parts: string[] = [];\n let current = '';\n let inString = false;\n // A display string opens with `%\"`, and inside one a backslash is a literal\n // character rather than an escape — treating it as an escape would eat the\n // closing quote and swallow the rest of the field.\n let inDisplay = false;\n let escaped = false;\n let prev = '';\n for (const ch of input) {\n if (escaped) escaped = false;\n else if (inString && !inDisplay && ch === '\\\\') escaped = true;\n else if (ch === '\"') {\n if (!inString) inDisplay = prev === '%';\n inString = !inString;\n } else if (ch === sep && !inString) {\n parts.push(current);\n current = '';\n prev = ch;\n continue;\n }\n current += ch;\n prev = ch;\n }\n parts.push(current);\n return parts;\n}\n\n// Split-based rather than one regex: the field is a structured-field list, so\n// params may be reordered (`\"hour\";t=1724;r=0`), spaced, or carry unknown\n// vendor keys, and `t` may be absent — a pattern pinned to `r=<n>;t=<n>` yields\n// nothing on variants that mean the same thing, and silence here costs the\n// specifics exactly when they matter. Never throws.\n//\n// `dropped` reports whether anything was thrown away unread — only a malformed\n// item, since an unreadable name keeps its numbers. It has to reach the caller:\n// otherwise a discarded `r=0;t=3600` lets a readable 10-second sibling answer\n// as if it were the whole truth.\ninterface ParsedWindows {\n windows: ExhaustedWindow[];\n dropped: boolean;\n}\n\n// A member's parameters, and whether reading them left `r` or `t` unreadable.\n// Three tells, the first two being shapes a lost comma produces:\n// - a valueless parameter FOLLOWED BY `r` or `t`. Valueless is the only legal\n// way to send a Boolean-true flag (RFC 9651 §3.1.2: a true value MUST be\n// omitted), but it is also exactly what a glued bare-token name looks like —\n// `\"minute\";t=10;hour;r=0` is one item with an `hour` flag, or two items and\n// a lost comma, and nothing in the field tells them apart. Only a number\n// AFTER the flag can have been misattributed, so a trailing flag\n// (`\"hour\";r=0;t=3600;acme-flag`) costs nothing and is kept.\n// - `r` or `t` twice. RFC 9651's note on parsing parameters — \"when duplicate\n// parameter keys are encountered, all but the last instance are ignored\" —\n// carries no RFC 2119 keyword, and `minute;r=0;t=2876;minute;r=0;t=1641` is\n// the same glue, where last-wins answers a 2876s lockout with 1641s.\n// - `r` or `t` sent valueless at all, glue or not. A bare flag is Boolean\n// true, so `\"hour\";r=0;t` states no reset and `\"hour\";r=0;r` never says\n// the window is out — the value simply isn't there to read.\n// In every case we can't say what the number is, or whether it is ours, so\n// don't pick one.\nfunction readParams(params: string[]): {\n values: Map<string, string>;\n unreadable: boolean;\n} {\n const values = new Map<string, string>();\n let unreadable = false;\n let valuelessAt = -1;\n for (const [index, param] of params.entries()) {\n const eq = param.indexOf('=');\n // Keys are lowercase in the grammar; fold case rather than miss an\n // exhausted window over a server sending `R=0`.\n const key = (eq === -1 ? param : param.slice(0, eq)).trim().toLowerCase();\n if (!key) continue;\n if (eq === -1) {\n // A bare `r`/`t` is Boolean true, and parameters are last-wins, so the\n // member's own count or reset is not there to read — `t=3599;t` must not\n // quote 3599 as fact, nor `r=0;t=3599;r` call a window exhausted unasked.\n // Checked before the `continue`, or neither would be caught at all.\n //\n // The two differ in what survives. `r` is REQUIRED, so losing it loses\n // the only thing that says the window is out: nothing to report. `t` is\n // optional, and a member that states `r=0` with no readable reset is an\n // ordinary \"exhausted, reset unknown\" — already modelled, and already\n // what an unparseable `t=1.5` produces. Dropping the whole member there\n // would throw away a readable `r=0` to avoid an unreadable `t`.\n if (key === 'r') unreadable = true;\n if (key === 't') values.delete('t');\n valuelessAt = index;\n continue;\n }\n // A repeated `r`/`t`, or one that follows a valueless flag: either way\n // we can't say which name the number belongs to.\n if (\n (key === 'r' || key === 't') &&\n (values.has(key) || (valuelessAt !== -1 && index > valuelessAt))\n ) {\n unreadable = true;\n }\n values.set(key, param.slice(eq + 1));\n }\n return { values, unreadable };\n}\n\nfunction parseExhaustedWindows(header: string | null): ParsedWindows {\n if (!header) return { windows: [], dropped: false };\n const windows: ExhaustedWindow[] = [];\n let dropped = false;\n for (const item of splitOutsideStrings(header, ',')) {\n if (item.trim() === '') continue;\n const [rawName, ...params] = splitOutsideStrings(item, ';');\n if (!params.every(isWellFormedParam)) {\n dropped = true;\n continue;\n }\n const { values, unreadable } = readParams(params);\n if (unreadable) {\n dropped = true;\n continue;\n }\n // draft-11 §4.1 makes `r` REQUIRED, so a member without a readable one is\n // malformed: we know a window exists but not whether it is exhausted, which\n // hides at least as much as an item rejected outright. A readable non-zero\n // `r` is different — that window simply has requests left.\n const remaining = remainingRequests(values.get('r'));\n if (remaining === null) {\n dropped = true;\n continue;\n }\n if (remaining !== 0) continue;\n // A name we can't read costs us the name, not the reset behind it.\n windows.push({\n window: windowName(rawName),\n resetS: waitSeconds(values.get('t')),\n });\n }\n return { windows, dropped };\n}\n\n// \"1724s\" is easy to skim past; \"~29 minutes\" is not, and an agent that grasps\n// the scale is the one that stops retrying. Exact seconds stay in the string,\n// so the unit is only ever a reading aid — but an agent that skims the unit and\n// not the parenthesis must not be sent back early, so each tier rounds UP: 140s\n// reads \"~3 minutes\", never \"~2 minutes\". The minute and hour tiers hand over\n// before their count reaches three digits, a reading as hard to skim as the raw\n// seconds it would replace.\nfunction humanWait(seconds: number): string {\n if (seconds < 120) return `~${seconds}s`;\n if (seconds < 3600)\n return `~${Math.ceil(seconds / 60)} minutes (${seconds}s)`;\n if (seconds < 2 * 86400) {\n const hours = Math.ceil(seconds / 3600);\n return `~${hours} hour${hours === 1 ? '' : 's'} (${seconds}s)`;\n }\n // The days tier opens at two days, so it is never singular.\n return `~${Math.ceil(seconds / 86400)} days (${seconds}s)`;\n}\n\n// Name the exhausted quota and say how long to wait, from the two headers v2\n// sends on a 429 — `Retry-After` (seconds) and `RateLimit` (live per-window\n// state, `\"minute\";r=0;t=12, \"hour\";r=2800;t=1812`, where r = calls left and\n// t = seconds to reset).\n//\n// Report the LONGEST wait either header justifies. draft-11 §7 says Retry-After\n// MUST take precedence, but §6 says a server SHOULD NOT send one earlier than\n// the end of the effective window, and this API's is documented \"per the\n// exceeded window\" (tests/fixtures/v2-openapi.yaml) — i.e. it can be earlier.\n// RFC 9110 §10.2.3 defines Retry-After as how long a client \"ought to wait\" and\n// sets no upper bound (its \"minimum time\" wording is scoped to 3xx), so waiting\n// longer conforms. §7's \"MAY be ignored\" is permission, not obligation, and\n// max() never waits less than Retry-After asks — so this declines the\n// permission rather than violating the precedence. Windows can read r=0\n// together — a loop pacing itself at the 60/min quota sits at minute r=0, and\n// 3000/hr is an exact multiple of 60, so the call that zeroes the hour is also\n// the 60th of its minute — and Retry-After then describes only the window that\n// rejected THIS request. Obeying it alone sends the agent back in 12s to a\n// quota that is out for another half hour. Hence \"wait ~N\" rather than \"the\n// hour quota resets in ~N\": the number is an instruction, not a claim about the\n// named window, so the two can never contradict each other. Generic guidance\n// when neither header is usable — the v1 REST API sends neither, and this\n// client also serves the serverless host, which this repo doesn't document.\nexport function rateLimitHint(\n rateLimitHeader: string | null,\n retryAfterHeader?: string | null\n): string {\n const { windows, dropped } = parseExhaustedWindows(rateLimitHeader);\n // Longest reset first; a window that sent no `t` sorts last, since an\n // unknown reset can't outrank a known one. Ties keep header order.\n windows.sort((a, b) => (b.resetS ?? -1) - (a.resetS ?? -1));\n // Typed explicitly: `strict` is on but `noUncheckedIndexedAccess` is not, so\n // `windows[0]` types as non-optional and every `?.` below reads as dead. It\n // isn't — `windows` is empty on every generic-hint path.\n const longestWindow: ExhaustedWindow | undefined = windows[0];\n // A wait of zero — from `t` or from `Retry-After` — is the one instruction a\n // 429 can't mean, so it is not a wait on offer from either source. An\n // HTTP-date Retry-After (legal per RFC 9110, not what this API sends) or\n // junk is likewise rejected, leaving the RateLimit reset to answer.\n const waitCandidates = [\n waitSeconds(retryAfterHeader),\n longestWindow?.resetS,\n ].filter((n): n is number => typeof n === 'number' && n > 0);\n const waitS = waitCandidates.length ? Math.max(...waitCandidates) : null;\n // A Retry-After we couldn't read is a missed instruction, not an absent one:\n // the server named a delay, we don't know it, and it may be far longer than\n // anything RateLimit says. An HTTP-date, `3600.5`, `+1800` and a value past\n // the plausibility cap all land here, hedging the same way they would if the\n // unreadable value had arrived in `t`. A readable `0` is not unreadable.\n const retryAfterUnreadable =\n typeof retryAfterHeader === 'string' &&\n retryAfterHeader.trim() !== '' &&\n waitSeconds(retryAfterHeader) === null;\n // The generic hint is for a response that withheld nothing: no header, no\n // exhausted window, no delay stated. It must not absorb a response that DID\n // state something we failed to read — that would hand back the file's mildest\n // wording precisely when the most was hidden, and a header stating an\n // unreadable day-long lockout would read softer than one stating no reset at\n // all. Anything unread falls through to \"no usable reset time\" instead.\n if (!longestWindow && waitS === null) {\n if (!retryAfterUnreadable) return GENERIC_RATE_LIMIT_HINT;\n }\n // \"the hour quota is exhausted\" rather than \"rate limited on the hour quota\":\n // the response body names the window that rejected THIS request, which in the\n // co-exhaustion case is a different one. Both statements are true, and this\n // phrasing doesn't claim to be the other.\n const namedClause = longestWindow?.window\n ? `the ${longestWindow.window} quota is exhausted`\n : '';\n // Exhausted, but nothing usable said when it clears. A number here would be\n // invented, which is the defect this function exists to avoid.\n if (waitS === null) {\n const cause = namedClause\n ? `${namedClause}, but the response gave no usable reset time`\n : 'the response gave no usable reset time';\n return `rate limited — ${cause}; back off substantially before retrying, and pace bulk operations`;\n }\n // This number is a floor, not the answer, whenever the header held something\n // we couldn't read: another exhausted window that omitted its reset, or an\n // item dropped whole. A quantified 10s window must not speak for a silent\n // day-long one.\n const hedge =\n dropped || retryAfterUnreadable || windows.some((w) => w.resetS === null)\n ? 'at least '\n : '';\n const wait = `wait ${hedge}${humanWait(waitS)} before retrying, and pace bulk operations`;\n return `rate limited — ${namedClause ? `${namedClause}; ` : ''}${wait}`;\n}\n","import { rateLimitHint } from './rate-limit.js';\n\n// ============== UNIFIED HTTP CLIENT (REST + Serverless) ==============\n// One authenticated JSON client that replaces the byte-identical\n// `runpodRequest` + `serverlessRequest` helpers. The adapter\n// (src/_shared/backend.ts) resolves the full URL, so this client takes a\n// resolved URL — the old `endpointId`-split shape collapses into the path.\n//\n// GraphQL is intentionally NOT routed through here: it sends no Authorization\n// header and has its own `{data,errors}` envelope + error prefix. It stays a\n// separate helper through Phase A.\n//\n// `fetch` and `tracking` are injected so this is unit-testable offline with no\n// network and no MCP SDK.\n\n// The shape of a `fetch` request init this client builds. Named so the client's\n// internal `init` literal can't drift from what `FetchLike` actually accepts.\ninterface RequestInitLike {\n method: string;\n headers: Record<string, string>;\n body?: string;\n // Required so an unbounded request through THIS client is a compile error\n // rather than a test. It says nothing about requests built elsewhere — the\n // GraphQL helper (tools/runtime.ts), the flash auth calls (api/index.ts) and\n // the install wizard each bound their own fetch.\n signal: AbortSignal;\n}\n\ntype FetchLike = (\n url: string,\n init: RequestInitLike\n) => Promise<HttpResponseLike>;\n\ninterface HttpResponseLike {\n ok: boolean;\n status: number;\n text(): Promise<string>;\n json(): Promise<unknown>;\n headers: { get(name: string): string | null };\n}\n\n// The 401 hint. A 401 from a credentialed request means the credential itself\n// is dead (expired/revoked key), not that the request was wrong. Say so — a\n// bare \"401 - Unauthorized\" gives an agent nothing actionable, and on stdio\n// (env-var API key) there is no HTTP layer to convert this into a re-auth\n// signal. Passed by each CREDENTIALED construction site via `hint` rather than\n// baked into the constructor, because whether a credential was sent is caller\n// context the class doesn't have: the public GraphQL path sends no\n// Authorization header at all, so a 401 from that host (WAF, misconfigured\n// RUNPOD_PUBLIC_GRAPHQL_URL) says nothing about the caller's API key — the\n// same reasoning that keeps that path off observeUnauthorized (runtime.ts).\n// Bare inner text: the constructor adds the ` (…)` wrapping.\nexport const EXPIRED_CREDENTIAL_HINT =\n 'the Runpod API rejected the credential — the API key may be expired or revoked; re-authenticate or update RUNPOD_API_KEY';\n\n// Thrown on any non-OK response. Carries `status` + `body` so callers can\n// branch (e.g. create-pod maps a 501 to a clean \"CPU pods not yet supported\"\n// message) without parsing the message string. Because it still THROWS, loops\n// that count consecutive errors (e.g. stream-job) keep working — a 501 mid-poll\n// is surfaced as an error, not swallowed. Status-specific hints need caller\n// context (the 429 RateLimit header, whether a 401's request was credentialed),\n// so all of them arrive via `hint`.\nexport class HttpError extends Error {\n readonly status: number;\n readonly body: string;\n constructor(prefix: string, status: number, body: string, hint?: string) {\n // The body is embedded in the message for readability but capped: a WAF or\n // proxy error page can run to hundreds of KB, and the message lands\n // verbatim in an agent's context. `.body` keeps the full text for\n // programmatic callers.\n const shownBody =\n body.length > 2048\n ? `${body.slice(0, 2048)}… [truncated ${body.length - 2048} chars]`\n : body;\n super(`${prefix}: ${status} - ${shownBody}${hint ? ` (${hint})` : ''}`);\n this.name = 'HttpError';\n this.status = status;\n this.body = body;\n }\n}\n\n// ============== REQUEST DEADLINE ==============\n// node-fetch has no timeout of its own, so a server that accepts the connection\n// then goes silent leaves the request pending until the platform reaps the\n// function — a bare 504, with whatever the tool had collected thrown away.\n// Same mechanism as credential-check.ts and backend.ts.\n//\n// 30s is generous for a control-plane call that answers sub-second, and leaves\n// the hosted function room to serialize a real error. runsync-endpoint asks the\n// server to hold the connection open, so it raises its own (tools/jobs.ts).\nexport const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\n\n// Floor for a deadline shrunk by a nearly-spent invocation budget (see\n// `maxTimeoutMs`). A 0ms deadline aborts before the socket opens and reports\n// \"no response after 0ms\", which reads as a server fault rather than a budget\n// we had already used up; a second is enough for a warm host to answer, and\n// overshooting an exhausted budget by 1s is strictly better than the bare 504\n// the whole deadline exists to replace.\nexport const MIN_REQUEST_TIMEOUT_MS = 1_000;\n\n// A bare AbortError names neither the API nor the deadline, and the MCP SDK\n// hands that string straight to the agent.\nexport class RequestTimeoutError extends Error {\n readonly timeoutMs: number;\n readonly method: string;\n constructor(prefix: string, timeoutMs: number, method: string) {\n // We abandoned the request, we did not undo it, and nothing sends an\n // idempotency key — so a timed-out write may well have landed. Retry advice\n // has to key off the method or an agent double-creates a billed resource.\n const write = method !== 'GET' && method !== 'HEAD';\n super(\n `${prefix}: no response after ${timeoutMs}ms for ${method} (the API may be overloaded, or still be working on it). ${\n write\n ? 'This request may have SUCCEEDED upstream — do not retry blindly. Check with the matching list-/get- tool first and retry only if it did not take effect.'\n : 'This was a read and changed nothing, so retrying is safe.'\n }`\n );\n this.name = 'RequestTimeoutError';\n this.timeoutMs = timeoutMs;\n this.method = method;\n }\n}\n\n// Covers the whole exchange, not just the connect: a server can send headers\n// then stall mid-body. Shared with the GraphQL helper in tools/runtime.ts,\n// which builds its own request but has the same failure mode.\nexport async function withRequestTimeout<T>(\n errorPrefix: string,\n timeoutMs: number,\n method: string,\n run: (signal: AbortSignal) => Promise<T>\n): Promise<T> {\n const signal = AbortSignal.timeout(timeoutMs);\n try {\n return await run(signal);\n } catch (error) {\n // `signal.aborted`, not an error name: node-fetch, undici and the test\n // fakes each label an abort differently. A real status beats a timeout.\n if (error instanceof HttpError || !signal.aborted) throw error;\n throw new RequestTimeoutError(errorPrefix, timeoutMs, method);\n }\n}\n\n// A response whose content-type marks it as JSON — including v2's RFC-9457\n// `application/problem+json` error bodies. We match the `+json`/`/json` shape,\n// NOT the literal substring `application/json` (which `problem+json` does not\n// contain), so v2 error bodies are parsed rather than swallowed.\nfunction isJsonContentType(contentType: string | null): boolean {\n if (!contentType) return false;\n const ct = contentType.toLowerCase();\n return ct.includes('application/json') || ct.includes('+json');\n}\n\n// Body-carrying methods. This is now the single unified client for all REST\n// calls, so include PUT — otherwise a future tool passing method='PUT' with a\n// body would have the payload silently dropped (request sent with no body, no\n// error). GET/DELETE never carry a body.\nfunction methodSendsBody(method: string): boolean {\n return method === 'POST' || method === 'PATCH' || method === 'PUT';\n}\n\n// The auth + content-type + caller-tracking headers sent on every request.\nfunction buildRequestHeaders(\n apiKey: string,\n tracking: () => Record<string, string>\n): Record<string, string> {\n return {\n Authorization: `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n ...tracking(),\n };\n}\n\n// Trailing and optional so existing call sites are untouched and only the\n// calls that need a different deadline mention one.\nexport interface RequestOptions {\n // Clamped by the client's `maxTimeoutMs`. Only runsync-endpoint sets it.\n timeoutMs?: number;\n}\n\n// A per-request deadline bounds one stalled socket; it does not bound a handler\n// that makes several calls in a row. get-job-status (status, then the queued-job\n// diagnosis), deploy-hub-repo (catalog, then the saveEndpoint mutation) and\n// update-endpoint (read the scaler, then PATCH) each issue two, so two default\n// deadlines back to back outlast the platform and the 504 comes back.\n//\n// So the hosted ceiling is a function, not a constant: it reports what is left\n// of the whole invocation, and every request is clamped to that. The first call\n// may take the lot; the second only gets the remainder.\nexport type TimeoutCeiling = number | (() => number);\n\nfunction resolveCeiling(ceiling: TimeoutCeiling | undefined): number {\n if (ceiling === undefined) return Infinity;\n // Only a thunk gets the floor. It is the one that decays as the invocation is\n // spent and so can reach zero on its own; a static ceiling is a number\n // someone chose, and silently raising it would be the surprise.\n if (typeof ceiling !== 'function') return ceiling;\n return Math.max(ceiling(), MIN_REQUEST_TIMEOUT_MS);\n}\n\n// Honoring an override past the platform limit just trades the named timeout\n// back for the 504 it exists to replace. Shared with the GraphQL helper in\n// tools/runtime.ts, which builds its own request but is under the same budget.\nexport function clampTimeout(\n requestedMs: number | undefined,\n ceiling: TimeoutCeiling | undefined,\n defaultMs: number = DEFAULT_REQUEST_TIMEOUT_MS\n): number {\n return Math.min(requestedMs ?? defaultMs, resolveCeiling(ceiling));\n}\n\nexport interface HttpClient {\n (\n url: string,\n method?: string,\n body?: Record<string, unknown>,\n options?: RequestOptions\n ): Promise<unknown>;\n}\n\nexport function createHttpClient(deps: {\n apiKey: string;\n fetch: FetchLike;\n tracking: () => Record<string, string>;\n // Distinct per backend so error messages stay attributable\n // (\"Runpod API Error\" / \"Runpod Serverless API Error\").\n errorPrefix: string;\n // Deadline for calls that pass no `timeoutMs` of their own.\n defaultTimeoutMs?: number;\n // Ceiling on the default and on any override, for a caller under a platform\n // deadline it cannot outlive (HTTP_TRANSPORT_BUDGET_MS in tools/runtime.ts).\n // A thunk re-reads the remaining budget per call. Unset = no ceiling.\n maxTimeoutMs?: TimeoutCeiling;\n}): HttpClient {\n const defaultTimeoutMs = deps.defaultTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\n return async function request(\n url: string,\n method: string = 'GET',\n body?: Record<string, unknown>,\n options?: RequestOptions\n ): Promise<unknown> {\n const timeoutMs = clampTimeout(\n options?.timeoutMs,\n deps.maxTimeoutMs,\n defaultTimeoutMs\n );\n\n return withRequestTimeout(\n deps.errorPrefix,\n timeoutMs,\n method,\n async (signal) => {\n const init: RequestInitLike = {\n method,\n headers: buildRequestHeaders(deps.apiKey, deps.tracking),\n signal,\n };\n if (body && methodSendsBody(method)) {\n init.body = JSON.stringify(body);\n }\n\n const response = await deps.fetch(url, init);\n\n if (!response.ok) {\n // Status and hint first: the body is awaited as a constructor\n // argument, so a deadline firing mid-drain would reject before the\n // HttpError exists and lose the 429 retry hint / 401 re-auth signal.\n const hint =\n response.status === 429\n ? rateLimitHint(\n response.headers.get('ratelimit'),\n response.headers.get('retry-after')\n )\n : response.status === 401\n ? EXPIRED_CREDENTIAL_HINT\n : undefined;\n const text = await response\n .text()\n .catch(\n () => '<body unavailable: the connection closed while reading it>'\n );\n throw new HttpError(deps.errorPrefix, response.status, text, hint);\n }\n\n // 204 / empty / non-JSON → a uniform success marker (matches today's\n // helpers and covers pod-action responses that return no JSON body).\n return isJsonContentType(response.headers.get('content-type'))\n ? response.json()\n : { success: true, status: response.status };\n }\n );\n };\n}\n","// ============== CALLER TRACKING (pure header construction) ==============\n// The structured User-Agent + session id that identify the calling MCP client\n// on every outbound API call. Pure functions here so they're unit-testable\n// without the MCP SDK; the SDK-touching part (reading clientInfo off the\n// handshake) stays at the call site and is reduced to plain strings before\n// reaching `buildTrackingHeaders`.\n\n// Sanitizes a value for inclusion in a User-Agent token. RFC 7230 reserves\n// parens and a few other chars; strip them so the structured UA stays\n// parseable, and bound the length so a hostile client can't blow up a header.\nexport function sanitizeUaToken(value: string): string {\n return value.replace(/[()<>@,;:\\\\\"/[\\]?={}\\s]/g, '_').slice(0, 64);\n}\n\nexport interface TrackingInput {\n // Client identity resolved from the MCP `initialize` handshake's clientInfo,\n // or the inbound HTTP User-Agent as a fallback (stateless HTTP).\n clientName?: string;\n clientVersion?: string;\n transport: 'stdio' | 'http';\n serverVersion: string;\n sessionId: string;\n}\n\n// Builds the `User-Agent` + `X-Runpod-Session-Id` headers. The clientInfo →\n// fallback → 'unknown' resolution is the caller's job (it owns the SDK handle);\n// this function just sanitizes and formats whatever identity it's given.\nexport function buildTrackingHeaders(\n input: TrackingInput\n): Record<string, string> {\n const name = sanitizeUaToken(input.clientName || 'unknown');\n const version = sanitizeUaToken(input.clientVersion || 'unknown');\n const userAgent = `runpod-mcp-server/${input.serverVersion} (caller=mcp; client=${name}; client_version=${version}; transport=${input.transport})`;\n return {\n 'User-Agent': userAgent,\n 'X-Runpod-Session-Id': input.sessionId,\n };\n}\n","import { z } from 'zod';\nimport { EXPIRED_CREDENTIAL_HINT, HttpError } from '../_shared/http.js';\nimport { rateLimitHint } from '../_shared/rate-limit.js';\nimport type { Backend, Resource } from '../_shared/backend.js';\nimport type { StreamSse, ToolRuntime } from './runtime.js';\n\n// ============== SHARED LOG STREAMING ==============\n// Pod logs (GET /v2/pods/{id}/logs) and worker logs\n// (GET /v2/serverless/{id}/workers/{workerId}/logs) are the same feature on two\n// resources: an SSE stream of `data: {source,line,ts}` frames. This module holds\n// everything they share — the frame parser, the param schema, the bounded read,\n// and the tool handler — so each tool registration is just a name + a URL.\n\n// One parsed log frame. A payload that isn't JSON is kept verbatim under `raw`.\nexport interface LogEntry {\n source?: string;\n line?: string;\n ts?: string;\n raw?: string;\n}\n\n// Parse SSE text into log frames. Pure, so it's unit-tested without a network.\n// Events are separated by a blank line; only `data:` fields are read (`id:`,\n// `event:`, and `:` comments are ignored). A `data:` field may span several lines.\nexport function parseLogSse(raw: string): LogEntry[] {\n const items: LogEntry[] = [];\n for (const block of raw.split(/\\r?\\n\\r?\\n/)) {\n const dataLines = block.split(/\\r?\\n/).filter((l) => l.startsWith('data:'));\n if (!dataLines.length) continue;\n // Drop the `data:` prefix and one optional leading space from each line.\n const payload = dataLines\n .map((l) => l.slice(5).replace(/^ /, ''))\n .join('\\n');\n if (!payload.trim()) continue;\n try {\n const parsed: unknown = JSON.parse(payload);\n // A log frame is a JSON object; a bare primitive/array/null (`data: 42`)\n // is not a LogEntry, so keep it verbatim under `raw` rather than pushing a\n // value that violates the LogEntry shape.\n items.push(\n parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as LogEntry)\n : { raw: payload }\n );\n } catch {\n items.push({ raw: payload });\n }\n }\n return items;\n}\n\n// How long to hold the stream open, and the byte cap that flips `truncated`.\nexport const LOG_STREAM_DEFAULT_WAIT_MS = 5000;\nexport const LOG_STREAM_MAX_BYTES = 256 * 1024;\n\n// Minimal structural shape of the fetch response the SSE reader consumes. Kept\n// deliberately narrow so both node-fetch (production) and a test fake satisfy it\n// without pulling in a full Response type.\nexport interface SseResponse {\n ok: boolean;\n status: number;\n // Read only for the 429 rate-limit hint. Required, so a fake that omits it\n // is an editor error — `pnpm type-check` excludes `**/*.test.ts`, so nothing\n // in CI enforces it. The one production caller casts global `fetch`\n // (`as unknown as SseFetch`), whose Response always carries headers.\n headers: { get(name: string): string | null };\n text(): Promise<string>;\n body: AsyncIterable<Buffer> | null;\n}\nexport type SseFetch = (\n url: string,\n init: { method: string; headers: Record<string, string>; signal: AbortSignal }\n) => Promise<SseResponse>;\n\n// The real bounded SSE reader, extracted so it can be unit-tested against a mock\n// Response (production wires it to node-fetch in createToolRuntime). Time-bounded\n// by maxWaitMs (the stream may stay open to tail live output) and byte-bounded by\n// maxBytes; whichever fires first aborts and returns what was collected so far.\n// A timeout/cap abort is a NORMAL end of a bounded snapshot — whether it fires\n// while CONNECTING (thrown from the `await fetchImpl`) or mid body read, keep the\n// collected bytes rather than throwing. Only a non-OK HTTP status throws\n// (HttpError). Buffers are concatenated and decoded ONCE at the end so a UTF-8\n// char split across two network chunks is never corrupted.\nexport async function readSseSnapshot(\n fetchImpl: SseFetch,\n url: string,\n headers: Record<string, string>,\n opts: { maxWaitMs: number; maxBytes: number }\n): Promise<{ raw: string; truncated: boolean }> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), opts.maxWaitMs);\n const chunks: Buffer[] = [];\n let bytes = 0;\n let truncated = false;\n try {\n const res = await fetchImpl(url, {\n method: 'GET',\n headers,\n signal: controller.signal,\n });\n if (!res.ok) {\n throw new HttpError(\n 'Runpod API Error',\n res.status,\n await res.text().catch(() => ''),\n // This throw bypasses createHttpClient, so the status hints are built\n // here too or these two tools alone return a bare 429/401. The 401\n // hint applies: this request always carries Authorization (see the\n // headers bound in runtime.ts streamSse).\n res.status === 429\n ? rateLimitHint(\n res.headers.get('ratelimit'),\n res.headers.get('retry-after')\n )\n : res.status === 401\n ? EXPIRED_CREDENTIAL_HINT\n : undefined\n );\n }\n for await (const chunk of (res.body ?? []) as AsyncIterable<Buffer>) {\n chunks.push(chunk);\n bytes += chunk.length;\n if (bytes >= opts.maxBytes) {\n truncated = true;\n controller.abort();\n break;\n }\n }\n } catch (err) {\n // Abort (timeout or byte-cap) is a clean end of a bounded read — keep what we\n // have. Anything else (incl. HttpError on a non-OK status) propagates.\n if (!(err instanceof Error && err.name === 'AbortError')) throw err;\n } finally {\n clearTimeout(timer);\n }\n return { raw: Buffer.concat(chunks).toString('utf8'), truncated };\n}\n\n// Zod params shared by both log tools; spread in alongside the resource id.\nexport const logStreamParams = {\n source: z\n .enum(['container', 'system', 'both'])\n .optional()\n .describe('Which log source to read (default: both)'),\n tail: z\n .number()\n .int()\n .min(0)\n .max(5000)\n .optional()\n .describe(\n 'Historical lines to backfill before live output (API default 100, max 5000; 0 = live only). Ignored when `since` is set.'\n ),\n since: z\n .string()\n .optional()\n .describe('RFC3339 timestamp to resume from; when set, `tail` is ignored.'),\n maxWaitMs: z\n .number()\n .int()\n .min(500)\n .max(30000)\n .optional()\n .describe('How long to read the stream, in ms (default 5000, max 30000)'),\n} as const;\n\nexport interface LogStreamParams {\n source?: 'container' | 'system' | 'both';\n tail?: number;\n since?: string;\n maxWaitMs?: number;\n}\n\n// Read a bounded snapshot of a log endpoint and return the parsed frames.\n// `source: 'both'` (or omitted) sends no `source` param — the endpoint returns\n// both streams when it's absent (the wire enum is only container|system).\n// Throws HttpError on a non-OK response.\nexport async function collectLogSnapshot(\n streamSse: StreamSse,\n logsUrl: string,\n params: LogStreamParams\n): Promise<{ items: LogEntry[]; count: number; truncated: boolean }> {\n const qs = new URLSearchParams();\n if (params.source && params.source !== 'both')\n qs.append('source', params.source);\n if (params.tail !== undefined) qs.append('tail', String(params.tail));\n if (params.since) qs.append('since', params.since);\n const query = qs.toString() ? `?${qs}` : '';\n const { raw, truncated } = await streamSse(`${logsUrl}${query}`, {\n maxWaitMs: params.maxWaitMs ?? LOG_STREAM_DEFAULT_WAIT_MS,\n maxBytes: LOG_STREAM_MAX_BYTES,\n });\n const items = parseLogSse(raw);\n // A byte-cap truncation almost always slices the final frame mid-line, so the\n // last parsed entry is a partial (typically garbage half-JSON kept under\n // `raw`). Drop it when truncated so callers never see a corrupt trailing\n // frame; `truncated: true` already signals output was cut.\n //\n // The pop is unconditional: in the rare case the cap lands exactly on a frame\n // boundary (or the final bytes were a non-`data:` block that produced no\n // entry), this drops one *valid* trailing line instead of a garbage one. That\n // over-trim is acceptable — `truncated: true` already tells the caller output\n // was cut, so losing one more line at the boundary is not misleading.\n if (truncated) items.pop();\n return { items, count: items.length, truncated };\n}\n\n// The full handler for a log tool: resolve the backend, gate v2-only, stream a\n// snapshot, and map an HTTP error to a JSON reply. `logsUrl` builds the endpoint\n// URL from the resolved backend (the resource id is closed over by the caller).\nexport async function streamLogsReply(\n rt: Pick<ToolRuntime, 'jsonReply' | 'backendFor' | 'streamSse'>,\n tool: {\n name: string;\n resource: Resource;\n logsUrl: (backend: Backend) => string;\n },\n params: LogStreamParams\n) {\n const backend = rt.backendFor(tool.resource);\n if (backend.version === 'v1') {\n return rt.jsonReply({\n error: `${tool.name} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,\n status: 501,\n });\n }\n try {\n return rt.jsonReply(\n await collectLogSnapshot(rt.streamSse, tool.logsUrl(backend), params)\n );\n } catch (error) {\n if (error instanceof HttpError) {\n return rt.jsonReply({ error: error.message, status: error.status });\n }\n throw error;\n }\n}\n","// ============== v1 → v2 REQUEST BODY MAPPERS ==============\n// Pure functions that translate the v1-shaped tool params (what the MCP tool\n// schemas accept today) into v2 REST request bodies. Isolated here so the v2\n// shape is a one-file change when the spec moves.\n//\n// These mappers target the vendored v2 spec snapshot under tests/fixtures and are\n// pinned by committed fixtures. When the live spec changes, update the mapper AND\n// its fixture in one commit.\n//\n// Two older mid-flight questions this file carried ARE now settled, and their\n// workarounds are gone: `cloud` dropped `ALL` (the tool never offered it), and\n// template `category` became optional with a documented server-side `NVIDIA`\n// default, so the mapper no longer forces a value.\n//\n// `/v2/serverless` writes now match the vendored spec: `type` is a create field,\n// `scaling` is a union keyed on it, and `idleTimeout` sits under `workers`. Note the\n// spec-parity gate cannot police this — it checks operationId-to-tool coverage and\n// never validates a request body against a schema, so the mapper tests in\n// tests/mappers.test.ts are the only guard on the emitted shape.\n\n// v1 params accepted by the create-pod / update-pod tool schemas (the fields the\n// mapper knows how to translate). Unknown keys are intentionally dropped.\ninterface V1PodParams {\n name?: string;\n imageName?: string;\n cloudType?: 'SECURE' | 'COMMUNITY';\n gpuTypeIds?: string[];\n gpuCount?: number;\n containerDiskInGb?: number;\n volumeInGb?: number;\n volumeMountPath?: string;\n ports?: string[];\n env?: Record<string, string>;\n dataCenterIds?: string[];\n containerRegistryAuthId?: string;\n // v2-only: mapped into gpu.allowedCudaVersions / gpu.minCudaVersion; the v1\n // path never sends them.\n allowedCudaVersions?: string[];\n minCudaVersion?: string;\n}\n\n// Drop undefined entries so we never emit explicit `undefined`/`null` the API\n// would reject; keeps the body minimal and the fixtures clean.\n// `T extends object`, not Record<string, unknown>: an interface has no index\n// signature, so the named wire shapes below would not satisfy the stricter\n// constraint. Widening it only accepts more callers; the body is unchanged.\nfunction compact<T extends object>(obj: T): Partial<T> {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined) out[k] = v;\n }\n return out as Partial<T>;\n}\n\n// volumeInGb / volumeMountPath → mounts.persistent.{size,path}.\n// v2 PersistentMount requires BOTH size and path (additionalProperties:false,\n// 422 on a partial). So only emit the mount when both are present; a partial\n// v1 input (size-only or path-only) yields `undefined` and is dropped rather\n// than producing a body the API rejects. (Tool-schema validation should require\n// both together.)\ntype PersistentMount = { persistent: { size: number; path: string } };\nfunction persistentMount(\n size?: number,\n path?: string\n): PersistentMount | undefined {\n if (size === undefined || path === undefined) return undefined;\n return { persistent: { size, path } };\n}\n\n// dockerStartCmd[] → v2 `args` (single string). Space-joined (lossy, but v2 has\n// only `args`). Empty/undefined → no `args` key, so we never overwrite with \"\".\nfunction joinStartCmd(cmd?: string[]): string | undefined {\n return cmd && cmd.length ? cmd.join(' ') : undefined;\n}\n\n// ---- ContainerConfig flatten shared by pod + template create/update ----\nfunction containerConfigToV2(p: {\n imageName?: string;\n containerDiskInGb?: number;\n volumeInGb?: number;\n volumeMountPath?: string;\n ports?: string[];\n env?: Record<string, string>;\n}): Record<string, unknown> {\n return compact({\n image: p.imageName,\n disk: p.containerDiskInGb,\n ports: p.ports,\n env: p.env,\n mounts: persistentMount(p.volumeInGb, p.volumeMountPath),\n });\n}\n\n// gpuTypeIds[] + gpuCount → gpu: { id, count }. v2 GpuConfig requires `id`, so\n// return undefined for a count-only input — an idless gpu would be rejected\n// (422). `count` is documented as optional (defaults to 1 server-side), but in\n// practice omitting it makes the scheduler match ZERO machines and every create\n// fails with a misleading \"no instances available\" 400 (observed live\n// 2026-08-04, both v2 hosts; the identical body with count:1 succeeds). So\n// always emit the documented default explicitly.\nfunction gpuConfigToV2(\n gpuTypeIds?: string[],\n gpuCount?: number,\n allowedCudaVersions?: string[],\n minCudaVersion?: string\n): Record<string, unknown> | undefined {\n const id = gpuTypeIds?.[0];\n if (id === undefined) return undefined;\n // CUDA host constraints are gpu-nested since 2.9.0 (never body top-level).\n // Passed through as given: an explicit [] is a spec-legal \"no constraint\".\n return compact({\n id,\n count: gpuCount ?? 1,\n allowedCudaVersions,\n minCudaVersion,\n });\n}\n\nexport function mapPodCreateToV2(params: V1PodParams): Record<string, unknown> {\n return compact({\n ...containerConfigToV2(params),\n name: params.name,\n // Private-image registry credential (v2 ContainerConfig `registry`). When\n // deploying from a template, this spreads over the template's registry, so\n // passing it overrides the inherited credential. An empty string is an\n // explicit opt-out: emit `registry: null` (v2 accepts null) to CLEAR a\n // template's inherited credential. Undefined is dropped by compact, so an\n // omitted param leaves the template's registry in place.\n registry:\n params.containerRegistryAuthId === ''\n ? null\n : params.containerRegistryAuthId,\n cloud: params.cloudType,\n // v2 CreatePodRequest takes `dataCenterIds` as an ARRAY (preferred data\n // centers; omit/empty = scheduler chooses). Pass the v1 list straight\n // through — the earlier singular `dataCenter` key did not exist on the pod\n // schema, so placement was being silently dropped. GPU, by contrast, IS\n // singular on v2 (`gpu.id`): gpuConfigToV2 takes the first id; the\n // create-pod handler warns when more than one gpuTypeId was supplied.\n dataCenterIds: params.dataCenterIds?.length\n ? params.dataCenterIds\n : undefined,\n gpu: gpuConfigToV2(\n params.gpuTypeIds,\n params.gpuCount,\n params.allowedCudaVersions,\n params.minCudaVersion\n ),\n });\n}\n\nexport function mapPodUpdateToV2(params: V1PodParams): Record<string, unknown> {\n // Update has no gpu/cloud/dataCenter; just name + ContainerConfig fields.\n return {\n ...containerConfigToV2(params),\n ...compact({ name: params.name }),\n };\n}\n\n// ---- Endpoint (serverless) create/update → v2 /v2/serverless body ----\n// v1 create-endpoint is templateId-based (a fundamentally different model); v2\n// takes an INLINE container + compute config (image, gpu.pools, workers, scaling)\n// and has NO templateId. So this mapper translates the v2-shaped tool params into\n// the nested v2 body. It deliberately does NOT reuse containerConfigToV2 — that\n// helper emits `mounts` (a pod/template concept), which the endpoint schema\n// rejects; endpoints attach storage via `networkVolumes` instead.\n//\n// Required by v2 CreateEndpointRequest: `name`, `image`, `gpu` (with `gpu.pools`\n// minItems 1), `type` and `scaling`. The create-endpoint handler guards the first\n// three before calling, so a missing field yields a clean 400 rather than a raw\n// 422 from the API; the last two are always emitted, defaulted below.\n\ninterface V2EndpointParams {\n name?: string;\n imageName?: string;\n args?: string;\n endpointType?: EndpointType;\n gpuPoolIds?: string[];\n gpuCount?: number;\n workersMin?: number;\n workersMax?: number;\n scalerType?: ScalerType;\n scalerValue?: number;\n idleTimeout?: number;\n dataCenterIds?: string[];\n networkVolumeIds?: string[];\n executionTimeoutMs?: number;\n flashboot?: 'OFF' | 'FLASHBOOT' | 'PRIORITY_FLASHBOOT';\n containerDiskInGb?: number;\n ports?: string[];\n env?: Record<string, string>;\n containerRegistryAuthId?: string;\n allowedCudaVersions?: string[];\n minCudaVersion?: string;\n}\n\ntype EndpointType = 'QUEUE' | 'LOAD_BALANCER';\ntype ScalerType = 'QUEUE_DELAY' | 'REQUEST_COUNT';\n\n// `type` and `scaling` are required on create, so something must be sent. These are\n// what the API used to apply server-side, so a caller that passes neither keeps\n// getting the endpoint it always got.\n//\n// DEFAULT_ENDPOINT_TYPE is create-only — update never sends `type`. DEFAULT_SCALER_VALUE\n// applies on BOTH: the scaling union requires a target next to its discriminator, so\n// `scalerType` alone still has to carry a value.\nconst DEFAULT_ENDPOINT_TYPE: EndpointType = 'QUEUE';\nconst DEFAULT_SCALER_VALUE = 4;\n\n// The scaler each endpoint type uses when the caller does not name one. Queue\n// endpoints may use either signal; load-balancing endpoints have no queue, so\n// REQUEST_COUNT is their only legal choice.\nfunction defaultScalerType(endpointType: EndpointType): ScalerType {\n return endpointType === 'LOAD_BALANCER' ? 'REQUEST_COUNT' : 'QUEUE_DELAY';\n}\n\n// `scaling` is a union discriminated on `type`, with the target carried under a\n// per-variant key rather than a shared `value`.\nfunction endpointScaling(\n scalerType: ScalerType,\n value: number\n): Record<string, unknown> {\n return scalerType === 'REQUEST_COUNT'\n ? { type: 'REQUEST_COUNT', requestCount: value }\n : { type: 'QUEUE_DELAY', queueDelay: value };\n}\n\n// The two nested objects in a v2 endpoint body, named so the wire shape is\n// checked at compile time rather than asserted by the fixtures alone. Both are\n// emitted through compact(), so every field is optional in practice.\ninterface V2GpuConfig {\n pools?: string[];\n count?: number;\n allowedCudaVersions?: string[];\n minCudaVersion?: string;\n}\n\ninterface V2Workers {\n min?: number;\n max?: number;\n idleTimeout?: number;\n}\n\n// CREATE requires `pools` (minItems 1): without them, return undefined so the\n// handler's guard, not the API, reports the omission — CUDA/count fields never\n// ride without a pool list on create. UPDATE (2.9.0) makes every gpu field\n// optional, so a pools-less gpu (CUDA-only or count-only patch) is emitted as\n// given; an empty object is still dropped. CUDA clear sentinels pass through:\n// [] clears the set, \"\" clears the floor.\nfunction endpointGpuConfig(\n params: V2EndpointParams,\n mode: 'create' | 'update'\n): Partial<V2GpuConfig> | undefined {\n const gpu = compact<V2GpuConfig>({\n pools: params.gpuPoolIds?.length ? params.gpuPoolIds : undefined,\n count: params.gpuCount,\n allowedCudaVersions: params.allowedCudaVersions,\n minCudaVersion: params.minCudaVersion,\n });\n if (mode === 'create' && !('pools' in gpu)) return undefined;\n return Object.keys(gpu).length ? gpu : undefined;\n}\n\n// `workers` absorbed `idleTimeout` from `scaling`. Returns undefined when the caller\n// set none of the three, so we never send an empty `workers: {}`.\nfunction endpointWorkers(\n params: V2EndpointParams\n): Partial<V2Workers> | undefined {\n const workers = compact<V2Workers>({\n min: params.workersMin,\n max: params.workersMax,\n idleTimeout: params.idleTimeout,\n });\n return Object.keys(workers).length ? workers : undefined;\n}\n\n// The half of the body shared by create and update: container config, compute,\n// placement and workers. Only `type` and `scaling` differ between the two.\nfunction endpointCommonToV2(\n params: V2EndpointParams,\n mode: 'create' | 'update'\n): Record<string, unknown> {\n return compact({\n name: params.name,\n image: params.imageName,\n args: params.args,\n disk: params.containerDiskInGb,\n ports: params.ports,\n env: params.env,\n registry: params.containerRegistryAuthId,\n gpu: endpointGpuConfig(params, mode),\n workers: endpointWorkers(params),\n dataCenterIds: params.dataCenterIds?.length\n ? params.dataCenterIds\n : undefined,\n networkVolumes: params.networkVolumeIds?.length\n ? params.networkVolumeIds\n : undefined,\n timeout: params.executionTimeoutMs,\n flashboot: params.flashboot,\n });\n}\n\n// CREATE. `type` and `scaling` are required, so both are always emitted.\nexport function mapEndpointCreateToV2(\n params: V2EndpointParams\n): Record<string, unknown> {\n const endpointType = params.endpointType ?? DEFAULT_ENDPOINT_TYPE;\n return compact({\n ...endpointCommonToV2(params, 'create'),\n type: endpointType,\n scaling: endpointScaling(\n params.scalerType ?? defaultScalerType(endpointType),\n params.scalerValue ?? DEFAULT_SCALER_VALUE\n ),\n });\n}\n\n// UPDATE. Only provided fields change, so nothing is defaulted into existence —\n// except the scaler target, which the union requires alongside its `type`\n// discriminator (so `scalerType` alone implies the default value). `type` is never\n// sent: the API rejects it on PATCH (\"additional properties 'type' not allowed\") —\n// an endpoint's routing model is fixed at creation.\nexport function mapEndpointUpdateToV2(\n params: V2EndpointParams\n): Record<string, unknown> {\n return compact({\n ...endpointCommonToV2(params, 'update'),\n scaling: params.scalerType\n ? endpointScaling(\n params.scalerType,\n params.scalerValue ?? DEFAULT_SCALER_VALUE\n )\n : undefined,\n });\n}\n\n// ---- CUDA constraint validation shared by the pod/endpoint tools ----\n// The API answers a raw 422/400; validating here names the field and the fix.\n// major.minor only — the REST body fields reject a bare major like \"12\" (the\n// catalog's minCudaVersion QUERY filter is the one place a bare major is legal).\nconst CUDA_MAJOR_MINOR = /^\\d+\\.\\d+$/;\n\nexport function cudaConstraintError(\n params: { allowedCudaVersions?: string[]; minCudaVersion?: string },\n opts: { allowClear?: boolean } = {}\n): string | undefined {\n const { allowedCudaVersions, minCudaVersion } = params;\n if (allowedCudaVersions?.length && minCudaVersion) {\n return 'allowedCudaVersions and minCudaVersion are mutually exclusive — pass either an exact set or an open-ended floor, not both.';\n }\n if (\n minCudaVersion !== undefined &&\n !CUDA_MAJOR_MINOR.test(minCudaVersion) &&\n !(opts.allowClear && minCudaVersion === '')\n ) {\n return `minCudaVersion must be major.minor (e.g. \"12.0\", not \"12\")${opts.allowClear ? '; an empty string clears the floor' : ''}.`;\n }\n const bad = allowedCudaVersions?.find((v) => !CUDA_MAJOR_MINOR.test(v));\n if (bad !== undefined) {\n return `allowedCudaVersions entries must be major.minor (e.g. \"12.8\"); got \"${bad}\". Discover valid values via get-capacity or list-gpu-types.`;\n }\n return undefined;\n}\n\n// ---- Network volume: dataCenterId → dataCenter (only field change) ----\ninterface V1NetworkVolumeCreate {\n name?: string;\n size?: number;\n dataCenterId?: string;\n // Storage tier. Optional on v2 — omitted means the data center's default\n // (primary) tier. Immutable after creation, so there is no update equivalent.\n volumeType?: 'STANDARD' | 'HIGH_PERFORMANCE';\n}\nexport function mapNetworkVolumeCreateToV2(\n params: V1NetworkVolumeCreate\n): Record<string, unknown> {\n return compact({\n name: params.name,\n size: params.size,\n dataCenter: params.dataCenterId,\n type: params.volumeType,\n });\n}\n\n// ---- Template: imageName→image, isServerless→serverless, dockerStartCmd→args,\n// ContainerConfig flatten. `dockerEntrypoint` and `readme` are dropped (no v2\n// equivalent — v2 ContainerConfig has only `args`, no separate entrypoint). ----\ninterface V1TemplateCreate {\n name?: string;\n imageName?: string;\n isServerless?: boolean;\n ports?: string[];\n env?: Record<string, string>;\n containerDiskInGb?: number;\n volumeInGb?: number;\n volumeMountPath?: string;\n // Startup command; joinStartCmd collapses the array into v2's `args` string.\n dockerStartCmd?: string[];\n // Template category (CPU/NVIDIA/AMD). Optional on v2, which defaults it to\n // NVIDIA server-side — so an omitted category is left unsent rather than\n // filled in here.\n category?: 'CPU' | 'NVIDIA' | 'AMD';\n // Container registry credential for pulling a private image. v1's field is\n // `containerRegistryAuthId`; v2 ContainerConfig calls it `registry`.\n containerRegistryAuthId?: string;\n}\nexport function mapTemplateCreateToV2(\n params: V1TemplateCreate\n): Record<string, unknown> {\n return {\n ...containerConfigToV2(params),\n ...compact({\n name: params.name,\n serverless: params.isServerless,\n registry: params.containerRegistryAuthId,\n args: joinStartCmd(params.dockerStartCmd),\n // No longer required by v2 (it defaults to NVIDIA server-side), so an\n // unset category is dropped by compact instead of being forced here.\n category: params.category,\n }),\n };\n}\n\n// ---- Template → pod create body (client-side template-based deploy) ----\n// v2 CreatePodRequest has no `templateId`, so create-pod can't deploy from a\n// template the way v1 did. Bridge on the MCP side: copy the template's container\n// config into the pod body as DEFAULTS; the caller's explicit params override\n// them. `registry` is included so a private-image template deploys with its\n// credential — the caller can override it via create-pod's containerRegistryAuthId\n// param. Template-only fields (id, serverless, public, category) and null values\n// are dropped.\nexport function podBodyFromTemplate(\n template: Record<string, unknown>\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n const keys = [\n 'name',\n 'image',\n 'args',\n 'disk',\n 'ports',\n 'env',\n 'mounts',\n 'registry',\n ];\n for (const key of keys) {\n const value = template[key];\n if (value !== undefined && value !== null) out[key] = value;\n }\n return out;\n}\n\n// ---- SSH convenience (create-pod sshPublicKey param) ----\n\n// An OpenSSH public key line: key-type token, then the base64 blob (a trailing\n// comment is optional). `ssh-*` covers rsa/ed25519/dss, `ecdsa-*` the NIST curves,\n// `sk-*` FIDO/U2F security keys. authorized_keys also permits a leading options\n// field (`no-port-forwarding ssh-rsa …`), which a .pub file never carries —\n// accepting arbitrary leading tokens would defeat the point of validating, so an\n// options-prefixed line is rejected with the format message.\nconst SSH_PUBLIC_KEY_LINE =\n /^(ssh-[a-z0-9-]+|ecdsa-[a-z0-9-]+|sk-[a-z0-9@.-]+)\\s+\\S+/i;\n\n// Any spelling of \"private key\" in any case, plus PuTTY's own header — a .ppk\n// says `PuTTY-User-Key-File-2` and keeps its secret under `Private-Lines`, so it\n// never contains the words the PEM guard looks for.\nconst PRIVATE_KEY_MARKER = /private[-\\s]?key|PuTTY-User-Key-File/i;\n\nexport type SshPublicKeyResult =\n | { ok: true; key: string }\n | { ok: false; error: string };\n\n/**\n * Validate and normalize the caller-supplied SSH public key(s).\n *\n * Anything that is not a usable public key must be rejected BEFORE a pod is\n * created: an unusable value still exposed 22/tcp, wrote junk into PUBLIC_KEY,\n * and returned the \"SSH configured\" note, leaving a running, billing pod that\n * nobody can log into and no indication of why. Both real cases fail that way —\n * a path (`~/.ssh/id_ed25519.pub`) or a fingerprint (`SHA256:…`) passed instead\n * of the file's contents, which is the mistake an LLM filling this param makes.\n *\n * Empty or whitespace-only is an error, not a silent skip: the way to say \"no\n * SSH\" is to omit the param, so a blank value is a caller bug, and failing here\n * is cheaper than a pod that cannot be reached.\n *\n * Normalizes CRLF and per-line whitespace. authorized_keys is one key per line,\n * and a stray \\r would corrupt the entry it lands on.\n *\n * The error messages never echo the input: a rejected value may be secret\n * (a header-stripped private blob), and the reply is logged by every client.\n */\nexport function normalizeSshPublicKey(input: string): SshPublicKeyResult {\n if (PRIVATE_KEY_MARKER.test(input))\n return {\n ok: false,\n error:\n 'sshPublicKey looks like a PRIVATE key. Pass the PUBLIC key instead — the contents of your ~/.ssh/<key>.pub file (e.g. \"ssh-ed25519 AAAA…\").',\n };\n const lines = input\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n if (lines.length === 0)\n return {\n ok: false,\n error:\n 'sshPublicKey is empty. Pass the contents of your ~/.ssh/<key>.pub file, or omit the parameter entirely to create the pod without SSH.',\n };\n if (lines.some((line) => !SSH_PUBLIC_KEY_LINE.test(line)))\n return {\n ok: false,\n error:\n 'sshPublicKey is not an SSH public key. Expected the CONTENTS of your ~/.ssh/<key>.pub file — a line like \"ssh-ed25519 AAAAC3Nza… you@host\" (ssh-*, ecdsa-*, or sk-* followed by the base64 key) — not a file path, a SHA256 fingerprint, or a bare base64 blob. Newline-separate multiple keys.',\n };\n return { ok: true, key: lines.join('\\n') };\n}\n\n// The REST API has no SSH switch yet: the console and runpodctl set GraphQL\n// `startSsh`, which injects the account's registered keys as a PUBLIC_KEY env\n// var that official images install into authorized_keys before starting sshd.\n// Until REST v2 ships its own startSsh field, create-pod reproduces the\n// convention client-side from a caller-supplied key: merge the key into\n// env.PUBLIC_KEY and make sure 22/tcp is exposed. Must run on the FINAL body\n// (after any template merge) so a template's ports/env are extended, not\n// clobbered. Works on v1 and v2 bodies alike — `ports` and `env` have the\n// same shape on both.\nexport function applySshPublicKey(\n body: Record<string, unknown>,\n sshPublicKey: string\n): Record<string, unknown> {\n const ports = Array.isArray(body.ports) ? (body.ports as string[]) : [];\n const hasSshPort = ports.some((p) => /^\\s*22\\/tcp\\s*$/i.test(String(p)));\n const env = { ...((body.env as Record<string, string> | undefined) ?? {}) };\n const key = sshPublicKey.trim();\n // A PUBLIC_KEY already present (caller's env or the template's) stays\n // authorized: authorized_keys is one key per line, so append, don't replace.\n if (!env.PUBLIC_KEY) env.PUBLIC_KEY = key;\n else if (!env.PUBLIC_KEY.includes(key))\n env.PUBLIC_KEY = `${env.PUBLIC_KEY}\\n${key}`;\n return {\n ...body,\n ports: hasSshPort ? ports : [...ports, '22/tcp'],\n env,\n };\n}\n\ninterface V1TemplateUpdate {\n name?: string;\n imageName?: string;\n ports?: string[];\n env?: Record<string, string>;\n dockerStartCmd?: string[];\n containerRegistryAuthId?: string;\n // readme has no v2 equivalent — dropped.\n}\n// Update has no required `category`; flatten ContainerConfig + name. Maps\n// imageName→image (v2 expects `image`) and dockerStartCmd→args like create.\nexport function mapTemplateUpdateToV2(\n params: V1TemplateUpdate\n): Record<string, unknown> {\n return {\n ...containerConfigToV2(params),\n ...compact({\n name: params.name,\n registry: params.containerRegistryAuthId,\n args: joinStartCmd(params.dockerStartCmd),\n }),\n };\n}\n","// ============== BACKEND ADAPTER (v1/v2 routing core) ==============\n// Pure routing layer that lets tools stay version-agnostic. A tool asks for a\n// resource backend; the adapter decides v1 vs v2, resolves the base URL + path,\n// normalizes the list response shape (`unwrapList`), and maps request bodies.\n//\n// This module is intentionally pure and side-effect free: env, transport, and\n// the v2 probe are all passed in, so every function is unit-testable offline\n// with no network and no MCP SDK. The HTTP client (src/_shared/http.ts) and the\n// per-resource tool handlers (src/tools/<resource>.ts) consume this.\n\nimport {\n mapPodCreateToV2,\n mapPodUpdateToV2,\n mapTemplateCreateToV2,\n mapTemplateUpdateToV2,\n mapNetworkVolumeCreateToV2,\n mapEndpointCreateToV2,\n mapEndpointUpdateToV2,\n} from './mappers.js';\n\n// One canonical resource union used by unwrapList, resolveVersion, and\n// resolveBackend alike — so the steps don't each invent a spelling. The v2\n// list-envelope key happens to equal the resource name.\nexport type Resource =\n | 'pods'\n | 'templates'\n | 'networkVolumes'\n | 'registries'\n | 'gpus'\n | 'cpus'\n | 'dataCenters'\n | 'endpoints'\n | 'jobs'\n | 'workers'\n | 'billing';\n\nexport type RestVersion = 'v1' | 'v2';\n\n// Resources that have no v2 REST home and stay on v1 regardless of any flag.\n// - jobs: serverless runtime, lives on api.runpod.ai/v2 (a different service)\n// (endpoints now route to v2 at /v2/serverless — see V2_REST_PATHS/V2_MAPPERS.)\nconst V1_ONLY: ReadonlySet<Resource> = new Set<Resource>(['jobs']);\n\n// The key each v2 list response is wrapped in: `{ pods: [...] }`, etc.\n// `billing` wraps its rows under `records` (not the resource name); `workers`\n// under `workers`.\nconst V2_UNWRAP_KEY: Record<Resource, string> = {\n pods: 'pods',\n templates: 'templates',\n networkVolumes: 'networkVolumes',\n registries: 'registries',\n gpus: 'gpus',\n cpus: 'cpus',\n dataCenters: 'dataCenters',\n endpoints: 'endpoints',\n jobs: 'jobs',\n workers: 'workers',\n billing: 'records',\n};\n\n// Env-var suffix for the per-resource version override (RUNPOD_REST_VERSION_<KEY>).\nconst RESOURCE_ENV_KEY: Record<Resource, string> = {\n pods: 'PODS',\n templates: 'TEMPLATES',\n networkVolumes: 'NETWORK_VOLUMES',\n registries: 'REGISTRIES',\n gpus: 'GPUS',\n cpus: 'CPUS',\n dataCenters: 'DATA_CENTERS',\n endpoints: 'ENDPOINTS',\n jobs: 'JOBS',\n workers: 'WORKERS',\n billing: 'BILLING',\n};\n\n// ---- Base URLs (resolved from env, with the same defaults tools.ts uses) ----\n\nexport type Env = Record<string, string | undefined>;\n\nexport function restV1Base(env: Env): string {\n return env.RUNPOD_REST_API_URL ?? 'https://rest.runpod.io/v1';\n}\nexport function restV2Base(env: Env): string {\n return env.RUNPOD_REST_V2_API_URL ?? 'https://api.runpod.io/v2';\n}\nexport function serverlessBase(env: Env): string {\n return env.RUNPOD_SERVERLESS_API_URL ?? 'https://api.runpod.ai/v2';\n}\nexport function publicGraphqlBase(env: Env): string {\n return env.RUNPOD_PUBLIC_GRAPHQL_URL ?? 'https://api.runpod.io/graphql';\n}\n// A separate var from publicGraphqlBase despite the identical default:\n// RUNPOD_PUBLIC_GRAPHQL_URL is the documented credential-free discovery\n// override and gets pointed at stubs freely, so routing authed calls through it\n// would turn \"point this anywhere\" into \"send the caller's API key there\".\nexport function authedGraphqlBase(env: Env): string {\n return env.RUNPOD_AUTHED_GRAPHQL_URL ?? 'https://api.runpod.io/graphql';\n}\n\n// ---- A0: unwrap a list response into a plain array ----\n// v1 returns a bare array; v2 wraps it in a per-resource envelope. Either way\n// we hand `capListResult` a plain array. Anything unexpected → [] (never throws\n// in an agent's face, and never passes a non-array through to the cap).\nexport function unwrapList(\n resource: Resource,\n version: RestVersion,\n raw: unknown\n): unknown[] {\n if (version === 'v1') return Array.isArray(raw) ? raw : [];\n if (raw === null || typeof raw !== 'object') return [];\n const arr = (raw as Record<string, unknown>)[V2_UNWRAP_KEY[resource]];\n return Array.isArray(arr) ? arr : [];\n}\n\n// ---- A1: resolve which version a given resource call should use ----\n// Pure: env, transport, and the already-resolved v2 probe verdict are injected.\n// Precedence: v1-only resources → v1; else RUNPOD_REST_VERSION_<RESOURCE> →\n// RUNPOD_REST_VERSION → default 'v2'. `auto` probes only on stdio (one process =\n// one key); on hosted HTTP `auto` is treated as v1 (a warm instance serves many\n// keys, so a cached probe verdict would leak across users).\n//\n// `v2Available` is a *resolved boolean*, NOT a thunk or a promise: the caller\n// awaits createV2Prober() once at startup (stdio only) and passes the boolean\n// in. This is deliberate — a `() => Promise<boolean>` would be truthy even when\n// the probe failed, silently forcing v2. Keeping it a plain boolean removes that\n// footgun. When omitted under `auto`, we conservatively resolve to v1.\nexport interface VersionCtx {\n transport: 'stdio' | 'http';\n}\n\nexport function resolveVersion(opts: {\n resource: Resource;\n env: Env;\n ctx: VersionCtx;\n v2Available?: boolean;\n}): RestVersion {\n const { resource, env, ctx, v2Available } = opts;\n if (V1_ONLY.has(resource)) return 'v1';\n\n const perResource = env[`RUNPOD_REST_VERSION_${RESOURCE_ENV_KEY[resource]}`];\n const setting = (\n perResource ??\n env.RUNPOD_REST_VERSION ??\n 'v2'\n ).toLowerCase();\n\n if (setting === 'v2') return 'v2';\n if (setting === 'auto') {\n if (ctx.transport === 'http') return 'v1';\n return v2Available === true ? 'v2' : 'v1';\n }\n return 'v1';\n}\n\n// True if any version setting (global or per-resource) is `auto` — i.e. the\n// stdio entrypoint should run the v2 probe once at startup. Pure.\nexport function wantsAutoProbe(env: Env): boolean {\n return Object.entries(env).some(\n ([k, v]) =>\n k.startsWith('RUNPOD_REST_VERSION') && (v ?? '').toLowerCase() === 'auto'\n );\n}\n\n// Human-readable summary of how RUNPOD_REST_VERSION is configured, surfaced in\n// the MCP serverInfo version so a plain `initialize` reveals whether the server\n// is pinned to v1/v2/auto (default v2) without inspecting the deployment env.\n// Echoes the global var's value (or \"unset (default v2)\") and flags how many\n// per-resource overrides (RUNPOD_REST_VERSION_<RESOURCE>) are set. Pure.\nexport function restVersionSummary(env: Env): string {\n const raw = (env.RUNPOD_REST_VERSION ?? '').trim().toLowerCase();\n const base = raw\n ? `RUNPOD_REST_VERSION=${raw}`\n : 'RUNPOD_REST_VERSION unset (default v2)';\n const overrides = Object.keys(env).filter(\n (k) => k.startsWith('RUNPOD_REST_VERSION_') && (env[k] ?? '').trim() !== ''\n ).length;\n return overrides > 0 ? `${base}, +${overrides} per-resource` : base;\n}\n\n// ---- A1b: the real `auto` probe (decision logic, injected fetch) ----\n// Returns an async resolver that probes once and memoizes the verdict for the\n// process. The startup wiring (stdio only) awaits this once, then passes the\n// resulting boolean to resolveVersion as `v2Available`. Never wire into the HTTP\n// path (see resolveVersion).\n//\n// Only *definitive* verdicts are cached: a reachable endpoint (any non-5xx\n// response) pins true/false, but a transient failure (thrown error or 5xx) is\n// NOT cached — it returns false for this call and re-probes next time, so a\n// one-time startup network blip doesn't pin the process to v1 forever.\n// Exported so the stdio entrypoint can type its node-fetch adapter against the\n// exact shape the prober needs (just `{ status }`), instead of casting.\n// Named `ProbeFetch` (not `FetchLike`) to avoid colliding with the unrelated,\n// stricter `FetchLike` in _shared/http.ts (which requires method+headers) — the\n// two model different contracts and the distinct name keeps that obvious.\nexport type ProbeFetch = (\n url: string,\n init?: {\n method?: string;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n }\n) => Promise<{ status: number }>;\n\nexport interface ProbeDeps {\n fetch: ProbeFetch;\n baseUrl: string;\n apiKey: string;\n timeoutMs?: number;\n // Optional, injected — called when the probe falls back to v1 for a TRANSIENT\n // reason (timeout / network error / 5xx / 401-403), so an operator who set\n // `auto` and silently got v1 has a signal. Kept as an injected callback\n // (defaulting to no-op) rather than a direct console.* so this module stays\n // pure/side-effect-free and unit tests stay silent. NOT called on a definitive\n // 404 (v2 genuinely not deployed) — that's the expected answer, not a blip.\n warn?: (message: string) => void;\n}\n\n// Bound the startup probe so a slow-but-reachable v2 host can't hang stdio\n// startup forever — an abort throws, which runV2Probe degrades to a transient.\nexport const PROBE_TIMEOUT_MS = 4000;\n\n// Classify a probe response status into a definitive verdict, or `undefined` for\n// a transient failure that must NOT be cached (so the next call re-probes).\n// Transient = 5xx AND 401/403: an auth failure during a rollout, or a key whose\n// v2 scope is later expanded, must not pin the process to v1 for its lifetime\n// with no recovery short of restart. A 2xx pins true; any other definitive 4xx\n// (e.g. 404 = no v2 endpoint) pins false.\nfunction classifyProbeStatus(status: number): boolean | undefined {\n if (status >= 500) return undefined;\n if (status === 401 || status === 403) return undefined;\n return status >= 200 && status < 300;\n}\n\n// One probe attempt. Returns a definitive verdict, or `undefined` when the\n// attempt was transient (network throw / timeout / 5xx) and should be retried.\nasync function runV2Probe(deps: ProbeDeps): Promise<boolean | undefined> {\n try {\n const res = await deps.fetch(`${deps.baseUrl}/catalog/gpus`, {\n method: 'GET',\n headers: { Authorization: `Bearer ${deps.apiKey}` },\n signal: AbortSignal.timeout(deps.timeoutMs ?? PROBE_TIMEOUT_MS),\n });\n const verdict = classifyProbeStatus(res.status);\n if (verdict === undefined) {\n deps.warn?.(\n `v2 auto-probe: ${deps.baseUrl} returned ${res.status} (transient) — using v1 for now; will re-probe. Pin RUNPOD_REST_VERSION=v2 to force v2.`\n );\n }\n return verdict;\n } catch {\n // Thrown = timeout (AbortSignal) or network error — transient, not cached.\n deps.warn?.(\n `v2 auto-probe: ${deps.baseUrl} unreachable (timed out after ${\n deps.timeoutMs ?? PROBE_TIMEOUT_MS\n }ms or network error) — using v1 for now; will re-probe. Pin RUNPOD_REST_VERSION=v2 to force v2.`\n );\n return undefined;\n }\n}\n\n// Memoizing v2 prober: probes once, caches only a *definitive* verdict, and\n// coalesces concurrent first-calls onto a single in-flight probe. A transient\n// attempt resolves to false for this call but leaves the cache unset, so the\n// next call re-probes (a one-time startup blip can't pin the process to v1).\nexport function createV2Prober(deps: ProbeDeps): () => Promise<boolean> {\n let cached: boolean | undefined;\n let inFlight: Promise<boolean> | undefined;\n\n const probeOnce = async (): Promise<boolean> => {\n const verdict = await runV2Probe(deps);\n if (verdict !== undefined) cached = verdict;\n return verdict ?? false;\n };\n\n return () => {\n if (cached !== undefined) return Promise.resolve(cached);\n if (!inFlight) {\n inFlight = probeOnce().finally(() => {\n inFlight = undefined;\n });\n }\n return inFlight;\n };\n}\n\n// ---- A2: resolve the backend descriptor for a resource ----\n// `kind` distinguishes a normal REST resource from the GraphQL-backed catalog\n// (v1) and the serverless-runtime jobs base. mapCreate/mapUpdate default to\n// identity under v1; Phase B fills the v2 half (paths + body mappers + unwrap).\nexport type BackendKind = 'rest' | 'graphql';\n\nexport interface Backend {\n kind: BackendKind;\n version: RestVersion;\n base: string;\n list?: string;\n get?: (id: string) => string;\n unwrap: (raw: unknown) => unknown[];\n mapCreate: (body: unknown) => unknown;\n mapUpdate: (body: unknown) => unknown;\n}\n\nconst identity = (body: unknown): unknown => body;\n\n// v1 REST paths, exactly as tools.ts uses them today.\nconst V1_REST_PATHS: Partial<\n Record<Resource, { list: string; get: (id: string) => string }>\n> = {\n pods: { list: '/pods', get: (id) => `/pods/${id}` },\n templates: { list: '/templates', get: (id) => `/templates/${id}` },\n networkVolumes: {\n list: '/networkvolumes',\n get: (id) => `/networkvolumes/${id}`,\n },\n registries: {\n list: '/containerregistryauth',\n get: (id) => `/containerregistryauth/${id}`,\n },\n endpoints: { list: '/endpoints', get: (id) => `/endpoints/${id}` },\n};\n\nconst CATALOG: ReadonlySet<Resource> = new Set<Resource>([\n 'gpus',\n 'cpus',\n 'dataCenters',\n]);\n\n// Resources that exist ONLY on v2 REST — no v1 REST path and no v1 GraphQL\n// equivalent. Under v1 they resolve to a stub backend (version 'v1', no paths)\n// so the tool emits a clean \"v2 only\" 501 notice instead of throwing on a\n// missing path table entry (mirrors how get-gpu-type/list-cpu-types degrade).\nconst V2_ONLY: ReadonlySet<Resource> = new Set<Resource>([\n 'workers',\n 'billing',\n]);\n\n// v2 REST paths (the control-plane resources). Paths are relative to the v2\n// base which ALREADY includes `/v2` (restV2Base → https://api.runpod.io/v2),\n// so they do NOT repeat the `/v2` prefix — `base + list` = `.../v2/pods`. (The\n// prober relies on the same convention: `base + /catalog/gpus`.)\n// Renames vs v1: network-volumes (hyphen), registries (was containerregistryauth),\n// catalog/*. Catalog `datacenters` is intentionally NOT hyphenated (matches the spec).\nconst V2_REST_PATHS: Partial<\n Record<Resource, { list: string; get?: (id: string) => string }>\n> = {\n pods: { list: '/pods', get: (id) => `/pods/${id}` },\n templates: { list: '/templates', get: (id) => `/templates/${id}` },\n networkVolumes: {\n list: '/network-volumes',\n get: (id) => `/network-volumes/${id}`,\n },\n registries: { list: '/registries', get: (id) => `/registries/${id}` },\n gpus: { list: '/catalog/gpus', get: (id) => `/catalog/gpus/${id}` },\n cpus: { list: '/catalog/cpus', get: (id) => `/catalog/cpus/${id}` },\n dataCenters: {\n list: '/catalog/datacenters',\n get: (id) => `/catalog/datacenters/${id}`,\n },\n // Endpoints live under /v2/serverless on v2 (NOT /v2/endpoints). The v2 list\n // response wraps rows under `endpoints` (see V2_UNWRAP_KEY), so the existing\n // unwrap key stays correct.\n endpoints: { list: '/serverless', get: (id) => `/serverless/${id}` },\n // workers/billing build their own sub-paths in the tool from `base`; the\n // list entry just satisfies buildV2Backend (which requires a paths entry).\n workers: { list: '/serverless' },\n billing: { list: '/billing' },\n};\n\n// v2 request-body mappers per resource (identity where v1==v2, e.g. registries).\nconst V2_MAPPERS: Partial<\n Record<\n Resource,\n {\n create: (body: unknown) => unknown;\n update: (body: unknown) => unknown;\n }\n >\n> = {\n pods: {\n create: (b) =>\n mapPodCreateToV2(b as Parameters<typeof mapPodCreateToV2>[0]),\n update: (b) =>\n mapPodUpdateToV2(b as Parameters<typeof mapPodUpdateToV2>[0]),\n },\n templates: {\n create: (b) =>\n mapTemplateCreateToV2(b as Parameters<typeof mapTemplateCreateToV2>[0]),\n update: (b) =>\n mapTemplateUpdateToV2(b as Parameters<typeof mapTemplateUpdateToV2>[0]),\n },\n networkVolumes: {\n create: (b) =>\n mapNetworkVolumeCreateToV2(\n b as Parameters<typeof mapNetworkVolumeCreateToV2>[0]\n ),\n update: identity,\n },\n registries: { create: identity, update: identity },\n endpoints: {\n create: (b) =>\n mapEndpointCreateToV2(b as Parameters<typeof mapEndpointCreateToV2>[0]),\n update: (b) =>\n mapEndpointUpdateToV2(b as Parameters<typeof mapEndpointUpdateToV2>[0]),\n },\n};\n\n// The unwrap closure is built once in resolveBackend and threaded into whichever\n// builder runs, so the per-version builders stay free of unwrap plumbing.\ntype Unwrap = Backend['unwrap'];\n\n// jobs never reaches here (pinned v1 by resolveVersion). Endpoints DO reach here\n// on v2 → /v2/serverless via V2_REST_PATHS + V2_MAPPERS. Catalog v2 is REST (not\n// GraphQL); CRUD resources use their v2 paths + body mappers.\nfunction buildV2Backend(resource: Resource, env: Env, unwrap: Unwrap): Backend {\n const paths = V2_REST_PATHS[resource];\n if (!paths) throw new Error(`no v2 REST paths for resource \"${resource}\"`);\n const mappers = V2_MAPPERS[resource];\n return {\n kind: 'rest',\n version: 'v2',\n base: restV2Base(env),\n list: paths.list,\n get: paths.get,\n unwrap,\n mapCreate: mappers?.create ?? identity,\n mapUpdate: mappers?.update ?? identity,\n };\n}\n\nfunction buildV1Backend(resource: Resource, env: Env, unwrap: Unwrap): Backend {\n // jobs: serverless runtime base; no list/get/path table entry.\n if (resource === 'jobs') {\n return {\n kind: 'rest',\n version: 'v1',\n base: serverlessBase(env),\n unwrap,\n mapCreate: identity,\n mapUpdate: identity,\n };\n }\n // v2-only resources have no v1 home: return a stub so the tool sees\n // version 'v1' and returns a clean 501 (it never reads list/get on this).\n if (V2_ONLY.has(resource)) {\n return {\n kind: 'rest',\n version: 'v1',\n base: restV1Base(env),\n unwrap,\n mapCreate: identity,\n mapUpdate: identity,\n };\n }\n // v1 catalog is GraphQL via a separate helper (kept out of the unified REST\n // client through Phase A); folds into REST at Step B5.\n if (CATALOG.has(resource)) {\n return {\n kind: 'graphql',\n version: 'v1',\n base: publicGraphqlBase(env),\n unwrap,\n mapCreate: identity,\n mapUpdate: identity,\n };\n }\n const paths = V1_REST_PATHS[resource];\n if (!paths) throw new Error(`no v1 REST paths for resource \"${resource}\"`);\n return {\n kind: 'rest',\n version: 'v1',\n base: restV1Base(env),\n list: paths.list,\n get: paths.get,\n unwrap,\n mapCreate: identity,\n mapUpdate: identity,\n };\n}\n\nexport function resolveBackend(opts: {\n resource: Resource;\n env: Env;\n ctx: VersionCtx;\n v2Available?: boolean;\n}): Backend {\n const { resource, env } = opts;\n const version = resolveVersion(opts);\n const unwrap: Unwrap = (raw) => unwrapList(resource, version, raw);\n return version === 'v2'\n ? buildV2Backend(resource, env, unwrap)\n : buildV1Backend(resource, env, unwrap);\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { capListResult, listPaginationParams } from '../pagination.js';\nimport { READ_ONLY, type ToolRuntime } from './runtime.js';\n\n// ============== INFRASTRUCTURE / CATALOG TOOLS ==============\n// GPU types, data centers, CPU types. v1 reads these over GraphQL; v2 has a REST\n// catalog (GET /v2/catalog/*). The adapter picks the backend; v2-only entries\n// return a 501 notice on v1.\n\n// Availability product contexts accepted by the v2 GPU catalog. Availability is\n// product-specific, so the API requires `product` with include=AVAILABILITY —\n// and rejects it without (400 either way).\nconst GPU_PRODUCTS = new Set(['POD', 'CLUSTER', 'SERVERLESS']);\n\nexport function registerCatalogTools(server: McpServer, rt: ToolRuntime): void {\n const { jsonReply, graphql, callRestUrl, backendFor } = rt;\n\n // List GPU Types\n server.tool(\n 'list-gpu-types',\n 'List available GPU types with stock/pricing and capability filters (minimum VRAM, secure/community cloud, name search). Use this to discover valid gpuTypeIds before creating a pod or endpoint. By default the full catalog is returned: each result includes an `availability` summary (HIGH/MEDIUM/LOW/NONE) and results are sorted with the most-available GPUs first, but nothing is hidden. Set includeUnavailable:false to drop out-of-stock GPUs and list only deployable ones (set includeAvailability:false to skip the stock lookup entirely). For per-datacenter availability (to pick a dataCenterIds), use get-gpu-type.',\n {\n ...listPaginationParams,\n minMemoryGb: z\n .number()\n .optional()\n .describe('Filter to GPUs with at least this much VRAM in GB'),\n secureCloudOnly: z\n .boolean()\n .optional()\n .describe('Filter to only GPUs available in secure cloud'),\n communityCloudOnly: z\n .boolean()\n .optional()\n .describe('Filter to only GPUs available in community cloud'),\n searchTerm: z\n .string()\n .optional()\n .describe(\n \"Search term to filter GPUs by name (e.g., 'A100', 'RTX 4090')\"\n ),\n includeUnavailable: z\n .boolean()\n .optional()\n .describe(\n 'Out-of-stock GPUs are included by default (annotated availability:NONE and sorted last). Set false to hide them and list only currently-deployable GPUs.'\n ),\n includeAvailability: z\n .boolean()\n .optional()\n .describe(\n 'Request realtime stock and annotate each GPU with an availability summary (HIGH/MEDIUM/LOW/NONE). Default true. Set false to skip the availability lookup — then out-of-stock GPUs cannot be filtered.'\n ),\n product: z\n .enum(['POD', 'CLUSTER', 'SERVERLESS'])\n .optional()\n .describe(\n 'Product context for the availability lookup — the same GPU can be scarce for Pods and plentiful for Serverless. Default POD. Use SERVERLESS when picking a GPU for an endpoint, CLUSTER for Instant Clusters. Ignored when includeAvailability is false.'\n ),\n minCudaVersion: z\n .string()\n .optional()\n .describe(\n 'Scope the availability lookup to hosts at or above this CUDA version (v2 only). Integer major or major.minor — \"12\" means any 12.x release; unlike the create-pod/create-endpoint body fields, a bare major is accepted here because the filter only widens a read. Requires includeAvailability (the default).'\n ),\n },\n { title: 'List GPU types', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('gpus');\n if (params.minCudaVersion !== undefined && backend.version !== 'v2') {\n return jsonReply({\n error:\n 'The minCudaVersion filter is only supported on the v2 REST API. Set RUNPOD_REST_VERSION=v2, or use get-capacity for per-CUDA-version availability.',\n status: 501,\n });\n }\n if (backend.version === 'v2') {\n // v2 REST: GET /v2/catalog/gpus?include=AVAILABILITY&product=… →\n // { gpus: [...] }, each with an `availability` summary\n // (HIGH/MEDIUM/LOW/NONE) and a per-datacenter `dataCenters` breakdown.\n // Filters re-applied against v2 field names. Opt out with\n // includeAvailability:false (then the filter/sort below no-op, since\n // there's no data). `product` rides along exactly when availability is\n // requested — the API requires it with include=AVAILABILITY and\n // rejects it without. Re-validated here because direct handler calls\n // can bypass zod (same stance as get-capacity).\n const wantAvailability = params.includeAvailability !== false;\n const product = GPU_PRODUCTS.has(String(params.product))\n ? String(params.product)\n : 'POD';\n // The filter scopes the availability lookup, so it is meaningless (and\n // rejected by the API) without include=AVAILABILITY. Bare major is\n // legal here — this filter only widens a read. Re-validated like\n // `product` because direct handler calls can bypass zod.\n if (params.minCudaVersion !== undefined) {\n if (!wantAvailability) {\n return jsonReply({\n error:\n 'minCudaVersion filters the availability lookup, so it requires includeAvailability (the default). Drop includeAvailability:false or the filter.',\n status: 400,\n });\n }\n if (!/^\\d+(\\.\\d+)?$/.test(params.minCudaVersion)) {\n return jsonReply({\n error:\n 'minCudaVersion must be an integer major (\"12\") or major.minor (\"12.1\").',\n status: 400,\n });\n }\n }\n // Built as params rather than nested template ternaries: the query is\n // all-or-nothing on availability, and minCudaVersion only rides along\n // with it (guarded above).\n const query = new URLSearchParams();\n if (wantAvailability) {\n query.set('include', 'AVAILABILITY');\n query.set('product', product);\n if (params.minCudaVersion !== undefined) {\n query.set('minCudaVersion', params.minCudaVersion);\n }\n }\n const raw = await callRestUrl(\n `${backend.base}${backend.list}${query.size ? `?${query}` : ''}`\n );\n let gpus = backend.unwrap(raw) as Array<Record<string, unknown>>;\n // Drop the \"unknown\" sentinel (matches the v1 path). It's a NONE-stock\n // placeholder that used to be masked by the default hide; now that the\n // full catalog shows by default it would otherwise leak into the list.\n gpus = gpus.filter((g) => g.id !== 'unknown');\n if (params.minMemoryGb !== undefined)\n gpus = gpus.filter(\n (g) => Number(g.memory ?? 0) >= params.minMemoryGb!\n );\n if (params.secureCloudOnly) gpus = gpus.filter((g) => g.secure);\n if (params.communityCloudOnly) gpus = gpus.filter((g) => g.community);\n if (params.searchTerm) {\n const t = params.searchTerm.toLowerCase();\n gpus = gpus.filter(\n (g) =>\n String(g.id ?? '')\n .toLowerCase()\n .includes(t) ||\n String(g.name ?? '')\n .toLowerCase()\n .includes(t)\n );\n }\n // Full catalog by default; only includeUnavailable:false filters down to\n // deployable GPUs. A GPU with no `availability` from the backend is\n // treated as available, so the opt-in never over-filters.\n if (params.includeUnavailable === false)\n gpus = gpus.filter((g) => g.availability !== 'NONE');\n // Highest stock first so the best pick is at the top.\n const rank: Record<string, number> = {\n HIGH: 3,\n MEDIUM: 2,\n LOW: 1,\n NONE: 0,\n };\n gpus.sort(\n (a, b) =>\n (rank[String(b.availability)] ?? 0) -\n (rank[String(a.availability)] ?? 0)\n );\n // Drop the bulky per-datacenter breakdown from the list; keep the\n // `availability` summary. get-gpu-type returns the full detail.\n gpus = gpus.map(({ dataCenters: _dataCenters, ...rest }) => rest);\n return capListResult(gpus, {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n\n interface GpuTypesResponse {\n gpuTypes: Array<{\n id: string;\n displayName: string;\n memoryInGb: number;\n secureCloud: boolean;\n communityCloud: boolean;\n lowestPrice?: { stockStatus: string | null } | null;\n }>;\n }\n\n const data = await graphql<GpuTypesResponse>(`\n query {\n gpuTypes {\n id\n displayName\n memoryInGb\n secureCloud\n communityCloud\n lowestPrice(input: { gpuCount: 1 }) {\n stockStatus\n }\n }\n }\n `);\n\n const stockPriority: Record<string, number> = {\n High: 3,\n Medium: 2,\n Low: 1,\n };\n\n const isAvailable = (gpu: GpuTypesResponse['gpuTypes'][number]) => {\n const status = gpu.lowestPrice?.stockStatus;\n return !!status && status !== 'Out';\n };\n\n let gpuTypes = data.gpuTypes.filter((gpu) => gpu.id !== 'unknown');\n\n // Full catalog by default; opt in (includeUnavailable:false) to hide out-of-stock.\n if (params.includeUnavailable === false) {\n gpuTypes = gpuTypes.filter(isAvailable);\n }\n if (params.minMemoryGb) {\n gpuTypes = gpuTypes.filter(\n (gpu) => gpu.memoryInGb >= params.minMemoryGb!\n );\n }\n if (params.secureCloudOnly) {\n gpuTypes = gpuTypes.filter((gpu) => gpu.secureCloud);\n }\n if (params.communityCloudOnly) {\n gpuTypes = gpuTypes.filter((gpu) => gpu.communityCloud);\n }\n if (params.searchTerm) {\n const term = params.searchTerm.toLowerCase();\n gpuTypes = gpuTypes.filter(\n (gpu) =>\n gpu.id.toLowerCase().includes(term) ||\n gpu.displayName.toLowerCase().includes(term)\n );\n }\n\n gpuTypes.sort((a, b) => {\n const aP = stockPriority[a.lowestPrice?.stockStatus || ''] || 0;\n const bP = stockPriority[b.lowestPrice?.stockStatus || ''] || 0;\n if (bP !== aP) return bP - aP;\n return b.memoryInGb - a.memoryInGb;\n });\n\n const result = gpuTypes.map((gpu) => ({\n id: gpu.id,\n displayName: gpu.displayName,\n memoryGb: gpu.memoryInGb,\n secureCloud: gpu.secureCloud,\n communityCloud: gpu.communityCloud,\n stockStatus: gpu.lowestPrice?.stockStatus || 'unavailable',\n available: isAvailable(gpu),\n }));\n\n return capListResult(result, {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n );\n\n // List Data Centers\n server.tool(\n 'list-data-centers',\n 'List Runpod data centers (id, name, region/location). Use this to discover valid dataCenterIds for placing pods, endpoints, or network volumes.',\n {\n ...listPaginationParams,\n region: z\n .string()\n .optional()\n .describe(\n \"Filter by region/location (e.g., 'United States', 'Europe', 'Canada')\"\n ),\n },\n { title: 'List data centers', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('dataCenters');\n if (backend.version === 'v2') {\n // v2 REST: GET /v2/catalog/datacenters → { dataCenters: [...] }. v2 uses\n // a `region` enum (vs v1 free-text `location`); region filter matches it.\n const raw = await callRestUrl(`${backend.base}${backend.list}`);\n let dcs = backend.unwrap(raw) as Array<Record<string, unknown>>;\n if (params.region) {\n const t = params.region.toLowerCase();\n dcs = dcs.filter((dc) =>\n String(dc.region ?? '')\n .toLowerCase()\n .includes(t)\n );\n }\n return capListResult(dcs, {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n\n interface DataCentersResponse {\n dataCenters: Array<{\n id: string;\n name: string;\n location: string;\n }>;\n }\n\n const data = await graphql<DataCentersResponse>(`\n query {\n dataCenters {\n id\n name\n location\n }\n }\n `);\n\n let dataCenters = data.dataCenters;\n\n if (params.region) {\n const term = params.region.toLowerCase();\n dataCenters = dataCenters.filter((dc) =>\n dc.location.toLowerCase().includes(term)\n );\n }\n\n return capListResult(dataCenters, {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n );\n\n // List CPU Types (v2-only — v2 catalog REST has no v1/GraphQL equivalent)\n server.tool(\n 'list-cpu-types',\n 'List available CPU flavor types for CPU pods/endpoints. v2-only — returns a 501 notice on the v1 API.',\n { ...listPaginationParams },\n { title: 'List CPU types', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('cpus');\n if (backend.version === 'v1') {\n return jsonReply({\n error:\n 'list-cpu-types is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.',\n status: 501,\n });\n }\n const raw = await callRestUrl(`${backend.base}${backend.list}`);\n return capListResult(backend.unwrap(raw), {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n );\n\n // Get GPU Type by id (v2-only — GET /v2/catalog/gpus/{id})\n server.tool(\n 'get-gpu-type',\n 'Get details for a single GPU type by id, including per-datacenter availability. v2-only — returns a 501 notice on the v1 API (use list-gpu-types there). Use the returned dataCenters[].availability to pick a dataCenterIds with stock before creating a pod.',\n {\n gpuTypeId: z.string().describe('ID of the GPU type to retrieve'),\n includeAvailability: z\n .boolean()\n .optional()\n .describe(\n 'Include realtime per-datacenter availability (HIGH/MEDIUM/LOW/NONE). Default true.'\n ),\n product: z\n .enum(['POD', 'CLUSTER', 'SERVERLESS'])\n .optional()\n .describe(\n 'Product context for the availability lookup — stock differs by product. Default POD. Use SERVERLESS when picking a GPU for an endpoint, CLUSTER for Instant Clusters. Ignored when includeAvailability is false.'\n ),\n },\n { title: 'Get GPU type', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('gpus');\n if (backend.version === 'v1') {\n return jsonReply({\n error:\n 'get-gpu-type is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2 (or use list-gpu-types on v1).',\n status: 501,\n });\n }\n // GPU ids contain spaces (e.g. \"NVIDIA GeForce RTX 4090\"), so encode the\n // path segment. Availability on by default — it's the point of a single\n // GPU. `product` goes with it (required with include=AVAILABILITY,\n // rejected without); re-validated for zod-bypassed direct calls.\n const path = backend.get!(encodeURIComponent(params.gpuTypeId));\n const product = GPU_PRODUCTS.has(String(params.product))\n ? String(params.product)\n : 'POD';\n const query =\n params.includeAvailability === false\n ? ''\n : `?include=AVAILABILITY&product=${product}`;\n const result = await callRestUrl(`${backend.base}${path}${query}`);\n return jsonReply(result);\n }\n );\n\n // Get CPU Type by id (v2-only — GET /v2/catalog/cpus/{id})\n server.tool(\n 'get-cpu-type',\n 'Get details for a single CPU flavor type by id. v2-only — returns a 501 notice on the v1 API (use list-cpu-types there).',\n {\n cpuTypeId: z.string().describe('ID of the CPU type to retrieve'),\n },\n { title: 'Get CPU type', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('cpus');\n if (backend.version === 'v1') {\n return jsonReply({\n error:\n 'get-cpu-type is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.',\n status: 501,\n });\n }\n const result = await callRestUrl(\n `${backend.base}${backend.get!(params.cpuTypeId)}`\n );\n return jsonReply(result);\n }\n );\n\n // Get capacity (GPU × host-CUDA availability). Public GraphQL on both API\n // versions — the v2 REST catalog's only CUDA dimension is a minCudaVersion\n // floor filter on availability (see MinCudaVersionFilter in the vendored\n // spec); the exact per-version breakdown, per-version graded stock, and\n // per-version pricing this tool returns are GraphQL-only, so unlike the\n // other catalog tools this one never branches on backendFor.\n server.tool(\n 'get-capacity',\n \"GPU capacity across host CUDA versions, as a matrix. Use this to choose an endpoint's allowedCudaVersions/minCudaVersion (or diagnose/widen a capacity-starved one) and to distinguish capacity problems from compatibility problems. Default mode is one call returning, per GPU type, overall stock plus AVAILABLE/UNAVAILABLE per host-reported CUDA version. Pass cudaVersions to deep-probe instead: one stock lookup per listed version, returning graded stock (High/Medium/Low/Out) and the lowest on-demand price per version; a probe that fails transiently reports a per-version error instead of failing the call. Nothing is hidden by default — set includeUnavailable:false to drop GPUs with no stock on any listed version. Credential-free public catalog data; works on both v1 and v2 APIs. Stock is live, so page cursors can shift between calls. Note: the matrix reflects host-reported versions, but endpoint allowedCudaVersions only accepts values from the platform enum — check create-endpoint/update-endpoint for the accepted list.\",\n {\n ...listPaginationParams,\n cudaVersions: z\n .array(z.string().regex(/^\\d{1,2}\\.\\d{1,2}$/))\n .min(1)\n .max(12)\n .optional()\n .describe(\n 'Deep-probe these host CUDA versions (e.g. [\"12.8\", \"13.0\"], max 12): one stock lookup per version returns graded stock and price. Omit for the single-call AVAILABLE/UNAVAILABLE matrix across all versions the fleet currently reports.'\n ),\n includeUnavailable: z\n .boolean()\n .optional()\n .describe(\n 'Out-of-stock GPUs are included by default (explicit \"Out\" cells / all-UNAVAILABLE rows, sorted last). Set false to hide GPUs with no stock on any listed version.'\n ),\n gpuTypeIds: z\n .array(z.string())\n .optional()\n .describe(\n \"Filter to GPU types matching any of these ids or display names (case-insensitive substring, e.g. ['NVIDIA GeForce RTX 4090'] or ['4090', 'H200']).\"\n ),\n gpuCount: z\n .number()\n .int()\n .min(1)\n .max(8)\n .optional()\n .describe('GPUs per worker/pod to check stock for (default 1).'),\n secureCloudOnly: z\n .boolean()\n .optional()\n .describe('Restrict the stock lookup to Secure Cloud hosts.'),\n },\n { title: 'Get GPU capacity by CUDA version', ...READ_ONLY },\n async (params) => {\n // The public GraphQL path takes no variables, so arguments are inlined.\n // Every inlined value is re-validated here at runtime (not just in zod)\n // because direct handler calls can bypass schema validation: gpuCount is\n // coerced to an int in [1,8], cudaVersions is regex-checked below, and\n // secureCloud is a literal.\n const gpuCount = Math.min(\n 8,\n Math.max(1, Math.floor(Number(params.gpuCount)) || 1)\n );\n const secureArg = params.secureCloudOnly ? ', secureCloud: true' : '';\n\n interface CapacityGpu {\n id: string;\n displayName: string;\n memoryInGb: number;\n secureCloud: boolean;\n communityCloud: boolean;\n lowestPrice?: {\n stockStatus: string | null;\n uninterruptablePrice: number | null;\n gpuTypeCudaVersions?: Array<{\n cudaVersion: string;\n availability: string;\n }> | null;\n } | null;\n }\n interface CapacityResponse {\n gpuTypes: CapacityGpu[];\n }\n\n // Blank/non-string filter entries are ignored (an all-blank list means\n // \"no filter\", same as omitting it) — a zod-bypassed [\"\"] must not\n // silently match the whole catalog as if it were a real term.\n const filterTerms = (params.gpuTypeIds ?? [])\n .filter((t) => typeof t === 'string' && t.trim().length > 0)\n .map((t) => t.toLowerCase());\n const matchesFilter = (gpu: CapacityGpu) => {\n if (filterTerms.length === 0) return true;\n const id = gpu.id.toLowerCase();\n const name = gpu.displayName.toLowerCase();\n return filterTerms.some(\n (term) => id.includes(term) || name.includes(term)\n );\n };\n\n const stockPriority: Record<string, number> = {\n High: 3,\n Medium: 2,\n Low: 1,\n };\n\n // Matrix mode: one query, per-version availability from the fleet.\n if (!params.cudaVersions?.length) {\n const data = await graphql<CapacityResponse>(`\n query {\n gpuTypes {\n id\n displayName\n memoryInGb\n secureCloud\n communityCloud\n lowestPrice(input: { gpuCount: ${gpuCount}${secureArg} }) {\n stockStatus\n uninterruptablePrice\n gpuTypeCudaVersions {\n cudaVersion\n availability\n }\n }\n }\n }\n `);\n let rows = data.gpuTypes\n .filter((gpu) => gpu.id !== 'unknown' && matchesFilter(gpu))\n .map((gpu) => {\n const cuda: Record<string, string> = {};\n for (const c of gpu.lowestPrice?.gpuTypeCudaVersions ?? []) {\n cuda[c.cudaVersion] = c.availability;\n }\n return {\n id: gpu.id,\n displayName: gpu.displayName,\n memoryGb: gpu.memoryInGb,\n secureCloud: gpu.secureCloud,\n communityCloud: gpu.communityCloud,\n stockStatus: gpu.lowestPrice?.stockStatus || 'unavailable',\n pricePerHr: gpu.lowestPrice?.uninterruptablePrice ?? null,\n cudaVersions: cuda,\n };\n });\n const availableCount = (r: (typeof rows)[number]) =>\n Object.values(r.cudaVersions).filter((a) => a === 'AVAILABLE').length;\n if (params.includeUnavailable === false)\n rows = rows.filter((r) => availableCount(r) > 0);\n rows.sort((a, b) => {\n const diff = availableCount(b) - availableCount(a);\n if (diff !== 0) return diff;\n const stockDiff =\n (stockPriority[b.stockStatus] || 0) -\n (stockPriority[a.stockStatus] || 0);\n if (stockDiff !== 0) return stockDiff;\n return b.memoryGb - a.memoryGb;\n });\n return capListResult(rows, {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n\n // Probe mode: one stock lookup per requested version, merged per GPU.\n // Zod caps at 12, but the cap is re-enforced here because direct handler\n // calls (tests, other transports) can bypass schema validation.\n const versions = [...new Set(params.cudaVersions)].slice(0, 12);\n const invalid = versions.filter((v) => !/^\\d{1,2}\\.\\d{1,2}$/.test(v));\n if (invalid.length > 0) {\n return jsonReply({\n error: `Invalid CUDA version format: ${invalid.join(', ')}. Use \"major.minor\" strings like \"12.8\".`,\n status: 400,\n });\n }\n // allSettled, not all: a transient failure (429/5xx) on one probe must\n // not discard the other versions' results — the same best-effort stance\n // as get-job-status's worker fan-out. Failed versions are reported in a\n // probeErrors sibling field instead of failing the call.\n const perVersion = await Promise.allSettled(\n versions.map((v) =>\n graphql<CapacityResponse>(`\n query {\n gpuTypes {\n id\n displayName\n memoryInGb\n secureCloud\n communityCloud\n lowestPrice(input: { gpuCount: ${gpuCount}, allowedCudaVersions: [\"${v}\"]${secureArg} }) {\n stockStatus\n uninterruptablePrice\n }\n }\n }\n `)\n )\n );\n\n const probeErrors: Record<string, string> = {};\n const byId = new Map<\n string,\n {\n id: string;\n displayName: string;\n memoryGb: number;\n secureCloud: boolean;\n communityCloud: boolean;\n cudaVersions: Record<\n string,\n { stock: string; pricePerHr: number | null }\n >;\n }\n >();\n versions.forEach((v, i) => {\n const settled = perVersion[i];\n if (settled.status === 'rejected') {\n probeErrors[v] =\n settled.reason instanceof Error\n ? settled.reason.message\n : String(settled.reason);\n return;\n }\n for (const gpu of settled.value.gpuTypes) {\n if (gpu.id === 'unknown' || !matchesFilter(gpu)) continue;\n let row = byId.get(gpu.id);\n if (!row) {\n row = {\n id: gpu.id,\n displayName: gpu.displayName,\n memoryGb: gpu.memoryInGb,\n secureCloud: gpu.secureCloud,\n communityCloud: gpu.communityCloud,\n cudaVersions: {},\n };\n byId.set(gpu.id, row);\n }\n const stock = gpu.lowestPrice?.stockStatus;\n // No stockStatus means no hosts match this version at all — an\n // explicit \"Out\" cell, so starvation is visible rather than an\n // absence the agent has to infer (a capacity-diagnosis tool must\n // not hide the empty cells it exists to reveal).\n row.cudaVersions[v] =\n !stock || stock === 'Out'\n ? { stock: 'Out', pricePerHr: null }\n : {\n stock,\n pricePerHr: gpu.lowestPrice?.uninterruptablePrice ?? null,\n };\n }\n });\n\n let rows = [...byId.values()];\n const inStockCount = (r: (typeof rows)[number]) =>\n Object.values(r.cudaVersions).filter((c) => c.stock !== 'Out').length;\n if (params.includeUnavailable === false)\n rows = rows.filter((r) => inStockCount(r) > 0);\n rows.sort((a, b) => {\n const diff = inStockCount(b) - inStockCount(a);\n if (diff !== 0) return diff;\n const best = (r: (typeof rows)[number]) =>\n Math.max(\n 0,\n ...Object.values(r.cudaVersions).map(\n (c) => stockPriority[c.stock] || 0\n )\n );\n const bestDiff = best(b) - best(a);\n if (bestDiff !== 0) return bestDiff;\n return b.memoryGb - a.memoryGb;\n });\n return capListResult(\n rows,\n { limit: params.limit, cursor: params.cursor },\n {\n probedCudaVersions: versions,\n ...(Object.keys(probeErrors).length > 0 ? { probeErrors } : {}),\n }\n );\n }\n );\n\n // Get Data Center by id (v2-only — GET /v2/catalog/datacenters/{id})\n server.tool(\n 'get-data-center',\n 'Get details for a single data center by id. v2-only — returns a 501 notice on the v1 API (use list-data-centers there).',\n {\n dataCenterId: z.string().describe('ID of the data center to retrieve'),\n },\n { title: 'Get data center', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('dataCenters');\n if (backend.version === 'v1') {\n return jsonReply({\n error:\n 'get-data-center is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.',\n status: 501,\n });\n }\n const result = await callRestUrl(\n `${backend.base}${backend.get!(params.dataCenterId)}`\n );\n return jsonReply(result);\n }\n );\n}\n","// ============== LIST RESULT PAGINATION / TOKEN CAPS ==============\n// The REST list endpoints do not yet support server-side pagination, so a\n// large account can return a payload many times the size of an LLM's context\n// window (e.g. list-templates has been observed at ~15x Claude Code's window),\n// which ends the agent session. Until the REST API ships cursor pagination,\n// the MCP caps list responses client-side: it returns at most `limit` items\n// and reports how many were omitted so the agent knows to narrow its query.\n//\n// The `limit` and `cursor` parameters are shaped to match the cursor-based\n// pagination the REST API will add, so the tool signatures do not change when\n// server-side pagination lands; the MCP will simply forward them and surface a\n// real `nextCursor` instead of the client-side offset used here.\n\nimport { z } from 'zod';\n\n// Default and maximum number of items returned by a list tool in a single\n// call. The cap is what protects the agent's context; `limit` lets a caller\n// ask for fewer (or more, up to the ceiling) when they know what they want.\nexport const DEFAULT_LIST_LIMIT = 20;\nexport const MAX_LIST_LIMIT = 100;\n\n// Shared Zod parameters added to every list-* tool. Optional so existing\n// callers that pass nothing keep working (they get the default page).\nexport const listPaginationParams = {\n limit: z\n .number()\n .int()\n .positive()\n .max(MAX_LIST_LIMIT)\n .optional()\n .describe(\n `Maximum number of items to return (default ${DEFAULT_LIST_LIMIT}, max ${MAX_LIST_LIMIT}). Use the returned nextCursor to fetch the next page.`\n ),\n cursor: z\n .string()\n .optional()\n .describe(\n 'Opaque pagination cursor from a previous response (nextCursor). Omit to start from the beginning.'\n ),\n};\n\n// Decodes the opaque cursor into a numeric offset. The cursor is currently a\n// base64-encoded offset; when the REST API ships real cursors this becomes a\n// passthrough. Invalid cursors are treated as the start so a bad value never\n// throws in an agent's face.\nexport function decodeCursorOffset(cursor: string | undefined): number {\n if (!cursor) return 0;\n try {\n const decoded = Buffer.from(cursor, 'base64').toString('utf8');\n const offset = Number.parseInt(decoded, 10);\n return Number.isFinite(offset) && offset >= 0 ? offset : 0;\n } catch {\n return 0;\n }\n}\n\nexport function encodeCursorOffset(offset: number): string {\n return Buffer.from(String(offset), 'utf8').toString('base64');\n}\n\n// Caps a list response to `limit` items starting at `cursor`, returning an\n// envelope that tells the agent how much it is seeing and how to get the rest.\n// Only arrays are paginated; any other shape is passed through untouched.\n//\n// `extra` merges sibling fields alongside `items`/`pagination` (e.g. billing\n// `metadata`, an endpoint-workers `summary`) so a capped list response can still\n// carry the small aggregate fields that accompany the array. Reserved keys\n// (`items`, `pagination`) in `extra` are ignored so they can't shadow the cap.\nexport function capListResult(\n result: unknown,\n options: { limit?: number; cursor?: string },\n extra?: Record<string, unknown>\n): { content: Array<{ type: 'text'; text: string }> } {\n const limit = Math.min(options.limit ?? DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT);\n const offset = decodeCursorOffset(options.cursor);\n\n if (!Array.isArray(result)) {\n return {\n content: [\n { type: 'text' as const, text: JSON.stringify(result, null, 2) },\n ],\n };\n }\n\n const total = result.length;\n const page = result.slice(offset, offset + limit);\n const nextOffset = offset + page.length;\n const hasMore = nextOffset < total;\n\n const { items: _i, pagination: _p, ...safeExtra } = extra ?? {};\n void _i;\n void _p;\n\n const envelope = {\n ...safeExtra,\n items: page,\n pagination: {\n total,\n returned: page.length,\n offset,\n truncated: hasMore,\n nextCursor: hasMore ? encodeCursorOffset(nextOffset) : null,\n note: hasMore\n ? `Showing ${page.length} of ${total}. Pass cursor=nextCursor to fetch more, or narrow the query with filters.`\n : undefined,\n },\n };\n\n return {\n content: [\n { type: 'text' as const, text: JSON.stringify(envelope, null, 2) },\n ],\n };\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { capListResult, listPaginationParams } from '../pagination.js';\nimport { READ_ONLY, WRITE, type ToolRuntime } from './runtime.js';\n\n// ============== HUB TOOLS ==============\n// Read-only discovery of Runpod Hub listings (prebuilt serverless workers and\n// pod templates published from GitHub repos, e.g. worker-vllm, worker-comfyui).\n// Served by the public GraphQL endpoint — no auth required — like the v1\n// catalog tools. There is no REST home for the Hub yet, so this is\n// version-agnostic: the same GraphQL path is used on v1 and v2.\n//\n// The `listings(input: {})` query returns the full public catalog; the\n// endpoint does not accept GraphQL variables on the public path, so filters\n// (search, category, type, owner) are applied client-side, matching the\n// list-gpu-types v1 pattern. Each listing carries its currently listed\n// release, whose id (`hubReleaseId`) and built image are what a Hub deploy is\n// pinned to.\n\ninterface HubBuild {\n id: string;\n imageName: string;\n}\n\ninterface HubRelease {\n id: string;\n name: string;\n tagName: string;\n createdAt: string;\n config?: string | null;\n build?: HubBuild | null;\n}\n\ninterface HubListing {\n id: string;\n repoId: string;\n title: string;\n description: string | null;\n repoName: string;\n repoOwner: string;\n createdAt: string;\n updatedAt: string;\n views: number;\n stars: number;\n deploys: number;\n language: string | null;\n category: string | null;\n tags: string[] | null;\n type: string;\n listedRelease?: HubRelease | null;\n}\n\ninterface ListingsResponse {\n listings: HubListing[];\n}\n\n// The release `config` is the repo's .runpod/hub.json config serialized as a\n// JSON string (hardware requirements + the env-var input schema). It can be\n// tens of KB per listing, so it is only requested when includeConfig is set.\nfunction parseReleaseConfig(config: string | null | undefined): unknown {\n if (!config) return undefined;\n try {\n return JSON.parse(config);\n } catch {\n return config;\n }\n}\n\n// Typed view of the parts of the release config a deploy consumes. Everything\n// is optional — the config is repo-authored and defensively read.\ninterface HubReleaseConfig {\n runsOn?: string;\n containerDiskInGb?: number;\n gpuIds?: string;\n gpuCount?: number;\n allowedCudaVersions?: string[];\n env?: Array<{\n key: string;\n input?: {\n name?: string;\n type?: string;\n default?: unknown;\n required?: boolean;\n trueValue?: string;\n falseValue?: string;\n };\n }>;\n}\n\n// The one listings query both hub tools share. `withConfig` pulls in the\n// (large) release config only when the caller needs it.\nfunction listingsQuery(withConfig: boolean): string {\n return `\n query {\n listings(input: {}) {\n id\n repoId\n title\n description\n repoName\n repoOwner\n createdAt\n updatedAt\n views\n stars\n deploys\n language\n category\n tags\n type\n listedRelease {\n id\n name\n tagName\n createdAt${withConfig ? '\\n config' : ''}\n build {\n id\n imageName\n }\n }\n }\n }\n `;\n}\n\n// Serializes a boolean through the schema's trueValue/falseValue. Applied to\n// caller values too: an agent naturally passes 'true' where the schema declares\n// trueValue '1', so the worker would get a value its own schema calls invalid.\n// Only exact 'true'/'false' map; anything else is a deliberate literal.\nfunction serializeBoolean(\n value: string,\n input: { type?: string; trueValue?: string; falseValue?: string }\n): string {\n if (value === 'true') return input.trueValue ?? 'true';\n if (value === 'false') return input.falseValue ?? 'false';\n return value;\n}\n\n// Builds the template env array a Hub deploy submits: every key from the\n// release's env schema with its default (booleans serialized through\n// trueValue/falseValue), overridden by caller-provided values. Caller keys not\n// in the schema are appended verbatim. Returns the missing required keys so\n// the tool can fail with an actionable message instead of a broken endpoint.\nexport function buildHubEnv(\n config: HubReleaseConfig,\n overrides: Record<string, string>\n): {\n env: Array<{ key: string; value: string }>;\n missingRequired: string[];\n} {\n const env: Array<{ key: string; value: string }> = [];\n const missingRequired: string[] = [];\n const remaining = { ...overrides };\n\n for (const entry of config.env ?? []) {\n const input = entry.input ?? {};\n const override = entry.key in remaining ? remaining[entry.key] : undefined;\n delete remaining[entry.key];\n\n // An override present but EMPTY is not a value. Checking `key in remaining`\n // alone let `env: { MODEL_NAME: '' }` satisfy a required key, so fail-fast\n // passed and the deploy produced a broken worker.\n if (override !== undefined && override !== '') {\n env.push({\n key: entry.key,\n value:\n input.type === 'boolean'\n ? serializeBoolean(override, input)\n : override,\n });\n continue;\n }\n\n if (input.default !== undefined && input.default !== null) {\n env.push({\n key: entry.key,\n value:\n typeof input.default === 'boolean'\n ? input.default\n ? (input.trueValue ?? 'true')\n : (input.falseValue ?? 'false')\n : String(input.default),\n });\n continue;\n }\n\n if (input.required) {\n missingRequired.push(entry.key);\n // Still submitted so the key is visible on the endpoint, though the tool\n // aborts on a non-empty missingRequired before the caller sees it.\n env.push({ key: entry.key, value: '' });\n continue;\n }\n\n // Optional, no default, nothing supplied: OMIT the key rather than send ''.\n // os.environ.get('X', 'fb') returns '' when X='', shadowing the fallback.\n }\n\n for (const [key, value] of Object.entries(remaining)) {\n env.push({ key, value });\n }\n\n return { env, missingRequired };\n}\n\n// Lowest entry of the config's allowedCudaVersions (numeric-aware compare, so\n// '12.10' > '12.9'). The console submits this as minCudaVersion.\nexport function minCudaVersion(versions: string[] | undefined): string | null {\n if (!versions || versions.length === 0) return null;\n const compare = (a: string, b: string) => {\n const pa = a.split('.').map(Number);\n const pb = b.split('.').map(Number);\n for (let i = 0; i < Math.max(pa.length, pb.length); i++) {\n const d = (pa[i] ?? 0) - (pb[i] ?? 0);\n if (d !== 0) return d;\n }\n return 0;\n };\n return [...versions].sort(compare)[0];\n}\n\nfunction randomSuffix(): string {\n return Math.random().toString(36).slice(2, 8);\n}\n\nexport function registerHubTools(server: McpServer, rt: ToolRuntime): void {\n const { graphql, graphqlAuthed, jsonReply } = rt;\n\n server.tool(\n 'list-hub-repos',\n 'List repos published to the Runpod Hub (prebuilt Serverless workers and Pod templates, e.g. vLLM, ComfyUI). Public catalog — no auth required. Each result includes the currently listed release with its hubReleaseId and prebuilt image name. Results are sorted by deploy count (most popular first). Set includeConfig:true to also return the release config (hardware requirements and environment-variable schema) — it is large, so prefer requesting it for a single repo via the repoOwner/searchTerm filters.',\n {\n ...listPaginationParams,\n searchTerm: z\n .string()\n .optional()\n .describe(\n \"Case-insensitive search across title, description, repo name, owner, and tags (e.g. 'vllm', 'comfyui', 'fine-tuning')\"\n ),\n category: z\n .string()\n .optional()\n .describe(\n \"Filter by category (e.g. 'language', 'image', 'audio', 'video', 'embedding')\"\n ),\n type: z\n .enum(['SERVERLESS', 'POD'])\n .optional()\n .describe(\n 'Filter by listing type: SERVERLESS (endpoint workers) or POD (pod templates)'\n ),\n repoOwner: z\n .string()\n .optional()\n .describe(\n \"Filter to listings from this GitHub owner/org (e.g. 'runpod-workers', 'axolotl-ai-cloud')\"\n ),\n includeConfig: z\n .boolean()\n .optional()\n .describe(\n 'Include the listed release config (hardware requirements + env-var input schema, parsed from .runpod/hub.json). Large — off by default.'\n ),\n },\n { title: 'List Hub repos', ...READ_ONLY },\n async (params) => {\n const data = await graphql<ListingsResponse>(\n listingsQuery(params.includeConfig === true)\n );\n\n let listings = data.listings;\n\n if (params.type) {\n listings = listings.filter(\n (l) => (l.type ?? '').toUpperCase() === params.type\n );\n }\n if (params.category) {\n const term = params.category.toLowerCase();\n listings = listings.filter(\n (l) => (l.category ?? '').toLowerCase() === term\n );\n }\n if (params.repoOwner) {\n const term = params.repoOwner.toLowerCase();\n listings = listings.filter(\n (l) => (l.repoOwner ?? '').toLowerCase() === term\n );\n }\n if (params.searchTerm) {\n const term = params.searchTerm.toLowerCase();\n listings = listings.filter((l) =>\n [\n l.title,\n l.description,\n l.repoName,\n l.repoOwner,\n ...(l.tags ?? []),\n ].some((field) => (field ?? '').toLowerCase().includes(term))\n );\n }\n\n // Most-deployed first so well-known workers surface at the top, id as\n // tiebreak. The tiebreak is load-bearing: each page re-fetches the whole\n // catalog and `listings(input: {})` promises no order, so ties move\n // between requests and paging silently skips and duplicates.\n listings = [...listings].sort(\n (a, b) =>\n (b.deploys ?? 0) - (a.deploys ?? 0) || a.id.localeCompare(b.id)\n );\n\n const result = listings.map((l) => ({\n id: l.id,\n repo: `${l.repoOwner}/${l.repoName}`,\n title: l.title,\n description: l.description,\n type: l.type,\n category: l.category,\n tags: l.tags,\n language: l.language,\n stars: l.stars,\n deploys: l.deploys,\n views: l.views,\n githubUrl: `https://github.com/${l.repoOwner}/${l.repoName}`,\n hubUrl: `https://console.runpod.io/hub/${l.repoOwner}/${l.repoName}`,\n createdAt: l.createdAt,\n updatedAt: l.updatedAt,\n listedRelease: l.listedRelease\n ? {\n hubReleaseId: l.listedRelease.id,\n name: l.listedRelease.name,\n tagName: l.listedRelease.tagName,\n createdAt: l.listedRelease.createdAt,\n imageName: l.listedRelease.build?.imageName,\n ...(params.includeConfig\n ? { config: parseReleaseConfig(l.listedRelease.config) }\n : {}),\n }\n : null,\n }));\n\n return capListResult(result, {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n );\n\n server.tool(\n 'deploy-hub-repo',\n \"Deploy a Runpod Hub repo's listed release as a new Serverless endpoint (the same as clicking Deploy on the Hub). Identify the repo by `repo` (\\\"owner/name\\\" from list-hub-repos) or by `hubReleaseId`. The release supplies the prebuilt image, container disk, CUDA constraints, and env-var defaults; pass `env` to override or fill in values (required keys without a default must be provided — check list-hub-repos with includeConfig:true for the schema). GPU selection comes from the release config when it specifies one; otherwise pass gpuIds (GPU pool names, e.g. 'ADA_24' or 'ADA_80_PRO,AMPERE_80'). Uses the authenticated GraphQL API (no REST home for Hub deploys yet), so it works the same on v1 and v2.\",\n {\n repo: z\n .string()\n .optional()\n .describe(\n 'The Hub repo to deploy as \"owner/name\" (e.g. \\'runpod-workers/worker-comfyui\\'). Deploys its currently listed release. Provide this or hubReleaseId.'\n ),\n hubReleaseId: z\n .string()\n .optional()\n .describe(\n 'The Hub release ID to deploy (from list-hub-repos listedRelease.hubReleaseId). Provide this or repo.'\n ),\n name: z\n .string()\n .optional()\n .describe(\n \"Name for the new endpoint. Defaults to '<listing title> <release tag>'.\"\n ),\n env: z\n .record(z.string())\n .optional()\n .describe(\n 'Environment variable overrides, merged over the release config defaults. Keys not in the release schema are passed through as-is.'\n ),\n gpuIds: z\n .string()\n .optional()\n .describe(\n \"Comma-separated GPU pool names for workers (e.g. 'ADA_24' or 'ADA_80_PRO,AMPERE_80'). To pin specific GPU SKUs within a pool, append exclusions prefixed with '-' using GPU type ids from list-gpu-types (e.g. 'AMPERE_16,-NVIDIA RTX 2000 Ada Generation,-NVIDIA RTX A4500' allows only the pool's remaining SKUs). Defaults to the release config's gpuIds; required when the config does not specify one.\"\n ),\n gpuCount: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('GPUs per worker. Defaults to the release config (or 1).'),\n containerDiskInGb: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('Container disk size in GB. Defaults to the release config.'),\n workersMin: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe('Minimum workers (default 0 — scale to zero).'),\n workersMax: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('Maximum workers (default: account default).'),\n idleTimeout: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('Seconds a worker idles before scaling down (default 5).'),\n scalerType: z\n .enum(['QUEUE_DELAY', 'REQUEST_COUNT'])\n .optional()\n .describe('Autoscaler type (default QUEUE_DELAY).'),\n scalerValue: z\n .number()\n .optional()\n .describe('Autoscaler target value (default 4).'),\n executionTimeoutMs: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('Per-job execution timeout in ms (default 600000).'),\n flashboot: z\n .enum(['OFF', 'FLASHBOOT', 'PRIORITY_FLASHBOOT'])\n .optional()\n .describe('FlashBoot mode (default FLASHBOOT).'),\n },\n { title: 'Deploy Hub repo', ...WRITE },\n async (params) => {\n if (!params.repo && !params.hubReleaseId) {\n return jsonReply({\n error:\n 'Provide either repo (\"owner/name\") or hubReleaseId. Use list-hub-repos to discover both.',\n });\n }\n\n // Resolve the listing + release from the public catalog. The catalog only\n // exposes each repo's currently LISTED release, so a hubReleaseId must\n // match one of those (which is also the only state the console deploys).\n const catalog = await graphql<ListingsResponse>(listingsQuery(true));\n let listing: HubListing | undefined;\n if (params.hubReleaseId) {\n listing = catalog.listings.find(\n (l) => l.listedRelease?.id === params.hubReleaseId\n );\n } else {\n const repoKey = params.repo!.toLowerCase();\n listing = catalog.listings.find(\n (l) => `${l.repoOwner}/${l.repoName}`.toLowerCase() === repoKey\n );\n }\n if (!listing) {\n return jsonReply({\n error: `No Hub listing found for ${\n params.hubReleaseId\n ? `hubReleaseId \"${params.hubReleaseId}\" (only each repo's currently listed release is deployable)`\n : `repo \"${params.repo}\"`\n }. Use list-hub-repos to see the catalog.`,\n });\n }\n const release = listing.listedRelease;\n if (!release?.build?.imageName) {\n return jsonReply({\n error: `Hub repo ${listing.repoOwner}/${listing.repoName} has no listed release with a built image, so it cannot be deployed.`,\n });\n }\n if ((listing.type ?? '').toUpperCase() !== 'SERVERLESS') {\n return jsonReply({\n error: `Hub repo ${listing.repoOwner}/${listing.repoName} is a ${listing.type} listing — only SERVERLESS listings deploy as endpoints.`,\n });\n }\n\n const config = (parseReleaseConfig(release.config) ??\n {}) as HubReleaseConfig;\n\n const gpuIds = params.gpuIds ?? config.gpuIds;\n if (!gpuIds) {\n return jsonReply({\n error:\n 'This release does not specify a GPU pool — pass gpuIds (comma-separated pool names, e.g. \"ADA_80_PRO,AMPERE_80\"; see the pool field on list-gpu-types).',\n });\n }\n\n const { env, missingRequired } = buildHubEnv(config, params.env ?? {});\n if (missingRequired.length > 0) {\n return jsonReply({\n error: `Missing required environment variables for this release: ${missingRequired.join(\n ', '\n )}. Pass them via the env parameter.`,\n });\n }\n\n const endpointName = params.name ?? `${listing.title} ${release.tagName}`;\n const minCuda = minCudaVersion(config.allowedCudaVersions);\n\n const input: Record<string, unknown> = {\n name: endpointName,\n hubReleaseId: release.id,\n type: 'QB',\n gpuIds,\n gpuCount: params.gpuCount ?? config.gpuCount ?? 1,\n workersMin: params.workersMin ?? 0,\n workersMax: params.workersMax ?? null,\n idleTimeout: params.idleTimeout ?? 5,\n scalerType: params.scalerType ?? 'QUEUE_DELAY',\n scalerValue: params.scalerValue ?? 4,\n executionTimeoutMs: params.executionTimeoutMs ?? 600000,\n flashBootType: params.flashboot ?? 'FLASHBOOT',\n locations: null,\n networkVolumeIds: null,\n compliance: [],\n modelReferences: [],\n ...(minCuda\n ? {\n minCudaVersion: minCuda,\n allowedCudaVersions: config.allowedCudaVersions!.join(','),\n }\n : {}),\n template: {\n name: `${endpointName}__template__${randomSuffix()}`,\n imageName: release.build.imageName,\n containerDiskInGb:\n params.containerDiskInGb ?? config.containerDiskInGb ?? 20,\n containerRegistryAuthId: '',\n dockerArgs: '',\n startScript: '',\n ports: '',\n env,\n },\n };\n\n interface SaveEndpointResponse {\n saveEndpoint: {\n id: string;\n name: string;\n gpuIds: string;\n gpuCount: number;\n workersMin: number;\n workersMax: number | null;\n idleTimeout: number;\n scalerType: string;\n scalerValue: number;\n flashBootType: string;\n templateId: string;\n };\n }\n\n const data = await graphqlAuthed<SaveEndpointResponse>(\n `\n mutation saveEndpoint($input: EndpointInput!) {\n saveEndpoint(input: $input) {\n id\n name\n gpuIds\n gpuCount\n workersMin\n workersMax\n idleTimeout\n scalerType\n scalerValue\n flashBootType\n templateId\n }\n }\n `,\n { input }\n );\n\n return jsonReply({\n endpoint: data.saveEndpoint,\n deployed: {\n repo: `${listing.repoOwner}/${listing.repoName}`,\n release: release.tagName,\n hubReleaseId: release.id,\n imageName: release.build.imageName,\n },\n note: `Endpoint created. Submit jobs with run-endpoint/runsync-endpoint using endpointId \"${data.saveEndpoint.id}\".`,\n });\n }\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { capListResult, listPaginationParams } from '../pagination.js';\nimport { READ_ONLY, type ToolRuntime } from './runtime.js';\n\n// ============== PUBLIC ENDPOINT TOOLS ==============\n// Read-only discovery of Runpod Public Endpoints — the managed, pay-per-use\n// model APIs (e.g. Kimi, Hailuo, Veo) that need no deployment. Served by the\n// public GraphQL endpoint (allAiApiPublicConfigs) — no auth required — like\n// the Hub catalog, and version-agnostic (no REST home yet).\n//\n// A public endpoint is invoked like any Serverless endpoint using its\n// `endpointId` (the aiApiId), so results here feed directly into\n// run-endpoint / runsync-endpoint.\n\ninterface PublicEndpointConfig {\n id: string;\n aiApiId: string;\n modelName: string;\n displayName: string;\n description: string | null;\n metadata: string | null;\n isLive: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface PublicEndpointsResponse {\n allAiApiPublicConfigs: PublicEndpointConfig[];\n}\n\n// `metadata` is a JSON string carrying pricing and classification (cost,\n// owner, source/modality, tag, priceString). Parsed defensively — a malformed\n// value falls back to undefined fields rather than a crash.\ninterface PublicEndpointMetadata {\n cost?: number;\n owner?: string;\n source?: string;\n tag?: string;\n priceString?: string;\n}\n\nfunction parseMetadata(\n metadata: string | null | undefined\n): PublicEndpointMetadata {\n if (!metadata) return {};\n try {\n const parsed = JSON.parse(metadata) as unknown;\n return parsed && typeof parsed === 'object'\n ? (parsed as PublicEndpointMetadata)\n : {};\n } catch {\n return {};\n }\n}\n\nexport function registerPublicEndpointTools(\n server: McpServer,\n rt: ToolRuntime\n): void {\n const { graphql } = rt;\n\n server.tool(\n 'list-public-endpoints',\n 'List Runpod Public Endpoints — managed, pay-per-use model APIs (text, image, video, audio) that require no deployment. Public catalog, no auth required. Each result includes the endpointId to call with run-endpoint/runsync-endpoint (or via https://api.runpod.ai/v2/{endpointId}), the model name, modality, owner, and pricing. Only live endpoints are returned by default; set includeOffline:true to also list ones that are not currently live.',\n {\n ...listPaginationParams,\n searchTerm: z\n .string()\n .optional()\n .describe(\n \"Case-insensitive search across display name, model name, endpoint id, description, owner, and tag (e.g. 'kimi', 'video', 'flux')\"\n ),\n modality: z\n .string()\n .optional()\n .describe(\n \"Filter by modality/category (the metadata source field, e.g. 'language', 'image', 'video', 'audio')\"\n ),\n owner: z\n .string()\n .optional()\n .describe(\n \"Filter by model owner (e.g. 'moonshot', 'minimax', 'google')\"\n ),\n includeOffline: z\n .boolean()\n .optional()\n .describe(\n 'Include endpoints that are not currently live (isLive:false). Default false — only live endpoints are listed.'\n ),\n },\n { title: 'List public endpoints', ...READ_ONLY },\n async (params) => {\n const data = await graphql<PublicEndpointsResponse>(`\n query {\n allAiApiPublicConfigs {\n id\n aiApiId\n modelName\n displayName\n description\n metadata\n isLive\n createdAt\n updatedAt\n }\n }\n `);\n\n let configs = data.allAiApiPublicConfigs.map((c) => ({\n config: c,\n meta: parseMetadata(c.metadata),\n }));\n\n if (!params.includeOffline) {\n configs = configs.filter(({ config }) => config.isLive);\n }\n if (params.modality) {\n const term = params.modality.toLowerCase();\n configs = configs.filter(\n ({ meta }) => (meta.source ?? '').toLowerCase() === term\n );\n }\n if (params.owner) {\n const term = params.owner.toLowerCase();\n configs = configs.filter(\n ({ meta }) => (meta.owner ?? '').toLowerCase() === term\n );\n }\n if (params.searchTerm) {\n const term = params.searchTerm.toLowerCase();\n configs = configs.filter(({ config, meta }) =>\n [\n config.displayName,\n config.modelName,\n config.aiApiId,\n config.description,\n meta.owner,\n meta.tag,\n meta.source,\n ].some((field) => (field ?? '').toLowerCase().includes(term))\n );\n }\n\n // Stable, scannable order for the catalog.\n configs = [...configs].sort((a, b) =>\n a.config.displayName.localeCompare(b.config.displayName)\n );\n\n const result = configs.map(({ config, meta }) => ({\n endpointId: config.aiApiId,\n displayName: config.displayName,\n modelName: config.modelName,\n description: config.description,\n modality: meta.source,\n tag: meta.tag,\n owner: meta.owner,\n pricing: meta.priceString,\n isLive: config.isLive,\n baseUrl: `https://api.runpod.ai/v2/${config.aiApiId}`,\n createdAt: config.createdAt,\n updatedAt: config.updatedAt,\n }));\n\n return capListResult(result, {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { WRITE, type ToolRuntime } from './runtime.js';\n\n// ============== ENDPOINT GPU PINNING ==============\n// Sets which GPUs a Serverless endpoint's workers run on, including pinning\n// specific GPU SKUs — a capability the REST API cannot express (its\n// gpuPoolIds field has no SKU exclusion concept). The GraphQL `gpuIds` string\n// is \"POOL[,POOL...][,-<GPU type id>...]\": pool names allow every SKU in the\n// pool, and '-'-prefixed GPU type ids (from list-gpu-types) exclude SKUs, so\n// a pool minus all-but-one SKU pins that SKU exactly.\n//\n// saveEndpoint is NOT a sparse update. Measured live with an\n// id+name+gpuIds-only update: workersMax 7→3, idleTimeout 42→10, scalerValue\n// 9→4 all reset to server defaults. Everything else held — including templateId,\n// the CUDA fields, compliance and modelReferences. So the reset set is narrower\n// than \"every omitted field\", but it is undocumented server behavior and could\n// widen, so this tool reads the endpoint and echoes every field back with only\n// gpuIds (and gpuCount) changed.\n//\n// Read shapes are not write shapes: networkVolumeIds reads as `NetworkVolumeIds`\n// (networkVolumeId + dataCenterId) but writes as `NetworkVolumeIdsInput`\n// (networkVolumeId ONLY), so echoing it verbatim gives `Field \"dataCenterId\" is\n// not defined by type \"NetworkVolumeIdsInput\"` and breaks every volume-bearing\n// endpoint.\n\ninterface EndpointSnapshot {\n id: string;\n name: string;\n gpuIds: string;\n gpuCount: number;\n workersMin: number;\n workersMax: number;\n idleTimeout: number;\n scalerType: string;\n scalerValue: number;\n executionTimeoutMs: number;\n flashBootType: string;\n type: string;\n locations: string | null;\n templateId: string | null;\n // A comma-separated String on read AND write, not a list.\n allowedCudaVersions: string | null;\n minCudaVersion: string | null;\n // A [Compliance] ENUM on input, not [String] — the server rejects 'gdpr' and\n // suggests 'GDPR'. Read values are already enum names: pass back verbatim.\n compliance: string[] | null;\n modelReferences: string[] | null;\n networkVolumeIds: Array<{\n networkVolumeId: string;\n dataCenterId: string | null;\n }> | null;\n}\n\ninterface MyEndpointsResponse {\n myself: { endpoints: EndpointSnapshot[] };\n}\n\nexport function registerEndpointGpuTools(\n server: McpServer,\n rt: ToolRuntime\n): void {\n const { graphqlAuthed, jsonReply } = rt;\n\n server.tool(\n 'set-endpoint-gpus',\n \"Set which GPUs a Serverless endpoint's workers run on — including pinning specific GPU SKUs, which create-endpoint/update-endpoint cannot express. Provide either a raw gpuIds string, or pools plus optional excludeGpuTypeIds (GPU type ids from list-gpu-types) and the exclusion string is built for you: a pool allows every SKU in it, and excluding all but one SKU pins that SKU exactly (e.g. pools:['AMPERE_16'], excludeGpuTypeIds:['NVIDIA RTX 2000 Ada Generation','NVIDIA RTX 4000 Ada Generation','NVIDIA RTX A4500'] pins RTX A4000). All other endpoint settings (workers, scaling, timeouts, template) are read first and preserved. Works on any Serverless endpoint via the authenticated GraphQL API.\",\n {\n endpointId: z\n .string()\n .describe('ID of the Serverless endpoint to update'),\n gpuIds: z\n .string()\n .optional()\n .describe(\n \"Raw gpuIds string, e.g. 'ADA_24' or 'AMPERE_16,-NVIDIA RTX A4500'. Takes precedence over pools/excludeGpuTypeIds.\"\n ),\n pools: z\n .array(z.string())\n .optional()\n .describe(\n \"GPU pool names workers may use (e.g. ['ADA_80_PRO','AMPERE_80']). The pool field from list-gpu-types.\"\n ),\n excludeGpuTypeIds: z\n .array(z.string())\n .optional()\n .describe(\n \"GPU type ids to exclude from the allowed pools (e.g. ['NVIDIA H100 NVL']). Use with pools to pin specific SKUs.\"\n ),\n gpuCount: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('GPUs per worker. Omit to keep the current value.'),\n minCudaVersion: z\n .string()\n .optional()\n .describe(\n \"Minimum host CUDA version workers may run on (e.g. '12.4'). Omit to keep the current value.\"\n ),\n allowedCudaVersions: z\n .string()\n .optional()\n .describe(\n \"Comma-separated allowed host CUDA versions (e.g. '12.8,12.7,12.6'). Omit to keep the current value. CUDA compatibility is part of GPU selection — a narrow list can leave an endpoint unable to schedule workers.\"\n ),\n },\n { title: 'Set endpoint GPUs', ...WRITE, idempotentHint: true },\n async (params) => {\n const gpuIds =\n params.gpuIds ??\n (params.pools && params.pools.length > 0\n ? [\n ...params.pools,\n ...(params.excludeGpuTypeIds ?? []).map((id) => `-${id}`),\n ].join(',')\n : undefined);\n if (!gpuIds) {\n return jsonReply({\n error:\n 'Provide gpuIds (raw string) or pools (with optional excludeGpuTypeIds). See list-gpu-types for pool names and GPU type ids.',\n });\n }\n\n // Read the endpoint's current settings — saveEndpoint resets omitted\n // endpoint-level fields to defaults, so everything must be echoed back.\n const data = await graphqlAuthed<MyEndpointsResponse>(`\n query {\n myself {\n endpoints {\n id\n name\n gpuIds\n gpuCount\n workersMin\n workersMax\n idleTimeout\n scalerType\n scalerValue\n executionTimeoutMs\n flashBootType\n type\n locations\n templateId\n allowedCudaVersions\n minCudaVersion\n compliance\n modelReferences\n networkVolumeIds {\n networkVolumeId\n dataCenterId\n }\n }\n }\n }\n `);\n const current = data.myself.endpoints.find(\n (e) => e.id === params.endpointId\n );\n if (!current) {\n return jsonReply({\n error: `No Serverless endpoint found with id \"${params.endpointId}\". Use list-endpoints to see your endpoints.`,\n });\n }\n\n const input: Record<string, unknown> = {\n id: current.id,\n name: current.name,\n gpuIds,\n gpuCount: params.gpuCount ?? current.gpuCount,\n workersMin: current.workersMin,\n workersMax: current.workersMax,\n idleTimeout: current.idleTimeout,\n scalerType: current.scalerType,\n scalerValue: current.scalerValue,\n executionTimeoutMs: current.executionTimeoutMs,\n flashBootType: current.flashBootType,\n type: current.type,\n locations: current.locations,\n networkVolumeIds:\n current.networkVolumeIds && current.networkVolumeIds.length > 0\n ? // Drop dataCenterId: NetworkVolumeIdsInput takes networkVolumeId\n // ONLY, and the read shape is rejected outright.\n current.networkVolumeIds.map((v) => ({\n networkVolumeId: v.networkVolumeId,\n }))\n : null,\n };\n\n // Echoed only when set. Omitting a field that currently reads null resets\n // it to a default that is already null, while an explicit null risks a\n // server-side type rejection for no gain.\n for (const [key, value] of Object.entries({\n templateId: current.templateId,\n allowedCudaVersions:\n params.allowedCudaVersions ?? current.allowedCudaVersions,\n minCudaVersion: params.minCudaVersion ?? current.minCudaVersion,\n compliance: current.compliance,\n modelReferences: current.modelReferences,\n })) {\n if (value !== null && value !== undefined) input[key] = value;\n }\n\n interface SaveEndpointResponse {\n saveEndpoint: {\n id: string;\n name: string;\n gpuIds: string;\n gpuCount: number;\n workersMin: number;\n workersMax: number;\n };\n }\n\n const result = await graphqlAuthed<SaveEndpointResponse>(\n `\n mutation saveEndpoint($input: EndpointInput!) {\n saveEndpoint(input: $input) {\n id\n name\n gpuIds\n gpuCount\n workersMin\n workersMax\n }\n }\n `,\n { input }\n );\n\n return jsonReply({\n endpoint: result.saveEndpoint,\n previousGpuIds: current.gpuIds,\n });\n }\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { capListResult, listPaginationParams } from '../pagination.js';\nimport { HttpError } from '../_shared/http.js';\nimport { restV1Base, restV2Base } from '../_shared/backend.js';\nimport {\n applySshPublicKey,\n normalizeSshPublicKey,\n podBodyFromTemplate,\n cudaConstraintError,\n} from '../_shared/mappers.js';\nimport { READ_ONLY, WRITE, DESTRUCTIVE, type ToolRuntime } from './runtime.js';\nimport { logStreamParams, streamLogsReply } from './logs.js';\n\n// ============== POD MANAGEMENT TOOLS ==============\n\nexport function registerPodTools(server: McpServer, rt: ToolRuntime): void {\n const { jsonReply, callRestUrl, backendFor, podAction, env } = rt;\n\n // List Pods\n server.tool(\n 'list-pods',\n 'List your pods (running and stopped) with optional filters — compute type, GPU type, data center, name — plus machine/network-volume expansion. Paginated via limit/cursor.',\n {\n ...listPaginationParams,\n computeType: z\n .enum(['GPU', 'CPU'])\n .optional()\n .describe('Filter to only GPU or only CPU Pods'),\n gpuTypeId: z\n .array(z.string())\n .optional()\n .describe('Filter to Pods with any of the listed GPU types'),\n dataCenterId: z\n .array(z.string())\n .optional()\n .describe('Filter to Pods in any of the provided data centers'),\n name: z\n .string()\n .optional()\n .describe('Filter to Pods with the provided name'),\n includeMachine: z\n .boolean()\n .optional()\n .describe('Include information about the machine'),\n includeNetworkVolume: z\n .boolean()\n .optional()\n .describe('Include information about attached network volumes'),\n },\n { title: 'List pods', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('pods');\n\n // v1 supports query-param filters; v2 has none (the spec declares no query\n // params and ignores them), so we only build the query string under v1.\n let queryString = '';\n if (backend.version === 'v1') {\n const queryParams = new URLSearchParams();\n if (params.computeType)\n queryParams.append('computeType', params.computeType);\n if (params.gpuTypeId)\n params.gpuTypeId.forEach((type) =>\n queryParams.append('gpuTypeId', type)\n );\n if (params.dataCenterId)\n params.dataCenterId.forEach((dc) =>\n queryParams.append('dataCenterId', dc)\n );\n if (params.name) queryParams.append('name', params.name);\n if (params.includeMachine)\n queryParams.append(\n 'includeMachine',\n params.includeMachine.toString()\n );\n if (params.includeNetworkVolume)\n queryParams.append(\n 'includeNetworkVolume',\n params.includeNetworkVolume.toString()\n );\n queryString = queryParams.toString()\n ? `?${queryParams.toString()}`\n : '';\n }\n\n const result = await callRestUrl(\n `${backend.base}${backend.list}${queryString}`\n );\n\n return capListResult(backend.unwrap(result), {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n );\n\n // Get Pod Details\n server.tool(\n 'get-pod',\n 'Get full details for one pod by id, optionally expanding machine and attached network-volume info.',\n {\n podId: z.string().describe('ID of the pod to retrieve'),\n includeMachine: z\n .boolean()\n .optional()\n .describe('Include information about the machine'),\n includeNetworkVolume: z\n .boolean()\n .optional()\n .describe('Include information about attached network volumes'),\n },\n { title: 'Get pod', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('pods');\n\n // v1-only expansion query params (v2 has no query params on get).\n let queryString = '';\n if (backend.version === 'v1') {\n const queryParams = new URLSearchParams();\n if (params.includeMachine)\n queryParams.append(\n 'includeMachine',\n params.includeMachine.toString()\n );\n if (params.includeNetworkVolume)\n queryParams.append(\n 'includeNetworkVolume',\n params.includeNetworkVolume.toString()\n );\n queryString = queryParams.toString()\n ? `?${queryParams.toString()}`\n : '';\n }\n\n const result = await callRestUrl(\n `${backend.base}${backend.get!(params.podId)}${queryString}`\n );\n return jsonReply(result);\n }\n );\n\n // Create Pod\n server.tool(\n 'create-pod',\n 'Create a new GPU/CPU pod on Runpod. Pass gpuTypeIds for a GPU pod, or computeType:\"CPU\" for a CPU pod (CPU pods are served by the v1 API for now). Pass either imageName or templateId (templateId deploys from an existing template — its name, image, start command, ports, env, disk, volume, and registry credential are used as defaults, and any field you also pass explicitly replaces the template value for that field, e.g. passing env replaces the whole env set rather than merging; pass containerRegistryAuthId to override or supply a private-image credential, or an empty string to deploy with none). For full SSH access (including SCP/SFTP/rsync), pass sshPublicKey — it sets the PUBLIC_KEY env var and exposes 22/tcp, which official Runpod images turn into a running sshd with the key authorized. If the user specifies neither an image nor a template, recommend the \"Runpod Pytorch 2.8.0\" image (runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404) as the default — it has the most up-to-date CUDA and PyTorch versions.',\n {\n name: z\n .string()\n .optional()\n .describe(\n 'Name for the pod. Required on v2 unless deploying from a templateId (the template supplies its name as the default).'\n ),\n imageName: z\n .string()\n .optional()\n .describe('Docker image to use. Optional when templateId is provided.'),\n templateId: z\n .string()\n .optional()\n .describe(\n 'Deploy from an existing template (see list-templates / get-template). The template supplies name, image, start command, ports, env, disk, volume, and registry credential as defaults; any field you also pass replaces the template value for that field (whole-field, not a merge). Override the registry credential with containerRegistryAuthId. Requires the v2 REST API. Still pass gpuTypeIds or computeType to choose the compute — a template does not pin the pod compute.'\n ),\n computeType: z\n .enum(['GPU', 'CPU'])\n .optional()\n .describe(\n \"Pod type. On v2, required when not passing gpuTypeIds. 'CPU' is served by the v1 API for now (v2 has no CPU pods yet).\"\n ),\n cloudType: z\n .enum(['SECURE', 'COMMUNITY'])\n .optional()\n .describe('SECURE or COMMUNITY cloud'),\n gpuTypeIds: z\n .array(z.string())\n .optional()\n .describe('List of acceptable GPU types'),\n // v2 GpuConfig.count is `integer, minimum: 1`. Without these bounds a 0,\n // a negative, or a fractional count reached the wire as-is and came back\n // as a raw 422; reject it here with a message that names the field.\n gpuCount: z\n .number()\n .int()\n .min(1)\n .optional()\n .describe('Number of GPUs (minimum 1)'),\n containerDiskInGb: z\n .number()\n .optional()\n .describe('Container disk size in GB'),\n volumeInGb: z.number().optional().describe('Volume size in GB'),\n volumeMountPath: z\n .string()\n .optional()\n .describe('Path to mount the volume'),\n ports: z\n .array(z.string())\n .optional()\n .describe(\"Ports to expose (e.g., '8888/http', '22/tcp')\"),\n env: z.record(z.string()).optional().describe('Environment variables'),\n sshPublicKey: z\n .string()\n .optional()\n .describe(\n 'SSH PUBLIC key(s) to authorize on the pod — the CONTENTS of your ~/.ssh/<key>.pub file, i.e. a literal \"ssh-ed25519 AAAAC3Nza… you@host\" line, NOT a path to the file, a fingerprint, or the private key; newline-separate multiple keys. Merges the key into the PUBLIC_KEY env var and ensures 22/tcp is exposed, enabling full SSH (SCP/SFTP/rsync) on any image that honors the PUBLIC_KEY convention — all runpod/* official images do. Extends template ports/env rather than replacing them.'\n ),\n containerRegistryAuthId: z\n .string()\n .optional()\n .describe(\n 'Container registry credential ID for pulling a private image (see list-container-registry-auths). When deploying from a templateId, this overrides the credential the template carries; pass an empty string to deploy with NO credential (clears a template’s inherited registry).'\n ),\n dataCenterIds: z\n .array(z.string())\n .optional()\n .describe('List of data centers'),\n allowedCudaVersions: z\n .array(z.string())\n .optional()\n .describe(\n 'Acceptable host CUDA versions as major.minor, e.g. [\"12.8\",\"12.6\"] (v2, GPU pods only). Exact match — discover valid values per GPU type with get-capacity. Mutually exclusive with minCudaVersion.'\n ),\n minCudaVersion: z\n .string()\n .optional()\n .describe(\n 'Lowest acceptable host CUDA version as major.minor, e.g. \"12.4\" (v2, GPU pods only). A bare major like \"12\" is rejected. Open-ended floor; mutually exclusive with a non-empty allowedCudaVersions.'\n ),\n },\n { title: 'Create pod', ...WRITE },\n async (params) => {\n const backend = backendFor('pods');\n\n // sshPublicKey is an MCP-side convenience, not an API field: strip it from\n // the params that become the request body (v1 mapCreate is identity, so it\n // would leak through) and fold it into the FINAL body via applySshPublicKey.\n const { sshPublicKey, ...podParams } = params;\n\n // Validate and normalize BEFORE any pod exists. Refuses a PRIVATE key\n // before it can reach a pod's env (and every process listing inside the\n // container), and refuses a value that is not a public key at all — a path\n // or a fingerprint passed instead of the .pub file's contents used to\n // succeed, exposing 22/tcp with junk in PUBLIC_KEY and reporting SSH as\n // configured. See normalizeSshPublicKey.\n let sshKey: string | undefined;\n if (sshPublicKey !== undefined) {\n const validated = normalizeSshPublicKey(sshPublicKey);\n if (!validated.ok)\n return jsonReply({ error: validated.error, status: 400 });\n sshKey = validated.key;\n }\n\n // Reply note attached whenever sshPublicKey was applied, so the caller\n // knows what was set up and what full SSH still depends on.\n const sshNote =\n 'sshPublicKey applied: PUBLIC_KEY env set and 22/tcp exposed. Full SSH (incl. SCP/SFTP/rsync) also requires an image that installs PUBLIC_KEY into authorized_keys and starts sshd (all runpod/* official images do) and a machine with a public IP — read the pod’s portMappings for port 22 once it is running.';\n\n // Template deploy is v2-only: v2 CreatePodRequest has no templateId, so we\n // fetch the template and spread its container config into the body below.\n // Only v2 templates are ContainerConfig-shaped.\n const hasCudaParams =\n params.allowedCudaVersions !== undefined ||\n params.minCudaVersion !== undefined;\n if (hasCudaParams && backend.version !== 'v2') {\n return jsonReply({\n error:\n 'allowedCudaVersions/minCudaVersion are only supported on the v2 REST API. Set RUNPOD_REST_VERSION=v2.',\n status: 501,\n });\n }\n if (params.templateId && backend.version !== 'v2') {\n return jsonReply({\n error:\n 'Deploying a pod from a templateId is only supported on the v2 REST API. Set RUNPOD_REST_VERSION=v2, or pass imageName instead.',\n status: 501,\n });\n }\n // Image comes from either imageName or the template; require one of them.\n if (!params.imageName && !params.templateId) {\n return jsonReply({\n error:\n 'Provide imageName, or templateId to deploy from an existing template.',\n status: 400,\n });\n }\n\n // Pod type is decided by STATED intent, never inferred from the absence of\n // GPU fields (absence used to silently downgrade a misspecified GPU request\n // into a CPU pod). On v2 we require an explicit, non-contradictory choice;\n // v1 keeps its passthrough behavior (it validates the body itself).\n const hasGpuTypes = (params.gpuTypeIds?.length ?? 0) > 0;\n const hasGpuCount = (params.gpuCount ?? 0) > 0;\n const wantsCpu = params.computeType === 'CPU';\n const wantsGpu =\n hasGpuTypes || hasGpuCount || params.computeType === 'GPU';\n\n if (backend.version === 'v2') {\n if (wantsGpu && wantsCpu) {\n return jsonReply({\n error:\n \"Specify a GPU pod (gpuTypeIds or computeType:'GPU') OR a CPU pod (computeType:'CPU'), not both.\",\n status: 400,\n });\n }\n if (!wantsGpu && !wantsCpu) {\n return jsonReply({\n error:\n \"No pod type specified. Pass gpuTypeIds (see list-gpu-types) for a GPU pod, or computeType:'CPU' for a CPU pod.\",\n status: 400,\n });\n }\n if (wantsGpu && !hasGpuTypes) {\n return jsonReply({\n error:\n 'A GPU pod needs gpuTypeIds (see list-gpu-types); gpuCount or computeType alone is under-specified.',\n status: 400,\n });\n }\n // CUDA constraints live under gpu since 2.9.0 and are unrepresentable\n // on a CPU pod — validate here so the caller gets the reason, not a 422.\n if (hasCudaParams && !wantsGpu) {\n return jsonReply({\n error:\n 'allowedCudaVersions/minCudaVersion apply to GPU pods only — pass gpuTypeIds (see list-gpu-types).',\n status: 400,\n });\n }\n if (hasCudaParams) {\n const cudaError = cudaConstraintError(params);\n if (cudaError) return jsonReply({ error: cudaError, status: 400 });\n }\n // v2 CreatePodRequest requires `name`. Guard the direct-image path so\n // the caller gets a clean 400 instead of the API's raw 422; a\n // templateId deploy inherits the template's name below. CPU pods fall\n // through to v1, which has no such requirement.\n if (!wantsCpu && !params.name && !params.templateId) {\n return jsonReply({\n error:\n 'Provide a name for the pod (deploying from a templateId inherits the template name).',\n status: 400,\n });\n }\n // v2 has no CPU pods yet (AE-2991): serve a CPU pod from v1 (which supports\n // them) instead of failing, and tell the caller which API answered.\n if (wantsCpu) {\n // CPU pods route to v1, but the template fetch/merge below is v2-only;\n // rather than silently ignore the template, tell the caller.\n if (params.templateId) {\n return jsonReply({\n error:\n 'Template-based deploy is currently supported only for GPU pods on the v2 API. Create the CPU pod with an explicit imageName instead.',\n status: 400,\n });\n }\n // v1 is a passthrough (mapCreate is identity there), so the stripped\n // tool params are the correct v1 body — `computeType` is a valid v1\n // field. Wrap the call so a v1 error surfaces as a clean message, not\n // a raw throw (matching the GPU/v2 path's error handling below).\n try {\n const cpuBody = sshKey\n ? applySshPublicKey(podParams as Record<string, unknown>, sshKey)\n : (podParams as Record<string, unknown>);\n const result = (await callRestUrl(\n `${restV1Base(env)}/pods`,\n 'POST',\n cpuBody\n )) as Record<string, unknown>;\n return jsonReply({\n ...result,\n ...(sshKey ? { _ssh: sshNote } : {}),\n _servedBy: 'v1',\n _note:\n 'CPU pod: created on the v1 API, because the v2 REST API does not support CPU pods yet.',\n });\n } catch (error) {\n if (error instanceof HttpError) {\n return jsonReply({\n error: error.message,\n status: error.status,\n _servedBy: 'v1',\n _note: 'CPU pod create was routed to the v1 API and failed.',\n });\n }\n throw error;\n }\n }\n }\n\n let body = backend.mapCreate(podParams) as Record<string, unknown>;\n\n // Template-based deploy (guaranteed v2 here). Fetch the template and use its\n // container config as the base; the mapped pod params spread ON TOP so an\n // explicitly-passed field (image, ports, disk, …) still wins.\n if (params.templateId) {\n // Fetch the template from v2 EXPLICITLY (not via backendFor('templates'),\n // whose independent version resolution could return a v1 shape under a\n // split override like RUNPOD_REST_VERSION_TEMPLATES=v1 → merged body with\n // `imageName` not `image` → 422). Template deploy is v2-only, so pin v2.\n let template: Record<string, unknown>;\n try {\n template = (await callRestUrl(\n `${restV2Base(env)}/templates/${params.templateId}`\n )) as Record<string, unknown>;\n } catch (error) {\n if (error instanceof HttpError) {\n return jsonReply({\n error: `Failed to load template ${params.templateId}: ${error.message}`,\n status: error.status,\n });\n }\n throw error;\n }\n // Template config as defaults; caller-passed fields spread on top and win\n // (including registry, overridable via containerRegistryAuthId).\n body = { ...podBodyFromTemplate(template), ...body };\n }\n\n // After the template merge, so a template's ports/env are extended (a\n // pre-merge injection would clobber the template's ports whole-field).\n if (sshKey) body = applySshPublicKey(body, sshKey);\n\n try {\n const result = (await callRestUrl(\n `${backend.base}${backend.list}`,\n 'POST',\n body\n )) as Record<string, unknown>;\n const extras: Record<string, unknown> = {};\n if (sshKey) extras._ssh = sshNote;\n // v2 accepts a single GPU type (`gpu.id`); v1's gpuTypeIds is \"any of\n // these\". If the caller offered several, only the first was sent — say\n // so in the reply so the narrowing isn't silent.\n if (backend.version === 'v2' && (params.gpuTypeIds?.length ?? 0) > 1) {\n extras._warning = `v2 accepts one GPU type per pod; used \"${params.gpuTypeIds![0]}\" and ignored ${\n params.gpuTypeIds!.length - 1\n } other(s): ${params.gpuTypeIds!.slice(1).join(', ')}.`;\n }\n return jsonReply(\n Object.keys(extras).length ? { ...result, ...extras } : result\n );\n } catch (error) {\n if (error instanceof HttpError) {\n // Surface any API rejection (4xx/429/5xx/501) as a clean {error, status}\n // carrying the API's own message, instead of throwing a raw stack.\n //\n // Do NOT special-case 501 as \"CPU not supported\" here: CPU pods route to\n // v1 above and never reach this v2 POST, so a 501 here is a GPU request\n // hitting an unimplemented path — that CPU guidance lives on the CPU branch.\n return jsonReply({ error: error.message, status: error.status });\n }\n throw error;\n }\n }\n );\n\n // Update Pod\n server.tool(\n 'update-pod',\n \"Update a pod's mutable fields (name, image, container disk, volume, ports, env). Only the fields you provide change.\",\n {\n podId: z.string().describe('ID of the pod to update'),\n name: z.string().optional().describe('New name for the pod'),\n imageName: z.string().optional().describe('New Docker image'),\n containerDiskInGb: z\n .number()\n .optional()\n .describe('New container disk size in GB'),\n volumeInGb: z.number().optional().describe('New volume size in GB'),\n volumeMountPath: z\n .string()\n .optional()\n .describe('New path to mount the volume'),\n ports: z.array(z.string()).optional().describe('New ports to expose'),\n env: z\n .record(z.string())\n .optional()\n .describe('New environment variables'),\n },\n { title: 'Update pod', ...WRITE, idempotentHint: true },\n async (params) => {\n const { podId, ...updateParams } = params;\n const backend = backendFor('pods');\n const body = backend.mapUpdate(updateParams) as Record<string, unknown>;\n const result = await callRestUrl(\n `${backend.base}${backend.get!(podId)}`,\n 'PATCH',\n body\n );\n return jsonReply(result);\n }\n );\n\n // Start Pod — v1: POST /pods/{id}/start ; v2: POST /pods/{id}/action {action:'start'}\n server.tool(\n 'start-pod',\n 'Start a stopped pod (resumes the same pod and its billing).',\n {\n podId: z.string().describe('ID of the pod to start'),\n },\n { title: 'Start pod', ...WRITE, idempotentHint: true },\n async (params) => {\n const result = await podAction(params.podId, 'start');\n return jsonReply(result);\n }\n );\n\n // Stop Pod — v1: POST /pods/{id}/stop ; v2: POST /pods/{id}/action {action:'stop'}\n server.tool(\n 'stop-pod',\n 'Stop a running pod. The pod and its disk persist; GPU billing stops.',\n {\n podId: z.string().describe('ID of the pod to stop'),\n },\n { title: 'Stop pod', ...WRITE, idempotentHint: true },\n async (params) => {\n const result = await podAction(params.podId, 'stop');\n return jsonReply(result);\n }\n );\n\n // Restart Pod — v2-only state transition (PodAction enum). The v1 REST API has\n // no restart endpoint, so under v1 we return a clean message instead of firing\n // a request that would 404 (mirrors the create-pod 501 handling).\n server.tool(\n 'restart-pod',\n 'Restart a running pod (single PodAction call). v2-only — on the v1 API returns a 501 notice (stop then start the pod instead).',\n {\n podId: z.string().describe('ID of the pod to restart'),\n },\n { title: 'Restart pod', ...WRITE },\n async (params) => {\n const backend = backendFor('pods');\n if (backend.version === 'v1') {\n return jsonReply({\n error:\n 'restart-pod is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2 (or stop then start the pod on v1).',\n status: 501,\n });\n }\n const result = await podAction(params.podId, 'restart');\n return jsonReply(result);\n }\n );\n\n // Stream Pod Logs (v2-only — GET /v2/pods/{id}/logs, Server-Sent Events).\n // Stream Pod Logs (v2-only — GET /v2/pods/{id}/logs). See streamLogsReply.\n server.tool(\n 'stream-pod-logs',\n \"Fetch a bounded snapshot of a pod's live logs (container and/or system) via Server-Sent Events. v2-only — returns a 501 notice on the v1 API. Reads for up to maxWaitMs (default 5s) and returns the collected log lines; use `tail` to backfill recent lines first. Large output is truncated (see the `truncated` flag).\",\n {\n podId: z.string().describe('ID of the pod whose logs to stream'),\n ...logStreamParams,\n },\n { title: 'Stream pod logs', ...READ_ONLY },\n (params) =>\n streamLogsReply(\n rt,\n {\n name: 'stream-pod-logs',\n resource: 'pods',\n logsUrl: (backend) =>\n `${backend.base}${backend.get!(encodeURIComponent(params.podId))}/logs`,\n },\n params\n )\n );\n\n // Delete Pod\n server.tool(\n 'delete-pod',\n 'Permanently delete a pod and its data. This cannot be undone.',\n {\n podId: z.string().describe('ID of the pod to delete'),\n },\n { title: 'Delete pod', ...DESTRUCTIVE },\n async (params) => {\n const backend = backendFor('pods');\n const result = await callRestUrl(\n `${backend.base}${backend.get!(params.podId)}`,\n 'DELETE'\n );\n return jsonReply(result);\n }\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { capListResult, listPaginationParams } from '../pagination.js';\nimport { READ_ONLY, WRITE, DESTRUCTIVE, type ToolRuntime } from './runtime.js';\nimport { logStreamParams, streamLogsReply } from './logs.js';\nimport { cudaConstraintError } from '../_shared/mappers.js';\n\n// ============== ENDPOINT MANAGEMENT TOOLS ==============\n// Serverless endpoint CRUD, version-aware via the backend adapter.\n// v2 (default): endpoints live at /v2/serverless with an INLINE config —\n// image + gpu.pools + workers + scaling (NO templateId).\n// v1: the legacy /endpoints model — templateId-based.\n// list/get/delete route through the adapter on both versions; create/update\n// branch on backend.version because the request model differs fundamentally.\n\n// Autoscaling bounds, mirroring the v2 spec so a bad value is a clean client-side\n// error instead of a server 422. `scalerValue` feeds two different union variants\n// with different bounds — QUEUE_DELAY takes a float >= 0.5, REQUEST_COUNT an\n// integer >= 1 — so the schema can only enforce the looser floor. The\n// REQUEST_COUNT half is checked per-call by scalerValueError() below, which needs\n// the resolved scaler type.\nconst MIN_QUEUE_DELAY = 0.5;\nconst MIN_REQUEST_COUNT = 1;\n\n/** Narrows the scaler type the API reports back to the one value we branch on. */\nfunction isRequestCountScaler(type: string | undefined): boolean {\n return type === 'REQUEST_COUNT';\n}\nconst MIN_IDLE_TIMEOUT = 1;\nconst MAX_IDLE_TIMEOUT = 3600;\n\n// Returns a message when scalerValue is illegal for the scaler it will be sent as,\n// or undefined when it is fine (including when either input is absent — the\n// mapper's defaults are always in range).\nfunction scalerValueError(\n scalerType: 'QUEUE_DELAY' | 'REQUEST_COUNT' | undefined,\n scalerValue: number | undefined\n): string | undefined {\n if (scalerType !== 'REQUEST_COUNT' || scalerValue === undefined) return;\n if (!Number.isInteger(scalerValue) || scalerValue < MIN_REQUEST_COUNT) {\n return `REQUEST_COUNT scales on whole in-flight requests per worker, so scalerValue must be an integer >= ${MIN_REQUEST_COUNT} (got ${scalerValue}). For a fractional target use scalerType QUEUE_DELAY, which takes seconds >= ${MIN_QUEUE_DELAY}.`;\n }\n return;\n}\n\nexport function registerEndpointTools(\n server: McpServer,\n rt: ToolRuntime\n): void {\n const { jsonReply, callRestUrl, backendFor } = rt;\n\n // List Endpoints — v1 supports includeTemplate/includeWorkers query params;\n // v2 (GET /v2/serverless) declares none, so we only build the query under v1.\n server.tool(\n 'list-endpoints',\n 'List your Serverless endpoints, optionally expanding template and worker details (v1 only). Paginated via limit/cursor. On v2 each endpoint carries its request routing in `type` and the URLs to call it in `requestUrls`.',\n {\n ...listPaginationParams,\n includeTemplate: z\n .boolean()\n .optional()\n .describe('Include template information (v1 only)'),\n includeWorkers: z\n .boolean()\n .optional()\n .describe('Include information about workers (v1 only)'),\n },\n { title: 'List endpoints', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('endpoints');\n\n let queryString = '';\n if (backend.version === 'v1') {\n const queryParams = new URLSearchParams();\n if (params.includeTemplate)\n queryParams.append(\n 'includeTemplate',\n params.includeTemplate.toString()\n );\n if (params.includeWorkers)\n queryParams.append(\n 'includeWorkers',\n params.includeWorkers.toString()\n );\n queryString = queryParams.toString()\n ? `?${queryParams.toString()}`\n : '';\n }\n\n const result = await callRestUrl(\n `${backend.base}${backend.list}${queryString}`\n );\n\n return capListResult(backend.unwrap(result), {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n );\n\n // Get Endpoint Details\n server.tool(\n 'get-endpoint',\n 'Get one Serverless endpoint by id, optionally expanding template and worker details (v1 only). On v2 the reply carries `type` (queue-based vs load-balancing routing) and `requestUrls` — the run/runsync/status/... URLs for a queue endpoint, or the base + health URLs for a load-balancing one. Read those rather than constructing endpoint URLs by hand.',\n {\n endpointId: z.string().describe('ID of the endpoint to retrieve'),\n includeTemplate: z\n .boolean()\n .optional()\n .describe('Include template information (v1 only)'),\n includeWorkers: z\n .boolean()\n .optional()\n .describe('Include information about workers (v1 only)'),\n },\n { title: 'Get endpoint', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('endpoints');\n\n let queryString = '';\n if (backend.version === 'v1') {\n const queryParams = new URLSearchParams();\n if (params.includeTemplate)\n queryParams.append(\n 'includeTemplate',\n params.includeTemplate.toString()\n );\n if (params.includeWorkers)\n queryParams.append(\n 'includeWorkers',\n params.includeWorkers.toString()\n );\n queryString = queryParams.toString()\n ? `?${queryParams.toString()}`\n : '';\n }\n\n const result = await callRestUrl(\n `${backend.base}${backend.get!(params.endpointId)}${queryString}`\n );\n\n return jsonReply(result);\n }\n );\n\n // Create Endpoint — model differs by version:\n // v2: inline config. Requires imageName + gpuPoolIds (gpu.pools minItems 1).\n // v1: templateId-based.\n server.tool(\n 'create-endpoint',\n 'Create a Serverless endpoint. On v2 (default), pass an inline config: imageName + gpuPoolIds (GPU pool names from list-gpu-types — the `pool` field, e.g. AMPERE_80/ADA_24) plus optional workers/scaling/disk/env. On v1, pass a templateId instead. Worker min/max set autoscaling bounds (min 0 = scale to zero). Use endpointType to pick queue-based (default) or load-balancing request routing; the response `requestUrls` carries the URLs to call the endpoint with.',\n {\n name: z.string().optional().describe('Name for the endpoint'),\n // --- v2 inline-config fields ---\n imageName: z\n .string()\n .optional()\n .describe('Docker image (v2). Required on v2 instead of a templateId.'),\n endpointType: z\n .enum(['QUEUE', 'LOAD_BALANCER'])\n .optional()\n .describe(\n 'How requests reach the workers (v2). QUEUE (default) = submit jobs through the managed queue (run/runsync/status/...). LOAD_BALANCER = send HTTP requests straight to worker-defined paths, and forces scalerType REQUEST_COUNT. Fixed at creation — update-endpoint cannot change it.'\n ),\n gpuPoolIds: z\n .array(z.string())\n .optional()\n .describe(\n 'GPU pool names (v2, required). The `pool` field from list-gpu-types, e.g. [\"AMPERE_80\"]. NOT GPU type ids.'\n ),\n gpuCount: z.number().optional().describe('GPUs per worker (v2)'),\n allowedCudaVersions: z\n .array(z.string())\n .optional()\n .describe(\n 'Acceptable host CUDA versions for worker placement as major.minor, e.g. [\"12.8\"] (v2). Exact match — see get-capacity for which versions have stock. Mutually exclusive with minCudaVersion.'\n ),\n minCudaVersion: z\n .string()\n .optional()\n .describe(\n 'Lowest acceptable host CUDA version as major.minor, e.g. \"12.4\" (v2). A bare major like \"12\" is rejected. Mutually exclusive with a non-empty allowedCudaVersions.'\n ),\n args: z.string().optional().describe('Container start command/args (v2)'),\n containerDiskInGb: z\n .number()\n .optional()\n .describe('Container disk size in GB (v2)'),\n ports: z\n .array(z.string())\n .optional()\n .describe(\"Ports to expose (v2), e.g. ['8000/http']\"),\n env: z\n .record(z.string())\n .optional()\n .describe('Environment variables (v2)'),\n containerRegistryAuthId: z\n .string()\n .optional()\n .describe('Container registry auth id for a private image (v2)'),\n networkVolumeIds: z\n .array(z.string())\n .optional()\n .describe('Network volume ids to attach (v2)'),\n executionTimeoutMs: z\n .number()\n .optional()\n .describe('Per-job execution timeout in ms (v2)'),\n flashboot: z\n .enum(['OFF', 'FLASHBOOT', 'PRIORITY_FLASHBOOT'])\n .optional()\n .describe('FlashBoot mode (v2)'),\n // --- v1 template-based fields ---\n templateId: z\n .string()\n .optional()\n .describe('Template ID (v1). Required on v1.'),\n computeType: z\n .enum(['GPU', 'CPU'])\n .optional()\n .describe('GPU or CPU endpoint (v1)'),\n gpuTypeIds: z\n .array(z.string())\n .optional()\n .describe('List of acceptable GPU types (v1)'),\n // --- shared autoscaling fields ---\n workersMin: z.number().optional().describe('Minimum number of workers'),\n workersMax: z.number().optional().describe('Maximum number of workers'),\n scalerType: z\n .enum(['QUEUE_DELAY', 'REQUEST_COUNT'])\n .optional()\n .describe(\n 'Autoscaler signal. QUEUE_DELAY scales on how long requests wait in the queue (queue endpoints only); REQUEST_COUNT scales on in-flight requests per worker. Defaults to QUEUE_DELAY for QUEUE endpoints and REQUEST_COUNT for LOAD_BALANCER ones.'\n ),\n scalerValue: z\n .number()\n .min(MIN_QUEUE_DELAY)\n .optional()\n .describe(\n 'Autoscaler target for the chosen scalerType — seconds of queue delay (min 0.5) or in-flight requests per worker (integer, min 1). Defaults to 4.'\n ),\n idleTimeout: z\n .number()\n .int()\n .min(MIN_IDLE_TIMEOUT)\n .max(MAX_IDLE_TIMEOUT)\n .optional()\n .describe(\n 'Idle timeout in seconds before scaling a worker down (1-3600). Does not apply to QUEUE endpoints scaling on REQUEST_COUNT.'\n ),\n dataCenterIds: z\n .array(z.string())\n .optional()\n .describe('List of preferred data centers'),\n },\n { title: 'Create endpoint', ...WRITE },\n async (params) => {\n const backend = backendFor('endpoints');\n\n const hasCudaParams =\n params.allowedCudaVersions !== undefined ||\n params.minCudaVersion !== undefined;\n if (hasCudaParams && backend.version !== 'v2') {\n return jsonReply({\n error:\n 'allowedCudaVersions/minCudaVersion are only supported on the v2 REST API. Set RUNPOD_REST_VERSION=v2.',\n status: 501,\n });\n }\n\n if (backend.version === 'v2') {\n // Guard the v2-required fields before calling, so the caller gets a\n // clean 400 rather than a raw 422 from the API (mirrors create-pod).\n // v2 CreateEndpointRequest marks `name` required too — guard it so an\n // omitted name is a clean 400, not an opaque 422.\n if (!params.name) {\n return jsonReply({\n error: 'On the v2 REST API, create-endpoint requires a name.',\n status: 400,\n });\n }\n if (!params.imageName) {\n return jsonReply({\n error:\n 'On the v2 REST API, create-endpoint needs an imageName (v2 endpoints are image-based, not template-based). To use a templateId, set RUNPOD_REST_VERSION=v1.',\n status: 400,\n });\n }\n if (!params.gpuPoolIds?.length) {\n return jsonReply({\n error:\n 'create-endpoint needs gpuPoolIds on v2 (GPU pool names from list-gpu-types — the `pool` field, e.g. [\"AMPERE_80\"]).',\n status: 400,\n });\n }\n // A load balancer has no request queue, so there is no queue delay to\n // scale on. The API rejects this too, but catching it here costs a round\n // trip less and names the reason directly.\n if (\n params.endpointType === 'LOAD_BALANCER' &&\n params.scalerType === 'QUEUE_DELAY'\n ) {\n return jsonReply({\n error:\n 'LOAD_BALANCER endpoints have no request queue, so they cannot scale on QUEUE_DELAY. Use scalerType REQUEST_COUNT (the default for this endpoint type), or create a QUEUE endpoint instead.',\n status: 400,\n });\n }\n // REQUEST_COUNT is the default scaler for a load balancer, so the value\n // has to be checked against the scaler that will actually be sent, not\n // just the one the caller named.\n const scalerError = scalerValueError(\n params.scalerType ??\n (params.endpointType === 'LOAD_BALANCER'\n ? 'REQUEST_COUNT'\n : undefined),\n params.scalerValue\n );\n if (scalerError) {\n return jsonReply({ error: scalerError, status: 400 });\n }\n if (hasCudaParams) {\n const cudaError = cudaConstraintError(params);\n if (cudaError) return jsonReply({ error: cudaError, status: 400 });\n }\n const body = backend.mapCreate(params) as Record<string, unknown>;\n const result = await callRestUrl(\n `${backend.base}${backend.list}`,\n 'POST',\n body\n );\n return jsonReply(result);\n }\n\n // v1: legacy /endpoints, templateId-based. Guard the v1-required field.\n if (!params.templateId) {\n return jsonReply({\n error:\n 'On the v1 REST API, create-endpoint needs a templateId. (On v2 — the default — pass imageName + gpuPoolIds instead.)',\n status: 400,\n });\n }\n // Send ONLY v1-relevant fields — the tool schema is a v1+v2 union, so\n // forwarding the raw params would also send v2-only keys (gpuPoolIds,\n // flashboot, networkVolumeIds, …) to the v1 API. Harmless today (v1 ignores\n // unknowns) but imprecise; filter explicitly.\n const v1Body: Record<string, unknown> = {};\n for (const k of [\n 'templateId',\n 'name',\n 'computeType',\n 'gpuTypeIds',\n 'gpuCount',\n 'workersMin',\n 'workersMax',\n 'dataCenterIds',\n ] as const) {\n if (params[k] !== undefined) v1Body[k] = params[k];\n }\n const result = await callRestUrl(\n `${backend.base}${backend.list}`,\n 'POST',\n v1Body\n );\n return jsonReply(result);\n }\n );\n\n // Update Endpoint — only provided fields change. v2 maps to the nested\n // /v2/serverless body; v1 passes the flat fields through.\n server.tool(\n 'update-endpoint',\n \"Update a Serverless endpoint's config. On v2 you can change image/disk/env/ports/registry/workers/scaling/networkVolumes/timeout/flashboot, plus the GPU selection (gpuPoolIds/gpuCount) and its CUDA constraints (allowedCudaVersions/minCudaVersion) — CUDA-only or count-only changes do not require resending gpuPoolIds; on v1, scaling fields (worker min/max, idle timeout, scaler type/value, name). Only provided fields change. An endpoint's request routing (queue vs load balancer) is fixed at creation and cannot be changed here — recreate the endpoint instead. Note: passing gpuPoolIds replaces the GPU selection wholesale, which clears any GPU-type exclusions set elsewhere (console or set-endpoint-gpus) — the reply carries a _warning when that happens.\",\n {\n endpointId: z.string().describe('ID of the endpoint to update'),\n name: z.string().optional().describe('New name for the endpoint'),\n workersMin: z\n .number()\n .optional()\n .describe('New minimum number of workers'),\n workersMax: z\n .number()\n .optional()\n .describe('New maximum number of workers'),\n idleTimeout: z\n .number()\n .int()\n .min(MIN_IDLE_TIMEOUT)\n .max(MAX_IDLE_TIMEOUT)\n .optional()\n .describe(\n 'New idle timeout in seconds (1-3600). Does not apply to queue endpoints scaling on REQUEST_COUNT.'\n ),\n scalerType: z\n .enum(['QUEUE_DELAY', 'REQUEST_COUNT'])\n .optional()\n .describe(\n 'New autoscaler signal. Switchable on queue endpoints; load-balancing endpoints only accept REQUEST_COUNT.'\n ),\n scalerValue: z\n .number()\n .min(MIN_QUEUE_DELAY)\n .optional()\n .describe(\n \"New autoscaler target — seconds of queue delay (min 0.5) or in-flight requests per worker (integer, min 1). Applies to the endpoint's current scalerType unless you also pass a new one.\"\n ),\n // --- v2-only inline-config fields ---\n imageName: z.string().optional().describe('New Docker image (v2)'),\n gpuPoolIds: z\n .array(z.string())\n .optional()\n .describe('New GPU pool names (v2), e.g. [\"AMPERE_80\"]'),\n gpuCount: z.number().optional().describe('New GPUs per worker (v2)'),\n allowedCudaVersions: z\n .array(z.string())\n .optional()\n .describe(\n 'New acceptable host CUDA versions as major.minor (v2). Changing only this does not require resending gpuPoolIds. An explicit [] clears the constraint. Mutually exclusive with minCudaVersion.'\n ),\n minCudaVersion: z\n .string()\n .optional()\n .describe(\n 'New lowest acceptable host CUDA version as major.minor (v2). An empty string clears the floor; a bare major like \"12\" is rejected. Mutually exclusive with a non-empty allowedCudaVersions.'\n ),\n args: z.string().optional().describe('New container args (v2)'),\n containerDiskInGb: z\n .number()\n .optional()\n .describe('New container disk size in GB (v2)'),\n ports: z.array(z.string()).optional().describe('New ports (v2)'),\n env: z\n .record(z.string())\n .optional()\n .describe('New environment variables (v2)'),\n containerRegistryAuthId: z\n .string()\n .optional()\n .describe('New container registry auth id (v2)'),\n networkVolumeIds: z\n .array(z.string())\n .optional()\n .describe('New network volume ids (v2)'),\n executionTimeoutMs: z\n .number()\n .optional()\n .describe('New per-job execution timeout in ms (v2)'),\n flashboot: z\n .enum(['OFF', 'FLASHBOOT', 'PRIORITY_FLASHBOOT'])\n .optional()\n .describe('New FlashBoot mode (v2)'),\n },\n { title: 'Update endpoint', ...WRITE, idempotentHint: true },\n async (params) => {\n const { endpointId, ...updateParams } = params;\n const backend = backendFor('endpoints');\n const url = `${backend.base}${backend.get!(endpointId)}`;\n\n const hasCudaParams =\n updateParams.allowedCudaVersions !== undefined ||\n updateParams.minCudaVersion !== undefined;\n if (backend.version !== 'v2') {\n if (hasCudaParams) {\n return jsonReply({\n error:\n 'allowedCudaVersions/minCudaVersion are only supported on the v2 REST API. Set RUNPOD_REST_VERSION=v2.',\n status: 501,\n });\n }\n const body = backend.mapUpdate(updateParams) as Record<string, unknown>;\n return jsonReply(await callRestUrl(url, 'PATCH', body));\n }\n\n if (hasCudaParams) {\n // allowClear: [] clears the set, \"\" clears the floor (update-only\n // sentinels — the create schema rejects both).\n const cudaError = cudaConstraintError(updateParams, {\n allowClear: true,\n });\n if (cudaError) return jsonReply({ error: cudaError, status: 400 });\n }\n\n // Unlike allowedCudaVersions, [] is not a clear sentinel here: the GPU\n // selection is not clearable (gpu.pools is minItems 1 upstream), and the\n // mapper would drop the field — a silent no-op the caller didn't ask for.\n if (\n updateParams.gpuPoolIds !== undefined &&\n !updateParams.gpuPoolIds.length\n ) {\n return jsonReply({\n error:\n 'gpuPoolIds cannot be empty — omit it to keep the current GPU selection.',\n status: 400,\n });\n }\n\n // `scaling` is a union keyed on the scaler type, so a bare \"change the target\n // to N\" has no expressible form without knowing which scaler is in effect.\n // Rather than reject the call, read the endpoint's current scaler and keep\n // it — which is what such a request has always meant.\n let scalerType = updateParams.scalerType;\n const needScalerRead =\n scalerType === undefined && updateParams.scalerValue !== undefined;\n // Since 2.9.0, sending pools replaces the GPU selection wholesale, which\n // clears any excludedTypes pinned out-of-band (console/set-endpoint-gpus).\n // Read them first so the wipe is loud, not silent.\n const needExclusionRead = (updateParams.gpuPoolIds?.length ?? 0) > 0;\n let clearedExclusions: string[] = [];\n if (needScalerRead || needExclusionRead) {\n const current = (await callRestUrl(url)) as\n | { scaling?: { type?: string }; gpu?: { excludedTypes?: string[] } }\n | undefined;\n if (needScalerRead) {\n // Anything the API reports other than REQUEST_COUNT is treated as\n // QUEUE_DELAY, the server-side default for queue endpoints, so an\n // unknown future scaler type fails closed onto the stricter bound.\n scalerType = isRequestCountScaler(current?.scaling?.type)\n ? 'REQUEST_COUNT'\n : 'QUEUE_DELAY';\n }\n if (needExclusionRead && current?.gpu?.excludedTypes?.length) {\n clearedExclusions = current.gpu.excludedTypes;\n }\n }\n\n // Checked against the resolved scaler, so `scalerValue` alone on an endpoint\n // already scaling on REQUEST_COUNT is still held to the integer bound.\n const scalerError = scalerValueError(\n scalerType,\n updateParams.scalerValue\n );\n if (scalerError) {\n return jsonReply({ error: scalerError, status: 400 });\n }\n\n const body = backend.mapUpdate({\n ...updateParams,\n scalerType,\n }) as Record<string, unknown>;\n const result = (await callRestUrl(url, 'PATCH', body)) as Record<\n string,\n unknown\n >;\n if (clearedExclusions.length) {\n return jsonReply({\n ...result,\n _warning: `Supplying gpuPoolIds replaced the GPU selection wholesale, clearing ${clearedExclusions.length} GPU-type exclusion(s) previously set on this endpoint: ${clearedExclusions.join(', ')}. Re-apply them with set-endpoint-gpus if still wanted.`,\n });\n }\n return jsonReply(result);\n }\n );\n\n // Delete Endpoint\n server.tool(\n 'delete-endpoint',\n 'Permanently delete a Serverless endpoint. This cannot be undone.',\n {\n endpointId: z.string().describe('ID of the endpoint to delete'),\n },\n { title: 'Delete endpoint', ...DESTRUCTIVE },\n async (params) => {\n const backend = backendFor('endpoints');\n const result = await callRestUrl(\n `${backend.base}${backend.get!(params.endpointId)}`,\n 'DELETE'\n );\n return jsonReply(result);\n }\n );\n\n // List Endpoint Releases — GET /v2/serverless/{id}/releases. v2-only; v1 has no\n // equivalent, hence the 501 notice.\n server.tool(\n 'list-endpoint-releases',\n \"List a Serverless endpoint's release history and current rollout status (workers on the latest version). v2-only — returns a 501 notice on the v1 API. Paginated via limit/cursor.\",\n {\n ...listPaginationParams,\n endpointId: z\n .string()\n .describe('ID of the Serverless endpoint whose releases to list'),\n },\n { title: 'List endpoint releases', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('endpoints');\n if (backend.version === 'v1') {\n return jsonReply({\n error:\n 'list-endpoint-releases is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.',\n status: 501,\n });\n }\n const raw = (await callRestUrl(\n `${backend.base}/serverless/${params.endpointId}/releases`\n )) as Record<string, unknown> | undefined;\n const releases = Array.isArray(raw?.releases) ? raw!.releases : [];\n return capListResult(\n releases,\n { limit: params.limit, cursor: params.cursor },\n { rollout: raw?.rollout, endpointVersion: raw?.endpointVersion }\n );\n }\n );\n\n // List Endpoint Workers (v2-only — GET /v2/serverless/{id}/workers)\n // Returns the workers backing an endpoint plus an aggregate summary. v2-only:\n // returns a 501 notice on the v1 API. The workers array is capped via\n // limit/cursor; the `summary` and `endpointVersion` are preserved alongside.\n server.tool(\n 'list-endpoint-workers',\n 'List the workers backing a Serverless endpoint, with their status and an aggregate summary. v2-only — returns a 501 notice on the v1 API. An UNHEALTHY worker usually means the container is crash-looping (repeated starts that exit before the handler runs) — jobs on the endpoint will sit IN_QUEUE even though this is a worker failure, not a capacity shortage; inspect it with stream-worker-logs. Zero workers while jobs queue means the endpoint is waiting for GPU capacity. Paginated via limit/cursor.',\n {\n ...listPaginationParams,\n endpointId: z\n .string()\n .describe('ID of the Serverless endpoint whose workers to list'),\n },\n { title: 'List endpoint workers', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('workers');\n if (backend.version === 'v1') {\n return jsonReply({\n error:\n 'list-endpoint-workers is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2 (on v1, use get-endpoint with includeWorkers).',\n status: 501,\n });\n }\n const raw = (await callRestUrl(\n `${backend.base}/serverless/${params.endpointId}/workers`\n )) as Record<string, unknown> | undefined;\n return capListResult(\n backend.unwrap(raw),\n { limit: params.limit, cursor: params.cursor },\n { summary: raw?.summary, endpointVersion: raw?.endpointVersion }\n );\n }\n );\n\n // Stream Worker Logs (v2-only — GET /v2/serverless/{id}/workers/{workerId}/logs).\n // Same feature as stream-pod-logs; see streamLogsReply.\n server.tool(\n 'stream-worker-logs',\n \"Fetch a bounded snapshot of a serverless worker's live logs (container and/or system) via Server-Sent Events. v2-only — returns a 501 notice on the v1 API. Get the workerId from list-endpoint-workers. Use this to diagnose UNHEALTHY workers: repeated `start container` system lines with no container output is the crash-loop signature (the container exits before the handler runs). Reads for up to maxWaitMs (default 5s) and returns the collected log lines; use `tail` to backfill recent lines first. Large output is truncated (see the `truncated` flag).\",\n {\n endpointId: z\n .string()\n .describe('ID of the Serverless endpoint the worker belongs to'),\n workerId: z\n .string()\n .describe(\n 'ID of the worker whose logs to stream (from list-endpoint-workers)'\n ),\n ...logStreamParams,\n },\n { title: 'Stream worker logs', ...READ_ONLY },\n (params) =>\n streamLogsReply(\n rt,\n {\n name: 'stream-worker-logs',\n resource: 'workers',\n logsUrl: (backend) =>\n `${backend.base}/serverless/${encodeURIComponent(params.endpointId)}/workers/${encodeURIComponent(params.workerId)}/logs`,\n },\n params\n )\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { READ_ONLY, WRITE, type ToolRuntime } from './runtime.js';\n\n// ============== SERVERLESS RUNTIME TOOLS ==============\n// Job submission and lifecycle against the Serverless runtime API\n// (api.runpod.ai/v2/{endpointId}/...). Distinct from endpoint CRUD.\n\n// Agents poll get-job-status in a tight loop while a job is queued, and each\n// IN_QUEUE poll would otherwise refetch the same worker list — dozens of\n// identical diagnoses for one queued job. Worker state changes on the order of\n// tens of seconds (scheduling, container starts), so a short-lived cache keyed\n// by endpoint keeps the amplification at one extra call per endpoint per TTL.\n// Module-level so it survives across per-request tool registries on a warm\n// hosted instance; bounded so one-off endpoint ids cannot grow it unbounded.\nconst DIAGNOSIS_TTL_MS = 15_000;\nconst MAX_DIAGNOSIS_ENTRIES = 500;\ninterface CachedDiagnosis {\n value: { workerHealth: unknown; hint: string };\n expiresAt: number;\n}\nconst diagnosisCache = new Map<string, CachedDiagnosis>();\n// Test seam: module-level state would otherwise leak between test cases.\nexport function clearQueuedJobDiagnosisCache(): void {\n diagnosisCache.clear();\n}\n\n// An http deployment is assumed to sit behind a gateway that reaps the request\n// mid-flight; Runpod's hosted one does, at 60s (vercel.json maxDuration). Wait\n// longer and the gateway kills the call before the tool's own timeout path\n// runs: bare 504, collected output discarded. 45s leaves room for the 4s\n// credential pre-flight that precedes dispatch (the v2 probe does NOT apply —\n// stdio-only, see backend.ts). Exported for the test that checks it against\n// vercel.json; stdio has no deadline.\nexport const HTTP_LONG_POLL_BUDGET_MS = 45_000;\n// Exported so the poll-deadline tests measure attempts against the real stdio\n// budget rather than a copy of it.\nexport const STDIO_STREAM_BUDGET_MS = 5 * 60 * 1000;\n// Upstream defaults, mirrored not derived — re-check against ai-api\n// (pkg/api/runsync.go, pkg/api/stream.go) if the service changes.\nconst RUNSYNC_UPSTREAM_DEFAULT_WAIT_MS = 90_000;\n// What an empty GET /stream holds for when no ?wait= is sent. stdio omits the\n// query, so this — not the http value below — is the hold its polls bracket.\n// Exported so those tests read the hold instead of restating it.\nexport const STREAM_UPSTREAM_DEFAULT_WAIT_MS = 10_000;\n// Caps how long an empty /stream may hold the poll. Left at the server's 10s\n// default, a chunk-sparse job overshoots the 45s budget to ~54s, since the\n// budget is only checked between polls. Accepted range 1000–300000. Exported\n// for the same reason as its stdio sibling above.\nexport const HTTP_STREAM_POLL_WAIT_MS = 1000;\n// Exported so the budget tests tick the real interval, not a copy of it.\nexport const STREAM_JOB_POLL_INTERVAL_MS = 1000;\n// Consecutive failures that end the poll. Only reachable while each attempt is\n// bounded well inside the budget — see streamPollTimeoutMs. Exported so the\n// guard asserting the budget fits that many attempts counts the real cap.\nexport const MAX_CONSECUTIVE_STREAM_ERRORS = 5;\n// Shared by stream-job (stop polling) and runsync (nothing was lost to the\n// clamp), so the two can't drift on what \"finished\" means.\nconst TERMINAL_STATUSES = new Set([\n 'COMPLETED',\n 'FAILED',\n 'CANCELLED',\n 'TIMED_OUT',\n]);\n// Annotatable: a plain object (spreading an array yields {\"0\":…}) for a job\n// still running, not already carrying the key.\nfunction isUnfinishedJobReply(\n result: unknown\n): result is Record<string, unknown> {\n return (\n result !== null &&\n typeof result === 'object' &&\n !Array.isArray(result) &&\n !('waitClamped' in result) &&\n !TERMINAL_STATUSES.has((result as { status?: string }).status ?? '')\n );\n}\n\nfunction formatBudget(ms: number): string {\n return ms >= 120_000\n ? `${Math.round(ms / 60_000)} minutes`\n : `${Math.round(ms / 1000)} seconds`;\n}\n\n// A client deadline must outlast the wait the server was asked to hold, or we\n// abort the reply we are waiting for; this is the round-trip room on top. Used\n// by both long-poll callers (runsync's ?wait=, stream-job's per-poll hold).\n// Must also fit under the http ceiling, or the clamp lands the deadline back on\n// the hold (asserted in tests/http.test.ts).\nexport const UPSTREAM_HOLD_SLACK_MS = 5_000;\n// So the last poll of a nearly-spent budget can still answer. Exported so the\n// floor test asserts the real value.\nexport const MIN_STREAM_POLL_TIMEOUT_MS = 2_000;\n// The queued-job diagnosis is enrichment on an answer we already have, and it is\n// discarded on any failure. The shared invocation budget stops it from causing a\n// 504, but spending 30 of the caller's seconds to decorate a reply that was\n// ready is still the wrong trade — the status belongs to the caller, the hint is\n// a bonus. Short enough that losing it costs little.\nexport const QUEUED_DIAGNOSIS_TIMEOUT_MS = 5_000;\n\n// Deadline for ONE /stream poll: the hold it brackets, bounded by what is left\n// of the budget, and never below MIN_STREAM_POLL_TIMEOUT_MS. The budget is a\n// ceiling here, never the target —\n//\n// above the hold — a poll that aborts before the server's own wait elapses\n// kills a reply that was on its way, every time.\n// below the budget — a deadline set TO the budget lets one wedged socket\n// spend the entire run in a single attempt, so\n// MAX_CONSECUTIVE_STREAM_ERRORS never engages and the\n// caller gets nothing back from a stall the retry loop was\n// built to survive.\n//\n// The floor only bites once the budget is nearly spent, where a 0ms deadline\n// would report a server fault for time we had already used. On the hosted\n// transport it is not the last word either: clampTimeout then measures the\n// result against the remaining invocation budget, whose own floor is\n// MIN_REQUEST_TIMEOUT_MS (src/_shared/http.ts).\nexport function streamPollTimeoutMs(\n remainingMs: number,\n holdMs: number\n): number {\n return Math.max(\n Math.min(remainingMs, holdMs + UPSTREAM_HOLD_SLACK_MS),\n MIN_STREAM_POLL_TIMEOUT_MS\n );\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// Everything about a stream-job run that depends on the transport, chosen in\n// one place. The query and the hold MUST agree — a poll that asks the server\n// for one wait while its deadline brackets another either aborts replies in\n// flight or waits far past what it should — and bundling the budget with them\n// means a wrong transport here cannot slip past the budget tests.\n// stdio sends no ?wait= (fewer requests, no platform deadline to race), so its\n// hold is the server's own default rather than one we picked.\nexport function streamPollPlan(hosted: boolean): {\n budgetMs: number;\n holdMs: number;\n query: string;\n} {\n return hosted\n ? {\n budgetMs: HTTP_LONG_POLL_BUDGET_MS,\n holdMs: HTTP_STREAM_POLL_WAIT_MS,\n query: `?wait=${HTTP_STREAM_POLL_WAIT_MS}`,\n }\n : {\n budgetMs: STDIO_STREAM_BUDGET_MS,\n holdMs: STREAM_UPSTREAM_DEFAULT_WAIT_MS,\n query: '',\n };\n}\n\n// Both unfinished exits (budget spent, error cap) say the same two things: what\n// stopped the run, and that /stream drains what it hands out — so calling again\n// resumes where this run stopped rather than replaying from the start. Shared so\n// an agent that hits the error cap is not left with chunks and no way forward.\nconst RESUME_ADVICE =\n 'Call stream-job again to continue collecting output, get-job-status to check the job without streaming, or stream without a budget by calling the runtime API directly (GET https://api.runpod.ai/v2/{endpointId}/stream/{jobId} with a Bearer API key).';\n\nfunction budgetExhaustedNote(\n budgetMs: number,\n lastError: string | undefined\n): Record<string, unknown> {\n return {\n pollingTimedOut: true,\n note: `Polling stopped after ${formatBudget(\n budgetMs\n )} with the job possibly still running. ${RESUME_ADVICE}`,\n // Surface the trailing error (if any) instead of discarding it — the last\n // polls may have been failing (e.g. job expired) even though earlier ones\n // succeeded. Cleared on success, so this is never a stale error from\n // minutes of healthy streaming ago.\n ...(lastError ? { lastError } : {}),\n };\n}\n\nfunction errorCapNote(lastError: string): Record<string, unknown> {\n return {\n error: `Polling aborted after ${MAX_CONSECUTIVE_STREAM_ERRORS} consecutive errors: ${lastError}`,\n note: `Polling stopped after ${MAX_CONSECUTIVE_STREAM_ERRORS} consecutive errors with the job possibly still running. ${RESUME_ADVICE}`,\n };\n}\n\n// Poll until the job reaches a terminal status, the budget runs out, or the API\n// fails MAX_CONSECUTIVE_STREAM_ERRORS times in a row. Lifted out of the handler\n// so those three exits read in one screen, and so a test can drive the loop\n// through `poll` without an MCP server around it.\nexport async function collectJobStream(deps: {\n poll: (timeoutMs: number) => Promise<Record<string, unknown>>;\n budgetMs: number;\n holdMs: number;\n}): Promise<{ result: Record<string, unknown>; chunks: unknown[] }> {\n const { poll, budgetMs, holdMs } = deps;\n const startedAt = Date.now();\n const elapsed = () => Date.now() - startedAt;\n const chunks: unknown[] = [];\n // Never mutated in place: `poll` is caller-supplied now that this is\n // exported, and the last reply is not ours to annotate.\n let result: Record<string, unknown> = {};\n let consecutiveErrors = 0;\n let lastError: string | undefined;\n\n while (true) {\n try {\n const reply = await poll(\n streamPollTimeoutMs(budgetMs - elapsed(), holdMs)\n );\n // A success clears both: only CONSECUTIVE failures end the run, so a\n // flaky endpoint that answers in between keeps streaming — and the error\n // reported at the end is one that was still happening at the end.\n consecutiveErrors = 0;\n lastError = undefined;\n if (Array.isArray(reply.stream)) chunks.push(...reply.stream);\n result = reply;\n if (TERMINAL_STATUSES.has(reply.status as string)) break;\n } catch (error) {\n consecutiveErrors++;\n lastError = error instanceof Error ? error.message : String(error);\n if (consecutiveErrors >= MAX_CONSECUTIVE_STREAM_ERRORS) {\n result = { ...result, ...errorCapNote(lastError) };\n break;\n }\n }\n\n if (elapsed() > budgetMs) {\n result = { ...result, ...budgetExhaustedNote(budgetMs, lastError) };\n break;\n }\n\n await sleep(STREAM_JOB_POLL_INTERVAL_MS);\n }\n\n return { result, chunks };\n}\n\nexport function registerJobTools(server: McpServer, rt: ToolRuntime): void {\n const { jsonReply, serverlessRequest, backendFor, callRestUrl } = rt;\n // Built per instance so each caller is told only the budget that applies to\n // them; stating both leaves every reader a clause that is false for them.\n const hosted = rt.transport === 'http';\n const streamBudget = formatBudget(\n hosted ? HTTP_LONG_POLL_BUDGET_MS : STDIO_STREAM_BUDGET_MS\n );\n\n // A job stuck IN_QUEUE has two very different causes that its status alone\n // cannot distinguish: no host has the endpoint's GPU available (capacity),\n // or a worker DID spin up and is crash-looping — the platform marks it\n // UNHEALTHY while the job stays queued forever. Field experience shows\n // agents (and humans) read the endless IN_QUEUE as \"no capacity\" and stop\n // digging. So when get-job-status sees IN_QUEUE, it best-effort attaches the\n // endpoint's worker summary and a plain-language hint pointing at the next\n // diagnostic step. v2-only (the workers listing has no v1 REST home);\n // never fails the status call itself.\n interface WorkerSummary {\n idle?: number;\n initializing?: number;\n running?: number;\n throttled?: number;\n total?: number;\n unhealthy?: number;\n }\n\n async function diagnoseQueuedJob(\n endpointId: string\n ): Promise<{ workerHealth: WorkerSummary; hint: string } | null> {\n const cached = diagnosisCache.get(endpointId);\n if (cached && cached.expiresAt > Date.now()) {\n return cached.value as { workerHealth: WorkerSummary; hint: string };\n }\n diagnosisCache.delete(endpointId);\n try {\n const backend = backendFor('workers');\n if (backend.version !== 'v2') return null;\n const raw = (await callRestUrl(\n `${backend.base}/serverless/${endpointId}/workers`,\n 'GET',\n undefined,\n { timeoutMs: QUEUED_DIAGNOSIS_TIMEOUT_MS }\n )) as\n | {\n summary?: WorkerSummary;\n workers?: Array<{ id: string; status?: string }>;\n }\n | undefined;\n const summary = raw?.summary;\n const workers = raw?.workers ?? [];\n if (!summary) return null;\n\n const remember = (value: {\n workerHealth: WorkerSummary;\n hint: string;\n }) => {\n if (diagnosisCache.size >= MAX_DIAGNOSIS_ENTRIES) {\n const oldest = diagnosisCache.keys().next().value;\n if (oldest !== undefined) diagnosisCache.delete(oldest);\n }\n diagnosisCache.set(endpointId, {\n value,\n expiresAt: Date.now() + DIAGNOSIS_TTL_MS,\n });\n return value;\n };\n\n if ((summary.unhealthy ?? 0) > 0) {\n const badIds = workers\n .filter((w) => w.status === 'UNHEALTHY')\n .map((w) => w.id)\n .join(', ');\n return remember({\n workerHealth: summary,\n hint: `${summary.unhealthy} of ${summary.total} worker(s) on this endpoint are UNHEALTHY — the container is likely crash-looping (repeated starts that exit before the handler runs). The job can stay IN_QUEUE indefinitely; this is a worker failure, NOT a capacity shortage. Inspect the logs with stream-worker-logs${\n badIds ? ` (workerId: ${badIds})` : ''\n }.`,\n });\n }\n if ((summary.total ?? 0) === 0) {\n return remember({\n workerHealth: summary,\n hint: 'No workers are scheduled for this endpoint yet — it may simply be scaling up from zero (normal for the first seconds of a cold start), or it is waiting for GPU capacity / its GPU-CUDA constraints exclude the currently-available hosts. If this persists, check availability with list-gpu-types or widen the gpuIds/CUDA settings.',\n });\n }\n if ((summary.throttled ?? 0) > 0 && (summary.running ?? 0) === 0) {\n return remember({\n workerHealth: summary,\n hint: 'Workers exist but are throttled — the hosts are at capacity right now; the job should start when capacity frees up.',\n });\n }\n if ((summary.initializing ?? 0) > 0) {\n return remember({\n workerHealth: summary,\n hint: 'A worker is initializing — likely a cold start (image pull / model load); the job should start soon.',\n });\n }\n return remember({ workerHealth: summary, hint: '' });\n } catch {\n return null; // diagnosis is best-effort — never break the status call\n }\n }\n\n // Shared schemas for serverless tools\n const endpointIdSchema = z\n .string()\n .regex(/^[a-zA-Z0-9_-]+$/, 'Invalid endpoint ID format');\n\n const jobIdSchema = z\n .string()\n .regex(/^[a-zA-Z0-9_-]+$/, 'Invalid job ID format');\n\n const inputSchema = z\n .record(z.unknown())\n .describe(\n 'Input payload for the worker handler. The expected fields depend on the deployed model or worker.'\n );\n\n const webhookSchema = z\n .string()\n .url()\n .optional()\n .describe(\n 'Webhook URL to receive job completion notifications instead of polling'\n );\n\n const policySchema = z\n .object({\n executionTimeout: z\n .number()\n .optional()\n .describe('Maximum execution time in milliseconds'),\n lowPriority: z\n .boolean()\n .optional()\n .describe('Submit as a low-priority job'),\n ttl: z\n .number()\n .optional()\n .describe('Time-to-live for the job result in milliseconds'),\n })\n .optional()\n .describe('Execution policy options');\n\n const s3ConfigSchema = z\n .object({\n accessId: z.string().describe('S3 access key ID'),\n accessSecret: z.string().describe('S3 secret access key'),\n bucketName: z.string().describe('S3 bucket name'),\n endpointUrl: z.string().describe('S3 endpoint URL'),\n })\n .optional()\n .describe('S3-compatible storage config for large outputs');\n\n // Run Endpoint (Async)\n server.tool(\n 'run-endpoint',\n 'Submit an asynchronous job to a Serverless endpoint. Returns a job ID immediately — use get-job-status to poll for results. Async results are available for 30 minutes after completion.',\n {\n endpointId: endpointIdSchema.describe(\n 'ID of the Serverless endpoint to run'\n ),\n input: inputSchema,\n webhook: webhookSchema,\n policy: policySchema,\n s3Config: s3ConfigSchema,\n },\n { title: 'Run endpoint (async)', ...WRITE },\n async (params) => {\n const { endpointId, ...body } = params;\n const result = await serverlessRequest(\n endpointId,\n '/run',\n 'POST',\n body as Record<string, unknown>\n );\n\n return jsonReply(result);\n }\n );\n\n // Run Endpoint Sync\n server.tool(\n 'runsync-endpoint',\n `Submit a synchronous job to a Serverless endpoint and wait for the result. Best for fast tasks: if the job outlives the wait, the response returns a job ID and a non-terminal status (IN_QUEUE or IN_PROGRESS) to poll with get-job-status. Max payload 20 MB; results expire after 1 minute. ${\n hosted\n ? `This server waits up to ${HTTP_LONG_POLL_BUDGET_MS} ms — it runs behind a 60-second gateway deadline, so that is both the default and the ceiling for the wait parameter. For longer jobs use run-endpoint + get-job-status, or POST https://api.runpod.ai/v2/{endpointId}/runsync?wait=300000 directly with a Bearer API key.`\n : 'The wait parameter extends the server-side wait to up to 5 minutes (300000 ms); it defaults to 90000 (90 seconds).'\n }`,\n {\n endpointId: endpointIdSchema.describe(\n 'ID of the Serverless endpoint to run synchronously'\n ),\n input: inputSchema,\n wait: z\n .number()\n .min(1000)\n .max(300000)\n .optional()\n .describe(\n hosted\n ? `How long in milliseconds the server should wait for a result before returning a job ID to poll. Accepted range 1000–300000, but this server clamps anything above ${HTTP_LONG_POLL_BUDGET_MS} down to ${HTTP_LONG_POLL_BUDGET_MS}, which is also the effective default.`\n : 'How long in milliseconds the server should wait for a result before returning a job ID to poll (1000–300000). Defaults to 90000 (90 seconds).'\n ),\n webhook: webhookSchema,\n policy: policySchema,\n s3Config: s3ConfigSchema,\n },\n { title: 'Run endpoint (sync)', ...WRITE },\n async (params) => {\n const { endpointId, wait, ...body } = params;\n // Always sent on http: the upstream default (90s) outlives the deadline\n // too, so an omitted wait is as fatal as a long one.\n const effectiveWait = hosted\n ? Math.min(\n wait ?? RUNSYNC_UPSTREAM_DEFAULT_WAIT_MS,\n HTTP_LONG_POLL_BUDGET_MS\n )\n : wait;\n // undefined, not falsy: `wait: 0` would drop the query and inherit the\n // upstream 90s default. Zod blocks 0 today, but handlers are called\n // directly (catalog.ts guards the same way).\n const waitQuery =\n effectiveWait === undefined ? '' : `?wait=${effectiveWait}`;\n const path = `/runsync${waitQuery}`;\n const result = await serverlessRequest(\n endpointId,\n path,\n 'POST',\n body as Record<string, unknown>,\n {\n // From the wait actually sent — on http the clamped 45s, not the\n // 300s the caller may have asked for.\n timeoutMs:\n (effectiveWait ?? RUNSYNC_UPSTREAM_DEFAULT_WAIT_MS) +\n UPSTREAM_HOLD_SLACK_MS,\n }\n );\n\n // Unmarked, a reply from a job 45s in is identical to one from a job that\n // outlived the 300s asked for — and the reaction to the latter is to\n // cancel it. Only marked when the caller could actually be misled.\n const requestedWait = wait ?? RUNSYNC_UPSTREAM_DEFAULT_WAIT_MS;\n if (\n hosted &&\n requestedWait > HTTP_LONG_POLL_BUDGET_MS &&\n isUnfinishedJobReply(result)\n ) {\n return jsonReply({\n ...result,\n waitClamped: {\n requestedMs: requestedWait,\n effectiveMs: effectiveWait,\n reason: `This server runs behind a 60-second gateway deadline, so a non-terminal status here means the job outlived ${formatBudget(\n HTTP_LONG_POLL_BUDGET_MS\n )}, not the requested wait — poll get-job-status rather than treating it as stuck.`,\n },\n });\n }\n\n return jsonReply(result);\n }\n );\n\n // Get Job Status\n server.tool(\n 'get-job-status',\n 'Check the status of an asynchronous Serverless job. Returns the current status and output when complete. Job statuses: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELLED, TIMED_OUT. IMPORTANT: a job stuck IN_QUEUE does not necessarily mean a capacity shortage — a worker may have spun up and crash-looped (the platform marks it UNHEALTHY while the job stays queued). When the job is IN_QUEUE this tool attaches a workerHealth summary and a hint; to dig deeper, call list-endpoint-workers (look for UNHEALTHY) and stream-worker-logs.',\n {\n endpointId: endpointIdSchema.describe(\n 'ID of the Serverless endpoint the job belongs to'\n ),\n jobId: jobIdSchema.describe('ID of the job to check'),\n },\n { title: 'Get job status', ...READ_ONLY },\n async (params) => {\n const result = await serverlessRequest(\n params.endpointId,\n `/status/${params.jobId}`\n );\n\n if (\n result &&\n typeof result === 'object' &&\n (result as { status?: string }).status === 'IN_QUEUE'\n ) {\n const diagnosis = await diagnoseQueuedJob(params.endpointId);\n if (diagnosis) {\n return jsonReply({\n ...(result as Record<string, unknown>),\n workerHealth: diagnosis.workerHealth,\n ...(diagnosis.hint ? { hint: diagnosis.hint } : {}),\n });\n }\n }\n\n return jsonReply(result);\n }\n );\n\n // Stream Job Results\n server.tool(\n 'stream-job',\n `Retrieve streaming output from a Serverless job. The worker must support streaming output. Polls /stream/{jobId} and collects chunks until the status is COMPLETED, FAILED, CANCELLED, or TIMED_OUT, for up to ${streamBudget}${hosted ? ' (this server runs behind a 60-second gateway deadline)' : ''}. If the budget expires first, returns the chunks collected so far with pollingTimedOut: true — call stream-job again to resume where it left off, or get-job-status to check without streaming.${\n hosted\n ? ' To collect with no budget, poll GET https://api.runpod.ai/v2/{endpointId}/stream/{jobId} yourself with a Bearer API key until the status is terminal — each request drains only the chunks buffered so far, so a single request is not the whole output.'\n : ''\n }`,\n {\n endpointId: endpointIdSchema.describe(\n 'ID of the Serverless endpoint the job belongs to'\n ),\n jobId: jobIdSchema.describe('ID of the job to stream results from'),\n },\n { title: 'Stream job', ...READ_ONLY },\n async (params) => {\n const plan = streamPollPlan(hosted);\n const streamPath = `/stream/${params.jobId}${plan.query}`;\n\n const { result, chunks } = await collectJobStream({\n budgetMs: plan.budgetMs,\n holdMs: plan.holdMs,\n poll: (timeoutMs) =>\n serverlessRequest(params.endpointId, streamPath, 'GET', undefined, {\n timeoutMs,\n }) as Promise<Record<string, unknown>>,\n });\n\n return jsonReply({ ...result, stream: chunks });\n }\n );\n\n // Cancel Job\n server.tool(\n 'cancel-job',\n 'Cancel a Serverless job that is queued or in progress.',\n {\n endpointId: endpointIdSchema.describe(\n 'ID of the Serverless endpoint the job belongs to'\n ),\n jobId: jobIdSchema.describe('ID of the job to cancel'),\n },\n { title: 'Cancel job', ...WRITE, idempotentHint: true },\n async (params) => {\n const result = await serverlessRequest(\n params.endpointId,\n `/cancel/${params.jobId}`,\n 'POST'\n );\n\n return jsonReply(result);\n }\n );\n\n // Retry Job\n server.tool(\n 'retry-job',\n 'Retry a failed or timed-out Serverless job. Only works for jobs with FAILED or TIMED_OUT status. The previous output is removed and the job is requeued.',\n {\n endpointId: endpointIdSchema.describe(\n 'ID of the Serverless endpoint the job belongs to'\n ),\n jobId: jobIdSchema.describe('ID of the job to retry'),\n },\n { title: 'Retry job', ...WRITE },\n async (params) => {\n const result = await serverlessRequest(\n params.endpointId,\n `/retry/${params.jobId}`,\n 'POST'\n );\n\n return jsonReply(result);\n }\n );\n\n // Endpoint Health\n server.tool(\n 'endpoint-health',\n 'Get the health and operational status of a Serverless endpoint, including worker counts and job statistics. Use this when jobs are stuck IN_QUEUE: unhealthy workers mean a crash-looping container (a worker failure — inspect with stream-worker-logs), while zero workers means the endpoint is waiting for GPU capacity.',\n {\n endpointId: endpointIdSchema.describe(\n 'ID of the Serverless endpoint to check health for'\n ),\n },\n { title: 'Endpoint health', ...READ_ONLY },\n async (params) => {\n const result = await serverlessRequest(params.endpointId, '/health');\n\n return jsonReply(result);\n }\n );\n\n // Purge Endpoint Queue\n server.tool(\n 'purge-endpoint-queue',\n 'Remove all pending jobs from a Serverless endpoint queue. Only affects queued jobs — in-progress jobs continue running. Use this for error recovery or clearing outdated requests.',\n {\n endpointId: endpointIdSchema.describe(\n 'ID of the Serverless endpoint to purge the queue for'\n ),\n },\n { title: 'Purge endpoint queue', ...WRITE, idempotentHint: true },\n async (params) => {\n const result = await serverlessRequest(\n params.endpointId,\n '/purge-queue',\n 'POST'\n );\n\n return jsonReply(result);\n }\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { capListResult, listPaginationParams } from '../pagination.js';\nimport { READ_ONLY, WRITE, DESTRUCTIVE, type ToolRuntime } from './runtime.js';\n\n// ============== TEMPLATE MANAGEMENT TOOLS ==============\n\nexport function registerTemplateTools(\n server: McpServer,\n rt: ToolRuntime\n): void {\n const { jsonReply, callRestUrl, backendFor } = rt;\n\n // List Templates\n server.tool(\n 'list-templates',\n 'List available templates. By default returns only the user\\'s own templates. Use includeRunpodTemplates to also include official Runpod templates. The recommended default template for new pods is \"Runpod Pytorch 2.8.0\" (ID: runpod-torch-v280) — it has the latest CUDA and PyTorch versions.',\n {\n ...listPaginationParams,\n includeRunpodTemplates: z\n .boolean()\n .optional()\n .describe('Include official Runpod templates in the response'),\n includePublicTemplates: z\n .boolean()\n .optional()\n .describe('Include community-made public templates in the response'),\n includeEndpointBoundTemplates: z\n .boolean()\n .optional()\n .describe(\n 'Include templates bound to Serverless endpoints in the response'\n ),\n },\n { title: 'List templates', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('templates');\n let query = '';\n if (backend.version === 'v1') {\n const queryParams = new URLSearchParams();\n if (params.includeRunpodTemplates)\n queryParams.set('includeRunpodTemplates', 'true');\n if (params.includePublicTemplates)\n queryParams.set('includePublicTemplates', 'true');\n if (params.includeEndpointBoundTemplates)\n queryParams.set('includeEndpointBoundTemplates', 'true');\n query = queryParams.toString() ? `?${queryParams.toString()}` : '';\n }\n const result = await callRestUrl(\n `${backend.base}${backend.list}${query}`\n );\n\n return capListResult(backend.unwrap(result), {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n );\n\n // Get Template Details\n server.tool(\n 'get-template',\n 'Get one template by id (image, env, ports, disk/volume config).',\n {\n templateId: z.string().describe('ID of the template to retrieve'),\n },\n { title: 'Get template', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('templates');\n const result = await callRestUrl(\n `${backend.base}${backend.get!(params.templateId)}`\n );\n return jsonReply(result);\n }\n );\n\n // Create Template\n server.tool(\n 'create-template',\n 'Create a reusable pod/endpoint template (image, ports, env, disk/volume). Category is optional and defaults to NVIDIA.',\n {\n name: z.string().describe('Name for the template'),\n imageName: z.string().describe('Docker image to use'),\n isServerless: z\n .boolean()\n .optional()\n .describe('Is this a serverless template'),\n ports: z.array(z.string()).optional().describe('Ports to expose'),\n dockerEntrypoint: z\n .array(z.string())\n .optional()\n .describe(\n 'Docker entrypoint commands. Note: not persisted on the v2 REST API, which has no separate entrypoint field (only a start command).'\n ),\n dockerStartCmd: z\n .array(z.string())\n .optional()\n .describe(\n 'Container start command. On v2 this is stored as the template\\'s `args` string; a multi-element array is joined with spaces (e.g. [\"python\",\"-u\",\"app.py\"] → \"python -u app.py\"). The join is lossy: an element that itself contains spaces is NOT preserved as one argument — e.g. [\"sh\",\"-c\",\"echo hi\"] becomes \"sh -c echo hi\", so pass a shell command as a single pre-quoted element if that distinction matters. Omit or pass an empty array to leave the command unset (never overwrites with \"\").'\n ),\n env: z.record(z.string()).optional().describe('Environment variables'),\n containerDiskInGb: z\n .number()\n .optional()\n .describe('Container disk size in GB'),\n volumeInGb: z.number().optional().describe('Volume size in GB'),\n volumeMountPath: z\n .string()\n .optional()\n .describe('Path to mount the volume'),\n readme: z\n .string()\n .optional()\n .describe('README content in markdown format'),\n category: z\n .enum(['CPU', 'NVIDIA', 'AMD'])\n .optional()\n .describe(\n 'How the template is grouped and filtered in the Runpod console — it does NOT affect hardware selection, scheduling, or billing. Optional; defaults to NVIDIA. Set CPU or AMD only to file the template correctly in the console.'\n ),\n containerRegistryAuthId: z\n .string()\n .optional()\n .describe(\n 'ID of a container registry credential (from create-container-registry-auth / list-container-registry-auths) used to pull a private image. Required when imageName points at a private registry; without it serverless workers fail the image pull.'\n ),\n },\n { title: 'Create template', ...WRITE },\n async (params) => {\n const backend = backendFor('templates');\n const body = backend.mapCreate(params) as Record<string, unknown>;\n const result = await callRestUrl(\n `${backend.base}${backend.list}`,\n 'POST',\n body\n );\n return jsonReply(result);\n }\n );\n\n // Update Template\n server.tool(\n 'update-template',\n \"Update a template's mutable fields (name, image, ports, env, start command, registry auth). Only provided fields change; omitted fields are left untouched.\",\n {\n templateId: z.string().describe('ID of the template to update'),\n name: z.string().optional().describe('New name for the template'),\n imageName: z.string().optional().describe('New Docker image'),\n dockerStartCmd: z\n .array(z.string())\n .optional()\n .describe(\n \"New container start command. On v2 stored as the template's `args` string; a multi-element array is joined with spaces (lossy — an element containing spaces is split into separate tokens). Note: this can only set a command, not clear one — an empty or absent array is ignored so the existing command is preserved.\"\n ),\n ports: z.array(z.string()).optional().describe('New ports to expose'),\n env: z\n .record(z.string())\n .optional()\n .describe('New environment variables'),\n readme: z\n .string()\n .optional()\n .describe('New README content in markdown format'),\n containerRegistryAuthId: z\n .string()\n .optional()\n .describe(\n 'ID of a container registry credential (from create-container-registry-auth / list-container-registry-auths) used to pull a private image. Set this to attach a credential to a template referencing a private registry.'\n ),\n },\n { title: 'Update template', ...WRITE, idempotentHint: true },\n async (params) => {\n const { templateId, ...updateParams } = params;\n const backend = backendFor('templates');\n const body = backend.mapUpdate(updateParams) as Record<string, unknown>;\n const result = await callRestUrl(\n `${backend.base}${backend.get!(templateId)}`,\n 'PATCH',\n body\n );\n return jsonReply(result);\n }\n );\n\n // Delete Template\n server.tool(\n 'delete-template',\n 'Permanently delete a template. This cannot be undone.',\n {\n templateId: z.string().describe('ID of the template to delete'),\n },\n { title: 'Delete template', ...DESTRUCTIVE },\n async (params) => {\n const backend = backendFor('templates');\n const result = await callRestUrl(\n `${backend.base}${backend.get!(params.templateId)}`,\n 'DELETE'\n );\n return jsonReply(result);\n }\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { capListResult, listPaginationParams } from '../pagination.js';\nimport { READ_ONLY, WRITE, DESTRUCTIVE, type ToolRuntime } from './runtime.js';\n\n// ============== NETWORK VOLUME MANAGEMENT TOOLS ==============\n\nexport function registerNetworkVolumeTools(\n server: McpServer,\n rt: ToolRuntime\n): void {\n const { jsonReply, callRestUrl, backendFor } = rt;\n\n // List Network Volumes\n server.tool(\n 'list-network-volumes',\n 'List your network volumes (persistent storage attachable to pods). Paginated via limit/cursor.',\n listPaginationParams,\n { title: 'List network volumes', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('networkVolumes');\n const result = await callRestUrl(`${backend.base}${backend.list}`);\n\n return capListResult(backend.unwrap(result), {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n );\n\n // Get Network Volume Details\n server.tool(\n 'get-network-volume',\n 'Get one network volume by id (name, size, data center).',\n {\n networkVolumeId: z\n .string()\n .describe('ID of the network volume to retrieve'),\n },\n { title: 'Get network volume', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('networkVolumes');\n const result = await callRestUrl(\n `${backend.base}${backend.get!(params.networkVolumeId)}`\n );\n return jsonReply(result);\n }\n );\n\n // Create Network Volume\n server.tool(\n 'create-network-volume',\n 'Create a persistent network volume in a data center (size 10–4096 GB) that pods can mount. Pass volumeType to pick a storage tier; omit it to get the data center default.',\n {\n name: z.string().describe('Name for the network volume'),\n // Bounds mirror the v2 spec (integer, minimum 10, maximum 4096). Enforced\n // here as well as described, so an out-of-range value fails locally\n // instead of costing an API roundtrip.\n size: z.number().int().min(10).max(4096).describe('Size in GB (10-4096)'),\n dataCenterId: z\n .string()\n .describe('Data center ID (see list-data-centers)'),\n volumeType: z\n .enum(['STANDARD', 'HIGH_PERFORMANCE'])\n .optional()\n .describe(\n \"Storage tier. Omit for the data center's default (primary) tier. HIGH_PERFORMANCE provisions an HPS volume. Immutable after creation — update-network-volume cannot change it.\"\n ),\n },\n { title: 'Create network volume', ...WRITE },\n async (params) => {\n const backend = backendFor('networkVolumes');\n const body = backend.mapCreate(params) as Record<string, unknown>;\n const result = await callRestUrl(\n `${backend.base}${backend.list}`,\n 'POST',\n body\n );\n return jsonReply(result);\n }\n );\n\n // Update Network Volume\n server.tool(\n 'update-network-volume',\n 'Update a network volume (rename, or grow size — the new size must exceed the current size). Only provided fields change.',\n {\n networkVolumeId: z\n .string()\n .describe('ID of the network volume to update'),\n name: z.string().optional().describe('New name for the network volume'),\n // Same absolute bounds as create. \"Larger than current\" stays server-side —\n // we do not hold the current size here, so only the range is checked.\n size: z\n .number()\n .int()\n .min(10)\n .max(4096)\n .optional()\n .describe('New size in GB, 10-4096 (must be larger than current)'),\n },\n { title: 'Update network volume', ...WRITE, idempotentHint: true },\n async (params) => {\n const { networkVolumeId, ...updateParams } = params;\n const backend = backendFor('networkVolumes');\n const body = backend.mapUpdate(updateParams) as Record<string, unknown>;\n const result = await callRestUrl(\n `${backend.base}${backend.get!(networkVolumeId)}`,\n 'PATCH',\n body\n );\n return jsonReply(result);\n }\n );\n\n // Delete Network Volume\n server.tool(\n 'delete-network-volume',\n 'Permanently delete a network volume and its data. This cannot be undone.',\n {\n networkVolumeId: z\n .string()\n .describe('ID of the network volume to delete'),\n },\n { title: 'Delete network volume', ...DESTRUCTIVE },\n async (params) => {\n const backend = backendFor('networkVolumes');\n const result = await callRestUrl(\n `${backend.base}${backend.get!(params.networkVolumeId)}`,\n 'DELETE'\n );\n return jsonReply(result);\n }\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { capListResult, listPaginationParams } from '../pagination.js';\nimport { READ_ONLY, WRITE, DESTRUCTIVE, type ToolRuntime } from './runtime.js';\n\n// ============== CONTAINER REGISTRY TOOLS ==============\n// Two related things live here, both under the v2 `registries` resource:\n//\n// 1. Container registry auths (`/v2/registries`) — a stored username +\n// password used to pull a private image. Available on v1 and v2.\n// 2. ECR delegations (`/v2/registries/delegations`) — a different mechanism\n// for the same goal, specific to AWS ECR: instead of storing credentials,\n// you register an ECR repository ARN and Runpod is granted scoped pull\n// access to it, handing back a `dockerRegistryUri` to pull from. v2-only.\n//\n// The delegation sub-paths are built from the registries backend's own base +\n// list path, so they need no separate Resource entry in the adapter.\n\nconst DELEGATIONS = '/delegations';\n\nexport function registerRegistryTools(\n server: McpServer,\n rt: ToolRuntime\n): void {\n const { jsonReply, callRestUrl, backendFor } = rt;\n\n // ECR delegation is a v2-only concept — v1 has no equivalent endpoint, so the\n // tools return a clean 501 notice there rather than calling a v1 path that\n // does not exist (mirrors list-endpoint-workers / stream-pod-logs).\n const delegationsV2OnlyNotice = (tool: string) => ({\n error: `${tool} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,\n status: 501,\n });\n\n // List Container Registry Auths\n server.tool(\n 'list-container-registry-auths',\n 'List your saved container-registry credentials (private image-pull auth). Paginated via limit/cursor.',\n listPaginationParams,\n { title: 'List container registry auths', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('registries');\n const result = await callRestUrl(`${backend.base}${backend.list}`);\n\n return capListResult(backend.unwrap(result), {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n );\n\n // Get Container Registry Auth Details\n server.tool(\n 'get-container-registry-auth',\n 'Get one saved container-registry credential by id (the secret/password is not returned).',\n {\n containerRegistryAuthId: z\n .string()\n .describe('ID of the container registry auth to retrieve'),\n },\n { title: 'Get container registry auth', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('registries');\n const result = await callRestUrl(\n `${backend.base}${backend.get!(params.containerRegistryAuthId)}`\n );\n return jsonReply(result);\n }\n );\n\n // Create Container Registry Auth\n server.tool(\n 'create-container-registry-auth',\n 'Save container-registry credentials (username + password/token) so pods and endpoints can pull private images.',\n {\n name: z.string().describe('Name for the container registry auth'),\n username: z.string().describe('Registry username'),\n password: z\n .string()\n .describe('Registry password or access token (stored as a secret)'),\n },\n { title: 'Create container registry auth', ...WRITE },\n async (params) => {\n const backend = backendFor('registries');\n const body = backend.mapCreate(params) as Record<string, unknown>;\n const result = await callRestUrl(\n `${backend.base}${backend.list}`,\n 'POST',\n body\n );\n return jsonReply(result);\n }\n );\n\n // Delete Container Registry Auth\n server.tool(\n 'delete-container-registry-auth',\n 'Permanently delete a saved container-registry credential. This cannot be undone.',\n {\n containerRegistryAuthId: z\n .string()\n .describe('ID of the container registry auth to delete'),\n },\n { title: 'Delete container registry auth', ...DESTRUCTIVE },\n async (params) => {\n const backend = backendFor('registries');\n const result = await callRestUrl(\n `${backend.base}${backend.get!(params.containerRegistryAuthId)}`,\n 'DELETE'\n );\n return jsonReply(result);\n }\n );\n\n // ============== ECR DELEGATION TOOLS (v2-only) ==============\n\n // List ECR Delegations\n server.tool(\n 'list-registry-delegations',\n 'List your AWS ECR delegations — ECR repositories Runpod has been granted scoped pull access to, so private ECR images can be pulled without storing AWS credentials. Each entry includes the `dockerRegistryUri` to pull from. v2-only — returns a 501 notice on the v1 API. Paginated via limit/cursor.',\n listPaginationParams,\n { title: 'List registry delegations', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('registries');\n if (backend.version === 'v1') {\n return jsonReply(delegationsV2OnlyNotice('list-registry-delegations'));\n }\n // The response envelope is `{delegations:[…]}`, not the `{registries:[…]}`\n // that backend.unwrap expects for this resource, so read the key directly.\n const raw = (await callRestUrl(\n `${backend.base}${backend.list}${DELEGATIONS}`\n )) as Record<string, unknown> | undefined;\n const delegations = Array.isArray(raw?.delegations)\n ? raw.delegations\n : [];\n return capListResult(delegations, {\n limit: params.limit,\n cursor: params.cursor,\n });\n }\n );\n\n // Create ECR Delegation\n server.tool(\n 'create-registry-delegation',\n 'Register an AWS ECR repository so Runpod can pull private images from it without stored credentials. Pass the repository ARN; the reply carries the `dockerRegistryUri`, resolved repository/tag, and AWS region. v2-only — returns a 501 notice on the v1 API. For a non-ECR private registry use create-container-registry-auth instead.',\n {\n resource: z\n .string()\n .min(1)\n .describe(\n 'ECR resource ARN, e.g. \"arn:aws:ecr:us-east-2:123456789012:repository/my-org/my-image\"'\n ),\n name: z\n .string()\n .optional()\n .describe('Optional human-readable name for the delegation'),\n },\n { title: 'Create registry delegation', ...WRITE },\n async (params) => {\n const backend = backendFor('registries');\n if (backend.version === 'v1') {\n return jsonReply(delegationsV2OnlyNotice('create-registry-delegation'));\n }\n // CreateDelegationRequest is additionalProperties:false — send only the\n // two documented keys, and omit `name` entirely when unset rather than\n // sending null.\n const body: Record<string, unknown> = { resource: params.resource };\n if (params.name !== undefined) body.name = params.name;\n const result = await callRestUrl(\n `${backend.base}${backend.list}${DELEGATIONS}`,\n 'POST',\n body\n );\n return jsonReply(result);\n }\n );\n\n // Revoke ECR Delegation\n server.tool(\n 'delete-registry-delegation',\n \"Revoke an AWS ECR delegation, withdrawing Runpod's pull access to that repository. Existing workers keep running; future pulls fail. v2-only — returns a 501 notice on the v1 API. This cannot be undone.\",\n {\n delegationId: z\n .string()\n .describe(\n 'ID of the delegation to revoke (from list-registry-delegations)'\n ),\n },\n { title: 'Revoke registry delegation', ...DESTRUCTIVE },\n async (params) => {\n const backend = backendFor('registries');\n if (backend.version === 'v1') {\n return jsonReply(delegationsV2OnlyNotice('delete-registry-delegation'));\n }\n const result = await callRestUrl(\n `${backend.base}${backend.list}${DELEGATIONS}/${encodeURIComponent(\n params.delegationId\n )}`,\n 'DELETE'\n );\n return jsonReply(result);\n }\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { capListResult, listPaginationParams } from '../pagination.js';\nimport { READ_ONLY, type ToolRuntime } from './runtime.js';\n\n// ============== BILLING TOOLS (v2-only) ==============\n// Read-only, time-bucketed spend history. v2-only — returns a 501 notice on the\n// v1 API. The records array can be large (buckets × resources), so it is capped\n// via limit/cursor; the aggregate `metadata` is preserved alongside the page.\n\n// scope → URL sub-path. 'all' is the aggregate /v2/billing; the rest are\n// per-resource breakdowns. Values match the v2 spec sub-paths exactly\n// (note: 'networkvolumes' has no hyphen here, unlike the /v2/network-volumes\n// resource path).\nconst BILLING_SCOPE_PATH: Record<string, string> = {\n all: '/billing',\n pods: '/billing/pods',\n serverless: '/billing/serverless',\n endpoints: '/billing/endpoints',\n networkvolumes: '/billing/networkvolumes',\n clusters: '/billing/clusters',\n};\n\nexport function registerBillingTools(server: McpServer, rt: ToolRuntime): void {\n const { jsonReply, callRestUrl, backendFor } = rt;\n\n server.tool(\n 'get-billing',\n 'Get time-bucketed Runpod spend history, either aggregated across all resources or broken down by resource type (pods, serverless, endpoints, network volumes, clusters). v2-only — returns a 501 notice on the v1 API. Records are capped via limit/cursor; narrow the window with startTime/endTime or lastN.',\n {\n ...listPaginationParams,\n scope: z\n .enum([\n 'all',\n 'pods',\n 'serverless',\n 'endpoints',\n 'networkvolumes',\n 'clusters',\n ])\n .optional()\n .describe(\n 'Which billing breakdown to fetch. \"all\" (default) is the aggregate across every resource.'\n ),\n startTime: z\n .string()\n .optional()\n .describe(\n 'Start of the period (RFC 3339, e.g. 2026-05-01T00:00:00Z). Defaults to 30 days ago. Mutually exclusive with lastN.'\n ),\n endTime: z\n .string()\n .optional()\n .describe(\n 'End of the period (RFC 3339), exclusive. Defaults to now. Mutually exclusive with lastN.'\n ),\n bucketSize: z\n .enum(['hour', 'day', 'week', 'month', 'year'])\n .optional()\n .describe('Length of each time bucket. Defaults to day.'),\n lastN: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\n 'Return the last N buckets of bucketSize, ending with the current one. Mutually exclusive with startTime/endTime.'\n ),\n },\n { title: 'Get billing history', ...READ_ONLY },\n async (params) => {\n const backend = backendFor('billing');\n if (backend.version === 'v1') {\n return jsonReply({\n error:\n 'get-billing is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.',\n status: 501,\n });\n }\n\n const subPath = BILLING_SCOPE_PATH[params.scope ?? 'all'];\n const query = new URLSearchParams();\n if (params.startTime) query.set('startTime', params.startTime);\n if (params.endTime) query.set('endTime', params.endTime);\n if (params.bucketSize) query.set('bucketSize', params.bucketSize);\n // lastN is mutually exclusive with startTime/endTime; if a window was\n // given, ignore lastN so we never forward a contradictory request.\n if (\n params.lastN !== undefined &&\n !params.startTime &&\n !params.endTime\n )\n query.set('lastN', String(params.lastN));\n const qs = query.toString() ? `?${query.toString()}` : '';\n\n const raw = (await callRestUrl(`${backend.base}${subPath}${qs}`)) as\n | Record<string, unknown>\n | undefined;\n // Cap the (potentially large) records array but keep the aggregate\n // `metadata` alongside the page so totals aren't lost to truncation.\n return capListResult(\n backend.unwrap(raw),\n { limit: params.limit, cursor: params.cursor },\n { metadata: raw?.metadata }\n );\n }\n );\n}\n"],"mappings":"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","names":["minCudaVersion","fetch","import_zod","import_zod","jsonReply","rows","import_zod","jsonReply","import_zod","import_zod","jsonReply","import_zod","jsonReply","import_zod","jsonReply","result","body","import_zod","jsonReply","import_zod","jsonReply","import_zod","jsonReply","import_zod","jsonReply","import_zod","jsonReply"]}
|