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" ) // 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 web_navigate tool mcpServer.AddTool(mcp.Tool{ Name: "web_navigate", 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", 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"}, }, "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, 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, timeout) message = fmt.Sprintf("Filled element %s with value", selector) case "submit": err = cremoteServer.client.SubmitForm(tab, selector, timeout, 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, timeout) message = fmt.Sprintf("Uploaded file %s to 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", 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", 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", 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", 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) 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", 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", 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", 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", 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 }) // Start the server log.Printf("Cremote MCP server ready") if err := server.ServeStdio(mcpServer); err != nil { log.Fatalf("Server error: %v", err) } }