|
| 1 | +--- |
| 2 | +name: run-mcp-local |
| 3 | +description: Run the everyrow HTTP MCP server locally with Docker Compose and optionally expose it via Cloudflare tunnel. Use when starting/stopping the local MCP server, debugging startup issues, connecting Claude.ai or Claude Desktop to a local instance, or checking server logs. Triggers on mcp local, mcp server, run mcp, mcp docker, mcp tunnel, cloudflare tunnel, mcp logs. |
| 4 | +--- |
| 5 | + |
| 6 | +# Running the everyrow MCP Server Locally |
| 7 | + |
| 8 | +Two-container stack: **mcp-server** (FastAPI on :8000) and **redis** (on :6379), orchestrated by `everyrow-mcp/deploy/docker-compose.yaml` with local overrides. |
| 9 | + |
| 10 | +## Pre-flight Checks |
| 11 | + |
| 12 | +**CRITICAL: Always check for stale processes on port 8000 before starting.** |
| 13 | + |
| 14 | +A leftover `everyrow-mcp --no-auth` or similar process on the host will shadow the Docker container's port binding. All requests hit the stale process instead of the container — this can look like auth routes are broken, sheets tools are missing, etc. |
| 15 | + |
| 16 | +```bash |
| 17 | +# Check for anything on port 8000 |
| 18 | +lsof -i :8000 |
| 19 | + |
| 20 | +# Kill if needed |
| 21 | +lsof -ti :8000 | xargs kill -9 |
| 22 | +``` |
| 23 | + |
| 24 | +Also check Docker is running: |
| 25 | +```bash |
| 26 | +docker info --format '{{.ServerVersion}}' || colima start |
| 27 | +``` |
| 28 | + |
| 29 | +## Quick Start |
| 30 | + |
| 31 | +```bash |
| 32 | +cd everyrow-mcp/deploy |
| 33 | + |
| 34 | +REDIS_PASSWORD=testpass \ |
| 35 | +MCP_SERVER_URL=http://localhost:8000 \ |
| 36 | + docker compose \ |
| 37 | + -f docker-compose.yaml \ |
| 38 | + -f docker-compose.local.yaml \ |
| 39 | + up -d --build |
| 40 | +``` |
| 41 | + |
| 42 | +Verify: `curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/health` should return `200`. |
| 43 | + |
| 44 | +### Optional env vars |
| 45 | + |
| 46 | +Pass these alongside `REDIS_PASSWORD` and `MCP_SERVER_URL`: |
| 47 | + |
| 48 | +| Env var | Default | Purpose | |
| 49 | +|---------|---------|---------| |
| 50 | +| `ENABLE_SHEETS_TOOLS` | `false` | Register Google Sheets tools | |
| 51 | +| `TRUST_PROXY_HEADERS` | `false` | Trust X-Forwarded-For (required behind tunnel) | |
| 52 | +| `EXTRA_ALLOWED_HOSTS` | (empty) | Extra hostnames for DNS rebinding allowlist | |
| 53 | + |
| 54 | +These are templated in `docker-compose.local.yaml` as `${VAR:-default}` — the container must be **recreated** (not just restarted) for env var changes to take effect. |
| 55 | + |
| 56 | +## Secrets |
| 57 | + |
| 58 | +The `.env` file at `everyrow-mcp/deploy/.env` contains production secrets (Supabase, API keys, upload secret). It is already present and should NOT be committed or overwritten. |
| 59 | + |
| 60 | +`REDIS_PASSWORD` is intentionally NOT in `.env` — always pass it as an env var (`testpass` for local dev). |
| 61 | + |
| 62 | +### Worktrees |
| 63 | + |
| 64 | +The `.env` file is gitignored and won't exist in worktrees. Symlink it: |
| 65 | + |
| 66 | +```bash |
| 67 | +ln -s /Users/rafaelpoyiadzi/Documents/git/everyrow-sdk/everyrow-mcp/deploy/.env \ |
| 68 | + <worktree-path>/everyrow-mcp/deploy/.env |
| 69 | +``` |
| 70 | + |
| 71 | +## Exposing via Cloudflare Tunnel |
| 72 | + |
| 73 | +Required when testing with Claude.ai or Claude Desktop, which can't reach `localhost`. |
| 74 | + |
| 75 | +### Step 1: Kill stale tunnels and processes |
| 76 | + |
| 77 | +```bash |
| 78 | +pkill -f cloudflared 2>/dev/null |
| 79 | +rm -f /tmp/cf-tunnel.log |
| 80 | +lsof -ti :8000 | xargs kill -9 2>/dev/null |
| 81 | +``` |
| 82 | + |
| 83 | +### Step 2: Start the tunnel |
| 84 | + |
| 85 | +```bash |
| 86 | +cloudflared tunnel --url http://localhost:8000 2>/tmp/cf-tunnel.log & |
| 87 | +sleep 6 |
| 88 | +grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' /tmp/cf-tunnel.log | head -1 |
| 89 | +``` |
| 90 | + |
| 91 | +This prints a URL like `https://something-something.trycloudflare.com`. |
| 92 | + |
| 93 | +### Step 3: Start (or restart) the MCP server with the tunnel URL |
| 94 | + |
| 95 | +The server must know its public URL for OAuth redirects to work: |
| 96 | + |
| 97 | +```bash |
| 98 | +cd everyrow-mcp/deploy |
| 99 | + |
| 100 | +REDIS_PASSWORD=testpass \ |
| 101 | +MCP_SERVER_URL=https://something-something.trycloudflare.com \ |
| 102 | +TRUST_PROXY_HEADERS=true \ |
| 103 | +ENABLE_SHEETS_TOOLS=true \ |
| 104 | + docker compose \ |
| 105 | + -f docker-compose.yaml \ |
| 106 | + -f docker-compose.local.yaml \ |
| 107 | + up -d --build |
| 108 | +``` |
| 109 | + |
| 110 | +Key: `MCP_SERVER_URL` must match the tunnel URL exactly, and `TRUST_PROXY_HEADERS=true` is required so the server trusts the forwarded headers from Cloudflare. |
| 111 | + |
| 112 | +### Step 4: Verify OAuth discovery works end-to-end |
| 113 | + |
| 114 | +```bash |
| 115 | +# Through the tunnel (what Claude.ai sees) |
| 116 | +curl -s https://<tunnel-url>/.well-known/oauth-authorization-server | python3 -m json.tool | head -5 |
| 117 | + |
| 118 | +# Locally |
| 119 | +curl -s http://localhost:8000/.well-known/oauth-authorization-server | python3 -m json.tool | head -5 |
| 120 | +``` |
| 121 | + |
| 122 | +Both should return JSON with `issuer`, `authorization_endpoint`, etc. If local returns 404 but tunnel works (or vice versa), check for stale processes on port 8000. |
| 123 | + |
| 124 | +### Step 5: Connect clients |
| 125 | + |
| 126 | +**Claude.ai / Claude Desktop**: Use the tunnel URL as the MCP server URL in the client config. |
| 127 | + |
| 128 | +**Claude Code**: Add a project-scoped MCP server (writes to `.claude/settings.local.json` in the current dir, not the global config): |
| 129 | + |
| 130 | +```bash |
| 131 | +claude mcp add everyrow --scope project --transport http <TUNNEL_URL>/mcp |
| 132 | +``` |
| 133 | + |
| 134 | +Then restart Claude Code. Remove with `claude mcp remove everyrow --scope project`. |
| 135 | + |
| 136 | +## Logs |
| 137 | + |
| 138 | +```bash |
| 139 | +# All logs |
| 140 | +docker logs deploy-mcp-server-1 -f |
| 141 | + |
| 142 | +# Filter for errors |
| 143 | +docker logs deploy-mcp-server-1 2>&1 | grep -iE "error|warn|401|500" |
| 144 | + |
| 145 | +# Check User-Agent strings (for widget/client detection work) |
| 146 | +docker logs deploy-mcp-server-1 2>&1 | grep "User-Agent" |
| 147 | +``` |
| 148 | + |
| 149 | +## Teardown |
| 150 | + |
| 151 | +```bash |
| 152 | +cd everyrow-mcp/deploy |
| 153 | + |
| 154 | +REDIS_PASSWORD=testpass MCP_SERVER_URL=http://localhost:8000 \ |
| 155 | + docker compose -f docker-compose.yaml -f docker-compose.local.yaml down |
| 156 | +``` |
| 157 | + |
| 158 | +Kill the tunnel: `pkill -f cloudflared` or `kill %1` if it was backgrounded. |
| 159 | + |
| 160 | +## No-Auth Mode (without Docker) |
| 161 | + |
| 162 | +Run the server directly with `uv run` — no Docker needed. Useful for quick local testing with the MCP Inspector. |
| 163 | + |
| 164 | +**WARNING:** If you leave this running and later start the Docker stack, the local process will shadow Docker's port 8000. Always kill it first: `lsof -ti :8000 | xargs kill -9` |
| 165 | + |
| 166 | +### Prerequisites |
| 167 | + |
| 168 | +- Redis running on localhost:6379 (e.g. `docker run -d --name test-redis -p 6379:6379 redis:7-alpine`) |
| 169 | +- `EVERYROW_API_KEY` in `~/.claude/secrets/remote.env` |
| 170 | + |
| 171 | +### Start the server |
| 172 | + |
| 173 | +```bash |
| 174 | +cd everyrow-mcp |
| 175 | +ALLOW_NO_AUTH=1 \ |
| 176 | +UPLOAD_SECRET=$(python -c "import secrets; print(secrets.token_urlsafe(32))") \ |
| 177 | +EXTRA_ALLOWED_HOSTS="host.docker.internal,localhost" \ |
| 178 | + bash scripts/run-no-auth.sh |
| 179 | +``` |
| 180 | + |
| 181 | +### Connect with MCP Inspector |
| 182 | + |
| 183 | +1. Start the Inspector: `npx @modelcontextprotocol/inspector` |
| 184 | +2. Open the URL it prints (includes `MCP_PROXY_AUTH_TOKEN`) |
| 185 | +3. Settings: |
| 186 | + - Transport: **Streamable HTTP** |
| 187 | + - Mode: **Via Proxy** |
| 188 | + - URL: `http://localhost:8000/mcp` |
| 189 | + - Leave all OAuth fields **blank** |
| 190 | +4. Click **Connect** |
| 191 | + |
| 192 | +Note: Direct mode won't work (CORS). Auth mode won't work (Inspector v0.21.0 doesn't handle the OAuth flow). Use Via Proxy with no-auth. |
| 193 | + |
| 194 | +### Connect with SDK client |
| 195 | + |
| 196 | +```bash |
| 197 | +uv run python scripts/mcp_call.py list |
| 198 | +uv run python scripts/mcp_call.py call everyrow_balance |
| 199 | +uv run python scripts/mcp_call.py call everyrow_agent '{"params": {"task": "...", "data": [...]}}' |
| 200 | +``` |
| 201 | + |
| 202 | +Note: `mcp_call.py` only works against `--no-auth` servers. It doesn't do OAuth, so authenticated servers will show a subset of tools or fail. |
| 203 | + |
| 204 | +## Common Issues |
| 205 | + |
| 206 | +| Problem | Solution | |
| 207 | +|---------|----------| |
| 208 | +| `required variable REDIS_PASSWORD is missing` | Pass `REDIS_PASSWORD=testpass` as env var | |
| 209 | +| `required variable MCP_SERVER_URL is missing` | Pass `MCP_SERVER_URL=http://localhost:8000` (or tunnel URL) | |
| 210 | +| OAuth 401 when connecting via tunnel | `MCP_SERVER_URL` doesn't match the tunnel URL, or `TRUST_PROXY_HEADERS=true` is missing | |
| 211 | +| OAuth discovery returns 404 | **Check `lsof -i :8000`** — a stale local process is likely shadowing Docker. Kill it and restart containers with `down`/`up` (not just `restart`) | |
| 212 | +| Port 8000 already in use | `lsof -ti :8000 | xargs kill -9` then restart | |
| 213 | +| Redis connection refused | Check redis container is healthy: `docker ps | grep redis` | |
| 214 | +| cloudflared output is empty | It writes to stderr: use `2>/tmp/cf-tunnel.log` redirect | |
| 215 | +| Container doesn't pick up code changes | Add `--build` to the `docker compose up` command | |
| 216 | +| Container doesn't pick up env var changes | Must recreate: `docker compose ... down && docker compose ... up -d` | |
| 217 | +| `.env` not found in worktree | Symlink from main repo: `ln -s <main>/everyrow-mcp/deploy/.env <worktree>/everyrow-mcp/deploy/.env` | |
| 218 | +| Sheets tools not showing in tool list | Pass `ENABLE_SHEETS_TOOLS=true` and recreate the container | |
| 219 | +| `mcp_call.py` shows fewer tools than expected | It connects without auth — authenticated servers may filter tools | |
| 220 | +| Docker daemon not running | `colima start` (may need `colima stop && colima start` if socket is stale) | |
| 221 | + |
| 222 | +## Key Files |
| 223 | + |
| 224 | +| File | Purpose | |
| 225 | +|------|---------| |
| 226 | +| `everyrow-mcp/deploy/docker-compose.yaml` | Base compose (server + redis) | |
| 227 | +| `everyrow-mcp/deploy/docker-compose.local.yaml` | Local overrides (ports, env passthrough) | |
| 228 | +| `everyrow-mcp/deploy/.env` | Production secrets (DO NOT commit changes) | |
| 229 | +| `everyrow-mcp/deploy/Dockerfile` | Server container build | |
0 commit comments