This commit is contained in:
Josh at WLTechBlog
2025-09-30 14:11:27 -05:00
parent 86d1db55cd
commit a3c782eb24
11 changed files with 4904 additions and 6 deletions

View File

@@ -2440,6 +2440,869 @@ func main() {
}, 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
})
// Start the server
log.Printf("Cremote MCP server ready")
if err := server.ServeStdio(mcpServer); err != nil {