Every change Wellknown observed on this MCP server, newest first, with what it was before and what it became. Tool-surface changes carry the definition diff. Nothing here is edited after the fact.
Certificate changed, valid to 2027-01-06
Recently observed → Unavailable
Live → Recently observed
Changed the definition of "session_preload"
{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"recipe":{"default":null,"description":"Builtin recipe name appended after `scripts` (e.g. \"xhs-sign\"): the\nmaintained document-start wrapper for that site — same source the\nengine auto-mounts on its navigations, without pasting JS. Unknown\nnames are an error, not a silent no-op.","type":["string","null"]},"scripts":{"default":[],"description":" ⟨63 unchanged words⟩
Changed the definition of "session_input"
⟨42 unchanged words⟩ in onBlur (React capture listeners, #100).AIndisabledfull mode a newline in the text types as the Enter key (key:'Enter',readonlykeyCode 13 — "type + Enter submit"): a textarea keeps the newline in its value, a single-line input stays single-line, the page's Enter listeners fire before the blur, and if the key wasn't canceled the input's form submits implicitly (Chrome's default action; preventDefault on the key ordetachedsubmitfieldevents vetoes it, so chat inputs that swallow Enter stay unsubmitted). Any characters — newlines, quotes, backslashes — ride through as-is. A refused fill answers`filledfilled
Changed the definition of "session_screenshot"
{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"dpr":{"description":"Device pixel ratio (#185): rasterize at device resolution (Retina/HiDPI\nsharp) — the bitmap comes back width·dpr × height·dpr with CSS geometry\nintact. Clamped to 1.0..=3.0 (default: 1.0)","format":"float","type":["number","null"]},"full_page":{"default":false,"description":"Capture ⟨89 unchanged words⟩
Added "flow_search" (41 tools before, 42 now)
List flow packages installable from the hub (env-configured AGINXBROWSER_DUPHUB_URL, default the public DupHub) — flow discovery: flow_install takes a name, this is how you learn the names. Each entry carries name/description/version/downloads; feed the name to flow_install. Queries the market's kind=agx-flow filter (flows are templates whose package root carries flow.json) and re-filters client-side, so clones never surface as flows. A hub predating the kind protocol reports kind_filter unsupported instead of guessing.
Added "flow_install"; changed the definition of "session_cookies", "session_create" and "session_network" (40 tools before, 41 now)
Export the session's currentcookiescookies.asDefault[(meta absent/false) is the full Set-Cookie form ("name=valuename=value; Domain=…; Path=/",...]flagsforincluded) — thepage'slogin-stateURL.reuseUse
Changed the definition of "session_input"
⟨6 unchanged words⟩ by its index (from session_state output), focusing it and dispatching input/changeeventsevents.(events:"full"keyboardiscyclethepercompletecharacterhumanwithtypingeventsgesture:
Changed the definition of "fetch"
⟨53 unchanged words⟩ (pure HTTP, refuses the upgrade) /obscurabrowser (always the JS browser).
⟨64 unchanged words⟩ ,"type":"string"},{"const":"obscurabrowser","description":"Always use the diting browser (current behaviour pre-tiering).\n\"browser\" is.\nWireacceptednameasisan
Changed the definition of "session_create"
⟨286 unchanged words⟩ ":"Initial URL to navigate to (optional). `start_url` is honored as an\nalias (#115) — callers guessing that name must not land on about:blank.","type":["string","null"]} ⟨51 unchanged words⟩
Added "session_preload" (39 tools before, 40 now)
Replace the session's document-start preload group (empty array clears). Sources run before each new document's own scripts — including inline <script> tags — which is the only hook that beats pages whose signing layer captures window.fetch/XHR natives at parse time (xhs's inline jsvmp). Set before the first navigate; applies to every navigation from then on.
Added "account_login" and "session_verdict"; changed the definition of "flow_run" (37 tools before, 39 now)
⟨15 unchanged words⟩ , args, expect?, save?}]}"},"max_steps":{"default":null,"description":"Override the run's step-execution budget (branch loops re-run steps,\nso every revisit counts). Default 1000, clamped 1..=100000. A flow\ndocument may also declare its own max_steps; this wins.","format":"uint64","minimum":0,"type":["integer","null"]},"name":{"default":null,"description":"Or ⟨69 unchanged words⟩
Open a site's login page AS a named account and close the login loop. Creates the session as the account (private jar, device persona), navigates to `url`, and reports which generic login gates the page shows — needs: password | sms | qr | slider (QR scan like xiaohongshu, SMS code, password form, slider/captcha) — plus a session_verdict classification and, when a human step is needed, the session_id and a /live handoff so a person can finish it in the live view (the engine detects and describes; it never fills credentials or solves challenges). With `predicate` and no human gates detected, w…
Changed the definition of "session_challenges"
⟨95 unchanged words⟩ — a human opens the live view (web/live.html/live?session=<id> on the engine's HTTP port), solves the challenge in this session, ⟨16 unchanged words⟩
Changed the definition of "session_click", "session_drag" and "session_input"
⟨21 unchanged words⟩ a DOM click on the session's current page. Before clicking it re-verifies the element in the same frame — if the page changed since session_state (element detached, disabled, hidden, or covered by an overlay), it returns `clicked:false` with a `reason` ("detached"/"disabled"/"not_visible"/"covered_by") and, when covered, a `covered_by` description of the element that would eat the click — never a silent no-op. A submit click may navigate the session — ⟨29 unchanged words⟩
⟨6 unchanged words⟩ position to another: press at `from`, `steps`interpolatedmousemove events(delay_ms apart)
Changed the definition of "account_delete", "account_verify", "cache" and 32 more
⟨16 unchanged words⟩ string"}},"required":["name"],"title":"AccountDeleteParams","type":"object"}
⟨74 unchanged words⟩ "]}},"required":["name"],"title":"AccountVerifyParams","type":"object"}
Certificate recorded, valid to 2026-11-06
Authorization not required
Added "account_delete", "account_list", "account_verify" and 3 more; changed the definition of "import_curl", "render_markdown", "render_video" and 3 more (31 tools before, 37 now)
{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"account":{"default":null,"description":"Attach the session to a named account: the imported login lands in\nthe account's private jar and is written back under its name after\nevery action — one import per identity, no clobbering.","type":["string","null"]},"curl":{"description":"A \"Copy as ⟨54 unchanged words⟩
⟨377 unchanged words⟩ [].views in the receipt lists the tabs. motion: true bakes an entrance choreography into the artifact: pure-declarative CSS animation with zero scripts - headings split into per-glyph (CJK) / per-word (latin) spans that rise in with expo easing, prose blocks stagger up an nth-child delay ladder, diagram figures grow in with a back ease (GSAP's easing math as public cubic-bezier equivalents, nothing embedded); the diagrams themselves play a flow story on the same clock - nodes land beat by beat, solid edges draw in (dash-offset), dashed returns fade, sequence messages arrive as sent - with a timed caption strip under each figure as the subtitles, which becomes a static transcript under prefers-reduced-motion; the file itself animates in any browser and the receipt records motion plus diagrams[].story (beat times and captions - the hook for muxing voice later).
First tool surface recorded: 31 tools (server version 0.4.1)
Showing the latest 20 events. The API returns up to 500 and filters by kind: ?kind=tool_surface_changed
{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"meta":{"default":null,"description":"Metadata-only view (#102): {name, domain, path, secure, httpOnly,\nsameSite, expires, hostOnly} per cookie — no values, ever. For\nchecking what auth state exists; leave false for the value-bearing\nexport that round-trips login state.","type":["boolean","null"]},"session_id":{"description":"Session ID","type ⟨5 unchanged words⟩
⟨48 unchanged words⟩ create with the same name picks up\nthe warmjar.jar, plus the record's captured localStorage/sessionStorage\n(replayed onto the captured origin; an explicit `storage` param\nwins). 1-64 chars of [a-zA-Z0-9_-].","type": ⟨180 unchanged words⟩ that live in localStorage rather than the cookiejar.\nRound-tripsjar.withAn\noptionalsession_storage.\"origin\": \"https://site\" key holds the injection until a\nnavigation lands on that origin (a session created without a\nstart_url sits on about:blank first). Round-trips with\nsession_storage."},"ttl_secs":{"description":"Idle time-to-live ⟨106 unchanged words⟩
⟨82 unchanged words⟩ Default returns every request as compact rows (method/url/status/type/sizemethod/url/status/type/size/nav); include_headers=true adds each request's outbound header set to its row (#97). Each row's nav is the navigation generation that issued it and the payload's top-level nav is the current one - a lower row nav belongs to an earlier (e.g. timed-out) navigation attempt (#101). Navigate to the video page first, ⟨6 unchanged words⟩
⟨160 unchanged words⟩ "type":["boolean","null"]},"include_headers":{"default":null,"description":"Add each request's outbound header set to its row (`headers` on the\ncompact rows, `request_headers` on the `xhr` rows) — what the page's\nJS actually sent, signed customs like x-s/x-s-common included. Off by\ndefault: headers can carry tokens.","type":["boolean","null"]},"session_id":{"description":"Session ID","type ⟨21 unchanged words⟩
Install a third-party flow from DupHub into the server's workflow directory, making it runnable by name via flow_run. Pulls the template's manifest, verifies every file's sha256 against it (a package that fails anywhere is not installed), requires a valid flow.json with a steps array, and lands atomically under workflow/<name>/ — replacing any previous install, overriding a same-named built-in. The receipt lists files, total bytes, step count, and how many steps carry scripts (eval_steps) — third-party scripts run with this engine's privileges in session pages, so review before running. Sourc…
One call answers "where did this session land": verdict is one of challenge (risk control engaged — punish page or a 200-status API body that swallowed the wall; the response carries a handoff instruction for a human to solve it in the live view), captcha (explicit CAPTCHA interstitial), login (bounced to a login form — auth expired), empty, landed (normal 2xx content page), or unknown (couldn't classify — read facts; a non-2xx main document lands here with facts.doc_status carrying the number, so a zhihu-style burst 403 is branchable). The facts sheet also carries challenge_events, requests,…
⟨36 unchanged words⟩ "delay_ms":{"default":null,"description":"DelayMean delay between moves in ms (default 18 humanized / 30 linear)——\nper-stepgivestimingmousemove-driven\nwidgetsistimejitteredtoaroundreactthisperwhenstephumanizing","format":"uint64","minimum":0, ⟨8 unchanged words⟩ to press the mouse button down"},"humanize":{"default":null,"description":"Humanize the trajectory: minimum-jerk easing, perpendicular wobble,\ntiming jitter, grip/settle pauses, occasional hesitation and\novershoot-and-correct. Set false when a test/tool needs exact linear\ninterpolation. Default: true","type":["boolean","null"]},"session_id":{"description":"Session ID","type ⟨4 unchanged words⟩ description":"Interpolated mousemove events between from andtoto.(defaultDefault: 24 with\nhumanize on, 10)without","format":"uint32","minimum":0, ⟨17 unchanged words⟩
⟨5 unchanged words⟩ element by its index (from session_state output), dispatching input/change events (full keyboard cycle per character with events:"full"). A disabled, readonly, or detached field answers `filled:false` with a `reason` instead of a silent write. Hidden inputs are legitimate targets and are filled normally.
⟨165 unchanged words⟩ type":["string","null"]}},"title":"CacheParams","type":"object"}
⟨45 unchanged words⟩ ,"required":["url","selector"],"title":"ClickParams","type":"object"}
⟨97 unchanged words⟩ boolean"}},"required":["url"],"title":"DownloadParams","type":"object"}
⟨38 unchanged words⟩ ,"required":["url","script"],"title":"EvalParams","type":"object"}
⟨348 unchanged words⟩ "]}},"required":["url"],"title":"FetchParams","type":"object"}
⟨84 unchanged words⟩ wins over the flow's own\nvars defaults."}},"title":"FlowRunParams","type":"object"}
⟨95 unchanged words⟩ boolean"}},"required":["curl"],"title":"ImportCurlParams","type":"object"}
⟨307 unchanged words⟩ "]}},"required":["markdown"],"title":"RenderMarkdownParams","type":"object"}
{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"render_pdf parameters — mirrors POST /pdf's request shape.","properties":{"format":{"default":"pdf" ⟨190 unchanged words⟩ :"integer"}},"required":["url"],"title":"RenderPdfParams","type":"object"}
⟨167 unchanged words⟩ object"}},"$schema":"https://json-schema.org/draft/2020-12/schema","description":"render_video parameters — mirrors POST /video's request shape.","properties":{"audio":{"anyOf":[{" ⟨278 unchanged words⟩ :"integer"}},"required":["url"],"title":"RenderVideoParams","type":"object"}
⟨133 unchanged words⟩ "]}},"required":["q"],"title":"SearchParams","type":"object"}
⟨9 unchanged words⟩ string"}},"required":["session_id"],"title":"SessionChallengesParams","type":"object"}
⟨23 unchanged words⟩ ,"required":["session_id","index"],"title":"SessionClickParams","type":"object"}
⟨73 unchanged words⟩ :["session_id","x","y"],"title":"SessionClickXyParams","type":"object"}
⟨16 unchanged words⟩ string"}},"required":["session_id"],"title":"SessionCloneParams","type":"object"}
⟨9 unchanged words⟩ string"}},"required":["session_id"],"title":"SessionCloseParams","type":"object"}
⟨89 unchanged words⟩ "]}},"required":["session_id"],"title":"SessionConsoleParams","type":"object"}
⟨9 unchanged words⟩ string"}},"required":["session_id"],"title":"SessionCookiesParams","type":"object"}
⟨341 unchanged words⟩ type":["integer","null"]}},"title":"SessionCreateParams","type":"object"}
⟨61 unchanged words⟩ ,"required":["session_id","action"],"title":"SessionDialogParams","type":"object"}
⟨108 unchanged words⟩ :["session_id","from","to"],"title":"SessionDragParams","type":"object"}
⟨73 unchanged words⟩ ,"required":["session_id","script"],"title":"SessionEvalParams","type":"object"}
⟨57 unchanged words⟩ string"}},"required":["session_id"],"title":"SessionExportParams","type":"object"}
⟨79 unchanged words⟩ :["session_id","index","text"],"title":"SessionInputParams","type":"object"}
⟨18 unchanged words⟩ ,"required":["session_id","url"],"title":"SessionNavigateParams","type":"object"}
⟨185 unchanged words⟩ "]}},"required":["session_id"],"title":"SessionNetworkParams","type":"object"}
⟨94 unchanged words⟩ "]}},"required":["session_id"],"title":"SessionScreenshotParams","type":"object"}
⟨40 unchanged words⟩ string"}},"required":["session_id"],"title":"SessionScrollParams","type":"object"}
⟨112 unchanged words⟩ :["session_id","selector","files"],"title":"SessionSetFilesParams","type":"object"}
⟨9 unchanged words⟩ string"}},"required":["session_id"],"title":"SessionStateParams","type":"object"}
⟨9 unchanged words⟩ string"}},"required":["session_id"],"title":"SessionCookiesParams","type":"object"}
⟨70 unchanged words⟩ "]}},"required":["session_id"],"title":"SessionViewportParams","type":"object"}
⟨59 unchanged words⟩ integer"}},"required":["session_id"],"title":"SessionWaitParams","type":"object"}
⟨25 unchanged words⟩ inline SVG.","type":"string"},"motion":{"description":"Bake the entrance choreography into the artifact (default false):\npure-declarative CSS animation — headings split into per-glyph/per-\nword spans that rise in with expo easing, prose blocks stagger up a\nnth-child delay ladder, and diagram figures grow in with a back\nease (GSAP's easing math as public cubic-bezier equivalents). The\ndiagrams animate too, on one story clock: nodes pop in one beat at\na time, solid edges draw themselves (dash-offset drain), dashed\nreturns fade, sequence messages land as they are \"sent\", and a\ntimed caption strip under each figure subtitles the beats — under\nprefers-reduced-motion the strip becomes a static transcript.\nZero scripts: the file itself animates in any browser, subtitles\nand all; the receipt records motion (plus diagrams[].story with\nthe beat times, the hook for muxing voice later) so a cached\nartifact is never mistaken for the static one","type":["boolean","null"]},"preset":{"description":"Visual preset: \" ⟨143 unchanged words⟩
⟨76 unchanged words⟩ `subtitles_srt` muxes an SRT as a soft mov_text track and (by default, `burn_subtitles: false` to opt out) burns the same cues into the frame pixels — QuickTime, WeChat and most social embeds ignore the soft track. Requires ffmpeg on the server. Returns base64 ⟨8 unchanged words⟩
⟨197 unchanged words⟩ , volume-scaled, faded\nout at the tail."},"burn_subtitles":{"default":null,"description":"Burn the cues into the frame pixels too (hardsub) — on by default\nwhen `subtitles_srt` is present; QuickTime, WeChat and most social\nembeds ignore the soft mov_text track. `false` keeps the soft track\nonly.","type":["boolean","null"]},"fps":{"default":24,"description":"Frames ⟨223 unchanged words⟩
{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"account":{"default":null,"description":"Run as a named login identity (the multi-account layer): a private\ncookie jar seeded from the account record, write-back to the account\nstore after every action. Concurrent logins (`taobao-scraper` vs\n`taobao-publisher`) never clobber each other. The account record\nsurvives the session — a later create with the same name picks up\nthe warm jar. 1-64 chars of [a-zA-Z0-9_-].","type":["string","null"]},"cookies":{"default":[],"description":" ⟨280 unchanged words⟩
⟨14 unchanged words⟩ ":"Session ID","type":"string"},"timeout_ms":{"default":null,"description":"Await budget for the script's promise in ms (default 5000, clamped\n100..120000). Pass a larger budget for slow page-side work such as\nuploads through the page's own fetch; on expiry the tool errors with\nEVAL_TIMEOUT (the script may still be running) instead of returning\na null result.","format":"uint64","minimum":0,"type":["integer","null"]}},"required":["session_id","script"], ⟨4 unchanged words⟩
⟨43 unchanged words⟩ raw action log, one JSON object per line. Format "json" returns a flow.json document — the same recording as editable ops ({op, args}) with cookies/storage stripped — that flow_run replays server-side.
⟨29 unchanged words⟩ raw action log, one JSON object perlineline; \"json\" returns a\nflow.json document (editable ops, cookies/storage stripped) for\nreplay via flow_run","type":["string","null"]} ⟨12 unchanged words⟩
Delete a named login identity: stored record AND live jar. Cookie values are credentials — delete means gone. Sessions currently running as the account keep their in-process jar handle, but nothing writes back. Returns {deleted: name}, or an error naming the account if it does not exist.
List named login identities (the multi-account layer) with metadata only: name, cookie domains, cookie count, updated_at, the last account_verify verdict, and the identity's persona User-Agent (each account is one stable device: its own UA and hardware fingerprint, drawn once and reused). Cookie values are credentials and never leave the server. Use to see which identities exist before session_create {account} picks one.
Check whether a named account is still logged in. Teach-once: the first call passes url + predicate (a JS expression truthy on a logged-in page, e.g. !!document.querySelector('.user-nick')); the spec is remembered and later calls can be bare. Runs in a scratch session AS the account (private jar), so the probe doubles as a cookie refresh. Returns {name, logged_in, url, checked_at}.
Run a flow — a recorded, editable JSON browser-session script — deterministically, with zero model tokens. Steps are {op, args, expect?, save?}: ops cover navigate/click/click_xy/input/scroll/eval/wait/screenshot/state/cookies; {{var}} placeholders in args are filled from vars; expect asserts (url_contains | selector | text_contains | eval_truthy) abort with evidence on failure; save collects a step's output into the receipt. Source the flow inline via "flow", or by "name" from the server's workflow/<name>/flow.json (unknown name → error lists installed workflows). Pass session_id to reuse a …
One-call risk-control report: did this session hit an anti-bot wall? Taobao/tmall's x5 risk control answers 200 like a normal response — either a redirect onto a punish page (_____tmd_____/punish, punish.taobao.com) or an MTop API body carrying FAIL_SYS_USER_VALIDATE / RGV587 / x5secdata. Returns {total, events:[{url,method,status,kind,via}]} where via says whether the wall was navigated into ("url") or swallowed by an API response ("body"). When there are hits, the response also carries the account name (which identity got walled) and a `handoff` instruction: the engine detects and surfaces …
Select files on a file input programmatically (Playwright setInputFiles semantics): builds File objects from base64 content, assigns them to input.files, then dispatches input+change so framework onChange handlers fire. Selector-addressed because file inputs are often hidden and absent from the session_state index.