Files

286 lines
9.7 KiB
Go

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
}