MCP servers
Marka ships two MCP servers with different tool sets — the hosted gateway, which signs you in through your browser, and the SDK's local bridge. Which to use, and why they differ.
Marka speaks MCP so an AI assistant can search the marketplace and draft listings on your behalf. There are two servers, and they are not the same server with two transports — they expose different tools, backed by different things.
Connecting an AI coding agent?
The fastest way is to paste one sentence into the agent. It reads Marka's setup instructions, asks you for an API key, and adds the hosted gateway below to its own config. Agent setup has the sentence, and a guide for Claude Code, Codex, Cursor and OpenCode.
The hosted gateway: one URL, no key
Give your client the address. It opens a browser, you sign in and press Allow, and it is connected. Nothing to paste, nothing to store.
claude mcp add --transport http marka https://joinmarka.com/api/mcpThen run /mcp in Claude Code and pick Authenticate. What happens behind that:
- The client POSTs to the gateway with no credential and gets a
401carryingWWW-Authenticate: Bearer resource_metadata="https://joinmarka.com/.well-known/oauth-protected-resource". - It follows that to the protected-resource document, and from there to the authorization-server document.
- It registers itself (RFC 7591 dynamic client registration) and opens your browser at the authorization endpoint.
- You sign in if you are not already, and a consent screen names the client and shows where it will send you back to. Press Allow.
- The client exchanges its code for an access token (PKCE
S256is required) and a refresh token, and stores them itself.
Access tokens last an hour. A refresh token stays valid for thirty days from its last use, so a client you keep using stays connected indefinitely and one you stop using falls off after a month. Disconnecting is what actually ends access: under Settings → Connected apps you can see every client you have authorized, and disconnecting one takes effect immediately and revokes every token it holds.
Check the callback address on the consent screen
Any application can register itself under any name, so the name on the consent screen is not proof of anything. The callback origin shown beside it is: a client that says "Claude" but sends you back to somewhere else is not Claude.
API keys still work here
An mk_ key in x-api-key or Authorization: Bearer is accepted on the same endpoint, unchanged.
That is the right credential when there is no browser to open — a script, a cron job, a CI step:
claude mcp add --transport http marka https://joinmarka.com/api/mcp \
--header "x-api-key: mk_YOUR_KEY_HERE"The difference is what you are holding. A key is long-lived and sits in a config file; an OAuth token is short-lived, refreshable, and revocable from Settings without invalidating anything else.
Not a browser cookie
This endpoint does not accept a signed-in browser session. Its tools mutate listings, and it
stays a credential-bearing machine endpoint rather than becoming a second, cookie-authenticated
door into those mutations. A missing or rejected credential answers 401 with the
WWW-Authenticate challenge above.
GET on this URL answers 405 with an Allow: POST header — the endpoint is POST-only, and says
so rather than letting a 404 stand in for it.
Which one
| Hosted | Local (SDK bridge) | |
|---|---|---|
| Where it runs | Marka's servers | your machine |
| How you reach it | POST https://joinmarka.com/api/mcp | stdio subprocess |
| How you authenticate | OAuth in your browser, or an API key | an API key |
| Backed by | Marka's database, directly | the public /api/v1 HTTP API |
| Transport | streamable HTTP | stdio |
| Tools | 2 by default, search_tools and execute_typescript, over 6 operations — see them | 6, a different 6 — see them |
| Seller lookup | get_seller_info | — |
| Key/plan lookup | — | get_me |
| Write gating | your monthly AI copilot run quota | the api_write entitlement (Pro+) |
Use the hosted server if your client speaks streamable HTTP. It is one URL, it needs nothing installed, and its read tools are richer (it can look up a seller's public profile).
Use the local bridge if your client can only spawn a stdio subprocess, or if you are developing against a local Marka.
More on the hosted gateway
A request without a usable credential answers 401 with the WWW-Authenticate challenge above
and a JSON body of two fields: error, a short string saying what was wrong, and hint, the next
step in a sentence an agent can pass on to its user, with links to the API keys page and the
agent setup guide.
error | Cause |
|---|---|
missing api key | No x-api-key or Authorization header was sent |
invalid api key | The key is unknown, revoked, or expired: create a new one |
invalid access token | The OAuth token is unknown, revoked, or expired: refresh it or sign in again |
cookie sessions are not accepted; an api key is required | A signed-in browser cookie arrived instead of a key |
Its six operations are listed on Tools, generated from the server's own
registry. Four are public reads (search_listings, get_listing, list_categories,
get_seller_info); two create and edit drafts on your account (create_listing_draft,
update_listing_draft).
By default the gateway does not list those six as tools. A client sees two, search_tools and
execute_typescript: search_tools returns the operations as TypeScript declarations, and
execute_typescript runs a short program that calls them as external_<operation>(input), for
example return await external_search_listings({ query: "chair" });. Each call runs the operation
with the same authentication and checks as calling it directly. A program that fails still comes
back from a successful execute_typescript call, with success: false and the error in its
result, so read the result rather than the call status. If your client lists the six operations as
tools instead, it calls them directly.
OAuth scopes
An OAuth connection is granted one or both of two scopes, and the consent screen says in words what each one allows:
| Scope | Allows |
|---|---|
marka:read | The four read operations: search_listings, get_listing, list_categories, get_seller_info |
marka:write | Everything marka:read does, plus create_listing_draft and update_listing_draft |
Which operations are reads is not a list kept here: each operation declares it itself, and a new one
that does not say it only reads is treated as a write. A connection granted marka:read alone is not
shown the write operations by search_tools, and an execute_typescript program that calls one
fails with insufficient_scope. A direct call of a write operation answers 403 with the standard
challenge naming the scope to ask for:
WWW-Authenticate: Bearer resource_metadata="https://joinmarka.com/.well-known/oauth-protected-resource", error="insufficient_scope", scope="marka:write"marka:write is granted on every plan. It says what kind of call is allowed, never whether your plan
pays for it: the limits below apply to a connection exactly as they apply to an API key. A client
that asks for neither scope (every client that predates them, and any that only asks for openid)
keeps the full set, and an API key has no scopes and keeps every operation.
What the write tools cost
The hosted draft tools consume your AI copilot run quota — the same monthly allowance the in-app assistant draws on. On the free plan that is 3 runs per month; Pro and Business are unlimited. Exceeding it fails the tool call rather than silently doing nothing.
They do not go through /api/v1, so they do not require the api_write entitlement. This is
the one place the two surfaces genuinely diverge in what a free-plan credential can do: it can
draft a listing through the hosted MCP server (up to the copilot quota) but cannot create one
through POST /api/v1/listings.
Neither server can publish
There is no publish_listing tool on either. Publishing is what makes a listing visible to buyers
and commits you to selling the thing — it stays a human decision, taken in the Marka app or through
an explicit POST /api/v1/listings/{id}/publish call you make yourself.
Connecting
Claude Code
claude mcp add, then /mcp to authenticate.
Claude Desktop
A custom connector, or the stdio bridge.
Any other MCP client that speaks streamable HTTP can use the hosted gateway with a config block of this shape — no credential in the file:
{
"mcpServers": {
"marka": {
"type": "http",
"url": "https://joinmarka.com/api/mcp"
}
}
}That is exactly what getMcpOAuthServerConfig() returns from the SDK. For the API-key form, use
getMcpServerConfig({ apiKey }).http instead.
SDK cookbook
Six end-to-end recipes with runnable code — set up the client, create and publish a listing, search the market, reconcile inventory, process orders across both rails, and wire the MCP bridge into an AI client.
Tools
The six operations the hosted MCP gateway serves, and the two tools a client reaches them through by default. Generated from the server's own registry.