Compare commits
3 Commits
f0bfc3e300
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c5b81f711 | |||
| a48e262371 | |||
| fa1efe6fd5 |
@@ -1,2 +1,6 @@
|
||||
/browser_mcp
|
||||
*.log
|
||||
# registry.db sólo existe en la raíz del repo (regla db_locations). Si un test o el
|
||||
# binario lo crea aquí por un path relativo, es basura: ignorarlo evita trackearlo.
|
||||
registry.db
|
||||
operations.db*
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
name: browser_mcp
|
||||
lang: go
|
||||
domain: infra
|
||||
version: 0.7.0
|
||||
description: "Servidor MCP que expone control total del navegador via CDP (45 tools: navegación, DOM, cookies, iframes, teclado/scroll, diálogos, estado de sesión, selección determinista de pestaña, lectura compacta texto/AX nativa + bucle percibir→actuar por #ref con auto-observe, percepción y lectura de texto dentro de iframes, click por coordenadas, screenshot devuelto como image content que el LLM ve, y gestión del ciclo de vida de Chromium por perfil: listar masters en ejecución, lanzar un perfil concreto con o sin CDP, y cerrar limpio) reusando funciones del dominio browser del registry con un pool de conexiones CDP vivas. Por defecto opera sobre un Chrome aislado (puerto 9333) separado del navegador diario."
|
||||
version: 0.8.0
|
||||
description: "Servidor MCP que expone control total del navegador via CDP (46 tools: navegación, DOM, cookies, iframes, teclado/scroll, diálogos, estado de sesión, selección determinista de pestaña, modo de velocidad de sesión (browser_set_mode: 'auto' rápido por defecto / 'human' sigiloso anti-detección), lectura compacta texto/AX nativa + bucle percibir→actuar por #ref con auto-observe, percepción y lectura de texto dentro de iframes, click por coordenadas, screenshot devuelto como image content que el LLM ve, y gestión del ciclo de vida de Chromium por perfil: listar masters en ejecución, lanzar un perfil concreto con o sin CDP, y cerrar limpio) reusando funciones del dominio browser del registry con un pool de conexiones CDP vivas. Por defecto opera sobre un Chrome aislado (puerto 9333) separado del navegador diario."
|
||||
tags: [mcp, browser, cdp, automation, scraping]
|
||||
e2e_checks:
|
||||
- id: build
|
||||
@@ -118,12 +118,13 @@ podría manipular pestañas ajenas del usuario (banca, correo). Para evitarlo:
|
||||
- Para adjuntarte deliberadamente al navegador diario, pasa `port: 9222` explícito en cada
|
||||
tool. Hazlo solo con cuidado.
|
||||
|
||||
## Tools (45)
|
||||
## Tools (46)
|
||||
|
||||
### Sesión (`tools_session.go`)
|
||||
- `browser_launch` (MUTA) — lanza Chrome con CDP. args: port, headless, user_data_dir, url.
|
||||
- `browser_connect` — abre/poolea la conexión CDP del puerto. args: port.
|
||||
- `browser_disconnect` — cierra y descarta la conexión del puerto (no mata Chrome). args: port.
|
||||
- `browser_set_mode` — fija el modo de velocidad de sesión del puerto: `auto` (default, rápido) o `human` (sigiloso anti-detección). args: port, mode. Cada tool de acción puede overridearlo con su arg `mode`.
|
||||
|
||||
### Ciclo de vida por perfil (`tools_lifecycle.go`)
|
||||
Gestionan los Chromium del USUARIO por perfil (`Personal`, `Work`, ...), distintos del Chrome
|
||||
@@ -286,6 +287,24 @@ Funciones del dominio `browser` que NO se exponen como tools en esta versión, c
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v0.8.0 (2026-06-13) — Aceleración del manejo del navegador via CDP + flag de velocidad de
|
||||
sesión. (1) Nueva tool `browser_set_mode` (45 → 46 tools): fija el modo de velocidad por puerto
|
||||
en el pool — `auto` (default del MCP, rápido) vs `human` (sigiloso anti-detección). El modo se
|
||||
resuelve por acción con `effectiveMode`: arg `mode` de la tool > modo de sesión > `auto`. (2) Settle
|
||||
adaptativo: el sleep ciego fijo de 400ms tras cada acción mutante (`dom_click_ref`/`dom_type_ref`/
|
||||
`dom_hover_ref`/`dom_click_xy`) pasa a `settleForMode` — 60ms en `auto`, aleatorio 250-650ms en
|
||||
`human` (ritmo no-máquina), 0 en `instant`. (3) `dom_type_ref` ahora tiene arg `mode`: en `auto`
|
||||
usa `CdpTypeRefFast` (`Input.insertText`, un solo round-trip) y en `human` teclea carácter a
|
||||
carácter (`CdpTypeRef`) con pausas aleatorias. (4) `browser_launch_profile` reemplaza el `sleep(1s)`
|
||||
ciego por un poll del puerto CDP (`waitCDPPort`). Cambios en el dominio `browser` del registry que
|
||||
aprovecha el MCP: `Accessibility.enable`/`Network.enable`/`Page.enable` cacheados por conexión
|
||||
(`ensureAX`/`ensureNetwork`/`ensurePage` en `CDPConn`) — se eliminan round-trips redundantes en cada
|
||||
percepción/espera; `cdp_wait_load` pasa de polling de `document.readyState` cada 200ms a esperar el
|
||||
evento `Page.loadEventFired` (fast path si ya está `complete`); `sendCDP` adquiere timeout
|
||||
(`cdpCmdTimeout` 30s) para no colgar el tool indefinidamente; nuevas `CdpInsertText` y
|
||||
`CdpTypeRefFast` (camino rápido de escritura); el modo `auto` se añade al perfil de ratón
|
||||
(`MouseProfileForMode`) como alias rápido de `fast`. Smoke contra Chrome 9333: percepción #2 con
|
||||
enable cacheado 1.7ms (vs 3.7ms la #1), `wait_load` fast-path 245µs (vs ≥200ms del polling previo).
|
||||
- v0.7.0 (2026-06-10) — Ciclo de vida de Chromium por perfil (`tools_lifecycle.go`). Tres tools
|
||||
nuevas: `browser_list` (enumera los procesos master de Chromium leyendo `/proc/*/cmdline`,
|
||||
filtrando por `--user-data-dir` presente y `--type=` ausente), `browser_launch_profile` (lanza un
|
||||
|
||||
@@ -23,6 +23,7 @@ type connPool struct {
|
||||
pids map[int]int // puerto -> PID del Chrome lanzado por el MCP (solo los SUYOS)
|
||||
cancels map[int]func() // cancels de handlers persistentes (handle_dialog)
|
||||
dialogLogs map[int]*browser.DialogLog // log de diálogos auto-respondidos por puerto
|
||||
modes map[int]string // puerto -> modo de velocidad de sesión ("auto"|"human"|...)
|
||||
}
|
||||
|
||||
func newConnPool() *connPool {
|
||||
@@ -31,9 +32,25 @@ func newConnPool() *connPool {
|
||||
pids: map[int]int{},
|
||||
cancels: map[int]func(){},
|
||||
dialogLogs: map[int]*browser.DialogLog{},
|
||||
modes: map[int]string{},
|
||||
}
|
||||
}
|
||||
|
||||
// setMode fija el modo de velocidad de sesión para un puerto (lo lee
|
||||
// effectiveMode cuando una tool de acción no trae su propio arg `mode`).
|
||||
func (p *connPool) setMode(port int, mode string) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.modes[port] = mode
|
||||
}
|
||||
|
||||
// getMode devuelve el modo de sesión del puerto ("" si no se fijó ninguno).
|
||||
func (p *connPool) getMode(port int) string {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.modes[port]
|
||||
}
|
||||
|
||||
func (p *connPool) get(port int) (*browser.CDPConn, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
@@ -121,6 +138,7 @@ func (p *connPool) drop(port int) {
|
||||
_ = browser.CdpClose(c, pid)
|
||||
delete(p.conns, port)
|
||||
delete(p.pids, port)
|
||||
delete(p.modes, port)
|
||||
}
|
||||
|
||||
// connectTarget descarta la conexión actual del puerto y reconecta a un target
|
||||
@@ -188,6 +206,7 @@ func (p *connPool) closeAll() {
|
||||
p.pids = map[int]int{}
|
||||
p.cancels = map[int]func(){}
|
||||
p.dialogLogs = map[int]*browser.DialogLog{}
|
||||
p.modes = map[int]string{}
|
||||
}
|
||||
|
||||
// isConnErr reconoce errores de conexión CDP muerta para reintentar UNA vez.
|
||||
|
||||
+65
-15
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
@@ -29,9 +30,40 @@ func registerDomTools(s *server.MCPServer, d *deps) {
|
||||
}
|
||||
}
|
||||
|
||||
// settleDelay es la espera breve tras una acción mutante antes de re-percibir,
|
||||
// dando tiempo a que el DOM se asiente (navegación, focus, repaint).
|
||||
const settleDelay = 400 * time.Millisecond
|
||||
// defaultMode es el modo de velocidad cuando ni la llamada ni la sesión fijan uno.
|
||||
// "auto" = rápido (movimiento de ratón mínimo, escritura en un solo evento, settle
|
||||
// breve) — el modo por defecto del MCP. "human" (Bézier + esperas aleatorias) se
|
||||
// activa explícitamente vía browser_set_mode o el arg `mode` cuando un sitio
|
||||
// aplique detección anti-bot fuerte.
|
||||
const defaultMode = "auto"
|
||||
|
||||
// effectiveMode resuelve el modo de velocidad de una acción: el arg de la llamada
|
||||
// gana; si está vacío, el modo de sesión fijado por browser_set_mode; si tampoco
|
||||
// hay, defaultMode.
|
||||
func (d *deps) effectiveMode(port int, callMode string) string {
|
||||
if callMode != "" {
|
||||
return callMode
|
||||
}
|
||||
if m := d.pool.getMode(port); m != "" {
|
||||
return m
|
||||
}
|
||||
return defaultMode
|
||||
}
|
||||
|
||||
// settleForMode es la espera tras una acción mutante antes de re-percibir, dando
|
||||
// tiempo a que el DOM se asiente (navegación, focus, repaint). En "human" es
|
||||
// ALEATORIA (250-650ms) para no exhibir un ritmo de máquina; en auto/fast es breve
|
||||
// y fija (60ms); en "instant" es nula.
|
||||
func settleForMode(mode string) time.Duration {
|
||||
switch mode {
|
||||
case "human", "":
|
||||
return time.Duration(250+rand.Intn(401)) * time.Millisecond // 250..650
|
||||
case "instant":
|
||||
return 0
|
||||
default: // auto, fast
|
||||
return 60 * time.Millisecond
|
||||
}
|
||||
}
|
||||
|
||||
// ---- dom_click_ref (MUTA) — bucle percibir→actuar ----
|
||||
|
||||
@@ -46,19 +78,22 @@ func domClickRefTool() mcp.Tool {
|
||||
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("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).")),
|
||||
mcp.WithString("mode", mcp.Description("Velocidad: 'auto' (default de sesión: movimiento de ratón reducido, rápido), 'human' (Bézier+jitter+pausas aleatorias anti-bot, para detección fuerte), 'instant' (element.click() JS, sin eventos de ratón; también fallback si el elemento no tiene geometría). Vacío = modo de sesión (browser_set_mode) o 'auto'.")),
|
||||
)
|
||||
}
|
||||
|
||||
func (d *deps) handleDomClickRef(_ context.Context, _ mcp.CallToolRequest, a domClickRefArgs) (*mcp.CallToolResult, error) {
|
||||
port := portOr(a.Port)
|
||||
mode := d.effectiveMode(port, a.Mode)
|
||||
err := d.withConn(port, func(c *browser.CDPConn) error {
|
||||
return browser.CdpClickRef(c, a.Ref, browser.MouseProfileForMode(a.Mode))
|
||||
return browser.CdpClickRef(c, a.Ref, browser.MouseProfileForMode(mode))
|
||||
})
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
time.Sleep(settleDelay)
|
||||
if dl := settleForMode(mode); dl > 0 {
|
||||
time.Sleep(dl)
|
||||
}
|
||||
outline, _ := d.perceiveOutline(port, 8000)
|
||||
return mcp.NewToolResultText("clicked ref " + fmt.Sprint(a.Ref) + "\n\n" + outline), nil
|
||||
}
|
||||
@@ -69,6 +104,7 @@ type domTypeRefArgs struct {
|
||||
Port int `json:"port"`
|
||||
Ref int `json:"ref"`
|
||||
Text string `json:"text"`
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
|
||||
func domTypeRefTool() mcp.Tool {
|
||||
@@ -77,6 +113,7 @@ func domTypeRefTool() mcp.Tool {
|
||||
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.WithString("text", mcp.Required(), mcp.Description("Texto a escribir en el elemento.")),
|
||||
mcp.WithString("mode", mcp.Description("Velocidad: 'auto' (default de sesión, escribe en un solo evento Input.insertText — rápido) o 'human' (caracter a caracter con pausas aleatorias, anti-detección). Vacío = modo de sesión (browser_set_mode) o 'auto'.")),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -85,14 +122,21 @@ func (d *deps) handleDomTypeRef(_ context.Context, _ mcp.CallToolRequest, a domT
|
||||
return mcp.NewToolResultError("text is required"), nil
|
||||
}
|
||||
port := portOr(a.Port)
|
||||
// TODO: preset de humanización por sesión (human/fast/instant)
|
||||
mode := d.effectiveMode(port, a.Mode)
|
||||
err := d.withConn(port, func(c *browser.CDPConn) error {
|
||||
return browser.CdpTypeRef(c, a.Ref, a.Text)
|
||||
// human => teclea caracter a caracter (eventos de tecla reales + ritmo
|
||||
// irregular). auto/fast/instant => inserta todo en un solo round-trip.
|
||||
if mode == "human" {
|
||||
return browser.CdpTypeRef(c, a.Ref, a.Text)
|
||||
}
|
||||
return browser.CdpTypeRefFast(c, a.Ref, a.Text)
|
||||
})
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
time.Sleep(settleDelay)
|
||||
if dl := settleForMode(mode); dl > 0 {
|
||||
time.Sleep(dl)
|
||||
}
|
||||
outline, _ := d.perceiveOutline(port, 8000)
|
||||
return mcp.NewToolResultText("typed into ref " + fmt.Sprint(a.Ref) + "\n\n" + outline), nil
|
||||
}
|
||||
@@ -110,19 +154,22 @@ func domHoverRefTool() mcp.Tool {
|
||||
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("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).")),
|
||||
mcp.WithString("mode", mcp.Description("Velocidad: 'auto' (default de sesión: movimiento reducido, rápido), 'human' (Bézier+jitter+pausas aleatorias anti-bot), 'instant' (sin movimiento de ratón). Vacío = modo de sesión (browser_set_mode) o 'auto'.")),
|
||||
)
|
||||
}
|
||||
|
||||
func (d *deps) handleDomHoverRef(_ context.Context, _ mcp.CallToolRequest, a domHoverRefArgs) (*mcp.CallToolResult, error) {
|
||||
port := portOr(a.Port)
|
||||
mode := d.effectiveMode(port, a.Mode)
|
||||
err := d.withConn(port, func(c *browser.CDPConn) error {
|
||||
return browser.CdpHoverRef(c, a.Ref, browser.MouseProfileForMode(a.Mode))
|
||||
return browser.CdpHoverRef(c, a.Ref, browser.MouseProfileForMode(mode))
|
||||
})
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
time.Sleep(settleDelay)
|
||||
if dl := settleForMode(mode); dl > 0 {
|
||||
time.Sleep(dl)
|
||||
}
|
||||
outline, _ := d.perceiveOutline(port, 8000)
|
||||
return mcp.NewToolResultText("hovered ref " + fmt.Sprint(a.Ref) + "\n\n" + outline), nil
|
||||
}
|
||||
@@ -142,19 +189,22 @@ func domClickXYTool() mcp.Tool {
|
||||
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).")),
|
||||
mcp.WithString("mode", mcp.Description("Velocidad: 'auto' (default de sesión: movimiento reducido, rápido), 'human' (Bézier+jitter+pausas aleatorias anti-bot), 'instant' (sin movimiento de ratón). Vacío = modo de sesión (browser_set_mode) o 'auto'.")),
|
||||
)
|
||||
}
|
||||
|
||||
func (d *deps) handleDomClickXY(_ context.Context, _ mcp.CallToolRequest, a domClickXYArgs) (*mcp.CallToolResult, error) {
|
||||
port := portOr(a.Port)
|
||||
mode := d.effectiveMode(port, a.Mode)
|
||||
err := d.withConn(port, func(c *browser.CDPConn) error {
|
||||
return browser.CdpClickXYHuman(c, a.X, a.Y, browser.MouseProfileForMode(a.Mode))
|
||||
return browser.CdpClickXYHuman(c, a.X, a.Y, browser.MouseProfileForMode(mode))
|
||||
})
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
time.Sleep(settleDelay)
|
||||
if dl := settleForMode(mode); dl > 0 {
|
||||
time.Sleep(dl)
|
||||
}
|
||||
outline, _ := d.perceiveOutline(port, 8000)
|
||||
return mcp.NewToolResultText(fmt.Sprintf("clicked at (%g, %g)\n\n%s", a.X, a.Y, outline)), nil
|
||||
}
|
||||
|
||||
+20
-7
@@ -324,16 +324,16 @@ func (d *deps) handleBrowserLaunchProfile(_ context.Context, _ mcp.CallToolReque
|
||||
pid := cmd.Process.Pid
|
||||
_ = cmd.Process.Release()
|
||||
|
||||
// Give Chromium a moment to come up. If it forwarded to an existing master the
|
||||
// child exits fast; the launched pid is still informative.
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// When cdp=true, opportunistically confirm the port responds (best-effort: a
|
||||
// forwarded launch may not bind the port if the master had no CDP).
|
||||
// Give Chromium a moment to come up. With CDP we poll the port instead of a
|
||||
// blind 1s sleep: we return as soon as it responds (best-effort: a forwarded
|
||||
// launch may not bind the port if the master had no CDP). Without CDP there's
|
||||
// no port to poll, so we give the window a short margin to appear / forward.
|
||||
if a.CDP && note == "" {
|
||||
if !cdpPortResponds(cdpPort) {
|
||||
if !waitCDPPort(cdpPort, 5*time.Second) {
|
||||
note = "cdp port not confirmed listening yet"
|
||||
}
|
||||
} else {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
||||
|
||||
out := map[string]any{
|
||||
@@ -452,6 +452,19 @@ func processAlive(pid int) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// waitCDPPort polls the CDP port until it accepts a TCP connection or the timeout
|
||||
// elapses. Replaces a blind sleep: returns as soon as Chromium binds the port.
|
||||
func waitCDPPort(port int, timeout time.Duration) bool {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
if cdpPortResponds(port) {
|
||||
return true
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return cdpPortResponds(port)
|
||||
}
|
||||
|
||||
// cdpPortResponds reports whether something is listening on the CDP port on
|
||||
// 127.0.0.1. Single TCP dial with a short timeout; best-effort confirmation only.
|
||||
func cdpPortResponds(port int) bool {
|
||||
|
||||
+30
-1
@@ -12,13 +12,15 @@ import (
|
||||
"fn-registry/functions/browser"
|
||||
)
|
||||
|
||||
// registerSessionTools wires browser_launch (MUTA), browser_connect, browser_disconnect.
|
||||
// registerSessionTools wires browser_launch (MUTA), browser_connect, browser_disconnect,
|
||||
// browser_set_mode.
|
||||
func registerSessionTools(s *server.MCPServer, d *deps) {
|
||||
if !d.readOnly {
|
||||
s.AddTool(launchTool(), mcp.NewTypedToolHandler(d.handleLaunch))
|
||||
}
|
||||
s.AddTool(connectTool(), mcp.NewTypedToolHandler(d.handleConnect))
|
||||
s.AddTool(disconnectTool(), mcp.NewTypedToolHandler(d.handleDisconnect))
|
||||
s.AddTool(setModeTool(), mcp.NewTypedToolHandler(d.handleSetMode))
|
||||
}
|
||||
|
||||
// maxLaunchedChromes es el tope duro de instancias Chrome que el MCP puede tener
|
||||
@@ -142,3 +144,30 @@ func (d *deps) handleDisconnect(_ context.Context, _ mcp.CallToolRequest, a disc
|
||||
}
|
||||
return mcp.NewToolResultText(msg), nil
|
||||
}
|
||||
|
||||
// ---- browser_set_mode ----
|
||||
|
||||
type setModeArgs struct {
|
||||
Port int `json:"port"`
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
|
||||
func setModeTool() mcp.Tool {
|
||||
return mcp.NewTool("browser_set_mode",
|
||||
mcp.WithDescription("Fija el modo de velocidad de SESIÓN de las acciones del navegador en este puerto. 'auto' (default del MCP) = rápido: movimiento de ratón mínimo, escritura en un solo evento (Input.insertText) y esperas breves — para scraping y automatización propia. 'human' = sigiloso anti-detección: trayectoria de ratón Bézier con jitter, escritura carácter a carácter y esperas ALEATORIAS entre acción y percepción — actívalo cuando un sitio aplique detección anti-bot fuerte. El arg 'mode' de cada tool de acción (dom_click_ref, dom_type_ref, dom_hover_ref, dom_click_xy) sigue ganando puntualmente sobre este ajuste de sesión."),
|
||||
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("mode", mcp.Required(), mcp.Description("'auto' (rápido, default) o 'human' (sigiloso, anti-detección). También admite 'fast' (alias de auto) e 'instant' (sin movimiento de ratón) para casos puntuales.")),
|
||||
)
|
||||
}
|
||||
|
||||
func (d *deps) handleSetMode(_ context.Context, _ mcp.CallToolRequest, a setModeArgs) (*mcp.CallToolResult, error) {
|
||||
switch a.Mode {
|
||||
case "auto", "human", "fast", "instant":
|
||||
// válido
|
||||
default:
|
||||
return mcp.NewToolResultError("mode debe ser 'auto' o 'human' (también 'fast'/'instant')"), nil
|
||||
}
|
||||
port := portOr(a.Port)
|
||||
d.pool.setMode(port, a.Mode)
|
||||
return mcp.NewToolResultText(fmt.Sprintf("session mode set to %q for port=%d (cada tool de acción puede overridearlo con su arg mode)", a.Mode, port)), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user