feat: 4 tools nuevas + browser_list enriquecido

Tools nuevas (wrappers finos sobre funciones del registry functions/browser):
- page_collect_console  -> cdp_collect_console (console + exceptions + log, snapshot)
- page_pdf              -> cdp_print_pdf (Page.printToPDF a archivo)
- dom_select_option     -> cdp_select_option (<select> por value/texto + input/change)
- dom_set_files         -> cdp_set_file_input (subir archivos a <input type=file>)

browser_list ahora enriquece cada master con CDP con pages (nº de page targets),
active_title y active_url via GET /json (best-effort: si el puerto no responde
los campos quedan a cero y el listado de procesos no falla).

Total tools: 46 -> 50.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Egutierrez
2026-06-16 20:25:35 +02:00
parent 15949bf4ed
commit 6b7f71c39f
3 changed files with 186 additions and 2 deletions
+81
View File
@@ -3,6 +3,8 @@ package main
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"github.com/mark3labs/mcp-go/mcp"
@@ -20,12 +22,91 @@ func registerReadTools(s *server.MCPServer, d *deps) {
s.AddTool(pageGetTextTool(), mcp.NewTypedToolHandler(d.handlePageGetText))
s.AddTool(pagePerceiveTool(), mcp.NewTypedToolHandler(d.handlePagePerceive))
s.AddTool(pageScreenshotTool(), mcp.NewTypedToolHandler(d.handlePageScreenshot))
s.AddTool(pageCollectConsoleTool(), mcp.NewTypedToolHandler(d.handlePageCollectConsole))
s.AddTool(pagePDFTool(), mcp.NewTypedToolHandler(d.handlePagePDF))
if !d.readOnly {
s.AddTool(pageEvalJSTool(), mcp.NewTypedToolHandler(d.handlePageEvalJS))
}
}
// ---- page_collect_console ----
type pageCollectConsoleArgs struct {
Port int `json:"port"`
DurationMs int `json:"duration_ms"`
}
func pageCollectConsoleTool() mcp.Tool {
return mcp.NewTool("page_collect_console",
mcp.WithDescription("Capture the page's console output (console.log/info/warn/error), uncaught JS exceptions and browser log entries during a time window, and return them as JSON. It is a SNAPSHOT: it records what happens during duration_ms AFTER the call starts, not past history — so trigger the action you want to observe (reload, click) right before or during the window. Use this to debug why a page misbehaves without flying blind."),
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("duration_ms", mcp.Description("Capture window in milliseconds. Default 1500.")),
)
}
func (d *deps) handlePageCollectConsole(_ context.Context, _ mcp.CallToolRequest, a pageCollectConsoleArgs) (*mcp.CallToolResult, error) {
var entries []browser.ConsoleEntry
err := d.withConn(portOr(a.Port), func(c *browser.CDPConn) error {
var e error
entries, e = browser.CdpCollectConsole(c, a.DurationMs)
return e
})
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
if entries == nil {
entries = []browser.ConsoleEntry{}
}
b, _ := json.MarshalIndent(entries, "", " ")
return mcp.NewToolResultText(truncate(string(b), htmlMax)), nil
}
// ---- page_pdf ----
type pagePDFArgs struct {
Port int `json:"port"`
Path string `json:"path"`
Landscape bool `json:"landscape"`
PrintBackground bool `json:"print_background"`
Scale float64 `json:"scale"`
}
func pagePDFTool() mcp.Tool {
return mcp.NewTool("page_pdf",
mcp.WithDescription("Render the current page to a PDF (Page.printToPDF) and write it to a local file path. Use for archiving an article/invoice/report exactly as laid out, when a screenshot is not enough (multi-page, selectable text)."),
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("path", mcp.Required(), mcp.Description("Output .pdf file path.")),
mcp.WithBoolean("landscape", mcp.Description("Landscape orientation. Default false (portrait).")),
mcp.WithBoolean("print_background", mcp.Description("Include background graphics/colors. Default false.")),
mcp.WithNumber("scale", mcp.Description("Render scale. Default 1.0.")),
)
}
func (d *deps) handlePagePDF(_ context.Context, _ mcp.CallToolRequest, a pagePDFArgs) (*mcp.CallToolResult, error) {
if a.Path == "" {
return mcp.NewToolResultError("path is required"), nil
}
opts := browser.CdpPrintPDFOpts{
Landscape: a.Landscape,
PrintBackground: a.PrintBackground,
Scale: a.Scale,
}
var data []byte
err := d.withConn(portOr(a.Port), func(c *browser.CDPConn) error {
var e error
data, e = browser.CdpPrintPDF(c, opts)
return e
})
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
if e := os.WriteFile(a.Path, data, 0o644); e != nil {
return mcp.NewToolResultError("saving pdf to " + a.Path + ": " + e.Error()), nil
}
return mcp.NewToolResultText(fmt.Sprintf("pdf saved to %s (%d bytes)", a.Path, len(data))), nil
}
// ---- page_get_text ----
type pageGetTextArgs struct {