Skip to content

Commit 0670376

Browse files
authored
Fixes and updates (#31)
* Handle errors in a single place (stop Cobra automatically outputting them) * Remove duplicate logger creation code * Update README * Improved parsing of stderr for MCP server output
1 parent 6c20891 commit 0670376

File tree

11 files changed

+385
-101
lines changed

11 files changed

+385
-101
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ make docs-nav
8585

8686
## 🤝 Contributing
8787

88-
Coming soon.
88+
Please see our [Contributing to mcpd](CONTRIBUTING.md) guide for more information.
8989

9090
## 📄 License
9191

cmd/add.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,10 @@ func (c *AddCmd) run(cmd *cobra.Command, args []string) error {
101101
return fmt.Errorf("server name cannot be empty")
102102
}
103103

104-
logger := c.Logger()
104+
logger, err := c.Logger()
105+
if err != nil {
106+
return err
107+
}
105108

106109
reg, err := c.registryBuilder.Build()
107110
if err != nil {

cmd/daemon.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,19 @@ func (c *DaemonCmd) run(cmd *cobra.Command, args []string) error {
8585
// c.Logger.Info("Dev API key", "value", "dev-api-key-12345") // TODO: Generate local key
8686
// c.Logger.Info("Secrets file", "path", "~/.mcpd/secrets.dev") // TODO: Configurable?
8787
// c.Logger.Info("Press Ctrl+C to stop.")
88+
logger, err := c.Logger()
89+
if err != nil {
90+
return err
91+
}
92+
93+
daemonCtx, daemonCtxCancel := context.WithCancel(context.Background())
94+
defer daemonCtxCancel()
8895

89-
d, err := daemon.NewDaemon(c.Logger(), c.cfgLoader, addr)
96+
d, err := daemon.NewDaemon(logger, c.cfgLoader, addr)
9097
if err != nil {
9198
return fmt.Errorf("failed to create mcpd daemon instance: %w", err)
9299
}
93-
if err := d.StartAndManage(context.Background()); err != nil {
100+
if err := d.StartAndManage(daemonCtx); err != nil {
94101
return fmt.Errorf("daemon start failed: %w", err)
95102
}
96103

cmd/init.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ The configuration file path can be overridden using the '--%s' flag or the '%s'
5151
}
5252

5353
func (c *InitCmd) run(cmd *cobra.Command, _ []string) error {
54-
logger := c.Logger()
54+
logger, err := c.Logger()
55+
if err != nil {
56+
return err
57+
}
5558

5659
var initFilePath string
5760

cmd/remove.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ func (c *RemoveCmd) run(cmd *cobra.Command, args []string) error {
5353
return fmt.Errorf("server name is required and cannot be empty")
5454
}
5555

56-
logger := c.Logger()
56+
logger, err := c.Logger()
57+
if err != nil {
58+
return err
59+
}
5760

5861
name := strings.TrimSpace(args[0])
5962
if name == "" {

cmd/root.go

Lines changed: 11 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ package cmd
22

33
import (
44
"fmt"
5-
"io"
6-
"os"
7-
"strings"
85

9-
"github.com/hashicorp/go-hclog"
106
"github.com/spf13/cobra"
117

128
"github.com/mozilla-ai/mcpd/v2/internal/cmd"
@@ -15,8 +11,6 @@ import (
1511
"github.com/mozilla-ai/mcpd/v2/internal/printer"
1612
)
1713

18-
var version = "dev" // Set at build time using -ldflags
19-
2014
// createCmdFunc aliases the signature for a new command function.
2115
type createCmdFunc func(baseCmd *cmd.BaseCmd, opt ...options.CmdOption) (*cobra.Command, error)
2216

@@ -27,7 +21,7 @@ type RootCmd struct {
2721
// Global variable to hold the root command instance
2822
var rootCmdInstance *RootCmd
2923

30-
func Execute() {
24+
func Execute() error {
3125
// Create the root command instance
3226
rootCmdInstance = &RootCmd{
3327
BaseCmd: &cmd.BaseCmd{},
@@ -36,34 +30,24 @@ func Execute() {
3630
// Create cobra command.
3731
rootCmd, err := NewRootCmd(rootCmdInstance)
3832
if err != nil {
39-
// TODO: Handle top level error.
40-
}
41-
42-
// Add hook to update loggers after flag parsing
43-
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
44-
// Configure logger with parsed flags
45-
logger, err := configureLogger()
46-
if err != nil {
47-
fmt.Fprintf(os.Stderr, "error configuring logger: %s\n", err)
48-
os.Exit(1)
49-
}
50-
51-
// Update the root command instance
52-
rootCmdInstance.SetLogger(logger)
33+
return fmt.Errorf("could not create root command: %w", err)
5334
}
5435

5536
if err := rootCmd.Execute(); err != nil {
56-
os.Exit(1)
37+
return err
5738
}
39+
40+
return nil
5841
}
5942

6043
func NewRootCmd(c *RootCmd) (*cobra.Command, error) {
6144
rootCmd := &cobra.Command{
62-
Use: "mcpd <command> [sub-command] [args]",
63-
Short: "'mcpd' CLI is the primary interface for developers to interact with mcpd.",
64-
Long: c.longDescription(),
65-
SilenceUsage: true,
66-
Version: version,
45+
Use: "mcpd <command> [sub-command] [args]",
46+
Short: "'mcpd' CLI is the primary interface for developers to interact with mcpd.",
47+
Long: c.longDescription(),
48+
SilenceUsage: true,
49+
SilenceErrors: true,
50+
Version: cmd.Version(),
6751
}
6852

6953
// Global flags
@@ -100,43 +84,6 @@ func NewRootCmd(c *RootCmd) (*cobra.Command, error) {
10084
return rootCmd, nil
10185
}
10286

103-
// TODO: Remove and call RootCmd.Logger()?
104-
func configureLogger() (hclog.Logger, error) {
105-
// Use flags first, then fall back to env vars
106-
logPath := flags.LogPath
107-
if logPath == "" {
108-
logPath = strings.TrimSpace(os.Getenv(flags.EnvVarLogPath))
109-
}
110-
111-
// If log path is empty, don't log to a file
112-
logOutput := io.Discard
113-
114-
if logPath != "" {
115-
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
116-
if err != nil {
117-
return nil, fmt.Errorf("failed to open log file (%s): %w", logPath, err)
118-
}
119-
logOutput = f
120-
}
121-
122-
// Use flags first, then fall back to env vars
123-
logLevel := flags.LogLevel
124-
if logLevel == "" {
125-
logLevel = strings.ToLower(os.Getenv(flags.EnvVarLogLevel))
126-
if logLevel == "" {
127-
logLevel = flags.DefaultLogLevel
128-
}
129-
}
130-
131-
logger := hclog.New(&hclog.LoggerOptions{
132-
Name: "mcpd",
133-
Level: hclog.LevelFromString(logLevel),
134-
Output: logOutput,
135-
})
136-
137-
return logger, nil
138-
}
139-
14087
func (c *RootCmd) longDescription() string {
14188
return `The 'mcpd' CLI is the primary interface for developers to interact with the
14289
mcpd Control Plane, define their agent projects, and manage MCP server dependencies.`

internal/cmd/basecmd.go

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ import (
1414
"github.com/mozilla-ai/mcpd/v2/internal/runtime"
1515
)
1616

17+
var version = "dev" // Set at build time using -ldflags
18+
19+
// Version is used by other packages to retrieve the build version of mcpd.
20+
func Version() string {
21+
return version
22+
}
23+
24+
// AppName returns the name of the mcpd application.
25+
func AppName() string {
26+
return "mcpd"
27+
}
28+
1729
var _ registry.Builder = (*BaseCmd)(nil)
1830

1931
type BaseCmd struct {
@@ -26,9 +38,9 @@ func (c *BaseCmd) SetLogger(logger hclog.Logger) {
2638
}
2739

2840
// Logger returns the current logger for the command
29-
func (c *BaseCmd) Logger() hclog.Logger {
41+
func (c *BaseCmd) Logger() (hclog.Logger, error) {
3042
if c.logger != nil {
31-
return c.logger
43+
return c.logger, nil
3244
}
3345

3446
// Get log level from flags first, then environment, then default
@@ -46,32 +58,35 @@ func (c *BaseCmd) Logger() hclog.Logger {
4658
logPath = strings.TrimSpace(os.Getenv(flags.EnvVarLogPath))
4759
}
4860

49-
// Configure logger output
50-
output := io.Discard // os.Stderr
61+
// Configure logger output based on the log file path
62+
output := io.Discard // Default to discarding log output.
5163
if logPath != "" {
5264
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
5365
if err != nil {
54-
fmt.Fprintf(os.Stderr, "Failed to open log file (%s): %v, using stderr\n", logPath, err)
66+
return nil, fmt.Errorf("failed to open log file (%s): %w", logPath, err)
5567
} else {
5668
output = f
5769
}
5870
}
5971

60-
// Using flags/env for fallback logger
6172
c.logger = hclog.New(&hclog.LoggerOptions{
62-
Name: "mcpd-default",
73+
Name: AppName(),
6374
Level: hclog.LevelFromString(logLevel),
6475
Output: output,
6576
})
6677

67-
return c.logger
78+
return c.logger, nil
6879
}
6980

7081
func (c *BaseCmd) Build() (registry.PackageProvider, error) {
71-
l := c.Logger().Named("registry")
82+
logger, err := c.Logger()
83+
if err != nil {
84+
return nil, err
85+
}
7286

7387
supportedRuntimes := c.MCPDSupportedRuntimes()
7488
opts := runtime.WithSupportedRuntimes(supportedRuntimes...)
89+
l := logger.Named("registry")
7590

7691
// TODO: Should we be using a hardcoded URL
7792
mcpm, err := mcpm.NewRegistry(l, "https://getmcp.io/api/servers.json", opts)

0 commit comments

Comments
 (0)