using System.Collections; using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; namespace ModelContextProtocol.Server; /// Provides a thread-safe collection of instances, indexed by their names. /// The type of primitive stored in the collection. public class McpServerPrimitiveCollection : ICollection, IReadOnlyCollection where T : IMcpServerPrimitive { /// Concurrent dictionary of primitives, indexed by their names. private readonly ConcurrentDictionary _primitives; /// Lock protecting and . private readonly object _deferralLock = new(); /// Depth counter for active scopes. Positive means notifications are deferred. private int _activeDeferralScopes; /// Whether a change occurred while notifications were deferred. private bool _hasDeferredChangeEvents; /// /// Initializes a new instance of the class. /// public McpServerPrimitiveCollection(IEqualityComparer? keyComparer = null) { _primitives = new(keyComparer ?? EqualityComparer.Default); } /// Occurs when the collection is changed. /// /// By default, this event is raised when a primitive is added or removed. However, a derived implementation /// might raise this event for other reasons, such as when a primitive is modified. /// public event EventHandler? Changed; /// Gets the number of primitives in the collection. public int Count => _primitives.Count; /// Gets a value that indicates whether there are any primitives in the collection. public bool IsEmpty => _primitives.IsEmpty; /// /// Begins a deferred-change scope. notifications are suppressed /// until the returned scope is disposed, at which point a single notification is raised /// if any mutation occurred during the scope. Multiple scopes may be active simultaneously; /// the notification fires once all active scopes have been disposed. /// /// An that ends the deferral scope when disposed. /// /// The scope is exception-safe: even if an exception is thrown inside a using block, /// the deferral is ended on dispose. If any mutation occurred before the exception, a single /// notification is raised. /// /// Mutations from any thread during an open scope are coalesced. A single /// notification fires on the thread that disposes the last active scope, only if at least one /// mutation occurred. All deferral state transitions are guarded by an internal lock, so /// concurrent mutations and concurrent scope disposal are both safe. Disposing the same scope /// instance more than once is safe and has no additional effect. /// /// public IDisposable DeferChangedEvents() { lock (_deferralLock) { _activeDeferralScopes++; } return new ChangeDeferralScope(this); } /// Raises if there are registered handlers. /// /// If a scope is active, the notification is deferred until all /// active scopes are disposed. Derived types that override mutation methods and call /// will automatically participate in deferral. /// protected void RaiseChanged() { lock (_deferralLock) { if (_activeDeferralScopes > 0) { _hasDeferredChangeEvents = true; return; } } Changed?.Invoke(this, EventArgs.Empty); } private void EndDeferral() { bool raise; lock (_deferralLock) { raise = --_activeDeferralScopes == 0 && _hasDeferredChangeEvents; if (raise) { _hasDeferredChangeEvents = false; } } if (raise) { Changed?.Invoke(this, EventArgs.Empty); } } private sealed class ChangeDeferralScope : IDisposable { private McpServerPrimitiveCollection? _collection; public ChangeDeferralScope(McpServerPrimitiveCollection collection) => _collection = collection; public void Dispose() { McpServerPrimitiveCollection? collection = Interlocked.Exchange(ref _collection, null); collection?.EndDeferral(); } } /// Gets the with the specified from the collection. /// The name of the primitive to retrieve. /// The with the specified name. /// is . /// A primitive with the specified name does not exist in the collection. public T this[string name] { get { Throw.IfNull(name); return _primitives[name]; } } /// Clears all primitives from the collection. public virtual void Clear() { _primitives.Clear(); RaiseChanged(); } /// Adds the specified to the collection. /// The primitive to be added. /// is . /// A primitive with the same name as already exists in the collection. public void Add(T primitive) { if (!TryAdd(primitive)) { throw new ArgumentException($"A primitive with the same name '{primitive.Id}' already exists in the collection.", nameof(primitive)); } } /// Adds the specified to the collection. /// The primitive to be added. /// if the primitive was added; otherwise, . /// is . public virtual bool TryAdd(T primitive) { Throw.IfNull(primitive); bool added = _primitives.TryAdd(primitive.Id, primitive); if (added) { RaiseChanged(); } return added; } /// Removes the specified primitive from the collection. /// The primitive to be removed from the collection. /// /// if the primitive was found in the collection and removed; if it wasn't found. /// /// is . public virtual bool Remove(T primitive) { Throw.IfNull(primitive); bool removed = ((ICollection>)_primitives).Remove(new(primitive.Id, primitive)); if (removed) { RaiseChanged(); } return removed; } /// Attempts to get the primitive with the specified name from the collection. /// The name of the primitive to retrieve. /// The primitive, if found; otherwise, . /// /// if the primitive was found in the collection and returned; if it wasn't found. /// /// is . public virtual bool TryGetPrimitive(string name, [NotNullWhen(true)] out T? primitive) { Throw.IfNull(name); return _primitives.TryGetValue(name, out primitive); } /// Checks if a specific primitive is present in the collection of primitives. /// The primitive to search for in the collection. /// if the primitive was found in the collection and returned; if it wasn't found. /// is . public virtual bool Contains(T primitive) { Throw.IfNull(primitive); return ((ICollection>)_primitives).Contains(new(primitive.Id, primitive)); } /// Gets the names of all of the primitives in the collection. public virtual ICollection PrimitiveNames => _primitives.Keys; /// Creates an array containing all of the primitives in the collection. /// An array containing all of the primitives in the collection. public virtual T[] ToArray() => _primitives.Values.ToArray(); /// public virtual void CopyTo(T[] array, int arrayIndex) { Throw.IfNull(array); _primitives.Values.CopyTo(array, arrayIndex); } /// public virtual IEnumerator GetEnumerator() { foreach (var entry in _primitives) { yield return entry.Value; } } /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// bool ICollection.IsReadOnly => false; }