package main import ( "context" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" "fn-registry/functions/browser" ) const htmlMax = 200_000 // registerReadTools wires page_get_html, page_eval_js (MUTA), page_screenshot. func registerReadTools(s *server.MCPServer, d *deps) { s.AddTool(pageGetHTMLTool(), mcp.NewTypedToolHandler(d.handlePageGetHTML)) s.AddTool(pageScreenshotTool(), mcp.NewTypedToolHandler(d.handlePageScreenshot)) if !d.readOnly { s.AddTool(pageEvalJSTool(), mcp.NewTypedToolHandler(d.handlePageEvalJS)) } } // ---- page_get_html ---- type pageGetHTMLArgs struct { Port int `json:"port"` } func pageGetHTMLTool() mcp.Tool { return mcp.NewTool("page_get_html", mcp.WithDescription("Return the current page's full serialized HTML (outerHTML). Truncated to 200000 chars."), mcp.WithNumber("port", mcp.Description("CDP port. Default 9222.")), ) } func (d *deps) handlePageGetHTML(_ context.Context, _ mcp.CallToolRequest, a pageGetHTMLArgs) (*mcp.CallToolResult, error) { var html string err := d.withConn(portOr(a.Port), func(c *browser.CDPConn) error { var e error html, e = browser.CdpGetHTML(c) return e }) if err != nil { return mcp.NewToolResultError(err.Error()), nil } return mcp.NewToolResultText(truncate(html, htmlMax)), nil } // ---- page_eval_js (MUTA) ---- type pageEvalJSArgs struct { Port int `json:"port"` Expression string `json:"expression"` } func pageEvalJSTool() mcp.Tool { return mcp.NewTool("page_eval_js", mcp.WithDescription("Evaluate a JavaScript expression in the page context via Runtime.evaluate. Returns the stringified result."), mcp.WithNumber("port", mcp.Description("CDP port. Default 9222.")), mcp.WithString("expression", mcp.Required(), mcp.Description("JavaScript expression to evaluate.")), ) } func (d *deps) handlePageEvalJS(_ context.Context, _ mcp.CallToolRequest, a pageEvalJSArgs) (*mcp.CallToolResult, error) { if a.Expression == "" { return mcp.NewToolResultError("expression is required"), nil } var res string err := d.withConn(portOr(a.Port), func(c *browser.CDPConn) error { var e error res, e = browser.CdpEvaluate(c, a.Expression) return e }) if err != nil { return mcp.NewToolResultError(err.Error()), nil } return mcp.NewToolResultText(truncate(res, htmlMax)), nil } // ---- page_screenshot ---- type pageScreenshotArgs struct { Port int `json:"port"` Path string `json:"path"` FullPage bool `json:"full_page"` } func pageScreenshotTool() mcp.Tool { return mcp.NewTool("page_screenshot", mcp.WithDescription("Capture a screenshot of the current page and write it to a local path (.png/.jpg)."), mcp.WithNumber("port", mcp.Description("CDP port. Default 9222.")), mcp.WithString("path", mcp.Required(), mcp.Description("Output file path (.png or .jpg).")), mcp.WithBoolean("full_page", mcp.Description("Capture the full scroll height instead of just the viewport.")), ) } func (d *deps) handlePageScreenshot(_ context.Context, _ mcp.CallToolRequest, a pageScreenshotArgs) (*mcp.CallToolResult, error) { if a.Path == "" { return mcp.NewToolResultError("path is required"), nil } opts := browser.CdpScreenshotOpts{FullPage: a.FullPage} err := d.withConn(portOr(a.Port), func(c *browser.CDPConn) error { return browser.CdpScreenshot(c, a.Path, opts) }) if err != nil { return mcp.NewToolResultError(err.Error()), nil } return mcp.NewToolResultText("screenshot saved to " + a.Path), nil }