MutatorDocs

MCP server

Connect an AI agent — Claude, ChatGPT, Cursor, anything that speaks Model Context Protocol — to a Mutator workspace, so it can find automations, test them, read what happened and report what came out.

Endpoint: https://mutator.app/mcp

The one thing to know

No tool on this server can post anything.

That is not a scope you can widen or a permission you can grant. It is the shape of the server. An agent can build a run, watch it, read what came out and tell you about it; a person opens Mutator and decides whether any of it reaches a real account.

Nor can the public API, since approvals were removed on 20 September 2026: a run publishes nothing at all, and a post exists only once a person has put a particular file in a particular account's schedule, in Mutator, with their name on the decision. There is no key of any scope that reaches that. See what marketing may claim about this, and what the platform reviews were told.

Connecting

Two ways in, and both reach exactly the same tools:

  • OAuth, for clients that are given only the address: Claude and ChatGPT connectors, Claude Code, and any client that follows the MCP authorization spec. The client opens Mutator in a browser, the workspace owner signs in, picks the workspace and chooses whether it may start runs.
  • An API key as a bearer token, for clients configured with a header: the same keys the public API uses, created under Settings → API keys. See the public API documentation, which says what a key reaches on each plan.

Either needs the workspace owner, on any paid plan. read reaches every tool except the four that write: mutator_start_run, mutator_create_automation, mutator_save_automation_graph and mutator_set_schedule. Give read only unless the agent genuinely needs to build or run things. Nothing either way can publish.

Claude or ChatGPT

In Claude, add a custom connector under Settings → Connectors. In ChatGPT, add a connector with OAuth. Paste https://mutator.app/mcp and nothing else, then approve it in the window Mutator opens. Connected apps are listed under Settings → API keys, where each can be disconnected.

To make content in ChatGPT rather than build automations, use Mutator in ChatGPT instead: its own server, https://mutator.app/mcp/chatgpt, with five tools for making, previewing and handing content to Add to schedule.

Claude Code

claude mcp add --transport http mutator https://mutator.app/mcp

Then run /mcp in Claude Code and sign in. To use a key instead, add --header "Authorization: Bearer loop_sk_..." to the command.

Anything with a JSON config

{
  "mcpServers": {
    "mutator": {
      "type": "http",
      "url": "https://mutator.app/mcp",
      "headers": { "Authorization": "Bearer loop_sk_..." }
    }
  }
}

How OAuth works here

Mutator is its own authorization server and /mcp its only protected resource, following the MCP authorization spec (2025-06-18 and later):

  • A request without a key or token gets 401 with WWW-Authenticate: Bearer resource_metadata="https://mutator.app/.well-known/oauth-protected-resource/mcp". The handshake included: a client that got a 200 from initialize would never offer to sign in.
  • Metadata: /.well-known/oauth-protected-resource (RFC 9728) and /.well-known/oauth-authorization-server (RFC 8414).
  • Clients register themselves at /oauth/register (RFC 7591). Public clients use PKCE alone; a client that names no auth method gets a secret.
  • /oauth/authorize takes the code flow with PKCE (S256 only) and a resource of https://mutator.app/mcp (RFC 8707). The person approves on /connect, which shows where the answer will be sent. An app's name is its own claim; the redirect address is fixed when it registers.
  • /oauth/token issues an access token for an hour and a refresh token for 90 days, rotated on every use. A refresh token presented again after it was traded in ends the connection. /oauth/revoke (RFC 7009) ends it too.
  • An access token is accepted at /mcp only, never by /api/v1, and stops working the moment the connection is revoked or the workspace leaves a paid plan, like a key.

Check it works by asking the agent to call mutator_whoami, or from a shell:

curl -s https://mutator.app/mcp -H "Authorization: Bearer loop_sk_..." -H "content-type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

GET https://mutator.app/mcp needs no key or token and returns a description of the server and its tools — useful for checking reachability, and for anything cataloguing MCP servers.

That same URL opens a page in a browser. One address serves both: a request asking for text/html gets the page, and everything else — a POST, or a GET sending */* or application/json — gets the protocol. Nothing about the endpoint changed for a client that was already using it.

Building an automation

An agent reads the vocabulary before it writes: mutator_list_brands for the brand the automation belongs to, mutator_list_step_types for the steps that exist, mutator_list_formats for the content formats, and mutator_list_connections for the account a publish step must name — there is no "post to TikTok" field, only "post to this connection, which happens to be TikTok".

Then mutator_create_automation with a graph of { key, type, position, config } nodes and { sourceKey, targetKey } edges. Most settings have defaults, so config: {} is usually enough. Two branches out of one step is how the same idea is produced two ways.

An invalid graph is saved anyway, with the problems returned as issues. That is deliberate: half-built is a normal state for a draft, and failing the whole call would lose the work. Two settings are refused outright at save time rather than at run time, because otherwise the executor would refuse them later, in the middle of somebody's batch: several versions of one video, and an image batch that is not 1 or exactly 4.

Tools

ToolScopeWhat it does
mutator_whoamireadWhich workspace this key reaches and what it may do
mutator_list_automationsreadEvery automation with id, name and status
mutator_get_automationreadOne automation: status, schedule, spending limits
mutator_list_runsreadRecent runs of one automation
mutator_get_runreadOne run, with per-step detail
mutator_list_approvalsreadAlways empty: approvals were removed
mutator_get_analyticsreadHow published content performed
mutator_get_spend_limitsreadDaily and monthly ceilings, and what is left
mutator_start_runwriteStart a run. Dry by default
mutator_list_brandsreadThe brands an automation can belong to
mutator_list_connectionsreadConnected accounts, and the id a publish step needs
mutator_list_formatsreadThe library's formats (the newest flagged, each public page's address as page, the ones made only in the app as needsScreenshot) and the catalogue, with the niches they suit
mutator_list_step_typesreadEvery step an automation can be built from
mutator_create_automationwriteCreate an automation. Draft only
mutator_save_automation_graphwriteReplace its steps. Draft only
mutator_set_schedulewriteSet when it would run. Saved switched off

Dry by default

mutator_start_run runs dry unless the caller passes dryRun: false. A dry run skips publishing (and scheduling a repeat), not generation. It is not free: create and understand ignore dryRun, so a dry run makes the same paid generation calls a real run does, costs the same credits or provider charges, and is checked against the same spend caps (src/server/engine/runs.ts, the comment above assertSpendWithinCaps).

The public API leaves dryRun undefined and lets the engine decide, which is right for an integration somebody wrote on purpose. Here the caller is a model that may have inferred the entire call from one sentence, so the safe reading of silence is "test it": whatever it generates stops short of any social account. That limits what a mistaken call can publish, not what it can spend, which is why the tool description tells the model to start a run only when the user wants one.

idempotencyKey is required, 8–80 characters, and namespaced per key on the way through. Retrying with the same value returns the original run rather than starting a second one.

What this server is not

  • Not an activator. An agent can build a format and save it as a draft, and make from it the way Make does in Mutator: mutator_start_run on a format that is not switched on runs its latest draft as a dry run, which makes things and publishes nothing. It cannot switch one on, and nothing it makes posts until a person schedules it in Mutator.
  • Not a publisher. Above.
  • Not a trend service. There is no virality, trending or discovery data in this product. mutator_list_formats returns the formats the Mutator team built for its library, then a hand-written catalogue, and a niche orders them rather than filtering them. newThisWeek means added to the library in the last seven days, not trending. An agent asked for "viral formats" can offer these; it cannot say any of them is trending.
  • Not a place to make Green screen formats. A format marked needsScreenshot puts the person in front of a screenshot the user uploads, with their tick for the people in it, so it is made in the Mutator app. Saving a graph that turns a format's Green screen on is refused before anything is created.
  • Not stateful. No session id is issued, so any replica answers any request and a deploy loses nothing. Each call carries its own key.

Implementation notes

The server speaks JSON-RPC directly rather than using the official SDK. MCP is JSON-RPC 2.0 with four methods that matter to a tools-only server — initialize, tools/list, tools/call, ping — and the SDK's value is in transports that assume a long-lived process owning a socket. A Next route handler is neither.

Authentication, rate limiting and error shaping are shared with the public API, and the endpoint carries no scope of its own: reaching it needs only a valid key or token, and the write tools check the scope themselves at call time. Requiring write at the door would lock read-only keys out of initialize.

No CORS headers, deliberately. Every MCP client that matters connects from a server or a desktop process, and authentication is a bearer token rather than a cookie, so there is nothing for a browser to be tricked into sending.

The protocol versions spoken are 2025-06-18, 2025-03-26 and 2024-11-05. An unknown version is answered with the newest rather than refused.