@runpod/mcp-server 1.0.0 → 1.2.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 +15 -0
- package/README.md +167 -61
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +512 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +502 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -18
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
"use strict";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
@@ -5,6 +6,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
8
13
|
var __copyProps = (to, from, except, desc) => {
|
|
9
14
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
15
|
for (let key of __getOwnPropNames(from))
|
|
@@ -21,31 +26,89 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
26
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
27
|
mod
|
|
23
28
|
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
24
30
|
|
|
25
31
|
// src/index.ts
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
setTransport: () => setTransport
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
37
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
27
38
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
28
39
|
var import_zod = require("zod");
|
|
29
40
|
var import_node_fetch = __toESM(require("node-fetch"), 1);
|
|
41
|
+
var import_node_crypto = require("crypto");
|
|
30
42
|
var API_BASE_URL = "https://rest.runpod.io/v1";
|
|
31
43
|
var API_KEY = process.env.RUNPOD_API_KEY;
|
|
32
44
|
if (!API_KEY) {
|
|
33
45
|
console.error("RUNPOD_API_KEY environment variable is required");
|
|
34
46
|
process.exit(1);
|
|
35
47
|
}
|
|
48
|
+
var MCP_SERVER_NAME = "RunPod API Server";
|
|
49
|
+
var MCP_SERVER_VERSION = true ? "1.2.0" : "dev";
|
|
50
|
+
var MCP_TRANSPORT = "stdio";
|
|
51
|
+
function setTransport(t) {
|
|
52
|
+
MCP_TRANSPORT = t;
|
|
53
|
+
}
|
|
36
54
|
var server = new import_mcp.McpServer({
|
|
37
|
-
name:
|
|
38
|
-
version:
|
|
55
|
+
name: MCP_SERVER_NAME,
|
|
56
|
+
version: MCP_SERVER_VERSION,
|
|
39
57
|
capabilities: {
|
|
40
58
|
resources: {},
|
|
41
59
|
tools: {}
|
|
42
60
|
}
|
|
43
61
|
});
|
|
62
|
+
var SESSION_ID = (0, import_node_crypto.randomUUID)();
|
|
63
|
+
function resolveClientInfo() {
|
|
64
|
+
const info = server.server.getClientVersion();
|
|
65
|
+
if (info?.name) {
|
|
66
|
+
return { name: info.name, version: info.version || "unknown" };
|
|
67
|
+
}
|
|
68
|
+
return { name: "unknown", version: "unknown" };
|
|
69
|
+
}
|
|
70
|
+
function sanitizeUaToken(value) {
|
|
71
|
+
return value.replace(/[()<>@,;:\\"/\[\]?={}\s]/g, "_").slice(0, 64);
|
|
72
|
+
}
|
|
73
|
+
function buildUserAgent() {
|
|
74
|
+
const { name, version } = resolveClientInfo();
|
|
75
|
+
const safeName = sanitizeUaToken(name);
|
|
76
|
+
const safeVersion = sanitizeUaToken(version);
|
|
77
|
+
return `runpod-mcp-server/${MCP_SERVER_VERSION} (caller=mcp; client=${safeName}; client_version=${safeVersion}; transport=${MCP_TRANSPORT})`;
|
|
78
|
+
}
|
|
79
|
+
function trackingHeaders() {
|
|
80
|
+
return {
|
|
81
|
+
"User-Agent": buildUserAgent(),
|
|
82
|
+
"X-Runpod-Session-Id": SESSION_ID
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
var GRAPHQL_URL = "https://api.runpod.io/graphql";
|
|
86
|
+
async function graphqlRequest(query) {
|
|
87
|
+
const response = await (0, import_node_fetch.default)(GRAPHQL_URL, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: {
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
...trackingHeaders()
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({ query })
|
|
94
|
+
});
|
|
95
|
+
const result = await response.json();
|
|
96
|
+
if (result.errors && result.errors.length > 0) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`GraphQL Error: ${result.errors.map((e) => e.message).join(", ")}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (!result.data) {
|
|
102
|
+
throw new Error("No data returned from GraphQL query");
|
|
103
|
+
}
|
|
104
|
+
return result.data;
|
|
105
|
+
}
|
|
44
106
|
async function runpodRequest(endpoint, method = "GET", body) {
|
|
45
107
|
const url = `${API_BASE_URL}${endpoint}`;
|
|
46
108
|
const headers = {
|
|
47
109
|
Authorization: `Bearer ${API_KEY}`,
|
|
48
|
-
"Content-Type": "application/json"
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
...trackingHeaders()
|
|
49
112
|
};
|
|
50
113
|
const options = {
|
|
51
114
|
method,
|
|
@@ -70,6 +133,156 @@ async function runpodRequest(endpoint, method = "GET", body) {
|
|
|
70
133
|
throw error;
|
|
71
134
|
}
|
|
72
135
|
}
|
|
136
|
+
server.tool(
|
|
137
|
+
"list-gpu-types",
|
|
138
|
+
{
|
|
139
|
+
minMemoryGb: import_zod.z.number().optional().describe("Filter to GPUs with at least this much VRAM in GB"),
|
|
140
|
+
secureCloudOnly: import_zod.z.boolean().optional().describe("Filter to only GPUs available in secure cloud"),
|
|
141
|
+
communityCloudOnly: import_zod.z.boolean().optional().describe("Filter to only GPUs available in community cloud"),
|
|
142
|
+
searchTerm: import_zod.z.string().optional().describe(
|
|
143
|
+
"Search term to filter GPUs by name (e.g., 'A100', 'RTX 4090')"
|
|
144
|
+
),
|
|
145
|
+
includeUnavailable: import_zod.z.boolean().optional().describe(
|
|
146
|
+
"Include GPUs that are currently out of stock. Default is false"
|
|
147
|
+
)
|
|
148
|
+
},
|
|
149
|
+
async (params) => {
|
|
150
|
+
const data = await graphqlRequest(`
|
|
151
|
+
query {
|
|
152
|
+
gpuTypes {
|
|
153
|
+
id
|
|
154
|
+
displayName
|
|
155
|
+
memoryInGb
|
|
156
|
+
secureCloud
|
|
157
|
+
communityCloud
|
|
158
|
+
lowestPrice(input: { gpuCount: 1 }) {
|
|
159
|
+
stockStatus
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
`);
|
|
164
|
+
const stockPriority = {
|
|
165
|
+
High: 3,
|
|
166
|
+
Medium: 2,
|
|
167
|
+
Low: 1
|
|
168
|
+
};
|
|
169
|
+
const isAvailable = (gpu) => {
|
|
170
|
+
const status = gpu.lowestPrice?.stockStatus;
|
|
171
|
+
return !!status && status !== "Out";
|
|
172
|
+
};
|
|
173
|
+
let gpuTypes = data.gpuTypes.filter((gpu) => gpu.id !== "unknown");
|
|
174
|
+
if (!params.includeUnavailable) {
|
|
175
|
+
gpuTypes = gpuTypes.filter(isAvailable);
|
|
176
|
+
}
|
|
177
|
+
if (params.minMemoryGb) {
|
|
178
|
+
gpuTypes = gpuTypes.filter(
|
|
179
|
+
(gpu) => gpu.memoryInGb >= params.minMemoryGb
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
if (params.secureCloudOnly) {
|
|
183
|
+
gpuTypes = gpuTypes.filter((gpu) => gpu.secureCloud);
|
|
184
|
+
}
|
|
185
|
+
if (params.communityCloudOnly) {
|
|
186
|
+
gpuTypes = gpuTypes.filter((gpu) => gpu.communityCloud);
|
|
187
|
+
}
|
|
188
|
+
if (params.searchTerm) {
|
|
189
|
+
const term = params.searchTerm.toLowerCase();
|
|
190
|
+
gpuTypes = gpuTypes.filter(
|
|
191
|
+
(gpu) => gpu.id.toLowerCase().includes(term) || gpu.displayName.toLowerCase().includes(term)
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
gpuTypes.sort((a, b) => {
|
|
195
|
+
const aP = stockPriority[a.lowestPrice?.stockStatus || ""] || 0;
|
|
196
|
+
const bP = stockPriority[b.lowestPrice?.stockStatus || ""] || 0;
|
|
197
|
+
if (bP !== aP) return bP - aP;
|
|
198
|
+
return b.memoryInGb - a.memoryInGb;
|
|
199
|
+
});
|
|
200
|
+
const result = gpuTypes.map((gpu) => ({
|
|
201
|
+
id: gpu.id,
|
|
202
|
+
displayName: gpu.displayName,
|
|
203
|
+
memoryGb: gpu.memoryInGb,
|
|
204
|
+
secureCloud: gpu.secureCloud,
|
|
205
|
+
communityCloud: gpu.communityCloud,
|
|
206
|
+
stockStatus: gpu.lowestPrice?.stockStatus || "unavailable",
|
|
207
|
+
available: isAvailable(gpu)
|
|
208
|
+
}));
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: JSON.stringify(result, null, 2)
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
server.tool(
|
|
220
|
+
"list-data-centers",
|
|
221
|
+
{
|
|
222
|
+
region: import_zod.z.string().optional().describe(
|
|
223
|
+
"Filter by region/location (e.g., 'United States', 'Europe', 'Canada')"
|
|
224
|
+
)
|
|
225
|
+
},
|
|
226
|
+
async (params) => {
|
|
227
|
+
const data = await graphqlRequest(`
|
|
228
|
+
query {
|
|
229
|
+
dataCenters {
|
|
230
|
+
id
|
|
231
|
+
name
|
|
232
|
+
location
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
`);
|
|
236
|
+
let dataCenters = data.dataCenters;
|
|
237
|
+
if (params.region) {
|
|
238
|
+
const term = params.region.toLowerCase();
|
|
239
|
+
dataCenters = dataCenters.filter(
|
|
240
|
+
(dc) => dc.location.toLowerCase().includes(term)
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
content: [
|
|
245
|
+
{
|
|
246
|
+
type: "text",
|
|
247
|
+
text: JSON.stringify(dataCenters, null, 2)
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
|
+
var SERVERLESS_API_BASE_URL = "https://api.runpod.ai/v2";
|
|
254
|
+
async function serverlessRequest(endpointId, path, method = "GET", body) {
|
|
255
|
+
const url = `${SERVERLESS_API_BASE_URL}/${endpointId}${path}`;
|
|
256
|
+
const headers = {
|
|
257
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
258
|
+
"Content-Type": "application/json",
|
|
259
|
+
...trackingHeaders()
|
|
260
|
+
};
|
|
261
|
+
const options = {
|
|
262
|
+
method,
|
|
263
|
+
headers
|
|
264
|
+
};
|
|
265
|
+
if (body && (method === "POST" || method === "PATCH")) {
|
|
266
|
+
options.body = JSON.stringify(body);
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
const response = await (0, import_node_fetch.default)(url, options);
|
|
270
|
+
if (!response.ok) {
|
|
271
|
+
const errorText = await response.text();
|
|
272
|
+
throw new Error(
|
|
273
|
+
`RunPod Serverless API Error: ${response.status} - ${errorText}`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
const contentType = response.headers.get("content-type");
|
|
277
|
+
if (contentType && contentType.includes("application/json")) {
|
|
278
|
+
return await response.json();
|
|
279
|
+
}
|
|
280
|
+
return { success: true, status: response.status };
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error("Error calling RunPod Serverless API:", error);
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
73
286
|
server.tool(
|
|
74
287
|
"list-pods",
|
|
75
288
|
{
|
|
@@ -140,6 +353,7 @@ server.tool(
|
|
|
140
353
|
);
|
|
141
354
|
server.tool(
|
|
142
355
|
"create-pod",
|
|
356
|
+
'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.',
|
|
143
357
|
{
|
|
144
358
|
name: import_zod.z.string().optional().describe("Name for the pod"),
|
|
145
359
|
imageName: import_zod.z.string().describe("Docker image to use"),
|
|
@@ -364,17 +578,298 @@ server.tool(
|
|
|
364
578
|
};
|
|
365
579
|
}
|
|
366
580
|
);
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
581
|
+
var endpointIdSchema = import_zod.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid endpoint ID format");
|
|
582
|
+
var jobIdSchema = import_zod.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid job ID format");
|
|
583
|
+
var inputSchema = import_zod.z.record(import_zod.z.unknown()).describe(
|
|
584
|
+
"Input payload for the worker handler. The expected fields depend on the deployed model or worker."
|
|
585
|
+
);
|
|
586
|
+
var webhookSchema = import_zod.z.string().url().optional().describe(
|
|
587
|
+
"Webhook URL to receive job completion notifications instead of polling"
|
|
588
|
+
);
|
|
589
|
+
var policySchema = import_zod.z.object({
|
|
590
|
+
executionTimeout: import_zod.z.number().optional().describe("Maximum execution time in milliseconds"),
|
|
591
|
+
lowPriority: import_zod.z.boolean().optional().describe("Submit as a low-priority job"),
|
|
592
|
+
ttl: import_zod.z.number().optional().describe("Time-to-live for the job result in milliseconds")
|
|
593
|
+
}).optional().describe("Execution policy options");
|
|
594
|
+
var s3ConfigSchema = import_zod.z.object({
|
|
595
|
+
accessId: import_zod.z.string().describe("S3 access key ID"),
|
|
596
|
+
accessSecret: import_zod.z.string().describe("S3 secret access key"),
|
|
597
|
+
bucketName: import_zod.z.string().describe("S3 bucket name"),
|
|
598
|
+
endpointUrl: import_zod.z.string().describe("S3 endpoint URL")
|
|
599
|
+
}).optional().describe("S3-compatible storage config for large outputs");
|
|
600
|
+
server.tool(
|
|
601
|
+
"run-endpoint",
|
|
602
|
+
"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.",
|
|
603
|
+
{
|
|
604
|
+
endpointId: endpointIdSchema.describe(
|
|
605
|
+
"ID of the Serverless endpoint to run"
|
|
606
|
+
),
|
|
607
|
+
input: inputSchema,
|
|
608
|
+
webhook: webhookSchema,
|
|
609
|
+
policy: policySchema,
|
|
610
|
+
s3Config: s3ConfigSchema
|
|
611
|
+
},
|
|
612
|
+
async (params) => {
|
|
613
|
+
const { endpointId, ...body } = params;
|
|
614
|
+
const result = await serverlessRequest(
|
|
615
|
+
endpointId,
|
|
616
|
+
"/run",
|
|
617
|
+
"POST",
|
|
618
|
+
body
|
|
619
|
+
);
|
|
620
|
+
return {
|
|
621
|
+
content: [
|
|
622
|
+
{
|
|
623
|
+
type: "text",
|
|
624
|
+
text: JSON.stringify(result, null, 2)
|
|
625
|
+
}
|
|
626
|
+
]
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
);
|
|
630
|
+
server.tool(
|
|
631
|
+
"runsync-endpoint",
|
|
632
|
+
"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).",
|
|
633
|
+
{
|
|
634
|
+
endpointId: endpointIdSchema.describe(
|
|
635
|
+
"ID of the Serverless endpoint to run synchronously"
|
|
636
|
+
),
|
|
637
|
+
input: inputSchema,
|
|
638
|
+
wait: import_zod.z.number().min(1e3).max(3e5).optional().describe(
|
|
639
|
+
"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)."
|
|
640
|
+
),
|
|
641
|
+
webhook: webhookSchema,
|
|
642
|
+
policy: policySchema,
|
|
643
|
+
s3Config: s3ConfigSchema
|
|
644
|
+
},
|
|
645
|
+
async (params) => {
|
|
646
|
+
const { endpointId, wait, ...body } = params;
|
|
647
|
+
const path = wait ? `/runsync?wait=${wait}` : "/runsync";
|
|
648
|
+
const result = await serverlessRequest(
|
|
649
|
+
endpointId,
|
|
650
|
+
path,
|
|
651
|
+
"POST",
|
|
652
|
+
body
|
|
653
|
+
);
|
|
654
|
+
return {
|
|
655
|
+
content: [
|
|
656
|
+
{
|
|
657
|
+
type: "text",
|
|
658
|
+
text: JSON.stringify(result, null, 2)
|
|
659
|
+
}
|
|
660
|
+
]
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
);
|
|
664
|
+
server.tool(
|
|
665
|
+
"get-job-status",
|
|
666
|
+
"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.",
|
|
667
|
+
{
|
|
668
|
+
endpointId: endpointIdSchema.describe(
|
|
669
|
+
"ID of the Serverless endpoint the job belongs to"
|
|
670
|
+
),
|
|
671
|
+
jobId: jobIdSchema.describe("ID of the job to check")
|
|
672
|
+
},
|
|
673
|
+
async (params) => {
|
|
674
|
+
const result = await serverlessRequest(
|
|
675
|
+
params.endpointId,
|
|
676
|
+
`/status/${params.jobId}`
|
|
677
|
+
);
|
|
678
|
+
return {
|
|
679
|
+
content: [
|
|
680
|
+
{
|
|
681
|
+
type: "text",
|
|
682
|
+
text: JSON.stringify(result, null, 2)
|
|
683
|
+
}
|
|
684
|
+
]
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
);
|
|
688
|
+
server.tool(
|
|
689
|
+
"stream-job",
|
|
690
|
+
"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.",
|
|
691
|
+
{
|
|
692
|
+
endpointId: endpointIdSchema.describe(
|
|
693
|
+
"ID of the Serverless endpoint the job belongs to"
|
|
694
|
+
),
|
|
695
|
+
jobId: jobIdSchema.describe("ID of the job to stream results from")
|
|
696
|
+
},
|
|
697
|
+
async (params) => {
|
|
698
|
+
const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
699
|
+
"COMPLETED",
|
|
700
|
+
"FAILED",
|
|
701
|
+
"CANCELLED",
|
|
702
|
+
"TIMED_OUT"
|
|
703
|
+
]);
|
|
704
|
+
const MAX_POLL_TIME_MS = 5 * 60 * 1e3;
|
|
705
|
+
const POLL_INTERVAL_MS = 1e3;
|
|
706
|
+
const MAX_CONSECUTIVE_ERRORS = 5;
|
|
707
|
+
const allChunks = [];
|
|
708
|
+
let finalResult = {};
|
|
709
|
+
let consecutiveErrors = 0;
|
|
710
|
+
const startTime = Date.now();
|
|
711
|
+
while (true) {
|
|
712
|
+
try {
|
|
713
|
+
const result = await serverlessRequest(
|
|
714
|
+
params.endpointId,
|
|
715
|
+
`/stream/${params.jobId}`
|
|
716
|
+
);
|
|
717
|
+
consecutiveErrors = 0;
|
|
718
|
+
if (Array.isArray(result.stream)) {
|
|
719
|
+
allChunks.push(...result.stream);
|
|
720
|
+
}
|
|
721
|
+
finalResult = result;
|
|
722
|
+
if (TERMINAL_STATUSES.has(result.status)) {
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
} catch (error) {
|
|
726
|
+
consecutiveErrors++;
|
|
727
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
728
|
+
finalResult.error = `Polling aborted after ${MAX_CONSECUTIVE_ERRORS} consecutive errors: ${error instanceof Error ? error.message : String(error)}`;
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
374
731
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
732
|
+
if (Date.now() - startTime > MAX_POLL_TIME_MS) {
|
|
733
|
+
finalResult.pollingTimedOut = true;
|
|
734
|
+
finalResult.note = "Polling timed out after 5 minutes. Use get-job-status to check the job later.";
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
content: [
|
|
741
|
+
{
|
|
742
|
+
type: "text",
|
|
743
|
+
text: JSON.stringify({ ...finalResult, stream: allChunks }, null, 2)
|
|
744
|
+
}
|
|
745
|
+
]
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
);
|
|
749
|
+
server.tool(
|
|
750
|
+
"cancel-job",
|
|
751
|
+
"Cancel a Serverless job that is queued or in progress.",
|
|
752
|
+
{
|
|
753
|
+
endpointId: endpointIdSchema.describe(
|
|
754
|
+
"ID of the Serverless endpoint the job belongs to"
|
|
755
|
+
),
|
|
756
|
+
jobId: jobIdSchema.describe("ID of the job to cancel")
|
|
757
|
+
},
|
|
758
|
+
async (params) => {
|
|
759
|
+
const result = await serverlessRequest(
|
|
760
|
+
params.endpointId,
|
|
761
|
+
`/cancel/${params.jobId}`,
|
|
762
|
+
"POST"
|
|
763
|
+
);
|
|
764
|
+
return {
|
|
765
|
+
content: [
|
|
766
|
+
{
|
|
767
|
+
type: "text",
|
|
768
|
+
text: JSON.stringify(result, null, 2)
|
|
769
|
+
}
|
|
770
|
+
]
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
);
|
|
774
|
+
server.tool(
|
|
775
|
+
"retry-job",
|
|
776
|
+
"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.",
|
|
777
|
+
{
|
|
778
|
+
endpointId: endpointIdSchema.describe(
|
|
779
|
+
"ID of the Serverless endpoint the job belongs to"
|
|
780
|
+
),
|
|
781
|
+
jobId: jobIdSchema.describe("ID of the job to retry")
|
|
782
|
+
},
|
|
783
|
+
async (params) => {
|
|
784
|
+
const result = await serverlessRequest(
|
|
785
|
+
params.endpointId,
|
|
786
|
+
`/retry/${params.jobId}`,
|
|
787
|
+
"POST"
|
|
788
|
+
);
|
|
789
|
+
return {
|
|
790
|
+
content: [
|
|
791
|
+
{
|
|
792
|
+
type: "text",
|
|
793
|
+
text: JSON.stringify(result, null, 2)
|
|
794
|
+
}
|
|
795
|
+
]
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
);
|
|
799
|
+
server.tool(
|
|
800
|
+
"endpoint-health",
|
|
801
|
+
"Get the health and operational status of a Serverless endpoint, including worker counts and job statistics.",
|
|
802
|
+
{
|
|
803
|
+
endpointId: endpointIdSchema.describe(
|
|
804
|
+
"ID of the Serverless endpoint to check health for"
|
|
805
|
+
)
|
|
806
|
+
},
|
|
807
|
+
async (params) => {
|
|
808
|
+
const result = await serverlessRequest(params.endpointId, "/health");
|
|
809
|
+
return {
|
|
810
|
+
content: [
|
|
811
|
+
{
|
|
812
|
+
type: "text",
|
|
813
|
+
text: JSON.stringify(result, null, 2)
|
|
814
|
+
}
|
|
815
|
+
]
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
);
|
|
819
|
+
server.tool(
|
|
820
|
+
"purge-endpoint-queue",
|
|
821
|
+
"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.",
|
|
822
|
+
{
|
|
823
|
+
endpointId: endpointIdSchema.describe(
|
|
824
|
+
"ID of the Serverless endpoint to purge the queue for"
|
|
825
|
+
)
|
|
826
|
+
},
|
|
827
|
+
async (params) => {
|
|
828
|
+
const result = await serverlessRequest(
|
|
829
|
+
params.endpointId,
|
|
830
|
+
"/purge-queue",
|
|
831
|
+
"POST"
|
|
832
|
+
);
|
|
833
|
+
return {
|
|
834
|
+
content: [
|
|
835
|
+
{
|
|
836
|
+
type: "text",
|
|
837
|
+
text: JSON.stringify(result, null, 2)
|
|
838
|
+
}
|
|
839
|
+
]
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
);
|
|
843
|
+
server.tool(
|
|
844
|
+
"list-templates",
|
|
845
|
+
`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.`,
|
|
846
|
+
{
|
|
847
|
+
includeRunpodTemplates: import_zod.z.boolean().optional().describe("Include official RunPod templates in the response"),
|
|
848
|
+
includePublicTemplates: import_zod.z.boolean().optional().describe("Include community-made public templates in the response"),
|
|
849
|
+
includeEndpointBoundTemplates: import_zod.z.boolean().optional().describe(
|
|
850
|
+
"Include templates bound to Serverless endpoints in the response"
|
|
851
|
+
)
|
|
852
|
+
},
|
|
853
|
+
async (params) => {
|
|
854
|
+
const queryParams = new URLSearchParams();
|
|
855
|
+
if (params.includeRunpodTemplates)
|
|
856
|
+
queryParams.set("includeRunpodTemplates", "true");
|
|
857
|
+
if (params.includePublicTemplates)
|
|
858
|
+
queryParams.set("includePublicTemplates", "true");
|
|
859
|
+
if (params.includeEndpointBoundTemplates)
|
|
860
|
+
queryParams.set("includeEndpointBoundTemplates", "true");
|
|
861
|
+
const query = queryParams.toString();
|
|
862
|
+
const result = await runpodRequest(`/templates${query ? `?${query}` : ""}`);
|
|
863
|
+
return {
|
|
864
|
+
content: [
|
|
865
|
+
{
|
|
866
|
+
type: "text",
|
|
867
|
+
text: JSON.stringify(result, null, 2)
|
|
868
|
+
}
|
|
869
|
+
]
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
);
|
|
378
873
|
server.tool(
|
|
379
874
|
"get-template",
|
|
380
875
|
{
|
|
@@ -634,4 +1129,8 @@ server.tool(
|
|
|
634
1129
|
);
|
|
635
1130
|
var transport = new import_stdio.StdioServerTransport();
|
|
636
1131
|
server.connect(transport);
|
|
1132
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1133
|
+
0 && (module.exports = {
|
|
1134
|
+
setTransport
|
|
1135
|
+
});
|
|
637
1136
|
//# sourceMappingURL=index.js.map
|