What does Agent Ready measure?
Agent Ready is an independent validator for the Vercel Agent Readability Spec and the llmstxt.org standard. The score reports how well your site exposes itself to AI agents and LLM-based clients — the same way a Lighthouse score reports how well your site performs for human users. We fetch your URL once, fan out to the discovery files and well-known endpoints AI agents probe, and grade the result.
Is agent readability a published spec, or Agent Ready’s own rubric?
Published spec. Agent Ready doesn’t author the standard it grades against — it validates against ones that already exist. The Vercel Agent Readability Spec and the llmstxt.org standard are maintained at their own canonical locations, by their own authors, independent of this site; the agent-protocol specs behind the C-series checks (MCP, A2A, agents.json, UCP, x402, and the rest — see the spec registry) work the same way. When a spec updates, the corresponding checks are updated to match — the spec doesn’t bend to fit an existing check.
That’s worth stating plainly because “agent readiness” has attracted more than one self-styled scoring standard from tools that also happen to sell the audit against it — grading your own homework. A validator that references published, third-party specs can be checked against those specs directly; one that grades against a private rubric can only be checked against itself.
One exception, named rather than buried: the MCP server scanner’s M1–M13 quality score is our own weighting — how many points a missing output schema or tool annotation costs is Agent Ready’s editorial judgment, not a value defined by the Model Context Protocol spec. The fields it grades (tools, resources, prompts, output schemas, annotations) are real MCP primitives; the scoring of them is ours, and it’s scoped to that one tool, kept separate from — and never contributing to — the 70-check site score above.
How is the score calculated?
Two scores are reported: an overall agent readability score (0–100) and an llms.txt sub-score (0–100).
The overall score is a simple percentage: count of passing checks divided by total checks, rounded. Warns and fails both count against you; pass is the only state that earns credit. Checks marked unreliable by the JS-rendering check (P23) are excluded entirely so a single architectural choice doesn’t penalise four dependent checks at once.
Rating bands are derived from the overall score:
| Score | Rating | Meaning |
|---|---|---|
| 90–100 | excellent | Ready for AI citation; all critical surfaces present. |
| 70–89 | good | Discoverable, but a few extractability gaps. |
| 50–69 | fair | Partial coverage; multiple required surfaces missing. |
| 0–49 | needs improvement | Not yet AI-readable; start with llms.txt and AGENTS.md. |
Why is the llms.txt sub-score weighted differently?
The llmstxt.org spec treats some properties as foundational and others as optional. We mirror that with weights: structural checks (file accessible, H1 present, valid markdown) count 3×, content checks count 1×, and the optional llms-full.txt presence check counts 0.5×. The overall score is unweighted because every check on the Vercel spec is equally normative; the llmstxt.org spec is explicitly layered.
How is the MCP server score calculated?
The MCP server scanner is a separate tool with its own 0–100 score. It connects to a live MCP endpoint and grades the tools, resources, and prompts it advertises against MCP best practices. It is independent of the site checks above and never affects a site’s Vercel score — the M-series below is not part of the 70-check site registry.
Like the llms.txt sub-score it is weighted: tool quality (M3–M7) carries the most points, because tool definitions are what an agent reasons over. Checks that don’t apply to a given server — e.g. resource quality on a tools-only server — are excluded from both the numerator and the denominator, so a server is graded on what it actually offers, not penalised for scope. Authentication (M11) is informational only. The same rating bands apply (90+ excellent, 70+ good, 50+ fair).
Each check returns pass, warn, or fail, and — unlike the site score, where a warning counts as failing — the MCP score awards partial credit: a check earns its full weight on a pass, half on a warn, and none on a fail. Checks measured as a fraction of tools (e.g. M3 tool descriptions) pass at 100%, warn at 50–99%, and fail below 50%. Output schemas (M5) and tool annotations (M6) are graded as best-practice adoption, not protocol compliance — they are newer additions to MCP, so a server that omits them is still valid, just less self-describing for agents. M5 credits a specific schema (named fields or a $ref), not a bare {"type":"object"}, which validates but describes nothing. Annotations (M6) are weighted slightly above output schemas (M5) because they’re a safety signal: without a readOnlyHint/destructiveHint a client can’t decide whether to auto-approve or gate a tool call. Error-output modeling (M13) is informational (weight 0): of the tools that declare a specific schema, the share whose schema admits a failure path (an error/status field or a union) rather than only the success case. It’s read from the schema, not by calling the tool — the scan never invokes a tool.
| ID | Check | Weight |
|---|---|---|
| M1 | Handshake | 10 |
| M2 | Server metadata | 15 |
| M3 | Tool descriptions | 12 |
| M4 | Parameter descriptions | 10 |
| M5 | Output schemas | 6 |
| M6 | Tool annotations | 8 |
| M7 | Naming conventions | 4 |
| M8 | Resources | 8 |
| M9 | Prompts | 6 |
| M10 | Capability honesty | 10 |
| M11 | Authentication | informational |
| M12 | MCP Apps (UI) | 5 |
| M13 | Error output modeling | informational |
What does each check cover?
70 checks across four categories. Site checks run once per scan; page checks run against every fetched URL; llms.txt and protocol checks run conditionally on file presence.
Site checks (S1–S15) — 15 checks
| ID | Check |
|---|---|
| S1 | llms.txt exists |
| S2 | llms.txt Content-Type |
| S3 | llms.txt not empty |
| S4 | llms.txt URL format |
| S5 | robots.txt — AI bots allowed |
| S6 | robots.txt — /llms.txt not blocked |
| S7 | robots.txt exists |
| S8 | sitemap.xml valid |
| S9 | sitemap.xml has lastmod |
| S10 | sitemap.md exists |
| S11 | sitemap.md has headings + links |
| S12 | AGENTS.md exists |
| S13 | AGENTS.md has required sections |
| S14 | HTTPS |
| S15 | Root OpenAPI spec |
Page checks (P1–P23) — 23 checks
| ID | Check |
|---|---|
| P1 | HTTP 200 |
| P2 | Redirect chain |
| P3 | Content-Type header |
| P4 | x-robots-tag |
| P5 | Canonical link |
| P6 | Meta description |
| P7 | og:title |
| P8 | og:description |
| P9 | HTML lang attribute |
| P10 | JSON-LD present |
| P11 | JSON-LD has required fields |
| P12 | Section headings |
| P13 | Text-to-HTML ratio |
| P14 | Glossary link |
| P15 | Markdown mirror exists |
| P16 | Markdown frontmatter |
| P17 | Alternate link (markdown) |
| P18 | Link header in markdown |
| P19 | Content negotiation |
| P20 | Sitemap section in markdown |
| P21 | Code block language tags |
| P22 | API schema link |
| P23 | JS rendering dependency |
llms.txt checks (L1–L10) — 10 checks
| ID | Check | Weight |
|---|---|---|
| L1 | File accessible | 3× |
| L2 | H1 present | 3× |
| L3 | Valid markdown | 3× |
| L4 | Blockquote summary | 1× |
| L5 | H2 file-list sections | 1× |
| L6 | Link format correct | 1× |
| L7 | Links are accessible | 1× |
| L8 | Optional section used correctly | 1× |
| L9 | Content-Type: text/plain | 1× |
| L10 | llms-full.txt available | 0.5× |
Protocol checks (C1–C22) — 22 checks
Protocol checks discover-then-validate: when the relevant endpoint is absent we drop the check rather than failing it, so a marketing site doesn’t score itself against agent manifests it has no reason to ship.
| ID | Check |
|---|---|
| C1 | MCP Server Card exists |
| C2 | MCP Server Card fields |
| C3 | MCP OAuth Protected Resource metadata |
| C4 | A2A Agent Card exists |
| C5 | A2A Agent Card fields |
| C6 | Wildcard agents.json |
| C7 | agent-permissions.json |
| C8 | UCP profile (/.well-known/ucp) |
| C9 | UCP OAuth Authorization Server metadata |
| C10 | x402 Payment Required response |
| C11 | x402 accepts entries |
| C12 | NLWeb endpoint |
| C13 | API Catalog (RFC 9727) |
| C14 | Web Bot Auth directory |
| C15 | Agent Skills Discovery |
| C16 | Content parity (no cloaking) |
| C17 | Agent-driven UI (A2UI) |
| C18 | MPP Payment challenge |
| C19 | MPP challenge params |
| C20 | AP2 payment protocol support |
| C21 | ACP profile (/.well-known/acp.json) |
| C22 | Declared endpoints crawlable |
Accessibility checks (A1–A23) — separate sub-score
The accessibility tree is what assistive technology and AI agents parse to understand and act on a page, and layout instability breaks agents that act on element positions — so these are agent-readability signals, not only human accessibility. They run over the homepage and are scored into a separate accessibilityScore. Accessibility is WCAG, not the Vercel Agent Readability Spec, so it never moves the Vercel score.
Only what exists is scored. Most of these checks are conditional — if a page has images, they need alt text — so on a page with no images, tables or media there is nothing to grade. Those results are excluded from both the numerator and the denominator rather than counted as passes, which would reward a site for what it lacks. Checks that state a prohibition (no positive tabindex, no timed meta refresh) or a document-level requirement (a title, a language, a main landmark) always count: for those, absence is a real verdict.
| ID | Check |
|---|---|
| A1 | Images have text alternatives (WCAG 1.1.1) |
| A2 | Form controls have labels (WCAG 1.3.1 / 4.1.2) |
| A3 | Controls have accessible names (WCAG 4.1.2) |
| A4 | Media declares explicit dimensions (layout stability) |
| A5 | ARIA name references resolve (WCAG 4.1.2) |
| A6 | Iframes have an accessible name (WCAG 4.1.2) |
| A7 | Heading hierarchy is well-formed (WCAG 1.3.1 / 2.4.6) |
| A8 | No positive tabindex (WCAG 2.4.3) |
| A9 | Pinch-zoom is not disabled (WCAG 1.4.4) |
| A10 | Page has a title (WCAG 2.4.2) |
| A11 | Accessible name contains the visible label (WCAG 2.5.3) |
| A12 | Content is reachable past the chrome (WCAG 2.4.1) |
| A13 | Language tag is valid (WCAG 3.1.1) |
| A14 | Hidden elements are not focusable (WCAG 4.1.2) |
| A15 | Interactive controls are not nested (WCAG 4.1.2) |
| A16 | Headings are not empty (WCAG 1.3.1) |
| A17 | No timed meta refresh (WCAG 2.2.1 / 3.2.5) |
| A18 | Autocomplete tokens are valid (WCAG 1.3.5) |
| A19 | Tables declare their headers (WCAG 1.3.1) |
| A20 | SVG images have a text alternative (WCAG 1.1.1) |
| A21 | Media declares a captions track (WCAG 1.2.2) |
| A22 | List structure is well-formed (WCAG 1.3.1) |
| A23 | ARIA roles and attributes are valid (WCAG 4.1.2) |
How do we handle JavaScript-rendered pages?
P23 detects pages where the static HTML response lacks data that only appears after client-side rendering — missing H1, empty body text, JSON-LD that injects after hydration. When P23 fires, the runner marks the dependent per-page checks (P12, P13, P14, P21) as unreliable and the scorer excludes them from both numerator and denominator. Without this, a single SPA architecture choice would compound into a multi-check score drop.
Only what exists is scored. Most A-checks are conditional — “if the page has images, they need alt text” — so on a page with no images, no tables and no media there is nothing to grade. Those results are marked notApplicable and excluded from both numerator and denominator, exactly as the MCP grader excludes a tools-only server’s resource checks. Scoring them as passes rewarded a site for what it lacked: a plain text page collected free credit for every feature it never used. Checks that state a prohibition (no positive tabindex, no timed meta refresh) or a document-level requirement (a title, a language, a main landmark) always count — for those, absence is a real verdict rather than an absence of evidence.
The same gate applies to the accessibility sub-score. Most A-series checks count elements — images, form controls, links, iframes — and treat “found none” as a pass. On a client-rendered page the static DOM is a skeleton, so “found none” means “could not look”, and those vacuous passes would otherwise award a high accessibilityScore for a page we never read. When P23 fails, the element-counting A-checks are marked unreliable and dropped from the score; if every one of them is, the score is null rather than 0 — we don’t know. The checks that read <head> or <html> (A9 viewport, A10 title, A13 lang, A17 meta refresh) stay trustworthy, because a JS-only shell still serves them.
How often do we refresh the spec mapping?
The Vercel Agent Readability Spec is published on the Vercel Knowledge Base; we track it and fold in new check IDs as they ship. The llmstxt.org spec changes less frequently — the structure has been stable since the late-2024 proposal.
Have the checks changed? (changelog)
When a check’s behaviour changes, scores can move without the site changing. We record those here rather than let them look like noise.
- 2026-07-31 — The accessibility sub-score now grades a site on what it has
- What changed. Three things, all affecting accessibilityScore only — the Vercel score is untouched. First, the A-series grew from 9 checks to 23, adding a page title, a valid language tag, a main landmark, accessible names that match their visible text, valid ARIA, well-formed lists and tables, SVG image names, captions, and autofill token validity. Second, most A-checks count elements and treated “found none” as a pass, so a client-rendered page — whose static HTML holds no images, forms or controls — collected a near-perfect score for a page we never actually read; those results are now marked unreliable and excluded, the same treatment P23 already gave its dependent page checks. Third, and most visibly, a check with nothing to grade no longer counts as a pass.
- Why the third one matters. Most of these checks are conditional: if a page has images, they need alt text. On a page with no images, no tables and no media there is nothing to judge, and scoring those as passes rewarded a site for what it lacked — a plain text page earned free credit for every feature it never used. Those results are now excluded from both the numerator and the denominator, exactly as the MCP server grader already excluded a tools-only server's resource checks. Checks that state a prohibition (no positive tabindex, no timed meta refresh) or a document-level requirement (a title, a language, a main landmark) still always count: for those, absence is a real verdict rather than an absence of evidence.
- What it does to your score. Every accessibilityScore moves, and simple pages move most. A page with few features is now graded on its handful of applicable checks instead of on a denominator padded with automatic passes, so a single real failure weighs far more than it used to — one of our own test fixtures fell from 87 to 67 without a byte of its markup changing. Sites with rich, well-built markup move least. We did not re-score historical scans: a scan is a record of what we saw on the day, and rewriting it would be dishonest. Scans run before this date were scored under the old rule.
- 2026-07-13 — S15 and P22 now verify real OpenAPI documents
- What changed. S15 (“Root OpenAPI spec”) used to treat any HTTP 200 at a well-known OpenAPI path as a pass, even when the URL served HTML or another non-OpenAPI body. P22 (“API schema link”) also recognised openapi.json and Swagger filenames, but missed the plain openapi.yaml filename. Both were wrong: agents need a machine-readable API contract, not just a successful fetch or a partial filename match. S15 now parses the discovered body as JSON or YAML and requires an openapi/swagger version field plus an info object; P22 now recognises openapi.yaml links.
- What it does to your score. S15 and P22 are scored checks, so scores can move. API-first sites that returned HTML at /openapi.json or another probed path may go down. API pages that linked only to openapi.yaml may go up. We did not re-score historical scans: a scan is a record of what we saw on the day, and rewriting it would be dishonest. Scans run before this date were scored under the old rule.
- 2026-07-13 — S5 no longer penalises an AI-training opt-out
- What changed. S5 (“robots.txt — AI bots allowed”) used to fail a site for blocking GPTBot, ClaudeBot, Google-Extended or Applebot-Extended, and told the publisher those crawlers “need access to index your content for AI-powered search”. That was wrong. All four are training crawlers or usage-control tokens — none of them builds the index an assistant cites at answer time. Blocking them is a deliberate, cost-free policy choice, and it is the one our own AI robots.txt generator recommends. S5 now fails only when a crawler that would actually cite you is blocked: OAI-SearchBot, Claude-SearchBot, PerplexityBot and their peers. Training and user-triggered blocks are reported on the result without costing you a point.
- What it does to your score. S5 is one check in an unweighted average, so the move is under a point on a typical multi-page scan. If you block training crawlers and nothing else, your score goes up. If you block a citation crawler, it goes down — which it always should have. We did not re-score historical scans: a scan is a record of what we saw on the day, and rewriting it would be dishonest. Scans run before this date were scored under the old rule.
Where is the source for each check?
Every check is implemented as a single function in src/lib/checks/{site,page,llmstxt,protocol}/. The naming convention is {id}-{slug}.ts (e.g. p11-json-ld-fields.ts). Each file exports a check definition; the registry collects them into a single array that the runner iterates. If you want to see exactly what we’re asserting, read the source — one check per file.
Frequently asked questions
- How is the agent readability score calculated?
- Each check returns pass, warn, or fail. The score is the percentage of passing checks across all categories: site-wide checks (run once per scan) plus per-page checks for every URL we fetch. Warnings count toward the failing side. Checks marked unreliable by P23 — JS-rendered pages where the static HTML lacks the data — are excluded from both numerator and denominator so a single architectural choice doesn't penalise four downstream checks.
- Which spec does Agent Ready implement?
- The Vercel Agent Readability Spec drives the S- (site), P- (page), and C- (protocol) check series. The L- series implements the llmstxt.org specification for llms.txt files. Both specs are tracked at their canonical locations and updates are folded in when published.
- Is agent readability a published spec, or Agent Ready's own rubric?
- A published spec, for the 70-check site score. Agent Ready doesn't author the standard it grades against — it validates against ones that already exist: the Vercel Agent Readability Spec, the llmstxt.org standard, and the agent-protocol specs listed in the spec registry, each maintained at its own canonical location by its own authors, independent of this site. When a spec updates, the checks are updated to match. One exception: the MCP server scanner's M1–M13 quality score is our own weighting of real MCP primitives (tools, resources, output schemas, annotations) — the fields are from the spec, the scoring of them is ours — and it's scoped to that one tool, never contributing to the site score.
- Why does my score change between scans even though I didn't change anything?
- Three causes. First, content negotiation (P19) depends on your origin honouring Accept headers — CDN caches can serve stale variants. Second, AI-bot crawl checks (S5) depend on what's currently in robots.txt for the rotating list of bot user-agents. Third, the protocol checks (C1–C22) discover-then-validate — if your /.well-known endpoint times out, the check drops rather than failing. Re-run; transient network errors are common.
- Why does the llms.txt score differ from the overall score?
- The overall score is pass/total across all 70 checks weighted equally. The llms.txt sub-score uses category weights from llmstxt.org: structural checks (file accessible, H1 present, valid markdown) count 3×; content checks count 1×; the llms-full.txt presence check counts 0.5×. Re-weighting matters here because the structural checks are foundational — a file that doesn't parse is worth less than one with the optional companion missing.
- What's the difference between the page checks and the site checks?
- Site checks (S1–S15) run once per scan against your root URL — they cover discovery files (llms.txt, robots.txt, sitemap.xml, AGENTS.md), HTTPS, and the OpenAPI spec probe. Page checks (P1–P23) run against every URL we fetch — they cover HTTP semantics, metadata, JSON-LD, markdown mirrors, and the static-render guarantee. A typical scan emits 15 + (23 × pages-fetched) check results.
- Does the MCP server score affect my site's Vercel score?
- No. The MCP server scanner (/mcp-server-scanner) is a separate tool that connects to a live MCP endpoint and grades its tools, resources, and prompts on its own 0–100 scale (the M1–M13 series). It is not one of the 70 site checks and never contributes to a site's Vercel agent-readability score. Its score is weighted toward tool quality, excludes checks that don't apply to a given server, and treats authentication as informational only.
- Why are some checks not in my scan results?
- Protocol checks (C1–C22) follow a discover-then-validate pattern: if your /.well-known endpoint returns 404, we drop the result rather than failing it. The same applies to the x402 probe — if the path doesn't return 402 Payment Required, neither C10 nor C11 appears. This keeps validator-only scans (e.g. an MCP server card check on a static marketing site) from showing irrelevant failures.