package config import ( "fmt" "os" "time" "gopkg.in/yaml.v3" ) type Config struct { Server ServerConfig `yaml:"server"` Vault VaultConfig `yaml:"vault"` Git GitConfig `yaml:"git"` Index IndexConfig `yaml:"index"` Search SearchConfig `yaml:"search"` Cache CacheConfig `yaml:"cache"` Logging LoggingConfig `yaml:"logging"` Performance PerformanceConfig `yaml:"performance"` Security SecurityConfig `yaml:"security"` } type ServerConfig struct { Name string `yaml:"name"` Version string `yaml:"version"` Transport string `yaml:"transport"` // "stdio" or "sse" } type VaultConfig struct { Path string `yaml:"path"` ExcludePaths []string `yaml:"exclude_paths"` WatchChanges bool `yaml:"watch_changes"` } type GitConfig struct { Enabled bool `yaml:"enabled"` AutoPull bool `yaml:"auto_pull"` AutoPush bool `yaml:"auto_push"` AutoCommit bool `yaml:"auto_commit"` CommitPrefix string `yaml:"commit_prefix"` Remote string `yaml:"remote"` Branch string `yaml:"branch"` } type IndexConfig struct { BuildOnStartup bool `yaml:"build_on_startup"` RebuildInterval time.Duration `yaml:"rebuild_interval"` MaxNotesInMemory int `yaml:"max_notes_in_memory"` } type SearchConfig struct { MaxResults int `yaml:"max_results"` ContextLines int `yaml:"context_lines"` CaseSensitive bool `yaml:"case_sensitive"` } type CacheConfig struct { Enabled bool `yaml:"enabled"` MaxSize int `yaml:"max_size"` TTL time.Duration `yaml:"ttl"` } type LoggingConfig struct { Level string `yaml:"level"` // debug, info, warn, error Format string `yaml:"format"` // text or json File string `yaml:"file"` } type PerformanceConfig struct { MaxConcurrentOperations int `yaml:"max_concurrent_operations"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` } type RateLimitConfig struct { Enabled bool `yaml:"enabled"` RequestsPerSecond int `yaml:"requests_per_second"` Burst int `yaml:"burst"` } type SecurityConfig struct { RateLimit RateLimitConfig `yaml:"rate_limit"` MaxFileSize string `yaml:"max_file_size"` } func NewDefaultConfig() *Config { return &Config{ Server: ServerConfig{ Name: "obsidian-mcp", Version: "1.0.0", Transport: "stdio", }, Vault: VaultConfig{ Path: "", ExcludePaths: []string{ ".obsidian/", ".git/", ".trash/", }, WatchChanges: true, }, Git: GitConfig{ Enabled: true, AutoPull: true, AutoPush: false, AutoCommit: true, CommitPrefix: "[MCP]", Remote: "origin", Branch: "main", }, Index: IndexConfig{ BuildOnStartup: true, RebuildInterval: 5 * time.Minute, MaxNotesInMemory: 10000, }, Search: SearchConfig{ MaxResults: 50, ContextLines: 2, CaseSensitive: false, }, Cache: CacheConfig{ Enabled: true, MaxSize: 1000, TTL: 5 * time.Minute, }, Logging: LoggingConfig{ Level: "info", Format: "text", File: "", }, Performance: PerformanceConfig{ MaxConcurrentOperations: 10, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, }, Security: SecurityConfig{ RateLimit: RateLimitConfig{ Enabled: true, RequestsPerSecond: 10, Burst: 20, }, MaxFileSize: "10MB", }, } } func LoadConfig(path string) (*Config, error) { config := NewDefaultConfig() if path == "" { return config, nil } data, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return config, nil } return nil, fmt.Errorf("failed to read config file: %w", err) } if err := yaml.Unmarshal(data, config); err != nil { return nil, fmt.Errorf("failed to parse config file: %w", err) } if err := config.Validate(); err != nil { return nil, fmt.Errorf("invalid config: %w", err) } return config, nil } func LoadConfigFromEnv() (*Config, error) { config := NewDefaultConfig() if vaultPath := os.Getenv("VAULT_PATH"); vaultPath != "" { config.Vault.Path = vaultPath } if logLevel := os.Getenv("LOG_LEVEL"); logLevel != "" { config.Logging.Level = logLevel } if gitEnabled := os.Getenv("GIT_ENABLED"); gitEnabled == "false" { config.Git.Enabled = false } if gitAutoPush := os.Getenv("GIT_AUTO_PUSH"); gitAutoPush == "true" { config.Git.AutoPush = true } if cacheEnabled := os.Getenv("CACHE_ENABLED"); cacheEnabled == "false" { config.Cache.Enabled = false } return config, config.Validate() } func (c *Config) Validate() error { if c.Vault.Path == "" { return fmt.Errorf("vault path is required") } if c.Server.Transport != "stdio" && c.Server.Transport != "sse" { return fmt.Errorf("invalid transport: %s", c.Server.Transport) } if c.Search.MaxResults <= 0 { return fmt.Errorf("max results must be positive") } if c.Cache.MaxSize <= 0 { return fmt.Errorf("cache max size must be positive") } validLogLevels := map[string]bool{ "debug": true, "info": true, "warn": true, "error": true, } if !validLogLevels[c.Logging.Level] { return fmt.Errorf("invalid log level: %s", c.Logging.Level) } return nil } func (c *Config) SaveToFile(path string) error { data, err := yaml.Marshal(c) if err != nil { return fmt.Errorf("failed to marshal config: %w", err) } if err := os.WriteFile(path, data, 0644); err != nil { return fmt.Errorf("failed to write config file: %w", err) } return nil }