18 KiB
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
- Local (Claude Desktop) — Development + personal use
- Docker Container — Portable, isolated environment
- Railway.app — Hosted deployment (for web-accessible MCPs)
- npm Registry — Public distribution
- GitHub — Source code + documentation
2. Local Deployment (Claude Desktop)
Standard Configuration
Location: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
{
"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
envobject - Server name (
myservice) appears in Claude Desktop - Restart Claude Desktop after config changes
Alternative: npx Installation
{
"mcpServers": {
"myservice": {
"command": "npx",
"args": ["-y", "mcp-server-myservice"],
"env": {
"MY_SERVICE_API_KEY": "your_api_key_here"
}
}
}
}
Requires:
- Package published to npm
binfield in package.json pointing to executable
3. Docker Containerization
Dockerfile Template
# 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 thannpm install--production→ Excludes devDependenciesnode: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
# 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)
# 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
# Run with docker-compose
docker-compose up -d
4. Railway Deployment
railway.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 TypeScriptstartCommand→ Run compiled serverrestartPolicyType→ Auto-restart on failure
Environment Variables
In Railway Dashboard:
- Go to project → Variables
- Add all required environment variables:
MY_SERVICE_API_KEYMY_SERVICE_API_SECRETNODE_ENV=production
Deployment Commands
# 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)
[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
{
"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 vianpxfiles→ Only include necessary files in packagekeywords→ Helps with npm searchrepository→ 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
# 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
# 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
# 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
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
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:
- Go to npmjs.com → Account Settings → Access Tokens
- Create new token (Automation or Publish)
- 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.jsonhas correctname,version,descriptionfilesfield only includes necessary fileskeywordsadded for npm searchrepository,bugs,homepageURLs 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
.envin.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
# 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:
## 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:
- Build:
npm run build - Test locally in Claude Desktop
- Create README.md with installation instructions
- Add Dockerfile + railway.json (if deploying)
- Publish to npm:
npm publish - Push to GitHub with proper README
- Tag releases for versioning
- Automate with GitHub Actions
Key files:
package.json→ npm distributionDockerfile→ Docker containerizationrailway.json→ Railway deploymentREADME.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.