タグ
白いカードに、赤い泣き顔の仮面と緑の笑い顔の仮面が重なるPlaywrightのロゴと、黒のPlaywrightのロゴタイプ

Chrome DevTools MCP と Playwright MCP はどう違うのか — Astro ブログで Chrome DevTools を選んだ理由

Chrome DevTools MCP と Playwright MCP がコーディングエージェントに渡すものは違います。それぞれが何を提供するのか、そして Astro ブログが一方を残して他方を拒否した理由。

目次

はじめに

また 1 つブラウザツールを許可しようと .claude/settings.json を開いたところ、ブラウザ自動化サーバーが 1 つではなく 2 つ入っていることに気づきました。どちらも何か月も有効なままで、どちらもクリックとスクリーンショットができ、どちらを使うべきかはリポジトリのどこにも書かれていませんでした。その下の権限リストは手作業で追加した 23 件にまで膨らんでいました。新しいツールが初めて確認を求めるたびに 1 行ずつ足したもので、それでもまだ足りていませんでした。

結果として、このブログは現在Chrome DevTools MCP のみを使い、Playwright MCP は拒否されています。そしてこの deny ルールは、私たちのどちらも想定していなかった動き方をしました。ツールをブロックするのではなく、エージェントのコンテキストから完全に削除するのです。つまり権限ルールは、安全を保つ手段であると同時に、コンテキストを取り戻す手段でもあります。

本記事では、2 つのサーバーがコーディングエージェントに実際に何を渡すのか、どこが本当に違うのか、このプロジェクトがなぜ一方を選んだのか、もう一方が適する場面はどこか、そして権限まわりが 23 件から 2 行に縮んだ経緯を整理します。最後に、検証中に浮かび上がった MCP とはまったく関係のない罠を 1 つ紹介します。

ブラウザサーバー 2 つで 1 ターンあたり 53 個のツールスキーマ

どちらのサーバーも ~/.claude/config.json でマシン全体に対して有効でした。

~/.claude/config.json
"enabledPlugins": {
"playwright@claude-plugins-official": true,
"chrome-devtools-mcp@claude-plugins-official": true
}

MCP サーバーは待機中には何のコストもかかりません。コストになるのは毎リクエストに載せるツールスキーマです。エージェントは呼ぶかどうかを判断する前に、何を呼べるのかを知らされている必要があるからです。Playwright MCP は 24 個、Chrome DevTools MCP は 29 個のツールを公開します。毎ターン 53 個のスキーマが同乗していることになります。1 セッションでせいぜい 4 個しか使わないリポジトリでの話です。

これについて警告は出ません。サーバーは動き、ツールは現れ、コストは毎リクエストに薄く広がっているので、修正すべき問題として表に出てくることがありません。

共通部分と、それぞれ固有の部分上部の横長の帯に、両方のサーバーが備える 6 つの機能が並ぶ。ページ遷移、クリックと入力、スクリーンショット、画面サイズ変更、コンソール読み取り、JS の実行。その下に 2 枚のカードが左右に並ぶ。Chrome DevTools MCP のカードにはパフォーマンス計測、Lighthouse 監査、ヒープスナップショット、CPU / ネットワーク制限が並び、Chromium のみと記される。Playwright MCP のカードにはロールとテキストで検索、Playwright コードの実行、タブを第一級で扱う、ドロップダウンの選択が並び、Chromium・Firefox・WebKit と記される。両方のサーバーにある機能ページ遷移クリックと入力スクリーンショット画面サイズ変更コンソール読み取りJS の実行Chrome DevTools MCPパフォーマンス計測Lighthouse 監査ヒープスナップショットCPU / ネットワーク制限Chromium のみPlaywright MCPロールとテキストで検索Playwright コードの実行タブを第一級で扱うドロップダウンの選択Chromium・Firefox・WebKit
機能の 3 分の 2 は共通です。2 つのサーバーを分けるのは、片方にしかない行です。

それぞれのサーバーがエージェントに渡すもの

「一方は計測し、他方は操作する」とまとめたくなりますが、それは誤りで、広まる前に潰しておく価値があります。**どちらのサーバーもクリック、入力、ホバー、スクリーンショット、画面サイズ変更、JavaScript の実行、コンソールの読み取りができます。**機能のおよそ 3 分の 2 は名前が違うだけの同じ仕事なので、「クリックできるか」を基準にした比較は引き分けにしかなりません。

違いは、その共通部分の外側にあります。

機能 Chrome DevTools MCP Playwright MCP
ページ遷移、クリック、入力、ホバー、スクリーンショット あり あり
コンソール読み取り、JavaScript の実行 あり あり
パフォーマンス計測とインサイト分析 performance_start_traceperformance_analyze_insight 相当機能なし
Lighthouse 監査 lighthouse_audit 相当機能なし
ネットワークリクエストの調査 list_network_requestsget_network_request browser_network_requests
ヒープスナップショット take_heapsnapshot 相当機能なし
CPU とネットワークの制限 emulate 相当機能なし
ロールとテキストによる要素検索 take_snapshot がツリーを返す browser_find が専用に用意されている
任意の自動化コードの実行 なし browser_run_code_unsafe
複数タブを第一級のオブジェクトとして扱う list_pagesselect_page browser_tabs
ブラウザエンジン Chromium のみ Chromium、Firefox、WebKit

固有の行を上から読んでいくと、2 つのサーバーは競合には見えなくなります。Chrome DevTools MCP がエージェントに渡すのは DevTools のパネルそのものです。パフォーマンスタイムライン、ネットワークのウォーターフォール、メモリプロファイラ、Lighthouse。ブラウザ自身の計測機能をツールとして公開したもので、Chromium 限定なのはそれらのパネルが Chromium のものだからです。

**Playwright MCP がエージェントに渡すのはテストハーネスです。**ロールベースの要素検索、browser_run_code_unsafe による任意の Playwright コード、タブ管理、そして 1 つではなく 3 つのレンダリングエンジン。テストスイートがサイトを操作するのと同じやり方のために作られています。

Chrome DevTools MCP がその枠を取った理由

この判断がどう下されたのかを先に明確にしておきます。それによって、この結論にどれだけの重みを置くべきかが変わるからです。**上の比較はエージェントが機能の網羅範囲から作ったもので、私は提示された選択肢から選びました。**ベンチマークは一切取っていません。どちらのサーバーも時間を計測しておらず、呼び出しあたりのトークンコストも測っておらず、同じタスクを 2 回走らせた比較もしていません。以下は 1 つのワークロードに対する適合性の議論であって、どちらのプロジェクトが優れているかの判定ではありません。

そのワークロードはこのブログであり、ここでブラウザを使って実際に検証していることは狭く反復的です。

  • 目次は正しいスクロール位置で読者に追従しているか。
  • コードブロックは固定ヘッダーの下で正しく重なっているか。
  • SVG 図版のラベルは、日本語や繁体字中国語に訳したあとで viewBox からはみ出さないか。
  • IME の変換中に検索ボックスは正しく振る舞うか。
  • レイアウト変更のあとも Core Web Vitals を満たしているか。

いずれもブラウザをまたいだフローの自動化ではなく、Chromium のページの計測です。最後の 1 つはパフォーマンス計測か Lighthouse でしか答えが出せず、Playwright MCP にはどちらもありません。このリストの中に、Firefox や WebKit やロールベースのロケーターをそれ以上に必要とするものはありません。

つまり決め手になったのは計測機能の広さであり、判断を誤ったときの代償も小さいものでした。静的サイトであり、ブラウザ作業は出荷されるテストスイートではなく検証だからです。

Playwright MCP のほうが適する場面

上の理屈はきれいに反転するので、正直な反対側の事例を挙げます。

  • **Firefox や WebKit が必要な場合。**Chrome DevTools MCP は Chromium 限定で、それを変える方法はありません。Safari で再現するバグなら、他の行を見るまでもなく決着します。
  • すでに Playwright のテストスイートがある場合。browser_run_code_unsafe を使えば、エージェントはテストと同じ API でコードを書けます。ツールの数とは無関係の、実質的な節約です。
  • **仕事が計測ではなくフローである場合。**複数ステップのフォーム、認証、カートの決済など。browser_find によるロールベースの検索は、スナップショットのツリーを読んで uid を拾うより明確に優れています。
  • 複数のタブを同時に扱う場合。browser_tabs はそれを第一級の概念として扱います。

このプロジェクトはどれにも当てはまらず、だからこそ選択は簡単でした。どれかに当てはまるプロジェクトは、同じ表を読んで逆の結論に達するべきです。

1 つだけ挙げておく例外があります。以前の作業でエージェントがこれに引っかかったからです。**WebKit は Chrome DevTools Protocol ではまったく操作できません。**このブログで Safari 固有の composition イベント順序の癖を検証したときは手作業でやるほかなく、どの MCP サーバーを選んでいても結果は同じでした。

23 件の権限エントリが 1 つのグロブになった

選択が決まっても、そもそもの発端だった権限リストは散らかったままでした。13 件が Chrome DevTools のツール、10 件が Playwright のツールを指名しており、まだ使っていないブラウザツールはすべて確認プロンプト予備軍でした。

これを解決するルールは権限のドキュメントにあります。

allow ルールがツール名のグロブを受け付けるのは、リテラルの mcp__<server>__ 接頭辞のあとに限られる。ルールが設定済みの特定のサーバーを名指しするよう、サーバーの部分にはグロブを含められない。

これで Chrome DevTools の 13 行は 1 行になります。

.claude/settings.json
"allow": [
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__*"
]

覚えておく価値があるのは、サーバー部分にグロブを使えないという条件のほうです。mcp__* のようなルールは、すべての MCP ツールを許可しそうに見えて実際にはしません。ドキュメントによれば、起点の定まらない allow グロブは「警告とともにスキップされ、何も自動承認しない」とされています。サーバー部分が実在のサーバーを名指しする必要があるという制約が、便利のためのワイルドカードが黙って全面承認に化けることを防いでいます。

それぞれのグロブが短くするもの左から右へ流れる 2 行の図。上の行は手作業で列挙した 13 個のエントリから始まり、allow ルールを通って 1 個のエントリになるが、ツール 29 個は文脈に残ったままである。下の行は毎リクエストに含まれるツール 24 個から始まり、deny ルールを通ってツール 0 個になり、スキーマごと文脈から消える。最後に、allow は設定ファイルを短くし deny はリクエストを短くする、と記される。allow のグロブ13 個のエントリ手作業で列挙allow: mcp__…__*1 個のエントリツール 29 個は文脈に残るdeny のグロブツール 24 個毎リクエストに同梱deny: mcp__…__*ツール 0 個スキーマごと文脈から消えるallow は設定ファイルを短くし、deny はリクエストを短くする。
allow グロブは設定ファイルの整理です。毎リクエストから重さを取り除くのは deny グロブのほうです。

グロブが効いていることを、残っていたエントリのおかげではないと確かめるには、旧リストに一度も載っていなかったツールを使う必要があります。list_network_requests は個別に承認されたことが一度もありません。これが確認なしで実行されました。

deny ルールは 24 個のツールをコンテキストから削除した

もう一方は 1 行です。

.claude/settings.json
"deny": [
"mcp__plugin_playwright_playwright__*"
]

deny ルールは門番のように読めます。エージェントが尋ね、ルールが拒む、と。ドキュメントが説明しているのは別のものです。

名前のみのグロブによる deny ルールに一致したツールは、Claude のコンテキストから削除される。

これは仕組みも見返りも異なります。**24 個の Playwright ツールは禁止されたのではありません。存在しなくなったのです。**ルールを保存した瞬間にスキーマはリクエストから消え、再起動もなく、実行中のセッションはそれらを持たなくなりました。

権限ファイルはコンテキストの予算表でもある

エージェントを安全に保つために書くルールは分かりやすい使い方です。**エージェントを小さく保つために書くルールは、ほとんど誰も書きません。**このリポジトリでは、JSON 1 行が毎ターン 24 個のスキーマ分の価値を持ちました。この節約は理論上のものではなく、次のセッションまで持ち越されるものでもなく、確認にコストもかかりません。ツールはリストにあるか、ないかのどちらかです。

はっきり書いておくと、これはプロジェクト単位のルールです。Playwright のプラグインはマシン全体では有効なままです。他のリポジトリでは必要かもしれず、1 つのリポジトリの設定にある deny はそれについて何も主張しないからです。

playwright という名前のものが 2 つある

この変更が招く罠であり、良い罠です。

package.json
"devDependencies": {
"playwright": "^1.62.1"
}

これは MCP サーバーではなく Playwright のライブラリで、このブログはこれに大きく依存しています。scripts/check-figure-fit.ts はこれを通じて Chromium を起動し、訳された SVG のラベルが viewBox からはみ出していないかを計測します。pnpm figures:fit が実行しているのがこれです。先に触れた Safari の composition 検証も、同じライブラリが開いた CDP セッションで実際の IME 変換を動かしています。

mcp__plugin_playwright_playwright__* を拒否しても、そのどれにも影響しません。2 つは名前を共有しているだけです。一方はエージェントのコンテキストにツールを公開するサーバーで、他方はスクリプトが import するパッケージです。**deny を見て依存関係を削除した将来の読み手は、図版チェックを壊します。**型チェックもビルドもそれを検知しません。スクリプトは必要なときにしか走らないからです。だからプロジェクトのルールは、この区別が自明だと期待せずに、パッケージ名とスクリプト名とコマンド名を明示しています。

おまけ: 古い開発サーバーが公開済みの記事を 404 にする

これは MCP とは無関係です。変更の検証中に表面化し、実際に時間を奪い、そして 2 つのうちより汎用的に役立つ発見でした。

ローカルサーバーに対して図版チェックを走らせると、こうなりました。

FAIL astro-like-counter-upstash-redis-vercel-serverless-route [en] — 404 at http://localhost:4321/blog/astro-like-counter-upstash-redis-vercel-serverless-route/
FAIL astro-like-counter-upstash-redis-vercel-serverless-route [ja] — 404 at http://localhost:4321/ja/blog/astro-like-counter-upstash-redis-vercel-serverless-route/
FAIL astro-like-counter-upstash-redis-vercel-serverless-route [zh-tw] — 404 at http://localhost:4321/zh-tw/blog/astro-like-counter-upstash-redis-vercel-serverless-route/
No overflow found, but some pages could not be measured. 57 page(s) checked.

記事はコミット済みで、status'published'publishedAt も過去の時刻でした。直感はルーティングの不具合かフロントマターの不具合だと言いますが、どちらも外れです。記事は正常でした。古かったのはサーバーです。

astro dev はここでは常駐サーバーとして動き、起動時にコンテンツコレクションのスナップショットを取ります。このサーバーは 16:52 に起動しており、記事のコミットは 16:59 でした。記事が存在するより前に起動したサーバーはその記事を配信することがなく、そのことに気づきもしません。

見えにくくしていた要因が 2 つあります。1 つは、すでにサーバーが動いていると pnpm dev が新しいサーバーを起動しないことです。

$ pnpm dev
Dev server already running at http://localhost:4321 (pid 41460)

これを表示して終了コード 0 で終わります。ここでの成功終了は「起動した」ではなく「いつ起動したか分からないサーバーを再利用した」という意味で、まさに疑ってかかるべき成功風の出力です。

もう 1 つは、手がかりが要約行に埋もれていたことです。3 ロケール 30 記事に対する 57 page(s) checked は 3 ページ足りず、出力の中で何かがおかしいと知っていた数値はこのページ数だけでした。pnpm stop && pnpm dev のあと、同じコマンドはこう言いました。

Everything fits. 60 page(s) measured.
対象の記事より古い開発サーバー3 つの目印を持つ時系列の図。16:52 に開発サーバーが起動し、コレクションには 30 記事がある。16:59 に記事がコミットされるが、起動中のサーバーはそれを認識しない。その後、図のチェックが実行され、60 ページ中 57 ページだけが計測される。時系列の上の帯は、サーバーが知っている内容が起動時のまま 3 つの時点を通じて変わらないことを示す。下部の結果には、公開済みの記事に 404 が 3 件と記される。開発サーバーが知っている内容は起動時のまま変わらない16:52開発サーバー起動コレクションには 30 記事16:59記事をコミット起動中のサーバーは認識しないその後図のチェック実行60 ページ中 57 ページを計測公開済みの記事に 404 が 3 件
記事がコミットされたのはサーバー起動の 7 分後で、サーバーは起動時に読み込んだコレクションから応答し続けていました。

隣のブラウザが同じ問題を起こしえない理由

面白いのは対比のほうです。このセッションには長命なプロセスが 2 つ関わっていました。Astro の開発サーバーと、MCP サーバーが生かし続けている Chromium です。同じもののように見えます。正しい扱いは正反対で、その理由は利便性ではありません。

**開発サーバーは私のコンテンツの複製を持ち、ブラウザは持ちません。**ブラウザは指し示されるたびに HTTP で取得するので、3 日開きっぱなしのブラウザでも今の真実を映します。古くなりうる複製を何も持っていないということは、動かしたままにしても誤った答えは生まれないということです。再起動しても何も消えません。消すものがなかったからです。

どちらのプロセスがソースの複製を持つか上部にディスク上のコンテンツが置かれ、そこから 2 本の矢印が下へ伸びる。左の矢印は起動時に一度だけ複製と記され、Astro 開発サーバーに届く。このサーバーは起動時に取得したコンテンツのスナップショットを保持し、古いデータを返しうると記される。右の矢印はリクエストごとに HTTP で取得と記され、MCP ブラウザに届く。こちらはソースの複製をまったく持たず、古くなりようがないと記される。最後の指針は、複製を持つプロセスを再起動し、持たない方はそのままでよい、と述べる。ディスク上のコンテンツ起動時に一度だけ複製リクエストごとに HTTP で取得Astro 開発サーバー起動時に取得したコンテンツのスナップショット古いデータを返しうるMCP ブラウザソースの複製をまったく持たない古くなりようがない複製を持つプロセスを再起動する。持たない方はそのままでよい。
ライフサイクルの原則は 1 つの事実から導かれます。古い内容を返しうるのは、ソースの複製を持つプロセスだけです。

つまり原則は、再起動のコストではなく状態がどこにあるかから導かれます。

Astro 開発サーバー プレビューサーバー MCP ブラウザ
ソースの複製を持つか 持つ。起動時のスナップショット 持たないが、スナップショットであるビルドを配信する 持たない。HTTP で取得する
古い答えを返しうるか ありうる ありうる。ビルド出力に由来する ない
解消する方法 再起動 再ビルド。再起動ではない 消すものがない
動かしたままのコスト 誤った結果 ポートを黙って占有する なし
正しい習慣 検証が終わったら止める 検証が終わったら止める そのままにしておく

プレビューサーバーは 2 つ目の開発サーバーではなく第 3 の類型

プレビューサーバーは同じ生き物に見えますが、違います。配信するのはコンテンツコレクションではなくビルド出力なので、ソースの複製は持たず、それ自体の起動時刻からは何も分かりません。**古くなるのは、それが配信しているビルドのほうです。**プレビューサーバーを再起動しても同じバイト列を配信し直すだけで、直し方は pnpm build です。プレビューの 404 をプロセスの再起動で調べるのは、対象を間違えた時間の使い方です。

本当の危険はまた別で、静かなものです。**プレビューサーバーにとって使用中のポートはエラーではありません。次のポートを取って、何も言いません。**繰り返し実行するたびに 4322、4323、4324 と上がっていき、それ以前のプロセスはすべて生き残ります。ここでは 1 日のうちに 6 つ溜まり、3 日後もまだ 6 つのポートを握ったまま、すべてのパスに 404 を返していました。astro preview はデプロイ用のアダプターとはまったく動作しないからです。

これはこのリポジトリに限っては、散らかっている以上に厄介です。pnpm figures:fitFIT_BASE_URL=http://localhost:4321 を読むので、そのポートに居座られていたら、図版チェックは何日も前のビルドを計測し、コンテンツの欠落と見分けのつかない 404 を報告していたはずです。これを見つけるチェックは 1 行です。

ターミナルウィンドウ
lsof -nP -iTCP:4321 -sTCP:LISTEN

何にも一致しないパターンは、きれいなマシンとまったく同じに見える

その 6 つを片付ける過程で、より良い教訓が出てきました。当然だと思ったコマンドが何もしなかったのです。

ターミナルウィンドウ
pkill -f "astro preview" # 一致するプロセスはゼロ

実際のコマンドラインは .../astro/bin/astro.mjs preview なので、パターンは一度も一致せず、pkill は静かに終了し、6 つとも動き続けました。**何にも一致しないパターンは、一致するものが何もないマシンと区別がつきません。**エラーも出力も非ゼロ終了もありません。pkill -f "astro.mjs preview" にしたところ、6 つとも即座に終了しました。

同じ間違いはその日すでに 1 度出荷されていました。pgrep -f "astro dev" と書かれた鮮度チェックで、開発サーバーが応答している最中でも空の結果を返すものです。どちらも同じ誤りでした。プロセステーブルではなくシェルの履歴にしか存在しない、呼びやすい名前に一致させようとしていたのです。

ブラウザを再起動しない理由もあり、それはプロジェクトのルールがかつて挙げていたものとは違います。以前のルールはブラウザを温かいまま保つためだと書いていました。これは利便性の議論であり、弱い議論です。エージェントはそれを繰り返していましたが、私が「再起動が安いのになぜそれが問題なのか」と尋ねたところ、その理由は問いに耐えませんでした。本当の理由は、別のセッションがそのブラウザを操作しているかもしれないことで、プロセス ID はそれが誰のものかを教えてくれません。どうしても終了させる必要があるときは、プロファイルのパスが使い捨てかどうかを見分けます。

ターミナルウィンドウ
ps -p <pid> -o command= | tr ' ' '\n' | grep user-data-dir

~/.cache/chrome-devtools-mcp/ 配下のパスなら、それはサーバー自身の使い捨てプロファイルです。それ以外は、実際のタブが開かれた本物のブラウザかもしれません。

まとめ

  • **Chrome DevTools MCP と Playwright MCP は機能のおよそ 3 分の 2 が重なります。**どちらもクリック、入力、スクリーンショット、JavaScript の実行ができるので、そこを基準にした比較は引き分けになります。
  • **違いはその先で何を公開するかにあります。**Chrome DevTools MCP がエージェントに渡すのは DevTools の計測機能です。パフォーマンス計測、Lighthouse、ネットワーク、ヒープ、速度制限、そして Chromium 限定。Playwright MCP が渡すのはテストハーネスです。ロールベースの検索、任意の自動化コード、タブ、3 つのエンジン。
  • **このブログが Chrome DevTools MCP を残したのは、ブラウザ作業がクロスブラウザの自動化ではなく計測だからです。**これは機能の網羅範囲から導いた、1 つのワークロードに対する適合性の議論であって、ベンチマークによるものではありません。WebKit が必要なプロジェクトや、既存の Playwright スイートがあるプロジェクトは、同じ表から逆の結論に達するべきです。
  • **allow グロブが効くのは、リテラルでグロブを含まない mcp__<server>__ 接頭辞のあとだけです。**これで列挙されていた 13 件が 1 行になりました。mcp__* なら代わりに警告付きでスキップされていました。
  • **deny グロブはツールをブロックするのではなくコンテキストから削除します。**JSON 1 行が、24 個のツールスキーマを毎リクエストから即座に、再起動なしで取り除きました。権限ルールはエージェントを安全に保つ手段であるだけでなく、小さく保つ手段でもあります。
  • **npm パッケージの playwright は Playwright MCP ではありません。**サーバーを拒否しても、ライブラリを import するスクリプトには何の影響もなく、両者を混同するとビルド工程が検知しないものが壊れます。
  • **検証が終わったら開発サーバーは止め、MCP ブラウザはそのままにします。**開発サーバーは起動時にコンテンツのスナップショットを取るので、嘘をつきうるからです。ブラウザは何も持たないので、つけません。
  • **プレビューサーバーは 2 つ目の開発サーバーではなく第 3 の類型です。**それ自体の起動時刻は無意味で、古くなるのは配信しているビルドのほうなので、直し方は再ビルドであって再起動ではありません。それでも止めてください。使用中のポートがあると黙って次を取るため、固定した FIT_BASE_URL が後で当たるポートを孤立プロセスが握り続けます。
  • 何にも一致しないプロセスパターンは、きれいなマシンとまったく同じに見えます。pkill -f "astro preview"pgrep -f "astro dev" も一致するプロセスはゼロです。プロセステーブルにあるのは astro.mjs だからです。どちらも何も報告せず、そこが問題です。シェルで打つ名前ではなく、プロセステーブルにあるものに一致させてください。

参考リンク

この記事をシェア