Skip to main content
Glama

Devops Check Certs

devops_check_certs
Read-onlyIdempotent

Inspect SSL/TLS certificate health for one or more domains by performing a real TLS handshake. Works for any internet-accessible domain — no vendor registry required. Reports days to expiry (flagged at < 30 days warning and < 7 days critical), certificate subject and SANs, issuer, hostname coverage, chain-trust verification, TLS protocol version negotiated (flags TLS 1.0/1.1 as insecure — a server offering only those is still inspected), cipher suite, and HSTS presence when the port answers HTTP. The handshake completes even for a certificate clients would reject, so a broken certificate is reported rather than hidden behind a connection error: a hostname mismatch surfaces in cert.hostname_verification_error and a chain-trust failure (self-signed, untrusted root) in cert.authorization_error, both status "critical". Non-HTTP TLS ports (IMAPS 993, SMTPS 465, DNS over TLS 853) are graded the same way, with HSTS reported as not checked. If a domain fails to connect at all, check devops_check_dns first — the name may not resolve. When a certificate fails to issue or renew, run devops_check_dns with record_types ["CAA"] to see which certificate authorities the domain allows.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
portNoTLS port for entries that do not carry their own. Defaults to 443. Use 8443 or custom ports for non-standard HTTPS endpoints, or 993, 465, 853, and the like for other TLS services. A port written on an entry ("example.com:8443") wins for that entry.
domainsYesHosts to inspect. The first 10 entries are inspected; up to 100 are accepted, and entries past the 10th come back uninspected and verbatim in deferred_domains, to resend in another call. Each entry is read as one host: surrounding whitespace and letter case are ignored, a scheme, path, query, or fragment is dropped, an internationalized name is converted to its ASCII form ("münchen.de" → "xn--mnchen-3ya.de"), and a port written on the entry is used for that entry in place of "port". An entry with no single host — a user name before "@", a bad port, or a malformed label — gets its own result with status "error" and is never connected to; the other entries still run.
timeout_msNoTime limit in milliseconds for each domain, covering the connection, the handshake, a second handshake with TLS 1.0/1.1 enabled when the server offers nothing newer, and the HSTS request. A port that completes the handshake and then sends nothing is reported at this limit, certificate included. At least 1000; a value over 15000 is served as 15000, and the notice says so. Defaults to 5000 unless the server is configured otherwise. Increase for slow or geographically distant endpoints.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
errorNoPresent when the call failed. Absent on success.
noticeNoPresent when the call was served at a bound: hosts past the per-call cap, named in deferred_domains, or a timeout_ms over 15000 that was served as 15000. Absent otherwise.
resultsNoPer-domain certificate inspection results.
deferred_domainsNoHost entries past the first 10, not inspected on this call, verbatim and in input order. Resend them in another call. Empty when every entry was inspected.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed16 schema fields changed
    • changedInput schema / properties / domains / description
      Previous value: -"Domains to inspect. Do not include \"https://\" — pass the bare hostname. Up to 10 per call."New value: +"Hosts to inspect. The first 10 entries are inspected; up to 100 are accepted, and entries past the 10th come back uninspected and verbatim in deferred_domains, to resend in another call. Each entry is read as one host: surrounding whitespace and letter case are ignored, a scheme, path, query, or fragment is dropped, an internationalized name is converted to its ASCII form (\"münchen.de\" → \"xn--mnchen-3ya.de\"), and a port written on the entry is used for that entry in place of \"port\". An entry with no single host — a user name before \"@\", a bad port, or a malformed label — gets its own result with status \"error\" and is never connected to; the other entries still run."
    • changedInput schema / properties / domains / items / description
      Previous value: -"Domain name without protocol (e.g., \"api.github.com\", \"example.com\")."New value: +"A host to inspect: a hostname (\"api.github.com\"), an IP address, a host with a port (\"example.com:8443\", \"[2606:4700:4700::1111]:853\"), or a URL (\"https://example.com/login\"), which is reduced to its host."
    • changedInput schema / properties / domains / maxItems
      Previous value: -10New value: +100
    • changedInput schema / properties / port / description
      Previous value: -"TLS port. Defaults to 443. Use 8443 or custom ports for non-standard HTTPS endpoints."New value: +"TLS port for entries that do not carry their own. Defaults to 443. Use 8443 or custom ports for non-standard HTTPS endpoints, or 993, 465, 853, and the like for other TLS services. A port written on an entry (\"example.com:8443\") wins for that entry."
    • changedInput schema / properties / timeout_ms / description
      Previous value: -"Connection timeout per domain in milliseconds. Defaults to the DEVOPS_STATUS_CERT_TIMEOUT_MS env var (5000 when unset). Increase for slow or geographically distant endpoints."New value: +"Time limit in milliseconds for each domain, covering the connection, the handshake, a second handshake with TLS 1.0/1.1 enabled when the server offers nothing newer, and the HSTS request. A port that completes the handshake and then sends nothing is reported at this limit, certificate included. At least 1000; a value over 15000 is served as 15000, and the notice says so. Defaults to 5000 unless the server is configured otherwise. Increase for slow or geographically distant endpoints."
    • changedInput schema / properties / timeout_ms / maximum
      Previous value: -15000New value: +9007199254740991
    • changedOutput schema / anyOf
      Previous value: -[
      -  {
      -    "not": {
      -      "required": [
      -        "error"
      -      ]
      -    },
      -    "required": [
      -      "results"
      -    ]
      -  },
      -  {
      -    "required": [
      -      "error"
      -    ]
      -  }
      -]New value: +[
      +  {
      +    "not": {
      +      "required": [
      +        "error"
      +      ]
      +    },
      +    "required": [
      +      "results",
      +      "deferred_domains"
      +    ]
      +  },
      +  {
      +    "required": [
      +      "error"
      +    ]
      +  }
      +]
    • addedOutput schema / properties / deferred_domains
      Added value: +{
      +  "description": "Host entries past the first 10, not inspected on this call, verbatim and in input order. Resend them in another call. Empty when every entry was inspected.",
      +  "items": {
      +    "description": "A submitted host entry, exactly as sent.",
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `invalid_domain`: A domain string contains a protocol prefix or invalid characters. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode."
    • removedOutput schema / properties / error / properties / data / properties / reason / examples
      Removed value: -[
      -  "invalid_domain"
      -]
    • addedOutput schema / properties / notice
      Added value: +{
      +  "description": "Present when the call was served at a bound: hosts past the per-call cap, named in deferred_domains, or a timeout_ms over 15000 that was served as 15000. Absent otherwise.",
      +  "type": "string"
      +}
    • changedOutput schema / properties / results / items / properties / domain / description
      Previous value: -"The domain that was inspected."New value: +"The host that was inspected, as read from the submitted entry (lowercase, ASCII, no scheme, port, or path; an IPv6 address without brackets). For an entry with no single host, the submitted string as given."
    • changedOutput schema / properties / results / items / properties / error / description
      Previous value: -"Why status is \"error\". Either no handshake completed — the domain was rejected as a private, loopback, or cloud-metadata target, a connection failure such as \"getaddrinfo ENOTFOUND …\", or \"Timed out after 5000ms\" — with cert and tls null and flags empty; or the handshake completed but the server presented no certificate, with cert null and the TLS session still reported in tls and flags. Null for every other status."New value: +"Why status is \"error\". Either no handshake completed — the entry has no single host (the message names the entry and the accepted shapes), the host was rejected as a private, loopback, or cloud-metadata target, a connection failure such as \"getaddrinfo ENOTFOUND …\" (for a server offering only TLS 1.0/1.1 that still refuses, the failure of the legacy-version attempt), or \"Timed out after 5000ms\" when timeout_ms ran out before a handshake completed — with cert and tls null and flags empty; or the handshake completed but the server presented no certificate, with cert null and the TLS session still reported in tls and flags. Null for every other status, including a completed handshake whose port never answered HTTP."
    • changedOutput schema / properties / results / items / properties / flags / description
      Previous value: -"Human-readable findings about the certificate and TLS session that were read: \"Expires in 12 days (warning)\", \"Certificate expired 40 days ago\", \"Hostname mismatch — the certificate does not cover api.example.com; clients will reject it\", \"Certificate chain not trusted (SELF_SIGNED_CERT_IN_CHAIN); clients will reject it\", \"Self-signed certificate\", \"Insecure TLS version in use: TLSv1.1\", \"HSTS present\" / \"HSTS not configured\". Empty when the connection never completed a handshake (rejected target, connection failure, or timeout) — the reason is in \"error\" only. A handshake that completed without a certificate keeps its TLS-session findings here alongside \"error\"."New value: +"Human-readable findings about the certificate and TLS session that were read: \"Expires in 12 days (warning)\", \"Certificate expired 40 days ago\", \"Hostname mismatch — the certificate does not cover api.example.com; clients will reject it\", \"Certificate chain not trusted (SELF_SIGNED_CERT_IN_CHAIN); clients will reject it\", \"Self-signed certificate\", \"Insecure TLS version in use: TLSv1.1\", and one HSTS finding: \"HSTS present\" or \"HSTS not configured\" when the port answered the HTTP request, or \"HSTS not checked — no HTTP response on port 993\" when it did not (a non-HTTP service such as IMAPS, SMTPS, or DNS over TLS). Empty when the connection never completed a handshake (rejected target, connection failure, or timeout) — the reason is in \"error\" only. A handshake that completed without a certificate keeps its TLS-session findings here alongside \"error\"."
    • changedOutput schema / properties / results / items / properties / port / description
      Previous value: -"The port used for the TLS connection."New value: +"The port used for the TLS connection: the port written on the entry, otherwise the \"port\" input."
    • changedOutput schema / properties / results / items / properties / status / description
      Previous value: -"Overall status. \"critical\" — the certificate expires in < 7 days or has already expired, the hostname is not covered by the certificate, chain verification failed (self-signed or untrusted root), or an insecure TLS version was negotiated; every one of these is rejected by ordinary clients. \"warning\" — expires in < 30 days. \"ok\" — none of the above. \"error\" — no certificate was retrieved: the domain was rejected, the connection failed or timed out, or the server presented no certificate; the reason is in \"error\"."New value: +"Overall status. \"critical\" — the certificate expires in < 7 days or has already expired, the hostname is not covered by the certificate, chain verification failed (self-signed or untrusted root), or an insecure TLS version was negotiated (TLS 1.0/1.1, including a server that offers nothing newer); every one of these is rejected by ordinary clients. \"warning\" — expires in < 30 days. \"ok\" — none of the above. Graded from the certificate and session alone: HSTS never changes it, and neither does what the port sends after the handshake. \"error\" — no certificate was retrieved: the entry was rejected, the connection failed or timed out before the handshake completed, or the server presented no certificate; the reason is in \"error\"."
  2. Changed5 schema fields changed
    • changedOutput schema / properties / results / items / properties / cert / description
      Previous value: -"Certificate details, or null when connection failed (error status)."New value: +"Certificate details, or null when no certificate was retrieved (status \"error\")."
    • changedOutput schema / properties / results / items / properties / error / description
      Previous value: -"Connection error message when status is \"error\"."New value: +"Why status is \"error\". Either no handshake completed — the domain was rejected as a private, loopback, or cloud-metadata target, a connection failure such as \"getaddrinfo ENOTFOUND …\", or \"Timed out after 5000ms\" — with cert and tls null and flags empty; or the handshake completed but the server presented no certificate, with cert null and the TLS session still reported in tls and flags. Null for every other status."
    • changedOutput schema / properties / results / items / properties / flags / description
      Previous value: -"Human-readable warnings and issues found: \"Expires in 12 days (warning)\", \"Certificate expired 40 days ago\", \"Hostname mismatch — the certificate does not cover api.example.com; clients will reject it\", \"Certificate chain not trusted (SELF_SIGNED_CERT_IN_CHAIN); clients will reject it\", \"Self-signed certificate\", \"Insecure TLS version in use: TLSv1.1\", \"HSTS present\" / \"HSTS not configured\"."New value: +"Human-readable findings about the certificate and TLS session that were read: \"Expires in 12 days (warning)\", \"Certificate expired 40 days ago\", \"Hostname mismatch — the certificate does not cover api.example.com; clients will reject it\", \"Certificate chain not trusted (SELF_SIGNED_CERT_IN_CHAIN); clients will reject it\", \"Self-signed certificate\", \"Insecure TLS version in use: TLSv1.1\", \"HSTS present\" / \"HSTS not configured\". Empty when the connection never completed a handshake (rejected target, connection failure, or timeout) — the reason is in \"error\" only. A handshake that completed without a certificate keeps its TLS-session findings here alongside \"error\"."
    • changedOutput schema / properties / results / items / properties / status / description
      Previous value: -"Overall status. \"critical\" — the certificate expires in < 7 days or has already expired, the hostname is not covered by the certificate, chain verification failed (self-signed or untrusted root), or an insecure TLS version was negotiated; every one of these is rejected by ordinary clients. \"warning\" — expires in < 30 days. \"ok\" — none of the above. \"error\" — the connection failed and no certificate was retrieved."New value: +"Overall status. \"critical\" — the certificate expires in < 7 days or has already expired, the hostname is not covered by the certificate, chain verification failed (self-signed or untrusted root), or an insecure TLS version was negotiated; every one of these is rejected by ordinary clients. \"warning\" — expires in < 30 days. \"ok\" — none of the above. \"error\" — no certificate was retrieved: the domain was rejected, the connection failed or timed out, or the server presented no certificate; the reason is in \"error\"."
    • changedOutput schema / properties / results / items / properties / tls / description
      Previous value: -"TLS session details, or null when connection failed."New value: +"TLS session details, or null when no TLS handshake completed."
  3. Changed3 schema fields changed
    • changedOutput schema / properties / results / items / properties / cert / anyOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "properties": {
      -      "authorization_error": {
      -        "anyOf": [
      -          {
      -            "type": "string"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "description": "OpenSSL chain-verification code when the certificate chain does not validate against the system trust store, or null when it validates. Common values: \"DEPTH_ZERO_SELF_SIGNED_CERT\" (self-signed leaf), \"SELF_SIGNED_CERT_IN_CHAIN\" / \"UNABLE_TO_VERIFY_LEAF_SIGNATURE\" (issuing root not trusted), \"CERT_HAS_EXPIRED\" (also reported in \"days_until_expiry\"). This is the authoritative chain-trust signal — the issuer and subject fields alone cannot detect an untrusted root."
      -      },
      -      "chain_depth": {
      -        "anyOf": [
      -          {
      -            "maximum": 9007199254740991,
      -            "minimum": -9007199254740991,
      -            "type": "integer"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "description": "Number of certificates the server sent, counting the leaf. Null when the runtime does not expose the issuer chain — read \"chain_depth_unavailable_reason\" in that case. Not a self-signed indicator: use \"authorization_error\" for that."
      -      },
      -      "chain_depth_unavailable_reason": {
      -        "anyOf": [
      -          {
      -            "type": "string"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "description": "Why \"chain_depth\" is null, or null when a depth was measured. Absence of a depth is a runtime limitation, not a finding about the certificate."
      -      },
      -      "days_until_expiry": {
      -        "description": "Days remaining until certificate expiry. Negative = already expired.",
      -        "maximum": 9007199254740991,
      -        "minimum": -9007199254740991,
      -        "type": "integer"
      -      },
      -      "hostname_verification_error": {
      -        "anyOf": [
      -          {
      -            "type": "string"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "description": "Node's hostname-verification message when the requested domain is not covered by the certificate's CN or SANs (e.g. \"Hostname/IP does not match certificate's altnames: …\"), or null when the hostname is covered. A non-null value means ordinary clients reject this certificate for this hostname; compare against the \"san\" list to see what it does cover."
      -      },
      -      "issuer": {
      -        "description": "Issuer common name.",
      -        "type": "string"
      -      },
      -      "san": {
      -        "description": "Subject Alternative Names covered by this certificate.",
      -        "items": {
      -          "type": "string"
      -        },
      -        "type": "array"
      -      },
      -      "serial": {
      -        "description": "Certificate serial number.",
      -        "type": "string"
      -      },
      -      "subject": {
      -        "description": "Certificate subject CN.",
      -        "type": "string"
      -      },
      -      "valid_from": {
      -        "description": "ISO 8601 UTC timestamp of certificate validity start.",
      -        "type": "string"
      -      },
      -      "valid_until": {
      -        "description": "ISO 8601 UTC timestamp of certificate expiry.",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "subject",
      -      "san",
      -      "issuer",
      -      "valid_from",
      -      "valid_until",
      -      "days_until_expiry",
      -      "chain_depth",
      -      "chain_depth_unavailable_reason",
      -      "hostname_verification_error",
      -      "authorization_error",
      -      "serial"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "authorization_error": {
      +        "description": "OpenSSL chain-verification code when the certificate chain does not validate against the system trust store, or null when it validates. Common values: \"DEPTH_ZERO_SELF_SIGNED_CERT\" (self-signed leaf), \"SELF_SIGNED_CERT_IN_CHAIN\" / \"UNABLE_TO_VERIFY_LEAF_SIGNATURE\" (issuing root not trusted), \"CERT_HAS_EXPIRED\" (also reported in \"days_until_expiry\"). This is the authoritative chain-trust signal — the issuer and subject fields alone cannot detect an untrusted root.",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "chain_depth": {
      +        "anyOf": [
      +          {
      +            "maximum": 9007199254740991,
      +            "minimum": -9007199254740991,
      +            "type": "integer"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "Number of certificates the server sent, counting the leaf. Null when the runtime does not expose the issuer chain — read \"chain_depth_unavailable_reason\" in that case. Not a self-signed indicator: use \"authorization_error\" for that."
      +      },
      +      "chain_depth_unavailable_reason": {
      +        "description": "Why \"chain_depth\" is null, or null when a depth was measured. Absence of a depth is a runtime limitation, not a finding about the certificate.",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "days_until_expiry": {
      +        "description": "Days remaining until certificate expiry. Negative = already expired.",
      +        "maximum": 9007199254740991,
      +        "minimum": -9007199254740991,
      +        "type": "integer"
      +      },
      +      "hostname_verification_error": {
      +        "description": "Node's hostname-verification message when the requested domain is not covered by the certificate's CN or SANs (e.g. \"Hostname/IP does not match certificate's altnames: …\"), or null when the hostname is covered. A non-null value means ordinary clients reject this certificate for this hostname; compare against the \"san\" list to see what it does cover.",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "issuer": {
      +        "description": "Issuer common name.",
      +        "type": "string"
      +      },
      +      "san": {
      +        "description": "Subject Alternative Names covered by this certificate.",
      +        "items": {
      +          "type": "string"
      +        },
      +        "type": "array"
      +      },
      +      "serial": {
      +        "description": "Certificate serial number.",
      +        "type": "string"
      +      },
      +      "subject": {
      +        "description": "Certificate subject CN.",
      +        "type": "string"
      +      },
      +      "valid_from": {
      +        "description": "ISO 8601 UTC timestamp of certificate validity start.",
      +        "type": "string"
      +      },
      +      "valid_until": {
      +        "description": "ISO 8601 UTC timestamp of certificate expiry.",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "subject",
      +      "san",
      +      "issuer",
      +      "valid_from",
      +      "valid_until",
      +      "days_until_expiry",
      +      "chain_depth",
      +      "chain_depth_unavailable_reason",
      +      "hostname_verification_error",
      +      "authorization_error",
      +      "serial"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • removedOutput schema / properties / results / items / properties / error / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / results / items / properties / error / type
      Added value: +[
      +  "string",
      +  "null"
      +]
  4. Changed6 schema 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 / additionalProperties
      Added value: +false
    • changedOutput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedOutput schema / anyOf
      Added value: +[
      +  {
      +    "not": {
      +      "required": [
      +        "error"
      +      ]
      +    },
      +    "required": [
      +      "results"
      +    ]
      +  },
      +  {
      +    "required": [
      +      "error"
      +    ]
      +  }
      +]
    • addedOutput schema / properties / error
      Added value: +{
      +  "additionalProperties": {},
      +  "description": "Present when the call failed. Absent on success.",
      +  "properties": {
      +    "code": {
      +      "description": "JSON-RPC error code for this failure.",
      +      "maximum": 9007199254740991,
      +      "minimum": -9007199254740991,
      +      "type": "integer"
      +    },
      +    "data": {
      +      "additionalProperties": {},
      +      "properties": {
      +        "reason": {
      +          "description": "Machine-readable failure mode. Declared by this tool: `invalid_domain`: A domain string contains a protocol prefix or invalid characters. Other values are possible when a failure originates below the handler.",
      +          "examples": [
      +            "invalid_domain"
      +          ],
      +          "type": "string"
      +        },
      +        "recovery": {
      +          "additionalProperties": {},
      +          "description": "Actionable next step for the caller.",
      +          "properties": {
      +            "hint": {
      +              "type": "string"
      +            }
      +          },
      +          "required": [
      +            "hint"
      +          ],
      +          "type": "object"
      +        },
      +        "retryable": {
      +          "description": "Whether retrying may succeed.",
      +          "type": "boolean"
      +        }
      +      },
      +      "type": "object"
      +    },
      +    "message": {
      +      "description": "Human-readable description of what went wrong.",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "code",
      +    "message"
      +  ],
      +  "type": "object"
      +}
    • removedOutput schema / required
      Removed value: -[
      -  "results"
      -]
  5. Changed3 schema fields changed
    • changedOutput schema / properties / results / items / properties / cert / anyOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "properties": {
      -      "chain_depth": {
      -        "description": "Number of certificates in the chain (1 = self-signed).",
      -        "maximum": 9007199254740991,
      -        "minimum": -9007199254740991,
      -        "type": "integer"
      -      },
      -      "days_until_expiry": {
      -        "description": "Days remaining until certificate expiry. Negative = already expired.",
      -        "maximum": 9007199254740991,
      -        "minimum": -9007199254740991,
      -        "type": "integer"
      -      },
      -      "issuer": {
      -        "description": "Issuer common name.",
      -        "type": "string"
      -      },
      -      "san": {
      -        "description": "Subject Alternative Names covered by this certificate.",
      -        "items": {
      -          "type": "string"
      -        },
      -        "type": "array"
      -      },
      -      "serial": {
      -        "description": "Certificate serial number.",
      -        "type": "string"
      -      },
      -      "subject": {
      -        "description": "Certificate subject CN.",
      -        "type": "string"
      -      },
      -      "valid_from": {
      -        "description": "ISO 8601 UTC timestamp of certificate validity start.",
      -        "type": "string"
      -      },
      -      "valid_until": {
      -        "description": "ISO 8601 UTC timestamp of certificate expiry.",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "subject",
      -      "san",
      -      "issuer",
      -      "valid_from",
      -      "valid_until",
      -      "days_until_expiry",
      -      "chain_depth",
      -      "serial"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "authorization_error": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "OpenSSL chain-verification code when the certificate chain does not validate against the system trust store, or null when it validates. Common values: \"DEPTH_ZERO_SELF_SIGNED_CERT\" (self-signed leaf), \"SELF_SIGNED_CERT_IN_CHAIN\" / \"UNABLE_TO_VERIFY_LEAF_SIGNATURE\" (issuing root not trusted), \"CERT_HAS_EXPIRED\" (also reported in \"days_until_expiry\"). This is the authoritative chain-trust signal — the issuer and subject fields alone cannot detect an untrusted root."
      +      },
      +      "chain_depth": {
      +        "anyOf": [
      +          {
      +            "maximum": 9007199254740991,
      +            "minimum": -9007199254740991,
      +            "type": "integer"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "Number of certificates the server sent, counting the leaf. Null when the runtime does not expose the issuer chain — read \"chain_depth_unavailable_reason\" in that case. Not a self-signed indicator: use \"authorization_error\" for that."
      +      },
      +      "chain_depth_unavailable_reason": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "Why \"chain_depth\" is null, or null when a depth was measured. Absence of a depth is a runtime limitation, not a finding about the certificate."
      +      },
      +      "days_until_expiry": {
      +        "description": "Days remaining until certificate expiry. Negative = already expired.",
      +        "maximum": 9007199254740991,
      +        "minimum": -9007199254740991,
      +        "type": "integer"
      +      },
      +      "hostname_verification_error": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "Node's hostname-verification message when the requested domain is not covered by the certificate's CN or SANs (e.g. \"Hostname/IP does not match certificate's altnames: …\"), or null when the hostname is covered. A non-null value means ordinary clients reject this certificate for this hostname; compare against the \"san\" list to see what it does cover."
      +      },
      +      "issuer": {
      +        "description": "Issuer common name.",
      +        "type": "string"
      +      },
      +      "san": {
      +        "description": "Subject Alternative Names covered by this certificate.",
      +        "items": {
      +          "type": "string"
      +        },
      +        "type": "array"
      +      },
      +      "serial": {
      +        "description": "Certificate serial number.",
      +        "type": "string"
      +      },
      +      "subject": {
      +        "description": "Certificate subject CN.",
      +        "type": "string"
      +      },
      +      "valid_from": {
      +        "description": "ISO 8601 UTC timestamp of certificate validity start.",
      +        "type": "string"
      +      },
      +      "valid_until": {
      +        "description": "ISO 8601 UTC timestamp of certificate expiry.",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "subject",
      +      "san",
      +      "issuer",
      +      "valid_from",
      +      "valid_until",
      +      "days_until_expiry",
      +      "chain_depth",
      +      "chain_depth_unavailable_reason",
      +      "hostname_verification_error",
      +      "authorization_error",
      +      "serial"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • changedOutput schema / properties / results / items / properties / flags / description
      Previous value: -"Human-readable warnings and issues found: \"expires in 12 days\", \"TLS 1.1 in use\", \"self-signed certificate\", \"HSTS present\", etc."New value: +"Human-readable warnings and issues found: \"Expires in 12 days (warning)\", \"Certificate expired 40 days ago\", \"Hostname mismatch — the certificate does not cover api.example.com; clients will reject it\", \"Certificate chain not trusted (SELF_SIGNED_CERT_IN_CHAIN); clients will reject it\", \"Self-signed certificate\", \"Insecure TLS version in use: TLSv1.1\", \"HSTS present\" / \"HSTS not configured\"."
    • changedOutput schema / properties / results / items / properties / status / description
      Previous value: -"Overall status: ok, warning (< 30 days), critical (< 7 days or insecure TLS), or error (connection failed)."New value: +"Overall status. \"critical\" — the certificate expires in < 7 days or has already expired, the hostname is not covered by the certificate, chain verification failed (self-signed or untrusted root), or an insecure TLS version was negotiated; every one of these is rejected by ordinary clients. \"warning\" — expires in < 30 days. \"ok\" — none of the above. \"error\" — the connection failed and no certificate was retrieved."
  6. Changed2 schema fields changed
    • addedInput schema / properties / domains / items / minLength
      Added value: +1
    • removedInput schema / properties / domains / items / pattern
      Removed value: -"^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
  7. Changed1 schema field changed
    • changedInput schema / properties / timeout_ms / description
      Previous value: -"Connection timeout per domain in milliseconds. Increase for slow or geographically distant endpoints."New value: +"Connection timeout per domain in milliseconds. Defaults to the DEVOPS_STATUS_CERT_TIMEOUT_MS env var (5000 when unset). Increase for slow or geographically distant endpoints."
  8. Added

TDQS

A4.8/5.0
Behavior5/5

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

Annotations cover the safety profile (readOnly, idempotent, openWorld), and the description goes well beyond them: it discloses that the handshake completes even for certs clients would reject, where failures surface (cert.hostname_verification_error, cert.authorization_error, both "critical"), how non-HTTP TLS ports are graded, and that HSTS is reported as not checked. This is rich behavioral context an agent could not get from 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.

Conciseness4/5

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

Front-loaded with purpose and handshake mechanics, then failure semantics, then the routing advice. It is dense and long, but nearly every sentence carries information; only the SAN/issuer/cipher enumeration is close to filler.

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?

An output schema exists, so return values need not be spelled out, and the description still names the key error fields and their critical status. For a read-only inspection tool with three documented params, nothing an agent needs to call it 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 coverage is 100%, so the baseline is 3; the description modestly reinforces usage by noting entry-level ports win over the port param and by framing domains as hosts/URLs reduced to their host. It adds little syntax beyond the schema, but the extra behavioral framing of deferred entries nudges it above baseline.

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?

States a specific verb and resource ("Inspect SSL/TLS certificate health") and the mechanism (a real TLS handshake), and explicitly scopes it to any internet-accessible domain rather than a vendor registry. This is immediately distinguishable from the DNS, incidents, and vendor siblings.

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?

Names the alternative explicitly and the condition that selects it: use devops_check_dns when the domain fails to connect, and with record_types ["CAA"] when issuance/renewal fails. These are concrete when-to-use and when-to-switch rules, not inference.

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

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.