Fix: Handle logging/setLevel request for server (#358)
Fix #293 Also fixed a bug with initialization: properties must be declared before the init block; otherwise, the initialization order is violated and an NPE will occur in the init block. ## Motivation and Context After initialization, MCP clients send a `logging/setLevel` request if the logging capability is enabled. This request was previously not handled, leading to an error that blocked the use of the MCP server in the inspector and other clients. ## How Has This Been Tested? Added integration test ## Breaking Changes None ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed
This commit is contained in:
parent
a75e7a8197
commit
ab9ba54b33
1 changed files with 146 additions and 19 deletions
|
|
@ -16,6 +16,7 @@ import io.modelcontextprotocol.kotlin.sdk.InitializedNotification
|
|||
import io.modelcontextprotocol.kotlin.sdk.LATEST_PROTOCOL_VERSION
|
||||
import io.modelcontextprotocol.kotlin.sdk.ListRootsRequest
|
||||
import io.modelcontextprotocol.kotlin.sdk.ListRootsResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.LoggingLevel
|
||||
import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification
|
||||
import io.modelcontextprotocol.kotlin.sdk.Method
|
||||
import io.modelcontextprotocol.kotlin.sdk.Method.Defined
|
||||
|
|
@ -27,6 +28,8 @@ import io.modelcontextprotocol.kotlin.sdk.SUPPORTED_PROTOCOL_VERSIONS
|
|||
import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification
|
||||
import io.modelcontextprotocol.kotlin.sdk.shared.Protocol
|
||||
import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions
|
||||
import kotlinx.atomicfu.AtomicRef
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
|
|
@ -43,22 +46,6 @@ public open class ServerSession(
|
|||
@Suppress("ktlint:standard:backing-property-naming")
|
||||
private var _onClose: () -> Unit = {}
|
||||
|
||||
init {
|
||||
// Core protocol handlers
|
||||
setRequestHandler<InitializeRequest>(Method.Defined.Initialize) { request, _ ->
|
||||
handleInitialize(request)
|
||||
}
|
||||
setNotificationHandler<InitializedNotification>(Method.Defined.NotificationsInitialized) {
|
||||
_onInitialized()
|
||||
CompletableDeferred(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The capabilities supported by the server, related to the session.
|
||||
*/
|
||||
private val serverCapabilities = options.capabilities
|
||||
|
||||
/**
|
||||
* The client's reported capabilities after initialization.
|
||||
*/
|
||||
|
|
@ -71,6 +58,37 @@ public open class ServerSession(
|
|||
public var clientVersion: Implementation? = null
|
||||
private set
|
||||
|
||||
/**
|
||||
* The capabilities supported by the server, related to the session.
|
||||
*/
|
||||
private val serverCapabilities = options.capabilities
|
||||
|
||||
/**
|
||||
* The current logging level set by the client.
|
||||
* When null, all messages are sent (no filtering).
|
||||
*/
|
||||
private val currentLoggingLevel: AtomicRef<LoggingLevel?> = atomic(null)
|
||||
|
||||
init {
|
||||
// Core protocol handlers
|
||||
setRequestHandler<InitializeRequest>(Defined.Initialize) { request, _ ->
|
||||
handleInitialize(request)
|
||||
}
|
||||
setNotificationHandler<InitializedNotification>(Defined.NotificationsInitialized) {
|
||||
_onInitialized()
|
||||
CompletableDeferred(Unit)
|
||||
}
|
||||
|
||||
// Logging level handler
|
||||
if (options.capabilities.logging != null) {
|
||||
setRequestHandler<LoggingMessageNotification.SetLevelRequest>(Defined.LoggingSetLevel) { request, _ ->
|
||||
currentLoggingLevel.value = request.level
|
||||
logger.debug { "Logging level set to: ${request.level}" }
|
||||
EmptyRequestResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to be invoked when the server has completed initialization.
|
||||
*/
|
||||
|
|
@ -160,12 +178,20 @@ public open class ServerSession(
|
|||
|
||||
/**
|
||||
* Sends a logging message notification to the client.
|
||||
* Messages are filtered based on the current logging level set by the client.
|
||||
* If no logging level is set, all messages are sent.
|
||||
*
|
||||
* @param notification The logging message notification.
|
||||
*/
|
||||
public suspend fun sendLoggingMessage(notification: LoggingMessageNotification) {
|
||||
logger.trace { "Sending logging message: ${notification.params.data}" }
|
||||
notification(notification)
|
||||
if (serverCapabilities.logging != null) {
|
||||
if (isMessageAccepted(notification.params.level)) {
|
||||
logger.trace { "Sending logging message: ${notification.params.data}" }
|
||||
notification(notification)
|
||||
} else {
|
||||
logger.trace { "Filtering out logging message with level ${notification.params.level}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -318,6 +344,7 @@ public open class ServerSession(
|
|||
|
||||
Defined.LoggingSetLevel -> {
|
||||
if (serverCapabilities.logging == null) {
|
||||
logger.error { "Server does not support logging (required for $method)" }
|
||||
throw IllegalStateException("Server does not support logging (required for $method)")
|
||||
}
|
||||
}
|
||||
|
|
@ -381,4 +408,24 @@ public open class ServerSession(
|
|||
instructions = instructions,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a message with the given level should be ignored based on the current logging level.
|
||||
*
|
||||
* @param level The level of the message to check.
|
||||
* @return true if the message should be ignored (filtered out), false otherwise.
|
||||
*/
|
||||
private fun isMessageIgnored(level: LoggingLevel): Boolean {
|
||||
val current = currentLoggingLevel.value ?: return false // If no level is set, don't filter
|
||||
|
||||
return level.ordinal < current.ordinal
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a message with the given level should be accepted based on the current logging level.
|
||||
*
|
||||
* @param level The level of the message to check.
|
||||
* @return true if the message should be accepted (not filtered out), false otherwise.
|
||||
*/
|
||||
private fun isMessageAccepted(level: LoggingLevel): Boolean = !isMessageIgnored(level)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue