Passepartout
README.md
# ποΈ Passepartout
**The master key for piloting self-hosted WordPress from any AI client.**
Passepartout is a [Model Context Protocol](https://modelcontextprotocol.io) (MCP)
server that lets an AI assistant β Claude Desktop today, any MCP client tomorrow β
read and write **content** (posts, pages, media, taxonomies) across **all of your
self-hosted WordPress sites**, using a simple shared key instead of WordPress
Application Passwords.
```
AI client ββMCPβββΊ Passepartout ββREST + X-Passepartout-KeyβββΊ your WordPress sites
```
---
## Why this exists
If you run self-hosted WordPress and tried to connect it to an AI agent, you have
probably hit this wall:
- **Application Passwords silently disappear.** Behind a reverse proxy (most managed
hosts), WordPress' internal `is_ssl()` check fails, the "Application Passwords"
section vanishes from the profile screen, and nothing tells you why.
- **No OAuth for self-hosted.** The official tooling assumes WordPress.com-style
OAuth, which self-hosted installs don't have out of the box.
- **The web connector is left out.** Most self-hosted MCP paths only work with a
local desktop proxy.
The lock jammed. So Passepartout is a **skeleton key**: a tiny companion plugin
validates a secret you control (`X-Passepartout-Key`), and the server speaks the
plain WordPress REST API. No Application Passwords, no SSL-detection roulette, and
**one server drives every site you own**.
> Named after Jules Verne's tireless globe-trotting valet β and after the French
> *passe-partout*, the key that opens every door.
---
## Features
- π **Custom-key auth** β bypasses Application Passwords entirely.
- π **Multi-site** β manage many WordPress installs from a single server; target one
with a `site` argument.
- π **Content tools** β list/get/create/update/delete **posts** and **pages**,
manage **categories/tags**, **search**, and **upload media from a URL**.
- π **Safe by default** β new posts/pages default to `draft`; destructive deletes are
explicitly annotated and trash-first.
- π§± **Clean TypeScript** β MCP SDK, Zod-validated inputs, strict mode, MIT-licensed.
---
## Quick start
### 1. Install the companion plugin (per site)
Copy [`wp-plugin/passepartout-auth.php`](wp-plugin/passepartout-auth.php) to
`wp-content/mu-plugins/passepartout-auth.php` on each WordPress site, then add the
secret to `wp-config.php` (above the *"That's all, stop editing!"* line):
```php
define( 'PASSEPARTOUT_KEY', 'a-long-random-secret' );
// Optional β act as a specific user instead of the first administrator:
// define( 'PASSEPARTOUT_USER_ID', 1 );
```
> Use a long, random secret and serve the site over HTTPS.
### 2. Configure the server
Create a `sites.json` (see [`sites.example.json`](sites.example.json)):
```json
[
{ "id": "baair", "label": "BAAIR Solutions", "url": "https://baair.solutions", "key": "a-long-random-secret" }
]
```
The `key` here must match `PASSEPARTOUT_KEY` on that site.
### 3. Add it to your MCP client
Claude Desktop (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"passepartout": {
"command": "npx",
"args": ["-y", "passepartout-mcp@latest"],
"env": {
"PASSEPARTOUT_SITES_FILE": "/absolute/path/to/sites.json"
}
}
}
}
```
Restart the client fully, then try: *"List the draft posts on baair."*
---
## Configuration
Pick **one** of three styles:
| Variable | Meaning |
| --- | --- |
| `PASSEPARTOUT_SITES_FILE` | Absolute path to a JSON array of sites (recommended). |
| `PASSEPARTOUT_SITES` | Inline JSON array of sites. |
| `WP_URL` + `WP_KEY` (+ `WP_SITE_ID`, `WP_LABEL`) | Single-site shorthand. |
Each site object: `{ "id", "url", "key", "label?" }`.
---
## Tools
| Tool | What it does | Destructive |
| --- | --- | --- |
| `wp_list_sites` | List configured sites (never exposes keys) | no |
| `wp_list_posts` / `wp_get_post` | Browse and read posts | no |
| `wp_create_post` / `wp_update_post` | Author and edit posts (draft by default) | no |
| `wp_delete_post` | Trash (or force-delete) a post | **yes** |
| `wp_list_pages` / `wp_get_page` | Browse and read pages | no |
| `wp_create_page` / `wp_update_page` | Author and edit pages | no |
| `wp_list_terms` / `wp_create_term` | Manage categories and tags | no |
| `wp_list_media` / `wp_upload_media_from_url` | Browse media; import an image by URL | no |
| `wp_search` | Search posts and pages | no |
---
## Security model
- The companion plugin authenticates a request **only** when `X-Passepartout-Key`
matches `PASSEPARTOUT_KEY`, using a constant-time comparison.
- The server **never** logs or returns your keys; `wp_list_sites` omits them.
- Capabilities follow the WordPress role of the resolved user β use a dedicated
machine user with the least privilege you need.
- Always serve sites over HTTPS. Rotate a key by changing it on both sides.
---
## Build from source
```bash
npm install
npm run build
node dist/index.js # expects the env config above
```
Inspect tools interactively with the MCP Inspector:
```bash
npx @modelcontextprotocol/inspector node dist/index.js
```
---
## Roadmap
- [ ] Streamable HTTP transport + OAuth, for the **claude.ai web connector**
- [ ] Custom post types & ACF fields
- [ ] WP-CLI-over-SSH fallback adapter
- [ ] Structured (`outputSchema`) responses
Contributions welcome β see [CONTRIBUTING.md](CONTRIBUTING.md).
## License
MIT Β© Alexandre Bruneau (BAAIR)
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues