feat(browser_mcp): perceive nativo Go, datos de iframe, click XY y screenshot como imagen (v0.6.0)
Capacidades nuevas y cambios (40 -> 42 tools): - page_perceive ahora se genera de forma NATIVA en Go sobre la conexion CDP viva del pool (cdp_get_ax_outline_go_browser). Elimina el subprocess `fn run cdp_perceive_outline` (Python), el venv y la dependencia del binario `fn` en runtime (se borra resolveRoot/exec.Command). Respeta tab_select. - page_perceive acepta frame_id para percibir DENTRO de un iframe. El campo tab_id queda obsoleto (se ignora; usar tab_select) pero se conserva por compatibilidad. - frame_get_text (nueva, lectura): innerText de un iframe via cdp_get_text_in_frame_go_browser. Activa tambien bajo --read-only. - dom_click_xy (nueva, MUTA): click humanizado por coordenadas absolutas via cdp_click_xy_human_go_browser, con mode human/fast/instant y auto-observe. Fallback para actuar sobre lo que el LLM ve en page_screenshot. - page_screenshot devuelve la imagen como image content (cdp_screenshot_bytes_go_browser + mcp.NewToolResultImage) para que el LLM vea los pixeles; path pasa a ser opcional (si se da, ademas guarda a disco). - Auto-observe de las tools *_ref sube su truncado de 4000 a 8000 chars. - Fix de seguridad documental: todas las descripciones del parametro port que decian "Default 9222" (navegador diario del usuario) corregidas a "Default 9333" (Chrome aislado del MCP). El codigo ya usaba 9333; la doc era falsa y podia inducir al modelo a tocar pestanas de banca/correo. uses_functions del app.md: +cdp_get_ax_outline, +cdp_get_text_in_frame, +cdp_screenshot_bytes; -cdp_perceive_outline_py_pipelines. Verificacion: go build OK, go test OK (4 unit pass, 3 e2e skip gated BMCP_E2E=1), go vet OK, gofmt limpio, sin "Default 9222" en el codigo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+42
-9
@@ -25,6 +25,7 @@ func registerDomTools(s *server.MCPServer, d *deps) {
|
||||
s.AddTool(domClickRefTool(), mcp.NewTypedToolHandler(d.handleDomClickRef))
|
||||
s.AddTool(domTypeRefTool(), mcp.NewTypedToolHandler(d.handleDomTypeRef))
|
||||
s.AddTool(domHoverRefTool(), mcp.NewTypedToolHandler(d.handleDomHoverRef))
|
||||
s.AddTool(domClickXYTool(), mcp.NewTypedToolHandler(d.handleDomClickXY))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +59,7 @@ func (d *deps) handleDomClickRef(_ context.Context, _ mcp.CallToolRequest, a dom
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
time.Sleep(settleDelay)
|
||||
outline, _ := d.perceiveOutline(port, 4000)
|
||||
outline, _ := d.perceiveOutline(port, 8000)
|
||||
return mcp.NewToolResultText("clicked ref " + fmt.Sprint(a.Ref) + "\n\n" + outline), nil
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ func (d *deps) handleDomTypeRef(_ context.Context, _ mcp.CallToolRequest, a domT
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
time.Sleep(settleDelay)
|
||||
outline, _ := d.perceiveOutline(port, 4000)
|
||||
outline, _ := d.perceiveOutline(port, 8000)
|
||||
return mcp.NewToolResultText("typed into ref " + fmt.Sprint(a.Ref) + "\n\n" + outline), nil
|
||||
}
|
||||
|
||||
@@ -122,10 +123,42 @@ func (d *deps) handleDomHoverRef(_ context.Context, _ mcp.CallToolRequest, a dom
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
time.Sleep(settleDelay)
|
||||
outline, _ := d.perceiveOutline(port, 4000)
|
||||
outline, _ := d.perceiveOutline(port, 8000)
|
||||
return mcp.NewToolResultText("hovered ref " + fmt.Sprint(a.Ref) + "\n\n" + outline), nil
|
||||
}
|
||||
|
||||
// ---- dom_click_xy (MUTA) — click humanizado por coordenadas absolutas ----
|
||||
|
||||
type domClickXYArgs struct {
|
||||
Port int `json:"port"`
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
|
||||
func domClickXYTool() mcp.Tool {
|
||||
return mcp.NewTool("dom_click_xy",
|
||||
mcp.WithDescription("Fallback de click por coordenadas absolutas (x, y) en CSS pixels del viewport, con movimiento de ratón humanizado por defecto. Pensado para usarse sobre lo que el agente VE en page_screenshot cuando el outline de page_perceive no basta (canvas, mapas, layouts visuales). Prefiere dom_click_ref cuando el elemento aparece en el outline. Devuelve el outline actualizado tras la acción (auto-observe)."),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9333 (Chrome isolated del MCP); usa 9222 explícito solo para adjuntarte al navegador diario.")),
|
||||
mcp.WithNumber("x", mcp.Required(), mcp.Description("Coordenada X absoluta en CSS pixels del viewport.")),
|
||||
mcp.WithNumber("y", mcp.Required(), mcp.Description("Coordenada Y absoluta en CSS pixels del viewport.")),
|
||||
mcp.WithString("mode", mcp.Description("Velocidad: 'human' (default, Bézier+jitter anti-bot), 'fast' (movimiento reducido, scraping masivo), 'instant' (sin movimiento de ratón).")),
|
||||
)
|
||||
}
|
||||
|
||||
func (d *deps) handleDomClickXY(_ context.Context, _ mcp.CallToolRequest, a domClickXYArgs) (*mcp.CallToolResult, error) {
|
||||
port := portOr(a.Port)
|
||||
err := d.withConn(port, func(c *browser.CDPConn) error {
|
||||
return browser.CdpClickXYHuman(c, a.X, a.Y, browser.MouseProfileForMode(a.Mode))
|
||||
})
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
time.Sleep(settleDelay)
|
||||
outline, _ := d.perceiveOutline(port, 8000)
|
||||
return mcp.NewToolResultText(fmt.Sprintf("clicked at (%g, %g)\n\n%s", a.X, a.Y, outline)), nil
|
||||
}
|
||||
|
||||
// ---- dom_click (MUTA) ----
|
||||
|
||||
type domClickArgs struct {
|
||||
@@ -136,7 +169,7 @@ type domClickArgs struct {
|
||||
func domClickTool() mcp.Tool {
|
||||
return mcp.NewTool("dom_click",
|
||||
mcp.WithDescription("Click the element matching the CSS selector (synthetic CDP click)."),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9222.")),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9333 (Chrome isolated del MCP); usa 9222 explícito solo para adjuntarte al navegador diario.")),
|
||||
mcp.WithString("selector", mcp.Required(), mcp.Description("CSS selector of the element to click.")),
|
||||
)
|
||||
}
|
||||
@@ -164,7 +197,7 @@ type domClickHumanArgs struct {
|
||||
func domClickHumanTool() mcp.Tool {
|
||||
return mcp.NewTool("dom_click_human",
|
||||
mcp.WithDescription("Click the element matching the CSS selector with human-like mouse movement (Bézier path + jitter + press/release pause)."),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9222.")),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9333 (Chrome isolated del MCP); usa 9222 explícito solo para adjuntarte al navegador diario.")),
|
||||
mcp.WithString("selector", mcp.Required(), mcp.Description("CSS selector of the element to click.")),
|
||||
)
|
||||
}
|
||||
@@ -192,7 +225,7 @@ type domClickTextArgs struct {
|
||||
func domClickTextTool() mcp.Tool {
|
||||
return mcp.NewTool("dom_click_text",
|
||||
mcp.WithDescription("Find the first element whose visible text matches and click it."),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9222.")),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9333 (Chrome isolated del MCP); usa 9222 explícito solo para adjuntarte al navegador diario.")),
|
||||
mcp.WithString("text", mcp.Required(), mcp.Description("Visible text to match (substring).")),
|
||||
)
|
||||
}
|
||||
@@ -220,7 +253,7 @@ type domTypeArgs struct {
|
||||
func domTypeTool() mcp.Tool {
|
||||
return mcp.NewTool("dom_type",
|
||||
mcp.WithDescription("Type text into the currently focused element (dispatches key events char by char)."),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9222.")),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9333 (Chrome isolated del MCP); usa 9222 explícito solo para adjuntarte al navegador diario.")),
|
||||
mcp.WithString("text", mcp.Required(), mcp.Description("Text to type.")),
|
||||
)
|
||||
}
|
||||
@@ -248,7 +281,7 @@ type domFindByTextArgs struct {
|
||||
func domFindByTextTool() mcp.Tool {
|
||||
return mcp.NewTool("dom_find_by_text",
|
||||
mcp.WithDescription("Find the first element whose visible text matches and return a unique CSS selector for it (empty string if none)."),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9222.")),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9333 (Chrome isolated del MCP); usa 9222 explícito solo para adjuntarte al navegador diario.")),
|
||||
mcp.WithString("text", mcp.Required(), mcp.Description("Visible text to match (substring).")),
|
||||
)
|
||||
}
|
||||
@@ -315,7 +348,7 @@ type domWaitElementArgs struct {
|
||||
func domWaitElementTool() mcp.Tool {
|
||||
return mcp.NewTool("dom_wait_element",
|
||||
mcp.WithDescription("Block until an element matching the CSS selector appears in the DOM (or timeout)."),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9222.")),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9333 (Chrome isolated del MCP); usa 9222 explícito solo para adjuntarte al navegador diario.")),
|
||||
mcp.WithString("selector", mcp.Required(), mcp.Description("CSS selector to wait for.")),
|
||||
mcp.WithNumber("timeout_ms", mcp.Description("Max wait in ms. Default 10000.")),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user