@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/stdio.cjs CHANGED
@@ -448,81 +448,6 @@ var import_node_fetch2 = __toESM(require("node-fetch"), 1);
448
448
 
449
449
  // src/server.ts
450
450
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
451
- var SERVER_NAME = "Runpod API Server";
452
- var SERVER_VERSION = true ? "2.0.0" : "dev";
453
- function createServer(version = SERVER_VERSION) {
454
- return new import_mcp.McpServer({
455
- name: SERVER_NAME,
456
- version,
457
- capabilities: {
458
- resources: {},
459
- tools: {}
460
- }
461
- });
462
- }
463
-
464
- // src/tools/runtime.ts
465
- var import_node_fetch = __toESM(require("node-fetch"), 1);
466
- var import_node_crypto = require("crypto");
467
-
468
- // src/_shared/http.ts
469
- var HttpError = class extends Error {
470
- constructor(prefix, status, body) {
471
- super(`${prefix}: ${status} - ${body}`);
472
- this.name = "HttpError";
473
- this.status = status;
474
- this.body = body;
475
- }
476
- };
477
- function isJsonContentType(contentType) {
478
- if (!contentType) return false;
479
- const ct = contentType.toLowerCase();
480
- return ct.includes("application/json") || ct.includes("+json");
481
- }
482
- function methodSendsBody(method) {
483
- return method === "POST" || method === "PATCH" || method === "PUT";
484
- }
485
- function buildRequestHeaders(apiKey, tracking) {
486
- return {
487
- Authorization: `Bearer ${apiKey}`,
488
- "Content-Type": "application/json",
489
- ...tracking()
490
- };
491
- }
492
- function createHttpClient(deps) {
493
- return async function request(url, method = "GET", body) {
494
- const init = {
495
- method,
496
- headers: buildRequestHeaders(deps.apiKey, deps.tracking)
497
- };
498
- if (body && methodSendsBody(method)) {
499
- init.body = JSON.stringify(body);
500
- }
501
- const response = await deps.fetch(url, init);
502
- if (!response.ok) {
503
- throw new HttpError(
504
- deps.errorPrefix,
505
- response.status,
506
- await response.text()
507
- );
508
- }
509
- return isJsonContentType(response.headers.get("content-type")) ? response.json() : { success: true, status: response.status };
510
- };
511
- }
512
-
513
- // src/_shared/tracking.ts
514
- function sanitizeUaToken(value) {
515
- return value.replace(/[()<>@,;:\\"/[\]?={}\s]/g, "_").slice(0, 64);
516
- }
517
- function buildTrackingHeaders(input) {
518
- const name = sanitizeUaToken(input.clientName || "unknown");
519
- const version = sanitizeUaToken(input.clientVersion || "unknown");
520
- const userAgent = `runpod-mcp-server/${input.serverVersion} (caller=mcp; client=${name}; client_version=${version}; transport=${input.transport})`;
521
- return {
522
- "User-Agent": userAgent,
523
- "X-Runpod-Session-Id": input.sessionId
524
- };
525
- }
526
451
 
527
452
  // src/_shared/mappers.ts
528
453
  function compact(obj) {
@@ -536,6 +461,9 @@ function persistentMount(size, path2) {
536
461
  if (size === void 0 || path2 === void 0) return void 0;
537
462
  return { persistent: { size, path: path2 } };
538
463
  }
464
+ function joinStartCmd(cmd) {
465
+ return cmd && cmd.length ? cmd.join(" ") : void 0;
466
+ }
539
467
  function containerConfigToV2(p2) {
540
468
  return compact({
541
469
  image: p2.imageName,
@@ -554,6 +482,13 @@ function mapPodCreateToV2(params) {
554
482
  return compact({
555
483
  ...containerConfigToV2(params),
556
484
  name: params.name,
485
+ // Private-image registry credential (v2 ContainerConfig `registry`). When
486
+ // deploying from a template, this spreads over the template's registry, so
487
+ // passing it overrides the inherited credential. An empty string is an
488
+ // explicit opt-out: emit `registry: null` (v2 accepts null) to CLEAR a
489
+ // template's inherited credential. Undefined is dropped by compact, so an
490
+ // omitted param leaves the template's registry in place.
491
+ registry: params.containerRegistryAuthId === "" ? null : params.containerRegistryAuthId,
557
492
  cloud: params.cloudType,
558
493
  // v2 CreatePodRequest takes `dataCenterIds` as an ARRAY (preferred data
559
494
  // centers; omit/empty = scheduler chooses). Pass the v1 list straight
@@ -610,7 +545,8 @@ function mapNetworkVolumeCreateToV2(params) {
610
545
  return compact({
611
546
  name: params.name,
612
547
  size: params.size,
613
- dataCenter: params.dataCenterId
548
+ dataCenter: params.dataCenterId,
549
+ type: params.volumeType
614
550
  });
615
551
  }
616
552
  function mapTemplateCreateToV2(params) {
@@ -619,18 +555,39 @@ function mapTemplateCreateToV2(params) {
619
555
  ...compact({
620
556
  name: params.name,
621
557
  serverless: params.isServerless,
622
- registry: params.containerRegistryAuthId
623
- }),
624
- // Required by v2 — always emit, defaulting to NVIDIA.
625
- category: params.category ?? "NVIDIA"
558
+ registry: params.containerRegistryAuthId,
559
+ args: joinStartCmd(params.dockerStartCmd),
560
+ // No longer required by v2 (it defaults to NVIDIA server-side), so an
561
+ // unset category is dropped by compact instead of being forced here.
562
+ category: params.category
563
+ })
626
564
  };
627
565
  }
566
+ function podBodyFromTemplate(template) {
567
+ const out = {};
568
+ const keys = [
569
+ "name",
570
+ "image",
571
+ "args",
572
+ "disk",
573
+ "ports",
574
+ "env",
575
+ "mounts",
576
+ "registry"
577
+ ];
578
+ for (const key of keys) {
579
+ const value = template[key];
580
+ if (value !== void 0 && value !== null) out[key] = value;
581
+ }
582
+ return out;
583
+ }
628
584
  function mapTemplateUpdateToV2(params) {
629
585
  return {
630
586
  ...containerConfigToV2(params),
631
587
  ...compact({
632
588
  name: params.name,
633
- registry: params.containerRegistryAuthId
589
+ registry: params.containerRegistryAuthId,
590
+ args: joinStartCmd(params.dockerStartCmd)
634
591
  })
635
592
  };
636
593
  }
@@ -647,7 +604,6 @@ var V2_UNWRAP_KEY = {
647
604
  dataCenters: "dataCenters",
648
605
  endpoints: "endpoints",
649
606
  jobs: "jobs",
650
- tags: "tags",
651
607
  workers: "workers",
652
608
  billing: "records"
653
609
  };
@@ -661,7 +617,6 @@ var RESOURCE_ENV_KEY = {
661
617
  dataCenters: "DATA_CENTERS",
662
618
  endpoints: "ENDPOINTS",
663
619
  jobs: "JOBS",
664
- tags: "TAGS",
665
620
  workers: "WORKERS",
666
621
  billing: "BILLING"
667
622
  };
@@ -677,6 +632,9 @@ function serverlessBase(env) {
677
632
  function publicGraphqlBase(env) {
678
633
  return env.RUNPOD_PUBLIC_GRAPHQL_URL ?? "https://api.runpod.io/graphql";
679
634
  }
635
+ function authedGraphqlBase(env) {
636
+ return env.RUNPOD_AUTHED_GRAPHQL_URL ?? "https://api.runpod.io/graphql";
637
+ }
680
638
  function unwrapList(resource, version, raw) {
681
639
  if (version === "v1") return Array.isArray(raw) ? raw : [];
682
640
  if (raw === null || typeof raw !== "object") return [];
@@ -700,6 +658,14 @@ function wantsAutoProbe(env) {
700
658
  ([k, v]) => k.startsWith("RUNPOD_REST_VERSION") && (v ?? "").toLowerCase() === "auto"
701
659
  );
702
660
  }
661
+ function restVersionSummary(env) {
662
+ const raw = (env.RUNPOD_REST_VERSION ?? "").trim().toLowerCase();
663
+ const base = raw ? `RUNPOD_REST_VERSION=${raw}` : "RUNPOD_REST_VERSION unset (default v2)";
664
+ const overrides = Object.keys(env).filter(
665
+ (k) => k.startsWith("RUNPOD_REST_VERSION_") && (env[k] ?? "").trim() !== ""
666
+ ).length;
667
+ return overrides > 0 ? `${base}, +${overrides} per-resource` : base;
668
+ }
703
669
  var PROBE_TIMEOUT_MS = 4e3;
704
670
  function classifyProbeStatus(status) {
705
671
  if (status >= 500) return void 0;
@@ -765,7 +731,6 @@ var CATALOG = /* @__PURE__ */ new Set([
765
731
  "dataCenters"
766
732
  ]);
767
733
  var V2_ONLY = /* @__PURE__ */ new Set([
768
- "tags",
769
734
  "workers",
770
735
  "billing"
771
736
  ]);
@@ -783,7 +748,6 @@ var V2_REST_PATHS = {
783
748
  list: "/catalog/datacenters",
784
749
  get: (id) => `/catalog/datacenters/${id}`
785
750
  },
786
- tags: { list: "/tags", get: (id) => `/tags/${id}` },
787
751
  // Endpoints live under /v2/serverless on v2 (NOT /v2/endpoints). The v2 list
788
752
  // response wraps rows under `endpoints` (see V2_UNWRAP_KEY), so the existing
789
753
  // unwrap key stays correct.
@@ -880,8 +844,186 @@ function resolveBackend(opts) {
880
844
  return version === "v2" ? buildV2Backend(resource, env, unwrap) : buildV1Backend(resource, env, unwrap);
881
845
  }
882
846
 
847
+ // src/server.ts
848
+ var SERVER_NAME = "Runpod API Server";
849
+ var SERVER_VERSION = true ? "3.0.0" : "dev";
850
+ function createServer(version = SERVER_VERSION) {
851
+ return new import_mcp.McpServer({
852
+ name: SERVER_NAME,
853
+ version: `${version} [${restVersionSummary(process.env)}]`,
854
+ capabilities: {
855
+ resources: {},
856
+ tools: {}
857
+ }
858
+ });
859
+ }
860
+
883
861
  // src/tools/runtime.ts
884
- var MCP_SERVER_VERSION = true ? "2.0.0" : "dev";
862
+ var import_node_fetch = __toESM(require("node-fetch"), 1);
863
+ var import_node_crypto = require("crypto");
864
+
865
+ // src/_shared/http.ts
866
+ var HttpError = class extends Error {
867
+ constructor(prefix, status, body) {
868
+ 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)" : "";
869
+ super(`${prefix}: ${status} - ${body}${hint}`);
870
+ this.name = "HttpError";
871
+ this.status = status;
872
+ this.body = body;
873
+ }
874
+ };
875
+ function isJsonContentType(contentType) {
876
+ if (!contentType) return false;
877
+ const ct = contentType.toLowerCase();
878
+ return ct.includes("application/json") || ct.includes("+json");
879
+ }
880
+ function methodSendsBody(method) {
881
+ return method === "POST" || method === "PATCH" || method === "PUT";
882
+ }
883
+ function buildRequestHeaders(apiKey, tracking) {
884
+ return {
885
+ Authorization: `Bearer ${apiKey}`,
886
+ "Content-Type": "application/json",
887
+ ...tracking()
888
+ };
889
+ }
890
+ function createHttpClient(deps) {
891
+ return async function request(url, method = "GET", body) {
892
+ const init = {
893
+ method,
894
+ headers: buildRequestHeaders(deps.apiKey, deps.tracking)
895
+ };
896
+ if (body && methodSendsBody(method)) {
897
+ init.body = JSON.stringify(body);
898
+ }
899
+ const response = await deps.fetch(url, init);
900
+ if (!response.ok) {
901
+ throw new HttpError(
902
+ deps.errorPrefix,
903
+ response.status,
904
+ await response.text()
905
+ );
906
+ }
907
+ return isJsonContentType(response.headers.get("content-type")) ? response.json() : { success: true, status: response.status };
908
+ };
909
+ }
910
+
911
+ // src/_shared/tracking.ts
912
+ function sanitizeUaToken(value) {
913
+ return value.replace(/[()<>@,;:\\"/[\]?={}\s]/g, "_").slice(0, 64);
914
+ }
915
+ function buildTrackingHeaders(input) {
916
+ const name = sanitizeUaToken(input.clientName || "unknown");
917
+ const version = sanitizeUaToken(input.clientVersion || "unknown");
918
+ const userAgent = `runpod-mcp-server/${input.serverVersion} (caller=mcp; client=${name}; client_version=${version}; transport=${input.transport})`;
919
+ return {
920
+ "User-Agent": userAgent,
921
+ "X-Runpod-Session-Id": input.sessionId
922
+ };
923
+ }
924
+
925
+ // src/tools/logs.ts
926
+ var import_zod = require("zod");
927
+ function parseLogSse(raw) {
928
+ const items = [];
929
+ for (const block of raw.split(/\r?\n\r?\n/)) {
930
+ const dataLines = block.split(/\r?\n/).filter((l) => l.startsWith("data:"));
931
+ if (!dataLines.length) continue;
932
+ const payload = dataLines.map((l) => l.slice(5).replace(/^ /, "")).join("\n");
933
+ if (!payload.trim()) continue;
934
+ try {
935
+ const parsed = JSON.parse(payload);
936
+ items.push(
937
+ parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { raw: payload }
938
+ );
939
+ } catch {
940
+ items.push({ raw: payload });
941
+ }
942
+ }
943
+ return items;
944
+ }
945
+ var LOG_STREAM_DEFAULT_WAIT_MS = 5e3;
946
+ var LOG_STREAM_MAX_BYTES = 256 * 1024;
947
+ async function readSseSnapshot(fetchImpl, url, headers, opts) {
948
+ const controller = new AbortController();
949
+ const timer = setTimeout(() => controller.abort(), opts.maxWaitMs);
950
+ const chunks = [];
951
+ let bytes = 0;
952
+ let truncated = false;
953
+ try {
954
+ const res = await fetchImpl(url, {
955
+ method: "GET",
956
+ headers,
957
+ signal: controller.signal
958
+ });
959
+ if (!res.ok) {
960
+ throw new HttpError(
961
+ "Runpod API Error",
962
+ res.status,
963
+ await res.text().catch(() => "")
964
+ );
965
+ }
966
+ for await (const chunk of res.body ?? []) {
967
+ chunks.push(chunk);
968
+ bytes += chunk.length;
969
+ if (bytes >= opts.maxBytes) {
970
+ truncated = true;
971
+ controller.abort();
972
+ break;
973
+ }
974
+ }
975
+ } catch (err) {
976
+ if (!(err instanceof Error && err.name === "AbortError")) throw err;
977
+ } finally {
978
+ clearTimeout(timer);
979
+ }
980
+ return { raw: Buffer.concat(chunks).toString("utf8"), truncated };
981
+ }
982
+ var logStreamParams = {
983
+ source: import_zod.z.enum(["container", "system", "both"]).optional().describe("Which log source to read (default: both)"),
984
+ tail: import_zod.z.number().int().min(0).max(5e3).optional().describe(
985
+ "Historical lines to backfill before live output (API default 100, max 5000; 0 = live only). Ignored when `since` is set."
986
+ ),
987
+ since: import_zod.z.string().optional().describe("RFC3339 timestamp to resume from; when set, `tail` is ignored."),
988
+ maxWaitMs: import_zod.z.number().int().min(500).max(3e4).optional().describe("How long to read the stream, in ms (default 5000, max 30000)")
989
+ };
990
+ async function collectLogSnapshot(streamSse, logsUrl, params) {
991
+ const qs = new URLSearchParams();
992
+ if (params.source && params.source !== "both")
993
+ qs.append("source", params.source);
994
+ if (params.tail !== void 0) qs.append("tail", String(params.tail));
995
+ if (params.since) qs.append("since", params.since);
996
+ const query = qs.toString() ? `?${qs}` : "";
997
+ const { raw, truncated } = await streamSse(`${logsUrl}${query}`, {
998
+ maxWaitMs: params.maxWaitMs ?? LOG_STREAM_DEFAULT_WAIT_MS,
999
+ maxBytes: LOG_STREAM_MAX_BYTES
1000
+ });
1001
+ const items = parseLogSse(raw);
1002
+ if (truncated) items.pop();
1003
+ return { items, count: items.length, truncated };
1004
+ }
1005
+ async function streamLogsReply(rt, tool, params) {
1006
+ const backend = rt.backendFor(tool.resource);
1007
+ if (backend.version === "v1") {
1008
+ return rt.jsonReply({
1009
+ error: `${tool.name} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,
1010
+ status: 501
1011
+ });
1012
+ }
1013
+ try {
1014
+ return rt.jsonReply(
1015
+ await collectLogSnapshot(rt.streamSse, tool.logsUrl(backend), params)
1016
+ );
1017
+ } catch (error) {
1018
+ if (error instanceof HttpError) {
1019
+ return rt.jsonReply({ error: error.message, status: error.status });
1020
+ }
1021
+ throw error;
1022
+ }
1023
+ }
1024
+
1025
+ // src/tools/runtime.ts
1026
+ var MCP_SERVER_VERSION = true ? "3.0.0" : "dev";
885
1027
  var SESSION_ID = (0, import_node_crypto.randomUUID)();
886
1028
  function trackingHeaders(server, ctx) {
887
1029
  const info = server.server.getClientVersion();
@@ -918,14 +1060,18 @@ var DESTRUCTIVE = {
918
1060
  idempotentHint: true,
919
1061
  openWorldHint: true
920
1062
  };
921
- async function graphqlRequest(query, tracking, fetchImpl, url) {
1063
+ async function graphqlRequest(query, tracking, fetchImpl, url, options) {
922
1064
  const response = await fetchImpl(url, {
923
1065
  method: "POST",
924
1066
  headers: {
925
1067
  "Content-Type": "application/json",
1068
+ ...options?.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {},
926
1069
  ...tracking()
927
1070
  },
928
- body: JSON.stringify({ query })
1071
+ body: JSON.stringify({
1072
+ query,
1073
+ ...options?.variables ? { variables: options.variables } : {}
1074
+ })
929
1075
  });
930
1076
  const result = await response.json();
931
1077
  if (result.errors && result.errors.length > 0) {
@@ -940,7 +1086,17 @@ async function graphqlRequest(query, tracking, fetchImpl, url) {
940
1086
  }
941
1087
  function createToolRuntime(server, ctx, deps = {}) {
942
1088
  const tracking = () => trackingHeaders(server, ctx);
943
- const httpFetch = deps.fetch ?? defaultFetch;
1089
+ function observeUnauthorized(fn) {
1090
+ const notify = ctx.onUnauthorized;
1091
+ if (!notify) return fn;
1092
+ return async (...args) => {
1093
+ const response = await fn(...args);
1094
+ if (response.status === 401) notify();
1095
+ return response;
1096
+ };
1097
+ }
1098
+ const rawFetch = deps.fetch ?? defaultFetch;
1099
+ const httpFetch = observeUnauthorized(rawFetch);
944
1100
  const v1Client = createHttpClient({
945
1101
  apiKey: ctx.apiKey,
946
1102
  fetch: httpFetch,
@@ -975,47 +1131,19 @@ function createToolRuntime(server, ctx, deps = {}) {
975
1131
  "Error calling Runpod API:",
976
1132
  () => restClient(url, method, body)
977
1133
  );
978
- const streamSse = deps.streamSse ?? (async (url, opts) => {
979
- const controller = new AbortController();
980
- const timer = setTimeout(() => controller.abort(), opts.maxWaitMs);
981
- try {
982
- const res = await (0, import_node_fetch.default)(url, {
983
- method: "GET",
984
- headers: {
985
- Authorization: `Bearer ${ctx.apiKey}`,
986
- Accept: "text/event-stream",
987
- ...tracking()
988
- },
989
- signal: controller.signal
990
- });
991
- if (!res.ok) {
992
- throw new HttpError(
993
- "Runpod API Error",
994
- res.status,
995
- await res.text().catch(() => "")
996
- );
997
- }
998
- const chunks = [];
999
- let bytes = 0;
1000
- let truncated = false;
1001
- try {
1002
- for await (const chunk of res.body) {
1003
- chunks.push(chunk);
1004
- bytes += chunk.length;
1005
- if (bytes >= opts.maxBytes) {
1006
- truncated = true;
1007
- controller.abort();
1008
- break;
1009
- }
1010
- }
1011
- } catch (err) {
1012
- if (!(err instanceof Error && err.name === "AbortError")) throw err;
1013
- }
1014
- return { raw: Buffer.concat(chunks).toString("utf8"), truncated };
1015
- } finally {
1016
- clearTimeout(timer);
1017
- }
1018
- });
1134
+ const sseFetch = observeUnauthorized(
1135
+ deps.sseFetch ?? import_node_fetch.default
1136
+ );
1137
+ const streamSse = deps.streamSse ?? ((url, opts) => readSseSnapshot(
1138
+ sseFetch,
1139
+ url,
1140
+ {
1141
+ Authorization: `Bearer ${ctx.apiKey}`,
1142
+ Accept: "text/event-stream",
1143
+ ...tracking()
1144
+ },
1145
+ opts
1146
+ ));
1019
1147
  const backendFor = (resource) => resolveBackend({
1020
1148
  resource,
1021
1149
  env: process.env,
@@ -1037,9 +1165,18 @@ function createToolRuntime(server, ctx, deps = {}) {
1037
1165
  graphql: (query) => graphqlRequest(
1038
1166
  query,
1039
1167
  tracking,
1040
- httpFetch,
1168
+ // rawFetch, not httpFetch: this call carries no credential (see above).
1169
+ rawFetch,
1041
1170
  publicGraphqlBase(process.env)
1042
1171
  ),
1172
+ graphqlAuthed: (query, variables) => graphqlRequest(
1173
+ query,
1174
+ tracking,
1175
+ httpFetch,
1176
+ // NOT publicGraphqlBase: this call carries the caller's API key.
1177
+ authedGraphqlBase(process.env),
1178
+ { variables, apiKey: ctx.apiKey }
1179
+ ),
1043
1180
  runpodRequest,
1044
1181
  serverlessRequest,
1045
1182
  callRestUrl,
@@ -1051,17 +1188,17 @@ function createToolRuntime(server, ctx, deps = {}) {
1051
1188
  }
1052
1189
 
1053
1190
  // src/tools/catalog.ts
1054
- var import_zod2 = require("zod");
1191
+ var import_zod3 = require("zod");
1055
1192
 
1056
1193
  // src/pagination.ts
1057
- var import_zod = require("zod");
1194
+ var import_zod2 = require("zod");
1058
1195
  var DEFAULT_LIST_LIMIT = 20;
1059
1196
  var MAX_LIST_LIMIT = 100;
1060
1197
  var listPaginationParams = {
1061
- limit: import_zod.z.number().int().positive().max(MAX_LIST_LIMIT).optional().describe(
1198
+ limit: import_zod2.z.number().int().positive().max(MAX_LIST_LIMIT).optional().describe(
1062
1199
  `Maximum number of items to return (default ${DEFAULT_LIST_LIMIT}, max ${MAX_LIST_LIMIT}). Use the returned nextCursor to fetch the next page.`
1063
1200
  ),
1064
- cursor: import_zod.z.string().optional().describe(
1201
+ cursor: import_zod2.z.string().optional().describe(
1065
1202
  "Opaque pagination cursor from a previous response (nextCursor). Omit to start from the beginning."
1066
1203
  )
1067
1204
  };
@@ -1119,25 +1256,32 @@ function registerCatalogTools(server, rt) {
1119
1256
  const { jsonReply: jsonReply2, graphql, callRestUrl, backendFor } = rt;
1120
1257
  server.tool(
1121
1258
  "list-gpu-types",
1122
- "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.",
1259
+ "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.",
1123
1260
  {
1124
1261
  ...listPaginationParams,
1125
- minMemoryGb: import_zod2.z.number().optional().describe("Filter to GPUs with at least this much VRAM in GB"),
1126
- secureCloudOnly: import_zod2.z.boolean().optional().describe("Filter to only GPUs available in secure cloud"),
1127
- communityCloudOnly: import_zod2.z.boolean().optional().describe("Filter to only GPUs available in community cloud"),
1128
- searchTerm: import_zod2.z.string().optional().describe(
1262
+ minMemoryGb: import_zod3.z.number().optional().describe("Filter to GPUs with at least this much VRAM in GB"),
1263
+ secureCloudOnly: import_zod3.z.boolean().optional().describe("Filter to only GPUs available in secure cloud"),
1264
+ communityCloudOnly: import_zod3.z.boolean().optional().describe("Filter to only GPUs available in community cloud"),
1265
+ searchTerm: import_zod3.z.string().optional().describe(
1129
1266
  "Search term to filter GPUs by name (e.g., 'A100', 'RTX 4090')"
1130
1267
  ),
1131
- includeUnavailable: import_zod2.z.boolean().optional().describe(
1132
- "Include GPUs that are currently out of stock. Default is false"
1268
+ includeUnavailable: import_zod3.z.boolean().optional().describe(
1269
+ "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."
1270
+ ),
1271
+ includeAvailability: import_zod3.z.boolean().optional().describe(
1272
+ "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."
1133
1273
  )
1134
1274
  },
1135
1275
  { title: "List GPU types", ...READ_ONLY },
1136
1276
  async (params) => {
1137
1277
  const backend = backendFor("gpus");
1138
1278
  if (backend.version === "v2") {
1139
- const raw = await callRestUrl(`${backend.base}${backend.list}`);
1279
+ const wantAvailability = params.includeAvailability !== false;
1280
+ const raw = await callRestUrl(
1281
+ `${backend.base}${backend.list}${wantAvailability ? "?include=AVAILABILITY" : ""}`
1282
+ );
1140
1283
  let gpus = backend.unwrap(raw);
1284
+ gpus = gpus.filter((g) => g.id !== "unknown");
1141
1285
  if (params.minMemoryGb !== void 0)
1142
1286
  gpus = gpus.filter(
1143
1287
  (g) => Number(g.memory ?? 0) >= params.minMemoryGb
@@ -1150,6 +1294,18 @@ function registerCatalogTools(server, rt) {
1150
1294
  (g) => String(g.id ?? "").toLowerCase().includes(t) || String(g.name ?? "").toLowerCase().includes(t)
1151
1295
  );
1152
1296
  }
1297
+ if (params.includeUnavailable === false)
1298
+ gpus = gpus.filter((g) => g.availability !== "NONE");
1299
+ const rank = {
1300
+ HIGH: 3,
1301
+ MEDIUM: 2,
1302
+ LOW: 1,
1303
+ NONE: 0
1304
+ };
1305
+ gpus.sort(
1306
+ (a, b) => (rank[String(b.availability)] ?? 0) - (rank[String(a.availability)] ?? 0)
1307
+ );
1308
+ gpus = gpus.map(({ dataCenters: _dataCenters, ...rest }) => rest);
1153
1309
  return capListResult(gpus, {
1154
1310
  limit: params.limit,
1155
1311
  cursor: params.cursor
@@ -1179,7 +1335,7 @@ function registerCatalogTools(server, rt) {
1179
1335
  return !!status && status !== "Out";
1180
1336
  };
1181
1337
  let gpuTypes = data.gpuTypes.filter((gpu) => gpu.id !== "unknown");
1182
- if (!params.includeUnavailable) {
1338
+ if (params.includeUnavailable === false) {
1183
1339
  gpuTypes = gpuTypes.filter(isAvailable);
1184
1340
  }
1185
1341
  if (params.minMemoryGb) {
@@ -1225,7 +1381,7 @@ function registerCatalogTools(server, rt) {
1225
1381
  "List Runpod data centers (id, name, region/location). Use this to discover valid dataCenterIds for placing pods, endpoints, or network volumes.",
1226
1382
  {
1227
1383
  ...listPaginationParams,
1228
- region: import_zod2.z.string().optional().describe(
1384
+ region: import_zod3.z.string().optional().describe(
1229
1385
  "Filter by region/location (e.g., 'United States', 'Europe', 'Canada')"
1230
1386
  )
1231
1387
  },
@@ -1289,72 +1445,646 @@ function registerCatalogTools(server, rt) {
1289
1445
  }
1290
1446
  );
1291
1447
  server.tool(
1292
- "get-gpu-type",
1293
- "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).",
1448
+ "get-gpu-type",
1449
+ "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.",
1450
+ {
1451
+ gpuTypeId: import_zod3.z.string().describe("ID of the GPU type to retrieve"),
1452
+ includeAvailability: import_zod3.z.boolean().optional().describe(
1453
+ "Include realtime per-datacenter availability (HIGH/MEDIUM/LOW/NONE). Default true."
1454
+ )
1455
+ },
1456
+ { title: "Get GPU type", ...READ_ONLY },
1457
+ async (params) => {
1458
+ const backend = backendFor("gpus");
1459
+ if (backend.version === "v1") {
1460
+ return jsonReply2({
1461
+ error: "get-gpu-type is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2 (or use list-gpu-types on v1).",
1462
+ status: 501
1463
+ });
1464
+ }
1465
+ const path2 = backend.get(encodeURIComponent(params.gpuTypeId));
1466
+ const query = params.includeAvailability === false ? "" : "?include=AVAILABILITY";
1467
+ const result = await callRestUrl(`${backend.base}${path2}${query}`);
1468
+ return jsonReply2(result);
1469
+ }
1470
+ );
1471
+ server.tool(
1472
+ "get-cpu-type",
1473
+ "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).",
1474
+ {
1475
+ cpuTypeId: import_zod3.z.string().describe("ID of the CPU type to retrieve")
1476
+ },
1477
+ { title: "Get CPU type", ...READ_ONLY },
1478
+ async (params) => {
1479
+ const backend = backendFor("cpus");
1480
+ if (backend.version === "v1") {
1481
+ return jsonReply2({
1482
+ error: "get-cpu-type is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
1483
+ status: 501
1484
+ });
1485
+ }
1486
+ const result = await callRestUrl(
1487
+ `${backend.base}${backend.get(params.cpuTypeId)}`
1488
+ );
1489
+ return jsonReply2(result);
1490
+ }
1491
+ );
1492
+ server.tool(
1493
+ "get-data-center",
1494
+ "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).",
1495
+ {
1496
+ dataCenterId: import_zod3.z.string().describe("ID of the data center to retrieve")
1497
+ },
1498
+ { title: "Get data center", ...READ_ONLY },
1499
+ async (params) => {
1500
+ const backend = backendFor("dataCenters");
1501
+ if (backend.version === "v1") {
1502
+ return jsonReply2({
1503
+ error: "get-data-center is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
1504
+ status: 501
1505
+ });
1506
+ }
1507
+ const result = await callRestUrl(
1508
+ `${backend.base}${backend.get(params.dataCenterId)}`
1509
+ );
1510
+ return jsonReply2(result);
1511
+ }
1512
+ );
1513
+ }
1514
+
1515
+ // src/tools/hub.ts
1516
+ var import_zod4 = require("zod");
1517
+ function parseReleaseConfig(config) {
1518
+ if (!config) return void 0;
1519
+ try {
1520
+ return JSON.parse(config);
1521
+ } catch {
1522
+ return config;
1523
+ }
1524
+ }
1525
+ function listingsQuery(withConfig) {
1526
+ return `
1527
+ query {
1528
+ listings(input: {}) {
1529
+ id
1530
+ repoId
1531
+ title
1532
+ description
1533
+ repoName
1534
+ repoOwner
1535
+ createdAt
1536
+ updatedAt
1537
+ views
1538
+ stars
1539
+ deploys
1540
+ language
1541
+ category
1542
+ tags
1543
+ type
1544
+ listedRelease {
1545
+ id
1546
+ name
1547
+ tagName
1548
+ createdAt${withConfig ? "\n config" : ""}
1549
+ build {
1550
+ id
1551
+ imageName
1552
+ }
1553
+ }
1554
+ }
1555
+ }
1556
+ `;
1557
+ }
1558
+ function serializeBoolean(value, input) {
1559
+ if (value === "true") return input.trueValue ?? "true";
1560
+ if (value === "false") return input.falseValue ?? "false";
1561
+ return value;
1562
+ }
1563
+ function buildHubEnv(config, overrides) {
1564
+ const env = [];
1565
+ const missingRequired = [];
1566
+ const remaining = { ...overrides };
1567
+ for (const entry of config.env ?? []) {
1568
+ const input = entry.input ?? {};
1569
+ const override = entry.key in remaining ? remaining[entry.key] : void 0;
1570
+ delete remaining[entry.key];
1571
+ if (override !== void 0 && override !== "") {
1572
+ env.push({
1573
+ key: entry.key,
1574
+ value: input.type === "boolean" ? serializeBoolean(override, input) : override
1575
+ });
1576
+ continue;
1577
+ }
1578
+ if (input.default !== void 0 && input.default !== null) {
1579
+ env.push({
1580
+ key: entry.key,
1581
+ value: typeof input.default === "boolean" ? input.default ? input.trueValue ?? "true" : input.falseValue ?? "false" : String(input.default)
1582
+ });
1583
+ continue;
1584
+ }
1585
+ if (input.required) {
1586
+ missingRequired.push(entry.key);
1587
+ env.push({ key: entry.key, value: "" });
1588
+ continue;
1589
+ }
1590
+ }
1591
+ for (const [key, value] of Object.entries(remaining)) {
1592
+ env.push({ key, value });
1593
+ }
1594
+ return { env, missingRequired };
1595
+ }
1596
+ function minCudaVersion(versions) {
1597
+ if (!versions || versions.length === 0) return null;
1598
+ const compare = (a, b) => {
1599
+ const pa = a.split(".").map(Number);
1600
+ const pb = b.split(".").map(Number);
1601
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
1602
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
1603
+ if (d !== 0) return d;
1604
+ }
1605
+ return 0;
1606
+ };
1607
+ return [...versions].sort(compare)[0];
1608
+ }
1609
+ function randomSuffix() {
1610
+ return Math.random().toString(36).slice(2, 8);
1611
+ }
1612
+ function registerHubTools(server, rt) {
1613
+ const { graphql, graphqlAuthed, jsonReply: jsonReply2 } = rt;
1614
+ server.tool(
1615
+ "list-hub-repos",
1616
+ "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.",
1617
+ {
1618
+ ...listPaginationParams,
1619
+ searchTerm: import_zod4.z.string().optional().describe(
1620
+ "Case-insensitive search across title, description, repo name, owner, and tags (e.g. 'vllm', 'comfyui', 'fine-tuning')"
1621
+ ),
1622
+ category: import_zod4.z.string().optional().describe(
1623
+ "Filter by category (e.g. 'language', 'image', 'audio', 'video', 'embedding')"
1624
+ ),
1625
+ type: import_zod4.z.enum(["SERVERLESS", "POD"]).optional().describe(
1626
+ "Filter by listing type: SERVERLESS (endpoint workers) or POD (pod templates)"
1627
+ ),
1628
+ repoOwner: import_zod4.z.string().optional().describe(
1629
+ "Filter to listings from this GitHub owner/org (e.g. 'runpod-workers', 'axolotl-ai-cloud')"
1630
+ ),
1631
+ includeConfig: import_zod4.z.boolean().optional().describe(
1632
+ "Include the listed release config (hardware requirements + env-var input schema, parsed from .runpod/hub.json). Large \u2014 off by default."
1633
+ )
1634
+ },
1635
+ { title: "List Hub repos", ...READ_ONLY },
1636
+ async (params) => {
1637
+ const data = await graphql(
1638
+ listingsQuery(params.includeConfig === true)
1639
+ );
1640
+ let listings = data.listings;
1641
+ if (params.type) {
1642
+ listings = listings.filter(
1643
+ (l) => (l.type ?? "").toUpperCase() === params.type
1644
+ );
1645
+ }
1646
+ if (params.category) {
1647
+ const term = params.category.toLowerCase();
1648
+ listings = listings.filter(
1649
+ (l) => (l.category ?? "").toLowerCase() === term
1650
+ );
1651
+ }
1652
+ if (params.repoOwner) {
1653
+ const term = params.repoOwner.toLowerCase();
1654
+ listings = listings.filter(
1655
+ (l) => (l.repoOwner ?? "").toLowerCase() === term
1656
+ );
1657
+ }
1658
+ if (params.searchTerm) {
1659
+ const term = params.searchTerm.toLowerCase();
1660
+ listings = listings.filter(
1661
+ (l) => [
1662
+ l.title,
1663
+ l.description,
1664
+ l.repoName,
1665
+ l.repoOwner,
1666
+ ...l.tags ?? []
1667
+ ].some((field) => (field ?? "").toLowerCase().includes(term))
1668
+ );
1669
+ }
1670
+ listings = [...listings].sort(
1671
+ (a, b) => (b.deploys ?? 0) - (a.deploys ?? 0) || a.id.localeCompare(b.id)
1672
+ );
1673
+ const result = listings.map((l) => ({
1674
+ id: l.id,
1675
+ repo: `${l.repoOwner}/${l.repoName}`,
1676
+ title: l.title,
1677
+ description: l.description,
1678
+ type: l.type,
1679
+ category: l.category,
1680
+ tags: l.tags,
1681
+ language: l.language,
1682
+ stars: l.stars,
1683
+ deploys: l.deploys,
1684
+ views: l.views,
1685
+ githubUrl: `https://github.com/${l.repoOwner}/${l.repoName}`,
1686
+ hubUrl: `https://console.runpod.io/hub/${l.repoOwner}/${l.repoName}`,
1687
+ createdAt: l.createdAt,
1688
+ updatedAt: l.updatedAt,
1689
+ listedRelease: l.listedRelease ? {
1690
+ hubReleaseId: l.listedRelease.id,
1691
+ name: l.listedRelease.name,
1692
+ tagName: l.listedRelease.tagName,
1693
+ createdAt: l.listedRelease.createdAt,
1694
+ imageName: l.listedRelease.build?.imageName,
1695
+ ...params.includeConfig ? { config: parseReleaseConfig(l.listedRelease.config) } : {}
1696
+ } : null
1697
+ }));
1698
+ return capListResult(result, {
1699
+ limit: params.limit,
1700
+ cursor: params.cursor
1701
+ });
1702
+ }
1703
+ );
1704
+ server.tool(
1705
+ "deploy-hub-repo",
1706
+ "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.",
1294
1707
  {
1295
- gpuTypeId: import_zod2.z.string().describe("ID of the GPU type to retrieve")
1708
+ repo: import_zod4.z.string().optional().describe(
1709
+ `The Hub repo to deploy as "owner/name" (e.g. 'runpod-workers/worker-comfyui'). Deploys its currently listed release. Provide this or hubReleaseId.`
1710
+ ),
1711
+ hubReleaseId: import_zod4.z.string().optional().describe(
1712
+ "The Hub release ID to deploy (from list-hub-repos listedRelease.hubReleaseId). Provide this or repo."
1713
+ ),
1714
+ name: import_zod4.z.string().optional().describe(
1715
+ "Name for the new endpoint. Defaults to '<listing title> <release tag>'."
1716
+ ),
1717
+ env: import_zod4.z.record(import_zod4.z.string()).optional().describe(
1718
+ "Environment variable overrides, merged over the release config defaults. Keys not in the release schema are passed through as-is."
1719
+ ),
1720
+ gpuIds: import_zod4.z.string().optional().describe(
1721
+ "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."
1722
+ ),
1723
+ gpuCount: import_zod4.z.number().int().positive().optional().describe("GPUs per worker. Defaults to the release config (or 1)."),
1724
+ containerDiskInGb: import_zod4.z.number().int().positive().optional().describe("Container disk size in GB. Defaults to the release config."),
1725
+ workersMin: import_zod4.z.number().int().min(0).optional().describe("Minimum workers (default 0 \u2014 scale to zero)."),
1726
+ workersMax: import_zod4.z.number().int().positive().optional().describe("Maximum workers (default: account default)."),
1727
+ idleTimeout: import_zod4.z.number().int().positive().optional().describe("Seconds a worker idles before scaling down (default 5)."),
1728
+ scalerType: import_zod4.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Autoscaler type (default QUEUE_DELAY)."),
1729
+ scalerValue: import_zod4.z.number().optional().describe("Autoscaler target value (default 4)."),
1730
+ executionTimeoutMs: import_zod4.z.number().int().positive().optional().describe("Per-job execution timeout in ms (default 600000)."),
1731
+ flashboot: import_zod4.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("FlashBoot mode (default FLASHBOOT).")
1296
1732
  },
1297
- { title: "Get GPU type", ...READ_ONLY },
1733
+ { title: "Deploy Hub repo", ...WRITE },
1298
1734
  async (params) => {
1299
- const backend = backendFor("gpus");
1300
- if (backend.version === "v1") {
1735
+ if (!params.repo && !params.hubReleaseId) {
1301
1736
  return jsonReply2({
1302
- error: "get-gpu-type is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2 (or use list-gpu-types on v1).",
1303
- status: 501
1737
+ error: 'Provide either repo ("owner/name") or hubReleaseId. Use list-hub-repos to discover both.'
1304
1738
  });
1305
1739
  }
1306
- const result = await callRestUrl(
1307
- `${backend.base}${backend.get(params.gpuTypeId)}`
1740
+ const catalog = await graphql(listingsQuery(true));
1741
+ let listing;
1742
+ if (params.hubReleaseId) {
1743
+ listing = catalog.listings.find(
1744
+ (l) => l.listedRelease?.id === params.hubReleaseId
1745
+ );
1746
+ } else {
1747
+ const repoKey = params.repo.toLowerCase();
1748
+ listing = catalog.listings.find(
1749
+ (l) => `${l.repoOwner}/${l.repoName}`.toLowerCase() === repoKey
1750
+ );
1751
+ }
1752
+ if (!listing) {
1753
+ return jsonReply2({
1754
+ 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.`
1755
+ });
1756
+ }
1757
+ const release = listing.listedRelease;
1758
+ if (!release?.build?.imageName) {
1759
+ return jsonReply2({
1760
+ error: `Hub repo ${listing.repoOwner}/${listing.repoName} has no listed release with a built image, so it cannot be deployed.`
1761
+ });
1762
+ }
1763
+ if ((listing.type ?? "").toUpperCase() !== "SERVERLESS") {
1764
+ return jsonReply2({
1765
+ error: `Hub repo ${listing.repoOwner}/${listing.repoName} is a ${listing.type} listing \u2014 only SERVERLESS listings deploy as endpoints.`
1766
+ });
1767
+ }
1768
+ const config = parseReleaseConfig(release.config) ?? {};
1769
+ const gpuIds = params.gpuIds ?? config.gpuIds;
1770
+ if (!gpuIds) {
1771
+ return jsonReply2({
1772
+ 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).'
1773
+ });
1774
+ }
1775
+ const { env, missingRequired } = buildHubEnv(config, params.env ?? {});
1776
+ if (missingRequired.length > 0) {
1777
+ return jsonReply2({
1778
+ error: `Missing required environment variables for this release: ${missingRequired.join(
1779
+ ", "
1780
+ )}. Pass them via the env parameter.`
1781
+ });
1782
+ }
1783
+ const endpointName = params.name ?? `${listing.title} ${release.tagName}`;
1784
+ const minCuda = minCudaVersion(config.allowedCudaVersions);
1785
+ const input = {
1786
+ name: endpointName,
1787
+ hubReleaseId: release.id,
1788
+ type: "QB",
1789
+ gpuIds,
1790
+ gpuCount: params.gpuCount ?? config.gpuCount ?? 1,
1791
+ workersMin: params.workersMin ?? 0,
1792
+ workersMax: params.workersMax ?? null,
1793
+ idleTimeout: params.idleTimeout ?? 5,
1794
+ scalerType: params.scalerType ?? "QUEUE_DELAY",
1795
+ scalerValue: params.scalerValue ?? 4,
1796
+ executionTimeoutMs: params.executionTimeoutMs ?? 6e5,
1797
+ flashBootType: params.flashboot ?? "FLASHBOOT",
1798
+ locations: null,
1799
+ networkVolumeIds: null,
1800
+ compliance: [],
1801
+ modelReferences: [],
1802
+ ...minCuda ? {
1803
+ minCudaVersion: minCuda,
1804
+ allowedCudaVersions: config.allowedCudaVersions.join(",")
1805
+ } : {},
1806
+ template: {
1807
+ name: `${endpointName}__template__${randomSuffix()}`,
1808
+ imageName: release.build.imageName,
1809
+ containerDiskInGb: params.containerDiskInGb ?? config.containerDiskInGb ?? 20,
1810
+ containerRegistryAuthId: "",
1811
+ dockerArgs: "",
1812
+ startScript: "",
1813
+ ports: "",
1814
+ env
1815
+ }
1816
+ };
1817
+ const data = await graphqlAuthed(
1818
+ `
1819
+ mutation saveEndpoint($input: EndpointInput!) {
1820
+ saveEndpoint(input: $input) {
1821
+ id
1822
+ name
1823
+ gpuIds
1824
+ gpuCount
1825
+ workersMin
1826
+ workersMax
1827
+ idleTimeout
1828
+ scalerType
1829
+ scalerValue
1830
+ flashBootType
1831
+ templateId
1832
+ }
1833
+ }
1834
+ `,
1835
+ { input }
1308
1836
  );
1309
- return jsonReply2(result);
1837
+ return jsonReply2({
1838
+ endpoint: data.saveEndpoint,
1839
+ deployed: {
1840
+ repo: `${listing.repoOwner}/${listing.repoName}`,
1841
+ release: release.tagName,
1842
+ hubReleaseId: release.id,
1843
+ imageName: release.build.imageName
1844
+ },
1845
+ note: `Endpoint created. Submit jobs with run-endpoint/runsync-endpoint using endpointId "${data.saveEndpoint.id}".`
1846
+ });
1310
1847
  }
1311
1848
  );
1849
+ }
1850
+
1851
+ // src/tools/public-endpoints.ts
1852
+ var import_zod5 = require("zod");
1853
+ function parseMetadata(metadata) {
1854
+ if (!metadata) return {};
1855
+ try {
1856
+ const parsed = JSON.parse(metadata);
1857
+ return parsed && typeof parsed === "object" ? parsed : {};
1858
+ } catch {
1859
+ return {};
1860
+ }
1861
+ }
1862
+ function registerPublicEndpointTools(server, rt) {
1863
+ const { graphql } = rt;
1312
1864
  server.tool(
1313
- "get-cpu-type",
1314
- "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).",
1865
+ "list-public-endpoints",
1866
+ "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.",
1315
1867
  {
1316
- cpuTypeId: import_zod2.z.string().describe("ID of the CPU type to retrieve")
1868
+ ...listPaginationParams,
1869
+ searchTerm: import_zod5.z.string().optional().describe(
1870
+ "Case-insensitive search across display name, model name, endpoint id, description, owner, and tag (e.g. 'kimi', 'video', 'flux')"
1871
+ ),
1872
+ modality: import_zod5.z.string().optional().describe(
1873
+ "Filter by modality/category (the metadata source field, e.g. 'language', 'image', 'video', 'audio')"
1874
+ ),
1875
+ owner: import_zod5.z.string().optional().describe(
1876
+ "Filter by model owner (e.g. 'moonshot', 'minimax', 'google')"
1877
+ ),
1878
+ includeOffline: import_zod5.z.boolean().optional().describe(
1879
+ "Include endpoints that are not currently live (isLive:false). Default false \u2014 only live endpoints are listed."
1880
+ )
1317
1881
  },
1318
- { title: "Get CPU type", ...READ_ONLY },
1882
+ { title: "List public endpoints", ...READ_ONLY },
1319
1883
  async (params) => {
1320
- const backend = backendFor("cpus");
1321
- if (backend.version === "v1") {
1322
- return jsonReply2({
1323
- error: "get-cpu-type is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
1324
- status: 501
1325
- });
1884
+ const data = await graphql(`
1885
+ query {
1886
+ allAiApiPublicConfigs {
1887
+ id
1888
+ aiApiId
1889
+ modelName
1890
+ displayName
1891
+ description
1892
+ metadata
1893
+ isLive
1894
+ createdAt
1895
+ updatedAt
1896
+ }
1897
+ }
1898
+ `);
1899
+ let configs = data.allAiApiPublicConfigs.map((c) => ({
1900
+ config: c,
1901
+ meta: parseMetadata(c.metadata)
1902
+ }));
1903
+ if (!params.includeOffline) {
1904
+ configs = configs.filter(({ config }) => config.isLive);
1326
1905
  }
1327
- const result = await callRestUrl(
1328
- `${backend.base}${backend.get(params.cpuTypeId)}`
1906
+ if (params.modality) {
1907
+ const term = params.modality.toLowerCase();
1908
+ configs = configs.filter(
1909
+ ({ meta }) => (meta.source ?? "").toLowerCase() === term
1910
+ );
1911
+ }
1912
+ if (params.owner) {
1913
+ const term = params.owner.toLowerCase();
1914
+ configs = configs.filter(
1915
+ ({ meta }) => (meta.owner ?? "").toLowerCase() === term
1916
+ );
1917
+ }
1918
+ if (params.searchTerm) {
1919
+ const term = params.searchTerm.toLowerCase();
1920
+ configs = configs.filter(
1921
+ ({ config, meta }) => [
1922
+ config.displayName,
1923
+ config.modelName,
1924
+ config.aiApiId,
1925
+ config.description,
1926
+ meta.owner,
1927
+ meta.tag,
1928
+ meta.source
1929
+ ].some((field) => (field ?? "").toLowerCase().includes(term))
1930
+ );
1931
+ }
1932
+ configs = [...configs].sort(
1933
+ (a, b) => a.config.displayName.localeCompare(b.config.displayName)
1329
1934
  );
1330
- return jsonReply2(result);
1935
+ const result = configs.map(({ config, meta }) => ({
1936
+ endpointId: config.aiApiId,
1937
+ displayName: config.displayName,
1938
+ modelName: config.modelName,
1939
+ description: config.description,
1940
+ modality: meta.source,
1941
+ tag: meta.tag,
1942
+ owner: meta.owner,
1943
+ pricing: meta.priceString,
1944
+ isLive: config.isLive,
1945
+ baseUrl: `https://api.runpod.ai/v2/${config.aiApiId}`,
1946
+ createdAt: config.createdAt,
1947
+ updatedAt: config.updatedAt
1948
+ }));
1949
+ return capListResult(result, {
1950
+ limit: params.limit,
1951
+ cursor: params.cursor
1952
+ });
1331
1953
  }
1332
1954
  );
1955
+ }
1956
+
1957
+ // src/tools/endpoint-gpus.ts
1958
+ var import_zod6 = require("zod");
1959
+ function registerEndpointGpuTools(server, rt) {
1960
+ const { graphqlAuthed, jsonReply: jsonReply2 } = rt;
1333
1961
  server.tool(
1334
- "get-data-center",
1335
- "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).",
1962
+ "set-endpoint-gpus",
1963
+ "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.",
1336
1964
  {
1337
- dataCenterId: import_zod2.z.string().describe("ID of the data center to retrieve")
1965
+ endpointId: import_zod6.z.string().describe("ID of the Serverless endpoint to update"),
1966
+ gpuIds: import_zod6.z.string().optional().describe(
1967
+ "Raw gpuIds string, e.g. 'ADA_24' or 'AMPERE_16,-NVIDIA RTX A4500'. Takes precedence over pools/excludeGpuTypeIds."
1968
+ ),
1969
+ pools: import_zod6.z.array(import_zod6.z.string()).optional().describe(
1970
+ "GPU pool names workers may use (e.g. ['ADA_80_PRO','AMPERE_80']). The pool field from list-gpu-types."
1971
+ ),
1972
+ excludeGpuTypeIds: import_zod6.z.array(import_zod6.z.string()).optional().describe(
1973
+ "GPU type ids to exclude from the allowed pools (e.g. ['NVIDIA H100 NVL']). Use with pools to pin specific SKUs."
1974
+ ),
1975
+ gpuCount: import_zod6.z.number().int().positive().optional().describe("GPUs per worker. Omit to keep the current value."),
1976
+ minCudaVersion: import_zod6.z.string().optional().describe(
1977
+ "Minimum host CUDA version workers may run on (e.g. '12.4'). Omit to keep the current value."
1978
+ ),
1979
+ allowedCudaVersions: import_zod6.z.string().optional().describe(
1980
+ "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."
1981
+ )
1338
1982
  },
1339
- { title: "Get data center", ...READ_ONLY },
1983
+ { title: "Set endpoint GPUs", ...WRITE, idempotentHint: true },
1340
1984
  async (params) => {
1341
- const backend = backendFor("dataCenters");
1342
- if (backend.version === "v1") {
1985
+ const gpuIds = params.gpuIds ?? (params.pools && params.pools.length > 0 ? [
1986
+ ...params.pools,
1987
+ ...(params.excludeGpuTypeIds ?? []).map((id) => `-${id}`)
1988
+ ].join(",") : void 0);
1989
+ if (!gpuIds) {
1343
1990
  return jsonReply2({
1344
- error: "get-data-center is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
1345
- status: 501
1991
+ error: "Provide gpuIds (raw string) or pools (with optional excludeGpuTypeIds). See list-gpu-types for pool names and GPU type ids."
1346
1992
  });
1347
1993
  }
1348
- const result = await callRestUrl(
1349
- `${backend.base}${backend.get(params.dataCenterId)}`
1994
+ const data = await graphqlAuthed(`
1995
+ query {
1996
+ myself {
1997
+ endpoints {
1998
+ id
1999
+ name
2000
+ gpuIds
2001
+ gpuCount
2002
+ workersMin
2003
+ workersMax
2004
+ idleTimeout
2005
+ scalerType
2006
+ scalerValue
2007
+ executionTimeoutMs
2008
+ flashBootType
2009
+ type
2010
+ locations
2011
+ templateId
2012
+ allowedCudaVersions
2013
+ minCudaVersion
2014
+ compliance
2015
+ modelReferences
2016
+ networkVolumeIds {
2017
+ networkVolumeId
2018
+ dataCenterId
2019
+ }
2020
+ }
2021
+ }
2022
+ }
2023
+ `);
2024
+ const current = data.myself.endpoints.find(
2025
+ (e) => e.id === params.endpointId
1350
2026
  );
1351
- return jsonReply2(result);
2027
+ if (!current) {
2028
+ return jsonReply2({
2029
+ error: `No Serverless endpoint found with id "${params.endpointId}". Use list-endpoints to see your endpoints.`
2030
+ });
2031
+ }
2032
+ const input = {
2033
+ id: current.id,
2034
+ name: current.name,
2035
+ gpuIds,
2036
+ gpuCount: params.gpuCount ?? current.gpuCount,
2037
+ workersMin: current.workersMin,
2038
+ workersMax: current.workersMax,
2039
+ idleTimeout: current.idleTimeout,
2040
+ scalerType: current.scalerType,
2041
+ scalerValue: current.scalerValue,
2042
+ executionTimeoutMs: current.executionTimeoutMs,
2043
+ flashBootType: current.flashBootType,
2044
+ type: current.type,
2045
+ locations: current.locations,
2046
+ networkVolumeIds: current.networkVolumeIds && current.networkVolumeIds.length > 0 ? (
2047
+ // Drop dataCenterId: NetworkVolumeIdsInput takes networkVolumeId
2048
+ // ONLY, and the read shape is rejected outright.
2049
+ current.networkVolumeIds.map((v) => ({
2050
+ networkVolumeId: v.networkVolumeId
2051
+ }))
2052
+ ) : null
2053
+ };
2054
+ for (const [key, value] of Object.entries({
2055
+ templateId: current.templateId,
2056
+ allowedCudaVersions: params.allowedCudaVersions ?? current.allowedCudaVersions,
2057
+ minCudaVersion: params.minCudaVersion ?? current.minCudaVersion,
2058
+ compliance: current.compliance,
2059
+ modelReferences: current.modelReferences
2060
+ })) {
2061
+ if (value !== null && value !== void 0) input[key] = value;
2062
+ }
2063
+ const result = await graphqlAuthed(
2064
+ `
2065
+ mutation saveEndpoint($input: EndpointInput!) {
2066
+ saveEndpoint(input: $input) {
2067
+ id
2068
+ name
2069
+ gpuIds
2070
+ gpuCount
2071
+ workersMin
2072
+ workersMax
2073
+ }
2074
+ }
2075
+ `,
2076
+ { input }
2077
+ );
2078
+ return jsonReply2({
2079
+ endpoint: result.saveEndpoint,
2080
+ previousGpuIds: current.gpuIds
2081
+ });
1352
2082
  }
1353
2083
  );
1354
2084
  }
1355
2085
 
1356
2086
  // src/tools/pods.ts
1357
- var import_zod3 = require("zod");
2087
+ var import_zod7 = require("zod");
1358
2088
  function registerPodTools(server, rt) {
1359
2089
  const { jsonReply: jsonReply2, callRestUrl, backendFor, podAction, env } = rt;
1360
2090
  server.tool(
@@ -1362,12 +2092,12 @@ function registerPodTools(server, rt) {
1362
2092
  "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.",
1363
2093
  {
1364
2094
  ...listPaginationParams,
1365
- computeType: import_zod3.z.enum(["GPU", "CPU"]).optional().describe("Filter to only GPU or only CPU Pods"),
1366
- gpuTypeId: import_zod3.z.array(import_zod3.z.string()).optional().describe("Filter to Pods with any of the listed GPU types"),
1367
- dataCenterId: import_zod3.z.array(import_zod3.z.string()).optional().describe("Filter to Pods in any of the provided data centers"),
1368
- name: import_zod3.z.string().optional().describe("Filter to Pods with the provided name"),
1369
- includeMachine: import_zod3.z.boolean().optional().describe("Include information about the machine"),
1370
- includeNetworkVolume: import_zod3.z.boolean().optional().describe("Include information about attached network volumes")
2095
+ computeType: import_zod7.z.enum(["GPU", "CPU"]).optional().describe("Filter to only GPU or only CPU Pods"),
2096
+ gpuTypeId: import_zod7.z.array(import_zod7.z.string()).optional().describe("Filter to Pods with any of the listed GPU types"),
2097
+ dataCenterId: import_zod7.z.array(import_zod7.z.string()).optional().describe("Filter to Pods in any of the provided data centers"),
2098
+ name: import_zod7.z.string().optional().describe("Filter to Pods with the provided name"),
2099
+ includeMachine: import_zod7.z.boolean().optional().describe("Include information about the machine"),
2100
+ includeNetworkVolume: import_zod7.z.boolean().optional().describe("Include information about attached network volumes")
1371
2101
  },
1372
2102
  { title: "List pods", ...READ_ONLY },
1373
2103
  async (params) => {
@@ -1411,9 +2141,9 @@ function registerPodTools(server, rt) {
1411
2141
  "get-pod",
1412
2142
  "Get full details for one pod by id, optionally expanding machine and attached network-volume info.",
1413
2143
  {
1414
- podId: import_zod3.z.string().describe("ID of the pod to retrieve"),
1415
- includeMachine: import_zod3.z.boolean().optional().describe("Include information about the machine"),
1416
- includeNetworkVolume: import_zod3.z.boolean().optional().describe("Include information about attached network volumes")
2144
+ podId: import_zod7.z.string().describe("ID of the pod to retrieve"),
2145
+ includeMachine: import_zod7.z.boolean().optional().describe("Include information about the machine"),
2146
+ includeNetworkVolume: import_zod7.z.boolean().optional().describe("Include information about attached network volumes")
1417
2147
  },
1418
2148
  { title: "Get pod", ...READ_ONLY },
1419
2149
  async (params) => {
@@ -1441,26 +2171,44 @@ function registerPodTools(server, rt) {
1441
2171
  );
1442
2172
  server.tool(
1443
2173
  "create-pod",
1444
- '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.',
2174
+ '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.',
1445
2175
  {
1446
- name: import_zod3.z.string().optional().describe("Name for the pod"),
1447
- imageName: import_zod3.z.string().describe("Docker image to use"),
1448
- computeType: import_zod3.z.enum(["GPU", "CPU"]).optional().describe(
2176
+ name: import_zod7.z.string().optional().describe("Name for the pod"),
2177
+ imageName: import_zod7.z.string().optional().describe("Docker image to use. Optional when templateId is provided."),
2178
+ templateId: import_zod7.z.string().optional().describe(
2179
+ "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."
2180
+ ),
2181
+ computeType: import_zod7.z.enum(["GPU", "CPU"]).optional().describe(
1449
2182
  "Pod type. On v2, required when not passing gpuTypeIds. 'CPU' is served by the v1 API for now (v2 has no CPU pods yet)."
1450
2183
  ),
1451
- cloudType: import_zod3.z.enum(["SECURE", "COMMUNITY"]).optional().describe("SECURE or COMMUNITY cloud"),
1452
- gpuTypeIds: import_zod3.z.array(import_zod3.z.string()).optional().describe("List of acceptable GPU types"),
1453
- gpuCount: import_zod3.z.number().optional().describe("Number of GPUs"),
1454
- containerDiskInGb: import_zod3.z.number().optional().describe("Container disk size in GB"),
1455
- volumeInGb: import_zod3.z.number().optional().describe("Volume size in GB"),
1456
- volumeMountPath: import_zod3.z.string().optional().describe("Path to mount the volume"),
1457
- ports: import_zod3.z.array(import_zod3.z.string()).optional().describe("Ports to expose (e.g., '8888/http', '22/tcp')"),
1458
- env: import_zod3.z.record(import_zod3.z.string()).optional().describe("Environment variables"),
1459
- dataCenterIds: import_zod3.z.array(import_zod3.z.string()).optional().describe("List of data centers")
2184
+ cloudType: import_zod7.z.enum(["SECURE", "COMMUNITY"]).optional().describe("SECURE or COMMUNITY cloud"),
2185
+ gpuTypeIds: import_zod7.z.array(import_zod7.z.string()).optional().describe("List of acceptable GPU types"),
2186
+ gpuCount: import_zod7.z.number().optional().describe("Number of GPUs"),
2187
+ containerDiskInGb: import_zod7.z.number().optional().describe("Container disk size in GB"),
2188
+ volumeInGb: import_zod7.z.number().optional().describe("Volume size in GB"),
2189
+ volumeMountPath: import_zod7.z.string().optional().describe("Path to mount the volume"),
2190
+ ports: import_zod7.z.array(import_zod7.z.string()).optional().describe("Ports to expose (e.g., '8888/http', '22/tcp')"),
2191
+ env: import_zod7.z.record(import_zod7.z.string()).optional().describe("Environment variables"),
2192
+ containerRegistryAuthId: import_zod7.z.string().optional().describe(
2193
+ "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)."
2194
+ ),
2195
+ dataCenterIds: import_zod7.z.array(import_zod7.z.string()).optional().describe("List of data centers")
1460
2196
  },
1461
2197
  { title: "Create pod", ...WRITE },
1462
2198
  async (params) => {
1463
2199
  const backend = backendFor("pods");
2200
+ if (params.templateId && backend.version !== "v2") {
2201
+ return jsonReply2({
2202
+ error: "Deploying a pod from a templateId is only supported on the v2 REST API. Set RUNPOD_REST_VERSION=v2, or pass imageName instead.",
2203
+ status: 501
2204
+ });
2205
+ }
2206
+ if (!params.imageName && !params.templateId) {
2207
+ return jsonReply2({
2208
+ error: "Provide imageName, or templateId to deploy from an existing template.",
2209
+ status: 400
2210
+ });
2211
+ }
1464
2212
  const hasGpuTypes = (params.gpuTypeIds?.length ?? 0) > 0;
1465
2213
  const hasGpuCount = (params.gpuCount ?? 0) > 0;
1466
2214
  const wantsCpu = params.computeType === "CPU";
@@ -1485,6 +2233,12 @@ function registerPodTools(server, rt) {
1485
2233
  });
1486
2234
  }
1487
2235
  if (wantsCpu) {
2236
+ if (params.templateId) {
2237
+ return jsonReply2({
2238
+ error: "Template-based deploy is currently supported only for GPU pods on the v2 API. Create the CPU pod with an explicit imageName instead.",
2239
+ status: 400
2240
+ });
2241
+ }
1488
2242
  try {
1489
2243
  const result = await callRestUrl(
1490
2244
  `${restV1Base(env)}/pods`,
@@ -1509,7 +2263,24 @@ function registerPodTools(server, rt) {
1509
2263
  }
1510
2264
  }
1511
2265
  }
1512
- const body = backend.mapCreate(params);
2266
+ let body = backend.mapCreate(params);
2267
+ if (params.templateId) {
2268
+ let template;
2269
+ try {
2270
+ template = await callRestUrl(
2271
+ `${restV2Base(env)}/templates/${params.templateId}`
2272
+ );
2273
+ } catch (error) {
2274
+ if (error instanceof HttpError) {
2275
+ return jsonReply2({
2276
+ error: `Failed to load template ${params.templateId}: ${error.message}`,
2277
+ status: error.status
2278
+ });
2279
+ }
2280
+ throw error;
2281
+ }
2282
+ body = { ...podBodyFromTemplate(template), ...body };
2283
+ }
1513
2284
  try {
1514
2285
  const result = await callRestUrl(
1515
2286
  `${backend.base}${backend.list}`,
@@ -1524,11 +2295,8 @@ function registerPodTools(server, rt) {
1524
2295
  }
1525
2296
  return jsonReply2(result);
1526
2297
  } catch (error) {
1527
- if (error instanceof HttpError && error.status === 501) {
1528
- return jsonReply2({
1529
- 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).",
1530
- status: 501
1531
- });
2298
+ if (error instanceof HttpError) {
2299
+ return jsonReply2({ error: error.message, status: error.status });
1532
2300
  }
1533
2301
  throw error;
1534
2302
  }
@@ -1538,14 +2306,14 @@ function registerPodTools(server, rt) {
1538
2306
  "update-pod",
1539
2307
  "Update a pod's mutable fields (name, image, container disk, volume, ports, env). Only the fields you provide change.",
1540
2308
  {
1541
- podId: import_zod3.z.string().describe("ID of the pod to update"),
1542
- name: import_zod3.z.string().optional().describe("New name for the pod"),
1543
- imageName: import_zod3.z.string().optional().describe("New Docker image"),
1544
- containerDiskInGb: import_zod3.z.number().optional().describe("New container disk size in GB"),
1545
- volumeInGb: import_zod3.z.number().optional().describe("New volume size in GB"),
1546
- volumeMountPath: import_zod3.z.string().optional().describe("New path to mount the volume"),
1547
- ports: import_zod3.z.array(import_zod3.z.string()).optional().describe("New ports to expose"),
1548
- env: import_zod3.z.record(import_zod3.z.string()).optional().describe("New environment variables")
2309
+ podId: import_zod7.z.string().describe("ID of the pod to update"),
2310
+ name: import_zod7.z.string().optional().describe("New name for the pod"),
2311
+ imageName: import_zod7.z.string().optional().describe("New Docker image"),
2312
+ containerDiskInGb: import_zod7.z.number().optional().describe("New container disk size in GB"),
2313
+ volumeInGb: import_zod7.z.number().optional().describe("New volume size in GB"),
2314
+ volumeMountPath: import_zod7.z.string().optional().describe("New path to mount the volume"),
2315
+ ports: import_zod7.z.array(import_zod7.z.string()).optional().describe("New ports to expose"),
2316
+ env: import_zod7.z.record(import_zod7.z.string()).optional().describe("New environment variables")
1549
2317
  },
1550
2318
  { title: "Update pod", ...WRITE, idempotentHint: true },
1551
2319
  async (params) => {
@@ -1564,7 +2332,7 @@ function registerPodTools(server, rt) {
1564
2332
  "start-pod",
1565
2333
  "Start a stopped pod (resumes the same pod and its billing).",
1566
2334
  {
1567
- podId: import_zod3.z.string().describe("ID of the pod to start")
2335
+ podId: import_zod7.z.string().describe("ID of the pod to start")
1568
2336
  },
1569
2337
  { title: "Start pod", ...WRITE, idempotentHint: true },
1570
2338
  async (params) => {
@@ -1576,7 +2344,7 @@ function registerPodTools(server, rt) {
1576
2344
  "stop-pod",
1577
2345
  "Stop a running pod. The pod and its disk persist; GPU billing stops.",
1578
2346
  {
1579
- podId: import_zod3.z.string().describe("ID of the pod to stop")
2347
+ podId: import_zod7.z.string().describe("ID of the pod to stop")
1580
2348
  },
1581
2349
  { title: "Stop pod", ...WRITE, idempotentHint: true },
1582
2350
  async (params) => {
@@ -1588,7 +2356,7 @@ function registerPodTools(server, rt) {
1588
2356
  "restart-pod",
1589
2357
  "Restart a running pod (single PodAction call). v2-only \u2014 on the v1 API returns a 501 notice (stop then start the pod instead).",
1590
2358
  {
1591
- podId: import_zod3.z.string().describe("ID of the pod to restart")
2359
+ podId: import_zod7.z.string().describe("ID of the pod to restart")
1592
2360
  },
1593
2361
  { title: "Restart pod", ...WRITE },
1594
2362
  async (params) => {
@@ -1603,11 +2371,29 @@ function registerPodTools(server, rt) {
1603
2371
  return jsonReply2(result);
1604
2372
  }
1605
2373
  );
2374
+ server.tool(
2375
+ "stream-pod-logs",
2376
+ "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).",
2377
+ {
2378
+ podId: import_zod7.z.string().describe("ID of the pod whose logs to stream"),
2379
+ ...logStreamParams
2380
+ },
2381
+ { title: "Stream pod logs", ...READ_ONLY },
2382
+ (params) => streamLogsReply(
2383
+ rt,
2384
+ {
2385
+ name: "stream-pod-logs",
2386
+ resource: "pods",
2387
+ logsUrl: (backend) => `${backend.base}${backend.get(encodeURIComponent(params.podId))}/logs`
2388
+ },
2389
+ params
2390
+ )
2391
+ );
1606
2392
  server.tool(
1607
2393
  "delete-pod",
1608
2394
  "Permanently delete a pod and its data. This cannot be undone.",
1609
2395
  {
1610
- podId: import_zod3.z.string().describe("ID of the pod to delete")
2396
+ podId: import_zod7.z.string().describe("ID of the pod to delete")
1611
2397
  },
1612
2398
  { title: "Delete pod", ...DESTRUCTIVE },
1613
2399
  async (params) => {
@@ -1622,7 +2408,7 @@ function registerPodTools(server, rt) {
1622
2408
  }
1623
2409
 
1624
2410
  // src/tools/endpoints.ts
1625
- var import_zod4 = require("zod");
2411
+ var import_zod8 = require("zod");
1626
2412
  function registerEndpointTools(server, rt) {
1627
2413
  const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
1628
2414
  server.tool(
@@ -1630,8 +2416,8 @@ function registerEndpointTools(server, rt) {
1630
2416
  "List your Serverless endpoints, optionally expanding template and worker details (v1 only). Paginated via limit/cursor.",
1631
2417
  {
1632
2418
  ...listPaginationParams,
1633
- includeTemplate: import_zod4.z.boolean().optional().describe("Include template information (v1 only)"),
1634
- includeWorkers: import_zod4.z.boolean().optional().describe("Include information about workers (v1 only)")
2419
+ includeTemplate: import_zod8.z.boolean().optional().describe("Include template information (v1 only)"),
2420
+ includeWorkers: import_zod8.z.boolean().optional().describe("Include information about workers (v1 only)")
1635
2421
  },
1636
2422
  { title: "List endpoints", ...READ_ONLY },
1637
2423
  async (params) => {
@@ -1664,9 +2450,9 @@ function registerEndpointTools(server, rt) {
1664
2450
  "get-endpoint",
1665
2451
  "Get one Serverless endpoint by id, optionally expanding template and worker details (v1 only).",
1666
2452
  {
1667
- endpointId: import_zod4.z.string().describe("ID of the endpoint to retrieve"),
1668
- includeTemplate: import_zod4.z.boolean().optional().describe("Include template information (v1 only)"),
1669
- includeWorkers: import_zod4.z.boolean().optional().describe("Include information about workers (v1 only)")
2453
+ endpointId: import_zod8.z.string().describe("ID of the endpoint to retrieve"),
2454
+ includeTemplate: import_zod8.z.boolean().optional().describe("Include template information (v1 only)"),
2455
+ includeWorkers: import_zod8.z.boolean().optional().describe("Include information about workers (v1 only)")
1670
2456
  },
1671
2457
  { title: "Get endpoint", ...READ_ONLY },
1672
2458
  async (params) => {
@@ -1696,32 +2482,32 @@ function registerEndpointTools(server, rt) {
1696
2482
  "create-endpoint",
1697
2483
  "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).",
1698
2484
  {
1699
- name: import_zod4.z.string().optional().describe("Name for the endpoint"),
2485
+ name: import_zod8.z.string().optional().describe("Name for the endpoint"),
1700
2486
  // --- v2 inline-config fields ---
1701
- imageName: import_zod4.z.string().optional().describe("Docker image (v2). Required on v2 instead of a templateId."),
1702
- gpuPoolIds: import_zod4.z.array(import_zod4.z.string()).optional().describe(
2487
+ imageName: import_zod8.z.string().optional().describe("Docker image (v2). Required on v2 instead of a templateId."),
2488
+ gpuPoolIds: import_zod8.z.array(import_zod8.z.string()).optional().describe(
1703
2489
  'GPU pool names (v2, required). The `pool` field from list-gpu-types, e.g. ["AMPERE_80"]. NOT GPU type ids.'
1704
2490
  ),
1705
- gpuCount: import_zod4.z.number().optional().describe("GPUs per worker (v2)"),
1706
- args: import_zod4.z.string().optional().describe("Container start command/args (v2)"),
1707
- containerDiskInGb: import_zod4.z.number().optional().describe("Container disk size in GB (v2)"),
1708
- ports: import_zod4.z.array(import_zod4.z.string()).optional().describe("Ports to expose (v2), e.g. ['8000/http']"),
1709
- env: import_zod4.z.record(import_zod4.z.string()).optional().describe("Environment variables (v2)"),
1710
- containerRegistryAuthId: import_zod4.z.string().optional().describe("Container registry auth id for a private image (v2)"),
1711
- networkVolumeIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("Network volume ids to attach (v2)"),
1712
- executionTimeoutMs: import_zod4.z.number().optional().describe("Per-job execution timeout in ms (v2)"),
1713
- flashboot: import_zod4.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("FlashBoot mode (v2)"),
2491
+ gpuCount: import_zod8.z.number().optional().describe("GPUs per worker (v2)"),
2492
+ args: import_zod8.z.string().optional().describe("Container start command/args (v2)"),
2493
+ containerDiskInGb: import_zod8.z.number().optional().describe("Container disk size in GB (v2)"),
2494
+ ports: import_zod8.z.array(import_zod8.z.string()).optional().describe("Ports to expose (v2), e.g. ['8000/http']"),
2495
+ env: import_zod8.z.record(import_zod8.z.string()).optional().describe("Environment variables (v2)"),
2496
+ containerRegistryAuthId: import_zod8.z.string().optional().describe("Container registry auth id for a private image (v2)"),
2497
+ networkVolumeIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("Network volume ids to attach (v2)"),
2498
+ executionTimeoutMs: import_zod8.z.number().optional().describe("Per-job execution timeout in ms (v2)"),
2499
+ flashboot: import_zod8.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("FlashBoot mode (v2)"),
1714
2500
  // --- v1 template-based fields ---
1715
- templateId: import_zod4.z.string().optional().describe("Template ID (v1). Required on v1."),
1716
- computeType: import_zod4.z.enum(["GPU", "CPU"]).optional().describe("GPU or CPU endpoint (v1)"),
1717
- gpuTypeIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("List of acceptable GPU types (v1)"),
2501
+ templateId: import_zod8.z.string().optional().describe("Template ID (v1). Required on v1."),
2502
+ computeType: import_zod8.z.enum(["GPU", "CPU"]).optional().describe("GPU or CPU endpoint (v1)"),
2503
+ gpuTypeIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("List of acceptable GPU types (v1)"),
1718
2504
  // --- shared autoscaling fields ---
1719
- workersMin: import_zod4.z.number().optional().describe("Minimum number of workers"),
1720
- workersMax: import_zod4.z.number().optional().describe("Maximum number of workers"),
1721
- scalerType: import_zod4.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Autoscaler type"),
1722
- scalerValue: import_zod4.z.number().optional().describe("Autoscaler target value"),
1723
- idleTimeout: import_zod4.z.number().optional().describe("Idle timeout in seconds before scaling a worker down"),
1724
- dataCenterIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("List of preferred data centers")
2505
+ workersMin: import_zod8.z.number().optional().describe("Minimum number of workers"),
2506
+ workersMax: import_zod8.z.number().optional().describe("Maximum number of workers"),
2507
+ scalerType: import_zod8.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Autoscaler type"),
2508
+ scalerValue: import_zod8.z.number().optional().describe("Autoscaler target value"),
2509
+ idleTimeout: import_zod8.z.number().optional().describe("Idle timeout in seconds before scaling a worker down"),
2510
+ dataCenterIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("List of preferred data centers")
1725
2511
  },
1726
2512
  { title: "Create endpoint", ...WRITE },
1727
2513
  async (params) => {
@@ -1784,25 +2570,25 @@ function registerEndpointTools(server, rt) {
1784
2570
  "update-endpoint",
1785
2571
  "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.",
1786
2572
  {
1787
- endpointId: import_zod4.z.string().describe("ID of the endpoint to update"),
1788
- name: import_zod4.z.string().optional().describe("New name for the endpoint"),
1789
- workersMin: import_zod4.z.number().optional().describe("New minimum number of workers"),
1790
- workersMax: import_zod4.z.number().optional().describe("New maximum number of workers"),
1791
- idleTimeout: import_zod4.z.number().optional().describe("New idle timeout in seconds"),
1792
- scalerType: import_zod4.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Scaler type"),
1793
- scalerValue: import_zod4.z.number().optional().describe("Scaler value"),
2573
+ endpointId: import_zod8.z.string().describe("ID of the endpoint to update"),
2574
+ name: import_zod8.z.string().optional().describe("New name for the endpoint"),
2575
+ workersMin: import_zod8.z.number().optional().describe("New minimum number of workers"),
2576
+ workersMax: import_zod8.z.number().optional().describe("New maximum number of workers"),
2577
+ idleTimeout: import_zod8.z.number().optional().describe("New idle timeout in seconds"),
2578
+ scalerType: import_zod8.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Scaler type"),
2579
+ scalerValue: import_zod8.z.number().optional().describe("Scaler value"),
1794
2580
  // --- v2-only inline-config fields ---
1795
- imageName: import_zod4.z.string().optional().describe("New Docker image (v2)"),
1796
- gpuPoolIds: import_zod4.z.array(import_zod4.z.string()).optional().describe('New GPU pool names (v2), e.g. ["AMPERE_80"]'),
1797
- gpuCount: import_zod4.z.number().optional().describe("New GPUs per worker (v2)"),
1798
- args: import_zod4.z.string().optional().describe("New container args (v2)"),
1799
- containerDiskInGb: import_zod4.z.number().optional().describe("New container disk size in GB (v2)"),
1800
- ports: import_zod4.z.array(import_zod4.z.string()).optional().describe("New ports (v2)"),
1801
- env: import_zod4.z.record(import_zod4.z.string()).optional().describe("New environment variables (v2)"),
1802
- containerRegistryAuthId: import_zod4.z.string().optional().describe("New container registry auth id (v2)"),
1803
- networkVolumeIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("New network volume ids (v2)"),
1804
- executionTimeoutMs: import_zod4.z.number().optional().describe("New per-job execution timeout in ms (v2)"),
1805
- flashboot: import_zod4.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("New FlashBoot mode (v2)")
2581
+ imageName: import_zod8.z.string().optional().describe("New Docker image (v2)"),
2582
+ gpuPoolIds: import_zod8.z.array(import_zod8.z.string()).optional().describe('New GPU pool names (v2), e.g. ["AMPERE_80"]'),
2583
+ gpuCount: import_zod8.z.number().optional().describe("New GPUs per worker (v2)"),
2584
+ args: import_zod8.z.string().optional().describe("New container args (v2)"),
2585
+ containerDiskInGb: import_zod8.z.number().optional().describe("New container disk size in GB (v2)"),
2586
+ ports: import_zod8.z.array(import_zod8.z.string()).optional().describe("New ports (v2)"),
2587
+ env: import_zod8.z.record(import_zod8.z.string()).optional().describe("New environment variables (v2)"),
2588
+ containerRegistryAuthId: import_zod8.z.string().optional().describe("New container registry auth id (v2)"),
2589
+ networkVolumeIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("New network volume ids (v2)"),
2590
+ executionTimeoutMs: import_zod8.z.number().optional().describe("New per-job execution timeout in ms (v2)"),
2591
+ flashboot: import_zod8.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("New FlashBoot mode (v2)")
1806
2592
  },
1807
2593
  { title: "Update endpoint", ...WRITE, idempotentHint: true },
1808
2594
  async (params) => {
@@ -1821,7 +2607,7 @@ function registerEndpointTools(server, rt) {
1821
2607
  "delete-endpoint",
1822
2608
  "Permanently delete a Serverless endpoint. This cannot be undone.",
1823
2609
  {
1824
- endpointId: import_zod4.z.string().describe("ID of the endpoint to delete")
2610
+ endpointId: import_zod8.z.string().describe("ID of the endpoint to delete")
1825
2611
  },
1826
2612
  { title: "Delete endpoint", ...DESTRUCTIVE },
1827
2613
  async (params) => {
@@ -1833,12 +2619,39 @@ function registerEndpointTools(server, rt) {
1833
2619
  return jsonReply2(result);
1834
2620
  }
1835
2621
  );
2622
+ server.tool(
2623
+ "list-endpoint-releases",
2624
+ "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.",
2625
+ {
2626
+ ...listPaginationParams,
2627
+ endpointId: import_zod8.z.string().describe("ID of the Serverless endpoint whose releases to list")
2628
+ },
2629
+ { title: "List endpoint releases", ...READ_ONLY },
2630
+ async (params) => {
2631
+ const backend = backendFor("endpoints");
2632
+ if (backend.version === "v1") {
2633
+ return jsonReply2({
2634
+ error: "list-endpoint-releases is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
2635
+ status: 501
2636
+ });
2637
+ }
2638
+ const raw = await callRestUrl(
2639
+ `${backend.base}/serverless/${params.endpointId}/releases`
2640
+ );
2641
+ const releases = Array.isArray(raw?.releases) ? raw.releases : [];
2642
+ return capListResult(
2643
+ releases,
2644
+ { limit: params.limit, cursor: params.cursor },
2645
+ { rollout: raw?.rollout, endpointVersion: raw?.endpointVersion }
2646
+ );
2647
+ }
2648
+ );
1836
2649
  server.tool(
1837
2650
  "list-endpoint-workers",
1838
2651
  "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.",
1839
2652
  {
1840
2653
  ...listPaginationParams,
1841
- endpointId: import_zod4.z.string().describe("ID of the Serverless endpoint whose workers to list")
2654
+ endpointId: import_zod8.z.string().describe("ID of the Serverless endpoint whose workers to list")
1842
2655
  },
1843
2656
  { title: "List endpoint workers", ...READ_ONLY },
1844
2657
  async (params) => {
@@ -1859,30 +2672,51 @@ function registerEndpointTools(server, rt) {
1859
2672
  );
1860
2673
  }
1861
2674
  );
2675
+ server.tool(
2676
+ "stream-worker-logs",
2677
+ "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).",
2678
+ {
2679
+ endpointId: import_zod8.z.string().describe("ID of the Serverless endpoint the worker belongs to"),
2680
+ workerId: import_zod8.z.string().describe(
2681
+ "ID of the worker whose logs to stream (from list-endpoint-workers)"
2682
+ ),
2683
+ ...logStreamParams
2684
+ },
2685
+ { title: "Stream worker logs", ...READ_ONLY },
2686
+ (params) => streamLogsReply(
2687
+ rt,
2688
+ {
2689
+ name: "stream-worker-logs",
2690
+ resource: "workers",
2691
+ logsUrl: (backend) => `${backend.base}/serverless/${encodeURIComponent(params.endpointId)}/workers/${encodeURIComponent(params.workerId)}/logs`
2692
+ },
2693
+ params
2694
+ )
2695
+ );
1862
2696
  }
1863
2697
 
1864
2698
  // src/tools/jobs.ts
1865
- var import_zod5 = require("zod");
2699
+ var import_zod9 = require("zod");
1866
2700
  function registerJobTools(server, rt) {
1867
2701
  const { jsonReply: jsonReply2, serverlessRequest } = rt;
1868
- const endpointIdSchema = import_zod5.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid endpoint ID format");
1869
- const jobIdSchema = import_zod5.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid job ID format");
1870
- const inputSchema = import_zod5.z.record(import_zod5.z.unknown()).describe(
2702
+ const endpointIdSchema = import_zod9.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid endpoint ID format");
2703
+ const jobIdSchema = import_zod9.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid job ID format");
2704
+ const inputSchema = import_zod9.z.record(import_zod9.z.unknown()).describe(
1871
2705
  "Input payload for the worker handler. The expected fields depend on the deployed model or worker."
1872
2706
  );
1873
- const webhookSchema = import_zod5.z.string().url().optional().describe(
2707
+ const webhookSchema = import_zod9.z.string().url().optional().describe(
1874
2708
  "Webhook URL to receive job completion notifications instead of polling"
1875
2709
  );
1876
- const policySchema = import_zod5.z.object({
1877
- executionTimeout: import_zod5.z.number().optional().describe("Maximum execution time in milliseconds"),
1878
- lowPriority: import_zod5.z.boolean().optional().describe("Submit as a low-priority job"),
1879
- ttl: import_zod5.z.number().optional().describe("Time-to-live for the job result in milliseconds")
2710
+ const policySchema = import_zod9.z.object({
2711
+ executionTimeout: import_zod9.z.number().optional().describe("Maximum execution time in milliseconds"),
2712
+ lowPriority: import_zod9.z.boolean().optional().describe("Submit as a low-priority job"),
2713
+ ttl: import_zod9.z.number().optional().describe("Time-to-live for the job result in milliseconds")
1880
2714
  }).optional().describe("Execution policy options");
1881
- const s3ConfigSchema = import_zod5.z.object({
1882
- accessId: import_zod5.z.string().describe("S3 access key ID"),
1883
- accessSecret: import_zod5.z.string().describe("S3 secret access key"),
1884
- bucketName: import_zod5.z.string().describe("S3 bucket name"),
1885
- endpointUrl: import_zod5.z.string().describe("S3 endpoint URL")
2715
+ const s3ConfigSchema = import_zod9.z.object({
2716
+ accessId: import_zod9.z.string().describe("S3 access key ID"),
2717
+ accessSecret: import_zod9.z.string().describe("S3 secret access key"),
2718
+ bucketName: import_zod9.z.string().describe("S3 bucket name"),
2719
+ endpointUrl: import_zod9.z.string().describe("S3 endpoint URL")
1886
2720
  }).optional().describe("S3-compatible storage config for large outputs");
1887
2721
  server.tool(
1888
2722
  "run-endpoint",
@@ -1916,7 +2750,7 @@ function registerJobTools(server, rt) {
1916
2750
  "ID of the Serverless endpoint to run synchronously"
1917
2751
  ),
1918
2752
  input: inputSchema,
1919
- wait: import_zod5.z.number().min(1e3).max(3e5).optional().describe(
2753
+ wait: import_zod9.z.number().min(1e3).max(3e5).optional().describe(
1920
2754
  "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)."
1921
2755
  ),
1922
2756
  webhook: webhookSchema,
@@ -2085,7 +2919,7 @@ function registerJobTools(server, rt) {
2085
2919
  }
2086
2920
 
2087
2921
  // src/tools/templates.ts
2088
- var import_zod6 = require("zod");
2922
+ var import_zod10 = require("zod");
2089
2923
  function registerTemplateTools(server, rt) {
2090
2924
  const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
2091
2925
  server.tool(
@@ -2093,9 +2927,9 @@ function registerTemplateTools(server, rt) {
2093
2927
  `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.`,
2094
2928
  {
2095
2929
  ...listPaginationParams,
2096
- includeRunpodTemplates: import_zod6.z.boolean().optional().describe("Include official Runpod templates in the response"),
2097
- includePublicTemplates: import_zod6.z.boolean().optional().describe("Include community-made public templates in the response"),
2098
- includeEndpointBoundTemplates: import_zod6.z.boolean().optional().describe(
2930
+ includeRunpodTemplates: import_zod10.z.boolean().optional().describe("Include official Runpod templates in the response"),
2931
+ includePublicTemplates: import_zod10.z.boolean().optional().describe("Include community-made public templates in the response"),
2932
+ includeEndpointBoundTemplates: import_zod10.z.boolean().optional().describe(
2099
2933
  "Include templates bound to Serverless endpoints in the response"
2100
2934
  )
2101
2935
  },
@@ -2126,7 +2960,7 @@ function registerTemplateTools(server, rt) {
2126
2960
  "get-template",
2127
2961
  "Get one template by id (image, env, ports, disk/volume config).",
2128
2962
  {
2129
- templateId: import_zod6.z.string().describe("ID of the template to retrieve")
2963
+ templateId: import_zod10.z.string().describe("ID of the template to retrieve")
2130
2964
  },
2131
2965
  { title: "Get template", ...READ_ONLY },
2132
2966
  async (params) => {
@@ -2139,23 +2973,27 @@ function registerTemplateTools(server, rt) {
2139
2973
  );
2140
2974
  server.tool(
2141
2975
  "create-template",
2142
- "Create a reusable pod/endpoint template (image, ports, env, disk/volume). On v2 a category is required and defaults to NVIDIA.",
2976
+ "Create a reusable pod/endpoint template (image, ports, env, disk/volume). Category is optional and defaults to NVIDIA.",
2143
2977
  {
2144
- name: import_zod6.z.string().describe("Name for the template"),
2145
- imageName: import_zod6.z.string().describe("Docker image to use"),
2146
- isServerless: import_zod6.z.boolean().optional().describe("Is this a serverless template"),
2147
- ports: import_zod6.z.array(import_zod6.z.string()).optional().describe("Ports to expose"),
2148
- dockerEntrypoint: import_zod6.z.array(import_zod6.z.string()).optional().describe("Docker entrypoint commands"),
2149
- dockerStartCmd: import_zod6.z.array(import_zod6.z.string()).optional().describe("Docker start commands"),
2150
- env: import_zod6.z.record(import_zod6.z.string()).optional().describe("Environment variables"),
2151
- containerDiskInGb: import_zod6.z.number().optional().describe("Container disk size in GB"),
2152
- volumeInGb: import_zod6.z.number().optional().describe("Volume size in GB"),
2153
- volumeMountPath: import_zod6.z.string().optional().describe("Path to mount the volume"),
2154
- readme: import_zod6.z.string().optional().describe("README content in markdown format"),
2155
- category: import_zod6.z.enum(["CPU", "NVIDIA", "AMD"]).optional().describe(
2156
- "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."
2978
+ name: import_zod10.z.string().describe("Name for the template"),
2979
+ imageName: import_zod10.z.string().describe("Docker image to use"),
2980
+ isServerless: import_zod10.z.boolean().optional().describe("Is this a serverless template"),
2981
+ ports: import_zod10.z.array(import_zod10.z.string()).optional().describe("Ports to expose"),
2982
+ dockerEntrypoint: import_zod10.z.array(import_zod10.z.string()).optional().describe(
2983
+ "Docker entrypoint commands. Note: not persisted on the v2 REST API, which has no separate entrypoint field (only a start command)."
2984
+ ),
2985
+ dockerStartCmd: import_zod10.z.array(import_zod10.z.string()).optional().describe(
2986
+ '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 "").'
2157
2987
  ),
2158
- containerRegistryAuthId: import_zod6.z.string().optional().describe(
2988
+ env: import_zod10.z.record(import_zod10.z.string()).optional().describe("Environment variables"),
2989
+ containerDiskInGb: import_zod10.z.number().optional().describe("Container disk size in GB"),
2990
+ volumeInGb: import_zod10.z.number().optional().describe("Volume size in GB"),
2991
+ volumeMountPath: import_zod10.z.string().optional().describe("Path to mount the volume"),
2992
+ readme: import_zod10.z.string().optional().describe("README content in markdown format"),
2993
+ category: import_zod10.z.enum(["CPU", "NVIDIA", "AMD"]).optional().describe(
2994
+ "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."
2995
+ ),
2996
+ containerRegistryAuthId: import_zod10.z.string().optional().describe(
2159
2997
  "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."
2160
2998
  )
2161
2999
  },
@@ -2173,15 +3011,18 @@ function registerTemplateTools(server, rt) {
2173
3011
  );
2174
3012
  server.tool(
2175
3013
  "update-template",
2176
- "Update a template's mutable fields (name, image, ports, env, readme). Only provided fields change.",
3014
+ "Update a template's mutable fields (name, image, ports, env, start command, registry auth). Only provided fields change; omitted fields are left untouched.",
2177
3015
  {
2178
- templateId: import_zod6.z.string().describe("ID of the template to update"),
2179
- name: import_zod6.z.string().optional().describe("New name for the template"),
2180
- imageName: import_zod6.z.string().optional().describe("New Docker image"),
2181
- ports: import_zod6.z.array(import_zod6.z.string()).optional().describe("New ports to expose"),
2182
- env: import_zod6.z.record(import_zod6.z.string()).optional().describe("New environment variables"),
2183
- readme: import_zod6.z.string().optional().describe("New README content in markdown format"),
2184
- containerRegistryAuthId: import_zod6.z.string().optional().describe(
3016
+ templateId: import_zod10.z.string().describe("ID of the template to update"),
3017
+ name: import_zod10.z.string().optional().describe("New name for the template"),
3018
+ imageName: import_zod10.z.string().optional().describe("New Docker image"),
3019
+ dockerStartCmd: import_zod10.z.array(import_zod10.z.string()).optional().describe(
3020
+ "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."
3021
+ ),
3022
+ ports: import_zod10.z.array(import_zod10.z.string()).optional().describe("New ports to expose"),
3023
+ env: import_zod10.z.record(import_zod10.z.string()).optional().describe("New environment variables"),
3024
+ readme: import_zod10.z.string().optional().describe("New README content in markdown format"),
3025
+ containerRegistryAuthId: import_zod10.z.string().optional().describe(
2185
3026
  "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."
2186
3027
  )
2187
3028
  },
@@ -2202,7 +3043,7 @@ function registerTemplateTools(server, rt) {
2202
3043
  "delete-template",
2203
3044
  "Permanently delete a template. This cannot be undone.",
2204
3045
  {
2205
- templateId: import_zod6.z.string().describe("ID of the template to delete")
3046
+ templateId: import_zod10.z.string().describe("ID of the template to delete")
2206
3047
  },
2207
3048
  { title: "Delete template", ...DESTRUCTIVE },
2208
3049
  async (params) => {
@@ -2217,7 +3058,7 @@ function registerTemplateTools(server, rt) {
2217
3058
  }
2218
3059
 
2219
3060
  // src/tools/network-volumes.ts
2220
- var import_zod7 = require("zod");
3061
+ var import_zod11 = require("zod");
2221
3062
  function registerNetworkVolumeTools(server, rt) {
2222
3063
  const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
2223
3064
  server.tool(
@@ -2238,7 +3079,7 @@ function registerNetworkVolumeTools(server, rt) {
2238
3079
  "get-network-volume",
2239
3080
  "Get one network volume by id (name, size, data center).",
2240
3081
  {
2241
- networkVolumeId: import_zod7.z.string().describe("ID of the network volume to retrieve")
3082
+ networkVolumeId: import_zod11.z.string().describe("ID of the network volume to retrieve")
2242
3083
  },
2243
3084
  { title: "Get network volume", ...READ_ONLY },
2244
3085
  async (params) => {
@@ -2251,11 +3092,17 @@ function registerNetworkVolumeTools(server, rt) {
2251
3092
  );
2252
3093
  server.tool(
2253
3094
  "create-network-volume",
2254
- "Create a persistent network volume in a data center (size 1\u20134000 GB) that pods can mount.",
3095
+ "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.",
2255
3096
  {
2256
- name: import_zod7.z.string().describe("Name for the network volume"),
2257
- size: import_zod7.z.number().describe("Size in GB (1-4000)"),
2258
- dataCenterId: import_zod7.z.string().describe("Data center ID (see list-data-centers)")
3097
+ name: import_zod11.z.string().describe("Name for the network volume"),
3098
+ // Bounds mirror the v2 spec (integer, minimum 10, maximum 4096). Enforced
3099
+ // here as well as described, so an out-of-range value fails locally
3100
+ // instead of costing an API roundtrip.
3101
+ size: import_zod11.z.number().int().min(10).max(4096).describe("Size in GB (10-4096)"),
3102
+ dataCenterId: import_zod11.z.string().describe("Data center ID (see list-data-centers)"),
3103
+ volumeType: import_zod11.z.enum(["STANDARD", "HIGH_PERFORMANCE"]).optional().describe(
3104
+ "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."
3105
+ )
2259
3106
  },
2260
3107
  { title: "Create network volume", ...WRITE },
2261
3108
  async (params) => {
@@ -2273,9 +3120,11 @@ function registerNetworkVolumeTools(server, rt) {
2273
3120
  "update-network-volume",
2274
3121
  "Update a network volume (rename, or grow size \u2014 the new size must exceed the current size). Only provided fields change.",
2275
3122
  {
2276
- networkVolumeId: import_zod7.z.string().describe("ID of the network volume to update"),
2277
- name: import_zod7.z.string().optional().describe("New name for the network volume"),
2278
- size: import_zod7.z.number().optional().describe("New size in GB (must be larger than current)")
3123
+ networkVolumeId: import_zod11.z.string().describe("ID of the network volume to update"),
3124
+ name: import_zod11.z.string().optional().describe("New name for the network volume"),
3125
+ // Same absolute bounds as create. "Larger than current" stays server-side —
3126
+ // we do not hold the current size here, so only the range is checked.
3127
+ size: import_zod11.z.number().int().min(10).max(4096).optional().describe("New size in GB, 10-4096 (must be larger than current)")
2279
3128
  },
2280
3129
  { title: "Update network volume", ...WRITE, idempotentHint: true },
2281
3130
  async (params) => {
@@ -2294,7 +3143,7 @@ function registerNetworkVolumeTools(server, rt) {
2294
3143
  "delete-network-volume",
2295
3144
  "Permanently delete a network volume and its data. This cannot be undone.",
2296
3145
  {
2297
- networkVolumeId: import_zod7.z.string().describe("ID of the network volume to delete")
3146
+ networkVolumeId: import_zod11.z.string().describe("ID of the network volume to delete")
2298
3147
  },
2299
3148
  { title: "Delete network volume", ...DESTRUCTIVE },
2300
3149
  async (params) => {
@@ -2309,9 +3158,14 @@ function registerNetworkVolumeTools(server, rt) {
2309
3158
  }
2310
3159
 
2311
3160
  // src/tools/registries.ts
2312
- var import_zod8 = require("zod");
3161
+ var import_zod12 = require("zod");
3162
+ var DELEGATIONS = "/delegations";
2313
3163
  function registerRegistryTools(server, rt) {
2314
3164
  const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
3165
+ const delegationsV2OnlyNotice = (tool) => ({
3166
+ error: `${tool} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,
3167
+ status: 501
3168
+ });
2315
3169
  server.tool(
2316
3170
  "list-container-registry-auths",
2317
3171
  "List your saved container-registry credentials (private image-pull auth). Paginated via limit/cursor.",
@@ -2330,7 +3184,7 @@ function registerRegistryTools(server, rt) {
2330
3184
  "get-container-registry-auth",
2331
3185
  "Get one saved container-registry credential by id (the secret/password is not returned).",
2332
3186
  {
2333
- containerRegistryAuthId: import_zod8.z.string().describe("ID of the container registry auth to retrieve")
3187
+ containerRegistryAuthId: import_zod12.z.string().describe("ID of the container registry auth to retrieve")
2334
3188
  },
2335
3189
  { title: "Get container registry auth", ...READ_ONLY },
2336
3190
  async (params) => {
@@ -2345,9 +3199,9 @@ function registerRegistryTools(server, rt) {
2345
3199
  "create-container-registry-auth",
2346
3200
  "Save container-registry credentials (username + password/token) so pods and endpoints can pull private images.",
2347
3201
  {
2348
- name: import_zod8.z.string().describe("Name for the container registry auth"),
2349
- username: import_zod8.z.string().describe("Registry username"),
2350
- password: import_zod8.z.string().describe("Registry password or access token (stored as a secret)")
3202
+ name: import_zod12.z.string().describe("Name for the container registry auth"),
3203
+ username: import_zod12.z.string().describe("Registry username"),
3204
+ password: import_zod12.z.string().describe("Registry password or access token (stored as a secret)")
2351
3205
  },
2352
3206
  { title: "Create container registry auth", ...WRITE },
2353
3207
  async (params) => {
@@ -2365,7 +3219,7 @@ function registerRegistryTools(server, rt) {
2365
3219
  "delete-container-registry-auth",
2366
3220
  "Permanently delete a saved container-registry credential. This cannot be undone.",
2367
3221
  {
2368
- containerRegistryAuthId: import_zod8.z.string().describe("ID of the container registry auth to delete")
3222
+ containerRegistryAuthId: import_zod12.z.string().describe("ID of the container registry auth to delete")
2369
3223
  },
2370
3224
  { title: "Delete container registry auth", ...DESTRUCTIVE },
2371
3225
  async (params) => {
@@ -2377,166 +3231,69 @@ function registerRegistryTools(server, rt) {
2377
3231
  return jsonReply2(result);
2378
3232
  }
2379
3233
  );
2380
- }
2381
-
2382
- // src/tools/tags.ts
2383
- var import_zod9 = require("zod");
2384
- var TAG_RESOURCE_TYPES = [
2385
- "POD",
2386
- "NETWORK_VOLUME",
2387
- "CLUSTER",
2388
- "SERVERLESS_ENDPOINT"
2389
- ];
2390
- var taggedResourceSchema = import_zod9.z.object({
2391
- id: import_zod9.z.string().describe("Resource identifier (e.g. a pod or endpoint id)"),
2392
- type: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to associate with the tag")
2393
- });
2394
- function v2OnlyNotice(tool) {
2395
- return {
2396
- error: `${tool} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,
2397
- status: 501
2398
- };
2399
- }
2400
- function registerTagTools(server, rt) {
2401
- const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
2402
3234
  server.tool(
2403
- "list-tags",
2404
- "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.",
2405
- {
2406
- ...listPaginationParams,
2407
- includeResources: import_zod9.z.boolean().optional().describe("Include the resources attached to each tag in the response")
2408
- },
2409
- { title: "List tags", ...READ_ONLY },
3235
+ "list-registry-delegations",
3236
+ "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.",
3237
+ listPaginationParams,
3238
+ { title: "List registry delegations", ...READ_ONLY },
2410
3239
  async (params) => {
2411
- const backend = backendFor("tags");
2412
- if (backend.version === "v1") return jsonReply2(v2OnlyNotice("list-tags"));
2413
- const query = params.includeResources ? "?include=resources" : "";
2414
- const raw = await callRestUrl(`${backend.base}${backend.list}${query}`);
2415
- return capListResult(backend.unwrap(raw), {
3240
+ const backend = backendFor("registries");
3241
+ if (backend.version === "v1") {
3242
+ return jsonReply2(delegationsV2OnlyNotice("list-registry-delegations"));
3243
+ }
3244
+ const raw = await callRestUrl(
3245
+ `${backend.base}${backend.list}${DELEGATIONS}`
3246
+ );
3247
+ const delegations = Array.isArray(raw?.delegations) ? raw.delegations : [];
3248
+ return capListResult(delegations, {
2416
3249
  limit: params.limit,
2417
3250
  cursor: params.cursor
2418
3251
  });
2419
3252
  }
2420
3253
  );
2421
3254
  server.tool(
2422
- "get-tag",
2423
- "Get one tag by id, optionally including its attached resources. v2-only \u2014 returns a 501 notice on the v1 API.",
3255
+ "create-registry-delegation",
3256
+ "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.",
2424
3257
  {
2425
- tagId: import_zod9.z.string().describe("ID of the tag to retrieve"),
2426
- includeResources: import_zod9.z.boolean().optional().describe("Include the resources attached to this tag")
2427
- },
2428
- { title: "Get tag", ...READ_ONLY },
2429
- async (params) => {
2430
- const backend = backendFor("tags");
2431
- if (backend.version === "v1") return jsonReply2(v2OnlyNotice("get-tag"));
2432
- const query = params.includeResources ? "?include=resources" : "";
2433
- const result = await callRestUrl(
2434
- `${backend.base}${backend.get(params.tagId)}${query}`
2435
- );
2436
- return jsonReply2(result);
2437
- }
2438
- );
2439
- server.tool(
2440
- "create-tag",
2441
- "Create a key/value tag, optionally attaching resources at creation. v2-only \u2014 returns a 501 notice on the v1 API.",
2442
- {
2443
- key: import_zod9.z.string().min(1).describe('Tag key (e.g. "project")'),
2444
- value: import_zod9.z.string().min(1).describe('Tag value (e.g. "gpt-training")'),
2445
- resources: import_zod9.z.array(taggedResourceSchema).optional().describe("Resources to attach to the tag at creation time")
3258
+ resource: import_zod12.z.string().min(1).describe(
3259
+ 'ECR resource ARN, e.g. "arn:aws:ecr:us-east-2:123456789012:repository/my-org/my-image"'
3260
+ ),
3261
+ name: import_zod12.z.string().optional().describe("Optional human-readable name for the delegation")
2446
3262
  },
2447
- { title: "Create tag", ...WRITE },
3263
+ { title: "Create registry delegation", ...WRITE },
2448
3264
  async (params) => {
2449
- const backend = backendFor("tags");
2450
- if (backend.version === "v1")
2451
- return jsonReply2(v2OnlyNotice("create-tag"));
3265
+ const backend = backendFor("registries");
3266
+ if (backend.version === "v1") {
3267
+ return jsonReply2(delegationsV2OnlyNotice("create-registry-delegation"));
3268
+ }
3269
+ const body = { resource: params.resource };
3270
+ if (params.name !== void 0) body.name = params.name;
2452
3271
  const result = await callRestUrl(
2453
- `${backend.base}${backend.list}`,
3272
+ `${backend.base}${backend.list}${DELEGATIONS}`,
2454
3273
  "POST",
2455
- params
3274
+ body
2456
3275
  );
2457
3276
  return jsonReply2(result);
2458
3277
  }
2459
3278
  );
2460
3279
  server.tool(
2461
- "update-tag",
2462
- "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.",
3280
+ "delete-registry-delegation",
3281
+ "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.",
2463
3282
  {
2464
- tagId: import_zod9.z.string().describe("ID of the tag to update"),
2465
- key: import_zod9.z.string().min(1).optional().describe("New tag key"),
2466
- value: import_zod9.z.string().min(1).optional().describe("New tag value"),
2467
- resources: import_zod9.z.array(taggedResourceSchema).optional().describe(
2468
- "Replace the tag's resource associations with exactly these. Omit to leave unchanged; pass [] to detach all."
3283
+ delegationId: import_zod12.z.string().describe(
3284
+ "ID of the delegation to revoke (from list-registry-delegations)"
2469
3285
  )
2470
3286
  },
2471
- // No idempotentHint: replacing `resources` is non-atomic upstream, so a
2472
- // retried-after-partial-failure update is not guaranteed to be a no-op.
2473
- { title: "Update tag", ...WRITE },
2474
- async (params) => {
2475
- const { tagId, ...updateParams } = params;
2476
- const backend = backendFor("tags");
2477
- if (backend.version === "v1")
2478
- return jsonReply2(v2OnlyNotice("update-tag"));
2479
- const result = await callRestUrl(
2480
- `${backend.base}${backend.get(tagId)}`,
2481
- "PATCH",
2482
- updateParams
2483
- );
2484
- return jsonReply2(result);
2485
- }
2486
- );
2487
- server.tool(
2488
- "delete-tag",
2489
- "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.",
2490
- {
2491
- tagId: import_zod9.z.string().describe("ID of the tag to delete")
2492
- },
2493
- { title: "Delete tag", ...DESTRUCTIVE },
2494
- async (params) => {
2495
- const backend = backendFor("tags");
2496
- if (backend.version === "v1")
2497
- return jsonReply2(v2OnlyNotice("delete-tag"));
2498
- const result = await callRestUrl(
2499
- `${backend.base}${backend.get(params.tagId)}`,
2500
- "DELETE"
2501
- );
2502
- return jsonReply2(result);
2503
- }
2504
- );
2505
- server.tool(
2506
- "attach-tag",
2507
- "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.",
2508
- {
2509
- tagId: import_zod9.z.string().describe("ID of the tag"),
2510
- resourceType: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to attach the tag to"),
2511
- resourceId: import_zod9.z.string().describe("ID of the resource to attach the tag to")
2512
- },
2513
- { title: "Attach tag to resource", ...WRITE, idempotentHint: true },
2514
- async (params) => {
2515
- const backend = backendFor("tags");
2516
- if (backend.version === "v1")
2517
- return jsonReply2(v2OnlyNotice("attach-tag"));
2518
- const result = await callRestUrl(
2519
- `${backend.base}/tags/${encodeURIComponent(params.tagId)}/resources/${params.resourceType}/${encodeURIComponent(params.resourceId)}`,
2520
- "PUT"
2521
- );
2522
- return jsonReply2(result);
2523
- }
2524
- );
2525
- server.tool(
2526
- "detach-tag",
2527
- "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.",
2528
- {
2529
- tagId: import_zod9.z.string().describe("ID of the tag"),
2530
- resourceType: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to detach the tag from"),
2531
- resourceId: import_zod9.z.string().describe("ID of the resource to detach the tag from")
2532
- },
2533
- { title: "Detach tag from resource", ...WRITE, idempotentHint: true },
3287
+ { title: "Revoke registry delegation", ...DESTRUCTIVE },
2534
3288
  async (params) => {
2535
- const backend = backendFor("tags");
2536
- if (backend.version === "v1")
2537
- return jsonReply2(v2OnlyNotice("detach-tag"));
3289
+ const backend = backendFor("registries");
3290
+ if (backend.version === "v1") {
3291
+ return jsonReply2(delegationsV2OnlyNotice("delete-registry-delegation"));
3292
+ }
2538
3293
  const result = await callRestUrl(
2539
- `${backend.base}/tags/${encodeURIComponent(params.tagId)}/resources/${params.resourceType}/${encodeURIComponent(params.resourceId)}`,
3294
+ `${backend.base}${backend.list}${DELEGATIONS}/${encodeURIComponent(
3295
+ params.delegationId
3296
+ )}`,
2540
3297
  "DELETE"
2541
3298
  );
2542
3299
  return jsonReply2(result);
@@ -2545,7 +3302,7 @@ function registerTagTools(server, rt) {
2545
3302
  }
2546
3303
 
2547
3304
  // src/tools/billing.ts
2548
- var import_zod10 = require("zod");
3305
+ var import_zod13 = require("zod");
2549
3306
  var BILLING_SCOPE_PATH = {
2550
3307
  all: "/billing",
2551
3308
  pods: "/billing/pods",
@@ -2561,7 +3318,7 @@ function registerBillingTools(server, rt) {
2561
3318
  "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.",
2562
3319
  {
2563
3320
  ...listPaginationParams,
2564
- scope: import_zod10.z.enum([
3321
+ scope: import_zod13.z.enum([
2565
3322
  "all",
2566
3323
  "pods",
2567
3324
  "serverless",
@@ -2571,14 +3328,14 @@ function registerBillingTools(server, rt) {
2571
3328
  ]).optional().describe(
2572
3329
  'Which billing breakdown to fetch. "all" (default) is the aggregate across every resource.'
2573
3330
  ),
2574
- startTime: import_zod10.z.string().optional().describe(
3331
+ startTime: import_zod13.z.string().optional().describe(
2575
3332
  "Start of the period (RFC 3339, e.g. 2026-05-01T00:00:00Z). Defaults to 30 days ago. Mutually exclusive with lastN."
2576
3333
  ),
2577
- endTime: import_zod10.z.string().optional().describe(
3334
+ endTime: import_zod13.z.string().optional().describe(
2578
3335
  "End of the period (RFC 3339), exclusive. Defaults to now. Mutually exclusive with lastN."
2579
3336
  ),
2580
- bucketSize: import_zod10.z.enum(["hour", "day", "week", "month", "year"]).optional().describe("Length of each time bucket. Defaults to day."),
2581
- lastN: import_zod10.z.number().int().positive().optional().describe(
3337
+ bucketSize: import_zod13.z.enum(["hour", "day", "week", "month", "year"]).optional().describe("Length of each time bucket. Defaults to day."),
3338
+ lastN: import_zod13.z.number().int().positive().optional().describe(
2582
3339
  "Return the last N buckets of bucketSize, ending with the current one. Mutually exclusive with startTime/endTime."
2583
3340
  )
2584
3341
  },
@@ -2613,13 +3370,15 @@ function registerBillingTools(server, rt) {
2613
3370
  function registerTools(server, ctx, deps = {}) {
2614
3371
  const rt = createToolRuntime(server, ctx, deps);
2615
3372
  registerCatalogTools(server, rt);
3373
+ registerHubTools(server, rt);
3374
+ registerPublicEndpointTools(server, rt);
2616
3375
  registerPodTools(server, rt);
2617
3376
  registerEndpointTools(server, rt);
3377
+ registerEndpointGpuTools(server, rt);
2618
3378
  registerJobTools(server, rt);
2619
3379
  registerTemplateTools(server, rt);
2620
3380
  registerNetworkVolumeTools(server, rt);
2621
3381
  registerRegistryTools(server, rt);
2622
- registerTagTools(server, rt);
2623
3382
  registerBillingTools(server, rt);
2624
3383
  }
2625
3384