Agent-native apps make their content and operations discoverable without asking an agent to scrape navigation chrome or reverse-engineer a visual interface. Humans and agents still use the same domain handlers and authorization rules.
Cloudflare reports that automated agents and bots now drive more than half of web requests. Its CEO Matthew Prince describes the resulting shift plainly: “the majority of traffic on the Internet is non-human.” That is a reason to design an explicit agent surface, not to make every bot trusted.
Add agent readiness
ferrite add agent-ready
Declare one Surface containing the application identity, canonical origin,
human pages, routes, operations, JSON schemas, and read-only or idempotent
traits. Route the well-known handler before application-specific routing.
Publish the discovery set
| Path | Purpose |
|---|---|
/robots.txt | Crawler policy and discovery links |
/sitemap.xml | Canonical human and machine URLs |
/llms.txt | Concise app orientation and core links |
/llms-full.txt | Broader Markdown context |
/index.md or Accept: text/markdown | Clean page representation |
/openapi.json | HTTP operation schemas |
/.well-known/agent-card.json | App identity, skills, and interfaces |
/.well-known/agent-readiness | Machine-readable readiness evidence |
POST /mcp | Stateless MCP tools |
| HTML page | WebMCP tools in the signed-in browser |
Generate each representation from the same authored pages and operation declarations. A second hand-written “agent API” will drift.
Keep one authorization boundary
Public catalogue tools may be anonymous. Orders, tickets, evidence, staff schedules, and administrative actions require the same subject, tenant, role, ownership, and step-up checks as the UI. Server MCP does not inherit a browser cookie. WebMCP must not export that cookie into agent context.
Map an API operation
Operation {
name: "list_products".into(),
method: "GET".into(),
path: "/api/products".into(),
description: "List products with current price and availability.".into(),
input_schema: Some(json!({
"type": "object",
"properties": { "query": { "type": "string" } },
"additionalProperties": false
})),
read_only: true,
idempotent: true,
..Default::default()
}
The MCP tool, OpenAPI operation, and WebMCP registration call the same
list_products domain function.
Verify the served behavior
node tools/agent-score/agent-score.mjs scan http://127.0.0.1:8787
The score checks real responses and a real MCP tool call. A metadata file that claims an endpoint exists cannot make a missing endpoint pass.
Continue with WebMCP & OpenAPI and the Apps that implement the complete pattern.