feat: browser_mcp — servidor MCP de control de navegador CDP (33 tools + pool de conexiones)
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"fn-registry/functions/browser"
|
||||
)
|
||||
|
||||
// connPool reusa conexiones CDP entre invocaciones de tools. Clave = puerto CDP.
|
||||
// Una conexión = una sesión viva a una tab "page". Mantenerla evita pagar el
|
||||
// handshake WebSocket en cada tool y preserva estado (event handlers, contexto).
|
||||
type connPool struct {
|
||||
mu sync.Mutex
|
||||
conns map[int]*browser.CDPConn
|
||||
cancels map[int]func() // cancels de handlers persistentes (handle_dialog)
|
||||
}
|
||||
|
||||
func newConnPool() *connPool {
|
||||
return &connPool{conns: map[int]*browser.CDPConn{}, cancels: map[int]func(){}}
|
||||
}
|
||||
|
||||
func (p *connPool) get(port int) (*browser.CDPConn, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if c, ok := p.conns[port]; ok && c != nil {
|
||||
return c, nil
|
||||
}
|
||||
c, err := browser.CdpConnect(port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.conns[port] = c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (p *connPool) drop(port int) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if cancel, ok := p.cancels[port]; ok && cancel != nil {
|
||||
cancel()
|
||||
delete(p.cancels, port)
|
||||
}
|
||||
if c, ok := p.conns[port]; ok && c != nil {
|
||||
_ = browser.CdpClose(c, 0)
|
||||
delete(p.conns, port)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *connPool) setCancel(port int, cancel func()) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if old := p.cancels[port]; old != nil {
|
||||
old()
|
||||
}
|
||||
p.cancels[port] = cancel
|
||||
}
|
||||
|
||||
func (p *connPool) closeAll() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
for port, c := range p.conns {
|
||||
if cancel := p.cancels[port]; cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
if c != nil {
|
||||
_ = browser.CdpClose(c, 0)
|
||||
}
|
||||
}
|
||||
p.conns = map[int]*browser.CDPConn{}
|
||||
p.cancels = map[int]func(){}
|
||||
}
|
||||
|
||||
// isConnErr reconoce errores de conexión CDP muerta para reintentar UNA vez.
|
||||
func isConnErr(err error) bool {
|
||||
s := err.Error()
|
||||
return strings.Contains(s, "connection close") || strings.Contains(s, "broken pipe") ||
|
||||
strings.Contains(s, "use of closed") || strings.Contains(s, "ws read") || strings.Contains(s, "EOF")
|
||||
}
|
||||
Reference in New Issue
Block a user