patchright-cli 0.7.0__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/.claude-plugin/marketplace.json +1 -1
  2. patchright_cli-0.7.0/README.md → patchright_cli-0.8.0/PKG-INFO +64 -0
  3. patchright_cli-0.7.0/PKG-INFO → patchright_cli-0.8.0/README.md +28 -33
  4. patchright_cli-0.8.0/docs/mcp-architecture.svg +120 -0
  5. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/pyproject.toml +6 -1
  6. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/src/patchright_cli/__init__.py +1 -1
  7. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/src/patchright_cli/cli.py +44 -1
  8. patchright_cli-0.8.0/src/patchright_cli/mcp_server.py +1325 -0
  9. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/tests/test_daemon_handlers.py +32 -0
  10. patchright_cli-0.8.0/tests/test_mcp.py +172 -0
  11. patchright_cli-0.8.0/uv.lock +2953 -0
  12. patchright_cli-0.7.0/uv.lock +0 -1154
  13. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/.claude-plugin/skills/browser-automation.json +0 -0
  14. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/.github/workflows/ci.yml +0 -0
  15. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/.github/workflows/publish.yml +0 -0
  16. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/.gitignore +0 -0
  17. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/.pre-commit-config.yaml +0 -0
  18. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/CLAUDE.md +0 -0
  19. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/CONTRIBUTING.md +0 -0
  20. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/GLOSSARY.md +0 -0
  21. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/LICENSE +0 -0
  22. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/Makefile +0 -0
  23. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/docs/installation.md +0 -0
  24. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/patchright-cli +0 -0
  25. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/patchright-cli.cmd +0 -0
  26. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/skills/patchright-cli/SKILL.md +0 -0
  27. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/skills/patchright-cli/references/request-mocking.md +0 -0
  28. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/skills/patchright-cli/references/running-code.md +0 -0
  29. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/skills/patchright-cli/references/session-management.md +0 -0
  30. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/skills/patchright-cli/references/snapshot-refs.md +0 -0
  31. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/skills/patchright-cli/references/storage-state.md +0 -0
  32. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/skills/patchright-cli/references/tracing.md +0 -0
  33. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/skills/patchright-cli/references/video-recording.md +0 -0
  34. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/src/patchright_cli/__main__.py +0 -0
  35. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/src/patchright_cli/daemon.py +24 -24
  36. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/src/patchright_cli/dashboard.py +0 -0
  37. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/src/patchright_cli/ref_registry.py +0 -0
  38. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/src/patchright_cli/snapshot.py +0 -0
  39. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/tests/__init__.py +0 -0
  40. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/tests/fixture.html +0 -0
  41. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/tests/test_annotate.py +0 -0
  42. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/tests/test_attach.py +0 -0
  43. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/tests/test_cli_config.py +0 -0
  44. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/tests/test_e2e.py +0 -0
  45. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/tests/test_packaging.py +0 -0
  46. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/tests/test_profiles.py +0 -0
  47. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/tests/test_ref_registry.py +0 -0
  48. {patchright_cli-0.7.0 → patchright_cli-0.8.0}/tests/test_tabs.py +0 -0
@@ -5,7 +5,7 @@
5
5
  },
6
6
  "metadata": {
7
7
  "description": "Anti-detect browser automation CLI & Skills for AI agents, powered by Patchright",
8
- "version": "0.7.0"
8
+ "version": "0.8.0"
9
9
  },
10
10
  "plugins": [
11
11
  {
@@ -1,3 +1,39 @@
1
+ Metadata-Version: 2.5
2
+ Name: patchright-cli
3
+ Version: 0.8.0
4
+ Summary: Anti-detect browser automation CLI using Patchright (undetected Playwright fork)
5
+ Project-URL: Homepage, https://github.com/AhaiMk01/patchright-cli
6
+ Project-URL: Repository, https://github.com/AhaiMk01/patchright-cli
7
+ Project-URL: Issues, https://github.com/AhaiMk01/patchright-cli/issues
8
+ Author: zpahai
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ Keywords: anti-detect,automation,browser,cli,patchright,playwright,scraping
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
22
+ Classifier: Topic :: Software Development :: Testing
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: aiohttp>=3.9
25
+ Requires-Dist: click
26
+ Requires-Dist: patchright
27
+ Requires-Dist: pyyaml
28
+ Provides-Extra: dev
29
+ Requires-Dist: fastmcp<5,>=3.0; extra == 'dev'
30
+ Requires-Dist: pre-commit; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
32
+ Requires-Dist: pytest>=8.0; extra == 'dev'
33
+ Provides-Extra: mcp
34
+ Requires-Dist: fastmcp<5,>=3.0; extra == 'mcp'
35
+ Description-Content-Type: text/markdown
36
+
1
37
  # patchright-cli
2
38
 
3
39
  [![PyPI version](https://img.shields.io/pypi/v/patchright-cli?color=blue&label=PyPI)](https://pypi.org/project/patchright-cli/)
@@ -339,6 +375,34 @@ patchright-cli install --skills
339
375
 
340
376
  </details>
341
377
 
378
+ ### MCP server
379
+
380
+ MCP support is optional so the base CLI install stays small:
381
+
382
+ ```bash
383
+ pip install 'patchright-cli[mcp]'
384
+
385
+ # stdio (default)
386
+ patchright-cli mcp
387
+
388
+ # Streamable HTTP
389
+ patchright-cli mcp --http --host=127.0.0.1 --port=8000
390
+ ```
391
+
392
+ ![How a remote MCP call reaches Chrome](docs/mcp-architecture.svg)
393
+
394
+ The HTTP endpoint is `/mcp`. Set `PATCHRIGHT_CLI_MCP_TOKEN` to require `Authorization: Bearer <token>` on every request, and always set it before binding to `0.0.0.0`.
395
+
396
+ File paths passed to MCP tools (uploads, state and cookie files, screenshot/PDF/snapshot output, profile directories) must be absolute and beneath the MCP root, so a client cannot reach arbitrary host files. Outputs without a path land in `<root>/output/`, which `browser_delete_output` can clean up. `patchright_eval` and `patchright_run_code` still run arbitrary JavaScript in the page, so protect HTTP deployments regardless.
397
+
398
+ | Variable | Default | Effect |
399
+ |---|---|---|
400
+ | `PATCHRIGHT_CLI_MCP_TOKEN` | unset (no auth) | Bearer token required by the HTTP transport |
401
+ | `PATCHRIGHT_CLI_MCP_ROOT` | working directory | Root that tool file paths must sit beneath |
402
+ | `PATCHRIGHT_CLI_MCP_UNRESTRICTED_FILESYSTEM` | unset | `1` drops the path check and enables `patchright_install_skills` |
403
+ | `PATCHRIGHT_CLI_MCP_HEADLESS` | unset (headed) | `1` makes `patchright_open` default to headless, for hosts without a display |
404
+ | `PATCHRIGHT_CLI_MCP_DAEMON_PORT` | `9321` | Daemon port the tools talk to |
405
+
342
406
  ### Skill docs
343
407
 
344
408
  | File | Topic |
@@ -1,36 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: patchright-cli
3
- Version: 0.7.0
4
- Summary: Anti-detect browser automation CLI using Patchright (undetected Playwright fork)
5
- Project-URL: Homepage, https://github.com/AhaiMk01/patchright-cli
6
- Project-URL: Repository, https://github.com/AhaiMk01/patchright-cli
7
- Project-URL: Issues, https://github.com/AhaiMk01/patchright-cli/issues
8
- Author: zpahai
9
- License-Expression: Apache-2.0
10
- License-File: LICENSE
11
- Keywords: anti-detect,automation,browser,cli,patchright,playwright,scraping
12
- Classifier: Development Status :: 4 - Beta
13
- Classifier: Environment :: Console
14
- Classifier: Intended Audience :: Developers
15
- Classifier: License :: OSI Approved :: Apache Software License
16
- Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.10
18
- Classifier: Programming Language :: Python :: 3.11
19
- Classifier: Programming Language :: Python :: 3.12
20
- Classifier: Programming Language :: Python :: 3.13
21
- Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
22
- Classifier: Topic :: Software Development :: Testing
23
- Requires-Python: >=3.10
24
- Requires-Dist: aiohttp>=3.9
25
- Requires-Dist: click
26
- Requires-Dist: patchright
27
- Requires-Dist: pyyaml
28
- Provides-Extra: dev
29
- Requires-Dist: pre-commit; extra == 'dev'
30
- Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
31
- Requires-Dist: pytest>=8.0; extra == 'dev'
32
- Description-Content-Type: text/markdown
33
-
34
1
  # patchright-cli
35
2
 
36
3
  [![PyPI version](https://img.shields.io/pypi/v/patchright-cli?color=blue&label=PyPI)](https://pypi.org/project/patchright-cli/)
@@ -372,6 +339,34 @@ patchright-cli install --skills
372
339
 
373
340
  </details>
374
341
 
342
+ ### MCP server
343
+
344
+ MCP support is optional so the base CLI install stays small:
345
+
346
+ ```bash
347
+ pip install 'patchright-cli[mcp]'
348
+
349
+ # stdio (default)
350
+ patchright-cli mcp
351
+
352
+ # Streamable HTTP
353
+ patchright-cli mcp --http --host=127.0.0.1 --port=8000
354
+ ```
355
+
356
+ ![How a remote MCP call reaches Chrome](docs/mcp-architecture.svg)
357
+
358
+ The HTTP endpoint is `/mcp`. Set `PATCHRIGHT_CLI_MCP_TOKEN` to require `Authorization: Bearer <token>` on every request, and always set it before binding to `0.0.0.0`.
359
+
360
+ File paths passed to MCP tools (uploads, state and cookie files, screenshot/PDF/snapshot output, profile directories) must be absolute and beneath the MCP root, so a client cannot reach arbitrary host files. Outputs without a path land in `<root>/output/`, which `browser_delete_output` can clean up. `patchright_eval` and `patchright_run_code` still run arbitrary JavaScript in the page, so protect HTTP deployments regardless.
361
+
362
+ | Variable | Default | Effect |
363
+ |---|---|---|
364
+ | `PATCHRIGHT_CLI_MCP_TOKEN` | unset (no auth) | Bearer token required by the HTTP transport |
365
+ | `PATCHRIGHT_CLI_MCP_ROOT` | working directory | Root that tool file paths must sit beneath |
366
+ | `PATCHRIGHT_CLI_MCP_UNRESTRICTED_FILESYSTEM` | unset | `1` drops the path check and enables `patchright_install_skills` |
367
+ | `PATCHRIGHT_CLI_MCP_HEADLESS` | unset (headed) | `1` makes `patchright_open` default to headless, for hosts without a display |
368
+ | `PATCHRIGHT_CLI_MCP_DAEMON_PORT` | `9321` | Daemon port the tools talk to |
369
+
375
370
  ### Skill docs
376
371
 
377
372
  | File | Topic |
@@ -0,0 +1,120 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="470" viewBox="0 0 1200 470" role="img" aria-label="A remote MCP client sends tools/call over HTTP across a network boundary guarded by an optional Bearer token to the patchright-cli MCP server on the host. The server spawns one short-lived CLI process per call, which sends length-prefixed JSON over TCP to the long-lived daemon. The daemon drives real Chrome over CDP; headed Chrome draws on an Xvfb display and loads websites over HTTPS. Output files land in the root's output directory on the host.">
2
+ <style>
3
+ svg { --bg: #ffffff; --fg: #1b2230; --muted: #5d6778; --line: #c9cfd9; --accent: #c2410c; --host: #eef1f6;
4
+ color: var(--fg); font-family: -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; }
5
+ @media (prefers-color-scheme: dark) {
6
+ svg { --bg: #0d1117; --fg: #e6edf3; --muted: #9aa4b4; --line: #3a4352; --accent: #fb923c; --host: #161b22; }
7
+ }
8
+ .bg { fill: var(--bg); }
9
+ .box { fill: var(--bg); stroke: currentColor; stroke-width: 1.4; }
10
+ .ghost { fill: var(--bg); stroke: var(--line); stroke-width: 1.2; }
11
+ .hostbg { fill: var(--host); stroke: var(--line); stroke-width: 1.2; }
12
+ .t { fill: currentColor; font-size: 14px; font-weight: 600; text-anchor: middle; }
13
+ .s { fill: var(--muted); font-size: 11.5px; text-anchor: middle; }
14
+ .m { fill: var(--muted); font-family: ui-monospace, Consolas, monospace; font-size: 11px; text-anchor: middle; }
15
+ .lbl { fill: currentColor; font-size: 12px; text-anchor: middle; paint-order: stroke; stroke: var(--bg); stroke-width: 5px; stroke-linejoin: round; }
16
+ .lbl.start { text-anchor: start; }
17
+ .region { fill: var(--muted); font-family: ui-monospace, Consolas, monospace; font-size: 11px; letter-spacing: 0.05em; text-transform: uppercase; }
18
+ .edge { stroke: currentColor; stroke-width: 1.4; fill: none; }
19
+ .edge.dim { stroke: var(--muted); stroke-dasharray: 4 3; }
20
+ .boundary { stroke: var(--accent); stroke-width: 1.6; stroke-dasharray: 6 5; }
21
+ .warn { fill: var(--accent); font-size: 12px; font-weight: 600; text-anchor: middle; paint-order: stroke; stroke: var(--bg); stroke-width: 5px; }
22
+ .head { fill: currentColor; }
23
+ .head.dim { fill: var(--muted); }
24
+ </style>
25
+ <rect class="bg" x="0" y="0" width="1200" height="470"/>
26
+ <defs>
27
+ <marker id="ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
28
+ <path class="head" d="M0,0 L10,5 L0,10 z"/>
29
+ </marker>
30
+ <marker id="ahd" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
31
+ <path class="head dim" d="M0,0 L10,5 L0,10 z"/>
32
+ </marker>
33
+ </defs>
34
+
35
+ <!-- host region -->
36
+ <rect class="hostbg" x="250" y="30" width="820" height="420" rx="10"/>
37
+ <text class="region" x="268" y="54">Host · container or server</text>
38
+ <text class="region" x="20" y="54">Remote machine</text>
39
+ <text class="region" x="1100" y="54">Internet</text>
40
+
41
+ <!-- trust boundary -->
42
+ <line class="boundary" x1="205" y1="24" x2="205" y2="456"/>
43
+ <text class="warn" x="205" y="300">trust boundary</text>
44
+ <text class="warn" x="205" y="317" style="font-weight:400">optional Bearer token</text>
45
+
46
+ <!-- client -->
47
+ <rect class="box" x="20" y="170" width="140" height="80" rx="6"/>
48
+ <text class="t" x="90" y="205">MCP client</text>
49
+ <text class="s" x="90" y="225">Claude Code, agents</text>
50
+
51
+ <!-- client <-> server -->
52
+ <line class="edge" x1="160" y1="195" x2="268" y2="195" marker-end="url(#ah)"/>
53
+ <text class="lbl" x="215" y="187">tools/call</text>
54
+ <line class="edge" x1="270" y1="228" x2="162" y2="228" marker-end="url(#ah)"/>
55
+ <text class="lbl" x="215" y="246">text · PNG · YAML</text>
56
+
57
+ <!-- MCP server -->
58
+ <rect class="box" x="270" y="170" width="170" height="80" rx="6"/>
59
+ <text class="t" x="355" y="198">MCP server</text>
60
+ <text class="m" x="355" y="217">mcp --http :8000/mcp</text>
61
+ <text class="s" x="355" y="236">checks session, paths</text>
62
+
63
+ <!-- server -> CLI -->
64
+ <line class="edge" x1="440" y1="210" x2="518" y2="210" marker-end="url(#ah)"/>
65
+ <text class="lbl" x="479" y="202">spawns</text>
66
+
67
+ <!-- CLI process stack -->
68
+ <rect class="ghost" x="536" y="158" width="160" height="80" rx="6"/>
69
+ <rect class="ghost" x="528" y="164" width="160" height="80" rx="6"/>
70
+ <rect class="box" x="520" y="170" width="160" height="80" rx="6"/>
71
+ <text class="t" x="600" y="198">CLI process</text>
72
+ <text class="m" x="600" y="217">python -m patchright_cli</text>
73
+ <text class="s" x="600" y="236">one per call, exits</text>
74
+
75
+ <!-- CLI -> daemon -->
76
+ <line class="edge" x1="696" y1="210" x2="778" y2="210" marker-end="url(#ah)"/>
77
+ <text class="lbl" x="737" y="202">TCP · JSON</text>
78
+ <text class="s" x="737" y="229">starts it</text>
79
+ <text class="s" x="737" y="243">if absent</text>
80
+
81
+ <!-- daemon -->
82
+ <rect class="box" x="780" y="170" width="120" height="80" rx="6"/>
83
+ <text class="t" x="840" y="198">Daemon</text>
84
+ <text class="m" x="840" y="217">127.0.0.1:9321</text>
85
+ <text class="s" x="840" y="236">long-lived</text>
86
+
87
+ <!-- daemon -> chrome -->
88
+ <line class="edge" x1="900" y1="210" x2="948" y2="210" marker-end="url(#ah)"/>
89
+ <text class="lbl" x="924" y="202">CDP</text>
90
+
91
+ <!-- chrome -->
92
+ <rect class="box" x="950" y="170" width="100" height="80" rx="6"/>
93
+ <text class="t" x="1000" y="205">Chrome</text>
94
+ <text class="s" x="1000" y="225">headed or headless</text>
95
+
96
+ <!-- xvfb -->
97
+ <rect class="ghost" x="950" y="72" width="100" height="48" rx="6"/>
98
+ <text class="t" x="1000" y="96" style="font-weight:500">Xvfb :99</text>
99
+ <text class="s" x="1000" y="112">headed only</text>
100
+ <line class="edge dim" x1="1000" y1="120" x2="1000" y2="168" marker-end="url(#ahd)"/>
101
+ <text class="lbl start" x="1008" y="149" style="fill:var(--muted)">DISPLAY</text>
102
+
103
+ <!-- chrome -> web -->
104
+ <line class="edge" x1="1050" y1="210" x2="1098" y2="210" marker-end="url(#ah)"/>
105
+ <text class="lbl" x="1075" y="202">HTTPS</text>
106
+ <rect class="box" x="1100" y="170" width="80" height="80" rx="6"/>
107
+ <text class="t" x="1140" y="214">Websites</text>
108
+
109
+ <!-- output dir -->
110
+ <rect class="box" x="520" y="350" width="380" height="60" rx="6"/>
111
+ <text class="m" x="710" y="375" style="fill:currentColor;font-size:12px">&lt;root&gt;/output/</text>
112
+ <text class="s" x="710" y="395">screenshots · PDFs · snapshots · state files</text>
113
+
114
+ <line class="edge" x1="840" y1="250" x2="840" y2="348" marker-end="url(#ah)"/>
115
+ <text class="lbl start" x="848" y="304">writes files</text>
116
+ <line class="edge dim" x1="600" y1="252" x2="600" y2="348" marker-end="url(#ahd)"/>
117
+ <text class="lbl start" x="608" y="304" style="fill:var(--muted)">cwd</text>
118
+ <polyline class="edge dim" points="355,250 355,380 518,380" marker-end="url(#ahd)"/>
119
+ <text class="lbl" x="437" y="372" style="fill:var(--muted)">delete_output</text>
120
+ </svg>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "patchright-cli"
3
- version = "0.7.0"
3
+ version = "0.8.0"
4
4
  description = "Anti-detect browser automation CLI using Patchright (undetected Playwright fork)"
5
5
  license = "Apache-2.0"
6
6
  requires-python = ">=3.10"
@@ -54,16 +54,21 @@ markers = [
54
54
  ]
55
55
 
56
56
  [project.optional-dependencies]
57
+ mcp = [
58
+ "fastmcp>=3.0,<5",
59
+ ]
57
60
  dev = [
58
61
  "pytest>=8.0",
59
62
  "pytest-asyncio>=0.23",
60
63
  "pre-commit",
64
+ "fastmcp>=3.0,<5",
61
65
  ]
62
66
 
63
67
  [dependency-groups]
64
68
  dev = [
65
69
  "pytest>=8.0",
66
70
  "pytest-asyncio>=0.23",
71
+ "fastmcp>=3.0,<5",
67
72
  ]
68
73
 
69
74
  [tool.ruff]
@@ -1,3 +1,3 @@
1
1
  """patchright-cli — Undetected browser automation CLI using Patchright."""
2
2
 
3
- __version__ = "0.7.0"
3
+ __version__ = "0.8.0"
@@ -6,6 +6,7 @@ receives the result, and prints it.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import argparse
9
10
  import json
10
11
  import os
11
12
  import socket
@@ -248,6 +249,36 @@ def _handle_install(args: list) -> None:
248
249
  click.echo(f"\npatchright-cli v{__version__} skills installed.")
249
250
 
250
251
 
252
+ def _handle_mcp(args: list[str]) -> None:
253
+ """Handle the optional MCP server command."""
254
+ parser = argparse.ArgumentParser(
255
+ prog="patchright-cli mcp",
256
+ description="Run the MCP server. Stdio is the default transport.",
257
+ )
258
+ parser.add_argument("--http", action="store_true", help="Use Streamable HTTP instead of stdio.")
259
+ parser.add_argument("--host", default="127.0.0.1", help="HTTP bind host.")
260
+ parser.add_argument("--port", type=int, default=8000, help="HTTP bind port.")
261
+ options = parser.parse_args(args)
262
+
263
+ if not 1 <= options.port <= 65535:
264
+ parser.error("--port must be between 1 and 65535")
265
+ if not options.http and (options.host != "127.0.0.1" or options.port != 8000):
266
+ parser.error("--host and --port require --http")
267
+
268
+ try:
269
+ from patchright_cli.mcp_server import run_mcp_server
270
+ except ImportError as exc:
271
+ if exc.name not in {"fastmcp", "mcp", "pydantic"}:
272
+ raise
273
+ click.echo(
274
+ "MCP support is not installed. Install it with: pip install 'patchright-cli[mcp]'",
275
+ err=True,
276
+ )
277
+ raise SystemExit(1) from exc
278
+
279
+ run_mcp_server(http=options.http, host=options.host, port=options.port)
280
+
281
+
251
282
  _VERSION_CHECK_INTERVAL = 24 * 60 * 60
252
283
  _PYPI_URL = "https://pypi.org/pypi/patchright-cli/json"
253
284
 
@@ -510,6 +541,7 @@ COMMANDS_HELP = {
510
541
  "codegen-stop": "codegen-stop [file] Stop recording and save script",
511
542
  # Setup
512
543
  "install": "install --skills Install skill files for AI agents",
544
+ "mcp": "mcp [--http] [--host=HOST] [--port=N] Start MCP server (stdio by default)",
513
545
  }
514
546
 
515
547
  ALL_COMMANDS = list(COMMANDS_HELP.keys())
@@ -616,7 +648,7 @@ def _print_help():
616
648
  ("Profiles", ["profile-list", "profile-delete"]),
617
649
  ("Dashboard", ["show"]),
618
650
  ("Codegen", ["codegen", "codegen-stop"]),
619
- ("Setup", ["install"]),
651
+ ("Setup", ["install", "mcp"]),
620
652
  ]
621
653
  for cat_name, cmds in categories:
622
654
  click.echo(f"\n {cat_name}:")
@@ -630,6 +662,13 @@ def main():
630
662
  _soften_output_encoding(sys.stdout, sys.stderr)
631
663
  argv = sys.argv[1:]
632
664
 
665
+ # MCP is a self-contained subcommand with its own transport options.
666
+ # Dispatch it before global option parsing so `patchright-cli mcp --help`
667
+ # shows MCP help rather than the top-level CLI help.
668
+ if argv and argv[0] == "mcp":
669
+ _handle_mcp(argv[1:])
670
+ return
671
+
633
672
  # Parse global options manually (before the command)
634
673
  headless = False
635
674
  persistent = False
@@ -779,6 +818,10 @@ def main():
779
818
  _handle_install(args + [k if v is True else f"{k}={v}" for k, v in extra_opts.items()])
780
819
  sys.exit(0)
781
820
 
821
+ if command == "mcp":
822
+ _handle_mcp(args)
823
+ sys.exit(0)
824
+
782
825
  _warn_stale_skills()
783
826
 
784
827
  # Only on `open`: one browser launch already costs seconds, and this keeps