Add one JavaScript file. Make your website agent-ready.
AgentReady.js is a progressive-enhancement layer for the existing web. Load it with a single
<script> tag and it turns your site's semantic HTML — forms, navigation, buttons, app state —
into safe, structured WebMCP tools that any
WebMCP-capable agent can call directly: ChatGPT's in-app browser, Chrome 149+, or in-page agents
using the standard getTools() / executeTool() shape.
Humans keep using the same interface. Agents get a reliable interface of their own.
<script src="agentready.js" defer></script>Built for the WebMCP Challenge (Sep 2026).
🌐 繁體中文版:README.zh-tw.md
WebMCP lets a website declare its capabilities to agents instead of leaving them to guess through screenshots and DOM spelunking. But rewriting millions of existing websites isn't realistic — so AgentReady.js derives the tool layer automatically:
| Level | What the site does | What agents get |
|---|---|---|
| 0 — one script tag | Nothing. Just load agentready.js |
7 core semantic tools + auto-synthesized form tools |
| 1 — HTML metadata | Add data-agent-* attributes |
Precise tool names, descriptions, submit policies |
| 2 — native registration | Call AgentReady.register({...}) |
Full domain-specific tools with your own logic |
The same page then works with any WebMCP agent — ChatGPT, an in-page agent, or a future
client — because everything is exposed through the standard document.modelContext API
(with a navigator.modelContext fallback for Firefox, and an identical in-page fallback for
browsers without it — including ChatGPT desktop's register-only client).
Human
│
┌─────┴─────┐
│ Website │
└─────┬─────┘
AgentReady.js (semantic discovery + policy + inspector)
│
document.modelContext ← native WebMCP when available
│
ChatGPT browser · Chrome 149+ · in-page agents (AskPage, …)
<script src="https://cdn.jsdelivr.net/npm/@willh/agentready@latest/dist/agentready.js" defer></script>Or install via npm: npm install @willh/agentready → serve node_modules/@willh/agentready/dist/agentready.js.
That's it. AgentReady discovers the page and registers:
| Tool | Type | Description |
|---|---|---|
get_page_context |
read | Semantic summary: title, headings, regions, forms, counts |
find_on_page |
read | Keyword / natural-language search over interactive elements and visible page text (any language, incl. CJK) → stable refs or text snippets; a miss explains what was searched and what to try next |
read_target |
read | Details of one ref: value, options, href, surrounding content |
activate_target |
write | Click buttons / links / tabs (destructive ones need human approval) |
set_field |
write | Set one input / select / checkbox / radio, firing real input+change events |
fill_form |
write | Fill a whole form by label; never submits |
submit_form |
write | Submit with explicit human approval in the on-page panel |
search_products, signup_form, … |
auto | One synthesized tool per semantic form, with a real JSON Schema |
Synthesized tools are derived from aria-label / headings / action URLs. A search form becomes
search_products({ q, category, max_price }) — not twenty set_input_17 primitives.
<form
method="get"
aria-label="Product search"
data-agent-name="search_products"
data-agent-description="Search products in the catalog"
data-agent-submit="auto">
<input name="q" type="search" placeholder="Search…" />
<select name="category">…</select>
<button>Search</button>
</form>data-agent-name/data-agent-tool— tool name (else derived from labels/heading/action)data-agent-description— tool descriptiondata-agent-submit="auto | confirm | never"— override the submit policydata-agent-hide— exclude a field from agents entirelydata-agent-priority— synthesize this form first (max 8 per page)
<script>
window.AgentReadyConfig = { siteName: 'My Store' };
</script>
<script src="agentready.js" defer></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
window.AgentReady.register({
name: 'add_to_cart',
description: 'Add a product to the shopping cart',
inputSchema: {
type: 'object',
properties: { productId: { type: 'string' }, quantity: { type: 'number' } },
required: ['productId'],
},
execute: ({ productId, quantity }) => cart.add(productId, quantity ?? 1),
});
});
</script>window.AgentReady also exposes getTools(), executeTool(name, argsJson) and inspect() —
the same shapes as the WebMCP standard API — so in-page agents work on every browser,
even without the native API.
AgentReady treats everything on the page — and everything an agent might do — as untrusted by
default. All tools carry untrustedContentHint; read tools carry readOnlyHint. Tool output is
clamped to ~1,500 characters per call.
| Content / action | Policy |
|---|---|
| Reading page content, search, navigation | ✅ Allow — page-text search skips hidden content, data-agent-hide subtrees, scripts, form-control values, and the labels/hints of sensitive fields; text hits are read-only (activate_target / set_field refuse them) |
| Filling normal form fields | ✅ Allow |
Hidden inputs, tokens, passwords, card fields (cc-*, CVV) |
⛔ Never exposed, never filled, values redacted |
| Form submission, checkout, delete/purchase-style buttons | 🙋 Human approval via the on-page panel |
| Arbitrary JavaScript execution | ⛔ Never provided |
The inspector (bottom-right badge) shows the live tool list, every agent action with its arguments, highlights targets as the agent touches them, and pops an Approve / Decline dialog for consequential actions. Sensitive fields are excluded from schemas and discovery, so agents never see their existence or values.
Configuration:
window.AgentReadyConfig = {
inspector: true, // on-page badge + activity + confirm dialogs (false: confirm-gated actions are auto-declined — fail-closed)
siteName: 'My Store', // badge label
maxResults: 8, // find_on_page result cap
};The inspector badge is enabled by default (inspector: true). Depending on your needs, you can hide or customize it:
-
Completely disable the inspector UI (Configuration):
<script> window.AgentReadyConfig = { inspector: false, // Disables badge, activity feed, and approval dialogs }; </script> <script src="agentready.js" defer></script>
Note: AgentReady uses a fail-closed safety model. If
inspectoris set tofalse, any consequential actions requiring human confirmation (e.g., checkout, form submission) are automatically declined because there is no dialog UI for human approval. -
Hide the entire UI with CSS: The inspector host element has a
data-agentready-uiattribute. You can hide it via global CSS:div[data-agentready-ui] { display: none !important; }
(Note: This keeps the inspector logic running, but approval popups will also be visually hidden).
-
Hide ONLY the badge (keeping human confirmation dialogs active): Because the inspector UI uses an open Shadow DOM (
mode: 'open'), you can hide just the floating.badgewhile keeping the Approve / Decline modal functional when an agent requests a sensitive action:<script> window.addEventListener('DOMContentLoaded', () => { const host = document.querySelector('div[data-agentready-ui]'); if (host?.shadowRoot) { const style = document.createElement('style'); style.textContent = '.badge { display: none !important; }'; host.shadowRoot.appendChild(style); } }); </script>
Toolchain: Bun for installs/bundling/serving, TypeScript 7 (strict) for all source.
bun install # or: make install
make ci # typecheck → lint → build → unit tests → size → E2E (real Chrome)
make demo # serve at http://localhost:8788| URL | What it is |
|---|---|
http://localhost:8788/demo/store/ |
Legacy Store demo — a deliberately ordinary storefront |
http://localhost:8788/demo/test-page/ |
Verification page: status dashboard, fixtures, scenario console |
| Layer | Runner | Covers |
|---|---|---|
| Unit | bun test tests/agentready.test.ts (happy-dom) |
Policy classification, semantic discovery/matching, control events, form synthesis, runtime shim |
| E2E | bun test tests/e2e (Playwright + real Chrome) |
Boot, inspector UI, confirm gates approve/decline, synthesized tools, MutationObserver re-synthesis, output budgets, full shopping flow, zero console errors |
| Manual | /demo/test-page/ |
Live tool registry, redaction demos, anything from the DevTools console via AgentReady.executeTool(...) |
E2E uses your installed Chrome (channel: 'chrome') and falls back to Playwright's Chromium.
src/
index.ts boot, MutationObserver, public API
runtime.ts document.modelContext adapter + in-page shim (getTools/executeTool)
semantic.ts DOM discovery, stable refs (WeakRef), natural-language matching
policy.ts exposure levels, output budgets, sensitive-field redaction
inspector.ts shadow-DOM activity UI + human approval dialog
env.ts shared types: AgentEnv, AgentReadyConfig, FormInfo, Activity
tools/ page.ts · interact.ts · forms.ts · controls.ts
demo/store/ Legacy Store demo (self-contained, deployable)
demo/test-page/ verification harness
tests/ bun:test unit + Playwright E2E
- ChatGPT desktop app — the in-app browser supports WebMCP by default (register-only client;
AgentReady detects it and keeps
getTools/executeToolin-page). - Chrome 149+ — enable
chrome://flags/#enable-webmcp-testing, restart, done. - Firefox — AgentReady also detects
navigator.modelContext. - Without the native API, AgentReady runs its in-page shim: all tools still work through
window.AgentReady— useful for local dev (http://localhost) and non-WebMCP browsers.
The landing page lives in site/ (plain HTML/CSS/JS, no build step) and is published
to https://agentready.gh.miniasp.com/ by .github/workflows/pages.yml on every push to main
that touches site/, demo/ or src/. The workflow assembles site/ + demo/store/ +
demo/test-page/ into one GitHub Pages artifact, so the demo is reachable at /demo/store/.
Preview locally with make demo → http://localhost:8788/site/ (the demo/store/ link only
resolves on the deployed site).
demo/store/ is self-contained (bundle copied to vendor/agentready.js at build time) —
drag it into Netlify, vercel deploy, npx wrangler pages deploy, or any static host.
MIT © 2026 Will Huang