Skip to main content

Overview

Ads MCP is a remote MCP (Model Context Protocol) server built into X’s API gateway at https://ads-api.x.com/mcp. MCP is the open standard for connecting LLM agents to tools, so any MCP-capable client — Grok, Claude Code, or a custom agent built on the MCP SDKs — can connect and immediately use 74 X Ads tools with zero custom Ads API integration. The agent reads campaign data, runs analytics, and creates and manages campaigns through natural language; the MCP client handles all of the API plumbing. Authentication is the user’s own OAuth2 token, so an agent only ever sees the ads accounts that user can access. Writes are safe by default: campaigns and line items are always created PAUSED, and nothing spends money until it is explicitly activated. Why MCP is useful:
  • Zero integration cost — no bespoke Ads API client code; point any MCP client at one URL and the tools are discovered automatically.
  • Natural-language campaign management — the model chains tools itself (accounts → funding instruments → campaign → line item → targeting) from a single plain-English request.
  • Instant analytics — pull performance stats and reach estimates conversationally, without writing queries or scripts.
  • Scoped and safe — the user’s own token bounds what the agent can see, and every write starts paused.
  • Full conversion tracking — create and manage website pixels (web event tags) conversationally, then set one as a line item’s primary_web_event_tag: Sales / website-conversion campaigns work end to end, from pixel creation to conversion reporting.
  • Audiences and creatives — build custom audiences and do-not-reach lists, estimate audience size before spending, and manage the full creative stack: cards, the media library, account media, and media creatives.
  • App campaigns and precise reads — app event tags, tracking tags, and app lists cover mobile-app campaign setup, and single-entity reads (get_line_item, get_card, get_custom_audience, …) let the agent fetch exactly the object it needs.

Server Capabilities

Get Started

Step One: Register your X Ads App ID

Go to the X Developer Console, create or reuse a previously created app. In the app’s settings, enable the below:
  • App Permissions: Read and Write
  • Type of app: Native App (public client). (Confidential clients — Web App, Automated App or Bot — also work for Grok Build and manual token minting via client_secret_basic; never use client_secret_post, which X rejects.)
  • Callback URI / Redirect URL: register the callback for the client that you’ll use:
You may register multiple callbacks, but X keeps a single live OAuth grant per (app, user): signing in from a second client revokes the first client’s tokens. In practice, use one client per X app — create one app per client if you want to run several simultaneously.
  • Website URL: http://x.com
Under Keys and tokens, copy the OAuth 2.0 Client ID — the long string like TzNHbE5X…6MTpjaQ, not the numeric app ID shown elsewhere in the console. Native apps have no client secret (PKCE only). Under Project Access, click MANAGE and select Ads Project to automatically enable Ads API access to your App ID. Your X user must also have access to at least one ads account. You can open ads.x.com to ensure that your account is properly connected as MCP only sees accounts that you have access to.

Step Two: Connect your LLM

The Ads MCP is a standard remote MCP server using the Streamable HTTP transport:
  • Endpoint: https://ads-api.x.com/mcp (all requests are HTTP POST with JSON-RPC; responses are SSE framed)
Choosing scopes: ads.read grants the read and analytics tools, ads.write grants campaign and creative writes, and offline.access enables token refresh — always include it, or tokens expire in ~2 hours with no way to refresh. Add media.write if the agent will upload media or use the media library write tools (create_media_library_item, update_media_library_item, delete_media_library_item, delete_account_media); it is not needed for read-only media browsing or for campaign management. For a read-only agent that can browse and analyze but not create or change anything, omit ads.write — write tools will fail with authorization errors. Include both ads scopes for full campaign management, and add media.write only when media uploads or media-library writes are required. Each client recipe below shows where to set them.

Grok Build (CLI)

Add to ~/.grok/config.toml — the OAuth keys must be camelCase (snake_case keys are silently ignored):
The scopes array is where you choose access — drop "ads.write" for a read-only agent. Add "media.write" only if the agent will upload media or use the media library write tools. Then run grok/mcps → select ads-mcp → press i → complete the browser consent → done. The flow uses PKCE S256 and refreshes tokens automatically. No secret is needed with a Native App. If you use a confidential app, add clientSecretEnvVar = "ADS_MCP_CLIENT_SECRET" and export that variable in the same shell — never point it at an empty env var, which sends an empty secret.

Grok (web)

Go to grok.com/connectors → New ConnectorCustom:
  • Server URL: https://ads-api.x.com/mcp
  • Client ID: your OAuth 2.0 Client ID string
  • Client Secret: leave blank
  • Token Auth Method: “none (PKCE only, recommended)”
  • Leave the auto-discovered endpoints as-is
  • Grok pre-fills the scope chips from the server’s discovery metadata — delete all pre-filled chips and keep only ads.read, ads.write, offline.access (and optionally media.write).
The scope chips are where you choose access — for a read-only agent, keep only ads.read and offline.access. Connect → complete the X consent screen → the connector is live.

Claude Code

Requires a public client — Claude Code sends client_secret_post, which X rejects for confidential clients:
Then run /mcp inside Claude Code to sign in. There is no CLI flag for scopes — by default Claude Code requests the scopes the server advertises; to narrow to read-only, set "oauth": { "scopes": "ads.read offline.access" } on the server’s entry in ~/.claude.json (a single space-separated string). To include media library writes, use "oauth": { "scopes": "ads.read ads.write media.write offline.access" } (media.write is optional). Alternatively, use a static token:

User access token (curl, custom agents)

The three clients above mint and refresh tokens automatically via OAuth login — skip this section unless your client only supports a static Authorization header. To mint a token manually, open the below URL in a browser logged in as the X account that owns your ads account (replace YOUR_CLIENT_ID):
The scope= parameter is where you choose access — the read-only variant is scope=ads.read%20offline.access. Add %20media.write if the agent will upload media or use the media library write tools. After clicking Authorize, the browser lands on a dead localhost:8080/callback page — copy the code= from the address bar and exchange it within ~30 seconds:
The response contains an access_token (~2 hours) and a refresh_token. When the access token expires, refresh without the browser:
Refresh tokens rotate — always save the newest one — and when calls start failing with 401s, refresh and update the header in your client config. Point any MCP client or SDK at the endpoint with the token in an Authorization header; tools are discovered automatically via tools/list. For example, with the official TypeScript SDK:
To check your token and connectivity without any client:
A successful response lists the server’s tools (the 74 documented here among them). If you get a 401, your token is invalid or expired; a 403 means your app isn’t enrolled yet (see step one).

Step Three: Test It

Start with reads: “List my ads accounts”, “Show my campaigns and how they performed last week”. Then try the write flow, e.g.:
Using my X Ads Account, create a paused campaign called “Test campaign” with a 10dailybudget,thenaddapauseditemwithawebsiteclicksobjectiveanda10 daily budget, then add a paused item with a website clicks objective and a 1 bid targeting US users.
The model chains the tools itself (funding instrument lookup → campaign → line item → targeting). create_line_item requires placements as a comma-separated string (for example ALL_ON_TWITTER or TWITTER_TIMELINE,TWITTER_PROFILE); valid values for a product and objective come from list_line_item_placements. Budget, schedule, and pacing are set on the line item — create_campaign does not accept standard_delivery. Campaigns and line items are always created PAUSED — nothing spends until you explicitly activate with activate_campaign / activate_line_item. To pause a running line item, call update_line_item with entity_status=PAUSED (the only way to pause one); activate_line_item is the only way to make it eligible to spend again. Campaign pausing uses update_campaign with entity_status=PAUSED.