Skip to content

Commit f5f576b

Browse files
keugenekclaude
andauthored
Handle shutdown signals gracefully in MCP server (#4227)
## Summary - Add signal handling for SIGINT and SIGTERM in MCP server - Call `Shutdown()` method when signals are received - Exit cleanly with code 0 instead of crashing ## Problem When Claude Agent SDK terminates the MCP server process, the Go process doesn't handle signals gracefully. This causes the process to crash during cleanup, resulting in "Command failed with exit code 1" error that propagates back to the SDK. This is a known issue pattern documented in: - anthropics/claude-code#7718 (SIGABRT during MCP shutdown) - anthropics/claude-code#5506 (MCP Server Shutdown Failure) ## Solution Run the server in a goroutine and wait for either: 1. Server completion (EOF, error, or context cancellation) 2. Shutdown signal (SIGINT or SIGTERM) When a signal is received, cancel the context and call `Shutdown()` to clean up resources gracefully. ## Test plan - [x] Build succeeds - [ ] Manual test with Claude Agent SDK - verify no "exit code 1" errors on session end 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d35b0e3 commit f5f576b

File tree

1 file changed

+35
-3
lines changed

1 file changed

+35
-3
lines changed

experimental/apps-mcp/cmd/apps_mcp.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package mcp
22

33
import (
4+
"context"
5+
"os"
6+
"os/signal"
7+
"syscall"
8+
49
mcplib "github.com/databricks/cli/experimental/apps-mcp/lib"
510
"github.com/databricks/cli/experimental/apps-mcp/lib/server"
611
"github.com/databricks/cli/libs/log"
@@ -25,7 +30,13 @@ The server communicates via stdio using the Model Context Protocol.`,
2530
Example: ` # Start MCP server with required warehouse
2631
databricks experimental apps-mcp --warehouse-id abc123`,
2732
RunE: func(cmd *cobra.Command, args []string) error {
28-
ctx := cmd.Context()
33+
// Create cancellable context for graceful shutdown
34+
ctx, cancel := context.WithCancel(cmd.Context())
35+
defer cancel()
36+
37+
// Handle shutdown signals (SIGINT, SIGTERM)
38+
sigCh := make(chan os.Signal, 1)
39+
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
2940

3041
// Build MCP config from flags
3142
cfg := &mcplib.Config{}
@@ -41,8 +52,29 @@ The server communicates via stdio using the Model Context Protocol.`,
4152
return err
4253
}
4354

44-
// Run server
45-
return srv.Run(ctx)
55+
// Run server in goroutine so we can handle signals
56+
errCh := make(chan error, 1)
57+
go func() {
58+
errCh <- srv.Run(ctx)
59+
}()
60+
61+
// Wait for either server error or shutdown signal
62+
select {
63+
case err := <-errCh:
64+
// Server stopped (EOF, error, or context cancelled)
65+
if shutdownErr := srv.Shutdown(ctx); shutdownErr != nil {
66+
log.Warnf(ctx, "Shutdown error: %v", shutdownErr)
67+
}
68+
return err
69+
case sig := <-sigCh:
70+
// Received shutdown signal - exit gracefully
71+
log.Infof(ctx, "Received signal %v, shutting down gracefully", sig)
72+
cancel() // Cancel context to stop server.Run()
73+
if shutdownErr := srv.Shutdown(ctx); shutdownErr != nil {
74+
log.Warnf(ctx, "Shutdown error: %v", shutdownErr)
75+
}
76+
return nil
77+
}
4678
},
4779
}
4880

0 commit comments

Comments
 (0)