dryhack-mcp 2.1.2__tar.gz → 2.2.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.
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/PKG-INFO +71 -2
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/README.md +67 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp/__init__.py +1 -1
- dryhack_mcp-2.2.0/dryhack_mcp/server.py +189 -0
- dryhack_mcp-2.2.0/dryhack_mcp/tools/browser_tool.py +652 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp.egg-info/PKG-INFO +71 -2
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp.egg-info/SOURCES.txt +3 -1
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp.egg-info/requires.txt +3 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/pyproject.toml +3 -2
- dryhack_mcp-2.2.0/tests/test_browser.py +97 -0
- dryhack_mcp-2.1.2/dryhack_mcp/server.py +0 -77
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/LICENSE +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp/__main__.py +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp/config.py +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp/tools/__init__.py +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp/tools/authorize.py +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp/tools/curl_tool.py +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp/tools/exec.py +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp/tools/python_tool.py +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp/tools/scope.py +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp/tools/shell_tool.py +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp.egg-info/dependency_links.txt +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp.egg-info/entry_points.txt +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/dryhack_mcp.egg-info/top_level.txt +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/setup.cfg +0 -0
- {dryhack_mcp-2.1.2 → dryhack_mcp-2.2.0}/tests/test_authorize.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dryhack-mcp
|
|
3
|
-
Version: 2.
|
|
4
|
-
Summary: DryHack-MCP - an MCP server providing offensive-security tooling (curl, python, shell) and
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: DryHack-MCP - an MCP server providing offensive-security tooling (curl, python, shell, Playwright browser) and a scope-gated authorize engine for authorized penetration testing.
|
|
5
5
|
Author: Afrizal F.A
|
|
6
6
|
License: MIT License
|
|
7
7
|
|
|
@@ -39,6 +39,8 @@ Requires-Dist: mcp<2,>=1.2.0
|
|
|
39
39
|
Provides-Extra: dev
|
|
40
40
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
41
41
|
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
42
|
+
Provides-Extra: browser
|
|
43
|
+
Requires-Dist: playwright>=1.40; extra == "browser"
|
|
42
44
|
Dynamic: license-file
|
|
43
45
|
|
|
44
46
|
# DryHack-MCP
|
|
@@ -51,6 +53,7 @@ tooling for **authorized** penetration testing. It exposes four tools:
|
|
|
51
53
|
| `curl` | Raw HTTP interaction for web recon/exploitation |
|
|
52
54
|
| `python` | Run ad-hoc Python snippets for scripted probing |
|
|
53
55
|
| `shell` | Run shell commands (`nmap`, `ffuf`, `nc`, `sqlmap`, …) |
|
|
56
|
+
| `browser` | Full Playwright browser automation: navigation, DOM interaction, JavaScript injection, screenshots/PDF, cookies/headers, multi-step chains |
|
|
54
57
|
| `authorize` | Checks target membership in the per-call `scope` parameter; does not verify permission (no external API, no creds) |
|
|
55
58
|
|
|
56
59
|
> ⚠️ **Legal notice.** Use this only against systems you own or are explicitly
|
|
@@ -83,6 +86,19 @@ For development:
|
|
|
83
86
|
python3 -m pip install -e ".[dev]"
|
|
84
87
|
```
|
|
85
88
|
|
|
89
|
+
### Browser tool (Playwright)
|
|
90
|
+
|
|
91
|
+
The `browser` tool needs the optional `browser` extra plus the Playwright
|
|
92
|
+
browser binaries:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python3 -m pip install 'dryhack-mcp[browser]'
|
|
96
|
+
playwright install # or: playwright install chromium
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
If Playwright is not installed, the `browser` tool returns an install hint
|
|
100
|
+
instead of failing — the rest of the server keeps working.
|
|
101
|
+
|
|
86
102
|
## Run
|
|
87
103
|
|
|
88
104
|
Two transports are supported. Select with `--transport` (argparse).
|
|
@@ -171,6 +187,59 @@ Start the server separately with `dryhack-mcp --transport http`.
|
|
|
171
187
|
> The server stores **no credentials/API keys**. All settings above are
|
|
172
188
|
> operational only.
|
|
173
189
|
|
|
190
|
+
## browser (Playwright automation)
|
|
191
|
+
|
|
192
|
+
Drive a real browser (Chromium/Firefox/WebKit) for web recon and exploitation.
|
|
193
|
+
Run a single `action` (with its relevant params) **or** a `steps` list of
|
|
194
|
+
`{"action": ..., ...}` dicts executed in order within one browser session.
|
|
195
|
+
|
|
196
|
+
Supported actions:
|
|
197
|
+
|
|
198
|
+
- **Navigation:** `goto`/`navigate`, `reload`, `back`, `forward`
|
|
199
|
+
- **Interaction:** `click`, `dblclick`, `fill`, `type`, `press`, `hover`,
|
|
200
|
+
`focus`, `check`, `uncheck`, `select_option`, `upload`, `mouse_click`,
|
|
201
|
+
`keyboard_type`, `scroll`
|
|
202
|
+
- **Waiting:** `wait_for_selector`, `wait_for_timeout`, `wait_for_load_state`,
|
|
203
|
+
`wait_for_url`
|
|
204
|
+
- **JavaScript injection:** `evaluate`/`eval` (with `script` + optional `arg`),
|
|
205
|
+
`evaluate_handle`, `add_init_script` (runs before every document's own
|
|
206
|
+
scripts), `add_script_tag`, `add_style_tag`
|
|
207
|
+
- **Extraction:** `content`, `inner_text`, `inner_html`, `text_content`,
|
|
208
|
+
`get_attribute`, `query_all`, `title`, `url`
|
|
209
|
+
- **Capture:** `screenshot` (`path`, `full_page`), `pdf`
|
|
210
|
+
- **Session/context:** `set_viewport`, `set_extra_headers`, `get_cookies`,
|
|
211
|
+
`set_cookies`, `clear_cookies`, `storage_state`, `emulate_media`
|
|
212
|
+
|
|
213
|
+
Session-level options: `browser_type` (chromium/firefox/webkit), `headless`,
|
|
214
|
+
`user_agent`, `viewport`, `extra_http_headers`, `cookies`, `proxy`, `locale`,
|
|
215
|
+
`timezone`, `geolocation`, `device_scale_factor`, `ignore_https_errors`,
|
|
216
|
+
`bypass_csp`, `java_script_enabled`, `init_scripts`, `slow_mo`, `args`,
|
|
217
|
+
`executable_path`, `timeout`, and `stop_on_error`.
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
# Single action: run JavaScript in the page
|
|
221
|
+
browser(action="evaluate", url=None, script="() => document.cookie")
|
|
222
|
+
|
|
223
|
+
# Multi-step session: login, inject JS, screenshot
|
|
224
|
+
browser(steps=[
|
|
225
|
+
{"action": "goto", "url": "https://staging.lab.example.com/login"},
|
|
226
|
+
{"action": "fill", "selector": "#user", "value": "admin"},
|
|
227
|
+
{"action": "fill", "selector": "#pass", "value": "s3cret"},
|
|
228
|
+
{"action": "click", "selector": "button[type=submit]"},
|
|
229
|
+
{"action": "wait_for_load_state", "state": "networkidle"},
|
|
230
|
+
{"action": "evaluate", "script": "() => localStorage.getItem('token')"},
|
|
231
|
+
{"action": "screenshot", "path": "/tmp/after-login.png", "full_page": True},
|
|
232
|
+
], headless=True, user_agent="DryHack", extra_http_headers={"X-Test": "1"})
|
|
233
|
+
|
|
234
|
+
# Inject a payload before any page script runs
|
|
235
|
+
browser(action="goto", url="https://target.example/",
|
|
236
|
+
init_script="Object.defineProperty(navigator,'webdriver',{get:()=>false})")
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Playwright runs in a dedicated worker thread (the MCP server owns the asyncio
|
|
240
|
+
loop, and Playwright's sync API cannot run inside a live loop). The same
|
|
241
|
+
authorized-use scope rules as the other tools apply.
|
|
242
|
+
|
|
174
243
|
## authorize (per-call scope)
|
|
175
244
|
|
|
176
245
|
Pass `scope` directly to the tool; no environment or native scope configuration
|
|
@@ -8,6 +8,7 @@ tooling for **authorized** penetration testing. It exposes four tools:
|
|
|
8
8
|
| `curl` | Raw HTTP interaction for web recon/exploitation |
|
|
9
9
|
| `python` | Run ad-hoc Python snippets for scripted probing |
|
|
10
10
|
| `shell` | Run shell commands (`nmap`, `ffuf`, `nc`, `sqlmap`, …) |
|
|
11
|
+
| `browser` | Full Playwright browser automation: navigation, DOM interaction, JavaScript injection, screenshots/PDF, cookies/headers, multi-step chains |
|
|
11
12
|
| `authorize` | Checks target membership in the per-call `scope` parameter; does not verify permission (no external API, no creds) |
|
|
12
13
|
|
|
13
14
|
> ⚠️ **Legal notice.** Use this only against systems you own or are explicitly
|
|
@@ -40,6 +41,19 @@ For development:
|
|
|
40
41
|
python3 -m pip install -e ".[dev]"
|
|
41
42
|
```
|
|
42
43
|
|
|
44
|
+
### Browser tool (Playwright)
|
|
45
|
+
|
|
46
|
+
The `browser` tool needs the optional `browser` extra plus the Playwright
|
|
47
|
+
browser binaries:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python3 -m pip install 'dryhack-mcp[browser]'
|
|
51
|
+
playwright install # or: playwright install chromium
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If Playwright is not installed, the `browser` tool returns an install hint
|
|
55
|
+
instead of failing — the rest of the server keeps working.
|
|
56
|
+
|
|
43
57
|
## Run
|
|
44
58
|
|
|
45
59
|
Two transports are supported. Select with `--transport` (argparse).
|
|
@@ -128,6 +142,59 @@ Start the server separately with `dryhack-mcp --transport http`.
|
|
|
128
142
|
> The server stores **no credentials/API keys**. All settings above are
|
|
129
143
|
> operational only.
|
|
130
144
|
|
|
145
|
+
## browser (Playwright automation)
|
|
146
|
+
|
|
147
|
+
Drive a real browser (Chromium/Firefox/WebKit) for web recon and exploitation.
|
|
148
|
+
Run a single `action` (with its relevant params) **or** a `steps` list of
|
|
149
|
+
`{"action": ..., ...}` dicts executed in order within one browser session.
|
|
150
|
+
|
|
151
|
+
Supported actions:
|
|
152
|
+
|
|
153
|
+
- **Navigation:** `goto`/`navigate`, `reload`, `back`, `forward`
|
|
154
|
+
- **Interaction:** `click`, `dblclick`, `fill`, `type`, `press`, `hover`,
|
|
155
|
+
`focus`, `check`, `uncheck`, `select_option`, `upload`, `mouse_click`,
|
|
156
|
+
`keyboard_type`, `scroll`
|
|
157
|
+
- **Waiting:** `wait_for_selector`, `wait_for_timeout`, `wait_for_load_state`,
|
|
158
|
+
`wait_for_url`
|
|
159
|
+
- **JavaScript injection:** `evaluate`/`eval` (with `script` + optional `arg`),
|
|
160
|
+
`evaluate_handle`, `add_init_script` (runs before every document's own
|
|
161
|
+
scripts), `add_script_tag`, `add_style_tag`
|
|
162
|
+
- **Extraction:** `content`, `inner_text`, `inner_html`, `text_content`,
|
|
163
|
+
`get_attribute`, `query_all`, `title`, `url`
|
|
164
|
+
- **Capture:** `screenshot` (`path`, `full_page`), `pdf`
|
|
165
|
+
- **Session/context:** `set_viewport`, `set_extra_headers`, `get_cookies`,
|
|
166
|
+
`set_cookies`, `clear_cookies`, `storage_state`, `emulate_media`
|
|
167
|
+
|
|
168
|
+
Session-level options: `browser_type` (chromium/firefox/webkit), `headless`,
|
|
169
|
+
`user_agent`, `viewport`, `extra_http_headers`, `cookies`, `proxy`, `locale`,
|
|
170
|
+
`timezone`, `geolocation`, `device_scale_factor`, `ignore_https_errors`,
|
|
171
|
+
`bypass_csp`, `java_script_enabled`, `init_scripts`, `slow_mo`, `args`,
|
|
172
|
+
`executable_path`, `timeout`, and `stop_on_error`.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
# Single action: run JavaScript in the page
|
|
176
|
+
browser(action="evaluate", url=None, script="() => document.cookie")
|
|
177
|
+
|
|
178
|
+
# Multi-step session: login, inject JS, screenshot
|
|
179
|
+
browser(steps=[
|
|
180
|
+
{"action": "goto", "url": "https://staging.lab.example.com/login"},
|
|
181
|
+
{"action": "fill", "selector": "#user", "value": "admin"},
|
|
182
|
+
{"action": "fill", "selector": "#pass", "value": "s3cret"},
|
|
183
|
+
{"action": "click", "selector": "button[type=submit]"},
|
|
184
|
+
{"action": "wait_for_load_state", "state": "networkidle"},
|
|
185
|
+
{"action": "evaluate", "script": "() => localStorage.getItem('token')"},
|
|
186
|
+
{"action": "screenshot", "path": "/tmp/after-login.png", "full_page": True},
|
|
187
|
+
], headless=True, user_agent="DryHack", extra_http_headers={"X-Test": "1"})
|
|
188
|
+
|
|
189
|
+
# Inject a payload before any page script runs
|
|
190
|
+
browser(action="goto", url="https://target.example/",
|
|
191
|
+
init_script="Object.defineProperty(navigator,'webdriver',{get:()=>false})")
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Playwright runs in a dedicated worker thread (the MCP server owns the asyncio
|
|
195
|
+
loop, and Playwright's sync API cannot run inside a live loop). The same
|
|
196
|
+
authorized-use scope rules as the other tools apply.
|
|
197
|
+
|
|
131
198
|
## authorize (per-call scope)
|
|
132
199
|
|
|
133
200
|
Pass `scope` directly to the tool; no environment or native scope configuration
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""DryHack-MCP server: registers the offensive-security tools over MCP."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
# mcp 1.x
|
|
8
|
+
from mcp.server.fastmcp import FastMCP
|
|
9
|
+
except ModuleNotFoundError: # pragma: no cover - mcp 2.x fallback
|
|
10
|
+
# mcp 2.x renamed FastMCP -> MCPServer
|
|
11
|
+
from mcp.server.mcpserver import MCPServer as FastMCP
|
|
12
|
+
|
|
13
|
+
from typing import Any, Dict, List
|
|
14
|
+
|
|
15
|
+
from . import config
|
|
16
|
+
from .tools.curl_tool import curl as _curl
|
|
17
|
+
from .tools.python_tool import python_run as _python_run
|
|
18
|
+
from .tools.shell_tool import shell as _shell
|
|
19
|
+
from .tools.authorize import authorize as _authorize
|
|
20
|
+
from .tools.browser_tool import browser as _browser
|
|
21
|
+
|
|
22
|
+
mcp = FastMCP("dryhack-mcp")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@mcp.tool()
|
|
26
|
+
def curl(args: str, timeout: Optional[int] = None) -> str:
|
|
27
|
+
"""Run curl for HTTP recon/exploitation.
|
|
28
|
+
|
|
29
|
+
Provide the arguments only (without the leading `curl`), e.g.
|
|
30
|
+
"-skiL https://target/". Returns exit code, stdout and stderr.
|
|
31
|
+
"""
|
|
32
|
+
return _curl(args, timeout=timeout)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@mcp.tool()
|
|
36
|
+
def python(code: str, timeout: Optional[int] = None) -> str:
|
|
37
|
+
"""Execute an ad-hoc Python snippet for scripted probing/exploitation."""
|
|
38
|
+
return _python_run(code, timeout=timeout)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@mcp.tool()
|
|
42
|
+
def shell(command: str, timeout: Optional[int] = None) -> str:
|
|
43
|
+
"""Run a shell command line (nmap, ffuf, nc, sqlmap, ...)."""
|
|
44
|
+
return _shell(command, timeout=timeout)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@mcp.tool()
|
|
48
|
+
def authorize(target: str, request: str, scope: list[str], context: str = "", goal: str = "") -> str:
|
|
49
|
+
"""Check target against caller-supplied scope (hosts/domains/IPs/CIDRs).
|
|
50
|
+
|
|
51
|
+
Scope must reflect the operator's actual engagement scope; never infer it
|
|
52
|
+
from the target just to obtain a match. Membership covers the full range
|
|
53
|
+
of authorized engagement actions for that host (recon, exploitation,
|
|
54
|
+
configuration changes such as standing up or hardening a staging
|
|
55
|
+
deployment) — it is not restricted to read-only assessment. This checks
|
|
56
|
+
membership only, not ownership or legal permission, and does not override
|
|
57
|
+
client safeguards. No environment scope, network calls, credentials, or
|
|
58
|
+
persistent state.
|
|
59
|
+
"""
|
|
60
|
+
return _authorize(target, request, scope=scope, context=context, goal=goal)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@mcp.tool()
|
|
64
|
+
def browser(
|
|
65
|
+
action: Optional[str] = None,
|
|
66
|
+
url: Optional[str] = None,
|
|
67
|
+
steps: Optional[List[Dict[str, Any]]] = None,
|
|
68
|
+
selector: Optional[str] = None,
|
|
69
|
+
text: Optional[str] = None,
|
|
70
|
+
value: Optional[str] = None,
|
|
71
|
+
script: Optional[str] = None,
|
|
72
|
+
arg: Any = None,
|
|
73
|
+
init_script: Optional[str] = None,
|
|
74
|
+
init_scripts: Optional[List[str]] = None,
|
|
75
|
+
key: Optional[str] = None,
|
|
76
|
+
name: Optional[str] = None,
|
|
77
|
+
files: Optional[Any] = None,
|
|
78
|
+
path: Optional[str] = None,
|
|
79
|
+
full_page: bool = False,
|
|
80
|
+
wait_until: Optional[str] = None,
|
|
81
|
+
state: Optional[str] = None,
|
|
82
|
+
browser_type: str = "chromium",
|
|
83
|
+
headless: bool = True,
|
|
84
|
+
timeout: Optional[int] = None,
|
|
85
|
+
user_agent: Optional[str] = None,
|
|
86
|
+
viewport: Optional[Dict[str, int]] = None,
|
|
87
|
+
extra_http_headers: Optional[Dict[str, str]] = None,
|
|
88
|
+
cookies: Optional[List[Dict[str, Any]]] = None,
|
|
89
|
+
proxy: Optional[Dict[str, str]] = None,
|
|
90
|
+
ignore_https_errors: bool = False,
|
|
91
|
+
java_script_enabled: bool = True,
|
|
92
|
+
bypass_csp: bool = False,
|
|
93
|
+
locale: Optional[str] = None,
|
|
94
|
+
timezone: Optional[str] = None,
|
|
95
|
+
geolocation: Optional[Dict[str, float]] = None,
|
|
96
|
+
device_scale_factor: Optional[float] = None,
|
|
97
|
+
stop_on_error: bool = True,
|
|
98
|
+
slow_mo: Optional[int] = None,
|
|
99
|
+
args: Optional[List[str]] = None,
|
|
100
|
+
executable_path: Optional[str] = None,
|
|
101
|
+
) -> str:
|
|
102
|
+
"""Drive a real browser via Playwright for web recon/exploitation.
|
|
103
|
+
|
|
104
|
+
Run a single `action` (with its relevant params) OR a `steps` list of
|
|
105
|
+
`{"action": ..., ...}` dicts executed in order within one browser session.
|
|
106
|
+
|
|
107
|
+
Supported actions cover navigation (`goto`, `reload`, `back`, `forward`),
|
|
108
|
+
interaction (`click`, `dblclick`, `fill`, `type`, `press`, `hover`, `focus`,
|
|
109
|
+
`check`, `uncheck`, `select_option`, `upload`, `mouse_click`,
|
|
110
|
+
`keyboard_type`, `scroll`), waiting (`wait_for_selector`,
|
|
111
|
+
`wait_for_timeout`, `wait_for_load_state`, `wait_for_url`), JavaScript
|
|
112
|
+
injection (`evaluate`/`eval`, `evaluate_handle`, `add_init_script`,
|
|
113
|
+
`add_script_tag`, `add_style_tag`), extraction (`content`, `inner_text`,
|
|
114
|
+
`inner_html`, `text_content`, `get_attribute`, `query_all`, `title`, `url`),
|
|
115
|
+
capture (`screenshot`, `pdf`), and session control (`set_viewport`,
|
|
116
|
+
`set_extra_headers`, `get_cookies`, `set_cookies`, `clear_cookies`,
|
|
117
|
+
`storage_state`, `emulate_media`).
|
|
118
|
+
|
|
119
|
+
Session-level options let you pick `browser_type` (chromium/firefox/webkit),
|
|
120
|
+
toggle `headless`, set a `user_agent`, `viewport`, `extra_http_headers`,
|
|
121
|
+
`cookies`, `proxy`, `locale`, `timezone`, `geolocation`, per-document
|
|
122
|
+
`init_scripts`, and error handling (`stop_on_error`).
|
|
123
|
+
|
|
124
|
+
JavaScript injection: use `evaluate` (with `script` and optional `arg`) to
|
|
125
|
+
run JS in the current page, or `add_init_script`/`init_scripts` to inject JS
|
|
126
|
+
that runs before every document's own scripts.
|
|
127
|
+
|
|
128
|
+
Requires the optional Playwright extra: `pip install 'dryhack-mcp[browser]'`
|
|
129
|
+
then `playwright install`. Same authorized-use scope rules as the other
|
|
130
|
+
tools apply.
|
|
131
|
+
"""
|
|
132
|
+
return _browser(
|
|
133
|
+
action=action,
|
|
134
|
+
url=url,
|
|
135
|
+
steps=steps,
|
|
136
|
+
selector=selector,
|
|
137
|
+
text=text,
|
|
138
|
+
value=value,
|
|
139
|
+
script=script,
|
|
140
|
+
arg=arg,
|
|
141
|
+
init_script=init_script,
|
|
142
|
+
init_scripts=init_scripts,
|
|
143
|
+
key=key,
|
|
144
|
+
name=name,
|
|
145
|
+
files=files,
|
|
146
|
+
path=path,
|
|
147
|
+
full_page=full_page,
|
|
148
|
+
wait_until=wait_until,
|
|
149
|
+
state=state,
|
|
150
|
+
browser_type=browser_type,
|
|
151
|
+
headless=headless,
|
|
152
|
+
timeout=timeout,
|
|
153
|
+
user_agent=user_agent,
|
|
154
|
+
viewport=viewport,
|
|
155
|
+
extra_http_headers=extra_http_headers,
|
|
156
|
+
cookies=cookies,
|
|
157
|
+
proxy=proxy,
|
|
158
|
+
ignore_https_errors=ignore_https_errors,
|
|
159
|
+
java_script_enabled=java_script_enabled,
|
|
160
|
+
bypass_csp=bypass_csp,
|
|
161
|
+
locale=locale,
|
|
162
|
+
timezone=timezone,
|
|
163
|
+
geolocation=geolocation,
|
|
164
|
+
device_scale_factor=device_scale_factor,
|
|
165
|
+
stop_on_error=stop_on_error,
|
|
166
|
+
slow_mo=slow_mo,
|
|
167
|
+
args=args,
|
|
168
|
+
executable_path=executable_path,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def run(
|
|
173
|
+
transport: str = "stdio",
|
|
174
|
+
host: Optional[str] = None,
|
|
175
|
+
port: Optional[int] = None,
|
|
176
|
+
) -> None:
|
|
177
|
+
"""Start the MCP server.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
transport: "stdio" (default) or "http" (streamable HTTP).
|
|
181
|
+
host: Bind address for http transport.
|
|
182
|
+
port: Bind port for http transport.
|
|
183
|
+
"""
|
|
184
|
+
if transport == "http":
|
|
185
|
+
mcp.settings.host = host or config.HTTP_HOST
|
|
186
|
+
mcp.settings.port = port or config.HTTP_PORT
|
|
187
|
+
mcp.run(transport="streamable-http")
|
|
188
|
+
else:
|
|
189
|
+
mcp.run(transport="stdio")
|