# Directory Structure
```
├── .changeset
│ └── config.json
├── .cursor
│ └── mcp.json
├── .github
│ ├── CONTRIBUTING.md
│ └── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── Dockerfile
├── docs
│ └── context.md
├── eslint.config.mjs
├── LICENSE
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── README.md
├── smithery.yaml
├── src
│ └── index.ts
├── tsconfig.build.json
├── tsconfig.json
└── tsup.config.ts
```
# Files
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
```
1 | {
2 | "semi": true,
3 | "trailingComma": "es5",
4 | "singleQuote": true,
5 | "printWidth": 80,
6 | "tabWidth": 2,
7 | "useTabs": false
8 | }
9 |
10 |
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | build/
2 | dist/
3 | .changeset/
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 |
13 | # Diagnostic reports (https://nodejs.org/api/report.html)
14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
15 |
16 | # Runtime data
17 | pids
18 | *.pid
19 | *.seed
20 | *.pid.lock
21 |
22 | # Directory for instrumented libs generated by jscoverage/JSCover
23 | lib-cov
24 |
25 | # Coverage directory used by tools like istanbul
26 | coverage
27 | *.lcov
28 |
29 | # nyc test coverage
30 | .nyc_output
31 |
32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
33 | .grunt
34 |
35 | # Bower dependency directory (https://bower.io/)
36 | bower_components
37 |
38 | # node-waf configuration
39 | .lock-wscript
40 |
41 | # Compiled binary addons (https://nodejs.org/api/addons.html)
42 | build/Release
43 |
44 | # Dependency directories
45 | node_modules/
46 | jspm_packages/
47 |
48 | # Snowpack dependency directory (https://snowpack.dev/)
49 | web_modules/
50 |
51 | # TypeScript cache
52 | *.tsbuildinfo
53 |
54 | # Optional npm cache directory
55 | .npm
56 |
57 | # Optional eslint cache
58 | .eslintcache
59 |
60 | # Optional stylelint cache
61 | .stylelintcache
62 |
63 | # Optional REPL history
64 | .node_repl_history
65 |
66 | # Output of 'npm pack'
67 | *.tgz
68 |
69 | # Yarn Integrity file
70 | .yarn-integrity
71 |
72 | # dotenv environment variable files
73 | .env
74 | .env.*
75 | !.env.example
76 |
77 | # parcel-bundler cache (https://parceljs.org/)
78 | .cache
79 | .parcel-cache
80 |
81 | # Next.js build output
82 | .next
83 | out
84 |
85 | # Nuxt.js build / generate output
86 | .nuxt
87 | dist
88 | .output
89 |
90 | # Gatsby files
91 | .cache/
92 | # Comment in the public line in if your project uses Gatsby and not Next.js
93 | # https://nextjs.org/blog/next-9-1#public-directory-support
94 | # public
95 |
96 | # vuepress build output
97 | .vuepress/dist
98 |
99 | # vuepress v2.x temp and cache directory
100 | .temp
101 | .cache
102 |
103 | # Sveltekit cache directory
104 | .svelte-kit/
105 |
106 | # vitepress build output
107 | **/.vitepress/dist
108 |
109 | # vitepress cache directory
110 | **/.vitepress/cache
111 |
112 | # Docusaurus cache and generated files
113 | .docusaurus
114 |
115 | # Serverless directories
116 | .serverless/
117 |
118 | # FuseBox cache
119 | .fusebox/
120 |
121 | # DynamoDB Local files
122 | .dynamodb/
123 |
124 | # Firebase cache directory
125 | .firebase/
126 |
127 | # TernJS port file
128 | .tern-port
129 |
130 | # Stores VSCode versions used for testing VSCode extensions
131 | .vscode-test
132 |
133 | # yarn v3
134 | .pnp.*
135 | .yarn/*
136 | !.yarn/patches
137 | !.yarn/plugins
138 | !.yarn/releases
139 | !.yarn/sdks
140 | !.yarn/versions
141 |
142 | # Vite files
143 | vite.config.js.timestamp-*
144 | vite.config.ts.timestamp-*
145 | .vite/
146 |
```
--------------------------------------------------------------------------------
/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 | ## Quick Start
17 |
18 | ### Prerequisites
19 |
20 | - Node.js 18 or higher
21 | - A Runpod account and API key ([get your API key](https://www.runpod.io/console/user/settings))
22 |
23 | ### Running with npx
24 |
25 | You can run the server directly without installation:
26 |
27 | ```bash
28 | RUNPOD_API_KEY=your_api_key_here npx @runpod/mcp-server@latest
29 | ```
30 |
31 | ### Installing via Smithery
32 |
33 | To install for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@runpod/runpod-mcp-ts):
34 |
35 | ```bash
36 | npx -y @smithery/cli install @runpod/runpod-mcp-ts --client claude
37 | ```
38 |
39 | ## Setting up with Claude for Desktop
40 |
41 | 1. Open Claude for Desktop
42 | 2. Edit the config file:
43 | - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
44 | - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
45 | 3. Add the server configuration:
46 |
47 | ```json
48 | {
49 | "mcpServers": {
50 | "runpod": {
51 | "command": "npx",
52 | "args": ["@runpod/mcp-server@latest"],
53 | "env": {
54 | "RUNPOD_API_KEY": "your_api_key_here"
55 | }
56 | }
57 | }
58 | }
59 | ```
60 |
61 | 4. Restart Claude for Desktop
62 |
63 | ## Usage Examples
64 |
65 | ### List all pods
66 |
67 | ```
68 | Can you list all my Runpod pods?
69 | ```
70 |
71 | ### Create a new pod
72 |
73 | ```
74 | Create a new Runpod pod with the following specifications:
75 | - Name: test-pod
76 | - Image: runpod/pytorch:2.1.0-py3.10-cuda11.8.0-devel-ubuntu22.04
77 | - GPU Type: NVIDIA GeForce RTX 4090
78 | - GPU Count: 1
79 | ```
80 |
81 | ### Create a serverless endpoint
82 |
83 | ```
84 | Create a Runpod serverless endpoint with the following configuration:
85 | - Name: my-endpoint
86 | - Template ID: 30zmvf89kd
87 | - Minimum workers: 0
88 | - Maximum workers: 3
89 | ```
90 |
91 | ## Security Considerations
92 |
93 | This server requires your Runpod API key, which grants full access to your Runpod account. For security:
94 |
95 | - Never share your API key
96 | - Be cautious about what operations you perform
97 | - Consider setting up a separate API key with limited permissions
98 | - Don't use this in a production environment without proper security measures
99 |
100 | ## License
101 |
102 | Apache-2.0
103 |
```
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
```markdown
1 | # Contributing to @runpod/mcp-server
2 |
3 | Thank you for your interest in contributing to the RunPod MCP Server! This document outlines the process for making releases and contributing to the project.
4 |
5 | ## Development Setup
6 |
7 | 1. **Clone the repository**
8 |
9 | ```bash
10 | git clone https://github.com/runpod/mcp-server.git
11 | cd mcp-server
12 | ```
13 |
14 | 2. **Install dependencies**
15 |
16 | ```bash
17 | pnpm install
18 | ```
19 |
20 | 3. **Build the project**
21 | ```bash
22 | pnpm build
23 | ```
24 |
25 | 4. **Run the server**
26 | ```bash
27 | pnpm start
28 | ```
29 |
30 | ## Making Changes
31 |
32 | 1. **Create a feature branch**
33 |
34 | ```bash
35 | git checkout -b feature/your-feature-name
36 | ```
37 |
38 | 2. **Make your changes**
39 |
40 | - Write code following the existing patterns
41 | - Update documentation as needed
42 | - Ensure code passes linting and type checking
43 |
44 | 3. **Test your changes**
45 | ```bash
46 | pnpm type-check
47 | pnpm lint
48 | pnpm build
49 | ```
50 |
51 | ## Release Process
52 |
53 | We use [Changesets](https://github.com/changesets/changesets) to manage versioning and releases. Here's how to create a release:
54 |
55 | ### 1. Create a Changeset
56 |
57 | When you make changes that should be released, create a changeset:
58 |
59 | ```bash
60 | pnpm changeset
61 | ```
62 |
63 | This will prompt you to:
64 |
65 | - Select which packages have changed (select `@runpod/mcp-server`)
66 | - Choose the type of change:
67 | - **patch**: Bug fixes, small improvements
68 | - **minor**: New features, backwards compatible changes
69 | - **major**: Breaking changes
70 | - Write a summary of the changes
71 |
72 | Example changeset creation:
73 |
74 | ```bash
75 | $ pnpm changeset
76 | 🦋 Which packages would you like to include? · @runpod/mcp-server
77 | 🦋 Which packages should have a major bump? · No items were selected
78 | 🦋 Which packages should have a minor bump? · @runpod/mcp-server
79 | 🦋 Please enter a summary for this change (this will be in the CHANGELOG).
80 | 🦋 (submit empty line to open external editor)
81 | 🦋 Summary › Add support for new API endpoints
82 | ```
83 |
84 | This creates a markdown file in `.changeset/` describing your changes.
85 |
86 | ### 2. Commit and Push
87 |
88 | ```bash
89 | git add .
90 | git commit -m "feat: add new API endpoints"
91 | git push origin feature/your-feature-name
92 | ```
93 |
94 | ### 3. Create Pull Request
95 |
96 | Create a PR to merge your changes into `main`. Include:
97 |
98 | - Clear description of changes
99 | - Link to any relevant issues
100 | - Verification that build passes
101 |
102 | ### 4. Automated Release
103 |
104 | Once your PR is merged to `main`, our GitHub Actions workflow will:
105 |
106 | 1. **Create a Version PR**: The workflow creates a PR with:
107 |
108 | - Updated version numbers
109 | - Generated CHANGELOG.md entries
110 | - Consumed changeset files
111 |
112 | 2. **Publish Release**: When the version PR is merged:
113 | - Package is built
114 | - Published to npm
115 | - GitHub release is created
116 |
117 | ## Release Types
118 |
119 | - **Patch (0.1.0 → 0.1.1)**: Bug fixes, documentation updates
120 | - **Minor (0.1.0 → 0.2.0)**: New features, improvements
121 | - **Major (0.1.0 → 1.0.0)**: Breaking changes
122 |
123 | ## Manual Release (Emergency)
124 |
125 | If you need to publish manually:
126 |
127 | ```bash
128 | # Update versions and generate changelog
129 | pnpm changeset:version
130 |
131 | # Build
132 | pnpm build
133 |
134 | # Publish to npm
135 | pnpm changeset:publish
136 | ```
137 |
138 | ## Best Practices
139 |
140 | 1. **Always create changesets** for user-facing changes
141 | 2. **Write clear changeset summaries** - they become changelog entries
142 | 3. **Test thoroughly** before creating PRs
143 | 4. **Follow semantic versioning** when choosing change types
144 | 5. **Keep PRs focused** - one feature/fix per PR
145 |
146 | ## Questions?
147 |
148 | If you have questions about the release process or contributing:
149 |
150 | - Open an issue on GitHub
151 | - Check existing issues for similar questions
152 | - Review the [Changesets documentation](https://github.com/changesets/changesets)
153 |
154 |
```
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "composite": false
5 | }
6 | }
7 |
8 |
```
--------------------------------------------------------------------------------
/.cursor/mcp.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "mcpServers": {
3 | "runpod": {
4 | "command": "node",
5 | "args": ["build/index.js"],
6 | "env": {
7 | "RUNPOD_API_KEY": "${RUNPOD_API_KEY}"
8 | }
9 | }
10 | }
11 | }
12 |
```
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "public",
8 | "baseBranch": "main",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
12 |
13 |
```
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
1 | # @runpod/mcp-server
2 |
3 | ## 1.1.0
4 |
5 | ### Minor Changes
6 |
7 | - c49b2cc: Add npx support for running the MCP server directly without installation. Includes bin field in package.json, shebang in build output, and updated README with npx command. Also fixes branding consistency (RunPod -> Runpod) and adds development conventions documentation.
8 |
```
--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { defineConfig } from 'tsup';
2 |
3 | export default defineConfig([
4 | {
5 | entry: ['src/index.ts'],
6 | format: ['cjs', 'esm'],
7 | dts: true,
8 | sourcemap: true,
9 | banner: {
10 | js: '#!/usr/bin/env node',
11 | },
12 | outExtension({ format }) {
13 | return {
14 | js: format === 'cjs' ? '.js' : '.mjs',
15 | };
16 | },
17 | },
18 | ]);
19 |
```
--------------------------------------------------------------------------------
/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 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "ESNext",
5 | "moduleResolution": "node",
6 | "declaration": true,
7 | "declarationMap": true,
8 | "sourceMap": true,
9 | "strict": true,
10 | "esModuleInterop": true,
11 | "skipLibCheck": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "rootDir": "src",
14 | "outDir": "dist",
15 | "composite": true
16 | },
17 | "include": ["src/**/*"],
18 | "exclude": ["dist", "build", "node_modules", "tsup.config.ts", "**/*.test.ts"]
19 | }
20 |
```
--------------------------------------------------------------------------------
/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: ['dist/index.js'],
20 | env: { RUNPOD_API_KEY: config.runpodApiKey }
21 | })
22 | exampleConfig:
23 | runpodApiKey: your_dummy_runpod_api_key_here
24 |
```
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
```
1 | import tseslint from '@typescript-eslint/eslint-plugin';
2 | import tsparser from '@typescript-eslint/parser';
3 |
4 | export default [
5 | {
6 | files: ['**/*.ts', '**/*.tsx'],
7 | languageOptions: {
8 | parser: tsparser,
9 | parserOptions: {
10 | ecmaVersion: 2020,
11 | sourceType: 'module',
12 | },
13 | },
14 | plugins: {
15 | '@typescript-eslint': tseslint,
16 | },
17 | rules: {
18 | '@typescript-eslint/no-unused-vars': [
19 | 'error',
20 | { argsIgnorePattern: '^_' },
21 | ],
22 | '@typescript-eslint/no-explicit-any': 'warn',
23 | 'no-console': 'off',
24 | 'prefer-const': 'error',
25 | 'no-var': 'error',
26 | },
27 | },
28 | {
29 | ignores: ['dist/**', 'build/**', 'node_modules/**', '*.js', '*.mjs'],
30 | },
31 | ];
32 |
33 |
```
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | test:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [18, 20, 22]
16 |
17 | steps:
18 | - name: Checkout code
19 | uses: actions/checkout@v4
20 |
21 | - name: Setup pnpm
22 | uses: pnpm/action-setup@v4
23 | with:
24 | version: latest
25 |
26 | - name: Setup Node.js ${{ matrix.node-version }}
27 | uses: actions/setup-node@v4
28 | with:
29 | node-version: ${{ matrix.node-version }}
30 | cache: "pnpm"
31 |
32 | - name: Install dependencies
33 | run: pnpm install --frozen-lockfile
34 |
35 | - name: Type check
36 | run: pnpm run type-check
37 |
38 | - name: Lint
39 | run: pnpm run lint
40 |
41 | - name: Build
42 | run: pnpm run build
43 |
44 |
```
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | concurrency: ${{ github.workflow }}-${{ github.ref }}
9 |
10 | permissions:
11 | contents: write
12 | pull-requests: write
13 | id-token: write # Required for OIDC
14 |
15 | jobs:
16 | release:
17 | name: Release
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Checkout Repo
21 | uses: actions/checkout@v4
22 |
23 | - name: Setup pnpm
24 | uses: pnpm/action-setup@v4
25 | with:
26 | version: latest
27 |
28 | - name: Setup Node.js
29 | uses: actions/setup-node@v4
30 | with:
31 | node-version: 20
32 | registry-url: https://registry.npmjs.org
33 |
34 | - name: Ensure recent npm (OIDC + provenance support)
35 | run: npm i -g npm@latest
36 |
37 | - name: Install Dependencies
38 | run: pnpm install --frozen-lockfile
39 |
40 | - name: Build package
41 | run: pnpm run build
42 |
43 | - name: Create Release Pull Request or Publish to npm (OIDC)
44 | id: changesets
45 | uses: changesets/action@v1
46 | with:
47 | publish: pnpm changeset:publish
48 | version: pnpm changeset:version
49 | commit: 'chore: version packages'
50 | title: 'chore: version packages'
51 | env:
52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53 | NPM_CONFIG_ACCESS: public # For scoped public packages
54 |
55 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@runpod/mcp-server",
3 | "version": "1.1.0",
4 | "description": "MCP server for interacting with RunPod API",
5 | "license": "Apache-2.0",
6 | "type": "module",
7 | "main": "./dist/index.js",
8 | "module": "./dist/index.mjs",
9 | "types": "./dist/index.d.ts",
10 | "bin": {
11 | "runpod-mcp": "./dist/index.mjs"
12 | },
13 | "files": [
14 | "dist/**/*",
15 | "CHANGELOG.md"
16 | ],
17 | "scripts": {
18 | "build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
19 | "build:watch": "pnpm clean && tsup --watch",
20 | "clean": "rm -rf dist *.tsbuildinfo",
21 | "lint": "eslint \"./**/*.ts*\"",
22 | "type-check": "tsc --build",
23 | "prettier-check": "prettier --check \"./**/*.ts*\"",
24 | "start": "node dist/index.js",
25 | "dev": "tsx src/index.ts",
26 | "changeset": "changeset",
27 | "changeset:version": "changeset version",
28 | "changeset:publish": "changeset publish"
29 | },
30 | "exports": {
31 | "./package.json": "./package.json",
32 | ".": {
33 | "types": "./dist/index.d.ts",
34 | "import": "./dist/index.mjs",
35 | "require": "./dist/index.js"
36 | }
37 | },
38 | "dependencies": {
39 | "@modelcontextprotocol/sdk": "^1.7.0",
40 | "node-fetch": "^3.3.2",
41 | "zod": "^3.22.4"
42 | },
43 | "devDependencies": {
44 | "@changesets/cli": "^2.29.6",
45 | "@types/node": "^20.10.5",
46 | "@typescript-eslint/eslint-plugin": "^8.39.1",
47 | "@typescript-eslint/parser": "^8.39.1",
48 | "eslint": "^9.33.0",
49 | "prettier": "^3.6.2",
50 | "tsup": "^8.0.0",
51 | "tsx": "^4.7.0",
52 | "typescript": "^5.3.3"
53 | },
54 | "engines": {
55 | "node": ">=18"
56 | },
57 | "publishConfig": {
58 | "access": "public"
59 | },
60 | "homepage": "https://runpod.io",
61 | "repository": {
62 | "type": "git",
63 | "url": "git+https://github.com/runpod/runpod-mcp.git"
64 | },
65 | "bugs": {
66 | "url": "https://github.com/runpod/runpod-mcp/issues"
67 | },
68 | "keywords": [
69 | "mcp",
70 | "model-context-protocol",
71 | "runpod",
72 | "server",
73 | "api"
74 | ]
75 | }
76 |
```
--------------------------------------------------------------------------------
/docs/context.md:
--------------------------------------------------------------------------------
```markdown
1 | # Runpod MCP Server - Development Conventions
2 |
3 | This document outlines the core conventions, rules, and best practices for developing and maintaining the Runpod MCP Server.
4 |
5 | ## What is this Project?
6 |
7 | ### The Model Context Protocol
8 |
9 | The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open protocol that enables AI assistants to securely access external data sources and tools. MCP servers expose capabilities through standardized interfaces that MCP clients (like Claude Desktop) can consume.
10 |
11 | ### Our Server
12 |
13 | The **Runpod MCP Server** (`@runpod/mcp-server`) is an MCP server implementation that exposes Runpod's REST API as MCP tools. It enables AI assistants to manage Runpod infrastructure (pods, endpoints, templates, volumes) through natural language interactions.
14 |
15 | ### How it Works
16 |
17 | The server runs as a stdio-based MCP server that:
18 | - Accepts tool calls from MCP clients via stdin
19 | - Makes authenticated requests to Runpod's REST API (`https://rest.runpod.io/v1`)
20 | - Returns structured responses via stdout
21 | - Uses Zod schemas for input validation
22 |
23 | ### Value Proposition
24 |
25 | - Natural Language Control: Manage Runpod infrastructure through AI assistants
26 | - Standardized Interface: MCP protocol ensures compatibility across clients
27 | - Full API Coverage: Exposes all major Runpod management operations
28 | - Type Safety: Zod schemas provide runtime validation and type inference
29 |
30 | ## Project Structure
31 |
32 | ```
33 | runpod-mcp/
34 | ├── src/ # Source code
35 | │ └── index.ts # Single-file server implementation
36 | ├── docs/ # Documentation
37 | ├── dist/ # Build output (generated)
38 | ├── smithery.yaml # Smithery deployment config
39 | └── .changeset/ # Changesets for versioning
40 | ```
41 |
42 | ## Core Principles
43 |
44 | ### 1. Branding & Naming
45 |
46 | - Always use "Runpod" (not "RunPod", "runpod", or "run-pod") in user-facing text
47 | - Package name: `@runpod/mcp-server`
48 | - Server name: "Runpod API Server"
49 | - Environment variable: `RUNPOD_API_KEY`
50 |
51 | ### 2. API Structure
52 |
53 | - Base URL: `https://rest.runpod.io/v1`
54 | - Authentication: Bearer token via `Authorization` header
55 | - Request format: JSON for POST/PATCH body, query params for filters
56 | - Response format: JSON with error handling for non-JSON responses
57 |
58 | ### 3. Tool Organization
59 |
60 | Tools are organized by resource type:
61 | - Pod Management: `list-pods`, `get-pod`, `create-pod`, `update-pod`, `start-pod`, `stop-pod`, `delete-pod`
62 | - Endpoint Management: `list-endpoints`, `get-endpoint`, `create-endpoint`, `update-endpoint`, `delete-endpoint`
63 | - Template Management: `list-templates`, `get-template`, `create-template`, `update-template`, `delete-template`
64 | - Network Volume Management: `list-network-volumes`, `get-network-volume`, `create-network-volume`, `update-network-volume`, `delete-network-volume`
65 | - Container Registry Auth: `list-container-registry-auths`, `get-container-registry-auth`, `create-container-registry-auth`, `delete-container-registry-auth`
66 |
67 | ### 4. Error Handling
68 |
69 | - Always return structured error messages with HTTP status codes
70 | - Handle non-JSON responses gracefully
71 | - Log errors to stderr for debugging
72 | - Return JSON responses wrapped in MCP content format
73 |
74 | ## Development Workflow
75 |
76 | ### Package Management
77 |
78 | - Use pnpm as the primary package manager
79 | - Maintain `pnpm-lock.yaml` (not `package-lock.json`)
80 | - Support all package managers in documentation
81 |
82 | ### Version Management
83 |
84 | - Use Changesets for version management and releases
85 | - Never manually edit version numbers or `CHANGELOG.md`
86 | - Create changesets with: `pnpm changeset`
87 | - Automated releases via GitHub Actions
88 |
89 | ### Code Quality
90 |
91 | - ESLint: Use `eslint.config.mjs` for Node 18+ compatibility
92 | - Prettier: Single quotes, 2 spaces, trailing commas
93 | - TypeScript: Strict mode, full type safety
94 | - Single-file architecture: Keep implementation in `src/index.ts` for simplicity
95 |
96 | ## Code Style
97 |
98 | ### TypeScript
99 |
100 | ```typescript
101 | // Good - Consistent naming and structure
102 | server.tool(
103 | 'list-pods',
104 | {
105 | computeType: z.enum(['GPU', 'CPU']).optional().describe('Filter to only GPU or only CPU Pods'),
106 | // ... more params
107 | },
108 | async (params) => {
109 | const result = await runpodRequest(`/pods${queryString}`);
110 | return {
111 | content: [
112 | {
113 | type: 'text',
114 | text: JSON.stringify(result, null, 2),
115 | },
116 | ],
117 | };
118 | }
119 | );
120 | ```
121 |
122 | ### Request Helper Pattern
123 |
124 | ```typescript
125 | // Good - Centralized request handling
126 | async function runpodRequest(
127 | endpoint: string,
128 | method: string = 'GET',
129 | body?: Record<string, unknown>
130 | ) {
131 | const url = `${API_BASE_URL}${endpoint}`;
132 | const headers = {
133 | Authorization: `Bearer ${API_KEY}`,
134 | 'Content-Type': 'application/json',
135 | };
136 | // ... error handling
137 | }
138 | ```
139 |
140 | ### Query Parameter Construction
141 |
142 | ```typescript
143 | // Good - Build query params conditionally
144 | const queryParams = new URLSearchParams();
145 | if (params.computeType) queryParams.append('computeType', params.computeType);
146 | if (params.gpuTypeId) {
147 | params.gpuTypeId.forEach((type) => queryParams.append('gpuTypeId', type));
148 | }
149 | const queryString = queryParams.toString() ? `?${queryParams.toString()}` : '';
150 | ```
151 |
152 | ## Testing Standards
153 |
154 | ### Manual Testing
155 |
156 | - Test each tool with Claude Desktop or MCP client
157 | - Verify error handling with invalid inputs
158 | - Test query parameter combinations
159 | - Validate response format matches MCP spec
160 |
161 | ### Development Testing
162 |
163 | ```bash
164 | # Build and run locally
165 | pnpm build
166 | pnpm start
167 |
168 | # Or run directly with tsx
169 | pnpm dev
170 | ```
171 |
172 | ## Documentation
173 |
174 | ### README Structure
175 |
176 | 1. Title & Badge (Smithery)
177 | 2. Features (list of tool categories)
178 | 3. Setup (prerequisites, installation, configuration)
179 | 4. Smithery Installation (automated setup)
180 | 5. Manual Setup (Claude Desktop config)
181 | 6. Usage Examples (natural language examples)
182 | 7. Security Considerations
183 |
184 | ### Code Comments
185 |
186 | ```typescript
187 | // Good - Explain the "why", not the "what"
188 | // Some endpoints might not return JSON
189 | const contentType = response.headers.get('content-type');
190 | if (contentType && contentType.includes('application/json')) {
191 | return await response.json();
192 | }
193 | ```
194 |
195 | ## Release Management
196 |
197 | - Use Changesets for all releases
198 | - Never manually edit version numbers or `CHANGELOG.md`
199 | - Create changeset for any user-facing changes with `pnpm changeset`
200 | - GitHub Actions handles versioning and publishing automatically
201 |
202 | ## Common Patterns
203 |
204 | ### Tool Definition Pattern
205 |
206 | ```typescript
207 | server.tool(
208 | 'resource-action',
209 | {
210 | resourceId: z.string().describe('ID of the resource'),
211 | optionalParam: z.string().optional().describe('Optional parameter'),
212 | },
213 | async (params) => {
214 | const { resourceId, ...rest } = params;
215 | const result = await runpodRequest(`/resources/${resourceId}`, 'POST', rest);
216 | return {
217 | content: [
218 | {
219 | type: 'text',
220 | text: JSON.stringify(result, null, 2),
221 | },
222 | ],
223 | };
224 | }
225 | );
226 | ```
227 |
228 | ### Environment Variable Loading
229 |
230 | ```typescript
231 | // Good - Fail fast on missing API key
232 | const API_KEY = process.env.RUNPOD_API_KEY;
233 | if (!API_KEY) {
234 | console.error('RUNPOD_API_KEY environment variable is required');
235 | process.exit(1);
236 | }
237 | ```
238 |
239 | ## Common Pitfalls
240 |
241 | ### Don't Do This
242 |
243 | ```typescript
244 | // Bad - Inconsistent naming
245 | const apiKey = process.env.runpod_api_key;
246 |
247 | // Bad - Missing error context
248 | throw new Error('API error');
249 |
250 | // Bad - Hardcoded values
251 | const url = 'https://rest.runpod.io/v1/pods';
252 |
253 | // Bad - Not handling non-JSON responses
254 | return await response.json();
255 | ```
256 |
257 | ### Do This Instead
258 |
259 | ```typescript
260 | // Good - Consistent naming
261 | const API_KEY = process.env.RUNPOD_API_KEY;
262 |
263 | // Good - Clear error with context
264 | throw new Error(`Runpod API Error: ${response.status} - ${errorText}`);
265 |
266 | // Good - Use constant
267 | const API_BASE_URL = 'https://rest.runpod.io/v1';
268 | const url = `${API_BASE_URL}${endpoint}`;
269 |
270 | // Good - Handle different response types
271 | const contentType = response.headers.get('content-type');
272 | if (contentType && contentType.includes('application/json')) {
273 | return await response.json();
274 | }
275 | return { success: true, status: response.status };
276 | ```
277 |
278 | ## Deployment
279 |
280 | ### Smithery Integration
281 |
282 | - Configuration defined in `smithery.yaml`
283 | - Supports stdio transport
284 | - Requires `runpodApiKey` in config schema
285 | - Command function generates node command with env vars
286 |
287 | ### Manual Deployment
288 |
289 | - Build: `pnpm build`
290 | - Run: `node dist/index.js`
291 | - Requires `RUNPOD_API_KEY` environment variable
292 | - Compatible with any MCP client supporting stdio transport
293 |
294 | ## Development Commands
295 |
296 | ```bash
297 | # Development
298 | pnpm dev # Run with tsx (watch mode)
299 | pnpm build # Production build
300 | pnpm start # Run built server
301 |
302 | # Code Quality
303 | pnpm lint # ESLint checking
304 | pnpm type-check # TypeScript checking
305 | pnpm prettier-check # Prettier checking
306 |
307 | # Release Management
308 | pnpm changeset # Create changeset
309 | pnpm changeset:version # Update versions
310 | pnpm changeset:publish # Publish to npm
311 | ```
312 |
313 | ---
314 |
315 | **Remember**: These conventions exist to maintain consistency, quality, and ease of maintenance. When in doubt, follow the existing patterns in the codebase.
316 |
317 |
```
--------------------------------------------------------------------------------
/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, { type RequestInit as NodeFetchRequestInit } 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?: Record<string, unknown>
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: NodeFetchRequestInit = {
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(
790 | '/containerregistryauth',
791 | 'POST',
792 | params
793 | );
794 |
795 | return {
796 | content: [
797 | {
798 | type: 'text',
799 | text: JSON.stringify(result, null, 2),
800 | },
801 | ],
802 | };
803 | }
804 | );
805 |
806 | // Delete Container Registry Auth
807 | server.tool(
808 | 'delete-container-registry-auth',
809 | {
810 | containerRegistryAuthId: z
811 | .string()
812 | .describe('ID of the container registry auth to delete'),
813 | },
814 | async (params) => {
815 | const result = await runpodRequest(
816 | `/containerregistryauth/${params.containerRegistryAuthId}`,
817 | 'DELETE'
818 | );
819 |
820 | return {
821 | content: [
822 | {
823 | type: 'text',
824 | text: JSON.stringify(result, null, 2),
825 | },
826 | ],
827 | };
828 | }
829 | );
830 |
831 | // Start receiving messages on stdin and sending messages on stdout
832 | const transport = new StdioServerTransport();
833 | server.connect(transport);
834 |
```