Skip to content
Merged
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -1035,7 +1035,7 @@ Legend: `shipped` ≥95% checked · `in-flight` 1–94% · `drafted` 0% · `—`
| [105-agent-scope-hardening](./specs/105-agent-scope-hardening/) | `in-flight` | 94/113 (83%) |
| [106-security-residual-fixes](./specs/106-security-residual-fixes/) | `shipped` | 18/19 (95%) |
| [107-server-edition-sso-hardening](./specs/107-server-edition-sso-hardening/) | `shipped` | 126/126 (100%) |
| [108-profiles-v3](./specs/108-profiles-v3/) | `shipped` | 185/186 (99%) |
| [108-profiles-v3](./specs/108-profiles-v3/) | `shipped` | 189/190 (99%) |
| [109-ux-navigation-consistency](./specs/109-ux-navigation-consistency/) | `shipped` | 233/234 (100%) |
| [110-catalog-popularity](./specs/110-catalog-popularity/) | `in-flight` | 19/23 (83%) |
| [112-client-header-forwarding](./specs/112-client-header-forwarding/) | `shipped` | 38/40 (95%) |
24 changes: 10 additions & 14 deletions cmd/mcpproxy/auth_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -105,19 +104,19 @@ func init() {
authLoginCmd.Flags().BoolVar(&authAll, "all", false, "Authenticate all servers that require OAuth")
authLoginCmd.Flags().BoolVar(&authForce, "force", false, "Skip confirmation prompt when using --all")
authLoginCmd.Flags().StringVarP(&authLogLevel, "log-level", "l", "info", "Log level (trace, debug, info, warn, error)")
authLoginCmd.Flags().StringVarP(&authConfigPath, "config", "c", "", "Path to MCP configuration file (default: ~/.mcpproxy/mcp_config.json)")
addConfigFlag(authLoginCmd.Flags(), &authConfigPath, "Path to MCP configuration file (default: ~/.mcpproxy/mcp_config.json)")
authLoginCmd.Flags().DurationVar(&authTimeout, "timeout", 5*time.Minute, "Authentication timeout")

// Define flags for auth status command
authStatusCmd.Flags().StringVarP(&authServerName, "server", "s", "", "Server name to check status for (optional)")
authStatusCmd.Flags().StringVarP(&authLogLevel, "log-level", "l", "info", "Log level (trace, debug, info, warn, error)")
authStatusCmd.Flags().StringVarP(&authConfigPath, "config", "c", "", "Path to MCP configuration file (default: ~/.mcpproxy/mcp_config.json)")
addConfigFlag(authStatusCmd.Flags(), &authConfigPath, "Path to MCP configuration file (default: ~/.mcpproxy/mcp_config.json)")
authStatusCmd.Flags().BoolVar(&authAll, "all", false, "Show status for all servers")

// Define flags for auth logout command
authLogoutCmd.Flags().StringVarP(&authServerName, "server", "s", "", "Server name to logout from (required)")
authLogoutCmd.Flags().StringVarP(&authLogLevel, "log-level", "l", "info", "Log level (trace, debug, info, warn, error)")
authLogoutCmd.Flags().StringVarP(&authConfigPath, "config", "c", "", "Path to MCP configuration file (default: ~/.mcpproxy/mcp_config.json)")
addConfigFlag(authLogoutCmd.Flags(), &authConfigPath, "Path to MCP configuration file (default: ~/.mcpproxy/mcp_config.json)")
authLogoutCmd.Flags().DurationVar(&authTimeout, "timeout", 30*time.Second, "Logout timeout")

// Mark required flags
Expand Down Expand Up @@ -585,20 +584,17 @@ func displayAuthStatusPretty(servers []map[string]interface{}) error {
}

func loadAuthConfig() (*config.Config, error) {
var configFile string
if authConfigPath != "" {
configFile = authConfigPath
} else {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("failed to get user home directory: %w", err)
cfgPath := resolveCLIConfigPath(authConfigPath)
if cfgPath == "" {
var err error
if cfgPath, err = defaultHomeConfigPath(); err != nil {
return nil, err
}
configFile = filepath.Join(homeDir, ".mcpproxy", "mcp_config.json")
}

globalConfig, err := config.LoadFromFile(configFile)
globalConfig, err := config.LoadFromFile(cfgPath)
if err != nil {
return nil, fmt.Errorf("failed to load config from %s: %w", configFile, err)
return nil, fmt.Errorf("failed to load config from %s: %w", cfgPath, err)
}

// Respect global --data-dir flag
Expand Down
20 changes: 7 additions & 13 deletions cmd/mcpproxy/call_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -147,7 +146,7 @@ func init() {
callToolCmd.Flags().StringVarP(&callToolName, "tool-name", "t", "", "Tool name in format server:tool_name (required)")
callToolCmd.Flags().StringVarP(&callJSONArgs, "json_args", "j", "{}", "JSON arguments for the tool (default: {})")
callToolCmd.Flags().StringVarP(&callLogLevel, "log-level", "l", "info", "Log level (trace, debug, info, warn, error)")
callToolCmd.Flags().StringVarP(&callConfigPath, "config", "c", "", "Path to MCP configuration file (default: ~/.mcpproxy/mcp_config.json)")
addConfigFlag(callToolCmd.Flags(), &callConfigPath, "Path to MCP configuration file (default: ~/.mcpproxy/mcp_config.json)")
callToolCmd.Flags().DurationVar(&callTimeout, "timeout", 30*time.Second, "Tool call timeout")
callToolCmd.Flags().StringVarP(&callOutputFormat, "output", "o", "pretty", "Output format (pretty, json)")

Expand Down Expand Up @@ -179,7 +178,7 @@ func setupToolVariantFlags(cmd *cobra.Command) {
cmd.Flags().StringVarP(&callToolName, "tool-name", "t", "", "Tool name in format server:tool_name (required)")
cmd.Flags().StringVarP(&callJSONArgs, "json_args", "j", "{}", "JSON arguments for the tool (default: {})")
cmd.Flags().StringVarP(&callLogLevel, "log-level", "l", "info", "Log level (trace, debug, info, warn, error)")
cmd.Flags().StringVarP(&callConfigPath, "config", "c", "", "Path to MCP configuration file (default: ~/.mcpproxy/mcp_config.json)")
addConfigFlag(cmd.Flags(), &callConfigPath, "Path to MCP configuration file (default: ~/.mcpproxy/mcp_config.json)")
cmd.Flags().DurationVar(&callTimeout, "timeout", 30*time.Second, "Tool call timeout")
cmd.Flags().StringVarP(&callOutputFormat, "output", "o", "pretty", "Output format (pretty, json)")

Expand Down Expand Up @@ -207,17 +206,12 @@ Example:

// loadCallConfig loads the MCP configuration file for call command
func loadCallConfig() (*config.Config, error) {
var configFilePath string

if callConfigPath != "" {
configFilePath = callConfigPath
} else {
// Use default path
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("failed to get user home directory: %w", err)
configFilePath := resolveCLIConfigPath(callConfigPath)
if configFilePath == "" {
var err error
if configFilePath, err = defaultHomeConfigPath(); err != nil {
return nil, err
}
configFilePath = filepath.Join(homeDir, ".mcpproxy", "mcp_config.json")
}

// Check if config file exists
Expand Down
2 changes: 1 addition & 1 deletion cmd/mcpproxy/catalog_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ the curated official/popular sections with no query.
'catalog search'/'catalog add' supersede 'registry search'/'registry add'.
'registry list'/'add-source'/'edit'/'remove' still manage catalog SOURCES.`,
}
cmd.PersistentFlags().StringVarP(&registryConfigPath, "config", "c", "", "Path to MCP configuration file")
addConfigFlag(cmd.PersistentFlags(), &registryConfigPath, "Path to MCP configuration file")
cmd.AddCommand(newCatalogSearchCmd(), newCatalogShowCmd(), newCatalogAddCmd())
return cmd
}
Expand Down
58 changes: 56 additions & 2 deletions cmd/mcpproxy/cli_config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package main

import (
"fmt"
"io"
"os"
"path/filepath"

"github.com/smart-mcp-proxy/mcpproxy-go/internal/config"
)
Expand All @@ -23,8 +25,14 @@ var cliDiagnosticsWriter io.Writer = os.Stderr
func loadCLIConfig(explicitPath string) (*config.Config, error) {
var cfg *config.Config
var err error
if explicitPath != "" {
cfg, err = config.LoadFromFile(explicitPath)
if path := resolveCLIConfigPath(explicitPath); path != "" {
cfg, err = config.LoadFromFile(path)
} else if dataDir != "" && !legacyConfigExists() {
// A --data-dir was given but no config file exists anywhere: use
// defaults rooted at that directory. Legacy discovery would create
// $HOME/.mcpproxy/mcp_config.json and report the HOME defaults, which
// contradicts the data dir the operator named.
cfg = config.DefaultConfig()
} else {
cfg, err = config.Load()
}
Expand All @@ -42,6 +50,52 @@ func loadCLIConfig(explicitPath string) (*config.Config, error) {
return cfg, nil
}

// resolveCLIConfigPath picks the config file a management command reads. The
// command's own --config wins, then the global -c/--config, then
// <data-dir>/mcp_config.json when --data-dir was given and that file exists.
// "" means legacy discovery (cwd, then $HOME/.mcpproxy). Every per-command
// loader goes through it so the global flags behave the same in every position
// and for every subcommand, whether or not it registers a local --config.
func resolveCLIConfigPath(local string) string {
if local != "" {
return local
}
if configFile != "" {
return configFile
}
if dataDir != "" {
p := config.GetConfigPath(dataDir)
if _, err := os.Stat(p); err == nil {
return p
}
}
return ""
}

// legacyConfigExists reports whether the loader's discovery locations (cwd,
// then $HOME/.mcpproxy) already hold a config file.
func legacyConfigExists() bool {
if _, err := os.Stat(config.ConfigFileName); err == nil {
return true
}
if home, err := os.UserHomeDir(); err == nil {
if _, err := os.Stat(filepath.Join(home, config.DefaultDataDir, config.ConfigFileName)); err == nil {
return true
}
}
return false
}

// defaultHomeConfigPath is the documented fallback for loaders that require an
// existing file (auth, call, code, tools).
func defaultHomeConfigPath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get user home directory: %w", err)
}
return filepath.Join(home, ".mcpproxy", "mcp_config.json"), nil
}

// Per-command loaders for commands whose config flow previously used bare
// config.Load() and ignored --data-dir (GH #908).

Expand Down
Loading
Loading