get_pending_result on the hosted server: what it does,
what it takes, what it returns and when it refuses.
Overview
Tools are grouped in the order a run uses them:Conventions
- Needs an open page. Page tools act on the browser that
open_pagelaunches. Called first, they refuse and tell you to callopen_page. The discovery and account tools need no browser. - Errors. A refused call returns an MCP tool error (
isError: true) whose text is the proxy’s message. The underlying codes and whether to retry are on Error codes. - Hosted server. On the hosted server, every tool that uses your account
is refused until the account holder chooses what the connection may use on
Connected apps. Only
get_checkout_recipesworks before then. Every tool also takes an optionalsessionargument, which the parameter lists below leave out. A result from a call that ran on a session ends with a line naming its handle, such assession: s_8f3k.... Pass that handle assessionon every later call in the task. The server sends noMcp-Session-Idheader, so the argument is the only way back to the session. Without it, a call afteropen_pagecannot reach the open browser: a page tool is refused with “call open_page first”, and anotheropen_pagestarts a new browser. See On the hosted server and Hosted MCP server errors. - Selectors. Any selector Playwright accepts, unless a tool says otherwise.
A
frameparameter is the CSS selector of the iframe the element lives in. - Timeouts are in milliseconds.
- During a handoff, page tools are refused with
challenge_requiredwhile the account owner has the page.close_sessionstill works, andget_run_reporttouches no page.
Status line
Every prose answer’s first text block,content[0].text, starts with one short,
machine-readable line, then a newline, then the answer as before. A client that
reads only the start of the first text block still gets what it must act on.
The line is key=value pairs separated by spaces, with status first, and it
is at most 120 characters:
An answer whose text is a JSON object (
observe_page, get_run_report,
close_session, count, list_frames, detect_challenge,
get_session_outcome, get_token_catalog, get_checkout_recipes and the
others that answer JSON) has no line before it, so JSON.parse(content[0].text)
works as it is. It carries the same fields as its first keys instead, compact or
indented as the JSON already was:
close_session already has a
status key ("Session closed"), so its answer starts
{"_status":"ok","status":"Session closed",...}. A JSON array
(list_page_hint_kinds) is not wrapped in an object: the status line is a text
block of its own, content[0], and the array is content[1].
get_pending_result returns the finished call’s own status, as a line or as
keys. The hosted server also
gives a schema refusal (code=invalid_params) a status line. The local stdio
server does not, because the MCP SDK answers those before a tool runs.
On the hosted server
The hosted server has the same 29 tools as the local one, with the same parameters, results and errors, plusget_pending_result for a call that is still running.
Two things differ: every tool takes one more parameter, and a browser belongs to a session
you name on each call, not to the server process. The hosted browser never
tunnels through your machine: its traffic leaves from the deployment’s
residential egress where one is configured,
and from the proxy’s address otherwise. A residential egress session is HTTPS
only.
The session argument
string
The session handle an earlier result in this task returned, such as
s_8f3k.... At most 200 characters. Pass it on every call so the call reaches
the same browser. Leave it out only to start a new session.Mcp-Session-Id
response header: an MCP client takes that header as the id of its own
connection, and ends it with an HTTP DELETE when it reconnects, which used to
close the browser in the middle of a task. A call without a session argument
still uses a handle in the Mcp-Session-Id request header, from a client that
kept one from before. A handle belongs to the connection that started it. On
any other connection it reads as a session that has ended.
Calls without a session
Read the catalog on the session you fill on. The session keeps what
get_token_catalog returned and uses it to check your fills, and it records
login writes for get_run_report. So start a task with get_token_catalog (or a
login tool) and pass its handle to open_page, rather than letting open_page
start a second session.
A first call that fails starts no session, with one exception: when a login
tool itself returns an error, its session is kept and the handle returned,
because a write may have landed before the error.
When a session ends
A session ends when you callclose_session, after 13 minutes with no call
that reaches its browser, 56 minutes after its browser opened, a minute before
its credential lapses, when the gateway closes it to make room at the account’s
session limit, or when the gateway restarts. The age check skips a session
while a call is in flight, so a long call can still be running at the proxy’s
60-minute limit and fail with hard_timeout. The credential check does not: the
session ends then, and its browser closes once the running call finishes. See
Limits.
A call naming an ended session is refused, and nothing runs. The message is one
of two:
Both end with “Call open_page without
session to start a new one.” The reasons
the first message names are:
it was idle too longits browser was near the proxy's one-hour limit for a sessionits credential was about to lapsethe account was at its browser session limit and another session of this connection needed the slotno call had used it in the 2 minutes since it started, and this account's hosted apps needed its browser session slotit was asked to (close_session)
note: “No report could
be read for that session. Do NOT read this as nothing having been filled.”
When the handle came only from the Mcp-Session-Id header, and the tool can
start a session (see Calls without a session), the
call starts a new session instead and returns the new handle. A client that
kept a handle from before keeps sending it, so this keeps it from being stuck
on a dead one. If the gateway had a record of the old session, the result adds a line
beginning “The earlier session”, with what its close found.
Closing a hosted session
close_session needs the handle. It takes the session out of use at once, so no
new call can enter it, then closes the browser and gives back the session’s
credential. Giving it back is best effort: if Agent Vault cannot be reached at
that moment, the credential stays counted toward the connection’s 50 until it
expires, at most 75 minutes after it was issued. A call that failed with
unavailable can also leave a credential that no session owns, which
close_session cannot give back. See Limits.
- Sent again for the same handle, it returns the first answer again.
- A call still running on the session is ended. If it finishes after the close, the reply adds a line beginning “Calls still running when the session closed finished afterwards”, with what the final close found.
- Refused for its arguments, it still closes the session, and the reply adds “The session is closed anyway.”
- If the close itself fails, the reply reads “close_session hit an internal error, but the session is closed.” followed by what the close found.
DELETE closes nothing: the server answers it 405, and a session
ends only with close_session, after being idle, or at one of the limits
above. See Protocol responses.
Repeated and retried calls
- The same call at the same time. While a call is running, a second call with the same tool and the same arguments on the same session joins it and gets the same answer, instead of running twice. A first call without a session joins the same way on one connection. Once the first has finished, the same call runs again, so polling works.
- A retransmitted call. Calls that change something are kept for 2 minutes:
open_page,click,press,fill_checkout,fill_login,declare_page_hint,close_popup,wait_for_popup,request_challenge_handoff,create_login,set_login_username,rotate_login_password,submit_checkout_recipe, andget_session_outcomewithconfirmsCheckout: true. The same call again (same tool, arguments, session and JSON-RPC id) within those 2 minutes gets the first answer back and does not run. A new id runs it again. - A retransmitted first call. One that started a session never starts a
second.
get_token_catalog,get_signup_requestandwait_for_signup_approvalrun again on the session the first call started, so they read the current state. The login writes get the first answer back. - What is not kept. A refusal made before the tool ran: a sign-in or credential refusal, arguments that fail the schema, a missing or ended session, or a restarting gateway. See Gateway messages. Sending that call again runs it.
- How many are kept. The latest 200 answers per connection. A call that
started a session counts twice. Past 200 within 2 minutes, the oldest are
dropped early, and a retransmission of a dropped call runs again. A gateway
restart drops them all. After a burst of calls or a restart, check the page or
get_run_reportbefore you resend a call that changes something.
Call deadlines
A call still running after 25 seconds, or as soon as the proxy reports that it is waiting on the owner (a signup wait keeps the 25 seconds), answerspending
instead of waiting, with a wait id, and keeps running
on the server. Fetch its answer with
get_pending_result. Do not repeat the call: that starts
a second one. A call this slow can be waiting on the account owner, for
example the first fill on a new site, which waits up to 15 minutes for them to
approve the site. The pending answer names the wait once the proxy reports it,
and waiting_on_owner is true only then. When it names one, tell the user
right away rather than polling in silence. When it does not, no owner wait is
being reported for this call: do not tell the user a new approval is waiting
(one they were already told about may still be open in their email or
dashboard). Either way, keep calling
get_pending_result in the same turn: an agent that ends its turn is never
called back, so the answer goes uncollected and the checkout stalls.
A hosted call gets 4 minutes, and request_challenge_handoff gets 12. Past that
the call answers with the tool’s name, for example “click did not finish within
4 minutes. Its outcome is unknown: check before repeating it.” The call may still
finish on the server. Check the page or get_run_report before you repeat it.
get_pending_result
Hosted server only. Get the answer to a call that came back pending.
string
required
The
wait id from the pending answer.pending again, with the same wait id: call get_pending_result
again. When the pending answer names a wait on the account owner, tell the user
what they need to approve, and where: the email Agent Vault sent them or the
banner on their dashboard. Then keep polling in the same turn; do not end it to
wait for them. A pending answer that names no wait reports no owner wait for
this call: do not tell the user a new approval is waiting.
When the reason is known, the pending answer names it. For example, a first
fill_checkout on a site the account has not authorized answers: “fill_checkout
is waiting for the account owner to approve a new site (www.site.com) … they can
approve it from the email titled ‘Authorize new site: www.site.com’ or the banner
on their Agent Vault dashboard. It expires at 2026-10-14T14:32:00Z.” Each wait
has its own sentence: a new site, a purchase over the card’s approval threshold,
a card security code to enter, a challenge to complete, or a signup to approve.
The same facts are in structuredContent:
Until the proxy reports a wait, the answer says no request to the owner has
been reported,
waiting_on_owner is false, and the other fields are absent.
Once the wait ends and the call keeps running, the answer says the call stopped
waiting on the owner and its answer will say what came of the request.
Discover
These tools need no open page.get_token_catalog
List the mock tokens this key may use: identity, addresses, cards and logins.
string
Narrows cards and logins to one site, for example
https://shop.example.com/checkout. Identity and addresses always come back
unfiltered.identity, addresses, cards, logins, and, under
a url, relatedLogins. It never contains a real card number or CVV. Full
shape: Token catalog.
How the url filter behaves:
- A card marked
anySite: trueis not scoped to a list of sites. It is always included, after any card registered for that site. An emptycardsunder aurlmeans no card for that site is visible to this key: a key limited to Only selected cards drops every other card before theurlfilter runs. Ask the account holder to check the key’s card access on API keys before giving up on the checkout. - A
urlthat does not parse matches no site. Site-scoped cards and every login drop out, and theanySitecards are still returned. - A returned card is a candidate, not a guarantee. The owner’s rules still apply
at fill time and can refuse it with
merchant_blocked, or withdestination_unauthorizedon a site that is not an authorized destination. - A login appears only for its own site. A login on a sibling host of the same
site (
us.example.comwhile you are onwww.example.com) is listed inrelatedLogins, hosts only. It will not fill here: navigate to thatwebsiteto sign in. An emptyloginsbeside an emptyrelatedLoginsmeans no login for this site is visible to this key. A key limited to Only selected logins does not see logins outside its selection, so ask the account holder to check Logins, and widen the key’s access if one exists, before callingcreate_login.
invalid_api_key, rate_limited (wait for Retry-After).
Example arguments
get_checkout_recipes
Look up the open checkout recipe registry for a page before improvising a
checkout. Each registry call here and in submit_checkout_recipe and
get_recipe_submission gets 20 seconds, then fails with “The recipe registry
did not answer in time.”
string
required
The absolute product or checkout URL, such as
https://shop.example.com/product/thing. The query, fragment and any
hosted-checkout token are stripped before the lookup. A string that does not
parse as a URL is sent as is, so never pass one that contains a session token.null when nobody has recorded coverage for that
host yet:
object | null
The recipe for this host: selector overrides, gotchas,
preconditions, and a
deadEnd classification when unattended checkout is impossible there. A
deadEnd means do not attempt the checkout, and says why.object | null
The platform recipe the merchant names: detection,
cardSurface, step-by-step
fill instructions and, where recorded, frame selectors under selectors.object | null
How this host’s signup form behaves.
object | null
The identity provider the signup recipe names (for example Auth0,
Okta, Cognito or Shopify customer accounts).
preconditions first. Each is a step to take, not a reason to stop:
A signup recipe’s
ownerSteps lists what the account owner does after submit.
For each email_code, sms_code or captcha, call
request_challenge_handoff. For an email_link, stop and tell the owner which
site is waiting. A repeated step is the site asking again, not a rejected code.
A signup recipe describes a form. It is not permission to open an account,
which always goes through
create_login and the owner’s approval, given per
request or in advance on their Pre-approved signups list.Example arguments
submit_checkout_recipe
Contribute what you learned about a site to the registry, after solving a new
site or hitting a dead end the registry does not know about.
object
required
The recipe body. A merchant recipe carries
id (the primary host),
kind: "merchant", hosts, platform and status, plus optional cardSurface,
overrides, gotchas, notes, deadEnd, exampleProductUrl and
preconditions. Signup and signup platform recipes are accepted too. See
Recipe format.string
Free text for the maintainer, such as how you tested it. Never personal data.
With
verificationRequested, an approved note is published inside the recipe
as the evidence, word for word.boolean
true when a real purchase completed (or, for a signup recipe, an account was
actually created) and note holds the sanitized evidence.id, its status and its verdict. The call waits
up to about 45 seconds for screening. If screening has not finished by then,
status is still pending and verdict is empty: poll the same id with
get_recipe_submission. Do not resubmit.
Rules the registry enforces:
- Never claim
status: "verified". That status is maintainer gated. Usepartial,unverifiedordead-end, and request verification instead. preconditionsis a closed vocabulary. A merchant recipe accepts exactlydelivery_location,region_selectandaccount_required, with no repeats. A signup recipe accepts exactlyregion_select,consent_gateandmode_toggle. Omit the field rather than sending an empty array.- A submission for the same store under another host is combined with the existing recipe.
invalid_recipe (fix it, do not resend unchanged), rate_limited
(the budget of 20 in any 24 hours: stop submitting until the oldest submission
in the last 24 hours turns 24 hours old).
Example arguments
get_recipe_submission
Read the verdict of a submission that was still pending. Poll this rather than
resubmitting.
string
required
The submission id
submit_checkout_recipe returned.id, its status and its verdict. status is
one of:
pending: screening is still running, even aftersubmit_checkout_recipegave up its 45 second wait.verdictis empty. Poll the sameidagain shortly, and do not resubmit.approved: the recipe is live.merged: it was combined with an existing recipe for the same store.rejected:verdictnames the rule it broke.needs_human: a maintainer will decide. No action needed.
not_found (the id will not start existing).
Example arguments
Open an account
create_login, set_login_username and rotate_login_password write to the
credential store, so they need a key the owner has allowed to create logins.
That permission is off by default per key; without it those tools refuse with
signup_not_enabled. get_signup_request and wait_for_signup_approval only
read, and answer only the key that filed the request. None needs an open page.
create_login
Open an account on a site. Returns a request handle, not a credential.
Usually the request is pending until the account owner approves it. If the owner
has put this site and this exact email on their pre-approved signup list, the
login is created at once and the handle comes back already approved.
string
required
The site’s registrable domain, for example
doordash.com. Not the signup URL.
The credential is locked to this domain and its subdomains, so a sign-in that
later moves to identity.doordash.com still works. A bare suffix such as
com or co.uk is refused.string
required
A mailbox the agent controls. Never invent one. It is stored readable, and the
owner sees it before deciding.
string
The site’s separate handle. Pass it only when the site has already accepted
that exact handle. Approval stores whatever you pass, and only the first handle
sticks. Otherwise omit it and record one later with
set_login_username.integer
The site’s stated minimum password length, when longer than the default of 24.
At most 64, the longest password the vault generates. A higher value is
refused with
invalid_policy: on such a site, ask the account holder to add
the login themselves.integer
The site’s stated maximum password length. Below 12 is refused with
password_too_short.boolean
true only when the site rejects punctuation. It weakens the password, so do
not set it speculatively.string
The exact punctuation the site accepts. Applied as a filter over the vault’s
own safe set,
!@#$*+-_=.?: characters outside that set are ignored. If none
of them is in the set, the call is refused with invalid_policy. Pass
noSymbols instead when the site takes no punctuation at all.boolean
true when the site rejects three identical characters in a row.noSymbols is set. A minLength greater than maxLength is
refused with invalid_policy.
Returns requestId, status, expiresAt and a reminder note. Nothing in
it is safe to type into a form. status is one of:
Agent Vault still tries to notify the owner of every account opened from the
pre-approved list (best effort). Pre-approval completes at once for at most 2
signups per listed site and, by default, 60 per account in a 24-hour window.
Past either ceiling, or when another approval check fails, the request falls back
to
pending, so handle both statuses. See Limits.
The 500-login ceiling is the exception: an account that already holds 500 logins
gets login_limit straight away, pre-approved site or not, and no request is
created.
Errors: signup_not_enabled, too_many_pending, login_limit (the
account already holds 500 logins, counting ones the account holder added; no
request is created), invalid_website, invalid_email, invalid_username,
invalid_policy, password_too_short, rate_limited (over 20 credential writes this minute for
this key; wait, then call again), write_outcome_unknown (never retry blind).
Example arguments
get_signup_request
Check a signup request once, without blocking.
string
required
The
requestId create_login returned.status:
On
approved the reply is
{ status, login, passwordPolicy, passwordOwnerManaged }:
object
The new login’s catalog entry,
exactly as
get_token_catalog will list it. Its fields:object
What the vault generated (length, character classes, symbol alphabet), so you
can answer a “must contain” validator without knowing the value.
boolean
true when the owner has since replaced the password from their dashboard.
passwordPolicy then describes a password that no longer exists, and
rotate_login_password is permanently refused for this login. The token still
fills the owner’s password. If the site rejects it, tell the owner.request_not_found (re-read the id; do not guess),
scope_denied (the owner has since removed the approved login from this key’s
scope).
Example arguments
wait_for_signup_approval
Block until the owner decides, instead of polling yourself.
string
required
The
requestId create_login returned.number
default:"45000"
How long to wait. Must be greater than 0:
0 or a negative value is refused
before the wait starts, with a tool error beginning MCP error -32602.
Anything over 45000 is clamped to 45000, because most MCP hosts abandon a
tool call at about 60 seconds. Call again to keep waiting.status and a note for every decision, and for a wait that
ran out:
An owner usually takes longer than one wait, so expect to call this several
times. A wait that ends leaves the request untouched. On no result should you
call
create_login again for the same site.
Errors: a refusal no further poll can change ends the wait at once as a tool
error, not a status:
request_not_found: the id is unknown, malformed or belongs to another key, or (rarely) the approved login was since deleted. Re-read the id; do not guess.scope_denied: the request was approved, but the owner has since removed that login from this key’s or connection’s access. Ask the owner.- A key refusal such as
invalid_api_keyorkey_revoked. See Error codes.
429 or a 5xx on one poll does not end the wait. The
next poll asks again, and if none answers before the wait runs out the status is
unknown.
Example arguments
set_login_username
Record the handle a site has already accepted on a login opened with only an
email.
string
required
The login’s label:
login.label from the approved request, or the label in
the token catalog.string
required
The handle the site accepted.
not_found,
the same answer as an unknown label. Re-sending the handle already stored still
succeeds after the seven days.
Errors: username_already_set, not_found (no such login, or not one this
key may change), invalid_username, signup_not_enabled, daily_limit (the
account’s 25 changes in a 24-hour window are spent; it resets 24 hours after the
first counted change),
rate_limited (over 20 credential writes this minute; wait, then call again),
write_outcome_unknown (re-send the same handle).
Example arguments
rotate_login_password
Mint a replacement password for a login an agent created, when the site rejected
the generated one at signup, or on a change form that asks only for a new
password.
string
required
The login’s label.
integer
The site’s stated minimum length. At most 64: a higher value is refused with
invalid_policy, and the account holder has to set the password themselves.integer
The site’s stated maximum length. Below 12 is refused with
password_too_short.boolean
true only when the site rejects punctuation.string
The punctuation the site accepts, filtered to the vault’s safe set
!@#$*+-_=.?. None of them in that set is refused with invalid_policy.boolean
true when the site rejects three identical characters in a row.create_login: 12 to 64 characters, 24 by default.
Returns success. The mock tokens do not change: re-fill the same password
token and the new password goes in. The password is generated on the server,
stored encrypted and never returned.
Only the API key that created the login can rotate its password, and only within
seven days of the login being created. Seven days is the default; a deployment
can set a different window.
Errors: not_rotatable (a login the owner saved, one whose password they
replaced, one another key created, or one created more than seven days ago; this
tells you nothing about whether such a login exists),
not_found, invalid_policy, password_too_short, signup_not_enabled,
daily_limit, rate_limited (over 20 credential writes this minute; wait, then
call again), write_outcome_unknown (stop and hand it to the owner).
Example arguments
Drive the page
open_page
Launch the proxied browser on the first call, then navigate. Later calls reuse
the same page, so you can walk a multi-step checkout.
string
required
Absolute URL, for example
https://shop.example.com/checkout.Opened <url>. If the proxy connection had dropped, open_page
launches a replacement session; the run report then covers only what happened
after the restart (see get_run_report).
On the hosted server, an open_page without session starts a new browser
every time. To go to another page, pass the session you already have. When
the connection already has other browsers open, the answer names them, so you
can reuse or close them rather than load the site in several at once.
In a residential egress session, which includes
every session on the hosted server where the deployment has one, an http://
address is opened as https:// on the same host, and so is any http:// page a
link, form or popup leads to.
Errors:
insecure_site. The site would not load over HTTPS, or it redirected to anhttp://address. The message begins “This site doesn’t support HTTPS” or “This site redirected to a plain http:// address”, and ends “Nothing was sent over plain http.” The page did not change: the tab is still where it was, and what you filled there is still there. Do not callopen_pageon the same address again, since it fails the same way. Choose another site, or tell the user this one cannot be used. When aclickorpressleads to such a page, it returns the browser’s own navigation error instead, such asnet::ERR_SSL_PROTOCOL_ERROR, and the tab stays where it was.egress_unavailable. The firstopen_pagecould not start a session, because residential egress is required (SELF_AGENT_PAY_RESIDENTIAL_EGRESS=1) and the deployment has none, or its provider is not answering for the account’s country. Nothing was opened. The message says how many seconds to wait before trying again.egress_lost. The session’s residential egress stopped working. Every later call that reaches the browser is refused with it, includingget_session_outcome.get_run_reportandclose_sessionstill work. Read the report, callclose_session, thenopen_pageagain.
Example arguments
click
Click an element, for example the place-order button.
string
required
The element to click.
Clicked <selector>. When the click opened a new tab, the reply
says so: call wait_for_popup before anything else, because every tool still
targets the page you were on.
A reply without that notice does not prove no tab opened. The proxy reports
only a tab that arrives within a short window after the click. A tab the site
opens later, for example a 3-D Secure or wallet window after a server round
trip, is still queued for wait_for_popup with no notice. When the checkout
plainly moved to another tab, call wait_for_popup anyway.
When a click times out because something covers the target (an expanded payment
iframe, a cookie wall, a modal scrim), the error says so and names the fix.
Usually that is press with Enter, or Space for a checkbox or radio. Where
no keypress activates the target, the error asks you to clear the overlay.
Example arguments
press
Send a keypress to an element instead of clicking it. Use it when an overlay
swallows pointer events, or when a form only submits on Enter.
string
required
The element to press the key on.
string
required
Playwright key syntax:
Enter, Tab, Escape, ArrowDown, or a character.string
The iframe the element is inside, for a hosted checkout that submits on Enter.
Pressed <key> on <selector>, and a new-tab notice like click.
Errors: clipboard_blocked for copy, cut and paste chords (Control+c,
Control+v, Control+x and their Meta spellings) once any token has been
filled. Click, or press a plain key, instead.
Example arguments
wait_for_selector
Wait for an element to reach a state.
string
required
The element to wait for.
string
One of
attached, detached, visible, hidden. Defaults to the page’s
default (appear).number
default:"30000"
How long to wait, in milliseconds.
string
Wait inside a hosted iframe, for example to check a card field is there before
filling it.
Found <selector>.
A wait that returns counts as an action between a card number and its
security code. Do your waiting before the number goes in. See
Card order.
Example arguments
count
How many elements match a selector right now, and how many of those are
visible. Never waits, so probing several candidate selectors costs no timeout.
string
required
The selector to count.
string
Count inside a hosted iframe.
{ count, visible }.
A nonzero
visible is not a promise the fill will work: a visible input can
still be disabled or read-only. Set fill_checkout’s timeout regardless.
count is the one probe that is safe between a card number and its security
code.
After any token fill, a selector that tests a field’s value, or puts a digit in
a text match, is refused with sensitive_data_blocked. Point with an id, class,
[name=…], [type=…] or text without digits. Text without digits is allowed
whatever value the page is showing, so the non-digit characters of any value a
page echoes as text stay testable. See
What the agent can see.
Example arguments
wait_for_popup
Wait for a popup or new tab this page opened (a 3-D Secure window, a wallet
sign-in, a hosted checkout) and make it the current page.
number
default:"10000"
How long to wait. Anything over 60000 is clamped to 60000, because this wait
holds the session. Call again to keep waiting.
fill_checkout, click, press,
wait_for_selector and open_page all target the popup. If the popup closed
itself and had opened a tab of its own, you land on that child tab and the
reply says so. It is a different document, so read it before acting.
Errors: fails if no popup opens within the timeout. A late popup is queued,
so one retry is worthwhile.
Example arguments
close_popup
Close the popup and make the page that opened it current again. Safe even if the
popup already closed itself.
This tool takes no parameters.
Returns where you landed: the opener; a sibling tab queued under it; a child
tab the popup opened; or, if every page that opened it had closed, a blank page
in the same browser with cookies intact (navigate back with open_page). Read
the page before assuming your checkout is still on screen.
Fill
fill_checkout
Fill an ordered list of fields with mock tokens or real values.
array
required
The fields to fill, in order.
number
default:"30000"
How long each field gets to become fillable before the call fails with the
reason. Must be 1 or more:
0 means no timeout, not an immediate failure. It
does not count time spent waiting on the account owner (a purchase approval or
a live CVV entry).Filled N field(s). When the account owner was pulled in to approve
the purchase or type the security code, the reply says so. A plain reply means
nobody was asked.
When the key is verification-only (see API keys) and
the batch filled a card field, the reply also carries a card_fields_verified
note. The proxy typed a public test card, not the owner’s card. Nothing was
reserved or charged, and the owner was not alerted for that card fill. The test card does not
force a merchant to refuse the order, so submit only on a checkout meant for
testing. Treat the fill as a probe of the form, not a purchase.
Card order
Fill expiry and any billing field first, then the card number, then the security code immediately after it. Filling the number authorizes the security code that follows it exactly once. The next action spends that authorization: another field, a click, or a wait that returns. The account owner is then asked to approve the same purchase a second time. Reads do not spend it:observe_page, detect_challenge and
count leave it in place, and a wait that timed out observed nothing.
The tool enforces this:
- A batch that puts any other field straight after the card number is
refused before anything is typed. Reorder it. Only
cardFieldfields move. - A vault value between the number and the code is refused, so fill billing first.
- When a batch ends on the card number, the next
fill_checkoutis refused unless it starts with that card’s security code.
cvv_entry_required means the security code was not supplied. If the owner
did not enter it in time, or the prompt could not be started, report it and let
the user retry. If the message says “Live CVV entry is not yet available”, every retry
fails the same way: stop and tell the user that this card cannot be used on this
deployment for a checkout that asks for the security code. See
Live CVV.
Example arguments
number and cvv tokens you
fill: its expiryMonth and expiryYear, not fixed values like the ones above.
Both are numbers, with a four-digit year. Match the format the control expects,
for example 11 and 2029, or 1129 for a single MMYY field.
fill_login
Fill a sign-in or signup form’s identifier and password with their mock tokens.
Each half is optional.
string
The identifier field. Pass with
usernameToken.string
Choose by the field’s visible label and
type, not its name. For an email
field, tokens.email. If the entry has no tokens.email, ask the account
holder rather than filling tokens.username, which may be a handle. For a
field that asks for the site’s own handle in so many words, tokens.username. See
Pick the identifier token.string
The password field. Pass with
passwordToken.string
The login’s password token.
string
A repeat-password field on a signup form, filled with the same password token.
Needs the password pair in the same call.
Filled username and password.
- Email-first sign-ins (Microsoft, Okta, Google) take two calls: the identifier pair, submit, wait for the password field, then the password pair. Passing both before the password field exists fills nothing and says so. Each field gets 3 seconds to appear and be visible before the call refuses with “Nothing was filled”.
- Do not choose the token from the field’s name. A field named
username(in itsname,id,data-testidorautocomplete) is very often the email field. Read itstypeand visible label instead. - Authenticator codes. If this sign-in then asks for an authenticator-app
code and the login has
tokens.totp, fill that token withfill_checkoutand submit promptly. Use it only for this login’s authenticator step.
Example arguments
declare_page_hint
Tell the vault where something is on the current page when it cannot find it by
convention. The main use is the order total on a capped card.
string
required
The hint kind, for example
order.total. See list_page_hint_kinds.string
required
Plain CSS only. No
text=, :has-text(), >>, XPath, _react= or
:visible: a hint points at an element, it does not search for one. At most
512 characters. An order.total selector matching more than 8 elements, or an
element over 4,000 characters, adds nothing and answers accepted: false.string
The iframe the element lives in. The only way to reach a total inside an
overlay checkout such as Paddle or Lemon Squeezy. Plain CSS, at most 512
characters. For
order.total the vault also reads the frame’s whole text and
every other frame on the page. Over 20,000 characters in the frame, over
16,384 counting the page’s own text, or more than 32 frames on the page, the
hint is dropped and answers accepted: false even though the selector
matched. Point at a total outside the frame if there is one. Otherwise a card
with an approval threshold asks the account holder, and a card with spending
limits is refused with checkout_total_unknown.string
default:"text"
text or attribute. An attribute’s value must name a currency: a bare
49.99 is refused.string
Which attribute to read when
read is attribute, for example aria-label,
content or data-total.string
For
login.totp_secret: the label of the stored login the secret belongs to.{ kind, accepted, checked }, plus resolved, amount and
currency when checked is true, or untrusted naming why a figure was
refused. A login.totp_secret hint returns stored, digits and period.
After any token fill the reply is { accepted: true, checked: false }: not a
rejection, because the hint is kept and used at the card fill. That applies only
to a selector and frame built from ids, classes, element names and
presence-only attribute tests such as [data-total]. One that compares an
attribute value, such as [data-testid="total"], is refused with invalid_hint
after a fill, and one declared before the fill is skipped when the card fill
reads the total. accepted: false
means the hint was dropped, so declare it again. See
What changes after the first fill.
You supply a location, never a value. Declare order.total
before filling the card, and again after a navigation.
Errors: invalid_hint, hint_unresolved, hint_uncertain. A selector
or frame over 512 characters is refused with invalid_hint before anything is
recorded. A page holds
at most 4 hints of each kind: a fifth distinct hint of the same kind on that page
is refused with invalid_hint. Repeating a hint does not count, and any
navigation of the page clears the count. A
login.totp_secret hint can also fail its preconditions first:
sensitive_data_blocked (this session has not signed in as that login) or
insecure_page (the page is not on https://, and is not a loopback
http:// page). Full guide:
Page hints.
Example arguments
list_page_hint_kinds
List the hint kinds this deployment accepts, what each is for and which
attributes it may read. Kinds are added server side, so ask rather than assume.
This tool takes no parameters. It needs the session open_page starts, because
the list comes from the vault.
Look at the page
None of these tools return a filled value, and none spends the card authorization.observe_page
A fixed set of proxy-computed answers about the page. This is how you tell a
page that rejected your input from one that moved on, including after a fill,
when screenshots are blacked out.
This tool takes no parameters.
Returns:
boolean
A password field is on screen.
boolean
A field the page declares as its sign-in identifier.
boolean
An email or phone input. A shipping form has one too.
boolean
The page says a code was sent.
boolean
A validation or error message is showing.
boolean
The page is refusing further attempts.
string
Which signals matched. Value free.
string[]
Short strings saying why the page rejected you, in its own words, for example
["Enter a valid postcode"]. At most 5, each cut to 200 characters, drawn
only from elements the page marks as validation output and scrubbed of filled
values and anything card, email, phone or token shaped.true
Present only when the scan did not finish.
- Read
errorTextwhenever it is non-empty, not only whenerroris true, then fix your input and fill again. Do not branch on the wording or paste it to the user. - An empty
errorTextdoes not mean nothing was rejected. Some merchants style errors without marking them up. It is also withheld for the rest of a session in which the owner drove the page during a handoff. - A live region also announces success. Read what it says.
snapshot_page
The accessibility tree as YAML: every field, its accessible name and a
[ref=eN] handle, reaching into iframes including cross-origin ones. Call it
first on a page you have not seen.
number
default:"12"
How deep to walk. The proxy clamps it to 30.
number
Line offset for the next page of a truncated result. Pass it with the depth
the truncation note names.
- Pre-fill only. Once any token is filled it is refused with
sensitive_data_blockedfor the rest of the session, because the page chooses accessible names. Snapshot while orienting, then useobserve_pageandcount. - A ref is a selector. Pass
aria-ref=e7tocount,wait_for_selector,clickorpress. Refs survive DOM changes but not a navigation. A fill refuses a ref: fill by CSS selector. - Field selectors. The snapshot ends with a
# Field selectorscomment that names, for each field ref, the CSSselectorto pass tofill_checkout, and the iframe’sframeselector for a field inside one. Use those rather than guessing. - Values are never in it. A blank-looking field says nothing about whether a fill landed.
- Paging. A result holds at most 24,000 characters. If it was cut, its last line names a cursor and depth. Pass both: a cursor is an offset into a capture at that depth. Lower the depth instead (and drop the cursor) only when what overflowed is nesting, not width.
Example arguments
list_frames
This page’s frames as { name, url }, to find which iframe holds the payment
form. Never frame content.
This tool takes no parameters.
Returns { frames: [{ name, url }] }. A cross-origin frame may report an
empty name or URL.
Errors: sensitive_data_blocked once any token has been filled, for the
rest of the session.
Use it when a page already shows its payment surface and you have filled
nothing. On a multi-page checkout the card frame usually appears only after
sign-in, so plan on targeting it by CSS instead: get the selector from
get_checkout_recipes, or use the known Stripe titles:
Settle every card frame before the number goes in.
detect_challenge
Whether the page is showing a 3-D Secure prompt, a one-time-code step, a
CAPTCHA or bot wall, or a silent block. Call it first whenever a click or fill
seems to do nothing: an invisible bot wall looks exactly like a stuck overlay.
This tool takes no parameters.
Returns { detected, type, types, reason, signature?, deliveredCode?, ambiguousOnly?, inconclusive? }.
type is three_ds, otp, captcha or blocked. Never page text.
blocked means the site stopped answering without saying why. signature
names the shape: stalled_search (a search that never loads), search_bounce
(a click bounced back to the previous page with an error) or error_reference
(an error dialog with a reference code). deliveredCode: true on an otp
means the page says the code was sent by SMS or email.
ambiguousOnly means the page has a field that might be a code input,
flagged only because this sign-in’s windows (this page, its opener, or a popup
it opened) recently took a login password and no checkout token since.
request_challenge_handoff refuses it with handoff_not_applicable.get_network_log
A diagnostic that is off unless the deployment enables it, typically on
staging. Lists the current page’s last 25 document, xhr and fetch requests:
method, host, path (cut to 200 characters), status, and how long ago.
This tool takes no parameters.
Returns one line per request, oldest first, or a message saying it is not
available on this deployment. No bodies, headers or query strings are ever
returned. After a vault value has been filled, the host is only this site or
another site and no path is shown.
Use it when a submit appears to do nothing: a silent server refusal shows as a
4xx on the form’s request.
Hand off a challenge
request_challenge_handoff
Hand the page to the account owner so they complete a challenge you must not,
then resume.
This tool takes no parameters.
The proxy re-detects before summoning anyone, then asks the owner with a
dashboard banner, plus an email, desktop or background alert on each channel
they turned on while their A checkout needs me to clear a bank or
verification step switch is on, with a link that streams the live page. The call
blocks until they finish, the wait times out (about 10 minutes), or the
session ends.
Returns { outcome, challengeType, durationMs }, never what the owner
typed.
Errors:
handoff_not_applicable (an ambiguousOnly detection; the page is
still yours), challenge_scan_incomplete (nobody was notified; detect again),
challenge_required for other page tools while the handoff is parked, and
unknown_target in two variants. Unknown pageId means the page was gone before
the handoff started: nobody was notified, so call close_popup or move to another
live page and detect again. No challenge is present on this page means the
proxy re-detected on a live page and found nothing: carry on.
Most MCP hosts cancel a tool call at 60 seconds. That does not cancel the
handoff: the owner still has the page. Re-orient with
wait_for_selector or
get_session_outcome once they are done, rather than calling this again.Confirm and finish
get_session_outcome
After submitting, race a success selector against an optional failure selector.
Use this instead of scraping a confirmation page, which may echo card digits.
string
required
Appears on success, for example
#order-success.string
Appears on failure, for example
.checkout-error.number
default:"15000"
Maximum 30000; larger values are clamped.
0 or a negative value means the
default, not “no timeout”. A handoff waits for this check, so it
is also the longest the owner can be kept from the page. To watch longer, call
again.boolean
true only when the success selector confirms a completed checkout, so the
session is recorded as confirmed. Leave it off for logins.string
Required with
confirmsCheckout. The control you clicked or pressed (with
Enter) to place the order, exactly as you passed it, and the latest click or
press on the page. Call this right after the submit.outcome (success, failure, timeout or cancelled) and
matched, the selector that decided it. timeout means neither selector
appeared yet, not that the checkout failed. cancelled means a handoff took
the page over mid-check: ask again once it returns.
With confirmsCheckout: true, the confirmation is recorded only when it is
corroborated:
- the submit control has left the screen,
- no tab this page opened is queued or waiting for
wait_for_popup, - the page shows no error, rate limit, sign-in step, sent-code notice or challenge step,
- the success selector is still there and the failure selector is not.
corroborated: true and the recording is started (best
effort; get_run_report is the record). Otherwise it says corroborated: false
with notCorroboratedBecause, and nothing is recorded. Do not report an order
on it.
A redacted receipt image is captured only for a session that filled no mock
tokens, such as a saved-payment checkout. A normal token-filled checkout is
marked confirmed with no receipt image.
Example arguments
get_run_report
What the session actually did, from the proxy’s own record. Read this before
telling anyone whether a card was charged.
This tool takes no parameters. It needs an open session, and is safe to call
while a handoff is parked.
Returns:
string[]
The distinct token kinds filled, for example
["identity", "address", "card", "cvv"].number
How many fills the proxy refused (paused card, merchant block, scope, URL lock).
A burst of
rate_limited refusals counts once a minute, not once each.boolean
Whether a checkout confirmation was recorded.
string
The total on that confirmation page, when there was one.
string[]
What the session changed in the vault, as
label:change, for example
["doordash:signup_requested", "doordash:created", "doordash:totp_enrolled"].
These outlive the session. Tell the owner about them even when the checkout
failed.boolean
false when open_page had to start a new proxy session partway through the
run, for example after the connection dropped. The numbers then cover only
what happened after the restart, and a note says so. A short record is not
evidence of a quiet run.object[]
Only when
open_page replaced this run’s proxy session partway through (a
lost tab, say) and the old session’s record could still be read. One object
per earlier proxy session, oldest first, each with the same
filledFieldKinds, deniedCount, confirmationDetected, orderTotal and
credentialWrites fields as the report. An earlierReportsNote comes with it.timeout from get_session_outcome plus card in filledFieldKinds means the
card was entered and you did not see the confirmation. Say that.
After a restart
WithearlierReports, the run is split into segments: each earlier proxy
session, then the current one. The top-level fields describe only the current
one. Check every segment before you tell anyone nothing was charged. card in
any segment’s filledFieldKinds, or confirmationDetected: true in any
segment, holds for the whole run.
When the current proxy session cannot be read but earlier segments were kept,
the reply has no top-level report fields. It carries earlierReports,
currentReport: null, coversWholeRun: false, a currentReportNote, and
credentialWrites when the run changed the vault:
currentReport: null does not mean nothing was filled since the restart. It
means Agent Vault could not ask. The earlier segments still count.
Kinds and counts only, never a filled value. A read it cannot complete, with no
earlier segments to report, refuses and says so rather than answering with
zeros. The proxy gets 5 seconds to answer this read, here and in
close_session.
close_session
Close the browser and end the proxy session. Call it when the task is done.
This tool takes no parameters.
Returns status and report, the same object get_run_report returns, so
your last act is reading what the run did. If the browser did not close cleanly,
status says so and teardownError is set. When open_page replaced a proxy
session partway through, the reply also carries earlierReports and
earlierReportsNote, as in get_run_report. Count every
segment with report.
If no session was ever opened, or its connection had already died, there is no
report and the reply says so. Do not read that as nothing having been filled.
Any credentialWrites made through the server are still listed, and so are any
earlierReports, with coversWholeRun: false.
Related
Rules of engagement
The rules your agent follows when it uses these tools.
Error codes
Every refusal and whether retrying is safe.
Handling challenges
3-D Secure, one-time codes and CAPTCHAs.
Confirming outcomes
What counts as proof an order went through.