浏览器自动化MCP服务器与CLI

为AI编程代理提供连接Customaise Chrome扩展的MCP服务器与CLI,支持用户脚本管理、AgentScripts构建、WebMCP工具调用、可视化DOM元素选择及标签页控制,适用于IDE或命令行中的浏览器自动化场景。

  • by getcustomaise
  • TypeScript
  • 本地部署
  • 9 星标
  • 更新于
在 GitHub 查看 getcustomaise/customaise-mcp

使用说明

@customaise/mcp

MCP server and CLI that connect AI coding agents to the Customaise Chrome extension. Manage UserScripts, build AgentScripts, call WebMCP tools inside the user's signed-in browser session, select DOM elements visually, and drive tabs. Drive it over stdio from an IDE, or as a customaise command from a shell.

19 tools, 5 resources plus a script resource template, WebSocket bridge between your agent and a real Chrome session, whichever door it arrives through.

Two ways in. customaise-mcp is the MCP server an IDE spawns over stdio. customaise is a CLI for agents that have a shell instead, driving the same tools through a resident daemon. Both go through the same cap enforcement and the same consent gate.

AI Agent ←(stdio)→ MCP Server ←(WebSocket)→ Customaise Extension

Speaks the MCP 2026-07-28 revision, negotiated per connection: a client on that revision gets the stateless flow with cacheable tools/list, and a client on the 2025 revisions keeps working unchanged. The package is 3.x precisely so nobody has to guess this from a 2.x version number.

Quick Start

1. Install Customaise

Install the Customaise Chrome extension on Chrome 147 or later and enable MCP Bridge in Settings. Chrome 147 is the tested release baseline for extension 1.3.9.

2. Add to your IDE

Cursor (.cursor/mcp.json):

{
  "mcpServers": {
    "customaise": {
      "command": "npx",
      "args": ["-y", "@customaise/mcp"]
    }
  }
}

Claude Code (one command, no file to edit):

claude mcp add customaise -- npx -y @customaise/mcp

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "customaise": {
      "command": "npx",
      "args": ["-y", "@customaise/mcp"]
    }
  }
}

Kiro (.kiro/mcp.json):

{
  "mcpServers": {
    "customaise": {
      "command": "npx",
      "args": ["-y", "@customaise/mcp"]
    }
  }
}

Codex (~/.codex/config.toml):

[mcp_servers.customaise]
command = "npx"
args = ["-y", "@customaise/mcp"]

Antigravity (mcp_config.json):

{
  "mcpServers": {
    "customaise": {
      "command": "npx",
      "args": ["-y", "@customaise/mcp"]
    }
  }
}

3. Done

Your agent can now read and edit UserScripts, build AgentScripts that expose WebMCP tools to it, select DOM elements visually, inspect the console, and take screenshots of the live tab.

Tools (19)

Script Lifecycle

Tool Description
list_scripts List every script (UserScripts and AgentScripts) managed by the extension
import_script Pull a script to a local file for editing
export_script Push a local file to Customaise (validates and installs)
delete_script Permanently delete a script
toggle_script Enable or disable a script

Browser Context

Tool Description
get_page_context DOM snapshot of the current page
get_console_context Console logs, errors, and GM_log output
list_tabs List all open browser tabs

Tab Control

Tool Description
open_tab Open a new tab at a given URL
close_tab Close a tab by ID
focus_tab Switch focus to a tab by ID
reload_tab Reload a tab to re-inject scripts

Visual DOM Targeting

Tool Description
get_selected_elements Get the DOM elements the user has visually selected, with bulletproof selectors and screenshots
take_screenshot Capture any tab (not just the visible one) as a viewport or full-page image

WebMCP Agent Tools

Tool Description
list_webmcp_tools List the WebMCP tools currently registered on a tab by AgentScripts
call_webmcp_tool Call a WebMCP tool; prompt-gated tools block on user consent (see below)

UI Control & Batch

Tool Description
toggle_ui Show or hide the Customaise UI overlay
sync_scripts Bulk export all scripts to a local directory

Diagnostics

Tool Description
get_bridge_status Report extension attachment, plan tier, sign-in, remaining quota and the browser's agent permission mode when supported. Costs no quota itself.

Resources (5 plus a template)

Five resources and one script resource template that connected agents can read via resources/read. The two conventions handbooks define exactly how Customaise expects UserScripts and AgentScripts to be written. Agents should read the relevant handbook before touching a script.

URI Description
customaise://scripts Live JSON list of every script the extension manages (ID, name, enabled state, match patterns, shared flag)
customaise://scripts/{scriptId} Full source and metadata for a specific script
customaise://conventions Points at the right handbook for the script type you're working on
customaise://userscript-conventions Full UserScript reference: file structure, IIFE pattern, GM_* APIs, symbol-level editing, @match and @namespace rules
customaise://agentscript-conventions Full AgentScript reference: the // ==AgentScript== block, // @webmcp <tool> <permission> declarations, navigator.modelContext.registerTool(), consent model
customaise://script-decisions Shared UserScript/AgentScript guide: Decisions API questions, the CM_decisions API, working example, device/provider setup, billing and failure handling

Script Decisions in UserScripts and AgentScripts

Extension 1.3.8 and MCP/CLI 3.2.7 include the Script Decisions guide and example. Script Decisions is a Power User capability; the form described here needs extension 1.3.8 or later. Read customaise://script-decisions over MCP or run:

customaise resource script-decisions

Use it for bounded text classification, relevance checks and scoring; use ordinary code for extraction, exact rules and actions. Both script engines declare @grant CM_decisions and call CM_decisions.create or createMany with Decisions API questions written out in the code. The resource contains a complete UserScript and instructions for wrapping the same helper in an AgentScript tool.

The user saves separate Jev/Decisions API keys on /account#script-decisions, enables the device in Your Devices (from another device when that browser is marked Operated by an AI agent), and selects a provider in the script's Decisions menu (its branch icon, or Quick Actions → Script Decisions). Requests then run automatically. Settings can pause the device without losing selections; Data & Analytics reports usage by device/provider. Provider charges and budgets are separate from Customaise browser AI tokens. MCP/CLI cannot set these protected permissions or supply keys. WebMCP approval still applies independently to the containing tool.

create and createMany wait for the answers themselves. Preserve refusals and errors; do not resend uncertain work or switch providers automatically. Changing a script's code asks for its provider to be chosen again. See the Script Decisions guide.

Let an agent set up the CLI

In extension Settings → MCP & Terminal → Quick Setup → Terminal, copy the setup prompt for Codex, Claude Code or another shell agent. The Grok Bot tab has a prompt for installing the CLI on the Bot's own computer. Both prompts verify Node.js, install the current published package, run doctor, add the project primer with init, and read the conventions before script work. The CLI guide and Grok Bot guide provide the same prompts.

Updating to MCP/CLI 3.2.8 and extension 1.3.9

The CLI and Desktop bundle ship the same tested runtime dependencies. Runtime dependencies are bundled in the npm tarball so fresh installs retain those versions. Contributors should use npm ci; dependency updates belong in package-lock.json and must pass the packaged-release checks before publication.

Update both the extension and the companion for the latest permission controls and status reporting. The npm package contains both customaise-mcp and the customaise CLI; Claude Desktop uses the matching 3.2.8 .mcpb bundle. Restart all MCP clients and any running Customaise CLI daemon after updating. The 19-tool surface and relay protocol 2 are unchanged from 3.2.3.

The UserScript handbook now covers the 1.3.9 library workflow: Tampermonkey ZIP/JSON restore through Settings → Scripts → Manual Restore, preserved source and saved values, nine Script options, all five execution timings, API-specific callback/Promise forms, IPv6 network declarations, compressed/large responses, local dependencies, private-session values and paused updates after edits. Read customaise://userscript-conventions or run customaise resource userscript-conventions before editing an imported script. The migration reference explains browser requirements and known limits. Selected regression coverage does not establish universal Tampermonkey compatibility.

In extension 1.3.6, Settings → Agent permissions offers Use tool permissions (the default) and Full access. A user-confirmed Full access grant lets eligible Prompt tools run without repeated Customaise approvals in that browser profile, including new and updated tools. Saved Deny rules, undeclared tools, disabled scripts and URL restrictions still apply. Saved per-tool choices are preserved; turning Full access off restores them. The grant stays on until disabled or the user signs out, is not synced, and does not enable Chrome DevTools access. Browsers configured to accept approvals only from another device cannot enable Full access locally.

get_bridge_status and customaise doctor expose agentPermissions with mode, revision, scope and locked. Older extensions may return no status. There is no MCP/CLI command to grant Full access: the user controls it in the extension. The interactive bottom bar exposes active permissions, DevTools, bridge and sync controls; Appearance can keep them visible while off as well.

A temporary account-verification failure is reported as unknown/verifying, not a confirmed Free subscription. Calls that cannot safely verify access fail with retryable entitlement_unavailable and do not consume Free quota. Wait for verification to recover before retrying; a genuine subscription change still applies normally. Tool actions are never replayed automatically. Structured permission refusal details and CLI exit codes survive leader/follower forwarding.

Extension 1.3.5 also syncs explicit per-tool Allow/Prompt/Deny choices with each script. Update and finish syncing the browser holding those choices before removing its profile, and use the updated extension when restoring. Previously discarded local-only choices cannot be recovered from older cloud snapshots. Resetting a tool to its script default syncs too; older clients without this field do not erase the saved choices.

Save deadlines and recovery (extension 1.3.4 / MCP 3.2.3)

export_script reports an operation ID and stages such as normalization, Monaco queue/setup, resources and persistence. The extension bounds the whole save to 90 seconds, including waiting for the offscreen document. Cancellation and transport timeouts propagate through both leader and follower processes. Work cancelled before persistence cannot later publish. Concurrent preparations and storage revisions are checked again at the commit boundary.

After a timeout, use get_bridge_status({ scriptId, operationId }), with the IDs from the error, or customaise doctor --script ID --operation ID. This authenticated read costs no quota, including when the normal quota is exhausted. committed means there is a retained commit receipt; matchesCurrentCode says whether that version remains current. not_committed identifies a known attempt that has not written. unknown means there is insufficient retained proof: inspect the current script before retrying. A submitted Chrome storage write cannot be recalled. Its receipt is stored with the script and survives a browser restart. Only the latest receipt per script is durable; recent operation stages are retained in memory for up to 15 minutes (128 entries, 32 running saves).

For edits, pass the codeHash returned by import_script as export_script.expectedCodeHash to reject a save based on an outdated import. Update all MCP processes together: internal leader/follower relay protocol 2 adds cancellation ownership. The extension wire protocol and 19-tool count are unchanged; the full save contract requires extension 1.3.4.

WebMCP Tool Calls & Consent (HITL)

AgentScripts register tools on web pages via navigator.modelContext.registerTool(...). Each tool is declared in the AgentScript's // @webmcp <toolName> <permission> header with one of three permissions:

  • allow: tool executes immediately. ~50 to 100ms round-trip per call (the extension still runs permission checks).
  • prompt: in the default tool-permissions mode, each call surfaces an in-browser consent modal and blocks until the user approves or denies. User-enabled Full access in extension 1.3.6 lets eligible Prompt tools run without that modal; Deny rules and separate DevTools requirements still apply. Up to 5 minutes. Design for this. Don't chain prompt-gated calls in tight loops, and treat a long call_webmcp_tool as normal.
  • deny: tool is suppressed and calls fail immediately.

"Always allow" and "Always deny" buttons on the consent modal persist the decision per-script per-tool until the user resets it in extension Settings. These overrides are stored with the script and included in encrypted sync when enabled. The extension evaluates them for each call; MCP cannot set them or grant itself Full access.

Remote approvals (optional)

If the user has Power User and has enabled Remote HITL Approvals on their Customaise account page, prompt-gated calls are also mirrored there. They can approve or deny from any signed-in browser, including a phone. Either the extension modal or the remote surface can resolve; first signed decision wins. From the MCP client's perspective this is transparent: call_webmcp_tool simply returns the result when any authorised surface approves, or an error if denied or timed out.

What MCP clients see

  • A prompt-gated call_webmcp_tool response may take up to 5 minutes. Surface a pending state to the end user rather than timing out aggressively.
  • While it waits, the server sends notifications/progress on any request that carried a progressToken, one per extension of the consent budget. A client that resets its request timeout on progress (the MCP SDK does, so does Claude Code) waits with the user. Claude Desktop and Cursor cap a tool call at a fixed 60 seconds at the time of writing and will report the call failed while the user can still approve it; the tool description tells the agent not to blindly re-issue a call with side effects when that happens.
  • If the user denies, call_webmcp_tool returns an error. The MCP server does not retry.
  • Tool-call arguments transit HTTPS in plaintext to our backend and land KMS-encrypted at rest in Firestore. Metadata (toolName, scriptName, origin) stays plaintext. See the Customaise Privacy Policy.

Visual DOM Selection

Users can visually select elements in the browser, and the extension pushes context files to your workspace in real time:

.customaise/dom-context/<script-name>/
├── element-name.dom.md          # Selectors, element context, user comments
├── element-name.screenshot.png  # Cropped screenshot of the selected element
└── ...

[!NOTE] Where are the files saved? The MCP server writes .customaise/ to its current working directory (usually your project root in Cursor). If you are using a global IDE like Claude Desktop, it defaults to your home directory (~/.customaise/). To force a specific project folder, set CUSTOMAISE_WORKSPACE in your MCP config:

"env": { "CUSTOMAISE_WORKSPACE": "/absolute/path/to/your/project" }

[!NOTE] From the CLI, the directory you ran the command in wins over both. The daemon is long-lived and was started from whatever directory you happened to be in the first time, so it takes the caller's word for it on every command. CUSTOMAISE_WORKSPACE still beats a plain cwd for IDE servers, unchanged.

Use get_selected_elements to retrieve selections programmatically, or read the pushed .dom.md files directly from the workspace.

Each selection includes bulletproof tiered selectors (stable IDs → data attributes → ARIA → semantic classes → structural positioning) so targeting survives page updates.

Workflows

UserScript

1. get_page_context       → understand the target page
2. User selects elements  → .dom.md files auto-pushed to workspace
3. Write .user.js file    → AI writes the script using IDE tools
4. export_script          → Customaise validates and installs
5. reload_tab             → re-inject the script
6. get_console_context    → check for errors
7. take_screenshot        → verify the visual result

AgentScript

1. Read customaise://agentscript-conventions   → get the structure right before writing
2. get_page_context                            → find stable selectors on the target page
3. Write .agent.js file                        → declare tools via // @webmcp, register with navigator.modelContext.registerTool()
4. export_script                               → Customaise validates and injects
5. reload_tab                                  → the AgentScript registers its tools in the page
6. list_webmcp_tools                           → confirm tools surfaced
7. call_webmcp_tool                            → invoke one; prompt-gated calls wait for user consent

File Sync

Use sync_scripts to bulk-export every script to a local directory:

sync_scripts({ directory: "~/customaise-scripts" })

This creates:

  • One .user.js file per script. Filename is derived from the script name (lowercase, hyphens, e.g. my-cool-script.user.js).
  • .customaise-manifest.json: maps filenames to script IDs for round-trip editing.

Manifest format

{
  "dark-mode-fix.user.js": "vm_script_1774225715376_lus75sdzn",
  "my-cool-script.user.js": "vm_script_1774225800123_abc12defg"
}

Round-trip

  1. sync_scripts exports all scripts to a directory.
  2. Edit any .user.js file in your IDE.
  3. export_script with the file path and scriptId from the manifest updates that script.
  4. Omit scriptId when calling export_script to create a new script instead.

File watcher (auto-export)

Once sync_scripts has been called, the MCP server watches the directory for .user.js changes. Saving a file in your IDE pushes it to Customaise automatically, no manual export_script needed.

Configuration

Environment Variable Default Description
CUSTOMAISE_WS_PORT 4050 WebSocket server port
CUSTOMAISE_MCP_EXTRA_EXTENSION_IDS (empty) Comma-separated list of extra extension IDs allowed to connect. Needed for unpacked dev builds with a non-standard extension ID
CUSTOMAISE_MCP_ALLOW_INSECURE (unset) Set to 1 to disable the origin allowlist. Tests only. Emits a loud warning at startup
CUSTOMAISE_WORKSPACE (cwd) Absolute path where .customaise/ files should be written. Useful for IDEs that don't set cwd to the project root (Claude Desktop, Antigravity)
CUSTOMAISE_CONFIG_DIR ~/.config/customaise Where the CLI keeps its daemon connection file and remembered tab. Delete this directory to remove everything the CLI stores; uninstalling the extension does not, because these live outside the browser profile
CUSTOMAISE_CLI_SCOPE (empty) Optional Bot/session name separating remembered tabs within the same working directory. Remembered tabs are always scoped to the working directory; this adds isolation for Bots sharing one directory
CUSTOMAISE_HTTP_PORT 4051 Loopback port the daemon serves the CLI on. Distinct from CUSTOMAISE_WS_PORT, which is the extension's WebSocket bridge
CUSTOMAISE_MCP_OUTPUT file Where get_page_context, get_console_context and take_screenshot put their full payload when the call does not say. file writes to disk and returns a summary plus the path; inline writes nothing and returns the whole payload (for the screenshot, the image itself) in the response. Set this to inline for a client with no filesystem tool. See Chat clients and file-less agents
CUSTOMAISE_MCP_INLINE_MAX_KB 64 Ceiling on an inline JSON payload. Over it, lists are shortened (never the JSON itself, so it still parses) and the response reports exactly what was dropped
CUSTOMAISE_MCP_INLINE_IMAGE_MAX_KB 1536 Ceiling on an inline take_screenshot image, in KB of base64. Over it the capture is saved to a file instead, because half an image is not an image

Chat clients and file-less agents

get_page_context, get_console_context and take_screenshot spool their full payload to a file by default and hand back a summary plus a path. That is the right shape for an IDE agent: a DOM snapshot is routinely hundreds of KB and belongs on disk rather than in a context window.

It is a dead end for a chat client. Claude Desktop runs this server over stdio, so the write succeeds, but the model on the other end has no filesystem tool with which to open what was written. It receives a path it can never read.

Three ways out, in the order they are consulted:

  1. Per call. Pass output: "inline" and the full payload comes back in the response, with nothing written to disk. All three tools say so in their own descriptions and in every file-mode response, so an agent that hits the dead end can recover on its own in one extra call.
  2. Per install. Set CUSTOMAISE_MCP_OUTPUT=inline in the server's env. The .mcpb bundle ships with this set, because that bundle is installed into Claude Desktop and nowhere else. For an IDE, put it in the env block of your MCP config. For the CLI, exporting it in your shell is enough: the customaise binary reads it and forwards it per call, so it does not matter that the resident daemon was started earlier without it.
  3. Default. file, unchanged.

take_screenshot is the same flag with a different payload: output: "inline" attaches the capture to the response as an MCP image block rather than writing a PNG and returning its path, so a multimodal chat client can actually see it. An image cannot be shortened the way a snapshot can, so a capture over CUSTOMAISE_MCP_INLINE_IMAGE_MAX_KB (1536 KB of base64) falls back to a file and tells the caller to retry with fullPage: false, which is usually small enough, or to raise the ceiling.

Inline JSON responses are capped at CUSTOMAISE_MCP_INLINE_MAX_KB (64 KB by default). Over the cap the payload is shortened by trimming lists, not by truncating the JSON text, so what arrives still parses and still has every key and nesting level. The response carries truncated: true and an omitted array naming each shortened list and how many items it lost.

There is deliberately no client detection here. clientInfo.name is a guessing game, and the roots capability says the client declares project directories, not that the model can read them. It is also unavailable on 2026-07-28 connections. An explicit flag with a self-advertising fallback beats a heuristic that is confidently wrong.

Publishing the public mirror

This package is developed in a private monorepo and mirrored to getcustomaise/customaise-mcp, which is what package.json repository points at and what MCP scanners and directories read. Keep them in step with the sync script rather than by hand: the previous hand-copy shipped 7 of 21 source files, so the published repo did not compile and carried none of the test suites.

npm run mirror:plan                     # what would be published, as JSON
npm run mirror:verify -- ../customaise-mcp   # drift report, exit 1 if adrift
npm run mirror:apply  -- ../customaise-mcp   # copy the plan over a checkout

mirror:apply writes into a clone, deletes files this package no longer ships, and leaves the repo's own .github/ furniture alone. It never pushes. Review git diff in the checkout and push yourself.

The plan is built from git ls-files, so anything uncommitted is invisible to it. The script refuses to run while untracked files exist under mcp/ rather than publishing a tree that is missing them. src/__tests__/public-mirror.test.ts guards the shape of the plan in CI; the drift check needs a checkout and so stays a release step.

Security Boundary

The MCP server listens on ws://localhost:4050 in plaintext on your loopback interface. The connection is authenticated by an HTTP Origin header allowlist:

  • Allowed: chrome-extension://anmpijcpaobaabcdncjjmnhdeibipmko (production) and chrome-extension://ijjaffggglamocdapoihpkcpealflopp (staging). Chrome stamps this header automatically on WebSocket handshakes from extension service workers; you don't configure anything.
  • Rejected: regular web pages (https://...), unknown extension IDs, and handshakes with no Origin header. Returns HTTP 403.

What this stops: a malicious webpage opening new WebSocket('ws://localhost:4050') and calling WebMCP tools behind your back. This is the most likely abuse vector.

What this does NOT stop: a malicious native process running as your user. Node's ws client (and most HTTP libraries) lets callers forge any Origin header. If you can't trust processes running as your OS user, the threat model is already broader than this bridge.

Approval modes: tools whose effective permission remains prompt require your approval in the Customaise consent modal. The default mode follows saved tool rules. A user-enabled Full access grant can allow eligible Prompt tools in this browser profile, including new and updated tools, while saved Deny rules stay blocked.

Tools declared allow run without asking, with one exception that matters here: a script written through this bridge or the customaise CLI does not get to grant itself allow. Its self-declared allow resolves as prompt in the default mode. A saved user Allow override or a user-enabled Full access grant can permit the call without prompting; the script cannot create either grant itself. Choosing "Always allow" stores an override and it never prompts again. The MCP tool API cannot resolve the consent modal. Remote approvals route prompts away from the requesting browser, but the backend authenticates the signed-in account and accepts a client-supplied device claim; this is not proof of a separate human or physical device. An agent with account credentials or full OS access needs independent account/OS access restrictions.

Scripts you wrote yourself, and scripts you subscribed to from the marketplace, are unaffected: allow means allow. For marketplace scripts that means the old advice still holds, so only subscribe to AgentScripts from sources you trust.

Dev builds: if you load an unpacked extension with a custom key, set CUSTOMAISE_MCP_EXTRA_EXTENSION_IDS=<your-extension-id> in the MCP server's env.

CLI

Share scripts through the extension

In extension 1.3.7, open Quick Actions → Share Script on an owned UserScript or AgentScript. Publish & share keeps the hosted, read-only subscription flow with publisher updates. Send creates a 24-hour link to an independent editable copy. Sending requires Power User; receiving works with a free signed-in account. There is no MCP/CLI Send or Publish command.

Send copies start disabled, keep their own edits and saved selectors, and remain after link expiry or Stop this send, without publisher updates. Refresh link for 24 hours replaces the active snapshot with the current saved script; merely opening or copying a URL does not extend its deadline. Anyone with the active link can preview source. Review source and selectors before sending: secrets embedded there remain included. Saved account keys, GM values, browser logins, tool approvals and Script Decisions provider choices are not copied. Each recipient configures their own access and provider. Fork a subscription before modifying or sending an independent copy.

Terminal setup

For an agent with a terminal rather than an MCP client.

npm i -g @customaise/mcp     # both binaries on PATH
customaise doctor            # bridge, sign-in, tier, quota, and whether
                             # "Allow user scripts" is on. Costs no quota.
customaise init              # writes AGENTS.md in this project, so the next
                             # agent finds the CLI without being told
customaise scripts list
customaise scripts install ./my-tool.agent.js
customaise scripts get mcp_script_123 -o ./my-tool.agent.js
customaise scripts enable mcp_script_123      # or disable
customaise scripts fork shared_abc -o ./mine.agent.js
customaise scripts rm mcp_script_123
customaise sync ./customaise-scripts          # bulk export your scripts
customaise tabs
customaise tab reload 42
customaise use --tab 42                      # remember it for later commands
customaise tools                             # WebMCP tools on that tab
customaise call my_tool --args '{"q":"hi"}'
customaise context page                      # DOM snapshot
customaise shot -o ./page.png
customaise tab list                          # every short verb has a noun-verb form: tab list|shot|use
customaise schema                            # the whole command tree as JSON, for an agent to read
customaise daemon status | stop

customaise use --tab N remembers the tab for the current working directory. Bots sharing a directory can set distinct CUSTOMAISE_CLI_SCOPE values before both use and later commands. Scheduled routines should pass --tab N explicitly and verify the tab URL each run, since the browser itself remains shared and a tab can close or navigate. This scoping does not isolate browser logins or files. After upgrading from the former global remembered-tab file, select the tab once in each workspace; the global choice is not copied into every Bot's workspace.

Without a global install, npx -p @customaise/mcp customaise <verb> works but costs roughly half a second of package resolution per command against about fifty milliseconds installed. For anything in a loop, install it.

Output contract. JSON on stdout, always, so it pipes. Diagnostics on stderr. --pretty indents the JSON; -h or --help prints usage, and customaise schema prints the whole command tree as JSON for an agent to read. Exit codes are the interface:

Code Meaning
0 success
2 usage error
3 daemon or extension unreachable, including after signing out or with "Allow user scripts" off
4 signed in, but the token expired or could not be refreshed
5 free-tier cap reached
6 consent denied by the user
7 consent expired unanswered
8 rejected by Customaise (diagnostics in the payload)

Two things make 3 more common than it looks. Signing out of Customaise tears the bridge down deliberately, so it reports 3, not 4. And the "Allow user scripts" toggle on the Customaise card in chrome://extensions resets on every Chrome restart; while it is off, scripts install fine and no tool ever registers, so doctor reports 3 rather than claiming a healthy setup that cannot run anything. 4 is the narrower case: the bridge is up and the token behind it went stale. Both mean stop, and both are worth telling the user about, but only 3 is worth checking Chrome over.

Codes 5, 6 and 7 are deliberately distinct: an agent that cannot tell a cap from a refusal from a timeout retries into a wall. Code 8 is the one to handle first when installing scripts: the sanitization pipeline refused the file and the diagnostics say what to change, where a 1 means something broke and rewriting the script will not help.

The daemon. Started on first use, and it holds the WebSocket to the extension so commands do not each pay for a reconnect. It binds 127.0.0.1 only and authenticates the CLI with a token in a 0600 file that exists only while the endpoint is live. customaise daemon stop ends it.

When several customaise-mcp processes share a machine, the first to bind :4050 leads and the rest relay through it, so one extension serves every IDE and every shell at once. That seam carries its own protocol version: a process built against different frames is refused rather than served, with an error naming both versions and which one to restart (-40033). Package versions may differ freely; only a change to the frames themselves moves it. A leader that sees a follower from a newer package steps down and rejoins behind it, so a resident daemon can never pin the machine to an old version.

Custom HTTP clients: workspace and resource limits

The loopback daemon requires x-customaise-token and an absolute x-customaise-workspace on every HTTP request, including negotiation. The CLI supplies these automatically. A missing, relative or malformed workspace returns HTTP 400 before dispatch; requests never inherit another caller's directory. An explicitly declared filesystem root is honored, with ordinary filesystem permissions still applying. This header chooses the output directory; it is not an OS sandbox for clients that share the same daemon token.

For Unicode paths, send encodeURIComponent(absolutePath) as the workspace and x-customaise-workspace-encoding: uri. The daemon decodes once; raw headers without that encoding retain literal percent signs. New daemons advertise workspaceEncoding: "uri" in daemon.json. The CLI uses that capability and asks you to stop an older daemon before using a path it cannot represent safely.

The daemon admits at most 16 concurrent HTTP requests and remembers up to 256 workspaces active in the last 15 minutes. New requests above either limit receive HTTP 429 before tool dispatch. Uploads are limited to 8 MiB and 30 seconds; responses to 32 MiB cumulatively and requests to 10 minutes, including streamed output. These transport limits do not raise the smaller limits of individual tools. At most 64 TCP connections are accepted, with a 15-second header timeout. SSE progress streams immediately, and slow readers apply backpressure instead of causing the daemon to buffer the entire response.

An oversized upload receives HTTP 413; a stalled upload receives HTTP 408. Pre-dispatch errors include outcome: "not_dispatched". Once a tool has been submitted, a response failure or disconnect may leave its outcome unknown: check what happened before retrying a mutating tool. If a response exceeds its limit after streaming starts, the transport closes with an error rather than returning a truncated success. Disconnects and the request deadline propagate cancellation; a handler that ignores cancellation retains its admission slot until it settles, so a stream of replacements cannot create unbounded background work.

Requirements

  • Node.js ≥ 20
  • Chrome with the Customaise extension installed (≥ 1.2.3 for the v2 bridge protocol; older extensions still work but don't surface the cap-usage display)
  • MCP Bridge enabled in Customaise Settings (free, signed-in)

Plan tiers

The MCP Bridge is free for any signed-in Customaise user. Free use is capped at 50 calls per UTC day and 150 calls per rolling 7-day window. Power User unlocks unlimited MCP. The cap covers every successful tool dispatch (built-in tools and WebMCP calls alike); failed calls and protocol-level traffic don't count.

When the cap fires, the server returns a JSON-RPC error with code -40029 and a human-readable message + structured data carrying scope, used/limit, and reset timestamp. IDEs that surface tool errors render the message verbatim. Sign-in is required regardless of tier; without a fresh Firebase ID token the server returns -40028 MCP_AUTH_REQUIRED. Branch on structuredContent.error.type, never on the number: the codes moved once already (3.2.0 took them out of the range the 2026-07-28 specification reserved for itself; the old -3202x values are still accepted from older extensions), the type strings did not.

Troubleshooting

"Customaise extension is not connected"

  • Make sure Chrome is running with the Customaise extension.
  • Check that MCP Bridge is enabled in extension Settings.
  • The extension connects automatically within a few seconds.

Port conflict on 4050

  • Set a different port: CUSTOMAISE_WS_PORT=4051 npx @customaise/mcp.

Scripts not running after export

  • Call reload_tab to trigger script re-injection.
  • Check the @match pattern covers the current URL.

call_webmcp_tool hangs for minutes

  • The tool is prompt-gated. The user has to approve in the browser, or remotely if Remote HITL Approvals is on. 5-minute budget before auto-deny. Surface a pending state rather than timing out.

call_webmcp_tool returned an error like "consent denied"

  • Expected when the user denied the modal, the 5-minute budget expired, or a previous "Always deny" override was set on that tool. The user can reset per-tool overrides in extension Settings.

list_webmcp_tools returns empty after a reload

  • Walk the conventions handbook's troubleshooting checklist. Check Chrome's Allow user scripts setting with customaise doctor, the intended script's enable switch in Scripts, and whether its @match covers the URL. Reload the page after installing or changing the script. See customaise://agentscript-conventions for the full list.

License

MIT