Add MCP integration for SetTaskSuccess and SetTaskFailure

This commit is contained in:
Ian Gulliver
2025-07-04 22:52:18 -07:00
parent 3dadea457a
commit f0c7ee76ef
6 changed files with 230 additions and 16 deletions

41
example_mcp_test.go Normal file
View File

@@ -0,0 +1,41 @@
package taskcp_test
import (
"fmt"
"github.com/gopatchy/taskcp"
"github.com/mark3labs/mcp-go/server"
)
func ExampleRegisterMCPTools() {
service := taskcp.New()
project := service.AddProject()
fmt.Printf("Created project: %s\n", project.ID)
task1 := project.InsertTaskBefore("", "Compile the code", func(task *taskcp.Task) {
fmt.Printf("Task %s completed with state: %s\n", task.ID, task.State)
})
task2 := project.InsertTaskBefore("", "Run tests", func(task *taskcp.Task) {
fmt.Printf("Task %s completed with state: %s\n", task.ID, task.State)
})
task1.NextTaskID = task2.ID
project.NextTaskID = task1.ID
mcpServer := server.NewMCPServer(
"TaskCP Server",
"1.0.0",
server.WithToolCapabilities(true),
)
err := taskcp.RegisterMCPTools(mcpServer, service)
if err != nil {
fmt.Printf("Failed to register tools: %v\n", err)
return
}
fmt.Println("MCP tools registered successfully")
}

3
go.mod
View File

@@ -4,11 +4,14 @@ go 1.24.4
require (
github.com/google/uuid v1.6.0
github.com/mark3labs/mcp-go v0.32.0
github.com/stretchr/testify v1.10.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

16
go.sum
View File

@@ -1,11 +1,27 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8=
github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

129
mcp.go Normal file
View File

@@ -0,0 +1,129 @@
package taskcp
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func RegisterMCPTools(s *server.MCPServer, service *Service) error {
s.AddTool(
mcp.NewTool(
"SetTaskSuccess",
mcp.WithDescription("Mark a task as successfully completed"),
mcp.WithString("projectId",
mcp.Required(),
mcp.Description("The project ID"),
),
mcp.WithString("taskId",
mcp.Required(),
mcp.Description("The task ID to mark as successful"),
),
mcp.WithString("result",
mcp.Required(),
mcp.Description("The result of the task execution"),
),
mcp.WithString("notes",
mcp.Description("Additional notes about the task completion"),
),
),
handleSetTaskSuccess(service),
)
s.AddTool(
mcp.NewTool(
"SetTaskFailure",
mcp.WithDescription("Mark a task as failed"),
mcp.WithString("projectId",
mcp.Required(),
mcp.Description("The project ID"),
),
mcp.WithString("taskId",
mcp.Required(),
mcp.Description("The task ID to mark as failed"),
),
mcp.WithString("error",
mcp.Required(),
mcp.Description("The error message describing why the task failed"),
),
mcp.WithString("notes",
mcp.Description("Additional notes about the task failure"),
),
),
handleSetTaskFailure(service),
)
return nil
}
func handleSetTaskSuccess(service *Service) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
projectId, err := request.RequireString("projectId")
if err != nil {
return nil, fmt.Errorf("failed to get projectId: %w", err)
}
taskId, err := request.RequireString("taskId")
if err != nil {
return nil, fmt.Errorf("failed to get taskId: %w", err)
}
result, err := request.RequireString("result")
if err != nil {
return nil, fmt.Errorf("failed to get result: %w", err)
}
notes := request.GetString("notes", "")
project, err := service.GetProject(projectId)
if err != nil {
return nil, fmt.Errorf("failed to get project: %w", err)
}
nextTask := project.SetTaskSuccess(taskId, result, notes)
message := fmt.Sprintf("Task %s marked as successful", taskId)
if nextTask != nil {
message += fmt.Sprintf("\nNext task: %s (ID: %s)", nextTask.Instructions, nextTask.ID)
}
return mcp.NewToolResultText(message), nil
}
}
func handleSetTaskFailure(service *Service) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
projectId, err := request.RequireString("projectId")
if err != nil {
return nil, fmt.Errorf("failed to get projectId: %w", err)
}
taskId, err := request.RequireString("taskId")
if err != nil {
return nil, fmt.Errorf("failed to get taskId: %w", err)
}
errorMsg, err := request.RequireString("error")
if err != nil {
return nil, fmt.Errorf("failed to get error: %w", err)
}
notes := request.GetString("notes", "")
project, err := service.GetProject(projectId)
if err != nil {
return nil, fmt.Errorf("failed to get project: %w", err)
}
nextTask := project.SetTaskFailure(taskId, errorMsg, notes)
message := fmt.Sprintf("Task %s marked as failed", taskId)
if nextTask != nil {
message += fmt.Sprintf("\nNext task: %s (ID: %s)", nextTask.Instructions, nextTask.ID)
}
return mcp.NewToolResultText(message), nil
}
}

19
mcp_test.go Normal file
View File

@@ -0,0 +1,19 @@
package taskcp
import (
"testing"
"github.com/mark3labs/mcp-go/server"
)
func TestRegisterMCPTools(t *testing.T) {
service := New()
s := server.NewMCPServer("Test Server", "1.0.0")
err := RegisterMCPTools(s, service)
if err != nil {
t.Fatalf("Failed to register MCP tools: %v", err)
}
}

View File

@@ -30,7 +30,7 @@ type Task struct {
ID string
State TaskState
NextTaskID string
ChangeCallback func(task *Task)
CompletionCallback func(task *Task)
// Written by creator
Instructions string
@@ -62,11 +62,12 @@ func (s *Service) GetProject(id string) (*Project, error) {
if !ok {
return nil, fmt.Errorf("project not found")
}
return project, nil
}
func (p *Project) InsertTaskBefore(id string, instructions string, changeCallback func(task *Task)) *Task {
task := p.newTask(instructions, changeCallback, id)
func (p *Project) InsertTaskBefore(id string, instructions string, completionCallback func(task *Task)) *Task {
task := p.newTask(instructions, completionCallback, id)
for t := range p.tasks() {
if t.NextTaskID == id {
@@ -83,7 +84,9 @@ func (p *Project) GetNextTask() *Task {
return nil
}
return p.Tasks[p.NextTaskID]
task := p.Tasks[p.NextTaskID]
task.State = TaskStateRunning
return task
}
func (p *Project) SetTaskSuccess(id string, result string, notes string) *Task {
@@ -91,27 +94,30 @@ func (p *Project) SetTaskSuccess(id string, result string, notes string) *Task {
task.State = TaskStateSuccess
task.Result = result
task.Notes = notes
task.ChangeCallback(task)
task.CompletionCallback(task)
p.NextTaskID = task.NextTaskID
return p.GetNextTask()
}
func (p *Project) SetTaskFailure(id string, error string, notes string) {
func (p *Project) SetTaskFailure(id string, error string, notes string) *Task {
task := p.Tasks[id]
task.State = TaskStateFailure
task.Error = error
task.Notes = notes
task.ChangeCallback(task)
task.CompletionCallback(task)
p.NextTaskID = task.NextTaskID
return p.GetNextTask()
}
func (p *Project) newTask(instructions string, changeCallback func(task *Task), nextTaskID string) *Task {
func (p *Project) newTask(instructions string, completionCallback func(task *Task), nextTaskID string) *Task {
task := &Task{
ID: uuid.New().String(),
State: TaskStatePending,
NextTaskID: nextTaskID,
Instructions: instructions,
ChangeCallback: changeCallback,
CompletionCallback: completionCallback,
}
p.Tasks[task.ID] = task
return task