index.js•20.3 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fetch from "node-fetch";
// Base URL for RunPod API
const API_BASE_URL = "https://rest.runpod.io/v1";
// Get API key from environment variable
const API_KEY = process.env.RUNPOD_API_KEY;
if (!API_KEY) {
console.error("RUNPOD_API_KEY environment variable is required");
process.exit(1);
}
// Create an MCP server
const server = new McpServer({
name: "RunPod API Server",
version: "1.0.0",
capabilities: {
resources: {},
tools: {},
},
});
// Helper function to make authenticated API requests to RunPod
async function runpodRequest(endpoint, method = "GET", body) {
const url = `${API_BASE_URL}${endpoint}`;
const headers = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
};
const options = {
method,
headers,
};
if (body && (method === "POST" || method === "PATCH")) {
options.body = JSON.stringify(body);
}
try {
const response = await fetch(url, options);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`RunPod API Error: ${response.status} - ${errorText}`);
}
// Some endpoints might not return JSON
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
return await response.json();
}
return { success: true, status: response.status };
}
catch (error) {
console.error("Error calling RunPod API:", error);
throw error;
}
}
// ============== POD MANAGEMENT TOOLS ==============
// List Pods
server.tool("list-pods", {
computeType: z
.enum(["GPU", "CPU"])
.optional()
.describe("Filter to only GPU or only CPU Pods"),
gpuTypeId: z
.array(z.string())
.optional()
.describe("Filter to Pods with any of the listed GPU types"),
dataCenterId: z
.array(z.string())
.optional()
.describe("Filter to Pods in any of the provided data centers"),
name: z
.string()
.optional()
.describe("Filter to Pods with the provided name"),
includeMachine: z
.boolean()
.optional()
.describe("Include information about the machine"),
includeNetworkVolume: z
.boolean()
.optional()
.describe("Include information about attached network volumes"),
}, async (params) => {
// Construct query parameters
const queryParams = new URLSearchParams();
if (params.computeType)
queryParams.append("computeType", params.computeType);
if (params.gpuTypeId)
params.gpuTypeId.forEach((type) => queryParams.append("gpuTypeId", type));
if (params.dataCenterId)
params.dataCenterId.forEach((dc) => queryParams.append("dataCenterId", dc));
if (params.name)
queryParams.append("name", params.name);
if (params.includeMachine)
queryParams.append("includeMachine", params.includeMachine.toString());
if (params.includeNetworkVolume)
queryParams.append("includeNetworkVolume", params.includeNetworkVolume.toString());
const queryString = queryParams.toString()
? `?${queryParams.toString()}`
: "";
const result = await runpodRequest(`/pods${queryString}`);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Get Pod Details
server.tool("get-pod", {
podId: z.string().describe("ID of the pod to retrieve"),
includeMachine: z
.boolean()
.optional()
.describe("Include information about the machine"),
includeNetworkVolume: z
.boolean()
.optional()
.describe("Include information about attached network volumes"),
}, async (params) => {
// Construct query parameters
const queryParams = new URLSearchParams();
if (params.includeMachine)
queryParams.append("includeMachine", params.includeMachine.toString());
if (params.includeNetworkVolume)
queryParams.append("includeNetworkVolume", params.includeNetworkVolume.toString());
const queryString = queryParams.toString()
? `?${queryParams.toString()}`
: "";
const result = await runpodRequest(`/pods/${params.podId}${queryString}`);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Create Pod
server.tool("create-pod", {
name: z.string().optional().describe("Name for the pod"),
imageName: z.string().describe("Docker image to use"),
cloudType: z
.enum(["SECURE", "COMMUNITY"])
.optional()
.describe("SECURE or COMMUNITY cloud"),
gpuTypeIds: z
.array(z.string())
.optional()
.describe("List of acceptable GPU types"),
gpuCount: z.number().optional().describe("Number of GPUs"),
containerDiskInGb: z
.number()
.optional()
.describe("Container disk size in GB"),
volumeInGb: z.number().optional().describe("Volume size in GB"),
volumeMountPath: z.string().optional().describe("Path to mount the volume"),
ports: z
.array(z.string())
.optional()
.describe("Ports to expose (e.g., '8888/http', '22/tcp')"),
env: z.record(z.string()).optional().describe("Environment variables"),
dataCenterIds: z
.array(z.string())
.optional()
.describe("List of data centers"),
}, async (params) => {
const result = await runpodRequest("/pods", "POST", params);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Update Pod
server.tool("update-pod", {
podId: z.string().describe("ID of the pod to update"),
name: z.string().optional().describe("New name for the pod"),
imageName: z.string().optional().describe("New Docker image"),
containerDiskInGb: z
.number()
.optional()
.describe("New container disk size in GB"),
volumeInGb: z.number().optional().describe("New volume size in GB"),
volumeMountPath: z
.string()
.optional()
.describe("New path to mount the volume"),
ports: z.array(z.string()).optional().describe("New ports to expose"),
env: z.record(z.string()).optional().describe("New environment variables"),
}, async (params) => {
const { podId, ...updateParams } = params;
const result = await runpodRequest(`/pods/${podId}`, "PATCH", updateParams);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Start Pod
server.tool("start-pod", {
podId: z.string().describe("ID of the pod to start"),
}, async (params) => {
const result = await runpodRequest(`/pods/${params.podId}/start`, "POST");
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Stop Pod
server.tool("stop-pod", {
podId: z.string().describe("ID of the pod to stop"),
}, async (params) => {
const result = await runpodRequest(`/pods/${params.podId}/stop`, "POST");
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Delete Pod
server.tool("delete-pod", {
podId: z.string().describe("ID of the pod to delete"),
}, async (params) => {
const result = await runpodRequest(`/pods/${params.podId}`, "DELETE");
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// ============== ENDPOINT MANAGEMENT TOOLS ==============
// List Endpoints
server.tool("list-endpoints", {
includeTemplate: z
.boolean()
.optional()
.describe("Include template information"),
includeWorkers: z
.boolean()
.optional()
.describe("Include information about workers"),
}, async (params) => {
// Construct query parameters
const queryParams = new URLSearchParams();
if (params.includeTemplate)
queryParams.append("includeTemplate", params.includeTemplate.toString());
if (params.includeWorkers)
queryParams.append("includeWorkers", params.includeWorkers.toString());
const queryString = queryParams.toString()
? `?${queryParams.toString()}`
: "";
const result = await runpodRequest(`/endpoints${queryString}`);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Get Endpoint Details
server.tool("get-endpoint", {
endpointId: z.string().describe("ID of the endpoint to retrieve"),
includeTemplate: z
.boolean()
.optional()
.describe("Include template information"),
includeWorkers: z
.boolean()
.optional()
.describe("Include information about workers"),
}, async (params) => {
// Construct query parameters
const queryParams = new URLSearchParams();
if (params.includeTemplate)
queryParams.append("includeTemplate", params.includeTemplate.toString());
if (params.includeWorkers)
queryParams.append("includeWorkers", params.includeWorkers.toString());
const queryString = queryParams.toString()
? `?${queryParams.toString()}`
: "";
const result = await runpodRequest(`/endpoints/${params.endpointId}${queryString}`);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Create Endpoint
server.tool("create-endpoint", {
name: z.string().optional().describe("Name for the endpoint"),
templateId: z.string().describe("Template ID to use"),
computeType: z
.enum(["GPU", "CPU"])
.optional()
.describe("GPU or CPU endpoint"),
gpuTypeIds: z
.array(z.string())
.optional()
.describe("List of acceptable GPU types"),
gpuCount: z.number().optional().describe("Number of GPUs per worker"),
workersMin: z.number().optional().describe("Minimum number of workers"),
workersMax: z.number().optional().describe("Maximum number of workers"),
dataCenterIds: z
.array(z.string())
.optional()
.describe("List of data centers"),
}, async (params) => {
const result = await runpodRequest("/endpoints", "POST", params);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Update Endpoint
server.tool("update-endpoint", {
endpointId: z.string().describe("ID of the endpoint to update"),
name: z.string().optional().describe("New name for the endpoint"),
workersMin: z.number().optional().describe("New minimum number of workers"),
workersMax: z.number().optional().describe("New maximum number of workers"),
idleTimeout: z.number().optional().describe("New idle timeout in seconds"),
scalerType: z
.enum(["QUEUE_DELAY", "REQUEST_COUNT"])
.optional()
.describe("Scaler type"),
scalerValue: z.number().optional().describe("Scaler value"),
}, async (params) => {
const { endpointId, ...updateParams } = params;
const result = await runpodRequest(`/endpoints/${endpointId}`, "PATCH", updateParams);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Delete Endpoint
server.tool("delete-endpoint", {
endpointId: z.string().describe("ID of the endpoint to delete"),
}, async (params) => {
const result = await runpodRequest(`/endpoints/${params.endpointId}`, "DELETE");
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// ============== TEMPLATE MANAGEMENT TOOLS ==============
// List Templates
server.tool("list-templates", {}, async () => {
const result = await runpodRequest("/templates");
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Get Template Details
server.tool("get-template", {
templateId: z.string().describe("ID of the template to retrieve"),
}, async (params) => {
const result = await runpodRequest(`/templates/${params.templateId}`);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Create Template
server.tool("create-template", {
name: z.string().describe("Name for the template"),
imageName: z.string().describe("Docker image to use"),
isServerless: z
.boolean()
.optional()
.describe("Is this a serverless template"),
ports: z.array(z.string()).optional().describe("Ports to expose"),
dockerEntrypoint: z
.array(z.string())
.optional()
.describe("Docker entrypoint commands"),
dockerStartCmd: z
.array(z.string())
.optional()
.describe("Docker start commands"),
env: z.record(z.string()).optional().describe("Environment variables"),
containerDiskInGb: z
.number()
.optional()
.describe("Container disk size in GB"),
volumeInGb: z.number().optional().describe("Volume size in GB"),
volumeMountPath: z.string().optional().describe("Path to mount the volume"),
readme: z.string().optional().describe("README content in markdown format"),
}, async (params) => {
const result = await runpodRequest("/templates", "POST", params);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Update Template
server.tool("update-template", {
templateId: z.string().describe("ID of the template to update"),
name: z.string().optional().describe("New name for the template"),
imageName: z.string().optional().describe("New Docker image"),
ports: z.array(z.string()).optional().describe("New ports to expose"),
env: z.record(z.string()).optional().describe("New environment variables"),
readme: z
.string()
.optional()
.describe("New README content in markdown format"),
}, async (params) => {
const { templateId, ...updateParams } = params;
const result = await runpodRequest(`/templates/${templateId}`, "PATCH", updateParams);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Delete Template
server.tool("delete-template", {
templateId: z.string().describe("ID of the template to delete"),
}, async (params) => {
const result = await runpodRequest(`/templates/${params.templateId}`, "DELETE");
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// ============== NETWORK VOLUME MANAGEMENT TOOLS ==============
// List Network Volumes
server.tool("list-network-volumes", {}, async () => {
const result = await runpodRequest("/networkvolumes");
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Get Network Volume Details
server.tool("get-network-volume", {
networkVolumeId: z
.string()
.describe("ID of the network volume to retrieve"),
}, async (params) => {
const result = await runpodRequest(`/networkvolumes/${params.networkVolumeId}`);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Create Network Volume
server.tool("create-network-volume", {
name: z.string().describe("Name for the network volume"),
size: z.number().describe("Size in GB (1-4000)"),
dataCenterId: z.string().describe("Data center ID"),
}, async (params) => {
const result = await runpodRequest("/networkvolumes", "POST", params);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Update Network Volume
server.tool("update-network-volume", {
networkVolumeId: z.string().describe("ID of the network volume to update"),
name: z.string().optional().describe("New name for the network volume"),
size: z
.number()
.optional()
.describe("New size in GB (must be larger than current)"),
}, async (params) => {
const { networkVolumeId, ...updateParams } = params;
const result = await runpodRequest(`/networkvolumes/${networkVolumeId}`, "PATCH", updateParams);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Delete Network Volume
server.tool("delete-network-volume", {
networkVolumeId: z.string().describe("ID of the network volume to delete"),
}, async (params) => {
const result = await runpodRequest(`/networkvolumes/${params.networkVolumeId}`, "DELETE");
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// ============== CONTAINER REGISTRY AUTH TOOLS ==============
// List Container Registry Auths
server.tool("list-container-registry-auths", {}, async () => {
const result = await runpodRequest("/containerregistryauth");
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Get Container Registry Auth Details
server.tool("get-container-registry-auth", {
containerRegistryAuthId: z
.string()
.describe("ID of the container registry auth to retrieve"),
}, async (params) => {
const result = await runpodRequest(`/containerregistryauth/${params.containerRegistryAuthId}`);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Create Container Registry Auth
server.tool("create-container-registry-auth", {
name: z.string().describe("Name for the container registry auth"),
username: z.string().describe("Registry username"),
password: z.string().describe("Registry password"),
}, async (params) => {
const result = await runpodRequest("/containerregistryauth", "POST", params);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Delete Container Registry Auth
server.tool("delete-container-registry-auth", {
containerRegistryAuthId: z
.string()
.describe("ID of the container registry auth to delete"),
}, async (params) => {
const result = await runpodRequest(`/containerregistryauth/${params.containerRegistryAuthId}`, "DELETE");
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
});
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
server.connect(transport);