# Agent Readiness Score (ARS) 3.2 — Methodology

Last updated 2026-10-12 · score_version `ars-3.2.0`
HTML version: https://glintbase.dev/methodology
Source of truth: https://github.com/Glintbase/glintscanner/blob/main/docs/methodology/ars-3.2.md (check registry: `src/lib/scanner/v2/probes/registry.ts`)

## 1. What ARS measures, and what it does not

ARS 3.2 measures which agent-readiness signals a product publicly exposes and whether they work when probed: machine-readable docs, specs, MCP servers, auth metadata, and content agents can read without a browser.

ARS is **not validated as a predictor of agent task success**. No study correlates ARS with measured agent outcomes. Read a score as "readiness signals present and verified", not "agents will succeed".

## 2. Core rule: points only for observed evidence

| Status | Meaning | Effect on score |
|---|---|---|
| `pass` | The scanner observed the evidence | Earns the check's full registry points |
| `warn` / `fail` | Checked; evidence missing or wrong | Earns 0. Base checks still count in the layer denominator; bonus checks have no effect |
| `na` | The surface the check inspects does not exist (e.g. no MCP server, no OpenAPI) | Excluded |
| `skip` | The surface exists but this scan could not verify the check | Excluded |

- No check may pass because a related file merely exists, or by assumption.
- No partial credit: `warn` earns 0.
- Absent surfaces are penalised once, by their existence check (`mcp-server-manifest`, `openapi-spec`, `llms-txt-exists`, ...). Quality checks for an absent surface are `na`.
- The registry owns each check's points and base/bonus status; probes only decide the status.

## 3. Scoring

**Archetype and layer weights.** In a URL scan the classifier sees only the URL: `docs.`, `/docs`, `/documentation`, `/kb` → docs; `/api`, `api.` → API / devtool; `/shop`, `/store`, `/cart`, `/checkout`, `/pricing` → e-commerce; `/blog`, `/news`, `/article` → content / media; anything else → API / devtool. Scans run with an explicit archetype override are stored as history only and never replace a company's latest score.

| Archetype | Discovery | Access | Usability | Payments | Denominator |
|---|---:|---:|---:|---:|---:|
| API / devtool | 20 | 30 | 35 | 0 | 85 |
| Docs / knowledge base | 20 | 30 | 35 | 0 | 85 |
| E-commerce | 20 | 30 | 40 | 10 | 100 |
| Content / media | 20 | 30 | 0 | 0 | 50 |

- **Layer base** = `round(passed base points / verifiable base points × layer weight)`, where verifiable means `pass`, `warn` or `fail`.
- **Bonus** = points from passed bonus checks. Payments is excluded and uses OR-scoring: in a URL scan it is scored only for e-commerce, where any one verified protocol (ACP, UCP, AP2, x402) earns the full 10 points.
- **Final score** = `min(100, round((Σ layer base + bonus) / (denominator + bonus) × 100))`. A bonus can raise a score; a missing bonus never lowers one.

Illustrative example (API / devtool): Discovery 4/6 verifiable base pts → 13; Access 40/50 → 24; Usability 10/30 → 12; bonus 6 → `round((13+24+12+6) / (85+6) × 100) = 60` → grade C.

| Score | Grade | Band |
|---:|:---:|---|
| 95–100 | A+ | Agent-Native |
| 86–94 | A | Agent-Native |
| 70–85 | B | AI-Friendly |
| 48–69 | C | AI-Capable |
| 28–47 | D | Legacy |
| 0–27 | F | Legacy |

## 4. Evidence coverage

Every scorecard reports:

- `evidence.checksEvaluated` / `evidence.checksRegistered`: checks with a verdict (pass/warn/fail) vs. the 129 registered checks.
- `evidence.baseCoverage`: verified base points ÷ registered base points across scored layers (Payments excluded).
- `layers.<id>.coverage` and `layers.<id>.lowConfidence`: a layer is low evidence when under 50% of its registered base points could be verified (`MIN_LAYER_COVERAGE`).

Publish the number of checks a scan measured, not the registry size. 26 registered checks are not probed yet (always `na`/`skip`, or for `mpp-support`, never pass).

Registered base points a remote URL scan cannot verify (derived from the registry and probe code):

| Layer | Registered base pts | Never verifiable | Why |
|---|---:|---:|---|
| Discovery | 21 | 15 | 14 pts in unprobed checks (`brand-search-accuracy`, `agentic-search-specific`, `mcp-registry-listed`, `skills-sh-listed`, `agentic-search-usecase`); `agent-rules-repo` (1 pt) is local-codebase only |
| Access | 54 | 7 | Seven unprobed 1-pt checks |
| Usability | 41 | 1 | `api-sdk-documentation`; docs archetype also skips `mcp-tools-breadth` (3 pts) |

So on URL scans Discovery can verify at most 6/21 base points (29%) and is always flagged low evidence; its 20-point weight rests on `registry-branding`, `ard-catalog`, `npm-sdk-package` and `robots-ai-policy-quality`.

## 5. What each layer probes

| Layer | Checks | Base (points) | Bonus (points) | Not yet probed |
|---|---:|---:|---:|---:|
| Discovery | 17 | 10 (21) | 7 (14) | 6 |
| Access | 45 | 37 (54) | 8 (14) | 7 |
| Usability | 61 | 24 (41) | 37 (68) | 12 |
| Payments | 6 | 0 | 6 (16) | 1 |

**Discovery.** robots.txt AI-crawler policy (ClaudeBot, GPTBot, PerplexityBot, Google-Extended, anthropic-ai, cohere-ai, OAI-SearchBot); ARD / AI catalog files (`/.well-known/ard.json`, `/.well-known/ai-catalog.json`, `/ai-catalog.json`); MCP manifest branding; Wikidata official-website claim (P856, matched on the registrable domain); an npm package under the brand name that links back to the brand's domain or GitHub org. Brand = registrable domain (`docs.stripe.com` → `stripe`). An HTML page at `/robots.txt` counts as no robots.txt; `#` comments are stripped. Agent rules files are only checked for local codebases (`skip` remotely).

**Access.** Zero-JS content; soft-404 canary (random path must return 404/410); `llms.txt` (exists, formatting, up to 8 declared links fetched, ≥80% must resolve); `llms-full.txt`; markdown negotiation; OpenAPI at 13 standard paths (JSON parsed); HTTP hygiene headers; sitemap; security.txt; bot challenges (challenge markers or `cf-mitigated: challenge`, re-reading 403/429/503 bodies). If the homepage cannot be fetched, every homepage-derived check is `skip`, and `anti-crawler-challenge-free` is `skip` unless a challenge was observed. If the soft-404 canary does not complete, `anti-spa-404` is `skip`.

**Usability.**
- MCP discovery: standard paths on the site, `docs.` / `api.` / `mcp.` subdomains of the registrable domain, and the root plus standard paths of `mcp.` subdomains. A 401/403 counts as MCP only on an MCP-like path or host and with a JSON-RPC envelope or an RFC 9728 `resource_metadata=` challenge. The first candidate to answer is used.
- Live MCP probe: `initialize` on the 2026-07-28 and legacy wire forms (2026-07-28 support requires the server to answer with that version), `tools/list` (with session id), schema strictness and `readOnlyHint` / `destructiveHint` annotations from real tool definitions, capabilities (prompts, resources, logging), and an unknown-tool canary that must get an in-channel error.
- Safety: a real tool is called only if the server annotates it `readOnlyHint: true` and it has no required arguments. Unannotated tools are never called. SSRF behaviour is not probed.
- Transports: Streamable HTTP is base; legacy SSE is bonus only.
- auth.md stage checks are bonus only and each stage must appear in the file. OAuth PRM (RFC 9728) / AS metadata (RFC 8414) must be valid JSON with the expected fields; bearer and scope checks read them.
- OpenAPI inspection: unique operationIds, JSON request schemas, typed error responses (≥50% of operations), Idempotency-Key, rate-limit headers, versioned URIs, search, cursor pagination, webhooks, problem+json, OpenAPI 3.1. YAML specs are detected but not parsed (`skip`).
- WebMCP (bonus): `modelContext` registration or declarative form tool attributes in the homepage HTML.

**Payments.** Only for the e-commerce archetype, or local codebase scans with commerce signals: ACP (`/checkout_sessions`, `/agentic_commerce/delegate_payment`), UCP (`/.well-known/ucp`), AP2 (`/.well-known/ap2`), x402 (HTTP 402 at `/api/payment`), MPP (detection not implemented; `mpp-support` never passes). Otherwise all six checks are `skip`.

## 6. Safety and etiquette

- Read-only probing: GET for pages, files and metadata (the unscored surface inventory also sends HEAD). The only POSTs are MCP JSON-RPC: `initialize`, `tools/list`, one call to a non-existent canary tool, and at most one `tools/call` with empty arguments to a tool annotated `readOnlyHint: true` with no required arguments.
- No SSRF probing of MCP servers (`mcp-ssrf-guard` is `skip`).
- Private-network targets refused: loopback, private, link-local, CGNAT, reserved and cloud-metadata addresses and hosts (`localhost`, `*.local`, `*.internal`), URLs with credentials, non-standard ports. In the scored probes every request and redirect hop (max 5) is re-checked. The policy checks host names and IP literals; it does not resolve DNS.
- Unreachable sites are not scored: no HTTP response at all (after one retry) → `TARGET_UNREACHABLE`. A failed probe suite → `PROBES_FAILED`; no fallback score.
- Bounded requests: timeouts of 1.5–15 s and size caps (2 MB default, up to 12 MB for OpenAPI); MCP candidates and OpenAPI paths tried 4 at a time, stopping at the first hit; at most 8 llms.txt links. The unscored surface inventory has a 25 s budget.
- Rate limits: hosted scanner 5 scans/hour per IP (`/api/scan`); hosted MCP 60 requests/minute.
- Identification: scored probes send a desktop browser User-Agent; the surface inventory sends `Glintscanner-V2/2.0`; MCP `initialize` names the client `glintbase-live-probe` or `glintbase-audit`. Discovery queries the Wikidata API and the npm registry by brand name.

## 7. Known limitations

- No outcome validation.
- Single-sample measurements: latency and availability come from one request per URL.
- Keyword heuristics: auth.md stage detection and WebMCP detection match keywords and can be gamed.
- Fixed paths: OpenAPI is only found at standard paths; not followed from links, headers or llms.txt.
- Endpoint choice: when several MCP candidates respond, the first to answer is used.
- Auth-gated MCP without RFC 9728: a gated server answering 401 with neither a JSON-RPC body nor `resource_metadata=` is not detected.
- Absent MCP weighs heavily: for API and docs archetypes, a site without an MCP server or OpenAPI spec typically scores 0 on most of the 35-point usability layer (an intended, revisitable policy choice).
- Archetype drives rubric: a docs host whose MCP server is a product (action) server is judged by the docs rubric (e.g. auth-gated MCP is a `warn`).
- 26 registered checks are not probed yet; on URL scans Discovery is always low evidence.

## 8. Changes from ARS 3.1

- Checks pass only on observed evidence; MCP, OpenAPI, auth.md, llms.txt link and bot-challenge checks rewritten accordingly.
- Registry owns points and bonus flags; no partial credit for `warn`.
- Legacy SSE transport and the 8 auth.md stage checks moved to bonus.
- `mcp-inchannel-signalling` removed (duplicate of `mcp-error-reporting`); registry is 129 checks.
- 2026-07-28 wire-form support requires the server to negotiate that version.
- MCP servers at the root of `mcp.` subdomains are discovered.
- Evidence coverage and low-evidence layers reported on every scan.
- Grades and bands unified.
- Auth-gated MCP detection requires a JSON-RPC envelope or an RFC 9728 `resource_metadata` challenge.
- Unreachable homepages and incomplete soft-404 canaries produce `skip`, not `fail` / `warn` / `pass`.
- A failed probe suite fails the scan (`PROBES_FAILED`) instead of falling back to a discovery-only score.
- Scans run with an explicit archetype override are stored as history only.

**ARS 3.1 and earlier scores are not comparable with 3.2.** Scorecard pages flag older scans and offer a rescan.

## 9. Links

- Scanner: https://scan.glintbase.dev
- Leaderboard: https://scan.glintbase.dev/leaderboard
- Open-source engine: https://github.com/Glintbase/glintscanner (methodology: `docs/methodology/ars-3.2.md`)
- ARS 2026 errata: https://glintbase.dev/research/data/ARS_2026_ERRATA.md
- Sandboxes Edition 1 errata: https://glintbase.dev/research/sandboxes-edition-1/reports/ERRATA.md

## 10. Check catalog (129 checks)

Columns: id · layer · base/bonus · points · notes. "not yet probed" = the probe code can never return pass.

<!-- catalog:start -->
| Check | Layer | Type | Points | Notes |
|---|---|---|---:|---|
| `registry-branding` | discovery | base | 2 |  |
| `ard-catalog` | discovery | base | 1 |  |
| `ai-catalog-published` | discovery | bonus | 1 |  |
| `ard-entries-valid` | discovery | bonus | 2 |  |
| `ard-trust-manifest` | discovery | bonus | 2 |  |
| `chatgpt-app-listed` | discovery | bonus | 2 | not yet probed |
| `brand-search-accuracy` | discovery | base | 3 | not yet probed |
| `agentic-search-specific` | discovery | base | 3 | not yet probed |
| `wikipedia-presence` | discovery | bonus | 4 |  |
| `mcp-registry-listed` | discovery | base | 1 | not yet probed |
| `npm-sdk-package` | discovery | base | 1 |  |
| `agent-rules-repo` | discovery | base | 1 | Only checked when scanning a local codebase; skip on remote URL scans. |
| `agent-plugins-repo` | discovery | bonus | 1 |  |
| `skills-sh-listed` | discovery | base | 1 | not yet probed |
| `robots-ai-policy-quality` | discovery | base | 2 |  |
| `agentic-search-usecase` | discovery | base | 6 | not yet probed; Held at na (unscored) pending noise reduction. |
| `robots-oai-searchbot` | discovery | bonus | 2 |  |
| `content-no-js` | access | base | 3 |  |
| `page-token-budget` | access | base | 1 |  |
| `anti-spa-404` | access | base | 2 |  |
| `llms-txt-exists` | access | base | 1 |  |
| `llms-txt-formatting` | access | base | 2 |  |
| `llms-txt-links-resolve` | access | base | 2 |  |
| `markdown-negotiation` | access | base | 1 |  |
| `markdown-negotiation-vary` | access | bonus | 1 |  |
| `markdown-url-fallback` | access | bonus | 2 |  |
| `code-fence-validity` | access | base | 1 |  |
| `openapi-spec` | access | base | 7 |  |
| `developer-portal` | access | base | 6 |  |
| `api-catalog-rfc9727` | access | bonus | 2 |  |
| `ssl-tls-enforced` | access | base | 1 |  |
| `http2-support` | access | base | 1 | not yet probed |
| `cors-headers` | access | base | 1 |  |
| `compression-gzip-br` | access | base | 1 |  |
| `clean-semantic-html` | access | base | 1 |  |
| `heading-hierarchy` | access | base | 1 |  |
| `meta-description-quality` | access | base | 1 |  |
| `json-ld-structured-data` | access | base | 1 |  |
| `sitemap-xml-present` | access | base | 1 |  |
| `charset-utf8-declared` | access | base | 1 |  |
| `canonical-url-present` | access | base | 1 |  |
| `content-type-charset` | access | base | 1 |  |
| `etag-caching` | access | base | 1 |  |
| `cache-control-headers` | access | base | 1 |  |
| `response-latency-p95` | access | base | 1 |  |
| `status-code-accuracy` | access | base | 1 |  |
| `text-density-ratio` | access | base | 1 |  |
| `pagination-rel-links` | access | base | 1 | not yet probed |
| `link-header-canonical` | access | base | 1 |  |
| `dns-caa-record` | access | base | 1 | not yet probed |
| `security-headers-csp` | access | base | 1 |  |
| `security-txt-rfc9116` | access | base | 1 |  |
| `llms-full-txt-present` | access | bonus | 1 |  |
| `markdown-table-syntax` | access | base | 1 | not yet probed |
| `relative-link-resolution` | access | base | 1 | not yet probed |
| `image-alt-prose` | access | base | 1 | not yet probed |
| `plain-text-accessible` | access | base | 1 | not yet probed |
| `anti-crawler-challenge-free` | access | base | 2 |  |
| `llms-txt-graph-reachability` | access | bonus | 2 |  |
| `markdown-html-parity` | access | bonus | 2 |  |
| `crosspage-policy-consistency` | access | bonus | 2 |  |
| `nojs-fact-integrity` | access | bonus | 2 |  |
| `mcp-server-manifest` | usability | base | 2 |  |
| `mcp-tools-listing` | usability | base | 3 |  |
| `mcp-tools-focus` | usability | bonus | 3 | Docs archetype only (1-3 tools); skip for other archetypes. |
| `mcp-tools-breadth` | usability | base | 3 | Non-docs archetypes only (3+ tools); skip for docs. |
| `mcp-tools-schema-strict` | usability | base | 2 |  |
| `mcp-tools-readonly-hint` | usability | bonus | 2 |  |
| `mcp-tools-destructive-hint` | usability | bonus | 2 |  |
| `mcp-auth-requirement` | usability | base | 2 |  |
| `mcp-transport-streamable` | usability | base | 3 |  |
| `mcp-transport-sse` | usability | bonus | 2 |  |
| `mcp-app-registry` | usability | bonus | 4 | not yet probed |
| `mcp-apps-ui-quality` | usability | bonus | 4 | not yet probed |
| `mcp-view-csp` | usability | bonus | 4 | not yet probed |
| `mcp-prompts-listing` | usability | bonus | 1 |  |
| `mcp-resources-listing` | usability | bonus | 1 |  |
| `mcp-error-reporting` | usability | base | 2 |  |
| `mcp-protocol-version` | usability | base | 1 |  |
| `mcp-sampling-support` | usability | bonus | 2 | not yet probed |
| `mcp-roots-support` | usability | bonus | 1 | not yet probed |
| `mcp-logging-notifications` | usability | bonus | 1 |  |
| `auth-md-structure` | usability | bonus | 2 |  |
| `auth-md-stage-discover` | usability | bonus | 1 |  |
| `auth-md-stage-methods` | usability | bonus | 1 |  |
| `auth-md-stage-register` | usability | bonus | 1 |  |
| `auth-md-stage-claim` | usability | bonus | 1 |  |
| `auth-md-stage-exchange` | usability | bonus | 1 |  |
| `auth-md-stage-use` | usability | bonus | 1 |  |
| `auth-md-stage-errors` | usability | bonus | 1 |  |
| `auth-md-stage-revocation` | usability | bonus | 1 |  |
| `auth-md-walkthrough-simulation` | usability | bonus | 2 |  |
| `agent-auth-www-authenticate` | usability | bonus | 1 |  |
| `oauth-rfc8414-metadata` | usability | bonus | 2 |  |
| `oauth-rfc9728-prm` | usability | bonus | 2 |  |
| `auth-bearer-token-support` | usability | base | 2 |  |
| `auth-scoped-permissions` | usability | base | 1 |  |
| `webmcp` | usability | bonus | 5 |  |
| `webmcp-model-context` | usability | bonus | 2 |  |
| `webmcp-form-attributes` | usability | bonus | 2 |  |
| `api-operation-ids` | usability | base | 2 |  |
| `api-json-schemas` | usability | base | 2 |  |
| `api-error-schemas` | usability | base | 2 |  |
| `api-idempotency-keys` | usability | base | 2 |  |
| `api-rate-limit-headers` | usability | base | 2 |  |
| `api-curl-examples` | usability | base | 1 |  |
| `api-code-samples` | usability | base | 1 |  |
| `api-versioning-in-uri` | usability | base | 1 |  |
| `api-search-endpoint` | usability | base | 1 |  |
| `api-rfc7807-problem-details` | usability | base | 2 |  |
| `api-bulk-batch-operations` | usability | bonus | 1 | not yet probed |
| `api-pagination-cursor` | usability | base | 1 |  |
| `api-webhook-declarations` | usability | base | 1 |  |
| `api-openapi-3-1-strict` | usability | base | 1 |  |
| `api-sandbox-environment` | usability | bonus | 2 | not yet probed |
| `api-sdk-documentation` | usability | base | 1 | not yet probed |
| `api-graphql-schema` | usability | bonus | 1 | not yet probed |
| `api-grpc-reflection` | usability | bonus | 1 | not yet probed |
| `webmcp-js-registration` | usability | bonus | 2 |  |
| `mcp-wire-compatibility` | usability | bonus | 2 |  |
| `mcp-ssrf-guard` | usability | bonus | 2 | not yet probed; Deliberately not probed: it would require sending private-network URLs to the server. |
| `mcp-honest-schemas` | usability | bonus | 2 |  |
| `api-error-actionability` | usability | bonus | 2 | not yet probed |
| `acp-support` | payments | bonus | 3 |  |
| `acp-delegate-payment` | payments | bonus | 3 |  |
| `ucp-support` | payments | bonus | 3 |  |
| `ap2-support` | payments | bonus | 3 |  |
| `x402-support` | payments | bonus | 2 |  |
| `mpp-support` | payments | bonus | 2 | not yet probed; MPP detection is not implemented; this check never passes. |
<!-- catalog:end -->
