feat: add new tools for listing areas and goals

Amolith created

Add new tools to the MCP server for listing areas and their IDs, and listing
goals and their IDs. Update the LunataskCreateTaskRequest struct to include
validation rules and default values for the AreaID field.

- Adds a new tool "list_areas" to the MCP server.
- Adds a new tool "list_goals" to the MCP server.
- Updates the LunataskCreateTaskRequest struct to include validation rules and
  default values for the AreaID field.

Change summary

main.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 54 insertions(+), 10 deletions(-)

Detailed changes

main.go 🔗

@@ -177,22 +177,66 @@ func NewMCPServer(config *Config) *server.MCPServer {
 		return handleCreateTask(ctx, request, config)
 	})
 
+	mcpServer.AddTool(
+		mcp.NewTool(
+			"list_areas",
+			mcp.WithDescription("List areas and their IDs."),
+		),
+		func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+			var b strings.Builder
+			b.WriteString("| Area Name | Area ID |\n|-----------|--------|\n")
+			for _, area := range config.Areas {
+				fmt.Fprintf(&b, "| %s | %s |\n", area.Name, area.ID)
+			}
+			return &mcp.CallToolResult{
+				Content: []mcp.Content{
+					mcp.TextContent{
+						Type: "text",
+						Text: b.String(),
+					},
+				},
+			}, nil
+		},
+	)
+
+	mcpServer.AddTool(
+		mcp.NewTool(
+			"list_goals",
+			mcp.WithDescription("List goals and their IDs."),
+		),
+		func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+			var b strings.Builder
+			b.WriteString("| Goal Name | Goal ID |\n|----------|--------|\n")
+			for _, goal := range config.Goals {
+				fmt.Fprintf(&b, "| %s | %s |\n", goal.Name, goal.ID)
+			}
+			return &mcp.CallToolResult{
+				Content: []mcp.Content{
+					mcp.TextContent{
+						Type: "text",
+						Text: b.String(),
+					},
+				},
+			}, nil
+		},
+	)
+
 	return mcpServer
 }
 
 // LunataskCreateTaskRequest represents the request payload for creating a task in Lunatask
 type LunataskCreateTaskRequest struct {
 	AreaID      string `json:"area_id"`
-	GoalID      string `json:"goal_id,omitempty"`
+	GoalID      string `json:"goal_id,omitempty" validate:"omitempty"`
 	Name        string `json:"name" validate:"max=100"`
-	Note        string `json:"note,omitempty"`
-	Status      string `json:"status,omitempty" validate:"oneof=later next started waiting completed"`
-	Motivation  string `json:"motivation,omitempty" validate:"oneof=must should want unknown"`
-	Estimate    int    `json:"estimate,omitempty" validate:"min=0,max=720"`
-	Priority    int    `json:"priority,omitempty" validate:"min=-2,max=2"`
-	ScheduledOn string `json:"scheduled_on,omitempty"`
-	CompletedAt string `json:"completed_at,omitempty"`
-	Source      string `json:"source,omitempty"`
+	Note        string `json:"note,omitempty" validate:"omitempty"`
+	Status      string `json:"status,omitempty" validate:"omitempty,oneof=later next started waiting completed"`
+	Motivation  string `json:"motivation,omitempty" validate:"omitempty,oneof=must should want unknown"`
+	Estimate    int    `json:"estimate,omitempty" validate:"omitempty,min=0,max=720"`
+	Priority    int    `json:"priority,omitempty" validate:"omitempty,min=-2,max=2"`
+	ScheduledOn string `json:"scheduled_on,omitempty" validate:"omitempty"`
+	CompletedAt string `json:"completed_at,omitempty" validate:"omitempty"`
+	Source      string `json:"source,omitempty" validate:"omitempty"`
 }
 
 // LunataskCreateTaskResponse represents the response from Lunatask API when creating a task
@@ -218,7 +262,7 @@ func handleCreateTask(
 	arguments := request.Params.Arguments
 
 	payload := LunataskCreateTaskRequest{
-		Source: "lmcps",
+		AreaID: config.Areas[0].ID,
 	}
 	argBytes, err := json.Marshal(arguments)
 	if err != nil {