Skip to content

Commit 2c040a9

Browse files
committed
feat: add support for Markdown notes in plans and tasks with CRUD operations
Signed-off-by: jbrinkman <joe.brinkman@improving.com>
1 parent df85de2 commit 2c040a9

File tree

2 files changed

+243
-0
lines changed

2 files changed

+243
-0
lines changed

‎internal/mcp/notes_tools.go‎

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package mcp
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/mark3labs/mcp-go/mcp"
9+
)
10+
11+
// registerUpdatePlanNotesTool registers a tool to update notes for a plan
12+
func (s *MCPGoServer) registerUpdatePlanNotesTool() {
13+
tool := mcp.NewTool("update_plan_notes",
14+
mcp.WithDescription("Update the notes for a specific plan"),
15+
mcp.WithString("id",
16+
mcp.Required(),
17+
mcp.Description("Plan ID"),
18+
),
19+
mcp.WithString("notes",
20+
mcp.Required(),
21+
mcp.Description("Markdown-formatted notes content"),
22+
),
23+
)
24+
25+
s.server.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
26+
id, err := request.RequireString("id")
27+
if err != nil {
28+
return mcp.NewToolResultError(err.Error()), nil
29+
}
30+
31+
notes, err := request.RequireString("notes")
32+
if err != nil {
33+
return mcp.NewToolResultError(err.Error()), nil
34+
}
35+
36+
// Update the notes
37+
err = s.planRepo.UpdateNotes(ctx, id, notes)
38+
if err != nil {
39+
return mcp.NewToolResultError(fmt.Sprintf("Failed to update plan notes: %v", err)), nil
40+
}
41+
42+
// Get the updated plan
43+
plan, err := s.planRepo.Get(ctx, id)
44+
if err != nil {
45+
return mcp.NewToolResultError(fmt.Sprintf("Failed to get updated plan: %v", err)), nil
46+
}
47+
48+
planJson, err := json.Marshal(plan)
49+
if err != nil {
50+
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal plan: %v", err)), nil
51+
}
52+
return mcp.NewToolResultText(string(planJson)), nil
53+
})
54+
}
55+
56+
// registerGetPlanNotesTool registers a tool to get notes for a plan
57+
func (s *MCPGoServer) registerGetPlanNotesTool() {
58+
tool := mcp.NewTool("get_plan_notes",
59+
mcp.WithDescription("Retrieve the notes for a specific plan"),
60+
mcp.WithString("id",
61+
mcp.Required(),
62+
mcp.Description("Plan ID"),
63+
),
64+
)
65+
66+
s.server.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
67+
id, err := request.RequireString("id")
68+
if err != nil {
69+
return mcp.NewToolResultError(err.Error()), nil
70+
}
71+
72+
// Get the notes
73+
notes, err := s.planRepo.GetNotes(ctx, id)
74+
if err != nil {
75+
return mcp.NewToolResultError(fmt.Sprintf("Failed to get plan notes: %v", err)), nil
76+
}
77+
78+
result := map[string]string{
79+
"id": id,
80+
"notes": notes,
81+
}
82+
83+
resultJson, err := json.Marshal(result)
84+
if err != nil {
85+
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal result: %v", err)), nil
86+
}
87+
return mcp.NewToolResultText(string(resultJson)), nil
88+
})
89+
}
90+
91+
// registerUpdateTaskNotesTool registers a tool to update notes for a task
92+
func (s *MCPGoServer) registerUpdateTaskNotesTool() {
93+
tool := mcp.NewTool("update_task_notes",
94+
mcp.WithDescription("Update the notes for a specific task"),
95+
mcp.WithString("id",
96+
mcp.Required(),
97+
mcp.Description("Task ID"),
98+
),
99+
mcp.WithString("notes",
100+
mcp.Required(),
101+
mcp.Description("Markdown-formatted notes content"),
102+
),
103+
)
104+
105+
s.server.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
106+
id, err := request.RequireString("id")
107+
if err != nil {
108+
return mcp.NewToolResultError(err.Error()), nil
109+
}
110+
111+
notes, err := request.RequireString("notes")
112+
if err != nil {
113+
return mcp.NewToolResultError(err.Error()), nil
114+
}
115+
116+
// Update the notes
117+
err = s.taskRepo.UpdateNotes(ctx, id, notes)
118+
if err != nil {
119+
return mcp.NewToolResultError(fmt.Sprintf("Failed to update task notes: %v", err)), nil
120+
}
121+
122+
// Get the updated task
123+
task, err := s.taskRepo.Get(ctx, id)
124+
if err != nil {
125+
return mcp.NewToolResultError(fmt.Sprintf("Failed to get updated task: %v", err)), nil
126+
}
127+
128+
taskJson, err := json.Marshal(task)
129+
if err != nil {
130+
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal task: %v", err)), nil
131+
}
132+
return mcp.NewToolResultText(string(taskJson)), nil
133+
})
134+
}
135+
136+
// registerGetTaskNotesTool registers a tool to get notes for a task
137+
func (s *MCPGoServer) registerGetTaskNotesTool() {
138+
tool := mcp.NewTool("get_task_notes",
139+
mcp.WithDescription("Retrieve the notes for a specific task"),
140+
mcp.WithString("id",
141+
mcp.Required(),
142+
mcp.Description("Task ID"),
143+
),
144+
)
145+
146+
s.server.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
147+
id, err := request.RequireString("id")
148+
if err != nil {
149+
return mcp.NewToolResultError(err.Error()), nil
150+
}
151+
152+
// Get the notes
153+
notes, err := s.taskRepo.GetNotes(ctx, id)
154+
if err != nil {
155+
return mcp.NewToolResultError(fmt.Sprintf("Failed to get task notes: %v", err)), nil
156+
}
157+
158+
result := map[string]string{
159+
"id": id,
160+
"notes": notes,
161+
}
162+
163+
resultJson, err := json.Marshal(result)
164+
if err != nil {
165+
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal result: %v", err)), nil
166+
}
167+
return mcp.NewToolResultText(string(resultJson)), nil
168+
})
169+
}

‎internal/mcp/server_mcp.go‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ func (s *MCPGoServer) registerTools() {
6464
s.registerDeletePlanTool()
6565
s.registerUpdatePlanStatusTool()
6666
s.registerListPlansByStatusTool()
67+
68+
// Plan notes tools
69+
s.registerUpdatePlanNotesTool()
70+
s.registerGetPlanNotesTool()
6771

6872
// Task tools
6973
s.registerCreateTaskTool()
@@ -76,6 +80,10 @@ func (s *MCPGoServer) registerTools() {
7680
s.registerBulkCreateTasksTool()
7781
s.registerReorderTaskTool()
7882
s.registerListOrphanedTasksTool()
83+
84+
// Task notes tools
85+
s.registerUpdateTaskNotesTool()
86+
s.registerGetTaskNotesTool()
7987
}
8088

8189
// Plan tools implementation
@@ -94,6 +102,9 @@ func (s *MCPGoServer) registerCreatePlanTool() {
94102
mcp.WithString("description",
95103
mcp.Description("Detailed description of the feature's goals, requirements, and scope (optional)"),
96104
),
105+
mcp.WithString("notes",
106+
mcp.Description("Initial Markdown-formatted notes for the plan (optional)"),
107+
),
97108
)
98109

99110
s.server.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -109,13 +120,28 @@ func (s *MCPGoServer) registerCreatePlanTool() {
109120
}
110121

111122
description := request.GetString("description", "no description provided")
123+
notes := request.GetString("notes", "")
112124

113125
// Create the plan
114126
plan, err := s.planRepo.Create(ctx, applicationID, name, description)
115127
if err != nil {
116128
return mcp.NewToolResultError(fmt.Sprintf("Failed to create plan: %v", err)), nil
117129
}
118130

131+
// If notes were provided, update them
132+
if notes != "" {
133+
err = s.planRepo.UpdateNotes(ctx, plan.ID, notes)
134+
if err != nil {
135+
return mcp.NewToolResultError(fmt.Sprintf("Failed to set initial notes: %v", err)), nil
136+
}
137+
138+
// Refresh plan to include notes
139+
plan, err = s.planRepo.Get(ctx, plan.ID)
140+
if err != nil {
141+
return mcp.NewToolResultError(fmt.Sprintf("Failed to refresh plan: %v", err)), nil
142+
}
143+
}
144+
119145
planJson, err := json.Marshal(plan)
120146
if err != nil {
121147
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal plan: %v", err)), nil
@@ -277,6 +303,9 @@ func (s *MCPGoServer) registerUpdatePlanTool() {
277303
mcp.WithString("description",
278304
mcp.Description("New plan description (optional)"),
279305
),
306+
mcp.WithString("notes",
307+
mcp.Description("New Markdown-formatted notes (optional)"),
308+
),
280309
)
281310

282311
s.server.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -301,6 +330,18 @@ func (s *MCPGoServer) registerUpdatePlanTool() {
301330
if description != plan.Description {
302331
plan.Description = description
303332
}
333+
334+
// Check if notes are provided
335+
notes := request.GetString("notes", "")
336+
if notes != "" {
337+
// Update notes separately using the dedicated method
338+
err = s.planRepo.UpdateNotes(ctx, id, notes)
339+
if err != nil {
340+
return mcp.NewToolResultError(fmt.Sprintf("Failed to update notes: %v", err)), nil
341+
}
342+
// Update plan.Notes for the response
343+
plan.Notes = notes
344+
}
304345

305346
// Save the updated plan
306347
err = s.planRepo.Update(ctx, plan)
@@ -399,6 +440,9 @@ func (s *MCPGoServer) registerCreateTaskTool() {
399440
mcp.Description("Importance and urgency of this task in the overall feature implementation plan (optional, defaults to 'medium')"),
400441
mcp.Enum("low", "medium", "high"),
401442
),
443+
mcp.WithString("notes",
444+
mcp.Description("Initial Markdown-formatted notes for the task (optional)"),
445+
),
402446
)
403447

404448
s.server.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -413,6 +457,7 @@ func (s *MCPGoServer) registerCreateTaskTool() {
413457
}
414458

415459
description := request.GetString("description", "no description provided")
460+
notes := request.GetString("notes", "")
416461

417462
priorityStr := request.GetString("priority", string(models.TaskPriorityMedium))
418463
priority := models.TaskPriority(priorityStr)
@@ -422,6 +467,20 @@ func (s *MCPGoServer) registerCreateTaskTool() {
422467
return mcp.NewToolResultError(fmt.Sprintf("Failed to create task: %v", err)), nil
423468
}
424469

470+
// If notes were provided, update them
471+
if notes != "" {
472+
err = s.taskRepo.UpdateNotes(ctx, task.ID, notes)
473+
if err != nil {
474+
return mcp.NewToolResultError(fmt.Sprintf("Failed to set initial notes: %v", err)), nil
475+
}
476+
477+
// Refresh task to include notes
478+
task, err = s.taskRepo.Get(ctx, task.ID)
479+
if err != nil {
480+
return mcp.NewToolResultError(fmt.Sprintf("Failed to refresh task: %v", err)), nil
481+
}
482+
}
483+
425484
taskJson, err := json.Marshal(task)
426485
if err != nil {
427486
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal task: %v", err)), nil
@@ -538,6 +597,9 @@ func (s *MCPGoServer) registerUpdateTaskTool() {
538597
mcp.Description("New task priority (optional)"),
539598
mcp.Enum("low", "medium", "high"),
540599
),
600+
mcp.WithString("notes",
601+
mcp.Description("New Markdown-formatted notes (optional)"),
602+
),
541603
)
542604

543605
s.server.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -569,6 +631,18 @@ func (s *MCPGoServer) registerUpdateTaskTool() {
569631
priorityStr := request.GetString("priority", string(task.Priority))
570632
task.Priority = models.TaskPriority(priorityStr)
571633

634+
// Check if notes are provided
635+
notes := request.GetString("notes", "")
636+
if notes != "" {
637+
// Update notes separately using the dedicated method
638+
err = s.taskRepo.UpdateNotes(ctx, id, notes)
639+
if err != nil {
640+
return mcp.NewToolResultError(fmt.Sprintf("Failed to update notes: %v", err)), nil
641+
}
642+
// Update task.Notes for the response
643+
task.Notes = notes
644+
}
645+
572646
// Save the updated task
573647
err = s.taskRepo.Update(ctx, task)
574648
if err != nil {

0 commit comments

Comments
 (0)