From 1dd332adf75d8a003c8db2e0d033e1a7d4a651bf Mon Sep 17 00:00:00 2001 From: Algis Dumbris Date: Mon, 16 Mar 2026 17:59:23 +0200 Subject: [PATCH] fix: OAuth token introspection "context canceled" on concurrent MCP connections Decouple fosite token introspection from the HTTP request context using context.WithoutCancel + 10s timeout. When claude.ai opens multiple concurrent MCP connections and one disconnects, the token validation for subsequent connections no longer fails with "context canceled". Fixes Bug #6 from #bugs-synapbus. Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/agents/middleware.go | 10 +++++++--- internal/auth/middleware.go | 8 ++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/internal/agents/middleware.go b/internal/agents/middleware.go index 761da0c..dafb503 100644 --- a/internal/agents/middleware.go +++ b/internal/agents/middleware.go @@ -229,9 +229,13 @@ func RequiredAuthMiddlewareWithOAuth(service *AgentService, keyService *apikeys. // resolveOAuthToken introspects an OAuth bearer token and extracts agent identity. func resolveOAuthToken(ctx context.Context, provider fosite.OAuth2Provider, token string, service *AgentService) (agentName string, ownerID string, ok bool) { - // Use a fositeSession-compatible struct for introspection. - // We import the type indirectly through the fosite interface. - _, ar, err := provider.IntrospectToken(ctx, token, fosite.AccessToken, &oauthIntrospectSession{}) + // Decouple from the HTTP request context so token introspection completes + // even if the client disconnects (fixes "context canceled" errors during + // concurrent MCP connections from claude.ai). + dbCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 10*time.Second) + defer cancel() + + _, ar, err := provider.IntrospectToken(dbCtx, token, fosite.AccessToken, &oauthIntrospectSession{}) if err != nil { return "", "", false } diff --git a/internal/auth/middleware.go b/internal/auth/middleware.go index d641360..78fc76a 100644 --- a/internal/auth/middleware.go +++ b/internal/auth/middleware.go @@ -5,6 +5,7 @@ import ( "log/slog" "net/http" "strings" + "time" "github.com/ory/fosite" ) @@ -107,8 +108,11 @@ func RequireBearer(provider fosite.OAuth2Provider, userStore UserStore) func(htt token := parts[1] _ = token - // Use fosite introspection - _, ar, err := provider.IntrospectToken(r.Context(), parts[1], fosite.AccessToken, new(fositeSession)) + // Use fosite introspection — decouple from HTTP request context so + // token validation completes even if the client disconnects. + dbCtx, cancel := context.WithTimeout(context.WithoutCancel(r.Context()), 10*time.Second) + _, ar, err := provider.IntrospectToken(dbCtx, parts[1], fosite.AccessToken, new(fositeSession)) + cancel() if err != nil { slog.Debug("bearer token validation failed", "error", err) w.Header().Set("WWW-Authenticate", `Bearer error="invalid_token"`)