@@ -2,15 +2,20 @@ package cmd
2
2
3
3
import (
4
4
"context"
5
+ "errors"
5
6
"fmt"
7
+ "os"
8
+ "os/signal"
6
9
"strings"
10
+ "syscall"
7
11
8
12
"github.com/spf13/cobra"
9
13
10
14
"github.com/mozilla-ai/mcpd/v2/internal/cmd"
11
15
cmdopts "github.com/mozilla-ai/mcpd/v2/internal/cmd/options"
12
16
"github.com/mozilla-ai/mcpd/v2/internal/config"
13
17
"github.com/mozilla-ai/mcpd/v2/internal/daemon"
18
+ "github.com/mozilla-ai/mcpd/v2/internal/flags"
14
19
)
15
20
16
21
// DaemonCmd should be used to represent the 'daemon' command.
@@ -35,8 +40,8 @@ func NewDaemonCmd(baseCmd *cmd.BaseCmd, opt ...cmdopts.CmdOption) (*cobra.Comman
35
40
36
41
cobraCommand := & cobra.Command {
37
42
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" ,
40
45
RunE : c .run ,
41
46
}
42
47
@@ -51,55 +56,59 @@ func NewDaemonCmd(baseCmd *cmd.BaseCmd, opt ...cmdopts.CmdOption) (*cobra.Comman
51
56
& c .Addr ,
52
57
"addr" ,
53
58
"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)" ,
55
60
)
56
61
cobraCommand .MarkFlagsMutuallyExclusive ("dev" , "addr" )
57
62
58
63
return cobraCommand , nil
59
64
}
60
65
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
-
68
66
// run is configured (via NewDaemonCmd) to be called by the Cobra framework when the command is executed.
69
67
// It may return an error (or nil, when there is no error).
70
68
func (c * DaemonCmd ) run (cmd * cobra.Command , args []string ) error {
71
69
// Validate flags.
72
70
addr := strings .TrimSpace (c .Addr )
73
71
if err := daemon .IsValidAddr (addr ); err != nil {
74
- return fmt . Errorf ( "invalid address flag value: %s: %w" , addr , err )
72
+ return err
75
73
}
76
74
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.")
88
75
logger , err := c .Logger ()
89
76
if err != nil {
90
77
return err
91
78
}
92
79
93
- daemonCtx , daemonCtxCancel := context .WithCancel (context .Background ())
94
- defer daemonCtxCancel ()
95
-
96
80
d , err := daemon .NewDaemon (logger , c .cfgLoader , addr )
97
81
if err != nil {
98
82
return fmt .Errorf ("failed to create mcpd daemon instance: %w" , err )
99
83
}
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 )
102
105
}
103
106
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
+ }
105
114
}
0 commit comments