package main import ( "context" "encoding/json" "fmt" "log" "os" "path/filepath" "strconv" "strings" "time" "git.teamworkapps.com/shortcut/cremote/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) const Version = "2.0.1" // 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 } // handleOptionalNavigation handles optional URL navigation and cache clearing before tool execution // Returns the tab ID to use (may create a new tab if needed) func handleOptionalNavigation(cremoteServer *CremoteServer, params map[string]any, timeout int) (string, error) { url := getStringParam(params, "url", "") clearCache := getBoolParam(params, "clear_cache", false) tab := getStringParam(params, "tab", cremoteServer.currentTab) // If URL is provided, navigate to it if url != "" { // If no tab specified and no current tab, create a new one if tab == "" { newTab, err := cremoteServer.client.OpenTab(timeout) if err != nil { return "", fmt.Errorf("failed to create new tab: %w", err) } tab = newTab cremoteServer.currentTab = tab cremoteServer.tabHistory = append(cremoteServer.tabHistory, tab) } // Clear cache if requested if clearCache { err := cremoteServer.client.ClearCache(tab, timeout) if err != nil { return "", fmt.Errorf("failed to clear cache: %w", err) } } // Load the URL err := cremoteServer.client.LoadURL(tab, url, timeout) if err != nil { return "", fmt.Errorf("failed to load URL: %w", err) } // Wait 3 seconds after page load to ensure JavaScript has executed time.Sleep(3 * time.Second) } else if clearCache && tab != "" { // Clear cache even if not navigating err := cremoteServer.client.ClearCache(tab, timeout) if err != nil { return "", fmt.Errorf("failed to clear cache: %w", err) } } return tab, nil } 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", Version) // 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 with optional cache clearing, screenshot, and data extraction in a single call", 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, }, "clear_cache": map[string]any{ "type": "boolean", "description": "Clear browser cache before navigation (default: false)", "default": false, }, "screenshot": map[string]any{ "type": "boolean", "description": "Take screenshot after navigation", }, "extract": map[string]any{ "type": "string", "description": "Extract data after navigation: 'source' (page HTML), 'element' (requires extract_selector), 'javascript' (requires extract_code)", "enum": []any{"source", "element", "javascript"}, }, "extract_selector": map[string]any{ "type": "string", "description": "CSS selector for element extraction (required when extract='element')", }, "extract_code": map[string]any{ "type": "string", "description": "JavaScript code to execute (required when extract='javascript')", }, }, 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) clearCache := getBoolParam(params, "clear_cache", false) takeScreenshot := getBoolParam(params, "screenshot", false) extractType := getStringParam(params, "extract", "") extractSelector := getStringParam(params, "extract_selector", "") extractCode := getStringParam(params, "extract_code", "") // 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) } // Clear cache if requested if clearCache { err := cremoteServer.client.ClearCache(tab, timeout) if err != nil { return nil, fmt.Errorf("failed to clear cache: %w", err) } } // Load the URL err := cremoteServer.client.LoadURL(tab, url, timeout) if err != nil { return nil, fmt.Errorf("failed to load URL: %w", err) } // Wait 3 seconds after page load to ensure JavaScript has executed time.Sleep(3 * time.Second) message := fmt.Sprintf("Successfully navigated to %s in tab %s", url, tab) var extractedData string // Extract data if requested if extractType != "" { switch extractType { case "source": data, err := cremoteServer.client.GetPageSource(tab, timeout) if err != nil { return nil, fmt.Errorf("failed to extract page source: %w", err) } extractedData = data message += "\n\nExtracted page source" case "element": if extractSelector == "" { return nil, fmt.Errorf("extract_selector is required when extract='element'") } data, err := cremoteServer.client.GetElementHTML(tab, extractSelector, timeout) if err != nil { return nil, fmt.Errorf("failed to extract element: %w", err) } extractedData = data message += fmt.Sprintf("\n\nExtracted element: %s", extractSelector) case "javascript": if extractCode == "" { return nil, fmt.Errorf("extract_code is required when extract='javascript'") } data, err := cremoteServer.client.EvalJS(tab, extractCode, timeout) if err != nil { return nil, fmt.Errorf("failed to execute JavaScript: %w", err) } extractedData = data message += "\n\nExecuted JavaScript" default: return nil, fmt.Errorf("unknown extraction type: %s", extractType) } } // 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("\n\nScreenshot saved to %s", screenshotPath) } } // Build response content content := []mcp.Content{mcp.NewTextContent(message)} if extractedData != "" { content = append(content, mcp.NewTextContent(fmt.Sprintf("\n\nExtracted data:\n%s", extractedData))) } return &mcp.CallToolResult{ Content: content, IsError: false, }, nil }) // Register web_interact tool mcpServer.AddTool(mcp.Tool{ Name: "web_interact_cremotemcp", Description: "Interact with web elements (click, fill, submit, upload). For upload action, automatically handles file transfer from host to container if needed.", 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). For upload, can be either a local host path or container path - will auto-detect and transfer if needed.", }, "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") } // Auto-detect if file needs to be transferred to container // If the path doesn't start with /tmp/ or other container paths, assume it's a host path containerPath := value var transferredFile bool // Check if file exists in container - if not, try to upload from host if !strings.HasPrefix(value, "/tmp/") && !strings.HasPrefix(value, "/var/") { // This looks like a host path, try to upload to container uploadedPath, uploadErr := cremoteServer.client.UploadFileToContainer(value, "") if uploadErr == nil { containerPath = uploadedPath transferredFile = true } else { // If upload fails, maybe it's already a valid container path, try to use it as-is // The actual upload to the form will fail if the file doesn't exist } } err = cremoteServer.client.UploadFile(tab, selector, containerPath, timeout) if transferredFile { message = fmt.Sprintf("Transferred file from host (%s) to container (%s) and uploaded to element %s", value, containerPath, selector) } else { message = fmt.Sprintf("Uploaded file %s to element %s", containerPath, 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). Optionally navigate to URL first with cache clearing.", 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)", }, "url": map[string]any{ "type": "string", "description": "Optional URL to navigate to before extraction", }, "clear_cache": map[string]any{ "type": "boolean", "description": "Clear browser cache before operation (default: false)", "default": false, }, "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", "") timeout := getIntParam(params, "timeout", 5) if extractType == "" { return nil, fmt.Errorf("type parameter is required") } // Handle optional navigation and cache clearing tab, err := handleOptionalNavigation(cremoteServer, params, timeout) if err != nil { return nil, err } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first or provide url parameter") } var data string 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. Automatically downloads the screenshot from container to host.", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "output": map[string]any{ "type": "string", "description": "Output file path on the host machine (e.g., /home/user/screenshots/page.png). The screenshot will be automatically downloaded from the container.", }, "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") } // Determine container path and host path var containerPath, hostPath string // If output path starts with /tmp/ or /var/, treat it as container path if strings.HasPrefix(output, "/tmp/") || strings.HasPrefix(output, "/var/") { containerPath = output // Generate a default host path hostPath = "/tmp/" + filepath.Base(output) } else { // Treat as host path, generate container path hostPath = output containerPath = "/tmp/screenshot-" + strconv.FormatInt(time.Now().Unix(), 10) + filepath.Ext(output) } // Build command parameters for taking screenshot in container cmdParams := map[string]string{ "output": containerPath, "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 to take screenshot 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 } // Automatically download the screenshot from container to host err = cremoteServer.client.DownloadFileFromContainer(containerPath, hostPath) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Screenshot taken in container (%s) but failed to download to host: %v", containerPath, err)), }, IsError: true, }, nil } cremoteServer.screenshots = append(cremoteServer.screenshots, hostPath) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Screenshot saved to %s (automatically downloaded from container)", hostPath)), }, 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. Optionally navigate to URL first with cache clearing.", 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", }, "url": map[string]any{ "type": "string", "description": "Optional URL to navigate to before checking element", }, "clear_cache": map[string]any{ "type": "boolean", "description": "Clear browser cache before operation (default: false)", "default": false, }, "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") timeout := getIntParam(params, "timeout", 5) if selector == "" { return nil, fmt.Errorf("selector parameter is required") } // Handle optional navigation and cache clearing tab, err := handleOptionalNavigation(cremoteServer, params, timeout) if err != nil { return nil, err } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first or provide url parameter") } 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. Optionally navigate to URL first with cache clearing.", 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", }, "url": map[string]any{ "type": "string", "description": "Optional URL to navigate to before getting attributes", }, "clear_cache": map[string]any{ "type": "boolean", "description": "Clear browser cache before operation (default: false)", "default": false, }, "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") timeout := getIntParam(params, "timeout", 5) if selector == "" { return nil, fmt.Errorf("selector parameter is required") } // Handle optional navigation and cache clearing tab, err := handleOptionalNavigation(cremoteServer, params, timeout) if err != nil { return nil, err } if tab == "" { return nil, fmt.Errorf("no tab available - navigate to a page first or provide url parameter") } 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. Optionally navigate to URL first with cache clearing.", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "url": map[string]any{ "type": "string", "description": "Optional URL to navigate to before getting page info", }, "clear_cache": map[string]any{ "type": "boolean", "description": "Clear browser cache before operation (default: false)", "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)", "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") } timeout := getIntParam(params, "timeout", 5) // Handle optional navigation and cache clearing tabID, err := handleOptionalNavigation(cremoteServer, params, timeout) if err != nil { return nil, err } 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. Automatically downloads the screenshot from container to host.", 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 on the host machine. The screenshot will be automatically downloaded from the container.", }, "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") } // Determine container path and host path var containerPath, hostPath string // If output path starts with /tmp/ or /var/, treat it as container path if strings.HasPrefix(output, "/tmp/") || strings.HasPrefix(output, "/var/") { containerPath = output // Generate a default host path hostPath = "/tmp/" + filepath.Base(output) } else { // Treat as host path, generate container path hostPath = output containerPath = "/tmp/screenshot-element-" + strconv.FormatInt(time.Now().Unix(), 10) + filepath.Ext(output) } // Take screenshot in container err := cremoteServer.client.ScreenshotElement(tab, selector, containerPath, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error taking element screenshot: %v", err)), }, IsError: true, }, nil } // Automatically download the screenshot from container to host err = cremoteServer.client.DownloadFileFromContainer(containerPath, hostPath) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Screenshot taken in container (%s) but failed to download to host: %v", containerPath, err)), }, IsError: true, }, nil } cremoteServer.screenshots = append(cremoteServer.screenshots, hostPath) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Element screenshot saved to %s (automatically downloaded from container)", hostPath)), }, 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). Automatically downloads the screenshot from container to host.", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "output": map[string]any{ "type": "string", "description": "Path where to save the screenshot on the host machine. The screenshot will be automatically downloaded from the container.", }, "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") } // Determine container path and host path var containerPath, hostPath string // If output path starts with /tmp/ or /var/, treat it as container path if strings.HasPrefix(output, "/tmp/") || strings.HasPrefix(output, "/var/") { containerPath = output // Generate a default host path hostPath = "/tmp/" + filepath.Base(output) } else { // Treat as host path, generate container path hostPath = output containerPath = "/tmp/screenshot-enhanced-" + strconv.FormatInt(time.Now().Unix(), 10) + filepath.Ext(output) } // Take enhanced screenshot in container metadata, err := cremoteServer.client.ScreenshotEnhanced(tab, containerPath, fullPage, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Error taking enhanced screenshot: %v", err)), }, IsError: true, }, nil } // Automatically download the screenshot from container to host err = cremoteServer.client.DownloadFileFromContainer(containerPath, hostPath) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Screenshot taken in container (%s) but failed to download to host: %v", containerPath, err)), }, IsError: true, }, nil } cremoteServer.screenshots = append(cremoteServer.screenshots, hostPath) 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 to %s (automatically downloaded from container) with metadata:\n%s", hostPath, 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. NOTE: Auto-injection happens automatically when using web_run_axe_cremotemcp or web_page_accessibility_report_cremotemcp, so manual injection is optional.", 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. Automatically injects axe-core if not already loaded.", 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 { 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_gradient_contrast_check tool mcpServer.AddTool(mcp.Tool{ Name: "web_gradient_contrast_check_cremotemcp", Description: "Check color contrast for text on gradient backgrounds using ImageMagick analysis. Samples 100 points across the background and reports worst-case contrast ratio.", 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 element with gradient background (required)", }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds (default: 10)", "default": 10, }, }, 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") } tab := getStringParam(params, "tab", cremoteServer.currentTab) selector := getStringParam(params, "selector", "") timeout := getIntParam(params, "timeout", 10) if selector == "" { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Error: selector parameter is required for gradient contrast check"), }, IsError: true, }, nil } result, err := cremoteServer.client.CheckGradientContrast(tab, selector, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to check gradient contrast: %v", err)), }, IsError: true, }, nil } // Check if there was an error in the result if result.Error != "" { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Gradient Contrast Check Error:\n%s", result.Error)), }, 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 complianceStatus := "✅ PASS" if !result.PassesAA { complianceStatus = "❌ FAIL" } summary := fmt.Sprintf("Gradient Contrast Check Results:\n\n"+ "Element: %s\n"+ "Text Color: %s\n"+ "Background Gradient Range:\n"+ " Darkest: %s\n"+ " Lightest: %s\n\n"+ "Contrast Ratios:\n"+ " Worst Case: %.2f:1\n"+ " Best Case: %.2f:1\n\n"+ "WCAG Compliance:\n"+ " Text Size: %s\n"+ " Required AA: %.1f:1\n"+ " Required AAA: %.1f:1\n"+ " AA Compliance: %s\n"+ " AAA Compliance: %s\n\n"+ "Analysis:\n"+ " Sample Points: %d\n"+ " Status: %s\n\n", result.Selector, result.TextColor, result.DarkestBgColor, result.LightestBgColor, result.WorstContrast, result.BestContrast, map[bool]string{true: "Large (18pt+ or 14pt+ bold)", false: "Normal"}[result.IsLargeText], result.RequiredAA, result.RequiredAAA, map[bool]string{true: "✅ PASS", false: "❌ FAIL"}[result.PassesAA], map[bool]string{true: "✅ PASS", false: "❌ FAIL"}[result.PassesAAA], result.SamplePoints, complianceStatus) if !result.PassesAA { summary += fmt.Sprintf("\n⚠️ WARNING: Worst-case contrast ratio (%.2f:1) fails WCAG AA requirements (%.1f:1)\n"+ "This gradient background creates accessibility issues for users with low vision.\n"+ "Recommendation: Adjust gradient colors or use solid background.\n", result.WorstContrast, result.RequiredAA) } summary += fmt.Sprintf("\nFull Results:\n%s", string(resultJSON)) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(summary), }, IsError: false, }, nil }) // Register web_media_validation tool mcpServer.AddTool(mcp.Tool{ Name: "web_media_validation_cremotemcp", Description: "Validate time-based media (video/audio) for WCAG compliance: checks for captions, audio descriptions, transcripts, controls, and autoplay issues", 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: 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) result, err := cremoteServer.client.ValidateMedia(tab, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to validate media: %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 complianceStatus := "✅ PASS" if result.CriticalViolations > 0 { complianceStatus = "❌ CRITICAL VIOLATIONS" } else if result.TotalViolations > 0 { complianceStatus = "⚠️ VIOLATIONS" } else if result.Warnings > 0 { complianceStatus = "⚠️ WARNINGS" } summary := fmt.Sprintf("Time-Based Media Validation Results:\n\n"+ "Summary:\n"+ " Videos Found: %d\n"+ " Audio Elements Found: %d\n"+ " Embedded Players: %d (YouTube/Vimeo)\n"+ " Transcript Links: %d\n\n"+ "Compliance Status: %s\n"+ " Critical Violations: %d\n"+ " Total Violations: %d\n"+ " Warnings: %d\n\n", len(result.Videos), len(result.Audios), len(result.EmbeddedPlayers), len(result.TranscriptLinks), complianceStatus, result.CriticalViolations, result.TotalViolations, result.Warnings) // Add video details if any violations if result.CriticalViolations > 0 || result.TotalViolations > 0 { summary += "Video Issues:\n" for i, video := range result.Videos { if len(video.Violations) > 0 || len(video.Warnings) > 0 { summary += fmt.Sprintf("\n Video %d: %s\n", i+1, video.Src) summary += fmt.Sprintf(" Has Captions: %v\n", video.HasCaptions) summary += fmt.Sprintf(" Has Descriptions: %v\n", video.HasDescriptions) summary += fmt.Sprintf(" Has Controls: %v\n", video.HasControls) summary += fmt.Sprintf(" Autoplay: %v\n", video.Autoplay) if len(video.Violations) > 0 { summary += " Violations:\n" for _, violation := range video.Violations { summary += fmt.Sprintf(" - %s\n", violation) } } if len(video.Warnings) > 0 { summary += " Warnings:\n" for _, warning := range video.Warnings { summary += fmt.Sprintf(" - %s\n", warning) } } } } summary += "\n" } // Add recommendations if critical violations if result.CriticalViolations > 0 { summary += "⚠️ CRITICAL RECOMMENDATIONS:\n" summary += " 1. Add elements to all videos\n" summary += " 2. Ensure caption files (.vtt, .srt) are accessible\n" summary += " 3. Test captions display correctly in video player\n" summary += " 4. Consider adding audio descriptions for visual content\n\n" } // Add embedded player notes if len(result.EmbeddedPlayers) > 0 { summary += "Embedded Players:\n" for i, player := range result.EmbeddedPlayers { summary += fmt.Sprintf(" %d. %s: %s\n", i+1, player.Type, player.Src) } summary += "\nNote: YouTube and Vimeo players should have captions enabled in their settings.\n" summary += "Check video settings on the platform to ensure captions are available.\n\n" } // Add transcript links if len(result.TranscriptLinks) > 0 { summary += "Transcript Links Found:\n" for i, link := range result.TranscriptLinks { summary += fmt.Sprintf(" %d. %s\n", i+1, link) } 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_hover_focus_test tool mcpServer.AddTool(mcp.Tool{ Name: "web_hover_focus_test_cremotemcp", Description: "Test WCAG 1.4.13 compliance for content on hover or focus: checks dismissibility, hoverability, and persistence", 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: 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) result, err := cremoteServer.client.TestHoverFocusContent(tab, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to test hover/focus content: %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 complianceStatus := "✅ PASS" if result.ElementsWithIssues > 0 { complianceStatus = "⚠️ ISSUES FOUND" } summary := fmt.Sprintf("Hover/Focus Content Test Results (WCAG 1.4.13):\n\n"+ "Summary:\n"+ " Total Elements Tested: %d\n"+ " Elements with Issues: %d\n"+ " Elements Passed: %d\n"+ " Compliance Status: %s\n\n", result.TotalElements, result.ElementsWithIssues, result.PassedElements, complianceStatus) // Add issues if any if len(result.Issues) > 0 { summary += "Issues Found:\n" for i, issue := range result.Issues { summary += fmt.Sprintf("\n %d. %s\n", i+1, issue.Selector) summary += fmt.Sprintf(" Type: %s\n", issue.Type) summary += fmt.Sprintf(" Severity: %s\n", issue.Severity) summary += fmt.Sprintf(" Description: %s\n", issue.Description) summary += fmt.Sprintf(" WCAG: %s\n", issue.WCAG) } summary += "\n" } // Add element details if result.TotalElements > 0 { summary += "Tested Elements:\n" for i, elem := range result.TestedElements { if len(elem.Violations) > 0 { summary += fmt.Sprintf("\n %d. %s (%s)\n", i+1, elem.Selector, elem.Type) summary += fmt.Sprintf(" Dismissible: %v\n", elem.Dismissible) summary += fmt.Sprintf(" Hoverable: %v\n", elem.Hoverable) summary += fmt.Sprintf(" Persistent: %v\n", elem.Persistent) summary += fmt.Sprintf(" Passes WCAG: %v\n", elem.PassesWCAG) if len(elem.Violations) > 0 { summary += " Violations:\n" for _, violation := range elem.Violations { summary += fmt.Sprintf(" - %s\n", violation) } } } } summary += "\n" } // Add recommendations if issues found if result.ElementsWithIssues > 0 { summary += "⚠️ RECOMMENDATIONS:\n" summary += " 1. Replace native title attributes with custom tooltips that can be dismissed with Escape\n" summary += " 2. Ensure hover/focus content can be dismissed without moving pointer/focus\n" summary += " 3. Allow pointer to move over new content without it disappearing\n" summary += " 4. Keep content visible until dismissed or no longer relevant\n" summary += " 5. Test with keyboard-only navigation (Tab, Escape keys)\n\n" } summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON)) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(summary), }, IsError: false, }, nil }) // Register web_text_in_images tool mcpServer.AddTool(mcp.Tool{ Name: "web_text_in_images_cremotemcp", Description: "Detect text in images using Tesseract OCR and flag accessibility violations (WCAG 1.4.5, 1.4.9)", 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: 30 for OCR processing)", "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) result, err := cremoteServer.client.DetectTextInImages(tab, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to detect text in images: %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 complianceStatus := "✅ PASS" if result.Violations > 0 { complianceStatus = "❌ CRITICAL VIOLATIONS" } else if result.Warnings > 0 { complianceStatus = "⚠️ WARNINGS" } summary := fmt.Sprintf("Text-in-Images Detection Results:\n\n"+ "Summary:\n"+ " Total Images Analyzed: %d\n"+ " Images with Text: %d\n"+ " Images without Text: %d\n"+ " Compliance Status: %s\n"+ " Critical Violations: %d\n"+ " Warnings: %d\n\n", result.TotalImages, result.ImagesWithText, result.ImagesWithoutText, complianceStatus, result.Violations, result.Warnings) // Add violations if any if result.Violations > 0 || result.Warnings > 0 { summary += "Images with Issues:\n" for i, img := range result.Images { if img.IsViolation { summary += fmt.Sprintf("\n %d. %s\n", i+1, img.Src) summary += fmt.Sprintf(" Has Alt: %v\n", img.HasAlt) if img.Alt != "" { summary += fmt.Sprintf(" Alt Text: \"%s\"\n", img.Alt) } summary += fmt.Sprintf(" Detected Text: \"%s\"\n", img.DetectedText) summary += fmt.Sprintf(" Text Length: %d characters\n", img.TextLength) summary += fmt.Sprintf(" Confidence: %.1f%%\n", img.Confidence*100) summary += fmt.Sprintf(" Violation Type: %s\n", img.ViolationType) summary += fmt.Sprintf(" Recommendation: %s\n", img.Recommendation) } } summary += "\n" } // Add recommendations if violations if result.Violations > 0 { summary += "⚠️ CRITICAL RECOMMENDATIONS:\n" summary += " 1. Add alt text to all images containing text\n" summary += " 2. Ensure alt text includes all text visible in the image\n" summary += " 3. Consider using real text instead of text-in-images where possible\n" summary += " 4. If text-in-images is necessary, provide equivalent text alternatives\n\n" } // Add WCAG references summary += "WCAG Criteria:\n" summary += " - WCAG 1.4.5 (Images of Text - Level AA): Use real text instead of images of text\n" summary += " - WCAG 1.4.9 (Images of Text - No Exception - Level AAA): No images of text except logos\n" summary += " - WCAG 1.1.1 (Non-text Content - Level A): All images must have text alternatives\n\n" summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON)) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(summary), }, IsError: false, }, nil }) // Register web_cross_page_consistency tool mcpServer.AddTool(mcp.Tool{ Name: "web_cross_page_consistency_cremotemcp", Description: "Check consistency of navigation, headers, footers, and landmarks across multiple pages (WCAG 3.2.3, 3.2.4)", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "urls": map[string]any{ "type": "array", "description": "Array of URLs to check for consistency", "items": map[string]any{ "type": "string", }, }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds per page (default: 10)", "default": 10, }, }, Required: []string{"urls"}, }, }, 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 URLs array urlsParam, ok := params["urls"].([]any) if !ok || len(urlsParam) == 0 { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Error: urls parameter is required and must be a non-empty array"), }, IsError: true, }, nil } urls := make([]string, 0, len(urlsParam)) for _, u := range urlsParam { if urlStr, ok := u.(string); ok { urls = append(urls, urlStr) } } if len(urls) == 0 { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent("Error: no valid URLs provided"), }, IsError: true, }, nil } result, err := cremoteServer.client.CheckCrossPageConsistency(tab, urls, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to check cross-page consistency: %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 complianceStatus := "✅ PASS" if result.ConsistencyIssues > 0 { complianceStatus = "❌ INCONSISTENCIES FOUND" } summary := fmt.Sprintf("Cross-Page Consistency Check Results:\n\n"+ "Summary:\n"+ " Pages Analyzed: %d\n"+ " Compliance Status: %s\n"+ " Total Issues: %d\n"+ " Navigation Issues: %d\n"+ " Structure Issues: %d\n"+ " Common Navigation Links: %d\n\n", result.PagesAnalyzed, complianceStatus, result.ConsistencyIssues, result.NavigationIssues, result.StructureIssues, len(result.CommonNavigation)) // Add common navigation if len(result.CommonNavigation) > 0 { summary += "Common Navigation Links (present on all pages):\n" for i, link := range result.CommonNavigation { summary += fmt.Sprintf(" %d. %s\n", i+1, link) } summary += "\n" } // Add inconsistent pages if len(result.InconsistentPages) > 0 { summary += "Pages with Inconsistencies:\n" for i, url := range result.InconsistentPages { summary += fmt.Sprintf(" %d. %s\n", i+1, url) } summary += "\n" } // Add page details if result.ConsistencyIssues > 0 { summary += "Page Details:\n" for i, page := range result.Pages { if len(page.Issues) > 0 { summary += fmt.Sprintf("\n %d. %s\n", i+1, page.URL) summary += fmt.Sprintf(" Title: %s\n", page.Title) summary += fmt.Sprintf(" Has Header: %v\n", page.HasHeader) summary += fmt.Sprintf(" Has Footer: %v\n", page.HasFooter) summary += fmt.Sprintf(" Has Navigation: %v\n", page.HasNavigation) summary += fmt.Sprintf(" Main Landmarks: %d\n", page.MainLandmarks) summary += " Issues:\n" for _, issue := range page.Issues { summary += fmt.Sprintf(" - %s\n", issue) } } } summary += "\n" } // Add recommendations if result.ConsistencyIssues > 0 { summary += "⚠️ RECOMMENDATIONS:\n" if result.NavigationIssues > 0 { summary += " 1. Ensure consistent navigation across all pages\n" summary += " 2. Use the same navigation structure and labels on every page\n" } if result.StructureIssues > 0 { summary += " 3. Add proper landmark elements (header, footer, main, nav)\n" summary += " 4. Ensure exactly one main landmark per page\n" } summary += "\n" } // Add WCAG references summary += "WCAG Criteria:\n" summary += " - WCAG 3.2.3 (Consistent Navigation - Level AA): Navigation repeated on multiple pages must be in the same relative order\n" summary += " - WCAG 3.2.4 (Consistent Identification - Level AA): Components with the same functionality must be identified consistently\n" summary += " - WCAG 1.3.1 (Info and Relationships - Level A): Proper use of landmarks for page structure\n\n" summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON)) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(summary), }, IsError: false, }, nil }) // Register web_animation_flash tool mcpServer.AddTool(mcp.Tool{ Name: "web_animation_flash_cremotemcp", Description: "Detect animations and flashing content that may trigger seizures or cause accessibility issues (WCAG 2.3.1, 2.2.2)", 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: 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) result, err := cremoteServer.client.DetectAnimationFlash(tab, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to detect animation/flash: %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 complianceStatus := "✅ PASS" if result.Violations > 0 { complianceStatus = "❌ VIOLATIONS FOUND" } else if result.Warnings > 0 { complianceStatus = "⚠️ WARNINGS" } summary := fmt.Sprintf("Animation/Flash Detection Results:\n\n"+ "Summary:\n"+ " Total Animations: %d\n"+ " Flashing Content: %d\n"+ " Rapid Animations: %d\n"+ " Autoplay Animations: %d\n"+ " Compliance Status: %s\n"+ " Violations: %d\n"+ " Warnings: %d\n\n", result.TotalAnimations, result.FlashingContent, result.RapidAnimations, result.AutoplayAnimations, complianceStatus, result.Violations, result.Warnings) // Add violations/warnings if result.Violations > 0 || result.Warnings > 0 { summary += "Elements with Issues:\n" for i, elem := range result.Elements { if elem.IsViolation || elem.ViolationType != "" { if i >= 10 { // Limit to first 10 for readability summary += fmt.Sprintf("\n ... and %d more elements\n", len(result.Elements)-10) break } summary += fmt.Sprintf("\n %d. <%s> %s\n", i+1, elem.TagName, elem.Selector) summary += fmt.Sprintf(" Animation Type: %s\n", elem.AnimationType) summary += fmt.Sprintf(" Duration: %.2fs\n", elem.Duration) if elem.FlashRate > 0 { summary += fmt.Sprintf(" Flash Rate: %.1f flashes/second\n", elem.FlashRate) } summary += fmt.Sprintf(" Autoplay: %v\n", elem.IsAutoplay) summary += fmt.Sprintf(" Has Controls: %v\n", elem.HasControls) summary += fmt.Sprintf(" Can Pause: %v\n", elem.CanPause) if elem.IsViolation { summary += fmt.Sprintf(" ❌ Violation: %s\n", elem.ViolationType) } else { summary += fmt.Sprintf(" ⚠️ Warning: %s\n", elem.ViolationType) } summary += fmt.Sprintf(" Recommendation: %s\n", elem.Recommendation) } } summary += "\n" } // Add recommendations if result.Violations > 0 { summary += "⚠️ CRITICAL RECOMMENDATIONS:\n" if result.FlashingContent > 0 { summary += " 1. Reduce flash rate to 3 or fewer flashes per second\n" summary += " 2. Provide mechanism to disable flashing content\n" } if result.AutoplayAnimations > 0 { summary += " 3. Provide pause/stop controls for autoplay animations longer than 5 seconds\n" summary += " 4. Allow users to disable animations\n" } summary += "\n" } // Add WCAG references summary += "WCAG Criteria:\n" summary += " - WCAG 2.3.1 (Three Flashes or Below Threshold - Level A): Content must not flash more than 3 times per second\n" summary += " - WCAG 2.2.2 (Pause, Stop, Hide - Level A): Moving, blinking, or auto-updating content must have pause/stop controls\n" summary += " - WCAG 2.3.2 (Three Flashes - Level AAA): No content flashes more than 3 times per second\n\n" summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON)) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(summary), }, IsError: false, }, nil }) // Register web_enhanced_accessibility tool mcpServer.AddTool(mcp.Tool{ Name: "web_enhanced_accessibility_cremotemcp", Description: "Perform enhanced accessibility tree analysis with ARIA validation, role verification, and relationship checking (WCAG 1.3.1, 4.1.2)", 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: 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) result, err := cremoteServer.client.AnalyzeEnhancedAccessibility(tab, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to analyze enhanced accessibility: %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 complianceStatus := "✅ PASS" if result.ARIAViolations > 0 || result.LandmarkIssues > 0 { complianceStatus = "❌ VIOLATIONS FOUND" } summary := fmt.Sprintf("Enhanced Accessibility Analysis Results:\n\n"+ "Summary:\n"+ " Total Elements Analyzed: %d\n"+ " Elements with Issues: %d\n"+ " Compliance Status: %s\n"+ " ARIA Violations: %d\n"+ " Role Violations: %d\n"+ " Relationship Issues: %d\n"+ " Landmark Issues: %d\n\n", result.TotalElements, result.ElementsWithIssues, complianceStatus, result.ARIAViolations, result.RoleViolations, result.RelationshipIssues, result.LandmarkIssues) // Add elements with issues if result.ElementsWithIssues > 0 { summary += "Elements with Issues:\n" for i, elem := range result.Elements { if i >= 15 { // Limit to first 15 for readability summary += fmt.Sprintf("\n ... and %d more elements\n", len(result.Elements)-15) break } summary += fmt.Sprintf("\n %d. <%s> %s\n", i+1, elem.TagName, elem.Selector) summary += fmt.Sprintf(" Role: %s\n", elem.Role) if elem.AriaLabel != "" { summary += fmt.Sprintf(" ARIA Label: %s\n", elem.AriaLabel) } if elem.AriaLabelledBy != "" { summary += fmt.Sprintf(" ARIA Labelled By: %s\n", elem.AriaLabelledBy) } if elem.AriaDescribedBy != "" { summary += fmt.Sprintf(" ARIA Described By: %s\n", elem.AriaDescribedBy) } summary += fmt.Sprintf(" Interactive: %v\n", elem.IsInteractive) summary += fmt.Sprintf(" Has Accessible Name: %v\n", elem.HasAccessibleName) if elem.TabIndex != 0 { summary += fmt.Sprintf(" Tab Index: %d\n", elem.TabIndex) } if len(elem.Issues) > 0 { summary += " Issues:\n" for _, issue := range elem.Issues { summary += fmt.Sprintf(" - %s\n", issue) } } if len(elem.Recommendations) > 0 { summary += " Recommendations:\n" for _, rec := range elem.Recommendations { summary += fmt.Sprintf(" - %s\n", rec) } } } summary += "\n" } // Add recommendations if result.ARIAViolations > 0 || result.LandmarkIssues > 0 { summary += "⚠️ CRITICAL RECOMMENDATIONS:\n" if result.ARIAViolations > 0 { summary += " 1. Add accessible names to all interactive elements\n" summary += " 2. Remove aria-hidden from interactive elements\n" summary += " 3. Use valid tabindex values (0 or -1)\n" } if result.LandmarkIssues > 0 { summary += " 4. Add distinguishing labels to multiple landmarks of the same type\n" summary += " 5. Use aria-label or aria-labelledby for landmark identification\n" } summary += "\n" } // Add WCAG references summary += "WCAG Criteria:\n" summary += " - WCAG 1.3.1 (Info and Relationships - Level A): Information, structure, and relationships must be programmatically determined\n" summary += " - WCAG 4.1.2 (Name, Role, Value - Level A): All UI components must have accessible names and roles\n" summary += " - WCAG 2.4.6 (Headings and Labels - Level AA): Headings and labels must describe topic or purpose\n\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. Uses real Tab key simulation for accurate :focus-within and :focus-visible testing. Automatically detects accessibility plugins (Accessifix, UserWay, AccessiBe, AudioEye, EqualWeb) and provides confidence scoring for focus indicator findings.", 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, true, 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 }) // Register web_page_accessibility_report tool mcpServer.AddTool(mcp.Tool{ Name: "web_page_accessibility_report_cremotemcp", Description: "Perform comprehensive accessibility assessment of a page and return a summarized report with actionable findings. This tool combines multiple accessibility tests (axe-core, contrast, keyboard, forms) and returns only the critical findings in a token-efficient format. Keyboard testing uses real Tab key simulation for accurate :focus-visible detection and includes accessibility plugin detection with confidence scoring. Automatically injects axe-core if not already loaded.", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "tests": map[string]any{ "type": "array", "description": "Array of test types to run (e.g., ['wcag', 'contrast', 'keyboard', 'forms']). Defaults to 'all'", "items": map[string]any{ "type": "string", }, }, "standard": map[string]any{ "type": "string", "description": "WCAG standard to test against (default: WCAG21AA)", "default": "WCAG21AA", }, "include_screenshots": map[string]any{ "type": "boolean", "description": "Whether to capture screenshots of violations (default: false)", "default": false, }, "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) standard := getStringParam(params, "standard", "WCAG21AA") includeScreenshots := getBoolParam(params, "include_screenshots", false) timeout := getIntParam(params, "timeout", 30) // Parse tests array var tests []string if testsParam, ok := params["tests"].([]interface{}); ok { for _, t := range testsParam { if testStr, ok := t.(string); ok { tests = append(tests, testStr) } } } result, err := cremoteServer.client.GetPageAccessibilityReport(tab, tests, standard, includeScreenshots, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to get page accessibility report: %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) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(string(resultJSON)), }, IsError: false, }, nil }) // Register web_accessibility_full_audit tool - Master accessibility tool with navigation mcpServer.AddTool(mcp.Tool{ Name: "web_accessibility_full_audit_cremotemcp", Description: "Master accessibility tool: Navigate to URL, clear cache, and run comprehensive accessibility assessment in a single call. Combines navigation, cache clearing, and all accessibility tests (axe-core, contrast, keyboard, forms) with token-efficient reporting. Automatically injects axe-core if not already loaded.", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "url": map[string]any{ "type": "string", "description": "URL to navigate to and test (required)", }, "clear_cache": map[string]any{ "type": "boolean", "description": "Clear browser cache before navigation (default: true)", "default": true, }, "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, creates new tab if not specified)", }, "tests": map[string]any{ "type": "array", "description": "Array of test types to run (e.g., ['wcag', 'contrast', 'keyboard', 'forms']). Defaults to 'all'", "items": map[string]any{ "type": "string", }, }, "standard": map[string]any{ "type": "string", "description": "WCAG standard to test against (default: WCAG21AA)", "default": "WCAG21AA", }, "include_screenshots": map[string]any{ "type": "boolean", "description": "Whether to capture screenshots of violations (default: false)", "default": false, }, "timeout": map[string]any{ "type": "integer", "description": "Timeout in seconds for each operation (default: 30)", "default": 30, }, }, Required: []string{"url"}, }, }, 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") } url := getStringParam(params, "url", "") if url == "" { return nil, fmt.Errorf("url parameter is required") } clearCache := getBoolParam(params, "clear_cache", true) standard := getStringParam(params, "standard", "WCAG21AA") includeScreenshots := getBoolParam(params, "include_screenshots", false) timeout := getIntParam(params, "timeout", 30) // Parse tests array var tests []string if testsParam, ok := params["tests"].([]interface{}); ok { for _, t := range testsParam { if testStr, ok := t.(string); ok { tests = append(tests, testStr) } } } // Handle navigation with cache clearing tab, err := handleOptionalNavigation(cremoteServer, params, timeout) if err != nil { return nil, fmt.Errorf("failed to navigate: %w", err) } if tab == "" { return nil, fmt.Errorf("failed to create or identify tab") } // Run comprehensive accessibility assessment result, err := cremoteServer.client.GetPageAccessibilityReport(tab, tests, standard, includeScreenshots, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to get page accessibility report: %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) } // Build response message message := fmt.Sprintf("Accessibility audit completed for %s", url) if clearCache { message += " (cache cleared)" } message += "\n\n" + string(resultJSON) return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(message), }, IsError: false, }, nil }) // Register web_contrast_audit tool mcpServer.AddTool(mcp.Tool{ Name: "web_contrast_audit_cremotemcp", Description: "Perform smart contrast checking with prioritized failures and pattern detection. Returns only failures and common patterns, significantly reducing token usage compared to full contrast check.", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "priority_selectors": map[string]any{ "type": "array", "description": "Array of CSS selectors to prioritize (e.g., ['button', 'a', 'nav', 'footer'])", "items": map[string]any{ "type": "string", }, }, "threshold": map[string]any{ "type": "string", "description": "WCAG level to test against: 'AA' or 'AAA' (default: AA)", "default": "AA", }, "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) threshold := getStringParam(params, "threshold", "AA") timeout := getIntParam(params, "timeout", 10) // Parse priority selectors array var prioritySelectors []string if selectorsParam, ok := params["priority_selectors"].([]interface{}); ok { for _, s := range selectorsParam { if selectorStr, ok := s.(string); ok { prioritySelectors = append(prioritySelectors, selectorStr) } } } result, err := cremoteServer.client.GetContrastAudit(tab, prioritySelectors, threshold, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to get contrast audit: %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) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(string(resultJSON)), }, IsError: false, }, nil }) // Register web_keyboard_audit tool mcpServer.AddTool(mcp.Tool{ Name: "web_keyboard_audit_cremotemcp", Description: "Perform keyboard navigation assessment with actionable results. Uses real Tab key simulation for accurate :focus-within and :focus-visible testing. Automatically detects accessibility plugins (Accessifix, UserWay, AccessiBe, AudioEye, EqualWeb) and provides confidence scoring for focus indicator findings. Returns summary of issues rather than full element lists, reducing token usage by ~80%.", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "check_focus_indicators": map[string]any{ "type": "boolean", "description": "Check for visible focus indicators (default: true)", "default": true, }, "check_tab_order": map[string]any{ "type": "boolean", "description": "Check tab order (default: true)", "default": true, }, "check_keyboard_traps": map[string]any{ "type": "boolean", "description": "Check for keyboard traps (default: true)", "default": true, }, "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) checkFocusIndicators := getBoolParam(params, "check_focus_indicators", true) checkTabOrder := getBoolParam(params, "check_tab_order", true) checkKeyboardTraps := getBoolParam(params, "check_keyboard_traps", true) timeout := getIntParam(params, "timeout", 15) result, err := cremoteServer.client.GetKeyboardAudit(tab, checkFocusIndicators, checkTabOrder, checkKeyboardTraps, true, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to get keyboard audit: %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) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(string(resultJSON)), }, IsError: false, }, nil }) // Register web_form_accessibility_audit tool mcpServer.AddTool(mcp.Tool{ Name: "web_form_accessibility_audit_cremotemcp", Description: "Perform comprehensive form accessibility check with summarized results. Analyzes labels, ARIA attributes, keyboard accessibility, and contrast issues.", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]any{ "tab": map[string]any{ "type": "string", "description": "Tab ID (optional, uses current tab)", }, "form_selector": map[string]any{ "type": "string", "description": "CSS selector for specific form (optional, defaults to all forms)", }, "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) formSelector := getStringParam(params, "form_selector", "") timeout := getIntParam(params, "timeout", 10) result, err := cremoteServer.client.GetFormAccessibilityAudit(tab, formSelector, timeout) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(fmt.Sprintf("Failed to get form accessibility audit: %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) } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(string(resultJSON)), }, 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) } }