diff --git a/home/modules/browser/tests/chrome-devtools-mcp-connection.bats b/home/modules/browser/tests/chrome-devtools-mcp-connection.bats new file mode 100644 index 00000000..5174f775 --- /dev/null +++ b/home/modules/browser/tests/chrome-devtools-mcp-connection.bats @@ -0,0 +1,60 @@ +#!/usr/bin/env bats + +readonly CHROME_USER_DATA_DIR="$HOME/.config/google-chrome" +readonly DEVTOOLS_PORT_FILE="$CHROME_USER_DATA_DIR/DevToolsActivePort" +readonly MCP_BINARY="$HOME/.local/share/chrome-devtools-mcp-npm/bin/chrome-devtools-mcp" +readonly CHROME_LOCAL_STATE="$CHROME_USER_DATA_DIR/Local State" +readonly CHROME_POLICY_FILE="$CHROME_USER_DATA_DIR/policies/managed/agent-browser-control.json" + +@test "DevToolsActivePort file exists when Chrome is running" { + [[ -f "$DEVTOOLS_PORT_FILE" ]] +} + +@test "DevToolsActivePort has port on first line" { + port=$(head -1 "$DEVTOOLS_PORT_FILE") + [[ "$port" =~ ^[0-9]+$ ]] +} + +@test "DevToolsActivePort has WebSocket path on second line" { + ws_path=$(tail -1 "$DEVTOOLS_PORT_FILE") + [[ "$ws_path" == /devtools/browser/* ]] +} + +@test "chrome-devtools-mcp binary exists and is executable" { + [[ -x "$MCP_BINARY" ]] +} + +@test "Chrome remote debugging toggle is enabled in Local State" { + value=$(jq -r '.devtools.remote_debugging["user-enabled"]' "$CHROME_LOCAL_STATE") + [[ "$value" == "true" ]] +} + +@test "Chrome enterprise policy file deployed" { + [[ -f "$CHROME_POLICY_FILE" ]] + value=$(jq -r '.RemoteDebuggingAllowed' "$CHROME_POLICY_FILE") + [[ "$value" == "true" ]] +} + +@test "MCP server connects and initializes via wsEndpoint" { + port=$(head -1 "$DEVTOOLS_PORT_FILE") + ws_path=$(tail -1 "$DEVTOOLS_PORT_FILE") + ws_url="ws://127.0.0.1:${port}${ws_path}" + + result=$(echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}' \ + | timeout 10 "$MCP_BINARY" --wsEndpoint "$ws_url" --usageStatistics false 2>/dev/null \ + | head -1) + + echo "$result" | jq -e '.result.serverInfo.name == "chrome_devtools"' +} + +@test "MCP server can list pages" { + port=$(head -1 "$DEVTOOLS_PORT_FILE") + ws_path=$(tail -1 "$DEVTOOLS_PORT_FILE") + ws_url="ws://127.0.0.1:${port}${ws_path}" + + result=$( (echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}'; sleep 3; echo '{"jsonrpc":"2.0","method":"notifications/initialized"}'; sleep 1; echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_pages","arguments":{}}}'; sleep 3) \ + | timeout 15 "$MCP_BINARY" --wsEndpoint "$ws_url" --usageStatistics false 2>/dev/null \ + | tail -1) + + echo "$result" | jq -e '.result.content[0].text' | grep -q "Pages" +} diff --git a/home/modules/claude/mcp.nix b/home/modules/claude/mcp.nix index 2803f2e0..19488386 100644 --- a/home/modules/claude/mcp.nix +++ b/home/modules/claude/mcp.nix @@ -29,6 +29,13 @@ let --registry "https://registry.npmjs.org/" ''; + autoAcceptChromeDebuggingDialog = pkgs.writeShellScript "auto-accept-chrome-debugging-dialog" '' + sleep 2 + ${pkgs.hyprland}/bin/hyprctl dispatch focuswindow class:google-chrome 2>/dev/null || true + sleep 1 + ${pkgs.wtype}/bin/wtype -k Return 2>/dev/null || true + ''; + chromeDevtoolsMcpAutoconnectWrapper = pkgs.writeShellScriptBin "chrome-devtools-mcp-autoconnect" '' set -euo pipefail export PATH="${nodejs}/bin:''${PATH:+:$PATH}" @@ -44,6 +51,8 @@ let CHROME_WS_PATH=$(tail -1 "$DEVTOOLS_PORT_FILE") CHROME_WS_URL="ws://127.0.0.1:''${CHROME_PORT}''${CHROME_WS_PATH}" + ${autoAcceptChromeDebuggingDialog} & + exec "${chromeDevtoolsMcpBinary}" \ --wsEndpoint "$CHROME_WS_URL" \ --usageStatistics false \ @@ -95,7 +104,10 @@ let ''; in { - home.packages = [ latest.google-chrome ]; + home.packages = [ + latest.google-chrome + pkgs.wtype + ]; home.file.".config/google-chrome/policies/managed/agent-browser-control.json".text = builtins.toJSON diff --git a/home/modules/claude/tests/checks.nix b/home/modules/claude/tests/checks.nix index 5ec08f6c..fd58fdb1 100644 --- a/home/modules/claude/tests/checks.nix +++ b/home/modules/claude/tests/checks.nix @@ -52,9 +52,10 @@ in mkEvalCheck "claude-skills-directory" (hasFilePrefix ".claude/skills/") "skills directory entries should be in home.file"; - claude-mcp-json = - mkEvalCheck "claude-mcp-json" (builtins.hasAttr ".claude/mcp.json" cfg.home.file) - "mcp.json should be in home.file"; + claude-chrome-policy = + mkEvalCheck "claude-chrome-policy" + (builtins.hasAttr ".config/google-chrome/policies/managed/agent-browser-control.json" cfg.home.file) + "Chrome enterprise policy for remote debugging should be deployed"; claude-bin-wrapper = mkEvalCheck "claude-bin-wrapper" (builtins.hasAttr ".local/bin/claude" cfg.home.file)