@runpod/mcp-server 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/http.mjs CHANGED
@@ -1,5 +1,10 @@
1
1
  // src/http.ts
2
2
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3
+ import {
4
+ CallToolRequestSchema as CallToolRequestSchema2,
5
+ InitializeRequestSchema,
6
+ JSONRPCMessageSchema
7
+ } from "@modelcontextprotocol/sdk/types.js";
3
8
 
4
9
  // src/specgen/server.ts
5
10
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -87,11 +92,11 @@ function readParams(params) {
87
92
  }
88
93
  return { values, unreadable };
89
94
  }
90
- function parseExhaustedWindows(header) {
91
- if (!header) return { windows: [], dropped: false };
95
+ function parseExhaustedWindows(header2) {
96
+ if (!header2) return { windows: [], dropped: false };
92
97
  const windows = [];
93
98
  let dropped = false;
94
- for (const item of splitOutsideStrings(header, ",")) {
99
+ for (const item of splitOutsideStrings(header2, ",")) {
95
100
  if (item.trim() === "") continue;
96
101
  const [rawName, ...params] = splitOutsideStrings(item, ";");
97
102
  if (!params.every(isWellFormedParam)) {
@@ -209,10 +214,13 @@ function validateArguments(inputSchema, args) {
209
214
  async function dispatchGeneratedTool(client, tool, args) {
210
215
  const rejected = validateArguments(tool.inputSchema, args);
211
216
  if (rejected) return rejected;
217
+ const required = new Set(tool.inputSchema.required ?? []);
212
218
  const pathParams = {};
213
219
  const query = {};
214
220
  for (const param of tool.params) {
215
221
  if (args[param.name] === void 0) continue;
222
+ if (param.location === "query" && !required.has(param.name) && isEmptyValue(args[param.name]))
223
+ continue;
216
224
  if (param.location === "path") {
217
225
  pathParams[param.name] = args[param.name];
218
226
  continue;
@@ -234,6 +242,11 @@ async function dispatchGeneratedTool(client, tool, args) {
234
242
  };
235
243
  }
236
244
  }
245
+ if (tool.hasBody && requestBody !== void 0) {
246
+ const properties = tool.inputSchema.properties ?? {};
247
+ const defs = tool.inputSchema.$defs ?? {};
248
+ requestBody = coerceScalars(requestBody, properties.body, defs);
249
+ }
237
250
  const verbs = client;
238
251
  const { data, error, response } = await verbs[tool.method](tool.path, {
239
252
  params: { path: pathParams, query },
@@ -246,6 +259,79 @@ async function dispatchGeneratedTool(client, tool, args) {
246
259
  payload: data ?? { status: response.status }
247
260
  };
248
261
  }
262
+ function isEmptyValue(value) {
263
+ return value === null || value === "" || Array.isArray(value) && !value.length;
264
+ }
265
+ function asSchema(value) {
266
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
267
+ }
268
+ function resolveSchema(schema, defs, depth = 0) {
269
+ const node = asSchema(schema);
270
+ if (!node || depth > 16) return node;
271
+ const ref = node.$ref;
272
+ if (typeof ref === "string" && ref.startsWith("#/$defs/"))
273
+ return resolveSchema(defs[ref.slice("#/$defs/".length)], defs, depth + 1);
274
+ return node;
275
+ }
276
+ function branches(schema, defs, depth = 0) {
277
+ const node = resolveSchema(schema, defs);
278
+ if (!node || depth > 16) return [];
279
+ const out = [node];
280
+ for (const key of ["allOf", "anyOf", "oneOf"]) {
281
+ const list = node[key];
282
+ if (Array.isArray(list))
283
+ for (const sub of list) out.push(...branches(sub, defs, depth + 1));
284
+ }
285
+ return out;
286
+ }
287
+ function types(schemas) {
288
+ const out = /* @__PURE__ */ new Set();
289
+ for (const s of schemas) {
290
+ if (typeof s.type === "string") out.add(s.type);
291
+ else if (Array.isArray(s.type)) {
292
+ for (const t of s.type) if (typeof t === "string") out.add(t);
293
+ }
294
+ }
295
+ return out;
296
+ }
297
+ var NUMERIC = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
298
+ function coerceScalars(value, schema, defs, depth = 0) {
299
+ if (depth > 32) return value;
300
+ const applicable = branches(schema, defs);
301
+ if (!applicable.length) return value;
302
+ const allowed = types(applicable);
303
+ if (typeof value === "string") {
304
+ if (allowed.has("string")) return value;
305
+ if ((allowed.has("integer") || allowed.has("number")) && NUMERIC.test(value)) {
306
+ const n = Number(value);
307
+ if (allowed.has("number") || Number.isInteger(n)) return n;
308
+ }
309
+ if (allowed.has("boolean") && (value === "true" || value === "false"))
310
+ return value === "true";
311
+ return value;
312
+ }
313
+ if (Array.isArray(value)) {
314
+ const items = applicable.map((s) => s.items).filter(Boolean);
315
+ if (!items.length) return value;
316
+ return value.map(
317
+ (item) => coerceScalars(item, { anyOf: items }, defs, depth + 1)
318
+ );
319
+ }
320
+ const object = asSchema(value);
321
+ if (!object) return value;
322
+ const out = {};
323
+ for (const [key, child] of Object.entries(object)) {
324
+ const childSchemas = [];
325
+ for (const s of applicable) {
326
+ const props = asSchema(s.properties);
327
+ if (props && key in props) childSchemas.push(props[key]);
328
+ else if (asSchema(s.additionalProperties))
329
+ childSchemas.push(s.additionalProperties);
330
+ }
331
+ out[key] = childSchemas.length ? coerceScalars(child, { anyOf: childSchemas }, defs, depth + 1) : child;
332
+ }
333
+ return out;
334
+ }
249
335
 
250
336
  // src/specgen/generated/tools.gen.ts
251
337
  var generatedTools = [
@@ -263,6 +349,7 @@ var generatedTools = [
263
349
  idempotentHint: false,
264
350
  openWorldHint: true
265
351
  },
352
+ schemaOnDemand: true,
266
353
  inputSchema: {
267
354
  type: "object",
268
355
  properties: {
@@ -274,11 +361,10 @@ var generatedTools = [
274
361
  $defs: {
275
362
  BaseContainerConfig: {
276
363
  type: "object",
277
- 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",
278
364
  properties: {
279
365
  args: {
280
366
  type: "string",
281
- 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',
367
+ 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.',
282
368
  examples: [
283
369
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
284
370
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -290,7 +376,7 @@ var generatedTools = [
290
376
  items: {
291
377
  type: "string"
292
378
  },
293
- 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",
379
+ 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.",
294
380
  examples: [
295
381
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
296
382
  ]
@@ -300,7 +386,7 @@ var generatedTools = [
300
386
  items: {
301
387
  type: "string"
302
388
  },
303
- 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",
389
+ 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.",
304
390
  examples: [["/bin/bash", "-c"]]
305
391
  },
306
392
  disk: {
@@ -336,6 +422,21 @@ var generatedTools = [
336
422
  }
337
423
  }
338
424
  },
425
+ BaseMounts: {
426
+ type: "object",
427
+ properties: {
428
+ persistent: {
429
+ $ref: "#/$defs/PersistentMount"
430
+ },
431
+ network: {
432
+ type: "array",
433
+ maxItems: 1,
434
+ items: {
435
+ $ref: "#/$defs/NetworkMount"
436
+ }
437
+ }
438
+ }
439
+ },
339
440
  ClusterCompute: {
340
441
  type: "object",
341
442
  additionalProperties: false,
@@ -363,6 +464,15 @@ var generatedTools = [
363
464
  }
364
465
  }
365
466
  },
467
+ ClusterMounts: {
468
+ description: "Storage mounts for a cluster create: `persistent` or `network`, not both. Clusters cannot mount global volumes, so `global` is rejected.",
469
+ allOf: [
470
+ {
471
+ $ref: "#/$defs/BaseMounts"
472
+ }
473
+ ],
474
+ unevaluatedProperties: false
475
+ },
366
476
  ClusterType: {
367
477
  type: "string",
368
478
  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.",
@@ -377,7 +487,7 @@ var generatedTools = [
377
487
  {
378
488
  type: "object",
379
489
  required: ["name", "type", "compute"],
380
- 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",
490
+ description: "Request body for creating a cluster. `compute` defines the homogeneous pod shape; the container configuration (image, env, ports, \u2026) applies to every pod.",
381
491
  properties: {
382
492
  compute: {
383
493
  $ref: "#/$defs/ClusterCompute"
@@ -393,7 +503,7 @@ var generatedTools = [
393
503
  templateId: {
394
504
  type: "string",
395
505
  minLength: 1,
396
- 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",
506
+ 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.",
397
507
  examples: ["30zmvf89kd"]
398
508
  },
399
509
  dataCenterIds: {
@@ -401,11 +511,11 @@ var generatedTools = [
401
511
  items: {
402
512
  type: "string"
403
513
  },
404
- 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",
514
+ 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.",
405
515
  example: ["US-TX-3"]
406
516
  },
407
517
  mounts: {
408
- $ref: "#/$defs/Mounts"
518
+ $ref: "#/$defs/ClusterMounts"
409
519
  },
410
520
  startJupyter: {
411
521
  type: "boolean",
@@ -422,28 +532,11 @@ var generatedTools = [
422
532
  ],
423
533
  unevaluatedProperties: false
424
534
  },
425
- Mounts: {
426
- type: "object",
427
- additionalProperties: false,
428
- 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",
429
- properties: {
430
- persistent: {
431
- $ref: "#/$defs/PersistentMount"
432
- },
433
- network: {
434
- type: "array",
435
- maxItems: 1,
436
- items: {
437
- $ref: "#/$defs/NetworkMount"
438
- }
439
- }
440
- }
441
- },
442
535
  NetworkMount: {
443
536
  type: "object",
444
537
  required: ["volumeId", "path"],
445
538
  additionalProperties: false,
446
- 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",
539
+ 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.",
447
540
  properties: {
448
541
  volumeId: {
449
542
  type: "string",
@@ -461,7 +554,7 @@ var generatedTools = [
461
554
  type: "object",
462
555
  required: ["size", "path"],
463
556
  additionalProperties: false,
464
- 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",
557
+ 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.",
465
558
  properties: {
466
559
  size: {
467
560
  type: "integer",
@@ -538,6 +631,7 @@ var generatedTools = [
538
631
  idempotentHint: false,
539
632
  openWorldHint: true
540
633
  },
634
+ schemaOnDemand: true,
541
635
  inputSchema: {
542
636
  type: "object",
543
637
  properties: {
@@ -549,11 +643,10 @@ var generatedTools = [
549
643
  $defs: {
550
644
  BaseContainerConfig: {
551
645
  type: "object",
552
- 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",
553
646
  properties: {
554
647
  args: {
555
648
  type: "string",
556
- 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',
649
+ 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.',
557
650
  examples: [
558
651
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
559
652
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -565,7 +658,7 @@ var generatedTools = [
565
658
  items: {
566
659
  type: "string"
567
660
  },
568
- 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",
661
+ 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.",
569
662
  examples: [
570
663
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
571
664
  ]
@@ -575,7 +668,7 @@ var generatedTools = [
575
668
  items: {
576
669
  type: "string"
577
670
  },
578
- 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",
671
+ 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.",
579
672
  examples: [["/bin/bash", "-c"]]
580
673
  },
581
674
  disk: {
@@ -635,7 +728,7 @@ var generatedTools = [
635
728
  pools: {
636
729
  type: "array",
637
730
  minItems: 1,
638
- 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',
731
+ 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.",
639
732
  items: {
640
733
  type: "string"
641
734
  },
@@ -644,7 +737,7 @@ var generatedTools = [
644
737
  excludedTypes: {
645
738
  type: "array",
646
739
  uniqueItems: true,
647
- 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',
740
+ 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.",
648
741
  items: {
649
742
  type: "string",
650
743
  pattern: "^\\s*[^-\\s]"
@@ -661,7 +754,6 @@ var generatedTools = [
661
754
  }
662
755
  },
663
756
  ContainerConfig: {
664
- description: "Reusable container configuration shared across templates, pods, and serverless endpoints. Adding a field here automatically propagates to all three resources.\n",
665
757
  allOf: [
666
758
  {
667
759
  $ref: "#/$defs/BaseContainerConfig"
@@ -687,7 +779,7 @@ var generatedTools = [
687
779
  unevaluatedProperties: false
688
780
  },
689
781
  CreateEndpointGpuConfig: {
690
- 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",
782
+ 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.",
691
783
  allOf: [
692
784
  {
693
785
  $ref: "#/$defs/BaseEndpointGpuConfig"
@@ -702,13 +794,13 @@ var generatedTools = [
702
794
  type: "string",
703
795
  pattern: "^\\d+\\.\\d+$"
704
796
  },
705
- 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",
797
+ 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`).",
706
798
  examples: [["12.8", "12.6"]]
707
799
  },
708
800
  minCudaVersion: {
709
801
  type: "string",
710
802
  pattern: "^\\d+\\.\\d+$",
711
- 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",
803
+ 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.",
712
804
  examples: ["12.1"]
713
805
  }
714
806
  }
@@ -752,13 +844,13 @@ var generatedTools = [
752
844
  $ref: "#/$defs/EndpointType"
753
845
  }
754
846
  ],
755
- description: "Request-routing model. Required \u2014 it determines the valid scaler\nand request URLs, so it must be chosen explicitly on every create.\n"
847
+ description: "Request-routing model. Required \u2014 it determines the valid scaler and request URLs, so it must be chosen explicitly on every create."
756
848
  },
757
849
  cpu: {
758
850
  type: "array",
759
851
  minItems: 1,
760
852
  uniqueItems: true,
761
- 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",
853
+ 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.",
762
854
  items: {
763
855
  $ref: "#/$defs/CreateCpuConfig"
764
856
  }
@@ -782,12 +874,17 @@ var generatedTools = [
782
874
  type: "array",
783
875
  items: {
784
876
  type: "string"
785
- }
877
+ },
878
+ description: "Network volume IDs.",
879
+ deprecated: true
880
+ },
881
+ mounts: {
882
+ $ref: "#/$defs/EndpointMountsInput"
786
883
  },
787
884
  templateId: {
788
885
  type: "string",
789
886
  minLength: 1,
790
- 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",
887
+ 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.",
791
888
  examples: ["30zmvf89kd"]
792
889
  },
793
890
  timeout: {
@@ -835,8 +932,42 @@ var generatedTools = [
835
932
  }
836
933
  }
837
934
  },
935
+ EndpointMount: {
936
+ type: "object",
937
+ required: ["volumeId"],
938
+ additionalProperties: false,
939
+ description: "A volume mounted on every worker, always at `/runpod-volume`. Custom paths are not supported yet.",
940
+ properties: {
941
+ volumeId: {
942
+ type: "string",
943
+ description: "ID of a NetworkVolume (under `network`) or GlobalVolume (under `global`).",
944
+ examples: ["vol_abc"]
945
+ }
946
+ }
947
+ },
948
+ EndpointMountsInput: {
949
+ type: "object",
950
+ additionalProperties: false,
951
+ 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.",
952
+ properties: {
953
+ network: {
954
+ type: "array",
955
+ description: "Network volumes, at most one per data center.",
956
+ items: {
957
+ $ref: "#/$defs/EndpointMount"
958
+ }
959
+ },
960
+ global: {
961
+ type: "array",
962
+ maxItems: 1,
963
+ items: {
964
+ $ref: "#/$defs/EndpointMount"
965
+ }
966
+ }
967
+ }
968
+ },
838
969
  EndpointScaling: {
839
- 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",
970
+ 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.",
840
971
  oneOf: [
841
972
  {
842
973
  $ref: "#/$defs/QueueDelayScaling"
@@ -855,7 +986,7 @@ var generatedTools = [
855
986
  },
856
987
  EndpointType: {
857
988
  type: "string",
858
- 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",
989
+ 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.",
859
990
  "x-enum-varnames": ["EndpointTypeQueue", "EndpointTypeLoadBalancer"],
860
991
  enum: ["QUEUE", "LOAD_BALANCER"]
861
992
  },
@@ -879,14 +1010,14 @@ var generatedTools = [
879
1010
  type: "integer",
880
1011
  minimum: 1,
881
1012
  maximum: 3600,
882
- 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",
1013
+ 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.",
883
1014
  examples: [5]
884
1015
  }
885
1016
  }
886
1017
  },
887
1018
  FlashBoot: {
888
1019
  type: "string",
889
- description: "FlashBoot cold-start acceleration mode.\n- `OFF` \u2014 disabled\n- `FLASHBOOT` \u2014 enabled\n- `PRIORITY_FLASHBOOT` \u2014 enabled with priority capacity\n",
1020
+ description: "FlashBoot cold-start acceleration mode. `OFF` \u2014 disabled. `FLASHBOOT` \u2014 enabled. `PRIORITY_FLASHBOOT` \u2014 enabled with priority capacity",
890
1021
  enum: ["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]
891
1022
  },
892
1023
  QueueDelayScaling: {
@@ -912,7 +1043,7 @@ var generatedTools = [
912
1043
  RequestCountScaling: {
913
1044
  type: "object",
914
1045
  additionalProperties: false,
915
- description: "Scale on concurrent in-flight requests per worker. Required for\nload-balancing endpoints; also selectable for queue-based.\n",
1046
+ description: "Scale on concurrent in-flight requests per worker. Required for load-balancing endpoints; also selectable for queue-based.",
916
1047
  required: ["type", "requestCount"],
917
1048
  properties: {
918
1049
  type: {
@@ -931,6 +1062,47 @@ var generatedTools = [
931
1062
  }
932
1063
  }
933
1064
  },
1065
+ {
1066
+ name: "create-global-volume",
1067
+ operationId: "createGlobalVolume",
1068
+ 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`.",
1069
+ method: "POST",
1070
+ path: "/v2/global-volumes",
1071
+ params: [],
1072
+ hasBody: true,
1073
+ annotations: {
1074
+ readOnlyHint: false,
1075
+ destructiveHint: false,
1076
+ idempotentHint: false,
1077
+ openWorldHint: true
1078
+ },
1079
+ inputSchema: {
1080
+ type: "object",
1081
+ properties: {
1082
+ body: {
1083
+ $ref: "#/$defs/CreateGlobalVolumeRequest"
1084
+ }
1085
+ },
1086
+ required: ["body"],
1087
+ $defs: {
1088
+ CreateGlobalVolumeRequest: {
1089
+ type: "object",
1090
+ additionalProperties: false,
1091
+ required: ["name"],
1092
+ properties: {
1093
+ name: {
1094
+ type: "string",
1095
+ minLength: 1,
1096
+ maxLength: 191,
1097
+ pattern: "[^\\p{Z}\\t-\\r\x85]",
1098
+ description: "Human-readable name, 1\u2013191 characters with at least one non-whitespace character. Leading and trailing whitespace is trimmed.",
1099
+ examples: ["training-data"]
1100
+ }
1101
+ }
1102
+ }
1103
+ }
1104
+ }
1105
+ },
934
1106
  {
935
1107
  name: "create-network-volume",
936
1108
  operationId: "createNetworkVolume",
@@ -984,7 +1156,7 @@ var generatedTools = [
984
1156
  $ref: "#/$defs/VolumeType"
985
1157
  }
986
1158
  ],
987
- 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"
1159
+ description: "Storage tier for the volume. Optional. When omitted, the volume is provisioned using the requested data center's default (primary) storage tier."
988
1160
  }
989
1161
  }
990
1162
  },
@@ -1010,6 +1182,7 @@ var generatedTools = [
1010
1182
  idempotentHint: false,
1011
1183
  openWorldHint: true
1012
1184
  },
1185
+ schemaOnDemand: true,
1013
1186
  inputSchema: {
1014
1187
  type: "object",
1015
1188
  properties: {
@@ -1021,11 +1194,10 @@ var generatedTools = [
1021
1194
  $defs: {
1022
1195
  BaseContainerConfig: {
1023
1196
  type: "object",
1024
- 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",
1025
1197
  properties: {
1026
1198
  args: {
1027
1199
  type: "string",
1028
- 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',
1200
+ 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.',
1029
1201
  examples: [
1030
1202
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
1031
1203
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -1037,7 +1209,7 @@ var generatedTools = [
1037
1209
  items: {
1038
1210
  type: "string"
1039
1211
  },
1040
- 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",
1212
+ 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.",
1041
1213
  examples: [
1042
1214
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
1043
1215
  ]
@@ -1047,7 +1219,7 @@ var generatedTools = [
1047
1219
  items: {
1048
1220
  type: "string"
1049
1221
  },
1050
- 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",
1222
+ 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.",
1051
1223
  examples: [["/bin/bash", "-c"]]
1052
1224
  },
1053
1225
  disk: {
@@ -1119,13 +1291,27 @@ var generatedTools = [
1119
1291
  }
1120
1292
  }
1121
1293
  },
1294
+ BaseMounts: {
1295
+ type: "object",
1296
+ properties: {
1297
+ persistent: {
1298
+ $ref: "#/$defs/PersistentMount"
1299
+ },
1300
+ network: {
1301
+ type: "array",
1302
+ maxItems: 1,
1303
+ items: {
1304
+ $ref: "#/$defs/NetworkMount"
1305
+ }
1306
+ }
1307
+ }
1308
+ },
1122
1309
  Cloud: {
1123
1310
  type: "string",
1124
- description: "Cloud tier.\n- `SECURE` \u2014 Runpod-owned datacenter hardware\n- `COMMUNITY` \u2014 community-hosted hardware\n",
1311
+ description: "Cloud tier. `SECURE` \u2014 Runpod-owned datacenter hardware. `COMMUNITY` \u2014 community-hosted hardware",
1125
1312
  enum: ["SECURE", "COMMUNITY"]
1126
1313
  },
1127
1314
  ContainerConfig: {
1128
- description: "Reusable container configuration shared across templates, pods, and serverless endpoints. Adding a field here automatically propagates to all three resources.\n",
1129
1315
  allOf: [
1130
1316
  {
1131
1317
  $ref: "#/$defs/BaseContainerConfig"
@@ -1151,7 +1337,7 @@ var generatedTools = [
1151
1337
  unevaluatedProperties: false
1152
1338
  },
1153
1339
  CreateGpuConfig: {
1154
- 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",
1340
+ 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.",
1155
1341
  allOf: [
1156
1342
  {
1157
1343
  $ref: "#/$defs/BaseGpuConfig"
@@ -1165,13 +1351,13 @@ var generatedTools = [
1165
1351
  type: "string",
1166
1352
  pattern: "^\\d+\\.\\d+$"
1167
1353
  },
1168
- 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",
1354
+ description: "Acceptable CUDA versions for the host machine, as `major.minor`. Omit to accept any version.",
1169
1355
  examples: [["12.8", "12.6"]]
1170
1356
  },
1171
1357
  minCudaVersion: {
1172
1358
  type: "string",
1173
1359
  pattern: "^\\d+\\.\\d+$",
1174
- 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",
1360
+ 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.",
1175
1361
  examples: ["12.1"]
1176
1362
  },
1177
1363
  minRamPerGpu: {
@@ -1199,7 +1385,7 @@ var generatedTools = [
1199
1385
  {
1200
1386
  type: "object",
1201
1387
  required: ["name"],
1202
- 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",
1388
+ description: "Request body for creating a pod. Exactly one of `gpu` or `cpu` must be set \u2014 enforced at the handler layer.",
1203
1389
  properties: {
1204
1390
  name: {
1205
1391
  type: "string",
@@ -1223,13 +1409,13 @@ var generatedTools = [
1223
1409
  items: {
1224
1410
  type: "string"
1225
1411
  },
1226
- description: "Preferred data centers for placement. Omit or pass an empty\narray to let the scheduler choose.\n",
1412
+ description: "Preferred data centers for placement. Omit or pass an empty array to let the scheduler choose.",
1227
1413
  examples: [["US-TX-3"]]
1228
1414
  },
1229
1415
  globalNetworking: {
1230
1416
  type: "boolean",
1231
1417
  default: false,
1232
- 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.",
1418
+ 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).",
1233
1419
  examples: [false]
1234
1420
  },
1235
1421
  gpu: {
@@ -1241,19 +1427,19 @@ var generatedTools = [
1241
1427
  startJupyter: {
1242
1428
  type: "boolean",
1243
1429
  default: false,
1244
- 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",
1430
+ 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.",
1245
1431
  examples: [true]
1246
1432
  },
1247
1433
  startSsh: {
1248
1434
  type: "boolean",
1249
1435
  default: false,
1250
- 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",
1436
+ 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`.",
1251
1437
  examples: [true]
1252
1438
  },
1253
1439
  templateId: {
1254
1440
  type: "string",
1255
1441
  minLength: 1,
1256
- 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",
1442
+ 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.",
1257
1443
  examples: ["30zmvf89kd"]
1258
1444
  }
1259
1445
  }
@@ -1269,28 +1455,51 @@ var generatedTools = [
1269
1455
  required: ["image"]
1270
1456
  }
1271
1457
  },
1272
- Mounts: {
1458
+ GlobalMount: {
1273
1459
  type: "object",
1460
+ required: ["volumeId"],
1274
1461
  additionalProperties: false,
1275
- 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",
1462
+ 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.",
1276
1463
  properties: {
1277
- persistent: {
1278
- $ref: "#/$defs/PersistentMount"
1464
+ volumeId: {
1465
+ type: "string",
1466
+ description: "ID of an existing GlobalVolume.",
1467
+ examples: ["gsb_abc"]
1279
1468
  },
1280
- network: {
1281
- type: "array",
1282
- maxItems: 1,
1283
- items: {
1284
- $ref: "#/$defs/NetworkMount"
1285
- }
1469
+ path: {
1470
+ type: "string",
1471
+ 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.",
1472
+ examples: ["/data"]
1286
1473
  }
1287
1474
  }
1288
1475
  },
1476
+ Mounts: {
1477
+ 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.",
1478
+ allOf: [
1479
+ {
1480
+ $ref: "#/$defs/BaseMounts"
1481
+ },
1482
+ {
1483
+ type: "object",
1484
+ properties: {
1485
+ global: {
1486
+ type: "array",
1487
+ maxItems: 1,
1488
+ 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.",
1489
+ items: {
1490
+ $ref: "#/$defs/GlobalMount"
1491
+ }
1492
+ }
1493
+ }
1494
+ }
1495
+ ],
1496
+ unevaluatedProperties: false
1497
+ },
1289
1498
  NetworkMount: {
1290
1499
  type: "object",
1291
1500
  required: ["volumeId", "path"],
1292
1501
  additionalProperties: false,
1293
- 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",
1502
+ 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.",
1294
1503
  properties: {
1295
1504
  volumeId: {
1296
1505
  type: "string",
@@ -1308,7 +1517,7 @@ var generatedTools = [
1308
1517
  type: "object",
1309
1518
  required: ["size", "path"],
1310
1519
  additionalProperties: false,
1311
- 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",
1520
+ 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.",
1312
1521
  properties: {
1313
1522
  size: {
1314
1523
  type: "integer",
@@ -1391,6 +1600,7 @@ var generatedTools = [
1391
1600
  idempotentHint: false,
1392
1601
  openWorldHint: true
1393
1602
  },
1603
+ schemaOnDemand: true,
1394
1604
  inputSchema: {
1395
1605
  type: "object",
1396
1606
  properties: {
@@ -1410,7 +1620,7 @@ var generatedTools = [
1410
1620
  minLength: 1,
1411
1621
  maxLength: 191,
1412
1622
  pattern: "^[a-zA-Z_][a-zA-Z0-9_.\\-/]*$",
1413
- 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).",
1623
+ 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 `_.-/`.",
1414
1624
  examples: ["hf-token"]
1415
1625
  },
1416
1626
  value: {
@@ -1444,6 +1654,7 @@ var generatedTools = [
1444
1654
  idempotentHint: false,
1445
1655
  openWorldHint: true
1446
1656
  },
1657
+ schemaOnDemand: true,
1447
1658
  inputSchema: {
1448
1659
  type: "object",
1449
1660
  properties: {
@@ -1455,11 +1666,10 @@ var generatedTools = [
1455
1666
  $defs: {
1456
1667
  BaseContainerConfig: {
1457
1668
  type: "object",
1458
- 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",
1459
1669
  properties: {
1460
1670
  args: {
1461
1671
  type: "string",
1462
- 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',
1672
+ 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.',
1463
1673
  examples: [
1464
1674
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
1465
1675
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -1471,7 +1681,7 @@ var generatedTools = [
1471
1681
  items: {
1472
1682
  type: "string"
1473
1683
  },
1474
- 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",
1684
+ 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.",
1475
1685
  examples: [
1476
1686
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
1477
1687
  ]
@@ -1481,7 +1691,7 @@ var generatedTools = [
1481
1691
  items: {
1482
1692
  type: "string"
1483
1693
  },
1484
- 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",
1694
+ 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.",
1485
1695
  examples: [["/bin/bash", "-c"]]
1486
1696
  },
1487
1697
  disk: {
@@ -1518,7 +1728,6 @@ var generatedTools = [
1518
1728
  }
1519
1729
  },
1520
1730
  ContainerConfig: {
1521
- description: "Reusable container configuration shared across templates, pods, and serverless endpoints. Adding a field here automatically propagates to all three resources.\n",
1522
1731
  allOf: [
1523
1732
  {
1524
1733
  $ref: "#/$defs/BaseContainerConfig"
@@ -1555,7 +1764,7 @@ var generatedTools = [
1555
1764
  type: "string",
1556
1765
  pattern: "^\\d+\\.\\d+$"
1557
1766
  },
1558
- 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",
1767
+ 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.",
1559
1768
  examples: [["12.8", "12.6"]]
1560
1769
  },
1561
1770
  category: {
@@ -1581,13 +1790,13 @@ var generatedTools = [
1581
1790
  startJupyter: {
1582
1791
  type: "boolean",
1583
1792
  default: true,
1584
- 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",
1793
+ description: "Start JupyterLab in containers created from this template: injects a generated `JUPYTER_PASSWORD` environment variable, unless `env` already sets one.",
1585
1794
  examples: [false]
1586
1795
  },
1587
1796
  startSsh: {
1588
1797
  type: "boolean",
1589
1798
  default: true,
1590
- 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",
1799
+ 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.",
1591
1800
  examples: [true]
1592
1801
  }
1593
1802
  }
@@ -1599,7 +1808,7 @@ var generatedTools = [
1599
1808
  type: "object",
1600
1809
  required: ["size", "path"],
1601
1810
  additionalProperties: false,
1602
- 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",
1811
+ 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.",
1603
1812
  properties: {
1604
1813
  size: {
1605
1814
  type: "integer",
@@ -1616,13 +1825,13 @@ var generatedTools = [
1616
1825
  },
1617
1826
  TemplateCategory: {
1618
1827
  type: "string",
1619
- 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",
1828
+ 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",
1620
1829
  enum: ["CPU", "NVIDIA", "AMD"]
1621
1830
  },
1622
1831
  TemplateMounts: {
1623
1832
  type: "object",
1624
1833
  additionalProperties: false,
1625
- 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",
1834
+ 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.",
1626
1835
  properties: {
1627
1836
  persistent: {
1628
1837
  $ref: "#/$defs/PersistentMount"
@@ -1692,6 +1901,36 @@ var generatedTools = [
1692
1901
  required: ["id"]
1693
1902
  }
1694
1903
  },
1904
+ {
1905
+ name: "delete-global-volume",
1906
+ operationId: "deleteGlobalVolume",
1907
+ 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.",
1908
+ method: "DELETE",
1909
+ path: "/v2/global-volumes/{id}",
1910
+ params: [
1911
+ {
1912
+ name: "id",
1913
+ location: "path"
1914
+ }
1915
+ ],
1916
+ hasBody: false,
1917
+ annotations: {
1918
+ readOnlyHint: false,
1919
+ destructiveHint: true,
1920
+ idempotentHint: true,
1921
+ openWorldHint: true
1922
+ },
1923
+ inputSchema: {
1924
+ type: "object",
1925
+ properties: {
1926
+ id: {
1927
+ type: "string",
1928
+ description: "Global volume identifier"
1929
+ }
1930
+ },
1931
+ required: ["id"]
1932
+ }
1933
+ },
1695
1934
  {
1696
1935
  name: "delete-network-volume",
1697
1936
  operationId: "deleteNetworkVolume",
@@ -2057,12 +2296,42 @@ var generatedTools = [
2057
2296
  },
2058
2297
  buildId: {
2059
2298
  type: "string",
2060
- description: "GitHub build identifier (from GET /v2/serverless/{id}/builds or a release's `buildId`)"
2299
+ description: "GitHub build identifier (from `list-endpoint-builds` or a release's `buildId`)"
2061
2300
  }
2062
2301
  },
2063
2302
  required: ["id", "buildId"]
2064
2303
  }
2065
2304
  },
2305
+ {
2306
+ name: "get-global-volume",
2307
+ operationId: "getGlobalVolume",
2308
+ description: "Get a global volume. Returns a single global volume by ID.",
2309
+ method: "GET",
2310
+ path: "/v2/global-volumes/{id}",
2311
+ params: [
2312
+ {
2313
+ name: "id",
2314
+ location: "path"
2315
+ }
2316
+ ],
2317
+ hasBody: false,
2318
+ annotations: {
2319
+ readOnlyHint: true,
2320
+ destructiveHint: false,
2321
+ idempotentHint: true,
2322
+ openWorldHint: true
2323
+ },
2324
+ inputSchema: {
2325
+ type: "object",
2326
+ properties: {
2327
+ id: {
2328
+ type: "string",
2329
+ description: "Global volume identifier"
2330
+ }
2331
+ },
2332
+ required: ["id"]
2333
+ }
2334
+ },
2066
2335
  {
2067
2336
  name: "get-gpu-type",
2068
2337
  operationId: "getGpuType",
@@ -2133,7 +2402,7 @@ var generatedTools = [
2133
2402
  items: {
2134
2403
  $ref: "#/$defs/Product"
2135
2404
  },
2136
- 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."
2405
+ description: "Comma-separated availability product contexts. Supported values: POD, CLUSTER, SERVERLESS. Required with include=AVAILABILITY, and valid only with it (400 either way)."
2137
2406
  },
2138
2407
  count: {
2139
2408
  type: "integer",
@@ -2151,7 +2420,7 @@ var generatedTools = [
2151
2420
  type: "string",
2152
2421
  pattern: "^[A-Z]{2}$"
2153
2422
  },
2154
- 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."
2423
+ 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."
2155
2424
  },
2156
2425
  cudaVersions: {
2157
2426
  type: "array",
@@ -2159,12 +2428,12 @@ var generatedTools = [
2159
2428
  type: "string",
2160
2429
  pattern: "^\\d+\\.\\d+$"
2161
2430
  },
2162
- 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."
2431
+ 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."
2163
2432
  },
2164
2433
  minCudaVersion: {
2165
2434
  type: "string",
2166
2435
  pattern: "^\\d+(\\.\\d+)?$",
2167
- 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."
2436
+ description: "Lowest acceptable CUDA version to scope availability and lowest-price calculations to, compared numerically."
2168
2437
  }
2169
2438
  },
2170
2439
  required: ["id"],
@@ -2217,6 +2486,25 @@ var generatedTools = [
2217
2486
  required: ["id"]
2218
2487
  }
2219
2488
  },
2489
+ {
2490
+ name: "get-organization",
2491
+ operationId: "getOrganization",
2492
+ 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.",
2493
+ method: "GET",
2494
+ path: "/v2/organization",
2495
+ params: [],
2496
+ hasBody: false,
2497
+ annotations: {
2498
+ readOnlyHint: true,
2499
+ destructiveHint: false,
2500
+ idempotentHint: true,
2501
+ openWorldHint: true
2502
+ },
2503
+ inputSchema: {
2504
+ type: "object",
2505
+ properties: {}
2506
+ }
2507
+ },
2220
2508
  {
2221
2509
  name: "get-pod",
2222
2510
  operationId: "getPod",
@@ -2391,12 +2679,12 @@ var generatedTools = [
2391
2679
  startTime: {
2392
2680
  type: "string",
2393
2681
  format: "date-time",
2394
- 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"
2682
+ description: "Start of the billing period (RFC 3339). Defaults to 30 days ago."
2395
2683
  },
2396
2684
  endTime: {
2397
2685
  type: "string",
2398
2686
  format: "date-time",
2399
- 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"
2687
+ 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."
2400
2688
  },
2401
2689
  bucketSize: {
2402
2690
  $ref: "#/$defs/BillingBucketSize",
@@ -2405,7 +2693,7 @@ var generatedTools = [
2405
2693
  lastN: {
2406
2694
  type: "integer",
2407
2695
  minimum: 1,
2408
- 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'
2696
+ 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".'
2409
2697
  }
2410
2698
  },
2411
2699
  $defs: {
@@ -2467,12 +2755,12 @@ var generatedTools = [
2467
2755
  startTime: {
2468
2756
  type: "string",
2469
2757
  format: "date-time",
2470
- 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"
2758
+ description: "Start of the billing period (RFC 3339). Defaults to 30 days ago."
2471
2759
  },
2472
2760
  endTime: {
2473
2761
  type: "string",
2474
2762
  format: "date-time",
2475
- 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"
2763
+ 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."
2476
2764
  },
2477
2765
  bucketSize: {
2478
2766
  $ref: "#/$defs/BillingBucketSize",
@@ -2481,7 +2769,7 @@ var generatedTools = [
2481
2769
  lastN: {
2482
2770
  type: "integer",
2483
2771
  minimum: 1,
2484
- 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'
2772
+ 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".'
2485
2773
  },
2486
2774
  clusterId: {
2487
2775
  type: "string",
@@ -2539,10 +2827,19 @@ var generatedTools = [
2539
2827
  {
2540
2828
  name: "list-clusters",
2541
2829
  operationId: "listClusters",
2542
- description: "List clusters. Returns all clusters owned by the authenticated user.",
2830
+ 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.",
2543
2831
  method: "GET",
2544
2832
  path: "/v2/clusters",
2545
- params: [],
2833
+ params: [
2834
+ {
2835
+ name: "cursor",
2836
+ location: "query"
2837
+ },
2838
+ {
2839
+ name: "limit",
2840
+ location: "query"
2841
+ }
2842
+ ],
2546
2843
  hasBody: false,
2547
2844
  annotations: {
2548
2845
  readOnlyHint: true,
@@ -2552,7 +2849,19 @@ var generatedTools = [
2552
2849
  },
2553
2850
  inputSchema: {
2554
2851
  type: "object",
2555
- properties: {}
2852
+ properties: {
2853
+ cursor: {
2854
+ type: "string",
2855
+ minLength: 1,
2856
+ 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."
2857
+ },
2858
+ limit: {
2859
+ type: "integer",
2860
+ minimum: 1,
2861
+ maximum: 1e3,
2862
+ description: "Page size, 1\u20131000. Defaults to 1000 when omitted."
2863
+ }
2864
+ }
2556
2865
  }
2557
2866
  },
2558
2867
  {
@@ -2806,12 +3115,12 @@ var generatedTools = [
2806
3115
  startTime: {
2807
3116
  type: "string",
2808
3117
  format: "date-time",
2809
- 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"
3118
+ description: "Start of the billing period (RFC 3339). Defaults to 30 days ago."
2810
3119
  },
2811
3120
  endTime: {
2812
3121
  type: "string",
2813
3122
  format: "date-time",
2814
- 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"
3123
+ 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."
2815
3124
  },
2816
3125
  bucketSize: {
2817
3126
  $ref: "#/$defs/BillingBucketSize",
@@ -2820,7 +3129,7 @@ var generatedTools = [
2820
3129
  lastN: {
2821
3130
  type: "integer",
2822
3131
  minimum: 1,
2823
- 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'
3132
+ 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".'
2824
3133
  }
2825
3134
  },
2826
3135
  $defs: {
@@ -2878,7 +3187,7 @@ var generatedTools = [
2878
3187
  cursor: {
2879
3188
  type: "string",
2880
3189
  minLength: 1,
2881
- 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"
3190
+ 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."
2882
3191
  },
2883
3192
  limit: {
2884
3193
  type: "integer",
@@ -2927,7 +3236,7 @@ var generatedTools = [
2927
3236
  cursor: {
2928
3237
  type: "string",
2929
3238
  minLength: 1,
2930
- 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"
3239
+ 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."
2931
3240
  },
2932
3241
  limit: {
2933
3242
  type: "integer",
@@ -2969,6 +3278,46 @@ var generatedTools = [
2969
3278
  required: ["id"]
2970
3279
  }
2971
3280
  },
3281
+ {
3282
+ name: "list-global-volumes",
3283
+ operationId: "listGlobalVolumes",
3284
+ 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.",
3285
+ method: "GET",
3286
+ path: "/v2/global-volumes",
3287
+ params: [
3288
+ {
3289
+ name: "cursor",
3290
+ location: "query"
3291
+ },
3292
+ {
3293
+ name: "limit",
3294
+ location: "query"
3295
+ }
3296
+ ],
3297
+ hasBody: false,
3298
+ annotations: {
3299
+ readOnlyHint: true,
3300
+ destructiveHint: false,
3301
+ idempotentHint: true,
3302
+ openWorldHint: true
3303
+ },
3304
+ inputSchema: {
3305
+ type: "object",
3306
+ properties: {
3307
+ cursor: {
3308
+ type: "string",
3309
+ minLength: 1,
3310
+ 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."
3311
+ },
3312
+ limit: {
3313
+ type: "integer",
3314
+ minimum: 1,
3315
+ maximum: 1e3,
3316
+ description: "Page size, 1\u20131000. Defaults to 1000 when omitted."
3317
+ }
3318
+ }
3319
+ }
3320
+ },
2972
3321
  {
2973
3322
  name: "list-gpu-types",
2974
3323
  operationId: "listGpuTypes",
@@ -3032,7 +3381,7 @@ var generatedTools = [
3032
3381
  items: {
3033
3382
  $ref: "#/$defs/Product"
3034
3383
  },
3035
- 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."
3384
+ description: "Comma-separated availability product contexts. Supported values: POD, CLUSTER, SERVERLESS. Required with include=AVAILABILITY, and valid only with it (400 either way)."
3036
3385
  },
3037
3386
  count: {
3038
3387
  type: "integer",
@@ -3050,7 +3399,7 @@ var generatedTools = [
3050
3399
  type: "string",
3051
3400
  pattern: "^[A-Z]{2}$"
3052
3401
  },
3053
- 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."
3402
+ 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."
3054
3403
  },
3055
3404
  cudaVersions: {
3056
3405
  type: "array",
@@ -3058,12 +3407,12 @@ var generatedTools = [
3058
3407
  type: "string",
3059
3408
  pattern: "^\\d+\\.\\d+$"
3060
3409
  },
3061
- 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."
3410
+ 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."
3062
3411
  },
3063
3412
  minCudaVersion: {
3064
3413
  type: "string",
3065
3414
  pattern: "^\\d+(\\.\\d+)?$",
3066
- 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."
3415
+ description: "Lowest acceptable CUDA version to scope availability and lowest-price calculations to, compared numerically."
3067
3416
  }
3068
3417
  },
3069
3418
  $defs: {
@@ -3126,12 +3475,12 @@ var generatedTools = [
3126
3475
  startTime: {
3127
3476
  type: "string",
3128
3477
  format: "date-time",
3129
- 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"
3478
+ description: "Start of the billing period (RFC 3339). Defaults to 30 days ago."
3130
3479
  },
3131
3480
  endTime: {
3132
3481
  type: "string",
3133
3482
  format: "date-time",
3134
- 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"
3483
+ 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."
3135
3484
  },
3136
3485
  bucketSize: {
3137
3486
  $ref: "#/$defs/BillingBucketSize",
@@ -3140,7 +3489,7 @@ var generatedTools = [
3140
3489
  lastN: {
3141
3490
  type: "integer",
3142
3491
  minimum: 1,
3143
- 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'
3492
+ 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".'
3144
3493
  },
3145
3494
  networkVolumeId: {
3146
3495
  type: "string",
@@ -3168,10 +3517,19 @@ var generatedTools = [
3168
3517
  {
3169
3518
  name: "list-network-volumes",
3170
3519
  operationId: "listNetworkVolumes",
3171
- description: "List network volumes. Returns all network volumes owned by the authenticated user.",
3520
+ 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.",
3172
3521
  method: "GET",
3173
3522
  path: "/v2/network-volumes",
3174
- params: [],
3523
+ params: [
3524
+ {
3525
+ name: "cursor",
3526
+ location: "query"
3527
+ },
3528
+ {
3529
+ name: "limit",
3530
+ location: "query"
3531
+ }
3532
+ ],
3175
3533
  hasBody: false,
3176
3534
  annotations: {
3177
3535
  readOnlyHint: true,
@@ -3181,7 +3539,19 @@ var generatedTools = [
3181
3539
  },
3182
3540
  inputSchema: {
3183
3541
  type: "object",
3184
- properties: {}
3542
+ properties: {
3543
+ cursor: {
3544
+ type: "string",
3545
+ minLength: 1,
3546
+ 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."
3547
+ },
3548
+ limit: {
3549
+ type: "integer",
3550
+ minimum: 1,
3551
+ maximum: 1e3,
3552
+ description: "Page size, 1\u20131000. Defaults to 1000 when omitted."
3553
+ }
3554
+ }
3185
3555
  }
3186
3556
  },
3187
3557
  {
@@ -3225,12 +3595,12 @@ var generatedTools = [
3225
3595
  startTime: {
3226
3596
  type: "string",
3227
3597
  format: "date-time",
3228
- 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"
3598
+ description: "Start of the billing period (RFC 3339). Defaults to 30 days ago."
3229
3599
  },
3230
3600
  endTime: {
3231
3601
  type: "string",
3232
3602
  format: "date-time",
3233
- 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"
3603
+ 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."
3234
3604
  },
3235
3605
  bucketSize: {
3236
3606
  $ref: "#/$defs/BillingBucketSize",
@@ -3239,7 +3609,7 @@ var generatedTools = [
3239
3609
  lastN: {
3240
3610
  type: "integer",
3241
3611
  minimum: 1,
3242
- 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'
3612
+ 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".'
3243
3613
  },
3244
3614
  podId: {
3245
3615
  type: "string",
@@ -3267,7 +3637,7 @@ var generatedTools = [
3267
3637
  {
3268
3638
  name: "list-pods",
3269
3639
  operationId: "listPods",
3270
- 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.",
3640
+ 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.",
3271
3641
  method: "GET",
3272
3642
  path: "/v2/pods",
3273
3643
  params: [
@@ -3302,7 +3672,7 @@ var generatedTools = [
3302
3672
  cursor: {
3303
3673
  type: "string",
3304
3674
  minLength: 1,
3305
- 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"
3675
+ 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."
3306
3676
  },
3307
3677
  limit: {
3308
3678
  type: "integer",
@@ -3339,7 +3709,7 @@ var generatedTools = [
3339
3709
  type: "string",
3340
3710
  enum: ["official", "verified", "community"],
3341
3711
  default: "official",
3342
- 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"
3712
+ 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."
3343
3713
  }
3344
3714
  }
3345
3715
  }
@@ -3433,12 +3803,12 @@ var generatedTools = [
3433
3803
  startTime: {
3434
3804
  type: "string",
3435
3805
  format: "date-time",
3436
- 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"
3806
+ description: "Start of the billing period (RFC 3339). Defaults to 30 days ago."
3437
3807
  },
3438
3808
  endTime: {
3439
3809
  type: "string",
3440
3810
  format: "date-time",
3441
- 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"
3811
+ 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."
3442
3812
  },
3443
3813
  bucketSize: {
3444
3814
  $ref: "#/$defs/BillingBucketSize",
@@ -3447,7 +3817,7 @@ var generatedTools = [
3447
3817
  lastN: {
3448
3818
  type: "integer",
3449
3819
  minimum: 1,
3450
- 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'
3820
+ 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".'
3451
3821
  },
3452
3822
  serverlessId: {
3453
3823
  type: "string",
@@ -3586,7 +3956,7 @@ var generatedTools = [
3586
3956
  type: "object",
3587
3957
  additionalProperties: false,
3588
3958
  minProperties: 1,
3589
- 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",
3959
+ 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.",
3590
3960
  properties: {
3591
3961
  name: {
3592
3962
  type: "string",
@@ -3617,6 +3987,7 @@ var generatedTools = [
3617
3987
  idempotentHint: false,
3618
3988
  openWorldHint: true
3619
3989
  },
3990
+ schemaOnDemand: true,
3620
3991
  inputSchema: {
3621
3992
  type: "object",
3622
3993
  properties: {
@@ -3632,11 +4003,10 @@ var generatedTools = [
3632
4003
  $defs: {
3633
4004
  BaseContainerConfig: {
3634
4005
  type: "object",
3635
- 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",
3636
4006
  properties: {
3637
4007
  args: {
3638
4008
  type: "string",
3639
- 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',
4009
+ 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.',
3640
4010
  examples: [
3641
4011
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
3642
4012
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -3648,7 +4018,7 @@ var generatedTools = [
3648
4018
  items: {
3649
4019
  type: "string"
3650
4020
  },
3651
- 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",
4021
+ 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.",
3652
4022
  examples: [
3653
4023
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
3654
4024
  ]
@@ -3658,7 +4028,7 @@ var generatedTools = [
3658
4028
  items: {
3659
4029
  type: "string"
3660
4030
  },
3661
- 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",
4031
+ 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.",
3662
4032
  examples: [["/bin/bash", "-c"]]
3663
4033
  },
3664
4034
  disk: {
@@ -3718,7 +4088,7 @@ var generatedTools = [
3718
4088
  pools: {
3719
4089
  type: "array",
3720
4090
  minItems: 1,
3721
- 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',
4091
+ 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.",
3722
4092
  items: {
3723
4093
  type: "string"
3724
4094
  },
@@ -3727,7 +4097,7 @@ var generatedTools = [
3727
4097
  excludedTypes: {
3728
4098
  type: "array",
3729
4099
  uniqueItems: true,
3730
- 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',
4100
+ 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.",
3731
4101
  items: {
3732
4102
  type: "string",
3733
4103
  pattern: "^\\s*[^-\\s]"
@@ -3744,7 +4114,6 @@ var generatedTools = [
3744
4114
  }
3745
4115
  },
3746
4116
  ContainerConfig: {
3747
- description: "Reusable container configuration shared across templates, pods, and serverless endpoints. Adding a field here automatically propagates to all three resources.\n",
3748
4117
  allOf: [
3749
4118
  {
3750
4119
  $ref: "#/$defs/BaseContainerConfig"
@@ -3769,8 +4138,42 @@ var generatedTools = [
3769
4138
  ],
3770
4139
  unevaluatedProperties: false
3771
4140
  },
4141
+ EndpointMount: {
4142
+ type: "object",
4143
+ required: ["volumeId"],
4144
+ additionalProperties: false,
4145
+ description: "A volume mounted on every worker, always at `/runpod-volume`. Custom paths are not supported yet.",
4146
+ properties: {
4147
+ volumeId: {
4148
+ type: "string",
4149
+ description: "ID of a NetworkVolume (under `network`) or GlobalVolume (under `global`).",
4150
+ examples: ["vol_abc"]
4151
+ }
4152
+ }
4153
+ },
4154
+ EndpointMountsInput: {
4155
+ type: "object",
4156
+ additionalProperties: false,
4157
+ 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.",
4158
+ properties: {
4159
+ network: {
4160
+ type: "array",
4161
+ description: "Network volumes, at most one per data center.",
4162
+ items: {
4163
+ $ref: "#/$defs/EndpointMount"
4164
+ }
4165
+ },
4166
+ global: {
4167
+ type: "array",
4168
+ maxItems: 1,
4169
+ items: {
4170
+ $ref: "#/$defs/EndpointMount"
4171
+ }
4172
+ }
4173
+ }
4174
+ },
3772
4175
  EndpointScaling: {
3773
- 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",
4176
+ 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.",
3774
4177
  oneOf: [
3775
4178
  {
3776
4179
  $ref: "#/$defs/QueueDelayScaling"
@@ -3807,14 +4210,14 @@ var generatedTools = [
3807
4210
  type: "integer",
3808
4211
  minimum: 1,
3809
4212
  maximum: 3600,
3810
- 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",
4213
+ 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.",
3811
4214
  examples: [5]
3812
4215
  }
3813
4216
  }
3814
4217
  },
3815
4218
  FlashBoot: {
3816
4219
  type: "string",
3817
- description: "FlashBoot cold-start acceleration mode.\n- `OFF` \u2014 disabled\n- `FLASHBOOT` \u2014 enabled\n- `PRIORITY_FLASHBOOT` \u2014 enabled with priority capacity\n",
4220
+ description: "FlashBoot cold-start acceleration mode. `OFF` \u2014 disabled. `FLASHBOOT` \u2014 enabled. `PRIORITY_FLASHBOOT` \u2014 enabled with priority capacity",
3818
4221
  enum: ["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]
3819
4222
  },
3820
4223
  QueueDelayScaling: {
@@ -3840,7 +4243,7 @@ var generatedTools = [
3840
4243
  RequestCountScaling: {
3841
4244
  type: "object",
3842
4245
  additionalProperties: false,
3843
- description: "Scale on concurrent in-flight requests per worker. Required for\nload-balancing endpoints; also selectable for queue-based.\n",
4246
+ description: "Scale on concurrent in-flight requests per worker. Required for load-balancing endpoints; also selectable for queue-based.",
3844
4247
  required: ["type", "requestCount"],
3845
4248
  properties: {
3846
4249
  type: {
@@ -3857,7 +4260,7 @@ var generatedTools = [
3857
4260
  }
3858
4261
  },
3859
4262
  UpdateEndpointGpuConfig: {
3860
- 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",
4263
+ 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.",
3861
4264
  allOf: [
3862
4265
  {
3863
4266
  $ref: "#/$defs/BaseEndpointGpuConfig"
@@ -3874,12 +4277,12 @@ var generatedTools = [
3874
4277
  type: "string",
3875
4278
  pattern: "^\\d+\\.\\d+$"
3876
4279
  },
3877
- 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"
4280
+ 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."
3878
4281
  },
3879
4282
  minCudaVersion: {
3880
4283
  type: "string",
3881
4284
  pattern: "^$|^\\d+\\.\\d+$",
3882
- 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',
4285
+ 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.',
3883
4286
  examples: ["12.1"]
3884
4287
  }
3885
4288
  }
@@ -3900,7 +4303,7 @@ var generatedTools = [
3900
4303
  type: "array",
3901
4304
  minItems: 1,
3902
4305
  uniqueItems: true,
3903
- description: "Complete replacement CPU selection. Valid only for an existing CPU\nendpoint; endpoint compute family cannot be changed.\n",
4306
+ description: "Complete replacement CPU selection. Valid only for an existing CPU endpoint; endpoint compute family cannot be changed.",
3904
4307
  items: {
3905
4308
  $ref: "#/$defs/CreateCpuConfig"
3906
4309
  }
@@ -3926,14 +4329,19 @@ var generatedTools = [
3926
4329
  type: "array",
3927
4330
  items: {
3928
4331
  type: "string"
3929
- }
4332
+ },
4333
+ description: "Network volume IDs. Pass `[]` to detach.",
4334
+ deprecated: true
4335
+ },
4336
+ mounts: {
4337
+ $ref: "#/$defs/EndpointMountsInput"
3930
4338
  },
3931
4339
  scaling: {
3932
4340
  $ref: "#/$defs/EndpointScaling"
3933
4341
  },
3934
4342
  templateId: {
3935
4343
  type: "string",
3936
- 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",
4344
+ 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.",
3937
4345
  examples: ["30zmvf89kd"]
3938
4346
  },
3939
4347
  timeout: {
@@ -3950,6 +4358,57 @@ var generatedTools = [
3950
4358
  }
3951
4359
  }
3952
4360
  },
4361
+ {
4362
+ name: "update-global-volume",
4363
+ operationId: "updateGlobalVolume",
4364
+ description: "Update a global volume. Updates mutable fields on a global volume. Only provided fields are changed.",
4365
+ method: "PATCH",
4366
+ path: "/v2/global-volumes/{id}",
4367
+ params: [
4368
+ {
4369
+ name: "id",
4370
+ location: "path"
4371
+ }
4372
+ ],
4373
+ hasBody: true,
4374
+ annotations: {
4375
+ readOnlyHint: false,
4376
+ destructiveHint: false,
4377
+ idempotentHint: false,
4378
+ openWorldHint: true
4379
+ },
4380
+ inputSchema: {
4381
+ type: "object",
4382
+ properties: {
4383
+ id: {
4384
+ type: "string",
4385
+ description: "Global volume identifier"
4386
+ },
4387
+ body: {
4388
+ $ref: "#/$defs/UpdateGlobalVolumeRequest"
4389
+ }
4390
+ },
4391
+ required: ["id", "body"],
4392
+ $defs: {
4393
+ UpdateGlobalVolumeRequest: {
4394
+ type: "object",
4395
+ additionalProperties: false,
4396
+ minProperties: 1,
4397
+ description: "Only the provided fields are updated. At least one field must be present; an empty body is rejected.",
4398
+ properties: {
4399
+ name: {
4400
+ type: "string",
4401
+ minLength: 1,
4402
+ maxLength: 191,
4403
+ pattern: "[^\\p{Z}\\t-\\r\x85]",
4404
+ description: "New human-readable name, 1\u2013191 characters with at least one non-whitespace character. Leading and trailing whitespace is trimmed.",
4405
+ examples: ["renamed-volume"]
4406
+ }
4407
+ }
4408
+ }
4409
+ }
4410
+ }
4411
+ },
3953
4412
  {
3954
4413
  name: "update-network-volume",
3955
4414
  operationId: "updateNetworkVolume",
@@ -3986,7 +4445,7 @@ var generatedTools = [
3986
4445
  type: "object",
3987
4446
  additionalProperties: false,
3988
4447
  minProperties: 1,
3989
- description: "Only the provided fields are updated. At least one field must be\npresent; an empty body is rejected.\n",
4448
+ description: "Only the provided fields are updated. At least one field must be present; an empty body is rejected.",
3990
4449
  properties: {
3991
4450
  name: {
3992
4451
  type: "string",
@@ -3998,7 +4457,7 @@ var generatedTools = [
3998
4457
  type: "integer",
3999
4458
  minimum: 10,
4000
4459
  maximum: 4096,
4001
- description: "New size in GB. Must be greater than or equal to the current size \u2014\nnetwork volume storage cannot be reduced.\n",
4460
+ description: "New size in GB. Must be greater than or equal to the current size \u2014 network volume storage cannot be reduced.",
4002
4461
  examples: [100]
4003
4462
  }
4004
4463
  }
@@ -4025,6 +4484,7 @@ var generatedTools = [
4025
4484
  idempotentHint: false,
4026
4485
  openWorldHint: true
4027
4486
  },
4487
+ schemaOnDemand: true,
4028
4488
  inputSchema: {
4029
4489
  type: "object",
4030
4490
  properties: {
@@ -4040,11 +4500,10 @@ var generatedTools = [
4040
4500
  $defs: {
4041
4501
  BaseContainerConfig: {
4042
4502
  type: "object",
4043
- 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",
4044
4503
  properties: {
4045
4504
  args: {
4046
4505
  type: "string",
4047
- 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',
4506
+ 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.',
4048
4507
  examples: [
4049
4508
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
4050
4509
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -4056,7 +4515,7 @@ var generatedTools = [
4056
4515
  items: {
4057
4516
  type: "string"
4058
4517
  },
4059
- 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",
4518
+ 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.",
4060
4519
  examples: [
4061
4520
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
4062
4521
  ]
@@ -4066,7 +4525,7 @@ var generatedTools = [
4066
4525
  items: {
4067
4526
  type: "string"
4068
4527
  },
4069
- 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",
4528
+ 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.",
4070
4529
  examples: [["/bin/bash", "-c"]]
4071
4530
  },
4072
4531
  disk: {
@@ -4102,8 +4561,22 @@ var generatedTools = [
4102
4561
  }
4103
4562
  }
4104
4563
  },
4564
+ BaseMounts: {
4565
+ type: "object",
4566
+ properties: {
4567
+ persistent: {
4568
+ $ref: "#/$defs/PersistentMount"
4569
+ },
4570
+ network: {
4571
+ type: "array",
4572
+ maxItems: 1,
4573
+ items: {
4574
+ $ref: "#/$defs/NetworkMount"
4575
+ }
4576
+ }
4577
+ }
4578
+ },
4105
4579
  ContainerConfig: {
4106
- description: "Reusable container configuration shared across templates, pods, and serverless endpoints. Adding a field here automatically propagates to all three resources.\n",
4107
4580
  allOf: [
4108
4581
  {
4109
4582
  $ref: "#/$defs/BaseContainerConfig"
@@ -4120,28 +4593,51 @@ var generatedTools = [
4120
4593
  }
4121
4594
  ]
4122
4595
  },
4123
- Mounts: {
4596
+ GlobalMount: {
4124
4597
  type: "object",
4598
+ required: ["volumeId"],
4125
4599
  additionalProperties: false,
4126
- 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",
4600
+ 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.",
4127
4601
  properties: {
4128
- persistent: {
4129
- $ref: "#/$defs/PersistentMount"
4602
+ volumeId: {
4603
+ type: "string",
4604
+ description: "ID of an existing GlobalVolume.",
4605
+ examples: ["gsb_abc"]
4130
4606
  },
4131
- network: {
4132
- type: "array",
4133
- maxItems: 1,
4134
- items: {
4135
- $ref: "#/$defs/NetworkMount"
4136
- }
4607
+ path: {
4608
+ type: "string",
4609
+ 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.",
4610
+ examples: ["/data"]
4137
4611
  }
4138
4612
  }
4139
4613
  },
4614
+ Mounts: {
4615
+ 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.",
4616
+ allOf: [
4617
+ {
4618
+ $ref: "#/$defs/BaseMounts"
4619
+ },
4620
+ {
4621
+ type: "object",
4622
+ properties: {
4623
+ global: {
4624
+ type: "array",
4625
+ maxItems: 1,
4626
+ 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.",
4627
+ items: {
4628
+ $ref: "#/$defs/GlobalMount"
4629
+ }
4630
+ }
4631
+ }
4632
+ }
4633
+ ],
4634
+ unevaluatedProperties: false
4635
+ },
4140
4636
  NetworkMount: {
4141
4637
  type: "object",
4142
4638
  required: ["volumeId", "path"],
4143
4639
  additionalProperties: false,
4144
- 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",
4640
+ 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.",
4145
4641
  properties: {
4146
4642
  volumeId: {
4147
4643
  type: "string",
@@ -4159,7 +4655,7 @@ var generatedTools = [
4159
4655
  type: "object",
4160
4656
  required: ["size", "path"],
4161
4657
  additionalProperties: false,
4162
- 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",
4658
+ 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.",
4163
4659
  properties: {
4164
4660
  size: {
4165
4661
  type: "integer",
@@ -4184,7 +4680,7 @@ var generatedTools = [
4184
4680
  properties: {
4185
4681
  globalNetworking: {
4186
4682
  type: "boolean",
4187
- 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."
4683
+ 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)."
4188
4684
  },
4189
4685
  locked: {
4190
4686
  type: "boolean",
@@ -4199,7 +4695,7 @@ var generatedTools = [
4199
4695
  },
4200
4696
  templateId: {
4201
4697
  type: "string",
4202
- 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",
4698
+ 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.",
4203
4699
  examples: ["30zmvf89kd"]
4204
4700
  }
4205
4701
  }
@@ -4229,6 +4725,7 @@ var generatedTools = [
4229
4725
  idempotentHint: false,
4230
4726
  openWorldHint: true
4231
4727
  },
4728
+ schemaOnDemand: true,
4232
4729
  inputSchema: {
4233
4730
  type: "object",
4234
4731
  properties: {
@@ -4246,7 +4743,7 @@ var generatedTools = [
4246
4743
  type: "object",
4247
4744
  additionalProperties: false,
4248
4745
  minProperties: 1,
4249
- description: "Only the provided fields are updated; at least one field is required.\nThe secret's `name` is immutable.\n",
4746
+ description: "Only the provided fields are updated; at least one field is required. The secret's `name` is immutable.",
4250
4747
  properties: {
4251
4748
  value: {
4252
4749
  type: "string",
@@ -4298,7 +4795,7 @@ var generatedTools = [
4298
4795
  type: "string",
4299
4796
  pattern: "^(ssh|ecdsa|sk)-[^\\s]+ [^\\s]+([ \\t][^\\n\\r]*)?$"
4300
4797
  },
4301
- 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",
4798
+ 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.",
4302
4799
  examples: [
4303
4800
  [
4304
4801
  "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILXGDN/SclOozk1xsDztpmhGiKkkrfQB9SKoO8dSIQQZ me@example.com"
@@ -4329,6 +4826,7 @@ var generatedTools = [
4329
4826
  idempotentHint: false,
4330
4827
  openWorldHint: true
4331
4828
  },
4829
+ schemaOnDemand: true,
4332
4830
  inputSchema: {
4333
4831
  type: "object",
4334
4832
  properties: {
@@ -4343,11 +4841,10 @@ var generatedTools = [
4343
4841
  $defs: {
4344
4842
  BaseContainerConfig: {
4345
4843
  type: "object",
4346
- 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",
4347
4844
  properties: {
4348
4845
  args: {
4349
4846
  type: "string",
4350
- 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',
4847
+ 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.',
4351
4848
  examples: [
4352
4849
  "--model meta-llama/Llama-3-8B --max-model-len 8192",
4353
4850
  '{"entrypoint":["/bin/bash","-c"],"cmd":["python -u main.py"]}',
@@ -4359,7 +4856,7 @@ var generatedTools = [
4359
4856
  items: {
4360
4857
  type: "string"
4361
4858
  },
4362
- 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",
4859
+ 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.",
4363
4860
  examples: [
4364
4861
  ["--model", "meta-llama/Llama-3-8B", "--max-model-len", "8192"]
4365
4862
  ]
@@ -4369,7 +4866,7 @@ var generatedTools = [
4369
4866
  items: {
4370
4867
  type: "string"
4371
4868
  },
4372
- 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",
4869
+ 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.",
4373
4870
  examples: [["/bin/bash", "-c"]]
4374
4871
  },
4375
4872
  disk: {
@@ -4406,7 +4903,6 @@ var generatedTools = [
4406
4903
  }
4407
4904
  },
4408
4905
  ContainerConfig: {
4409
- description: "Reusable container configuration shared across templates, pods, and serverless endpoints. Adding a field here automatically propagates to all three resources.\n",
4410
4906
  allOf: [
4411
4907
  {
4412
4908
  $ref: "#/$defs/BaseContainerConfig"
@@ -4427,7 +4923,7 @@ var generatedTools = [
4427
4923
  type: "object",
4428
4924
  required: ["size", "path"],
4429
4925
  additionalProperties: false,
4430
- 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",
4926
+ 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.",
4431
4927
  properties: {
4432
4928
  size: {
4433
4929
  type: "integer",
@@ -4444,13 +4940,13 @@ var generatedTools = [
4444
4940
  },
4445
4941
  TemplateCategory: {
4446
4942
  type: "string",
4447
- 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",
4943
+ 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",
4448
4944
  enum: ["CPU", "NVIDIA", "AMD"]
4449
4945
  },
4450
4946
  TemplateMounts: {
4451
4947
  type: "object",
4452
4948
  additionalProperties: false,
4453
- 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",
4949
+ 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.",
4454
4950
  properties: {
4455
4951
  persistent: {
4456
4952
  $ref: "#/$defs/PersistentMount"
@@ -6012,7 +6508,7 @@ function authedGraphqlBase(env) {
6012
6508
  return pick(env.RUNPOD_AUTHED_GRAPHQL_URL, "https://api.runpod.io/graphql");
6013
6509
  }
6014
6510
 
6015
- // node_modules/.pnpm/@runpod+typescript-api-sdk@0.2.0/node_modules/@runpod/typescript-api-sdk/dist/index.js
6511
+ // node_modules/.pnpm/@runpod+typescript-api-sdk@0.3.0/node_modules/@runpod/typescript-api-sdk/dist/index.js
6016
6512
  var PATH_PARAM_RE = /\{[^{}]+\}/g;
6017
6513
  var supportsRequestInitExt = () => {
6018
6514
  return typeof process === "object" && Number.parseInt(process?.versions?.node?.substring(0, 2)) >= 18 && process.versions.undici;
@@ -6909,9 +7405,9 @@ function isDigit(char) {
6909
7405
  return false;
6910
7406
  return isDigitRegex.test(char);
6911
7407
  }
6912
- function parseRetryAfter(header, now = Date.now()) {
6913
- if (header === null) return void 0;
6914
- const value = header.trim();
7408
+ function parseRetryAfter(header2, now = Date.now()) {
7409
+ if (header2 === null) return void 0;
7410
+ const value = header2.trim();
6915
7411
  if (/^\d+$/.test(value)) {
6916
7412
  const ms = Number(value) * 1e3;
6917
7413
  return Number.isSafeInteger(ms) ? ms : void 0;
@@ -7702,6 +8198,7 @@ var source = {
7702
8198
  var listGuides = {
7703
8199
  name: "list-guides",
7704
8200
  annotations: bundledRead,
8201
+ recordedArguments: ["kind", "query", "concept"],
7705
8202
  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.",
7706
8203
  inputSchema: {
7707
8204
  type: "object",
@@ -7737,6 +8234,8 @@ var listGuides = {
7737
8234
  var readGuide = {
7738
8235
  name: "read-guide",
7739
8236
  annotations: bundledRead,
8237
+ recordedArguments: ["id"],
8238
+ recordedGuideKind: (args) => knowledge.guides.find((guide) => guide.id === String(args.id).trim())?.kind,
7740
8239
  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").',
7741
8240
  inputSchema: {
7742
8241
  type: "object",
@@ -7774,6 +8273,7 @@ var readGuide = {
7774
8273
  var lookupConcept = {
7775
8274
  name: "lookup-concept",
7776
8275
  annotations: bundledRead,
8276
+ recordedArguments: ["concept"],
7777
8277
  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".',
7778
8278
  inputSchema: {
7779
8279
  type: "object",
@@ -7826,6 +8326,7 @@ var lookupConcept = {
7826
8326
  var searchConcepts = {
7827
8327
  name: "search-concepts",
7828
8328
  annotations: bundledRead,
8329
+ recordedArguments: ["query", "limit"],
7829
8330
  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.',
7830
8331
  inputSchema: {
7831
8332
  type: "object",
@@ -7890,9 +8391,143 @@ var guideResources = knowledge.guides.map((guide) => ({
7890
8391
  text: guide.body
7891
8392
  }));
7892
8393
 
8394
+ // src/specgen/prose.ts
8395
+ var ABBREVIATIONS = ["e.g.", "i.e.", "etc.", "vs."];
8396
+ var DOT = "\0";
8397
+ function splitSentences(text) {
8398
+ let guarded = text;
8399
+ for (const abbreviation of ABBREVIATIONS) {
8400
+ guarded = guarded.replaceAll(
8401
+ abbreviation,
8402
+ abbreviation.replaceAll(".", DOT)
8403
+ );
8404
+ }
8405
+ return guarded.split(/(?<=[.!?])\s+(?=[A-Z`"'([])/).map((sentence) => sentence.replaceAll(DOT, ".").trim()).filter(Boolean);
8406
+ }
8407
+ function leadSentence(text) {
8408
+ return splitSentences(text)[0] ?? text;
8409
+ }
8410
+
8411
+ // src/specgen/on-demand.ts
8412
+ var DESCRIBE_TOOL_NAME = "describe-tool";
8413
+ var ON_DEMAND_POINTER = ` Call ${DESCRIBE_TOOL_NAME} for the full argument schema before the first use.`;
8414
+ function listedAnnotations(annotations) {
8415
+ const { idempotentHint, ...listed } = annotations;
8416
+ return idempotentHint ? annotations : listed;
8417
+ }
8418
+ function listTool(tool) {
8419
+ const { name, description, inputSchema } = tool;
8420
+ const annotations = listedAnnotations(tool.annotations);
8421
+ if (!tool.schemaOnDemand)
8422
+ return { name, description, inputSchema, annotations };
8423
+ return {
8424
+ name,
8425
+ description: description + ON_DEMAND_POINTER,
8426
+ inputSchema: callShape(inputSchema),
8427
+ annotations
8428
+ };
8429
+ }
8430
+ function callShape(schema) {
8431
+ const defs = schema.$defs ?? {};
8432
+ const resolve = (node) => {
8433
+ if (typeof node.$ref !== "string") return node;
8434
+ const name = node.$ref.split("/").pop() ?? "";
8435
+ return defs[name] ?? {};
8436
+ };
8437
+ const properties = Object.fromEntries(
8438
+ Object.entries(schema.properties ?? {}).map(
8439
+ ([key, value]) => [key, shallowProperty(resolve(value), resolve)]
8440
+ )
8441
+ );
8442
+ return {
8443
+ type: "object",
8444
+ properties,
8445
+ ...schema.required ? { required: schema.required } : {}
8446
+ };
8447
+ }
8448
+ function shallowProperty(node, resolve) {
8449
+ const out = {};
8450
+ const type = schemaType(node, resolve);
8451
+ if (type !== void 0) out.type = type;
8452
+ if (type === "array") {
8453
+ const items = resolve(node.items ?? {});
8454
+ const itemType = schemaType(items, resolve);
8455
+ if (itemType !== void 0) out.items = { type: itemType };
8456
+ }
8457
+ if (Array.isArray(node.enum)) out.enum = node.enum;
8458
+ if (typeof node.description === "string")
8459
+ out.description = leadSentence(node.description);
8460
+ return out;
8461
+ }
8462
+ function schemaType(node, resolve) {
8463
+ if (node.type !== void 0) return node.type;
8464
+ if (node.properties || node.allOf) return "object";
8465
+ const branches2 = node.anyOf ?? node.oneOf;
8466
+ const allObjects = branches2?.every(
8467
+ (branch) => schemaType(resolve(branch), resolve) === "object"
8468
+ ) ?? false;
8469
+ return allObjects ? "object" : void 0;
8470
+ }
8471
+
8472
+ // src/specgen/tools/describe.ts
8473
+ function createDescribeTool(served) {
8474
+ return {
8475
+ name: DESCRIBE_TOOL_NAME,
8476
+ 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.",
8477
+ inputSchema: {
8478
+ type: "object",
8479
+ properties: {
8480
+ name: {
8481
+ type: "string",
8482
+ description: "A tool name exactly as this server's tool list spells it."
8483
+ }
8484
+ },
8485
+ required: ["name"]
8486
+ },
8487
+ annotations: {
8488
+ readOnlyHint: true,
8489
+ destructiveHint: false,
8490
+ idempotentHint: true,
8491
+ openWorldHint: false
8492
+ },
8493
+ handler: async (_ctx, args) => {
8494
+ const name = String(args.name ?? "");
8495
+ const tools = served();
8496
+ const tool = tools.find((candidate) => candidate.name === name);
8497
+ if (!tool) {
8498
+ return {
8499
+ ok: false,
8500
+ status: 404,
8501
+ payload: {
8502
+ error: `No tool named ${name}.`,
8503
+ suggestions: similarNames(
8504
+ name,
8505
+ tools.map((candidate) => candidate.name)
8506
+ ),
8507
+ hint: "Tool names are exactly as this server lists them; the suggestions share a word with the name you sent."
8508
+ }
8509
+ };
8510
+ }
8511
+ const { description, inputSchema, annotations } = tool;
8512
+ return ok({ name: tool.name, description, inputSchema, annotations });
8513
+ }
8514
+ };
8515
+ }
8516
+ function similarNames(query, names) {
8517
+ const wanted = new Set(words2(query));
8518
+ return names.map((name) => {
8519
+ const own = new Set(words2(name));
8520
+ const shared = [...own].filter((word) => wanted.has(word)).length;
8521
+ return { name, score: shared / (/* @__PURE__ */ new Set([...own, ...wanted])).size };
8522
+ }).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name)).slice(0, 8).map((entry) => entry.name);
8523
+ }
8524
+ function words2(name) {
8525
+ return name.toLowerCase().split(/[-_\s]+/).filter(Boolean).map((word) => word.replace(/s$/, ""));
8526
+ }
8527
+
7893
8528
  // src/server.ts
7894
8529
  var SERVER_NAME = "Runpod API Server";
7895
- var SERVER_VERSION = true ? "4.2.0" : "dev";
8530
+ var SERVER_VERSION = true ? "4.3.0" : "dev";
7896
8531
 
7897
8532
  // src/specgen/analytics.ts
7898
8533
  import { createHmac } from "crypto";
@@ -7908,7 +8543,22 @@ function analyticsCallerId(apiKey, accountId) {
7908
8543
  const prefix = accountId ? "a:" : "k:";
7909
8544
  return prefix + createHmac("sha256", salt).update(identity).digest("hex").slice(0, 16);
7910
8545
  }
7911
- function captureToolCall(apiKey, event) {
8546
+ var MAX_RECORDED_VALUE_LENGTH = 200;
8547
+ function bounded(value) {
8548
+ return value.slice(0, MAX_RECORDED_VALUE_LENGTH);
8549
+ }
8550
+ function recordedArgumentValues(names, args) {
8551
+ if (!names?.length) return void 0;
8552
+ const values = {};
8553
+ for (const name of names) {
8554
+ const value = args[name];
8555
+ if (typeof value === "string" || typeof value === "number") {
8556
+ values[name] = bounded(String(value).trim());
8557
+ }
8558
+ }
8559
+ return Object.keys(values).length ? values : void 0;
8560
+ }
8561
+ function capture(event, distinctId, properties) {
7912
8562
  const posthogKey = process.env.POSTHOG_API_KEY;
7913
8563
  if (!posthogKey) return;
7914
8564
  void fetch(`${posthogHost()}/capture/`, {
@@ -7916,18 +8566,12 @@ function captureToolCall(apiKey, event) {
7916
8566
  headers: { "Content-Type": "application/json" },
7917
8567
  body: JSON.stringify({
7918
8568
  api_key: posthogKey,
7919
- event: "runpod_mcp_tool_call",
7920
- distinct_id: analyticsCallerId(apiKey, event.accountId),
8569
+ event,
8570
+ distinct_id: distinctId,
7921
8571
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7922
8572
  properties: {
7923
- tool: event.tool,
7924
- ok: event.ok,
7925
- status: event.status,
7926
- duration_ms: event.durationMs,
7927
- transport: event.transport,
7928
- server_version: event.serverVersion,
8573
+ ...properties,
7929
8574
  surface_version: SURFACE_VERSION,
7930
- ...event.clientName ? { client_name: event.clientName } : {},
7931
8575
  $process_person_profile: false
7932
8576
  }
7933
8577
  }),
@@ -7935,6 +8579,63 @@ function captureToolCall(apiKey, event) {
7935
8579
  }).catch(() => {
7936
8580
  });
7937
8581
  }
8582
+ function captureToolCall(apiKey, event) {
8583
+ const args = Object.fromEntries(
8584
+ Object.entries(event.args ?? {}).map(([name, value]) => [
8585
+ `arg_${name}`,
8586
+ value
8587
+ ])
8588
+ );
8589
+ capture("runpod_mcp_tool_call", analyticsCallerId(apiKey, event.accountId), {
8590
+ tool: event.tool,
8591
+ ok: event.ok,
8592
+ status: event.status,
8593
+ duration_ms: event.durationMs,
8594
+ transport: event.transport,
8595
+ server_version: event.serverVersion,
8596
+ ...event.clientName ? { client_name: event.clientName } : {},
8597
+ ...event.guideKind ? { guide_kind: event.guideKind } : {},
8598
+ ...event.blockedBy ? { blocked_by: event.blockedBy } : {},
8599
+ ...args
8600
+ });
8601
+ }
8602
+ function captureLoopBlocked(apiKey, event) {
8603
+ capture(
8604
+ "runpod_mcp_loop_blocked",
8605
+ analyticsCallerId(apiKey, event.accountId),
8606
+ {
8607
+ tool: event.tool,
8608
+ blocked_for_s: event.blockedForS,
8609
+ transport: event.transport,
8610
+ server_version: event.serverVersion,
8611
+ ...event.clientName ? { client_name: event.clientName } : {}
8612
+ }
8613
+ );
8614
+ }
8615
+ function captureResourceCall(apiKey, event) {
8616
+ capture(
8617
+ "runpod_mcp_resource_call",
8618
+ analyticsCallerId(apiKey, event.accountId),
8619
+ {
8620
+ method: event.method,
8621
+ uri: bounded(event.uri),
8622
+ ok: event.ok,
8623
+ transport: event.transport,
8624
+ server_version: event.serverVersion,
8625
+ ...event.clientName ? { client_name: event.clientName } : {}
8626
+ }
8627
+ );
8628
+ }
8629
+
8630
+ // src/alp/ingest.ts
8631
+ function isSinkUrl(value) {
8632
+ try {
8633
+ const url = new URL(value);
8634
+ return url.protocol === "https:" && url.hostname.endsWith(".convex.site") && url.pathname === "/alp/submit";
8635
+ } catch {
8636
+ return false;
8637
+ }
8638
+ }
7938
8639
 
7939
8640
  // src/alp/read.ts
7940
8641
  var DEFAULT_JOURNAL_LIMIT = 20;
@@ -8196,14 +8897,21 @@ PLAYBOOKS \u2014 READ BEFORE ACTING. Each Runpod task has a journey playbook car
8196
8897
 
8197
8898
  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.
8198
8899
 
8199
- 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.`;
8900
+ 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.`;
8200
8901
  var ALP_INSTRUCTIONS = `
8201
8902
 
8202
8903
  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.`;
8203
8904
  function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
8204
8905
  const rateLimiter = opts.rateLimiter ?? noopRateLimiter;
8205
8906
  const caller = callerId(ctx.apiKey);
8206
- const servedCuratedTools = opts.alp ? [...curatedTools, ...createAlpTools(opts.alp)] : curatedTools;
8907
+ const configuredCuratedTools = opts.alp ? [...curatedTools, ...createAlpTools(opts.alp)] : curatedTools;
8908
+ const servedCuratedTools = [
8909
+ ...configuredCuratedTools,
8910
+ createDescribeTool(() => [...servedCuratedTools, ...generatedTools])
8911
+ ];
8912
+ const callShapeOnlyTools = new Set(
8913
+ [...servedCuratedTools, ...generatedTools].filter((tool) => tool.schemaOnDemand).map((tool) => tool.name)
8914
+ );
8207
8915
  const server = new Server(
8208
8916
  { name: SERVER_NAME, version: `${version} [specgen]` },
8209
8917
  {
@@ -8222,24 +8930,7 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
8222
8930
  }
8223
8931
  };
8224
8932
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
8225
- tools: [
8226
- ...servedCuratedTools.map(
8227
- ({ name, description, inputSchema, annotations }) => ({
8228
- name,
8229
- description,
8230
- inputSchema,
8231
- annotations
8232
- })
8233
- ),
8234
- ...generatedTools.map(
8235
- ({ name, description, inputSchema, annotations }) => ({
8236
- name,
8237
- description,
8238
- inputSchema,
8239
- annotations
8240
- })
8241
- )
8242
- ]
8933
+ tools: [...servedCuratedTools, ...generatedTools].map(listTool)
8243
8934
  }));
8244
8935
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
8245
8936
  resources: guideResources.map(({ text: _text, ...resource }) => resource)
@@ -8251,6 +8942,14 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
8251
8942
  const guide = guideResources.find(
8252
8943
  (candidate) => candidate.uri === request.params.uri
8253
8944
  );
8945
+ if (opts.analytics) {
8946
+ captureResourceCall(ctx.apiKey, {
8947
+ method: "resources/read",
8948
+ uri: guide ? guide.uri : "unknown",
8949
+ ok: Boolean(guide),
8950
+ ...opts.analytics
8951
+ });
8952
+ }
8254
8953
  if (!guide) {
8255
8954
  throw new Error(
8256
8955
  `Unknown resource ${request.params.uri}. Resources are the plugin guides under ${GUIDE_URI_PREFIX}; call list-guides for the guide ids.`
@@ -8273,6 +8972,16 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
8273
8972
  status: 429,
8274
8973
  durationMs: 0
8275
8974
  });
8975
+ if (opts.analytics) {
8976
+ captureToolCall(ctx.apiKey, {
8977
+ tool: request.params.name,
8978
+ ok: false,
8979
+ status: 429,
8980
+ durationMs: 0,
8981
+ ...verdict.reason ? { blockedBy: verdict.reason } : {},
8982
+ ...opts.analytics
8983
+ });
8984
+ }
8276
8985
  return {
8277
8986
  content: [
8278
8987
  {
@@ -8310,11 +9019,14 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
8310
9019
  ok: result.ok,
8311
9020
  status: result.status,
8312
9021
  durationMs: Date.now() - startedAt,
9022
+ args: recordedArgumentValues(curated?.recordedArguments, args),
9023
+ guideKind: curated?.recordedGuideKind?.(args),
8313
9024
  ...opts.analytics
8314
9025
  });
8315
9026
  }
9027
+ opts.onToolResult?.(request.params.name, result.status);
8316
9028
  if (!result.ok && result.status === 401) opts.onUnauthorized?.();
8317
- const payload = result.ok ? result.payload : withRecoveryHint(result);
9029
+ const payload = result.ok ? result.payload : withRecoveryHint(result, callShapeOnlyTools.has(request.params.name));
8318
9030
  return {
8319
9031
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
8320
9032
  isError: !result.ok
@@ -8332,9 +9044,29 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
8332
9044
  }
8333
9045
  return server;
8334
9046
  }
8335
- function errorHint(status) {
8336
- if (status === 400 || status === 422)
8337
- 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.";
9047
+ 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.";
9048
+ var DETAIL_HINTS = [
9049
+ [
9050
+ /no longer any instances available|not enough free GPUs/i,
9051
+ "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."
9052
+ ],
9053
+ [
9054
+ /at least \$\d|insufficient (?:funds|balance)|\bbalance\b/i,
9055
+ "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."
9056
+ ],
9057
+ [
9058
+ /mutually exclusive|cannot be combined with/i,
9059
+ "Two arguments conflict: send only one of them and leave the other out entirely. Do not change anything else."
9060
+ ]
9061
+ ];
9062
+ function errorHint(status, detail) {
9063
+ if (status === 400 && detail) {
9064
+ for (const [pattern, hint] of DETAIL_HINTS)
9065
+ if (pattern.test(detail)) return hint;
9066
+ if (/^unauthorized$/i.test(detail.trim()))
9067
+ 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.";
9068
+ }
9069
+ if (status === 400 || status === 422) return SHAPE_HINT;
8338
9070
  if (status === 401) return "The Runpod API key is missing or invalid.";
8339
9071
  if (status === 402)
8340
9072
  return "Check your account balance in the Runpod console before retrying billable operations; list-billing shows the recent spend behind it.";
@@ -8348,13 +9080,23 @@ function errorHint(status) {
8348
9080
  return "Upstream Runpod error: retry once; if it persists, report it as an API-side failure.";
8349
9081
  return void 0;
8350
9082
  }
8351
- function withRecoveryHint(result) {
9083
+ var CALL_SHAPE_ONLY_HINT = `This tool lists its call shape only; ${DESCRIBE_TOOL_NAME} serves the full argument schema.`;
9084
+ function withRecoveryHint(result, listsCallShapeOnly = false) {
8352
9085
  const payload = asRecord(result.payload) ?? { error: result.payload };
9086
+ const hint = payload.hint ?? asRecord(payload.detail)?.hint ?? errorHint(result.status, detailText(payload));
9087
+ const shapeRejected = result.status === 400 || result.status === 422;
8353
9088
  return {
8354
9089
  ...payload,
8355
- hint: payload.hint ?? asRecord(payload.detail)?.hint ?? errorHint(result.status)
9090
+ hint: listsCallShapeOnly && shapeRejected ? [hint, CALL_SHAPE_ONLY_HINT].filter(Boolean).join(" ") : hint
8356
9091
  };
8357
9092
  }
9093
+ function detailText(payload) {
9094
+ if (Array.isArray(payload.errors) && payload.errors.length) return void 0;
9095
+ const detail = payload.detail ?? payload.error;
9096
+ if (typeof detail === "string") return detail;
9097
+ const nested = asRecord(detail);
9098
+ return typeof nested?.detail === "string" ? nested.detail : void 0;
9099
+ }
8358
9100
  function asRecord(value) {
8359
9101
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
8360
9102
  }
@@ -8520,6 +9262,58 @@ function createToolContext(options = {}) {
8520
9262
  };
8521
9263
  }
8522
9264
 
9265
+ // src/specgen/not-found-limiter.ts
9266
+ var SINK_TIMEOUT_MS = 3e3;
9267
+ var NOT_FOUND_LOOP = "not_found_loop";
9268
+ var blocked = /* @__PURE__ */ new Map();
9269
+ function notFoundSinkUrl(submitUrl) {
9270
+ if (!isSinkUrl(submitUrl)) return null;
9271
+ const url = new URL(submitUrl);
9272
+ url.pathname = "/ratelimit/not-found";
9273
+ return url.toString();
9274
+ }
9275
+ function createNotFoundLimiter(opts) {
9276
+ const fetchImpl = opts.fetchImpl ?? fetch;
9277
+ const now = opts.now ?? Date.now;
9278
+ return {
9279
+ rateLimiter: async (_caller, tool) => {
9280
+ const pair = `${opts.key}|${tool}`;
9281
+ const until = blocked.get(pair);
9282
+ if (until === void 0) return { allowed: true };
9283
+ if (until <= now()) {
9284
+ blocked.delete(pair);
9285
+ return { allowed: true };
9286
+ }
9287
+ return {
9288
+ allowed: false,
9289
+ retryAfterS: Math.ceil((until - now()) / 1e3),
9290
+ reason: NOT_FOUND_LOOP
9291
+ };
9292
+ },
9293
+ onToolResult: (tool, status) => {
9294
+ if (status !== 404) return;
9295
+ const pair = `${opts.key}|${tool}`;
9296
+ void fetchImpl(opts.sinkUrl, {
9297
+ method: "POST",
9298
+ headers: {
9299
+ "Content-Type": "application/json",
9300
+ "X-ALP-Secret": opts.secret
9301
+ },
9302
+ body: JSON.stringify({ key: pair }),
9303
+ signal: AbortSignal.timeout(SINK_TIMEOUT_MS)
9304
+ }).then((response) => response.ok ? response.json() : null).then((body) => {
9305
+ const reply = body;
9306
+ const until = reply?.blockedUntil;
9307
+ if (typeof until === "number" && until > now()) {
9308
+ blocked.set(pair, until);
9309
+ if (reply?.started === true) opts.onBlockStarted?.(tool, until);
9310
+ }
9311
+ }).catch(() => {
9312
+ });
9313
+ }
9314
+ };
9315
+ }
9316
+
8523
9317
  // src/_shared/credential-check.ts
8524
9318
  import { createHash as createHash2 } from "crypto";
8525
9319
  var VALID_TTL_MS = 6e4;
@@ -8631,8 +9425,7 @@ function extractBearerToken(req) {
8631
9425
  return parts[1];
8632
9426
  }
8633
9427
  function analyticsOptedOut(req) {
8634
- const raw = req.headers["x-runpod-analytics"];
8635
- const value = (Array.isArray(raw) ? raw[0] : raw)?.trim().toLowerCase();
9428
+ const value = header(req, "x-runpod-analytics")?.trim().toLowerCase();
8636
9429
  return value === "off" || value === "0" || value === "false";
8637
9430
  }
8638
9431
  function getBaseUrl(req) {
@@ -8691,14 +9484,16 @@ function credentialCheckMayBeWrongEnvironment(env = process.env) {
8691
9484
  );
8692
9485
  return restMoved !== graphqlMoved;
8693
9486
  }
8694
- function looksLikeMcpMessage(value) {
8695
- if (typeof value !== "object" || value === null) return false;
8696
- const msg = value;
8697
- return msg.jsonrpc === "2.0" && typeof msg.method === "string";
9487
+ function parseMcpMessages(body) {
9488
+ const elements = Array.isArray(body) ? body : [body];
9489
+ return elements.flatMap((element) => {
9490
+ const parsed = JSONRPCMessageSchema.safeParse(element);
9491
+ return parsed.success ? [parsed.data] : [];
9492
+ });
8698
9493
  }
9494
+ var invokesMethod = (message) => "method" in message;
8699
9495
  function bodyIsCheckable(body) {
8700
- const messages = Array.isArray(body) ? body : [body];
8701
- return messages.some(looksLikeMcpMessage);
9496
+ return parseMcpMessages(body).some(invokesMethod);
8702
9497
  }
8703
9498
  function isJsonContentType(req) {
8704
9499
  const ct = req.headers["content-type"];
@@ -8736,17 +9531,76 @@ function earlyCredentialCheckSkip(req) {
8736
9531
  if (credentialCheckMayBeWrongEnvironment()) return "skip_env_mismatch";
8737
9532
  return null;
8738
9533
  }
9534
+ var isToolCall = (message) => CallToolRequestSchema2.safeParse(message).success;
9535
+ function prefersJsonResponse(messages) {
9536
+ return messages.length > 0 && !messages.some(isToolCall);
9537
+ }
9538
+ var DEFAULT_REQUEST_LOG_SAMPLE_RATE = 0.1;
9539
+ function requestLogSampleRate(env = process.env) {
9540
+ if (env.MCP_VERBOSE_LOGS === "true") return 1;
9541
+ const raw = env.MCP_REQUEST_LOG_SAMPLE_RATE;
9542
+ if (raw === void 0 || raw === "") return DEFAULT_REQUEST_LOG_SAMPLE_RATE;
9543
+ const parsed = Number(raw);
9544
+ if (!Number.isFinite(parsed)) return DEFAULT_REQUEST_LOG_SAMPLE_RATE;
9545
+ return Math.min(1, Math.max(0, parsed));
9546
+ }
9547
+ function summarizeRpc(messages) {
9548
+ const rpc = messages.map((message) => {
9549
+ const call = CallToolRequestSchema2.safeParse(message);
9550
+ if (call.success) return `tools/call:${call.data.params.name}`;
9551
+ return "method" in message ? message.method : "response";
9552
+ });
9553
+ const init = messages.map((message) => InitializeRequestSchema.safeParse(message)).find((result) => result.success);
9554
+ const client = init?.success ? `${init.data.params.clientInfo.name}/${init.data.params.clientInfo.version}` : void 0;
9555
+ return { rpc: rpc.length > 0 ? rpc.join(",") : void 0, client };
9556
+ }
9557
+ function header(req, name) {
9558
+ const value = req.headers[name];
9559
+ return Array.isArray(value) ? value[0] : value;
9560
+ }
9561
+ function clientAddress(req) {
9562
+ return header(req, "x-real-ip") || header(req, "x-forwarded-for")?.split(",")[0].trim() || req.socket?.remoteAddress || void 0;
9563
+ }
9564
+ function startRequestLog(req, res, bearerToken) {
9565
+ const sampleRate = requestLogSampleRate();
9566
+ if (Math.random() >= sampleRate) return { note() {
9567
+ } };
9568
+ const startedAt = Date.now();
9569
+ let summary = {};
9570
+ const address = clientAddress(req);
9571
+ const userAgent = header(req, "user-agent");
9572
+ let emitted = false;
9573
+ const emit = () => {
9574
+ if (emitted) return;
9575
+ emitted = true;
9576
+ console.log("mcp_request", {
9577
+ method: req.method,
9578
+ url: req.url,
9579
+ status: res.writableFinished ? res.statusCode : void 0,
9580
+ durationMs: Date.now() - startedAt,
9581
+ rpc: summary.rpc,
9582
+ client: summary.client,
9583
+ ua: userAgent?.slice(0, 160),
9584
+ caller: callerId(bearerToken ?? void 0),
9585
+ ip: address ? callerId(address) : void 0,
9586
+ sampleRate
9587
+ });
9588
+ };
9589
+ res.on("finish", emit);
9590
+ res.on("close", emit);
9591
+ return {
9592
+ note(next) {
9593
+ summary = next;
9594
+ }
9595
+ };
9596
+ }
8739
9597
  function logCredentialCheck(outcome) {
8740
9598
  if (outcome === "pass" && process.env.MCP_VERBOSE_LOGS !== "true") return;
8741
9599
  console.log("credential_check", { outcome });
8742
9600
  }
8743
9601
  async function handleMcpRequest(req, res, opts = {}) {
8744
9602
  const bearerToken = extractBearerToken(req);
8745
- console.log("mcp_request", {
8746
- method: req.method,
8747
- url: req.url,
8748
- hasBearerToken: !!bearerToken
8749
- });
9603
+ const requestLog = startRequestLog(req, res, bearerToken);
8750
9604
  if (req.method === "GET") {
8751
9605
  res.writeHead(405, {
8752
9606
  Allow: "POST, DELETE",
@@ -8775,9 +9629,7 @@ async function handleMcpRequest(req, res, opts = {}) {
8775
9629
  let body = req.body;
8776
9630
  let accountId;
8777
9631
  const early = earlyCredentialCheckSkip(req);
8778
- if (early) {
8779
- logCredentialCheck(early);
8780
- } else {
9632
+ if (!early) {
8781
9633
  const read = await readJsonBody(req);
8782
9634
  if (read === BODY_TOO_LARGE) {
8783
9635
  res.writeHead(413, { "Content-Type": "application/json" });
@@ -8785,26 +9637,47 @@ async function handleMcpRequest(req, res, opts = {}) {
8785
9637
  return;
8786
9638
  }
8787
9639
  body = read;
8788
- if (!bodyIsCheckable(body)) {
8789
- logCredentialCheck("skip_not_mcp_body");
8790
- } else {
8791
- const verify = opts.verifyCredential ?? defaultCredentialChecker.verify;
8792
- const verdict = await verify(bearerToken);
8793
- if (verdict.status === "valid") accountId = verdict.accountId;
8794
- logCredentialCheck(
8795
- verdict.status === "invalid" ? "reject" : verdict.status === "valid" ? "pass" : "fail_open"
9640
+ }
9641
+ const messages = parseMcpMessages(body);
9642
+ requestLog.note(summarizeRpc(messages));
9643
+ if (early) {
9644
+ logCredentialCheck(early);
9645
+ } else if (!messages.some(invokesMethod)) {
9646
+ logCredentialCheck("skip_not_mcp_body");
9647
+ } else {
9648
+ const verify = opts.verifyCredential ?? defaultCredentialChecker.verify;
9649
+ const verdict = await verify(bearerToken);
9650
+ if (verdict.status === "valid") accountId = verdict.accountId;
9651
+ logCredentialCheck(
9652
+ verdict.status === "invalid" ? "reject" : verdict.status === "valid" ? "pass" : "fail_open"
9653
+ );
9654
+ if (verdict.status === "invalid") {
9655
+ writeUnauthorized(
9656
+ req,
9657
+ res,
9658
+ `${verdict.reason} Re-authenticate to continue.`,
9659
+ true
8796
9660
  );
8797
- if (verdict.status === "invalid") {
8798
- writeUnauthorized(
8799
- req,
8800
- res,
8801
- `${verdict.reason} Re-authenticate to continue.`,
8802
- true
8803
- );
8804
- return;
8805
- }
9661
+ return;
8806
9662
  }
8807
9663
  }
9664
+ const notFoundUrl = process.env.ALP_SINK_URL ? notFoundSinkUrl(process.env.ALP_SINK_URL) : null;
9665
+ const notFoundLimiter = bearerToken && notFoundUrl && process.env.ALP_SINK_SECRET ? createNotFoundLimiter({
9666
+ key: analyticsCallerId(bearerToken, accountId),
9667
+ sinkUrl: notFoundUrl,
9668
+ secret: process.env.ALP_SINK_SECRET,
9669
+ // One usage event per block, unless this client opted out.
9670
+ onBlockStarted: analyticsOptedOut(req) ? void 0 : (tool, blockedUntil) => captureLoopBlocked(bearerToken, {
9671
+ tool,
9672
+ blockedForS: Math.round((blockedUntil - Date.now()) / 1e3),
9673
+ transport: "http",
9674
+ serverVersion: opts.serverVersion ?? SERVER_VERSION,
9675
+ clientName: sanitizeUaToken(
9676
+ String(req.headers["user-agent"] ?? "unknown")
9677
+ ),
9678
+ accountId
9679
+ })
9680
+ }) : null;
8808
9681
  const server = createSpecgenServer(
8809
9682
  createToolContext({
8810
9683
  apiKey: bearerToken,
@@ -8845,6 +9718,10 @@ async function handleMcpRequest(req, res, opts = {}) {
8845
9718
  harnessSource: "user_agent"
8846
9719
  }
8847
9720
  } : {},
9721
+ ...notFoundLimiter ? {
9722
+ rateLimiter: notFoundLimiter.rateLimiter,
9723
+ onToolResult: notFoundLimiter.onToolResult
9724
+ } : {},
8848
9725
  // A tool call that 401s proves a cached "valid" verdict wrong before
8849
9726
  // its TTL — drop it so the next request re-checks and re-auths.
8850
9727
  onUnauthorized: () => (opts.invalidateCredential ?? defaultCredentialChecker.invalidate)(
@@ -8853,8 +9730,9 @@ async function handleMcpRequest(req, res, opts = {}) {
8853
9730
  }
8854
9731
  );
8855
9732
  const transport = new StreamableHTTPServerTransport({
8856
- sessionIdGenerator: void 0
9733
+ sessionIdGenerator: void 0,
8857
9734
  // stateless — no session persistence
9735
+ enableJsonResponse: prefersJsonResponse(messages)
8858
9736
  });
8859
9737
  res.on("close", () => {
8860
9738
  transport.close().catch(() => {
@@ -8869,6 +9747,10 @@ export {
8869
9747
  bodyIsCheckable,
8870
9748
  defaultCredentialChecker,
8871
9749
  getBaseUrl,
8872
- handleMcpRequest
9750
+ handleMcpRequest,
9751
+ parseMcpMessages,
9752
+ prefersJsonResponse,
9753
+ requestLogSampleRate,
9754
+ summarizeRpc
8873
9755
  };
8874
9756
  //# sourceMappingURL=http.mjs.map