@@ -4,11 +4,17 @@ import (
 	"bytes"
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"log"
 	"net/http"
 	"os"
+	"strings"
+	"time"
+
+	"github.com/go-playground/validator/v10"
+	"github.com/ijt/go-anytime"
 
 	"github.com/BurntSushi/toml"
 	"github.com/mark3labs/mcp-go/mcp"
@@ -21,25 +27,23 @@ type Area struct {
 	ID   string `toml:"id"`
 }
 
-// Config holds the application's configuration loaded from TOML
-type Config struct {
-	AccessToken string `toml:"access_token"`
-	Areas       []Area `toml:"areas"`
+// Goal represents a Lunatask goal with its name and ID
+type Goal struct {
+	Name string `toml:"name"`
+	ID   string `toml:"id"`
 }
 
-
-// LunataskCreateTaskRequest represents the request payload for creating a task in Lunatask
-type LunataskCreateTaskRequest struct {
-	Name   string `json:"name"`
-	Source string `json:"source"`
-	AreaID string `json:"area_id"`
+// Config holds the application's configuration loaded from TOML
+type ServerConfig struct {
+	Host string `toml:"host"`
+	Port int    `toml:"port"`
 }
 
-// LunataskCreateTaskResponse represents the response from Lunatask API when creating a task
-type LunataskCreateTaskResponse struct {
-	Task struct {
-		ID string `json:"id"`
-	} `json:"task"`
+type Config struct {
+	AccessToken string       `toml:"access_token"`
+	Areas       []Area       `toml:"areas"`
+	Goals       []Goal       `toml:"goals"`
+	Server      ServerConfig `toml:"server"`
 }
 
 func main() {
@@ -53,21 +57,40 @@ func main() {
 		}
 	}
 
+	// Check if config exists; if not, generate default config and exit
+	if _, err := os.Stat(configPath); os.IsNotExist(err) {
+		createDefaultConfigFile(configPath)
+	}
+
 	// Load and decode TOML config
 	var config Config
 	if _, err := toml.DecodeFile(configPath, &config); err != nil {
 		log.Fatalf("Failed to load config file %s: %v", configPath, err)
 	}
 
-	if config.AccessToken == "" || config.AreaID == "" {
-		log.Fatalf("Config file must provide access_token and area_id")
+	if config.AccessToken == "" || len(config.Areas) == 0 {
+		log.Fatalf("Config file must provide access_token and at least one area.")
+	}
+	// All areas must have both name and id
+	for i, area := range config.Areas {
+		if area.Name == "" || area.ID == "" {
+			log.Fatalf("All areas (areas[%d]) must have both a name and id", i)
+		}
+	}
+	// If goals exist, all must have name and id
+	for i, goal := range config.Goals {
+		if goal.Name == "" || goal.ID == "" {
+			log.Fatalf("All goals (goals[%d]) must have both a name and id", i)
+		}
 	}
 
 	mcpServer := NewMCPServer(&config)
 
-	sseServer := server.NewSSEServer(mcpServer, server.WithBaseURL("http://localhost:8080"))
-	log.Printf("SSE server listening on :8080")
-	if err := sseServer.Start(":8080"); err != nil {
+	baseURL := fmt.Sprintf("http://%s:%d", config.Server.Host, config.Server.Port)
+	sseServer := server.NewSSEServer(mcpServer, server.WithBaseURL(baseURL))
+	listenAddr := fmt.Sprintf("%s:%d", config.Server.Host, config.Server.Port)
+	log.Printf("SSE server listening on %s (baseURL: %s)", listenAddr, baseURL)
+	if err := sseServer.Start(listenAddr); err != nil {
 		log.Fatalf("Server error: %v", err)
 	}
 }
@@ -99,17 +122,57 @@ func NewMCPServer(config *Config) *server.MCPServer {
 
 	mcpServer := server.NewMCPServer(
 		"Lunatask MCP Server",
-		"1.0.0",
+		"0.1.0",
 		server.WithHooks(hooks),
 	)
 
-	// Pass config to the handler through closure
+	mcpServer.AddTool(mcp.NewTool("get_date_for_task",
+		mcp.WithDescription("Retrieves the formatted date for a task"),
+		mcp.WithString("natural_language_date",
+			mcp.Description("Natural language date as described by the user, e.g. '1 week', 'tomorrow', etc."),
+			mcp.Required(),
+		),
+	), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+		natLangDate, ok := request.Params.Arguments["natural_language_date"].(string)
+		if !ok || natLangDate == "" {
+			return reportMCPError("Missing or invalid required argument: natural_language_date")
+		}
+		parsedTime, err := anytime.Parse(natLangDate, time.Now())
+		if err != nil {
+			return reportMCPError(fmt.Sprintf("Could not parse natural language date: %v", err))
+		}
+		return &mcp.CallToolResult{
+			Content: []mcp.Content{
+				mcp.TextContent{
+					Type: "text",
+					Text: parsedTime.Format(time.RFC3339),
+				},
+			},
+		}, nil
+	})
+
 	mcpServer.AddTool(mcp.NewTool("create_task",
 		mcp.WithDescription("Creates a new task"),
+		mcp.WithString("area_id",
+			mcp.Description("Area ID in which to create the task"),
+			mcp.Required(),
+		),
+		mcp.WithString("goal_id",
+			mcp.Description("Goal the task should be associated with"),
+		),
 		mcp.WithString("name",
-			mcp.Description("Name of the task"),
+			mcp.Description("Plain text task name"),
 			mcp.Required(),
 		),
+		mcp.WithString("note",
+			mcp.Description("Note attached to the task, optionally Markdown-formatted"),
+		),
+		mcp.WithNumber("estimate",
+			mcp.Description("Estimated time to completion in minutes"),
+		),
+		mcp.WithString("scheduled_on",
+			mcp.Description("Natural language date the task is scheduled on"),
+		),
 	), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 		return handleCreateTask(ctx, request, config)
 	})
@@ -117,29 +180,77 @@ func NewMCPServer(config *Config) *server.MCPServer {
 	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"`
+	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"`
+}
+
+// LunataskCreateTaskResponse represents the response from Lunatask API when creating a task
+type LunataskCreateTaskResponse struct {
+	Task struct {
+		ID string `json:"id"`
+	} `json:"task"`
+}
+
+func reportMCPError(msg string) (*mcp.CallToolResult, error) {
+	return &mcp.CallToolResult{
+		IsError: true,
+		Content: []mcp.Content{mcp.TextContent{Type: "text", Text: msg}},
+	}, nil
+}
+
+// handleCreateTask handles the creation of a task in Lunatask
 func handleCreateTask(
 	ctx context.Context,
 	request mcp.CallToolRequest,
 	config *Config,
 ) (*mcp.CallToolResult, error) {
-	// Extract the name parameter from the request
 	arguments := request.Params.Arguments
-	name, ok := arguments["name"].(string)
-	if !ok {
-		return nil, fmt.Errorf("invalid value for argument 'name'")
-	}
 
-	// Create the payload for the Lunatask API using our struct
 	payload := LunataskCreateTaskRequest{
-		Name:   name,
 		Source: "lmcps",
-		AreaID: config.AreaID,
+	}
+	argBytes, err := json.Marshal(arguments)
+	if err != nil {
+		return reportMCPError(fmt.Sprintf("Failed to process arguments: %v", err))
+	}
+	if err := json.Unmarshal(argBytes, &payload); err != nil {
+		return reportMCPError(fmt.Sprintf("Failed to parse arguments: %v", err))
+	}
+
+	// Validate the struct before sending
+	validate := validator.New()
+	if err := validate.Struct(payload); err != nil {
+		var invalidValidationError *validator.InvalidValidationError
+		if errors.As(err, &invalidValidationError) {
+			return reportMCPError(fmt.Sprintf("Invalid validation error: %v", err))
+		}
+		var validationErrs validator.ValidationErrors
+		if errors.As(err, &validationErrs) {
+			var msgBuilder strings.Builder
+			msgBuilder.WriteString("task validation failed:")
+			for _, e := range validationErrs {
+				fmt.Fprintf(&msgBuilder, " field '%s' failed '%s' validation (was: %v);", e.Field(), e.Tag(), e.Value())
+			}
+			return reportMCPError(msgBuilder.String())
+		}
+		return reportMCPError(fmt.Sprintf("Validation error: %v", err))
 	}
 
 	// Convert the payload to JSON
 	payloadBytes, err := json.Marshal(payload)
 	if err != nil {
-		return nil, fmt.Errorf("failed to marshal payload: %w", err)
+		return reportMCPError(fmt.Sprintf("Failed to marshal payload: %v", err))
 	}
 
 	// Create the HTTP request
@@ -150,18 +261,18 @@ func handleCreateTask(
 		bytes.NewBuffer(payloadBytes),
 	)
 	if err != nil {
-		return nil, fmt.Errorf("failed to create HTTP request: %w", err)
+		return reportMCPError(fmt.Sprintf("Failed to create HTTP request: %v", err))
 	}
 
 	// Set the required headers
 	req.Header.Set("Content-Type", "application/json")
-	req.Header.Set("Authorization", "bearer " + config.AccessToken)
+	req.Header.Set("Authorization", "bearer "+config.AccessToken)
 
 	// Send the request
 	client := &http.Client{}
 	resp, err := client.Do(req)
 	if err != nil {
-		return nil, fmt.Errorf("failed to send HTTP request: %w", err)
+		return reportMCPError(fmt.Sprintf("Failed to send HTTP request: %v", err))
 	}
 	defer resp.Body.Close()
 
@@ -171,7 +282,7 @@ func handleCreateTask(
 			Content: []mcp.Content{
 				mcp.TextContent{
 					Type: "text",
-					Text: "Duplicate task found, no new task created.",
+					Text: "Task already exists (not an error).",
 				},
 			},
 		}, nil
@@ -180,7 +291,10 @@ func handleCreateTask(
 	// Check for error responses
 	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
 		respBody, _ := io.ReadAll(resp.Body)
-		return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(respBody))
+		return &mcp.CallToolResult{
+			IsError: true,
+			Content: []mcp.Content{mcp.TextContent{Type: "text", Text: fmt.Sprintf("API error (status %d): %s", resp.StatusCode, string(respBody))}},
+		}, nil
 	}
 
 	// Parse the response
@@ -188,12 +302,12 @@ func handleCreateTask(
 
 	respBody, err := io.ReadAll(resp.Body)
 	if err != nil {
-		return nil, fmt.Errorf("failed to read response body: %w", err)
+		return reportMCPError(fmt.Sprintf("Failed to read response body: %v", err))
 	}
 
 	err = json.Unmarshal(respBody, &response)
 	if err != nil {
-		return nil, fmt.Errorf("failed to parse response: %w", err)
+		return reportMCPError(fmt.Sprintf("Failed to parse response: %v", err))
 	}
 
 	// Return success result
@@ -201,8 +315,36 @@ func handleCreateTask(
 		Content: []mcp.Content{
 			mcp.TextContent{
 				Type: "text",
-				Text: fmt.Sprintf("Task created successfully! Task ID: %s", response.Task.ID),
+				Text: fmt.Sprint("Task created successfully.", response.Task.ID),
 			},
 		},
 	}, nil
 }
+
+func createDefaultConfigFile(configPath string) {
+	defaultConfig := Config{
+		Server: ServerConfig{
+			Host: "localhost",
+			Port: 8080,
+		},
+		AccessToken: "",
+		Areas: []Area{{
+			Name: "Example Area",
+			ID:   "area-id-placeholder",
+		}},
+		Goals: []Goal{{
+			Name: "Example Goal",
+			ID:   "goal-id-placeholder",
+		}},
+	}
+	file, err := os.Create(configPath)
+	if err != nil {
+		log.Fatalf("Failed to create default config at %s: %v", configPath, err)
+	}
+	defer file.Close()
+	if err := toml.NewEncoder(file).Encode(defaultConfig); err != nil {
+		log.Fatalf("Failed to encode default config to %s: %v", configPath, err)
+	}
+	fmt.Printf("A default config has been created at %s.\nPlease edit it to provide your Lunatask access token and correct area/goal IDs, then restart the server.\n", configPath)
+	os.Exit(1)
+}