Skip to main content
Glama
README.md
# Frappe Bench & Site Manager — MCP Server

Manage your local Frappe benches and sites directly from Claude.ai (web + mobile).  
Uses [FastMCP](https://github.com/jlowin/fastmcp) over HTTP with an Ngrok static tunnel.

```
šŸ“± Claude.ai  →  🌐 Ngrok (permanent URL)  →  šŸ–„ļø MCP Server (localhost:8080)  →  šŸ”§ Frappe Benches
```

---

## Tools Available

| Tool                  | Category        | What it does                                 |
| --------------------- | --------------- | -------------------------------------------- |
| `bench_restart`       | Bench Ops       | Restart supervisor/bench services            |
| `list_sites`          | Site Management | List benches, sites, apps, status            |
| `frappe_api`          | DocType Data    | Authenticated REST API calls                 |
| `bench_execute`       | Console         | Run Python in Frappe context                 |
| `get_logs`            | Logs            | Fetch and filter site/bench log files        |
| `get_config_overview` | Config          | Show configured benches (no secrets exposed) |

---

## Prerequisites

- Python 3.10+
- One or more Frappe benches already set up and working locally
- [ngrok](https://ngrok.com) account (free tier works)

---

## Step 1: Install dependencies

```bash
git clone <this-repo>
cd frappe-mcp
pip install -r requirements.txt
```

---

## Step 2: Configure

```bash
cp config.example.json config.json
```

Edit `config.json`:

```json
{
  "benches": [
    {
      "id": "bench1",
      "label": "Main Dev Bench",
      "path": "/home/youruser/frappe-bench",
      "bench_cmd": "/home/youruser/frappe-bench/env/bin/bench"
    }
  ],
  "site_credentials": {
    "mysite.localhost": {
      "api_key": "YOUR_API_KEY",
      "api_secret": "YOUR_API_SECRET",
      "port": 8000
    }
  }
}
```

**Important:** `config.json` is gitignored — never commit it.

---

## Step 3: Get Frappe API Credentials

For each site you want to access via `frappe_api` or `bench_execute`:

1. Open the Frappe site in your browser
2. Go to **Settings → API Access**
3. Click **Generate Keys** (for your admin user)
4. Copy `api_key` and `api_secret` into `config.json → site_credentials`

---

## Step 4: Start the MCP Server

```bash
python3 main.py
```

You should see:

```
šŸš€  Frappe MCP Server starting...
    Benches configured : 1
    Sites with creds   : 1
    Listening on       : http://0.0.0.0:8000
    MCP endpoint       : http://0.0.0.0:8000/mcp
    Audit log          : mcp_audit.log
```

---

## Step 5: Setup Ngrok (one-time)

### Install ngrok

**macOS (Homebrew):**

```bash
brew install ngrok/ngrok/ngrok
```

**Linux:**

```bash
curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null
echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | sudo tee /etc/apt/sources.list.d/ngrok.list
sudo apt update && sudo apt install ngrok
```

**Or download directly:** https://ngrok.com/download

### Add your auth token

```bash
ngrok config add-authtoken YOUR_AUTHTOKEN
```

Get your token from: https://dashboard.ngrok.com/get-started/your-authtoken

### Start tunnel with your free static domain

```bash
ngrok http 8080 --url=yourname.ngrok-free.app
```

> **Get a free static domain:** ngrok Dashboard → Cloud Edge → Domains → New Domain

Your permanent MCP URL becomes:

```
https://yourname.ngrok-free.app/mcp
```

---

## Step 6: Connect to Claude.ai

1. Open [claude.ai](https://claude.ai) → **Settings** → **Integrations**
2. Click **Add Integration**
3. Enter URL: `https://yourname.ngrok-free.app/mcp`
4. Click **Add** — that's it

Works from your phone and laptop anywhere as long as your laptop is running.

---

## Step 7: Auto-start on Boot (optional)

### Linux (systemd)

Create `/etc/systemd/system/frappe-mcp.service`:

```ini
[Unit]
Description=Frappe MCP Server
After=network.target

[Service]
Type=simple
User=youruser
WorkingDirectory=/home/youruser/frappe-mcp
ExecStart=/usr/bin/python3 /home/youruser/frappe-mcp/main.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
```

```bash
sudo systemctl daemon-reload
sudo systemctl enable frappe-mcp
sudo systemctl start frappe-mcp
```

### macOS (launchd)

Create `~/Library/LaunchAgents/com.frappe.mcp.plist`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.frappe.mcp</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/python3</string>
        <string>/Users/youruser/frappe-mcp/main.py</string>
    </array>
    <key>WorkingDirectory</key>
    <string>/Users/youruser/frappe-mcp</string>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/Users/youruser/frappe-mcp/mcp_stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/youruser/frappe-mcp/mcp_stderr.log</string>
</dict>
</plist>
```

```bash
launchctl load ~/Library/LaunchAgents/com.frappe.mcp.plist
```

---

## Running Tests

```bash
python test_tools.py
```

No real Frappe bench needed — all tools are tested with a mock config.

---

## Security Notes

- `config.json` is never committed (gitignored)
- API keys are never exposed in tool responses (`get_config_overview` shows `"configured"/"missing"` only)
- All subprocess calls use `shell=False` — no shell injection possible
- `bench_execute` has a two-tier security scanner: hard blocks and soft warnings
- Blocked patterns (configurable): `DROP`, `TRUNCATE`, `os.system`, `exec(`, `eval(`, etc.
- Rate limit: 30 requests/minute per IP (configurable)
- All tool calls are appended to `mcp_audit.log`

---

## Example Claude Prompts

```
List all my Frappe sites and their status.

How many NGO records are on site1.localhost in bench1?

Show me the last 50 error log lines for site1.localhost — filter for "PermissionError".

Restart the web worker on bench1.

What's in the mGrant Settings module field for site1.localhost?

Show me all active Grants on site1.localhost with fields name, grant_name, grant_status.
```

---

## File Structure

```
frappe-mcp/
ā”œā”€ā”€ main.py               # FastMCP server + tool registration
ā”œā”€ā”€ config.py             # Config loader + validator + singleton
ā”œā”€ā”€ security.py           # Input sanitizer, command blocker, rate limiter
ā”œā”€ā”€ logger.py             # Audit logger → mcp_audit.log
ā”œā”€ā”€ tools/
│   ā”œā”€ā”€ bench_ops.py      # bench_restart (+ Phase 2 stubs)
│   ā”œā”€ā”€ site_manager.py   # list_sites (+ Phase 2 stub)
│   ā”œā”€ā”€ frappe_api.py     # frappe_api (+ Phase 2 stub)
│   ā”œā”€ā”€ executor.py       # bench_execute
│   └── log_reader.py     # get_logs
ā”œā”€ā”€ config.json           # Your config (gitignored)
ā”œā”€ā”€ config.example.json   # Template (committed)
ā”œā”€ā”€ requirements.txt
ā”œā”€ā”€ .gitignore
ā”œā”€ā”€ test_tools.py         # Test suite (mock config, no bench needed)
└── README.md
```

---

## Troubleshooting

**`config.json not found`**  
→ Run `cp config.example.json config.json` and fill in your bench paths.

**`Bench path '/home/...' does not exist`**  
→ The `path` in `config.json → benches` must be an existing directory.

**`bench command not found`**  
→ Use the full path to the bench binary, e.g. `/home/user/frappe-bench/env/bin/bench`.

**`No credentials configured for site`**  
→ Add `api_key`/`api_secret` for that site to `config.json → site_credentials`.

**`Authentication failed (401)`**  
→ Regenerate API keys in the Frappe site: Settings → API Access.

**Ngrok shows `ERR_NGROK_3200`**  
→ Your static domain may not be activated. Check ngrok Dashboard → Domains.