mcp: fix context propagation in StreamableClientTransport (#514)
## Problem Context propagation was broken in `StreamableClientTransport` because: 1. `Connect()` used `context.Background()` instead of the parent context 2. `Close()` created a race condition where DELETE requests were cancelled before completion ## Solution - Use parent context when creating the connection context in `Connect()` - Reorder `Close()` operations to perform cleanup DELETE before cancelling context ## Impact - Request-scoped values (auth tokens, trace IDs) now propagate correctly to background HTTP operations - Eliminates race condition in cleanup operations - Maintains proper Go context semantics Fixes #513
This commit is contained in:
parent
33ff851ef3
commit
5d64d61974
1 changed files with 65 additions and 10 deletions
|
|
@ -1310,8 +1310,9 @@ func textContent(t *testing.T, res *CallToolResult) string {
|
|||
}
|
||||
|
||||
func TestTokenInfo(t *testing.T) {
|
||||
defer func(b bool) { testAuth = b }(testAuth)
|
||||
testAuth = true
|
||||
oldAuth := testAuth.Load()
|
||||
defer testAuth.Store(oldAuth)
|
||||
testAuth.Store(true)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a server with a tool that returns TokenInfo.
|
||||
|
|
@ -1430,3 +1431,57 @@ func TestStreamableGET(t *testing.T) {
|
|||
t.Errorf("GET with session ID: got status %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamableClientContextPropagation(t *testing.T) {
|
||||
type contextKey string
|
||||
const testKey = contextKey("test-key")
|
||||
const testValue = "test-value"
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
ctx2 := context.WithValue(ctx, testKey, testValue)
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
switch req.Method {
|
||||
case "POST":
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Mcp-Session-Id", "test-session")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-03-26","capabilities":{},"serverInfo":{"name":"test","version":"1.0"}}}`))
|
||||
case "GET":
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case "DELETE":
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
transport := &StreamableClientTransport{Endpoint: server.URL}
|
||||
conn, err := transport.Connect(ctx2)
|
||||
if err != nil {
|
||||
t.Fatalf("Connect failed: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
streamableConn, ok := conn.(*streamableClientConn)
|
||||
if !ok {
|
||||
t.Fatalf("Expected *streamableClientConn, got %T", conn)
|
||||
}
|
||||
|
||||
if got := streamableConn.ctx.Value(testKey); got != testValue {
|
||||
t.Errorf("Context value not propagated: got %v, want %v", got, testValue)
|
||||
}
|
||||
|
||||
if streamableConn.ctx.Done() == nil {
|
||||
t.Error("Connection context is not cancellable")
|
||||
}
|
||||
|
||||
cancel()
|
||||
select {
|
||||
case <-streamableConn.ctx.Done():
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Error("Connection context was not cancelled when parent was cancelled")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue