Skip to content

Commit 29f1a73

Browse files
committed
Implemented: mcpd config daemon --help
* set: sets dot-path keys to values * remove: removes dot-path keys (and their values) * list: shows all config * list --available: show all config keys that can be set * get: gets the current value for a dot-path key
1 parent 939c3cc commit 29f1a73

File tree

13 files changed

+2023
-22
lines changed

13 files changed

+2023
-22
lines changed

cmd/add_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ func (f *fakeConfig) ListServers() []config.ServerEntry {
3737
return []config.ServerEntry{f.entry}
3838
}
3939

40+
func (f *fakeConfig) SaveConfig() error {
41+
return nil
42+
}
43+
4044
type fakeLoader struct {
4145
cfg *fakeConfig
4246
err error

cmd/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/spf13/cobra"
55

66
"github.com/mozilla-ai/mcpd/v2/cmd/config/args"
7+
"github.com/mozilla-ai/mcpd/v2/cmd/config/daemon"
78
"github.com/mozilla-ai/mcpd/v2/cmd/config/env"
89
"github.com/mozilla-ai/mcpd/v2/cmd/config/export"
910
"github.com/mozilla-ai/mcpd/v2/cmd/config/tools"
@@ -21,6 +22,7 @@ func NewConfigCmd(baseCmd *cmd.BaseCmd, opt ...options.CmdOption) (*cobra.Comman
2122
// Sub-commands for: mcpd config
2223
fns := []func(baseCmd *cmd.BaseCmd, opt ...options.CmdOption) (*cobra.Command, error){
2324
args.NewCmd, // args
25+
daemon.NewCmd, // daemon
2426
env.NewCmd, // env
2527
tools.NewCmd, // tools
2628
export.NewCmd, // export

cmd/config/daemon/cmd.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package daemon
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
6+
"github.com/mozilla-ai/mcpd/v2/internal/cmd"
7+
"github.com/mozilla-ai/mcpd/v2/internal/cmd/options"
8+
)
9+
10+
func NewCmd(baseCmd *cmd.BaseCmd, opt ...options.CmdOption) (*cobra.Command, error) {
11+
cobraCmd := &cobra.Command{
12+
Use: "daemon",
13+
Short: "Manages daemon configuration",
14+
Long: "Manages daemon configuration in .mcpd.toml including API settings, CORS, timeouts and intervals",
15+
}
16+
17+
// Sub-commands for: mcpd config daemon
18+
fns := []func(baseCmd *cmd.BaseCmd, opt ...options.CmdOption) (*cobra.Command, error){
19+
NewSetCmd, // set
20+
NewGetCmd, // get
21+
NewListCmd, // list
22+
NewRemoveCmd, // remove
23+
// NewValidateCmd, // validate
24+
}
25+
26+
for _, fn := range fns {
27+
tempCmd, err := fn(baseCmd, opt...)
28+
if err != nil {
29+
return nil, err
30+
}
31+
cobraCmd.AddCommand(tempCmd)
32+
}
33+
34+
return cobraCmd, nil
35+
}

cmd/config/daemon/get.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package daemon
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/spf13/cobra"
8+
9+
"github.com/mozilla-ai/mcpd/v2/internal/cmd"
10+
cmdopts "github.com/mozilla-ai/mcpd/v2/internal/cmd/options"
11+
"github.com/mozilla-ai/mcpd/v2/internal/config"
12+
)
13+
14+
type GetCmd struct {
15+
*cmd.BaseCmd
16+
cfgLoader config.Loader
17+
}
18+
19+
func NewGetCmd(baseCmd *cmd.BaseCmd, opt ...cmdopts.CmdOption) (*cobra.Command, error) {
20+
opts, err := cmdopts.NewOptions(opt...)
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
c := &GetCmd{
26+
BaseCmd: baseCmd,
27+
cfgLoader: opts.ConfigLoader,
28+
}
29+
30+
cobraCmd := &cobra.Command{
31+
Use: "get <key>",
32+
Short: "Get daemon configuration value",
33+
Long: `Get a specific daemon configuration value from .mcpd.toml file using dotted key notation.
34+
35+
Examples:
36+
mcpd config daemon get api.addr
37+
mcpd config daemon get api.cors.enable
38+
mcpd config daemon get mcp.timeout.health`,
39+
RunE: c.run,
40+
Args: cobra.ExactArgs(1),
41+
}
42+
43+
return cobraCmd, nil
44+
}
45+
46+
func (c *GetCmd) run(cmd *cobra.Command, args []string) error {
47+
cfg, err := c.LoadConfig(c.cfgLoader)
48+
if err != nil {
49+
return err
50+
}
51+
52+
if cfg.Daemon == nil {
53+
return fmt.Errorf("no daemon configuration found")
54+
}
55+
56+
// Split dotted notation into keys for variadic Get
57+
key := args[0]
58+
keys := strings.Split(key, ".")
59+
60+
value, err := cfg.Daemon.Get(keys...)
61+
if err != nil {
62+
return err
63+
}
64+
65+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), c.formatValue(value))
66+
return nil
67+
}
68+
69+
func (c *GetCmd) formatValue(value any) string {
70+
switch v := value.(type) {
71+
case *config.Duration:
72+
if v == nil {
73+
return ""
74+
}
75+
return v.String()
76+
case config.Duration:
77+
return v.String()
78+
case []string:
79+
if len(v) == 0 {
80+
return "[]"
81+
}
82+
return strings.Join(v, ",")
83+
case *string:
84+
if v == nil {
85+
return ""
86+
}
87+
return *v
88+
case *bool:
89+
if v == nil {
90+
return ""
91+
}
92+
return fmt.Sprintf("%t", *v)
93+
default:
94+
return fmt.Sprintf("%v", v)
95+
}
96+
}

cmd/config/daemon/list.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package daemon
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/spf13/cobra"
8+
9+
"github.com/mozilla-ai/mcpd/v2/internal/cmd"
10+
cmdopts "github.com/mozilla-ai/mcpd/v2/internal/cmd/options"
11+
"github.com/mozilla-ai/mcpd/v2/internal/config"
12+
)
13+
14+
type ListCmd struct {
15+
*cmd.BaseCmd
16+
cfgLoader config.Loader
17+
available bool
18+
}
19+
20+
func NewListCmd(baseCmd *cmd.BaseCmd, opt ...cmdopts.CmdOption) (*cobra.Command, error) {
21+
opts, err := cmdopts.NewOptions(opt...)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
c := &ListCmd{
27+
BaseCmd: baseCmd,
28+
cfgLoader: opts.ConfigLoader,
29+
}
30+
31+
cobraCmd := &cobra.Command{
32+
Use: "list",
33+
Short: "List daemon configuration",
34+
Long: `List daemon configuration from .mcpd.toml file.
35+
36+
Examples:
37+
mcpd config daemon list # Show current configuration
38+
mcpd config daemon list --available # Show all available configuration keys`,
39+
RunE: c.run,
40+
Args: cobra.NoArgs,
41+
}
42+
43+
cobraCmd.Flags().
44+
BoolVar(&c.available, "available", false, "Show all available configuration keys with descriptions")
45+
46+
return cobraCmd, nil
47+
}
48+
49+
func (c *ListCmd) run(cmd *cobra.Command, args []string) error {
50+
if c.available {
51+
return c.showAvailableKeys(cmd)
52+
}
53+
54+
cfg, err := c.LoadConfig(c.cfgLoader)
55+
if err != nil {
56+
return err
57+
}
58+
59+
if cfg.Daemon == nil {
60+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "No daemon configuration found")
61+
return nil
62+
}
63+
64+
// Use ConfigGetter to get all configuration
65+
allConfig, err := cfg.Daemon.Get()
66+
if err != nil {
67+
return err
68+
}
69+
70+
return c.showConfig(cmd, allConfig, "daemon")
71+
}
72+
73+
func (c *ListCmd) showConfig(cmd *cobra.Command, config any, prefix string) error {
74+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "[%s]\n", prefix)
75+
76+
if configMap, ok := config.(map[string]any); ok {
77+
c.printConfigMap(cmd, configMap, prefix, " ")
78+
}
79+
80+
return nil
81+
}
82+
83+
func (c *ListCmd) printConfigMap(cmd *cobra.Command, configMap map[string]any, prefix string, indent string) {
84+
for key, value := range configMap {
85+
switch v := value.(type) {
86+
case map[string]any:
87+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s[%s.%s]\n", indent, prefix, key)
88+
c.printConfigMap(cmd, v, fmt.Sprintf("%s.%s", prefix, key), indent+" ")
89+
case []string:
90+
if len(v) > 0 {
91+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s%s = %q\n", indent, key, v)
92+
}
93+
case bool:
94+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s%s = %t\n", indent, key, v)
95+
case string:
96+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s%s = %q\n", indent, key, v)
97+
default:
98+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s%s = %v\n", indent, key, v)
99+
}
100+
}
101+
}
102+
103+
func (c *ListCmd) showAvailableKeys(cmd *cobra.Command) error {
104+
// Create a dummy daemon config to get the available keys
105+
daemonConfig := &config.DaemonConfig{}
106+
keys := daemonConfig.AvailableKeys()
107+
108+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Available daemon configuration keys:")
109+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "")
110+
111+
// Group keys by top-level section
112+
apiKeys := []config.ConfigKey{}
113+
mcpKeys := []config.ConfigKey{}
114+
115+
for _, key := range keys {
116+
if strings.HasPrefix(key.Path, "api.") {
117+
apiKeys = append(apiKeys, key)
118+
} else if strings.HasPrefix(key.Path, "mcp.") {
119+
mcpKeys = append(mcpKeys, key)
120+
}
121+
}
122+
123+
// Show API keys
124+
if len(apiKeys) > 0 {
125+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "API Configuration:")
126+
for _, key := range apiKeys {
127+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), " %-30s %-12s %s\n", key.Path, "("+key.Type+")", key.Description)
128+
}
129+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "")
130+
}
131+
132+
// Show MCP keys
133+
if len(mcpKeys) > 0 {
134+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "MCP Configuration:")
135+
for _, key := range mcpKeys {
136+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), " %-30s %-12s %s\n", key.Path, "("+key.Type+")", key.Description)
137+
}
138+
}
139+
140+
return nil
141+
}

cmd/config/daemon/remove.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package daemon
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
8+
"github.com/mozilla-ai/mcpd/v2/internal/cmd"
9+
cmdopts "github.com/mozilla-ai/mcpd/v2/internal/cmd/options"
10+
"github.com/mozilla-ai/mcpd/v2/internal/config"
11+
)
12+
13+
type RemoveCmd struct {
14+
*cmd.BaseCmd
15+
cfgLoader config.Loader
16+
}
17+
18+
func NewRemoveCmd(baseCmd *cmd.BaseCmd, opt ...cmdopts.CmdOption) (*cobra.Command, error) {
19+
opts, err := cmdopts.NewOptions(opt...)
20+
if err != nil {
21+
return nil, err
22+
}
23+
24+
c := &RemoveCmd{
25+
BaseCmd: baseCmd,
26+
cfgLoader: opts.ConfigLoader,
27+
}
28+
29+
cobraCmd := &cobra.Command{
30+
Use: "remove <key> [key ...]",
31+
Short: "Remove daemon configuration values",
32+
Long: `Remove specific daemon configuration values from .mcpd.toml file using dotted key notation.
33+
34+
Examples:
35+
mcpd config daemon remove api.addr
36+
mcpd config daemon remove api.cors.enable api.cors.allow_origins
37+
mcpd config daemon remove mcp.timeout.health`,
38+
RunE: c.run,
39+
Args: cobra.MinimumNArgs(1),
40+
}
41+
42+
return cobraCmd, nil
43+
}
44+
45+
func (c *RemoveCmd) run(cmd *cobra.Command, args []string) error {
46+
cfg, err := c.LoadConfig(c.cfgLoader)
47+
if err != nil {
48+
return err
49+
}
50+
51+
if cfg.Daemon == nil {
52+
return fmt.Errorf("no daemon configuration found")
53+
}
54+
55+
// Remove each specified key by setting to empty value
56+
for _, key := range args {
57+
_, err := cfg.Daemon.Set(key, "") // Internal use with empty value
58+
if err != nil {
59+
return err // Already has full path context from domain object
60+
}
61+
62+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "✓ Removed daemon config '%s'\n", key)
63+
}
64+
65+
// Save config
66+
if err := cfg.SaveConfig(); err != nil {
67+
return fmt.Errorf("failed to save config: %w", err)
68+
}
69+
70+
return nil
71+
}

0 commit comments

Comments
 (0)