オープンソース · MIT · MCP

AI エージェントに、ユーザーのスマートフォン内の WebView をデバッグさせる。

H5 ページやアプリの WebView に script タグを 1 つ追加します。ユーザーが短いデバイスコードを読み上げれば、Claude Code、Codex、Cursor などの MCP クライアントがページを見て、console とネットワークを読み、自分でタップして不具合を再現できます。その様子はユーザーに見えていて、いつでも引き継げます。

  • USB ケーブル不要
  • リモートデバッグ用ポート不要
  • アプリは script タグ 1 つの注入だけ
  • 不具合が起きた後からでも間に合う

01ライブデモ実際に試す

「実機」で本物の不具合を突き止める次は自分のエージェントで試してみてください

このデモのスマートフォンでは、デモショップの中で本物のページ SDK が動いています。ゲートウェイはブラウザ内のモックなので、このタブから出ていくものは何もありません。けれども下のスナップショット、クリック、console の 1 行、ライブパッチは、すべて本物です。

こちらは本物です。demo.debugger.site に公開のデモゲートウェイがあり、ほかのすべてから隔離されていて毎日リセットされます。スマートフォンの中のショップはそこから読み込んでいます。キーを取得してエージェントの接続先に指定すれば、ここで動く様子が見られます。

準備完了
ショップがオンラインになるのを待っています…
demo.debugger.site

AI エージェント

rwd MCP サーバーに接続済み

エージェントがスマートフォンに接続し、決済の不具合を再現して根本原因を突き止め、修正をその場で検証するまでを見てみましょう。

  1. 自分用のキーを取得する

    クリック 1 回、登録は不要です。このキーはデモゲートウェイでのみ使え、デモがリセットされるまで有効です。

    キーを取得すると、下のコマンドに埋め込まれます。

  2. デモゲートウェイをエージェントに追加する

    ターミナルでコマンドを一度実行するか、MCP クライアントの設定に書き込みます。キーはすでに埋め込まれています。

    terminal
    claude mcp add --transport http rwd-demo https://demo.debugger.site/v1/mcp \
      --header "Authorization: Bearer rwd_sk_live_…"
  3. エージェントに頼む

    スマートフォンの中のショップのデバイスコードはプロンプトに埋め込まれています。エージェントが接続を求めたら、ショップで 許可 をタップして動く様子を見てください。引き継ぐ や 終了 はいつでも押せます。

    プロンプト
    rwd-demo の MCP サーバーを使ってデバイス XXXX-XXXX に接続して。ショップで商品に Gift box の包装を選んで決済に進み、合計が「--」と表示される原因を調べて。

    XXXX-XXXX はショップに表示されているデバイスコードに置き換えてください。

デモの制限

  • 訪問者ごとにキー 1 つ:エージェントが届くのは、あなたがデバイスコードを渡し、許可したページだけです
  • レベルは interact のみ:スクリプト実行、リクエスト本文、ストレージは使えません
  • 1 つのエージェントが 1 ページを占有できるのは最大 15 分(無操作なら 5 分)です
  • MCP リクエストは IP ごとに毎分 120 件までです
  • 毎日 04:00(UTC+8)にリセットされます。実在する個人情報は入力しないでください

お客さまのスマートフォンK7Q2-9M4X

デモショップ(ライブ)····-····

ショップがオンラインになるのを待っています…

コードを読み取ってスマートフォンで開く
新しいタブで開く

これは demo.debugger.site そのもので、本物の SDK が動いています。コードを読み取れば、スマートフォンでも開けます。

完全にブラウザ内で動作します:iframe の中は本物の SDK(sdk.js)、WebSocket のシムがその接続をページ内のモックゲートウェイにトンネルし、スクリプト化されたエージェントが本物の RPC を発行します。エージェントの発言はページの言語に従い、ツールの結果はゲートウェイの言語(英語または中国語)になります。

02仕組み

「自分のスマートフォンで壊れている」から根本原因まで、3 ステップ

  1. ページに SDK を追加する

    <head> にできるだけ早く script タグを 1 つ。console とネットワークが 1 行目から記録されます。アプリでは、同じスクリプトをドキュメント開始時に注入します。

  2. ユーザーがデバイスコードを伝える

    どのスマートフォンにも K7Q2-9M4X のような固定のデバイスコードがあり、小さなピルに表示されます。ユーザーが サポートを依頼 をタップすると、そのデバイスはエージェントの一覧の先頭に並びます。

  3. エージェントが MCP で接続する

    ゲートウェイがホストする MCP のエンドポイントを API キー付きで追加し、あとは問題を説明するだけです。エージェントがデバイスを見つけ、許可を求め、調査を始めます。

index.html
<!-- In <head>, before your own scripts; pin a version in production -->
<script src="https://cdn.jsdelivr.net/npm/remote-webview-debugger/dist/sdk.js"></script>
<script>
  RemoteWebviewDebugger.run("https://rwd.example.com/v1", "rwd_pk_…", {
    userId: currentUser?.id ?? null, // optional: find devices by user
    helpButton: true,                // show the “Request assistance” button
  })
</script>

本番ではバージョンを固定するか、ゲートウェイ自身のコピー /v1/sdk.js を使ってください(SRI の値は管理コンソールにあります)。

terminal
claude mcp add --transport http rwd https://rwd.example.com/v1/mcp \
  --header "Authorization: Bearer rwd_sk_live_…"
デバイス K7Q2-9M4X で決済できず、合計が「--」と表示される。原因を調べて。

03エージェントができること

おなじみの DevTools のやり方を、MCP 経由で他人のスマートフォンに

  • アクセシビリティスナップショット

    Playwright 形式の YAML に要素の ref 付き。エージェントはページをピクセルではなく構造として読み、ref で正確に操作します。

  • 本物の操作

    クリック、フォーム入力、文字入力、キー押下、選択、ホバー、スクロール、ドラッグ。操作可能かを確認し、React や Vue が受け付けるイベントを発行します。

  • console とネットワーク

    ページの読み込み時から上限付きのバッファに記録するので、エージェントが参加する前のエラーも残っています。本文も必要に応じて取得できます。

  • 複数ページのセッション

    一覧 → 詳細 → 決済、あるいは 2 つ目の WebView。1 台のデバイスのすべてのページが 1 つのセッションになり、ページをまたぐタイムラインが見られます。

  • セッションの変化をプッシュ

    ツールの結果の末尾には必ず変化が付きます。新しいページ、遷移、エラー、ユーザーによる引き継ぎ。ポーリングは不要です。

  • スマートフォン上のタスクカード

    エージェントが計画と進捗を端末に表示するので、ユーザーは AI が何をなぜしているのかを常に把握できます。

  • スクリーンショット

    アプリがブリッジを提供していればネイティブの実ピクセル、なければ DOM の再描画。どちらにもマスクが適用されます。

  • 端末上のセッションログ

    アプリの起動以降のページ、タップ、console、すべてのリクエストを、スマートフォンの IndexedDB に保存。後からでも読めます。

  • ライブパッチ

    advanced レベルでは、エージェントが evaluate_script で動いているページにパッチを当て、修正をその場で検証できます。

  • 不安定なネットワーク

    バックオフ付きの軽いハートビート。3 回失敗したら毎分 1 回の存在確認に切り替え、必要になればゲートウェイがページを起こします。

  • 11 言語対応

    スマートフォン側の UI、管理コンソール、エージェント向けのテキストが 11 言語(右から左に書くアラビア語を含む)。独自の言語パックや文言も追加できます。

  • 任意の MCP クライアント

    /v1/mcp は Streamable HTTP で、Claude Code、Codex、Cursor などがそのまま使えます。stdio しか話さないクライアントには小さなブリッジがあります。

すべてのツール 34

  • list_devices
  • connect_device
  • wait_for_device
  • list_pages
  • select_page
  • wait_for_page
  • take_snapshot
  • find_elements
  • take_screenshot
  • click
  • fill
  • type_text
  • press_key
  • select_option
  • hover
  • scroll
  • drag
  • batch_actions
  • navigate_page
  • go_back
  • reload_page
  • wait_for
  • list_console_messages
  • list_network_requests
  • get_network_request
  • get_storage
  • evaluate_script
  • set_task
  • get_session_timeline
  • get_session_overview
  • get_session_log
  • wait_for_event
  • wait_for_control
  • disconnect_device

04注目ポイント

現場でしか起きない不具合のために

ケーブル、リモートデバッグ用ポート、画面録画では得られない 3 つのこと。

セッションログ · 端末上のコールドストレージ

不具合はもう起きてしまった? ログはまだスマートフォンの中にあります。

アプリが起動した瞬間から、SDK は端末の IndexedDB にセッションログを書き込みます。訪れたページ、ユーザーのタップと入力、console の出力、そしてすべてのリクエスト。失敗したものはヘッダーと本文も一緒に残ります。どこにも送信されません。同じマスクのルールのもとでスマートフォンに留まり、ユーザーが許可したエージェントが get_session_overview と get_session_log で読むまでそのままです。直近 3 回の起動分を、1 回あたり最大 10 MB まで保持します。

ライブパッチ · evaluate_script

出荷する前に、お客さまのスマートフォンで修正を検証。

ユーザーが advanced を許可すると、evaluate_script がページの中で実行されます。エージェントは壊れた状態を直し、関数を差し替え、ビューを再描画して、その結果を実機で確かめられます。診断を証明するライブパッチです。それはそのページのメモリにしか存在せず、何も永続化されません。センシティブなクリックには、やはりユーザーの同意が必要です。

エージェント向け · MCP

DevTools を使う人間ではなく、エージェントのために作られています。

ツールの結果の末尾には、前回の呼び出し以降に端末で起きた変化が必ず付くので、エージェントはポーリングしません。ページの内容は信頼できないデータとして渡されます。スマートフォンのタスクカードがエージェントの計画を示し、ゲートウェイはエージェントの言語で話します。ツールの説明と結果が 11 言語あります。

05プライバシーと主導権

ユーザーのスマートフォンは、あくまでユーザーのもの

リモートデバッグは信頼されてはじめて成り立ちます。どのセッションも、見えていて、許可が必要で、いつでも止められます。

  • まず許可

    エージェントが接続を求めると、ユーザーは誰が求めているかを見て、スマートフォンでレベルを選びます。有効なのは実際のタップだけで、スクリプトが自分を許可することはできません。

  • いつでも引き継ぐ、終了する

    エージェントが操作している間、ページは光る縁の内側でロックされます。引き継ぐ を 1 回タップすればエージェントは一時停止し、終了 で切断されます。

  • デフォルトでマスク

    パスワードと data-rwd-mask を付けた要素は、スナップショット、スクリーンショット、console、ネットワークのデータ、セッションログで •••• と表示されます。プロジェクトごとにセレクターや正規表現も追加できます。

  • センシティブなクリックには同意が必要

    「注文を確定」「アカウントを削除」のようなボタンに data-rwd-confirm を付けると、エージェントがクリックするたびにユーザーが確認します。

  • 権限レベル

    observe は閲覧、interact は操作、advanced はさらにスクリプト実行、リクエスト本文、ストレージ。キーごと、プロジェクトごとに上限を設けられます。

  • 監査ログ

    接続、承認、RPC、引き継ぎのすべてを、誰が・何を・いつ、で記録します。ページの内容やパラメータの値は記録しません。

06ネイティブアプリ

アプリでは、ユーザースクリプト 1 つです

SDK を一度ダウンロードし、ドキュメント開始時に注入します。iOS は WKUserScript、Android は addDocumentStartJavaScript。Web ページ側は何も変えません。

  • どこでも同じデバイスコード

    固定の deviceId を渡し(deviceToken は保存しておけば)、WebView やドメインをまたいでもデバイスコードは変わりません。

  • ネイティブの操作ボタン

    nativeControl() で独自の引き継ぐ / AI に戻す / 終了ボタンを表示できます。ページのスクリプトからは呼び出せません。

  • ネイティブの承認ダイアログ

    nativeApproval: true にすると、接続の要求と確認をアプリ自身の UI に渡せます。

  • 実ピクセルのスクリーンショット

    小さなブリッジ(WKWebView のスナップショット、Android のビットマップ)があれば、エージェントは DOM の再描画ではなく本物の画面を受け取ります。

RemoteDebugger.swift
import WebKit

/// Call before webView.load(…): fetch the SDK once, inject it at document start.
func installRemoteDebugger(into controller: WKUserContentController,
                           userId: String?, done: @escaping () -> Void) {
    let gateway = "https://rwd.example.com/v1"
    URLSession.shared.dataTask(with: URL(string: gateway + "/sdk.js")!) { data, _, _ in
        DispatchQueue.main.async {
            defer { done() }
            guard let data, let sdk = String(data: data, encoding: .utf8) else { return }
            let options: [String: Any] = [
                "deviceId": stableDeviceId, // a UUID kept in UserDefaults
                "userId": userId ?? NSNull(),
                "helpButton": true,
            ]
            let json = String(data: try! JSONSerialization.data(withJSONObject: options), encoding: .utf8)!
            let boot = sdk + "\n;RemoteWebviewDebugger.run(\"\(gateway)\", \"rwd_pk_…\", \(json));"
            controller.addUserScript(WKUserScript(source: boot,
                                                  injectionTime: .atDocumentStart,
                                                  forMainFrameOnly: true))
        }
    }.resume()
}

07セルフホスト

Go のバイナリ 1 つ。あなたのサーバー、あなたのデータ。

ゲートウェイは管理コンソールとページ SDK を埋め込んでいます。標準で SQLite、必要なら Postgres、HTTPS は Docker Compose と Caddy で。

  • 管理コンソール

    デバイス、API キー、メンバーとロール、QR コードの招待、ポリシー、監査ログ。さらに自分のキーが埋め込まれた統合ガイドも付いています。

  • データベース

    標準では SQLite のファイル 1 つ。RWD_DATABASE_URL=postgres://… を設定すれば Postgres を使います。

  • 運用

    /v1/metrics の Prometheus メトリクス、SRI 付きでバージョンを固定した SDK の URL、/v1/healthz のヘルスチェック。

デプロイガイド

クイックスタート
# コンテナ 1 つ:管理コンソール + ゲートウェイ + SDK
docker run -d --name rwd -p 8080:8080 -v rwd-data:/data \
  -e RWD_ADMIN_PASSWORD='change-me' \
  ghcr.io/zanel1u/remote-webview-debugger:latest

# または自分でビルドして配置し、Caddy で HTTPS にする
git clone https://github.com/ZaneL1u/remote-webview-debugger
RWD_DOMAIN=rwd.example.com \
  remote-webview-debugger/deploy/platform/deploy.sh you@your-server amd64
# → 管理コンソール  https://rwd.example.com
# → ゲートウェイ    https://rwd.example.com/v1(MCP は /v1/mcp)

サーバーに必要なのは Docker だけです。ソースからビルドするには Go と Node が必要です。

08よくある質問

よく聞かれること

ユーザーは何かインストールする必要がありますか?

いりません。H5 ページでは SDK がページの一部です。アプリではネイティブコードから注入します。ユーザーに見えるのは、デバイスコードが載った小さなピルと、エージェントが接続するときの許可ダイアログだけです。

エージェントは何を見られて、何ができますか?

ユーザーが許可した範囲だけです。そのデバイスの WebView のページを、与えられたレベルで扱います。WebView の外は一切操作しません。マスクされた項目はマスクされたままで、ページの内容は信頼できないデータとしてモデルに渡されます。

エージェントが接続する前に不具合が起きました。もう追えませんか?

追えます。SDK はアプリの起動時から端末にセッションログを残します。ページ、タップ、console、リクエスト。後から接続したエージェントも get_session_log で不具合までの道筋をすべて読めます。それ以外の場面で、ログがスマートフォンから出ることはありません。

エージェントはスマートフォン上で不具合を直せますか?

修正を検証できます。advanced レベルでは、evaluate_script が動いているページにメモリ上でパッチを当て、状態を直し、関数を差し替え、再描画して、問題が消えるかをエージェントが確かめます。本当の修正はいつもどおりのリリースで出してください。端末には何も残りません。

どのエージェントで使えますか?

任意の MCP クライアントです。Claude Code、Codex、Cursor はホストされた Streamable HTTP のエンドポイントに直接つながります。stdio しか話さないクライアントは rwd mcp(同じエンドポイントへの小さなブリッジ)を使います。

どのプラットフォームに対応していますか?

最近のモバイル WebView やブラウザであれば対応します。iOS の WKWebView、Android System WebView、アプリ内ブラウザ、そしてモバイルの Safari や Chrome。デスクトップのブラウザでも動きます。

ネットワークが悪い、WebSocket が遮られる場合はどうなりますか?

ページは軽いハートビートの WebSocket を指数バックオフ付きで維持します。3 回続けて接続できなければ毎分 1 回の存在確認に切り替え、エージェントが接続したいときはゲートウェイがページを起こします。

ページが遅くなりませんか?

script タグ 1 つです。console とネットワークの記録は上限付きのリングバッファ(各 500 件、レスポンス本文は合計 5 MB まで)に入り、セッションログはバックグラウンドで書き込まれ、待機中の接続はハートビートだけをやり取りします。

eruda、vConsole、PageSpy と一緒に使えますか?

使えます。スナップショットとヒットテストはそれらのノードを飛ばし、SDK 自身の通信はそれらのネットワークログに出ません。eruda の中のタブとして収めることもできます。

上のデモは本物ですか?

SDK は本物で、手を加えていません。模擬しているのはゲートウェイだけで、あなたのブラウザの中にあります。WebSocket のシムが postMessage で SDK の接続をそこへトンネルし、スクリプト化されたエージェントが本物のゲートウェイと同じ RPC を送ります。端から端まで本物を試すなら、自分のエージェントで試してみてください。