MCP server
Connect Claude, Claude Code, Cursor, ChatGPT or any MCP client to your Searchkeep endpoint, and the full reference for every tool, its parameters and defaults.
Endpoint and auth
Your Searchkeep deployment is an MCP server at:
https://searchkeep.example.com/mcp- Transport: Streamable HTTP, stateless. There are no long-lived sessions to keep alive, so it works well with Workers and with clients that reconnect often.
- Auth: OAuth 2.1, provided by Cloudflare Access’s Managed OAuth. Clients discover the authorization server, register themselves dynamically and use PKCE. You never paste a client ID or token: the client opens a browser, you sign in to Access, and it is connected.
- Readiness: until your deployment is
ready(see Security),/mcpreturns a JSON-RPC error (HTTP 403) that explains why it is locked and what to fix, instead of serving tools.
Connect a client
Replace searchkeep.example.com with your own custom domain. Menu names in AI clients change often; if a
label below has moved, look for “connectors” or “MCP servers”.
Claude
- In Claude (web or desktop), open Settings → Connectors and choose Add custom connector.
- Name it
Searchkeepand enter your/mcpURL. - Click Connect, sign in to Cloudflare Access in the browser window that opens, and approve.
- In a chat, enable the Searchkeep connector from the tools menu and ask a question.
Claude Code
claude mcp add --transport http searchkeep https://searchkeep.example.com/mcpThen run /mcp inside Claude Code, select searchkeep and authenticate. Add --scope user to make it available in every project, or --scope project to share it with your
team through .mcp.json (each person still signs in with their own Access identity).
Cursor
Add the server to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):
{
"mcpServers": {
"searchkeep": {
"url": "https://searchkeep.example.com/mcp"
}
}
}Cursor detects that the server needs OAuth and shows a Login button in its MCP settings.
ChatGPT
- In ChatGPT settings, open Apps & Connectors and turn on Developer mode under advanced settings.
- Create a connector with your
/mcpURL and OAuth authentication. - Sign in to Cloudflare Access when prompted, then enable the connector in a chat.
Other clients
Any client that speaks Streamable HTTP with OAuth works directly. For a client that only supports local
(stdio) servers, bridge it with mcp-remote, which handles the OAuth flow for you:
{
"mcpServers": {
"searchkeep": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://searchkeep.example.com/mcp"]
}
}
}Common parameters
Most tools share these parameters; parameter names are camelCase. All are optional unless a tool marks them required.
| Parameter | Type | Meaning |
|---|---|---|
site | string | Site id or domain from SITES. May be omitted when only one site is configured. |
start, end | YYYY-MM-DD | Inclusive UTC date range. end defaults to the latest day with data, start to end minus days plus one. |
days | number | Window length when start is omitted. Defaults to 28 (7 for ai_crawlers). |
source | "gsc" | "bing" | Search engine for search-performance tools. Defaults to gsc. Bing data is weekly, so Bing ranges are aligned to whole weeks. |
brand | "all" | "brand" | "non_brand" | Brand split using the configured brand regex. Defaults to all. |
limit | number | Maximum rows returned (at most 500). Results are aggregated on the server and capped. |
A call looks like this:
{
"name": "find_quick_wins",
"arguments": {
"site": "acme",
"start": "2026-08-01",
"end": "2026-10-31",
"brand": "non_brand",
"limit": 10
}
}Tool reference
All tools are read-only and annotated as such, except submit_sitemap, which is not registered unless you enable writes. Tools read from D1 unless noted (inspect_url, list_sitemaps with live, and the probes of setup_status call the source APIs),
so they are fast and do not spend your API quotas. Every result is JSON; errors come back as {"error": "…"} with an explanation.
list_sites
read-onlyLists every site in SITES with the sources connected for it (GSC, Bing, Cloudflare AI crawlers, Web Analytics) and, for each dataset, the first and last date stored in D1. Start here.
No parameters.
Try “Which sites are connected, and how far back does each one’s data go?”
query_performance
read-onlyThe general-purpose search analytics query. Groups GSC or Bing data by up to three dimensions and filters by query, page, country or device. It reads the history stored in D1, so ranges older than 16 months work once you have kept them. country and device cannot be combined with query/page dimensions, their filters or a brand split, because Search Console stores them separately; Bing has no country or device data and reports queries and pages separately.
| Parameter | Type | Description |
|---|---|---|
site | string | Site id or domain from SITES. Optional when only one site is configured; see list_sites. |
start | YYYY-MM-DD | First day of the range (UTC). Defaults to end minus days plus one. |
end | YYYY-MM-DD | Last day of the range (UTC). Defaults to the latest day with data. |
days | number | Window length in days when start is omitted (1–3660). Defaults to 28. |
source | "gsc" | "bing" | gsc (Google Search Console, daily) or bing (Bing Webmaster Tools, weekly; ranges are aligned to whole Monday–Sunday weeks). Defaults to gsc. |
dimensions | Array<"date" | "week" | "month" | "query" | "page" | "country" | "device"> | Up to three group-by dimensions. Omit for totals over the range. |
queryContains | string | Case-insensitive substring filter on the query text. |
pageContains | string | Case-insensitive substring filter on the page path. |
country | string | ISO 3166-1 alpha-3 country code, as GSC reports it (e.g. usa). |
device | "DESKTOP" | "MOBILE" | "TABLET" | Device filter. |
brand | "all" | "brand" | "non_brand" | Split queries by the brand regex (the site’s brandRegex or BRAND_REGEX). Defaults to all. |
orderBy | "clicks" | "impressions" | "ctr" | "position" | "date" | Sort key. Defaults to date when grouping by a single time dimension, otherwise clicks. |
order | "asc" | "desc" | Sort direction. Defaults to asc for a single time dimension, otherwise desc. |
limit | number | Maximum rows returned (1–500). Defaults to 50. |
Try “Top non-brand queries for /pricing over the last 90 days, by week.”
compare_periods
read-onlyCompares a period with the equal-length period right before it, or with a custom comparison period, and returns totals for both, the change in clicks and impressions, and the queries or pages that gained and lost the most clicks, with impression and position deltas.
| Parameter | Type | Description |
|---|---|---|
site | string | Site id or domain from SITES. Optional when only one site is configured; see list_sites. |
start | YYYY-MM-DD | First day of the range (UTC). Defaults to end minus days plus one. |
end | YYYY-MM-DD | Last day of the range (UTC). Defaults to the latest day with data. |
days | number | Window length in days when start is omitted (1–3660). Defaults to 28. |
source | "gsc" | "bing" | gsc (Google Search Console, daily) or bing (Bing Webmaster Tools, weekly; ranges are aligned to whole Monday–Sunday weeks). Defaults to gsc. |
dimension | "query" | "page" | What to compare. Defaults to query. |
compareStart | YYYY-MM-DD | Start of the comparison period. Set it together with compareEnd; otherwise the previous period is used. |
compareEnd | YYYY-MM-DD | End of the comparison period. |
brand | "all" | "brand" | "non_brand" | Split queries by the brand regex (the site’s brandRegex or BRAND_REGEX). Defaults to all. |
limit | number | Maximum rows returned (1–500). Defaults to 20. |
Try “What dropped the most this month compared to the same month last year?”
find_quick_wins
read-onlyFinds queries with an average position between minPosition and maxPosition and enough impressions to matter, together with the page that ranks for each (GSC), ranked by the extra clicks available if they reached position 3 at a benchmark CTR. These are usually the cheapest traffic to win.
| Parameter | Type | Description |
|---|---|---|
site | string | Site id or domain from SITES. Optional when only one site is configured; see list_sites. |
start | YYYY-MM-DD | First day of the range (UTC). Defaults to end minus days plus one. |
end | YYYY-MM-DD | Last day of the range (UTC). Defaults to the latest day with data. |
days | number | Window length in days when start is omitted (1–3660). Defaults to 28. |
source | "gsc" | "bing" | gsc (Google Search Console, daily) or bing (Bing Webmaster Tools, weekly; ranges are aligned to whole Monday–Sunday weeks). Defaults to gsc. |
minPosition | number | Lower bound of average position. Defaults to 8. |
maxPosition | number | Upper bound of average position. Defaults to 20. |
minImpressions | number | Ignore queries below this. Defaults to twice the number of days in the range (at least 10). |
brand | "all" | "brand" | "non_brand" | Split queries by the brand regex (the site’s brandRegex or BRAND_REGEX). Defaults to all. |
limit | number | Maximum rows returned (1–500). Defaults to 25. |
find_cannibalization
read-onlyFinds queries for which two or more of your pages each get a meaningful share of the impressions, with each page’s impressions, clicks, position and share, so you can consolidate or differentiate them. Search Console only, because it needs query × page data.
| Parameter | Type | Description |
|---|---|---|
site | string | Site id or domain from SITES. Optional when only one site is configured; see list_sites. |
start | YYYY-MM-DD | First day of the range (UTC). Defaults to end minus days plus one. |
end | YYYY-MM-DD | Last day of the range (UTC). Defaults to the latest day with data. |
days | number | Window length in days when start is omitted (1–3660). Defaults to 28. |
minImpressions | number | Minimum impressions per query/page pair. Defaults to 10. |
minShare | number | Minimum share of the query’s impressions a page needs to count (0.01–0.5). Defaults to 0.1. |
brand | "all" | "brand" | "non_brand" | Split queries by the brand regex (the site’s brandRegex or BRAND_REGEX). Defaults to all. |
limit | number | Maximum rows returned (1–500). Defaults to 25. |
find_ctr_anomalies
read-onlyCompares each query’s CTR with the median CTR of your own queries at the same rounded position (a benchmark curve when there are fewer than 8 samples) and returns the queries below expected × threshold, ranked by lost clicks. Only positions up to 20 count. Typical causes: a weak title or description, a rich result taking the click, or a mismatch with search intent.
| Parameter | Type | Description |
|---|---|---|
site | string | Site id or domain from SITES. Optional when only one site is configured; see list_sites. |
start | YYYY-MM-DD | First day of the range (UTC). Defaults to end minus days plus one. |
end | YYYY-MM-DD | Last day of the range (UTC). Defaults to the latest day with data. |
days | number | Window length in days when start is omitted (1–3660). Defaults to 28. |
source | "gsc" | "bing" | gsc (Google Search Console, daily) or bing (Bing Webmaster Tools, weekly; ranges are aligned to whole Monday–Sunday weeks). Defaults to gsc. |
minImpressions | number | Minimum impressions per query. Defaults to five times the number of days in the range (at least 50). |
threshold | number | Flag a query when its CTR is below the expected CTR times this (0.05–0.95). Defaults to 0.5. |
brand | "all" | "brand" | "non_brand" | Split queries by the brand regex (the site’s brandRegex or BRAND_REGEX). Defaults to all. |
limit | number | Maximum rows returned (1–500). Defaults to 25. |
find_content_decay
read-onlyCompares each page’s last 4 weeks of clicks with its best earlier 4-week window and returns the pages that fell by at least minDecline and are still trending down (not a single bad week), with peak and recent weekly clicks and the full weekly series.
| Parameter | Type | Description |
|---|---|---|
site | string | Site id or domain from SITES. Optional when only one site is configured; see list_sites. |
end | YYYY-MM-DD | Last day of the range (UTC). Defaults to the latest day with data. |
weeks | number | Weeks of history to analyse (8–104). Defaults to 16. |
minWeeklyClicks | number | Ignore pages below this many weekly clicks at their peak. Defaults to 5. |
minDecline | number | Minimum decline from the peak (0.05–0.95). Defaults to 0.3. |
source | "gsc" | "bing" | gsc (Google Search Console, daily) or bing (Bing Webmaster Tools, weekly; ranges are aligned to whole Monday–Sunday weeks). Defaults to gsc. |
brand | "all" | "brand" | "non_brand" | Split queries by the brand regex (the site’s brandRegex or BRAND_REGEX). Defaults to all. |
limit | number | Maximum rows returned (1–500). Defaults to 25. |
geo_funnel
read-onlyJoins, for each page, AI crawler requests (Cloudflare), citations in AI answers (imported from Bing AI Performance CSV exports), visits referred by AI assistants (Web Analytics) and search clicks and impressions (GSC plus Bing). Returns totals, the pages, and the most-crawled pages that were never cited (once citation data exists). Notes say which step has no data and why.
| Parameter | Type | Description |
|---|---|---|
site | string | Site id or domain from SITES. Optional when only one site is configured; see list_sites. |
start | YYYY-MM-DD | First day of the range (UTC). Defaults to end minus days plus one. |
end | YYYY-MM-DD | Last day of the range (UTC). Defaults to the latest day with data. |
days | number | Window length in days when start is omitted (1–3660). Defaults to 28. |
pageContains | string | Case-insensitive substring filter on the page path. |
sortBy | "crawled" | "cited" | "aiVisits" | "clicks" | Sort key, descending. Defaults to crawled. |
limit | number | Maximum rows returned (1–500). Defaults to 50. |
Try “Which pages do AI crawlers hit but never get cited?”
ai_crawlers
read-onlySummarises requests from known AI crawlers and assistants (GPTBot, ClaudeBot, PerplexityBot, OAI-SearchBot and others) recorded by Cloudflare, classified by operator and purpose (training, search, user). Includes HTTP status, so blocked or failing fetches stand out. Without dates, the window ends at the latest day of crawler data.
| Parameter | Type | Description |
|---|---|---|
site | string | Site id or domain from SITES. Optional when only one site is configured; see list_sites. |
start | YYYY-MM-DD | First day of the range (UTC). Defaults to end minus days plus one. |
end | YYYY-MM-DD | Last day of the range (UTC). Defaults to the latest day with data. |
days | number | Window length in days when start is omitted (1–3660). Defaults to 7. |
groupBy | Array<"crawler" | "operator" | "purpose" | "path" | "status" | "date"> | Up to three group-by dimensions. Defaults to ["crawler"]. |
crawler | string | Only this crawler, e.g. GPTBot. |
pathContains | string | Case-insensitive substring filter on the path. |
limit | number | Maximum rows returned (1–500). Defaults to 50. |
Try “Are any AI crawlers getting 4xx or 5xx responses from us?”
inspect_url
read-onlyCalls the Search Console URL Inspection API in real time: verdict, coverage and indexing state, robots.txt and fetch state, last crawl time, the canonical chosen by Google and the one you declared, mobile usability and rich results verdicts, and a link to the full report. Google allows about 2,000 inspections per property per day.
| Parameter | Type | Description |
|---|---|---|
site | string | Site id or domain from SITES. Optional when only one site is configured; see list_sites. |
urlrequired | string | Absolute URL, or a path on the site (resolved against its domain). |
list_sitemaps
read-onlyLists the sitemaps Search Console knows for the property, with last submitted and downloaded times, pending state, errors, warnings and submitted vs indexed counts. By default it reads the snapshot the sync takes once a day.
| Parameter | Type | Description |
|---|---|---|
site | string | Site id or domain from SITES. Optional when only one site is configured; see list_sites. |
live | boolean | true refreshes the list from the Search Console API first. |
submit_sitemap
write · off by defaultThe only tool that writes to an external system. It is not registered unless ENABLE_WRITES is true, and it needs the service account to have Full (not Restricted) permission on the GSC property. It is annotated as a write so MCP clients ask before running it.
| Parameter | Type | Description |
|---|---|---|
site | string | Site id or domain from SITES. Optional when only one site is configured; see list_sites. |
sitemapUrlrequired | string | Absolute https URL of the sitemap. |
setup_status
read-onlyReports the readiness state with one line per check: the Access checks, configuration errors, which secrets are present (never their values), live probes of the Cloudflare token, Search Console and Bing, and the most recent sync. It also returns each site’s connected sources, the date range stored per dataset and recent sync runs. Agents call it to verify a deployment. Like every tool it is served only once the deployment is ready; before that, /mcp returns a JSON-RPC error naming the state, and the dashboard’s status page (/api/status) shows the readiness checks.
| Parameter | Type | Description |
|---|---|---|
probe | boolean | Call the source APIs to verify credentials. Defaults to true. |
Try “Run setup_status and tell me what is still missing.”
Questions to try
You do not need to name tools; the assistant picks them. A few good starting points:
- Which pages do AI crawlers hit but never get cited?
- Compare non-brand clicks this October with last October, by page.
- Which of our pages compete with each other for the same queries?
- Find queries where our CTR is far below what our position should give us.
- Which docs pages have been losing clicks for the last three months, and why?
- Is any AI crawler getting 404s or 5xx from us? Group by crawler and path.
- Inspect https://acme.com/pricing in Google and tell me if it is indexed.
- Which pages get the most visits from AI assistants, and do they also rank in search?