Skip to content

Releases

v0.2.1

Choose a tag to compare

@vikasiwp vikasiwp released this 10 Sep 09:27
4551606

Security release. Published to npm as @instawp/mcp-wp@0.2.1.

Install or upgrade with npm i @instawp/mcp-wp@latest. Merging the fix did not reach anyone — this tag is what published it, and an already-installed copy stays vulnerable until it is upgraded.

If you pasted the WordPress endpoint example from README.md into your site, re-paste the current version — the old snippet was weaker than the client and is not updated by npm.

Reported privately by Syed Anas Mohiuddin.


Security

  • execute_sql_query's read-only gate no longer lets a SELECT reach the filesystem. INTO OUTFILE,
    INTO DUMPFILE and LOAD_FILE() are valid SELECT syntax and matched none of the old checks (a
    startsWith('SELECT') prefix test, a multi-statement test, and a fixed DDL/DML blocklist), so
    SELECT LOAD_FILE('/etc/passwd') and SELECT '<?php … ?>' INTO DUMPFILE '…/uploads/pwn.php' both
    passed validation while the tool description promised "only SELECT queries are allowed". All three
    are now rejected. Every check also runs against a normalized query — string literals, quoted
    identifiers and comments collapsed to whitespace — so a keyword hidden inside a literal is no longer
    a false positive (SELECT … WHERE title = 'how to drop a table' used to be refused) and one split
    by a comment is not a bypass. A query that cannot be read unambiguously is rejected rather than
    guessed at: an unterminated string or comment; a backslash-escaped quote inside a literal (its
    meaning depends on the server's NO_BACKSLASH_ESCAPES sql_mode); a /*! MySQL or /*M! MariaDB
    executable comment, whose contents the server runs; or a function called by a quoted name —
    SELECT `LOAD_FILE`('/etc/passwd') and its ANSI_QUOTES double-quoted form resolve to the builtin
    on both engines (verified on MariaDB 11.8 and MySQL 8.0.46), so blanking the identifier the way an
    identifier is blanked would have erased the keyword before any check saw it. That test runs on the
    finished normalized query rather than on the raw text, so it holds however the name is separated from
    its ( — including by a comment, and including a -- comment started by a control character,
    which both servers accept (my_isspace || my_iscntrl) and no whitespace class covers. The --
    comment rule is therefore matched as [\x00-\x20\x7f]: too wide blanks text the server executes, and
    too narrow leaves text the server drops sitting between the tokens being compared. Above 0x7f that
    rule is decided by character_set_client, and MySQL and MariaDB disagree with each other, so a high
    byte straight after -- is refused rather than classified — guessing is a bypass either way. The
    separator between a quoted name and its ( is a different class and is widened instead, since
    widening there can only reject more. The example WordPress
    endpoint in README.md now repeats these checks server-side using the same scanner, since the
    client's validation is not a boundary — its previous regex-based normalizer stripped comments before
    string literals, which made SELECT '#' INTO OUTFILE '/tmp/x' read as harmless.
  • Credential-shaped request-body keys are redacted from debug logging too. The Data: line one
    below the header bag logged the request body verbatim, and create_user/update_user pass their
    params straight through — so a debug run put a WordPress user's password in the clear in the same
    stderr stream. password, token, secret, api_key and their siblings now log as [REDACTED],
    nested objects and arrays included. Query text and result rows are still not redacted; that is
    documented rather than changed.
  • Credential headers are redacted from debug logging. Authorization: Basic base64(user:app-password)
    was logged verbatim at debug level, and base64 is reversible — so WORDPRESS_LOG_LEVEL=debug put
    the WordPress application password in the clear. Despite its name logToFile writes to stderr, which
    for a stdio MCP server the host client captures into its own log files. Authorization, Cookie and
    their siblings now log as [REDACTED].

Both reported privately by Syed Anas Mohiuddin.

Fixed

  • execute_sql_query no longer refuses an identifier that merely ends in a DDL keyword. The
    blocklist matched UPDATE\s+ rather than the whole word, so SELECT last_update FROM … came back as
    "potentially dangerous SQL statement". It now matches on word boundaries, which also catches a
    keyword at the very end of a query — something the trailing-whitespace form missed. INSERT,
    TRUNCATE and REPLACE are exempted when a ( follows, because all three are also ordinary
    read-only functions (SELECT INSERT('Quadratic',3,4,'What'), SELECT TRUNCATE(1.234,2),
    SELECT REPLACE(a,'x','y')); no statement form can reach that check anyway, since the query must
    already start with SELECT/WITH/EXPLAIN. REPLACE is new to the list — REPLACE tbl SET … needs no
    INTO, so nothing else there would have caught it.
  • The README.md endpoint accepts WITH … and EXPLAIN …, which the client allows and the
    example rejected with a 400, and returns a 400 rather than a PHP error when query is not a string.
  • README.md no longer claims queries are logged to logs/wordpress-api.log. No such file is
    written: logging goes to stderr, only when WORDPRESS_LOG_LEVEL=debug is set (the default is
    error). Same correction in CLAUDE.md.

Added

  • SECURITY.md — a private disclosure route (GitHub private vulnerability reporting, plus
    support@instawp.com), response targets, and scope. The reporter above had to find us through the
    support desk because the repository named no security contact.

v0.2.0

Choose a tag to compare

@vikasiwp vikasiwp released this 19 Aug 09:18
e4f8fe8
npm install @instawp/mcp-wp@0.2.0

Added

  • WORDPRESS_USER_AGENT — sets the user-agent on every outbound request: the WordPress REST client behind all tools, the SQL endpoint, both api.wordpress.org lookups, and remote media downloads. Leave it unset (the default) and nothing changes — axios keeps sending its own axios/<version>. Set it when a CDN or WAF in front of your site rejects that default. An empty or whitespace-only value is treated as unset, since some edges block an empty user-agent too. (#30, #35)

Changed

  • execute_sql_query now explains an HTTP 403 rather than just reporting it. A 403 can mean WordPress rejected the credentials, or that a CDN/WAF returned a challenge page before the request ever reached WordPress. The error says so and prints the (truncated) response body, which is the only thing that distinguishes the two. The bare Request failed with status code 403 is what made #28 hard to diagnose. (#35)

Security

  • vitest bumped to ^4.1.11, clearing GHSA-5xrq-8626-4rwp (critical, vitest < 3.2.6) and four moderate/high advisories in the vite / vite-node / esbuild chain. Dev-only — none of it ships in the published package, so no consumer was exposed. (#34)

v0.1.2

Choose a tag to compare

@vikasiwp vikasiwp released this 19 Aug 08:55
0a14b41

First release published to npm carrying the execute_sql_query fix.

npm install @instawp/mcp-wp@0.1.2

Fixed (from 0.1.1)

  • execute_sql_query no longer sends User-Agent: Mozilla/5.0. That bare UA is a well-known bot signature, and CDNs/WAFs (WP Engine, Cloudflare bot protection) answer it with a 403 challenge page — so the tool failed against healthy, correctly authenticated SQL endpoints while every other tool worked. (#28, #29)

Added

  • Automated npm publishing. A vX.Y.Z tag now builds, tests and publishes with provenance, then verifies the registry actually serves the new version. 0.1.1 was tagged and released here but never reached npm because publishing was manual — this release is that gap being closed. (#32)
  • repository, homepage and bugs in package.json (the first is required for provenance).

Note: 0.1.1 exists as a git tag and GitHub release only; it was never published to npm. Go straight from 0.1.0 to 0.1.2.

Follow-up tracked in #30.

v0.1.1

Choose a tag to compare

@agentosiwp agentosiwp released this 19 Aug 07:14
0a82222

Fixed

  • execute_sql_query no longer sends User-Agent: Mozilla/5.0. The bare Mozilla/5.0 is a well-known bot signature that CDNs and WAFs (WP Engine, Cloudflare bot protection) answer with a 403 challenge page, so the tool failed against healthy, correctly authenticated SQL endpoints while every other tool worked. It now sends no User-Agent override, matching the rest of the package. (#28, #29)

Upgrading: npm install @instawp/mcp-wp@0.1.1. If any origin has an allow-rule matching Mozilla/5.0 specifically, it needs to allow the axios default user-agent instead.

Follow-up tracked in #30 (a package-wide user-agent setting, and a clearer 403 error).

v0.1.0

Choose a tag to compare

@vikasiwp vikasiwp released this 15 Jun 06:17

First feature release since 0.0.3 — partial content edits, content summaries, SEO/meta correctness, multi-site SQL, and the project's first test suite.

Added

  • Partial content edits — content_edit (append/prepend/insert_before/insert_after/replace) on update_content and find_content_by_url, plus include_raw_content/content_raw for exact raw-content targeting (#26)
  • get_content_summary — minimal fixed-shape summary by id or url for token-cheap audit/lookup workflows (#21)
  • Dropped-meta warnings — write tools warn when WordPress silently drops unregistered (show_in_rest) meta keys, e.g. Yoast/Rank Math/AIOSEO (#17)
  • Multi-site site_id for execute_sql_query (#25)
  • Vitest test suite + GitHub Actions CI (#18)

Fixed

  • Taxonomy tools for divergent rest_base — correct resolution via /wp/v2/taxonomies; assign_terms_to_content now verifies the write and errors instead of falsely reporting success on a no-op (#23)
  • execute_sql_query endpoint URL corrected to /wp-json/mcp/v1/query, with hardened read-only validation (#25)

Changed

  • Response trimming — yoast_head/yoast_head_json stripped by default (~10KB/response saved), configurable via MCP_WP_STRIP_FIELDS (#16)
  • BREAKING: assign_terms_to_content terms now accepts only integer term IDs (number[]); string slugs are rejected (#23)

Docs

  • Meta-field limitations for SEO plugin keys (#19); WPRM recipe-card support via custom_fields (#20)

Full changelog: https://github.com/InstaWP/mcp-wp/blob/main/CHANGELOG.md

Note: published to npm separately — run npm publish (or it ships when authed).