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