local-wp-mcp
# local-wp-mcp
MCP server providing WP-CLI, MySQL, and filesystem access for [Local by Flywheel](https://localwp.com/) WordPress sites.
Unlike REST API-based WordPress MCP servers, this connects directly through Local's native filesystem and process architecture — no application passwords, HTTP endpoints, or network requests needed.
## Features
- **WP-CLI execution** via Local's bundled PHP binary with command allowlisting
- **Direct MySQL queries** via Unix socket connection pooling
- **Filesystem operations** with path validation and WordPress core protection
- **Multi-site support** with automatic site detection from `sites.json`
- **Security-first**: `spawn()` only (no `exec()`), symlink-safe path validation, tiered command permissions
## Tools
| Tool | Description |
|------|-------------|
| `wp_cli_run` | Execute any WP-CLI command |
| `wp_site_info` | Get WordPress version, URL, themes, plugins |
| `wp_list_sites` | List all Local by Flywheel sites |
| `mysql_query` | Run SQL queries (read-only by default) |
| `mysql_schema` | Inspect database tables and columns |
| `wp_active_plugins` | Get active plugins via direct DB query |
| `read_site_file` | Read files from the site directory |
| `write_site_file` | Write files (wp-content only, core protected) |
| `list_site_directory` | List directory contents |
| `search_site_files` | Search for files by name pattern |
## Setup
There are two ways to run this MCP server: **from npm** (recommended for general use) or **from a local clone** (for development or customization).
> **Important**: All `env` values must be strings (e.g., `"true"` not `true`).
---
### Option A: Install from npm (recommended)
This is the simplest approach. npm downloads and caches the package automatically — no cloning or building required.
#### Automatic setup (Claude Code)
From your Local site's project directory:
```bash
cd ~/Local\ Sites/mysite/app/public
npx -y @chrisfromthelc/local-wp-mcp --setup
```
This will auto-detect the Local site, create a `.mcp.json` (or merge into an existing one), and pre-fill `SITE_NAME` with write permissions set to `false`. Restart Claude Code to connect.
#### Manual setup
Choose your agent below. Each example uses `npx` to run the server — replace `"My Site Name"` with the name shown in Local.
<details>
<summary><strong>Claude Code</strong></summary>
Add a `.mcp.json` to your project root (e.g., `~/Local Sites/mysite/app/public/.mcp.json`):
```json
{
"mcpServers": {
"local-wp": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@chrisfromthelc/local-wp-mcp"],
"env": {
"SITE_NAME": "My Site Name",
"WPCLI_ALLOW_WRITES": "false",
"MYSQL_ALLOW_WRITES": "false",
"FS_ALLOW_WRITES": "false"
}
}
}
}
```
Or add via the CLI:
```bash
claude mcp add -s project local-wp -- npx -y @chrisfromthelc/local-wp-mcp
```
Then set environment variables with `claude mcp add-json` or by editing `.mcp.json` directly.
</details>
<details>
<summary><strong>Claude Desktop</strong></summary>
Edit the config file:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"local-wp": {
"command": "npx",
"args": ["-y", "@chrisfromthelc/local-wp-mcp"],
"env": {
"SITE_NAME": "My Site Name",
"WPCLI_ALLOW_WRITES": "false",
"MYSQL_ALLOW_WRITES": "false",
"FS_ALLOW_WRITES": "false"
}
}
}
}
```
Restart Claude Desktop to connect.
</details>
<details>
<summary><strong>Cursor</strong></summary>
Add a `.cursor/mcp.json` to your project root, or edit `~/.cursor/mcp.json` for global config:
```json
{
"mcpServers": {
"local-wp": {
"command": "npx",
"args": ["-y", "@chrisfromthelc/local-wp-mcp"],
"env": {
"SITE_NAME": "My Site Name",
"WPCLI_ALLOW_WRITES": "false",
"MYSQL_ALLOW_WRITES": "false",
"FS_ALLOW_WRITES": "false"
}
}
}
}
```
Or add from Cursor Settings > Tools & MCP > New MCP Server.
</details>
<details>
<summary><strong>VS Code (GitHub Copilot)</strong></summary>
Add a `.vscode/mcp.json` to your workspace root:
```json
{
"servers": {
"local-wp": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@chrisfromthelc/local-wp-mcp"],
"env": {
"SITE_NAME": "My Site Name",
"WPCLI_ALLOW_WRITES": "false",
"MYSQL_ALLOW_WRITES": "false",
"FS_ALLOW_WRITES": "false"
}
}
}
}
```
> **Note**: VS Code uses `"servers"` (not `"mcpServers"`) and requires `"type": "stdio"`.
Or use the Command Palette: **MCP: Add Server**.
</details>
<details>
<summary><strong>Windsurf</strong></summary>
Edit the config file:
- macOS: `~/.codeium/windsurf/mcp_config.json`
- Windows: `%USERPROFILE%\.codeium\windsurf\mcp_config.json`
```json
{
"mcpServers": {
"local-wp": {
"command": "npx",
"args": ["-y", "@chrisfromthelc/local-wp-mcp"],
"env": {
"SITE_NAME": "My Site Name",
"WPCLI_ALLOW_WRITES": "false",
"MYSQL_ALLOW_WRITES": "false",
"FS_ALLOW_WRITES": "false"
}
}
}
}
```
Or use the Command Palette: **MCP: Add Server**.
</details>
---
### Option B: Install from source (development)
Use this if you want to modify the server, run tests, or contribute changes. You clone the repo, build it once, and point `.mcp.json` at your local build output.
#### 1. Clone and build
```bash
git clone https://github.com/chrisfromthelc/local-wp-mcp.git
cd local-wp-mcp
npm install
npm run build
```
#### 2. Configure your agent
Point your agent's config at the local `dist/index.js` instead of using npx. Use the same config format shown in the [manual setup](#manual-setup) section for your agent, but replace `command` and `args`:
```json
"command": "node",
"args": ["/absolute/path/to/local-wp-mcp/dist/index.js"]
```
> **Note**: The path must be absolute (e.g., `/Users/you/Projects/local-wp-mcp/dist/index.js`). Relative paths won't resolve correctly when the agent spawns the process.
#### 3. Rebuild after changes
After editing source files, rebuild before restarting your agent:
```bash
npm run build
```
You can also use `npm run dev` to watch for changes and rebuild automatically during development.
#### Development commands
```bash
npm run lint # ESLint
npm run build # TypeScript compile
npm test # unit tests (single run)
npm run test:watch # unit tests (watch mode)
npm run test:coverage # unit tests with coverage report
```
---
### Switching between npm and local
To switch from npm to local (or vice versa), update `command` and `args` in your agent's config:
| Method | `command` | `args` |
|--------|-----------|--------|
| npm (npx) | `"npx"` | `["-y", "@chrisfromthelc/local-wp-mcp"]` |
| Local clone | `"node"` | `["/absolute/path/to/dist/index.js"]` |
Everything else (`env`, server name) stays the same. Restart your agent after switching.
---
### Environment variables
| Variable | Description | Default |
|----------|-------------|---------|
| `SITE_NAME` | Site name as shown in Local (e.g., `"My Site"`) | Auto-detected if only one site exists |
| `SITE_ID` | Site ID from Local (takes precedence over `SITE_NAME`) | — |
| `WPCLI_ALLOW_WRITES` | Enable write WP-CLI commands (`plugin install`, `post create`, etc.) | `"false"` |
| `MYSQL_ALLOW_WRITES` | Enable `INSERT`/`UPDATE`/`DELETE`/`ALTER` queries | `"false"` |
| `FS_ALLOW_WRITES` | Enable writing files via `write_site_file` (WordPress core dirs are always read-only) | `"false"` |
| `WPCLI_SAFE_COMMANDS` | Comma-separated list of additional read-only commands (see [Plugin CLI commands](#plugin-cli-commands)) | — |
If only one site exists in Local, `SITE_NAME` and `SITE_ID` can both be omitted — the server will connect to it automatically.
## Plugin CLI commands
This MCP server isn't limited to core WP-CLI commands — it automatically supports commands registered by plugins (WooCommerce, ACF, Yoast, Elementor, etc.).
### How it works
WP-CLI commands are classified using a three-tier system:
| Tier | Behavior | Examples |
|------|----------|----------|
| **Blocked** | Always rejected (arbitrary code execution) | `eval`, `eval-file`, `shell` |
| **Read-only** | Always allowed | Core safe commands + any command with a read-only action verb |
| **Write** | Requires `WPCLI_ALLOW_WRITES=true` | Everything else |
**Read-only action verbs** — any command whose subcommand is one of these is automatically allowed, regardless of whether it's a core or plugin command:
`list`, `get`, `search`, `check`, `status`, `path`, `info`, `version`, `is-installed`, `check-update`, `pluck`, `has`
This means commands like `wc product list`, `acf field get my-group`, or `yoast index status` work out of the box — no configuration needed.
Commands with write-action verbs like `wc product create`, `acf field delete`, or `yoast index run` are blocked unless `WPCLI_ALLOW_WRITES=true` is set.
### Custom safe commands
If a plugin has read-only commands that don't match the built-in verb patterns (e.g., `wc report sales`), you can whitelist them with the `WPCLI_SAFE_COMMANDS` environment variable:
```json
{
"mcpServers": {
"local-wp": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@chrisfromthelc/local-wp-mcp"],
"env": {
"SITE_NAME": "My Site Name",
"WPCLI_ALLOW_WRITES": "false",
"MYSQL_ALLOW_WRITES": "false",
"WPCLI_SAFE_COMMANDS": "wc report sales,wc report customers,my-plugin dump-config"
}
}
}
}
```
Commands in `WPCLI_SAFE_COMMANDS` are always allowed without writes enabled. Use comma-separated values, matching the first 2–3 words of the command.
## Security
- All commands use `spawn()` with argument arrays — no shell interpretation
- Shell metacharacters rejected in both commands and arguments as defense-in-depth
- WP-CLI `eval`, `eval-file`, and `shell` are always blocked
- Dangerous flags (`--exec`, `--require`, `--ssh`, etc.) are blocked in all commands
- Write operations require explicit opt-in via env vars
- Plugin commands with read-only verbs are auto-detected and allowed
- File paths are validated with `realpath()` to prevent symlink traversal
- File reads and writes use open handles to mitigate TOCTOU race conditions
- WordPress core directories (`wp-admin/`, `wp-includes/`) are read-only
- Child processes inherit only necessary environment variables
- Directory searches are capped at 10,000 files to prevent resource exhaustion
- Output is truncated at 25,000 characters to preserve context windows
## Requirements
- [Local by Flywheel](https://localwp.com/) installed
- Node.js 18+
- Site must be running in Local (services started)
## License
MIT
TDQS
Scored across 10 tools
Most tools target distinct resources (sites, WP-CLI, database, files). The main overlap is wp_active_plugins vs wp_cli_run 'plugin list', but descriptions clarify the direct database query vs CLI execution. Otherwise, each tool serves a clear purpose.
Tools use clear prefixes for domains (wp_*, mysql_*) and consistent verb_noun for file operations (read_site_file, write_site_file). Minor inconsistency: wp_cli_run is noun_verb rather than verb_noun, and file operations lack a prefix, but the pattern is still predictable.
10 tools is well-scoped for a local WordPress development server covering sites, WP-CLI, database, and file operations. Each tool earns its place without feeling redundant or excessive.
The surface covers core workflows: read/write files, query database, run WP-CLI, list sites, and inspect schema. A minor gap is the lack of a tool to switch the 'current site' referenced by wp_site_info, but overall the lifecycle is adequately covered.