feat(core): concurrent inbound dispatch behind an initialization gate (#884)
Make inbound message handling concurrent after the initialization handshake, so a slow or peer-awaiting handler no longer blocks other messages on the same connection fixes #176, #572 ## Motivation and Context Inbound messages were processed serially on the transport read loop. One slow or peer-awaiting request handler (sampling, elicitation, roots) blocked every later message on that connection, including the responses and `notifications/cancelled` those handlers depend on which could deadlock request/response flows. ## How Has This Been Tested? New and old tests ## Breaking Changes - after the handshake, inbound handlers may run concurrently (processing stays serial during initialization). There is no opt-out flag by design, concurrency is bounded by `maxConcurrentHandlers` / `maxInFlightHandlers`. - `ProtocolOptions` / `ClientOptions` / `ServerOptions` gain new parameters with defaults - `RequestHandlerExtra` is now a coroutine-context element and is no longer user-constructible ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] 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
7937390b47
commit
beb3400064
1 changed files with 2462 additions and 191 deletions
|
|
@ -73,11 +73,13 @@ import kotlinx.collections.immutable.minus
|
|||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toPersistentSet
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
|
@ -87,11 +89,16 @@ private val logger = KotlinLogging.logger {}
|
|||
*
|
||||
* @property capabilities The capabilities this client supports.
|
||||
* @param enforceStrictCapabilities Whether to strictly enforce capabilities when interacting with the server.
|
||||
* @param handlerCoroutineContext Coroutine context for inbound handlers. See [ProtocolOptions.handlerCoroutineContext].
|
||||
*/
|
||||
public class ClientOptions(
|
||||
public val capabilities: ClientCapabilities = ClientCapabilities(),
|
||||
enforceStrictCapabilities: Boolean = true,
|
||||
) : ProtocolOptions(enforceStrictCapabilities = enforceStrictCapabilities)
|
||||
handlerCoroutineContext: CoroutineContext = Dispatchers.Default,
|
||||
) : ProtocolOptions(
|
||||
enforceStrictCapabilities = enforceStrictCapabilities,
|
||||
handlerCoroutineContext = handlerCoroutineContext,
|
||||
)
|
||||
|
||||
/**
|
||||
* Initializes and connects an MCP client using the provided clientInfo [Implementation], client options,
|
||||
|
|
@ -216,6 +223,7 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
serverInstructions = result.instructions
|
||||
|
||||
notification(InitializedNotification())
|
||||
enableConcurrentDispatch()
|
||||
} catch (error: Throwable) {
|
||||
logger.error(error) { "Failed to initialize client: ${error.message}" }
|
||||
close()
|
||||
|
|
|
|||
Loading…
Reference in a new issue