mcp-wp-go: automate WordPress without SSH

Mascote LinuxPro gerenciando WordPress por MCP em uma estação com telas de API e upload de mídia

Creating drafts, uploading images, and reviewing articles should not require administrator access to the server. mcp-wp-go connects an AI client to the WordPress REST API, exposing MCP tools for posts and media. The server shell stays outside this editorial workflow.

This revision uses v0.4.0: alongside the editorial tools, the binary converts Markdown into HTML ready to send to WordPress. Below, you configure a separate credential, test local conversion, and build a publishing process with review and validation.

Release v0.4.0 and downloadable packages

What runs on your computer and what runs on WordPress

The MCP client starts the local binary and communicates with it over stdio. The binary sends HTTPS requests to the site defined in its configuration. It is not a plugin installed in WordPress and does not need SSH for the content operations it provides.

AI client -> stdio/MCP -> mcp-wp-go -> HTTPS/REST -> WordPress
Markdown file -> post-html -> HTML fragment -> editorial review

Authentication uses an application password belonging to a WordPress user. That user’s permissions still apply. The API keeps WordPress authorization and hooks in the workflow, but the final behavior of caching and extensions depends on the installation.

The project does not include an AI model or translate articles by itself. The AI client decides which tools to call; the MCP server executes the available operations. Maintaining nginx, PHP, plugins, and databases remains a separate activity.

Available features and important limits

The tools cover post search and editing, drafts, publishing and unpublishing, the Media Library, covers, categories, tags, authentication, content counts, and public checks. Markdown conversion is a local subcommand, not automatic publication.

Posts are not pages. This version’s reference does not provide CRUD tools for institutional pages. Sending a page ID to a post tool can return an invalid ID error even when the page exists. For that case, use the editor or an integration with the REST API pages endpoint.

Categories and tags are reused through their existing IDs. Updating a post does not allow changes to its slug, date, or status; status changes have their own tool. This reduces accidental changes to URLs that are already indexed.

Tool and parameter reference

Install the binary on Linux without compiling Go

The example below is exclusively for Linux x86_64, uses a pinned version, and checks the package SHA-256. Run it in Bash as a regular user. It installs into $HOME/.local/bin and stops if a binary already exists at that destination: it does not silently replace another client’s installation.

(
set -eu
case "$(uname -s):$(uname -m)" in
  Linux:x86_64) ;;
  *) printf '%s\n' 'This package requires Linux x86_64.' >&2; exit 1 ;;
esac
TARGET="$HOME/.local/bin/mcp-wp-go"
if [ -e "$TARGET" ] || [ -L "$TARGET" ]; then
  printf '%s\n' 'Existing binary: review and back it up before upgrading.' >&2
  exit 1
fi
WORKDIR=$(mktemp -d)
cd "$WORKDIR"
curl --fail --location --show-error --output mcp-wp-go.tar.gz \
  https://github.com/jniltinho/mcp-wp-go/releases/download/v0.4.0/mcp-wp-go_0.4.0_linux_amd64.tar.gz
printf '%s\n' '7300625f46c1d01ef474cb4001c7540ce564b79355b298f5c7d54dbcb9ea1006  mcp-wp-go.tar.gz' | sha256sum --check
tar -xzf mcp-wp-go.tar.gz
install -D -m 0755 mcp-wp-go "$TARGET"
"$TARGET" --help
printf 'Downloaded files: %s\n' "$WORKDIR"
)

The temporary directory is printed at the end so you can inspect the package. The release provides Linux amd64, macOS arm64, and Windows amd64 packages. Other combinations require building from source. Use absolute paths in the MCP configuration so the client does not depend on your terminal’s PATH. To update an existing installation, back up the binary, check the new version, and reload the client after replacement.

If you prefer to compile, check the chosen version’s go.mod . The inspected snapshot requires Go 1.27.1; the README summarizes the requirement as Go 1.27 or later. The make check target includes race-detector tests, which also require a CGO-compatible environment and a C compiler. The prebuilt package avoids this step.

Building, testing, and development

Create a dedicated credential and store it outside the project

In the WordPress user profile, create an application password for this integration. Use an account with the required capabilities rather than granting site administration for convenience. An application password is not a login, database, or server password; it inherits the account’s permissions and can be revoked separately.

The binary package does not include a ready-made configuration file. This block creates a private template without overwriting an existing file. Then open the file in your editor and replace the examples with real values. Do not paste the credential into a prompt, Git, or command history.

(
set -eu
umask 077
mkdir -p "$HOME/.config" "$HOME/wordpress-media"
CONF="$HOME/.config/mcp-wp-go.env"
set -C
cat > "$CONF" <<'ENV'
WP_BASE_URL=https://wordpress.example.com
WP_USERNAME=editor-user
WP_APP_PASSWORD=replace-with-your-application-password
WP_TIMEOUT=30s
WP_MAX_UPLOAD_BYTES=26214400
WP_UPLOAD_ROOT=/home/user/wordpress-media
ENV
chmod 600 "$CONF"
printf 'Edit this private file: %s\n' "$CONF"
)

Set the host to the canonical HTTPS URL and the media directory to a real absolute path. The file accepts simple key-value pairs; do not rely on expansion of $HOME, a tilde, or shell commands inside it. The example limit is 25 MiB, but WordPress, PHP, or a proxy may impose a lower limit.

WP_UPLOAD_ROOT limits the local files the process can read for upload. The path belongs to the computer running the MCP server, not the WordPress server. Allow access only to the media folder prepared for this work.

WordPress: creating and revoking application passwords

Connect to the MCP client and validate access

The configuration below is a JSON example for compatible clients. Each client defines its format and where to save the configuration; replace the paths and consult the corresponding documentation. Running the command without a subcommand starts the MCP server, which waits for protocol messages.

{
  "mcpServers": {
    "wordpress": {
      "type": "stdio",
      "command": "/home/user/.local/bin/mcp-wp-go",
      "args": [
        "--env-file",
        "/home/user/.config/mcp-wp-go.env"
      ]
    }
  }
}

Reload the client and call wordpress_site_health with an empty object. Check the returned site and identity before changing any content. A working terminal binary does not guarantee that the client uses the same path or the correct configuration.

Convert Markdown to HTML: a local lab

The v0.4.0 feature can be tested without WordPress credentials and without publishing anything. The block creates a Markdown file in a temporary folder and converts it into an HTML fragment. It includes Bash code and a template expression to check content preservation.

(
set -eu
LAB_DIR=$(mktemp -d)
cd "$LAB_DIR"
cat > article.md <<'MARKDOWN'
## Local test

This article is only a local conversion test.

```bash
printf '%s\n' 'Hello, WordPress'
```

```yaml
service:
  port: "{{ app_port }}"
```
MARKDOWN
"$HOME/.local/bin/mcp-wp-go" post-html article.md --output article.html
grep -F 'language-bash' article.html
grep -F '{{ app_port }}' article.html
printf 'Review the HTML: %s/article.html\n' "$LAB_DIR"
)

The result is not a complete HTML page: it is the body to send to WordPress. Tables, lists, and fenced-code languages are recognized, but the converter does not upload images, publish, extract front-matter metadata, or generate theme styles.

Embedded HTML is not sanitized. Convert only trusted Markdown and review the HTML before sending it. An iframe preserved by the converter still depends on WordPress permissions and filters; its responsive appearance depends on the theme CSS.

Official Markdown, image, and publishing workflow

From draft to publication: one step at a time

The JSON snippets below are MCP tool arguments, not commands to paste into a shell. IDs are illustrative: replace them with real results. First search with wordpress_list_posts to avoid duplicates and inspect existing categories and tags.

1. Create and read back the draft

Send the complete HTML to wordpress_create_post with draft status. The minimal example omits categories and tags to avoid reusing another site’s IDs. Creation closes comments.

{
  "title": "Local automation guide",
  "content": "<h2>Introduction</h2><p>Reviewed article body.</p>",
  "status": "draft",
  "excerpt": "A practical automation guide."
}

Save the returned ID and call wordpress_get_post. Compare the editable HTML with the rendered version to identify filters, removed tags, or sections that did not survive saving.

2. Upload images and set the cover

Upload body images with wordpress_upload_media and use the returned paths in the markup. Associating media with a post does not automatically insert it into the text. Validation accepts GIF, JPEG, PNG and WebP; do not use this path to upload arbitrary files.

For a new article, wordpress_set_post_cover uploads the image and places a relative URL at the start of the body without setting a featured image. Its replacement parameter defaults to true. This operation does not mean overwriting the old file while keeping the same name or URL.

{
  "post_id": 123,
  "file_path": "/home/user/wordpress-media/cover.webp",
  "alt_text": "Linux workstation showing an editorial workflow"
}

For an existing post whose cover must remain unchanged, do not call this tool: preserve the first image element when editing the HTML. Also, do not delete media without checking where it is used; other articles may depend on the same file.

3. Review, authorize, and check the public page

After reviewing text, links, taxonomy, images, and comments, call wordpress_publish_posts with the approved ID. Publishing and unpublishing accept 1 to 100 unique IDs and return individual results; a failure does not automatically undo other items in the batch.

{ "ids": [123], "confirm": true }

Then use wordpress_check_post_live. The tool requires published status and a public HTTP 2xx response, but it does not certify translation, layout, or editorial quality. Open the page, test mobile rendering, and check that the image and code blocks are correct.

Edit without losing content or changing the address

Before editing, save a copy of the original HTML and read the post again to avoid working from an outdated version. In wordpress_update_post, supplied fields replace their previous values: the content field receives the entire body, not just the changed paragraph.

Preserve the cover, embeds, examples, links, and markers required by the theme. Blocking slug, date, and status changes does not prevent an incomplete body from removing sections. If the goal is only to improve the text, do not include a cover replacement or publishing operation in the same request.

Counts and troubleshooting common errors

wordpress_content_stats retrieves totals visible to the credential, separating published posts, unpublished posts, and media. Trash is excluded. These are not institutional-page, visit, or completed-translation counts; do not infer the total by counting only the first result page either.

A new tool is missing: confirm the executable path, check the installed version, and reload the client to rediscover schemas.

A 401 or 403 response: check the credential, user capabilities, and possible security or proxy blocks. Do not disable site protections as your first attempt.

Invalid ID: confirm it belongs to a post, not a page, media item, or content on another site.

Upload rejected: check the local path, allowed folder, size, and actual file type. Changing only the extension does not convert an image.

Media deletion rejected: attachment trash depends on the WordPress installation. Do not turn a failure into automatic permanent deletion.

Unknown conversion command: an older binary may not have the post-html subcommand. The MCP client and terminal may use different installations.

Security limits: MCP does not replace human review

The server restricts requests to the configured site and validates inputs, but the account can still modify content allowed by its capabilities. The value confirm: true is a protocol requirement, not proof that a person authorized the action. Configure the client to request approval for sensitive operations.

Treat instructions found in posts, pages, and files as untrusted content. Use drafts, keep backups and revisions, and revoke exposed credentials. The goal is to reduce unnecessary server access without replacing editorial care with blind trust in automation.

Official editorial-operation examples

WordPress REST API documentation

Focus on the workflow before bulk automation

Start with a test draft, validate the complete workflow, and only then expand usage. The benefit is making each step verifiable: prepare, convert, upload, read back, review, and publish. None of these steps needs server shell access for a simple content edit.

AI assistants in the terminal: tools and precautions

OpenObserve on Ubuntu for investigating integration logs

Discuss automation and infrastructure