claude-mcp-server-template

asannikov/claude-mcp-server-template

3.2

If you are the rightful owner of claude-mcp-server-template and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to henry@mcphub.com.

This document provides a comprehensive guide to setting up and running a Model Context Protocol (MCP) server with HTTP/SSE transport support.

MCP Server Template

A minimal working template for an MCP (Model Context Protocol) server with HTTP/SSE transport support.

Quick Start

1. Installation

npm install
cp .env.example .env
# Edit .env and set your AUTH_TOKEN

2. Running

Local stdio (for development):

npm start

HTTP server (for remote access):

npm run http

With PM2 (for production):

pm2 start ecosystem.config.cjs
pm2 save

3. Connecting with Claude CLI

Local stdio:

claude mcp add my-server \
  --transport stdio \
  node /path/to/src/index.js

Local HTTP:

claude mcp add my-server-http \
  http://localhost:3020/sse \
  --transport sse \
  --header "Authorization: Bearer your-token"

Remote HTTPS (via nginx):

claude mcp add my-server-remote \
  https://your-domain.com:8443/sse \
  --transport sse \
  --header "Authorization: Bearer your-token"

Ports

The default port is 3020 (configurable via PORT in .env).

Why 3020? Port 3010 is used by the main project, so 3020 was chosen for the template.

Critical Pitfalls

1. HTTP/2 Is Incompatible with SSE

Problem: HTTP/2 multiplexing breaks SSE long-lived connections.

Solution: Use a separate HTTPS endpoint with HTTP/1.1:

server {
    listen 8443 ssl;           # WITHOUT http2!
    listen [::]:8443 ssl;

    location / {
        proxy_pass http://localhost:3020/;
        proxy_http_version 1.1;           # REQUIRED
        proxy_set_header Connection "";   # REQUIRED
        proxy_buffering off;              # REQUIRED
        proxy_read_timeout 86400;         # 24 hours for long-lived connections
    }
}

āŒ DON'T DO THIS:

listen 443 ssl http2;  # HTTP/2 will break SSE!

2. Nginx Proxy Settings

For SSE to work through nginx, special settings are required:

# HTTP/1.1 and remove Connection: close
proxy_http_version 1.1;
proxy_set_header Connection "";

# Disable buffering for streaming
proxy_buffering off;
proxy_cache off;
proxy_request_buffering off;

# Long timeouts for SSE (24 hours)
proxy_connect_timeout 30;
proxy_send_timeout 86400;
proxy_read_timeout 86400;

3. Docker Port Mapping

When adding a new port in docker-compose, --force-recreate is required:

# āŒ Insufficient
docker-compose restart nginx

# āœ… Correct
docker-compose up -d --force-recreate nginx

4. Claude CLI Health Check

Important: Claude CLI may show "Failed to connect" for public endpoints, even when they work. This is a cosmetic issue.

Functionality check:

curl -N -H "Accept: text/event-stream" \
  -H "Authorization: Bearer your-token" \
  http://localhost:3020/sse

If you see event: endpoint, it's working!

5. IPv4 vs IPv6

CRITICAL: Claude Code requires IPv6 support. You MUST listen on both IPv4 and IPv6!

In your Node.js server:

httpServer.listen(PORT, '0.0.0.0', () => { ... });  // IPv4

In nginx (BOTH lines are REQUIRED):

listen 8443 ssl;        # IPv4
listen [::]:8443 ssl;   # IPv6 - REQUIRED for Claude Code!

āŒ DON'T DO THIS:

listen 8443 ssl;  # Missing IPv6 - Claude Code won't work!

Nginx Configuration for Production

Option 1: Separate HTTPS Port (Recommended)

Create a new server block on port 8443:

# /etc/nginx/sites-available/mcp-server
server {
    listen 8443 ssl;
    listen [::]:8443 ssl;
    server_name your-domain.com;

    # SSL certificates
    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    # Modern SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';

    location / {
        proxy_pass http://localhost:3020/;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;

        # CRITICAL for SSE
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_buffering off;
        proxy_read_timeout 86400;
    }
}

Add the port to docker-compose.yml (if using Docker):

nginx:
  ports:
    - "443:443"
    - "8443:8443"  # MCP server

Option 2: SSH Tunnel

For maximum security:

# On the client
ssh -L 3020:localhost:3020 user@your-server.com -N &

# Then connect locally
claude mcp add my-server \
  http://localhost:3020/sse \
  --transport sse \
  --header "Authorization: Bearer your-token"

Health Checks

Health Check

curl http://localhost:3020/health

SSE Connection Test

curl -N -H "Accept: text/event-stream" \
  -H "Authorization: Bearer your-token" \
  http://localhost:3020/sse

Expected result:

event: endpoint
data: /messages?sessionId=template-xxx-xxx-xxx

HTTP/1.1 Verification (for HTTPS)

curl -v https://your-domain.com:8443/health 2>&1 | grep ALPN

Should show:

* ALPN, server accepted to use http/1.1

Should NOT show h2 (HTTP/2)!

Security

Token Generation

node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

Recommendations

  1. Don't use HTTP without SSL for production
  2. Rotate tokens regularly
  3. Use different tokens for different clients
  4. Monitor logs for suspicious activity
  5. Use SSH tunnel or VPN for maximum security

Project Structure

mcp-server-template/
ā”œā”€ā”€ src/
│   ā”œā”€ā”€ index.js          # Stdio server (local)
│   └── http-server.js    # HTTP server (remote)
ā”œā”€ā”€ package.json
ā”œā”€ā”€ .env.example
ā”œā”€ā”€ .gitignore
ā”œā”€ā”€ ecosystem.config.cjs  # PM2 configuration
└── README.md

Extension

Add your tools in the tools/list handler:

server.setRequestHandler('tools/list', async () => {
  return {
    tools: [
      {
        name: 'your_tool',
        description: 'What your tool does',
        inputSchema: {
          type: 'object',
          properties: {
            param: { type: 'string', description: 'Parameter description' }
          },
          required: ['param']
        }
      }
    ]
  };
});

server.setRequestHandler('tools/call', async (request) => {
  if (request.params.name === 'your_tool') {
    // Your implementation
    return {
      content: [{ type: 'text', text: 'Result' }]
    };
  }
});

Troubleshooting

"Failed to connect" in Claude CLI

  1. Check the health endpoint:

    curl http://localhost:3020/health
    
  2. Check the SSE endpoint:

    curl -N -H "Authorization: Bearer TOKEN" http://localhost:3020/sse
    
  3. For HTTPS, verify HTTP/1.1:

    curl -v https://domain:8443/health 2>&1 | grep ALPN
    

Nginx won't start

nginx -t  # Check configuration
tail -f /var/log/nginx/error.log

Server not responding

pm2 status
pm2 logs mcp-template-http --lines 50
netstat -tlnp | grep 3020

Useful Links

License

MIT