@@ -38,8 +38,22 @@ func parseLevel(level mcp.LoggingLevel) slog.Level {
41+// ClientSession wraps an mcp.ClientSession with a context cancel function so
42+// that the context created during session establishment is properly cleaned up
44+type ClientSession struct {
46+ cancel context.CancelFunc
49+// Close cancels the session context and then closes the underlying session.
50+func (s *ClientSession) Close() error {
52+ return s.ClientSession.Close()
42− sessions = csync.NewMap[string, *mcp.ClientSession]()
56+ sessions = csync.NewMap[string, *ClientSession]()
43 states = csync.NewMap[string, ClientInfo]()
57 states = csync.NewMap[string, ClientInfo]()
44 broker = pubsub.NewBroker[Event]()
58 broker = pubsub.NewBroker[Event]()
45 initOnce sync.Once
59 initOnce sync.Once
@@ -102,7 +116,7 @@ type ClientInfo struct {
102 Name string
116 Name string
103 State State
117 State State
104 Error error
118 Error error
105− Client *mcp.ClientSession
119+ Client *ClientSession
106 Counts Counts
120 Counts Counts
107 ConnectedAt time.Time
121 ConnectedAt time.Time
@@ -239,7 +253,7 @@ func WaitForInit(ctx context.Context) error {
242−func getOrRenewClient(ctx context.Context, cfg *config.Config, name string) (*mcp.ClientSession, error) {
256+func getOrRenewClient(ctx context.Context, cfg *config.Config, name string) (*ClientSession, error) {
243 sess, ok := sessions.Get(name)
257 sess, ok := sessions.Get(name)
245 return nil, fmt.Errorf("mcp '%s' not available", name)
259 return nil, fmt.Errorf("mcp '%s' not available", name)
@@ -268,7 +282,7 @@ func getOrRenewClient(ctx context.Context, cfg *config.Config, name string) (*mc
270// updateState updates the state of an MCP client and publishes an event
284// updateState updates the state of an MCP client and publishes an event
271−func updateState(name string, state State, err error, client *mcp.ClientSession, counts Counts) {
285+func updateState(name string, state State, err error, client *ClientSession, counts Counts) {
272 info := ClientInfo{
286 info := ClientInfo{
273 Name: name,
287 Name: name,
274 State: state,
288 State: state,
@@ -294,7 +308,7 @@ func updateState(name string, state State, err error, client *mcp.ClientSession,
297−func createSession(ctx context.Context, name string, m config.MCPConfig, resolver config.VariableResolver) (*mcp.ClientSession, error) {
311+func createSession(ctx context.Context, name string, m config.MCPConfig, resolver config.VariableResolver) (*ClientSession, error) {
298 timeout := mcpTimeout(m)
312 timeout := mcpTimeout(m)
299 mcpCtx, cancel := context.WithCancel(ctx)
313 mcpCtx, cancel := context.WithCancel(ctx)
300 cancelTimer := time.AfterFunc(timeout, cancel)
314 cancelTimer := time.AfterFunc(timeout, cancel)
@@ -352,7 +366,7 @@ func createSession(ctx context.Context, name string, m config.MCPConfig, resolve
353 cancelTimer.Stop()
367 cancelTimer.Stop()
354 slog.Debug("MCP client initialized", "name", name)
368 slog.Debug("MCP client initialized", "name", name)
355− return session, nil
369+ return &ClientSession{session, cancel}, nil
358// maybeStdioErr if a stdio mcp prints an error in non-json format, it'll fail
372// maybeStdioErr if a stdio mcp prints an error in non-json format, it'll fail