Skip to content

Document tool_page_unloaded for WebMCP invoke - #738

Open
rgarcia wants to merge 3 commits into
mainfrom
hypeship/webmcp-tool-page-unloaded
Open

rgarcia wants to merge 3 commits into
mainfrom
hypeship/webmcp-tool-page-unloaded

Conversation

@rgarcia

@rgarcia rgarcia commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Documents the new WebMCP invoke failure tool_page_unloaded (HTTP 410) and the X-Should-Retry: false header on it and on outcome_unknown.

  • Tool reference lifecycle: navigation after an invocation begins still returns a result when the tool starts it. If something else reloads or navigates the page first, the API returns tool_page_unloaded once the new page loads instead of waiting for timeout_sec.

  • Handle an unknown outcome: points to the new code and notes the header, while keeping the advice to disable SDK retries so transport failures aren't retried either.

  • New "Tool page unloaded" subsection: example body, what the code does and doesn't tell you (the tool may still have had side effects), and what to do next.

  • Tab crashes: a crash also returns tool_page_unloaded, the crashed tab's tools disappear from discovery and their references return 404, and Kernel doesn't replace a crashed tab.

  • Input limit: the invoke input limit is now 4 MiB (was 1 MiB), with a 4 MiB plus 4 KiB request body.

Merge after

Merge once kernel/kernel#5165, kernel/kernel-images#498, and kernel/kernel-images#500 are deployed and a browser image with the change is live.

Reviewed with sol-loop: 5 rounds with openai-codex/gpt-6.1-sol on extra-high reasoning. Sol verdict: approved.

@mintlify

mintlify Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
Kernel 🟢 Ready View Preview Nov 2, 2026, 4:14 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

@rgarcia

rgarcia commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

Picked tnsardesai to review these related PRs (kernel/kernel-images#498, kernel/kernel#5165): touched the changed code in webmcpclient; nothing pending

@tnsardesai tnsardesai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

rgarcia added a commit to kernel/kernel-images that referenced this pull request Nov 1, 2026
…is replaced (#498)

## Summary

When the document that owns a pending WebMCP invocation is reloaded or
navigated by anything other than the tool itself, `POST /webmcp/invoke`
waits out the full `timeout_sec` (up to 120s) before returning
`outcome_unknown`, even though no result can ever arrive. This fails
those invocations fast with a distinct `410 tool_page_unloaded`, and
marks both invoke failures that may have had side effects as
non-retryable for SDKs.

Chromium only answers a pending invocation across a navigation when the
tool started that navigation (`WebMCPHandler::DidFinishNavigation`
checks `GetScriptToolInvocationId()`): cross-origin gets an immediate
`Error`, same-origin gets a result from the new document around its
`DOMContentLoaded`. A reload or navigation from Playwright, the user, or
unrelated page script never produces `WebMCP.toolResponded`.
`webmcpclient` dropped the frame's tools on a document change but kept
the invocation waiting.

## Changes

**Fail fast** (`lib/webmcpclient`)
- Mark a waiting invocation when its frame commits a new document
(`EventDocumentChanged`).
- When that replacement document finishes loading
(`Page.frameStoppedLoading` on the same session and frame), wait 500ms
more, then fail the invocation with `ErrToolPageUnloaded` (keeps
`invocation_id`).
- Waiting for the replacement load rather than a fixed delay after
commit keeps tool-initiated navigations working: their result can arrive
seconds after commit on slow pages, but always before the load finishes.

**Renderer crashes** (`lib/webmcpclient`)
- Chrome keeps a crashed tab open, so its tools stayed in discovery.
Invoking one hung for the full `timeout_sec` (the `WebMCP.invokeTool`
command to a dead renderer never answers), and an invocation pending at
crash time also waited out the timeout.
- On `Inspector.targetCrashed`, drop that session's tools and fail its
pending invocations with `ErrToolPageUnloaded` after the same 500ms
grace. The session stays enabled, so tools registered after the tab is
reloaded or navigated are picked up.
- A recovery reload right after the crash keeps the crash deadline.

**API** (`cmd/api/api/webmcp.go`, `openapi.yaml`, regenerated
`lib/oapi/oapi.go`)
- `ErrToolPageUnloaded` → `410` with `code: "tool_page_unloaded"`,
`message`, and `invocation_id`.
- `410 tool_page_unloaded` and the existing `504 outcome_unknown` both
set `X-Should-Retry: false`. Kernel SDKs retry 408/409/429/5xx by
default unless that header says otherwise, so a default-configured
client could re-invoke a tool after a 504 even though it may already
have run. 410 isn't retried by the SDKs even without the header. Both
headers are declared in the spec.
- `410` reports a known cause, not a known outcome: the tool may have
had side effects before its page went away, so callers must not retry it
automatically.
- REPL help (`webmcp.invokeTool`) and `docs/repl.md` mention the new
code.

## Deploy order

kernel/kernel#5165 must deploy first. Until metro-api allows the new
code, its `/browser/kernel` passthrough rewrites `410
tool_page_unloaded` to `session_gone`. That PR also adds the header and
410 passthrough on the control-plane path. Docs: kernel/docs#738.

## Tests

- New regression fixture `reloadWithoutResult` in the fake CDP server
replays the event sequence observed from Chromium for an external reload
(`frameStartedLoading` → `frameNavigated` → `frameStoppedLoading`, no
`toolResponded`).
`TestInvocationFailsFastWhenToolDocumentReloadsWithoutResult` waited the
full 10s before this change and returns `ErrToolPageUnloaded` in about
1s after.
- The existing navigate-then-respond fixture now emits
`frameStoppedLoading` before the late result, covering the grace window.
- `TestInvocationFailsFastWhenToolRendererCrashes` (crash while pending,
tools removed, stale ref returns `ErrToolNotFound`) and
`TestInvocationFailsFastWhenCrashedToolPageReloads` (crash, then a
recovery commit that never finishes loading) both waited the full 10s
before the fix.
- Handler tests check status, `X-Should-Retry`, and body for both the
504 and the 410.
- `go vet` clean; `go test -race` passes for all non-e2e packages except
`lib/devtoolsproxy` `TestUpstreamManagerDetectsChromiumAndRestart`,
which fails the same way on `main` in my environment (temp dir cleanup
with local Chromium) and doesn't depend on this change.
- Drove a `webmcpclient.Manager` with the fail-fast change against a
production Chromium 155 browser over CDP (20s timeout):

| scenario | before | after |
|---|---|---|
| external reload / `Page.navigate` / page-script reload | 20.0s
`outcome_unknown` | 1.9–2.3s, invocation failed |
| tool reloads its own page | `Completed` with output | `Completed` with
output |
| tool navigates cross-origin | Chromium `Error` | Chromium `Error` |
| no navigation | 20s timeout | 20s timeout |

Renderer crash via CDP `Page.crash` (15s timeout):

| scenario | before | after |
|---|---|---|
| invocation pending when the tab crashes | 15.0s `outcome_unknown` |
1.7s `tool_page_unloaded` |
| tools listed after the crash | crashed tab's tools | none |
| invoke a crashed tab's tool ref | 15.0s `outcome_unknown` | immediate
404 |
| navigate the crashed tab, register, invoke ×3 | ~0.02s each | ~0.02s
each |

## Not included

- A `dispatched` field on failures.
- A reload or crash in the short window between `WebMCP.invokeTool`
dispatch and its response isn't tracked and still waits for the timeout.

Reviewed with sol-loop: 5 rounds with openai-codex/gpt-6.1-sol on
extra-high reasoning. Sol verdict: approved.

---------

Co-authored-by: rgarcia <72655+rgarcia@users.noreply.github.com>
rgarcia added a commit to kernel/kernel-images that referenced this pull request Nov 2, 2026
## Summary

Raises the `POST /webmcp/invoke` input limit from 1 MiB to 4 MiB after
JSON serialization, with the request body cap moving to 4 MiB plus the
existing 4 KiB envelope allowance. MCP clients commonly allow 4 MiB tool
calls, so agents send inputs (for example images as `data:` URLs) that
the old limit rejected.

- `maxWebMCPInputBytes` goes to `4 << 20`; `maxWebMCPRequestBytes` and
the request-size middleware follow it.
- The 400 message and the spec descriptions (`WebMCPInvokeRequest.input`
and the request body) say 4 MiB; `lib/oapi/oapi.go` regenerated
(embedded spec only).
- Tool output limits are unchanged.

Chromium handles larger inputs: `WebMCP.invokeTool` with 1.5, 4, 8, and
16 MiB string inputs completed on a production browser over metro's CDP
proxy (4 MiB in about 0.6s).

## Rollout

The public API checks the same limit, so kernel/kernel#5165 raises it
there. Ship this image before the API change reaches production; until
then, browsers on older images reject inputs over 1 MiB with a 400.
Docs: kernel/docs#738.

## Tests

- New `TestInvokeWebMCPToolAcceptsInputAboveOneMiB` (2 MiB input reaches
the client).
- Existing oversized-input and middleware tests use the constants, so
they now check the 4 MiB boundary.
- `go test ./cmd/api/api/ -run WebMCP` passes; `go vet` clean.

Co-authored-by: rgarcia <72655+rgarcia@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
staging — 03025ee4 Deployed Nov 2, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants