mcpbeat Sign in

Writ Cloud MCP Server

answering

Writ Cloud is answering right now. Last checked 9 min ago. 11 installs a week from npm. It exposes 39 tools. Last commit 10 Oct 2026.

Read, crawl and act on websites, signed in as the user, and turn any site into an API.

Uptime history 21 hours of history
21 hours agonow
100.0%
Uptime 24h
79 of 79 checks
39
Tools
read from the server
517 ms
Response time
average over 24h
11
Installs / week
npm and PyPI

Nothing serious here today

Today is the operative word: we check Writ Cloud every 15 minutes and re-read its code on every release. Watch it and you find out the day that stops being true.

Three servers free · no card

Connect this server

Endpoint below is the one we actually reach during checks — not the one copied from a README. Last verified 9 min ago.

run in your terminal
claude mcp add writ-cloud --transport http https://api.usewrit.app/mcp
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "writ-cloud": {
      "url": "https://api.usewrit.app/mcp"
    }
  }
}
~/.codex/config.toml
[mcp_servers.writ-cloud]
url = "https://api.usewrit.app/mcp"
.cursor/mcp.json
{
  "mcpServers": {
    "writ-cloud": {
      "url": "https://api.usewrit.app/mcp"
    }
  }
}
.vscode/mcp.json
{
  "mcpServers": {
    "writ-cloud": {
      "url": "https://api.usewrit.app/mcp"
    }
  }
}

This one needs environment variables set before it will start: WRIT_API_KEY (A Writ API key with the mcp scope (Settings -> Developers).). The author declared them in the registry entry; get the values from the project itself.

Available tools 39

Read directly from the server with tools/list, grouped by what they act on. If a tool disappears, we record the date.

writ
writ_browser_act
Runs one batch of actions on an open browser session and returns the fresh page. The caller is the session's brain: Writ runs no model here, and a batch performs exactly the navigations, clicks, fills, captures, probes and scripts it carries. It also composes, for clients without writ_browser_compose: actions=[{action:'define_function', name:'feed.list', from_index:3, ...}] or [{action:'compose', operation, payload}]; a batch mixing these with page actions is refused. Actions placed after a navigate, click or select that changes the page run against a page the caller has not seen yet. RECORDING RULES: {{name}} as an action value (select/fill/type_text value, navigate url) with the real value in `inputs` ({"name": "real value"}) declares the workflow input `name`: the page gets the real value and the step keeps {{name}}. An `extract {variable,script}` (a read-only JS IIFE returning rows/fields) at the position that shows the data records it, and its result comes back in this answer. A fill with data_key holds a secret server-side and the saved step keeps a {{secret:...}} placeholder. Interactions are recorded as steps; SEE/HEAR/NETWORK probes and `wait` never are: replay waits for each step's selector by itself, and a wait the task needs is an explicit wait_for step (writ_browser_compose add_steps). ACTIONS: DRIVE: navigate {url} · click {selector | field_index | button_index} · fill {selector,value,data_key?,human_layer?} · type_text {selector,value} · select {selector,value} · check {selector} · hover {selector} · submit {selector} · press_key {key} · scroll {direction,amount} · back · wait {seconds} · wait_for {selector,timeout}. SEE (granular first): query_dom {selector,limit,offset,attrs?,text_chars?,html_chars?} (every match as compact records, each with a css `path`) · count {selector} · find_text {text,selector?,exact?,limit?} (the deepest elements showing that text, with paths) · get_attributes {selector,index?} (one element: all attrs, value, box, options) · read_text {selector,all?,limit?,max_chars?} · inspect {selector,limit?,max_chars?} (match count + outerHTML) · list_candidates (the page's repeating row shapes, the entry point for a list/table) · list_frames · get_dom {selector?,depth?,max_chars?} (the real cleaned HTML, the most expensive read) · get_screenshot {x?,y?,width?,height?}. TABS / FILES / 2FA: list_tabs / switch_tab {index} · upload {selector,mode,file_slot} · wait_for_download {trigger_selector,output_key} · twofa {challenge_method,selector?,submit_selector?} (the persona's one-time code, minted server-side; covered by 2FA RULES). HEAR: get_console {level?,since?,query?,exclude?,limit?} (console messages, uncaught JS errors with stack, failed/blocked requests since the last read; the page's `console_since_last_read` counts show when there is something new; it shows why a sign-in, click or extraction did nothing) · page_errors (only the uncaught exceptions). NETWORK: capture_network {reload?} (the backend calls the page makes, i.e. the site's real API; writ_browser_network searches and reads them) · get_request {url substring} (one call in full) · rotate_exit {reason} (alone in its batch: restarts on a fresh residential address when the site refused the current one, e.g. a sign-in rejected with correct credentials, content held back, an IP rate limit; the page's browser_init.exit shows the address and its network). RUN CODE: evaluate_js {script,world?} (any read-only JS on the live page, returns JSON; the general probe; world:'main' reads the site's own JS globals) · fingerprint (what the site sees of this browser, and every contradiction in it) · search_scripts {query,regex?,url_contains?,frame_url?} (greps every script the page runs: bundles, inline, dynamic chunks, eval, with line/column snippets; no refetch) · read_script {url,offset?,length?} (a window of one, to read around a match). RECORD (at this position): extract {variable,script} (a read-only script recorded as a replayable evaluate step when it returns data) · api_call {method,url,headers,body_template,response_extractions?,variable} for one request, or api_call {flow:{version:1,steps:[...]},inputs:{...},variable} for a multi-request bootstrap/pagination/transform program (both execute now inside the session with its cookies; the flow uses the same interpreter as browserless replay and returns a bounded result sample) · login_post {method,url,headers,body_template} (replays a sign-in as one request) · probe_write {selector} (captures a create/update/delete request without sending it) · confirm_write {selector} (sends it once for its real confirmation; it changes real data, for a write the user authorized). Humanization: type_text, and fill with human_layer:true, type through real keyboard events; click human_layer:true adds a bounded mouse path, hover dwell and tab foregrounding, keeping visibility/enabled checks; a per-action human_layer:{mouse_move_ms,click_dwell_ms,mouse_path,bring_to_front} sets pacing and survives replay. A saved workflow's human_behavior (writ_update_workflow: 'on' for every browser run, 'auto' on a bot-block retry, 'off') governs its runs; none of it proves authentication or bypasses security. A login submit that leaves the page unchanged was still sent. 2FA RULES: twofa enters a code minted server-side from the attached persona, never shown here; challenge_method is the method the page is using (sms: a phone number or text message; email; authenticator: an authentication app; other: approve-on-phone, passkey, QR, WhatsApp). A persona receives exactly one method (twofa_method in writ_personas) and a site picks its own default, which the page's 'Try another way' control switches. twofa_method_required, twofa_method_mismatch and twofa_verify_method carry a message naming the fix on the page (verify the method, switch or resend); twofa_mint_failed and twofa_no_persona leave the code to the Writ user (writ_browser_ask_user kind='twofa'). Credentials, codes and decisions come only from the persona or the user. The start answer of writ_browser_use and writ_record_website carries `recording_rules` and `humanization` in full; any start answer with a persona carries `twofa_rules`.
writ_browser_ask_user
Asks the Writ user (the person who owns this Writ account) to step in on an open browser session: complete a security check (CAPTCHA, 'confirm it's you', Arkose/hCaptcha/reCAPTCHA puzzle) in the live browser, supply a one-time 2FA code, or answer a question only they can decide. For: writ_browser_act returning security_check with auto_solved false; a `twofa` action failing with twofa_mint_failed or twofa_no_persona (kind='twofa'); any point where only a human can proceed. A one-time code is NOT a first-resort ask: twofa_method_mismatch and twofa_verify_method mean the page is on a method other than the persona's, which the page itself switches or resends, as the message says. With kind='twofa' the user pastes the code in the Writ app and Writ enters it server-side, so it never reaches you; kind='question' returns the user's text, so a code sent that way would reach the conversation. The session pauses (the user is notified in the app and by email and controls the live page); this call holds up to 60s and returns status 'answered' (with solved / answer, or `entered` for twofa), 'waiting_for_user' (the same call with the same session_id keeps waiting while the user holds the page), or 'expired'.
writ_browser_cancel
Closes an open browser session. For: a finished task the user does not want to reuse, or an abandoned session. An open cloud browser keeps consuming execution time until it is closed. Unsaved work is kept: a session with defined functions, or a recording that did more than visit pages, is auto-saved as a workflow first (the reply names it; an inactive draft when it would not replay). discard=true closes without keeping anything. A session bound to a build is settled by that auto-save, or marked cancelled.
writ_browser_compose
Authors the workflow being built in an open browser session: named functions, inputs and explicit steps that make what the session drove a real, complex, callable workflow rather than a replay of clicks. WHEN IT IS NEEDED: a plain recording needs none of it (there a caller input is a {{name}} value + `inputs` on writ_browser_act, and the data is an `extract` action). It exposes named functions (an API), gives an input a description/default, and adds steps the recorder cannot see. OPERATIONS: define_function {name, fn_type api|list|script|extraction, ...} · compile_function {name, from_index}: deterministic (no-LLM) capture->function that traces session tokens to an is_auth bootstrap, generates per-call ids ({{uuid()}}), and marks a write so the build never sends it (a real run does) · test_function {name, sample_inputs} · remove_function {name} · set_inputs {inputs:{name:{default?,description?,required?,example?}}} · add_steps {steps:[{type,...}], at?} · remove_step {id} · list (the draft: steps, data_steps, functions, inputs, build). Fastest paths: (a) a list / table / search-results page: writ_browser_context section=lists returns a live-tested `define_function` payload, and with then_save:{name} one call here defines, tests and saves it. (b) a site endpoint: once capture_network and writ_browser_network locate the call, define_function {name:'quotes.list', from_index:<index>, request:{url:'https://site/api/quotes?page={{page}}'}, input_variables:[{name:'page',example:'1'}], response_extractions:{quotes:{from:'json',path:'quotes'}, has_next:{from:'json',path:'has_next'}}, then_save:{name:'...'}}. Every function is live-tested as it is defined (an in-session request, or a DOM read, with sample_inputs={name: value}); a failed test returns feedback and keeps nothing, and a corrected definition reuses the same name. test=false skips the proof (a real run proves it later). Saved functions run through writ_run_workflow function_name. DETAILS: - define_function: a named callable the saved workflow exposes. fn_type api (backed by one of the site's endpoints: from_index=<a captured call's index from writ_browser_network> seeds method/url/headers/body from the capture; overridden request fields take {{name}} placeholders, secrets {{secret:name}}, anti-CSRF echoes {{cookie:NAME}}), script (a read-only JS IIFE returning the data from the page), list (the generated-JS form for any list/table: row_selector + fields {name: sub-selector | {selector, attr}}; writ_browser_context section=lists returns this payload ready-made), or extraction (one selector's text). A list/script/extraction function reads the page it was defined on: page_url as a template (https://site/search?q={{query}}), or an `example` on each input_variable from which the URL is templated. then_save:true (or {name, description}) saves the workflow the moment the function passes its live test. Names of the form <surface>.<verb> (orders.list, orders.create) group functions by surface. input_variables=[{name,description,required,example}], output_fields and response_extractions declare the fields callers pass and get back. Supported specs: JSON {from:'json',path:'data.items'}, embedded JSON {from:'embedded_json',kind:'array',has:['id']}, server HTML {from:'html_css',selector:'.row',attribute:'data-id',all:true}; with `fields` it returns row objects, which is how a server-rendered list becomes a BROWSERLESS function: {from:'html_css',selector:'tr.athing',all:true,base_url:'<page>',fields:{title:{selector:'.titleline > a'},url:{selector:'.titleline > a',attribute:'href'}}} on an `api` function that GETs the page (no browser at replay, so cheaper than a `list`/`script` function whenever the rows are in the served HTML); a field with several values per row (tags, authors) is {selector:'a.tag',all:true} (a list; without all, the first only); a has_next flag is {from:'html_css',selector:'<next link>',exists:true} (true/false, never the link text) and the next page is its href {selector:'<next link>',attribute:'href',required:false}; regex {from:'regex',pattern:'...',group:1}, header {from:'header',name:'x-next'}, body {from:'body'}, or legacy '$.json.path'. The default shape is an Auphan-style named graph: ordered is_auth functions publish dynamic token/id/origin values consumed as {{extracted:name}}, while each data function remains independently callable. flow={version:1,steps:[...]} is for request loops, recursive mapping, cross-page dedupe, cursor pagination or a composite return; it is schema-validated and live-tested at once in the current browser session with its cookies, persona and egress, using the same interpreter as the saved HTTP lane, and the later saved run remains the final engine=http parity proof. is_auth=true marks the sign-in function: it runs first on every replay and its response_extractions publish values the others consume as {{extracted:<name>}}. - test_function {name, sample_inputs}: proves a defined function again. - remove_function {name}. - add_steps {steps:[...], at?}: explicit replayable steps the DOM recorder cannot see (navigate, click, fill, select, press, wait, wait_for, extract, evaluate, api_call, login_post, return, upload, wait_for_download), inserted at a position (default: append). - remove_step {id}. - set_inputs {inputs:{name:{default?,description?,required?,example?}}}: the parameters a caller passes at run time. A save is refused unless every {{name}} in a step or function is a declared input, a credential, a {{cookie:}}/{{extracted:}} runtime reference, or produced by an earlier step. Credentials are never inputs: they come from the persona or a data_key fill. - list: the draft so far. On writ_browser_save, api functions become api_call steps (auth first), the workflow becomes api_recorded when every step is a call, and each function is callable by name (writ_run_workflow function_name) and documented at GET /api/v1/workflows/{id}/api-docs.
writ_browser_context
Reads context for an open browser session. section=page (default) re-reads the live page: url, form fields, buttons, links and the cleaned DOM. section=map reads the build this session is bound to: the pages, captured calls and candidate functions Writ's earlier rungs found (evidence, not yet verified), plus what has been composed so far. section=lists scans the live page at no AI cost: the repeating rows, their field selectors, which captured request carries them, and a live-tested `define_function` payload that writ_browser_compose accepts as is, so a guided session opened on a results URL becomes one list/search API with that payload and a save, without probing the page by hand. section=explorer pages through Writ's full recording policy; section=concierge_api pages through the API-builder policy. The policy sections are reference for unusual flows (logins, multi-request chains), not a prerequisite.
writ_browser_network
Searches or reads the requests the live page has made: the site's real backend API, as opposed to its HTML. operation=search lists matching calls (filtered by `query` / `method`); operation=detail returns one call in full by `index` (method, url, request headers, request body, response body). Indices are stable for the session, so one from an earlier search still resolves later; only the oldest calls age out of the retained window, and asking for one of those says so rather than returning a different call. Calls come from the capture_network action of writ_browser_act, which reloads the page with capture armed; a POST (login/search/submit) is caught by performing the action that triggers it, then capturing. Any call's index feeds writ_browser_compose define_function as from_index=<index>, turning it into a callable function. Held credential values are replaced with their placeholder in the output.
writ_browser_save
Saves the open browser session as a clean, replayable workflow and closes the browser. Everything composed with writ_browser_compose is materialized: named api functions become api_call steps with the auth function first, declared inputs become the workflow's parameters, explicit steps land at their position. The saved workflow is active immediately: it runs on demand through writ_run_workflow (function_name calls one function) or its own run_<name> tool (writ_pin_workflow_tool) at zero AI cost, can be scheduled with writ_set_schedule, exposed as a REST endpoint with writ_expose_workflow_api, and documented at GET /api/v1/workflows/{id}/api-docs. A session bound to a build (writ_website_to_api guided) settles that build. The save is refused, with the reasons, when a step or function references something no run could resolve; the browser then stays open. A workflow is only as sound as the task's run on the live page.
writ_browser_sessions
Lists the cloud browser sessions this account has open. An open session is warm, parked on its current page, and keeps billing while it stays open; its session_id resumes it in writ_browser_act / writ_browser_context without a second browser beside it (a new writ_browser_use opens one). Returns each session's id, status, resumable flag, current url and goal.
writ_browser_use
A REAL CLOUD BROWSER FOR A TASK ON A WEBSITE: the user's own signed-in account (email, social, shop, bank or work portal, through a persona_id from writ_personas: the password stays sealed in Writ, and sessions never ask for a password), a click, form, submit, search inside an app, setting change, booking or post, or a page a plain fetch cannot open (login wall, 403, CAPTCHA). OPENS a real cloud browser and returns the first live page observation. It is not an autonomous agent: Writ runs no model here, the caller is the brain and the driver, and each writ_browser_act(session_id) batch performs exactly the actions it is given. One call opens one browser for one task and performs no step itself. RECORDING IS ALWAYS ON, saving is on demand: every interaction in the session is recorded. A task recorded for reuse carries each value that changes as {{name}} with its real value in writ_browser_act `inputs`, and its data leaves through an `extract` action; writ_browser_save(name) then answers with the steps, inputs and a run_example, and the workflow replays at zero AI cost (writ_run_workflow). writ_browser_cancel closes an unsaved session; an open browser bills until it is closed. In the session: navigate, click, fill, type, select, press keys, scroll, switch tabs, upload a file, sign in (a persona's 2FA code is minted server-side); see the page (read_text, get_dom for the real HTML, inspect a selector, list_candidates for repeating rows, get_screenshot); read every backend call the page makes (capture_network, then search/read them with writ_browser_network); run any JavaScript on the live page (evaluate_js) and call the site's backend from inside the session with its cookies (api_call); work a page whose content only appears after interaction. NOT FOR: reading a page, a few pages, or the top N items of a listing (writ_scrape: one call, 2-10s, no browser); collecting a site into a dataset (writ_crawl_site); turning a site into an API (writ_website_to_api, which opens the same browser bound to a build). A browser costs execution time for as long as it is open. The page comes back after every batch and on demand via writ_browser_context(section=page). A sensitive fill carries data_key: the value is held server-side and the saved step keeps a placeholder, never the raw value. writ_browser_compose adds what the recorder cannot see (named functions, explicit steps, an input's description/default). A saved workflow replays the same task without a browser (writ_list_workflows -> writ_run_workflow); the first observation lists this site's matching ones in `saved_for_this_site`. A browser's exit IP is fixed once it opens: past a bot wall or CAPTCHA, a new session with use_residential=true exits residential (writ_browser_cancel closes the blocked one). writ_browser_sessions lists open sessions; an open one is warm and cheaper to continue than a new one.
writ_crawl_files
The original documents a crawl captured (PDFs, office docs, images, CSVs) as stored files: filename, size, version, source_url, and a short-TTL download_url fetchable with no further auth. The crawl's dataset holds the extracted text; this returns the files themselves. `crawl_id` selects one run; `crawl` (saved crawl slug/name/id) its most recent completed run(s).
writ_crawl_site
COLLECT A SITE (or a section of it) into a dataset: a distributed Dragnet crawl that discovers pages and stores each one as a queryable, change-tracked row. For: 'crawl <site>', 'every page of the docs', 'all products in this category', 'a dataset of <site>', or anything queried, exported, monitored or re-run later. NOT FOR: reading one or a few pages now, which is writ_scrape (url / urls / top_n answer in one call, no dataset); acting on a page, which is writ_browser_use. Modes: all three fetch pages the same way and differ in who reads each page. - CLASSIC (default: extract_mode='markdown', executor='regular'): every page becomes clean markdown, no AI spent, fastest. Fits content, docs, articles, discussions (threads keep [top-level]/[reply · depth N] tags), and any ask the two modes below do not cover. - SCHEMA (extract_mode='schema' + extract_schema): every page holds the same structured record (a product, a listing row), returned as rows, not prose. Deterministic CSS extraction, no AI. - AI-ASSISTED (executor='ai' + extract_prompt): fields that need understanding and vary per page (sentiment, pros/cons, a classification, free-form values with no stable selector) across many pages. Each page waits on a model call (~10s) and bills 5x the page rate; on a few pages, or on fields the other modes capture, it adds cost and no accuracy. Scope: an unscoped crawl of a real site collects hundreds of nav, tag and pagination pages and bills for each. Shapes: - A section ('the docs', 'the pricing and blog pages'): `intent` in plain language (the server derives include/exclude paths and depth from the site's real URLs) plus `relevance_threshold` ≈0.3, which drops off-goal pages. - Known pages as a dataset: `seed_urls` (no discovery). The immediate answer is writ_scrape(urls). - Top N of a listing as a dataset (re-run later, monitored): `rank_cap`=N. The immediate answer is writ_scrape(url, top_n). - Whole site ('every page'): the defaults; `page_budget` caps the spend. Delivery: a bounded crawl (rank_cap / seed_urls) waits and returns its pages in `data.rows` in this call; an open site crawl returns a crawl id for writ_crawl_status, and its results land as a workflow dataset (writ_workflow_data, writ_search_data, writ_export_data). Comment and discussion threads are kept by content_spec {"preset": "full", "include_comments": true} (rank_cap crawls set it already). Behind a login: persona_id, whose saved session the crawl uses. `save_as` keeps the crawl for re-running; writ_saved_crawls lists those, each answering only the asks its `scope` covers. Response shape: by default every answer is Writ's envelope (definition + crawl status + a `data` table whose rows wrap `fields` in run bookkeeping, and whose records carry page metadata like `content_kind`/`depth`). `output` gives an API built on a crawl its consumer's shape: {shape:'record'} for one entity (a usage meter, a dashboard), {shape:'records'} for a list, `fields` to pick/rename ('percent_used as pct', dotted paths), `exclude` to drop, `key` to wrap. Page metadata is stripped unless include_meta=true. Saved with save_as, it becomes the API's default shape (overridable per call on writ_run_saved_crawl / writ_saved_crawl_data). The metadata is added after the extract_prompt model answers, so `output` removes it and the prompt cannot.
writ_crawl_status
Status of a crawl by id: page counts, status, the dataset workflow id. With wait=true one call holds up to 75 s and returns when the crawl converges, with the collected rows inline (`data`, shaped by `output`); it resolves a crawl tool's 504 / crawl-id handle. A crawl still running at the ceiling answers with its current status, and the same call can be repeated.
writ_create_automation
Creates an automation: on an event, it runs a workflow, sends a notification, and/or wakes an AI agent. It chains workflows (A completes → B runs), alerts on completion, or has an agent act on the event (`ai_prompt`). workflow_* events take a source workflow in `on_workflow`; at least one of `run_workflow` / `notify` / `ai_prompt` is required. `notify` is a template over the event, so it carries the data, not just that it ran: after a workflow, {{result.extracted_data.0.title}} / {{result.extracted_data.<var>.0.url}} (the run's own rows); after a crawl, {{rows.0.<field>}} … {{row_count}} (the records it collected, schema fields included) plus {{seed_host}} {{pages_done}}; after a monitor change, {{extracted.price}}. A missing path renders empty, so a digest of N rows is N numbered lines. Digest pattern: writ_set_schedule on the workflow, then this with when=workflow_completed; a crawl has no schedule of its own: when='scheduled' + a `crawl` block, then this with when=crawl_completed. On a clock: when='scheduled' + `schedule` fires the actions at that time (a notify after run_workflow waits for that run, so {{result.extracted_data...}} is filled). `run_functions` + `inputs` call chosen functions of a multi-function workflow (what they need runs too). Anything else (conditions, scrape, extract, branches) is a raw `blocks` tree. On a webhook: when='webhook_received' mints a signed inbound URL; the answer's `webhook` holds the url, its signing_secret (shown only then), the two headers every call signs, curl and Python examples and an example_body. Each top-level JSON field of a call becomes the run input of the same name; `inputs`, `notify` and `ai_prompt` templates read any field as {{payload.<path>}}. A call is acknowledged at once; with ?wait=true it is held until the workflow runs the automation starts finish and answers their data. `webhook_trigger_id` reuses an existing URL (writ_list_webhooks).
writ_create_http_extraction
Creates or revises an advanced browserless HTTP extraction from plain language and real browser-network evidence (a browser experiment or capture_network). For: what one simple request or a named auth/function graph cannot express (request loops, GraphQL descriptor discovery, recursive JSON, cross-page dedupe, sorting, cursor pagination); ordinary login/bootstrap/data chains are writ_browser_compose define_function with is_auth/order/typed response_extractions. Generates a universal api_call.config.flow; site behavior stays inside the workflow. apply=false returns the draft and its validation; apply=true writes a valid draft. It is ready to expose once writ_diagnose_http_workflow shows a run with engine=http and records. Requests are flow steps (no fetch in evaluate_js); Writ-only controls never reach the site.
writ_create_monitor
Creates a monitor that watches a URL for changes/updates: Writ checks it on a schedule and fires a change_detected event when the page, a CSS selector's text, or a visual zone of the page changes. Returns the monitor id that writ_wire_monitor takes. Selector proof: with the `session_id` of a writ_browser_use session open on the page, the selector is checked on the live page before saving. One that matches several elements (Amazon '.a-price' matches a dozen; the check would join them into one blob) is pinned to the one shown; one that matches nothing or only an image becomes a visual zone watch. With no selector at all, mode='visual' + zone_text=<the value exactly as the page prints it, e.g. '51,77 EUR'> watches that area's pixels (digits are compared, so 51.77 finds '51,77 EUR'). A zone fires on any visual change, so it suits a change alert, not a threshold. `selector_check` in the answer says what was done. Interval and access: no `interval` → needs_input (this plan's options + a tell_user written for the user; nothing created); one the plan refuses is refused with the allowed ones. The page is read first: behind a sign-in → needs_persona; a bot check → an offer of use_residential.
writ_devices
The user's linked Writ desktops (there can be several): which are connected right now, and how many local workflows and personas each offers. action='list' (default) shows them; action='use' device=<agent_id or name> scopes this connection to one: runs (writ_run_workflow), browser sessions (writ_browser_use), desktop workflows (writ_list_workflows, runs_on='desktop') and desktop personas (writ_personas, source='device') then target it, and its run history (writ_workflow_runs), data (writ_workflow_data, writ_search_data) and monitors (writ_create_monitor, writ_wire_monitor; action='monitors' lists them) are read from and created on it. action='clear' removes the scope. A desktop's personas sign in from its own vault: the credentials never leave it. For: work 'on my laptop' or 'on my work computer', or in the user's own browser with their logins.
writ_diagnose_http_workflow
Diagnoses HTTP-lane readiness for a saved workflow. Reports browser-only dependencies, invalid flow actions/expressions, unsafe internal query parameters, eligibility versus actual proof, and the next repair action. task_id (a representative run) confirms whether it really used engine=http, did not fall back, and returned records (an answer such as {ok:true,count:0,listings:[]} is not proof). With task_id it also returns the run's steps and, for a flow, what every request it made was answered with (status, sizes, a response sample): the first evidence when a run returned nothing or failed. A 200 with an empty feed means the site stopped serving this session (signed out, blocked, or a null written over a working request variable), not 'no matches'.
writ_discovery_status
Status of a build started by writ_website_to_api. Terminal states: succeeded, failed, cancelled. Resting state: needs_guidance — Writ's mechanical rungs are done and the build is the caller's to finish; it carries `map` (endpoints seen, specs, candidate functions), and writ_website_to_api mode=guided build_id=<this id> continues it. A rung the ladder replaced reports superseded=true with fallback_build_id; with wait=true this tool follows that pointer and answers with the rung now running (`followed_from` lists the ids it walked; the returned build_id is the current one from then on). `escalations` lists every earlier rung with the real reason it handed over (robots.txt refused the crawl, no pages fetched, nothing matched the goal, no structured list...), i.e. why a browser was needed. On success it returns the workflow_id, the surfaces mapped, and `verified` — false for a fast/browser map, whose functions are candidates until a real run proves them. Any function runs with writ_run_workflow (function_name); the generated API docs (OpenAPI 3, Markdown or a Postman collection, all pointing at the real Writ endpoint) are at GET /api/v1/workflows/{workflow_id}/api-docs.
writ_export_data
Exports a workflow's full extracted-data table as CSV or JSON (search/filter applied, unpaginated).
writ_expose_workflow_api
Publishes a saved workflow through Writ's managed REST gateway (the same resource as the app's REST endpoint switch). The returned POST URL waits for the workflow and returns its JSON result. Repeated calls reuse the existing endpoint.
writ_list_webhooks
The account's inbound webhook URLs: for each, its webhook_trigger_id, the callable url, whether it is signed, the automations it fires and how often it was called. Signing secrets are stored encrypted and never shown. An automation reuses one with writ_create_automation webhook_trigger_id=<id>; when='webhook_received' without it mints a new URL.
writ_list_workflows
Lists the workflows saved in the Writ account; each runs on demand without live browsing. Returns id, name, declared inputs, schedule, and whether it is pinned as its own run_<name> tool.
writ_payment
Pays on a website with one of the user's cards, without the card number ever reaching this conversation: the tool never sees or asks for card numbers. action='request' (site, max_amount, purpose) returns a `tell_user` and an `open_url`: a Writ window where the user picks or adds a card (or makes a virtual card) and approves it. A purchase needs that approval. action='wait' grant_id=<id> is one held call (up to 60s) that answers when they approve, with the card's brand and last four digits only. Ordering: action='checkout' (session on the final checkout page, commit_selector = the place-order button, total_selector = the order total, grant_id, or max_amount when the store uses its own saved card) pauses the session and the user confirms (emailed link, the Writ app, or an auto-confirm rule they turned on); then Writ types the card, checks the total and clicks the order button itself: a real purchase. An order-button click sent by the assistant is refused. action='wait' confirmation_id=<id> returns the outcome. action='fill' types an approved virtual card early (multi-page checkouts). Card use is limited to the site and amount granted; an unused grant expires after 15 minutes. action='list' shows the user's payment methods as handles (kind, brand, last4, id), never numbers; a handle goes into writ_wire_monitor buy.payment {kind, ref}.
writ_personas
The user's own accounts on websites. When a task needs the user signed in (email, social, shop, bank or work portal), a persona is how Writ signs in as them. No password passes through this tool or the conversation, and its answers never ask for one: credentials are typed only into a Writ window. A persona is a saved sign-in identity: a site's username plus credentials sealed server-side (never readable here), optional 2FA whose codes are minted server-side, and a warm signed-in session. Its persona_id is accepted by writ_browser_use, writ_crawl_site, writ_scrape and writ_run_workflow. action='list' (filter by domain) shows the personas usable on a site; action='get' inspects one (include_runs adds its recent runs); action='sign_in' runs its login workflow now (force=true re-logs-in even when the session looks usable); action='record_login' has a server-side AI sign in as it once and record the flow as its login workflow, so it can sign itself back in. This tool cannot create a persona. list with a domain and no match answers `persona_needed`: a `tell_user` (a message written for the user), a `create_url` (a minted link to a small Writ window with only the persona form, pre-filled for that site) and its `link_id`; action='request' domain=<site> mints one on purpose (another account for a site). action='wait' link_id=<link_id> is one held call that answers the moment the user saves the persona, with its persona_id. Also listed: the personas of the user's linked Writ desktop (source='device', id `device:<agent>:<id>`, name and site only). That id as persona_id on writ_run_workflow, writ_browser_use / writ_record_website, writ_scrape or writ_crawl_site sends the work to that desktop, which signs in from its own vault and only on the persona's own site: the credentials never leave it. Scoped to a desktop (writ_devices / `device`), list and request ask ON it: its Writ app pops the form, and `wait` on the `device:…` link_id returns the `device:…` persona_id.
writ_pin_workflow_tool
Pins (or unpins) a saved workflow as its own run_<name> tool on this server. Workflows are not exposed as individual tools by default; every one stays callable through writ_run_workflow, and the pinned list is capped. For: the few workflows the user runs often or wants to call as a tool from here.
writ_record_website
Records a repeatable website task as a workflow that replays on demand. For recording, capturing, teaching, automating or repeating actions on a site when the point is the task, not an API surface. Writ opens a real cloud browser, returns an observation and runs no model there; the caller is the brain: writ_browser_act drives the browser, writ_browser_compose authors what the recorder cannot see (inputs a caller passes, explicit steps, named functions), and writ_browser_save saves the finished task. The saved workflow replays at zero AI cost (writ_run_workflow, or its own run_<name> tool once pinned with writ_pin_workflow_tool) and can be scheduled. A goal that asks for an API ("turn <site> into an API", "an endpoint for") is routed automatically to writ_website_to_api's intelligent ladder (the fast path, then Writ's AI browser rung), so the recording is a real build. That build first proposes the user's own matching workflows (existing_workflows) and ready-made marketplace APIs (marketplace_candidates); skip_existing / skip_marketplace bypass those.
writ_run_saved_crawl
Runs a saved crawl with its stored settings. With `max_age`, the data it already collected comes back when that run is recent enough (the cheap path); otherwise it re-crawls. The response's `_cache.hit` and `_cache.age_seconds` say which happened.
writ_run_workflow
Runs a saved workflow by id or name and, by default, waits for it to finish, returning the extracted data. Workflow inputs go as top-level fields or under `inputs`; file inputs go under `files`.
writ_saved_crawl_data
Reads the data a saved crawl already collected on its most recent completed run, at any age. It never starts a crawl.
writ_saved_crawls
Lists the crawls the user saved for re-running (callable by API, each with a `scope`: seed_url, rank_cap, include_paths, extract_mode, executor). On a saved crawl whose scope matches an ask, writ_run_saved_crawl(max_age=…) returns recent data instantly at no cost. A saved crawl of a different page, or one using executor=ai, does not answer a fresh question; writ_crawl_site(rank_cap=N) does.
writ_scrape
READ PAGE CONTENT NOW: one page, a list of pages, or the top N items of a listing, returned as clean markdown in this call (2-10s). For: 'what does <page> say', 'summarize <url>', 'the top N posts/products/results of <listing> and what's on each', 'fetch these 3 links', including a page a plain fetch cannot read: blocked or empty (403, bot wall), rendered by JavaScript, or behind the user's sign-in (render_mode, use_residential, persona_id). NOT FOR: collecting a whole site or section into a dataset (writ_crawl_site); clicking, typing, signing in or any action on a page (writ_browser_use). Three shapes, one call each: - url → that page. - urls=[...] (≤20) → all of them, fetched in parallel, `pages` in the order given. - url=<listing page> + top_n=N (≤20) → the listing (`listing`) and the N top-ranked item pages it links to (`pages`, in rank order); e.g. url='https://news.ycombinator.com/', top_n=3 returns the front page and the 3 top stories' discussion pages. include_paths=['item\\?id='] gives the item-link shape; without it the server detects it. Discussion pages keep their comment threads; each comment is tagged [top-level] or [reply · depth N], so 'the top-level comments' is answerable from the text. Long pages are preview-cut at 12000 chars (`_truncated`); the `hint` says how to fetch a full page. The caller reads the markdown; no AI is spent here. Behind a login: persona_id. Bot wall: use_residential=true.
writ_search_data
Searches everything the account's workflows already collected for a term: answers data questions from past runs without running anything. Scopes to one workflow when given, else fans out. Matches come back preview-sized; writ_workflow_data(refs=...) returns full records.
writ_set_schedule
Schedules a saved workflow to run automatically: `every_minutes` for an interval, or `kind`='daily'/'weekly' with `time` (HH:MM) and `days`. On a multi-function workflow (an API from writ_website_to_api) `functions` schedules one or several of its functions instead of the whole workflow, with saved `inputs` for them: each run executes those functions plus whatever they need (sign-in, a token, the search whose ids another reads) exactly once. The answer names `also_runs` and any `missing_inputs`.
writ_update_saved_crawl
Inspect or customize a saved crawl in place. With no changes, returns its complete definition. `settings` recursively merges any crawl option into the stored config (scope, paths, budgets, extraction, rendering, persona, residential egress/country, speed, output shape and other flags) without dropping unrelated settings.
writ_update_workflow
Inspects and edits a saved workflow. No edits returns a compact outline with stable step ids and source hashes; verbose=true reads the full definition. section=contract lists every HTTP operator with its operands and what it does (operator selects one); section=flow + function_name lists nested node paths. function_name reads one function's source, with optional step_id and path (JSON Pointer, e.g. /config/flow or /config/script); offset/max_chars window the source. Targeted edit: function_updates=[{function_name, step_id?, expected_hash?, patch?, json_edits?, script_edits?}]. An api_call step binds config.function_name. patch merges type/config/enabled; json_edits use test/add/replace/remove with path/value to change one nested HTTP action, JSON field or array item without rewriting the program. script_edits use path/old/new and optional expected_hash; old must match exactly once. Function identity is preserved. validate_only=true previews edits and validates HTTP grammar without running JavaScript or making requests. expected_updated_at from a read prevents concurrent overwrite (409: stale read). function_updates is the targeted repair; step_updates merge indexed steps; replace_steps replaces all steps. patch edits settings and metadata; credentials stay in persona/vault. Saving runs nothing; a run on representative inputs, diagnosed by its task_id, proves the edit. regenerate_skill=true rebuilds the agent skill; patch.skill_md edits it.
writ_website_to_api
Turns a website into a callable API: the one tool for this, every lane. For a service with no official or practical API whose data or actions are wanted programmatically: "turn <site> into an API", "map the API of <site>", "expose every feature", "give me an endpoint for <site>". A build is 3 CALLS: (1) this tool with url + goal (+ save_as); (2) writ_discovery_status(build_id, wait=true), one held call that follows every rung; (3) on `succeeded`, the answer's `run_example` (writ_run_workflow: workflow_id + function_name + inputs) runs it, and a working function answers two different inputs differently. START ON THE PAGE THAT ALREADY SHOWS THE ROWS: the url is the search-results / category / listing URL (e.g. https://www.google.com/maps/search/bakeries+Montreal/), not the app's home page; a build seeded at an empty shell spent 6 minutes over three rungs and produced no function. A goal naming the inputs and the fields ("page number in; quotes with text/author/tags and has_next out") shapes the functions. The build has its own browser, and an identical request during it joins it. An answer of existing_workflows / marketplace_candidates is a proposal, not a build: the match runs as is, and skip_existing / skip_marketplace start a fresh build. Status needs_guidance means the build is the caller's: mode=guided build_id=<id> continues it in a browser the caller drives as its brain. writ_diagnose_http_workflow(workflow_id, task_id) explains a run that returns nothing. NOT FOR: reading a page's content (writ_scrape), collecting a site as a dataset (writ_crawl_site), or a task that is not an API surface (writ_record_website). Login: an app behind a sign-in builds as a saved identity (persona_id from writ_personas, which also carries 2FA); credentials never pass through this tool. Without one, `persona_needed` carries `tell_user`, written to ask the user for that sign-in. Default mode intelligent: Writ runs the whole ladder itself, and the caller only starts it and waits. The ladder: the user's own matching workflows (existing_workflows; skip_existing=true bypasses), ready-made marketplace APIs (marketplace_candidates; skip_marketplace=true), then the fast path: one real cloud browser (the persona's session, residential exit, CAPTCHA and bot-wall handling, like every Writ session) where one AI call plans the functions the goal needs (GET reads, POST writes, in-page extractions) and the steps that reach each, the browser runs them, and per function one more AI call picks what backs it — the site's own captured request (compiled: tokens traced, inputs templated), the page's list, or the write's captured request (probe_write: never sent) — each live-tested, reads proven on a second input. Only when it cannot prove them does Writ's AI browser rung take over (turn by turn: ranks traffic, promotes HTTP requests, tests pagination); the newer rung's status carries `escalations`, the reason the fast path handed over. A saved fast-path API with gaps names them in `missing_functions`. mode=crawl / mode=browser run the whole-site crawl rungs instead (static / rendered; robots.txt respected unless respect_robots=false): broad maps of server-rendered sites, unverified (`verified:false`) until a run proves them. mode=auto is the same ladder with the caller as the last rung: when the fast path cannot prove the functions, the build parks as status=needs_guidance with `map` (pages, captured calls, each planned function and why it fell short) and what it already defined, instead of spending Writ's agent. mode=fast, mode=crawl and mode=browser start on their own rung and park the same way when they fall short. mode=guided (with build_id=<that id> to continue, or alone to start) opens a real browser bound to the build, driven turn by turn with writ_browser_act (navigate, sign in, capture_network, evaluate_js; calls read with writ_browser_network), where writ_browser_compose define_function defines the API: api functions from captured calls via from_index, or proven scripts/extractions, each live-tested as it is defined; set_inputs for parameters; is_auth for a sign-in function whose response_extractions feed the others. writ_browser_save settles the build: the workflow is callable at once (writ_run_workflow with function_name), pinnable, schedulable, exposable as REST (writ_expose_workflow_api), and its API docs are at GET /api/v1/workflows/{workflow_id}/api-docs. HTTP-first: the guided browser is the experiment bench, and what ships are direct API functions defined from a captured representative search/filter request and next-page request. The default shape is the Auphan-style named function graph: ordered is_auth functions publish tokens/ids/origins through response_extractions, and data functions consume {{extracted:name}}; config.flow covers loops, recursive mapping, cross-page dedupe and composite returns. Typed extraction sources: json, embedded_json, html_css, regex, header, body. search/filter/limit/page/offset/cursor are declared inputs, and next_cursor/next_offset/has_more are returned. writ_diagnose_http_workflow(task_id=...) checks a saved run made with the intended persona; an API is ready to expose once engine=http returns non-empty data and pagination matches the browser baseline, or once a measured browser-only dependency is named.
writ_wire_monitor
Wires a writ_create_monitor monitor's change_detected event to an action. action='workflow' runs a saved workflow when the monitored page changes; action='notify' sends a notification to `channels` + `recipients` the account has configured; action='ai_task' wakes an AI agent with a task `prompt`: the agent opens the monitored page in a cloud browser, sees what changed (diff + extracted values) and works the prompt autonomously (`channels`/`recipients` also notify when it finishes). A price watch takes `threshold`: the action then runs once, when a check reads a price at or below it (`threshold_op` picks the side), not on every change. Buying at the price, in order: 1) writ_create_monitor reads the price; 2) writ_record_website or writ_browser_use, then writ_browser_save, record the checkout up to the order page, with the product, quantity and shipping as inputs where they vary, an `extract` of the total, and the order button never pressed (its selector kept); 3) writ_run_workflow with mutation_mode='dry_run' rehearses it once, to end on the order page and return the total (safe to rehearse only a checkout recorded this way: an older one may hold the order click); 4) action='workflow' + that workflow + `buy` (payment {kind, ref} from writ_payment, total_selector, commit_selector = that button (Writ clicks it live), fallback_ai_session: true); 5) the fallback, ONLY when the checkout can't be recorded or its rehearsal fails (bot wall, a checkout that won't replay, a login the persona can't hold): action='ai_task' with a `prompt` naming what to buy and the same `buy`; an AI PURCHASE session browses to the order page and hands over to Writ's confirmed checkout. Either way it is saved as a REHEARSAL (dry run) until the user turns purchases on in the Writ app, and a person confirms each purchase unless an auto-confirm rule they set covers automation purchases. The answer's `buy.runs_as` names the path: recorded_checkout or ai_purchase_session.
writ_workflow_data
Reads the accumulated extracted data of a saved workflow as a table (columns + rows). `q` filters; `run_id` selects one run. Long text cells arrive preview-cut (`_truncated` lists the fields); `refs` returns full records. Crawl ids are a different namespace: a writ_crawl_site run's data lives behind writ_crawl_status / writ_saved_crawl_data, not here.
writ_workflow_runs
Run history (status, timing, errors, and the `venue` each run executed on: cloud or the user's desktop) for one workflow or across all of them.

Endpoints

URLTransportStateLatencyChecked
https://api.usewrit.app/mcp streamable-http answering 529 ms 9 min ago

Alternatives to Writ Cloud

same job, measured the same way
Fastcrawl
by fastcrawl

Cloud scraping & crawling API for AI agents. Turn any URL into clean, LLM-ready markdown.

answering
WebLens
by weblens

Scrape, crawl, map and extract the web. Pay per call in USDC, no account or API key.

36 tools answering
Apify
by mcparmory

Build, deploy, and run web scraping and automation actors in the cloud

245 installs/wk local only
CrawlCheck
by emmanuelorta

Verification layer for the agentic web: a signed answer about any site before an agent acts.

23 tools answering
Ultimate Web Scraper
by ultimatewebscraper

Scrape and crawl websites into queryable tables: products, leads, Shopify catalogs, real estate.

answering
Fitter
by pxyup

Turn any website or API into structured JSON with LLM-authored declarative scraping configs.

local only
WebReaper
by alex-on-ai

AI-native web scraper: scrape, crawl and map any site to clean markdown over stdio. MIT-licensed.

local only
rasterly
by rasterly-dev

Web toolkit for AI agents: read any URL as Markdown, screenshot, extract data, or crawl a site.

34 installs/wk local only

Writ Cloud — questions

Answers built from our own checks of this server.

What can Writ Cloud do?
It exposes 39 tools, read directly from the server on our last check. Among them: writ_browser_act, writ_browser_ask_user, writ_browser_cancel, writ_browser_compose, writ_browser_context, writ_browser_network and 33 more. The full list with descriptions is on this page — we take it from the server itself via tools/list, not from a README. How MCP servers expose tools in the first place →
Is Writ Cloud working right now?
We send a real MCP handshake every 15 minutes. Over the last 24 hours 79 of 79 checks got a reply (100.0%), average response time 517 ms. The bar chart above shows every period we have measured.
How do I connect Writ Cloud?
Copy the ready config from this page — we generate it for Claude Code, Claude Desktop, Codex, Cursor and VS Code, each with the file path that client actually reads. It is a remote server, so there is nothing to install — the client connects to the address.
Does Writ Cloud need an API key?
No. Writ Cloud completed a full MCP handshake with us as an anonymous client and listed its tools without asking for anything. All 39 of them are readable on this page. This is what we observed, not what the docs claim.
How fast is Writ Cloud?
It answers our handshake in 517 ms on average, which is faster than 24% of all working MCP servers we measure. That is on the slow side — worth knowing if the tool sits inside an interactive loop. The comparison comes from our own checks across the whole registry, every 15 minutes.
How many people use Writ Cloud?
The npm package writ-mcp was installed 11 times in the last week. We show installs rather than GitHub stars on purpose: a star is a bookmark, an install is someone actually running it.
Is Writ Cloud open source?
Yes — it is published under the MIT licence, written in JavaScript and 1 stars on GitHub. The source link is on this page, so you can read exactly what it does with your data before you connect it.