# MCP Deployment & Distribution

**When to use this skill:** Packaging and distributing MCP servers. Use when preparing servers for production, Docker containers, Railway deployment, or GitHub publishing.

**What this covers:** Deployment patterns from 30+ production MCP servers including Docker, Railway, npm publishing, and GitHub repository setup.

---

## 1. Deployment Overview

### Common Deployment Targets

1. **Local (Claude Desktop)** — Development + personal use
2. **Docker Container** — Portable, isolated environment
3. **Railway.app** — Hosted deployment (for web-accessible MCPs)
4. **npm Registry** — Public distribution
5. **GitHub** — Source code + documentation

---

## 2. Local Deployment (Claude Desktop)

### Standard Configuration

**Location:** `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)

```json
{
  "mcpServers": {
    "myservice": {
      "command": "node",
      "args": [
        "/absolute/path/to/mcp-server-myservice/dist/index.js"
      ],
      "env": {
        "MY_SERVICE_API_KEY": "your_api_key_here",
        "MY_SERVICE_API_SECRET": "your_secret_here"
      }
    }
  }
}
```

**Key points:**
- Use absolute paths for `args`
- Environment variables in `env` object
- Server name (`myservice`) appears in Claude Desktop
- Restart Claude Desktop after config changes

### Alternative: npx Installation

```json
{
  "mcpServers": {
    "myservice": {
      "command": "npx",
      "args": ["-y", "mcp-server-myservice"],
      "env": {
        "MY_SERVICE_API_KEY": "your_api_key_here"
      }
    }
  }
}
```

**Requires:**
- Package published to npm
- `bin` field in package.json pointing to executable

---

## 3. Docker Containerization

### Dockerfile Template

```dockerfile
# Multi-stage build for smaller final image
FROM node:20-alpine AS builder

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci

# Copy source code
COPY . .

# Build TypeScript
RUN npm run build

# Production stage
FROM node:20-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install production dependencies only
RUN npm ci --production

# Copy built files from builder
COPY --from=builder /app/dist ./dist

# Copy UI files (if using MCP Apps)
COPY --from=builder /app/dist/app-ui ./dist/app-ui

# Expose port (if using HTTP transport)
# EXPOSE 3000

# Set environment variable defaults
ENV NODE_ENV=production

# Run the MCP server
CMD ["node", "dist/index.js"]
```

**Key features:**
- Multi-stage build → Smaller final image
- `npm ci` → Faster, more reliable than `npm install`
- `--production` → Excludes devDependencies
- `node:20-alpine` → Lightweight base image

### .dockerignore

```
node_modules
dist
.env
.git
.gitignore
*.md
npm-debug.log
```

**Why:** Prevents unnecessary files from being copied into image

### Build & Run

```bash
# Build image
docker build -t mcp-server-myservice .

# Run container
docker run -it --rm \
  -e MY_SERVICE_API_KEY=your_key \
  -e MY_SERVICE_API_SECRET=your_secret \
  mcp-server-myservice

# Run with env file
docker run -it --rm \
  --env-file .env \
  mcp-server-myservice
```

### Docker Compose (Optional)

```yaml
# docker-compose.yml
version: '3.8'

services:
  mcp-server:
    build: .
    environment:
      - MY_SERVICE_API_KEY=${MY_SERVICE_API_KEY}
      - MY_SERVICE_API_SECRET=${MY_SERVICE_API_SECRET}
    restart: unless-stopped
```

```bash
# Run with docker-compose
docker-compose up -d
```

---

## 4. Railway Deployment

### railway.json

```json
{
  "$schema": "https://railway.app/railway.schema.json",
  "build": {
    "builder": "NIXPACKS",
    "buildCommand": "npm run build"
  },
  "deploy": {
    "startCommand": "node dist/index.js",
    "restartPolicyType": "ON_FAILURE",
    "restartPolicyMaxRetries": 10
  }
}
```

**Key fields:**
- `buildCommand` → Compile TypeScript
- `startCommand` → Run compiled server
- `restartPolicyType` → Auto-restart on failure

### Environment Variables

**In Railway Dashboard:**
1. Go to project → Variables
2. Add all required environment variables:
   - `MY_SERVICE_API_KEY`
   - `MY_SERVICE_API_SECRET`
   - `NODE_ENV=production`

### Deployment Commands

```bash
# Install Railway CLI
npm install -g @railway/cli

# Login
railway login

# Link to project
railway link

# Deploy
railway up

# View logs
railway logs
```

### railway.toml (Alternative)

```toml
[build]
builder = "NIXPACKS"
buildCommand = "npm ci && npm run build"

[deploy]
startCommand = "node dist/index.js"
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 10

[env]
NODE_ENV = "production"
```

---

## 5. npm Publishing

### package.json Configuration

```json
{
  "name": "mcp-server-myservice",
  "version": "1.0.0",
  "description": "MCP server for MyService integration",
  "type": "module",
  "main": "dist/index.js",
  "bin": {
    "mcp-server-myservice": "dist/index.js"
  },
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ],
  "keywords": [
    "mcp",
    "mcp-server",
    "model-context-protocol",
    "myservice",
    "claude-desktop"
  ],
  "author": "Your Name <your.email@example.com>",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/yourusername/mcp-server-myservice.git"
  },
  "bugs": {
    "url": "https://github.com/yourusername/mcp-server-myservice/issues"
  },
  "homepage": "https://github.com/yourusername/mcp-server-myservice#readme"
}
```

**Key fields:**
- `bin` → Makes package executable via `npx`
- `files` → Only include necessary files in package
- `keywords` → Helps with npm search
- `repository` → Links to GitHub

### .npmignore

```
src
*.ts
tsconfig.json
.env
.env.example
node_modules
.git
.DS_Store
```

**Why:** Prevents source files from being published (only `dist/` is needed)

### Publishing Workflow

```bash
# 1. Ensure you're logged in to npm
npm login

# 2. Build the project
npm run build

# 3. Test locally before publishing
npm pack
# This creates a .tgz file - inspect it to verify contents

# 4. Publish to npm
npm publish

# For scoped packages (e.g., @yourorg/mcp-server-myservice)
npm publish --access public
```

### Versioning

```bash
# Patch release (1.0.0 -> 1.0.1)
npm version patch

# Minor release (1.0.0 -> 1.1.0)
npm version minor

# Major release (1.0.0 -> 2.0.0)
npm version major

# Then publish
npm publish
```

---

## 6. GitHub Repository Setup

### File Structure

```
mcp-server-myservice/
├── .github/
│   └── workflows/
│       ├── build.yml           # CI/CD
│       └── publish.yml         # npm publish automation
├── src/
│   └── index.ts
├── dist/                        # gitignored
├── .env.example                 # Template for env vars
├── .gitignore
├── .npmignore
├── Dockerfile
├── docker-compose.yml
├── railway.json
├── package.json
├── tsconfig.json
├── README.md
├── LICENSE
└── CHANGELOG.md
```

### .gitignore

```
# Dependencies
node_modules/

# Build output
dist/

# Environment variables
.env
.env.local

# Logs
*.log
npm-debug.log*

# OS files
.DS_Store
Thumbs.db

# IDE
.vscode/
.idea/
*.swp
*.swo

# Misc
.cache/
```

### README.md Template

```markdown
# MCP Server for MyService

MCP (Model Context Protocol) server integration for MyService. Enables Claude Desktop to interact with MyService API.

## Features

- ✅ List and search contacts
- ✅ Get contact details
- ✅ Create and update contacts
- ✅ View dashboard metrics
- ✅ Rich UI components (contact grid, dashboard)

## Installation

### Claude Desktop

Add to your `claude_desktop_config.json`:

\`\`\`json
{
  "mcpServers": {
    "myservice": {
      "command": "npx",
      "args": ["-y", "mcp-server-myservice"],
      "env": {
        "MY_SERVICE_API_KEY": "your_api_key_here"
      }
    }
  }
}
\`\`\`

### Manual Installation

\`\`\`bash
git clone https://github.com/yourusername/mcp-server-myservice.git
cd mcp-server-myservice
npm install
npm run build
\`\`\`

Add to `claude_desktop_config.json`:

\`\`\`json
{
  "mcpServers": {
    "myservice": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-server-myservice/dist/index.js"],
      "env": {
        "MY_SERVICE_API_KEY": "your_api_key_here"
      }
    }
  }
}
\`\`\`

## Configuration

### Required Environment Variables

- `MY_SERVICE_API_KEY` — Your MyService API key ([get one here](https://myservice.com/api-keys))
- `MY_SERVICE_API_SECRET` — Your MyService API secret (optional)

### Optional Environment Variables

- `MY_SERVICE_BASE_URL` — Override API base URL (default: `https://api.myservice.com`)
- `LOG_LEVEL` — Logging level: `debug`, `info`, `warn`, `error` (default: `info`)

## Available Tools

### Core Tools

- `list_contacts` — List contacts with pagination and filters
- `get_contact` — Get detailed contact information
- `create_contact` — Create a new contact
- `update_contact` — Update existing contact
- `delete_contact` — Delete a contact

### App Tools (Rich UI)

- `view_contact_grid` — Display contact search results in a data grid
- `show_dashboard` — Display dashboard with metrics and KPIs

## Usage Examples

### List contacts

\`\`\`
Can you show me all active contacts?
\`\`\`

### Search and display

\`\`\`
Search for contacts with "john" in their name and show me the grid
\`\`\`

### Create contact

\`\`\`
Create a new contact:
Name: Jane Smith
Email: jane@example.com
Phone: 555-1234
\`\`\`

## Development

\`\`\`bash
# Install dependencies
npm install

# Run in development mode
npm run dev

# Build for production
npm run build

# Run tests
npm test
\`\`\`

## Docker

\`\`\`bash
# Build image
docker build -t mcp-server-myservice .

# Run container
docker run -it --rm \
  -e MY_SERVICE_API_KEY=your_key \
  mcp-server-myservice
\`\`\`

## Railway Deployment

1. Fork this repository
2. Connect to Railway
3. Add environment variables in Railway dashboard
4. Deploy

## Contributing

Pull requests are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details.

## License

MIT License - see [LICENSE](LICENSE) file for details.

## Support

- [Open an issue](https://github.com/yourusername/mcp-server-myservice/issues)
- [MyService API Documentation](https://myservice.com/docs)
- [MCP Documentation](https://modelcontextprotocol.io)
```

### LICENSE (MIT Template)

```
MIT License

Copyright (c) 2026 Your Name

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

---

## 7. GitHub Actions CI/CD

### .github/workflows/build.yml

```yaml
name: Build and Test

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

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
      - uses: actions/checkout@v3

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Run tests
        run: npm test
        if: ${{ hashFiles('**/*.test.ts') != '' }}

      - name: Verify dist exists
        run: test -d dist && test -f dist/index.js
```

### .github/workflows/publish.yml

```yaml
name: Publish to npm

on:
  release:
    types: [created]

jobs:
  publish:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Use Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20.x'
          registry-url: 'https://registry.npmjs.org'

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Publish to npm
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

**Setup:**
1. Go to npmjs.com → Account Settings → Access Tokens
2. Create new token (Automation or Publish)
3. Add to GitHub repo → Settings → Secrets → `NPM_TOKEN`

---

## 8. Distribution Checklist

Before publishing/deploying:

### Code Quality
- [ ] All TypeScript compiles without errors
- [ ] No console.logs in production code (use proper logging)
- [ ] Error handling implemented for all tools
- [ ] Environment variables validated on startup

### Documentation
- [ ] README.md with installation instructions
- [ ] .env.example with all required variables
- [ ] Tool descriptions are clear and helpful
- [ ] Examples provided in README

### Package Configuration
- [ ] `package.json` has correct `name`, `version`, `description`
- [ ] `files` field only includes necessary files
- [ ] `keywords` added for npm search
- [ ] `repository`, `bugs`, `homepage` URLs set
- [ ] License file included

### Testing
- [ ] Tested locally in Claude Desktop
- [ ] All tools work as expected
- [ ] Apps render correctly (if applicable)
- [ ] Error cases handled gracefully

### Security
- [ ] No API keys hardcoded
- [ ] `.env` in `.gitignore`
- [ ] Sensitive data not logged
- [ ] Dependencies up to date (`npm audit`)

### Deployment
- [ ] Dockerfile builds successfully
- [ ] Docker container runs without errors
- [ ] Railway deployment works (if applicable)
- [ ] npm package installs and runs via `npx`

---

## 9. Version Management

### Semantic Versioning

- **Patch (1.0.0 → 1.0.1):** Bug fixes, no API changes
- **Minor (1.0.0 → 1.1.0):** New features, backward compatible
- **Major (1.0.0 → 2.0.0):** Breaking changes

### CHANGELOG.md

```markdown
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Added
- New `search_contacts` tool with full-text search

### Changed
- Improved error messages for API failures

### Fixed
- Fixed pagination issue in `list_contacts`

## [1.1.0] - 2026-02-03

### Added
- Contact grid MCP app
- Dashboard MCP app
- Docker support

### Changed
- Updated dependencies to latest versions

## [1.0.0] - 2026-01-15

### Added
- Initial release
- Basic CRUD tools for contacts
- MyService API integration
```

---

## 10. Multi-Platform Distribution

### npm + Docker + GitHub

**Best practice:** Offer multiple installation methods

**README.md section:**

```markdown
## Installation Methods

### 1. npx (Easiest)

\`\`\`bash
# Add to claude_desktop_config.json
{
  "mcpServers": {
    "myservice": {
      "command": "npx",
      "args": ["-y", "mcp-server-myservice"]
    }
  }
}
\`\`\`

### 2. npm Global Install

\`\`\`bash
npm install -g mcp-server-myservice

# Then reference in claude_desktop_config.json
{
  "mcpServers": {
    "myservice": {
      "command": "mcp-server-myservice"
    }
  }
}
\`\`\`

### 3. Docker

\`\`\`bash
docker run -it --rm \
  -e MY_SERVICE_API_KEY=your_key \
  ghcr.io/yourusername/mcp-server-myservice:latest
\`\`\`

### 4. From Source

\`\`\`bash
git clone https://github.com/yourusername/mcp-server-myservice.git
cd mcp-server-myservice
npm install && npm run build

# Reference dist/index.js in claude_desktop_config.json
\`\`\`
```

---

## 11. Common Deployment Issues

### Issue: "Cannot find module"
**Cause:** Missing dependencies or incorrect path
**Fix:** Run `npm ci` and use absolute paths in config

### Issue: "Environment variable not set"
**Cause:** Missing env vars
**Fix:** Add to `env` object in Claude Desktop config or `.env` file

### Issue: "UI files not found"
**Cause:** `dist/app-ui/` not copied during build
**Fix:** Add `build:ui` script to copy HTML files

### Issue: "ENOENT: no such file or directory"
**Cause:** Path resolution fails in compiled code
**Fix:** Use `fileURLToPath` for ESM `__dirname` equivalent

### Issue: Docker build fails
**Cause:** Missing build step or dependencies
**Fix:** Ensure `npm run build` runs in Dockerfile and all deps installed

---

## 12. Resources

- **MCP Deployment Guide:** https://modelcontextprotocol.io/docs/deployment
- **Railway Docs:** https://docs.railway.app
- **npm Publishing Guide:** https://docs.npmjs.com/creating-and-publishing-scoped-public-packages
- **Docker Best Practices:** https://docs.docker.com/develop/dev-best-practices

---

## Summary

**Distribution workflow:**
1. Build: `npm run build`
2. Test locally in Claude Desktop
3. Create README.md with installation instructions
4. Add Dockerfile + railway.json (if deploying)
5. Publish to npm: `npm publish`
6. Push to GitHub with proper README
7. Tag releases for versioning
8. Automate with GitHub Actions

**Key files:**
- `package.json` → npm distribution
- `Dockerfile` → Docker containerization
- `railway.json` → Railway deployment
- `README.md` → User documentation
- `.env.example` → Configuration template
- `.github/workflows/` → CI/CD automation

Follow these patterns and your MCP servers will be production-ready and easy to distribute.
