# RankOrg - MCP for AI Agents

Connect Claude, ChatGPT, Codex, Cursor, Grok, and other MCP-capable agents to research keywords, inspect schedules, edit drafts, generate articles, and publish content.

## MCP endpoint

- MCP server: `https://rankit.blog/api/mcp`
- Transport: Streamable HTTP, stateless (POST only).
- Auth: OAuth 2.1 sign-in for assistants that support it (Claude, ChatGPT, Cursor); an organisation API key as `Authorization: Bearer` for everything else.
- Manage connections and keys: https://rankit.blog/app/integrations/assistants (owners and admins).

## Agent setup

### Claude

Add RankOrg as a custom connector (Settings, Connectors, Add custom connector) and paste the URL. Claude finds the sign-in on its own: you log in to RankOrg, pick the workspace and approve.

Remote MCP server URL:

```text
https://rankit.blog/api/mcp
```

Prompt:

```text
Use the RankOrg MCP server. First get my site context, then list my scheduled articles for the next 7 days as a concise list with title, publish date, status, slug, and article ID.
```

### ChatGPT

Add RankOrg as a custom connector (Settings, Connectors, Create) with the URL below and OAuth as the sign-in. ChatGPT sends you to RankOrg to log in and approve; no key to copy.

Remote MCP server URL:

```text
https://rankit.blog/api/mcp
```

Prompt:

```text
Use the RankOrg MCP server. First get my site context, then list my scheduled articles for the next 7 days as a concise list with title, publish date, status, slug, and article ID.
```

### Codex

Create an API key, export it as RANKORG_API_KEY, then add the RankOrg MCP server to your Codex config. Restart Codex afterwards so it loads the server and the variable.

zsh export:

```bash
echo 'export RANKORG_API_KEY="rko_your_key_here"' >> ~/.zshrc
source ~/.zshrc
```

bash export:

```bash
echo 'export RANKORG_API_KEY="rko_your_key_here"' >> ~/.bashrc
source ~/.bashrc
```

Codex config:

```toml
[mcp_servers.rankorg]
url = "https://rankit.blog/api/mcp"
bearer_token_env_var = "RANKORG_API_KEY"
```

Prompt:

```text
Use the RankOrg MCP server. First get my site context, then list my scheduled articles for the next 7 days as a concise list with title, publish date, status, slug, and article ID.
```

### Cursor

Add RankOrg to Cursor's MCP settings. With only a URL, Cursor opens the RankOrg sign-in in your browser. To use a key instead, add the Authorization header.

Cursor MCP config (sign in with OAuth):

```json
{
  "mcpServers": {
    "rankorg": {
      "url": "https://rankit.blog/api/mcp"
    }
  }
}
```

Cursor MCP config (API key):

```json
{
  "mcpServers": {
    "rankorg": {
      "url": "https://rankit.blog/api/mcp",
      "headers": {
        "Authorization": "Bearer rko_YOUR_KEY"
      }
    }
  }
}
```

Prompt:

```text
Use the RankOrg MCP server. First get my site context, then list my scheduled articles for the next 7 days as a concise list with title, publish date, status, slug, and article ID.
```

### Grok

If your Grok workspace supports custom MCP servers, add RankOrg as a remote server. Use the sign-in when it offers one, or send the key as a Bearer header. A key in the URL is not accepted.

Remote MCP server URL:

```text
https://rankit.blog/api/mcp
```

Bearer header:

```text
Authorization: Bearer rko_YOUR_KEY
```

Prompt:

```text
Use the RankOrg MCP server. First get my site context, then list my scheduled articles for the next 7 days as a concise list with title, publish date, status, slug, and article ID.
```

## Authentication

- **OAuth 2.1 (assistants):** give your assistant the server URL. It reads `/.well-known/oauth-protected-resource` (RFC 9728), registers itself (RFC 7591 dynamic client registration), and sends you to RankOrg to sign in with PKCE. You pick the workspace and approve; the connection acts for you with your role in that workspace, and you can disconnect it in Settings.
- **Organisation API keys:** an owner or admin creates a key in Settings, AI assistants (https://rankit.blog/app/integrations/assistants). It starts with `rko_`, is shown once, may have an expiry, and acts as the member who created it with that member's current role. Send it as `Authorization: Bearer rko_…`.
- A key or token in the URL (`?token=`) is not accepted; use the header.

```http
POST https://rankit.blog/api/mcp
Authorization: Bearer rko_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Accept: application/json, text/event-stream
```

## Roles and plans

- Any member can run the read tools. An editor, admin or owner can also approve a post that is waiting for approval. Only an admin or owner can edit a post, reschedule it, publish it now, merge posts, research keywords or write a post now (the same people who can do these in the app). Billing is never a tool.
- Tools that cause paid or publishing work need an active plan; without one they answer with a link to Plan & billing and change nothing.
- Every tool that works on a blog takes an optional `site` (its slug, address or id); leave it out when the workspace has one blog.

## Rate limits

Each connection or key may make 120 requests a minute, and a whole workspace 600 a minute across all its connections and keys. Over the limit you get HTTP 429 with `Retry-After: 60`. Repeated requests with bad credentials from one address are refused with 429 for a minute.

## Tools

Read tools inspect your workspace. Write tools change posts, schedules or the live blog, or cost money, so an assistant should confirm with you first.

| Tool | What it does | Access | Who can run it | Needs an active plan |
| --- | --- | --- | --- | --- |
| `listSites` | List the blogs (sites) of the connected RankOrg workspace, with their address and whether autopilot is on. | read | Any member | No |
| `getSiteContext` | Get the blog's business profile (name, website, niche, services), its addresses, autopilot, the workspace's plan and whether new paid work can run, and what your role lets you do. | read | Any member | No |
| `listContent` | List the blog's posts, as the Posts page does: posts that need you first, then the ones to come, then live ones. | read | Any member | No |
| `getContent` | Get one post: its HTML body, title, meta description, the short answer and takeaways, topic, target search, state and schedule. | read | Any member | No |
| `previewArticle` | See what a post will look like before it goes live: title, description, short answer, outline (its headings), the body as plain text, the hero image, and the address and date it will have. | read | Any member | No |
| `getTopicalMap` | Get the blog's topical map as the Topics page shows it: topics → subtopics → the searches each targets, with keyword difficulty (KD 0-100), searches a month and the post that answers each search; topics on the way or declined; site strength (Domain Rating by Ahrefs); and when research can next be asked for. | read | Any member | No |
| `getPublishStatus` | Where publishing stands: autopilot, whether new work can run, the build readers see now and any newer one on its way, a rebuild still waiting, each address and its state, the next scheduled post, and what needs the owner (posts held for review, posts that did not go live, DNS to fix). | read | Any member | No |
| `getSearchPerformance` | Google Search Console numbers for the blog: clicks, impressions, click-through rate and average position over the last `days` days (default 28), the same for the period before, and the top pages and searches. | read | Any member | No |
| `getBlogMetrics` | The blog's own reader numbers from RankOrg's privacy-friendly counter (not Google): page views, visitors, call-to-action clicks and calls over the last `days` days (default 28, up to 90), the posts read most and where readers came from. | read | Any member | No |
| `updateContent` | Edit a post's title, meta description and/or HTML body (give at least one). | write | Admin or owner | On some calls |
| `publishContent` | Publish a written post: `when: now` (default) puts it on the public blog within a minute, approving it if it waits for the owner's OK (a post due today moves to this post's day); it needs an admin or owner. | write | Editor, admin or owner | Yes |
| `rescheduleContent` | Move a post that is still to come to another day (`move` with `newDate` YYYY-MM-DD, a day after today in the blog's time zone; it keeps its time of day, and a post already on that day that is also still to come swaps with it), skip its day (`skip`: the day stays empty), or restore a skipped day (`unskip`). | write | Admin or owner | On some calls |
| `mergeContent` | Fold duplicate live posts into one: the post you keep (`primaryId`) stays, each merged post leaves the blog and its address becomes a permanent (301) redirect to the one you keep, so its search value passes on. | write | Admin or owner | Yes |
| `unmergeContent` | Undo a merge: put a merged post back on the blog at its own address and remove its redirect. | write | Admin or owner | Yes |
| `researchKeywords` | Start keyword research for the blog, with the same limits as the Topics page. | write | Admin or owner | Yes |
| `generateArticle` | Have the blog's next planned post written now instead of when its day comes near. | write | Admin or owner | Yes |
| `generateContentMap` | Generate a free topical content map for ANY website URL (not just the user's site): the site's niche, Domain Rating, and 4 content clusters → topics → subtopics → keywords with monthly search volume, estimated keyword difficulty (KD), search intent, and rankability for that site. | write | Any member | No |

### `listSites` (read)

List the blogs (sites) of the connected RankOrg workspace, with their address and whether autopilot is on. Other tools take a blog's slug, address or id as `site`.

Who can run it: Any member. Needs an active plan: no.

Arguments: none.

### `getSiteContext` (read)

Get the blog's business profile (name, website, niche, services), its addresses, autopilot, the workspace's plan and whether new paid work can run, and what your role lets you do. Call this FIRST so you know the business before researching, writing or scheduling.

Who can run it: Any member. Needs an active plan: no.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).

### `listContent` (read)

List the blog's posts, as the Posts page does: posts that need you first, then the ones to come, then live ones. filter: all, waiting (held for your OK), ready (written, scheduled), live, failed (did not go live), today (on today's calendar day); `scheduled` and `published` mean ready and live. Optional title search and page (20 a page).

Who can run it: Any member. Needs an active plan: no.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).
- `filter` ("all" | "waiting" | "ready" | "live" | "failed" | "today" | "scheduled" | "published", optional)
- `query` (string, optional): Words in the title.
- `page` (integer, optional)

### `getContent` (read)

Get one post: its HTML body, title, meta description, the short answer and takeaways, topic, target search, state and schedule. Use it before editing so you have the current content.

Who can run it: Any member. Needs an active plan: no.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).
- `id` (string, required): The post's id (from listContent).

### `previewArticle` (read)

See what a post will look like before it goes live: title, description, short answer, outline (its headings), the body as plain text, the hero image, and the address and date it will have.

Who can run it: Any member. Needs an active plan: no.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).
- `id` (string, required): The post's id (from listContent).

### `getTopicalMap` (read)

Get the blog's topical map as the Topics page shows it: topics → subtopics → the searches each targets, with keyword difficulty (KD 0-100), searches a month and the post that answers each search; topics on the way or declined; site strength (Domain Rating by Ahrefs); and when research can next be asked for.

Who can run it: Any member. Needs an active plan: no.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).

### `getPublishStatus` (read)

Where publishing stands: autopilot, whether new work can run, the build readers see now and any newer one on its way, a rebuild still waiting, each address and its state, the next scheduled post, and what needs the owner (posts held for review, posts that did not go live, DNS to fix).

Who can run it: Any member. Needs an active plan: no.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).

### `getSearchPerformance` (read)

Google Search Console numbers for the blog: clicks, impressions, click-through rate and average position over the last `days` days (default 28), the same for the period before, and the top pages and searches. Answers plainly when Search Console is not connected yet.

Who can run it: Any member. Needs an active plan: no.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).
- `days` (integer, optional): How many days back, up to 90 (default 28).

### `getBlogMetrics` (read)

The blog's own reader numbers from RankOrg's privacy-friendly counter (not Google): page views, visitors, call-to-action clicks and calls over the last `days` days (default 28, up to 90), the posts read most and where readers came from. Plus `results`: Seen, Visited and Acted (the owner app's three numbers) over the same number of days ending when Google's numbers do (2–3 days ago), with the period before. Pass `post` for one post's numbers.

Who can run it: Any member. Needs an active plan: no.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).
- `days` (integer, optional): How many days back, up to 90 (default 28).
- `post` (string, optional): One post's numbers instead of the blog's (from listContent).

### `updateContent` (write)

Edit a post's title, meta description and/or HTML body (give at least one). The address (slug) never changes. The body is cleaned to the blog's safe HTML (no scripts or event handlers). Editing a live post changes the public blog within minutes and needs an active plan.

Who can run it: Admin or owner. Needs an active plan: on some calls.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).
- `id` (string, required): The post's id (from listContent).
- `title` (string, optional)
- `metaDescription` (string, optional)
- `content` (string, optional): The full post body as HTML.

### `publishContent` (write)

Publish a written post: `when: now` (default) puts it on the public blog within a minute, approving it if it waits for the owner's OK (a post due today moves to this post's day); it needs an admin or owner. `when: on_schedule` only approves a post that waits, so it goes live on its own day; an editor may do that. Public and needs an active plan: only call it after the user confirms.

Who can run it: Editor, admin or owner. Needs an active plan: yes.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).
- `id` (string, required): The post's id (from listContent).
- `when` ("now" | "on_schedule", optional)

### `rescheduleContent` (write)

Move a post that is still to come to another day (`move` with `newDate` YYYY-MM-DD, a day after today in the blog's time zone; it keeps its time of day, and a post already on that day that is also still to come swaps with it), skip its day (`skip`: the day stays empty), or restore a skipped day (`unskip`). Restoring a written post whose time has passed publishes it now, which needs an active plan.

Who can run it: Admin or owner. Needs an active plan: on some calls.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).
- `id` (string, required): The post's id (from listContent).
- `action` ("move" | "skip" | "unskip", required)
- `newDate` (string, optional): For move: the new day, YYYY-MM-DD, after today.

### `mergeContent` (write)

Fold duplicate live posts into one: the post you keep (`primaryId`) stays, each merged post leaves the blog and its address becomes a permanent (301) redirect to the one you keep, so its search value passes on. Changes live addresses and needs an active plan: only call it after the user confirms which post to keep. unmergeContent undoes it.

Who can run it: Admin or owner. Needs an active plan: yes.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).
- `primaryId` (string, required): The live post to keep.
- `mergedIds` (string[], required): The live posts to fold into it (they become redirects).

### `unmergeContent` (write)

Undo a merge: put a merged post back on the blog at its own address and remove its redirect. Changes the live blog and needs an active plan: only call it after the user confirms.

Who can run it: Admin or owner. Needs an active plan: yes.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).
- `id` (string, required): The merged post to restore.

### `researchKeywords` (write)

Start keyword research for the blog, with the same limits as the Topics page. With `topic` (plain words, up to 80 characters): research that new topic and add it to the topical map (3 per blog every 30 days). Without it: re-check how hard each search is to rank for (once per blog every 30 days). Uses Google Ads and DataForSEO data; results show up in getTopicalMap when the research is done. Needs an active plan.

Who can run it: Admin or owner. Needs an active plan: yes.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).
- `topic` (string, optional): A new topic to research, in plain words.

### `generateArticle` (write)

Have the blog's next planned post written now instead of when its day comes near. It keeps its day on the calendar (it does not publish now) and may hold for the owner's OK. Once per plan cadence (weekly plan: once a week; daily plan: once a day) and needs an active plan.

Who can run it: Admin or owner. Needs an active plan: yes.

Arguments:
- `site` (string, optional): The blog to act on: its slug, one of its addresses (blog.example.com) or its id. Leave it out when the workspace has one blog (listSites shows them).

### `generateContentMap` (write)

Generate a free topical content map for ANY website URL (not just the user's site): the site's niche, Domain Rating, and 4 content clusters → topics → subtopics → keywords with monthly search volume, estimated keyword difficulty (KD), search intent, and rankability for that site. Reports are cached for 30 days per domain. Use for competitor research, prospect audits, or planning a new site. Free for every member: up to 3 new sites a day per person and per workspace (a site mapped in the last 30 days never counts). A new site takes about a minute: when the map is not ready within 45 seconds the answer has status "pending"; call the tool again with the same URL to get it. RankOrg API documentation: https://rankorg.com/docs/mcp#tool-generateContentMap.

Who can run it: Any member. Needs an active plan: no.

Arguments:
- `url` (string, required): Website URL or domain, e.g. example.com
