| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 46628fb commit fefe4df
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -84,13 +84,31 @@ public function __construct( | |||
| 84 | 84 | */ | |
| 85 | 85 | public function load(): void | |
| 86 | 86 | { | |
| 87 | - if ($this->loaded || $this->loading || null === $this->loader) { | ||
| 87 | + if (null === $this->loader) { | ||
| 88 | + return; | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + $this->loadFrom($this->loader); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + /** | ||
| 95 | + * Runs a loader supplied from outside, under the same guard {@see load()} uses. | ||
| 96 | + * | ||
| 97 | + * A registry the caller constructed cannot be given a loader through the constructor, so | ||
| 98 | + * {@see \Mcp\Server\Builder} loads it from the outside instead. Doing that by calling the | ||
| 99 | + * loader directly skips the guard, and then every element registered during the load is | ||
| 100 | + * announced as a change — which on a configured notification bus is a burst of `list_changed` | ||
| 101 | + * notifications on every build, for a registry that did not change. | ||
| 102 | + */ | ||
| 103 | + public function loadFrom(LoaderInterface $loader): void | ||
| 104 | + { | ||
| 105 | + if ($this->loaded || $this->loading) { | ||
| 88 | 106 | return; | |
| 89 | 107 | } | |
| 90 | 108 | ||
| 91 | 109 | $this->loading = true; | |
| 92 | 110 | try { | |
| 93 | - $this->loader->load($this); | ||
| 111 | + $loader->load($this); | ||
| 94 | 112 | $this->loaded = true; | |
| 95 | 113 | } finally { | |
| 96 | 114 | $this->loading = false; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1045,8 +1045,10 @@ private function resolve(): array | |||
| 1045 | 1045 | ||
| 1046 | 1046 | if ($this->hasCustomRegistry) { | |
| 1047 | 1047 | // Builder can't inject the loader into an already-constructed instance, so load it eagerly. | |
| 1048 | + // Through loadFrom() where possible: loading a registry from the outside otherwise skips the | ||
| 1049 | + // guard that keeps the load itself from being announced as a change. | ||
| 1048 | 1050 | $registry = $this->registry; | |
| 1049 | - $chainLoader->load($registry); | ||
| 1051 | + $registry instanceof Registry ? $registry->loadFrom($chainLoader) : $chainLoader->load($registry); | ||
| 1050 | 1052 | $eagerlyLoaded = true; | |
| 1051 | 1053 | } else { | |
| 1052 | 1054 | $registry = new Registry($eventDispatcher, $logger, loader: $chainLoader); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -753,6 +753,34 @@ public function load(RegistryInterface $registry): void | |||
| 753 | 753 | $this->assertTrue($registry->hasPrompts()); | |
| 754 | 754 | } | |
| 755 | 755 | ||
| 756 | + public function testListChangedEventsAreSuppressedWhenTheLoaderIsSuppliedFromOutside(): void | ||
| 757 | + { | ||
| 758 | + // A registry the caller constructed cannot take a loader through the constructor, so | ||
| 759 | + // Builder loads it from the outside. That has to be as quiet as the deferred load above: | ||
| 760 | + // the elements arriving are the registry's initial contents, not a change to them. | ||
| 761 | + $eventDispatcher = $this->createMock(EventDispatcherInterface::class); | ||
| 762 | + $eventDispatcher->expects($this->never())->method('dispatch'); | ||
| 763 | + | ||
| 764 | + $registry = new Registry($eventDispatcher, $this->logger); | ||
| 765 | + $registry->loadFrom($this->toolLoader($this->createValidTool('loaded'))); | ||
| 766 | + | ||
| 767 | + $this->assertTrue($registry->hasTool('loaded')); | ||
| 768 | + } | ||
| 769 | + | ||
| 770 | + public function testARuntimeRegistrationAfterAnExternalLoadIsStillDispatched(): void | ||
| 771 | + { | ||
| 772 | + $eventDispatcher = $this->createMock(EventDispatcherInterface::class); | ||
| 773 | + $eventDispatcher->expects($this->once()) | ||
| 774 | + ->method('dispatch') | ||
| 775 | + ->with($this->isInstanceOf(ToolListChangedEvent::class)) | ||
| 776 | + ->willReturnArgument(0); | ||
| 777 | + | ||
| 778 | + $registry = new Registry($eventDispatcher, $this->logger); | ||
| 779 | + $registry->loadFrom($this->toolLoader($this->createValidTool('loaded'))); | ||
| 780 | + | ||
| 781 | + $registry->registerTool($this->createValidTool('runtime'), 'handler'); | ||
| 782 | + } | ||
| 783 | + | ||
| 756 | 784 | public function testListChangedEventsAreStillDispatchedForRuntimeRegistrations(): void | |
| 757 | 785 | { | |
| 758 | 786 | $eventDispatcher = $this->createMock(EventDispatcherInterface::class); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -32,6 +32,8 @@ | |||
| 32 | 32 | use Mcp\Server\Protocol; | |
| 33 | 33 | use Mcp\Server\Session\SessionInterface; | |
| 34 | 34 | use Mcp\Server\Stateless\StatelessProtocol; | |
| 35 | + use Mcp\Server\Subscription\InMemoryNotificationBus; | ||
| 36 | + use Mcp\Server\Subscription\PublishingEventDispatcher; | ||
| 35 | 37 | use Mcp\Tests\Unit\Server\Extension\ThingExtension; | |
| 36 | 38 | use Mcp\Tests\Unit\Server\Extension\ThingListHandler; | |
| 37 | 39 | use Mcp\Tests\Unit\Server\Extension\ThingListRequest; | |
@@ -404,6 +406,31 @@ private function callTool(Server $server, string $toolName): mixed | |||
| 404 | 406 | ||
| 405 | 407 | $this->fail('CallToolHandler not found in request handlers'); | |
| 406 | 408 | } | |
| 409 | + | ||
| 410 | + public function testBuildingWithASuppliedRegistryDoesNotAnnounceTheLoadAsAChange(): void | ||
| 411 | + { | ||
| 412 | + // The registry a caller supplies carries its own dispatcher, which the Builder has no | ||
| 413 | + // handle on — so the load has to be quiet at the registry, or every build publishes one | ||
| 414 | + // list_changed per element. On a shared bus that is a burst per process, for no change. | ||
| 415 | + $bus = new InMemoryNotificationBus(); | ||
| 416 | + $registry = new Registry(new PublishingEventDispatcher($bus)); | ||
| 417 | + | ||
| 418 | + Server::builder() | ||
| 419 | + ->setRegistry($registry) | ||
| 420 | + ->setNotificationBus($bus) | ||
| 421 | + ->addTool(static fn (): string => 'ok', 'alpha') | ||
| 422 | + ->addTool(static fn (): string => 'ok', 'beta') | ||
| 423 | + ->build(); | ||
| 424 | + | ||
| 425 | + $this->assertSame(0, $bus->cursor()); | ||
| 426 | + $this->assertTrue($registry->hasTool('alpha')); | ||
| 427 | + $this->assertTrue($registry->hasTool('beta')); | ||
| 428 | + | ||
| 429 | + // What the bus is for still works: a change after the build is published. | ||
| 430 | + $registry->unregisterTool('alpha'); | ||
| 431 | + | ||
| 432 | + $this->assertSame(1, $bus->cursor()); | ||
| 433 | + } | ||
| 407 | 434 | } | |
| 408 | 435 | ||
| 409 | 436 | /** | |
| Back | FazBrowse Home | New Git URL |
0 commit comments