Skip to content

0.15.0

Latest

Choose a tag to compare

@devcrocod devcrocod released this 28 Jul 14:51
· 38 commits to main since this release
Immutable release. Only release title and notes can be modified.
76b8e7d

Description

This release makes inbound message handling concurrent after the initialization handshake, adds SSE heartbeats and elicitation schema validation, and fixes several Streamable HTTP response and session-lifecycle bugs.

Breaking Changes

Concurrent inbound dispatch by @devcrocod in #884

Inbound messages were processed serially on the transport read loop, so one slow or peer-awaiting handler (sampling, elicitation, roots) blocked every later message on the connection, including the responses and notifications/cancelled those handlers depend on. After the handshake, handlers may now run concurrently; processing stays serial during initialization.

  • ProtocolOptions / ClientOptions / ServerOptions gain a handlerCoroutineContext parameter (defaults to Dispatchers.Default). Concurrency is bounded internally; there is no opt-out flag by design.
  • RequestHandlerExtra is now a CoroutineContext.Element and is no longer user-constructible. It exposes requestId, method, sendRequest, and sendNotification, and can be read inside a handler via the new currentRequestHandlerExtra().
- val extra = RequestHandlerExtra()
+ val extra = currentRequestHandlerExtra()

Duplicate feature names are rejected at registration by @devcrocod in #883

addTool/addPrompt/addResource/addResourceTemplate silently replaced an existing entry and emitted a spurious list_changed notification. They now throw IllegalArgumentException and leave the existing registration intact. The batch variants (addTools/addPrompts/addResources) are all-or-nothing. To replace a feature, remove it first.

- server.addTool(name = "search", ...)  // silently overwrote
+ server.removeTool("search")
+ server.addTool(name = "search", ...)

eventStore removed from mcpStatelessStreamableHttp by @devcrocod in #909

The parameter was never read or written: a stateless endpoint answers GET with 405, so no stream exists to store or replay events on. The old overload is retained at DeprecationLevel.ERROR with a ReplaceWith migration hint. Use mcpStreamableHttp when you need resumability.

  application.mcpStatelessStreamableHttp(
      path = "/mcp",
-     eventStore = myEventStore,
  ) { server }

Features

  • Optional SSE heartbeats for Streamable HTTP servers via mcpStreamableHttp(sseHeartbeatConfig = { ... }), keeping long-lived streams alive against clients that disconnect on idle. Heartbeats stay off by default, by @UnscientificJsZhai in #761
  • Accepted form-mode elicitation content is now validated against requestedSchema; a mismatch fails createElicitation with McpException (INVALID_PARAMS) instead of reaching server code unchecked, by @rea9r in #896
  • The Streamable HTTP client now sends the standard Mcp-Method and Mcp-Name POST headers, by @AndreKalberer in #894

Fixed

  • Stateless Streamable HTTP now closes its per-request session, fixing unbounded growth of the session registry and notification subscriptions, by @KlyneChrysler in #872
  • Streamable HTTP responses keep their status and body when the client accepts only text/event-stream, by @devcrocod in #911
  • The Accept header is parsed as media ranges, so clients sending */* or application/* are matched correctly, by @devcrocod in #912
  • Malformed request params are reported as Invalid params (-32602) instead of Internal error, by @jstar0 in #886
  • Duplicate initialize errors preserve the JSON-RPC request id, by @jstar0 in #868
  • Connection teardown clears the recently-cancelled request id buffer, by @devcrocod in #892

Maintenance

  • Create the server session before connecting the client in tests, avoiding a ChannelTransport startup race, by @devcrocod in #885

Dependencies

New Contributors

Full Changelog: 0.14.0...0.15.0