customaise
Official# @customaise/mcp
MCP server and CLI that connect AI coding agents to the [Customaise](https://customaise.com) 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](https://customaise.com) on **Chrome 147 or later**, sign in, and turn on **Agent Bridge** in Settings → MCP & Terminal. Chrome 147 is the tested release baseline for extension 1.3.9.
### 2. Add to your IDE
**Cursor** (`.cursor/mcp.json`):
```json
{
"mcpServers": {
"customaise": {
"command": "npx",
"args": ["-y", "@customaise/mcp"]
}
}
}
```
**Claude Code** (one command, no file to edit):
```bash
claude mcp add customaise -- npx -y @customaise/mcp
```
**Claude Desktop**: install `customaise.mcpb` from the [latest release](https://github.com/getcustomaise/customaise-mcp/releases/latest). The bundle returns page content inline, which a chat client needs (see [Chat clients and file-less agents](#chat-clients-and-file-less-agents)). Or add it to `claude_desktop_config.json` with the same setting:
```json
{
"mcpServers": {
"customaise": {
"command": "npx",
"args": ["-y", "@customaise/mcp"],
"env": { "CUSTOMAISE_MCP_OUTPUT": "inline" }
}
}
}
```
**Kiro** (`.kiro/mcp.json`):
```json
{
"mcpServers": {
"customaise": {
"command": "npx",
"args": ["-y", "@customaise/mcp"]
}
}
}
```
**Codex** (`~/.codex/config.toml`):
```toml
[mcp_servers.customaise]
command = "npx"
args = ["-y", "@customaise/mcp"]
```
**Antigravity** (`mcp_config.json`):
```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 stable, tiered 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
Script Decisions is a **Power User capability** and needs extension 1.3.8 or later.
Read `customaise://script-decisions` over MCP or run:
```sh
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](https://customaise.com/learn/script-decisions).
## 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](https://customaise.com/learn/userscripts#moving-scripts)
covers moving a library from Tampermonkey. Customaise provides every GM API and
metadata key Tampermonkey supports on Chrome.
## Agent permissions and Full access
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.
Customaise 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
`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 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](https://customaise.com/privacy).
## 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:
>
> ```json
> "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 **stable, tiered selectors** (stable IDs → data attributes → ARIA → semantic classes → structural positioning) so targeting is more likely to survive 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
```json
{
"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. The extension always connects to 4050, so change this only for tests |
| `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](#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.
## Source
This repository mirrors the package source from Customaise's own monorepo at each release.
## 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.
## Sharing scripts
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.
## CLI
### 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](https://customaise.com/learn/cli) and
[Grok Bot guide](https://customaise.com/learn/grok-bot) provide the same prompts.
### Terminal setup
For an agent with a terminal rather than an MCP client.
```sh
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
```
```sh
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 147 or later** with Customaise (1.3.9 is the tested pair for 3.2.8), signed in, with **Agent Bridge** on.
## Plan tiers
The Agent 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 **Agent Bridge** is on in Settings → MCP & Terminal.
- The extension connects automatically within a few seconds.
**Port conflict on 4050**
- The extension always connects to localhost:4050. Find what holds the port with `lsof -i :4050` and stop it. `CUSTOMAISE_WS_PORT` is for tests.
**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
TDQS
Scored across 19 tools
Each tool maps to a distinct resource/action: tabs, scripts, WebMCP tools, and context gathering are cleanly separated. The one soft spot is the import_script/export_script/sync_scripts trio, where the directionality (import = pull to local file, export = push to Customaise, sync = bulk pull) is counterintuitive and could cause misselection, though the descriptions clarify it.
Consistent snake_case verb_noun pattern throughout (focus_tab, list_tabs, open_tab, close_tab, get_page_context, toggle_script, delete_script). Verb prefixes are predictable and used uniformly across all 19 tools.
19 tools is a touch heavy but each earns its place across three clear concerns: browser tab control, script lifecycle management, and WebMCP/script execution. Slightly on the high side but not bloated for the scope.
Full lifecycle coverage for scripts (list/import/export/delete/toggle/sync) and tabs (open/close/focus/list/reload), plus context and WebMCP execution. Minor gaps like creating a script from scratch or reading a single script's raw source still get 'minor gap' rather than blocking.