2177 lines
64 KiB
Go
2177 lines
64 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",
|
|
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,
|
|
},
|
|
"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)
|
|
|
|
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.TakeScreenshot(tab, output, fullPage, timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to take screenshot: %w", err)
|
|
}
|
|
|
|
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",
|
|
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)",
|
|
},
|
|
"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)
|
|
timeout := getIntParam(params, "timeout", 5)
|
|
|
|
if command == "" {
|
|
return nil, fmt.Errorf("command parameter is required")
|
|
}
|
|
|
|
// Execute console command using existing EvalJS functionality
|
|
result, err := cremoteServer.client.EvalJS(tab, command, timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to execute console command: %w", err)
|
|
}
|
|
|
|
return &mcp.CallToolResult{
|
|
Content: []mcp.Content{
|
|
mcp.NewTextContent(fmt.Sprintf("Console command executed successfully.\nCommand: %s\nResult: %s", command, result)),
|
|
},
|
|
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 or with limited depth",
|
|
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,
|
|
},
|
|
"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)
|
|
|
|
// Parse depth parameter
|
|
var depth *int
|
|
if depthParam := getIntParam(params, "depth", -1); depthParam >= 0 {
|
|
depth = &depthParam
|
|
}
|
|
|
|
result, err := cremoteServer.client.GetAccessibilityTree(tab, depth, timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get 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: "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
|
|
})
|
|
|
|
// Start the server
|
|
log.Printf("Cremote MCP server ready")
|
|
if err := server.ServeStdio(mcpServer); err != nil {
|
|
log.Fatalf("Server error: %v", err)
|
|
}
|
|
}
|