Skip to main content
Glama

domain_fetch_domain_history

Retrieve historical SSL certificate records for any domain using Certificate Transparency logs. Provides past certificate data for security and compliance analysis.

Instructions

Use this to get historical SSL certificate records for a domain. Provide the domain name. Returns past certificates from Certificate Transparency logs.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
domainYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The actual tool handler 'fetch_domain_history' (tool name 'domain_fetch_domain_history' when mounted under the 'domain' namespace). Fetches historical SSL certificate records for a domain from crt.sh Certificate Transparency logs. Uses a wildcard query to capture all subdomain certs, falls back to exact domain. Returns certificate events with logging dates, validity periods, issuer, and names.
    # ══════════════════════════════════════════════════════════════════════════════
    # DATA TOOL 4 — fetch_domain_history
    # ══════════════════════════════════════════════════════════════════════════════
    
    @mcp.tool()
    @with_timeout
    @verify_entitlement("T07")
    async def fetch_domain_history(domain: str) -> dict:
        """Use this to get historical SSL certificate records for a domain.
        Provide the domain name.
        Returns past certificates from Certificate Transparency logs."""
        domain_clean = domain.strip().lower().lstrip("www.").split("/")[0]
        params = {"domain": domain_clean, "query_type": "history"}
    
        async with AuditContext("T07", params, "1.0") as ctx:
            _incr_calls("T07")
            phash = make_params_hash(params)
    
            cached = get_cached("T07", phash)
            if cached:
                ctx.set_cache_hit(True)
                return {
                    **cached,
                    **standard_response_fields(
                        ctx.query_hash,
                        cached.get("data_as_of", ""),
                        True,
                    ),
                    "cache_hit": True,
                }
    
            if is_tripped("crt_sh"):
                return error_response(
                    ErrorCode.CIRCUIT_OPEN,
                    "crt.sh Certificate Transparency temporarily unavailable. Try again later.",
                    ctx.query_hash, 300, False,
                )
    
            try:
                # Broader wildcard query to capture all subdomains' historical certs
                history = await _fetch_crt_sh(f"%.{domain_clean}", limit=50)
                if not history:
                    # Fallback to exact domain
                    history = await _fetch_crt_sh(domain_clean, limit=50)
            except httpx.TimeoutException:
                record_failure_sync("crt_sh")
                return error_response(
                    ErrorCode.UPSTREAM_TIMEOUT,
                    "crt.sh timed out. Try again shortly.",
                    ctx.query_hash, 30, False,
                )
            except Exception:
                record_failure_sync("crt_sh")
                log.exception("t07.fetch_domain_history error domain=%s", domain_clean)
                return error_response(
                    ErrorCode.INTERNAL_ERROR,
                    "An internal error occurred. Please try again.",
                    ctx.query_hash, 0, False,
                )
    
            data_as_of   = datetime.now(timezone.utc).isoformat()
            raw_bytes    = json.dumps(history).encode()
            payload_hash = compute_payload_hash(raw_bytes)
            markdown     = _build_history_markdown(history, domain_clean)
            _validate_canary(markdown)
    
            payload = {
                "tool_id":         "T07",
                "source_url":      f"https://crt.sh/?q=%.{domain_clean}&output=json",
                "fetch_timestamp": data_as_of,
                "cache_hit":       False,
                "staleness_notice": None,
                "sha256_hash":     payload_hash,
                "data":            {"domain": domain_clean, "certificate_events": history, "count": len(history)},
                "markdown_output": markdown,
                "disclaimer":      DISCLAIMER,
                "data_as_of":      data_as_of,
                "ingest_healthy":  True,
            }
    
            set_cached("T07", phash, payload, T07_TTL)
            ctx.set_cache_hit(False)
            record_success_sync("crt_sh")
    
            log.info("t07.fetch_domain_history ok domain=%s events=%d",
                     domain_clean, len(history))
            return {**payload, **standard_response_fields(ctx.query_hash, data_as_of, True)}
  • Input schema: function signature takes a single 'domain: str' parameter. No explicit Pydantic schema class — uses FastMCP type inference from the function signature.
    async def fetch_domain_history(domain: str) -> dict:
  • Helper '_fetch_crt_sh' — the upstream fetch function that queries crt.sh JSON API, used by both fetch_ssl_certificate_chain and fetch_domain_history.
    async def _fetch_crt_sh(query: str, limit: int = 10) -> list:
        async with httpx.AsyncClient(
            timeout=httpx.Timeout(20.0, connect=5.0),
            headers=_HEADERS,
            follow_redirects=True,
        ) as client:
            resp = await client.get(
                CRT_SH_URL,
                params={"q": query, "output": "json"},
            )
            if resp.status_code == 404:
                return []
            resp.raise_for_status()
    
            try:
                data = resp.json()
            except Exception:
                return []
    
        if not isinstance(data, list):
            return []
    
        # Deduplicate by certificate serial / subject and limit
        seen_ids: set = set()
        certs = []
        for entry in data:
            cert_id = entry.get("id") or entry.get("serial_number", "")
            if cert_id in seen_ids:
                continue
            seen_ids.add(cert_id)
            certs.append({
                "id":           entry.get("id", ""),
                "logged_at":    (entry.get("logged_at") or "")[:10],
                "not_before":   (entry.get("not_before") or "")[:10],
                "not_after":    (entry.get("not_after") or "")[:10],
                "common_name":  entry.get("common_name", ""),
                "issuer_name":  entry.get("issuer_name", ""),
                "name_value":   entry.get("name_value", ""),
            })
            if len(certs) >= limit:
                break
    
        return certs
  • Helper '_build_history_markdown' — builds the markdown table output for fetch_domain_history results.
    def _build_history_markdown(history: list, domain: str) -> str:
        lines = [
            f"## SSL Certificate History — {domain}",
            f"Found **{len(history)}** certificate event(s) in CT logs.\n",
        ]
    
        if not history:
            lines.append("No historical certificate records found.")
        else:
            lines.append("| Logged | Valid From | Valid To | Issuer | Name/SAN |")
            lines.append("|--------|------------|----------|--------|----------|")
            for c in history[:25]:  # cap display at 25 rows
                issuer_short = c.get("issuer_name", "")[:35]
                name = (c.get("common_name") or c.get("name_value", ""))[:40]
                lines.append(
                    f"| {c.get('logged_at','')} "
                    f"| {c.get('not_before','')} "
                    f"| {c.get('not_after','')} "
                    f"| {issuer_short} "
                    f"| {name} |"
                )
            if len(history) > 25:
                lines.append(f"\n*…and {len(history)-25} more records (showing most recent 25).*")
    
        lines.append("")
        lines.append(f"*{DISCLAIMER}*")
        return "\n".join(lines)
  • Registration: fetch_domain_history is imported from t07.py and registered via domain.tool()(fetch_domain_history) on a FastMCP sub-server named 'domain'. This is mounted in main.py under the 'domain' namespace, so the full MCP tool name becomes 'domain_fetch_domain_history'.
        fetch_domain_history,
    )
    
    domain = FastMCP("DataNexus Domain")
    
    domain.tool()(fetch_domain_rdap)
    domain.tool()(fetch_ssl_certificate_chain)
    domain.tool()(fetch_dns_records)
    domain.tool()(fetch_domain_history)
Behavior2/5

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

With no annotations, the description must fully disclose behavior. It states the data source (CT logs) but omits potential issues like no results, rate limits, or authentication needs. This lack of transparency for a read operation leaves gaps.

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 concise with three sentences, front-loading the purpose. No extraneous text, but it could be slightly more streamlined.

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

Completeness3/5

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

Given the tool's simplicity, one parameter, and an existing output schema, the description covers the core behavior. However, it does not address edge cases (e.g., missing domain) or limitations, leaving it adequate but not fully complete.

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

Parameters2/5

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

The only parameter 'domain' has 0% schema description coverage. The description merely repeats 'Provide the domain name' without specifying format (e.g., FQDN, with/without www) or constraints, adding minimal value beyond the schema.

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 retrieves historical SSL certificate records for a domain from Certificate Transparency logs, using specific verbs (get, returns) and distinguishing it from sibling tools like domain_fetch_dns_records or domain_fetch_ssl_certificate_chain.

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

Usage Guidelines4/5

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

The description tells the agent to use this for historical SSL certificates and to provide the domain name. While it doesn't explicitly exclude alternatives, the context of sibling tools makes the use case clear, earning a score of 4.

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

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/datanexusmcp/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server