@runpod/mcp-server 2.0.0 → 3.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/dist/tools.cjs CHANGED
@@ -41,7 +41,8 @@ var import_node_crypto = require("crypto");
41
41
  // src/_shared/http.ts
42
42
  var HttpError = class extends Error {
43
43
  constructor(prefix, status, body) {
44
- super(`${prefix}: ${status} - ${body}`);
44
+ const hint = status === 401 ? " (the Runpod API rejected the credential \u2014 the API key may be expired or revoked; re-authenticate or update RUNPOD_API_KEY)" : "";
45
+ super(`${prefix}: ${status} - ${body}${hint}`);
45
46
  this.name = "HttpError";
46
47
  this.status = status;
47
48
  this.body = body;
@@ -97,6 +98,106 @@ function buildTrackingHeaders(input) {
97
98
  };
98
99
  }
99
100
 
101
+ // src/tools/logs.ts
102
+ var import_zod = require("zod");
103
+ function parseLogSse(raw) {
104
+ const items = [];
105
+ for (const block of raw.split(/\r?\n\r?\n/)) {
106
+ const dataLines = block.split(/\r?\n/).filter((l) => l.startsWith("data:"));
107
+ if (!dataLines.length) continue;
108
+ const payload = dataLines.map((l) => l.slice(5).replace(/^ /, "")).join("\n");
109
+ if (!payload.trim()) continue;
110
+ try {
111
+ const parsed = JSON.parse(payload);
112
+ items.push(
113
+ parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { raw: payload }
114
+ );
115
+ } catch {
116
+ items.push({ raw: payload });
117
+ }
118
+ }
119
+ return items;
120
+ }
121
+ var LOG_STREAM_DEFAULT_WAIT_MS = 5e3;
122
+ var LOG_STREAM_MAX_BYTES = 256 * 1024;
123
+ async function readSseSnapshot(fetchImpl, url, headers, opts) {
124
+ const controller = new AbortController();
125
+ const timer = setTimeout(() => controller.abort(), opts.maxWaitMs);
126
+ const chunks = [];
127
+ let bytes = 0;
128
+ let truncated = false;
129
+ try {
130
+ const res = await fetchImpl(url, {
131
+ method: "GET",
132
+ headers,
133
+ signal: controller.signal
134
+ });
135
+ if (!res.ok) {
136
+ throw new HttpError(
137
+ "Runpod API Error",
138
+ res.status,
139
+ await res.text().catch(() => "")
140
+ );
141
+ }
142
+ for await (const chunk of res.body ?? []) {
143
+ chunks.push(chunk);
144
+ bytes += chunk.length;
145
+ if (bytes >= opts.maxBytes) {
146
+ truncated = true;
147
+ controller.abort();
148
+ break;
149
+ }
150
+ }
151
+ } catch (err) {
152
+ if (!(err instanceof Error && err.name === "AbortError")) throw err;
153
+ } finally {
154
+ clearTimeout(timer);
155
+ }
156
+ return { raw: Buffer.concat(chunks).toString("utf8"), truncated };
157
+ }
158
+ var logStreamParams = {
159
+ source: import_zod.z.enum(["container", "system", "both"]).optional().describe("Which log source to read (default: both)"),
160
+ tail: import_zod.z.number().int().min(0).max(5e3).optional().describe(
161
+ "Historical lines to backfill before live output (API default 100, max 5000; 0 = live only). Ignored when `since` is set."
162
+ ),
163
+ since: import_zod.z.string().optional().describe("RFC3339 timestamp to resume from; when set, `tail` is ignored."),
164
+ maxWaitMs: import_zod.z.number().int().min(500).max(3e4).optional().describe("How long to read the stream, in ms (default 5000, max 30000)")
165
+ };
166
+ async function collectLogSnapshot(streamSse, logsUrl, params) {
167
+ const qs = new URLSearchParams();
168
+ if (params.source && params.source !== "both")
169
+ qs.append("source", params.source);
170
+ if (params.tail !== void 0) qs.append("tail", String(params.tail));
171
+ if (params.since) qs.append("since", params.since);
172
+ const query = qs.toString() ? `?${qs}` : "";
173
+ const { raw, truncated } = await streamSse(`${logsUrl}${query}`, {
174
+ maxWaitMs: params.maxWaitMs ?? LOG_STREAM_DEFAULT_WAIT_MS,
175
+ maxBytes: LOG_STREAM_MAX_BYTES
176
+ });
177
+ const items = parseLogSse(raw);
178
+ if (truncated) items.pop();
179
+ return { items, count: items.length, truncated };
180
+ }
181
+ async function streamLogsReply(rt, tool, params) {
182
+ const backend = rt.backendFor(tool.resource);
183
+ if (backend.version === "v1") {
184
+ return rt.jsonReply({
185
+ error: `${tool.name} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,
186
+ status: 501
187
+ });
188
+ }
189
+ try {
190
+ return rt.jsonReply(
191
+ await collectLogSnapshot(rt.streamSse, tool.logsUrl(backend), params)
192
+ );
193
+ } catch (error) {
194
+ if (error instanceof HttpError) {
195
+ return rt.jsonReply({ error: error.message, status: error.status });
196
+ }
197
+ throw error;
198
+ }
199
+ }
200
+
100
201
  // src/_shared/mappers.ts
101
202
  function compact(obj) {
102
203
  const out = {};
@@ -109,6 +210,9 @@ function persistentMount(size, path) {
109
210
  if (size === void 0 || path === void 0) return void 0;
110
211
  return { persistent: { size, path } };
111
212
  }
213
+ function joinStartCmd(cmd) {
214
+ return cmd && cmd.length ? cmd.join(" ") : void 0;
215
+ }
112
216
  function containerConfigToV2(p) {
113
217
  return compact({
114
218
  image: p.imageName,
@@ -127,6 +231,13 @@ function mapPodCreateToV2(params) {
127
231
  return compact({
128
232
  ...containerConfigToV2(params),
129
233
  name: params.name,
234
+ // Private-image registry credential (v2 ContainerConfig `registry`). When
235
+ // deploying from a template, this spreads over the template's registry, so
236
+ // passing it overrides the inherited credential. An empty string is an
237
+ // explicit opt-out: emit `registry: null` (v2 accepts null) to CLEAR a
238
+ // template's inherited credential. Undefined is dropped by compact, so an
239
+ // omitted param leaves the template's registry in place.
240
+ registry: params.containerRegistryAuthId === "" ? null : params.containerRegistryAuthId,
130
241
  cloud: params.cloudType,
131
242
  // v2 CreatePodRequest takes `dataCenterIds` as an ARRAY (preferred data
132
243
  // centers; omit/empty = scheduler chooses). Pass the v1 list straight
@@ -183,7 +294,8 @@ function mapNetworkVolumeCreateToV2(params) {
183
294
  return compact({
184
295
  name: params.name,
185
296
  size: params.size,
186
- dataCenter: params.dataCenterId
297
+ dataCenter: params.dataCenterId,
298
+ type: params.volumeType
187
299
  });
188
300
  }
189
301
  function mapTemplateCreateToV2(params) {
@@ -192,18 +304,39 @@ function mapTemplateCreateToV2(params) {
192
304
  ...compact({
193
305
  name: params.name,
194
306
  serverless: params.isServerless,
195
- registry: params.containerRegistryAuthId
196
- }),
197
- // Required by v2 — always emit, defaulting to NVIDIA.
198
- category: params.category ?? "NVIDIA"
307
+ registry: params.containerRegistryAuthId,
308
+ args: joinStartCmd(params.dockerStartCmd),
309
+ // No longer required by v2 (it defaults to NVIDIA server-side), so an
310
+ // unset category is dropped by compact instead of being forced here.
311
+ category: params.category
312
+ })
199
313
  };
200
314
  }
315
+ function podBodyFromTemplate(template) {
316
+ const out = {};
317
+ const keys = [
318
+ "name",
319
+ "image",
320
+ "args",
321
+ "disk",
322
+ "ports",
323
+ "env",
324
+ "mounts",
325
+ "registry"
326
+ ];
327
+ for (const key of keys) {
328
+ const value = template[key];
329
+ if (value !== void 0 && value !== null) out[key] = value;
330
+ }
331
+ return out;
332
+ }
201
333
  function mapTemplateUpdateToV2(params) {
202
334
  return {
203
335
  ...containerConfigToV2(params),
204
336
  ...compact({
205
337
  name: params.name,
206
- registry: params.containerRegistryAuthId
338
+ registry: params.containerRegistryAuthId,
339
+ args: joinStartCmd(params.dockerStartCmd)
207
340
  })
208
341
  };
209
342
  }
@@ -220,7 +353,6 @@ var V2_UNWRAP_KEY = {
220
353
  dataCenters: "dataCenters",
221
354
  endpoints: "endpoints",
222
355
  jobs: "jobs",
223
- tags: "tags",
224
356
  workers: "workers",
225
357
  billing: "records"
226
358
  };
@@ -234,7 +366,6 @@ var RESOURCE_ENV_KEY = {
234
366
  dataCenters: "DATA_CENTERS",
235
367
  endpoints: "ENDPOINTS",
236
368
  jobs: "JOBS",
237
- tags: "TAGS",
238
369
  workers: "WORKERS",
239
370
  billing: "BILLING"
240
371
  };
@@ -250,6 +381,9 @@ function serverlessBase(env) {
250
381
  function publicGraphqlBase(env) {
251
382
  return env.RUNPOD_PUBLIC_GRAPHQL_URL ?? "https://api.runpod.io/graphql";
252
383
  }
384
+ function authedGraphqlBase(env) {
385
+ return env.RUNPOD_AUTHED_GRAPHQL_URL ?? "https://api.runpod.io/graphql";
386
+ }
253
387
  function unwrapList(resource, version, raw) {
254
388
  if (version === "v1") return Array.isArray(raw) ? raw : [];
255
389
  if (raw === null || typeof raw !== "object") return [];
@@ -288,7 +422,6 @@ var CATALOG = /* @__PURE__ */ new Set([
288
422
  "dataCenters"
289
423
  ]);
290
424
  var V2_ONLY = /* @__PURE__ */ new Set([
291
- "tags",
292
425
  "workers",
293
426
  "billing"
294
427
  ]);
@@ -306,7 +439,6 @@ var V2_REST_PATHS = {
306
439
  list: "/catalog/datacenters",
307
440
  get: (id) => `/catalog/datacenters/${id}`
308
441
  },
309
- tags: { list: "/tags", get: (id) => `/tags/${id}` },
310
442
  // Endpoints live under /v2/serverless on v2 (NOT /v2/endpoints). The v2 list
311
443
  // response wraps rows under `endpoints` (see V2_UNWRAP_KEY), so the existing
312
444
  // unwrap key stays correct.
@@ -404,7 +536,7 @@ function resolveBackend(opts) {
404
536
  }
405
537
 
406
538
  // src/tools/runtime.ts
407
- var MCP_SERVER_VERSION = true ? "2.0.0" : "dev";
539
+ var MCP_SERVER_VERSION = true ? "3.0.0" : "dev";
408
540
  var SESSION_ID = (0, import_node_crypto.randomUUID)();
409
541
  function trackingHeaders(server, ctx) {
410
542
  const info = server.server.getClientVersion();
@@ -441,14 +573,18 @@ var DESTRUCTIVE = {
441
573
  idempotentHint: true,
442
574
  openWorldHint: true
443
575
  };
444
- async function graphqlRequest(query, tracking, fetchImpl, url) {
576
+ async function graphqlRequest(query, tracking, fetchImpl, url, options) {
445
577
  const response = await fetchImpl(url, {
446
578
  method: "POST",
447
579
  headers: {
448
580
  "Content-Type": "application/json",
581
+ ...options?.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {},
449
582
  ...tracking()
450
583
  },
451
- body: JSON.stringify({ query })
584
+ body: JSON.stringify({
585
+ query,
586
+ ...options?.variables ? { variables: options.variables } : {}
587
+ })
452
588
  });
453
589
  const result = await response.json();
454
590
  if (result.errors && result.errors.length > 0) {
@@ -463,7 +599,17 @@ async function graphqlRequest(query, tracking, fetchImpl, url) {
463
599
  }
464
600
  function createToolRuntime(server, ctx, deps = {}) {
465
601
  const tracking = () => trackingHeaders(server, ctx);
466
- const httpFetch = deps.fetch ?? defaultFetch;
602
+ function observeUnauthorized(fn) {
603
+ const notify = ctx.onUnauthorized;
604
+ if (!notify) return fn;
605
+ return async (...args) => {
606
+ const response = await fn(...args);
607
+ if (response.status === 401) notify();
608
+ return response;
609
+ };
610
+ }
611
+ const rawFetch = deps.fetch ?? defaultFetch;
612
+ const httpFetch = observeUnauthorized(rawFetch);
467
613
  const v1Client = createHttpClient({
468
614
  apiKey: ctx.apiKey,
469
615
  fetch: httpFetch,
@@ -498,47 +644,19 @@ function createToolRuntime(server, ctx, deps = {}) {
498
644
  "Error calling Runpod API:",
499
645
  () => restClient(url, method, body)
500
646
  );
501
- const streamSse = deps.streamSse ?? (async (url, opts) => {
502
- const controller = new AbortController();
503
- const timer = setTimeout(() => controller.abort(), opts.maxWaitMs);
504
- try {
505
- const res = await (0, import_node_fetch.default)(url, {
506
- method: "GET",
507
- headers: {
508
- Authorization: `Bearer ${ctx.apiKey}`,
509
- Accept: "text/event-stream",
510
- ...tracking()
511
- },
512
- signal: controller.signal
513
- });
514
- if (!res.ok) {
515
- throw new HttpError(
516
- "Runpod API Error",
517
- res.status,
518
- await res.text().catch(() => "")
519
- );
520
- }
521
- const chunks = [];
522
- let bytes = 0;
523
- let truncated = false;
524
- try {
525
- for await (const chunk of res.body) {
526
- chunks.push(chunk);
527
- bytes += chunk.length;
528
- if (bytes >= opts.maxBytes) {
529
- truncated = true;
530
- controller.abort();
531
- break;
532
- }
533
- }
534
- } catch (err) {
535
- if (!(err instanceof Error && err.name === "AbortError")) throw err;
536
- }
537
- return { raw: Buffer.concat(chunks).toString("utf8"), truncated };
538
- } finally {
539
- clearTimeout(timer);
540
- }
541
- });
647
+ const sseFetch = observeUnauthorized(
648
+ deps.sseFetch ?? import_node_fetch.default
649
+ );
650
+ const streamSse = deps.streamSse ?? ((url, opts) => readSseSnapshot(
651
+ sseFetch,
652
+ url,
653
+ {
654
+ Authorization: `Bearer ${ctx.apiKey}`,
655
+ Accept: "text/event-stream",
656
+ ...tracking()
657
+ },
658
+ opts
659
+ ));
542
660
  const backendFor = (resource) => resolveBackend({
543
661
  resource,
544
662
  env: process.env,
@@ -560,9 +678,18 @@ function createToolRuntime(server, ctx, deps = {}) {
560
678
  graphql: (query) => graphqlRequest(
561
679
  query,
562
680
  tracking,
563
- httpFetch,
681
+ // rawFetch, not httpFetch: this call carries no credential (see above).
682
+ rawFetch,
564
683
  publicGraphqlBase(process.env)
565
684
  ),
685
+ graphqlAuthed: (query, variables) => graphqlRequest(
686
+ query,
687
+ tracking,
688
+ httpFetch,
689
+ // NOT publicGraphqlBase: this call carries the caller's API key.
690
+ authedGraphqlBase(process.env),
691
+ { variables, apiKey: ctx.apiKey }
692
+ ),
566
693
  runpodRequest,
567
694
  serverlessRequest,
568
695
  callRestUrl,
@@ -574,17 +701,17 @@ function createToolRuntime(server, ctx, deps = {}) {
574
701
  }
575
702
 
576
703
  // src/tools/catalog.ts
577
- var import_zod2 = require("zod");
704
+ var import_zod3 = require("zod");
578
705
 
579
706
  // src/pagination.ts
580
- var import_zod = require("zod");
707
+ var import_zod2 = require("zod");
581
708
  var DEFAULT_LIST_LIMIT = 20;
582
709
  var MAX_LIST_LIMIT = 100;
583
710
  var listPaginationParams = {
584
- limit: import_zod.z.number().int().positive().max(MAX_LIST_LIMIT).optional().describe(
711
+ limit: import_zod2.z.number().int().positive().max(MAX_LIST_LIMIT).optional().describe(
585
712
  `Maximum number of items to return (default ${DEFAULT_LIST_LIMIT}, max ${MAX_LIST_LIMIT}). Use the returned nextCursor to fetch the next page.`
586
713
  ),
587
- cursor: import_zod.z.string().optional().describe(
714
+ cursor: import_zod2.z.string().optional().describe(
588
715
  "Opaque pagination cursor from a previous response (nextCursor). Omit to start from the beginning."
589
716
  )
590
717
  };
@@ -642,25 +769,32 @@ function registerCatalogTools(server, rt) {
642
769
  const { jsonReply: jsonReply2, graphql, callRestUrl, backendFor } = rt;
643
770
  server.tool(
644
771
  "list-gpu-types",
645
- "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. On v2 the realtime stock filter is a no-op.",
772
+ "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.",
646
773
  {
647
774
  ...listPaginationParams,
648
- minMemoryGb: import_zod2.z.number().optional().describe("Filter to GPUs with at least this much VRAM in GB"),
649
- secureCloudOnly: import_zod2.z.boolean().optional().describe("Filter to only GPUs available in secure cloud"),
650
- communityCloudOnly: import_zod2.z.boolean().optional().describe("Filter to only GPUs available in community cloud"),
651
- searchTerm: import_zod2.z.string().optional().describe(
775
+ minMemoryGb: import_zod3.z.number().optional().describe("Filter to GPUs with at least this much VRAM in GB"),
776
+ secureCloudOnly: import_zod3.z.boolean().optional().describe("Filter to only GPUs available in secure cloud"),
777
+ communityCloudOnly: import_zod3.z.boolean().optional().describe("Filter to only GPUs available in community cloud"),
778
+ searchTerm: import_zod3.z.string().optional().describe(
652
779
  "Search term to filter GPUs by name (e.g., 'A100', 'RTX 4090')"
653
780
  ),
654
- includeUnavailable: import_zod2.z.boolean().optional().describe(
655
- "Include GPUs that are currently out of stock. Default is false"
781
+ includeUnavailable: import_zod3.z.boolean().optional().describe(
782
+ "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."
783
+ ),
784
+ includeAvailability: import_zod3.z.boolean().optional().describe(
785
+ "Request realtime stock and annotate each GPU with an availability summary (HIGH/MEDIUM/LOW/NONE). Default true. Set false to skip the availability lookup \u2014 then out-of-stock GPUs cannot be filtered."
656
786
  )
657
787
  },
658
788
  { title: "List GPU types", ...READ_ONLY },
659
789
  async (params) => {
660
790
  const backend = backendFor("gpus");
661
791
  if (backend.version === "v2") {
662
- const raw = await callRestUrl(`${backend.base}${backend.list}`);
792
+ const wantAvailability = params.includeAvailability !== false;
793
+ const raw = await callRestUrl(
794
+ `${backend.base}${backend.list}${wantAvailability ? "?include=AVAILABILITY" : ""}`
795
+ );
663
796
  let gpus = backend.unwrap(raw);
797
+ gpus = gpus.filter((g) => g.id !== "unknown");
664
798
  if (params.minMemoryGb !== void 0)
665
799
  gpus = gpus.filter(
666
800
  (g) => Number(g.memory ?? 0) >= params.minMemoryGb
@@ -673,6 +807,18 @@ function registerCatalogTools(server, rt) {
673
807
  (g) => String(g.id ?? "").toLowerCase().includes(t) || String(g.name ?? "").toLowerCase().includes(t)
674
808
  );
675
809
  }
810
+ if (params.includeUnavailable === false)
811
+ gpus = gpus.filter((g) => g.availability !== "NONE");
812
+ const rank = {
813
+ HIGH: 3,
814
+ MEDIUM: 2,
815
+ LOW: 1,
816
+ NONE: 0
817
+ };
818
+ gpus.sort(
819
+ (a, b) => (rank[String(b.availability)] ?? 0) - (rank[String(a.availability)] ?? 0)
820
+ );
821
+ gpus = gpus.map(({ dataCenters: _dataCenters, ...rest }) => rest);
676
822
  return capListResult(gpus, {
677
823
  limit: params.limit,
678
824
  cursor: params.cursor
@@ -702,7 +848,7 @@ function registerCatalogTools(server, rt) {
702
848
  return !!status && status !== "Out";
703
849
  };
704
850
  let gpuTypes = data.gpuTypes.filter((gpu) => gpu.id !== "unknown");
705
- if (!params.includeUnavailable) {
851
+ if (params.includeUnavailable === false) {
706
852
  gpuTypes = gpuTypes.filter(isAvailable);
707
853
  }
708
854
  if (params.minMemoryGb) {
@@ -748,7 +894,7 @@ function registerCatalogTools(server, rt) {
748
894
  "List Runpod data centers (id, name, region/location). Use this to discover valid dataCenterIds for placing pods, endpoints, or network volumes.",
749
895
  {
750
896
  ...listPaginationParams,
751
- region: import_zod2.z.string().optional().describe(
897
+ region: import_zod3.z.string().optional().describe(
752
898
  "Filter by region/location (e.g., 'United States', 'Europe', 'Canada')"
753
899
  )
754
900
  },
@@ -813,9 +959,12 @@ function registerCatalogTools(server, rt) {
813
959
  );
814
960
  server.tool(
815
961
  "get-gpu-type",
816
- "Get details for a single GPU type by id. v2-only \u2014 returns a 501 notice on the v1 API (use list-gpu-types there).",
962
+ "Get details for a single GPU type by id, including per-datacenter availability. v2-only \u2014 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.",
817
963
  {
818
- gpuTypeId: import_zod2.z.string().describe("ID of the GPU type to retrieve")
964
+ gpuTypeId: import_zod3.z.string().describe("ID of the GPU type to retrieve"),
965
+ includeAvailability: import_zod3.z.boolean().optional().describe(
966
+ "Include realtime per-datacenter availability (HIGH/MEDIUM/LOW/NONE). Default true."
967
+ )
819
968
  },
820
969
  { title: "Get GPU type", ...READ_ONLY },
821
970
  async (params) => {
@@ -826,9 +975,9 @@ function registerCatalogTools(server, rt) {
826
975
  status: 501
827
976
  });
828
977
  }
829
- const result = await callRestUrl(
830
- `${backend.base}${backend.get(params.gpuTypeId)}`
831
- );
978
+ const path = backend.get(encodeURIComponent(params.gpuTypeId));
979
+ const query = params.includeAvailability === false ? "" : "?include=AVAILABILITY";
980
+ const result = await callRestUrl(`${backend.base}${path}${query}`);
832
981
  return jsonReply2(result);
833
982
  }
834
983
  );
@@ -836,7 +985,7 @@ function registerCatalogTools(server, rt) {
836
985
  "get-cpu-type",
837
986
  "Get details for a single CPU flavor type by id. v2-only \u2014 returns a 501 notice on the v1 API (use list-cpu-types there).",
838
987
  {
839
- cpuTypeId: import_zod2.z.string().describe("ID of the CPU type to retrieve")
988
+ cpuTypeId: import_zod3.z.string().describe("ID of the CPU type to retrieve")
840
989
  },
841
990
  { title: "Get CPU type", ...READ_ONLY },
842
991
  async (params) => {
@@ -857,7 +1006,7 @@ function registerCatalogTools(server, rt) {
857
1006
  "get-data-center",
858
1007
  "Get details for a single data center by id. v2-only \u2014 returns a 501 notice on the v1 API (use list-data-centers there).",
859
1008
  {
860
- dataCenterId: import_zod2.z.string().describe("ID of the data center to retrieve")
1009
+ dataCenterId: import_zod3.z.string().describe("ID of the data center to retrieve")
861
1010
  },
862
1011
  { title: "Get data center", ...READ_ONLY },
863
1012
  async (params) => {
@@ -876,8 +1025,579 @@ function registerCatalogTools(server, rt) {
876
1025
  );
877
1026
  }
878
1027
 
1028
+ // src/tools/hub.ts
1029
+ var import_zod4 = require("zod");
1030
+ function parseReleaseConfig(config) {
1031
+ if (!config) return void 0;
1032
+ try {
1033
+ return JSON.parse(config);
1034
+ } catch {
1035
+ return config;
1036
+ }
1037
+ }
1038
+ function listingsQuery(withConfig) {
1039
+ return `
1040
+ query {
1041
+ listings(input: {}) {
1042
+ id
1043
+ repoId
1044
+ title
1045
+ description
1046
+ repoName
1047
+ repoOwner
1048
+ createdAt
1049
+ updatedAt
1050
+ views
1051
+ stars
1052
+ deploys
1053
+ language
1054
+ category
1055
+ tags
1056
+ type
1057
+ listedRelease {
1058
+ id
1059
+ name
1060
+ tagName
1061
+ createdAt${withConfig ? "\n config" : ""}
1062
+ build {
1063
+ id
1064
+ imageName
1065
+ }
1066
+ }
1067
+ }
1068
+ }
1069
+ `;
1070
+ }
1071
+ function serializeBoolean(value, input) {
1072
+ if (value === "true") return input.trueValue ?? "true";
1073
+ if (value === "false") return input.falseValue ?? "false";
1074
+ return value;
1075
+ }
1076
+ function buildHubEnv(config, overrides) {
1077
+ const env = [];
1078
+ const missingRequired = [];
1079
+ const remaining = { ...overrides };
1080
+ for (const entry of config.env ?? []) {
1081
+ const input = entry.input ?? {};
1082
+ const override = entry.key in remaining ? remaining[entry.key] : void 0;
1083
+ delete remaining[entry.key];
1084
+ if (override !== void 0 && override !== "") {
1085
+ env.push({
1086
+ key: entry.key,
1087
+ value: input.type === "boolean" ? serializeBoolean(override, input) : override
1088
+ });
1089
+ continue;
1090
+ }
1091
+ if (input.default !== void 0 && input.default !== null) {
1092
+ env.push({
1093
+ key: entry.key,
1094
+ value: typeof input.default === "boolean" ? input.default ? input.trueValue ?? "true" : input.falseValue ?? "false" : String(input.default)
1095
+ });
1096
+ continue;
1097
+ }
1098
+ if (input.required) {
1099
+ missingRequired.push(entry.key);
1100
+ env.push({ key: entry.key, value: "" });
1101
+ continue;
1102
+ }
1103
+ }
1104
+ for (const [key, value] of Object.entries(remaining)) {
1105
+ env.push({ key, value });
1106
+ }
1107
+ return { env, missingRequired };
1108
+ }
1109
+ function minCudaVersion(versions) {
1110
+ if (!versions || versions.length === 0) return null;
1111
+ const compare = (a, b) => {
1112
+ const pa = a.split(".").map(Number);
1113
+ const pb = b.split(".").map(Number);
1114
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
1115
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
1116
+ if (d !== 0) return d;
1117
+ }
1118
+ return 0;
1119
+ };
1120
+ return [...versions].sort(compare)[0];
1121
+ }
1122
+ function randomSuffix() {
1123
+ return Math.random().toString(36).slice(2, 8);
1124
+ }
1125
+ function registerHubTools(server, rt) {
1126
+ const { graphql, graphqlAuthed, jsonReply: jsonReply2 } = rt;
1127
+ server.tool(
1128
+ "list-hub-repos",
1129
+ "List repos published to the Runpod Hub (prebuilt Serverless workers and Pod templates, e.g. vLLM, ComfyUI). Public catalog \u2014 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) \u2014 it is large, so prefer requesting it for a single repo via the repoOwner/searchTerm filters.",
1130
+ {
1131
+ ...listPaginationParams,
1132
+ searchTerm: import_zod4.z.string().optional().describe(
1133
+ "Case-insensitive search across title, description, repo name, owner, and tags (e.g. 'vllm', 'comfyui', 'fine-tuning')"
1134
+ ),
1135
+ category: import_zod4.z.string().optional().describe(
1136
+ "Filter by category (e.g. 'language', 'image', 'audio', 'video', 'embedding')"
1137
+ ),
1138
+ type: import_zod4.z.enum(["SERVERLESS", "POD"]).optional().describe(
1139
+ "Filter by listing type: SERVERLESS (endpoint workers) or POD (pod templates)"
1140
+ ),
1141
+ repoOwner: import_zod4.z.string().optional().describe(
1142
+ "Filter to listings from this GitHub owner/org (e.g. 'runpod-workers', 'axolotl-ai-cloud')"
1143
+ ),
1144
+ includeConfig: import_zod4.z.boolean().optional().describe(
1145
+ "Include the listed release config (hardware requirements + env-var input schema, parsed from .runpod/hub.json). Large \u2014 off by default."
1146
+ )
1147
+ },
1148
+ { title: "List Hub repos", ...READ_ONLY },
1149
+ async (params) => {
1150
+ const data = await graphql(
1151
+ listingsQuery(params.includeConfig === true)
1152
+ );
1153
+ let listings = data.listings;
1154
+ if (params.type) {
1155
+ listings = listings.filter(
1156
+ (l) => (l.type ?? "").toUpperCase() === params.type
1157
+ );
1158
+ }
1159
+ if (params.category) {
1160
+ const term = params.category.toLowerCase();
1161
+ listings = listings.filter(
1162
+ (l) => (l.category ?? "").toLowerCase() === term
1163
+ );
1164
+ }
1165
+ if (params.repoOwner) {
1166
+ const term = params.repoOwner.toLowerCase();
1167
+ listings = listings.filter(
1168
+ (l) => (l.repoOwner ?? "").toLowerCase() === term
1169
+ );
1170
+ }
1171
+ if (params.searchTerm) {
1172
+ const term = params.searchTerm.toLowerCase();
1173
+ listings = listings.filter(
1174
+ (l) => [
1175
+ l.title,
1176
+ l.description,
1177
+ l.repoName,
1178
+ l.repoOwner,
1179
+ ...l.tags ?? []
1180
+ ].some((field) => (field ?? "").toLowerCase().includes(term))
1181
+ );
1182
+ }
1183
+ listings = [...listings].sort(
1184
+ (a, b) => (b.deploys ?? 0) - (a.deploys ?? 0) || a.id.localeCompare(b.id)
1185
+ );
1186
+ const result = listings.map((l) => ({
1187
+ id: l.id,
1188
+ repo: `${l.repoOwner}/${l.repoName}`,
1189
+ title: l.title,
1190
+ description: l.description,
1191
+ type: l.type,
1192
+ category: l.category,
1193
+ tags: l.tags,
1194
+ language: l.language,
1195
+ stars: l.stars,
1196
+ deploys: l.deploys,
1197
+ views: l.views,
1198
+ githubUrl: `https://github.com/${l.repoOwner}/${l.repoName}`,
1199
+ hubUrl: `https://console.runpod.io/hub/${l.repoOwner}/${l.repoName}`,
1200
+ createdAt: l.createdAt,
1201
+ updatedAt: l.updatedAt,
1202
+ listedRelease: l.listedRelease ? {
1203
+ hubReleaseId: l.listedRelease.id,
1204
+ name: l.listedRelease.name,
1205
+ tagName: l.listedRelease.tagName,
1206
+ createdAt: l.listedRelease.createdAt,
1207
+ imageName: l.listedRelease.build?.imageName,
1208
+ ...params.includeConfig ? { config: parseReleaseConfig(l.listedRelease.config) } : {}
1209
+ } : null
1210
+ }));
1211
+ return capListResult(result, {
1212
+ limit: params.limit,
1213
+ cursor: params.cursor
1214
+ });
1215
+ }
1216
+ );
1217
+ server.tool(
1218
+ "deploy-hub-repo",
1219
+ "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 \u2014 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.",
1220
+ {
1221
+ repo: import_zod4.z.string().optional().describe(
1222
+ `The Hub repo to deploy as "owner/name" (e.g. 'runpod-workers/worker-comfyui'). Deploys its currently listed release. Provide this or hubReleaseId.`
1223
+ ),
1224
+ hubReleaseId: import_zod4.z.string().optional().describe(
1225
+ "The Hub release ID to deploy (from list-hub-repos listedRelease.hubReleaseId). Provide this or repo."
1226
+ ),
1227
+ name: import_zod4.z.string().optional().describe(
1228
+ "Name for the new endpoint. Defaults to '<listing title> <release tag>'."
1229
+ ),
1230
+ env: import_zod4.z.record(import_zod4.z.string()).optional().describe(
1231
+ "Environment variable overrides, merged over the release config defaults. Keys not in the release schema are passed through as-is."
1232
+ ),
1233
+ gpuIds: import_zod4.z.string().optional().describe(
1234
+ "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."
1235
+ ),
1236
+ gpuCount: import_zod4.z.number().int().positive().optional().describe("GPUs per worker. Defaults to the release config (or 1)."),
1237
+ containerDiskInGb: import_zod4.z.number().int().positive().optional().describe("Container disk size in GB. Defaults to the release config."),
1238
+ workersMin: import_zod4.z.number().int().min(0).optional().describe("Minimum workers (default 0 \u2014 scale to zero)."),
1239
+ workersMax: import_zod4.z.number().int().positive().optional().describe("Maximum workers (default: account default)."),
1240
+ idleTimeout: import_zod4.z.number().int().positive().optional().describe("Seconds a worker idles before scaling down (default 5)."),
1241
+ scalerType: import_zod4.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Autoscaler type (default QUEUE_DELAY)."),
1242
+ scalerValue: import_zod4.z.number().optional().describe("Autoscaler target value (default 4)."),
1243
+ executionTimeoutMs: import_zod4.z.number().int().positive().optional().describe("Per-job execution timeout in ms (default 600000)."),
1244
+ flashboot: import_zod4.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("FlashBoot mode (default FLASHBOOT).")
1245
+ },
1246
+ { title: "Deploy Hub repo", ...WRITE },
1247
+ async (params) => {
1248
+ if (!params.repo && !params.hubReleaseId) {
1249
+ return jsonReply2({
1250
+ error: 'Provide either repo ("owner/name") or hubReleaseId. Use list-hub-repos to discover both.'
1251
+ });
1252
+ }
1253
+ const catalog = await graphql(listingsQuery(true));
1254
+ let listing;
1255
+ if (params.hubReleaseId) {
1256
+ listing = catalog.listings.find(
1257
+ (l) => l.listedRelease?.id === params.hubReleaseId
1258
+ );
1259
+ } else {
1260
+ const repoKey = params.repo.toLowerCase();
1261
+ listing = catalog.listings.find(
1262
+ (l) => `${l.repoOwner}/${l.repoName}`.toLowerCase() === repoKey
1263
+ );
1264
+ }
1265
+ if (!listing) {
1266
+ return jsonReply2({
1267
+ error: `No Hub listing found for ${params.hubReleaseId ? `hubReleaseId "${params.hubReleaseId}" (only each repo's currently listed release is deployable)` : `repo "${params.repo}"`}. Use list-hub-repos to see the catalog.`
1268
+ });
1269
+ }
1270
+ const release = listing.listedRelease;
1271
+ if (!release?.build?.imageName) {
1272
+ return jsonReply2({
1273
+ error: `Hub repo ${listing.repoOwner}/${listing.repoName} has no listed release with a built image, so it cannot be deployed.`
1274
+ });
1275
+ }
1276
+ if ((listing.type ?? "").toUpperCase() !== "SERVERLESS") {
1277
+ return jsonReply2({
1278
+ error: `Hub repo ${listing.repoOwner}/${listing.repoName} is a ${listing.type} listing \u2014 only SERVERLESS listings deploy as endpoints.`
1279
+ });
1280
+ }
1281
+ const config = parseReleaseConfig(release.config) ?? {};
1282
+ const gpuIds = params.gpuIds ?? config.gpuIds;
1283
+ if (!gpuIds) {
1284
+ return jsonReply2({
1285
+ error: 'This release does not specify a GPU pool \u2014 pass gpuIds (comma-separated pool names, e.g. "ADA_80_PRO,AMPERE_80"; see the pool field on list-gpu-types).'
1286
+ });
1287
+ }
1288
+ const { env, missingRequired } = buildHubEnv(config, params.env ?? {});
1289
+ if (missingRequired.length > 0) {
1290
+ return jsonReply2({
1291
+ error: `Missing required environment variables for this release: ${missingRequired.join(
1292
+ ", "
1293
+ )}. Pass them via the env parameter.`
1294
+ });
1295
+ }
1296
+ const endpointName = params.name ?? `${listing.title} ${release.tagName}`;
1297
+ const minCuda = minCudaVersion(config.allowedCudaVersions);
1298
+ const input = {
1299
+ name: endpointName,
1300
+ hubReleaseId: release.id,
1301
+ type: "QB",
1302
+ gpuIds,
1303
+ gpuCount: params.gpuCount ?? config.gpuCount ?? 1,
1304
+ workersMin: params.workersMin ?? 0,
1305
+ workersMax: params.workersMax ?? null,
1306
+ idleTimeout: params.idleTimeout ?? 5,
1307
+ scalerType: params.scalerType ?? "QUEUE_DELAY",
1308
+ scalerValue: params.scalerValue ?? 4,
1309
+ executionTimeoutMs: params.executionTimeoutMs ?? 6e5,
1310
+ flashBootType: params.flashboot ?? "FLASHBOOT",
1311
+ locations: null,
1312
+ networkVolumeIds: null,
1313
+ compliance: [],
1314
+ modelReferences: [],
1315
+ ...minCuda ? {
1316
+ minCudaVersion: minCuda,
1317
+ allowedCudaVersions: config.allowedCudaVersions.join(",")
1318
+ } : {},
1319
+ template: {
1320
+ name: `${endpointName}__template__${randomSuffix()}`,
1321
+ imageName: release.build.imageName,
1322
+ containerDiskInGb: params.containerDiskInGb ?? config.containerDiskInGb ?? 20,
1323
+ containerRegistryAuthId: "",
1324
+ dockerArgs: "",
1325
+ startScript: "",
1326
+ ports: "",
1327
+ env
1328
+ }
1329
+ };
1330
+ const data = await graphqlAuthed(
1331
+ `
1332
+ mutation saveEndpoint($input: EndpointInput!) {
1333
+ saveEndpoint(input: $input) {
1334
+ id
1335
+ name
1336
+ gpuIds
1337
+ gpuCount
1338
+ workersMin
1339
+ workersMax
1340
+ idleTimeout
1341
+ scalerType
1342
+ scalerValue
1343
+ flashBootType
1344
+ templateId
1345
+ }
1346
+ }
1347
+ `,
1348
+ { input }
1349
+ );
1350
+ return jsonReply2({
1351
+ endpoint: data.saveEndpoint,
1352
+ deployed: {
1353
+ repo: `${listing.repoOwner}/${listing.repoName}`,
1354
+ release: release.tagName,
1355
+ hubReleaseId: release.id,
1356
+ imageName: release.build.imageName
1357
+ },
1358
+ note: `Endpoint created. Submit jobs with run-endpoint/runsync-endpoint using endpointId "${data.saveEndpoint.id}".`
1359
+ });
1360
+ }
1361
+ );
1362
+ }
1363
+
1364
+ // src/tools/public-endpoints.ts
1365
+ var import_zod5 = require("zod");
1366
+ function parseMetadata(metadata) {
1367
+ if (!metadata) return {};
1368
+ try {
1369
+ const parsed = JSON.parse(metadata);
1370
+ return parsed && typeof parsed === "object" ? parsed : {};
1371
+ } catch {
1372
+ return {};
1373
+ }
1374
+ }
1375
+ function registerPublicEndpointTools(server, rt) {
1376
+ const { graphql } = rt;
1377
+ server.tool(
1378
+ "list-public-endpoints",
1379
+ "List Runpod Public Endpoints \u2014 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.",
1380
+ {
1381
+ ...listPaginationParams,
1382
+ searchTerm: import_zod5.z.string().optional().describe(
1383
+ "Case-insensitive search across display name, model name, endpoint id, description, owner, and tag (e.g. 'kimi', 'video', 'flux')"
1384
+ ),
1385
+ modality: import_zod5.z.string().optional().describe(
1386
+ "Filter by modality/category (the metadata source field, e.g. 'language', 'image', 'video', 'audio')"
1387
+ ),
1388
+ owner: import_zod5.z.string().optional().describe(
1389
+ "Filter by model owner (e.g. 'moonshot', 'minimax', 'google')"
1390
+ ),
1391
+ includeOffline: import_zod5.z.boolean().optional().describe(
1392
+ "Include endpoints that are not currently live (isLive:false). Default false \u2014 only live endpoints are listed."
1393
+ )
1394
+ },
1395
+ { title: "List public endpoints", ...READ_ONLY },
1396
+ async (params) => {
1397
+ const data = await graphql(`
1398
+ query {
1399
+ allAiApiPublicConfigs {
1400
+ id
1401
+ aiApiId
1402
+ modelName
1403
+ displayName
1404
+ description
1405
+ metadata
1406
+ isLive
1407
+ createdAt
1408
+ updatedAt
1409
+ }
1410
+ }
1411
+ `);
1412
+ let configs = data.allAiApiPublicConfigs.map((c) => ({
1413
+ config: c,
1414
+ meta: parseMetadata(c.metadata)
1415
+ }));
1416
+ if (!params.includeOffline) {
1417
+ configs = configs.filter(({ config }) => config.isLive);
1418
+ }
1419
+ if (params.modality) {
1420
+ const term = params.modality.toLowerCase();
1421
+ configs = configs.filter(
1422
+ ({ meta }) => (meta.source ?? "").toLowerCase() === term
1423
+ );
1424
+ }
1425
+ if (params.owner) {
1426
+ const term = params.owner.toLowerCase();
1427
+ configs = configs.filter(
1428
+ ({ meta }) => (meta.owner ?? "").toLowerCase() === term
1429
+ );
1430
+ }
1431
+ if (params.searchTerm) {
1432
+ const term = params.searchTerm.toLowerCase();
1433
+ configs = configs.filter(
1434
+ ({ config, meta }) => [
1435
+ config.displayName,
1436
+ config.modelName,
1437
+ config.aiApiId,
1438
+ config.description,
1439
+ meta.owner,
1440
+ meta.tag,
1441
+ meta.source
1442
+ ].some((field) => (field ?? "").toLowerCase().includes(term))
1443
+ );
1444
+ }
1445
+ configs = [...configs].sort(
1446
+ (a, b) => a.config.displayName.localeCompare(b.config.displayName)
1447
+ );
1448
+ const result = configs.map(({ config, meta }) => ({
1449
+ endpointId: config.aiApiId,
1450
+ displayName: config.displayName,
1451
+ modelName: config.modelName,
1452
+ description: config.description,
1453
+ modality: meta.source,
1454
+ tag: meta.tag,
1455
+ owner: meta.owner,
1456
+ pricing: meta.priceString,
1457
+ isLive: config.isLive,
1458
+ baseUrl: `https://api.runpod.ai/v2/${config.aiApiId}`,
1459
+ createdAt: config.createdAt,
1460
+ updatedAt: config.updatedAt
1461
+ }));
1462
+ return capListResult(result, {
1463
+ limit: params.limit,
1464
+ cursor: params.cursor
1465
+ });
1466
+ }
1467
+ );
1468
+ }
1469
+
1470
+ // src/tools/endpoint-gpus.ts
1471
+ var import_zod6 = require("zod");
1472
+ function registerEndpointGpuTools(server, rt) {
1473
+ const { graphqlAuthed, jsonReply: jsonReply2 } = rt;
1474
+ server.tool(
1475
+ "set-endpoint-gpus",
1476
+ "Set which GPUs a Serverless endpoint's workers run on \u2014 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.",
1477
+ {
1478
+ endpointId: import_zod6.z.string().describe("ID of the Serverless endpoint to update"),
1479
+ gpuIds: import_zod6.z.string().optional().describe(
1480
+ "Raw gpuIds string, e.g. 'ADA_24' or 'AMPERE_16,-NVIDIA RTX A4500'. Takes precedence over pools/excludeGpuTypeIds."
1481
+ ),
1482
+ pools: import_zod6.z.array(import_zod6.z.string()).optional().describe(
1483
+ "GPU pool names workers may use (e.g. ['ADA_80_PRO','AMPERE_80']). The pool field from list-gpu-types."
1484
+ ),
1485
+ excludeGpuTypeIds: import_zod6.z.array(import_zod6.z.string()).optional().describe(
1486
+ "GPU type ids to exclude from the allowed pools (e.g. ['NVIDIA H100 NVL']). Use with pools to pin specific SKUs."
1487
+ ),
1488
+ gpuCount: import_zod6.z.number().int().positive().optional().describe("GPUs per worker. Omit to keep the current value."),
1489
+ minCudaVersion: import_zod6.z.string().optional().describe(
1490
+ "Minimum host CUDA version workers may run on (e.g. '12.4'). Omit to keep the current value."
1491
+ ),
1492
+ allowedCudaVersions: import_zod6.z.string().optional().describe(
1493
+ "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 \u2014 a narrow list can leave an endpoint unable to schedule workers."
1494
+ )
1495
+ },
1496
+ { title: "Set endpoint GPUs", ...WRITE, idempotentHint: true },
1497
+ async (params) => {
1498
+ const gpuIds = params.gpuIds ?? (params.pools && params.pools.length > 0 ? [
1499
+ ...params.pools,
1500
+ ...(params.excludeGpuTypeIds ?? []).map((id) => `-${id}`)
1501
+ ].join(",") : void 0);
1502
+ if (!gpuIds) {
1503
+ return jsonReply2({
1504
+ error: "Provide gpuIds (raw string) or pools (with optional excludeGpuTypeIds). See list-gpu-types for pool names and GPU type ids."
1505
+ });
1506
+ }
1507
+ const data = await graphqlAuthed(`
1508
+ query {
1509
+ myself {
1510
+ endpoints {
1511
+ id
1512
+ name
1513
+ gpuIds
1514
+ gpuCount
1515
+ workersMin
1516
+ workersMax
1517
+ idleTimeout
1518
+ scalerType
1519
+ scalerValue
1520
+ executionTimeoutMs
1521
+ flashBootType
1522
+ type
1523
+ locations
1524
+ templateId
1525
+ allowedCudaVersions
1526
+ minCudaVersion
1527
+ compliance
1528
+ modelReferences
1529
+ networkVolumeIds {
1530
+ networkVolumeId
1531
+ dataCenterId
1532
+ }
1533
+ }
1534
+ }
1535
+ }
1536
+ `);
1537
+ const current = data.myself.endpoints.find(
1538
+ (e) => e.id === params.endpointId
1539
+ );
1540
+ if (!current) {
1541
+ return jsonReply2({
1542
+ error: `No Serverless endpoint found with id "${params.endpointId}". Use list-endpoints to see your endpoints.`
1543
+ });
1544
+ }
1545
+ const input = {
1546
+ id: current.id,
1547
+ name: current.name,
1548
+ gpuIds,
1549
+ gpuCount: params.gpuCount ?? current.gpuCount,
1550
+ workersMin: current.workersMin,
1551
+ workersMax: current.workersMax,
1552
+ idleTimeout: current.idleTimeout,
1553
+ scalerType: current.scalerType,
1554
+ scalerValue: current.scalerValue,
1555
+ executionTimeoutMs: current.executionTimeoutMs,
1556
+ flashBootType: current.flashBootType,
1557
+ type: current.type,
1558
+ locations: current.locations,
1559
+ networkVolumeIds: current.networkVolumeIds && current.networkVolumeIds.length > 0 ? (
1560
+ // Drop dataCenterId: NetworkVolumeIdsInput takes networkVolumeId
1561
+ // ONLY, and the read shape is rejected outright.
1562
+ current.networkVolumeIds.map((v) => ({
1563
+ networkVolumeId: v.networkVolumeId
1564
+ }))
1565
+ ) : null
1566
+ };
1567
+ for (const [key, value] of Object.entries({
1568
+ templateId: current.templateId,
1569
+ allowedCudaVersions: params.allowedCudaVersions ?? current.allowedCudaVersions,
1570
+ minCudaVersion: params.minCudaVersion ?? current.minCudaVersion,
1571
+ compliance: current.compliance,
1572
+ modelReferences: current.modelReferences
1573
+ })) {
1574
+ if (value !== null && value !== void 0) input[key] = value;
1575
+ }
1576
+ const result = await graphqlAuthed(
1577
+ `
1578
+ mutation saveEndpoint($input: EndpointInput!) {
1579
+ saveEndpoint(input: $input) {
1580
+ id
1581
+ name
1582
+ gpuIds
1583
+ gpuCount
1584
+ workersMin
1585
+ workersMax
1586
+ }
1587
+ }
1588
+ `,
1589
+ { input }
1590
+ );
1591
+ return jsonReply2({
1592
+ endpoint: result.saveEndpoint,
1593
+ previousGpuIds: current.gpuIds
1594
+ });
1595
+ }
1596
+ );
1597
+ }
1598
+
879
1599
  // src/tools/pods.ts
880
- var import_zod3 = require("zod");
1600
+ var import_zod7 = require("zod");
881
1601
  function registerPodTools(server, rt) {
882
1602
  const { jsonReply: jsonReply2, callRestUrl, backendFor, podAction, env } = rt;
883
1603
  server.tool(
@@ -885,12 +1605,12 @@ function registerPodTools(server, rt) {
885
1605
  "List your pods (running and stopped) with optional filters \u2014 compute type, GPU type, data center, name \u2014 plus machine/network-volume expansion. Paginated via limit/cursor.",
886
1606
  {
887
1607
  ...listPaginationParams,
888
- computeType: import_zod3.z.enum(["GPU", "CPU"]).optional().describe("Filter to only GPU or only CPU Pods"),
889
- gpuTypeId: import_zod3.z.array(import_zod3.z.string()).optional().describe("Filter to Pods with any of the listed GPU types"),
890
- dataCenterId: import_zod3.z.array(import_zod3.z.string()).optional().describe("Filter to Pods in any of the provided data centers"),
891
- name: import_zod3.z.string().optional().describe("Filter to Pods with the provided name"),
892
- includeMachine: import_zod3.z.boolean().optional().describe("Include information about the machine"),
893
- includeNetworkVolume: import_zod3.z.boolean().optional().describe("Include information about attached network volumes")
1608
+ computeType: import_zod7.z.enum(["GPU", "CPU"]).optional().describe("Filter to only GPU or only CPU Pods"),
1609
+ gpuTypeId: import_zod7.z.array(import_zod7.z.string()).optional().describe("Filter to Pods with any of the listed GPU types"),
1610
+ dataCenterId: import_zod7.z.array(import_zod7.z.string()).optional().describe("Filter to Pods in any of the provided data centers"),
1611
+ name: import_zod7.z.string().optional().describe("Filter to Pods with the provided name"),
1612
+ includeMachine: import_zod7.z.boolean().optional().describe("Include information about the machine"),
1613
+ includeNetworkVolume: import_zod7.z.boolean().optional().describe("Include information about attached network volumes")
894
1614
  },
895
1615
  { title: "List pods", ...READ_ONLY },
896
1616
  async (params) => {
@@ -934,9 +1654,9 @@ function registerPodTools(server, rt) {
934
1654
  "get-pod",
935
1655
  "Get full details for one pod by id, optionally expanding machine and attached network-volume info.",
936
1656
  {
937
- podId: import_zod3.z.string().describe("ID of the pod to retrieve"),
938
- includeMachine: import_zod3.z.boolean().optional().describe("Include information about the machine"),
939
- includeNetworkVolume: import_zod3.z.boolean().optional().describe("Include information about attached network volumes")
1657
+ podId: import_zod7.z.string().describe("ID of the pod to retrieve"),
1658
+ includeMachine: import_zod7.z.boolean().optional().describe("Include information about the machine"),
1659
+ includeNetworkVolume: import_zod7.z.boolean().optional().describe("Include information about attached network volumes")
940
1660
  },
941
1661
  { title: "Get pod", ...READ_ONLY },
942
1662
  async (params) => {
@@ -964,26 +1684,44 @@ function registerPodTools(server, rt) {
964
1684
  );
965
1685
  server.tool(
966
1686
  "create-pod",
967
- '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). If the user does not specify an image, recommend the "Runpod Pytorch 2.8.0" image (runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404) as the default \u2014 it has the most up-to-date CUDA and PyTorch versions.',
1687
+ '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 \u2014 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). 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 \u2014 it has the most up-to-date CUDA and PyTorch versions.',
968
1688
  {
969
- name: import_zod3.z.string().optional().describe("Name for the pod"),
970
- imageName: import_zod3.z.string().describe("Docker image to use"),
971
- computeType: import_zod3.z.enum(["GPU", "CPU"]).optional().describe(
1689
+ name: import_zod7.z.string().optional().describe("Name for the pod"),
1690
+ imageName: import_zod7.z.string().optional().describe("Docker image to use. Optional when templateId is provided."),
1691
+ templateId: import_zod7.z.string().optional().describe(
1692
+ "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 \u2014 a template does not pin the pod compute."
1693
+ ),
1694
+ computeType: import_zod7.z.enum(["GPU", "CPU"]).optional().describe(
972
1695
  "Pod type. On v2, required when not passing gpuTypeIds. 'CPU' is served by the v1 API for now (v2 has no CPU pods yet)."
973
1696
  ),
974
- cloudType: import_zod3.z.enum(["SECURE", "COMMUNITY"]).optional().describe("SECURE or COMMUNITY cloud"),
975
- gpuTypeIds: import_zod3.z.array(import_zod3.z.string()).optional().describe("List of acceptable GPU types"),
976
- gpuCount: import_zod3.z.number().optional().describe("Number of GPUs"),
977
- containerDiskInGb: import_zod3.z.number().optional().describe("Container disk size in GB"),
978
- volumeInGb: import_zod3.z.number().optional().describe("Volume size in GB"),
979
- volumeMountPath: import_zod3.z.string().optional().describe("Path to mount the volume"),
980
- ports: import_zod3.z.array(import_zod3.z.string()).optional().describe("Ports to expose (e.g., '8888/http', '22/tcp')"),
981
- env: import_zod3.z.record(import_zod3.z.string()).optional().describe("Environment variables"),
982
- dataCenterIds: import_zod3.z.array(import_zod3.z.string()).optional().describe("List of data centers")
1697
+ cloudType: import_zod7.z.enum(["SECURE", "COMMUNITY"]).optional().describe("SECURE or COMMUNITY cloud"),
1698
+ gpuTypeIds: import_zod7.z.array(import_zod7.z.string()).optional().describe("List of acceptable GPU types"),
1699
+ gpuCount: import_zod7.z.number().optional().describe("Number of GPUs"),
1700
+ containerDiskInGb: import_zod7.z.number().optional().describe("Container disk size in GB"),
1701
+ volumeInGb: import_zod7.z.number().optional().describe("Volume size in GB"),
1702
+ volumeMountPath: import_zod7.z.string().optional().describe("Path to mount the volume"),
1703
+ ports: import_zod7.z.array(import_zod7.z.string()).optional().describe("Ports to expose (e.g., '8888/http', '22/tcp')"),
1704
+ env: import_zod7.z.record(import_zod7.z.string()).optional().describe("Environment variables"),
1705
+ containerRegistryAuthId: import_zod7.z.string().optional().describe(
1706
+ "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\u2019s inherited registry)."
1707
+ ),
1708
+ dataCenterIds: import_zod7.z.array(import_zod7.z.string()).optional().describe("List of data centers")
983
1709
  },
984
1710
  { title: "Create pod", ...WRITE },
985
1711
  async (params) => {
986
1712
  const backend = backendFor("pods");
1713
+ if (params.templateId && backend.version !== "v2") {
1714
+ return jsonReply2({
1715
+ error: "Deploying a pod from a templateId is only supported on the v2 REST API. Set RUNPOD_REST_VERSION=v2, or pass imageName instead.",
1716
+ status: 501
1717
+ });
1718
+ }
1719
+ if (!params.imageName && !params.templateId) {
1720
+ return jsonReply2({
1721
+ error: "Provide imageName, or templateId to deploy from an existing template.",
1722
+ status: 400
1723
+ });
1724
+ }
987
1725
  const hasGpuTypes = (params.gpuTypeIds?.length ?? 0) > 0;
988
1726
  const hasGpuCount = (params.gpuCount ?? 0) > 0;
989
1727
  const wantsCpu = params.computeType === "CPU";
@@ -1008,6 +1746,12 @@ function registerPodTools(server, rt) {
1008
1746
  });
1009
1747
  }
1010
1748
  if (wantsCpu) {
1749
+ if (params.templateId) {
1750
+ return jsonReply2({
1751
+ error: "Template-based deploy is currently supported only for GPU pods on the v2 API. Create the CPU pod with an explicit imageName instead.",
1752
+ status: 400
1753
+ });
1754
+ }
1011
1755
  try {
1012
1756
  const result = await callRestUrl(
1013
1757
  `${restV1Base(env)}/pods`,
@@ -1032,7 +1776,24 @@ function registerPodTools(server, rt) {
1032
1776
  }
1033
1777
  }
1034
1778
  }
1035
- const body = backend.mapCreate(params);
1779
+ let body = backend.mapCreate(params);
1780
+ if (params.templateId) {
1781
+ let template;
1782
+ try {
1783
+ template = await callRestUrl(
1784
+ `${restV2Base(env)}/templates/${params.templateId}`
1785
+ );
1786
+ } catch (error) {
1787
+ if (error instanceof HttpError) {
1788
+ return jsonReply2({
1789
+ error: `Failed to load template ${params.templateId}: ${error.message}`,
1790
+ status: error.status
1791
+ });
1792
+ }
1793
+ throw error;
1794
+ }
1795
+ body = { ...podBodyFromTemplate(template), ...body };
1796
+ }
1036
1797
  try {
1037
1798
  const result = await callRestUrl(
1038
1799
  `${backend.base}${backend.list}`,
@@ -1047,11 +1808,8 @@ function registerPodTools(server, rt) {
1047
1808
  }
1048
1809
  return jsonReply2(result);
1049
1810
  } catch (error) {
1050
- if (error instanceof HttpError && error.status === 501) {
1051
- return jsonReply2({
1052
- error: "CPU pods are not yet supported on the v2 REST API. Use a GPU pod, or create the CPU pod via the v1 API (set RUNPOD_REST_VERSION=v1).",
1053
- status: 501
1054
- });
1811
+ if (error instanceof HttpError) {
1812
+ return jsonReply2({ error: error.message, status: error.status });
1055
1813
  }
1056
1814
  throw error;
1057
1815
  }
@@ -1061,14 +1819,14 @@ function registerPodTools(server, rt) {
1061
1819
  "update-pod",
1062
1820
  "Update a pod's mutable fields (name, image, container disk, volume, ports, env). Only the fields you provide change.",
1063
1821
  {
1064
- podId: import_zod3.z.string().describe("ID of the pod to update"),
1065
- name: import_zod3.z.string().optional().describe("New name for the pod"),
1066
- imageName: import_zod3.z.string().optional().describe("New Docker image"),
1067
- containerDiskInGb: import_zod3.z.number().optional().describe("New container disk size in GB"),
1068
- volumeInGb: import_zod3.z.number().optional().describe("New volume size in GB"),
1069
- volumeMountPath: import_zod3.z.string().optional().describe("New path to mount the volume"),
1070
- ports: import_zod3.z.array(import_zod3.z.string()).optional().describe("New ports to expose"),
1071
- env: import_zod3.z.record(import_zod3.z.string()).optional().describe("New environment variables")
1822
+ podId: import_zod7.z.string().describe("ID of the pod to update"),
1823
+ name: import_zod7.z.string().optional().describe("New name for the pod"),
1824
+ imageName: import_zod7.z.string().optional().describe("New Docker image"),
1825
+ containerDiskInGb: import_zod7.z.number().optional().describe("New container disk size in GB"),
1826
+ volumeInGb: import_zod7.z.number().optional().describe("New volume size in GB"),
1827
+ volumeMountPath: import_zod7.z.string().optional().describe("New path to mount the volume"),
1828
+ ports: import_zod7.z.array(import_zod7.z.string()).optional().describe("New ports to expose"),
1829
+ env: import_zod7.z.record(import_zod7.z.string()).optional().describe("New environment variables")
1072
1830
  },
1073
1831
  { title: "Update pod", ...WRITE, idempotentHint: true },
1074
1832
  async (params) => {
@@ -1087,7 +1845,7 @@ function registerPodTools(server, rt) {
1087
1845
  "start-pod",
1088
1846
  "Start a stopped pod (resumes the same pod and its billing).",
1089
1847
  {
1090
- podId: import_zod3.z.string().describe("ID of the pod to start")
1848
+ podId: import_zod7.z.string().describe("ID of the pod to start")
1091
1849
  },
1092
1850
  { title: "Start pod", ...WRITE, idempotentHint: true },
1093
1851
  async (params) => {
@@ -1099,7 +1857,7 @@ function registerPodTools(server, rt) {
1099
1857
  "stop-pod",
1100
1858
  "Stop a running pod. The pod and its disk persist; GPU billing stops.",
1101
1859
  {
1102
- podId: import_zod3.z.string().describe("ID of the pod to stop")
1860
+ podId: import_zod7.z.string().describe("ID of the pod to stop")
1103
1861
  },
1104
1862
  { title: "Stop pod", ...WRITE, idempotentHint: true },
1105
1863
  async (params) => {
@@ -1111,7 +1869,7 @@ function registerPodTools(server, rt) {
1111
1869
  "restart-pod",
1112
1870
  "Restart a running pod (single PodAction call). v2-only \u2014 on the v1 API returns a 501 notice (stop then start the pod instead).",
1113
1871
  {
1114
- podId: import_zod3.z.string().describe("ID of the pod to restart")
1872
+ podId: import_zod7.z.string().describe("ID of the pod to restart")
1115
1873
  },
1116
1874
  { title: "Restart pod", ...WRITE },
1117
1875
  async (params) => {
@@ -1126,11 +1884,29 @@ function registerPodTools(server, rt) {
1126
1884
  return jsonReply2(result);
1127
1885
  }
1128
1886
  );
1887
+ server.tool(
1888
+ "stream-pod-logs",
1889
+ "Fetch a bounded snapshot of a pod's live logs (container and/or system) via Server-Sent Events. v2-only \u2014 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).",
1890
+ {
1891
+ podId: import_zod7.z.string().describe("ID of the pod whose logs to stream"),
1892
+ ...logStreamParams
1893
+ },
1894
+ { title: "Stream pod logs", ...READ_ONLY },
1895
+ (params) => streamLogsReply(
1896
+ rt,
1897
+ {
1898
+ name: "stream-pod-logs",
1899
+ resource: "pods",
1900
+ logsUrl: (backend) => `${backend.base}${backend.get(encodeURIComponent(params.podId))}/logs`
1901
+ },
1902
+ params
1903
+ )
1904
+ );
1129
1905
  server.tool(
1130
1906
  "delete-pod",
1131
1907
  "Permanently delete a pod and its data. This cannot be undone.",
1132
1908
  {
1133
- podId: import_zod3.z.string().describe("ID of the pod to delete")
1909
+ podId: import_zod7.z.string().describe("ID of the pod to delete")
1134
1910
  },
1135
1911
  { title: "Delete pod", ...DESTRUCTIVE },
1136
1912
  async (params) => {
@@ -1145,7 +1921,7 @@ function registerPodTools(server, rt) {
1145
1921
  }
1146
1922
 
1147
1923
  // src/tools/endpoints.ts
1148
- var import_zod4 = require("zod");
1924
+ var import_zod8 = require("zod");
1149
1925
  function registerEndpointTools(server, rt) {
1150
1926
  const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
1151
1927
  server.tool(
@@ -1153,8 +1929,8 @@ function registerEndpointTools(server, rt) {
1153
1929
  "List your Serverless endpoints, optionally expanding template and worker details (v1 only). Paginated via limit/cursor.",
1154
1930
  {
1155
1931
  ...listPaginationParams,
1156
- includeTemplate: import_zod4.z.boolean().optional().describe("Include template information (v1 only)"),
1157
- includeWorkers: import_zod4.z.boolean().optional().describe("Include information about workers (v1 only)")
1932
+ includeTemplate: import_zod8.z.boolean().optional().describe("Include template information (v1 only)"),
1933
+ includeWorkers: import_zod8.z.boolean().optional().describe("Include information about workers (v1 only)")
1158
1934
  },
1159
1935
  { title: "List endpoints", ...READ_ONLY },
1160
1936
  async (params) => {
@@ -1187,9 +1963,9 @@ function registerEndpointTools(server, rt) {
1187
1963
  "get-endpoint",
1188
1964
  "Get one Serverless endpoint by id, optionally expanding template and worker details (v1 only).",
1189
1965
  {
1190
- endpointId: import_zod4.z.string().describe("ID of the endpoint to retrieve"),
1191
- includeTemplate: import_zod4.z.boolean().optional().describe("Include template information (v1 only)"),
1192
- includeWorkers: import_zod4.z.boolean().optional().describe("Include information about workers (v1 only)")
1966
+ endpointId: import_zod8.z.string().describe("ID of the endpoint to retrieve"),
1967
+ includeTemplate: import_zod8.z.boolean().optional().describe("Include template information (v1 only)"),
1968
+ includeWorkers: import_zod8.z.boolean().optional().describe("Include information about workers (v1 only)")
1193
1969
  },
1194
1970
  { title: "Get endpoint", ...READ_ONLY },
1195
1971
  async (params) => {
@@ -1219,32 +1995,32 @@ function registerEndpointTools(server, rt) {
1219
1995
  "create-endpoint",
1220
1996
  "Create a Serverless endpoint. On v2 (default), pass an inline config: imageName + gpuPoolIds (GPU pool names from list-gpu-types \u2014 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).",
1221
1997
  {
1222
- name: import_zod4.z.string().optional().describe("Name for the endpoint"),
1998
+ name: import_zod8.z.string().optional().describe("Name for the endpoint"),
1223
1999
  // --- v2 inline-config fields ---
1224
- imageName: import_zod4.z.string().optional().describe("Docker image (v2). Required on v2 instead of a templateId."),
1225
- gpuPoolIds: import_zod4.z.array(import_zod4.z.string()).optional().describe(
2000
+ imageName: import_zod8.z.string().optional().describe("Docker image (v2). Required on v2 instead of a templateId."),
2001
+ gpuPoolIds: import_zod8.z.array(import_zod8.z.string()).optional().describe(
1226
2002
  'GPU pool names (v2, required). The `pool` field from list-gpu-types, e.g. ["AMPERE_80"]. NOT GPU type ids.'
1227
2003
  ),
1228
- gpuCount: import_zod4.z.number().optional().describe("GPUs per worker (v2)"),
1229
- args: import_zod4.z.string().optional().describe("Container start command/args (v2)"),
1230
- containerDiskInGb: import_zod4.z.number().optional().describe("Container disk size in GB (v2)"),
1231
- ports: import_zod4.z.array(import_zod4.z.string()).optional().describe("Ports to expose (v2), e.g. ['8000/http']"),
1232
- env: import_zod4.z.record(import_zod4.z.string()).optional().describe("Environment variables (v2)"),
1233
- containerRegistryAuthId: import_zod4.z.string().optional().describe("Container registry auth id for a private image (v2)"),
1234
- networkVolumeIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("Network volume ids to attach (v2)"),
1235
- executionTimeoutMs: import_zod4.z.number().optional().describe("Per-job execution timeout in ms (v2)"),
1236
- flashboot: import_zod4.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("FlashBoot mode (v2)"),
2004
+ gpuCount: import_zod8.z.number().optional().describe("GPUs per worker (v2)"),
2005
+ args: import_zod8.z.string().optional().describe("Container start command/args (v2)"),
2006
+ containerDiskInGb: import_zod8.z.number().optional().describe("Container disk size in GB (v2)"),
2007
+ ports: import_zod8.z.array(import_zod8.z.string()).optional().describe("Ports to expose (v2), e.g. ['8000/http']"),
2008
+ env: import_zod8.z.record(import_zod8.z.string()).optional().describe("Environment variables (v2)"),
2009
+ containerRegistryAuthId: import_zod8.z.string().optional().describe("Container registry auth id for a private image (v2)"),
2010
+ networkVolumeIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("Network volume ids to attach (v2)"),
2011
+ executionTimeoutMs: import_zod8.z.number().optional().describe("Per-job execution timeout in ms (v2)"),
2012
+ flashboot: import_zod8.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("FlashBoot mode (v2)"),
1237
2013
  // --- v1 template-based fields ---
1238
- templateId: import_zod4.z.string().optional().describe("Template ID (v1). Required on v1."),
1239
- computeType: import_zod4.z.enum(["GPU", "CPU"]).optional().describe("GPU or CPU endpoint (v1)"),
1240
- gpuTypeIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("List of acceptable GPU types (v1)"),
2014
+ templateId: import_zod8.z.string().optional().describe("Template ID (v1). Required on v1."),
2015
+ computeType: import_zod8.z.enum(["GPU", "CPU"]).optional().describe("GPU or CPU endpoint (v1)"),
2016
+ gpuTypeIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("List of acceptable GPU types (v1)"),
1241
2017
  // --- shared autoscaling fields ---
1242
- workersMin: import_zod4.z.number().optional().describe("Minimum number of workers"),
1243
- workersMax: import_zod4.z.number().optional().describe("Maximum number of workers"),
1244
- scalerType: import_zod4.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Autoscaler type"),
1245
- scalerValue: import_zod4.z.number().optional().describe("Autoscaler target value"),
1246
- idleTimeout: import_zod4.z.number().optional().describe("Idle timeout in seconds before scaling a worker down"),
1247
- dataCenterIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("List of preferred data centers")
2018
+ workersMin: import_zod8.z.number().optional().describe("Minimum number of workers"),
2019
+ workersMax: import_zod8.z.number().optional().describe("Maximum number of workers"),
2020
+ scalerType: import_zod8.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Autoscaler type"),
2021
+ scalerValue: import_zod8.z.number().optional().describe("Autoscaler target value"),
2022
+ idleTimeout: import_zod8.z.number().optional().describe("Idle timeout in seconds before scaling a worker down"),
2023
+ dataCenterIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("List of preferred data centers")
1248
2024
  },
1249
2025
  { title: "Create endpoint", ...WRITE },
1250
2026
  async (params) => {
@@ -1307,25 +2083,25 @@ function registerEndpointTools(server, rt) {
1307
2083
  "update-endpoint",
1308
2084
  "Update a Serverless endpoint's config. On v2 you can change image/disk/env/ports/registry/workers/scaling/networkVolumes/timeout/flashboot; on v1, scaling fields (worker min/max, idle timeout, scaler type/value, name). Only provided fields change.",
1309
2085
  {
1310
- endpointId: import_zod4.z.string().describe("ID of the endpoint to update"),
1311
- name: import_zod4.z.string().optional().describe("New name for the endpoint"),
1312
- workersMin: import_zod4.z.number().optional().describe("New minimum number of workers"),
1313
- workersMax: import_zod4.z.number().optional().describe("New maximum number of workers"),
1314
- idleTimeout: import_zod4.z.number().optional().describe("New idle timeout in seconds"),
1315
- scalerType: import_zod4.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Scaler type"),
1316
- scalerValue: import_zod4.z.number().optional().describe("Scaler value"),
2086
+ endpointId: import_zod8.z.string().describe("ID of the endpoint to update"),
2087
+ name: import_zod8.z.string().optional().describe("New name for the endpoint"),
2088
+ workersMin: import_zod8.z.number().optional().describe("New minimum number of workers"),
2089
+ workersMax: import_zod8.z.number().optional().describe("New maximum number of workers"),
2090
+ idleTimeout: import_zod8.z.number().optional().describe("New idle timeout in seconds"),
2091
+ scalerType: import_zod8.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Scaler type"),
2092
+ scalerValue: import_zod8.z.number().optional().describe("Scaler value"),
1317
2093
  // --- v2-only inline-config fields ---
1318
- imageName: import_zod4.z.string().optional().describe("New Docker image (v2)"),
1319
- gpuPoolIds: import_zod4.z.array(import_zod4.z.string()).optional().describe('New GPU pool names (v2), e.g. ["AMPERE_80"]'),
1320
- gpuCount: import_zod4.z.number().optional().describe("New GPUs per worker (v2)"),
1321
- args: import_zod4.z.string().optional().describe("New container args (v2)"),
1322
- containerDiskInGb: import_zod4.z.number().optional().describe("New container disk size in GB (v2)"),
1323
- ports: import_zod4.z.array(import_zod4.z.string()).optional().describe("New ports (v2)"),
1324
- env: import_zod4.z.record(import_zod4.z.string()).optional().describe("New environment variables (v2)"),
1325
- containerRegistryAuthId: import_zod4.z.string().optional().describe("New container registry auth id (v2)"),
1326
- networkVolumeIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("New network volume ids (v2)"),
1327
- executionTimeoutMs: import_zod4.z.number().optional().describe("New per-job execution timeout in ms (v2)"),
1328
- flashboot: import_zod4.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("New FlashBoot mode (v2)")
2094
+ imageName: import_zod8.z.string().optional().describe("New Docker image (v2)"),
2095
+ gpuPoolIds: import_zod8.z.array(import_zod8.z.string()).optional().describe('New GPU pool names (v2), e.g. ["AMPERE_80"]'),
2096
+ gpuCount: import_zod8.z.number().optional().describe("New GPUs per worker (v2)"),
2097
+ args: import_zod8.z.string().optional().describe("New container args (v2)"),
2098
+ containerDiskInGb: import_zod8.z.number().optional().describe("New container disk size in GB (v2)"),
2099
+ ports: import_zod8.z.array(import_zod8.z.string()).optional().describe("New ports (v2)"),
2100
+ env: import_zod8.z.record(import_zod8.z.string()).optional().describe("New environment variables (v2)"),
2101
+ containerRegistryAuthId: import_zod8.z.string().optional().describe("New container registry auth id (v2)"),
2102
+ networkVolumeIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("New network volume ids (v2)"),
2103
+ executionTimeoutMs: import_zod8.z.number().optional().describe("New per-job execution timeout in ms (v2)"),
2104
+ flashboot: import_zod8.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("New FlashBoot mode (v2)")
1329
2105
  },
1330
2106
  { title: "Update endpoint", ...WRITE, idempotentHint: true },
1331
2107
  async (params) => {
@@ -1344,7 +2120,7 @@ function registerEndpointTools(server, rt) {
1344
2120
  "delete-endpoint",
1345
2121
  "Permanently delete a Serverless endpoint. This cannot be undone.",
1346
2122
  {
1347
- endpointId: import_zod4.z.string().describe("ID of the endpoint to delete")
2123
+ endpointId: import_zod8.z.string().describe("ID of the endpoint to delete")
1348
2124
  },
1349
2125
  { title: "Delete endpoint", ...DESTRUCTIVE },
1350
2126
  async (params) => {
@@ -1356,12 +2132,39 @@ function registerEndpointTools(server, rt) {
1356
2132
  return jsonReply2(result);
1357
2133
  }
1358
2134
  );
2135
+ server.tool(
2136
+ "list-endpoint-releases",
2137
+ "List a Serverless endpoint's release history and current rollout status (workers on the latest version). v2-only \u2014 returns a 501 notice on the v1 API. Paginated via limit/cursor.",
2138
+ {
2139
+ ...listPaginationParams,
2140
+ endpointId: import_zod8.z.string().describe("ID of the Serverless endpoint whose releases to list")
2141
+ },
2142
+ { title: "List endpoint releases", ...READ_ONLY },
2143
+ async (params) => {
2144
+ const backend = backendFor("endpoints");
2145
+ if (backend.version === "v1") {
2146
+ return jsonReply2({
2147
+ error: "list-endpoint-releases is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
2148
+ status: 501
2149
+ });
2150
+ }
2151
+ const raw = await callRestUrl(
2152
+ `${backend.base}/serverless/${params.endpointId}/releases`
2153
+ );
2154
+ const releases = Array.isArray(raw?.releases) ? raw.releases : [];
2155
+ return capListResult(
2156
+ releases,
2157
+ { limit: params.limit, cursor: params.cursor },
2158
+ { rollout: raw?.rollout, endpointVersion: raw?.endpointVersion }
2159
+ );
2160
+ }
2161
+ );
1359
2162
  server.tool(
1360
2163
  "list-endpoint-workers",
1361
2164
  "List the workers backing a Serverless endpoint, with their status and an aggregate summary. v2-only \u2014 returns a 501 notice on the v1 API. Paginated via limit/cursor.",
1362
2165
  {
1363
2166
  ...listPaginationParams,
1364
- endpointId: import_zod4.z.string().describe("ID of the Serverless endpoint whose workers to list")
2167
+ endpointId: import_zod8.z.string().describe("ID of the Serverless endpoint whose workers to list")
1365
2168
  },
1366
2169
  { title: "List endpoint workers", ...READ_ONLY },
1367
2170
  async (params) => {
@@ -1382,30 +2185,51 @@ function registerEndpointTools(server, rt) {
1382
2185
  );
1383
2186
  }
1384
2187
  );
2188
+ server.tool(
2189
+ "stream-worker-logs",
2190
+ "Fetch a bounded snapshot of a serverless worker's live logs (container and/or system) via Server-Sent Events. v2-only \u2014 returns a 501 notice on the v1 API. Get the workerId from list-endpoint-workers. 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).",
2191
+ {
2192
+ endpointId: import_zod8.z.string().describe("ID of the Serverless endpoint the worker belongs to"),
2193
+ workerId: import_zod8.z.string().describe(
2194
+ "ID of the worker whose logs to stream (from list-endpoint-workers)"
2195
+ ),
2196
+ ...logStreamParams
2197
+ },
2198
+ { title: "Stream worker logs", ...READ_ONLY },
2199
+ (params) => streamLogsReply(
2200
+ rt,
2201
+ {
2202
+ name: "stream-worker-logs",
2203
+ resource: "workers",
2204
+ logsUrl: (backend) => `${backend.base}/serverless/${encodeURIComponent(params.endpointId)}/workers/${encodeURIComponent(params.workerId)}/logs`
2205
+ },
2206
+ params
2207
+ )
2208
+ );
1385
2209
  }
1386
2210
 
1387
2211
  // src/tools/jobs.ts
1388
- var import_zod5 = require("zod");
2212
+ var import_zod9 = require("zod");
1389
2213
  function registerJobTools(server, rt) {
1390
2214
  const { jsonReply: jsonReply2, serverlessRequest } = rt;
1391
- const endpointIdSchema = import_zod5.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid endpoint ID format");
1392
- const jobIdSchema = import_zod5.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid job ID format");
1393
- const inputSchema = import_zod5.z.record(import_zod5.z.unknown()).describe(
2215
+ const endpointIdSchema = import_zod9.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid endpoint ID format");
2216
+ const jobIdSchema = import_zod9.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid job ID format");
2217
+ const inputSchema = import_zod9.z.record(import_zod9.z.unknown()).describe(
1394
2218
  "Input payload for the worker handler. The expected fields depend on the deployed model or worker."
1395
2219
  );
1396
- const webhookSchema = import_zod5.z.string().url().optional().describe(
2220
+ const webhookSchema = import_zod9.z.string().url().optional().describe(
1397
2221
  "Webhook URL to receive job completion notifications instead of polling"
1398
2222
  );
1399
- const policySchema = import_zod5.z.object({
1400
- executionTimeout: import_zod5.z.number().optional().describe("Maximum execution time in milliseconds"),
1401
- lowPriority: import_zod5.z.boolean().optional().describe("Submit as a low-priority job"),
1402
- ttl: import_zod5.z.number().optional().describe("Time-to-live for the job result in milliseconds")
2223
+ const policySchema = import_zod9.z.object({
2224
+ executionTimeout: import_zod9.z.number().optional().describe("Maximum execution time in milliseconds"),
2225
+ lowPriority: import_zod9.z.boolean().optional().describe("Submit as a low-priority job"),
2226
+ ttl: import_zod9.z.number().optional().describe("Time-to-live for the job result in milliseconds")
1403
2227
  }).optional().describe("Execution policy options");
1404
- const s3ConfigSchema = import_zod5.z.object({
1405
- accessId: import_zod5.z.string().describe("S3 access key ID"),
1406
- accessSecret: import_zod5.z.string().describe("S3 secret access key"),
1407
- bucketName: import_zod5.z.string().describe("S3 bucket name"),
1408
- endpointUrl: import_zod5.z.string().describe("S3 endpoint URL")
2228
+ const s3ConfigSchema = import_zod9.z.object({
2229
+ accessId: import_zod9.z.string().describe("S3 access key ID"),
2230
+ accessSecret: import_zod9.z.string().describe("S3 secret access key"),
2231
+ bucketName: import_zod9.z.string().describe("S3 bucket name"),
2232
+ endpointUrl: import_zod9.z.string().describe("S3 endpoint URL")
1409
2233
  }).optional().describe("S3-compatible storage config for large outputs");
1410
2234
  server.tool(
1411
2235
  "run-endpoint",
@@ -1439,7 +2263,7 @@ function registerJobTools(server, rt) {
1439
2263
  "ID of the Serverless endpoint to run synchronously"
1440
2264
  ),
1441
2265
  input: inputSchema,
1442
- wait: import_zod5.z.number().min(1e3).max(3e5).optional().describe(
2266
+ wait: import_zod9.z.number().min(1e3).max(3e5).optional().describe(
1443
2267
  "How long in milliseconds the server should wait for a result before returning a job ID to poll (1000\u2013300000). Defaults to 90000 (90 seconds)."
1444
2268
  ),
1445
2269
  webhook: webhookSchema,
@@ -1608,7 +2432,7 @@ function registerJobTools(server, rt) {
1608
2432
  }
1609
2433
 
1610
2434
  // src/tools/templates.ts
1611
- var import_zod6 = require("zod");
2435
+ var import_zod10 = require("zod");
1612
2436
  function registerTemplateTools(server, rt) {
1613
2437
  const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
1614
2438
  server.tool(
@@ -1616,9 +2440,9 @@ function registerTemplateTools(server, rt) {
1616
2440
  `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) \u2014 it has the latest CUDA and PyTorch versions.`,
1617
2441
  {
1618
2442
  ...listPaginationParams,
1619
- includeRunpodTemplates: import_zod6.z.boolean().optional().describe("Include official Runpod templates in the response"),
1620
- includePublicTemplates: import_zod6.z.boolean().optional().describe("Include community-made public templates in the response"),
1621
- includeEndpointBoundTemplates: import_zod6.z.boolean().optional().describe(
2443
+ includeRunpodTemplates: import_zod10.z.boolean().optional().describe("Include official Runpod templates in the response"),
2444
+ includePublicTemplates: import_zod10.z.boolean().optional().describe("Include community-made public templates in the response"),
2445
+ includeEndpointBoundTemplates: import_zod10.z.boolean().optional().describe(
1622
2446
  "Include templates bound to Serverless endpoints in the response"
1623
2447
  )
1624
2448
  },
@@ -1649,7 +2473,7 @@ function registerTemplateTools(server, rt) {
1649
2473
  "get-template",
1650
2474
  "Get one template by id (image, env, ports, disk/volume config).",
1651
2475
  {
1652
- templateId: import_zod6.z.string().describe("ID of the template to retrieve")
2476
+ templateId: import_zod10.z.string().describe("ID of the template to retrieve")
1653
2477
  },
1654
2478
  { title: "Get template", ...READ_ONLY },
1655
2479
  async (params) => {
@@ -1662,23 +2486,27 @@ function registerTemplateTools(server, rt) {
1662
2486
  );
1663
2487
  server.tool(
1664
2488
  "create-template",
1665
- "Create a reusable pod/endpoint template (image, ports, env, disk/volume). On v2 a category is required and defaults to NVIDIA.",
2489
+ "Create a reusable pod/endpoint template (image, ports, env, disk/volume). Category is optional and defaults to NVIDIA.",
1666
2490
  {
1667
- name: import_zod6.z.string().describe("Name for the template"),
1668
- imageName: import_zod6.z.string().describe("Docker image to use"),
1669
- isServerless: import_zod6.z.boolean().optional().describe("Is this a serverless template"),
1670
- ports: import_zod6.z.array(import_zod6.z.string()).optional().describe("Ports to expose"),
1671
- dockerEntrypoint: import_zod6.z.array(import_zod6.z.string()).optional().describe("Docker entrypoint commands"),
1672
- dockerStartCmd: import_zod6.z.array(import_zod6.z.string()).optional().describe("Docker start commands"),
1673
- env: import_zod6.z.record(import_zod6.z.string()).optional().describe("Environment variables"),
1674
- containerDiskInGb: import_zod6.z.number().optional().describe("Container disk size in GB"),
1675
- volumeInGb: import_zod6.z.number().optional().describe("Volume size in GB"),
1676
- volumeMountPath: import_zod6.z.string().optional().describe("Path to mount the volume"),
1677
- readme: import_zod6.z.string().optional().describe("README content in markdown format"),
1678
- category: import_zod6.z.enum(["CPU", "NVIDIA", "AMD"]).optional().describe(
1679
- "Hardware category for the template. On v2 a category is required and defaults to NVIDIA when omitted \u2014 set CPU or AMD for non-NVIDIA templates so they are not mis-categorized."
2491
+ name: import_zod10.z.string().describe("Name for the template"),
2492
+ imageName: import_zod10.z.string().describe("Docker image to use"),
2493
+ isServerless: import_zod10.z.boolean().optional().describe("Is this a serverless template"),
2494
+ ports: import_zod10.z.array(import_zod10.z.string()).optional().describe("Ports to expose"),
2495
+ dockerEntrypoint: import_zod10.z.array(import_zod10.z.string()).optional().describe(
2496
+ "Docker entrypoint commands. Note: not persisted on the v2 REST API, which has no separate entrypoint field (only a start command)."
2497
+ ),
2498
+ dockerStartCmd: import_zod10.z.array(import_zod10.z.string()).optional().describe(
2499
+ '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"] \u2192 "python -u app.py"). The join is lossy: an element that itself contains spaces is NOT preserved as one argument \u2014 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 "").'
1680
2500
  ),
1681
- containerRegistryAuthId: import_zod6.z.string().optional().describe(
2501
+ env: import_zod10.z.record(import_zod10.z.string()).optional().describe("Environment variables"),
2502
+ containerDiskInGb: import_zod10.z.number().optional().describe("Container disk size in GB"),
2503
+ volumeInGb: import_zod10.z.number().optional().describe("Volume size in GB"),
2504
+ volumeMountPath: import_zod10.z.string().optional().describe("Path to mount the volume"),
2505
+ readme: import_zod10.z.string().optional().describe("README content in markdown format"),
2506
+ category: import_zod10.z.enum(["CPU", "NVIDIA", "AMD"]).optional().describe(
2507
+ "How the template is grouped and filtered in the Runpod console \u2014 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."
2508
+ ),
2509
+ containerRegistryAuthId: import_zod10.z.string().optional().describe(
1682
2510
  "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."
1683
2511
  )
1684
2512
  },
@@ -1696,15 +2524,18 @@ function registerTemplateTools(server, rt) {
1696
2524
  );
1697
2525
  server.tool(
1698
2526
  "update-template",
1699
- "Update a template's mutable fields (name, image, ports, env, readme). Only provided fields change.",
2527
+ "Update a template's mutable fields (name, image, ports, env, start command, registry auth). Only provided fields change; omitted fields are left untouched.",
1700
2528
  {
1701
- templateId: import_zod6.z.string().describe("ID of the template to update"),
1702
- name: import_zod6.z.string().optional().describe("New name for the template"),
1703
- imageName: import_zod6.z.string().optional().describe("New Docker image"),
1704
- ports: import_zod6.z.array(import_zod6.z.string()).optional().describe("New ports to expose"),
1705
- env: import_zod6.z.record(import_zod6.z.string()).optional().describe("New environment variables"),
1706
- readme: import_zod6.z.string().optional().describe("New README content in markdown format"),
1707
- containerRegistryAuthId: import_zod6.z.string().optional().describe(
2529
+ templateId: import_zod10.z.string().describe("ID of the template to update"),
2530
+ name: import_zod10.z.string().optional().describe("New name for the template"),
2531
+ imageName: import_zod10.z.string().optional().describe("New Docker image"),
2532
+ dockerStartCmd: import_zod10.z.array(import_zod10.z.string()).optional().describe(
2533
+ "New container start command. On v2 stored as the template's `args` string; a multi-element array is joined with spaces (lossy \u2014 an element containing spaces is split into separate tokens). Note: this can only set a command, not clear one \u2014 an empty or absent array is ignored so the existing command is preserved."
2534
+ ),
2535
+ ports: import_zod10.z.array(import_zod10.z.string()).optional().describe("New ports to expose"),
2536
+ env: import_zod10.z.record(import_zod10.z.string()).optional().describe("New environment variables"),
2537
+ readme: import_zod10.z.string().optional().describe("New README content in markdown format"),
2538
+ containerRegistryAuthId: import_zod10.z.string().optional().describe(
1708
2539
  "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."
1709
2540
  )
1710
2541
  },
@@ -1725,7 +2556,7 @@ function registerTemplateTools(server, rt) {
1725
2556
  "delete-template",
1726
2557
  "Permanently delete a template. This cannot be undone.",
1727
2558
  {
1728
- templateId: import_zod6.z.string().describe("ID of the template to delete")
2559
+ templateId: import_zod10.z.string().describe("ID of the template to delete")
1729
2560
  },
1730
2561
  { title: "Delete template", ...DESTRUCTIVE },
1731
2562
  async (params) => {
@@ -1740,7 +2571,7 @@ function registerTemplateTools(server, rt) {
1740
2571
  }
1741
2572
 
1742
2573
  // src/tools/network-volumes.ts
1743
- var import_zod7 = require("zod");
2574
+ var import_zod11 = require("zod");
1744
2575
  function registerNetworkVolumeTools(server, rt) {
1745
2576
  const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
1746
2577
  server.tool(
@@ -1761,7 +2592,7 @@ function registerNetworkVolumeTools(server, rt) {
1761
2592
  "get-network-volume",
1762
2593
  "Get one network volume by id (name, size, data center).",
1763
2594
  {
1764
- networkVolumeId: import_zod7.z.string().describe("ID of the network volume to retrieve")
2595
+ networkVolumeId: import_zod11.z.string().describe("ID of the network volume to retrieve")
1765
2596
  },
1766
2597
  { title: "Get network volume", ...READ_ONLY },
1767
2598
  async (params) => {
@@ -1774,11 +2605,17 @@ function registerNetworkVolumeTools(server, rt) {
1774
2605
  );
1775
2606
  server.tool(
1776
2607
  "create-network-volume",
1777
- "Create a persistent network volume in a data center (size 1\u20134000 GB) that pods can mount.",
2608
+ "Create a persistent network volume in a data center (size 10\u20134096 GB) that pods can mount. Pass volumeType to pick a storage tier; omit it to get the data center default.",
1778
2609
  {
1779
- name: import_zod7.z.string().describe("Name for the network volume"),
1780
- size: import_zod7.z.number().describe("Size in GB (1-4000)"),
1781
- dataCenterId: import_zod7.z.string().describe("Data center ID (see list-data-centers)")
2610
+ name: import_zod11.z.string().describe("Name for the network volume"),
2611
+ // Bounds mirror the v2 spec (integer, minimum 10, maximum 4096). Enforced
2612
+ // here as well as described, so an out-of-range value fails locally
2613
+ // instead of costing an API roundtrip.
2614
+ size: import_zod11.z.number().int().min(10).max(4096).describe("Size in GB (10-4096)"),
2615
+ dataCenterId: import_zod11.z.string().describe("Data center ID (see list-data-centers)"),
2616
+ volumeType: import_zod11.z.enum(["STANDARD", "HIGH_PERFORMANCE"]).optional().describe(
2617
+ "Storage tier. Omit for the data center's default (primary) tier. HIGH_PERFORMANCE provisions an HPS volume. Immutable after creation \u2014 update-network-volume cannot change it."
2618
+ )
1782
2619
  },
1783
2620
  { title: "Create network volume", ...WRITE },
1784
2621
  async (params) => {
@@ -1796,9 +2633,11 @@ function registerNetworkVolumeTools(server, rt) {
1796
2633
  "update-network-volume",
1797
2634
  "Update a network volume (rename, or grow size \u2014 the new size must exceed the current size). Only provided fields change.",
1798
2635
  {
1799
- networkVolumeId: import_zod7.z.string().describe("ID of the network volume to update"),
1800
- name: import_zod7.z.string().optional().describe("New name for the network volume"),
1801
- size: import_zod7.z.number().optional().describe("New size in GB (must be larger than current)")
2636
+ networkVolumeId: import_zod11.z.string().describe("ID of the network volume to update"),
2637
+ name: import_zod11.z.string().optional().describe("New name for the network volume"),
2638
+ // Same absolute bounds as create. "Larger than current" stays server-side —
2639
+ // we do not hold the current size here, so only the range is checked.
2640
+ size: import_zod11.z.number().int().min(10).max(4096).optional().describe("New size in GB, 10-4096 (must be larger than current)")
1802
2641
  },
1803
2642
  { title: "Update network volume", ...WRITE, idempotentHint: true },
1804
2643
  async (params) => {
@@ -1817,7 +2656,7 @@ function registerNetworkVolumeTools(server, rt) {
1817
2656
  "delete-network-volume",
1818
2657
  "Permanently delete a network volume and its data. This cannot be undone.",
1819
2658
  {
1820
- networkVolumeId: import_zod7.z.string().describe("ID of the network volume to delete")
2659
+ networkVolumeId: import_zod11.z.string().describe("ID of the network volume to delete")
1821
2660
  },
1822
2661
  { title: "Delete network volume", ...DESTRUCTIVE },
1823
2662
  async (params) => {
@@ -1832,9 +2671,14 @@ function registerNetworkVolumeTools(server, rt) {
1832
2671
  }
1833
2672
 
1834
2673
  // src/tools/registries.ts
1835
- var import_zod8 = require("zod");
2674
+ var import_zod12 = require("zod");
2675
+ var DELEGATIONS = "/delegations";
1836
2676
  function registerRegistryTools(server, rt) {
1837
2677
  const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
2678
+ const delegationsV2OnlyNotice = (tool) => ({
2679
+ error: `${tool} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,
2680
+ status: 501
2681
+ });
1838
2682
  server.tool(
1839
2683
  "list-container-registry-auths",
1840
2684
  "List your saved container-registry credentials (private image-pull auth). Paginated via limit/cursor.",
@@ -1853,7 +2697,7 @@ function registerRegistryTools(server, rt) {
1853
2697
  "get-container-registry-auth",
1854
2698
  "Get one saved container-registry credential by id (the secret/password is not returned).",
1855
2699
  {
1856
- containerRegistryAuthId: import_zod8.z.string().describe("ID of the container registry auth to retrieve")
2700
+ containerRegistryAuthId: import_zod12.z.string().describe("ID of the container registry auth to retrieve")
1857
2701
  },
1858
2702
  { title: "Get container registry auth", ...READ_ONLY },
1859
2703
  async (params) => {
@@ -1868,9 +2712,9 @@ function registerRegistryTools(server, rt) {
1868
2712
  "create-container-registry-auth",
1869
2713
  "Save container-registry credentials (username + password/token) so pods and endpoints can pull private images.",
1870
2714
  {
1871
- name: import_zod8.z.string().describe("Name for the container registry auth"),
1872
- username: import_zod8.z.string().describe("Registry username"),
1873
- password: import_zod8.z.string().describe("Registry password or access token (stored as a secret)")
2715
+ name: import_zod12.z.string().describe("Name for the container registry auth"),
2716
+ username: import_zod12.z.string().describe("Registry username"),
2717
+ password: import_zod12.z.string().describe("Registry password or access token (stored as a secret)")
1874
2718
  },
1875
2719
  { title: "Create container registry auth", ...WRITE },
1876
2720
  async (params) => {
@@ -1888,7 +2732,7 @@ function registerRegistryTools(server, rt) {
1888
2732
  "delete-container-registry-auth",
1889
2733
  "Permanently delete a saved container-registry credential. This cannot be undone.",
1890
2734
  {
1891
- containerRegistryAuthId: import_zod8.z.string().describe("ID of the container registry auth to delete")
2735
+ containerRegistryAuthId: import_zod12.z.string().describe("ID of the container registry auth to delete")
1892
2736
  },
1893
2737
  { title: "Delete container registry auth", ...DESTRUCTIVE },
1894
2738
  async (params) => {
@@ -1900,166 +2744,69 @@ function registerRegistryTools(server, rt) {
1900
2744
  return jsonReply2(result);
1901
2745
  }
1902
2746
  );
1903
- }
1904
-
1905
- // src/tools/tags.ts
1906
- var import_zod9 = require("zod");
1907
- var TAG_RESOURCE_TYPES = [
1908
- "POD",
1909
- "NETWORK_VOLUME",
1910
- "CLUSTER",
1911
- "SERVERLESS_ENDPOINT"
1912
- ];
1913
- var taggedResourceSchema = import_zod9.z.object({
1914
- id: import_zod9.z.string().describe("Resource identifier (e.g. a pod or endpoint id)"),
1915
- type: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to associate with the tag")
1916
- });
1917
- function v2OnlyNotice(tool) {
1918
- return {
1919
- error: `${tool} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,
1920
- status: 501
1921
- };
1922
- }
1923
- function registerTagTools(server, rt) {
1924
- const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
1925
2747
  server.tool(
1926
- "list-tags",
1927
- "List your tags (key/value labels on pods, volumes, clusters, and endpoints). v2-only \u2014 returns a 501 notice on the v1 API. Paginated via limit/cursor.",
1928
- {
1929
- ...listPaginationParams,
1930
- includeResources: import_zod9.z.boolean().optional().describe("Include the resources attached to each tag in the response")
1931
- },
1932
- { title: "List tags", ...READ_ONLY },
2748
+ "list-registry-delegations",
2749
+ "List your AWS ECR delegations \u2014 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 \u2014 returns a 501 notice on the v1 API. Paginated via limit/cursor.",
2750
+ listPaginationParams,
2751
+ { title: "List registry delegations", ...READ_ONLY },
1933
2752
  async (params) => {
1934
- const backend = backendFor("tags");
1935
- if (backend.version === "v1") return jsonReply2(v2OnlyNotice("list-tags"));
1936
- const query = params.includeResources ? "?include=resources" : "";
1937
- const raw = await callRestUrl(`${backend.base}${backend.list}${query}`);
1938
- return capListResult(backend.unwrap(raw), {
2753
+ const backend = backendFor("registries");
2754
+ if (backend.version === "v1") {
2755
+ return jsonReply2(delegationsV2OnlyNotice("list-registry-delegations"));
2756
+ }
2757
+ const raw = await callRestUrl(
2758
+ `${backend.base}${backend.list}${DELEGATIONS}`
2759
+ );
2760
+ const delegations = Array.isArray(raw?.delegations) ? raw.delegations : [];
2761
+ return capListResult(delegations, {
1939
2762
  limit: params.limit,
1940
2763
  cursor: params.cursor
1941
2764
  });
1942
2765
  }
1943
2766
  );
1944
2767
  server.tool(
1945
- "get-tag",
1946
- "Get one tag by id, optionally including its attached resources. v2-only \u2014 returns a 501 notice on the v1 API.",
1947
- {
1948
- tagId: import_zod9.z.string().describe("ID of the tag to retrieve"),
1949
- includeResources: import_zod9.z.boolean().optional().describe("Include the resources attached to this tag")
1950
- },
1951
- { title: "Get tag", ...READ_ONLY },
1952
- async (params) => {
1953
- const backend = backendFor("tags");
1954
- if (backend.version === "v1") return jsonReply2(v2OnlyNotice("get-tag"));
1955
- const query = params.includeResources ? "?include=resources" : "";
1956
- const result = await callRestUrl(
1957
- `${backend.base}${backend.get(params.tagId)}${query}`
1958
- );
1959
- return jsonReply2(result);
1960
- }
1961
- );
1962
- server.tool(
1963
- "create-tag",
1964
- "Create a key/value tag, optionally attaching resources at creation. v2-only \u2014 returns a 501 notice on the v1 API.",
2768
+ "create-registry-delegation",
2769
+ "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 \u2014 returns a 501 notice on the v1 API. For a non-ECR private registry use create-container-registry-auth instead.",
1965
2770
  {
1966
- key: import_zod9.z.string().min(1).describe('Tag key (e.g. "project")'),
1967
- value: import_zod9.z.string().min(1).describe('Tag value (e.g. "gpt-training")'),
1968
- resources: import_zod9.z.array(taggedResourceSchema).optional().describe("Resources to attach to the tag at creation time")
2771
+ resource: import_zod12.z.string().min(1).describe(
2772
+ 'ECR resource ARN, e.g. "arn:aws:ecr:us-east-2:123456789012:repository/my-org/my-image"'
2773
+ ),
2774
+ name: import_zod12.z.string().optional().describe("Optional human-readable name for the delegation")
1969
2775
  },
1970
- { title: "Create tag", ...WRITE },
2776
+ { title: "Create registry delegation", ...WRITE },
1971
2777
  async (params) => {
1972
- const backend = backendFor("tags");
1973
- if (backend.version === "v1")
1974
- return jsonReply2(v2OnlyNotice("create-tag"));
2778
+ const backend = backendFor("registries");
2779
+ if (backend.version === "v1") {
2780
+ return jsonReply2(delegationsV2OnlyNotice("create-registry-delegation"));
2781
+ }
2782
+ const body = { resource: params.resource };
2783
+ if (params.name !== void 0) body.name = params.name;
1975
2784
  const result = await callRestUrl(
1976
- `${backend.base}${backend.list}`,
2785
+ `${backend.base}${backend.list}${DELEGATIONS}`,
1977
2786
  "POST",
1978
- params
2787
+ body
1979
2788
  );
1980
2789
  return jsonReply2(result);
1981
2790
  }
1982
2791
  );
1983
2792
  server.tool(
1984
- "update-tag",
1985
- "Update a tag (rename its key/value, or replace its resource associations). Provide at least one of key, value, or resources. Sending resources:[] removes all associations; omitting resources leaves them unchanged. Replacing resources is NOT atomic \u2014 a failed update can leave a partially-applied set, so for small association changes prefer attach-tag/detach-tag. v2-only \u2014 returns a 501 notice on the v1 API.",
2793
+ "delete-registry-delegation",
2794
+ "Revoke an AWS ECR delegation, withdrawing Runpod's pull access to that repository. Existing workers keep running; future pulls fail. v2-only \u2014 returns a 501 notice on the v1 API. This cannot be undone.",
1986
2795
  {
1987
- tagId: import_zod9.z.string().describe("ID of the tag to update"),
1988
- key: import_zod9.z.string().min(1).optional().describe("New tag key"),
1989
- value: import_zod9.z.string().min(1).optional().describe("New tag value"),
1990
- resources: import_zod9.z.array(taggedResourceSchema).optional().describe(
1991
- "Replace the tag's resource associations with exactly these. Omit to leave unchanged; pass [] to detach all."
2796
+ delegationId: import_zod12.z.string().describe(
2797
+ "ID of the delegation to revoke (from list-registry-delegations)"
1992
2798
  )
1993
2799
  },
1994
- // No idempotentHint: replacing `resources` is non-atomic upstream, so a
1995
- // retried-after-partial-failure update is not guaranteed to be a no-op.
1996
- { title: "Update tag", ...WRITE },
1997
- async (params) => {
1998
- const { tagId, ...updateParams } = params;
1999
- const backend = backendFor("tags");
2000
- if (backend.version === "v1")
2001
- return jsonReply2(v2OnlyNotice("update-tag"));
2002
- const result = await callRestUrl(
2003
- `${backend.base}${backend.get(tagId)}`,
2004
- "PATCH",
2005
- updateParams
2006
- );
2007
- return jsonReply2(result);
2008
- }
2009
- );
2010
- server.tool(
2011
- "delete-tag",
2012
- "Permanently delete a tag (removes it from all resources it is attached to). This cannot be undone. v2-only \u2014 returns a 501 notice on the v1 API.",
2013
- {
2014
- tagId: import_zod9.z.string().describe("ID of the tag to delete")
2015
- },
2016
- { title: "Delete tag", ...DESTRUCTIVE },
2800
+ { title: "Revoke registry delegation", ...DESTRUCTIVE },
2017
2801
  async (params) => {
2018
- const backend = backendFor("tags");
2019
- if (backend.version === "v1")
2020
- return jsonReply2(v2OnlyNotice("delete-tag"));
2021
- const result = await callRestUrl(
2022
- `${backend.base}${backend.get(params.tagId)}`,
2023
- "DELETE"
2024
- );
2025
- return jsonReply2(result);
2026
- }
2027
- );
2028
- server.tool(
2029
- "attach-tag",
2030
- "Attach a tag to a resource (pod, network volume, cluster, or serverless endpoint). Idempotent \u2014 attaching an already-associated resource is a no-op. v2-only \u2014 returns a 501 notice on the v1 API.",
2031
- {
2032
- tagId: import_zod9.z.string().describe("ID of the tag"),
2033
- resourceType: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to attach the tag to"),
2034
- resourceId: import_zod9.z.string().describe("ID of the resource to attach the tag to")
2035
- },
2036
- { title: "Attach tag to resource", ...WRITE, idempotentHint: true },
2037
- async (params) => {
2038
- const backend = backendFor("tags");
2039
- if (backend.version === "v1")
2040
- return jsonReply2(v2OnlyNotice("attach-tag"));
2041
- const result = await callRestUrl(
2042
- `${backend.base}/tags/${encodeURIComponent(params.tagId)}/resources/${params.resourceType}/${encodeURIComponent(params.resourceId)}`,
2043
- "PUT"
2044
- );
2045
- return jsonReply2(result);
2046
- }
2047
- );
2048
- server.tool(
2049
- "detach-tag",
2050
- "Detach a tag from a resource (removes the association only \u2014 the resource and tag are not deleted). Idempotent \u2014 detaching a resource that is not associated is a no-op. v2-only \u2014 returns a 501 notice on the v1 API.",
2051
- {
2052
- tagId: import_zod9.z.string().describe("ID of the tag"),
2053
- resourceType: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to detach the tag from"),
2054
- resourceId: import_zod9.z.string().describe("ID of the resource to detach the tag from")
2055
- },
2056
- { title: "Detach tag from resource", ...WRITE, idempotentHint: true },
2057
- async (params) => {
2058
- const backend = backendFor("tags");
2059
- if (backend.version === "v1")
2060
- return jsonReply2(v2OnlyNotice("detach-tag"));
2802
+ const backend = backendFor("registries");
2803
+ if (backend.version === "v1") {
2804
+ return jsonReply2(delegationsV2OnlyNotice("delete-registry-delegation"));
2805
+ }
2061
2806
  const result = await callRestUrl(
2062
- `${backend.base}/tags/${encodeURIComponent(params.tagId)}/resources/${params.resourceType}/${encodeURIComponent(params.resourceId)}`,
2807
+ `${backend.base}${backend.list}${DELEGATIONS}/${encodeURIComponent(
2808
+ params.delegationId
2809
+ )}`,
2063
2810
  "DELETE"
2064
2811
  );
2065
2812
  return jsonReply2(result);
@@ -2068,7 +2815,7 @@ function registerTagTools(server, rt) {
2068
2815
  }
2069
2816
 
2070
2817
  // src/tools/billing.ts
2071
- var import_zod10 = require("zod");
2818
+ var import_zod13 = require("zod");
2072
2819
  var BILLING_SCOPE_PATH = {
2073
2820
  all: "/billing",
2074
2821
  pods: "/billing/pods",
@@ -2084,7 +2831,7 @@ function registerBillingTools(server, rt) {
2084
2831
  "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 \u2014 returns a 501 notice on the v1 API. Records are capped via limit/cursor; narrow the window with startTime/endTime or lastN.",
2085
2832
  {
2086
2833
  ...listPaginationParams,
2087
- scope: import_zod10.z.enum([
2834
+ scope: import_zod13.z.enum([
2088
2835
  "all",
2089
2836
  "pods",
2090
2837
  "serverless",
@@ -2094,14 +2841,14 @@ function registerBillingTools(server, rt) {
2094
2841
  ]).optional().describe(
2095
2842
  'Which billing breakdown to fetch. "all" (default) is the aggregate across every resource.'
2096
2843
  ),
2097
- startTime: import_zod10.z.string().optional().describe(
2844
+ startTime: import_zod13.z.string().optional().describe(
2098
2845
  "Start of the period (RFC 3339, e.g. 2026-05-01T00:00:00Z). Defaults to 30 days ago. Mutually exclusive with lastN."
2099
2846
  ),
2100
- endTime: import_zod10.z.string().optional().describe(
2847
+ endTime: import_zod13.z.string().optional().describe(
2101
2848
  "End of the period (RFC 3339), exclusive. Defaults to now. Mutually exclusive with lastN."
2102
2849
  ),
2103
- bucketSize: import_zod10.z.enum(["hour", "day", "week", "month", "year"]).optional().describe("Length of each time bucket. Defaults to day."),
2104
- lastN: import_zod10.z.number().int().positive().optional().describe(
2850
+ bucketSize: import_zod13.z.enum(["hour", "day", "week", "month", "year"]).optional().describe("Length of each time bucket. Defaults to day."),
2851
+ lastN: import_zod13.z.number().int().positive().optional().describe(
2105
2852
  "Return the last N buckets of bucketSize, ending with the current one. Mutually exclusive with startTime/endTime."
2106
2853
  )
2107
2854
  },
@@ -2136,13 +2883,15 @@ function registerBillingTools(server, rt) {
2136
2883
  function registerTools(server, ctx, deps = {}) {
2137
2884
  const rt = createToolRuntime(server, ctx, deps);
2138
2885
  registerCatalogTools(server, rt);
2886
+ registerHubTools(server, rt);
2887
+ registerPublicEndpointTools(server, rt);
2139
2888
  registerPodTools(server, rt);
2140
2889
  registerEndpointTools(server, rt);
2890
+ registerEndpointGpuTools(server, rt);
2141
2891
  registerJobTools(server, rt);
2142
2892
  registerTemplateTools(server, rt);
2143
2893
  registerNetworkVolumeTools(server, rt);
2144
2894
  registerRegistryTools(server, rt);
2145
- registerTagTools(server, rt);
2146
2895
  registerBillingTools(server, rt);
2147
2896
  }
2148
2897
  // Annotate the CommonJS export names for ESM import in node: