# MCP Dealer Lookup Server

Read-only MCP server for looking up dealer and user records with live Stripe billing data. Used by admins via Claude Code for customer support.

## Architecture

The server runs as a separate PM2 process on the production server, accessible via HTTPS through a Cloudflare-fronted subdomain.

```
Claude Code → HTTPS → Cloudflare → Apache (proxy) → PM2 (port 3010) → MCP Server
                                                                        ├── PostgreSQL (prod DB)
                                                                        └── Stripe API (prod keys)
```

```
Admin's machine                    Production server (InterServer VPS)
┌──────────────┐                  ┌──────────────────────────────────────┐
│ Claude Code  │                  │                                      │
│  .mcp.json:  │   HTTPS POST    │  Cloudflare ──► Apache ──► PM2       │
│  url: https: │ ───────────────► │  (subdomain)    (proxy)   (port 3010)│
│  //subdomain │   Bearer token   │                            │         │
│  /mcp        │                  │              ┌─────────────┘         │
│              │                  │              ▼                       │
└──────────────┘                  │  mcp/dealer-lookup/index.ts          │
                                  │  ├── IP allowlist (office IP only)    │
                                  │  ├── Rate limiting (60 req/min)      │
                                  │  ├── Bearer token check              │
                                  │  ├── StreamableHTTP transport        │
                                  │  ├── Prisma → PostgreSQL (prod DB)   │
                                  │  └── Stripe API (prod keys)          │
                                  │                                      │
                                  │  PM2 also runs:                      │
                                  │  ├── amsoil-dlp-staging   (port 3000)│
                                  │  └── amsoil-dlp-production(port 3001)│
                                  └──────────────────────────────────────┘
```

## Tools

### Live data tools (read-only, hits prod DB and Stripe)

| Tool              | Description                                                                  |
| ----------------- | ---------------------------------------------------------------------------- |
| `search_dealers`  | Search by name, email, subdomain, phone, dealer number                       |
| `get_dealer`      | Get dealer by ID with record counts and Stripe links                         |
| `search_users`    | Search by email, name, or role                                               |
| `get_user`        | Get user by ID with dealer and OAuth provider info                           |
| `get_stripe_info` | Live Stripe billing data: customer, subscriptions, invoices, payment methods |

### Documentation tools (reads markdown from `docs/`)

| Tool               | Description                                                                                                           |
| ------------------ | --------------------------------------------------------------------------------------------------------------------- |
| `support_overview` | Returns `docs/SUPPORT_OVERVIEW.md` - curated runbook for common dealer issues. Start here when answering a new topic. |
| `list_docs`        | Lists all `.md` files with title and first paragraph. Excludes `docs/archive` and `docs/plans` by default.            |
| `search_docs`      | Case-insensitive keyword search across docs. Returns file path, line number, snippet for each hit.                    |
| `get_doc`          | Returns full content of a specific doc by path relative to docs dir. Path-traversal protected.                        |

## Security

The MCP endpoint (`/mcp`) is protected by three layers:

1. **IP allowlist** - Only requests from the office IP (`47.49.48.146`) and localhost are accepted. The server checks `CF-Connecting-IP` (Cloudflare), `X-Forwarded-For` (Apache), then `req.ip`. Additional IPs can be added via the `MCP_ALLOWED_IPS` env var (comma-separated).
2. **Rate limiting** - 60 requests per minute per IP (in-memory sliding window). Prevents abuse if a token is compromised.
3. **Bearer token auth** - SHA-256 hashed timing-safe comparison. No length oracle.

The `/health` endpoint is unauthenticated (for monitoring) but verifies database connectivity.

## Admin Setup

Each admin needs to set the auth token in their shell environment:

```bash
# Add to ~/.bashrc, ~/.zshrc, or equivalent:
export MCP_DEALER_LOOKUP_TOKEN="<ask team lead for token>"
```

The `.mcp.json` in the repo is already configured. Once the env var is set, Claude Code will auto-discover the server.

To verify the connection:

```bash
claude mcp list
# Should show "dealer-lookup" as connected
```

## Production Server Setup

### 1. Generate auth token

```bash
openssl rand -base64 32
```

Add to the production `.env.local` at `/home/amsoildlp/public_html/amsoil.aimclear.com/.env.local`:

```
MCP_AUTH_TOKEN=<generated-token>
```

**`MCP_DOCS_DIR`** (optional) - Override the directory the doc tools read from. Default: `<repo>/docs` (resolved relative to `mcp/dealer-lookup/tools/docs.ts`). Useful for running with a different docs source or for tests.

### 2. Start PM2 process

```bash
cd /home/amsoildlp/public_html/amsoil.aimclear.com
pm2 start mcp/dealer-lookup/ecosystem.config.cjs
pm2 save
```

### 3. Apache proxy config

Add a new VirtualHost for the MCP subdomain. On the InterServer VPS this goes in the Apache config managed via cPanel/WHM:

```apache
<VirtualHost *:443>
    ServerName amsoil-dlp-mcp.acdev3.com

    SSLEngine on
    # SSL certs managed by Cloudflare origin certificates or Let's Encrypt

    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:3010/
    ProxyPassReverse / http://127.0.0.1:3010/

    # Only allow Cloudflare IPs (configured in server firewall)
</VirtualHost>
```

After adding the config:

```bash
systemctl restart httpd
```

### 4. Cloudflare DNS

Add an A record in the Cloudflare dashboard for the `acdev3.com` zone:

- Name: `amsoil-dlp-mcp`
- Content: `<SERVER_IP>` (get from team lead or existing DNS records)
- Proxy status: Proxied (orange cloud)

### 5. Verify

```bash
# Health check (no auth required)
curl https://amsoil-dlp-mcp.acdev3.com/health
# Expected: {"status":"ok","server":"amsoil-dealer-lookup","version":"1.0.0"}

# Auth test
curl -X POST https://amsoil-dlp-mcp.acdev3.com/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}'
# Expected: 200 with MCP initialize response
```

## Token Rotation

1. Generate new token: `openssl rand -base64 32`
2. Update production `.env.local` with new `MCP_AUTH_TOKEN`
3. Reload: `pm2 reload amsoil-dlp-mcp`
4. Distribute new token to admins
5. Each admin updates their `MCP_DEALER_LOOKUP_TOKEN` env var and reloads their shell

## Troubleshooting

```bash
# Check if MCP server is running
pm2 list | grep mcp

# View logs
pm2 logs amsoil-dlp-mcp --lines 50

# Test locally on server (no Cloudflare/Apache in the path)
curl http://localhost:3010/health

# Restart
pm2 reload amsoil-dlp-mcp

# Check Apache proxy is passing traffic
tail -f /var/log/httpd/access_log | grep mcp
```
