toolhq.io

All posts
7 min readby Jameel Haider

How to write your first agent skill

The format is simple enough that the writing is the hard part. This walks through one real skill, a TLS certificate triage skill, from empty folder to something that fires when you want it to.

If you have not met the format yet, what agent skills are covers the structure first.

Step 1: pick a job that repeats

The test for whether something should be a skill is not "is it useful" but "have I explained this more than twice".

Good candidates have an order of operations, a few traps that catch everyone, and a shape of answer you want. Certificate problems qualify: there is a correct order to check things in, almost everyone jumps to the wrong conclusion first, and the useful output is always the same three facts.

Bad candidates are one off requests and anything with a single correct answer that a model cannot compute reliably.

Step 2: make the folder

In a repository, so your team gets it too:

mkdir -p .claude/skills/tls-triage

For a skill you want everywhere, use ~/.claude/skills/tls-triage instead. Both are read from disk, with no upload step.

The folder name becomes the skill name if you leave the frontmatter field out, so name the folder properly.

Step 3: write the description first

This feels backwards and it is still right. The description is the only part loaded all the time, and it is what the assistant matches a request against. If it is wrong, nothing else in the file will ever be read.

Write it by imagining the sentences a real person types. Not "TLS certificate analysis utilities", but the actual words: the cert expired, it works in Chrome but curl fails, is my chain complete, why does it say untrusted.

---
name: tls-triage
description: Diagnose TLS and certificate faults on a live host, including
  expiry, incomplete chains, hostname mismatches and untrusted roots. Use
  when a site shows a certificate warning, a client reports an untrusted
  or expired certificate, curl fails but a browser works, or someone asks
  whether a certificate chain is complete.
---

Two habits that pay off. State what it does and when to use it, because the second half is what actually routes the request. And include the failure wording people really use, because "works in Chrome but not curl" is a phrase someone will type and "incomplete intermediate chain" is not.

Step 4: write the body as a procedure

The body is instructions for an assistant, so write imperatively and put the order in it. The most valuable content is not the explanation, it is the sequence and the traps.

# TLS triage

Check in this order. Each step rules out a cause, so do not skip ahead.
Never state a certificate's expiry, issuer or chain from memory: look it up.

## 1. What is actually served

Run `ssl_check` against the host. Record the subject, the issuer, the
expiry date and whether the chain is complete.

If the host uses a port other than 443, pass it explicitly.

## 2. Expiry

An expired leaf is the most common cause and the easiest to confirm.
Report the exact expiry date, not "soon".

## 3. Chain completeness

This is the most common cause that gets misdiagnosed. A browser may
succeed where curl fails, because browsers cache intermediates from
previous sites and other clients do not.

So "it works in my browser" is not evidence the chain is complete. Trust
the check, not the browser.

If an intermediate is missing, the fix is on the server: serve the full
chain, leaf first, then intermediates. Do not add the root.

## 4. Hostname match

Confirm the name requested appears in the subject or the subject
alternative names. A wildcard covers one label only: `*.example.com`
matches `www.example.com` and does not match `a.b.example.com`.

## 5. Trust

If the chain is complete and unexpired but still untrusted, the root is
not in the client's trust store. Self signed and internal CA
certificates land here.

## Report back

Always give these three, in this order:
1. The cause, in one sentence.
2. The evidence, with the dates and names from the check.
3. The fix, as the change to make on the server.

Notice what is doing the work there. The browser caching trap in step 3 is the single most useful line in the file, because it is the thing people get wrong and no amount of general knowledge supplies it. That is the kind of content worth writing down.

Step 5: give it something that can actually look

The skill above calls ssl_check, which does not exist until an assistant has a way to make a real TLS connection. Instructions alone cannot do this, and skills running through the API have no network access at all.

That capability comes from an MCP server:

claude mcp add --transport http toolhq https://toolhq.io/api/mcp

That adds eight read only checks, ssl_check among them. In Claude Code these appear with a prefixed name of the form mcp__toolhq__ssl_check, so if you want to be unambiguous in a skill, name the server and the check rather than the bare word. The toolhq MCP page lists all eight.

State the dependency in the skill body too, so a reader without the server knows why nothing works:

## Requires

The toolhq MCP server, for `ssl_check` and `dns_lookup`:
`claude mcp add --transport http toolhq https://toolhq.io/api/mcp`

Step 6: split out what you do not always need

Keep SKILL.md to the decisions. The moment you find yourself writing reference material, put it in a sibling file and point at it:

tls-triage/
  SKILL.md
  chain-order.md      how to assemble a fullchain for nginx and apache
  trust-stores.md     which roots each client trusts
For the exact server config, see [chain-order.md](chain-order.md).

Those files cost nothing until something reads them. The guidance is to keep SKILL.md short and keep references one level deep, so an assistant does not have to chase a chain of files to find an answer.

Step 7: test it properly

A skill that never fires is the normal outcome of a first attempt, and you will not notice unless you check. Two separate things to test.

Does it trigger? Start a fresh session and type a request in the words a real person would use, without naming the skill. "The cert on staging is throwing an untrusted error." If the skill does not load, the description is the problem, not the body. Add the vocabulary that was missing.

Does it produce the right work? Give it a case where you know the answer. A host with a deliberately incomplete chain is ideal, and you can confirm the truth independently in the SSL chain fixer. Then check the assistant followed the order and did not skip to a conclusion.

The second test is where most of the value is. How to test an agent skill goes through the failure modes in more detail, and testing an MCP server before agents use it covers the other half of the pair.

What to write down, and what to leave out

The rule that has held up: write the things that are not in the model already.

An assistant already knows what a certificate authority is. It does not know that your internal CA is only in the trust store on the build images, that port 8443 is the one that matters, or that the browser caching trap exists. Skip the explanation of TLS. Write the order, the traps, and your house conventions.

A short skill full of hard won specifics beats a long one that restates the manual.