package main import ( "context" "encoding/json" "fmt" "log" "os" "strconv" "time" "git.teamworkapps.com/shortcut/cremote/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) const Version = "2.0.0" // CremoteServer wraps the cremote client for MCP type CremoteServer struct { client *client.Client currentTab string tabHistory []string iframeMode bool screenshots []string } // NewCremoteServer creates a new cremote MCP server func NewCremoteServer(host string, port int) *CremoteServer { return &CremoteServer{ client: client.NewClient(host, port), tabHistory: make([]string, 0), screenshots: make([]string, 0), } } // Helper functions for parameter extraction func getStringParam(params map[string]any, key, defaultValue string) string { if val, ok := params[key].(string); ok { return val } return defaultValue } func getBoolParam(params map[string]any, key string, defaultValue bool) bool { if val, ok := params[key].(bool); ok { return val } return defaultValue } func getIntParam(params map[string]any, key string, defaultValue int) int { if val, ok := params[key].(float64); ok { return int(val) } if val, ok := params[key].(int); ok { return val } return defaultValue } func main() { // Get cremote daemon connection settings cremoteHost := os.Getenv("CREMOTE_HOST") if cremoteHost == "" { cremoteHost = "localhost" } cremotePortStr := os.Getenv("CREMOTE_PORT") cremotePort := 8989 if cremotePortStr != "" { if p, err := strconv.Atoi(cremotePortStr); err == nil { cremotePort = p } } log.Printf("Starting cremote MCP server, connecting to cremote daemon at %s:%d", cremoteHost, cremotePort) // Create the cremote server cremoteServer := NewCremoteServer(cremoteHost, cremotePort) // Create MCP server mcpServer := server.NewMCPServer("cremote-mcp", "2.0.0") // Register version tool mcpServer.AddTool(mcp.Tool{ Name: "version_cremotemcp", Description: "Get version information for MCP server and daemon", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{}, Required: []string{}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Get daemon version daemonVersion, err := cremoteServer.client.GetVersion() if err != nil { daemonVersion = fmt.Sprintf("Unable to connect: %v", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("MCP Server version: %s\nDaemon version: %s", Version, daemonVersion)), }, IsError: false, }, nil }) // Register web_navigate tool mcpServer.AddTool(mcp.Tool{ Name: "web_navigate_cremotemcp", Description: "Navigate to a URL and optionally take a screenshot", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "url": map[string]any{ "type": "string", "description": "URL to navigate to", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, "screenshot": map[string]any{ "type": "boolean", "description": "Take screenshot after navigation", }, }, Required: []string{"url"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } url := getStringParam(params, "url", "") if url == "" { return nil, fmt.Errorf("url parameter is required") } tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) takeScreenshot := getBoolParam(params, "screenshot", false) // If no tab specified and no current tab, create a new one if tab == "" { newTab, err := cremoteServer.client.OpenTab(timeout) if err != nil { return nil, fmt.Errorf("failed to create new tab: %w", err) } tab = newTab cremoteServer.currentTab = tab cremoteServer.tabHistory = append(cremoteServer.tabHistory, tab) } // Load the URL err := cremoteServer.client.LoadURL(tab, url, timeout) if err != nil { return nil, fmt.Errorf("failed to load URL: %w", err) } message := fmt.Sprintf("Successfully navigated to %s in tab %s", url, tab) // Take screenshot if requested if takeScreenshot { screenshotPath := fmt.Sprintf("/tmp/navigate-%d.png", time.Now().Unix()) err = cremoteServer.client.TakeScreenshot(tab, screenshotPath, false, timeout) if err == nil { cremoteServer.screenshots = append(cremoteServer.screenshots, screenshotPath) message += fmt.Sprintf(" (screenshot saved to %s)", screenshotPath) } } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(message), }, IsError: false, }, nil }) // Register web_interact tool mcpServer.AddTool(mcp.Tool{ Name: "web_interact_cremotemcp", Description: "Interact with web elements (click, fill, submit)", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "action": map[string]any{ "type": "string", "description": "Action to perform", "enum": []any{"click", "fill", "submit", "upload", "select"}, }, "selector": map[string]any{ "type": "string", "description": "CSS selector for the element", }, "value": map[string]any{ "type": "string", "description": "Value to fill (for fill/upload actions)", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"action", "selector"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } action := getStringParam(params, "action", "") selector := getStringParam(params, "selector", "") value := getStringParam(params, "value", "") tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) if action == "" { return nil, fmt.Errorf("action parameter is required") } if selector == "" { return nil, fmt.Errorf("selector parameter is required") } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first") } var err error var message string switch action { case "click": err = cremoteServer.client.ClickElement(tab, selector, timeout) message = fmt.Sprintf("Clicked element %s", selector) case "fill": if value == "" { return nil, fmt.Errorf("value parameter is required for fill action") } err = cremoteServer.client.FillFormField(tab, selector, value, timeout) message = fmt.Sprintf("Filled element %s with value", selector) case "submit": err = cremoteServer.client.SubmitForm(tab, selector, timeout) message = fmt.Sprintf("Submitted form %s", selector) case "upload": if value == "" { return nil, fmt.Errorf("value parameter (file path) is required for upload action") } err = cremoteServer.client.UploadFile(tab, selector, value, timeout) message = fmt.Sprintf("Uploaded file %s to element %s", value, selector) case "select": if value == "" { return nil, fmt.Errorf("value parameter is required for select action") } err = cremoteServer.client.SelectElement(tab, selector, value, timeout) message = fmt.Sprintf("Selected option %s in element %s", value, selector) default: return nil, fmt.Errorf("unknown action: %s", action) } if err != nil { return nil, fmt.Errorf("failed to %s element: %w", action, err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(message), }, IsError: false, }, nil }) // Register web_extract tool mcpServer.AddTool(mcp.Tool{ Name: "web_extract_cremotemcp", Description: "Extract data from the page (source, element HTML, or execute JavaScript)", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "type": map[string]any{ "type": "string", "description": "Type of extraction", "enum": []any{"source", "element", "javascript"}, }, "selector": map[string]any{ "type": "string", "description": "CSS selector (for element type)", }, "code": map[string]any{ "type": "string", "description": "JavaScript code (for javascript type)", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"type"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } extractType := getStringParam(params, "type", "") selector := getStringParam(params, "selector", "") code := getStringParam(params, "code", "") tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) if extractType == "" { return nil, fmt.Errorf("type parameter is required") } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first") } var data string var err error switch extractType { case "source": data, err = cremoteServer.client.GetPageSource(tab, timeout) case "element": if selector == "" { return nil, fmt.Errorf("selector parameter is required for element extraction") } data, err = cremoteServer.client.GetElementHTML(tab, selector, timeout) case "javascript": if code == "" { return nil, fmt.Errorf("code parameter is required for javascript extraction") } data, err = cremoteServer.client.EvalJS(tab, code, timeout) default: return nil, fmt.Errorf("unknown extraction type: %s", extractType) } if err != nil { return nil, fmt.Errorf("failed to extract %s: %w", extractType, err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Extracted %s data: %s", extractType, data)), }, IsError: false, }, nil }) // Register web_screenshot tool mcpServer.AddTool(mcp.Tool{ Name: "web_screenshot_cremotemcp", Description: "Take a screenshot of the current page with optional zoom level and viewport size for accessibility testing", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "output": map[string]any{ "type": "string", "description": "Output file path", }, "full_page": map[string]any{ "type": "boolean", "description": "Capture full page", "default": false, }, "zoom_level": map[string]any{ "type": "number", "description": "Zoom level (e.g., 1.0, 2.0, 4.0) for accessibility testing", }, "width": map[string]any{ "type": "integer", "description": "Viewport width in pixels for responsive testing", }, "height": map[string]any{ "type": "integer", "description": "Viewport height in pixels for responsive testing", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"output"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } output := getStringParam(params, "output", "") fullPage := getBoolParam(params, "full_page", false) tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) // Get optional zoom and viewport parameters var zoomLevel float64 if zoomParam, ok := params["zoom_level"].(float64); ok { zoomLevel = zoomParam } var width, height int if widthParam, ok := params["width"].(float64); ok { width = int(widthParam) } if heightParam, ok := params["height"].(float64); ok { height = int(heightParam) } if output == "" { return nil, fmt.Errorf("output parameter is required") } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first") } // Build command parameters cmdParams := map[string]string{ "output": output, "full-page": strconv.FormatBool(fullPage), "tab": tab, "timeout": strconv.Itoa(timeout), } if zoomLevel > 0 { cmdParams["zoom_level"] = strconv.FormatFloat(zoomLevel, 'f', 1, 64) } if width > 0 { cmdParams["width"] = strconv.Itoa(width) } if height > 0 { cmdParams["height"] = strconv.Itoa(height) } // Send command to daemon resp, err := cremoteServer.client.SendCommand("screenshot", cmdParams) if err != nil { return nil, fmt.Errorf("failed to take screenshot: %w", err) } if !resp.Success { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Screenshot failed: %s", resp.Error)), }, IsError: true, }, nil } cremoteServer.screenshots = append(cremoteServer.screenshots, output) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Screenshot saved to %s", output)), }, IsError: false, }, nil }) // Register web_manage_tabs tool mcpServer.AddTool(mcp.Tool{ Name: "web_manage_tabs_cremotemcp", Description: "Manage browser tabs (open, close, list, switch)", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "action": map[string]any{ "type": "string", "description": "Action to perform", "enum": []any{"open", "close", "list", "switch"}, }, "tab": map[string]any{ "type": "string", "description": "Tab ID (for close/switch actions)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"action"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } action := getStringParam(params, "action", "") tab := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) if action == "" { return nil, fmt.Errorf("action parameter is required") } var err error var message string switch action { case "open": newTab, err := cremoteServer.client.OpenTab(timeout) if err != nil { return nil, fmt.Errorf("failed to open new tab: %w", err) } cremoteServer.currentTab = newTab cremoteServer.tabHistory = append(cremoteServer.tabHistory, newTab) message = fmt.Sprintf("Opened new tab: %s", newTab) case "close": if tab == "" { return nil, fmt.Errorf("tab parameter is required for close action") } err = cremoteServer.client.CloseTab(tab, timeout) if err != nil { return nil, fmt.Errorf("failed to close tab: %w", err) } // Remove from history and update current tab for i, id := range cremoteServer.tabHistory { if id == tab { cremoteServer.tabHistory = append(cremoteServer.tabHistory[:i], cremoteServer.tabHistory[i+1:]...) break } } if cremoteServer.currentTab == tab { if len(cremoteServer.tabHistory) > 0 { cremoteServer.currentTab = cremoteServer.tabHistory[len(cremoteServer.tabHistory)-1] } else { cremoteServer.currentTab = "" } } message = fmt.Sprintf("Closed tab: %s", tab) case "list": tabs, err := cremoteServer.client.ListTabs() if err != nil { return nil, fmt.Errorf("failed to list tabs: %w", err) } message = fmt.Sprintf("Found %d tabs: %v", len(tabs), tabs) case "switch": if tab == "" { return nil, fmt.Errorf("tab parameter is required for switch action") } cremoteServer.currentTab = tab // Add to history if not already there found := false for _, t := range cremoteServer.tabHistory { if t == tab { found = true break } } if !found { cremoteServer.tabHistory = append(cremoteServer.tabHistory, tab) } message = fmt.Sprintf("Switched to tab: %s", tab) default: return nil, fmt.Errorf("unknown tab action: %s", action) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(message), }, IsError: false, }, nil }) // Register web_iframe tool mcpServer.AddTool(mcp.Tool{ Name: "web_iframe_cremotemcp", Description: "Switch iframe context for subsequent operations", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "action": map[string]any{ "type": "string", "description": "Action to perform", "enum": []any{"enter", "exit"}, }, "selector": map[string]any{ "type": "string", "description": "Iframe CSS selector (for enter action)", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"action"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } action := getStringParam(params, "action", "") selector := getStringParam(params, "selector", "") tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) if action == "" { return nil, fmt.Errorf("action parameter is required") } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first") } var err error var message string switch action { case "enter": if selector == "" { return nil, fmt.Errorf("selector parameter is required for enter action") } err = cremoteServer.client.SwitchToIframe(tab, selector, timeout) cremoteServer.iframeMode = true message = fmt.Sprintf("Entered iframe: %s", selector) case "exit": err = cremoteServer.client.SwitchToMain(tab, timeout) cremoteServer.iframeMode = false message = "Exited iframe context" default: return nil, fmt.Errorf("unknown iframe action: %s", action) } if err != nil { return nil, fmt.Errorf("failed to %s iframe: %w", action, err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(message), }, IsError: false, }, nil }) // Register file_upload tool mcpServer.AddTool(mcp.Tool{ Name: "file_upload_cremotemcp", Description: "Upload a file from the client to the container for use in form uploads", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "local_path": map[string]any{ "type": "string", "description": "Path to the file on the client machine", }, "container_path": map[string]any{ "type": "string", "description": "Optional path where to store the file in the container (defaults to /tmp/filename)", }, }, Required: []string{"local_path"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } localPath := getStringParam(params, "local_path", "") containerPath := getStringParam(params, "container_path", "") if localPath == "" { return nil, fmt.Errorf("local_path parameter is required") } // Upload the file to the container targetPath, err := cremoteServer.client.UploadFileToContainer(localPath, containerPath) if err != nil { return nil, fmt.Errorf("failed to upload file: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("File uploaded successfully to container at: %s", targetPath)), }, IsError: false, }, nil }) // Register file_download tool mcpServer.AddTool(mcp.Tool{ Name: "file_download_cremotemcp", Description: "Download a file from the container to the client (e.g., downloaded files from browser)", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "container_path": map[string]any{ "type": "string", "description": "Path to the file in the container", }, "local_path": map[string]any{ "type": "string", "description": "Path where to save the file on the client machine", }, }, Required: []string{"container_path", "local_path"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } containerPath := getStringParam(params, "container_path", "") localPath := getStringParam(params, "local_path", "") if containerPath == "" { return nil, fmt.Errorf("container_path parameter is required") } if localPath == "" { return nil, fmt.Errorf("local_path parameter is required") } // Download the file from the container err := cremoteServer.client.DownloadFileFromContainer(containerPath, localPath) if err != nil { return nil, fmt.Errorf("failed to download file: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("File downloaded successfully from container to: %s", localPath)), }, IsError: false, }, nil }) // Register console_logs tool mcpServer.AddTool(mcp.Tool{ Name: "console_logs_cremotemcp", Description: "Get console logs from the browser tab", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "clear": map[string]any{ "type": "boolean", "description": "Clear logs after retrieval (default: false)", "default": false, }, }, Required: []string{}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", cremoteServer.currentTab) clear := getBoolParam(params, "clear", false) // Get console logs logs, err := cremoteServer.client.GetConsoleLogs(tab, clear) if err != nil { return nil, fmt.Errorf("failed to get console logs: %w", err) } // Format logs for display var logText string if len(logs) == 0 { logText = "No console logs found." } else { logText = fmt.Sprintf("Found %d console log entries:\n\n", len(logs)) for i, log := range logs { level := log["level"].(string) message := log["message"].(string) timestamp := log["timestamp"].(string) logText += fmt.Sprintf("[%d] %s [%s]: %s\n", i+1, timestamp, level, message) } } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(logText), }, IsError: false, }, nil }) // Register console_command tool mcpServer.AddTool(mcp.Tool{ Name: "console_command_cremotemcp", Description: "Execute a command in the browser console with optional library injection (axe-core, jQuery, Lodash, etc.)", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "command": map[string]any{ "type": "string", "description": "JavaScript command to execute in console", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "inject_library": map[string]any{ "type": "string", "description": "Library to inject before executing command (optional). Can be a known library name (axe, jquery, lodash, moment, underscore) or a full URL", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"command"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } command := getStringParam(params, "command", "") tab := getStringParam(params, "tab", cremoteServer.currentTab) injectLibrary := getStringParam(params, "inject_library", "") timeout := getIntParam(params, "timeout", 5) if command == "" { return nil, fmt.Errorf("command parameter is required") } // Build command parameters cmdParams := map[string]string{ "command": command, "timeout": strconv.Itoa(timeout), } if tab != "" { cmdParams["tab"] = tab } if injectLibrary != "" { cmdParams["inject_library"] = injectLibrary } // Send command to daemon resp, err := cremoteServer.client.SendCommand("console-command", cmdParams) if err != nil { return nil, fmt.Errorf("failed to execute console command: %w", err) } if !resp.Success { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Console command failed: %s", resp.Error)), }, IsError: true, }, nil } // Format result resultStr := fmt.Sprintf("Console command executed successfully.\nCommand: %s", command) if injectLibrary != "" { resultStr += fmt.Sprintf("\nInjected library: %s", injectLibrary) } if resp.Data != nil { resultStr += fmt.Sprintf("\nResult: %v", resp.Data) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(resultStr), }, IsError: false, }, nil }) // Register web_element_check tool mcpServer.AddTool(mcp.Tool{ Name: "web_element_check_cremotemcp", Description: "Check existence, visibility, enabled state, count elements", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for the element(s)", }, "check_type": map[string]any{ "type": "string", "description": "Type of check to perform", "enum": []any{"exists", "visible", "enabled", "focused", "selected", "all"}, "default": "exists", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"selector"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selector := getStringParam(params, "selector", "") checkType := getStringParam(params, "check_type", "exists") tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) if selector == "" { return nil, fmt.Errorf("selector parameter is required") } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first") } result, err := cremoteServer.client.CheckElement(tab, selector, checkType, timeout) if err != nil { return nil, fmt.Errorf("failed to check element: %w", err) } // Format result as JSON string for display resultJSON, _ := json.Marshal(result) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Element check result: %s", string(resultJSON))), }, IsError: false, }, nil }) // Register web_element_attributes tool mcpServer.AddTool(mcp.Tool{ Name: "web_element_attributes_cremotemcp", Description: "Get attributes, properties, computed styles of an element", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for the element", }, "attributes": map[string]any{ "type": "string", "description": "Comma-separated list of attributes or 'all' for common attributes. Use 'style_' prefix for computed styles, 'prop_' for JavaScript properties", "default": "all", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"selector"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selector := getStringParam(params, "selector", "") attributes := getStringParam(params, "attributes", "all") tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) if selector == "" { return nil, fmt.Errorf("selector parameter is required") } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first") } result, err := cremoteServer.client.GetElementAttributes(tab, selector, attributes, timeout) if err != nil { return nil, fmt.Errorf("failed to get element attributes: %w", err) } // Format result as JSON string for display resultJSON, _ := json.Marshal(result) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Element attributes: %s", string(resultJSON))), }, IsError: false, }, nil }) // Register web_extract_multiple tool mcpServer.AddTool(mcp.Tool{ Name: "web_extract_multiple_cremotemcp", Description: "Extract from multiple selectors in one call", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selectors": map[string]any{ "type": "object", "description": "Object with keys as labels and values as CSS selectors", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"selectors"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selectorsParam := params["selectors"] tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) if selectorsParam == nil { return nil, fmt.Errorf("selectors parameter is required") } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first") } // Convert selectors to map[string]string selectorsMap := make(map[string]string) if selectorsObj, ok := selectorsParam.(map[string]any); ok { for key, value := range selectorsObj { if selector, ok := value.(string); ok { selectorsMap[key] = selector } } } else { return nil, fmt.Errorf("selectors must be an object with string values") } result, err := cremoteServer.client.ExtractMultiple(tab, selectorsMap, timeout) if err != nil { return nil, fmt.Errorf("failed to extract multiple: %w", err) } // Format result as JSON string for display resultJSON, _ := json.Marshal(result) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Multiple extraction result: %s", string(resultJSON))), }, IsError: false, }, nil }) // Register web_extract_links tool mcpServer.AddTool(mcp.Tool{ Name: "web_extract_links_cremotemcp", Description: "Extract all links with filtering options", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "container_selector": map[string]any{ "type": "string", "description": "Optional CSS selector to limit search to a container", }, "href_pattern": map[string]any{ "type": "string", "description": "Optional regex pattern to filter links by href", }, "text_pattern": map[string]any{ "type": "string", "description": "Optional regex pattern to filter links by text content", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } containerSelector := getStringParam(params, "container_selector", "") hrefPattern := getStringParam(params, "href_pattern", "") textPattern := getStringParam(params, "text_pattern", "") tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first") } result, err := cremoteServer.client.ExtractLinks(tab, containerSelector, hrefPattern, textPattern, timeout) if err != nil { return nil, fmt.Errorf("failed to extract links: %w", err) } // Format result as JSON string for display resultJSON, _ := json.Marshal(result) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Links extraction result: %s", string(resultJSON))), }, IsError: false, }, nil }) // Register web_extract_table tool mcpServer.AddTool(mcp.Tool{ Name: "web_extract_table_cremotemcp", Description: "Extract table data as structured JSON", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for the table element", }, "include_headers": map[string]any{ "type": "boolean", "description": "Whether to extract and use headers for structured data", "default": true, }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"selector"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selector := getStringParam(params, "selector", "") includeHeaders := getBoolParam(params, "include_headers", true) tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) if selector == "" { return nil, fmt.Errorf("selector parameter is required") } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first") } result, err := cremoteServer.client.ExtractTable(tab, selector, includeHeaders, timeout) if err != nil { return nil, fmt.Errorf("failed to extract table: %w", err) } // Format result as JSON string for display resultJSON, _ := json.Marshal(result) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Table extraction result: %s", string(resultJSON))), }, IsError: false, }, nil }) // Register web_extract_text tool mcpServer.AddTool(mcp.Tool{ Name: "web_extract_text_cremotemcp", Description: "Extract text with pattern matching", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for elements to extract text from", }, "pattern": map[string]any{ "type": "string", "description": "Optional regex pattern to match within text", }, "extract_type": map[string]any{ "type": "string", "description": "Type of text extraction", "enum": []any{"text", "innerText", "textContent"}, "default": "textContent", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"selector"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selector := getStringParam(params, "selector", "") pattern := getStringParam(params, "pattern", "") extractType := getStringParam(params, "extract_type", "textContent") tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) if selector == "" { return nil, fmt.Errorf("selector parameter is required") } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first") } result, err := cremoteServer.client.ExtractText(tab, selector, pattern, extractType, timeout) if err != nil { return nil, fmt.Errorf("failed to extract text: %w", err) } // Format result as JSON string for display resultJSON, _ := json.Marshal(result) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Text extraction result: %s", string(resultJSON))), }, IsError: false, }, nil }) // Register web_form_analyze tool mcpServer.AddTool(mcp.Tool{ Name: "web_form_analyze_cremotemcp", Description: "Analyze forms completely", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for the form element", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"selector"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selector := getStringParam(params, "selector", "") if selector == "" { return nil, fmt.Errorf("selector parameter is required") } tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) result, err := cremoteServer.client.AnalyzeForm(tab, selector, timeout) if err != nil { return nil, fmt.Errorf("failed to analyze form: %w", err) } resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal result: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Form analysis result: %s", string(resultJSON))), }, IsError: false, }, nil }) // Register web_interact_multiple tool mcpServer.AddTool(mcp.Tool{ Name: "web_interact_multiple_cremotemcp", Description: "Batch interactions", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "interactions": map[string]any{ "type": "array", "description": "Array of interactions to perform", "items": map[string]any{ "type": "object", "properties": map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for the element", }, "action": map[string]any{ "type": "string", "description": "Action to perform", "enum": []any{"click", "fill", "select", "check", "uncheck"}, }, "value": map[string]any{ "type": "string", "description": "Value for the action (required for fill, select)", }, }, "required": []string{"selector", "action"}, }, }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"interactions"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } interactionsData, ok := params["interactions"].([]any) if !ok { return nil, fmt.Errorf("interactions parameter is required and must be an array") } // Parse interactions var interactions []client.InteractionItem for _, interactionData := range interactionsData { interactionMap, ok := interactionData.(map[string]any) if !ok { return nil, fmt.Errorf("each interaction must be an object") } interaction := client.InteractionItem{} if selector, ok := interactionMap["selector"].(string); ok { interaction.Selector = selector } else { return nil, fmt.Errorf("each interaction must have a selector") } if action, ok := interactionMap["action"].(string); ok { interaction.Action = action } else { return nil, fmt.Errorf("each interaction must have an action") } if value, ok := interactionMap["value"].(string); ok { interaction.Value = value } interactions = append(interactions, interaction) } tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) result, err := cremoteServer.client.InteractMultiple(tab, interactions, timeout) if err != nil { return nil, fmt.Errorf("failed to perform multiple interactions: %w", err) } resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal result: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Multiple interactions result: %s", string(resultJSON))), }, IsError: false, }, nil }) // Register web_form_fill_bulk tool mcpServer.AddTool(mcp.Tool{ Name: "web_form_fill_bulk_cremotemcp", Description: "Fill entire forms with key-value pairs", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "fields": map[string]any{ "type": "object", "description": "Map of field names/selectors to values", }, "form_selector": map[string]any{ "type": "string", "description": "CSS selector for the form element (optional)", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"fields"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } fieldsData, ok := params["fields"].(map[string]any) if !ok { return nil, fmt.Errorf("fields parameter is required and must be an object") } // Convert fields to map[string]string fields := make(map[string]string) for key, value := range fieldsData { if strValue, ok := value.(string); ok { fields[key] = strValue } else { return nil, fmt.Errorf("all field values must be strings") } } formSelector := getStringParam(params, "form_selector", "") tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) result, err := cremoteServer.client.FillFormBulk(tab, formSelector, fields, timeout) if err != nil { return nil, fmt.Errorf("failed to fill form bulk: %w", err) } resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal result: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Form bulk fill result: %s", string(resultJSON))), }, IsError: false, }, nil }) // Register web_page_info tool mcpServer.AddTool(mcp.Tool{ Name: "web_page_info_cremotemcp", Description: "Get comprehensive page metadata and state information", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab if not specified)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tabID := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) result, err := cremoteServer.client.GetPageInfo(tabID, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error getting page info: %v", err)), }, IsError: true, }, nil } resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal result: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Page info: %s", string(resultJSON))), }, IsError: false, }, nil }) // Register web_viewport_info tool mcpServer.AddTool(mcp.Tool{ Name: "web_viewport_info_cremotemcp", Description: "Get viewport and scroll information", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab if not specified)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tabID := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) result, err := cremoteServer.client.GetViewportInfo(tabID, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error getting viewport info: %v", err)), }, IsError: true, }, nil } resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal result: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Viewport info: %s", string(resultJSON))), }, IsError: false, }, nil }) // Register web_performance_metrics tool mcpServer.AddTool(mcp.Tool{ Name: "web_performance_metrics_cremotemcp", Description: "Get page performance metrics", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab if not specified)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tabID := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) result, err := cremoteServer.client.GetPerformance(tabID, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error getting performance metrics: %v", err)), }, IsError: true, }, nil } resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal result: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Performance metrics: %s", string(resultJSON))), }, IsError: false, }, nil }) // Register web_content_check tool mcpServer.AddTool(mcp.Tool{ Name: "web_content_check_cremotemcp", Description: "Check for specific content types and loading states", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "type": map[string]any{ "type": "string", "description": "Content type to check", "enum": []any{"images", "scripts", "styles", "forms", "links", "iframes", "errors"}, }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab if not specified)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"type"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } contentType := getStringParam(params, "type", "") if contentType == "" { return nil, fmt.Errorf("type parameter is required") } tabID := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) result, err := cremoteServer.client.CheckContent(tabID, contentType, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error checking content: %v", err)), }, IsError: true, }, nil } resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal result: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Content check result: %s", string(resultJSON))), }, IsError: false, }, nil }) // Phase 5: Enhanced Screenshot and File Management Tools // web_screenshot_element_cremotemcp - Screenshot specific elements mcpServer.AddTool(mcp.Tool{ Name: "web_screenshot_element_cremotemcp", Description: "Take a screenshot of a specific element on the page", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for the element to screenshot", }, "output": map[string]any{ "type": "string", "description": "Path where to save the screenshot", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab if not specified)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", }, }, Required: []string{"selector", "output"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selector := getStringParam(params, "selector", "") output := getStringParam(params, "output", "") tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) if selector == "" { return nil, fmt.Errorf("selector parameter is required") } if output == "" { return nil, fmt.Errorf("output parameter is required") } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first") } err := cremoteServer.client.ScreenshotElement(tab, selector, output, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error taking element screenshot: %v", err)), }, IsError: true, }, nil } cremoteServer.screenshots = append(cremoteServer.screenshots, output) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Element screenshot saved to: %s", output)), }, IsError: false, }, nil }) // web_screenshot_enhanced_cremotemcp - Enhanced screenshots with metadata mcpServer.AddTool(mcp.Tool{ Name: "web_screenshot_enhanced_cremotemcp", Description: "Take an enhanced screenshot with metadata (timestamp, viewport size, URL)", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "output": map[string]any{ "type": "string", "description": "Path where to save the screenshot", }, "full_page": map[string]any{ "type": "boolean", "description": "Capture full page (default: false)", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab if not specified)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", }, }, Required: []string{"output"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } output := getStringParam(params, "output", "") fullPage := getBoolParam(params, "full_page", false) tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) if output == "" { return nil, fmt.Errorf("output parameter is required") } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first") } metadata, err := cremoteServer.client.ScreenshotEnhanced(tab, output, fullPage, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error taking enhanced screenshot: %v", err)), }, IsError: true, }, nil } cremoteServer.screenshots = append(cremoteServer.screenshots, output) metadataJSON, err := json.MarshalIndent(metadata, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal metadata: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Enhanced screenshot saved with metadata:\n%s", string(metadataJSON))), }, IsError: false, }, nil }) // file_operations_bulk_cremotemcp - Bulk file operations mcpServer.AddTool(mcp.Tool{ Name: "file_operations_bulk_cremotemcp", Description: "Perform bulk file operations (upload/download multiple files)", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "operation": map[string]any{ "type": "string", "description": "Operation type", "enum": []any{"upload", "download"}, }, "files": map[string]any{ "type": "array", "description": "Array of file operations", "items": map[string]any{ "type": "object", "properties": map[string]any{ "local_path": map[string]any{ "type": "string", "description": "Path on client machine", }, "container_path": map[string]any{ "type": "string", "description": "Path in container", }, "operation": map[string]any{ "type": "string", "description": "Override operation type for this file", "enum": []any{"upload", "download"}, }, }, "required": []any{"local_path", "container_path"}, }, }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 30)", }, }, Required: []string{"operation", "files"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } operation := getStringParam(params, "operation", "") filesParam := params["files"] timeout := getIntParam(params, "timeout", 30) if operation == "" { return nil, fmt.Errorf("operation parameter is required") } if filesParam == nil { return nil, fmt.Errorf("files parameter is required") } // Convert files parameter to FileOperation slice filesArray, ok := filesParam.([]any) if !ok { return nil, fmt.Errorf("files must be an array") } var operations []client.FileOperation for _, fileItem := range filesArray { fileMap, ok := fileItem.(map[string]any) if !ok { return nil, fmt.Errorf("each file must be an object") } localPath := getStringParam(fileMap, "local_path", "") containerPath := getStringParam(fileMap, "container_path", "") fileOperation := getStringParam(fileMap, "operation", operation) if localPath == "" || containerPath == "" { return nil, fmt.Errorf("local_path and container_path are required for each file") } operations = append(operations, client.FileOperation{ LocalPath: localPath, ContainerPath: containerPath, Operation: fileOperation, }) } result, err := cremoteServer.client.BulkFiles(operation, operations, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error performing bulk file operations: %v", err)), }, IsError: true, }, nil } resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal result: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Bulk file operations completed:\n%s", string(resultJSON))), }, IsError: false, }, nil }) // file_management_cremotemcp - File management operations mcpServer.AddTool(mcp.Tool{ Name: "file_management_cremotemcp", Description: "Manage files (cleanup, list, get info)", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "operation": map[string]any{ "type": "string", "description": "Management operation", "enum": []any{"cleanup", "list", "info"}, }, "pattern": map[string]any{ "type": "string", "description": "File pattern for cleanup/list, or file path for info", }, "max_age": map[string]any{ "type": "string", "description": "Max age in hours for cleanup (default: 24)", }, }, Required: []string{"operation"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } operation := getStringParam(params, "operation", "") pattern := getStringParam(params, "pattern", "") maxAge := getStringParam(params, "max_age", "") if operation == "" { return nil, fmt.Errorf("operation parameter is required") } result, err := cremoteServer.client.ManageFiles(operation, pattern, maxAge) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error managing files: %v", err)), }, IsError: true, }, nil } resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal result: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("File management result:\n%s", string(resultJSON))), }, IsError: false, }, nil }) // Accessibility tree tools mcpServer.AddTool(mcp.Tool{ Name: "get_accessibility_tree_cremotemcp", Description: "Get the full accessibility tree for a page with optional contrast data annotation", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "depth": map[string]any{ "type": "integer", "description": "Maximum depth to retrieve (optional, omit for full tree)", "minimum": 0, }, "include_contrast": map[string]any{ "type": "boolean", "description": "Include contrast check availability annotation for text nodes (default: false)", "default": false, }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", cremoteServer.currentTab) includeContrast := getBoolParam(params, "include_contrast", false) timeout := getIntParam(params, "timeout", 5) // Parse depth parameter var depth *int if depthParam := getIntParam(params, "depth", -1); depthParam >= 0 { depth = &depthParam } // Build command parameters cmdParams := map[string]string{ "timeout": strconv.Itoa(timeout), } if tab != "" { cmdParams["tab"] = tab } if depth != nil { cmdParams["depth"] = strconv.Itoa(*depth) } if includeContrast { cmdParams["include_contrast"] = "true" } // Send command to daemon resp, err := cremoteServer.client.SendCommand("get-accessibility-tree", cmdParams) if err != nil { return nil, fmt.Errorf("failed to get accessibility tree: %w", err) } if !resp.Success { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to get accessibility tree: %s", resp.Error)), }, IsError: true, }, nil } resultJSON, err := json.MarshalIndent(resp.Data, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal result: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(string(resultJSON)), }, IsError: false, }, nil }) mcpServer.AddTool(mcp.Tool{ Name: "get_partial_accessibility_tree_cremotemcp", Description: "Get accessibility tree for a specific element and its relatives", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for the target element", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "fetch_relatives": map[string]any{ "type": "boolean", "description": "Whether to fetch ancestors, siblings, and children", "default": true, }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{"selector"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selector := getStringParam(params, "selector", "") if selector == "" { return nil, fmt.Errorf("selector parameter is required") } tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) fetchRelatives := getBoolParam(params, "fetch_relatives", true) result, err := cremoteServer.client.GetPartialAccessibilityTree(tab, selector, fetchRelatives, timeout) if err != nil { return nil, fmt.Errorf("failed to get partial accessibility tree: %w", err) } resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal result: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(string(resultJSON)), }, IsError: false, }, nil }) mcpServer.AddTool(mcp.Tool{ Name: "query_accessibility_tree_cremotemcp", Description: "Query accessibility tree for nodes matching specific criteria", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "selector": map[string]any{ "type": "string", "description": "CSS selector to limit search scope (optional)", }, "accessible_name": map[string]any{ "type": "string", "description": "Accessible name to match (optional)", }, "role": map[string]any{ "type": "string", "description": "ARIA role to match (optional)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds", "default": 5, }, }, Required: []string{}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 5) selector := getStringParam(params, "selector", "") accessibleName := getStringParam(params, "accessible_name", "") role := getStringParam(params, "role", "") // At least one search criteria must be provided if selector == "" && accessibleName == "" && role == "" { return nil, fmt.Errorf("at least one search criteria (selector, accessible_name, or role) must be provided") } result, err := cremoteServer.client.QueryAccessibilityTree(tab, selector, accessibleName, role, timeout) if err != nil { return nil, fmt.Errorf("failed to query accessibility tree: %w", err) } resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal result: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(string(resultJSON)), }, IsError: false, }, nil }) // Cache management tools mcpServer.AddTool(mcp.Tool{ Name: "web_disable_cache_cremotemcp", Description: "Disable browser cache for a tab", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) err := cremoteServer.client.DisableCache(tab, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error disabling cache: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Cache disabled successfully"), }, IsError: false, }, nil }) mcpServer.AddTool(mcp.Tool{ Name: "web_enable_cache_cremotemcp", Description: "Enable browser cache for a tab", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) err := cremoteServer.client.EnableCache(tab, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error enabling cache: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Cache enabled successfully"), }, IsError: false, }, nil }) mcpServer.AddTool(mcp.Tool{ Name: "web_clear_cache_cremotemcp", Description: "Clear browser cache for a tab", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) err := cremoteServer.client.ClearCache(tab, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error clearing cache: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Cache cleared successfully"), }, IsError: false, }, nil }) // Site data management tools mcpServer.AddTool(mcp.Tool{ Name: "web_clear_all_site_data_cremotemcp", Description: "Clear all site data including cookies, storage, cache, etc. for a tab", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) err := cremoteServer.client.ClearAllSiteData(tab, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error clearing all site data: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("All site data cleared successfully"), }, IsError: false, }, nil }) mcpServer.AddTool(mcp.Tool{ Name: "web_clear_cookies_cremotemcp", Description: "Clear cookies for a tab", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) err := cremoteServer.client.ClearCookies(tab, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error clearing cookies: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Cookies cleared successfully"), }, IsError: false, }, nil }) mcpServer.AddTool(mcp.Tool{ Name: "web_clear_storage_cremotemcp", Description: "Clear web storage (localStorage, sessionStorage, IndexedDB, etc.) for a tab", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) err := cremoteServer.client.ClearStorage(tab, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error clearing storage: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Storage cleared successfully"), }, IsError: false, }, nil }) // Drag and drop tools mcpServer.AddTool(mcp.Tool{ Name: "web_drag_and_drop_cremotemcp", Description: "Perform drag and drop operation from source element to target element", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "source": map[string]any{ "type": "string", "description": "CSS selector for the source element to drag", }, "target": map[string]any{ "type": "string", "description": "CSS selector for the target element to drop on", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"source", "target"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } source := getStringParam(params, "source", "") target := getStringParam(params, "target", "") tab := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) if source == "" { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Error: source parameter is required"), }, IsError: true, }, nil } if target == "" { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Error: target parameter is required"), }, IsError: true, }, nil } err := cremoteServer.client.DragAndDrop(tab, source, target, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error performing drag and drop: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Drag and drop completed successfully from %s to %s", source, target)), }, IsError: false, }, nil }) mcpServer.AddTool(mcp.Tool{ Name: "web_drag_and_drop_coordinates_cremotemcp", Description: "Perform drag and drop operation from source element to specific coordinates", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "source": map[string]any{ "type": "string", "description": "CSS selector for the source element to drag", }, "x": map[string]any{ "type": "integer", "description": "Target X coordinate", }, "y": map[string]any{ "type": "integer", "description": "Target Y coordinate", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"source", "x", "y"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } source := getStringParam(params, "source", "") x := getIntParam(params, "x", 0) y := getIntParam(params, "y", 0) tab := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) if source == "" { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Error: source parameter is required"), }, IsError: true, }, nil } err := cremoteServer.client.DragAndDropToCoordinates(tab, source, x, y, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error performing drag and drop to coordinates: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Drag and drop to coordinates (%d, %d) completed successfully from %s", x, y, source)), }, IsError: false, }, nil }) mcpServer.AddTool(mcp.Tool{ Name: "web_drag_and_drop_offset_cremotemcp", Description: "Perform drag and drop operation from source element by relative offset", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "source": map[string]any{ "type": "string", "description": "CSS selector for the source element to drag", }, "offset_x": map[string]any{ "type": "integer", "description": "Horizontal offset in pixels", }, "offset_y": map[string]any{ "type": "integer", "description": "Vertical offset in pixels", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"source", "offset_x", "offset_y"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Convert arguments to map params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } source := getStringParam(params, "source", "") offsetX := getIntParam(params, "offset_x", 0) offsetY := getIntParam(params, "offset_y", 0) tab := getStringParam(params, "tab", "") timeout := getIntParam(params, "timeout", 5) if source == "" { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Error: source parameter is required"), }, IsError: true, }, nil } err := cremoteServer.client.DragAndDropByOffset(tab, source, offsetX, offsetY, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error performing drag and drop by offset: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Drag and drop by offset (%d, %d) completed successfully from %s", offsetX, offsetY, source)), }, IsError: false, }, nil }) // Register web_right_click tool mcpServer.AddTool(mcp.Tool{ Name: "web_right_click_cremotemcp", Description: "Right-click on an element", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for the element to right-click", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"selector"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selector, ok := params["selector"].(string) if !ok || selector == "" { return nil, fmt.Errorf("selector is required") } tab := "" if tabParam, exists := params["tab"]; exists { if tabStr, ok := tabParam.(string); ok { tab = tabStr } } timeout := 5 if timeoutParam, exists := params["timeout"]; exists { if timeoutFloat, ok := timeoutParam.(float64); ok { timeout = int(timeoutFloat) } } err := cremoteServer.client.RightClick(tab, selector, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to right-click: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Right-click completed successfully"), }, IsError: false, }, nil }) // Register web_double_click tool mcpServer.AddTool(mcp.Tool{ Name: "web_double_click_cremotemcp", Description: "Double-click on an element", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for the element to double-click", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"selector"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selector, ok := params["selector"].(string) if !ok || selector == "" { return nil, fmt.Errorf("selector is required") } tab := "" if tabParam, exists := params["tab"]; exists { if tabStr, ok := tabParam.(string); ok { tab = tabStr } } timeout := 5 if timeoutParam, exists := params["timeout"]; exists { if timeoutFloat, ok := timeoutParam.(float64); ok { timeout = int(timeoutFloat) } } err := cremoteServer.client.DoubleClick(tab, selector, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to double-click: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Double-click completed successfully"), }, IsError: false, }, nil }) // Register web_hover tool mcpServer.AddTool(mcp.Tool{ Name: "web_hover_cremotemcp", Description: "Hover over an element", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for the element to hover over", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"selector"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selector, ok := params["selector"].(string) if !ok || selector == "" { return nil, fmt.Errorf("selector is required") } tab := "" if tabParam, exists := params["tab"]; exists { if tabStr, ok := tabParam.(string); ok { tab = tabStr } } timeout := 5 if timeoutParam, exists := params["timeout"]; exists { if timeoutFloat, ok := timeoutParam.(float64); ok { timeout = int(timeoutFloat) } } err := cremoteServer.client.Hover(tab, selector, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to hover: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Hover completed successfully"), }, IsError: false, }, nil }) // Register web_middle_click tool mcpServer.AddTool(mcp.Tool{ Name: "web_middle_click_cremotemcp", Description: "Middle-click on an element (typically opens links in new tabs)", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for the element to middle-click", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"selector"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selector, ok := params["selector"].(string) if !ok || selector == "" { return nil, fmt.Errorf("selector is required") } tab := "" if tabParam, exists := params["tab"]; exists { if tabStr, ok := tabParam.(string); ok { tab = tabStr } } timeout := 5 if timeoutParam, exists := params["timeout"]; exists { if timeoutFloat, ok := timeoutParam.(float64); ok { timeout = int(timeoutFloat) } } err := cremoteServer.client.MiddleClick(tab, selector, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to middle-click: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Middle-click completed successfully"), }, IsError: false, }, nil }) // Register web_mouse_move tool mcpServer.AddTool(mcp.Tool{ Name: "web_mouse_move_cremotemcp", Description: "Move mouse to specific coordinates without clicking", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "x": map[string]any{ "type": "integer", "description": "X coordinate to move mouse to", }, "y": map[string]any{ "type": "integer", "description": "Y coordinate to move mouse to", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"x", "y"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } xFloat, ok := params["x"].(float64) if !ok { return nil, fmt.Errorf("x coordinate is required") } x := int(xFloat) yFloat, ok := params["y"].(float64) if !ok { return nil, fmt.Errorf("y coordinate is required") } y := int(yFloat) tab := "" if tabParam, exists := params["tab"]; exists { if tabStr, ok := tabParam.(string); ok { tab = tabStr } } timeout := 5 if timeoutParam, exists := params["timeout"]; exists { if timeoutFloat, ok := timeoutParam.(float64); ok { timeout = int(timeoutFloat) } } err := cremoteServer.client.MouseMove(tab, x, y, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to move mouse: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Mouse moved to coordinates (%d, %d) successfully", x, y)), }, IsError: false, }, nil }) // Register web_scroll_wheel tool mcpServer.AddTool(mcp.Tool{ Name: "web_scroll_wheel_cremotemcp", Description: "Scroll with mouse wheel at specific coordinates", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "x": map[string]any{ "type": "integer", "description": "X coordinate for scroll wheel", }, "y": map[string]any{ "type": "integer", "description": "Y coordinate for scroll wheel", }, "delta_x": map[string]any{ "type": "integer", "description": "Horizontal scroll delta (negative = left, positive = right)", "default": 0, }, "delta_y": map[string]any{ "type": "integer", "description": "Vertical scroll delta (negative = up, positive = down)", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"x", "y", "delta_y"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } xFloat, ok := params["x"].(float64) if !ok { return nil, fmt.Errorf("x coordinate is required") } x := int(xFloat) yFloat, ok := params["y"].(float64) if !ok { return nil, fmt.Errorf("y coordinate is required") } y := int(yFloat) deltaXFloat := float64(0) if deltaXParam, exists := params["delta_x"]; exists { if deltaXVal, ok := deltaXParam.(float64); ok { deltaXFloat = deltaXVal } } deltaX := int(deltaXFloat) deltaYFloat, ok := params["delta_y"].(float64) if !ok { return nil, fmt.Errorf("delta_y is required") } deltaY := int(deltaYFloat) tab := "" if tabParam, exists := params["tab"]; exists { if tabStr, ok := tabParam.(string); ok { tab = tabStr } } timeout := 5 if timeoutParam, exists := params["timeout"]; exists { if timeoutFloat, ok := timeoutParam.(float64); ok { timeout = int(timeoutFloat) } } err := cremoteServer.client.ScrollWheel(tab, x, y, deltaX, deltaY, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to scroll with mouse wheel: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Mouse wheel scroll at (%d, %d) with delta (%d, %d) completed successfully", x, y, deltaX, deltaY)), }, IsError: false, }, nil }) // Register web_key_combination tool mcpServer.AddTool(mcp.Tool{ Name: "web_key_combination_cremotemcp", Description: "Send key combinations like Ctrl+C, Alt+Tab, Shift+Enter, etc.", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "keys": map[string]any{ "type": "string", "description": "Key combination to send (e.g., 'Ctrl+C', 'Alt+Tab', 'Shift+Enter', 'Ctrl+Shift+T')", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"keys"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } keys, ok := params["keys"].(string) if !ok || keys == "" { return nil, fmt.Errorf("keys parameter is required") } tab := "" if tabParam, exists := params["tab"]; exists { if tabStr, ok := tabParam.(string); ok { tab = tabStr } } timeout := 5 if timeoutParam, exists := params["timeout"]; exists { if timeoutFloat, ok := timeoutParam.(float64); ok { timeout = int(timeoutFloat) } } err := cremoteServer.client.KeyCombination(tab, keys, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to send key combination: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Key combination '%s' sent successfully", keys)), }, IsError: false, }, nil }) // Register web_special_key tool mcpServer.AddTool(mcp.Tool{ Name: "web_special_key_cremotemcp", Description: "Send special keys like Enter, Escape, Tab, F1-F12, Arrow keys, etc.", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "key": map[string]any{ "type": "string", "description": "Special key to send (e.g., 'Enter', 'Escape', 'Tab', 'F1', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown', 'Delete', 'Backspace')", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"key"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } key, ok := params["key"].(string) if !ok || key == "" { return nil, fmt.Errorf("key parameter is required") } tab := "" if tabParam, exists := params["tab"]; exists { if tabStr, ok := tabParam.(string); ok { tab = tabStr } } timeout := 5 if timeoutParam, exists := params["timeout"]; exists { if timeoutFloat, ok := timeoutParam.(float64); ok { timeout = int(timeoutFloat) } } err := cremoteServer.client.SpecialKey(tab, key, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to send special key: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Special key '%s' sent successfully", key)), }, IsError: false, }, nil }) // Register web_modifier_click tool mcpServer.AddTool(mcp.Tool{ Name: "web_modifier_click_cremotemcp", Description: "Click on an element with modifier keys (Ctrl+click, Shift+click, Alt+click, etc.)", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "selector": map[string]any{ "type": "string", "description": "CSS selector for the element to click", }, "modifiers": map[string]any{ "type": "string", "description": "Modifier keys to hold while clicking (e.g., 'Ctrl', 'Shift', 'Alt', 'Ctrl+Shift', 'Ctrl+Alt')", }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 5)", "default": 5, }, }, Required: []string{"selector", "modifiers"}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } selector, ok := params["selector"].(string) if !ok || selector == "" { return nil, fmt.Errorf("selector is required") } modifiers, ok := params["modifiers"].(string) if !ok || modifiers == "" { return nil, fmt.Errorf("modifiers parameter is required") } tab := "" if tabParam, exists := params["tab"]; exists { if tabStr, ok := tabParam.(string); ok { tab = tabStr } } timeout := 5 if timeoutParam, exists := params["timeout"]; exists { if timeoutFloat, ok := timeoutParam.(float64); ok { timeout = int(timeoutFloat) } } err := cremoteServer.client.ModifierClick(tab, selector, modifiers, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to perform modifier click: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Modifier click with '%s' on %s completed successfully", modifiers, selector)), }, IsError: false, }, nil }) // Register web_inject_axe tool mcpServer.AddTool(mcp.Tool{ Name: "web_inject_axe_cremotemcp", Description: "Inject axe-core accessibility testing library into the page for comprehensive WCAG 2.1 AA/AAA testing", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "version": map[string]any{ "type": "string", "description": "Axe-core version (optional, defaults to 4.8.0)", "default": "4.8.0", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 10)", "default": 10, }, }, Required: []string{}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", cremoteServer.currentTab) version := getStringParam(params, "version", "4.8.0") timeout := getIntParam(params, "timeout", 10) err := cremoteServer.client.InjectAxeCore(tab, version, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to inject axe-core: %v", err)), }, IsError: true, }, nil } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Successfully injected axe-core v%s", version)), }, IsError: false, }, nil }) // Register web_run_axe tool mcpServer.AddTool(mcp.Tool{ Name: "web_run_axe_cremotemcp", Description: "Run axe-core accessibility tests and return violations, passes, incomplete checks, and inapplicable rules", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "run_only": map[string]any{ "type": "array", "description": "Array of tags to run (e.g., ['wcag2a', 'wcag2aa', 'wcag21aa'])", "items": map[string]any{ "type": "string", }, }, "rules": map[string]any{ "type": "object", "description": "Specific rules configuration (optional)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 30)", "default": 30, }, }, Required: []string{}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 30) // Build options options := make(map[string]interface{}) if runOnly, ok := params["run_only"].([]interface{}); ok && len(runOnly) > 0 { tags := make([]string, 0, len(runOnly)) for _, tag := range runOnly { if tagStr, ok := tag.(string); ok { tags = append(tags, tagStr) } } if len(tags) > 0 { options["runOnly"] = map[string]interface{}{ "type": "tag", "values": tags, } } } if rules, ok := params["rules"].(map[string]interface{}); ok && len(rules) > 0 { options["rules"] = rules } result, err := cremoteServer.client.RunAxeCore(tab, options, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to run axe-core: %v", err)), }, IsError: true, }, nil } // Format results as JSON resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal results: %w", err) } // Create summary summary := fmt.Sprintf("Axe-core Accessibility Test Results:\n\n"+ "URL: %s\n"+ "Timestamp: %s\n"+ "Test Engine: %s v%s\n\n"+ "Summary:\n"+ " Violations: %d\n"+ " Passes: %d\n"+ " Incomplete: %d (require manual review)\n"+ " Inapplicable: %d\n\n"+ "Full Results:\n%s", result.URL, result.Timestamp, result.TestEngine.Name, result.TestEngine.Version, len(result.Violations), len(result.Passes), len(result.Incomplete), len(result.Inapplicable), string(resultJSON)) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(summary), }, IsError: false, }, nil }) // Register web_contrast_check tool mcpServer.AddTool(mcp.Tool{ Name: "web_contrast_check_cremotemcp", Description: "Check color contrast ratios for text elements and verify WCAG AA/AAA compliance", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "selector": map[string]any{ "type": "string", "description": "CSS selector for specific elements (optional, defaults to all text elements)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 10)", "default": 10, }, }, Required: []string{}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", cremoteServer.currentTab) selector := getStringParam(params, "selector", "") timeout := getIntParam(params, "timeout", 10) result, err := cremoteServer.client.CheckContrast(tab, selector, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to check contrast: %v", err)), }, IsError: true, }, nil } // Format results as JSON resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal results: %w", err) } // Create summary with violations summary := fmt.Sprintf("Color Contrast Check Results:\n\n"+ "Summary:\n"+ " Total Elements Checked: %d\n"+ " WCAG AA Compliance:\n"+ " Passed: %d\n"+ " Failed: %d\n"+ " WCAG AAA Compliance:\n"+ " Passed: %d\n"+ " Failed: %d\n"+ " Unable to Check: %d\n\n", result.TotalElements, result.PassedAA, result.FailedAA, result.PassedAAA, result.FailedAAA, result.UnableToCheck) // Add violations if any if result.FailedAA > 0 { summary += "WCAG AA Violations:\n" for _, elem := range result.Elements { if !elem.PassesAA && elem.Error == "" { summary += fmt.Sprintf(" - %s: %.2f:1 (required: %.1f:1)\n"+ " Text: %s\n"+ " Colors: %s on %s\n", elem.Selector, elem.ContrastRatio, elem.RequiredAA, elem.Text, elem.ForegroundColor, elem.BackgroundColor) } } summary += "\n" } summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON)) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(summary), }, IsError: false, }, nil }) // Register web_keyboard_test tool mcpServer.AddTool(mcp.Tool{ Name: "web_keyboard_test_cremotemcp", Description: "Test keyboard navigation and accessibility including tab order, focus indicators, and keyboard traps", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 15)", "default": 15, }, }, Required: []string{}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 15) result, err := cremoteServer.client.TestKeyboardNavigation(tab, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to test keyboard navigation: %v", err)), }, IsError: true, }, nil } // Format results as JSON resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal results: %w", err) } // Create summary with issues summary := fmt.Sprintf("Keyboard Navigation Test Results:\n\n"+ "Summary:\n"+ " Total Interactive Elements: %d\n"+ " Keyboard Focusable: %d\n"+ " Not Focusable: %d\n"+ " Missing Focus Indicator: %d\n"+ " Keyboard Traps Detected: %d\n"+ " Total Issues: %d\n\n", result.TotalInteractive, result.Focusable, result.NotFocusable, result.NoFocusIndicator, result.KeyboardTraps, len(result.Issues)) // Add high severity issues highSeverityIssues := 0 for _, issue := range result.Issues { if issue.Severity == "high" { highSeverityIssues++ } } if highSeverityIssues > 0 { summary += fmt.Sprintf("High Severity Issues (%d):\n", highSeverityIssues) count := 0 for _, issue := range result.Issues { if issue.Severity == "high" && count < 10 { summary += fmt.Sprintf(" - %s: %s\n Element: %s\n", issue.Type, issue.Description, issue.Element) count++ } } if highSeverityIssues > 10 { summary += fmt.Sprintf(" ... and %d more issues\n", highSeverityIssues-10) } summary += "\n" } // Add tab order summary if len(result.TabOrder) > 0 { summary += fmt.Sprintf("Tab Order (first 5 elements):\n") for i, elem := range result.TabOrder { if i >= 5 { summary += fmt.Sprintf(" ... and %d more elements\n", len(result.TabOrder)-5) break } focusIndicator := "✓" if !elem.HasFocusStyle { focusIndicator = "✗" } summary += fmt.Sprintf(" %d. %s [%s] %s - Focus: %s\n", elem.Index+1, elem.Selector, elem.TagName, elem.Text, focusIndicator) } summary += "\n" } summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON)) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(summary), }, IsError: false, }, nil }) // Register web_zoom_test tool mcpServer.AddTool(mcp.Tool{ Name: "web_zoom_test_cremotemcp", Description: "Test page at different zoom levels (100%, 200%, 400%) and verify WCAG 1.4.4 and 1.4.10 compliance", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "zoom_levels": map[string]any{ "type": "array", "description": "Array of zoom levels to test (optional, defaults to [1.0, 2.0, 4.0])", "items": map[string]any{ "type": "number", }, }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds per zoom level (default: 10)", "default": 10, }, }, Required: []string{}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 10) // Parse zoom levels if provided var zoomLevels []float64 if zoomLevelsParam, ok := params["zoom_levels"].([]interface{}); ok { for _, level := range zoomLevelsParam { if levelFloat, ok := level.(float64); ok { zoomLevels = append(zoomLevels, levelFloat) } } } result, err := cremoteServer.client.TestZoom(tab, zoomLevels, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to test zoom: %v", err)), }, IsError: true, }, nil } // Format results as JSON resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal results: %w", err) } // Create summary summary := fmt.Sprintf("Zoom Level Test Results:\n\n"+ "Tested %d zoom levels\n"+ "Total Issues: %d\n\n", len(result.ZoomLevels), len(result.Issues)) // Add zoom level details for _, zoomTest := range result.ZoomLevels { status := "✓ PASS" if zoomTest.HasHorizontalScroll || zoomTest.OverflowingElements > 0 || !zoomTest.TextReadable { status = "✗ FAIL" } summary += fmt.Sprintf("Zoom %.0f%% %s:\n"+ " Viewport: %dx%d\n"+ " Content: %dx%d\n"+ " Horizontal Scroll: %v\n"+ " Overflowing Elements: %d\n"+ " Text Readable: %v\n\n", zoomTest.ZoomLevel*100, status, zoomTest.ViewportWidth, zoomTest.ViewportHeight, zoomTest.ContentWidth, zoomTest.ContentHeight, zoomTest.HasHorizontalScroll, zoomTest.OverflowingElements, zoomTest.TextReadable) } // Add issues if len(result.Issues) > 0 { summary += "Issues Found:\n" for _, issue := range result.Issues { summary += fmt.Sprintf(" [%.0f%%] %s (%s): %s\n", issue.ZoomLevel*100, issue.Type, issue.Severity, issue.Description) } summary += "\n" } summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON)) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(summary), }, IsError: false, }, nil }) // Register web_reflow_test tool mcpServer.AddTool(mcp.Tool{ Name: "web_reflow_test_cremotemcp", Description: "Test responsive design at WCAG breakpoints (320px, 1280px) and verify WCAG 1.4.10 reflow compliance", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "widths": map[string]any{ "type": "array", "description": "Array of viewport widths to test in pixels (optional, defaults to [320, 1280])", "items": map[string]any{ "type": "integer", }, }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds per width (default: 10)", "default": 10, }, }, Required: []string{}, }, }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { params, ok := request.Params.Arguments.(map[string]any) if !ok { return nil, fmt.Errorf("invalid arguments format") } tab := getStringParam(params, "tab", cremoteServer.currentTab) timeout := getIntParam(params, "timeout", 10) // Parse widths if provided var widths []int if widthsParam, ok := params["widths"].([]interface{}); ok { for _, width := range widthsParam { if widthFloat, ok := width.(float64); ok { widths = append(widths, int(widthFloat)) } } } result, err := cremoteServer.client.TestReflow(tab, widths, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to test reflow: %v", err)), }, IsError: true, }, nil } // Format results as JSON resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal results: %w", err) } // Create summary summary := fmt.Sprintf("Reflow/Responsive Design Test Results:\n\n"+ "Tested %d breakpoints\n"+ "Total Issues: %d\n\n", len(result.Breakpoints), len(result.Issues)) // Add breakpoint details for _, breakpoint := range result.Breakpoints { status := "✓ PASS" if breakpoint.HasHorizontalScroll || !breakpoint.ResponsiveLayout || breakpoint.OverflowingElements > 0 { status = "✗ FAIL" } summary += fmt.Sprintf("%dpx Width %s:\n"+ " Viewport: %dx%d\n"+ " Content: %dx%d\n"+ " Horizontal Scroll: %v\n"+ " Responsive Layout: %v\n"+ " Overflowing Elements: %d\n\n", breakpoint.Width, status, breakpoint.Width, breakpoint.Height, breakpoint.ContentWidth, breakpoint.ContentHeight, breakpoint.HasHorizontalScroll, breakpoint.ResponsiveLayout, breakpoint.OverflowingElements) } // Add issues if len(result.Issues) > 0 { summary += "Issues Found:\n" for _, issue := range result.Issues { summary += fmt.Sprintf(" [%dpx] %s (%s): %s\n", issue.Width, issue.Type, issue.Severity, issue.Description) } summary += "\n" } summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON)) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(summary), }, IsError: false, }, nil }) // Start the server log.Printf("Cremote MCP server ready") if err := server.ServeStdio(mcpServer); err != nil { log.Fatalf("Server error: %v", err) } }