#
tokens: 16359/50000 18/18 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | [![smithery badge](https://smithery.ai/badge/@runpod/runpod-mcp-ts)](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 | 
```