Skip to main content
Glama
minghsuy

ctscout

by minghsuy

ctscout-mcp-server

MCP server for ctscout.devnamed-entity attribution from Certificate Transparency logs (OV/EV only), with optional multi-signal corroboration on Pro. For mapping legal-entity digital footprints, sibling-domain discovery, and SAN-cohort analysis from LLM-driven workflows.

DV-only infrastructure (Let's Encrypt, ZeroSSL, cloud-native shops) is invisible to ctscout by design. See LIMITATIONS.md for what that means in practice.

Seven tools:

  • ctscout_search_company — find apex domains attributed to an organization by name

  • ctscout_search_company_batch — the same, for up to 10 organization names in one call

  • ctscout_lookup_domain — reverse-lookup the organization attributed to one or more domains

  • ctscout_submit_deep_dive — Pro only: queue an asynchronous multi-signal deep dive (see Deep dives)

  • ctscout_get_job — poll a deep dive and read its result

  • ctscout_lookup_lei — one LEI's record, or the LEIs under a legal name (see Research product)

  • ctscout_vendor_customers — a vendor's customer counts, and with a key the customer enumeration

The first three work over the public ctscout.dev /scan API (the batch tool wraps /scan/batch); the deep-dive pair wraps /jobs; the last two read the research product objects at /lei and /vendors. Free tier requires an API key (no email, no signup). A Pro key gets up to 25 rows, a 12-month window and 3,000 lookups a month on /scan, and can submit deep-dive jobs, which return a confidence_band per attribution with the named signals behind it (DNS brand tokens, RDAP, IP/ASN, homepage metadata, favicon). Visual brand verification (VLM) is not part of v1.

Not a cyber-risk-scoring tool. See LIMITATIONS.md for what ctscout is and isn't, the DV-cert coverage gap, and the corrections path.

Release history: see CHANGELOG.md.


Install

For Claude Code, Claude Desktop, Cursor, or any other MCP client. Two ways to connect: hosted (recommended, no install) or local npm (this package).

1. Get a free API key

Visit ctscout.dev and click "Get a free API key". Solve the Turnstile captcha. Copy the key (you can't recover it later — save it now).

All seven tools are hosted at https://ctscout.dev/mcp, including ctscout_lookup_lei and ctscout_vendor_customers. Nothing to install — point your MCP client at the URL with your API key as the X-API-Key header.

Claude Code (CLI):

claude mcp add \
  --scope user \
  --transport http \
  --header "X-API-Key: YOUR_KEY_HERE" \
  ctscout https://ctscout.dev/mcp

This stores the user-scoped connection in ~/.claude.json. Run claude mcp get ctscout, then open /mcp inside Claude Code to check that ctscout is connected. See the official Claude Code MCP guide.

Cursor — add the ctscout entry to mcpServers in ~/.cursor/mcp.json, preserving any existing servers and other settings:

{
  "mcpServers": {
    "ctscout": {
      "url": "https://ctscout.dev/mcp",
      "headers": { "X-API-Key": "YOUR_KEY_HERE" }
    }
  }
}

Check that ctscout is enabled and connected in Cursor's MCP settings. See Cursor's MCP documentation. Clients that only support legacy SSE can use https://ctscout.dev/sse with the same header.

Claude Desktop — use the local command configuration below. Desktop's remote connectors are a separate setup mechanism; this HTTP JSON is not a claude_desktop_config.json entry. See the official remote connector guide.

2b. Claude Desktop or local stdio clients

Claude Desktop can launch this published package as a local stdio process. It still calls https://ctscout.dev, so your network must allow that origin. Install the current Node.js LTS (Node.js 24 LTS is supported), including npm. The package supports Node.js 20 or newer. Verify both commands in a terminal:

node --version
npm --version

You do not need npm login or to clone this repository to use the public package. In Desktop, open Settings → Developer → Edit Config and add ctscout under mcpServers, preserving existing entries:

{
  "mcpServers": {
    "ctscout": {
      "command": "npx",
      "args": ["-y", "ctscout-mcp-server"],
      "env": { "CTSCOUT_API_KEY": "YOUR_KEY_HERE" }
    }
  }
}

The Desktop config is at ~/Library/Application Support/Claude/claude_desktop_config.json on macOS or %APPDATA%\Claude\claude_desktop_config.json on Windows. Fully quit and restart Desktop, then check that the ctscout tools appear. If Desktop cannot find npx, use its full installed path for command; see the official local-server setup and troubleshooting guide.

For Claude Code's local stdio fallback:

claude mcp add --scope user --env CTSCOUT_API_KEY=YOUR_KEY_HERE \
  ctscout -- npx -y ctscout-mcp-server

The hosted endpoint is the authoritative MCP contract and is the recommended path. The stdio package is a compatibility adapter over the same ctscout.dev API for clients that cannot connect to remote MCP servers yet.

Hosted and stdio expose the same seven public tool names and use the shared implementation, including the names-only ctscout_search_company_batch contract (1–10 organization names, ordered partial-failure results) and the deep-dive pair. The hosted adoption shipped in ctscout-worker#417, with production verification of all seven tools. Hosted connects over HTTP using X-API-Key; stdio runs a local process using CTSCOUT_API_KEY.

Both transports advertise an outputSchema on every tool and return structuredContent on successful calls. Results include snapshot and snapshot_source, except the ctscout_submit_deep_dive receipt: it is a job handle, not a warehouse read, so those fields arrive on ctscout_get_job. On scan and job results, snapshot is the warehouse sync date and snapshot_source is "scan" | "unavailable"; on ctscout_lookup_lei and ctscout_vendor_customers it is the research export's version and the source vocabulary is "product" | "unavailable" (see Research product) — the two are different origins on different cadences, so they do not share a label. A failed call (401, 429, timeout) is an isError result with no structuredContent at all, so do not dereference snapshot on it.

Since X-API-Version 2026-09-05 every /scan and /scan/batch answer carries snapshot, the warehouse sync date it was read from (the sync runs daily), so the scan tools report snapshot_source: "scan" with that date; a deep-dive result carries its own (above). snapshot_source is "unavailable" and snapshot is null only when the API sent none, which it does before a fresh deploy's first sync or when its lookup failed. There is deliberately no client-side fallback (a separately fetched date is not tied to the scan's generation). A client must treat a missing or null snapshot as unknown, never as "current". The hosted-side change was ctscout-worker#343.

The stdio package accepts both MCP protocol eras: current clients can discover the server through the stateless 2026-07-28 server/discover flow, while existing clients keep the 2025 initialize handshake.

3. Use it

Start with one concrete request in your connected client:

Use ctscout to find domains attributed to Cloudflare. Show the organization, returned apex domains and snapshot date, and explain the OV/EV-only coverage.

Approve the tool call if prompted. The client should call ctscout_search_company and return a tool result with attributed domains or an explicit no-match result, plus snapshot information when available. Counts and dates vary with the dataset; an empty result is not proof that an organization has no domains. A 401 means the key needs checking; a 429 means its quota or guard was reached. Neither is a successful lookup.

You can then ask who a domain is attributed to or request a batch of company names. All seven tools listed above remain available through either transport.


Related MCP server: dns-intel

Free tier vs Pro tier

The authoritative definition is the product page, https://ctscout.dev/#tiers; this table mirrors it.

Free

Pro

Lookups

10 / day

3,000 / month included

Results

Top 5

Top 25

History window

Last 90 days

Up to 12 months

Deep-dive jobs (async)

20 / day

Data

Daily snapshot

Daily snapshot

Customer lists

First 100 rows

Full list

Price

$0

$49 / month — subscribe, the key comes by email within a day

Answer pages, research notes, open data and the LEI endpoints need no key at all. A lookup is one query to the API or the MCP tools; on both tiers the snapshot field names the daily sync date the answer was read from, and a deep-dive result carries confidence_band plus the named signals per attribution. The MCP server uses the same API key for both — your tier is determined by the key. If you hit the free quota, the tool returns a 429 error with an upgrade hint. Terms and privacy: https://ctscout.dev/terms/.

Pro is paid monthly by card through Stripe and the key is sent by hand to the email on the receipt. Need more than 3,000 lookups a month, or the whole dataset? Email pro@ctscout.dev.

What the Pro response looks like

/scan returns the (domain, organization, certs, subdomains) table on both tiers; Pro gets more rows and a longer window. A deep-dive job result replaces it with a richer attribution table you can defend in a meeting:

| Domain | Attributed to | Band | Signals | Evidence |
|---|---|---|---|---|
| `coalition.com` | Coalition Inc | ✅ verified | dns_txt_brand_token, og_site_name_match, rdap_registrant_match, +1 | verified via google-site-verification, atlassian-domain-verification |
| `imposter.com` | Coalition Inc | ⚪ insufficient | dns_txt_brand_token | verified via google-site-verification |

Bands map to confidence intervals (verified ≥ multiple strong independent signals, down to insufficient = no signals or signals disagree). The 🚫VLM-veto tag is reserved for visual brand verification overriding the positive signals; VLM does not run in v1, so it never appears yet. Full structured payload is available via response_format: "json".

Deep dives (Pro, async)

A deep dive is the full multi-signal attribution run executed by a batch worker rather than inside the request. It is asynchronous and Pro only (ctscout-worker#344, contract v1):

  1. ctscout_submit_deep_dive (POST /jobs) takes company_name and/or seed_domain (max 10, validated exactly like /scan) and returns a receipt immediately: {job_id, status: "queued", submitted_at, poll}. Nothing is attributed yet. A free key gets HTTP 403 with the API's upgrade text; the quota is 20 submissions per key per day (HTTP 429 over). Submitting is not idempotent — a retry queues a second job.

  2. ctscout_get_job (GET /jobs/{id}) returns {job_id, kind, status, submitted_at, started_at, finished_at, result?, error?}. status is queued | running | done | failed; result is present only when done, error only when failed. Polling is read-only and debits no quota. Job ids are scoped to the submitting key, so an id another key submitted answers 404 exactly like an unknown id.

Poll with backoff: wait about 30 s before the first poll, then back off toward 5 min between polls. The batch worker picks up queued jobs every few minutes and a deep dive can take several minutes to run.

The result is the deep-dive shape — domains with attributed_to and an enrichment object per domain, plus entity, run_metadata, source, signals_degraded — and three fields the batch worker adds: snapshot (the warehouse date the deep dive read from, present on every deep-dive result because the worker sets it), worker_version and signals_attempted. The markdown output renders the band / signals / evidence table under the job's status lines (a /scan never carries one); structuredContent carries the record with the top-level snapshot / snapshot_source resolved from result.snapshot (null / "unavailable" until the job is done). "Attributed" and "candidate" mean what they mean elsewhere in this package: an attribution is what the evidence names for a domain, a candidate is a name-similarity guess — deep dives report attributions with a confidence band, never bare candidates.

Not in v1: visual brand verification (VLM) — vlm_status stays pending or skipped and never vetoes a band; webhooks, cancellation, retries and result expiry are follow-ups on the Worker side.


Research product (LEI and vendor tools)

ctscout_lookup_lei and ctscout_vendor_customers read a different index from the /scan warehouse: precomputed objects published by the ctscout-research refresh and served by ctscout.dev at /lei and /vendors (ctscout-worker#336). The Worker classifies nothing on these routes — it reads one object and returns it — so both tools are free and debit no quota.

ctscout_lookup_lei

Takes exactly one of lei or name; passing both, or neither, is rejected before any network call.

  • { lei } (GET /lei/{lei}, ISO 17442: 18 uppercase alphanumerics plus 2 check digits) returns the entity record: legal_name and country from GLEIF, isin_count (ISINs mapped to the LEI), apex_count (apex domains attributed to it), the first_seen / last_seen observation window, sample_domains (a hash-chosen sample — not a ranking and not a complete list; apex_count is the total. The size is whatever the research export published: the product contract deliberately does not pin it, so read the array rather than assuming a number, and the markdown says how many it listed) and vendors_confirmed, which is a list of vendor slugs you can pass straight to ctscout_vendor_customers.

  • { name } (GET /lei?name=) returns { query, name_match, leis, lei_count, limit, truncated }. leis is capped at limit (20) while lei_count is the total before the cap, so on a truncated answer the two disagree by design — report lei_count, not leis.length.

name_match is also the discriminator between the two answer shapes: it is present on the by-name answer and absent from the record.

name_match: "none" does not mean the company has no LEI. The name index is keyed by the research normalizer's form of the GLEIF legal name; the two spellings the route tries (the lowercased, trimmed query and its locale-suffix normalization) are not that normalizer, so a real entity can miss on a spelling. Retry with the exact GLEIF legal name, or look it up by LEI. The markdown output says this in place of the miss.

The answer also carries normalizer_mismatch: null when the API's normalizer is the one the index was keyed with, or { index, lookup } naming the two versions when they differ — then the normalized spellings are not the index's keys, and a none may be a spelling miss under this API rather than an absent name. The markdown says so in one line above the result; the JSON carries the field as sent, and the overflow envelope keeps the pair, bounded. An API that predates the check omits it.

ctscout_vendor_customers

Takes a vendor slug and, optionally, enumerate.

  • enumerate: false (the default, GET /vendors/{slug}) returns the free summary: vendor_name, vendor_apex (null when the vendor's brand token matches no label it certifies), the customers split, countries_top, co_use and sample_customers (a hash-chosen sample of the confirmed customers, sized by the export).

  • enumerate: true (GET /vendors/{slug}/customers) returns the enumeration: confirmed and candidates rows of { apex, attributed_to, lei }, plus counts and capped. This route needs an active ctscout.dev API key (any tier); without one the tool returns a clear 401 message that also points back at the keyless summary.

Free enumeration returns up to 100 rows from each list. free_slice records that allowance and truncated says whether the API shortened a list for the key's tier; Pro returns free_slice: null and the full published lists. This is separate from the research export's capped flag and MCP's truncation_note. Both response formats preserve those distinctions, including when a large answer collapses to its metadata envelope.

Candidates and confirmed are two different claims and are never summed. A candidate is an apex the vendor certified a hostname for — fan-out alone, which looks identical to an organization certifying hundreds of its own product sites. Confirmed is the DNS-confirmed subset: a vendor is confirmed when a hostname it certified resolves onto a domain it certifies and the customer's own www does not (or another organization certifies the apex). Because confirmed is a subset of candidates, adding the two double-counts; the markdown keeps them in separate tables and the JSON in separate fields.

Two more fields that read wrong if taken at face value: co_use[].confirmed counts this vendor's confirmed customers that the other vendor also certifies — a candidate there, not a mutual confirmation — and countries_top counts confirmed customers that resolved to an LEI only.

counts and capped describe the object the research build published: capped: true means the build itself kept a subset of the candidates. If this MCP server has to drop rows to stay under its own character limit, it writes a separate truncation_note and leaves counts and capped untouched, so a trimmed list never reads as a complete enumeration. The markdown and the structuredContent are two renderings of one bounded record — same rows, same "N listed of M" headings, one note — so the half you read can never describe a list the other half does not show.

Freshness and the 503

When a response is too large, it collapses through one envelope shared by every product object kind. That envelope knows for each field whether a list is a declared sample (partial against a total the record carries) or published complete, names any list it shortens together with the length the API sent, and never drops the provenance fields below — so a shortened list cannot read as a complete one, and a fallback cannot cost you the per-source provenance.

Both tools carry snapshot (the export's as_of) and snapshot_source ("product" when the API reported the version, "unavailable" when it did not — then snapshot is null and freshness is unknown, never "current"), plus snapshot_dates, the per-source provenance from the manifest (the dated GLEIF, ISIN, ELF and Wikidata snapshots and the PSL bundle each join read). The markdown renders both lines. This is a different clock from the /scan warehouse's daily sync: the research export is republished by its own refresh, so these answers move on that slower cadence.

No API key needed for the free routes

/lei and /vendors/{slug} are unauthenticated on ctscout.dev, so ctscout_lookup_lei and ctscout_vendor_customers without enumerate work with no CTSCOUT_API_KEY set at all — the request carries no X-API-Key header rather than an empty one. The server boots without a key and says on stderr which tools still work; the other five return the usual key error per call, and enumerate: true returns the same guidance it returns for an invalid key, without a network round-trip.

Until the refresh has published its first product, /lei and /vendors answer HTTP 503 with {"detail": "Research product not yet published…"}. Both tools surface that as a plain "not published yet" error naming the API's own detail — not as a server outage, and not as an empty result. A 503 carrying any other body is a different thing entirely (a Worker, proxy or upstream failure) and is reported as a temporary availability failure to retry shortly, never as an unpublished product.


What this is, and isn't

ctscout is a digital entity resolution tool — it maps apex domains to organizations attributed in their Certificate Transparency records, corroborated by DNS / RDAP / IP/ASN / homepage / favicon signals in a Pro deep dive.

It is NOT a cyber-risk quantification platform. It does not score security posture, predict breaches, or produce risk ratings. See LIMITATIONS.md for the full disclaimer, coverage gaps, and corrections path.

Coverage at a glance

ctscout's warehouse is built from OV/EV certificates only — the ones where the issuing CA validated the org's legal identity. DV-only infrastructure (Let's Encrypt, ZeroSSL, ACME-defaulting cloud hosts) is invisible to the warehouse.

The warehouse is strongest on: established US/EU enterprise, government, financial services, traditional infrastructure, defense, education.

The warehouse is weak on: modern cloud-native shops (most domains entirely behind Cloudflare/Vercel/Netlify), pre-launch / stealth-mode startups, anything that defaults to DV certs.

When ctscout_lookup_domain returns 0 results, the apex isn't in the warehouse — not necessarily that nobody owns it. See LIMITATIONS.md for the full coverage discussion; current warehouse size is published live at https://ctscout.dev/stats.


Local development

git clone https://github.com/minghsuy/ctscout-mcp.git
cd ctscout-mcp
npm install
npm run build

# Run the test suite (Vitest, no network)
npm test

# Maintainer-only, non-publishing release preflight
npm run release:check

# Run the server with no key: it boots and warns, and the free /lei and
# /vendors tools work; every other tool errors until a key is configured
node dist/index.js

# With a real key
CTSCOUT_API_KEY=your_key node dist/index.js

# Inspect with the official MCP inspector (browser UI)
npm run inspect

Test the protocol handshake without a real key

echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0.1"}},"id":1}' | \
  CTSCOUT_API_KEY=fake node dist/index.js

Should respond with the server's capabilities + tool registration. (Tool calls themselves require a real key.)


How it relates to ctscout.dev

This MCP server is a local stdio compatibility adapter over the public ctscout.dev /scan API. It does no auth-handling magic or caching — it translates MCP tool calls into HTTP requests and formats the response for an LLM consumer. Hosted https://ctscout.dev/mcp is the authoritative MCP surface.

If you're building your own integration in Python or another language, you can hit the same /scan endpoint directly. See ctscout.dev for curl examples.


License

MIT. See LICENSE.

The underlying ctscout service uses domain-scout (also MIT) for cert log analysis.

Release CLI compatibility

Maintainer release scripts support npm10 and npm12; npm10.9.8 and npm12.0.2 are exercised with the exact packed-artifact gate. npm12 requires Node ^22.22.2 || ^24.15.0 || >=26.0.0; a fresh Node24 LTS installation meets that requirement. The published MCP runtime still supports Node20+.

The scripts accept npm10's pack array and npm12's package-keyed object, plus registry scalar strings or singleton arrays. They reject multiple results, unsafe archive filenames, wrong packages/versions and missing or mismatched gitHead values. These compatibility checks do not change release authentication or exact-commit resume requirements. scripts/release.sh --check <version> expects an unpublished, reviewed version; it does not publish, tag, or create a GitHub release.

Available Tools

7 tools
ctscout_get_jobPoll a deep-dive job and read its resultA
Read-onlyIdempotent

Read the state of an asynchronous Pro deep dive submitted with ctscout_submit_deep_dive, via GET /jobs/{id}. Read-only and free to repeat: polling debits no quota.

Polling:

  • status is "queued" | "running" | "done" | "failed". Only "done" carries "result"; "failed" carries a short "error".

  • Back off: about 30 s before the first poll, then longer waits up to 5 min. A deep dive runs on a batch worker that picks up queued jobs every few minutes.

  • Pro only, and job ids are scoped to the submitting key: HTTP 404 means not your job or an unknown id.

Args:

  • job_id (string, required): the id returned by ctscout_submit_deep_dive.

  • response_format ('markdown' | 'json', default 'markdown'): output format.

Returns (on success, structuredContent follows the declared outputSchema; a failed call — 401, 403, 404, timeout — is isError with no structuredContent, so never dereference snapshot on a failed call):

  • In markdown: the job status lines; once done, the deep-dive attribution table (domain, attributed to, confidence band, signals, evidence) under a snapshot line. No /scan output carries this table.

  • In JSON, structured as: { "job_id": string, "kind": "deep_dive", "status": "queued" | "running" | "done" | "failed", "submitted_at": string, "started_at": string | null, "finished_at": string | null, "result": { // only when status is "done"; the deep-dive shape (see below), never returned by /scan "entity": {...}, "domains": [ { "domain": string, "attributed_to": string, "enrichment": {...}, "base": {...} } ], "run_metadata": {...}, "source": "live-enriched" | "cache-only", "signals_degraded": boolean, "snapshot": string, // warehouse date (YYYY-MM-DD) the deep dive read from — present, the batch worker sets it "worker_version": string, "signals_attempted": ... }, "error": string, // only when status is "failed" "snapshot": string | null, // copy of result.snapshot once done; null (unknown) before that "snapshot_source": "scan" | "unavailable" // 'scan' = the API response carried the date }

  • "Attributed" means the organization is what the evidence names for that domain, not an ownership claim. "Candidate" means a semantic name-similarity guess that is NOT an attribution. Deep dives return attributions with a confidence band (verified / likely / possible / insufficient), never bare candidates. When "signals_degraded" is true some signals errored: absence of their evidence is not evidence of absence.

  • Visual brand verification (VLM) is NOT included in v1: vlm_status stays "pending" or "skipped" and never vetoes a band.

Examples:

  • Use when: "Is my deep dive abc123 finished?" -> { job_id: "abc123" }

  • Don't use when: you have no job_id — submit first with ctscout_submit_deep_dive, or use the synchronous tools.

Corrections:

ParametersJSON Schema
NameRequiredDescriptionDefault
job_idYesThe job_id returned by ctscout_submit_deep_dive.
response_formatNoOutput format: 'markdown' for the job status and, once done, the deep-dive attribution table (band, signals, evidence); 'json' for the raw job record.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
kindNo'deep_dive'.
errorNoShort 'type: message' reason, present only when status is 'failed'.
job_idYes
resultNoPresent only when status is 'done'.
statusYes'queued' | 'running' | 'done' | 'failed'.
snapshotYesWarehouse/D1 sync date (YYYY-MM-DD) the answer was read from; the warehouse syncs daily. null when the API could not determine it.
started_atNo
finished_atNo
submitted_atYes
snapshot_sourceYes'scan' = the API response carried the date; 'unavailable' = it did not, snapshot is null and must be treated as unknown, never as current.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Extremely transparent about behavior: status values, polling intervals, error handling, no structuredContent on failure, snapshot_source semantics, attribution vs candidate meaning, signals_degraded implications, and VLM exclusion. Annotations are consistent and the description adds substantial behavioral detail.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well structured with clear headings (Polling, Returns, Examples, Corrections), but it is quite verbose. The length is justified by the complexity of the async result shape and semantics, so it remains effective despite being detailed.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Provides a complete picture for a complex asynchronous tool: full status lifecycle, output shape in both markdown and JSON, success/failure behavior, snapshot handling, attribution caveats, example usage, and correction channels. Nothing an agent needs to call and interpret this tool correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema already describes both parameters with high coverage, and the description reinforces job_id origin and response_format output differences. The extensive Returns section adds meaningful semantics beyond the schema, though the schema alone is already quite clear.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states it reads the state of an asynchronous deep-dive job and retrieves its result, with the specific verb 'Read' and the resource 'job'. It also distinguishes itself from submission and synchronous tools by naming them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit use and don't-use guidance: use when you have a job_id, don't use without one, and directs to submit or synchronous alternatives. Also explains polling backoff and when to expect completion.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ctscout_lookup_domainReverse-lookup organization for one or more domainsA
Read-only

Reverse-lookup ctscout.dev's domain-attribution warehouse: given one or more apex domains, return the organization(s) attributed to each.

Args:

  • domains (string[], required): apex domains to look up. Each between 3 and 253 chars. Max 10 per call. Examples: ["gs.com"], ["coalition.com", "at-bay.com"].

  • response_format ('markdown' | 'json', default 'markdown'): output format.

Returns (on success, structuredContent follows the declared outputSchema — the same one as ctscout_search_company; a failed call is isError with no structuredContent):

  • In markdown: a snapshot line, then a table of (domain, attributed to, cert count, subdomain count). Only domains found in the warehouse appear; a missing domain means no attribution in this snapshot, not a negative finding.

  • In JSON: the same structure as ctscout_search_company, including "snapshot" / "snapshot_source". The 'domains' array contains one entry per attributed (domain, org) pair found. Reverse lookups never return semantic candidates.

Examples:

  • Use when: "Who is gs.com attributed to?" -> { domains: ["gs.com"] }

  • Use when: "Are coalition.com and at-bay.com attributed to the same parent?" -> { domains: ["coalition.com", "at-bay.com"] }

  • Don't use when: You have a company name and want to enumerate its domains — use ctscout_search_company instead.

Coverage caveat:

  • Returns 0 results if domain isn't in the warehouse. Either the domain is not in our index, or no OV/EV certs have been issued for it. DV-only domains (Let's Encrypt etc.) are typically not indexed.

  • When a domain IS in the warehouse but the attributed org is a subsidiary (e.g. an Allianz brand domain), the 'org' field shows the cert-subject organization which may differ from the brand on the homepage.

Auth & limits: same as ctscout_search_company.

Corrections:

ParametersJSON Schema
NameRequiredDescriptionDefault
domainsYesApex domains to look up (e.g. ['gs.com', 'goldmansachs.com']). Returns the organization(s) attributed to each domain, plus any sibling domains in the warehouse attributed to the same orgs. Max 10.
response_formatNoOutput format: 'markdown' for human-readable summary, 'json' for the raw API response.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
totalNoMatching pairs in the warehouse before any cap.
sourceNo'warehouse' on /scan (both tiers); 'live-enriched' / 'cache-only' on a deep-dive result.
domainsYesAttributed (domain, organization) pairs. Empty when nothing is attributed.
snapshotYesWarehouse/D1 sync date (YYYY-MM-DD) the answer was read from; the warehouse syncs daily. null when the API could not determine it.
truncatedNo
candidatesNoPresent only when match_type is 'semantic'. Candidates are not attributions.
match_typeNo'exact' = domains are warehouse attributions; 'semantic' = domains is empty and candidates holds name-similarity guesses; 'none' = nothing matched.
empty_reasonNo
upgrade_hintNo
snapshot_sourceYes'scan' = the API response carried the date; 'unavailable' = it did not, snapshot is null and must be treated as unknown, never as current.
org_match_strategyNo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations provide readOnlyHint=true, openWorldHint=true, destructiveHint=false. The description adds substantial context beyond these: the open-world semantics are elaborated ('a missing domain means no attribution in this snapshot, not a negative finding'), it discloses the DV-only certification coverage caveat, explains subsidiary vs. cert-subject org differences, and documents auth/limits plus a corrections channel for wrong attributions. This far exceeds what the annotations convey alone.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but earns its length with clearly delimited sections (Args, Returns, Examples, Coverage caveat, Auth & limits, Corrections). The purpose statement is front-loaded and every section addresses a real agent need. Slight redundancy with the input schema's parameter descriptions and with sibling references could be trimmed, but structure is exemplary.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with two response formats, an output schema, and significant open-world caveats, the description is exceptionally complete: it explains both markdown and json return shapes, references the shared output schema, enumerates coverage caveats (DV-only domains, subsidiary attribution), and points to the corrections process. Nothing an agent needs to call it correctly or interpret results is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% (both parameters described), so baseline is 3. The description adds meaningful value on top: explicit char-length constraints (3-253), max-10-per-call limit, concrete usage examples for both parameters, and clarification of what 'markdown' vs 'json' return. Only minor redundancy with the schema's own parameter descriptions prevents a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb+resource ('Reverse-lookup ctscout.dev's domain-attribution warehouse') and states the exact input (apex domains) and output (attributed organizations). It differentiates from the sibling by explicitly declaring 'Reverse lookups never return semantic candidates' and pointing to ctscout_search_company as the forward direction. Purpose is unambiguous and distinct.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit 'Use when' and 'Don't use when' examples with concrete queries ('Who is gs.com attributed to?') and names the alternative tool (ctscout_search_company) for the opposite use case. The guidance is actionable and leaves no inference to the agent.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ctscout_lookup_leiLook up one LEI's record, or the LEIs published under a legal nameA
Read-onlyIdempotent

Read the ctscout research product's entity index: one LEI's record (GET /lei/{lei}), or the LEIs filed under a legal name (GET /lei?name=). Free, and it debits no quota — every answer is a precomputed object published by the ctscout-research refresh, not a live query.

Args (exactly one of lei / name; passing both is rejected before any network call):

  • lei (string, optional): an ISO 17442 LEI — 18 uppercase alphanumerics plus 2 check digits, e.g. '549300NDMY0KJK0ZLW17'.

  • name (string, optional): a legal entity name, 1–200 chars, e.g. 'Cloudflare, Inc.'.

  • response_format ('markdown' | 'json', default 'markdown'): output format.

Returns (on success, structuredContent follows the declared outputSchema; a failed call — 400, 404, 503, timeout — is isError with no structuredContent, so never dereference snapshot on it). One tool, two answer shapes; name_match is the discriminator, present only on the by-name answer:

  • By LEI: { "lei": string, "legal_name": string, // GLEIF's legal name "country": string, // GLEIF's country "isin_count": number, // ISINs mapped to this LEI in GLEIF's ISIN-to-LEI file "apex_count": number, // apex domains attributed to this LEI "first_seen": string, "last_seen": string, // warehouse observation window over those apexes "sample_domains": [string], // hash-chosen sample, whatever size the export published — NOT a ranking, not a complete list "vendors_confirmed": [string] // vendor SLUGS: pass one to ctscout_vendor_customers }

  • By name: { "query": string, "name_match": "exact" | "normalized" | "none", "normalizer_mismatch": null | { "index": string, "lookup": string }, // null: the API's normalizer keyed the index. Set: the two versions differ, so a "none" may be a spelling miss under this API. Absent: an API that predates the check. "leis": [string], // capped at "limit" (20) "lei_count": number, // matches BEFORE the cap — can exceed leis.length "limit": number, "truncated": boolean }

  • Both also carry "as_of" / "product_version" (the export version), "snapshot_dates" (the dated GLEIF / ISIN / ELF / Wikidata snapshot and the PSL bundle each join read), and this server's "snapshot" / "snapshot_source" ("product" when the API reported the version, "unavailable" when it did not — then snapshot is null and freshness is unknown, never "current").

What name_match: "none" means (important):

  • It does NOT mean this company has no LEI. The name index is keyed by the research normalizer's form of the GLEIF legal name; the two spellings the route tries (the lowercased, trimmed query and its locale-suffix normalization) are not the index's normalizer, so a real entity can miss on a spelling.

  • Retry with the exact GLEIF legal name, or look the entity up by LEI. Do not report a "none" as an absent LEI.

Vocabulary: a domain is ATTRIBUTED to an entity — that is what the certificate and DNS evidence names, not an ownership claim. A vendor in vendors_confirmed is CONFIRMED, which has a specific meaning: a vendor is confirmed when a hostname it certified resolves onto a domain it certifies and the customer's own www does not, or another organization certifies the apex.

Examples:

  • Use when: "What does ctscout know about LEI 549300NDMY0KJK0ZLW17?" -> { lei: "549300NDMY0KJK0ZLW17" }

  • Use when: "Which LEIs are filed under 'Cloudflare, Inc.'?" -> { name: "Cloudflare, Inc." }

  • Don't use when: you want the domains attributed to a company by cert subject — that is ctscout_search_company against the warehouse, a different index with different coverage.

Coverage & freshness:

  • The product covers LEIs with at least one attributed apex in the research build, so a 404 means "not in this published version", not "no such LEI". An entity has an LEI at all only where a regulator or a counterparty required one, so an absent LEI is not an absent entity either.

  • The export is republished by the ctscout-research refresh, so these answers move on that cadence — slower than the /scan warehouse, which syncs daily. Read "snapshot" for the version actually answered from.

  • Before the first publish the route answers HTTP 503 and this tool returns a plain "not published yet" error. That is expected, not a fault in the query.

Corrections:

ParametersJSON Schema
NameRequiredDescriptionDefault
leiNoA single LEI (ISO 17442: 18 uppercase alphanumerics + 2 check digits), e.g. '549300NDMY0KJK0ZLW17'. Give lei or name, not both.
nameNoA legal entity name to look up in the name index, e.g. 'Cloudflare, Inc.'. Give lei or name, not both.
response_formatNoOutput format: 'markdown' for the record or the name-match list, 'json' for the raw API response.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
leiNoBy-LEI answer: the LEI the record is filed under.
leisNoBy-name answer: matching LEIs, capped at `limit`. lei_count is the total.
as_ofNoThe product version this answer was read from.
limitNoBy-name answer: the cap applied to `leis`.
queryNoBy-name answer: the name as submitted.
countryNoBy-LEI answer: GLEIF's country for the entity.
snapshotYesThe research product version (YYYY-MM-DD) this answer was read from — the `as_of` of the export the ctscout-research refresh published. null when the API response carried none.
last_seenNoBy-LEI answer: latest warehouse observation across this LEI's apexes.
lei_countNoBy-name answer: matches before the cap.
truncatedNoBy-name answer: true when lei_count exceeds limit.
apex_countNoBy-LEI answer: apex domains attributed to this LEI in the research build.
first_seenNoBy-LEI answer: earliest warehouse observation across this LEI's apexes.
isin_countNoBy-LEI answer: ISINs mapped to this LEI in GLEIF's ISIN-to-LEI file.
legal_nameNoBy-LEI answer: GLEIF's legal name for the entity.
name_matchNoBy-name answer, and the discriminator between the two shapes: 'exact' | 'normalized' | 'none'. 'none' means neither spelling tried hit the index, NOT that the entity has no LEI.
sample_domainsNoBy-LEI answer: a hash-chosen sample of the attributed apexes — whatever size the research export published, not a ranking and not a complete list. apex_count is the total; the markdown says so if it lists fewer than the sample carries.
snapshot_datesNoPer-source provenance from the product manifest: elf, gleif, isin, psl, wikidata. Values are the dated snapshot each join read (psl is a bundle identifier, not a date).
product_versionNoSame value as as_of; the manifest's version.
snapshot_sourceYes'product' = the API response carried the export's as_of; 'unavailable' = it did not, snapshot is null and must be treated as unknown, never as current.
truncation_noteNoWritten by this MCP server, never by the API: present only when a list above was shortened to stay under the response character limit, naming each shortened list and the length the API actually sent. Absent means no list was cut here.
vendors_confirmedNoBy-LEI answer: vendor slugs confirmed on this LEI's domains. Pass one to ctscout_vendor_customers.
normalizer_mismatchNoBy-name answer: null when the API's normalizer is the one the index was keyed with. Otherwise { index, lookup } names the two versions: the normalized spellings are then not the index's keys, and a 'none' may be a spelling miss under this API rather than an absent name. Absent from an API that predates the check.

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes well beyond the readOnly/idempotent/openWorld annotations by explaining error semantics ('404 means not in this published version'), the 503 'not published yet' case, the absence of quota debits, and the fact that answers come from a precomputed export rather than a live query. It does not contradict any annotation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every section serves a distinct purpose: purpose, arguments, return shapes, meaning of 'none', vocabulary, examples, coverage, and corrections. It is front-loaded with the essential purpose and organized with headers and code blocks, so the length is appropriate for the complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

It fully specifies both return shapes, the name_match discriminator, error behavior, snapshot/freshness semantics, and even correction channels. Given the tool's non-obvious 'none' semantics and dual lookup modes, nothing needed to call it correctly appears to be missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

All three parameters are individually described in the schema, and the description adds practical constraints: exactly one of lei/name must be given, passing both is rejected before network access, and the response_format enum is explained with its two possible values.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Read'), resource ('ctscout research product's entity index'), and scope ('one LEI's record' or 'LEIs filed under a legal name'). It also explicitly differentiates from the sibling tool ctscout_search_company, so an agent can select it without ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit use cases ('Use when:') and non-use cases ('Don't use when:') with concrete examples. It also provides guidance on retrying when name_match is 'none' and explains the exact one-of constraint, so when and how to invoke it are clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ctscout_search_companySearch ctscout by company nameA
Read-only

Search ctscout.dev's domain-attribution warehouse by organization name. Returns apex domains attributed to that organization based on Certificate Transparency log analysis (OV/EV cert subjects matched to entity names).

Args:

  • company_name (string, required): organization name. Partial matches work — 'Goldman' matches 'Goldman Sachs'. Min 2 chars, max 200.

  • strict_match_org_only (boolean, optional): suppress semantic candidates and return only authoritative warehouse org matches.

  • org_match_field ('verbatim' | 'normalized', optional): raw cert-subject substring matching, or the research normalizer's key of the query against the normalized column (legal forms stripped at the edges, '&'/'and' and hyphen/space unified, a leading 'The' dropped, a generic tail like 'Holdings' kept). Leave unset for automatic verbatim-then-normalized fallback.

  • org_match_mode ('substring' | 'word', optional): use word-boundary matching to reduce noise from short/common names.

  • purpose ('underwriting' | 'corporate_family', optional): choose tight operational-attribution defaults or broader corporate-family defaults. Explicit matching controls override the preset.

  • response_format ('markdown' | 'json', default 'markdown'): output format.

Returns (on success, structuredContent follows the declared outputSchema; an error result — 401, 429, timeout — is isError with no structuredContent, so never dereference snapshot on a failed call):

  • "Attributed" means the organization is what the evidence names for that domain, not an ownership claim. On /scan that evidence is the OV/EV certificate subject on both tiers; multi-signal attribution (DNS, RDAP, IP/ASN, homepage, favicon) exists only in a deep-dive job result (ctscout_submit_deep_dive, Pro). "Candidate" means a semantic name-similarity guess that is NOT an attribution.

  • In markdown: a snapshot line, then a table of (domain, attributed to, cert count, subdomain count). When nothing is attributed but match_type is 'semantic', a table of candidate organizations is rendered instead, labelled as candidates.

  • In JSON, structured as: { "domains": [ // attributed pairs; empty when nothing is attributed { "org": string, // attributed organization: the OV/EV certificate subject (both tiers) "apex_domain": string, // e.g. "gs.com" "cert_count": number, // # of distinct certs observed for this pair "subdomain_count": number, // # of distinct subdomains "first_seen": string | null, // warehouse observation time — NOT the CT log SCT / issuance time "last_seen": string | null // warehouse observation time — NOT the CT log SCT / issuance time } ], "total": number, // total matching rows in warehouse "truncated": boolean, // true if response is capped "upgrade_hint": string, // present when truncated "source": "warehouse", // both tiers read the daily warehouse snapshot "match_type": "exact" | "semantic" | "none", // 'semantic' = domains empty, candidates offered "org_match_strategy": string, // which matching pass produced the answer "empty_reason": string, // present on empty results: why nothing was attributed "candidates": [ // only when match_type is 'semantic'; NOT attributions { "org": string, "similarity": number, "top_apex_domain": string | null } ], "snapshot": string | null, // warehouse/D1 sync date (YYYY-MM-DD) the answer was read from (API version 2026-09-05+); null only when the API could not determine it "snapshot_source": "scan" | "unavailable" // 'scan' = API carried the date; 'unavailable' = it did not (snapshot is null). null means unknown freshness, never "current" }

Examples:

  • Use when: "Find all domains attributed to Cloudflare" -> { company_name: "Cloudflare" }

  • Use when: "Which domains are attributed to Goldman?" -> { company_name: "Goldman Sachs" }

  • Don't use when: You have a specific domain and want to find the organization it's attributed to — use ctscout_lookup_domain instead.

Auth & limits:

  • Requires an API key in CTSCOUT_API_KEY. Get a free key (no email) at https://ctscout.dev.

  • Free tier: 10 successful lookups/day, top 5 results from a daily snapshot. The response's "snapshot" field carries that snapshot's sync date (the API reports it since X-API-Version 2026-09-05); when it is null the API could not determine it — treat freshness as unknown, never as current.

  • Pro tier: 3,000 successful lookups/month included, up to 25 rows, a 12-month window; deep-dive jobs (20/day) for multi-signal attribution. $49/month, subscribed from https://ctscout.dev/#tiers.

Error handling:

  • HTTP 401: API key missing or invalid.

  • HTTP 429: a quota on this key is exhausted, on either tier; the error quotes the API's own detail, which names the cap and when it resets. Tier allowances: https://ctscout.dev/#tiers.

  • "No domains found": try a shorter or different company name (see legal-vs-brand caveat below).

Legal-vs-brand caveat (important):

  • The cert subject (O field) uses LEGAL entity names, not brand names.

  • "Travelers Insurance" → 0 results because the legal name is "The Travelers Companies, Inc."

  • "Hartford Financial" → 0 results; legal names are "Hartford Fire Insurance Company" or "The Hartford Financial Services Group".

  • If a brand-name search returns nothing, retry with variants like "X Companies", "X Group", "X Inc", "X Corporation", or "The X". The empty-result markdown output includes these suggestions automatically when the input looks brand-shaped.

Coverage caveat:

  • Best for established US/EU tech companies with OV/EV certs.

  • Limited coverage on small private companies, cyber MGAs, and entities using only DV (Let's Encrypt) certs.

  • Warehouse size (organizations, org-domain pairs, last sync) is not stated here because it changes daily; read the live figures at https://ctscout.dev/stats before treating a miss as meaningful.

Corrections:

ParametersJSON Schema
NameRequiredDescriptionDefault
purposeNoOptional persona preset. 'underwriting' defaults to a tight operational attack-surface set; 'corporate_family' defaults to a broad brand, regional, and family set. Explicitly supplied matching controls always win.
company_nameYesCompany / organization name to search for. Partial matches work (e.g. 'Goldman' matches 'Goldman Sachs'). Case-insensitive.
org_match_modeNoOptional, default 'substring'. Use 'word' for short or common-token names to avoid unrelated substring matches. Applies only to verbatim matching.
org_match_fieldNoOptional, default 'verbatim'. 'verbatim' substring-matches the raw cert subject. 'normalized' compares the research normalizer's key of the query against the normalized column: GLEIF legal forms stripped at the edges, spelled out or abbreviated ('Aktiengesellschaft' and 'AG', 'Incorporated' and 'Inc.'), diacritics folded, lowercased; '&' and 'and' unify; a hyphen and a space no longer split ('Coca-Cola' and 'Coca Cola'), nor does a leading 'The'; a generic tail like 'Holdings' or 'Group' is part of the name and stays. A query that normalizes to nothing (a bare legal form) matches nothing. Leave unset to try verbatim first and retry normalized after an empty result; set 'normalized' only to skip the verbatim attempt.
response_formatNoOutput format: 'markdown' for human-readable summary, 'json' for the raw API response (useful for programmatic processing).markdown
strict_match_org_onlyNoOptional, default false. When true, suppress the semantic-name fallback and return only authoritative warehouse organization matches.

Output Schema

ParametersJSON Schema
NameRequiredDescription
totalNoMatching pairs in the warehouse before any cap.
sourceNo'warehouse' on /scan (both tiers); 'live-enriched' / 'cache-only' on a deep-dive result.
domainsYesAttributed (domain, organization) pairs. Empty when nothing is attributed.
snapshotYesWarehouse/D1 sync date (YYYY-MM-DD) the answer was read from; the warehouse syncs daily. null when the API could not determine it.
truncatedNo
candidatesNoPresent only when match_type is 'semantic'. Candidates are not attributions.
match_typeNo'exact' = domains are warehouse attributions; 'semantic' = domains is empty and candidates holds name-similarity guesses; 'none' = nothing matched.
empty_reasonNo
upgrade_hintNo
snapshot_sourceYes'scan' = the API response carried the date; 'unavailable' = it did not, snapshot is null and must be treated as unknown, never as current.
org_match_strategyNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and destructiveHint=false, but the description goes far beyond by detailing error handling (401/429), truncation via 'truncated' flag, snapshot freshness semantics, match_type behavior, and the difference between attribution and candidates. It discloses that success/failure is conveyed via structuredContent vs isError, and explains auth requirements. No contradictions with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but well-organized with clear section headers (Args, Returns, Examples, Auth, Error handling, Caveats). It front-loads the core purpose and returns structure before diving into caveats. While some repetition exists (snapshot freshness mentioned multiple times), each section serves a distinct purpose. It earns a high score for structure, slightly deducted for length that could be trimmed without losing critical info.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (6 params, 4 enums, output schema), the description is exceptionally complete. It covers all error scenarios, quota limits, legal-name pitfalls, coverage limitations, and even provides correction channels. The output schema is referenced and the JSON response structure is fully documented. An agent has everything needed to call this tool correctly in various situations.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, but the description adds substantial meaning beyond the schema. For instance, org_match_field gets a full explanation of normalization rules (GLEIF legal forms, '&' vs 'and', leading 'The' dropping, etc.), and org_match_mode is clarified as word-boundary matching. The response_format parameter is expanded with JSON structure details. The description enriches every parameter beyond the schema's basic type/enum descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool searches ctscout.dev's domain-attribution warehouse by organization name and returns attributed apex domains. It explicitly differentiates from sibling tools by naming when NOT to use it (domain lookup) and pointing to ctscout_lookup_domain. The verb 'search' and specific resource 'domain-attribution warehouse' make the purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit when-to-use examples ('Find all domains attributed to Cloudflare') and a direct exclusion for domain-to-org lookups. It also gives detailed usage guidance on legal-vs-brand naming, coverage limitations, and handling empty results with retry suggestions. The 'Don't use when' line names the alternative tool, fulfilling the when-not/alternatives criterion.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ctscout_search_company_batchSearch ctscout by multiple company names in one callA
Read-only

Look up apex domains for up to 10 organization names in a single call, via ctscout.dev's /scan/batch endpoint. Each name is matched exactly like ctscout_search_company; results come back in input order.

Args:

  • company_names (string[], required): 1–10 organization names. Partial matches work — 'Goldman' matches 'Goldman Sachs'. Each 2–200 chars.

  • response_format ('markdown' | 'json', default 'markdown'): output format.

Returns (on success, structuredContent follows the declared outputSchema; an error result — 401, 429, timeout — is isError with no structuredContent, so never dereference snapshot on a failed call):

  • "Attributed" and "candidate" mean exactly what they mean in ctscout_search_company: what the evidence names (the OV/EV certificate subject on /scan, both tiers) vs a semantic name-similarity guess that is NOT an attribution.

  • In markdown: a snapshot line, then one section per company (heading + the same attributed-domains table as ctscout_search_company; a candidate-organizations table when that name's match_type is 'semantic'), followed by remaining quota. Names that failed render an error line instead of a table.

  • In JSON, the batch envelope: { "results": [ { "query": {...}, "domains": [...], "total": number, "match_type": "exact"|"semantic"|"none", "candidates"?: [...] }, // same per-result fields as ctscout_search_company { "query": {...}, "error": { "code": number, "message": string } } ], "remaining_quota": number | null, // null = no remaining count reported (Pro today); a per-day request guard still applies "snapshot": string | null, // sync date shared by every result in the batch (API version 2026-09-05+); null (unknown freshness) only when the API could not determine it "snapshot_source": "scan" | "unavailable" }

Partial-failure semantics (important):

  • This is a 207-style mixed-result envelope, NOT all-or-nothing: one name can fail (its result carries an "error" object with no "domains") while the rest succeed.

  • Quota debits by the number of names in the batch — every name counts once, even zero-result ones. No free riders.

Examples:

  • Use when: "Look up Cloudflare, Fastly, and Akamai" -> { company_names: ["Cloudflare", "Fastly", "Akamai"] }

  • Don't use when: you have a single name (use ctscout_search_company) or a specific domain (use ctscout_lookup_domain).

Auth & limits:

  • Requires an API key in CTSCOUT_API_KEY, same as ctscout_search_company.

  • Oversized batches (>10 names) are rejected with a validation error before any network call and without a partial quota debit.

  • This MCP batch tool intentionally accepts names only. For matching modifiers such as strict_match_org_only, purpose, or org_match_mode, use individual ctscout_search_company calls or the REST /scan/batch endpoint.

Legal-vs-brand and coverage caveats are identical to ctscout_search_company — brand names may need legal-entity variants ("X Companies", "X Group", "The X"), and coverage is best for established US/EU entities with OV/EV certs.

Corrections:

ParametersJSON Schema
NameRequiredDescriptionDefault
company_namesYesCompany / organization names to look up in one call (1–10). Each is matched exactly as in ctscout_search_company (partial, case-insensitive). Results come back in input order; individual names can fail independently (partial-failure envelope), so a failed name doesn't sink the batch.
response_formatNoOutput format: 'markdown' for a per-company summary, 'json' for the raw batch envelope (useful for programmatic processing).markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYesOne item per input name, in input order.
snapshotYesWarehouse/D1 sync date (YYYY-MM-DD) the answer was read from; the warehouse syncs daily. null when the API could not determine it.
remaining_quotaYesnull = the API reported no remaining count for this key (Pro today); a per-day request guard still applies, and a 429 carries the API's own detail.
snapshot_sourceYes'scan' = the API response carried the date; 'unavailable' = it did not, snapshot is null and must be treated as unknown, never as current.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description details partial-failure semantics (207-style envelope), per-name quota debits with no free riders, snapshot freshness behavior, and error handling. It also discloses correction channels and coverage caveats, exceeding annotation transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is lengthy but well-organized with clear sections (Examples, Auth & limits, Partial-failure semantics, Corrections). Every part serves a purpose given the tool's complexity; however, some redundancy exists (e.g., repeated caveats tied to ctscout_search_company), making it slightly less concise than optimal.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description comprehensively covers all necessary context: mixed-result envelope structure, quota accounting, snapshot details, auth, limits, error behavior, distinctions from related tools, and how to report issues. Even with an output schema present, the description fully explains the response shape, exceeding the minimum requirement.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Both parameters are fully described in the schema and further elaborated in the description: company_names includes range, matching behavior, order, and independent failure; response_format explains default and use cases. Schema coverage is 100% and the description adds important context like case-insensitivity and partial matches.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Look up apex domains'), the resource (up to 10 organization names via /scan/batch), and explicitly distinguishes from siblings: single names go to ctscout_search_company, specific domains to ctscout_lookup_domain. The batch nature is unmistakable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides concrete when-to-use examples ('Look up Cloudflare, Fastly, and Akamai'), when-not-to-use instructions, authentication requirements (CTSCOUT_API_KEY), and batch size limits. Quota-debit semantics and partial-failure handling are explained, leaving no ambiguity about usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ctscout_submit_deep_diveSubmit an async Pro deep dive (multi-signal attribution job)A

Queue an asynchronous Pro deep dive on ctscout.dev: the full multi-signal attribution run (CT warehouse + DNS, RDAP, homepage, IP/ASN corroboration) executed by a batch worker, via POST /jobs. Returns a job receipt immediately, NOT results.

Asynchronous, Pro only:

  • The call returns as soon as the job is queued ({job_id, status: "queued", submitted_at}). Nothing is attributed yet.

  • Poll with ctscout_get_job using the returned job_id. Wait about 30 s before the first poll, then back off toward 5 min between polls; the batch worker picks up queued jobs every few minutes and a deep dive can take several minutes to run.

  • Requires a Pro API key. A free key gets HTTP 403 with the API's upgrade text (Pro is $49/month from https://ctscout.dev/#tiers). Quota: 20 submissions per key per day (HTTP 429 over). Submitting is not idempotent — a retry queues a second job.

Args:

  • company_name (string, optional): organization name, matched exactly as in ctscout_search_company (partial, case-insensitive; 2–200 chars).

  • seed_domain (string[], optional): known apex domains to pivot from, max 10. At least one of company_name / seed_domain is required; both may be given. Validated exactly like /scan.

  • response_format ('markdown' | 'json', default 'markdown'): a receipt with polling guidance, or the raw 202 body.

Returns (on success, structuredContent follows the declared outputSchema; a failed call — 401, 403, 429, timeout — is isError with no structuredContent): { "job_id": string, // opaque; pass to ctscout_get_job "status": "queued", "submitted_at": string, "poll": "/jobs/" // informational }

What the finished result contains (read it with ctscout_get_job):

  • The deep-dive result shape (a /scan never carries it): "domains" of attributed apex domains, each with "attributed_to", an "enrichment" object (confidence_band, weight_total, matched_via, evidence, signal_health, vlm_status, vlm_override) and the underlying discovery evidence under "base"; plus "entity", "run_metadata", "source" and "signals_degraded".

  • Plus "snapshot": the warehouse date (YYYY-MM-DD) the deep dive read from. It is present on every deep-dive result because the batch worker sets it, together with "worker_version" and "signals_attempted"; a /scan answer carries its own snapshot from the API.

  • "Attributed" means the organization is what the evidence names for that domain (certificate subject, corroborated by the enrichment signals), not an ownership claim. "Candidate" means a semantic name-similarity guess that is NOT an attribution; a deep dive reports attributions with a confidence band, never bare candidates.

  • Visual brand verification (VLM) is NOT included in v1: vlm_status stays "pending" or "skipped" and never vetoes a band.

Examples:

  • Use when: "Run a full attribution deep dive on CNA Financial" -> { company_name: "CNA Financial" }

  • Use when: "Deep-dive from these seed domains" -> { seed_domain: ["cna.com", "cnasurety.com"] }

  • Don't use when: you want an answer now — ctscout_search_company / ctscout_lookup_domain are synchronous. Don't resubmit while a job is queued or running; poll it.

Corrections:

ParametersJSON Schema
NameRequiredDescriptionDefault
seed_domainNoKnown apex domains of the organization to pivot from (e.g. ['gs.com']). Max 10. Give company_name, seed_domain, or both.
company_nameNoOrganization name to deep-dive, matched exactly as in ctscout_search_company. Give company_name, seed_domain, or both.
response_formatNoOutput format: 'markdown' for a submission receipt with polling guidance, 'json' for the raw 202 body ({job_id, status, submitted_at, poll}).markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
pollNoRelative API path to poll (informational).
job_idYesOpaque id; pass it to ctscout_get_job.
statusYes'queued' on submission.
submitted_atYesSubmission time as reported by the API.

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description thoroughly discloses non-idempotency (a retry queues a second job), async behavior, Pro-key requirement, quota limits (HTTP 429), and failure modes (401, 403, 429, timeout), going well beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Although long, the description is well-structured with clear sections (Args, Returns, finished result, Examples, Corrections) and front-loads the core action before diving into details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers the output schema, the meaning of the finished result, attribution semantics, VLM limitations, polling guidance, and even correction channels, leaving no significant context gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Every parameter is documented with additional detail beyond the schema: company_name matching semantics, seed_domain constraints and validation parity with /scan, and response_format behavior with the raw 202 body.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the specific action ('Queue an asynchronous Pro deep dive'), the resource ('multi-signal attribution job' on ctscout.dev), and contrasts it with synchronous siblings like ctscout_search_company and ctscout_lookup_domain.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states when to use the tool, gives concrete examples, warns against using it for immediate answers, names the synchronous alternatives, and advises not to resubmit while a job is queued or running.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ctscout_vendor_customersA vendor's customer counts, and — with a key — the customer enumerationA
Read-onlyIdempotent

Read the ctscout research product's vendor objects: the free summary for a vendor slug (GET /vendors/{slug}), or the per-customer enumeration (GET /vendors/{slug}/customers, which needs an API key). Debits no quota either way — both are precomputed objects published by the ctscout-research refresh.

Args:

  • slug (string, required): the vendor slug, one lowercase segment, e.g. 'cloudflare'. The values in a LEI record's vendors_confirmed are exactly these slugs.

  • enumerate (boolean, optional, default false): false = the free summary; true = the per-customer enumeration, which requires an active ctscout.dev API key (any tier) in CTSCOUT_API_KEY. Free returns up to 100 rows from each list; Pro returns the full published lists (which may still be export-capped or shortened for MCP). A missing, invalid or revoked key gets HTTP 401 and this tool explains that the summary is still available with enumerate: false.

  • response_format ('markdown' | 'json', default 'markdown'): output format.

Candidates and confirmed are two different claims and are NEVER summed:

  • Candidate = an apex domain this vendor certified a hostname for. Fan-out alone is not a vendor relationship: an organization certifying hundreds of its own product sites looks identical.

  • Confirmed = the DNS-confirmed subset of the candidates. The definition: a vendor is confirmed when a hostname it certified resolves onto a domain it certifies and the customer's own www does not, or another organization certifies the apex.

  • Confirmed is a SUBSET of candidates, so adding the two double-counts. The markdown keeps them in separate tables and the JSON in separate fields; report them apart.

Returns (on success, structuredContent follows the declared outputSchema; a failed call — 400, 401, 404, 503, timeout — is isError with no structuredContent):

  • enumerate: false (the summary): { "slug": string, "vendor_name": string, "vendor_apex": string | null, // null when the vendor's brand token matches no label it certifies "customers": { "candidates": number, "confirmed": number }, "countries_top": [ { "country": string, "confirmed": number } ], // CONFIRMED customers only "co_use": [ { "slug": string, "confirmed": number } ], // see below "sample_customers": [string] // hash-chosen sample of the CONFIRMED customers, whatever size the export published }

  • enumerate: true (the enumeration): { "slug": string, "confirmed": [ { "apex": string, "attributed_to": string | null, "lei": string | null } ], "candidates": [ same row shape ], "counts": { "candidates": number, "confirmed": number }, // what the research build holds "capped": boolean, // true = the build itself kept a subset of the candidates "truncated": boolean, // true = the API shortened a list for the key tier "free_slice": { "rows": number, "full_list": "pro" } | null, // Free limit per list; null for Pro "truncation_note": string // written by THIS server, only when it dropped rows to fit the character limit; counts and capped still describe the API's answer }

  • Both also carry "as_of" / "product_version", "snapshot_dates", and this server's "snapshot" / "snapshot_source" ("product" | "unavailable"; null snapshot means unknown freshness, never "current").

Reading the fields honestly:

  • co_use counts THIS vendor's confirmed customers that the other vendor also certifies — a candidate there, not a mutual confirmation.

  • countries_top counts confirmed customers that resolved to an LEI; candidates and LEI-less customers are not in it.

  • attributed_to is GLEIF's legal name when the apex resolves to one LEI, the single non-vendor certificate organization otherwise, and null when neither holds. It is an attribution, not an ownership claim.

Examples:

  • Use when: "How many customers does Cloudflare have in the index?" -> { slug: "cloudflare" } (report candidates and confirmed separately)

  • Use when: "List Cloudflare's confirmed customers" -> { slug: "cloudflare", enumerate: true }

  • Don't use when: you have a company and want its vendors — read vendors_confirmed from ctscout_lookup_lei instead.

Coverage & freshness:

  • A 404 means the slug is not in the published version, not that the vendor does not exist. The export is republished by the ctscout-research refresh, so these answers move on that cadence rather than the daily /scan warehouse sync.

  • Before the first publish the routes answer HTTP 503 and this tool returns a plain "not published yet" error. That is expected, not a fault in the query.

Corrections:

ParametersJSON Schema
NameRequiredDescriptionDefault
slugYesThe vendor's slug, e.g. 'cloudflare'. The slugs in a LEI record's vendors_confirmed are exactly these values.
enumerateNoOptional, default false. false returns the free vendor summary (counts, top countries, co-use, a customer sample). true returns the per-customer enumeration from GET /vendors/{slug}/customers, which requires an active ctscout.dev API key (any tier).
response_formatNoOutput format: 'markdown' for the counts (or the two customer tables), 'json' for the raw API response.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
slugNoThe vendor slug the answer is filed under.
as_ofNoThe product version this answer was read from.
cappedNoEnumeration view: true when the research build kept a subset of the candidates. Says nothing about this server's own truncation — see truncation_note.
co_useNoSummary view: other vendors certifying this vendor's confirmed customers. The count is this vendor's confirmed customers that the other vendor also certifies — a candidate there, not a mutual confirmation.
countsNoEnumeration view: the completeness metadata — the rows the research build holds. counts.candidates can exceed candidates.length; see `capped`.
snapshotYesThe research product version (YYYY-MM-DD) this answer was read from — the `as_of` of the export the ctscout-research refresh published. null when the API response carried none.
confirmedNoEnumeration view: the DNS-confirmed customer rows.
customersNoSummary view: the candidate/confirmed split. Two different claims about the same vendor — confirmed is the DNS-confirmed subset of candidates, so adding them double-counts.
truncatedNoEnumeration view: the API shortened either list for this key's tier.
candidatesNoEnumeration view: the candidate customer rows. counts.confirmed is a subset of counts.candidates, but when `capped` is true the LISTED candidates are a hash-chosen subset that may omit rows the confirmed list carries.
free_sliceNoEnumeration view: free-tier rows allowed per list and the tier for the published list; null for Pro. Separate from export and MCP size caps.
vendor_apexNoSummary view: the vendor's own apex, null when its brand token matches none.
vendor_nameNoSummary view: the vendor's certificate name.
countries_topNoSummary view: top countries by CONFIRMED customers (candidates are not counted).
snapshot_datesNoPer-source provenance from the product manifest: elf, gleif, isin, psl, wikidata. Values are the dated snapshot each join read (psl is a bundle identifier, not a date).
product_versionNoSame value as as_of; the manifest's version.
snapshot_sourceYes'product' = the API response carried the export's as_of; 'unavailable' = it did not, snapshot is null and must be treated as unknown, never as current.
truncation_noteNoWritten by this MCP server, never by the API: present only when rows were dropped from the lists above to stay under the response character limit. `counts` and `capped` still describe the API's answer, not this list.
sample_customersNoSummary view: a hash-chosen sample of the CONFIRMED customers — whatever size the research export published, not a ranking and not a complete list.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

While annotations declare readOnlyHint, openWorldHint, idempotentHint, and destructiveHint, the description adds substantial behavioral context: it debits no quota, explains that a missing/invalid API key yields HTTP 401 and that the summary still works with enumerate: false, clarifies that 404 means the slug isn't in the published version, and that 503 means 'not published yet'. It also distinguishes candidates from confirmed and warns against summing them, and explains the 'capped' and 'truncated' flags. These go far beyond the annotations and are consistent with them.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but well-structured with a clear opening summary, sections for arguments, returns, examples, coverage, and corrections. It is front-loaded with the core purpose. However, it could be tightened; some redundancies exist (e.g., the return structure is described in both prose and the output schema). The length is justified by the tool's complexity, so it earns a 4 rather than lower.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers every aspect an agent needs: the two modes, output structure (including nested fields and their meanings), error handling (400, 401, 404, 503, timeout), freshness semantics (as_of, product_version, snapshot), attribution rules for attributed_to, and the co_use semantics. It even includes examples and a corrections channel. Nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Even though the schema provides descriptions for all three parameters, the description enriches each: it ties slug to the exact values in a LEI record's vendors_confirmed, details the free vs Pro limits and key requirements for enumerate, and explains the difference between markdown and json output for response_format. It also adds edge-case behavior like the 401 fallback, which the schema omits.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise statement: 'Read the ctscout research product's vendor objects: the free summary for a vendor slug (GET /vendors/{slug}), or the per-customer enumeration (GET /vendors/{slug}/customers, which needs an API key).' It names both modes and explicitly contrasts with a sibling in the 'Don't use when' note, directing to ctscout_lookup_lei for vendor lookups. This makes the tool's purpose unambiguous and distinct.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit use cases with concrete examples: 'How many customers does Cloudflare have in the index?' and 'List Cloudflare's confirmed customers', plus a clear 'Don't use when' instruction that points to an alternative tool. It also explains when to set enumerate: true versus false, leaving no ambiguity about selection.

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.

  1. 1 tool updatev0.6.1
    • Changedctscout_vendor_customers2 fields changed
      • addedOutput schema / properties / free_slice
        Added value: +{
        +  "anyOf": [
        +    {
        +      "additionalProperties": {},
        +      "properties": {
        +        "full_list": {
        +          "type": "string"
        +        },
        +        "rows": {
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "rows",
        +        "full_list"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Enumeration view: free-tier rows allowed per list and the tier for the published list; null for Pro. Separate from export and MCP size caps."
        +}
      • addedOutput schema / properties / truncated
        Added value: +{
        +  "description": "Enumeration view: the API shortened either list for this key's tier.",
        +  "type": "boolean"
        +}
  2. 7 tool updatesv0.6.0
    • Addedctscout_get_job
    • Changedctscout_lookup_domain4 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedInput schema / properties / domains / description
        Previous value: -"Apex domains to look up (e.g. ['gs.com', 'goldmansachs.com']). Returns the organization(s) that own each domain, plus any sibling domains in the warehouse owned by the same orgs. Max 10."New value: +"Apex domains to look up (e.g. ['gs.com', 'goldmansachs.com']). Returns the organization(s) attributed to each domain, plus any sibling domains in the warehouse attributed to the same orgs. Max 10."
      • changedInput schema / properties / response_format / enum
        Previous value: -[
        -  "json",
        -  "markdown"
        -]New value: +[
        +  "markdown",
        +  "json"
        +]
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": {},
        +  "properties": {
        +    "candidates": {
        +      "description": "Present only when match_type is 'semantic'. Candidates are not attributions.",
        +      "items": {
        +        "additionalProperties": {},
        +        "properties": {
        +          "org": {
        +            "description": "Candidate organization name — a semantic match, NOT an attribution.",
        +            "type": "string"
        +          },
        +          "similarity": {
        +            "description": "Name-embedding similarity, 0..1.",
        +            "type": "number"
        +          },
        +          "top_apex_domain": {
        +            "description": "The apex domain most often attributed to this candidate, if any.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          }
        +        },
        +        "required": [
        +          "org"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "domains": {
        +      "description": "Attributed (domain, organization) pairs. Empty when nothing is attributed.",
        +      "items": {
        +        "additionalProperties": {},
        +        "properties": {
        +          "apex_domain": {
        +            "type": "string"
        +          },
        +          "attributed_to": {
        +            "type": "string"
        +          },
        +          "cert_count": {
        +            "description": "Distinct certificates observed for this pair.",
        +            "type": "number"
        +          },
        +          "domain": {
        +            "description": "Deep-dive row shape: the apex domain (no apex_domain field).",
        +            "type": "string"
        +          },
        +          "first_seen": {
        +            "description": "When the warehouse first ingested this pair (observation time, NOT the CT log SCT / issuance time).",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "last_seen": {
        +            "description": "When the warehouse last ingested this pair (observation time, not SCT time).",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "org": {
        +            "description": "Organization the domain is attributed to. Free tier: the OV/EV cert subject O field. On /scan, both tiers: the OV/EV certificate subject. Multi-signal attribution lives in a deep-dive job result (attributed_to + enrichment), not here.",
        +            "type": "string"
        +          },
        +          "subdomain_count": {
        +            "type": "number"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "empty_reason": {
        +      "type": "string"
        +    },
        +    "match_type": {
        +      "description": "'exact' = domains are warehouse attributions; 'semantic' = domains is empty and candidates holds name-similarity guesses; 'none' = nothing matched.",
        +      "type": "string"
        +    },
        +    "org_match_strategy": {
        +      "type": "string"
        +    },
        +    "snapshot": {
        +      "description": "Warehouse/D1 sync date (YYYY-MM-DD) the answer was read from; the warehouse syncs daily. null when the API could not determine it.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "snapshot_source": {
        +      "description": "'scan' = the API response carried the date; 'unavailable' = it did not, snapshot is null and must be treated as unknown, never as current.",
        +      "enum": [
        +        "scan",
        +        "unavailable"
        +      ],
        +      "type": "string"
        +    },
        +    "source": {
        +      "description": "'warehouse' on /scan (both tiers); 'live-enriched' / 'cache-only' on a deep-dive result.",
        +      "type": "string"
        +    },
        +    "total": {
        +      "description": "Matching pairs in the warehouse before any cap.",
        +      "type": "number"
        +    },
        +    "truncated": {
        +      "type": "boolean"
        +    },
        +    "upgrade_hint": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "domains",
        +    "snapshot",
        +    "snapshot_source"
        +  ],
        +  "type": "object"
        +}
    • Addedctscout_lookup_lei
    • Changedctscout_search_company7 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • addedInput schema / properties / org_match_field
        Added value: +{
        +  "description": "Optional, default 'verbatim'. 'verbatim' substring-matches the raw cert subject. 'normalized' compares the research normalizer's key of the query against the normalized column: GLEIF legal forms stripped at the edges, spelled out or abbreviated ('Aktiengesellschaft' and 'AG', 'Incorporated' and 'Inc.'), diacritics folded, lowercased; '&' and 'and' unify; a hyphen and a space no longer split ('Coca-Cola' and 'Coca Cola'), nor does a leading 'The'; a generic tail like 'Holdings' or 'Group' is part of the name and stays. A query that normalizes to nothing (a bare legal form) matches nothing. Leave unset to try verbatim first and retry normalized after an empty result; set 'normalized' only to skip the verbatim attempt.",
        +  "enum": [
        +    "verbatim",
        +    "normalized"
        +  ],
        +  "type": "string"
        +}
      • addedInput schema / properties / org_match_mode
        Added value: +{
        +  "description": "Optional, default 'substring'. Use 'word' for short or common-token names to avoid unrelated substring matches. Applies only to verbatim matching.",
        +  "enum": [
        +    "substring",
        +    "word"
        +  ],
        +  "type": "string"
        +}
      • addedInput schema / properties / purpose
        Added value: +{
        +  "description": "Optional persona preset. 'underwriting' defaults to a tight operational attack-surface set; 'corporate_family' defaults to a broad brand, regional, and family set. Explicitly supplied matching controls always win.",
        +  "enum": [
        +    "underwriting",
        +    "corporate_family"
        +  ],
        +  "type": "string"
        +}
      • changedInput schema / properties / response_format / enum
        Previous value: -[
        -  "json",
        -  "markdown"
        -]New value: +[
        +  "markdown",
        +  "json"
        +]
      • addedInput schema / properties / strict_match_org_only
        Added value: +{
        +  "description": "Optional, default false. When true, suppress the semantic-name fallback and return only authoritative warehouse organization matches.",
        +  "type": "boolean"
        +}
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": {},
        +  "properties": {
        +    "candidates": {
        +      "description": "Present only when match_type is 'semantic'. Candidates are not attributions.",
        +      "items": {
        +        "additionalProperties": {},
        +        "properties": {
        +          "org": {
        +            "description": "Candidate organization name — a semantic match, NOT an attribution.",
        +            "type": "string"
        +          },
        +          "similarity": {
        +            "description": "Name-embedding similarity, 0..1.",
        +            "type": "number"
        +          },
        +          "top_apex_domain": {
        +            "description": "The apex domain most often attributed to this candidate, if any.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          }
        +        },
        +        "required": [
        +          "org"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "domains": {
        +      "description": "Attributed (domain, organization) pairs. Empty when nothing is attributed.",
        +      "items": {
        +        "additionalProperties": {},
        +        "properties": {
        +          "apex_domain": {
        +            "type": "string"
        +          },
        +          "attributed_to": {
        +            "type": "string"
        +          },
        +          "cert_count": {
        +            "description": "Distinct certificates observed for this pair.",
        +            "type": "number"
        +          },
        +          "domain": {
        +            "description": "Deep-dive row shape: the apex domain (no apex_domain field).",
        +            "type": "string"
        +          },
        +          "first_seen": {
        +            "description": "When the warehouse first ingested this pair (observation time, NOT the CT log SCT / issuance time).",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "last_seen": {
        +            "description": "When the warehouse last ingested this pair (observation time, not SCT time).",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "org": {
        +            "description": "Organization the domain is attributed to. Free tier: the OV/EV cert subject O field. On /scan, both tiers: the OV/EV certificate subject. Multi-signal attribution lives in a deep-dive job result (attributed_to + enrichment), not here.",
        +            "type": "string"
        +          },
        +          "subdomain_count": {
        +            "type": "number"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "empty_reason": {
        +      "type": "string"
        +    },
        +    "match_type": {
        +      "description": "'exact' = domains are warehouse attributions; 'semantic' = domains is empty and candidates holds name-similarity guesses; 'none' = nothing matched.",
        +      "type": "string"
        +    },
        +    "org_match_strategy": {
        +      "type": "string"
        +    },
        +    "snapshot": {
        +      "description": "Warehouse/D1 sync date (YYYY-MM-DD) the answer was read from; the warehouse syncs daily. null when the API could not determine it.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "snapshot_source": {
        +      "description": "'scan' = the API response carried the date; 'unavailable' = it did not, snapshot is null and must be treated as unknown, never as current.",
        +      "enum": [
        +        "scan",
        +        "unavailable"
        +      ],
        +      "type": "string"
        +    },
        +    "source": {
        +      "description": "'warehouse' on /scan (both tiers); 'live-enriched' / 'cache-only' on a deep-dive result.",
        +      "type": "string"
        +    },
        +    "total": {
        +      "description": "Matching pairs in the warehouse before any cap.",
        +      "type": "number"
        +    },
        +    "truncated": {
        +      "type": "boolean"
        +    },
        +    "upgrade_hint": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "domains",
        +    "snapshot",
        +    "snapshot_source"
        +  ],
        +  "type": "object"
        +}
    • Addedctscout_search_company_batch
    • Addedctscout_submit_deep_dive
    • Addedctscout_vendor_customers
  3. 2 tool updatesv0.2.5
    • First observedctscout_lookup_domain
    • First observedctscout_search_company

TDQS

A4.8/5.0

Scored across 7 tools

Disambiguation5/5

Each tool targets a clearly distinct resource and operation: forward domain search, reverse domain lookup, batch search, LEI records, vendor customer data, and the async submit/poll pair. The descriptions cross-reference each other with explicit 'Don't use when' guidance that resolves the only potentially confusable pairs (search_company vs lookup_domain, single vs batch, submit vs poll).

Naming Consistency4/5

Six of seven tools follow the clean `ctscout_<verb>_<noun>` pattern with consistent lowercase verbs and the '_batch' modifier used sensibly. `ctscout_vendor_customers` is the one deviation, using a noun phrase rather than a verb, though it is still readable and unambiguous.

Tool Count5/5

Seven tools is well-scoped for a domain-attribution data service covering forward search, reverse lookup, LEI entity records, vendor relationships, and the deep-dive job lifecycle. Each tool earns its place with no redundant entries and no missing surface that would require a bloated count.

Completeness4/5

The core workflows are fully covered: forward/reverse lookup, batch search, LEI and vendor research products, and the submit→poll→get-result deep-dive lifecycle. Minor gaps exist — there is no job cancellation or job listing, and vendors are only reachable by slug rather than searchable by name — but agents can work around them without dead ends.

Maintenance

ActivityActive
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables ethical security testing and attack surface management through SSL certificate validation, CVE queries, subdomain enumeration, security header analysis, and comprehensive reconnaissance capabilities. Designed for authorized penetration testing workflows with responsible disclosure practices.
    -
  • A
    license
    A
    quality
    B
    maintenance
    Enables LLMs to query Certificate Transparency logs via CertIndex API, allowing searches for TLS certificates, subdomains, and certificate metadata.
    6
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Domain and company intelligence for AI agents. Enables vetting companies, qualifying leads, and mapping targets from free public data without API keys.
    MIT