Add DeferChangedEvents() to McpServerPrimitiveCollection for batched change notifications#1689
Add DeferChangedEvents() to McpServerPrimitiveCollection for batched change notifications#1689jeffhandley with Copilot wants to merge 12 commits into
Conversation
… notifications - Add _deferralDepth and _pendingChange int fields for thread-safe deferral tracking - Update RaiseChanged() to defer notification when _deferralDepth > 0 - Add DeferChanges() public method returning a DeferralScope : IDisposable - Add EndDeferral() private method using Interlocked ops for atomicity - Add unit tests: no-mutation scope, single/multi-mutations, mixed add+remove, Clear, nested scopes, idempotent dispose, no-handler case, baseline behavior Co-authored-by: jeffhandley <1031940+jeffhandley@users.noreply.github.com>
- Rename DeferralScope to ChangeDeferralScope - Add preexisting Changed behavior tests (TryAdd, Remove, Clear -- with and without success) - Add DeferChanges tests for duplicate TryAdd (with and without prior entry) - Add DeferChanges test for TryAdd-then-Remove same tool (fires one Changed even though net content is unchanged) Co-authored-by: jeffhandley <1031940+jeffhandley@users.noreply.github.com>
Co-authored-by: jeffhandley <1031940+jeffhandley@users.noreply.github.com>
tarekgh
left a comment
There was a problem hiding this comment.
Faithful implementation of the agreed design, and it picks up the gate-at-RaiseChanged and net-zero-fires points. One correctness issue on the concurrency handling, plus a small doc gap, noted inline.
|
A couple of tests are missing to back the review points, plus a few worth adding: Required:
Recommended:
|
|
@copilot Please address the following excellent feedback from @tarekgh:
|
Co-authored-by: jeffhandley <1031940+jeffhandley@users.noreply.github.com>
…in exception-safety sentence Co-authored-by: jeffhandley <1031940+jeffhandley@users.noreply.github.com>
jeffhandley
left a comment
There was a problem hiding this comment.
@copilot add unit tests that verify out-of-order disposals result in the Changed event being raised once all deferrals have been disposed. This can be achieved by capturing the IDisposable instances and calling Dispose() rather than with using blocks.
These tests should ensure re-disposing the same deferral instance multiple times also does not result in the count decrementing more than once (potentially leading to the Changed event being raised while there are still undisposed deferrals.
With this, we should change the "outermost" phrasing to say once all deferrals have been disposed, as the nesting isn't a requirement.
If you find bugs with these new tests, fix them.
…rmost phrasing Co-authored-by: jeffhandley <1031940+jeffhandley@users.noreply.github.com>
Done in the latest commit. Added four new tests:
Updated the |
… tests The non-generic TaskCompletionSource is .NET 5+ only and fails to compile under net472, which ModelContextProtocol.Tests targets. Switch to the generic form in the DeferChangedEvents batch tests for tools, prompts, and resources: new TaskCompletionSource() becomes new TaskCompletionSource<bool>() and TrySetResult() becomes TrySetResult(true). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a batching mechanism to McpServerPrimitiveCollection<T> so multiple tool/prompt/resource registrations can be performed as one logical operation without emitting a Changed event (and corresponding list_changed wire notification) for every individual mutation.
Changes:
- Introduces
DeferChangedEvents()with nested-scope support and a lock-guarded deferral state; emits at most oneChangedwhen the outermost scope ends and at least one mutation occurred. - Adds a comprehensive unit test suite for deferral semantics, nesting, idempotent disposal, concurrency, and derived-type
RaiseChanged()behavior. - Adds end-to-end tests for tools/prompts/resources ensuring a batch add emits exactly one wire notification.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ModelContextProtocol.Core/Server/McpServerPrimitiveCollection.cs | Implements deferrable/coalesced Changed notifications via an IDisposable deferral scope. |
| tests/ModelContextProtocol.Tests/Server/McpServerPrimitiveCollectionTests.cs | Adds unit tests validating baseline and deferred Changed behavior, including concurrency and nesting. |
| tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs | Adds an end-to-end test asserting a batched tool add emits exactly one tools/list_changed notification. |
| tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs | Adds an end-to-end test asserting a batched prompt add emits exactly one prompts/list_changed notification. |
| tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs | Adds an end-to-end test asserting a batched resource add emits exactly one resources/list_changed notification. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Fixes #1688
Registering a batch of N tools/prompts/resources emits N
list_changedwire notifications -- one perTryAdd-- instead of one. This can trip clients that debounce or rate-limit notifications, and is wasteful for progressive-disclosure patterns.Changes
DeferChangedEvents()onMcpServerPrimitiveCollection<T>-- returns anIDisposablescope that suppressesChangedevents. A single notification fires when the outermost scope disposes, only if at least one mutation occurred. Nesting is supported via a depth counter._activeDeferralScopesand_hasDeferredChangeEventsare guarded by_deferralLock.RaiseChanged()defers when_activeDeferralScopes > 0, andChangeDeferralScope.Dispose()is idempotent viaInterlocked.Exchange.Changedis invoked outside the lock.Clear, nested scopes, idempotent dispose, no-handler, baseline non-deferred behavior, concurrent mutations, mutation racing with dispose, derived-type coalescing, exception-safety, and prompt/resource collection coverage.McpServerBuilderExtensionsToolsTests,McpServerBuilderExtensionsPromptsTests, andMcpServerBuilderExtensionsResourcesTestsasserting exactly one wire notification for a batch add.Usage