Show HN: Cloudflare Workers Compatible MCP Boilerplate with OAuth & PostgreSQL

3 days ago 3

A complete boilerplate for building remote Model Context Protocol (MCP) servers on Cloudflare Workers with custom OAuth authentication and PostgreSQL database integration.

This boilerplate provides everything you need to build production-ready MCP servers:

  • 🔐 Complete OAuth 2.1 Provider - Custom OAuth implementation with user registration/login
  • 🗄️ PostgreSQL Integration - Full database schema with user management and OAuth tokens
  • ⚡ Cloudflare Workers - Serverless deployment with global edge distribution
  • 🛠️ MCP Tools Framework - Modular tool system with user context
  • 🔌 Custom Routes Framework - Easy-to-use system for adding REST API endpoints
  • 🎨 Beautiful UI - Responsive login/registration pages with customizable consent screen
  • 🔒 Security First - JWT tokens, bcrypt hashing, PKCE support
  • 📱 Mobile Ready - Works on desktop, web, and mobile MCP clients
  • 🔧 Developer Friendly - TypeScript, hot reload, comprehensive error handling
  • add - Basic math operations
  • userInfo - Get current user information
  • personalGreeting - Personalized user greetings
  • generateImage - AI image generation
  • getUserStats - User statistics and analytics

⚡ Quick Start from index.ts

Everything starts from src/index.ts - this is your main configuration file where you can easily:

// Register all MCP tools with one line each backend .registerTool(registerMeTool) .registerTool(registerGreetingTool) .registerTool(registerAddTool) .registerTool(registerGenerateImageTool);

🔌 Add Custom REST API Endpoints

// Public endpoints (no authentication required) backend .route('GET', '/api/ping', (c) => { return c.json({ message: 'pong', timestamp: new Date().toISOString() }); }) .route('POST', '/api/echo', async (c) => { const body = await c.req.json(); return c.json({ echo: body, timestamp: new Date().toISOString() }); }); // Protected endpoints (OAuth token required) backend .authRoute('GET', '/api/profile', (c, userContext, env) => { return c.json({ message: 'User profile data', user: userContext, timestamp: new Date().toISOString() }); }) .authRoute('POST', '/api/user/settings', async (c, userContext, env) => { const settings = await c.req.json(); return c.json({ message: 'Settings updated successfully', userId: userContext.userId, settings, timestamp: new Date().toISOString() }); });

🎨 Customizable Consent Screen

The OAuth consent screen is fully customizable with pure HTML, CSS, and JavaScript located in src/auth/views/consent.ts. Features include:

  • 🌙 Dark/Light Mode Toggle - Automatic system preference detection with manual override
  • 📱 Responsive Design - Mobile-first design with Tailwind CSS
  • 🎨 Custom Branding - Easy logo, colors, and styling customization
  • ⚡ Real-time Updates - JavaScript-powered consent flow with loading states
  • 🔒 Security Features - CSRF protection and secure form handling

Customize the consent screen:

// In src/auth/views/consent.ts - modify the generateConsentPage function export function generateConsentPage(data: ConsentPageData, config: ConsentPageConfig = {}) { const { title = "Your Custom Title", // ← Customize title subtitle = "Your custom subtitle", // ← Customize subtitle logoUrl = "/your-logo.png", // ← Add your logo brandColor = "purple" // ← Change brand color } = config; // Full HTML/CSS/JS customization available in the template string return `<!DOCTYPE html>...`; }

The consent screen includes:

  • Application info display with logos
  • User profile information
  • Granular permission scopes
  • Terms of Service and Privacy Policy links
  • Beautiful animations and transitions
  • Error handling and loading states

🔌 Custom Routes Framework

This boilerplate includes a powerful custom routes framework that makes it easy to add REST API endpoints alongside your MCP tools:

Public Routes (No Authentication)

backend.route('GET', '/api/status', (c) => { return c.json({ status: 'operational', version: '1.0.0' }); }); backend.route('POST', '/api/webhook', async (c) => { const payload = await c.req.json(); // Process webhook return c.json({ received: true }); });

Protected Routes (OAuth Required)

backend.authRoute('GET', '/api/user/dashboard', (c, userContext, env) => { // userContext contains: { userId, name, username, email, scopes } return c.json({ welcomeMessage: `Hello ${userContext.name}!`, userStats: { /* ... */ } }); }); backend.authRoute('PUT', '/api/user/profile', async (c, userContext, env) => { const updates = await c.req.json(); // Update user profile in database return c.json({ success: true }); });
  • 🔒 Automatic Authentication - Protected routes get user context automatically
  • 📝 Type Safety - Full TypeScript support with proper typing
  • 🌐 HTTP Methods - Support for GET, POST, PUT, DELETE, PATCH
  • 📊 Request Handling - Easy access to headers, body, query params
  • 🔄 Response Helpers - JSON, HTML, redirect, and custom responses
  • ⚡ Performance - Built on Hono framework for maximum speed

This makes it easy to create complete applications with both MCP tools and traditional REST APIs!

Before you start, ensure you have:

# Clone the repository git clone https://github.com/f/mcp-cloudflare-boilerplate cd mcp-cloudfare-boilerplate # Install dependencies npm install # Install Wrangler CLI globally (if not already installed) npm install -g wrangler

2. Database Setup Options

Choose one of these PostgreSQL hosting options:

Option A: Neon (Recommended - Free Tier)

  1. Go to neon.tech and create account
  2. Create a new project
  3. Copy the connection string from the dashboard

Option B: Supabase (Free Tier)

  1. Go to supabase.com and create account
  2. Create a new project
  3. Go to Settings → Database and copy the connection string

Option C: Railway (Simple Setup)

  1. Go to railway.app and create account
  2. Create a new PostgreSQL database
  3. Copy the connection string from the Connect tab

Option D: Local PostgreSQL

# Using Docker (recommended for local development) docker run --name mcp-postgres \ -e POSTGRES_DB=mcpserver \ -e POSTGRES_USER=mcpuser \ -e POSTGRES_PASSWORD=mcppassword \ -p 5432:5432 \ -d postgres:15 # Connection string will be: # postgresql://mcpuser:mcppassword@localhost:5432/mcpserver

Database Optimization with Hyperdrive (Optional)

This boilerplate is optimized to work on Cloudflare but doesn't rely on Cloudflare systems - you can run it on your own servers with any hosting provider.

Using Cloudflare Hyperdrive (Recommended for Cloudflare deployment)

Cloudflare Hyperdrive dramatically improves database performance by connection pooling and global caching. To enable Hyperdrive:

  1. Create a Hyperdrive configuration in your Cloudflare dashboard:

    # Via Wrangler CLI wrangler hyperdrive create my-mcp-db --connection-string="postgresql://username:password@host:port/database"
  2. Update wrangler.toml to reference your Hyperdrive:

    [[hyperdrive]] binding = "HYPERDRIVE" id = "your-hyperdrive-id"
  3. Use Hyperdrive in your environment:

    # In .dev.vars or production secrets DATABASE_URL="postgresql://username:password@host:port/database" # Hyperdrive will automatically optimize this connection

Alternative: Running on Other Platforms

Don't want to use Cloudflare? Simply change your deployment environment:

  • Vercel: Use @vercel/node runtime with PostgreSQL
  • Railway: Deploy directly with built-in PostgreSQL
  • AWS Lambda: Use with RDS or Aurora Serverless
  • Google Cloud Run: Deploy with Cloud SQL
  • Self-hosted: Deploy with Docker and any PostgreSQL instance

The codebase uses standard PostgreSQL connections and OAuth2, making it platform-agnostic. Just update your:

  • Database connection string
  • Deployment configuration
  • Environment variables

No Cloudflare-specific dependencies are required for core functionality.

# Copy environment template cp .dev.vars.example .dev.vars # Edit .dev.vars with your settings nano .dev.vars

Add your configuration:

# Database Configuration DATABASE_URL="postgresql://username:password@host:port/database" # Security Keys (IMPORTANT: Generate strong, unique keys) JWT_SECRET="your-super-secret-jwt-key-at-least-32-characters-long" COOKIE_ENCRYPTION_KEY="exactly-32-characters-for-encryption" # Optional: Image Generation (if using Workers AI) # ALLOWED_IMAGE_USERS="username1,username2"

🔒 Security Note: Generate strong secrets:

# Generate JWT secret (64 characters) openssl rand -hex 32 # Generate cookie encryption key (32 characters) openssl rand -hex 16
# Login to Cloudflare wrangler login # Follow the browser authentication flow
# Start development server npm run dev # In another terminal, initialize database tables curl -X POST http://localhost:8787/init-db # Expected response: # {"message":"Database initialized successfully"}
  1. Open your browser: Go to http://localhost:8787
  2. Register account: Create a new user account
  3. Test login: Login with your credentials
  4. Check tools: Visit http://localhost:8787/up to see available tools
  1. Set production secrets:
# Set each secret securely (you'll be prompted to enter values) wrangler secret put JWT_SECRET wrangler secret put COOKIE_ENCRYPTION_KEY
  1. Deploy to Cloudflare:
  1. Initialize production database:
# Replace with your actual worker URL curl -X POST https://your-worker.your-subdomain.workers.dev/init-db
  1. Update OAuth settings: Update any OAuth client redirect URIs to use your production URL.
  1. In Cloudflare dashboard, go to Workers & Pages
  2. Select your worker
  3. Go to Settings → Triggers
  4. Add custom domain
# Development with hot reload npm run dev # Type checking npm run type-check # Build for production npm run build # Deploy to Cloudflare npm run deploy # Run tests (if you add them) npm test
mcp-cf-boilerplate/ ├── src/ │ ├── auth/ # Authentication system │ │ ├── handlers/ # OAuth & auth endpoints │ │ ├── middleware/ # Session management │ │ ├── oauth2.ts # OAuth2 server implementation │ │ └── views/ # Login/register UI │ ├── database/ # Database models & queries │ ├── server/ # MCP server implementation │ ├── tools/ # MCP tools (modular) │ ├── types.ts # TypeScript definitions │ └── index.ts # Main entry point ├── wrangler.toml # Cloudflare Workers config ├── package.json # Dependencies & scripts └── README.md # This file
  1. Create tool file: src/tools/myTool.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { UserContext } from "../types"; export function registerMyTool(server: McpServer, userContext: UserContext, env: Env) { server.tool("myTool", "Description of my tool", { input: { type: "object", properties: { message: { type: "string", description: "Input message" } }, required: ["message"] } }, async ({ message }) => { return { content: [{ type: "text", text: `Hello ${userContext.name}! You said: ${message}` }] }; }); }
  1. Export from index: Add to src/tools/index.ts
export { registerMyTool } from './myTool';
  1. Register in main: Add to src/index.ts
import { registerMyTool } from "./tools"; backend.registerTool(registerMyTool);

Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):

{ "mcpServers": { "my-mcp-server": { "command": "npx", "args": [ "mcp-remote", "https://your-worker.your-subdomain.workers.dev/mcp" ] } } }
  • POST /mcp - Main MCP endpoint (requires OAuth)
  • GET /up - Health check and server info
  • POST /init-db - Initialize database (run once)
  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Make your changes
  4. Add tests if applicable
  5. Commit changes: git commit -m 'Add amazing feature'
  6. Push to branch: git push origin feature/amazing-feature
  7. Open a Pull Request
  • Follow TypeScript best practices
  • Add JSDoc comments for public APIs
  • Update README for new features
  • Test OAuth flows thoroughly
  • Ensure database migrations are safe

MIT License - see LICENSE file for details.


Need help? Open an issue on GitHub or check the troubleshooting section above.

Read Entire Article