package mcp import ( "context" "fmt" "strings" "github.com/mark3labs/mcp-go/mcp" deploycli "code.t-juice.club/torjus/homelab-deploy/internal/cli" "code.t-juice.club/torjus/homelab-deploy/internal/messages" ) // BuildTool creates the build tool definition. func BuildTool() mcp.Tool { return mcp.NewTool( "build", mcp.WithDescription("Trigger a Nix build on the build server"), mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name (must match builder config)"), ), mcp.WithString("target", mcp.Description("Target hostname, or omit to build all hosts"), ), mcp.WithBoolean("all", mcp.Description("Build all hosts in the repository (default if no target specified)"), ), mcp.WithString("branch", mcp.Description("Git branch to build (uses repo default if not specified)"), ), ) } // HandleBuild handles the build tool. func (h *ToolHandler) HandleBuild(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { repo, err := request.RequireString("repo") if err != nil { return mcp.NewToolResultError("repo is required"), nil } target := request.GetString("target", "") all := request.GetBool("all", false) branch := request.GetString("branch", "") // Default to "all" if no target specified if target == "" { if !all { all = true } target = "all" } if all && target != "all" { return mcp.NewToolResultError("cannot specify both target and all"), nil } cfg := deploycli.BuildConfig{ NATSUrl: h.cfg.NATSUrl, NKeyFile: h.cfg.NKeyFile, Repo: repo, Target: target, Branch: branch, Timeout: h.cfg.Timeout, } var output strings.Builder branchStr := branch if branchStr == "" { branchStr = "(default)" } output.WriteString(fmt.Sprintf("Building %s target=%s branch=%s\n\n", repo, target, branchStr)) result, err := deploycli.Build(ctx, cfg, func(resp *messages.BuildResponse) { switch resp.Status { case messages.BuildStatusStarted: output.WriteString(fmt.Sprintf("Started: %s\n", resp.Message)) case messages.BuildStatusProgress: successStr := "..." if resp.HostSuccess != nil { if *resp.HostSuccess { successStr = "success" } else { successStr = "failed" } } output.WriteString(fmt.Sprintf("[%d/%d] %s: %s\n", resp.HostsCompleted, resp.HostsTotal, resp.Host, successStr)) case messages.BuildStatusCompleted, messages.BuildStatusFailed: output.WriteString(fmt.Sprintf("\n%s\n", resp.Message)) case messages.BuildStatusRejected: output.WriteString(fmt.Sprintf("Rejected: %s\n", resp.Message)) } }) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("build failed: %v", err)), nil } if result.FinalResponse != nil { output.WriteString(fmt.Sprintf("\nBuild complete: %d succeeded, %d failed (%.1fs)\n", result.FinalResponse.Succeeded, result.FinalResponse.Failed, result.FinalResponse.TotalDurationSeconds)) } if !result.AllSucceeded() { output.WriteString("WARNING: Some builds failed\n") } return mcp.NewToolResultText(output.String()), nil }