Skip to content

Commit 3d61299

Browse files
authored
Merge pull request #480 from githubnext/copilot/enable-parallel-launching
Add parallel server launching with --parallel-launch flag
2 parents dd1fcde + f99339b commit 3d61299

4 files changed

Lines changed: 149 additions & 38 deletions

File tree

internal/cmd/root.go

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,30 @@ const (
2727
// DefaultListenIPv4 is the default interface used by the HTTP server.
2828
DefaultListenIPv4 = "127.0.0.1"
2929
// DefaultListenPort is the default port used by the HTTP server.
30-
DefaultListenPort = "3000"
31-
defaultListenAddr = DefaultListenIPv4 + ":" + DefaultListenPort
32-
defaultRoutedMode = false
33-
defaultUnifiedMode = false
34-
defaultEnvFile = ""
35-
defaultEnableDIFC = false
36-
defaultLogDir = "/tmp/gh-aw/mcp-logs"
30+
DefaultListenPort = "3000"
31+
defaultListenAddr = DefaultListenIPv4 + ":" + DefaultListenPort
32+
defaultRoutedMode = false
33+
defaultUnifiedMode = false
34+
defaultEnvFile = ""
35+
defaultEnableDIFC = false
36+
defaultLogDir = "/tmp/gh-aw/mcp-logs"
37+
defaultParallelLaunch = true
3738
)
3839

3940
var (
40-
configFile string
41-
configStdin bool
42-
listenAddr string
43-
routedMode bool
44-
unifiedMode bool
45-
envFile string
46-
enableDIFC bool
47-
logDir string
48-
validateEnv bool
49-
verbosity int // Verbosity level: 0 (default), 1 (-v info), 2 (-vv debug), 3 (-vvv trace)
50-
debugLog = logger.New("cmd:root")
51-
version = "dev" // Default version, overridden by SetVersion
41+
configFile string
42+
configStdin bool
43+
listenAddr string
44+
routedMode bool
45+
unifiedMode bool
46+
envFile string
47+
enableDIFC bool
48+
logDir string
49+
validateEnv bool
50+
parallelLaunch bool
51+
verbosity int // Verbosity level: 0 (default), 1 (-v info), 2 (-vv debug), 3 (-vvv trace)
52+
debugLog = logger.New("cmd:root")
53+
version = "dev" // Default version, overridden by SetVersion
5254
)
5355

5456
var rootCmd = &cobra.Command{
@@ -75,6 +77,7 @@ func init() {
7577
rootCmd.Flags().BoolVar(&enableDIFC, "enable-difc", defaultEnableDIFC, "Enable DIFC enforcement and session requirement (requires sys___init call before tool access)")
7678
rootCmd.Flags().StringVar(&logDir, "log-dir", getDefaultLogDir(), "Directory for log files (falls back to stdout if directory cannot be created)")
7779
rootCmd.Flags().BoolVar(&validateEnv, "validate-env", false, "Validate execution environment (Docker, env vars) before starting")
80+
rootCmd.Flags().BoolVar(&parallelLaunch, "parallel-launch", defaultParallelLaunch, "Launch MCP servers in parallel during startup (enabled by default)")
7881
rootCmd.Flags().CountVarP(&verbosity, "verbose", "v", "Increase verbosity level (use -v for info, -vv for debug, -vvv for trace)")
7982

8083
// Mark mutually exclusive flags
@@ -238,12 +241,19 @@ func run(cmd *cobra.Command, args []string) error {
238241

239242
// Apply command-line flags to config
240243
cfg.EnableDIFC = enableDIFC
244+
cfg.ParallelLaunch = parallelLaunch
241245
if enableDIFC {
242246
log.Println("DIFC enforcement and session requirement enabled")
243247
} else {
244248
log.Println("DIFC enforcement disabled (sessions auto-created for standard MCP client compatibility)")
245249
}
246250

251+
if parallelLaunch {
252+
log.Println("Parallel server launching enabled")
253+
} else {
254+
log.Println("Sequential server launching enabled")
255+
}
256+
247257
// Determine mode (default to unified if neither flag is set)
248258
mode := "unified"
249259
if routedMode {

internal/config/config.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ const (
2424

2525
// Config represents the MCPG configuration
2626
type Config struct {
27-
Servers map[string]*ServerConfig `toml:"servers"`
28-
EnableDIFC bool `toml:"enable_difc"` // When true, enables DIFC enforcement and requires sys___init call before tool access. Default is false for standard MCP client compatibility.
29-
Gateway *GatewayConfig `toml:"gateway"` // Gateway configuration (port, API key, etc.)
27+
Servers map[string]*ServerConfig `toml:"servers"`
28+
EnableDIFC bool `toml:"enable_difc"` // When true, enables DIFC enforcement and requires sys___init call before tool access. Default is false for standard MCP client compatibility.
29+
ParallelLaunch bool `toml:"parallel_launch"` // When true (default), launches MCP servers in parallel during startup.
30+
Gateway *GatewayConfig `toml:"gateway"` // Gateway configuration (port, API key, etc.)
3031
}
3132

3233
// GatewayConfig represents gateway-level configuration

internal/server/unified.go

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,15 @@ type ToolInfo struct {
7474

7575
// UnifiedServer implements a unified MCP server that aggregates multiple backend servers
7676
type UnifiedServer struct {
77-
launcher *launcher.Launcher
78-
sysServer *sys.SysServer
79-
ctx context.Context
80-
server *sdk.Server
81-
sessions map[string]*Session // mcp-session-id -> Session
82-
sessionMu sync.RWMutex
83-
tools map[string]*ToolInfo // prefixed tool name -> tool info
84-
toolsMu sync.RWMutex
77+
launcher *launcher.Launcher
78+
sysServer *sys.SysServer
79+
ctx context.Context
80+
server *sdk.Server
81+
sessions map[string]*Session // mcp-session-id -> Session
82+
sessionMu sync.RWMutex
83+
tools map[string]*ToolInfo // prefixed tool name -> tool info
84+
toolsMu sync.RWMutex
85+
parallelLaunch bool // When true (default), launches MCP servers in parallel during startup
8586

8687
// DIFC components
8788
guardRegistry *guard.Registry
@@ -101,15 +102,16 @@ type UnifiedServer struct {
101102

102103
// NewUnified creates a new unified MCP server
103104
func NewUnified(ctx context.Context, cfg *config.Config) (*UnifiedServer, error) {
104-
logUnified.Printf("Creating new unified server: enableDIFC=%v, servers=%d", cfg.EnableDIFC, len(cfg.Servers))
105+
logUnified.Printf("Creating new unified server: enableDIFC=%v, parallelLaunch=%v, servers=%d", cfg.EnableDIFC, cfg.ParallelLaunch, len(cfg.Servers))
105106
l := launcher.New(ctx, cfg)
106107

107108
us := &UnifiedServer{
108-
launcher: l,
109-
sysServer: sys.NewSysServer(l.ServerIDs()),
110-
ctx: ctx,
111-
sessions: make(map[string]*Session),
112-
tools: make(map[string]*ToolInfo),
109+
launcher: l,
110+
sysServer: sys.NewSysServer(l.ServerIDs()),
111+
ctx: ctx,
112+
sessions: make(map[string]*Session),
113+
tools: make(map[string]*ToolInfo),
114+
parallelLaunch: cfg.ParallelLaunch,
113115

114116
// Initialize DIFC components
115117
guardRegistry: guard.NewRegistry(),
@@ -141,6 +143,13 @@ func NewUnified(ctx context.Context, cfg *config.Config) (*UnifiedServer, error)
141143
return us, nil
142144
}
143145

146+
// launchResult stores the result of a backend server launch
147+
type launchResult struct {
148+
serverID string
149+
err error
150+
duration time.Duration
151+
}
152+
144153
// registerAllTools fetches and registers tools from all backend servers
145154
func (us *UnifiedServer) registerAllTools() error {
146155
log.Println("Registering tools from all backends...")
@@ -157,8 +166,22 @@ func (us *UnifiedServer) registerAllTools() error {
157166
log.Println("DIFC disabled: skipping sys tools registration")
158167
}
159168

160-
// Register tools from each backend server
161-
for _, serverID := range us.launcher.ServerIDs() {
169+
serverIDs := us.launcher.ServerIDs()
170+
171+
if us.parallelLaunch {
172+
// Launch servers in parallel
173+
return us.registerAllToolsParallel(serverIDs)
174+
} else {
175+
// Launch servers sequentially (original behavior)
176+
return us.registerAllToolsSequential(serverIDs)
177+
}
178+
}
179+
180+
// registerAllToolsSequential registers tools from backend servers sequentially
181+
func (us *UnifiedServer) registerAllToolsSequential(serverIDs []string) error {
182+
logUnified.Printf("Registering tools sequentially from %d backends", len(serverIDs))
183+
184+
for _, serverID := range serverIDs {
162185
logUnified.Printf("Registering tools from backend: %s", serverID)
163186
if err := us.registerToolsFromBackend(serverID); err != nil {
164187
log.Printf("Warning: failed to register tools from %s: %v", serverID, err)
@@ -170,6 +193,55 @@ func (us *UnifiedServer) registerAllTools() error {
170193
return nil
171194
}
172195

196+
// registerAllToolsParallel registers tools from backend servers in parallel
197+
func (us *UnifiedServer) registerAllToolsParallel(serverIDs []string) error {
198+
logUnified.Printf("Registering tools in parallel from %d backends", len(serverIDs))
199+
200+
var wg sync.WaitGroup
201+
results := make(chan launchResult, len(serverIDs))
202+
203+
// Launch each server in its own goroutine
204+
for _, serverID := range serverIDs {
205+
wg.Add(1)
206+
go func(sid string) {
207+
defer wg.Done()
208+
209+
startTime := time.Now()
210+
err := us.registerToolsFromBackend(sid)
211+
duration := time.Since(startTime)
212+
213+
results <- launchResult{
214+
serverID: sid,
215+
err: err,
216+
duration: duration,
217+
}
218+
}(serverID)
219+
}
220+
221+
// Wait for all goroutines to complete
222+
wg.Wait()
223+
close(results)
224+
225+
// Collect and log results
226+
successCount := 0
227+
failureCount := 0
228+
for result := range results {
229+
if result.err != nil {
230+
log.Printf("Warning: failed to register tools from %s (took %v): %v", result.serverID, result.duration, result.err)
231+
logger.LogWarn("backend", "Failed to register tools from %s (took %v): %v", result.serverID, result.duration, result.err)
232+
failureCount++
233+
} else {
234+
logUnified.Printf("Successfully registered tools from %s (took %v)", result.serverID, result.duration)
235+
logger.LogInfo("backend", "Successfully registered tools from %s (took %v)", result.serverID, result.duration)
236+
successCount++
237+
}
238+
}
239+
240+
log.Printf("Parallel tool registration complete: %d succeeded, %d failed, total tools=%d", successCount, failureCount, len(us.tools))
241+
logUnified.Printf("Tool registration complete: %d succeeded, %d failed, total tools=%d", successCount, failureCount, len(us.tools))
242+
return nil
243+
}
244+
173245
// registerToolsFromBackend registers tools from a specific backend with <server>___<tool> naming
174246
func (us *UnifiedServer) registerToolsFromBackend(serverID string) error {
175247
log.Printf("Registering tools from backend: %s", serverID)

internal/server/unified_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,3 +492,31 @@ func TestRequireSession_EdgeCases(t *testing.T) {
492492
})
493493
}
494494
}
495+
496+
func TestUnifiedServer_ParallelLaunch_Enabled(t *testing.T) {
497+
cfg := &config.Config{
498+
Servers: map[string]*config.ServerConfig{},
499+
ParallelLaunch: true,
500+
}
501+
502+
ctx := context.Background()
503+
us, err := NewUnified(ctx, cfg)
504+
require.NoError(t, err, "NewUnified() failed")
505+
defer us.Close()
506+
507+
assert.True(t, us.parallelLaunch, "ParallelLaunch should be enabled when configured")
508+
}
509+
510+
func TestUnifiedServer_ParallelLaunch_Disabled(t *testing.T) {
511+
cfg := &config.Config{
512+
Servers: map[string]*config.ServerConfig{},
513+
ParallelLaunch: false,
514+
}
515+
516+
ctx := context.Background()
517+
us, err := NewUnified(ctx, cfg)
518+
require.NoError(t, err, "NewUnified() failed")
519+
defer us.Close()
520+
521+
assert.False(t, us.parallelLaunch, "ParallelLaunch should be disabled when configured")
522+
}

0 commit comments

Comments
 (0)