Skip to content
Merged
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
19 changes: 12 additions & 7 deletions je_web_runner/mcp_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ class McpServerError(WebRunnerException):


_MCP_PROTOCOL_VERSION = "2024-11-05"

# Reused error messages — extracted so SonarCloud S1192 stays quiet and
# downstream tooling can grep for them.
_ERR_ACTIONS_LIST = "'actions' must be a list"
_ERR_TEXT_STRING = "'text' must be a string"
_SERVER_NAME = "webrunner-mcp"
_SERVER_VERSION = "0.1.0"

Expand Down Expand Up @@ -144,7 +149,7 @@ def _tool_lint_action(arguments: Dict[str, Any]) -> Any:
from je_web_runner.utils.linter.action_linter import lint_action
actions = arguments.get("actions")
if not isinstance(actions, list):
raise McpServerError("'actions' must be a list")
raise McpServerError(_ERR_ACTIONS_LIST)
# ``lint_action`` returns ``List[Dict[str, Any]]`` with ``rule`` /
# ``severity`` / ``message`` / ``location`` keys; pass through verbatim
# so MCP clients see the same shape the Python API exposes.
Expand Down Expand Up @@ -238,23 +243,23 @@ def _tool_format_actions(arguments: Dict[str, Any]) -> Any:
from je_web_runner.utils.action_formatter.formatter import format_actions
actions = arguments.get("actions")
if not isinstance(actions, list):
raise McpServerError("'actions' must be a list")
raise McpServerError(_ERR_ACTIONS_LIST)
return format_actions(actions, indent=int(arguments.get("indent", 2)))


def _tool_parse_markdown(arguments: Dict[str, Any]) -> Any:
from je_web_runner.utils.md_authoring.markdown_to_actions import parse_markdown
text = arguments.get("text")
if not isinstance(text, str):
raise McpServerError("'text' must be a string")
raise McpServerError(_ERR_TEXT_STRING)
return parse_markdown(text)


def _tool_translate_actions_to_playwright(arguments: Dict[str, Any]) -> Any:
from je_web_runner.utils.sel_to_pw.translator import translate_action_list
actions = arguments.get("actions")
if not isinstance(actions, list):
raise McpServerError("'actions' must be a list")
raise McpServerError(_ERR_ACTIONS_LIST)
return translate_action_list(actions)


Expand Down Expand Up @@ -295,7 +300,7 @@ def _tool_scan_pii(arguments: Dict[str, Any]) -> Any:
from je_web_runner.utils.pii_scanner.scanner import scan_text
text = arguments.get("text")
if not isinstance(text, str):
raise McpServerError("'text' must be a string")
raise McpServerError(_ERR_TEXT_STRING)
categories = arguments.get("categories")
findings = scan_text(text, categories=categories)
return [
Expand All @@ -309,7 +314,7 @@ def _tool_redact_pii(arguments: Dict[str, Any]) -> Any:
from je_web_runner.utils.pii_scanner.scanner import redact_text
text = arguments.get("text")
if not isinstance(text, str):
raise McpServerError("'text' must be a string")
raise McpServerError(_ERR_TEXT_STRING)
return redact_text(
text,
replacement=str(arguments.get("replacement", "[REDACTED]")),
Expand Down Expand Up @@ -351,7 +356,7 @@ def _tool_score_action_locators(arguments: Dict[str, Any]) -> Any:
from je_web_runner.utils.linter.locator_strength import score_action_locators
actions = arguments.get("actions")
if not isinstance(actions, list):
raise McpServerError("'actions' must be a list")
raise McpServerError(_ERR_ACTIONS_LIST)
return list(score_action_locators(actions))


Expand Down
7 changes: 4 additions & 3 deletions je_web_runner/utils/md_authoring/markdown_to_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ def _type_actions(text: str, selector: str) -> List[List[Any]]:
_PRESS_RE = re.compile(r"^press\s+(\S+)$", re.IGNORECASE)
_SCREENSHOT_RE = re.compile(r"^screenshot$", re.IGNORECASE)
# Template name allows ASCII identifier chars plus dashes; the bounded
# {0,80} caps the worst case at linear in input length.
_TEMPLATE_RE = re.compile( # NOSONAR S5852 / S5869 — bounded class, ``\w`` overlap with first class is intentional
r"^run\s+template\s+([A-Za-z_][\w-]{0,80})$", re.IGNORECASE,
# {0,80} caps the worst case at linear in input length. With re.IGNORECASE
# the first class only needs [A-Z_] — lowercase letters fold in automatically.
_TEMPLATE_RE = re.compile(
r"^run\s+template\s+([A-Z_][\w-]{0,80})$", re.IGNORECASE,
)
_QUIT_RE = re.compile(r"^quit$", re.IGNORECASE)

Expand Down
3 changes: 1 addition & 2 deletions je_web_runner/utils/visual_review/review_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,7 @@ def do_GET(self): # noqa: N802
"text/html; charset=utf-8",
)
return
if (parsed.path.startswith("/img/baseline/")
or parsed.path.startswith("/img/current/")):
if parsed.path.startswith(("/img/baseline/", "/img/current/")):
self._serve_image(parsed.path)
return
self._send(404, b"not found", _TEXT_PLAIN)
Expand Down
3 changes: 2 additions & 1 deletion test/unit_test/test_sharding.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import unittest
from itertools import chain

from je_web_runner.utils.sharding.shard import (
ShardingError,
Expand Down Expand Up @@ -56,7 +57,7 @@
def test_partition_with_spec_round_trip(self):
files = [f"x{i}.json" for i in range(20)]
parts = [partition_with_spec(files, f"{i}/4") for i in range(1, 5)]
merged = sum(parts, [])
merged = list(chain.from_iterable(parts))

Check warning on line 60 in test/unit_test/test_sharding.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this redundant call.

See more on https://sonarcloud.io/project/issues?id=Integration-Automation_WebRunner&issues=AZ5agaYiLTg-mVZu65ip&open=AZ5agaYiLTg-mVZu65ip&pullRequest=98
self.assertEqual(set(merged), set(files))


Expand Down
Loading
Loading