# Connect an AI agent (MCP)

> Source: https://rankbull.io/docs/mcp (canonical) · This is the agent-readable Markdown mirror of RankBull's MCP contract.

RankBull ships a built-in MCP server. An agent with an API key can run the workspace without the dashboard: visibility, prompts, calendar, custom HTML import, publish, keywords, competitors, site health, indexing, analytics. 48 tools. Same plan caps as the UI.

## Endpoint

- URL: `https://dashboard.rankbull.io/api/mcp`
- Transport: MCP over streamable HTTP (stateless JSON-RPC POST)
- Auth: `Authorization: Bearer YOUR_KEY` (or `x-api-key: YOUR_KEY`)
- Keys: dashboard → Settings → API & MCP. Shown once at creation.

## Operating rules

- Start every session with whoami. It returns workspaceId, key permissions, and siteIds.
- Plan caps match the dashboard. generate_article spends one article credit and real LLM budget — confirm with the user, never loop it.
- import_article does not spend a credit. contentHtml must be publish HTML (p, h2–h6, ul/ol, a, img, table). Markdown is rejected.
- Prompt and competitor adds fail with the same plan-limit errors as the UI.
- request_index_inspect is capped at 20 per site per day.
- Keyword research reads are free and never call DataForSEO. add_target_keywords is hand-typed only. run_keyword_ideas spends one ideas credit — confirm first.
- get_prompt_answers returns receipts: real engine runs and cited URLs.
- Revoking the API key in Settings cuts the agent off immediately.

## Connect

Claude Code:

```
claude mcp add --transport http rankbull https://dashboard.rankbull.io/api/mcp \
  --header "Authorization: Bearer YOUR_KEY"
```

Cursor (`~/.cursor/mcp.json` or `.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "rankbull": {
      "url": "https://dashboard.rankbull.io/api/mcp",
      "headers": { "Authorization": "Bearer YOUR_KEY" }
    }
  }
}
```

Windsurf (`~/.codeium/windsurf/mcp_config.json`): use `serverUrl` instead of `url`.

OpenAI Codex (`~/.codex/config.toml`):

```toml
[mcp_servers.rankbull]
url = "https://dashboard.rankbull.io/api/mcp"
http_headers = { "Authorization" = "Bearer YOUR_KEY" }
```

GitHub Copilot (`.vscode/mcp.json`):

```json
{
  "servers": {
    "rankbull": {
      "type": "http",
      "url": "https://dashboard.rankbull.io/api/mcp",
      "headers": { "Authorization": "Bearer YOUR_KEY" }
    }
  }
}
```

Gemini CLI (`~/.gemini/settings.json`): use `httpUrl` instead of `url`.

Any other MCP client: POST to the endpoint with the Authorization header.

## Tool index (48)

- `whoami` — Who am I
- `list_sites` — List sites
- `get_site` — Get site
- `list_monitors` — List tracking monitors
- `visibility_overview` — Visibility overview
- `list_prompts` — List tracked prompts
- `get_prompt_answers` — Get prompt answers
- `list_opportunities` — List opportunities
- `top_cited_sources` — Top cited sources
- `list_citations` — List citations
- `list_competitors` — List competitors
- `add_competitor` — Add competitor
- `remove_competitor` — Remove competitor
- `list_serp_competitors` — List Google SERP competitors
- `get_site_health` — Get site health
- `search_performance` — Search performance (GSC)
- `traffic_overview` — Traffic overview (GA4)
- `bing_performance` — Search performance (Bing)
- `domain_overview` — Domain overview
- `indexing_summary` — Indexing summary
- `list_indexing_pages` — List indexing pages
- `request_index_inspect` — Request index inspection
- `list_articles` — List articles
- `get_article` — Get article
- `get_article_content` — Get article HTML
- `list_calendar` — List publish calendar
- `list_keyword_candidates` — List keyword candidates
- `get_job_status` — Get job status
- `import_article` — Import custom article
- `save_article_html` — Edit article HTML
- `schedule_article` — Schedule article
- `reschedule_article` — Reschedule article
- `cancel_article_schedule` — Cancel scheduled publish
- `publish_article` — Publish article now
- `generate_article` — Generate article
- `list_keywords` — List keyword research
- `list_keyword_clusters` — List keyword clusters
- `list_target_keywords` — List custom target keywords
- `add_target_keywords` — Add custom keywords
- `remove_target_keyword` — Remove custom keyword
- `list_ranked_keywords` — List ranked keywords (GSC)
- `list_keyword_ideas` — List last keyword ideas run
- `run_keyword_ideas` — Run keyword ideas
- `add_prompt` — Add tracking prompt
- `set_prompt_status` — Pause, activate, or archive a prompt
- `get_automation_policy` — Get automation policy
- `pause_automation` — Pause automation
- `resume_automation` — Resume automation

## Tools

### Identity & sites

Start here. whoami is free and returns workspaceId, permissions, and siteIds.

#### `whoami`

Confirms which RankBull workspace this API key belongs to, lists the key's permissions, and (when the key has sites:read) the workspace's sites with their siteIds. Free to call — start every session here to discover siteIds for the other tools.

- Permission: `any valid API key`
- Kind: read-only

**Arguments**

_No arguments._

#### `list_sites`

Lists every active site in the key's workspace with siteId, domain, status, language, and sitemap URL. Use the returned siteId for all site-scoped tools.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

_No arguments._

#### `get_site`

Fetches one site's full record: domain, status, language, sitemap URL, target countries, and analysis state. Prefer list_sites first when you only need a siteId.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami (e.g. "js7…").

### AI visibility

Scores, prompts, receipts, opportunities, and cited sources.

#### `list_monitors`

Lists AI-visibility monitors for a site (monitorId, cadence, status, engines). Needed before add_prompt.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

#### `visibility_overview`

Daily AI-search visibility aggregates for a site: visibility score, presence rate, share of voice, citation rate, and run counts per engine/brand/day. Rows cover the site's own brand and tracked competitors (distinguish by brandId). Free to call.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `days` (optional, number): Lookback window in days (default 30, max 90).

#### `list_prompts`

Lists the AI-search prompts tracked for a site (across its monitors) with promptId, text, intent, funnel stage, locale, and status. Pass a promptId to get_prompt_answers for the underlying runs and citations.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

#### `get_prompt_answers`

The receipts for one tracked prompt: real AI-engine runs (mention/citation counts per run) plus every source URL those answers cited, flagged is_your_domain when the citation points at the site. Use this to prove or debug why a prompt does/doesn't surface the brand.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `promptId` (required, string): Prompt ID from list_prompts.
- `days` (optional, number): Lookback window in days (default 30, max 90).

#### `list_opportunities`

Scored AI-visibility opportunities for a site (content gaps, competitor wins, citation gaps) with a recommended action each. Highest composite score first — start at the top.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `status` (optional, "new" | "saved" | "in_progress" | "completed" | "dismissed"): Filter by workflow status (default "new").

#### `top_cited_sources`

Domains AI engines cite most when answering this site's tracked prompts, with citation counts, engine spread, and share of total. Use it to find the publications and directories worth earning mentions on.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `days` (optional, number): Lookback window in days (default 30, max 90).
- `engineKey` (optional, string): Filter to one AI engine: chatgpt, claude, perplexity, gemini, aio (Google AI Overviews), ai_mode, or grok. Omit for all engines.
- `limit` (optional, number): Max domains to return (default 25, max 100).

#### `list_citations`

Citation rows for a site (host, title, url, engine). Optional host or engineKey filter. Read-only.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `days` (optional, number): Lookback days (default 30, max 90).
- `host` (optional, string): Filter to one host, e.g. dev.to
- `engineKey` (optional, string): Filter to one engine, e.g. chatgpt
- `limit` (optional, number): Max rows (default from API).

### Competitors

Same per-site caps as the dashboard.

#### `list_competitors`

Tracked competitor brands for a site (linkId, name, domain, source). Use linkId with remove_competitor.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

#### `add_competitor`

Adds a manual competitor brand. Enforces the plan's per-site competitor cap (same error as the dashboard).

- Permission: `sites:write`
- Kind: write (side effects)

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `displayName` (required, string): Competitor brand name.
- `primaryDomain` (optional, string): Competitor domain, e.g. example.com. Optional but recommended.

#### `remove_competitor`

Removes a competitor link from the site. Frees a cap slot. Use linkId from list_competitors.

- Permission: `sites:write`
- Kind: write (side effects)

**Arguments**

- `linkId` (required, string): Competitor link ID from list_competitors.

#### `list_serp_competitors`

Domains that rank in stored Google SERP snapshots where this site does not. Read-only — does not spend a SERP refresh. Empty until keyword research has stored SERPs. For AI-answer competitors use list_competitors.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

### Site health

Read the latest scan. Does not start a crawl.

#### `get_site_health`

Latest site-health scan: score, status, summary, pages scanned, and recent score trend. Read-only — does not start a new crawl.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

### Analytics

Search Console, GA4, and Bing — once connected in Settings → Analytics. GSC data lags ~2 days.

#### `search_performance`

Google Search Console performance for a site: total clicks, impressions, CTR, and average position, plus top queries and a daily series. GSC data lags ~2 days behind real time. Returns connected:false when Search Console isn't linked — point the user at Settings → Analytics.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `dateRange` (optional, "7d" | "28d" | "90d"): Reporting window: 7d, 28d (default), or 90d. GSC data lags ~2 days, so the window ends about 2 days ago.

#### `traffic_overview`

Google Analytics 4 traffic for a site: page views, sessions, users, engagement, top pages, traffic sources, and a daily series. GA4 data lags ~1 day. Returns connected:false when GA4 isn't linked — point the user at Settings → Analytics.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `dateRange` (optional, "7d" | "28d" | "90d"): Reporting window: 7d, 28d (default), or 90d. GSC data lags ~2 days, so the window ends about 2 days ago.

#### `bing_performance`

Bing Webmaster Tools performance for a site: total clicks, impressions, CTR, and average position, plus top queries, top pages, and a daily series. Bing data lags ~2 days. Returns connected:false when Bing isn't linked — point the user at Settings → Analytics.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `dateRange` (optional, "7d" | "28d" | "90d"): Reporting window: 7d, 28d (default), or 90d. GSC data lags ~2 days, so the window ends about 2 days ago.

#### `domain_overview`

One snapshot of the site: GSC clicks/impressions/avg position (if connected) and latest site-health score. Does not call DataForSEO. Pair with visibility_overview for AI scores.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

### Indexing

Indexing Doctor reads plus a Google inspect (20/site/day).

#### `indexing_summary`

Google index coverage for a site's watched pages: counts per bucket (indexed, not_indexed, pending, blocked, error, excluded), pages due for re-inspection, last inspection time, IndexNow key status, and the sitemap URL. Use list_indexing_pages to drill into one bucket.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

#### `list_indexing_pages`

Lists the watched pages for a site with their index bucket, Google verdict, coverage state, and crawl/inspection timestamps, newest first. Filter by bucket (e.g. not_indexed) to find the pages that need fixing; paginate with the returned continueCursor.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `bucket` (optional, "pending" | "indexed" | "not_indexed" | "blocked" | "error" | "excluded"): Filter to one bucket: pending (not inspected yet), indexed, not_indexed, blocked (robots/noindex), error (fetch failed), or excluded (canonical elsewhere). Omit for all pages.
- `limit` (optional, number): Max pages to return (default 50, max 100).
- `cursor` (optional, string): Pagination cursor from a previous call's continueCursor.

#### `request_index_inspect`

Pulls one watched page's next Google inspection to now. Same cap as the dashboard: 20 manual inspects per site per day. Get pageId from list_indexing_pages.

- Permission: `sites:write`
- Kind: write (side effects)

**Arguments**

- `pageId` (required, string): indexing_pages id from list_indexing_pages (field id).

### Articles & calendar (read)

List, read HTML, calendar, generation job status, keyword candidates.

#### `list_articles`

Lists a site's articles (latest 100) with articleId, target keyword, status (queued/drafting/draft/published/failed…), current title, and published URL.

- Permission: `articles:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

#### `get_article`

Fetches one article's full record plus its latest version summary (title, slug, version number). Includes scheduling and publish state.

- Permission: `articles:read`
- Kind: read-only

**Arguments**

- `articleId` (required, string): Article ID from list_articles.

#### `get_article_content`

Returns the current article body HTML plus title/slug/meta/status/schedule so an agent can edit or review without the dashboard.

- Permission: `articles:read`
- Kind: read-only

**Arguments**

- `articleId` (required, string): Article ID from list_articles or import_article.

#### `list_calendar`

Lists reserved/failed scheduled publishes for a site (keyword, when, articleId). Use schedule_article / reschedule_article / cancel_article_schedule to change it.

- Permission: `articles:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

#### `list_keyword_candidates`

Researched keyword ideas for a site ranked by opportunity score, with search volume, difficulty, intent, and — when an article already targets the keyword — its articleId/status so you don't generate duplicates.

- Permission: `keywords:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `limit` (optional, number): Max candidates to return (default 100, max 500).

#### `get_job_status`

Status of a background job (article generation, publish, site analysis) with its latest run and any error. Poll this after generate_article; generation typically takes a few minutes — do not poll more than once every 30 seconds.

- Permission: `jobs:read`
- Kind: read-only

**Arguments**

- `jobId` (required, string): Job ID returned by a queuing tool such as generate_article.

### Articles & calendar (write)

import_article is free (publish HTML only). generate_article spends a credit — confirm first.

#### `import_article`

Imports a finished article you (or the agent) wrote. Does NOT consume an article credit and does NOT call the RankBull writer. contentHtml MUST be publish HTML — the same article.content_html the publisher webhook sends (p, h2–h6, ul/ol, a, img, table). Markdown is rejected. Creates a review-status article you can schedule or publish_now.

- Permission: `articles:write`
- Kind: write (side effects)

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `keyword` (required, string): Target keyword for this article (2+ characters).
- `contentHtml` (required, string): Publish HTML body (not markdown). Same tags the webhook posts as article.content_html.
- `title` (optional, string): Article title. Defaults to the keyword.
- `slug` (optional, string): URL slug. Optional.
- `metaTitle` (optional, string): SEO meta title. Optional.
- `metaDescription` (optional, string): SEO meta description. Optional.

#### `save_article_html`

Replaces the body of a draft/review/scheduled/published article with publish HTML (same sanitizer as the webhook). Does not consume an article credit. Cannot edit an article that is still generating.

- Permission: `articles:write`
- Kind: write (side effects)

**Arguments**

- `articleId` (required, string): Article ID from list_articles or import_article.
- `contentHtml` (required, string): Replacement publish HTML (not markdown).
- `title` (optional, string): New title. Omit to keep current.
- `slug` (optional, string): New slug. Omit to keep current.
- `metaTitle` (optional, string): New meta title. Omit to keep current.
- `metaDescription` (optional, string): New meta description. Omit to keep current.

#### `schedule_article`

Puts a review/draft article on the publish calendar at scheduledForIso. Same overlap checks as the dashboard. Does not consume an article credit. If duplicate_warning is returned, confirm with the user and retry with duplicateOverrideConfirmed=true.

- Permission: `articles:write`
- Kind: write (side effects)

**Arguments**

- `articleId` (required, string): Article ID from list_articles or import_article.
- `scheduledForIso` (required, string): ISO-8601 datetime in the future, e.g. 2026-09-12T15:00:00.000Z
- `duplicateOverrideConfirmed` (optional, boolean): Set true only after the user confirms publishing a near-duplicate.

#### `reschedule_article`

Moves an already-scheduled article to a new ISO datetime. Fails if nothing is scheduled or it is currently dispatching.

- Permission: `articles:write`
- Kind: write (side effects)

**Arguments**

- `articleId` (required, string): Article ID from list_articles or import_article.
- `scheduledForIso` (required, string): ISO-8601 datetime in the future, e.g. 2026-09-12T15:00:00.000Z

#### `cancel_article_schedule`

Removes the article from the calendar and returns it to review. Does not delete the article.

- Permission: `articles:write`
- Kind: write (side effects)

**Arguments**

- `articleId` (required, string): Article ID from list_articles or import_article.

#### `publish_article`

Publishes a draft/review article immediately through the site's connected destination (same job as the dashboard Publish button). Does not consume an extra article credit. Poll get_job_status with the returned jobId.

- Permission: `articles:write`
- Kind: write (side effects)

**Arguments**

- `articleId` (required, string): Article ID from list_articles or import_article.
- `forcePublish` (optional, boolean): Set true only if the user confirms publishing a flagged duplicate.

#### `generate_article`

SPENDS MONEY: creates an article for the keyword and queues AI generation. This consumes one article credit from the workspace's plan and real LLM budget — always confirm with the user before calling, and never call it in a loop. Check list_articles/list_keyword_candidates first to avoid duplicate keywords. Generation quality mode comes from the site's settings. Returns articleId + jobId; poll get_job_status for progress.

- Permission: `articles:write`
- Kind: write (side effects)

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `keyword` (required, string): Target keyword/topic for the article (2+ characters).
- `maxBudgetUsd` (optional, number): Optional hard cap on LLM spend for this generation in USD. Omit to use the plan default.

### Keywords

Research reads never call DataForSEO. add_target_keywords is hand-typed only. run_keyword_ideas spends one ideas credit.

#### `list_keywords`

Read-only dump of this site's keyword research (candidates with volume, difficulty, opportunity, intent, linked article). Does NOT trigger DataForSEO. For keywords you typed in yourself, use list_target_keywords.

- Permission: `keywords:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

#### `list_keyword_clusters`

Read-only topic clusters from prior keyword research. Does NOT trigger DataForSEO.

- Permission: `keywords:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

#### `list_target_keywords`

Keywords you (or an agent) added by hand. Not a DataForSEO run.

- Permission: `keywords:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

#### `add_target_keywords`

Adds keywords you supply to the site's target list. Does NOT call DataForSEO and does not consume keyword-research credits. Duplicates are skipped.

- Permission: `keywords:write`
- Kind: write (side effects)

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `keywords` (required, string[]): One or more keywords to track (max 50 per call).

#### `remove_target_keyword`

Removes one hand-added target keyword by keywordId from list_target_keywords.

- Permission: `keywords:write`
- Kind: write (side effects)

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `keywordId` (required, string): target_keywords id from list_target_keywords.

#### `list_ranked_keywords`

Queries this site already ranks for in Google Search Console (clicks, impressions, CTR, position). Free. GSC lags ~2 days. If connected:false, tell the user to connect Search Console in Settings → Analytics.

- Permission: `sites:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

#### `list_keyword_ideas`

Read the latest keyword-ideas result. Does not call DataForSEO. Use run_keyword_ideas to spend a credit and fetch new ideas.

- Permission: `keywords:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

#### `run_keyword_ideas`

Spends one keyword-ideas credit (hosted: 1/2/4 per month on Starter/Growth/Scale) and calls DataForSEO once. Confirm with the user before calling. Seed with a topic or brand term. Poll list_keyword_ideas until status is ready or failed.

- Permission: `keywords:write`
- Kind: write (side effects)

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `seed` (required, string): Seed keyword or topic (min 2 characters).

### Prompts

Add, pause, or archive. Same plan caps as the UI.

#### `add_prompt`

Adds one prompt to a monitor. Enforces the plan's per-site prompt cap (same error as the dashboard). Default status is active.

- Permission: `sites:write`
- Kind: write (side effects)

**Arguments**

- `monitorId` (required, string): Monitor ID from list_monitors or list_prompts.
- `text` (required, string): The question to ask AI engines (8+ characters).
- `intent` (optional, "informational" | "commercial" | "transactional" | "navigational" | "jtbd"): Search intent. Optional.
- `status` (optional, "active" | "paused" | "archived"): Defaults to active. Archived does not count toward the cap.

#### `set_prompt_status`

Sets one prompt to active, paused, or archived. Un-archiving an archived prompt re-checks the plan prompt cap.

- Permission: `sites:write`
- Kind: write (side effects)

**Arguments**

- `promptId` (required, string): Prompt ID from list_prompts.
- `status` (required, "active" | "paused" | "archived"): New status.

### Automation

Pause and resume use the same policy as the dashboard.

#### `get_automation_policy`

Current automation policy for the site (mode, enabled, cadence, autoPause).

- Permission: `automation:read`
- Kind: read-only

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.

#### `pause_automation`

Pauses site automation (same as the dashboard pause). Generation/publish robots stop until resume_automation.

- Permission: `automation:write`
- Kind: write (side effects)

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
- `reason` (optional, string): Optional reason stored on the policy.

#### `resume_automation`

Clears auto-pause so automation can run again.

- Permission: `automation:write`
- Kind: write (side effects)

**Arguments**

- `siteId` (required, string): Site ID from list_sites or whoami.
