Provider REST API
MarketingConnect Claude to WordPress
Read and write a WordPress site through its REST API - posts, pages, taxonomy, media, people and comments. Toolspoke puts 12 of its actions behind one MCP endpoint that Claude, Cursor, and Codex all speak.
- Connection
- Provider REST API
- Authentication
- Application password
- Actions exposed
- 12
- Cost per call (typical)
- 1 credit
- Adapter
- Maintained by Toolspoke
Connected in three steps
- 1
Install WordPress
Open the marketplace in your workspace, add WordPress to the project your agents work in, and it appears on the gateway immediately.
- 2
Connect the credential
Authenticate with application password. Where to get one, and what it has to be able to reach, is the next section.
- 3
Point your agent at the gateway
Give your client one address, https://toolspoke.com/mcp. Claude Code takes it as a command, Claude and Claude Desktop add it as a custom connector, and Cursor, Codex and VS Code each read it from a config file of their own.
.mcp.json{
"mcpServers": {
"toolspoke": {
"type": "http",
"url": "https://toolspoke.com/mcp"
}
}
}One block covers every tool you have installed. WordPress shows up in the client as soon as your policy allows it, and so does everything else you install later.
Where the address goes, per client
- Claude Code
Run it in your project, then /mcp to sign in
claude mcp add --transport http toolspoke https://toolspoke.com/mcp- Claude and Claude Desktop
Settings, then Connectors, then Add custom connector
https://toolspoke.com/mcp- Cursor
~/.cursor/mcp.json, or .cursor/mcp.json for one project
{ "mcpServers": { "toolspoke": { "url": "https://toolspoke.com/mcp" } } }- Codex
~/.codex/config.toml
[mcp_servers.toolspoke] url = "https://toolspoke.com/mcp"- VS Code
.vscode/mcp.json, or the MCP: Add Server command
{ "servers": { "toolspoke": { "type": "http", "url": "https://toolspoke.com/mcp" } } }
What WordPress asks for
Application password. You provide it once, when you install the connector. Toolspoke encrypts it at rest and decrypts it only for the length of a single call, and the gateway attaches it to the outbound request itself, so it is never part of the arguments an agent sends.
- Site URLRequired
- The site's own address, e.g. https://example.com - or https://example.com/blog if WordPress is installed under a path. Everything this connector calls hangs off /wp-json/wp/v2 below it. Must be reachable over HTTPS: WordPress refuses application passwords on a plain-HTTP site.
- https://example.com
- Username and application passwordRequired
- The WordPress username (or the account's email address), a colon, then an application password. Create one at wp-admin → Users → Profile → Application Passwords; it is shown once. The spaces WordPress displays are cosmetic and may be kept or removed. Sent as HTTP Basic. The password inherits that user's capabilities exactly, so create it on an account with only the roles this connection should have - an Editor can publish, an Author cannot touch other people's posts, a Subscriber can read.
- editor:abcd EFGH ijkl MNOP qrst UVWX
What Claude can do in WordPress
12 actions, each one declared and named by the connector rather than discovered at runtime. A workspace policy grants a person all of them, a hand-picked selection, everything on the read side, everything on the write side, or none.
- Reads
- 9Reads
- Writes
- 0Writes
- Destructive
- 3Destructive
Reads
9Fetches data and changes nothing.
list_postsList posts, newest first. Defaults to published posts only - pass `status` to see drafts, pending or private ones, which needs a credential allowed to edit them. `search` does a full-text match; `categories` and `tags` take term ids from list_categories and list_tags, not names. Content comes back as objects rather than strings: `title.rendered`, `content.rendered` and `excerpt.rendered` hold the HTML a visitor would see, and `content.raw` appears only when `context` is edit. Free - WordPress bills nothing; the only limit is the site's own hosting. The bodies are the site's published writing, so the payload is kept out of the audit log.
get_postRead one post by its numeric id, with the whole body. Free. `title.rendered` and `content.rendered` carry the HTML a visitor sees; pass `context: "edit"` to also get `content.raw`, the block markup an editor would work with, which is what update_post expects back. A password-protected post returns an empty `content.rendered` and `protected: true` unless the post's password is supplied. The body is the site's published writing, so the payload is kept out of the audit log.
list_pagesList pages - the site's standing content, as opposed to its posts. Free. Pages are hierarchical, so `parent` narrows to the children of one page and `menu_order` is the field a theme sorts navigation by; neither exists on posts. There are no categories or tags here and no sticky flag. Defaults to published pages only. The bodies are the site's own writing, so the payload is kept out of the audit log.
get_pageRead one page by its numeric id, with the whole body. Free. Same shape as get_post - `title.rendered` and `content.rendered` hold the HTML a visitor sees, `context: "edit"` adds the raw block markup. `parent` and `menu_order` say where the page sits in the site's hierarchy and menus. The body is the site's own writing, so the payload is kept out of the audit log.
list_categoriesList the site's categories with their ids, names, slugs, parents and post counts. Free. The ids are what list_posts filters by and what create_post assigns - a category name passed to either is ignored, silently, which is the most common way a post ends up uncategorised. Categories are hierarchical, so `parent` narrows to one branch; there is no `offset` here, because WordPress swaps `offset` for `parent` on hierarchical taxonomies. This is the connection test: it needs no arguments, returns the site's own taxonomy rather than anybody's writing, costs nothing, and proves three things at once - that the site URL is right, that WordPress is answering on /wp-json, and that the application password was accepted, since a rejected one fails every request including this.
list_tagsList the site's tags with their ids, names, slugs and post counts. Free. Same role as list_categories: the ids are what list_posts filters by and create_post assigns, and a tag name passed instead of an id is ignored. Tags are flat rather than hierarchical, which is why this endpoint has `offset` and no `parent` - the mirror image of categories.
list_mediaList items in the media library - images, video, audio and documents - with their attachment ids, source URLs, alt text, captions and generated size variants. Free. The `id` values are what create_post's `featured_media` takes. Note that media uses `status: "inherit"` by default rather than "publish", which is WordPress's own convention for attachments; `media_type` and `mime_type` are the useful filters. Uploading is not offered here: it needs a raw binary body with a Content-Disposition filename, which this connector cannot send. Alt text and captions are somebody's writing about somebody's pictures, so the payload is kept out of the audit log.
list_usersList the site's users - the people who write it. Free. What comes back depends entirely on the credential: an account without the list_users capability silently gets only users who have published something, in view context, with no email addresses and no roles; an administrator gets everyone, and `context: "edit"` adds email, roles and capabilities. Several filters are refused rather than ignored without that capability - `roles`, `capabilities`, and ordering by email or registration date each fail with their own error. The ids are what list_posts and create_post use to name an author. These are real people's names and, in edit context, their email addresses, so the payload is kept out of the audit log.
list_commentsList comments, newest first, approved ones by default. Free. `post` narrows to one post's thread and `parent` to the replies under one comment. The filters `status`, `type`, `author`, `author_exclude` and `author_email` all require a credential allowed to moderate - without it they are refused rather than ignored, which is the usual reason a call for pending comments comes back empty-looking. These are things members of the public wrote, under their own names and with their email addresses attached in edit context, so the payload is kept out of the audit log.
Destructive
3Deletes or permanently alters something. Worth granting on purpose.
create_postCreate a post. Classified destructive rather than write because of one argument: `status: "publish"` puts the text on a public website, sends it to feed readers and subscribers, and cannot be quietly undone - reverting it to draft afterwards does not recall what was already fetched. A read-and-write grant must not be able to do that, so the whole operation is gated by its most dangerous reachable outcome even though most calls to it will be harmless drafts. This connector sends `status: "draft"` when nobody chose, which is also WordPress's own default, so the safe outcome is what happens by accident. `content` is HTML or block markup; `title` and `excerpt` are plain text. `categories` and `tags` take term ids from list_categories and list_tags, never names - a name is silently ignored. Free. The text is the caller's own writing and the response echoes it, so neither is written to the audit log.
update_postChange an existing post. Destructive for two reasons, and both matter. Passing `status: "publish"` on a draft publishes it, which is the same irreversible step create_post is gated for; and every field named here replaces what was there, with no version this API can restore - WordPress keeps revisions in its own database but exposes no way to roll one back from wp/v2. Pass only the fields to change; anything omitted is left alone, and `status` in particular is never defaulted here, because defaulting it would silently unpublish a live post. Read the post with get_post and `context: "edit"` first to see the raw block markup you are replacing. WordPress documents POST for updates and also accepts PUT and PATCH. Free. The text is the caller's own writing, so neither the arguments nor the response are written to the audit log.
create_commentLeave a comment on a post. Classified destructive for the same reason gmail.json classifies sending a message that way: it is published under the site's name where the public can read it, and WordPress emails the post's author about it. Deleting the comment afterwards does not unsend the notification and does not recall what anybody already read. A credential with the moderate_comments capability produces an approved, immediately visible comment; without it the comment lands in the moderation queue instead - but the connector cannot know which, so it is gated by the more dangerous outcome. `post` is the id of the post being commented on and is what makes the call work at all; without it WordPress has nothing to attach the comment to. `status` is honoured only for a credential that can moderate. Free. The comment text and the commenter's name and email are prose and personal data, so neither the arguments nor the response are written to the audit log.
What it will not do
Enforced by the gateway rather than left to convention, which is why each of these can be stated flatly.
- It cannot call anything else
- The 12 actions above are the whole of it. A call to any other name is refused before it reaches WordPress rather than forwarded on, and connecting your account does not add to the list: it is fixed by the connector, not discovered at run time.
- It reaches no further than your credential
- Toolspoke holds no access to WordPress of its own. Every call carries the credential you stored and nothing besides, so whatever that credential cannot reach, this connector cannot reach either.
- It never hears from WordPress
- Nothing is pushed to it. There is no webhook, no subscription and no polling, so this connector cannot notice by itself that something changed in WordPress. An agent has to ask.
- It does not smooth over provider limits
- Toolspoke does not retry, queue or back off around WordPress's own rate limits. A call that WordPress refuses comes back to the agent as a failed call.
Before you connect it
What can Claude do in WordPress?
12 named actions: 9 that only read and 3 that delete or permanently alter something. They include list_posts, get_post and create_post. Nothing outside that list is reachable: the connector declares each operation by name rather than proxying whatever an agent asks for.
What credentials does the WordPress connector need?
Application password. The connector asks for site url and username and application password. Values are encrypted at rest and attached to the outbound request by the gateway, so they are never part of the arguments an agent sends and never reach the audit log.
Does the WordPress connector work with Cursor and Codex, or only Claude?
Any client that speaks MCP, and every one of them gets the same 12 actions. There is a single address, https://toolspoke.com/mcp. Claude Code adds it with claude mcp add --transport http, Claude and Claude Desktop take it as a custom connector in settings, Cursor reads it from .cursor/mcp.json, Codex from ~/.codex/config.toml, and VS Code from .vscode/mcp.json. Each of them signs in to the gateway itself, so there is no key to paste.
What does the WordPress connector not do?
The 12 actions above are the whole of it. A call to any other name is refused before it reaches WordPress rather than forwarded on, and connecting your account does not add to the list: it is fixed by the connector, not discovered at run time. Toolspoke holds no access to WordPress of its own. Every call carries the credential you stored and nothing besides, so whatever that credential cannot reach, this connector cannot reach either. Nothing is pushed to it. There is no webhook, no subscription and no polling, so this connector cannot notice by itself that something changed in WordPress. An agent has to ask. Toolspoke does not retry, queue or back off around WordPress's own rate limits. A call that WordPress refuses comes back to the agent as a failed call.
Can I limit which actions an agent can call?
Yes, in two places. The project switches WordPress's actions on and off one at a time, for everyone in the project at once, and the screen groups them by read, write and destructive so turning off everything that deletes is one click. An individual agent key can then be narrowed further, to particular toolkits in a project and to particular actions in a toolkit. Whatever it was granted, a key never reaches a project its owner cannot.
What gets recorded when an agent calls WordPress?
Every attempt, with the agent that made it and the person that agent belongs to, the full request payload, the response payload, the status, the duration, and the credits spent. Values whose key names a secret are masked out before the record is shown to anyone. An operation the connector marks as not retained never has its response body written at all, so the gateway keeps no second copy of what was read.