A MiniMax Code plugin that gives mcode access to the user's real, signed-in Chrome — the same browser, the same profile, the same logins — the way pi and muse already do.
It reuses the Pi Chrome Connector companion extension and the pi-chrome loopback bridge. Nothing
is re-launched, nothing is re-logged-in, and the user's cookies are the user's cookies.
MiniMax Code ──MCP (stdio)──> mcode-chrome server ──HTTP 127.0.0.1:17318──> Pi Chrome Connector ──> real Chrome
The MCP server is the plugin's only moving part. It speaks the pi-chrome wire protocol and has two modes:
- server — it owns the loopback port and brokers commands to the extension.
- client — another session (typically a running
pi) already owns the port, so commands are forwarded to that owner.
The mode is decided at startup and re-decided if the owner disappears, so mcode and pi can drive
the same browser at the same time without fighting over the port. mcp__chrome__status reports
which mode is in play.
Each session also gets its own automation tab, grouped as MCode Session: <id>. Tools with no
explicit target act on that tab, so the user's currently active tab is never navigated, overwritten,
or closed by accident.
One caveat about tab groups: the companion extension's groupTab renames the group a tab is
already in, rather than adding the tab to a second one. So tab {action: "group"} on a tab the
user grouped themselves renames the user's group to the session title. The plugin sends the real
session title — the extension's own "Pi" fallback is never used — but the rename itself is
extension behaviour, and it is worth knowing before pointing tab group at a user's tab.
# from a checkout of this repository
scripts/install-plugin.sh mcode-chrome
# or, from inside the plugin's own directory
bash mcode-chrome/scripts/install.shThat copies the plugin to ~/.minimax/plugins/mcode-chrome and registers it with the local
marketplace. Verify with:
mcode plugin listThe plugin needs node on PATH (the MCP server is launched as node ./server/main.mjs) and the
Pi Chrome Connector extension loaded in the Chrome profile you want to drive. The extension ships
with pi-chrome (MIT):
npm install -g pi-chromeThis plugin deliberately does not bundle a copy of the extension, because a Chrome extension's
id is derived from the unpacked folder's path — a second copy would register as a second connector
and race the first one for commands. If the extension is not loaded, mcp__chrome__status prints
the exact folder to select at chrome://extensions → "Load unpacked".
The MCP server is named chrome, so tools are mcp__chrome__<tool>.
| Tool | Purpose |
|---|---|
status |
Bridge health, extension connection, authorization state. |
authorize / revoke |
Unlock / lock the tools that change the page. |
tab |
List, open, activate, close, group tabs. |
snapshot |
Read the page: structure, actions, forms, text, changes. |
find |
Locate elements or text by natural-language query. |
inspect |
Deep-dive one element: nearby text, form context, ancestors. |
navigate |
Load a URL. |
evaluate |
Run JavaScript in the page (MAIN world, CSP-safe). |
click, type, fill, key |
Act on the page. |
hover, drag, tap, scroll |
Pointer and touch input. |
wait_for |
Wait for a selector or a truthy expression. |
console_messages |
Captured console output. |
network_requests, network_request |
Captured XHR/fetch traffic and bodies. |
screenshot |
Capture the page; the image is returned inline. |
upload_file |
Attach local files to a file input. |
snapshot, find, inspect, console_messages, network_requests and read-only evaluate are
read-only and always available. The rest need authorization.
The loopback bridge trusts any local process, so the plugin carries the gate that /chrome authorize carries in pi.
- Default: authorized. This matches the posture already chosen for pi on this machine
(
pi-chrome-auto-authpins pi's gate to indefinite). The user installed this plugin to give the agent browser access. mcp__chrome__authorizetakes a duration —indefiniteor a number of minutes.mcp__chrome__revokelocks actuation. Read-only tools keep working.
The decision is persisted in ~/.minimax/plugin-data/mcode-chrome/auth.json (override with
MCODE_CHROME_DATA_DIR). To lock the plugin by default, write {"until": 0} into that file.
The skills/chrome-browser/SKILL.md skill adds the second layer: read-only first, explicit
confirmation before actuating on a page, and secret-handling rules for a browser full of
credentials.
Mirrors pi-chrome deliberately:
- The listener binds to
127.0.0.1only — no network exposure. /commandis accepted only from a local process (noOrigin, nosec-fetch-site)./nextand/resultare accepted only from achrome-extension://origin.- Output is redacted before it is printed: secret-shaped keys lose their values, data URLs are elided, long strings are truncated.
Page content still reaches the model, exactly as in pi-chrome. The skill tells the agent to say so when the user asks it to work on a sensitive page.
node scripts/build-manifest.mjs # regenerate mcp/chrome.mcp.json from server/tools.mjs
node scripts/server-mode-test.mjs # standalone: own the port, mock companion, full protocol round trip
node scripts/smoke-test.mjs # initialize, tools/list, status, tab, snapshot
node scripts/e2e-test.mjs [url] # navigate + snapshot + find + evaluate + console + screenshotbuild-manifest exists so the declared tool list in the plugin manifest can never drift from the
running server: both are generated from server/tools.mjs.
server-mode-test.mjs covers the branch a live test cannot reach while another session owns the
port: it starts the bridge as the owner, runs a mock companion that speaks the real wire protocol
against it, and asserts the /next → command → /result round trip, the session key on the wire,
and that a companion-side failure rejects with its reason.
The server has zero dependencies — it runs on a plain node executable with nothing installed
alongside it. It imports only node:http, node:fs, node:path, node:os, and node:crypto.
This is a standalone mcode plugin, not a wrapper around pi.
Ours — everything mcode actually runs:
- the plugin manifest, the MCP server, the 23 tools, the skill
- the authorization gate, the redaction and snapshot formatting
- session scoping and the
MCode Session: <id>tab group - the owner-or-client port logic, written from scratch for this plugin
server/ imports nothing but Node built-ins. There is no node_modules, no import of
pi-chrome, and no dependency on the pi runtime. The single functional reference to pi-chrome is
a path lookup used only to print "load the extension from here" in status when the connector
is not yet running.
Borrowed (MIT, from pi-chrome):
- the wire protocol's shape — four endpoints, the same command/result envelope
- the companion extension itself, which is installed on this machine already
Shared at runtime: the loopback port. If a pi session is running it owns 127.0.0.1:17318 and
this plugin forwards commands to it; with no pi session running the plugin owns the port itself
and brokers commands to the extension directly. Both paths are tested, and neither requires pi to
exist.
screenshot (path) and upload_file (paths) take local paths. A relative path resolves
against the agent workspace — the directory mcode was launched from — because MiniMax Code
spawns plugin MCP servers with the home directory as their working directory and passes no
workspace path in the environment. An absolute path is unambiguous and preferred; baseDir
overrides the base directory for a relative path.
| Variable | Effect |
|---|---|
MCODE_CHROME_BRIDGE_HOST |
Bridge host (default 127.0.0.1). |
MCODE_CHROME_BRIDGE_PORT |
Bridge port (default 17318). |
MCODE_CHROME_BRIDGE_URL |
Force client mode against a specific URL. Debugging only. |
MCODE_CHROME_EXTENSION_PATH |
Path to the companion extension folder to report in status. |
MCODE_CHROME_DATA_DIR |
Where the authorization state is persisted. |
MCODE_SESSION_ID |
Stable session id; defaults to a hash of the workspace directory. |
MCODE_WORKSPACE |
Overrides the workspace directory that relative paths resolve against. |
While building this, a pre-existing bug in @minimax-ai/code 0.6.12 surfaced on this machine.
MiniMax Code seeds a recoverable-delete shim at <dataDir>/shims/rm and puts it ahead of /bin/rm
on the agent's PATH. The shim is generated from a template in
chunks/chunk-5ADYFF2U.js whose shebang is hardcoded to #!/bin/bash. This system is Nix-based
and has no /bin/bash (bash lives at /run/current-system/sw/bin/bash), so every rm in agent
bash failed with bad interpreter: No such file or directory — deletes never reached the trash.
There is no newer published version, so the template was patched to use #!/usr/bin/env bash,
which resolves through PATH on both Nix and conventional systems:
# in @minimax-ai/code/chunks/chunk-5ADYFF2U.js
var qur = `#!/usr/bin/env bash
# Managed by MiniMax Code. Do not edit.
...The app re-seeds the shim whenever the on-disk content differs from the template, so the fix applies from the next session start.
Rather than committing a 6 MB stale copy of the bundle, the repo ships an idempotent repair
script — run it after mcode update if the shim starts failing again:
node scripts/apply-mcode-rm-shim-fix.mjs # patch and re-seed
node scripts/apply-mcode-rm-shim-fix.mjs --check # report onlyThis plugin does not depend on the fix — scripts/install.sh removes the old plugin copy with
node rather than rm, so it works either way.
mcode-chrome/
├── .minimax-plugin/plugin.json plugin manifest
├── mcp/chrome.mcp.json MCP server declaration (generated)
├── server/
│ ├── main.mjs MCP stdio server
│ ├── bridge.mjs loopback bridge, owner-or-client
│ ├── tools.mjs tool definitions and handlers
│ ├── format.mjs snapshot rendering, redaction
│ ├── auth.mjs authorization gate
│ ├── session.mjs session key and tab group
│ ├── mcp-server.mjs shared MCP stdio server (generated copy)
│ ├── retry.mjs shared retry core (generated copy)
│ └── workspace.mjs shared workspace resolver (generated copy)
├── skills/chrome-browser/SKILL.md agent workflow and safety rules
└── scripts/ manifest builder, tests, installer
AGPL-3.0-or-later, matching this repository. The wire protocol and the companion extension come from pi-chrome (MIT); the MIT-licensed protocol is reimplemented here rather than vendored.