Skip to content

Conversation

Bellangelo
Copy link

@Bellangelo Bellangelo commented Sep 6, 2025

Introduce a NotificationPublisher that queues server-initiated MCP notifications. The Server now flushes this queue each tick and sends any pending notifications to the client.

We initially wired this via an EventDispatcher between the Registry and notifications, but, as per the discussion in this PR (see final comment), we simplified the design to invoke NotificationPublisher directly at the mutation points. As a result, the EventDispatcher wiring and the interim internal events used for that wiring have been removed.

Motivation and Context

Closes #12

How Has This Been Tested?

Unit and Integration tests. I have added extra tests for the Registry to increase its coverage.

Breaking Changes

None for typical users constructing the server via ServerBuilder.
(If someone manually instantiates Server, they now pass the NotificationPublisher )

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

@Bellangelo Bellangelo marked this pull request as draft September 6, 2025 23:26
@Bellangelo
Copy link
Author

Hi @chr-hertel, I created a PoC of a solution. There are a few things that do not work 100% correct but I wanted a quick validation around the idea. I had to implement a few new things so I wasn't sure if I understood correctly the issue.

@chr-hertel
Copy link
Member

Nice one @Bellangelo - thanks for working on this! :)

So, i just gave it a quick read, maybe i missed something. so using an event dispatcher in the registry was an idea to publish events, but i wonder if we'd go with your NotificationPublisher idea - and i like it btw 👍 - if we'd need that dispatcher at all.

with the publisher there is a central stack of notifications that get's emptied by the server - who else would listen to those events? and if there's only one consumer of events why not push from the emitting service (registry) to the publisher directly?

@Bellangelo
Copy link
Author

with the publisher there is a central stack of notifications that get's emptied by the server - who else would listen to those events? and if there's only one consumer of events why not push from the emitting service (registry) to the publisher directly?

Good question @chr-hertel, my initial thought for the PSR event dispatcher was that the developer building the server might also want to listen to these events. That’s why I’m dispatching through the developer’s event dispatcher here:
https://github.com/modelcontextprotocol/php-sdk/pull/42/files#diff-1758bd2c4379f6f88e9048d7b437c38d17a64e3d71dd2e9493322c7e61d4a87fR224-R229

If my understanding is off, then you’re right and we could call the NotificationPublisher directly instead of going through the dispatcher. The trade-off is that this would couple components that are otherwise unrelated. However, it might be unnecessary complexity at this stage of the project.

@chr-hertel
Copy link
Member

yup, valid point, server application code might listen to that as well - but on the other hand i'd expect that the impulse for change is rather coming from the application side anyways, since the server itself doesn't have inner logic to change the lists - as of now.
currently i'm more leaning to keeping it simple for now - like having the direct coupling and removing the EventDispatcher for now, yea

@huangdijia
Copy link

Transitionally depending on symfony is not a good thing; it lacks flexibility

@Bellangelo Bellangelo marked this pull request as ready for review September 7, 2025 10:17
@Bellangelo
Copy link
Author

hi @chr-hertel, it should be ready now.

@Bellangelo Bellangelo changed the title PoC - Support server notifications Support server notifications Sep 7, 2025
@butschster
Copy link
Contributor

Hi @Bellangelo 👋

I noticed that in addition to the MessageFactory instantiation https://github.com/Bellangelo/php-sdk/blob/support-server-notifications/src/JsonRpc/Handler.php#L58, the NotificationPublisher class also has a constructor that takes just one argument. I was wondering why you decided to introduce the extra static make() method for creating it, instead of relying directly on the constructor. (https://github.com/modelcontextprotocol/php-sdk/pull/42/files#diff-1758bd2c4379f6f88e9048d7b437c38d17a64e3d71dd2e9493322c7e61d4a87fR211)

Is there a specific reason or design goal behind this approach? I’d love to better understand the intention here. 🙂

@Bellangelo
Copy link
Author

Hi @Bellangelo 👋

Is there a specific reason or design goal behind this approach? I’d love to better understand the intention here. 🙂

Hey @butschster, I did this to minimize how many things the dependents know about their dependencies. If the NotificationPublisher grows more, you won't have to change the ServerBuilder.

$this->tools[$toolName] = new ToolReference($tool, $handler, $isManual);

$this->eventDispatcher?->dispatch(new ToolListChangedEvent());
$this->notificationPublisher->enqueue(ToolListChangedNotification::class);
Copy link
Member

Choose a reason for hiding this comment

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

wouldn't it be a bit easier to just go with

Suggested change
$this->notificationPublisher->enqueue(ToolListChangedNotification::class);
$this->notificationPublisher->enqueue(new ToolListChangedNotification());

Copy link
Author

Choose a reason for hiding this comment

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

@chr-hertel Hmm.. I thought that they would need to pass through the MessageFactory. If they don't need to pass, should I also remove these notifications from the MessageFactory? Because they won't be send as a response to a prompt.

Copy link
Author

Choose a reason for hiding this comment

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

MessageFactory removed from the NotificationPublisher in this commit 3019972

Copy link
Member

Choose a reason for hiding this comment

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

the MessageFactory is intended to handle incoming json/array payload - in this case it's outgoing

Copy link
Author

Choose a reason for hiding this comment

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

I know but it seems that it contains outgoing notifications https://github.com/modelcontextprotocol/php-sdk/blob/main/src/JsonRpc/MessageFactory.php#L39. So we should remove these from the MessageFactory, right?

* @return class-string<HasMethodInterface>
*/
private function getType(string $method): string
private function getType(string $method/**/): string
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private function getType(string $method/**/): string
private function getType(string $method): string

Copy link
Author

Choose a reason for hiding this comment

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

Fixed in 759a379

Comment on lines 36 to 39
$out = $this->queue;
$this->queue = [];

yield from $out;
Copy link
Member

Choose a reason for hiding this comment

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

should work as well

Suggested change
$out = $this->queue;
$this->queue = [];
yield from $out;
yield from $this->queue;
$this->queue = [];

Copy link
Author

Choose a reason for hiding this comment

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

Fixed in f226b2b

private array $resourceTemplates = [];

public function __construct(
private readonly NotificationPublisher $notificationPublisher,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private readonly NotificationPublisher $notificationPublisher,
private readonly NotificationPublisher $notificationPublisher = new NotificationPublisher(),

Copy link
Author

Choose a reason for hiding this comment

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

Fixed in 4c2415b

@Bellangelo Bellangelo force-pushed the support-server-notifications branch from 2d2b84f to 759a379 Compare September 9, 2025 22:58
Copy link
Member

@chr-hertel chr-hertel left a comment

Choose a reason for hiding this comment

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

Ready for merging from my point of view - thanks @Bellangelo!

@CodeWithKyrian any thoughts? :)

@chr-hertel
Copy link
Member

Needs a rebase to solve the conflicts

@Bellangelo
Copy link
Author

Bellangelo commented Sep 15, 2025

Needs a rebase to solve the conflicts

@chr-hertel Done.

@chr-hertel chr-hertel changed the title Support server notifications [Server] Support server notifications Sep 21, 2025
@CodeWithKyrian
Copy link
Contributor

@Bellangelo please rebase and adjust to fit the new architecture.

I’m also wondering if there’s a possibility of using the session as a message queue, since it can be persistent (depending on the store) and is unique to each client connection. Just rough thoughts at this point.

@chr-hertel, any thoughts?

@chr-hertel
Copy link
Member

@CodeWithKyrian that would be even required for making this work on the http transport, right?
@Bellangelo can you have a look into this? changed with merging #49

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Server] Emit Notification on Capability Change
5 participants