@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.cjs
CHANGED
|
@@ -46,6 +46,9 @@ function publicGraphqlBase(env) {
|
|
|
46
46
|
function authedGraphqlBase(env) {
|
|
47
47
|
return pick(env.RUNPOD_AUTHED_GRAPHQL_URL, "https://api.runpod.io/graphql");
|
|
48
48
|
}
|
|
49
|
+
function hostedMcpBase(env) {
|
|
50
|
+
return pick(env.RUNPOD_MCP_URL, "https://mcp.getrunpod.io");
|
|
51
|
+
}
|
|
49
52
|
var init_hosts = __esm({
|
|
50
53
|
"src/_shared/hosts.ts"() {
|
|
51
54
|
"use strict";
|
|
@@ -736,9 +739,10 @@ var init_clients = __esm({
|
|
|
736
739
|
path = __toESM(require("path"), 1);
|
|
737
740
|
import_child_process = require("child_process");
|
|
738
741
|
jsonc = __toESM(require("jsonc-parser"), 1);
|
|
742
|
+
init_hosts();
|
|
739
743
|
SERVER_NAME2 = "runpod";
|
|
740
744
|
NPM_PACKAGE = "@runpod/mcp-server@latest";
|
|
741
|
-
HOSTED_URL = process.env
|
|
745
|
+
HOSTED_URL = `${hostedMcpBase(process.env)}/`;
|
|
742
746
|
PROJECT_BOUNDARY_MARKERS = [
|
|
743
747
|
".git",
|
|
744
748
|
"package.json",
|
|
@@ -1179,7 +1183,7 @@ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
|
1179
1183
|
|
|
1180
1184
|
// src/server.ts
|
|
1181
1185
|
var SERVER_NAME = "Runpod API Server";
|
|
1182
|
-
var SERVER_VERSION = true ? "4.
|
|
1186
|
+
var SERVER_VERSION = true ? "4.4.0" : "dev";
|
|
1183
1187
|
|
|
1184
1188
|
// src/specgen/server.ts
|
|
1185
1189
|
var import_server3 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
@@ -6864,6 +6868,8 @@ var listHubRepos = {
|
|
|
6864
6868
|
};
|
|
6865
6869
|
var deployHubRepo = {
|
|
6866
6870
|
name: "deploy-hub-repo",
|
|
6871
|
+
// Two sequential upstream calls: together they can outlast a client's request timeout.
|
|
6872
|
+
holdsResponse: true,
|
|
6867
6873
|
annotations: write,
|
|
6868
6874
|
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).",
|
|
6869
6875
|
inputSchema: {
|
|
@@ -7352,6 +7358,7 @@ var runEndpoint = {
|
|
|
7352
7358
|
};
|
|
7353
7359
|
var runsyncEndpoint = {
|
|
7354
7360
|
name: "runsync-endpoint",
|
|
7361
|
+
holdsResponse: true,
|
|
7355
7362
|
annotations: write,
|
|
7356
7363
|
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)."),
|
|
7357
7364
|
inputSchema: {
|
|
@@ -7396,6 +7403,7 @@ var runsyncEndpoint = {
|
|
|
7396
7403
|
};
|
|
7397
7404
|
var getJobStatus = {
|
|
7398
7405
|
name: "get-job-status",
|
|
7406
|
+
holdsResponse: true,
|
|
7399
7407
|
annotations: readOnly,
|
|
7400
7408
|
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.",
|
|
7401
7409
|
inputSchema: {
|
|
@@ -7444,6 +7452,7 @@ var getJobStatus = {
|
|
|
7444
7452
|
};
|
|
7445
7453
|
var streamJob = {
|
|
7446
7454
|
name: "stream-job",
|
|
7455
|
+
holdsResponse: true,
|
|
7447
7456
|
annotations: readOnly,
|
|
7448
7457
|
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.`,
|
|
7449
7458
|
inputSchema: {
|
|
@@ -9067,6 +9076,7 @@ function logParams(args) {
|
|
|
9067
9076
|
}
|
|
9068
9077
|
var streamPodLogs = {
|
|
9069
9078
|
name: "stream-pod-logs",
|
|
9079
|
+
holdsResponse: true,
|
|
9070
9080
|
annotations: readOnly,
|
|
9071
9081
|
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.",
|
|
9072
9082
|
inputSchema: {
|
|
@@ -9089,6 +9099,7 @@ var streamPodLogs = {
|
|
|
9089
9099
|
};
|
|
9090
9100
|
var streamWorkerLogs = {
|
|
9091
9101
|
name: "stream-worker-logs",
|
|
9102
|
+
holdsResponse: true,
|
|
9092
9103
|
annotations: readOnly,
|
|
9093
9104
|
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.",
|
|
9094
9105
|
inputSchema: {
|
|
@@ -9943,6 +9954,70 @@ function logToolCall(entry) {
|
|
|
9943
9954
|
console.error("tool_call", JSON.stringify(entry));
|
|
9944
9955
|
}
|
|
9945
9956
|
|
|
9957
|
+
// src/search/knowledge.ts
|
|
9958
|
+
init_hosts();
|
|
9959
|
+
var SEARCH_NAMESPACES = ["docs", "knowledge", "blog", "site"];
|
|
9960
|
+
var MAX_QUERY_CHARS = 2e3;
|
|
9961
|
+
var MAX_SEARCH_LIMIT = 20;
|
|
9962
|
+
var UNREACHABLE = {
|
|
9963
|
+
status: 504,
|
|
9964
|
+
body: {
|
|
9965
|
+
error: "Runpod knowledge search did not respond.",
|
|
9966
|
+
hint: "Retry the same call once."
|
|
9967
|
+
}
|
|
9968
|
+
};
|
|
9969
|
+
function parseSearchRequest(raw) {
|
|
9970
|
+
if (typeof raw.query !== "string" || !raw.query.trim()) {
|
|
9971
|
+
return { error: "query must be a non-empty string." };
|
|
9972
|
+
}
|
|
9973
|
+
if (raw.query.length > MAX_QUERY_CHARS) {
|
|
9974
|
+
return { error: `query exceeds ${MAX_QUERY_CHARS} characters.` };
|
|
9975
|
+
}
|
|
9976
|
+
const request = { query: raw.query };
|
|
9977
|
+
if (raw.namespaces !== void 0) {
|
|
9978
|
+
if (!Array.isArray(raw.namespaces) || raw.namespaces.length === 0 || !raw.namespaces.every(
|
|
9979
|
+
(ns) => SEARCH_NAMESPACES.includes(ns)
|
|
9980
|
+
)) {
|
|
9981
|
+
return {
|
|
9982
|
+
error: `namespaces must be a non-empty list of: ${SEARCH_NAMESPACES.join(", ")}.`
|
|
9983
|
+
};
|
|
9984
|
+
}
|
|
9985
|
+
request.namespaces = raw.namespaces;
|
|
9986
|
+
}
|
|
9987
|
+
if (raw.limit !== void 0) {
|
|
9988
|
+
if (typeof raw.limit !== "number" || !Number.isInteger(raw.limit) || raw.limit < 1 || raw.limit > MAX_SEARCH_LIMIT) {
|
|
9989
|
+
return {
|
|
9990
|
+
error: `limit must be an integer from 1 to ${MAX_SEARCH_LIMIT}.`
|
|
9991
|
+
};
|
|
9992
|
+
}
|
|
9993
|
+
request.limit = raw.limit;
|
|
9994
|
+
}
|
|
9995
|
+
return { request };
|
|
9996
|
+
}
|
|
9997
|
+
var HOSTED_CLIENT_TIMEOUT_MS = 55e3;
|
|
9998
|
+
function hostedSearch(searchUrl, doFetch = fetch) {
|
|
9999
|
+
return async (request, apiKey) => {
|
|
10000
|
+
try {
|
|
10001
|
+
const res = await doFetch(searchUrl, {
|
|
10002
|
+
method: "POST",
|
|
10003
|
+
headers: {
|
|
10004
|
+
"Content-Type": "application/json",
|
|
10005
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
10006
|
+
},
|
|
10007
|
+
body: JSON.stringify(request),
|
|
10008
|
+
signal: AbortSignal.timeout(HOSTED_CLIENT_TIMEOUT_MS)
|
|
10009
|
+
});
|
|
10010
|
+
const body = await res.json().catch(() => null);
|
|
10011
|
+
return body ? { status: res.status, body } : {
|
|
10012
|
+
status: 502,
|
|
10013
|
+
body: { error: `Runpod knowledge search returned ${res.status}.` }
|
|
10014
|
+
};
|
|
10015
|
+
} catch {
|
|
10016
|
+
return UNREACHABLE;
|
|
10017
|
+
}
|
|
10018
|
+
};
|
|
10019
|
+
}
|
|
10020
|
+
|
|
9946
10021
|
// src/_shared/credential-check.ts
|
|
9947
10022
|
var import_node_crypto4 = require("crypto");
|
|
9948
10023
|
var VALID_TTL_MS = 6e4;
|
|
@@ -10274,6 +10349,50 @@ function createAlpTools(opts) {
|
|
|
10274
10349
|
return [reportFeedback, saveToJournal, askQuestion, readJournalTool];
|
|
10275
10350
|
}
|
|
10276
10351
|
|
|
10352
|
+
// src/specgen/tools/search.ts
|
|
10353
|
+
function createSearchDocsTool(search) {
|
|
10354
|
+
return {
|
|
10355
|
+
name: "search-docs",
|
|
10356
|
+
annotations: readOnly,
|
|
10357
|
+
recordedArguments: ["limit"],
|
|
10358
|
+
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.',
|
|
10359
|
+
inputSchema: {
|
|
10360
|
+
type: "object",
|
|
10361
|
+
properties: {
|
|
10362
|
+
query: {
|
|
10363
|
+
type: "string",
|
|
10364
|
+
maxLength: MAX_QUERY_CHARS,
|
|
10365
|
+
description: "The question, in natural language."
|
|
10366
|
+
},
|
|
10367
|
+
namespaces: {
|
|
10368
|
+
type: "array",
|
|
10369
|
+
items: { type: "string", enum: [...SEARCH_NAMESPACES] },
|
|
10370
|
+
minItems: 1,
|
|
10371
|
+
description: "Sources to search: docs (docs.runpod.io), knowledge (curated support answers), blog, site (runpod.io pages). Default: docs, knowledge, blog."
|
|
10372
|
+
},
|
|
10373
|
+
limit: {
|
|
10374
|
+
type: "integer",
|
|
10375
|
+
minimum: 1,
|
|
10376
|
+
maximum: MAX_SEARCH_LIMIT,
|
|
10377
|
+
description: "Most passages to return. Default 8."
|
|
10378
|
+
}
|
|
10379
|
+
},
|
|
10380
|
+
required: ["query"],
|
|
10381
|
+
additionalProperties: false
|
|
10382
|
+
},
|
|
10383
|
+
handler: async (ctx, args) => {
|
|
10384
|
+
const parsed = parseSearchRequest(args);
|
|
10385
|
+
if ("error" in parsed) return badRequest(parsed.error);
|
|
10386
|
+
const outcome = await search(parsed.request, ctx.apiKey);
|
|
10387
|
+
return {
|
|
10388
|
+
ok: outcome.status === 200,
|
|
10389
|
+
status: outcome.status,
|
|
10390
|
+
payload: outcome.body
|
|
10391
|
+
};
|
|
10392
|
+
}
|
|
10393
|
+
};
|
|
10394
|
+
}
|
|
10395
|
+
|
|
10277
10396
|
// src/specgen/server.ts
|
|
10278
10397
|
var curatedTools = [
|
|
10279
10398
|
listEndpoints,
|
|
@@ -10286,6 +10405,9 @@ var curatedTools = [
|
|
|
10286
10405
|
listPublicEndpoints,
|
|
10287
10406
|
...knowledgeTools
|
|
10288
10407
|
];
|
|
10408
|
+
var responseHoldingTools = new Set(
|
|
10409
|
+
curatedTools.filter((tool) => tool.holdsResponse).map((tool) => tool.name)
|
|
10410
|
+
);
|
|
10289
10411
|
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.
|
|
10290
10412
|
|
|
10291
10413
|
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.
|
|
@@ -10309,9 +10431,10 @@ CONTRIBUTING BACK IS OPTIONAL. Four tools feed Runpod's agent-learning pipeline,
|
|
|
10309
10431
|
function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
|
|
10310
10432
|
const rateLimiter = opts.rateLimiter ?? noopRateLimiter;
|
|
10311
10433
|
const caller = callerId(ctx.apiKey);
|
|
10312
|
-
const configuredCuratedTools = opts.alp ? [...curatedTools, ...createAlpTools(opts.alp)] : curatedTools;
|
|
10313
10434
|
const servedCuratedTools = [
|
|
10314
|
-
...
|
|
10435
|
+
...curatedTools,
|
|
10436
|
+
...opts.search ? [createSearchDocsTool(opts.search)] : [],
|
|
10437
|
+
...opts.alp ? createAlpTools(opts.alp) : [],
|
|
10315
10438
|
createDescribeTool(() => [...servedCuratedTools, ...generatedTools])
|
|
10316
10439
|
];
|
|
10317
10440
|
const callShapeOnlyTools = new Set(
|
|
@@ -10507,6 +10630,8 @@ function asRecord(value) {
|
|
|
10507
10630
|
}
|
|
10508
10631
|
|
|
10509
10632
|
// src/stdio.ts
|
|
10633
|
+
init_hosts();
|
|
10634
|
+
var SEARCH_URL = `${hostedMcpBase(process.env)}/api/search`;
|
|
10510
10635
|
var WIZARD_SUBCOMMANDS = ["add", "remove"];
|
|
10511
10636
|
var subcommand = process.argv[2];
|
|
10512
10637
|
var isWizardMode = WIZARD_SUBCOMMANDS.includes(subcommand);
|
|
@@ -10516,7 +10641,8 @@ async function main(apiKey) {
|
|
|
10516
10641
|
apiKey,
|
|
10517
10642
|
tracking: { transport: "stdio", serverVersion: SERVER_VERSION }
|
|
10518
10643
|
}),
|
|
10519
|
-
SERVER_VERSION
|
|
10644
|
+
SERVER_VERSION,
|
|
10645
|
+
{ search: hostedSearch(SEARCH_URL) }
|
|
10520
10646
|
);
|
|
10521
10647
|
await server.connect(new import_stdio.StdioServerTransport());
|
|
10522
10648
|
}
|