@@ -145,8 +145,10 @@ def _tool_lint_action(arguments: Dict[str, Any]) -> Any:
145145 actions = arguments .get ("actions" )
146146 if not isinstance (actions , list ):
147147 raise McpServerError ("'actions' must be a list" )
148- return [{"index" : f .index , "level" : f .level , "message" : f .message ,
149- "rule" : f .rule } for f in lint_action (actions )]
148+ # ``lint_action`` returns ``List[Dict[str, Any]]`` with ``rule`` /
149+ # ``severity`` / ``message`` / ``location`` keys; pass through verbatim
150+ # so MCP clients see the same shape the Python API exposes.
151+ return list (lint_action (actions ))
150152
151153
152154def _tool_locator_strength (arguments : Dict [str , Any ]) -> Any :
@@ -232,6 +234,127 @@ def _tool_partition(arguments: Dict[str, Any]) -> Any:
232234 )
233235
234236
237+ def _tool_format_actions (arguments : Dict [str , Any ]) -> Any :
238+ from je_web_runner .utils .action_formatter .formatter import format_actions
239+ actions = arguments .get ("actions" )
240+ if not isinstance (actions , list ):
241+ raise McpServerError ("'actions' must be a list" )
242+ return format_actions (actions , indent = int (arguments .get ("indent" , 2 )))
243+
244+
245+ def _tool_parse_markdown (arguments : Dict [str , Any ]) -> Any :
246+ from je_web_runner .utils .md_authoring .markdown_to_actions import parse_markdown
247+ text = arguments .get ("text" )
248+ if not isinstance (text , str ):
249+ raise McpServerError ("'text' must be a string" )
250+ return parse_markdown (text )
251+
252+
253+ def _tool_translate_actions_to_playwright (arguments : Dict [str , Any ]) -> Any :
254+ from je_web_runner .utils .sel_to_pw .translator import translate_action_list
255+ actions = arguments .get ("actions" )
256+ if not isinstance (actions , list ):
257+ raise McpServerError ("'actions' must be a list" )
258+ return translate_action_list (actions )
259+
260+
261+ def _tool_translate_python_to_playwright (arguments : Dict [str , Any ]) -> Any :
262+ from je_web_runner .utils .sel_to_pw .translator import translate_python_source
263+ source = arguments .get ("source" )
264+ if not isinstance (source , str ):
265+ raise McpServerError ("'source' must be a string" )
266+ translations = translate_python_source (source )
267+ return [
268+ {"line" : t .line , "original" : t .original ,
269+ "translated" : t .translated , "note" : t .note }
270+ for t in translations
271+ ]
272+
273+
274+ def _tool_pom_from_html (arguments : Dict [str , Any ]) -> Any :
275+ from je_web_runner .utils .pom_codegen .codegen import (
276+ discover_elements_from_html ,
277+ render_pom_module ,
278+ )
279+ html = arguments .get ("html" )
280+ if not isinstance (html , str ):
281+ raise McpServerError ("'html' must be a string" )
282+ elements = discover_elements_from_html (html )
283+ class_name = str (arguments .get ("class_name" , "WebRunnerPage" ))
284+ return {
285+ "module" : render_pom_module (elements , class_name = class_name ),
286+ "elements" : [
287+ {"name" : e .name , "strategy" : e .strategy ,
288+ "value" : e .value , "tag" : e .tag , "source" : e .source }
289+ for e in elements
290+ ],
291+ }
292+
293+
294+ def _tool_scan_pii (arguments : Dict [str , Any ]) -> Any :
295+ from je_web_runner .utils .pii_scanner .scanner import scan_text
296+ text = arguments .get ("text" )
297+ if not isinstance (text , str ):
298+ raise McpServerError ("'text' must be a string" )
299+ categories = arguments .get ("categories" )
300+ findings = scan_text (text , categories = categories )
301+ return [
302+ {"category" : f .category , "start" : f .start ,
303+ "end" : f .end , "redacted" : f .redacted }
304+ for f in findings
305+ ]
306+
307+
308+ def _tool_redact_pii (arguments : Dict [str , Any ]) -> Any :
309+ from je_web_runner .utils .pii_scanner .scanner import redact_text
310+ text = arguments .get ("text" )
311+ if not isinstance (text , str ):
312+ raise McpServerError ("'text' must be a string" )
313+ return redact_text (
314+ text ,
315+ replacement = str (arguments .get ("replacement" , "[REDACTED]" )),
316+ categories = arguments .get ("categories" ),
317+ )
318+
319+
320+ def _tool_cluster_failures (arguments : Dict [str , Any ]) -> Any :
321+ from je_web_runner .utils .failure_cluster .clustering import (
322+ cluster_failures ,
323+ cluster_summary ,
324+ )
325+ failures = arguments .get ("failures" )
326+ if not isinstance (failures , list ):
327+ raise McpServerError ("'failures' must be a list" )
328+ top_n = arguments .get ("top_n" )
329+ if top_n is not None :
330+ top_n = int (top_n )
331+ clusters = cluster_failures (failures , top_n = top_n )
332+ return cluster_summary (clusters )
333+
334+
335+ def _tool_a11y_diff (arguments : Dict [str , Any ]) -> Any :
336+ from je_web_runner .utils .accessibility .a11y_diff import diff_violations
337+ baseline = arguments .get ("baseline" )
338+ current = arguments .get ("current" )
339+ if not isinstance (baseline , list ) or not isinstance (current , list ):
340+ raise McpServerError ("'baseline' and 'current' must be lists" )
341+ diff = diff_violations (baseline , current )
342+ return {
343+ "added" : diff .added ,
344+ "resolved" : diff .resolved ,
345+ "persisting" : diff .persisting ,
346+ "regressed" : diff .regressed ,
347+ }
348+
349+
350+ def _tool_score_action_locators (arguments : Dict [str , Any ]) -> Any :
351+ from je_web_runner .utils .linter .locator_strength import score_action_locators
352+ actions = arguments .get ("actions" )
353+ if not isinstance (actions , list ):
354+ raise McpServerError ("'actions' must be a list" )
355+ return list (score_action_locators (actions ))
356+
357+
235358def build_default_tools () -> List [Tool ]:
236359 """Construct the default tool list shipped with the server."""
237360 return [
@@ -351,6 +474,146 @@ def build_default_tools() -> List[Tool]:
351474 },
352475 handler = _tool_partition ,
353476 ),
477+ Tool (
478+ name = "webrunner_format_actions" ,
479+ description = "Format an action JSON list with canonical kwarg order." ,
480+ input_schema = {
481+ "type" : "object" ,
482+ "properties" : {
483+ "actions" : {"type" : "array" },
484+ "indent" : {"type" : "integer" },
485+ },
486+ "required" : ["actions" ],
487+ },
488+ handler = _tool_format_actions ,
489+ ),
490+ Tool (
491+ name = "webrunner_parse_markdown" ,
492+ description = "Transpile a Markdown bullet list into a WR_* action list." ,
493+ input_schema = {
494+ "type" : "object" ,
495+ "properties" : {"text" : {"type" : "string" }},
496+ "required" : ["text" ],
497+ },
498+ handler = _tool_parse_markdown ,
499+ ),
500+ Tool (
501+ name = "webrunner_translate_actions_to_playwright" ,
502+ description = (
503+ "Rewrite a WR_* action list to its WR_pw_* Playwright"
504+ " equivalent (drops WR_implicitly_wait)."
505+ ),
506+ input_schema = {
507+ "type" : "object" ,
508+ "properties" : {"actions" : {"type" : "array" }},
509+ "required" : ["actions" ],
510+ },
511+ handler = _tool_translate_actions_to_playwright ,
512+ ),
513+ Tool (
514+ name = "webrunner_translate_python_to_playwright" ,
515+ description = (
516+ "Static translator: rewrites Selenium-style Python source"
517+ " into Playwright equivalents; returns per-line diffs."
518+ ),
519+ input_schema = {
520+ "type" : "object" ,
521+ "properties" : {"source" : {"type" : "string" }},
522+ "required" : ["source" ],
523+ },
524+ handler = _tool_translate_python_to_playwright ,
525+ ),
526+ Tool (
527+ name = "webrunner_pom_from_html" ,
528+ description = (
529+ "Discover [data-testid] / id / form fields in HTML and"
530+ " render a Python Page Object module."
531+ ),
532+ input_schema = {
533+ "type" : "object" ,
534+ "properties" : {
535+ "html" : {"type" : "string" },
536+ "class_name" : {"type" : "string" },
537+ },
538+ "required" : ["html" ],
539+ },
540+ handler = _tool_pom_from_html ,
541+ ),
542+ Tool (
543+ name = "webrunner_scan_pii" ,
544+ description = (
545+ "Scan text for PII (email / phone / Luhn-card / SSN /"
546+ " ROC ID / IPv4); returns category + redacted preview."
547+ ),
548+ input_schema = {
549+ "type" : "object" ,
550+ "properties" : {
551+ "text" : {"type" : "string" },
552+ "categories" : {"type" : "array" , "items" : {"type" : "string" }},
553+ },
554+ "required" : ["text" ],
555+ },
556+ handler = _tool_scan_pii ,
557+ ),
558+ Tool (
559+ name = "webrunner_redact_pii" ,
560+ description = "Replace each detected PII match with a sentinel string." ,
561+ input_schema = {
562+ "type" : "object" ,
563+ "properties" : {
564+ "text" : {"type" : "string" },
565+ "replacement" : {"type" : "string" },
566+ "categories" : {"type" : "array" , "items" : {"type" : "string" }},
567+ },
568+ "required" : ["text" ],
569+ },
570+ handler = _tool_redact_pii ,
571+ ),
572+ Tool (
573+ name = "webrunner_cluster_failures" ,
574+ description = (
575+ "Group failures by normalised error signature; returns"
576+ " top buckets sorted by count."
577+ ),
578+ input_schema = {
579+ "type" : "object" ,
580+ "properties" : {
581+ "failures" : {"type" : "array" },
582+ "top_n" : {"type" : "integer" },
583+ },
584+ "required" : ["failures" ],
585+ },
586+ handler = _tool_cluster_failures ,
587+ ),
588+ Tool (
589+ name = "webrunner_a11y_diff" ,
590+ description = (
591+ "Diff two axe-core ``violations`` arrays and bucket the"
592+ " findings into added / resolved / persisting."
593+ ),
594+ input_schema = {
595+ "type" : "object" ,
596+ "properties" : {
597+ "baseline" : {"type" : "array" },
598+ "current" : {"type" : "array" },
599+ },
600+ "required" : ["baseline" , "current" ],
601+ },
602+ handler = _tool_a11y_diff ,
603+ ),
604+ Tool (
605+ name = "webrunner_score_action_locators" ,
606+ description = (
607+ "Score every locator referenced by an action JSON list on"
608+ " a 0–100 scale; lower = more fragile."
609+ ),
610+ input_schema = {
611+ "type" : "object" ,
612+ "properties" : {"actions" : {"type" : "array" }},
613+ "required" : ["actions" ],
614+ },
615+ handler = _tool_score_action_locators ,
616+ ),
354617 ]
355618
356619
0 commit comments