Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions PR_BODY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
feat: CLI binary + auto-start Chrome on connect + packaging fixes

Summary
- Adds a global CLI entrypoint `chrome-devtools-mcp` so the MCP server can be installed and run via `uv tool install .` or `pipx install .` and referenced by Codex CLI (or other MCP clients) as a single command.
- Improves UX by auto-starting Chrome (with `--remote-debugging-port`) when `connect_to_browser()` is called and Chrome isn’t already running.
- Fixes packaging so all subpackages (e.g., `src.tools`) are included when installed as a tool.

Changes
- build(pyproject): add `[project.scripts]` entry `chrome-devtools-mcp = "src.main:main"`.
- deps: add `mcp[cli]` to ensure Typer-based CLI is available for the MCP tooling.
- build(setuptools): enable package discovery with `[tool.setuptools.packages.find] include = ["src*"]`.
- feat(chrome_management): `connect_to_browser(port=..., auto_start=True, headless=False, chrome_path=None, url=None)`; auto-starts Chrome if missing; ensures CDP client uses the requested port before connecting.
- feat(start_chrome): set client port before auto-connecting for consistency.
- docs(README): document auto-start usage and Codex config snippet.

Behavior notes
- Default `connect_to_browser()` now attempts to start Chrome if it isn’t already running on the requested port. To retain prior behavior, call `connect_to_browser(auto_start=False)`.
- You can pass `headless=True`, `chrome_path=...`, and `url=...` to control auto-start behavior.

Install and test
- uv: `uv tool install --force .` (or bump version and use `uv tool install .`)
- pipx: `pipx install .`
- Verify:
- `which chrome-devtools-mcp`
- `chrome-devtools-mcp` logs “Registering MCP tools…” and waits for client.

Codex CLI config example (TOML)
```toml
[mcp_servers.chrome-devtools]
command = "chrome-devtools-mcp"
args = []

[mcp_servers.chrome-devtools.env]
CHROME_DEBUG_PORT = "9222"
```

Checklist
- [x] CLI works when installed as a uv/pipx tool
- [x] Packaging includes subpackages (`src.tools`, etc.)
- [x] README updated
- [ ] Validate on Windows/Linux (tested on macOS)
- [ ] Consider adding a `--version/--help` fast-path for smoke tests

18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,22 @@ claude mcp get chrome-devtools
- **Problem**: Server doesn't start or shows as disconnected
- **Solution**: Test the command manually: `/path/to/.venv/bin/python /path/to/server.py`

### Auto-start Chrome on connect

By default, the `connect_to_browser` tool now auto-starts Chrome with
`--remote-debugging-port` if it is not already running. You can control this:

```python
# Connect (will auto-start Chrome on port 9222 if missing)
connect_to_browser()

# Disable auto-start
connect_to_browser(auto_start=False)

# Auto-start in headless mode on a custom port, opening a URL
connect_to_browser(port=9333, headless=True, url="https://example.com")
```

### Option 4: Manual Claude Desktop Setup

1. **Clone this repository**
Expand Down Expand Up @@ -740,4 +756,4 @@ Pre-commit hooks run automatically on `git commit` and can be run manually with

## License

MIT License
MIT License
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ classifiers = [
]

dependencies = [
"mcp>=1.0.0",
"mcp[cli]>=1.0.0",
"websockets>=12.0",
"aiohttp>=3.9.0",
]

[project.scripts]
# Installs a runnable CLI: `chrome-devtools-mcp`
chrome-devtools-mcp = "src.main:main"

[project.optional-dependencies]
test = [
"pytest>=8.0.0",
Expand All @@ -52,6 +56,10 @@ Issues = "https://github.com/benjaminr/chrome-devtools-mcp/issues"
[tool.hatch.build.targets.wheel]
packages = ["src"]

[tool.setuptools.packages.find]
# Include the top-level `src` package and all its subpackages
include = ["src*"]

[tool.uv]
dev-dependencies = [
"pytest>=8.0.0",
Expand Down
48 changes: 38 additions & 10 deletions src/tools/chrome_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ async def start_chrome(
# If auto_connect is enabled and Chrome is already running, connect now
if auto_connect and cdp_client:
try:
# Ensure client targets the correct port before connecting
cdp_client.port = port # type: ignore[attr-defined]
if not cdp_client.connected:
connected = await cdp_client.connect()
if connected:
Expand Down Expand Up @@ -318,6 +320,8 @@ async def start_chrome(
# Auto-connect if requested
if auto_connect and cdp_client:
try:
# Ensure client targets the correct port before connecting
cdp_client.port = port # type: ignore[attr-defined]
connected = await cdp_client.connect()
if connected:
await cdp_client.enable_domains()
Expand Down Expand Up @@ -390,7 +394,14 @@ async def start_chrome_and_connect(
return result # type: ignore

@mcp.tool()
async def connect_to_browser(port: int = 9222) -> dict[str, Any]:
async def connect_to_browser(
port: int = 9222,
*,
auto_start: bool = True,
headless: bool = False,
chrome_path: str | None = None,
url: str | None = None,
) -> dict[str, Any]:
"""Connect to an existing Chrome instance with remote debugging enabled.

Establishes a connection to a Chrome browser that's already running with
Expand All @@ -403,8 +414,11 @@ async def connect_to_browser(port: int = 9222) -> dict[str, Any]:

Args:
port: TCP port where Chrome remote debugging is listening (default: 9222).
Chrome must be started with --remote-debugging-port=PORT for this
to work.
auto_start: If True, start Chrome with remote debugging when not found
and then connect automatically.
headless: When auto-starting, whether to run Chrome in headless mode.
chrome_path: Optional explicit Chrome executable path when auto-starting.
url: Optional URL to open after connecting (used on auto-start too).

Returns:
Connection status dictionary containing:
Expand All @@ -416,9 +430,9 @@ async def connect_to_browser(port: int = 9222) -> dict[str, Any]:
- targetInfo: Browser version and capability information

Note:
If Chrome is not running on the specified port, the function returns
an error response with suggestions for starting Chrome with the correct
debugging options.
If Chrome is not running on the specified port and auto_start is True,
the server will launch Chrome with `--remote-debugging-port=PORT` and
connect to it automatically. Set auto_start=False to disable this.
"""
from .. import main

Expand All @@ -429,12 +443,26 @@ async def connect_to_browser(port: int = 9222) -> dict[str, Any]:

try:
if not await check_chrome_running(port):
return create_error_response(
f"Chrome not running on port {port}",
f"Start Chrome with: google-chrome --remote-debugging-port={port}",
if not auto_start:
return create_error_response(
f"Chrome not running on port {port}",
f"Start Chrome with: google-chrome --remote-debugging-port={port}",
)

# Attempt to start Chrome and auto-connect
start_result = await start_chrome(
port=port,
url=url,
headless=headless,
chrome_path=chrome_path,
auto_connect=True,
)
if not start_result.get("success"):
return start_result # Propagate detailed error

connected = await cdp_client.connect()
# Ensure the client uses the requested port
cdp_client.port = port # type: ignore[attr-defined]
connected = cdp_client.connected or await cdp_client.connect()
if not connected:
return create_error_response("Failed to connect to Chrome")

Expand Down
Loading