markaDocs
MCP

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/mcp

Then run /mcp in Claude Code and pick Authenticate. What happens behind that:

  1. The client POSTs to the gateway with no credential and gets a 401 carrying WWW-Authenticate: Bearer resource_metadata="https://joinmarka.com/.well-known/oauth-protected-resource".
  2. It follows that to the protected-resource document, and from there to the authorization-server document.
  3. It registers itself (RFC 7591 dynamic client registration) and opens your browser at the authorization endpoint.
  4. 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.
  5. The client exchanges its code for an access token (PKCE S256 is 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

HostedLocal (SDK bridge)
Where it runsMarka's serversyour machine
How you reach itPOST https://joinmarka.com/api/mcpstdio subprocess
How you authenticateOAuth in your browser, or an API keyan API key
Backed byMarka's database, directlythe public /api/v1 HTTP API
Transportstreamable HTTPstdio
Tools2 by default, search_tools and execute_typescript, over 6 operations — see them6, a different 6 — see them
Seller lookupget_seller_info—
Key/plan lookup—get_me
Write gatingyour monthly AI copilot run quotathe 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.

errorCause
missing api keyNo x-api-key or Authorization header was sent
invalid api keyThe key is unknown, revoked, or expired: create a new one
invalid access tokenThe OAuth token is unknown, revoked, or expired: refresh it or sign in again
cookie sessions are not accepted; an api key is requiredA 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:

ScopeAllows
marka:readThe four read operations: search_listings, get_listing, list_categories, get_seller_info
marka:writeEverything 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

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.

On this page