// pkg/mcp/tools_webview.go package mcp import ( "bytes" "context" "encoding/base64" "image" "image/draw" "image/png" "math" core "dappco.re/go/core" coreerr "dappco.re/go/core/log" "forge.lthn.ai/core/gui/pkg/webview" "forge.lthn.ai/core/gui/pkg/window" "github.com/modelcontextprotocol/go-sdk/mcp" ) // --- webview_eval --- type WebviewEvalInput struct { Window string `json:"window"` Script string `json:"script"` } type WebviewEvalOutput struct { Result any `json:"result"` Window string `json:"window"` } func (s *Subsystem) webviewEval(_ context.Context, _ *mcp.CallToolRequest, input WebviewEvalInput) (*mcp.CallToolResult, WebviewEvalOutput, error) { r := s.core.Action("webview.evaluate").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskEvaluate{Window: input.Window, Script: input.Script}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewEvalOutput{}, e } return nil, WebviewEvalOutput{}, nil } return nil, WebviewEvalOutput{Result: r.Value, Window: input.Window}, nil } // --- webview_click --- type WebviewClickInput struct { Window string `json:"window"` Selector string `json:"selector"` } type WebviewClickOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewClick(_ context.Context, _ *mcp.CallToolRequest, input WebviewClickInput) (*mcp.CallToolResult, WebviewClickOutput, error) { r := s.core.Action("webview.click").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskClick{Window: input.Window, Selector: input.Selector}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewClickOutput{}, e } return nil, WebviewClickOutput{}, nil } return nil, WebviewClickOutput{Success: true}, nil } // --- webview_type --- type WebviewTypeInput struct { Window string `json:"window"` Selector string `json:"selector"` Text string `json:"text"` } type WebviewTypeOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewType(_ context.Context, _ *mcp.CallToolRequest, input WebviewTypeInput) (*mcp.CallToolResult, WebviewTypeOutput, error) { r := s.core.Action("webview.type").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskType{Window: input.Window, Selector: input.Selector, Text: input.Text}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewTypeOutput{}, e } return nil, WebviewTypeOutput{}, nil } return nil, WebviewTypeOutput{Success: true}, nil } // --- webview_navigate --- type WebviewNavigateInput struct { Window string `json:"window"` URL string `json:"url"` } type WebviewNavigateOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewNavigate(_ context.Context, _ *mcp.CallToolRequest, input WebviewNavigateInput) (*mcp.CallToolResult, WebviewNavigateOutput, error) { r := s.core.Action("webview.navigate").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskNavigate{Window: input.Window, URL: input.URL}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewNavigateOutput{}, e } return nil, WebviewNavigateOutput{}, nil } return nil, WebviewNavigateOutput{Success: true}, nil } // --- webview_screenshot --- type WebviewScreenshotInput struct { Window string `json:"window"` } type WebviewScreenshotOutput struct { Base64 string `json:"base64"` MimeType string `json:"mimeType"` } func (s *Subsystem) webviewScreenshot(_ context.Context, _ *mcp.CallToolRequest, input WebviewScreenshotInput) (*mcp.CallToolResult, WebviewScreenshotOutput, error) { r := s.core.Action("webview.screenshot").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskScreenshot{Window: input.Window}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewScreenshotOutput{}, e } return nil, WebviewScreenshotOutput{}, nil } sr, ok := r.Value.(webview.ScreenshotResult) if !ok { return nil, WebviewScreenshotOutput{}, coreerr.E("mcp.webviewScreenshot", "unexpected result type", nil) } return nil, WebviewScreenshotOutput{Base64: sr.Base64, MimeType: sr.MimeType}, nil } // --- webview_scroll --- type WebviewScrollInput struct { Window string `json:"window"` X int `json:"x"` Y int `json:"y"` } type WebviewScrollOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewScroll(_ context.Context, _ *mcp.CallToolRequest, input WebviewScrollInput) (*mcp.CallToolResult, WebviewScrollOutput, error) { r := s.core.Action("webview.scroll").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskScroll{Window: input.Window, X: input.X, Y: input.Y}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewScrollOutput{}, e } return nil, WebviewScrollOutput{}, nil } return nil, WebviewScrollOutput{Success: true}, nil } // --- webview_hover --- type WebviewHoverInput struct { Window string `json:"window"` Selector string `json:"selector"` } type WebviewHoverOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewHover(_ context.Context, _ *mcp.CallToolRequest, input WebviewHoverInput) (*mcp.CallToolResult, WebviewHoverOutput, error) { r := s.core.Action("webview.hover").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskHover{Window: input.Window, Selector: input.Selector}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewHoverOutput{}, e } return nil, WebviewHoverOutput{}, nil } return nil, WebviewHoverOutput{Success: true}, nil } // --- webview_select --- type WebviewSelectInput struct { Window string `json:"window"` Selector string `json:"selector"` Value string `json:"value"` } type WebviewSelectOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewSelect(_ context.Context, _ *mcp.CallToolRequest, input WebviewSelectInput) (*mcp.CallToolResult, WebviewSelectOutput, error) { r := s.core.Action("webview.select").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskSelect{Window: input.Window, Selector: input.Selector, Value: input.Value}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewSelectOutput{}, e } return nil, WebviewSelectOutput{}, nil } return nil, WebviewSelectOutput{Success: true}, nil } // --- webview_check --- type WebviewCheckInput struct { Window string `json:"window"` Selector string `json:"selector"` Checked bool `json:"checked"` } type WebviewCheckOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewCheck(_ context.Context, _ *mcp.CallToolRequest, input WebviewCheckInput) (*mcp.CallToolResult, WebviewCheckOutput, error) { r := s.core.Action("webview.check").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskCheck{Window: input.Window, Selector: input.Selector, Checked: input.Checked}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewCheckOutput{}, e } return nil, WebviewCheckOutput{}, nil } return nil, WebviewCheckOutput{Success: true}, nil } // --- webview_upload --- type WebviewUploadInput struct { Window string `json:"window"` Selector string `json:"selector"` Paths []string `json:"paths"` } type WebviewUploadOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewUpload(_ context.Context, _ *mcp.CallToolRequest, input WebviewUploadInput) (*mcp.CallToolResult, WebviewUploadOutput, error) { r := s.core.Action("webview.uploadFile").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskUploadFile{Window: input.Window, Selector: input.Selector, Paths: input.Paths}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewUploadOutput{}, e } return nil, WebviewUploadOutput{}, nil } return nil, WebviewUploadOutput{Success: true}, nil } // --- webview_viewport --- type WebviewViewportInput struct { Window string `json:"window"` Width int `json:"width"` Height int `json:"height"` } type WebviewViewportOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewViewport(_ context.Context, _ *mcp.CallToolRequest, input WebviewViewportInput) (*mcp.CallToolResult, WebviewViewportOutput, error) { r := s.core.Action("webview.setViewport").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskSetViewport{Window: input.Window, Width: input.Width, Height: input.Height}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewViewportOutput{}, e } return nil, WebviewViewportOutput{}, nil } return nil, WebviewViewportOutput{Success: true}, nil } // --- webview_console --- type WebviewConsoleInput struct { Window string `json:"window"` Level string `json:"level,omitempty"` Limit int `json:"limit,omitempty"` } type WebviewConsoleOutput struct { Messages []webview.ConsoleMessage `json:"messages"` } func (s *Subsystem) webviewConsole(_ context.Context, _ *mcp.CallToolRequest, input WebviewConsoleInput) (*mcp.CallToolResult, WebviewConsoleOutput, error) { r := s.core.QUERY(webview.QueryConsole{Window: input.Window, Level: input.Level, Limit: input.Limit}) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewConsoleOutput{}, e } return nil, WebviewConsoleOutput{}, nil } msgs, ok := r.Value.([]webview.ConsoleMessage) if !ok { return nil, WebviewConsoleOutput{}, coreerr.E("mcp.webviewConsole", "unexpected result type", nil) } return nil, WebviewConsoleOutput{Messages: msgs}, nil } // --- webview_console_clear --- type WebviewConsoleClearInput struct { Window string `json:"window"` } type WebviewConsoleClearOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewConsoleClear(_ context.Context, _ *mcp.CallToolRequest, input WebviewConsoleClearInput) (*mcp.CallToolResult, WebviewConsoleClearOutput, error) { r := s.core.Action("webview.clearConsole").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskClearConsole{Window: input.Window}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewConsoleClearOutput{}, e } return nil, WebviewConsoleClearOutput{}, nil } return nil, WebviewConsoleClearOutput{Success: true}, nil } // --- webview_query --- type WebviewQueryInput struct { Window string `json:"window"` Selector string `json:"selector"` } type WebviewQueryOutput struct { Element *webview.ElementInfo `json:"element"` } func (s *Subsystem) webviewQuery(_ context.Context, _ *mcp.CallToolRequest, input WebviewQueryInput) (*mcp.CallToolResult, WebviewQueryOutput, error) { r := s.core.QUERY(webview.QuerySelector{Window: input.Window, Selector: input.Selector}) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewQueryOutput{}, e } return nil, WebviewQueryOutput{}, nil } el, ok := r.Value.(*webview.ElementInfo) if !ok { return nil, WebviewQueryOutput{}, coreerr.E("mcp.webviewQuery", "unexpected result type", nil) } return nil, WebviewQueryOutput{Element: el}, nil } // --- webview_query_all --- type WebviewQueryAllInput struct { Window string `json:"window"` Selector string `json:"selector"` } type WebviewQueryAllOutput struct { Elements []*webview.ElementInfo `json:"elements"` } func (s *Subsystem) webviewQueryAll(_ context.Context, _ *mcp.CallToolRequest, input WebviewQueryAllInput) (*mcp.CallToolResult, WebviewQueryAllOutput, error) { r := s.core.QUERY(webview.QuerySelectorAll{Window: input.Window, Selector: input.Selector}) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewQueryAllOutput{}, e } return nil, WebviewQueryAllOutput{}, nil } els, ok := r.Value.([]*webview.ElementInfo) if !ok { return nil, WebviewQueryAllOutput{}, coreerr.E("mcp.webviewQueryAll", "unexpected result type", nil) } return nil, WebviewQueryAllOutput{Elements: els}, nil } // --- webview_dom_tree --- type WebviewDOMTreeInput struct { Window string `json:"window"` Selector string `json:"selector,omitempty"` } type WebviewDOMTreeOutput struct { HTML string `json:"html"` } func (s *Subsystem) webviewDOMTree(_ context.Context, _ *mcp.CallToolRequest, input WebviewDOMTreeInput) (*mcp.CallToolResult, WebviewDOMTreeOutput, error) { r := s.core.QUERY(webview.QueryDOMTree{Window: input.Window, Selector: input.Selector}) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewDOMTreeOutput{}, e } return nil, WebviewDOMTreeOutput{}, nil } html, ok := r.Value.(string) if !ok { return nil, WebviewDOMTreeOutput{}, coreerr.E("mcp.webviewDOMTree", "unexpected result type", nil) } return nil, WebviewDOMTreeOutput{HTML: html}, nil } // --- webview_url --- type WebviewURLInput struct { Window string `json:"window"` } type WebviewURLOutput struct { URL string `json:"url"` } func (s *Subsystem) webviewURL(_ context.Context, _ *mcp.CallToolRequest, input WebviewURLInput) (*mcp.CallToolResult, WebviewURLOutput, error) { r := s.core.QUERY(webview.QueryURL{Window: input.Window}) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewURLOutput{}, e } return nil, WebviewURLOutput{}, nil } url, ok := r.Value.(string) if !ok { return nil, WebviewURLOutput{}, coreerr.E("mcp.webviewURL", "unexpected result type", nil) } return nil, WebviewURLOutput{URL: url}, nil } // --- webview_title --- type WebviewTitleInput struct { Window string `json:"window"` } type WebviewTitleOutput struct { Title string `json:"title"` } func (s *Subsystem) webviewTitle(_ context.Context, _ *mcp.CallToolRequest, input WebviewTitleInput) (*mcp.CallToolResult, WebviewTitleOutput, error) { r := s.core.QUERY(webview.QueryTitle{Window: input.Window}) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewTitleOutput{}, e } return nil, WebviewTitleOutput{}, nil } title, ok := r.Value.(string) if !ok { return nil, WebviewTitleOutput{}, coreerr.E("mcp.webviewTitle", "unexpected result type", nil) } return nil, WebviewTitleOutput{Title: title}, nil } // --- webview_list --- type WebviewListInput struct{} type WebviewListOutput struct { Windows []window.WindowInfo `json:"windows"` } func (s *Subsystem) webviewList(_ context.Context, _ *mcp.CallToolRequest, _ WebviewListInput) (*mcp.CallToolResult, WebviewListOutput, error) { r := s.core.QUERY(window.QueryWindowList{}) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewListOutput{}, e } return nil, WebviewListOutput{}, nil } windows, ok := r.Value.([]window.WindowInfo) if !ok { return nil, WebviewListOutput{}, coreerr.E("mcp.webviewList", "unexpected result type", nil) } return nil, WebviewListOutput{Windows: windows}, nil } // --- webview_errors --- type WebviewErrorsInput struct { Window string `json:"window"` Limit int `json:"limit,omitempty"` } type WebviewErrorsOutput struct { Errors []webview.ExceptionInfo `json:"errors"` } func (s *Subsystem) webviewErrors(_ context.Context, _ *mcp.CallToolRequest, input WebviewErrorsInput) (*mcp.CallToolResult, WebviewErrorsOutput, error) { r := s.core.QUERY(webview.QueryExceptions{Window: input.Window, Limit: input.Limit}) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewErrorsOutput{}, e } return nil, WebviewErrorsOutput{}, nil } errors, ok := r.Value.([]webview.ExceptionInfo) if !ok { return nil, WebviewErrorsOutput{}, coreerr.E("mcp.webviewErrors", "unexpected result type", nil) } return nil, WebviewErrorsOutput{Errors: errors}, nil } // --- webview_clear_console --- type WebviewClearConsoleInput struct { Window string `json:"window"` } type WebviewClearConsoleOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewClearConsole(_ context.Context, _ *mcp.CallToolRequest, input WebviewClearConsoleInput) (*mcp.CallToolResult, WebviewClearConsoleOutput, error) { _, out, err := s.webviewConsoleClear(context.Background(), nil, WebviewConsoleClearInput{Window: input.Window}) return nil, WebviewClearConsoleOutput{Success: out.Success}, err } // --- webview_element_info --- type WebviewElementInfoInput struct { Window string `json:"window"` Selector string `json:"selector"` } type WebviewElementInfoOutput struct { Element *webview.ElementInfo `json:"element"` } func (s *Subsystem) webviewElementInfo(_ context.Context, _ *mcp.CallToolRequest, input WebviewElementInfoInput) (*mcp.CallToolResult, WebviewElementInfoOutput, error) { _, out, err := s.webviewQuery(context.Background(), nil, WebviewQueryInput{Window: input.Window, Selector: input.Selector}) return nil, WebviewElementInfoOutput{Element: out.Element}, err } // --- webview_highlight --- type WebviewHighlightInput struct { Window string `json:"window"` Selector string `json:"selector"` Colour string `json:"colour,omitempty"` } type WebviewHighlightOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewHighlight(_ context.Context, _ *mcp.CallToolRequest, input WebviewHighlightInput) (*mcp.CallToolResult, WebviewHighlightOutput, error) { result, err := s.evaluateWebview(input.Window, webview.HighlightScript(input.Selector, input.Colour)) if err != nil { return nil, WebviewHighlightOutput{}, err } success, _ := result.(bool) return nil, WebviewHighlightOutput{Success: success}, nil } // --- webview_computed_style --- type WebviewComputedStyleInput struct { Window string `json:"window"` Selector string `json:"selector"` } type WebviewComputedStyleOutput struct { Styles map[string]any `json:"styles"` } func (s *Subsystem) webviewComputedStyle(_ context.Context, _ *mcp.CallToolRequest, input WebviewComputedStyleInput) (*mcp.CallToolResult, WebviewComputedStyleOutput, error) { result, err := s.evaluateWebview(input.Window, webview.ComputedStyleScript(input.Selector)) if err != nil { return nil, WebviewComputedStyleOutput{}, err } styles, err := decodeJSONLike[map[string]any](result) if err != nil { return nil, WebviewComputedStyleOutput{}, err } return nil, WebviewComputedStyleOutput{Styles: styles}, nil } // --- webview_source --- type WebviewSourceInput struct { Window string `json:"window"` } type WebviewSourceOutput struct { HTML string `json:"html"` } func (s *Subsystem) webviewSource(_ context.Context, _ *mcp.CallToolRequest, input WebviewSourceInput) (*mcp.CallToolResult, WebviewSourceOutput, error) { r := s.core.QUERY(webview.QueryDOMTree{Window: input.Window}) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewSourceOutput{}, e } return nil, WebviewSourceOutput{}, nil } html, ok := r.Value.(string) if !ok { return nil, WebviewSourceOutput{}, coreerr.E("mcp.webviewSource", "unexpected result type", nil) } return nil, WebviewSourceOutput{HTML: html}, nil } // --- webview_screenshot_element --- type WebviewScreenshotElementInput struct { Window string `json:"window"` Selector string `json:"selector"` } type WebviewScreenshotElementOutput struct { Base64 string `json:"base64"` MimeType string `json:"mimeType"` } func (s *Subsystem) webviewScreenshotElement(_ context.Context, _ *mcp.CallToolRequest, input WebviewScreenshotElementInput) (*mcp.CallToolResult, WebviewScreenshotElementOutput, error) { r := s.core.QUERY(webview.QuerySelector{Window: input.Window, Selector: input.Selector}) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewScreenshotElementOutput{}, e } return nil, WebviewScreenshotElementOutput{}, nil } element, ok := r.Value.(*webview.ElementInfo) if !ok { return nil, WebviewScreenshotElementOutput{}, coreerr.E("mcp.webviewScreenshotElement", "unexpected result type", nil) } if element == nil || element.BoundingBox == nil { return nil, WebviewScreenshotElementOutput{}, coreerr.E("mcp.webviewScreenshotElement", "element not found or has no bounding box", nil) } r = s.core.Action("webview.screenshot").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskScreenshot{Window: input.Window}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewScreenshotElementOutput{}, e } return nil, WebviewScreenshotElementOutput{}, nil } screenshotResult, ok := r.Value.(webview.ScreenshotResult) if !ok { return nil, WebviewScreenshotElementOutput{}, coreerr.E("mcp.webviewScreenshotElement", "unexpected screenshot result type", nil) } imageBytes, err := base64.StdEncoding.DecodeString(screenshotResult.Base64) if err != nil { return nil, WebviewScreenshotElementOutput{}, err } cropped, err := cropPNGToBoundingBox(imageBytes, element.BoundingBox) if err != nil { return nil, WebviewScreenshotElementOutput{}, err } return nil, WebviewScreenshotElementOutput{ Base64: base64.StdEncoding.EncodeToString(cropped), MimeType: "image/png", }, nil } // --- webview_pdf --- type WebviewPDFInput struct { Window string `json:"window"` } type WebviewPDFOutput struct { Base64 string `json:"base64"` MimeType string `json:"mimeType"` } func (s *Subsystem) webviewPDF(_ context.Context, _ *mcp.CallToolRequest, input WebviewPDFInput) (*mcp.CallToolResult, WebviewPDFOutput, error) { r := s.core.Action("webview.print").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskPrint{Window: input.Window, ToPDF: true}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewPDFOutput{}, e } return nil, WebviewPDFOutput{}, nil } result, ok := r.Value.(webview.PrintResult) if !ok { return nil, WebviewPDFOutput{}, coreerr.E("mcp.webviewPDF", "unexpected result type", nil) } return nil, WebviewPDFOutput{Base64: result.Base64, MimeType: result.MimeType}, nil } // --- webview_print --- type WebviewPrintInput struct { Window string `json:"window"` } type WebviewPrintOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewPrint(_ context.Context, _ *mcp.CallToolRequest, input WebviewPrintInput) (*mcp.CallToolResult, WebviewPrintOutput, error) { r := s.core.Action("webview.print").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskPrint{Window: input.Window}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewPrintOutput{}, e } return nil, WebviewPrintOutput{}, nil } return nil, WebviewPrintOutput{Success: true}, nil } // --- webview_network --- type WebviewNetworkInput struct { Window string `json:"window"` Limit int `json:"limit,omitempty"` } type WebviewNetworkOutput struct { Requests []map[string]any `json:"requests"` } func (s *Subsystem) webviewNetwork(_ context.Context, _ *mcp.CallToolRequest, input WebviewNetworkInput) (*mcp.CallToolResult, WebviewNetworkOutput, error) { result, err := s.evaluateWebview(input.Window, webview.NetworkLogScript(input.Limit)) if err != nil { return nil, WebviewNetworkOutput{}, err } requests, err := decodeJSONLike[[]map[string]any](result) if err != nil { return nil, WebviewNetworkOutput{}, err } return nil, WebviewNetworkOutput{Requests: requests}, nil } // --- webview_network_clear --- type WebviewNetworkClearInput struct { Window string `json:"window"` } type WebviewNetworkClearOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewNetworkClear(_ context.Context, _ *mcp.CallToolRequest, input WebviewNetworkClearInput) (*mcp.CallToolResult, WebviewNetworkClearOutput, error) { _, err := s.evaluateWebview(input.Window, webview.NetworkClearScript()) if err != nil { return nil, WebviewNetworkClearOutput{}, err } return nil, WebviewNetworkClearOutput{Success: true}, nil } // --- webview_network_inject --- type WebviewNetworkInjectInput struct { Window string `json:"window"` } type WebviewNetworkInjectOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewNetworkInject(_ context.Context, _ *mcp.CallToolRequest, input WebviewNetworkInjectInput) (*mcp.CallToolResult, WebviewNetworkInjectOutput, error) { _, err := s.evaluateWebview(input.Window, webview.NetworkInitScript()) if err != nil { return nil, WebviewNetworkInjectOutput{}, err } return nil, WebviewNetworkInjectOutput{Success: true}, nil } // --- webview_performance --- type WebviewPerformanceInput struct { Window string `json:"window"` } type WebviewPerformanceOutput struct { Metrics map[string]any `json:"metrics"` } func (s *Subsystem) webviewPerformance(_ context.Context, _ *mcp.CallToolRequest, input WebviewPerformanceInput) (*mcp.CallToolResult, WebviewPerformanceOutput, error) { result, err := s.evaluateWebview(input.Window, webview.PerformanceScript()) if err != nil { return nil, WebviewPerformanceOutput{}, err } metrics, err := decodeJSONLike[map[string]any](result) if err != nil { return nil, WebviewPerformanceOutput{}, err } return nil, WebviewPerformanceOutput{Metrics: metrics}, nil } // --- webview_resources --- type WebviewResourcesInput struct { Window string `json:"window"` } type WebviewResourcesOutput struct { Resources []map[string]any `json:"resources"` } func (s *Subsystem) webviewResources(_ context.Context, _ *mcp.CallToolRequest, input WebviewResourcesInput) (*mcp.CallToolResult, WebviewResourcesOutput, error) { result, err := s.evaluateWebview(input.Window, webview.ResourcesScript()) if err != nil { return nil, WebviewResourcesOutput{}, err } resources, err := decodeJSONLike[[]map[string]any](result) if err != nil { return nil, WebviewResourcesOutput{}, err } return nil, WebviewResourcesOutput{Resources: resources}, nil } // --- webview_devtools_open --- type WebviewDevToolsOpenInput struct { Window string `json:"window"` } type WebviewDevToolsOpenOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewDevToolsOpen(_ context.Context, _ *mcp.CallToolRequest, input WebviewDevToolsOpenInput) (*mcp.CallToolResult, WebviewDevToolsOpenOutput, error) { r := s.core.Action("webview.devtoolsOpen").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskDevToolsOpen{Window: input.Window}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewDevToolsOpenOutput{}, e } return nil, WebviewDevToolsOpenOutput{}, nil } return nil, WebviewDevToolsOpenOutput{Success: true}, nil } // --- webview_devtools_close --- type WebviewDevToolsCloseInput struct { Window string `json:"window"` } type WebviewDevToolsCloseOutput struct { Success bool `json:"success"` } func (s *Subsystem) webviewDevToolsClose(_ context.Context, _ *mcp.CallToolRequest, input WebviewDevToolsCloseInput) (*mcp.CallToolResult, WebviewDevToolsCloseOutput, error) { r := s.core.Action("webview.devtoolsClose").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskDevToolsClose{Window: input.Window}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, WebviewDevToolsCloseOutput{}, e } return nil, WebviewDevToolsCloseOutput{}, nil } return nil, WebviewDevToolsCloseOutput{Success: true}, nil } func (s *Subsystem) evaluateWebview(windowName, script string) (any, error) { r := s.core.Action("webview.evaluate").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskEvaluate{Window: windowName, Script: script}}, )) if !r.OK { if e, ok := r.Value.(error); ok { return nil, e } return nil, coreerr.E("mcp.evaluateWebview", "webview evaluation failed", nil) } return r.Value, nil } func decodeJSONLike[T any](value any) (T, error) { var out T result := core.JSONUnmarshalString(core.JSONMarshalString(value), &out) if !result.OK { if err, ok := result.Value.(error); ok { return out, err } return out, coreerr.E("mcp.decodeJSONLike", "failed to decode result", nil) } return out, nil } func cropPNGToBoundingBox(pngData []byte, bbox *webview.BoundingBox) ([]byte, error) { img, err := png.Decode(bytes.NewReader(pngData)) if err != nil { return nil, err } bounds := img.Bounds() rect := image.Rect( maxInt(bounds.Min.X, int(math.Floor(bbox.X))), maxInt(bounds.Min.Y, int(math.Floor(bbox.Y))), minInt(bounds.Max.X, int(math.Ceil(bbox.X+bbox.Width))), minInt(bounds.Max.Y, int(math.Ceil(bbox.Y+bbox.Height))), ) if rect.Empty() { return nil, coreerr.E("mcp.cropPNGToBoundingBox", "element bounding box is empty", nil) } var cropped image.Image if subImager, ok := img.(interface { SubImage(r image.Rectangle) image.Image }); ok { cropped = subImager.SubImage(rect) } else { target := image.NewRGBA(image.Rect(0, 0, rect.Dx(), rect.Dy())) draw.Draw(target, target.Bounds(), img, rect.Min, draw.Src) cropped = target } var out bytes.Buffer if err := png.Encode(&out, cropped); err != nil { return nil, err } return out.Bytes(), nil } func minInt(a, b int) int { if a < b { return a } return b } func maxInt(a, b int) int { if a > b { return a } return b } // --- Registration --- func (s *Subsystem) registerWebviewTools(server *mcp.Server) { addTool(s, server, &mcp.Tool{Name: "webview_eval", Description: "Execute JavaScript in a webview"}, s.webviewEval) addTool(s, server, &mcp.Tool{Name: "webview_list", Description: "List webview windows with geometry"}, s.webviewList) addTool(s, server, &mcp.Tool{Name: "webview_click", Description: "Click an element in a webview"}, s.webviewClick) addTool(s, server, &mcp.Tool{Name: "webview_type", Description: "Type text into an element in a webview"}, s.webviewType) addTool(s, server, &mcp.Tool{Name: "webview_navigate", Description: "Navigate a webview to a URL"}, s.webviewNavigate) addTool(s, server, &mcp.Tool{Name: "webview_screenshot", Description: "Capture a webview screenshot as base64 PNG"}, s.webviewScreenshot) addTool(s, server, &mcp.Tool{Name: "webview_screenshot_element", Description: "Capture a specific DOM element as base64 PNG"}, s.webviewScreenshotElement) addTool(s, server, &mcp.Tool{Name: "webview_scroll", Description: "Scroll a webview to an absolute position"}, s.webviewScroll) addTool(s, server, &mcp.Tool{Name: "webview_hover", Description: "Hover over an element in a webview"}, s.webviewHover) addTool(s, server, &mcp.Tool{Name: "webview_select", Description: "Select an option in a select element"}, s.webviewSelect) addTool(s, server, &mcp.Tool{Name: "webview_check", Description: "Check or uncheck a checkbox"}, s.webviewCheck) addTool(s, server, &mcp.Tool{Name: "webview_upload", Description: "Upload files to a file input element"}, s.webviewUpload) addTool(s, server, &mcp.Tool{Name: "webview_viewport", Description: "Set the webview viewport dimensions"}, s.webviewViewport) addTool(s, server, &mcp.Tool{Name: "webview_console", Description: "Get captured console messages from a webview"}, s.webviewConsole) addTool(s, server, &mcp.Tool{Name: "webview_console_clear", Description: "Clear captured console messages"}, s.webviewConsoleClear) addTool(s, server, &mcp.Tool{Name: "webview_clear_console", Description: "Clear captured console messages"}, s.webviewClearConsole) addTool(s, server, &mcp.Tool{Name: "webview_errors", Description: "Get captured JavaScript errors and exceptions"}, s.webviewErrors) addTool(s, server, &mcp.Tool{Name: "webview_query", Description: "Find a single DOM element by CSS selector"}, s.webviewQuery) addTool(s, server, &mcp.Tool{Name: "webview_element_info", Description: "Get detailed information about a DOM element"}, s.webviewElementInfo) addTool(s, server, &mcp.Tool{Name: "webview_query_all", Description: "Find all DOM elements matching a CSS selector"}, s.webviewQueryAll) addTool(s, server, &mcp.Tool{Name: "webview_dom_tree", Description: "Get HTML content of a webview"}, s.webviewDOMTree) addTool(s, server, &mcp.Tool{Name: "webview_source", Description: "Get the full HTML source of a webview"}, s.webviewSource) addTool(s, server, &mcp.Tool{Name: "webview_highlight", Description: "Highlight a DOM element inside the webview"}, s.webviewHighlight) addTool(s, server, &mcp.Tool{Name: "webview_computed_style", Description: "Get computed CSS styles for a DOM element"}, s.webviewComputedStyle) addTool(s, server, &mcp.Tool{Name: "webview_url", Description: "Get the current URL of a webview"}, s.webviewURL) addTool(s, server, &mcp.Tool{Name: "webview_title", Description: "Get the current page title of a webview"}, s.webviewTitle) addTool(s, server, &mcp.Tool{Name: "webview_pdf", Description: "Export the current page as PDF"}, s.webviewPDF) addTool(s, server, &mcp.Tool{Name: "webview_print", Description: "Open the native print dialog for the current page"}, s.webviewPrint) addTool(s, server, &mcp.Tool{Name: "webview_network", Description: "Get recent network activity for the page"}, s.webviewNetwork) addTool(s, server, &mcp.Tool{Name: "webview_network_clear", Description: "Clear the injected webview network log"}, s.webviewNetworkClear) addTool(s, server, &mcp.Tool{Name: "webview_network_inject", Description: "Inject fetch and XHR interception for detailed network logging"}, s.webviewNetworkInject) addTool(s, server, &mcp.Tool{Name: "webview_performance", Description: "Get page performance metrics"}, s.webviewPerformance) addTool(s, server, &mcp.Tool{Name: "webview_resources", Description: "List loaded page resources"}, s.webviewResources) addTool(s, server, &mcp.Tool{Name: "webview_devtools_open", Description: "Open native developer tools for the window"}, s.webviewDevToolsOpen) addTool(s, server, &mcp.Tool{Name: "webview_devtools_close", Description: "Close native developer tools for the window when supported"}, s.webviewDevToolsClose) }