Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"
"time"

"github.com/charmbracelet/lipgloss"
"github.com/cloudwego/eino/schema"
"github.com/mark3labs/mcphost/internal/agent"
"github.com/mark3labs/mcphost/internal/config"
Expand Down Expand Up @@ -199,6 +200,7 @@ func initConfig() {
viper.Set("hooks", hooksConfig)
}
}

}

// loadConfigWithEnvSubstitution loads a config file with environment variable substitution
Expand All @@ -222,13 +224,42 @@ func loadConfigWithEnvSubstitution(configPath string) error {
configType = "json"
}

config.SetConfigPath(configPath)

// Use viper to parse the processed content
viper.SetConfigType(configType)
return viper.ReadConfig(strings.NewReader(processedContent))
}

func configToUiTheme(theme config.Theme) ui.Theme {
return ui.Theme{
Primary: lipgloss.AdaptiveColor(theme.Primary),
Secondary: lipgloss.AdaptiveColor(theme.Secondary),
Success: lipgloss.AdaptiveColor(theme.Success),
Warning: lipgloss.AdaptiveColor(theme.Warning),
Error: lipgloss.AdaptiveColor(theme.Error),
Info: lipgloss.AdaptiveColor(theme.Info),
Text: lipgloss.AdaptiveColor(theme.Text),
Muted: lipgloss.AdaptiveColor(theme.Muted),
VeryMuted: lipgloss.AdaptiveColor(theme.VeryMuted),
Background: lipgloss.AdaptiveColor(theme.Background),
Border: lipgloss.AdaptiveColor(theme.Border),
MutedBorder: lipgloss.AdaptiveColor(theme.MutedBorder),
System: lipgloss.AdaptiveColor(theme.System),
Tool: lipgloss.AdaptiveColor(theme.Tool),
Accent: lipgloss.AdaptiveColor(theme.Accent),
Highlight: lipgloss.AdaptiveColor(theme.Highlight),
}
}

func init() {
cobra.OnInitialize(initConfig)
var theme config.Theme
err := config.FilepathOr("theme", &theme)
if err == nil && viper.InConfig("theme") {
uiTheme := configToUiTheme(theme)
ui.SetTheme(uiTheme)
}

rootCmd.PersistentFlags().
StringVar(&configFile, "config", "", "config file (default is $HOME/.mcp.json)")
Expand Down
98 changes: 98 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"os"
"path/filepath"
"strings"

"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)

// MCPServerConfig represents configuration for an MCP server
Expand Down Expand Up @@ -97,6 +100,45 @@ func (s *MCPServerConfig) UnmarshalJSON(data []byte) error {
return nil
}

type AdaptiveColor struct {
Light string `json:"light,omitempty" yaml:"light,omitempty"`
Dark string `json:"dark,omitempty" yaml:"dark,omitempty"`
}

type Theme struct {
Primary AdaptiveColor `json:"primary" yaml:"primary"`
Secondary AdaptiveColor `json:"secondary" yaml:"secondary"`
Success AdaptiveColor `json:"success" yaml:"success"`
Warning AdaptiveColor `json:"warning" yaml:"warning"`
Error AdaptiveColor `json:"error" yaml:"error"`
Info AdaptiveColor `json:"info" yaml:"info"`
Text AdaptiveColor `json:"text" yaml:"text"`
Muted AdaptiveColor `json:"muted" yaml:"muted"`
VeryMuted AdaptiveColor `json:"very-muted" yaml:"very-muted"`
Background AdaptiveColor `json:"background" yaml:"background"`
Border AdaptiveColor `json:"border" yaml:"border"`
MutedBorder AdaptiveColor `json:"muted-border" yaml:"muted-border"`
System AdaptiveColor `json:"system" yaml:"system"`
Tool AdaptiveColor `json:"tool" yaml:"tool"`
Accent AdaptiveColor `json:"accent" yaml:"accent"`
Highlight AdaptiveColor `json:"highlight" yaml:"highlight"`
}

type MarkdownTheme struct {
Text AdaptiveColor `json:"text" yaml:"text"`
Muted AdaptiveColor `json:"muted" yaml:"muted"`
Heading AdaptiveColor `json:"heading" yaml:"heading"`
Emph AdaptiveColor `json:"emph" yaml:"emph"`
Strong AdaptiveColor `json:"strong" yaml:"strong"`
Link AdaptiveColor `json:"link" yaml:"link"`
Code AdaptiveColor `json:"code" yaml:"code"`
Error AdaptiveColor `json:"error" yaml:"error"`
Keyword AdaptiveColor `json:"keyword" yaml:"keyword"`
String AdaptiveColor `json:"string" yaml:"string"`
Number AdaptiveColor `json:"number" yaml:"number"`
Comment AdaptiveColor `json:"comment" yaml:"comment"`
}

// Config represents the application configuration
type Config struct {
MCPServers map[string]MCPServerConfig `json:"mcpServers" yaml:"mcpServers"`
Expand All @@ -110,6 +152,8 @@ type Config struct {
Prompt string `json:"prompt,omitempty" yaml:"prompt,omitempty"`
NoExit bool `json:"no-exit,omitempty" yaml:"no-exit,omitempty"`
Stream *bool `json:"stream,omitempty" yaml:"stream,omitempty"`
Theme any `json:"theme" yaml:"theme"`
MarkdownTheme any `json:"markdown-theme" yaml:"markdown-theme"`

// Model generation parameters
MaxTokens int `json:"max-tokens,omitempty" yaml:"max-tokens,omitempty"`
Expand Down Expand Up @@ -333,3 +377,57 @@ mcpServers:

return nil
}

func FilepathOr[T any](key string, value *T) error {
var field any
err := viper.UnmarshalKey(key, &field)
if err != nil {
value = nil
return err
}
switch f := field.(type) {
case string:
{
absPath := f
if strings.HasPrefix(absPath, "~/") {
home, err := os.UserHomeDir()
if err != nil {
return err
}
filepath.Join(home, absPath[2:])
}
if !filepath.IsAbs(absPath) {
// base := GetConfigPath()
base := configPath
if base == "" {
fmt.Fprintf(os.Stderr, "unable to build relative path to config.")
os.Exit(1)
}
absPath = filepath.Join(filepath.Dir(base), absPath)
}
b, err := os.ReadFile(absPath)
if err != nil {
fmt.Fprintf(os.Stderr, "%q", err)
os.Exit(1)
}
if filepath.Ext(absPath) == ".json" {
return json.Unmarshal(b, value)
}

if filepath.Ext(absPath) == ".yaml" {
return yaml.Unmarshal(b, value)
}
}
case map[string]any:
return viper.UnmarshalKey(key, value)
default:
return fmt.Errorf("invalid type for field %q", key)
}
return nil
}

var configPath string

func SetConfigPath(path string) {
configPath = path
}
45 changes: 37 additions & 8 deletions internal/ui/styles.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/glamour/ansi"
"github.com/charmbracelet/lipgloss"
"github.com/mark3labs/mcphost/internal/config"
"github.com/spf13/viper"
)

const defaultMargin = 1
Expand All @@ -29,17 +31,42 @@ func GetMarkdownRenderer(width int) *glamour.TermRenderer {

// generateMarkdownStyleConfig creates an ansi.StyleConfig for markdown rendering
func generateMarkdownStyleConfig() ansi.StyleConfig {
// Define adaptive colors based on terminal background

var textColor, mutedColor string
if lipgloss.HasDarkBackground() {
var headingColor, emphColor, strongColor, linkColor, codeColor, errorColor, keywordColor, stringColor, numberColor, commentColor string
var mdTheme config.MarkdownTheme

err := config.FilepathOr("markdown-theme", &mdTheme)
fromConfig := err == nil && viper.InConfig("markdown-theme")
if fromConfig && lipgloss.HasDarkBackground() {
textColor = mdTheme.Text.Light
mutedColor = mdTheme.Muted.Light
headingColor = mdTheme.Heading.Light
emphColor = mdTheme.Emph.Light
strongColor = mdTheme.Strong.Light
linkColor = mdTheme.Link.Light
codeColor = mdTheme.Code.Light
errorColor = mdTheme.Error.Light
keywordColor = mdTheme.Keyword.Light
stringColor = mdTheme.String.Light
numberColor = mdTheme.Number.Light
commentColor = mdTheme.Comment.Light
} else if fromConfig {
textColor = mdTheme.Text.Dark
mutedColor = mdTheme.Muted.Dark
headingColor = mdTheme.Heading.Dark
emphColor = mdTheme.Emph.Dark
strongColor = mdTheme.Strong.Dark
linkColor = mdTheme.Link.Dark
codeColor = mdTheme.Code.Dark
errorColor = mdTheme.Error.Dark
keywordColor = mdTheme.Keyword.Dark
stringColor = mdTheme.String.Dark
numberColor = mdTheme.Number.Dark
commentColor = mdTheme.Comment.Dark
} else if lipgloss.HasDarkBackground() {
textColor = "#F9FAFB" // Light text for dark backgrounds
mutedColor = "#9CA3AF" // Light muted for dark backgrounds
} else {
textColor = "#1F2937" // Dark text for light backgrounds
mutedColor = "#6B7280" // Dark muted for light backgrounds
}
var headingColor, emphColor, strongColor, linkColor, codeColor, errorColor, keywordColor, stringColor, numberColor, commentColor string
if lipgloss.HasDarkBackground() {
// Dark background colors
headingColor = "#22D3EE" // Cyan
emphColor = "#FDE047" // Yellow
Expand All @@ -52,6 +79,8 @@ func generateMarkdownStyleConfig() ansi.StyleConfig {
numberColor = "#FBBF24" // Orange
commentColor = "#9CA3AF" // Muted gray
} else {
textColor = "#1F2937" // Dark text for light backgrounds
mutedColor = "#6B7280" // Dark muted for light backgrounds
// Light background colors
headingColor = "#0891B2" // Dark cyan
emphColor = "#D97706" // Orange
Expand Down
Loading