Skip to content

MCP server

Every Kreezalid marketplace can run its own Model Context Protocol server. Connect an AI agent to it and the agent can read how the marketplace is built and change it: categories, listing attributes, order types, user groups, collections, pages, blog, menus and public settings.

It exists so that a request like “set up a marketplace for unusual holiday rentals, with the right categories, attributes and order types” can be carried out rather than described. The agent reads the marketplace, writes a blueprint of what it should look like, shows you the plan, and applies it once you agree.

Task How
Read the structure of the marketplace describe_marketplace
Read one page, article, collection or collection model in full read_item
Get starter pages and blog articles for a new marketplace starter_blueprint
Create, update and delete structure and content apply_blueprint, in two steps: preview, then apply

What a blueprint can write, section by section:

Section What it changes
settings Marketplace name, language and time zone; homepage, listing, blog, contact and SEO settings; footer columns, social buttons, layouts and the options of the active theme package
user_groups Member groups (who may post listings, onboarding, moderation)
listing_attributes The fields a listing carries and their options
categories The category tree, up to three levels
order_types Selling, booking, digital or display-only, with cart, stock, shipping, calendar and commission options
collection_models Models that generate one collection per value of a criterion
collections Storefront collections and their conditions
pages CMS pages
blog Blog categories and articles
menus The links of existing menus

The agent never sees listings, orders, members, messages or reviews, and cannot change them. See What an agent cannot do.

Each marketplace has its own server, at /mcp on the address its storefront answers on:

https://<marketplace address>/mcp

For a marketplace at acme.mykreezalid.com, give your client https://acme.mykreezalid.com/mcp; for one served on its own domain, www.acme.com, give it https://www.acme.com/mcp. Copy the address on the AI connectors screen gives the right one. That is the only thing to configure: the client discovers the authorization server, the scopes and the registration endpoint from there.

  • Transport: Streamable HTTP. Requests are JSON-RPC 2.0 messages sent with POST; every response is a single JSON object (no event stream), and a notification gets 202 with an empty body. GET /mcp answers 405: the server sends no notifications.
  • Authentication: OAuth 2.1 with PKCE, a marketplace administrator approving each connection. There is no API key to paste.
  • Protocol versions: 2026-07-28, 2025-11-25, 2025-06-18 and 2025-03-26. The server answers initialize with the version the client asks for when it supports it, otherwise with 2026-07-28.

The server is off by default on every marketplace. Until an administrator switches it on, /mcp and the discovery documents answer 404, exactly like an address that does not exist.

  1. In the back office, open Configuration → AI connectors (/admin/ai-connectors).
  2. Click Switch on. The change takes effect immediately.
  3. Click Copy the address and give that address to your MCP client.

The same screen lists every connector: connected, disconnected, registered but never authorized, or blocked. Switch off makes the server answer 404 again for everything, tokens already issued included. It revokes nothing: switched back on, every token that has not expired works again.

When a client connects for the first time, it opens a browser window on the marketplace. A marketplace administrator signs in (if they are not already) and sees a consent screen that names the client and the address the authorization goes back to, with one box per permission. They tick what they grant and click Authorize. The client then holds a token for that marketplace only.

In Claude (web or desktop), open Settings → Connectors, choose Add custom connector, and paste the endpoint:

https://acme.mykreezalid.com/mcp

Claude opens the marketplace’s consent screen when you first connect.

A client that implements the MCP authorization specification needs nothing else. The discovery chain is standard:

Step Request What it returns
1 POST /mcp without a token 401 with WWW-Authenticate: Bearer resource_metadata="https://acme.mykreezalid.com/.well-known/oauth-protected-resource/mcp", scope="marketplace:read catalog:write content:write settings:write"
2 GET /.well-known/oauth-protected-resource/mcp Protected resource metadata (RFC 9728). The marketplace is its own authorization server.
3 GET /.well-known/oauth-authorization-server Authorization server metadata (RFC 8414): the endpoints below
4 POST /mcp/oauth/register Dynamic client registration (RFC 7591), or skip it and use a Client ID Metadata Document URL as client_id
5 GET /mcp/oauth/authorize The consent screen, in a browser
6 POST /mcp/oauth/token Access and refresh tokens

The discovery documents are also served at /.well-known/oauth-protected-resource, /.well-known/oauth-authorization-server/mcp and /.well-known/openid-configuration, for clients that look there.

To call the server from your own code (the OpenAI Responses API, an agent framework, a test script), run the authorization code flow once by hand and keep the refresh token.

  1. Register a client. The redirect URI must be https://, or http:// on 127.0.0.1, ::1 or localhost. No secret is issued: PKCE protects the flow.

    Terminal window
    curl -s https://acme.mykreezalid.com/mcp/oauth/register \
    -H 'Content-Type: application/json' \
    -d '{"client_name": "Acme catalogue script", "redirect_uris": ["http://127.0.0.1:8976/callback"]}'
    {
    "client_id": "krmcp-client-3f9c2a7d1e6b4c0a9d8e7f6a5b4c3d2e",
    "client_id_issued_at": 1792058400,
    "client_name": "Acme catalogue script",
    "redirect_uris": ["http://127.0.0.1:8976/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "token_endpoint_auth_method": "none"
    }
  2. Create a PKCE verifier and its S256 challenge. Only S256 is accepted.

    Terminal window
    VERIFIER=$(openssl rand -base64 64 | tr -dc 'A-Za-z0-9' | cut -c1-64)
    CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
  3. Open the authorization URL in a browser where a marketplace administrator is signed in (or will sign in). It is shown on several lines for reading: join them into one URL.

    https://acme.mykreezalid.com/mcp/oauth/authorize?response_type=code
    &client_id=krmcp-client-3f9c2a7d1e6b4c0a9d8e7f6a5b4c3d2e
    &redirect_uri=http%3A%2F%2F127.0.0.1%3A8976%2Fcallback
    &code_challenge=<CHALLENGE>&code_challenge_method=S256
    &scope=marketplace%3Aread%20catalog%3Awrite
    &resource=https%3A%2F%2Facme.mykreezalid.com%2Fmcp
    &state=4242

    After Authorize, the browser is sent to http://127.0.0.1:8976/callback?code=…&state=4242&iss=https%3A%2F%2Facme.mykreezalid.com. Nothing needs to listen on that port: copy code from the address bar. The code is valid for two minutes and can be used once.

  4. Exchange the code for tokens:

    Terminal window
    curl -s https://acme.mykreezalid.com/mcp/oauth/token \
    -d grant_type=authorization_code \
    -d code="$CODE" \
    -d client_id=krmcp-client-3f9c2a7d1e6b4c0a9d8e7f6a5b4c3d2e \
    -d redirect_uri=http://127.0.0.1:8976/callback \
    -d code_verifier="$VERIFIER" \
    -d resource=https://acme.mykreezalid.com/mcp
    {
    "access_token": "…",
    "token_type": "Bearer",
    "expires_in": 3600,
    "refresh_token": "…",
    "scope": "marketplace:read catalog:write"
    }

    scope is what the administrator granted, which can differ from what you asked for.

  5. Send the access token as Authorization: Bearer <access_token> on every POST /mcp. See Tools for the request format.

Renew the access token before it expires with the refresh token:

Terminal window
curl -s https://acme.mykreezalid.com/mcp/oauth/token \
-d grant_type=refresh_token \
-d refresh_token="$REFRESH_TOKEN" \
-d client_id=krmcp-client-3f9c2a7d1e6b4c0a9d8e7f6a5b4c3d2e

Each refresh returns a new refresh token and invalidates the old one. Store the new one. A refresh token presented twice is treated as stolen: every token of that client on that marketplace is revoked, and an administrator has to authorize it again. A refresh may ask for fewer scopes (-d scope=marketplace:read), never more.

With the OpenAI Responses API, pass the access token to the remote MCP tool:

agent.js
import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "gpt-5",
tools: [
{
type: "mcp",
server_label: "kreezalid",
server_url: "https://acme.mykreezalid.com/mcp",
authorization: process.env.KREEZALID_MCP_TOKEN,
require_approval: "always",
},
],
input: "List the categories of my marketplace and their listing counts.",
});
console.log(response.output_text);
Lifetime Value
Authorization code 2 minutes, single use
Access token 1 hour
Refresh token 30 days, single use (rotated on every refresh)

The administrator grants permissions box by box on the consent screen. A token carries exactly what was ticked, whatever the client asked for.

Scope Shown as Grants
marketplace:read Read your marketplace describe_marketplace, read_item, starter_blueprint. Always granted.
catalog:write Change your catalogue apply_blueprint on listing_attributes, categories, order_types, collection_models, collections
content:write Change your pages, blog and menus apply_blueprint on pages, blog, menus
settings:write Change your settings and user groups apply_blueprint on settings, user_groups
marketplace:destroy Delete Deletions (prune, or delete: true on an entry), on top of the write scope of the section
  • Scopes do not imply one another. A write scope does not include marketplace:read (which every token carries anyway), and marketplace:destroy grants no write.
  • Deletion is never ticked for the administrator. Even when a client asks for it, the box starts unticked. The administrator ticks it deliberately, or the agent cannot delete.
  • Write scopes start ticked when the client asks for them. A client that names no scope is asked for read and every write on its behalf, so they all start ticked.
  • A refusal is remembered for fifteen minutes, per client and marketplace: the next consent screen for that client also ticks the scope it lacked and the ones its token held, each saying why. ChatGPT asks for marketplace:read alone every time, so this is what saves the administrator from ticking everything again. Its client id is the same for every ChatGPT user: these boxes are defaults the administrator can untick, never a grant.

When a call needs a scope the token lacks, the server refuses it with an insufficient_scope challenge that names the scopes the token holds plus the missing ones. MCP clients react by sending the administrator back to the consent screen. Let the client reconnect, tick the box, and run the call again.

The server is designed for an agent that reads text it did not write. Every write is bounded by what the administrator granted and by refusals the agent cannot talk its way past.

Preview, then apply. apply_blueprint defaults to a dry run. The preview returns the list of operations and a plan_hash. Applying requires that hash, and the plan is recomputed against the marketplace as it stands: if anything changed in between (someone edited the back office, another agent applied a plan), the apply is refused and the agent has to preview again. What is applied is exactly what was shown.

Ask before you apply. The plan_hash proves the plan was shown to the agent, not that a person read it. Configure your client to ask for confirmation before apply_blueprint runs (for example require_approval: "always" with the OpenAI API), and read the plan, in particular every delete and every note.

Absence is never deletion. An entry missing from a blueprint is left alone. Nothing is deleted unless the blueprint sets prune: true or an entry says "delete": true, and both need marketplace:destroy. The server also declines deletions that would lose data: a category with listings or subcategories, a group with members, an order type in use. Those appear in the plan as notes, not as deletions.

One apply at a time. Applies on one marketplace run one after the other, and each is atomic: either every operation of a plan is written, or none is. An apply that finds another one running waits up to 5 seconds, then is refused with a message asking to try again.

No secrets leave. No tool returns a credential of any kind. Payment providers are reported as status only (is_configured: true), and a setting whose value looks like a credential comes back as "[redacted: …]".

No code goes in. Markup that runs code is refused anywhere in a blueprint: <script>, <iframe>, <form>, <style>, <svg> and any element outside a list of formatting elements, on… event attributes, javascript:, vbscript: and data: URLs. Custom CSS, custom JavaScript, the theme and its colour file are not writable at all. The refusal names the back-office screen where an administrator can add them by hand.

Marketplace text is data. read_item labels what it returns as text written on the marketplace, to be read as data and never as instructions. Tell your agent the same in its own instructions.

  • Read or change listings, orders, members, messages or reviews.
  • Read or write credentials, payment configuration, or plugin settings.
  • Change the theme, or write custom CSS or JavaScript.
  • Upload images: collection and model images are uploaded in the back office. Blog articles can take a cover from a built-in pool or an https:// URL.
  • Create or delete menus (it can rewrite the links of an existing one).
  • Change the slug of a category or a page: a different slug is a different item.
  • Backdate a blog article.
  • Delete anything without marketplace:destroy.

In Configuration → AI connectors, two actions take access away:

  • Disconnect revokes every token issued to the connector, immediately, and nothing more. The client can come back: it asks for a new authorization, and an administrator accepts it on the consent screen.
  • Block does the same and also refuses the client: its authorization requests get invalid_client, the same answer as for a client that does not exist. Unblock lifts it; the client can ask again and gets nothing until an administrator accepts. A client identified by a metadata document URL has its document read again on that next request.

A connector is listed as disconnected when it was authorized once and holds nothing alive any more: disconnected by an administrator, or unused for a month, when its refresh token expires.

ChatGPT uses the same client_id for all its users. Disconnecting or blocking ChatGPT on a marketplace cuts off every administrator of that marketplace who connected it.

To cut off every agent at once, click Switch off.

Refusals happen before a tool runs, at the HTTP level, in this order:

Status error Meaning What to do
404 not_found The server is switched off for this marketplace Switch it on
403 invalid_request The request carries an Origin header that is not the marketplace’s own Call the server from a server, not from a web page
405 method_not_allowed The request is not a POST Send JSON-RPC with POST
400 invalid_request Unsupported MCP-Protocol-Version header Use one of the supported versions
503 tenant_unresolved The marketplace behind the address could not be loaded Retry later
401 invalid_request No Authorization: Bearer header Send the token as a Bearer header; nothing else is accepted
401 invalid_token The token is unknown, malformed, expired or revoked, or was issued for another marketplace Refresh it, or authorize again on this marketplace
403 access_denied The token is not allowed from this network address Call from an allowed address
403 unknown_tool The tool does not exist Call tools/list
403 or 401 insufficient_scope The token lacks a scope this call needs Reconnect and grant the scope named in the challenge

The body is JSON: {"error": "…", "error_description": "…"}, or {"error": "not_found"} alone for a 404. A token issued for another marketplace gets the same answer as an unknown token. On /mcp/oauth/authorize, an error that cannot be sent back to the client (an unknown client, a redirect URI it did not register) is shown to the administrator as a plain text page instead.

Errors inside a tool (an invalid blueprint, an unknown slug, a stale plan_hash) are not refusals: they come back as a normal tool result with isError: true. See Tools.