Files
cremote/mcp/main.go
Josh at WLTechBlog ccd8c77a3e remove sensory tools
2025-10-07 11:47:47 -05:00

5237 lines
155 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 <track kind=\"captions\"> elements to all videos\n"
summary += " 2. Ensure caption files (.vtt, .srt) are accessible\n"
summary += " 3. Test captions display correctly in video player\n"
summary += " 4. Consider adding audio descriptions for visual content\n\n"
}
// Add embedded player notes
if len(result.EmbeddedPlayers) > 0 {
summary += "Embedded Players:\n"
for i, player := range result.EmbeddedPlayers {
summary += fmt.Sprintf(" %d. %s: %s\n", i+1, player.Type, player.Src)
}
summary += "\nNote: YouTube and Vimeo players should have captions enabled in their settings.\n"
summary += "Check video settings on the platform to ensure captions are available.\n\n"
}
// Add transcript links
if len(result.TranscriptLinks) > 0 {
summary += "Transcript Links Found:\n"
for i, link := range result.TranscriptLinks {
summary += fmt.Sprintf(" %d. %s\n", i+1, link)
}
summary += "\n"
}
summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON))
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(summary),
},
IsError: false,
}, nil
})
// Register web_hover_focus_test tool
mcpServer.AddTool(mcp.Tool{
Name: "web_hover_focus_test_cremotemcp",
Description: "Test WCAG 1.4.13 compliance for content on hover or focus: checks dismissibility, hoverability, and persistence",
InputSchema: mcp.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"tab": map[string]any{
"type": "string",
"description": "Tab ID (optional, uses current tab)",
},
"timeout": map[string]any{
"type": "integer",
"description": "Timeout in seconds (default: 10)",
"default": 10,
},
},
Required: []string{},
},
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
params, ok := request.Params.Arguments.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid arguments format")
}
tab := getStringParam(params, "tab", cremoteServer.currentTab)
timeout := getIntParam(params, "timeout", 10)
result, err := cremoteServer.client.TestHoverFocusContent(tab, timeout)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(fmt.Sprintf("Failed to test hover/focus content: %v", err)),
},
IsError: true,
}, nil
}
// Format results as JSON
resultJSON, err := json.MarshalIndent(result, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal results: %w", err)
}
// Create summary
complianceStatus := "✅ PASS"
if result.ElementsWithIssues > 0 {
complianceStatus = "⚠️ ISSUES FOUND"
}
summary := fmt.Sprintf("Hover/Focus Content Test Results (WCAG 1.4.13):\n\n"+
"Summary:\n"+
" Total Elements Tested: %d\n"+
" Elements with Issues: %d\n"+
" Elements Passed: %d\n"+
" Compliance Status: %s\n\n",
result.TotalElements,
result.ElementsWithIssues,
result.PassedElements,
complianceStatus)
// Add issues if any
if len(result.Issues) > 0 {
summary += "Issues Found:\n"
for i, issue := range result.Issues {
summary += fmt.Sprintf("\n %d. %s\n", i+1, issue.Selector)
summary += fmt.Sprintf(" Type: %s\n", issue.Type)
summary += fmt.Sprintf(" Severity: %s\n", issue.Severity)
summary += fmt.Sprintf(" Description: %s\n", issue.Description)
summary += fmt.Sprintf(" WCAG: %s\n", issue.WCAG)
}
summary += "\n"
}
// Add element details
if result.TotalElements > 0 {
summary += "Tested Elements:\n"
for i, elem := range result.TestedElements {
if len(elem.Violations) > 0 {
summary += fmt.Sprintf("\n %d. %s (%s)\n", i+1, elem.Selector, elem.Type)
summary += fmt.Sprintf(" Dismissible: %v\n", elem.Dismissible)
summary += fmt.Sprintf(" Hoverable: %v\n", elem.Hoverable)
summary += fmt.Sprintf(" Persistent: %v\n", elem.Persistent)
summary += fmt.Sprintf(" Passes WCAG: %v\n", elem.PassesWCAG)
if len(elem.Violations) > 0 {
summary += " Violations:\n"
for _, violation := range elem.Violations {
summary += fmt.Sprintf(" - %s\n", violation)
}
}
}
}
summary += "\n"
}
// Add recommendations if issues found
if result.ElementsWithIssues > 0 {
summary += "⚠️ RECOMMENDATIONS:\n"
summary += " 1. Replace native title attributes with custom tooltips that can be dismissed with Escape\n"
summary += " 2. Ensure hover/focus content can be dismissed without moving pointer/focus\n"
summary += " 3. Allow pointer to move over new content without it disappearing\n"
summary += " 4. Keep content visible until dismissed or no longer relevant\n"
summary += " 5. Test with keyboard-only navigation (Tab, Escape keys)\n\n"
}
summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON))
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(summary),
},
IsError: false,
}, nil
})
// Register web_text_in_images tool
mcpServer.AddTool(mcp.Tool{
Name: "web_text_in_images_cremotemcp",
Description: "Detect text in images using Tesseract OCR and flag accessibility violations (WCAG 1.4.5, 1.4.9)",
InputSchema: mcp.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"tab": map[string]any{
"type": "string",
"description": "Tab ID (optional, uses current tab)",
},
"timeout": map[string]any{
"type": "integer",
"description": "Timeout in seconds (default: 30 for OCR processing)",
"default": 30,
},
},
Required: []string{},
},
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
params, ok := request.Params.Arguments.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid arguments format")
}
tab := getStringParam(params, "tab", cremoteServer.currentTab)
timeout := getIntParam(params, "timeout", 30)
result, err := cremoteServer.client.DetectTextInImages(tab, timeout)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(fmt.Sprintf("Failed to detect text in images: %v", err)),
},
IsError: true,
}, nil
}
// Format results as JSON
resultJSON, err := json.MarshalIndent(result, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal results: %w", err)
}
// Create summary
complianceStatus := "✅ PASS"
if result.Violations > 0 {
complianceStatus = "❌ CRITICAL VIOLATIONS"
} else if result.Warnings > 0 {
complianceStatus = "⚠️ WARNINGS"
}
summary := fmt.Sprintf("Text-in-Images Detection Results:\n\n"+
"Summary:\n"+
" Total Images Analyzed: %d\n"+
" Images with Text: %d\n"+
" Images without Text: %d\n"+
" Compliance Status: %s\n"+
" Critical Violations: %d\n"+
" Warnings: %d\n\n",
result.TotalImages,
result.ImagesWithText,
result.ImagesWithoutText,
complianceStatus,
result.Violations,
result.Warnings)
// Add violations if any
if result.Violations > 0 || result.Warnings > 0 {
summary += "Images with Issues:\n"
for i, img := range result.Images {
if img.IsViolation {
summary += fmt.Sprintf("\n %d. %s\n", i+1, img.Src)
summary += fmt.Sprintf(" Has Alt: %v\n", img.HasAlt)
if img.Alt != "" {
summary += fmt.Sprintf(" Alt Text: \"%s\"\n", img.Alt)
}
summary += fmt.Sprintf(" Detected Text: \"%s\"\n", img.DetectedText)
summary += fmt.Sprintf(" Text Length: %d characters\n", img.TextLength)
summary += fmt.Sprintf(" Confidence: %.1f%%\n", img.Confidence*100)
summary += fmt.Sprintf(" Violation Type: %s\n", img.ViolationType)
summary += fmt.Sprintf(" Recommendation: %s\n", img.Recommendation)
}
}
summary += "\n"
}
// Add recommendations if violations
if result.Violations > 0 {
summary += "⚠️ CRITICAL RECOMMENDATIONS:\n"
summary += " 1. Add alt text to all images containing text\n"
summary += " 2. Ensure alt text includes all text visible in the image\n"
summary += " 3. Consider using real text instead of text-in-images where possible\n"
summary += " 4. If text-in-images is necessary, provide equivalent text alternatives\n\n"
}
// Add WCAG references
summary += "WCAG Criteria:\n"
summary += " - WCAG 1.4.5 (Images of Text - Level AA): Use real text instead of images of text\n"
summary += " - WCAG 1.4.9 (Images of Text - No Exception - Level AAA): No images of text except logos\n"
summary += " - WCAG 1.1.1 (Non-text Content - Level A): All images must have text alternatives\n\n"
summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON))
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(summary),
},
IsError: false,
}, nil
})
// Register web_cross_page_consistency tool
mcpServer.AddTool(mcp.Tool{
Name: "web_cross_page_consistency_cremotemcp",
Description: "Check consistency of navigation, headers, footers, and landmarks across multiple pages (WCAG 3.2.3, 3.2.4)",
InputSchema: mcp.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"tab": map[string]any{
"type": "string",
"description": "Tab ID (optional, uses current tab)",
},
"urls": map[string]any{
"type": "array",
"description": "Array of URLs to check for consistency",
"items": map[string]any{
"type": "string",
},
},
"timeout": map[string]any{
"type": "integer",
"description": "Timeout in seconds per page (default: 10)",
"default": 10,
},
},
Required: []string{"urls"},
},
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
params, ok := request.Params.Arguments.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid arguments format")
}
tab := getStringParam(params, "tab", cremoteServer.currentTab)
timeout := getIntParam(params, "timeout", 10)
// Parse URLs array
urlsParam, ok := params["urls"].([]any)
if !ok || len(urlsParam) == 0 {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent("Error: urls parameter is required and must be a non-empty array"),
},
IsError: true,
}, nil
}
urls := make([]string, 0, len(urlsParam))
for _, u := range urlsParam {
if urlStr, ok := u.(string); ok {
urls = append(urls, urlStr)
}
}
if len(urls) == 0 {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent("Error: no valid URLs provided"),
},
IsError: true,
}, nil
}
result, err := cremoteServer.client.CheckCrossPageConsistency(tab, urls, timeout)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(fmt.Sprintf("Failed to check cross-page consistency: %v", err)),
},
IsError: true,
}, nil
}
// Format results as JSON
resultJSON, err := json.MarshalIndent(result, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal results: %w", err)
}
// Create summary
complianceStatus := "✅ PASS"
if result.ConsistencyIssues > 0 {
complianceStatus = "❌ INCONSISTENCIES FOUND"
}
summary := fmt.Sprintf("Cross-Page Consistency Check Results:\n\n"+
"Summary:\n"+
" Pages Analyzed: %d\n"+
" Compliance Status: %s\n"+
" Total Issues: %d\n"+
" Navigation Issues: %d\n"+
" Structure Issues: %d\n"+
" Common Navigation Links: %d\n\n",
result.PagesAnalyzed,
complianceStatus,
result.ConsistencyIssues,
result.NavigationIssues,
result.StructureIssues,
len(result.CommonNavigation))
// Add common navigation
if len(result.CommonNavigation) > 0 {
summary += "Common Navigation Links (present on all pages):\n"
for i, link := range result.CommonNavigation {
summary += fmt.Sprintf(" %d. %s\n", i+1, link)
}
summary += "\n"
}
// Add inconsistent pages
if len(result.InconsistentPages) > 0 {
summary += "Pages with Inconsistencies:\n"
for i, url := range result.InconsistentPages {
summary += fmt.Sprintf(" %d. %s\n", i+1, url)
}
summary += "\n"
}
// Add page details
if result.ConsistencyIssues > 0 {
summary += "Page Details:\n"
for i, page := range result.Pages {
if len(page.Issues) > 0 {
summary += fmt.Sprintf("\n %d. %s\n", i+1, page.URL)
summary += fmt.Sprintf(" Title: %s\n", page.Title)
summary += fmt.Sprintf(" Has Header: %v\n", page.HasHeader)
summary += fmt.Sprintf(" Has Footer: %v\n", page.HasFooter)
summary += fmt.Sprintf(" Has Navigation: %v\n", page.HasNavigation)
summary += fmt.Sprintf(" Main Landmarks: %d\n", page.MainLandmarks)
summary += " Issues:\n"
for _, issue := range page.Issues {
summary += fmt.Sprintf(" - %s\n", issue)
}
}
}
summary += "\n"
}
// Add recommendations
if result.ConsistencyIssues > 0 {
summary += "⚠️ RECOMMENDATIONS:\n"
if result.NavigationIssues > 0 {
summary += " 1. Ensure consistent navigation across all pages\n"
summary += " 2. Use the same navigation structure and labels on every page\n"
}
if result.StructureIssues > 0 {
summary += " 3. Add proper landmark elements (header, footer, main, nav)\n"
summary += " 4. Ensure exactly one main landmark per page\n"
}
summary += "\n"
}
// Add WCAG references
summary += "WCAG Criteria:\n"
summary += " - WCAG 3.2.3 (Consistent Navigation - Level AA): Navigation repeated on multiple pages must be in the same relative order\n"
summary += " - WCAG 3.2.4 (Consistent Identification - Level AA): Components with the same functionality must be identified consistently\n"
summary += " - WCAG 1.3.1 (Info and Relationships - Level A): Proper use of landmarks for page structure\n\n"
summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON))
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(summary),
},
IsError: false,
}, nil
})
// Register web_animation_flash tool
mcpServer.AddTool(mcp.Tool{
Name: "web_animation_flash_cremotemcp",
Description: "Detect animations and flashing content that may trigger seizures or cause accessibility issues (WCAG 2.3.1, 2.2.2)",
InputSchema: mcp.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"tab": map[string]any{
"type": "string",
"description": "Tab ID (optional, uses current tab)",
},
"timeout": map[string]any{
"type": "integer",
"description": "Timeout in seconds (default: 10)",
"default": 10,
},
},
Required: []string{},
},
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
params, ok := request.Params.Arguments.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid arguments format")
}
tab := getStringParam(params, "tab", cremoteServer.currentTab)
timeout := getIntParam(params, "timeout", 10)
result, err := cremoteServer.client.DetectAnimationFlash(tab, timeout)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(fmt.Sprintf("Failed to detect animation/flash: %v", err)),
},
IsError: true,
}, nil
}
// Format results as JSON
resultJSON, err := json.MarshalIndent(result, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal results: %w", err)
}
// Create summary
complianceStatus := "✅ PASS"
if result.Violations > 0 {
complianceStatus = "❌ VIOLATIONS FOUND"
} else if result.Warnings > 0 {
complianceStatus = "⚠️ WARNINGS"
}
summary := fmt.Sprintf("Animation/Flash Detection Results:\n\n"+
"Summary:\n"+
" Total Animations: %d\n"+
" Flashing Content: %d\n"+
" Rapid Animations: %d\n"+
" Autoplay Animations: %d\n"+
" Compliance Status: %s\n"+
" Violations: %d\n"+
" Warnings: %d\n\n",
result.TotalAnimations,
result.FlashingContent,
result.RapidAnimations,
result.AutoplayAnimations,
complianceStatus,
result.Violations,
result.Warnings)
// Add violations/warnings
if result.Violations > 0 || result.Warnings > 0 {
summary += "Elements with Issues:\n"
for i, elem := range result.Elements {
if elem.IsViolation || elem.ViolationType != "" {
if i >= 10 { // Limit to first 10 for readability
summary += fmt.Sprintf("\n ... and %d more elements\n", len(result.Elements)-10)
break
}
summary += fmt.Sprintf("\n %d. <%s> %s\n", i+1, elem.TagName, elem.Selector)
summary += fmt.Sprintf(" Animation Type: %s\n", elem.AnimationType)
summary += fmt.Sprintf(" Duration: %.2fs\n", elem.Duration)
if elem.FlashRate > 0 {
summary += fmt.Sprintf(" Flash Rate: %.1f flashes/second\n", elem.FlashRate)
}
summary += fmt.Sprintf(" Autoplay: %v\n", elem.IsAutoplay)
summary += fmt.Sprintf(" Has Controls: %v\n", elem.HasControls)
summary += fmt.Sprintf(" Can Pause: %v\n", elem.CanPause)
if elem.IsViolation {
summary += fmt.Sprintf(" ❌ Violation: %s\n", elem.ViolationType)
} else {
summary += fmt.Sprintf(" ⚠️ Warning: %s\n", elem.ViolationType)
}
summary += fmt.Sprintf(" Recommendation: %s\n", elem.Recommendation)
}
}
summary += "\n"
}
// Add recommendations
if result.Violations > 0 {
summary += "⚠️ CRITICAL RECOMMENDATIONS:\n"
if result.FlashingContent > 0 {
summary += " 1. Reduce flash rate to 3 or fewer flashes per second\n"
summary += " 2. Provide mechanism to disable flashing content\n"
}
if result.AutoplayAnimations > 0 {
summary += " 3. Provide pause/stop controls for autoplay animations longer than 5 seconds\n"
summary += " 4. Allow users to disable animations\n"
}
summary += "\n"
}
// Add WCAG references
summary += "WCAG Criteria:\n"
summary += " - WCAG 2.3.1 (Three Flashes or Below Threshold - Level A): Content must not flash more than 3 times per second\n"
summary += " - WCAG 2.2.2 (Pause, Stop, Hide - Level A): Moving, blinking, or auto-updating content must have pause/stop controls\n"
summary += " - WCAG 2.3.2 (Three Flashes - Level AAA): No content flashes more than 3 times per second\n\n"
summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON))
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(summary),
},
IsError: false,
}, nil
})
// Register web_enhanced_accessibility tool
mcpServer.AddTool(mcp.Tool{
Name: "web_enhanced_accessibility_cremotemcp",
Description: "Perform enhanced accessibility tree analysis with ARIA validation, role verification, and relationship checking (WCAG 1.3.1, 4.1.2)",
InputSchema: mcp.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"tab": map[string]any{
"type": "string",
"description": "Tab ID (optional, uses current tab)",
},
"timeout": map[string]any{
"type": "integer",
"description": "Timeout in seconds (default: 10)",
"default": 10,
},
},
Required: []string{},
},
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
params, ok := request.Params.Arguments.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid arguments format")
}
tab := getStringParam(params, "tab", cremoteServer.currentTab)
timeout := getIntParam(params, "timeout", 10)
result, err := cremoteServer.client.AnalyzeEnhancedAccessibility(tab, timeout)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(fmt.Sprintf("Failed to analyze enhanced accessibility: %v", err)),
},
IsError: true,
}, nil
}
// Format results as JSON
resultJSON, err := json.MarshalIndent(result, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal results: %w", err)
}
// Create summary
complianceStatus := "✅ PASS"
if result.ARIAViolations > 0 || result.LandmarkIssues > 0 {
complianceStatus = "❌ VIOLATIONS FOUND"
}
summary := fmt.Sprintf("Enhanced Accessibility Analysis Results:\n\n"+
"Summary:\n"+
" Total Elements Analyzed: %d\n"+
" Elements with Issues: %d\n"+
" Compliance Status: %s\n"+
" ARIA Violations: %d\n"+
" Role Violations: %d\n"+
" Relationship Issues: %d\n"+
" Landmark Issues: %d\n\n",
result.TotalElements,
result.ElementsWithIssues,
complianceStatus,
result.ARIAViolations,
result.RoleViolations,
result.RelationshipIssues,
result.LandmarkIssues)
// Add elements with issues
if result.ElementsWithIssues > 0 {
summary += "Elements with Issues:\n"
for i, elem := range result.Elements {
if i >= 15 { // Limit to first 15 for readability
summary += fmt.Sprintf("\n ... and %d more elements\n", len(result.Elements)-15)
break
}
summary += fmt.Sprintf("\n %d. <%s> %s\n", i+1, elem.TagName, elem.Selector)
summary += fmt.Sprintf(" Role: %s\n", elem.Role)
if elem.AriaLabel != "" {
summary += fmt.Sprintf(" ARIA Label: %s\n", elem.AriaLabel)
}
if elem.AriaLabelledBy != "" {
summary += fmt.Sprintf(" ARIA Labelled By: %s\n", elem.AriaLabelledBy)
}
if elem.AriaDescribedBy != "" {
summary += fmt.Sprintf(" ARIA Described By: %s\n", elem.AriaDescribedBy)
}
summary += fmt.Sprintf(" Interactive: %v\n", elem.IsInteractive)
summary += fmt.Sprintf(" Has Accessible Name: %v\n", elem.HasAccessibleName)
if elem.TabIndex != 0 {
summary += fmt.Sprintf(" Tab Index: %d\n", elem.TabIndex)
}
if len(elem.Issues) > 0 {
summary += " Issues:\n"
for _, issue := range elem.Issues {
summary += fmt.Sprintf(" - %s\n", issue)
}
}
if len(elem.Recommendations) > 0 {
summary += " Recommendations:\n"
for _, rec := range elem.Recommendations {
summary += fmt.Sprintf(" - %s\n", rec)
}
}
}
summary += "\n"
}
// Add recommendations
if result.ARIAViolations > 0 || result.LandmarkIssues > 0 {
summary += "⚠️ CRITICAL RECOMMENDATIONS:\n"
if result.ARIAViolations > 0 {
summary += " 1. Add accessible names to all interactive elements\n"
summary += " 2. Remove aria-hidden from interactive elements\n"
summary += " 3. Use valid tabindex values (0 or -1)\n"
}
if result.LandmarkIssues > 0 {
summary += " 4. Add distinguishing labels to multiple landmarks of the same type\n"
summary += " 5. Use aria-label or aria-labelledby for landmark identification\n"
}
summary += "\n"
}
// Add WCAG references
summary += "WCAG Criteria:\n"
summary += " - WCAG 1.3.1 (Info and Relationships - Level A): Information, structure, and relationships must be programmatically determined\n"
summary += " - WCAG 4.1.2 (Name, Role, Value - Level A): All UI components must have accessible names and roles\n"
summary += " - WCAG 2.4.6 (Headings and Labels - Level AA): Headings and labels must describe topic or purpose\n\n"
summary += fmt.Sprintf("Full Results:\n%s", string(resultJSON))
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(summary),
},
IsError: false,
}, nil
})
// Register web_keyboard_test tool
mcpServer.AddTool(mcp.Tool{
Name: "web_keyboard_test_cremotemcp",
Description: "Test keyboard navigation and accessibility including tab order, focus indicators, and keyboard traps",
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)
}
}