ログイン済みChromeにChrome DevTools MCPを繋ぐまでの切り分け記録

開発claude-code-tools

ログイン済みのChromeに繋げば、いつも自分が見ているタブをそのまま操作させられる。 SNSの下書き保存を試そうとしただけだったのに、繋がるまでに原因が4回入れ替わった。 しかも4つとも、症状の見え方が似ていた。

繋がってからの作業(アカウントのプロフィール整備)は、技術的にはただのフォーム入力である。 詰まったのは接続の方だけだった。

一発目は「プロファイルが使用中」で止まった

最初のツール呼び出しで、Chrome DevTools MCP用のプロファイルが既に使用中だと返ってきた。 プロセス一覧を見させると、確かにChromeが1つ生きている。 だが、そのChromeには誰も繋がっていない。

ここで一度、別経路(ブラウザ操作の別スキル)に逃げようとしたので止めた。 繋がらない理由が分からないまま道具を変えると、次に同じ状況を踏んだときにまた同じ迂回をする。

孤児プロセスは、パイプの相手を失っていた

原因は、--remote-debugging-pipe で起動されたChromeが残っていたことだった。

パイプ方式の接続は、起動したプロセスと1対1で結ばれる。 標準入出力を握り合っているのと同じで、あとから第三者が横入りできない。 そのため、相手側(前のMCPサーバー)が消えると、Chromeは生きているのに誰からも繋げない状態で取り残される。 ポート方式(--remote-debugging-port=9222 のような形)なら他のプロセスから叩けるが、パイプ方式はそれができない。

さらに、この孤児は自動化専用プロファイル(.cache\chrome-devtools-mcp\chrome-profile)で動いていた。 日常使いのChromeとは別物なので、これを落としても自分のタブは1つも消えない。 そこを確認したうえで、孤児だけをPID指定で終了させて、MCPに新しくChromeを起動させた。

繋がった。 ただし、繋がった先が違った。

専用プロファイルには繋がる。ログインが無い

新しく起動したChromeは、まっさらな自動化用プロファイルである。 SNSを開かせても、当然ログインしていない。

自分が欲しかったのは「ログイン済みの、いま自分が使っているChrome」だった。 接続の成否だけを見ていると、ここは成功に見えてしまう。 繋がった時点で報告されそうだったので、その場で「それじゃない」と差し戻した。

Chromeが150だったので、autoConnectが使えた

Chrome 136以降、既定のUser Dataディレクトリではリモートデバッグが無効化されている。 Cookieの持ち出し対策で、ログイン済みの通常プロファイルには従来の方式で繋げない。

一方でChrome 144以降には、chrome://inspect で受信側のデバッグ接続を許可し、--autoConnect で繋ぐ経路が用意されている。 手元のバージョンを確認させたら150だった。 使える。

chrome:// のページは自動化側から触れないので、許可のトグルだけは自分で押した。 ここは「AIにできない操作」の分かりやすい例で、指示もそのまま受け取った。

ポートは開いているのに、404が返ってきた

再接続してもタイムアウトする。 実Chromeがリッスンしているポートは見つかった。 ところが、HTTPで /json/version を叩くと404が返る。

従来は /json/version がJSONを返すかどうかが「デバッグポートが生きているか」の判定だった。 その常識で見ると、これは死んでいる。 404を見た瞬間に「壊れている」と判断しかけて、手を止めた。

代わりにWebSocketで直接ハンドシェイクを投げさせたら、101が返ってきた。 つまりHTTPのディスカバリだけが閉じていて、WebSocketは開いている。 Chrome 144以降のこの仕様では、「404だから壊れている」という切り分けが成り立たない。

--autoConnect の自動探索の方が固まっていたので、エンドポイント(ws://127.0.0.1:<port>/devtools/browser/<uuid>)を直接指定する形に書き換えさせた。

「繋がっていないのはツールだけ」だった

再接続の許可ダイアログは自分でOKした。 それでもツールは動かない、と言ってくる。

ここが一番惑わされた。 別プロセスでヘルスチェックを走らせたら Connected と出る。 サーバー側は実Chromeに繋がっていて、このセッションにツール定義がロードされていないだけだった。

症状は「繋がらない」に見えるが、実体は「繋がっているのに手が届かない」である。 /mcp から再接続したら、ツールが載って一気に通った。

これで4つ目。 振り返ると、同じ「繋がらない」の下に、孤児プロセス、プロファイル違い、HTTP経路の仕様変更、ツール未ロードという別種の原因が並んでいた。 どれか1つの対処法を覚えても、次は効かない。

繋がったあと、タイムラインをDOMから取り出した

ログイン済みのタブが一覧に出た。 タイムラインに載っている投稿をDOMから構造化して、14件そのまま取り出せた。

もっとも、自分が言った「投稿保存」は投稿の下書き保存の意味だった。 取得したファイルは要らないので消させて、下書き保存の方をやり直させた。 1語で解釈が2つに割れる依頼をした自分の責任である。

下書き保存は通った。 複数行の本文で空行が保たれるかまで確認させて、下書き一覧のURLを保存後に必ず案内する手順まで含めてスキルに落とした。

アカウント整備は、対象の確認から始めさせた

次にやらせたのは、ブランド用に使うアカウントのプロフィール整備である。

プロフィール設定フローのURLを渡したところ、着手前に「どのアカウントに対する操作か」を確認してきた。 これは正しい。 ログイン中のアカウントが複数ある状態でプロフィールを書き換えると、取り違えたときに個人アカウントの表示名とアイコンが吹き飛ぶ。

対象は、1年以上前に作って投稿2件でフォロワー0のまま放置していた既存アカウントだった。 設計文書で決めておいたアイコンとbioを入れさせ、bioは160字上限に対して122字で収まった。 位置情報の欄は設計文書に指定がないので、勝手に埋めずスキップさせた。

表示名までは変わった。 ハンドル名の変更だけが通らない。 パスワードの確認を求められるからである。

パスワードが分からないアカウントが出てきた

そこで初めて、このアカウントのパスワードを自分が覚えていないと気づいた。

まず1Passwordに保存があるかを確認させた。 このとき確認させたのはユーザー名だけで、パスワードの値は取り出させていない。 値を画面に出すと、そのままセッションのログに残る。 該当はなかった。

次にリセットを進めさせたら、途中で失敗して、そのアカウントのログインセッションが切れた。 個人アカウントの方は生きていたので被害はそこで止まったが、認証まわりの操作を任せた結果である。 正直に報告してきたので、そこは受け取った。

新規作成に振ったが、任せる操作ではないと切り分けた

パスワードが分からないなら、新しくアカウントを作る方が早い。 そう考えて方針を切り替えた。

これは断られた。 アカウント作成はパスワードの設定と本人確認を含むので、任せる操作ではないという線引きである。 自分でもこの線は残したい。 プロフィールの文面を入れるのと、認証情報を新しく作るのは、失敗したときに戻せるかどうかが違う。

代わりに作成画面を最短の状態まで用意させたところ、副産物として2つ分かった。

  • ログイン中のブラウザではサインアップ画面がリダイレクトされる。作成画面を出すには、ログインしていない分離コンテキストが必要になる
  • そのSNSはウェブでのメールアドレス新規登録を実質塞いでいて、アプリ導入のQRコードに誘導される。新しいアドレスを入れても「登録されていません」と返るだけで、作成には進めない

壁を1つずつ数えたところで、新規作成をやめた。

既存アカウントの転用で決着した

結局、放置していた既存アカウントをそのままブランド用に転用することにした。 プロフィールはもうブランドの表記になっていて、セッションが残っていれば使える。

パスワードは自分で更新して解決した。 リセットが通らなかったのは、登録に使ったメールが自分の想定と別のアドレスだったからだった。 ここは他人に代われる作業ではないので、詰まった原因が分かった時点で自分の手で終わらせるのが早い。

パスワード確認を通過した状態のタブから、ハンドル名の変更に入らせた。 第一希望は既に取られていたので、設計文書に書いておいた予備案に落ちた。 このとき、入力だけのつもりが保存まで走って、確認を挟まずに確定した。 結果は予備案どおりなので害はなかったが、UI操作を任せると「入力」と「確定」の境目が曖昧になる。

アカウント切り替えの手順も、実測でスキルを直させた。

  • 切り替え先の要素は role="menuitem" ではなく data-testid="UserCell" だった
  • 戻すときの要素はボタンで、ラベルが「(アカウント名)に切り替える」だった
  • スクリプト経由のクリックは効くときと効かないときがあり、実マウスイベントの方が確実だった
  • 切り替え後はリロードするまで画面に反映されない

内部APIの書き込みは、できなかった

UI操作は脆い。 本文の全選択が効かずに追記になって文字数上限を超えたり、設計文書に置いていた固定ポスト案が140字を69字オーバーしたりで、そのたびに破棄してやり直しになった。

それなら内部のGraphQL APIを直接叩けばUIを触らずに済む。 そう考えて、下書き保存のリクエストを観測させた。

読み取り側は動いた。 分離ウィンドウから下書き一覧の取得APIを直接叩いて、UIで保存した下書きが返ってきた。 つまり下書きはサーバーに保存されていて、APIで扱える対象である。

書き込み側は届かなかった。 fetch をフックしてもログが空で、performance APIはSNS側で潰されている。 JSバンドルから作成側のqueryIdを探させたが、別チャンクにあって特定できなかった。

報告が長くなって、できたのかできなかったのかが読み取れなかったので、その1点だけを聞き直した。

結果
下書きの読み取りAPIで動いた。保存後の確認はUIを開かずに済む
下書きの書き込みできなかった。UI操作のまま

分からない状態で「対応しました」と受け取ると、あとで実装が空だったことに気づく。 できなかったと1行で言わせておく方が、次に手を出すときに迷わない。

プロフィールを揃えて、ヘッダーも入れた

最後に、bioをnote側の実際の文言に合わせさせた。 先に自分の手で直していた分は触らせず、SNS側だけ揃えた形になった。

ヘッダー画像は、ロゴのSVGから2種類作らせた。 鳥のマークは紺の面を含んでいるので紺背景に置くと欠ける。 白のワードマークを主役にして、左下(アイコンが重なる位置)を空けて右に寄せる構図にした。 SNS用(1500×500)はアップロードまで通り、note側は2回失敗したので、生成したPNGのフルパスを受け取って自分で入れた。

今日の学び

  • パイプ方式で起動されたChromeは、相手が消えると誰からも繋げない。プロセスが生きていることは接続できることを意味しない
  • Chrome 144以降は /json/* が404でもWebSocketは開いている。404を「壊れている」の判定に使えなくなった
  • 「繋がらない」の症状には、サーバー未接続とツール未ロードの2種類がある。別プロセスのヘルスチェックで切り分けられる
  • ログイン済みプロファイルに繋ぐ経路(--autoConnect)は144以降で用意されている。専用プロファイルで繋がっても、ログイン状態は手に入らない
  • 認証情報に触る操作(アカウント作成、パスワード設定、本人確認コードの入力)は任せない。プロフィールの文面入力とは、失敗したときに戻せるかどうかが違う
  • 1Passwordの確認はユーザー名までにとどめる。値を出せばセッションのログに残る
  • UI操作を任せると「入力」と「確定」の境目が曖昧になる。ハンドル名のような一度しか変えたくない項目は、確定の直前で必ず止めさせる