#
tokens: 11587/50000 18/18 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false
}


```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
build/
dist/
.changeset/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.*
!.env.example

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist
.output

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Sveltekit cache directory
.svelte-kit/

# vitepress build output
**/.vitepress/dist

# vitepress cache directory
**/.vitepress/cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# Firebase cache directory
.firebase/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v3
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# Vite files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vite/

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Runpod MCP Server
[![smithery badge](https://smithery.ai/badge/@runpod/runpod-mcp-ts)](https://smithery.ai/server/@runpod/runpod-mcp-ts)

This Model Context Protocol (MCP) server enables you to interact with the Runpod REST API through Claude or other MCP-compatible clients.

## Features

The server provides tools for managing:

- **Pods**: Create, list, get details, update, start, stop, and delete pods
- **Endpoints**: Create, list, get details, update, and delete serverless endpoints
- **Templates**: Create, list, get details, update, and delete templates
- **Network Volumes**: Create, list, get details, update, and delete network volumes
- **Container Registry Authentications**: Create, list, get details, and delete authentications

## Quick Start

### Prerequisites

- Node.js 18 or higher
- A Runpod account and API key ([get your API key](https://www.runpod.io/console/user/settings))

### Running with npx

You can run the server directly without installation:

```bash
RUNPOD_API_KEY=your_api_key_here npx @runpod/mcp-server@latest
```

### Installing via Smithery

To install for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@runpod/runpod-mcp-ts):

```bash
npx -y @smithery/cli install @runpod/runpod-mcp-ts --client claude
```

## Setting up with Claude for Desktop

1. Open Claude for Desktop
2. Edit the config file:
   - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
   - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
3. Add the server configuration:

```json
{
  "mcpServers": {
    "runpod": {
      "command": "npx",
      "args": ["@runpod/mcp-server@latest"],
      "env": {
        "RUNPOD_API_KEY": "your_api_key_here"
      }
    }
  }
}
```

4. Restart Claude for Desktop

## Usage Examples

### List all pods

```
Can you list all my Runpod pods?
```

### Create a new pod

```
Create a new Runpod pod with the following specifications:
- Name: test-pod
- Image: runpod/pytorch:2.1.0-py3.10-cuda11.8.0-devel-ubuntu22.04
- GPU Type: NVIDIA GeForce RTX 4090
- GPU Count: 1
```

### Create a serverless endpoint

```
Create a Runpod serverless endpoint with the following configuration:
- Name: my-endpoint
- Template ID: 30zmvf89kd
- Minimum workers: 0
- Maximum workers: 3
```

## Security Considerations

This server requires your Runpod API key, which grants full access to your Runpod account. For security:

- Never share your API key
- Be cautious about what operations you perform
- Consider setting up a separate API key with limited permissions
- Don't use this in a production environment without proper security measures

## License

Apache-2.0

```

--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
# Contributing to @runpod/mcp-server

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.

## Development Setup

1. **Clone the repository**

   ```bash
   git clone https://github.com/runpod/mcp-server.git
   cd mcp-server
   ```

2. **Install dependencies**

   ```bash
   pnpm install
   ```

3. **Build the project**
   ```bash
   pnpm build
   ```

4. **Run the server**
   ```bash
   pnpm start
   ```

## Making Changes

1. **Create a feature branch**

   ```bash
   git checkout -b feature/your-feature-name
   ```

2. **Make your changes**

   - Write code following the existing patterns
   - Update documentation as needed
   - Ensure code passes linting and type checking

3. **Test your changes**
   ```bash
   pnpm type-check
   pnpm lint
   pnpm build
   ```

## Release Process

We use [Changesets](https://github.com/changesets/changesets) to manage versioning and releases. Here's how to create a release:

### 1. Create a Changeset

When you make changes that should be released, create a changeset:

```bash
pnpm changeset
```

This will prompt you to:

- Select which packages have changed (select `@runpod/mcp-server`)
- Choose the type of change:
  - **patch**: Bug fixes, small improvements
  - **minor**: New features, backwards compatible changes
  - **major**: Breaking changes
- Write a summary of the changes

Example changeset creation:

```bash
$ pnpm changeset
🦋  Which packages would you like to include? · @runpod/mcp-server
🦋  Which packages should have a major bump? · No items were selected
🦋  Which packages should have a minor bump? · @runpod/mcp-server
🦋  Please enter a summary for this change (this will be in the CHANGELOG).
🦋    (submit empty line to open external editor)
🦋  Summary › Add support for new API endpoints
```

This creates a markdown file in `.changeset/` describing your changes.

### 2. Commit and Push

```bash
git add .
git commit -m "feat: add new API endpoints"
git push origin feature/your-feature-name
```

### 3. Create Pull Request

Create a PR to merge your changes into `main`. Include:

- Clear description of changes
- Link to any relevant issues
- Verification that build passes

### 4. Automated Release

Once your PR is merged to `main`, our GitHub Actions workflow will:

1. **Create a Version PR**: The workflow creates a PR with:

   - Updated version numbers
   - Generated CHANGELOG.md entries
   - Consumed changeset files

2. **Publish Release**: When the version PR is merged:
   - Package is built
   - Published to npm
   - GitHub release is created

## Release Types

- **Patch (0.1.0 → 0.1.1)**: Bug fixes, documentation updates
- **Minor (0.1.0 → 0.2.0)**: New features, improvements
- **Major (0.1.0 → 1.0.0)**: Breaking changes

## Manual Release (Emergency)

If you need to publish manually:

```bash
# Update versions and generate changelog
pnpm changeset:version

# Build
pnpm build

# Publish to npm
pnpm changeset:publish
```

## Best Practices

1. **Always create changesets** for user-facing changes
2. **Write clear changeset summaries** - they become changelog entries
3. **Test thoroughly** before creating PRs
4. **Follow semantic versioning** when choosing change types
5. **Keep PRs focused** - one feature/fix per PR

## Questions?

If you have questions about the release process or contributing:

- Open an issue on GitHub
- Check existing issues for similar questions
- Review the [Changesets documentation](https://github.com/changesets/changesets)


```

--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------

```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "composite": false
  }
}


```

--------------------------------------------------------------------------------
/.cursor/mcp.json:
--------------------------------------------------------------------------------

```json
{
  "mcpServers": {
    "runpod": {
      "command": "node",
      "args": ["build/index.js"],
      "env": {
        "RUNPOD_API_KEY": "${RUNPOD_API_KEY}"
      }
    }
  }
}

```

--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------

```json
{
  "$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}


```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
# @runpod/mcp-server

## 1.1.0

### Minor Changes

- 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.

```

--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------

```typescript
import { defineConfig } from 'tsup';

export default defineConfig([
  {
    entry: ['src/index.ts'],
    format: ['cjs', 'esm'],
    dts: true,
    sourcemap: true,
    banner: {
      js: '#!/usr/bin/env node',
    },
    outExtension({ format }) {
      return {
        js: format === 'cjs' ? '.js' : '.mjs',
      };
    },
  },
]);

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:lts-alpine

# Set working directory
WORKDIR /app

# Copy package.json and package-lock.json if available
COPY package*.json ./

# Install dependencies
RUN npm install --ignore-scripts

# Copy the rest of the application
COPY . .

# Build the project
RUN npm run build

# Expose any necessary port if required (not specified, so leaving out)

# Start the server
CMD ["npm", "start"]

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "rootDir": "src",
    "outDir": "dist",
    "composite": true
  },
  "include": ["src/**/*"],
  "exclude": ["dist", "build", "node_modules", "tsup.config.ts", "**/*.test.ts"]
}

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - runpodApiKey
    properties:
      runpodApiKey:
        type: string
        description: Your RunPod API key
  commandFunction:
    # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
    |-
    (config) => ({
      command: 'node',
      args: ['dist/index.js'],
      env: { RUNPOD_API_KEY: config.runpodApiKey }
    })
  exampleConfig:
    runpodApiKey: your_dummy_runpod_api_key_here

```

--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------

```
import tseslint from '@typescript-eslint/eslint-plugin';
import tsparser from '@typescript-eslint/parser';

export default [
  {
    files: ['**/*.ts', '**/*.tsx'],
    languageOptions: {
      parser: tsparser,
      parserOptions: {
        ecmaVersion: 2020,
        sourceType: 'module',
      },
    },
    plugins: {
      '@typescript-eslint': tseslint,
    },
    rules: {
      '@typescript-eslint/no-unused-vars': [
        'error',
        { argsIgnorePattern: '^_' },
      ],
      '@typescript-eslint/no-explicit-any': 'warn',
      'no-console': 'off',
      'prefer-const': 'error',
      'no-var': 'error',
    },
  },
  {
    ignores: ['dist/**', 'build/**', 'node_modules/**', '*.js', '*.mjs'],
  },
];


```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18, 20, 22]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: latest

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: "pnpm"

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Type check
        run: pnpm run type-check

      - name: Lint
        run: pnpm run lint

      - name: Build
        run: pnpm run build


```

--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------

```yaml
name: Release

on:
  push:
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
  contents: write
  pull-requests: write
  id-token: write # Required for OIDC

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: latest

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: https://registry.npmjs.org

      - name: Ensure recent npm (OIDC + provenance support)
        run: npm i -g npm@latest

      - name: Install Dependencies
        run: pnpm install --frozen-lockfile

      - name: Build package
        run: pnpm run build

      - name: Create Release Pull Request or Publish to npm (OIDC)
        id: changesets
        uses: changesets/action@v1
        with:
          publish: pnpm changeset:publish
          version: pnpm changeset:version
          commit: 'chore: version packages'
          title: 'chore: version packages'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_CONFIG_ACCESS: public # For scoped public packages


```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "@runpod/mcp-server",
  "version": "1.1.0",
  "description": "MCP server for interacting with RunPod API",
  "license": "Apache-2.0",
  "type": "module",
  "main": "./dist/index.js",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "bin": {
    "runpod-mcp": "./dist/index.mjs"
  },
  "files": [
    "dist/**/*",
    "CHANGELOG.md"
  ],
  "scripts": {
    "build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
    "build:watch": "pnpm clean && tsup --watch",
    "clean": "rm -rf dist *.tsbuildinfo",
    "lint": "eslint \"./**/*.ts*\"",
    "type-check": "tsc --build",
    "prettier-check": "prettier --check \"./**/*.ts*\"",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts",
    "changeset": "changeset",
    "changeset:version": "changeset version",
    "changeset:publish": "changeset publish"
  },
  "exports": {
    "./package.json": "./package.json",
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs",
      "require": "./dist/index.js"
    }
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.7.0",
    "node-fetch": "^3.3.2",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@changesets/cli": "^2.29.6",
    "@types/node": "^20.10.5",
    "@typescript-eslint/eslint-plugin": "^8.39.1",
    "@typescript-eslint/parser": "^8.39.1",
    "eslint": "^9.33.0",
    "prettier": "^3.6.2",
    "tsup": "^8.0.0",
    "tsx": "^4.7.0",
    "typescript": "^5.3.3"
  },
  "engines": {
    "node": ">=18"
  },
  "publishConfig": {
    "access": "public"
  },
  "homepage": "https://runpod.io",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/runpod/runpod-mcp.git"
  },
  "bugs": {
    "url": "https://github.com/runpod/runpod-mcp/issues"
  },
  "keywords": [
    "mcp",
    "model-context-protocol",
    "runpod",
    "server",
    "api"
  ]
}

```

--------------------------------------------------------------------------------
/docs/context.md:
--------------------------------------------------------------------------------

```markdown
# Runpod MCP Server - Development Conventions

This document outlines the core conventions, rules, and best practices for developing and maintaining the Runpod MCP Server.

## What is this Project?

### The Model Context Protocol

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.

### Our Server

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.

### How it Works

The server runs as a stdio-based MCP server that:
- Accepts tool calls from MCP clients via stdin
- Makes authenticated requests to Runpod's REST API (`https://rest.runpod.io/v1`)
- Returns structured responses via stdout
- Uses Zod schemas for input validation

### Value Proposition

- Natural Language Control: Manage Runpod infrastructure through AI assistants
- Standardized Interface: MCP protocol ensures compatibility across clients
- Full API Coverage: Exposes all major Runpod management operations
- Type Safety: Zod schemas provide runtime validation and type inference

## Project Structure

```
runpod-mcp/
├── src/                    # Source code
│   └── index.ts           # Single-file server implementation
├── docs/                   # Documentation
├── dist/                  # Build output (generated)
├── smithery.yaml         # Smithery deployment config
└── .changeset/           # Changesets for versioning
```

## Core Principles

### 1. Branding & Naming

- Always use "Runpod" (not "RunPod", "runpod", or "run-pod") in user-facing text
- Package name: `@runpod/mcp-server`
- Server name: "Runpod API Server"
- Environment variable: `RUNPOD_API_KEY`

### 2. API Structure

- Base URL: `https://rest.runpod.io/v1`
- Authentication: Bearer token via `Authorization` header
- Request format: JSON for POST/PATCH body, query params for filters
- Response format: JSON with error handling for non-JSON responses

### 3. Tool Organization

Tools are organized by resource type:
- Pod Management: `list-pods`, `get-pod`, `create-pod`, `update-pod`, `start-pod`, `stop-pod`, `delete-pod`
- Endpoint Management: `list-endpoints`, `get-endpoint`, `create-endpoint`, `update-endpoint`, `delete-endpoint`
- Template Management: `list-templates`, `get-template`, `create-template`, `update-template`, `delete-template`
- Network Volume Management: `list-network-volumes`, `get-network-volume`, `create-network-volume`, `update-network-volume`, `delete-network-volume`
- Container Registry Auth: `list-container-registry-auths`, `get-container-registry-auth`, `create-container-registry-auth`, `delete-container-registry-auth`

### 4. Error Handling

- Always return structured error messages with HTTP status codes
- Handle non-JSON responses gracefully
- Log errors to stderr for debugging
- Return JSON responses wrapped in MCP content format

## Development Workflow

### Package Management

- Use pnpm as the primary package manager
- Maintain `pnpm-lock.yaml` (not `package-lock.json`)
- Support all package managers in documentation

### Version Management

- Use Changesets for version management and releases
- Never manually edit version numbers or `CHANGELOG.md`
- Create changesets with: `pnpm changeset`
- Automated releases via GitHub Actions

### Code Quality

- ESLint: Use `eslint.config.mjs` for Node 18+ compatibility
- Prettier: Single quotes, 2 spaces, trailing commas
- TypeScript: Strict mode, full type safety
- Single-file architecture: Keep implementation in `src/index.ts` for simplicity

## Code Style

### TypeScript

```typescript
// Good - Consistent naming and structure
server.tool(
  'list-pods',
  {
    computeType: z.enum(['GPU', 'CPU']).optional().describe('Filter to only GPU or only CPU Pods'),
    // ... more params
  },
  async (params) => {
    const result = await runpodRequest(`/pods${queryString}`);
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);
```

### Request Helper Pattern

```typescript
// Good - Centralized request handling
async function runpodRequest(
  endpoint: string,
  method: string = 'GET',
  body?: Record<string, unknown>
) {
  const url = `${API_BASE_URL}${endpoint}`;
  const headers = {
    Authorization: `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  };
  // ... error handling
}
```

### Query Parameter Construction

```typescript
// Good - Build query params conditionally
const queryParams = new URLSearchParams();
if (params.computeType) queryParams.append('computeType', params.computeType);
if (params.gpuTypeId) {
  params.gpuTypeId.forEach((type) => queryParams.append('gpuTypeId', type));
}
const queryString = queryParams.toString() ? `?${queryParams.toString()}` : '';
```

## Testing Standards

### Manual Testing

- Test each tool with Claude Desktop or MCP client
- Verify error handling with invalid inputs
- Test query parameter combinations
- Validate response format matches MCP spec

### Development Testing

```bash
# Build and run locally
pnpm build
pnpm start

# Or run directly with tsx
pnpm dev
```

## Documentation

### README Structure

1. Title & Badge (Smithery)
2. Features (list of tool categories)
3. Setup (prerequisites, installation, configuration)
4. Smithery Installation (automated setup)
5. Manual Setup (Claude Desktop config)
6. Usage Examples (natural language examples)
7. Security Considerations

### Code Comments

```typescript
// Good - Explain the "why", not the "what"
// Some endpoints might not return JSON
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
  return await response.json();
}
```

## Release Management

- Use Changesets for all releases
- Never manually edit version numbers or `CHANGELOG.md`
- Create changeset for any user-facing changes with `pnpm changeset`
- GitHub Actions handles versioning and publishing automatically

## Common Patterns

### Tool Definition Pattern

```typescript
server.tool(
  'resource-action',
  {
    resourceId: z.string().describe('ID of the resource'),
    optionalParam: z.string().optional().describe('Optional parameter'),
  },
  async (params) => {
    const { resourceId, ...rest } = params;
    const result = await runpodRequest(`/resources/${resourceId}`, 'POST', rest);
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);
```

### Environment Variable Loading

```typescript
// Good - Fail fast on missing API key
const API_KEY = process.env.RUNPOD_API_KEY;
if (!API_KEY) {
  console.error('RUNPOD_API_KEY environment variable is required');
  process.exit(1);
}
```

## Common Pitfalls

### Don't Do This

```typescript
// Bad - Inconsistent naming
const apiKey = process.env.runpod_api_key;

// Bad - Missing error context
throw new Error('API error');

// Bad - Hardcoded values
const url = 'https://rest.runpod.io/v1/pods';

// Bad - Not handling non-JSON responses
return await response.json();
```

### Do This Instead

```typescript
// Good - Consistent naming
const API_KEY = process.env.RUNPOD_API_KEY;

// Good - Clear error with context
throw new Error(`Runpod API Error: ${response.status} - ${errorText}`);

// Good - Use constant
const API_BASE_URL = 'https://rest.runpod.io/v1';
const url = `${API_BASE_URL}${endpoint}`;

// Good - Handle different response types
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
  return await response.json();
}
return { success: true, status: response.status };
```

## Deployment

### Smithery Integration

- Configuration defined in `smithery.yaml`
- Supports stdio transport
- Requires `runpodApiKey` in config schema
- Command function generates node command with env vars

### Manual Deployment

- Build: `pnpm build`
- Run: `node dist/index.js`
- Requires `RUNPOD_API_KEY` environment variable
- Compatible with any MCP client supporting stdio transport

## Development Commands

```bash
# Development
pnpm dev          # Run with tsx (watch mode)
pnpm build        # Production build
pnpm start        # Run built server

# Code Quality
pnpm lint         # ESLint checking
pnpm type-check   # TypeScript checking
pnpm prettier-check # Prettier checking

# Release Management
pnpm changeset           # Create changeset
pnpm changeset:version   # Update versions
pnpm changeset:publish   # Publish to npm
```

---

**Remember**: These conventions exist to maintain consistency, quality, and ease of maintenance. When in doubt, follow the existing patterns in the codebase.


```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import fetch, { type RequestInit as NodeFetchRequestInit } from 'node-fetch';

// Base URL for RunPod API
const API_BASE_URL = 'https://rest.runpod.io/v1';

// Get API key from environment variable
const API_KEY = process.env.RUNPOD_API_KEY;
if (!API_KEY) {
  console.error('RUNPOD_API_KEY environment variable is required');
  process.exit(1);
}

// Create an MCP server
const server = new McpServer({
  name: 'RunPod API Server',
  version: '1.0.0',
  capabilities: {
    resources: {},
    tools: {},
  },
});

// Helper function to make authenticated API requests to RunPod
async function runpodRequest(
  endpoint: string,
  method: string = 'GET',
  body?: Record<string, unknown>
) {
  const url = `${API_BASE_URL}${endpoint}`;
  const headers = {
    Authorization: `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  };

  const options: NodeFetchRequestInit = {
    method,
    headers,
  };

  if (body && (method === 'POST' || method === 'PATCH')) {
    options.body = JSON.stringify(body);
  }

  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`RunPod API Error: ${response.status} - ${errorText}`);
    }

    // Some endpoints might not return JSON
    const contentType = response.headers.get('content-type');
    if (contentType && contentType.includes('application/json')) {
      return await response.json();
    }

    return { success: true, status: response.status };
  } catch (error) {
    console.error('Error calling RunPod API:', error);
    throw error;
  }
}

// ============== POD MANAGEMENT TOOLS ==============

// List Pods
server.tool(
  'list-pods',
  {
    computeType: z
      .enum(['GPU', 'CPU'])
      .optional()
      .describe('Filter to only GPU or only CPU Pods'),
    gpuTypeId: z
      .array(z.string())
      .optional()
      .describe('Filter to Pods with any of the listed GPU types'),
    dataCenterId: z
      .array(z.string())
      .optional()
      .describe('Filter to Pods in any of the provided data centers'),
    name: z
      .string()
      .optional()
      .describe('Filter to Pods with the provided name'),
    includeMachine: z
      .boolean()
      .optional()
      .describe('Include information about the machine'),
    includeNetworkVolume: z
      .boolean()
      .optional()
      .describe('Include information about attached network volumes'),
  },
  async (params) => {
    // Construct query parameters
    const queryParams = new URLSearchParams();

    if (params.computeType)
      queryParams.append('computeType', params.computeType);
    if (params.gpuTypeId)
      params.gpuTypeId.forEach((type) => queryParams.append('gpuTypeId', type));
    if (params.dataCenterId)
      params.dataCenterId.forEach((dc) =>
        queryParams.append('dataCenterId', dc)
      );
    if (params.name) queryParams.append('name', params.name);
    if (params.includeMachine)
      queryParams.append('includeMachine', params.includeMachine.toString());
    if (params.includeNetworkVolume)
      queryParams.append(
        'includeNetworkVolume',
        params.includeNetworkVolume.toString()
      );

    const queryString = queryParams.toString()
      ? `?${queryParams.toString()}`
      : '';
    const result = await runpodRequest(`/pods${queryString}`);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Get Pod Details
server.tool(
  'get-pod',
  {
    podId: z.string().describe('ID of the pod to retrieve'),
    includeMachine: z
      .boolean()
      .optional()
      .describe('Include information about the machine'),
    includeNetworkVolume: z
      .boolean()
      .optional()
      .describe('Include information about attached network volumes'),
  },
  async (params) => {
    // Construct query parameters
    const queryParams = new URLSearchParams();

    if (params.includeMachine)
      queryParams.append('includeMachine', params.includeMachine.toString());
    if (params.includeNetworkVolume)
      queryParams.append(
        'includeNetworkVolume',
        params.includeNetworkVolume.toString()
      );

    const queryString = queryParams.toString()
      ? `?${queryParams.toString()}`
      : '';
    const result = await runpodRequest(`/pods/${params.podId}${queryString}`);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Create Pod
server.tool(
  'create-pod',
  {
    name: z.string().optional().describe('Name for the pod'),
    imageName: z.string().describe('Docker image to use'),
    cloudType: z
      .enum(['SECURE', 'COMMUNITY'])
      .optional()
      .describe('SECURE or COMMUNITY cloud'),
    gpuTypeIds: z
      .array(z.string())
      .optional()
      .describe('List of acceptable GPU types'),
    gpuCount: z.number().optional().describe('Number of GPUs'),
    containerDiskInGb: z
      .number()
      .optional()
      .describe('Container disk size in GB'),
    volumeInGb: z.number().optional().describe('Volume size in GB'),
    volumeMountPath: z.string().optional().describe('Path to mount the volume'),
    ports: z
      .array(z.string())
      .optional()
      .describe("Ports to expose (e.g., '8888/http', '22/tcp')"),
    env: z.record(z.string()).optional().describe('Environment variables'),
    dataCenterIds: z
      .array(z.string())
      .optional()
      .describe('List of data centers'),
  },
  async (params) => {
    const result = await runpodRequest('/pods', 'POST', params);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Update Pod
server.tool(
  'update-pod',
  {
    podId: z.string().describe('ID of the pod to update'),
    name: z.string().optional().describe('New name for the pod'),
    imageName: z.string().optional().describe('New Docker image'),
    containerDiskInGb: z
      .number()
      .optional()
      .describe('New container disk size in GB'),
    volumeInGb: z.number().optional().describe('New volume size in GB'),
    volumeMountPath: z
      .string()
      .optional()
      .describe('New path to mount the volume'),
    ports: z.array(z.string()).optional().describe('New ports to expose'),
    env: z.record(z.string()).optional().describe('New environment variables'),
  },
  async (params) => {
    const { podId, ...updateParams } = params;
    const result = await runpodRequest(`/pods/${podId}`, 'PATCH', updateParams);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Start Pod
server.tool(
  'start-pod',
  {
    podId: z.string().describe('ID of the pod to start'),
  },
  async (params) => {
    const result = await runpodRequest(`/pods/${params.podId}/start`, 'POST');

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Stop Pod
server.tool(
  'stop-pod',
  {
    podId: z.string().describe('ID of the pod to stop'),
  },
  async (params) => {
    const result = await runpodRequest(`/pods/${params.podId}/stop`, 'POST');

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Delete Pod
server.tool(
  'delete-pod',
  {
    podId: z.string().describe('ID of the pod to delete'),
  },
  async (params) => {
    const result = await runpodRequest(`/pods/${params.podId}`, 'DELETE');

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// ============== ENDPOINT MANAGEMENT TOOLS ==============

// List Endpoints
server.tool(
  'list-endpoints',
  {
    includeTemplate: z
      .boolean()
      .optional()
      .describe('Include template information'),
    includeWorkers: z
      .boolean()
      .optional()
      .describe('Include information about workers'),
  },
  async (params) => {
    // Construct query parameters
    const queryParams = new URLSearchParams();

    if (params.includeTemplate)
      queryParams.append('includeTemplate', params.includeTemplate.toString());
    if (params.includeWorkers)
      queryParams.append('includeWorkers', params.includeWorkers.toString());

    const queryString = queryParams.toString()
      ? `?${queryParams.toString()}`
      : '';
    const result = await runpodRequest(`/endpoints${queryString}`);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Get Endpoint Details
server.tool(
  'get-endpoint',
  {
    endpointId: z.string().describe('ID of the endpoint to retrieve'),
    includeTemplate: z
      .boolean()
      .optional()
      .describe('Include template information'),
    includeWorkers: z
      .boolean()
      .optional()
      .describe('Include information about workers'),
  },
  async (params) => {
    // Construct query parameters
    const queryParams = new URLSearchParams();

    if (params.includeTemplate)
      queryParams.append('includeTemplate', params.includeTemplate.toString());
    if (params.includeWorkers)
      queryParams.append('includeWorkers', params.includeWorkers.toString());

    const queryString = queryParams.toString()
      ? `?${queryParams.toString()}`
      : '';
    const result = await runpodRequest(
      `/endpoints/${params.endpointId}${queryString}`
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Create Endpoint
server.tool(
  'create-endpoint',
  {
    name: z.string().optional().describe('Name for the endpoint'),
    templateId: z.string().describe('Template ID to use'),
    computeType: z
      .enum(['GPU', 'CPU'])
      .optional()
      .describe('GPU or CPU endpoint'),
    gpuTypeIds: z
      .array(z.string())
      .optional()
      .describe('List of acceptable GPU types'),
    gpuCount: z.number().optional().describe('Number of GPUs per worker'),
    workersMin: z.number().optional().describe('Minimum number of workers'),
    workersMax: z.number().optional().describe('Maximum number of workers'),
    dataCenterIds: z
      .array(z.string())
      .optional()
      .describe('List of data centers'),
  },
  async (params) => {
    const result = await runpodRequest('/endpoints', 'POST', params);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Update Endpoint
server.tool(
  'update-endpoint',
  {
    endpointId: z.string().describe('ID of the endpoint to update'),
    name: z.string().optional().describe('New name for the endpoint'),
    workersMin: z.number().optional().describe('New minimum number of workers'),
    workersMax: z.number().optional().describe('New maximum number of workers'),
    idleTimeout: z.number().optional().describe('New idle timeout in seconds'),
    scalerType: z
      .enum(['QUEUE_DELAY', 'REQUEST_COUNT'])
      .optional()
      .describe('Scaler type'),
    scalerValue: z.number().optional().describe('Scaler value'),
  },
  async (params) => {
    const { endpointId, ...updateParams } = params;
    const result = await runpodRequest(
      `/endpoints/${endpointId}`,
      'PATCH',
      updateParams
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Delete Endpoint
server.tool(
  'delete-endpoint',
  {
    endpointId: z.string().describe('ID of the endpoint to delete'),
  },
  async (params) => {
    const result = await runpodRequest(
      `/endpoints/${params.endpointId}`,
      'DELETE'
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// ============== TEMPLATE MANAGEMENT TOOLS ==============

// List Templates
server.tool('list-templates', {}, async () => {
  const result = await runpodRequest('/templates');

  return {
    content: [
      {
        type: 'text',
        text: JSON.stringify(result, null, 2),
      },
    ],
  };
});

// Get Template Details
server.tool(
  'get-template',
  {
    templateId: z.string().describe('ID of the template to retrieve'),
  },
  async (params) => {
    const result = await runpodRequest(`/templates/${params.templateId}`);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Create Template
server.tool(
  'create-template',
  {
    name: z.string().describe('Name for the template'),
    imageName: z.string().describe('Docker image to use'),
    isServerless: z
      .boolean()
      .optional()
      .describe('Is this a serverless template'),
    ports: z.array(z.string()).optional().describe('Ports to expose'),
    dockerEntrypoint: z
      .array(z.string())
      .optional()
      .describe('Docker entrypoint commands'),
    dockerStartCmd: z
      .array(z.string())
      .optional()
      .describe('Docker start commands'),
    env: z.record(z.string()).optional().describe('Environment variables'),
    containerDiskInGb: z
      .number()
      .optional()
      .describe('Container disk size in GB'),
    volumeInGb: z.number().optional().describe('Volume size in GB'),
    volumeMountPath: z.string().optional().describe('Path to mount the volume'),
    readme: z.string().optional().describe('README content in markdown format'),
  },
  async (params) => {
    const result = await runpodRequest('/templates', 'POST', params);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Update Template
server.tool(
  'update-template',
  {
    templateId: z.string().describe('ID of the template to update'),
    name: z.string().optional().describe('New name for the template'),
    imageName: z.string().optional().describe('New Docker image'),
    ports: z.array(z.string()).optional().describe('New ports to expose'),
    env: z.record(z.string()).optional().describe('New environment variables'),
    readme: z
      .string()
      .optional()
      .describe('New README content in markdown format'),
  },
  async (params) => {
    const { templateId, ...updateParams } = params;
    const result = await runpodRequest(
      `/templates/${templateId}`,
      'PATCH',
      updateParams
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Delete Template
server.tool(
  'delete-template',
  {
    templateId: z.string().describe('ID of the template to delete'),
  },
  async (params) => {
    const result = await runpodRequest(
      `/templates/${params.templateId}`,
      'DELETE'
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// ============== NETWORK VOLUME MANAGEMENT TOOLS ==============

// List Network Volumes
server.tool('list-network-volumes', {}, async () => {
  const result = await runpodRequest('/networkvolumes');

  return {
    content: [
      {
        type: 'text',
        text: JSON.stringify(result, null, 2),
      },
    ],
  };
});

// Get Network Volume Details
server.tool(
  'get-network-volume',
  {
    networkVolumeId: z
      .string()
      .describe('ID of the network volume to retrieve'),
  },
  async (params) => {
    const result = await runpodRequest(
      `/networkvolumes/${params.networkVolumeId}`
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Create Network Volume
server.tool(
  'create-network-volume',
  {
    name: z.string().describe('Name for the network volume'),
    size: z.number().describe('Size in GB (1-4000)'),
    dataCenterId: z.string().describe('Data center ID'),
  },
  async (params) => {
    const result = await runpodRequest('/networkvolumes', 'POST', params);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Update Network Volume
server.tool(
  'update-network-volume',
  {
    networkVolumeId: z.string().describe('ID of the network volume to update'),
    name: z.string().optional().describe('New name for the network volume'),
    size: z
      .number()
      .optional()
      .describe('New size in GB (must be larger than current)'),
  },
  async (params) => {
    const { networkVolumeId, ...updateParams } = params;
    const result = await runpodRequest(
      `/networkvolumes/${networkVolumeId}`,
      'PATCH',
      updateParams
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Delete Network Volume
server.tool(
  'delete-network-volume',
  {
    networkVolumeId: z.string().describe('ID of the network volume to delete'),
  },
  async (params) => {
    const result = await runpodRequest(
      `/networkvolumes/${params.networkVolumeId}`,
      'DELETE'
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// ============== CONTAINER REGISTRY AUTH TOOLS ==============

// List Container Registry Auths
server.tool('list-container-registry-auths', {}, async () => {
  const result = await runpodRequest('/containerregistryauth');

  return {
    content: [
      {
        type: 'text',
        text: JSON.stringify(result, null, 2),
      },
    ],
  };
});

// Get Container Registry Auth Details
server.tool(
  'get-container-registry-auth',
  {
    containerRegistryAuthId: z
      .string()
      .describe('ID of the container registry auth to retrieve'),
  },
  async (params) => {
    const result = await runpodRequest(
      `/containerregistryauth/${params.containerRegistryAuthId}`
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Create Container Registry Auth
server.tool(
  'create-container-registry-auth',
  {
    name: z.string().describe('Name for the container registry auth'),
    username: z.string().describe('Registry username'),
    password: z.string().describe('Registry password'),
  },
  async (params) => {
    const result = await runpodRequest(
      '/containerregistryauth',
      'POST',
      params
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Delete Container Registry Auth
server.tool(
  'delete-container-registry-auth',
  {
    containerRegistryAuthId: z
      .string()
      .describe('ID of the container registry auth to delete'),
  },
  async (params) => {
    const result = await runpodRequest(
      `/containerregistryauth/${params.containerRegistryAuthId}`,
      'DELETE'
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  }
);

// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
server.connect(transport);

```