@runpod/mcp-server 1.3.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 +61 -0
- package/README.md +177 -47
- package/dist/chunk-ZP3TCVXG.mjs +2116 -0
- package/dist/chunk-ZP3TCVXG.mjs.map +1 -0
- package/dist/http.cjs +1596 -588
- package/dist/http.cjs.map +1 -1
- package/dist/http.mjs +2 -2
- package/dist/stdio.cjs +2141 -634
- package/dist/stdio.cjs.map +1 -1
- package/dist/stdio.mjs +1719 -631
- package/dist/stdio.mjs.map +1 -1
- package/dist/tools.cjs +1596 -586
- 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/dist/wizard-37ZJSVB5.mjs +402 -0
- package/dist/wizard-37ZJSVB5.mjs.map +1 -0
- package/package.json +6 -2
- package/dist/chunk-CRHHCQRI.mjs +0 -1108
- package/dist/chunk-CRHHCQRI.mjs.map +0 -1
package/dist/stdio.cjs
CHANGED
|
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
9
16
|
var __copyProps = (to, from, except, desc) => {
|
|
10
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,13 +30,426 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
30
|
mod
|
|
24
31
|
));
|
|
25
32
|
|
|
33
|
+
// src/install/clients.ts
|
|
34
|
+
function localServerConfig(apiKey, serverProperty) {
|
|
35
|
+
const config = {
|
|
36
|
+
command: "npx",
|
|
37
|
+
args: ["-y", NPM_PACKAGE],
|
|
38
|
+
env: { RUNPOD_API_KEY: apiKey }
|
|
39
|
+
};
|
|
40
|
+
return serverProperty === "servers" ? { type: "stdio", ...config } : config;
|
|
41
|
+
}
|
|
42
|
+
function hostedServerConfig(strategy, serverProperty, url) {
|
|
43
|
+
if (strategy === "mcp-remote") {
|
|
44
|
+
return { command: "npx", args: ["-y", "mcp-remote", url] };
|
|
45
|
+
}
|
|
46
|
+
return serverProperty === "servers" ? { type: "http", url } : { url };
|
|
47
|
+
}
|
|
48
|
+
function upsertJsonServer(configPath, serverProperty, value) {
|
|
49
|
+
try {
|
|
50
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
51
|
+
const content = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
|
|
52
|
+
const edits = jsonc.modify(content, [serverProperty, SERVER_NAME2], value, {
|
|
53
|
+
formattingOptions: { tabSize: 2, insertSpaces: true }
|
|
54
|
+
});
|
|
55
|
+
fs.writeFileSync(configPath, jsonc.applyEdits(content, edits), "utf8");
|
|
56
|
+
return { success: true };
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return { success: false, message: errMessage(error) };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function removeJsonServer(configPath, serverProperty) {
|
|
62
|
+
try {
|
|
63
|
+
if (!fs.existsSync(configPath)) {
|
|
64
|
+
return { success: true, message: "nothing to remove" };
|
|
65
|
+
}
|
|
66
|
+
const content = fs.readFileSync(configPath, "utf8");
|
|
67
|
+
const edits = jsonc.modify(
|
|
68
|
+
content,
|
|
69
|
+
[serverProperty, SERVER_NAME2],
|
|
70
|
+
void 0,
|
|
71
|
+
{ formattingOptions: { tabSize: 2, insertSpaces: true } }
|
|
72
|
+
);
|
|
73
|
+
fs.writeFileSync(configPath, jsonc.applyEdits(content, edits), "utf8");
|
|
74
|
+
return { success: true };
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return { success: false, message: errMessage(error) };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function errMessage(error) {
|
|
80
|
+
return error instanceof Error ? error.message : String(error);
|
|
81
|
+
}
|
|
82
|
+
function exists(p2) {
|
|
83
|
+
try {
|
|
84
|
+
return fs.existsSync(p2);
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function jsonClient(opts) {
|
|
90
|
+
const serverProperty = opts.serverProperty ?? "mcpServers";
|
|
91
|
+
const hostedStrategy = opts.hostedStrategy ?? "http";
|
|
92
|
+
return {
|
|
93
|
+
id: opts.id,
|
|
94
|
+
name: opts.name,
|
|
95
|
+
detect: () => Promise.resolve(
|
|
96
|
+
(opts.detectPaths?.() ?? [path.dirname(opts.configPath())]).some(exists)
|
|
97
|
+
),
|
|
98
|
+
add: (mode) => {
|
|
99
|
+
const value = mode.kind === "local" ? localServerConfig(mode.apiKey, serverProperty) : hostedServerConfig(hostedStrategy, serverProperty, mode.url);
|
|
100
|
+
return Promise.resolve(
|
|
101
|
+
upsertJsonServer(opts.configPath(), serverProperty, value)
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
remove: () => Promise.resolve(removeJsonServer(opts.configPath(), serverProperty)),
|
|
105
|
+
describeTarget: () => opts.configPath()
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function findClaudeBinary() {
|
|
109
|
+
const candidates = [
|
|
110
|
+
path.join(os.homedir(), ".claude", "local", "claude"),
|
|
111
|
+
"/usr/local/bin/claude",
|
|
112
|
+
"/opt/homebrew/bin/claude"
|
|
113
|
+
];
|
|
114
|
+
for (const candidate of candidates) {
|
|
115
|
+
if (exists(candidate)) return candidate;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
(0, import_child_process.execSync)("command -v claude", { stdio: "pipe" });
|
|
119
|
+
return "claude";
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function runClaude(binary, args) {
|
|
125
|
+
try {
|
|
126
|
+
(0, import_child_process.execFileSync)(binary, args, { stdio: "pipe" });
|
|
127
|
+
return { success: true };
|
|
128
|
+
} catch (error) {
|
|
129
|
+
const message = errMessage(error);
|
|
130
|
+
if (message.includes("already exists")) {
|
|
131
|
+
return { success: true, message: "already configured" };
|
|
132
|
+
}
|
|
133
|
+
return { success: false, message };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function claudeDesktopConfigPath() {
|
|
137
|
+
if (process.platform === "win32") {
|
|
138
|
+
return path.join(appData, "Claude", "claude_desktop_config.json");
|
|
139
|
+
}
|
|
140
|
+
return path.join(
|
|
141
|
+
home,
|
|
142
|
+
"Library",
|
|
143
|
+
"Application Support",
|
|
144
|
+
"Claude",
|
|
145
|
+
"claude_desktop_config.json"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
function vsCodeConfigPath() {
|
|
149
|
+
if (process.platform === "win32") {
|
|
150
|
+
return path.join(appData, "Code", "User", "mcp.json");
|
|
151
|
+
}
|
|
152
|
+
if (process.platform === "darwin") {
|
|
153
|
+
return path.join(
|
|
154
|
+
home,
|
|
155
|
+
"Library",
|
|
156
|
+
"Application Support",
|
|
157
|
+
"Code",
|
|
158
|
+
"User",
|
|
159
|
+
"mcp.json"
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return path.join(home, ".config", "Code", "User", "mcp.json");
|
|
163
|
+
}
|
|
164
|
+
var fs, os, path, import_child_process, jsonc, SERVER_NAME2, NPM_PACKAGE, HOSTED_URL, claudeCodeClient, home, appData, CLIENTS;
|
|
165
|
+
var init_clients = __esm({
|
|
166
|
+
"src/install/clients.ts"() {
|
|
167
|
+
"use strict";
|
|
168
|
+
fs = __toESM(require("fs"), 1);
|
|
169
|
+
os = __toESM(require("os"), 1);
|
|
170
|
+
path = __toESM(require("path"), 1);
|
|
171
|
+
import_child_process = require("child_process");
|
|
172
|
+
jsonc = __toESM(require("jsonc-parser"), 1);
|
|
173
|
+
SERVER_NAME2 = "runpod";
|
|
174
|
+
NPM_PACKAGE = "@runpod/mcp-server@latest";
|
|
175
|
+
HOSTED_URL = process.env.RUNPOD_MCP_URL ?? "https://mcp.getrunpod.io/";
|
|
176
|
+
claudeCodeClient = {
|
|
177
|
+
id: "claude-code",
|
|
178
|
+
name: "Claude Code",
|
|
179
|
+
detect: () => Promise.resolve(findClaudeBinary() !== null),
|
|
180
|
+
add: (mode) => {
|
|
181
|
+
const binary = findClaudeBinary();
|
|
182
|
+
if (!binary) {
|
|
183
|
+
return Promise.resolve({
|
|
184
|
+
success: false,
|
|
185
|
+
message: "claude CLI not found"
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
const args = mode.kind === "local" ? [
|
|
189
|
+
"mcp",
|
|
190
|
+
"add",
|
|
191
|
+
SERVER_NAME2,
|
|
192
|
+
"--scope",
|
|
193
|
+
"user",
|
|
194
|
+
"-e",
|
|
195
|
+
`RUNPOD_API_KEY=${mode.apiKey}`,
|
|
196
|
+
"--",
|
|
197
|
+
"npx",
|
|
198
|
+
"-y",
|
|
199
|
+
NPM_PACKAGE
|
|
200
|
+
] : [
|
|
201
|
+
"mcp",
|
|
202
|
+
"add",
|
|
203
|
+
"--transport",
|
|
204
|
+
"http",
|
|
205
|
+
"--scope",
|
|
206
|
+
"user",
|
|
207
|
+
SERVER_NAME2,
|
|
208
|
+
mode.url
|
|
209
|
+
];
|
|
210
|
+
return Promise.resolve(runClaude(binary, args));
|
|
211
|
+
},
|
|
212
|
+
remove: () => {
|
|
213
|
+
const binary = findClaudeBinary();
|
|
214
|
+
if (!binary) {
|
|
215
|
+
return Promise.resolve({
|
|
216
|
+
success: false,
|
|
217
|
+
message: "claude CLI not found"
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
(0, import_child_process.execFileSync)(binary, ["mcp", "remove", "--scope", "user", SERVER_NAME2], {
|
|
222
|
+
stdio: "pipe"
|
|
223
|
+
});
|
|
224
|
+
return Promise.resolve({ success: true });
|
|
225
|
+
} catch (error) {
|
|
226
|
+
return Promise.resolve({ success: true, message: errMessage(error) });
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
describeTarget: () => "Claude Code user config (via `claude mcp` CLI)"
|
|
230
|
+
};
|
|
231
|
+
home = os.homedir();
|
|
232
|
+
appData = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
|
|
233
|
+
CLIENTS = [
|
|
234
|
+
claudeCodeClient,
|
|
235
|
+
jsonClient({
|
|
236
|
+
id: "cursor",
|
|
237
|
+
name: "Cursor",
|
|
238
|
+
configPath: () => path.join(home, ".cursor", "mcp.json")
|
|
239
|
+
}),
|
|
240
|
+
jsonClient({
|
|
241
|
+
id: "windsurf",
|
|
242
|
+
name: "Windsurf",
|
|
243
|
+
// Windsurf is stdio-only, so it reaches the hosted server via mcp-remote.
|
|
244
|
+
hostedStrategy: "mcp-remote",
|
|
245
|
+
configPath: () => path.join(home, ".codeium", "windsurf", "mcp_config.json")
|
|
246
|
+
}),
|
|
247
|
+
jsonClient({
|
|
248
|
+
id: "claude-desktop",
|
|
249
|
+
name: "Claude Desktop",
|
|
250
|
+
// Claude Desktop config files are stdio-only; use the mcp-remote bridge.
|
|
251
|
+
hostedStrategy: "mcp-remote",
|
|
252
|
+
configPath: claudeDesktopConfigPath,
|
|
253
|
+
// Detect by the application/support directory, which exists once the app runs.
|
|
254
|
+
detectPaths: () => [path.dirname(claudeDesktopConfigPath())]
|
|
255
|
+
}),
|
|
256
|
+
jsonClient({
|
|
257
|
+
id: "vscode",
|
|
258
|
+
name: "Visual Studio Code",
|
|
259
|
+
serverProperty: "servers",
|
|
260
|
+
configPath: vsCodeConfigPath,
|
|
261
|
+
detectPaths: () => [path.dirname(vsCodeConfigPath())]
|
|
262
|
+
})
|
|
263
|
+
];
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// src/install/wizard.ts
|
|
268
|
+
var wizard_exports = {};
|
|
269
|
+
__export(wizard_exports, {
|
|
270
|
+
runWizard: () => runWizard
|
|
271
|
+
});
|
|
272
|
+
function openBrowser(url) {
|
|
273
|
+
try {
|
|
274
|
+
if (process.platform === "darwin") {
|
|
275
|
+
(0, import_child_process2.execFileSync)("open", [url], { stdio: "ignore" });
|
|
276
|
+
} else if (process.platform === "win32") {
|
|
277
|
+
(0, import_child_process2.execFileSync)("cmd", ["/c", "start", "", url], { stdio: "ignore" });
|
|
278
|
+
} else {
|
|
279
|
+
(0, import_child_process2.execFileSync)("xdg-open", [url], { stdio: "ignore" });
|
|
280
|
+
}
|
|
281
|
+
} catch {
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async function verifyApiKey(apiKey) {
|
|
285
|
+
try {
|
|
286
|
+
const base = process.env.RUNPOD_REST_API_URL ?? "https://rest.runpod.io/v1";
|
|
287
|
+
const response = await fetch(`${base}/pods`, {
|
|
288
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
289
|
+
});
|
|
290
|
+
if (response.ok) return true;
|
|
291
|
+
if (response.status === 401 || response.status === 403) return false;
|
|
292
|
+
return null;
|
|
293
|
+
} catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function bail(message = "Cancelled.") {
|
|
298
|
+
p.cancel(message);
|
|
299
|
+
process.exit(0);
|
|
300
|
+
}
|
|
301
|
+
async function promptForApiKey() {
|
|
302
|
+
const fromEnv = process.env.RUNPOD_API_KEY;
|
|
303
|
+
if (fromEnv) {
|
|
304
|
+
const useEnv = await p.confirm({
|
|
305
|
+
message: "Use the RUNPOD_API_KEY from your environment?"
|
|
306
|
+
});
|
|
307
|
+
if (p.isCancel(useEnv)) bail();
|
|
308
|
+
if (useEnv) return fromEnv;
|
|
309
|
+
}
|
|
310
|
+
const create = await p.confirm({
|
|
311
|
+
message: "Open the Runpod console to create or copy an API key?"
|
|
312
|
+
});
|
|
313
|
+
if (p.isCancel(create)) bail();
|
|
314
|
+
if (create) {
|
|
315
|
+
openBrowser(API_KEYS_URL);
|
|
316
|
+
p.log.info(`Opening ${API_KEYS_URL}`);
|
|
317
|
+
}
|
|
318
|
+
const apiKey = await p.password({
|
|
319
|
+
message: "Paste your Runpod API key",
|
|
320
|
+
validate: (value) => !value || value.trim().length === 0 ? "API key cannot be empty" : void 0
|
|
321
|
+
});
|
|
322
|
+
if (p.isCancel(apiKey)) bail();
|
|
323
|
+
return apiKey.trim();
|
|
324
|
+
}
|
|
325
|
+
async function selectClients(detected, verb) {
|
|
326
|
+
const options = CLIENTS.map((client) => ({
|
|
327
|
+
value: client.id,
|
|
328
|
+
label: client.name,
|
|
329
|
+
hint: detected.get(client.id) ? "detected" : void 0
|
|
330
|
+
}));
|
|
331
|
+
const selected = await p.multiselect({
|
|
332
|
+
message: `Which agents should we ${verb}?`,
|
|
333
|
+
options,
|
|
334
|
+
// Pre-select detected clients so the common case is a single Enter press.
|
|
335
|
+
initialValues: CLIENTS.filter((c) => detected.get(c.id)).map((c) => c.id),
|
|
336
|
+
required: true
|
|
337
|
+
});
|
|
338
|
+
if (p.isCancel(selected)) bail();
|
|
339
|
+
return CLIENTS.filter((c) => selected.includes(c.id));
|
|
340
|
+
}
|
|
341
|
+
async function detectAll() {
|
|
342
|
+
const entries = await Promise.all(
|
|
343
|
+
CLIENTS.map(async (c) => [c.id, await c.detect()])
|
|
344
|
+
);
|
|
345
|
+
return new Map(entries);
|
|
346
|
+
}
|
|
347
|
+
async function selectMode() {
|
|
348
|
+
const kind = await p.select({
|
|
349
|
+
message: "How do you want to connect to Runpod?",
|
|
350
|
+
options: [
|
|
351
|
+
{
|
|
352
|
+
value: "hosted",
|
|
353
|
+
label: "Sign in with Runpod (recommended)",
|
|
354
|
+
hint: "hosted, OAuth, no API key stored"
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
value: "local",
|
|
358
|
+
label: "Run locally with an API key",
|
|
359
|
+
hint: "runs on this machine via npx"
|
|
360
|
+
}
|
|
361
|
+
],
|
|
362
|
+
initialValue: "hosted"
|
|
363
|
+
});
|
|
364
|
+
if (p.isCancel(kind)) bail();
|
|
365
|
+
if (kind === "hosted") {
|
|
366
|
+
return { kind: "hosted", url: HOSTED_URL };
|
|
367
|
+
}
|
|
368
|
+
const apiKey = await promptForApiKey();
|
|
369
|
+
const verifySpinner = p.spinner();
|
|
370
|
+
verifySpinner.start("Verifying API key");
|
|
371
|
+
const valid = await verifyApiKey(apiKey);
|
|
372
|
+
if (valid === true) {
|
|
373
|
+
verifySpinner.stop("API key verified.");
|
|
374
|
+
} else if (valid === false) {
|
|
375
|
+
verifySpinner.stop("API key rejected by Runpod.");
|
|
376
|
+
const proceed = await p.confirm({
|
|
377
|
+
message: "The key did not authenticate. Continue anyway?",
|
|
378
|
+
initialValue: false
|
|
379
|
+
});
|
|
380
|
+
if (p.isCancel(proceed) || !proceed) bail();
|
|
381
|
+
} else {
|
|
382
|
+
verifySpinner.stop("Could not verify the key (offline?). Continuing.");
|
|
383
|
+
}
|
|
384
|
+
return { kind: "local", apiKey };
|
|
385
|
+
}
|
|
386
|
+
async function runAdd() {
|
|
387
|
+
const detected = await detectAll();
|
|
388
|
+
const clients = await selectClients(detected, "set up");
|
|
389
|
+
const mode = await selectMode();
|
|
390
|
+
const results = [];
|
|
391
|
+
for (const client of clients) {
|
|
392
|
+
const spinner2 = p.spinner();
|
|
393
|
+
spinner2.start(`Configuring ${client.name}`);
|
|
394
|
+
const result = await client.add(mode);
|
|
395
|
+
if (result.success) {
|
|
396
|
+
spinner2.stop(`${client.name} configured.`);
|
|
397
|
+
results.push(`\u2713 ${client.name} \u2014 ${client.describeTarget()}`);
|
|
398
|
+
} else {
|
|
399
|
+
spinner2.stop(`${client.name} failed.`);
|
|
400
|
+
results.push(`\u2717 ${client.name} \u2014 ${result.message ?? "unknown error"}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
p.note(results.join("\n"), "Results");
|
|
404
|
+
p.outro(
|
|
405
|
+
mode.kind === "hosted" ? 'Restart your agent, then complete "Sign in with Runpod" when it connects.' : "Restart your agent (or reconnect MCP) to load the Runpod tools."
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
async function runRemove() {
|
|
409
|
+
const detected = await detectAll();
|
|
410
|
+
const clients = await selectClients(detected, "remove Runpod from");
|
|
411
|
+
const results = [];
|
|
412
|
+
for (const client of clients) {
|
|
413
|
+
const spinner2 = p.spinner();
|
|
414
|
+
spinner2.start(`Removing from ${client.name}`);
|
|
415
|
+
const result = await client.remove();
|
|
416
|
+
spinner2.stop(
|
|
417
|
+
result.success ? `${client.name} cleaned up.` : `${client.name} failed.`
|
|
418
|
+
);
|
|
419
|
+
results.push(
|
|
420
|
+
`${result.success ? "\u2713" : "\u2717"} ${client.name}${result.message ? ` \u2014 ${result.message}` : ""}`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
p.note(results.join("\n"), "Results");
|
|
424
|
+
p.outro("Done.");
|
|
425
|
+
}
|
|
426
|
+
async function runWizard(subcommand2) {
|
|
427
|
+
p.intro("Runpod MCP server installer");
|
|
428
|
+
if (subcommand2 === "remove") {
|
|
429
|
+
await runRemove();
|
|
430
|
+
} else {
|
|
431
|
+
await runAdd();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
var import_child_process2, p, API_KEYS_URL;
|
|
435
|
+
var init_wizard = __esm({
|
|
436
|
+
"src/install/wizard.ts"() {
|
|
437
|
+
"use strict";
|
|
438
|
+
import_child_process2 = require("child_process");
|
|
439
|
+
p = __toESM(require("@clack/prompts"), 1);
|
|
440
|
+
init_clients();
|
|
441
|
+
API_KEYS_URL = "https://www.runpod.io/console/user/settings";
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
26
445
|
// src/stdio.ts
|
|
27
446
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
447
|
+
var import_node_fetch2 = __toESM(require("node-fetch"), 1);
|
|
28
448
|
|
|
29
449
|
// src/server.ts
|
|
30
450
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
31
451
|
var SERVER_NAME = "Runpod API Server";
|
|
32
|
-
var SERVER_VERSION = true ? "
|
|
452
|
+
var SERVER_VERSION = true ? "2.0.0" : "dev";
|
|
33
453
|
function createServer(version = SERVER_VERSION) {
|
|
34
454
|
return new import_mcp.McpServer({
|
|
35
455
|
name: SERVER_NAME,
|
|
@@ -41,32 +461,465 @@ function createServer(version = SERVER_VERSION) {
|
|
|
41
461
|
});
|
|
42
462
|
}
|
|
43
463
|
|
|
44
|
-
// src/tools.ts
|
|
45
|
-
var import_zod = require("zod");
|
|
464
|
+
// src/tools/runtime.ts
|
|
46
465
|
var import_node_fetch = __toESM(require("node-fetch"), 1);
|
|
47
466
|
var import_node_crypto = require("crypto");
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
var
|
|
51
|
-
|
|
52
|
-
|
|
467
|
+
|
|
468
|
+
// src/_shared/http.ts
|
|
469
|
+
var HttpError = class extends Error {
|
|
470
|
+
constructor(prefix, status, body) {
|
|
471
|
+
super(`${prefix}: ${status} - ${body}`);
|
|
472
|
+
this.name = "HttpError";
|
|
473
|
+
this.status = status;
|
|
474
|
+
this.body = body;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
function isJsonContentType(contentType) {
|
|
478
|
+
if (!contentType) return false;
|
|
479
|
+
const ct = contentType.toLowerCase();
|
|
480
|
+
return ct.includes("application/json") || ct.includes("+json");
|
|
481
|
+
}
|
|
482
|
+
function methodSendsBody(method) {
|
|
483
|
+
return method === "POST" || method === "PATCH" || method === "PUT";
|
|
484
|
+
}
|
|
485
|
+
function buildRequestHeaders(apiKey, tracking) {
|
|
486
|
+
return {
|
|
487
|
+
Authorization: `Bearer ${apiKey}`,
|
|
488
|
+
"Content-Type": "application/json",
|
|
489
|
+
...tracking()
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function createHttpClient(deps) {
|
|
493
|
+
return async function request(url, method = "GET", body) {
|
|
494
|
+
const init = {
|
|
495
|
+
method,
|
|
496
|
+
headers: buildRequestHeaders(deps.apiKey, deps.tracking)
|
|
497
|
+
};
|
|
498
|
+
if (body && methodSendsBody(method)) {
|
|
499
|
+
init.body = JSON.stringify(body);
|
|
500
|
+
}
|
|
501
|
+
const response = await deps.fetch(url, init);
|
|
502
|
+
if (!response.ok) {
|
|
503
|
+
throw new HttpError(
|
|
504
|
+
deps.errorPrefix,
|
|
505
|
+
response.status,
|
|
506
|
+
await response.text()
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
return isJsonContentType(response.headers.get("content-type")) ? response.json() : { success: true, status: response.status };
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/_shared/tracking.ts
|
|
53
514
|
function sanitizeUaToken(value) {
|
|
54
515
|
return value.replace(/[()<>@,;:\\"/[\]?={}\s]/g, "_").slice(0, 64);
|
|
55
516
|
}
|
|
56
|
-
function
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const version = sanitizeUaToken(info?.version || "unknown");
|
|
61
|
-
const serverVersion = ctx.serverVersion || MCP_SERVER_VERSION;
|
|
62
|
-
const userAgent = `runpod-mcp-server/${serverVersion} (caller=mcp; client=${name}; client_version=${version}; transport=${ctx.transport})`;
|
|
517
|
+
function buildTrackingHeaders(input) {
|
|
518
|
+
const name = sanitizeUaToken(input.clientName || "unknown");
|
|
519
|
+
const version = sanitizeUaToken(input.clientVersion || "unknown");
|
|
520
|
+
const userAgent = `runpod-mcp-server/${input.serverVersion} (caller=mcp; client=${name}; client_version=${version}; transport=${input.transport})`;
|
|
63
521
|
return {
|
|
64
522
|
"User-Agent": userAgent,
|
|
65
|
-
"X-Runpod-Session-Id":
|
|
523
|
+
"X-Runpod-Session-Id": input.sessionId
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/_shared/mappers.ts
|
|
528
|
+
function compact(obj) {
|
|
529
|
+
const out = {};
|
|
530
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
531
|
+
if (v !== void 0) out[k] = v;
|
|
532
|
+
}
|
|
533
|
+
return out;
|
|
534
|
+
}
|
|
535
|
+
function persistentMount(size, path2) {
|
|
536
|
+
if (size === void 0 || path2 === void 0) return void 0;
|
|
537
|
+
return { persistent: { size, path: path2 } };
|
|
538
|
+
}
|
|
539
|
+
function containerConfigToV2(p2) {
|
|
540
|
+
return compact({
|
|
541
|
+
image: p2.imageName,
|
|
542
|
+
disk: p2.containerDiskInGb,
|
|
543
|
+
ports: p2.ports,
|
|
544
|
+
env: p2.env,
|
|
545
|
+
mounts: persistentMount(p2.volumeInGb, p2.volumeMountPath)
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
function gpuConfigToV2(gpuTypeIds, gpuCount) {
|
|
549
|
+
const id = gpuTypeIds?.[0];
|
|
550
|
+
if (id === void 0) return void 0;
|
|
551
|
+
return compact({ id, count: gpuCount });
|
|
552
|
+
}
|
|
553
|
+
function mapPodCreateToV2(params) {
|
|
554
|
+
return compact({
|
|
555
|
+
...containerConfigToV2(params),
|
|
556
|
+
name: params.name,
|
|
557
|
+
cloud: params.cloudType,
|
|
558
|
+
// v2 CreatePodRequest takes `dataCenterIds` as an ARRAY (preferred data
|
|
559
|
+
// centers; omit/empty = scheduler chooses). Pass the v1 list straight
|
|
560
|
+
// through — the earlier singular `dataCenter` key did not exist on the pod
|
|
561
|
+
// schema, so placement was being silently dropped. GPU, by contrast, IS
|
|
562
|
+
// singular on v2 (`gpu.id`): gpuConfigToV2 takes the first id; the
|
|
563
|
+
// create-pod handler warns when more than one gpuTypeId was supplied.
|
|
564
|
+
dataCenterIds: params.dataCenterIds?.length ? params.dataCenterIds : void 0,
|
|
565
|
+
gpu: gpuConfigToV2(params.gpuTypeIds, params.gpuCount)
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function mapPodUpdateToV2(params) {
|
|
569
|
+
return {
|
|
570
|
+
...containerConfigToV2(params),
|
|
571
|
+
...compact({ name: params.name })
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function nestedOrUndefined(obj) {
|
|
575
|
+
const c = compact(obj);
|
|
576
|
+
return Object.keys(c).length ? c : void 0;
|
|
577
|
+
}
|
|
578
|
+
function endpointGpuConfig(pools, count) {
|
|
579
|
+
if (!pools?.length) return void 0;
|
|
580
|
+
return compact({ pools, count });
|
|
581
|
+
}
|
|
582
|
+
function mapEndpointToV2(params) {
|
|
583
|
+
return compact({
|
|
584
|
+
name: params.name,
|
|
585
|
+
image: params.imageName,
|
|
586
|
+
args: params.args,
|
|
587
|
+
disk: params.containerDiskInGb,
|
|
588
|
+
ports: params.ports,
|
|
589
|
+
env: params.env,
|
|
590
|
+
registry: params.containerRegistryAuthId,
|
|
591
|
+
gpu: endpointGpuConfig(params.gpuPoolIds, params.gpuCount),
|
|
592
|
+
workers: nestedOrUndefined({
|
|
593
|
+
min: params.workersMin,
|
|
594
|
+
max: params.workersMax
|
|
595
|
+
}),
|
|
596
|
+
scaling: nestedOrUndefined({
|
|
597
|
+
type: params.scalerType,
|
|
598
|
+
value: params.scalerValue,
|
|
599
|
+
idleTimeout: params.idleTimeout
|
|
600
|
+
}),
|
|
601
|
+
dataCenterIds: params.dataCenterIds?.length ? params.dataCenterIds : void 0,
|
|
602
|
+
networkVolumes: params.networkVolumeIds?.length ? params.networkVolumeIds : void 0,
|
|
603
|
+
timeout: params.executionTimeoutMs,
|
|
604
|
+
flashboot: params.flashboot
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
var mapEndpointCreateToV2 = mapEndpointToV2;
|
|
608
|
+
var mapEndpointUpdateToV2 = mapEndpointToV2;
|
|
609
|
+
function mapNetworkVolumeCreateToV2(params) {
|
|
610
|
+
return compact({
|
|
611
|
+
name: params.name,
|
|
612
|
+
size: params.size,
|
|
613
|
+
dataCenter: params.dataCenterId
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
function mapTemplateCreateToV2(params) {
|
|
617
|
+
return {
|
|
618
|
+
...containerConfigToV2(params),
|
|
619
|
+
...compact({
|
|
620
|
+
name: params.name,
|
|
621
|
+
serverless: params.isServerless,
|
|
622
|
+
registry: params.containerRegistryAuthId
|
|
623
|
+
}),
|
|
624
|
+
// Required by v2 — always emit, defaulting to NVIDIA.
|
|
625
|
+
category: params.category ?? "NVIDIA"
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function mapTemplateUpdateToV2(params) {
|
|
629
|
+
return {
|
|
630
|
+
...containerConfigToV2(params),
|
|
631
|
+
...compact({
|
|
632
|
+
name: params.name,
|
|
633
|
+
registry: params.containerRegistryAuthId
|
|
634
|
+
})
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/_shared/backend.ts
|
|
639
|
+
var V1_ONLY = /* @__PURE__ */ new Set(["jobs"]);
|
|
640
|
+
var V2_UNWRAP_KEY = {
|
|
641
|
+
pods: "pods",
|
|
642
|
+
templates: "templates",
|
|
643
|
+
networkVolumes: "networkVolumes",
|
|
644
|
+
registries: "registries",
|
|
645
|
+
gpus: "gpus",
|
|
646
|
+
cpus: "cpus",
|
|
647
|
+
dataCenters: "dataCenters",
|
|
648
|
+
endpoints: "endpoints",
|
|
649
|
+
jobs: "jobs",
|
|
650
|
+
tags: "tags",
|
|
651
|
+
workers: "workers",
|
|
652
|
+
billing: "records"
|
|
653
|
+
};
|
|
654
|
+
var RESOURCE_ENV_KEY = {
|
|
655
|
+
pods: "PODS",
|
|
656
|
+
templates: "TEMPLATES",
|
|
657
|
+
networkVolumes: "NETWORK_VOLUMES",
|
|
658
|
+
registries: "REGISTRIES",
|
|
659
|
+
gpus: "GPUS",
|
|
660
|
+
cpus: "CPUS",
|
|
661
|
+
dataCenters: "DATA_CENTERS",
|
|
662
|
+
endpoints: "ENDPOINTS",
|
|
663
|
+
jobs: "JOBS",
|
|
664
|
+
tags: "TAGS",
|
|
665
|
+
workers: "WORKERS",
|
|
666
|
+
billing: "BILLING"
|
|
667
|
+
};
|
|
668
|
+
function restV1Base(env) {
|
|
669
|
+
return env.RUNPOD_REST_API_URL ?? "https://rest.runpod.io/v1";
|
|
670
|
+
}
|
|
671
|
+
function restV2Base(env) {
|
|
672
|
+
return env.RUNPOD_REST_V2_API_URL ?? "https://v2-rest.runpod.io/v2";
|
|
673
|
+
}
|
|
674
|
+
function serverlessBase(env) {
|
|
675
|
+
return env.RUNPOD_SERVERLESS_API_URL ?? "https://api.runpod.ai/v2";
|
|
676
|
+
}
|
|
677
|
+
function publicGraphqlBase(env) {
|
|
678
|
+
return env.RUNPOD_PUBLIC_GRAPHQL_URL ?? "https://api.runpod.io/graphql";
|
|
679
|
+
}
|
|
680
|
+
function unwrapList(resource, version, raw) {
|
|
681
|
+
if (version === "v1") return Array.isArray(raw) ? raw : [];
|
|
682
|
+
if (raw === null || typeof raw !== "object") return [];
|
|
683
|
+
const arr = raw[V2_UNWRAP_KEY[resource]];
|
|
684
|
+
return Array.isArray(arr) ? arr : [];
|
|
685
|
+
}
|
|
686
|
+
function resolveVersion(opts) {
|
|
687
|
+
const { resource, env, ctx, v2Available } = opts;
|
|
688
|
+
if (V1_ONLY.has(resource)) return "v1";
|
|
689
|
+
const perResource = env[`RUNPOD_REST_VERSION_${RESOURCE_ENV_KEY[resource]}`];
|
|
690
|
+
const setting = (perResource ?? env.RUNPOD_REST_VERSION ?? "v2").toLowerCase();
|
|
691
|
+
if (setting === "v2") return "v2";
|
|
692
|
+
if (setting === "auto") {
|
|
693
|
+
if (ctx.transport === "http") return "v1";
|
|
694
|
+
return v2Available === true ? "v2" : "v1";
|
|
695
|
+
}
|
|
696
|
+
return "v1";
|
|
697
|
+
}
|
|
698
|
+
function wantsAutoProbe(env) {
|
|
699
|
+
return Object.entries(env).some(
|
|
700
|
+
([k, v]) => k.startsWith("RUNPOD_REST_VERSION") && (v ?? "").toLowerCase() === "auto"
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
var PROBE_TIMEOUT_MS = 4e3;
|
|
704
|
+
function classifyProbeStatus(status) {
|
|
705
|
+
if (status >= 500) return void 0;
|
|
706
|
+
if (status === 401 || status === 403) return void 0;
|
|
707
|
+
return status >= 200 && status < 300;
|
|
708
|
+
}
|
|
709
|
+
async function runV2Probe(deps) {
|
|
710
|
+
try {
|
|
711
|
+
const res = await deps.fetch(`${deps.baseUrl}/catalog/gpus`, {
|
|
712
|
+
method: "GET",
|
|
713
|
+
headers: { Authorization: `Bearer ${deps.apiKey}` },
|
|
714
|
+
signal: AbortSignal.timeout(deps.timeoutMs ?? PROBE_TIMEOUT_MS)
|
|
715
|
+
});
|
|
716
|
+
const verdict = classifyProbeStatus(res.status);
|
|
717
|
+
if (verdict === void 0) {
|
|
718
|
+
deps.warn?.(
|
|
719
|
+
`v2 auto-probe: ${deps.baseUrl} returned ${res.status} (transient) \u2014 using v1 for now; will re-probe. Pin RUNPOD_REST_VERSION=v2 to force v2.`
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
return verdict;
|
|
723
|
+
} catch {
|
|
724
|
+
deps.warn?.(
|
|
725
|
+
`v2 auto-probe: ${deps.baseUrl} unreachable (timed out after ${deps.timeoutMs ?? PROBE_TIMEOUT_MS}ms or network error) \u2014 using v1 for now; will re-probe. Pin RUNPOD_REST_VERSION=v2 to force v2.`
|
|
726
|
+
);
|
|
727
|
+
return void 0;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function createV2Prober(deps) {
|
|
731
|
+
let cached;
|
|
732
|
+
let inFlight;
|
|
733
|
+
const probeOnce = async () => {
|
|
734
|
+
const verdict = await runV2Probe(deps);
|
|
735
|
+
if (verdict !== void 0) cached = verdict;
|
|
736
|
+
return verdict ?? false;
|
|
737
|
+
};
|
|
738
|
+
return () => {
|
|
739
|
+
if (cached !== void 0) return Promise.resolve(cached);
|
|
740
|
+
if (!inFlight) {
|
|
741
|
+
inFlight = probeOnce().finally(() => {
|
|
742
|
+
inFlight = void 0;
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
return inFlight;
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
var identity = (body) => body;
|
|
749
|
+
var V1_REST_PATHS = {
|
|
750
|
+
pods: { list: "/pods", get: (id) => `/pods/${id}` },
|
|
751
|
+
templates: { list: "/templates", get: (id) => `/templates/${id}` },
|
|
752
|
+
networkVolumes: {
|
|
753
|
+
list: "/networkvolumes",
|
|
754
|
+
get: (id) => `/networkvolumes/${id}`
|
|
755
|
+
},
|
|
756
|
+
registries: {
|
|
757
|
+
list: "/containerregistryauth",
|
|
758
|
+
get: (id) => `/containerregistryauth/${id}`
|
|
759
|
+
},
|
|
760
|
+
endpoints: { list: "/endpoints", get: (id) => `/endpoints/${id}` }
|
|
761
|
+
};
|
|
762
|
+
var CATALOG = /* @__PURE__ */ new Set([
|
|
763
|
+
"gpus",
|
|
764
|
+
"cpus",
|
|
765
|
+
"dataCenters"
|
|
766
|
+
]);
|
|
767
|
+
var V2_ONLY = /* @__PURE__ */ new Set([
|
|
768
|
+
"tags",
|
|
769
|
+
"workers",
|
|
770
|
+
"billing"
|
|
771
|
+
]);
|
|
772
|
+
var V2_REST_PATHS = {
|
|
773
|
+
pods: { list: "/pods", get: (id) => `/pods/${id}` },
|
|
774
|
+
templates: { list: "/templates", get: (id) => `/templates/${id}` },
|
|
775
|
+
networkVolumes: {
|
|
776
|
+
list: "/network-volumes",
|
|
777
|
+
get: (id) => `/network-volumes/${id}`
|
|
778
|
+
},
|
|
779
|
+
registries: { list: "/registries", get: (id) => `/registries/${id}` },
|
|
780
|
+
gpus: { list: "/catalog/gpus", get: (id) => `/catalog/gpus/${id}` },
|
|
781
|
+
cpus: { list: "/catalog/cpus", get: (id) => `/catalog/cpus/${id}` },
|
|
782
|
+
dataCenters: {
|
|
783
|
+
list: "/catalog/datacenters",
|
|
784
|
+
get: (id) => `/catalog/datacenters/${id}`
|
|
785
|
+
},
|
|
786
|
+
tags: { list: "/tags", get: (id) => `/tags/${id}` },
|
|
787
|
+
// Endpoints live under /v2/serverless on v2 (NOT /v2/endpoints). The v2 list
|
|
788
|
+
// response wraps rows under `endpoints` (see V2_UNWRAP_KEY), so the existing
|
|
789
|
+
// unwrap key stays correct.
|
|
790
|
+
endpoints: { list: "/serverless", get: (id) => `/serverless/${id}` },
|
|
791
|
+
// workers/billing build their own sub-paths in the tool from `base`; the
|
|
792
|
+
// list entry just satisfies buildV2Backend (which requires a paths entry).
|
|
793
|
+
workers: { list: "/serverless" },
|
|
794
|
+
billing: { list: "/billing" }
|
|
795
|
+
};
|
|
796
|
+
var V2_MAPPERS = {
|
|
797
|
+
pods: {
|
|
798
|
+
create: (b) => mapPodCreateToV2(b),
|
|
799
|
+
update: (b) => mapPodUpdateToV2(b)
|
|
800
|
+
},
|
|
801
|
+
templates: {
|
|
802
|
+
create: (b) => mapTemplateCreateToV2(b),
|
|
803
|
+
update: (b) => mapTemplateUpdateToV2(b)
|
|
804
|
+
},
|
|
805
|
+
networkVolumes: {
|
|
806
|
+
create: (b) => mapNetworkVolumeCreateToV2(
|
|
807
|
+
b
|
|
808
|
+
),
|
|
809
|
+
update: identity
|
|
810
|
+
},
|
|
811
|
+
registries: { create: identity, update: identity },
|
|
812
|
+
endpoints: {
|
|
813
|
+
create: (b) => mapEndpointCreateToV2(b),
|
|
814
|
+
update: (b) => mapEndpointUpdateToV2(b)
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
function buildV2Backend(resource, env, unwrap) {
|
|
818
|
+
const paths = V2_REST_PATHS[resource];
|
|
819
|
+
if (!paths) throw new Error(`no v2 REST paths for resource "${resource}"`);
|
|
820
|
+
const mappers = V2_MAPPERS[resource];
|
|
821
|
+
return {
|
|
822
|
+
kind: "rest",
|
|
823
|
+
version: "v2",
|
|
824
|
+
base: restV2Base(env),
|
|
825
|
+
list: paths.list,
|
|
826
|
+
get: paths.get,
|
|
827
|
+
unwrap,
|
|
828
|
+
mapCreate: mappers?.create ?? identity,
|
|
829
|
+
mapUpdate: mappers?.update ?? identity
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
function buildV1Backend(resource, env, unwrap) {
|
|
833
|
+
if (resource === "jobs") {
|
|
834
|
+
return {
|
|
835
|
+
kind: "rest",
|
|
836
|
+
version: "v1",
|
|
837
|
+
base: serverlessBase(env),
|
|
838
|
+
unwrap,
|
|
839
|
+
mapCreate: identity,
|
|
840
|
+
mapUpdate: identity
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
if (V2_ONLY.has(resource)) {
|
|
844
|
+
return {
|
|
845
|
+
kind: "rest",
|
|
846
|
+
version: "v1",
|
|
847
|
+
base: restV1Base(env),
|
|
848
|
+
unwrap,
|
|
849
|
+
mapCreate: identity,
|
|
850
|
+
mapUpdate: identity
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
if (CATALOG.has(resource)) {
|
|
854
|
+
return {
|
|
855
|
+
kind: "graphql",
|
|
856
|
+
version: "v1",
|
|
857
|
+
base: publicGraphqlBase(env),
|
|
858
|
+
unwrap,
|
|
859
|
+
mapCreate: identity,
|
|
860
|
+
mapUpdate: identity
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
const paths = V1_REST_PATHS[resource];
|
|
864
|
+
if (!paths) throw new Error(`no v1 REST paths for resource "${resource}"`);
|
|
865
|
+
return {
|
|
866
|
+
kind: "rest",
|
|
867
|
+
version: "v1",
|
|
868
|
+
base: restV1Base(env),
|
|
869
|
+
list: paths.list,
|
|
870
|
+
get: paths.get,
|
|
871
|
+
unwrap,
|
|
872
|
+
mapCreate: identity,
|
|
873
|
+
mapUpdate: identity
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
function resolveBackend(opts) {
|
|
877
|
+
const { resource, env } = opts;
|
|
878
|
+
const version = resolveVersion(opts);
|
|
879
|
+
const unwrap = (raw) => unwrapList(resource, version, raw);
|
|
880
|
+
return version === "v2" ? buildV2Backend(resource, env, unwrap) : buildV1Backend(resource, env, unwrap);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/tools/runtime.ts
|
|
884
|
+
var MCP_SERVER_VERSION = true ? "2.0.0" : "dev";
|
|
885
|
+
var SESSION_ID = (0, import_node_crypto.randomUUID)();
|
|
886
|
+
function trackingHeaders(server, ctx) {
|
|
887
|
+
const info = server.server.getClientVersion();
|
|
888
|
+
return buildTrackingHeaders({
|
|
889
|
+
clientName: info?.name || ctx.clientUserAgent || "unknown",
|
|
890
|
+
clientVersion: info?.version || "unknown",
|
|
891
|
+
transport: ctx.transport,
|
|
892
|
+
// Prefer a server version supplied by the caller (the hosted entrypoint reads
|
|
893
|
+
// it from package.json at runtime, since tsup's build-time define does not
|
|
894
|
+
// run when Vercel compiles api/index.ts). Fall back to the build-time value.
|
|
895
|
+
serverVersion: ctx.serverVersion || MCP_SERVER_VERSION,
|
|
896
|
+
sessionId: SESSION_ID
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
var defaultFetch = import_node_fetch.default;
|
|
900
|
+
async function withApiErrorLog(label, call) {
|
|
901
|
+
try {
|
|
902
|
+
return await call();
|
|
903
|
+
} catch (error) {
|
|
904
|
+
console.error(label, error);
|
|
905
|
+
throw error;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
function jsonReply(result) {
|
|
909
|
+
return {
|
|
910
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
66
911
|
};
|
|
67
912
|
}
|
|
68
|
-
|
|
69
|
-
|
|
913
|
+
var READ_ONLY = { readOnlyHint: true, openWorldHint: true };
|
|
914
|
+
var WRITE = { readOnlyHint: false, openWorldHint: true };
|
|
915
|
+
var DESTRUCTIVE = {
|
|
916
|
+
readOnlyHint: false,
|
|
917
|
+
destructiveHint: true,
|
|
918
|
+
idempotentHint: true,
|
|
919
|
+
openWorldHint: true
|
|
920
|
+
};
|
|
921
|
+
async function graphqlRequest(query, tracking, fetchImpl, url) {
|
|
922
|
+
const response = await fetchImpl(url, {
|
|
70
923
|
method: "POST",
|
|
71
924
|
headers: {
|
|
72
925
|
"Content-Type": "application/json",
|
|
@@ -85,92 +938,224 @@ async function graphqlRequest(query, tracking) {
|
|
|
85
938
|
}
|
|
86
939
|
return result.data;
|
|
87
940
|
}
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
941
|
+
function createToolRuntime(server, ctx, deps = {}) {
|
|
942
|
+
const tracking = () => trackingHeaders(server, ctx);
|
|
943
|
+
const httpFetch = deps.fetch ?? defaultFetch;
|
|
944
|
+
const v1Client = createHttpClient({
|
|
945
|
+
apiKey: ctx.apiKey,
|
|
946
|
+
fetch: httpFetch,
|
|
947
|
+
tracking,
|
|
948
|
+
errorPrefix: "Runpod API Error"
|
|
949
|
+
});
|
|
950
|
+
const runpodRequest = (endpoint, method = "GET", body) => withApiErrorLog(
|
|
951
|
+
"Error calling Runpod API:",
|
|
952
|
+
() => v1Client(`${restV1Base(process.env)}${endpoint}`, method, body)
|
|
953
|
+
);
|
|
954
|
+
const serverlessClient = createHttpClient({
|
|
955
|
+
apiKey: ctx.apiKey,
|
|
956
|
+
fetch: httpFetch,
|
|
957
|
+
tracking,
|
|
958
|
+
errorPrefix: "Runpod Serverless API Error"
|
|
959
|
+
});
|
|
960
|
+
const serverlessRequest = (endpointId, path2, method = "GET", body) => withApiErrorLog(
|
|
961
|
+
"Error calling Runpod Serverless API:",
|
|
962
|
+
() => serverlessClient(
|
|
963
|
+
`${serverlessBase(process.env)}/${endpointId}${path2}`,
|
|
97
964
|
method,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
965
|
+
body
|
|
966
|
+
)
|
|
967
|
+
);
|
|
968
|
+
const restClient = createHttpClient({
|
|
969
|
+
apiKey: ctx.apiKey,
|
|
970
|
+
fetch: httpFetch,
|
|
971
|
+
tracking,
|
|
972
|
+
errorPrefix: "Runpod API Error"
|
|
973
|
+
});
|
|
974
|
+
const callRestUrl = (url, method = "GET", body) => withApiErrorLog(
|
|
975
|
+
"Error calling Runpod API:",
|
|
976
|
+
() => restClient(url, method, body)
|
|
977
|
+
);
|
|
978
|
+
const streamSse = deps.streamSse ?? (async (url, opts) => {
|
|
979
|
+
const controller = new AbortController();
|
|
980
|
+
const timer = setTimeout(() => controller.abort(), opts.maxWaitMs);
|
|
103
981
|
try {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
982
|
+
const res = await (0, import_node_fetch.default)(url, {
|
|
983
|
+
method: "GET",
|
|
984
|
+
headers: {
|
|
985
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
986
|
+
Accept: "text/event-stream",
|
|
987
|
+
...tracking()
|
|
988
|
+
},
|
|
989
|
+
signal: controller.signal
|
|
990
|
+
});
|
|
991
|
+
if (!res.ok) {
|
|
992
|
+
throw new HttpError(
|
|
993
|
+
"Runpod API Error",
|
|
994
|
+
res.status,
|
|
995
|
+
await res.text().catch(() => "")
|
|
996
|
+
);
|
|
108
997
|
}
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
998
|
+
const chunks = [];
|
|
999
|
+
let bytes = 0;
|
|
1000
|
+
let truncated = false;
|
|
1001
|
+
try {
|
|
1002
|
+
for await (const chunk of res.body) {
|
|
1003
|
+
chunks.push(chunk);
|
|
1004
|
+
bytes += chunk.length;
|
|
1005
|
+
if (bytes >= opts.maxBytes) {
|
|
1006
|
+
truncated = true;
|
|
1007
|
+
controller.abort();
|
|
1008
|
+
break;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
if (!(err instanceof Error && err.name === "AbortError")) throw err;
|
|
112
1013
|
}
|
|
113
|
-
return {
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
1014
|
+
return { raw: Buffer.concat(chunks).toString("utf8"), truncated };
|
|
1015
|
+
} finally {
|
|
1016
|
+
clearTimeout(timer);
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
const backendFor = (resource) => resolveBackend({
|
|
1020
|
+
resource,
|
|
1021
|
+
env: process.env,
|
|
1022
|
+
ctx: { transport: ctx.transport },
|
|
1023
|
+
v2Available: deps.v2Available
|
|
1024
|
+
});
|
|
1025
|
+
const podAction = async (podId, action) => {
|
|
1026
|
+
const backend = backendFor("pods");
|
|
1027
|
+
const podPath = backend.get(podId);
|
|
1028
|
+
if (backend.version === "v2") {
|
|
1029
|
+
return callRestUrl(`${backend.base}${podPath}/action`, "POST", {
|
|
1030
|
+
action
|
|
1031
|
+
});
|
|
117
1032
|
}
|
|
1033
|
+
return callRestUrl(`${backend.base}${podPath}/${action}`, "POST");
|
|
1034
|
+
};
|
|
1035
|
+
return {
|
|
1036
|
+
jsonReply,
|
|
1037
|
+
graphql: (query) => graphqlRequest(
|
|
1038
|
+
query,
|
|
1039
|
+
tracking,
|
|
1040
|
+
httpFetch,
|
|
1041
|
+
publicGraphqlBase(process.env)
|
|
1042
|
+
),
|
|
1043
|
+
runpodRequest,
|
|
1044
|
+
serverlessRequest,
|
|
1045
|
+
callRestUrl,
|
|
1046
|
+
backendFor,
|
|
1047
|
+
streamSse,
|
|
1048
|
+
podAction,
|
|
1049
|
+
env: process.env
|
|
118
1050
|
};
|
|
119
1051
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
1052
|
+
|
|
1053
|
+
// src/tools/catalog.ts
|
|
1054
|
+
var import_zod2 = require("zod");
|
|
1055
|
+
|
|
1056
|
+
// src/pagination.ts
|
|
1057
|
+
var import_zod = require("zod");
|
|
1058
|
+
var DEFAULT_LIST_LIMIT = 20;
|
|
1059
|
+
var MAX_LIST_LIMIT = 100;
|
|
1060
|
+
var listPaginationParams = {
|
|
1061
|
+
limit: import_zod.z.number().int().positive().max(MAX_LIST_LIMIT).optional().describe(
|
|
1062
|
+
`Maximum number of items to return (default ${DEFAULT_LIST_LIMIT}, max ${MAX_LIST_LIMIT}). Use the returned nextCursor to fetch the next page.`
|
|
1063
|
+
),
|
|
1064
|
+
cursor: import_zod.z.string().optional().describe(
|
|
1065
|
+
"Opaque pagination cursor from a previous response (nextCursor). Omit to start from the beginning."
|
|
1066
|
+
)
|
|
1067
|
+
};
|
|
1068
|
+
function decodeCursorOffset(cursor) {
|
|
1069
|
+
if (!cursor) return 0;
|
|
1070
|
+
try {
|
|
1071
|
+
const decoded = Buffer.from(cursor, "base64").toString("utf8");
|
|
1072
|
+
const offset = Number.parseInt(decoded, 10);
|
|
1073
|
+
return Number.isFinite(offset) && offset >= 0 ? offset : 0;
|
|
1074
|
+
} catch {
|
|
1075
|
+
return 0;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function encodeCursorOffset(offset) {
|
|
1079
|
+
return Buffer.from(String(offset), "utf8").toString("base64");
|
|
1080
|
+
}
|
|
1081
|
+
function capListResult(result, options, extra) {
|
|
1082
|
+
const limit = Math.min(options.limit ?? DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT);
|
|
1083
|
+
const offset = decodeCursorOffset(options.cursor);
|
|
1084
|
+
if (!Array.isArray(result)) {
|
|
1085
|
+
return {
|
|
1086
|
+
content: [
|
|
1087
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
1088
|
+
]
|
|
131
1089
|
};
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
throw error;
|
|
1090
|
+
}
|
|
1091
|
+
const total = result.length;
|
|
1092
|
+
const page = result.slice(offset, offset + limit);
|
|
1093
|
+
const nextOffset = offset + page.length;
|
|
1094
|
+
const hasMore = nextOffset < total;
|
|
1095
|
+
const { items: _i, pagination: _p, ...safeExtra } = extra ?? {};
|
|
1096
|
+
void _i;
|
|
1097
|
+
void _p;
|
|
1098
|
+
const envelope = {
|
|
1099
|
+
...safeExtra,
|
|
1100
|
+
items: page,
|
|
1101
|
+
pagination: {
|
|
1102
|
+
total,
|
|
1103
|
+
returned: page.length,
|
|
1104
|
+
offset,
|
|
1105
|
+
truncated: hasMore,
|
|
1106
|
+
nextCursor: hasMore ? encodeCursorOffset(nextOffset) : null,
|
|
1107
|
+
note: hasMore ? `Showing ${page.length} of ${total}. Pass cursor=nextCursor to fetch more, or narrow the query with filters.` : void 0
|
|
151
1108
|
}
|
|
152
1109
|
};
|
|
1110
|
+
return {
|
|
1111
|
+
content: [
|
|
1112
|
+
{ type: "text", text: JSON.stringify(envelope, null, 2) }
|
|
1113
|
+
]
|
|
1114
|
+
};
|
|
153
1115
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
1116
|
+
|
|
1117
|
+
// src/tools/catalog.ts
|
|
1118
|
+
function registerCatalogTools(server, rt) {
|
|
1119
|
+
const { jsonReply: jsonReply2, graphql, callRestUrl, backendFor } = rt;
|
|
1120
|
+
server.tool(
|
|
159
1121
|
"list-gpu-types",
|
|
1122
|
+
"List available GPU types with stock/pricing and capability filters (minimum VRAM, secure/community cloud, name search). Use this to discover valid gpuTypeIds before creating a pod or endpoint. On v2 the realtime stock filter is a no-op.",
|
|
160
1123
|
{
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
1124
|
+
...listPaginationParams,
|
|
1125
|
+
minMemoryGb: import_zod2.z.number().optional().describe("Filter to GPUs with at least this much VRAM in GB"),
|
|
1126
|
+
secureCloudOnly: import_zod2.z.boolean().optional().describe("Filter to only GPUs available in secure cloud"),
|
|
1127
|
+
communityCloudOnly: import_zod2.z.boolean().optional().describe("Filter to only GPUs available in community cloud"),
|
|
1128
|
+
searchTerm: import_zod2.z.string().optional().describe(
|
|
165
1129
|
"Search term to filter GPUs by name (e.g., 'A100', 'RTX 4090')"
|
|
166
1130
|
),
|
|
167
|
-
includeUnavailable:
|
|
1131
|
+
includeUnavailable: import_zod2.z.boolean().optional().describe(
|
|
168
1132
|
"Include GPUs that are currently out of stock. Default is false"
|
|
169
1133
|
)
|
|
170
1134
|
},
|
|
1135
|
+
{ title: "List GPU types", ...READ_ONLY },
|
|
171
1136
|
async (params) => {
|
|
172
|
-
const
|
|
173
|
-
|
|
1137
|
+
const backend = backendFor("gpus");
|
|
1138
|
+
if (backend.version === "v2") {
|
|
1139
|
+
const raw = await callRestUrl(`${backend.base}${backend.list}`);
|
|
1140
|
+
let gpus = backend.unwrap(raw);
|
|
1141
|
+
if (params.minMemoryGb !== void 0)
|
|
1142
|
+
gpus = gpus.filter(
|
|
1143
|
+
(g) => Number(g.memory ?? 0) >= params.minMemoryGb
|
|
1144
|
+
);
|
|
1145
|
+
if (params.secureCloudOnly) gpus = gpus.filter((g) => g.secure);
|
|
1146
|
+
if (params.communityCloudOnly) gpus = gpus.filter((g) => g.community);
|
|
1147
|
+
if (params.searchTerm) {
|
|
1148
|
+
const t = params.searchTerm.toLowerCase();
|
|
1149
|
+
gpus = gpus.filter(
|
|
1150
|
+
(g) => String(g.id ?? "").toLowerCase().includes(t) || String(g.name ?? "").toLowerCase().includes(t)
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
return capListResult(gpus, {
|
|
1154
|
+
limit: params.limit,
|
|
1155
|
+
cursor: params.cursor
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
const data = await graphql(`
|
|
174
1159
|
query {
|
|
175
1160
|
gpuTypes {
|
|
176
1161
|
id
|
|
@@ -183,9 +1168,7 @@ function registerTools(server2, ctx) {
|
|
|
183
1168
|
}
|
|
184
1169
|
}
|
|
185
1170
|
}
|
|
186
|
-
|
|
187
|
-
tracking
|
|
188
|
-
);
|
|
1171
|
+
`);
|
|
189
1172
|
const stockPriority = {
|
|
190
1173
|
High: 3,
|
|
191
1174
|
Medium: 2,
|
|
@@ -231,26 +1214,39 @@ function registerTools(server2, ctx) {
|
|
|
231
1214
|
stockStatus: gpu.lowestPrice?.stockStatus || "unavailable",
|
|
232
1215
|
available: isAvailable(gpu)
|
|
233
1216
|
}));
|
|
234
|
-
return {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
text: JSON.stringify(result, null, 2)
|
|
239
|
-
}
|
|
240
|
-
]
|
|
241
|
-
};
|
|
1217
|
+
return capListResult(result, {
|
|
1218
|
+
limit: params.limit,
|
|
1219
|
+
cursor: params.cursor
|
|
1220
|
+
});
|
|
242
1221
|
}
|
|
243
1222
|
);
|
|
244
|
-
|
|
1223
|
+
server.tool(
|
|
245
1224
|
"list-data-centers",
|
|
1225
|
+
"List Runpod data centers (id, name, region/location). Use this to discover valid dataCenterIds for placing pods, endpoints, or network volumes.",
|
|
246
1226
|
{
|
|
247
|
-
|
|
1227
|
+
...listPaginationParams,
|
|
1228
|
+
region: import_zod2.z.string().optional().describe(
|
|
248
1229
|
"Filter by region/location (e.g., 'United States', 'Europe', 'Canada')"
|
|
249
1230
|
)
|
|
250
1231
|
},
|
|
1232
|
+
{ title: "List data centers", ...READ_ONLY },
|
|
251
1233
|
async (params) => {
|
|
252
|
-
const
|
|
253
|
-
|
|
1234
|
+
const backend = backendFor("dataCenters");
|
|
1235
|
+
if (backend.version === "v2") {
|
|
1236
|
+
const raw = await callRestUrl(`${backend.base}${backend.list}`);
|
|
1237
|
+
let dcs = backend.unwrap(raw);
|
|
1238
|
+
if (params.region) {
|
|
1239
|
+
const t = params.region.toLowerCase();
|
|
1240
|
+
dcs = dcs.filter(
|
|
1241
|
+
(dc) => String(dc.region ?? "").toLowerCase().includes(t)
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
return capListResult(dcs, {
|
|
1245
|
+
limit: params.limit,
|
|
1246
|
+
cursor: params.cursor
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
const data = await graphql(`
|
|
254
1250
|
query {
|
|
255
1251
|
dataCenters {
|
|
256
1252
|
id
|
|
@@ -258,9 +1254,7 @@ function registerTools(server2, ctx) {
|
|
|
258
1254
|
location
|
|
259
1255
|
}
|
|
260
1256
|
}
|
|
261
|
-
|
|
262
|
-
tracking
|
|
263
|
-
);
|
|
1257
|
+
`);
|
|
264
1258
|
let dataCenters = data.dataCenters;
|
|
265
1259
|
if (params.region) {
|
|
266
1260
|
const term = params.region.toLowerCase();
|
|
@@ -268,343 +1262,629 @@ function registerTools(server2, ctx) {
|
|
|
268
1262
|
(dc) => dc.location.toLowerCase().includes(term)
|
|
269
1263
|
);
|
|
270
1264
|
}
|
|
271
|
-
return {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
1265
|
+
return capListResult(dataCenters, {
|
|
1266
|
+
limit: params.limit,
|
|
1267
|
+
cursor: params.cursor
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
);
|
|
1271
|
+
server.tool(
|
|
1272
|
+
"list-cpu-types",
|
|
1273
|
+
"List available CPU flavor types for CPU pods/endpoints. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
1274
|
+
{ ...listPaginationParams },
|
|
1275
|
+
{ title: "List CPU types", ...READ_ONLY },
|
|
1276
|
+
async (params) => {
|
|
1277
|
+
const backend = backendFor("cpus");
|
|
1278
|
+
if (backend.version === "v1") {
|
|
1279
|
+
return jsonReply2({
|
|
1280
|
+
error: "list-cpu-types is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
|
|
1281
|
+
status: 501
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
const raw = await callRestUrl(`${backend.base}${backend.list}`);
|
|
1285
|
+
return capListResult(backend.unwrap(raw), {
|
|
1286
|
+
limit: params.limit,
|
|
1287
|
+
cursor: params.cursor
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
);
|
|
1291
|
+
server.tool(
|
|
1292
|
+
"get-gpu-type",
|
|
1293
|
+
"Get details for a single GPU type by id. v2-only \u2014 returns a 501 notice on the v1 API (use list-gpu-types there).",
|
|
1294
|
+
{
|
|
1295
|
+
gpuTypeId: import_zod2.z.string().describe("ID of the GPU type to retrieve")
|
|
1296
|
+
},
|
|
1297
|
+
{ title: "Get GPU type", ...READ_ONLY },
|
|
1298
|
+
async (params) => {
|
|
1299
|
+
const backend = backendFor("gpus");
|
|
1300
|
+
if (backend.version === "v1") {
|
|
1301
|
+
return jsonReply2({
|
|
1302
|
+
error: "get-gpu-type is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2 (or use list-gpu-types on v1).",
|
|
1303
|
+
status: 501
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
const result = await callRestUrl(
|
|
1307
|
+
`${backend.base}${backend.get(params.gpuTypeId)}`
|
|
1308
|
+
);
|
|
1309
|
+
return jsonReply2(result);
|
|
1310
|
+
}
|
|
1311
|
+
);
|
|
1312
|
+
server.tool(
|
|
1313
|
+
"get-cpu-type",
|
|
1314
|
+
"Get details for a single CPU flavor type by id. v2-only \u2014 returns a 501 notice on the v1 API (use list-cpu-types there).",
|
|
1315
|
+
{
|
|
1316
|
+
cpuTypeId: import_zod2.z.string().describe("ID of the CPU type to retrieve")
|
|
1317
|
+
},
|
|
1318
|
+
{ title: "Get CPU type", ...READ_ONLY },
|
|
1319
|
+
async (params) => {
|
|
1320
|
+
const backend = backendFor("cpus");
|
|
1321
|
+
if (backend.version === "v1") {
|
|
1322
|
+
return jsonReply2({
|
|
1323
|
+
error: "get-cpu-type is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
|
|
1324
|
+
status: 501
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
const result = await callRestUrl(
|
|
1328
|
+
`${backend.base}${backend.get(params.cpuTypeId)}`
|
|
1329
|
+
);
|
|
1330
|
+
return jsonReply2(result);
|
|
279
1331
|
}
|
|
280
1332
|
);
|
|
281
|
-
|
|
1333
|
+
server.tool(
|
|
1334
|
+
"get-data-center",
|
|
1335
|
+
"Get details for a single data center by id. v2-only \u2014 returns a 501 notice on the v1 API (use list-data-centers there).",
|
|
1336
|
+
{
|
|
1337
|
+
dataCenterId: import_zod2.z.string().describe("ID of the data center to retrieve")
|
|
1338
|
+
},
|
|
1339
|
+
{ title: "Get data center", ...READ_ONLY },
|
|
1340
|
+
async (params) => {
|
|
1341
|
+
const backend = backendFor("dataCenters");
|
|
1342
|
+
if (backend.version === "v1") {
|
|
1343
|
+
return jsonReply2({
|
|
1344
|
+
error: "get-data-center is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
|
|
1345
|
+
status: 501
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
const result = await callRestUrl(
|
|
1349
|
+
`${backend.base}${backend.get(params.dataCenterId)}`
|
|
1350
|
+
);
|
|
1351
|
+
return jsonReply2(result);
|
|
1352
|
+
}
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// src/tools/pods.ts
|
|
1357
|
+
var import_zod3 = require("zod");
|
|
1358
|
+
function registerPodTools(server, rt) {
|
|
1359
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor, podAction, env } = rt;
|
|
1360
|
+
server.tool(
|
|
282
1361
|
"list-pods",
|
|
1362
|
+
"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.",
|
|
283
1363
|
{
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
1364
|
+
...listPaginationParams,
|
|
1365
|
+
computeType: import_zod3.z.enum(["GPU", "CPU"]).optional().describe("Filter to only GPU or only CPU Pods"),
|
|
1366
|
+
gpuTypeId: import_zod3.z.array(import_zod3.z.string()).optional().describe("Filter to Pods with any of the listed GPU types"),
|
|
1367
|
+
dataCenterId: import_zod3.z.array(import_zod3.z.string()).optional().describe("Filter to Pods in any of the provided data centers"),
|
|
1368
|
+
name: import_zod3.z.string().optional().describe("Filter to Pods with the provided name"),
|
|
1369
|
+
includeMachine: import_zod3.z.boolean().optional().describe("Include information about the machine"),
|
|
1370
|
+
includeNetworkVolume: import_zod3.z.boolean().optional().describe("Include information about attached network volumes")
|
|
290
1371
|
},
|
|
1372
|
+
{ title: "List pods", ...READ_ONLY },
|
|
291
1373
|
async (params) => {
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
params.
|
|
297
|
-
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
1374
|
+
const backend = backendFor("pods");
|
|
1375
|
+
let queryString = "";
|
|
1376
|
+
if (backend.version === "v1") {
|
|
1377
|
+
const queryParams = new URLSearchParams();
|
|
1378
|
+
if (params.computeType)
|
|
1379
|
+
queryParams.append("computeType", params.computeType);
|
|
1380
|
+
if (params.gpuTypeId)
|
|
1381
|
+
params.gpuTypeId.forEach(
|
|
1382
|
+
(type) => queryParams.append("gpuTypeId", type)
|
|
1383
|
+
);
|
|
1384
|
+
if (params.dataCenterId)
|
|
1385
|
+
params.dataCenterId.forEach(
|
|
1386
|
+
(dc) => queryParams.append("dataCenterId", dc)
|
|
1387
|
+
);
|
|
1388
|
+
if (params.name) queryParams.append("name", params.name);
|
|
1389
|
+
if (params.includeMachine)
|
|
1390
|
+
queryParams.append(
|
|
1391
|
+
"includeMachine",
|
|
1392
|
+
params.includeMachine.toString()
|
|
1393
|
+
);
|
|
1394
|
+
if (params.includeNetworkVolume)
|
|
1395
|
+
queryParams.append(
|
|
1396
|
+
"includeNetworkVolume",
|
|
1397
|
+
params.includeNetworkVolume.toString()
|
|
1398
|
+
);
|
|
1399
|
+
queryString = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
1400
|
+
}
|
|
1401
|
+
const result = await callRestUrl(
|
|
1402
|
+
`${backend.base}${backend.list}${queryString}`
|
|
1403
|
+
);
|
|
1404
|
+
return capListResult(backend.unwrap(result), {
|
|
1405
|
+
limit: params.limit,
|
|
1406
|
+
cursor: params.cursor
|
|
1407
|
+
});
|
|
321
1408
|
}
|
|
322
1409
|
);
|
|
323
|
-
|
|
1410
|
+
server.tool(
|
|
324
1411
|
"get-pod",
|
|
1412
|
+
"Get full details for one pod by id, optionally expanding machine and attached network-volume info.",
|
|
325
1413
|
{
|
|
326
|
-
podId:
|
|
327
|
-
includeMachine:
|
|
328
|
-
includeNetworkVolume:
|
|
1414
|
+
podId: import_zod3.z.string().describe("ID of the pod to retrieve"),
|
|
1415
|
+
includeMachine: import_zod3.z.boolean().optional().describe("Include information about the machine"),
|
|
1416
|
+
includeNetworkVolume: import_zod3.z.boolean().optional().describe("Include information about attached network volumes")
|
|
329
1417
|
},
|
|
1418
|
+
{ title: "Get pod", ...READ_ONLY },
|
|
330
1419
|
async (params) => {
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
1420
|
+
const backend = backendFor("pods");
|
|
1421
|
+
let queryString = "";
|
|
1422
|
+
if (backend.version === "v1") {
|
|
1423
|
+
const queryParams = new URLSearchParams();
|
|
1424
|
+
if (params.includeMachine)
|
|
1425
|
+
queryParams.append(
|
|
1426
|
+
"includeMachine",
|
|
1427
|
+
params.includeMachine.toString()
|
|
1428
|
+
);
|
|
1429
|
+
if (params.includeNetworkVolume)
|
|
1430
|
+
queryParams.append(
|
|
1431
|
+
"includeNetworkVolume",
|
|
1432
|
+
params.includeNetworkVolume.toString()
|
|
1433
|
+
);
|
|
1434
|
+
queryString = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
1435
|
+
}
|
|
1436
|
+
const result = await callRestUrl(
|
|
1437
|
+
`${backend.base}${backend.get(params.podId)}${queryString}`
|
|
1438
|
+
);
|
|
1439
|
+
return jsonReply2(result);
|
|
349
1440
|
}
|
|
350
1441
|
);
|
|
351
|
-
|
|
1442
|
+
server.tool(
|
|
352
1443
|
"create-pod",
|
|
353
|
-
'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.',
|
|
1444
|
+
'Create a new GPU/CPU pod on Runpod. Pass gpuTypeIds for a GPU pod, or computeType:"CPU" for a CPU pod (CPU pods are served by the v1 API for now). If the user does not specify an image, recommend the "Runpod Pytorch 2.8.0" image (runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404) as the default \u2014 it has the most up-to-date CUDA and PyTorch versions.',
|
|
354
1445
|
{
|
|
355
|
-
name:
|
|
356
|
-
imageName:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
1446
|
+
name: import_zod3.z.string().optional().describe("Name for the pod"),
|
|
1447
|
+
imageName: import_zod3.z.string().describe("Docker image to use"),
|
|
1448
|
+
computeType: import_zod3.z.enum(["GPU", "CPU"]).optional().describe(
|
|
1449
|
+
"Pod type. On v2, required when not passing gpuTypeIds. 'CPU' is served by the v1 API for now (v2 has no CPU pods yet)."
|
|
1450
|
+
),
|
|
1451
|
+
cloudType: import_zod3.z.enum(["SECURE", "COMMUNITY"]).optional().describe("SECURE or COMMUNITY cloud"),
|
|
1452
|
+
gpuTypeIds: import_zod3.z.array(import_zod3.z.string()).optional().describe("List of acceptable GPU types"),
|
|
1453
|
+
gpuCount: import_zod3.z.number().optional().describe("Number of GPUs"),
|
|
1454
|
+
containerDiskInGb: import_zod3.z.number().optional().describe("Container disk size in GB"),
|
|
1455
|
+
volumeInGb: import_zod3.z.number().optional().describe("Volume size in GB"),
|
|
1456
|
+
volumeMountPath: import_zod3.z.string().optional().describe("Path to mount the volume"),
|
|
1457
|
+
ports: import_zod3.z.array(import_zod3.z.string()).optional().describe("Ports to expose (e.g., '8888/http', '22/tcp')"),
|
|
1458
|
+
env: import_zod3.z.record(import_zod3.z.string()).optional().describe("Environment variables"),
|
|
1459
|
+
dataCenterIds: import_zod3.z.array(import_zod3.z.string()).optional().describe("List of data centers")
|
|
366
1460
|
},
|
|
1461
|
+
{ title: "Create pod", ...WRITE },
|
|
367
1462
|
async (params) => {
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
1463
|
+
const backend = backendFor("pods");
|
|
1464
|
+
const hasGpuTypes = (params.gpuTypeIds?.length ?? 0) > 0;
|
|
1465
|
+
const hasGpuCount = (params.gpuCount ?? 0) > 0;
|
|
1466
|
+
const wantsCpu = params.computeType === "CPU";
|
|
1467
|
+
const wantsGpu = hasGpuTypes || hasGpuCount || params.computeType === "GPU";
|
|
1468
|
+
if (backend.version === "v2") {
|
|
1469
|
+
if (wantsGpu && wantsCpu) {
|
|
1470
|
+
return jsonReply2({
|
|
1471
|
+
error: "Specify a GPU pod (gpuTypeIds or computeType:'GPU') OR a CPU pod (computeType:'CPU'), not both.",
|
|
1472
|
+
status: 400
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
if (!wantsGpu && !wantsCpu) {
|
|
1476
|
+
return jsonReply2({
|
|
1477
|
+
error: "No pod type specified. Pass gpuTypeIds (see list-gpu-types) for a GPU pod, or computeType:'CPU' for a CPU pod.",
|
|
1478
|
+
status: 400
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
if (wantsGpu && !hasGpuTypes) {
|
|
1482
|
+
return jsonReply2({
|
|
1483
|
+
error: "A GPU pod needs gpuTypeIds (see list-gpu-types); gpuCount or computeType alone is under-specified.",
|
|
1484
|
+
status: 400
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
if (wantsCpu) {
|
|
1488
|
+
try {
|
|
1489
|
+
const result = await callRestUrl(
|
|
1490
|
+
`${restV1Base(env)}/pods`,
|
|
1491
|
+
"POST",
|
|
1492
|
+
params
|
|
1493
|
+
);
|
|
1494
|
+
return jsonReply2({
|
|
1495
|
+
...result,
|
|
1496
|
+
_servedBy: "v1",
|
|
1497
|
+
_note: "CPU pod: created on the v1 API, because the v2 REST API does not support CPU pods yet."
|
|
1498
|
+
});
|
|
1499
|
+
} catch (error) {
|
|
1500
|
+
if (error instanceof HttpError) {
|
|
1501
|
+
return jsonReply2({
|
|
1502
|
+
error: error.message,
|
|
1503
|
+
status: error.status,
|
|
1504
|
+
_servedBy: "v1",
|
|
1505
|
+
_note: "CPU pod create was routed to the v1 API and failed."
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
throw error;
|
|
374
1509
|
}
|
|
375
|
-
|
|
376
|
-
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
const body = backend.mapCreate(params);
|
|
1513
|
+
try {
|
|
1514
|
+
const result = await callRestUrl(
|
|
1515
|
+
`${backend.base}${backend.list}`,
|
|
1516
|
+
"POST",
|
|
1517
|
+
body
|
|
1518
|
+
);
|
|
1519
|
+
if (backend.version === "v2" && (params.gpuTypeIds?.length ?? 0) > 1) {
|
|
1520
|
+
return jsonReply2({
|
|
1521
|
+
...result,
|
|
1522
|
+
_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(", ")}.`
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
return jsonReply2(result);
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
if (error instanceof HttpError && error.status === 501) {
|
|
1528
|
+
return jsonReply2({
|
|
1529
|
+
error: "CPU pods are not yet supported on the v2 REST API. Use a GPU pod, or create the CPU pod via the v1 API (set RUNPOD_REST_VERSION=v1).",
|
|
1530
|
+
status: 501
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
throw error;
|
|
1534
|
+
}
|
|
377
1535
|
}
|
|
378
1536
|
);
|
|
379
|
-
|
|
1537
|
+
server.tool(
|
|
380
1538
|
"update-pod",
|
|
1539
|
+
"Update a pod's mutable fields (name, image, container disk, volume, ports, env). Only the fields you provide change.",
|
|
381
1540
|
{
|
|
382
|
-
podId:
|
|
383
|
-
name:
|
|
384
|
-
imageName:
|
|
385
|
-
containerDiskInGb:
|
|
386
|
-
volumeInGb:
|
|
387
|
-
volumeMountPath:
|
|
388
|
-
ports:
|
|
389
|
-
env:
|
|
1541
|
+
podId: import_zod3.z.string().describe("ID of the pod to update"),
|
|
1542
|
+
name: import_zod3.z.string().optional().describe("New name for the pod"),
|
|
1543
|
+
imageName: import_zod3.z.string().optional().describe("New Docker image"),
|
|
1544
|
+
containerDiskInGb: import_zod3.z.number().optional().describe("New container disk size in GB"),
|
|
1545
|
+
volumeInGb: import_zod3.z.number().optional().describe("New volume size in GB"),
|
|
1546
|
+
volumeMountPath: import_zod3.z.string().optional().describe("New path to mount the volume"),
|
|
1547
|
+
ports: import_zod3.z.array(import_zod3.z.string()).optional().describe("New ports to expose"),
|
|
1548
|
+
env: import_zod3.z.record(import_zod3.z.string()).optional().describe("New environment variables")
|
|
390
1549
|
},
|
|
1550
|
+
{ title: "Update pod", ...WRITE, idempotentHint: true },
|
|
391
1551
|
async (params) => {
|
|
392
1552
|
const { podId, ...updateParams } = params;
|
|
393
|
-
const
|
|
394
|
-
|
|
1553
|
+
const backend = backendFor("pods");
|
|
1554
|
+
const body = backend.mapUpdate(updateParams);
|
|
1555
|
+
const result = await callRestUrl(
|
|
1556
|
+
`${backend.base}${backend.get(podId)}`,
|
|
395
1557
|
"PATCH",
|
|
396
|
-
|
|
1558
|
+
body
|
|
397
1559
|
);
|
|
398
|
-
return
|
|
399
|
-
content: [
|
|
400
|
-
{
|
|
401
|
-
type: "text",
|
|
402
|
-
text: JSON.stringify(result, null, 2)
|
|
403
|
-
}
|
|
404
|
-
]
|
|
405
|
-
};
|
|
1560
|
+
return jsonReply2(result);
|
|
406
1561
|
}
|
|
407
1562
|
);
|
|
408
|
-
|
|
1563
|
+
server.tool(
|
|
409
1564
|
"start-pod",
|
|
1565
|
+
"Start a stopped pod (resumes the same pod and its billing).",
|
|
410
1566
|
{
|
|
411
|
-
podId:
|
|
1567
|
+
podId: import_zod3.z.string().describe("ID of the pod to start")
|
|
412
1568
|
},
|
|
1569
|
+
{ title: "Start pod", ...WRITE, idempotentHint: true },
|
|
413
1570
|
async (params) => {
|
|
414
|
-
const result = await
|
|
415
|
-
return
|
|
416
|
-
content: [
|
|
417
|
-
{
|
|
418
|
-
type: "text",
|
|
419
|
-
text: JSON.stringify(result, null, 2)
|
|
420
|
-
}
|
|
421
|
-
]
|
|
422
|
-
};
|
|
1571
|
+
const result = await podAction(params.podId, "start");
|
|
1572
|
+
return jsonReply2(result);
|
|
423
1573
|
}
|
|
424
1574
|
);
|
|
425
|
-
|
|
1575
|
+
server.tool(
|
|
426
1576
|
"stop-pod",
|
|
1577
|
+
"Stop a running pod. The pod and its disk persist; GPU billing stops.",
|
|
427
1578
|
{
|
|
428
|
-
podId:
|
|
1579
|
+
podId: import_zod3.z.string().describe("ID of the pod to stop")
|
|
429
1580
|
},
|
|
1581
|
+
{ title: "Stop pod", ...WRITE, idempotentHint: true },
|
|
430
1582
|
async (params) => {
|
|
431
|
-
const result = await
|
|
432
|
-
return
|
|
433
|
-
content: [
|
|
434
|
-
{
|
|
435
|
-
type: "text",
|
|
436
|
-
text: JSON.stringify(result, null, 2)
|
|
437
|
-
}
|
|
438
|
-
]
|
|
439
|
-
};
|
|
1583
|
+
const result = await podAction(params.podId, "stop");
|
|
1584
|
+
return jsonReply2(result);
|
|
440
1585
|
}
|
|
441
1586
|
);
|
|
442
|
-
|
|
1587
|
+
server.tool(
|
|
1588
|
+
"restart-pod",
|
|
1589
|
+
"Restart a running pod (single PodAction call). v2-only \u2014 on the v1 API returns a 501 notice (stop then start the pod instead).",
|
|
1590
|
+
{
|
|
1591
|
+
podId: import_zod3.z.string().describe("ID of the pod to restart")
|
|
1592
|
+
},
|
|
1593
|
+
{ title: "Restart pod", ...WRITE },
|
|
1594
|
+
async (params) => {
|
|
1595
|
+
const backend = backendFor("pods");
|
|
1596
|
+
if (backend.version === "v1") {
|
|
1597
|
+
return jsonReply2({
|
|
1598
|
+
error: "restart-pod is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2 (or stop then start the pod on v1).",
|
|
1599
|
+
status: 501
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
const result = await podAction(params.podId, "restart");
|
|
1603
|
+
return jsonReply2(result);
|
|
1604
|
+
}
|
|
1605
|
+
);
|
|
1606
|
+
server.tool(
|
|
443
1607
|
"delete-pod",
|
|
1608
|
+
"Permanently delete a pod and its data. This cannot be undone.",
|
|
444
1609
|
{
|
|
445
|
-
podId:
|
|
1610
|
+
podId: import_zod3.z.string().describe("ID of the pod to delete")
|
|
446
1611
|
},
|
|
1612
|
+
{ title: "Delete pod", ...DESTRUCTIVE },
|
|
447
1613
|
async (params) => {
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
]
|
|
456
|
-
};
|
|
1614
|
+
const backend = backendFor("pods");
|
|
1615
|
+
const result = await callRestUrl(
|
|
1616
|
+
`${backend.base}${backend.get(params.podId)}`,
|
|
1617
|
+
"DELETE"
|
|
1618
|
+
);
|
|
1619
|
+
return jsonReply2(result);
|
|
457
1620
|
}
|
|
458
1621
|
);
|
|
459
|
-
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// src/tools/endpoints.ts
|
|
1625
|
+
var import_zod4 = require("zod");
|
|
1626
|
+
function registerEndpointTools(server, rt) {
|
|
1627
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
1628
|
+
server.tool(
|
|
460
1629
|
"list-endpoints",
|
|
1630
|
+
"List your Serverless endpoints, optionally expanding template and worker details (v1 only). Paginated via limit/cursor.",
|
|
461
1631
|
{
|
|
462
|
-
|
|
463
|
-
|
|
1632
|
+
...listPaginationParams,
|
|
1633
|
+
includeTemplate: import_zod4.z.boolean().optional().describe("Include template information (v1 only)"),
|
|
1634
|
+
includeWorkers: import_zod4.z.boolean().optional().describe("Include information about workers (v1 only)")
|
|
464
1635
|
},
|
|
1636
|
+
{ title: "List endpoints", ...READ_ONLY },
|
|
465
1637
|
async (params) => {
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
1638
|
+
const backend = backendFor("endpoints");
|
|
1639
|
+
let queryString = "";
|
|
1640
|
+
if (backend.version === "v1") {
|
|
1641
|
+
const queryParams = new URLSearchParams();
|
|
1642
|
+
if (params.includeTemplate)
|
|
1643
|
+
queryParams.append(
|
|
1644
|
+
"includeTemplate",
|
|
1645
|
+
params.includeTemplate.toString()
|
|
1646
|
+
);
|
|
1647
|
+
if (params.includeWorkers)
|
|
1648
|
+
queryParams.append(
|
|
1649
|
+
"includeWorkers",
|
|
1650
|
+
params.includeWorkers.toString()
|
|
1651
|
+
);
|
|
1652
|
+
queryString = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
1653
|
+
}
|
|
1654
|
+
const result = await callRestUrl(
|
|
1655
|
+
`${backend.base}${backend.list}${queryString}`
|
|
1656
|
+
);
|
|
1657
|
+
return capListResult(backend.unwrap(result), {
|
|
1658
|
+
limit: params.limit,
|
|
1659
|
+
cursor: params.cursor
|
|
1660
|
+
});
|
|
484
1661
|
}
|
|
485
1662
|
);
|
|
486
|
-
|
|
1663
|
+
server.tool(
|
|
487
1664
|
"get-endpoint",
|
|
1665
|
+
"Get one Serverless endpoint by id, optionally expanding template and worker details (v1 only).",
|
|
488
1666
|
{
|
|
489
|
-
endpointId:
|
|
490
|
-
includeTemplate:
|
|
491
|
-
includeWorkers:
|
|
1667
|
+
endpointId: import_zod4.z.string().describe("ID of the endpoint to retrieve"),
|
|
1668
|
+
includeTemplate: import_zod4.z.boolean().optional().describe("Include template information (v1 only)"),
|
|
1669
|
+
includeWorkers: import_zod4.z.boolean().optional().describe("Include information about workers (v1 only)")
|
|
492
1670
|
},
|
|
1671
|
+
{ title: "Get endpoint", ...READ_ONLY },
|
|
493
1672
|
async (params) => {
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
1673
|
+
const backend = backendFor("endpoints");
|
|
1674
|
+
let queryString = "";
|
|
1675
|
+
if (backend.version === "v1") {
|
|
1676
|
+
const queryParams = new URLSearchParams();
|
|
1677
|
+
if (params.includeTemplate)
|
|
1678
|
+
queryParams.append(
|
|
1679
|
+
"includeTemplate",
|
|
1680
|
+
params.includeTemplate.toString()
|
|
1681
|
+
);
|
|
1682
|
+
if (params.includeWorkers)
|
|
1683
|
+
queryParams.append(
|
|
1684
|
+
"includeWorkers",
|
|
1685
|
+
params.includeWorkers.toString()
|
|
1686
|
+
);
|
|
1687
|
+
queryString = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
1688
|
+
}
|
|
1689
|
+
const result = await callRestUrl(
|
|
1690
|
+
`${backend.base}${backend.get(params.endpointId)}${queryString}`
|
|
505
1691
|
);
|
|
506
|
-
return
|
|
507
|
-
content: [
|
|
508
|
-
{
|
|
509
|
-
type: "text",
|
|
510
|
-
text: JSON.stringify(result, null, 2)
|
|
511
|
-
}
|
|
512
|
-
]
|
|
513
|
-
};
|
|
1692
|
+
return jsonReply2(result);
|
|
514
1693
|
}
|
|
515
1694
|
);
|
|
516
|
-
|
|
1695
|
+
server.tool(
|
|
517
1696
|
"create-endpoint",
|
|
1697
|
+
"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).",
|
|
518
1698
|
{
|
|
519
|
-
name:
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
1699
|
+
name: import_zod4.z.string().optional().describe("Name for the endpoint"),
|
|
1700
|
+
// --- v2 inline-config fields ---
|
|
1701
|
+
imageName: import_zod4.z.string().optional().describe("Docker image (v2). Required on v2 instead of a templateId."),
|
|
1702
|
+
gpuPoolIds: import_zod4.z.array(import_zod4.z.string()).optional().describe(
|
|
1703
|
+
'GPU pool names (v2, required). The `pool` field from list-gpu-types, e.g. ["AMPERE_80"]. NOT GPU type ids.'
|
|
1704
|
+
),
|
|
1705
|
+
gpuCount: import_zod4.z.number().optional().describe("GPUs per worker (v2)"),
|
|
1706
|
+
args: import_zod4.z.string().optional().describe("Container start command/args (v2)"),
|
|
1707
|
+
containerDiskInGb: import_zod4.z.number().optional().describe("Container disk size in GB (v2)"),
|
|
1708
|
+
ports: import_zod4.z.array(import_zod4.z.string()).optional().describe("Ports to expose (v2), e.g. ['8000/http']"),
|
|
1709
|
+
env: import_zod4.z.record(import_zod4.z.string()).optional().describe("Environment variables (v2)"),
|
|
1710
|
+
containerRegistryAuthId: import_zod4.z.string().optional().describe("Container registry auth id for a private image (v2)"),
|
|
1711
|
+
networkVolumeIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("Network volume ids to attach (v2)"),
|
|
1712
|
+
executionTimeoutMs: import_zod4.z.number().optional().describe("Per-job execution timeout in ms (v2)"),
|
|
1713
|
+
flashboot: import_zod4.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("FlashBoot mode (v2)"),
|
|
1714
|
+
// --- v1 template-based fields ---
|
|
1715
|
+
templateId: import_zod4.z.string().optional().describe("Template ID (v1). Required on v1."),
|
|
1716
|
+
computeType: import_zod4.z.enum(["GPU", "CPU"]).optional().describe("GPU or CPU endpoint (v1)"),
|
|
1717
|
+
gpuTypeIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("List of acceptable GPU types (v1)"),
|
|
1718
|
+
// --- shared autoscaling fields ---
|
|
1719
|
+
workersMin: import_zod4.z.number().optional().describe("Minimum number of workers"),
|
|
1720
|
+
workersMax: import_zod4.z.number().optional().describe("Maximum number of workers"),
|
|
1721
|
+
scalerType: import_zod4.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Autoscaler type"),
|
|
1722
|
+
scalerValue: import_zod4.z.number().optional().describe("Autoscaler target value"),
|
|
1723
|
+
idleTimeout: import_zod4.z.number().optional().describe("Idle timeout in seconds before scaling a worker down"),
|
|
1724
|
+
dataCenterIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("List of preferred data centers")
|
|
527
1725
|
},
|
|
1726
|
+
{ title: "Create endpoint", ...WRITE },
|
|
528
1727
|
async (params) => {
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
{
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
|
|
1728
|
+
const backend = backendFor("endpoints");
|
|
1729
|
+
if (backend.version === "v2") {
|
|
1730
|
+
if (!params.name) {
|
|
1731
|
+
return jsonReply2({
|
|
1732
|
+
error: "On the v2 REST API, create-endpoint requires a name.",
|
|
1733
|
+
status: 400
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
if (!params.imageName) {
|
|
1737
|
+
return jsonReply2({
|
|
1738
|
+
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.",
|
|
1739
|
+
status: 400
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
if (!params.gpuPoolIds?.length) {
|
|
1743
|
+
return jsonReply2({
|
|
1744
|
+
error: 'create-endpoint needs gpuPoolIds on v2 (GPU pool names from list-gpu-types \u2014 the `pool` field, e.g. ["AMPERE_80"]).',
|
|
1745
|
+
status: 400
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
const body = backend.mapCreate(params);
|
|
1749
|
+
const result2 = await callRestUrl(
|
|
1750
|
+
`${backend.base}${backend.list}`,
|
|
1751
|
+
"POST",
|
|
1752
|
+
body
|
|
1753
|
+
);
|
|
1754
|
+
return jsonReply2(result2);
|
|
1755
|
+
}
|
|
1756
|
+
if (!params.templateId) {
|
|
1757
|
+
return jsonReply2({
|
|
1758
|
+
error: "On the v1 REST API, create-endpoint needs a templateId. (On v2 \u2014 the default \u2014 pass imageName + gpuPoolIds instead.)",
|
|
1759
|
+
status: 400
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
const v1Body = {};
|
|
1763
|
+
for (const k of [
|
|
1764
|
+
"templateId",
|
|
1765
|
+
"name",
|
|
1766
|
+
"computeType",
|
|
1767
|
+
"gpuTypeIds",
|
|
1768
|
+
"gpuCount",
|
|
1769
|
+
"workersMin",
|
|
1770
|
+
"workersMax",
|
|
1771
|
+
"dataCenterIds"
|
|
1772
|
+
]) {
|
|
1773
|
+
if (params[k] !== void 0) v1Body[k] = params[k];
|
|
1774
|
+
}
|
|
1775
|
+
const result = await callRestUrl(
|
|
1776
|
+
`${backend.base}${backend.list}`,
|
|
1777
|
+
"POST",
|
|
1778
|
+
v1Body
|
|
1779
|
+
);
|
|
1780
|
+
return jsonReply2(result);
|
|
538
1781
|
}
|
|
539
1782
|
);
|
|
540
|
-
|
|
1783
|
+
server.tool(
|
|
541
1784
|
"update-endpoint",
|
|
1785
|
+
"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.",
|
|
542
1786
|
{
|
|
543
|
-
endpointId:
|
|
544
|
-
name:
|
|
545
|
-
workersMin:
|
|
546
|
-
workersMax:
|
|
547
|
-
idleTimeout:
|
|
548
|
-
scalerType:
|
|
549
|
-
scalerValue:
|
|
1787
|
+
endpointId: import_zod4.z.string().describe("ID of the endpoint to update"),
|
|
1788
|
+
name: import_zod4.z.string().optional().describe("New name for the endpoint"),
|
|
1789
|
+
workersMin: import_zod4.z.number().optional().describe("New minimum number of workers"),
|
|
1790
|
+
workersMax: import_zod4.z.number().optional().describe("New maximum number of workers"),
|
|
1791
|
+
idleTimeout: import_zod4.z.number().optional().describe("New idle timeout in seconds"),
|
|
1792
|
+
scalerType: import_zod4.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Scaler type"),
|
|
1793
|
+
scalerValue: import_zod4.z.number().optional().describe("Scaler value"),
|
|
1794
|
+
// --- v2-only inline-config fields ---
|
|
1795
|
+
imageName: import_zod4.z.string().optional().describe("New Docker image (v2)"),
|
|
1796
|
+
gpuPoolIds: import_zod4.z.array(import_zod4.z.string()).optional().describe('New GPU pool names (v2), e.g. ["AMPERE_80"]'),
|
|
1797
|
+
gpuCount: import_zod4.z.number().optional().describe("New GPUs per worker (v2)"),
|
|
1798
|
+
args: import_zod4.z.string().optional().describe("New container args (v2)"),
|
|
1799
|
+
containerDiskInGb: import_zod4.z.number().optional().describe("New container disk size in GB (v2)"),
|
|
1800
|
+
ports: import_zod4.z.array(import_zod4.z.string()).optional().describe("New ports (v2)"),
|
|
1801
|
+
env: import_zod4.z.record(import_zod4.z.string()).optional().describe("New environment variables (v2)"),
|
|
1802
|
+
containerRegistryAuthId: import_zod4.z.string().optional().describe("New container registry auth id (v2)"),
|
|
1803
|
+
networkVolumeIds: import_zod4.z.array(import_zod4.z.string()).optional().describe("New network volume ids (v2)"),
|
|
1804
|
+
executionTimeoutMs: import_zod4.z.number().optional().describe("New per-job execution timeout in ms (v2)"),
|
|
1805
|
+
flashboot: import_zod4.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("New FlashBoot mode (v2)")
|
|
550
1806
|
},
|
|
1807
|
+
{ title: "Update endpoint", ...WRITE, idempotentHint: true },
|
|
551
1808
|
async (params) => {
|
|
552
1809
|
const { endpointId, ...updateParams } = params;
|
|
553
|
-
const
|
|
554
|
-
|
|
1810
|
+
const backend = backendFor("endpoints");
|
|
1811
|
+
const body = backend.mapUpdate(updateParams);
|
|
1812
|
+
const result = await callRestUrl(
|
|
1813
|
+
`${backend.base}${backend.get(endpointId)}`,
|
|
555
1814
|
"PATCH",
|
|
556
|
-
|
|
1815
|
+
body
|
|
557
1816
|
);
|
|
558
|
-
return
|
|
559
|
-
content: [
|
|
560
|
-
{
|
|
561
|
-
type: "text",
|
|
562
|
-
text: JSON.stringify(result, null, 2)
|
|
563
|
-
}
|
|
564
|
-
]
|
|
565
|
-
};
|
|
1817
|
+
return jsonReply2(result);
|
|
566
1818
|
}
|
|
567
1819
|
);
|
|
568
|
-
|
|
1820
|
+
server.tool(
|
|
569
1821
|
"delete-endpoint",
|
|
1822
|
+
"Permanently delete a Serverless endpoint. This cannot be undone.",
|
|
570
1823
|
{
|
|
571
|
-
endpointId:
|
|
1824
|
+
endpointId: import_zod4.z.string().describe("ID of the endpoint to delete")
|
|
572
1825
|
},
|
|
1826
|
+
{ title: "Delete endpoint", ...DESTRUCTIVE },
|
|
573
1827
|
async (params) => {
|
|
574
|
-
const
|
|
575
|
-
|
|
1828
|
+
const backend = backendFor("endpoints");
|
|
1829
|
+
const result = await callRestUrl(
|
|
1830
|
+
`${backend.base}${backend.get(params.endpointId)}`,
|
|
576
1831
|
"DELETE"
|
|
577
1832
|
);
|
|
578
|
-
return
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
1833
|
+
return jsonReply2(result);
|
|
1834
|
+
}
|
|
1835
|
+
);
|
|
1836
|
+
server.tool(
|
|
1837
|
+
"list-endpoint-workers",
|
|
1838
|
+
"List the workers backing a Serverless endpoint, with their status and an aggregate summary. v2-only \u2014 returns a 501 notice on the v1 API. Paginated via limit/cursor.",
|
|
1839
|
+
{
|
|
1840
|
+
...listPaginationParams,
|
|
1841
|
+
endpointId: import_zod4.z.string().describe("ID of the Serverless endpoint whose workers to list")
|
|
1842
|
+
},
|
|
1843
|
+
{ title: "List endpoint workers", ...READ_ONLY },
|
|
1844
|
+
async (params) => {
|
|
1845
|
+
const backend = backendFor("workers");
|
|
1846
|
+
if (backend.version === "v1") {
|
|
1847
|
+
return jsonReply2({
|
|
1848
|
+
error: "list-endpoint-workers is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2 (on v1, use get-endpoint with includeWorkers).",
|
|
1849
|
+
status: 501
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
const raw = await callRestUrl(
|
|
1853
|
+
`${backend.base}/serverless/${params.endpointId}/workers`
|
|
1854
|
+
);
|
|
1855
|
+
return capListResult(
|
|
1856
|
+
backend.unwrap(raw),
|
|
1857
|
+
{ limit: params.limit, cursor: params.cursor },
|
|
1858
|
+
{ summary: raw?.summary, endpointVersion: raw?.endpointVersion }
|
|
1859
|
+
);
|
|
586
1860
|
}
|
|
587
1861
|
);
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// src/tools/jobs.ts
|
|
1865
|
+
var import_zod5 = require("zod");
|
|
1866
|
+
function registerJobTools(server, rt) {
|
|
1867
|
+
const { jsonReply: jsonReply2, serverlessRequest } = rt;
|
|
1868
|
+
const endpointIdSchema = import_zod5.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid endpoint ID format");
|
|
1869
|
+
const jobIdSchema = import_zod5.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid job ID format");
|
|
1870
|
+
const inputSchema = import_zod5.z.record(import_zod5.z.unknown()).describe(
|
|
591
1871
|
"Input payload for the worker handler. The expected fields depend on the deployed model or worker."
|
|
592
1872
|
);
|
|
593
|
-
const webhookSchema =
|
|
1873
|
+
const webhookSchema = import_zod5.z.string().url().optional().describe(
|
|
594
1874
|
"Webhook URL to receive job completion notifications instead of polling"
|
|
595
1875
|
);
|
|
596
|
-
const policySchema =
|
|
597
|
-
executionTimeout:
|
|
598
|
-
lowPriority:
|
|
599
|
-
ttl:
|
|
1876
|
+
const policySchema = import_zod5.z.object({
|
|
1877
|
+
executionTimeout: import_zod5.z.number().optional().describe("Maximum execution time in milliseconds"),
|
|
1878
|
+
lowPriority: import_zod5.z.boolean().optional().describe("Submit as a low-priority job"),
|
|
1879
|
+
ttl: import_zod5.z.number().optional().describe("Time-to-live for the job result in milliseconds")
|
|
600
1880
|
}).optional().describe("Execution policy options");
|
|
601
|
-
const s3ConfigSchema =
|
|
602
|
-
accessId:
|
|
603
|
-
accessSecret:
|
|
604
|
-
bucketName:
|
|
605
|
-
endpointUrl:
|
|
1881
|
+
const s3ConfigSchema = import_zod5.z.object({
|
|
1882
|
+
accessId: import_zod5.z.string().describe("S3 access key ID"),
|
|
1883
|
+
accessSecret: import_zod5.z.string().describe("S3 secret access key"),
|
|
1884
|
+
bucketName: import_zod5.z.string().describe("S3 bucket name"),
|
|
1885
|
+
endpointUrl: import_zod5.z.string().describe("S3 endpoint URL")
|
|
606
1886
|
}).optional().describe("S3-compatible storage config for large outputs");
|
|
607
|
-
|
|
1887
|
+
server.tool(
|
|
608
1888
|
"run-endpoint",
|
|
609
1889
|
"Submit an asynchronous job to a Serverless endpoint. Returns a job ID immediately \u2014 use get-job-status to poll for results. Async results are available for 30 minutes after completion.",
|
|
610
1890
|
{
|
|
@@ -616,6 +1896,7 @@ function registerTools(server2, ctx) {
|
|
|
616
1896
|
policy: policySchema,
|
|
617
1897
|
s3Config: s3ConfigSchema
|
|
618
1898
|
},
|
|
1899
|
+
{ title: "Run endpoint (async)", ...WRITE },
|
|
619
1900
|
async (params) => {
|
|
620
1901
|
const { endpointId, ...body } = params;
|
|
621
1902
|
const result = await serverlessRequest(
|
|
@@ -624,17 +1905,10 @@ function registerTools(server2, ctx) {
|
|
|
624
1905
|
"POST",
|
|
625
1906
|
body
|
|
626
1907
|
);
|
|
627
|
-
return
|
|
628
|
-
content: [
|
|
629
|
-
{
|
|
630
|
-
type: "text",
|
|
631
|
-
text: JSON.stringify(result, null, 2)
|
|
632
|
-
}
|
|
633
|
-
]
|
|
634
|
-
};
|
|
1908
|
+
return jsonReply2(result);
|
|
635
1909
|
}
|
|
636
1910
|
);
|
|
637
|
-
|
|
1911
|
+
server.tool(
|
|
638
1912
|
"runsync-endpoint",
|
|
639
1913
|
"Submit a synchronous job to a Serverless endpoint and wait for the result. Best for tasks completing within 90 seconds. If processing exceeds 90 seconds, the response returns a job ID to poll with get-job-status. Max payload: 20 MB. Results expire after 1 minute. Use the wait parameter to extend the server-side wait up to 5 minutes (300000 ms).",
|
|
640
1914
|
{
|
|
@@ -642,33 +1916,27 @@ function registerTools(server2, ctx) {
|
|
|
642
1916
|
"ID of the Serverless endpoint to run synchronously"
|
|
643
1917
|
),
|
|
644
1918
|
input: inputSchema,
|
|
645
|
-
wait:
|
|
1919
|
+
wait: import_zod5.z.number().min(1e3).max(3e5).optional().describe(
|
|
646
1920
|
"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)."
|
|
647
1921
|
),
|
|
648
1922
|
webhook: webhookSchema,
|
|
649
1923
|
policy: policySchema,
|
|
650
1924
|
s3Config: s3ConfigSchema
|
|
651
1925
|
},
|
|
1926
|
+
{ title: "Run endpoint (sync)", ...WRITE },
|
|
652
1927
|
async (params) => {
|
|
653
1928
|
const { endpointId, wait, ...body } = params;
|
|
654
|
-
const
|
|
1929
|
+
const path2 = wait ? `/runsync?wait=${wait}` : "/runsync";
|
|
655
1930
|
const result = await serverlessRequest(
|
|
656
1931
|
endpointId,
|
|
657
|
-
|
|
1932
|
+
path2,
|
|
658
1933
|
"POST",
|
|
659
1934
|
body
|
|
660
1935
|
);
|
|
661
|
-
return
|
|
662
|
-
content: [
|
|
663
|
-
{
|
|
664
|
-
type: "text",
|
|
665
|
-
text: JSON.stringify(result, null, 2)
|
|
666
|
-
}
|
|
667
|
-
]
|
|
668
|
-
};
|
|
1936
|
+
return jsonReply2(result);
|
|
669
1937
|
}
|
|
670
1938
|
);
|
|
671
|
-
|
|
1939
|
+
server.tool(
|
|
672
1940
|
"get-job-status",
|
|
673
1941
|
"Check the status of an asynchronous Serverless job. Returns the current status and output when complete. Job statuses: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELLED, TIMED_OUT.",
|
|
674
1942
|
{
|
|
@@ -677,22 +1945,16 @@ function registerTools(server2, ctx) {
|
|
|
677
1945
|
),
|
|
678
1946
|
jobId: jobIdSchema.describe("ID of the job to check")
|
|
679
1947
|
},
|
|
1948
|
+
{ title: "Get job status", ...READ_ONLY },
|
|
680
1949
|
async (params) => {
|
|
681
1950
|
const result = await serverlessRequest(
|
|
682
1951
|
params.endpointId,
|
|
683
1952
|
`/status/${params.jobId}`
|
|
684
1953
|
);
|
|
685
|
-
return
|
|
686
|
-
content: [
|
|
687
|
-
{
|
|
688
|
-
type: "text",
|
|
689
|
-
text: JSON.stringify(result, null, 2)
|
|
690
|
-
}
|
|
691
|
-
]
|
|
692
|
-
};
|
|
1954
|
+
return jsonReply2(result);
|
|
693
1955
|
}
|
|
694
1956
|
);
|
|
695
|
-
|
|
1957
|
+
server.tool(
|
|
696
1958
|
"stream-job",
|
|
697
1959
|
"Retrieve all streaming output from a Serverless job by polling until the job reaches a terminal state. The worker must support streaming output. Polls /stream/{jobId} repeatedly and collects every chunk until status is COMPLETED, FAILED, CANCELLED, or TIMED_OUT.",
|
|
698
1960
|
{
|
|
@@ -701,6 +1963,7 @@ function registerTools(server2, ctx) {
|
|
|
701
1963
|
),
|
|
702
1964
|
jobId: jobIdSchema.describe("ID of the job to stream results from")
|
|
703
1965
|
},
|
|
1966
|
+
{ title: "Stream job", ...READ_ONLY },
|
|
704
1967
|
async (params) => {
|
|
705
1968
|
const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
706
1969
|
"COMPLETED",
|
|
@@ -746,21 +2009,10 @@ function registerTools(server2, ctx) {
|
|
|
746
2009
|
}
|
|
747
2010
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
748
2011
|
}
|
|
749
|
-
return {
|
|
750
|
-
content: [
|
|
751
|
-
{
|
|
752
|
-
type: "text",
|
|
753
|
-
text: JSON.stringify(
|
|
754
|
-
{ ...finalResult, stream: allChunks },
|
|
755
|
-
null,
|
|
756
|
-
2
|
|
757
|
-
)
|
|
758
|
-
}
|
|
759
|
-
]
|
|
760
|
-
};
|
|
2012
|
+
return jsonReply2({ ...finalResult, stream: allChunks });
|
|
761
2013
|
}
|
|
762
2014
|
);
|
|
763
|
-
|
|
2015
|
+
server.tool(
|
|
764
2016
|
"cancel-job",
|
|
765
2017
|
"Cancel a Serverless job that is queued or in progress.",
|
|
766
2018
|
{
|
|
@@ -769,23 +2021,17 @@ function registerTools(server2, ctx) {
|
|
|
769
2021
|
),
|
|
770
2022
|
jobId: jobIdSchema.describe("ID of the job to cancel")
|
|
771
2023
|
},
|
|
2024
|
+
{ title: "Cancel job", ...WRITE, idempotentHint: true },
|
|
772
2025
|
async (params) => {
|
|
773
2026
|
const result = await serverlessRequest(
|
|
774
2027
|
params.endpointId,
|
|
775
2028
|
`/cancel/${params.jobId}`,
|
|
776
2029
|
"POST"
|
|
777
2030
|
);
|
|
778
|
-
return
|
|
779
|
-
content: [
|
|
780
|
-
{
|
|
781
|
-
type: "text",
|
|
782
|
-
text: JSON.stringify(result, null, 2)
|
|
783
|
-
}
|
|
784
|
-
]
|
|
785
|
-
};
|
|
2031
|
+
return jsonReply2(result);
|
|
786
2032
|
}
|
|
787
2033
|
);
|
|
788
|
-
|
|
2034
|
+
server.tool(
|
|
789
2035
|
"retry-job",
|
|
790
2036
|
"Retry a failed or timed-out Serverless job. Only works for jobs with FAILED or TIMED_OUT status. The previous output is removed and the job is requeued.",
|
|
791
2037
|
{
|
|
@@ -794,23 +2040,17 @@ function registerTools(server2, ctx) {
|
|
|
794
2040
|
),
|
|
795
2041
|
jobId: jobIdSchema.describe("ID of the job to retry")
|
|
796
2042
|
},
|
|
2043
|
+
{ title: "Retry job", ...WRITE },
|
|
797
2044
|
async (params) => {
|
|
798
2045
|
const result = await serverlessRequest(
|
|
799
2046
|
params.endpointId,
|
|
800
2047
|
`/retry/${params.jobId}`,
|
|
801
2048
|
"POST"
|
|
802
2049
|
);
|
|
803
|
-
return
|
|
804
|
-
content: [
|
|
805
|
-
{
|
|
806
|
-
type: "text",
|
|
807
|
-
text: JSON.stringify(result, null, 2)
|
|
808
|
-
}
|
|
809
|
-
]
|
|
810
|
-
};
|
|
2050
|
+
return jsonReply2(result);
|
|
811
2051
|
}
|
|
812
2052
|
);
|
|
813
|
-
|
|
2053
|
+
server.tool(
|
|
814
2054
|
"endpoint-health",
|
|
815
2055
|
"Get the health and operational status of a Serverless endpoint, including worker counts and job statistics.",
|
|
816
2056
|
{
|
|
@@ -818,19 +2058,13 @@ function registerTools(server2, ctx) {
|
|
|
818
2058
|
"ID of the Serverless endpoint to check health for"
|
|
819
2059
|
)
|
|
820
2060
|
},
|
|
2061
|
+
{ title: "Endpoint health", ...READ_ONLY },
|
|
821
2062
|
async (params) => {
|
|
822
2063
|
const result = await serverlessRequest(params.endpointId, "/health");
|
|
823
|
-
return
|
|
824
|
-
content: [
|
|
825
|
-
{
|
|
826
|
-
type: "text",
|
|
827
|
-
text: JSON.stringify(result, null, 2)
|
|
828
|
-
}
|
|
829
|
-
]
|
|
830
|
-
};
|
|
2064
|
+
return jsonReply2(result);
|
|
831
2065
|
}
|
|
832
2066
|
);
|
|
833
|
-
|
|
2067
|
+
server.tool(
|
|
834
2068
|
"purge-endpoint-queue",
|
|
835
2069
|
"Remove all pending jobs from a Serverless endpoint queue. Only affects queued jobs \u2014 in-progress jobs continue running. Use this for error recovery or clearing outdated requests.",
|
|
836
2070
|
{
|
|
@@ -838,321 +2072,594 @@ function registerTools(server2, ctx) {
|
|
|
838
2072
|
"ID of the Serverless endpoint to purge the queue for"
|
|
839
2073
|
)
|
|
840
2074
|
},
|
|
2075
|
+
{ title: "Purge endpoint queue", ...WRITE, idempotentHint: true },
|
|
841
2076
|
async (params) => {
|
|
842
2077
|
const result = await serverlessRequest(
|
|
843
2078
|
params.endpointId,
|
|
844
2079
|
"/purge-queue",
|
|
845
2080
|
"POST"
|
|
846
2081
|
);
|
|
847
|
-
return
|
|
848
|
-
content: [
|
|
849
|
-
{
|
|
850
|
-
type: "text",
|
|
851
|
-
text: JSON.stringify(result, null, 2)
|
|
852
|
-
}
|
|
853
|
-
]
|
|
854
|
-
};
|
|
2082
|
+
return jsonReply2(result);
|
|
855
2083
|
}
|
|
856
2084
|
);
|
|
857
|
-
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
// src/tools/templates.ts
|
|
2088
|
+
var import_zod6 = require("zod");
|
|
2089
|
+
function registerTemplateTools(server, rt) {
|
|
2090
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
2091
|
+
server.tool(
|
|
858
2092
|
"list-templates",
|
|
859
2093
|
`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.`,
|
|
860
2094
|
{
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
2095
|
+
...listPaginationParams,
|
|
2096
|
+
includeRunpodTemplates: import_zod6.z.boolean().optional().describe("Include official Runpod templates in the response"),
|
|
2097
|
+
includePublicTemplates: import_zod6.z.boolean().optional().describe("Include community-made public templates in the response"),
|
|
2098
|
+
includeEndpointBoundTemplates: import_zod6.z.boolean().optional().describe(
|
|
864
2099
|
"Include templates bound to Serverless endpoints in the response"
|
|
865
2100
|
)
|
|
866
2101
|
},
|
|
2102
|
+
{ title: "List templates", ...READ_ONLY },
|
|
867
2103
|
async (params) => {
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
2104
|
+
const backend = backendFor("templates");
|
|
2105
|
+
let query = "";
|
|
2106
|
+
if (backend.version === "v1") {
|
|
2107
|
+
const queryParams = new URLSearchParams();
|
|
2108
|
+
if (params.includeRunpodTemplates)
|
|
2109
|
+
queryParams.set("includeRunpodTemplates", "true");
|
|
2110
|
+
if (params.includePublicTemplates)
|
|
2111
|
+
queryParams.set("includePublicTemplates", "true");
|
|
2112
|
+
if (params.includeEndpointBoundTemplates)
|
|
2113
|
+
queryParams.set("includeEndpointBoundTemplates", "true");
|
|
2114
|
+
query = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
2115
|
+
}
|
|
2116
|
+
const result = await callRestUrl(
|
|
2117
|
+
`${backend.base}${backend.list}${query}`
|
|
878
2118
|
);
|
|
879
|
-
return {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
text: JSON.stringify(result, null, 2)
|
|
884
|
-
}
|
|
885
|
-
]
|
|
886
|
-
};
|
|
2119
|
+
return capListResult(backend.unwrap(result), {
|
|
2120
|
+
limit: params.limit,
|
|
2121
|
+
cursor: params.cursor
|
|
2122
|
+
});
|
|
887
2123
|
}
|
|
888
2124
|
);
|
|
889
|
-
|
|
2125
|
+
server.tool(
|
|
890
2126
|
"get-template",
|
|
2127
|
+
"Get one template by id (image, env, ports, disk/volume config).",
|
|
891
2128
|
{
|
|
892
|
-
templateId:
|
|
2129
|
+
templateId: import_zod6.z.string().describe("ID of the template to retrieve")
|
|
893
2130
|
},
|
|
2131
|
+
{ title: "Get template", ...READ_ONLY },
|
|
894
2132
|
async (params) => {
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
text: JSON.stringify(result, null, 2)
|
|
901
|
-
}
|
|
902
|
-
]
|
|
903
|
-
};
|
|
2133
|
+
const backend = backendFor("templates");
|
|
2134
|
+
const result = await callRestUrl(
|
|
2135
|
+
`${backend.base}${backend.get(params.templateId)}`
|
|
2136
|
+
);
|
|
2137
|
+
return jsonReply2(result);
|
|
904
2138
|
}
|
|
905
2139
|
);
|
|
906
|
-
|
|
2140
|
+
server.tool(
|
|
907
2141
|
"create-template",
|
|
2142
|
+
"Create a reusable pod/endpoint template (image, ports, env, disk/volume). On v2 a category is required and defaults to NVIDIA.",
|
|
908
2143
|
{
|
|
909
|
-
name:
|
|
910
|
-
imageName:
|
|
911
|
-
isServerless:
|
|
912
|
-
ports:
|
|
913
|
-
dockerEntrypoint:
|
|
914
|
-
dockerStartCmd:
|
|
915
|
-
env:
|
|
916
|
-
containerDiskInGb:
|
|
917
|
-
volumeInGb:
|
|
918
|
-
volumeMountPath:
|
|
919
|
-
readme:
|
|
2144
|
+
name: import_zod6.z.string().describe("Name for the template"),
|
|
2145
|
+
imageName: import_zod6.z.string().describe("Docker image to use"),
|
|
2146
|
+
isServerless: import_zod6.z.boolean().optional().describe("Is this a serverless template"),
|
|
2147
|
+
ports: import_zod6.z.array(import_zod6.z.string()).optional().describe("Ports to expose"),
|
|
2148
|
+
dockerEntrypoint: import_zod6.z.array(import_zod6.z.string()).optional().describe("Docker entrypoint commands"),
|
|
2149
|
+
dockerStartCmd: import_zod6.z.array(import_zod6.z.string()).optional().describe("Docker start commands"),
|
|
2150
|
+
env: import_zod6.z.record(import_zod6.z.string()).optional().describe("Environment variables"),
|
|
2151
|
+
containerDiskInGb: import_zod6.z.number().optional().describe("Container disk size in GB"),
|
|
2152
|
+
volumeInGb: import_zod6.z.number().optional().describe("Volume size in GB"),
|
|
2153
|
+
volumeMountPath: import_zod6.z.string().optional().describe("Path to mount the volume"),
|
|
2154
|
+
readme: import_zod6.z.string().optional().describe("README content in markdown format"),
|
|
2155
|
+
category: import_zod6.z.enum(["CPU", "NVIDIA", "AMD"]).optional().describe(
|
|
2156
|
+
"Hardware category for the template. On v2 a category is required and defaults to NVIDIA when omitted \u2014 set CPU or AMD for non-NVIDIA templates so they are not mis-categorized."
|
|
2157
|
+
),
|
|
2158
|
+
containerRegistryAuthId: import_zod6.z.string().optional().describe(
|
|
2159
|
+
"ID of a container registry credential (from create-container-registry-auth / list-container-registry-auths) used to pull a private image. Required when imageName points at a private registry; without it serverless workers fail the image pull."
|
|
2160
|
+
)
|
|
920
2161
|
},
|
|
2162
|
+
{ title: "Create template", ...WRITE },
|
|
921
2163
|
async (params) => {
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
};
|
|
2164
|
+
const backend = backendFor("templates");
|
|
2165
|
+
const body = backend.mapCreate(params);
|
|
2166
|
+
const result = await callRestUrl(
|
|
2167
|
+
`${backend.base}${backend.list}`,
|
|
2168
|
+
"POST",
|
|
2169
|
+
body
|
|
2170
|
+
);
|
|
2171
|
+
return jsonReply2(result);
|
|
931
2172
|
}
|
|
932
2173
|
);
|
|
933
|
-
|
|
2174
|
+
server.tool(
|
|
934
2175
|
"update-template",
|
|
2176
|
+
"Update a template's mutable fields (name, image, ports, env, readme). Only provided fields change.",
|
|
935
2177
|
{
|
|
936
|
-
templateId:
|
|
937
|
-
name:
|
|
938
|
-
imageName:
|
|
939
|
-
ports:
|
|
940
|
-
env:
|
|
941
|
-
readme:
|
|
2178
|
+
templateId: import_zod6.z.string().describe("ID of the template to update"),
|
|
2179
|
+
name: import_zod6.z.string().optional().describe("New name for the template"),
|
|
2180
|
+
imageName: import_zod6.z.string().optional().describe("New Docker image"),
|
|
2181
|
+
ports: import_zod6.z.array(import_zod6.z.string()).optional().describe("New ports to expose"),
|
|
2182
|
+
env: import_zod6.z.record(import_zod6.z.string()).optional().describe("New environment variables"),
|
|
2183
|
+
readme: import_zod6.z.string().optional().describe("New README content in markdown format"),
|
|
2184
|
+
containerRegistryAuthId: import_zod6.z.string().optional().describe(
|
|
2185
|
+
"ID of a container registry credential (from create-container-registry-auth / list-container-registry-auths) used to pull a private image. Set this to attach a credential to a template referencing a private registry."
|
|
2186
|
+
)
|
|
942
2187
|
},
|
|
2188
|
+
{ title: "Update template", ...WRITE, idempotentHint: true },
|
|
943
2189
|
async (params) => {
|
|
944
2190
|
const { templateId, ...updateParams } = params;
|
|
945
|
-
const
|
|
946
|
-
|
|
2191
|
+
const backend = backendFor("templates");
|
|
2192
|
+
const body = backend.mapUpdate(updateParams);
|
|
2193
|
+
const result = await callRestUrl(
|
|
2194
|
+
`${backend.base}${backend.get(templateId)}`,
|
|
947
2195
|
"PATCH",
|
|
948
|
-
|
|
2196
|
+
body
|
|
949
2197
|
);
|
|
950
|
-
return
|
|
951
|
-
content: [
|
|
952
|
-
{
|
|
953
|
-
type: "text",
|
|
954
|
-
text: JSON.stringify(result, null, 2)
|
|
955
|
-
}
|
|
956
|
-
]
|
|
957
|
-
};
|
|
2198
|
+
return jsonReply2(result);
|
|
958
2199
|
}
|
|
959
2200
|
);
|
|
960
|
-
|
|
2201
|
+
server.tool(
|
|
961
2202
|
"delete-template",
|
|
2203
|
+
"Permanently delete a template. This cannot be undone.",
|
|
962
2204
|
{
|
|
963
|
-
templateId:
|
|
2205
|
+
templateId: import_zod6.z.string().describe("ID of the template to delete")
|
|
964
2206
|
},
|
|
2207
|
+
{ title: "Delete template", ...DESTRUCTIVE },
|
|
965
2208
|
async (params) => {
|
|
966
|
-
const
|
|
967
|
-
|
|
2209
|
+
const backend = backendFor("templates");
|
|
2210
|
+
const result = await callRestUrl(
|
|
2211
|
+
`${backend.base}${backend.get(params.templateId)}`,
|
|
968
2212
|
"DELETE"
|
|
969
2213
|
);
|
|
970
|
-
return
|
|
971
|
-
content: [
|
|
972
|
-
{
|
|
973
|
-
type: "text",
|
|
974
|
-
text: JSON.stringify(result, null, 2)
|
|
975
|
-
}
|
|
976
|
-
]
|
|
977
|
-
};
|
|
2214
|
+
return jsonReply2(result);
|
|
978
2215
|
}
|
|
979
2216
|
);
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// src/tools/network-volumes.ts
|
|
2220
|
+
var import_zod7 = require("zod");
|
|
2221
|
+
function registerNetworkVolumeTools(server, rt) {
|
|
2222
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
2223
|
+
server.tool(
|
|
2224
|
+
"list-network-volumes",
|
|
2225
|
+
"List your network volumes (persistent storage attachable to pods). Paginated via limit/cursor.",
|
|
2226
|
+
listPaginationParams,
|
|
2227
|
+
{ title: "List network volumes", ...READ_ONLY },
|
|
2228
|
+
async (params) => {
|
|
2229
|
+
const backend = backendFor("networkVolumes");
|
|
2230
|
+
const result = await callRestUrl(`${backend.base}${backend.list}`);
|
|
2231
|
+
return capListResult(backend.unwrap(result), {
|
|
2232
|
+
limit: params.limit,
|
|
2233
|
+
cursor: params.cursor
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
);
|
|
2237
|
+
server.tool(
|
|
992
2238
|
"get-network-volume",
|
|
2239
|
+
"Get one network volume by id (name, size, data center).",
|
|
993
2240
|
{
|
|
994
|
-
networkVolumeId:
|
|
2241
|
+
networkVolumeId: import_zod7.z.string().describe("ID of the network volume to retrieve")
|
|
995
2242
|
},
|
|
2243
|
+
{ title: "Get network volume", ...READ_ONLY },
|
|
996
2244
|
async (params) => {
|
|
997
|
-
const
|
|
998
|
-
|
|
2245
|
+
const backend = backendFor("networkVolumes");
|
|
2246
|
+
const result = await callRestUrl(
|
|
2247
|
+
`${backend.base}${backend.get(params.networkVolumeId)}`
|
|
999
2248
|
);
|
|
1000
|
-
return
|
|
1001
|
-
content: [
|
|
1002
|
-
{
|
|
1003
|
-
type: "text",
|
|
1004
|
-
text: JSON.stringify(result, null, 2)
|
|
1005
|
-
}
|
|
1006
|
-
]
|
|
1007
|
-
};
|
|
2249
|
+
return jsonReply2(result);
|
|
1008
2250
|
}
|
|
1009
2251
|
);
|
|
1010
|
-
|
|
2252
|
+
server.tool(
|
|
1011
2253
|
"create-network-volume",
|
|
2254
|
+
"Create a persistent network volume in a data center (size 1\u20134000 GB) that pods can mount.",
|
|
1012
2255
|
{
|
|
1013
|
-
name:
|
|
1014
|
-
size:
|
|
1015
|
-
dataCenterId:
|
|
2256
|
+
name: import_zod7.z.string().describe("Name for the network volume"),
|
|
2257
|
+
size: import_zod7.z.number().describe("Size in GB (1-4000)"),
|
|
2258
|
+
dataCenterId: import_zod7.z.string().describe("Data center ID (see list-data-centers)")
|
|
1016
2259
|
},
|
|
2260
|
+
{ title: "Create network volume", ...WRITE },
|
|
1017
2261
|
async (params) => {
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
};
|
|
2262
|
+
const backend = backendFor("networkVolumes");
|
|
2263
|
+
const body = backend.mapCreate(params);
|
|
2264
|
+
const result = await callRestUrl(
|
|
2265
|
+
`${backend.base}${backend.list}`,
|
|
2266
|
+
"POST",
|
|
2267
|
+
body
|
|
2268
|
+
);
|
|
2269
|
+
return jsonReply2(result);
|
|
1027
2270
|
}
|
|
1028
2271
|
);
|
|
1029
|
-
|
|
2272
|
+
server.tool(
|
|
1030
2273
|
"update-network-volume",
|
|
2274
|
+
"Update a network volume (rename, or grow size \u2014 the new size must exceed the current size). Only provided fields change.",
|
|
1031
2275
|
{
|
|
1032
|
-
networkVolumeId:
|
|
1033
|
-
name:
|
|
1034
|
-
size:
|
|
2276
|
+
networkVolumeId: import_zod7.z.string().describe("ID of the network volume to update"),
|
|
2277
|
+
name: import_zod7.z.string().optional().describe("New name for the network volume"),
|
|
2278
|
+
size: import_zod7.z.number().optional().describe("New size in GB (must be larger than current)")
|
|
1035
2279
|
},
|
|
2280
|
+
{ title: "Update network volume", ...WRITE, idempotentHint: true },
|
|
1036
2281
|
async (params) => {
|
|
1037
2282
|
const { networkVolumeId, ...updateParams } = params;
|
|
1038
|
-
const
|
|
1039
|
-
|
|
2283
|
+
const backend = backendFor("networkVolumes");
|
|
2284
|
+
const body = backend.mapUpdate(updateParams);
|
|
2285
|
+
const result = await callRestUrl(
|
|
2286
|
+
`${backend.base}${backend.get(networkVolumeId)}`,
|
|
1040
2287
|
"PATCH",
|
|
1041
|
-
|
|
2288
|
+
body
|
|
1042
2289
|
);
|
|
1043
|
-
return
|
|
1044
|
-
content: [
|
|
1045
|
-
{
|
|
1046
|
-
type: "text",
|
|
1047
|
-
text: JSON.stringify(result, null, 2)
|
|
1048
|
-
}
|
|
1049
|
-
]
|
|
1050
|
-
};
|
|
2290
|
+
return jsonReply2(result);
|
|
1051
2291
|
}
|
|
1052
2292
|
);
|
|
1053
|
-
|
|
2293
|
+
server.tool(
|
|
1054
2294
|
"delete-network-volume",
|
|
2295
|
+
"Permanently delete a network volume and its data. This cannot be undone.",
|
|
1055
2296
|
{
|
|
1056
|
-
networkVolumeId:
|
|
2297
|
+
networkVolumeId: import_zod7.z.string().describe("ID of the network volume to delete")
|
|
1057
2298
|
},
|
|
2299
|
+
{ title: "Delete network volume", ...DESTRUCTIVE },
|
|
1058
2300
|
async (params) => {
|
|
1059
|
-
const
|
|
1060
|
-
|
|
2301
|
+
const backend = backendFor("networkVolumes");
|
|
2302
|
+
const result = await callRestUrl(
|
|
2303
|
+
`${backend.base}${backend.get(params.networkVolumeId)}`,
|
|
1061
2304
|
"DELETE"
|
|
1062
2305
|
);
|
|
1063
|
-
return
|
|
1064
|
-
content: [
|
|
1065
|
-
{
|
|
1066
|
-
type: "text",
|
|
1067
|
-
text: JSON.stringify(result, null, 2)
|
|
1068
|
-
}
|
|
1069
|
-
]
|
|
1070
|
-
};
|
|
2306
|
+
return jsonReply2(result);
|
|
1071
2307
|
}
|
|
1072
2308
|
);
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
// src/tools/registries.ts
|
|
2312
|
+
var import_zod8 = require("zod");
|
|
2313
|
+
function registerRegistryTools(server, rt) {
|
|
2314
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
2315
|
+
server.tool(
|
|
2316
|
+
"list-container-registry-auths",
|
|
2317
|
+
"List your saved container-registry credentials (private image-pull auth). Paginated via limit/cursor.",
|
|
2318
|
+
listPaginationParams,
|
|
2319
|
+
{ title: "List container registry auths", ...READ_ONLY },
|
|
2320
|
+
async (params) => {
|
|
2321
|
+
const backend = backendFor("registries");
|
|
2322
|
+
const result = await callRestUrl(`${backend.base}${backend.list}`);
|
|
2323
|
+
return capListResult(backend.unwrap(result), {
|
|
2324
|
+
limit: params.limit,
|
|
2325
|
+
cursor: params.cursor
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
);
|
|
2329
|
+
server.tool(
|
|
1085
2330
|
"get-container-registry-auth",
|
|
2331
|
+
"Get one saved container-registry credential by id (the secret/password is not returned).",
|
|
1086
2332
|
{
|
|
1087
|
-
containerRegistryAuthId:
|
|
2333
|
+
containerRegistryAuthId: import_zod8.z.string().describe("ID of the container registry auth to retrieve")
|
|
1088
2334
|
},
|
|
2335
|
+
{ title: "Get container registry auth", ...READ_ONLY },
|
|
1089
2336
|
async (params) => {
|
|
1090
|
-
const
|
|
1091
|
-
|
|
2337
|
+
const backend = backendFor("registries");
|
|
2338
|
+
const result = await callRestUrl(
|
|
2339
|
+
`${backend.base}${backend.get(params.containerRegistryAuthId)}`
|
|
1092
2340
|
);
|
|
1093
|
-
return
|
|
1094
|
-
content: [
|
|
1095
|
-
{
|
|
1096
|
-
type: "text",
|
|
1097
|
-
text: JSON.stringify(result, null, 2)
|
|
1098
|
-
}
|
|
1099
|
-
]
|
|
1100
|
-
};
|
|
2341
|
+
return jsonReply2(result);
|
|
1101
2342
|
}
|
|
1102
2343
|
);
|
|
1103
|
-
|
|
2344
|
+
server.tool(
|
|
1104
2345
|
"create-container-registry-auth",
|
|
2346
|
+
"Save container-registry credentials (username + password/token) so pods and endpoints can pull private images.",
|
|
1105
2347
|
{
|
|
1106
|
-
name:
|
|
1107
|
-
username:
|
|
1108
|
-
password:
|
|
2348
|
+
name: import_zod8.z.string().describe("Name for the container registry auth"),
|
|
2349
|
+
username: import_zod8.z.string().describe("Registry username"),
|
|
2350
|
+
password: import_zod8.z.string().describe("Registry password or access token (stored as a secret)")
|
|
1109
2351
|
},
|
|
2352
|
+
{ title: "Create container registry auth", ...WRITE },
|
|
1110
2353
|
async (params) => {
|
|
1111
|
-
const
|
|
1112
|
-
|
|
2354
|
+
const backend = backendFor("registries");
|
|
2355
|
+
const body = backend.mapCreate(params);
|
|
2356
|
+
const result = await callRestUrl(
|
|
2357
|
+
`${backend.base}${backend.list}`,
|
|
1113
2358
|
"POST",
|
|
1114
|
-
|
|
2359
|
+
body
|
|
1115
2360
|
);
|
|
1116
|
-
return
|
|
1117
|
-
content: [
|
|
1118
|
-
{
|
|
1119
|
-
type: "text",
|
|
1120
|
-
text: JSON.stringify(result, null, 2)
|
|
1121
|
-
}
|
|
1122
|
-
]
|
|
1123
|
-
};
|
|
2361
|
+
return jsonReply2(result);
|
|
1124
2362
|
}
|
|
1125
2363
|
);
|
|
1126
|
-
|
|
2364
|
+
server.tool(
|
|
1127
2365
|
"delete-container-registry-auth",
|
|
2366
|
+
"Permanently delete a saved container-registry credential. This cannot be undone.",
|
|
1128
2367
|
{
|
|
1129
|
-
containerRegistryAuthId:
|
|
2368
|
+
containerRegistryAuthId: import_zod8.z.string().describe("ID of the container registry auth to delete")
|
|
1130
2369
|
},
|
|
2370
|
+
{ title: "Delete container registry auth", ...DESTRUCTIVE },
|
|
1131
2371
|
async (params) => {
|
|
1132
|
-
const
|
|
1133
|
-
|
|
2372
|
+
const backend = backendFor("registries");
|
|
2373
|
+
const result = await callRestUrl(
|
|
2374
|
+
`${backend.base}${backend.get(params.containerRegistryAuthId)}`,
|
|
1134
2375
|
"DELETE"
|
|
1135
2376
|
);
|
|
1136
|
-
return
|
|
1137
|
-
content: [
|
|
1138
|
-
{
|
|
1139
|
-
type: "text",
|
|
1140
|
-
text: JSON.stringify(result, null, 2)
|
|
1141
|
-
}
|
|
1142
|
-
]
|
|
1143
|
-
};
|
|
2377
|
+
return jsonReply2(result);
|
|
1144
2378
|
}
|
|
1145
2379
|
);
|
|
1146
2380
|
}
|
|
1147
2381
|
|
|
2382
|
+
// src/tools/tags.ts
|
|
2383
|
+
var import_zod9 = require("zod");
|
|
2384
|
+
var TAG_RESOURCE_TYPES = [
|
|
2385
|
+
"POD",
|
|
2386
|
+
"NETWORK_VOLUME",
|
|
2387
|
+
"CLUSTER",
|
|
2388
|
+
"SERVERLESS_ENDPOINT"
|
|
2389
|
+
];
|
|
2390
|
+
var taggedResourceSchema = import_zod9.z.object({
|
|
2391
|
+
id: import_zod9.z.string().describe("Resource identifier (e.g. a pod or endpoint id)"),
|
|
2392
|
+
type: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to associate with the tag")
|
|
2393
|
+
});
|
|
2394
|
+
function v2OnlyNotice(tool) {
|
|
2395
|
+
return {
|
|
2396
|
+
error: `${tool} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,
|
|
2397
|
+
status: 501
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
function registerTagTools(server, rt) {
|
|
2401
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
2402
|
+
server.tool(
|
|
2403
|
+
"list-tags",
|
|
2404
|
+
"List your tags (key/value labels on pods, volumes, clusters, and endpoints). v2-only \u2014 returns a 501 notice on the v1 API. Paginated via limit/cursor.",
|
|
2405
|
+
{
|
|
2406
|
+
...listPaginationParams,
|
|
2407
|
+
includeResources: import_zod9.z.boolean().optional().describe("Include the resources attached to each tag in the response")
|
|
2408
|
+
},
|
|
2409
|
+
{ title: "List tags", ...READ_ONLY },
|
|
2410
|
+
async (params) => {
|
|
2411
|
+
const backend = backendFor("tags");
|
|
2412
|
+
if (backend.version === "v1") return jsonReply2(v2OnlyNotice("list-tags"));
|
|
2413
|
+
const query = params.includeResources ? "?include=resources" : "";
|
|
2414
|
+
const raw = await callRestUrl(`${backend.base}${backend.list}${query}`);
|
|
2415
|
+
return capListResult(backend.unwrap(raw), {
|
|
2416
|
+
limit: params.limit,
|
|
2417
|
+
cursor: params.cursor
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
);
|
|
2421
|
+
server.tool(
|
|
2422
|
+
"get-tag",
|
|
2423
|
+
"Get one tag by id, optionally including its attached resources. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
2424
|
+
{
|
|
2425
|
+
tagId: import_zod9.z.string().describe("ID of the tag to retrieve"),
|
|
2426
|
+
includeResources: import_zod9.z.boolean().optional().describe("Include the resources attached to this tag")
|
|
2427
|
+
},
|
|
2428
|
+
{ title: "Get tag", ...READ_ONLY },
|
|
2429
|
+
async (params) => {
|
|
2430
|
+
const backend = backendFor("tags");
|
|
2431
|
+
if (backend.version === "v1") return jsonReply2(v2OnlyNotice("get-tag"));
|
|
2432
|
+
const query = params.includeResources ? "?include=resources" : "";
|
|
2433
|
+
const result = await callRestUrl(
|
|
2434
|
+
`${backend.base}${backend.get(params.tagId)}${query}`
|
|
2435
|
+
);
|
|
2436
|
+
return jsonReply2(result);
|
|
2437
|
+
}
|
|
2438
|
+
);
|
|
2439
|
+
server.tool(
|
|
2440
|
+
"create-tag",
|
|
2441
|
+
"Create a key/value tag, optionally attaching resources at creation. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
2442
|
+
{
|
|
2443
|
+
key: import_zod9.z.string().min(1).describe('Tag key (e.g. "project")'),
|
|
2444
|
+
value: import_zod9.z.string().min(1).describe('Tag value (e.g. "gpt-training")'),
|
|
2445
|
+
resources: import_zod9.z.array(taggedResourceSchema).optional().describe("Resources to attach to the tag at creation time")
|
|
2446
|
+
},
|
|
2447
|
+
{ title: "Create tag", ...WRITE },
|
|
2448
|
+
async (params) => {
|
|
2449
|
+
const backend = backendFor("tags");
|
|
2450
|
+
if (backend.version === "v1")
|
|
2451
|
+
return jsonReply2(v2OnlyNotice("create-tag"));
|
|
2452
|
+
const result = await callRestUrl(
|
|
2453
|
+
`${backend.base}${backend.list}`,
|
|
2454
|
+
"POST",
|
|
2455
|
+
params
|
|
2456
|
+
);
|
|
2457
|
+
return jsonReply2(result);
|
|
2458
|
+
}
|
|
2459
|
+
);
|
|
2460
|
+
server.tool(
|
|
2461
|
+
"update-tag",
|
|
2462
|
+
"Update a tag (rename its key/value, or replace its resource associations). Provide at least one of key, value, or resources. Sending resources:[] removes all associations; omitting resources leaves them unchanged. Replacing resources is NOT atomic \u2014 a failed update can leave a partially-applied set, so for small association changes prefer attach-tag/detach-tag. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
2463
|
+
{
|
|
2464
|
+
tagId: import_zod9.z.string().describe("ID of the tag to update"),
|
|
2465
|
+
key: import_zod9.z.string().min(1).optional().describe("New tag key"),
|
|
2466
|
+
value: import_zod9.z.string().min(1).optional().describe("New tag value"),
|
|
2467
|
+
resources: import_zod9.z.array(taggedResourceSchema).optional().describe(
|
|
2468
|
+
"Replace the tag's resource associations with exactly these. Omit to leave unchanged; pass [] to detach all."
|
|
2469
|
+
)
|
|
2470
|
+
},
|
|
2471
|
+
// No idempotentHint: replacing `resources` is non-atomic upstream, so a
|
|
2472
|
+
// retried-after-partial-failure update is not guaranteed to be a no-op.
|
|
2473
|
+
{ title: "Update tag", ...WRITE },
|
|
2474
|
+
async (params) => {
|
|
2475
|
+
const { tagId, ...updateParams } = params;
|
|
2476
|
+
const backend = backendFor("tags");
|
|
2477
|
+
if (backend.version === "v1")
|
|
2478
|
+
return jsonReply2(v2OnlyNotice("update-tag"));
|
|
2479
|
+
const result = await callRestUrl(
|
|
2480
|
+
`${backend.base}${backend.get(tagId)}`,
|
|
2481
|
+
"PATCH",
|
|
2482
|
+
updateParams
|
|
2483
|
+
);
|
|
2484
|
+
return jsonReply2(result);
|
|
2485
|
+
}
|
|
2486
|
+
);
|
|
2487
|
+
server.tool(
|
|
2488
|
+
"delete-tag",
|
|
2489
|
+
"Permanently delete a tag (removes it from all resources it is attached to). This cannot be undone. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
2490
|
+
{
|
|
2491
|
+
tagId: import_zod9.z.string().describe("ID of the tag to delete")
|
|
2492
|
+
},
|
|
2493
|
+
{ title: "Delete tag", ...DESTRUCTIVE },
|
|
2494
|
+
async (params) => {
|
|
2495
|
+
const backend = backendFor("tags");
|
|
2496
|
+
if (backend.version === "v1")
|
|
2497
|
+
return jsonReply2(v2OnlyNotice("delete-tag"));
|
|
2498
|
+
const result = await callRestUrl(
|
|
2499
|
+
`${backend.base}${backend.get(params.tagId)}`,
|
|
2500
|
+
"DELETE"
|
|
2501
|
+
);
|
|
2502
|
+
return jsonReply2(result);
|
|
2503
|
+
}
|
|
2504
|
+
);
|
|
2505
|
+
server.tool(
|
|
2506
|
+
"attach-tag",
|
|
2507
|
+
"Attach a tag to a resource (pod, network volume, cluster, or serverless endpoint). Idempotent \u2014 attaching an already-associated resource is a no-op. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
2508
|
+
{
|
|
2509
|
+
tagId: import_zod9.z.string().describe("ID of the tag"),
|
|
2510
|
+
resourceType: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to attach the tag to"),
|
|
2511
|
+
resourceId: import_zod9.z.string().describe("ID of the resource to attach the tag to")
|
|
2512
|
+
},
|
|
2513
|
+
{ title: "Attach tag to resource", ...WRITE, idempotentHint: true },
|
|
2514
|
+
async (params) => {
|
|
2515
|
+
const backend = backendFor("tags");
|
|
2516
|
+
if (backend.version === "v1")
|
|
2517
|
+
return jsonReply2(v2OnlyNotice("attach-tag"));
|
|
2518
|
+
const result = await callRestUrl(
|
|
2519
|
+
`${backend.base}/tags/${encodeURIComponent(params.tagId)}/resources/${params.resourceType}/${encodeURIComponent(params.resourceId)}`,
|
|
2520
|
+
"PUT"
|
|
2521
|
+
);
|
|
2522
|
+
return jsonReply2(result);
|
|
2523
|
+
}
|
|
2524
|
+
);
|
|
2525
|
+
server.tool(
|
|
2526
|
+
"detach-tag",
|
|
2527
|
+
"Detach a tag from a resource (removes the association only \u2014 the resource and tag are not deleted). Idempotent \u2014 detaching a resource that is not associated is a no-op. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
2528
|
+
{
|
|
2529
|
+
tagId: import_zod9.z.string().describe("ID of the tag"),
|
|
2530
|
+
resourceType: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to detach the tag from"),
|
|
2531
|
+
resourceId: import_zod9.z.string().describe("ID of the resource to detach the tag from")
|
|
2532
|
+
},
|
|
2533
|
+
{ title: "Detach tag from resource", ...WRITE, idempotentHint: true },
|
|
2534
|
+
async (params) => {
|
|
2535
|
+
const backend = backendFor("tags");
|
|
2536
|
+
if (backend.version === "v1")
|
|
2537
|
+
return jsonReply2(v2OnlyNotice("detach-tag"));
|
|
2538
|
+
const result = await callRestUrl(
|
|
2539
|
+
`${backend.base}/tags/${encodeURIComponent(params.tagId)}/resources/${params.resourceType}/${encodeURIComponent(params.resourceId)}`,
|
|
2540
|
+
"DELETE"
|
|
2541
|
+
);
|
|
2542
|
+
return jsonReply2(result);
|
|
2543
|
+
}
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
// src/tools/billing.ts
|
|
2548
|
+
var import_zod10 = require("zod");
|
|
2549
|
+
var BILLING_SCOPE_PATH = {
|
|
2550
|
+
all: "/billing",
|
|
2551
|
+
pods: "/billing/pods",
|
|
2552
|
+
serverless: "/billing/serverless",
|
|
2553
|
+
endpoints: "/billing/endpoints",
|
|
2554
|
+
networkvolumes: "/billing/networkvolumes",
|
|
2555
|
+
clusters: "/billing/clusters"
|
|
2556
|
+
};
|
|
2557
|
+
function registerBillingTools(server, rt) {
|
|
2558
|
+
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
2559
|
+
server.tool(
|
|
2560
|
+
"get-billing",
|
|
2561
|
+
"Get time-bucketed Runpod spend history, either aggregated across all resources or broken down by resource type (pods, serverless, endpoints, network volumes, clusters). v2-only \u2014 returns a 501 notice on the v1 API. Records are capped via limit/cursor; narrow the window with startTime/endTime or lastN.",
|
|
2562
|
+
{
|
|
2563
|
+
...listPaginationParams,
|
|
2564
|
+
scope: import_zod10.z.enum([
|
|
2565
|
+
"all",
|
|
2566
|
+
"pods",
|
|
2567
|
+
"serverless",
|
|
2568
|
+
"endpoints",
|
|
2569
|
+
"networkvolumes",
|
|
2570
|
+
"clusters"
|
|
2571
|
+
]).optional().describe(
|
|
2572
|
+
'Which billing breakdown to fetch. "all" (default) is the aggregate across every resource.'
|
|
2573
|
+
),
|
|
2574
|
+
startTime: import_zod10.z.string().optional().describe(
|
|
2575
|
+
"Start of the period (RFC 3339, e.g. 2026-05-01T00:00:00Z). Defaults to 30 days ago. Mutually exclusive with lastN."
|
|
2576
|
+
),
|
|
2577
|
+
endTime: import_zod10.z.string().optional().describe(
|
|
2578
|
+
"End of the period (RFC 3339), exclusive. Defaults to now. Mutually exclusive with lastN."
|
|
2579
|
+
),
|
|
2580
|
+
bucketSize: import_zod10.z.enum(["hour", "day", "week", "month", "year"]).optional().describe("Length of each time bucket. Defaults to day."),
|
|
2581
|
+
lastN: import_zod10.z.number().int().positive().optional().describe(
|
|
2582
|
+
"Return the last N buckets of bucketSize, ending with the current one. Mutually exclusive with startTime/endTime."
|
|
2583
|
+
)
|
|
2584
|
+
},
|
|
2585
|
+
{ title: "Get billing history", ...READ_ONLY },
|
|
2586
|
+
async (params) => {
|
|
2587
|
+
const backend = backendFor("billing");
|
|
2588
|
+
if (backend.version === "v1") {
|
|
2589
|
+
return jsonReply2({
|
|
2590
|
+
error: "get-billing is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
|
|
2591
|
+
status: 501
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
const subPath = BILLING_SCOPE_PATH[params.scope ?? "all"];
|
|
2595
|
+
const query = new URLSearchParams();
|
|
2596
|
+
if (params.startTime) query.set("startTime", params.startTime);
|
|
2597
|
+
if (params.endTime) query.set("endTime", params.endTime);
|
|
2598
|
+
if (params.bucketSize) query.set("bucketSize", params.bucketSize);
|
|
2599
|
+
if (params.lastN !== void 0 && !params.startTime && !params.endTime)
|
|
2600
|
+
query.set("lastN", String(params.lastN));
|
|
2601
|
+
const qs = query.toString() ? `?${query.toString()}` : "";
|
|
2602
|
+
const raw = await callRestUrl(`${backend.base}${subPath}${qs}`);
|
|
2603
|
+
return capListResult(
|
|
2604
|
+
backend.unwrap(raw),
|
|
2605
|
+
{ limit: params.limit, cursor: params.cursor },
|
|
2606
|
+
{ metadata: raw?.metadata }
|
|
2607
|
+
);
|
|
2608
|
+
}
|
|
2609
|
+
);
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
// src/tools.ts
|
|
2613
|
+
function registerTools(server, ctx, deps = {}) {
|
|
2614
|
+
const rt = createToolRuntime(server, ctx, deps);
|
|
2615
|
+
registerCatalogTools(server, rt);
|
|
2616
|
+
registerPodTools(server, rt);
|
|
2617
|
+
registerEndpointTools(server, rt);
|
|
2618
|
+
registerJobTools(server, rt);
|
|
2619
|
+
registerTemplateTools(server, rt);
|
|
2620
|
+
registerNetworkVolumeTools(server, rt);
|
|
2621
|
+
registerRegistryTools(server, rt);
|
|
2622
|
+
registerTagTools(server, rt);
|
|
2623
|
+
registerBillingTools(server, rt);
|
|
2624
|
+
}
|
|
2625
|
+
|
|
1148
2626
|
// src/stdio.ts
|
|
1149
|
-
var
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
2627
|
+
var WIZARD_SUBCOMMANDS = ["add", "remove"];
|
|
2628
|
+
var subcommand = process.argv[2];
|
|
2629
|
+
var isWizardMode = WIZARD_SUBCOMMANDS.includes(subcommand);
|
|
2630
|
+
async function main(apiKey) {
|
|
2631
|
+
let v2Available;
|
|
2632
|
+
if (wantsAutoProbe(process.env)) {
|
|
2633
|
+
const probe = createV2Prober({
|
|
2634
|
+
// node-fetch's signature is wider than the `{ status }` the prober needs.
|
|
2635
|
+
fetch: import_node_fetch2.default,
|
|
2636
|
+
baseUrl: restV2Base(process.env),
|
|
2637
|
+
apiKey,
|
|
2638
|
+
// Surface a transient/timeout fallback to v1 — otherwise `auto` silently
|
|
2639
|
+
// pins the session to v1 with no signal (stderr is the MCP log channel).
|
|
2640
|
+
warn: (message) => console.error(`[runpod-mcp] ${message}`)
|
|
2641
|
+
});
|
|
2642
|
+
v2Available = await probe();
|
|
2643
|
+
}
|
|
2644
|
+
const server = createServer();
|
|
2645
|
+
registerTools(server, { apiKey, transport: "stdio" }, { v2Available });
|
|
2646
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
2647
|
+
await server.connect(transport);
|
|
2648
|
+
}
|
|
2649
|
+
if (isWizardMode) {
|
|
2650
|
+
Promise.resolve().then(() => (init_wizard(), wizard_exports)).then(({ runWizard: runWizard2 }) => runWizard2(subcommand)).catch((error) => {
|
|
2651
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
2652
|
+
process.exit(1);
|
|
2653
|
+
});
|
|
2654
|
+
} else {
|
|
2655
|
+
const API_KEY = process.env.RUNPOD_API_KEY;
|
|
2656
|
+
if (!API_KEY) {
|
|
2657
|
+
console.error("RUNPOD_API_KEY environment variable is required");
|
|
2658
|
+
process.exit(1);
|
|
2659
|
+
}
|
|
2660
|
+
main(API_KEY).catch((error) => {
|
|
2661
|
+
console.error("Failed to start Runpod MCP server:", error);
|
|
2662
|
+
process.exit(1);
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
1158
2665
|
//# sourceMappingURL=stdio.cjs.map
|