package tools import ( "context" "fmt" "forge-tools-ssh/internal/ssh" "github.com/modelcontextprotocol/go-sdk/mcp" ) func registerDockerTools(s *mcp.Server) { // docker_ps s.AddTool(&mcp.Tool{ Name: "docker_ps", Description: "List Docker containers", InputSchema: schema(map[string]any{ "all": boolProps("Show all containers (default: only running)", false), "target": strProps("Connection alias (default: primary)", false), }, nil), }, dockerPsHandler) // docker_logs s.AddTool(&mcp.Tool{ Name: "docker_logs", Description: "Get logs from a Docker container", InputSchema: schema(map[string]any{ "container": strProps("Container name or ID", true), "lines": intProps("Number of lines (default: 50)", false), "target": strProps("Connection alias (default: primary)", false), }, []string{"container"}), }, dockerLogsHandler) // docker_op s.AddTool(&mcp.Tool{ Name: "docker_op", Description: "Start, stop, or restart a Docker container", InputSchema: schema(map[string]any{ "container": strProps("Container name or ID", true), "action": strProps("Action to perform on the container", true, "start", "stop", "restart"), "target": strProps("Connection alias (default: primary)", false), }, []string{"container", "action"}), }, dockerOpHandler) // docker_ip s.AddTool(&mcp.Tool{ Name: "docker_ip", Description: "Get IP address(es) of a Docker container", InputSchema: schema(map[string]any{ "container": strProps("Container name", true), "target": strProps("Connection alias (default: primary)", false), }, []string{"container"}), }, dockerIPHandler) // docker_find_by_ip s.AddTool(&mcp.Tool{ Name: "docker_find_by_ip", Description: "Find which Docker container has a specific IP", InputSchema: schema(map[string]any{ "ip": strProps("IP address to search", true), "target": strProps("Connection alias (default: primary)", false), }, []string{"ip"}), }, dockerFindByIPHandler) // docker_networks s.AddTool(&mcp.Tool{ Name: "docker_networks", Description: "List all Docker networks and their containers", InputSchema: schema(map[string]any{ "target": strProps("Connection alias (default: primary)", false), }, nil), }, dockerNetworksHandler) // docker_cp_from s.AddTool(&mcp.Tool{ Name: "docker_cp_from", Description: "Copy file from Docker container to host", InputSchema: schema(map[string]any{ "container": strProps("Container name", true), "container_path": strProps("Path inside container", true), "host_path": strProps("Destination path on host", true), "target": strProps("Connection alias (default: primary)", false), }, []string{"container", "container_path", "host_path"}), }, dockerCpFromHandler) // docker_cp_to s.AddTool(&mcp.Tool{ Name: "docker_cp_to", Description: "Copy file from host to Docker container", InputSchema: schema(map[string]any{ "host_path": strProps("Source path on host", true), "container": strProps("Container name", true), "container_path": strProps("Destination path inside container", true), "target": strProps("Connection alias (default: primary)", false), }, []string{"host_path", "container", "container_path"}), }, dockerCpToHandler) } func dockerPsHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { m := manager() if m == nil { return errorResult("no SSH manager initialized"), nil } args := requestArgs(req) all := getBool(args, "all", false) target := getString(args, "target", "primary") if err := checkDockerAvailable(ctx, m, target); err != nil { return errorResult(err.Error()), nil } flag := "" if all { flag = "-a" } cmd := fmt.Sprintf("docker ps %s --format 'table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}'", flag) output, err := m.Execute(ctx, cmd, target) if err != nil { return errorResult(err.Error()), nil } return textResult(output), nil } func dockerLogsHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { m := manager() if m == nil { return errorResult("no SSH manager initialized"), nil } args := requestArgs(req) container, _ := requireString(args, "container") lines := getInt(args, "lines", 50) target := getString(args, "target", "primary") if err := checkDockerAvailable(ctx, m, target); err != nil { return errorResult(err.Error()), nil } cmd := fmt.Sprintf("docker logs --tail %d %s 2>&1", lines, shellQuote(container)) output, err := m.Execute(ctx, cmd, target) if err != nil { return errorResult(err.Error()), nil } return textResult(output), nil } func dockerOpHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { m := manager() if m == nil { return errorResult("no SSH manager initialized"), nil } args := requestArgs(req) container, _ := requireString(args, "container") action, _ := requireString(args, "action") target := getString(args, "target", "primary") if action != "start" && action != "stop" && action != "restart" { return errorResult("Invalid action. Use: start, stop, restart"), nil } if err := checkDockerAvailable(ctx, m, target); err != nil { return errorResult(err.Error()), nil } cmd := fmt.Sprintf("docker %s %s 2>&1", shellQuote(action), shellQuote(container)) output, err := m.Execute(ctx, cmd, target) if err != nil { return errorResult(err.Error()), nil } return textResult(fmt.Sprintf("%s: %s\n%s", action, container, output)), nil } func dockerIPHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { m := manager() if m == nil { return errorResult("no SSH manager initialized"), nil } args := requestArgs(req) container, _ := requireString(args, "container") target := getString(args, "target", "primary") if err := checkDockerAvailable(ctx, m, target); err != nil { return errorResult(err.Error()), nil } cmd := fmt.Sprintf("docker inspect --format '{{range $net, $conf := .NetworkSettings.Networks}}{{$net}}:{{$conf.IPAddress}}|{{end}}' %s 2>/dev/null", shellQuote(container)) output, err := m.Execute(ctx, cmd, target) if err != nil { return errorResult(err.Error()), nil } return textResult(fmt.Sprintf("Container: %s\nNetworks: %s", container, output)), nil } func dockerFindByIPHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { m := manager() if m == nil { return errorResult("no SSH manager initialized"), nil } args := requestArgs(req) ip, _ := requireString(args, "ip") target := getString(args, "target", "primary") if err := checkDockerAvailable(ctx, m, target); err != nil { return errorResult(err.Error()), nil } cmd := fmt.Sprintf(`docker ps -q | xargs -I {} docker inspect --format '{{.Name}}|{{range $net, $conf := .NetworkSettings.Networks}}{{$net}}:{{$conf.IPAddress}},{{end}}' {} 2>/dev/null | grep %s`, shellQuote(ip)) output, err := m.Execute(ctx, cmd, target) if err != nil { return textResult(fmt.Sprintf("No container found with IP: %s", ip)), nil } return textResult(output), nil } func dockerNetworksHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { m := manager() if m == nil { return errorResult("no SSH manager initialized"), nil } target := getString(requestArgs(req), "target", "primary") if err := checkDockerAvailable(ctx, m, target); err != nil { return errorResult(err.Error()), nil } cmd := "docker network ls --format '{{.Name}} ({{.Driver}})'" output, err := m.Execute(ctx, cmd, target) if err != nil { return errorResult(err.Error()), nil } return textResult(output), nil } func dockerCpFromHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { m := manager() if m == nil { return errorResult("no SSH manager initialized"), nil } args := requestArgs(req) container, _ := requireString(args, "container") containerPath, _ := requireString(args, "container_path") hostPath, _ := requireString(args, "host_path") target := getString(args, "target", "primary") if err := checkDockerAvailable(ctx, m, target); err != nil { return errorResult(err.Error()), nil } cmd := fmt.Sprintf("docker cp %s:%s %s 2>&1", shellQuote(container), shellQuote(containerPath), shellQuote(hostPath)) output, err := m.Execute(ctx, cmd, target) if err != nil { return errorResult(err.Error()), nil } if containsString(output, "Error") || containsString(output, "No such") { return errorResult(output), nil } return textResult(fmt.Sprintf("Copied %s:%s to %s", container, containerPath, hostPath)), nil } func dockerCpToHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { m := manager() if m == nil { return errorResult("no SSH manager initialized"), nil } args := requestArgs(req) hostPath, _ := requireString(args, "host_path") container, _ := requireString(args, "container") containerPath, _ := requireString(args, "container_path") target := getString(args, "target", "primary") if err := checkDockerAvailable(ctx, m, target); err != nil { return errorResult(err.Error()), nil } cmd := fmt.Sprintf("docker cp %s %s:%s 2>&1", shellQuote(hostPath), shellQuote(container), shellQuote(containerPath)) output, err := m.Execute(ctx, cmd, target) if err != nil { return errorResult(err.Error()), nil } if containsString(output, "Error") || containsString(output, "No such") { return errorResult(output), nil } return textResult(fmt.Sprintf("Copied %s to %s:%s", hostPath, container, containerPath)), nil } func checkDockerAvailable(ctx context.Context, m *ssh.Manager, target string) error { available, err := m.IsDockerAvailable(ctx, target) if err != nil { return err } if !available { return fmt.Errorf("docker command not found on target") } return nil }