Model Context Protocol

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.

  • Endpointhttps://mcp.wpbay.com/mcp
  • TransportStreamable HTTP
  • AuthenticationNone — public catalog
  • Tools3, all read-only
  • Payload formatSchema-validated structuredContent
  • Catalog API version1

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:

AnnotationValueMeaning
readOnlyHinttrueThe tool never modifies anything.
destructiveHintfalseNo data can be lost by calling it.
idempotentHinttrueRepeating a call with the same arguments is safe.
openWorldHintfalseThe 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.

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

ParameterTypeConstraintsDescription
querystring≤ 100 charactersNatural-language capability or product phrase.
product_typesstring[]≤ 4 items; plugin, theme, script, serviceRestrict results to these product types.
categoriesstring[]≤ 10 items, each 1–80 charactersWPBay category names or slugs.
price_minnumber0 – 1,000,000Lowest acceptable price.
price_maxnumber0 – 1,000,000; must be ≥ price_minHighest acceptable price.
minimum_ratingnumber1 – 5Minimum average customer rating.
minimum_salesinteger0 – 1,000,000,000Minimum recorded sales.
wordpress_versionsstring[]≤ 10 items, each ≤ 80 charactersRequired WordPress version compatibility, e.g. 6.8.
php_versionsstring[]≤ 10 items, each ≤ 80 charactersRequired PHP version compatibility, e.g. 8.3.
compatible_withstring[]≤ 10 items, each ≤ 80 charactersRequired integrations, e.g. woocommerce, elementor.
files_includedstring[]≤ 10 items, each ≤ 80 charactersRequired included file types, e.g. php, css.
payment_modelenumlifetime, subscription, subscription_month, subscription_yearRestrict to a purchase model.
licenseenumsplit, gpl, mitRestrict to a software licence.
maintenanceenumexcellent, healthy, fairMinimum Product Health tier.
recently_updatedbooleanOnly products updated recently.
uses_wpbay_sdkbooleanOnly products that ship the WPBay SDK.
tagsstring[]≤ 15 items, each ≤ 80 charactersCatalog tags.
date_addedenumall, week, month, 6months, yearHow recently the product was listed.
sortenumrelevance, newest, best_selling, top_rated, price_low, price_high, healthResult ordering.
pageinteger1 – 100  default 1Page number.
per_pageinteger1 – 20  default 8Results 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

ParameterTypeConstraintsDescription
product_id requiredintegerPositive integerThe 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

ParameterTypeConstraintsDescription
product_ids requiredinteger[]2–5 items, each a positive integer, all distinctThe 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".

ToolEnvelopeProjection
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

FieldTypeNotes
idintegerCanonical product ID. Stable. Use it for detail and comparison calls.
slugstring1–200 characters.
titlestring1–200 characters.
summarystring≤ 320 characters of plain text; HTML and shortcodes removed.
product_typeenumplugin, theme, script, service, or software when the listing does not map to a specific type.
categoriesstring[]≤ 20 names, each ≤ 160 characters.
tagsstring[]≤ 20 names, each ≤ 160 characters.
sellerobject{ name (≤300), url }. url is the public store page or an empty string. No email address.
urlstringCanonical public product page.
thumbnail_urlstring | nullProduct image, or null.
pricingobjectPricing summary — see below.
licenseobject | null{ key, label } software licence declaration, or null when undeclared.
ratingobject{ average: 0–5, count: integer }.
salesintegerRecorded sales, never negative.
compatibilityobject{ wordpress[] (≤30), php[] (≤20), integrations[] (≤30) }.
updated_atstring | nullISO 8601 with UTC offset, or null when never updated.
product_healthobjectCompact health object in search; detailed in comparison and detail.
uses_wpbay_sdkbooleanWhether the product ships the WPBay SDK (in-dashboard updates and licensing).

Comparison projection — additional fields

FieldTypeNotes
versionstring≤ 100 characters. Empty when the seller publishes no version.
published_atstring | nullISO 8601 with offset — first publication.
files_includedstring[]≤ 20 entries, each ≤ 120 characters, e.g. PHP, CSS, JavaScript.
supportobject{ key, label } — see the support enumeration. Never an email address.
documentationobject{ available, type, label, url }. url is populated only for online documentation; ZIP documentation is declared but never linked.
product_healthobjectUpgraded to the detailed form, adding facts[] (≤ 4 short statements) and url.

Detail projection — additional fields

FieldTypeNotes
descriptionstring≤ 6,000 characters of plain text.
pricing.offersobject[]≤ 100 concrete purchase options — see below.
pricing.offers_truncatedbooleantrue when more than 100 offers existed and the list was cut.
previewsobject[]≤ 2 entries of { type, label, url } — seller-supplied, never fetched by WPBay.
screenshotsobject[]≤ 6 entries of { url, thumbnail_url, alt }.
changelogobject{ available, url }.
serviceobjectPresent only when product_type is service. See below.

Nested objects

pricing (summary — all projections)

FieldTypeNotes
currencystring≤ 10 characters, e.g. USD.
minimum_pricenumber | nullLowest available price. null when no price is listed.
maximum_pricenumber | nullHighest available price.
payment_modelsobject[]≤ 4 entries of { key, label }.
license_sitesstring[]≤ 100 site-count tiers, each ≤ 100 characters.

pricing.offers[] (detail only)

FieldTypeNotes
planobject{ key, label } — the purchasable plan.
licenseobject | null{ key, label } site-licence tier, or null.
payment_modelobject{ key, label }.
current_pricenumber | nullPrice a buyer pays today.
regular_pricenumber | nullUndiscounted price.
sale_pricenumber | nullSet when a sale is active.
billingobject | nullSubscription terms, or null for one-off purchases.

pricing.offers[].billing

FieldTypeNotes
recurring_pricenumber | nullCharged each billing cycle.
signup_feenumber | nullOne-off fee at signup.
periodstring≤ 30 characters, e.g. month, year.
intervalinteger≥ 1. Charge every N periods.
lengthinteger≥ 0. Number of cycles; 0 means until cancelled.
trial_lengthinteger≥ 0. 0 means no free trial.
trial_periodstring≤ 30 characters, e.g. month. Combine with trial_length1 + 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.

FieldTypeNotes
availablebooleanWhether a health assessment exists.
scoreinteger0–100. Only when available is true.
statusenumexcellent, healthy, fair, needs_attention, limited.
labelstringLocalised status label, ≤ 100 characters.
factsstring[]Comparison and detail only. ≤ 4 supporting statements, each ≤ 240 characters.
urlstringComparison and detail only. Public health report page, or an empty string.

service (detail, service listings only)

FieldTypeNotes
plansstring[]≤ 20 plan names, each ≤ 200 characters.
delivery_timestring≤ 200 characters.
included_revisionsstring≤ 200 characters.
includedstring[]≤ 20 entries, each ≤ 240 characters.
excludedstring[]≤ 20 entries, each ≤ 240 characters.
buyer_requirementsstring[]≤ 20 entries, each ≤ 240 characters.

pagination (search only)

FieldTypeNotes
pageinteger1–100, echoing the requested page.
per_pageinteger1–20, echoing the requested page size.
totalintegerMatches found by the catalog query. An upper bound on retrievable products.
total_pagesintegerPages available at the requested page size.

8. Value enumerations

WhereFieldAccepted / returned values
Input filterproduct_types[]plugin, theme, script, service
Outputproduct_typeplugin, theme, script, service, software
Input filterpayment_modellifetime, subscription, subscription_month, subscription_year
Outputpayment_models[].keysingle (“Lifetime”), subscription, subscription_month, subscription_year
Input filterlicensesplit, gpl, mit
Input filtermaintenanceexcellent, healthy, fair
Input filtersortrelevance, newest, best_selling, top_rated, price_low, price_high, health
Input filterdate_addedall, week, month, 6months, year
Outputproduct_health.statusexcellent, healthy, fair, needs_attention, limited
Outputdocumentation.typeurl, zip, or "" when unavailable
Outputpreviews[].typelive, video
Outputsupport.keywpbay, 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.

CategoryMessageWhat 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.

StatusBodyCause
400{"error":"invalid_request"}Malformed JSON body.
403Host 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.
429JSON-RPC error -32000Rate 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.

ScopeLimitApplies to
Per client, per method120 requests / 60sEach of initialize, tools/list, ping, and each individual tools/call tool name.
Per client, all methods240 requests / 60sEverything you send, combined. This is the number to design against.
Service-wide, per method5,000 requests / 60sAll clients together, per method.
Service-wide, all methods10,000 requests / 60sAll 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.

SurfaceLifetimeNotes
Search results15 seconds public cache; 60 seconds server-sidePlus stale-while-revalidate=30.
Product detail60 seconds public cache; 120 seconds server-sideInvalidated early when the product is edited.
Comparison30 seconds public cache; 120 seconds server-sideBuilt from the same per-product cache entries.
tools/list and discovery5 minutesAdvertised to clients as a public cache hint.
Errors and /healthzNever cachedCache-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 & pathParametersReturns
GET /searchThe 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 /compareids — 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-...."
}
StatuscodecategoryCause
400invalid_requestinvalid_argumentsUnknown, malformed or out-of-range parameter.
404product_not_foundnot_foundProduct missing or not publicly listed.
429rate_limitedrate_limitedRate limit exceeded. Includes Retry-After.
503temporary_failuretemporary_failureThe catalog is temporarily unavailable.

Response headers

HeaderValue
X-Request-IDCorrelation ID for the request.
X-Content-Type-Optionsnosniff
VaryAccept
Cache-Controlpublic, max-age=N, s-maxage=N, stale-while-revalidate=30 on success; no-store on errors.
Retry-AfterSeconds 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:

EndpointLimit per IP / 60s
/search30
/products/{id}60
/compare30
All endpoints combined90

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_product and compare_wordpress_products take IDs, never names. Always resolve through search_wordpress_software.
  • Prefer one comparison to three detail calls. compare_wordpress_products returns normalized fields for up to five products in a single request and is far cheaper against your budget.
  • Raise per_page before raising page. One request for 20 results costs a twentieth of twenty requests for one.
  • Read structuredContent, not the text summary. The content string exists only as a fallback for clients without structured output; it is a one-line gloss, not the data.
  • Treat absent as absent. null prices, empty version strings and available: false health 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-token and x-wpbay-mcp-client headers 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.
  • Host and Origin are validated against an allow-list; anything else is refused with 403.
  • 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

VariableDefaultBounds and notes
WPBAY_API_BASE_URLrequiredAbsolute URL. No credentials, query string or fragment. Must be HTTPS when NODE_ENV=production.
WPBAY_MCP_INTERNAL_TOKENempty≥ 32 characters in production, ≤ 512. Selects the trusted rate-limit tier upstream and salts the client fingerprint.
NODE_ENVdevelopmentdevelopment, test or production.
WPBAY_MCP_HOST127.0.0.1Loopback only — 127.0.0.1, localhost or ::1. Put a reverse proxy in front; the process refuses to bind a public interface.
PORT30001 – 65535.
WPBAY_MCP_ALLOWED_HOSTS127.0.0.1, localhost, [::1], mcp.wpbay.comComma-separated, 1 – 20 hostnames.
WPBAY_MCP_ALLOWED_ORIGINSsame as aboveComma-separated, 1 – 20 hostnames.
WPBAY_MCP_BODY_LIMIT64kbBetween 1kb and 1mb.
WPBAY_API_TIMEOUT_MS8000500 – 30,000 ms.
WPBAY_API_MAX_RESPONSE_BYTES104857616 KB – 4 MB.
WPBAY_API_RETRY_BASE_DELAY_MS1000 – 1,000 ms; jittered.
WPBAY_MCP_RATE_LIMIT_WINDOW_MS600001,000 ms – 1 hour.
WPBAY_MCP_RATE_LIMIT_CLIENT_MAX120Per client, per method. 1 – 100,000.
WPBAY_MCP_RATE_LIMIT_CLIENT_TOTAL_MAX240Per client, all methods. 1 – 100,000.
WPBAY_MCP_RATE_LIMIT_GLOBAL_MAX5000All clients, per method. 1 – 1,000,000.
WPBAY_MCP_RATE_LIMIT_GLOBAL_TOTAL_MAX10000All 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_modules and 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 /healthz and 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.