@runpod/mcp-server 4.2.0 → 4.4.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
@@ -46,6 +46,9 @@ function publicGraphqlBase(env) {
46
46
  function authedGraphqlBase(env) {
47
47
  return pick(env.RUNPOD_AUTHED_GRAPHQL_URL, "https://api.runpod.io/graphql");
48
48
  }
49
+ function hostedMcpBase(env) {
50
+ return pick(env.RUNPOD_MCP_URL, "https://mcp.getrunpod.io");
51
+ }
49
52
  var init_hosts = __esm({
50
53
  "src/_shared/hosts.ts"() {
51
54
  "use strict";
@@ -224,7 +227,7 @@ function jsonClient(opts) {
224
227
  (opts.detectPaths?.() ?? [path.dirname(opts.configPath())]).some(exists)
225
228
  ),
226
229
  add: (mode) => {
227
- const value = mode.kind === "local" ? localServerConfig(mode.apiKey, serverProperty) : hostedServerConfig(hostedStrategy, serverProperty, mode.url);
230
+ const value = opts.serverValue ? opts.serverValue(mode) : mode.kind === "local" ? localServerConfig(mode.apiKey, serverProperty) : hostedServerConfig(hostedStrategy, serverProperty, mode.url);
228
231
  return Promise.resolve(
229
232
  upsertJsonServer(opts.configPath(), serverProperty, value, opts.dialect)
230
233
  );
@@ -702,6 +705,31 @@ function vsCodeConfigPath() {
702
705
  "mcp.json"
703
706
  );
704
707
  }
708
+ function xdgConfigHome() {
709
+ return absoluteEnvDir(
710
+ process.env.XDG_CONFIG_HOME,
711
+ () => path.join(home, ".config")
712
+ );
713
+ }
714
+ function opencodeConfigPath() {
715
+ const dir = path.join(xdgConfigHome(), "opencode");
716
+ const jsoncPath = path.join(dir, "opencode.jsonc");
717
+ return exists(jsoncPath) ? jsoncPath : path.join(dir, "opencode.json");
718
+ }
719
+ function opencodeServerValue(mode) {
720
+ return mode.kind === "local" ? {
721
+ type: "local",
722
+ command: ["npx", "-y", NPM_PACKAGE],
723
+ environment: { RUNPOD_API_KEY: mode.apiKey },
724
+ enabled: true
725
+ } : { type: "remote", url: mode.url, enabled: true };
726
+ }
727
+ function piMcpConfigPath() {
728
+ const configured = process.env.PI_CODING_AGENT_DIR?.trim();
729
+ if (!configured) return path.join(home, ".pi", "agent", "mcp.json");
730
+ const expanded = configured === "~" ? home : configured.startsWith("~/") ? path.join(home, configured.slice(2)) : configured;
731
+ return path.join(expanded, "mcp.json");
732
+ }
705
733
  var fs, os, path, import_child_process, jsonc, SERVER_NAME2, NPM_PACKAGE, HOSTED_URL, PROJECT_BOUNDARY_MARKERS, CMD_META, KEY_PLACEHOLDER, claudeCodeClient, home, CLIENTS;
706
734
  var init_clients = __esm({
707
735
  "src/install/clients.ts"() {
@@ -711,9 +739,10 @@ var init_clients = __esm({
711
739
  path = __toESM(require("path"), 1);
712
740
  import_child_process = require("child_process");
713
741
  jsonc = __toESM(require("jsonc-parser"), 1);
742
+ init_hosts();
714
743
  SERVER_NAME2 = "runpod";
715
744
  NPM_PACKAGE = "@runpod/mcp-server@latest";
716
- HOSTED_URL = process.env.RUNPOD_MCP_URL ?? "https://mcp.getrunpod.io/";
745
+ HOSTED_URL = `${hostedMcpBase(process.env)}/`;
717
746
  PROJECT_BOUNDARY_MARKERS = [
718
747
  ".git",
719
748
  "package.json",
@@ -770,6 +799,25 @@ var init_clients = __esm({
770
799
  dialect: "jsonc",
771
800
  configPath: vsCodeConfigPath,
772
801
  detectPaths: () => [path.dirname(vsCodeConfigPath())]
802
+ }),
803
+ jsonClient({
804
+ id: "opencode",
805
+ name: "opencode",
806
+ serverProperty: "mcp",
807
+ // Loaded a commented, trailing-comma config when run; see ConfigDialect.
808
+ dialect: "jsonc",
809
+ configPath: opencodeConfigPath,
810
+ detectPaths: () => [path.dirname(opencodeConfigPath())],
811
+ serverValue: opencodeServerValue
812
+ }),
813
+ jsonClient({
814
+ id: "pi",
815
+ name: "Pi",
816
+ // Rejected a single comment when run; see ConfigDialect.
817
+ dialect: "json",
818
+ // Native Streamable HTTP (`pi mcp add --url` writes exactly `{ url }`).
819
+ configPath: piMcpConfigPath,
820
+ detectPaths: () => [path.dirname(piMcpConfigPath())]
773
821
  })
774
822
  ];
775
823
  }
@@ -802,6 +850,68 @@ var init_verify_key = __esm({
802
850
  }
803
851
  });
804
852
 
853
+ // src/install/args.ts
854
+ function parseWizardArgs(argv, knownIds) {
855
+ const parsed = { help: false, list: false, errors: [] };
856
+ const addClients = (raw, flag) => {
857
+ if (raw === void 0 || raw.startsWith("-")) {
858
+ parsed.errors.push(`${flag} needs a value, e.g. ${flag} cursor`);
859
+ return;
860
+ }
861
+ for (const id of raw.split(",").map((s) => s.trim().toLowerCase())) {
862
+ if (!id) continue;
863
+ if (!knownIds.includes(id)) {
864
+ parsed.errors.push(
865
+ `unknown client "${id}". Supported: ${knownIds.join(", ")}`
866
+ );
867
+ continue;
868
+ }
869
+ parsed.clients ??= [];
870
+ if (!parsed.clients.includes(id)) parsed.clients.push(id);
871
+ }
872
+ };
873
+ const setMode = (mode) => {
874
+ if (parsed.mode && parsed.mode !== mode)
875
+ parsed.errors.push("--hosted and --local cannot be combined");
876
+ parsed.mode = mode;
877
+ };
878
+ for (let i = 0; i < argv.length; i++) {
879
+ const arg = argv[i];
880
+ if (arg === "-h" || arg === "--help") parsed.help = true;
881
+ else if (arg === "--list") parsed.list = true;
882
+ else if (arg === "--hosted") setMode("hosted");
883
+ else if (arg === "--local") setMode("local");
884
+ else if (arg === "--client" || arg === "-c") addClients(argv[++i], arg);
885
+ else if (arg.startsWith("--client="))
886
+ addClients(arg.slice("--client=".length), "--client");
887
+ else parsed.errors.push(`unknown option "${arg}"`);
888
+ }
889
+ return parsed;
890
+ }
891
+ function wizardUsage(knownIds) {
892
+ return [
893
+ "Usage:",
894
+ " npx @runpod/mcp-server@latest add interactive setup (needs a terminal)",
895
+ " npx @runpod/mcp-server@latest add --client <ids> no prompts: configure these clients",
896
+ " npx @runpod/mcp-server@latest remove --client <ids> no prompts: remove Runpod from them",
897
+ " npx @runpod/mcp-server@latest add --list list clients, whether each is detected, and its config file",
898
+ "",
899
+ "Options:",
900
+ ` -c, --client <ids> comma-separated or repeated: ${knownIds.join(", ")}`,
901
+ " --hosted connect to the hosted server and sign in with Runpod (OAuth); the default",
902
+ " --local run the server locally with the API key in RUNPOD_API_KEY",
903
+ " (read from the environment only, never from an argument)",
904
+ " -h, --help show this help",
905
+ "",
906
+ "Exit codes: 0 configured, 1 a client failed, 2 bad usage or no terminal for prompts."
907
+ ].join("\n");
908
+ }
909
+ var init_args = __esm({
910
+ "src/install/args.ts"() {
911
+ "use strict";
912
+ }
913
+ });
914
+
805
915
  // src/install/wizard.ts
806
916
  var wizard_exports = {};
807
917
  __export(wizard_exports, {
@@ -950,15 +1060,107 @@ async function runRemove() {
950
1060
  p.note(results.join("\n"), "Results");
951
1061
  p.outro("Done.");
952
1062
  }
953
- async function runWizard(subcommand2) {
1063
+ function signInStep(client) {
1064
+ return SIGN_IN[client.id] ?? 'restart it; it opens "Sign in with Runpod" when it connects';
1065
+ }
1066
+ async function runNonInteractive(subcommand2, clientIds, requestedMode) {
1067
+ const clients = clientIds.map((id) => CLIENTS.find((c) => c.id === id));
1068
+ if (subcommand2 === "remove") {
1069
+ let failed2 = 0;
1070
+ for (const client of clients) {
1071
+ const result = await client.remove();
1072
+ if (!result.success) failed2++;
1073
+ console.log(
1074
+ `${result.success ? "\u2713" : "\u2717"} ${client.name}${result.message ? ` \u2014 ${result.message}` : ""}`
1075
+ );
1076
+ }
1077
+ return failed2 ? 1 : 0;
1078
+ }
1079
+ let mode = { kind: "hosted", url: HOSTED_URL };
1080
+ if (requestedMode === "local") {
1081
+ const apiKey = process.env.RUNPOD_API_KEY?.trim();
1082
+ if (!apiKey) {
1083
+ console.error(
1084
+ "--local reads the API key from RUNPOD_API_KEY, which is not set. Export it, or use --hosted to sign in with Runpod instead."
1085
+ );
1086
+ return 2;
1087
+ }
1088
+ const valid = await verifyApiKey(apiKey);
1089
+ if (valid === false) {
1090
+ console.error(
1091
+ "RUNPOD_API_KEY was rejected by Runpod. Nothing was configured."
1092
+ );
1093
+ return 1;
1094
+ }
1095
+ if (valid === null)
1096
+ console.error("Could not verify RUNPOD_API_KEY (offline?). Continuing.");
1097
+ mode = { kind: "local", apiKey };
1098
+ }
1099
+ let failed = 0;
1100
+ for (const client of clients) {
1101
+ const result = await client.add(mode);
1102
+ if (result.success) {
1103
+ console.log(
1104
+ `\u2713 ${client.name} \u2014 ${result.message ? `${result.message}; ` : ""}${client.describeTarget()}`
1105
+ );
1106
+ } else {
1107
+ failed++;
1108
+ console.log(`\u2717 ${client.name} \u2014 ${result.message ?? "unknown error"}`);
1109
+ }
1110
+ }
1111
+ for (const client of clients) {
1112
+ console.log(
1113
+ mode.kind === "hosted" ? `Next for ${client.name}: ${signInStep(client)}.` : `Next for ${client.name}: restart it (or reconnect MCP) to load the Runpod tools.`
1114
+ );
1115
+ }
1116
+ return failed ? 1 : 0;
1117
+ }
1118
+ async function listClients() {
1119
+ const detected = await detectAll();
1120
+ for (const client of CLIENTS) {
1121
+ console.log(
1122
+ `${client.id.padEnd(15)} ${detected.get(client.id) ? "detected " : "not detected"} ${client.describeTarget()}`
1123
+ );
1124
+ }
1125
+ }
1126
+ async function runWizard(subcommand2, argv = process.argv.slice(3), isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
1127
+ const ids = CLIENTS.map((c) => c.id);
1128
+ const args = parseWizardArgs(argv, ids);
1129
+ if (args.errors.length) {
1130
+ for (const error of args.errors) console.error(`error: ${error}`);
1131
+ console.error(`
1132
+ ${wizardUsage(ids)}`);
1133
+ return 2;
1134
+ }
1135
+ if (args.help) {
1136
+ console.log(wizardUsage(ids));
1137
+ return 0;
1138
+ }
1139
+ if (args.list) {
1140
+ await listClients();
1141
+ return 0;
1142
+ }
1143
+ if (args.clients) {
1144
+ return runNonInteractive(subcommand2, args.clients, args.mode);
1145
+ }
1146
+ if (!isInteractive) {
1147
+ console.error(
1148
+ `This installer needs an interactive terminal to show its prompts, and none is attached. Nothing was configured.
1149
+ To run it without prompts, name the clients: npx @runpod/mcp-server@latest ${subcommand2} --client <ids>
1150
+
1151
+ ${wizardUsage(ids)}`
1152
+ );
1153
+ return 2;
1154
+ }
954
1155
  p.intro("Runpod MCP server installer");
955
1156
  if (subcommand2 === "remove") {
956
1157
  await runRemove();
957
1158
  } else {
958
1159
  await runAdd();
959
1160
  }
1161
+ return 0;
960
1162
  }
961
- var import_child_process2, p, API_KEYS_URL;
1163
+ var import_child_process2, p, API_KEYS_URL, SIGN_IN;
962
1164
  var init_wizard = __esm({
963
1165
  "src/install/wizard.ts"() {
964
1166
  "use strict";
@@ -966,7 +1168,13 @@ var init_wizard = __esm({
966
1168
  p = __toESM(require("@clack/prompts"), 1);
967
1169
  init_clients();
968
1170
  init_verify_key();
1171
+ init_args();
969
1172
  API_KEYS_URL = "https://www.runpod.io/console/user/settings";
1173
+ SIGN_IN = {
1174
+ "claude-code": "in Claude Code run /mcp \u2192 runpod \u2192 Sign in with Runpod",
1175
+ opencode: "run `opencode mcp auth runpod`",
1176
+ pi: "run `pi mcp login runpod`"
1177
+ };
970
1178
  }
971
1179
  });
972
1180
 
@@ -975,11 +1183,11 @@ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
975
1183
 
976
1184
  // src/server.ts
977
1185
  var SERVER_NAME = "Runpod API Server";
978
- var SERVER_VERSION = true ? "4.2.0" : "dev";
1186
+ var SERVER_VERSION = true ? "4.4.0" : "dev";
979
1187
 
980
1188
  // src/specgen/server.ts
981
1189
  var import_server3 = require("@modelcontextprotocol/sdk/server/index.js");
982
- var import_types = require("@modelcontextprotocol/sdk/types.js");
1190
+ var import_types2 = require("@modelcontextprotocol/sdk/types.js");
983
1191
 
984
1192
  // src/_shared/rate-limit.ts
985
1193
  var GENERIC_RATE_LIMIT_HINT = "rate limited \u2014 back off before retrying, and pace bulk operations";
@@ -1179,10 +1387,13 @@ function validateArguments(inputSchema, args) {
1179
1387
  async function dispatchGeneratedTool(client, tool, args) {
1180
1388
  const rejected = validateArguments(tool.inputSchema, args);
1181
1389
  if (rejected) return rejected;
1390
+ const required = new Set(tool.inputSchema.required ?? []);
1182
1391
  const pathParams = {};
1183
1392
  const query = {};
1184
1393
  for (const param of tool.params) {
1185
1394
  if (args[param.name] === void 0) continue;
1395
+ if (param.location === "query" && !required.has(param.name) && isEmptyValue(args[param.name]))
1396
+ continue;
1186
1397
  if (param.location === "path") {
1187
1398
  pathParams[param.name] = args[param.name];
1188
1399
  continue;
@@ -1204,6 +1415,11 @@ async function dispatchGeneratedTool(client, tool, args) {
1204
1415
  };
1205
1416
  }
1206
1417
  }
1418
+ if (tool.hasBody && requestBody !== void 0) {
1419
+ const properties = tool.inputSchema.properties ?? {};
1420
+ const defs = tool.inputSchema.$defs ?? {};
1421
+ requestBody = coerceScalars(requestBody, properties.body, defs);
1422
+ }
1207
1423
  const verbs = client;
1208
1424
  const { data, error, response } = await verbs[tool.method](tool.path, {
1209
1425
  params: { path: pathParams, query },
@@ -1216,6 +1432,79 @@ async function dispatchGeneratedTool(client, tool, args) {
1216
1432
  payload: data ?? { status: response.status }
1217
1433
  };
1218
1434
  }
1435
+ function isEmptyValue(value) {
1436
+ return value === null || value === "" || Array.isArray(value) && !value.length;
1437
+ }
1438
+ function asSchema(value) {
1439
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1440
+ }
1441
+ function resolveSchema(schema, defs, depth = 0) {
1442
+ const node = asSchema(schema);
1443
+ if (!node || depth > 16) return node;
1444
+ const ref = node.$ref;
1445
+ if (typeof ref === "string" && ref.startsWith("#/$defs/"))
1446
+ return resolveSchema(defs[ref.slice("#/$defs/".length)], defs, depth + 1);
1447
+ return node;
1448
+ }
1449
+ function branches(schema, defs, depth = 0) {
1450
+ const node = resolveSchema(schema, defs);
1451
+ if (!node || depth > 16) return [];
1452
+ const out = [node];
1453
+ for (const key of ["allOf", "anyOf", "oneOf"]) {
1454
+ const list = node[key];
1455
+ if (Array.isArray(list))
1456
+ for (const sub of list) out.push(...branches(sub, defs, depth + 1));
1457
+ }
1458
+ return out;
1459
+ }
1460
+ function types(schemas) {
1461
+ const out = /* @__PURE__ */ new Set();
1462
+ for (const s of schemas) {
1463
+ if (typeof s.type === "string") out.add(s.type);
1464
+ else if (Array.isArray(s.type)) {
1465
+ for (const t of s.type) if (typeof t === "string") out.add(t);
1466
+ }
1467
+ }
1468
+ return out;
1469
+ }
1470
+ var NUMERIC = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
1471
+ function coerceScalars(value, schema, defs, depth = 0) {
1472
+ if (depth > 32) return value;
1473
+ const applicable = branches(schema, defs);
1474
+ if (!applicable.length) return value;
1475
+ const allowed = types(applicable);
1476
+ if (typeof value === "string") {
1477
+ if (allowed.has("string")) return value;
1478
+ if ((allowed.has("integer") || allowed.has("number")) && NUMERIC.test(value)) {
1479
+ const n = Number(value);
1480
+ if (allowed.has("number") || Number.isInteger(n)) return n;
1481
+ }
1482
+ if (allowed.has("boolean") && (value === "true" || value === "false"))
1483
+ return value === "true";
1484
+ return value;
1485
+ }
1486
+ if (Array.isArray(value)) {
1487
+ const items = applicable.map((s) => s.items).filter(Boolean);
1488
+ if (!items.length) return value;
1489
+ return value.map(
1490
+ (item) => coerceScalars(item, { anyOf: items }, defs, depth + 1)
1491
+ );
1492
+ }
1493
+ const object = asSchema(value);
1494
+ if (!object) return value;
1495
+ const out = {};
1496
+ for (const [key, child] of Object.entries(object)) {
1497
+ const childSchemas = [];
1498
+ for (const s of applicable) {
1499
+ const props = asSchema(s.properties);
1500
+ if (props && key in props) childSchemas.push(props[key]);
1501
+ else if (asSchema(s.additionalProperties))
1502
+ childSchemas.push(s.additionalProperties);
1503
+ }
1504
+ out[key] = childSchemas.length ? coerceScalars(child, { anyOf: childSchemas }, defs, depth + 1) : child;
1505
+ }
1506
+ return out;
1507
+ }
1219
1508
 
1220
1509
  // src/specgen/generated/tools.gen.ts
1221
1510
  var generatedTools = [
@@ -1233,6 +1522,7 @@ var generatedTools = [
1233
1522
  idempotentHint: false,
1234
1523
  openWorldHint: true
1235
1524
  },
1525
+ schemaOnDemand: true,
1236
1526
  inputSchema: {
1237
1527
  type: "object",
1238
1528
  properties: {
@@ -1244,11 +1534,10 @@ var generatedTools = [
1244
1534
  $defs: {
1245
1535
  BaseContainerConfig: {
1246
1536
  type: "object",
1247
- description: "Container configuration universal to every containerized resource. Compose ContainerConfig instead unless the resource cannot support a direct registry credential (clusters \u2014 there the registry credential arrives via a pod template, see CreateClusterRequest.templateId).\n",
1248
1537
  properties: {
1249
1538
  args: {
1250
1539
  type: "string",
1251
- description: 'The container\'s command, as a single raw string. This is the field `entrypoint` and `cmd` encode into, exposed in its stored form.\n\nTwo shapes are accepted. A bare shell string is treated as CMD and split into arguments, which is what the console\'s "Container start command" field writes. A JSON object of the form `{"entrypoint":[...],"cmd":[...]}` sets either or both explicitly.\n\nResponses always return both representations: `args` exactly as stored, plus the deconstructed `entrypoint` and `cmd`. Supplying `args` together with `entrypoint` or `cmd` is allowed only when they describe the same command, so a read-modify-write client can send back everything it received. Send `""` to clear, omit to leave unchanged.\n',
1540
+ description: 'The container\'s command as one raw string: a bare shell string is treated as CMD and split into arguments; a JSON object `{"entrypoint":[...],"cmd":[...]}` sets either or both. Send `""` to clear; omit to leave unchanged.',
1252
1541
  examples: [
1253
1542
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
1254
1543
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -1260,7 +1549,7 @@ var generatedTools = [
1260
1549
  items: {
1261
1550
  type: "string"
1262
1551
  },
1263
- description: "Container CMD in exec form. When the image defines an ENTRYPOINT, this is the argument list passed to it. Encoded into the `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
1552
+ description: "Container CMD in exec form: when the image defines an ENTRYPOINT, the argument list passed to it. Encoded into `args`; supply both only when they describe the same command. Send `[]` to clear; omit to leave unchanged.",
1264
1553
  examples: [
1265
1554
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
1266
1555
  ]
@@ -1270,7 +1559,7 @@ var generatedTools = [
1270
1559
  items: {
1271
1560
  type: "string"
1272
1561
  },
1273
- description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
1562
+ description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.",
1274
1563
  examples: [["/bin/bash", "-c"]]
1275
1564
  },
1276
1565
  disk: {
@@ -1306,6 +1595,21 @@ var generatedTools = [
1306
1595
  }
1307
1596
  }
1308
1597
  },
1598
+ BaseMounts: {
1599
+ type: "object",
1600
+ properties: {
1601
+ persistent: {
1602
+ $ref: "#/$defs/PersistentMount"
1603
+ },
1604
+ network: {
1605
+ type: "array",
1606
+ maxItems: 1,
1607
+ items: {
1608
+ $ref: "#/$defs/NetworkMount"
1609
+ }
1610
+ }
1611
+ }
1612
+ },
1309
1613
  ClusterCompute: {
1310
1614
  type: "object",
1311
1615
  additionalProperties: false,
@@ -1333,6 +1637,15 @@ var generatedTools = [
1333
1637
  }
1334
1638
  }
1335
1639
  },
1640
+ ClusterMounts: {
1641
+ description: "Storage mounts for a cluster create: `persistent` or `network`, not both. Clusters cannot mount global volumes, so `global` is rejected.",
1642
+ allOf: [
1643
+ {
1644
+ $ref: "#/$defs/BaseMounts"
1645
+ }
1646
+ ],
1647
+ unevaluatedProperties: false
1648
+ },
1336
1649
  ClusterType: {
1337
1650
  type: "string",
1338
1651
  description: "Cluster type. TRAINING is the generic distributed-training cluster; SLURM provisions a managed Slurm controller/compute topology; RAY provisions a managed Ray head/worker topology; APPLICATION is a general multi-node application cluster.",
@@ -1347,7 +1660,7 @@ var generatedTools = [
1347
1660
  {
1348
1661
  type: "object",
1349
1662
  required: ["name", "type", "compute"],
1350
- description: "Request body for creating a cluster. `compute` defines the\nhomogeneous pod shape; the container configuration (image, env, ports,\n\u2026) applies to every pod. Pass `templateId` to provision every member\npod from a pod template instead of inline container fields \u2014 the\ntemplate also supplies the container registry credential, the only\nprivate-image path for clusters (a bare `registry` property on this\nbody is rejected).\n",
1663
+ description: "Request body for creating a cluster. `compute` defines the homogeneous pod shape; the container configuration (image, env, ports, \u2026) applies to every pod.",
1351
1664
  properties: {
1352
1665
  compute: {
1353
1666
  $ref: "#/$defs/ClusterCompute"
@@ -1363,7 +1676,7 @@ var generatedTools = [
1363
1676
  templateId: {
1364
1677
  type: "string",
1365
1678
  minLength: 1,
1366
- description: "ID of a pod template to provision every member pod from. The\ntemplate supplies the container settings (image, args, disk,\nenv, ports) and the container registry credential for private\nimages \u2014 the only private-image path for clusters. Mutually\nexclusive with `image`, `args`, `entrypoint`, `cmd`, `disk`,\n`env`, `ports`, and `mounts` (rejected with 400). The cluster\nretains the link: the `template` response field is set. Must be\na non-serverless pod template accessible to the caller.\n",
1679
+ description: "ID of a pod template to provision every member pod from: it supplies the container settings (image, args, disk, env, ports) and the registry credential for private images \u2014 the only private-image path for clusters. Mutually exclusive with the inline container fields (`image`, `args`, `entrypoint`, `cmd`, `disk`, `env`, `ports`, `mounts`). Must be a non-serverless template you can access.",
1367
1680
  examples: ["30zmvf89kd"]
1368
1681
  },
1369
1682
  dataCenterIds: {
@@ -1371,11 +1684,11 @@ var generatedTools = [
1371
1684
  items: {
1372
1685
  type: "string"
1373
1686
  },
1374
- description: "Preferred data centers for placement. Omit or pass an empty\narray to let the scheduler choose. A cluster is always placed\nwithin a single data center.\n",
1687
+ description: "Preferred data centers for placement. Omit or pass an empty array to let the scheduler choose. A cluster is always placed within a single data center.",
1375
1688
  example: ["US-TX-3"]
1376
1689
  },
1377
1690
  mounts: {
1378
- $ref: "#/$defs/Mounts"
1691
+ $ref: "#/$defs/ClusterMounts"
1379
1692
  },
1380
1693
  startJupyter: {
1381
1694
  type: "boolean",
@@ -1392,28 +1705,11 @@ var generatedTools = [
1392
1705
  ],
1393
1706
  unevaluatedProperties: false
1394
1707
  },
1395
- Mounts: {
1396
- type: "object",
1397
- additionalProperties: false,
1398
- description: "Storage mounts attached to a pod. At-most-one of `persistent` or\n`network` may be set today (mutually exclusive, enforced at the\nhandler with 400 if both are present). The `network` field is an\narray for forward compatibility with eventual multi-network-volume\nsupport, but `maxItems` is 1 today.\n\nPATCH semantics:\n- Omitting `mounts` or sending `{}` leaves the existing mount\n unchanged.\n- An explicit `network: []` is rejected with 400 (clearing mounts\n is not supported).\n- Mount kind is fixed at create \u2014 a PATCH that introduces a kind\n not present at create (persistent on a network pod, network on\n a persistent pod, or any mount on a previously-mountless pod)\n is rejected with 400.\n- The `volumeId` of a network mount is immutable; a PATCH that\n names a different `volumeId` is rejected with 400.\n- Partial mounts are not supported \u2014 every mount entry must\n include the full schema (`size` + `path` for persistent,\n `volumeId` + `path` for network). Missing required fields \u2192 422.\n",
1399
- properties: {
1400
- persistent: {
1401
- $ref: "#/$defs/PersistentMount"
1402
- },
1403
- network: {
1404
- type: "array",
1405
- maxItems: 1,
1406
- items: {
1407
- $ref: "#/$defs/NetworkMount"
1408
- }
1409
- }
1410
- }
1411
- },
1412
1708
  NetworkMount: {
1413
1709
  type: "object",
1414
1710
  required: ["volumeId", "path"],
1415
1711
  additionalProperties: false,
1416
- description: "Reference to a NetworkVolume. Custom paths are honored at runtime on\nboth GPU and CPU pods. The underlying `volumeId` is immutable\npost-create; the mount `path` may be changed via PATCH.\n",
1712
+ description: "Reference to a NetworkVolume. Custom paths are honored at runtime on both GPU and CPU pods. The underlying `volumeId` is immutable post-create; the mount `path` may be changed via PATCH.",
1417
1713
  properties: {
1418
1714
  volumeId: {
1419
1715
  type: "string",
@@ -1431,7 +1727,7 @@ var generatedTools = [
1431
1727
  type: "object",
1432
1728
  required: ["size", "path"],
1433
1729
  additionalProperties: false,
1434
- description: "Host-local persistent storage. Pinned to the pod's host machine \u2014 data\ndoes not survive a host failure. Disallowed on CPU pods. Mutually\nexclusive with NetworkMount. Deprecated: prefer NetworkMount for any\ndata you cannot recreate.\n",
1730
+ description: "Host-local persistent storage. Pinned to the pod's host machine \u2014 data does not survive a host failure. Disallowed on CPU pods. Mutually exclusive with NetworkMount. Deprecated: prefer NetworkMount for any data you cannot recreate.",
1435
1731
  properties: {
1436
1732
  size: {
1437
1733
  type: "integer",
@@ -1508,6 +1804,7 @@ var generatedTools = [
1508
1804
  idempotentHint: false,
1509
1805
  openWorldHint: true
1510
1806
  },
1807
+ schemaOnDemand: true,
1511
1808
  inputSchema: {
1512
1809
  type: "object",
1513
1810
  properties: {
@@ -1519,11 +1816,10 @@ var generatedTools = [
1519
1816
  $defs: {
1520
1817
  BaseContainerConfig: {
1521
1818
  type: "object",
1522
- description: "Container configuration universal to every containerized resource. Compose ContainerConfig instead unless the resource cannot support a direct registry credential (clusters \u2014 there the registry credential arrives via a pod template, see CreateClusterRequest.templateId).\n",
1523
1819
  properties: {
1524
1820
  args: {
1525
1821
  type: "string",
1526
- description: 'The container\'s command, as a single raw string. This is the field `entrypoint` and `cmd` encode into, exposed in its stored form.\n\nTwo shapes are accepted. A bare shell string is treated as CMD and split into arguments, which is what the console\'s "Container start command" field writes. A JSON object of the form `{"entrypoint":[...],"cmd":[...]}` sets either or both explicitly.\n\nResponses always return both representations: `args` exactly as stored, plus the deconstructed `entrypoint` and `cmd`. Supplying `args` together with `entrypoint` or `cmd` is allowed only when they describe the same command, so a read-modify-write client can send back everything it received. Send `""` to clear, omit to leave unchanged.\n',
1822
+ description: 'The container\'s command as one raw string: a bare shell string is treated as CMD and split into arguments; a JSON object `{"entrypoint":[...],"cmd":[...]}` sets either or both. Send `""` to clear; omit to leave unchanged.',
1527
1823
  examples: [
1528
1824
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
1529
1825
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -1535,7 +1831,7 @@ var generatedTools = [
1535
1831
  items: {
1536
1832
  type: "string"
1537
1833
  },
1538
- description: "Container CMD in exec form. When the image defines an ENTRYPOINT, this is the argument list passed to it. Encoded into the `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
1834
+ description: "Container CMD in exec form: when the image defines an ENTRYPOINT, the argument list passed to it. Encoded into `args`; supply both only when they describe the same command. Send `[]` to clear; omit to leave unchanged.",
1539
1835
  examples: [
1540
1836
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
1541
1837
  ]
@@ -1545,7 +1841,7 @@ var generatedTools = [
1545
1841
  items: {
1546
1842
  type: "string"
1547
1843
  },
1548
- description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
1844
+ description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.",
1549
1845
  examples: [["/bin/bash", "-c"]]
1550
1846
  },
1551
1847
  disk: {
@@ -1605,7 +1901,7 @@ var generatedTools = [
1605
1901
  pools: {
1606
1902
  type: "array",
1607
1903
  minItems: 1,
1608
- description: 'Serverless GPU pool IDs (as returned by `GET /v2/catalog/gpus` in\n`pool`). Workers are placed on whichever listed pool has capacity.\nNarrow a pool down to specific cards with `excludedTypes`.\n\nOn `PATCH`, `pools` and `excludedTypes` are one selection and are\nreplaced together, so sending `pools` by itself **clears the\nexclusions**. Two cases:\n\n- **Changing pools, keeping exclusions** \u2014 send both fields in one\n request: `{"gpu": {"pools": ["ADA_24"], "excludedTypes":\n ["NVIDIA L40"]}}`. `GET` the endpoint first to read the current\n `excludedTypes` and resend the ones that still apply to the new\n pools; an exclusion naming a type outside `pools` is a 400.\n- **Changing only `count` or a CUDA constraint** \u2014 omit `pools`:\n `{"gpu": {"minCudaVersion": "12.4"}}`. The pool list and the\n exclusions are both left exactly as they are.\n\n`excludedTypes` documents the full rule.\n',
1904
+ description: "Serverless GPU pool IDs (the `pool` field of `list-gpu-types`). Workers are placed on whichever listed pool has capacity; narrow to specific cards with `excludedTypes`. On update, `pools` and `excludedTypes` are replaced together, so sending `pools` alone clears the exclusions.",
1609
1905
  items: {
1610
1906
  type: "string"
1611
1907
  },
@@ -1614,7 +1910,7 @@ var generatedTools = [
1614
1910
  excludedTypes: {
1615
1911
  type: "array",
1616
1912
  uniqueItems: true,
1617
- description: 'GPU **type** IDs to subtract from the selected pools \u2014 the `id`\nfield of `GET /v2/catalog/gpus`, the same identifiers pods take in\n`gpu.id`. Workers run on every type in `pools` except these. Omit to\nuse the whole pool.\n\nPools stay the unit of selection; types are the unit of\nsubtraction. There is no inclusive allowlist: a card later added to\none of your pools becomes eligible, which is the honest reading of\n"this pool, minus these".\n\nTied to `pools`, because the two together are one selection:\nsupplying `pools` replaces that selection wholesale, so a `PATCH`\nsending `pools` **without `excludedTypes`** **clears** them \u2014\nrestate them to keep them. A `PATCH` that omits `pools` leaves both\nthe pools and the exclusions untouched, so changing only a CUDA\nconstraint cannot widen a pinned endpoint.\n\nRejected with 400 if a value is not a GPU type in one of `pools`;\nupstream accepts unrecognized exclusions silently, so a typo would\notherwise produce a filter that does nothing. Surrounding whitespace\nis trimmed, so `" NVIDIA L40"` and `"NVIDIA L40"` mean the same card.\n',
1913
+ description: "GPU type IDs to subtract from the selected pools \u2014 the `id` field of `list-gpu-types`, the same identifiers pods take in `gpu.id`. Workers run on every type in `pools` except these; omit to use the whole pool. A value outside `pools` is rejected. On update, restate them whenever you send `pools`, or they are cleared.",
1618
1914
  items: {
1619
1915
  type: "string",
1620
1916
  pattern: "^\\s*[^-\\s]"
@@ -1631,7 +1927,6 @@ var generatedTools = [
1631
1927
  }
1632
1928
  },
1633
1929
  ContainerConfig: {
1634
- description: "Reusable container configuration shared across templates, pods, and serverless endpoints. Adding a field here automatically propagates to all three resources.\n",
1635
1930
  allOf: [
1636
1931
  {
1637
1932
  $ref: "#/$defs/BaseContainerConfig"
@@ -1657,7 +1952,7 @@ var generatedTools = [
1657
1952
  unevaluatedProperties: false
1658
1953
  },
1659
1954
  CreateEndpointGpuConfig: {
1660
- description: "GPU request for an endpoint create. Carries the CUDA constraints, which\nlive here rather than at the body's top level so they are\nunrepresentable on a CPU endpoint.\n",
1955
+ description: "GPU request for an endpoint create. Carries the CUDA constraints, which live here rather than at the body's top level so they are unrepresentable on a CPU endpoint.",
1661
1956
  allOf: [
1662
1957
  {
1663
1958
  $ref: "#/$defs/BaseEndpointGpuConfig"
@@ -1672,13 +1967,13 @@ var generatedTools = [
1672
1967
  type: "string",
1673
1968
  pattern: "^\\d+\\.\\d+$"
1674
1969
  },
1675
- description: "Acceptable CUDA versions for worker placement, as\n`major.minor`. Omit to accept any version (or inherit the\ntemplate's constraint when creating from `templateId`).\nMatching is exact \u2014 discover valid values per GPU type via\n`GET /v2/catalog/gpus?include=AVAILABILITY&product=SERVERLESS`\n(`cudaVersions`).\n\nA non-empty set is mutually exclusive with minCudaVersion (400\nif both are sent). An explicit `[]` states no constraint, so it\nmay accompany a floor.\n",
1970
+ description: "Acceptable CUDA versions for worker placement, as `major.minor`. Omit to accept any version (or inherit the template's constraint when creating from `templateId`).",
1676
1971
  examples: [["12.8", "12.6"]]
1677
1972
  },
1678
1973
  minCudaVersion: {
1679
1974
  type: "string",
1680
1975
  pattern: "^\\d+\\.\\d+$",
1681
- description: "Lowest acceptable CUDA version for worker placement, as\n`major.minor`, compared numerically rather than as a decimal \u2014\nso 12.11 is above 12.2. Use this for an open-ended floor and\nallowedCudaVersions for an exact set.\n\nMutually exclusive with a non-empty allowedCudaVersions (400 if\nboth are sent); an explicit `[]` there states no constraint and\nmay accompany this floor.\n",
1976
+ description: "Lowest acceptable CUDA version for worker placement, as `major.minor`, compared numerically rather than as a decimal \u2014 so 12.11 is above 12.2. Use this for an open-ended floor and allowedCudaVersions for an exact set.",
1682
1977
  examples: ["12.1"]
1683
1978
  }
1684
1979
  }
@@ -1722,13 +2017,13 @@ var generatedTools = [
1722
2017
  $ref: "#/$defs/EndpointType"
1723
2018
  }
1724
2019
  ],
1725
- description: "Request-routing model. Required \u2014 it determines the valid scaler\nand request URLs, so it must be chosen explicitly on every create.\n"
2020
+ description: "Request-routing model. Required \u2014 it determines the valid scaler and request URLs, so it must be chosen explicitly on every create."
1726
2021
  },
1727
2022
  cpu: {
1728
2023
  type: "array",
1729
2024
  minItems: 1,
1730
2025
  uniqueItems: true,
1731
- description: "Eligible CPU configurations for each worker. Memory is derived from the\nselected flavor's catalog RAM multiplier. Exact duplicate configurations\nare rejected; the same flavor may be listed at different vCPU counts.\n",
2026
+ description: "Eligible CPU configurations for each worker. Memory is derived from the selected flavor's catalog RAM multiplier. Exact duplicate configurations are rejected; the same flavor may be listed at different vCPU counts.",
1732
2027
  items: {
1733
2028
  $ref: "#/$defs/CreateCpuConfig"
1734
2029
  }
@@ -1752,12 +2047,17 @@ var generatedTools = [
1752
2047
  type: "array",
1753
2048
  items: {
1754
2049
  type: "string"
1755
- }
2050
+ },
2051
+ description: "Network volume IDs.",
2052
+ deprecated: true
2053
+ },
2054
+ mounts: {
2055
+ $ref: "#/$defs/EndpointMountsInput"
1756
2056
  },
1757
2057
  templateId: {
1758
2058
  type: "string",
1759
2059
  minLength: 1,
1760
- description: "ID of a serverless template to base this endpoint on. The\ntemplate is resolved at create time into the same container\nsettings you could otherwise spread into this body (image,\nargs, disk, ports, env, registry); explicit body fields\noverride the template's, except `env`, which is merged per\nkey with body values winning. The template's\nallowedCudaVersions seeds `gpu.allowedCudaVersions` when the\nbody omits it \u2014 but only for a GPU create, since a CPU endpoint\nhas no gpu block to seed into, and not when the body sets\n`gpu.minCudaVersion`, since seeding a set beside a floor would\nmanufacture the mutual-exclusion 400 from a valid request. Its\npod-specific startSsh/startJupyter flags are\nignored. Later template edits do not affect the endpoint.\nThe template may be one of your own or a public catalog\ntemplate \u2014 see `GET /v2/catalog/templates` (unknown or\ninaccessible ID \u2192 404) \u2014 and must be a serverless template\n(\u2192 422).\n",
2060
+ description: "ID of a serverless template to base this endpoint on (must be serverless). Its container settings (image, args, disk, ports, env, registry) fill in what this body omits; body fields win, and `env` merges per key with body winning. Its allowedCudaVersions seeds `gpu.allowedCudaVersions` when the body omits it; pod-only flags are ignored. Copied once: later edits do not apply.",
1761
2061
  examples: ["30zmvf89kd"]
1762
2062
  },
1763
2063
  timeout: {
@@ -1805,8 +2105,42 @@ var generatedTools = [
1805
2105
  }
1806
2106
  }
1807
2107
  },
2108
+ EndpointMount: {
2109
+ type: "object",
2110
+ required: ["volumeId"],
2111
+ additionalProperties: false,
2112
+ description: "A volume mounted on every worker, always at `/runpod-volume`. Custom paths are not supported yet.",
2113
+ properties: {
2114
+ volumeId: {
2115
+ type: "string",
2116
+ description: "ID of a NetworkVolume (under `network`) or GlobalVolume (under `global`).",
2117
+ examples: ["vol_abc"]
2118
+ }
2119
+ }
2120
+ },
2121
+ EndpointMountsInput: {
2122
+ type: "object",
2123
+ additionalProperties: false,
2124
+ description: "Volumes to mount on the endpoint's workers. A `network` or `global` list that is present replaces the endpoint's current list of that kind; an empty list detaches it, and an omitted list is left unchanged.",
2125
+ properties: {
2126
+ network: {
2127
+ type: "array",
2128
+ description: "Network volumes, at most one per data center.",
2129
+ items: {
2130
+ $ref: "#/$defs/EndpointMount"
2131
+ }
2132
+ },
2133
+ global: {
2134
+ type: "array",
2135
+ maxItems: 1,
2136
+ items: {
2137
+ $ref: "#/$defs/EndpointMount"
2138
+ }
2139
+ }
2140
+ }
2141
+ },
1808
2142
  EndpointScaling: {
1809
- description: "Autoscaling signal \u2014 a discriminated union on `type`: `QUEUE_DELAY`\n(queue-based endpoints only) or `REQUEST_COUNT`. The scaler is chosen\nindependently of the endpoint's routing `type` and can be switched on\nupdate.\n",
2143
+ description: "Autoscaling signal \u2014 a discriminated union on `type`: `QUEUE_DELAY` (queue-based endpoints only) or `REQUEST_COUNT`. The scaler is chosen independently of the endpoint's routing `type` and can be switched on update.",
1810
2144
  oneOf: [
1811
2145
  {
1812
2146
  $ref: "#/$defs/QueueDelayScaling"
@@ -1825,7 +2159,7 @@ var generatedTools = [
1825
2159
  },
1826
2160
  EndpointType: {
1827
2161
  type: "string",
1828
- description: "Request-routing semantics for a modern serverless endpoint.\n- `QUEUE` \u2014 submit asynchronous or synchronous jobs through the managed queue.\n- `LOAD_BALANCER` \u2014 send requests directly to worker-defined HTTP paths.\n Configure via `env`: `PORT` (server port, default 80), `PORT_HEALTH`\n (health-check port, default 80), and `HEALTH_CHECK_PATH` (path the\n load balancer polls for worker health, default `/ping`).\n",
2162
+ description: "Request-routing semantics for a modern serverless endpoint. `QUEUE` \u2014 submit asynchronous or synchronous jobs through the managed queue. `LOAD_BALANCER` \u2014 send requests directly to worker-defined HTTP paths.",
1829
2163
  "x-enum-varnames": ["EndpointTypeQueue", "EndpointTypeLoadBalancer"],
1830
2164
  enum: ["QUEUE", "LOAD_BALANCER"]
1831
2165
  },
@@ -1849,14 +2183,14 @@ var generatedTools = [
1849
2183
  type: "integer",
1850
2184
  minimum: 1,
1851
2185
  maximum: 3600,
1852
- description: "Seconds before idle workers scale down. Not applicable to queue-based\nendpoints scaling on `requestCount` \u2014 rejected on create/update and\nomitted from responses for that combination.\n",
2186
+ description: "Seconds before idle workers scale down. Not applicable to queue-based endpoints scaling on `requestCount` \u2014 rejected on create/update and omitted from responses for that combination.",
1853
2187
  examples: [5]
1854
2188
  }
1855
2189
  }
1856
2190
  },
1857
2191
  FlashBoot: {
1858
2192
  type: "string",
1859
- description: "FlashBoot cold-start acceleration mode.\n- `OFF` \u2014 disabled\n- `FLASHBOOT` \u2014 enabled\n- `PRIORITY_FLASHBOOT` \u2014 enabled with priority capacity\n",
2193
+ description: "FlashBoot cold-start acceleration mode. `OFF` \u2014 disabled. `FLASHBOOT` \u2014 enabled. `PRIORITY_FLASHBOOT` \u2014 enabled with priority capacity",
1860
2194
  enum: ["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]
1861
2195
  },
1862
2196
  QueueDelayScaling: {
@@ -1882,7 +2216,7 @@ var generatedTools = [
1882
2216
  RequestCountScaling: {
1883
2217
  type: "object",
1884
2218
  additionalProperties: false,
1885
- description: "Scale on concurrent in-flight requests per worker. Required for\nload-balancing endpoints; also selectable for queue-based.\n",
2219
+ description: "Scale on concurrent in-flight requests per worker. Required for load-balancing endpoints; also selectable for queue-based.",
1886
2220
  required: ["type", "requestCount"],
1887
2221
  properties: {
1888
2222
  type: {
@@ -1901,6 +2235,47 @@ var generatedTools = [
1901
2235
  }
1902
2236
  }
1903
2237
  },
2238
+ {
2239
+ name: "create-global-volume",
2240
+ operationId: "createGlobalVolume",
2241
+ description: "Create a global volume. Provisions a new global volume \u2014 storage that mounts into workloads in any data center. Only a `name` is required; storage is provisioned for you. Accounts have a cap on how many global volumes they can hold; creating one past it fails with `400`. Returns `201` with the created global volume, including its assigned `id`.",
2242
+ method: "POST",
2243
+ path: "/v2/global-volumes",
2244
+ params: [],
2245
+ hasBody: true,
2246
+ annotations: {
2247
+ readOnlyHint: false,
2248
+ destructiveHint: false,
2249
+ idempotentHint: false,
2250
+ openWorldHint: true
2251
+ },
2252
+ inputSchema: {
2253
+ type: "object",
2254
+ properties: {
2255
+ body: {
2256
+ $ref: "#/$defs/CreateGlobalVolumeRequest"
2257
+ }
2258
+ },
2259
+ required: ["body"],
2260
+ $defs: {
2261
+ CreateGlobalVolumeRequest: {
2262
+ type: "object",
2263
+ additionalProperties: false,
2264
+ required: ["name"],
2265
+ properties: {
2266
+ name: {
2267
+ type: "string",
2268
+ minLength: 1,
2269
+ maxLength: 191,
2270
+ pattern: "[^\\p{Z}\\t-\\r\x85]",
2271
+ description: "Human-readable name, 1\u2013191 characters with at least one non-whitespace character. Leading and trailing whitespace is trimmed.",
2272
+ examples: ["training-data"]
2273
+ }
2274
+ }
2275
+ }
2276
+ }
2277
+ }
2278
+ },
1904
2279
  {
1905
2280
  name: "create-network-volume",
1906
2281
  operationId: "createNetworkVolume",
@@ -1954,7 +2329,7 @@ var generatedTools = [
1954
2329
  $ref: "#/$defs/VolumeType"
1955
2330
  }
1956
2331
  ],
1957
- description: "Storage tier for the volume. Optional. When omitted, the volume is\nprovisioned using the requested data center's default (primary)\nstorage tier. HIGH_PERFORMANCE provisions a high-performance (HPS)\nvolume; STANDARD provisions a standard volume. A volume's tier is\nimmutable after creation.\n"
2332
+ description: "Storage tier for the volume. Optional. When omitted, the volume is provisioned using the requested data center's default (primary) storage tier."
1958
2333
  }
1959
2334
  }
1960
2335
  },
@@ -1980,6 +2355,7 @@ var generatedTools = [
1980
2355
  idempotentHint: false,
1981
2356
  openWorldHint: true
1982
2357
  },
2358
+ schemaOnDemand: true,
1983
2359
  inputSchema: {
1984
2360
  type: "object",
1985
2361
  properties: {
@@ -1991,11 +2367,10 @@ var generatedTools = [
1991
2367
  $defs: {
1992
2368
  BaseContainerConfig: {
1993
2369
  type: "object",
1994
- description: "Container configuration universal to every containerized resource. Compose ContainerConfig instead unless the resource cannot support a direct registry credential (clusters \u2014 there the registry credential arrives via a pod template, see CreateClusterRequest.templateId).\n",
1995
2370
  properties: {
1996
2371
  args: {
1997
2372
  type: "string",
1998
- description: 'The container\'s command, as a single raw string. This is the field `entrypoint` and `cmd` encode into, exposed in its stored form.\n\nTwo shapes are accepted. A bare shell string is treated as CMD and split into arguments, which is what the console\'s "Container start command" field writes. A JSON object of the form `{"entrypoint":[...],"cmd":[...]}` sets either or both explicitly.\n\nResponses always return both representations: `args` exactly as stored, plus the deconstructed `entrypoint` and `cmd`. Supplying `args` together with `entrypoint` or `cmd` is allowed only when they describe the same command, so a read-modify-write client can send back everything it received. Send `""` to clear, omit to leave unchanged.\n',
2373
+ description: 'The container\'s command as one raw string: a bare shell string is treated as CMD and split into arguments; a JSON object `{"entrypoint":[...],"cmd":[...]}` sets either or both. Send `""` to clear; omit to leave unchanged.',
1999
2374
  examples: [
2000
2375
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
2001
2376
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -2007,7 +2382,7 @@ var generatedTools = [
2007
2382
  items: {
2008
2383
  type: "string"
2009
2384
  },
2010
- description: "Container CMD in exec form. When the image defines an ENTRYPOINT, this is the argument list passed to it. Encoded into the `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
2385
+ description: "Container CMD in exec form: when the image defines an ENTRYPOINT, the argument list passed to it. Encoded into `args`; supply both only when they describe the same command. Send `[]` to clear; omit to leave unchanged.",
2011
2386
  examples: [
2012
2387
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
2013
2388
  ]
@@ -2017,7 +2392,7 @@ var generatedTools = [
2017
2392
  items: {
2018
2393
  type: "string"
2019
2394
  },
2020
- description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
2395
+ description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.",
2021
2396
  examples: [["/bin/bash", "-c"]]
2022
2397
  },
2023
2398
  disk: {
@@ -2089,13 +2464,27 @@ var generatedTools = [
2089
2464
  }
2090
2465
  }
2091
2466
  },
2467
+ BaseMounts: {
2468
+ type: "object",
2469
+ properties: {
2470
+ persistent: {
2471
+ $ref: "#/$defs/PersistentMount"
2472
+ },
2473
+ network: {
2474
+ type: "array",
2475
+ maxItems: 1,
2476
+ items: {
2477
+ $ref: "#/$defs/NetworkMount"
2478
+ }
2479
+ }
2480
+ }
2481
+ },
2092
2482
  Cloud: {
2093
2483
  type: "string",
2094
- description: "Cloud tier.\n- `SECURE` \u2014 Runpod-owned datacenter hardware\n- `COMMUNITY` \u2014 community-hosted hardware\n",
2484
+ description: "Cloud tier. `SECURE` \u2014 Runpod-owned datacenter hardware. `COMMUNITY` \u2014 community-hosted hardware",
2095
2485
  enum: ["SECURE", "COMMUNITY"]
2096
2486
  },
2097
2487
  ContainerConfig: {
2098
- description: "Reusable container configuration shared across templates, pods, and serverless endpoints. Adding a field here automatically propagates to all three resources.\n",
2099
2488
  allOf: [
2100
2489
  {
2101
2490
  $ref: "#/$defs/BaseContainerConfig"
@@ -2121,7 +2510,7 @@ var generatedTools = [
2121
2510
  unevaluatedProperties: false
2122
2511
  },
2123
2512
  CreateGpuConfig: {
2124
- description: "GPU request for a pod create. Carries the CUDA host constraints, which\nlive here rather than at the body's top level so they are\nunrepresentable on a CPU pod.\n",
2513
+ description: "GPU request for a pod create. Carries the CUDA host constraints, which live here rather than at the body's top level so they are unrepresentable on a CPU pod.",
2125
2514
  allOf: [
2126
2515
  {
2127
2516
  $ref: "#/$defs/BaseGpuConfig"
@@ -2135,13 +2524,13 @@ var generatedTools = [
2135
2524
  type: "string",
2136
2525
  pattern: "^\\d+\\.\\d+$"
2137
2526
  },
2138
- description: "Acceptable CUDA versions for the host machine, as `major.minor`.\nOmit to accept any version. Matching is exact, so a version no\nmachine reports yields a capacity error rather than a fallback \u2014\ndiscover valid values per GPU type via\n`GET /v2/catalog/gpus?include=AVAILABILITY&product=POD`\n(`cudaVersions`).\n\nA non-empty set is mutually exclusive with minCudaVersion (400\nif both are sent). An explicit `[]` states no constraint, so it\nmay accompany a floor.\n",
2527
+ description: "Acceptable CUDA versions for the host machine, as `major.minor`. Omit to accept any version.",
2139
2528
  examples: [["12.8", "12.6"]]
2140
2529
  },
2141
2530
  minCudaVersion: {
2142
2531
  type: "string",
2143
2532
  pattern: "^\\d+\\.\\d+$",
2144
- description: "Lowest acceptable CUDA version for the host machine, as\n`major.minor`, compared numerically rather than as a decimal \u2014\nso 12.11 is above 12.2. Use this for an open-ended floor and\nallowedCudaVersions for an exact set.\n\nMutually exclusive with a non-empty allowedCudaVersions (400 if\nboth are sent); an explicit `[]` there states no constraint and\nmay accompany this floor.\n",
2533
+ description: "Lowest acceptable CUDA version for the host machine, as `major.minor`, compared numerically rather than as a decimal \u2014 so 12.11 is above 12.2. Use this for an open-ended floor and allowedCudaVersions for an exact set.",
2145
2534
  examples: ["12.1"]
2146
2535
  },
2147
2536
  minRamPerGpu: {
@@ -2169,7 +2558,7 @@ var generatedTools = [
2169
2558
  {
2170
2559
  type: "object",
2171
2560
  required: ["name"],
2172
- description: "Request body for creating a pod. Exactly one of `gpu` or `cpu`\nmust be set \u2014 enforced at the handler layer. For CPU pods, memory\nis derived by the API from the selected flavor's RAM multiplier;\nclients provide only CPU flavor and vCPU count. CPU pods support\ncontainer disk and network volumes only; `mounts.persistent` is\ninvalid when `cpu` is set.\n\n`image` is required unless `templateId` is set.\n",
2561
+ description: "Request body for creating a pod. Exactly one of `gpu` or `cpu` must be set \u2014 enforced at the handler layer.",
2173
2562
  properties: {
2174
2563
  name: {
2175
2564
  type: "string",
@@ -2193,13 +2582,13 @@ var generatedTools = [
2193
2582
  items: {
2194
2583
  type: "string"
2195
2584
  },
2196
- description: "Preferred data centers for placement. Omit or pass an empty\narray to let the scheduler choose.\n",
2585
+ description: "Preferred data centers for placement. Omit or pass an empty array to let the scheduler choose.",
2197
2586
  examples: [["US-TX-3"]]
2198
2587
  },
2199
2588
  globalNetworking: {
2200
2589
  type: "boolean",
2201
2590
  default: false,
2202
- description: "Enable global networking, giving the pod a private IP reachable across data centers. Requires an NVIDIA GPU and a global-networking-enabled data center (both enforced upstream). See `GET /v2/catalog/datacenters` (`globalNetwork`) for eligible data centers.",
2591
+ description: "Enable global networking, giving the pod a private IP reachable across data centers. Requires an NVIDIA GPU and a global-networking-enabled data center (both enforced upstream).",
2203
2592
  examples: [false]
2204
2593
  },
2205
2594
  gpu: {
@@ -2211,19 +2600,19 @@ var generatedTools = [
2211
2600
  startJupyter: {
2212
2601
  type: "boolean",
2213
2602
  default: false,
2214
- description: "Create-time flag telling the provisioner to start JupyterLab:\ninjects a generated `JUPYTER_PASSWORD` environment variable,\nunless the request already sets one. Only images that honor\nthe convention start Jupyter from it (Runpod official images\ndo); expose `8888/http` in `ports` to reach it.\n\nNot part of the pod's readable config \u2014 never returned by\nGET and not changeable by PATCH.\n",
2603
+ description: "Create-time flag: starts JupyterLab by injecting a generated `JUPYTER_PASSWORD` environment variable, unless the request sets one. Expose `8888/http` in `ports` to reach it.",
2215
2604
  examples: [true]
2216
2605
  },
2217
2606
  startSsh: {
2218
2607
  type: "boolean",
2219
2608
  default: false,
2220
- description: "Create-time flag telling the provisioner to set up SSH\naccess: injects a `PUBLIC_KEY` environment variable carrying\nyour account's registered SSH public keys, unless the request\nalready sets one. **Requires registered keys** (`PUT\n/v2/account/ssh-keys`) \u2014 with none registered the flag does\nnothing and the pod has no SSH access. Only images that honor\nthe convention start sshd from it (all Runpod official images\ndo). Connect using the pod's `ssh` block; the `ssh.direct`\nvariant additionally needs a `22/tcp` entry in `ports`.\n\nNot part of the pod's readable config \u2014 never returned by\nGET and not changeable by PATCH.\n",
2609
+ description: "Create-time flag: sets up SSH access by injecting a `PUBLIC_KEY` environment variable with your account's registered SSH keys (`update-ssh-keys`), unless the request sets one. With no registered keys it does nothing and the pod has no SSH access. Direct SSH also needs a `22/tcp` entry in `ports`.",
2221
2610
  examples: [true]
2222
2611
  },
2223
2612
  templateId: {
2224
2613
  type: "string",
2225
2614
  minLength: 1,
2226
- description: "ID of a pod template to base this pod on. The template is\nresolved at create time into the same container settings you\ncould otherwise spread into this body (image, args, disk,\nports, env, registry, persistent mount, startSsh,\nstartJupyter, allowedCudaVersions); explicit body fields\noverride the template's, except `env`, which is merged per\nkey with body values winning. Sending either CUDA field\n(`gpu.allowedCudaVersions` or `gpu.minCudaVersion`) replaces\nthe template's CUDA constraint entirely, and CPU pods ignore\nit (like the persistent mount). The template is a one-time\nsource of settings: later template edits do not affect the\npod, and the created pod does not retain a link to the\ntemplate (`template` stays null). The template may be one\nof your own or a public catalog template \u2014 see\n`GET /v2/catalog/templates` (unknown or inaccessible ID \u2192\n404) \u2014 and must not be a serverless template (\u2192 422). CPU\npods do not inherit a template's persistent mount.\n",
2615
+ description: "ID of a pod template to base this pod on (not a serverless one). Its container settings (image, args, disk, ports, env, registry, persistent mount) fill in what this body omits; body fields win, and `env` merges per key with body winning. A CUDA field in `gpu` replaces the template's CUDA constraint; CPU pods ignore its mount. Copied once: later template edits do not affect the pod.",
2227
2616
  examples: ["30zmvf89kd"]
2228
2617
  }
2229
2618
  }
@@ -2239,28 +2628,51 @@ var generatedTools = [
2239
2628
  required: ["image"]
2240
2629
  }
2241
2630
  },
2242
- Mounts: {
2631
+ GlobalMount: {
2243
2632
  type: "object",
2633
+ required: ["volumeId"],
2244
2634
  additionalProperties: false,
2245
- description: "Storage mounts attached to a pod. At-most-one of `persistent` or\n`network` may be set today (mutually exclusive, enforced at the\nhandler with 400 if both are present). The `network` field is an\narray for forward compatibility with eventual multi-network-volume\nsupport, but `maxItems` is 1 today.\n\nPATCH semantics:\n- Omitting `mounts` or sending `{}` leaves the existing mount\n unchanged.\n- An explicit `network: []` is rejected with 400 (clearing mounts\n is not supported).\n- Mount kind is fixed at create \u2014 a PATCH that introduces a kind\n not present at create (persistent on a network pod, network on\n a persistent pod, or any mount on a previously-mountless pod)\n is rejected with 400.\n- The `volumeId` of a network mount is immutable; a PATCH that\n names a different `volumeId` is rejected with 400.\n- Partial mounts are not supported \u2014 every mount entry must\n include the full schema (`size` + `path` for persistent,\n `volumeId` + `path` for network). Missing required fields \u2192 422.\n",
2635
+ description: "Reference to a GlobalVolume. Usage-billed, so it has no size. The underlying `volumeId` is immutable post-create; the mount `path` may be changed via PATCH.",
2246
2636
  properties: {
2247
- persistent: {
2248
- $ref: "#/$defs/PersistentMount"
2637
+ volumeId: {
2638
+ type: "string",
2639
+ description: "ID of an existing GlobalVolume.",
2640
+ examples: ["gsb_abc"]
2249
2641
  },
2250
- network: {
2251
- type: "array",
2252
- maxItems: 1,
2253
- items: {
2254
- $ref: "#/$defs/NetworkMount"
2255
- }
2642
+ path: {
2643
+ type: "string",
2644
+ description: "Mount path inside the container. Optional on create, where a default is chosen for you; a PATCH that omits it leaves the path unchanged.",
2645
+ examples: ["/data"]
2256
2646
  }
2257
2647
  }
2258
2648
  },
2649
+ Mounts: {
2650
+ description: "Storage mounts for the pod: `persistent` (size + path) excludes both `network` and `global`; `network` (volumeId + path) and `global` (volumeId, optional path) take one entry each and can be combined. Every entry needs its full shape. On update, omit the field to leave the mounts as they are; the mount kind and a volume's volumeId cannot change, and mounts cannot be cleared.",
2651
+ allOf: [
2652
+ {
2653
+ $ref: "#/$defs/BaseMounts"
2654
+ },
2655
+ {
2656
+ type: "object",
2657
+ properties: {
2658
+ global: {
2659
+ type: "array",
2660
+ maxItems: 1,
2661
+ description: "At most one global volume, on GPU and CPU pods, with no data center constraint. Combinable with `network` but not with `persistent` (rejected): upstream drops a persistent disk whenever a volume is attached.",
2662
+ items: {
2663
+ $ref: "#/$defs/GlobalMount"
2664
+ }
2665
+ }
2666
+ }
2667
+ }
2668
+ ],
2669
+ unevaluatedProperties: false
2670
+ },
2259
2671
  NetworkMount: {
2260
2672
  type: "object",
2261
2673
  required: ["volumeId", "path"],
2262
2674
  additionalProperties: false,
2263
- description: "Reference to a NetworkVolume. Custom paths are honored at runtime on\nboth GPU and CPU pods. The underlying `volumeId` is immutable\npost-create; the mount `path` may be changed via PATCH.\n",
2675
+ description: "Reference to a NetworkVolume. Custom paths are honored at runtime on both GPU and CPU pods. The underlying `volumeId` is immutable post-create; the mount `path` may be changed via PATCH.",
2264
2676
  properties: {
2265
2677
  volumeId: {
2266
2678
  type: "string",
@@ -2278,7 +2690,7 @@ var generatedTools = [
2278
2690
  type: "object",
2279
2691
  required: ["size", "path"],
2280
2692
  additionalProperties: false,
2281
- description: "Host-local persistent storage. Pinned to the pod's host machine \u2014 data\ndoes not survive a host failure. Disallowed on CPU pods. Mutually\nexclusive with NetworkMount. Deprecated: prefer NetworkMount for any\ndata you cannot recreate.\n",
2693
+ description: "Host-local persistent storage. Pinned to the pod's host machine \u2014 data does not survive a host failure. Disallowed on CPU pods. Mutually exclusive with NetworkMount. Deprecated: prefer NetworkMount for any data you cannot recreate.",
2282
2694
  properties: {
2283
2695
  size: {
2284
2696
  type: "integer",
@@ -2361,6 +2773,7 @@ var generatedTools = [
2361
2773
  idempotentHint: false,
2362
2774
  openWorldHint: true
2363
2775
  },
2776
+ schemaOnDemand: true,
2364
2777
  inputSchema: {
2365
2778
  type: "object",
2366
2779
  properties: {
@@ -2380,7 +2793,7 @@ var generatedTools = [
2380
2793
  minLength: 1,
2381
2794
  maxLength: 191,
2382
2795
  pattern: "^[a-zA-Z_][a-zA-Z0-9_.\\-/]*$",
2383
- description: "Unique name for the secret \u2014 referenced from environment variables as `{{ RUNPOD_SECRET_<name> }}`; immutable after creation. Maximum 191 characters, must start with a letter or underscore, and may contain letters, digits, and `_.-/`. Names beginning with the reserved prefix `RUNPOD` are rejected (case-insensitive).",
2796
+ description: "Unique name for the secret \u2014 referenced from environment variables as `{{ RUNPOD_SECRET_<name> }}`; immutable after creation. Maximum 191 characters, must start with a letter or underscore, and may contain letters, digits, and `_.-/`.",
2384
2797
  examples: ["hf-token"]
2385
2798
  },
2386
2799
  value: {
@@ -2414,6 +2827,7 @@ var generatedTools = [
2414
2827
  idempotentHint: false,
2415
2828
  openWorldHint: true
2416
2829
  },
2830
+ schemaOnDemand: true,
2417
2831
  inputSchema: {
2418
2832
  type: "object",
2419
2833
  properties: {
@@ -2425,11 +2839,10 @@ var generatedTools = [
2425
2839
  $defs: {
2426
2840
  BaseContainerConfig: {
2427
2841
  type: "object",
2428
- description: "Container configuration universal to every containerized resource. Compose ContainerConfig instead unless the resource cannot support a direct registry credential (clusters \u2014 there the registry credential arrives via a pod template, see CreateClusterRequest.templateId).\n",
2429
2842
  properties: {
2430
2843
  args: {
2431
2844
  type: "string",
2432
- description: 'The container\'s command, as a single raw string. This is the field `entrypoint` and `cmd` encode into, exposed in its stored form.\n\nTwo shapes are accepted. A bare shell string is treated as CMD and split into arguments, which is what the console\'s "Container start command" field writes. A JSON object of the form `{"entrypoint":[...],"cmd":[...]}` sets either or both explicitly.\n\nResponses always return both representations: `args` exactly as stored, plus the deconstructed `entrypoint` and `cmd`. Supplying `args` together with `entrypoint` or `cmd` is allowed only when they describe the same command, so a read-modify-write client can send back everything it received. Send `""` to clear, omit to leave unchanged.\n',
2845
+ description: 'The container\'s command as one raw string: a bare shell string is treated as CMD and split into arguments; a JSON object `{"entrypoint":[...],"cmd":[...]}` sets either or both. Send `""` to clear; omit to leave unchanged.',
2433
2846
  examples: [
2434
2847
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
2435
2848
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -2441,7 +2854,7 @@ var generatedTools = [
2441
2854
  items: {
2442
2855
  type: "string"
2443
2856
  },
2444
- description: "Container CMD in exec form. When the image defines an ENTRYPOINT, this is the argument list passed to it. Encoded into the `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
2857
+ description: "Container CMD in exec form: when the image defines an ENTRYPOINT, the argument list passed to it. Encoded into `args`; supply both only when they describe the same command. Send `[]` to clear; omit to leave unchanged.",
2445
2858
  examples: [
2446
2859
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
2447
2860
  ]
@@ -2451,7 +2864,7 @@ var generatedTools = [
2451
2864
  items: {
2452
2865
  type: "string"
2453
2866
  },
2454
- description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
2867
+ description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.",
2455
2868
  examples: [["/bin/bash", "-c"]]
2456
2869
  },
2457
2870
  disk: {
@@ -2488,7 +2901,6 @@ var generatedTools = [
2488
2901
  }
2489
2902
  },
2490
2903
  ContainerConfig: {
2491
- description: "Reusable container configuration shared across templates, pods, and serverless endpoints. Adding a field here automatically propagates to all three resources.\n",
2492
2904
  allOf: [
2493
2905
  {
2494
2906
  $ref: "#/$defs/BaseContainerConfig"
@@ -2525,7 +2937,7 @@ var generatedTools = [
2525
2937
  type: "string",
2526
2938
  pattern: "^\\d+\\.\\d+$"
2527
2939
  },
2528
- description: "Acceptable CUDA versions for containers created from this\ntemplate, as `major.minor`. Omit to accept any version \u2014 see\nthe same field on `createPod` for matching semantics.\nExpanded into GPU pod and serverless endpoint creates; CPU\npods ignore it.\n",
2940
+ description: "Acceptable CUDA versions for containers created from this template, as `major.minor`. Omit to accept any version \u2014 see the same field on `createPod` for matching semantics.",
2529
2941
  examples: [["12.8", "12.6"]]
2530
2942
  },
2531
2943
  category: {
@@ -2551,13 +2963,13 @@ var generatedTools = [
2551
2963
  startJupyter: {
2552
2964
  type: "boolean",
2553
2965
  default: true,
2554
- description: "Start JupyterLab in containers created from this template:\ninjects a generated `JUPYTER_PASSWORD` environment variable,\nunless `env` already sets one. Only images that honor the\nconvention start Jupyter from it (Runpod official images do);\nexpose `8888/http` in `ports` to reach it. Defaults to `true`\nwhen omitted, matching console-created templates.\n",
2966
+ description: "Start JupyterLab in containers created from this template: injects a generated `JUPYTER_PASSWORD` environment variable, unless `env` already sets one.",
2555
2967
  examples: [false]
2556
2968
  },
2557
2969
  startSsh: {
2558
2970
  type: "boolean",
2559
2971
  default: true,
2560
- description: "Provision SSH access in containers created from this template:\ninjects a `PUBLIC_KEY` environment variable carrying the\ndeployer's registered SSH public keys (`PUT\n/v2/account/ssh-keys` \u2014 with none registered the flag does\nnothing), unless `env` already sets one. Only images that\nhonor the convention start sshd from it (all Runpod official\nimages do); direct SSH also needs a `22/tcp` entry in\n`ports`. Defaults to `true` when omitted, matching\nconsole-created templates.\n",
2972
+ description: "Provision SSH access in containers created from this template: injects a `PUBLIC_KEY` environment variable carrying the deployer's registered SSH public keys (`update-ssh-keys` \u2014 with none registered the flag does nothing), unless `env` already sets one.",
2561
2973
  examples: [true]
2562
2974
  }
2563
2975
  }
@@ -2569,7 +2981,7 @@ var generatedTools = [
2569
2981
  type: "object",
2570
2982
  required: ["size", "path"],
2571
2983
  additionalProperties: false,
2572
- description: "Host-local persistent storage. Pinned to the pod's host machine \u2014 data\ndoes not survive a host failure. Disallowed on CPU pods. Mutually\nexclusive with NetworkMount. Deprecated: prefer NetworkMount for any\ndata you cannot recreate.\n",
2984
+ description: "Host-local persistent storage. Pinned to the pod's host machine \u2014 data does not survive a host failure. Disallowed on CPU pods. Mutually exclusive with NetworkMount. Deprecated: prefer NetworkMount for any data you cannot recreate.",
2573
2985
  properties: {
2574
2986
  size: {
2575
2987
  type: "integer",
@@ -2586,13 +2998,13 @@ var generatedTools = [
2586
2998
  },
2587
2999
  TemplateCategory: {
2588
3000
  type: "string",
2589
- description: "Controls how the template is grouped and filtered in the Runpod console.\nIt does not affect hardware selection, scheduling, or billing.\n- `CPU` \u2014 CPU-only workloads\n- `NVIDIA` \u2014 NVIDIA GPU workloads\n- `AMD` \u2014 AMD GPU workloads\n",
3001
+ description: "Controls how the template is grouped and filtered in the Runpod console. It does not affect hardware selection, scheduling, or billing. `CPU` \u2014 CPU-only workloads. `NVIDIA` \u2014 NVIDIA GPU workloads. `AMD` \u2014 AMD GPU workloads",
2590
3002
  enum: ["CPU", "NVIDIA", "AMD"]
2591
3003
  },
2592
3004
  TemplateMounts: {
2593
3005
  type: "object",
2594
3006
  additionalProperties: false,
2595
- description: "Storage mounts attached to a template. Templates support only a\nsingle persistent mount today; any `network` property is rejected\nwith 422 by the schema validator.\n\nPATCH semantics: omitting `mounts` or sending `{}` leaves the\nexisting mount unchanged.\n",
3007
+ description: "Storage mounts for containers from this template: a single `persistent` mount only (`network` is rejected). On update, omitting `mounts` or sending `{}` leaves the existing mount unchanged.",
2596
3008
  properties: {
2597
3009
  persistent: {
2598
3010
  $ref: "#/$defs/PersistentMount"
@@ -2662,6 +3074,36 @@ var generatedTools = [
2662
3074
  required: ["id"]
2663
3075
  }
2664
3076
  },
3077
+ {
3078
+ name: "delete-global-volume",
3079
+ operationId: "deleteGlobalVolume",
3080
+ description: "Delete a global volume. Permanently deletes a global volume and its stored data. Fails with `409` while the volume is still mounted: terminate every pod that mounts it (stopped pods count) and detach it from every serverless endpoint first. Nothing is detached or terminated on your behalf.",
3081
+ method: "DELETE",
3082
+ path: "/v2/global-volumes/{id}",
3083
+ params: [
3084
+ {
3085
+ name: "id",
3086
+ location: "path"
3087
+ }
3088
+ ],
3089
+ hasBody: false,
3090
+ annotations: {
3091
+ readOnlyHint: false,
3092
+ destructiveHint: true,
3093
+ idempotentHint: true,
3094
+ openWorldHint: true
3095
+ },
3096
+ inputSchema: {
3097
+ type: "object",
3098
+ properties: {
3099
+ id: {
3100
+ type: "string",
3101
+ description: "Global volume identifier"
3102
+ }
3103
+ },
3104
+ required: ["id"]
3105
+ }
3106
+ },
2665
3107
  {
2666
3108
  name: "delete-network-volume",
2667
3109
  operationId: "deleteNetworkVolume",
@@ -3027,12 +3469,42 @@ var generatedTools = [
3027
3469
  },
3028
3470
  buildId: {
3029
3471
  type: "string",
3030
- description: "GitHub build identifier (from GET /v2/serverless/{id}/builds or a release's `buildId`)"
3472
+ description: "GitHub build identifier (from `list-endpoint-builds` or a release's `buildId`)"
3031
3473
  }
3032
3474
  },
3033
3475
  required: ["id", "buildId"]
3034
3476
  }
3035
3477
  },
3478
+ {
3479
+ name: "get-global-volume",
3480
+ operationId: "getGlobalVolume",
3481
+ description: "Get a global volume. Returns a single global volume by ID.",
3482
+ method: "GET",
3483
+ path: "/v2/global-volumes/{id}",
3484
+ params: [
3485
+ {
3486
+ name: "id",
3487
+ location: "path"
3488
+ }
3489
+ ],
3490
+ hasBody: false,
3491
+ annotations: {
3492
+ readOnlyHint: true,
3493
+ destructiveHint: false,
3494
+ idempotentHint: true,
3495
+ openWorldHint: true
3496
+ },
3497
+ inputSchema: {
3498
+ type: "object",
3499
+ properties: {
3500
+ id: {
3501
+ type: "string",
3502
+ description: "Global volume identifier"
3503
+ }
3504
+ },
3505
+ required: ["id"]
3506
+ }
3507
+ },
3036
3508
  {
3037
3509
  name: "get-gpu-type",
3038
3510
  operationId: "getGpuType",
@@ -3103,7 +3575,7 @@ var generatedTools = [
3103
3575
  items: {
3104
3576
  $ref: "#/$defs/Product"
3105
3577
  },
3106
- description: "Comma-separated availability product contexts. Supported values: POD, CLUSTER, SERVERLESS. Required with include=AVAILABILITY, and valid only with it (400 either way). There is no default: the same GPU type can be scarce for pods and plentiful for serverless, so the context has to be stated rather than assumed."
3578
+ description: "Comma-separated availability product contexts. Supported values: POD, CLUSTER, SERVERLESS. Required with include=AVAILABILITY, and valid only with it (400 either way)."
3107
3579
  },
3108
3580
  count: {
3109
3581
  type: "integer",
@@ -3121,7 +3593,7 @@ var generatedTools = [
3121
3593
  type: "string",
3122
3594
  pattern: "^[A-Z]{2}$"
3123
3595
  },
3124
- description: "Comma-separated ISO 3166-1 alpha-2 country codes, uppercase, to constrain availability to \u2014 e.g. FR or FR,DE. Values within this filter use OR semantics. Valid only with include=AVAILABILITY (400 otherwise); a malformed entry is a 422. Scopes availability, lowest-price calculations and the dataCenters array to those countries, so a listed data center outside them is omitted rather than returned with availability NONE. On the list endpoint a GPU type with no data center in those countries drops out entirely; the single-GPU endpoint still returns the requested type, with availability NONE and dataCenters omitted, so a 404 keeps meaning the GPU type does not exist. Read the NONE on availability rather than the absence of dataCenters, which is also absent when availability was not requested."
3596
+ description: "Comma-separated ISO 3166-1 alpha-2 country codes, uppercase, to constrain availability to \u2014 e.g. FR or FR,DE. Values within this filter use OR semantics. Valid only with include=AVAILABILITY; a malformed entry is a 422."
3125
3597
  },
3126
3598
  cudaVersions: {
3127
3599
  type: "array",
@@ -3129,12 +3601,12 @@ var generatedTools = [
3129
3601
  type: "string",
3130
3602
  pattern: "^\\d+\\.\\d+$"
3131
3603
  },
3132
- description: "Comma-separated CUDA versions to scope availability and lowest-price calculations to, matched exactly. Format: major.minor, e.g. 12.8 \u2014 a bare major is rejected here because it identifies no version. Valid only with include=AVAILABILITY (400 otherwise) and mutually exclusive with minCudaVersion (400 if both are sent); a malformed entry is a 422. Also narrows the returned cudaVersions array; omit it to enumerate every version offered."
3604
+ description: "Comma-separated CUDA versions to scope availability and lowest-price calculations to, matched exactly. Format: major.minor, e.g. 12.8 \u2014 a bare major is rejected here because it identifies no version."
3133
3605
  },
3134
3606
  minCudaVersion: {
3135
3607
  type: "string",
3136
3608
  pattern: "^\\d+(\\.\\d+)?$",
3137
- description: "Lowest acceptable CUDA version to scope availability and lowest-price calculations to, compared numerically. Format: integer major or major.minor, e.g. 12 or 12.1 \u2014 unlike the `gpu.minCudaVersion` body field on pod and endpoint create, a bare major is accepted here and means any release of that major, because this filter only widens a read. Valid only with include=AVAILABILITY (400 otherwise) and mutually exclusive with cudaVersions (400 if both are sent); a malformed value is a 422. Use this for an open-ended floor and cudaVersions for an exact set."
3609
+ description: "Lowest acceptable CUDA version to scope availability and lowest-price calculations to, compared numerically."
3138
3610
  }
3139
3611
  },
3140
3612
  required: ["id"],
@@ -3187,6 +3659,25 @@ var generatedTools = [
3187
3659
  required: ["id"]
3188
3660
  }
3189
3661
  },
3662
+ {
3663
+ name: "get-organization",
3664
+ operationId: "getOrganization",
3665
+ description: "Get your current organization. Returns the authenticated caller's current organization. Organization context comes from the caller's membership; no organization selector is required. Returns 404 when the caller does not belong to an organization, or 403 when organizations are unavailable to the caller.",
3666
+ method: "GET",
3667
+ path: "/v2/organization",
3668
+ params: [],
3669
+ hasBody: false,
3670
+ annotations: {
3671
+ readOnlyHint: true,
3672
+ destructiveHint: false,
3673
+ idempotentHint: true,
3674
+ openWorldHint: true
3675
+ },
3676
+ inputSchema: {
3677
+ type: "object",
3678
+ properties: {}
3679
+ }
3680
+ },
3190
3681
  {
3191
3682
  name: "get-pod",
3192
3683
  operationId: "getPod",
@@ -3361,12 +3852,12 @@ var generatedTools = [
3361
3852
  startTime: {
3362
3853
  type: "string",
3363
3854
  format: "date-time",
3364
- description: "Start of the billing period (RFC 3339). Defaults to 30 days ago. Snapped down to the start of its bucketSize bucket so the window aligns with the returned records; provide a boundary-aligned value (e.g. midnight for bucketSize=day) to avoid widening.\n"
3855
+ description: "Start of the billing period (RFC 3339). Defaults to 30 days ago."
3365
3856
  },
3366
3857
  endTime: {
3367
3858
  type: "string",
3368
3859
  format: "date-time",
3369
- description: "End of the billing period (RFC 3339), exclusive. Defaults to now. Snapped up to the end of the bucketSize bucket it lands in (unless already on a boundary) so the window aligns with the returned records.\n"
3860
+ description: "End of the billing period (RFC 3339), exclusive. Defaults to now. Snapped up to the end of the bucketSize bucket it lands in (unless already on a boundary) so the window aligns with the returned records."
3370
3861
  },
3371
3862
  bucketSize: {
3372
3863
  $ref: "#/$defs/BillingBucketSize",
@@ -3375,7 +3866,7 @@ var generatedTools = [
3375
3866
  lastN: {
3376
3867
  type: "integer",
3377
3868
  minimum: 1,
3378
- description: 'Return the last N buckets of bucketSize, ending with the current (in-progress) bucket \u2014 e.g. lastN=100 with bucketSize=day is "last 100 days". The resolved window is aligned to bucket boundaries: startTime is the start of the earliest bucket (e.g. midnight of the earliest day) and endTime is the end of the current bucket. Mutually exclusive with startTime/endTime; provide one or the other, not both.\n'
3869
+ description: 'Return the last N buckets of bucketSize, ending with the current (in-progress) bucket \u2014 e.g. lastN=100 with bucketSize=day is "last 100 days".'
3379
3870
  }
3380
3871
  },
3381
3872
  $defs: {
@@ -3437,12 +3928,12 @@ var generatedTools = [
3437
3928
  startTime: {
3438
3929
  type: "string",
3439
3930
  format: "date-time",
3440
- description: "Start of the billing period (RFC 3339). Defaults to 30 days ago. Snapped down to the start of its bucketSize bucket so the window aligns with the returned records; provide a boundary-aligned value (e.g. midnight for bucketSize=day) to avoid widening.\n"
3931
+ description: "Start of the billing period (RFC 3339). Defaults to 30 days ago."
3441
3932
  },
3442
3933
  endTime: {
3443
3934
  type: "string",
3444
3935
  format: "date-time",
3445
- description: "End of the billing period (RFC 3339), exclusive. Defaults to now. Snapped up to the end of the bucketSize bucket it lands in (unless already on a boundary) so the window aligns with the returned records.\n"
3936
+ description: "End of the billing period (RFC 3339), exclusive. Defaults to now. Snapped up to the end of the bucketSize bucket it lands in (unless already on a boundary) so the window aligns with the returned records."
3446
3937
  },
3447
3938
  bucketSize: {
3448
3939
  $ref: "#/$defs/BillingBucketSize",
@@ -3451,7 +3942,7 @@ var generatedTools = [
3451
3942
  lastN: {
3452
3943
  type: "integer",
3453
3944
  minimum: 1,
3454
- description: 'Return the last N buckets of bucketSize, ending with the current (in-progress) bucket \u2014 e.g. lastN=100 with bucketSize=day is "last 100 days". The resolved window is aligned to bucket boundaries: startTime is the start of the earliest bucket (e.g. midnight of the earliest day) and endTime is the end of the current bucket. Mutually exclusive with startTime/endTime; provide one or the other, not both.\n'
3945
+ description: 'Return the last N buckets of bucketSize, ending with the current (in-progress) bucket \u2014 e.g. lastN=100 with bucketSize=day is "last 100 days".'
3455
3946
  },
3456
3947
  clusterId: {
3457
3948
  type: "string",
@@ -3509,10 +4000,19 @@ var generatedTools = [
3509
4000
  {
3510
4001
  name: "list-clusters",
3511
4002
  operationId: "listClusters",
3512
- description: "List clusters. Returns all clusters owned by the authenticated user.",
4003
+ description: "List clusters. Returns clusters owned by the authenticated user, cursor-paginated newest-first; an omitted `limit` defaults to 1000. Follow `pagination.nextCursor` until `hasNextPage` is false.",
3513
4004
  method: "GET",
3514
4005
  path: "/v2/clusters",
3515
- params: [],
4006
+ params: [
4007
+ {
4008
+ name: "cursor",
4009
+ location: "query"
4010
+ },
4011
+ {
4012
+ name: "limit",
4013
+ location: "query"
4014
+ }
4015
+ ],
3516
4016
  hasBody: false,
3517
4017
  annotations: {
3518
4018
  readOnlyHint: true,
@@ -3522,7 +4022,19 @@ var generatedTools = [
3522
4022
  },
3523
4023
  inputSchema: {
3524
4024
  type: "object",
3525
- properties: {}
4025
+ properties: {
4026
+ cursor: {
4027
+ type: "string",
4028
+ minLength: 1,
4029
+ description: "Opaque resume cursor \u2014 pass the previous response's `pagination.nextCursor` through verbatim; omit for the first page. A cursor is only valid for the operation and parameters that issued it; a malformed or foreign cursor is rejected."
4030
+ },
4031
+ limit: {
4032
+ type: "integer",
4033
+ minimum: 1,
4034
+ maximum: 1e3,
4035
+ description: "Page size, 1\u20131000. Defaults to 1000 when omitted."
4036
+ }
4037
+ }
3526
4038
  }
3527
4039
  },
3528
4040
  {
@@ -3776,12 +4288,12 @@ var generatedTools = [
3776
4288
  startTime: {
3777
4289
  type: "string",
3778
4290
  format: "date-time",
3779
- description: "Start of the billing period (RFC 3339). Defaults to 30 days ago. Snapped down to the start of its bucketSize bucket so the window aligns with the returned records; provide a boundary-aligned value (e.g. midnight for bucketSize=day) to avoid widening.\n"
4291
+ description: "Start of the billing period (RFC 3339). Defaults to 30 days ago."
3780
4292
  },
3781
4293
  endTime: {
3782
4294
  type: "string",
3783
4295
  format: "date-time",
3784
- description: "End of the billing period (RFC 3339), exclusive. Defaults to now. Snapped up to the end of the bucketSize bucket it lands in (unless already on a boundary) so the window aligns with the returned records.\n"
4296
+ description: "End of the billing period (RFC 3339), exclusive. Defaults to now. Snapped up to the end of the bucketSize bucket it lands in (unless already on a boundary) so the window aligns with the returned records."
3785
4297
  },
3786
4298
  bucketSize: {
3787
4299
  $ref: "#/$defs/BillingBucketSize",
@@ -3790,7 +4302,7 @@ var generatedTools = [
3790
4302
  lastN: {
3791
4303
  type: "integer",
3792
4304
  minimum: 1,
3793
- description: 'Return the last N buckets of bucketSize, ending with the current (in-progress) bucket \u2014 e.g. lastN=100 with bucketSize=day is "last 100 days". The resolved window is aligned to bucket boundaries: startTime is the start of the earliest bucket (e.g. midnight of the earliest day) and endTime is the end of the current bucket. Mutually exclusive with startTime/endTime; provide one or the other, not both.\n'
4305
+ description: 'Return the last N buckets of bucketSize, ending with the current (in-progress) bucket \u2014 e.g. lastN=100 with bucketSize=day is "last 100 days".'
3794
4306
  }
3795
4307
  },
3796
4308
  $defs: {
@@ -3848,7 +4360,7 @@ var generatedTools = [
3848
4360
  cursor: {
3849
4361
  type: "string",
3850
4362
  minLength: 1,
3851
- description: "Opaque resume cursor \u2014 pass the previous response's `pagination.nextCursor` through verbatim; omit for the first page. A cursor is only valid for the operation and parameters that issued it; a malformed or foreign cursor is rejected with 422.\n"
4363
+ description: "Opaque resume cursor \u2014 pass the previous response's `pagination.nextCursor` through verbatim; omit for the first page. A cursor is only valid for the operation and parameters that issued it; a malformed or foreign cursor is rejected."
3852
4364
  },
3853
4365
  limit: {
3854
4366
  type: "integer",
@@ -3897,7 +4409,7 @@ var generatedTools = [
3897
4409
  cursor: {
3898
4410
  type: "string",
3899
4411
  minLength: 1,
3900
- description: "Opaque resume cursor \u2014 pass the previous response's `pagination.nextCursor` through verbatim; omit for the first page. A cursor is only valid for the operation and parameters that issued it; a malformed or foreign cursor is rejected with 422.\n"
4412
+ description: "Opaque resume cursor \u2014 pass the previous response's `pagination.nextCursor` through verbatim; omit for the first page. A cursor is only valid for the operation and parameters that issued it; a malformed or foreign cursor is rejected."
3901
4413
  },
3902
4414
  limit: {
3903
4415
  type: "integer",
@@ -3939,6 +4451,46 @@ var generatedTools = [
3939
4451
  required: ["id"]
3940
4452
  }
3941
4453
  },
4454
+ {
4455
+ name: "list-global-volumes",
4456
+ operationId: "listGlobalVolumes",
4457
+ description: "List global volumes. Returns global volumes owned by the authenticated user, cursor-paginated newest-first; an omitted `limit` defaults to 1000. Follow `pagination.nextCursor` until `hasNextPage` is false.",
4458
+ method: "GET",
4459
+ path: "/v2/global-volumes",
4460
+ params: [
4461
+ {
4462
+ name: "cursor",
4463
+ location: "query"
4464
+ },
4465
+ {
4466
+ name: "limit",
4467
+ location: "query"
4468
+ }
4469
+ ],
4470
+ hasBody: false,
4471
+ annotations: {
4472
+ readOnlyHint: true,
4473
+ destructiveHint: false,
4474
+ idempotentHint: true,
4475
+ openWorldHint: true
4476
+ },
4477
+ inputSchema: {
4478
+ type: "object",
4479
+ properties: {
4480
+ cursor: {
4481
+ type: "string",
4482
+ minLength: 1,
4483
+ description: "Opaque resume cursor \u2014 pass the previous response's `pagination.nextCursor` through verbatim; omit for the first page. A cursor is only valid for the operation and parameters that issued it; a malformed or foreign cursor is rejected."
4484
+ },
4485
+ limit: {
4486
+ type: "integer",
4487
+ minimum: 1,
4488
+ maximum: 1e3,
4489
+ description: "Page size, 1\u20131000. Defaults to 1000 when omitted."
4490
+ }
4491
+ }
4492
+ }
4493
+ },
3942
4494
  {
3943
4495
  name: "list-gpu-types",
3944
4496
  operationId: "listGpuTypes",
@@ -4002,7 +4554,7 @@ var generatedTools = [
4002
4554
  items: {
4003
4555
  $ref: "#/$defs/Product"
4004
4556
  },
4005
- description: "Comma-separated availability product contexts. Supported values: POD, CLUSTER, SERVERLESS. Required with include=AVAILABILITY, and valid only with it (400 either way). There is no default: the same GPU type can be scarce for pods and plentiful for serverless, so the context has to be stated rather than assumed."
4557
+ description: "Comma-separated availability product contexts. Supported values: POD, CLUSTER, SERVERLESS. Required with include=AVAILABILITY, and valid only with it (400 either way)."
4006
4558
  },
4007
4559
  count: {
4008
4560
  type: "integer",
@@ -4020,7 +4572,7 @@ var generatedTools = [
4020
4572
  type: "string",
4021
4573
  pattern: "^[A-Z]{2}$"
4022
4574
  },
4023
- description: "Comma-separated ISO 3166-1 alpha-2 country codes, uppercase, to constrain availability to \u2014 e.g. FR or FR,DE. Values within this filter use OR semantics. Valid only with include=AVAILABILITY (400 otherwise); a malformed entry is a 422. Scopes availability, lowest-price calculations and the dataCenters array to those countries, so a listed data center outside them is omitted rather than returned with availability NONE. On the list endpoint a GPU type with no data center in those countries drops out entirely; the single-GPU endpoint still returns the requested type, with availability NONE and dataCenters omitted, so a 404 keeps meaning the GPU type does not exist. Read the NONE on availability rather than the absence of dataCenters, which is also absent when availability was not requested."
4575
+ description: "Comma-separated ISO 3166-1 alpha-2 country codes, uppercase, to constrain availability to \u2014 e.g. FR or FR,DE. Values within this filter use OR semantics. Valid only with include=AVAILABILITY; a malformed entry is a 422."
4024
4576
  },
4025
4577
  cudaVersions: {
4026
4578
  type: "array",
@@ -4028,12 +4580,12 @@ var generatedTools = [
4028
4580
  type: "string",
4029
4581
  pattern: "^\\d+\\.\\d+$"
4030
4582
  },
4031
- description: "Comma-separated CUDA versions to scope availability and lowest-price calculations to, matched exactly. Format: major.minor, e.g. 12.8 \u2014 a bare major is rejected here because it identifies no version. Valid only with include=AVAILABILITY (400 otherwise) and mutually exclusive with minCudaVersion (400 if both are sent); a malformed entry is a 422. Also narrows the returned cudaVersions array; omit it to enumerate every version offered."
4583
+ description: "Comma-separated CUDA versions to scope availability and lowest-price calculations to, matched exactly. Format: major.minor, e.g. 12.8 \u2014 a bare major is rejected here because it identifies no version."
4032
4584
  },
4033
4585
  minCudaVersion: {
4034
4586
  type: "string",
4035
4587
  pattern: "^\\d+(\\.\\d+)?$",
4036
- description: "Lowest acceptable CUDA version to scope availability and lowest-price calculations to, compared numerically. Format: integer major or major.minor, e.g. 12 or 12.1 \u2014 unlike the `gpu.minCudaVersion` body field on pod and endpoint create, a bare major is accepted here and means any release of that major, because this filter only widens a read. Valid only with include=AVAILABILITY (400 otherwise) and mutually exclusive with cudaVersions (400 if both are sent); a malformed value is a 422. Use this for an open-ended floor and cudaVersions for an exact set."
4588
+ description: "Lowest acceptable CUDA version to scope availability and lowest-price calculations to, compared numerically."
4037
4589
  }
4038
4590
  },
4039
4591
  $defs: {
@@ -4096,12 +4648,12 @@ var generatedTools = [
4096
4648
  startTime: {
4097
4649
  type: "string",
4098
4650
  format: "date-time",
4099
- description: "Start of the billing period (RFC 3339). Defaults to 30 days ago. Snapped down to the start of its bucketSize bucket so the window aligns with the returned records; provide a boundary-aligned value (e.g. midnight for bucketSize=day) to avoid widening.\n"
4651
+ description: "Start of the billing period (RFC 3339). Defaults to 30 days ago."
4100
4652
  },
4101
4653
  endTime: {
4102
4654
  type: "string",
4103
4655
  format: "date-time",
4104
- description: "End of the billing period (RFC 3339), exclusive. Defaults to now. Snapped up to the end of the bucketSize bucket it lands in (unless already on a boundary) so the window aligns with the returned records.\n"
4656
+ description: "End of the billing period (RFC 3339), exclusive. Defaults to now. Snapped up to the end of the bucketSize bucket it lands in (unless already on a boundary) so the window aligns with the returned records."
4105
4657
  },
4106
4658
  bucketSize: {
4107
4659
  $ref: "#/$defs/BillingBucketSize",
@@ -4110,7 +4662,7 @@ var generatedTools = [
4110
4662
  lastN: {
4111
4663
  type: "integer",
4112
4664
  minimum: 1,
4113
- description: 'Return the last N buckets of bucketSize, ending with the current (in-progress) bucket \u2014 e.g. lastN=100 with bucketSize=day is "last 100 days". The resolved window is aligned to bucket boundaries: startTime is the start of the earliest bucket (e.g. midnight of the earliest day) and endTime is the end of the current bucket. Mutually exclusive with startTime/endTime; provide one or the other, not both.\n'
4665
+ description: 'Return the last N buckets of bucketSize, ending with the current (in-progress) bucket \u2014 e.g. lastN=100 with bucketSize=day is "last 100 days".'
4114
4666
  },
4115
4667
  networkVolumeId: {
4116
4668
  type: "string",
@@ -4138,10 +4690,19 @@ var generatedTools = [
4138
4690
  {
4139
4691
  name: "list-network-volumes",
4140
4692
  operationId: "listNetworkVolumes",
4141
- description: "List network volumes. Returns all network volumes owned by the authenticated user.",
4693
+ description: "List network volumes. Returns network volumes owned by the authenticated user, cursor-paginated newest-first; an omitted `limit` defaults to 1000. Follow `pagination.nextCursor` until `hasNextPage` is false.",
4142
4694
  method: "GET",
4143
4695
  path: "/v2/network-volumes",
4144
- params: [],
4696
+ params: [
4697
+ {
4698
+ name: "cursor",
4699
+ location: "query"
4700
+ },
4701
+ {
4702
+ name: "limit",
4703
+ location: "query"
4704
+ }
4705
+ ],
4145
4706
  hasBody: false,
4146
4707
  annotations: {
4147
4708
  readOnlyHint: true,
@@ -4151,7 +4712,19 @@ var generatedTools = [
4151
4712
  },
4152
4713
  inputSchema: {
4153
4714
  type: "object",
4154
- properties: {}
4715
+ properties: {
4716
+ cursor: {
4717
+ type: "string",
4718
+ minLength: 1,
4719
+ description: "Opaque resume cursor \u2014 pass the previous response's `pagination.nextCursor` through verbatim; omit for the first page. A cursor is only valid for the operation and parameters that issued it; a malformed or foreign cursor is rejected."
4720
+ },
4721
+ limit: {
4722
+ type: "integer",
4723
+ minimum: 1,
4724
+ maximum: 1e3,
4725
+ description: "Page size, 1\u20131000. Defaults to 1000 when omitted."
4726
+ }
4727
+ }
4155
4728
  }
4156
4729
  },
4157
4730
  {
@@ -4195,12 +4768,12 @@ var generatedTools = [
4195
4768
  startTime: {
4196
4769
  type: "string",
4197
4770
  format: "date-time",
4198
- description: "Start of the billing period (RFC 3339). Defaults to 30 days ago. Snapped down to the start of its bucketSize bucket so the window aligns with the returned records; provide a boundary-aligned value (e.g. midnight for bucketSize=day) to avoid widening.\n"
4771
+ description: "Start of the billing period (RFC 3339). Defaults to 30 days ago."
4199
4772
  },
4200
4773
  endTime: {
4201
4774
  type: "string",
4202
4775
  format: "date-time",
4203
- description: "End of the billing period (RFC 3339), exclusive. Defaults to now. Snapped up to the end of the bucketSize bucket it lands in (unless already on a boundary) so the window aligns with the returned records.\n"
4776
+ description: "End of the billing period (RFC 3339), exclusive. Defaults to now. Snapped up to the end of the bucketSize bucket it lands in (unless already on a boundary) so the window aligns with the returned records."
4204
4777
  },
4205
4778
  bucketSize: {
4206
4779
  $ref: "#/$defs/BillingBucketSize",
@@ -4209,7 +4782,7 @@ var generatedTools = [
4209
4782
  lastN: {
4210
4783
  type: "integer",
4211
4784
  minimum: 1,
4212
- description: 'Return the last N buckets of bucketSize, ending with the current (in-progress) bucket \u2014 e.g. lastN=100 with bucketSize=day is "last 100 days". The resolved window is aligned to bucket boundaries: startTime is the start of the earliest bucket (e.g. midnight of the earliest day) and endTime is the end of the current bucket. Mutually exclusive with startTime/endTime; provide one or the other, not both.\n'
4785
+ description: 'Return the last N buckets of bucketSize, ending with the current (in-progress) bucket \u2014 e.g. lastN=100 with bucketSize=day is "last 100 days".'
4213
4786
  },
4214
4787
  podId: {
4215
4788
  type: "string",
@@ -4237,7 +4810,7 @@ var generatedTools = [
4237
4810
  {
4238
4811
  name: "list-pods",
4239
4812
  operationId: "listPods",
4240
- description: "List pods. Returns pods owned by the authenticated user. Cluster member pods are excluded by default; set `includeClusterPods=true` to include them (each carries a non-null `cluster` membership block). Results are cursor-paginated newest-first; an omitted `limit` defaults to 1000. When cluster member pods are excluded, the exclusion applies to each page after it is cut, so a page may hold fewer than `limit` pods \u2014 follow `pagination.nextCursor` until `hasNextPage` is false rather than counting items.",
4813
+ description: "List pods. Returns pods owned by the authenticated user. Cluster member pods are excluded by default; set `includeClusterPods=true` to include them (each carries a non-null `cluster` membership block). Results are cursor-paginated newest-first; an omitted `limit` defaults to 1000. The cluster-member exclusion is applied before the page is cut, so a page holds `limit` pods whenever that many remain. Follow `pagination.nextCursor` until `hasNextPage` is false.",
4241
4814
  method: "GET",
4242
4815
  path: "/v2/pods",
4243
4816
  params: [
@@ -4272,7 +4845,7 @@ var generatedTools = [
4272
4845
  cursor: {
4273
4846
  type: "string",
4274
4847
  minLength: 1,
4275
- description: "Opaque resume cursor \u2014 pass the previous response's `pagination.nextCursor` through verbatim; omit for the first page. A cursor is only valid for the operation and parameters that issued it; a malformed or foreign cursor is rejected with 422.\n"
4848
+ description: "Opaque resume cursor \u2014 pass the previous response's `pagination.nextCursor` through verbatim; omit for the first page. A cursor is only valid for the operation and parameters that issued it; a malformed or foreign cursor is rejected."
4276
4849
  },
4277
4850
  limit: {
4278
4851
  type: "integer",
@@ -4309,7 +4882,7 @@ var generatedTools = [
4309
4882
  type: "string",
4310
4883
  enum: ["official", "verified", "community"],
4311
4884
  default: "official",
4312
- description: "Which slice of the catalog to return: `official` for\nRunpod-curated templates (default), `verified` for\nRunpod-verified community templates, or `community` for all other\npublicly shared templates.\n"
4885
+ description: "Which slice of the catalog to return: `official` for Runpod-curated templates (default), `verified` for Runpod-verified community templates, or `community` for all other publicly shared templates."
4313
4886
  }
4314
4887
  }
4315
4888
  }
@@ -4403,12 +4976,12 @@ var generatedTools = [
4403
4976
  startTime: {
4404
4977
  type: "string",
4405
4978
  format: "date-time",
4406
- description: "Start of the billing period (RFC 3339). Defaults to 30 days ago. Snapped down to the start of its bucketSize bucket so the window aligns with the returned records; provide a boundary-aligned value (e.g. midnight for bucketSize=day) to avoid widening.\n"
4979
+ description: "Start of the billing period (RFC 3339). Defaults to 30 days ago."
4407
4980
  },
4408
4981
  endTime: {
4409
4982
  type: "string",
4410
4983
  format: "date-time",
4411
- description: "End of the billing period (RFC 3339), exclusive. Defaults to now. Snapped up to the end of the bucketSize bucket it lands in (unless already on a boundary) so the window aligns with the returned records.\n"
4984
+ description: "End of the billing period (RFC 3339), exclusive. Defaults to now. Snapped up to the end of the bucketSize bucket it lands in (unless already on a boundary) so the window aligns with the returned records."
4412
4985
  },
4413
4986
  bucketSize: {
4414
4987
  $ref: "#/$defs/BillingBucketSize",
@@ -4417,7 +4990,7 @@ var generatedTools = [
4417
4990
  lastN: {
4418
4991
  type: "integer",
4419
4992
  minimum: 1,
4420
- description: 'Return the last N buckets of bucketSize, ending with the current (in-progress) bucket \u2014 e.g. lastN=100 with bucketSize=day is "last 100 days". The resolved window is aligned to bucket boundaries: startTime is the start of the earliest bucket (e.g. midnight of the earliest day) and endTime is the end of the current bucket. Mutually exclusive with startTime/endTime; provide one or the other, not both.\n'
4993
+ description: 'Return the last N buckets of bucketSize, ending with the current (in-progress) bucket \u2014 e.g. lastN=100 with bucketSize=day is "last 100 days".'
4421
4994
  },
4422
4995
  serverlessId: {
4423
4996
  type: "string",
@@ -4556,7 +5129,7 @@ var generatedTools = [
4556
5129
  type: "object",
4557
5130
  additionalProperties: false,
4558
5131
  minProperties: 1,
4559
- description: "Request body for updating a cluster. Only the cluster name can be\nchanged \u2014 this endpoint is a rename. Compute shape, type, and container\nconfiguration are fixed at creation.\n",
5132
+ description: "Request body for updating a cluster. Only the cluster name can be changed \u2014 this endpoint is a rename. Compute shape, type, and container configuration are fixed at creation.",
4560
5133
  properties: {
4561
5134
  name: {
4562
5135
  type: "string",
@@ -4587,6 +5160,7 @@ var generatedTools = [
4587
5160
  idempotentHint: false,
4588
5161
  openWorldHint: true
4589
5162
  },
5163
+ schemaOnDemand: true,
4590
5164
  inputSchema: {
4591
5165
  type: "object",
4592
5166
  properties: {
@@ -4602,11 +5176,10 @@ var generatedTools = [
4602
5176
  $defs: {
4603
5177
  BaseContainerConfig: {
4604
5178
  type: "object",
4605
- description: "Container configuration universal to every containerized resource. Compose ContainerConfig instead unless the resource cannot support a direct registry credential (clusters \u2014 there the registry credential arrives via a pod template, see CreateClusterRequest.templateId).\n",
4606
5179
  properties: {
4607
5180
  args: {
4608
5181
  type: "string",
4609
- description: 'The container\'s command, as a single raw string. This is the field `entrypoint` and `cmd` encode into, exposed in its stored form.\n\nTwo shapes are accepted. A bare shell string is treated as CMD and split into arguments, which is what the console\'s "Container start command" field writes. A JSON object of the form `{"entrypoint":[...],"cmd":[...]}` sets either or both explicitly.\n\nResponses always return both representations: `args` exactly as stored, plus the deconstructed `entrypoint` and `cmd`. Supplying `args` together with `entrypoint` or `cmd` is allowed only when they describe the same command, so a read-modify-write client can send back everything it received. Send `""` to clear, omit to leave unchanged.\n',
5182
+ description: 'The container\'s command as one raw string: a bare shell string is treated as CMD and split into arguments; a JSON object `{"entrypoint":[...],"cmd":[...]}` sets either or both. Send `""` to clear; omit to leave unchanged.',
4610
5183
  examples: [
4611
5184
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
4612
5185
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -4618,7 +5191,7 @@ var generatedTools = [
4618
5191
  items: {
4619
5192
  type: "string"
4620
5193
  },
4621
- description: "Container CMD in exec form. When the image defines an ENTRYPOINT, this is the argument list passed to it. Encoded into the `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
5194
+ description: "Container CMD in exec form: when the image defines an ENTRYPOINT, the argument list passed to it. Encoded into `args`; supply both only when they describe the same command. Send `[]` to clear; omit to leave unchanged.",
4622
5195
  examples: [
4623
5196
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
4624
5197
  ]
@@ -4628,7 +5201,7 @@ var generatedTools = [
4628
5201
  items: {
4629
5202
  type: "string"
4630
5203
  },
4631
- description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
5204
+ description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.",
4632
5205
  examples: [["/bin/bash", "-c"]]
4633
5206
  },
4634
5207
  disk: {
@@ -4688,7 +5261,7 @@ var generatedTools = [
4688
5261
  pools: {
4689
5262
  type: "array",
4690
5263
  minItems: 1,
4691
- description: 'Serverless GPU pool IDs (as returned by `GET /v2/catalog/gpus` in\n`pool`). Workers are placed on whichever listed pool has capacity.\nNarrow a pool down to specific cards with `excludedTypes`.\n\nOn `PATCH`, `pools` and `excludedTypes` are one selection and are\nreplaced together, so sending `pools` by itself **clears the\nexclusions**. Two cases:\n\n- **Changing pools, keeping exclusions** \u2014 send both fields in one\n request: `{"gpu": {"pools": ["ADA_24"], "excludedTypes":\n ["NVIDIA L40"]}}`. `GET` the endpoint first to read the current\n `excludedTypes` and resend the ones that still apply to the new\n pools; an exclusion naming a type outside `pools` is a 400.\n- **Changing only `count` or a CUDA constraint** \u2014 omit `pools`:\n `{"gpu": {"minCudaVersion": "12.4"}}`. The pool list and the\n exclusions are both left exactly as they are.\n\n`excludedTypes` documents the full rule.\n',
5264
+ description: "Serverless GPU pool IDs (the `pool` field of `list-gpu-types`). Workers are placed on whichever listed pool has capacity; narrow to specific cards with `excludedTypes`. On update, `pools` and `excludedTypes` are replaced together, so sending `pools` alone clears the exclusions.",
4692
5265
  items: {
4693
5266
  type: "string"
4694
5267
  },
@@ -4697,7 +5270,7 @@ var generatedTools = [
4697
5270
  excludedTypes: {
4698
5271
  type: "array",
4699
5272
  uniqueItems: true,
4700
- description: 'GPU **type** IDs to subtract from the selected pools \u2014 the `id`\nfield of `GET /v2/catalog/gpus`, the same identifiers pods take in\n`gpu.id`. Workers run on every type in `pools` except these. Omit to\nuse the whole pool.\n\nPools stay the unit of selection; types are the unit of\nsubtraction. There is no inclusive allowlist: a card later added to\none of your pools becomes eligible, which is the honest reading of\n"this pool, minus these".\n\nTied to `pools`, because the two together are one selection:\nsupplying `pools` replaces that selection wholesale, so a `PATCH`\nsending `pools` **without `excludedTypes`** **clears** them \u2014\nrestate them to keep them. A `PATCH` that omits `pools` leaves both\nthe pools and the exclusions untouched, so changing only a CUDA\nconstraint cannot widen a pinned endpoint.\n\nRejected with 400 if a value is not a GPU type in one of `pools`;\nupstream accepts unrecognized exclusions silently, so a typo would\notherwise produce a filter that does nothing. Surrounding whitespace\nis trimmed, so `" NVIDIA L40"` and `"NVIDIA L40"` mean the same card.\n',
5273
+ description: "GPU type IDs to subtract from the selected pools \u2014 the `id` field of `list-gpu-types`, the same identifiers pods take in `gpu.id`. Workers run on every type in `pools` except these; omit to use the whole pool. A value outside `pools` is rejected. On update, restate them whenever you send `pools`, or they are cleared.",
4701
5274
  items: {
4702
5275
  type: "string",
4703
5276
  pattern: "^\\s*[^-\\s]"
@@ -4714,7 +5287,6 @@ var generatedTools = [
4714
5287
  }
4715
5288
  },
4716
5289
  ContainerConfig: {
4717
- description: "Reusable container configuration shared across templates, pods, and serverless endpoints. Adding a field here automatically propagates to all three resources.\n",
4718
5290
  allOf: [
4719
5291
  {
4720
5292
  $ref: "#/$defs/BaseContainerConfig"
@@ -4739,8 +5311,42 @@ var generatedTools = [
4739
5311
  ],
4740
5312
  unevaluatedProperties: false
4741
5313
  },
5314
+ EndpointMount: {
5315
+ type: "object",
5316
+ required: ["volumeId"],
5317
+ additionalProperties: false,
5318
+ description: "A volume mounted on every worker, always at `/runpod-volume`. Custom paths are not supported yet.",
5319
+ properties: {
5320
+ volumeId: {
5321
+ type: "string",
5322
+ description: "ID of a NetworkVolume (under `network`) or GlobalVolume (under `global`).",
5323
+ examples: ["vol_abc"]
5324
+ }
5325
+ }
5326
+ },
5327
+ EndpointMountsInput: {
5328
+ type: "object",
5329
+ additionalProperties: false,
5330
+ description: "Volumes to mount on the endpoint's workers. A `network` or `global` list that is present replaces the endpoint's current list of that kind; an empty list detaches it, and an omitted list is left unchanged.",
5331
+ properties: {
5332
+ network: {
5333
+ type: "array",
5334
+ description: "Network volumes, at most one per data center.",
5335
+ items: {
5336
+ $ref: "#/$defs/EndpointMount"
5337
+ }
5338
+ },
5339
+ global: {
5340
+ type: "array",
5341
+ maxItems: 1,
5342
+ items: {
5343
+ $ref: "#/$defs/EndpointMount"
5344
+ }
5345
+ }
5346
+ }
5347
+ },
4742
5348
  EndpointScaling: {
4743
- description: "Autoscaling signal \u2014 a discriminated union on `type`: `QUEUE_DELAY`\n(queue-based endpoints only) or `REQUEST_COUNT`. The scaler is chosen\nindependently of the endpoint's routing `type` and can be switched on\nupdate.\n",
5349
+ description: "Autoscaling signal \u2014 a discriminated union on `type`: `QUEUE_DELAY` (queue-based endpoints only) or `REQUEST_COUNT`. The scaler is chosen independently of the endpoint's routing `type` and can be switched on update.",
4744
5350
  oneOf: [
4745
5351
  {
4746
5352
  $ref: "#/$defs/QueueDelayScaling"
@@ -4777,14 +5383,14 @@ var generatedTools = [
4777
5383
  type: "integer",
4778
5384
  minimum: 1,
4779
5385
  maximum: 3600,
4780
- description: "Seconds before idle workers scale down. Not applicable to queue-based\nendpoints scaling on `requestCount` \u2014 rejected on create/update and\nomitted from responses for that combination.\n",
5386
+ description: "Seconds before idle workers scale down. Not applicable to queue-based endpoints scaling on `requestCount` \u2014 rejected on create/update and omitted from responses for that combination.",
4781
5387
  examples: [5]
4782
5388
  }
4783
5389
  }
4784
5390
  },
4785
5391
  FlashBoot: {
4786
5392
  type: "string",
4787
- description: "FlashBoot cold-start acceleration mode.\n- `OFF` \u2014 disabled\n- `FLASHBOOT` \u2014 enabled\n- `PRIORITY_FLASHBOOT` \u2014 enabled with priority capacity\n",
5393
+ description: "FlashBoot cold-start acceleration mode. `OFF` \u2014 disabled. `FLASHBOOT` \u2014 enabled. `PRIORITY_FLASHBOOT` \u2014 enabled with priority capacity",
4788
5394
  enum: ["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]
4789
5395
  },
4790
5396
  QueueDelayScaling: {
@@ -4810,7 +5416,7 @@ var generatedTools = [
4810
5416
  RequestCountScaling: {
4811
5417
  type: "object",
4812
5418
  additionalProperties: false,
4813
- description: "Scale on concurrent in-flight requests per worker. Required for\nload-balancing endpoints; also selectable for queue-based.\n",
5419
+ description: "Scale on concurrent in-flight requests per worker. Required for load-balancing endpoints; also selectable for queue-based.",
4814
5420
  required: ["type", "requestCount"],
4815
5421
  properties: {
4816
5422
  type: {
@@ -4827,7 +5433,7 @@ var generatedTools = [
4827
5433
  }
4828
5434
  },
4829
5435
  UpdateEndpointGpuConfig: {
4830
- description: "Partial GPU update \u2014 every field is optional and an omitted one is left\nunchanged. Unlike create, `pools` is optional, so changing only a CUDA\nconstraint does not require resending the pool list.\n\n`excludedTypes` requires `pools`, because the two are one selection and\nonly a supplied `pools` replaces it \u2014 an exclusion on its own would\notherwise be silently dropped.\n",
5436
+ description: "Partial GPU update \u2014 every field is optional and an omitted one is left unchanged. `pools` is optional here, so a CUDA-only change does not resend the pool list; `excludedTypes` needs `pools` in the same request, and `pools` sent alone clears the exclusions.",
4831
5437
  allOf: [
4832
5438
  {
4833
5439
  $ref: "#/$defs/BaseEndpointGpuConfig"
@@ -4844,12 +5450,12 @@ var generatedTools = [
4844
5450
  type: "string",
4845
5451
  pattern: "^\\d+\\.\\d+$"
4846
5452
  },
4847
- description: "Acceptable CUDA versions for worker placement, as\n`major.minor`. An explicit `[]` clears the constraint;\nomitting the field leaves it unchanged. Takes effect as\nworkers are replaced.\n\nA non-empty set is mutually exclusive with minCudaVersion (400\nif both are sent). An explicit `[]` states no constraint, so it\nmay accompany a floor.\nSetting one does not clear the other \u2014 clear it explicitly in\nthe same patch if the endpoint already carries it.\n"
5453
+ description: "Acceptable CUDA versions for worker placement, as `major.minor`. An explicit `[]` clears the constraint; omitting the field leaves it unchanged. A non-empty set cannot be sent with minCudaVersion, and setting one does not clear the other: clear the other explicitly in the same patch. Takes effect as workers are replaced."
4848
5454
  },
4849
5455
  minCudaVersion: {
4850
5456
  type: "string",
4851
5457
  pattern: "^$|^\\d+\\.\\d+$",
4852
- description: 'Lowest acceptable CUDA version for worker placement, as\n`major.minor`. An explicit `""` clears the floor; omitting the\nfield leaves it unchanged. Takes effect as workers are\nreplaced.\n\nMutually exclusive with a non-empty allowedCudaVersions (400 if\nboth are sent); an explicit `[]` there states no constraint and\nmay accompany this floor.\n',
5458
+ description: 'Lowest acceptable CUDA version for worker placement, as `major.minor`. An explicit `""` clears the floor; omitting the field leaves it unchanged. Cannot be sent with a non-empty allowedCudaVersions (an explicit `[]` there is fine); clear that explicitly in the same patch if the endpoint carries one. Takes effect as workers are replaced.',
4853
5459
  examples: ["12.1"]
4854
5460
  }
4855
5461
  }
@@ -4870,7 +5476,7 @@ var generatedTools = [
4870
5476
  type: "array",
4871
5477
  minItems: 1,
4872
5478
  uniqueItems: true,
4873
- description: "Complete replacement CPU selection. Valid only for an existing CPU\nendpoint; endpoint compute family cannot be changed.\n",
5479
+ description: "Complete replacement CPU selection. Valid only for an existing CPU endpoint; endpoint compute family cannot be changed.",
4874
5480
  items: {
4875
5481
  $ref: "#/$defs/CreateCpuConfig"
4876
5482
  }
@@ -4896,14 +5502,19 @@ var generatedTools = [
4896
5502
  type: "array",
4897
5503
  items: {
4898
5504
  type: "string"
4899
- }
5505
+ },
5506
+ description: "Network volume IDs. Pass `[]` to detach.",
5507
+ deprecated: true
5508
+ },
5509
+ mounts: {
5510
+ $ref: "#/$defs/EndpointMountsInput"
4900
5511
  },
4901
5512
  scaling: {
4902
5513
  $ref: "#/$defs/EndpointScaling"
4903
5514
  },
4904
5515
  templateId: {
4905
5516
  type: "string",
4906
- description: "ID of a serverless template whose container settings are\napplied as if they were provided in this PATCH body (image,\nargs, disk, ports, env, registry). Explicit body fields\noverride the template's; `env` merges template and body per\nkey (body wins) and, per PATCH semantics, replaces the\nendpoint's env. One-time application \u2014 no link to the\ntemplate is retained. Must be one of your templates or a\npublic template (unknown or inaccessible ID \u2192 404); must be\na serverless template (\u2192 422).\n",
5517
+ description: "ID of a serverless template whose container settings (image, args, disk, ports, env, registry) are applied as if sent in this PATCH body; explicit body fields win. `env` merges template and body per key (body wins) and replaces the endpoint's existing env. Applied once; must be a serverless template you can access.",
4907
5518
  examples: ["30zmvf89kd"]
4908
5519
  },
4909
5520
  timeout: {
@@ -4920,6 +5531,57 @@ var generatedTools = [
4920
5531
  }
4921
5532
  }
4922
5533
  },
5534
+ {
5535
+ name: "update-global-volume",
5536
+ operationId: "updateGlobalVolume",
5537
+ description: "Update a global volume. Updates mutable fields on a global volume. Only provided fields are changed.",
5538
+ method: "PATCH",
5539
+ path: "/v2/global-volumes/{id}",
5540
+ params: [
5541
+ {
5542
+ name: "id",
5543
+ location: "path"
5544
+ }
5545
+ ],
5546
+ hasBody: true,
5547
+ annotations: {
5548
+ readOnlyHint: false,
5549
+ destructiveHint: false,
5550
+ idempotentHint: false,
5551
+ openWorldHint: true
5552
+ },
5553
+ inputSchema: {
5554
+ type: "object",
5555
+ properties: {
5556
+ id: {
5557
+ type: "string",
5558
+ description: "Global volume identifier"
5559
+ },
5560
+ body: {
5561
+ $ref: "#/$defs/UpdateGlobalVolumeRequest"
5562
+ }
5563
+ },
5564
+ required: ["id", "body"],
5565
+ $defs: {
5566
+ UpdateGlobalVolumeRequest: {
5567
+ type: "object",
5568
+ additionalProperties: false,
5569
+ minProperties: 1,
5570
+ description: "Only the provided fields are updated. At least one field must be present; an empty body is rejected.",
5571
+ properties: {
5572
+ name: {
5573
+ type: "string",
5574
+ minLength: 1,
5575
+ maxLength: 191,
5576
+ pattern: "[^\\p{Z}\\t-\\r\x85]",
5577
+ description: "New human-readable name, 1\u2013191 characters with at least one non-whitespace character. Leading and trailing whitespace is trimmed.",
5578
+ examples: ["renamed-volume"]
5579
+ }
5580
+ }
5581
+ }
5582
+ }
5583
+ }
5584
+ },
4923
5585
  {
4924
5586
  name: "update-network-volume",
4925
5587
  operationId: "updateNetworkVolume",
@@ -4956,7 +5618,7 @@ var generatedTools = [
4956
5618
  type: "object",
4957
5619
  additionalProperties: false,
4958
5620
  minProperties: 1,
4959
- description: "Only the provided fields are updated. At least one field must be\npresent; an empty body is rejected.\n",
5621
+ description: "Only the provided fields are updated. At least one field must be present; an empty body is rejected.",
4960
5622
  properties: {
4961
5623
  name: {
4962
5624
  type: "string",
@@ -4968,7 +5630,7 @@ var generatedTools = [
4968
5630
  type: "integer",
4969
5631
  minimum: 10,
4970
5632
  maximum: 4096,
4971
- description: "New size in GB. Must be greater than or equal to the current size \u2014\nnetwork volume storage cannot be reduced.\n",
5633
+ description: "New size in GB. Must be greater than or equal to the current size \u2014 network volume storage cannot be reduced.",
4972
5634
  examples: [100]
4973
5635
  }
4974
5636
  }
@@ -4995,6 +5657,7 @@ var generatedTools = [
4995
5657
  idempotentHint: false,
4996
5658
  openWorldHint: true
4997
5659
  },
5660
+ schemaOnDemand: true,
4998
5661
  inputSchema: {
4999
5662
  type: "object",
5000
5663
  properties: {
@@ -5010,11 +5673,10 @@ var generatedTools = [
5010
5673
  $defs: {
5011
5674
  BaseContainerConfig: {
5012
5675
  type: "object",
5013
- description: "Container configuration universal to every containerized resource. Compose ContainerConfig instead unless the resource cannot support a direct registry credential (clusters \u2014 there the registry credential arrives via a pod template, see CreateClusterRequest.templateId).\n",
5014
5676
  properties: {
5015
5677
  args: {
5016
5678
  type: "string",
5017
- description: 'The container\'s command, as a single raw string. This is the field `entrypoint` and `cmd` encode into, exposed in its stored form.\n\nTwo shapes are accepted. A bare shell string is treated as CMD and split into arguments, which is what the console\'s "Container start command" field writes. A JSON object of the form `{"entrypoint":[...],"cmd":[...]}` sets either or both explicitly.\n\nResponses always return both representations: `args` exactly as stored, plus the deconstructed `entrypoint` and `cmd`. Supplying `args` together with `entrypoint` or `cmd` is allowed only when they describe the same command, so a read-modify-write client can send back everything it received. Send `""` to clear, omit to leave unchanged.\n',
5679
+ description: 'The container\'s command as one raw string: a bare shell string is treated as CMD and split into arguments; a JSON object `{"entrypoint":[...],"cmd":[...]}` sets either or both. Send `""` to clear; omit to leave unchanged.',
5018
5680
  examples: [
5019
5681
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
5020
5682
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -5026,7 +5688,7 @@ var generatedTools = [
5026
5688
  items: {
5027
5689
  type: "string"
5028
5690
  },
5029
- description: "Container CMD in exec form. When the image defines an ENTRYPOINT, this is the argument list passed to it. Encoded into the `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
5691
+ description: "Container CMD in exec form: when the image defines an ENTRYPOINT, the argument list passed to it. Encoded into `args`; supply both only when they describe the same command. Send `[]` to clear; omit to leave unchanged.",
5030
5692
  examples: [
5031
5693
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
5032
5694
  ]
@@ -5036,7 +5698,7 @@ var generatedTools = [
5036
5698
  items: {
5037
5699
  type: "string"
5038
5700
  },
5039
- description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
5701
+ description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.",
5040
5702
  examples: [["/bin/bash", "-c"]]
5041
5703
  },
5042
5704
  disk: {
@@ -5072,8 +5734,22 @@ var generatedTools = [
5072
5734
  }
5073
5735
  }
5074
5736
  },
5737
+ BaseMounts: {
5738
+ type: "object",
5739
+ properties: {
5740
+ persistent: {
5741
+ $ref: "#/$defs/PersistentMount"
5742
+ },
5743
+ network: {
5744
+ type: "array",
5745
+ maxItems: 1,
5746
+ items: {
5747
+ $ref: "#/$defs/NetworkMount"
5748
+ }
5749
+ }
5750
+ }
5751
+ },
5075
5752
  ContainerConfig: {
5076
- description: "Reusable container configuration shared across templates, pods, and serverless endpoints. Adding a field here automatically propagates to all three resources.\n",
5077
5753
  allOf: [
5078
5754
  {
5079
5755
  $ref: "#/$defs/BaseContainerConfig"
@@ -5090,28 +5766,51 @@ var generatedTools = [
5090
5766
  }
5091
5767
  ]
5092
5768
  },
5093
- Mounts: {
5769
+ GlobalMount: {
5094
5770
  type: "object",
5771
+ required: ["volumeId"],
5095
5772
  additionalProperties: false,
5096
- description: "Storage mounts attached to a pod. At-most-one of `persistent` or\n`network` may be set today (mutually exclusive, enforced at the\nhandler with 400 if both are present). The `network` field is an\narray for forward compatibility with eventual multi-network-volume\nsupport, but `maxItems` is 1 today.\n\nPATCH semantics:\n- Omitting `mounts` or sending `{}` leaves the existing mount\n unchanged.\n- An explicit `network: []` is rejected with 400 (clearing mounts\n is not supported).\n- Mount kind is fixed at create \u2014 a PATCH that introduces a kind\n not present at create (persistent on a network pod, network on\n a persistent pod, or any mount on a previously-mountless pod)\n is rejected with 400.\n- The `volumeId` of a network mount is immutable; a PATCH that\n names a different `volumeId` is rejected with 400.\n- Partial mounts are not supported \u2014 every mount entry must\n include the full schema (`size` + `path` for persistent,\n `volumeId` + `path` for network). Missing required fields \u2192 422.\n",
5773
+ description: "Reference to a GlobalVolume. Usage-billed, so it has no size. The underlying `volumeId` is immutable post-create; the mount `path` may be changed via PATCH.",
5097
5774
  properties: {
5098
- persistent: {
5099
- $ref: "#/$defs/PersistentMount"
5775
+ volumeId: {
5776
+ type: "string",
5777
+ description: "ID of an existing GlobalVolume.",
5778
+ examples: ["gsb_abc"]
5100
5779
  },
5101
- network: {
5102
- type: "array",
5103
- maxItems: 1,
5104
- items: {
5105
- $ref: "#/$defs/NetworkMount"
5106
- }
5780
+ path: {
5781
+ type: "string",
5782
+ description: "Mount path inside the container. Optional on create, where a default is chosen for you; a PATCH that omits it leaves the path unchanged.",
5783
+ examples: ["/data"]
5107
5784
  }
5108
5785
  }
5109
5786
  },
5787
+ Mounts: {
5788
+ description: "Storage mounts for the pod: `persistent` (size + path) excludes both `network` and `global`; `network` (volumeId + path) and `global` (volumeId, optional path) take one entry each and can be combined. Every entry needs its full shape. On update, omit the field to leave the mounts as they are; the mount kind and a volume's volumeId cannot change, and mounts cannot be cleared.",
5789
+ allOf: [
5790
+ {
5791
+ $ref: "#/$defs/BaseMounts"
5792
+ },
5793
+ {
5794
+ type: "object",
5795
+ properties: {
5796
+ global: {
5797
+ type: "array",
5798
+ maxItems: 1,
5799
+ description: "At most one global volume, on GPU and CPU pods, with no data center constraint. Combinable with `network` but not with `persistent` (rejected): upstream drops a persistent disk whenever a volume is attached.",
5800
+ items: {
5801
+ $ref: "#/$defs/GlobalMount"
5802
+ }
5803
+ }
5804
+ }
5805
+ }
5806
+ ],
5807
+ unevaluatedProperties: false
5808
+ },
5110
5809
  NetworkMount: {
5111
5810
  type: "object",
5112
5811
  required: ["volumeId", "path"],
5113
5812
  additionalProperties: false,
5114
- description: "Reference to a NetworkVolume. Custom paths are honored at runtime on\nboth GPU and CPU pods. The underlying `volumeId` is immutable\npost-create; the mount `path` may be changed via PATCH.\n",
5813
+ description: "Reference to a NetworkVolume. Custom paths are honored at runtime on both GPU and CPU pods. The underlying `volumeId` is immutable post-create; the mount `path` may be changed via PATCH.",
5115
5814
  properties: {
5116
5815
  volumeId: {
5117
5816
  type: "string",
@@ -5129,7 +5828,7 @@ var generatedTools = [
5129
5828
  type: "object",
5130
5829
  required: ["size", "path"],
5131
5830
  additionalProperties: false,
5132
- description: "Host-local persistent storage. Pinned to the pod's host machine \u2014 data\ndoes not survive a host failure. Disallowed on CPU pods. Mutually\nexclusive with NetworkMount. Deprecated: prefer NetworkMount for any\ndata you cannot recreate.\n",
5831
+ description: "Host-local persistent storage. Pinned to the pod's host machine \u2014 data does not survive a host failure. Disallowed on CPU pods. Mutually exclusive with NetworkMount. Deprecated: prefer NetworkMount for any data you cannot recreate.",
5133
5832
  properties: {
5134
5833
  size: {
5135
5834
  type: "integer",
@@ -5154,7 +5853,7 @@ var generatedTools = [
5154
5853
  properties: {
5155
5854
  globalNetworking: {
5156
5855
  type: "boolean",
5157
- description: "Enable (true) or disable (false) global networking. Takes effect on the next pod start/restart, not live. Requires an NVIDIA GPU and a global-networking-enabled data center (both enforced upstream). See `GET /v2/catalog/datacenters` (`globalNetwork`) for eligible data centers."
5856
+ description: "Enable (true) or disable (false) global networking. Takes effect on the next pod start/restart, not live. Requires an NVIDIA GPU and a global-networking-enabled data center (both enforced upstream)."
5158
5857
  },
5159
5858
  locked: {
5160
5859
  type: "boolean",
@@ -5169,7 +5868,7 @@ var generatedTools = [
5169
5868
  },
5170
5869
  templateId: {
5171
5870
  type: "string",
5172
- description: "ID of a pod template whose container settings are applied as\nif they were provided in this PATCH body (image, args, disk,\nports, env, registry \u2014 mounts are not applied on update).\nExplicit body fields override the template's; `env` merges\ntemplate and body per key (body wins) and, per PATCH\nsemantics, replaces the pod's env. One-time application \u2014\nno link to the template is retained. Must be one of your\ntemplates or a public template (unknown or inaccessible ID\n\u2192 404); must not be a serverless template (\u2192 422).\n",
5871
+ description: "ID of a pod template whose container settings (image, args, disk, ports, env, registry; not mounts) are applied as if sent in this PATCH body; explicit body fields win. `env` merges template and body per key (body wins) and replaces the pod's existing env. Applied once; must be a non-serverless template you can access.",
5173
5872
  examples: ["30zmvf89kd"]
5174
5873
  }
5175
5874
  }
@@ -5199,6 +5898,7 @@ var generatedTools = [
5199
5898
  idempotentHint: false,
5200
5899
  openWorldHint: true
5201
5900
  },
5901
+ schemaOnDemand: true,
5202
5902
  inputSchema: {
5203
5903
  type: "object",
5204
5904
  properties: {
@@ -5216,7 +5916,7 @@ var generatedTools = [
5216
5916
  type: "object",
5217
5917
  additionalProperties: false,
5218
5918
  minProperties: 1,
5219
- description: "Only the provided fields are updated; at least one field is required.\nThe secret's `name` is immutable.\n",
5919
+ description: "Only the provided fields are updated; at least one field is required. The secret's `name` is immutable.",
5220
5920
  properties: {
5221
5921
  value: {
5222
5922
  type: "string",
@@ -5268,7 +5968,7 @@ var generatedTools = [
5268
5968
  type: "string",
5269
5969
  pattern: "^(ssh|ecdsa|sk)-[^\\s]+ [^\\s]+([ \\t][^\\n\\r]*)?$"
5270
5970
  },
5271
- description: "The full set of SSH public keys to register \u2014 this is a complete\nreplacement, not a merge. Each entry is an authorized_keys-style\nline: `<type> <base64-key> [comment]`, e.g. from\n`~/.ssh/id_ed25519.pub`. Send `[]` to remove all keys. These keys\nare provisioned into pods created with `startSsh` and\nauthenticate both SSH paths reported in the pod's `ssh` block.\n",
5971
+ description: "The full set of SSH public keys to register \u2014 this is a complete replacement, not a merge. Each entry is an authorized_keys-style line: `<type> <base64-key> [comment]`, e.g. from `~/.ssh/id_ed25519.pub`. Send `[]` to remove all keys.",
5272
5972
  examples: [
5273
5973
  [
5274
5974
  "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILXGDN/SclOozk1xsDztpmhGiKkkrfQB9SKoO8dSIQQZ me@example.com"
@@ -5299,6 +5999,7 @@ var generatedTools = [
5299
5999
  idempotentHint: false,
5300
6000
  openWorldHint: true
5301
6001
  },
6002
+ schemaOnDemand: true,
5302
6003
  inputSchema: {
5303
6004
  type: "object",
5304
6005
  properties: {
@@ -5313,11 +6014,10 @@ var generatedTools = [
5313
6014
  $defs: {
5314
6015
  BaseContainerConfig: {
5315
6016
  type: "object",
5316
- description: "Container configuration universal to every containerized resource. Compose ContainerConfig instead unless the resource cannot support a direct registry credential (clusters \u2014 there the registry credential arrives via a pod template, see CreateClusterRequest.templateId).\n",
5317
6017
  properties: {
5318
6018
  args: {
5319
6019
  type: "string",
5320
- description: 'The container\'s command, as a single raw string. This is the field `entrypoint` and `cmd` encode into, exposed in its stored form.\n\nTwo shapes are accepted. A bare shell string is treated as CMD and split into arguments, which is what the console\'s "Container start command" field writes. A JSON object of the form `{"entrypoint":[...],"cmd":[...]}` sets either or both explicitly.\n\nResponses always return both representations: `args` exactly as stored, plus the deconstructed `entrypoint` and `cmd`. Supplying `args` together with `entrypoint` or `cmd` is allowed only when they describe the same command, so a read-modify-write client can send back everything it received. Send `""` to clear, omit to leave unchanged.\n',
6020
+ description: 'The container\'s command as one raw string: a bare shell string is treated as CMD and split into arguments; a JSON object `{"entrypoint":[...],"cmd":[...]}` sets either or both. Send `""` to clear; omit to leave unchanged.',
5321
6021
  examples: [
5322
6022
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
5323
6023
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -5329,7 +6029,7 @@ var generatedTools = [
5329
6029
  items: {
5330
6030
  type: "string"
5331
6031
  },
5332
- description: "Container CMD in exec form. When the image defines an ENTRYPOINT, this is the argument list passed to it. Encoded into the `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
6032
+ description: "Container CMD in exec form: when the image defines an ENTRYPOINT, the argument list passed to it. Encoded into `args`; supply both only when they describe the same command. Send `[]` to clear; omit to leave unchanged.",
5333
6033
  examples: [
5334
6034
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
5335
6035
  ]
@@ -5339,7 +6039,7 @@ var generatedTools = [
5339
6039
  items: {
5340
6040
  type: "string"
5341
6041
  },
5342
- description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.\n",
6042
+ description: "Container ENTRYPOINT in exec form, overriding the image's own. Encoded into `args` field; supplying both is allowed only when they describe the same command. Send `[]` to clear, omit to leave unchanged.",
5343
6043
  examples: [["/bin/bash", "-c"]]
5344
6044
  },
5345
6045
  disk: {
@@ -5376,7 +6076,6 @@ var generatedTools = [
5376
6076
  }
5377
6077
  },
5378
6078
  ContainerConfig: {
5379
- description: "Reusable container configuration shared across templates, pods, and serverless endpoints. Adding a field here automatically propagates to all three resources.\n",
5380
6079
  allOf: [
5381
6080
  {
5382
6081
  $ref: "#/$defs/BaseContainerConfig"
@@ -5397,7 +6096,7 @@ var generatedTools = [
5397
6096
  type: "object",
5398
6097
  required: ["size", "path"],
5399
6098
  additionalProperties: false,
5400
- description: "Host-local persistent storage. Pinned to the pod's host machine \u2014 data\ndoes not survive a host failure. Disallowed on CPU pods. Mutually\nexclusive with NetworkMount. Deprecated: prefer NetworkMount for any\ndata you cannot recreate.\n",
6099
+ description: "Host-local persistent storage. Pinned to the pod's host machine \u2014 data does not survive a host failure. Disallowed on CPU pods. Mutually exclusive with NetworkMount. Deprecated: prefer NetworkMount for any data you cannot recreate.",
5401
6100
  properties: {
5402
6101
  size: {
5403
6102
  type: "integer",
@@ -5414,13 +6113,13 @@ var generatedTools = [
5414
6113
  },
5415
6114
  TemplateCategory: {
5416
6115
  type: "string",
5417
- description: "Controls how the template is grouped and filtered in the Runpod console.\nIt does not affect hardware selection, scheduling, or billing.\n- `CPU` \u2014 CPU-only workloads\n- `NVIDIA` \u2014 NVIDIA GPU workloads\n- `AMD` \u2014 AMD GPU workloads\n",
6116
+ description: "Controls how the template is grouped and filtered in the Runpod console. It does not affect hardware selection, scheduling, or billing. `CPU` \u2014 CPU-only workloads. `NVIDIA` \u2014 NVIDIA GPU workloads. `AMD` \u2014 AMD GPU workloads",
5418
6117
  enum: ["CPU", "NVIDIA", "AMD"]
5419
6118
  },
5420
6119
  TemplateMounts: {
5421
6120
  type: "object",
5422
6121
  additionalProperties: false,
5423
- description: "Storage mounts attached to a template. Templates support only a\nsingle persistent mount today; any `network` property is rejected\nwith 422 by the schema validator.\n\nPATCH semantics: omitting `mounts` or sending `{}` leaves the\nexisting mount unchanged.\n",
6122
+ description: "Storage mounts for containers from this template: a single `persistent` mount only (`network` is rejected). On update, omitting `mounts` or sending `{}` leaves the existing mount unchanged.",
5424
6123
  properties: {
5425
6124
  persistent: {
5426
6125
  $ref: "#/$defs/PersistentMount"
@@ -6169,6 +6868,8 @@ var listHubRepos = {
6169
6868
  };
6170
6869
  var deployHubRepo = {
6171
6870
  name: "deploy-hub-repo",
6871
+ // Two sequential upstream calls: together they can outlast a client's request timeout.
6872
+ holdsResponse: true,
6172
6873
  annotations: write,
6173
6874
  description: "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).",
6174
6875
  inputSchema: {
@@ -6657,6 +7358,7 @@ var runEndpoint = {
6657
7358
  };
6658
7359
  var runsyncEndpoint = {
6659
7360
  name: "runsync-endpoint",
7361
+ holdsResponse: true,
6660
7362
  annotations: write,
6661
7363
  description: "Submit a synchronous job to a Serverless endpoint and wait for the result. Best for fast tasks: if the job outlives the wait, the response returns a job ID and a non-terminal status (IN_QUEUE or IN_PROGRESS) to poll with get-job-status. Max payload 20 MB; results expire after 1 minute. The wait parameter extends the server-side wait" + (HOSTED ? ` \u2014 this server runs behind a 60-second gateway deadline, so waits clamp to ${HTTP_LONG_POLL_BUDGET_MS} ms, which is also the effective default. For longer jobs use run-endpoint + get-job-status.` : " to up to 5 minutes (300000 ms); it defaults to 90000 (90 seconds)."),
6662
7364
  inputSchema: {
@@ -6701,6 +7403,7 @@ var runsyncEndpoint = {
6701
7403
  };
6702
7404
  var getJobStatus = {
6703
7405
  name: "get-job-status",
7406
+ holdsResponse: true,
6704
7407
  annotations: readOnly,
6705
7408
  description: "Check the status of a Serverless job. Returns the current status and output when complete. Job statuses: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELLED, TIMED_OUT. Pass `wait` (milliseconds, up to " + STATUS_WAIT_MAX_MS + ") to BLOCK server-side, polling until the job reaches a terminal status or the budget expires \u2014 use it to ride out a cold start (a first job on a fresh worker pulls the image and loads the model, commonly 1\u20135+ minutes) in a single call instead of returning on the first IN_QUEUE and tight-looping the tool. Without `wait` it returns the current status immediately (a single check). If the budget expires before a terminal status, it returns the latest non-terminal status with pollingTimedOut:true \u2014 call again (with `wait`) to keep blocking. IMPORTANT: a job stuck IN_QUEUE does not necessarily mean a capacity shortage \u2014 a worker may have spun up and crash-looped. When the job is IN_QUEUE this tool attaches a workerHealth summary and a hint. To dig deeper call list-endpoint-workers, then read stream-worker-logs regardless of the status a worker reports: a container failing to start loops while its worker still shows RUNNING or THROTTLED and unhealthy stays 0, so UNHEALTHY is one symptom of a crash loop and not a test for it. The logs are the authority.",
6706
7409
  inputSchema: {
@@ -6749,6 +7452,7 @@ var getJobStatus = {
6749
7452
  };
6750
7453
  var streamJob = {
6751
7454
  name: "stream-job",
7455
+ holdsResponse: true,
6752
7456
  annotations: readOnly,
6753
7457
  description: `Retrieve streaming output from a Serverless job. The worker must support streaming output. Polls /stream/{jobId} and collects chunks until the status is COMPLETED, FAILED, CANCELLED, or TIMED_OUT, for up to ${formatBudget(STREAM_BUDGET_MS)}. If the budget expires first, returns the chunks collected so far with pollingTimedOut: true \u2014 call stream-job again to resume where it left off, or get-job-status to check without streaming.`,
6754
7458
  inputSchema: {
@@ -6968,7 +7672,7 @@ var listTemplates = {
6968
7672
  // src/specgen/tools/logs.ts
6969
7673
  init_hosts();
6970
7674
 
6971
- // node_modules/.pnpm/@runpod+typescript-api-sdk@0.2.0/node_modules/@runpod/typescript-api-sdk/dist/index.js
7675
+ // node_modules/.pnpm/@runpod+typescript-api-sdk@0.3.0/node_modules/@runpod/typescript-api-sdk/dist/index.js
6972
7676
  var PATH_PARAM_RE = /\{[^{}]+\}/g;
6973
7677
  var supportsRequestInitExt = () => {
6974
7678
  return typeof process === "object" && Number.parseInt(process?.versions?.node?.substring(0, 2)) >= 18 && process.versions.undici;
@@ -8372,6 +9076,7 @@ function logParams(args) {
8372
9076
  }
8373
9077
  var streamPodLogs = {
8374
9078
  name: "stream-pod-logs",
9079
+ holdsResponse: true,
8375
9080
  annotations: readOnly,
8376
9081
  description: "Read a bounded snapshot of a Pod's logs (container and/or system source). Holds the live stream open for maxWaitMs and returns the parsed log lines; `truncated: true` means the byte cap cut the output. Use `since` to resume from a timestamp.",
8377
9082
  inputSchema: {
@@ -8394,6 +9099,7 @@ var streamPodLogs = {
8394
9099
  };
8395
9100
  var streamWorkerLogs = {
8396
9101
  name: "stream-worker-logs",
9102
+ holdsResponse: true,
8397
9103
  annotations: readOnly,
8398
9104
  description: "Read a bounded snapshot of a Serverless worker's logs (container and/or system source). Holds the live stream open for maxWaitMs and returns the parsed log lines; `truncated: true` means the byte cap cut the output. Get worker IDs from list-endpoint-workers. Use this whenever a job stays IN_QUEUE while workers exist, whatever status they report \u2014 a crash-looping container often still shows RUNNING or THROTTLED with unhealthy 0, so these logs, not the worker status, are what identify a crash loop.",
8399
9105
  inputSchema: {
@@ -8656,6 +9362,7 @@ var source = {
8656
9362
  var listGuides = {
8657
9363
  name: "list-guides",
8658
9364
  annotations: bundledRead,
9365
+ recordedArguments: ["kind", "query", "concept"],
8659
9366
  description: "List the official Runpod guides: skills (how to work with each tool), reference docs (how Runpod works), and golden paths (live-verified end-to-end walkthroughs such as a dev pod, a Whisper endpoint or a fine-tune-to-serverless pipeline). Read one with read-guide. For a golden path, mcp says whether an agent with only these MCP tools can finish it: full (follow it with tool calls), partial (use it for the plan and facts; name the step the user must run in a terminal), none (reference only). Skills and reference docs have mcp null. concepts lists the concept ids it works with (see lookup-concept). Filter with kind, query or concept.",
8660
9367
  inputSchema: {
8661
9368
  type: "object",
@@ -8691,6 +9398,8 @@ var listGuides = {
8691
9398
  var readGuide = {
8692
9399
  name: "read-guide",
8693
9400
  annotations: bundledRead,
9401
+ recordedArguments: ["id"],
9402
+ recordedGuideKind: (args) => knowledge.guides.find((guide) => guide.id === String(args.id).trim())?.kind,
8694
9403
  description: 'Read one official Runpod guide in full, by the id list-guides returns (for example "runpod-usage/storage" or "golden-path/06-dev-pod").',
8695
9404
  inputSchema: {
8696
9405
  type: "object",
@@ -8728,6 +9437,7 @@ var readGuide = {
8728
9437
  var lookupConcept = {
8729
9438
  name: "lookup-concept",
8730
9439
  annotations: bundledRead,
9440
+ recordedArguments: ["concept"],
8731
9441
  description: 'Look up one Runpod concept (pod, network volume, machine, worker, template, \u2026) by id, name or alias; case, plurals and small typos are tolerated, and `matched_as` shows the key used when the match was not exact. Returns its summary, fields, states, relations and rules, each rule with the public source it comes from, the concepts that point at it, and the linked guides: examples (golden paths that use it, with their mcp level) and docs (skills and reference docs that explain it). Use it for exact facts, for example "is a volume disk tied to one machine" or "what happens to a stopped pod\'s GPU".',
8732
9442
  inputSchema: {
8733
9443
  type: "object",
@@ -8780,6 +9490,7 @@ var lookupConcept = {
8780
9490
  var searchConcepts = {
8781
9491
  name: "search-concepts",
8782
9492
  annotations: bundledRead,
9493
+ recordedArguments: ["query", "limit"],
8783
9494
  description: 'Search the rules of the Runpod concept graph by words, for example "restart stopped pod no gpu" or "network volume data center". Returns the best-matching rules with their concept and public evidence. Follow up with lookup-concept for the full concept.',
8784
9495
  inputSchema: {
8785
9496
  type: "object",
@@ -8844,6 +9555,140 @@ var guideResources = knowledge.guides.map((guide) => ({
8844
9555
  text: guide.body
8845
9556
  }));
8846
9557
 
9558
+ // src/specgen/prose.ts
9559
+ var ABBREVIATIONS = ["e.g.", "i.e.", "etc.", "vs."];
9560
+ var DOT = "\0";
9561
+ function splitSentences(text) {
9562
+ let guarded = text;
9563
+ for (const abbreviation of ABBREVIATIONS) {
9564
+ guarded = guarded.replaceAll(
9565
+ abbreviation,
9566
+ abbreviation.replaceAll(".", DOT)
9567
+ );
9568
+ }
9569
+ return guarded.split(/(?<=[.!?])\s+(?=[A-Z`"'([])/).map((sentence) => sentence.replaceAll(DOT, ".").trim()).filter(Boolean);
9570
+ }
9571
+ function leadSentence(text) {
9572
+ return splitSentences(text)[0] ?? text;
9573
+ }
9574
+
9575
+ // src/specgen/on-demand.ts
9576
+ var DESCRIBE_TOOL_NAME = "describe-tool";
9577
+ var ON_DEMAND_POINTER = ` Call ${DESCRIBE_TOOL_NAME} for the full argument schema before the first use.`;
9578
+ function listedAnnotations(annotations) {
9579
+ const { idempotentHint, ...listed } = annotations;
9580
+ return idempotentHint ? annotations : listed;
9581
+ }
9582
+ function listTool(tool) {
9583
+ const { name, description, inputSchema } = tool;
9584
+ const annotations = listedAnnotations(tool.annotations);
9585
+ if (!tool.schemaOnDemand)
9586
+ return { name, description, inputSchema, annotations };
9587
+ return {
9588
+ name,
9589
+ description: description + ON_DEMAND_POINTER,
9590
+ inputSchema: callShape(inputSchema),
9591
+ annotations
9592
+ };
9593
+ }
9594
+ function callShape(schema) {
9595
+ const defs = schema.$defs ?? {};
9596
+ const resolve2 = (node) => {
9597
+ if (typeof node.$ref !== "string") return node;
9598
+ const name = node.$ref.split("/").pop() ?? "";
9599
+ return defs[name] ?? {};
9600
+ };
9601
+ const properties = Object.fromEntries(
9602
+ Object.entries(schema.properties ?? {}).map(
9603
+ ([key, value]) => [key, shallowProperty(resolve2(value), resolve2)]
9604
+ )
9605
+ );
9606
+ return {
9607
+ type: "object",
9608
+ properties,
9609
+ ...schema.required ? { required: schema.required } : {}
9610
+ };
9611
+ }
9612
+ function shallowProperty(node, resolve2) {
9613
+ const out = {};
9614
+ const type = schemaType(node, resolve2);
9615
+ if (type !== void 0) out.type = type;
9616
+ if (type === "array") {
9617
+ const items = resolve2(node.items ?? {});
9618
+ const itemType = schemaType(items, resolve2);
9619
+ if (itemType !== void 0) out.items = { type: itemType };
9620
+ }
9621
+ if (Array.isArray(node.enum)) out.enum = node.enum;
9622
+ if (typeof node.description === "string")
9623
+ out.description = leadSentence(node.description);
9624
+ return out;
9625
+ }
9626
+ function schemaType(node, resolve2) {
9627
+ if (node.type !== void 0) return node.type;
9628
+ if (node.properties || node.allOf) return "object";
9629
+ const branches2 = node.anyOf ?? node.oneOf;
9630
+ const allObjects = branches2?.every(
9631
+ (branch) => schemaType(resolve2(branch), resolve2) === "object"
9632
+ ) ?? false;
9633
+ return allObjects ? "object" : void 0;
9634
+ }
9635
+
9636
+ // src/specgen/tools/describe.ts
9637
+ function createDescribeTool(served) {
9638
+ return {
9639
+ name: DESCRIBE_TOOL_NAME,
9640
+ description: "Return one tool's full definition: its description, complete argument schema (every field, nested objects, constraints) and annotations. Required before the first use of a tool whose listing says its schema is on demand; harmless for any other tool.",
9641
+ inputSchema: {
9642
+ type: "object",
9643
+ properties: {
9644
+ name: {
9645
+ type: "string",
9646
+ description: "A tool name exactly as this server's tool list spells it."
9647
+ }
9648
+ },
9649
+ required: ["name"]
9650
+ },
9651
+ annotations: {
9652
+ readOnlyHint: true,
9653
+ destructiveHint: false,
9654
+ idempotentHint: true,
9655
+ openWorldHint: false
9656
+ },
9657
+ handler: async (_ctx, args) => {
9658
+ const name = String(args.name ?? "");
9659
+ const tools = served();
9660
+ const tool = tools.find((candidate) => candidate.name === name);
9661
+ if (!tool) {
9662
+ return {
9663
+ ok: false,
9664
+ status: 404,
9665
+ payload: {
9666
+ error: `No tool named ${name}.`,
9667
+ suggestions: similarNames(
9668
+ name,
9669
+ tools.map((candidate) => candidate.name)
9670
+ ),
9671
+ hint: "Tool names are exactly as this server lists them; the suggestions share a word with the name you sent."
9672
+ }
9673
+ };
9674
+ }
9675
+ const { description, inputSchema, annotations } = tool;
9676
+ return ok({ name: tool.name, description, inputSchema, annotations });
9677
+ }
9678
+ };
9679
+ }
9680
+ function similarNames(query, names) {
9681
+ const wanted = new Set(words2(query));
9682
+ return names.map((name) => {
9683
+ const own = new Set(words2(name));
9684
+ const shared = [...own].filter((word) => wanted.has(word)).length;
9685
+ return { name, score: shared / (/* @__PURE__ */ new Set([...own, ...wanted])).size };
9686
+ }).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name)).slice(0, 8).map((entry) => entry.name);
9687
+ }
9688
+ function words2(name) {
9689
+ return name.toLowerCase().split(/[-_\s]+/).filter(Boolean).map((word) => word.replace(/s$/, ""));
9690
+ }
9691
+
8847
9692
  // src/specgen/analytics.ts
8848
9693
  var import_node_crypto = require("crypto");
8849
9694
  var CAPTURE_TIMEOUT_MS = 3e3;
@@ -8858,7 +9703,22 @@ function analyticsCallerId(apiKey, accountId) {
8858
9703
  const prefix = accountId ? "a:" : "k:";
8859
9704
  return prefix + (0, import_node_crypto.createHmac)("sha256", salt).update(identity).digest("hex").slice(0, 16);
8860
9705
  }
8861
- function captureToolCall(apiKey, event) {
9706
+ var MAX_RECORDED_VALUE_LENGTH = 200;
9707
+ function bounded(value) {
9708
+ return value.slice(0, MAX_RECORDED_VALUE_LENGTH);
9709
+ }
9710
+ function recordedArgumentValues(names, args) {
9711
+ if (!names?.length) return void 0;
9712
+ const values = {};
9713
+ for (const name of names) {
9714
+ const value = args[name];
9715
+ if (typeof value === "string" || typeof value === "number") {
9716
+ values[name] = bounded(String(value).trim());
9717
+ }
9718
+ }
9719
+ return Object.keys(values).length ? values : void 0;
9720
+ }
9721
+ function capture(event, distinctId, properties) {
8862
9722
  const posthogKey = process.env.POSTHOG_API_KEY;
8863
9723
  if (!posthogKey) return;
8864
9724
  void fetch(`${posthogHost()}/capture/`, {
@@ -8866,18 +9726,12 @@ function captureToolCall(apiKey, event) {
8866
9726
  headers: { "Content-Type": "application/json" },
8867
9727
  body: JSON.stringify({
8868
9728
  api_key: posthogKey,
8869
- event: "runpod_mcp_tool_call",
8870
- distinct_id: analyticsCallerId(apiKey, event.accountId),
9729
+ event,
9730
+ distinct_id: distinctId,
8871
9731
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8872
9732
  properties: {
8873
- tool: event.tool,
8874
- ok: event.ok,
8875
- status: event.status,
8876
- duration_ms: event.durationMs,
8877
- transport: event.transport,
8878
- server_version: event.serverVersion,
9733
+ ...properties,
8879
9734
  surface_version: SURFACE_VERSION,
8880
- ...event.clientName ? { client_name: event.clientName } : {},
8881
9735
  $process_person_profile: false
8882
9736
  }
8883
9737
  }),
@@ -8885,9 +9739,44 @@ function captureToolCall(apiKey, event) {
8885
9739
  }).catch(() => {
8886
9740
  });
8887
9741
  }
9742
+ function captureToolCall(apiKey, event) {
9743
+ const args = Object.fromEntries(
9744
+ Object.entries(event.args ?? {}).map(([name, value]) => [
9745
+ `arg_${name}`,
9746
+ value
9747
+ ])
9748
+ );
9749
+ capture("runpod_mcp_tool_call", analyticsCallerId(apiKey, event.accountId), {
9750
+ tool: event.tool,
9751
+ ok: event.ok,
9752
+ status: event.status,
9753
+ duration_ms: event.durationMs,
9754
+ transport: event.transport,
9755
+ server_version: event.serverVersion,
9756
+ ...event.clientName ? { client_name: event.clientName } : {},
9757
+ ...event.guideKind ? { guide_kind: event.guideKind } : {},
9758
+ ...event.blockedBy ? { blocked_by: event.blockedBy } : {},
9759
+ ...args
9760
+ });
9761
+ }
9762
+ function captureResourceCall(apiKey, event) {
9763
+ capture(
9764
+ "runpod_mcp_resource_call",
9765
+ analyticsCallerId(apiKey, event.accountId),
9766
+ {
9767
+ method: event.method,
9768
+ uri: bounded(event.uri),
9769
+ ok: event.ok,
9770
+ transport: event.transport,
9771
+ server_version: event.serverVersion,
9772
+ ...event.clientName ? { client_name: event.clientName } : {}
9773
+ }
9774
+ );
9775
+ }
8888
9776
 
8889
9777
  // src/http.ts
8890
9778
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
9779
+ var import_types = require("@modelcontextprotocol/sdk/types.js");
8891
9780
 
8892
9781
  // src/specgen/context.ts
8893
9782
  var import_node_crypto2 = require("crypto");
@@ -9053,14 +9942,90 @@ function createToolContext(options = {}) {
9053
9942
  };
9054
9943
  }
9055
9944
 
9056
- // src/_shared/credential-check.ts
9945
+ // src/specgen/ops.ts
9057
9946
  var import_node_crypto3 = require("crypto");
9947
+ var SALT = (0, import_node_crypto3.randomBytes)(16).toString("hex");
9948
+ function callerId(token) {
9949
+ if (!token) return "anonymous";
9950
+ return (0, import_node_crypto3.createHash)("sha256").update(SALT).update(token).digest("hex").slice(0, 12);
9951
+ }
9952
+ var noopRateLimiter = async () => ({ allowed: true });
9953
+ function logToolCall(entry) {
9954
+ console.error("tool_call", JSON.stringify(entry));
9955
+ }
9956
+
9957
+ // src/search/knowledge.ts
9958
+ init_hosts();
9959
+ var SEARCH_NAMESPACES = ["docs", "knowledge", "blog", "site"];
9960
+ var MAX_QUERY_CHARS = 2e3;
9961
+ var MAX_SEARCH_LIMIT = 20;
9962
+ var UNREACHABLE = {
9963
+ status: 504,
9964
+ body: {
9965
+ error: "Runpod knowledge search did not respond.",
9966
+ hint: "Retry the same call once."
9967
+ }
9968
+ };
9969
+ function parseSearchRequest(raw) {
9970
+ if (typeof raw.query !== "string" || !raw.query.trim()) {
9971
+ return { error: "query must be a non-empty string." };
9972
+ }
9973
+ if (raw.query.length > MAX_QUERY_CHARS) {
9974
+ return { error: `query exceeds ${MAX_QUERY_CHARS} characters.` };
9975
+ }
9976
+ const request = { query: raw.query };
9977
+ if (raw.namespaces !== void 0) {
9978
+ if (!Array.isArray(raw.namespaces) || raw.namespaces.length === 0 || !raw.namespaces.every(
9979
+ (ns) => SEARCH_NAMESPACES.includes(ns)
9980
+ )) {
9981
+ return {
9982
+ error: `namespaces must be a non-empty list of: ${SEARCH_NAMESPACES.join(", ")}.`
9983
+ };
9984
+ }
9985
+ request.namespaces = raw.namespaces;
9986
+ }
9987
+ if (raw.limit !== void 0) {
9988
+ if (typeof raw.limit !== "number" || !Number.isInteger(raw.limit) || raw.limit < 1 || raw.limit > MAX_SEARCH_LIMIT) {
9989
+ return {
9990
+ error: `limit must be an integer from 1 to ${MAX_SEARCH_LIMIT}.`
9991
+ };
9992
+ }
9993
+ request.limit = raw.limit;
9994
+ }
9995
+ return { request };
9996
+ }
9997
+ var HOSTED_CLIENT_TIMEOUT_MS = 55e3;
9998
+ function hostedSearch(searchUrl, doFetch = fetch) {
9999
+ return async (request, apiKey) => {
10000
+ try {
10001
+ const res = await doFetch(searchUrl, {
10002
+ method: "POST",
10003
+ headers: {
10004
+ "Content-Type": "application/json",
10005
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
10006
+ },
10007
+ body: JSON.stringify(request),
10008
+ signal: AbortSignal.timeout(HOSTED_CLIENT_TIMEOUT_MS)
10009
+ });
10010
+ const body = await res.json().catch(() => null);
10011
+ return body ? { status: res.status, body } : {
10012
+ status: 502,
10013
+ body: { error: `Runpod knowledge search returned ${res.status}.` }
10014
+ };
10015
+ } catch {
10016
+ return UNREACHABLE;
10017
+ }
10018
+ };
10019
+ }
10020
+
10021
+ // src/_shared/credential-check.ts
10022
+ var import_node_crypto4 = require("crypto");
9058
10023
  var VALID_TTL_MS = 6e4;
9059
10024
  var INVALID_TTL_MS = 3e4;
9060
10025
  var REQUEST_TIMEOUT_MS = 4e3;
9061
10026
  var MAX_CACHE_ENTRIES = 1e3;
9062
10027
  function hashToken(token) {
9063
- return (0, import_node_crypto3.createHash)("sha256").update(token).digest("hex");
10028
+ return (0, import_node_crypto4.createHash)("sha256").update(token).digest("hex");
9064
10029
  }
9065
10030
  function createCredentialChecker(deps) {
9066
10031
  const now = deps.now ?? (() => performance.now());
@@ -9384,16 +10349,48 @@ function createAlpTools(opts) {
9384
10349
  return [reportFeedback, saveToJournal, askQuestion, readJournalTool];
9385
10350
  }
9386
10351
 
9387
- // src/specgen/ops.ts
9388
- var import_node_crypto4 = require("crypto");
9389
- var SALT = (0, import_node_crypto4.randomBytes)(16).toString("hex");
9390
- function callerId(token) {
9391
- if (!token) return "anonymous";
9392
- return (0, import_node_crypto4.createHash)("sha256").update(SALT).update(token).digest("hex").slice(0, 12);
9393
- }
9394
- var noopRateLimiter = async () => ({ allowed: true });
9395
- function logToolCall(entry) {
9396
- console.error("tool_call", JSON.stringify(entry));
10352
+ // src/specgen/tools/search.ts
10353
+ function createSearchDocsTool(search) {
10354
+ return {
10355
+ name: "search-docs",
10356
+ annotations: readOnly,
10357
+ recordedArguments: ["limit"],
10358
+ description: 'Search Runpod documentation, curated support knowledge, the Runpod blog and runpod.io for passages that answer a question, for example "how do I attach a network volume to a Serverless endpoint". Returns ranked passages with their source URLs. Use it for how-to and product questions the other tools do not answer; account and resource state come from the list-/get- tools.',
10359
+ inputSchema: {
10360
+ type: "object",
10361
+ properties: {
10362
+ query: {
10363
+ type: "string",
10364
+ maxLength: MAX_QUERY_CHARS,
10365
+ description: "The question, in natural language."
10366
+ },
10367
+ namespaces: {
10368
+ type: "array",
10369
+ items: { type: "string", enum: [...SEARCH_NAMESPACES] },
10370
+ minItems: 1,
10371
+ description: "Sources to search: docs (docs.runpod.io), knowledge (curated support answers), blog, site (runpod.io pages). Default: docs, knowledge, blog."
10372
+ },
10373
+ limit: {
10374
+ type: "integer",
10375
+ minimum: 1,
10376
+ maximum: MAX_SEARCH_LIMIT,
10377
+ description: "Most passages to return. Default 8."
10378
+ }
10379
+ },
10380
+ required: ["query"],
10381
+ additionalProperties: false
10382
+ },
10383
+ handler: async (ctx, args) => {
10384
+ const parsed = parseSearchRequest(args);
10385
+ if ("error" in parsed) return badRequest(parsed.error);
10386
+ const outcome = await search(parsed.request, ctx.apiKey);
10387
+ return {
10388
+ ok: outcome.status === 200,
10389
+ status: outcome.status,
10390
+ payload: outcome.body
10391
+ };
10392
+ }
10393
+ };
9397
10394
  }
9398
10395
 
9399
10396
  // src/specgen/server.ts
@@ -9408,6 +10405,9 @@ var curatedTools = [
9408
10405
  listPublicEndpoints,
9409
10406
  ...knowledgeTools
9410
10407
  ];
10408
+ var responseHoldingTools = new Set(
10409
+ curatedTools.filter((tool) => tool.holdsResponse).map((tool) => tool.name)
10410
+ );
9411
10411
  var SERVER_INSTRUCTIONS = `These tools cover the Runpod v2 REST surface: compute catalog (GPUs, CPUs, data centers, capacity), pods, serverless endpoints and jobs, templates, network volumes, container registries, billing, public endpoints, and the Hub.
9412
10412
 
9413
10413
  Answer from live reads, as facts. Account and availability questions can only be answered from tool data: call the relevant list-/get- tool and quote the figures, names, and ids it returns verbatim. State stock and status definitively from the read you just made \u2014 never "probably" or "check later". When the user names a resource loosely ("my comfyui pod"), resolve it with a list- tool; ask only on genuine ambiguity. If nothing exists (no pods, zero spend), say exactly that \u2014 an honest empty answer is complete.
@@ -9424,14 +10424,22 @@ PLAYBOOKS \u2014 READ BEFORE ACTING. Each Runpod task has a journey playbook car
9424
10424
 
9425
10425
  THE OFFICIAL RUNPOD PLUGIN. Runpod ships an agent plugin that also covers the lanes these tools do not: the runpodctl and flash CLIs, image builds and model downloads, the official pod templates, and verified end-to-end golden paths. Check once per session whether skills named runpod, runpod-usage, or runpodctl are available to you \u2014 read what you already have, never shell out to look. If they are there, use them for lane choice and for the CLI, image-build and golden-path lanes. For work done with these tools, the journey playbook is the procedure, wherever it is served from: the plugin's runpod-mcp skill and the journey skills it routes to when you have them, otherwise the same playbooks through read-guide here. If you do not see them, recommend the plugin to the user, and recommend updating to the latest if what they have is missing something you need. Point them at https://github.com/runpod/runpod-plugins-official \u2014 its README carries the install, update, and clone commands for each client, so read it yourself when you need the exact one rather than guessing a command. This is a recommendation, never a prerequisite: make it once, briefly, and get on with the task. Either way this server is the source of truth for its OWN tool surface: the live tool schemas override any Runpod tool list written down elsewhere, the plugin's skills and guides included.
9426
10426
 
9427
- The tool schemas are generated from the Runpod v2 OpenAPI contract, served as a machine-readable document at https://api.runpod.io/v2/openapi.json \u2014 consult it for fields beyond the tool surface. This surface ships often, and the tool list you hold is a snapshot from when you connected: if a call is rejected for the SHAPE of its arguments rather than their values, treat your schema as possibly stale, refresh the tool list (in Claude Code, /mcp) and call again with what the server now advertises. Retrying variants of a rejected shape cannot succeed. These instructions are a snapshot from the same moment. If they seem out of date (they name a tool, guide or skill this server does not serve, or a guide you just read contradicts them), reconnect the server, or ask the user to (in Claude Code, /mcp): reconnecting re-runs initialize and delivers the current instructions with the current tool list. Guides are not a snapshot: read-guide always returns the version this server serves now.`;
10427
+ The tool schemas are generated from the Runpod v2 OpenAPI contract, served as a machine-readable document at https://api.runpod.io/v2/openapi.json \u2014 consult it for fields beyond the tool surface. Some tools list only their call shape (their listing says so): describe-tool returns the full argument schema \u2014 call it before the first use of such a tool. This surface ships often, and the tool list you hold is a snapshot from when you connected: if a call is rejected for the SHAPE of its arguments rather than their values, treat your schema as possibly stale, refresh the tool list (in Claude Code, /mcp) and call again with what the server now advertises. Retrying variants of a rejected shape cannot succeed. These instructions are a snapshot from the same moment. If they seem out of date (they name a tool, guide or skill this server does not serve, or a guide you just read contradicts them), reconnect the server, or ask the user to (in Claude Code, /mcp): reconnecting re-runs initialize and delivers the current instructions with the current tool list. Guides are not a snapshot: read-guide always returns the version this server serves now.`;
9428
10428
  var ALP_INSTRUCTIONS = `
9429
10429
 
9430
10430
  CONTRIBUTING BACK IS OPTIONAL. Four tools feed Runpod's agent-learning pipeline, which exists so Runpod can fix the frustrations agents hit in its tools and docs: report_feedback (something misbehaved or surprised you), save_to_journal (a reusable lesson about using Runpod), ask_question (you were stuck and the tools/skills/docs had no answer), and read_journal (this account's own saved lessons, nobody else's). Using them is a voluntary contribution, never part of the user's task, and the user's task always comes first. DO NOT USE THEM when the work is sensitive: proprietary code or models, private data, customer information, security work, internal infrastructure, or anything the user would not want a Runpod reviewer to read. Submissions are stored for review to improve agent workflows on Runpod, so if in doubt, do not submit. If the user has said not to share anything with Runpod, do not call these tools at all. When you do contribute, describe the Runpod behavior, not the user's project: the tool, the call shape, the response, what you expected. Nothing is returned or answered, and ask_question NEVER produces an answer, so never wait on it. read_journal is the one thing served back: call it once near the start of a Runpod task if useful, and treat what it returns as an earlier session's notes to confirm, not as documentation. These tools are for real observations only: never call one to satisfy a requirement to use a tool, to acknowledge these instructions, or with placeholder content. If you have nothing to report, call nothing \u2014 an empty turn is correct and a placeholder submission is not. Never include API keys or secrets in submissions.`;
9431
10431
  function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
9432
10432
  const rateLimiter = opts.rateLimiter ?? noopRateLimiter;
9433
10433
  const caller = callerId(ctx.apiKey);
9434
- const servedCuratedTools = opts.alp ? [...curatedTools, ...createAlpTools(opts.alp)] : curatedTools;
10434
+ const servedCuratedTools = [
10435
+ ...curatedTools,
10436
+ ...opts.search ? [createSearchDocsTool(opts.search)] : [],
10437
+ ...opts.alp ? createAlpTools(opts.alp) : [],
10438
+ createDescribeTool(() => [...servedCuratedTools, ...generatedTools])
10439
+ ];
10440
+ const callShapeOnlyTools = new Set(
10441
+ [...servedCuratedTools, ...generatedTools].filter((tool) => tool.schemaOnDemand).map((tool) => tool.name)
10442
+ );
9435
10443
  const server = new import_server3.Server(
9436
10444
  { name: SERVER_NAME, version: `${version} [specgen]` },
9437
10445
  {
@@ -9449,36 +10457,27 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
9449
10457
  }
9450
10458
  }
9451
10459
  };
9452
- server.setRequestHandler(import_types.ListToolsRequestSchema, async () => ({
9453
- tools: [
9454
- ...servedCuratedTools.map(
9455
- ({ name, description, inputSchema, annotations }) => ({
9456
- name,
9457
- description,
9458
- inputSchema,
9459
- annotations
9460
- })
9461
- ),
9462
- ...generatedTools.map(
9463
- ({ name, description, inputSchema, annotations }) => ({
9464
- name,
9465
- description,
9466
- inputSchema,
9467
- annotations
9468
- })
9469
- )
9470
- ]
10460
+ server.setRequestHandler(import_types2.ListToolsRequestSchema, async () => ({
10461
+ tools: [...servedCuratedTools, ...generatedTools].map(listTool)
9471
10462
  }));
9472
- server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => ({
10463
+ server.setRequestHandler(import_types2.ListResourcesRequestSchema, async () => ({
9473
10464
  resources: guideResources.map(({ text: _text, ...resource }) => resource)
9474
10465
  }));
9475
- server.setRequestHandler(import_types.ListResourceTemplatesRequestSchema, async () => ({
10466
+ server.setRequestHandler(import_types2.ListResourceTemplatesRequestSchema, async () => ({
9476
10467
  resourceTemplates: []
9477
10468
  }));
9478
- server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
10469
+ server.setRequestHandler(import_types2.ReadResourceRequestSchema, async (request) => {
9479
10470
  const guide = guideResources.find(
9480
10471
  (candidate) => candidate.uri === request.params.uri
9481
10472
  );
10473
+ if (opts.analytics) {
10474
+ captureResourceCall(ctx.apiKey, {
10475
+ method: "resources/read",
10476
+ uri: guide ? guide.uri : "unknown",
10477
+ ok: Boolean(guide),
10478
+ ...opts.analytics
10479
+ });
10480
+ }
9482
10481
  if (!guide) {
9483
10482
  throw new Error(
9484
10483
  `Unknown resource ${request.params.uri}. Resources are the plugin guides under ${GUIDE_URI_PREFIX}; call list-guides for the guide ids.`
@@ -9490,7 +10489,7 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
9490
10489
  ]
9491
10490
  };
9492
10491
  });
9493
- server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
10492
+ server.setRequestHandler(import_types2.CallToolRequestSchema, async (request) => {
9494
10493
  const args = request.params.arguments ?? {};
9495
10494
  const verdict = await rateLimiter(caller, request.params.name);
9496
10495
  if (!verdict.allowed) {
@@ -9501,6 +10500,16 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
9501
10500
  status: 429,
9502
10501
  durationMs: 0
9503
10502
  });
10503
+ if (opts.analytics) {
10504
+ captureToolCall(ctx.apiKey, {
10505
+ tool: request.params.name,
10506
+ ok: false,
10507
+ status: 429,
10508
+ durationMs: 0,
10509
+ ...verdict.reason ? { blockedBy: verdict.reason } : {},
10510
+ ...opts.analytics
10511
+ });
10512
+ }
9504
10513
  return {
9505
10514
  content: [
9506
10515
  {
@@ -9538,11 +10547,14 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
9538
10547
  ok: result.ok,
9539
10548
  status: result.status,
9540
10549
  durationMs: Date.now() - startedAt,
10550
+ args: recordedArgumentValues(curated?.recordedArguments, args),
10551
+ guideKind: curated?.recordedGuideKind?.(args),
9541
10552
  ...opts.analytics
9542
10553
  });
9543
10554
  }
10555
+ opts.onToolResult?.(request.params.name, result.status);
9544
10556
  if (!result.ok && result.status === 401) opts.onUnauthorized?.();
9545
- const payload = result.ok ? result.payload : withRecoveryHint(result);
10557
+ const payload = result.ok ? result.payload : withRecoveryHint(result, callShapeOnlyTools.has(request.params.name));
9546
10558
  return {
9547
10559
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
9548
10560
  isError: !result.ok
@@ -9560,9 +10572,29 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
9560
10572
  }
9561
10573
  return server;
9562
10574
  }
9563
- function errorHint(status) {
9564
- if (status === 400 || status === 422)
9565
- return "Input shape mismatch: check this tool's schema for required fields. For job payloads, the worker's release config defines the expected input object \u2014 fix the payload and retry; don't abandon the task.";
10575
+ var SHAPE_HINT = "Input shape mismatch: check this tool's schema for required fields. For job payloads, the worker's release config defines the expected input object \u2014 fix the payload and retry; don't abandon the task.";
10576
+ var DETAIL_HINTS = [
10577
+ [
10578
+ /no longer any instances available|not enough free GPUs/i,
10579
+ "Capacity, not input: the request is valid but no machine can take it right now. Don't change the payload shape. Retry with another GPU type, data center, cloud, CUDA version or count, or try again later. For a stopped Pod, its host's GPUs are taken; that Pod can only start once they free up."
10580
+ ],
10581
+ [
10582
+ /at least \$\d|insufficient (?:funds|balance)|\bbalance\b/i,
10583
+ "Account balance, not input: this operation needs funds on the account. Ask the user to add credit in the Runpod console; list-billing shows recent spend."
10584
+ ],
10585
+ [
10586
+ /mutually exclusive|cannot be combined with/i,
10587
+ "Two arguments conflict: send only one of them and leave the other out entirely. Do not change anything else."
10588
+ ]
10589
+ ];
10590
+ function errorHint(status, detail) {
10591
+ if (status === 400 && detail) {
10592
+ for (const [pattern, hint] of DETAIL_HINTS)
10593
+ if (pattern.test(detail)) return hint;
10594
+ if (/^unauthorized$/i.test(detail.trim()))
10595
+ return "Upstream refused this write. With a key that can read, this usually means the account has no balance: ask the user to check their balance in the Runpod console before retrying.";
10596
+ }
10597
+ if (status === 400 || status === 422) return SHAPE_HINT;
9566
10598
  if (status === 401) return "The Runpod API key is missing or invalid.";
9567
10599
  if (status === 402)
9568
10600
  return "Check your account balance in the Runpod console before retrying billable operations; list-billing shows the recent spend behind it.";
@@ -9576,18 +10608,30 @@ function errorHint(status) {
9576
10608
  return "Upstream Runpod error: retry once; if it persists, report it as an API-side failure.";
9577
10609
  return void 0;
9578
10610
  }
9579
- function withRecoveryHint(result) {
10611
+ var CALL_SHAPE_ONLY_HINT = `This tool lists its call shape only; ${DESCRIBE_TOOL_NAME} serves the full argument schema.`;
10612
+ function withRecoveryHint(result, listsCallShapeOnly = false) {
9580
10613
  const payload = asRecord(result.payload) ?? { error: result.payload };
10614
+ const hint = payload.hint ?? asRecord(payload.detail)?.hint ?? errorHint(result.status, detailText(payload));
10615
+ const shapeRejected = result.status === 400 || result.status === 422;
9581
10616
  return {
9582
10617
  ...payload,
9583
- hint: payload.hint ?? asRecord(payload.detail)?.hint ?? errorHint(result.status)
10618
+ hint: listsCallShapeOnly && shapeRejected ? [hint, CALL_SHAPE_ONLY_HINT].filter(Boolean).join(" ") : hint
9584
10619
  };
9585
10620
  }
10621
+ function detailText(payload) {
10622
+ if (Array.isArray(payload.errors) && payload.errors.length) return void 0;
10623
+ const detail = payload.detail ?? payload.error;
10624
+ if (typeof detail === "string") return detail;
10625
+ const nested = asRecord(detail);
10626
+ return typeof nested?.detail === "string" ? nested.detail : void 0;
10627
+ }
9586
10628
  function asRecord(value) {
9587
10629
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
9588
10630
  }
9589
10631
 
9590
10632
  // src/stdio.ts
10633
+ init_hosts();
10634
+ var SEARCH_URL = `${hostedMcpBase(process.env)}/api/search`;
9591
10635
  var WIZARD_SUBCOMMANDS = ["add", "remove"];
9592
10636
  var subcommand = process.argv[2];
9593
10637
  var isWizardMode = WIZARD_SUBCOMMANDS.includes(subcommand);
@@ -9597,12 +10641,28 @@ async function main(apiKey) {
9597
10641
  apiKey,
9598
10642
  tracking: { transport: "stdio", serverVersion: SERVER_VERSION }
9599
10643
  }),
9600
- SERVER_VERSION
10644
+ SERVER_VERSION,
10645
+ { search: hostedSearch(SEARCH_URL) }
9601
10646
  );
9602
10647
  await server.connect(new import_stdio.StdioServerTransport());
9603
10648
  }
9604
- if (isWizardMode) {
9605
- Promise.resolve().then(() => (init_wizard(), wizard_exports)).then(({ runWizard: runWizard2 }) => runWizard2(subcommand)).catch((error) => {
10649
+ if (subcommand === "--version" || subcommand === "-v") {
10650
+ console.log(SERVER_VERSION);
10651
+ } else if (subcommand === "--help" || subcommand === "-h") {
10652
+ console.log(
10653
+ [
10654
+ `@runpod/mcp-server ${SERVER_VERSION}`,
10655
+ "",
10656
+ "Usage:",
10657
+ " RUNPOD_API_KEY=<key> npx @runpod/mcp-server@latest run the MCP server over stdio",
10658
+ " npx @runpod/mcp-server@latest add [--client <ids>] configure your agents (see add --help)",
10659
+ " npx @runpod/mcp-server@latest remove --client <ids> remove Runpod from your agents"
10660
+ ].join("\n")
10661
+ );
10662
+ } else if (isWizardMode) {
10663
+ Promise.resolve().then(() => (init_wizard(), wizard_exports)).then(async ({ runWizard: runWizard2 }) => {
10664
+ process.exitCode = await runWizard2(subcommand);
10665
+ }).catch((error) => {
9606
10666
  console.error(error instanceof Error ? error.message : String(error));
9607
10667
  process.exit(1);
9608
10668
  });