feat: tool dom_find_ref_by_text (click-by-text por #ref) + mode en click_ref/hover_ref
dom_find_ref_by_text usa la nueva CdpFindRefByText del registry: encuentra por texto y devuelve el #ref (backendDOMNodeId) listo para dom_click_ref, sin selector CSS frágil; reporta count para ambigüedad. Incluye WIP pre-existente ya estable: dom_click_ref/dom_hover_ref exponen 'mode' (human/fast/instant) vía MouseProfileForMode. Compila + 9 e2e verdes.
This commit is contained in:
+48
-10
@@ -14,6 +14,7 @@ import (
|
|||||||
// registerDomTools wires DOM interaction tools. find/wait stay on under --read-only.
|
// registerDomTools wires DOM interaction tools. find/wait stay on under --read-only.
|
||||||
func registerDomTools(s *server.MCPServer, d *deps) {
|
func registerDomTools(s *server.MCPServer, d *deps) {
|
||||||
s.AddTool(domFindByTextTool(), mcp.NewTypedToolHandler(d.handleDomFindByText))
|
s.AddTool(domFindByTextTool(), mcp.NewTypedToolHandler(d.handleDomFindByText))
|
||||||
|
s.AddTool(domFindRefByTextTool(), mcp.NewTypedToolHandler(d.handleDomFindRefByText))
|
||||||
s.AddTool(domWaitElementTool(), mcp.NewTypedToolHandler(d.handleDomWaitElement))
|
s.AddTool(domWaitElementTool(), mcp.NewTypedToolHandler(d.handleDomWaitElement))
|
||||||
|
|
||||||
if !d.readOnly {
|
if !d.readOnly {
|
||||||
@@ -34,23 +35,24 @@ const settleDelay = 400 * time.Millisecond
|
|||||||
// ---- dom_click_ref (MUTA) — bucle percibir→actuar ----
|
// ---- dom_click_ref (MUTA) — bucle percibir→actuar ----
|
||||||
|
|
||||||
type domClickRefArgs struct {
|
type domClickRefArgs struct {
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Ref int `json:"ref"`
|
Ref int `json:"ref"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func domClickRefTool() mcp.Tool {
|
func domClickRefTool() mcp.Tool {
|
||||||
return mcp.NewTool("dom_click_ref",
|
return mcp.NewTool("dom_click_ref",
|
||||||
mcp.WithDescription("Click humanizado sobre el elemento por su #ref del outline de page_perceive (backendDOMNodeId estable). Usa humanización por defecto (Bézier+jitter). Devuelve el outline actualizado tras la acción (auto-observe)."),
|
mcp.WithDescription("Click sobre el elemento por su #ref del outline de page_perceive (backendDOMNodeId estable). 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("port", mcp.Description("CDP port. Default 9333 (Chrome isolated del MCP); usa 9222 explícito solo para adjuntarte al navegador diario.")),
|
||||||
mcp.WithNumber("ref", mcp.Required(), mcp.Description("#ref del elemento (backendDOMNodeId) leído del outline de page_perceive.")),
|
mcp.WithNumber("ref", mcp.Required(), mcp.Description("#ref del elemento (backendDOMNodeId) leído del outline de page_perceive.")),
|
||||||
|
mcp.WithString("mode", mcp.Description("Velocidad: 'human' (default, Bézier+jitter anti-bot), 'fast' (movimiento reducido, scraping masivo), 'instant' (element.click() JS, sin eventos de ratón; también fallback si el elemento no tiene geometría).")),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *deps) handleDomClickRef(_ context.Context, _ mcp.CallToolRequest, a domClickRefArgs) (*mcp.CallToolResult, error) {
|
func (d *deps) handleDomClickRef(_ context.Context, _ mcp.CallToolRequest, a domClickRefArgs) (*mcp.CallToolResult, error) {
|
||||||
port := portOr(a.Port)
|
port := portOr(a.Port)
|
||||||
// TODO: preset de humanización por sesión (human/fast/instant)
|
|
||||||
err := d.withConn(port, func(c *browser.CDPConn) error {
|
err := d.withConn(port, func(c *browser.CDPConn) error {
|
||||||
return browser.CdpClickRef(c, a.Ref, browser.MouseHumanOpts{})
|
return browser.CdpClickRef(c, a.Ref, browser.MouseProfileForMode(a.Mode))
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mcp.NewToolResultError(err.Error()), nil
|
return mcp.NewToolResultError(err.Error()), nil
|
||||||
@@ -97,23 +99,24 @@ func (d *deps) handleDomTypeRef(_ context.Context, _ mcp.CallToolRequest, a domT
|
|||||||
// ---- dom_hover_ref (MUTA) — bucle percibir→actuar ----
|
// ---- dom_hover_ref (MUTA) — bucle percibir→actuar ----
|
||||||
|
|
||||||
type domHoverRefArgs struct {
|
type domHoverRefArgs struct {
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Ref int `json:"ref"`
|
Ref int `json:"ref"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func domHoverRefTool() mcp.Tool {
|
func domHoverRefTool() mcp.Tool {
|
||||||
return mcp.NewTool("dom_hover_ref",
|
return mcp.NewTool("dom_hover_ref",
|
||||||
mcp.WithDescription("Hover humanizado sobre el elemento por su #ref del outline de page_perceive (backendDOMNodeId estable). Usa humanización por defecto (Bézier+jitter). Devuelve el outline actualizado tras la acción (auto-observe)."),
|
mcp.WithDescription("Hover sobre el elemento por su #ref del outline de page_perceive (backendDOMNodeId estable). 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("port", mcp.Description("CDP port. Default 9333 (Chrome isolated del MCP); usa 9222 explícito solo para adjuntarte al navegador diario.")),
|
||||||
mcp.WithNumber("ref", mcp.Required(), mcp.Description("#ref del elemento (backendDOMNodeId) leído del outline de page_perceive.")),
|
mcp.WithNumber("ref", mcp.Required(), mcp.Description("#ref del elemento (backendDOMNodeId) leído del outline de page_perceive.")),
|
||||||
|
mcp.WithString("mode", mcp.Description("Velocidad: 'human' (default, Bézier+jitter), 'fast' (movimiento reducido), 'instant' (sin movimiento de ratón).")),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *deps) handleDomHoverRef(_ context.Context, _ mcp.CallToolRequest, a domHoverRefArgs) (*mcp.CallToolResult, error) {
|
func (d *deps) handleDomHoverRef(_ context.Context, _ mcp.CallToolRequest, a domHoverRefArgs) (*mcp.CallToolResult, error) {
|
||||||
port := portOr(a.Port)
|
port := portOr(a.Port)
|
||||||
// TODO: preset de humanización por sesión (human/fast/instant)
|
|
||||||
err := d.withConn(port, func(c *browser.CDPConn) error {
|
err := d.withConn(port, func(c *browser.CDPConn) error {
|
||||||
return browser.CdpHoverRef(c, a.Ref, browser.MouseHumanOpts{})
|
return browser.CdpHoverRef(c, a.Ref, browser.MouseProfileForMode(a.Mode))
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mcp.NewToolResultError(err.Error()), nil
|
return mcp.NewToolResultError(err.Error()), nil
|
||||||
@@ -266,6 +269,41 @@ func (d *deps) handleDomFindByText(_ context.Context, _ mcp.CallToolRequest, a d
|
|||||||
return mcp.NewToolResultText(sel), nil
|
return mcp.NewToolResultText(sel), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- dom_find_ref_by_text ----
|
||||||
|
|
||||||
|
type domFindRefByTextArgs struct {
|
||||||
|
Port int `json:"port"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func domFindRefByTextTool() mcp.Tool {
|
||||||
|
return mcp.NewTool("dom_find_ref_by_text",
|
||||||
|
mcp.WithDescription("Find the first element whose visible text matches and return its #ref (backendDOMNodeId) ready for dom_click_ref/dom_hover_ref — no fragile CSS selector. Also reports how many elements match (count>1 = ambiguous)."),
|
||||||
|
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).")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deps) handleDomFindRefByText(_ context.Context, _ mcp.CallToolRequest, a domFindRefByTextArgs) (*mcp.CallToolResult, error) {
|
||||||
|
if a.Text == "" {
|
||||||
|
return mcp.NewToolResultError("text is required"), nil
|
||||||
|
}
|
||||||
|
var ref, count int
|
||||||
|
err := d.withConn(portOr(a.Port), func(c *browser.CDPConn) error {
|
||||||
|
var e error
|
||||||
|
ref, count, e = browser.CdpFindRefByText(c, a.Text, browser.FindByTextOpts{})
|
||||||
|
return e
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return mcp.NewToolResultError(err.Error()), nil
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintf("ref=%d count=%d", ref, count)
|
||||||
|
if count > 1 {
|
||||||
|
msg += " (ambiguous: returning the first match; refine the text to disambiguate)"
|
||||||
|
}
|
||||||
|
return mcp.NewToolResultText(msg), nil
|
||||||
|
}
|
||||||
|
|
||||||
// ---- dom_wait_element ----
|
// ---- dom_wait_element ----
|
||||||
|
|
||||||
type domWaitElementArgs struct {
|
type domWaitElementArgs struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user