{
  "name": "Scopeweb",
  "version": "0.1.0",
  "base_url": "https://api.scopeweb.io",
  "mcp_url": "https://mcp.scopeweb.io/mcp",
  "expansion_version": 1,
  "score_version": 1,
  "laws": [
    {
      "id": "evidence-grade",
      "title": "PARENT LAW — every assertion carries its evidence grade, and absence of evidence never upgrades to a claim",
      "text": "The laws below are three instances of one rule. A failed lookup is not a verdict. A non-authoritative source is not an authority. An unregistered name is not a price. In each case something is MISSING — a response, a mandate, a quote — and the missing thing must never be silently promoted into a positive claim. Concretely: every result states what was measured (classification), when (last_verified_at), how it was obtained (source) and on whose authority (rdap_source). When you cannot tell, the answer is 'unknown', and 'unknown' is a real answer here rather than a failure to produce one. This costs a round trip. The alternative costs somebody a domain."
    },
    {
      "id": "unknown-is-never-available",
      "title": "Unknown is never available",
      "text": "A domain is reported 'unregistered' ONLY on an authoritative RDAP 404. If the registry lookup fails, times out or is throttled, the answer is 'unknown' — never 'unregistered'. 'pending' (the per-domain deadline elapsed) and 'unprobed' (generated but never checked) are likewise not registration claims. Re-confirm with check_live immediately before acting on any of it. See also: unregistered is not the same as purchasable. Corollary, learned the hard way: ONLY AN AUTHORITATIVE REGISTRY MAY SAY 'NOT REGISTERED'. Bootstrap aggregators answer 404 both for an unregistered name and for a TLD they cannot route, and the two are indistinguishable in the response — so a 404 from a non-authoritative source is downgraded to 'unknown'. Every result carries rdap_source ('authoritative' or 'aggregator') so you can see which you got."
    },
    {
      "id": "unregistered-is-not-purchasable",
      "title": "Unregistered is not purchasable",
      "text": "RDAP 404 proves no registration EXISTS. It proves nothing about whether the name can be bought, or at what price. Registry-reserved names, premium-tier names and registry holds all answer 404 and are reported 'unregistered' — yet cost far more than the TLD's list price, or cannot be bought at all. tld_base_price_usd is the TLD's standard rate, NEVER a quote for a specific name. 'purchasable' stays null until a registrar quote resolves it. Do not tell a user a name is available to buy, or what it costs, on the strength of a scan."
    },
    {
      "id": "three-state-resolves",
      "title": "resolves is three-state, and the states mean different things",
      "text": "undefined = never looked (check_live is registration-only) -> classification 'registered', nothing further is claimed. null = the DNS lookup FAILED -> 'unknown'. false = we looked and there is no A record -> 'dormant', a real measurement. The distinction exists so the API never reports a state it did not measure."
    },
    {
      "id": "freshness-is-always-stated",
      "title": "Every verdict carries when it was taken",
      "text": "Rows carry last_verified_at and source ('probe' = fetched now, 'cache' = a previous scan still inside its freshness tier, 'generated' = never checked, 'timeout' = deadline elapsed). Availability is cached for 15 minutes; everything else for 6 hours, because a stale 'active' is cosmetic and a stale 'available' sends someone to buy a domain that is gone."
    },
    {
      "id": "identifiers-do-not-change-meaning",
      "title": "Versioned identifiers",
      "text": "scan_id embeds the expansion algorithm version and liveness scores embed score_v. If the algorithm changes, an old scan_id is REJECTED with HTTP 409 and a re-scan instruction rather than silently returning a differently-generated page. Paginating across a deploy can never yield silent duplicates or gaps."
    },
    {
      "id": "pace-from-success",
      "title": "Rate-limit headers ride on success",
      "text": "x-ratelimit-limit / -remaining / -reset appear on successful responses, not only on 429. A client that learns its budget only at the moment of rejection cannot pace itself. On 429, retry-after is in seconds."
    }
  ],
  "idioms": [
    {
      "id": "sourcing-young-positives",
      "title": "Sourcing young positives for endpoint admission",
      "text": "Admission requires positives spanning registration ages, which is hard on a TLD whose short names all date to launch week — every desirable .me name is 2008-2009, for instance. Certificate Transparency logs solve it: a fresh registration almost always gets a TLS certificate within days, so CT surfaces recent registrations for any TLD. This is how .me's era-cutoff hypothesis was refuted rather than assumed — vibecode.me (2025) and aiagent.me (2024) answered 200 alongside vibe.me (2008), a 17-year span."
    },
    {
      "id": "progressive-deepening",
      "title": "Progressive deepening",
      "text": "suggest_names expands a concept into hundreds of candidates but probes only a budgeted slice per call; the rest come back as 'unprobed'. Re-querying the SAME scan_id spends the next call's budget on candidates not yet checked, and earlier results are served from cache. Coverage converges over roughly 3 calls. Re-query the same scan_id to walk deeper rather than widening limit."
    },
    {
      "id": "throttle-routing",
      "title": "Registry throttling is detected and routed around",
      "text": "Registries rate-limit per source IP, and a throttled RDAP response degrades to 'unknown'. Candidates are grouped by TLD and run in parallel ACROSS registries but gently within one, with a retry on 429/503. Without this, naive parallel scanning silently poisons a large fraction of results with false 'unknown' verdicts."
    }
  ],
  "latency": {
    "note": "Cold and warm are separate contracts. Honesty is preserved at the probe layer; speed comes from the cache layer.",
    "cold_p95_ms": 3500,
    "warm_p95_ms": 2500,
    "per_domain_deadline_ms": 3000,
    "detail": "A candidate exceeding the per-domain deadline returns 'pending'. Its scan continues server-side and still writes to the scans table, so the next call for that domain is a cache hit — a timed-out probe warms the cache rather than being wasted."
  },
  "rate_limits": {
    "drafts": {
      "max": 20,
      "per": "day",
      "scope": "user"
    },
    "scans": {
      "max": 60,
      "per": "hour",
      "scope": "token"
    },
    "note": "Counters are KV-backed and eventually consistent: a concurrent burst from one token can overshoot slightly. This is an abuse floor, not a billing meter."
  },
  "preview_hosting": {
    "host_pattern": "{draft_id}.scopedraft.dev",
    "note": "Draft previews are served from a SEPARATE registrable domain so user content never shares the brand domain's cookie scope or blocklist reputation. Previews are served noindex, with a Content-Security-Policy pinning form-action and connect-src to 'self' — the browser refuses credential exfiltration regardless of what the markup does. Static content screening is a floor on top of that, not the primary control."
  },
  "spending": {
    "law": "Agents propose. Only humans spend.",
    "note": "quote_domain is exposed to agents because pricing a name is research. THERE IS NO ORDER TOOL IN THIS SURFACE. No verb listed here can complete a purchase; the capability is absent rather than restricted, so there is nothing to obey and nothing to bypass. POST /orders does exist on the HTTP API and a bearer token reaches it -- we are not claiming otherwise -- but it is not exposed as a tool, and the confirm page is where a human types the domain to arm it. Registrations also sit behind a floor: an account must be 24 hours old and may place at most 5 orders a day. Both limits are disclosed in every quote's blockers rather than discovered at checkout."
  },
  "tools": [
    {
      "name": "create_draft",
      "http": {
        "method": "POST",
        "path": "/drafts",
        "body": "args"
      },
      "summary": "Store a draft website and get a persistent preview URL.",
      "description": "Use this when the user wants a page, site or landing draft for a name and there is nothing to edit yet. Store a draft website in the user's Scopeweb portfolio. Returns a persistent draft_id and a live preview_url. Drafts survive across conversations — use list_drafts in future sessions to resume work. Content is screened on every version; a flagged draft still returns normally here but its preview serves 451.",
      "annotations": {
        "title": "Save a draft site",
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "name_hint": {
            "type": "string",
            "description": "Working name for the project"
          },
          "description": {
            "type": "string",
            "description": "One paragraph on what this is"
          },
          "files": {
            "type": "array",
            "description": "Site files. index.html required for a browsable preview.",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "content": {
                  "type": "string"
                }
              },
              "required": [
                "path",
                "content"
              ]
            }
          }
        },
        "required": [
          "files"
        ]
      }
    },
    {
      "name": "update_draft",
      "http": {
        "method": "PUT",
        "path": "/drafts/{draft_id}",
        "body": [
          "files"
        ]
      },
      "summary": "Write a new immutable version of a draft (patch semantics).",
      "description": "Use this when a draft already exists and the user asks to change its copy, sections or layout. Call get_draft first if you do not have the current text. Update files in an existing draft. Acts as a patch: files you send are written as a new version, files you omit are carried forward unchanged. Previous versions are retained immutably. Returns the new version and preview_url.",
      "annotations": {
        "title": "Update a draft site",
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": false,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "draft_id": {
            "type": "string"
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "content": {
                  "type": "string"
                }
              },
              "required": [
                "path",
                "content"
              ]
            }
          }
        },
        "required": [
          "draft_id",
          "files"
        ]
      }
    },
    {
      "name": "list_drafts",
      "http": {
        "method": "GET",
        "path": "/drafts"
      },
      "summary": "List the user's drafts.",
      "description": "List all drafts in the user's Scopeweb portfolio with status, preview URLs and timestamps. Read-only. Cannot spend money. Call this at the start of a session to see existing work.",
      "annotations": {
        "title": "List drafts (read-only)",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {}
      }
    },
    {
      "name": "get_draft",
      "http": {
        "method": "GET",
        "path": "/drafts/{draft_id}",
        "query": [
          "path"
        ]
      },
      "summary": "Read a draft's metadata, file list, or one file's contents.",
      "description": "Use this before editing a draft you did not create in this conversation, so you are changing the text that actually exists rather than what you remember. Get a draft's metadata and file list, or a single file's content by passing path. Read-only. Cannot spend money. Use to resume work on a draft from a previous conversation.",
      "annotations": {
        "title": "Open a draft (read-only)",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "draft_id": {
            "type": "string"
          },
          "path": {
            "type": "string",
            "description": "Optional: return this file's content"
          }
        },
        "required": [
          "draft_id"
        ]
      }
    },
    {
      "name": "verify_ownership",
      "http": {
        "method": "POST",
        "path": "/domains/verify-{action}",
        "body": [
          "domain"
        ],
        "defaults": {
          "action": "start"
        }
      },
      "summary": "Prove domain ownership via a DNS TXT challenge.",
      "description": "Use this when the user claims a domain is theirs and you are about to act on that claim. Ownership asserted in chat is not ownership; this is how it gets proven. Prove the user owns a domain via a DNS TXT challenge. action 'start' returns the TXT record to publish; action 'check' verifies it. Verification is required before a domain appears in list_domains. A failed DNS lookup returns an error, never 'not owned'.",
      "annotations": {
        "title": "Prove you own a domain",
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": true
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "enum": [
              "start",
              "check"
            ],
            "description": "Defaults to 'start'."
          }
        },
        "required": [
          "domain"
        ]
      }
    },
    {
      "name": "list_domains",
      "http": {
        "method": "GET",
        "path": "/domains"
      },
      "summary": "The user's verified domain inventory with cached scan data.",
      "description": "Use this at the start of any portfolio, renewal or 'what do I own' question, before assuming which names the user holds. List the user's domain inventory: every domain with a verification challenge issued or completed, plus cached liveness, classification, title and expiry. Read-only. Cannot spend money. Includes scan_age_ms so a stale verdict is never mistaken for a fresh one. Use before suggesting the user buy anything.",
      "annotations": {
        "title": "List your domains (read-only)",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {}
      }
    },
    {
      "name": "suggest_names",
      "http": {
        "method": "POST",
        "path": "/browse",
        "body": [
          "query",
          "tlds",
          "limit",
          "offset",
          "scan_id"
        ]
      },
      "summary": "Suggest domain names for a concept. The first step when choosing a name.",
      "description": "★ START HERE when the user is choosing or inventing a name: it returns candidate names across a namespace. 'suggest names', 'help me name X', 'what should we call it', 'find me a domain for Y'. Give it a CONCEPT (a word or short phrase) and it generates and ranks candidates around it, classified by what is actually free. Read-only. Cannot spend money. (Renamed from browse_namespace on 2026-09-09: the old name described the mechanism, so assistants looking for a way to SUGGEST NAMES never matched it and invented candidates from their own heads instead.) Use scan_namespace instead when you already have an explicit list. Follow this with score_name to rank, and check_live LAST as the purchase gate. Re-querying the same scan_id walks deeper into the namespace and converges over about 3 calls. Unregistered does not mean purchasable: reserved and premium names answer the registry the same way. Only a registrar quote settles a price. PRICES ARE IDENTICAL FOR ALL BUYERS REGARDLESS OF BUDGET. A budget changes which candidates are recommended and how they are ordered; it never changes what anything costs.",
      "annotations": {
        "title": "Explore names (read-only)",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": false,
        "openWorldHint": true
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "description": "A word, name or idea, e.g. 'stowed'"
          },
          "tlds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Defaults to com/io/ai/app/dev/co."
          },
          "limit": {
            "type": "number",
            "description": "Candidates per page, max 50 (default 25)"
          },
          "offset": {
            "type": "number",
            "description": "Walk deeper into the expansion"
          },
          "scan_id": {
            "type": "string",
            "description": "Resume a previous browse; replaces query/tlds"
          }
        }
      }
    },
    {
      "name": "score_name",
      "http": {
        "method": "POST",
        "path": "/name/score",
        "body": [
          "domain",
          "domains"
        ]
      },
      "summary": "Grade candidate names on measurable, audited components.",
      "description": "Use this when the user is choosing between names, or asks whether a name is any good. Not for deciding availability, which is check_live. Grade candidate names on measurable properties of the string: length, syllables, pronounceability (the radio test), presence in a published English word list, edit distance to major brands, hyphens/digits, and TLD perception. Read-only. Cannot spend money. Every component returns its score, weight and basis so the number can be audited rather than trusted. Severe properties CAP the total instead of being averaged away — a name one edit from a major brand cannot score well however short it is. EXPLICITLY NOT a search-ranking prediction: exact-match-domain SEO value has been largely dead since Google's 2012 EMD update and nothing here forecasts how a name will rank. NOT a trademark search: brand_collision is string similarity to a published list, not legal clearance. Components that cannot be measured (zone rarity, trademark) are returned as unavailable with reasons and never estimated.",
      "annotations": {
        "title": "Grade a name (read-only)",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string",
            "description": "A single domain, e.g. 'forge.com'"
          },
          "domains": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Up to 10 domains; returned ranked best-first"
          }
        }
      }
    },
    {
      "name": "scan_namespace",
      "http": {
        "method": "POST",
        "path": "/scan",
        "body": [
          "candidates"
        ]
      },
      "summary": "Classify an explicit list of domains.",
      "description": "Classify an explicit list of candidate domains (max 25). Read-only. Cannot spend money. Use suggest_names instead when you have a concept rather than a list. Returns registered, resolves, serves, parked, classification and liveness for each.",
      "annotations": {
        "title": "Classify names (read-only)",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": true
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "candidates": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Full domain names, max 25."
          }
        },
        "required": [
          "candidates"
        ]
      }
    },
    {
      "name": "check_live",
      "http": {
        "method": "POST",
        "path": "/scan/live",
        "body": [
          "domains"
        ]
      },
      "summary": "Authoritative registration check. The purchase gate.",
      "description": "⛔ THE LAST GATE, NOT THE FIRST STEP. Use this to CONFIRM a name immediately before acting on it, after suggest_names has generated candidates and score_name has ranked them. It answers only 'is this registered', and it will answer just as confidently for a list you invented as for one this service helped you build -- so a clean result here says nothing about whether the name is any good, or whether a better one sits next to it. If none of the names you pass have been seen by suggest_names or scan_namespace, the response says so and points you back. Use this immediately before you recommend buying anything, and any time an earlier answer said 'unknown', 'pending' or 'unprobed'. Those are never registration claims. Real-time authoritative registration check for up to 10 domains via RDAP. Read-only. Cannot spend money. This is the final gate before recommending a purchase — never rely on cached or older scan results for buying decisions. It answers whether a registration EXISTS, not whether the name can be bought or at what price: reserved and premium names return 404 here too. Registration-only: it does not fetch the site, so it returns 'registered' rather than 'active' or 'dormant'.",
      "annotations": {
        "title": "Check availability (read-only)",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": true
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "domains": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "domains"
        ]
      }
    },
    {
      "name": "audit_domain",
      "http": {
        "method": "POST",
        "path": "/scan/audit",
        "body": [
          "domain"
        ]
      },
      "summary": "Fetch one domain's live, observable surface.",
      "description": "Use this when the user asks what is wrong with a site they already own, or wants a name they hold assessed rather than a new one found. Fetch a live domain's observable surface: HTTP status, redirect target, title, meta description, h1 headings and tech hints. Read-only. Cannot spend money. Use to compare what is actually deployed against a spec, or to check whether a domain serves anything at all.",
      "annotations": {
        "title": "Inspect a live site (read-only)",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": true
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string"
          }
        },
        "required": [
          "domain"
        ]
      }
    },
    {
      "name": "set_budget",
      "http": {
        "method": "PUT",
        "path": "/projects/{project_id}/budget",
        "body": [
          "period",
          "amount_cents"
        ]
      },
      "summary": "Set a project's budget. Echoes carry and headroom immediately.",
      "description": "Use this when the user states a spending limit for a project. It records their ceiling; it never authorises a purchase. Set a monthly or yearly budget for a project, in cents. Records a number you chose. Cannot spend money and cannot buy anything. The response comes back with the project's CURRENT CARRY and HEADROOM already computed, because a budget with no carry beside it is a number rather than information. Headroom is flagged `partial` whenever the project contains a domain whose renewal price is unknown — a renewal we cannot price is counted as unknown, NEVER as zero, so headroom is an upper bound in that case and real headroom is lower.PRICES ARE IDENTICAL FOR ALL BUYERS REGARDLESS OF BUDGET. A budget changes what is recommended and how results are ordered; it never changes what anything costs.",
      "annotations": {
        "title": "Set a project budget",
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": "string"
          },
          "period": {
            "type": "string",
            "enum": [
              "monthly",
              "yearly"
            ]
          },
          "amount_cents": {
            "type": "integer"
          }
        },
        "required": [
          "project_id",
          "period",
          "amount_cents"
        ]
      }
    },
    {
      "name": "get_finance",
      "http": {
        "method": "GET",
        "path": "/projects/{project_id}/finance"
      },
      "summary": "What a project costs to keep, and what is left.",
      "description": "Use this before recommending anything with a price, so the numbers you quote are this account's real ones rather than list prices. The fiduciary readout for a project: the budget, per-domain renewal costs, total known carry, how many domains have UNKNOWN renewal costs, and the remaining headroom. Read-only. Cannot spend money. Read `carry_unknown_count` before quoting headroom to anyone: domains held at another registrar renew at that registrar's price list, which we cannot see, so their cost is reported as unknown with `reason: foreign_registrar_pricing` — never guessed, never zero. Where we can price a transfer to us, it appears as `alternative.transfer_in_price` and is explicitly NOT their renewal price. When `partial` is true, headroom is an UPPER BOUND and the true figure is lower.",
      "annotations": {
        "title": "Read project finances (read-only)",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": "string"
          }
        },
        "required": [
          "project_id"
        ]
      }
    },
    {
      "name": "kb_search",
      "http": {
        "method": "POST",
        "path": "/kb/search",
        "body": [
          "query",
          "top_k"
        ]
      },
      "summary": "Search a curated corpus. Retrieval, not authority.",
      "description": "Use this to understand how something works before explaining it. Do NOT use it to decide anything: registry policy and registration status come from the dedicated tools, and they win. Semantic search over a small curated corpus: this system's own documentation and methodology, ICANN policy, and per-registry policy documents. Read-only. Cannot spend money. Returns passages with the source, its URL, and what that source may be cited FOR. THIS IS RETRIEVAL, NOT AUTHORITY. A vector search always returns its nearest neighbour, so it always looks confident; nearness is not correctness, and a passage being returned is not evidence that it answers you. Use it to understand how something works. Do NOT use it to decide anything: registry policy comes from GET /tld/:tld/policy, which carries per-field citations and marks unverified fields as unknown, and registration status comes from check_live via RDAP/WHOIS. If those two disagree with a passage here, they win.",
      "annotations": {
        "title": "Search the knowledge base (read-only)",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string"
          },
          "top_k": {
            "type": "integer"
          }
        },
        "required": [
          "query"
        ]
      }
    },
    {
      "name": "show_plans",
      "http": {
        "method": "GET",
        "path": "/plans"
      },
      "summary": "The plans, what never varies between them, and where the caller stands.",
      "description": "Use this when the user asks what a tier costs or bumps into a limit, so the answer is this account's actual plan rather than a guess. Show the available plans with what each includes, plus the caller's current tier and any allowance they have used. Read-only. Cannot spend money — it displays pricing and cannot start, change or cancel a subscription. Call this when a refusal names it as the recovery: an allowance is exhausted and the user is deciding what to do about it. PLANS GATE DEPTH, NEVER TRUTH — the registration verdict, its rdap_source and a domain's price are identical on every tier, and the response lists exactly what does not vary. Paying buys more of the picture, never a different answer about reality, so never present an upgrade as a way to get a better verdict.",
      "annotations": {
        "title": "Show plans (read-only)",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {}
      }
    },
    {
      "name": "quote_domain",
      "http": {
        "method": "POST",
        "path": "/quote",
        "body": [
          "domain"
        ]
      },
      "summary": "Price an unregistered name. Proposing is allowed; spending is not.",
      "description": "Use this when the user has chosen a name and wants the real price. It returns a confirm_url for the human to complete. It is NOT a purchase and you cannot make one. Get a real price for a name you believe is unregistered. Reads prices only. Cannot spend money — there is deliberately no tool in this server that can complete a purchase. Re-runs the authoritative gate first: a name that is registered, or whose status cannot be authoritatively determined, is never quoted. Returns total_cents, renewal_cents, an expiry, and a confirm_url. THE CONFIRM URL IS NOT A PURCHASE — it opens a page where a human must type the domain to authorise the charge. There is deliberately no tool to complete an order. An agent using this surface has no verb that spends, so nothing here needs obeying. Premium names are quoted at their real price or refused; they are never sold at the TLD base rate. An expired quote is re-quoted, never honoured.PRICES ARE IDENTICAL FOR ALL BUYERS REGARDLESS OF BUDGET — passing a project_id adds budget CONTEXT (does it fit, what is left after) and never changes the number.",
      "annotations": {
        "title": "Check a price (read-only)",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": false,
        "openWorldHint": true
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string"
          }
        },
        "required": [
          "domain"
        ]
      }
    }
  ]
}