Skip to content

Feature Request: Toolhive Config file #919

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
222 changes: 215 additions & 7 deletions cmd/thv/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/stacklok/toolhive/pkg/labels"
"github.com/stacklok/toolhive/pkg/logger"
"github.com/stacklok/toolhive/pkg/networking"
"github.com/stacklok/toolhive/pkg/registry"
"github.com/stacklok/toolhive/pkg/transport"
)

Expand Down Expand Up @@ -115,10 +116,86 @@ var unsetRegistryURLCmd = &cobra.Command{
RunE: unsetRegistryURLCmdFunc,
}

var addDefaultServerCmd = &cobra.Command{
Use: "add-default-server [server]",
Short: "Add a server to the list of default servers",
Long: `Add a server to the list of default servers that will be started when running 'thv run' without arguments.
The server must exist in the registry.

Example:
thv config add-default-server my-server`,
Args: cobra.ExactArgs(1),
RunE: addDefaultServerCmdFunc,
}

var removeDefaultServerCmd = &cobra.Command{
Use: "remove-default-server [server]",
Short: "Remove a server from the list of default servers",
Long: `Remove a server from the list of default servers.
This only removes the server from the default list, it does not delete or stop the server.

Example:
thv config remove-default-server my-server`,
Args: cobra.ExactArgs(1),
RunE: removeDefaultServerCmdFunc,
}

var (
allowPrivateRegistryIp bool
)

var (
resetServerArgsAll bool
)

var resetServerArgsCmd = &cobra.Command{
Use: "reset-server-args [SERVER_NAME]",
Short: "Reset/delete arguments for MCP servers from the config",
Long: `Reset/delete configured arguments for MCP servers from the config.
This command removes saved arguments from the config file.
Future runs of the affected servers will not use any pre-configured arguments unless explicitly provided.

Examples:
# Reset arguments for a specific server
thv config reset-server-args my-server

# Reset arguments for all servers
thv config reset-server-args --all`,
Args: cobra.MaximumNArgs(1),
RunE: resetServerArgsCmdFunc,
PreRunE: func(_ *cobra.Command, args []string) error {
if !resetServerArgsAll && len(args) == 0 {
return fmt.Errorf("server name is required when --all flag is not set")
}
if resetServerArgsAll && len(args) > 0 {
return fmt.Errorf("server name cannot be specified when using --all flag")
}
return nil
},
}

var setGlobalServerArgsCmd = &cobra.Command{
Use: "set-global-server-args KEY=VALUE [KEY=VALUE...]",
Short: "Set global arguments for all MCP servers",
Long: `Set global arguments that will be applied to all MCP servers.
These arguments will be used as defaults for all servers unless overridden by server-specific arguments.

Example:
thv config set-global-server-args debug=true log-level=info`,
Args: cobra.MinimumNArgs(1),
RunE: setGlobalServerArgsCmdFunc,
}

var resetGlobalServerArgsCmd = &cobra.Command{
Use: "reset-global-server-args",
Short: "Reset/delete global arguments for all MCP servers",
Long: `Reset/delete global arguments from the config.
This command removes all saved global arguments from the config file.
Future runs of servers will not use any pre-configured global arguments.`,
Args: cobra.NoArgs,
RunE: resetGlobalServerArgsCmdFunc,
}

func init() {
// Add config command to root command
rootCmd.AddCommand(configCmd)
Expand All @@ -131,15 +208,16 @@ func init() {
configCmd.AddCommand(getCACertCmd)
configCmd.AddCommand(unsetCACertCmd)
configCmd.AddCommand(setRegistryURLCmd)
setRegistryURLCmd.Flags().BoolVarP(
&allowPrivateRegistryIp,
"allow-private-ip",
"p",
false,
"Allow setting the registry URL, even if it references a private IP address",
)
configCmd.AddCommand(getRegistryURLCmd)
configCmd.AddCommand(unsetRegistryURLCmd)
configCmd.AddCommand(resetServerArgsCmd)
configCmd.AddCommand(setGlobalServerArgsCmd)
configCmd.AddCommand(resetGlobalServerArgsCmd)
configCmd.AddCommand(addDefaultServerCmd)
configCmd.AddCommand(removeDefaultServerCmd)

resetServerArgsCmd.Flags().BoolVarP(&resetServerArgsAll, "all", "a", false,
"Reset arguments for all MCP servers")

// Add OTEL parent command to config
configCmd.AddCommand(OtelCmd)
Expand Down Expand Up @@ -480,3 +558,133 @@ func listRegisteredClientsCmdFunc(_ *cobra.Command, _ []string) error {

return nil
}

func resetServerArgsCmdFunc(_ *cobra.Command, args []string) error {
// Load the config
cfg, err := config.LoadOrCreateConfig()
if err != nil {
return fmt.Errorf("failed to load config: %v", err)
}

if resetServerArgsAll {
// Delete all server arguments
if err := cfg.DeleteAllServerArgs(); err != nil {
return fmt.Errorf("failed to reset all server arguments: %v", err)
}
logger.Info("Successfully reset arguments for all servers")
} else {
// Delete arguments for specific server
serverName := args[0]
if err := cfg.DeleteServerArgs(serverName); err != nil {
return fmt.Errorf("failed to reset server arguments: %v", err)
}
logger.Infof("Successfully reset arguments for server %s", serverName)
}

return nil
}

func setGlobalServerArgsCmdFunc(_ *cobra.Command, args []string) error {
// Parse the arguments into a map
argsMap := make(map[string]string)
for _, arg := range args {
parts := strings.SplitN(arg, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("invalid argument format: %s (expected KEY=VALUE)", arg)
}
argsMap[parts[0]] = parts[1]
}

// Load the config
cfg, err := config.LoadOrCreateConfig()
if err != nil {
return fmt.Errorf("failed to load config: %v", err)
}

// Set the global arguments
if err := cfg.SetGlobalServerArgs(argsMap); err != nil {
return fmt.Errorf("failed to set global server arguments: %v", err)
}

logger.Info("Successfully set global server arguments")
return nil
}

func resetGlobalServerArgsCmdFunc(_ *cobra.Command, _ []string) error {
// Load the config
cfg, err := config.LoadOrCreateConfig()
if err != nil {
return fmt.Errorf("failed to load config: %v", err)
}

// Reset the global arguments
if err := cfg.DeleteGlobalServerArgs(); err != nil {
return fmt.Errorf("failed to reset global server arguments: %v", err)
}

logger.Info("Successfully reset global server arguments")
return nil
}

func addDefaultServerCmdFunc(_ *cobra.Command, args []string) error {
serverName := args[0]

// Load the registry to validate the server exists
provider, err := registry.GetDefaultProvider()
if err != nil {
return fmt.Errorf("failed to get registry provider: %v", err)
}

// Check if the server exists in the registry
if _, err := provider.GetServer(serverName); err != nil {
return fmt.Errorf("server %q not found in registry: %w", serverName, err)
}

// Update the configuration
err = config.UpdateConfig(func(c *config.Config) {
// Check if server is already in default servers
for _, s := range c.DefaultServers {
if s == serverName {
fmt.Printf("Server %q is already in default servers list\n", serverName)
return
}
}

// Add the server to default servers
c.DefaultServers = append(c.DefaultServers, serverName)
})
if err != nil {
return fmt.Errorf("failed to update configuration: %w", err)
}

fmt.Printf("Successfully added %q to default servers\n", serverName)
return nil
}

func removeDefaultServerCmdFunc(_ *cobra.Command, args []string) error {
serverName := args[0]

// Update the configuration
err := config.UpdateConfig(func(c *config.Config) {
// Find and remove the server from the default servers list
found := false
for i, s := range c.DefaultServers {
if s == serverName {
// Remove the server by appending the slice before and after the index
c.DefaultServers = append(c.DefaultServers[:i], c.DefaultServers[i+1:]...)
found = true
break
}
}
if !found {
fmt.Printf("Server %q not found in default servers list\n", serverName)
return
}
})
if err != nil {
return fmt.Errorf("failed to update configuration: %w", err)
}

fmt.Printf("Successfully removed %q from default servers\n", serverName)
return nil
}
Loading
Loading