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
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -687,11 +687,26 @@ WebRunner ships a [Model Context Protocol](https://modelcontextprotocol.io/) ser
python -m je_web_runner.mcp_server
```

The default tool list exposes:
The default tool list (19 tools) exposes:

- `webrunner_lint_action`, `webrunner_locator_strength`
- `webrunner_render_template`, `webrunner_compute_trend`
- `webrunner_validate_response`, `webrunner_summary_markdown`
Action JSON authoring & linting:
- `webrunner_lint_action`, `webrunner_score_action_locators`, `webrunner_locator_strength`
- `webrunner_format_actions`, `webrunner_parse_markdown`, `webrunner_render_template`
- `webrunner_translate_actions_to_playwright`, `webrunner_translate_python_to_playwright`

Code generation:
- `webrunner_pom_from_html`

Quality / triage:
- `webrunner_a11y_diff`, `webrunner_cluster_failures`, `webrunner_compute_trend`

Security & privacy:
- `webrunner_scan_pii`, `webrunner_redact_pii`

Reporting & contract:
- `webrunner_summary_markdown`, `webrunner_validate_response`

Sharding & infra:
- `webrunner_diff_shard`, `webrunner_render_k8s`, `webrunner_partition_shard`

```python
Expand Down
21 changes: 16 additions & 5 deletions docs/source/Eng/doc/extended_features/extended_features_doc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -380,11 +380,22 @@ drive it over JSON-RPC stdio:

python -m je_web_runner.mcp_server

Default tools registered: ``webrunner_lint_action``,
``webrunner_locator_strength``, ``webrunner_render_template``,
``webrunner_compute_trend``, ``webrunner_validate_response``,
``webrunner_summary_markdown``, ``webrunner_diff_shard``,
``webrunner_render_k8s``, ``webrunner_partition_shard``.
Default tools registered (19 in total):

* Action authoring & lint: ``webrunner_lint_action``,
``webrunner_score_action_locators``, ``webrunner_locator_strength``,
``webrunner_format_actions``, ``webrunner_parse_markdown``,
``webrunner_render_template``,
``webrunner_translate_actions_to_playwright``,
``webrunner_translate_python_to_playwright``
* Code generation: ``webrunner_pom_from_html``
* Quality & triage: ``webrunner_a11y_diff``,
``webrunner_cluster_failures``, ``webrunner_compute_trend``
* Security: ``webrunner_scan_pii``, ``webrunner_redact_pii``
* Reporting & contract: ``webrunner_summary_markdown``,
``webrunner_validate_response``
* Sharding / infra: ``webrunner_diff_shard``,
``webrunner_render_k8s``, ``webrunner_partition_shard``

Custom tools register via ``McpServer.register(Tool(...))``; the server
implements MCP ``2024-11-05`` (``initialize`` / ``tools/list`` /
Expand Down
25 changes: 19 additions & 6 deletions docs/source/Zh/doc/extended_features/extended_features_doc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,25 @@ MCP server

python -m je_web_runner.mcp_server

預設工具:``webrunner_lint_action`` / ``webrunner_locator_strength`` /
``webrunner_render_template`` / ``webrunner_compute_trend`` /
``webrunner_validate_response`` / ``webrunner_summary_markdown`` /
``webrunner_diff_shard`` / ``webrunner_render_k8s`` /
``webrunner_partition_shard``。可透過 ``McpServer.register(Tool(...))``
擴充自訂工具,協定版本 ``2024-11-05``。
預設工具共 19 個,依用途分組:

* Action 撰寫 / lint:``webrunner_lint_action`` /
``webrunner_score_action_locators`` / ``webrunner_locator_strength`` /
``webrunner_format_actions`` / ``webrunner_parse_markdown`` /
``webrunner_render_template`` /
``webrunner_translate_actions_to_playwright`` /
``webrunner_translate_python_to_playwright``
* 程式碼生成:``webrunner_pom_from_html``
* 品質 / triage:``webrunner_a11y_diff`` / ``webrunner_cluster_failures``
/ ``webrunner_compute_trend``
* 安全 / 隱私:``webrunner_scan_pii`` / ``webrunner_redact_pii``
* 報告 / contract:``webrunner_summary_markdown`` /
``webrunner_validate_response``
* Sharding / infra:``webrunner_diff_shard`` / ``webrunner_render_k8s``
/ ``webrunner_partition_shard``

可透過 ``McpServer.register(Tool(...))`` 自行擴充工具,協定版本
``2024-11-05``。

Action JSON LSP
===============
Expand Down
267 changes: 265 additions & 2 deletions je_web_runner/mcp_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,11 @@
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")

Check failure on line 147 in je_web_runner/mcp_server/server.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "'actions' must be a list" 4 times.

See more on https://sonarcloud.io/project/issues?id=Integration-Automation_WebRunner&issues=AZ3JeY1Ln072oHSM7v7p&open=AZ3JeY1Ln072oHSM7v7p&pullRequest=88
return [{"index": f.index, "level": f.level, "message": f.message,
"rule": f.rule} for f in lint_action(actions)]
# ``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.
return list(lint_action(actions))


def _tool_locator_strength(arguments: Dict[str, Any]) -> Any:
Expand Down Expand Up @@ -232,6 +234,127 @@
)


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")
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")

Check failure on line 249 in je_web_runner/mcp_server/server.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "'text' must be a string" 3 times.

See more on https://sonarcloud.io/project/issues?id=Integration-Automation_WebRunner&issues=AZ3JeY1Ln072oHSM7v7q&open=AZ3JeY1Ln072oHSM7v7q&pullRequest=88
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")
return translate_action_list(actions)


def _tool_translate_python_to_playwright(arguments: Dict[str, Any]) -> Any:
from je_web_runner.utils.sel_to_pw.translator import translate_python_source
source = arguments.get("source")
if not isinstance(source, str):
raise McpServerError("'source' must be a string")
translations = translate_python_source(source)
return [
{"line": t.line, "original": t.original,
"translated": t.translated, "note": t.note}
for t in translations
]


def _tool_pom_from_html(arguments: Dict[str, Any]) -> Any:
from je_web_runner.utils.pom_codegen.codegen import (
discover_elements_from_html,
render_pom_module,
)
html = arguments.get("html")
if not isinstance(html, str):
raise McpServerError("'html' must be a string")
elements = discover_elements_from_html(html)
class_name = str(arguments.get("class_name", "WebRunnerPage"))
return {
"module": render_pom_module(elements, class_name=class_name),
"elements": [
{"name": e.name, "strategy": e.strategy,
"value": e.value, "tag": e.tag, "source": e.source}
for e in elements
],
}


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")
categories = arguments.get("categories")
findings = scan_text(text, categories=categories)
return [
{"category": f.category, "start": f.start,
"end": f.end, "redacted": f.redacted}
for f in findings
]


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")
return redact_text(
text,
replacement=str(arguments.get("replacement", "[REDACTED]")),
categories=arguments.get("categories"),
)


def _tool_cluster_failures(arguments: Dict[str, Any]) -> Any:
from je_web_runner.utils.failure_cluster.clustering import (
cluster_failures,
cluster_summary,
)
failures = arguments.get("failures")
if not isinstance(failures, list):
raise McpServerError("'failures' must be a list")
top_n = arguments.get("top_n")
if top_n is not None:
top_n = int(top_n)
clusters = cluster_failures(failures, top_n=top_n)
return cluster_summary(clusters)


def _tool_a11y_diff(arguments: Dict[str, Any]) -> Any:
from je_web_runner.utils.accessibility.a11y_diff import diff_violations
baseline = arguments.get("baseline")
current = arguments.get("current")
if not isinstance(baseline, list) or not isinstance(current, list):
raise McpServerError("'baseline' and 'current' must be lists")
diff = diff_violations(baseline, current)
return {
"added": diff.added,
"resolved": diff.resolved,
"persisting": diff.persisting,
"regressed": diff.regressed,
}


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")
return list(score_action_locators(actions))


def build_default_tools() -> List[Tool]:
"""Construct the default tool list shipped with the server."""
return [
Expand Down Expand Up @@ -351,6 +474,146 @@
},
handler=_tool_partition,
),
Tool(
name="webrunner_format_actions",
description="Format an action JSON list with canonical kwarg order.",
input_schema={
"type": "object",
"properties": {
"actions": {"type": "array"},
"indent": {"type": "integer"},
},
"required": ["actions"],
},
handler=_tool_format_actions,
),
Tool(
name="webrunner_parse_markdown",
description="Transpile a Markdown bullet list into a WR_* action list.",
input_schema={
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
},
handler=_tool_parse_markdown,
),
Tool(
name="webrunner_translate_actions_to_playwright",
description=(
"Rewrite a WR_* action list to its WR_pw_* Playwright"
" equivalent (drops WR_implicitly_wait)."
),
input_schema={
"type": "object",
"properties": {"actions": {"type": "array"}},
"required": ["actions"],
},
handler=_tool_translate_actions_to_playwright,
),
Tool(
name="webrunner_translate_python_to_playwright",
description=(
"Static translator: rewrites Selenium-style Python source"
" into Playwright equivalents; returns per-line diffs."
),
input_schema={
"type": "object",
"properties": {"source": {"type": "string"}},
"required": ["source"],
},
handler=_tool_translate_python_to_playwright,
),
Tool(
name="webrunner_pom_from_html",
description=(
"Discover [data-testid] / id / form fields in HTML and"
" render a Python Page Object module."
),
input_schema={
"type": "object",
"properties": {
"html": {"type": "string"},
"class_name": {"type": "string"},
},
"required": ["html"],
},
handler=_tool_pom_from_html,
),
Tool(
name="webrunner_scan_pii",
description=(
"Scan text for PII (email / phone / Luhn-card / SSN /"
" ROC ID / IPv4); returns category + redacted preview."
),
input_schema={
"type": "object",
"properties": {
"text": {"type": "string"},
"categories": {"type": "array", "items": {"type": "string"}},
},
"required": ["text"],
},
handler=_tool_scan_pii,
),
Tool(
name="webrunner_redact_pii",
description="Replace each detected PII match with a sentinel string.",
input_schema={
"type": "object",
"properties": {
"text": {"type": "string"},
"replacement": {"type": "string"},
"categories": {"type": "array", "items": {"type": "string"}},
},
"required": ["text"],
},
handler=_tool_redact_pii,
),
Tool(
name="webrunner_cluster_failures",
description=(
"Group failures by normalised error signature; returns"
" top buckets sorted by count."
),
input_schema={
"type": "object",
"properties": {
"failures": {"type": "array"},
"top_n": {"type": "integer"},
},
"required": ["failures"],
},
handler=_tool_cluster_failures,
),
Tool(
name="webrunner_a11y_diff",
description=(
"Diff two axe-core ``violations`` arrays and bucket the"
" findings into added / resolved / persisting."
),
input_schema={
"type": "object",
"properties": {
"baseline": {"type": "array"},
"current": {"type": "array"},
},
"required": ["baseline", "current"],
},
handler=_tool_a11y_diff,
),
Tool(
name="webrunner_score_action_locators",
description=(
"Score every locator referenced by an action JSON list on"
" a 0–100 scale; lower = more fragile."
),
input_schema={
"type": "object",
"properties": {"actions": {"type": "array"}},
"required": ["actions"],
},
handler=_tool_score_action_locators,
),
]


Expand Down
Loading
Loading