diff --git a/mcp.go b/mcp.go index a6bc86a..d0b4eac 100644 --- a/mcp.go +++ b/mcp.go @@ -10,23 +10,21 @@ import ( ) type setTaskSuccessArgs struct { - ProjectID string `json:"project_id"` - TaskID string `json:"task_id"` + ProjectID int `json:"project_id"` + TaskID int `json:"task_id"` Result string `json:"result"` Notes string `json:"notes,omitempty"` } type setTaskFailureArgs struct { - ProjectID string `json:"project_id"` - TaskID string `json:"task_id"` + ProjectID int `json:"project_id"` + TaskID int `json:"task_id"` Error string `json:"error"` Notes string `json:"notes,omitempty"` } type taskResponse struct { - TaskID string `json:"task_id"` - Message string `json:"message"` - NextTask *Task `json:"next_task,omitempty"` + NextTask *Task `json:"next_task"` } type errorResponse struct { @@ -64,7 +62,7 @@ func (s *Service) RegisterMCPTools(mcpServer *server.MCPServer) error { mcp.NewTool( "set_task_success", mcp.WithDescription("Mark a task as successfully completed"), - mcp.WithString("project_id", + mcp.WithNumber("project_id", mcp.Required(), mcp.Description("The project ID"), ), @@ -87,7 +85,7 @@ func (s *Service) RegisterMCPTools(mcpServer *server.MCPServer) error { mcp.NewTool( "set_task_failure", mcp.WithDescription("Mark a task as failed"), - mcp.WithString("project_id", + mcp.WithNumber("project_id", mcp.Required(), mcp.Description("The project ID"), ), @@ -121,8 +119,6 @@ func handleSetTaskSuccess(s *Service, ctx context.Context, args setTaskSuccessAr } response := &taskResponse{ - TaskID: args.TaskID, - Message: fmt.Sprintf("Task %s marked as successful", args.TaskID), NextTask: nextTask, } @@ -141,8 +137,6 @@ func handleSetTaskFailure(s *Service, ctx context.Context, args setTaskFailureAr } response := &taskResponse{ - TaskID: args.TaskID, - Message: fmt.Sprintf("Task %s marked as failed", args.TaskID), NextTask: nextTask, } diff --git a/taskcp.go b/taskcp.go index 486f204..21f9287 100644 --- a/taskcp.go +++ b/taskcp.go @@ -5,19 +5,17 @@ import ( "fmt" "iter" "strings" - - "github.com/google/uuid" ) type Service struct { - Projects map[string]*Project + projects []*Project mcpService string } type Project struct { - ID string - Tasks map[string]*Task - nextTaskID string + ID int + Tasks []*Task + nextTaskID int mcpService string } @@ -31,7 +29,7 @@ const ( ) type Task struct { - ID string `json:"id"` + ID int `json:"id"` State TaskState `json:"-"` Title string `json:"title"` Instructions string `json:"instructions"` @@ -40,7 +38,7 @@ type Task struct { Error string `json:"-"` Notes string `json:"-"` - NextTaskID string `json:"-"` + NextTaskID int `json:"-"` project *Project completionCallback func(project *Project, task *Task) error @@ -59,50 +57,48 @@ type ProjectSummary struct { func New(mcpService string) *Service { return &Service{ - Projects: map[string]*Project{}, mcpService: mcpService, } } func (s *Service) AddProject() *Project { project := &Project{ - ID: uuid.New().String(), - Tasks: map[string]*Task{}, - nextTaskID: "", + ID: len(s.projects), + Tasks: []*Task{}, + nextTaskID: -1, mcpService: s.mcpService, } - s.Projects[project.ID] = project + s.projects = append(s.projects, project) return project } -func (s *Service) GetProject(id string) (*Project, error) { - project, ok := s.Projects[id] - if !ok { - return nil, fmt.Errorf("project not found") +func (s *Service) GetProject(id int) (*Project, error) { + if id < 0 || id >= len(s.projects) { + return nil, fmt.Errorf("invalid project id: %d", id) } - return project, nil + return s.projects[id], nil } -func (p *Project) InsertTaskBefore(beforeID string, title string, instructions string, completionCallback func(project *Project, task *Task) error) *Task { - task := p.newTask(title, instructions, completionCallback, beforeID) +func (p *Project) InsertTaskBefore(beforeID int, title string, instructions string, completionCallback func(project *Project, task *Task) error) *Task { + newTask := p.newTask(title, instructions, completionCallback, beforeID) - if p.nextTaskID == "" && beforeID == "" { - p.nextTaskID = task.ID + if p.nextTaskID == -1 && beforeID == -1 { + p.nextTaskID = newTask.ID } else { for t := range p.tasks() { if t.NextTaskID == beforeID { - t.NextTaskID = task.ID + t.NextTaskID = newTask.ID break } } } - return task + return newTask } func (p *Project) GetNextTask() *Task { - if p.nextTaskID == "" { + if p.nextTaskID == -1 { return nil } @@ -111,7 +107,7 @@ func (p *Project) GetNextTask() *Task { return task } -func (p *Project) SetTaskSuccess(id string, result string, notes string) (*Task, error) { +func (p *Project) SetTaskSuccess(id int, result string, notes string) (*Task, error) { task := p.Tasks[id] task.State = TaskStateSuccess task.Result = result @@ -129,7 +125,7 @@ func (p *Project) SetTaskSuccess(id string, result string, notes string) (*Task, return p.GetNextTask(), nil } -func (p *Project) SetTaskFailure(id string, error string, notes string) (*Task, error) { +func (p *Project) SetTaskFailure(id int, error string, notes string) (*Task, error) { task := p.Tasks[id] task.State = TaskStateFailure task.Error = error @@ -147,9 +143,9 @@ func (p *Project) SetTaskFailure(id string, error string, notes string) (*Task, return p.GetNextTask(), nil } -func (p *Project) newTask(title string, instructions string, completionCallback func(project *Project, task *Task) error, nextTaskID string) *Task { +func (p *Project) newTask(title string, instructions string, completionCallback func(project *Project, task *Task) error, nextTaskID int) *Task { task := &Task{ - ID: uuid.New().String(), + ID: len(p.Tasks), State: TaskStatePending, NextTaskID: nextTaskID, Title: title, @@ -162,13 +158,13 @@ func (p *Project) newTask(title string, instructions string, completionCallback task.Instructions = strings.ReplaceAll(task.Instructions, "{SUCCESS_PROMPT}", task.SuccessPrompt()) task.Instructions = strings.ReplaceAll(task.Instructions, "{FAILURE_PROMPT}", task.FailurePrompt()) - p.Tasks[task.ID] = task + p.Tasks = append(p.Tasks, task) return task } func (p *Project) tasks() iter.Seq[*Task] { return func(yield func(*Task) bool) { - for tid := p.nextTaskID; tid != ""; tid = p.Tasks[tid].NextTaskID { + for tid := p.nextTaskID; tid != -1; tid = p.Tasks[tid].NextTaskID { t := p.Tasks[tid] if !yield(t) { return @@ -194,13 +190,13 @@ func (p *Project) Summary() ProjectSummary { func (t *Task) SuccessPrompt() string { return fmt.Sprintf(`To mark this task as successful, use the MCP tool: -%s.set_task_success(project_id="%s", task_id="%s", result="", notes="")`, +%s.set_task_success(project_id=%d, task_id=%d, result="", notes="")`, t.project.mcpService, t.project.ID, t.ID) } func (t *Task) FailurePrompt() string { return fmt.Sprintf(`To mark this task as failed, use the MCP tool: -%s.set_task_failure(project_id="%s", task_id="%s", error="", notes="")`, +%s.set_task_failure(project_id=%d, task_id=%d, error="", notes="")`, t.project.mcpService, t.project.ID, t.ID) } diff --git a/taskcp_test.go b/taskcp_test.go index d3ef8ff..dd0b6ef 100644 --- a/taskcp_test.go +++ b/taskcp_test.go @@ -12,28 +12,28 @@ func TestTaskPrompts(t *testing.T) { service := taskcp.New("my_service") project := service.AddProject() - task := project.InsertTaskBefore("", "Write unit tests", "", func(project *taskcp.Project, task *taskcp.Task) error { return nil }) + task := project.InsertTaskBefore(-1, "Write unit tests", "", func(project *taskcp.Project, task *taskcp.Task) error { return nil }) successPrompt := task.SuccessPrompt() require.Contains(t, successPrompt, "my_service.set_task_success") - require.Contains(t, successPrompt, `project_id="`+project.ID+`"`) - require.Contains(t, successPrompt, `task_id="`+task.ID+`"`) + require.Contains(t, successPrompt, fmt.Sprintf(`project_id=%d`, project.ID)) + require.Contains(t, successPrompt, fmt.Sprintf(`task_id=%d`, task.ID)) failurePrompt := task.FailurePrompt() require.Contains(t, failurePrompt, "my_service.set_task_failure") - require.Contains(t, failurePrompt, `project_id="`+project.ID+`"`) - require.Contains(t, failurePrompt, `task_id="`+task.ID+`"`) + require.Contains(t, failurePrompt, fmt.Sprintf(`project_id=%d`, project.ID)) + require.Contains(t, failurePrompt, fmt.Sprintf(`task_id=%d`, task.ID)) } func TestPlaceholderExpansion(t *testing.T) { service := taskcp.New("my_service") project := service.AddProject() - task1 := project.InsertTaskBefore("", "Please complete this task.", "{SUCCESS_PROMPT}", func(project *taskcp.Project, task *taskcp.Task) error { return nil }) + task1 := project.InsertTaskBefore(-1, "Please complete this task.", "{SUCCESS_PROMPT}", func(project *taskcp.Project, task *taskcp.Task) error { return nil }) require.Contains(t, task1.Instructions, "my_service.set_task_success") require.NotContains(t, task1.Instructions, "{SUCCESS_PROMPT}") - task2 := project.InsertTaskBefore("", "Try this risky operation.", "{FAILURE_PROMPT}", func(project *taskcp.Project, task *taskcp.Task) error { return nil }) + task2 := project.InsertTaskBefore(-1, "Try this risky operation.", "{FAILURE_PROMPT}", func(project *taskcp.Project, task *taskcp.Task) error { return nil }) require.Contains(t, task2.Instructions, "my_service.set_task_failure") require.NotContains(t, task2.Instructions, "{FAILURE_PROMPT}") } @@ -42,14 +42,14 @@ func TestTaskFlow(t *testing.T) { service := taskcp.New("test_service") project := service.AddProject() - var completed []string + var completed []int - task1 := project.InsertTaskBefore("", "First task", "", func(project *taskcp.Project, task *taskcp.Task) error { + task1 := project.InsertTaskBefore(-1, "First task", "", func(project *taskcp.Project, task *taskcp.Task) error { completed = append(completed, task.ID) return nil }) - task2 := project.InsertTaskBefore("", "Second task", "", func(project *taskcp.Project, task *taskcp.Task) error { + task2 := project.InsertTaskBefore(-1, "Second task", "", func(project *taskcp.Project, task *taskcp.Task) error { completed = append(completed, task.ID) return nil }) @@ -68,7 +68,7 @@ func TestTaskFlow(t *testing.T) { require.NoError(t, err) require.Nil(t, next2) - require.Equal(t, []string{task1.ID, task2.ID}, completed) + require.Equal(t, []int{task1.ID, task2.ID}, completed) require.Equal(t, taskcp.TaskStateSuccess, project.Tasks[task1.ID].State) require.Equal(t, taskcp.TaskStateFailure, project.Tasks[task2.ID].State) } @@ -79,7 +79,7 @@ func TestCallbackError(t *testing.T) { expectedErr := fmt.Errorf("callback error") - task := project.InsertTaskBefore("", "Task with error callback", "", func(project *taskcp.Project, task *taskcp.Task) error { + task := project.InsertTaskBefore(-1, "Task with error callback", "", func(project *taskcp.Project, task *taskcp.Task) error { return expectedErr }) @@ -87,12 +87,10 @@ func TestCallbackError(t *testing.T) { require.NotNil(t, current) require.Equal(t, task.ID, current.ID) - // Test error propagation on success _, err := project.SetTaskSuccess(current.ID, "Result", "") require.Error(t, err) require.Equal(t, expectedErr, err) - // Test error propagation on failure _, err = project.SetTaskFailure(current.ID, "Task failed", "") require.Error(t, err) require.Equal(t, expectedErr, err)