MCP server (Streamable HTTP transport)
package main
import ( "fmt" "strings" "net/http" "io")
func main() {
url := "https://api.ai.wpengine.com/v1/mcp"
payload := strings.NewReader("{ \"jsonrpc\": \"2.0\", \"id\": \"example\", \"method\": \"initialize\", \"params\": { \"protocolVersion\": \"example\", \"capabilities\": {}, \"clientInfo\": { \"name\": \"example\", \"version\": \"example\" } } }")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>") req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close() body, _ := io.ReadAll(res.Body)
fmt.Println(res) fmt.Println(string(body))
}const url = 'https://api.ai.wpengine.com/v1/mcp';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"jsonrpc":"2.0","id":"example","method":"initialize","params":{"protocolVersion":"example","capabilities":{},"clientInfo":{"name":"example","version":"example"}}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}<?php
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.ai.wpengine.com/v1/mcp', [ 'body' => '{ "jsonrpc": "2.0", "id": "example", "method": "initialize", "params": { "protocolVersion": "example", "capabilities": {}, "clientInfo": { "name": "example", "version": "example" } } }', 'headers' => [ 'Authorization' => 'Bearer <token>', 'Content-Type' => 'application/json', ],]);
echo $response->getBody();curl --request POST \ --url https://api.ai.wpengine.com/v1/mcp \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "jsonrpc": "2.0", "id": "example", "method": "initialize", "params": { "protocolVersion": "example", "capabilities": {}, "clientInfo": { "name": "example", "version": "example" } } }'Exposes the gateway as a remote MCP server over the Streamable HTTP transport (JSON-RPC 2.0 over HTTP, optionally upgrading to Server-Sent Events for streamed responses). Accepts either a WP Engine API key (prefixed “wpe_”) carrying the mcp scope, scoped to the key’s project, or an Okta JWT with WPEngine-Account and WPEngine-Project headers (Standalone Coworker). External MCP clients (e.g. Claude Desktop) can instead use interactive OAuth sign-in, discovered through /.well-known/oauth-protected-resource/v1/mcp. An OAuth access token must grant a scope for each tool it calls: urn:wpengine:ai:wordpress:rw for the site, knowledge base and ability tools, and urn:wpengine:hosting:tools:rw for the WP Engine account and environment tools. A request whose token lacks the scope returns 403 with WWW-Authenticate: Bearer error="insufficient_scope", scope="..." naming the scopes to request: the token’s current scopes plus the one required. Scopes do not apply to API keys or Okta JWTs. The gateway does not preserve MCP session state between requests; clients should not rely on server-side session continuity across calls. The list_site_abilities, search_abilities, and run_site_ability tools additionally require a personal API key when using API-key auth; a project-scoped key resolves no acting user and these tools return an unauthorized error. Okta JWT callers use the verified subject instead. For run_site_ability, a timed-out read returns the tool error code timeout, including when WordPress reports an upstream timeout with a REST 504 response. Any WordPress error code and message are retained in the tool error message. Retrying the identical read is safe. An uncertain write returns ambiguous_outcome and must not be retried automatically. Site abilities publish their own input and output schemas through discovery. When available, wp-engine-ai/search-content performs literal search of editable stored content and REST-exposed custom fields, rather than relevance-ranked knowledge-base search. Matching ignores letter case by default; set case_sensitive: true for exact case. Each call visits up to 200 posts subject to work and result limits. Pass its returned next_cursor as cursor with the same query, case mode, scope and limit until complete is true; an empty page does not imply completion. Each ability response returns up to 100 occurrences by default, with a maximum of 200 occurrences and 100,000 encoded JSON bytes, excluding the MCP transport envelope. Every literal occurrence, including overlaps, produces a result with a zero-based Unicode character match_offset within its stored string leaf and a preview. Field identities can repeat at different offsets. Incomplete pages have totals: null. Cursors use compact unpadded base64url and must be copied unchanged, including letter case, with the original limit and search options. The final page has cumulative totals across all pages: matched_posts, matched_fields, and occurrences, even when its matches is empty. Separate metadata rows count as separate matching fields. Summaries should use these totals verbatim rather than recounting generated tables. Search results identify update targets, not replacement values or a snapshot of the site. When available, wp-engine-ai/replace-content applies an approved literal, case-sensitive, non-overlapping replacement to stored post content and/or manual excerpts. Send ids, find and replace; optional fields defaults to ["content"]. Explicitly select ["excerpt"] for excerpts only or ["content","excerpt"] for both. Only changed selected fields are written, together per post. A changed field that the post endpoint does not accept fails that post with unknown_fields before any selected field is saved. Core pages expose writable REST excerpts even without editor excerpt support. Custom types/controllers must expose the selected field. Generated excerpts, meta/custom fields and ACF are not replaced. Empty, duplicate or unsupported field selections reject the request before reads or writes. An empty replacement deletes the text. Search for equivalent targets with the same fields and case_sensitive: true. wp-engine-ai/manage-posts applies the same status, categories and/or tags to explicit ids, with no action selector or replacement inputs. categories and tags replace complete sets. Each category/tag list accepts at most 100 input entries before deduplication. Over-limit lists reject the entire request before reads or writes. Larger incremental tag changes can be split across calls; never split full-set category/tag replacements, since each call replaces the complete set. Use tags_add to add existing tag IDs or tags_remove to remove only those IDs, preserving all unrelated tags. Both delta fields may be combined with each other or status/categories, but not with tags; overlapping addition/removal IDs reject the request before writes. Already-present additions and absent removals are unchanged without a write. Tag deltas require a writable REST-visible tags field and term assignment permissions. Non-mutating PATCH probes honor matched REST route permissions and REST policy filters before each native tag operation without running the controller’s full-set save. A failed entry with partial: true means some changes may already have landed; inspect current state before retrying. No rollback is performed. Ordinary PATCH errors are potentially partial too: core fields can save before a later operation fails. Authorized read-back verifies status/term values; content and excerpt differences are not compared because WordPress save filters may alter them. Unavailable verification is explicitly reported with diagnostics_truncated; zero not_applied_count then is not proof that requested values landed. Both tools accept at most 50 positive integer ID entries per call. Deduplicate search-content match IDs after collecting every page and split approved IDs into batches. Both check a 10-second cooperative budget before starting each post. After expiry, remaining IDs are failed with reason not_attempted, included in failed_count, and were not read or written. Completed outcomes are preserved. Submit only those unattempted IDs in a new approved call, never replay the entire batch. Catchable unexpected site errors also stop the batch: completed outcomes remain, the interrupted post fails with execution_failed or wp_fatal_error, and later IDs are not_attempted. A started write makes the interrupted post potentially partial with unverified, truncated diagnostics. Expected WordPress error responses remain per-post failures. A started post normally finishes its mutation and verification before the next deadline check. A blocking save hook/controller or shorter host/gateway timeout can still prevent a result envelope; inspect current state instead of automatically retrying that ambiguous outcome. Invalid IDs and unsupported fields reject the batch before writes; individual permission or save failures do not roll back other posts. Already-matching values are returned as unchanged without a write. Both require an editable supported post type and recheck each ID. Inspect per-post not_applied_count and not_applied for requested status or terms ignored or changed on save. Full result envelopes are capped at 100,000 encoded bytes, retaining all IDs, outcomes and counts. diagnostics_truncated marks shortened diagnostics, values_truncated marks shortened requested/stored value previews within a diagnostic entry, and diagnostics_error reports unavailable diagnostics. Never retry changed IDs to retrieve diagnostics; replacement is not necessarily idempotent. Search results do not authorize writes. Fresh content is read at execution, but there is no snapshot or atomic conflict guard. Site-written strings and object keys in run_site_ability, get_site_knowledge_base, search_knowledge_base, and fetch_knowledge_base_document results have invisible characters removed. Each serialized result is limited to 128 KiB. Larger results are replaced with {"result_truncated":true,"excerpt":"... [tool result truncated]"}; the excerpt is a JSON prefix, not a complete document. Error messages are similarly cleaned and limited to 16 KiB of JSON-encoded text.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Header Parameters
Section titled “Header Parameters”WP Engine account context. For API keys the account acted on is derived from the key and this header is informational only. For Okta JWT (Standalone Coworker) this header is required and must match the token’s account claim.
WP Engine project context. Required for Okta JWT callers (Console Coworker). For API keys the project is derived from the key.
Optional. The agent session a site or knowledge-base tool call is being performed for, when the call answers a tool request from that session. The call is grouped with the rest of the session’s activity in observability data, and the session’s site_ids limits which sites, and which sites’ knowledge bases, those tools may reach: an out-of- scope call fails with the site_not_in_scope tool error. A value that does not name a session in the caller’s project fails those tool calls with agent_session_invalid.
Request Bodyrequired
Section titled “Request Bodyrequired”JSON-RPC request initiating the MCP session handshake.
object
object
MCP protocol version the client supports, e.g. “2025-06-18”.
Client-declared capabilities (roots, sampling, elicitation, etc.) per the MCP spec.
object
Name and version of an MCP client or server implementation.
object
JSON-RPC liveness check at the protocol level. Distinct from the ping MCP tool invoked via tools/call, which returns the server name and version as tool output.
JSON-RPC request listing the tools this server exposes.
JSON-RPC request invoking one tool.
object
object
Tool name, e.g. list_account_sites.
Tool-specific input matching that tool’s inputSchema. Fetch the authoritative shape live via tools/list rather than relying on static docs.
object
Notification confirming the client has processed the initialize response. Notifications carry no id and receive no reply (HTTP 202).
object
JSON-RPC 2.0 batch of up to 10 messages. POST bodies with more than 10 messages are rejected with HTTP 400 and a JSON-RPC Invalid Request error before any message is dispatched.
JSON-RPC request initiating the MCP session handshake.
object
object
MCP protocol version the client supports, e.g. “2025-06-18”.
Client-declared capabilities (roots, sampling, elicitation, etc.) per the MCP spec.
object
Name and version of an MCP client or server implementation.
object
JSON-RPC liveness check at the protocol level. Distinct from the ping MCP tool invoked via tools/call, which returns the server name and version as tool output.
JSON-RPC request listing the tools this server exposes.
JSON-RPC request invoking one tool.
object
object
Tool name, e.g. list_account_sites.
Tool-specific input matching that tool’s inputSchema. Fetch the authoritative shape live via tools/list rather than relying on static docs.
object
Notification confirming the client has processed the initialize response. Notifications carry no id and receive no reply (HTTP 202).
object
Responses
Section titled “Responses”One application/json JSON-RPC response for a single request, or an array of responses when the POST body was a JSON-RPC batch.
JSON-RPC 2.0 success response. result’s shape depends on the request’s method — see McpInitializeResult, McpPingResult, McpListToolsResult, and McpCallToolResult.
object
Result of an initialize request.
object
Server-declared capabilities. This server advertises tools only — no resources, prompts, sampling, or roots.
object
Name and version of an MCP client or server implementation.
object
Optional human-readable usage instructions from the server.
Empty result confirming the server is responsive.
object
Result of a tools/list request.
object
object
The tool’s input JSON Schema. Fetch live rather than relying on a static copy.
object
The tool’s output JSON Schema, when it declares one.
object
Opaque pagination cursor for the next page, when more tools remain.
Result of a tools/call request.
object
Content blocks returned by the tool (text, images, etc.) per the MCP spec. Shape is per-tool and intentionally not typed further here.
object
Optional structured result matching the tool’s outputSchema, when the tool declares one. Oversized site ability and knowledge-base search/fetch results instead contain result_truncated: true and an excerpt string.
object
True when the tool call failed; content then holds a human-readable error description rather than the tool’s normal result.
JSON-RPC 2.0 error response.
object
JSON-RPC 2.0 error object.
object
JSON-RPC error code (e.g. -32600 Invalid Request, -32601 Method not found, -32602 Invalid params, -32603 Internal error).
Optional additional error context.
object
JSON-RPC 2.0 responses for a batch request.
JSON-RPC 2.0 success response. result’s shape depends on the request’s method — see McpInitializeResult, McpPingResult, McpListToolsResult, and McpCallToolResult.
object
Result of an initialize request.
object
Server-declared capabilities. This server advertises tools only — no resources, prompts, sampling, or roots.
object
Name and version of an MCP client or server implementation.
object
Optional human-readable usage instructions from the server.
Empty result confirming the server is responsive.
object
Result of a tools/list request.
object
object
The tool’s input JSON Schema. Fetch live rather than relying on a static copy.
object
The tool’s output JSON Schema, when it declares one.
object
Opaque pagination cursor for the next page, when more tools remain.
Result of a tools/call request.
object
Content blocks returned by the tool (text, images, etc.) per the MCP spec. Shape is per-tool and intentionally not typed further here.
object
Optional structured result matching the tool’s outputSchema, when the tool declares one. Oversized site ability and knowledge-base search/fetch results instead contain result_truncated: true and an excerpt string.
object
True when the tool call failed; content then holds a human-readable error description rather than the tool’s normal result.
JSON-RPC 2.0 error response.
object
JSON-RPC 2.0 error object.
object
JSON-RPC error code (e.g. -32600 Invalid Request, -32601 Method not found, -32602 Invalid params, -32603 Internal error).
Optional additional error context.
object
Example
{ "jsonrpc": "2.0"}Accepted with no body for JSON-RPC notifications (such as notifications/initialized) that do not expect a response.
Invalid MCP request — for example, a missing or invalid Accept header, an empty POST body, a JSON-RPC batch exceeding 10 messages, or malformed JSON-RPC payload. Oversized batches return HTTP 400 with a JSON-RPC Invalid Request error (code: -32600, message: batch exceeds maximum of 10 calls) before any message is dispatched. Most other validation failures return plain text; MCP standard header mismatches return a JSON-RPC error object instead.
JSON-RPC 2.0 error returned when a POST body is a batch exceeding 10 messages.
object
object
JSON-RPC error code (e.g. -32600 Invalid Request, -32601 Method not found, -32602 Invalid params, -32603 Internal error).
Optional additional error context.
object
object
Example
{ "jsonrpc": "2.0", "error": { "code": -32600, "message": "batch exceeds maximum of 10 calls" }}Missing or invalid bearer token
object
object
Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.
Example
{ "error": { "type": "invalid_request_error", "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5" }}Headers
Section titled “Headers”Example
Bearer realm="ai-services"The token is valid, but the request is forbidden. Error type “permission_error” means the token’s scope/permission is insufficient or the authenticated user is not authorized for the target account; “entitlement_required” means the account lacks the required product entitlement; and “account_required” means no billing account could be resolved. On /v1/chat/completions when content moderation is enabled, and on /v1/sessions/{session_id}/events, error type “content_policy_violation” instead means the request content was blocked by content moderation policy; that response never identifies which filter or category matched.
object
object
Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.
Example
{ "error": { "type": "invalid_request_error", "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5" }}Request Content-Type is not application/json.
A required dependency is temporarily unavailable. Retry after a short delay with exponential back-off. An entitlement lookup failure uses error type “entitlement_unavailable”.
object
object
Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.
Example
{ "error": { "type": "invalid_request_error", "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5" }}