// SPDX-License-Identifier: EUPL-1.2 package agentic import ( "context" "time" core "dappco.re/go/core" "github.com/modelcontextprotocol/go-sdk/mcp" ) // input := agentic.PhaseGetInput{PlanSlug: "my-plan-abc123", PhaseOrder: 1} type PhaseGetInput struct { PlanSlug string `json:"plan_slug"` PhaseOrder int `json:"phase_order"` } // input := agentic.PhaseStatusInput{PlanSlug: "my-plan-abc123", PhaseOrder: 1, Status: "completed"} type PhaseStatusInput struct { PlanSlug string `json:"plan_slug"` PhaseOrder int `json:"phase_order"` Status string `json:"status"` Reason string `json:"reason,omitempty"` } // input := agentic.PhaseCheckpointInput{PlanSlug: "my-plan-abc123", PhaseOrder: 1, Note: "Build passes"} type PhaseCheckpointInput struct { PlanSlug string `json:"plan_slug"` PhaseOrder int `json:"phase_order"` Note string `json:"note"` Context map[string]any `json:"context,omitempty"` } // out := agentic.PhaseOutput{Success: true, Phase: agentic.Phase{Number: 1, Name: "Setup"}} type PhaseOutput struct { Success bool `json:"success"` Phase Phase `json:"phase"` } // phase := agentic.AgentPhase{Number: 1, Name: "Build", Status: "in_progress"} type AgentPhase = Phase // result := c.Action("phase.get").Run(ctx, core.NewOptions(core.Option{Key: "plan_slug", Value: "my-plan-abc123"})) func (s *PrepSubsystem) handlePhaseGet(ctx context.Context, options core.Options) core.Result { _, output, err := s.phaseGet(ctx, nil, PhaseGetInput{ PlanSlug: optionStringValue(options, "plan_slug", "plan", "slug"), PhaseOrder: optionIntValue(options, "phase_order", "phase"), }) if err != nil { return core.Result{Value: err, OK: false} } return core.Result{Value: output, OK: true} } // result := c.Action("phase.update_status").Run(ctx, core.NewOptions(core.Option{Key: "status", Value: "completed"})) func (s *PrepSubsystem) handlePhaseUpdateStatus(ctx context.Context, options core.Options) core.Result { _, output, err := s.phaseUpdateStatus(ctx, nil, PhaseStatusInput{ PlanSlug: optionStringValue(options, "plan_slug", "plan", "slug"), PhaseOrder: optionIntValue(options, "phase_order", "phase"), Status: optionStringValue(options, "status"), Reason: optionStringValue(options, "reason"), }) if err != nil { return core.Result{Value: err, OK: false} } return core.Result{Value: output, OK: true} } // result := c.Action("phase.add_checkpoint").Run(ctx, core.NewOptions(core.Option{Key: "note", Value: "Build passes"})) func (s *PrepSubsystem) handlePhaseAddCheckpoint(ctx context.Context, options core.Options) core.Result { _, output, err := s.phaseAddCheckpoint(ctx, nil, PhaseCheckpointInput{ PlanSlug: optionStringValue(options, "plan_slug", "plan", "slug"), PhaseOrder: optionIntValue(options, "phase_order", "phase"), Note: optionStringValue(options, "note"), Context: optionAnyMapValue(options, "context"), }) if err != nil { return core.Result{Value: err, OK: false} } return core.Result{Value: output, OK: true} } func (s *PrepSubsystem) registerPhaseTools(server *mcp.Server) { mcp.AddTool(server, &mcp.Tool{ Name: "phase_get", Description: "Get a phase by plan slug and phase order.", }, s.phaseGet) mcp.AddTool(server, &mcp.Tool{ Name: "phase_update_status", Description: "Update a phase status by plan slug and phase order.", }, s.phaseUpdateStatus) mcp.AddTool(server, &mcp.Tool{ Name: "phase_add_checkpoint", Description: "Append a checkpoint note to a phase.", }, s.phaseAddCheckpoint) } func (s *PrepSubsystem) phaseGet(_ context.Context, _ *mcp.CallToolRequest, input PhaseGetInput) (*mcp.CallToolResult, PhaseOutput, error) { plan, phaseIndex, err := planPhaseByOrder(PlansRoot(), input.PlanSlug, input.PhaseOrder) if err != nil { return nil, PhaseOutput{}, err } return nil, PhaseOutput{ Success: true, Phase: plan.Phases[phaseIndex], }, nil } func (s *PrepSubsystem) phaseUpdateStatus(_ context.Context, _ *mcp.CallToolRequest, input PhaseStatusInput) (*mcp.CallToolResult, PhaseOutput, error) { if !validPhaseStatus(input.Status) { return nil, PhaseOutput{}, core.E("phaseUpdateStatus", core.Concat("invalid status: ", input.Status), nil) } plan, phaseIndex, err := planPhaseByOrder(PlansRoot(), input.PlanSlug, input.PhaseOrder) if err != nil { return nil, PhaseOutput{}, err } plan.Phases[phaseIndex].Status = input.Status if reason := core.Trim(input.Reason); reason != "" { plan.Phases[phaseIndex].Notes = appendPlanNote(plan.Phases[phaseIndex].Notes, reason) } plan.UpdatedAt = time.Now() if result := writePlanResult(PlansRoot(), plan); !result.OK { err, _ := result.Value.(error) if err == nil { err = core.E("phaseUpdateStatus", "failed to write plan", nil) } return nil, PhaseOutput{}, err } return nil, PhaseOutput{ Success: true, Phase: plan.Phases[phaseIndex], }, nil } func (s *PrepSubsystem) phaseAddCheckpoint(_ context.Context, _ *mcp.CallToolRequest, input PhaseCheckpointInput) (*mcp.CallToolResult, PhaseOutput, error) { if core.Trim(input.Note) == "" { return nil, PhaseOutput{}, core.E("phaseAddCheckpoint", "note is required", nil) } plan, phaseIndex, err := planPhaseByOrder(PlansRoot(), input.PlanSlug, input.PhaseOrder) if err != nil { return nil, PhaseOutput{}, err } plan.Phases[phaseIndex].Checkpoints = append(plan.Phases[phaseIndex].Checkpoints, PhaseCheckpoint{ Note: input.Note, Context: input.Context, CreatedAt: time.Now().Format(time.RFC3339), }) plan.UpdatedAt = time.Now() if result := writePlanResult(PlansRoot(), plan); !result.OK { err, _ := result.Value.(error) if err == nil { err = core.E("phaseAddCheckpoint", "failed to write plan", nil) } return nil, PhaseOutput{}, err } return nil, PhaseOutput{ Success: true, Phase: plan.Phases[phaseIndex], }, nil } func planPhaseByOrder(dir, planSlug string, phaseOrder int) (*Plan, int, error) { if core.Trim(planSlug) == "" { return nil, 0, core.E("planPhaseByOrder", "plan_slug is required", nil) } if phaseOrder <= 0 { return nil, 0, core.E("planPhaseByOrder", "phase_order is required", nil) } plan, err := readPlan(dir, planSlug) if err != nil { return nil, 0, err } for index := range plan.Phases { if plan.Phases[index].Number == phaseOrder { return plan, index, nil } } return nil, 0, core.E("planPhaseByOrder", core.Concat("phase not found: ", core.Sprint(phaseOrder)), nil) } func appendPlanNote(existing, note string) string { if existing == "" { return note } return core.Concat(existing, "\n", note) } func validPhaseStatus(status string) bool { switch status { case "pending", "in_progress", "completed", "blocked", "skipped": return true } return false }