@runpod/mcp-server 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/README.md +173 -53
- package/dist/chunk-ZP3TCVXG.mjs +2116 -0
- package/dist/chunk-ZP3TCVXG.mjs.map +1 -0
- package/dist/http.cjs +1545 -589
- package/dist/http.cjs.map +1 -1
- package/dist/http.mjs +2 -2
- package/dist/stdio.cjs +1619 -593
- package/dist/stdio.cjs.map +1 -1
- package/dist/stdio.mjs +1619 -593
- package/dist/stdio.mjs.map +1 -1
- package/dist/tools.cjs +1545 -587
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.d.cts +51 -3
- package/dist/tools.d.ts +51 -3
- package/dist/tools.mjs +1 -1
- package/package.json +3 -2
- package/dist/chunk-D4AEYJ2O.mjs +0 -1160
- package/dist/chunk-D4AEYJ2O.mjs.map +0 -1
package/dist/tools.cjs
CHANGED
|
@@ -33,10 +33,549 @@ __export(tools_exports, {
|
|
|
33
33
|
registerTools: () => registerTools
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(tools_exports);
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
// src/tools/runtime.ts
|
|
37
38
|
var import_node_fetch = __toESM(require("node-fetch"), 1);
|
|
38
39
|
var import_node_crypto = require("crypto");
|
|
39
40
|
|
|
41
|
+
// src/_shared/http.ts
|
|
42
|
+
var HttpError = class extends Error {
|
|
43
|
+
constructor(prefix, status, body) {
|
|
44
|
+
super(`${prefix}: ${status} - ${body}`);
|
|
45
|
+
this.name = "HttpError";
|
|
46
|
+
this.status = status;
|
|
47
|
+
this.body = body;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function isJsonContentType(contentType) {
|
|
51
|
+
if (!contentType) return false;
|
|
52
|
+
const ct = contentType.toLowerCase();
|
|
53
|
+
return ct.includes("application/json") || ct.includes("+json");
|
|
54
|
+
}
|
|
55
|
+
function methodSendsBody(method) {
|
|
56
|
+
return method === "POST" || method === "PATCH" || method === "PUT";
|
|
57
|
+
}
|
|
58
|
+
function buildRequestHeaders(apiKey, tracking) {
|
|
59
|
+
return {
|
|
60
|
+
Authorization: `Bearer ${apiKey}`,
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
...tracking()
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function createHttpClient(deps) {
|
|
66
|
+
return async function request(url, method = "GET", body) {
|
|
67
|
+
const init = {
|
|
68
|
+
method,
|
|
69
|
+
headers: buildRequestHeaders(deps.apiKey, deps.tracking)
|
|
70
|
+
};
|
|
71
|
+
if (body && methodSendsBody(method)) {
|
|
72
|
+
init.body = JSON.stringify(body);
|
|
73
|
+
}
|
|
74
|
+
const response = await deps.fetch(url, init);
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
throw new HttpError(
|
|
77
|
+
deps.errorPrefix,
|
|
78
|
+
response.status,
|
|
79
|
+
await response.text()
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return isJsonContentType(response.headers.get("content-type")) ? response.json() : { success: true, status: response.status };
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/_shared/tracking.ts
|
|
87
|
+
function sanitizeUaToken(value) {
|
|
88
|
+
return value.replace(/[()<>@,;:\\"/[\]?={}\s]/g, "_").slice(0, 64);
|
|
89
|
+
}
|
|
90
|
+
function buildTrackingHeaders(input) {
|
|
91
|
+
const name = sanitizeUaToken(input.clientName || "unknown");
|
|
92
|
+
const version = sanitizeUaToken(input.clientVersion || "unknown");
|
|
93
|
+
const userAgent = `runpod-mcp-server/${input.serverVersion} (caller=mcp; client=${name}; client_version=${version}; transport=${input.transport})`;
|
|
94
|
+
return {
|
|
95
|
+
"User-Agent": userAgent,
|
|
96
|
+
"X-Runpod-Session-Id": input.sessionId
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/_shared/mappers.ts
|
|
101
|
+
function compact(obj) {
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
104
|
+
if (v !== void 0) out[k] = v;
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
function persistentMount(size, path) {
|
|
109
|
+
if (size === void 0 || path === void 0) return void 0;
|
|
110
|
+
return { persistent: { size, path } };
|
|
111
|
+
}
|
|
112
|
+
function containerConfigToV2(p) {
|
|
113
|
+
return compact({
|
|
114
|
+
image: p.imageName,
|
|
115
|
+
disk: p.containerDiskInGb,
|
|
116
|
+
ports: p.ports,
|
|
117
|
+
env: p.env,
|
|
118
|
+
mounts: persistentMount(p.volumeInGb, p.volumeMountPath)
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function gpuConfigToV2(gpuTypeIds, gpuCount) {
|
|
122
|
+
const id = gpuTypeIds?.[0];
|
|
123
|
+
if (id === void 0) return void 0;
|
|
124
|
+
return compact({ id, count: gpuCount });
|
|
125
|
+
}
|
|
126
|
+
function mapPodCreateToV2(params) {
|
|
127
|
+
return compact({
|
|
128
|
+
...containerConfigToV2(params),
|
|
129
|
+
name: params.name,
|
|
130
|
+
cloud: params.cloudType,
|
|
131
|
+
// v2 CreatePodRequest takes `dataCenterIds` as an ARRAY (preferred data
|
|
132
|
+
// centers; omit/empty = scheduler chooses). Pass the v1 list straight
|
|
133
|
+
// through — the earlier singular `dataCenter` key did not exist on the pod
|
|
134
|
+
// schema, so placement was being silently dropped. GPU, by contrast, IS
|
|
135
|
+
// singular on v2 (`gpu.id`): gpuConfigToV2 takes the first id; the
|
|
136
|
+
// create-pod handler warns when more than one gpuTypeId was supplied.
|
|
137
|
+
dataCenterIds: params.dataCenterIds?.length ? params.dataCenterIds : void 0,
|
|
138
|
+
gpu: gpuConfigToV2(params.gpuTypeIds, params.gpuCount)
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function mapPodUpdateToV2(params) {
|
|
142
|
+
return {
|
|
143
|
+
...containerConfigToV2(params),
|
|
144
|
+
...compact({ name: params.name })
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function nestedOrUndefined(obj) {
|
|
148
|
+
const c = compact(obj);
|
|
149
|
+
return Object.keys(c).length ? c : void 0;
|
|
150
|
+
}
|
|
151
|
+
function endpointGpuConfig(pools, count) {
|
|
152
|
+
if (!pools?.length) return void 0;
|
|
153
|
+
return compact({ pools, count });
|
|
154
|
+
}
|
|
155
|
+
function mapEndpointToV2(params) {
|
|
156
|
+
return compact({
|
|
157
|
+
name: params.name,
|
|
158
|
+
image: params.imageName,
|
|
159
|
+
args: params.args,
|
|
160
|
+
disk: params.containerDiskInGb,
|
|
161
|
+
ports: params.ports,
|
|
162
|
+
env: params.env,
|
|
163
|
+
registry: params.containerRegistryAuthId,
|
|
164
|
+
gpu: endpointGpuConfig(params.gpuPoolIds, params.gpuCount),
|
|
165
|
+
workers: nestedOrUndefined({
|
|
166
|
+
min: params.workersMin,
|
|
167
|
+
max: params.workersMax
|
|
168
|
+
}),
|
|
169
|
+
scaling: nestedOrUndefined({
|
|
170
|
+
type: params.scalerType,
|
|
171
|
+
value: params.scalerValue,
|
|
172
|
+
idleTimeout: params.idleTimeout
|
|
173
|
+
}),
|
|
174
|
+
dataCenterIds: params.dataCenterIds?.length ? params.dataCenterIds : void 0,
|
|
175
|
+
networkVolumes: params.networkVolumeIds?.length ? params.networkVolumeIds : void 0,
|
|
176
|
+
timeout: params.executionTimeoutMs,
|
|
177
|
+
flashboot: params.flashboot
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
var mapEndpointCreateToV2 = mapEndpointToV2;
|
|
181
|
+
var mapEndpointUpdateToV2 = mapEndpointToV2;
|
|
182
|
+
function mapNetworkVolumeCreateToV2(params) {
|
|
183
|
+
return compact({
|
|
184
|
+
name: params.name,
|
|
185
|
+
size: params.size,
|
|
186
|
+
dataCenter: params.dataCenterId
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function mapTemplateCreateToV2(params) {
|
|
190
|
+
return {
|
|
191
|
+
...containerConfigToV2(params),
|
|
192
|
+
...compact({
|
|
193
|
+
name: params.name,
|
|
194
|
+
serverless: params.isServerless,
|
|
195
|
+
registry: params.containerRegistryAuthId
|
|
196
|
+
}),
|
|
197
|
+
// Required by v2 — always emit, defaulting to NVIDIA.
|
|
198
|
+
category: params.category ?? "NVIDIA"
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function mapTemplateUpdateToV2(params) {
|
|
202
|
+
return {
|
|
203
|
+
...containerConfigToV2(params),
|
|
204
|
+
...compact({
|
|
205
|
+
name: params.name,
|
|
206
|
+
registry: params.containerRegistryAuthId
|
|
207
|
+
})
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/_shared/backend.ts
|
|
212
|
+
var V1_ONLY = /* @__PURE__ */ new Set(["jobs"]);
|
|
213
|
+
var V2_UNWRAP_KEY = {
|
|
214
|
+
pods: "pods",
|
|
215
|
+
templates: "templates",
|
|
216
|
+
networkVolumes: "networkVolumes",
|
|
217
|
+
registries: "registries",
|
|
218
|
+
gpus: "gpus",
|
|
219
|
+
cpus: "cpus",
|
|
220
|
+
dataCenters: "dataCenters",
|
|
221
|
+
endpoints: "endpoints",
|
|
222
|
+
jobs: "jobs",
|
|
223
|
+
tags: "tags",
|
|
224
|
+
workers: "workers",
|
|
225
|
+
billing: "records"
|
|
226
|
+
};
|
|
227
|
+
var RESOURCE_ENV_KEY = {
|
|
228
|
+
pods: "PODS",
|
|
229
|
+
templates: "TEMPLATES",
|
|
230
|
+
networkVolumes: "NETWORK_VOLUMES",
|
|
231
|
+
registries: "REGISTRIES",
|
|
232
|
+
gpus: "GPUS",
|
|
233
|
+
cpus: "CPUS",
|
|
234
|
+
dataCenters: "DATA_CENTERS",
|
|
235
|
+
endpoints: "ENDPOINTS",
|
|
236
|
+
jobs: "JOBS",
|
|
237
|
+
tags: "TAGS",
|
|
238
|
+
workers: "WORKERS",
|
|
239
|
+
billing: "BILLING"
|
|
240
|
+
};
|
|
241
|
+
function restV1Base(env) {
|
|
242
|
+
return env.RUNPOD_REST_API_URL ?? "https://rest.runpod.io/v1";
|
|
243
|
+
}
|
|
244
|
+
function restV2Base(env) {
|
|
245
|
+
return env.RUNPOD_REST_V2_API_URL ?? "https://v2-rest.runpod.io/v2";
|
|
246
|
+
}
|
|
247
|
+
function serverlessBase(env) {
|
|
248
|
+
return env.RUNPOD_SERVERLESS_API_URL ?? "https://api.runpod.ai/v2";
|
|
249
|
+
}
|
|
250
|
+
function publicGraphqlBase(env) {
|
|
251
|
+
return env.RUNPOD_PUBLIC_GRAPHQL_URL ?? "https://api.runpod.io/graphql";
|
|
252
|
+
}
|
|
253
|
+
function unwrapList(resource, version, raw) {
|
|
254
|
+
if (version === "v1") return Array.isArray(raw) ? raw : [];
|
|
255
|
+
if (raw === null || typeof raw !== "object") return [];
|
|
256
|
+
const arr = raw[V2_UNWRAP_KEY[resource]];
|
|
257
|
+
return Array.isArray(arr) ? arr : [];
|
|
258
|
+
}
|
|
259
|
+
function resolveVersion(opts) {
|
|
260
|
+
const { resource, env, ctx, v2Available } = opts;
|
|
261
|
+
if (V1_ONLY.has(resource)) return "v1";
|
|
262
|
+
const perResource = env[`RUNPOD_REST_VERSION_${RESOURCE_ENV_KEY[resource]}`];
|
|
263
|
+
const setting = (perResource ?? env.RUNPOD_REST_VERSION ?? "v2").toLowerCase();
|
|
264
|
+
if (setting === "v2") return "v2";
|
|
265
|
+
if (setting === "auto") {
|
|
266
|
+
if (ctx.transport === "http") return "v1";
|
|
267
|
+
return v2Available === true ? "v2" : "v1";
|
|
268
|
+
}
|
|
269
|
+
return "v1";
|
|
270
|
+
}
|
|
271
|
+
var identity = (body) => body;
|
|
272
|
+
var V1_REST_PATHS = {
|
|
273
|
+
pods: { list: "/pods", get: (id) => `/pods/${id}` },
|
|
274
|
+
templates: { list: "/templates", get: (id) => `/templates/${id}` },
|
|
275
|
+
networkVolumes: {
|
|
276
|
+
list: "/networkvolumes",
|
|
277
|
+
get: (id) => `/networkvolumes/${id}`
|
|
278
|
+
},
|
|
279
|
+
registries: {
|
|
280
|
+
list: "/containerregistryauth",
|
|
281
|
+
get: (id) => `/containerregistryauth/${id}`
|
|
282
|
+
},
|
|
283
|
+
endpoints: { list: "/endpoints", get: (id) => `/endpoints/${id}` }
|
|
284
|
+
};
|
|
285
|
+
var CATALOG = /* @__PURE__ */ new Set([
|
|
286
|
+
"gpus",
|
|
287
|
+
"cpus",
|
|
288
|
+
"dataCenters"
|
|
289
|
+
]);
|
|
290
|
+
var V2_ONLY = /* @__PURE__ */ new Set([
|
|
291
|
+
"tags",
|
|
292
|
+
"workers",
|
|
293
|
+
"billing"
|
|
294
|
+
]);
|
|
295
|
+
var V2_REST_PATHS = {
|
|
296
|
+
pods: { list: "/pods", get: (id) => `/pods/${id}` },
|
|
297
|
+
templates: { list: "/templates", get: (id) => `/templates/${id}` },
|
|
298
|
+
networkVolumes: {
|
|
299
|
+
list: "/network-volumes",
|
|
300
|
+
get: (id) => `/network-volumes/${id}`
|
|
301
|
+
},
|
|
302
|
+
registries: { list: "/registries", get: (id) => `/registries/${id}` },
|
|
303
|
+
gpus: { list: "/catalog/gpus", get: (id) => `/catalog/gpus/${id}` },
|
|
304
|
+
cpus: { list: "/catalog/cpus", get: (id) => `/catalog/cpus/${id}` },
|
|
305
|
+
dataCenters: {
|
|
306
|
+
list: "/catalog/datacenters",
|
|
307
|
+
get: (id) => `/catalog/datacenters/${id}`
|
|
308
|
+
},
|
|
309
|
+
tags: { list: "/tags", get: (id) => `/tags/${id}` },
|
|
310
|
+
// Endpoints live under /v2/serverless on v2 (NOT /v2/endpoints). The v2 list
|
|
311
|
+
// response wraps rows under `endpoints` (see V2_UNWRAP_KEY), so the existing
|
|
312
|
+
// unwrap key stays correct.
|
|
313
|
+
endpoints: { list: "/serverless", get: (id) => `/serverless/${id}` },
|
|
314
|
+
// workers/billing build their own sub-paths in the tool from `base`; the
|
|
315
|
+
// list entry just satisfies buildV2Backend (which requires a paths entry).
|
|
316
|
+
workers: { list: "/serverless" },
|
|
317
|
+
billing: { list: "/billing" }
|
|
318
|
+
};
|
|
319
|
+
var V2_MAPPERS = {
|
|
320
|
+
pods: {
|
|
321
|
+
create: (b) => mapPodCreateToV2(b),
|
|
322
|
+
update: (b) => mapPodUpdateToV2(b)
|
|
323
|
+
},
|
|
324
|
+
templates: {
|
|
325
|
+
create: (b) => mapTemplateCreateToV2(b),
|
|
326
|
+
update: (b) => mapTemplateUpdateToV2(b)
|
|
327
|
+
},
|
|
328
|
+
networkVolumes: {
|
|
329
|
+
create: (b) => mapNetworkVolumeCreateToV2(
|
|
330
|
+
b
|
|
331
|
+
),
|
|
332
|
+
update: identity
|
|
333
|
+
},
|
|
334
|
+
registries: { create: identity, update: identity },
|
|
335
|
+
endpoints: {
|
|
336
|
+
create: (b) => mapEndpointCreateToV2(b),
|
|
337
|
+
update: (b) => mapEndpointUpdateToV2(b)
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
function buildV2Backend(resource, env, unwrap) {
|
|
341
|
+
const paths = V2_REST_PATHS[resource];
|
|
342
|
+
if (!paths) throw new Error(`no v2 REST paths for resource "${resource}"`);
|
|
343
|
+
const mappers = V2_MAPPERS[resource];
|
|
344
|
+
return {
|
|
345
|
+
kind: "rest",
|
|
346
|
+
version: "v2",
|
|
347
|
+
base: restV2Base(env),
|
|
348
|
+
list: paths.list,
|
|
349
|
+
get: paths.get,
|
|
350
|
+
unwrap,
|
|
351
|
+
mapCreate: mappers?.create ?? identity,
|
|
352
|
+
mapUpdate: mappers?.update ?? identity
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function buildV1Backend(resource, env, unwrap) {
|
|
356
|
+
if (resource === "jobs") {
|
|
357
|
+
return {
|
|
358
|
+
kind: "rest",
|
|
359
|
+
version: "v1",
|
|
360
|
+
base: serverlessBase(env),
|
|
361
|
+
unwrap,
|
|
362
|
+
mapCreate: identity,
|
|
363
|
+
mapUpdate: identity
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
if (V2_ONLY.has(resource)) {
|
|
367
|
+
return {
|
|
368
|
+
kind: "rest",
|
|
369
|
+
version: "v1",
|
|
370
|
+
base: restV1Base(env),
|
|
371
|
+
unwrap,
|
|
372
|
+
mapCreate: identity,
|
|
373
|
+
mapUpdate: identity
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (CATALOG.has(resource)) {
|
|
377
|
+
return {
|
|
378
|
+
kind: "graphql",
|
|
379
|
+
version: "v1",
|
|
380
|
+
base: publicGraphqlBase(env),
|
|
381
|
+
unwrap,
|
|
382
|
+
mapCreate: identity,
|
|
383
|
+
mapUpdate: identity
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
const paths = V1_REST_PATHS[resource];
|
|
387
|
+
if (!paths) throw new Error(`no v1 REST paths for resource "${resource}"`);
|
|
388
|
+
return {
|
|
389
|
+
kind: "rest",
|
|
390
|
+
version: "v1",
|
|
391
|
+
base: restV1Base(env),
|
|
392
|
+
list: paths.list,
|
|
393
|
+
get: paths.get,
|
|
394
|
+
unwrap,
|
|
395
|
+
mapCreate: identity,
|
|
396
|
+
mapUpdate: identity
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function resolveBackend(opts) {
|
|
400
|
+
const { resource, env } = opts;
|
|
401
|
+
const version = resolveVersion(opts);
|
|
402
|
+
const unwrap = (raw) => unwrapList(resource, version, raw);
|
|
403
|
+
return version === "v2" ? buildV2Backend(resource, env, unwrap) : buildV1Backend(resource, env, unwrap);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/tools/runtime.ts
|
|
407
|
+
var MCP_SERVER_VERSION = true ? "2.0.0" : "dev";
|
|
408
|
+
var SESSION_ID = (0, import_node_crypto.randomUUID)();
|
|
409
|
+
function trackingHeaders(server, ctx) {
|
|
410
|
+
const info = server.server.getClientVersion();
|
|
411
|
+
return buildTrackingHeaders({
|
|
412
|
+
clientName: info?.name || ctx.clientUserAgent || "unknown",
|
|
413
|
+
clientVersion: info?.version || "unknown",
|
|
414
|
+
transport: ctx.transport,
|
|
415
|
+
// Prefer a server version supplied by the caller (the hosted entrypoint reads
|
|
416
|
+
// it from package.json at runtime, since tsup's build-time define does not
|
|
417
|
+
// run when Vercel compiles api/index.ts). Fall back to the build-time value.
|
|
418
|
+
serverVersion: ctx.serverVersion || MCP_SERVER_VERSION,
|
|
419
|
+
sessionId: SESSION_ID
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
var defaultFetch = import_node_fetch.default;
|
|
423
|
+
async function withApiErrorLog(label, call) {
|
|
424
|
+
try {
|
|
425
|
+
return await call();
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.error(label, error);
|
|
428
|
+
throw error;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function jsonReply(result) {
|
|
432
|
+
return {
|
|
433
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
var READ_ONLY = { readOnlyHint: true, openWorldHint: true };
|
|
437
|
+
var WRITE = { readOnlyHint: false, openWorldHint: true };
|
|
438
|
+
var DESTRUCTIVE = {
|
|
439
|
+
readOnlyHint: false,
|
|
440
|
+
destructiveHint: true,
|
|
441
|
+
idempotentHint: true,
|
|
442
|
+
openWorldHint: true
|
|
443
|
+
};
|
|
444
|
+
async function graphqlRequest(query, tracking, fetchImpl, url) {
|
|
445
|
+
const response = await fetchImpl(url, {
|
|
446
|
+
method: "POST",
|
|
447
|
+
headers: {
|
|
448
|
+
"Content-Type": "application/json",
|
|
449
|
+
...tracking()
|
|
450
|
+
},
|
|
451
|
+
body: JSON.stringify({ query })
|
|
452
|
+
});
|
|
453
|
+
const result = await response.json();
|
|
454
|
+
if (result.errors && result.errors.length > 0) {
|
|
455
|
+
throw new Error(
|
|
456
|
+
`GraphQL Error: ${result.errors.map((e) => e.message).join(", ")}`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
if (!result.data) {
|
|
460
|
+
throw new Error("No data returned from GraphQL query");
|
|
461
|
+
}
|
|
462
|
+
return result.data;
|
|
463
|
+
}
|
|
464
|
+
function createToolRuntime(server, ctx, deps = {}) {
|
|
465
|
+
const tracking = () => trackingHeaders(server, ctx);
|
|
466
|
+
const httpFetch = deps.fetch ?? defaultFetch;
|
|
467
|
+
const v1Client = createHttpClient({
|
|
468
|
+
apiKey: ctx.apiKey,
|
|
469
|
+
fetch: httpFetch,
|
|
470
|
+
tracking,
|
|
471
|
+
errorPrefix: "Runpod API Error"
|
|
472
|
+
});
|
|
473
|
+
const runpodRequest = (endpoint, method = "GET", body) => withApiErrorLog(
|
|
474
|
+
"Error calling Runpod API:",
|
|
475
|
+
() => v1Client(`${restV1Base(process.env)}${endpoint}`, method, body)
|
|
476
|
+
);
|
|
477
|
+
const serverlessClient = createHttpClient({
|
|
478
|
+
apiKey: ctx.apiKey,
|
|
479
|
+
fetch: httpFetch,
|
|
480
|
+
tracking,
|
|
481
|
+
errorPrefix: "Runpod Serverless API Error"
|
|
482
|
+
});
|
|
483
|
+
const serverlessRequest = (endpointId, path, method = "GET", body) => withApiErrorLog(
|
|
484
|
+
"Error calling Runpod Serverless API:",
|
|
485
|
+
() => serverlessClient(
|
|
486
|
+
`${serverlessBase(process.env)}/${endpointId}${path}`,
|
|
487
|
+
method,
|
|
488
|
+
body
|
|
489
|
+
)
|
|
490
|
+
);
|
|
491
|
+
const restClient = createHttpClient({
|
|
492
|
+
apiKey: ctx.apiKey,
|
|
493
|
+
fetch: httpFetch,
|
|
494
|
+
tracking,
|
|
495
|
+
errorPrefix: "Runpod API Error"
|
|
496
|
+
});
|
|
497
|
+
const callRestUrl = (url, method = "GET", body) => withApiErrorLog(
|
|
498
|
+
"Error calling Runpod API:",
|
|
499
|
+
() => restClient(url, method, body)
|
|
500
|
+
);
|
|
501
|
+
const streamSse = deps.streamSse ?? (async (url, opts) => {
|
|
502
|
+
const controller = new AbortController();
|
|
503
|
+
const timer = setTimeout(() => controller.abort(), opts.maxWaitMs);
|
|
504
|
+
try {
|
|
505
|
+
const res = await (0, import_node_fetch.default)(url, {
|
|
506
|
+
method: "GET",
|
|
507
|
+
headers: {
|
|
508
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
509
|
+
Accept: "text/event-stream",
|
|
510
|
+
...tracking()
|
|
511
|
+
},
|
|
512
|
+
signal: controller.signal
|
|
513
|
+
});
|
|
514
|
+
if (!res.ok) {
|
|
515
|
+
throw new HttpError(
|
|
516
|
+
"Runpod API Error",
|
|
517
|
+
res.status,
|
|
518
|
+
await res.text().catch(() => "")
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
const chunks = [];
|
|
522
|
+
let bytes = 0;
|
|
523
|
+
let truncated = false;
|
|
524
|
+
try {
|
|
525
|
+
for await (const chunk of res.body) {
|
|
526
|
+
chunks.push(chunk);
|
|
527
|
+
bytes += chunk.length;
|
|
528
|
+
if (bytes >= opts.maxBytes) {
|
|
529
|
+
truncated = true;
|
|
530
|
+
controller.abort();
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
} catch (err) {
|
|
535
|
+
if (!(err instanceof Error && err.name === "AbortError")) throw err;
|
|
536
|
+
}
|
|
537
|
+
return { raw: Buffer.concat(chunks).toString("utf8"), truncated };
|
|
538
|
+
} finally {
|
|
539
|
+
clearTimeout(timer);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
const backendFor = (resource) => resolveBackend({
|
|
543
|
+
resource,
|
|
544
|
+
env: process.env,
|
|
545
|
+
ctx: { transport: ctx.transport },
|
|
546
|
+
v2Available: deps.v2Available
|
|
547
|
+
});
|
|
548
|
+
const podAction = async (podId, action) => {
|
|
549
|
+
const backend = backendFor("pods");
|
|
550
|
+
const podPath = backend.get(podId);
|
|
551
|
+
if (backend.version === "v2") {
|
|
552
|
+
return callRestUrl(`${backend.base}${podPath}/action`, "POST", {
|
|
553
|
+
action
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
return callRestUrl(`${backend.base}${podPath}/${action}`, "POST");
|
|
557
|
+
};
|
|
558
|
+
return {
|
|
559
|
+
jsonReply,
|
|
560
|
+
graphql: (query) => graphqlRequest(
|
|
561
|
+
query,
|
|
562
|
+
tracking,
|
|
563
|
+
httpFetch,
|
|
564
|
+
publicGraphqlBase(process.env)
|
|
565
|
+
),
|
|
566
|
+
runpodRequest,
|
|
567
|
+
serverlessRequest,
|
|
568
|
+
callRestUrl,
|
|
569
|
+
backendFor,
|
|
570
|
+
streamSse,
|
|
571
|
+
podAction,
|
|
572
|
+
env: process.env
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// src/tools/catalog.ts
|
|
577
|
+
var import_zod2 = require("zod");
|
|
578
|
+
|
|
40
579
|
// src/pagination.ts
|
|
41
580
|
var import_zod = require("zod");
|
|
42
581
|
var DEFAULT_LIST_LIMIT = 20;
|
|
@@ -62,7 +601,7 @@ function decodeCursorOffset(cursor) {
|
|
|
62
601
|
function encodeCursorOffset(offset) {
|
|
63
602
|
return Buffer.from(String(offset), "utf8").toString("base64");
|
|
64
603
|
}
|
|
65
|
-
function capListResult(result, options) {
|
|
604
|
+
function capListResult(result, options, extra) {
|
|
66
605
|
const limit = Math.min(options.limit ?? DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT);
|
|
67
606
|
const offset = decodeCursorOffset(options.cursor);
|
|
68
607
|
if (!Array.isArray(result)) {
|
|
@@ -76,7 +615,11 @@ function capListResult(result, options) {
|
|
|
76
615
|
const page = result.slice(offset, offset + limit);
|
|
77
616
|
const nextOffset = offset + page.length;
|
|
78
617
|
const hasMore = nextOffset < total;
|
|
618
|
+
const { items: _i, pagination: _p, ...safeExtra } = extra ?? {};
|
|
619
|
+
void _i;
|
|
620
|
+
void _p;
|
|
79
621
|
const envelope = {
|
|
622
|
+
...safeExtra,
|
|
80
623
|
items: page,
|
|
81
624
|
pagination: {
|
|
82
625
|
total,
|
|
@@ -94,120 +637,14 @@ function capListResult(result, options) {
|
|
|
94
637
|
};
|
|
95
638
|
}
|
|
96
639
|
|
|
97
|
-
// src/tools.ts
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
var GRAPHQL_URL = process.env.RUNPOD_PUBLIC_GRAPHQL_URL ?? "https://api.runpod.io/graphql";
|
|
101
|
-
var MCP_SERVER_VERSION = true ? "1.4.0" : "dev";
|
|
102
|
-
var SESSION_ID = (0, import_node_crypto.randomUUID)();
|
|
103
|
-
function sanitizeUaToken(value) {
|
|
104
|
-
return value.replace(/[()<>@,;:\\"/[\]?={}\s]/g, "_").slice(0, 64);
|
|
105
|
-
}
|
|
106
|
-
function trackingHeaders(server, ctx) {
|
|
107
|
-
const info = server.server.getClientVersion();
|
|
108
|
-
const fallbackName = ctx.clientUserAgent || "unknown";
|
|
109
|
-
const name = sanitizeUaToken(info?.name || fallbackName);
|
|
110
|
-
const version = sanitizeUaToken(info?.version || "unknown");
|
|
111
|
-
const serverVersion = ctx.serverVersion || MCP_SERVER_VERSION;
|
|
112
|
-
const userAgent = `runpod-mcp-server/${serverVersion} (caller=mcp; client=${name}; client_version=${version}; transport=${ctx.transport})`;
|
|
113
|
-
return {
|
|
114
|
-
"User-Agent": userAgent,
|
|
115
|
-
"X-Runpod-Session-Id": SESSION_ID
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
async function graphqlRequest(query, tracking) {
|
|
119
|
-
const response = await (0, import_node_fetch.default)(GRAPHQL_URL, {
|
|
120
|
-
method: "POST",
|
|
121
|
-
headers: {
|
|
122
|
-
"Content-Type": "application/json",
|
|
123
|
-
...tracking()
|
|
124
|
-
},
|
|
125
|
-
body: JSON.stringify({ query })
|
|
126
|
-
});
|
|
127
|
-
const result = await response.json();
|
|
128
|
-
if (result.errors && result.errors.length > 0) {
|
|
129
|
-
throw new Error(
|
|
130
|
-
`GraphQL Error: ${result.errors.map((e) => e.message).join(", ")}`
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
if (!result.data) {
|
|
134
|
-
throw new Error("No data returned from GraphQL query");
|
|
135
|
-
}
|
|
136
|
-
return result.data;
|
|
137
|
-
}
|
|
138
|
-
function createRunpodRequest(apiKey, tracking) {
|
|
139
|
-
return async function runpodRequest(endpoint, method = "GET", body) {
|
|
140
|
-
const url = `${API_BASE_URL}${endpoint}`;
|
|
141
|
-
const headers = {
|
|
142
|
-
Authorization: `Bearer ${apiKey}`,
|
|
143
|
-
"Content-Type": "application/json",
|
|
144
|
-
...tracking()
|
|
145
|
-
};
|
|
146
|
-
const options = {
|
|
147
|
-
method,
|
|
148
|
-
headers
|
|
149
|
-
};
|
|
150
|
-
if (body && (method === "POST" || method === "PATCH")) {
|
|
151
|
-
options.body = JSON.stringify(body);
|
|
152
|
-
}
|
|
153
|
-
try {
|
|
154
|
-
const response = await (0, import_node_fetch.default)(url, options);
|
|
155
|
-
if (!response.ok) {
|
|
156
|
-
const errorText = await response.text();
|
|
157
|
-
throw new Error(`Runpod API Error: ${response.status} - ${errorText}`);
|
|
158
|
-
}
|
|
159
|
-
const contentType = response.headers.get("content-type");
|
|
160
|
-
if (contentType && contentType.includes("application/json")) {
|
|
161
|
-
return await response.json();
|
|
162
|
-
}
|
|
163
|
-
return { success: true, status: response.status };
|
|
164
|
-
} catch (error) {
|
|
165
|
-
console.error("Error calling Runpod API:", error);
|
|
166
|
-
throw error;
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
function createServerlessRequest(apiKey, tracking) {
|
|
171
|
-
return async function serverlessRequest(endpointId, path, method = "GET", body) {
|
|
172
|
-
const url = `${SERVERLESS_API_BASE_URL}/${endpointId}${path}`;
|
|
173
|
-
const headers = {
|
|
174
|
-
Authorization: `Bearer ${apiKey}`,
|
|
175
|
-
"Content-Type": "application/json",
|
|
176
|
-
...tracking()
|
|
177
|
-
};
|
|
178
|
-
const options = {
|
|
179
|
-
method,
|
|
180
|
-
headers
|
|
181
|
-
};
|
|
182
|
-
if (body && (method === "POST" || method === "PATCH")) {
|
|
183
|
-
options.body = JSON.stringify(body);
|
|
184
|
-
}
|
|
185
|
-
try {
|
|
186
|
-
const response = await (0, import_node_fetch.default)(url, options);
|
|
187
|
-
if (!response.ok) {
|
|
188
|
-
const errorText = await response.text();
|
|
189
|
-
throw new Error(
|
|
190
|
-
`Runpod Serverless API Error: ${response.status} - ${errorText}`
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
const contentType = response.headers.get("content-type");
|
|
194
|
-
if (contentType && contentType.includes("application/json")) {
|
|
195
|
-
return await response.json();
|
|
196
|
-
}
|
|
197
|
-
return { success: true, status: response.status };
|
|
198
|
-
} catch (error) {
|
|
199
|
-
console.error("Error calling Runpod Serverless API:", error);
|
|
200
|
-
throw error;
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
function registerTools(server, ctx) {
|
|
205
|
-
const tracking = () => trackingHeaders(server, ctx);
|
|
206
|
-
const runpodRequest = createRunpodRequest(ctx.apiKey, tracking);
|
|
207
|
-
const serverlessRequest = createServerlessRequest(ctx.apiKey, tracking);
|
|
640
|
+
// src/tools/catalog.ts
|
|
641
|
+
function registerCatalogTools(server, rt) {
|
|
642
|
+
const { jsonReply: jsonReply2, graphql, callRestUrl, backendFor } = rt;
|
|
208
643
|
server.tool(
|
|
209
644
|
"list-gpu-types",
|
|
645
|
+
"List available GPU types with stock/pricing and capability filters (minimum VRAM, secure/community cloud, name search). Use this to discover valid gpuTypeIds before creating a pod or endpoint. On v2 the realtime stock filter is a no-op.",
|
|
210
646
|
{
|
|
647
|
+
...listPaginationParams,
|
|
211
648
|
minMemoryGb: import_zod2.z.number().optional().describe("Filter to GPUs with at least this much VRAM in GB"),
|
|
212
649
|
secureCloudOnly: import_zod2.z.boolean().optional().describe("Filter to only GPUs available in secure cloud"),
|
|
213
650
|
communityCloudOnly: import_zod2.z.boolean().optional().describe("Filter to only GPUs available in community cloud"),
|
|
@@ -218,9 +655,30 @@ function registerTools(server, ctx) {
|
|
|
218
655
|
"Include GPUs that are currently out of stock. Default is false"
|
|
219
656
|
)
|
|
220
657
|
},
|
|
658
|
+
{ title: "List GPU types", ...READ_ONLY },
|
|
221
659
|
async (params) => {
|
|
222
|
-
const
|
|
223
|
-
|
|
660
|
+
const backend = backendFor("gpus");
|
|
661
|
+
if (backend.version === "v2") {
|
|
662
|
+
const raw = await callRestUrl(`${backend.base}${backend.list}`);
|
|
663
|
+
let gpus = backend.unwrap(raw);
|
|
664
|
+
if (params.minMemoryGb !== void 0)
|
|
665
|
+
gpus = gpus.filter(
|
|
666
|
+
(g) => Number(g.memory ?? 0) >= params.minMemoryGb
|
|
667
|
+
);
|
|
668
|
+
if (params.secureCloudOnly) gpus = gpus.filter((g) => g.secure);
|
|
669
|
+
if (params.communityCloudOnly) gpus = gpus.filter((g) => g.community);
|
|
670
|
+
if (params.searchTerm) {
|
|
671
|
+
const t = params.searchTerm.toLowerCase();
|
|
672
|
+
gpus = gpus.filter(
|
|
673
|
+
(g) => String(g.id ?? "").toLowerCase().includes(t) || String(g.name ?? "").toLowerCase().includes(t)
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
return capListResult(gpus, {
|
|
677
|
+
limit: params.limit,
|
|
678
|
+
cursor: params.cursor
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
const data = await graphql(`
|
|
224
682
|
query {
|
|
225
683
|
gpuTypes {
|
|
226
684
|
id
|
|
@@ -233,9 +691,7 @@ function registerTools(server, ctx) {
|
|
|
233
691
|
}
|
|
234
692
|
}
|
|
235
693
|
}
|
|
236
|
-
|
|
237
|
-
tracking
|
|
238
|
-
);
|
|
694
|
+
`);
|
|
239
695
|
const stockPriority = {
|
|
240
696
|
High: 3,
|
|
241
697
|
Medium: 2,
|
|
@@ -281,26 +737,39 @@ function registerTools(server, ctx) {
|
|
|
281
737
|
stockStatus: gpu.lowestPrice?.stockStatus || "unavailable",
|
|
282
738
|
available: isAvailable(gpu)
|
|
283
739
|
}));
|
|
284
|
-
return {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
text: JSON.stringify(result, null, 2)
|
|
289
|
-
}
|
|
290
|
-
]
|
|
291
|
-
};
|
|
740
|
+
return capListResult(result, {
|
|
741
|
+
limit: params.limit,
|
|
742
|
+
cursor: params.cursor
|
|
743
|
+
});
|
|
292
744
|
}
|
|
293
745
|
);
|
|
294
746
|
server.tool(
|
|
295
747
|
"list-data-centers",
|
|
748
|
+
"List Runpod data centers (id, name, region/location). Use this to discover valid dataCenterIds for placing pods, endpoints, or network volumes.",
|
|
296
749
|
{
|
|
750
|
+
...listPaginationParams,
|
|
297
751
|
region: import_zod2.z.string().optional().describe(
|
|
298
752
|
"Filter by region/location (e.g., 'United States', 'Europe', 'Canada')"
|
|
299
753
|
)
|
|
300
754
|
},
|
|
755
|
+
{ title: "List data centers", ...READ_ONLY },
|
|
301
756
|
async (params) => {
|
|
302
|
-
const
|
|
303
|
-
|
|
757
|
+
const backend = backendFor("dataCenters");
|
|
758
|
+
if (backend.version === "v2") {
|
|
759
|
+
const raw = await callRestUrl(`${backend.base}${backend.list}`);
|
|
760
|
+
let dcs = backend.unwrap(raw);
|
|
761
|
+
if (params.region) {
|
|
762
|
+
const t = params.region.toLowerCase();
|
|
763
|
+
dcs = dcs.filter(
|
|
764
|
+
(dc) => String(dc.region ?? "").toLowerCase().includes(t)
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
return capListResult(dcs, {
|
|
768
|
+
limit: params.limit,
|
|
769
|
+
cursor: params.cursor
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
const data = await graphql(`
|
|
304
773
|
query {
|
|
305
774
|
dataCenters {
|
|
306
775
|
id
|
|
@@ -308,9 +777,7 @@ function registerTools(server, ctx) {
|
|
|
308
777
|
location
|
|
309
778
|
}
|
|
310
779
|
}
|
|
311
|
-
|
|
312
|
-
tracking
|
|
313
|
-
);
|
|
780
|
+
`);
|
|
314
781
|
let dataCenters = data.dataCenters;
|
|
315
782
|
if (params.region) {
|
|
316
783
|
const term = params.region.toLowerCase();
|
|
@@ -318,50 +785,146 @@ function registerTools(server, ctx) {
|
|
|
318
785
|
(dc) => dc.location.toLowerCase().includes(term)
|
|
319
786
|
);
|
|
320
787
|
}
|
|
321
|
-
return {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
text: JSON.stringify(dataCenters, null, 2)
|
|
326
|
-
}
|
|
327
|
-
]
|
|
328
|
-
};
|
|
788
|
+
return capListResult(dataCenters, {
|
|
789
|
+
limit: params.limit,
|
|
790
|
+
cursor: params.cursor
|
|
791
|
+
});
|
|
329
792
|
}
|
|
330
793
|
);
|
|
794
|
+
server.tool(
|
|
795
|
+
"list-cpu-types",
|
|
796
|
+
"List available CPU flavor types for CPU pods/endpoints. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
797
|
+
{ ...listPaginationParams },
|
|
798
|
+
{ title: "List CPU types", ...READ_ONLY },
|
|
799
|
+
async (params) => {
|
|
800
|
+
const backend = backendFor("cpus");
|
|
801
|
+
if (backend.version === "v1") {
|
|
802
|
+
return jsonReply2({
|
|
803
|
+
error: "list-cpu-types is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
|
|
804
|
+
status: 501
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
const raw = await callRestUrl(`${backend.base}${backend.list}`);
|
|
808
|
+
return capListResult(backend.unwrap(raw), {
|
|
809
|
+
limit: params.limit,
|
|
810
|
+
cursor: params.cursor
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
);
|
|
814
|
+
server.tool(
|
|
815
|
+
"get-gpu-type",
|
|
816
|
+
"Get details for a single GPU type by id. v2-only \u2014 returns a 501 notice on the v1 API (use list-gpu-types there).",
|
|
817
|
+
{
|
|
818
|
+
gpuTypeId: import_zod2.z.string().describe("ID of the GPU type to retrieve")
|
|
819
|
+
},
|
|
820
|
+
{ title: "Get GPU type", ...READ_ONLY },
|
|
821
|
+
async (params) => {
|
|
822
|
+
const backend = backendFor("gpus");
|
|
823
|
+
if (backend.version === "v1") {
|
|
824
|
+
return jsonReply2({
|
|
825
|
+
error: "get-gpu-type is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2 (or use list-gpu-types on v1).",
|
|
826
|
+
status: 501
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
const result = await callRestUrl(
|
|
830
|
+
`${backend.base}${backend.get(params.gpuTypeId)}`
|
|
831
|
+
);
|
|
832
|
+
return jsonReply2(result);
|
|
833
|
+
}
|
|
834
|
+
);
|
|
835
|
+
server.tool(
|
|
836
|
+
"get-cpu-type",
|
|
837
|
+
"Get details for a single CPU flavor type by id. v2-only \u2014 returns a 501 notice on the v1 API (use list-cpu-types there).",
|
|
838
|
+
{
|
|
839
|
+
cpuTypeId: import_zod2.z.string().describe("ID of the CPU type to retrieve")
|
|
840
|
+
},
|
|
841
|
+
{ title: "Get CPU type", ...READ_ONLY },
|
|
842
|
+
async (params) => {
|
|
843
|
+
const backend = backendFor("cpus");
|
|
844
|
+
if (backend.version === "v1") {
|
|
845
|
+
return jsonReply2({
|
|
846
|
+
error: "get-cpu-type is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
|
|
847
|
+
status: 501
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
const result = await callRestUrl(
|
|
851
|
+
`${backend.base}${backend.get(params.cpuTypeId)}`
|
|
852
|
+
);
|
|
853
|
+
return jsonReply2(result);
|
|
854
|
+
}
|
|
855
|
+
);
|
|
856
|
+
server.tool(
|
|
857
|
+
"get-data-center",
|
|
858
|
+
"Get details for a single data center by id. v2-only \u2014 returns a 501 notice on the v1 API (use list-data-centers there).",
|
|
859
|
+
{
|
|
860
|
+
dataCenterId: import_zod2.z.string().describe("ID of the data center to retrieve")
|
|
861
|
+
},
|
|
862
|
+
{ title: "Get data center", ...READ_ONLY },
|
|
863
|
+
async (params) => {
|
|
864
|
+
const backend = backendFor("dataCenters");
|
|
865
|
+
if (backend.version === "v1") {
|
|
866
|
+
return jsonReply2({
|
|
867
|
+
error: "get-data-center is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
|
|
868
|
+
status: 501
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
const result = await callRestUrl(
|
|
872
|
+
`${backend.base}${backend.get(params.dataCenterId)}`
|
|
873
|
+
);
|
|
874
|
+
return jsonReply2(result);
|
|
875
|
+
}
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/tools/pods.ts
|
|
880
|
+
var import_zod3 = require("zod");
|
|
881
|
+
function registerPodTools(server, rt) {
|
|
882
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor, podAction, env } = rt;
|
|
331
883
|
server.tool(
|
|
332
884
|
"list-pods",
|
|
885
|
+
"List your pods (running and stopped) with optional filters \u2014 compute type, GPU type, data center, name \u2014 plus machine/network-volume expansion. Paginated via limit/cursor.",
|
|
333
886
|
{
|
|
334
887
|
...listPaginationParams,
|
|
335
|
-
computeType:
|
|
336
|
-
gpuTypeId:
|
|
337
|
-
dataCenterId:
|
|
338
|
-
name:
|
|
339
|
-
includeMachine:
|
|
340
|
-
includeNetworkVolume:
|
|
888
|
+
computeType: import_zod3.z.enum(["GPU", "CPU"]).optional().describe("Filter to only GPU or only CPU Pods"),
|
|
889
|
+
gpuTypeId: import_zod3.z.array(import_zod3.z.string()).optional().describe("Filter to Pods with any of the listed GPU types"),
|
|
890
|
+
dataCenterId: import_zod3.z.array(import_zod3.z.string()).optional().describe("Filter to Pods in any of the provided data centers"),
|
|
891
|
+
name: import_zod3.z.string().optional().describe("Filter to Pods with the provided name"),
|
|
892
|
+
includeMachine: import_zod3.z.boolean().optional().describe("Include information about the machine"),
|
|
893
|
+
includeNetworkVolume: import_zod3.z.boolean().optional().describe("Include information about attached network volumes")
|
|
341
894
|
},
|
|
895
|
+
{ title: "List pods", ...READ_ONLY },
|
|
342
896
|
async (params) => {
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
params.
|
|
348
|
-
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
897
|
+
const backend = backendFor("pods");
|
|
898
|
+
let queryString = "";
|
|
899
|
+
if (backend.version === "v1") {
|
|
900
|
+
const queryParams = new URLSearchParams();
|
|
901
|
+
if (params.computeType)
|
|
902
|
+
queryParams.append("computeType", params.computeType);
|
|
903
|
+
if (params.gpuTypeId)
|
|
904
|
+
params.gpuTypeId.forEach(
|
|
905
|
+
(type) => queryParams.append("gpuTypeId", type)
|
|
906
|
+
);
|
|
907
|
+
if (params.dataCenterId)
|
|
908
|
+
params.dataCenterId.forEach(
|
|
909
|
+
(dc) => queryParams.append("dataCenterId", dc)
|
|
910
|
+
);
|
|
911
|
+
if (params.name) queryParams.append("name", params.name);
|
|
912
|
+
if (params.includeMachine)
|
|
913
|
+
queryParams.append(
|
|
914
|
+
"includeMachine",
|
|
915
|
+
params.includeMachine.toString()
|
|
916
|
+
);
|
|
917
|
+
if (params.includeNetworkVolume)
|
|
918
|
+
queryParams.append(
|
|
919
|
+
"includeNetworkVolume",
|
|
920
|
+
params.includeNetworkVolume.toString()
|
|
921
|
+
);
|
|
922
|
+
queryString = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
923
|
+
}
|
|
924
|
+
const result = await callRestUrl(
|
|
925
|
+
`${backend.base}${backend.list}${queryString}`
|
|
926
|
+
);
|
|
927
|
+
return capListResult(backend.unwrap(result), {
|
|
365
928
|
limit: params.limit,
|
|
366
929
|
cursor: params.cursor
|
|
367
930
|
});
|
|
@@ -369,159 +932,252 @@ function registerTools(server, ctx) {
|
|
|
369
932
|
);
|
|
370
933
|
server.tool(
|
|
371
934
|
"get-pod",
|
|
935
|
+
"Get full details for one pod by id, optionally expanding machine and attached network-volume info.",
|
|
372
936
|
{
|
|
373
|
-
podId:
|
|
374
|
-
includeMachine:
|
|
375
|
-
includeNetworkVolume:
|
|
937
|
+
podId: import_zod3.z.string().describe("ID of the pod to retrieve"),
|
|
938
|
+
includeMachine: import_zod3.z.boolean().optional().describe("Include information about the machine"),
|
|
939
|
+
includeNetworkVolume: import_zod3.z.boolean().optional().describe("Include information about attached network volumes")
|
|
376
940
|
},
|
|
941
|
+
{ title: "Get pod", ...READ_ONLY },
|
|
377
942
|
async (params) => {
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
943
|
+
const backend = backendFor("pods");
|
|
944
|
+
let queryString = "";
|
|
945
|
+
if (backend.version === "v1") {
|
|
946
|
+
const queryParams = new URLSearchParams();
|
|
947
|
+
if (params.includeMachine)
|
|
948
|
+
queryParams.append(
|
|
949
|
+
"includeMachine",
|
|
950
|
+
params.includeMachine.toString()
|
|
951
|
+
);
|
|
952
|
+
if (params.includeNetworkVolume)
|
|
953
|
+
queryParams.append(
|
|
954
|
+
"includeNetworkVolume",
|
|
955
|
+
params.includeNetworkVolume.toString()
|
|
956
|
+
);
|
|
957
|
+
queryString = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
958
|
+
}
|
|
959
|
+
const result = await callRestUrl(
|
|
960
|
+
`${backend.base}${backend.get(params.podId)}${queryString}`
|
|
961
|
+
);
|
|
962
|
+
return jsonReply2(result);
|
|
396
963
|
}
|
|
397
964
|
);
|
|
398
965
|
server.tool(
|
|
399
966
|
"create-pod",
|
|
400
|
-
'Create a new GPU/CPU pod on Runpod. If the user does not specify an image, recommend the "Runpod Pytorch 2.8.0" image (runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404) as the default \u2014 it has the most up-to-date CUDA and PyTorch versions.',
|
|
967
|
+
'Create a new GPU/CPU pod on Runpod. Pass gpuTypeIds for a GPU pod, or computeType:"CPU" for a CPU pod (CPU pods are served by the v1 API for now). If the user does not specify an image, recommend the "Runpod Pytorch 2.8.0" image (runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404) as the default \u2014 it has the most up-to-date CUDA and PyTorch versions.',
|
|
401
968
|
{
|
|
402
|
-
name:
|
|
403
|
-
imageName:
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
969
|
+
name: import_zod3.z.string().optional().describe("Name for the pod"),
|
|
970
|
+
imageName: import_zod3.z.string().describe("Docker image to use"),
|
|
971
|
+
computeType: import_zod3.z.enum(["GPU", "CPU"]).optional().describe(
|
|
972
|
+
"Pod type. On v2, required when not passing gpuTypeIds. 'CPU' is served by the v1 API for now (v2 has no CPU pods yet)."
|
|
973
|
+
),
|
|
974
|
+
cloudType: import_zod3.z.enum(["SECURE", "COMMUNITY"]).optional().describe("SECURE or COMMUNITY cloud"),
|
|
975
|
+
gpuTypeIds: import_zod3.z.array(import_zod3.z.string()).optional().describe("List of acceptable GPU types"),
|
|
976
|
+
gpuCount: import_zod3.z.number().optional().describe("Number of GPUs"),
|
|
977
|
+
containerDiskInGb: import_zod3.z.number().optional().describe("Container disk size in GB"),
|
|
978
|
+
volumeInGb: import_zod3.z.number().optional().describe("Volume size in GB"),
|
|
979
|
+
volumeMountPath: import_zod3.z.string().optional().describe("Path to mount the volume"),
|
|
980
|
+
ports: import_zod3.z.array(import_zod3.z.string()).optional().describe("Ports to expose (e.g., '8888/http', '22/tcp')"),
|
|
981
|
+
env: import_zod3.z.record(import_zod3.z.string()).optional().describe("Environment variables"),
|
|
982
|
+
dataCenterIds: import_zod3.z.array(import_zod3.z.string()).optional().describe("List of data centers")
|
|
413
983
|
},
|
|
984
|
+
{ title: "Create pod", ...WRITE },
|
|
414
985
|
async (params) => {
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
986
|
+
const backend = backendFor("pods");
|
|
987
|
+
const hasGpuTypes = (params.gpuTypeIds?.length ?? 0) > 0;
|
|
988
|
+
const hasGpuCount = (params.gpuCount ?? 0) > 0;
|
|
989
|
+
const wantsCpu = params.computeType === "CPU";
|
|
990
|
+
const wantsGpu = hasGpuTypes || hasGpuCount || params.computeType === "GPU";
|
|
991
|
+
if (backend.version === "v2") {
|
|
992
|
+
if (wantsGpu && wantsCpu) {
|
|
993
|
+
return jsonReply2({
|
|
994
|
+
error: "Specify a GPU pod (gpuTypeIds or computeType:'GPU') OR a CPU pod (computeType:'CPU'), not both.",
|
|
995
|
+
status: 400
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
if (!wantsGpu && !wantsCpu) {
|
|
999
|
+
return jsonReply2({
|
|
1000
|
+
error: "No pod type specified. Pass gpuTypeIds (see list-gpu-types) for a GPU pod, or computeType:'CPU' for a CPU pod.",
|
|
1001
|
+
status: 400
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
if (wantsGpu && !hasGpuTypes) {
|
|
1005
|
+
return jsonReply2({
|
|
1006
|
+
error: "A GPU pod needs gpuTypeIds (see list-gpu-types); gpuCount or computeType alone is under-specified.",
|
|
1007
|
+
status: 400
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
if (wantsCpu) {
|
|
1011
|
+
try {
|
|
1012
|
+
const result = await callRestUrl(
|
|
1013
|
+
`${restV1Base(env)}/pods`,
|
|
1014
|
+
"POST",
|
|
1015
|
+
params
|
|
1016
|
+
);
|
|
1017
|
+
return jsonReply2({
|
|
1018
|
+
...result,
|
|
1019
|
+
_servedBy: "v1",
|
|
1020
|
+
_note: "CPU pod: created on the v1 API, because the v2 REST API does not support CPU pods yet."
|
|
1021
|
+
});
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
if (error instanceof HttpError) {
|
|
1024
|
+
return jsonReply2({
|
|
1025
|
+
error: error.message,
|
|
1026
|
+
status: error.status,
|
|
1027
|
+
_servedBy: "v1",
|
|
1028
|
+
_note: "CPU pod create was routed to the v1 API and failed."
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
throw error;
|
|
421
1032
|
}
|
|
422
|
-
|
|
423
|
-
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
const body = backend.mapCreate(params);
|
|
1036
|
+
try {
|
|
1037
|
+
const result = await callRestUrl(
|
|
1038
|
+
`${backend.base}${backend.list}`,
|
|
1039
|
+
"POST",
|
|
1040
|
+
body
|
|
1041
|
+
);
|
|
1042
|
+
if (backend.version === "v2" && (params.gpuTypeIds?.length ?? 0) > 1) {
|
|
1043
|
+
return jsonReply2({
|
|
1044
|
+
...result,
|
|
1045
|
+
_warning: `v2 accepts one GPU type per pod; used "${params.gpuTypeIds[0]}" and ignored ${params.gpuTypeIds.length - 1} other(s): ${params.gpuTypeIds.slice(1).join(", ")}.`
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
return jsonReply2(result);
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
if (error instanceof HttpError && error.status === 501) {
|
|
1051
|
+
return jsonReply2({
|
|
1052
|
+
error: "CPU pods are not yet supported on the v2 REST API. Use a GPU pod, or create the CPU pod via the v1 API (set RUNPOD_REST_VERSION=v1).",
|
|
1053
|
+
status: 501
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
throw error;
|
|
1057
|
+
}
|
|
424
1058
|
}
|
|
425
1059
|
);
|
|
426
1060
|
server.tool(
|
|
427
1061
|
"update-pod",
|
|
1062
|
+
"Update a pod's mutable fields (name, image, container disk, volume, ports, env). Only the fields you provide change.",
|
|
428
1063
|
{
|
|
429
|
-
podId:
|
|
430
|
-
name:
|
|
431
|
-
imageName:
|
|
432
|
-
containerDiskInGb:
|
|
433
|
-
volumeInGb:
|
|
434
|
-
volumeMountPath:
|
|
435
|
-
ports:
|
|
436
|
-
env:
|
|
1064
|
+
podId: import_zod3.z.string().describe("ID of the pod to update"),
|
|
1065
|
+
name: import_zod3.z.string().optional().describe("New name for the pod"),
|
|
1066
|
+
imageName: import_zod3.z.string().optional().describe("New Docker image"),
|
|
1067
|
+
containerDiskInGb: import_zod3.z.number().optional().describe("New container disk size in GB"),
|
|
1068
|
+
volumeInGb: import_zod3.z.number().optional().describe("New volume size in GB"),
|
|
1069
|
+
volumeMountPath: import_zod3.z.string().optional().describe("New path to mount the volume"),
|
|
1070
|
+
ports: import_zod3.z.array(import_zod3.z.string()).optional().describe("New ports to expose"),
|
|
1071
|
+
env: import_zod3.z.record(import_zod3.z.string()).optional().describe("New environment variables")
|
|
437
1072
|
},
|
|
1073
|
+
{ title: "Update pod", ...WRITE, idempotentHint: true },
|
|
438
1074
|
async (params) => {
|
|
439
1075
|
const { podId, ...updateParams } = params;
|
|
440
|
-
const
|
|
441
|
-
|
|
1076
|
+
const backend = backendFor("pods");
|
|
1077
|
+
const body = backend.mapUpdate(updateParams);
|
|
1078
|
+
const result = await callRestUrl(
|
|
1079
|
+
`${backend.base}${backend.get(podId)}`,
|
|
442
1080
|
"PATCH",
|
|
443
|
-
|
|
1081
|
+
body
|
|
444
1082
|
);
|
|
445
|
-
return
|
|
446
|
-
content: [
|
|
447
|
-
{
|
|
448
|
-
type: "text",
|
|
449
|
-
text: JSON.stringify(result, null, 2)
|
|
450
|
-
}
|
|
451
|
-
]
|
|
452
|
-
};
|
|
1083
|
+
return jsonReply2(result);
|
|
453
1084
|
}
|
|
454
1085
|
);
|
|
455
1086
|
server.tool(
|
|
456
1087
|
"start-pod",
|
|
1088
|
+
"Start a stopped pod (resumes the same pod and its billing).",
|
|
457
1089
|
{
|
|
458
|
-
podId:
|
|
1090
|
+
podId: import_zod3.z.string().describe("ID of the pod to start")
|
|
459
1091
|
},
|
|
1092
|
+
{ title: "Start pod", ...WRITE, idempotentHint: true },
|
|
460
1093
|
async (params) => {
|
|
461
|
-
const result = await
|
|
462
|
-
return
|
|
463
|
-
content: [
|
|
464
|
-
{
|
|
465
|
-
type: "text",
|
|
466
|
-
text: JSON.stringify(result, null, 2)
|
|
467
|
-
}
|
|
468
|
-
]
|
|
469
|
-
};
|
|
1094
|
+
const result = await podAction(params.podId, "start");
|
|
1095
|
+
return jsonReply2(result);
|
|
470
1096
|
}
|
|
471
1097
|
);
|
|
472
1098
|
server.tool(
|
|
473
1099
|
"stop-pod",
|
|
1100
|
+
"Stop a running pod. The pod and its disk persist; GPU billing stops.",
|
|
474
1101
|
{
|
|
475
|
-
podId:
|
|
1102
|
+
podId: import_zod3.z.string().describe("ID of the pod to stop")
|
|
476
1103
|
},
|
|
1104
|
+
{ title: "Stop pod", ...WRITE, idempotentHint: true },
|
|
477
1105
|
async (params) => {
|
|
478
|
-
const result = await
|
|
479
|
-
return
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
1106
|
+
const result = await podAction(params.podId, "stop");
|
|
1107
|
+
return jsonReply2(result);
|
|
1108
|
+
}
|
|
1109
|
+
);
|
|
1110
|
+
server.tool(
|
|
1111
|
+
"restart-pod",
|
|
1112
|
+
"Restart a running pod (single PodAction call). v2-only \u2014 on the v1 API returns a 501 notice (stop then start the pod instead).",
|
|
1113
|
+
{
|
|
1114
|
+
podId: import_zod3.z.string().describe("ID of the pod to restart")
|
|
1115
|
+
},
|
|
1116
|
+
{ title: "Restart pod", ...WRITE },
|
|
1117
|
+
async (params) => {
|
|
1118
|
+
const backend = backendFor("pods");
|
|
1119
|
+
if (backend.version === "v1") {
|
|
1120
|
+
return jsonReply2({
|
|
1121
|
+
error: "restart-pod is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2 (or stop then start the pod on v1).",
|
|
1122
|
+
status: 501
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
const result = await podAction(params.podId, "restart");
|
|
1126
|
+
return jsonReply2(result);
|
|
487
1127
|
}
|
|
488
1128
|
);
|
|
489
1129
|
server.tool(
|
|
490
1130
|
"delete-pod",
|
|
1131
|
+
"Permanently delete a pod and its data. This cannot be undone.",
|
|
491
1132
|
{
|
|
492
|
-
podId:
|
|
1133
|
+
podId: import_zod3.z.string().describe("ID of the pod to delete")
|
|
493
1134
|
},
|
|
1135
|
+
{ title: "Delete pod", ...DESTRUCTIVE },
|
|
494
1136
|
async (params) => {
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
}
|
|
502
|
-
]
|
|
503
|
-
};
|
|
1137
|
+
const backend = backendFor("pods");
|
|
1138
|
+
const result = await callRestUrl(
|
|
1139
|
+
`${backend.base}${backend.get(params.podId)}`,
|
|
1140
|
+
"DELETE"
|
|
1141
|
+
);
|
|
1142
|
+
return jsonReply2(result);
|
|
504
1143
|
}
|
|
505
1144
|
);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// src/tools/endpoints.ts
|
|
1148
|
+
var import_zod4 = require("zod");
|
|
1149
|
+
function registerEndpointTools(server, rt) {
|
|
1150
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
506
1151
|
server.tool(
|
|
507
1152
|
"list-endpoints",
|
|
1153
|
+
"List your Serverless endpoints, optionally expanding template and worker details (v1 only). Paginated via limit/cursor.",
|
|
508
1154
|
{
|
|
509
1155
|
...listPaginationParams,
|
|
510
|
-
includeTemplate:
|
|
511
|
-
includeWorkers:
|
|
1156
|
+
includeTemplate: import_zod4.z.boolean().optional().describe("Include template information (v1 only)"),
|
|
1157
|
+
includeWorkers: import_zod4.z.boolean().optional().describe("Include information about workers (v1 only)")
|
|
512
1158
|
},
|
|
1159
|
+
{ title: "List endpoints", ...READ_ONLY },
|
|
513
1160
|
async (params) => {
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
1161
|
+
const backend = backendFor("endpoints");
|
|
1162
|
+
let queryString = "";
|
|
1163
|
+
if (backend.version === "v1") {
|
|
1164
|
+
const queryParams = new URLSearchParams();
|
|
1165
|
+
if (params.includeTemplate)
|
|
1166
|
+
queryParams.append(
|
|
1167
|
+
"includeTemplate",
|
|
1168
|
+
params.includeTemplate.toString()
|
|
1169
|
+
);
|
|
1170
|
+
if (params.includeWorkers)
|
|
1171
|
+
queryParams.append(
|
|
1172
|
+
"includeWorkers",
|
|
1173
|
+
params.includeWorkers.toString()
|
|
1174
|
+
);
|
|
1175
|
+
queryString = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
1176
|
+
}
|
|
1177
|
+
const result = await callRestUrl(
|
|
1178
|
+
`${backend.base}${backend.list}${queryString}`
|
|
1179
|
+
);
|
|
1180
|
+
return capListResult(backend.unwrap(result), {
|
|
525
1181
|
limit: params.limit,
|
|
526
1182
|
cursor: params.cursor
|
|
527
1183
|
});
|
|
@@ -529,124 +1185,227 @@ function registerTools(server, ctx) {
|
|
|
529
1185
|
);
|
|
530
1186
|
server.tool(
|
|
531
1187
|
"get-endpoint",
|
|
1188
|
+
"Get one Serverless endpoint by id, optionally expanding template and worker details (v1 only).",
|
|
532
1189
|
{
|
|
533
|
-
endpointId:
|
|
534
|
-
includeTemplate:
|
|
535
|
-
includeWorkers:
|
|
1190
|
+
endpointId: import_zod4.z.string().describe("ID of the endpoint to retrieve"),
|
|
1191
|
+
includeTemplate: import_zod4.z.boolean().optional().describe("Include template information (v1 only)"),
|
|
1192
|
+
includeWorkers: import_zod4.z.boolean().optional().describe("Include information about workers (v1 only)")
|
|
536
1193
|
},
|
|
1194
|
+
{ title: "Get endpoint", ...READ_ONLY },
|
|
537
1195
|
async (params) => {
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
1196
|
+
const backend = backendFor("endpoints");
|
|
1197
|
+
let queryString = "";
|
|
1198
|
+
if (backend.version === "v1") {
|
|
1199
|
+
const queryParams = new URLSearchParams();
|
|
1200
|
+
if (params.includeTemplate)
|
|
1201
|
+
queryParams.append(
|
|
1202
|
+
"includeTemplate",
|
|
1203
|
+
params.includeTemplate.toString()
|
|
1204
|
+
);
|
|
1205
|
+
if (params.includeWorkers)
|
|
1206
|
+
queryParams.append(
|
|
1207
|
+
"includeWorkers",
|
|
1208
|
+
params.includeWorkers.toString()
|
|
1209
|
+
);
|
|
1210
|
+
queryString = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
1211
|
+
}
|
|
1212
|
+
const result = await callRestUrl(
|
|
1213
|
+
`${backend.base}${backend.get(params.endpointId)}${queryString}`
|
|
549
1214
|
);
|
|
550
|
-
return
|
|
551
|
-
content: [
|
|
552
|
-
{
|
|
553
|
-
type: "text",
|
|
554
|
-
text: JSON.stringify(result, null, 2)
|
|
555
|
-
}
|
|
556
|
-
]
|
|
557
|
-
};
|
|
1215
|
+
return jsonReply2(result);
|
|
558
1216
|
}
|
|
559
1217
|
);
|
|
560
1218
|
server.tool(
|
|
561
1219
|
"create-endpoint",
|
|
1220
|
+
"Create a Serverless endpoint. On v2 (default), pass an inline config: imageName + gpuPoolIds (GPU pool names from list-gpu-types \u2014 the `pool` field, e.g. AMPERE_80/ADA_24) plus optional workers/scaling/disk/env. On v1, pass a templateId instead. Worker min/max set autoscaling bounds (min 0 = scale to zero).",
|
|
562
1221
|
{
|
|
563
|
-
name:
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
1222
|
+
name: import_zod4.z.string().optional().describe("Name for the endpoint"),
|
|
1223
|
+
// --- v2 inline-config fields ---
|
|
1224
|
+
imageName: import_zod4.z.string().optional().describe("Docker image (v2). Required on v2 instead of a templateId."),
|
|
1225
|
+
gpuPoolIds: import_zod4.z.array(import_zod4.z.string()).optional().describe(
|
|
1226
|
+
'GPU pool names (v2, required). The `pool` field from list-gpu-types, e.g. ["AMPERE_80"]. NOT GPU type ids.'
|
|
1227
|
+
),
|
|
1228
|
+
gpuCount: import_zod4.z.number().optional().describe("GPUs per worker (v2)"),
|
|
1229
|
+
args: import_zod4.z.string().optional().describe("Container start command/args (v2)"),
|
|
1230
|
+
containerDiskInGb: import_zod4.z.number().optional().describe("Container disk size in GB (v2)"),
|
|
1231
|
+
ports: import_zod4.z.array(import_zod4.z.string()).optional().describe("Ports to expose (v2), e.g. ['8000/http']"),
|
|
1232
|
+
env: import_zod4.z.record(import_zod4.z.string()).optional().describe("Environment variables (v2)"),
|
|
1233
|
+
containerRegistryAuthId: import_zod4.z.string().optional().describe("Container registry auth id for a private image (v2)"),
|
|
1234
|
+
networkVolumeIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("Network volume ids to attach (v2)"),
|
|
1235
|
+
executionTimeoutMs: import_zod4.z.number().optional().describe("Per-job execution timeout in ms (v2)"),
|
|
1236
|
+
flashboot: import_zod4.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("FlashBoot mode (v2)"),
|
|
1237
|
+
// --- v1 template-based fields ---
|
|
1238
|
+
templateId: import_zod4.z.string().optional().describe("Template ID (v1). Required on v1."),
|
|
1239
|
+
computeType: import_zod4.z.enum(["GPU", "CPU"]).optional().describe("GPU or CPU endpoint (v1)"),
|
|
1240
|
+
gpuTypeIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("List of acceptable GPU types (v1)"),
|
|
1241
|
+
// --- shared autoscaling fields ---
|
|
1242
|
+
workersMin: import_zod4.z.number().optional().describe("Minimum number of workers"),
|
|
1243
|
+
workersMax: import_zod4.z.number().optional().describe("Maximum number of workers"),
|
|
1244
|
+
scalerType: import_zod4.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Autoscaler type"),
|
|
1245
|
+
scalerValue: import_zod4.z.number().optional().describe("Autoscaler target value"),
|
|
1246
|
+
idleTimeout: import_zod4.z.number().optional().describe("Idle timeout in seconds before scaling a worker down"),
|
|
1247
|
+
dataCenterIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("List of preferred data centers")
|
|
571
1248
|
},
|
|
1249
|
+
{ title: "Create endpoint", ...WRITE },
|
|
572
1250
|
async (params) => {
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
{
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
|
|
1251
|
+
const backend = backendFor("endpoints");
|
|
1252
|
+
if (backend.version === "v2") {
|
|
1253
|
+
if (!params.name) {
|
|
1254
|
+
return jsonReply2({
|
|
1255
|
+
error: "On the v2 REST API, create-endpoint requires a name.",
|
|
1256
|
+
status: 400
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
if (!params.imageName) {
|
|
1260
|
+
return jsonReply2({
|
|
1261
|
+
error: "On the v2 REST API, create-endpoint needs an imageName (v2 endpoints are image-based, not template-based). To use a templateId, set RUNPOD_REST_VERSION=v1.",
|
|
1262
|
+
status: 400
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
if (!params.gpuPoolIds?.length) {
|
|
1266
|
+
return jsonReply2({
|
|
1267
|
+
error: 'create-endpoint needs gpuPoolIds on v2 (GPU pool names from list-gpu-types \u2014 the `pool` field, e.g. ["AMPERE_80"]).',
|
|
1268
|
+
status: 400
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
const body = backend.mapCreate(params);
|
|
1272
|
+
const result2 = await callRestUrl(
|
|
1273
|
+
`${backend.base}${backend.list}`,
|
|
1274
|
+
"POST",
|
|
1275
|
+
body
|
|
1276
|
+
);
|
|
1277
|
+
return jsonReply2(result2);
|
|
1278
|
+
}
|
|
1279
|
+
if (!params.templateId) {
|
|
1280
|
+
return jsonReply2({
|
|
1281
|
+
error: "On the v1 REST API, create-endpoint needs a templateId. (On v2 \u2014 the default \u2014 pass imageName + gpuPoolIds instead.)",
|
|
1282
|
+
status: 400
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
const v1Body = {};
|
|
1286
|
+
for (const k of [
|
|
1287
|
+
"templateId",
|
|
1288
|
+
"name",
|
|
1289
|
+
"computeType",
|
|
1290
|
+
"gpuTypeIds",
|
|
1291
|
+
"gpuCount",
|
|
1292
|
+
"workersMin",
|
|
1293
|
+
"workersMax",
|
|
1294
|
+
"dataCenterIds"
|
|
1295
|
+
]) {
|
|
1296
|
+
if (params[k] !== void 0) v1Body[k] = params[k];
|
|
1297
|
+
}
|
|
1298
|
+
const result = await callRestUrl(
|
|
1299
|
+
`${backend.base}${backend.list}`,
|
|
1300
|
+
"POST",
|
|
1301
|
+
v1Body
|
|
1302
|
+
);
|
|
1303
|
+
return jsonReply2(result);
|
|
582
1304
|
}
|
|
583
1305
|
);
|
|
584
1306
|
server.tool(
|
|
585
1307
|
"update-endpoint",
|
|
1308
|
+
"Update a Serverless endpoint's config. On v2 you can change image/disk/env/ports/registry/workers/scaling/networkVolumes/timeout/flashboot; on v1, scaling fields (worker min/max, idle timeout, scaler type/value, name). Only provided fields change.",
|
|
586
1309
|
{
|
|
587
|
-
endpointId:
|
|
588
|
-
name:
|
|
589
|
-
workersMin:
|
|
590
|
-
workersMax:
|
|
591
|
-
idleTimeout:
|
|
592
|
-
scalerType:
|
|
593
|
-
scalerValue:
|
|
1310
|
+
endpointId: import_zod4.z.string().describe("ID of the endpoint to update"),
|
|
1311
|
+
name: import_zod4.z.string().optional().describe("New name for the endpoint"),
|
|
1312
|
+
workersMin: import_zod4.z.number().optional().describe("New minimum number of workers"),
|
|
1313
|
+
workersMax: import_zod4.z.number().optional().describe("New maximum number of workers"),
|
|
1314
|
+
idleTimeout: import_zod4.z.number().optional().describe("New idle timeout in seconds"),
|
|
1315
|
+
scalerType: import_zod4.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Scaler type"),
|
|
1316
|
+
scalerValue: import_zod4.z.number().optional().describe("Scaler value"),
|
|
1317
|
+
// --- v2-only inline-config fields ---
|
|
1318
|
+
imageName: import_zod4.z.string().optional().describe("New Docker image (v2)"),
|
|
1319
|
+
gpuPoolIds: import_zod4.z.array(import_zod4.z.string()).optional().describe('New GPU pool names (v2), e.g. ["AMPERE_80"]'),
|
|
1320
|
+
gpuCount: import_zod4.z.number().optional().describe("New GPUs per worker (v2)"),
|
|
1321
|
+
args: import_zod4.z.string().optional().describe("New container args (v2)"),
|
|
1322
|
+
containerDiskInGb: import_zod4.z.number().optional().describe("New container disk size in GB (v2)"),
|
|
1323
|
+
ports: import_zod4.z.array(import_zod4.z.string()).optional().describe("New ports (v2)"),
|
|
1324
|
+
env: import_zod4.z.record(import_zod4.z.string()).optional().describe("New environment variables (v2)"),
|
|
1325
|
+
containerRegistryAuthId: import_zod4.z.string().optional().describe("New container registry auth id (v2)"),
|
|
1326
|
+
networkVolumeIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("New network volume ids (v2)"),
|
|
1327
|
+
executionTimeoutMs: import_zod4.z.number().optional().describe("New per-job execution timeout in ms (v2)"),
|
|
1328
|
+
flashboot: import_zod4.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("New FlashBoot mode (v2)")
|
|
594
1329
|
},
|
|
1330
|
+
{ title: "Update endpoint", ...WRITE, idempotentHint: true },
|
|
595
1331
|
async (params) => {
|
|
596
1332
|
const { endpointId, ...updateParams } = params;
|
|
597
|
-
const
|
|
598
|
-
|
|
1333
|
+
const backend = backendFor("endpoints");
|
|
1334
|
+
const body = backend.mapUpdate(updateParams);
|
|
1335
|
+
const result = await callRestUrl(
|
|
1336
|
+
`${backend.base}${backend.get(endpointId)}`,
|
|
599
1337
|
"PATCH",
|
|
600
|
-
|
|
1338
|
+
body
|
|
601
1339
|
);
|
|
602
|
-
return
|
|
603
|
-
content: [
|
|
604
|
-
{
|
|
605
|
-
type: "text",
|
|
606
|
-
text: JSON.stringify(result, null, 2)
|
|
607
|
-
}
|
|
608
|
-
]
|
|
609
|
-
};
|
|
1340
|
+
return jsonReply2(result);
|
|
610
1341
|
}
|
|
611
1342
|
);
|
|
612
1343
|
server.tool(
|
|
613
1344
|
"delete-endpoint",
|
|
1345
|
+
"Permanently delete a Serverless endpoint. This cannot be undone.",
|
|
614
1346
|
{
|
|
615
|
-
endpointId:
|
|
1347
|
+
endpointId: import_zod4.z.string().describe("ID of the endpoint to delete")
|
|
616
1348
|
},
|
|
1349
|
+
{ title: "Delete endpoint", ...DESTRUCTIVE },
|
|
617
1350
|
async (params) => {
|
|
618
|
-
const
|
|
619
|
-
|
|
1351
|
+
const backend = backendFor("endpoints");
|
|
1352
|
+
const result = await callRestUrl(
|
|
1353
|
+
`${backend.base}${backend.get(params.endpointId)}`,
|
|
620
1354
|
"DELETE"
|
|
621
1355
|
);
|
|
622
|
-
return
|
|
623
|
-
content: [
|
|
624
|
-
{
|
|
625
|
-
type: "text",
|
|
626
|
-
text: JSON.stringify(result, null, 2)
|
|
627
|
-
}
|
|
628
|
-
]
|
|
629
|
-
};
|
|
1356
|
+
return jsonReply2(result);
|
|
630
1357
|
}
|
|
631
1358
|
);
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
1359
|
+
server.tool(
|
|
1360
|
+
"list-endpoint-workers",
|
|
1361
|
+
"List the workers backing a Serverless endpoint, with their status and an aggregate summary. v2-only \u2014 returns a 501 notice on the v1 API. Paginated via limit/cursor.",
|
|
1362
|
+
{
|
|
1363
|
+
...listPaginationParams,
|
|
1364
|
+
endpointId: import_zod4.z.string().describe("ID of the Serverless endpoint whose workers to list")
|
|
1365
|
+
},
|
|
1366
|
+
{ title: "List endpoint workers", ...READ_ONLY },
|
|
1367
|
+
async (params) => {
|
|
1368
|
+
const backend = backendFor("workers");
|
|
1369
|
+
if (backend.version === "v1") {
|
|
1370
|
+
return jsonReply2({
|
|
1371
|
+
error: "list-endpoint-workers is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2 (on v1, use get-endpoint with includeWorkers).",
|
|
1372
|
+
status: 501
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
const raw = await callRestUrl(
|
|
1376
|
+
`${backend.base}/serverless/${params.endpointId}/workers`
|
|
1377
|
+
);
|
|
1378
|
+
return capListResult(
|
|
1379
|
+
backend.unwrap(raw),
|
|
1380
|
+
{ limit: params.limit, cursor: params.cursor },
|
|
1381
|
+
{ summary: raw?.summary, endpointVersion: raw?.endpointVersion }
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// src/tools/jobs.ts
|
|
1388
|
+
var import_zod5 = require("zod");
|
|
1389
|
+
function registerJobTools(server, rt) {
|
|
1390
|
+
const { jsonReply: jsonReply2, serverlessRequest } = rt;
|
|
1391
|
+
const endpointIdSchema = import_zod5.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid endpoint ID format");
|
|
1392
|
+
const jobIdSchema = import_zod5.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid job ID format");
|
|
1393
|
+
const inputSchema = import_zod5.z.record(import_zod5.z.unknown()).describe(
|
|
635
1394
|
"Input payload for the worker handler. The expected fields depend on the deployed model or worker."
|
|
636
1395
|
);
|
|
637
|
-
const webhookSchema =
|
|
1396
|
+
const webhookSchema = import_zod5.z.string().url().optional().describe(
|
|
638
1397
|
"Webhook URL to receive job completion notifications instead of polling"
|
|
639
1398
|
);
|
|
640
|
-
const policySchema =
|
|
641
|
-
executionTimeout:
|
|
642
|
-
lowPriority:
|
|
643
|
-
ttl:
|
|
1399
|
+
const policySchema = import_zod5.z.object({
|
|
1400
|
+
executionTimeout: import_zod5.z.number().optional().describe("Maximum execution time in milliseconds"),
|
|
1401
|
+
lowPriority: import_zod5.z.boolean().optional().describe("Submit as a low-priority job"),
|
|
1402
|
+
ttl: import_zod5.z.number().optional().describe("Time-to-live for the job result in milliseconds")
|
|
644
1403
|
}).optional().describe("Execution policy options");
|
|
645
|
-
const s3ConfigSchema =
|
|
646
|
-
accessId:
|
|
647
|
-
accessSecret:
|
|
648
|
-
bucketName:
|
|
649
|
-
endpointUrl:
|
|
1404
|
+
const s3ConfigSchema = import_zod5.z.object({
|
|
1405
|
+
accessId: import_zod5.z.string().describe("S3 access key ID"),
|
|
1406
|
+
accessSecret: import_zod5.z.string().describe("S3 secret access key"),
|
|
1407
|
+
bucketName: import_zod5.z.string().describe("S3 bucket name"),
|
|
1408
|
+
endpointUrl: import_zod5.z.string().describe("S3 endpoint URL")
|
|
650
1409
|
}).optional().describe("S3-compatible storage config for large outputs");
|
|
651
1410
|
server.tool(
|
|
652
1411
|
"run-endpoint",
|
|
@@ -660,6 +1419,7 @@ function registerTools(server, ctx) {
|
|
|
660
1419
|
policy: policySchema,
|
|
661
1420
|
s3Config: s3ConfigSchema
|
|
662
1421
|
},
|
|
1422
|
+
{ title: "Run endpoint (async)", ...WRITE },
|
|
663
1423
|
async (params) => {
|
|
664
1424
|
const { endpointId, ...body } = params;
|
|
665
1425
|
const result = await serverlessRequest(
|
|
@@ -668,14 +1428,7 @@ function registerTools(server, ctx) {
|
|
|
668
1428
|
"POST",
|
|
669
1429
|
body
|
|
670
1430
|
);
|
|
671
|
-
return
|
|
672
|
-
content: [
|
|
673
|
-
{
|
|
674
|
-
type: "text",
|
|
675
|
-
text: JSON.stringify(result, null, 2)
|
|
676
|
-
}
|
|
677
|
-
]
|
|
678
|
-
};
|
|
1431
|
+
return jsonReply2(result);
|
|
679
1432
|
}
|
|
680
1433
|
);
|
|
681
1434
|
server.tool(
|
|
@@ -686,13 +1439,14 @@ function registerTools(server, ctx) {
|
|
|
686
1439
|
"ID of the Serverless endpoint to run synchronously"
|
|
687
1440
|
),
|
|
688
1441
|
input: inputSchema,
|
|
689
|
-
wait:
|
|
1442
|
+
wait: import_zod5.z.number().min(1e3).max(3e5).optional().describe(
|
|
690
1443
|
"How long in milliseconds the server should wait for a result before returning a job ID to poll (1000\u2013300000). Defaults to 90000 (90 seconds)."
|
|
691
1444
|
),
|
|
692
1445
|
webhook: webhookSchema,
|
|
693
1446
|
policy: policySchema,
|
|
694
1447
|
s3Config: s3ConfigSchema
|
|
695
1448
|
},
|
|
1449
|
+
{ title: "Run endpoint (sync)", ...WRITE },
|
|
696
1450
|
async (params) => {
|
|
697
1451
|
const { endpointId, wait, ...body } = params;
|
|
698
1452
|
const path = wait ? `/runsync?wait=${wait}` : "/runsync";
|
|
@@ -702,14 +1456,7 @@ function registerTools(server, ctx) {
|
|
|
702
1456
|
"POST",
|
|
703
1457
|
body
|
|
704
1458
|
);
|
|
705
|
-
return
|
|
706
|
-
content: [
|
|
707
|
-
{
|
|
708
|
-
type: "text",
|
|
709
|
-
text: JSON.stringify(result, null, 2)
|
|
710
|
-
}
|
|
711
|
-
]
|
|
712
|
-
};
|
|
1459
|
+
return jsonReply2(result);
|
|
713
1460
|
}
|
|
714
1461
|
);
|
|
715
1462
|
server.tool(
|
|
@@ -721,19 +1468,13 @@ function registerTools(server, ctx) {
|
|
|
721
1468
|
),
|
|
722
1469
|
jobId: jobIdSchema.describe("ID of the job to check")
|
|
723
1470
|
},
|
|
1471
|
+
{ title: "Get job status", ...READ_ONLY },
|
|
724
1472
|
async (params) => {
|
|
725
1473
|
const result = await serverlessRequest(
|
|
726
1474
|
params.endpointId,
|
|
727
1475
|
`/status/${params.jobId}`
|
|
728
1476
|
);
|
|
729
|
-
return
|
|
730
|
-
content: [
|
|
731
|
-
{
|
|
732
|
-
type: "text",
|
|
733
|
-
text: JSON.stringify(result, null, 2)
|
|
734
|
-
}
|
|
735
|
-
]
|
|
736
|
-
};
|
|
1477
|
+
return jsonReply2(result);
|
|
737
1478
|
}
|
|
738
1479
|
);
|
|
739
1480
|
server.tool(
|
|
@@ -745,6 +1486,7 @@ function registerTools(server, ctx) {
|
|
|
745
1486
|
),
|
|
746
1487
|
jobId: jobIdSchema.describe("ID of the job to stream results from")
|
|
747
1488
|
},
|
|
1489
|
+
{ title: "Stream job", ...READ_ONLY },
|
|
748
1490
|
async (params) => {
|
|
749
1491
|
const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
750
1492
|
"COMPLETED",
|
|
@@ -790,18 +1532,7 @@ function registerTools(server, ctx) {
|
|
|
790
1532
|
}
|
|
791
1533
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
792
1534
|
}
|
|
793
|
-
return {
|
|
794
|
-
content: [
|
|
795
|
-
{
|
|
796
|
-
type: "text",
|
|
797
|
-
text: JSON.stringify(
|
|
798
|
-
{ ...finalResult, stream: allChunks },
|
|
799
|
-
null,
|
|
800
|
-
2
|
|
801
|
-
)
|
|
802
|
-
}
|
|
803
|
-
]
|
|
804
|
-
};
|
|
1535
|
+
return jsonReply2({ ...finalResult, stream: allChunks });
|
|
805
1536
|
}
|
|
806
1537
|
);
|
|
807
1538
|
server.tool(
|
|
@@ -813,20 +1544,14 @@ function registerTools(server, ctx) {
|
|
|
813
1544
|
),
|
|
814
1545
|
jobId: jobIdSchema.describe("ID of the job to cancel")
|
|
815
1546
|
},
|
|
1547
|
+
{ title: "Cancel job", ...WRITE, idempotentHint: true },
|
|
816
1548
|
async (params) => {
|
|
817
1549
|
const result = await serverlessRequest(
|
|
818
1550
|
params.endpointId,
|
|
819
1551
|
`/cancel/${params.jobId}`,
|
|
820
1552
|
"POST"
|
|
821
1553
|
);
|
|
822
|
-
return
|
|
823
|
-
content: [
|
|
824
|
-
{
|
|
825
|
-
type: "text",
|
|
826
|
-
text: JSON.stringify(result, null, 2)
|
|
827
|
-
}
|
|
828
|
-
]
|
|
829
|
-
};
|
|
1554
|
+
return jsonReply2(result);
|
|
830
1555
|
}
|
|
831
1556
|
);
|
|
832
1557
|
server.tool(
|
|
@@ -838,20 +1563,14 @@ function registerTools(server, ctx) {
|
|
|
838
1563
|
),
|
|
839
1564
|
jobId: jobIdSchema.describe("ID of the job to retry")
|
|
840
1565
|
},
|
|
1566
|
+
{ title: "Retry job", ...WRITE },
|
|
841
1567
|
async (params) => {
|
|
842
1568
|
const result = await serverlessRequest(
|
|
843
1569
|
params.endpointId,
|
|
844
1570
|
`/retry/${params.jobId}`,
|
|
845
1571
|
"POST"
|
|
846
1572
|
);
|
|
847
|
-
return
|
|
848
|
-
content: [
|
|
849
|
-
{
|
|
850
|
-
type: "text",
|
|
851
|
-
text: JSON.stringify(result, null, 2)
|
|
852
|
-
}
|
|
853
|
-
]
|
|
854
|
-
};
|
|
1573
|
+
return jsonReply2(result);
|
|
855
1574
|
}
|
|
856
1575
|
);
|
|
857
1576
|
server.tool(
|
|
@@ -862,16 +1581,10 @@ function registerTools(server, ctx) {
|
|
|
862
1581
|
"ID of the Serverless endpoint to check health for"
|
|
863
1582
|
)
|
|
864
1583
|
},
|
|
1584
|
+
{ title: "Endpoint health", ...READ_ONLY },
|
|
865
1585
|
async (params) => {
|
|
866
1586
|
const result = await serverlessRequest(params.endpointId, "/health");
|
|
867
|
-
return
|
|
868
|
-
content: [
|
|
869
|
-
{
|
|
870
|
-
type: "text",
|
|
871
|
-
text: JSON.stringify(result, null, 2)
|
|
872
|
-
}
|
|
873
|
-
]
|
|
874
|
-
};
|
|
1587
|
+
return jsonReply2(result);
|
|
875
1588
|
}
|
|
876
1589
|
);
|
|
877
1590
|
server.tool(
|
|
@@ -882,46 +1595,51 @@ function registerTools(server, ctx) {
|
|
|
882
1595
|
"ID of the Serverless endpoint to purge the queue for"
|
|
883
1596
|
)
|
|
884
1597
|
},
|
|
1598
|
+
{ title: "Purge endpoint queue", ...WRITE, idempotentHint: true },
|
|
885
1599
|
async (params) => {
|
|
886
1600
|
const result = await serverlessRequest(
|
|
887
1601
|
params.endpointId,
|
|
888
1602
|
"/purge-queue",
|
|
889
1603
|
"POST"
|
|
890
1604
|
);
|
|
891
|
-
return
|
|
892
|
-
content: [
|
|
893
|
-
{
|
|
894
|
-
type: "text",
|
|
895
|
-
text: JSON.stringify(result, null, 2)
|
|
896
|
-
}
|
|
897
|
-
]
|
|
898
|
-
};
|
|
1605
|
+
return jsonReply2(result);
|
|
899
1606
|
}
|
|
900
1607
|
);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// src/tools/templates.ts
|
|
1611
|
+
var import_zod6 = require("zod");
|
|
1612
|
+
function registerTemplateTools(server, rt) {
|
|
1613
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
901
1614
|
server.tool(
|
|
902
1615
|
"list-templates",
|
|
903
1616
|
`List available templates. By default returns only the user's own templates. Use includeRunpodTemplates to also include official Runpod templates. The recommended default template for new pods is "Runpod Pytorch 2.8.0" (ID: runpod-torch-v280) \u2014 it has the latest CUDA and PyTorch versions.`,
|
|
904
1617
|
{
|
|
905
1618
|
...listPaginationParams,
|
|
906
|
-
includeRunpodTemplates:
|
|
907
|
-
includePublicTemplates:
|
|
908
|
-
includeEndpointBoundTemplates:
|
|
1619
|
+
includeRunpodTemplates: import_zod6.z.boolean().optional().describe("Include official Runpod templates in the response"),
|
|
1620
|
+
includePublicTemplates: import_zod6.z.boolean().optional().describe("Include community-made public templates in the response"),
|
|
1621
|
+
includeEndpointBoundTemplates: import_zod6.z.boolean().optional().describe(
|
|
909
1622
|
"Include templates bound to Serverless endpoints in the response"
|
|
910
1623
|
)
|
|
911
1624
|
},
|
|
1625
|
+
{ title: "List templates", ...READ_ONLY },
|
|
912
1626
|
async (params) => {
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1627
|
+
const backend = backendFor("templates");
|
|
1628
|
+
let query = "";
|
|
1629
|
+
if (backend.version === "v1") {
|
|
1630
|
+
const queryParams = new URLSearchParams();
|
|
1631
|
+
if (params.includeRunpodTemplates)
|
|
1632
|
+
queryParams.set("includeRunpodTemplates", "true");
|
|
1633
|
+
if (params.includePublicTemplates)
|
|
1634
|
+
queryParams.set("includePublicTemplates", "true");
|
|
1635
|
+
if (params.includeEndpointBoundTemplates)
|
|
1636
|
+
queryParams.set("includeEndpointBoundTemplates", "true");
|
|
1637
|
+
query = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
1638
|
+
}
|
|
1639
|
+
const result = await callRestUrl(
|
|
1640
|
+
`${backend.base}${backend.list}${query}`
|
|
923
1641
|
);
|
|
924
|
-
return capListResult(result, {
|
|
1642
|
+
return capListResult(backend.unwrap(result), {
|
|
925
1643
|
limit: params.limit,
|
|
926
1644
|
cursor: params.cursor
|
|
927
1645
|
});
|
|
@@ -929,196 +1647,203 @@ function registerTools(server, ctx) {
|
|
|
929
1647
|
);
|
|
930
1648
|
server.tool(
|
|
931
1649
|
"get-template",
|
|
1650
|
+
"Get one template by id (image, env, ports, disk/volume config).",
|
|
932
1651
|
{
|
|
933
|
-
templateId:
|
|
1652
|
+
templateId: import_zod6.z.string().describe("ID of the template to retrieve")
|
|
934
1653
|
},
|
|
1654
|
+
{ title: "Get template", ...READ_ONLY },
|
|
935
1655
|
async (params) => {
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
text: JSON.stringify(result, null, 2)
|
|
942
|
-
}
|
|
943
|
-
]
|
|
944
|
-
};
|
|
1656
|
+
const backend = backendFor("templates");
|
|
1657
|
+
const result = await callRestUrl(
|
|
1658
|
+
`${backend.base}${backend.get(params.templateId)}`
|
|
1659
|
+
);
|
|
1660
|
+
return jsonReply2(result);
|
|
945
1661
|
}
|
|
946
1662
|
);
|
|
947
1663
|
server.tool(
|
|
948
1664
|
"create-template",
|
|
1665
|
+
"Create a reusable pod/endpoint template (image, ports, env, disk/volume). On v2 a category is required and defaults to NVIDIA.",
|
|
949
1666
|
{
|
|
950
|
-
name:
|
|
951
|
-
imageName:
|
|
952
|
-
isServerless:
|
|
953
|
-
ports:
|
|
954
|
-
dockerEntrypoint:
|
|
955
|
-
dockerStartCmd:
|
|
956
|
-
env:
|
|
957
|
-
containerDiskInGb:
|
|
958
|
-
volumeInGb:
|
|
959
|
-
volumeMountPath:
|
|
960
|
-
readme:
|
|
961
|
-
|
|
1667
|
+
name: import_zod6.z.string().describe("Name for the template"),
|
|
1668
|
+
imageName: import_zod6.z.string().describe("Docker image to use"),
|
|
1669
|
+
isServerless: import_zod6.z.boolean().optional().describe("Is this a serverless template"),
|
|
1670
|
+
ports: import_zod6.z.array(import_zod6.z.string()).optional().describe("Ports to expose"),
|
|
1671
|
+
dockerEntrypoint: import_zod6.z.array(import_zod6.z.string()).optional().describe("Docker entrypoint commands"),
|
|
1672
|
+
dockerStartCmd: import_zod6.z.array(import_zod6.z.string()).optional().describe("Docker start commands"),
|
|
1673
|
+
env: import_zod6.z.record(import_zod6.z.string()).optional().describe("Environment variables"),
|
|
1674
|
+
containerDiskInGb: import_zod6.z.number().optional().describe("Container disk size in GB"),
|
|
1675
|
+
volumeInGb: import_zod6.z.number().optional().describe("Volume size in GB"),
|
|
1676
|
+
volumeMountPath: import_zod6.z.string().optional().describe("Path to mount the volume"),
|
|
1677
|
+
readme: import_zod6.z.string().optional().describe("README content in markdown format"),
|
|
1678
|
+
category: import_zod6.z.enum(["CPU", "NVIDIA", "AMD"]).optional().describe(
|
|
1679
|
+
"Hardware category for the template. On v2 a category is required and defaults to NVIDIA when omitted \u2014 set CPU or AMD for non-NVIDIA templates so they are not mis-categorized."
|
|
1680
|
+
),
|
|
1681
|
+
containerRegistryAuthId: import_zod6.z.string().optional().describe(
|
|
962
1682
|
"ID of a container registry credential (from create-container-registry-auth / list-container-registry-auths) used to pull a private image. Required when imageName points at a private registry; without it serverless workers fail the image pull."
|
|
963
1683
|
)
|
|
964
1684
|
},
|
|
1685
|
+
{ title: "Create template", ...WRITE },
|
|
965
1686
|
async (params) => {
|
|
966
|
-
const
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
};
|
|
1687
|
+
const backend = backendFor("templates");
|
|
1688
|
+
const body = backend.mapCreate(params);
|
|
1689
|
+
const result = await callRestUrl(
|
|
1690
|
+
`${backend.base}${backend.list}`,
|
|
1691
|
+
"POST",
|
|
1692
|
+
body
|
|
1693
|
+
);
|
|
1694
|
+
return jsonReply2(result);
|
|
975
1695
|
}
|
|
976
1696
|
);
|
|
977
1697
|
server.tool(
|
|
978
1698
|
"update-template",
|
|
1699
|
+
"Update a template's mutable fields (name, image, ports, env, readme). Only provided fields change.",
|
|
979
1700
|
{
|
|
980
|
-
templateId:
|
|
981
|
-
name:
|
|
982
|
-
imageName:
|
|
983
|
-
ports:
|
|
984
|
-
env:
|
|
985
|
-
readme:
|
|
986
|
-
containerRegistryAuthId:
|
|
1701
|
+
templateId: import_zod6.z.string().describe("ID of the template to update"),
|
|
1702
|
+
name: import_zod6.z.string().optional().describe("New name for the template"),
|
|
1703
|
+
imageName: import_zod6.z.string().optional().describe("New Docker image"),
|
|
1704
|
+
ports: import_zod6.z.array(import_zod6.z.string()).optional().describe("New ports to expose"),
|
|
1705
|
+
env: import_zod6.z.record(import_zod6.z.string()).optional().describe("New environment variables"),
|
|
1706
|
+
readme: import_zod6.z.string().optional().describe("New README content in markdown format"),
|
|
1707
|
+
containerRegistryAuthId: import_zod6.z.string().optional().describe(
|
|
987
1708
|
"ID of a container registry credential (from create-container-registry-auth / list-container-registry-auths) used to pull a private image. Set this to attach a credential to a template referencing a private registry."
|
|
988
1709
|
)
|
|
989
1710
|
},
|
|
1711
|
+
{ title: "Update template", ...WRITE, idempotentHint: true },
|
|
990
1712
|
async (params) => {
|
|
991
1713
|
const { templateId, ...updateParams } = params;
|
|
992
|
-
const
|
|
993
|
-
|
|
1714
|
+
const backend = backendFor("templates");
|
|
1715
|
+
const body = backend.mapUpdate(updateParams);
|
|
1716
|
+
const result = await callRestUrl(
|
|
1717
|
+
`${backend.base}${backend.get(templateId)}`,
|
|
994
1718
|
"PATCH",
|
|
995
|
-
|
|
1719
|
+
body
|
|
996
1720
|
);
|
|
997
|
-
return
|
|
998
|
-
content: [
|
|
999
|
-
{
|
|
1000
|
-
type: "text",
|
|
1001
|
-
text: JSON.stringify(result, null, 2)
|
|
1002
|
-
}
|
|
1003
|
-
]
|
|
1004
|
-
};
|
|
1721
|
+
return jsonReply2(result);
|
|
1005
1722
|
}
|
|
1006
1723
|
);
|
|
1007
1724
|
server.tool(
|
|
1008
1725
|
"delete-template",
|
|
1726
|
+
"Permanently delete a template. This cannot be undone.",
|
|
1009
1727
|
{
|
|
1010
|
-
templateId:
|
|
1728
|
+
templateId: import_zod6.z.string().describe("ID of the template to delete")
|
|
1011
1729
|
},
|
|
1730
|
+
{ title: "Delete template", ...DESTRUCTIVE },
|
|
1012
1731
|
async (params) => {
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1732
|
+
const backend = backendFor("templates");
|
|
1733
|
+
const result = await callRestUrl(
|
|
1734
|
+
`${backend.base}${backend.get(params.templateId)}`,
|
|
1015
1735
|
"DELETE"
|
|
1016
1736
|
);
|
|
1017
|
-
return
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1737
|
+
return jsonReply2(result);
|
|
1738
|
+
}
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// src/tools/network-volumes.ts
|
|
1743
|
+
var import_zod7 = require("zod");
|
|
1744
|
+
function registerNetworkVolumeTools(server, rt) {
|
|
1745
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
1746
|
+
server.tool(
|
|
1747
|
+
"list-network-volumes",
|
|
1748
|
+
"List your network volumes (persistent storage attachable to pods). Paginated via limit/cursor.",
|
|
1749
|
+
listPaginationParams,
|
|
1750
|
+
{ title: "List network volumes", ...READ_ONLY },
|
|
1751
|
+
async (params) => {
|
|
1752
|
+
const backend = backendFor("networkVolumes");
|
|
1753
|
+
const result = await callRestUrl(`${backend.base}${backend.list}`);
|
|
1754
|
+
return capListResult(backend.unwrap(result), {
|
|
1755
|
+
limit: params.limit,
|
|
1756
|
+
cursor: params.cursor
|
|
1757
|
+
});
|
|
1025
1758
|
}
|
|
1026
1759
|
);
|
|
1027
|
-
server.tool("list-network-volumes", listPaginationParams, async (params) => {
|
|
1028
|
-
const result = await runpodRequest("/networkvolumes");
|
|
1029
|
-
return capListResult(result, {
|
|
1030
|
-
limit: params.limit,
|
|
1031
|
-
cursor: params.cursor
|
|
1032
|
-
});
|
|
1033
|
-
});
|
|
1034
1760
|
server.tool(
|
|
1035
1761
|
"get-network-volume",
|
|
1762
|
+
"Get one network volume by id (name, size, data center).",
|
|
1036
1763
|
{
|
|
1037
|
-
networkVolumeId:
|
|
1764
|
+
networkVolumeId: import_zod7.z.string().describe("ID of the network volume to retrieve")
|
|
1038
1765
|
},
|
|
1766
|
+
{ title: "Get network volume", ...READ_ONLY },
|
|
1039
1767
|
async (params) => {
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1768
|
+
const backend = backendFor("networkVolumes");
|
|
1769
|
+
const result = await callRestUrl(
|
|
1770
|
+
`${backend.base}${backend.get(params.networkVolumeId)}`
|
|
1042
1771
|
);
|
|
1043
|
-
return
|
|
1044
|
-
content: [
|
|
1045
|
-
{
|
|
1046
|
-
type: "text",
|
|
1047
|
-
text: JSON.stringify(result, null, 2)
|
|
1048
|
-
}
|
|
1049
|
-
]
|
|
1050
|
-
};
|
|
1772
|
+
return jsonReply2(result);
|
|
1051
1773
|
}
|
|
1052
1774
|
);
|
|
1053
1775
|
server.tool(
|
|
1054
1776
|
"create-network-volume",
|
|
1777
|
+
"Create a persistent network volume in a data center (size 1\u20134000 GB) that pods can mount.",
|
|
1055
1778
|
{
|
|
1056
|
-
name:
|
|
1057
|
-
size:
|
|
1058
|
-
dataCenterId:
|
|
1779
|
+
name: import_zod7.z.string().describe("Name for the network volume"),
|
|
1780
|
+
size: import_zod7.z.number().describe("Size in GB (1-4000)"),
|
|
1781
|
+
dataCenterId: import_zod7.z.string().describe("Data center ID (see list-data-centers)")
|
|
1059
1782
|
},
|
|
1783
|
+
{ title: "Create network volume", ...WRITE },
|
|
1060
1784
|
async (params) => {
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
};
|
|
1785
|
+
const backend = backendFor("networkVolumes");
|
|
1786
|
+
const body = backend.mapCreate(params);
|
|
1787
|
+
const result = await callRestUrl(
|
|
1788
|
+
`${backend.base}${backend.list}`,
|
|
1789
|
+
"POST",
|
|
1790
|
+
body
|
|
1791
|
+
);
|
|
1792
|
+
return jsonReply2(result);
|
|
1070
1793
|
}
|
|
1071
1794
|
);
|
|
1072
1795
|
server.tool(
|
|
1073
1796
|
"update-network-volume",
|
|
1797
|
+
"Update a network volume (rename, or grow size \u2014 the new size must exceed the current size). Only provided fields change.",
|
|
1074
1798
|
{
|
|
1075
|
-
networkVolumeId:
|
|
1076
|
-
name:
|
|
1077
|
-
size:
|
|
1799
|
+
networkVolumeId: import_zod7.z.string().describe("ID of the network volume to update"),
|
|
1800
|
+
name: import_zod7.z.string().optional().describe("New name for the network volume"),
|
|
1801
|
+
size: import_zod7.z.number().optional().describe("New size in GB (must be larger than current)")
|
|
1078
1802
|
},
|
|
1803
|
+
{ title: "Update network volume", ...WRITE, idempotentHint: true },
|
|
1079
1804
|
async (params) => {
|
|
1080
1805
|
const { networkVolumeId, ...updateParams } = params;
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1806
|
+
const backend = backendFor("networkVolumes");
|
|
1807
|
+
const body = backend.mapUpdate(updateParams);
|
|
1808
|
+
const result = await callRestUrl(
|
|
1809
|
+
`${backend.base}${backend.get(networkVolumeId)}`,
|
|
1083
1810
|
"PATCH",
|
|
1084
|
-
|
|
1811
|
+
body
|
|
1085
1812
|
);
|
|
1086
|
-
return
|
|
1087
|
-
content: [
|
|
1088
|
-
{
|
|
1089
|
-
type: "text",
|
|
1090
|
-
text: JSON.stringify(result, null, 2)
|
|
1091
|
-
}
|
|
1092
|
-
]
|
|
1093
|
-
};
|
|
1813
|
+
return jsonReply2(result);
|
|
1094
1814
|
}
|
|
1095
1815
|
);
|
|
1096
1816
|
server.tool(
|
|
1097
1817
|
"delete-network-volume",
|
|
1818
|
+
"Permanently delete a network volume and its data. This cannot be undone.",
|
|
1098
1819
|
{
|
|
1099
|
-
networkVolumeId:
|
|
1820
|
+
networkVolumeId: import_zod7.z.string().describe("ID of the network volume to delete")
|
|
1100
1821
|
},
|
|
1822
|
+
{ title: "Delete network volume", ...DESTRUCTIVE },
|
|
1101
1823
|
async (params) => {
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1824
|
+
const backend = backendFor("networkVolumes");
|
|
1825
|
+
const result = await callRestUrl(
|
|
1826
|
+
`${backend.base}${backend.get(params.networkVolumeId)}`,
|
|
1104
1827
|
"DELETE"
|
|
1105
1828
|
);
|
|
1106
|
-
return
|
|
1107
|
-
content: [
|
|
1108
|
-
{
|
|
1109
|
-
type: "text",
|
|
1110
|
-
text: JSON.stringify(result, null, 2)
|
|
1111
|
-
}
|
|
1112
|
-
]
|
|
1113
|
-
};
|
|
1829
|
+
return jsonReply2(result);
|
|
1114
1830
|
}
|
|
1115
1831
|
);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// src/tools/registries.ts
|
|
1835
|
+
var import_zod8 = require("zod");
|
|
1836
|
+
function registerRegistryTools(server, rt) {
|
|
1837
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
1116
1838
|
server.tool(
|
|
1117
1839
|
"list-container-registry-auths",
|
|
1840
|
+
"List your saved container-registry credentials (private image-pull auth). Paginated via limit/cursor.",
|
|
1118
1841
|
listPaginationParams,
|
|
1842
|
+
{ title: "List container registry auths", ...READ_ONLY },
|
|
1119
1843
|
async (params) => {
|
|
1120
|
-
const
|
|
1121
|
-
|
|
1844
|
+
const backend = backendFor("registries");
|
|
1845
|
+
const result = await callRestUrl(`${backend.base}${backend.list}`);
|
|
1846
|
+
return capListResult(backend.unwrap(result), {
|
|
1122
1847
|
limit: params.limit,
|
|
1123
1848
|
cursor: params.cursor
|
|
1124
1849
|
});
|
|
@@ -1126,66 +1851,299 @@ function registerTools(server, ctx) {
|
|
|
1126
1851
|
);
|
|
1127
1852
|
server.tool(
|
|
1128
1853
|
"get-container-registry-auth",
|
|
1854
|
+
"Get one saved container-registry credential by id (the secret/password is not returned).",
|
|
1129
1855
|
{
|
|
1130
|
-
containerRegistryAuthId:
|
|
1856
|
+
containerRegistryAuthId: import_zod8.z.string().describe("ID of the container registry auth to retrieve")
|
|
1131
1857
|
},
|
|
1858
|
+
{ title: "Get container registry auth", ...READ_ONLY },
|
|
1132
1859
|
async (params) => {
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1860
|
+
const backend = backendFor("registries");
|
|
1861
|
+
const result = await callRestUrl(
|
|
1862
|
+
`${backend.base}${backend.get(params.containerRegistryAuthId)}`
|
|
1135
1863
|
);
|
|
1136
|
-
return
|
|
1137
|
-
content: [
|
|
1138
|
-
{
|
|
1139
|
-
type: "text",
|
|
1140
|
-
text: JSON.stringify(result, null, 2)
|
|
1141
|
-
}
|
|
1142
|
-
]
|
|
1143
|
-
};
|
|
1864
|
+
return jsonReply2(result);
|
|
1144
1865
|
}
|
|
1145
1866
|
);
|
|
1146
1867
|
server.tool(
|
|
1147
1868
|
"create-container-registry-auth",
|
|
1869
|
+
"Save container-registry credentials (username + password/token) so pods and endpoints can pull private images.",
|
|
1148
1870
|
{
|
|
1149
|
-
name:
|
|
1150
|
-
username:
|
|
1151
|
-
password:
|
|
1871
|
+
name: import_zod8.z.string().describe("Name for the container registry auth"),
|
|
1872
|
+
username: import_zod8.z.string().describe("Registry username"),
|
|
1873
|
+
password: import_zod8.z.string().describe("Registry password or access token (stored as a secret)")
|
|
1152
1874
|
},
|
|
1875
|
+
{ title: "Create container registry auth", ...WRITE },
|
|
1153
1876
|
async (params) => {
|
|
1154
|
-
const
|
|
1155
|
-
|
|
1877
|
+
const backend = backendFor("registries");
|
|
1878
|
+
const body = backend.mapCreate(params);
|
|
1879
|
+
const result = await callRestUrl(
|
|
1880
|
+
`${backend.base}${backend.list}`,
|
|
1156
1881
|
"POST",
|
|
1157
|
-
|
|
1882
|
+
body
|
|
1158
1883
|
);
|
|
1159
|
-
return
|
|
1160
|
-
content: [
|
|
1161
|
-
{
|
|
1162
|
-
type: "text",
|
|
1163
|
-
text: JSON.stringify(result, null, 2)
|
|
1164
|
-
}
|
|
1165
|
-
]
|
|
1166
|
-
};
|
|
1884
|
+
return jsonReply2(result);
|
|
1167
1885
|
}
|
|
1168
1886
|
);
|
|
1169
1887
|
server.tool(
|
|
1170
1888
|
"delete-container-registry-auth",
|
|
1889
|
+
"Permanently delete a saved container-registry credential. This cannot be undone.",
|
|
1171
1890
|
{
|
|
1172
|
-
containerRegistryAuthId:
|
|
1891
|
+
containerRegistryAuthId: import_zod8.z.string().describe("ID of the container registry auth to delete")
|
|
1173
1892
|
},
|
|
1893
|
+
{ title: "Delete container registry auth", ...DESTRUCTIVE },
|
|
1174
1894
|
async (params) => {
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1895
|
+
const backend = backendFor("registries");
|
|
1896
|
+
const result = await callRestUrl(
|
|
1897
|
+
`${backend.base}${backend.get(params.containerRegistryAuthId)}`,
|
|
1177
1898
|
"DELETE"
|
|
1178
1899
|
);
|
|
1179
|
-
return
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1900
|
+
return jsonReply2(result);
|
|
1901
|
+
}
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
// src/tools/tags.ts
|
|
1906
|
+
var import_zod9 = require("zod");
|
|
1907
|
+
var TAG_RESOURCE_TYPES = [
|
|
1908
|
+
"POD",
|
|
1909
|
+
"NETWORK_VOLUME",
|
|
1910
|
+
"CLUSTER",
|
|
1911
|
+
"SERVERLESS_ENDPOINT"
|
|
1912
|
+
];
|
|
1913
|
+
var taggedResourceSchema = import_zod9.z.object({
|
|
1914
|
+
id: import_zod9.z.string().describe("Resource identifier (e.g. a pod or endpoint id)"),
|
|
1915
|
+
type: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to associate with the tag")
|
|
1916
|
+
});
|
|
1917
|
+
function v2OnlyNotice(tool) {
|
|
1918
|
+
return {
|
|
1919
|
+
error: `${tool} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,
|
|
1920
|
+
status: 501
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
function registerTagTools(server, rt) {
|
|
1924
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
1925
|
+
server.tool(
|
|
1926
|
+
"list-tags",
|
|
1927
|
+
"List your tags (key/value labels on pods, volumes, clusters, and endpoints). v2-only \u2014 returns a 501 notice on the v1 API. Paginated via limit/cursor.",
|
|
1928
|
+
{
|
|
1929
|
+
...listPaginationParams,
|
|
1930
|
+
includeResources: import_zod9.z.boolean().optional().describe("Include the resources attached to each tag in the response")
|
|
1931
|
+
},
|
|
1932
|
+
{ title: "List tags", ...READ_ONLY },
|
|
1933
|
+
async (params) => {
|
|
1934
|
+
const backend = backendFor("tags");
|
|
1935
|
+
if (backend.version === "v1") return jsonReply2(v2OnlyNotice("list-tags"));
|
|
1936
|
+
const query = params.includeResources ? "?include=resources" : "";
|
|
1937
|
+
const raw = await callRestUrl(`${backend.base}${backend.list}${query}`);
|
|
1938
|
+
return capListResult(backend.unwrap(raw), {
|
|
1939
|
+
limit: params.limit,
|
|
1940
|
+
cursor: params.cursor
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
);
|
|
1944
|
+
server.tool(
|
|
1945
|
+
"get-tag",
|
|
1946
|
+
"Get one tag by id, optionally including its attached resources. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
1947
|
+
{
|
|
1948
|
+
tagId: import_zod9.z.string().describe("ID of the tag to retrieve"),
|
|
1949
|
+
includeResources: import_zod9.z.boolean().optional().describe("Include the resources attached to this tag")
|
|
1950
|
+
},
|
|
1951
|
+
{ title: "Get tag", ...READ_ONLY },
|
|
1952
|
+
async (params) => {
|
|
1953
|
+
const backend = backendFor("tags");
|
|
1954
|
+
if (backend.version === "v1") return jsonReply2(v2OnlyNotice("get-tag"));
|
|
1955
|
+
const query = params.includeResources ? "?include=resources" : "";
|
|
1956
|
+
const result = await callRestUrl(
|
|
1957
|
+
`${backend.base}${backend.get(params.tagId)}${query}`
|
|
1958
|
+
);
|
|
1959
|
+
return jsonReply2(result);
|
|
1187
1960
|
}
|
|
1188
1961
|
);
|
|
1962
|
+
server.tool(
|
|
1963
|
+
"create-tag",
|
|
1964
|
+
"Create a key/value tag, optionally attaching resources at creation. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
1965
|
+
{
|
|
1966
|
+
key: import_zod9.z.string().min(1).describe('Tag key (e.g. "project")'),
|
|
1967
|
+
value: import_zod9.z.string().min(1).describe('Tag value (e.g. "gpt-training")'),
|
|
1968
|
+
resources: import_zod9.z.array(taggedResourceSchema).optional().describe("Resources to attach to the tag at creation time")
|
|
1969
|
+
},
|
|
1970
|
+
{ title: "Create tag", ...WRITE },
|
|
1971
|
+
async (params) => {
|
|
1972
|
+
const backend = backendFor("tags");
|
|
1973
|
+
if (backend.version === "v1")
|
|
1974
|
+
return jsonReply2(v2OnlyNotice("create-tag"));
|
|
1975
|
+
const result = await callRestUrl(
|
|
1976
|
+
`${backend.base}${backend.list}`,
|
|
1977
|
+
"POST",
|
|
1978
|
+
params
|
|
1979
|
+
);
|
|
1980
|
+
return jsonReply2(result);
|
|
1981
|
+
}
|
|
1982
|
+
);
|
|
1983
|
+
server.tool(
|
|
1984
|
+
"update-tag",
|
|
1985
|
+
"Update a tag (rename its key/value, or replace its resource associations). Provide at least one of key, value, or resources. Sending resources:[] removes all associations; omitting resources leaves them unchanged. Replacing resources is NOT atomic \u2014 a failed update can leave a partially-applied set, so for small association changes prefer attach-tag/detach-tag. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
1986
|
+
{
|
|
1987
|
+
tagId: import_zod9.z.string().describe("ID of the tag to update"),
|
|
1988
|
+
key: import_zod9.z.string().min(1).optional().describe("New tag key"),
|
|
1989
|
+
value: import_zod9.z.string().min(1).optional().describe("New tag value"),
|
|
1990
|
+
resources: import_zod9.z.array(taggedResourceSchema).optional().describe(
|
|
1991
|
+
"Replace the tag's resource associations with exactly these. Omit to leave unchanged; pass [] to detach all."
|
|
1992
|
+
)
|
|
1993
|
+
},
|
|
1994
|
+
// No idempotentHint: replacing `resources` is non-atomic upstream, so a
|
|
1995
|
+
// retried-after-partial-failure update is not guaranteed to be a no-op.
|
|
1996
|
+
{ title: "Update tag", ...WRITE },
|
|
1997
|
+
async (params) => {
|
|
1998
|
+
const { tagId, ...updateParams } = params;
|
|
1999
|
+
const backend = backendFor("tags");
|
|
2000
|
+
if (backend.version === "v1")
|
|
2001
|
+
return jsonReply2(v2OnlyNotice("update-tag"));
|
|
2002
|
+
const result = await callRestUrl(
|
|
2003
|
+
`${backend.base}${backend.get(tagId)}`,
|
|
2004
|
+
"PATCH",
|
|
2005
|
+
updateParams
|
|
2006
|
+
);
|
|
2007
|
+
return jsonReply2(result);
|
|
2008
|
+
}
|
|
2009
|
+
);
|
|
2010
|
+
server.tool(
|
|
2011
|
+
"delete-tag",
|
|
2012
|
+
"Permanently delete a tag (removes it from all resources it is attached to). This cannot be undone. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
2013
|
+
{
|
|
2014
|
+
tagId: import_zod9.z.string().describe("ID of the tag to delete")
|
|
2015
|
+
},
|
|
2016
|
+
{ title: "Delete tag", ...DESTRUCTIVE },
|
|
2017
|
+
async (params) => {
|
|
2018
|
+
const backend = backendFor("tags");
|
|
2019
|
+
if (backend.version === "v1")
|
|
2020
|
+
return jsonReply2(v2OnlyNotice("delete-tag"));
|
|
2021
|
+
const result = await callRestUrl(
|
|
2022
|
+
`${backend.base}${backend.get(params.tagId)}`,
|
|
2023
|
+
"DELETE"
|
|
2024
|
+
);
|
|
2025
|
+
return jsonReply2(result);
|
|
2026
|
+
}
|
|
2027
|
+
);
|
|
2028
|
+
server.tool(
|
|
2029
|
+
"attach-tag",
|
|
2030
|
+
"Attach a tag to a resource (pod, network volume, cluster, or serverless endpoint). Idempotent \u2014 attaching an already-associated resource is a no-op. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
2031
|
+
{
|
|
2032
|
+
tagId: import_zod9.z.string().describe("ID of the tag"),
|
|
2033
|
+
resourceType: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to attach the tag to"),
|
|
2034
|
+
resourceId: import_zod9.z.string().describe("ID of the resource to attach the tag to")
|
|
2035
|
+
},
|
|
2036
|
+
{ title: "Attach tag to resource", ...WRITE, idempotentHint: true },
|
|
2037
|
+
async (params) => {
|
|
2038
|
+
const backend = backendFor("tags");
|
|
2039
|
+
if (backend.version === "v1")
|
|
2040
|
+
return jsonReply2(v2OnlyNotice("attach-tag"));
|
|
2041
|
+
const result = await callRestUrl(
|
|
2042
|
+
`${backend.base}/tags/${encodeURIComponent(params.tagId)}/resources/${params.resourceType}/${encodeURIComponent(params.resourceId)}`,
|
|
2043
|
+
"PUT"
|
|
2044
|
+
);
|
|
2045
|
+
return jsonReply2(result);
|
|
2046
|
+
}
|
|
2047
|
+
);
|
|
2048
|
+
server.tool(
|
|
2049
|
+
"detach-tag",
|
|
2050
|
+
"Detach a tag from a resource (removes the association only \u2014 the resource and tag are not deleted). Idempotent \u2014 detaching a resource that is not associated is a no-op. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
2051
|
+
{
|
|
2052
|
+
tagId: import_zod9.z.string().describe("ID of the tag"),
|
|
2053
|
+
resourceType: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to detach the tag from"),
|
|
2054
|
+
resourceId: import_zod9.z.string().describe("ID of the resource to detach the tag from")
|
|
2055
|
+
},
|
|
2056
|
+
{ title: "Detach tag from resource", ...WRITE, idempotentHint: true },
|
|
2057
|
+
async (params) => {
|
|
2058
|
+
const backend = backendFor("tags");
|
|
2059
|
+
if (backend.version === "v1")
|
|
2060
|
+
return jsonReply2(v2OnlyNotice("detach-tag"));
|
|
2061
|
+
const result = await callRestUrl(
|
|
2062
|
+
`${backend.base}/tags/${encodeURIComponent(params.tagId)}/resources/${params.resourceType}/${encodeURIComponent(params.resourceId)}`,
|
|
2063
|
+
"DELETE"
|
|
2064
|
+
);
|
|
2065
|
+
return jsonReply2(result);
|
|
2066
|
+
}
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
// src/tools/billing.ts
|
|
2071
|
+
var import_zod10 = require("zod");
|
|
2072
|
+
var BILLING_SCOPE_PATH = {
|
|
2073
|
+
all: "/billing",
|
|
2074
|
+
pods: "/billing/pods",
|
|
2075
|
+
serverless: "/billing/serverless",
|
|
2076
|
+
endpoints: "/billing/endpoints",
|
|
2077
|
+
networkvolumes: "/billing/networkvolumes",
|
|
2078
|
+
clusters: "/billing/clusters"
|
|
2079
|
+
};
|
|
2080
|
+
function registerBillingTools(server, rt) {
|
|
2081
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
2082
|
+
server.tool(
|
|
2083
|
+
"get-billing",
|
|
2084
|
+
"Get time-bucketed Runpod spend history, either aggregated across all resources or broken down by resource type (pods, serverless, endpoints, network volumes, clusters). v2-only \u2014 returns a 501 notice on the v1 API. Records are capped via limit/cursor; narrow the window with startTime/endTime or lastN.",
|
|
2085
|
+
{
|
|
2086
|
+
...listPaginationParams,
|
|
2087
|
+
scope: import_zod10.z.enum([
|
|
2088
|
+
"all",
|
|
2089
|
+
"pods",
|
|
2090
|
+
"serverless",
|
|
2091
|
+
"endpoints",
|
|
2092
|
+
"networkvolumes",
|
|
2093
|
+
"clusters"
|
|
2094
|
+
]).optional().describe(
|
|
2095
|
+
'Which billing breakdown to fetch. "all" (default) is the aggregate across every resource.'
|
|
2096
|
+
),
|
|
2097
|
+
startTime: import_zod10.z.string().optional().describe(
|
|
2098
|
+
"Start of the period (RFC 3339, e.g. 2026-05-01T00:00:00Z). Defaults to 30 days ago. Mutually exclusive with lastN."
|
|
2099
|
+
),
|
|
2100
|
+
endTime: import_zod10.z.string().optional().describe(
|
|
2101
|
+
"End of the period (RFC 3339), exclusive. Defaults to now. Mutually exclusive with lastN."
|
|
2102
|
+
),
|
|
2103
|
+
bucketSize: import_zod10.z.enum(["hour", "day", "week", "month", "year"]).optional().describe("Length of each time bucket. Defaults to day."),
|
|
2104
|
+
lastN: import_zod10.z.number().int().positive().optional().describe(
|
|
2105
|
+
"Return the last N buckets of bucketSize, ending with the current one. Mutually exclusive with startTime/endTime."
|
|
2106
|
+
)
|
|
2107
|
+
},
|
|
2108
|
+
{ title: "Get billing history", ...READ_ONLY },
|
|
2109
|
+
async (params) => {
|
|
2110
|
+
const backend = backendFor("billing");
|
|
2111
|
+
if (backend.version === "v1") {
|
|
2112
|
+
return jsonReply2({
|
|
2113
|
+
error: "get-billing is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
|
|
2114
|
+
status: 501
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
const subPath = BILLING_SCOPE_PATH[params.scope ?? "all"];
|
|
2118
|
+
const query = new URLSearchParams();
|
|
2119
|
+
if (params.startTime) query.set("startTime", params.startTime);
|
|
2120
|
+
if (params.endTime) query.set("endTime", params.endTime);
|
|
2121
|
+
if (params.bucketSize) query.set("bucketSize", params.bucketSize);
|
|
2122
|
+
if (params.lastN !== void 0 && !params.startTime && !params.endTime)
|
|
2123
|
+
query.set("lastN", String(params.lastN));
|
|
2124
|
+
const qs = query.toString() ? `?${query.toString()}` : "";
|
|
2125
|
+
const raw = await callRestUrl(`${backend.base}${subPath}${qs}`);
|
|
2126
|
+
return capListResult(
|
|
2127
|
+
backend.unwrap(raw),
|
|
2128
|
+
{ limit: params.limit, cursor: params.cursor },
|
|
2129
|
+
{ metadata: raw?.metadata }
|
|
2130
|
+
);
|
|
2131
|
+
}
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// src/tools.ts
|
|
2136
|
+
function registerTools(server, ctx, deps = {}) {
|
|
2137
|
+
const rt = createToolRuntime(server, ctx, deps);
|
|
2138
|
+
registerCatalogTools(server, rt);
|
|
2139
|
+
registerPodTools(server, rt);
|
|
2140
|
+
registerEndpointTools(server, rt);
|
|
2141
|
+
registerJobTools(server, rt);
|
|
2142
|
+
registerTemplateTools(server, rt);
|
|
2143
|
+
registerNetworkVolumeTools(server, rt);
|
|
2144
|
+
registerRegistryTools(server, rt);
|
|
2145
|
+
registerTagTools(server, rt);
|
|
2146
|
+
registerBillingTools(server, rt);
|
|
1189
2147
|
}
|
|
1190
2148
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1191
2149
|
0 && (module.exports = {
|