Added MCP support

This commit is contained in:
Pavel Pivovarov
2025-12-15 15:15:40 +11:00
parent 272d223f73
commit 25e263b7a6
9 changed files with 635 additions and 201 deletions

View File

@@ -5,29 +5,35 @@ import (
"os"
"path/filepath"
"gopkg.in/ini.v1"
"gopkg.in/yaml.v3"
)
// Config holds the application configuration
type Config struct {
LLM LLMConfig
SearXNG SearXNGConfig
// LLM Configuration
APIURL string `yaml:"api_url"`
Model string `yaml:"model"`
ContextSize int `yaml:"context_size"`
APIKey string `yaml:"api_key"`
// SearXNG Configuration
SearXNGURL string `yaml:"searxng_url"`
// System Prompt
Prompt string `yaml:"prompt"`
// MCP Server Configuration
MCPServers map[string]MCPServer `yaml:"mcp_servers"`
}
// LLMConfig holds LLM API configuration
type LLMConfig struct {
APIURL string
Model string
ContextSize int
APIKey string
// MCPServer represents a single MCP server configuration (stdio transport only)
type MCPServer struct {
Command string `yaml:"command"`
Args []string `yaml:"args,omitempty"`
Env map[string]string `yaml:"env,omitempty"`
}
// SearXNGConfig holds SearXNG configuration
type SearXNGConfig struct {
URL string
}
// Load reads and parses the INI configuration file from ~/.config/tell-me.ini
// Load reads and parses the YAML configuration file from ~/.config/tell-me.yaml
func Load() (*Config, error) {
// Get home directory
homeDir, err := os.UserHomeDir()
@@ -36,47 +42,43 @@ func Load() (*Config, error) {
}
// Build config path
configPath := filepath.Join(homeDir, ".config", "tell-me.ini")
configPath := filepath.Join(homeDir, ".config", "tell-me.yaml")
// Check if config file exists
if _, err := os.Stat(configPath); os.IsNotExist(err) {
return nil, fmt.Errorf("config file not found at %s. Please create it from tell-me.ini.example", configPath)
return nil, fmt.Errorf("config file not found at %s. Please create it from tell-me.yaml.example", configPath)
}
// Load INI file
cfg, err := ini.Load(configPath)
// Read YAML file
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to load config file: %w", err)
return nil, fmt.Errorf("failed to read config file: %w", err)
}
// Parse LLM section
llmSection := cfg.Section("llm")
llmConfig := LLMConfig{
APIURL: llmSection.Key("api_url").String(),
Model: llmSection.Key("model").String(),
ContextSize: llmSection.Key("context_size").MustInt(16000),
APIKey: llmSection.Key("api_key").String(),
// Parse YAML
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}
// Parse SearXNG section
searxngSection := cfg.Section("searxng")
searxngConfig := SearXNGConfig{
URL: searxngSection.Key("url").String(),
// Set defaults
if cfg.ContextSize == 0 {
cfg.ContextSize = 16000
}
// Validate required fields
if llmConfig.APIURL == "" {
return nil, fmt.Errorf("llm.api_url is required in config")
if cfg.APIURL == "" {
return nil, fmt.Errorf("api_url is required in config")
}
if llmConfig.Model == "" {
return nil, fmt.Errorf("llm.model is required in config")
if cfg.Model == "" {
return nil, fmt.Errorf("model is required in config")
}
if searxngConfig.URL == "" {
return nil, fmt.Errorf("searxng.url is required in config")
if cfg.SearXNGURL == "" {
return nil, fmt.Errorf("searxng_url is required in config")
}
if cfg.Prompt == "" {
return nil, fmt.Errorf("prompt is required in config")
}
return &Config{
LLM: llmConfig,
SearXNG: searxngConfig,
}, nil
return &cfg, nil
}