Skip to main content
Glama
minghsuy

ctscout

by minghsuy

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
CTSCOUT_API_KEYYesYour ctscout.dev API key. Required for both hosted and local npm modes.

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": true
}

Tools

Functions exposed to the LLM to take actions

NameDescription
ctscout_search_companyA

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:

ctscout_search_company_batchA

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:

ctscout_lookup_domainA

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:

ctscout_submit_deep_diveA

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:

ctscout_get_jobA

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:

ctscout_lookup_leiA

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:

ctscout_vendor_customersA

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:

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

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