# Directory Structure ``` ├── .cursor │ └── mcp.json ├── .gitignore ├── build │ └── index.js ├── Dockerfile ├── LICENSE ├── package.json ├── README.md ├── smithery.yaml ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | /node_modules 2 | /package-lock.json 3 | .env ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # RunPod MCP Server 2 | [](https://smithery.ai/server/@runpod/runpod-mcp-ts) 3 | 4 | This Model Context Protocol (MCP) server enables you to interact with the RunPod REST API through Claude or other MCP-compatible clients. 5 | 6 | ## Features 7 | 8 | The server provides tools for managing: 9 | 10 | - **Pods**: Create, list, get details, update, start, stop, and delete pods 11 | - **Endpoints**: Create, list, get details, update, and delete serverless endpoints 12 | - **Templates**: Create, list, get details, update, and delete templates 13 | - **Network Volumes**: Create, list, get details, update, and delete network volumes 14 | - **Container Registry Authentications**: Create, list, get details, and delete authentications 15 | 16 | ## Setup 17 | 18 | ### Prerequisites 19 | 20 | - Node.js 18 or higher 21 | - A RunPod account and API key 22 | - Claude for Desktop or another MCP-compatible client 23 | 24 | ### Installing via Smithery 25 | 26 | To install runpod-mcp-ts for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@runpod/runpod-mcp-ts): 27 | 28 | ```bash 29 | npx -y @smithery/cli install @runpod/runpod-mcp-ts --client claude 30 | ``` 31 | 32 | ### Installation 33 | 34 | 1. Clone the repository 35 | 2. Install dependencies: 36 | ``` 37 | npm install 38 | ``` 39 | 3. Build the server: 40 | ``` 41 | npm run build 42 | ``` 43 | 44 | ### Configuration 45 | 46 | Set your RunPod API key as an environment variable: 47 | 48 | ```bash 49 | # Linux/macOS 50 | export RUNPOD_API_KEY=your_api_key_here 51 | 52 | # Windows (Command Prompt) 53 | set RUNPOD_API_KEY=your_api_key_here 54 | 55 | # Windows (PowerShell) 56 | $env:RUNPOD_API_KEY="your_api_key_here" 57 | ``` 58 | 59 | You can get your API key from the [RunPod console](https://www.runpod.io/console/user/settings). 60 | 61 | ### Running the Server 62 | 63 | Start the server: 64 | 65 | ```bash 66 | npm start 67 | ``` 68 | 69 | ## Setting up with Claude for Desktop 70 | 71 | 1. Open Claude for Desktop 72 | 2. Edit the config file: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows) 73 | 3. Add the server configuration: 74 | 75 | ```json 76 | { 77 | "mcpServers": { 78 | "runpod": { 79 | "command": "node", 80 | "args": ["/path/to/runpod-mcp-server/build/index.js"], 81 | "env": { 82 | "RUNPOD_API_KEY": "your_api_key_here" 83 | } 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | Make sure to replace the `"args": ["/path/to/runpod-mcp-server/build/index.js"]` with the path to the build folder in the repository. 90 | 91 | 4. Restart Claude for Desktop 92 | 93 | ## Usage Examples 94 | 95 | Here are some examples of how to use the server with Claude: 96 | 97 | ### List all pods 98 | 99 | ``` 100 | Can you list all my RunPod pods? 101 | ``` 102 | 103 | ### Create a new pod 104 | 105 | ``` 106 | Create a new RunPod pod with the following specifications: 107 | - Name: test-pod 108 | - Image: runpod/pytorch:2.1.0-py3.10-cuda11.8.0-devel-ubuntu22.04 109 | - GPU Type: NVIDIA GeForce RTX 4090 110 | - GPU Count: 1 111 | ``` 112 | 113 | ### Create a serverless endpoint 114 | 115 | ``` 116 | Create a RunPod serverless endpoint with the following configuration: 117 | - Name: my-endpoint 118 | - Template ID: 30zmvf89kd 119 | - Minimum workers: 0 120 | - Maximum workers: 3 121 | ``` 122 | 123 | ## Security Considerations 124 | 125 | This server requires your RunPod API key, which grants full access to your RunPod account. For security: 126 | 127 | - Never share your API key 128 | - Be cautious about what operations you perform 129 | - Consider setting up a separate API key with limited permissions 130 | - Don't use this in a production environment without proper security measures 131 | 132 | ## License 133 | 134 | MIT 135 | ``` -------------------------------------------------------------------------------- /.cursor/mcp.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "runpod": { 4 | "command": "node", 5 | "args": ["your/path/to/runpod-mcp-ts/build/index.js"], 6 | "env": { 7 | "RUNPOD_API_KEY": "your_api_key_here" 8 | } 9 | } 10 | } 11 | } 12 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | FROM node:lts-alpine 3 | 4 | # Set working directory 5 | WORKDIR /app 6 | 7 | # Copy package.json and package-lock.json if available 8 | COPY package*.json ./ 9 | 10 | # Install dependencies 11 | RUN npm install --ignore-scripts 12 | 13 | # Copy the rest of the application 14 | COPY . . 15 | 16 | # Build the project 17 | RUN npm run build 18 | 19 | # Expose any necessary port if required (not specified, so leaving out) 20 | 21 | # Start the server 22 | CMD ["npm", "start"] 23 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "runpod-mcp-server", 3 | "version": "1.0.0", 4 | "description": "MCP server for interacting with RunPod API", 5 | "type": "module", 6 | "main": "build/index.js", 7 | "scripts": { 8 | "build": "tsc", 9 | "start": "node build/index.js", 10 | "dev": "tsx index.ts" 11 | }, 12 | "dependencies": { 13 | "@modelcontextprotocol/sdk": "^1.7.0", 14 | "node-fetch": "^3.3.2", 15 | "zod": "^3.22.4" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20.10.5", 19 | "tsx": "^4.7.0", 20 | "typescript": "^5.3.3" 21 | } 22 | } 23 | ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: 9 | - runpodApiKey 10 | properties: 11 | runpodApiKey: 12 | type: string 13 | description: Your RunPod API key 14 | commandFunction: 15 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio. 16 | |- 17 | (config) => ({ 18 | command: 'node', 19 | args: ['build/index.js'], 20 | env: { RUNPOD_API_KEY: config.runpodApiKey } 21 | }) 22 | exampleConfig: 23 | runpodApiKey: your_dummy_runpod_api_key_here 24 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" 3 | import { z } from "zod" 4 | import fetch from "node-fetch" 5 | 6 | // Base URL for RunPod API 7 | const API_BASE_URL = "https://rest.runpod.io/v1" 8 | 9 | // Get API key from environment variable 10 | const API_KEY = process.env.RUNPOD_API_KEY 11 | if (!API_KEY) { 12 | console.error("RUNPOD_API_KEY environment variable is required") 13 | process.exit(1) 14 | } 15 | 16 | // Create an MCP server 17 | const server = new McpServer({ 18 | name: "RunPod API Server", 19 | version: "1.0.0", 20 | capabilities: { 21 | resources: {}, 22 | tools: {}, 23 | }, 24 | }) 25 | 26 | // Helper function to make authenticated API requests to RunPod 27 | async function runpodRequest( 28 | endpoint: string, 29 | method: string = "GET", 30 | body?: any 31 | ) { 32 | const url = `${API_BASE_URL}${endpoint}` 33 | const headers = { 34 | Authorization: `Bearer ${API_KEY}`, 35 | "Content-Type": "application/json", 36 | } 37 | 38 | const options: any = { 39 | method, 40 | headers, 41 | } 42 | 43 | if (body && (method === "POST" || method === "PATCH")) { 44 | options.body = JSON.stringify(body) 45 | } 46 | 47 | try { 48 | const response = await fetch(url, options) 49 | 50 | if (!response.ok) { 51 | const errorText = await response.text() 52 | throw new Error(`RunPod API Error: ${response.status} - ${errorText}`) 53 | } 54 | 55 | // Some endpoints might not return JSON 56 | const contentType = response.headers.get("content-type") 57 | if (contentType && contentType.includes("application/json")) { 58 | return await response.json() 59 | } 60 | 61 | return { success: true, status: response.status } 62 | } catch (error) { 63 | console.error("Error calling RunPod API:", error) 64 | throw error 65 | } 66 | } 67 | 68 | // ============== POD MANAGEMENT TOOLS ============== 69 | 70 | // List Pods 71 | server.tool( 72 | "list-pods", 73 | { 74 | computeType: z 75 | .enum(["GPU", "CPU"]) 76 | .optional() 77 | .describe("Filter to only GPU or only CPU Pods"), 78 | gpuTypeId: z 79 | .array(z.string()) 80 | .optional() 81 | .describe("Filter to Pods with any of the listed GPU types"), 82 | dataCenterId: z 83 | .array(z.string()) 84 | .optional() 85 | .describe("Filter to Pods in any of the provided data centers"), 86 | name: z 87 | .string() 88 | .optional() 89 | .describe("Filter to Pods with the provided name"), 90 | includeMachine: z 91 | .boolean() 92 | .optional() 93 | .describe("Include information about the machine"), 94 | includeNetworkVolume: z 95 | .boolean() 96 | .optional() 97 | .describe("Include information about attached network volumes"), 98 | }, 99 | async (params) => { 100 | // Construct query parameters 101 | const queryParams = new URLSearchParams() 102 | 103 | if (params.computeType) 104 | queryParams.append("computeType", params.computeType) 105 | if (params.gpuTypeId) 106 | params.gpuTypeId.forEach((type) => queryParams.append("gpuTypeId", type)) 107 | if (params.dataCenterId) 108 | params.dataCenterId.forEach((dc) => 109 | queryParams.append("dataCenterId", dc) 110 | ) 111 | if (params.name) queryParams.append("name", params.name) 112 | if (params.includeMachine) 113 | queryParams.append("includeMachine", params.includeMachine.toString()) 114 | if (params.includeNetworkVolume) 115 | queryParams.append( 116 | "includeNetworkVolume", 117 | params.includeNetworkVolume.toString() 118 | ) 119 | 120 | const queryString = queryParams.toString() 121 | ? `?${queryParams.toString()}` 122 | : "" 123 | const result = await runpodRequest(`/pods${queryString}`) 124 | 125 | return { 126 | content: [ 127 | { 128 | type: "text", 129 | text: JSON.stringify(result, null, 2), 130 | }, 131 | ], 132 | } 133 | } 134 | ) 135 | 136 | // Get Pod Details 137 | server.tool( 138 | "get-pod", 139 | { 140 | podId: z.string().describe("ID of the pod to retrieve"), 141 | includeMachine: z 142 | .boolean() 143 | .optional() 144 | .describe("Include information about the machine"), 145 | includeNetworkVolume: z 146 | .boolean() 147 | .optional() 148 | .describe("Include information about attached network volumes"), 149 | }, 150 | async (params) => { 151 | // Construct query parameters 152 | const queryParams = new URLSearchParams() 153 | 154 | if (params.includeMachine) 155 | queryParams.append("includeMachine", params.includeMachine.toString()) 156 | if (params.includeNetworkVolume) 157 | queryParams.append( 158 | "includeNetworkVolume", 159 | params.includeNetworkVolume.toString() 160 | ) 161 | 162 | const queryString = queryParams.toString() 163 | ? `?${queryParams.toString()}` 164 | : "" 165 | const result = await runpodRequest(`/pods/${params.podId}${queryString}`) 166 | 167 | return { 168 | content: [ 169 | { 170 | type: "text", 171 | text: JSON.stringify(result, null, 2), 172 | }, 173 | ], 174 | } 175 | } 176 | ) 177 | 178 | // Create Pod 179 | server.tool( 180 | "create-pod", 181 | { 182 | name: z.string().optional().describe("Name for the pod"), 183 | imageName: z.string().describe("Docker image to use"), 184 | cloudType: z 185 | .enum(["SECURE", "COMMUNITY"]) 186 | .optional() 187 | .describe("SECURE or COMMUNITY cloud"), 188 | gpuTypeIds: z 189 | .array(z.string()) 190 | .optional() 191 | .describe("List of acceptable GPU types"), 192 | gpuCount: z.number().optional().describe("Number of GPUs"), 193 | containerDiskInGb: z 194 | .number() 195 | .optional() 196 | .describe("Container disk size in GB"), 197 | volumeInGb: z.number().optional().describe("Volume size in GB"), 198 | volumeMountPath: z.string().optional().describe("Path to mount the volume"), 199 | ports: z 200 | .array(z.string()) 201 | .optional() 202 | .describe("Ports to expose (e.g., '8888/http', '22/tcp')"), 203 | env: z.record(z.string()).optional().describe("Environment variables"), 204 | dataCenterIds: z 205 | .array(z.string()) 206 | .optional() 207 | .describe("List of data centers"), 208 | }, 209 | async (params) => { 210 | const result = await runpodRequest("/pods", "POST", params) 211 | 212 | return { 213 | content: [ 214 | { 215 | type: "text", 216 | text: JSON.stringify(result, null, 2), 217 | }, 218 | ], 219 | } 220 | } 221 | ) 222 | 223 | // Update Pod 224 | server.tool( 225 | "update-pod", 226 | { 227 | podId: z.string().describe("ID of the pod to update"), 228 | name: z.string().optional().describe("New name for the pod"), 229 | imageName: z.string().optional().describe("New Docker image"), 230 | containerDiskInGb: z 231 | .number() 232 | .optional() 233 | .describe("New container disk size in GB"), 234 | volumeInGb: z.number().optional().describe("New volume size in GB"), 235 | volumeMountPath: z 236 | .string() 237 | .optional() 238 | .describe("New path to mount the volume"), 239 | ports: z.array(z.string()).optional().describe("New ports to expose"), 240 | env: z.record(z.string()).optional().describe("New environment variables"), 241 | }, 242 | async (params) => { 243 | const { podId, ...updateParams } = params 244 | const result = await runpodRequest(`/pods/${podId}`, "PATCH", updateParams) 245 | 246 | return { 247 | content: [ 248 | { 249 | type: "text", 250 | text: JSON.stringify(result, null, 2), 251 | }, 252 | ], 253 | } 254 | } 255 | ) 256 | 257 | // Start Pod 258 | server.tool( 259 | "start-pod", 260 | { 261 | podId: z.string().describe("ID of the pod to start"), 262 | }, 263 | async (params) => { 264 | const result = await runpodRequest(`/pods/${params.podId}/start`, "POST") 265 | 266 | return { 267 | content: [ 268 | { 269 | type: "text", 270 | text: JSON.stringify(result, null, 2), 271 | }, 272 | ], 273 | } 274 | } 275 | ) 276 | 277 | // Stop Pod 278 | server.tool( 279 | "stop-pod", 280 | { 281 | podId: z.string().describe("ID of the pod to stop"), 282 | }, 283 | async (params) => { 284 | const result = await runpodRequest(`/pods/${params.podId}/stop`, "POST") 285 | 286 | return { 287 | content: [ 288 | { 289 | type: "text", 290 | text: JSON.stringify(result, null, 2), 291 | }, 292 | ], 293 | } 294 | } 295 | ) 296 | 297 | // Delete Pod 298 | server.tool( 299 | "delete-pod", 300 | { 301 | podId: z.string().describe("ID of the pod to delete"), 302 | }, 303 | async (params) => { 304 | const result = await runpodRequest(`/pods/${params.podId}`, "DELETE") 305 | 306 | return { 307 | content: [ 308 | { 309 | type: "text", 310 | text: JSON.stringify(result, null, 2), 311 | }, 312 | ], 313 | } 314 | } 315 | ) 316 | 317 | // ============== ENDPOINT MANAGEMENT TOOLS ============== 318 | 319 | // List Endpoints 320 | server.tool( 321 | "list-endpoints", 322 | { 323 | includeTemplate: z 324 | .boolean() 325 | .optional() 326 | .describe("Include template information"), 327 | includeWorkers: z 328 | .boolean() 329 | .optional() 330 | .describe("Include information about workers"), 331 | }, 332 | async (params) => { 333 | // Construct query parameters 334 | const queryParams = new URLSearchParams() 335 | 336 | if (params.includeTemplate) 337 | queryParams.append("includeTemplate", params.includeTemplate.toString()) 338 | if (params.includeWorkers) 339 | queryParams.append("includeWorkers", params.includeWorkers.toString()) 340 | 341 | const queryString = queryParams.toString() 342 | ? `?${queryParams.toString()}` 343 | : "" 344 | const result = await runpodRequest(`/endpoints${queryString}`) 345 | 346 | return { 347 | content: [ 348 | { 349 | type: "text", 350 | text: JSON.stringify(result, null, 2), 351 | }, 352 | ], 353 | } 354 | } 355 | ) 356 | 357 | // Get Endpoint Details 358 | server.tool( 359 | "get-endpoint", 360 | { 361 | endpointId: z.string().describe("ID of the endpoint to retrieve"), 362 | includeTemplate: z 363 | .boolean() 364 | .optional() 365 | .describe("Include template information"), 366 | includeWorkers: z 367 | .boolean() 368 | .optional() 369 | .describe("Include information about workers"), 370 | }, 371 | async (params) => { 372 | // Construct query parameters 373 | const queryParams = new URLSearchParams() 374 | 375 | if (params.includeTemplate) 376 | queryParams.append("includeTemplate", params.includeTemplate.toString()) 377 | if (params.includeWorkers) 378 | queryParams.append("includeWorkers", params.includeWorkers.toString()) 379 | 380 | const queryString = queryParams.toString() 381 | ? `?${queryParams.toString()}` 382 | : "" 383 | const result = await runpodRequest( 384 | `/endpoints/${params.endpointId}${queryString}` 385 | ) 386 | 387 | return { 388 | content: [ 389 | { 390 | type: "text", 391 | text: JSON.stringify(result, null, 2), 392 | }, 393 | ], 394 | } 395 | } 396 | ) 397 | 398 | // Create Endpoint 399 | server.tool( 400 | "create-endpoint", 401 | { 402 | name: z.string().optional().describe("Name for the endpoint"), 403 | templateId: z.string().describe("Template ID to use"), 404 | computeType: z 405 | .enum(["GPU", "CPU"]) 406 | .optional() 407 | .describe("GPU or CPU endpoint"), 408 | gpuTypeIds: z 409 | .array(z.string()) 410 | .optional() 411 | .describe("List of acceptable GPU types"), 412 | gpuCount: z.number().optional().describe("Number of GPUs per worker"), 413 | workersMin: z.number().optional().describe("Minimum number of workers"), 414 | workersMax: z.number().optional().describe("Maximum number of workers"), 415 | dataCenterIds: z 416 | .array(z.string()) 417 | .optional() 418 | .describe("List of data centers"), 419 | }, 420 | async (params) => { 421 | const result = await runpodRequest("/endpoints", "POST", params) 422 | 423 | return { 424 | content: [ 425 | { 426 | type: "text", 427 | text: JSON.stringify(result, null, 2), 428 | }, 429 | ], 430 | } 431 | } 432 | ) 433 | 434 | // Update Endpoint 435 | server.tool( 436 | "update-endpoint", 437 | { 438 | endpointId: z.string().describe("ID of the endpoint to update"), 439 | name: z.string().optional().describe("New name for the endpoint"), 440 | workersMin: z.number().optional().describe("New minimum number of workers"), 441 | workersMax: z.number().optional().describe("New maximum number of workers"), 442 | idleTimeout: z.number().optional().describe("New idle timeout in seconds"), 443 | scalerType: z 444 | .enum(["QUEUE_DELAY", "REQUEST_COUNT"]) 445 | .optional() 446 | .describe("Scaler type"), 447 | scalerValue: z.number().optional().describe("Scaler value"), 448 | }, 449 | async (params) => { 450 | const { endpointId, ...updateParams } = params 451 | const result = await runpodRequest( 452 | `/endpoints/${endpointId}`, 453 | "PATCH", 454 | updateParams 455 | ) 456 | 457 | return { 458 | content: [ 459 | { 460 | type: "text", 461 | text: JSON.stringify(result, null, 2), 462 | }, 463 | ], 464 | } 465 | } 466 | ) 467 | 468 | // Delete Endpoint 469 | server.tool( 470 | "delete-endpoint", 471 | { 472 | endpointId: z.string().describe("ID of the endpoint to delete"), 473 | }, 474 | async (params) => { 475 | const result = await runpodRequest( 476 | `/endpoints/${params.endpointId}`, 477 | "DELETE" 478 | ) 479 | 480 | return { 481 | content: [ 482 | { 483 | type: "text", 484 | text: JSON.stringify(result, null, 2), 485 | }, 486 | ], 487 | } 488 | } 489 | ) 490 | 491 | // ============== TEMPLATE MANAGEMENT TOOLS ============== 492 | 493 | // List Templates 494 | server.tool("list-templates", {}, async () => { 495 | const result = await runpodRequest("/templates") 496 | 497 | return { 498 | content: [ 499 | { 500 | type: "text", 501 | text: JSON.stringify(result, null, 2), 502 | }, 503 | ], 504 | } 505 | }) 506 | 507 | // Get Template Details 508 | server.tool( 509 | "get-template", 510 | { 511 | templateId: z.string().describe("ID of the template to retrieve"), 512 | }, 513 | async (params) => { 514 | const result = await runpodRequest(`/templates/${params.templateId}`) 515 | 516 | return { 517 | content: [ 518 | { 519 | type: "text", 520 | text: JSON.stringify(result, null, 2), 521 | }, 522 | ], 523 | } 524 | } 525 | ) 526 | 527 | // Create Template 528 | server.tool( 529 | "create-template", 530 | { 531 | name: z.string().describe("Name for the template"), 532 | imageName: z.string().describe("Docker image to use"), 533 | isServerless: z 534 | .boolean() 535 | .optional() 536 | .describe("Is this a serverless template"), 537 | ports: z.array(z.string()).optional().describe("Ports to expose"), 538 | dockerEntrypoint: z 539 | .array(z.string()) 540 | .optional() 541 | .describe("Docker entrypoint commands"), 542 | dockerStartCmd: z 543 | .array(z.string()) 544 | .optional() 545 | .describe("Docker start commands"), 546 | env: z.record(z.string()).optional().describe("Environment variables"), 547 | containerDiskInGb: z 548 | .number() 549 | .optional() 550 | .describe("Container disk size in GB"), 551 | volumeInGb: z.number().optional().describe("Volume size in GB"), 552 | volumeMountPath: z.string().optional().describe("Path to mount the volume"), 553 | readme: z.string().optional().describe("README content in markdown format"), 554 | }, 555 | async (params) => { 556 | const result = await runpodRequest("/templates", "POST", params) 557 | 558 | return { 559 | content: [ 560 | { 561 | type: "text", 562 | text: JSON.stringify(result, null, 2), 563 | }, 564 | ], 565 | } 566 | } 567 | ) 568 | 569 | // Update Template 570 | server.tool( 571 | "update-template", 572 | { 573 | templateId: z.string().describe("ID of the template to update"), 574 | name: z.string().optional().describe("New name for the template"), 575 | imageName: z.string().optional().describe("New Docker image"), 576 | ports: z.array(z.string()).optional().describe("New ports to expose"), 577 | env: z.record(z.string()).optional().describe("New environment variables"), 578 | readme: z 579 | .string() 580 | .optional() 581 | .describe("New README content in markdown format"), 582 | }, 583 | async (params) => { 584 | const { templateId, ...updateParams } = params 585 | const result = await runpodRequest( 586 | `/templates/${templateId}`, 587 | "PATCH", 588 | updateParams 589 | ) 590 | 591 | return { 592 | content: [ 593 | { 594 | type: "text", 595 | text: JSON.stringify(result, null, 2), 596 | }, 597 | ], 598 | } 599 | } 600 | ) 601 | 602 | // Delete Template 603 | server.tool( 604 | "delete-template", 605 | { 606 | templateId: z.string().describe("ID of the template to delete"), 607 | }, 608 | async (params) => { 609 | const result = await runpodRequest( 610 | `/templates/${params.templateId}`, 611 | "DELETE" 612 | ) 613 | 614 | return { 615 | content: [ 616 | { 617 | type: "text", 618 | text: JSON.stringify(result, null, 2), 619 | }, 620 | ], 621 | } 622 | } 623 | ) 624 | 625 | // ============== NETWORK VOLUME MANAGEMENT TOOLS ============== 626 | 627 | // List Network Volumes 628 | server.tool("list-network-volumes", {}, async () => { 629 | const result = await runpodRequest("/networkvolumes") 630 | 631 | return { 632 | content: [ 633 | { 634 | type: "text", 635 | text: JSON.stringify(result, null, 2), 636 | }, 637 | ], 638 | } 639 | }) 640 | 641 | // Get Network Volume Details 642 | server.tool( 643 | "get-network-volume", 644 | { 645 | networkVolumeId: z 646 | .string() 647 | .describe("ID of the network volume to retrieve"), 648 | }, 649 | async (params) => { 650 | const result = await runpodRequest( 651 | `/networkvolumes/${params.networkVolumeId}` 652 | ) 653 | 654 | return { 655 | content: [ 656 | { 657 | type: "text", 658 | text: JSON.stringify(result, null, 2), 659 | }, 660 | ], 661 | } 662 | } 663 | ) 664 | 665 | // Create Network Volume 666 | server.tool( 667 | "create-network-volume", 668 | { 669 | name: z.string().describe("Name for the network volume"), 670 | size: z.number().describe("Size in GB (1-4000)"), 671 | dataCenterId: z.string().describe("Data center ID"), 672 | }, 673 | async (params) => { 674 | const result = await runpodRequest("/networkvolumes", "POST", params) 675 | 676 | return { 677 | content: [ 678 | { 679 | type: "text", 680 | text: JSON.stringify(result, null, 2), 681 | }, 682 | ], 683 | } 684 | } 685 | ) 686 | 687 | // Update Network Volume 688 | server.tool( 689 | "update-network-volume", 690 | { 691 | networkVolumeId: z.string().describe("ID of the network volume to update"), 692 | name: z.string().optional().describe("New name for the network volume"), 693 | size: z 694 | .number() 695 | .optional() 696 | .describe("New size in GB (must be larger than current)"), 697 | }, 698 | async (params) => { 699 | const { networkVolumeId, ...updateParams } = params 700 | const result = await runpodRequest( 701 | `/networkvolumes/${networkVolumeId}`, 702 | "PATCH", 703 | updateParams 704 | ) 705 | 706 | return { 707 | content: [ 708 | { 709 | type: "text", 710 | text: JSON.stringify(result, null, 2), 711 | }, 712 | ], 713 | } 714 | } 715 | ) 716 | 717 | // Delete Network Volume 718 | server.tool( 719 | "delete-network-volume", 720 | { 721 | networkVolumeId: z.string().describe("ID of the network volume to delete"), 722 | }, 723 | async (params) => { 724 | const result = await runpodRequest( 725 | `/networkvolumes/${params.networkVolumeId}`, 726 | "DELETE" 727 | ) 728 | 729 | return { 730 | content: [ 731 | { 732 | type: "text", 733 | text: JSON.stringify(result, null, 2), 734 | }, 735 | ], 736 | } 737 | } 738 | ) 739 | 740 | // ============== CONTAINER REGISTRY AUTH TOOLS ============== 741 | 742 | // List Container Registry Auths 743 | server.tool("list-container-registry-auths", {}, async () => { 744 | const result = await runpodRequest("/containerregistryauth") 745 | 746 | return { 747 | content: [ 748 | { 749 | type: "text", 750 | text: JSON.stringify(result, null, 2), 751 | }, 752 | ], 753 | } 754 | }) 755 | 756 | // Get Container Registry Auth Details 757 | server.tool( 758 | "get-container-registry-auth", 759 | { 760 | containerRegistryAuthId: z 761 | .string() 762 | .describe("ID of the container registry auth to retrieve"), 763 | }, 764 | async (params) => { 765 | const result = await runpodRequest( 766 | `/containerregistryauth/${params.containerRegistryAuthId}` 767 | ) 768 | 769 | return { 770 | content: [ 771 | { 772 | type: "text", 773 | text: JSON.stringify(result, null, 2), 774 | }, 775 | ], 776 | } 777 | } 778 | ) 779 | 780 | // Create Container Registry Auth 781 | server.tool( 782 | "create-container-registry-auth", 783 | { 784 | name: z.string().describe("Name for the container registry auth"), 785 | username: z.string().describe("Registry username"), 786 | password: z.string().describe("Registry password"), 787 | }, 788 | async (params) => { 789 | const result = await runpodRequest("/containerregistryauth", "POST", params) 790 | 791 | return { 792 | content: [ 793 | { 794 | type: "text", 795 | text: JSON.stringify(result, null, 2), 796 | }, 797 | ], 798 | } 799 | } 800 | ) 801 | 802 | // Delete Container Registry Auth 803 | server.tool( 804 | "delete-container-registry-auth", 805 | { 806 | containerRegistryAuthId: z 807 | .string() 808 | .describe("ID of the container registry auth to delete"), 809 | }, 810 | async (params) => { 811 | const result = await runpodRequest( 812 | `/containerregistryauth/${params.containerRegistryAuthId}`, 813 | "DELETE" 814 | ) 815 | 816 | return { 817 | content: [ 818 | { 819 | type: "text", 820 | text: JSON.stringify(result, null, 2), 821 | }, 822 | ], 823 | } 824 | } 825 | ) 826 | 827 | // Start receiving messages on stdin and sending messages on stdout 828 | const transport = new StdioServerTransport() 829 | server.connect(transport) 830 | ```