Skip to content

Commit 6c04094

Browse files
feat: CP-1068 Create MCP tool to bump timestamp of a project
1 parent ceafc3b commit 6c04094

3 files changed

Lines changed: 124 additions & 0 deletions

File tree

cmd/state-mcp/internal/registry/registry.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func New() *Registry {
3838
r.RegisterTool(DownloadSourceFileTool())
3939
r.RegisterTool(GetIngredientDetailsTool())
4040
r.RegisterTool(CreateIngredientRevisionTool())
41+
r.RegisterTool(RebuildProjectTool())
4142

4243
r.RegisterPrompt(ProjectPrompt())
4344
r.RegisterPrompt(IngredientPrompt())

cmd/state-mcp/internal/registry/tools.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/ActiveState/cli/internal/runners/mcp/downloadsource"
1414
"github.com/ActiveState/cli/internal/runners/mcp/ingredientdetails"
1515
"github.com/ActiveState/cli/internal/runners/mcp/projecterrors"
16+
"github.com/ActiveState/cli/internal/runners/mcp/rebuildproject"
1617
"github.com/ActiveState/cli/pkg/project"
1718
"github.com/mark3labs/mcp-go/mcp"
1819
)
@@ -297,3 +298,37 @@ func CreateIngredientRevisionTool() Tool {
297298
},
298299
}
299300
}
301+
302+
func RebuildProjectTool() Tool {
303+
return Tool{
304+
Category: CategoryDebug,
305+
Tool: mcp.NewTool(
306+
"rebuild_project",
307+
mcp.WithDescription("Triggers a project rebuild after all errors have been addressed"),
308+
mcp.WithString("project", mcp.Description("Project namespace in format 'owner/project'"), mcp.Required()),
309+
),
310+
Handler: func(ctx context.Context, p *primer.Values, mcpRequest mcp.CallToolRequest) (*mcp.CallToolResult, error) {
311+
namespace, err := mcpRequest.RequireString("project")
312+
if err != nil {
313+
return mcp.NewToolResultError(fmt.Sprintf("a project in the format 'owner/project' is required: %s", errs.JoinMessage(err))), nil
314+
}
315+
ns, err := project.ParseNamespace(namespace)
316+
if err != nil {
317+
return mcp.NewToolResultError(fmt.Sprintf("error parsing project namespace: %s", errs.JoinMessage(err))), nil
318+
}
319+
320+
params := rebuildproject.NewParams(ns)
321+
runner := rebuildproject.New(p)
322+
323+
err = runner.Run(params)
324+
325+
if err != nil {
326+
return mcp.NewToolResultError(fmt.Sprintf("error rebuilding project: %s", errs.JoinMessage(err))), nil
327+
}
328+
329+
return mcp.NewToolResultText(
330+
strings.Join(p.Output().History().Print, "\n"),
331+
), nil
332+
},
333+
}
334+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package rebuildproject
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/ActiveState/cli/internal/captain"
8+
"github.com/ActiveState/cli/internal/errs"
9+
"github.com/ActiveState/cli/internal/output"
10+
"github.com/ActiveState/cli/internal/primer"
11+
"github.com/ActiveState/cli/internal/runbits/commits_runbit"
12+
"github.com/ActiveState/cli/pkg/platform/authentication"
13+
"github.com/ActiveState/cli/pkg/platform/model"
14+
bpModel "github.com/ActiveState/cli/pkg/platform/model/buildplanner"
15+
"github.com/ActiveState/cli/pkg/project"
16+
)
17+
18+
type ProjectErrorsRunner struct {
19+
auth *authentication.Auth
20+
output output.Outputer
21+
svcModel *model.SvcModel
22+
}
23+
24+
func New(p *primer.Values) *ProjectErrorsRunner {
25+
return &ProjectErrorsRunner{
26+
auth: p.Auth(),
27+
output: p.Output(),
28+
svcModel: p.SvcModel(),
29+
}
30+
}
31+
32+
type Params struct {
33+
project *project.Namespaced
34+
}
35+
36+
func NewParams(project *project.Namespaced) *Params {
37+
return &Params{
38+
project: project,
39+
}
40+
}
41+
42+
func (runner *ProjectErrorsRunner) Run(params *Params) error {
43+
branch, err := model.DefaultBranchForProjectName(params.project.Owner, params.project.Project)
44+
if err != nil {
45+
return fmt.Errorf("error fetching default branch: %w", err)
46+
}
47+
48+
// Collect "before" buildplan
49+
bpm := bpModel.NewBuildPlannerModel(runner.auth, runner.svcModel)
50+
localCommit, err := bpm.FetchCommit(*branch.CommitID, params.project.Owner, params.project.Project, nil)
51+
if err != nil {
52+
return errs.Wrap(err, "Failed to fetch build result")
53+
}
54+
55+
// Collect "after" buildplan
56+
bumpedBS, err := localCommit.BuildScript().Clone()
57+
if err != nil {
58+
return errs.Wrap(err, "Failed to clone build script")
59+
}
60+
61+
now := captain.TimeValue{}
62+
now.Set("now")
63+
ts, err := commits_runbit.ExpandTime(&now, runner.auth)
64+
if err != nil {
65+
return errs.Wrap(err, "Failed to fetch latest timestamp")
66+
}
67+
bumpedBS.SetAtTime(ts, true)
68+
69+
// Since our platform is commit based we need to create a commit for the "after" buildplan, even though we may not
70+
// end up using it it the user doesn't confirm the upgrade.
71+
bumpedCommit, err := bpm.StageCommitAndPoll(bpModel.StageCommitParams{
72+
Owner: params.project.Owner,
73+
Project: params.project.Project,
74+
ParentCommit: branch.CommitID.String(),
75+
Script: bumpedBS,
76+
})
77+
if err != nil {
78+
return errs.Wrap(err, "Failed to stage bumped commit")
79+
}
80+
81+
jsonBytes, err := json.Marshal(bumpedCommit)
82+
if err != nil {
83+
return fmt.Errorf("error marshaling results: %w", err)
84+
}
85+
runner.output.Print(string(jsonBytes))
86+
87+
return nil
88+
}

0 commit comments

Comments
 (0)