@marrowdev/browse-mcp
v0.19.1
Published
Drive your own Chrome from an AI agent, with its logins intact. A local stdio MCP server: it maps what a page affords, acts on it, and hands the keyboard back to you for logins, 2FA and CAPTCHAs instead of solving them.
Maintainers
Readme
marrow-browse — ローカル stdio MCP(実ブラウザを操縦する)
ユーザー自身の Chrome を、専用プロファイルで Playwright から駆動する stdio MCP。
AI は外の脳として browse_open / browse_act / browse_read / browse_paginate /
browse_wait_for_human / browse_list / browse_close / browse_shutdown を使う。
🤝 browse_wait_for_human だけは向きが逆(2026-08-03・Marrow #16)。他は「AI が動く」道具だが、
これは人が動くのを AI が待つ道具 —— CAPTCHA・ログイン・2FA・同意画面。デスクトップ通知を出し、
条件が満たされるまで上限付きで待ち、終わり方を全部名前で返す(cleared / already /
navigated / widget-gone / timed-out / page-gone / blind)。⚠️ チャレンジを自動で解かない・
自動化を隠さない —— 人間チェックは人間が通す、という線は意図的(証跡が正直であることがこの製品の価値)。
🚨 弱い名前が 2 つあるのは意図的(navigated / widget-gone)。観測していないことを名乗らないため:
| 名前 | 観測したこと | 言えないこと |
|---|---|---|
| navigated | ページが 1 回移った | 多段ログインの途中の段かもしれない(Marrow #63・npm で実測) |
| widget-gone | チャレンジ部品が消えた | サイトが受け入れたかは見ていない(#22・Stripe で実測) |
⭐ #22 の実測: Stripe は multicaptcha(hCaptcha + HUMAN Security)を併走させ、人が hCaptcha を
解いて部品が消えた後に 401 を返し、しかもそれを**「メールまたはパスワードが正しくありません」と
名乗った。⇒ 見えるチャレンジを人が通しても、ブラウザ自体を見ている 2 つ目の検査は覆らない。
どちらの名前も expect(行き先)を渡せば cleared になる —— 行き先を名乗れた**時だけ cleared。
⚠️ browse_close は 1 ページ、browse_shutdown はブラウザごと(2026-07-31 に分離)。
ブラウザが生きている限りセッション Cookie も生きるので、remember-me を持たないサイトの
ログインも残る。browse_shutdown を呼ぶとそのサーバは二度と起動できない(原因未特定)。
インストール
npm i -g @marrowdev/browse-mcp前提物は Google Chrome だけ(node は 20 以上 —— ⚠️ 2026-08-13 実測で 18 では起動しない。
playwright-core が Playwright requires Node.js 20 or higher. と言って止まる。
🚨 package.json の engines は長く >=18 を名乗っていた= npm は通し、走らせると死ぬ形だった)。⚠️ Chrome は同梱しないし、落としてもこない ——
掴むのはあなたが昨日ログインしたままの Chromeなので、代替ブラウザや playwright の同梱 chromium を
使うと素のプロファイルで起動する=この道具の意味が消える。見つからなければ chrome-not-found と
名前で止まり、CHROME_PATH の設定を促す(スタックトレースは出さない)。
MCP クライアントの設定(例: ~/.claude.json):
"marrow-browse": {
"command": "marrow-browse",
"env": {
"MARROW_BROWSE_HEADLESS": "0",
"MARROW_BROWSE_IDLE_MS": "1800000"
}
}🤝 初回は MARROW_BROWSE_HEADLESS=0(表示あり)で。窓が出たら、使いたいサイトに人が一度
ログインする —— 以後そのプロファイルにセッションが residing するので、AI はその先を歩ける。
⚠️ パスワードはこの道具に渡さない(渡す口が無い。打つのは人とブラウザの autofill)。
リポの clone から動かす場合(開発)
⚠️ 上とは別経路。clone には run.sh が在り、起動ごとに焼き直す(焼いた物を残さない)。
"marrow-browse": {
"type": "stdio",
"command": "<clone>/browse-mcp/run.sh",
"env": {
"PATH": "<node 20+ の bin>:/usr/bin:/bin",
"MARROW_PROFILE_DIR": "<state>/marrow/profiles/default",
"MARROW_BROWSE_HEADLESS": "0",
"CHROME_PATH": "/usr/bin/google-chrome"
}
}🚨 run.sh を通すこと。bun run browse-mcp/index.ts で直に起動しない —— playwright の
connectOverCDP は bun でハングする(playwright-core 1.58.1 / 1.52 / 1.48 / 1.44 / 1.40 の
5 版で 2026-08-02 に実測・全部 timeout)。run.sh は node 向けに焼いてから node で走らせる、
そのためだけに在る。⚠️ この行が要る理由は run.sh の冒頭が全部書いている。
🚨 <clone> を実パスに読み替えるのは読む人。ここに実パスを焼かない —— 2026-08-11 の分割で
/home/takaki2/dev/Marrow を焼いたこの例が丸ごと存在しない場所を指すようになり、
live 登録もろとも 2 日間死んでいた(2026-08-13 実測・このリポの #1)。⚠️ 環境依存の絶対パスは、
書いた面の数だけ次に腐る面が増える。
⚠️ 落とし穴 3 つ(全部踏んだ):
- env は MCP の起動設定側で渡す。
StdioClientTransportは呼び出し側の env を素通ししない —— シェルでexportしても効かず、既定パスに落ちる。 PATHに node 20+ を入れる(2026-08-13 実測)。run.shは最後にexec nodeするので、 クライアントが渡す最小 PATH で **/usr/bin/node(18 系)**を掴むと、esbuild は緑のまま playwright がrequires Node.js 20 or higherで止まる。🚨 ビルドが通ってから死ぬので 「起動した」ように見える。nvm を使っているなら~/.nvm/versions/node/<ver>/binを明示する。- ~~
cdを書く~~ → ✅ もう要らない(2026-08-13)。run.shは冒頭で自分の位置から repo root へ移るので、/bin/sh -cとcdを挟まず run.sh を絶対パスで直接 command にする。 ⚠️ 2026-08-10 に「この設定を書き換える作業でcdを 2 回落とした」と書いてあった落とし穴は、 消えやすい物を無くす方で塞いだ。無関係な cwd(/tmp)から起動して緑を確認済み。
- 起動(手で):
<clone>/browse-mcp/run.sh(bun install済みであること=バンドラが devDependency) - プロファイルの仕組み・器の制約・実測記録・移植の分界線 → リポのルートにある
USAGE.md
🚨 ここをリンクにしないこと(#12)。この README は npm に同梱されて配られるので、
パッケージの外へ登る相対リンクは npmjs.com で 404 する —— clone では緑に見えるまま、
②に初めて会う人の所でだけ切れる。⚠️ 絶対 URL も使えない: 出所は LAN の Gitea で、
外からは辿れない(repository / homepage を意図的に載せていないのと同じ理由)。
💭 公開ページが出来たら(Marrow #38)そこを指すのが本筋。check-dist.mjs が
prepublishOnly から見張っていて、登るリンクが在れば publish が止まる。
設定を更新したら /mcp reconnect
🚨 設定エントリを変えた直後の reconnect だけ、前のサーバが取り残される(2026-08-10 実測・
クライアントのログと 5/5 で一致)。定義が変わらない reconnect では、クライアントが
SIGINT → SIGTERM できちんと止めている。
✅ Linux では新しいサーバが起動時に前の自分を落とすので、放っておいてよい
(stderr に swept 1 older self/selves: <pid> と出る)。
🚨 macOS にはその掃除が無い(/proc を読む実装のため)。あちらでは手で:
ps -eo pid,ppid,pgrp,lstart,args | grep target/browse-mcp.mjs # 同じ pgrp の古い方が残骸⚠️ pkill -f target/browse-mcp.mjs は使わないこと —— 他セッションの現用サーバも
同じコマンド行なので、まとめて落とす。pid を見てから落とす。
- スモークテスト:
bun run browse-mcp/probes/probe-paginate-wire.ts(MCP の面を実際に叩く) ⚠️ 昔ここはpoc/act-perceive/test-client.tsを指していたが、poc/は 2026-08-11 の分割で Marrow に残ったので、このリポには無い(→ #4)。 🚨browse-mcp/probes/e2e-lifecycle.tsは回さないこと —— あれだけ共有プロファイルを使いbrowse_shutdownまで踏むので、走らせるとログインを失いうる(前科あり)。
この repo の中に置いてある理由
src/affordance.ts(perceive / act)に直接依存するため。mcp-servers/ に移すと
別リポの内部実装を相対 import することになり、複製に逆戻りしやすい。
🚨 その懸念は実際に当たった。ここへ昇格したとき(2026-07-30)旧 poc/act-perceive/core.ts を
消さなかったので、同じ export を持つ 2 つ目の実体が残り、5 本の PoC がそちらを使い続けた。
修正が丸一日届かなかった(ref 一意化バグ)。2026-07-31 に core.ts を削除して解消。
src/affordance.ts が唯一の実体。ここにコピーを作らないこと。
⚠️ 2026-08-11 の分割後もこの理由は変わっていない —— 変わったのは「この repo」が
Marrow ではなく marrow-browse を指すようになったことだけ。browse-mcp/ と src/ が
兄弟のままなのはそのため(移設で import を 1 行も書き換えていない)。
💭 Marrow 側が同じ 5 本を使う必要は、コピーではなく @marrowdev/browse-core で満たしている。
| MCP | 経路 | 何を操縦するか | 置き場 |
|---|---|---|---|
| marrow-browse(これ) | stdio・ローカル | 本人のブラウザ(ログイン済みセッション) | この repo |
| marrow-lan | HTTP(:3002) | macmini の worker(取得サービス・LAN 限定・課金なし) | ~/dev/mcp-servers/marrow-lan-mcp |
| marrow | — | ③ SaaS(Edge 経由・課金) | Marrow の mcp-saas/(npm @marrowdev/cloud-mcp) |
環境変数
| | 既定 | |
|---|---|---|
| MARROW_PROFILE_DIR | OS 別(Linux ~/.local/share/marrow/profiles/default) | ログインが溜まる器。⚠️ 日常プロファイルを指さない |
| MARROW_BROWSE_HEADLESS | 1(0 で表示あり) | 初回ログインは表示ありで |
| CHROME_PATH | OS 別の候補から実在する最初の 1 つ(Linux /usr/bin/google-chrome → -stable → /opt/google/chrome/chrome、macOS は /Applications と ~/Applications の app bundle) | ⚠️ Chromium / Brave / Edge は候補に入れない。探しているのは「ページを開ける物」ではなく本人の Chrome のログイン済みプロファイルなので、代替を掴むと素のプロファイルで起動する=②の意味が消える。見つからなければ chrome-not-found で止まる(browse-mcp/probes/probe-chrome-missing.ts)|
| MARROW_BROWSE_MAX | 8 | 同時 page 数(⚠️ 器 1 つに Chrome 1 つなので page 単位) |
| MARROW_BROWSE_IDLE_MS | 300000 | アイドル回収。⚠️ 実際の登録は 1800000(30 分)で上書き=既定値を運用値と読まない。刈るのはページだけでブラウザは畳まない |
| MARROW_BROWSE_LAUNCH_TIMEOUT_MS | 20000 | 起動の上限(⚠️ Playwright の launch 既定は 180 秒) |
| MARROW_BROWSE_NEWPAGE_TIMEOUT_MS | 20000 | newPage() の上限(newPage は timeout 引数を取らないので外側で囲む) |
| MARROW_BROWSE_NOTIFY | 1 | 人を呼ぶデスクトップ通知(0 で切る)。Linux は notify-send -u critical・macOS は osascript。⚠️ 窓は前に出さない(Wayland に手段が無く、上がらなくても例外が出ない=嘘になる)ので、報せ自体が行き先(サイト名)を持つ ——🚨 持つのは title(Marrow — <host>)。本文の最終行に置いていたら切り詰めで最初に消える場所だった(Marrow #58) |
| MARROW_BROWSE_NOTIFY_CMD | — | 通知コマンドの差し替え。検出器用の縫い目(probe-handoff.ts が偽の通知器で「人が何を読むか」を実測する) |
🚧 通知の面はここから広げない(2026-08-03 に決めた): 一方向・撃ちっぱなしの「人が要る」
信号だけ。返事は受け取らない。 ボタン / 音 / 既読 / 常駐は全部「返事を受け取る」側で、
そこからが UI の所有 —— 返事が要る用事は client の仕事(MCP elicitation)。
⚠️ 文面は英語 1 本。ここに翻訳表を置かない(呼び出し側がその人の言語を知っている LLM なので、
表を持つのは逆さま)。理由と逃げ道の全文は notify.ts の冒頭(⚠️ ソースなので同梱されない・リンクにしない・#12)。
