dryhack-mcp 2.3.0__tar.gz → 2.3.1__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 (25) hide show
  1. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/PKG-INFO +1 -1
  2. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp/__init__.py +1 -1
  3. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp/tools/browser_tool.py +21 -3
  4. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp.egg-info/PKG-INFO +1 -1
  5. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/pyproject.toml +1 -1
  6. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/tests/test_browser.py +24 -0
  7. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/LICENSE +0 -0
  8. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/README.md +0 -0
  9. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp/__main__.py +0 -0
  10. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp/config.py +0 -0
  11. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp/server.py +0 -0
  12. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp/tools/__init__.py +0 -0
  13. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp/tools/authorize.py +0 -0
  14. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp/tools/curl_tool.py +0 -0
  15. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp/tools/exec.py +0 -0
  16. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp/tools/python_tool.py +0 -0
  17. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp/tools/scope.py +0 -0
  18. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp/tools/shell_tool.py +0 -0
  19. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp.egg-info/SOURCES.txt +0 -0
  20. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp.egg-info/dependency_links.txt +0 -0
  21. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp.egg-info/entry_points.txt +0 -0
  22. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp.egg-info/requires.txt +0 -0
  23. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/dryhack_mcp.egg-info/top_level.txt +0 -0
  24. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/setup.cfg +0 -0
  25. {dryhack_mcp-2.3.0 → dryhack_mcp-2.3.1}/tests/test_authorize.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dryhack-mcp
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
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
@@ -1,3 +1,3 @@
1
1
  """DryHack-MCP: offensive-security MCP server for authorized penetration testing."""
2
2
 
3
- __version__ = "2.3.0"
3
+ __version__ = "2.3.1"
@@ -449,8 +449,18 @@ def _auto_install_browser(browser_name: str) -> Tuple[bool, str]:
449
449
  return False, f"{type(exc).__name__}: {exc}"
450
450
 
451
451
 
452
- def _launch_browser(browser_type, browser_name: str, launch_kwargs: Dict[str, Any]):
453
- """Launch the browser, auto-installing its binary on first use if missing."""
452
+ def _launch_browser(
453
+ browser_type,
454
+ browser_name: str,
455
+ launch_kwargs: Dict[str, Any],
456
+ session: Optional[Dict[str, Any]] = None,
457
+ ):
458
+ """Launch the browser, auto-installing its binary on first use if missing.
459
+
460
+ On a successful on-demand install, records a human-readable note in
461
+ ``session["_install_note"]`` so the caller can surface the one-time download
462
+ in the tool output (otherwise the first call looks like a silent hang).
463
+ """
454
464
  try:
455
465
  return browser_type.launch(**launch_kwargs)
456
466
  except Exception as exc: # noqa: BLE001
@@ -463,6 +473,11 @@ def _launch_browser(browser_type, browser_name: str, launch_kwargs: Dict[str, An
463
473
  if binary_missing and config.BROWSER_AUTO_INSTALL:
464
474
  ok, log = _auto_install_browser(browser_name)
465
475
  if ok:
476
+ if session is not None:
477
+ session["_install_note"] = (
478
+ f"auto-installed the '{browser_name}' browser binary on "
479
+ "first use (one-time download)"
480
+ )
466
481
  return browser_type.launch(**launch_kwargs)
467
482
  raise RuntimeError(
468
483
  f"{browser_name} binary missing and auto-install failed. "
@@ -504,7 +519,7 @@ def _run_session(
504
519
  if session.get("proxy") is not None:
505
520
  launch_kwargs["proxy"] = session["proxy"]
506
521
 
507
- browser = _launch_browser(browser_type, browser_name, launch_kwargs)
522
+ browser = _launch_browser(browser_type, browser_name, launch_kwargs, session)
508
523
  try:
509
524
  context = browser.new_context(**_build_context_options(session))
510
525
  if default_timeout is not None:
@@ -690,6 +705,9 @@ def browser(
690
705
  f"$ browser[{session['browser_type']}, "
691
706
  f"headless={session['headless']}] {len(normalized)} step(s)"
692
707
  )
708
+ install_note = session.get("_install_note")
709
+ if install_note:
710
+ header = f"{header}\n[setup] {install_note}"
693
711
  if error:
694
712
  return f"{header}\n[error] {error}"
695
713
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dryhack-mcp
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dryhack-mcp"
7
- version = "2.3.0"
7
+ version = "2.3.1"
8
8
  description = "DryHack-MCP - an MCP server providing offensive-security tooling (curl, python, shell, Playwright browser) and a scope-gated authorize engine for authorized penetration testing."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -124,6 +124,30 @@ class BrowserToolTests(unittest.TestCase):
124
124
  self.assertEqual(calls["launch"], 2) # failed once, retried once
125
125
  self.assertEqual(calls["install"], 1)
126
126
 
127
+ def test_launch_records_install_note_in_session(self):
128
+ state = {"launch": 0}
129
+
130
+ class FakeBrowserType:
131
+ def launch(self, **kwargs):
132
+ state["launch"] += 1
133
+ if state["launch"] == 1:
134
+ raise RuntimeError("Executable doesn't exist; playwright install")
135
+ return "handle"
136
+
137
+ session = {}
138
+ original = browser_tool._auto_install_browser
139
+ browser_tool._auto_install_browser = lambda name: (True, "ok")
140
+ original_flag = browser_tool.config.BROWSER_AUTO_INSTALL
141
+ browser_tool.config.BROWSER_AUTO_INSTALL = True
142
+ try:
143
+ browser_tool._launch_browser(FakeBrowserType(), "chromium", {}, session)
144
+ finally:
145
+ browser_tool._auto_install_browser = original
146
+ browser_tool.config.BROWSER_AUTO_INSTALL = original_flag
147
+
148
+ self.assertIn("_install_note", session)
149
+ self.assertIn("chromium", session["_install_note"])
150
+
127
151
  def test_launch_does_not_autoinstall_when_disabled(self):
128
152
  class FakeBrowserType:
129
153
  def launch(self, **kwargs):
File without changes
File without changes
File without changes