MCP server

Ten tools over Streamable HTTP, with a bearer key. The connection block for every client, then a reference for every tool.

Domduck's MCP server is one of the three surfaces the product ships: a dashboard for people, an MCP server for agents, and an embeddable badge for other people's pages.

  • Endpoint POST https://domduck.com/api/mcp
  • Transport Streamable HTTP. Every answer is the response to a POST; this server never opens a stream.
  • Authentication Authorization: Bearer ddk_.... Create a key at https://domduck.com/settings/api-keys.
  • Rate limit 240 requests a minute, per key.
  • Scope Every tool answers for the account that owns the key, and only for domains that account already tracks.

There is also a public server with three read-only tools and no key at all.

Connect

Claude Code

claude mcp add --transport http domduck https://domduck.com/api/mcp \
  --header "Authorization: Bearer ddk_YOUR_KEY"

Cursor, VS Code, and any client with an mcp.json

{
  "mcpServers": {
    "domduck": {
      "type": "http",
      "url": "https://domduck.com/api/mcp",
      "headers": { "Authorization": "Bearer ddk_YOUR_KEY" }
    }
  }
}

Claude Messages API

{
  "mcp_servers": [
    {
      "type": "url",
      "url": "https://domduck.com/api/mcp",
      "name": "domduck",
      "authorization_token": "ddk_YOUR_KEY"
    }
  ],
  "tools": [{ "type": "mcp_toolset", "mcp_server_name": "domduck" }]
}

Send the beta header anthropic-beta: mcp-client-2025-11-20 with that request.

Raw JSON-RPC

curl -s https://domduck.com/api/mcp \
  -H "Authorization: Bearer ddk_YOUR_KEY" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Protocol

This server answers both protocol eras on one endpoint.

EraRevisionsShape
Modern2026-07-28Stateless. Every request declares its version in params._meta["io.modelcontextprotocol/protocolVersion"] and carries the matching MCP-Protocol-Version, Mcp-Method and Mcp-Name headers.
Legacy2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05The initialize handshake.

Which era a request is in is decided by the body, not the header. A request with no _meta version is legacy even when it sends MCP-Protocol-Version, because that header existed in 2025-06-18 too.

server/discover is implemented and answers on both paths, with or without _meta, so a probe does not have to know the era first.

Modern requests get the status codes that revision requires: 400 with -32022 for a version this server does not implement, 400 with -32020 when a header disagrees with the body, and 404 with -32601 for an unknown method. Legacy requests answer 200 throughout.

Errors

A tool that fails returns a normal result with isError: true and a plain sentence, not a JSON-RPC error. That is deliberate: a model can fix its own arguments and retry, and it cannot fix a protocol error. Read the message.

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "stripe.com is not tracked by this account. Use add_domain first, or list_domains to see what is."
      }
    ],
    "isError": true
  }
}

A domain this account does not track is always reported as untracked, never as non-existent. The distinction would turn this endpoint into a way to ask what other people are watching.

Tools

ToolWritesWhat it does
[list_domains](#list-domains)noEvery domain this account tracks, with its latest Domain Rating, Tranco rank, tags and observation count.
[get_domain](#get-domain)noThe latest reading from every source for one tracked domain: Domain Rating, Tranco rank, registration date, TLS certificate, DNS, AI crawler policy.
[get_history](#get-history)noOne reading per day for a domain and source, oldest first.
[get_ai_crawler_policy](#get-ai-crawler-policy)noWhich of the eight tracked AI crawlers a domain's robots.txt admits, and whether it publishes an llms.txt.
[add_domain](#add-domain)yesAdd a domain to this account and immediately read every cheap source once, so it has values rather than dashes.
[remove_domain](#remove-domain)yesTake a domain off this account.
[check_domain](#check-domain)yesRe-read every cheap source for one tracked domain right now, instead of waiting for the daily sweep.
[set_domain_flags](#set-domain-flags)yesChange how a tracked domain is filed.
[list_tags](#list-tags)noThis account's tags with the number of domains carrying each.
[list_sources](#list-sources)noWhat Domduck reads and what each source means, including the eight AI crawlers whose robots.txt policy is tracked.

A worked call

curl -s https://domduck.com/api/mcp \
  -H "Authorization: Bearer ddk_YOUR_KEY" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"get_history",
                 "arguments":{"domain":"stripe.com","source":"ahrefs_dr","days":30}}}'

The result's content[0].text is JSON as a string, which is how MCP carries a structured answer:

{
  "domain": "stripe.com",
  "source": "ahrefs_dr",
  "days": 30,
  "higherIsBetter": true,
  "points": [
    { "day": "2026-07-05", "value": 95 },
    { "day": "2026-07-06", "value": null },
    { "day": "2026-07-07", "value": 95 }
  ]
}

**null is a day with no reading, and it is not a zero.** Nobody looked that day. A zero is a value a domain can hold, and on a rank series where lower is better, reading a gap as zero turns every gap into the best result ever recorded. Draw a gap, not a point.

list_domains

Every domain this account tracks, with its latest Domain Rating, Tranco rank, tags and observation count. The place to start: most other tools take a domain from this list.

ArgumentTypeRequiredNotes
tagstringnoOnly domains carrying this tag.
starredbooleannoOnly starred domains.
sortname, dr, rank, observations, addednoSort key. Defaults to name.
dirasc, descno

get_domain

The latest reading from every source for one tracked domain: Domain Rating, Tranco rank, registration date, TLS certificate, DNS, AI crawler policy. Includes how many days of history exist per source and any recent source failures.

ArgumentTypeRequiredNotes
domainstringyesA domain name. Scheme, www and trailing dots are stripped.

get_history

One reading per day for a domain and source, oldest first. This is what the product is for: the series cannot be obtained anywhere else and cannot be backfilled. A null value is a day with no reading, which is a real state and not a zero.

ArgumentTypeRequiredNotes
domainstringyesA domain name. Scheme, www and trailing dots are stripped.
sourceahrefs_dr, tranco, openpagerank, cloudflare_radar, robots_txtyesWhich series. ahrefs_dr is Domain Rating 0-100, tranco is popularity rank where lower is better, robots_txt is the count of blocked AI crawlers.
daysintegernoWindow length. Defaults to 90.

get_ai_crawler_policy

Which of the eight tracked AI crawlers a domain's robots.txt admits, and whether it publishes an llms.txt. Three states per agent: allowed, disallowed, unspecified. Unspecified is NOT allowed, because a catch-all Disallow blocks an agent the file never names, so read effective for what is actually in force. Reads the last stored reading; it does not fetch the file.

ArgumentTypeRequiredNotes
domainstringyesA domain name. Scheme, www and trailing dots are stripped.

add_domain

Add a domain to this account and immediately read every cheap source once, so it has values rather than dashes. Unlimited domains, no tier cap. Re-adding one already tracked is a no-op that still refreshes it.

ArgumentTypeRequiredNotes
domainstringyesA domain name. Scheme, www and trailing dots are stripped.
tagsarray of stringnoOptional labels. Tags, not folders, so a domain can carry several.

remove_domain

Take a domain off this account. Its observation history is NOT deleted: it keeps accumulating and comes back intact if the domain is added again. Only the list changes.

ArgumentTypeRequiredNotes
domainstringyesA domain name. Scheme, www and trailing dots are stripped.

check_domain

Re-read every cheap source for one tracked domain right now, instead of waiting for the daily sweep. Use it after a change that should move a number, not as a polling loop: the metrics move over weeks.

ArgumentTypeRequiredNotes
domainstringyesA domain name. Scheme, www and trailing dots are stripped.

set_domain_flags

Change how a tracked domain is filed. Pausing keeps the domain and its history but drops it out of the daily sweep. Omitted fields are left alone.

ArgumentTypeRequiredNotes
domainstringyesA domain name. Scheme, www and trailing dots are stripped.
starredbooleanno
pausedbooleannoPaused domains keep their history but stop being re-read.
addTagsarray of stringno
removeTagsarray of stringno

list_tags

This account's tags with the number of domains carrying each. Tags are per-user and a domain can hold several. Takes no arguments.

list_sources

What Domduck reads and what each source means, including the eight AI crawlers whose robots.txt policy is tracked. Useful for interpreting get_domain output without guessing at field names. Takes no arguments.