feat! Add context to server call handlers#515
feat! Add context to server call handlers#515rnett wants to merge 22 commits intomodelcontextprotocol:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a Context receiver to server request handlers so tool/prompt/resource implementations can access the active ServerSession (e.g., to send notifications).
Changes:
- Introduced
Context+Handler<T, R>(suspend Context.(T) -> R) to provide session-aware handler APIs - Updated server dispatch to pass session-backed context through tool/prompt/resource handler execution
- Added an integration test verifying a tool can send a notification via
Context.session
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt | Threads ServerSession into handler execution and updates public registration APIs to use Handler |
| kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt | Introduces Handler typealias and updates registered feature handler types |
| kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Context.kt | Adds new public Context interface backed by internal ContextImpl(session) |
| integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolNotificationTest.kt | Validates tools can send notifications using Context.session |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt
Outdated
Show resolved
Hide resolved
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt
Outdated
Show resolved
Hide resolved
| return prompt.run { | ||
| ContextImpl(session).messageProvider(request) | ||
| } |
There was a problem hiding this comment.
The new context-receiver behavior is exercised for tools via the added integration test, but prompts/resources now also rely on ContextImpl(session) for execution. Add integration (or unit) tests covering at least one prompt and one resource handler using session from Context (e.g., sending a notification/log message) to ensure the new receiver wiring works consistently across all handler types.
kpavlov
left a comment
There was a problem hiding this comment.
This change makes perfect sense, and your integration test really shows it off! 👍🏻
However, merging it as it is could cause some issues.
- Let's add overloads as context is not always required and to avoid breaking compatibility.
- Also, let’s include integration tests for all types of affecter handlers.
devcrocod
left a comment
There was a problem hiding this comment.
At the moment, I don’t quite see the need for a Context and just store session, if we only want to expose a session. We could achieve the same without requiring session.someFun() to be written every time
I’ll also try to find out whether it’s possible to overload functions by lambda. in the past, Kotlin had a special annotation for this. That could help us preserve compatibility.
I’m also concerned about the naming. Since this will be part of the public API, it would be better to use more specific names. Besides potential conflicts, this could also confuse users, as code completion will frequently suggest these symbols
cc: @tiginamaria
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Context.kt
Outdated
Show resolved
Hide resolved
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt
Outdated
Show resolved
Hide resolved
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt
Show resolved
Hide resolved
|
I do not agree, that it is a kind of useless api. This is at least the second community PR: #144 Context is pretty common pattern for server implementation and it provides is a good extension point, also could be used as a place to store some user attributes... |
|
FWIW, the reason I went with |
|
RE overloads (@kpavlov), do you mean for the addTool(tool) {
// is there a Context receiver or not? - the compiler reports an overload resolution error
}The only alternative I could think of is using a different name for the new methods, like |
Yes, that makes sense. What I meant was to attach extensions to the context in this case, so that the calls are made without a session. Since in the context we have only one variable, all calls will go through the session anyway, so specifying it every time is redundant |
I checked and it looks like there’s only an annotation for overload resolution by return type |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt
Show resolved
Hide resolved
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt
Outdated
Show resolved
Hide resolved
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt
Show resolved
Hide resolved
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt
Outdated
Show resolved
Hide resolved
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Ah I see. Personally, I don't find it that verbose, but I've updated the context so that you can call the methods directly on it. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
...-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt
Outdated
Show resolved
Hide resolved
164c935 to
dbbe95b
Compare
...-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt
Outdated
Show resolved
Hide resolved
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt
Outdated
Show resolved
Hide resolved
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt
Outdated
Show resolved
Hide resolved
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt
Outdated
Show resolved
Hide resolved
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt
Outdated
Show resolved
Hide resolved
...rc/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerHandlerNotificationTest.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This reverts commit 835d9f3.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt:306
- The public handler APIs now expose ClientConnection (which is annotated @ExperimentalMcpApi) in their signatures, but Server/addTool/addPrompt/addResource themselves are only annotated with @OptIn at the class level. If the intent is for these new context-receiver handler APIs to be experimental for callers, these public methods should be annotated with @ExperimentalMcpApi (or ClientConnection should be de-experimentalized) so the opt-in requirement is clearly propagated to downstream users.
/**
* Registers a single tool. The client can then call this tool.
*
* @param tool A [Tool] object describing the tool.
* @param handler A suspend function that handles executing the tool when called by the client.
* @throws IllegalStateException If the server does not support tools.
*/
public fun addTool(tool: Tool, handler: suspend ClientConnection.(CallToolRequest) -> CallToolResult) {
check(options.capabilities.tools != null) {
logger.error { "Failed to add tool '${tool.name}': Server does not support tools capability" }
"Server does not support tools capability. Enable it in ServerOptions."
}
toolRegistry.add(RegisteredTool(tool, handler))
}
integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerHandlerNotificationTest.kt:54
- These inline comments refer to using the "session" from context, but the new receiver provided to handlers is ClientConnection (and does not directly expose the session). Consider updating the wording to avoid confusion for future readers of the test.
server.addTool(toolName, "A tool that notifies") { _ ->
// Use the session from context to send a notification
sendLoggingMessage(
LoggingMessageNotification(
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt
Outdated
Show resolved
Hide resolved
...-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Thank you, @rnett
Looks good, but I have couple of observations.
-
At Copilot mentioned, marking ClientConnection as experimental api requires OptIn for all addXxxHandler calls. This change will be disruptive for users and we do want to keep the current api shape, so it's not really experimental for the users. Let's remove ExperimentalMcpApi from ClientConnection
-
Maybe lets make ClientConnection an interface and create ClientConnectionImpl as internal class just for the sake of Dependency Inversion Principle, to have more flexibility in the future.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| // Start the ServerSession redirection section | ||
| // Start the ServerSession / ClientConnection redirection section |
There was a problem hiding this comment.
Should we expose it to the handler?
There was a problem hiding this comment.
Should we expose it to the handler?
There was a problem hiding this comment.
Should we expose it to the handler?
There was a problem hiding this comment.
Should we expose such low-level API to the handler?
There was a problem hiding this comment.
I removed some methods from ClientConnection.kt, updated tests
but overall LGTM.
@devcrocod , WDYT
…ty from `ClientConnection` - Removed `serverCapabilities` and `clientCapabilities` properties, along with redundant `onClose`, `request`, and logging-level handling methods. - Simplified `request` and `notification` methods for improved clarity. - Updated corresponding tests to reflect the removed functionality.
…ing for notifications - Added tests to ensure `sendLoggingMessage` and notifications respect the set logging level and properly filter messages below the threshold. - Updated assertions to use `shouldBe` and collection matchers for clarity.
…ication handling in tests - Updated `ClientConnection` to simplify methods by consolidating parameters and removing unused logic. - Replaced `Notification` with `ServerNotification` for stronger typing. - Removed client version and message acceptance logic from the interface. - Simplified test cases to use `shouldBe` and removed redundant assertions for improved clarity and maintainability.
…tekt baselines, and enhance `AbstractServerFeaturesTest` - Made `clientConnection` and `serverCapabilities` private to restrict access scope. - Simplified detekt baselines by removing outdated or unneeded configurations. - Enhanced `AbstractServerFeaturesTest` by adding `addTool` helper for streamlined test setup.
Motivation and Context
Adds a
ClientConnectioncontext receiver to server call handlers, which provides access to the client communication channel. This is necessary to allow tools to send notifications, logging, elicitations, etc.Supersedes #144, discussed in #295.
How Has This Been Tested?
With the added integration test.
Breaking Changes
It breaks binary compatibility and breaks source compatibility for tool handlers that already use
this.If you care a lot about maintaining compatibility, I could add some overloads to ease the transition. But the new methods would likely need to be named differently for resolution.
Future compatibility should be easier to maintain -
Contextis an interface (with experimental opt-in subclassing), so new fields can be added without breaking compatibility. Different types could also be introduced for different handles without breaking compatibility, as long as they extendClientConnection.Types of changes
Checklist
Additional context