Skip to content

Commit eb21e32

Browse files
authored
Fix --dev mode for daemon (#76)
* Clean up --dev mode * Remove requirement for name in search cmd
1 parent e750318 commit eb21e32

File tree

6 files changed

+208
-160
lines changed

6 files changed

+208
-160
lines changed

cmd/daemon.go

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@ package cmd
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
7+
"os"
8+
"os/signal"
69
"strings"
10+
"syscall"
711

812
"github.com/spf13/cobra"
913

1014
"github.com/mozilla-ai/mcpd/v2/internal/cmd"
1115
cmdopts "github.com/mozilla-ai/mcpd/v2/internal/cmd/options"
1216
"github.com/mozilla-ai/mcpd/v2/internal/config"
1317
"github.com/mozilla-ai/mcpd/v2/internal/daemon"
18+
"github.com/mozilla-ai/mcpd/v2/internal/flags"
1419
)
1520

1621
// DaemonCmd should be used to represent the 'daemon' command.
@@ -35,8 +40,8 @@ func NewDaemonCmd(baseCmd *cmd.BaseCmd, opt ...cmdopts.CmdOption) (*cobra.Comman
3540

3641
cobraCommand := &cobra.Command{
3742
Use: "daemon",
38-
Short: "Launches an mcpd daemon instance (Execution Plane)",
39-
Long: c.longDescription(),
43+
Short: "Launches an mcpd daemon instance",
44+
Long: "Launches an mcpd daemon instance, which starts MCP servers and provides routing via HTTP API",
4045
RunE: c.run,
4146
}
4247

@@ -51,55 +56,59 @@ func NewDaemonCmd(baseCmd *cmd.BaseCmd, opt ...cmdopts.CmdOption) (*cobra.Comman
5156
&c.Addr,
5257
"addr",
5358
"localhost:8090",
54-
"Specify the address for the daemon to bind (not applicable in --dev mode)",
59+
"Address for the daemon to bind (not applicable in --dev mode)",
5560
)
5661
cobraCommand.MarkFlagsMutuallyExclusive("dev", "addr")
5762

5863
return cobraCommand, nil
5964
}
6065

61-
// longDescription returns the long version of the command description.
62-
func (c *DaemonCmd) longDescription() string {
63-
return `Launches an mcpd daemon instance (Execution Plane).
64-
In dev mode, binds to localhost, logs to console, and exposes local endpoint.
65-
In prod, binds to 0.0.0.0, logs to stdout, and runs as background service`
66-
}
67-
6866
// run is configured (via NewDaemonCmd) to be called by the Cobra framework when the command is executed.
6967
// It may return an error (or nil, when there is no error).
7068
func (c *DaemonCmd) run(cmd *cobra.Command, args []string) error {
7169
// Validate flags.
7270
addr := strings.TrimSpace(c.Addr)
7371
if err := daemon.IsValidAddr(addr); err != nil {
74-
return fmt.Errorf("invalid address flag value: %s: %w", addr, err)
72+
return err
7573
}
7674

77-
// TODO: Currently only runs in 'dev' mode... (even without flag)
78-
// addr := "localhost:8080"
79-
// if c.Addr != "" {
80-
// addr = c.Addr
81-
// }
82-
//
83-
// c.Logger.Info("Launching daemon (dev mode)", "bindAddr", addr)
84-
// c.Logger.Info("Local endpoint", "url", "http://"+addr+"/api")
85-
// c.Logger.Info("Dev API key", "value", "dev-api-key-12345") // TODO: Generate local key
86-
// c.Logger.Info("Secrets file", "path", "~/.config/mcpd/secrets.dev.toml") // TODO: Configurable?
87-
// c.Logger.Info("Press Ctrl+C to stop.")
8875
logger, err := c.Logger()
8976
if err != nil {
9077
return err
9178
}
9279

93-
daemonCtx, daemonCtxCancel := context.WithCancel(context.Background())
94-
defer daemonCtxCancel()
95-
9680
d, err := daemon.NewDaemon(logger, c.cfgLoader, addr)
9781
if err != nil {
9882
return fmt.Errorf("failed to create mcpd daemon instance: %w", err)
9983
}
100-
if err := d.StartAndManage(daemonCtx); err != nil {
101-
return fmt.Errorf("daemon start failed: %w", err)
84+
85+
// Create the signal handling context for the application.
86+
daemonCtx, daemonCtxCancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
87+
defer daemonCtxCancel()
88+
89+
runErr := make(chan error, 1)
90+
go func() {
91+
if err := d.StartAndManage(daemonCtx); err != nil && !errors.Is(err, context.Canceled) {
92+
runErr <- err
93+
}
94+
}()
95+
96+
// Print --dev mode banner if required.
97+
if c.Dev {
98+
logger.Info("Launching daemon in dev mode", "addr", addr)
99+
fmt.Printf("mcpd daemon running in 'dev' mode.\n\n"+
100+
" Local API:\thttp://%s/api/v1\n"+
101+
" OpenAPI UI:\thttp://%s/docs\n"+
102+
" Config file:\t%s\n"+
103+
" Secrets file:\t%s\n\n"+
104+
"Press Ctrl+C to stop.\n\n", addr, addr, flags.ConfigFile, flags.RuntimeFile)
102105
}
103106

104-
return nil
107+
select {
108+
case <-daemonCtx.Done():
109+
return nil // Graceful Ctrl+C / SIGTERM
110+
case err := <-runErr:
111+
logger.Error("error running daemon instance", "error", err)
112+
return err // Propagate daemon failure
113+
}
105114
}

cmd/search.go

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@ func NewSearchCmd(baseCmd *cmd.BaseCmd, opt ...cmdopts.CmdOption) (*cobra.Comman
4444
}
4545

4646
cobraCommand := &cobra.Command{
47-
Use: "search <server-name>",
47+
Use: "search [server-name]",
4848
Short: "Searches all configured registries for matching MCP servers",
49-
Long: c.longDescription(),
50-
RunE: c.run,
49+
Long: fmt.Sprintf("Searches all configured registries for matching MCP servers, "+
50+
"the wildcard '%s' is the default when name is not specified. "+
51+
"Returns aggregated results from all configured registries", options.WildcardCharacter),
52+
RunE: c.run,
5153
}
5254

5355
cobraCommand.Flags().StringVar(
@@ -102,11 +104,6 @@ func NewSearchCmd(baseCmd *cmd.BaseCmd, opt ...cmdopts.CmdOption) (*cobra.Comman
102104
return cobraCommand, nil
103105
}
104106

105-
// longDescription returns the long version of the command description.
106-
func (c *SearchCmd) longDescription() string {
107-
return `Searches all configured registries for matching MCP servers. Returns aggregated results for matches`
108-
}
109-
110107
func (c *SearchCmd) filters() map[string]string {
111108
f := make(map[string]string)
112109

@@ -133,12 +130,10 @@ func (c *SearchCmd) filters() map[string]string {
133130
}
134131

135132
func (c *SearchCmd) run(cmd *cobra.Command, args []string) error {
136-
if len(args) == 0 || strings.TrimSpace(args[0]) == "" {
137-
return fmt.Errorf("name is required and cannot be empty")
138-
}
139-
name := strings.TrimSpace(args[0])
140-
if name == "" {
141-
return fmt.Errorf("name cannot be empty")
133+
// Name not required, default to the wildcard.
134+
name := options.WildcardCharacter
135+
if len(args) > 0 && strings.TrimSpace(args[0]) != "" {
136+
name = strings.TrimSpace(args[0])
142137
}
143138

144139
reg, err := c.registryBuilder.Build()

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/spf13/cobra v1.9.1
1212
github.com/spf13/pflag v1.0.6
1313
github.com/stretchr/testify v1.10.0
14+
golang.org/x/sync v0.16.0
1415
gopkg.in/yaml.v3 v3.0.1
1516
)
1617

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
3030
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
3131
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
3232
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
33-
github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8=
34-
github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
3533
github.com/mark3labs/mcp-go v0.33.0 h1:naxhjnTIs/tyPZmWUZFuG0lDmdA6sUyYGGf3gsHvTCc=
3634
github.com/mark3labs/mcp-go v0.33.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
3735
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -60,6 +58,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
6058
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
6159
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
6260
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
61+
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
62+
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
6363
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6464
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6565
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

0 commit comments

Comments
 (0)