@@ -11,6 +11,13 @@ import (
"git.secluded.site/lune/internal/client"
"git.secluded.site/lune/internal/config"
+ "git.secluded.site/lune/internal/mcp/tools/habit"
+ "git.secluded.site/lune/internal/mcp/tools/journal"
+ "git.secluded.site/lune/internal/mcp/tools/note"
+ "git.secluded.site/lune/internal/mcp/tools/person"
+ "git.secluded.site/lune/internal/mcp/tools/task"
+ "git.secluded.site/lune/internal/mcp/tools/timestamp"
+ mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/spf13/cobra"
)
@@ -22,14 +29,18 @@ const (
)
var (
- errUnknownTransport = errors.New("unknown transport; use stdio, sse, or http")
- errNoToken = errors.New("no access token; run 'lune init' first")
+ errUnknownTransport = errors.New("unknown transport; use stdio, sse, or http")
+ errNoToken = errors.New("no access token; run 'lune init' first")
+ errMutuallyExclusive = errors.New("--enabled-tools and --disabled-tools are mutually exclusive")
+ errUnknownTool = errors.New("unknown tool name")
)
var (
- transport string
- host string
- port int
+ transport string
+ host string
+ port int
+ enabledTools []string
+ disabledTools []string
)
// Cmd is the mcp command for starting the MCP server.
@@ -58,14 +69,26 @@ func init() {
"Transport type: stdio, sse, http (default: stdio or config)")
Cmd.Flags().StringVar(&host, "host", "", "Server host (for sse/http)")
Cmd.Flags().IntVar(&port, "port", 0, "Server port (for sse/http)")
+ Cmd.Flags().StringSliceVar(&enabledTools, "enabled-tools", nil,
+ "Enable only these tools (comma-separated); overrides config")
+ Cmd.Flags().StringSliceVar(&disabledTools, "disabled-tools", nil,
+ "Disable these tools (comma-separated); overrides config")
}
func runMCP(cmd *cobra.Command, _ []string) error {
+ if len(enabledTools) > 0 && len(disabledTools) > 0 {
+ return errMutuallyExclusive
+ }
+
cfg, err := loadConfig()
if err != nil {
return err
}
+ if err := resolveTools(cfg); err != nil {
+ return err
+ }
+
token, err := client.GetToken()
if err != nil {
return fmt.Errorf("getting access token: %w", err)
@@ -77,9 +100,11 @@ func runMCP(cmd *cobra.Command, _ []string) error {
mcpServer := newMCPServer(cfg, token)
- effectiveTransport := resolveTransport(cfg)
+ return runTransport(cmd, mcpServer, cfg)
+}
- switch effectiveTransport {
+func runTransport(cmd *cobra.Command, mcpServer *mcpsdk.Server, cfg *config.Config) error {
+ switch resolveTransport(cfg) {
case TransportStdio:
return runStdio(mcpServer)
case TransportSSE:
@@ -129,3 +154,78 @@ func resolvePort(cfg *config.Config) int {
return cfg.MCP.Port
}
+
+// validToolNames maps MCP tool names to their ToolsConfig field setters.
+var validToolNames = map[string]func(*config.ToolsConfig, bool){
+ timestamp.ToolName: func(t *config.ToolsConfig, v bool) { t.GetTimestamp = v },
+ task.CreateToolName: func(t *config.ToolsConfig, v bool) { t.CreateTask = v },
+ task.UpdateToolName: func(t *config.ToolsConfig, v bool) { t.UpdateTask = v },
+ task.DeleteToolName: func(t *config.ToolsConfig, v bool) { t.DeleteTask = v },
+ task.ListToolName: func(t *config.ToolsConfig, v bool) { t.ListTasks = v },
+ task.ShowToolName: func(t *config.ToolsConfig, v bool) { t.ShowTask = v },
+ note.CreateToolName: func(t *config.ToolsConfig, v bool) { t.CreateNote = v },
+ note.UpdateToolName: func(t *config.ToolsConfig, v bool) { t.UpdateNote = v },
+ note.DeleteToolName: func(t *config.ToolsConfig, v bool) { t.DeleteNote = v },
+ note.ListToolName: func(t *config.ToolsConfig, v bool) { t.ListNotes = v },
+ person.CreateToolName: func(t *config.ToolsConfig, v bool) { t.CreatePerson = v },
+ person.UpdateToolName: func(t *config.ToolsConfig, v bool) { t.UpdatePerson = v },
+ person.DeleteToolName: func(t *config.ToolsConfig, v bool) { t.DeletePerson = v },
+ person.ListToolName: func(t *config.ToolsConfig, v bool) { t.ListPeople = v },
+ person.TimelineToolName: func(t *config.ToolsConfig, v bool) { t.PersonTimeline = v },
+ habit.TrackToolName: func(t *config.ToolsConfig, v bool) { t.TrackHabit = v },
+ journal.CreateToolName: func(t *config.ToolsConfig, v bool) { t.CreateJournal = v },
+ // TODO: Add these once implemented:
+ // - show_note (ShowNote)
+ // - show_person (ShowPerson)
+ // - list_habits (ListHabits)
+ // - list_areas (ListAreas) - needs config field
+ // - list_goals (ListGoals) - needs config field
+}
+
+// resolveTools modifies cfg.MCP.Tools based on CLI flags.
+// If --enabled-tools is set, only those tools are enabled.
+// If --disabled-tools is set, all tools except those are enabled.
+// If neither is set, config values are used unchanged.
+func resolveTools(cfg *config.Config) error {
+ if len(enabledTools) > 0 {
+ // Validate all tool names first
+ for _, name := range enabledTools {
+ if _, ok := validToolNames[name]; !ok {
+ return fmt.Errorf("%w: %s", errUnknownTool, name)
+ }
+ }
+
+ // Start with everything disabled
+ cfg.MCP.Tools = config.ToolsConfig{}
+
+ // Enable only specified tools
+ for _, name := range enabledTools {
+ validToolNames[name](&cfg.MCP.Tools, true)
+ }
+
+ return nil
+ }
+
+ if len(disabledTools) > 0 {
+ // Validate all tool names first
+ for _, name := range disabledTools {
+ if _, ok := validToolNames[name]; !ok {
+ return fmt.Errorf("%w: %s", errUnknownTool, name)
+ }
+ }
+
+ // Start with everything enabled
+ cfg.MCP.Tools = config.ToolsConfig{}
+ cfg.MCP.Tools.ApplyDefaults()
+
+ // Disable specified tools
+ for _, name := range disabledTools {
+ validToolNames[name](&cfg.MCP.Tools, false)
+ }
+
+ return nil
+ }
+
+ // Neither flag set, use config as-is
+ return nil
+}