feat: browser_mcp — servidor MCP de control de navegador CDP (33 tools + pool de conexiones)
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
|
||||
"fn-registry/functions/browser"
|
||||
)
|
||||
|
||||
// registerSessionTools wires browser_launch (MUTA), browser_connect, browser_disconnect.
|
||||
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))
|
||||
}
|
||||
|
||||
// ---- browser_launch (MUTA) ----
|
||||
|
||||
type launchArgs struct {
|
||||
Port int `json:"port"`
|
||||
Headless bool `json:"headless"`
|
||||
UserDataDir string `json:"user_data_dir"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func launchTool() mcp.Tool {
|
||||
return mcp.NewTool("browser_launch",
|
||||
mcp.WithDescription("Launch a Chrome/Chromium instance with CDP remote debugging enabled. Returns the launched PID. Waits up to 15s for the CDP port to be ready."),
|
||||
mcp.WithNumber("port", mcp.Description("CDP remote debugging port. Default 9222.")),
|
||||
mcp.WithBoolean("headless", mcp.Description("Run headless (--headless=new). Default false.")),
|
||||
mcp.WithString("user_data_dir", mcp.Description("Chrome profile directory. Empty = /tmp/chrome-cdp-profile.")),
|
||||
mcp.WithString("url", mcp.Description("Optional initial URL to open on launch.")),
|
||||
)
|
||||
}
|
||||
|
||||
func (d *deps) handleLaunch(_ context.Context, _ mcp.CallToolRequest, a launchArgs) (*mcp.CallToolResult, error) {
|
||||
opts := browser.ChromeLaunchOpts{
|
||||
Port: portOr(a.Port),
|
||||
Headless: a.Headless,
|
||||
UserDataDir: a.UserDataDir,
|
||||
}
|
||||
if a.URL != "" {
|
||||
opts.ExtraArgs = append(opts.ExtraArgs, a.URL)
|
||||
}
|
||||
pid, err := browser.ChromeLaunch(opts)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
return mcp.NewToolResultText(fmt.Sprintf("launched pid=%d port=%d", pid, opts.Port)), nil
|
||||
}
|
||||
|
||||
// ---- browser_connect ----
|
||||
|
||||
type connectArgs struct {
|
||||
Port int `json:"port"`
|
||||
}
|
||||
|
||||
func connectTool() mcp.Tool {
|
||||
return mcp.NewTool("browser_connect",
|
||||
mcp.WithDescription("Open (and pool) a CDP WebSocket connection to a running Chrome's first 'page' tab on the given port. Subsequent tools reuse this live session."),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9222.")),
|
||||
)
|
||||
}
|
||||
|
||||
func (d *deps) handleConnect(_ context.Context, _ mcp.CallToolRequest, a connectArgs) (*mcp.CallToolResult, error) {
|
||||
port := portOr(a.Port)
|
||||
if _, err := d.pool.get(port); err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
return mcp.NewToolResultText(fmt.Sprintf("connected port=%d", port)), nil
|
||||
}
|
||||
|
||||
// ---- browser_disconnect ----
|
||||
|
||||
type disconnectArgs struct {
|
||||
Port int `json:"port"`
|
||||
}
|
||||
|
||||
func disconnectTool() mcp.Tool {
|
||||
return mcp.NewTool("browser_disconnect",
|
||||
mcp.WithDescription("Close and drop the pooled CDP connection for the given port (cancels any armed dialog handler). Does NOT kill Chrome."),
|
||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9222.")),
|
||||
)
|
||||
}
|
||||
|
||||
func (d *deps) handleDisconnect(_ context.Context, _ mcp.CallToolRequest, a disconnectArgs) (*mcp.CallToolResult, error) {
|
||||
port := portOr(a.Port)
|
||||
d.pool.drop(port)
|
||||
return mcp.NewToolResultText(fmt.Sprintf("disconnected port=%d", port)), nil
|
||||
}
|
||||
Reference in New Issue
Block a user