You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Move MCP backend client lifecycle from per-request creation/destruction to session-scoped management. Clients will be created once during session initialization, reused throughout the session lifetime, and closed during session cleanup. This simplifies the client pooling architecture and ensures consistent backend state preservation.
12
+
Move MCP backend client lifecycle from per-request creation/destruction to session-scoped management. Clients will be created once during session initialization, reused throughout the session lifetime, and closed during session cleanup. This simplifies the architecture and ensures consistent backend state preservation.
13
13
14
14
## Problem Statement
15
15
@@ -130,14 +130,17 @@ Sessions already exist and have defined lifetimes (TTL-based expiration). Aligni
130
130
131
131
**Extend AfterInitialize Hook**:
132
132
133
-
The `AfterInitialize` hook in the discovery middleware already performs capability discovery for each backend. Extend this to also create and initialize MCP clients:
133
+
The `AfterInitialize` hook in the discovery middleware already performs capability discovery for each backend. Restructure this to create clients once and reuse them:
134
134
135
-
1.After capability discovery completes, iterate through all backends in the group
136
-
2. For each backend, create a client using the backend target configuration
135
+
1.Iterate through all backends in the group
136
+
2. For each backend, create a client using the backend target configuration via `createAndInitializeClient()`
137
137
3. Initialize the client immediately (MCP handshake)
138
-
4. Store successful clients in a map keyed by workload ID
139
-
5. For failed initializations: log warning and continue (partial initialization is acceptable)
140
-
6. Store the complete client map in the session via `SetBackendClients()`
138
+
4. Use this same client to perform capability discovery (call `ListCapabilities`)
139
+
5. Store the initialized client in a map keyed by workload ID
140
+
6. For failed initializations: log warning and continue (partial initialization is acceptable)
141
+
7. Store the complete client map in the session via `SetBackendClients()`
142
+
143
+
**Key Improvement**: By reusing the discovery client as the session-scoped client, we avoid redundant connection setup and better meet the stated goal of reducing overhead.
-`ListCapabilities`: Keep existing per-request pattern (only called during session init)
175
+
-**Note**: During session initialization in the discovery middleware, `createAndInitializeClient()` is called once per backend, and that same client is used for capability discovery and registered as the session-scoped client, avoiding redundant connection setup
173
176
174
177
**Location**: `pkg/vmcp/client/client.go`
175
178
176
179
#### 4. Session Cleanup Integration
177
180
178
-
The transport layer's `LocalStorage` implementation already calls `Close()` on sessions before deletion (implemented in a previous PR). We leverage this existing mechanism to close backend clients.
181
+
**Current State**: The `Session` interface does not currently have a `Close()` method. This RFC will add it.
182
+
183
+
**Required Changes**:
184
+
1. Add `Close() error` method to the `Session` interface in `pkg/transport/session/manager.go`
185
+
2. Implement `Close()` in `VMCPSession` to close all backend clients
186
+
3. Update `LocalStorage` to call `session.Close()` before deletion in `Delete()` and `DeleteExpired()` methods
187
+
188
+
**Reference**: The storage layer was refactored to use a pluggable interface in [PR #1989](https://github.com/stacklok/toolhive/pull/1989), but session cleanup hooks were not added at that time.
179
189
180
190
**VMCPSession.Close() Implementation**:
181
191
- Iterate through all clients in the `backendClients` map
@@ -185,12 +195,12 @@ The transport layer's `LocalStorage` implementation already calls `Close()` on s
185
195
186
196
**Cleanup Triggers**:
187
197
188
-
The existing session cleanup infrastructure ensures clients are closed when:
189
-
-**TTL Expiration**: Session manager's cleanup worker calls `DeleteExpired()`, which calls `Close()` on expired sessions
190
-
-**Explicit Deletion**: Manual session deletion via `Delete()`calls `Close()` before removing the session
191
-
-**Manager Shutdown**: `Stop()`calls`Close()` on all remaining sessions
198
+
By adding `Close()` to the `Session` interface and calling it from `LocalStorage`, clients will be closed when:
199
+
-**TTL Expiration**: Session manager's cleanup worker calls `DeleteExpired()`, which will call `session.Close()` on expired sessions
200
+
-**Explicit Deletion**: Manual session deletion via `Delete()`will call `session.Close()` before removing the session
201
+
-**Manager Shutdown**: `Manager.Stop()`will call`Close()` on all remaining sessions
192
202
193
-
No changes needed to the transport layer - it already has the hooks in place.
203
+
The session cleanup infrastructure already exists (from [PR #1989](https://github.com/stacklok/toolhive/pull/1989)); we're adding the `Close()` hook to integrate with it.
194
204
195
205
#### 5. Error Handling
196
206
@@ -201,14 +211,14 @@ No changes needed to the transport layer - it already has the hooks in place.
201
211
202
212
**Connection Failures During Tool Calls**:
203
213
- Return error to client (existing behavior)
204
-
- Health monitor marks backend unhealthy
205
-
-Subsequent sessions won't attempt to initialize unhealthy backends
214
+
- Health monitor marks backend unhealthy (existing behavior)
215
+
-Client initialization attempts continue for unhealthy backends (health-based filtering is future work, see Phase 4)
206
216
207
217
**Client Already Closed**:
208
218
- If session expired and client is closed, return clear error
209
219
- Client should refresh session via `/initialize` endpoint
210
220
211
-
## Security
221
+
## Security Considerations
212
222
213
223
### Threat Model
214
224
@@ -249,60 +259,64 @@ The only difference is **when** clients are created (session init vs first use),
249
259
250
260
## Alternatives Considered
251
261
252
-
### Alternative 1: Keep Pooling but Decouple from httpBackendClient
262
+
### Alternative 1: Keep Per-Request Pattern with Connection Pooling
253
263
254
-
**Approach**: Make `pooledBackendClient` accept an interface instead of embedding `*httpBackendClient`.
264
+
**Approach**: Continue creating/closing clients per request but add a connection pool underneath to reuse TCP connections.
0 commit comments