Skip to content

Theme Dev MCP server

Connect an AI client to the FirearmCart theme development MCP server — transport, tokens, abilities, and team scoping

Updated View as Markdown

FirearmCart runs a Model Context Protocol server that lets an AI client browse, edit, screenshot and publish your store’s Liquid themes. It is a sibling of the v1 REST API, not part of it: same host, same bearer tokens, different protocol.

POST https://api.firearmcart.com/mcp/themes

The server advertises itself as FirearmCart Theme Dev and exposes 33 tools, 3 resources and 2 prompts. Nothing here is reachable over /v1 — there is no REST equivalent for theme files.

Property Value
Endpoint https://api.firearmcart.com/mcp/themes
Method POST only
Transport Streamable HTTP (JSON-RPC 2.0)
Auth Authorization: Bearer <token> — the same store token the REST API uses
Abilities themes:read and/or themes:write
Rate limit 240 requests/minute per store, in its own bucket
Protocol versions 2025-11-25 (default), 2025-06-18, 2025-03-26, 2024-11-05

Transport

The endpoint speaks Streamable HTTP, the MCP transport where every client message is an HTTP POST carrying a JSON-RPC envelope.

Three things about this deployment are worth knowing before you configure a client:

  • POST is the only usable method. GET and DELETE on the endpoint are registered, but they answer 405 with Allow: POST and nothing else. There is no server-initiated SSE channel to listen on, so a client that insists on opening a GET stream before it will talk to you cannot connect.
  • Those 405s are unauthenticated. The auth, ability and throttle middleware are attached to the POST route only. A GET without a token returns 405, never 401 — so a probe that expects a 401 to confirm the endpoint exists will read the wrong signal.
  • Responses are application/json. The server replies Content-Type: application/json for ordinary requests and only switches to text/event-stream when a tool streams its output. Send Accept: application/json, text/event-stream as the MCP spec requires; the server reorders that header internally so application/json wins content negotiation.

A session id is returned in an MCP-Session-Id response header on initialize. The server does not persist sessions and does not reject later requests that omit the header.


Get a token

The MCP server uses the same tokens as the REST API. Mint one at Profile → API Tokens (/user/api-tokens) on https://app.firearmcart.com, after switching to the store you want the token to act on.

Tick themes:read, themes:write, or both. Neither ability appears in the pre-ticked read-only default, so you have to select them deliberately. See Authentication for the token model and the token format.

Your store must also pass the API access gate — the plan must include API & Webhooks, or the store must hold the API access add-on. The gate runs on MCP requests exactly as it runs on /v1. See Authentication.


Connecting a client

The raw contract

If you are wiring this up by hand, or your client’s config format is not covered below, this is everything the server needs:

URL https://api.firearmcart.com/mcp/themes
Method POST
Authorization Bearer <your-token>
Content-Type application/json
Accept application/json, text/event-stream

There is no OAuth flow on this server. It does not publish authorization-server metadata, and an unauthenticated request answers with WWW-Authenticate: Bearer realm="mcp", error="invalid_token" rather than a discovery URL. A client that only knows how to obtain a token by OAuth cannot bootstrap itself here — paste a token in.

Claude Code

claude mcp add --transport http fc-themes https://api.firearmcart.com/mcp/themes \
  --header "Authorization: Bearer $FIREARMCART_TOKEN"

Clients that take a JSON server list

Most desktop and IDE MCP clients keep a JSON file of servers, and most of them accept a remote HTTP server in roughly this shape:

{
  "mcpServers": {
    "fc-themes": {
      "type": "http",
      "url": "https://api.firearmcart.com/mcp/themes",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN_HERE"
      }
    }
  }
}

Note: The exact key names differ between clients — some spell the transport "transport" rather than "type", some nest headers differently, and some do not support remote servers at all without a local bridge. We cannot promise a schema we do not control. Check your client’s own documentation and map it onto the raw contract above; the only values that come from FirearmCart are the URL and the Authorization header.

Verify with curl

The handshake is plain JSON-RPC, so you can prove a token works before touching a client config:

curl -sS https://api.firearmcart.com/mcp/themes \
  -H "Authorization: Bearer $FIREARMCART_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-11-25",
      "capabilities": {},
      "clientInfo": { "name": "curl", "version": "1.0" }
    }
  }'

A working token returns a result whose serverInfo.name is FirearmCart Theme Dev and whose instructions field carries the server’s full authoring brief. Swap "method": "initialize" for "method": "tools/list" to see the tools your token can actually reach — which is not always all 33. That is the next section.


Which ability connects, which ability gates

This distinction is the one thing most likely to confuse you, so it is worth being precise.

Connecting requires any one of the two theme abilities, not both. themes:read alone is enough to open a session; themes:write alone is too.

Practically:

  • A token holding only themes:read connects successfully. It is not a second-class connection; the handshake, resources and prompts all work.
  • A token holding only themes:write also connects.
  • A token holding neither is rejected at the route with 403 {"message": "Invalid ability provided."}.

Write access is then enforced per tool, in two layers:

  1. The 15 write tools hide themselves. Each one’s shouldRegister() checks the token for themes:write. A read-only token’s tools/list returns 18 tools, not 33 — the write tools are absent from the listing rather than present-and-failing. If you are debugging a “the agent says that tool doesn’t exist” report, check the token’s abilities first.
  2. The tools re-check on call. Reaching a write tool anyway returns an error result: This token lacks themes:write. Create a token with Themes: Write ability.

One tool sits slightly off this grid. screenshot-theme renders a preview and uploads the image, so it is not annotated read-only the way the other 17 read tools are — but it is not write-gated either, and a themes:read token can call it. Screenshotting is how an agent verifies its own work, so a read-only token can still run the full inspect-and-report loop.


Team scoping

Everything here inherits the REST API’s identity model: the authenticated principal is a store, not a person. Read Authentication if you have not.

Every tool resolves the store from the token and constrains its query to that store’s rows before doing anything else. There is no store argument on any tool and no way to reach another store’s themes:

  • Theme lookups match on team_id plus the id or UUID you passed. A theme that exists but belongs elsewhere is indistinguishable from one that does not exist: Theme not found for this team. Call list-themes first.
  • Blog articles have no store column of their own, so article tools resolve the blog first and reach articles only through it. The scoping cannot be bypassed by passing an article id directly.
  • A token that authenticates but does not resolve to a store is rejected by the tool itself with an unauthenticated error, telling you to provide a store API token.

If you run several stores, you need one token per store, and one configured MCP server per store.


Rate limits

The MCP server has its own bucket: 240 requests per minute, keyed by store. Agent sessions make many small tool calls, so it is four times the /v1 allowance.

Limit Keyed by
Authenticated 240/minute Store
Unauthenticated 10/minute Client IP

It does not draw on, and is not drawn on by, the /v1 budget of 60/minute. Exhausting one leaves the other untouched. See Rate limits.

Note that the unit is requests, not tool calls. A client that batches several JSON-RPC messages into one POST spends one slot.


Failures that are not JSON-RPC

Tool-level problems come back as ordinary MCP error results, and your client will render them. Middleware-level rejections happen before the MCP server runs at all, so they arrive as plain HTTP with a framework error body — which most clients surface as a bare connection failure.

Status Body Cause
401 {"message": "Unauthenticated."} Missing, mistyped or revoked token. Also carries WWW-Authenticate: Bearer realm="mcp", error="invalid_token".
403 {"error": "forbidden", "message": "API access is not enabled for this team."} The store’s plan has no API access and it holds no add-on.
403 {"error": "subscription_expired", "message": "Your subscription has expired. Please renew to continue using the API."} Subscription suspended after a failed payment, or past its cancellation date.
403 {"message": "Invalid ability provided."} The token holds neither themes:read nor themes:write.
405 (empty, with Allow: POST) The request used GET or DELETE.
429 Standard throttle response, with Retry-After 240/minute exceeded.

If a client reports “server disconnected” or “failed to initialize” with no further detail, replay the handshake with the curl command above — the HTTP status tells you which row you are in.


How the server expects to be used

The initialize response ships a long instructions block that your agent receives automatically. Its shape, condensed:

  1. list-themes, then never build against the active theme. The active theme is the live storefront. Use an inactive library copy, or call duplicate-theme to make one. File-write tools refuse the active theme unless you pass allow_active: true.
  2. Read the liquid-guidelines resource before writing any Liquid. This is not optional advice; it is the platform-specific rulebook, and the failure modes it covers are silent ones. See Resources & prompts.
  3. Inventory before authoring. list-theme-files and get-section-schema first — configuring an existing section through templates/*.json beats writing a new one.
  4. Save, then look. Writes are validated (Liquid, JSON and schema) and versioned; screenshot-theme on desktop, then a tablet and mobile pass.
  5. Revert cheaply. list-file-versions and restore-file-version when a change made things worse.
  6. Publish last. publish-theme when the merchant is ready; the previously active theme is deactivated automatically.

The brief also covers what a theme reads but does not own. Menus, policies and plugin sections are store data with their own tools. Collection filters are a theme setting: which specification groups appear as filters on collection pages is trimmed with list-collection-filters and set-collection-filters, never by editing the facets snippets or hiding filter groups with CSS.

The full tool surface is on Tools, and the reference material the server hands your agent is on Resources & prompts.


  • Tools — all 33 tools, their arguments and their guardrails
  • Resources & prompts — the 3 resources and 2 prompts the server ships
  • Authentication — the store-scoped token model and the API access gate
  • Rate limits — how the MCP bucket sits alongside the /v1 buckets
  • Themes — the merchant-facing theme editor these tools write into
Navigation

Type to search…

↑↓ navigate↵ selectEsc close