The Bluehost MCP module exposes tools via the Bluehost MCP Server at /wp-json/blu/mcp. These are WordPress abilities exposed as MCP tools for AI assistants and MCP clients.
Developer documentation: see docs/index.md (table of contents) and AGENTS.md for agents and repo orientation.
MCP tool naming: Abilities registered as blu/<something> are exposed as MCP tools named blu-<something> (slash replaced with hyphen). For example, ability blu/posts-search becomes MCP tool blu-posts-search. This hyphen form is what appears in tools/list and what the gateway returns.
The server exposes 3 gateway tools instead of ~83 individual tools. This reduces token usage by ~96% — the LLM discovers and calls abilities on demand rather than receiving all tool schemas upfront.
The server registers exactly 3 gateway tools. The default names are shown below, but MCP clients must not hardcode these names. Instead, call tools/list and identify the 3 gateway roles by their input schema shape:
| Role | Default name | How to identify (from tools/list inputSchema) |
|---|---|---|
| List | blu-list-abilities |
Has optional search and name_prefix properties (both string), no ability_name |
| Schema | blu-get-ability-schema |
Requires ability_name (string), no parameters property |
| Call | blu-call-ability |
Requires ability_name (string) and has optional parameters (object) |
Before calling any tools, establish an MCP session:
- Send
initialize→ server returnsMcp-Session-Idheader - Send
notifications/initializedwith that session ID - Use the same
Mcp-Session-Idin all subsequent requests until it expires (24h inactivity timeout)
You do not need to re-initialize for each tool call — reuse the session ID.
Once a session is established, call tools/list to get the 3 gateway tools and identify them by schema shape. Then interact in 3 steps:
1. Discover — call the List tool to see what's available:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "blu-list-abilities",
"arguments": {}
}
}Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\"statusCode\":200,\"status\":\"success\",\"message\":[...]}"
}
],
"structuredContent": {
"statusCode": 200,
"status": "success",
"message": [
{
"name": "blu-posts-search",
"label": "Search Posts",
"description": "Search and filter WordPress posts with pagination",
"annotations": { "readonly": true }
}
]
}
}
}The ability list is in result.structuredContent.message (parsed) or result.content[0].text (JSON string). Each entry includes name (hyphen-form, use this with blu-get-ability-schema and blu-call-ability), label, description, and annotations.
2. Inspect — call the Schema tool to learn what parameters an ability accepts:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "blu-get-ability-schema",
"arguments": { "ability_name": "blu-posts-search" }
}
}Response result.structuredContent.message contains:
{
"name": "blu-posts-search",
"label": "Search Posts",
"description": "Search and filter WordPress posts with pagination",
"input_schema": {
"type": "object",
"properties": {
"search": { "type": "string", "description": "Search term" },
"per_page": { "type": "integer", "description": "Posts per page" }
}
},
"annotations": { "readonly": true }
}3. Execute — call the Call tool with the ability name and parameters:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "blu-call-ability",
"arguments": {
"ability_name": "blu-posts-search",
"parameters": { "search": "hello", "per_page": 5 }
}
}
}Response result.structuredContent contains the ability's result (format varies by ability).
Important: Never call ability names directly as MCP tool names (e.g.
"name": "blu-posts-search"at thetools/calllevel). Abilities are only accessible through the Call gateway tool. The only valid MCP tool names are the 3 gateway tools returned bytools/list.
Both gateway list tools (blu-list-abilities) and the REST catalog tool (blu-list-api-functions) accept optional filters. All filters are AND-composed; omit them to return the full catalog.
blu-list-abilities:
| Filter | Type | Behavior |
|---|---|---|
search |
string | Case-insensitive substring match across each ability's name (hyphen form), label, and description. |
name_prefix |
string | Prefix match on the MCP tool name (hyphen form). Two WooCommerce surfaces are exposed: "blu-wc-" for Bluehost's WooCommerce wrappers and "woocommerce-" for WooCommerce-native abilities. Slash form is normalized to hyphen form (e.g. "blu/wc" ≡ "blu-wc"). |
// Bluehost's WooCommerce wrappers under "blu-wc-products"
{
"method": "tools/call",
"params": {
"name": "blu-list-abilities",
"arguments": { "name_prefix": "blu-wc-products", "search": "category" }
}
}
// WooCommerce-native abilities under "woocommerce-products"
{
"method": "tools/call",
"params": {
"name": "blu-list-abilities",
"arguments": { "name_prefix": "woocommerce-products" }
}
}blu-list-api-functions:
| Filter | Type | Behavior |
|---|---|---|
namespace |
string | Exact match on the REST namespace as WordPress registered it. Multi-segment ("wp/v2", "wc/v3", "wc-admin/marketing") and single-segment ("wc-analytics") namespaces are both supported. Leading and trailing slashes are tolerated. |
methods |
array of "GET" | "POST" | "PATCH" | "DELETE" |
Restrict to listed HTTP methods (uppercase, validated by the schema enum). Omit or pass an empty array to allow all methods. |
search |
string | Case-insensitive substring match on the route string. |
{
"method": "tools/call",
"params": {
"name": "blu-list-api-functions",
"arguments": { "namespace": "wp/v2", "methods": ["GET"] }
}
}Each item in the response is one (route, method) pair plus the derived namespace (e.g. "wp/v2" or "wc-analytics"), so clients can group or filter further without parsing route strings. Endpoints registered with combined methods (e.g. WP_REST_Server::EDITABLE = "POST, PUT, PATCH") emit one row per method.
The MCP transport route /blu/mcp is excluded from the catalog so the LLM can't discover-and-invoke its way back into the transport. The same route is also rejected by blu-run-api-function if passed directly.
The schemas above are the what; this section is the how. Every list call has a cost — both the request schema the model carries around and the response it has to read and reason about. The filters exist precisely so the model doesn't slurp the whole registry into context every time. Used together, they're the difference between "load all 80-odd abilities and pick one" and "load the four that could possibly match."
blu-list-abilities — name_prefix and search are your scalpel.
name_prefixis the cheapest filter. Anchor on a known surface and you cut the response immediately:blu-postsfor the post abilities,blu-mediafor the media abilities,blu-usersfor user abilities,blu-global-stylesfor theme/styles work. The server normalizes slash form to hyphens and trims trailing-for you, soblu/posts,blu-posts, andblu-posts-all mean the same thing — you don't have to remember which one.searchdoes a case-insensitive substring match across each ability'sname(hyphen form),label, anddescription. That last one is the secret weapon — a keyword like"upload"or"category"will hit descriptions even when the tool's name says nothing about it. Usesearchwhen you know what you want,name_prefixwhen you know where it lives.- They compose with AND.
name_prefix: "blu-media"+search: "upload"returns just the media upload ability — a much cheaper request than fetching the whole catalog and filtering client-side. Always reach for both together when you can.
blu-list-api-functions — namespace, methods, and search keep REST discovery cheap.
namespaceis an exact match on the namespace WordPress registered the route under —"wp/v2","wc/v3","wc-analytics","wc-admin/marketing". Single- and multi-segment both work; leading/trailing slashes are forgiven. If you already know which namespace you want, scoping to that one alone cuts the response dramatically.methodsaccepts an array of"GET","POST","PATCH","DELETE"(uppercase, validated by the schema enum, max 4 unique). Passing["GET"]when you're exploring read-only routes is one of the highest-leverage filters available — on a typical WordPress install you're often dropping more than half the rows in one go.searchhere is a case-insensitive substring match on the route path only, not the description. Sosearch: "posts"matches/wp/v2/postsand/wp/v2/posts/(?P<id>[\d]+), but won't find a route whose description happens to mention posts.
The rule of thumb: the model's first call into either list tool should almost always carry at least one filter. An unfiltered call is a fine debug move; in production flow, it's a tax you don't need to pay.
The gateway only exposes abilities matching allowed namespaces or categories:
- Namespaces:
blu/,woocommerce/(configurable viablu_mcp_allowed_namespacesfilter) - Categories:
blu-mcp,woocommerce-rest(configurable viablu_mcp_allowed_categoriesfilter)
blu/ and blu-mcp cover Bluehost's own abilities (including the blu/wc-* WooCommerce wrappers). woocommerce/ and woocommerce-rest cover the abilities WooCommerce registers natively (since WC 10.3 ships its own Abilities API integration — products, orders, etc. under woocommerce/<resource>-<op>).
To add another namespace:
add_filter( 'blu_mcp_allowed_namespaces', function ( $namespaces ) {
$namespaces[] = 'myplugin/';
return $namespaces;
} );To bypass the gateway and expose all individual tools directly (previous behavior):
add_filter( 'blu_mcp_use_gateway', '__return_false' );All abilities below are accessible through the gateway. The Ability name column shows the internal registration name. The MCP tool name column shows the hyphen-form name to use with blu-call-ability and blu-get-ability-schema.
| Ability name | MCP tool name | Description |
|---|---|---|
blu/posts-search |
blu-posts-search |
Search and filter WordPress posts with pagination |
blu/get-post |
blu-get-post |
Get a WordPress post by ID |
blu/add-post |
blu-add-post |
Add a new WordPress post |
blu/update-post |
blu-update-post |
Update a WordPress post by ID |
blu/delete-post |
blu-delete-post |
Delete a WordPress post by ID |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/list-categories |
blu-list-categories |
List all WordPress post categories |
blu/add-category |
blu-add-category |
Add a new WordPress post category |
blu/update-category |
blu-update-category |
Update a WordPress post category |
blu/delete-category |
blu-delete-category |
Delete a WordPress post category |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/list-tags |
blu-list-tags |
List all WordPress post tags |
blu/add-tag |
blu-add-tag |
Add a new WordPress post tag |
blu/update-tag |
blu-update-tag |
Update a WordPress post tag |
blu/delete-tag |
blu-delete-tag |
Delete a WordPress post tag |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/pages-search |
blu-pages-search |
Search and filter WordPress pages with pagination |
blu/get-page |
blu-get-page |
Get a WordPress page by ID |
blu/add-page |
blu-add-page |
Add a new WordPress page |
blu/update-page |
blu-update-page |
Update a WordPress page by ID |
blu/delete-page |
blu-delete-page |
Delete a WordPress page by ID |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/list-media |
blu-list-media |
List WordPress media items with pagination and filtering |
blu/get-media |
blu-get-media |
Get a WordPress media item by ID |
blu/get-media-file |
blu-get-media-file |
Get the actual file content (blob) of a WordPress media item |
blu/upload-media |
blu-upload-media |
Upload a new media file to WordPress |
blu/update-media |
blu-update-media |
Update a WordPress media item |
blu/delete-media |
blu-delete-media |
Delete a WordPress media item permanently |
blu/search-media |
blu-search-media |
Search WordPress media by title, caption, or description |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/list-post-types |
blu-list-post-types |
List all registered WordPress post types (built-in and custom) |
blu/cpt-search |
blu-cpt-search |
Search and filter content items within a custom post type with pagination |
blu/get-cpt |
blu-get-cpt |
Get a single content item from a custom post type by ID |
blu/add-cpt |
blu-add-cpt |
Create a new content item within an existing custom post type |
blu/update-cpt |
blu-update-cpt |
Update an existing content item in a custom post type by ID |
blu/delete-cpt |
blu-delete-cpt |
Permanently delete a content item from a custom post type by ID |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/users-search |
blu-users-search |
Search and filter WordPress users with pagination |
blu/get-user |
blu-get-user |
Get a WordPress user by ID |
blu/add-user |
blu-add-user |
Add a new WordPress user |
blu/update-user |
blu-update-user |
Update a WordPress user by ID |
blu/delete-user |
blu-delete-user |
Delete a WordPress user by ID |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/get-general-settings |
blu-get-general-settings |
Get WordPress general site settings |
blu/update-general-settings |
blu-update-general-settings |
Update WordPress general site settings |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/get-site-info |
blu-get-site-info |
Get detailed site information (name, URL, description, admin email, plugins, themes, users, etc.) |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/get-global-styles |
blu-get-global-styles |
Get a global styles configuration by ID |
blu/update-global-styles |
blu-update-global-styles |
Update a global styles configuration (colors, typography, spacing, etc.) |
blu/get-active-global-styles |
blu-get-active-global-styles |
Get the currently active global styles for the current theme |
blu/get-active-global-styles-id |
blu-get-active-global-styles-id |
Get the active global styles ID (for get/update) |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/get-active-theme |
blu-get-active-theme |
Get the active theme information |
Two surfaces are exposed:
- Bluehost WooCommerce tools (
blu/wc-*, MCP formblu-wc-*): wrappers under theblu/namespace listed below. Usename_prefix: "blu-wc-"onblu-list-abilitiesto isolate. - WooCommerce-native abilities (
woocommerce/<resource>-<op>, MCP formwoocommerce-<resource>-<op>): registered by WooCommerce 10.3+ inwoocommerce/src/Internal/Abilities/AbilitiesRestBridge.php. Covers products, orders, and other WC resources with list/get/create/update/delete operations. Usename_prefix: "woocommerce-"to isolate. Bothwoocommerce/namespace andwoocommerce-restcategory are whitelisted by default.
| Ability name | MCP tool name | Description |
|---|---|---|
blu/wc-products-search |
blu-wc-products-search |
Search WooCommerce products |
blu/wc-get-product |
blu-wc-get-product |
Get a WooCommerce product by ID |
blu/wc-add-product |
blu-wc-add-product |
Add a WooCommerce product |
blu/wc-update-product |
blu-wc-update-product |
Update a WooCommerce product |
blu/wc-delete-product |
blu-wc-delete-product |
Delete a WooCommerce product |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/wc-list-product-categories |
blu-wc-list-product-categories |
List WooCommerce product categories |
blu/wc-add-product-category |
blu-wc-add-product-category |
Add a WooCommerce product category |
blu/wc-update-product-category |
blu-wc-update-product-category |
Update a WooCommerce product category |
blu/wc-delete-product-category |
blu-wc-delete-product-category |
Delete a WooCommerce product category |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/wc-list-product-tags |
blu-wc-list-product-tags |
List WooCommerce product tags |
blu/wc-add-product-tag |
blu-wc-add-product-tag |
Add a WooCommerce product tag |
blu/wc-update-product-tag |
blu-wc-update-product-tag |
Update a WooCommerce product tag |
blu/wc-delete-product-tag |
blu-wc-delete-product-tag |
Delete a WooCommerce product tag |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/wc-list-product-brands |
blu-wc-list-product-brands |
List WooCommerce product brands |
blu/wc-add-product-brand |
blu-wc-add-product-brand |
Add a WooCommerce product brand |
blu/wc-update-product-brand |
blu-wc-update-product-brand |
Update a WooCommerce product brand |
blu/wc-delete-product-brand |
blu-wc-delete-product-brand |
Delete a WooCommerce product brand |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/wc-orders-search |
blu-wc-orders-search |
Get a list of WooCommerce orders |
blu/wc-reports-coupons-totals |
blu-wc-reports-coupons-totals |
Get WooCommerce coupons totals report |
blu/wc-reports-customers-totals |
blu-wc-reports-customers-totals |
Get WooCommerce customers totals report |
blu/wc-reports-orders-totals |
blu-wc-reports-orders-totals |
Get WooCommerce orders totals report |
blu/wc-reports-products-totals |
blu-wc-reports-products-totals |
Get WooCommerce products totals report |
blu/wc-reports-reviews-totals |
blu-wc-reports-reviews-totals |
Get WooCommerce reviews totals report |
blu/wc-reports-sales |
blu-wc-reports-sales |
Get WooCommerce sales report |
| Ability name | MCP tool name | Description |
|---|---|---|
blu/list-api-functions |
blu-list-api-functions |
List all available WordPress REST API endpoints that support CRUD |
blu/get-function-details |
blu-get-function-details |
Get detailed metadata for a specific REST API route and HTTP method |
blu/run-api-function |
blu-run-api-function |
Execute a REST API request by route, method, and parameters |
Tokens are the gold of the AI era. Every byte of context you spend on tool definitions, schemas, descriptions, and noisy responses is a byte the model no longer has for thinking about the user's actual problem. The gateway pattern above already saves ~96% of the upfront tool-schema cost — but every new ability we add still chips away at the reserve: one more row in tools/list, one more schema fetch the model might trigger, one more "which of these similar tools should I pick?" decision.
So before reaching for a new ability, treat the catalog like a vault, not a catch-all. Ask first: can blu-run-api-function already do this? If the REST route shows up in blu-list-api-functions, the model can call it through the generic CRUD ability today — no new tool needed, no new tokens spent.
A new ability earns its place in the vault only when one of these is true:
The tool introduces platform-specific behavior that no underlying REST endpoint exposes. blu/update-global-styles is the canonical example — on a successful write it attaches an applied / not_applied diff to the response, where each not_applied entry carries the dot-path and a human-readable reason ("wrong key path", "value dropped by sanitization"). The raw PUT /wp/v2/global-styles/<id> returns the updated record but quietly drops anything it didn't like — the model never finds out. If your "new" tool is just a rename of fields the REST route already accepts, you've added a synonym, not a tool.
A single logical operation that would otherwise require the model to chain several REST calls (each with its own request/response token tax) collapses cleanly into one ability. blu/get-site-info is a clean example — one call returns site name, URL, description, admin email, WordPress version, plugins (with active state), themes (active + all), and users in a single payload. Without it, the model would have to call site-info, list plugins, list active plugins, get themes, and list users separately — five round-trips, five JSON blobs to parse. The token math almost always wins here.
The native REST response is noisy, deeply nested, or buries the one field the model actually needs under 4KB of metadata. A wrapper that flattens, prunes, or derives computed fields can turn a 5KB response into 500 bytes. That saving compounds every single time the tool is called — pure gold left in the reserve.
For high-frequency operations, a focused first-class tool beats a generic one. blu/posts-search and blu/pages-search exist as dedicated abilities precisely because content discovery dominates WordPress workflows — they ship with a tight, purpose-built schema instead of forcing the model to negotiate the full /wp/v2/posts collection-param surface every time. Shaving even a handful of tokens per call adds up fast across thousands of conversations.
- The REST endpoint already works fine through
blu-run-api-functionand the model can figure it out. - You only need a slightly different parameter name or shape — write better descriptions instead.
- It's a "nice to have" for a workflow that fires once a quarter.
- You're not sure yet. Add it later when the need is concrete; deleting a shipped tool is harder than adding one.
Every tool you don't add is gold left in the vault for the next conversation. Spend it deliberately.
Any MCP client connecting to this server must handle the following. This applies regardless of the LLM being used.
- URL:
https://YOUR-SITE.com/wp-json/blu/mcp - Methods: POST (messages), GET (SSE, currently 405), DELETE (session termination)
- Authentication: Required (e.g. WordPress Application Password or JWT via Hiive)
- POST
initializerequest → server returnsMcp-Session-Idresponse header - POST
notifications/initializednotification (noidfield, no response expected) with that session header - Include
Mcp-Session-Idheader on every subsequent request - Sessions expire after 24 hours of inactivity (max 32 per user)
- On "Invalid or expired session" error, re-run steps 1–2
POST /wp-json/blu/mcp
Authorization: Bearer <token>
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"my-client","version":"1.0"}}}
Response includes Mcp-Session-Id: <id> header.
POST /wp-json/blu/mcp
Authorization: Bearer <token>
Mcp-Session-Id: <id>
Content-Type: application/json
{"jsonrpc":"2.0","method":"notifications/initialized"}
Every request and response uses the JSON-RPC 2.0 format:
Request: { "jsonrpc": "2.0", "id": <int|string>, "method": "<method>", "params": {...} }
Response: { "jsonrpc": "2.0", "id": <int|string>, "result": {...} }
Error: { "jsonrpc": "2.0", "id": <int|string>, "error": { "code": <int>, "message": "<string>" } }
Batch requests (array of messages) are supported per the JSON-RPC 2.0 spec.
Successful tool calls return a nested response that clients must unwrap:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{ "type": "text", "text": "<JSON string of the result>" }
],
"structuredContent": { ... }
}
}result.structuredContent— the parsed result object (preferred)result.content[0].text— the same result as a JSON string (fallback)- Image results use
content[0].type: "image"with base64dataandmimeType
Errors come in two forms that clients must distinguish:
Protocol errors (tool not found, invalid request) — JSON-RPC error format:
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "Tool not found: foo" } }Tool execution errors (permission denied, ability failure) — MCP isError format:
{
"jsonrpc": "2.0", "id": 1,
"result": {
"content": [{ "type": "text", "text": "Access denied for tool: blu-call-ability" }],
"isError": true
}
}Inside structuredContent, gateway abilities return a consistent wrapper:
{
"statusCode": 200,
"status": "success",
"message": [ ... ]
}statusCode— HTTP-style status (200, 400, 404, 500)status—"success"or"error"message— the actual payload (array for lists, object for single items, string for errors)
The MCP 2025-06-18 spec defines GET for SSE streaming. This server currently returns HTTP 405 for GET requests (not implemented), but spec-compliant clients should be prepared to handle SSE in future versions.