Skip to main content
Glama
mdsakib-dr

WordPress Self-Hosted MCP Server

by mdsakib-dr
README.md
# WordPress MCP Server (for Prefect Horizon)

An MCP server that lets an AI client **draft, publish, update, and search posts**
on your self-hosted WordPress site (the kind hosted on Hostinger). It uses the
built-in WordPress REST API and authenticates with an **Application Password** —
no plugins to install on WordPress.

Built with the standalone [`fastmcp`](https://gofastmcp.com) package so it deploys
cleanly on **Prefect Horizon** (`fastmcp.app`). Horizon serves the HTTP transport
for you; you just point it at the `mcp` object.

- **Entrypoint:** `main.py:mcp`

## Tools

| Tool | What it does |
|------|--------------|
| `wordpress_create_draft` | Create a new post saved as a **draft** (private, for review) |
| `wordpress_publish_post` | Create a new post and **publish** it immediately (live) |
| `wordpress_update_post` | Edit an existing post by ID, or change its status (draft → publish) |
| `wordpress_list_posts` | List / search posts to find IDs, review drafts, or check content |

---

## Step 1 — Create a WordPress Application Password

Application Passwords are built into WordPress 5.6+ and let an app authenticate
without your real password.

1. Log in to WordPress admin (`https://yoursite.com/wp-admin`).
2. Go to **Users → Profile** (or **Users → All Users → your user → Edit**).
3. Scroll to **Application Passwords**.
4. Enter a name like `Horizon MCP` and click **Add New Application Password**.
5. Copy the generated password (e.g. `abcd EFGH ijkl MNOP qrst UVWX`). You only see
   it once. Spaces are fine — the server strips them.

Your account needs the **Editor** or **Administrator** role to publish.

**If the Application Passwords section is missing on Hostinger:** it's usually
because the site isn't on HTTPS, or a security plugin (Wordfence, iThemes, etc.)
disabled the feature or the REST API. Make sure the site loads over `https://`
and check the plugin's REST API / Application Password settings.

---

## Step 2 — Put this repo on GitHub

Horizon deploys from a GitHub repository.

```bash
cd wordpress-mcp
git init
git add main.py requirements.txt pyproject.toml README.md .gitignore
git commit -m "WordPress MCP server for Horizon"
# create a repo on GitHub, then:
git remote add origin https://github.com/<you>/wordpress-mcp.git
git push -u origin main
```

> Do **not** commit real credentials. There are none in the repo — they're supplied
> as environment variables in Horizon (Step 3). The included `.gitignore` keeps
> `.env` and the local virtualenv out.

---

## Step 3 — Deploy on Prefect Horizon

1. Go to **https://horizon.prefect.io** and sign in with GitHub.
2. Click to create/deploy a new server and **select this repository**.
3. Configure:
   - **Name:** e.g. `wordpress` (your URL becomes `https://wordpress.fastmcp.app/mcp`)
   - **Entrypoint:** `main.py:mcp`
   - **Authentication:** toggle on if you want only your org's authenticated users
     to be able to connect (recommended).
   - **Environment variables / secrets:**
     | Key | Value |
     |-----|-------|
     | `WORDPRESS_URL` | `https://yoursite.com` |
     | `WORDPRESS_USERNAME` | your WordPress login name (not the email) |
     | `WORDPRESS_APP_PASSWORD` | the Application Password from Step 1 |
4. Click **Deploy**. Horizon clones the repo, installs `requirements.txt`, and
   brings the server up (~1 minute).
5. Test it with Horizon's built-in **Inspector** (call `wordpress_list_posts`) or
   **ChatMCP** before wiring up other clients.

Your MCP endpoint will be **`https://<name>.fastmcp.app/mcp`**. Add that URL as a
remote MCP server in any client that supports remote MCP (including Claude).

---

## Run it locally (optional)

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
export WORDPRESS_URL="https://yoursite.com"
export WORDPRESS_USERNAME="your_wp_login"
export WORDPRESS_APP_PASSWORD="abcd EFGH ijkl MNOP qrst UVWX"
python main.py        # runs over stdio for a local client
```

---

## Notes

- **Content accepts HTML.** `<h2>…</h2><p>…</p>` produces formatted posts; plain
  text works too.
- **Categories and tags are numeric IDs**, not names. (Adding name→ID lookup is an
  easy future addition.)
- **Publishing vs. drafting:** `wordpress_create_draft` stages content; flip a draft
  live later with `wordpress_update_post` (`status: "publish"`).
- **Security:** the Application Password lives only in Horizon's env/secrets, never
  in the repo. Revoke it anytime under **Users → Profile** without affecting login.

## Troubleshooting

| Symptom | Likely cause / fix |
|---------|--------------------|
| `Authentication failed (401)` | Wrong username, or you used your login password instead of an Application Password. |
| `Permission denied (403)` | User role too low to publish, or a security plugin blocks the REST API. |
| `Not found (404)` on every call | `/wp-json/` disabled/blocked, or `WORDPRESS_URL` wrong. Open `https://yoursite.com/wp-json/` in a browser — you should see JSON. |
| `credentials are not configured` | One of the three env vars isn't set in Horizon's server settings. |
| Build fails on Horizon | Confirm the entrypoint is exactly `main.py:mcp` and `requirements.txt` is in the repo root. |