vercel.com
13 of 22 applicable checks passed.
Agent Ready Summary
3 fully passingThese standards are live on your site. Here's what AI agents can do with them today.
Agent Ready
3 capabilities fully passing — agents can rely on these.
llms.txt
Learn →2 / 2 checks passed▾
llms.txt
Learn →robots.txt directives
Learn →3 / 3 checks passed▾
robots.txt directives
Learn →Content quality
1 / 1 checks passed▾
Content quality
(Optional) Declare at least two Schema.org `@type` values across your homepage JSON-LD. `Organization` alone is just entity resolution; agents benefit when you also describe *what the page is* — e.g. `SoftwareApplication` or `WebApplication` for a SaaS, `LocalBusiness` + `Service` for a local site, `Product` for a product page, `FAQPage` for a homepage FAQ. Richer types help agents understand your content structure. See https://agentgrade.com/kb/organization-jsonld.
<!-- Declare multiple Schema.org types so agents understand what kind of page this is. -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://vercel.com/#org",
"name": "vercel.com",
"url": "https://vercel.com/"
},
{
"@type": "SoftwareApplication",
"name": "vercel.com",
"applicationCategory": "BusinessApplication",
"operatingSystem": "Web",
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }
}
]
}
</script>(Optional) Add `<link rel="alternate" type="application/rss+xml" href="/feed">` or serve a feed at /feed, /rss, /atom.xml, or /feed.json. Lets agents subscribe to content updates.
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>vercel.com</title>
<link>https://vercel.com/</link>
<description>Updates from vercel.com</description>
<item>
<title>First post</title>
<link>https://vercel.com/posts/first</link>
<pubDate>Mon, 12 May 2026 12:00:00 GMT</pubDate>
<description>One-line summary of the post.</description>
</item>
</channel>
</rss>Needs Work
9 areas with missing or partial checks.
Tailored Recommendations
4 actionsPrioritized by impact on agent performance. Tackle Critical and High items first.
Why it matters: Missing this signal makes it harder for agents to understand your site.
Fix these 1 item- llms-full.txt foundServe `/llms-full.txt` — the companion to llms.txt containing the full concatenated text of your site so non-browsing LLMs (RAG pipelines, embedded agents) can ingest your content in one fetch. See [the spec](https://llmstxt.org/).
- 2
Homepage & Meta
Medium impact4/6 passingWhy it matters: Missing this signal makes it harder for agents to understand your site.
Fix these 2 items- llms.txt linked from HTMLAdd `<link rel="alternate" type="text/plain" href="/llms.txt">` to your HTML `<head>` so search engines (especially Google for Gemini) index the relationship.
- Organization JSON-LDAdd an `Organization` JSON-LD block to your homepage with `name`, `url`, `logo`, and `description`. This is the canonical entity declaration AI agents and search engines use to identify your company. Use your real URL and a reachable logo URL — fake URLs make agents distrust the block. See https://agentgrade.com/kb/organization-jsonld.
Why it matters: Missing this signal makes it harder for agents to understand your site.
Fix these 5 items- Agent UA gets non-HTMLAgent fetch tools commonly send `Accept: text/markdown, text/html, */*` — markdown listed first, which per [RFC 9110 §12.5.1](https://www.rfc-editor.org/rfc/rfc9110#name-accept) signals it as the preferred type. Serve markdown or plain text in that case, not HTML. Three common bugs produce the wrong outcome: (1) substring-matching the Accept header (`if accept.includes('text/html')`) — ignores preference order entirely; (2) framework defaults that pick HTML whenever `*/*` is in Accept, overriding explicit non-HTML preferences (Rails 8: `Accept: text/markdown, */*` returns HTML even though markdown is listed first); (3) preferring server-declared format order over client preference and ignoring q-values — even `Accept: text/plain;q=0.9, text/html;q=0.5` returns HTML when the server's internal preference list favors HTML. Fix recipes by stack: Express — `req.accepts(['text/html','text/markdown','text/plain'])`; Node bare — `negotiator` npm package; Python — `werkzeug.wrappers.AcceptMixin`; Go — `github.com/markusthoemmes/goautoneg`; Rails — set `request.format` explicitly in a `before_action` based on the leading Accept type. All proper Accept negotiators handle preference order, q-values, and wildcards per RFC 9110 — the fix recipe is the same regardless of which of the three bug patterns is in play. Pass requires Content-Type `text/markdown`, `text/plain`, or `application/json` (anything but HTML) with body ≥20 bytes. See https://agentgrade.com/kb/content-negotiation.
- Accept: JSON returns JSONReturn JSON with status 200 when `Accept: application/json` is sent to the root URL. Note: the response must not have a top-level `"error"` field — structured error responses like `{"error": "..."}` will fail this check.
- Accept: text returns textReturn `Content-Type: text/plain` or `text/markdown` with status 200 when `Accept: text/plain` is sent to the root URL. The body must be at least 20 bytes.
- Accept: markdown returns markdownReturn `Content-Type: text/markdown` when the client sends `Accept: text/markdown`. Agents that prefer markdown can request it directly without scraping HTML.
- + 1 more — see full breakdown below.
Why it matters: Missing this signal makes it harder for agents to understand your site.
Fix these 1 item- Structured errors (JSON 404)Return JSON error responses instead of HTML error pages. Agents can parse `{"error": "not found"}` but not an HTML 404 page wrapped in nav/footer chrome. Use content negotiation: when `Accept: application/json`, return JSON; for browsers, return HTML. SPA gotcha: if your catch-all serves `index.html` with status 200 for unknown paths, the check fails — return status 404 from the server, then let the SPA route on the client. See https://agentgrade.com/kb/infrastructure.
0%llms-full.txt
Learn →0 / 1 checks passed▾
llms-full.txt
Learn →Serve `/llms-full.txt` — the companion to llms.txt containing the full concatenated text of your site so non-browsing LLMs (RAG pipelines, embedded agents) can ingest your content in one fetch. See [the spec](https://llmstxt.org/).
Add `<link rel="alternate" type="text/plain" href="/llms-full.txt">` to your HTML `<head>` so agents and search engines discover the full-content companion file.
<link rel="alternate" type="text/plain" title="llms-full.txt" href="/llms-full.txt">67%Homepage & Meta
4 / 6 checks passed▾
Homepage & Meta
Add `<link rel="alternate" type="text/plain" href="/llms.txt">` to your HTML `<head>` so search engines (especially Google for Gemini) index the relationship.
<link rel="alternate" type="text/plain" title="llms.txt" href="/llms.txt">Add an `Organization` JSON-LD block to your homepage with `name`, `url`, `logo`, and `description`. This is the canonical entity declaration AI agents and search engines use to identify your company. Use your real URL and a reachable logo URL — fake URLs make agents distrust the block. See https://agentgrade.com/kb/organization-jsonld.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "vercel.com",
"url": "https://vercel.com/",
"logo": "https://vercel.com/logo.png",
"description": "What your company does, in one sentence."
}
</script>(Optional) Add a `sameAs` array to your Organization JSON-LD listing your canonical surfaces (GitHub, npm package, X profile, LinkedIn, registry listings). List only profiles you actually maintain — agents follow these links and dead/wrong entries undermine the trust signal. See https://agentgrade.com/kb/organization-jsonld.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "vercel.com",
"url": "https://vercel.com/",
"sameAs": [
"https://github.com/your-org",
"https://x.com/your-handle",
"https://www.linkedin.com/company/your-org",
"https://www.npmjs.com/package/your-package"
]
}
</script>Add `<link rel="icon" type="image/svg+xml" href="/favicon.svg">` for crisp icons at any resolution.
Add `<link rel="icon" type="image/png" href="/favicon.png">` (at least 180x180) or serve `/favicon.png` for broad browser compatibility.
0%agents.txt
Learn →0 / 0 checks passed▾
agents.txt
Learn →Serve `/agents.txt` with at least 10 characters of content. There is no required format — the scanner only checks for existence. Best practice: define agent access policies, rate limits, and allowed actions.
0%Content negotiation
Learn →0 / 5 checks passed▾
Content negotiation
Learn →Agent fetch tools commonly send `Accept: text/markdown, text/html, */*` — markdown listed first, which per [RFC 9110 §12.5.1](https://www.rfc-editor.org/rfc/rfc9110#name-accept) signals it as the preferred type. Serve markdown or plain text in that case, not HTML. Three common bugs produce the wrong outcome: (1) substring-matching the Accept header (`if accept.includes('text/html')`) — ignores preference order entirely; (2) framework defaults that pick HTML whenever `*/*` is in Accept, overriding explicit non-HTML preferences (Rails 8: `Accept: text/markdown, */*` returns HTML even though markdown is listed first); (3) preferring server-declared format order over client preference and ignoring q-values — even `Accept: text/plain;q=0.9, text/html;q=0.5` returns HTML when the server's internal preference list favors HTML. Fix recipes by stack: Express — `req.accepts(['text/html','text/markdown','text/plain'])`; Node bare — `negotiator` npm package; Python — `werkzeug.wrappers.AcceptMixin`; Go — `github.com/markusthoemmes/goautoneg`; Rails — set `request.format` explicitly in a `before_action` based on the leading Accept type. All proper Accept negotiators handle preference order, q-values, and wildcards per RFC 9110 — the fix recipe is the same regardless of which of the three bug patterns is in play. Pass requires Content-Type `text/markdown`, `text/plain`, or `application/json` (anything but HTML) with body ≥20 bytes. See https://agentgrade.com/kb/content-negotiation.
Return JSON with status 200 when `Accept: application/json` is sent to the root URL. Note: the response must not have a top-level `"error"` field — structured error responses like `{"error": "..."}` will fail this check.
Return `Content-Type: text/plain` or `text/markdown` with status 200 when `Accept: text/plain` is sent to the root URL. The body must be at least 20 bytes.
Return `Content-Type: text/markdown` when the client sends `Accept: text/markdown`. Agents that prefer markdown can request it directly without scraping HTML.
Stricter than "Agent UA gets non-HTML": the response Content-Type must match the type the client actually preferred, accounting for client order AND q-values. The scanner probes four variations: (1) `Accept: text/markdown, text/html, */*` should return `text/markdown` (Claude Code / Cursor pattern); (2) `Accept: text/html, text/markdown, */*` should return `text/html` (reverse-order — catches sites that ignore client order and use server-side preference instead); (3) `Accept: text/markdown;q=0.5, text/html;q=1.0` should return `text/html` (catches sites that ignore q-values entirely — see [RFC 9110 §12.5.1](https://www.rfc-editor.org/rfc/rfc9110#name-accept)); (4) `Accept: application/json, text/html, */*` should return `application/json` (programmatic discovery). All four must pass. Common bug patterns: substring matching the Accept header (`if accept.includes('text/html')`); framework defaults that fall back to HTML when `*/*` is present (Rails 8); hand-rolled selection logic using server-side priority order; q-value parsing missing entirely. Fix recipe by stack: Express — `req.accepts(['text/html','text/markdown','text/plain','application/json'])`; Node bare — `negotiator` npm package; Python — `werkzeug.wrappers.AcceptMixin`; Go — `github.com/markusthoemmes/goautoneg`; Rails — `before_action` setting `request.format` based on the leading non-wildcard Accept type. All proper Accept negotiators handle order, q-values, and wildcards per RFC 9110 — the fix recipe is the same regardless of which bug pattern is in play. See https://agentgrade.com/kb/content-negotiation.
Your homepage redirects agent requests (302/301) to a different URL — likely `/llms.txt` or similar. Industry best practice is **inline content negotiation**: same URL returns different bodies based on the `Accept` header. Cited by [RFC 9110 §12.2](https://www.rfc-editor.org/rfc/rfc9110#name-reactive-negotiation) (reactive/redirect-based negotiation "suffers from... needing a second request"), and used by every major content-negotiation-aware site we tested — GitHub API, Stripe docs, Cloudflare docs, Vercel, Mintlify, Sanity. The fix: detect Accept on the same URL, serve the right body inline, set `Vary: Accept`. No 302. Concrete benefits: one fetch instead of two (half the latency), the URL the agent reports to the user is the URL they asked about (not a redirect target), and caches handle it cleanly. See https://agentgrade.com/kb/content-negotiation.
When the same URL returns different bodies based on the `Accept` header, include `Vary: Accept` in the response so shared caches (CDNs, proxies) key entries by `Accept`. Without it, a CDN can serve JSON to a browser or HTML to an agent depending on which request hit first. `Vary: *` or `Cache-Control: private` / `no-store` also satisfy this. Only applies to sites that actually negotiate — API-only origins serving JSON to every request are exempt.
75%Infrastructure
Learn →3 / 4 checks passed▾
Infrastructure
Learn →Return JSON error responses instead of HTML error pages. Agents can parse `{"error": "not found"}` but not an HTML 404 page wrapped in nav/footer chrome. Use content negotiation: when `Accept: application/json`, return JSON; for browsers, return HTML. SPA gotcha: if your catch-all serves `index.html` with status 200 for unknown paths, the check fails — return status 404 from the server, then let the SPA route on the client. See https://agentgrade.com/kb/infrastructure.
// Content negotiation: JSON for agents, HTML for browsers
app.use((req, res) => {
res.status(404);
if (req.accepts('json') && !req.accepts('html')) {
return res.json({ error: 'not_found', path: req.path });
}
res.format({
'application/json': () => res.json({ error: 'not_found', path: req.path }),
'text/html': () => res.send('<h1>404 Not Found</h1>'),
default: () => res.type('text/plain').send('Not found'),
});
});(Optional) Return `X-RateLimit-Limit`, `X-RateLimit-Remaining`, or `Retry-After` headers so agents can self-throttle. See https://agentgrade.com/kb/infrastructure.
// Surface rate-limit state so agents can self-throttle
import rateLimit from 'express-rate-limit';
app.use('/api', rateLimit({
windowMs: 60_000,
max: 60,
standardHeaders: true, // adds RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset
legacyHeaders: false,
}));0%Bot authentication
Learn →0 / 0 checks passed▾
Bot authentication
Learn →Serve `/.well-known/http-message-signatures-directory` with a JSON object listing your agent identity and public keys (RFC 9421).
Add a `members` array to your signatures directory with at least one entry containing `name` and `publicKeyUrl`.
Each member in your signatures directory should have a `publicKeyUrl` pointing to a fetchable public key for signature verification.
0%A2A (Agent-to-Agent)
Learn →0 / 0 checks passed▾
A2A (Agent-to-Agent)
Learn →(Optional) Publish a Google A2A Agent Card at `/.well-known/agent.json` with `name`, `url`, `description`, and `capabilities` to let other agents discover and interact with your agent. Use your real URL and only capabilities you actually expose. See https://agentgrade.com/kb/a2a.
{
"name": "vercel.com",
"url": "https://vercel.com/",
"description": "What this agent does, in one sentence.",
"version": "1.0.0",
"capabilities": {
"streaming": false,
"pushNotifications": false
},
"skills": [
{
"id": "search",
"name": "Search catalog",
"description": "Search the product catalog for matching items"
}
]
}(Optional) Ensure your Agent Card contains at least `name` and `url` fields so agents can identify and connect to your service. See https://agentgrade.com/kb/a2a.
0%WebMCP
Learn →0 / 0 checks passed▾
WebMCP
Learn →(Optional) Publish a WebMCP manifest at `/.well-known/webmcp.json` listing tools your site exposes to browser-based AI agents. See https://agentgrade.com/kb/webmcp.
{
"name": "vercel.com",
"version": "1.0.0",
"description": "Browser-accessible MCP tools for vercel.com",
"tools": [
{
"name": "search",
"description": "Search the catalog",
"inputSchema": {
"type": "object",
"properties": { "query": { "type": "string" } },
"required": ["query"]
}
}
]
}(Optional) Add `tool-name` and `tool-description` attributes to `<form>` elements so browser AI agents can invoke them as structured tools. See https://agentgrade.com/kb/webmcp.
<form action="/api/v1/search" method="post"
tool-name="search"
tool-description="Search the catalog">
<input name="query" type="text" placeholder="Search..." required>
<button type="submit">Search</button>
</form>0%Identity
Learn →0 / 0 checks passed▾
Identity
Learn →Not Applicable
Skipped for this type of site.
Machine Payments
Learn →Not applicable to this site▾
Machine Payments
Learn →Return HTTP 402 with a Payment-Required header on at least one paid endpoint.
Add x402 middleware that returns a base64-encoded Payment-Required header with amount, asset, payTo, and facilitator.
Add MPP middleware: `npm install mppx`, configure with MPP_SECRET_KEY and MPP_RECIPIENT_ADDRESS.
Add Stripe SPT support via mppx: `npm install mppx`, configure with Stripe secret key. Returns 402 with `WWW-Authenticate: Payment method="stripe"`.
Add L402 support: return 402 with `WWW-Authenticate: L402 macaroon="...", invoice="..."`.
Ensure the Payment-Required header is valid base64-encoded JSON with x402Version, accepts[], and resource fields.
Include `payTo` (wallet address) in the payment challenge or in /.well-known/x402.json.
Include `amount` in the payment challenge.
Publish `/.well-known/x402.json` so agents can discover your paid endpoints. Required fields: `x402Version`, `name`, `network`, `facilitator`, `payTo`, `services[]`. See https://agentgrade.com/kb/bazaar for schema and example.
Bazaar discovery
Learn →Not applicable to this site▾
Bazaar discovery
Learn →Add `"extensions": { "bazaar": { "discoverable": true } }` inside the base64-encoded Payment-Required header JSON. See https://agentgrade.com/kb/bazaar for a full example.
Serve `/.well-known/x402.json` so agents can discover your paid endpoints. Required fields: `x402Version`, `name`, `network`, `facilitator`, `payTo`, `services[]`. See https://agentgrade.com/kb/bazaar for the full spec.
{
"x402Version": 2,
"name": "vercel.com",
"network": "base",
"facilitator": "coinbase",
"payTo": "0xYourWalletAddress",
"services": [
{
"method": "POST",
"path": "/your-paid-endpoint",
"description": "What this endpoint does",
"amount": "0.50"
}
]
}Declare at least one service with `method` and `path` fields (e.g. `"method": "POST", "path": "/your-endpoint"`). Optionally add `extensions.bazaar.discoverable: true` for Bazaar registry listing.
Add `"name": "vercel.com"` to `/.well-known/x402.json`.
Add `"facilitator": "coinbase"` (or your facilitator URL) to `/.well-known/x402.json`.
Add `"payTo": "0xYourWallet"` to `/.well-known/x402.json` — this is the wallet address that receives payments.
MCP endpoint
Learn →Not applicable to this site▾
MCP endpoint
Learn →Serve a JSON-RPC 2.0 endpoint (checked at /mcp, /api/mcp, /.well-known/mcp.json, /mcp/docs). The scanner accepts 200, 405, or 406 as indicators that an MCP endpoint exists. See https://agentgrade.com/kb/mcp.
// Minimal Express JSON-RPC 2.0 handler at /mcp
app.post('/mcp', express.json(), (req, res) => {
const { id, method, params } = req.body || {};
if (method === 'initialize') {
return res.json({ jsonrpc: '2.0', id, result: {
protocolVersion: '2024-11-05',
capabilities: { tools: {} },
serverInfo: { name: 'vercel.com', version: '1.0.0' },
}});
}
if (method === 'tools/list') {
return res.json({ jsonrpc: '2.0', id, result: { tools: [
{ name: 'echo', description: 'Echoes its input',
inputSchema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] } },
]}});
}
if (method === 'tools/call') {
const { name, arguments: args } = params || {};
if (name === 'echo') {
return res.json({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: args.text }] }});
}
return res.json({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Unknown tool: ' + name }});
}
res.json({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' }});
});Handle the `initialize` JSON-RPC method and return `{ "protocolVersion": "2024-11-05", "capabilities": {}, "serverInfo": { "name": "your-service" } }` in the result. See https://agentgrade.com/kb/mcp.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": { "tools": {} },
"serverInfo": { "name": "vercel.com", "version": "1.0.0" }
}
}Handle `tools/list` and return `{ "tools": [{ "name": "...", "description": "...", "inputSchema": {...} }] }`. See https://agentgrade.com/kb/mcp.
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "search",
"description": "Search the catalog for matching items",
"inputSchema": {
"type": "object",
"properties": { "query": { "type": "string" } },
"required": ["query"]
}
}
]
}
}Handle `tools/call` with `{ "name": "tool_name", "arguments": {...} }` — even a structured JSON-RPC error response proves the pipeline works. See https://agentgrade.com/kb/mcp.
// Handle tools/call in the same /mcp handler
if (method === 'tools/call') {
const { name, arguments: args } = params || {};
if (name === 'search') {
const results = await db.search(args.query);
return res.json({ jsonrpc: '2.0', id, result: {
content: [{ type: 'text', text: JSON.stringify(results) }]
}});
}
return res.json({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Unknown tool: ' + name }});
}Return `name` in your MCP manifest or `serverInfo.name` in the initialize response. See https://agentgrade.com/kb/mcp.
(Optional) Serve SSE by returning `Content-Type: text/event-stream` on GET requests to the MCP endpoint. Streamable HTTP (the newer MCP transport) is detected separately via 406 status. See https://agentgrade.com/kb/mcp.
Add `Access-Control-Allow-Origin: *`, `Access-Control-Allow-Methods: GET, POST, OPTIONS`, and `Access-Control-Allow-Headers: Content-Type, Accept, Authorization` to your MCP endpoint, and respond to OPTIONS preflight with 204. Without these, browser-based agents (Claude.ai web, ChatGPT browser, extensions) cannot reach your MCP endpoint. Use a specific origin instead of `*` if your endpoint is paid or auth-gated. See https://agentgrade.com/kb/mcp.
// Browser-based agents (Claude.ai web, ChatGPT browser) need CORS on /mcp
app.options('/mcp', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept, Authorization');
res.status(204).end();
});
app.use('/mcp', (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
next();
});OpenAPI spec
Learn →Not applicable to this site▾
OpenAPI spec
Learn →Serve an OpenAPI spec (checked at /openapi.json, /openapi.yaml, /swagger.json, /api-docs, /v1/openapi.json). Must contain an `openapi` or `swagger` key and be valid JSON. See https://agentgrade.com/kb/openapi.
{
"openapi": "3.1.0",
"info": {
"title": "vercel.com API",
"version": "1.0.0",
"description": "Public API for vercel.com"
},
"servers": [{ "url": "https://vercel.com" }],
"paths": {
"/api/v1/things": {
"post": {
"summary": "Create a thing",
"requestBody": {
"required": true,
"content": { "application/json": { "schema": {
"type": "object",
"properties": { "name": { "type": "string" }, "qty": { "type": "integer" } },
"required": ["name"]
}}}
},
"responses": { "201": { "description": "Created" } }
}
}
}
}Add `paths` to your OpenAPI spec with at least one endpoint definition.
Ensure the paths declared in your OpenAPI spec resolve to real routes (any non-404 response counts, including 401 and 402).
Ensure at least one declared endpoint returns a JSON response with status 200-499 (excluding 404) and a Content-Type matching your spec.
Include `"openapi": "3.0.0"` or `"swagger": "2.0"` at the root of your spec.
Add `"info": { "title": "Your API" }` to your OpenAPI spec.
Add `x-payment-info` to paid OpenAPI operations: `"x-payment-info": { "protocols": [...], "price": { "amount": "0.50", "currency": "USD" } }`. Helps agents discover pricing before triggering a 402.
Skill file
Learn →Not applicable to this site▾
Skill file
Learn →Serve `/skill.md` per the [agentskills.io](https://agentskills.io/specification) spec — markdown with YAML frontmatter (`name`, `description`) and a body of agent instructions.
Add YAML frontmatter to your skill.md: `---\nname: my-skill\ndescription: One-line summary\n---`. `name` is 1-64 chars, lowercase alphanumeric with single hyphens. `description` is 1-1024 chars.