Skip to content

Commit 0d2d53c

Browse files
Merge pull request #3713 from ActiveState/samueld/CP-1068
feat: CP-1068 Create MCP tool to bump timestamp of a project
2 parents a1c8e35 + 7e4b619 commit 0d2d53c

File tree

3 files changed

+127
-0
lines changed

3 files changed

+127
-0
lines changed

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: 36 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,38 @@ 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()
321+
params.Namespace = ns
322+
323+
runner := rebuildproject.New(p)
324+
err = runner.Run(params)
325+
326+
if err != nil {
327+
return mcp.NewToolResultError(fmt.Sprintf("error rebuilding project: %s", errs.JoinMessage(err))), nil
328+
}
329+
330+
return mcp.NewToolResultText(
331+
strings.Join(p.Output().History().Print, "\n"),
332+
), nil
333+
},
334+
}
335+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package rebuildproject
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/ActiveState/cli/internal/errs"
7+
"github.com/ActiveState/cli/internal/output"
8+
"github.com/ActiveState/cli/internal/primer"
9+
"github.com/ActiveState/cli/pkg/platform/api/buildplanner/types"
10+
"github.com/ActiveState/cli/pkg/platform/authentication"
11+
"github.com/ActiveState/cli/pkg/platform/model"
12+
"github.com/ActiveState/cli/pkg/platform/model/buildplanner"
13+
bpModel "github.com/ActiveState/cli/pkg/platform/model/buildplanner"
14+
"github.com/ActiveState/cli/pkg/project"
15+
)
16+
17+
type RebuildProjectRunner struct {
18+
auth *authentication.Auth
19+
output output.Outputer
20+
svcModel *model.SvcModel
21+
}
22+
23+
func New(p *primer.Values) *RebuildProjectRunner {
24+
return &RebuildProjectRunner{
25+
auth: p.Auth(),
26+
output: p.Output(),
27+
svcModel: p.SvcModel(),
28+
}
29+
}
30+
31+
type Params struct {
32+
Namespace *project.Namespaced
33+
}
34+
35+
func NewParams() *Params {
36+
return &Params{}
37+
}
38+
39+
func (runner *RebuildProjectRunner) Run(params *Params) error {
40+
branch, err := model.DefaultBranchForProjectName(params.Namespace.Owner, params.Namespace.Project)
41+
if err != nil {
42+
return fmt.Errorf("error fetching default branch: %w", err)
43+
}
44+
45+
// Collect "before" buildscript
46+
bpm := bpModel.NewBuildPlannerModel(runner.auth, runner.svcModel)
47+
localCommit, err := bpm.FetchCommitNoPoll(*branch.CommitID, params.Namespace.Owner, params.Namespace.Project, nil)
48+
if err != nil {
49+
return errs.Wrap(err, "Failed to fetch build result")
50+
}
51+
52+
// Collect "after" buildscript
53+
bumpedBS, err := localCommit.BuildScript().Clone()
54+
if err != nil {
55+
return errs.Wrap(err, "Failed to clone build script")
56+
}
57+
58+
latest, err := model.FetchLatestRevisionTimeStamp(runner.auth)
59+
if err != nil {
60+
return errs.Wrap(err, "Failed to fetch latest timestamp")
61+
}
62+
bumpedBS.SetAtTime(latest, true)
63+
64+
// Since our platform is commit based we need to create a commit for the "after" buildscript
65+
bumpedCommit, err := bpm.StageCommitAndPoll(bpModel.StageCommitParams{
66+
Owner: params.Namespace.Owner,
67+
Project: params.Namespace.Project,
68+
ParentCommit: branch.CommitID.String(),
69+
Script: bumpedBS,
70+
})
71+
if err != nil {
72+
return errs.Wrap(err, "Failed to stage bumped commit")
73+
}
74+
75+
// Now, merge the new commit using the branch name to fast-forward
76+
_, err = bpm.MergeCommit(&buildplanner.MergeCommitParams{
77+
Owner: params.Namespace.Owner,
78+
Project: params.Namespace.Project,
79+
TargetRef: branch.Label,
80+
OtherRef: bumpedCommit.CommitID.String(),
81+
Strategy: types.MergeCommitStrategyFastForward,
82+
})
83+
if err != nil {
84+
return fmt.Errorf("error merging commit: %w", err)
85+
}
86+
87+
runner.output.Print("Project is now rebuilding with commit ID " + bumpedCommit.CommitID.String())
88+
89+
return nil
90+
}

0 commit comments

Comments
 (0)