Skip to content

Conversation

@tiginamaria
Copy link
Contributor

@tiginamaria tiginamaria commented Dec 1, 2025

Motivation and Context

Implemented thread-safe notification service following eventual consistency principle.
Fixes #249
Contracts:

  • If the client connection and feature update happened in the same thread, the timestamps of the events will be consequent
server.connect(client)
server.add(feature)
// client will receive the notification
server.add(feature)
server.connect(client)
// client will NOT receive the notification
  • If the client connection and feature update happened in the different threads, both options can be possible

The flow of the notification is explained in the diagram:

image

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:

  • I haven't found the subscription confirmed response message content docs, how it should look like?

How Has This Been Tested?

Multiple tests with various notification scenarios were added

Breaking Changes

Types of changes

  • 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

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@tiginamaria tiginamaria marked this pull request as ready for review December 1, 2025 14:46
@tiginamaria tiginamaria added the enhancement New feature or request label Dec 1, 2025
@kpavlov kpavlov force-pushed the tigina/server-notifications branch from 28d86b9 to fde0d7a Compare December 3, 2025 11:12
kpavlov
kpavlov previously approved these changes Dec 3, 2025
Copy link
Contributor

@kpavlov kpavlov left a 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}" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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) {
Copy link
Contributor

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}" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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" }
Copy link
Contributor

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.

Suggested change
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" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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}" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
logger.info { "Notification emitted $timestamp: $notification" }
logger.debug { "Notification emitted $timestamp: $notification" }

Fix logging levels
Extract send event handling
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix notification pipeline

3 participants