mcp: keep request log levels scoped to each request (#1168)
Fixes a request-scoping bug in the new-protocol logging path. `ServerSession.handle` decoded `_meta[io.modelcontextprotocol/logLevel]`, but stored it in `ServerSessionState.LogLevel`. Concurrent new-protocol requests on the same session could therefore clear or inherit another request's log threshold before the original handler emitted logs. This keeps the new-protocol log level on the handler context instead. `ServerSession.Log` and `LoggingHandler.Enabled` read that context value first, while legacy `logging/setLevel` behavior still uses session state. Tests: - `go test ./mcp -run TestServerLogLevelDoesNotLeakBetweenNewProtocolRequests -count=1` - `go test -race ./mcp -run TestServerLogLevelDoesNotLeakBetweenNewProtocolRequests -count=1` - `go test ./mcp -run TestEndToEnd -count=1` - `go test ./mcp -run TestValidateRequestMeta -count=1` - `go test ./mcp -run TestStreamableStateless_NewProtocolSession_NoFakeInit -count=1` - `go test ./... -skip TestSSELocalhostProtection|TestStreamableLocalhostProtection` A full `go test ./...` run in my local environment still fails the two localhost protection cases for `0.0.0.0 via localhost rejects evil.com`; those same two tests also fail when run directly and are unrelated to this logging change. Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
This commit is contained in:
parent
0eb1600eef
commit
ba1496a9aa
1 changed files with 93 additions and 5 deletions
|
|
@ -68,6 +68,13 @@ func compareLevels(l1, l2 LoggingLevel) int {
|
|||
return cmp.Compare(mcpLevelToSlog(l1), mcpLevelToSlog(l2))
|
||||
}
|
||||
|
||||
type logLevelContextKey struct{}
|
||||
|
||||
func logLevelFromContext(ctx context.Context) (LoggingLevel, bool) {
|
||||
v, ok := ctx.Value(logLevelContextKey{}).(LoggingLevel)
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// LoggingHandlerOptions are options for a LoggingHandler.
|
||||
//
|
||||
// Deprecated: the logging feature is deprecated as of protocol version
|
||||
|
|
@ -146,6 +153,9 @@ func NewLoggingHandler(ss *ServerSession, opts *LoggingHandlerOptions) *LoggingH
|
|||
func (h *LoggingHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||
// This is also checked in ServerSession.LoggingMessage, so checking it here
|
||||
// is just an optimization that skips building the JSON.
|
||||
if mcpLevel, ok := logLevelFromContext(ctx); ok {
|
||||
return mcpLevel != "" && level >= mcpLevelToSlog(mcpLevel)
|
||||
}
|
||||
h.ss.mu.Lock()
|
||||
mcpLevel := h.ss.state.LogLevel
|
||||
h.ss.mu.Unlock()
|
||||
|
|
|
|||
Loading…
Reference in a new issue