Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Build outputs
dist/
*.tsbuildinfo

# Test artifacts
coverage/
.nyc_output/

# Environment files (should be provided at runtime)
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Git
.git/
.gitignore

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

# Documentation (not needed in image, except README)
*.md
!README.md
docs/

# CI/CD
.github/
.gitlab-ci.yml

# Docker
Dockerfile
docker-compose*.yml
.dockerignore

# OS files
.DS_Store
Thumbs.db

# Logs
logs/
*.log

# Temporary files
tmp/
temp/
.cache/

# Project-specific exclusions
.serena/
.editorconfig
scryfall-mcp.code-workspace
test-artifact-fix.js
3-feature-proposals.md
AGENTS.md
EDITORCONFIG_GUIDE.md
ENHANCEMENT_PROPOSALS.md
FEATURE_2_IMPLEMENTATION_PLAN.md
SECURITY_FIXES.md
CLAUDE.md

# Plan files
.claude/
39 changes: 39 additions & 0 deletions .env.docker
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Docker-specific environment configuration
# Copy this to .env.docker and customize as needed for your deployment
# This file is used as a template - uncomment env_file in docker-compose.yml to use it

# Node environment
NODE_ENV=production

# Logging configuration
# Options: trace, debug, info, warn, error, fatal
LOG_LEVEL=info

# Scryfall API compliance settings
# User-Agent identifies your application to Scryfall
# Default uses Docker-specific identifier for tracking
SCRYFALL_USER_AGENT=ScryfallMCPServer/1.0.2-docker

# Minimum milliseconds between API requests (Scryfall requires 100ms minimum)
RATE_LIMIT_MS=100

# Request timeout in milliseconds
SCRYFALL_TIMEOUT_MS=15000

# Health check configuration
HEALTHCHECK_DEEP=false

# Rate limiter settings
# Maximum number of queued requests before rejecting new ones
RATE_LIMIT_QUEUE_MAX=500

# Cache configuration
# Maximum number of entries in cache
CACHE_MAX_SIZE=10000

# Maximum memory usage for cache in MB
CACHE_MAX_MEMORY_MB=100

# Optional: Custom user identification for tracking
# If you want to identify your specific deployment to Scryfall, customize this:
# SCRYFALL_USER_AGENT=YourApp/1.0-docker (your-email@example.com)
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,6 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# IntelliJ IDEA
.idea
77 changes: 77 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Stage 1: Build stage with full development dependencies
FROM node:18-alpine AS builder

# Install build dependencies (needed for some native modules)
RUN apk add --no-cache python3 make g++

WORKDIR /build

# Copy package files first (layer caching optimization)
COPY package*.json ./

# Install ALL dependencies (including devDependencies for build)
RUN npm ci

# Copy source code
COPY tsconfig.json vitest.config.ts ./
COPY src/ ./src/
COPY tests/ ./tests/
COPY mtgrules.txt ./

# Build: TypeScript compilation + tests
# This will fail if tests don't pass, ensuring quality
RUN npm run build

# Stage 2: Production dependencies stage
FROM node:18-alpine AS deps

WORKDIR /deps

# Copy package files
COPY package*.json ./

# Install ONLY production dependencies
RUN npm ci --only=production && \
npm cache clean --force

# Stage 3: Runtime stage with distroless (minimal, secure, no shell)
FROM gcr.io/distroless/nodejs18-debian12:nonroot

# Set working directory
WORKDIR /app

# Copy production dependencies from deps stage
COPY --from=deps /deps/node_modules ./node_modules

# Copy built artifacts from builder stage
COPY --from=builder /build/dist ./dist

# Copy runtime files
COPY --chown=nonroot:nonroot package*.json ./
COPY --chown=nonroot:nonroot .env.example ./
COPY --chown=nonroot:nonroot mtgrules.txt ./

# Distroless already runs as nonroot user (uid 65532)
# No need to create users - it's baked into the image

# Environment variables with defaults
ENV NODE_ENV=production \
LOG_LEVEL=info \
SCRYFALL_USER_AGENT="ScryfallMCPServer/1.0.2-docker" \
RATE_LIMIT_MS=100 \
SCRYFALL_TIMEOUT_MS=15000 \
HEALTHCHECK_DEEP=false \
RATE_LIMIT_QUEUE_MAX=500 \
CACHE_MAX_SIZE=10000 \
CACHE_MAX_MEMORY_MB=100

# The server uses stdio transport, so no EXPOSE needed
# Entry point runs the compiled production code
CMD ["dist/index.js"]

# Metadata labels
LABEL org.opencontainers.image.title="Scryfall MCP Server" \
org.opencontainers.image.description="Model Context Protocol server for Scryfall API" \
org.opencontainers.image.version="1.0.0" \
org.opencontainers.image.source="https://github.com/bmurdock/scryfall-mcp" \
org.opencontainers.image.licenses="MIT"
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,83 @@ Add the following to your Claude Desktop configuration file:

Replace `/absolute/path/to/scryfall-mcp` with the actual path to your installation.

## Docker Deployment

Deploy the Scryfall MCP Server using Docker for isolated, reproducible environments. The Docker image is optimized with a **2-stage build** using **Google's distroless** base for maximum security and minimal size (**132MB**).

### Quick Start

**Build the image:**
```bash
docker build -t scryfall-mcp:latest .
```

**Run with Docker:**
```bash
docker run -d --name scryfall-mcp --restart unless-stopped scryfall-mcp:latest
```

**Run with Docker Compose (recommended):**
```bash
docker-compose up -d
```

### Integration with Claude Desktop

Add to `claude_desktop_config.json`:

```json
{
"mcpServers": {
"scryfall": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"--name", "scryfall-mcp-claude-desktop",
"-e", "LOG_LEVEL=info",
"scryfall-mcp:latest"
]
}
}
}
```

**Config locations:**
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
- **Linux**: `~/.config/Claude/claude_desktop_config.json`

### Integration with Claude Code

Add to `.claude.json` in your project:

```json
{
"mcpServers": {
"scryfall": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"--name", "scryfall-mcp-claude-code",
"-e", "LOG_LEVEL=info",
"scryfall-mcp:latest"
]
}
}
}
```

### Detailed Documentation

For comprehensive Docker documentation including:
- Architecture details and build stages
- Environment configuration
- Troubleshooting guide
- Best practices
- Advanced topics (Kubernetes, CI/CD, etc.)

See **[Docker Deployment Guide](docs/docker-deployment.md)**

## Tool Usage Examples

### Natural Language Query Building
Expand Down
51 changes: 51 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
services:
scryfall-mcp:
build:
context: .
dockerfile: Dockerfile
image: scryfall-mcp:latest
container_name: scryfall-mcp-server

# Environment variables (override .env.example defaults)
environment:
- NODE_ENV=production
- LOG_LEVEL=info
- SCRYFALL_USER_AGENT=ScryfallMCPServer/1.0.2-docker
- RATE_LIMIT_MS=100
- SCRYFALL_TIMEOUT_MS=15000
- HEALTHCHECK_DEEP=false
- RATE_LIMIT_QUEUE_MAX=500
- CACHE_MAX_SIZE=10000
- CACHE_MAX_MEMORY_MB=100

# Alternatively, use env_file for easier management
# Uncomment the following line and create a .env.docker file
# env_file:
# - .env.docker

# stdio transport mode - no ports exposed
# The container will communicate via stdin/stdout

# Resource limits (optional but recommended)
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M

# Restart policy
restart: unless-stopped

# Health check disabled for distroless image (no shell/utilities)
# Distroless is so minimal it doesn't include health check tools
# The restart policy will handle container failures automatically
# healthcheck:
# disable: true

# Optional: Define network for isolation
networks:
default:
name: scryfall-mcp-network
Loading