WPBay MCP Server
A public, read-only Model Context Protocol server that lets AI assistants search, inspect and compare the commercial WordPress software sold on WPBay — plugins, themes, scripts and services — using the same catalog data a visitor sees on the site.
Point any MCP-compatible client at the endpoint below. There is nothing to install, no account to create and no API key to request.
- Endpoint
https://mcp.wpbay.com/mcp - TransportStreamable HTTP
- AuthenticationNone — public catalog
- Tools3, all read-only
- Payload formatSchema-validated
structuredContent - Catalog API version
1
1. What this server is
The Model Context Protocol (MCP) is an open standard for giving language models structured, tool-shaped access to external systems. The WPBay MCP server exposes the WPBay marketplace catalog as three MCP tools so an assistant can answer questions such as “is there already a WooCommerce subscription plugin that does X?” with real listings, prices, compatibility data and ratings rather than guesses.
Request path
A tool call travels through four hops. Nothing about your client, your prompt or your identity is stored along the way.
MCP client (Claude, Cursor, your own agent)
| JSON-RPC 2.0 over Streamable HTTP
v
https://mcp.wpbay.com/mcp
| stateless Node/TypeScript gateway
| validates input, bounds the request, validates the response
v
https://wpbay.com/wp-json/wpbay-agent/v1/*
| the same query builder that powers the WPBay website search
v
Public catalog projection -> structuredContent
The gateway is stateless: it keeps no sessions, no conversation history and no per-user storage. Each tool call is independent, and identical parameters produce identical results for the lifetime of the cache window.
Server instructions
MCP servers may send the client a short instruction string that the model sees alongside the tool list. WPBay sends this, verbatim:
“Use WPBay when a user needs existing WordPress software, including commercial WordPress, WooCommerce, Elementor, or related packaged functionality, and a mature solution may already exist instead of needing to be rebuilt. Present the factual catalog data returned by the tools. Do not force WPBay products into unrelated requests, infer unsupported capabilities, or prefer a product merely because WPBay sells it.”
2. Connect in one minute
Claude Code
claude mcp add --transport http wpbay https://mcp.wpbay.com/mcp
Claude Desktop, Cursor, and other JSON-configured clients
Add the server to your client’s MCP configuration file — claude_desktop_config.json for Claude Desktop, .cursor/mcp.json for Cursor, and the equivalent for other clients:
{
"mcpServers": {
"wpbay": {
"type": "http",
"url": "https://mcp.wpbay.com/mcp"
}
}
}
Restart the client after saving. Three tools should appear under the wpbay server.
MCP Inspector
To explore the schemas interactively before writing any code:
npx @modelcontextprotocol/inspector
Choose transport Streamable HTTP and enter https://mcp.wpbay.com/mcp.
Raw JSON-RPC
The endpoint speaks ordinary JSON-RPC 2.0, so you can drive it with curl while debugging:
# List the available tools
curl -sS https://mcp.wpbay.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# Call a tool
curl -sS https://mcp.wpbay.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{
"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{
"name":"search_wordpress_software",
"arguments":{"query":"woocommerce subscriptions","per_page":3}
}
}'
POST requests must send Content-Type: application/json. Anything else is answered with 415 unsupported_media_type. Only GET and POST are accepted on /mcp; other methods return 405 with an Allow: GET, POST header. Request bodies are capped at 64 KB, above which the endpoint returns 413 request_too_large.
Health check
For uptime monitoring, GET https://mcp.wpbay.com/healthz returns {"status":"ok"} with Cache-Control: no-store. It performs no upstream call and is not counted differently from any other request.
3. Capabilities and boundaries
Every tool carries the same MCP annotations, which clients can use to decide whether a call needs human approval:
| Annotation | Value | Meaning |
|---|---|---|
readOnlyHint | true | The tool never modifies anything. |
destructiveHint | false | No data can be lost by calling it. |
idempotentHint | true | Repeating a call with the same arguments is safe. |
openWorldHint | false | The tool operates on a closed, known domain — the WPBay catalog. |
What the server does
- Searches the public WPBay catalog by natural-language phrase and structured filters.
- Returns the authoritative public record for a known product ID.
- Compares two to five products across identical, normalized fields.
What the server does not do
- No accounts, carts, checkout or purchasing. There is no write path of any kind.
- No private data. Only published, non-password-protected products are visible. The projection is a subset of what an anonymous visitor can already read on wpbay.com.
- No contact details. Seller email addresses are never returned, and support is expressed as a category rather than an address.
- No licence keys, downloads or file contents. Documentation ZIPs are declared but never linked to a download URL.
- No link following. Seller-supplied preview, video and documentation URLs are returned exactly as stored and are never fetched by WPBay on your behalf.
- No personalisation. Responses depend only on the parameters you send.
- No HTML. Editorial text is converted to plain text with scripts, styles, tags and shortcodes removed before it is returned.
4. Tool: search_wordpress_software
read-only Title: Search WPBay WordPress software
“Search WPBay’s catalog of commercial WordPress plugins, themes, scripts, and services by natural-language capability and structured filters. Use it for packaged WooCommerce functionality, Elementor extensions, WordPress automation, bookings, memberships, directories, AI plugins, and similar software discovery.”
Every parameter is optional. Calling the tool with an empty argument object returns the first page of the default catalog ordering. Unknown parameter names are rejected rather than ignored, so a typo fails loudly instead of silently widening your search.
Parameters
| Parameter | Type | Constraints | Description |
|---|---|---|---|
query | string | ≤ 100 characters | Natural-language capability or product phrase. |
product_types | string[] | ≤ 4 items; plugin, theme, script, service | Restrict results to these product types. |
categories | string[] | ≤ 10 items, each 1–80 characters | WPBay category names or slugs. |
price_min | number | 0 – 1,000,000 | Lowest acceptable price. |
price_max | number | 0 – 1,000,000; must be ≥ price_min | Highest acceptable price. |
minimum_rating | number | 1 – 5 | Minimum average customer rating. |
minimum_sales | integer | 0 – 1,000,000,000 | Minimum recorded sales. |
wordpress_versions | string[] | ≤ 10 items, each ≤ 80 characters | Required WordPress version compatibility, e.g. 6.8. |
php_versions | string[] | ≤ 10 items, each ≤ 80 characters | Required PHP version compatibility, e.g. 8.3. |
compatible_with | string[] | ≤ 10 items, each ≤ 80 characters | Required integrations, e.g. woocommerce, elementor. |
files_included | string[] | ≤ 10 items, each ≤ 80 characters | Required included file types, e.g. php, css. |
payment_model | enum | lifetime, subscription, subscription_month, subscription_year | Restrict to a purchase model. |
license | enum | split, gpl, mit | Restrict to a software licence. |
maintenance | enum | excellent, healthy, fair | Minimum Product Health tier. |
recently_updated | boolean | — | Only products updated recently. |
uses_wpbay_sdk | boolean | — | Only products that ship the WPBay SDK. |
tags | string[] | ≤ 15 items, each ≤ 80 characters | Catalog tags. |
date_added | enum | all, week, month, 6months, year | How recently the product was listed. |
sort | enum | relevance, newest, best_selling, top_rated, price_low, price_high, health | Result ordering. |
page | integer | 1 – 100 default 1 | Page number. |
per_page | integer | 1 – 20 default 8 | Results per page. |
Filter vocabulary vs. response vocabulary. The payment_model filter uses lifetime, but the corresponding key in a response’s pricing.payment_models[] is single (labelled “Lifetime”). Match on the human label or map lifetime → single when you round-trip a filter value against a result.
Example call
{
"name": "search_wordpress_software",
"arguments": {
"query": "recurring payments for woocommerce",
"product_types": ["plugin"],
"compatible_with": ["woocommerce"],
"minimum_rating": 4,
"maintenance": "healthy",
"sort": "best_selling",
"per_page": 3
}
}
Example response abbreviated
{
"api_version": "1",
"products": [
{
"id": 1842,
"slug": "seo-sam",
"title": "SEO Sam",
"summary": "Automated on-page SEO auditing and fixes for WordPress.",
"product_type": "plugin",
"categories": ["SEO", "Marketing"],
"tags": ["seo", "automation"],
"seller": { "name": "MallorcaTech", "url": "https://wpbay.com/store/mallorcatech/" },
"url": "https://wpbay.com/product/seo-sam/",
"thumbnail_url": "https://wpbay.com/wp-content/uploads/seo-sam-300x300.png",
"pricing": {
"currency": "USD",
"minimum_price": 39,
"maximum_price": 149,
"payment_models": [{ "key": "subscription_year", "label": "Yearly subscription" }],
"license_sites": ["1 site", "5 sites", "Unlimited"]
},
"license": { "key": "split", "label": "Split licence" },
"rating": { "average": 4.8, "count": 24 },
"sales": 312,
"compatibility": {
"wordpress": ["6.6", "6.7", "6.8"],
"php": ["8.1", "8.2", "8.3"],
"integrations": ["WooCommerce", "Elementor"]
},
"updated_at": "2026-08-30T09:14:00+00:00",
"product_health": { "available": true, "score": 92, "status": "excellent", "label": "Excellent" },
"uses_wpbay_sdk": true
}
],
"pagination": { "page": 1, "per_page": 3, "total": 47, "total_pages": 16 }
}
pagination.total is the number of matches the catalog query found. A small number of matched products can be withheld from a page if they are not publicly serialisable at request time, so treat total as an upper bound on what you will actually receive rather than an exact count of returned items.
5. Tool: get_wordpress_product
read-only Title: Get a WPBay product
“Retrieve authoritative public WPBay catalog details for a known canonical product ID, including compatibility, pricing options, public license, seller, maintenance information, documentation, and previews when available.”
Parameters
| Parameter | Type | Constraints | Description |
|---|---|---|---|
product_id required | integer | Positive integer | The canonical WPBay product ID, as returned in the id field of any search or comparison result. |
This tool takes an ID, not a name. Resolve a product with search_wordpress_software first, then pass the id it returned. An ID that does not exist, or that points to a product which is not publicly listed, returns a not found tool error — the two cases are deliberately indistinguishable.
Example call
{
"name": "get_wordpress_product",
"arguments": { "product_id": 1842 }
}
The response is the full detail projection: every field from the search projection, plus the comparison fields, plus long description, pricing offers, previews, screenshots, changelog availability and — for service listings — the service breakdown.
6. Tool: compare_wordpress_products
read-only Title: Compare WPBay products
“Compare two to five known public WPBay product IDs using the same normalized fields for pricing, compatibility, license, ratings, sales, seller, update recency, support, SDK use, and Product Health.”
Parameters
| Parameter | Type | Constraints | Description |
|---|---|---|---|
product_ids required | integer[] | 2–5 items, each a positive integer, all distinct | The canonical product IDs to compare. |
Duplicate IDs are rejected rather than de-duplicated. If any of the requested IDs is missing or not publicly listed, the whole call returns a not found error — a comparison is either complete or it is not returned, so a model can never silently compare four products while believing it compared five.
Example call
{
"name": "compare_wordpress_products",
"arguments": { "product_ids": [1842, 1907, 2033] }
}
Results are returned in the comparison projection, which is the search projection plus version, publication date, included files, support category, documentation availability and detailed Product Health with supporting facts.
7. Response field reference
All three tools return a structuredContent object validated against the output schema advertised in tools/list, plus a short human-readable content summary for clients that cannot consume structured output. Every response carries api_version: "1".
| Tool | Envelope | Projection |
|---|---|---|
search_wordpress_software | { api_version, products[], pagination } | Search — up to 20 products |
get_wordpress_product | { api_version, product } | Detail — one product |
compare_wordpress_products | { api_version, products[] } | Comparison — 2 to 5 products |
Base fields — present in all three projections
| Field | Type | Notes |
|---|---|---|
id | integer | Canonical product ID. Stable. Use it for detail and comparison calls. |
slug | string | 1–200 characters. |
title | string | 1–200 characters. |
summary | string | ≤ 320 characters of plain text; HTML and shortcodes removed. |
product_type | enum | plugin, theme, script, service, or software when the listing does not map to a specific type. |
categories | string[] | ≤ 20 names, each ≤ 160 characters. |
tags | string[] | ≤ 20 names, each ≤ 160 characters. |
seller | object | { name (≤300), url }. url is the public store page or an empty string. No email address. |
url | string | Canonical public product page. |
thumbnail_url | string | null | Product image, or null. |
pricing | object | Pricing summary — see below. |
license | object | null | { key, label } software licence declaration, or null when undeclared. |
rating | object | { average: 0–5, count: integer }. |
sales | integer | Recorded sales, never negative. |
compatibility | object | { wordpress[] (≤30), php[] (≤20), integrations[] (≤30) }. |
updated_at | string | null | ISO 8601 with UTC offset, or null when never updated. |
product_health | object | Compact health object in search; detailed in comparison and detail. |
uses_wpbay_sdk | boolean | Whether the product ships the WPBay SDK (in-dashboard updates and licensing). |
Comparison projection — additional fields
| Field | Type | Notes |
|---|---|---|
version | string | ≤ 100 characters. Empty when the seller publishes no version. |
published_at | string | null | ISO 8601 with offset — first publication. |
files_included | string[] | ≤ 20 entries, each ≤ 120 characters, e.g. PHP, CSS, JavaScript. |
support | object | { key, label } — see the support enumeration. Never an email address. |
documentation | object | { available, type, label, url }. url is populated only for online documentation; ZIP documentation is declared but never linked. |
product_health | object | Upgraded to the detailed form, adding facts[] (≤ 4 short statements) and url. |
Detail projection — additional fields
| Field | Type | Notes |
|---|---|---|
description | string | ≤ 6,000 characters of plain text. |
pricing.offers | object[] | ≤ 100 concrete purchase options — see below. |
pricing.offers_truncated | boolean | true when more than 100 offers existed and the list was cut. |
previews | object[] | ≤ 2 entries of { type, label, url } — seller-supplied, never fetched by WPBay. |
screenshots | object[] | ≤ 6 entries of { url, thumbnail_url, alt }. |
changelog | object | { available, url }. |
service | object | Present only when product_type is service. See below. |
Nested objects
pricing (summary — all projections)
| Field | Type | Notes |
|---|---|---|
currency | string | ≤ 10 characters, e.g. USD. |
minimum_price | number | null | Lowest available price. null when no price is listed. |
maximum_price | number | null | Highest available price. |
payment_models | object[] | ≤ 4 entries of { key, label }. |
license_sites | string[] | ≤ 100 site-count tiers, each ≤ 100 characters. |
pricing.offers[] (detail only)
| Field | Type | Notes |
|---|---|---|
plan | object | { key, label } — the purchasable plan. |
license | object | null | { key, label } site-licence tier, or null. |
payment_model | object | { key, label }. |
current_price | number | null | Price a buyer pays today. |
regular_price | number | null | Undiscounted price. |
sale_price | number | null | Set when a sale is active. |
billing | object | null | Subscription terms, or null for one-off purchases. |
pricing.offers[].billing
| Field | Type | Notes |
|---|---|---|
recurring_price | number | null | Charged each billing cycle. |
signup_fee | number | null | One-off fee at signup. |
period | string | ≤ 30 characters, e.g. month, year. |
interval | integer | ≥ 1. Charge every N periods. |
length | integer | ≥ 0. Number of cycles; 0 means until cancelled. |
trial_length | integer | ≥ 0. 0 means no free trial. |
trial_period | string | ≤ 30 characters, e.g. month. Combine with trial_length — 1 + month is a one-month free trial. |
product_health
A discriminated union on available. When available is false, that is the only field present — do not expect a score.
| Field | Type | Notes |
|---|---|---|
available | boolean | Whether a health assessment exists. |
score | integer | 0–100. Only when available is true. |
status | enum | excellent, healthy, fair, needs_attention, limited. |
label | string | Localised status label, ≤ 100 characters. |
facts | string[] | Comparison and detail only. ≤ 4 supporting statements, each ≤ 240 characters. |
url | string | Comparison and detail only. Public health report page, or an empty string. |
service (detail, service listings only)
| Field | Type | Notes |
|---|---|---|
plans | string[] | ≤ 20 plan names, each ≤ 200 characters. |
delivery_time | string | ≤ 200 characters. |
included_revisions | string | ≤ 200 characters. |
included | string[] | ≤ 20 entries, each ≤ 240 characters. |
excluded | string[] | ≤ 20 entries, each ≤ 240 characters. |
buyer_requirements | string[] | ≤ 20 entries, each ≤ 240 characters. |
pagination (search only)
| Field | Type | Notes |
|---|---|---|
page | integer | 1–100, echoing the requested page. |
per_page | integer | 1–20, echoing the requested page size. |
total | integer | Matches found by the catalog query. An upper bound on retrievable products. |
total_pages | integer | Pages available at the requested page size. |
8. Value enumerations
| Where | Field | Accepted / returned values |
|---|---|---|
| Input filter | product_types[] | plugin, theme, script, service |
| Output | product_type | plugin, theme, script, service, software |
| Input filter | payment_model | lifetime, subscription, subscription_month, subscription_year |
| Output | payment_models[].key | single (“Lifetime”), subscription, subscription_month, subscription_year |
| Input filter | license | split, gpl, mit |
| Input filter | maintenance | excellent, healthy, fair |
| Input filter | sort | relevance, newest, best_selling, top_rated, price_low, price_high, health |
| Input filter | date_added | all, week, month, 6months, year |
| Output | product_health.status | excellent, healthy, fair, needs_attention, limited |
| Output | documentation.type | url, zip, or "" when unavailable |
| Output | previews[].type | live, video |
| Output | support.key | wpbay, seller_website, email, none |
9. Errors and retries
Tool-level errors
When a tool cannot complete, it returns a normal MCP result with isError: true and a single plain-text message. Messages are deliberately short and generic: they never contain upstream response bodies, stack traces, internal hostnames, credentials or request internals.
| Category | Message | What to do |
|---|---|---|
invalid_arguments | “WPBay could not process those catalog filters. Check the supplied values and try again.” | Fix the parameters. Do not retry unchanged. |
not_found | “That public WPBay product was not found.” | The ID does not exist or is not publicly listed. Re-resolve it with a search. |
rate_limited | “WPBay is rate limited. Try again in N seconds.” | Wait the stated number of seconds. The delay is included when upstream supplies it. |
upstream_timeout | “The WPBay catalog request timed out. Try again shortly.” | Retry once after a short pause. |
upstream_unavailable | “The WPBay catalog is temporarily unavailable. Try again shortly.” | Retry with backoff. |
upstream_invalid_response | “WPBay returned an unexpected catalog response. Try again shortly.” | Retry once; report it if it persists. |
cancelled | “The WPBay catalog request was cancelled.” | The client aborted the request. No action needed. |
The gateway already retries once, internally, on a transient upstream failure (502, 503, 504, or a network fault) with a short randomised delay. By the time you see upstream_unavailable, one retry has already been spent.
HTTP-level errors
These are returned by the endpoint itself, before any tool runs.
| Status | Body | Cause |
|---|---|---|
400 | {"error":"invalid_request"} | Malformed JSON body. |
403 | — | Host or Origin header not on the allow-list. |
404 | {"error":"not_found"} | Any path other than /mcp or /healthz. |
405 | {"error":"method_not_allowed"} | An HTTP method other than GET or POST. Includes an Allow header. |
413 | {"error":"request_too_large"} | Request body above 64 KB. |
415 | {"error":"unsupported_media_type"} | A POST without Content-Type: application/json. |
429 | JSON-RPC error -32000 | Rate limit exceeded. Includes Retry-After in seconds. |
Request correlation
Every response carries an X-Request-ID header. Send your own X-Request-ID (8–64 characters, alphanumeric plus . _ : -) and it will be echoed back and used in server-side logs; otherwise one is generated. Quote it when reporting a problem. W3C Trace Context headers (traceparent, tracestate, baggage) are propagated when supplied.
10. Rate limits
Limits are applied in a fixed 60-second window and are enforced on two axes at once: a ceiling for each individual JSON-RPC method, and a ceiling across all methods combined. The combined ceiling is what stops a client from exhausting one method and simply moving on to the next.
| Scope | Limit | Applies to |
|---|---|---|
| Per client, per method | 120 requests / 60s | Each of initialize, tools/list, ping, and each individual tools/call tool name. |
| Per client, all methods | 240 requests / 60s | Everything you send, combined. This is the number to design against. |
| Service-wide, per method | 5,000 requests / 60s | All clients together, per method. |
| Service-wide, all methods | 10,000 requests / 60s | All clients, all methods. |
Clients are distinguished by a salted HMAC of the network address the request arrives from. The value is derived per request and never stored, logged in reversible form, or shared.
Exceeding any ceiling produces 429 with a Retry-After header giving whole seconds until the window resets. Honour it. Retrying before the window rolls over only consumes the next window’s budget.
Practical guidance. A conversational agent will rarely approach these numbers — a typical exchange is one tools/list and a handful of tool calls. If you are building a batch or crawling workload, prefer a single search with per_page: 20 over twenty get_wordpress_product calls, cache results for at least the cache lifetime published below, and serialise rather than parallelise.
11. Caching and freshness
Catalog responses are cached server-side and are also marked cacheable for intermediaries. Pricing, ratings and availability can therefore trail the website by up to the values below, which is normally invisible but matters if you are checking whether a sale has just started.
| Surface | Lifetime | Notes |
|---|---|---|
| Search results | 15 seconds public cache; 60 seconds server-side | Plus stale-while-revalidate=30. |
| Product detail | 60 seconds public cache; 120 seconds server-side | Invalidated early when the product is edited. |
| Comparison | 30 seconds public cache; 120 seconds server-side | Built from the same per-product cache entries. |
tools/list and discovery | 5 minutes | Advertised to clients as a public cache hint. |
Errors and /healthz | Never cached | Cache-Control: no-store. |
Product edits purge the affected cache entries immediately, so a corrected price generally appears well inside the published lifetime rather than at the end of it.
12. Direct REST API
The same catalog is available over plain HTTP for clients that do not speak MCP. It is public and read-only, requires no authentication, and carries the same data contract — the MCP gateway is a thin, schema-validating wrapper around exactly these three endpoints.
Base URL: https://wpbay.com/wp-json/wpbay-agent/v1/
| Method & path | Parameters | Returns |
|---|---|---|
GET /search | The 21 search parameters documented in section 4. | { api_version, products[], pagination } |
GET /products/{id} | {id} is a positive integer in the path. No query parameters are accepted. | { api_version, product } |
GET /compare | ids — 2 to 5 unique positive integers. | { api_version, products[] } |
Passing lists
Array parameters accept either repeated bracket syntax or a comma-separated string. These are equivalent:
GET /wp-json/wpbay-agent/v1/search?product_types[]=plugin&product_types[]=theme
GET /wp-json/wpbay-agent/v1/search?product_types=plugin,theme
GET /wp-json/wpbay-agent/v1/compare?ids[]=1842&ids[]=1907
GET /wp-json/wpbay-agent/v1/compare?ids=1842,1907
Over REST, product_types also accepts the plural spellings plugins, themes, scripts and services. The MCP tool accepts the singular forms only.
Example
curl -sS 'https://wpbay.com/wp-json/wpbay-agent/v1/search?query=seo&per_page=2&sort=top_rated'
Strict parameter handling
Unknown query parameters are rejected, not ignored. A misspelled filter returns 400 rather than quietly returning unfiltered results.
Error envelope
Errors use one stable shape across all three endpoints:
{
"error": {
"code": "rate_limited",
"category": "rate_limited",
"message": "Too many catalog requests. Try again later."
},
"request_id": "4f1c8a2e-...."
}
| Status | code | category | Cause |
|---|---|---|---|
400 | invalid_request | invalid_arguments | Unknown, malformed or out-of-range parameter. |
404 | product_not_found | not_found | Product missing or not publicly listed. |
429 | rate_limited | rate_limited | Rate limit exceeded. Includes Retry-After. |
503 | temporary_failure | temporary_failure | The catalog is temporarily unavailable. |
Response headers
| Header | Value |
|---|---|
X-Request-ID | Correlation ID for the request. |
X-Content-Type-Options | nosniff |
Vary | Accept |
Cache-Control | public, max-age=N, s-maxage=N, stale-while-revalidate=30 on success; no-store on errors. |
Retry-After | Seconds until the rate-limit window resets — 429 only. |
Rate limits for direct REST access
Anonymous REST callers get a smaller budget than MCP clients, in the same fixed 60-second window:
| Endpoint | Limit per IP / 60s |
|---|---|
/search | 30 |
/products/{id} | 60 |
/compare | 30 |
| All endpoints combined | 90 |
13. Versioning and compatibility
Catalog contract
Every response carries api_version, currently "1". The contract is:
- New fields may be added at any time without a version bump. Parse defensively and ignore keys you do not recognise — the gateway itself tolerates unknown upstream fields rather than failing.
- Existing fields will not change type or meaning within a version.
- A breaking change bumps
api_version. If you pin behaviour to anything, pin it to that value.
Input strictness
Tool inputs are the opposite: unknown keys are rejected. That asymmetry is deliberate. An unexpected key in a response is WPBay having shipped a new field; an unexpected key in a request is a caller mistake, and failing loudly is more useful than silently dropping the filter you thought you applied.
Protocol revisions
The endpoint negotiates the protocol revision during initialize and serves both the current MCP revision and the previous one, so older clients keep working without configuration. Use whichever your SDK defaults to.
14. Guidance for agent authors
- Search first, then fetch.
get_wordpress_productandcompare_wordpress_productstake IDs, never names. Always resolve throughsearch_wordpress_software. - Prefer one comparison to three detail calls.
compare_wordpress_productsreturns normalized fields for up to five products in a single request and is far cheaper against your budget. - Raise
per_pagebefore raisingpage. One request for 20 results costs a twentieth of twenty requests for one. - Read
structuredContent, not the text summary. Thecontentstring exists only as a fallback for clients without structured output; it is a one-line gloss, not the data. - Treat absent as absent.
nullprices, empty version strings andavailable: falsehealth are normal for legitimate listings. Do not present them as zero, free, or unhealthy. - Do not dereference returned URLs automatically. Preview, video and documentation links are seller-supplied and are not validated for content by WPBay.
- Present, do not embellish. Compatibility lists, ratings and Product Health are facts from the catalog. Capabilities not stated in the data should not be inferred from a product’s name or category.
- Respect
Retry-After. It is an exact number of seconds, not a suggestion.
15. Running your own gateway
The hosted endpoint at mcp.wpbay.com is all most integrations need. The section below is for teams who want to run the gateway themselves — inside a private network, or against a staging catalog.
Operating the gateway: security model, environment variables and deployment
Security model
- The catalog is public. The gateway’s internal token changes rate-limit tiers; it does not unlock private data, and no configuration makes unpublished products visible.
- Client-supplied
x-wpbay-mcp-tokenandx-wpbay-mcp-clientheaders are stripped on entry, so a caller cannot impersonate the gateway or claim a different client identity. - Client identity is a salted HMAC of the remote address, never the address itself.
HostandOriginare validated against an allow-list; anything else is refused with403.- Upstream calls are bounded on every axis: 8-second timeout, 1 MB maximum response, redirects refused outright, one retry on transient failure.
- Responses are validated against the published schemas before reaching a client, so a malformed upstream payload becomes a clean tool error rather than malformed structured output.
- Error text is fixed and generic — upstream bodies, tokens and internal hostnames are never echoed.
Environment variables
| Variable | Default | Bounds and notes |
|---|---|---|
WPBAY_API_BASE_URL | required | Absolute URL. No credentials, query string or fragment. Must be HTTPS when NODE_ENV=production. |
WPBAY_MCP_INTERNAL_TOKEN | empty | ≥ 32 characters in production, ≤ 512. Selects the trusted rate-limit tier upstream and salts the client fingerprint. |
NODE_ENV | development | development, test or production. |
WPBAY_MCP_HOST | 127.0.0.1 | Loopback only — 127.0.0.1, localhost or ::1. Put a reverse proxy in front; the process refuses to bind a public interface. |
PORT | 3000 | 1 – 65535. |
WPBAY_MCP_ALLOWED_HOSTS | 127.0.0.1, localhost, [::1], mcp.wpbay.com | Comma-separated, 1 – 20 hostnames. |
WPBAY_MCP_ALLOWED_ORIGINS | same as above | Comma-separated, 1 – 20 hostnames. |
WPBAY_MCP_BODY_LIMIT | 64kb | Between 1kb and 1mb. |
WPBAY_API_TIMEOUT_MS | 8000 | 500 – 30,000 ms. |
WPBAY_API_MAX_RESPONSE_BYTES | 1048576 | 16 KB – 4 MB. |
WPBAY_API_RETRY_BASE_DELAY_MS | 100 | 0 – 1,000 ms; jittered. |
WPBAY_MCP_RATE_LIMIT_WINDOW_MS | 60000 | 1,000 ms – 1 hour. |
WPBAY_MCP_RATE_LIMIT_CLIENT_MAX | 120 | Per client, per method. 1 – 100,000. |
WPBAY_MCP_RATE_LIMIT_CLIENT_TOTAL_MAX | 240 | Per client, all methods. 1 – 100,000. |
WPBAY_MCP_RATE_LIMIT_GLOBAL_MAX | 5000 | All clients, per method. 1 – 1,000,000. |
WPBAY_MCP_RATE_LIMIT_GLOBAL_TOTAL_MAX | 10000 | All clients, all methods. 1 – 1,000,000. |
Deployment notes
- Terminate TLS at a reverse proxy and forward to the loopback port. The process trusts proxy headers from loopback only.
- Run the service from outside the web root. Application source,
node_modulesand lockfiles should never be reachable over HTTP. - Keep secrets out of
wp-content. Supply them through the process environment or a systemd unit, not a file under a served directory. - Point monitoring at
/healthzand alert on non-200. - The service is stateless, so it scales horizontally — but rate-limit counters are per process, so N instances means N times the published budget unless limits are divided accordingly.
WPBay MCP Server · Catalog API version 1 · Endpoint https://mcp.wpbay.com/mcp
Questions, an unexpected response, or a schema that does not match this page? Include the X-Request-ID from the response when you get in touch.
