-
Notifications
You must be signed in to change notification settings - Fork 181
Server feature list changed and resource updated notifications #441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
28d86b9 to
fde0d7a
Compare
kpavlov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, @tiginamaria very good PR
I have few comments about logging, but it can be addressed in a separate PR
| object : FeatureListener { | ||
| override fun onFeatureUpdated(featureKey: FeatureKey) { | ||
| val notification = notificationProvider(featureKey) | ||
| logger.info { "Emitting notification: ${notification.method.value}" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| logger.info { "Emitting notification: ${notification.method.value}" } | |
| logger.debug { "Emitting notification: ${notification.method.value}" } |
| logger.info { "Starting notification job from timestamp $fromTimestamp for sessionId: ${session.sessionId} " } | ||
| job = scope.launch { | ||
| events.takeWhile { it !is EndEvent }.collect { event -> | ||
| when (event) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's extract handleEvent method
|
|
||
| sessionNotificationJobs.getAndUpdate { | ||
| if (it.containsKey(session.sessionId)) { | ||
| logger.info { "Session already subscribed: ${session.sessionId}" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| logger.info { "Session already subscribed: ${session.sessionId}" } | |
| logger.debug { "Session already subscribed: ${session.sessionId}" } |
| // Create a timestamp before emit to ensure notifications are processed in order | ||
| val timestamp = getCurrentTimestamp() | ||
| if (closingService.value) { | ||
| logger.warn { "Skipping emitting notification as service is closing: $notification" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a normal condition.
| logger.warn { "Skipping emitting notification as service is closing: $notification" } | |
| logger.debug { "Skipping emitting notification as service is closing: $notification" } |
|
|
||
| // Launching emit lazily to put it to the jobs queue before the completion | ||
| val job = notificationScope.launch(start = CoroutineStart.LAZY) { | ||
| logger.info { "Actually emitting notification $timestamp: $notification" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| logger.info { "Actually emitting notification $timestamp: $notification" } | |
| logger.debug { "Actually emitting notification $timestamp: $notification" } |
|
|
||
| val timestamp = getCurrentTimestamp() | ||
| if (closingService.value) { | ||
| logger.warn { "Skipping subscription notification as service is closing: ${session.sessionId}" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| logger.warn { "Skipping subscription notification as service is closing: ${session.sessionId}" } | |
| logger.debug { "Skipping subscription notification as service is closing: ${session.sessionId}" } |
| val job = notificationScope.launch(start = CoroutineStart.LAZY) { | ||
| logger.info { "Actually emitting notification $timestamp: $notification" } | ||
| notificationEvents.emit(SendEvent(timestamp, notification)) | ||
| logger.info { "Notification emitted $timestamp: $notification" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| logger.info { "Notification emitted $timestamp: $notification" } | |
| logger.debug { "Notification emitted $timestamp: $notification" } |
Fix logging levels Extract send event handling
Motivation and Context
Implemented thread-safe notification service following eventual consistency principle.
Fixes #249
Contracts:
The flow of the notification is explained in the diagram:
Documentation references:
https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#list-changed-notification
https://modelcontextprotocol.io/specification/2025-06-18/server/tools#list-changed-notification
https://modelcontextprotocol.io/specification/2025-06-18/server/resources#list-changed-notification
https://modelcontextprotocol.io/specification/2025-06-18/server/resources#subscriptions
Questions:
How Has This Been Tested?
Multiple tests with various notification scenarios were added
Breaking Changes
Types of changes
Checklist
Additional context