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.
What an agent can do
Section titled “What an agent can do”| 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.
Endpoint
Section titled “Endpoint”Each marketplace has its own server, at /mcp on the address its storefront answers on:
https://<marketplace address>/mcpFor 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 gets202with an empty body.GET /mcpanswers405: 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-18and2025-03-26. The server answersinitializewith the version the client asks for when it supports it, otherwise with2026-07-28.
Switch the server on
Section titled “Switch the server on”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.
- In the back office, open Configuration → AI connectors (
/admin/ai-connectors). - Click Switch on. The change takes effect immediately.
- 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.
Connect a client
Section titled “Connect a client”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/mcpClaude opens the marketplace’s consent screen when you first connect.
claude mcp add --transport http kreezalid https://acme.mykreezalid.com/mcpThen run /mcp in a Claude Code session and choose Authenticate for kreezalid. Your
browser opens the consent screen.
Add the endpoint as a custom connector with OAuth authentication:
https://acme.mykreezalid.com/mcpChatGPT identifies itself with a client metadata document, which the server reads; there is no client ID or secret to enter.
{ "mcpServers": { "kreezalid": { "url": "https://acme.mykreezalid.com/mcp" } }}Cursor starts the OAuth flow when the server first asks for authorization.
{ "servers": { "kreezalid": { "type": "http", "url": "https://acme.mykreezalid.com/mcp" } }}VS Code registers itself and opens the consent screen when you start the server.
Any other client
Section titled “Any other client”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.
Authorize a script yourself
Section titled “Authorize a script yourself”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.
-
Register a client. The redirect URI must be
https://, orhttp://on127.0.0.1,::1orlocalhost. 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"} -
Create a PKCE verifier and its
S256challenge. OnlyS256is 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 '=') -
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=4242After 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: copycodefrom the address bar. The code is valid for two minutes and can be used once. -
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"}scopeis what the administrator granted, which can differ from what you asked for. -
Send the access token as
Authorization: Bearer <access_token>on everyPOST /mcp. See Tools for the request format.
Renew the access token before it expires with the refresh token:
curl -s https://acme.mykreezalid.com/mcp/oauth/token \ -d grant_type=refresh_token \ -d refresh_token="$REFRESH_TOKEN" \ -d client_id=krmcp-client-3f9c2a7d1e6b4c0a9d8e7f6a5b4c3d2eEach 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:
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) |
Permissions
Section titled “Permissions”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), andmarketplace:destroygrants 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:readalone 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.
Safety
Section titled “Safety”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.
What an agent cannot do
Section titled “What an agent cannot do”- 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.
Disconnect a connector
Section titled “Disconnect a connector”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.
When a call is refused
Section titled “When a call is refused”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.