MCP server: One MCP server.

Writ Cloud

@usewritRun by its publisher: served from their own domain, namespace checked by the registry.MCP server · External

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

Browser automationv1.0.0MITJavaScript
Freeno charge per call

One address for any MCP client. No sign-in needed on this server.

The Connect page of a self-hosted Writ coordinator, showing the writ-mcp one-liner and the tools it exposes
From the README

What it does

4 read10 act25 other

Grouped by tool name. At most: moves money, runs code or commands, deletes data, or sends messages for you.

What it costs

Free

Odel charges nothing per call. The publisher may charge for its own service.

Tools

39 · grouped by tool name · checked 11 Oct 2026

Read · 4Look things up

  • writ_list_workflows
  • writ_search_data
  • writ_export_data
  • writ_list_webhooks

Act · 10Change something

  • writ_browser_save
  • writ_browser_cancel
  • writ_run_workflow
  • writ_update_workflow
  • writ_create_http_extraction
  • writ_set_schedule
  • writ_update_saved_crawl
  • writ_run_saved_crawl
  • writ_create_automation
  • writ_create_monitor

Other · 25Neither hinted nor named

  • writ_browser_use
  • writ_record_website
  • writ_website_to_api
  • writ_discovery_status
  • writ_browser_act
  • writ_browser_context
  • writ_browser_network
  • writ_browser_compose
  • writ_browser_ask_user
  • writ_browser_sessions
  • writ_personas
  • writ_payment
  • writ_devices
  • writ_diagnose_http_workflow
  • writ_pin_workflow_tool
  • writ_workflow_data
  • writ_workflow_runs
  • writ_expose_workflow_api
  • writ_crawl_site
  • writ_saved_crawls
  • writ_saved_crawl_data
  • writ_scrape
  • writ_crawl_status
  • writ_crawl_files
  • writ_wire_monitor
Try it in the Inspector
README13 min read
writ-mcp — the official Writ MCP server connector

CI npm version License: MIT Node 18+ Zero dependencies Published with npm provenance PRs welcome

Your saved browser workflows, as tools your assistant can call.

Quick start · Tools · Configuration · Security · Troubleshooting · Contributing


writ-mcp connects a stdio MCP client — Claude Code, Claude Desktop, Cursor, Windsurf, Codex — to Writ, so the browser workflows you already recorded become tools your assistant can call: run them, read the data they collected, search past results, schedule them, expose them as REST endpoints, and kick off site crawls.

Terminal: claude mcp add writ-selfhost, then claude mcp list reporting Connected

It is a transparent stdio↔HTTP proxy and nothing else. Every tool, schema and rule lives server-side, so what you get always matches what your instance can do — and a new tool never requires upgrading this package. Zero dependencies, one file, Node core only.

Clients that speak Streamable HTTP natively don't need this at all — point them straight at the /mcp endpoint with an Authorization: Bearer <key> header.

Quick start

1. Get an API key. In the Writ app: Settings → Developers → API keys. Keys look like wt_….

2. Add the server.

Against a self-hosted coordinator:

bash
claude mcp add writ-selfhost -e WRIT_API_KEY=<YOUR_API_KEY> -- npx -y writ-mcp --url https://writ.example.com

Against a coordinator on this machine:

bash
claude mcp add writ-selfhost -e WRIT_API_KEY=<YOUR_API_KEY> -- npx -y writ-mcp --url http://localhost:8000

Against a published per-workflow endpoint (an "Expose as MCP" slug URL is used verbatim — no path rewriting):

bash
claude mcp add my-tools -e WRIT_API_KEY=<YOUR_API_KEY> -- npx -y writ-mcp --url https://mcp.example.com/mcp/my-tools

3. Check it.

bash
claude mcp list

Writ Cloud is the default target when you pass no --url (https://api.usewrit.app). Pass --url to point the connector at your own coordinator instead. See Status.

Pass the key through the environment, not the command line. --api-key works, but it puts your key in the process's argument list where any local process can read it via ps, and your shell records it in history. The connector prints a note when you use it.

Your running coordinator also hands out these one-liners, pre-filled, on its Connect page and at GET /api/mcp/connect-info:

The Connect page of a self-hosted Writ coordinator, showing the writ-mcp one-liner and the tools it exposes

Claude Desktop / Cursor (config file)

Add to claude_desktop_config.json (Claude Desktop) or ~/.cursor/mcp.json (Cursor). The env form is recommended — it keeps the key out of the argument list:

json
{
  "mcpServers": {
    "writ-selfhost": {
      "command": "npx",
      "args": ["-y", "writ-mcp"],
      "env": {
        "WRIT_COORDINATOR_URL": "https://writ.example.com",
        "WRIT_API_KEY": "<YOUR_API_KEY>"
      }
    }
  }
}

Without npm

A self-host install bundles this connector at connectors/writ-mcp. There is no build step, so you can run it straight from disk:

bash
node /path/to/writ/connectors/writ-mcp/index.js --url https://writ.example.com

It coexists with the other Writ servers. Each surface registers under its own slug on purpose — the desktop app is writ, Writ Cloud is writ-cloud, a self-hosted coordinator is writ-selfhost — and each identifies itself to the assistant with a distinct title. Keep any combination connected at once.

Tools you get

Served by the server you connect to, not by this package, so a new server tool reaches you without a connector update. Writ Cloud lists the 39 tools below; a self-hosted coordinator lists the ones marked in its column.

ToolWhat it doesWrit CloudSelf-hosted
writ_list_workflowsYour saved workflows (a run_<name> tool is added only for workflows you pin)yesyes
writ_run_workflowRun one and wait for the extracted datayesyes
writ_pin_workflow_toolPin or unpin a workflow as its own run_<name> tool — off by default, capped; everything still runs via writ_run_workflowyesyes
writ_update_workflowRead a workflow's outline or edit it in place (settings, steps, functions, its agent skill)yesyes
writ_diagnose_http_workflowShow what every request of a run was answered with, and whether it ran without a browseryesno
writ_create_http_extractionAuthor a multi-request browserless extraction from captured requestsyesno
writ_workflow_dataRead a workflow's accumulated data tableyesyes
writ_search_dataSearch across everything already collectedyesyes
writ_export_dataExport a workflow's data as CSV/JSONyesyes
writ_workflow_runsRun history and statusyesyes
writ_set_scheduleSchedule a workflow (interval / daily / weekly)yesyes
writ_expose_workflow_apiPublish a workflow as a callable REST endpointyesyes
writ_scrapeRead a page, or several, as clean markdown (Cloud also reads a listing's top N)yesyes
writ_crawl_site / writ_crawl_statusStart and poll a distributed site crawlyesyes
writ_crawl_filesThe original documents a crawl captured (PDFs, office files, images)yesno
writ_saved_crawls / writ_run_saved_crawl / writ_saved_crawl_dataSaved crawls (see below)yesyes
writ_update_saved_crawlInspect or change a saved crawl's settingsyesno
writ_personasSee your saved sign-in identities and get them ready (see below)yesyes
writ_paymentPay with a card the user approves; Writ places the order once they confirmyesno
writ_devicesYour linked Writ desktops: list them, or point this connection at oneyesno
writ_create_automationEvent or clock → run a workflow, notify, or wake an agentyesyes
writ_list_webhooksYour inbound webhook URLs and the automations they fireyesyes
writ_create_monitor / writ_wire_monitorWatch a page and react to changesyesyes
writ_browser_useOpen a real browser your assistant drives turn by turnyesyes
writ_record_websiteRecord a repeatable task as a workflowyesyes
writ_website_to_apiTurn a website into a callable APIyesyes
writ_discovery_statusFollow an API build to its resultyesno
writ_browser_act / _context / _network / _save / _cancelAct in the browser, read the page and its network calls, save or close the sessionyesyes
writ_browser_composeDefine named functions, inputs and explicit steps in a sessionyesno
writ_browser_ask_userHand a CAPTCHA, a code or a decision to the user in the live browseryesno
writ_browser_sessionsOpen browser sessions you can resumeyesno
writ_record_start / _act / _context / _network / _save / _cancelOlder names of the browser loop, kept for compatibilitynoyes

The build tools (writ_browser_use, writ_record_website, writ_website_to_api, then writ_browser_act / _context / _network / _save / _cancel) are on both targets; neither has a writ_build tool, so use writ_record_website. Your assistant opens a real browser, drives it turn by turn, and saves the session as a reusable workflow that afterwards replays with no model in the loop. Writ Cloud runs it on Writ's fleet; a self-hosted coordinator runs it on your own fleet agent. Either way your assistant is the brain — no second model key is involved.

Reusing a recent result (max_age)

Running a workflow drives a real browser, so asking the same question twice in one session costs two full runs and two waits. Every workflow tool takes an optional max_age (seconds) meaning a recent answer is good enough:

jsonc
{ "name": "run_price_check", "arguments": { "sku": "B0C123", "max_age": 300 } }
  • omitted or 0 — always run fresh (the default; nothing goes stale on its own).
  • N — reuse a result younger than N seconds, otherwise run.

A reused answer carries _cache: {hit: true, age_seconds: N} so the assistant can tell how current it is.

max_age works identically against Writ Cloud and a self-hosted coordinator, and on a pinned workflow's own run_<name> tool as well as writ_run_workflow.

Calling a saved crawl (writ_run_saved_crawl)

A whole-site crawl is slow and metered, so re-crawling to answer the same question is the most expensive mistake an assistant can make. A saved crawl is a stored crawl configuration with a stable name, and the same max_age contract applies to it:

jsonc
{ "name": "writ_run_saved_crawl", "arguments": { "crawl": "docs", "max_age": 86400 } }
  • hit — the pages that crawl already collected come back inline, instantly, with nothing crawled and nothing metered.
  • miss — the site is crawled again with the saved settings, and you get a crawl id to poll with writ_crawl_status (a crawl outlives a single tool call).

Three tools cover the surface:

ToolWhat it does
writ_saved_crawlsList saved crawls. Check here before crawling a site again.
writ_run_saved_crawlRun one, reusing recent data when max_age allows.
writ_saved_crawl_dataRead what one already collected, at any age. Never crawls.

To create one, pass save_as to writ_crawl_site — that saves the settings and runs them, so the crawl becomes callable by REST as well. Re-using the same save_as updates that saved crawl instead of piling up duplicates. Saving needs an admin-scoped credential (it creates reusable, callable configuration); running one needs only run.

Sites behind a login (writ_personas)

A persona is a saved sign-in identity — username, credentials sealed server-side, optional 2FA, and a warm signed-in session. writ_personas lists yours (with per-persona readiness), inspects one (get), refreshes its session by running its login workflow (sign_in), or has the AI record the sign-in flow once so the persona can sign itself in from then on (record_login). The assistant then passes a persona_id to writ_run_workflow (and each pinned run_<name> tool), writ_crawl_site, or writ_scrape — plus writ_browser_use on Writ Cloud — and the task runs signed in, with 2FA codes minted server-side.

By design the connector can only use personas, never manage them: creating, editing or deleting one involves credentials, and no credential or one-time code ever crosses the MCP connection in either direction. Personas are created in the Writ dashboard.

If a tool call times out

It comes back as status: "running" with retryable: true. The run was not cancelled — calling the tool again starts a second run. Wait, then retry with a max_age wide enough to pick up the first run's result once it lands.

Configuration

Flags take precedence over environment variables.

FlagEnvDefaultPurpose
--urlWRIT_COORDINATOR_URL / WRIT_URLhttps://api.usewrit.appTarget base URL. A URL whose path is already /mcp or /mcp/<slug> is used verbatim.
--api-keyWRIT_API_KEY— (required)Key for Authorization: Bearer. Prefer the env var.
--insecureWRIT_INSECURE_TLS=1offAccept a self-signed local-CA cert. Private networks only.
--timeoutWRIT_MCP_TIMEOUT_MS600000Per-request timeout (ms); covers long writ_run_workflow waits.
--help / --version——Print usage or version and exit.

HTTPS with a local CA (self-host): trust the CA (recommended) and use the https:// address, or set NODE_EXTRA_CA_CERTS=/path/to/ca.pem. Use --insecure only for localhost testing — it disables certificate verification entirely, which exposes your key to a man-in-the-middle.

Security

This process exists to carry a credential, so everything that could expose one is made loud rather than convenient. Full detail in SECURITY.md.

  • The key goes to --url and nowhere else. No telemetry, no analytics, no update check. Zero dependencies means there is no transitive code in the process that could phone home — and CI fails the build if that ever changes.
  • Redirects are never followed. Replaying your Authorization header to whatever origin a Location header names would hand your key to a host you didn't choose. A 3xx becomes an error telling you to point --url at the final URL.
  • Credentials in a URL are redacted from every diagnostic. MCP clients write a server's stderr to a log file on disk; https://user:pass@host/ would otherwise be written down in plaintext.
  • Loud warnings, on stderr, for every way a key leaks: TLS verification disabled, plaintext http:// to a non-loopback host, or a key passed on the command line.
  • Retries never double-run a workflow. Only read-only methods are retried; tools/call is sent exactly once, because a retry could re-execute a side effect the connector cannot see.
  • Responses are bounded at 32 MB, so a broken endpoint can't grow this process until the OS kills your session.
  • No request is ever left unanswered — a hung MCP client is a denial of service on your assistant, and that is the failure this connector works hardest to make impossible.

Scope your keys. Give a key only workflows:read / workflows:execute unless a tool you actually use needs more.

Verifying what you install

Releases are published from CI with npm provenance, so the tarball is cryptographically linked to the commit and workflow that built it:

bash
npm audit signatures

Troubleshooting

SymptomCause and fix
Unauthorized: … rejected the API keyThe key is wrong, disabled, or lacks scope. Recreate it under Settings → Developers with workflows:read / workflows:execute.
Cannot reach …Wrong --url, or the target is down. For a self-signed cert see the local-CA note above.
… redirected (HTTP 301)Your reverse proxy redirects (usually http → https). Point --url at the final URL.
The API key contains characters that cannot be sent in an HTTP headerA newline or control character got into the key — usually a copy-paste artifact. Re-copy it.
No tools listedThe connector could not reach the coordinator, or the key was rejected — see the rows above. With a working connection the static writ_* tools are always listed, even before you save a workflow.
A workflow has no run_<name> toolDerived tools are opt-in and off by default. Pin the workflow with writ_pin_workflow_tool (or the app's Expose as MCP tool switch) and it appears on the next tools/list, up to 20 per account. Every workflow runs through writ_run_workflow either way.
Client won't connect, no errorRead the connector's stderr — your client logs it. Claude Code: ~/Library/Caches/claude-cli-nodejs/<project>/mcp-logs-<name>/.

Status

Self-hosted coordinatorSupported and verified end to end.
Published /mcp/<slug> endpointsSupported.
Writ Cloud (https://api.usewrit.app, the no---url default)Supported. Used automatically when no --url is passed.

Development

bash
npm test

No dev dependencies — the suite uses Node's built-in node:test and drives the real index.js as a subprocess against a mock MCP server, exercising the same stdio path an MCP client uses. It runs in about six seconds. npm publish runs it automatically via prepublishOnly.

See CONTRIBUTING.md — note the two hard rules: zero dependencies, permanently, and no tool logic here.

The rest of Writ

usewrit/writThe self-host coordinator — web UI, API, your data. Start here.
usewrit/writ-agentThe Rust fleet worker that does the actual browsing.
writ-mcp (this repo)The MCP connector.

License

MIT — see LICENSE.

This package is deliberately permissive because it runs inside your MCP client, not inside the coordinator, so it has to be embeddable anywhere. The coordinator it talks to is AGPL-3.0-only; the two licenses are not interchangeable.

Similar modules

Browse Browser automation