mcp-server-canyougrab
CanYouGrab API
Domain availability lookup API with subscription billing, built on FastAPI + DNS (Unbound resolver).
Live services:
API:
https://api.canyougrab.itDeveloper portal:
https://portal.canyougrab.itAuth:
https://auth.canyougrab.it(Auth0 custom domain)
Architecture Overview
┌──────────────────────────────────────────────────────────────────┐
│ Developer Portal │
│ (Zudoku/React on portal.canyougrab.it) │
│ Usage Dashboard · API Keys · Pricing · API Reference (OAS) │
└──────────────────┬───────────────────────────────────────────────┘
│ Auth0 JWT (portal) / Bearer API key (API)
▼
┌──────────────────────────────────────────────────────────────────┐
│ FastAPI Backend (v5.0.0) │
│ api.canyougrab.it:8000 │
│ │
│ ┌─────────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ app.py │ │ keys.py │ │billing.py│ │ auth.py │ │
│ │ /check/bulk │ │ /keys │ │/billing │ │ API key + │ │
│ │ /usage │ │ CRUD │ │/stripe │ │ JWT auth │ │
│ │ │ │ rotate │ │ webhook │ │ │ │
│ └──────┬──────┘ └──────────┘ └────┬─────┘ └─────────────┘ │
│ │ │ │
└─────────┼────────────────────────────┼───────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Valkey (Redis) │ │ Stripe API │
│ Job queue + │ │ Subscriptions │
│ Rate limiting │ │ Webhooks │
└────────┬────────┘ └─────────────────┘
│
▼
┌─────────────────┐ ┌──────────────────────────────┐
│ RQ Worker │──DNS──▶ │ Unbound Resolver │
│ (worker.py) │ │ (dedicated droplet) │
│ ThreadPool(10) │ │ NS queries via VPC │
│ via RQ queue │ └──────────────────────────────┘
└─────────────────┘
┌──────────────────────────────┐
───────────────────▶│ PostgreSQL │
(auth, usage, │ API keys + Usage logs │
billing only) └──────────────────────────────┘Related MCP server: MCP Domain Availability Server
Directory Structure
zuplo/
├── backend/ # Python FastAPI backend
│ ├── app.py # Main API: /check/bulk, /usage, /health
│ ├── auth.py # API key auth (SHA-256) + Auth0 JWT auth (RS256)
│ ├── billing.py # Stripe checkout, portal, webhooks, card-on-file, usage details
│ ├── keys.py # API key CRUD: create, list, rotate, revoke (+ Turnstile)
│ ├── antifraud.py # Anti-fraud: Turnstile, device fingerprints, risk scoring
│ ├── email_utils.py # Email normalization + disposable email detection
│ ├── dns_client.py # DNS-based domain availability checking via Unbound
│ ├── queries.py # PostgreSQL queries: usage tracking, auth, billing
│ ├── valkey_client.py # Redis/Valkey job queue client (RQ-backed)
│ ├── rq_tasks.py # RQ task function: process_domain_job()
│ ├── worker.py # RQ worker process (ThreadPoolExecutor per job)
│ ├── migrations/ # SQL migrations
│ │ └── 001_free_tier_antifraud.sql
│ └── requirements.txt # Python dependencies
├── portal/ # Developer portal (Zuplo + Zudoku)
│ ├── config/
│ │ ├── routes.oas.json # OpenAPI 3.1 spec (public API documentation)
│ │ └── policies.json # Zuplo policies (empty — all routing is direct)
│ ├── docs/ # Zudoku documentation portal
│ │ ├── src/
│ │ │ ├── config.ts # API_BASE, Turnstile site key, Stripe PK
│ │ │ ├── UsageDashboard.tsx # Usage + billing dashboard component
│ │ │ ├── PricingPage.tsx # Plan selection + Stripe checkout
│ │ │ ├── PricingPlans.tsx # Pricing card grid component
│ │ │ └── CardSetupPage.tsx # Stripe Elements card-on-file for Free+
│ │ ├── public/ # Static assets (logos, banners, CSS overrides)
│ │ ├── zudoku.config.tsx # Portal config: theme, nav, Auth0, API key mgmt
│ │ └── package.json # Frontend dependencies (React 19, Zudoku)
│ ├── package.json # Workspace root (Zuplo v6, TypeScript v5)
│ └── README.md # Zuplo boilerplate (not project-specific)
├── mcp-server/ # MCP package for ChatGPT, Claude, and remote MCP clients
│ ├── pyproject.toml # MCP package metadata + version
│ ├── server.json # MCP registry/server metadata
│ ├── uv.lock # Locked MCP runtime dependencies
│ └── src/canyougrab_mcp/
│ ├── __init__.py
│ └── server.py # stdio + streamable-http MCP entrypoint
├── .github/workflows/
│ ├── deploy.yml # Production deploy (on tag push v*)
│ └── deploy-dev.yml # Dev deploy (on push to dev branch)
├── .claude/
│ └── launch.json # Local dev server config (port 9200)
└── package.json # Root workspace configAPI Endpoints
Public API (API key auth: Authorization: Bearer cyg_...)
Method | Path | Description |
|
| Check up to 100 domains. Long-polls until results ready (30s max). |
|
| Usage summary for the authenticated consumer. |
|
| Lightweight monthly + per-minute quota check. |
|
| Health check (no auth). |
Portal API (Auth0 JWT auth)
Method | Path | Description |
|
| Create new API key. |
|
| List user's API keys. |
|
| Rotate key (revoke old, create new). |
|
| Revoke (soft-delete) a key. |
|
| Create Stripe Checkout session. |
|
| Create Stripe Customer Portal session. |
|
| Create SetupIntent for Free+ card-on-file. |
|
| Verify card fingerprint and upgrade to Free+. |
|
| Check if user has a card on file. |
|
| Per-key usage breakdown for portal dashboard. |
|
| Verify Cloudflare Turnstile token. |
|
| Register device fingerprint (Fingerprint Pro). |
|
| Get risk assessment for authenticated user. |
|
| Run multi-signal risk assessment at signup. |
Internal / Webhook
Method | Path | Description |
|
| Multi-consumer usage breakdown. |
|
| Stripe webhook receiver (signature-verified). |
Core Request Flow
Domain Availability Check
Client FastAPI Valkey Worker Unbound
│ │ │ │ │
│ POST /api/check/bulk │ │ │ │
│ { domains: [...] } │ │ │ │
│─────────────────────────▶│ │ │ │
│ │── validate key ──────────│ │ │
│ │── check minute rate ────▶│ INCR ratelimit:id:min │ │
│ │── check monthly quota ──▶│ (PostgreSQL) │ │
│ │── record usage ─────────▶│ (PostgreSQL) │ │
│ │── create_job() ─────────▶│ HSET job:{uuid} │ │
│ │ │ RQ enqueue (with retry) │ │
│ │ │ │ │
│ │ │◀── RQ Worker dequeues ───│ │
│ │ │ │ │
│ │ │──── claim_job() ────────▶│ │
│ │ │ │── ThreadPool(10) │
│ │ │ │── check_domain_dns()
│ │ │ │── NS query ───────▶│
│ │ │ │◀── NOERROR/NXDOMAIN│
│ │ │◀── complete_job() ───────│ │
│ │ │ │ │
│ │◀─ poll get_job_status() ─│ │ │
│ │ (0.3s interval, 30s │ │ │
│ │ max timeout) │ │ │
│◀─────────────────────────│ │ │ │
│ { results: [...] } │ │ │ │Billing / Subscription Flow
User → Pricing Page → Select Plan → Auth0 login
→ POST /api/billing/checkout (JWT auth)
→ Find/create Stripe customer (linked by auth0_sub metadata)
→ Stripe Checkout Session created → redirect to Stripe
→ User pays → Stripe fires webhook
→ POST /api/stripe/webhook (HMAC-SHA256 verified)
→ checkout.session.completed → fetch subscription → get price ID
→ Map price to plan → UPDATE api_keys SET plan, lookups_limitSubscription Plans
Plan | Monthly Lookups | Per-Minute Rate Limit | Domains/Request | Price |
Free | 500 | 30/min | 30 | $0 |
Free+ | 10,000 | 100/min | 100 | $0 (card on file) |
Basic | 20,000 | 300/min | 100 | $10/mo |
Pro | 50,000 | 1,000/min | 100 | $20/mo |
Business | 300,000 | 3,000/min | 100 | $30/mo |
Authentication
API Key Auth (public API consumers)
Format:
Authorization: Bearer cyg_<token>Keys are SHA-256 hashed in the
api_keystable (plaintext never stored)Key prefix (
cyg_XXXXXXXX...) is stored for display in the portalFull key returned only once at creation time
Soft-delete on revocation (
revoked_attimestamp, not physically deleted)
JWT Auth (portal/dashboard)
Auth0 tenant:
dev-mqe5tavp6dr62e7uCustom domain:
auth.canyougrab.itAlgorithm: RS256 with JWKS validation (1-hour cache)
Audience:
https://api.canyougrab.itSocial logins: Google, Apple (Sign in with Apple)
Domain Availability via DNS
Domain availability is checked by querying a dedicated Unbound recursive DNS resolver running on a separate DigitalOcean droplet. The worker sends NS record queries over the VPC private network and interprets the response:
DNS Response |
| Meaning |
NOERROR + NS records |
| Domain is registered and delegated |
NXDOMAIN |
| Domain not in zone — probably available |
NoAnswer (NOERROR, no NS) |
| Registered but parked/undelegated |
SERVFAIL / Timeout |
| Ambiguous — check failed |
The Unbound resolver caches aggressively (7 days for registered domains, 5 minutes for NXDOMAIN) and queries TLD authoritative servers directly, avoiding public resolver rate limits.
Database Schema
PostgreSQL is used for authentication, usage tracking, and billing — not for domain lookups.
PostgreSQL Tables
Table | Purpose | Key Columns |
| User API keys |
|
| Daily usage aggregates |
|
| Per-minute usage aggregates |
|
| One free account per card |
|
| Device-based multi-account detection |
|
| Composite risk scoring |
|
Valkey (Redis) Data Structures
Key Pattern | Type | Purpose | TTL |
| Hash | Job status, domains, results | 1 hour |
| List | RQ job queue (managed by RQ) | — |
| Hash | RQ job metadata (retries, status) | Configurable |
| String | Per-minute rate limit counter (INCR) | 60s |
Infrastructure
Servers
Environment | Domain | Purpose |
Production |
| FastAPI + Worker |
Dev |
| FastAPI + Worker |
Portal |
| Zudoku static site |
Auth |
| Auth0 custom domain |
External Services
Service | Purpose |
DigitalOcean | Droplets (API servers, Unbound resolver), Managed PostgreSQL, Managed Valkey |
Cloudflare | DNS, CDN, SSL for canyougrab.it zone, Turnstile bot prevention |
Auth0 | User authentication, social login (Google + Apple), JWT issuance |
Stripe | Subscription billing, checkout, customer portal, webhooks |
GitHub Actions | CI/CD deployment pipelines |
Deployment Pipelines
Production Deploy
Trigger: Push a git tag matching v* (e.g., v1.0.5)
git tag v1.0.5 && git push origin v1.0.5Pipeline (.github/workflows/deploy.yml):
GitHub Actions triggers on
v*tag pushSSH into production server (
DEPLOY_HOSTsecret) usingDEPLOY_SSH_KEYBootstraps the target ref on the server (
git fetch+git checkout <tag>)Runs the repo-managed deploy script:
/opt/canyougrab-repo/scripts/deploy-host.sh
Dev Deploy
Trigger: Push to the dev branch
git push origin devPipeline (.github/workflows/deploy-dev.yml):
GitHub Actions triggers on push to
devSSH into dev server (
DEV_DEPLOY_HOSTsecret) using sameDEPLOY_SSH_KEYBootstraps the
devref on the server (git fetch+git checkout dev)Runs the repo-managed deploy script:
/opt/canyougrab-repo/scripts/deploy-host.sh
Repo-Managed Host Deploy Script
Both pipelines use a small inline SSH bootstrap to update /opt/canyougrab-repo to the target ref, then call scripts/deploy-host.sh from that checked-out revision. The repo-managed deploy script handles:
pip install -r requirements.txtfor backend dependenciesrsyncof backend, portal, and MCP source trees into their runtime directoriesReinstall or refresh the
mcp-serverpackage/runtime when the host also runscanyougrab-mcp.serviceRestart of FastAPI (uvicorn), worker, and MCP services via systemd
If /mcp is served by the same host as the API, backend-only deploys are not enough. The MCP service must be updated and restarted during the same deploy or the OAuth metadata and live MCP behavior can drift apart.
An existing /opt/deploy.sh can still be kept as a manual compatibility shim if desired, but the automated pipeline should treat the repo copy of scripts/deploy-host.sh as the source of truth.
Branching Strategy
Branch | Purpose | Deploys To |
| Production releases | Tagged → |
| Development/staging | Auto → |
Feature branches | In-progress work | No auto-deploy |
Environment Variables
Backend (required on servers)
# DNS Resolver (Unbound on dedicated droplet)
DNS_RESOLVER_HOSTNAME=unbound.canyougrab.internal # VPC internal hostname (resolved via socket.gethostbyname)
DNS_RESOLVER_PORT=53
DNS_QUERY_TIMEOUT=5.0 # Per-query timeout in seconds
# PostgreSQL (DigitalOcean Managed Database — auth, usage, billing only)
POSTGRES_HOST= # DB cluster hostname
POSTGRES_PORT=5432
POSTGRES_DB=canyougrab
POSTGRES_USER=canyougrab
POSTGRES_PASSWORD= # DB password
POSTGRES_SSLMODE=require
# Valkey / Redis (DigitalOcean Managed Database)
VALKEY_HOST= # Valkey cluster hostname
VALKEY_PORT=25061
VALKEY_USERNAME=default
VALKEY_PASSWORD= # Valkey password
# Auth0
AUTH0_DOMAIN=dev-mqe5tavp6dr62e7u.us.auth0.com
AUTH0_AUDIENCE=https://api.canyougrab.it
# Stripe
STRIPE_SECRET_KEY= # sk_live_... (prod) or sk_test_... (dev)
STRIPE_WEBHOOK_SECRET= # whsec_... (per-environment)
STRIPE_PRICE_BASIC= # price_... (live price ID for Basic plan)
STRIPE_PRICE_PRO= # price_... (live price ID for Pro plan)
STRIPE_PRICE_BUSINESS= # price_... (live price ID for Business plan)
# Cloudflare Turnstile (bot prevention on key creation)
TURNSTILE_SECRET_KEY= # 0x4AAAA... (from Cloudflare dashboard)
# Portal
PORTAL_URL=https://portal.canyougrab.it
# Worker
BATCH_CONCURRENCY=10 # Thread pool size for domain checks
VALKEY_QUEUE_NAME=canyougrab-jobs # RQ queue name (default: canyougrab-jobs)
# Monitoring (optional — only if monitoring stack is installed)
SLACK_ALERTS_WEBHOOK_URL= # Slack incoming webhook for Alertmanager
RQ_METRICS_PORT=9122 # Prometheus metrics exporter port
# Auto-scaler (optional — only on API host with autoscaler enabled)
DO_API_TOKEN= # DigitalOcean API token
DO_WORKER_SNAPSHOT_ID= # Snapshot ID for new worker droplets
AUTOSCALER_MIN_WORKERS=1
AUTOSCALER_MAX_WORKERS=5
AUTOSCALER_SCALE_UP_THRESHOLD=50
AUTOSCALER_SCALE_DOWN_IDLE_MINUTES=10GitHub Actions Secrets
Secret | Purpose |
| Production server IP |
| Dev server IP |
| SSH private key for both servers |
Local Development
Backend
cd backend
pip install -r requirements.txt
# Start API server
uvicorn app:app --reload --port 8000
# Start worker (separate terminal)
python worker.pyRequires DNS_RESOLVER_HOST pointing to an Unbound instance (or use 8.8.8.8 for basic testing), plus PostgreSQL and Valkey/Redis (local or tunneled to managed instances).
Portal
cd portal/docs
npm install
npm run dev # Starts Zudoku dev server (via zuplo)Or use the configured launch server:
# From repo root
npx zuplo dev # Starts on port 9200Portal dev server hardcodes API_BASE to https://api.canyougrab.it in portal/docs/src/config.ts. To develop against a local backend, change this to http://localhost:8000.
SEO: .it TLD Geo-Targeting Requirements
Because canyougrab.it uses a .it country-code TLD (Italy), all user-facing HTML (portal, docs) must include signals that the content targets English speakers:
<html lang="en">on every page.Hreflang tags in
<head>:<link rel="alternate" hreflang="en-US" href="{page_url}" /> <link rel="alternate" hreflang="en" href="{page_url}" /> <link rel="alternate" hreflang="x-default" href="{page_url}" />Canonical tag — Self-referencing
<link rel="canonical" href="{page_url}" />.All content in English — No Italian text on any page.
This applies to the portal (portal.canyougrab.it), API docs, and any other publicly rendered HTML.
Key Design Decisions
Live DNS lookups: Domain availability is checked via NS queries to a dedicated Unbound recursive resolver. This gives real-time results (no 24-hour zone file lag), works for any TLD automatically, and avoids the operational complexity of daily batch zone file loading. Unbound caches aggressively (7 days for registered domains) so repeated queries are ~1ms.
Nullable availability: DNS has more failure modes than a database lookup. When Unbound returns SERVFAIL or times out, the API returns
available: nullinstead of a potentially dangerous false positive. Consumers should treatnullas "could not determine."Job queue for bulk checks: The bulk endpoint uses RQ (Redis Queue) backed by Valkey to dispatch jobs with automatic retries (2 retries, 5s/30s backoff) and failed-job tracking. Workers parallelize DNS queries across a thread pool.
Long-polling: The bulk check endpoint holds the HTTP connection open (polling Valkey every 0.3s, up to 45s) rather than requiring clients to implement polling. Job results are stored in custom Valkey hashes (not RQ's built-in result storage) so the poll logic is independent of the queue framework.
API keys with SHA-256 hashing: Keys are hashed before storage (like passwords), so a database breach doesn't expose raw keys.
Stripe metadata linking: Stripe customers are linked to Auth0 users via
auth0_submetadata on the Stripe customer object, avoiding a separate mapping table.Usage double-tracking: Both daily (
usage_log_daily) and per-minute (usage_log_minute) usage are recorded for monthly quota and per-minute rate limiting respectively.No Zuplo gateway policies: The project originally used Zuplo as an API gateway but migrated to direct FastAPI (commit
3abef70). The Zuplo portal/docs framework is still used for the developer portal.
Processes on Servers
Core services (every host)
FastAPI (uvicorn): Serves all HTTP endpoints on port 8000
RQ Worker (worker.py): Processes domain check jobs from the RQ queue
MCP server (if
/mcpis hosted on this server): Serves remote MCP clients
Monitoring stack (API host, optional)
Prometheus (localhost:9090): Scrapes metrics, evaluates alert rules
Alertmanager (localhost:9093): Routes alerts to Slack
redis_exporter (localhost:9121): Exports Valkey metrics to Prometheus
RQ metrics exporter (localhost:9122): Exports queue depth, worker count, failed jobs
Grafana (localhost:3000, nginx-proxied): Dashboards for queue health
Auto-scaler (optional): Scales worker droplets up/down via DO API
Install the monitoring stack with: sudo bash scripts/setup-monitoring.sh
All active services on the host must be managed via systemd and restarted during deployments.
All automated deploys should route through scripts/deploy-host.sh, which is responsible for restarting whichever of these services are installed on the host.
Available Tools
3 toolscheck_domainsCheck Domain AvailabilityARead-onlyIdempotentInspect
Use this when the user wants to know whether one or more domains are available to register. Returns confidence, source, cache age, and ambiguous results when the lookup cannot be determined safely.
| Name | Required | Description | Default |
|---|---|---|---|
| domains | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/openWorld/idempotent, so no credit for that. Description adds value by disclosing return fields (confidence, source, cache age) and ambiguous result handling, which is behavior not visible elsewhere.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with usage trigger, then return details. No filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only lookup with one parameter, the description covers purpose, return values, and edge-case behavior (ambiguous results). It lacks deeper details on interpreting confidence/cache age and domain format, but annotations plus schema make the tool usable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The only parameter is 'domains' (array of strings), and description says 'one or more domains,' reinforcing plurality. However, schema coverage is 0% and description does not specify expected domain format (e.g., no protocol, TLD required), leaving room for ambiguity.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and resource: 'whether one or more domains are available to register.' This clearly distinguishes from siblings by focusing on registration availability rather than usage or general info.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Starts with 'Use this when...' giving explicit trigger context. It doesn't provide exclusions or compare with sibling tools, but the context is clear enough to avoid overlap with check_usage or get_domain_info.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
check_usageCheck API UsageARead-onlyIdempotentInspect
Use this when the user wants to see their CanYouGrab.it plan, usage, and remaining quota for the current billing period.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false. The description adds that the tool reports on the current billing period and includes plan/usage/quota, which is useful. It doesn't describe the return format or other behavioral details, but given the strong annotations, the description provides sufficient additional context without contradicting them.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single sentence that front-loads the usage condition and clearly communicates what the tool does. Every word earns its place, with no redundancy or extraneous information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only tool with no parameters and no output schema, the description is complete. It specifies the exact kind of data the user will see (plan, usage, remaining quota) and the time scope (current billing period), which fully aligns with the annotations. The description is sufficient for an agent to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, and the input schema is empty with 100% coverage. The description correctly does not attempt to explain any parameters, earning the baseline score of 4 for parameter semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('see') and clearly identifies the resource: the user's CanYouGrab.it plan, usage, and remaining quota for the current billing period. This distinguishes it from the sibling tools check_domains and get_domain_info, which focus on domains rather than account usage.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states when to use the tool ('when the user wants to see their plan, usage, and remaining quota'), providing a clear context. However, it does not mention alternatives or when not to use it, so it stops short of a full 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_domain_infoDomain Information LookupARead-onlyIdempotentInspect
Use this when the user wants WHOIS or registration information about a specific domain — such as who owns it, when it was registered, when it expires, or what nameservers it uses.
| Name | Required | Description | Default |
|---|---|---|---|
| domain | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the operation as read-only, idempotent, and non-destructive. The description adds useful behavioral context by specifying what kind of data the call returns (WHOIS/registration details, owner, expiry, nameservers). It does not discuss potential edge cases like data redaction or rate limits, but the annotation coverage lowers the burden.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single sentence that front-loads the usage trigger, gives concrete examples, and contains no filler or redundant content. It is compact and immediately useful.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter read-only lookup, the description covers the core purpose and likely return content well. However, it omits any mention of output shape and does not address how this tool relates to its siblings, which leaves slight room for ambiguity in more complex workflows.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has no parameter description (0% coverage), and the description only says 'a specific domain' without clarifying the expected format—e.g., whether it should be 'example.com', include protocol, or support IDN/punycode. The agent is left to infer valid input from the parameter name alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Use this when the user wants WHOIS or registration information about a specific domain' and enumerates concrete examples like ownership, registration date, expiry, and nameservers. This makes the action and resource clear and distinguishes it from sibling tools like check_domains, which sounds plural/batch-oriented.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states when to use the tool ('Use this when the user wants WHOIS...'), providing clear context. However, it does not explicitly mention excluded cases or point to alternative sibling tools, so it stops short of full when-not guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v1.1.1- First observed
check_domains - First observed
check_usage - First observed
get_domain_info
TDQS
Scored across 3 tools
check_domains targets domain availability, get_domain_info targets WHOIS/registration details, and check_usage targets the user's own account quota. Each tool clearly covers a distinct purpose with no overlapping boundaries.
All tool names follow a verb_noun pattern, but there is a mix of 'check_' and 'get_' prefixes. This is mostly consistent and readable, though a fully uniform verb would improve predictability.
Three tools is on the low end but appropriate for a focused domain lookup service. Each tool adds a distinct capability, and the small set is not lacking because the domain is narrow.
The set covers the core lookup workflows—availability checking, WHOIS retrieval, and account usage. Gaps like domain registration or purchase are likely outside the server's stated purpose, so it is reasonably complete.
Maintenance
Related MCP Connectors
Check domain name availability via RDAP. Single, bulk, and smart suggestions. No API key needed.
Domain intel for AI agents: RDAP registration, DNS, email deliverability, tech stack.
Domain intel for AI agents: RDAP registration, DNS, email deliverability, tech stack.
Domain availability search, WHOIS lookup, TLD pricing and registry rules from name.ai. Read-only.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables AI agents to check domain availability, perform batch domain lookups, and generate intelligent domain name suggestions across multiple TLDs using WHOIS integration.3MIT
- AlicenseAqualityDmaintenanceEnables AI assistants to check domain name availability for single or multiple domains using DNS, RDAP, and WHOIS lookups. It provides detailed registration status including registrar information and expiration dates while supporting bulk checks of up to 50 domains.2MIT
- FlicenseAqualityDmaintenanceEnables domain name availability checking through DNS and WHOIS lookups with confidence scoring. It supports searching across alternative TLDs and generating domain name variations for branding purposes.4-
- AlicenseNot gradedqualityNot gradedmaintenanceEnables users to check domain name availability across 877 TLDs, generate intelligent suggestions, and retrieve WHOIS registration information. It supports bulk domain checks and cross-TLD searches through natural language interfaces.0-