Connect an AI client (MCP)

One remote URL connects Claude, ChatGPT, Cursor, VS Code, Gemini CLI or Windsurf to your Marketing OS account — nothing to install, and every change still waits for your approval.

Marketing OS speaks the Model Context Protocol over a single remote URL. There is nothing to install, no local process, and no config file to hand-edit:

https://marketing-api.keyescode.com/mcp

Locally it is http://localhost:4000/mcp.

Plans

MCP is included on every paid plan (Starter and up), each with a monthly tool-call allowance shown as a meter on your dashboard. The free plan does not include it: a client connected to a free organization still sees the full tool list, and every call answers with a sentence naming the plan that includes it — the same tools keep working from the Terminal inside the app. Moving up a plan switches MCP on immediately, with nothing to reconnect.

Authentication

Two ways, both against the same endpoint. OAuth is the one to use: it is the only one that lets you narrow the connection on a consent screen, and its tokens refresh and expire.

HowBest for
OAuth 2.0The client opens your browser, you pick what it may see and do, tokens refreshA person connecting their own client
API keySend Authorization: Bearer mos_…Servers, CI, scripts, shared agents

A client that supports remote OAuth discovers everything it needs by itself:

  • GET /.well-known/oauth-protected-resource — the resource and its authorization server
  • GET /.well-known/oauth-authorization-server — endpoints, PKCE method, grant types
  • POST /oauth/register — dynamic client registration (RFC 7591); no console visit needed

API keys are minted under Settings → API Keys in Marketing OS. The raw key is shown exactly once, only a hash is stored, and it can be confined to a single business and to read or write.

Pick your client

ClientSetup
Claude CodeOne claude mcp add command
Claude Desktop / claude.aiSettings → Connectors → Add custom connector
ChatGPTSettings → Connectors → Create
Cursor~/.cursor/mcp.json
VS Code (Copilot).vscode/mcp.json
Gemini CLI~/.gemini/settings.json
Windsurf~/.codeium/windsurf/mcp_config.json

Any other client that speaks Streamable HTTP works too: point it at the URL above, over OAuth if it supports it and with a mos_ bearer token if it does not.

What the tools do

Every channel Marketing OS touches — not just ads. Each one is the same tool the app itself runs: there is one registry, and MCP shows you what is in it.

The full tool reference is generated from that registry, so it is never a version behind what your client will actually see. Every tool except list_businesses takes a businessId, which you get from list_businesses.

How "needs approval" works

Marketing OS does not hand an AI client the keys. Every tool declares a risk tier, and the tier is the whole gate:

  • read — nothing changes.
  • low — a reversible change inside Marketing OS (a draft, a plan, a pause). Runs, and is written to the audit log.
  • high — a side effect on the outside world or on your money. It does not run. The call files the exact change in your approvals inbox and returns immediately, saying so. A person approves it in Marketing OS, and only then does it happen.

A good client will tell you a change is pending rather than claim it made one; the tool description says so in as many words, and the result starts with AWAITING APPROVAL.

Guardrails sit under all of it

Spend caps, a minimum ROAS before delivery can be increased, a maximum single budget jump, allowed/excluded countries and blocked creative angles are hard floors. They refuse the call outright — before it even reaches the approvals inbox — and no approval, autonomy setting or API call can cross one. A refusal comes back naming the rule and both numbers, so the model can propose something that fits instead of guessing. Read them first with get_guardrails.

Scoping — narrower than the competition

An AdAdvisor API key is organization-wide by design; their own docs say "API keys are scoped to your organization. Anyone on your team with an API key can query the organization's ad data."

Marketing OS lets you go one level narrower, on both auth paths:

  • API key — create it under Settings → API Keys and pick a single business. That key can then reach that business and nothing else, not even the names of its siblings.
  • OAuth — the consent screen lists your organizations and each business inside them. Pick one business and the token is confined to it for its whole life.

And a second dimension, independent of the first:

  • Read only — the client sees only the read tools. It cannot even list the others.
  • Read and propose changes — the client can also draft, plan and propose. Anything that spends money or leaves Marketing OS still waits in your approvals inbox.

On an API key that is the key's read/write scope; over OAuth you pick it on the consent screen. Either way the credential expires, records a last-used timestamp, is revocable, and stops working the moment its owner leaves the organization.


The skills pack — teach the client how to use the tools

Connecting the server gives a client the tools. It does not tell it what good looks like: which window to judge a campaign over, that a high call was filed rather than done, or that a ROAS means nothing without the break-even it is measured against.

That is what the open-source skills pack is for — eight Apache-2.0 skills covering paid, organic social, content, local SEO and AI-search visibility, written against this exact tool registry. Read them at the skills index. In Claude Code they install alongside the connection in one step:

sh
/plugin marketplace add KeyesCode/marketing-os-skills
/plugin install marketing-os@marketing-os

The plugin bundles this MCP server, so that install wires the connection too — you can skip the claude mcp add on the Claude Code page. For Claude.ai and Claude Desktop, build the per-skill ZIPs with sh skills/scripts/build-zips.sh and upload them under Settings → Capabilities → Skills. Everything else reads the skills as plain markdown.

Tool names are public API once the pack ships — renaming one breaks every installed copy, so run node skills/scripts/check-tool-names.mjs . when you touch the registry, and regenerate the tool reference with pnpm docs:tools.


Rate limits

Every credential — each API key, each OAuth grant, each signed-in session — has its own request limits, so one runaway client cannot slow down anybody else or hammer the ad platforms on your behalf. They sit on top of your plan's monthly tool-call allowance, never instead of it — and a plan without MCP (free) is refused before either is counted.

LimitCountsSet byDefault
MCP requests per minuteevery POST /mcp, per API key or OAuth grantRATE_LIMIT_MCP_PER_MIN60
API requests per minute (burst)every authenticated API request, per API key or sessionRATE_LIMIT_BURST_PER_MIN300
API requests per hour (sustained)the same requests, per hourRATE_LIMIT_SUSTAINED_PER_HOUR5000

The defaults are what a deployment gets when it sets nothing; an operator can change any of them, so read the live numbers off the responses rather than this table:

  • Every response carries X-RateLimit-Limit-<name>, X-RateLimit-Remaining-<name> and X-RateLimit-Reset-<name> (seconds) for the limits it counted against — mcp on the MCP endpoint, burst and sustained on the API.

  • Over a limit the answer is HTTP 429 with a Retry-After header in seconds. On the MCP endpoint the body is a JSON-RPC error, so the client can read it:

    json
    {
      "jsonrpc": "2.0",
      "error": {
        "code": -32000,
        "message": "Rate limit exceeded: more than 60 MCP requests a minute for this credential. Retry after 42s.",
        "data": { "retryAfterSeconds": 42, "limit": 60, "windowSeconds": 60 }
      },
      "id": 7
    }

Wait retryAfterSeconds and try again — retrying sooner does not help, because the block runs to the end of the window. One MCP tool call also makes a few API requests on your credential behind the scenes, so a busy MCP client counts against the API limits too.


Troubleshooting

401 with WWW-Authenticate: Bearer resource_metadata=… — expected on the first call. It is how the endpoint tells the client where to start the OAuth flow. A client that stops here does not support remote OAuth; use an API key.

"Sign in to Marketing OS first" during consent — the consent screen reads your normal Marketing OS session. Open the app, sign in, then press Continue. In production the two share a cookie domain, so this only happens when you are signed out or your session went stale.

Only read tools show up — the credential is read-only. Mint a write key under Settings → API Keys, or reconnect over OAuth and pick "Read and propose changes".

"Only an organization admin can run this tool" — reads are open to any member; anything that writes or proposes a write is admin-only, over MCP exactly as in the app.

A change came back as AWAITING APPROVAL — working as intended. It is in the approvals inbox in Marketing OS; approve it there and it runs.

"Refused by the … money guardrail" — a hard ceiling stopped it. The message carries the limit and the actual value; propose something inside the limit, or raise the guardrail in the app (which only a human can do — there is no tool for it, deliberately).

429 / JSON-RPC error -32000 — a rate limit. Wait the Retry-After seconds (or error.data.retryAfterSeconds) and retry once; see Rate limits. A client that retries in a tight loop only keeps itself blocked.

402 from a tool — the organization's subscription is not active. The MCP endpoint dispatches every tool through the API's own routes, so the paywall applies here too; it is not a way around billing.

"This credential is confined to business X" — the key or grant is scoped to one business and the tool asked for another. Working as intended.

A browser-based client is blocked by CORS — add its origin to the API's CORS_ORIGIN. Desktop clients and server-side connectors (Claude Code, Claude Desktop, ChatGPT, Cursor, VS Code, Gemini CLI, Windsurf) are unaffected.

Local development

A stdio server in apps/mcp still exists for working on the tools themselves. It renders the same registry through the same shared layer, so a tool can never exist on one transport and not the other.


Something wrong or missing on this page? Tell us and we will fix it.