Architecture & Design
This document provides a comprehensive guide to the codebase architecture and core design decisions. The project uses a clean three-layer architecture that separates concerns between MCP server handling, business logic, and API client operations.
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).
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.