Detailed changes
@@ -347,6 +347,7 @@ func registerTools(
mcp.AddTool(mcpServer, &mcp.Tool{
Name: timestamp.ToolName,
Description: timestamp.ToolDescription,
+ Annotations: timestamp.ToolAnnotations(),
}, tsHandler.Handle)
}
@@ -355,6 +356,7 @@ func registerTools(
mcp.AddTool(mcpServer, &mcp.Tool{
Name: timeline.ToolName,
Description: timeline.ToolDescription,
+ Annotations: timeline.ToolAnnotations(),
}, timelineHandler.Handle)
}
@@ -363,6 +365,7 @@ func registerTools(
mcp.AddTool(mcpServer, &mcp.Tool{
Name: habit.TrackToolName,
Description: habit.TrackToolDescription,
+ Annotations: habit.TrackToolAnnotations(),
}, habitHandler.HandleTrack)
}
@@ -388,6 +391,8 @@ func registerCRUDTools(
mcp.AddTool(mcpServer, &mcp.Tool{
Name: crud.CreateToolName,
Description: crud.CreateToolDescription,
+ InputSchema: crud.CreateInputSchema(),
+ Annotations: crud.CreateToolAnnotations(),
}, crudHandler.HandleCreate)
}
@@ -395,6 +400,8 @@ func registerCRUDTools(
mcp.AddTool(mcpServer, &mcp.Tool{
Name: crud.UpdateToolName,
Description: crud.UpdateToolDescription,
+ InputSchema: crud.UpdateInputSchema(),
+ Annotations: crud.UpdateToolAnnotations(),
}, crudHandler.HandleUpdate)
}
@@ -402,6 +409,8 @@ func registerCRUDTools(
mcp.AddTool(mcpServer, &mcp.Tool{
Name: crud.DeleteToolName,
Description: crud.DeleteToolDescription,
+ InputSchema: crud.DeleteInputSchema(),
+ Annotations: crud.DeleteToolAnnotations(),
}, crudHandler.HandleDelete)
}
@@ -409,6 +418,8 @@ func registerCRUDTools(
mcp.AddTool(mcpServer, &mcp.Tool{
Name: crud.QueryToolName,
Description: crud.QueryToolDescription,
+ InputSchema: crud.QueryInputSchema(),
+ Annotations: crud.QueryToolAnnotations(),
}, crudHandler.HandleQuery)
}
}
@@ -7,13 +7,14 @@ module git.secluded.site/lune
go 1.25.5
require (
- git.secluded.site/go-lunatask v0.1.0-rc9.2
+ git.secluded.site/go-lunatask v0.1.0-rc9.3
github.com/BurntSushi/toml v1.6.0
github.com/KarpelesLab/strtotime v0.0.1
github.com/charmbracelet/fang v0.4.4
github.com/charmbracelet/huh v0.8.0
github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3
github.com/charmbracelet/lipgloss v1.1.0
+ github.com/google/jsonschema-go v0.4.2
github.com/klauspost/lctime v0.1.0
github.com/mattn/go-isatty v0.0.20
github.com/modelcontextprotocol/go-sdk v1.2.0
@@ -46,7 +47,6 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
- github.com/google/jsonschema-go v0.4.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
@@ -2,8 +2,8 @@ al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXy
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 h1:D9PbaszZYpB4nj+d6HTWr1onlmlyuGVNfL9gAi8iB3k=
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410/go.mod h1:1qZyvvVCenJO2M1ac2mX0yyiIZJoZmDM4DG4s0udJkU=
-git.secluded.site/go-lunatask v0.1.0-rc9.2 h1:fk5fCGdHmKpwz5HPy/n/LURBNweJoRN2xSay36VgA7g=
-git.secluded.site/go-lunatask v0.1.0-rc9.2/go.mod h1:rxps7BBqF+BkY8VN5E7J9zSOzSbtZ1hDmLEOHxjTHZQ=
+git.secluded.site/go-lunatask v0.1.0-rc9.3 h1:BrX6S7oQMylWAglSBXy7JjjwuSNYcPTGUlQTdwmdmFc=
+git.secluded.site/go-lunatask v0.1.0-rc9.3/go.mod h1:rxps7BBqF+BkY8VN5E7J9zSOzSbtZ1hDmLEOHxjTHZQ=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/KarpelesLab/strtotime v0.0.1 h1:Af8vDb5RzwHhLP7ctSs7Y4CeiE+qlMXWWMqNd8xGOWY=
@@ -79,8 +79,6 @@ github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeD
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
-github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
@@ -13,6 +13,7 @@ import (
"git.secluded.site/lune/internal/dateutil"
"git.secluded.site/lune/internal/mcp/shared"
"git.secluded.site/lune/internal/validate"
+ "github.com/google/jsonschema-go/jsonschema"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@@ -32,80 +33,67 @@ const (
const CreateToolName = "create"
// CreateToolDescription describes the create tool for LLMs.
-const CreateToolDescription = `Creates a new entity in Lunatask.
-
-Required:
-- entity: Type to create (task, note, person, journal)
-
-Entity-specific fields:
-
-**task** (requires name, area_id):
-- name: Task title
-- area_id: Area UUID, lunatask:// deep link, or config key
-- goal_id: Goal UUID, deep link, or config key (optional)
-- status: later, next, started, waiting (default: later)
-- note: Markdown note/description
-- priority: lowest, low, normal, high, highest
-- estimate: Time estimate in minutes (0-720)
-- motivation: must, should, want
-- important: true/false for Eisenhower matrix
-- urgent: true/false for Eisenhower matrix
-- scheduled_on: Date to schedule (YYYY-MM-DD or natural language)
-
-**note** (all fields optional):
-- name: Note title
-- notebook_id: Notebook UUID
-- content: Markdown content
-- source: Origin identifier for integrations
-- source_id: Source-specific ID (requires source)
-
-**person** (requires first_name):
-- first_name: First name
-- last_name: Last name
-- relationship: Relationship strength (family, intimate-friends, close-friends,
- casual-friends, acquaintances, business-contacts, almost-strangers)
-- source: Origin identifier
-- source_id: Source-specific ID (requires source)
-
-**journal** (all fields optional):
-- name: Entry title (defaults to weekday name)
-- content: Markdown content
-- date: Entry date (YYYY-MM-DD or natural language, default: today)
-
-Returns the created entity's ID and deep link.`
+const CreateToolDescription = `Create a new entity in Lunatask.
+Required fields depend on entity type:
+- task: name, area_id
+- person: first_name
+- note, journal: all fields optional
+Returns the new entity's ID and deep link.`
+
+// CreateToolAnnotations returns hints about tool behavior.
+func CreateToolAnnotations() *mcp.ToolAnnotations {
+ return &mcp.ToolAnnotations{
+ DestructiveHint: ptr(false),
+ }
+}
+
+// CreateInputSchema returns a custom schema with enum constraints.
+func CreateInputSchema() *jsonschema.Schema {
+ schema, _ := jsonschema.For[CreateInput](nil)
+
+ schema.Properties["entity"].Enum = []any{
+ EntityTask, EntityNote, EntityPerson, EntityJournal,
+ }
+ schema.Properties["status"].Enum = toAnyStrings(lunatask.AllTaskStatuses())
+ schema.Properties["priority"].Enum = prioritiesToAny(lunatask.AllPriorities())
+ schema.Properties["motivation"].Enum = toAnyStrings(lunatask.AllMotivations())
+ schema.Properties["relationship"].Enum = toAnyStrings(lunatask.AllRelationshipStrengths())
+
+ return schema
+}
// CreateInput is the input schema for the consolidated create tool.
type CreateInput struct {
- Entity string `json:"entity" jsonschema:"required"`
+ Entity string `json:"entity" jsonschema:"Entity type to create"`
// Common fields
- Name *string `json:"name,omitempty"`
- Content *string `json:"content,omitempty"`
- Source *string `json:"source,omitempty"`
- SourceID *string `json:"source_id,omitempty"`
+ Name *string `json:"name,omitempty" jsonschema:"Title/name (required for task/person)"`
+ Content *string `json:"content,omitempty" jsonschema:"Markdown content"`
+ Source *string `json:"source,omitempty" jsonschema:"Origin identifier for integrations"`
+ SourceID *string `json:"source_id,omitempty" jsonschema:"Source-specific ID (requires source)"`
// Task-specific fields
- AreaID *string `json:"area_id,omitempty"`
- GoalID *string `json:"goal_id,omitempty"`
- Status *string `json:"status,omitempty"`
- Note *string `json:"note,omitempty"`
- Priority *string `json:"priority,omitempty"`
- Estimate *int `json:"estimate,omitempty"`
- Motivation *string `json:"motivation,omitempty"`
- Important *bool `json:"important,omitempty"`
- Urgent *bool `json:"urgent,omitempty"`
- ScheduledOn *string `json:"scheduled_on,omitempty"`
+ AreaID *string `json:"area_id,omitempty" jsonschema:"Area (UUID, deep link, or key) - required for task"`
+ GoalID *string `json:"goal_id,omitempty" jsonschema:"Goal (UUID, deep link, or config key)"`
+ Status *string `json:"status,omitempty" jsonschema:"Initial status (default: later)"`
+ Note *string `json:"note,omitempty" jsonschema:"Task note (Markdown)"`
+ Priority *string `json:"priority,omitempty" jsonschema:"Priority level"`
+ Estimate *int `json:"estimate,omitempty" jsonschema:"Time estimate in minutes (0-720)"`
+ Motivation *string `json:"motivation,omitempty" jsonschema:"Task motivation"`
+ Important *bool `json:"important,omitempty" jsonschema:"Eisenhower matrix: important"`
+ Urgent *bool `json:"urgent,omitempty" jsonschema:"Eisenhower matrix: urgent"`
+ ScheduledOn *string `json:"scheduled_on,omitempty" jsonschema:"Schedule date (strtotime syntax)"`
// Note-specific fields
- NotebookID *string `json:"notebook_id,omitempty"`
+ NotebookID *string `json:"notebook_id,omitempty" jsonschema:"Notebook UUID"`
// Person-specific fields
- FirstName *string `json:"first_name,omitempty"`
- LastName *string `json:"last_name,omitempty"`
- Relationship *string `json:"relationship,omitempty"`
+ FirstName *string `json:"first_name,omitempty" jsonschema:"First name (required for person)"`
+ LastName *string `json:"last_name,omitempty" jsonschema:"Last name"`
+ Relationship *string `json:"relationship,omitempty" jsonschema:"Relationship strength"`
// Journal-specific fields
- Date *string `json:"date,omitempty"`
+ Date *string `json:"date,omitempty" jsonschema:"Entry date (strtotime syntax, default: today)"`
}
// CreateOutput is the output schema for the consolidated create tool.
@@ -9,6 +9,7 @@ import (
"git.secluded.site/go-lunatask"
"git.secluded.site/lune/internal/mcp/shared"
+ "github.com/google/jsonschema-go/jsonschema"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@@ -16,20 +17,53 @@ import (
const DeleteToolName = "delete"
// DeleteToolDescription describes the delete tool for LLMs.
-const DeleteToolDescription = `Deletes an entity from Lunatask.
+const DeleteToolDescription = `Permanently delete an entity from Lunatask.
+This action cannot be undone. The entity and its associations are removed.`
-Required:
-- entity: Type to delete (task, note, person)
-- id: Entity UUID or lunatask:// deep link
+// DeleteToolAnnotations returns hints about tool behavior.
+func DeleteToolAnnotations() *mcp.ToolAnnotations {
+ return &mcp.ToolAnnotations{
+ DestructiveHint: ptr(true),
+ }
+}
+
+func ptr[T any](v T) *T { return &v }
+
+// toAnyStrings converts a slice of string-based types to []any for JSON schema enums.
+func toAnyStrings[T ~string](slice []T) []any {
+ result := make([]any, len(slice))
+ for i, v := range slice {
+ result[i] = string(v)
+ }
+
+ return result
+}
-This action is permanent and cannot be undone.
+// prioritiesToAny converts priorities to their string representations for JSON schema enums.
+func prioritiesToAny(priorities []lunatask.Priority) []any {
+ result := make([]any, len(priorities))
+ for i := range priorities {
+ result[i] = priorities[i].String()
+ }
+
+ return result
+}
+
+// DeleteInputSchema returns a custom schema with enum constraints.
+func DeleteInputSchema() *jsonschema.Schema {
+ schema, _ := jsonschema.For[DeleteInput](nil)
-Returns confirmation of deletion with the entity's deep link.`
+ schema.Properties["entity"].Enum = []any{
+ EntityTask, EntityNote, EntityPerson,
+ }
+
+ return schema
+}
// DeleteInput is the input schema for the consolidated delete tool.
type DeleteInput struct {
- Entity string `json:"entity" jsonschema:"required"`
- ID string `json:"id" jsonschema:"required"`
+ Entity string `json:"entity" jsonschema:"Entity type to delete"`
+ ID string `json:"id" jsonschema:"UUID or lunatask:// deep link"`
}
// DeleteOutput is the output schema for the consolidated delete tool.
@@ -12,6 +12,7 @@ import (
"git.secluded.site/go-lunatask"
"git.secluded.site/lune/internal/mcp/shared"
+ "github.com/google/jsonschema-go/jsonschema"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@@ -19,56 +20,49 @@ import (
const QueryToolName = "query"
// QueryToolDescription describes the query tool for LLMs.
-const QueryToolDescription = `Queries entities from Lunatask. Fallback for agents without MCP resource support.
+const QueryToolDescription = `Query Lunatask entities. Fallback for agents without MCP resource support.
-Required:
-- entity: Type to query (task, note, person, area, goal, notebook, habit)
+Provide id for single entity details, omit for filtered list.
+E2E encryption means names/content unavailable in lists—only metadata.
+Prefer MCP resources (lunatask://tasks/today, lunatask://areas) when available.`
-Optional:
-- id: Entity UUID or lunatask:// deep link (if provided, returns single entity details)
-
-When id is omitted, returns a list with optional filters:
-
-**task** filters:
-- area_id: Filter by area UUID
-- status: Filter by status (later, next, started, waiting, completed)
-- include_completed: Include completed tasks (default: false)
-
-**note** filters:
-- notebook_id: Filter by notebook UUID
-- source: Filter by source identifier
-- source_id: Filter by source-specific ID
-
-**person** filters:
-- source: Filter by source identifier
-- source_id: Filter by source-specific ID
+// QueryToolAnnotations returns hints about tool behavior.
+func QueryToolAnnotations() *mcp.ToolAnnotations {
+ return &mcp.ToolAnnotations{
+ ReadOnlyHint: true,
+ }
+}
-**goal** filters:
-- area_id: Required - area UUID, deep link, or config key
+// QueryInputSchema returns a custom schema with enum constraints.
+func QueryInputSchema() *jsonschema.Schema {
+ schema, _ := jsonschema.For[QueryInput](nil)
-**area, notebook, habit**: No filters (returns all from config)
+ schema.Properties["entity"].Enum = []any{
+ EntityTask, EntityNote, EntityPerson, EntityArea, EntityGoal, EntityNotebook, EntityHabit,
+ }
+ schema.Properties["status"].Enum = toAnyStrings(lunatask.AllTaskStatuses())
-Note: Due to end-to-end encryption, names and content are not available
-for list operations. Only metadata is returned. Use id parameter for details.`
+ return schema
+}
// QueryInput is the input schema for the consolidated query tool.
type QueryInput struct {
- Entity string `json:"entity" jsonschema:"required"`
- ID *string `json:"id,omitempty"`
+ Entity string `json:"entity" jsonschema:"Entity type to query"`
+ ID *string `json:"id,omitempty" jsonschema:"UUID or deep link for single entity lookup"`
// Task/Goal filters
- AreaID *string `json:"area_id,omitempty"`
+ AreaID *string `json:"area_id,omitempty" jsonschema:"Filter by area (UUID, deep link, or config key)"`
// Task filters
- Status *string `json:"status,omitempty"`
- IncludeCompleted *bool `json:"include_completed,omitempty"`
+ Status *string `json:"status,omitempty" jsonschema:"Filter by task status"`
+ IncludeCompleted *bool `json:"include_completed,omitempty" jsonschema:"Include completed tasks (default: false)"`
// Note filters
- NotebookID *string `json:"notebook_id,omitempty"`
+ NotebookID *string `json:"notebook_id,omitempty" jsonschema:"Filter by notebook UUID"`
// Note/Person filters
- Source *string `json:"source,omitempty"`
- SourceID *string `json:"source_id,omitempty"`
+ Source *string `json:"source,omitempty" jsonschema:"Filter by source identifier"`
+ SourceID *string `json:"source_id,omitempty" jsonschema:"Filter by source-specific ID"`
}
// QueryOutput is the output schema for the consolidated query tool.
@@ -11,6 +11,7 @@ import (
"git.secluded.site/lune/internal/dateutil"
"git.secluded.site/lune/internal/mcp/shared"
"git.secluded.site/lune/internal/validate"
+ "github.com/google/jsonschema-go/jsonschema"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@@ -18,70 +19,61 @@ import (
const UpdateToolName = "update"
// UpdateToolDescription describes the update tool for LLMs.
-const UpdateToolDescription = `Updates an existing entity in Lunatask.
-
-Required:
-- entity: Type to update (task, note, person)
-- id: Entity UUID or lunatask:// deep link
-
-Entity-specific fields (only provided fields are modified):
-
-**task**:
-- name: New task title
-- area_id: Move to area (UUID, deep link, or config key)
-- goal_id: Move to goal (UUID, 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
-- estimate: Time estimate in minutes (0-720)
-- motivation: must, should, want
-- important: true/false for Eisenhower matrix
-- urgent: true/false for Eisenhower matrix
-- scheduled_on: Date to schedule (YYYY-MM-DD or natural language)
-
-**note**:
-- name: New note title
-- notebook_id: Move to notebook (UUID)
-- content: Replace content (Markdown)
-- date: Note date (YYYY-MM-DD or natural language)
-
-**person**:
-- first_name: New first name
-- last_name: New last name
-- relationship: New relationship strength (family, intimate-friends, close-friends,
- casual-friends, acquaintances, business-contacts, almost-strangers)
-
-Returns the updated entity's deep link.`
+const UpdateToolDescription = `Update an existing Lunatask entity.
+Only provided fields are modified—omit fields to leave unchanged.
+Task note/content replaces existing (not appended). Idempotent.`
+
+// UpdateToolAnnotations returns hints about tool behavior.
+func UpdateToolAnnotations() *mcp.ToolAnnotations {
+ return &mcp.ToolAnnotations{
+ IdempotentHint: true,
+ }
+}
+
+// UpdateInputSchema returns a custom schema with enum constraints.
+func UpdateInputSchema() *jsonschema.Schema {
+ schema, _ := jsonschema.For[UpdateInput](nil)
+
+ schema.Properties["entity"].Enum = []any{
+ EntityTask, EntityNote, EntityPerson,
+ }
+ schema.Properties["status"].Enum = toAnyStrings(lunatask.AllTaskStatuses())
+ schema.Properties["priority"].Enum = prioritiesToAny(lunatask.AllPriorities())
+ schema.Properties["motivation"].Enum = toAnyStrings(lunatask.AllMotivations())
+ schema.Properties["relationship"].Enum = toAnyStrings(lunatask.AllRelationshipStrengths())
+
+ return schema
+}
// UpdateInput is the input schema for the consolidated update tool.
type UpdateInput struct {
- Entity string `json:"entity" jsonschema:"required"`
- ID string `json:"id" jsonschema:"required"`
+ Entity string `json:"entity" jsonschema:"Entity type to update"`
+ ID string `json:"id" jsonschema:"UUID or lunatask:// deep link"`
// Common fields
- Name *string `json:"name,omitempty"`
- Content *string `json:"content,omitempty"`
+ Name *string `json:"name,omitempty" jsonschema:"New title/name"`
+ Content *string `json:"content,omitempty" jsonschema:"New content (Markdown, replaces existing)"`
// Task-specific fields
- AreaID *string `json:"area_id,omitempty"`
- GoalID *string `json:"goal_id,omitempty"`
- Status *string `json:"status,omitempty"`
- Note *string `json:"note,omitempty"`
- Priority *string `json:"priority,omitempty"`
- Estimate *int `json:"estimate,omitempty"`
- Motivation *string `json:"motivation,omitempty"`
- Important *bool `json:"important,omitempty"`
- Urgent *bool `json:"urgent,omitempty"`
- ScheduledOn *string `json:"scheduled_on,omitempty"`
+ AreaID *string `json:"area_id,omitempty" jsonschema:"Move to area (UUID, deep link, or config key)"`
+ GoalID *string `json:"goal_id,omitempty" jsonschema:"Move to goal (requires area_id)"`
+ Status *string `json:"status,omitempty" jsonschema:"New task status"`
+ Note *string `json:"note,omitempty" jsonschema:"New task note (Markdown, replaces existing)"`
+ Priority *string `json:"priority,omitempty" jsonschema:"New priority level"`
+ Estimate *int `json:"estimate,omitempty" jsonschema:"Time estimate in minutes (0-720)"`
+ Motivation *string `json:"motivation,omitempty" jsonschema:"Task motivation"`
+ Important *bool `json:"important,omitempty" jsonschema:"Eisenhower matrix: important"`
+ Urgent *bool `json:"urgent,omitempty" jsonschema:"Eisenhower matrix: urgent"`
+ ScheduledOn *string `json:"scheduled_on,omitempty" jsonschema:"Schedule date (strtotime syntax)"`
// Note-specific fields
- NotebookID *string `json:"notebook_id,omitempty"`
- Date *string `json:"date,omitempty"`
+ NotebookID *string `json:"notebook_id,omitempty" jsonschema:"Move to notebook (UUID)"`
+ Date *string `json:"date,omitempty" jsonschema:"Note date (strtotime syntax)"`
// Person-specific fields
- FirstName *string `json:"first_name,omitempty"`
- LastName *string `json:"last_name,omitempty"`
- Relationship *string `json:"relationship,omitempty"`
+ FirstName *string `json:"first_name,omitempty" jsonschema:"New first name"`
+ LastName *string `json:"last_name,omitempty" jsonschema:"New last name"`
+ Relationship *string `json:"relationship,omitempty" jsonschema:"Relationship strength"`
}
// UpdateOutput is the output schema for the consolidated update tool.
@@ -18,20 +18,22 @@ import (
const TrackToolName = "track_habit"
// TrackToolDescription describes the track habit tool for LLMs.
-const TrackToolDescription = `Records that a habit was performed on a specific date.
+const TrackToolDescription = `Record that a habit was performed.
-Required:
-- habit_id: Habit UUID, deep link, or config key
+Use lunatask://habits resource to discover valid habit IDs and config keys.
+Tracks for today by default. Idempotent—re-tracking the same date has no effect.`
-Optional:
-- performed_on: Date performed (YYYY-MM-DD or natural language, default: today)
-
-Use the lunatask://habits resource to discover valid habit IDs.`
+// TrackToolAnnotations returns hints about tool behavior.
+func TrackToolAnnotations() *mcp.ToolAnnotations {
+ return &mcp.ToolAnnotations{
+ IdempotentHint: true,
+ }
+}
// TrackInput is the input schema for tracking a habit.
type TrackInput struct {
- HabitID string `json:"habit_id" jsonschema:"required"`
- PerformedOn *string `json:"performed_on,omitempty"`
+ HabitID string `json:"habit_id" jsonschema:"Habit UUID, lunatask:// deep link, or config key"`
+ PerformedOn *string `json:"performed_on,omitempty" jsonschema:"Date performed (strtotime syntax, default: today)"`
}
// TrackOutput is the output schema for tracking a habit.
@@ -18,23 +18,25 @@ import (
const ToolName = "add_timeline_note"
// ToolDescription describes the add timeline note tool for LLMs.
-const ToolDescription = `Adds a timeline note to a person's memory timeline in Lunatask.
+const ToolDescription = `Add a note to a person's memory timeline.
-Required:
-- person_id: Person UUID or lunatask://person/... deep link
+Append-only—each call creates a new timeline entry. Great for tracking
+interactions, meetings, or memorable moments with someone.`
-Optional:
-- content: Markdown content describing the interaction
-- date: Date of interaction (YYYY-MM-DD or natural language, default: today)
+// ToolAnnotations returns hints about tool behavior.
+func ToolAnnotations() *mcp.ToolAnnotations {
+ return &mcp.ToolAnnotations{
+ DestructiveHint: ptr(false),
+ }
+}
-This is append-only — adds to the person's memory timeline.
-Great for tracking when you last interacted with someone.`
+func ptr[T any](v T) *T { return &v }
// Input is the input schema for adding a timeline note.
type Input struct {
- PersonID string `json:"person_id" jsonschema:"required"`
- Content *string `json:"content,omitempty"`
- Date *string `json:"date,omitempty"`
+ PersonID string `json:"person_id" jsonschema:"Person UUID or lunatask:// deep link"`
+ Content *string `json:"content,omitempty" jsonschema:"Markdown content describing the interaction"`
+ Date *string `json:"date,omitempty" jsonschema:"Date of interaction (strtotime syntax, default: today)"`
}
// Output is the output schema for adding a timeline note.
@@ -18,25 +18,22 @@ import (
const ToolName = "get_timestamp"
// ToolDescription describes the tool for LLMs.
-const ToolDescription = `Parses natural language date/time expressions into RFC3339 timestamps.
-Uses PHP strtotime syntax.
-
-Accepts expressions like:
-- "today", "tomorrow", "yesterday"
-- "next Monday", "last Friday"
-- "next week", "last month", "next year"
-- "+3 days", "-1 week", "3 days" (use +/- prefix, not "ago" or "in")
-- Compound: "next Friday +2 weeks"
-- "March 5", "January 15 2024"
-- "2024-01-15"
-- "" (empty string returns today)
-
-Returns the timestamp in RFC3339 format (e.g., "2024-01-15T00:00:00Z").
-Use this tool to convert human-readable dates before passing them to task/habit tools.`
+const ToolDescription = `Parse natural language dates into RFC3339 timestamps.
+
+Use before passing dates to other tools. Supports PHP strtotime syntax:
+relative (+3 days, next Monday), named (March 5), ISO (2024-01-15).
+Empty input returns today.`
+
+// ToolAnnotations returns hints about tool behavior.
+func ToolAnnotations() *mcp.ToolAnnotations {
+ return &mcp.ToolAnnotations{
+ ReadOnlyHint: true,
+ }
+}
// Input is the input schema for the timestamp tool.
type Input struct {
- Date string `json:"date"`
+ Date string `json:"date" jsonschema:"Date/time expression to parse (empty = today)"`
}
// Output is the output schema for the timestamp tool.