Skip to content

Latest commit

 

History

History
420 lines (308 loc) · 16.1 KB

File metadata and controls

420 lines (308 loc) · 16.1 KB

Deploy Your Own MCP File Vault on Cloudflare Workers

A step-by-step guide to deploying a remote MCP server that gives Claude (or any MCP client) read/write access to a personal file vault backed by Cloudflare R2, with GitHub OAuth authentication.

By the end of this guide, you'll have a working MCP server that Claude.ai can connect to natively — no browser extensions, no local servers, no mcp-remote proxy needed.

What You'll Build

A Cloudflare Worker that exposes 9 MCP tools for file management:

  • Read/write/delete text files (markdown, JSON, YAML, code)
  • List, search, browse the file tree
  • Upload/download any file type via presigned URLs
  • Health check to verify the server is operational

The server authenticates users via GitHub OAuth and restricts access to a single authorized GitHub account (yours).

Prerequisites

Requirement Why
Node.js 18+ To run wrangler and build the project
A Cloudflare account Free tier is sufficient for Workers, KV, and R2
A GitHub account Used for OAuth authentication
wrangler CLI npm install -g wrangler (Cloudflare's CLI)

You should also be comfortable with the command line and have a basic understanding of what API keys and environment variables are.

Architecture Overview

MCP Client (Claude.ai, MCP Inspector)
    |
    | HTTPS (Streamable HTTP transport)
    v
┌──────────────────────────────────────────────┐
│  OAuthProvider wrapper                        │
│  - Intercepts /authorize, /token, /register  │
│  - Validates OAuth tokens on /mcp            │
├──────────────────────────────────────────────┤
│  /mcp → MCP Server                           │
│  - 9 file management tools                   │
│  - Reads/writes to R2 via internal binding   │
├──────────────────────────────────────────────┤
│  /authorize, /callback → GitHub OAuth        │
│  - Redirects to GitHub for login             │
│  - Verifies user identity (ID + username)    │
│  - Calls completeAuthorization() on success  │
├──────────────────────────────────────────────┤
│  KV Namespace (OAUTH_KV)                     │
│  - Stores OAuth tokens and sessions          │
├──────────────────────────────────────────────┤
│  R2 Bucket (VAULT)                           │
│  - Stores all vault files                    │
└──────────────────────────────────────────────┘

The key insight: @cloudflare/workers-oauth-provider wraps the entire Worker. It intercepts OAuth requests, manages tokens, and only passes authenticated requests to the MCP server. You don't handle token generation yourself — the library does it.


Step 1: Clone and Install

git clone https://github.com/your-username/second-brain-vault.git
cd second-brain-vault
npm install

Step 2: Authenticate with Cloudflare

npx wrangler login

This opens a browser window. Authorize wrangler to access your Cloudflare account.

Step 3: Create Cloudflare Resources

You need two resources: a KV namespace for OAuth session storage and an R2 bucket for file storage.

3a: KV Namespace

npx wrangler kv namespace create "OAUTH_KV"

This returns something like:

✨ Successfully created KV namespace "OAUTH_KV" with id "abc123def456..."

Copy the ID and paste it into wrangler.jsonc:

"kv_namespaces": [
  {
    "binding": "OAUTH_KV",
    "id": "abc123def456..."  // ← paste your ID here
  }
]

3b: R2 Bucket

npx wrangler r2 bucket create mcp-vault

The bucket name mcp-vault matches what's already in wrangler.jsonc. If you choose a different name, update the bucket_name field in the config.

3c: R2 API Keys (for presigned URLs)

The get_upload_url and get_download_url tools use presigned URLs, which require R2 API credentials (separate from your Cloudflare API token).

  1. Go to Cloudflare Dashboard > R2 > Manage R2 API Tokens
  2. Click Create API Token
  3. Give it Object Read & Write permission on the mcp-vault bucket
  4. Copy the Access Key ID and Secret Access Key — you'll need them in Step 5

Step 4: Create a GitHub OAuth App

You need a GitHub OAuth App to handle authentication. You'll create two: one for local development and one for production.

4a: Local Development OAuth App

  1. Go to GitHub Developer Settings > OAuth Apps
  2. Click New OAuth App
  3. Fill in:
    • Application name: MCP Vault (local)
    • Homepage URL: http://localhost:8787
    • Authorization callback URL: http://localhost:8787/callback
  4. Click Register application
  5. Copy the Client ID
  6. Click Generate a new client secret and copy it immediately

4b: Find Your GitHub User ID

You need your numeric GitHub user ID for the access control. Run:

curl -s https://api.github.com/users/YOUR_GITHUB_USERNAME | grep '"id"'

This returns something like "id": 12345678. Note this number.

Step 5: Configure Local Development

Create a .dev.vars file at the project root (this is wrangler's format for local secrets):

GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
COOKIE_ENCRYPTION_KEY=generate-with-openssl-rand-hex-32
R2_ACCESS_KEY_ID=your-r2-api-key-id
R2_SECRET_ACCESS_KEY=your-r2-api-secret
CF_ACCOUNT_ID=your-cloudflare-account-id
ALLOWED_GITHUB_ID=your-github-user-id
ALLOWED_GITHUB_LOGIN=your-github-username

To generate the cookie encryption key:

openssl rand -hex 32

Your Cloudflare Account ID is visible on the Cloudflare dashboard main page, in the right sidebar.

Step 6: Test Locally

npm run dev

The server starts on http://localhost:8787. Visit it in your browser — you should see the MCP Vault home page.

Test with MCP Inspector

npx @modelcontextprotocol/inspector@latest
  1. Open http://localhost:5173 in your browser
  2. Enter http://localhost:8787/mcp as the server URL
  3. In OAuth Settings, enable Quick OAuth Flow
  4. Click Connect — you'll be redirected to GitHub for authorization
  5. After authorizing, click List Tools

You should see all 9 tools. Try calling alive to confirm everything works.

Step 7: Deploy to Production

7a: Create a Production GitHub OAuth App

Go back to GitHub Developer Settings and create a second OAuth App:

  • Application name: MCP Vault (prod)
  • Homepage URL: https://your-worker-name.your-subdomain.workers.dev
  • Authorization callback URL: https://your-worker-name.your-subdomain.workers.dev/callback

Replace the URL with your actual Worker URL. The Worker name comes from the "name" field in wrangler.jsonc (second-brain-vault by default). Your subdomain is your Cloudflare Workers subdomain.

7b: Set Production Secrets

npx wrangler secret put GITHUB_CLIENT_ID
npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put COOKIE_ENCRYPTION_KEY
npx wrangler secret put R2_ACCESS_KEY_ID
npx wrangler secret put R2_SECRET_ACCESS_KEY
npx wrangler secret put CF_ACCOUNT_ID
npx wrangler secret put ALLOWED_GITHUB_ID
npx wrangler secret put ALLOWED_GITHUB_LOGIN

Wrangler prompts you interactively for each value. Use the production GitHub OAuth App credentials (not the local ones).

7c: Deploy

npm run deploy

Your MCP server is now live at https://your-worker-name.your-subdomain.workers.dev/mcp.

Step 8: Connect to Claude

Claude.ai (Web)

  1. Go to Settings > Integrations in Claude.ai
  2. Click Add custom integration
  3. Enter your Worker URL: https://your-worker-name.your-subdomain.workers.dev/mcp
  4. Claude will redirect you to GitHub for authentication
  5. Once authorized, the vault tools appear in your conversations

Claude Desktop

Add to your Claude Desktop config (claude_desktop_config.json):

{
  "mcpServers": {
    "vault": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://your-worker-name.your-subdomain.workers.dev/mcp"
      ]
    }
  }
}

Restart Claude Desktop. On first use, a GitHub authorization window opens.

Step 9: Verify Everything Works

Test Command / Action Expected Result
Server responds Call alive tool Success message
Write a file Call write_file with key: "test/hello.md" Write confirmation
Read it back Call read_file with key: "test/hello.md" File content returned
List files Call list_files Shows test/hello.md
Browse tree Call tree Shows directory structure
Search Call search_files with query: "hello" Finds the test file
Delete Call delete_file with key: "test/hello.md", confirm: true Deletion confirmed

Optional: Local File Sync with rclone

If you want to sync vault files to a local folder (so you can edit them in your text editor and have changes reflected in the vault), you can set up rclone.

Install rclone

# macOS
brew install rclone

# Linux
curl https://rclone.org/install.sh | sudo bash

Configure R2 Remote

rclone config

Create a new remote with these settings:

  • Name: r2-vault
  • Type: s3
  • Provider: Cloudflare
  • Access Key ID: your R2 API key ID
  • Secret Access Key: your R2 API secret
  • Endpoint: https://<YOUR_ACCOUNT_ID>.r2.cloudflarestorage.com

Run Initial Sync

rclone bisync ~/documents/vault r2-vault:mcp-vault --create-empty-src-dirs --resync

The --resync flag is required on the first run to establish the baseline.

Automate with Cron

crontab -e

Add:

*/5 * * * * /usr/local/bin/rclone bisync ~/documents/vault r2-vault:mcp-vault --create-empty-src-dirs --exclude "*.log" --exclude ".DS_Store" 2>&1 >> ~/documents/.vault-sync.log

This syncs every 5 minutes. The log file is stored outside the sync folder to avoid syncing it back to R2.


Understanding the OAuth Flow

This section explains what happens under the hood when Claude connects to your MCP server. You don't need to understand this to use the server, but it helps if you want to customize it or troubleshoot issues.

The Problem

Claude.ai's MCP connector expects the server to implement OAuth 2.1. When Claude connects to a custom MCP server, it performs OAuth discovery: it hits /.well-known/oauth-protected-resource, /.well-known/oauth-authorization-server, and /register. If these endpoints don't exist, Claude gives up and never sends an actual MCP request.

This means you can't just expose a simple HTTP endpoint — you need a full OAuth server.

The Solution

The @cloudflare/workers-oauth-provider library handles all of this. It's a wrapper around your Worker that:

  1. Responds to OAuth discovery requests
  2. Handles dynamic client registration (/register)
  3. Manages the token lifecycle (/token)
  4. Validates tokens on every request to /mcp
  5. Injects env.OAUTH_PROVIDER into your handlers

The Flow

Claude.ai                     Worker                         GitHub
   |                            |                              |
   |-- GET /.well-known/* ----->|                              |
   |<-- OAuth metadata --------|                              |
   |                            |                              |
   |-- POST /register -------->|                              |
   |<-- client_id, secret -----|                              |
   |                            |                              |
   |-- GET /authorize -------->|                              |
   |   (OAuthProvider parses)  |-- redirect to GitHub ------->|
   |                            |                              |
   |                            |<-- redirect with code -------|
   |                            |                              |
   |                            |-- POST github.com/access_token
   |                            |<-- GitHub access token ------|
   |                            |                              |
   |                            |-- GET api.github.com/user ---|
   |                            |<-- user info (id, login) ----|
   |                            |                              |
   |                            | Check: id == ALLOWED_GITHUB_ID
   |                            |        login == ALLOWED_GITHUB_LOGIN
   |                            |                              |
   |                            | completeAuthorization()      |
   |<-- redirect with code ----|                              |
   |                            |                              |
   |-- POST /token ----------->|                              |
   |<-- MCP access token ------|                              |
   |                            |                              |
   |-- POST /mcp (with token)->|                              |
   |<-- tool results -----------|                              |

Key Insight

The most common mistake when building this is trying to generate OAuth codes and tokens yourself. Don't. The OAuthProvider wrapper handles all of that internally. Your handler's only job is:

  1. /authorize: Parse the OAuth request with env.OAUTH_PROVIDER.parseAuthRequest(), store it in KV, and redirect to GitHub.
  2. /callback: Exchange GitHub's code for a token, verify the user, then call env.OAUTH_PROVIDER.completeAuthorization(). That function generates the MCP authorization code and returns a redirect URL.

That's it. You never touch token generation, code exchange, or any other OAuth plumbing.


Troubleshooting

Problem Cause Fix
Claude says "Failed to connect" OAuth callback URL mismatch Check the callback URL in your GitHub OAuth App matches your Worker URL exactly
InvalidGrantError: Invalid authorization code format You're generating codes manually Use env.OAUTH_PROVIDER.completeAuthorization() instead
403 "Access denied" after GitHub login Your GitHub ID or username doesn't match Verify ALLOWED_GITHUB_ID and ALLOWED_GITHUB_LOGIN secrets
alive works but read_file returns errors R2 bucket not created or binding missing Check wrangler.jsonc has the R2 binding and the bucket exists
Presigned URLs fail with checksum errors AWS SDK auto-checksum issue Make sure requestChecksumCalculation: "WHEN_REQUIRED" is set in the S3 client config
Local dev works but production doesn't Using local OAuth App credentials in production Create a separate GitHub OAuth App with the production callback URL

Uninstall

To cleanly remove everything:

Resource How to Remove
Worker npx wrangler delete from the project folder
KV Namespace Cloudflare Dashboard > Workers & Pages > KV > Delete
R2 Bucket Cloudflare Dashboard > R2 > mcp-vault > Delete
R2 API Token Cloudflare Dashboard > R2 > Manage R2 API Tokens > Revoke
GitHub OAuth App (local) GitHub > Settings > Developer settings > OAuth Apps > Delete
GitHub OAuth App (prod) GitHub > Settings > Developer settings > OAuth Apps > Delete
rclone config rm -rf ~/.config/rclone/
Cron job crontab -e then delete the rclone line

Further Reading