Detailed changes
@@ -142,7 +142,7 @@ func registerTools(
}, tsHandler.Handle)
}
- registerTaskTools(mcpServer, tools, accessToken, areaProviders)
+ registerTaskTools(mcpServer, cfg, tools, accessToken, areaProviders)
registerNoteTools(mcpServer, tools, accessToken, notebookProviders)
registerPersonTools(mcpServer, tools, accessToken)
@@ -165,11 +165,12 @@ func registerTools(
func registerTaskTools(
mcpServer *mcp.Server,
+ cfg *config.Config,
tools *config.ToolsConfig,
accessToken string,
areaProviders []shared.AreaProvider,
) {
- taskHandler := task.NewHandler(accessToken, areaProviders)
+ taskHandler := task.NewHandler(accessToken, cfg, areaProviders)
if tools.CreateTask {
mcp.AddTool(mcpServer, &mcp.Tool{
@@ -9,8 +9,10 @@ import (
"context"
"git.secluded.site/go-lunatask"
+ "git.secluded.site/lune/internal/config"
"git.secluded.site/lune/internal/dateutil"
"git.secluded.site/lune/internal/mcp/shared"
+ "git.secluded.site/lune/internal/validate"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@@ -24,8 +26,8 @@ Required:
- name: Task title
Optional:
-- area_id: Area UUID (get from lunatask://areas resource)
-- goal_id: Goal UUID (requires area_id; get from lunatask://areas resource)
+- area_id: Area UUID, lunatask:// deep link, or config key
+- goal_id: Goal UUID, lunatask:// deep link, or config key (requires area_id)
- status: later, next, started, waiting (default: later)
- note: Markdown note/description for the task
- priority: lowest, low, normal, high, highest
@@ -75,13 +77,15 @@ type parsedCreateInput struct {
// Handler handles task-related MCP tool requests.
type Handler struct {
client *lunatask.Client
+ cfg *config.Config
areas []shared.AreaProvider
}
// NewHandler creates a new task handler.
-func NewHandler(accessToken string, areas []shared.AreaProvider) *Handler {
+func NewHandler(accessToken string, cfg *config.Config, areas []shared.AreaProvider) *Handler {
return &Handler{
client: lunatask.NewClient(accessToken, lunatask.UserAgent("lune-mcp/1.0")),
+ cfg: cfg,
areas: areas,
}
}
@@ -92,7 +96,7 @@ func (h *Handler) HandleCreate(
_ *mcp.CallToolRequest,
input CreateInput,
) (*mcp.CallToolResult, CreateOutput, error) {
- parsed, errResult := parseCreateInput(input)
+ parsed, errResult := parseCreateInput(h.cfg, input)
if errResult != nil {
return errResult, CreateOutput{}, nil
}
@@ -115,11 +119,9 @@ func (h *Handler) HandleCreate(
}
//nolint:cyclop,funlen
-func parseCreateInput(input CreateInput) (*parsedCreateInput, *mcp.CallToolResult) {
+func parseCreateInput(cfg *config.Config, input CreateInput) (*parsedCreateInput, *mcp.CallToolResult) {
parsed := &parsedCreateInput{
Name: input.Name,
- AreaID: input.AreaID,
- GoalID: input.GoalID,
Note: input.Note,
Estimate: input.Estimate,
Important: input.Important,
@@ -127,15 +129,26 @@ func parseCreateInput(input CreateInput) (*parsedCreateInput, *mcp.CallToolResul
}
if input.AreaID != nil {
- if err := lunatask.ValidateUUID(*input.AreaID); err != nil {
- return nil, shared.ErrorResult("invalid area_id: expected UUID")
+ areaID, err := validate.AreaRef(cfg, *input.AreaID)
+ if err != nil {
+ return nil, shared.ErrorResult(err.Error())
}
+
+ parsed.AreaID = &areaID
}
if input.GoalID != nil {
- if err := lunatask.ValidateUUID(*input.GoalID); err != nil {
- return nil, shared.ErrorResult("invalid goal_id: expected UUID")
+ areaID := ""
+ if parsed.AreaID != nil {
+ areaID = *parsed.AreaID
+ }
+
+ goalID, err := validate.GoalRef(cfg, areaID, *input.GoalID)
+ if err != nil {
+ return nil, shared.ErrorResult(err.Error())
}
+
+ parsed.GoalID = &goalID
}
if input.Estimate != nil {
@@ -8,8 +8,10 @@ import (
"context"
"git.secluded.site/go-lunatask"
+ "git.secluded.site/lune/internal/config"
"git.secluded.site/lune/internal/dateutil"
"git.secluded.site/lune/internal/mcp/shared"
+ "git.secluded.site/lune/internal/validate"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@@ -24,8 +26,8 @@ Required:
Optional (only specified fields are updated):
- name: New task title
-- area_id: Move to area UUID
-- goal_id: Move to goal UUID (requires area_id)
+- area_id: Move to area (UUID, lunatask:// deep link, or config key)
+- goal_id: Move to goal (UUID, lunatask:// deep link, or config key; requires area_id)
- status: later, next, started, waiting, completed
- note: New markdown note (replaces existing)
- priority: lowest, low, normal, high, highest
@@ -80,7 +82,7 @@ func (h *Handler) HandleUpdate(
_ *mcp.CallToolRequest,
input UpdateInput,
) (*mcp.CallToolResult, UpdateOutput, error) {
- parsed, errResult := parseUpdateInput(input)
+ parsed, errResult := parseUpdateInput(h.cfg, input)
if errResult != nil {
return errResult, UpdateOutput{}, nil
}
@@ -103,7 +105,7 @@ func (h *Handler) HandleUpdate(
}
//nolint:cyclop,funlen
-func parseUpdateInput(input UpdateInput) (*parsedUpdateInput, *mcp.CallToolResult) {
+func parseUpdateInput(cfg *config.Config, input UpdateInput) (*parsedUpdateInput, *mcp.CallToolResult) {
_, id, err := lunatask.ParseReference(input.ID)
if err != nil {
return nil, shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link")
@@ -112,8 +114,6 @@ func parseUpdateInput(input UpdateInput) (*parsedUpdateInput, *mcp.CallToolResul
parsed := &parsedUpdateInput{
ID: id,
Name: input.Name,
- AreaID: input.AreaID,
- GoalID: input.GoalID,
Note: input.Note,
Estimate: input.Estimate,
Important: input.Important,
@@ -121,15 +121,26 @@ func parseUpdateInput(input UpdateInput) (*parsedUpdateInput, *mcp.CallToolResul
}
if input.AreaID != nil {
- if err := lunatask.ValidateUUID(*input.AreaID); err != nil {
- return nil, shared.ErrorResult("invalid area_id: expected UUID")
+ areaID, err := validate.AreaRef(cfg, *input.AreaID)
+ if err != nil {
+ return nil, shared.ErrorResult(err.Error())
}
+
+ parsed.AreaID = &areaID
}
if input.GoalID != nil {
- if err := lunatask.ValidateUUID(*input.GoalID); err != nil {
- return nil, shared.ErrorResult("invalid goal_id: expected UUID")
+ areaID := ""
+ if parsed.AreaID != nil {
+ areaID = *parsed.AreaID
+ }
+
+ goalID, err := validate.GoalRef(cfg, areaID, *input.GoalID)
+ if err != nil {
+ return nil, shared.ErrorResult(err.Error())
}
+
+ parsed.GoalID = &goalID
}
if input.Estimate != nil {
@@ -10,6 +10,7 @@ import (
"fmt"
"git.secluded.site/go-lunatask"
+ "git.secluded.site/lune/internal/config"
)
// ErrInvalidReference indicates the input is not a valid UUID or deep link.
@@ -69,3 +70,50 @@ func RelationshipStrength(input string) (lunatask.RelationshipStrength, error) {
return rel, nil
}
+
+// ErrInvalidArea indicates the area reference is not a valid UUID, deep link, or config key.
+var ErrInvalidArea = errors.New("invalid area: expected UUID, lunatask:// deep link, or config key")
+
+// AreaRef resolves an area reference to a UUID.
+// Accepts formats:
+// - UUID: "527a2b42-99fd-490d-8b21-c55451368f4c"
+// - Deep link: "lunatask://areas/527a2b42-..."
+// - Config key: "projects"
+func AreaRef(cfg *config.Config, input string) (string, error) {
+ // Try UUID or deep link first
+ if _, id, err := lunatask.ParseReference(input); err == nil {
+ return id, nil
+ }
+
+ // Try config key lookup
+ if area := cfg.AreaByKey(input); area != nil {
+ return area.ID, nil
+ }
+
+ return "", fmt.Errorf("%w: %s", ErrInvalidArea, input)
+}
+
+// ErrInvalidGoal indicates the goal reference is not a valid UUID, deep link, or config key.
+var ErrInvalidGoal = errors.New("invalid goal: expected UUID, lunatask:// deep link, or config key")
+
+// GoalRef resolves a goal reference to a UUID.
+// Requires a valid area ID to look up goals by key (goals are scoped to areas).
+// Accepts formats:
+// - UUID: "53ca909e-887d-4ed2-9943-d1212adf8ad8"
+// - Deep link: "lunatask://goals/53ca909e-..."
+// - Config key: "lunatask" (requires valid areaID for lookup)
+func GoalRef(cfg *config.Config, areaID, input string) (string, error) {
+ // Try UUID or deep link first
+ if _, id, err := lunatask.ParseReference(input); err == nil {
+ return id, nil
+ }
+
+ // Try config key lookup (requires area context)
+ if area := cfg.AreaByID(areaID); area != nil {
+ if goal := area.GoalByKey(input); goal != nil {
+ return goal.ID, nil
+ }
+ }
+
+ return "", fmt.Errorf("%w: %s", ErrInvalidGoal, input)
+}