---
title: "Publish an Agent Skills Index for Your Website"
slug: publish-agent-skills-index
published: 2026-09-27T08:23:21.893227+00:00
updated: 2026-09-27T08:23:21.893227+00:00
author: "Asif Rahman"
author_url: https://masifrahman.com
category: "AI Readiness"
tags: Agent Skills, SKILL.md, well-known, AI agent discovery, check:P3
description: "SKILL.md has a real specification. The well-known path a website uses to advertise one does not, yet. What to publish, in both places, and how to verify it."
url: https://aiscan.site/blog/publish-agent-skills-index
---

## Quick summary

| If you want to… | Do this | Time | What it changes |
|---|---|---|---|
| Give an agent a reusable, on-demand capability | Write a `SKILL.md` with `name` + `description` frontmatter, plus any scripts or reference files it needs | 20–40 min | The agent loads the instructions only when the task matches, not on every turn |
| Make that skill findable from your website | Serve a JSON index at `/.well-known/agent-skills/index.json` | 15 min | An agent that lands on your domain can list what you offer before it asks |
| Cover both conventions in the wild | Also serve `/.well-known/skills/index.json` with the same content | 5 min | Neither a legacy-path nor a new-path client comes back empty |
| Check what's live | Scan the domain at aiscan.site (check P3) or `curl` both paths yourself | 2 min | Confirms the file parses, and which path answered |

Two different things are true here, and neither one is us. The `SKILL.md` file format is a real, published specification, maintained at [agentskills.io](https://agentskills.io/specification). The two `.well-known` paths websites use to advertise one are not a joint standard; they're a convention several vendors converged on independently, and not on the same path. That gap is most of this article.

An [AIScan](https://aiscan.site/) scan checks this under **P3**, and, full disclosure up front, our own probe currently reads only the newer of the two paths. More on exactly where that leaves you further down.

## Why this file exists at all

An agent that can browse the web meets thousands of sites. Most of them it will visit once. A handful, the ones it works with daily, or the one it landed on because a user pasted a link, are worth knowing more about: not a general crawl of every page, but a packaged, reusable set of instructions for doing one thing on that particular site well.

That packaged unit is a **Skill**. Anthropic's own description of the format, [published in its developer docs](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview), frames it, in its own words, as "organized like an onboarding guide you'd create for a new team member": not a wall of text handed over on day one, but a folder the new hire opens page by page, only when a task calls for that page. A skill for filling out expense reports doesn't need to be read before the agent has an expense report to file.

Mechanically, a skill is a directory with one required file, `SKILL.md`, plus whatever else it needs:

```
publish-agent-skills-index/
├── SKILL.md          # required: frontmatter + instructions
├── scripts/           # optional: code the agent can run
├── references/        # optional: longer docs, read on demand
└── assets/             # optional: templates, schemas, examples
```

`SKILL.md` itself is YAML frontmatter followed by ordinary Markdown. The published specification sets two required fields and four optional ones:

| Field | Required | What it's for |
|---|---|---|
| `name` | Yes | Lowercase, hyphenated, ≤64 chars, must match the folder name |
| `description` | Yes | ≤1024 chars: what the skill does *and* when to use it |
| `license` | No | A license name, or a pointer to a bundled license file |
| `compatibility` | No | Environment needs, e.g. "requires Python 3.14+ and uv" |
| `metadata` | No | Free-form key/value pairs for anything the spec doesn't define |
| `allowed-tools` | No | A pre-approved tool list, marked experimental |

The `description` field is doing more work than its name suggests. It is the only thing an agent reads before deciding whether to open the file at all. According to Anthropic's own guidance on the format, this is what gets matched against the user's request, so a vague description ("Helps with PDFs") never triggers, while a specific one ("Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files…") does. That's the whole reason this loads in stages: `name` and `description` sit in every agent's context for every installed skill, all the time, at roughly 100 tokens each. The full `SKILL.md` body, recommended under 5,000 tokens, loads only once a task matches. Anything in `scripts/`, `references/` or `assets/` loads only if the body itself points there. A skill can bundle a hundred pages of reference material and cost nothing until one page is actually opened.

None of that (the folder, the frontmatter, the three-tier loading) says anything about a website. It's a convention for *packaging* a capability, agnostic to where the folder lives: on a coding agent's local disk, uploaded through an API, or fetched from somewhere else entirely. That "somewhere else" is where the second half of this article starts, and it's also where the convention stops being settled.

## The part that isn't in the spec: how a website advertises one

If skills are meant to be reusable and an agent is meant to discover them without a human pointing it at a URL, the obvious move is the one already worn smooth by `robots.txt`, `sitemap.xml` and `llms.txt`: a fixed, predictable path under `/.well-known/` that any agent can probe without being told it exists first.

Multiple sites now do exactly that. The trouble is they don't all probe, or serve, the same path.

We measured this directly across 52 documentation sites: `/.well-known/agent-skills/index.json` (the newer form) is served by 10 of them; `/.well-known/skills/index.json` (the older, shorter form) by 5; the union is 11, and only 4 sites answer at both. A client written against one path finds fewer than half of what's actually out there.

The split isn't random, and it isn't only in a sample from weeks ago. We re-verified it live while writing this piece. `docs.stripe.com` answers only the legacy path: fetched from that domain directly, it returns 200, `application/json`, listing skills named things like `connect-recommend` and `connect-required-verification-information`, each carrying a `description` and a `files` array pointing at its own `SKILL.md` and reference docs. The newer path on the same host returns Stripe's ordinary branded 404 page, confirmed against a deliberately nonexistent control path on the same host returning an identical 404 shell: this is a real absence, not a soft-200. GitBook, Pydantic, Resend, Supabase, Vercel and Prisma run the other way, answering the newer path only.

Two live files, two different shapes of the same idea. Fetched from `vercel.com` directly, the newer-path index carries a `$schema` field pointing at a discovery schema hosted under the `agentskills.io` domain, and its entries don't point at a `SKILL.md` folder at all; they point at a downloadable archive:

```json
{
  "name": "deploy-to-vercel",
  "description": "Deploy applications and websites to Vercel...",
  "type": "archive",
  "url": "https://github.com/vercel-labs/agent-skills/releases/download/.../deploy-to-vercel.tar.gz",
  "digest": "sha256:a1241958..."
}
```

Stripe's entries are closer to a directory listing: a name, a description, and a `files` array of relative paths an agent fetches individually. We checked our own site too, because the honest version of a how-to includes what we do, not only what others do: verified on aiscan.site itself, `/.well-known/agent-skills/index.json` answers 200, and its shape is a third variant again, a `schemaVersion` string, and entries carrying `skillUrl` and `claudeMd` fields that neither Stripe's nor Vercel's format uses.

| Site | Path that answers | Entry shape | Points to |
|---|---|---|---|
| Stripe (`docs.stripe.com`) | legacy only | `name`, `description`, `files[]` | individual files over HTTP |
| Vercel (`vercel.com`) | newer only | `name`, `description`, `type`, `url`, `digest` | one downloadable archive |
| AIScan (`aiscan.site`) | newer only | `schemaVersion`, `skillUrl`, `claudeMd` | its own bespoke fields |

Three real, live files, three different sets of fields, verified live on all three domains within the same afternoon. Neither shape is wrong; the specification governs what's inside a skill folder, not how an index describes where to find one. But a scanner or a client built against one shape can misread the other, and a reader trying to copy "the" format from a single example is copying a choice, not a rule. If you're writing a client against this convention today, read the file rather than assuming its shape. If you're publishing one, that same variance is your argument for keeping it boring and close to the specification's own examples rather than inventing new fields.

## Publish your own: two paths, write both

**Path 1: you already have (or are ready to write) a `SKILL.md`.** This is the every-day case: an internal process, a support workflow, an API your own agents already use, packaged once as a skill so any compatible agent can pick it up.

1. Create the folder and the required frontmatter:
   ```markdown
   ---
   name: your-skill-name
   description: What this does, specifically, and when an agent should reach for it.
   ---

   # Your Skill Name

   ## Instructions
   Step-by-step guidance the agent follows.
   ```
2. Add `scripts/`, `references/` or `assets/` only if the task genuinely needs them. An empty `references/` folder costs nothing, but don't manufacture one just to look complete.
3. Validate it before publishing. `skills-ref validate ./your-skill-name`, the reference checker the specification itself points to, catches frontmatter and naming mistakes (a name with a leading hyphen, a description over 1,024 characters) before an agent ever sees them.
4. Serve the folder somewhere fetchable: a static path on your own domain, or a release archive if you'd rather ship a single downloadable file, matching Vercel's `type: "archive"` shape.
5. Write the index and serve it at **both** paths, byte-identical:
   ```json
   {
     "skills": [
       {
         "name": "your-skill-name",
         "description": "Same description as the SKILL.md frontmatter.",
         "files": ["SKILL.md"]
       }
     ]
   }
   ```
   `/.well-known/agent-skills/index.json` and `/.well-known/skills/index.json`: same file, two paths. It costs one extra static-file rule and closes the gap this article measured on 52 real sites.

**Path 2: you run a documentation platform and want to know if it already does this for you.** Adoption tracks the platform, not the writer. On the same 52-site sample, Starlight and GitBook sites answer 3-for-3 and 2-for-2; Mintlify answers 4 of 5; Next.js custom docs sites 11 of 13; Docusaurus and Hugo both answer 2 of 3. Hand-rolled documentation stacks, sites that built their own instead of adopting one of the above, answer only 8 of 19. If your docs run on a generated platform, check whether it already writes this file before adding your own. If you hand-rolled your stack, budget the same static-file work as Path 1, because nothing is generating it for you.

Nobody's CMS panel handles this yet, ourselves included. [ThinkRank](https://thinkrank.ai) manages WordPress's robots.txt, robots meta, schema and llms.txt from one settings screen, but a skills index isn't one of its fields today, so a WordPress site adds this the same way a hand-rolled stack does: a static file dropped at both well-known paths. Same story on [StoreSEO](https://storeseo.com/) for a Shopify storefront. It generates your llms.txt from live products and pages, but a skills index today means a static asset served through a theme app extension, not a settings toggle.

## Verify it actually landed

```bash
for p in agent-skills/index.json skills/index.json; do
  echo "=== $p ==="
  curl -sL -o /tmp/skills-check.json -w '%{http_code} %{content_type}\n' \
    "https://yoursite.com/.well-known/$p"
  python3 -c "import json; d=json.load(open('/tmp/skills-check.json')); print(len(d.get('skills',[])),'skill(s) parsed')" 2>/dev/null \
    || echo "did not parse as JSON, check the content-type above"
done
```

Pass looks like `200 application/json` on the path(s) you serve, and the Python line printing a skill count rather than a parse error. Two failure modes are common, and both are silent unless you check for them. First, the file returns `200` but with `content-type: text/html`, meaning your host is answering with an app shell rather than the file you dropped; compare against a deliberately nonexistent path under the same `.well-known/` prefix, since an identical body and byte count on both means neither is really being served. Second, it validates as JSON but every `files` entry 404s, because the referenced `SKILL.md` never got deployed alongside the index that points at it.

## What AIScan checks, and what it can't

Check **P3** currently probes `/.well-known/agent-skills/index.json` only. That's a real limitation we're disclosing rather than fixing quietly: on the 52-site sample above, a scanner reading one path finds 10 or 5 of the 11 sites actually publishing something, depending which path it picked. If your file lives only at the legacy `/.well-known/skills/index.json` path, as Stripe's does, a P3 pass today reads as a fail. Serving both paths, as Path 1 above recommends, sidesteps the gap entirely rather than waiting on it.

What P3 can verify: the path resolves, returns parseable JSON, and the response isn't a soft-200 app shell. What it can't verify, and what no automated check can, is whether your `SKILL.md` files are actually well-written enough for an agent to complete the task they describe, or whether the archive a `type: "archive"` entry points at is the version you meant to ship. Both are review questions, not scan questions. The `skills-ref` validator referenced in the specification checks format compliance, not quality.

[Publishing an MCP server card](https://aiscan.site/blog/publish-mcp-server-card) is the closest sibling task: a different well-known file, a different discovery purpose (tools an agent can call, rather than instructions it can load), but the same shape of problem, a static file at a fixed path that most sites still don't serve. If you're doing one, budget time for the other.

## Run the scan on your own site

`npx aiscan-cli yoursite.com`, or paste the URL at [aiscan.site](https://aiscan.site/), free, no account. It runs check P3 alongside the rest of the capabilities dimension (P1, P2, P4, E2) and tells you which of the two well-known paths, if either, actually resolved on your domain. Browse the [full guide library](https://aiscan.site/guides) for the rest of the discovery-file series, including the MCP server card guide linked above.

