WebMCP
Use /webmcp <url> [request] when the user wants to open or operate a web app.
The first token is the URL and the remaining text is the request to complete in
that same browser session. For example:
/webmcp slides.agent-native.com make me a new deck about customer onboarding
Keep working through the request after opening the page. Do not stop after navigation when the user supplied an operation.
Open the requested URL
- Accept a full URL or hostname. If the scheme is omitted, prepend
https://; preserve the host, path, query, and hash exactly. Never guess beta or production variants or replace the request with an external browser. - The browser target is part of this skill: use the host's built-in or in-app browser, never a normal Chrome/Edge tab or browser-extension session. A matching URL in an external browser does not satisfy the request.
- Reuse an existing matching tab only when it belongs to the built-in browser.
Otherwise create a new built-in tab for the exact URL, make it visible, and
keep it open as the requested deliverable. In Codex CUA, use the
iabbrowser (cua.getTab(tabId, { browser: "iab" })orcua.createBrowserTab("iab", url, { visible: true })), neverbrowser: "chrome"for/webmcp. - Read the page after it loads. Opening the URL is enough if no operation follows.
Handle sign-in before tool work
After opening the page, check whether it is signed out or shows an auth-required state before attempting a mutation. If it needs sign-in:
- Leave the same built-in browser tab open.
- Tell the user:
Please sign in in the open browser, then reply "continue". - Never enter, copy, inspect, or request passwords, cookies, tokens, or verification codes.
When the user replies, read the page again and rediscover tools. Do not reuse tool descriptors or a failed result from before authentication.
Find and use page tools through the host
After navigation and authentication, make one bounded discovery pass. A host
bridge is useful only when its list and run tools are actually exposed to the
agent. Do not treat a capability wrapper, a manifest, or a failed
fetchTools() helper as the page's WebMCP surface.
- If the host exposes a browser-session or host WebMCP bridge, call its listed list tool once, then its matching run tool with the exact discovered name, origin, and args.
- If the host exposes MCP-B relay tools, call the listed source/tool discovery tool once and use the exact dynamic tool returned for the requested tab.
- Otherwise use the host's live-page JavaScript evaluator immediately. In
Codex CUA, use
tab.capabilities.get("cdp")and CDPRuntime.evaluatefirst so the expression runs in the page world where host-injected globals live. In Cowork, use its equivalent same-world page evaluator. Usetab.playwright.evaluateonly as a fallback because Playwright may run in an isolated world that cannot seedocument.modelContext. Do not open or type into a developer console. - Do not substitute a generic
tool-search, unrelated app connector,ask_app, or remote API for the current tab's page tools unless the host explicitly binds it to that browser session.
For a direct page call, keep listing and execution in the same page context so the host never tries to serialize or call a page-owned callback. In Codex CUA, the CDP response is nested, so explicitly surface its serializable value:
const cdp = await tab.capabilities.get("cdp");
const response = await cdp.send("Runtime.evaluate", {
expression: `(async () => {
const context = document.modelContext;
if (!context) return JSON.stringify({ state: "absent", href: location.href });
const tools = await context.getTools();
const tool = tools.find(({ name }) => name === TOOL_NAME);
if (!tool) return JSON.stringify({ state: "tool-missing", toolCount: tools.length, tools });
const result = await context.executeTool(tool, JSON.stringify(ARGS));
return JSON.stringify({
state: "executed",
result: typeof result === "string" ? JSON.parse(result) : result,
});
})()`,
awaitPromise: true,
returnByValue: true,
});
const raw = response?.result?.value;
nodeRepl.write(
JSON.stringify({
webmcpResult:
typeof raw === "string" ? JSON.parse(raw) : { state: "unreadable" },
}),
);
Replace TOOL_NAME and ARGS with the exact listed name and schema-shaped
arguments. document.modelContext is the canonical page API;
navigator.modelContext is deprecated. The framework's WebMCP surface accepts
the schema-shaped input as a JSON string. The descriptor is not a callable
run() function, so do not copy it out of the page and invoke it from the
host.
In Codex CUA, an evaluator response may prepend the tab's accessibility tree or
other observations and append the explicit returned value after that block.
Read through the end of the output and parse response.result.value or the
explicit webmcpResult payload from nodeRepl.write (or the host's equivalent
result channel). Treat the AX tree, screenshots, and other observations as
context only. A value you cannot find is unread, not absent. A Playwright
null or missing document.modelContext is indeterminate because its isolated
world may hide the host surface. Confirm it with CDP or a second same-world
evaluator before declaring WebMCP unsupported. If the second evaluator is
unavailable, report the evaluator as unavailable, not unsupported. A confirmed
empty list means the page advertised WebMCP but exposed no tools.
The controlled Codex CUA A/B on the beta Slides page showed the reason: the
Playwright evaluator returned supported:false, while the same-page CDP
evaluator returned state:"supported", toolCount:136, and create-deck
with its schema. Use the page-world result at the end of the evaluator output.
Do not probe hidden globals, guess alternate method signatures, or retry the same unavailable surface. Allow at most one fresh discovery after the user signs in or the page navigates.
The ChatGPT app's page-world document.modelContext may expose
codexExecuteTool, codexGetTools, executeTool, getTools, and
registerTool; these can all have length === 0, so do not infer their
signatures from arity. Its executeTool expects the tool descriptor followed by
JSON.stringify(args), not an object.
After discovery:
- For any state-dependent operation, call the page's current-screen or context read first. Use the exact IDs and selection metadata returned there.
- Call the smallest named MCP mutation that satisfies the request. Inspect
the discovered input schema for operation variants before concluding that a
capability is missing; a composite action may own the exact operation (for
example, Slides
patch-deckaccepts adelete-slideoperation). Keep unrelated content untouched. - Read the result back with the appropriate MCP read tool and only then report success.
List immediately before execution because page tools can change after
navigation, authentication, and selection changes. With JavaScript evaluation,
call getTools() immediately before executeTool() in the same evaluation.
Do not click, double-click, type, drag, or use browser DOM automation to perform an app operation when a matching MCP tool is available. UI controls may be used for navigation, visual inspection, and actions with no MCP equivalent.
Slides generation workflow
For Agent-Native Slides, /webmcp slides.agent-native.com <request> should
use the discovered action tools. For a new generated deck:
- Call
get-workspace-defaultswhen it is available and no reference deck or design system was named. - Call
create-deckwithslides: []or omitslides. It persists an empty editable deck, returns its id, and sends a navigation command to open it. - If the connected tab did not move, call
navigatewith that deck id. - Call
add-slideonce per slide in order and wait for each result. Read the deck back withget-deckbefore reporting completion.
Use a non-empty create-deck payload only for imports or an intentional atomic
bulk replacement. The empty-deck workflow lets the user watch the deck grow
without one long, fragile tool call.
For source-preserving Slides decks, read get-deck.sourceEditability before
structural edits. If the user explicitly asks to rewrite the imported deck,
pass rewriteSource: true to patch-deck; this conversion clears
source-preservation metadata, so do not use it merely to bypass a blocked
delete or reorder.
MCP unavailable
If neither host bridge nor a matching direct app MCP tool is actually available
after the bounded evaluator confirmation, stop before any state-changing UI
action. Say whether the page advertised MCP and which host capability is
missing. Never fall back to click, type, drag, or keyboard automation from
/webmcp; only an explicit request to use UI automation for this specific
operation changes that. In particular, a missing exact verb is not permission
to click when a discovered composite action can express it, and a generic reply
to continue is not permission to switch to UI automation. Do not silently treat
a tool-list failure, manifest fetch, tool acknowledgment, or queued task as
proof that an edit completed.
For Slides, delete slides through the discovered patch-deck action with
operations: [{ op: "delete-slide", slideId: "..." }] after reading stable
slide IDs. There is no need for a separate delete-slide tool or a keyboard
shortcut.
Keep this failure fast. Do not spend more than one discovery pass and one independent evaluator confirmation trying unsupported capability names or invocation signatures, and do not loop on console, DOM, CDP, or hidden-global probes.
Do not hand-build raw authenticated HTTP requests as a substitute for a host-provided MCP tool. If the page requires sign-in, keep authentication in the built-in browser. Never copy credentials, cookies, tokens, or codes into prompts or tool inputs; let the user complete any required sign-in or approval.
Example
/webmcp slides.agent-native.com make me a new deck about customer onboarding
opens https://slides.agent-native.com in the built-in browser, waits for the
user to sign in if needed, discovers the live page tools, creates an empty deck,
and adds slides one at a time. For a focused edit, inspect the current screen,
use the smallest supported slide-edit action, and read the slide back. Do not
double-click the canvas and type replacement text.