AGENTS.md
This file provides guidance to AI coding agents when working with code in this repository.
Build Commands
All commands use just task runner:
- Development workflow:
just(runs fmt, lint, staticcheck, test, vuln, reuse) - Format code:
just fmt(uses gofumpt) - Lint:
just lint(uses golangci-lint) - Static analysis:
just staticcheck(uses staticcheck) - Test:
just test(runs all tests with verbose output) - Single test:
go test -v ./path/to/package -run TestName - Vulnerability check:
just vuln(uses govulncheck) - License compliance:
just reuse(REUSE specification) - Build:
just build(outputs to./lunatask-mcp-server, supports GOOS/GOARCH env vars) - Run:
just run(builds and runs with version injection) - Compress binary:
just pack(requires upx, run after build) - Clean:
just clean(removes binary) orjust clean-all(removes binary and config)
Version is injected at build time via git describe, so always use just build or just run rather than plain go build.
Licensing
This project is REUSE compliant. All files must have SPDX headers:
- Code files (
.go):SPDX-License-Identifier: AGPL-3.0-or-later - Documentation (
.md,.toml):SPDX-License-Identifier: CC0-1.0 - Copyright:
SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
Always include both lines in new files.
Architecture Overview
This is an MCP (Model Context Protocol) server that exposes Lunatask API functionality to LLMs. The codebase uses a clean three-layer architecture:
Layer 1: Main Entry Point (cmd/lunatask-mcp-server.go)
- Loads TOML configuration from
config.toml(or custom path via-c/--config) - Creates and configures the MCP SSE server (Server-Sent Events transport)
- Implements Provider interfaces (
AreaProvider,GoalProvider,HabitProvider) that wrap config structs - Registers all MCP tools with their handlers
- Validates configuration on startup (areas/goals must have names and IDs, timezone must be valid)
Layer 2: Tools Package (tools/)
- Bridges MCP tool calls to Lunatask API client
- Handles MCP request/response formatting
- Performs pre-validation and argument transformation (e.g., string priorities → integers)
- Consumes Provider interfaces to access configuration without tight coupling
- Contains handlers for: timestamp parsing, area/goal listing, task CRUD, habit tracking
Layer 3: Lunatask Package (lunatask/)
- Low-level HTTP client for Lunatask REST API (
https://api.lunatask.app/v1) - Defines request/response types with JSON tags
- Uses
go-playground/validatorfor request validation - Contains API methods for tasks (
CreateTask,UpdateTask,DeleteTask) and habits (TrackHabitActivity)
Key Design Patterns
Provider Interfaces: The tools package defines AreaProvider, GoalProvider, and HabitProvider interfaces. The main package's config structs implement these, allowing tools to access config data without importing main or knowing config structure details.
String-to-Integer Mapping: MCP tools accept human-readable strings that get translated to API integers:
- Priority:
"lowest"=-2,"low"=-1,"neutral"=0,"high"=1,"highest"=2 - Eisenhower matrix:
"uncategorised"=0,"both urgent and important"=1,"urgent, but not important"=2,"important, but not urgent"=3,"neither urgent nor important"=4
Natural Language Date Parsing: Uses github.com/ijt/go-anytime with timezone support. The get_timestamp tool parses expressions like "tomorrow at 3pm" into RFC3339 timestamps. Timezone is configured in config.toml using IANA/Olson format (e.g., America/New_York).
Configuration System
On first run, the server generates config.toml with placeholder values and exits. Users must:
- Add their Lunatask API access token
- Configure areas with real IDs from Lunatask (areas can have nested goals)
- Configure habits with real IDs
- Set timezone in IANA format (e.g.,
America/New_York, notEST) - Optionally customize server host/port (defaults to
localhost:8080)
The config uses TOML (not JSON) per project philosophy. Example structure:
access_token = "your-token"
timezone = "America/New_York"
[server]
host = "localhost"
port = 8080
[[area]]
name = "Work"
id = "uuid-here"
[[area.goal]]
name = "Q1 Project"
id = "uuid-here"
[[habit]]
name = "Exercise"
id = "uuid-here"
Critical Implementation Details
Goal Validation in Task Operations
When creating or updating tasks with a goal_id, the tools package validates that the goal belongs to the specified area. This prevents associating tasks with goals from different areas, which Lunatask may reject or handle incorrectly.
Update vs Create Semantics
UpdateTask reuses CreateTaskRequest type but has different semantics:
- Only provided fields are updated (partial updates)
- Empty strings for text fields (name, note, scheduled_on) clear existing values
- Empty strings for enum fields (status, motivation) also clear values
- The tools handler only sends fields present in MCP request arguments
Empty Package Files
lunatask/notes.go, lunatask/people.go, and lunatask/timeline.go exist but are empty. These are placeholders for future Lunatask API features. Don't remove them without checking if they're imported elsewhere.
MCP Tool Workflow Dependencies
Several tools must be called in specific order:
list_areas_and_goals→ provides IDs forcreate_task/update_taskget_timestamp→ provides formatted timestamps for task scheduling and habit trackinglist_habits_and_activities→ provides IDs fortrack_habit_activity
The MCP tool descriptions document these workflows, and the LLM system prompts (in README.md) reinforce the pattern.
Validation Happens at Two Levels
- Tools layer: Type checking, area/goal relationship validation, string-to-int translation
- Lunatask layer: go-playground/validator tags enforce API constraints (UUID format, string lengths, numeric ranges, enum values)
This prevents invalid requests from reaching the API and provides better error messages.
Version Injection
The version variable in main is set via ldflags during build: -ldflags "-X main.version=...". The justfile extracts version from git describe --long. Don't hardcode version strings; if version is empty, it displays a helpful message about using just build.
Testing Considerations
- Tests should use
go test -v ./...to run all packages - Mock the HTTP client in lunatask package tests (set
Client.HTTPClientto custom*http.Client) - Mock Provider interfaces in tools package tests
- Timezone-dependent tests should explicitly set timezone in config
- Natural language date parsing is non-deterministic (relative to "now"), so test with absolute dates or mock time
Related Documentation
The README.md contains extensive documentation about:
- MCP tool descriptions and parameters (authoritative reference for tool behavior)
- Example LLM system prompts showing real-world usage patterns
- User-specific workflow rules (area workflows, estimate requirements, status defaults)
- Contribution workflow using pr.pico.sh (SSH-based patches, no GitHub account needed)
When modifying tools, ensure MCP descriptions in cmd/lunatask-mcp-server.go stay synchronized with actual behavior.