@@ -2,15 +2,20 @@ package cmd
22
33import (
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).
7068func (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:\t http://%s/api/v1\n " +
101+ " OpenAPI UI:\t http://%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}
0 commit comments