Skip to content

Commit 85f3f6b

Browse files
committed
Bump version to 1.0.2 - Fix DXT packaging with explicit version-based filename
- Update GitHub workflow to use explicit DXT output filename - Update Makefile package target to use version from manifest.json - Eliminates dependency on directory name for consistent CI/local builds Set up pre-commit hooks with ruff, mypy, and tests - Add .pre-commit-config.yaml with ruff formatting, mypy type checking, and test validation - Install pre-commit as dev dependency - Fix all type annotations in test file - Update chrome_management.py to use consistent cdp_client injection pattern - Add pre-commit targets to Makefile - Update README with pre-commit documentation - All pre-commit hooks now pass: ruff, mypy, pytest Update .gitignore and bump version to 1.0.3 - Add .gitignore entries for .dxt files and documentation artifacts - Bump version to 1.0.3 in manifest.json
1 parent 80d1f94 commit 85f3f6b

16 files changed

+346
-140
lines changed

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,12 @@ test_*.html
7474
logs/
7575

7676
# Chrome debugging
77-
chrome-debug-*
77+
chrome-debug-*
78+
79+
# Built packages
80+
*.dxt
81+
chrome-devtools-protocol-*.dxt
82+
83+
# Documentation artifacts
84+
CLAUDE.md
85+
.claude/

.pre-commit-config.yaml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
repos:
2+
# Ruff linting and formatting
3+
- repo: https://github.com/astral-sh/ruff-pre-commit
4+
rev: v0.8.4
5+
hooks:
6+
# Run the linter
7+
- id: ruff
8+
args: [--fix]
9+
types_or: [python, pyi]
10+
# Run the formatter
11+
- id: ruff-format
12+
types_or: [python, pyi]
13+
14+
# MyPy type checking
15+
- repo: https://github.com/pre-commit/mirrors-mypy
16+
rev: v1.13.0
17+
hooks:
18+
- id: mypy
19+
additional_dependencies:
20+
- mcp
21+
- fastmcp
22+
- websockets
23+
- types-requests
24+
args: [--ignore-missing-imports]
25+
26+
# Local hooks for tests and validation
27+
- repo: local
28+
hooks:
29+
# Run tests
30+
- id: pytest
31+
name: Run tests
32+
entry: uv run python -m pytest test_devtools_server.py -v
33+
language: system
34+
pass_filenames: false
35+
always_run: true
36+
stages: [pre-commit]
37+
38+
# Validate MCP server registration
39+
- id: mcp-validation
40+
name: Validate MCP server
41+
entry: uv run python test_devtools_server.py
42+
language: system
43+
pass_filenames: false
44+
always_run: true
45+
stages: [pre-commit]

Makefile

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Chrome DevTools MCP - Build Automation
22

3-
.PHONY: install test lint format clean build package check dev help
3+
.PHONY: install test lint format clean build package check dev help pre-commit
44

55
# Default target
66
help:
@@ -17,6 +17,7 @@ help:
1717
@echo " lint Run linting checks"
1818
@echo " format Format code with ruff"
1919
@echo " check Run all checks (lint + type check + test)"
20+
@echo " pre-commit Run pre-commit hooks"
2021
@echo ""
2122
@echo "Distribution:"
2223
@echo " package Build DXT extension package"
@@ -29,7 +30,7 @@ help:
2930
install:
3031
uv sync
3132

32-
dev: install
33+
dev: install install-pre-commit
3334
uv run mcp install server.py -n "Chrome DevTools MCP" --with-editable .
3435

3536
# Code quality
@@ -42,6 +43,13 @@ format:
4243
typecheck:
4344
uv run mypy src/
4445

46+
# Pre-commit hooks
47+
pre-commit:
48+
uv run pre-commit run --all-files
49+
50+
install-pre-commit:
51+
uv run pre-commit install
52+
4553
# Testing
4654
test:
4755
uv run python -m pytest test_devtools_server.py -v
@@ -58,8 +66,8 @@ test-console:
5866
test-network:
5967
uv run python -m pytest test_devtools_server.py -k "test_network" -v
6068

61-
# All checks
62-
check: lint typecheck test
69+
# All checks
70+
check: pre-commit
6371

6472
# CI test suite
6573
ci-test: install check

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,11 +654,24 @@ npx @anthropic-ai/dxt pack
654654
```bash
655655
make help # Show all commands
656656
make install # Install dependencies
657+
make dev # Setup development environment + pre-commit
657658
make check # Run all checks (lint + type + test)
659+
make pre-commit # Run pre-commit hooks manually
658660
make package # Build .dxt extension
659661
make release # Full release build
660662
```
661663

664+
### Pre-commit Hooks
665+
666+
This project uses pre-commit hooks to ensure code quality:
667+
668+
- **ruff**: Linting and formatting
669+
- **mypy**: Type checking
670+
- **pytest**: Test validation
671+
- **MCP validation**: Server registration check
672+
673+
Pre-commit hooks run automatically on `git commit` and can be run manually with `make pre-commit`.
674+
662675
## License
663676

664677
MIT License

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"dxt_version": "0.1",
33
"name": "chrome-devtools-protocol",
44
"display_name": "Chrome DevTools MCP",
5-
"version": "1.0.1",
5+
"version": "1.0.3",
66
"description": "Chrome DevTools Protocol integration for web application debugging",
77
"long_description": "This extension provides Chrome DevTools Protocol integration through a Python MCP server. It allows Claude to connect to Chrome browsers for debugging web applications, monitoring network requests, inspecting console logs, analyzing performance metrics, and manipulating DOM elements.",
88
"author": {

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ dev-dependencies = [
5858
"pytest-asyncio>=0.23.0",
5959
"ruff>=0.1.0",
6060
"mypy>=1.0.0",
61+
"pre-commit>=4.2.0",
6162
]
6263

6364
[tool.ruff]
@@ -80,4 +81,4 @@ ignore = []
8081
python_version = "3.10"
8182
warn_return_any = true
8283
warn_unused_configs = true
83-
disallow_untyped_defs = true
84+
disallow_untyped_defs = true

src/cdp_context.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,20 @@ def require_cdp_client(func: F) -> Callable[..., Awaitable[Any]]:
4545
4646
1. Imports the CDP client from the main module
4747
2. Validates that the client exists and is connected
48-
3. Passes the validated client as the first parameter to the decorated function
48+
3. Injects the validated client into kwargs as 'cdp_client'
4949
4. Returns appropriate error responses if client is unavailable
5050
5151
Args:
52-
func: The async function to decorate. Must accept cdp_client as first parameter.
52+
func: The async function to decorate. Can access cdp_client from kwargs.
5353
5454
Returns:
5555
The decorated function with automatic CDP client injection.
5656
5757
Example:
5858
```python
5959
@require_cdp_client
60-
async def get_page_title(cdp_client, **kwargs):
60+
async def get_page_title(**kwargs):
61+
cdp_client = kwargs['cdp_client']
6162
result = await cdp_client.send_command("Runtime.evaluate", {
6263
"expression": "document.title",
6364
"returnByValue": True
@@ -85,8 +86,11 @@ async def wrapper(*args: Any, **kwargs: Any) -> Any:
8586
"Not connected to browser. Please connect to Chrome first."
8687
)
8788

88-
# Call the original function with CDP client as first argument
89-
return await func(cdp_client, *args, **kwargs)
89+
# Inject CDP client into kwargs
90+
kwargs["cdp_client"] = cdp_client
91+
92+
# Call the original function with CDP client available in kwargs
93+
return await func(*args, **kwargs)
9094

9195
except ImportError:
9296
return create_error_response(
@@ -176,6 +180,7 @@ def get_cdp_client() -> Any:
176180
"""
177181
try:
178182
from . import main
183+
179184
return main.cdp_client
180185
except ImportError:
181186
return None

src/tools/chrome_management.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ async def check_chrome_running(port: int) -> bool:
133133
try:
134134
async with aiohttp.ClientSession() as session:
135135
async with session.get(f"http://localhost:{port}/json/version", timeout=2) as response:
136-
return response.status == 200
136+
return bool(response.status == 200)
137137
except Exception:
138138
return False
139139

@@ -175,6 +175,7 @@ async def start_chrome(
175175
headless: bool = False,
176176
chrome_path: str | None = None,
177177
auto_connect: bool = False,
178+
**kwargs: Any,
178179
) -> dict[str, Any]:
179180
"""Start Chrome with remote debugging enabled and optionally establish connection.
180181
@@ -349,7 +350,11 @@ async def start_chrome(
349350

350351
@mcp.tool()
351352
async def start_chrome_and_connect(
352-
url: str, port: int = 9222, headless: bool = False, chrome_path: str | None = None
353+
url: str,
354+
port: int = 9222,
355+
headless: bool = False,
356+
chrome_path: str | None = None,
357+
**kwargs: Any,
353358
) -> dict[str, Any]:
354359
"""Start Chrome with debugging, connect, and navigate to URL in one step.
355360
@@ -446,7 +451,7 @@ async def connect_to_browser(port: int = 9222) -> dict[str, Any]:
446451

447452
@mcp.tool()
448453
@require_cdp_client
449-
async def navigate_to_url(cdp_client: Any, url: str) -> dict[str, Any]:
454+
async def navigate_to_url(url: str, **kwargs: Any) -> dict[str, Any]:
450455
"""Navigate the connected browser to a specific URL.
451456
452457
Instructs the currently connected Chrome instance to navigate to the specified
@@ -472,6 +477,7 @@ async def navigate_to_url(cdp_client: Any, url: str) -> dict[str, Any]:
472477
load completion if needed.
473478
"""
474479
try:
480+
cdp_client = kwargs["cdp_client"]
475481
await cdp_client.send_command("Page.navigate", {"url": url})
476482
await asyncio.sleep(1)
477483

@@ -483,7 +489,8 @@ async def navigate_to_url(cdp_client: Any, url: str) -> dict[str, Any]:
483489
return create_error_response(f"Navigation error: {e}")
484490

485491
@mcp.tool()
486-
async def disconnect_from_browser() -> dict[str, Any]:
492+
@require_cdp_client
493+
async def disconnect_from_browser(**kwargs: Any) -> dict[str, Any]:
487494
"""Disconnect from the current browser session.
488495
489496
Cleanly terminates the connection to the Chrome browser instance while
@@ -504,14 +511,8 @@ async def disconnect_from_browser() -> dict[str, Any]:
504511
This function only disconnects the client from Chrome; it doesn't
505512
close or terminate the browser process itself.
506513
"""
507-
from .. import main
508-
509-
cdp_client = main.cdp_client
510-
511-
if not cdp_client:
512-
return create_error_response("CDP client not initialised")
513-
514514
try:
515+
cdp_client = kwargs["cdp_client"]
515516
await cdp_client.disconnect()
516517
return create_success_response(
517518
message="Disconnected from browser", data={"connected": False}
@@ -521,7 +522,8 @@ async def disconnect_from_browser() -> dict[str, Any]:
521522
return create_error_response(f"Disconnection error: {e}")
522523

523524
@mcp.tool()
524-
async def get_connection_status() -> dict[str, Any]:
525+
@require_cdp_client
526+
async def get_connection_status(**kwargs: Any) -> dict[str, Any]:
525527
"""Get the current connection status to the browser.
526528
527529
Retrieves comprehensive information about the current connection state,
@@ -548,17 +550,8 @@ async def get_connection_status() -> dict[str, Any]:
548550
This function is safe to call at any time and will not modify the
549551
connection state, only report it.
550552
"""
551-
from .. import main
552-
553-
cdp_client = main.cdp_client
554-
555-
if not cdp_client:
556-
return create_success_response(
557-
message="CDP client not initialised",
558-
data={"connected": False, "status": "not_initialised"},
559-
)
560-
561553
try:
554+
cdp_client = kwargs["cdp_client"]
562555
if cdp_client.connected:
563556
target_info = await cdp_client.get_target_info()
564557
return create_success_response(

0 commit comments

Comments
 (0)