diff --git a/internal/auth/header.go b/internal/auth/header.go index eb4f8e52..f2311ec6 100644 --- a/internal/auth/header.go +++ b/internal/auth/header.go @@ -165,3 +165,16 @@ func ExtractSessionID(authHeader string) string { log.Print("Using plain API key as session ID") return authHeader } + +// TruncateSessionID returns a truncated session ID for safe logging (first 8 chars). +// Returns "(none)" for empty session IDs, and appends "..." for truncated values. +// This is useful for logging session IDs without exposing sensitive information. +func TruncateSessionID(sessionID string) string { + if sessionID == "" { + return "(none)" + } + if len(sessionID) <= 8 { + return sessionID + } + return sessionID[:8] + "..." +} diff --git a/internal/server/auth.go b/internal/server/auth.go index 6e3fb7a5..d7d450f3 100644 --- a/internal/server/auth.go +++ b/internal/server/auth.go @@ -5,7 +5,6 @@ import ( "net/http" "time" - "github.com/githubnext/gh-aw-mcpg/internal/auth" "github.com/githubnext/gh-aw-mcpg/internal/logger" ) @@ -42,16 +41,6 @@ func authMiddleware(apiKey string, next http.HandlerFunc) http.HandlerFunc { } } -// extractSessionFromAuth extracts session ID from Authorization header. -// This function delegates to auth.ExtractSessionID for consistent session ID extraction. -// Per spec 7.1: When API key is configured, Authorization contains plain API key. -// When API key is not configured, supports Bearer token for backward compatibility. -// -// Deprecated: Use auth.ExtractSessionID directly instead. -func extractSessionFromAuth(authHeader string) string { - return auth.ExtractSessionID(authHeader) -} - // logRuntimeError logs runtime errors to stdout per spec section 9.2 func logRuntimeError(errorType, detail string, r *http.Request, serverName *string) { timestamp := time.Now().UTC().Format(time.RFC3339) diff --git a/internal/server/routed.go b/internal/server/routed.go index fa297935..39b4e682 100644 --- a/internal/server/routed.go +++ b/internal/server/routed.go @@ -9,6 +9,7 @@ import ( "net/http" "sync" + "github.com/githubnext/gh-aw-mcpg/internal/auth" "github.com/githubnext/gh-aw-mcpg/internal/logger" "github.com/githubnext/gh-aw-mcpg/internal/mcp" sdk "github.com/modelcontextprotocol/go-sdk/mcp" @@ -18,10 +19,12 @@ var logRouted = logger.New("server:routed") // rejectIfShutdown is a middleware that rejects requests with HTTP 503 when gateway is shutting down // Per spec 5.1.3: "Immediately reject any new RPC requests to /mcp/{server-name} endpoints with HTTP 503" -func rejectIfShutdown(unifiedServer *UnifiedServer, next http.Handler) http.Handler { +// The logNamespace parameter is used to create a logger for debug output specific to the call site. +func rejectIfShutdown(unifiedServer *UnifiedServer, next http.Handler, logNamespace string) http.Handler { + log := logger.New(logNamespace) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if unifiedServer.IsShutdown() { - logRouted.Printf("Rejecting request during shutdown: remote=%s, method=%s, path=%s", r.RemoteAddr, r.Method, r.URL.Path) + log.Printf("Rejecting request during shutdown: remote=%s, method=%s, path=%s", r.RemoteAddr, r.Method, r.URL.Path) logger.LogWarn("shutdown", "Request rejected during shutdown, remote=%s, path=%s", r.RemoteAddr, r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) @@ -109,7 +112,7 @@ func CreateHTTPServerForRoutedMode(addr string, unifiedServer *UnifiedServer, ap routeHandler := sdk.NewStreamableHTTPHandler(func(r *http.Request) *sdk.Server { // Extract session ID from Authorization header authHeader := r.Header.Get("Authorization") - sessionID := extractSessionFromAuth(authHeader) + sessionID := auth.ExtractSessionID(authHeader) // Reject requests without Authorization header if sessionID == "" { @@ -156,7 +159,7 @@ func CreateHTTPServerForRoutedMode(addr string, unifiedServer *UnifiedServer, ap // Apply shutdown check middleware (spec 5.1.3) // This must come before auth to ensure shutdown takes precedence - shutdownHandler := rejectIfShutdown(unifiedServer, loggedHandler) + shutdownHandler := rejectIfShutdown(unifiedServer, loggedHandler, "server:routed") // Apply auth middleware if API key is configured (spec 7.1) finalHandler := shutdownHandler diff --git a/internal/server/sdk_logging.go b/internal/server/sdk_logging.go index 228407dc..c9937bfb 100644 --- a/internal/server/sdk_logging.go +++ b/internal/server/sdk_logging.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/githubnext/gh-aw-mcpg/internal/auth" "github.com/githubnext/gh-aw-mcpg/internal/logger" ) @@ -45,12 +46,12 @@ func WithSDKLogging(handler http.Handler, mode string) http.Handler { // Extract session info for logging context authHeader := r.Header.Get("Authorization") - sessionID := extractSessionFromAuth(authHeader) + sessionID := auth.ExtractSessionID(authHeader) mcpSessionID := r.Header.Get("Mcp-Session-Id") // Log incoming request logSDK.Printf(">>> SDK Request [%s] session=%s mcp-session=%s method=%s path=%s", - mode, truncateSession(sessionID), truncateSession(mcpSessionID), r.Method, r.URL.Path) + mode, auth.TruncateSessionID(sessionID), auth.TruncateSessionID(mcpSessionID), r.Method, r.URL.Path) // Capture and log request body for POST requests var requestBody []byte @@ -66,7 +67,7 @@ func WithSDKLogging(handler http.Handler, mode string) http.Handler { if err := json.Unmarshal(requestBody, &jsonrpcReq); err == nil { logSDK.Printf(" JSON-RPC Request: method=%s id=%v", jsonrpcReq.Method, jsonrpcReq.ID) logger.LogDebug("sdk-frontend", "JSON-RPC request parsed: mode=%s, method=%s, id=%v, session=%s", - mode, jsonrpcReq.Method, jsonrpcReq.ID, truncateSession(sessionID)) + mode, jsonrpcReq.Method, jsonrpcReq.ID, auth.TruncateSessionID(sessionID)) } else { logSDK.Printf(" Failed to parse JSON-RPC request: %v", err) logSDK.Printf(" Raw body: %s", string(requestBody)) @@ -100,14 +101,14 @@ func WithSDKLogging(handler http.Handler, mode string) http.Handler { strings.Contains(jsonrpcResp.Error.Message, "invalid during") { logSDK.Printf(" ⚠️ PROTOCOL STATE ERROR DETECTED") logSDK.Printf(" Request method was: %s", jsonrpcReq.Method) - logSDK.Printf(" Session ID: %s", truncateSession(sessionID)) - logSDK.Printf(" MCP-Session-Id header: %s", truncateSession(mcpSessionID)) + logSDK.Printf(" Session ID: %s", auth.TruncateSessionID(sessionID)) + logSDK.Printf(" MCP-Session-Id header: %s", auth.TruncateSessionID(mcpSessionID)) logSDK.Printf(" This error indicates SDK's StreamableHTTPHandler created fresh protocol state") logger.LogWarn("sdk-frontend", "Protocol state error: mode=%s, method=%s, session=%s, mcp_session=%s, error=%q", - mode, jsonrpcReq.Method, truncateSession(sessionID), - truncateSession(mcpSessionID), jsonrpcResp.Error.Message) + mode, jsonrpcReq.Method, auth.TruncateSessionID(sessionID), + auth.TruncateSessionID(mcpSessionID), jsonrpcResp.Error.Message) } else { logger.LogError("sdk-frontend", "JSON-RPC error: mode=%s, method=%s, code=%d, message=%q", @@ -140,14 +141,3 @@ func WithSDKLogging(handler http.Handler, mode string) http.Handler { } }) } - -// truncateSession returns a truncated session ID for logging (first 8 chars) -func truncateSession(s string) string { - if s == "" { - return "(none)" - } - if len(s) <= 8 { - return s - } - return s[:8] + "..." -} diff --git a/internal/server/transport.go b/internal/server/transport.go index 7d7ac7bf..41f5734a 100644 --- a/internal/server/transport.go +++ b/internal/server/transport.go @@ -7,28 +7,13 @@ import ( "log" "net/http" + "github.com/githubnext/gh-aw-mcpg/internal/auth" "github.com/githubnext/gh-aw-mcpg/internal/logger" sdk "github.com/modelcontextprotocol/go-sdk/mcp" ) var logTransport = logger.New("server:transport") -// rejectIfShutdownUnified is a middleware that rejects requests with HTTP 503 when gateway is shutting down -// Per spec 5.1.3: "Immediately reject any new RPC requests to /mcp/{server-name} endpoints with HTTP 503" -func rejectIfShutdownUnified(unifiedServer *UnifiedServer, next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if unifiedServer.IsShutdown() { - logTransport.Printf("Rejecting request during shutdown: remote=%s, method=%s, path=%s", r.RemoteAddr, r.Method, r.URL.Path) - logger.LogWarn("shutdown", "Request rejected during shutdown, remote=%s, path=%s", r.RemoteAddr, r.URL.Path) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusServiceUnavailable) - w.Write([]byte(shutdownErrorJSON)) - return - } - next.ServeHTTP(w, r) - }) -} - // HTTPTransport wraps the SDK's HTTP transport type HTTPTransport struct { Addr string @@ -91,7 +76,7 @@ func CreateHTTPServerForMCP(addr string, unifiedServer *UnifiedServer, apiKey st // Extract session ID from Authorization header authHeader := r.Header.Get("Authorization") - sessionID := extractSessionFromAuth(authHeader) + sessionID := auth.ExtractSessionID(authHeader) // Reject requests without Authorization header if sessionID == "" { @@ -138,7 +123,7 @@ func CreateHTTPServerForMCP(addr string, unifiedServer *UnifiedServer, apiKey st // Apply shutdown check middleware (spec 5.1.3) // This must come before auth to ensure shutdown takes precedence - shutdownHandler := rejectIfShutdownUnified(unifiedServer, loggedHandler) + shutdownHandler := rejectIfShutdown(unifiedServer, loggedHandler, "server:transport") // Apply auth middleware if API key is configured (spec 7.1) finalHandler := shutdownHandler