@runpod/mcp-server 4.3.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +16 -0
- package/dist/{chunk-DDX42UTT.mjs → chunk-4GF3CMSH.mjs} +6 -2
- package/dist/{chunk-DDX42UTT.mjs.map → chunk-4GF3CMSH.mjs.map} +1 -1
- package/dist/http.cjs +198 -5
- package/dist/http.cjs.map +1 -1
- package/dist/http.d.cts +6 -1
- package/dist/http.d.ts +6 -1
- package/dist/http.mjs +197 -5
- package/dist/http.mjs.map +1 -1
- package/dist/stdio.cjs +131 -5
- package/dist/stdio.cjs.map +1 -1
- package/dist/stdio.mjs +127 -6
- package/dist/stdio.mjs.map +1 -1
- package/dist/{wizard-L25AYBNQ.mjs → wizard-KKTBTVXE.mjs} +4 -3
- package/dist/wizard-KKTBTVXE.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/wizard-L25AYBNQ.mjs.map +0 -1
package/dist/stdio.mjs
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
authedGraphqlBase,
|
|
4
|
+
hostedMcpBase,
|
|
4
5
|
publicGraphqlBase,
|
|
5
6
|
sdkBase,
|
|
6
7
|
serverlessBase
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-4GF3CMSH.mjs";
|
|
8
9
|
|
|
9
10
|
// src/stdio.ts
|
|
10
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
12
|
|
|
12
13
|
// src/server.ts
|
|
13
14
|
var SERVER_NAME = "Runpod API Server";
|
|
14
|
-
var SERVER_VERSION = true ? "4.
|
|
15
|
+
var SERVER_VERSION = true ? "4.4.0" : "dev";
|
|
15
16
|
|
|
16
17
|
// src/specgen/server.ts
|
|
17
18
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -5702,6 +5703,8 @@ var listHubRepos = {
|
|
|
5702
5703
|
};
|
|
5703
5704
|
var deployHubRepo = {
|
|
5704
5705
|
name: "deploy-hub-repo",
|
|
5706
|
+
// Two sequential upstream calls: together they can outlast a client's request timeout.
|
|
5707
|
+
holdsResponse: true,
|
|
5705
5708
|
annotations: write,
|
|
5706
5709
|
description: "Deploy a Runpod Hub repo's listed release as a new Serverless endpoint (the same as clicking Deploy on the Hub). Identify the repo by `repo` (\"owner/name\" from list-hub-repos) or by `hubReleaseId`. The release supplies the prebuilt image, container disk, CUDA constraints, and env-var defaults; pass `env` to override or fill in values (required keys without a default must be provided \u2014 check list-hub-repos with includeConfig:true for the schema). GPU selection comes from the release config when it specifies one; otherwise pass gpuIds (GPU pool names, e.g. 'ADA_24' or 'ADA_80_PRO,AMPERE_80'). Uses the authenticated GraphQL API (no REST home for Hub deploys yet).",
|
|
5707
5710
|
inputSchema: {
|
|
@@ -6190,6 +6193,7 @@ var runEndpoint = {
|
|
|
6190
6193
|
};
|
|
6191
6194
|
var runsyncEndpoint = {
|
|
6192
6195
|
name: "runsync-endpoint",
|
|
6196
|
+
holdsResponse: true,
|
|
6193
6197
|
annotations: write,
|
|
6194
6198
|
description: "Submit a synchronous job to a Serverless endpoint and wait for the result. Best for fast tasks: if the job outlives the wait, the response returns a job ID and a non-terminal status (IN_QUEUE or IN_PROGRESS) to poll with get-job-status. Max payload 20 MB; results expire after 1 minute. The wait parameter extends the server-side wait" + (HOSTED ? ` \u2014 this server runs behind a 60-second gateway deadline, so waits clamp to ${HTTP_LONG_POLL_BUDGET_MS} ms, which is also the effective default. For longer jobs use run-endpoint + get-job-status.` : " to up to 5 minutes (300000 ms); it defaults to 90000 (90 seconds)."),
|
|
6195
6199
|
inputSchema: {
|
|
@@ -6234,6 +6238,7 @@ var runsyncEndpoint = {
|
|
|
6234
6238
|
};
|
|
6235
6239
|
var getJobStatus = {
|
|
6236
6240
|
name: "get-job-status",
|
|
6241
|
+
holdsResponse: true,
|
|
6237
6242
|
annotations: readOnly,
|
|
6238
6243
|
description: "Check the status of a Serverless job. Returns the current status and output when complete. Job statuses: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELLED, TIMED_OUT. Pass `wait` (milliseconds, up to " + STATUS_WAIT_MAX_MS + ") to BLOCK server-side, polling until the job reaches a terminal status or the budget expires \u2014 use it to ride out a cold start (a first job on a fresh worker pulls the image and loads the model, commonly 1\u20135+ minutes) in a single call instead of returning on the first IN_QUEUE and tight-looping the tool. Without `wait` it returns the current status immediately (a single check). If the budget expires before a terminal status, it returns the latest non-terminal status with pollingTimedOut:true \u2014 call again (with `wait`) to keep blocking. IMPORTANT: a job stuck IN_QUEUE does not necessarily mean a capacity shortage \u2014 a worker may have spun up and crash-looped. When the job is IN_QUEUE this tool attaches a workerHealth summary and a hint. To dig deeper call list-endpoint-workers, then read stream-worker-logs regardless of the status a worker reports: a container failing to start loops while its worker still shows RUNNING or THROTTLED and unhealthy stays 0, so UNHEALTHY is one symptom of a crash loop and not a test for it. The logs are the authority.",
|
|
6239
6244
|
inputSchema: {
|
|
@@ -6282,6 +6287,7 @@ var getJobStatus = {
|
|
|
6282
6287
|
};
|
|
6283
6288
|
var streamJob = {
|
|
6284
6289
|
name: "stream-job",
|
|
6290
|
+
holdsResponse: true,
|
|
6285
6291
|
annotations: readOnly,
|
|
6286
6292
|
description: `Retrieve streaming output from a Serverless job. The worker must support streaming output. Polls /stream/{jobId} and collects chunks until the status is COMPLETED, FAILED, CANCELLED, or TIMED_OUT, for up to ${formatBudget(STREAM_BUDGET_MS)}. If the budget expires first, returns the chunks collected so far with pollingTimedOut: true \u2014 call stream-job again to resume where it left off, or get-job-status to check without streaming.`,
|
|
6287
6293
|
inputSchema: {
|
|
@@ -7902,6 +7908,7 @@ function logParams(args) {
|
|
|
7902
7908
|
}
|
|
7903
7909
|
var streamPodLogs = {
|
|
7904
7910
|
name: "stream-pod-logs",
|
|
7911
|
+
holdsResponse: true,
|
|
7905
7912
|
annotations: readOnly,
|
|
7906
7913
|
description: "Read a bounded snapshot of a Pod's logs (container and/or system source). Holds the live stream open for maxWaitMs and returns the parsed log lines; `truncated: true` means the byte cap cut the output. Use `since` to resume from a timestamp.",
|
|
7907
7914
|
inputSchema: {
|
|
@@ -7924,6 +7931,7 @@ var streamPodLogs = {
|
|
|
7924
7931
|
};
|
|
7925
7932
|
var streamWorkerLogs = {
|
|
7926
7933
|
name: "stream-worker-logs",
|
|
7934
|
+
holdsResponse: true,
|
|
7927
7935
|
annotations: readOnly,
|
|
7928
7936
|
description: "Read a bounded snapshot of a Serverless worker's logs (container and/or system source). Holds the live stream open for maxWaitMs and returns the parsed log lines; `truncated: true` means the byte cap cut the output. Get worker IDs from list-endpoint-workers. Use this whenever a job stays IN_QUEUE while workers exist, whatever status they report \u2014 a crash-looping container often still shows RUNNING or THROTTLED with unhealthy 0, so these logs, not the worker status, are what identify a crash loop.",
|
|
7929
7937
|
inputSchema: {
|
|
@@ -8781,6 +8789,69 @@ function logToolCall(entry) {
|
|
|
8781
8789
|
console.error("tool_call", JSON.stringify(entry));
|
|
8782
8790
|
}
|
|
8783
8791
|
|
|
8792
|
+
// src/search/knowledge.ts
|
|
8793
|
+
var SEARCH_NAMESPACES = ["docs", "knowledge", "blog", "site"];
|
|
8794
|
+
var MAX_QUERY_CHARS = 2e3;
|
|
8795
|
+
var MAX_SEARCH_LIMIT = 20;
|
|
8796
|
+
var UNREACHABLE = {
|
|
8797
|
+
status: 504,
|
|
8798
|
+
body: {
|
|
8799
|
+
error: "Runpod knowledge search did not respond.",
|
|
8800
|
+
hint: "Retry the same call once."
|
|
8801
|
+
}
|
|
8802
|
+
};
|
|
8803
|
+
function parseSearchRequest(raw) {
|
|
8804
|
+
if (typeof raw.query !== "string" || !raw.query.trim()) {
|
|
8805
|
+
return { error: "query must be a non-empty string." };
|
|
8806
|
+
}
|
|
8807
|
+
if (raw.query.length > MAX_QUERY_CHARS) {
|
|
8808
|
+
return { error: `query exceeds ${MAX_QUERY_CHARS} characters.` };
|
|
8809
|
+
}
|
|
8810
|
+
const request = { query: raw.query };
|
|
8811
|
+
if (raw.namespaces !== void 0) {
|
|
8812
|
+
if (!Array.isArray(raw.namespaces) || raw.namespaces.length === 0 || !raw.namespaces.every(
|
|
8813
|
+
(ns) => SEARCH_NAMESPACES.includes(ns)
|
|
8814
|
+
)) {
|
|
8815
|
+
return {
|
|
8816
|
+
error: `namespaces must be a non-empty list of: ${SEARCH_NAMESPACES.join(", ")}.`
|
|
8817
|
+
};
|
|
8818
|
+
}
|
|
8819
|
+
request.namespaces = raw.namespaces;
|
|
8820
|
+
}
|
|
8821
|
+
if (raw.limit !== void 0) {
|
|
8822
|
+
if (typeof raw.limit !== "number" || !Number.isInteger(raw.limit) || raw.limit < 1 || raw.limit > MAX_SEARCH_LIMIT) {
|
|
8823
|
+
return {
|
|
8824
|
+
error: `limit must be an integer from 1 to ${MAX_SEARCH_LIMIT}.`
|
|
8825
|
+
};
|
|
8826
|
+
}
|
|
8827
|
+
request.limit = raw.limit;
|
|
8828
|
+
}
|
|
8829
|
+
return { request };
|
|
8830
|
+
}
|
|
8831
|
+
var HOSTED_CLIENT_TIMEOUT_MS = 55e3;
|
|
8832
|
+
function hostedSearch(searchUrl, doFetch = fetch) {
|
|
8833
|
+
return async (request, apiKey) => {
|
|
8834
|
+
try {
|
|
8835
|
+
const res = await doFetch(searchUrl, {
|
|
8836
|
+
method: "POST",
|
|
8837
|
+
headers: {
|
|
8838
|
+
"Content-Type": "application/json",
|
|
8839
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
8840
|
+
},
|
|
8841
|
+
body: JSON.stringify(request),
|
|
8842
|
+
signal: AbortSignal.timeout(HOSTED_CLIENT_TIMEOUT_MS)
|
|
8843
|
+
});
|
|
8844
|
+
const body = await res.json().catch(() => null);
|
|
8845
|
+
return body ? { status: res.status, body } : {
|
|
8846
|
+
status: 502,
|
|
8847
|
+
body: { error: `Runpod knowledge search returned ${res.status}.` }
|
|
8848
|
+
};
|
|
8849
|
+
} catch {
|
|
8850
|
+
return UNREACHABLE;
|
|
8851
|
+
}
|
|
8852
|
+
};
|
|
8853
|
+
}
|
|
8854
|
+
|
|
8784
8855
|
// src/_shared/credential-check.ts
|
|
8785
8856
|
import { createHash as createHash2 } from "crypto";
|
|
8786
8857
|
var VALID_TTL_MS = 6e4;
|
|
@@ -9111,6 +9182,50 @@ function createAlpTools(opts) {
|
|
|
9111
9182
|
return [reportFeedback, saveToJournal, askQuestion, readJournalTool];
|
|
9112
9183
|
}
|
|
9113
9184
|
|
|
9185
|
+
// src/specgen/tools/search.ts
|
|
9186
|
+
function createSearchDocsTool(search) {
|
|
9187
|
+
return {
|
|
9188
|
+
name: "search-docs",
|
|
9189
|
+
annotations: readOnly,
|
|
9190
|
+
recordedArguments: ["limit"],
|
|
9191
|
+
description: 'Search Runpod documentation, curated support knowledge, the Runpod blog and runpod.io for passages that answer a question, for example "how do I attach a network volume to a Serverless endpoint". Returns ranked passages with their source URLs. Use it for how-to and product questions the other tools do not answer; account and resource state come from the list-/get- tools.',
|
|
9192
|
+
inputSchema: {
|
|
9193
|
+
type: "object",
|
|
9194
|
+
properties: {
|
|
9195
|
+
query: {
|
|
9196
|
+
type: "string",
|
|
9197
|
+
maxLength: MAX_QUERY_CHARS,
|
|
9198
|
+
description: "The question, in natural language."
|
|
9199
|
+
},
|
|
9200
|
+
namespaces: {
|
|
9201
|
+
type: "array",
|
|
9202
|
+
items: { type: "string", enum: [...SEARCH_NAMESPACES] },
|
|
9203
|
+
minItems: 1,
|
|
9204
|
+
description: "Sources to search: docs (docs.runpod.io), knowledge (curated support answers), blog, site (runpod.io pages). Default: docs, knowledge, blog."
|
|
9205
|
+
},
|
|
9206
|
+
limit: {
|
|
9207
|
+
type: "integer",
|
|
9208
|
+
minimum: 1,
|
|
9209
|
+
maximum: MAX_SEARCH_LIMIT,
|
|
9210
|
+
description: "Most passages to return. Default 8."
|
|
9211
|
+
}
|
|
9212
|
+
},
|
|
9213
|
+
required: ["query"],
|
|
9214
|
+
additionalProperties: false
|
|
9215
|
+
},
|
|
9216
|
+
handler: async (ctx, args) => {
|
|
9217
|
+
const parsed = parseSearchRequest(args);
|
|
9218
|
+
if ("error" in parsed) return badRequest(parsed.error);
|
|
9219
|
+
const outcome = await search(parsed.request, ctx.apiKey);
|
|
9220
|
+
return {
|
|
9221
|
+
ok: outcome.status === 200,
|
|
9222
|
+
status: outcome.status,
|
|
9223
|
+
payload: outcome.body
|
|
9224
|
+
};
|
|
9225
|
+
}
|
|
9226
|
+
};
|
|
9227
|
+
}
|
|
9228
|
+
|
|
9114
9229
|
// src/specgen/server.ts
|
|
9115
9230
|
var curatedTools = [
|
|
9116
9231
|
listEndpoints,
|
|
@@ -9123,6 +9238,9 @@ var curatedTools = [
|
|
|
9123
9238
|
listPublicEndpoints,
|
|
9124
9239
|
...knowledgeTools
|
|
9125
9240
|
];
|
|
9241
|
+
var responseHoldingTools = new Set(
|
|
9242
|
+
curatedTools.filter((tool) => tool.holdsResponse).map((tool) => tool.name)
|
|
9243
|
+
);
|
|
9126
9244
|
var SERVER_INSTRUCTIONS = `These tools cover the Runpod v2 REST surface: compute catalog (GPUs, CPUs, data centers, capacity), pods, serverless endpoints and jobs, templates, network volumes, container registries, billing, public endpoints, and the Hub.
|
|
9127
9245
|
|
|
9128
9246
|
Answer from live reads, as facts. Account and availability questions can only be answered from tool data: call the relevant list-/get- tool and quote the figures, names, and ids it returns verbatim. State stock and status definitively from the read you just made \u2014 never "probably" or "check later". When the user names a resource loosely ("my comfyui pod"), resolve it with a list- tool; ask only on genuine ambiguity. If nothing exists (no pods, zero spend), say exactly that \u2014 an honest empty answer is complete.
|
|
@@ -9146,9 +9264,10 @@ CONTRIBUTING BACK IS OPTIONAL. Four tools feed Runpod's agent-learning pipeline,
|
|
|
9146
9264
|
function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
|
|
9147
9265
|
const rateLimiter = opts.rateLimiter ?? noopRateLimiter;
|
|
9148
9266
|
const caller = callerId(ctx.apiKey);
|
|
9149
|
-
const configuredCuratedTools = opts.alp ? [...curatedTools, ...createAlpTools(opts.alp)] : curatedTools;
|
|
9150
9267
|
const servedCuratedTools = [
|
|
9151
|
-
...
|
|
9268
|
+
...curatedTools,
|
|
9269
|
+
...opts.search ? [createSearchDocsTool(opts.search)] : [],
|
|
9270
|
+
...opts.alp ? createAlpTools(opts.alp) : [],
|
|
9152
9271
|
createDescribeTool(() => [...servedCuratedTools, ...generatedTools])
|
|
9153
9272
|
];
|
|
9154
9273
|
const callShapeOnlyTools = new Set(
|
|
@@ -9344,6 +9463,7 @@ function asRecord(value) {
|
|
|
9344
9463
|
}
|
|
9345
9464
|
|
|
9346
9465
|
// src/stdio.ts
|
|
9466
|
+
var SEARCH_URL = `${hostedMcpBase(process.env)}/api/search`;
|
|
9347
9467
|
var WIZARD_SUBCOMMANDS = ["add", "remove"];
|
|
9348
9468
|
var subcommand = process.argv[2];
|
|
9349
9469
|
var isWizardMode = WIZARD_SUBCOMMANDS.includes(subcommand);
|
|
@@ -9353,7 +9473,8 @@ async function main(apiKey) {
|
|
|
9353
9473
|
apiKey,
|
|
9354
9474
|
tracking: { transport: "stdio", serverVersion: SERVER_VERSION }
|
|
9355
9475
|
}),
|
|
9356
|
-
SERVER_VERSION
|
|
9476
|
+
SERVER_VERSION,
|
|
9477
|
+
{ search: hostedSearch(SEARCH_URL) }
|
|
9357
9478
|
);
|
|
9358
9479
|
await server.connect(new StdioServerTransport());
|
|
9359
9480
|
}
|
|
@@ -9371,7 +9492,7 @@ if (subcommand === "--version" || subcommand === "-v") {
|
|
|
9371
9492
|
].join("\n")
|
|
9372
9493
|
);
|
|
9373
9494
|
} else if (isWizardMode) {
|
|
9374
|
-
import("./wizard-
|
|
9495
|
+
import("./wizard-KKTBTVXE.mjs").then(async ({ runWizard }) => {
|
|
9375
9496
|
process.exitCode = await runWizard(subcommand);
|
|
9376
9497
|
}).catch((error) => {
|
|
9377
9498
|
console.error(error instanceof Error ? error.message : String(error));
|