# CTRTool MCP guide for AI agents

CTRTool (https://ctrtool.com) runs real search-and-visit sessions for a customer's keywords and website on Google Search,
Bing Search, Google Maps and Google AI Overviews, with screenshot proof for every visit. Credits pay for visits.
This guide is for AI agents using the CTRTool MCP server. The human guide is https://ctrtool.com/mcp-guide.html.

## Connection

- Endpoint: `https://ctrtool.com/mcp` (Streamable HTTP, JSON responses, no session, no event stream).
- Auth: `Authorization: Bearer ctr_...` (the customer's personal API key), or the key in the path: `https://ctrtool.com/mcp/ctr_...`.
- Protocol versions: 2025-06-18, 2025-03-26, 2024-11-05. Methods: initialize, ping, tools/list, tools/call.
- A key acts as the customer: you can do what they can do in the app, with the same checks, and nothing more.
- Results come back as JSON in `structuredContent` (and the same JSON as text). Errors come back as a tool result with
  `isError: true` and a plain-language message: read it, fix the input, and try again.

## Workflow for bulk work

1. `get_account`: check the plan, credits and whether the email is confirmed.
2. `get_campaign_options`: campaign types, device modes, credit costs, every field and an example campaign.
3. `find_locations`: valid country codes, states and cities. Use these values exactly.
4. `create_campaigns` with `start_now: false` (the default): saves the campaigns paused. Up to 500 per call; send more calls for more.
5. Show the customer a short summary, then `set_campaign_status` with `status: "active"` to start them.
6. Later: `list_campaigns`, `get_campaign_report`, `get_position_history`, `list_recent_visits` to report results.

Before changing existing campaigns, read them with `list_campaigns` / `get_campaigns`. Prefer saving paused and confirming
with the customer before starting campaigns or archiving anything.

## Campaign fields

| Field | Rule |
|---|---|
| `name` | Required. 1-100 characters. |
| `campaign_type` | `google_search` (default), `bing_search`, `google_maps`, `google_ai_overview`. |
| `device_mode` | `desktop` (default) or `mobile` (mobile visits cost 2x credits). |
| `target_url` | Required. Full public URL of the page to find and visit. |
| `business_name` | `google_maps` only: exact business name shown in Maps (max 160). |
| `maps_place_url` | `google_maps` only: full Google Maps listing URL with a place ID or CID (not a short share link). |
| `keywords` | Required. Array of strings (or one per line), each up to 200 characters. No limit on the number. |
| `country` | Required. ISO code (US, GB, IN, ...) or `*` for any country. |
| `state`, `city` | Optional full names (need a country). Take them from `find_locations`. |
| `daily_limit` | Required. Daily search goal, 1-20000. |
| `total_seconds` | Required. Maximum total time on the site, 5-300. `total_seconds_min` optional (defaults to it). |
| `landing_seconds` | Required (unless a workflow is set). Landing-page time, 5-300. `landing_seconds_max` optional. |
| `internal_pages` | 0-5 extra pages per visit. Total time must cover the landing time plus 5 s per extra page. |
| `search_depth` | Multiple of 10, default 20. Maximum: 100 for Search, 50 for Maps desktop, 20 for Maps mobile and AI Overviews. |
| `delivery_mode` | `spread` (default, through the day), `interval` (use `interval_seconds`, 1-86400), or `immediate`. |
| `fallback_enabled` | `true` to run a second search with `fallback_prefix` / `fallback_suffix` added when the page is not found. |
| `daily_growth` | Optional with `spread`: `{ "start": N, "days": D }` grows the daily goal up to `daily_limit`. |
| `schedule` | Optional campaign window, e.g. `{ "timezone": "UTC", ... }`. Read an existing campaign for the format. |

To copy settings, read a campaign with `get_campaigns` and send its fields with your changes.

## Example calls

Create two campaigns, saved paused:

```json
{"name": "create_campaigns", "arguments": {"campaigns": [
  {"name": "Plumber Austin", "campaign_type": "google_search", "device_mode": "desktop",
   "target_url": "https://example.com/plumbing/", "keywords": ["emergency plumber austin", "austin plumbing repair"],
   "country": "US", "state": "Texas", "city": "Austin", "daily_limit": 20,
   "landing_seconds": 30, "total_seconds": 90, "internal_pages": 2, "search_depth": 20},
  {"name": "Plumber Dallas", "campaign_type": "google_search", "device_mode": "desktop",
   "target_url": "https://example.com/plumbing/", "keywords": ["emergency plumber dallas"],
   "country": "US", "state": "Texas", "city": "Dallas", "daily_limit": 20,
   "landing_seconds": 30, "total_seconds": 90, "internal_pages": 2, "search_depth": 20}
]}}
```

Start them: `{"name": "set_campaign_status", "arguments": {"ids": [41, 42], "status": "active"}}`

Change the daily goal and keywords of one campaign (only the fields you send change):
`{"name": "update_campaigns", "arguments": {"updates": [{"id": 41, "changes": {"daily_limit": 30, "keywords": ["plumber austin tx"]}}]}}`

## Rules

- All or nothing: in `create_campaigns` and `update_campaigns` every item is checked before any is saved. An error names
  the item (for example "Campaign 2 (Broken): ..."); fix that item and resend the whole batch.
- Starting campaigns needs a confirmed email, an active plan and enough credits (error: "Activate a plan with at least N
  available credits"). Save them paused instead and tell the customer.
- Editing a campaign or its daily goal pauses it (as in the app). Start it again with `set_campaign_status`.
- `archived` removes campaigns from the workspace; their delivery records stay. Confirm with the customer first.
- Up to 500 items per create/update call and 5,000 IDs per status call. No limit on campaigns or keywords overall.
- The customer must own the websites or have permission to send visits to them (https://ctrtool.com/terms.html).

## Common errors

| Message | What to do |
|---|---|
| 401 / "Add a CTRTool API key" | The key is missing or revoked: ask the customer for a new one (Your account > AI agents (MCP)). |
| "Choose a GEO with configured worker capacity" / "Choose a valid state" | Use `find_locations` and pick listed values. |
| "The website must resolve to a public internet address" | Use a complete, public URL. |
| "Confirm your email address" | The customer must confirm their email in the app. |
| "Minimum total site time must cover ..." | Raise `total_seconds` or lower `landing_seconds` / `internal_pages`. |
