blob: 97a4472440ea51045cf83086445c6880c0d6ee1f [file]
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of 'server.dart';
/// A mixin for MCP servers which support the `logging` capability.
///
/// See https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging/.
base mixin LoggingSupport on MCPServer {
/// The level at or above which [log] sends messages, or `null` to send none.
///
/// On 2026-07-28 `initialize` sets this to
/// [MCPServerInitialization.logLevel], overwriting any default the server
/// chose for itself. Earlier revisions keep that default, start at
/// [LoggingLevel.warning] when there is none, and serve `logging/setLevel`
/// to move it.
LoggingLevel? loggingLevel;
@override
FutureOr<void> initialize(MCPServerInitialization initialization) async {
if (initialization.protocolVersion < ProtocolVersion.v2026_07_28) {
loggingLevel ??= LoggingLevel.warning;
registerRequestHandler(SetLevelRequest.methodName, handleSetLevel);
} else {
// Assign instead of `??=`. 2026-07-28 took `logging/setLevel` out, so a
// default the server picked for itself would otherwise survive here and
// log on a request that asked for no level.
loggingLevel = initialization.logLevel;
}
await super.initialize(initialization);
capabilities.logging ??= Logging();
}
/// Sends a [LoggingMessageNotification] to the client, if [loggingLevel] is
/// set and is <= [level].
///
/// The [data] must either be some json serializable object, or a function
/// which takes no arguments and returns some json serializable object.
///
/// if [data] is a function then it must take zero arguments and return a
/// non-nullable result. It will only be invoked if the log message
/// will actually be sent.
///
/// If [data] is any other type of function, an [ArgumentError] will be
/// thrown.
void log(LoggingLevel level, Object data, {String? logger, Meta? meta}) {
final threshold = loggingLevel;
if (threshold == null || threshold > level) return;
if (data is Function) {
if (data is Object Function()) {
data = data();
} else {
throw ArgumentError.value(
data,
'data',
'When logging a lazily evaluated function, it must be of type '
'`Object Function()`, but the given function type was '
'`${data.runtimeType}`.',
);
}
}
sendNotification(
LoggingMessageNotification.methodName,
LoggingMessageNotification(
level: level,
data: data,
logger: logger,
meta: meta,
),
);
}
/// Handle a client request to change the logging level.
///
/// Registered for `logging/setLevel` on the revisions that still have the
/// method, and not on 2026-07-28. `handleStreamableHttpRequest` answers it
/// with `404` on that revision anyway, so this is what a transport calling
/// `handleRequestScopedMessage` on its own gets instead.
FutureOr<EmptyResult> handleSetLevel(SetLevelRequest request) {
loggingLevel = request.level;
return EmptyResult();
}
}