<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: zhong_theDevOps</title>
    <description>The latest articles on DEV Community by zhong_theDevOps (@zhong_thedevops).</description>
    <link>https://dev.to/zhong_thedevops</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4170541%2F57f9d2bf-dfd5-480b-974a-3f1fd9092cbb.png</url>
      <title>DEV Community: zhong_theDevOps</title>
      <link>https://dev.to/zhong_thedevops</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zhong_thedevops"/>
    <language>en</language>
    <item>
      <title>I run ten domains. My AI assistant monitors all of them, and I never opened the dashboard (MCP + webhooks)</title>
      <dc:creator>zhong_theDevOps</dc:creator>
      <pubDate>Sat, 10 Oct 2026 08:52:30 +0000</pubDate>
      <link>https://dev.to/zhong_thedevops/i-run-ten-domains-my-ai-assistant-monitors-all-of-them-and-i-never-opened-the-dashboard-mcp--41b8</link>
      <guid>https://dev.to/zhong_thedevops/i-run-ten-domains-my-ai-assistant-monitors-all-of-them-and-i-never-opened-the-dashboard-mcp--41b8</guid>
      <description>&lt;p&gt;I look after a fleet of domains — client sites, marketing properties, a couple of internal tools. For most of my career the monitoring loop was: open dashboard, scan a grid of expiry dates, close tab, carry on with whatever was actually broken that day.&lt;/p&gt;

&lt;p&gt;It is a terrible workflow and I defended it for years, mostly because the dashboard was &lt;em&gt;there&lt;/em&gt;. The whole loop depended on me remembering to perform it.&lt;/p&gt;

&lt;p&gt;Then I realised I had an AI assistant open all day, it could read structured data better than I could scan a grid, and the only thing between us was a small amount of configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I got wrong first
&lt;/h2&gt;

&lt;p&gt;My instinct was to hand the assistant a REST API and let it work out the rest. That works, and it's also a bad idea — you'll spend a week explaining the auth model to it.&lt;/p&gt;

&lt;p&gt;The much better pattern is to look for documentation written &lt;em&gt;for&lt;/em&gt; it. Not a docs site with a sidebar. A single file the model can read once and then act from.&lt;/p&gt;

&lt;p&gt;Certack ships several, which is what made the setup short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/SKILL.md       17 KB   the operational file: endpoints, auth, gotchas
/llms.txt        2.6 KB index + one-prompt onboarding
/llms-full.txt   2.8 KB extended summary: features, plans, integrations
/openapi.yaml     29 KB machine-readable API definition
/docs/api.txt    5.8 KB the full reference, as plain text for a context window
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last one matters more than people expect. HTML documentation has to be scraped and re-read on every call. A plain-text reference drops straight into a prompt and stays cached. I put the whole thing in context once at the start of a session and it works for the rest of the session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step one: a keyless endpoint, so there's nothing to leak
&lt;/h2&gt;

&lt;p&gt;Before configuring anything, I wanted to answer "is that certificate fine?" without provisioning a credential. Plenty of APIs let you create a key for that, which is a secret to rotate and keep out of config files forever.&lt;/p&gt;

&lt;p&gt;Certack exposes a public MCP endpoint — no key, rate limited per IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"certack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.certack.com/v1/public/mcp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three tools: &lt;code&gt;check_ssl&lt;/code&gt;, &lt;code&gt;check_dns&lt;/code&gt;, &lt;code&gt;check_domain&lt;/code&gt;. That's the entire unauthenticated surface, and it covers most of what I want to ask ad-hoc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step two: actually ask it something
&lt;/h2&gt;

&lt;p&gt;A real &lt;code&gt;tools/call&lt;/code&gt; against &lt;code&gt;example.com&lt;/code&gt;, and the response verbatim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tools/call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"check_ssl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"valid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issuer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CN=Cloudflare TLS Issuing ECC CA 3,O=SSL Corporation,C=US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"days_remaining"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"san"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*.example.com"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"protocol_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TLSv1.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cipher_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TLS_AES_128_GCM_SHA256"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seventeen fields come back in total — &lt;code&gt;chain&lt;/code&gt;, &lt;code&gt;chain_complete&lt;/code&gt;, &lt;code&gt;chain_error&lt;/code&gt;, &lt;code&gt;cipher_strength&lt;/code&gt;, &lt;code&gt;hsts&lt;/code&gt;, &lt;code&gt;mixed_content&lt;/code&gt;, &lt;code&gt;ocsp_stapling&lt;/code&gt;, &lt;code&gt;expires_at&lt;/code&gt;, and the rest.&lt;/p&gt;

&lt;p&gt;What matters for an assistant is that they're &lt;strong&gt;structured and flat&lt;/strong&gt;. No HTML to interpret, no prose to summarise. The model reads &lt;code&gt;days_remaining: 76&lt;/code&gt; and knows that's fine; it doesn't have to reason about what "76 days remaining" means when phrased in a paragraph.&lt;/p&gt;

&lt;p&gt;But notice what came back &lt;strong&gt;empty&lt;/strong&gt; on this call: &lt;code&gt;chain_complete&lt;/code&gt;, &lt;code&gt;ocsp_stapling&lt;/code&gt; and the &lt;code&gt;hsts&lt;/code&gt; object. Absent data, not &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So the useful prompt isn't "check my domains". It's closer to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before you change anything on these domains, check the certificate and tell me if anything expires in under 30 days. If a field comes back empty, say so rather than assuming it's fine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That second sentence is most of the trick. An assistant with structured data will confidently interpolate across gaps unless you tell it not to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step three: let it act, not just read
&lt;/h2&gt;

&lt;p&gt;The keyless endpoint is read-only by design, which is correct. But reading wasn't the part I wanted — I wanted it to &lt;em&gt;do&lt;/em&gt; things.&lt;/p&gt;

&lt;p&gt;With an authenticated endpoint on the same protocol (12 tools: add and remove sites, update per-site thresholds and check types, list and resolve alerts, pull certificate history) the conversation changes shape. Things I now say out loud:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Add &lt;code&gt;staging.example.com&lt;/code&gt; to monitoring, check types ssl and dns, alert me 14 days out."&lt;/li&gt;
&lt;li&gt;"What's unacknowledged across the fleet?"&lt;/li&gt;
&lt;li&gt;"&lt;code&gt;example.org&lt;/code&gt; — resolve that alert, I renewed it this morning."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No dashboard, no hunting for the right form.&lt;/p&gt;

&lt;p&gt;Two configuration details worth copying. First, &lt;strong&gt;the alert threshold is one number per site&lt;/strong&gt;, not a five-stage ladder: I set a lead time and get a warning at the lead time plus a critical at a quarter of it. Second, &lt;strong&gt;check cadence is fixed&lt;/strong&gt; — daily for certificates and DNS, weekly for domains, dropping to a two-hour DNS retry when resolution fails — and it's the same on every plan. Paying more buys sites and alert channels, not a faster poll. Knowing that up front stopped me from expecting the paid tier to poll hourly, which it doesn't.&lt;/p&gt;

&lt;p&gt;I still keep confirmation for anything destructive. The skill file documents what each tool does, including that removing a site deletes it, so the assistant can tell me that &lt;em&gt;before&lt;/em&gt; doing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step four: the part everyone needs and nobody builds
&lt;/h2&gt;

&lt;p&gt;Agent-initiated changes need an audit trail, and "the model did it" is not an audit trail.&lt;/p&gt;

&lt;p&gt;So the outbound side is a signed webhook — nine event types, HMAC-SHA256 — and there's one detail that took me two attempts to get right: &lt;strong&gt;the timestamp goes inside the signed payload&lt;/strong&gt;, not next to it. Sign only the body and the signature is replayable forever; anyone who captures one delivery can resend it indefinitely. Sign &lt;code&gt;timestamp + "." + body&lt;/code&gt; and you've bound it to a moment in time, which is what lets you reject it after five minutes.&lt;/p&gt;

&lt;p&gt;Then there's dedup, because webhooks are at-least-once. My first receiver returned a conflict on a duplicate, which meant the sender retried, which meant it saw a duplicate again. Duplicates are success — return 2xx and move on, keyed on a content fingerprint with a TTL.&lt;/p&gt;

&lt;p&gt;I wrote up the working receiver with all of it, because this is the part everyone reimplements badly: &lt;a href="https://certack.com?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=ai-native&amp;amp;utm_content=en" rel="noopener noreferrer"&gt;the five things every signed-webhook receiver gets wrong&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell my past self
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start keyless.&lt;/strong&gt; You get most of the value during setup and evaluation without provisioning a credential, and you learn what you actually want to automate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the machine-readable docs, not the human ones.&lt;/strong&gt; If a tool hasn't shipped an &lt;code&gt;llms.txt&lt;/code&gt;-style surface and a plain-text API reference, your assistant is scraping HTML on every call. That's your token bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ask for structured output, and be suspicious of empty fields.&lt;/strong&gt; Every monitoring API returns &lt;code&gt;false&lt;/code&gt; and &lt;code&gt;null&lt;/code&gt; for different things, and only one of those means "this is fine."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wire the outbound events early&lt;/strong&gt;, even if the inbound path works. The day you want to be &lt;em&gt;told&lt;/em&gt; about something rather than remember to ask, you'll want it already running — and that's five minutes on a weekend, not five minutes during an incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I still can't do this way
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Deciding whether a flagged certificate is a problem.&lt;/strong&gt; My monitoring flags it; I still have to know which CAs my own organisation buys from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watching my own renewal pipeline.&lt;/strong&gt; The assistant can tell me a certificate expires soon. Nothing tells me ACME renewal started failing three weeks ago — still a separate gap I haven't closed.&lt;/p&gt;

&lt;p&gt;The whole surface is documented for exactly this purpose — one file an assistant can read and act from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read https://certack.com/SKILL.md and follow the instructions to monitor example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the actual onboarding prompt, and it works in Claude, ChatGPT, Cursor and any MCP client. Raw reference at &lt;a href="https://certack.com/docs/api?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=ai-native&amp;amp;utm_content=en" rel="noopener noreferrer"&gt;/docs/api&lt;/a&gt;. Webhook events are a paid feature; the free plan covers two sites with in-dashboard alerts only, and the keyless checker above needs no account.&lt;/p&gt;




&lt;p&gt;If anyone's done this, I'm curious what bit you — the plain-text API reference, or the webhook receiver, or something I'm still doing the hard way on.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>monitoring</category>
    </item>
  </channel>
</rss>
