286 lines
9.7 KiB
Go
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
|
|
}
|