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:
King Star 2026-08-17 20:00:41 +08:00 • committed by GitHub
commit ba1496a9aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1739,9 +1739,12 @@ func (ss *ServerSession) Elicit(ctx context.Context, params *ElicitParams) (*Eli
// (at least twelve months). See
// https://modelcontextprotocol.io/seps/2577-deprecate-roots-sampling-and-logging.
func (ss *ServerSession) Log(ctx context.Context, params *LoggingMessageParams) error {
ss.mu.Lock()
logLevel := ss.state.LogLevel
ss.mu.Unlock()
logLevel, ok := logLevelFromContext(ctx)
if !ok {
ss.mu.Lock()
logLevel = ss.state.LogLevel
ss.mu.Unlock()
}
if logLevel == "" {
// The spec is unclear, but seems to imply that no log messages are sent until the client
// sets the level.
@ -1929,9 +1932,8 @@ func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any,
// server->client calls and notifications to the incoming request from which
// they originated. See [idContextKey] for details.
ctx = context.WithValue(ctx, idContextKey{}, req.ID)
// For new-protocol requests, propagate the per-request log level.
if validatedMeta.usesNewProtocol {
ss.setLevel(ctx, &SetLoggingLevelParams{Level: validatedMeta.logLevel})
ctx = context.WithValue(ctx, logLevelContextKey{}, validatedMeta.logLevel)
}
res, err := handleReceive(ctx, ss, req)
if err != nil {