5237 lines
155 KiB
Go
5237 lines
155 KiB
Go
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)
|
||
}
|
||
}
|