Documentation
MCP connector
Review Google Ads search terms from your own Claude. Free — the analysis runs here, the judgement runs on your subscription.
What this is
Search Term Pro is a hosted MCP server that finds wasted spend in a Google Ads account and blocks it. Installed as a connector, it lets your own Claude read an account's search terms, decide which ones are waste, and write negative keywords back — with every write checked server-side first.
The division of labour is the point. Pulling the account, filtering it, verifying a proposed negative, and applying it are all deterministic, and they happen here. The single judgement call — is this search term waste for this advertiser? — happens in your model, on your subscription. That is why this tier is free: the expensive step isn't ours.
Install
Two ways in, depending on the client. Anything with a connector UI — claude.ai, Claude Desktop, ChatGPT, Cursor — takes the endpoint URL and runs a browser sign-in (OAuth 2.1 with dynamic client registration; nothing to configure). Claude Code takes a bearer token in a header instead, which makes the install one line.
claude.ai, Claude Desktop, and other connector UIs — paste as the server URL
https://pro.twospouts.com/api/mcp
The client discovers the authorization server from the endpoint, registers itself, and opens a consent screen. Approve it while signed in to Search Term Pro and the connection is live; revoke it later from /claude.
Claude Code — sign in at /claude to mint a token (shown once), then
claude mcp add --header "Authorization: Bearer stp_mcp_YOUR_TOKEN" \ --transport http search-term-pro https://pro.twospouts.com/api/mcp
Either way you need a Search Term Pro account and at least one connected Google Ads account. You do NOT need a Google Ads API developer token, an OAuth client, or anything deployed — the server is hosted and holds the Ads credentials for you.
Protocol
| Endpoint | https://pro.twospouts.com/api/mcp |
| Transport | Streamable HTTP (JSON-RPC 2.0 over POST, single JSON response) |
| Protocol version | 2025-06-18 |
| Auth | Authorization: Bearer … — required on every request, initialize included — an unauthenticated request gets a 401 whose WWW-Authenticate names the resource metadata, which is how a client finds the sign-in. Either an OAuth 2.1 access token (metadata at /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server; PKCE S256, dynamic client registration, refresh tokens) or a long-lived stp_mcp_ token from /claude. |
| Capabilities | tools |
A tool that fails returns a normal result with isError: true and a readable message, not a JSON-RPC error — so the model can correct a bad account id or ask the user to connect an account instead of the turn aborting. JSON-RPC errors are reserved for malformed requests and unknown methods.
Tools
| Tool | Arguments | Returns |
|---|---|---|
| list_accounts | — | Connected accounts: account_id, name, website, customer_id, status, and any schedule. Call this first — every other tool needs an account_id from here. |
| connect_account | customer_id, brand, website — all optional | With no arguments, the ad accounts the workspace's stored Google sign-ins already reach. Connecting one of those needs no browser: call again with its customer_id plus brand and website. A sign-in URL comes back too, for accounts under a Google login the workspace doesn't have yet. |
| disconnect_account | account_id, confirm | Removes an account from the workspace, with its reviews, change log and schedule. Permanent, so confirm must repeat the account's name back. Google Ads itself is untouched. |
| get_search_terms | account_id, days (default 30) | The candidates worth reviewing, plus the context needed to judge them: brand terms, active keywords, converting queries, negative lists, and the operator's own notes and rules. |
| add_negatives | account_id, negatives[] (text, match_type, list, reason; optional campaign, ad_group), apply (default true) | Applies the negatives that pass verification. Returns what was applied and, for each rejection, the reason. A shared list is the default and preferred home; campaign or ad_group writes the negative at that level instead, for exclusions that should not apply account-wide. apply: false runs the same verification but saves the survivors as a proposed review for the user to approve in the dashboard, writing nothing. |
| audit_negative_lists | account_id | Findings on the negatives already in the account: ones blocking active keywords, lists attached to nothing, duplicates, and negatives another already covers. |
| get_account_settings | account_id | Brand, website, Background notes, Rules and the schedule — the same fields the dashboard edits. |
| update_account_settings | account_id, brand, website, background, rules (any subset) | Changes those fields; the dashboard's Prompts tab shows the result immediately and every future review reads it. Replaces a field wholesale — read first to append. |
| set_schedule | account_id, cadence (daily/weekly/monthly/quarterly/off), auto_apply, run_hour, day_of_week, day_of_month, timezone, fallback_after_days | Schedules reviews to run on the user's own Claude through the agent (below). Refuses until an agent has checked in. With auto_apply off the run saves a proposed review for the user to approve in the dashboard; fallback_after_days lets Search Term Pro run it on credits if their machine stays away. |
Scheduled reviews: the agent
The MCP server only answers when called, so a schedule needs something to call it. That something is the agent: a small, dependency-free script on a machine the user owns (their Mac, their VPS), installed with one command from /claude. Every hour it asks GET /api/agent/due whether a review is due; if one is, it runs `claude -p` with the review prompt under the user's own Claude Code login and reports back to POST /api/agent/done. On a Mac it is a launchd job, so a tick missed while the lid was shut runs on wake — a schedule is 'due', not 'fires at 06:00', and a review runs the first time the machine is on after it's due.
- Their Claude login never leaves their machine. The agent holds only the Search Term Pro token, which authorises these tools and nothing else.
- Their subscription pays for the thinking; the server does the deterministic parts as always.
- Schedules can't be saved — from the dashboard or set_schedule — until an agent has checked in, because a schedule nothing will run is a lie in the UI.
- Optional fallback: if the machine hasn't run a due review within N days, Search Term Pro runs it on the user's credits instead.
- Auto-apply off: the run saves a proposed review (add_negatives with apply: false); the user approves it on the review page, where applying is deterministic and free.
- Everything is visible in the dashboard: each review is labelled with who ran it (credits / your Claude / credits · fallback), and the account's Activity list shows every scheduled run — including failed agent runs and why.
Install (from /claude, with a token)
curl -fsSL https://pro.twospouts.com/agent/install.sh | sh -s -- stp_mcp_YOUR_TOKEN
What get_search_terms removes before you see it
The candidate set is not the whole window. Four filters run first, all free, and on a real account they remove most of it — a recent 7-day pull went from 131 terms to 4 candidates. What comes back is only what a human would actually have to think about:
- Terms with no spend — nothing to save.
- Terms that converted — converted traffic is not waste, whatever it looks like. Fractional conversions count.
- Terms matching the advertiser's own brand, resolved from the site rather than typed in.
- Terms an existing negative already blocks, checked per campaign against shared lists and campaign-level negatives.
Candidates come back sorted by cost, so the first ones you read are the ones worth the most.
What is checked before anything is written
add_negatives re-pulls the account and re-verifies every negative before writing. This runs regardless of which model proposed it, and it is why attempting an apply is safe: a bad proposal is rejected with a reason, not applied.
- Would it block a keyword the advertiser actively bids on? Rejected, naming the keyword.
- Would it block a query that has converted? Rejected, naming the query and its conversions.
- Is it already in one of the account's negative lists? Rejected as a duplicate.
- Does it match the advertiser's own brand? Rejected.
- Is it valid Google Ads negative text — length, word count, no stray brackets or quotes? Rejected if not.
The re-pull matters as much as the checks: a classification made hours or days ago is verified against the account as it is now, so a term that has converted since is caught rather than blocked.
A negative aimed at one campaign or ad group is checked against that scope rather than the whole account: it must not block a keyword or converting query in that campaign, and it is rejected if the same negative is already there. The account-wide duplicate check is deliberately skipped for these — the same text sitting in a shared list that doesn't reach the campaign is not a duplicate of a negative meant for it.
Everything applied is recorded in the account's change log with the resource name that undoes it, so any single negative can be reverted from the dashboard.
Scheduling, and its one real limit
set_schedule saves the cadence and whether to apply automatically, then returns the exact recurring-task prompt to create. It has to work this way: an MCP server is passive — it answers when called and cannot wake anything up. Nothing here can reach into your Claude and create a schedule for you.
So a scheduled review is a recurring task on your side, running on your subscription, and it runs when your Claude runs. If you want reviews that happen unattended at 6am whether or not anything of yours is awake, that is what the paid dashboard does — it runs the same analysis on our inference and bills credits for it.
How this differs from Google's own Ads MCP server
Google publishes an official MCP server for the Google Ads API (github.com/googleads/google-ads-mcp). It is a different kind of thing, and for a lot of work it is the right one — the two are complementary rather than competing.
| Google's official server | This server | |
|---|---|---|
| Shape | A thin wrapper over the Ads API: search (GAQL), get_resource_metadata, list_accessible_customers | Task-shaped: get the terms worth reviewing, add negatives, audit the lists |
| Writes | Read-only | Writes negatives, behind a deterministic safety gate |
| Hosting | You run it — pipx locally, or your own Cloud Run deploy | Hosted. Nothing to deploy |
| Credentials | Your own Google Ads developer token, OAuth client, and ADC | Sign in with Google once; the developer token is ours |
| Opinion | None. You write the GAQL and interpret the rows | Opinionated: it knows what a wasted term is and what makes a negative unsafe |
The practical differences are the developer token and the guard rails. Google's server needs one of your own, which means applying to Google and waiting for approval before you can call anything; this one uses ours, so installing is a token and a paste. And because Google's server is read-only and unopinionated, a model using it to manage negatives has to invent both the definition of waste and the safety checks — nothing stops it proposing a negative that blocks a keyword the advertiser is actively bidding on. That check is most of what this server is.
Use Google's for open-ended reporting and any part of the API this doesn't cover. Use this one for search-term work you intend to act on.
Cost
The connector is free and unmetered: unlimited accounts, unlimited reviews, no credits consumed. Nothing on this server calls a language model, so there is nothing for us to bill. The tokens your model spends reading the candidates and deciding on them are billed by Anthropic to your own plan, as with any other conversation.
A large account returns a large payload — a few thousand candidates plus context is tens of thousands of tokens in your context window. Narrow the window with the days argument if that matters to you.