using ModelContextProtocol.Protocol;
using System.Diagnostics;

namespace ModelContextProtocol;

/// Provides thread-safe storage for notification handlers.
internal sealed class NotificationHandlers
{
    /// A dictionary of linked lists of registrations, indexed by the notification method.
    private readonly Dictionary<string, Registration> _handlers = [];

    /// Gets the object to be used for all synchronization.
    private object SyncObj => _handlers;

    ///
    /// Registers a collection of notification handlers at once.
    ///
    /// <param name="handlers">
    /// A collection of notification method names paired with their corresponding handler functions.
    /// Each key in the collection is a notification method name, and each value is a handler function
    /// that will be invoked when a notification with that method name is received.
    /// </param>
    /// <remarks>
    /// <para>
    /// This method is typically used during client or server initialization to register
    /// all notification handlers provided in capabilities.
    /// </para>
    /// <para>
    /// Registrations completed with this method are permanent and non-removable.
    /// This differs from handlers registered with <see cref="Register"/> which can be temporary.
    /// </para>
    /// <para>
    /// When multiple handlers are registered for the same method, all handlers will be invoked
    /// in reverse order of registration (newest first) when a notification is received.
    /// </para>
    /// <para>
    /// The registered handlers will be invoked by <see cref="InvokeHandlers"/> when a notification
    /// with the corresponding method name is received.
    /// </para>
    /// </remarks>
    public void RegisterRange(IEnumerable<KeyValuePair<string, Func<JsonRpcNotification, CancellationToken, ValueTask>>> handlers)
    {
        foreach (var entry in handlers)
        {
            _ = Register(entry.Key, entry.Value, temporary: false);
        }
    }

    ///
    /// Adds a notification handler as part of configuring the endpoint.
    ///
    /// <param name="method">The notification method for which the handler is being registered.</param>
    /// <param name="handler">The handler being registered.</param>
    /// <param name="temporary">
    /// <see langword="true"/> if the registration can be removed later; <see langword="false"/> if it cannot.
    /// If <see langword="false"/>, the registration will be permanent: calling <see cref="IAsyncDisposable.DisposeAsync"/>
    /// on the returned instance will not unregister the handler.
    /// </param>
    /// <returns>
    /// An <see cref="IAsyncDisposable"/> that when disposed will unregister the handler if <paramref name="temporary"/> is <see langword="true"/>.
    /// </returns>
    /// <remarks>
    /// Multiple handlers can be registered for the same method. When a notification for that method is received,
    /// all registered handlers will be invoked in reverse order of registration (newest first).
    /// </remarks>
    public IAsyncDisposable Register(
        string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler, bool temporary = true)
    {
        // Create the new registration instance.
        Registration reg = new(this, method, handler, temporary);

        // Store the registration into the dictionary. If there's not currently a registration for the method,
        // then this registration instance just becomes the single value. If there is currently a registration,
        // then this new registration becomes the new head of the linked list, and the old head becomes the next
        // item in the list.
        lock (SyncObj)
        {
            if (_handlers.TryGetValue(method, out var existingHandlerHead))
            {
                reg.Next = existingHandlerHead;
                existingHandlerHead.Prev = reg;
            }

            _handlers[method] = reg;
        }

        // Return the new registration. It must be disposed of when no longer used, or it will end up being
        // leaked into the list. This is the same as with CancellationToken.Register.
        return reg;
    }

    ///
    /// Invokes all registered handlers for the specified notification method.
    ///
    /// <param name="method">The notification method name to invoke handlers for.</param>
    /// <param name="notification">The notification object to pass to each handler.</param>
    /// <param name="cancellationToken">A token that can be used to cancel the operation.</param>
    /// <remarks>
    /// Handlers are invoked in reverse order of registration (newest first).
    /// If any handler throws an exception, all handlers will still be invoked, and an <see cref="AggregateException"/>
    /// containing all exceptions will be thrown after all handlers have been invoked.
    /// </remarks>
    public async Task InvokeHandlers(string method, JsonRpcNotification notification, CancellationToken cancellationToken)
    {
        // If there are no handlers registered for this method, we're done.
        Registration? reg;
        lock (SyncObj)
        {
            if (!_handlers.TryGetValue(method, out reg))
            {
                return;
            }
        }

        // Invoke each handler in the list. We guarantee that we'll try to invoke
        // any handlers that were in the list when the list was fetched from the dictionary,
        // which is why DisposeAsync doesn't modify the Prev/Next of the registration being
        // disposed; if those were nulled out, we'd be unable to walk around it in the list
        // if we happened to be on that item when it was disposed.
        List<Exception>? exceptions = null;
        while (reg is not null)
        {
            try
            {
                await reg.InvokeAsync(notification, cancellationToken).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                (exceptions ??= []).Add(e);
            }

            lock (SyncObj)
            {
                reg = reg.Next;
            }
        }

        if (exceptions is not null)
        {
            throw new AggregateException(exceptions);
        }
    }

    /// Provides storage for a handler registration.

private sealed class Registration(
        NotificationHandlers handlers, string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler, bool unregisterable) : IAsyncDisposable
    {
        private static readonly AsyncLocal<int> s_invokingAncestor = new();
        private readonly NotificationHandlers _handlers = handlers;
        private readonly string _method = method;
        private readonly Func<JsonRpcNotification, CancellationToken, ValueTask> _handler = handler;
        private readonly bool _temporary = unregisterable;
        private TaskCompletionSource<bool>? _disposeTcs;
        private int _refCount = 1;
        private bool _disposedCalled = false;
        public Registration? Next;
        public Registration? Prev;
        public async ValueTask DisposeAsync()
        {
            if (!_temporary)
            {
                return;
            }
            lock (_handlers.SyncObj)
            {
                if (!_disposedCalled)
                {
                    _disposedCalled = true;
                    if (_handlers._handlers.TryGetValue(_method, out var handlers) && handlers == this)
                    {
                        if (Next is not null)
                        {
                            _handlers._handlers[_method] = Next;
                        }
                        else
                        {
                            _handlers._handlers.Remove(_method);
                        }
                    }
                    if (Prev is not null)
                    {
                        Prev.Next = Next;
                    }
                    if (Next is not null)
                    {
                        Next.Prev = Prev;
                    }
                    if (--_refCount != 0)
                    {
                        Debug.Assert(_disposeTcs is null);
                        _disposeTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
                    }
                }
            }
            if (_disposeTcs is not null && s_invokingAncestor.Value == 0)
            {
                await _disposeTcs.Task.ConfigureAwait(false);
            }
        }
        public ValueTask InvokeAsync(JsonRpcNotification notification, CancellationToken cancellationToken)
        {
            if (!_temporary)
            {
                return _handler(notification, cancellationToken);
            }
            return InvokeTemporaryAsync(notification, cancellationToken);
        }
        private async ValueTask InvokeTemporaryAsync(JsonRpcNotification notification, CancellationToken cancellationToken)
        {
            lock (_handlers.SyncObj)
            {
                Debug.Assert(_refCount != 0 || _disposedCalled, $"Expected {nameof(_disposedCalled)} == true when {nameof(_refCount)} == 0");
                if (_disposedCalled)
                {
                    return;
                }
                Debug.Assert(_refCount > 0);
                _refCount++;
            }
            s_invokingAncestor.Value++;
            try
            {
                await _handler(notification, cancellationToken).ConfigureAwait(false);
            }
            finally
            {
                s_invokingAncestor.Value--;
                lock (_handlers.SyncObj)
                {
                    _refCount--;
                    if (_refCount == 0)
                    {
                        Debug.Assert(_disposedCalled);
                        Debug.Assert(_disposeTcs is not null);
                        bool completed = _disposeTcs!.TrySetResult(true);
                        Debug.Assert(completed);
                    }
                }
            }
        }
    }

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Text.Json;
using System.Text.Json.Nodes;
#if !NET
using System.Threading.Channels;
#endif

namespace ModelContextProtocol;

///
/// Class for managing an MCP JSON-RPC session. This covers both MCP clients and servers.
///
internal sealed partial class McpSession : IDisposable
{
    private static readonly Histogram<double> s_clientSessionDuration = Diagnostics.CreateDurationHistogram(
        "mcp.client.session.duration", "Measures the duration of a client session.", longBuckets: true);
    private static readonly Histogram<double> s_serverSessionDuration = Diagnostics.CreateDurationHistogram(
        "mcp.server.session.duration", "Measures the duration of a server session.", longBuckets: true);
    private static readonly Histogram<double> s_clientOperationDuration = Diagnostics.CreateDurationHistogram(
        "mcp.client.operation.duration", "Measures the duration of outbound message.", longBuckets: false);
    private static readonly Histogram<double> s_serverOperationDuration = Diagnostics.CreateDurationHistogram(
        "mcp.server.operation.duration", "Measures the duration of inbound message processing.", longBuckets: false);

    /// The latest version of the protocol supported by this implementation.
    internal const string LatestProtocolVersion = "2025-06-18";

    /// All protocol versions supported by this implementation.
    internal static readonly string[] SupportedProtocolVersions =
    [
        "2024-11-05",
        "2025-03-26",
        LatestProtocolVersion,
    ];

    private readonly bool _isServer;
    private readonly string _transportKind;
    private readonly ITransport _transport;
    private readonly RequestHandlers _requestHandlers;
    private readonly NotificationHandlers _notificationHandlers;
    private readonly long _sessionStartingTimestamp = Stopwatch.GetTimestamp();

    private readonly DistributedContextPropagator _propagator = DistributedContextPropagator.Current;

    /// Collection of requests sent on this session and waiting for responses.
    private readonly ConcurrentDictionary<RequestId, TaskCompletionSource<JsonRpcMessage>> _pendingRequests = [];
    ///
    /// Collection of requests received on this session and currently being handled. The value provides a <see cref="CancellationTokenSource"/>
    /// that can be used to request cancellation of the in-flight handler.
    ///
    private readonly ConcurrentDictionary<RequestId, CancellationTokenSource> _handlingRequests = new();
    private readonly ILogger _logger;

    // This _sessionId is solely used to identify the session in telemetry and logs.
    private readonly string _sessionId = Guid.NewGuid().ToString("N");
    private long _lastRequestId;

    ///
    /// Initializes a new instance of the <see cref="McpSession"/> class.
    ///
    /// <param name="isServer">true if this is a server; false if it's a client.</param>
    /// <param name="transport">An MCP transport implementation.</param>
    /// <param name="endpointName">The name of the endpoint for logging and debug purposes.</param>
    /// <param name="requestHandlers">A collection of request handlers.</param>
    /// <param name="notificationHandlers">A collection of notification handlers.</param>
    /// <param name="logger">The logger.</param>
    public McpSession(
        bool isServer,
        ITransport transport,
        string endpointName,
        RequestHandlers requestHandlers,
        NotificationHandlers notificationHandlers,
        ILogger logger)
    {
        Throw.IfNull(transport);

        _transportKind = transport switch
        {
            StdioClientSessionTransport or StdioServerTransport => "stdio",
            StreamClientSessionTransport or StreamServerTransport => "stream",
            SseClientSessionTransport or SseResponseStreamTransport => "sse",
            StreamableHttpClientSessionTransport or StreamableHttpServerTransport or StreamableHttpPostTransport => "http",
            _ => "unknownTransport"
        };

        _isServer = isServer;
        _transport = transport;
        EndpointName = endpointName;
        _requestHandlers = requestHandlers;
        _notificationHandlers = notificationHandlers;
        _logger = logger ?? NullLogger.Instance;
    }

    ///
    /// Gets and sets the name of the endpoint for logging and debug purposes.
    ///
    public string EndpointName { get; set; }

    ///
    /// Starts processing messages from the transport. This method will block until the transport is disconnected.
    /// This is generally started in a background task or thread from the initialization logic of the derived class.
    ///
    public async Task ProcessMessagesAsync(CancellationToken cancellationToken)
    {
        try
        {
            await foreach (var message in _transport.MessageReader.ReadAllAsync(cancellationToken).ConfigureAwait(false))
            {
                LogMessageRead(EndpointName, message.GetType().Name);

                // Fire and forget the message handling to avoid blocking the transport.
                if (message.ExecutionContext is null)
                {
                    _ = ProcessMessageAsync();
                }
                else
                {
                    // Flow the execution context from the HTTP request corresponding to this message if provided.
                    ExecutionContext.Run(message.ExecutionContext, _ => _ = ProcessMessageAsync(), null);
                }

                async Task ProcessMessageAsync()
                {
                    JsonRpcMessageWithId? messageWithId = message as JsonRpcMessageWithId;
                    CancellationTokenSource? combinedCts = null;
                    try
                    {
                        // Register before we yield, so that the tracking is guaranteed to be there
                        // when subsequent messages arrive, even if the asynchronous processing happens
                        // out of order.
                        if (messageWithId is not null)
                        {
                            combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                            _handlingRequests[messageWithId.Id] = combinedCts;
                        }

                        // If we await the handler without yielding first, the transport may not be able to read more messages,
                        // which could lead to a deadlock if the handler sends a message back.
#if NET
                        await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
#else
                        await default(ForceYielding);
#endif

                        // Handle the message.
                        await HandleMessageAsync(message, combinedCts?.Token ?? cancellationToken).ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        // Only send responses for request errors that aren't user-initiated cancellation.
                        bool isUserCancellation =
                            ex is OperationCanceledException &&
                            !cancellationToken.IsCancellationRequested &&
                            combinedCts?.IsCancellationRequested is true;

                        if (!isUserCancellation && message is JsonRpcRequest request)
                        {
                            LogRequestHandlerException(EndpointName, request.Method, ex);

                            JsonRpcErrorDetail detail = ex is McpException mcpe ?
                                new()
                                {
                                    Code = (int)mcpe.ErrorCode,
                                    Message = mcpe.Message,
                                } :
                                new()
                                {
                                    Code = (int)McpErrorCode.InternalError,
                                    Message = "An error occurred.",
                                };

                            await SendMessageAsync(new JsonRpcError
                            {
                                Id = request.Id,
                                JsonRpc = "2.0",
                                Error = detail,
                                RelatedTransport = request.RelatedTransport,
                            }, cancellationToken).ConfigureAwait(false);
                        }
                        else if (ex is not OperationCanceledException)
                        {
                            if (_logger.IsEnabled(LogLevel.Trace))
                            {
                                LogMessageHandlerExceptionSensitive(EndpointName, message.GetType().Name, JsonSerializer.Serialize(message, McpJsonUtilities.JsonContext.Default.JsonRpcMessage), ex);
                            }
                            else
                            {
                                LogMessageHandlerException(EndpointName, message.GetType().Name, ex);
                            }
                        }
                    }
                    finally
                    {
                        if (messageWithId is not null)
                        {
                            _handlingRequests.TryRemove(messageWithId.Id, out _);
                            combinedCts!.Dispose();
                        }
                    }
                }
            }
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            // Normal shutdown
            LogEndpointMessageProcessingCanceled(EndpointName);
        }
        finally
        {
            // Fail any pending requests, as they'll never be satisfied.
            foreach (var entry in _pendingRequests)
            {
                entry.Value.TrySetException(new IOException("The server shut down unexpectedly."));
            }
        }
    }

    private async Task HandleMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken)
    {
        Histogram<double> durationMetric = _isServer ? s_serverOperationDuration : s_clientOperationDuration;
        string method = GetMethodName(message);

        long? startingTimestamp = durationMetric.Enabled ? Stopwatch.GetTimestamp() : null;

        Activity? activity = Diagnostics.ShouldInstrumentMessage(message) ?
            Diagnostics.ActivitySource.StartActivity(
                CreateActivityName(method),
                ActivityKind.Server,
                parentContext: _propagator.ExtractActivityContext(message),
                links: Diagnostics.ActivityLinkFromCurrent()) :
            null;

        TagList tags = default;
        bool addTags = activity is { IsAllDataRequested: true } || startingTimestamp is not null;
        try
        {
            if (addTags)
            {
                AddTags(ref tags, activity, message, method);
            }

            switch (message)
            {
                case JsonRpcRequest request:
                    var result = await HandleRequest(request, cancellationToken).ConfigureAwait(false);
                    AddResponseTags(ref tags, activity, result, method);
                    break;

                case JsonRpcNotification notification:
                    await HandleNotification(notification, cancellationToken).ConfigureAwait(false);
                    break;

                case JsonRpcMessageWithId messageWithId:
                    HandleMessageWithId(message, messageWithId);
                    break;

                default:
                    LogEndpointHandlerUnexpectedMessageType(EndpointName, message.GetType().Name);
                    break;
            }
        }
        catch (Exception e) when (addTags)
        {
            AddExceptionTags(ref tags, activity, e);
            throw;
        }
        finally
        {
            FinalizeDiagnostics(activity, startingTimestamp, durationMetric, ref tags);
        }
    }

    private async Task HandleNotification(JsonRpcNotification notification, CancellationToken cancellationToken)
    {
        // Special-case cancellation to cancel a pending operation. (We'll still subsequently invoke a user-specified handler if one exists.)
        if (notification.Method == NotificationMethods.CancelledNotification)
        {
            try
            {
                if (GetCancelledNotificationParams(notification.Params) is CancelledNotificationParams cn &&
                    _handlingRequests.TryGetValue(cn.RequestId, out var cts))
                {
                    await cts.CancelAsync().ConfigureAwait(false);
                    LogRequestCanceled(EndpointName, cn.RequestId, cn.Reason);
                }
            }
            catch
            {
                // "Invalid cancellation notifications SHOULD be ignored"
            }
        }

        // Handle user-defined notifications.
        await _notificationHandlers.InvokeHandlers(notification.Method, notification, cancellationToken).ConfigureAwait(false);
    }

    private void HandleMessageWithId(JsonRpcMessage message, JsonRpcMessageWithId messageWithId)
    {
        if (_pendingRequests.TryRemove(messageWithId.Id, out var tcs))
        {
            tcs.TrySetResult(message);
        }
        else
        {
            LogNoRequestFoundForMessageWithId(EndpointName, messageWithId.Id);
        }
    }

    private async Task<JsonNode?> HandleRequest(JsonRpcRequest request, CancellationToken cancellationToken)
    {
        if (!_requestHandlers.TryGetValue(request.Method, out var handler))
        {
            LogNoHandlerFoundForRequest(EndpointName, request.Method);
            throw new McpException($"Method '{request.Method}' is not available.", McpErrorCode.MethodNotFound);
        }

        LogRequestHandlerCalled(EndpointName, request.Method);
        JsonNode? result = await handler(request, cancellationToken).ConfigureAwait(false);
        LogRequestHandlerCompleted(EndpointName, request.Method);

        await SendMessageAsync(new JsonRpcResponse
        {
            Id = request.Id,
            Result = result,
            RelatedTransport = request.RelatedTransport,
        }, cancellationToken).ConfigureAwait(false);

        return result;
    }

    private CancellationTokenRegistration RegisterCancellation(CancellationToken cancellationToken, JsonRpcRequest request)
    {
        if (!cancellationToken.CanBeCanceled)
        {
            return default;
        }

        return cancellationToken.Register(static objState =>
        {
            var state = (Tuple<McpSession, JsonRpcRequest>)objState!;
            _ = state.Item1.SendMessageAsync(new JsonRpcNotification
            {
                Method = NotificationMethods.CancelledNotification,
                Params = JsonSerializer.SerializeToNode(new CancelledNotificationParams { RequestId = state.Item2.Id }, McpJsonUtilities.JsonContext.Default.CancelledNotificationParams),
                RelatedTransport = state.Item2.RelatedTransport,
            });
        }, Tuple.Create(this, request));
    }

    public IAsyncDisposable RegisterNotificationHandler(string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler)
    {
        Throw.IfNullOrWhiteSpace(method);
        Throw.IfNull(handler);

        return _notificationHandlers.Register(method, handler);
    }

    ///
    /// Sends a JSON-RPC request to the server.
    /// It is strongly recommended use the capability-specific methods instead of this one.
    /// Use this method for custom requests or those not yet covered explicitly by the endpoint implementation.
    ///
    /// <param name="request">The JSON-RPC request to send.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task containing the server's response.</returns>
    public async Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();

        Histogram<double> durationMetric = _isServer ? s_serverOperationDuration : s_clientOperationDuration;
        string method = request.Method;

        long? startingTimestamp = durationMetric.Enabled ? Stopwatch.GetTimestamp() : null;
        using Activity? activity = Diagnostics.ShouldInstrumentMessage(request) ?
            Diagnostics.ActivitySource.StartActivity(CreateActivityName(method), ActivityKind.Client) :
            null;

        // Set request ID
        if (request.Id.Id is null)
        {
            request = request.WithId(new RequestId(Interlocked.Increment(ref _lastRequestId)));
        }

        _propagator.InjectActivityContext(activity, request);

        TagList tags = default;
        bool addTags = activity is { IsAllDataRequested: true } || startingTimestamp is not null;

        var tcs = new TaskCompletionSource<JsonRpcMessage>(TaskCreationOptions.RunContinuationsAsynchronously);
        _pendingRequests[request.Id] = tcs;
        try
        {
            if (addTags)
            {
                AddTags(ref tags, activity, request, method);
            }

            if (_logger.IsEnabled(LogLevel.Trace))
            {
                LogSendingRequestSensitive(EndpointName, request.Method, JsonSerializer.Serialize(request, McpJsonUtilities.JsonContext.Default.JsonRpcMessage));
            }
            else
            {
                LogSendingRequest(EndpointName, request.Method);
            }

            await SendToRelatedTransportAsync(request, cancellationToken).ConfigureAwait(false);

            // Now that the request has been sent, register for cancellation. If we registered before,
            // a cancellation request could arrive before the server knew about that request ID, in which
            // case the server could ignore it.
            LogRequestSentAwaitingResponse(EndpointName, request.Method, request.Id);
            JsonRpcMessage? response;
            using (var registration = RegisterCancellation(cancellationToken, request))
            {
                response = await tcs.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
            }

            if (response is JsonRpcError error)
            {
                LogSendingRequestFailed(EndpointName, request.Method, error.Error.Message, error.Error.Code);
                throw new McpException($"Request failed (remote): {error.Error.Message}", (McpErrorCode)error.Error.Code);
            }

            if (response is JsonRpcResponse success)
            {
                if (addTags)
                {
                    AddResponseTags(ref tags, activity, success.Result, method);
                }

                if (_logger.IsEnabled(LogLevel.Trace))
                {
                    LogRequestResponseReceivedSensitive(EndpointName, request.Method, success.Result?.ToJsonString() ?? "null");
                }
                else
                {
                    LogRequestResponseReceived(EndpointName, request.Method);
                }

                return success;
            }

            // Unexpected response type
            LogSendingRequestInvalidResponseType(EndpointName, request.Method);
            throw new McpException("Invalid response type");
        }
        catch (Exception ex) when (addTags)
        {
            AddExceptionTags(ref tags, activity, ex);
            throw;
        }
        finally
        {
            _pendingRequests.TryRemove(request.Id, out _);
            FinalizeDiagnostics(activity, startingTimestamp, durationMetric, ref tags);
        }
    }

    public async Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(message);

        cancellationToken.ThrowIfCancellationRequested();

        Histogram<double> durationMetric = _isServer ? s_serverOperationDuration : s_clientOperationDuration;
        string method = GetMethodName(message);

        long? startingTimestamp = durationMetric.Enabled ? Stopwatch.GetTimestamp() : null;
        using Activity? activity = Diagnostics.ShouldInstrumentMessage(message) ?
            Diagnostics.ActivitySource.StartActivity(CreateActivityName(method), ActivityKind.Client) :
            null;

        TagList tags = default;
        bool addTags = activity is { IsAllDataRequested: true } || startingTimestamp is not null;

        // propagate trace context
        _propagator?.InjectActivityContext(activity, message);

        try
        {
            if (addTags)
            {
                AddTags(ref tags, activity, message, method);
            }

            if (_logger.IsEnabled(LogLevel.Trace))
            {
                LogSendingMessageSensitive(EndpointName, JsonSerializer.Serialize(message, McpJsonUtilities.JsonContext.Default.JsonRpcMessage));
            }
            else
            {
                LogSendingMessage(EndpointName);
            }

            await SendToRelatedTransportAsync(message, cancellationToken).ConfigureAwait(false);

            // If the sent notification was a cancellation notification, cancel the pending request's await, as either the
            // server won't be sending a response, or per the specification, the response should be ignored. There are inherent
            // race conditions here, so it's possible and allowed for the operation to complete before we get to this point.
            if (message is JsonRpcNotification { Method: NotificationMethods.CancelledNotification } notification &&
                GetCancelledNotificationParams(notification.Params) is CancelledNotificationParams cn &&
                _pendingRequests.TryRemove(cn.RequestId, out var tcs))
            {
                tcs.TrySetCanceled(default);
            }
        }
        catch (Exception ex) when (addTags)
        {
            AddExceptionTags(ref tags, activity, ex);
            throw;
        }
        finally
        {
            FinalizeDiagnostics(activity, startingTimestamp, durationMetric, ref tags);
        }
    }

    // The JsonRpcMessage should be sent over the RelatedTransport if set. This is used to support the
    // Streamable HTTP transport where the specification states that the server SHOULD include JSON-RPC responses in
    // the HTTP response body for the POST request containing the corresponding JSON-RPC request.
    private Task SendToRelatedTransportAsync(JsonRpcMessage message, CancellationToken cancellationToken)
        => (message.RelatedTransport ?? _transport).SendMessageAsync(message, cancellationToken);

    private static CancelledNotificationParams? GetCancelledNotificationParams(JsonNode? notificationParams)
    {
        try
        {
            return JsonSerializer.Deserialize(notificationParams, McpJsonUtilities.JsonContext.Default.CancelledNotificationParams);
        }
        catch
        {
            return null;
        }
    }

    private string CreateActivityName(string method) => method;

    private static string GetMethodName(JsonRpcMessage message) =>
        message switch
        {
            JsonRpcRequest request => request.Method,
            JsonRpcNotification notification => notification.Method,
            _ => "unknownMethod"
        };

    private void AddTags(ref TagList tags, Activity? activity, JsonRpcMessage message, string method)
    {
        tags.Add("mcp.method.name", method);
        tags.Add("network.transport", _transportKind);

        // TODO: When using SSE transport, add:
        // - server.address and server.port on client spans and metrics
        // - client.address and client.port on server spans (not metrics because of cardinality) when using SSE transport
        if (activity is { IsAllDataRequested: true })
        {
            // session and request id have high cardinality, so not applying to metric tags
            activity.AddTag("mcp.session.id", _sessionId);

            if (message is JsonRpcMessageWithId withId)
            {
                activity.AddTag("mcp.request.id", withId.Id.Id?.ToString());
            }
        }

        JsonObject? paramsObj = message switch
        {
            JsonRpcRequest request => request.Params as JsonObject,
            JsonRpcNotification notification => notification.Params as JsonObject,
            _ => null
        };

        if (paramsObj == null)
        {
            return;
        }

        string? target = null;
        switch (method)
        {
            case RequestMethods.ToolsCall:
            case RequestMethods.PromptsGet:
                target = GetStringProperty(paramsObj, "name");
                if (target is not null)
                {
                    tags.Add(method == RequestMethods.ToolsCall ? "mcp.tool.name" : "mcp.prompt.name", target);
                }
                break;

            case RequestMethods.ResourcesRead:
            case RequestMethods.ResourcesSubscribe:
            case RequestMethods.ResourcesUnsubscribe:
            case NotificationMethods.ResourceUpdatedNotification:
                target = GetStringProperty(paramsObj, "uri");
                if (target is not null)
                {
                    tags.Add("mcp.resource.uri", target);
                }
                break;
        }

        if (activity is { IsAllDataRequested: true })
        {
            activity.DisplayName = target == null ? method : $"{method} {target}";
        }
    }

    private static void AddExceptionTags(ref TagList tags, Activity? activity, Exception e)
    {
        if (e is AggregateException ae && ae.InnerException is not null and not AggregateException)
        {
            e = ae.InnerException;
        }

        int? intErrorCode =
            (int?)((e as McpException)?.ErrorCode) is int errorCode ? errorCode :
            e is JsonException ? (int)McpErrorCode.ParseError :
            null;

        string? errorType = intErrorCode?.ToString() ?? e.GetType().FullName;
        tags.Add("error.type", errorType);
        if (intErrorCode is not null)
        {
            tags.Add("rpc.jsonrpc.error_code", errorType);
        }

        if (activity is { IsAllDataRequested: true })
        {
            activity.SetStatus(ActivityStatusCode.Error, e.Message);
        }
    }

    private static void AddResponseTags(ref TagList tags, Activity? activity, JsonNode? response, string method)
    {
        if (response is JsonObject jsonObject
            && jsonObject.TryGetPropertyValue("isError", out var isError)
            && isError?.GetValueKind() == JsonValueKind.True)
        {
            if (activity is { IsAllDataRequested: true })
            {
                string? content = null;
                if (jsonObject.TryGetPropertyValue("content", out var prop) && prop != null)
                {
                    content = prop.ToJsonString();
                }

                activity.SetStatus(ActivityStatusCode.Error, content);
            }

            tags.Add("error.type", method == RequestMethods.ToolsCall ? "tool_error" : "_OTHER");
        }
    }

    private static void FinalizeDiagnostics(
        Activity? activity, long? startingTimestamp, Histogram<double> durationMetric, ref TagList tags)
    {
        try
        {
            if (startingTimestamp is not null)
            {
                durationMetric.Record(GetElapsed(startingTimestamp.Value).TotalSeconds, tags);
            }

            if (activity is { IsAllDataRequested: true })
            {
                foreach (var tag in tags)
                {
                    activity.AddTag(tag.Key, tag.Value);
                }
            }
        }
        finally
        {
            activity?.Dispose();
        }
    }

    public void Dispose()
    {
        Histogram<double> durationMetric = _isServer ? s_serverSessionDuration : s_clientSessionDuration;
        if (durationMetric.Enabled)
        {
            TagList tags = default;
            tags.Add("network.transport", _transportKind);

            // TODO: Add server.address and server.port on client-side when using SSE transport,
            // client.* attributes are not added to metrics because of cardinality
            durationMetric.Record(GetElapsed(_sessionStartingTimestamp).TotalSeconds, tags);
        }

        // Complete all pending requests with cancellation
        foreach (var entry in _pendingRequests)
        {
            entry.Value.TrySetCanceled();
        }

        _pendingRequests.Clear();
    }

#if !NET
    private static readonly double s_timestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
#endif

    private static TimeSpan GetElapsed(long startingTimestamp) =>
#if NET
        Stopwatch.GetElapsedTime(startingTimestamp);
#else
        new((long)(s_timestampToTicks * (Stopwatch.GetTimestamp() - startingTimestamp)));
#endif

    private static string? GetStringProperty(JsonObject parameters, string propName)
    {
        if (parameters.TryGetPropertyValue(propName, out var prop) && prop?.GetValueKind() is JsonValueKind.String)
        {
            return prop.GetValue<string>();
        }

        return null;
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} message processing canceled.")]
    private partial void LogEndpointMessageProcessingCanceled(string endpointName);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} method '{Method}' request handler called.")]
    private partial void LogRequestHandlerCalled(string endpointName, string method);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} method '{Method}' request handler completed.")]
    private partial void LogRequestHandlerCompleted(string endpointName, string method);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} method '{Method}' request handler failed.")]
    private partial void LogRequestHandlerException(string endpointName, string method, Exception exception);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} received request for unknown request ID '{RequestId}'.")]
    private partial void LogNoRequestFoundForMessageWithId(string endpointName, RequestId requestId);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} request failed for method '{Method}': {ErrorMessage} ({ErrorCode}).")]
    private partial void LogSendingRequestFailed(string endpointName, string method, string errorMessage, int errorCode);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} received invalid response for method '{Method}'.")]
    private partial void LogSendingRequestInvalidResponseType(string endpointName, string method);

    [LoggerMessage(Level = LogLevel.Debug, Message = "{EndpointName} sending method '{Method}' request.")]
    private partial void LogSendingRequest(string endpointName, string method);

    [LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} sending method '{Method}' request. Request: '{Request}'.")]
    private partial void LogSendingRequestSensitive(string endpointName, string method, string request);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} canceled request '{RequestId}' per client notification. Reason: '{Reason}'.")]
    private partial void LogRequestCanceled(string endpointName, RequestId requestId, string? reason);

    [LoggerMessage(Level = LogLevel.Debug, Message = "{EndpointName} Request response received for method {method}")]
    private partial void LogRequestResponseReceived(string endpointName, string method);

    [LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} Request response received for method {method}. Response: '{Response}'.")]
    private partial void LogRequestResponseReceivedSensitive(string endpointName, string method, string response);

    [LoggerMessage(Level = LogLevel.Debug, Message = "{EndpointName} read {MessageType} message from channel.")]
    private partial void LogMessageRead(string endpointName, string messageType);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} message handler {MessageType} failed.")]
    private partial void LogMessageHandlerException(string endpointName, string messageType, Exception exception);

    [LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} message handler {MessageType} failed. Message: '{Message}'.")]
    private partial void LogMessageHandlerExceptionSensitive(string endpointName, string messageType, string message, Exception exception);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} received unexpected {MessageType} message type.")]
    private partial void LogEndpointHandlerUnexpectedMessageType(string endpointName, string messageType);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} received request for method '{Method}', but no handler is available.")]
    private partial void LogNoHandlerFoundForRequest(string endpointName, string method);

    [LoggerMessage(Level = LogLevel.Debug, Message = "{EndpointName} waiting for response to request '{RequestId}' for method '{Method}'.")]
    private partial void LogRequestSentAwaitingResponse(string endpointName, string method, RequestId requestId);

    [LoggerMessage(Level = LogLevel.Debug, Message = "{EndpointName} sending message.")]
    private partial void LogSendingMessage(string endpointName);

    [LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} sending message. Message: '{Message}'.")]
    private partial void LogSendingMessageSensitive(string endpointName, string message);
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using ModelContextProtocol;

namespace System.IO;

/// <summary>
/// Netfx-compatible polyfill of System.IO.StreamReader that supports cancellation.
/// </summary>
internal class CancellableStreamReader : TextReader
{
    // CancellableStreamReader.Null is threadsafe.
    public static new readonly CancellableStreamReader Null = new NullCancellableStreamReader();

    // Using a 1K byte buffer and a 4K FileStream buffer works out pretty well
    // perf-wise.  On even a 40 MB text file, any perf loss by using a 4K
    // buffer is negated by the win of allocating a smaller byte[], which
    // saves construction time.  This does break adaptive buffering,
    // but this is slightly faster.
    private const int DefaultBufferSize = 1024;  // Byte buffer size
    private const int DefaultFileStreamBufferSize = 4096;
    private const int MinBufferSize = 128;

    private readonly Stream _stream;
    private Encoding _encoding = null!; // only null in NullCancellableStreamReader where this is never used
    private readonly byte[] _encodingPreamble = null!; // only null in NullCancellableStreamReader where this is never used
    private Decoder _decoder = null!; // only null in NullCancellableStreamReader where this is never used
    private readonly byte[] _byteBuffer = null!; // only null in NullCancellableStreamReader where this is never used
    private char[] _charBuffer = null!; // only null in NullCancellableStreamReader where this is never used
    private int _charPos;
    private int _charLen;
    // Record the number of valid bytes in the byteBuffer, for a few checks.
    private int _byteLen;
    // This is used only for preamble detection
    private int _bytePos;

    // This is the maximum number of chars we can get from one call to
    // ReadBuffer.  Used so ReadBuffer can tell when to copy data into
    // a user's char[] directly, instead of our internal char[].
    private int _maxCharsPerBuffer;

    /// <summary>True if the writer has been disposed; otherwise, false.</summary>
    private bool _disposed;

    // We will support looking for byte order marks in the stream and trying
    // to decide what the encoding might be from the byte order marks, IF they
    // exist.  But that's all we'll do.
    private bool _detectEncoding;

    // Whether we must still check for the encoding's given preamble at the
    // beginning of this file.
    private bool _checkPreamble;

    // Whether the stream is most likely not going to give us back as much
    // data as we want the next time we call it.  We must do the computation
    // before we do any byte order mark handling and save the result.  Note
    // that we need this to allow users to handle streams used for an
    // interactive protocol, where they block waiting for the remote end
    // to send a response, like logging in on a Unix machine.
    private bool _isBlocked;

    // The intent of this field is to leave open the underlying stream when
    // disposing of this CancellableStreamReader.  A name like _leaveOpen is better,
    // but this type is serializable, and this field's name was _closable.
    private readonly bool _closable;  // Whether to close the underlying stream.

    // We don't guarantee thread safety on CancellableStreamReader, but we should at
    // least prevent users from trying to read anything while an Async
    // read from the same thread is in progress.
    private Task _asyncReadTask = Task.CompletedTask;

    private void CheckAsyncTaskInProgress()
    {
        // We are not locking the access to _asyncReadTask because this is not meant to guarantee thread safety.
        // We are simply trying to deter calling any Read APIs while an async Read from the same thread is in progress.
        if (!_asyncReadTask.IsCompleted)
        {
            ThrowAsyncIOInProgress();
        }
    }

    [DoesNotReturn]
    private static void ThrowAsyncIOInProgress() =>
        throw new InvalidOperationException("Async IO is in progress");

    // CancellableStreamReader by default will ignore illegal UTF8 characters. We don't want to
    // throw here because we want to be able to read ill-formed data without choking.
    // The high level goal is to be tolerant of encoding errors when we read and very strict
    // when we write. Hence, default StreamWriter encoding will throw on error.

    private CancellableStreamReader()
    {
        Debug.Assert(this is NullCancellableStreamReader);
        _stream = Stream.Null;
        _closable = true;
    }

    public CancellableStreamReader(Stream stream)
        : this(stream, true)
    {
    }

    public CancellableStreamReader(Stream stream, bool detectEncodingFromByteOrderMarks)
        : this(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize, false)
    {
    }

    public CancellableStreamReader(Stream stream, Encoding? encoding)
        : this(stream, encoding, true, DefaultBufferSize, false)
    {
    }

    public CancellableStreamReader(Stream stream, Encoding? encoding, bool detectEncodingFromByteOrderMarks)
        : this(stream, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize, false)
    {
    }

    // Creates a new CancellableStreamReader for the given stream.  The
    // character encoding is set by encoding and the buffer size,
    // in number of 16-bit characters, is set by bufferSize.
    //
    // Note that detectEncodingFromByteOrderMarks is a very
    // loose attempt at detecting the encoding by looking at the first
    // 3 bytes of the stream.  It will recognize UTF-8, little endian
    // unicode, and big endian unicode text, but that's it.  If neither
    // of those three match, it will use the Encoding you provided.
    //
    public CancellableStreamReader(Stream stream, Encoding? encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
        : this(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, false)
    {
    }

    public CancellableStreamReader(Stream stream, Encoding? encoding = null, bool detectEncodingFromByteOrderMarks = true, int bufferSize = -1, bool leaveOpen = false)
    {
        Throw.IfNull(stream);

        if (!stream.CanRead)
        {
            throw new ArgumentException("Stream not readable.");
        }

        if (bufferSize == -1)
        {
            bufferSize = DefaultBufferSize;
        }

        if (bufferSize <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, "Buffer size must be greater than zero.");
        }

        _stream = stream;
        _encoding = encoding ??= Encoding.UTF8;
        _decoder = encoding.GetDecoder();
        if (bufferSize < MinBufferSize)
        {
            bufferSize = MinBufferSize;
        }

        _byteBuffer = new byte[bufferSize];
        _maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
        _charBuffer = new char[_maxCharsPerBuffer];
        _detectEncoding = detectEncodingFromByteOrderMarks;
        _encodingPreamble = encoding.GetPreamble();

        // If the preamble length is larger than the byte buffer length,
        // we'll never match it and will enter an infinite loop. This
        // should never happen in practice, but just in case, we'll skip
        // the preamble check for absurdly long preambles.
        int preambleLength = _encodingPreamble.Length;
        _checkPreamble = preambleLength > 0 && preambleLength <= bufferSize;

        _closable = !leaveOpen;
    }

    public override void Close()
    {
        Dispose(true);
    }

    protected override void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }
        _disposed = true;

        // Dispose of our resources if this CancellableStreamReader is closable.
        if (_closable)
        {
            try
            {
                // Note that Stream.Close() can potentially throw here. So we need to
                // ensure cleaning up internal resources, inside the finally block.
                if (disposing)
                {
                    _stream.Close();
                }
            }
            finally
            {
                _charPos = 0;
                _charLen = 0;
                base.Dispose(disposing);
            }
        }
    }

    public virtual Encoding CurrentEncoding => _encoding;

    public virtual Stream BaseStream => _stream;

    // DiscardBufferedData tells CancellableStreamReader to throw away its internal
    // buffer contents.  This is useful if the user needs to seek on the
    // underlying stream to a known location then wants the CancellableStreamReader
    // to start reading from this new point.  This method should be called
    // very sparingly, if ever, since it can lead to very poor performance.
    // However, it may be the only way of handling some scenarios where
    // users need to re-read the contents of a CancellableStreamReader a second time.
    public void DiscardBufferedData()
    {
        CheckAsyncTaskInProgress();

        _byteLen = 0;
        _charLen = 0;
        _charPos = 0;
        // in general we'd like to have an invariant that encoding isn't null. However,
        // for startup improvements for NullCancellableStreamReader, we want to delay load encoding.
        if (_encoding != null)
        {
            _decoder = _encoding.GetDecoder();
        }
        _isBlocked = false;
    }

    public bool EndOfStream
    {
        get
        {
            ThrowIfDisposed();
            CheckAsyncTaskInProgress();

            if (_charPos < _charLen)
            {
                return false;
            }

            // This may block on pipes!
            int numRead = ReadBuffer();
            return numRead == 0;
        }
    }

    public override int Peek()
    {
        ThrowIfDisposed();
        CheckAsyncTaskInProgress();

        if (_charPos == _charLen)
        {
            if (ReadBuffer() == 0)
            {
                return -1;
            }
        }
        return _charBuffer[_charPos];
    }

    public override int Read()
    {
        ThrowIfDisposed();
        CheckAsyncTaskInProgress();

        if (_charPos == _charLen)
        {
            if (ReadBuffer() == 0)
            {
                return -1;
            }
        }
        int result = _charBuffer[_charPos];
        _charPos++;
        return result;
    }

    public override int Read(char[] buffer, int index, int count)
    {
        Throw.IfNull(buffer);
        Throw.IfNegative(index);
        Throw.IfNegative(count);

        return ReadSpan(new Span<char>(buffer, index, count));
    }

    private int ReadSpan(Span<char> buffer)
    {
        ThrowIfDisposed();
        CheckAsyncTaskInProgress();

        int charsRead = 0;
        // As a perf optimization, if we had exactly one buffer's worth of
        // data read in, let's try writing directly to the user's buffer.
        bool readToUserBuffer = false;
        int count = buffer.Length;
        while (count > 0)
        {
            int n = _charLen - _charPos;
            if (n == 0)
            {
                n = ReadBuffer(buffer.Slice(charsRead), out readToUserBuffer);
            }
            if (n == 0)
            {
                break;  // We're at EOF
            }
            if (n > count)
            {
                n = count;
            }
            if (!readToUserBuffer)
            {
                new Span<char>(_charBuffer, _charPos, n).CopyTo(buffer.Slice(charsRead));
                _charPos += n;
            }

            charsRead += n;
            count -= n;
            // This function shouldn't block for an indefinite amount of time,
            // or reading from a network stream won't work right.  If we got
            // fewer bytes than we requested, then we want to break right here.
            if (_isBlocked)
            {
                break;
            }
        }

        return charsRead;
    }

    public override string ReadToEnd()
    {
        ThrowIfDisposed();
        CheckAsyncTaskInProgress();

        // Call ReadBuffer, then pull data out of charBuffer.
        StringBuilder sb = new StringBuilder(_charLen - _charPos);
        do
        {
            sb.Append(_charBuffer, _charPos, _charLen - _charPos);
            _charPos = _charLen;  // Note we consumed these characters
            ReadBuffer();
        } while (_charLen > 0);
        return sb.ToString();
    }

    public override int ReadBlock(char[] buffer, int index, int count)
    {
        Throw.IfNull(buffer);

        Throw.IfNegative(index);
        Throw.IfNegative(count);
        if (buffer.Length - index < count)
        {
            throw new ArgumentException("invalid offset length.");
        }
        ThrowIfDisposed();
        CheckAsyncTaskInProgress();

        return base.ReadBlock(buffer, index, count);
    }

    // Trims n bytes from the front of the buffer.
    private void CompressBuffer(int n)
    {
        Debug.Assert(_byteLen >= n, "CompressBuffer was called with a number of bytes greater than the current buffer length.  Are two threads using this CancellableStreamReader at the same time?");
        byte[] byteBuffer = _byteBuffer;
        _ = byteBuffer.Length; // allow JIT to prove object is not null
        new ReadOnlySpan<byte>(byteBuffer, n, _byteLen - n).CopyTo(byteBuffer);
        _byteLen -= n;
    }

    private void DetectEncoding()
    {
        Debug.Assert(_byteLen >= 2, "Caller should've validated that at least 2 bytes were available.");

        byte[] byteBuffer = _byteBuffer;
        _detectEncoding = false;
        bool changedEncoding = false;

        ushort firstTwoBytes = BinaryPrimitives.ReadUInt16LittleEndian(byteBuffer);
        if (firstTwoBytes == 0xFFFE)
        {
            // Big Endian Unicode
            _encoding = Encoding.BigEndianUnicode;
            CompressBuffer(2);
            changedEncoding = true;
        }
        else if (firstTwoBytes == 0xFEFF)
        {
            // Little Endian Unicode, or possibly little endian UTF32
            if (_byteLen < 4 || byteBuffer[2] != 0 || byteBuffer[3] != 0)
            {
                _encoding = Encoding.Unicode;
                CompressBuffer(2);
                changedEncoding = true;
            }
            else
            {
                _encoding = Encoding.UTF32;
                CompressBuffer(4);
                changedEncoding = true;
            }
        }
        else if (_byteLen >= 3 && firstTwoBytes == 0xBBEF && byteBuffer[2] == 0xBF)
        {
            // UTF-8
            _encoding = Encoding.UTF8;
            CompressBuffer(3);
            changedEncoding = true;
        }
        else if (_byteLen >= 4 && firstTwoBytes == 0 && byteBuffer[2] == 0xFE && byteBuffer[3] == 0xFF)
        {
            // Big Endian UTF32
            _encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true);
            CompressBuffer(4);
            changedEncoding = true;
        }
        else if (_byteLen == 2)
        {
            _detectEncoding = true;
        }
        // Note: in the future, if we change this algorithm significantly,
        // we can support checking for the preamble of the given encoding.

        if (changedEncoding)
        {
            _decoder = _encoding.GetDecoder();
            int newMaxCharsPerBuffer = _encoding.GetMaxCharCount(byteBuffer.Length);
            if (newMaxCharsPerBuffer > _maxCharsPerBuffer)
            {
                _charBuffer = new char[newMaxCharsPerBuffer];
            }
            _maxCharsPerBuffer = newMaxCharsPerBuffer;
        }
    }

    // Trims the preamble bytes from the byteBuffer. This routine can be called multiple times
    // and we will buffer the bytes read until the preamble is matched or we determine that
    // there is no match. If there is no match, every byte read previously will be available
    // for further consumption. If there is a match, we will compress the buffer for the
    // leading preamble bytes
    private bool IsPreamble()
    {
        if (!_checkPreamble)
        {
            return false;
        }

        return IsPreambleWorker(); // move this call out of the hot path
        bool IsPreambleWorker()
        {
            Debug.Assert(_checkPreamble);
            ReadOnlySpan<byte> preamble = _encodingPreamble;

            Debug.Assert(_bytePos < preamble.Length, "_compressPreamble was called with the current bytePos greater than the preamble buffer length.  Are two threads using this CancellableStreamReader at the same time?");
            int len = Math.Min(_byteLen, preamble.Length);

            for (int i = _bytePos; i < len; i++)
            {
                if (_byteBuffer[i] != preamble[i])
                {
                    _bytePos = 0; // preamble match failed; back up to beginning of buffer
                    _checkPreamble = false;
                    return false;
                }
            }
            _bytePos = len; // we've matched all bytes up to this point

            Debug.Assert(_bytePos <= preamble.Length, "possible bug in _compressPreamble.  Are two threads using this CancellableStreamReader at the same time?");

            if (_bytePos == preamble.Length)
            {
                // We have a match
                CompressBuffer(preamble.Length);
                _bytePos = 0;
                _checkPreamble = false;
                _detectEncoding = false;
            }

            return _checkPreamble;
        }
    }

    internal virtual int ReadBuffer()
    {
        _charLen = 0;
        _charPos = 0;

        if (!_checkPreamble)
        {
            _byteLen = 0;
        }

        bool eofReached = false;

        do
        {
            if (_checkPreamble)
            {
                Debug.Assert(_bytePos <= _encodingPreamble.Length, "possible bug in _compressPreamble.  Are two threads using this CancellableStreamReader at the same time?");
                int len = _stream.Read(_byteBuffer, _bytePos, _byteBuffer.Length - _bytePos);
                Debug.Assert(len >= 0, "Stream.Read returned a negative number!  This is a bug in your stream class.");

                if (len == 0)
                {
                    eofReached = true;
                    break;
                }

                _byteLen += len;
            }
            else
            {
                Debug.Assert(_bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble.  Are two threads using this CancellableStreamReader at the same time?");
                _byteLen = _stream.Read(_byteBuffer, 0, _byteBuffer.Length);
                Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number!  This is a bug in your stream class.");

                if (_byteLen == 0)
                {
                    eofReached = true;
                    break;
                }
            }

            // _isBlocked == whether we read fewer bytes than we asked for.
            // Note we must check it here because CompressBuffer or
            // DetectEncoding will change byteLen.
            _isBlocked = (_byteLen < _byteBuffer.Length);

            // Check for preamble before detect encoding. This is not to override the
            // user supplied Encoding for the one we implicitly detect. The user could
            // customize the encoding which we will loose, such as ThrowOnError on UTF8
            if (IsPreamble())
            {
                continue;
            }

            // If we're supposed to detect the encoding and haven't done so yet,
            // do it.  Note this may need to be called more than once.
            if (_detectEncoding && _byteLen >= 2)
            {
                DetectEncoding();
            }

            Debug.Assert(_charPos == 0 && _charLen == 0, "We shouldn't be trying to decode more data if we made progress in an earlier iteration.");
            _charLen = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, 0, flush: false);
        } while (_charLen == 0);

        if (eofReached)
        {
            // EOF has been reached - perform final flush.
            // We need to reset _bytePos and _byteLen just in case we hadn't
            // finished processing the preamble before we reached EOF.

            Debug.Assert(_charPos == 0 && _charLen == 0, "We shouldn't be looking for EOF unless we have an empty char buffer.");
            _charLen = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, 0, flush: true);
            _bytePos = 0;
            _byteLen = 0;
        }

        return _charLen;
    }

    // This version has a perf optimization to decode data DIRECTLY into the
    // user's buffer, bypassing CancellableStreamReader's own buffer.
    // This gives a > 20% perf improvement for our encodings across the board,
    // but only when asking for at least the number of characters that one
    // buffer's worth of bytes could produce.
    // This optimization, if run, will break SwitchEncoding, so we must not do
    // this on the first call to ReadBuffer.
    private int ReadBuffer(Span<char> userBuffer, out bool readToUserBuffer)
    {
        _charLen = 0;
        _charPos = 0;

        if (!_checkPreamble)
        {
            _byteLen = 0;
        }

        bool eofReached = false;
        int charsRead = 0;

        // As a perf optimization, we can decode characters DIRECTLY into a
        // user's char[].  We absolutely must not write more characters
        // into the user's buffer than they asked for.  Calculating
        // encoding.GetMaxCharCount(byteLen) each time is potentially very
        // expensive - instead, cache the number of chars a full buffer's
        // worth of data may produce.  Yes, this makes the perf optimization
        // less aggressive, in that all reads that asked for fewer than AND
        // returned fewer than _maxCharsPerBuffer chars won't get the user
        // buffer optimization.  This affects reads where the end of the
        // Stream comes in the middle somewhere, and when you ask for
        // fewer chars than your buffer could produce.
        readToUserBuffer = userBuffer.Length >= _maxCharsPerBuffer;

        do
        {
            Debug.Assert(charsRead == 0);

            if (_checkPreamble)
            {
                Debug.Assert(_bytePos <= _encodingPreamble.Length, "possible bug in _compressPreamble.  Are two threads using this CancellableStreamReader at the same time?");
                int len = _stream.Read(_byteBuffer, _bytePos, _byteBuffer.Length - _bytePos);
                Debug.Assert(len >= 0, "Stream.Read returned a negative number!  This is a bug in your stream class.");

                if (len == 0)
                {
                    eofReached = true;
                    break;
                }

                _byteLen += len;
            }
            else
            {
                Debug.Assert(_bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble.  Are two threads using this CancellableStreamReader at the same time?");
                _byteLen = _stream.Read(_byteBuffer, 0, _byteBuffer.Length);
                Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number!  This is a bug in your stream class.");

                if (_byteLen == 0)
                {
                    eofReached = true;
                    break;
                }
            }

            // _isBlocked == whether we read fewer bytes than we asked for.
            // Note we must check it here because CompressBuffer or
            // DetectEncoding will change byteLen.
            _isBlocked = (_byteLen < _byteBuffer.Length);

            // Check for preamble before detect encoding. This is not to override the
            // user supplied Encoding for the one we implicitly detect. The user could
            // customize the encoding which we will loose, such as ThrowOnError on UTF8
            // Note: we don't need to recompute readToUserBuffer optimization as IsPreamble
            // doesn't change the encoding or affect _maxCharsPerBuffer
            if (IsPreamble())
            {
                continue;
            }

            // On the first call to ReadBuffer, if we're supposed to detect the encoding, do it.
            if (_detectEncoding && _byteLen >= 2)
            {
                DetectEncoding();
                // DetectEncoding changes some buffer state.  Recompute this.
                readToUserBuffer = userBuffer.Length >= _maxCharsPerBuffer;
            }

            Debug.Assert(charsRead == 0 && _charPos == 0 && _charLen == 0, "We shouldn't be trying to decode more data if we made progress in an earlier iteration.");
            if (readToUserBuffer)
            {
                charsRead = GetChars(_decoder, new ReadOnlySpan<byte>(_byteBuffer, 0, _byteLen), userBuffer, flush: false);
            }
            else
            {
                charsRead = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, 0, flush: false);
                _charLen = charsRead;  // Number of chars in CancellableStreamReader's buffer.
            }
        } while (charsRead == 0);

        if (eofReached)
        {
            // EOF has been reached - perform final flush.
            // We need to reset _bytePos and _byteLen just in case we hadn't
            // finished processing the preamble before we reached EOF.

            Debug.Assert(charsRead == 0 && _charPos == 0 && _charLen == 0, "We shouldn't be looking for EOF unless we have an empty char buffer.");

            if (readToUserBuffer)
            {
                charsRead = GetChars(_decoder, new ReadOnlySpan<byte>(_byteBuffer, 0, _byteLen), userBuffer, flush: true);
            }
            else
            {
                charsRead = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, 0, flush: true);
                _charLen = charsRead;  // Number of chars in CancellableStreamReader's buffer.
            }
            _bytePos = 0;
            _byteLen = 0;
        }

        _isBlocked &= charsRead < userBuffer.Length;

        return charsRead;
    }

    // Reads a line. A line is defined as a sequence of characters followed by
    // a carriage return ('\r'), a line feed ('\n'), or a carriage return
    // immediately followed by a line feed. The resulting string does not
    // contain the terminating carriage return and/or line feed. The returned
    // value is null if the end of the input stream has been reached.
    //
    public override string? ReadLine()
    {
        ThrowIfDisposed();
        CheckAsyncTaskInProgress();

        if (_charPos == _charLen)
        {
            if (ReadBuffer() == 0)
            {
                return null;
            }
        }

        var vsb = new ValueStringBuilder(stackalloc char[256]);
        do
        {
            // Look for '\r' or \'n'.
            ReadOnlySpan<char> charBufferSpan = _charBuffer.AsSpan(_charPos, _charLen - _charPos);
            Debug.Assert(!charBufferSpan.IsEmpty, "ReadBuffer returned > 0 but didn't bump _charLen?");

            int idxOfNewline = charBufferSpan.IndexOfAny('\r', '\n');
            if (idxOfNewline >= 0)
            {
                string retVal;
                if (vsb.Length == 0)
                {
                    retVal = charBufferSpan.Slice(0, idxOfNewline).ToString();
                }
                else
                {
                    retVal = string.Concat(vsb.AsSpan().ToString(), charBufferSpan.Slice(0, idxOfNewline).ToString());
                    vsb.Dispose();
                }

                char matchedChar = charBufferSpan[idxOfNewline];
                _charPos += idxOfNewline + 1;

                // If we found '\r', consume any immediately following '\n'.
                if (matchedChar == '\r')
                {
                    if (_charPos < _charLen || ReadBuffer() > 0)
                    {
                        if (_charBuffer[_charPos] == '\n')
                        {
                            _charPos++;
                        }
                    }
                }

                return retVal;
            }

            // We didn't find '\r' or '\n'. Add it to the StringBuilder
            // and loop until we reach a newline or EOF.

            vsb.Append(charBufferSpan);
        } while (ReadBuffer() > 0);

        return vsb.ToString();
    }

    public override Task<string?> ReadLineAsync() =>
        ReadLineAsync(default).AsTask();

    /// <summary>
    /// Reads a line of characters asynchronously from the current stream and returns the data as a string.
    /// </summary>
    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
    /// <returns>A value task that represents the asynchronous read operation. The value of the <c>TResult</c>
    /// parameter contains the next line from the stream, or is <see langword="null" /> if all of the characters have been read.</returns>
    /// <exception cref="ArgumentOutOfRangeException">The number of characters in the next line is larger than <see cref="int.MaxValue"/>.</exception>
    /// <exception cref="ObjectDisposedException">The stream reader has been disposed.</exception>
    /// <exception cref="InvalidOperationException">The reader is currently in use by a previous read operation.</exception>
    /// <example>
    /// The following example shows how to read and print all lines from the file until the end of the file is reached or the operation timed out.
    /// <code lang="C#">
    /// using CancellationTokenSource tokenSource = new (TimeSpan.FromSeconds(1));
    /// using CancellableStreamReader reader = File.OpenText("existingfile.txt");
    ///
    /// string line;
    /// while ((line = await reader.ReadLineAsync(tokenSource.Token)) is not null)
    /// {
    ///     Console.WriteLine(line);
    /// }
    /// </code>
    /// </example>
    /// <remarks>
    /// If this method is canceled via <paramref name="cancellationToken"/>, some data
    /// that has been read from the current <see cref="Stream"/> but not stored (by the
    /// <see cref="CancellableStreamReader"/>) or returned (to the caller) may be lost.
    /// </remarks>
    public virtual ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
    {
        // If we have been inherited into a subclass, the following implementation could be incorrect
        // since it does not call through to Read() which a subclass might have overridden.
        // To be safe we will only use this implementation in cases where we know it is safe to do so,
        // and delegate to our base class (which will call into Read) when we are not sure.
        if (GetType() != typeof(CancellableStreamReader))
        {
            return new ValueTask<string?>(base.ReadLineAsync()!);
        }

        ThrowIfDisposed();
        CheckAsyncTaskInProgress();

        Task<string?> task = ReadLineAsyncInternal(cancellationToken);
        _asyncReadTask = task;

        return new ValueTask<string?>(task);
    }

    private async Task<string?> ReadLineAsyncInternal(CancellationToken cancellationToken)
    {
        if (_charPos == _charLen && (await ReadBufferAsync(cancellationToken).ConfigureAwait(false)) == 0)
        {
            return null;
        }

        string retVal;
        char[]? arrayPoolBuffer = null;
        int arrayPoolBufferPos = 0;

        do
        {
            char[] charBuffer = _charBuffer;
            int charLen = _charLen;
            int charPos = _charPos;

            // Look for '\r' or \'n'.
            Debug.Assert(charPos < charLen, "ReadBuffer returned > 0 but didn't bump _charLen?");

            int idxOfNewline = charBuffer.AsSpan(charPos, charLen - charPos).IndexOfAny('\r', '\n');
            if (idxOfNewline >= 0)
            {
                if (arrayPoolBuffer is null)
                {
                    retVal = new string(charBuffer, charPos, idxOfNewline);
                }
                else
                {
                    retVal = string.Concat(arrayPoolBuffer.AsSpan(0, arrayPoolBufferPos).ToString(), charBuffer.AsSpan(charPos, idxOfNewline).ToString());
                    ArrayPool<char>.Shared.Return(arrayPoolBuffer);
                }

                charPos += idxOfNewline;
                char matchedChar = charBuffer[charPos++];
                _charPos = charPos;

                // If we found '\r', consume any immediately following '\n'.
                if (matchedChar == '\r')
                {
                    if (charPos < charLen || (await ReadBufferAsync(cancellationToken).ConfigureAwait(false)) > 0)
                    {
                        if (_charBuffer[_charPos] == '\n')
                        {
                            _charPos++;
                        }
                    }
                }

                return retVal;
            }

            // We didn't find '\r' or '\n'. Add the read data to the pooled buffer
            // and loop until we reach a newline or EOF.
            if (arrayPoolBuffer is null)
            {
                arrayPoolBuffer = ArrayPool<char>.Shared.Rent(charLen - charPos + 80);
            }
            else if ((arrayPoolBuffer.Length - arrayPoolBufferPos) < (charLen - charPos))
            {
                char[] newBuffer = ArrayPool<char>.Shared.Rent(checked(arrayPoolBufferPos + charLen - charPos));
                arrayPoolBuffer.AsSpan(0, arrayPoolBufferPos).CopyTo(newBuffer);
                ArrayPool<char>.Shared.Return(arrayPoolBuffer);
                arrayPoolBuffer = newBuffer;
            }
            charBuffer.AsSpan(charPos, charLen - charPos).CopyTo(arrayPoolBuffer.AsSpan(arrayPoolBufferPos));
            arrayPoolBufferPos += charLen - charPos;
        }
        while (await ReadBufferAsync(cancellationToken).ConfigureAwait(false) > 0);

        if (arrayPoolBuffer is not null)
        {
            retVal = new string(arrayPoolBuffer, 0, arrayPoolBufferPos);
            ArrayPool<char>.Shared.Return(arrayPoolBuffer);
        }
        else
        {
            retVal = string.Empty;
        }

        return retVal;
    }

    public override Task<string> ReadToEndAsync() => ReadToEndAsync(default);

    /// <summary>
    /// Reads all characters from the current position to the end of the stream asynchronously and returns them as one string.
    /// </summary>
    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
    /// <returns>A task that represents the asynchronous read operation. The value of the <c>TResult</c> parameter contains
    /// a string with the characters from the current position to the end of the stream.</returns>
    /// <exception cref="ArgumentOutOfRangeException">The number of characters is larger than <see cref="int.MaxValue"/>.</exception>
    /// <exception cref="ObjectDisposedException">The stream reader has been disposed.</exception>
    /// <exception cref="InvalidOperationException">The reader is currently in use by a previous read operation.</exception>
    /// <example>
    /// The following example shows how to read the contents of a file by using the <see cref="ReadToEndAsync(CancellationToken)"/> method.
    /// <code lang="C#">
    /// using CancellationTokenSource tokenSource = new (TimeSpan.FromSeconds(1));
    /// using CancellableStreamReader reader = File.OpenText("existingfile.txt");
    ///
    /// Console.WriteLine(await reader.ReadToEndAsync(tokenSource.Token));
    /// </code>
    /// </example>
    /// <remarks>
    /// If this method is canceled via <paramref name="cancellationToken"/>, some data
    /// that has been read from the current <see cref="Stream"/> but not stored (by the
    /// <see cref="CancellableStreamReader"/>) or returned (to the caller) may be lost.
    /// </remarks>
    public virtual Task<string> ReadToEndAsync(CancellationToken cancellationToken)
    {
        // If we have been inherited into a subclass, the following implementation could be incorrect
        // since it does not call through to Read() which a subclass might have overridden.
        // To be safe we will only use this implementation in cases where we know it is safe to do so,
        // and delegate to our base class (which will call into Read) when we are not sure.
        if (GetType() != typeof(CancellableStreamReader))
        {
            return base.ReadToEndAsync();
        }

        ThrowIfDisposed();
        CheckAsyncTaskInProgress();

        Task<string> task = ReadToEndAsyncInternal(cancellationToken);
        _asyncReadTask = task;

        return task;
    }

    private async Task<string> ReadToEndAsyncInternal(CancellationToken cancellationToken)
    {
        // Call ReadBuffer, then pull data out of charBuffer.
        StringBuilder sb = new StringBuilder(_charLen - _charPos);
        do
        {
            int tmpCharPos = _charPos;
            sb.Append(_charBuffer, tmpCharPos, _charLen - tmpCharPos);
            _charPos = _charLen;  // We consumed these characters
            await ReadBufferAsync(cancellationToken).ConfigureAwait(false);
        } while (_charLen > 0);

        return sb.ToString();
    }

    public override Task<int> ReadAsync(char[] buffer, int index, int count)
    {
        Throw.IfNull(buffer);

        Throw.IfNegative(index);
        Throw.IfNegative(count);
        if (buffer.Length - index < count)
        {
            throw new ArgumentException("invalid offset length.");
        }

        // If we have been inherited into a subclass, the following implementation could be incorrect
        // since it does not call through to Read() which a subclass might have overridden.
        // To be safe we will only use this implementation in cases where we know it is safe to do so,
        // and delegate to our base class (which will call into Read) when we are not sure.
        if (GetType() != typeof(CancellableStreamReader))
        {
            return base.ReadAsync(buffer, index, count);
        }

        ThrowIfDisposed();
        CheckAsyncTaskInProgress();

        Task<int> task = ReadAsyncInternal(new Memory<char>(buffer, index, count), CancellationToken.None).AsTask();
        _asyncReadTask = task;

        return task;
    }

    public virtual ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default)
    {
        Debug.Assert(GetType() == typeof(CancellableStreamReader));

        ThrowIfDisposed();
        CheckAsyncTaskInProgress();

        if (cancellationToken.IsCancellationRequested)
        {
            return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
        }

        return ReadAsyncInternal(buffer, cancellationToken);
    }

    private protected virtual async ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken)
    {
        if (_charPos == _charLen && (await ReadBufferAsync(cancellationToken).ConfigureAwait(false)) == 0)
        {
            return 0;
        }

        int charsRead = 0;

        // As a perf optimization, if we had exactly one buffer's worth of
        // data read in, let's try writing directly to the user's buffer.
        bool readToUserBuffer = false;

        byte[] tmpByteBuffer = _byteBuffer;
        Stream tmpStream = _stream;

        int count = buffer.Length;
        while (count > 0)
        {
            // n is the characters available in _charBuffer
            int n = _charLen - _charPos;

            // charBuffer is empty, let's read from the stream
            if (n == 0)
            {
                _charLen = 0;
                _charPos = 0;

                if (!_checkPreamble)
                {
                    _byteLen = 0;
                }

                readToUserBuffer = count >= _maxCharsPerBuffer;

                // We loop here so that we read in enough bytes to yield at least 1 char.
                // We break out of the loop if the stream is blocked (EOF is reached).
                do
                {
                    Debug.Assert(n == 0);

                    if (_checkPreamble)
                    {
                        Debug.Assert(_bytePos <= _encodingPreamble.Length, "possible bug in _compressPreamble.  Are two threads using this CancellableStreamReader at the same time?");
                        int tmpBytePos = _bytePos;
                        int len = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos), cancellationToken).ConfigureAwait(false);
                        Debug.Assert(len >= 0, "Stream.Read returned a negative number!  This is a bug in your stream class.");

                        if (len == 0)
                        {
                            // EOF but we might have buffered bytes from previous
                            // attempts to detect preamble that needs to be decoded now
                            if (_byteLen > 0)
                            {
                                if (readToUserBuffer)
                                {
                                    n = GetChars(_decoder, new ReadOnlySpan<byte>(tmpByteBuffer, 0, _byteLen), buffer.Span.Slice(charsRead), flush: false);
                                    _charLen = 0;  // CancellableStreamReader's buffer is empty.
                                }
                                else
                                {
                                    n = _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, 0);
                                    _charLen += n;  // Number of chars in CancellableStreamReader's buffer.
                                }
                            }

                            // How can part of the preamble yield any chars?
                            Debug.Assert(n == 0);

                            _isBlocked = true;
                            break;
                        }
                        else
                        {
                            _byteLen += len;
                        }
                    }
                    else
                    {
                        Debug.Assert(_bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble.  Are two threads using this CancellableStreamReader at the same time?");

                        _byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer), cancellationToken).ConfigureAwait(false);

                        Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number!  This is a bug in your stream class.");

                        if (_byteLen == 0)  // EOF
                        {
                            _isBlocked = true;
                            break;
                        }
                    }

                    // _isBlocked == whether we read fewer bytes than we asked for.
                    // Note we must check it here because CompressBuffer or
                    // DetectEncoding will change _byteLen.
                    _isBlocked = (_byteLen < tmpByteBuffer.Length);

                    // Check for preamble before detect encoding. This is not to override the
                    // user supplied Encoding for the one we implicitly detect. The user could
                    // customize the encoding which we will loose, such as ThrowOnError on UTF8
                    // Note: we don't need to recompute readToUserBuffer optimization as IsPreamble
                    // doesn't change the encoding or affect _maxCharsPerBuffer
                    if (IsPreamble())
                    {
                        continue;
                    }

                    // On the first call to ReadBuffer, if we're supposed to detect the encoding, do it.
                    if (_detectEncoding && _byteLen >= 2)
                    {
                        DetectEncoding();
                        // DetectEncoding changes some buffer state.  Recompute this.
                        readToUserBuffer = count >= _maxCharsPerBuffer;
                    }

                    Debug.Assert(n == 0);

                    _charPos = 0;
                    if (readToUserBuffer)
                    {
                        n = GetChars(_decoder, new ReadOnlySpan<byte>(tmpByteBuffer, 0, _byteLen), buffer.Span.Slice(charsRead), flush: false);
                        _charLen = 0;  // CancellableStreamReader's buffer is empty.
                    }
                    else
                    {
                        n = _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, 0);
                        _charLen += n;  // Number of chars in CancellableStreamReader's buffer.
                    }
                } while (n == 0);

                if (n == 0)
                {
                    break;  // We're at EOF
                }
            }  // if (n == 0)

            // Got more chars in charBuffer than the user requested
            if (n > count)
            {
                n = count;
            }

            if (!readToUserBuffer)
            {
                new Span<char>(_charBuffer, _charPos, n).CopyTo(buffer.Span.Slice(charsRead));
                _charPos += n;
            }

            charsRead += n;
            count -= n;

            // This function shouldn't block for an indefinite amount of time,
            // or reading from a network stream won't work right.  If we got
            // fewer bytes than we requested, then we want to break right here.
            if (_isBlocked)
            {
                break;
            }
        }  // while (count > 0)

        return charsRead;
    }

    public override Task<int> ReadBlockAsync(char[] buffer, int index, int count)
    {
        Throw.IfNull(buffer);

        Throw.IfNegative(index);
        Throw.IfNegative(count);
        if (buffer.Length - index < count)
        {
            throw new ArgumentException("invalid offset length.");
        }

        // If we have been inherited into a subclass, the following implementation could be incorrect
        // since it does not call through to Read() which a subclass might have overridden.
        // To be safe we will only use this implementation in cases where we know it is safe to do so,
        // and delegate to our base class (which will call into Read) when we are not sure.
        if (GetType() != typeof(CancellableStreamReader))
        {
            return base.ReadBlockAsync(buffer, index, count);
        }

        ThrowIfDisposed();
        CheckAsyncTaskInProgress();

        Task<int> task = base.ReadBlockAsync(buffer, index, count);
        _asyncReadTask = task;

        return task;
    }

    public virtual ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default)
    {
        Debug.Assert(GetType() == typeof(CancellableStreamReader));

        ThrowIfDisposed();
        CheckAsyncTaskInProgress();

        if (cancellationToken.IsCancellationRequested)
        {
            return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
        }

        ValueTask<int> vt = ReadBlockAsyncInternal(buffer, cancellationToken);
        if (vt.IsCompletedSuccessfully)
        {
            return vt;
        }

        Task<int> t = vt.AsTask();
        _asyncReadTask = t;
        return new ValueTask<int>(t);
    }

    private async ValueTask<int> ReadBufferAsync(CancellationToken cancellationToken)
    {
        _charLen = 0;
        _charPos = 0;
        byte[] tmpByteBuffer = _byteBuffer;
        Stream tmpStream = _stream;

        if (!_checkPreamble)
        {
            _byteLen = 0;
        }

        bool eofReached = false;

        do
        {
            if (_checkPreamble)
            {
                Debug.Assert(_bytePos <= _encodingPreamble.Length, "possible bug in _compressPreamble. Are two threads using this CancellableStreamReader at the same time?");
                int tmpBytePos = _bytePos;
                int len = await tmpStream.ReadAsync(tmpByteBuffer.AsMemory(tmpBytePos), cancellationToken).ConfigureAwait(false);
                Debug.Assert(len >= 0, "Stream.Read returned a negative number!  This is a bug in your stream class.");

                if (len == 0)
                {
                    eofReached = true;
                    break;
                }

                _byteLen += len;
            }
            else
            {
                Debug.Assert(_bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this CancellableStreamReader at the same time?");
                _byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer), cancellationToken).ConfigureAwait(false);
                Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number!  Bug in stream class.");

                if (_byteLen == 0)
                {
                    eofReached = true;
                    break;
                }
            }

            // _isBlocked == whether we read fewer bytes than we asked for.
            // Note we must check it here because CompressBuffer or
            // DetectEncoding will change _byteLen.
            _isBlocked = (_byteLen < tmpByteBuffer.Length);

            // Check for preamble before detect encoding. This is not to override the
            // user supplied Encoding for the one we implicitly detect. The user could
            // customize the encoding which we will loose, such as ThrowOnError on UTF8
            if (IsPreamble())
            {
                continue;
            }

            // If we're supposed to detect the encoding and haven't done so yet,
            // do it.  Note this may need to be called more than once.
            if (_detectEncoding && _byteLen >= 2)
            {
                DetectEncoding();
            }

            Debug.Assert(_charPos == 0 && _charLen == 0, "We shouldn't be trying to decode more data if we made progress in an earlier iteration.");
            _charLen = _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, 0, flush: false);
        } while (_charLen == 0);

        if (eofReached)
        {
            // EOF has been reached - perform final flush.
            // We need to reset _bytePos and _byteLen just in case we hadn't
            // finished processing the preamble before we reached EOF.

            Debug.Assert(_charPos == 0 && _charLen == 0, "We shouldn't be looking for EOF unless we have an empty char buffer.");
            _charLen = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, 0, flush: true);
            _bytePos = 0;
            _byteLen = 0;
        }

        return _charLen;
    }

    private async ValueTask<int> ReadBlockAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken)
    {
        int n = 0, i;
        do
        {
            i = await ReadAsyncInternal(buffer.Slice(n), cancellationToken).ConfigureAwait(false);
            n += i;
        } while (i > 0 && n < buffer.Length);

        return n;
    }

    private static unsafe int GetChars(Decoder decoder, ReadOnlySpan<byte> bytes, Span<char> chars, bool flush = false)
    {
        Throw.IfNull(decoder);
        if (decoder is null || bytes.IsEmpty || chars.IsEmpty)
        {
            return 0;
        }

        fixed (byte* pBytes = bytes)
        fixed (char* pChars = chars)
        {
            return decoder.GetChars(pBytes, bytes.Length, pChars, chars.Length, flush);
        }
    }

    private void ThrowIfDisposed()
    {
        if (_disposed)
        {
            ThrowObjectDisposedException();
        }

        void ThrowObjectDisposedException() => throw new ObjectDisposedException(GetType().Name, "reader has been closed.");
    }

    // No data, class doesn't need to be serializable.
    // Note this class is threadsafe.
    internal sealed class NullCancellableStreamReader : CancellableStreamReader
    {
        public override Encoding CurrentEncoding => Encoding.Unicode;

        protected override void Dispose(bool disposing)
        {
            // Do nothing - this is essentially unclosable.
        }

        public override int Peek() => -1;

        public override int Read() => -1;

        public override int Read(char[] buffer, int index, int count) => 0;

        public override Task<int> ReadAsync(char[] buffer, int index, int count) => Task.FromResult(0);

        public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken) =>
            cancellationToken.IsCancellationRequested ? new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)) : default;

        public override int ReadBlock(char[] buffer, int index, int count) => 0;

        public override Task<int> ReadBlockAsync(char[] buffer, int index, int count) => Task.FromResult(0);

        public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken) =>
            cancellationToken.IsCancellationRequested ? new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)) : default;

        public override string? ReadLine() => null;

        public override Task<string?> ReadLineAsync() => Task.FromResult<string?>(null);

        public override ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken) =>
            cancellationToken.IsCancellationRequested ? new ValueTask<string?>(Task.FromCanceled<string?>(cancellationToken)) : default;

        public override string ReadToEnd() => "";

        public override Task<string> ReadToEndAsync() => Task.FromResult("");

        public override Task<string> ReadToEndAsync(CancellationToken cancellationToken) =>
            cancellationToken.IsCancellationRequested ? Task.FromCanceled<string>(cancellationToken) : Task.FromResult("");

        private protected override ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken) =>
            cancellationToken.IsCancellationRequested ? new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)) : default;

        internal override int ReadBuffer() => 0;
    }
}

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization.Metadata;

namespace ModelContextProtocol.Server;

/// <inheritdoc />
internal sealed class McpServer : McpEndpoint, IMcpServer
{
    internal static Implementation DefaultImplementation { get; } = new()
    {
        Name = DefaultAssemblyName.Name ?? nameof(McpServer),
        Version = DefaultAssemblyName.Version?.ToString() ?? "1.0.0",
    };

    private readonly ITransport _sessionTransport;
    private readonly bool _servicesScopePerRequest;
    private readonly List<Action> _disposables = [];

    private readonly string _serverOnlyEndpointName;
    private string? _endpointName;
    private int _started;

    /// <summary>Holds a boxed <see cref="LoggingLevel"/> value for the server.</summary>
    /// <remarks>
    /// Initialized to non-null the first time SetLevel is used. This is stored as a strong box
    /// rather than a nullable to be able to manipulate it atomically.
    /// </remarks>
    private StrongBox<LoggingLevel>? _loggingLevel;

    /// <summary>
    /// Creates a new instance of <see cref="McpServer"/>.
    /// </summary>
    /// <param name="transport">Transport to use for the server representing an already-established session.</param>
    /// <param name="options">Configuration options for this server, including capabilities.
    /// Make sure to accurately reflect exactly what capabilities the server supports and does not support.</param>
    /// <param name="loggerFactory">Logger factory to use for logging</param>
    /// <param name="serviceProvider">Optional service provider to use for dependency injection</param>
    /// <exception cref="McpException">The server was incorrectly configured.</exception>
    public McpServer(ITransport transport, McpServerOptions options, ILoggerFactory? loggerFactory, IServiceProvider? serviceProvider)
        : base(loggerFactory)
    {
        Throw.IfNull(transport);
        Throw.IfNull(options);

        options ??= new();

        _sessionTransport = transport;
        ServerOptions = options;
        Services = serviceProvider;
        _serverOnlyEndpointName = $"Server ({options.ServerInfo?.Name ?? DefaultImplementation.Name} {options.ServerInfo?.Version ?? DefaultImplementation.Version})";
        _servicesScopePerRequest = options.ScopeRequests;

        ClientInfo = options.KnownClientInfo;
        UpdateEndpointNameWithClientInfo();

        // Configure all request handlers based on the supplied options.
        ServerCapabilities = new();
        ConfigureInitialize(options);
        ConfigureTools(options);
        ConfigurePrompts(options);
        ConfigureResources(options);
        ConfigureLogging(options);
        ConfigureCompletion(options);
        ConfigureExperimental(options);
        ConfigurePing();

        // Register any notification handlers that were provided.
        if (options.Capabilities?.NotificationHandlers is { } notificationHandlers)
        {
            NotificationHandlers.RegisterRange(notificationHandlers);
        }

        // Now that everything has been configured, subscribe to any necessary notifications.
        if (transport is not StreamableHttpServerTransport streamableHttpTransport || streamableHttpTransport.Stateless is false)
        {
            Register(ServerOptions.Capabilities?.Tools?.ToolCollection, NotificationMethods.ToolListChangedNotification);
            Register(ServerOptions.Capabilities?.Prompts?.PromptCollection, NotificationMethods.PromptListChangedNotification);
            Register(ServerOptions.Capabilities?.Resources?.ResourceCollection, NotificationMethods.ResourceListChangedNotification);

            void Register<TPrimitive>(McpServerPrimitiveCollection<TPrimitive>? collection, string notificationMethod)
                where TPrimitive : IMcpServerPrimitive
            {
                if (collection is not null)
                {
                    EventHandler changed = (sender, e) => _ = this.SendNotificationAsync(notificationMethod);
                    collection.Changed += changed;
                    _disposables.Add(() => collection.Changed -= changed);
                }
            }
        }

        // And initialize the session.
        InitializeSession(transport);
    }

    /// <inheritdoc/>
    public string? SessionId => _sessionTransport.SessionId;

    /// <inheritdoc/>
    public ServerCapabilities ServerCapabilities { get; } = new();

    /// <inheritdoc />
    public ClientCapabilities? ClientCapabilities { get; set; }

    /// <inheritdoc />
    public Implementation? ClientInfo { get; set; }

    /// <inheritdoc />
    public McpServerOptions ServerOptions { get; }

    /// <inheritdoc />
    public IServiceProvider? Services { get; }

    /// <inheritdoc />
    public override string EndpointName => _endpointName ?? _serverOnlyEndpointName;

    /// <inheritdoc />
    public LoggingLevel? LoggingLevel => _loggingLevel?.Value;

    /// <inheritdoc />
    public async Task RunAsync(CancellationToken cancellationToken = default)
    {
        if (Interlocked.Exchange(ref _started, 1) != 0)
        {
            throw new InvalidOperationException($"{nameof(RunAsync)} must only be called once.");
        }

        try
        {
            StartSession(_sessionTransport, fullSessionCancellationToken: cancellationToken);
            await MessageProcessingTask.ConfigureAwait(false);
        }
        finally
        {
            await DisposeAsync().ConfigureAwait(false);
        }
    }

    public override async ValueTask DisposeUnsynchronizedAsync()
    {
        _disposables.ForEach(d => d());
        await base.DisposeUnsynchronizedAsync().ConfigureAwait(false);
    }

    private void ConfigurePing()
    {
        SetHandler(RequestMethods.Ping,
            async (request, _) => new PingResult(),
            McpJsonUtilities.JsonContext.Default.JsonNode,
            McpJsonUtilities.JsonContext.Default.PingResult);
    }

    private void ConfigureInitialize(McpServerOptions options)
    {
        RequestHandlers.Set(RequestMethods.Initialize,
            async (request, _, _) =>
            {
                ClientCapabilities = request?.Capabilities ?? new();
                ClientInfo = request?.ClientInfo;

                // Use the ClientInfo to update the session EndpointName for logging.
                UpdateEndpointNameWithClientInfo();
                GetSessionOrThrow().EndpointName = EndpointName;

                // Negotiate a protocol version. If the server options provide one, use that.
                // Otherwise, try to use whatever the client requested as long as it's supported.
                // If it's not supported, fall back to the latest supported version.
                string? protocolVersion = options.ProtocolVersion;
                if (protocolVersion is null)
                {
                    protocolVersion = request?.ProtocolVersion is string clientProtocolVersion && McpSession.SupportedProtocolVersions.Contains(clientProtocolVersion) ?
                        clientProtocolVersion :
                        McpSession.LatestProtocolVersion;
                }

                return new InitializeResult
                {
                    ProtocolVersion = protocolVersion,
                    Instructions = options.ServerInstructions,
                    ServerInfo = options.ServerInfo ?? DefaultImplementation,
                    Capabilities = ServerCapabilities ?? new(),
                };
            },
            McpJsonUtilities.JsonContext.Default.InitializeRequestParams,
            McpJsonUtilities.JsonContext.Default.InitializeResult);
    }

    private void ConfigureCompletion(McpServerOptions options)
    {
        if (options.Capabilities?.Completions is not { } completionsCapability)
        {
            return;
        }

        ServerCapabilities.Completions = new()
        {
            CompleteHandler = completionsCapability.CompleteHandler ?? (static async (_, __) => new CompleteResult())
        };

        SetHandler(
            RequestMethods.CompletionComplete,
            ServerCapabilities.Completions.CompleteHandler,
            McpJsonUtilities.JsonContext.Default.CompleteRequestParams,
            McpJsonUtilities.JsonContext.Default.CompleteResult);
    }

    private void ConfigureExperimental(McpServerOptions options)
    {
        ServerCapabilities.Experimental = options.Capabilities?.Experimental;
    }

    private void ConfigureResources(McpServerOptions options)
    {
        if (options.Capabilities?.Resources is not { } resourcesCapability)
        {
            return;
        }

        ServerCapabilities.Resources = new();

        var listResourcesHandler = resourcesCapability.ListResourcesHandler ?? (static async (_, __) => new ListResourcesResult());
        var listResourceTemplatesHandler = resourcesCapability.ListResourceTemplatesHandler ?? (static async (_, __) => new ListResourceTemplatesResult());
        var readResourceHandler = resourcesCapability.ReadResourceHandler ?? (static async (request, _) => throw new McpException($"Unknown resource URI: '{request.Params?.Uri}'", McpErrorCode.InvalidParams));
        var subscribeHandler = resourcesCapability.SubscribeToResourcesHandler ?? (static async (_, __) => new EmptyResult());
        var unsubscribeHandler = resourcesCapability.UnsubscribeFromResourcesHandler ?? (static async (_, __) => new EmptyResult());
        var resources = resourcesCapability.ResourceCollection;
        var listChanged = resourcesCapability.ListChanged;
        var subscribe = resourcesCapability.Subscribe;

        // Handle resources provided via DI.
        if (resources is { IsEmpty: false })
        {
            var originalListResourcesHandler = listResourcesHandler;
            listResourcesHandler = async (request, cancellationToken) =>
            {
                ListResourcesResult result = originalListResourcesHandler is not null ?
                    await originalListResourcesHandler(request, cancellationToken).ConfigureAwait(false) :
                    new();

                if (request.Params?.Cursor is null)
                {
                    foreach (var r in resources)
                    {
                        if (r.ProtocolResource is { } resource)
                        {
                            result.Resources.Add(resource);
                        }
                    }
                }

                return result;
            };

            var originalListResourceTemplatesHandler = listResourceTemplatesHandler;
            listResourceTemplatesHandler = async (request, cancellationToken) =>
            {
                ListResourceTemplatesResult result = originalListResourceTemplatesHandler is not null ?
                    await originalListResourceTemplatesHandler(request, cancellationToken).ConfigureAwait(false) :
                    new();

                if (request.Params?.Cursor is null)
                {
                    foreach (var rt in resources)
                    {
                        if (rt.IsTemplated)
                        {
                            result.ResourceTemplates.Add(rt.ProtocolResourceTemplate);
                        }
                    }
                }

                return result;
            };

            // Synthesize read resource handler, which covers both resources and resource templates.
            var originalReadResourceHandler = readResourceHandler;
            readResourceHandler = async (request, cancellationToken) =>
            {
                if (request.Params?.Uri is string uri)
                {
                    // First try an O(1) lookup by exact match.
                    if (resources.TryGetPrimitive(uri, out var resource))
                    {
                        if (await resource.ReadAsync(request, cancellationToken).ConfigureAwait(false) is { } result)
                        {
                            return result;
                        }
                    }

                    // Fall back to an O(N) lookup, trying to match against each URI template.
                    // The number of templates is controlled by the server developer, and the number is expected to be
                    // not terribly large. If that changes, this can be tweaked to enable a more efficient lookup.
                    foreach (var resourceTemplate in resources)
                    {
                        if (await resourceTemplate.ReadAsync(request, cancellationToken).ConfigureAwait(false) is { } result)
                        {
                            return result;
                        }
                    }
                }

                // Finally fall back to the handler.
                return await originalReadResourceHandler(request, cancellationToken).ConfigureAwait(false);
            };

            listChanged = true;

            // TODO: Implement subscribe/unsubscribe logic for resource and resource template collections.
            // subscribe = true;
        }

        ServerCapabilities.Resources.ListResourcesHandler = listResourcesHandler;
        ServerCapabilities.Resources.ListResourceTemplatesHandler = listResourceTemplatesHandler;
        ServerCapabilities.Resources.ReadResourceHandler = readResourceHandler;
        ServerCapabilities.Resources.ResourceCollection = resources;
        ServerCapabilities.Resources.SubscribeToResourcesHandler = subscribeHandler;
        ServerCapabilities.Resources.UnsubscribeFromResourcesHandler = unsubscribeHandler;
        ServerCapabilities.Resources.ListChanged = listChanged;
        ServerCapabilities.Resources.Subscribe = subscribe;

        SetHandler(
            RequestMethods.ResourcesList,
            listResourcesHandler,
            McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
            McpJsonUtilities.JsonContext.Default.ListResourcesResult);

        SetHandler(
            RequestMethods.ResourcesTemplatesList,
            listResourceTemplatesHandler,
            McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
            McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult);

        SetHandler(
            RequestMethods.ResourcesRead,
            readResourceHandler,
            McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams,
            McpJsonUtilities.JsonContext.Default.ReadResourceResult);

        SetHandler(
            RequestMethods.ResourcesSubscribe,
            subscribeHandler,
            McpJsonUtilities.JsonContext.Default.SubscribeRequestParams,
            McpJsonUtilities.JsonContext.Default.EmptyResult);

        SetHandler(
            RequestMethods.ResourcesUnsubscribe,
            unsubscribeHandler,
            McpJsonUtilities.JsonContext.Default.UnsubscribeRequestParams,
            McpJsonUtilities.JsonContext.Default.EmptyResult);
    }

    private void ConfigurePrompts(McpServerOptions options)
    {
        if (options.Capabilities?.Prompts is not { } promptsCapability)
        {
            return;
        }

        ServerCapabilities.Prompts = new();

        var listPromptsHandler = promptsCapability.ListPromptsHandler ?? (static async (_, __) => new ListPromptsResult());
        var getPromptHandler = promptsCapability.GetPromptHandler ?? (static async (request, _) => throw new McpException($"Unknown prompt: '{request.Params?.Name}'", McpErrorCode.InvalidParams));
        var prompts = promptsCapability.PromptCollection;
        var listChanged = promptsCapability.ListChanged;

        // Handle tools provided via DI by augmenting the handlers to incorporate them.
        if (prompts is { IsEmpty: false })
        {
            var originalListPromptsHandler = listPromptsHandler;
            listPromptsHandler = async (request, cancellationToken) =>
            {
                ListPromptsResult result = originalListPromptsHandler is not null ?
                    await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(false) :
                    new();

                if (request.Params?.Cursor is null)
                {
                    foreach (var p in prompts)
                    {
                        result.Prompts.Add(p.ProtocolPrompt);
                    }
                }

                return result;
            };

            var originalGetPromptHandler = getPromptHandler;
            getPromptHandler = (request, cancellationToken) =>
            {
                if (request.Params is not null &&
                    prompts.TryGetPrimitive(request.Params.Name, out var prompt))
                {
                    return prompt.GetAsync(request, cancellationToken);
                }

                return originalGetPromptHandler(request, cancellationToken);
            };

            listChanged = true;
        }

        ServerCapabilities.Prompts.ListPromptsHandler = listPromptsHandler;
        ServerCapabilities.Prompts.GetPromptHandler = getPromptHandler;
        ServerCapabilities.Prompts.PromptCollection = prompts;
        ServerCapabilities.Prompts.ListChanged = listChanged;

        SetHandler(
            RequestMethods.PromptsList,
            listPromptsHandler,
            McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
            McpJsonUtilities.JsonContext.Default.ListPromptsResult);

        SetHandler(
            RequestMethods.PromptsGet,
            getPromptHandler,
            McpJsonUtilities.JsonContext.Default.GetPromptRequestParams,
            McpJsonUtilities.JsonContext.Default.GetPromptResult);
    }

    private void ConfigureTools(McpServerOptions options)
    {
        if (options.Capabilities?.Tools is not { } toolsCapability)
        {
            return;
        }

        ServerCapabilities.Tools = new();

        var listToolsHandler = toolsCapability.ListToolsHandler ?? (static async (_, __) => new ListToolsResult());
        var callToolHandler = toolsCapability.CallToolHandler ?? (static async (request, _) => throw new McpException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams));
        var tools = toolsCapability.ToolCollection;
        var listChanged = toolsCapability.ListChanged;

        // Handle tools provided via DI by augmenting the handlers to incorporate them.
        if (tools is { IsEmpty: false })
        {
            var originalListToolsHandler = listToolsHandler;
            listToolsHandler = async (request, cancellationToken) =>
            {
                ListToolsResult result = originalListToolsHandler is not null ?
                    await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) :
                    new();

                if (request.Params?.Cursor is null)
                {
                    foreach (var t in tools)
                    {
                        result.Tools.Add(t.ProtocolTool);
                    }
                }

                return result;
            };

            var originalCallToolHandler = callToolHandler;
            callToolHandler = (request, cancellationToken) =>
            {
                if (request.Params is not null &&
                    tools.TryGetPrimitive(request.Params.Name, out var tool))
                {
                    return tool.InvokeAsync(request, cancellationToken);
                }

                return originalCallToolHandler(request, cancellationToken);
            };

            listChanged = true;
        }

        ServerCapabilities.Tools.ListToolsHandler = listToolsHandler;
        ServerCapabilities.Tools.CallToolHandler = callToolHandler;
        ServerCapabilities.Tools.ToolCollection = tools;
        ServerCapabilities.Tools.ListChanged = listChanged;

        SetHandler(
            RequestMethods.ToolsList,
            listToolsHandler,
            McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
            McpJsonUtilities.JsonContext.Default.ListToolsResult);

        SetHandler(
            RequestMethods.ToolsCall,
            callToolHandler,
            McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
            McpJsonUtilities.JsonContext.Default.CallToolResult);
    }

    private void ConfigureLogging(McpServerOptions options)
    {
        // We don't require that the handler be provided, as we always store the provided log level to the server.
        var setLoggingLevelHandler = options.Capabilities?.Logging?.SetLoggingLevelHandler;

        ServerCapabilities.Logging = new();
        ServerCapabilities.Logging.SetLoggingLevelHandler = setLoggingLevelHandler;

        RequestHandlers.Set(
            RequestMethods.LoggingSetLevel,
            (request, destinationTransport, cancellationToken) =>
            {
                // Store the provided level.
                if (request is not null)
                {
                    if (_loggingLevel is null)
                    {
                        Interlocked.CompareExchange(ref _loggingLevel, new(request.Level), null);
                    }

                    _loggingLevel.Value = request.Level;
                }

                // If a handler was provided, now delegate to it.
                if (setLoggingLevelHandler is not null)
                {
                    return InvokeHandlerAsync(setLoggingLevelHandler, request, destinationTransport, cancellationToken);
                }

                // Otherwise, consider it handled.
                return new ValueTask<EmptyResult>(EmptyResult.Instance);
            },
            McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
            McpJsonUtilities.JsonContext.Default.EmptyResult);
    }

    private ValueTask<TResult> InvokeHandlerAsync<TParams, TResult>(
        Func<RequestContext<TParams>, CancellationToken, ValueTask<TResult>> handler,
        TParams? args,
        ITransport? destinationTransport = null,
        CancellationToken cancellationToken = default)
    {
        return _servicesScopePerRequest ?
            InvokeScopedAsync(handler, args, cancellationToken) :
            handler(new(new DestinationBoundMcpServer(this, destinationTransport)) { Params = args }, cancellationToken);

        async ValueTask<TResult> InvokeScopedAsync(
            Func<RequestContext<TParams>, CancellationToken, ValueTask<TResult>> handler,
            TParams? args,
            CancellationToken cancellationToken)
        {
            var scope = Services?.GetService<IServiceScopeFactory>()?.CreateAsyncScope();
            try
            {
                return await handler(
                    new RequestContext<TParams>(new DestinationBoundMcpServer(this, destinationTransport))
                    {
                        Services = scope?.ServiceProvider ?? Services,
                        Params = args
                    },
                    cancellationToken).ConfigureAwait(false);
            }
            finally
            {
                if (scope is not null)
                {
                    await scope.Value.DisposeAsync().ConfigureAwait(false);
                }
            }
        }
    }

    private void SetHandler<TRequest, TResponse>(
        string method,
        Func<RequestContext<TRequest>, CancellationToken, ValueTask<TResponse>> handler,
        JsonTypeInfo<TRequest> requestTypeInfo,
        JsonTypeInfo<TResponse> responseTypeInfo)
    {
        RequestHandlers.Set(method,
            (request, destinationTransport, cancellationToken) =>
                InvokeHandlerAsync(handler, request, destinationTransport, cancellationToken),
            requestTypeInfo, responseTypeInfo);
    }

    private void UpdateEndpointNameWithClientInfo()
    {
        if (ClientInfo is null)
        {
            return;
        }

        _endpointName = $"{_serverOnlyEndpointName}, Client ({ClientInfo.Name} {ClientInfo.Version})";
    }

    /// <summary>Maps a <see cref="LogLevel"/> to a <see cref="LoggingLevel"/>.</summary>
    internal static LoggingLevel ToLoggingLevel(LogLevel level) =>
        level switch
        {
            LogLevel.Trace => Protocol.LoggingLevel.Debug,
            LogLevel.Debug => Protocol.LoggingLevel.Debug,
            LogLevel.Information => Protocol.LoggingLevel.Info,
            LogLevel.Warning => Protocol.LoggingLevel.Warning,
            LogLevel.Error => Protocol.LoggingLevel.Error,
            LogLevel.Critical => Protocol.LoggingLevel.Critical,
            _ => Protocol.LoggingLevel.Emergency,
        };
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// Copied from:
// https://github.com/dotnet/runtime/blob/dd75c45c123055baacd7aa4418f425f412797a29/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/DefaultInterpolatedStringHandler.cs
// and then modified to build on netstandard2.0.

#if !NET
using System.Buffers;
using System.Diagnostics;
using System.Globalization;

namespace System.Runtime.CompilerServices
{
    /// Provides a handler used by the language compiler to process interpolated strings into <see cref="string"/> instances.
    internal ref struct DefaultInterpolatedStringHandler
    {
        // Implementation note:
        // As this type lives in CompilerServices and is only intended to be targeted by the compiler,
        // public APIs eschew argument validation logic in a variety of places, e.g. allowing a null input
        // when one isn't expected to produce a NullReferenceException rather than an ArgumentNullException.

        /// Expected average length of formatted data used for an individual interpolation expression result.
        /// <remarks>
        /// This is inherited from string.Format, and could be changed based on further data.
        /// string.Format actually uses `format.Length + args.Length * 8`, but format.Length
        /// includes the format items themselves, e.g. "{0}", and since it's rare to have double-digit
        /// numbers of items, we bump the 8 up to 11 to account for the three extra characters in "{d}",
        /// since the compiler-provided base length won't include the equivalent character count.
        /// </remarks>
        private const int GuessedLengthPerHole = 11;
        /// Minimum size array to rent from the pool.
        /// <remarks>Same as stack-allocation size used today by string.Format.</remarks>
        private const int MinimumArrayPoolLength = 256;

        /// Optional provider to pass to IFormattable.ToString or ISpanFormattable.TryFormat calls.
        private readonly IFormatProvider? _provider;
        /// Array rented from the array pool and used to back <see cref="_chars"/>.
        private char[]? _arrayToReturnToPool;
        /// The span to write into.
        private Span<char> _chars;
        /// Position at which to write the next character.
        private int _pos;
        /// Whether <see cref="_provider"/> provides an ICustomFormatter.
        /// <remarks>
        /// Custom formatters are very rare.  We want to support them, but it's ok if we make them more expensive
        /// in order to make them as pay-for-play as possible.  So, we avoid adding another reference type field
        /// to reduce the size of the handler and to reduce required zero'ing, by only storing whether the provider
        /// provides a formatter, rather than actually storing the formatter.  This in turn means, if there is a
        /// formatter, we pay for the extra interface call on each AppendFormatted that needs it.
        /// </remarks>
        private readonly bool _hasCustomFormatter;

        /// Creates a handler used to translate an interpolated string into a <see cref="string"/>.
        /// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
        /// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
        /// <remarks>This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.</remarks>
        public DefaultInterpolatedStringHandler(int literalLength, int formattedCount)
        {
            _provider = null;
            _chars = _arrayToReturnToPool = ArrayPool<char>.Shared.Rent(GetDefaultLength(literalLength, formattedCount));
            _pos = 0;
            _hasCustomFormatter = false;
        }

        /// Creates a handler used to translate an interpolated string into a <see cref="string"/>.
        /// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
        /// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information.</param>
        /// <remarks>This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.</remarks>
        public DefaultInterpolatedStringHandler(int literalLength, int formattedCount, IFormatProvider? provider)
        {
            _provider = provider;
            _chars = _arrayToReturnToPool = ArrayPool<char>.Shared.Rent(GetDefaultLength(literalLength, formattedCount));
            _pos = 0;
            _hasCustomFormatter = provider is not null && HasCustomFormatter(provider);
        }

        /// Creates a handler used to translate an interpolated string into a <see cref="string"/>.
        /// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
        /// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information.</param>
        /// <param name="initialBuffer">A buffer temporarily transferred to the handler for use as part of its formatting.  Contents may be overwritten.</param>
        /// <remarks>This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.</remarks>
        public DefaultInterpolatedStringHandler(int literalLength, int formattedCount, IFormatProvider? provider, Span<char> initialBuffer)
        {
            _provider = provider;
            _chars = initialBuffer;
            _arrayToReturnToPool = null;
            _pos = 0;
            _hasCustomFormatter = provider is not null && HasCustomFormatter(provider);
        }

        /// Derives a default length with which to seed the handler.
        /// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
        /// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)] // becomes a constant when inputs are constant
        internal static int GetDefaultLength(int literalLength, int formattedCount) =>
            Math.Max(MinimumArrayPoolLength, literalLength + (formattedCount * GuessedLengthPerHole));

        /// Gets the built <see cref="string"/>.
        /// <returns>The built string.</returns>
        public override string ToString() => Text.ToString();

        /// Gets the built <see cref="string"/> and clears the handler.
        /// <returns>The built string.</returns>
        /// <remarks>
        /// This releases any resources used by the handler. The method should be invoked only
        /// once and as the last thing performed on the handler. Subsequent use is erroneous, ill-defined,
        /// and may destabilize the process, as may using any other copies of the handler after
        /// <see cref="ToStringAndClear" /> is called on any one of them.
        /// </remarks>
        public string ToStringAndClear()
        {
            string result = Text.ToString();
            Clear();
            return result;
        }

        /// Clears the handler.
        /// <remarks>
        /// This releases any resources used by the handler. The method should be invoked only
        /// once and as the last thing performed on the handler. Subsequent use is erroneous, ill-defined,
        /// and may destabilize the process, as may using any other copies of the handler after <see cref="Clear"/>
        /// is called on any one of them.
        /// </remarks>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Clear()
        {
            char[]? toReturn = _arrayToReturnToPool;

            // Defensive clear
            _arrayToReturnToPool = null;
            _chars = default;
            _pos = 0;

            if (toReturn is not null)
            {
                ArrayPool<char>.Shared.Return(toReturn);
            }
        }

        /// Gets a span of the characters appended to the handler.
        public ReadOnlySpan<char> Text => _chars.Slice(0, _pos);

        /// Writes the specified string to the handler.
        /// <param name="value">The string to write.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void AppendLiteral(string value)
        {
            if (value.AsSpan().TryCopyTo(_chars.Slice(_pos)))
            {
                _pos += value.Length;
            }
            else
            {
                GrowThenCopyString(value);
            }
        }

        #region AppendFormatted
        // Design note:
        // The compiler requires a AppendFormatted overload for anything that might be within an interpolation expression;
        // if it can't find an appropriate overload, for handlers in general it'll simply fail to compile.
        // (For target-typing to string where it uses DefaultInterpolatedStringHandler implicitly, it'll instead fall back to
        // its other mechanisms, e.g. using string.Format.  This fallback has the benefit that if we miss a case,
        // interpolated strings will still work, but it has the downside that a developer generally won't know
        // if the fallback is happening and they're paying more.)
        //
        // At a minimum, then, we would need an overload that accepts:
        //     (object value, int alignment = 0, string? format = null)
        // Such an overload would provide the same expressiveness as string.Format.  However, this has several
        // shortcomings:
        // - Every value type in an interpolation expression would be boxed.
        // - ReadOnlySpan<char> could not be used in interpolation expressions.
        // - Every AppendFormatted call would have three arguments at the call site, bloating the IL further.
        // - Every invocation would be more expensive, due to lack of specialization, every call needing to account
        //   for alignment and format, etc.
        //
        // To address that, we could just have overloads for T and ReadOnlySpan<char>:
        //     (T)
        //     (T, int alignment)
        //     (T, string? format)
        //     (T, int alignment, string? format)
        //     (ReadOnlySpan<char>)
        //     (ReadOnlySpan<char>, int alignment)
        //     (ReadOnlySpan<char>, string? format)
        //     (ReadOnlySpan<char>, int alignment, string? format)
        // but this also has shortcomings:
        // - Some expressions that would have worked with an object overload will now force a fallback to string.Format
        //   (or fail to compile if the handler is used in places where the fallback isn't provided), because the compiler
        //   can't always target type to T, e.g. `b switch { true => 1, false => null }` where `b` is a bool can successfully
        //   be passed as an argument of type `object` but not of type `T`.
        // - Reference types get no benefit from going through the generic code paths, and actually incur some overheads
        //   from doing so.
        // - Nullable value types also pay a heavy price, in particular around interface checks that would generally evaporate
        //   at compile time for value types but don't (currently) if the Nullable<T> goes through the same code paths
        //   (see https://github.com/dotnet/runtime/issues/50915).
        //
        // We could try to take a more elaborate approach for DefaultInterpolatedStringHandler, since it is the most common handler
        // and we want to minimize overheads both at runtime and in IL size, e.g. have a complete set of overloads for each of:
        //     (T, ...) where T : struct
        //     (T?, ...) where T : struct
        //     (object, ...)
        //     (ReadOnlySpan<char>, ...)
        //     (string, ...)
        // but this also has shortcomings, most importantly:
        // - If you have an unconstrained T that happens to be a value type, it'll now end up getting boxed to use the object overload.
        //   This also necessitates the T? overload, since nullable value types don't meet a T : struct constraint, so without those
        //   they'd all map to the object overloads as well.
        // - Any reference type with an implicit cast to ROS<char> will fail to compile due to ambiguities between the overloads. string
        //   is one such type, hence needing dedicated overloads for it that can be bound to more tightly.
        //
        // A middle ground we've settled on, which is likely to be the right approach for most other handlers as well, would be the set:
        //     (T, ...) with no constraint
        //     (ReadOnlySpan<char>) and (ReadOnlySpan<char>, int)
        //     (object, int alignment = 0, string? format = null)
        //     (string) and (string, int)
        // This would address most of the concerns, at the expense of:
        // - Most reference types going through the generic code paths and so being a bit more expensive.
        // - Nullable types being more expensive until https://github.com/dotnet/runtime/issues/50915 is addressed.
        //   We could choose to add a T? where T : struct set of overloads if necessary.
        // Strings don't require their own overloads here, but as they're expected to be very common and as we can
        // optimize them in several ways (can copy the contents directly, don't need to do any interface checks, don't
        // need to pay the shared generic overheads, etc.) we can add overloads specifically to optimize for them.
        //
        // Hole values are formatted according to the following policy:
        // 1. If an IFormatProvider was supplied and it provides an ICustomFormatter, use ICustomFormatter.Format (even if the value is null).
        // 2. If the type implements ISpanFormattable, use ISpanFormattable.TryFormat.
        // 3. If the type implements IFormattable, use IFormattable.ToString.
        // 4. Otherwise, use object.ToString.
        // This matches the behavior of string.Format, StringBuilder.AppendFormat, etc.  The only overloads for which this doesn't
        // apply is ReadOnlySpan<char>, which isn't supported by either string.Format nor StringBuilder.AppendFormat, but more
        // importantly which can't be boxed to be passed to ICustomFormatter.Format.

        #region AppendFormatted T
        /// Writes the specified value to the handler.
        /// <param name="value">The value to write.</param>
        /// <typeparam name="T">The type of the value to write.</typeparam>
        public void AppendFormatted<T>(T value)
        {
            // This method could delegate to AppendFormatted with a null format, but explicitly passing
            // default as the format to TryFormat helps to improve code quality in some cases when TryFormat is inlined,
            // e.g. for Int32 it enables the JIT to eliminate code in the inlined method based on a length check on the format.

            // If there's a custom formatter, always use it.
            if (_hasCustomFormatter)
            {
                AppendCustomFormatter(value, format: null);
                return;
            }

            string? s;
            if (value is IFormattable)
            {
                s = ((IFormattable)value).ToString(format: null, _provider); // constrained call avoiding boxing for value types
            }
            else
            {
                s = value?.ToString();
            }

            if (s is not null)
            {
                AppendLiteral(s);
            }
        }

        /// Writes the specified value to the handler.
        /// <param name="value">The value to write.</param>
        /// <param name="format">The format string.</param>
        /// <typeparam name="T">The type of the value to write.</typeparam>
        public void AppendFormatted<T>(T value, string? format)
        {
            // If there's a custom formatter, always use it.
            if (_hasCustomFormatter)
            {
                AppendCustomFormatter(value, format);
                return;
            }

            // Check first for IFormattable, even though we'll prefer to use ISpanFormattable, as the latter
            // requires the former.  For value types, it won't matter as the type checks devolve into
            // JIT-time constants.  For reference types, they're more likely to implement IFormattable
            // than they are to implement ISpanFormattable: if they don't implement either, we save an
            // interface check over first checking for ISpanFormattable and then for IFormattable, and
            // if it only implements IFormattable, we come out even: only if it implements both do we
            // end up paying for an extra interface check.
            string? s;
            if (value is IFormattable)
            {
                s = ((IFormattable)value).ToString(format, _provider); // constrained call avoiding boxing for value types
            }
            else
            {
                s = value?.ToString();
            }

            if (s is not null)
            {
                AppendLiteral(s);
            }
        }

        /// Writes the specified value to the handler.
        /// <param name="value">The value to write.</param>
        /// <param name="alignment">Minimum number of characters that should be written for this value.  If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
        /// <typeparam name="T">The type of the value to write.</typeparam>
        public void AppendFormatted<T>(T value, int alignment)
        {
            int startingPos = _pos;
            AppendFormatted(value);
            if (alignment != 0)
            {
                AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
            }
        }

        /// Writes the specified value to the handler.
        /// <param name="value">The value to write.</param>
        /// <param name="format">The format string.</param>
        /// <param name="alignment">Minimum number of characters that should be written for this value.  If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
        /// <typeparam name="T">The type of the value to write.</typeparam>
        public void AppendFormatted<T>(T value, int alignment, string? format)
        {
            int startingPos = _pos;
            AppendFormatted(value, format);
            if (alignment != 0)
            {
                AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
            }
        }
        #endregion

        #region AppendFormatted ReadOnlySpan<char>
        /// Writes the specified character span to the handler.
        /// <param name="value">The span to write.</param>
        public void AppendFormatted(scoped ReadOnlySpan<char> value)
        {
            // Fast path for when the value fits in the current buffer
            if (value.TryCopyTo(_chars.Slice(_pos)))
            {
                _pos += value.Length;
            }
            else
            {
                GrowThenCopySpan(value);
            }
        }

        /// Writes the specified string of chars to the handler.
        /// <param name="value">The span to write.</param>
        /// <param name="alignment">Minimum number of characters that should be written for this value.  If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
        /// <param name="format">The format string.</param>
        public void AppendFormatted(scoped ReadOnlySpan<char> value, int alignment = 0, string? format = null)
        {
            bool leftAlign = false;
            if (alignment < 0)
            {
                leftAlign = true;
                alignment = -alignment;
            }

            int paddingRequired = alignment - value.Length;
            if (paddingRequired <= 0)
            {
                // The value is as large or larger than the required amount of padding,
                // so just write the value.
                AppendFormatted(value);
                return;
            }

            // Write the value along with the appropriate padding.
            EnsureCapacityForAdditionalChars(value.Length + paddingRequired);
            if (leftAlign)
            {
                value.CopyTo(_chars.Slice(_pos));
                _pos += value.Length;
                _chars.Slice(_pos, paddingRequired).Fill(' ');
                _pos += paddingRequired;
            }
            else
            {
                _chars.Slice(_pos, paddingRequired).Fill(' ');
                _pos += paddingRequired;
                value.CopyTo(_chars.Slice(_pos));
                _pos += value.Length;
            }
        }
        #endregion

        #region AppendFormatted string
        /// Writes the specified value to the handler.
        /// <param name="value">The value to write.</param>
        public void AppendFormatted(string? value)
        {
            // Fast-path for no custom formatter and a non-null string that fits in the current destination buffer.
            if (!_hasCustomFormatter &&
                value is not null &&
                value.AsSpan().TryCopyTo(_chars.Slice(_pos)))
            {
                _pos += value.Length;
            }
            else
            {
                AppendFormattedSlow(value);
            }
        }

        /// Writes the specified value to the handler.
        /// <param name="value">The value to write.</param>
        /// <remarks>
        /// Slow path to handle a custom formatter, potentially null value,
        /// or a string that doesn't fit in the current buffer.
        /// </remarks>
        [MethodImpl(MethodImplOptions.NoInlining)]
        private void AppendFormattedSlow(string? value)
        {
            if (_hasCustomFormatter)
            {
                AppendCustomFormatter(value, format: null);
            }
            else if (value is not null)
            {
                EnsureCapacityForAdditionalChars(value.Length);
                value.AsSpan().CopyTo(_chars.Slice(_pos));
                _pos += value.Length;
            }
        }

        /// Writes the specified value to the handler.
        /// <param name="value">The value to write.</param>
        /// <param name="alignment">Minimum number of characters that should be written for this value.  If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
        /// <param name="format">The format string.</param>
        public void AppendFormatted(string? value, int alignment = 0, string? format = null) =>
            // Format is meaningless for strings and doesn't make sense for someone to specify.  We have the overload
            // simply to disambiguate between ROS<char> and object, just in case someone does specify a format, as
            // string is implicitly convertible to both. Just delegate to the T-based implementation.
            AppendFormatted<string?>(value, alignment, format);
        #endregion

        #region AppendFormatted object
        /// Writes the specified value to the handler.
        /// <param name="value">The value to write.</param>
        /// <param name="alignment">Minimum number of characters that should be written for this value.  If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
        /// <param name="format">The format string.</param>
        public void AppendFormatted(object? value, int alignment = 0, string? format = null) =>
            // This overload is expected to be used rarely, only if either a) something strongly typed as object is
            // formatted with both an alignment and a format, or b) the compiler is unable to target type to T. It
            // exists purely to help make cases from (b) compile. Just delegate to the T-based implementation.
            AppendFormatted<object?>(value, alignment, format);
        #endregion
        #endregion

        /// Gets whether the provider provides a custom formatter.
        [MethodImpl(MethodImplOptions.AggressiveInlining)] // only used in a few hot path call sites
        internal static bool HasCustomFormatter(IFormatProvider provider)
        {
            Debug.Assert(provider is not null);
            Debug.Assert(provider is not CultureInfo || provider.GetFormat(typeof(ICustomFormatter)) is null, "Expected CultureInfo to not provide a custom formatter");
            return
                provider!.GetType() != typeof(CultureInfo) && // optimization to avoid GetFormat in the majority case
                provider.GetFormat(typeof(ICustomFormatter)) != null;
        }

        /// Formats the value using the custom formatter from the provider.
        /// <param name="value">The value to write.</param>
        /// <param name="format">The format string.</param>
        /// <typeparam name="T">The type of the value to write.</typeparam>
        [MethodImpl(MethodImplOptions.NoInlining)]
        private void AppendCustomFormatter<T>(T value, string? format)
        {
            // This case is very rare, but we need to handle it prior to the other checks in case
            // a provider was used that supplied an ICustomFormatter which wanted to intercept the particular value.
            // We do the cast here rather than in the ctor, even though this could be executed multiple times per
            // formatting, to make the cast pay for play.
            Debug.Assert(_hasCustomFormatter);
            Debug.Assert(_provider != null);

            ICustomFormatter? formatter = (ICustomFormatter?)_provider!.GetFormat(typeof(ICustomFormatter));
            Debug.Assert(formatter != null, "An incorrectly written provider said it implemented ICustomFormatter, and then didn't");

            if (formatter is not null && formatter.Format(format, value, _provider) is string customFormatted)
            {
                AppendLiteral(customFormatted);
            }
        }

        /// Handles adding any padding required for aligning a formatted value in an interpolation expression.
        /// <param name="startingPos">The position at which the written value started.</param>
        /// <param name="alignment">Non-zero minimum number of characters that should be written for this value.  If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
        private void AppendOrInsertAlignmentIfNeeded(int startingPos, int alignment)
        {
            Debug.Assert(startingPos >= 0 && startingPos <= _pos);
            Debug.Assert(alignment != 0);

            int charsWritten = _pos - startingPos;

            bool leftAlign = false;
            if (alignment < 0)
            {
                leftAlign = true;
                alignment = -alignment;
            }

            int paddingNeeded = alignment - charsWritten;
            if (paddingNeeded > 0)
            {
                EnsureCapacityForAdditionalChars(paddingNeeded);

                if (leftAlign)
                {
                    _chars.Slice(_pos, paddingNeeded).Fill(' ');
                }
                else
                {
                    _chars.Slice(startingPos, charsWritten).CopyTo(_chars.Slice(startingPos + paddingNeeded));
                    _chars.Slice(startingPos, paddingNeeded).Fill(' ');
                }

                _pos += paddingNeeded;
            }
        }

        /// Ensures <see cref="_chars"/> has the capacity to store <paramref name="additionalChars"/> beyond <see cref="_pos"/>.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void EnsureCapacityForAdditionalChars(int additionalChars)
        {
            if (_chars.Length - _pos < additionalChars)
            {
                Grow(additionalChars);
            }
        }

        /// Fallback for fast path in <see cref="AppendLiteral(string)"/> when there's not enough space in the destination.
        /// <param name="value">The string to write.</param>
        [MethodImpl(MethodImplOptions.NoInlining)]
        private void GrowThenCopyString(string value)
        {
            Grow(value.Length);
            value.AsSpan().CopyTo(_chars.Slice(_pos));
            _pos += value.Length;
        }

        /// Fallback for <see cref="AppendFormatted(ReadOnlySpan{char})"/> for when not enough space exists in the current buffer.
        /// <param name="value">The span to write.</param>
        [MethodImpl(MethodImplOptions.NoInlining)]
        private void GrowThenCopySpan(scoped ReadOnlySpan<char> value)
        {
            Grow(value.Length);
            value.CopyTo(_chars.Slice(_pos));
            _pos += value.Length;
        }

        /// Grows <see cref="_chars"/> to have the capacity to store at least <paramref name="additionalChars"/> beyond <see cref="_pos"/>.
        [MethodImpl(MethodImplOptions.NoInlining)] // keep consumers as streamlined as possible
        private void Grow(int additionalChars)
        {
            // This method is called when the remaining space (_chars.Length - _pos) is
            // insufficient to store a specific number of additional characters.  Thus, we
            // need to grow to at least that new total. GrowCore will handle growing by more
            // than that if possible.
            Debug.Assert(additionalChars > _chars.Length - _pos);
            GrowCore((uint)_pos + (uint)additionalChars);
        }

        /// Grows the size of <see cref="_chars"/>.
        [MethodImpl(MethodImplOptions.NoInlining)] // keep consumers as streamlined as possible
        private void Grow()
        {
            // This method is called when the remaining space in _chars isn't sufficient to continue
            // the operation.  Thus, we need at least one character beyond _chars.Length.  GrowCore
            // will handle growing by more than that if possible.
            GrowCore((uint)_chars.Length + 1);
        }

        /// Grow the size of <see cref="_chars"/> to at least the specified <paramref name="requiredMinCapacity"/>.
        [MethodImpl(MethodImplOptions.AggressiveInlining)] // but reuse this grow logic directly in both of the above grow routines
        private void GrowCore(uint requiredMinCapacity)
        {
            // We want the max of how much space we actually required and doubling our capacity (without going beyond the max allowed length). We
            // also want to avoid asking for small arrays, to reduce the number of times we need to grow, and since we're working with unsigned
            // ints that could technically overflow if someone tried to, for example, append a huge string to a huge string, we also clamp to int.MaxValue.
            // Even if the array creation fails in such a case, we may later fail in ToStringAndClear.

            const int StringMaxLength = 0x3FFFFFDF;
            uint newCapacity = Math.Max(requiredMinCapacity, Math.Min((uint)_chars.Length * 2, StringMaxLength));
            int arraySize = (int)Clamp(newCapacity, MinimumArrayPoolLength, int.MaxValue);

            char[] newArray = ArrayPool<char>.Shared.Rent(arraySize);
            _chars.Slice(0, _pos).CopyTo(newArray);

            char[]? toReturn = _arrayToReturnToPool;
            _chars = _arrayToReturnToPool = newArray;

            if (toReturn is not null)
            {
                ArrayPool<char>.Shared.Return(toReturn);
            }
        }

        private static uint Clamp(uint value, uint min, uint max)
        {
            Debug.Assert(min <= max);

            if (value < min)
            {
                return min;
            }
            else if (value > max)
            {
                return max;
            }

            return value;
        }
    }
}
#endif

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace ModelContextProtocol;

/// <summary>
/// Base class for an MCP JSON-RPC endpoint. This covers both MCP clients and servers.
/// It is not supported, nor necessary, to implement both client and server functionality in the same class.
/// If an application needs to act as both a client and a server, it should use separate objects for each.
/// This is especially true as a client represents a connection to one and only one server, and vice versa.
/// Any multi-client or multi-server functionality should be implemented at a higher level of abstraction.
/// </summary>
internal abstract partial class McpEndpoint : IAsyncDisposable
{
    /// <summary>Cached naming information used for name/version when none is specified.</summary>
    internal static AssemblyName DefaultAssemblyName { get; } = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetName();

    private McpSession? _session;
    private CancellationTokenSource? _sessionCts;

    private readonly SemaphoreSlim _disposeLock = new(1, 1);
    private bool _disposed;

    protected readonly ILogger _logger;

    /// <summary>
    /// Initializes a new instance of the <see cref="McpEndpoint"/> class.
    /// </summary>
    /// <param name="loggerFactory">The logger factory.</param>
    protected McpEndpoint(ILoggerFactory? loggerFactory = null)
    {
        _logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance;
    }

    protected RequestHandlers RequestHandlers { get; } = [];

    protected NotificationHandlers NotificationHandlers { get; } = new();

    public Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken = default)
        => GetSessionOrThrow().SendRequestAsync(request, cancellationToken);

    public Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
        => GetSessionOrThrow().SendMessageAsync(message, cancellationToken);

    public IAsyncDisposable RegisterNotificationHandler(string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler) =>
        GetSessionOrThrow().RegisterNotificationHandler(method, handler);

    /// <summary>
    /// Gets the name of the endpoint for logging and debug purposes.
    /// </summary>
    public abstract string EndpointName { get; }

    /// <summary>
    /// Task that processes incoming messages from the transport.
    /// </summary>
    protected Task? MessageProcessingTask { get; private set; }

    protected void InitializeSession(ITransport sessionTransport)
    {
        _session = new McpSession(this is IMcpServer, sessionTransport, EndpointName, RequestHandlers, NotificationHandlers, _logger);
    }

    [MemberNotNull(nameof(MessageProcessingTask))]
    protected void StartSession(ITransport sessionTransport, CancellationToken fullSessionCancellationToken)
    {
        _sessionCts = CancellationTokenSource.CreateLinkedTokenSource(fullSessionCancellationToken);
        MessageProcessingTask = GetSessionOrThrow().ProcessMessagesAsync(_sessionCts.Token);
    }

    protected void CancelSession() => _sessionCts?.Cancel();

    public async ValueTask DisposeAsync()
    {
        using var _ = await _disposeLock.LockAsync().ConfigureAwait(false);

        if (_disposed)
        {
            return;
        }
        _disposed = true;

        await DisposeUnsynchronizedAsync().ConfigureAwait(false);
    }

    /// <summary>
    /// Cleans up the endpoint and releases resources.
    /// </summary>
    /// <returns></returns>
    public virtual async ValueTask DisposeUnsynchronizedAsync()
    {
        LogEndpointShuttingDown(EndpointName);

        try
        {
            if (_sessionCts is not null)
            {
                await _sessionCts.CancelAsync().ConfigureAwait(false);
            }

            if (MessageProcessingTask is not null)
            {
                try
                {
                    await MessageProcessingTask.ConfigureAwait(false);
                }
                catch (OperationCanceledException)
                {
                    // Ignore cancellation
                }
            }
        }
        finally
        {
            _session?.Dispose();
            _sessionCts?.Dispose();
        }

        LogEndpointShutDown(EndpointName);
    }

    protected McpSession GetSessionOrThrow()
    {
#if NET
        ObjectDisposedException.ThrowIf(_disposed, this);
#else
        if (_disposed)
        {
            throw new ObjectDisposedException(GetType().Name);
        }
#endif

        return _session ?? throw new InvalidOperationException($"This should be unreachable from public API! Call {nameof(InitializeSession)} before sending messages.");
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} shutting down.")]
    private partial void LogEndpointShuttingDown(string endpointName);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} shut down.")]
    private partial void LogEndpointShutDown(string endpointName);
}

using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
using System.Text.Json;

namespace ModelContextProtocol.Client;

/// <inheritdoc/>
internal sealed partial class McpClient : McpEndpoint, IMcpClient
{
    private static Implementation DefaultImplementation { get; } = new()
    {
        Name = DefaultAssemblyName.Name ?? nameof(McpClient),
        Version = DefaultAssemblyName.Version?.ToString() ?? "1.0.0",
    };

    private readonly IClientTransport _clientTransport;
    private readonly McpClientOptions _options;

    private ITransport? _sessionTransport;
    private CancellationTokenSource? _connectCts;

    private ServerCapabilities? _serverCapabilities;
    private Implementation? _serverInfo;
    private string? _serverInstructions;

    /// <summary>
    /// Initializes a new instance of the <see cref="McpClient"/> class.
    /// </summary>
    /// <param name="clientTransport">The transport to use for communication with the server.</param>
    /// <param name="options">Options for the client, defining protocol version and capabilities.</param>
    /// <param name="loggerFactory">The logger factory.</param>
    public McpClient(IClientTransport clientTransport, McpClientOptions? options, ILoggerFactory? loggerFactory)
        : base(loggerFactory)
    {
        options ??= new();

        _clientTransport = clientTransport;
        _options = options;

        EndpointName = clientTransport.Name;

        if (options.Capabilities is { } capabilities)
        {
            if (capabilities.NotificationHandlers is { } notificationHandlers)
            {
                NotificationHandlers.RegisterRange(notificationHandlers);
            }

            if (capabilities.Sampling is { } samplingCapability)
            {
                if (samplingCapability.SamplingHandler is not { } samplingHandler)
                {
                    throw new InvalidOperationException("Sampling capability was set but it did not provide a handler.");
                }

                RequestHandlers.Set(
                    RequestMethods.SamplingCreateMessage,
                    (request, _, cancellationToken) => samplingHandler(
                        request,
                        request?.ProgressToken is { } token ? new TokenProgress(this, token) : NullProgress.Instance,
                        cancellationToken),
                    McpJsonUtilities.JsonContext.Default.CreateMessageRequestParams,
                    McpJsonUtilities.JsonContext.Default.CreateMessageResult);
            }

            if (capabilities.Roots is { } rootsCapability)
            {
                if (rootsCapability.RootsHandler is not { } rootsHandler)
                {
                    throw new InvalidOperationException("Roots capability was set but it did not provide a handler.");
                }

                RequestHandlers.Set(
                    RequestMethods.RootsList,
                    (request, _, cancellationToken) => rootsHandler(request, cancellationToken),
                    McpJsonUtilities.JsonContext.Default.ListRootsRequestParams,
                    McpJsonUtilities.JsonContext.Default.ListRootsResult);
            }

            if (capabilities.Elicitation is { } elicitationCapability)
            {
                if (elicitationCapability.ElicitationHandler is not { } elicitationHandler)
                {
                    throw new InvalidOperationException("Elicitation capability was set but it did not provide a handler.");
                }

                RequestHandlers.Set(
                    RequestMethods.ElicitationCreate,
                    (request, _, cancellationToken) => elicitationHandler(request, cancellationToken),
                    McpJsonUtilities.JsonContext.Default.ElicitRequestParams,
                    McpJsonUtilities.JsonContext.Default.ElicitResult);
            }
        }
    }

    /// <inheritdoc/>
    public string? SessionId
    {
        get
        {
            if (_sessionTransport is null)
            {
                throw new InvalidOperationException("Must have already initialized a session when invoking this property.");
            }

            return _sessionTransport.SessionId;
        }
    }

    /// <inheritdoc/>
    public ServerCapabilities ServerCapabilities => _serverCapabilities ?? throw new InvalidOperationException("The client is not connected.");

    /// <inheritdoc/>
    public Implementation ServerInfo => _serverInfo ?? throw new InvalidOperationException("The client is not connected.");

    /// <inheritdoc/>
    public string? ServerInstructions => _serverInstructions;

    /// <inheritdoc/>
    public override string EndpointName { get; }

    /// <summary>
    /// Asynchronously connects to an MCP server, establishes the transport connection, and completes the initialization handshake.
    /// </summary>
    public async Task ConnectAsync(CancellationToken cancellationToken = default)
    {
        _connectCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        cancellationToken = _connectCts.Token;

        try
        {
            // Connect transport
            _sessionTransport = await _clientTransport.ConnectAsync(cancellationToken).ConfigureAwait(false);
            InitializeSession(_sessionTransport);
            // We don't want the ConnectAsync token to cancel the session after we've successfully connected.
            // The base class handles cleaning up the session in DisposeAsync without our help.
            StartSession(_sessionTransport, fullSessionCancellationToken: CancellationToken.None);

            // Perform initialization sequence
            using var initializationCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            initializationCts.CancelAfter(_options.InitializationTimeout);

            try
            {
                // Send initialize request
                string requestProtocol = _options.ProtocolVersion ?? McpSession.LatestProtocolVersion;
                var initializeResponse = await this.SendRequestAsync(
                    RequestMethods.Initialize,
                    new InitializeRequestParams
                    {
                        ProtocolVersion = requestProtocol,
                        Capabilities = _options.Capabilities ?? new ClientCapabilities(),
                        ClientInfo = _options.ClientInfo ?? DefaultImplementation,
                    },
                    McpJsonUtilities.JsonContext.Default.InitializeRequestParams,
                    McpJsonUtilities.JsonContext.Default.InitializeResult,
                    cancellationToken: initializationCts.Token).ConfigureAwait(false);

                // Store server information
                if (_logger.IsEnabled(LogLevel.Information))
                {
                    LogServerCapabilitiesReceived(EndpointName,
                        capabilities: JsonSerializer.Serialize(initializeResponse.Capabilities, McpJsonUtilities.JsonContext.Default.ServerCapabilities),
                        serverInfo: JsonSerializer.Serialize(initializeResponse.ServerInfo, McpJsonUtilities.JsonContext.Default.Implementation));
                }

                _serverCapabilities = initializeResponse.Capabilities;
                _serverInfo = initializeResponse.ServerInfo;
                _serverInstructions = initializeResponse.Instructions;

                // Validate protocol version
                bool isResponseProtocolValid =
                    _options.ProtocolVersion is { } optionsProtocol ? optionsProtocol == initializeResponse.ProtocolVersion :
                    McpSession.SupportedProtocolVersions.Contains(initializeResponse.ProtocolVersion);
                if (!isResponseProtocolValid)
                {
                    LogServerProtocolVersionMismatch(EndpointName, requestProtocol, initializeResponse.ProtocolVersion);
                    throw new McpException($"Server protocol version mismatch. Expected {requestProtocol}, got {initializeResponse.ProtocolVersion}");
                }

                // Send initialized notification
                await this.SendNotificationAsync(
                    NotificationMethods.InitializedNotification,
                    new InitializedNotificationParams(),
                    McpJsonUtilities.JsonContext.Default.InitializedNotificationParams,
                    cancellationToken: initializationCts.Token).ConfigureAwait(false);

            }
            catch (OperationCanceledException oce) when (initializationCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
            {
                LogClientInitializationTimeout(EndpointName);
                throw new TimeoutException("Initialization timed out", oce);
            }
        }
        catch (Exception e)
        {
            LogClientInitializationError(EndpointName, e);
            await DisposeAsync().ConfigureAwait(false);
            throw;
        }
    }

    /// <inheritdoc/>
    public override async ValueTask DisposeUnsynchronizedAsync()
    {
        try
        {
            if (_connectCts is not null)
            {
                await _connectCts.CancelAsync().ConfigureAwait(false);
                _connectCts.Dispose();
            }

            await base.DisposeUnsynchronizedAsync().ConfigureAwait(false);
        }
        finally
        {
            if (_sessionTransport is not null)
            {
                await _sessionTransport.DisposeAsync().ConfigureAwait(false);
            }
        }
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} client received server '{ServerInfo}' capabilities: '{Capabilities}'.")]
    private partial void LogServerCapabilitiesReceived(string endpointName, string capabilities, string serverInfo);

    [LoggerMessage(Level = LogLevel.Error, Message = "{EndpointName} client initialization error.")]
    private partial void LogClientInitializationError(string endpointName, Exception exception);

    [LoggerMessage(Level = LogLevel.Error, Message = "{EndpointName} client initialization timed out.")]
    private partial void LogClientInitializationTimeout(string endpointName);

    [LoggerMessage(Level = LogLevel.Error, Message = "{EndpointName} client protocol version mismatch with server. Expected '{Expected}', received '{Received}'.")]
    private partial void LogServerProtocolVersionMismatch(string endpointName, string expected, string received);
}

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Web;

namespace ModelContextProtocol.Authentication;

/// <summary>
/// A generic implementation of an OAuth authorization provider for MCP. This does not do any advanced token
/// protection or caching - it acquires a token and server metadata and holds it in memory.
/// This is suitable for demonstration and development purposes.
/// </summary>
internal sealed partial class ClientOAuthProvider
{
    /// <summary>
    /// The Bearer authentication scheme.
    /// </summary>
    private const string BearerScheme = "Bearer";

    private readonly Uri _serverUrl;
    private readonly Uri _redirectUri;
    private readonly string[]? _scopes;
    private readonly IDictionary<string, string> _additionalAuthorizationParameters;
    private readonly Func<IReadOnlyList<Uri>, Uri?> _authServerSelector;
    private readonly AuthorizationRedirectDelegate _authorizationRedirectDelegate;

    // _clientName and _client URI is used for dynamic client registration (RFC 7591)
    private readonly string? _clientName;
    private readonly Uri? _clientUri;

    private readonly HttpClient _httpClient;
    private readonly ILogger _logger;

    private string? _clientId;
    private string? _clientSecret;

    private TokenContainer? _token;
    private AuthorizationServerMetadata? _authServerMetadata;

    /// <summary>
    /// Initializes a new instance of the <see cref="ClientOAuthProvider"/> class using the specified options.
    /// </summary>
    /// <param name="serverUrl">The MCP server URL.</param>
    /// <param name="options">The OAuth provider configuration options.</param>
    /// <param name="httpClient">The HTTP client to use for OAuth requests. If null, a default HttpClient will be used.</param>
    /// <param name="loggerFactory">A logger factory to handle diagnostic messages.</param>
    /// <exception cref="ArgumentNullException">Thrown when serverUrl or options are null.</exception>
    public ClientOAuthProvider(
        Uri serverUrl,
        ClientOAuthOptions options,
        HttpClient? httpClient = null,
        ILoggerFactory? loggerFactory = null)
    {
        _serverUrl = serverUrl ?? throw new ArgumentNullException(nameof(serverUrl));
        _httpClient = httpClient ?? new HttpClient();
        _logger = (ILogger?)loggerFactory?.CreateLogger<ClientOAuthProvider>() ?? NullLogger.Instance;

        if (options is null)
        {
            throw new ArgumentNullException(nameof(options));
        }

        _clientId = options.ClientId;
        _clientSecret = options.ClientSecret;
        _redirectUri = options.RedirectUri ?? throw new ArgumentException("ClientOAuthOptions.RedirectUri must configured.");
        _clientName = options.ClientName;
        _clientUri = options.ClientUri;
        _scopes = options.Scopes?.ToArray();
        _additionalAuthorizationParameters = options.AdditionalAuthorizationParameters;

        // Set up authorization server selection strategy
        _authServerSelector = options.AuthServerSelector ?? DefaultAuthServerSelector;

        // Set up authorization URL handler (use default if not provided)
        _authorizationRedirectDelegate = options.AuthorizationRedirectDelegate ?? DefaultAuthorizationUrlHandler;
    }

    /// <summary>
    /// Default authorization server selection strategy that selects the first available server.
    /// </summary>
    /// <param name="availableServers">List of available authorization servers.</param>
    /// <returns>The selected authorization server, or null if none are available.</returns>
    private static Uri? DefaultAuthServerSelector(IReadOnlyList<Uri> availableServers) => availableServers.FirstOrDefault();

    /// <summary>
    /// Default authorization URL handler that displays the URL to the user for manual input.
    /// </summary>
    /// <param name="authorizationUrl">The authorization URL to handle.</param>
    /// <param name="redirectUri">The redirect URI where the authorization code will be sent.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>The authorization code entered by the user, or null if none was provided.</returns>
    private static Task<string?> DefaultAuthorizationUrlHandler(Uri authorizationUrl, Uri redirectUri, CancellationToken cancellationToken)
    {
        Console.WriteLine($"Please open the following URL in your browser to authorize the application:");
        Console.WriteLine($"{authorizationUrl}");
        Console.WriteLine();
        Console.Write("Enter the authorization code from the redirect URL: ");
        var authorizationCode = Console.ReadLine();
        return Task.FromResult<string?>(authorizationCode);
    }

    /// <summary>
    /// Gets the collection of authentication schemes supported by this provider.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This property returns all authentication schemes that this provider can handle,
    /// allowing clients to select the appropriate scheme based on server capabilities.
    /// </para>
    /// <para>
    /// Common values include "Bearer" for JWT tokens, "Basic" for username/password authentication,
    /// and "Negotiate" for integrated Windows authentication.
    /// </para>
    /// </remarks>
    public IEnumerable<string> SupportedSchemes => [BearerScheme];

    /// <summary>
    /// Gets an authentication token or credential for authenticating requests to a resource
    /// using the specified authentication scheme.
    /// </summary>
    /// <param name="scheme">The authentication scheme to use.</param>
    /// <param name="resourceUri">The URI of the resource requiring authentication.</param>
    /// <param name="cancellationToken">A token to cancel the operation.</param>
    /// <returns>An authentication token string or null if no token could be obtained for the specified scheme.</returns>
    public async Task<string?> GetCredentialAsync(string scheme, Uri resourceUri, CancellationToken cancellationToken = default)
    {
        ThrowIfNotBearerScheme(scheme);

        // Return the token if it's valid
        if (_token != null && _token.ExpiresAt > DateTimeOffset.UtcNow.AddMinutes(5))
        {
            return _token.AccessToken;
        }

        // Try to refresh the token if we have a refresh token
        if (_token?.RefreshToken != null && _authServerMetadata != null)
        {
            var newToken = await RefreshTokenAsync(_token.RefreshToken, resourceUri, _authServerMetadata, cancellationToken).ConfigureAwait(false);
            if (newToken != null)
            {
                _token = newToken;
                return _token.AccessToken;
            }
        }

        // No valid token - auth handler will trigger the 401 flow
        return null;
    }

    /// <summary>
    /// Handles a 401 Unauthorized response from a resource.
    /// </summary>
    /// <param name="scheme">The authentication scheme that was used when the unauthorized response was received.</param>
    /// <param name="response">The HTTP response that contained the 401 status code.</param>
    /// <param name="cancellationToken">A token to cancel the operation.</param>
    /// <returns>
    /// A result object indicating if the provider was able to handle the unauthorized response,
    /// and the authentication scheme that should be used for the next attempt, if any.
    /// </returns>
    public async Task HandleUnauthorizedResponseAsync(
        string scheme,
        HttpResponseMessage response,
        CancellationToken cancellationToken = default)
    {
        // This provider only supports Bearer scheme
        if (!string.Equals(scheme, BearerScheme, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("This credential provider only supports the Bearer scheme");
        }

        await PerformOAuthAuthorizationAsync(response, cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    /// Performs OAuth authorization by selecting an appropriate authorization server and completing the OAuth flow.
    /// </summary>
    /// <param name="response">The 401 Unauthorized response containing authentication challenge.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>Result indicating whether authorization was successful.</returns>
    private async Task PerformOAuthAuthorizationAsync(
        HttpResponseMessage response,
        CancellationToken cancellationToken)
    {
        // Get available authorization servers from the 401 response
        var protectedResourceMetadata = await ExtractProtectedResourceMetadata(response, _serverUrl, cancellationToken).ConfigureAwait(false);
        var availableAuthorizationServers = protectedResourceMetadata.AuthorizationServers;

        if (availableAuthorizationServers.Count == 0)
        {
            ThrowFailedToHandleUnauthorizedResponse("No authorization servers found in authentication challenge");
        }

        // Select authorization server using configured strategy
        var selectedAuthServer = _authServerSelector(availableAuthorizationServers);

        if (selectedAuthServer is null)
        {
            ThrowFailedToHandleUnauthorizedResponse($"Authorization server selection returned null. Available servers: {string.Join(", ", availableAuthorizationServers)}");
        }

        if (!availableAuthorizationServers.Contains(selectedAuthServer))
        {
            ThrowFailedToHandleUnauthorizedResponse($"Authorization server selector returned a server not in the available list: {selectedAuthServer}. Available servers: {string.Join(", ", availableAuthorizationServers)}");
        }

        LogSelectedAuthorizationServer(selectedAuthServer, availableAuthorizationServers.Count);

        // Get auth server metadata
        var authServerMetadata = await GetAuthServerMetadataAsync(selectedAuthServer, cancellationToken).ConfigureAwait(false);

        // Store auth server metadata for future refresh operations
        _authServerMetadata = authServerMetadata;

        // Perform dynamic client registration if needed
        if (string.IsNullOrEmpty(_clientId))
        {
            await PerformDynamicClientRegistrationAsync(authServerMetadata, cancellationToken).ConfigureAwait(false);
        }

        // Perform the OAuth flow
        var token = await InitiateAuthorizationCodeFlowAsync(protectedResourceMetadata, authServerMetadata, cancellationToken).ConfigureAwait(false);

        if (token is null)
        {
            ThrowFailedToHandleUnauthorizedResponse($"The {nameof(AuthorizationRedirectDelegate)} returned a null or empty token.");
        }

        _token = token;
        LogOAuthAuthorizationCompleted();
    }

    private async Task<AuthorizationServerMetadata> GetAuthServerMetadataAsync(Uri authServerUri, CancellationToken cancellationToken)
    {
        if (!authServerUri.OriginalString.EndsWith("/"))
        {
            authServerUri = new Uri(authServerUri.OriginalString + "/");
        }

        foreach (var path in new[] { ".well-known/openid-configuration", ".well-known/oauth-authorization-server" })
        {
            try
            {
                var wellKnownEndpoint = new Uri(authServerUri, path);

                var response = await _httpClient.GetAsync(wellKnownEndpoint, cancellationToken).ConfigureAwait(false);
                if (!response.IsSuccessStatusCode)
                {
                    continue;
                }

                using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
                var metadata = await JsonSerializer.DeserializeAsync(stream, McpJsonUtilities.JsonContext.Default.AuthorizationServerMetadata, cancellationToken).ConfigureAwait(false);

                if (metadata is null)
                {
                    continue;
                }

                if (metadata.AuthorizationEndpoint is null)
                {
                    ThrowFailedToHandleUnauthorizedResponse($"No authorization_endpoint was provided via '{wellKnownEndpoint}'.");
                }

                if (metadata.AuthorizationEndpoint.Scheme != Uri.UriSchemeHttp &&
                    metadata.AuthorizationEndpoint.Scheme != Uri.UriSchemeHttps)
                {
                    ThrowFailedToHandleUnauthorizedResponse($"AuthorizationEndpoint must use HTTP or HTTPS. '{metadata.AuthorizationEndpoint}' does not meet this requirement.");
                }

                metadata.ResponseTypesSupported ??= ["code"];
                metadata.GrantTypesSupported ??= ["authorization_code", "refresh_token"];
                metadata.TokenEndpointAuthMethodsSupported ??= ["client_secret_post"];
                metadata.CodeChallengeMethodsSupported ??= ["S256"];

                return metadata;
            }
            catch (Exception ex)
            {
                LogErrorFetchingAuthServerMetadata(ex, path);
            }
        }

        throw new McpException($"Failed to find .well-known/openid-configuration or .well-known/oauth-authorization-server metadata for authorization server: '{authServerUri}'");
    }

    private async Task<TokenContainer> RefreshTokenAsync(string refreshToken, Uri resourceUri, AuthorizationServerMetadata authServerMetadata, CancellationToken cancellationToken)
    {
        var requestContent = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["grant_type"] = "refresh_token",
            ["refresh_token"] = refreshToken,
            ["client_id"] = GetClientIdOrThrow(),
            ["client_secret"] = _clientSecret ?? string.Empty,
            ["resource"] = resourceUri.ToString(),
        });

        using var request = new HttpRequestMessage(HttpMethod.Post, authServerMetadata.TokenEndpoint)
        {
            Content = requestContent
        };

        return await FetchTokenAsync(request, cancellationToken).ConfigureAwait(false);
    }

    private async Task<TokenContainer?> InitiateAuthorizationCodeFlowAsync(
        ProtectedResourceMetadata protectedResourceMetadata,
        AuthorizationServerMetadata authServerMetadata,
        CancellationToken cancellationToken)
    {
        var codeVerifier = GenerateCodeVerifier();
        var codeChallenge = GenerateCodeChallenge(codeVerifier);

        var authUrl = BuildAuthorizationUrl(protectedResourceMetadata, authServerMetadata, codeChallenge);
        var authCode = await _authorizationRedirectDelegate(authUrl, _redirectUri, cancellationToken).ConfigureAwait(false);

        if (string.IsNullOrEmpty(authCode))
        {
            return null;
        }

        return await ExchangeCodeForTokenAsync(protectedResourceMetadata, authServerMetadata, authCode!, codeVerifier, cancellationToken).ConfigureAwait(false);
    }

    private Uri BuildAuthorizationUrl(
        ProtectedResourceMetadata protectedResourceMetadata,
        AuthorizationServerMetadata authServerMetadata,
        string codeChallenge)
    {
        var queryParamsDictionary = new Dictionary<string, string>
        {
            ["client_id"] = GetClientIdOrThrow(),
            ["redirect_uri"] = _redirectUri.ToString(),
            ["response_type"] = "code",
            ["code_challenge"] = codeChallenge,
            ["code_challenge_method"] = "S256",
            ["resource"] = protectedResourceMetadata.Resource.ToString(),
        };

        var scopesSupported = protectedResourceMetadata.ScopesSupported;
        if (_scopes is not null || scopesSupported.Count > 0)
        {
            queryParamsDictionary["scope"] = string.Join(" ", _scopes ?? scopesSupported.ToArray());
        }

        // Add extra parameters if provided. Load into a dictionary before constructing to avoid overwiting values.
        foreach (var kvp in _additionalAuthorizationParameters)
        {
            queryParamsDictionary.Add(kvp.Key, kvp.Value);
        }

        var queryParams = HttpUtility.ParseQueryString(string.Empty);
        foreach (var kvp in queryParamsDictionary)
        {
            queryParams[kvp.Key] = kvp.Value;
        }

        var uriBuilder = new UriBuilder(authServerMetadata.AuthorizationEndpoint)
        {
            Query = queryParams.ToString()
        };

        return uriBuilder.Uri;
    }

    private async Task<TokenContainer> ExchangeCodeForTokenAsync(
        ProtectedResourceMetadata protectedResourceMetadata,
        AuthorizationServerMetadata authServerMetadata,
        string authorizationCode,
        string codeVerifier,
        CancellationToken cancellationToken)
    {
        var requestContent = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["grant_type"] = "authorization_code",
            ["code"] = authorizationCode,
            ["redirect_uri"] = _redirectUri.ToString(),
            ["client_id"] = GetClientIdOrThrow(),
            ["code_verifier"] = codeVerifier,
            ["client_secret"] = _clientSecret ?? string.Empty,
            ["resource"] = protectedResourceMetadata.Resource.ToString(),
        });

        using var request = new HttpRequestMessage(HttpMethod.Post, authServerMetadata.TokenEndpoint)
        {
            Content = requestContent
        };

        return await FetchTokenAsync(request, cancellationToken).ConfigureAwait(false);
    }

    private async Task<TokenContainer> FetchTokenAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        using var httpResponse = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
        httpResponse.EnsureSuccessStatusCode();

        using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
        var tokenResponse = await JsonSerializer.DeserializeAsync(stream, McpJsonUtilities.JsonContext.Default.TokenContainer, cancellationToken).ConfigureAwait(false);

        if (tokenResponse is null)
        {
            ThrowFailedToHandleUnauthorizedResponse($"The token endpoint '{request.RequestUri}' returned an empty response.");
        }

        tokenResponse.ObtainedAt = DateTimeOffset.UtcNow;
        return tokenResponse;
    }

    /// <summary>
    /// Fetches the protected resource metadata from the provided URL.
    /// </summary>
    /// <param name="metadataUrl">The URL to fetch the metadata from.</param>
    /// <param name="cancellationToken">A token to cancel the operation.</param>
    /// <returns>The fetched ProtectedResourceMetadata, or null if it couldn't be fetched.</returns>
    private async Task<ProtectedResourceMetadata?> FetchProtectedResourceMetadataAsync(Uri metadataUrl, CancellationToken cancellationToken = default)
    {
        using var httpResponse = await _httpClient.GetAsync(metadataUrl, cancellationToken).ConfigureAwait(false);
        httpResponse.EnsureSuccessStatusCode();

        using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
        return await JsonSerializer.DeserializeAsync(stream, McpJsonUtilities.JsonContext.Default.ProtectedResourceMetadata, cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    /// Performs dynamic client registration with the authorization server.
    /// </summary>
    /// <param name="authServerMetadata">The authorization server metadata.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    private async Task PerformDynamicClientRegistrationAsync(
        AuthorizationServerMetadata authServerMetadata,
        CancellationToken cancellationToken)
    {
        if (authServerMetadata.RegistrationEndpoint is null)
        {
            ThrowFailedToHandleUnauthorizedResponse("Authorization server does not support dynamic client registration");
        }

        LogPerformingDynamicClientRegistration(authServerMetadata.RegistrationEndpoint);

        var registrationRequest = new DynamicClientRegistrationRequest
        {
            RedirectUris = [_redirectUri.ToString()],
            GrantTypes = ["authorization_code", "refresh_token"],
            ResponseTypes = ["code"],
            TokenEndpointAuthMethod = "client_secret_post",
            ClientName = _clientName,
            ClientUri = _clientUri?.ToString(),
            Scope = _scopes is not null ? string.Join(" ", _scopes) : null
        };

        var requestJson = JsonSerializer.Serialize(registrationRequest, McpJsonUtilities.JsonContext.Default.DynamicClientRegistrationRequest);
        using var requestContent = new StringContent(requestJson, Encoding.UTF8, "application/json");

        using var request = new HttpRequestMessage(HttpMethod.Post, authServerMetadata.RegistrationEndpoint)
        {
            Content = requestContent
        };

        using var httpResponse = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);

        if (!httpResponse.IsSuccessStatusCode)
        {
            var errorContent = await httpResponse.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
            ThrowFailedToHandleUnauthorizedResponse($"Dynamic client registration failed with status {httpResponse.StatusCode}: {errorContent}");
        }

        using var responseStream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
        var registrationResponse = await JsonSerializer.DeserializeAsync(
            responseStream,
            McpJsonUtilities.JsonContext.Default.DynamicClientRegistrationResponse,
            cancellationToken).ConfigureAwait(false);

        if (registrationResponse is null)
        {
            ThrowFailedToHandleUnauthorizedResponse("Dynamic client registration returned empty response");
        }

        // Update client credentials
        _clientId = registrationResponse.ClientId;
        if (!string.IsNullOrEmpty(registrationResponse.ClientSecret))
        {
            _clientSecret = registrationResponse.ClientSecret;
        }

        LogDynamicClientRegistrationSuccessful(_clientId!);
    }

    /// <summary>
    /// Verifies that the resource URI in the metadata exactly matches the original request URL as required by the RFC.
    /// Per RFC: The resource value must be identical to the URL that the client used to make the request to the resource server.
    /// </summary>
    /// <param name="protectedResourceMetadata">The metadata to verify.</param>
    /// <param name="resourceLocation">The original URL the client used to make the request to the resource server.</param>
    /// <returns>True if the resource URI exactly matches the original request URL, otherwise false.</returns>
    private static bool VerifyResourceMatch(ProtectedResourceMetadata protectedResourceMetadata, Uri resourceLocation)
    {
        if (protectedResourceMetadata.Resource == null || resourceLocation == null)
        {
            return false;
        }

        // Per RFC: The resource value must be identical to the URL that the client used
        // to make the request to the resource server. Compare entire URIs, not just the host.

        // Normalize the URIs to ensure consistent comparison
        string normalizedMetadataResource = NormalizeUri(protectedResourceMetadata.Resource);
        string normalizedResourceLocation = NormalizeUri(resourceLocation);

        return string.Equals(normalizedMetadataResource, normalizedResourceLocation, StringComparison.OrdinalIgnoreCase);
    }

    /// <summary>
    /// Normalizes a URI for consistent comparison.
    /// </summary>
    /// <param name="uri">The URI to normalize.</param>
    /// <returns>A normalized string representation of the URI.</returns>
    private static string NormalizeUri(Uri uri)
    {
        var builder = new UriBuilder(uri)
        {
            Port = -1  // Always remove port
        };

        if (builder.Path == "/")
        {
            builder.Path = string.Empty;
        }
        else if (builder.Path.Length > 1 && builder.Path.EndsWith("/"))
        {
            builder.Path = builder.Path.TrimEnd('/');
        }

        return builder.Uri.ToString();
    }

    /// <summary>
    /// Responds to a 401 challenge by parsing the WWW-Authenticate header, fetching the resource metadata,
    /// verifying the resource match, and returning the metadata if valid.
    /// </summary>
    /// <param name="response">The HTTP response containing the WWW-Authenticate header.</param>
    /// <param name="serverUrl">The server URL to verify against the resource metadata.</param>
    /// <param name="cancellationToken">A token to cancel the operation.</param>
    /// <returns>The resource metadata if the resource matches the server, otherwise throws an exception.</returns>
    /// <exception cref="InvalidOperationException">Thrown when the response is not a 401, lacks a WWW-Authenticate header,
    /// lacks a resource_metadata parameter, the metadata can't be fetched, or the resource URI doesn't match the server URL.</exception>
    private async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(HttpResponseMessage response, Uri serverUrl, CancellationToken cancellationToken = default)
    {
        if (response.StatusCode != System.Net.HttpStatusCode.Unauthorized)
        {
            throw new InvalidOperationException($"Expected a 401 Unauthorized response, but received {(int)response.StatusCode} {response.StatusCode}");
        }

        // Extract the WWW-Authenticate header
        if (response.Headers.WwwAuthenticate.Count == 0)
        {
            throw new McpException("The 401 response does not contain a WWW-Authenticate header");
        }

        // Look for the Bearer authentication scheme with resource_metadata parameter
        string? resourceMetadataUrl = null;
        foreach (var header in response.Headers.WwwAuthenticate)
        {
            if (string.Equals(header.Scheme, "Bearer", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(header.Parameter))
            {
                resourceMetadataUrl = ParseWwwAuthenticateParameters(header.Parameter, "resource_metadata");
                if (resourceMetadataUrl != null)
                {
                    break;
                }
            }
        }

        if (resourceMetadataUrl == null)
        {
            throw new McpException("The WWW-Authenticate header does not contain a resource_metadata parameter");
        }

        Uri metadataUri = new(resourceMetadataUrl);
        var metadata = await FetchProtectedResourceMetadataAsync(metadataUri, cancellationToken).ConfigureAwait(false)
            ?? throw new McpException($"Failed to fetch resource metadata from {resourceMetadataUrl}");

        // Per RFC: The resource value must be identical to the URL that the client used
        // to make the request to the resource server
        LogValidatingResourceMetadata(serverUrl);

        if (!VerifyResourceMatch(metadata, serverUrl))
        {
            throw new McpException($"Resource URI in metadata ({metadata.Resource}) does not match the expected URI ({serverUrl})");
        }

        return metadata;
    }

    /// <summary>
    /// Parses the WWW-Authenticate header parameters to extract a specific parameter.
    /// </summary>
    /// <param name="parameters">The parameter string from the WWW-Authenticate header.</param>
    /// <param name="parameterName">The name of the parameter to extract.</param>
    /// <returns>The value of the parameter, or null if not found.</returns>
    private static string? ParseWwwAuthenticateParameters(string parameters, string parameterName)
    {
        if (parameters.IndexOf(parameterName, StringComparison.OrdinalIgnoreCase) == -1)
        {
            return null;
        }

        foreach (var part in parameters.Split(','))
        {
            string trimmedPart = part.Trim();
            int equalsIndex = trimmedPart.IndexOf('=');

            if (equalsIndex <= 0)
            {
                continue;
            }

            string key = trimmedPart.Substring(0, equalsIndex).Trim();

            if (string.Equals(key, parameterName, StringComparison.OrdinalIgnoreCase))
            {
                string value = trimmedPart.Substring(equalsIndex + 1).Trim();

                if (value.StartsWith("\"") && value.EndsWith("\""))
                {
                    value = value.Substring(1, value.Length - 2);
                }

                return value;
            }
        }

        return null;
    }

    private static string GenerateCodeVerifier()
    {
        var bytes = new byte[32];
        using var rng = RandomNumberGenerator.Create();
        rng.GetBytes(bytes);
        return Convert.ToBase64String(bytes)
            .TrimEnd('=')
            .Replace('+', '-')
            .Replace('/', '_');
    }

    private static string GenerateCodeChallenge(string codeVerifier)
    {
        using var sha256 = SHA256.Create();
        var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
        return Convert.ToBase64String(challengeBytes)
            .TrimEnd('=')
            .Replace('+', '-')
            .Replace('/', '_');
    }

    private string GetClientIdOrThrow() => _clientId ?? throw new InvalidOperationException("Client ID is not available. This may indicate an issue with dynamic client registration.");

    private static void ThrowIfNotBearerScheme(string scheme)
    {
        if (!string.Equals(scheme, BearerScheme, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException($"The '{scheme}' is not supported. This credential provider only supports the '{BearerScheme}' scheme");
        }
    }

    [DoesNotReturn]
    private static void ThrowFailedToHandleUnauthorizedResponse(string message) =>
        throw new McpException($"Failed to handle unauthorized response with 'Bearer' scheme. {message}");

    [LoggerMessage(Level = LogLevel.Information, Message = "Selected authorization server: {Server} from {Count} available servers")]
    partial void LogSelectedAuthorizationServer(Uri server, int count);

    [LoggerMessage(Level = LogLevel.Information, Message = "OAuth authorization completed successfully")]
    partial void LogOAuthAuthorizationCompleted();

    [LoggerMessage(Level = LogLevel.Error, Message = "Error fetching auth server metadata from {Path}")]
    partial void LogErrorFetchingAuthServerMetadata(Exception ex, string path);

    [LoggerMessage(Level = LogLevel.Information, Message = "Performing dynamic client registration with {RegistrationEndpoint}")]
    partial void LogPerformingDynamicClientRegistration(Uri registrationEndpoint);

    [LoggerMessage(Level = LogLevel.Information, Message = "Dynamic client registration successful. Client ID: {ClientId}")]
    partial void LogDynamicClientRegistrationSuccessful(string clientId);

    [LoggerMessage(Level = LogLevel.Debug, Message = "Validating resource metadata against original server URL: {ServerUrl}")]
    partial void LogValidatingResourceMetadata(Uri serverUrl);
}

using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Runtime.CompilerServices;
using System.Text.Json;

namespace ModelContextProtocol.Client;

///
/// Provides extension methods for interacting with an <see cref="IMcpClient"/>.
///
/// <remarks>
/// <para>
/// This class contains extension methods that simplify common operations with an MCP client,
/// such as pinging a server, listing and working with tools, prompts, and resources, and
/// managing subscriptions to resources.
/// </para>
/// </remarks>
public static class McpClientExtensions
{
    ///
    /// Sends a ping request to verify server connectivity.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that completes when the ping is successful.</returns>
    /// <remarks>
    /// <para>
    /// This method is used to check if the MCP server is online and responding to requests.
    /// It can be useful for health checking, ensuring the connection is established, or verifying
    /// that the client has proper authorization to communicate with the server.
    /// </para>
    /// <para>
    /// The ping operation is lightweight and does not require any parameters. A successful completion
    /// of the task indicates that the server is operational and accessible.
    /// </para>
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    /// <exception cref="McpException">Thrown when the server cannot be reached or returns an error response.</exception>
    public static Task PingAsync(this IMcpClient client, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);

        return client.SendRequestAsync(
            RequestMethods.Ping,
            parameters: null,
            McpJsonUtilities.JsonContext.Default.Object!,
            McpJsonUtilities.JsonContext.Default.Object,
            cancellationToken: cancellationToken).AsTask();
    }

    ///
    /// Retrieves a list of available tools from the server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="serializerOptions">The serializer options governing tool parameter serialization. If null, the default options will be used.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A list of all available tools as <see cref="McpClientTool"/> instances.</returns>
    /// <remarks>
    /// <para>
    /// This method fetches all available tools from the MCP server and returns them as a complete list.
    /// It automatically handles pagination with cursors if the server responds with only a portion per request.
    /// </para>
    /// <para>
    /// For servers with a large number of tools and that responds with paginated responses, consider using
    /// <see cref="EnumerateToolsAsync"/> instead, as it streams tools as they arrive rather than loading them all at once.
    /// </para>
    /// <para>
    /// The serializer options provided are flowed to each <see cref="McpClientTool"/> and will be used
    /// when invoking tools in order to serialize any parameters.
    /// </para>
    /// </remarks>
    /// <example>
    ///
    /// // Get all tools available on the server
    /// var tools = await mcpClient.ListToolsAsync();
    ///
    /// // Use tools with an AI client
    /// ChatOptions chatOptions = new()
    /// {
    ///     Tools = [.. tools]
    /// };
    ///
    /// await foreach (var update in chatClient.GetStreamingResponseAsync(userMessage, chatOptions))
    /// {
    ///     Console.Write(update);
    /// }
    ///
    /// </example>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    public static async ValueTask<IList<McpClientTool>> ListToolsAsync(
        this IMcpClient client,
        JsonSerializerOptions? serializerOptions = null,
        CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);

        serializerOptions ??= McpJsonUtilities.DefaultOptions;
        serializerOptions.MakeReadOnly();

        List<McpClientTool>? tools = null;
        string? cursor = null;
        do
        {
            var toolResults = await client.SendRequestAsync(
                RequestMethods.ToolsList,
                new() { Cursor = cursor },
                McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
                McpJsonUtilities.JsonContext.Default.ListToolsResult,
                cancellationToken: cancellationToken).ConfigureAwait(false);

            tools ??= new List<McpClientTool>(toolResults.Tools.Count);
            foreach (var tool in toolResults.Tools)
            {
                tools.Add(new McpClientTool(client, tool, serializerOptions));
            }

            cursor = toolResults.NextCursor;
        }
        while (cursor is not null);

        return tools;
    }

    ///
    /// Creates an enumerable for asynchronously enumerating all available tools from the server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="serializerOptions">The serializer options governing tool parameter serialization. If null, the default options will be used.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>An asynchronous sequence of all available tools as <see cref="McpClientTool"/> instances.</returns>
    /// <remarks>
    /// <para>
    /// This method uses asynchronous enumeration to retrieve tools from the server, which allows processing tools
    /// as they arrive rather than waiting for all tools to be retrieved. The method automatically handles pagination
    /// with cursors if the server responds with tools split across multiple responses.
    /// </para>
    /// <para>
    /// The serializer options provided are flowed to each <see cref="McpClientTool"/> and will be used
    /// when invoking tools in order to serialize any parameters.
    /// </para>
    /// <para>
    /// Every iteration through the returned <see cref="IAsyncEnumerable{McpClientTool}"/>
    /// will result in re-querying the server and yielding the sequence of available tools.
    /// </para>
    /// </remarks>
    /// <example>
    ///
    /// // Enumerate all tools available on the server
    /// await foreach (var tool in client.EnumerateToolsAsync())
    /// {
    ///     Console.WriteLine($"Tool: {tool.Name}");
    /// }
    ///
    /// </example>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    public static async IAsyncEnumerable<McpClientTool> EnumerateToolsAsync(
        this IMcpClient client,
        JsonSerializerOptions? serializerOptions = null,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);

        serializerOptions ??= McpJsonUtilities.DefaultOptions;
        serializerOptions.MakeReadOnly();

        string? cursor = null;
        do
        {
            var toolResults = await client.SendRequestAsync(
                RequestMethods.ToolsList,
                new() { Cursor = cursor },
                McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
                McpJsonUtilities.JsonContext.Default.ListToolsResult,
                cancellationToken: cancellationToken).ConfigureAwait(false);

            foreach (var tool in toolResults.Tools)
            {
                yield return new McpClientTool(client, tool, serializerOptions);
            }

            cursor = toolResults.NextCursor;
        }
        while (cursor is not null);
    }

    ///
    /// Retrieves a list of available prompts from the server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A list of all available prompts as <see cref="McpClientPrompt"/> instances.</returns>
    /// <remarks>
    /// <para>
    /// This method fetches all available prompts from the MCP server and returns them as a complete list.
    /// It automatically handles pagination with cursors if the server responds with only a portion per request.
    /// </para>
    /// <para>
    /// For servers with a large number of prompts and that responds with paginated responses, consider using
    /// <see cref="EnumeratePromptsAsync"/> instead, as it streams prompts as they arrive rather than loading them all at once.
    /// </para>
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    public static async ValueTask<IList<McpClientPrompt>> ListPromptsAsync(
        this IMcpClient client, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);

        List<McpClientPrompt>? prompts = null;
        string? cursor = null;
        do
        {
            var promptResults = await client.SendRequestAsync(
                RequestMethods.PromptsList,
                new() { Cursor = cursor },
                McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
                McpJsonUtilities.JsonContext.Default.ListPromptsResult,
                cancellationToken: cancellationToken).ConfigureAwait(false);

            prompts ??= new List<McpClientPrompt>(promptResults.Prompts.Count);
            foreach (var prompt in promptResults.Prompts)
            {
                prompts.Add(new McpClientPrompt(client, prompt));
            }

            cursor = promptResults.NextCursor;
        }
        while (cursor is not null);

        return prompts;
    }

    ///
    /// Creates an enumerable for asynchronously enumerating all available prompts from the server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>An asynchronous sequence of all available prompts as <see cref="McpClientPrompt"/> instances.</returns>
    /// <remarks>
    /// <para>
    /// This method uses asynchronous enumeration to retrieve prompts from the server, which allows processing prompts
    /// as they arrive rather than waiting for all prompts to be retrieved. The method automatically handles pagination
    /// with cursors if the server responds with prompts split across multiple responses.
    /// </para>
    /// <para>
    /// Every iteration through the returned <see cref="IAsyncEnumerable{McpClientPrompt}"/>
    /// will result in re-querying the server and yielding the sequence of available prompts.
    /// </para>
    /// </remarks>
    /// <example>
    ///
    /// // Enumerate all prompts available on the server
    /// await foreach (var prompt in client.EnumeratePromptsAsync())
    /// {
    ///     Console.WriteLine($"Prompt: {prompt.Name}");
    /// }
    ///
    /// </example>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    public static async IAsyncEnumerable<McpClientPrompt> EnumeratePromptsAsync(
        this IMcpClient client, [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);

        string? cursor = null;
        do
        {
            var promptResults = await client.SendRequestAsync(
                RequestMethods.PromptsList,
                new() { Cursor = cursor },
                McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
                McpJsonUtilities.JsonContext.Default.ListPromptsResult,
                cancellationToken: cancellationToken).ConfigureAwait(false);

            foreach (var prompt in promptResults.Prompts)
            {
                yield return new(client, prompt);
            }

            cursor = promptResults.NextCursor;
        }
        while (cursor is not null);
    }

    ///
    /// Retrieves a specific prompt from the MCP server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="name">The name of the prompt to retrieve.</param>
    /// <param name="arguments">Optional arguments for the prompt. Keys are parameter names, and values are the argument values.</param>
    /// <param name="serializerOptions">The serialization options governing argument serialization.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task containing the prompt's result with content and messages.</returns>
    /// <remarks>
    /// <para>
    /// This method sends a request to the MCP server to create the specified prompt with the provided arguments.
    /// The server will process the arguments and return a prompt containing messages or other content.
    /// </para>
    /// <para>
    /// Arguments are serialized into JSON and passed to the server, where they may be used to customize the
    /// prompt's behavior or content. Each prompt may have different argument requirements.
    /// </para>
    /// <para>
    /// The returned <see cref="GetPromptResult"/> contains a collection of <see cref="PromptMessage"/> objects,
    /// which can be converted to <see cref="ChatMessage"/> objects using the <see cref="AIContentExtensions.ToChatMessages"/> method.
    /// </para>
    /// </remarks>
    /// <exception cref="McpException">Thrown when the prompt does not exist, when required arguments are missing, or when the server encounters an error processing the prompt.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    public static ValueTask<GetPromptResult> GetPromptAsync(
        this IMcpClient client,
        string name,
        IReadOnlyDictionary<string, object?>? arguments = null,
        JsonSerializerOptions? serializerOptions = null,
        CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);
        Throw.IfNullOrWhiteSpace(name);

        serializerOptions ??= McpJsonUtilities.DefaultOptions;
        serializerOptions.MakeReadOnly();

        return client.SendRequestAsync(
            RequestMethods.PromptsGet,
            new() { Name = name, Arguments = ToArgumentsDictionary(arguments, serializerOptions) },
            McpJsonUtilities.JsonContext.Default.GetPromptRequestParams,
            McpJsonUtilities.JsonContext.Default.GetPromptResult,
            cancellationToken: cancellationToken);
    }

    ///
    /// Retrieves a list of available resource templates from the server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A list of all available resource templates as <see cref="ResourceTemplate"/> instances.</returns>
    /// <remarks>
    /// <para>
    /// This method fetches all available resource templates from the MCP server and returns them as a complete list.
    /// It automatically handles pagination with cursors if the server responds with only a portion per request.
    /// </para>
    /// <para>
    /// For servers with a large number of resource templates and that responds with paginated responses, consider using
    /// <see cref="EnumerateResourceTemplatesAsync"/> instead, as it streams templates as they arrive rather than loading them all at once.
    /// </para>
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    public static async ValueTask<IList<McpClientResourceTemplate>> ListResourceTemplatesAsync(
        this IMcpClient client, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);

        List<McpClientResourceTemplate>? resourceTemplates = null;

        string? cursor = null;
        do
        {
            var templateResults = await client.SendRequestAsync(
                RequestMethods.ResourcesTemplatesList,
                new() { Cursor = cursor },
                McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
                McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
                cancellationToken: cancellationToken).ConfigureAwait(false);

            resourceTemplates ??= new List<McpClientResourceTemplate>(templateResults.ResourceTemplates.Count);
            foreach (var template in templateResults.ResourceTemplates)
            {
                resourceTemplates.Add(new McpClientResourceTemplate(client, template));
            }

            cursor = templateResults.NextCursor;
        }
        while (cursor is not null);

        return resourceTemplates;
    }

    ///
    /// Creates an enumerable for asynchronously enumerating all available resource templates from the server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>An asynchronous sequence of all available resource templates as <see cref="ResourceTemplate"/> instances.</returns>
    /// <remarks>
    /// <para>
    /// This method uses asynchronous enumeration to retrieve resource templates from the server, which allows processing templates
    /// as they arrive rather than waiting for all templates to be retrieved. The method automatically handles pagination
    /// with cursors if the server responds with templates split across multiple responses.
    /// </para>
    /// <para>
    /// Every iteration through the returned <see cref="IAsyncEnumerable{McpClientResourceTemplate}"/>
    /// will result in re-querying the server and yielding the sequence of available resource templates.
    /// </para>
    /// </remarks>
    /// <example>
    ///
    /// // Enumerate all resource templates available on the server
    /// await foreach (var template in client.EnumerateResourceTemplatesAsync())
    /// {
    ///     Console.WriteLine($"Template: {template.Name}");
    /// }
    ///
    /// </example>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    public static async IAsyncEnumerable<McpClientResourceTemplate> EnumerateResourceTemplatesAsync(
        this IMcpClient client, [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);

        string? cursor = null;
        do
        {
            var templateResults = await client.SendRequestAsync(
                RequestMethods.ResourcesTemplatesList,
                new() { Cursor = cursor },
                McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
                McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
                cancellationToken: cancellationToken).ConfigureAwait(false);

            foreach (var templateResult in templateResults.ResourceTemplates)
            {
                yield return new McpClientResourceTemplate(client, templateResult);
            }

            cursor = templateResults.NextCursor;
        }
        while (cursor is not null);
    }

    ///
    /// Retrieves a list of available resources from the server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A list of all available resources as <see cref="Resource"/> instances.</returns>
    /// <remarks>
    /// <para>
    /// This method fetches all available resources from the MCP server and returns them as a complete list.
    /// It automatically handles pagination with cursors if the server responds with only a portion per request.
    /// </para>
    /// <para>
    /// For servers with a large number of resources and that responds with paginated responses, consider using
    /// <see cref="EnumerateResourcesAsync"/> instead, as it streams resources as they arrive rather than loading them all at once.
    /// </para>
    /// </remarks>
    /// <example>
    ///
    /// // Get all resources available on the server
    /// var resources = await client.ListResourcesAsync();
    ///
    /// // Display information about each resource
    /// foreach (var resource in resources)
    /// {
    ///     Console.WriteLine($"Resource URI: {resource.Uri}");
    /// }
    ///
    /// </example>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    public static async ValueTask<IList<McpClientResource>> ListResourcesAsync(
        this IMcpClient client, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);

        List<McpClientResource>? resources = null;

        string? cursor = null;
        do
        {
            var resourceResults = await client.SendRequestAsync(
                RequestMethods.ResourcesList,
                new() { Cursor = cursor },
                McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
                McpJsonUtilities.JsonContext.Default.ListResourcesResult,
                cancellationToken: cancellationToken).ConfigureAwait(false);

            resources ??= new List<McpClientResource>(resourceResults.Resources.Count);
            foreach (var resource in resourceResults.Resources)
            {
                resources.Add(new McpClientResource(client, resource));
            }

            cursor = resourceResults.NextCursor;
        }
        while (cursor is not null);

        return resources;
    }

    ///
    /// Creates an enumerable for asynchronously enumerating all available resources from the server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>An asynchronous sequence of all available resources as <see cref="Resource"/> instances.</returns>
    /// <remarks>
    /// <para>
    /// This method uses asynchronous enumeration to retrieve resources from the server, which allows processing resources
    /// as they arrive rather than waiting for all resources to be retrieved. The method automatically handles pagination
    /// with cursors if the server responds with resources split across multiple responses.
    /// </para>
    /// <para>
    /// Every iteration through the returned <see cref="IAsyncEnumerable{McpClientResource}"/>
    /// will result in re-querying the server and yielding the sequence of available resources.
    /// </para>
    /// </remarks>
    /// <example>
    ///
    /// // Enumerate all resources available on the server
    /// await foreach (var resource in client.EnumerateResourcesAsync())
    /// {
    ///     Console.WriteLine($"Resource URI: {resource.Uri}");
    /// }
    ///
    /// </example>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    public static async IAsyncEnumerable<McpClientResource> EnumerateResourcesAsync(
        this IMcpClient client, [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);

        string? cursor = null;
        do
        {
            var resourceResults = await client.SendRequestAsync(
                RequestMethods.ResourcesList,
                new() { Cursor = cursor },
                McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
                McpJsonUtilities.JsonContext.Default.ListResourcesResult,
                cancellationToken: cancellationToken).ConfigureAwait(false);

            foreach (var resource in resourceResults.Resources)
            {
                yield return new McpClientResource(client, resource);
            }

            cursor = resourceResults.NextCursor;
        }
        while (cursor is not null);
    }

    ///
    /// Reads a resource from the server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="uri">The uri of the resource.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="uri"/> is empty or composed entirely of whitespace.</exception>
    public static ValueTask<ReadResourceResult> ReadResourceAsync(
        this IMcpClient client, string uri, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);
        Throw.IfNullOrWhiteSpace(uri);

        return client.SendRequestAsync(
            RequestMethods.ResourcesRead,
            new() { Uri = uri },
            McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams,
            McpJsonUtilities.JsonContext.Default.ReadResourceResult,
            cancellationToken: cancellationToken);
    }

    ///
    /// Reads a resource from the server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="uri">The uri of the resource.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <see langword="null"/>.</exception>
    public static ValueTask<ReadResourceResult> ReadResourceAsync(
        this IMcpClient client, Uri uri, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);
        Throw.IfNull(uri);

        return ReadResourceAsync(client, uri.ToString(), cancellationToken);
    }

    ///
    /// Reads a resource from the server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="uriTemplate">The uri template of the resource.</param>
    /// <param name="arguments">Arguments to use to format <paramref name="uriTemplate"/>.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="uriTemplate"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="uriTemplate"/> is empty or composed entirely of whitespace.</exception>
    public static ValueTask<ReadResourceResult> ReadResourceAsync(
        this IMcpClient client, string uriTemplate, IReadOnlyDictionary<string, object?> arguments, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);
        Throw.IfNullOrWhiteSpace(uriTemplate);
        Throw.IfNull(arguments);

        return client.SendRequestAsync(
            RequestMethods.ResourcesRead,
            new() { Uri = UriTemplate.FormatUri(uriTemplate, arguments) },
            McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams,
            McpJsonUtilities.JsonContext.Default.ReadResourceResult,
            cancellationToken: cancellationToken);
    }

    ///
    /// Requests completion suggestions for a prompt argument or resource reference.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="reference">The reference object specifying the type and optional URI or name.</param>
    /// <param name="argumentName">The name of the argument for which completions are requested.</param>
    /// <param name="argumentValue">The current value of the argument, used to filter relevant completions.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A <see cref="CompleteResult"/> containing completion suggestions.</returns>
    /// <remarks>
    /// <para>
    /// This method allows clients to request auto-completion suggestions for arguments in a prompt template
    /// or for resource references.
    /// </para>
    /// <para>
    /// When working with prompt references, the server will return suggestions for the specified argument
    /// that match or begin with the current argument value. This is useful for implementing intelligent
    /// auto-completion in user interfaces.
    /// </para>
    /// <para>
    /// When working with resource references, the server will return suggestions relevant to the specified
    /// resource URI.
    /// </para>
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="reference"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="argumentName"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="argumentName"/> is empty or composed entirely of whitespace.</exception>
    /// <exception cref="McpException">The server returned an error response.</exception>
    public static ValueTask<CompleteResult> CompleteAsync(this IMcpClient client, Reference reference, string argumentName, string argumentValue, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);
        Throw.IfNull(reference);
        Throw.IfNullOrWhiteSpace(argumentName);

        return client.SendRequestAsync(
            RequestMethods.CompletionComplete,
            new()
            {
                Ref = reference,
                Argument = new Argument { Name = argumentName, Value = argumentValue }
            },
            McpJsonUtilities.JsonContext.Default.CompleteRequestParams,
            McpJsonUtilities.JsonContext.Default.CompleteResult,
            cancellationToken: cancellationToken);
    }

    ///
    /// Subscribes to a resource on the server to receive notifications when it changes.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="uri">The URI of the resource to which to subscribe.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    /// <remarks>
    /// <para>
    /// This method allows the client to register interest in a specific resource identified by its URI.
    /// When the resource changes, the server will send notifications to the client, enabling real-time
    /// updates without polling.
    /// </para>
    /// <para>
    /// The subscription remains active until explicitly unsubscribed using <see cref="M:UnsubscribeFromResourceAsync"/>
    /// or until the client disconnects from the server.
    /// </para>
    /// <para>
    /// To handle resource change notifications, register an event handler for the appropriate notification events,
    /// such as with <see cref="IMcpEndpoint.RegisterNotificationHandler"/>.
    /// </para>
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="uri"/> is empty or composed entirely of whitespace.</exception>
    public static Task SubscribeToResourceAsync(this IMcpClient client, string uri, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);
        Throw.IfNullOrWhiteSpace(uri);

        return client.SendRequestAsync(
            RequestMethods.ResourcesSubscribe,
            new() { Uri = uri },
            McpJsonUtilities.JsonContext.Default.SubscribeRequestParams,
            McpJsonUtilities.JsonContext.Default.EmptyResult,
            cancellationToken: cancellationToken).AsTask();
    }

    ///
    /// Subscribes to a resource on the server to receive notifications when it changes.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="uri">The URI of the resource to which to subscribe.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    /// <remarks>
    /// <para>
    /// This method allows the client to register interest in a specific resource identified by its URI.
    /// When the resource changes, the server will send notifications to the client, enabling real-time
    /// updates without polling.
    /// </para>
    /// <para>
    /// The subscription remains active until explicitly unsubscribed using <see cref="M:UnsubscribeFromResourceAsync"/>
    /// or until the client disconnects from the server.
    /// </para>
    /// <para>
    /// To handle resource change notifications, register an event handler for the appropriate notification events,
    /// such as with <see cref="IMcpEndpoint.RegisterNotificationHandler"/>.
    /// </para>
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <see langword="null"/>.</exception>
    public static Task SubscribeToResourceAsync(this IMcpClient client, Uri uri, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);
        Throw.IfNull(uri);

        return SubscribeToResourceAsync(client, uri.ToString(), cancellationToken);
    }

    ///
    /// Unsubscribes from a resource on the server to stop receiving notifications about its changes.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="uri">The URI of the resource to unsubscribe from.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    /// <remarks>
    /// <para>
    /// This method cancels a previous subscription to a resource, stopping the client from receiving
    /// notifications when that resource changes.
    /// </para>
    /// <para>
    /// The unsubscribe operation is idempotent, meaning it can be called multiple times for the same
    /// resource without causing errors, even if there is no active subscription.
    /// </para>
    /// <para>
    /// Due to the nature of the MCP protocol, it is possible the client may receive notifications after
    /// unsubscribing if those notifications were issued by the server prior to the unsubscribe request being received.
    /// </para>
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="uri"/> is empty or composed entirely of whitespace.</exception>
    public static Task UnsubscribeFromResourceAsync(this IMcpClient client, string uri, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);
        Throw.IfNullOrWhiteSpace(uri);

        return client.SendRequestAsync(
            RequestMethods.ResourcesUnsubscribe,
            new() { Uri = uri },
            McpJsonUtilities.JsonContext.Default.UnsubscribeRequestParams,
            McpJsonUtilities.JsonContext.Default.EmptyResult,
            cancellationToken: cancellationToken).AsTask();
    }

    ///
    /// Unsubscribes from a resource on the server to stop receiving notifications about its changes.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="uri">The URI of the resource to unsubscribe from.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    /// <remarks>
    /// <para>
    /// This method cancels a previous subscription to a resource, stopping the client from receiving
    /// notifications when that resource changes.
    /// </para>
    /// <para>
    /// The unsubscribe operation is idempotent, meaning it can be called multiple times for the same
    /// resource without causing errors, even if there is no active subscription.
    /// </para>
    /// <para>
    /// Due to the nature of the MCP protocol, it is possible the client may receive notifications after
    /// unsubscribing if those notifications were issued by the server prior to the unsubscribe request being received.
    /// </para>
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <see langword="null"/>.</exception>
    public static Task UnsubscribeFromResourceAsync(this IMcpClient client, Uri uri, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);
        Throw.IfNull(uri);

        return UnsubscribeFromResourceAsync(client, uri.ToString(), cancellationToken);
    }

    ///
    /// Invokes a tool on the server.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="toolName">The name of the tool to call on the server..</param>
    /// <param name="arguments">An optional dictionary of arguments to pass to the tool. Each key represents a parameter name,
    /// and its associated value represents the argument value.
    /// </param>
    /// <param name="progress">
    /// An optional <see cref="IProgress{T}"/> to have progress notifications reported to it. Setting this to a non-<see langword="null"/>
    /// value will result in a progress token being included in the call, and any resulting progress notifications during the operation
    /// routed to this instance.
    /// </param>
    /// <param name="serializerOptions">
    /// The JSON serialization options governing argument serialization. If <see langword="null"/>, the default serialization options will be used.
    /// </param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>
    /// A task containing the <see cref="CallToolResult"/> from the tool execution. The response includes
    /// the tool's output content, which may be structured data, text, or an error message.
    /// </returns>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="toolName"/> is <see langword="null"/>.</exception>
    /// <exception cref="McpException">The server could not find the requested tool, or the server encountered an error while processing the request.</exception>
    /// <example>
    ///
    /// // Call a simple echo tool with a string argument
    /// var result = await client.CallToolAsync(
    ///     "echo",
    ///     new Dictionary<string, object?>
    ///     {
    ///         ["message"] = "Hello MCP!"
    ///     });
    ///
    /// </example>
    public static ValueTask<CallToolResult> CallToolAsync(
        this IMcpClient client,
        string toolName,
        IReadOnlyDictionary<string, object?>? arguments = null,
        IProgress<ProgressNotificationValue>? progress = null,
        JsonSerializerOptions? serializerOptions = null,
        CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);
        Throw.IfNull(toolName);
        serializerOptions ??= McpJsonUtilities.DefaultOptions;
        serializerOptions.MakeReadOnly();

        if (progress is not null)
        {
            return SendRequestWithProgressAsync(client, toolName, arguments, progress, serializerOptions, cancellationToken);
        }

        return client.SendRequestAsync(
            RequestMethods.ToolsCall,
            new()
            {
                Name = toolName,
                Arguments = ToArgumentsDictionary(arguments, serializerOptions),
            },
            McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
            McpJsonUtilities.JsonContext.Default.CallToolResult,
            cancellationToken: cancellationToken);

        static async ValueTask<CallToolResult> SendRequestWithProgressAsync(
            IMcpClient client,
            string toolName,
            IReadOnlyDictionary<string, object?>? arguments,
            IProgress<ProgressNotificationValue> progress,
            JsonSerializerOptions serializerOptions,
            CancellationToken cancellationToken)
        {
            ProgressToken progressToken = new(Guid.NewGuid().ToString("N"));

            await using var _ = client.RegisterNotificationHandler(NotificationMethods.ProgressNotification,
                (notification, cancellationToken) =>
                {
                    if (JsonSerializer.Deserialize(notification.Params, McpJsonUtilities.JsonContext.Default.ProgressNotificationParams) is { } pn &&
                        pn.ProgressToken == progressToken)
                    {
                        progress.Report(pn.Progress);
                    }

                    return default;
                }).ConfigureAwait(false);

            return await client.SendRequestAsync(
                RequestMethods.ToolsCall,
                new()
                {
                    Name = toolName,
                    Arguments = ToArgumentsDictionary(arguments, serializerOptions),
                    ProgressToken = progressToken,
                },
                McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
                McpJsonUtilities.JsonContext.Default.CallToolResult,
                cancellationToken: cancellationToken).ConfigureAwait(false);
        }
    }

    ///
    /// Converts the contents of a <see cref="CreateMessageRequestParams"/> into a pair of
    /// <see cref="IEnumerable{ChatMessage}"/> and <see cref="ChatOptions"/> instances to use
    /// as inputs into a <see cref="IChatClient"/> operation.
    ///
    /// <param name="requestParams"></param>
    /// <returns>The created pair of messages and options.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
    internal static (IList<ChatMessage> Messages, ChatOptions? Options) ToChatClientArguments(
        this CreateMessageRequestParams requestParams)
    {
        Throw.IfNull(requestParams);

        ChatOptions? options = null;

        if (requestParams.MaxTokens is int maxTokens)
        {
            (options ??= new()).MaxOutputTokens = maxTokens;
        }

        if (requestParams.Temperature is float temperature)
        {
            (options ??= new()).Temperature = temperature;
        }

        if (requestParams.StopSequences is { } stopSequences)
        {
            (options ??= new()).StopSequences = stopSequences.ToArray();
        }

        List<ChatMessage> messages =
            (from sm in requestParams.Messages
             let aiContent = sm.Content.ToAIContent()
             where aiContent is not null
             select new ChatMessage(sm.Role == Role.Assistant ? ChatRole.Assistant : ChatRole.User, [aiContent]))
            .ToList();

        return (messages, options);
    }

    /// Converts the contents of a <see cref="ChatResponse"/> into a <see cref="CreateMessageResult"/>.
    /// <param name="chatResponse">The <see cref="ChatResponse"/> whose contents should be extracted.</param>
    /// <returns>The created <see cref="CreateMessageResult"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="chatResponse"/> is <see langword="null"/>.</exception>
    internal static CreateMessageResult ToCreateMessageResult(this ChatResponse chatResponse)
    {
        Throw.IfNull(chatResponse);

        // The ChatResponse can include multiple messages, of varying modalities, but CreateMessageResult supports
        // only either a single blob of text or a single image. Heuristically, we'll use an image if there is one
        // in any of the response messages, or we'll use all the text from them concatenated, otherwise.

        ChatMessage? lastMessage = chatResponse.Messages.LastOrDefault();

        ContentBlock? content = null;
        if (lastMessage is not null)
        {
            foreach (var lmc in lastMessage.Contents)
            {
                if (lmc is DataContent dc && (dc.HasTopLevelMediaType("image") || dc.HasTopLevelMediaType("audio")))
                {
                    content = dc.ToContent();
                }
            }
        }

        return new()
        {
            Content = content ?? new TextContentBlock { Text = lastMessage?.Text ?? string.Empty },
            Model = chatResponse.ModelId ?? "unknown",
            Role = lastMessage?.Role == ChatRole.User ? Role.User : Role.Assistant,
            StopReason = chatResponse.FinishReason == ChatFinishReason.Length ? "maxTokens" : "endTurn",
        };
    }

    ///
    /// Creates a sampling handler for use with <see cref="SamplingCapability.SamplingHandler"/> that will
    /// satisfy sampling requests using the specified <see cref="IChatClient"/>.
    ///
    /// <param name="chatClient">The <see cref="IChatClient"/> with which to satisfy sampling requests.</param>
    /// <returns>The created handler delegate that can be assigned to <see cref="SamplingCapability.SamplingHandler"/>.</returns>
    /// <remarks>
    /// <para>
    /// This method creates a function that converts MCP message requests into chat client calls, enabling
    /// an MCP client to generate text or other content using an actual AI model via the provided chat client.
    /// </para>
    /// <para>
    /// The handler can process text messages, image messages, and resource messages as defined in the
    /// Model Context Protocol.
    /// </para>
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="chatClient"/> is <see langword="null"/>.</exception>
    public static Func<CreateMessageRequestParams?, IProgress<ProgressNotificationValue>, CancellationToken, ValueTask<CreateMessageResult>> CreateSamplingHandler(
        this IChatClient chatClient)
    {
        Throw.IfNull(chatClient);

        return async (requestParams, progress, cancellationToken) =>
        {
            Throw.IfNull(requestParams);

            var (messages, options) = requestParams.ToChatClientArguments();
            var progressToken = requestParams.ProgressToken;

            List<ChatResponseUpdate> updates = [];
            await foreach (var update in chatClient.GetStreamingResponseAsync(messages, options, cancellationToken).ConfigureAwait(false))
            {
                updates.Add(update);

                if (progressToken is not null)
                {
                    progress.Report(new()
                    {
                        Progress = updates.Count,
                    });
                }
            }

            return updates.ToChatResponse().ToCreateMessageResult();
        };
    }

    ///
    /// Sets the logging level for the server to control which log messages are sent to the client.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="level">The minimum severity level of log messages to receive from the server.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    /// <remarks>
    /// <para>
    /// After this request is processed, the server will send log messages at or above the specified
    /// logging level as notifications to the client. For example, if <see cref="LoggingLevel.Warning"/> is set,
    /// the client will receive <see cref="LoggingLevel.Warning"/>, <see cref="LoggingLevel.Error"/>,
    /// <see cref="LoggingLevel.Critical"/>, <see cref="LoggingLevel.Alert"/>, and <see cref="LoggingLevel.Emergency"/>
    /// level messages.
    /// </para>
    /// <para>
    /// To receive all log messages, set the level to <see cref="LoggingLevel.Debug"/>.
    /// </para>
    /// <para>
    /// Log messages are delivered as notifications to the client and can be captured by registering
    /// appropriate event handlers with the client implementation, such as with <see cref="IMcpEndpoint.RegisterNotificationHandler"/>.
    /// </para>
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    public static Task SetLoggingLevel(this IMcpClient client, LoggingLevel level, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);

        return client.SendRequestAsync(
            RequestMethods.LoggingSetLevel,
            new() { Level = level },
            McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
            McpJsonUtilities.JsonContext.Default.EmptyResult,
            cancellationToken: cancellationToken).AsTask();
    }

    ///
    /// Sets the logging level for the server to control which log messages are sent to the client.
    ///
    /// <param name="client">The client instance used to communicate with the MCP server.</param>
    /// <param name="level">The minimum severity level of log messages to receive from the server.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    /// <remarks>
    /// <para>
    /// After this request is processed, the server will send log messages at or above the specified
    /// logging level as notifications to the client. For example, if <see cref="LogLevel.Warning"/> is set,
    /// the client will receive <see cref="LogLevel.Warning"/>, <see cref="LogLevel.Error"/>,
    /// and <see cref="LogLevel.Critical"/> level messages.
    /// </para>
    /// <para>
    /// To receive all log messages, set the level to <see cref="LogLevel.Trace"/>.
    /// </para>
    /// <para>
    /// Log messages are delivered as notifications to the client and can be captured by registering
    /// appropriate event handlers with the client implementation, such as with <see cref="IMcpEndpoint.RegisterNotificationHandler"/>.
    /// </para>
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
    public static Task SetLoggingLevel(this IMcpClient client, LogLevel level, CancellationToken cancellationToken = default) =>
        SetLoggingLevel(client, McpServer.ToLoggingLevel(level), cancellationToken);

    /// Convers a dictionary with <see cref="object"/> values to a dictionary with <see cref="JsonElement"/> values.
    private static Dictionary<string, JsonElement>? ToArgumentsDictionary(
        IReadOnlyDictionary<string, object?>? arguments, JsonSerializerOptions options)
    {
        var typeInfo = options.GetTypeInfo<object?>();

        Dictionary<string, JsonElement>? result = null;
        if (arguments is not null)
        {
            result = new(arguments.Count);
            foreach (var kvp in arguments)
            {
                result.Add(kvp.Key, kvp.Value is JsonElement je ? je : JsonSerializer.SerializeToElement(kvp.Value, typeInfo));
            }
        }

        return result;
    }
}

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol.Protocol;
using System.Text;
using System.Text.Json;

namespace ModelContextProtocol.Server;

/// <summary>
/// Provides an <see cref="ITransport"/> implemented using a pair of input and output streams.
/// </summary>
/// <remarks>
/// The <see cref="StreamServerTransport"/> class implements bidirectional JSON-RPC messaging over arbitrary
/// streams, allowing MCP communication with clients through various I/O channels such as network sockets,
/// memory streams, or pipes.
/// </remarks>
public class StreamServerTransport : TransportBase
{
    private static readonly byte[] s_newlineBytes = "\n"u8.ToArray();

    private readonly ILogger _logger;

    private readonly TextReader _inputReader;
    private readonly Stream _outputStream;

    private readonly SemaphoreSlim _sendLock = new(1, 1);
    private readonly CancellationTokenSource _shutdownCts = new();

    private readonly Task _readLoopCompleted;
    private int _disposed = 0;

    /// <summary>
    /// Initializes a new instance of the <see cref="StreamServerTransport"/> class with explicit input/output streams.
    /// </summary>
    /// <param name="inputStream">The input <see cref="Stream"/> to use as standard input.</param>
    /// <param name="outputStream">The output <see cref="Stream"/> to use as standard output.</param>
    /// <param name="serverName">Optional name of the server, used for diagnostic purposes, like logging.</param>
    /// <param name="loggerFactory">Optional logger factory used for logging employed by the transport.</param>
    /// <exception cref="ArgumentNullException"><paramref name="inputStream"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="outputStream"/> is <see langword="null"/>.</exception>
    public StreamServerTransport(Stream inputStream, Stream outputStream, string? serverName = null, ILoggerFactory? loggerFactory = null)
        : base(serverName is not null ? $"Server (stream) ({serverName})" : "Server (stream)", loggerFactory)
    {
        Throw.IfNull(inputStream);
        Throw.IfNull(outputStream);

        _logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance;

#if NET
        _inputReader = new StreamReader(inputStream, Encoding.UTF8);
#else
        _inputReader = new CancellableStreamReader(inputStream, Encoding.UTF8);
#endif
        _outputStream = outputStream;

        SetConnected();
        _readLoopCompleted = Task.Run(ReadMessagesAsync, _shutdownCts.Token);
    }

    /// <inheritdoc/>
    public override async Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
    {
        if (!IsConnected)
        {
            return;
        }

        using var _ = await _sendLock.LockAsync(cancellationToken).ConfigureAwait(false);

        string id = "(no id)";
        if (message is JsonRpcMessageWithId messageWithId)
        {
            id = messageWithId.Id.ToString();
        }

        try
        {
            await JsonSerializer.SerializeAsync(_outputStream, message, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonRpcMessage)), cancellationToken).ConfigureAwait(false);
            await _outputStream.WriteAsync(s_newlineBytes, cancellationToken).ConfigureAwait(false);
            await _outputStream.FlushAsync(cancellationToken).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            LogTransportSendFailed(Name, id, ex);
            throw new IOException("Failed to send message.", ex);
        }
    }

    private async Task ReadMessagesAsync()
    {
        CancellationToken shutdownToken = _shutdownCts.Token;
        Exception? error = null;
        try
        {
            LogTransportEnteringReadMessagesLoop(Name);

            while (!shutdownToken.IsCancellationRequested)
            {
                var line = await _inputReader.ReadLineAsync(shutdownToken).ConfigureAwait(false);
                if (string.IsNullOrWhiteSpace(line))
                {
                    if (line is null)
                    {
                        LogTransportEndOfStream(Name);
                        break;
                    }

                    continue;
                }

                LogTransportReceivedMessageSensitive(Name, line);

                try
                {
                    if (JsonSerializer.Deserialize(line, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonRpcMessage))) is JsonRpcMessage message)
                    {
                        await WriteMessageAsync(message, shutdownToken).ConfigureAwait(false);
                    }
                    else
                    {
                        LogTransportMessageParseUnexpectedTypeSensitive(Name, line);
                    }
                }
                catch (JsonException ex)
                {
                    if (Logger.IsEnabled(LogLevel.Trace))
                    {
                        LogTransportMessageParseFailedSensitive(Name, line, ex);
                    }
                    else
                    {
                        LogTransportMessageParseFailed(Name, ex);
                    }

                    // Continue reading even if we fail to parse a message
                }
            }
        }
        catch (OperationCanceledException)
        {
            LogTransportReadMessagesCancelled(Name);
        }
        catch (Exception ex)
        {
            LogTransportReadMessagesFailed(Name, ex);
            error = ex;
        }
        finally
        {
            SetDisconnected(error);
        }
    }

    /// <inheritdoc />
    public override async ValueTask DisposeAsync()
    {
        if (Interlocked.Exchange(ref _disposed, 1) != 0)
        {
            return;
        }

        try
        {
            LogTransportShuttingDown(Name);

            // Signal to the stdin reading loop to stop.
            await _shutdownCts.CancelAsync().ConfigureAwait(false);
            _shutdownCts.Dispose();

            // Dispose of stdin/out. Cancellation may not be able to wake up operations
            // synchronously blocked in a syscall; we need to forcefully close the handle / file descriptor.
            _inputReader?.Dispose();
            _outputStream?.Dispose();

            // Make sure the work has quiesced.
            try
            {
                await _readLoopCompleted.ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
            }
            catch (Exception ex)
            {
                LogTransportCleanupReadTaskFailed(Name, ex);
            }
        }
        finally
        {
            SetDisconnected();
            LogTransportShutDown(Name);
        }

        GC.SuppressFinalize(this);
    }
}

using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol.Protocol;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;

namespace ModelContextProtocol.Server;

/// Provides an <see cref="McpServerTool"/> that's implemented via an <see cref="AIFunction"/>.
internal sealed partial class AIFunctionMcpServerTool : McpServerTool
{
    private readonly ILogger _logger;
    private readonly bool _structuredOutputRequiresWrapping;

    ///
    /// Creates an <see cref="McpServerTool"/> instance for a method, specified via a <see cref="Delegate"/> instance.
    ///
    public static new AIFunctionMcpServerTool Create(
        Delegate method,
        McpServerToolCreateOptions? options)
    {
        Throw.IfNull(method);

        options = DeriveOptions(method.Method, options);

        return Create(method.Method, method.Target, options);
    }

    ///
    /// Creates an <see cref="McpServerTool"/> instance for a method, specified via a <see cref="MethodInfo"/> instance.
    ///
    public static new AIFunctionMcpServerTool Create(
        MethodInfo method,
        object? target,
        McpServerToolCreateOptions? options)
    {
        Throw.IfNull(method);

        options = DeriveOptions(method, options);

        return Create(
            AIFunctionFactory.Create(method, target, CreateAIFunctionFactoryOptions(method, options)),
            options);
    }

    ///
    /// Creates an <see cref="McpServerTool"/> instance for a method, specified via a <see cref="MethodInfo"/> instance.
    ///
    public static new AIFunctionMcpServerTool Create(
        MethodInfo method,
        Func<RequestContext<CallToolRequestParams>, object> createTargetFunc,
        McpServerToolCreateOptions? options)
    {
        Throw.IfNull(method);
        Throw.IfNull(createTargetFunc);

        options = DeriveOptions(method, options);

        return Create(
            AIFunctionFactory.Create(method, args =>
            {
                Debug.Assert(args.Services is RequestServiceProvider<CallToolRequestParams>, $"The service provider should be a {nameof(RequestServiceProvider<CallToolRequestParams>)} for this method to work correctly.");
                return createTargetFunc(((RequestServiceProvider<CallToolRequestParams>)args.Services!).Request);
            }, CreateAIFunctionFactoryOptions(method, options)),
            options);
    }

    private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
        MethodInfo method, McpServerToolCreateOptions? options) =>
        new()
        {
            Name = options?.Name ?? method.GetCustomAttribute<McpServerToolAttribute>()?.Name ?? DeriveName(method),
            Description = options?.Description,
            MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
            SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
            JsonSchemaCreateOptions = options?.SchemaCreateOptions,
            ConfigureParameterBinding = pi =>
            {
                if (RequestServiceProvider<CallToolRequestParams>.IsAugmentedWith(pi.ParameterType) ||
                    (options?.Services?.GetService<IServiceProviderIsService>() is { } ispis &&
                     ispis.IsService(pi.ParameterType)))
                {
                    return new()
                    {
                        ExcludeFromSchema = true,
                        BindParameter = (pi, args) =>
                            args.Services?.GetService(pi.ParameterType) ??
                            (pi.HasDefaultValue ? null :
                             throw new ArgumentException("No service of the requested type was found.")),
                    };
                }

                if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
                {
                    return new()
                    {
                        ExcludeFromSchema = true,
                        BindParameter = (pi, args) =>
                            (args?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
                            (pi.HasDefaultValue ? null :
                             throw new ArgumentException("No service of the requested type was found.")),
                    };
                }

                return default;
            },
        };

    /// Creates an <see cref="McpServerTool"/> that wraps the specified <see cref="AIFunction"/>.
    public static new AIFunctionMcpServerTool Create(AIFunction function, McpServerToolCreateOptions? options)
    {
        Throw.IfNull(function);

        Tool tool = new()
        {
            Name = options?.Name ?? function.Name,
            Description = options?.Description ?? function.Description,
            InputSchema = function.JsonSchema,
            OutputSchema = CreateOutputSchema(function, options, out bool structuredOutputRequiresWrapping),
        };

        if (options is not null)
        {
            if (options.Title is not null ||
                options.Idempotent is not null ||
                options.Destructive is not null ||
                options.OpenWorld is not null ||
                options.ReadOnly is not null)
            {
                tool.Title = options.Title;

                tool.Annotations = new()
                {
                    Title = options.Title,
                    IdempotentHint = options.Idempotent,
                    DestructiveHint = options.Destructive,
                    OpenWorldHint = options.OpenWorld,
                    ReadOnlyHint = options.ReadOnly,
                };
            }
        }

        return new AIFunctionMcpServerTool(function, tool, options?.Services, structuredOutputRequiresWrapping);
    }

    private static McpServerToolCreateOptions DeriveOptions(MethodInfo method, McpServerToolCreateOptions? options)
    {
        McpServerToolCreateOptions newOptions = options?.Clone() ?? new();

        if (method.GetCustomAttribute<McpServerToolAttribute>() is { } toolAttr)
        {
            newOptions.Name ??= toolAttr.Name;
            newOptions.Title ??= toolAttr.Title;

            if (toolAttr._destructive is bool destructive)
            {
                newOptions.Destructive ??= destructive;
            }

            if (toolAttr._idempotent is bool idempotent)
            {
                newOptions.Idempotent ??= idempotent;
            }

            if (toolAttr._openWorld is bool openWorld)
            {
                newOptions.OpenWorld ??= openWorld;
            }

            if (toolAttr._readOnly is bool readOnly)
            {
                newOptions.ReadOnly ??= readOnly;
            }

            newOptions.UseStructuredContent = toolAttr.UseStructuredContent;
        }

        if (method.GetCustomAttribute<DescriptionAttribute>() is { } descAttr)
        {
            newOptions.Description ??= descAttr.Description;
        }

        return newOptions;
    }

    /// Gets the <see cref="AIFunction"/> wrapped by this tool.
    internal AIFunction AIFunction { get; }

    /// Initializes a new instance of the <see cref="McpServerTool"/> class.
    private AIFunctionMcpServerTool(AIFunction function, Tool tool, IServiceProvider? serviceProvider, bool structuredOutputRequiresWrapping)
    {
        AIFunction = function;
        ProtocolTool = tool;
        _logger = serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger<McpServerTool>() ?? (ILogger)NullLogger.Instance;
        _structuredOutputRequiresWrapping = structuredOutputRequiresWrapping;
    }

    /// <inheritdoc />
    public override Tool ProtocolTool { get; }

    /// <inheritdoc />
    public override async ValueTask<CallToolResult> InvokeAsync(
        RequestContext<CallToolRequestParams> request, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(request);
        cancellationToken.ThrowIfCancellationRequested();

        request.Services = new RequestServiceProvider<CallToolRequestParams>(request, request.Services);
        AIFunctionArguments arguments = new() { Services = request.Services };

        if (request.Params?.Arguments is { } argDict)
        {
            foreach (var kvp in argDict)
            {
                arguments[kvp.Key] = kvp.Value;
            }
        }

        object? result;
        try
        {
            result = await AIFunction.InvokeAsync(arguments, cancellationToken).ConfigureAwait(false);
        }
        catch (Exception e) when (e is not OperationCanceledException)
        {
            ToolCallError(request.Params?.Name ?? string.Empty, e);

            string errorMessage = e is McpException ?
                $"An error occurred invoking '{request.Params?.Name}': {e.Message}" :
                $"An error occurred invoking '{request.Params?.Name}'.";

            return new()
            {
                IsError = true,
                Content = [new TextContentBlock { Text = errorMessage }],
            };
        }

        JsonNode? structuredContent = CreateStructuredResponse(result);
        return result switch
        {
            AIContent aiContent => new()
            {
                Content = [aiContent.ToContent()],
                StructuredContent = structuredContent,
                IsError = aiContent is ErrorContent
            },

            null => new()
            {
                Content = [],
                StructuredContent = structuredContent,
            },

            string text => new()
            {
                Content = [new TextContentBlock { Text = text }],
                StructuredContent = structuredContent,
            },

            ContentBlock content => new()
            {
                Content = [content],
                StructuredContent = structuredContent,
            },

            IEnumerable<string> texts => new()
            {
                Content = [.. texts.Select(x => new TextContentBlock { Text = x ?? string.Empty })],
                StructuredContent = structuredContent,
            },

            IEnumerable<AIContent> contentItems => ConvertAIContentEnumerableToCallToolResult(contentItems, structuredContent),

            IEnumerable<ContentBlock> contents => new()
            {
                Content = [.. contents],
                StructuredContent = structuredContent,
            },

            CallToolResult callToolResponse => callToolResponse,

            _ => new()
            {
                Content = [new TextContentBlock { Text = JsonSerializer.Serialize(result, AIFunction.JsonSerializerOptions.GetTypeInfo(typeof(object))) }],
                StructuredContent = structuredContent,
            },
        };
    }

    /// Creates a name to use based on the supplied method and naming policy.
    internal static string DeriveName(MethodInfo method, JsonNamingPolicy? policy = null)
    {
        string name = method.Name;

        // Remove any "Async" suffix if the method is an async method and if the method name isn't just "Async".
        const string AsyncSuffix = "Async";
        if (IsAsyncMethod(method) &&
            name.EndsWith(AsyncSuffix, StringComparison.Ordinal) &&
            name.Length > AsyncSuffix.Length)
        {
            name = name.Substring(0, name.Length - AsyncSuffix.Length);
        }

        // Replace anything other than ASCII letters or digits with underscores, trim off any leading or trailing underscores.
        name = NonAsciiLetterDigitsRegex().Replace(name, "_").Trim('_');

        // If after all our transformations the name is empty, just use the original method name.
        if (name.Length == 0)
        {
            name = method.Name;
        }

        // Case the name based on the provided naming policy.
        return (policy ?? JsonNamingPolicy.SnakeCaseLower).ConvertName(name) ?? name;

        static bool IsAsyncMethod(MethodInfo method)
        {
            Type t = method.ReturnType;

            if (t == typeof(Task) || t == typeof(ValueTask))
            {
                return true;
            }

            if (t.IsGenericType)
            {
                t = t.GetGenericTypeDefinition();
                if (t == typeof(Task<>) || t == typeof(ValueTask<>) || t == typeof(IAsyncEnumerable<>))
                {
                    return true;
                }
            }

            return false;
        }
    }

    /// Regex that flags runs of characters other than ASCII digits or letters.
#if NET
    [GeneratedRegex("[^0-9A-Za-z]+")]
    private static partial Regex NonAsciiLetterDigitsRegex();
#else
    private static Regex NonAsciiLetterDigitsRegex() => _nonAsciiLetterDigits;
    private static readonly Regex _nonAsciiLetterDigits = new("[^0-9A-Za-z]+", RegexOptions.Compiled);
#endif

    private static JsonElement? CreateOutputSchema(AIFunction function, McpServerToolCreateOptions? toolCreateOptions, out bool structuredOutputRequiresWrapping)
    {
        structuredOutputRequiresWrapping = false;

        if (toolCreateOptions?.UseStructuredContent is not true)
        {
            return null;
        }

        if (function.ReturnJsonSchema is not JsonElement outputSchema)
        {
            return null;
        }

        if (outputSchema.ValueKind is not JsonValueKind.Object ||
            !outputSchema.TryGetProperty("type", out JsonElement typeProperty) ||
            typeProperty.ValueKind is not JsonValueKind.String ||
            typeProperty.GetString() is not "object")
        {
            // If the output schema is not an object, need to modify to be a valid MCP output schema.
            JsonNode? schemaNode = JsonSerializer.SerializeToNode(outputSchema, McpJsonUtilities.JsonContext.Default.JsonElement);

            if (schemaNode is JsonObject objSchema &&
                objSchema.TryGetPropertyValue("type", out JsonNode? typeNode) &&
                typeNode is JsonArray { Count: 2 } typeArray && typeArray.Any(type => (string?)type is "object") && typeArray.Any(type => (string?)type is "null"))
            {
                // For schemas that are of type ["object", "null"], replace with just "object" to be conformant.
                objSchema["type"] = "object";
            }
            else
            {
                // For anything else, wrap the schema in an envelope with a "result" property.
                schemaNode = new JsonObject
                {
                    ["type"] = "object",
                    ["properties"] = new JsonObject
                    {
                        ["result"] = schemaNode
                    },
                    ["required"] = new JsonArray { (JsonNode)"result" }
                };

                structuredOutputRequiresWrapping = true;
            }

            outputSchema = JsonSerializer.Deserialize(schemaNode, McpJsonUtilities.JsonContext.Default.JsonElement);
        }

        return outputSchema;
    }

    private JsonNode? CreateStructuredResponse(object? aiFunctionResult)
    {
        if (ProtocolTool.OutputSchema is null)
        {
            // Only provide structured responses if the tool has an output schema defined.
            return null;
        }

        JsonNode? nodeResult = aiFunctionResult switch
        {
            JsonNode node => node,
            JsonElement jsonElement => JsonSerializer.SerializeToNode(jsonElement, McpJsonUtilities.JsonContext.Default.JsonElement),
            _ => JsonSerializer.SerializeToNode(aiFunctionResult, AIFunction.JsonSerializerOptions.GetTypeInfo(typeof(object))),
        };

        if (_structuredOutputRequiresWrapping)
        {
            return new JsonObject
            {
                ["result"] = nodeResult
            };
        }

        return nodeResult;
    }

    private static CallToolResult ConvertAIContentEnumerableToCallToolResult(IEnumerable<AIContent> contentItems, JsonNode? structuredContent)
    {
        List<ContentBlock> contentList = [];
        bool allErrorContent = true;
        bool hasAny = false;

        foreach (var item in contentItems)
        {
            contentList.Add(item.ToContent());
            hasAny = true;

            if (allErrorContent && item is not ErrorContent)
            {
                allErrorContent = false;
            }
        }

        return new()
        {
            Content = contentList,
            StructuredContent = structuredContent,
            IsError = allErrorContent && hasAny
        };
    }

    [LoggerMessage(Level = LogLevel.Error, Message = "\"{ToolName}\" threw an unhandled exception.")]
    private partial void ToolCallError(string toolName, Exception exception);
}

using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ModelContextProtocol;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.Json;

namespace Microsoft.Extensions.DependencyInjection;

///
/// Provides methods for configuring MCP servers via dependency injection.
///
public static partial class McpServerBuilderExtensions
{
    #region WithTools
    private const string WithToolsRequiresUnreferencedCodeMessage =
        $"The non-generic {nameof(WithTools)} and {nameof(WithToolsFromAssembly)} methods require dynamic lookup of method metadata" +
        $"and may not work in Native AOT. Use the generic {nameof(WithTools)} method instead.";

    /// Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.
    /// <typeparam name="TToolType">The tool type.</typeparam>
    /// <param name="builder">The builder instance.</param>
    /// <param name="serializerOptions">The serializer options governing tool parameter marshalling.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// This method discovers all instance and static methods (public and non-public) on the specified <typeparamref name="TToolType"/>
    /// type, where the methods are attributed as <see cref="McpServerToolAttribute"/>, and adds an <see cref="McpServerTool"/>
    /// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
    /// </remarks>
    public static IMcpServerBuilder WithTools<[DynamicallyAccessedMembers(
        DynamicallyAccessedMemberTypes.PublicMethods |
        DynamicallyAccessedMemberTypes.NonPublicMethods |
        DynamicallyAccessedMemberTypes.PublicConstructors)] TToolType>(
        this IMcpServerBuilder builder,
        JsonSerializerOptions? serializerOptions = null)
    {
        Throw.IfNull(builder);

        foreach (var toolMethod in typeof(TToolType).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
        {
            if (toolMethod.GetCustomAttribute<McpServerToolAttribute>() is not null)
            {
                builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
                    services => McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = serializerOptions }) :
                    services => McpServerTool.Create(toolMethod, static r => CreateTarget(r.Services, typeof(TToolType)), new() { Services = services, SerializerOptions = serializerOptions })));
            }
        }

        return builder;
    }

    /// Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.
    /// <param name="builder">The builder instance.</param>
    /// <param name="tools">The <see cref="McpServerTool"/> instances to add to the server.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="tools"/> is <see langword="null"/>.</exception>
    public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable<McpServerTool> tools)
    {
        Throw.IfNull(builder);
        Throw.IfNull(tools);

        foreach (var tool in tools)
        {
            if (tool is not null)
            {
                builder.Services.AddSingleton(tool);
            }
        }

        return builder;
    }

    /// Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.
    /// <param name="builder">The builder instance.</param>
    /// <param name="toolTypes">Types with <see cref="McpServerToolAttribute"/>-attributed methods to add as tools to the server.</param>
    /// <param name="serializerOptions">The serializer options governing tool parameter marshalling.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="toolTypes"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// This method discovers all instance and static methods (public and non-public) on the specified <paramref name="toolTypes"/>
    /// types, where the methods are attributed as <see cref="McpServerToolAttribute"/>, and adds an <see cref="McpServerTool"/>
    /// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
    /// </remarks>
    [RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
    public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable<Type> toolTypes, JsonSerializerOptions? serializerOptions = null)
    {
        Throw.IfNull(builder);
        Throw.IfNull(toolTypes);

        foreach (var toolType in toolTypes)
        {
            if (toolType is not null)
            {
                foreach (var toolMethod in toolType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
                {
                    if (toolMethod.GetCustomAttribute<McpServerToolAttribute>() is not null)
                    {
                        builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
                            services => McpServerTool.Create(toolMethod, options: new() { Services = services , SerializerOptions = serializerOptions }) :
                            services => McpServerTool.Create(toolMethod, r => CreateTarget(r.Services, toolType), new() { Services = services , SerializerOptions = serializerOptions })));
                    }
                }
            }
        }

        return builder;
    }

    ///
    /// Adds types marked with the <see cref="McpServerToolTypeAttribute"/> attribute from the given assembly as tools to the server.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="serializerOptions">The serializer options governing tool parameter marshalling.</param>
    /// <param name="toolAssembly">The assembly to load the types from. If <see langword="null"/>, the calling assembly will be used.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// This method scans the specified assembly (or the calling assembly if none is provided) for classes
    /// marked with the <see cref="McpServerToolTypeAttribute"/>. It then discovers all methods within those
    /// classes that are marked with the <see cref="McpServerToolAttribute"/> and registers them as <see cref="McpServerTool"/>s
    /// in the <paramref name="builder"/>'s <see cref="IServiceCollection"/>.
    /// </para>
    /// <para>
    /// The method automatically handles both static and instance methods. For instance methods, a new instance
    /// of the containing class will be constructed for each invocation of the tool.
    /// </para>
    /// <para>
    /// Tools registered through this method can be discovered by clients using the <c>list_tools</c> request
    /// and invoked using the <c>call_tool</c> request.
    /// </para>
    /// <para>
    /// Note that this method performs reflection at runtime and may not work in Native AOT scenarios. For
    /// Native AOT compatibility, consider using the generic <see cref="WithTools{TToolType}"/> method instead.
    /// </para>
    /// </remarks>
    [RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
    public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null, JsonSerializerOptions? serializerOptions = null)
    {
        Throw.IfNull(builder);

        toolAssembly ??= Assembly.GetCallingAssembly();

        return builder.WithTools(
            from t in toolAssembly.GetTypes()
            where t.GetCustomAttribute<McpServerToolTypeAttribute>() is not null
            select t,
            serializerOptions);
    }
    #endregion

    #region WithPrompts
    private const string WithPromptsRequiresUnreferencedCodeMessage =
        $"The non-generic {nameof(WithPrompts)} and {nameof(WithPromptsFromAssembly)} methods require dynamic lookup of method metadata" +
        $"and may not work in Native AOT. Use the generic {nameof(WithPrompts)} method instead.";

    /// Adds <see cref="McpServerPrompt"/> instances to the service collection backing <paramref name="builder"/>.
    /// <typeparam name="TPromptType">The prompt type.</typeparam>
    /// <param name="builder">The builder instance.</param>
    /// <param name="serializerOptions">The serializer options governing prompt parameter marshalling.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// This method discovers all instance and static methods (public and non-public) on the specified <typeparamref name="TPromptType"/>
    /// type, where the methods are attributed as <see cref="McpServerPromptAttribute"/>, and adds an <see cref="McpServerPrompt"/>
    /// instance for each. For instance methods, an instance will be constructed for each invocation of the prompt.
    /// </remarks>
    public static IMcpServerBuilder WithPrompts<[DynamicallyAccessedMembers(
        DynamicallyAccessedMemberTypes.PublicMethods |
        DynamicallyAccessedMemberTypes.NonPublicMethods |
        DynamicallyAccessedMemberTypes.PublicConstructors)] TPromptType>(
        this IMcpServerBuilder builder,
        JsonSerializerOptions? serializerOptions = null)
    {
        Throw.IfNull(builder);

        foreach (var promptMethod in typeof(TPromptType).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
        {
            if (promptMethod.GetCustomAttribute<McpServerPromptAttribute>() is not null)
            {
                builder.Services.AddSingleton((Func<IServiceProvider, McpServerPrompt>)(promptMethod.IsStatic ?
                    services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions }) :
                    services => McpServerPrompt.Create(promptMethod, static r => CreateTarget(r.Services, typeof(TPromptType)), new() { Services = services, SerializerOptions = serializerOptions })));
            }
        }

        return builder;
    }

    /// Adds <see cref="McpServerPrompt"/> instances to the service collection backing <paramref name="builder"/>.
    /// <param name="builder">The builder instance.</param>
    /// <param name="prompts">The <see cref="McpServerPrompt"/> instances to add to the server.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="prompts"/> is <see langword="null"/>.</exception>
    public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnumerable<McpServerPrompt> prompts)
    {
        Throw.IfNull(builder);
        Throw.IfNull(prompts);

        foreach (var prompt in prompts)
        {
            if (prompt is not null)
            {
                builder.Services.AddSingleton(prompt);
            }
        }

        return builder;
    }

    /// Adds <see cref="McpServerPrompt"/> instances to the service collection backing <paramref name="builder"/>.
    /// <param name="builder">The builder instance.</param>
    /// <param name="promptTypes">Types with marked methods to add as prompts to the server.</param>
    /// <param name="serializerOptions">The serializer options governing prompt parameter marshalling.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="promptTypes"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// This method discovers all instance and static methods (public and non-public) on the specified <paramref name="promptTypes"/>
    /// types, where the methods are attributed as <see cref="McpServerPromptAttribute"/>, and adds an <see cref="McpServerPrompt"/>
    /// instance for each. For instance methods, an instance will be constructed for each invocation of the prompt.
    /// </remarks>
    [RequiresUnreferencedCode(WithPromptsRequiresUnreferencedCodeMessage)]
    public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnumerable<Type> promptTypes, JsonSerializerOptions? serializerOptions = null)
    {
        Throw.IfNull(builder);
        Throw.IfNull(promptTypes);

        foreach (var promptType in promptTypes)
        {
            if (promptType is not null)
            {
                foreach (var promptMethod in promptType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
                {
                    if (promptMethod.GetCustomAttribute<McpServerPromptAttribute>() is not null)
                    {
                        builder.Services.AddSingleton((Func<IServiceProvider, McpServerPrompt>)(promptMethod.IsStatic ?
                            services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions }) :
                            services => McpServerPrompt.Create(promptMethod, r => CreateTarget(r.Services, promptType), new() { Services = services, SerializerOptions = serializerOptions })));
                    }
                }
            }
        }

        return builder;
    }

    ///
    /// Adds types marked with the <see cref="McpServerPromptTypeAttribute"/> attribute from the given assembly as prompts to the server.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="serializerOptions">The serializer options governing prompt parameter marshalling.</param>
    /// <param name="promptAssembly">The assembly to load the types from. If <see langword="null"/>, the calling assembly will be used.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// This method scans the specified assembly (or the calling assembly if none is provided) for classes
    /// marked with the <see cref="McpServerPromptTypeAttribute"/>. It then discovers all methods within those
    /// classes that are marked with the <see cref="McpServerPromptAttribute"/> and registers them as <see cref="McpServerPrompt"/>s
    /// in the <paramref name="builder"/>'s <see cref="IServiceCollection"/>.
    /// </para>
    /// <para>
    /// The method automatically handles both static and instance methods. For instance methods, a new instance
    /// of the containing class will be constructed for each invocation of the prompt.
    /// </para>
    /// <para>
    /// Prompts registered through this method can be discovered by clients using the <c>list_prompts</c> request
    /// and invoked using the <c>call_prompt</c> request.
    /// </para>
    /// <para>
    /// Note that this method performs reflection at runtime and may not work in Native AOT scenarios. For
    /// Native AOT compatibility, consider using the generic <see cref="WithPrompts{TPromptType}"/> method instead.
    /// </para>
    /// </remarks>
    [RequiresUnreferencedCode(WithPromptsRequiresUnreferencedCodeMessage)]
    public static IMcpServerBuilder WithPromptsFromAssembly(this IMcpServerBuilder builder, Assembly? promptAssembly = null, JsonSerializerOptions? serializerOptions = null)
    {
        Throw.IfNull(builder);

        promptAssembly ??= Assembly.GetCallingAssembly();

        return builder.WithPrompts(
            from t in promptAssembly.GetTypes()
            where t.GetCustomAttribute<McpServerPromptTypeAttribute>() is not null
            select t,
            serializerOptions);
    }
    #endregion

    #region WithResources
    private const string WithResourcesRequiresUnreferencedCodeMessage =
        $"The non-generic {nameof(WithResources)} and {nameof(WithResourcesFromAssembly)} methods require dynamic lookup of member metadata" +
        $"and may not work in Native AOT. Use the generic {nameof(WithResources)} method instead.";

    /// Adds <see cref="McpServerResource"/> instances to the service collection backing <paramref name="builder"/>.
    /// <typeparam name="TResourceType">The resource type.</typeparam>
    /// <param name="builder">The builder instance.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// This method discovers all instance and static methods (public and non-public) on the specified <typeparamref name="TResourceType"/>
    /// type, where the members are attributed as <see cref="McpServerResourceAttribute"/>, and adds an <see cref="McpServerResource"/>
    /// instance for each. For instance members, an instance will be constructed for each invocation of the resource.
    /// </remarks>
    public static IMcpServerBuilder WithResources<[DynamicallyAccessedMembers(
        DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods |
        DynamicallyAccessedMemberTypes.PublicConstructors)] TResourceType>(
        this IMcpServerBuilder builder)
    {
        Throw.IfNull(builder);

        foreach (var resourceTemplateMethod in typeof(TResourceType).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
        {
            if (resourceTemplateMethod.GetCustomAttribute<McpServerResourceAttribute>() is not null)
            {
                builder.Services.AddSingleton((Func<IServiceProvider, McpServerResource>)(resourceTemplateMethod.IsStatic ?
                    services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services }) :
                    services => McpServerResource.Create(resourceTemplateMethod, static r => CreateTarget(r.Services, typeof(TResourceType)), new() { Services = services })));
            }
        }

        return builder;
    }

    /// Adds <see cref="McpServerResource"/> instances to the service collection backing <paramref name="builder"/>.
    /// <param name="builder">The builder instance.</param>
    /// <param name="resourceTemplates">The <see cref="McpServerResource"/> instances to add to the server.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="resourceTemplates"/> is <see langword="null"/>.</exception>
    public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IEnumerable<McpServerResource> resourceTemplates)
    {
        Throw.IfNull(builder);
        Throw.IfNull(resourceTemplates);

        foreach (var resourceTemplate in resourceTemplates)
        {
            if (resourceTemplate is not null)
            {
                builder.Services.AddSingleton(resourceTemplate);
            }
        }

        return builder;
    }

    /// Adds <see cref="McpServerResource"/> instances to the service collection backing <paramref name="builder"/>.
    /// <param name="builder">The builder instance.</param>
    /// <param name="resourceTemplateTypes">Types with marked methods to add as resources to the server.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="resourceTemplateTypes"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// This method discovers all instance and static methods (public and non-public) on the specified <paramref name="resourceTemplateTypes"/>
    /// types, where the methods are attributed as <see cref="McpServerResourceAttribute"/>, and adds an <see cref="McpServerResource"/>
    /// instance for each. For instance methods, an instance will be constructed for each invocation of the resource.
    /// </remarks>
    [RequiresUnreferencedCode(WithResourcesRequiresUnreferencedCodeMessage)]
    public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IEnumerable<Type> resourceTemplateTypes)
    {
        Throw.IfNull(builder);
        Throw.IfNull(resourceTemplateTypes);

        foreach (var resourceTemplateType in resourceTemplateTypes)
        {
            if (resourceTemplateType is not null)
            {
                foreach (var resourceTemplateMethod in resourceTemplateType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
                {
                    if (resourceTemplateMethod.GetCustomAttribute<McpServerResourceAttribute>() is not null)
                    {
                        builder.Services.AddSingleton((Func<IServiceProvider, McpServerResource>)(resourceTemplateMethod.IsStatic ?
                            services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services }) :
                            services => McpServerResource.Create(resourceTemplateMethod, r => CreateTarget(r.Services, resourceTemplateType), new() { Services = services })));
                    }
                }
            }
        }

        return builder;
    }

    ///
    /// Adds types marked with the <see cref="McpServerResourceTypeAttribute"/> attribute from the given assembly as resources to the server.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="resourceAssembly">The assembly to load the types from. If <see langword="null"/>, the calling assembly will be used.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// This method scans the specified assembly (or the calling assembly if none is provided) for classes
    /// marked with the <see cref="McpServerResourceTypeAttribute"/>. It then discovers all members within those
    /// classes that are marked with the <see cref="McpServerResourceAttribute"/> and registers them as <see cref="McpServerResource"/>s
    /// in the <paramref name="builder"/>'s <see cref="IServiceCollection"/>.
    /// </para>
    /// <para>
    /// The method automatically handles both static and instance members. For instance members, a new instance
    /// of the containing class will be constructed for each invocation of the resource.
    /// </para>
    /// <para>
    /// Resource templates registered through this method can be discovered by clients using the <c>list_resourceTemplates</c> request
    /// and invoked using the <c>read_resource</c> request.
    /// </para>
    /// <para>
    /// Note that this method performs reflection at runtime and may not work in Native AOT scenarios. For
    /// Native AOT compatibility, consider using the generic <see cref="WithResources{TResourceType}"/> method instead.
    /// </para>
    /// </remarks>
    [RequiresUnreferencedCode(WithResourcesRequiresUnreferencedCodeMessage)]
    public static IMcpServerBuilder WithResourcesFromAssembly(this IMcpServerBuilder builder, Assembly? resourceAssembly = null)
    {
        Throw.IfNull(builder);

        resourceAssembly ??= Assembly.GetCallingAssembly();

        return builder.WithResources(
            from t in resourceAssembly.GetTypes()
            where t.GetCustomAttribute<McpServerResourceTypeAttribute>() is not null
            select t);
    }
    #endregion

    #region Handlers
    ///
    /// Configures a handler for listing resource templates available from the Model Context Protocol server.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="handler">The handler function that processes resource template list requests.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// This handler is responsible for providing clients with information about available resource templates
    /// that can be used to construct resource URIs.
    /// </para>
    /// <para>
    /// Resource templates describe the structure of resource URIs that the server can handle. They include
    /// URI templates (according to RFC 6570) that clients can use to construct valid resource URIs.
    /// </para>
    /// <para>
    /// This handler is typically paired with <see cref="WithReadResourceHandler"/> to provide a complete
    /// resource system where templates define the URI patterns and the read handler provides the actual content.
    /// </para>
    /// </remarks>
    public static IMcpServerBuilder WithListResourceTemplatesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, ValueTask<ListResourceTemplatesResult>> handler)
    {
        Throw.IfNull(builder);

        builder.Services.Configure<McpServerHandlers>(s => s.ListResourceTemplatesHandler = handler);
        return builder;
    }

    ///
    /// Configures a handler for listing tools available from the Model Context Protocol server.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="handler">The handler that processes list tools requests.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// This handler is called when a client requests a list of available tools. It should return all tools
    /// that can be invoked through the server, including their names, descriptions, and parameter specifications.
    /// The handler can optionally support pagination via the cursor mechanism for large or dynamically-generated
    /// tool collections.
    /// </para>
    /// <para>
    /// When tools are also defined using <see cref="McpServerTool"/> collection, both sets of tools
    /// will be combined in the response to clients. This allows for a mix of programmatically defined
    /// tools and dynamically generated tools.
    /// </para>
    /// <para>
    /// This method is typically paired with <see cref="WithCallToolHandler"/> to provide a complete tools implementation,
    /// where <see cref="WithListToolsHandler"/> advertises available tools and <see cref="WithCallToolHandler"/>
    /// executes them when invoked by clients.
    /// </para>
    /// </remarks>
    public static IMcpServerBuilder WithListToolsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListToolsRequestParams>, CancellationToken, ValueTask<ListToolsResult>> handler)
    {
        Throw.IfNull(builder);

        builder.Services.Configure<McpServerHandlers>(s => s.ListToolsHandler = handler);
        return builder;
    }

    ///
    /// Configures a handler for calling tools available from the Model Context Protocol server.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="handler">The handler function that processes tool calls.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// The call tool handler is responsible for executing custom tools and returning their results to clients.
    /// This method is typically paired with <see cref="WithListToolsHandler"/> to provide a complete tools implementation,
    /// where <see cref="WithListToolsHandler"/> advertises available tools and this handler executes them.
    /// </remarks>
    public static IMcpServerBuilder WithCallToolHandler(this IMcpServerBuilder builder, Func<RequestContext<CallToolRequestParams>, CancellationToken, ValueTask<CallToolResult>> handler)
    {
        Throw.IfNull(builder);

        builder.Services.Configure<McpServerHandlers>(s => s.CallToolHandler = handler);
        return builder;
    }

    ///
    /// Configures a handler for listing prompts available from the Model Context Protocol server.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="handler">The handler that processes list prompts requests.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// This handler is called when a client requests a list of available prompts. It should return all prompts
    /// that can be invoked through the server, including their names, descriptions, and parameter specifications.
    /// The handler can optionally support pagination via the cursor mechanism for large or dynamically-generated
    /// prompt collections.
    /// </para>
    /// <para>
    /// When prompts are also defined using <see cref="McpServerPrompt"/> collection, both sets of prompts
    /// will be combined in the response to clients. This allows for a mix of programmatically defined
    /// prompts and dynamically generated prompts.
    /// </para>
    /// <para>
    /// This method is typically paired with <see cref="WithGetPromptHandler"/> to provide a complete prompts implementation,
    /// where <see cref="WithListPromptsHandler"/> advertises available prompts and <see cref="WithGetPromptHandler"/>
    /// produces them when invoked by clients.
    /// </para>
    /// </remarks>
    public static IMcpServerBuilder WithListPromptsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListPromptsRequestParams>, CancellationToken, ValueTask<ListPromptsResult>> handler)
    {
        Throw.IfNull(builder);

        builder.Services.Configure<McpServerHandlers>(s => s.ListPromptsHandler = handler);
        return builder;
    }

    ///
    /// Configures a handler for getting a prompt available from the Model Context Protocol server.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="handler">The handler function that processes prompt requests.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    public static IMcpServerBuilder WithGetPromptHandler(this IMcpServerBuilder builder, Func<RequestContext<GetPromptRequestParams>, CancellationToken, ValueTask<GetPromptResult>> handler)
    {
        Throw.IfNull(builder);

        builder.Services.Configure<McpServerHandlers>(s => s.GetPromptHandler = handler);
        return builder;
    }

    ///
    /// Configures a handler for listing resources available from the Model Context Protocol server.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="handler">The handler function that processes resource list requests.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// This handler is typically paired with <see cref="WithReadResourceHandler"/> to provide a complete resources implementation,
    /// where this handler advertises available resources and the read handler provides their content when requested.
    /// </para>
    /// </remarks>
    public static IMcpServerBuilder WithListResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourcesRequestParams>, CancellationToken, ValueTask<ListResourcesResult>> handler)
    {
        Throw.IfNull(builder);

        builder.Services.Configure<McpServerHandlers>(s => s.ListResourcesHandler = handler);
        return builder;
    }

    ///
    /// Configures a handler for reading a resource available from the Model Context Protocol server.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="handler">The handler function that processes resource read requests.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// This handler is typically paired with <see cref="WithListResourcesHandler"/> to provide a complete resources implementation,
    /// where the list handler advertises available resources and the read handler provides their content when requested.
    /// </remarks>
    public static IMcpServerBuilder WithReadResourceHandler(this IMcpServerBuilder builder, Func<RequestContext<ReadResourceRequestParams>, CancellationToken, ValueTask<ReadResourceResult>> handler)
    {
        Throw.IfNull(builder);

        builder.Services.Configure<McpServerHandlers>(s => s.ReadResourceHandler = handler);
        return builder;
    }

    ///
    /// Configures a handler for auto-completion suggestions for prompt arguments or resource references available from the Model Context Protocol server.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="handler">The handler function that processes completion requests.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// The completion handler is invoked when clients request suggestions for argument values.
    /// This enables auto-complete functionality for both prompt arguments and resource references.
    /// </remarks>
    public static IMcpServerBuilder WithCompleteHandler(this IMcpServerBuilder builder, Func<RequestContext<CompleteRequestParams>, CancellationToken, ValueTask<CompleteResult>> handler)
    {
        Throw.IfNull(builder);

        builder.Services.Configure<McpServerHandlers>(s => s.CompleteHandler = handler);
        return builder;
    }

    ///
    /// Configures a handler for resource subscription requests.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="handler">The handler function that processes resource subscription requests.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// The subscribe handler is responsible for registering client interest in specific resources. When a resource
    /// changes, the server can notify all subscribed clients about the change.
    /// </para>
    /// <para>
    /// This handler is typically paired with <see cref="WithUnsubscribeFromResourcesHandler"/> to provide a complete
    /// subscription management system. Resource subscriptions allow clients to maintain up-to-date information without
    /// needing to poll resources constantly.
    /// </para>
    /// <para>
    /// After registering a subscription, it's the server's responsibility to track which client is subscribed to which
    /// resources and to send appropriate notifications through the connection when resources change.
    /// </para>
    /// </remarks>
    public static IMcpServerBuilder WithSubscribeToResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<SubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>> handler)
    {
        Throw.IfNull(builder);

        builder.Services.Configure<McpServerHandlers>(s => s.SubscribeToResourcesHandler = handler);
        return builder;
    }

    ///
    /// Configures a handler for resource unsubscription requests.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="handler">The handler function that processes resource unsubscription requests.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// The unsubscribe handler is responsible for removing client interest in specific resources. When a client
    /// no longer needs to receive notifications about resource changes, it can send an unsubscribe request.
    /// </para>
    /// <para>
    /// This handler is typically paired with <see cref="WithSubscribeToResourcesHandler"/> to provide a complete
    /// subscription management system. The unsubscribe operation is idempotent, meaning it can be called multiple
    /// times for the same resource without causing errors, even if there is no active subscription.
    /// </para>
    /// <para>
    /// After removing a subscription, the server should stop sending notifications to the client about changes
    /// to the specified resource.
    /// </para>
    /// </remarks>
    public static IMcpServerBuilder WithUnsubscribeFromResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>> handler)
    {
        Throw.IfNull(builder);

        builder.Services.Configure<McpServerHandlers>(s => s.UnsubscribeFromResourcesHandler = handler);
        return builder;
    }

    ///
    /// Configures a handler for processing logging level change requests from clients.
    ///
    /// <param name="builder">The server builder instance.</param>
    /// <param name="handler">The handler that processes requests to change the logging level.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// When a client sends a <c>logging/setLevel</c> request, this handler will be invoked to process
    /// the requested level change. The server typically adjusts its internal logging level threshold
    /// and may begin sending log messages at or above the specified level to the client.
    /// </para>
    /// <para>
    /// Regardless of whether a handler is provided, an <see cref="IMcpServer"/> should itself handle
    /// such notifications by updating its <see cref="IMcpServer.LoggingLevel"/> property to return the
    /// most recently set level.
    /// </para>
    /// </remarks>
    public static IMcpServerBuilder WithSetLoggingLevelHandler(this IMcpServerBuilder builder, Func<RequestContext<SetLevelRequestParams>, CancellationToken, ValueTask<EmptyResult>> handler)
    {
        Throw.IfNull(builder);

        builder.Services.Configure<McpServerHandlers>(s => s.SetLoggingLevelHandler = handler);
        return builder;
    }
    #endregion

    #region Transports
    ///
    /// Adds a server transport that uses standard input (stdin) and standard output (stdout) for communication.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// This method configures the server to communicate using the standard input and output streams,
    /// which is commonly used when the Model Context Protocol server is launched locally by a client process.
    /// </para>
    /// <para>
    /// When using this transport, the server runs as a single-session service that exits when the
    /// stdin stream is closed. This makes it suitable for scenarios where the server should terminate
    /// when the parent process disconnects.
    /// </para>
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    public static IMcpServerBuilder WithStdioServerTransport(this IMcpServerBuilder builder)
    {
        Throw.IfNull(builder);

        AddSingleSessionServerDependencies(builder.Services);
        builder.Services.AddSingleton<ITransport>(sp =>
        {
            var serverOptions = sp.GetRequiredService<IOptions<McpServerOptions>>();
            var loggerFactory = sp.GetService<ILoggerFactory>();
            return new StdioServerTransport(serverOptions.Value, loggerFactory);
        });

        return builder;
    }

    ///
    /// Adds a server transport that uses the specified input and output streams for communication.
    ///
    /// <param name="builder">The builder instance.</param>
    /// <param name="inputStream">The input <see cref="Stream"/> to use as standard input.</param>
    /// <param name="outputStream">The output <see cref="Stream"/> to use as standard output.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="inputStream"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="outputStream"/> is <see langword="null"/>.</exception>
    public static IMcpServerBuilder WithStreamServerTransport(
        this IMcpServerBuilder builder,
        Stream inputStream,
        Stream outputStream)
    {
        Throw.IfNull(builder);
        Throw.IfNull(inputStream);
        Throw.IfNull(outputStream);

        AddSingleSessionServerDependencies(builder.Services);
        builder.Services.AddSingleton<ITransport>(new StreamServerTransport(inputStream, outputStream));

        return builder;
    }

    private static void AddSingleSessionServerDependencies(IServiceCollection services)
    {
        services.AddHostedService<SingleSessionMcpServerHostedService>();
        services.TryAddSingleton(services =>
        {
            ITransport serverTransport = services.GetRequiredService<ITransport>();
            IOptions<McpServerOptions> options = services.GetRequiredService<IOptions<McpServerOptions>>();
            ILoggerFactory? loggerFactory = services.GetService<ILoggerFactory>();
            return McpServerFactory.Create(serverTransport, options.Value, loggerFactory, services);
        });
    }
    #endregion

    #region Helpers
    /// Creates an instance of the target object.
    private static object CreateTarget(
        IServiceProvider? services,
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type) =>
        services is not null ? ActivatorUtilities.CreateInstance(services, type) :
        Activator.CreateInstance(type)!;
    #endregion
}

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Net.Http.Headers;
using System.Net.ServerSentEvents;
using System.Text.Json;
using ModelContextProtocol.Protocol;
using System.Threading.Channels;

namespace ModelContextProtocol.Client;

/// <summary>
/// The Streamable HTTP client transport implementation
/// </summary>
internal sealed partial class StreamableHttpClientSessionTransport : TransportBase
{
    private static readonly MediaTypeWithQualityHeaderValue s_applicationJsonMediaType = new("application/json");
    private static readonly MediaTypeWithQualityHeaderValue s_textEventStreamMediaType = new("text/event-stream");

    private readonly McpHttpClient _httpClient;
    private readonly SseClientTransportOptions _options;
    private readonly CancellationTokenSource _connectionCts;
    private readonly ILogger _logger;

    private string? _negotiatedProtocolVersion;
    private Task? _getReceiveTask;

    private readonly SemaphoreSlim _disposeLock = new(1, 1);
    private bool _disposed;

    public StreamableHttpClientSessionTransport(
        string endpointName,
        SseClientTransportOptions transportOptions,
        McpHttpClient httpClient,
        Channel<JsonRpcMessage>? messageChannel,
        ILoggerFactory? loggerFactory)
        : base(endpointName, messageChannel, loggerFactory)
    {
        Throw.IfNull(transportOptions);
        Throw.IfNull(httpClient);

        _options = transportOptions;
        _httpClient = httpClient;
        _connectionCts = new CancellationTokenSource();
        _logger = (ILogger?)loggerFactory?.CreateLogger<SseClientTransport>() ?? NullLogger.Instance;

        // We connect with the initialization request with the MCP transport. This means that any errors won't be observed
        // until the first call to SendMessageAsync. Fortunately, that happens internally in McpClientFactory.ConnectAsync
        // so we still throw any connection-related Exceptions from there and never expose a pre-connected client to the user.
        SetConnected();
    }

    /// <inheritdoc/>
    public override async Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
    {
        // Immediately dispose the response. SendHttpRequestAsync only returns the response so the auto transport can look at it.
        using var response = await SendHttpRequestAsync(message, cancellationToken).ConfigureAwait(false);
        response.EnsureSuccessStatusCode();
    }

    // This is used by the auto transport so it can fall back and try SSE given a non-200 response without catching an exception.
    internal async Task<HttpResponseMessage> SendHttpRequestAsync(JsonRpcMessage message, CancellationToken cancellationToken)
    {
        using var sendCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _connectionCts.Token);
        cancellationToken = sendCts.Token;

        using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, _options.Endpoint)
        {
            Headers =
            {
                Accept = { s_applicationJsonMediaType, s_textEventStreamMediaType },
            },
        };

        CopyAdditionalHeaders(httpRequestMessage.Headers, _options.AdditionalHeaders, SessionId, _negotiatedProtocolVersion);

        var response = await _httpClient.SendAsync(httpRequestMessage, message, cancellationToken).ConfigureAwait(false);

        // We'll let the caller decide whether to throw or fall back given an unsuccessful response.
        if (!response.IsSuccessStatusCode)
        {
            return response;
        }

        var rpcRequest = message as JsonRpcRequest;
        JsonRpcMessageWithId? rpcResponseOrError = null;

        if (response.Content.Headers.ContentType?.MediaType == "application/json")
        {
            var responseContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
            rpcResponseOrError = await ProcessMessageAsync(responseContent, rpcRequest, cancellationToken).ConfigureAwait(false);
        }
        else if (response.Content.Headers.ContentType?.MediaType == "text/event-stream")
        {
            using var responseBodyStream = await response.Content.ReadAsStreamAsync(cancellationToken);
            rpcResponseOrError = await ProcessSseResponseAsync(responseBodyStream, rpcRequest, cancellationToken).ConfigureAwait(false);
        }

        if (rpcRequest is null)
        {
            return response;
        }

        if (rpcResponseOrError is null)
        {
            throw new McpException($"Streamable HTTP POST response completed without a reply to request with ID: {rpcRequest.Id}");
        }

        if (rpcRequest.Method == RequestMethods.Initialize && rpcResponseOrError is JsonRpcResponse initResponse)
        {
            // We've successfully initialized! Copy session-id and protocol version, then start GET request if any.
            if (response.Headers.TryGetValues("Mcp-Session-Id", out var sessionIdValues))
            {
                SessionId = sessionIdValues.FirstOrDefault();
            }

            var initializeResult = JsonSerializer.Deserialize(initResponse.Result, McpJsonUtilities.JsonContext.Default.InitializeResult);
            _negotiatedProtocolVersion = initializeResult?.ProtocolVersion;

            _getReceiveTask = ReceiveUnsolicitedMessagesAsync();
        }

        return response;
    }

    public override async ValueTask DisposeAsync()
    {
        using var _ = await _disposeLock.LockAsync().ConfigureAwait(false);

        if (_disposed)
        {
            return;
        }
        _disposed = true;

        try
        {
            await _connectionCts.CancelAsync().ConfigureAwait(false);

            try
            {
                // Send DELETE request to terminate the session. Only send if we have a session ID, per MCP spec.
                if (!string.IsNullOrEmpty(SessionId))
                {
                    await SendDeleteRequest();
                }

                if (_getReceiveTask != null)
                {
                    await _getReceiveTask.ConfigureAwait(false);
                }
            }
            catch (OperationCanceledException)
            {
            }
            finally
            {
                _connectionCts.Dispose();
            }
        }
        finally
        {
            // If we're auto-detecting the transport and failed to connect, leave the message Channel open for the SSE transport.
            // This class isn't directly exposed to public callers, so we don't have to worry about changing the _state in this case.
            if (_options.TransportMode is not HttpTransportMode.AutoDetect || _getReceiveTask is not null)
            {
                SetDisconnected();
            }
        }
    }

    private async Task ReceiveUnsolicitedMessagesAsync()
    {
        // Send a GET request to handle any unsolicited messages not sent over a POST response.
        using var request = new HttpRequestMessage(HttpMethod.Get, _options.Endpoint);
        request.Headers.Accept.Add(s_textEventStreamMediaType);
        CopyAdditionalHeaders(request.Headers, _options.AdditionalHeaders, SessionId, _negotiatedProtocolVersion);

        using var response = await _httpClient.SendAsync(request, message: null, _connectionCts.Token).ConfigureAwait(false);

        if (!response.IsSuccessStatusCode)
        {
            // Server support for the GET request is optional. If it fails, we don't care. It just means we won't receive unsolicited messages.
            return;
        }

        using var responseStream = await response.Content.ReadAsStreamAsync(_connectionCts.Token).ConfigureAwait(false);
        await ProcessSseResponseAsync(responseStream, relatedRpcRequest: null, _connectionCts.Token).ConfigureAwait(false);
    }

    private async Task<JsonRpcMessageWithId?> ProcessSseResponseAsync(Stream responseStream, JsonRpcRequest? relatedRpcRequest, CancellationToken cancellationToken)
    {
        await foreach (SseItem<string> sseEvent in SseParser.Create(responseStream).EnumerateAsync(cancellationToken).ConfigureAwait(false))
        {
            if (sseEvent.EventType != "message")
            {
                continue;
            }

            var rpcResponseOrError = await ProcessMessageAsync(sseEvent.Data, relatedRpcRequest, cancellationToken).ConfigureAwait(false);

            // The server SHOULD end the HTTP response body here anyway, but we won't leave it to chance. This transport makes
            // a GET request for any notifications that might need to be sent after the completion of each POST.
            if (rpcResponseOrError is not null)
            {
                return rpcResponseOrError;
            }
        }

        return null;
    }

    private async Task<JsonRpcMessageWithId?> ProcessMessageAsync(string data, JsonRpcRequest? relatedRpcRequest, CancellationToken cancellationToken)
    {
        try
        {
            var message = JsonSerializer.Deserialize(data, McpJsonUtilities.JsonContext.Default.JsonRpcMessage);
            if (message is null)
            {
                LogTransportMessageParseUnexpectedTypeSensitive(Name, data);
                return null;
            }

            await WriteMessageAsync(message, cancellationToken).ConfigureAwait(false);
            if (message is JsonRpcResponse or JsonRpcError &&
                message is JsonRpcMessageWithId rpcResponseOrError &&
                rpcResponseOrError.Id == relatedRpcRequest?.Id)
            {
                return rpcResponseOrError;
            }
        }
        catch (JsonException ex)
        {
            LogJsonException(ex, data);
        }

        return null;
    }

    private async Task SendDeleteRequest()
    {
        using var deleteRequest = new HttpRequestMessage(HttpMethod.Delete, _options.Endpoint);
        CopyAdditionalHeaders(deleteRequest.Headers, _options.AdditionalHeaders, SessionId, _negotiatedProtocolVersion);

        try
        {
            // Do not validate we get a successful status code, because server support for the DELETE request is optional
            (await _httpClient.SendAsync(deleteRequest, message: null, CancellationToken.None).ConfigureAwait(false)).Dispose();
        }
        catch (Exception ex)
        {
            LogTransportShutdownFailed(Name, ex);
        }
    }

    private void LogJsonException(JsonException ex, string data)
    {
        if (_logger.IsEnabled(LogLevel.Trace))
        {
            LogTransportMessageParseFailedSensitive(Name, data, ex);
        }
        else
        {
            LogTransportMessageParseFailed(Name, ex);
        }
    }

    internal static void CopyAdditionalHeaders(
        HttpRequestHeaders headers,
        IDictionary<string, string>? additionalHeaders,
        string? sessionId,
        string? protocolVersion)
    {
        if (sessionId is not null)
        {
            headers.Add("Mcp-Session-Id", sessionId);
        }

        if (protocolVersion is not null)
        {
            headers.Add("MCP-Protocol-Version", protocolVersion);
        }

        if (additionalHeaders is null)
        {
            return;
        }

        foreach (var header in additionalHeaders)
        {
            if (!headers.TryAddWithoutValidation(header.Key, header.Value))
            {
                throw new InvalidOperationException($"Failed to add header '{header.Key}' with value '{header.Value}' from {nameof(SseClientTransportOptions.AdditionalHeaders)}.");
            }
        }
    }
}

using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Protocol;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace ModelContextProtocol.Server;

/// Provides an <see cref="AIFunctionMcpServerResource"/> that's implemented via an <see cref="AIFunction"/>.
internal sealed class AIFunctionMcpServerResource : McpServerResource
{
    private readonly Regex? _uriParser;
    private readonly string[] _templateVariableNames = [];

    ///
    /// Creates an <see cref="AIFunctionMcpServerResource"/> instance for a method, specified via a <see cref="Delegate"/> instance.
    ///
    public static new AIFunctionMcpServerResource Create(
        Delegate method,
        McpServerResourceCreateOptions? options)
    {
        Throw.IfNull(method);

        options = DeriveOptions(method.Method, options);

        return Create(method.Method, method.Target, options);
    }

    ///
    /// Creates an <see cref="McpServerResource"/> instance for a method, specified via a <see cref="MethodInfo"/> instance.
    ///
    public static new AIFunctionMcpServerResource Create(
        MethodInfo method,
        object? target,
        McpServerResourceCreateOptions? options)
    {
        Throw.IfNull(method);

        options = DeriveOptions(method, options);

        return Create(
            AIFunctionFactory.Create(method, target, CreateAIFunctionFactoryOptions(method, options)),
            options);
    }

    ///
    /// Creates an <see cref="McpServerResource"/> instance for a method, specified via a <see cref="MethodInfo"/> instance.
    ///
    public static new AIFunctionMcpServerResource Create(
        MethodInfo method,
        Func<RequestContext<ReadResourceRequestParams>, object> createTargetFunc,
        McpServerResourceCreateOptions? options)
    {
        Throw.IfNull(method);
        Throw.IfNull(createTargetFunc);

        options = DeriveOptions(method, options);

        return Create(
            AIFunctionFactory.Create(method, args =>
            {
                Debug.Assert(args.Services is RequestServiceProvider<ReadResourceRequestParams>, $"The service provider should be a {nameof(RequestServiceProvider<ReadResourceRequestParams>)} for this method to work correctly.");
                return createTargetFunc(((RequestServiceProvider<ReadResourceRequestParams>)args.Services!).Request);
            }, CreateAIFunctionFactoryOptions(method, options)),
            options);
    }

    private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
        MethodInfo method, McpServerResourceCreateOptions? options) =>
        new()
        {
            Name = options?.Name ?? method.GetCustomAttribute<McpServerResourceAttribute>()?.Name ?? AIFunctionMcpServerTool.DeriveName(method),
            Description = options?.Description,
            MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
            SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
            JsonSchemaCreateOptions = options?.SchemaCreateOptions,
            ConfigureParameterBinding = pi =>
            {
                if (RequestServiceProvider<ReadResourceRequestParams>.IsAugmentedWith(pi.ParameterType) ||
                    (options?.Services?.GetService<IServiceProviderIsService>() is { } ispis &&
                     ispis.IsService(pi.ParameterType)))
                {
                    return new()
                    {
                        ExcludeFromSchema = true,
                        BindParameter = (pi, args) =>
                            args.Services?.GetService(pi.ParameterType) ??
                            (pi.HasDefaultValue ? null :
                             throw new ArgumentException("No service of the requested type was found.")),
                    };
                }

                if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
                {
                    return new()
                    {
                        ExcludeFromSchema = true,
                        BindParameter = (pi, args) =>
                            (args?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
                            (pi.HasDefaultValue ? null :
                             throw new ArgumentException("No service of the requested type was found.")),
                    };
                }

                // These parameters are the ones and only ones to include in the schema. The schema
                // won't be consumed by anyone other than this instance, which will use it to determine
                // which properties should show up in the URI template.
                if (pi.Name is not null && GetConverter(pi.ParameterType) is { } converter)
                {
                    return new()
                    {
                        ExcludeFromSchema = false,
                        BindParameter = (pi, args) =>
                        {
                            if (args.TryGetValue(pi.Name!, out var value))
                            {
                                return
                                    value is null || pi.ParameterType.IsInstanceOfType(value) ? value :
                                    value is string stringValue ? converter(stringValue) :
                                    throw new ArgumentException($"Parameter '{pi.Name}' is of type '{pi.ParameterType}', but value '{value}' is of type '{value.GetType()}'.");
                            }

                            return
                                pi.HasDefaultValue ? pi.DefaultValue :
                                throw new ArgumentException($"Missing a value for the required parameter '{pi.Name}'.");
                        },
                    };
                }

                return default;
            },
        };

    private static readonly ConcurrentDictionary<Type, Func<string, object?>> s_convertersCache = [];

    private static Func<string, object?>? GetConverter(Type type)
    {
        Type key = type;

        if (s_convertersCache.TryGetValue(key, out var converter))
        {
            return converter;
        }

        if (Nullable.GetUnderlyingType(type) is { } underlyingType)
        {
            // We will have already screened out null values by the time the converter is used,
            // so we can parse just the underlying type.
            type = underlyingType;
        }

        if (type == typeof(string) || type == typeof(object)) converter = static s => s;
        if (type == typeof(bool)) converter = static s => bool.Parse(s);
        if (type == typeof(char)) converter = static s => char.Parse(s);
        if (type == typeof(byte)) converter = static s => byte.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(sbyte)) converter = static s => sbyte.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(ushort)) converter = static s => ushort.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(short)) converter = static s => short.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(uint)) converter = static s => uint.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(int)) converter = static s => int.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(ulong)) converter = static s => ulong.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(long)) converter = static s => long.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(float)) converter = static s => float.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(double)) converter = static s => double.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(decimal)) converter = static s => decimal.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(TimeSpan)) converter = static s => TimeSpan.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(DateTime)) converter = static s => DateTime.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(DateTimeOffset)) converter = static s => DateTimeOffset.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(Uri)) converter = static s => new Uri(s, UriKind.RelativeOrAbsolute);
        if (type == typeof(Guid)) converter = static s => Guid.Parse(s);
        if (type == typeof(Version)) converter = static s => Version.Parse(s);
#if NET
        if (type == typeof(Half)) converter = static s => Half.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(Int128)) converter = static s => Int128.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(UInt128)) converter = static s => UInt128.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(IntPtr)) converter = static s => IntPtr.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(UIntPtr)) converter = static s => UIntPtr.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(DateOnly)) converter = static s => DateOnly.Parse(s, CultureInfo.InvariantCulture);
        if (type == typeof(TimeOnly)) converter = static s => TimeOnly.Parse(s, CultureInfo.InvariantCulture);
#endif
        if (type.IsEnum) converter = s => Enum.Parse(type, s);

        if (type.GetCustomAttribute<TypeConverterAttribute>() is TypeConverterAttribute tca &&
            Type.GetType(tca.ConverterTypeName, throwOnError: false) is { } converterType &&
            Activator.CreateInstance(converterType) is TypeConverter typeConverter &&
            typeConverter.CanConvertFrom(typeof(string)))
        {
            converter = s => typeConverter.ConvertFrom(null, CultureInfo.InvariantCulture, s);
        }

        if (converter is not null)
        {
            s_convertersCache.TryAdd(key, converter);
        }

        return converter;
    }

    /// Creates an <see cref="McpServerResource"/> that wraps the specified <see cref="AIFunction"/>.
    public static new AIFunctionMcpServerResource Create(AIFunction function, McpServerResourceCreateOptions? options)
    {
        Throw.IfNull(function);

        string name = options?.Name ?? function.Name;

        ResourceTemplate resource = new()
        {
            UriTemplate = options?.UriTemplate ?? DeriveUriTemplate(name, function),
            Name = name,
            Title = options?.Title,
            Description = options?.Description,
            MimeType = options?.MimeType ?? "application/octet-stream",
        };

        return new AIFunctionMcpServerResource(function, resource);
    }

    private static McpServerResourceCreateOptions DeriveOptions(MemberInfo member, McpServerResourceCreateOptions? options)
    {
        McpServerResourceCreateOptions newOptions = options?.Clone() ?? new();

        if (member.GetCustomAttribute<McpServerResourceAttribute>() is { } resourceAttr)
        {
            newOptions.UriTemplate ??= resourceAttr.UriTemplate;
            newOptions.Name ??= resourceAttr.Name;
            newOptions.Title ??= resourceAttr.Title;
            newOptions.MimeType ??= resourceAttr.MimeType;
        }

        if (member.GetCustomAttribute<DescriptionAttribute>() is { } descAttr)
        {
            newOptions.Description ??= descAttr.Description;
        }

        return newOptions;
    }

    /// Derives a name to be used as a resource name.
    private static string DeriveUriTemplate(string name, AIFunction function)
    {
        StringBuilder template = new();

        template.Append("resource://mcp/").Append(Uri.EscapeDataString(name));

        if (function.JsonSchema.TryGetProperty("properties", out JsonElement properties))
        {
            string separator = "{?";
            foreach (var prop in properties.EnumerateObject())
            {
                template.Append(separator).Append(prop.Name);
                separator = ",";
            }

            if (separator == ",")
            {
                template.Append('}');
            }
        }

        return template.ToString();
    }

    /// Gets the <see cref="AIFunction"/> wrapped by this resource.
    internal AIFunction AIFunction { get; }

    /// Initializes a new instance of the <see cref="McpServerResource"/> class.
    private AIFunctionMcpServerResource(AIFunction function, ResourceTemplate resourceTemplate)
    {
        AIFunction = function;
        ProtocolResourceTemplate = resourceTemplate;
        ProtocolResource = resourceTemplate.AsResource();

        if (ProtocolResource is null)
        {
            _uriParser = UriTemplate.CreateParser(resourceTemplate.UriTemplate);
            _templateVariableNames = _uriParser.GetGroupNames().Where(n => n != "0").ToArray();
        }
    }

    /// <inheritdoc />
    public override ResourceTemplate ProtocolResourceTemplate { get; }

    /// <inheritdoc />
    public override Resource? ProtocolResource { get; }

    /// <inheritdoc />
    public override async ValueTask<ReadResourceResult?> ReadAsync(
        RequestContext<ReadResourceRequestParams> request, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(request);
        Throw.IfNull(request.Params);
        Throw.IfNull(request.Params.Uri);

        cancellationToken.ThrowIfCancellationRequested();

        // Check to see if this URI template matches the request URI. If it doesn't, return null.
        // For templates, use the Regex to parse. For static resources, we can just compare the URIs.
        Match? match = null;
        if (_uriParser is not null)
        {
            match = _uriParser.Match(request.Params.Uri);
            if (!match.Success)
            {
                return null;
            }
        }
        else if (!UriTemplate.UriTemplateComparer.Instance.Equals(request.Params.Uri, ProtocolResource!.Uri))
        {
            return null;
        }

        // Build up the arguments for the AIFunction call, including all of the name/value pairs from the URI.
        request.Services = new RequestServiceProvider<ReadResourceRequestParams>(request, request.Services);
        AIFunctionArguments arguments = new() { Services = request.Services };

        // For templates, populate the arguments from the URI template.
        if (match is not null)
        {
            foreach (string varName in _templateVariableNames)
            {
                if (match.Groups[varName] is { Success: true } value)
                {
                    arguments[varName] = Uri.UnescapeDataString(value.Value);
                }
            }
        }

        // Invoke the function.
        object? result = await AIFunction.InvokeAsync(arguments, cancellationToken).ConfigureAwait(false);

        // And process the result.
        return result switch
        {
            ReadResourceResult readResourceResult => readResourceResult,

            ResourceContents content => new()
            {
                Contents = [content],
            },

            TextContent tc => new()
            {
                Contents = [new TextResourceContents { Uri = request.Params!.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = tc.Text }],
            },

            DataContent dc => new()
            {
                Contents = [new BlobResourceContents { Uri = request.Params!.Uri, MimeType = dc.MediaType, Blob = dc.Base64Data.ToString() }],
            },

            string text => new()
            {
                Contents = [new TextResourceContents { Uri = request.Params!.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = text }],
            },

            IEnumerable<ResourceContents> contents => new()
            {
                Contents = contents.ToList(),
            },

            IEnumerable<AIContent> aiContents => new()
            {
                Contents = aiContents.Select<AIContent, ResourceContents>(
                    ac => ac switch
                    {
                        TextContent tc => new TextResourceContents
                        {
                            Uri = request.Params!.Uri,
                            MimeType = ProtocolResourceTemplate.MimeType,
                            Text = tc.Text
                        },

                        DataContent dc => new BlobResourceContents
                        {
                            Uri = request.Params!.Uri,
                            MimeType = dc.MediaType,
                            Blob = dc.Base64Data.ToString()
                        },

                        _ => throw new InvalidOperationException($"Unsupported AIContent type '{ac.GetType()}' returned from resource function."),
                    }).ToList(),
            },

            IEnumerable<string> strings => new()
            {
                Contents = strings.Select<string, ResourceContents>(text => new TextResourceContents
                {
                    Uri = request.Params!.Uri,
                    MimeType = ProtocolResourceTemplate.MimeType,
                    Text = text
                }).ToList(),
            },

            null => throw new InvalidOperationException("Null result returned from resource function."),

            _ => throw new InvalidOperationException($"Unsupported result type '{result.GetType()}' returned from resource function."),
        };
    }
}

using ModelContextProtocol.Protocol;
using System.IO.Pipelines;
using System.Threading.Channels;

namespace ModelContextProtocol.Server;

/// <summary>
/// Provides an <see cref="ITransport"/> implementation using Server-Sent Events (SSE) for server-to-client communication.
/// </summary>
/// <remarks>
/// <para>
/// This transport provides one-way communication from server to client using the SSE protocol over HTTP,
/// while receiving client messages through a separate mechanism. It writes messages as
/// SSE events to a response stream, typically associated with an HTTP response.
/// </para>
/// <para>
/// This transport is used in scenarios where the server needs to push messages to the client in real-time,
/// such as when streaming completion results or providing progress updates during long-running operations.
/// </para>
/// </remarks>
public sealed class StreamableHttpServerTransport : ITransport
{
    // For JsonRpcMessages without a RelatedTransport, we don't want to block just because the client didn't make a GET request to handle unsolicited messages.
    private readonly SseWriter _sseWriter = new(channelOptions: new BoundedChannelOptions(1)
    {
        SingleReader = true,
        SingleWriter = false,
        FullMode = BoundedChannelFullMode.DropOldest,
    });
    private readonly Channel<JsonRpcMessage> _incomingChannel = Channel.CreateBounded<JsonRpcMessage>(new BoundedChannelOptions(1)
    {
        SingleReader = true,
        SingleWriter = false,
    });
    private readonly CancellationTokenSource _disposeCts = new();

    private int _getRequestStarted;

    /// <inheritdoc/>
    public string? SessionId { get; set; }

    /// <summary>
    /// Configures whether the transport should be in stateless mode that does not require all requests for a given session
    /// to arrive to the same ASP.NET Core application process. Unsolicited server-to-client messages are not supported in this mode,
    /// so calling <see cref="HandleGetRequest(Stream, CancellationToken)"/> results in an <see cref="InvalidOperationException"/>.
    /// Server-to-client requests are also unsupported, because the responses may arrive at another ASP.NET Core application process.
    /// Client sampling and roots capabilities are also disabled in stateless mode, because the server cannot make requests.
    /// </summary>
    public bool Stateless { get; init; }

    /// <summary>
    /// Gets a value indicating whether the execution context should flow from the calls to <see cref="HandlePostRequest(IDuplexPipe, CancellationToken)"/>
    /// to the corresponding <see cref="JsonRpcMessage.ExecutionContext"/> emitted by the <see cref="MessageReader"/>.
    /// </summary>
    /// <remarks>
    /// Defaults to <see langword="false"/>.
    /// </remarks>
    public bool FlowExecutionContextFromRequests { get; init; }

    /// <summary>
    /// Gets or sets a callback to be invoked before handling the initialize request.
    /// </summary>
    public Func<InitializeRequestParams?, ValueTask>? OnInitRequestReceived { get; set; }

    /// <inheritdoc/>
    public ChannelReader<JsonRpcMessage> MessageReader => _incomingChannel.Reader;

    internal ChannelWriter<JsonRpcMessage> MessageWriter => _incomingChannel.Writer;

    /// <summary>
    /// Handles an optional SSE GET request a client using the Streamable HTTP transport might make by
    /// writing any unsolicited JSON-RPC messages sent via <see cref="SendMessageAsync"/>
    /// to the SSE response stream until cancellation is requested or the transport is disposed.
    /// </summary>
    /// <param name="sseResponseStream">The response stream to write MCP JSON-RPC messages as SSE events to.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task representing the send loop that writes JSON-RPC messages to the SSE response stream.</returns>
    public async Task HandleGetRequest(Stream sseResponseStream, CancellationToken cancellationToken)
    {
        if (Stateless)
        {
            throw new InvalidOperationException("GET requests are not supported in stateless mode.");
        }

        if (Interlocked.Exchange(ref _getRequestStarted, 1) == 1)
        {
            throw new InvalidOperationException("Session resumption is not yet supported. Please start a new session.");
        }

        // We do not need to reference _disposeCts like in HandlePostRequest, because the session ending completes the _sseWriter gracefully.
        await _sseWriter.WriteAllAsync(sseResponseStream, cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    /// Handles a Streamable HTTP POST request processing both the request body and response body ensuring that
    /// <see cref="JsonRpcResponse"/> and other correlated messages are sent back to the client directly in response
    /// to the <see cref="JsonRpcRequest"/> that initiated the message.
    /// </summary>
    /// <param name="httpBodies">The duplex pipe facilitates the reading and writing of HTTP request and response data.</param>
    /// <param name="cancellationToken">This token allows for the operation to be canceled if needed.</param>
    /// <returns>
    /// True, if data was written to the response body.
    /// False, if nothing was written because the request body did not contain any <see cref="JsonRpcRequest"/> messages to respond to.
    /// The HTTP application should typically respond with an empty "202 Accepted" response in this scenario.
    /// </returns>
    public async Task<bool> HandlePostRequest(IDuplexPipe httpBodies, CancellationToken cancellationToken)
    {
        using var postCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeCts.Token, cancellationToken);
        await using var postTransport = new StreamableHttpPostTransport(this, httpBodies);
        return await postTransport.RunAsync(postCts.Token).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
    {
        if (Stateless)
        {
            throw new InvalidOperationException("Unsolicited server to client messages are not supported in stateless mode.");
        }

        await _sseWriter.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async ValueTask DisposeAsync()
    {
        try
        {
            await _disposeCts.CancelAsync();
        }
        finally
        {
            try
            {
                await _sseWriter.DisposeAsync().ConfigureAwait(false);
            }
            finally
            {
                _disposeCts.Dispose();
            }
        }
    }
}

using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;

namespace ModelContextProtocol.Server;

///
/// Provides extension methods for interacting with an <see cref="IMcpServer"/> instance.
///
public static class McpServerExtensions
{
    ///
    /// Requests to sample an LLM via the client using the specified request parameters.
    ///
    /// <param name="server">The server instance initiating the request.</param>
    /// <param name="request">The parameters for the sampling request.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
    /// <returns>A task containing the sampling result from the client.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The client does not support sampling.</exception>
    /// <remarks>
    /// This method requires the client to support sampling capabilities.
    /// It allows detailed control over sampling parameters including messages, system prompt, temperature,
    /// and token limits.
    /// </remarks>
    public static ValueTask<CreateMessageResult> SampleAsync(
        this IMcpServer server, CreateMessageRequestParams request, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(server);
        ThrowIfSamplingUnsupported(server);

        return server.SendRequestAsync(
            RequestMethods.SamplingCreateMessage,
            request,
            McpJsonUtilities.JsonContext.Default.CreateMessageRequestParams,
            McpJsonUtilities.JsonContext.Default.CreateMessageResult,
            cancellationToken: cancellationToken);
    }

    ///
    /// Requests to sample an LLM via the client using the provided chat messages and options.
    ///
    /// <param name="server">The server initiating the request.</param>
    /// <param name="messages">The messages to send as part of the request.</param>
    /// <param name="options">The options to use for the request, including model parameters and constraints.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task containing the chat response from the model.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="messages"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The client does not support sampling.</exception>
    /// <remarks>
    /// This method converts the provided chat messages into a format suitable for the sampling API,
    /// handling different content types such as text, images, and audio.
    /// </remarks>
    public static async Task<ChatResponse> SampleAsync(
        this IMcpServer server,
        IEnumerable<ChatMessage> messages, ChatOptions? options = default, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(server);
        Throw.IfNull(messages);

        StringBuilder? systemPrompt = null;

        if (options?.Instructions is { } instructions)
        {
            (systemPrompt ??= new()).Append(instructions);
        }

        List<SamplingMessage> samplingMessages = [];
        foreach (var message in messages)
        {
            if (message.Role == ChatRole.System)
            {
                if (systemPrompt is null)
                {
                    systemPrompt = new();
                }
                else
                {
                    systemPrompt.AppendLine();
                }

                systemPrompt.Append(message.Text);
                continue;
            }

            if (message.Role == ChatRole.User || message.Role == ChatRole.Assistant)
            {
                Role role = message.Role == ChatRole.User ? Role.User : Role.Assistant;

                foreach (var content in message.Contents)
                {
                    switch (content)
                    {
                        case TextContent textContent:
                            samplingMessages.Add(new()
                            {
                                Role = role,
                                Content = new TextContentBlock { Text = textContent.Text },
                            });
                            break;

                        case DataContent dataContent when dataContent.HasTopLevelMediaType("image") || dataContent.HasTopLevelMediaType("audio"):
                            samplingMessages.Add(new()
                            {
                                Role = role,
                                Content = dataContent.HasTopLevelMediaType("image") ?
                                    new ImageContentBlock
                                    {
                                        MimeType = dataContent.MediaType,
                                        Data = dataContent.Base64Data.ToString(),
                                    } :
                                    new AudioContentBlock
                                    {
                                        MimeType = dataContent.MediaType,
                                        Data = dataContent.Base64Data.ToString(),
                                    },
                            });
                            break;
                    }
                }
            }
        }

        ModelPreferences? modelPreferences = null;
        if (options?.ModelId is { } modelId)
        {
            modelPreferences = new() { Hints = [new() { Name = modelId }] };
        }

        var result = await server.SampleAsync(new()
            {
                Messages = samplingMessages,
                MaxTokens = options?.MaxOutputTokens,
                StopSequences = options?.StopSequences?.ToArray(),
                SystemPrompt = systemPrompt?.ToString(),
                Temperature = options?.Temperature,
                ModelPreferences = modelPreferences,
            }, cancellationToken).ConfigureAwait(false);

        AIContent? responseContent = result.Content.ToAIContent();

        return new(new ChatMessage(result.Role is Role.User ? ChatRole.User : ChatRole.Assistant, responseContent is not null ? [responseContent] : []))
        {
            ModelId = result.Model,
            FinishReason = result.StopReason switch
            {
                "maxTokens" => ChatFinishReason.Length,
                "endTurn" or "stopSequence" or _ => ChatFinishReason.Stop,
            }
        };
    }

    ///
    /// Creates an <see cref="IChatClient"/> wrapper that can be used to send sampling requests to the client.
    ///
    /// <param name="server">The server to be wrapped as an <see cref="IChatClient"/>.</param>
    /// <returns>The <see cref="IChatClient"/> that can be used to issue sampling requests to the client.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The client does not support sampling.</exception>
    public static IChatClient AsSamplingChatClient(this IMcpServer server)
    {
        Throw.IfNull(server);
        ThrowIfSamplingUnsupported(server);

        return new SamplingChatClient(server);
    }

    /// Gets an <see cref="ILogger"/> on which logged messages will be sent as notifications to the client.
    /// <param name="server">The server to wrap as an <see cref="ILogger"/>.</param>
    /// <returns>An <see cref="ILogger"/> that can be used to log to the client..</returns>
    public static ILoggerProvider AsClientLoggerProvider(this IMcpServer server)
    {
        Throw.IfNull(server);

        return new ClientLoggerProvider(server);
    }

    ///
    /// Requests the client to list the roots it exposes.
    ///
    /// <param name="server">The server initiating the request.</param>
    /// <param name="request">The parameters for the list roots request.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
    /// <returns>A task containing the list of roots exposed by the client.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The client does not support roots.</exception>
    /// <remarks>
    /// This method requires the client to support the roots capability.
    /// Root resources allow clients to expose a hierarchical structure of resources that can be
    /// navigated and accessed by the server. These resources might include file systems, databases,
    /// or other structured data sources that the client makes available through the protocol.
    /// </remarks>
    public static ValueTask<ListRootsResult> RequestRootsAsync(
        this IMcpServer server, ListRootsRequestParams request, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(server);
        ThrowIfRootsUnsupported(server);

        return server.SendRequestAsync(
            RequestMethods.RootsList,
            request,
            McpJsonUtilities.JsonContext.Default.ListRootsRequestParams,
            McpJsonUtilities.JsonContext.Default.ListRootsResult,
            cancellationToken: cancellationToken);
    }

    ///
    /// Requests additional information from the user via the client, allowing the server to elicit structured data.
    ///
    /// <param name="server">The server initiating the request.</param>
    /// <param name="request">The parameters for the elicitation request.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
    /// <returns>A task containing the elicitation result.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The client does not support elicitation.</exception>
    /// <remarks>
    /// This method requires the client to support the elicitation capability.
    /// </remarks>
    public static ValueTask<ElicitResult> ElicitAsync(
        this IMcpServer server, ElicitRequestParams request, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(server);
        ThrowIfElicitationUnsupported(server);

        return server.SendRequestAsync(
            RequestMethods.ElicitationCreate,
            request,
            McpJsonUtilities.JsonContext.Default.ElicitRequestParams,
            McpJsonUtilities.JsonContext.Default.ElicitResult,
            cancellationToken: cancellationToken);
    }

    private static void ThrowIfSamplingUnsupported(IMcpServer server)
    {
        if (server.ClientCapabilities?.Sampling is null)
        {
            if (server.ServerOptions.KnownClientInfo is not null)
            {
                throw new InvalidOperationException("Sampling is not supported in stateless mode.");
            }

            throw new InvalidOperationException("Client does not support sampling.");
        }
    }

    private static void ThrowIfRootsUnsupported(IMcpServer server)
    {
        if (server.ClientCapabilities?.Roots is null)
        {
            if (server.ServerOptions.KnownClientInfo is not null)
            {
                throw new InvalidOperationException("Roots are not supported in stateless mode.");
            }

            throw new InvalidOperationException("Client does not support roots.");
        }
    }

    private static void ThrowIfElicitationUnsupported(IMcpServer server)
    {
        if (server.ClientCapabilities?.Elicitation is null)
        {
            if (server.ServerOptions.KnownClientInfo is not null)
            {
                throw new InvalidOperationException("Elicitation is not supported in stateless mode.");
            }

            throw new InvalidOperationException("Client does not support elicitation requests.");
        }
    }

    /// Provides an <see cref="IChatClient"/> implementation that's implemented via client sampling.
    private sealed class SamplingChatClient(IMcpServer server) : IChatClient
    {
        /// <inheritdoc/>
        public Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default) =>
            server.SampleAsync(messages, options, cancellationToken);

        /// <inheritdoc/>
        async IAsyncEnumerable<ChatResponseUpdate> IChatClient.GetStreamingResponseAsync(
            IEnumerable<ChatMessage> messages, ChatOptions? options, [EnumeratorCancellation] CancellationToken cancellationToken)
        {
            var response = await GetResponseAsync(messages, options, cancellationToken).ConfigureAwait(false);
            foreach (var update in response.ToChatResponseUpdates())
            {
                yield return update;
            }
        }

        /// <inheritdoc/>
        object? IChatClient.GetService(Type serviceType, object? serviceKey)
        {
            Throw.IfNull(serviceType);

            return
                serviceKey is not null ? null :
                serviceType.IsInstanceOfType(this) ? this :
                serviceType.IsInstanceOfType(server) ? server :
                null;
        }

        /// <inheritdoc/>
        void IDisposable.Dispose() { } // nop
    }

    ///
    /// Provides an <see cref="ILoggerProvider"/> implementation for creating loggers
    /// that send logging message notifications to the client for logged messages.
    ///
    private sealed class ClientLoggerProvider(IMcpServer server) : ILoggerProvider
    {
        /// <inheritdoc />
        public ILogger CreateLogger(string categoryName)
        {
            Throw.IfNull(categoryName);

            return new ClientLogger(server, categoryName);
        }

        /// <inheritdoc />
        void IDisposable.Dispose() { }

        private sealed class ClientLogger(IMcpServer server, string categoryName) : ILogger
        {
            /// <inheritdoc />
            public IDisposable? BeginScope<TState>(TState state) where TState : notnull =>
                null;

            /// <inheritdoc />
            public bool IsEnabled(LogLevel logLevel) =>
                server?.LoggingLevel is { } loggingLevel &&
                McpServer.ToLoggingLevel(logLevel) >= loggingLevel;

            /// <inheritdoc />
            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
            {
                if (!IsEnabled(logLevel))
                {
                    return;
                }

                Throw.IfNull(formatter);

                Log(logLevel, formatter(state, exception));

                void Log(LogLevel logLevel, string message)
                {
                    _ = server.SendNotificationAsync(NotificationMethods.LoggingMessageNotification, new LoggingMessageNotificationParams
                    {
                        Level = McpServer.ToLoggingLevel(logLevel),
                        Data = JsonSerializer.SerializeToElement(message, McpJsonUtilities.JsonContext.Default.String),
                        Logger = categoryName,
                    });
                }
            }
        }
    }
}

#if NET
using System.Buffers;
#endif
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;

namespace ModelContextProtocol;

/// Provides basic support for parsing and formatting URI templates.
/// <remarks>
/// This implementation should correctly handle valid URI templates, but it has undefined output for invalid templates,
/// e.g. it may treat portions of invalid templates as literals rather than throwing.
/// </remarks>
internal static partial class UriTemplate
{
    /// Regex pattern for finding URI template expressions and parsing out the operator and varname.
    private const string UriTemplateExpressionPattern = """
        {                                                       # opening brace
            (?<operator>[+#./;?&]?)                             # optional operator
            (?<varname>
                (?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2})                # varchar: letter, digit, underscore, or pct-encoded
                (?:\.?(?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2}))*        # optionally dot-separated subsequent varchars
            )
            (?: :[1-9][0-9]{0,3} )?                             # optional prefix modifier (1–4 digits)
            \*?                                                 # optional explode
            (?:,                                                # comma separator, followed by the same as above
                (?<varname>
                    (?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2})
                    (?:\.?(?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2}))*
                )
                (?: :[1-9][0-9]{0,3} )?
                \*?
            )*                                                  # zero or more additional vars
        }                                                       # closing brace
        """;

    /// Gets a regex for finding URI template expressions and parsing out the operator and varname.
    /// <remarks>
    /// This regex is for parsing a static URI template.
    /// It is not for parsing a URI according to a template.
    /// </remarks>
#if NET
    [GeneratedRegex(UriTemplateExpressionPattern, RegexOptions.IgnorePatternWhitespace)]
    private static partial Regex UriTemplateExpression();
#else
    private static Regex UriTemplateExpression() => s_uriTemplateExpression;
    private static readonly Regex s_uriTemplateExpression = new(UriTemplateExpressionPattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
#endif

#if NET
    /// SearchValues for characters that needn't be escaped when allowing reserved characters.
    private static readonly SearchValues<char> s_appendWhenAllowReserved = SearchValues.Create(
        "abcdefghijklmnopqrstuvwxyz" + // ASCII lowercase letters
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + // ASCII uppercase letters
        "0123456789" +                 // ASCII digits
        "-._~" +                       // unreserved characters
        ":/?#[]@!$&'()*+,;=");         // reserved characters
#endif

    /// Create a <see cref="Regex"/> for matching a URI against a URI template.
    /// <param name="uriTemplate">The template against which to match.</param>
    /// <returns>A regex pattern that can be used to match the specified URI template.</returns>
    public static Regex CreateParser(string uriTemplate)
    {
        DefaultInterpolatedStringHandler pattern = new(0, 0, CultureInfo.InvariantCulture, stackalloc char[256]);
        pattern.AppendFormatted('^');

        int lastIndex = 0;
        for (Match m = UriTemplateExpression().Match(uriTemplate); m.Success; m = m.NextMatch())
        {
            pattern.AppendFormatted(Regex.Escape(uriTemplate[lastIndex..m.Index]));
            lastIndex = m.Index + m.Length;

            var captures = m.Groups["varname"].Captures;
            List<string> paramNames = new(captures.Count);
            foreach (Capture c in captures)
            {
                paramNames.Add(c.Value);
            }

            switch (m.Groups["operator"].Value)
            {
                case "#": AppendExpression(ref pattern, paramNames, '#', "[^,]+"); break;
                case "/": AppendExpression(ref pattern, paramNames, '/', "[^/?]+"); break;
                default:  AppendExpression(ref pattern, paramNames, null, "[^/?&]+"); break;

                case "?": AppendQueryExpression(ref pattern, paramNames, '?'); break;
                case "&": AppendQueryExpression(ref pattern, paramNames, '&'); break;
            }
        }

        pattern.AppendFormatted(Regex.Escape(uriTemplate.Substring(lastIndex)));
        pattern.AppendFormatted('$');

        return new Regex(
            pattern.ToStringAndClear(),
            RegexOptions.IgnoreCase | RegexOptions.CultureInvariant |
#if NET
            RegexOptions.NonBacktracking);
#else
            RegexOptions.Compiled, TimeSpan.FromSeconds(10));
#endif

        // Appends a regex fragment to `pattern` that matches an optional query string starting
        // with the given `prefix` (? or &), and up to one occurrence of each name in
        // `paramNames`. Each parameter is made optional and captured by a named group
        // of the form “paramName=value”.
        static void AppendQueryExpression(ref DefaultInterpolatedStringHandler pattern, List<string> paramNames, char prefix)
        {
            Debug.Assert(prefix is '?' or '&');

            pattern.AppendFormatted("(?:\\");
            pattern.AppendFormatted(prefix);

            if (paramNames.Count > 0)
            {
                AppendParameter(ref pattern, paramNames[0]);
                for (int i = 1; i < paramNames.Count; i++)
                {
                    pattern.AppendFormatted("\\&?");
                    AppendParameter(ref pattern, paramNames[i]);
                }

                static void AppendParameter(ref DefaultInterpolatedStringHandler pattern, string paramName)
                {
                    paramName = Regex.Escape(paramName);
                    pattern.AppendFormatted("(?:");
                    pattern.AppendFormatted(paramName);
                    pattern.AppendFormatted("=(?<");
                    pattern.AppendFormatted(paramName);
                    pattern.AppendFormatted(">[^/?&]+))?");
                }
            }

            pattern.AppendFormatted(")?");
        }

        // Chooses a regex character‐class (`valueChars`) based on the initial `prefix` to define which
        // characters make up a parameter value. Then, for each name in `paramNames`, it optionally
        // appends the escaped `prefix` (only on the first parameter, then switches to ','), and
        // adds an optional named capture group `(?<paramName>valueChars)` to match and capture that value.
        static void AppendExpression(ref DefaultInterpolatedStringHandler pattern, List<string> paramNames, char? prefix, string valueChars)
        {
            Debug.Assert(prefix is '#' or '/' or null);

            if (paramNames.Count > 0)
            {
                if (prefix is not null)
                {
                    pattern.AppendFormatted('\\');
                    pattern.AppendFormatted(prefix);
                    pattern.AppendFormatted('?');
                }

                AppendParameter(ref pattern, paramNames[0], valueChars);
                for (int i = 1; i < paramNames.Count; i++)
                {
                    pattern.AppendFormatted("\\,?");
                    AppendParameter(ref pattern, paramNames[i], valueChars);
                }

                static void AppendParameter(ref DefaultInterpolatedStringHandler pattern, string paramName, string valueChars)
                {
                    pattern.AppendFormatted("(?<");
                    pattern.AppendFormatted(Regex.Escape(paramName));
                    pattern.AppendFormatted('>');
                    pattern.AppendFormatted(valueChars);
                    pattern.AppendFormatted(")?");
                }
            }
        }
    }

    ///
    /// Expand a URI template using the given variable values.
    ///
    public static string FormatUri(string uriTemplate, IReadOnlyDictionary<string, object?> arguments)
    {
        Throw.IfNull(uriTemplate);

        ReadOnlySpan<char> uriTemplateSpan = uriTemplate.AsSpan();
        DefaultInterpolatedStringHandler builder = new(0, 0, CultureInfo.InvariantCulture, stackalloc char[256]);
        while (!uriTemplateSpan.IsEmpty)
        {
            // Find the next expression.
            int openBracePos = uriTemplateSpan.IndexOf('{');
            if (openBracePos < 0)
            {
                if (uriTemplate.Length == uriTemplateSpan.Length)
                {
                    return uriTemplate;
                }

                builder.AppendFormatted(uriTemplateSpan);
                break;
            }

            // Append as a literal everything before the next expression.
            builder.AppendFormatted(uriTemplateSpan.Slice(0, openBracePos));
            uriTemplateSpan = uriTemplateSpan.Slice(openBracePos + 1);

            int closeBracePos = uriTemplateSpan.IndexOf('}');
            if (closeBracePos < 0)
            {
                throw new FormatException($"Unmatched '{{' in URI template '{uriTemplate}'");
            }

            ReadOnlySpan<char> expression = uriTemplateSpan.Slice(0, closeBracePos);
            uriTemplateSpan = uriTemplateSpan.Slice(closeBracePos + 1);
            if (expression.IsEmpty)
            {
                continue;
            }

            // The start of the expression may be a modifier; if it is, slice it off the expression.
            char modifier = expression[0];
            (string Prefix, string Separator, bool Named, bool IncludeNameIfEmpty, bool IncludeSeparatorIfEmpty, bool AllowReserved, bool PrefixEmptyExpansions, int ExpressionSlice) modifierBehavior = modifier switch
            {
                '+' => (string.Empty, ",", false, false, true, true, false, 1),
                '#' => ("#", ",", false, false, true, true, true, 1),
                '.' => (".", ".", false, false, true, false, true, 1),
                '/' => ("/", "/", false, false, true, false, false, 1),
                ';' => (";", ";", true, true, false, false, false, 1),
                '?' => ("?", "&", true, true, true, false, false, 1),
                '&' => ("&", "&", true, true, true, false, false, 1),
                _   => (string.Empty, ",", false, false, true, false, false, 0),
            };
            expression = expression.Slice(modifierBehavior.ExpressionSlice);

            List<string> expansions = [];

            // Process each varspec in the comma-delimited list in the expression (if it doesn't have any
            // commas, it will be the whole expression).
            while (!expression.IsEmpty)
            {
                // Find the next name.
                int commaPos = expression.IndexOf(',');
                ReadOnlySpan<char> name;
                if (commaPos < 0)
                {
                    name = expression;
                    expression = ReadOnlySpan<char>.Empty;
                }
                else
                {
                    name = expression.Slice(0, commaPos);
                    expression = expression.Slice(commaPos + 1);
                }

                bool explode = false;
                int prefixLength = -1;

                // If the name ends with a *, it means we should explode the value into separate
                // name=value pairs. If it has a colon, it means we should only take the first N characters
                // of the value. If it has both, the * takes precedence and we ignore the colon.
                if (!name.IsEmpty && name[name.Length - 1] == '*')
                {
                    explode = true;
                    name = name.Slice(0, name.Length - 1);
                }
                else if (name.IndexOf(':') >= 0)
                {
                    int colonPos = name.IndexOf(':');
                    if (colonPos < 0)
                    {
                        throw new FormatException($"Invalid varspec '{name.ToString()}'");
                    }

                    if (!int.TryParse(name.Slice(colonPos + 1)
#if !NET
                        .ToString()
#endif
                        , out prefixLength))
                    {
                        throw new FormatException($"Invalid prefix length in varspec '{name.ToString()}'");
                    }

                    name = name.Slice(0, colonPos);
                }

                // Look up the value for this name. If it doesn't exist, skip it.
                string nameString = name.ToString();
                if (!arguments.TryGetValue(nameString, out var value) || value is null)
                {
                    continue;
                }

                if (value is IEnumerable<string> list)
                {
                    var items = list.Select(i => Encode(i, modifierBehavior.AllowReserved));
                    if (explode)
                    {
                        if (modifierBehavior.Named)
                        {
                            foreach (var item in items)
                            {
                                expansions.Add($"{nameString}={item}");
                            }
                        }
                        else
                        {
                            foreach (var item in items)
                            {
                                expansions.Add(item);
                            }
                        }
                    }
                    else
                    {
                        var joined = string.Join(",", items);
                        expansions.Add(joined.Length > 0 && modifierBehavior.Named ?
                            $"{nameString}={joined}" :
                            joined);
                    }
                }
                else if (value is IReadOnlyDictionary<string, string> assoc)
                {
                    var pairs = assoc.Select(kvp => (
                        Encode(kvp.Key, modifierBehavior.AllowReserved),
                        Encode(kvp.Value, modifierBehavior.AllowReserved)
                    ));

                    if (explode)
                    {
                        foreach (var (k, v) in pairs)
                        {
                            expansions.Add($"{k}={v}");
                        }
                    }
                    else
                    {
                        var joined = string.Join(",", pairs.Select(p => $"{p.Item1},{p.Item2}"));
                        if (joined.Length > 0)
                        {
                            expansions.Add(modifierBehavior.Named ? $"{nameString}={joined}" : joined);
                        }
                    }
                }
                else
                {
                    string s =
                        value as string ??
                        (value is IFormattable f ? f.ToString(null, CultureInfo.InvariantCulture) : value.ToString()) ??
                        string.Empty;

                    s = Encode((uint)prefixLength < s.Length ? s.Substring(0, prefixLength) : s, modifierBehavior.AllowReserved);
                    if (!modifierBehavior.Named)
                    {
                        expansions.Add(s);
                    }
                    else if (s.Length != 0 || modifierBehavior.IncludeNameIfEmpty)
                    {
                        expansions.Add(
                            s.Length != 0 ? $"{nameString}={s}" :
                            modifierBehavior.IncludeSeparatorIfEmpty ? $"{nameString}=" :
                            nameString);
                    }
                }
            }

            if (expansions.Count > 0 &&
                (modifierBehavior.PrefixEmptyExpansions || !expansions.All(string.IsNullOrEmpty)))
            {
                builder.AppendLiteral(modifierBehavior.Prefix);
                AppendJoin(ref builder, modifierBehavior.Separator, expansions);
            }
        }

        return builder.ToStringAndClear();
    }

    private static void AppendJoin(ref DefaultInterpolatedStringHandler builder, string separator, IList<string> values)
    {
        int count = values.Count;
        if (count > 0)
        {
            builder.AppendLiteral(values[0]);
            for (int i = 1; i < count; i++)
            {
                builder.AppendLiteral(separator);
                builder.AppendLiteral(values[i]);
            }
        }
    }

    private static string Encode(string value, bool allowReserved)
    {
        if (!allowReserved)
        {
            return Uri.EscapeDataString(value);
        }

        DefaultInterpolatedStringHandler builder = new(0, 0, CultureInfo.InvariantCulture, stackalloc char[256]);
        int i = 0;
#if NET
        i = value.AsSpan().IndexOfAnyExcept(s_appendWhenAllowReserved);
        if (i < 0)
        {
            return value;
        }

        builder.AppendFormatted(value.AsSpan(0, i));
#endif

        for (; i < value.Length; ++i)
        {
            char c = value[i];
            if (((uint)((c | 0x20) - 'a') <= 'z' - 'a') ||
                ((uint)(c - '0') <= '9' - '0') ||
                "-._~:/?#[]@!$&'()*+,;=".Contains(c))
            {
                builder.AppendFormatted(c);
            }
            else if (c == '%' && i < value.Length - 2 && Uri.IsHexDigit(value[i + 1]) && Uri.IsHexDigit(value[i + 2]))
            {
                builder.AppendFormatted(value.AsSpan(i, 3));
                i += 2;
            }
            else
            {
                AppendHex(ref builder, c);
            }
        }

        return builder.ToStringAndClear();

        static void AppendHex(ref DefaultInterpolatedStringHandler builder, char c)
        {
            ReadOnlySpan<char> hexDigits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];

            if (c <= 0x7F)
            {
                builder.AppendFormatted('%');
                builder.AppendFormatted(hexDigits[c >> 4]);
                builder.AppendFormatted(hexDigits[c & 0xF]);
            }
            else
            {
#if NET
            Span<byte> utf8 = stackalloc byte[Encoding.UTF8.GetMaxByteCount(1)];
            foreach (byte b in utf8.Slice(0, new Rune(c).EncodeToUtf8(utf8)))
#else
                foreach (byte b in Encoding.UTF8.GetBytes([c]))
#endif
                {
                    builder.AppendFormatted('%');
                    builder.AppendFormatted(hexDigits[b >> 4]);
                    builder.AppendFormatted(hexDigits[b & 0xF]);
                }
            }
        }
    }

    ///
    /// Defines an equality comparer for Uri templates as follows:
    /// 1. Non-templated Uris use regular System.Uri equality comparison (host name is case insensitive).
    /// 2. Templated Uris use regular string equality.
    ///
    /// We do this because non-templated resources are looked up directly from the resource dictionary
    /// and we need to make sure equality is implemented correctly. Templated Uris are resolved in a
    /// fallback step using linear traversal of the resource dictionary, so their equality is only
    /// there to distinguish between different templates.
    ///
    public sealed class UriTemplateComparer : IEqualityComparer<string>
    {
        public static IEqualityComparer<string> Instance { get; } = new UriTemplateComparer();

        public bool Equals(string? uriTemplate1, string? uriTemplate2)
        {
            if (TryParseAsNonTemplatedUri(uriTemplate1, out Uri? uri1) &&
                TryParseAsNonTemplatedUri(uriTemplate2, out Uri? uri2))
            {
                return uri1 == uri2;
            }

            return string.Equals(uriTemplate1, uriTemplate2, StringComparison.Ordinal);
        }

        public int GetHashCode([DisallowNull] string uriTemplate)
        {
            if (TryParseAsNonTemplatedUri(uriTemplate, out Uri? uri))
            {
                return uri.GetHashCode();
            }
            else
            {
                return StringComparer.Ordinal.GetHashCode(uriTemplate);
            }
        }

        private static bool TryParseAsNonTemplatedUri(string? uriTemplate, [NotNullWhen(true)] out Uri? uri)
        {
            if (uriTemplate is null || uriTemplate.Contains('{'))
            {
                uri = null;
                return false;
            }

            return Uri.TryCreate(uriTemplate, UriKind.Absolute, out uri);
        }
    }
}

using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
using System.Text;
using System.Text.Json;

namespace ModelContextProtocol.Client;

/// <summary>Provides the client side of a stream-based session transport.</summary>
internal class StreamClientSessionTransport : TransportBase
{
    internal static UTF8Encoding NoBomUtf8Encoding { get; } = new(encoderShouldEmitUTF8Identifier: false);

    private readonly TextReader _serverOutput;
    private readonly TextWriter _serverInput;
    private readonly SemaphoreSlim _sendLock = new(1, 1);
    private CancellationTokenSource? _shutdownCts = new();
    private Task? _readTask;

    /// <summary>
    /// Initializes a new instance of the <see cref="StreamClientSessionTransport"/> class.
    /// </summary>
    /// <param name="serverInput">
    /// The text writer connected to the server's input stream.
    /// Messages written to this writer will be sent to the server.
    /// </param>
    /// <param name="serverOutput">
    /// The text reader connected to the server's output stream.
    /// Messages read from this reader will be received from the server.
    /// </param>
    /// <param name="endpointName">
    /// A name that identifies this transport endpoint in logs.
    /// </param>
    /// <param name="loggerFactory">
    /// Optional factory for creating loggers. If null, a NullLogger will be used.
    /// </param>
    /// <remarks>
    /// This constructor starts a background task to read messages from the server output stream.
    /// The transport will be marked as connected once initialized.
    /// </remarks>
    public StreamClientSessionTransport(
        TextWriter serverInput, TextReader serverOutput, string endpointName, ILoggerFactory? loggerFactory)
        : base(endpointName, loggerFactory)
    {
        _serverOutput = serverOutput;
        _serverInput = serverInput;

        SetConnected();

        // Start reading messages in the background. We use the rarer pattern of new Task + Start
        // in order to ensure that the body of the task will always see _readTask initialized.
        // It is then able to reliably null it out on completion.
        var readTask = new Task<Task>(
            thisRef => ((StreamClientSessionTransport)thisRef!).ReadMessagesAsync(_shutdownCts.Token),
            this,
            TaskCreationOptions.DenyChildAttach);
        _readTask = readTask.Unwrap();
        readTask.Start();
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="StreamClientSessionTransport"/> class.
    /// </summary>
    /// <param name="serverInput">
    /// The server's input stream. Messages written to this stream will be sent to the server.
    /// </param>
    /// <param name="serverOutput">
    /// The server's output stream. Messages read from this stream will be received from the server.
    /// </param>
    /// <param name="encoding">
    /// The encoding used for reading and writing messages from the input and output streams. Defaults to UTF-8 without BOM if null.
    /// </param>
    /// <param name="endpointName">
    /// A name that identifies this transport endpoint in logs.
    /// </param>
    /// <param name="loggerFactory">
    /// Optional factory for creating loggers. If null, a NullLogger will be used.
    /// </param>
    /// <remarks>
    /// This constructor starts a background task to read messages from the server output stream.
    /// The transport will be marked as connected once initialized.
    /// </remarks>
    public StreamClientSessionTransport(Stream serverInput, Stream serverOutput, Encoding? encoding, string endpointName, ILoggerFactory? loggerFactory)
        : this(
            new StreamWriter(serverInput, encoding ?? NoBomUtf8Encoding),
#if NET
            new StreamReader(serverOutput, encoding ?? NoBomUtf8Encoding),
#else
            new CancellableStreamReader(serverOutput, encoding ?? NoBomUtf8Encoding),
#endif
            endpointName,
            loggerFactory)
    {
        Throw.IfNull(serverInput);
        Throw.IfNull(serverOutput);
    }

    /// <inheritdoc/>
    public override async Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
    {
        string id = "(no id)";
        if (message is JsonRpcMessageWithId messageWithId)
        {
            id = messageWithId.Id.ToString();
        }

        var json = JsonSerializer.Serialize(message, McpJsonUtilities.JsonContext.Default.JsonRpcMessage);

        using var _ = await _sendLock.LockAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            // Write the message followed by a newline using our UTF-8 writer
            await _serverInput.WriteLineAsync(json).ConfigureAwait(false);
            await _serverInput.FlushAsync(cancellationToken).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            LogTransportSendFailed(Name, id, ex);
            throw new IOException("Failed to send message.", ex);
        }
    }

    /// <inheritdoc/>
    public override ValueTask DisposeAsync() =>
        CleanupAsync(cancellationToken: CancellationToken.None);

    private async Task ReadMessagesAsync(CancellationToken cancellationToken)
    {
        Exception? error = null;
        try
        {
            LogTransportEnteringReadMessagesLoop(Name);

            while (true)
            {
                if (await _serverOutput.ReadLineAsync(cancellationToken).ConfigureAwait(false) is not string line)
                {
                    LogTransportEndOfStream(Name);
                    break;
                }

                if (string.IsNullOrWhiteSpace(line))
                {
                    continue;
                }

                LogTransportReceivedMessageSensitive(Name, line);

                await ProcessMessageAsync(line, cancellationToken).ConfigureAwait(false);
            }
        }
        catch (OperationCanceledException)
        {
            LogTransportReadMessagesCancelled(Name);
        }
        catch (Exception ex)
        {
            error = ex;
            LogTransportReadMessagesFailed(Name, ex);
        }
        finally
        {
            _readTask = null;
            await CleanupAsync(error, cancellationToken).ConfigureAwait(false);
        }
    }

    private async Task ProcessMessageAsync(string line, CancellationToken cancellationToken)
    {
        try
        {
            var message = (JsonRpcMessage?)JsonSerializer.Deserialize(line.AsSpan().Trim(), McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonRpcMessage)));
            if (message != null)
            {
                await WriteMessageAsync(message, cancellationToken).ConfigureAwait(false);
            }
            else
            {
                LogTransportMessageParseUnexpectedTypeSensitive(Name, line);
            }
        }
        catch (JsonException ex)
        {
            if (Logger.IsEnabled(LogLevel.Trace))
            {
                LogTransportMessageParseFailedSensitive(Name, line, ex);
            }
            else
            {
                LogTransportMessageParseFailed(Name, ex);
            }
        }
    }

    protected virtual async ValueTask CleanupAsync(Exception? error = null, CancellationToken cancellationToken = default)
    {
        LogTransportShuttingDown(Name);

        if (Interlocked.Exchange(ref _shutdownCts, null) is { } shutdownCts)
        {
            await shutdownCts.CancelAsync().ConfigureAwait(false);
            shutdownCts.Dispose();
        }

        if (Interlocked.Exchange(ref _readTask, null) is Task readTask)
        {
            try
            {
                await readTask.WaitAsync(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
            }
            catch (Exception ex)
            {
                LogTransportCleanupReadTaskFailed(Name, ex);
            }
        }

        SetDisconnected(error);
        LogTransportShutDown(Name);
    }
}

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol.Protocol;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously

namespace ModelContextProtocol.Client;

/// <summary>
/// Provides a <see cref="IClientTransport"/> implemented via "stdio" (standard input/output).
/// </summary>
/// <remarks>
/// <para>
/// This transport launches an external process and communicates with it through standard input and output streams.
/// It's used to connect to MCP servers launched and hosted in child processes.
/// </para>
/// <para>
/// The transport manages the entire lifecycle of the process: starting it with specified command-line arguments
/// and environment variables, handling output, and properly terminating the process when the transport is closed.
/// </para>
/// </remarks>
public sealed partial class StdioClientTransport : IClientTransport
{
    private readonly StdioClientTransportOptions _options;
    private readonly ILoggerFactory? _loggerFactory;

    /// <summary>
    /// Initializes a new instance of the <see cref="StdioClientTransport"/> class.
    /// </summary>
    /// <param name="options">Configuration options for the transport, including the command to execute, arguments, working directory, and environment variables.</param>
    /// <param name="loggerFactory">Logger factory for creating loggers used for diagnostic output during transport operations.</param>
    public StdioClientTransport(StdioClientTransportOptions options, ILoggerFactory? loggerFactory = null)
    {
        Throw.IfNull(options);

        _options = options;
        _loggerFactory = loggerFactory;
        Name = options.Name ?? $"stdio-{Regex.Replace(Path.GetFileName(options.Command), @"[\s\.]+", "-")}";
    }

    /// <inheritdoc />
    public string Name { get; }

    /// <inheritdoc />
    public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default)
    {
        string endpointName = Name;

        Process? process = null;
        bool processStarted = false;

        string command = _options.Command;
        IList<string>? arguments = _options.Arguments;
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
            !string.Equals(Path.GetFileName(command), "cmd.exe", StringComparison.OrdinalIgnoreCase))
        {
            // On Windows, for stdio, we need to wrap non-shell commands with cmd.exe /c {command} (usually npx or uvicorn).
            // The stdio transport will not work correctly if the command is not run in a shell.
            arguments = arguments is null or [] ? ["/c", command] : ["/c", command, ..arguments];
            command = "cmd.exe";
        }

        ILogger logger = (ILogger?)_loggerFactory?.CreateLogger<StdioClientTransport>() ?? NullLogger.Instance;
        try
        {
            LogTransportConnecting(logger, endpointName);

            ProcessStartInfo startInfo = new()
            {
                FileName = command,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
                WorkingDirectory = _options.WorkingDirectory ?? Environment.CurrentDirectory,
                StandardOutputEncoding = StreamClientSessionTransport.NoBomUtf8Encoding,
                StandardErrorEncoding = StreamClientSessionTransport.NoBomUtf8Encoding,
#if NET
                StandardInputEncoding = StreamClientSessionTransport.NoBomUtf8Encoding,
#endif
            };

            if (arguments is not null)
            {
#if NET
                foreach (string arg in arguments)
                {
                    startInfo.ArgumentList.Add(arg);
                }
#else
                StringBuilder argsBuilder = new();
                foreach (string arg in arguments)
                {
                    PasteArguments.AppendArgument(argsBuilder, arg);
                }

                startInfo.Arguments = argsBuilder.ToString();
#endif
            }

            if (_options.EnvironmentVariables != null)
            {
                foreach (var entry in _options.EnvironmentVariables)
                {
                    startInfo.Environment[entry.Key] = entry.Value;
                }
            }

            if (logger.IsEnabled(LogLevel.Trace))
            {
                LogCreateProcessForTransportSensitive(logger, endpointName, _options.Command,
                    startInfo.Arguments,
                    string.Join(", ", startInfo.Environment.Select(kvp => $"{kvp.Key}={kvp.Value}")),
                    startInfo.WorkingDirectory);
            }
            else
            {
                LogCreateProcessForTransport(logger, endpointName, _options.Command);
            }

            process = new() { StartInfo = startInfo };

            // Set up stderr handling. Log all stderr output, and keep the last
            // few lines in a rolling log for use in exceptions.
            const int MaxStderrLength = 10; // keep the last 10 lines of stderr
            Queue<string> stderrRollingLog = new(MaxStderrLength);
            process.ErrorDataReceived += (sender, args) =>
            {
                string? data = args.Data;
                if (data is not null)
                {
                    lock (stderrRollingLog)
                    {
                        if (stderrRollingLog.Count >= MaxStderrLength)
                        {
                            stderrRollingLog.Dequeue();
                        }

                        stderrRollingLog.Enqueue(data);
                    }

                    _options.StandardErrorLines?.Invoke(data);

                    LogReadStderr(logger, endpointName, data);
                }
            };

            // We need both stdin and stdout to use a no-BOM UTF-8 encoding. On .NET Core,
            // we can use ProcessStartInfo.StandardOutputEncoding/StandardInputEncoding, but
            // StandardInputEncoding doesn't exist on .NET Framework; instead, it always picks
            // up the encoding from Console.InputEncoding. As such, when not targeting .NET Core,
            // we temporarily change Console.InputEncoding to no-BOM UTF-8 around the Process.Start
            // call, to ensure it picks up the correct encoding.
#if NET
            processStarted = process.Start();
#else
            Encoding originalInputEncoding = Console.InputEncoding;
            try
            {
                Console.InputEncoding = StreamClientSessionTransport.NoBomUtf8Encoding;
                processStarted = process.Start();
            }
            finally
            {
                Console.InputEncoding = originalInputEncoding;
            }
#endif

            if (!processStarted)
            {
                LogTransportProcessStartFailed(logger, endpointName);
                throw new IOException("Failed to start MCP server process.");
            }

            LogTransportProcessStarted(logger, endpointName, process.Id);

            process.BeginErrorReadLine();

            return new StdioClientSessionTransport(_options, process, endpointName, stderrRollingLog, _loggerFactory);
        }
        catch (Exception ex)
        {
            LogTransportConnectFailed(logger, endpointName, ex);

            try
            {
                DisposeProcess(process, processStarted, _options.ShutdownTimeout, endpointName);
            }
            catch (Exception ex2)
            {
                LogTransportShutdownFailed(logger, endpointName, ex2);
            }

            throw new IOException("Failed to connect transport.", ex);
        }
    }

    internal static void DisposeProcess(
        Process? process, bool processRunning, TimeSpan shutdownTimeout, string endpointName)
    {
        if (process is not null)
        {
            try
            {
                processRunning = processRunning && !HasExited(process);
                if (processRunning)
                {
                    // Wait for the process to exit.
                    // Kill the while process tree because the process may spawn child processes
                    // and Node.js does not kill its children when it exits properly.
                    process.KillTree(shutdownTimeout);
                }
            }
            finally
            {
                process.Dispose();
            }
        }
    }

    /// <summary>Gets whether <paramref name="process"/> has exited.</summary>
    internal static bool HasExited(Process process)
    {
        try
        {
            return process.HasExited;
        }
        catch
        {
            return true;
        }
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} connecting.")]
    private static partial void LogTransportConnecting(ILogger logger, string endpointName);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} starting server process. Command: '{Command}'.")]
    private static partial void LogCreateProcessForTransport(ILogger logger, string endpointName, string command);

    [LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} starting server process. Command: '{Command}', Arguments: {Arguments}, Environment: {Environment}, Working directory: {WorkingDirectory}.")]
    private static partial void LogCreateProcessForTransportSensitive(ILogger logger, string endpointName, string command, string? arguments, string environment, string workingDirectory);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} failed to start server process.")]
    private static partial void LogTransportProcessStartFailed(ILogger logger, string endpointName);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} received stderr log: '{Data}'.")]
    private static partial void LogReadStderr(ILogger logger, string endpointName, string data);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} started server process with PID {ProcessId}.")]
    private static partial void LogTransportProcessStarted(ILogger logger, string endpointName, int processId);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} connect failed.")]
    private static partial void LogTransportConnectFailed(ILogger logger, string endpointName, Exception exception);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} shutdown failed.")]
    private static partial void LogTransportShutdownFailed(ILogger logger, string endpointName, Exception exception);
}

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol.Protocol;
using System.Diagnostics;
using System.Net.Http.Headers;
using System.Net.ServerSentEvents;
using System.Text.Json;
using System.Threading.Channels;

namespace ModelContextProtocol.Client;

/// <summary>
/// The ServerSideEvents client transport implementation
/// </summary>
internal sealed partial class SseClientSessionTransport : TransportBase
{
    private readonly McpHttpClient _httpClient;
    private readonly SseClientTransportOptions _options;
    private readonly Uri _sseEndpoint;
    private Uri? _messageEndpoint;
    private readonly CancellationTokenSource _connectionCts;
    private Task? _receiveTask;
    private readonly ILogger _logger;
    private readonly TaskCompletionSource<bool> _connectionEstablished;

    /// <summary>
    /// SSE transport for a single session. Unlike stdio it does not launch a process, but connects to an existing server.
    /// The HTTP server can be local or remote, and must support the SSE protocol.
    /// </summary>
    public SseClientSessionTransport(
        string endpointName,
        SseClientTransportOptions transportOptions,
        McpHttpClient httpClient,
        Channel<JsonRpcMessage>? messageChannel,
        ILoggerFactory? loggerFactory)
        : base(endpointName, messageChannel, loggerFactory)
    {
        Throw.IfNull(transportOptions);
        Throw.IfNull(httpClient);

        _options = transportOptions;
        _sseEndpoint = transportOptions.Endpoint;
        _httpClient = httpClient;
        _connectionCts = new CancellationTokenSource();
        _logger = (ILogger?)loggerFactory?.CreateLogger<SseClientTransport>() ?? NullLogger.Instance;
        _connectionEstablished = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
    }

    /// <inheritdoc/>
    public async Task ConnectAsync(CancellationToken cancellationToken = default)
    {
        Debug.Assert(!IsConnected);
        try
        {
            // Start message receiving loop
            _receiveTask = ReceiveMessagesAsync(_connectionCts.Token);

            await _connectionEstablished.Task.WaitAsync(_options.ConnectionTimeout, cancellationToken).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            LogTransportConnectFailed(Name, ex);
            await CloseAsync().ConfigureAwait(false);
            throw new InvalidOperationException("Failed to connect transport", ex);
        }
    }

    /// <inheritdoc/>
    public override async Task SendMessageAsync(
        JsonRpcMessage message,
        CancellationToken cancellationToken = default)
    {
        if (_messageEndpoint == null)
            throw new InvalidOperationException("Transport not connected");

        string messageId = "(no id)";

        if (message is JsonRpcMessageWithId messageWithId)
        {
            messageId = messageWithId.Id.ToString();
        }

        using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, _messageEndpoint);
        StreamableHttpClientSessionTransport.CopyAdditionalHeaders(httpRequestMessage.Headers, _options.AdditionalHeaders, sessionId: null, protocolVersion: null);
        var response = await _httpClient.SendAsync(httpRequestMessage, message, cancellationToken).ConfigureAwait(false);

        if (!response.IsSuccessStatusCode)
        {
            if (_logger.IsEnabled(LogLevel.Trace))
            {
                LogRejectedPostSensitive(Name, messageId, await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false));
            }
            else
            {
                LogRejectedPost(Name, messageId);
            }

            response.EnsureSuccessStatusCode();
        }
    }

    private async Task CloseAsync()
    {
        try
        {
            await _connectionCts.CancelAsync().ConfigureAwait(false);

            try
            {
                if (_receiveTask != null)
                {
                    await _receiveTask.ConfigureAwait(false);
                }
            }
            finally
            {
                _connectionCts.Dispose();
            }
        }
        finally
        {
            SetDisconnected();
        }
    }

    /// <inheritdoc/>
    public override async ValueTask DisposeAsync()
    {
        try
        {
            await CloseAsync().ConfigureAwait(false);
        }
        catch (Exception)
        {
            // Ignore exceptions on close
        }
    }

    private async Task ReceiveMessagesAsync(CancellationToken cancellationToken)
    {
        try
        {
            using var request = new HttpRequestMessage(HttpMethod.Get, _sseEndpoint);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream"));
            StreamableHttpClientSessionTransport.CopyAdditionalHeaders(request.Headers, _options.AdditionalHeaders, sessionId: null, protocolVersion: null);

            using var response = await _httpClient.SendAsync(request, message: null, cancellationToken).ConfigureAwait(false);

            response.EnsureSuccessStatusCode();

            using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);

            await foreach (SseItem<string> sseEvent in SseParser.Create(stream).EnumerateAsync(cancellationToken).ConfigureAwait(false))
            {
                switch (sseEvent.EventType)
                {
                    case "endpoint":
                        HandleEndpointEvent(sseEvent.Data);
                        break;

                    case "message":
                        await ProcessSseMessage(sseEvent.Data, cancellationToken).ConfigureAwait(false);
                        break;
                }
            }
        }
        catch (Exception ex)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                // Normal shutdown
                LogTransportReadMessagesCancelled(Name);
                _connectionEstablished.TrySetCanceled(cancellationToken);
            }
            else
            {
                LogTransportReadMessagesFailed(Name, ex);
                _connectionEstablished.TrySetException(ex);
                throw;
            }
        }
        finally
        {
            SetDisconnected();
        }
    }

    private async Task ProcessSseMessage(string data, CancellationToken cancellationToken)
    {
        if (!IsConnected)
        {
            LogTransportMessageReceivedBeforeConnected(Name);
            return;
        }

        try
        {
            var message = JsonSerializer.Deserialize(data, McpJsonUtilities.JsonContext.Default.JsonRpcMessage);
            if (message == null)
            {
                LogTransportMessageParseUnexpectedTypeSensitive(Name, data);
                return;
            }

            await WriteMessageAsync(message, cancellationToken).ConfigureAwait(false);
        }
        catch (JsonException ex)
        {
            if (_logger.IsEnabled(LogLevel.Trace))
            {
                LogTransportMessageParseFailedSensitive(Name, data, ex);
            }
            else
            {
                LogTransportMessageParseFailed(Name, ex);
            }
        }
    }

    private void HandleEndpointEvent(string data)
    {
        if (string.IsNullOrEmpty(data))
        {
            LogTransportEndpointEventInvalid(Name);
            return;
        }

        // If data is an absolute URL, the Uri will be constructed entirely from it and not the _sseEndpoint.
        _messageEndpoint = new Uri(_sseEndpoint, data);

        // Set connected state
        SetConnected();
        _connectionEstablished.TrySetResult(true);
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} accepted SSE transport POST for message ID '{MessageId}'.")]
    private partial void LogAcceptedPost(string endpointName, string messageId);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} rejected SSE transport POST for message ID '{MessageId}'.")]
    private partial void LogRejectedPost(string endpointName, string messageId);

    [LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} rejected SSE transport POST for message ID '{MessageId}'. Server response: '{responseContent}'.")]
    private partial void LogRejectedPostSensitive(string endpointName, string messageId, string responseContent);
}

using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ModelContextProtocol;

/// <summary>
/// Helper class for working with processes.
/// </summary>
internal static class ProcessHelper
{
    private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
    private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);

    /// <summary>
    /// Kills a process and all of its child processes (entire process tree).
    /// </summary>
    /// <param name="process">The process to terminate along with its child processes.</param>
    /// <remarks>
    /// This method uses a default timeout of 30 seconds when waiting for processes to exit.
    /// On Windows, this uses the "taskkill" command with the /T flag.
    /// On non-Windows platforms, it recursively identifies and terminates child processes.
    /// </remarks>
    public static void KillTree(this Process process) => process.KillTree(_defaultTimeout);

    /// <summary>
    /// Kills a process and all of its child processes (entire process tree) with a specified timeout.
    /// </summary>
    /// <param name="process">The process to terminate along with its child processes.</param>
    /// <param name="timeout">The maximum time to wait for the processes to exit.</param>
    /// <remarks>
    /// On Windows, this uses the "taskkill" command with the /T flag to terminate the process tree.
    /// On non-Windows platforms, it recursively identifies and terminates child processes.
    /// The method waits for the specified timeout for processes to exit before continuing.
    /// This is particularly useful for applications that spawn child processes (like Node.js)
    /// that wouldn't be terminated automatically when the parent process exits.
    /// </remarks>
    public static void KillTree(this Process process, TimeSpan timeout)
    {
        var pid = process.Id;
        if (_isWindows)
        {
            RunProcessAndWaitForExit(
                "taskkill",
                $"/T /F /PID {pid}",
                timeout,
                out var _);
        }
        else
        {
            var children = new HashSet<int>();
            GetAllChildIdsUnix(pid, children, timeout);
            foreach (var childId in children)
            {
                KillProcessUnix(childId, timeout);
            }
            KillProcessUnix(pid, timeout);
        }

        // wait until the process finishes exiting/getting killed.
        // We don't want to wait forever here because the task is already supposed to be dieing, we just want to give it long enough
        // to try and flush what it can and stop. If it cannot do that in a reasonable time frame then we will just ignore it.
        process.WaitForExit((int)timeout.TotalMilliseconds);
    }

    private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
    {
        var exitcode = RunProcessAndWaitForExit(
            "pgrep",
            $"-P {parentId}",
            timeout,
            out var stdout);

        if (exitcode == 0 && !string.IsNullOrEmpty(stdout))
        {
            using var reader = new StringReader(stdout);
            while (true)
            {
                var text = reader.ReadLine();
                if (text == null)
                    return;

                if (int.TryParse(text, out var id))
                {
                    children.Add(id);
                    // Recursively get the children
                    GetAllChildIdsUnix(id, children, timeout);
                }
            }
        }
    }

    private static void KillProcessUnix(int processId, TimeSpan timeout)
    {
        RunProcessAndWaitForExit(
            "kill",
            $"-TERM {processId}",
            timeout,
            out var _);
    }

    private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string? stdout)
    {
        var startInfo = new ProcessStartInfo
        {
            FileName = fileName,
            Arguments = arguments,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
        };

        stdout = null;

        var process = Process.Start(startInfo);
        if (process == null)
            return -1;

        if (process.WaitForExit((int)timeout.TotalMilliseconds))
        {
            stdout = process.StandardOutput.ReadToEnd();
        }
        else
        {
            process.Kill();
        }

        return process.ExitCode;
    }
}

using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents any JSON-RPC message used in the Model Context Protocol (MCP).
/// </summary>
/// <remarks>
/// This interface serves as the foundation for all message types in the JSON-RPC 2.0 protocol
/// used by MCP, including requests, responses, notifications, and errors. JSON-RPC is a stateless,
/// lightweight remote procedure call (RPC) protocol that uses JSON as its data format.
/// </remarks>
[JsonConverter(typeof(Converter))]
public abstract class JsonRpcMessage
{
    /// <summary>Prevent external derivations.</summary>
    private protected JsonRpcMessage()
    {
    }

    /// <summary>
    /// Gets the JSON-RPC protocol version used.
    /// </summary>
    /// <inheritdoc />
    [JsonPropertyName("jsonrpc")]
    public string JsonRpc { get; init; } = "2.0";

    /// <summary>
    /// Gets or sets the transport the <see cref="JsonRpcMessage"/> was received on or should be sent over.
    /// </summary>
    /// <remarks>
    /// This is used to support the Streamable HTTP transport where the specification states that the server
    /// SHOULD include JSON-RPC responses in the HTTP response body for the POST request containing
    /// the corresponding JSON-RPC request. It may be <see langword="null"/> for other transports.
    /// </remarks>
    [JsonIgnore]
    public ITransport? RelatedTransport { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="ExecutionContext"/> that should be used to run any handlers
    /// </summary>
    /// <remarks>
    /// This is used to support the Streamable HTTP transport in its default stateful mode. In this mode,
    /// the <see cref="IMcpServer"/> outlives the initial HTTP request context it was created on, and new
    /// JSON-RPC messages can originate from future HTTP requests. This allows the transport to flow the
    /// context with the JSON-RPC message. This is particularly useful for enabling IHttpContextAccessor
    /// in tool calls.
    /// </remarks>
    [JsonIgnore]
    public ExecutionContext? ExecutionContext { get; set; }

    /// <summary>
    /// Provides a <see cref="JsonConverter"/> for <see cref="JsonRpcMessage"/> messages,
    /// handling polymorphic deserialization of different message types.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This converter is responsible for correctly deserializing JSON-RPC messages into their appropriate
    /// concrete types based on the message structure. It analyzes the JSON payload and determines if it
    /// represents a request, notification, successful response, or error response.
    /// </para>
    /// <para>
    /// The type determination rules follow the JSON-RPC 2.0 specification:
    /// <list type="bullet">
    /// <item><description>Messages with "method" and "id" properties are deserialized as <see cref="JsonRpcRequest"/>.</description></item>
    /// <item><description>Messages with "method" but no "id" property are deserialized as <see cref="JsonRpcNotification"/>.</description></item>
    /// <item><description>Messages with "id" and "result" properties are deserialized as <see cref="JsonRpcResponse"/>.</description></item>
    /// <item><description>Messages with "id" and "error" properties are deserialized as <see cref="JsonRpcError"/>.</description></item>
    /// </list>
    /// </para>
    /// </remarks>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed class Converter : JsonConverter<JsonRpcMessage>
    {
        /// <inheritdoc/>
        public override JsonRpcMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException("Expected StartObject token");
            }

            using var doc = JsonDocument.ParseValue(ref reader);
            var root = doc.RootElement;

            // All JSON-RPC messages must have a jsonrpc property with value "2.0"
            if (!root.TryGetProperty("jsonrpc", out var versionProperty) ||
                versionProperty.GetString() != "2.0")
            {
                throw new JsonException("Invalid or missing jsonrpc version");
            }

            // Determine the message type based on the presence of id, method, and error properties
            bool hasId = root.TryGetProperty("id", out _);
            bool hasMethod = root.TryGetProperty("method", out _);
            bool hasError = root.TryGetProperty("error", out _);

            var rawText = root.GetRawText();

            // Messages with an id but no method are responses
            if (hasId && !hasMethod)
            {
                // Messages with an error property are error responses
                if (hasError)
                {
                    return JsonSerializer.Deserialize(rawText, options.GetTypeInfo<JsonRpcError>());
                }

                // Messages with a result property are success responses
                if (root.TryGetProperty("result", out _))
                {
                    return JsonSerializer.Deserialize(rawText, options.GetTypeInfo<JsonRpcResponse>());
                }

                throw new JsonException("Response must have either result or error");
            }

            // Messages with a method but no id are notifications
            if (hasMethod && !hasId)
            {
                return JsonSerializer.Deserialize(rawText, options.GetTypeInfo<JsonRpcNotification>());
            }

            // Messages with both method and id are requests
            if (hasMethod && hasId)
            {
                return JsonSerializer.Deserialize(rawText, options.GetTypeInfo<JsonRpcRequest>());
            }

            throw new JsonException("Invalid JSON-RPC message format");
        }

        /// <inheritdoc/>
        public override void Write(Utf8JsonWriter writer, JsonRpcMessage value, JsonSerializerOptions options)
        {
            switch (value)
            {
                case JsonRpcRequest request:
                    JsonSerializer.Serialize(writer, request, options.GetTypeInfo<JsonRpcRequest>());
                    break;
                case JsonRpcNotification notification:
                    JsonSerializer.Serialize(writer, notification, options.GetTypeInfo<JsonRpcNotification>());
                    break;
                case JsonRpcResponse response:
                    JsonSerializer.Serialize(writer, response, options.GetTypeInfo<JsonRpcResponse>());
                    break;
                case JsonRpcError error:
                    JsonSerializer.Serialize(writer, error, options.GetTypeInfo<JsonRpcError>());
                    break;
                default:
                    throw new JsonException($"Unknown JSON-RPC message type: {value.GetType()}");
            }
        }
    }
}

using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
using System.Diagnostics;

namespace ModelContextProtocol.Client;

/// <summary>Provides the client side of a stdio-based session transport.</summary>
internal sealed class StdioClientSessionTransport : StreamClientSessionTransport
{
    private readonly StdioClientTransportOptions _options;
    private readonly Process _process;
    private readonly Queue<string> _stderrRollingLog;
    private int _cleanedUp = 0;

    public StdioClientSessionTransport(StdioClientTransportOptions options, Process process, string endpointName, Queue<string> stderrRollingLog, ILoggerFactory? loggerFactory)
        : base(process.StandardInput.BaseStream, process.StandardOutput.BaseStream, encoding: null, endpointName, loggerFactory)
    {
        _process = process;
        _options = options;
        _stderrRollingLog = stderrRollingLog;
    }

    /// <inheritdoc/>
    public override async Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
    {
        try
        {
            await base.SendMessageAsync(message, cancellationToken);
        }
        catch (IOException)
        {
            // We failed to send due to an I/O error. If the server process has exited, which is then very likely the cause
            // for the I/O error, we should throw an exception for that instead.
            if (await GetUnexpectedExitExceptionAsync(cancellationToken).ConfigureAwait(false) is Exception processExitException)
            {
                throw processExitException;
            }

            throw;
        }
    }

    /// <inheritdoc/>
    protected override async ValueTask CleanupAsync(Exception? error = null, CancellationToken cancellationToken = default)
    {
        // Only clean up once.
        if (Interlocked.Exchange(ref _cleanedUp, 1) != 0)
        {
            return;
        }

        // We've not yet forcefully terminated the server. If it's already shut down, something went wrong,
        // so create an exception with details about that.
        error ??= await GetUnexpectedExitExceptionAsync(cancellationToken).ConfigureAwait(false);

        // Now terminate the server process.
        try
        {
            StdioClientTransport.DisposeProcess(_process, processRunning: true, _options.ShutdownTimeout, Name);
        }
        catch (Exception ex)
        {
            LogTransportShutdownFailed(Name, ex);
        }

        // And handle cleanup in the base type.
        await base.CleanupAsync(error, cancellationToken);
    }

    private async ValueTask<Exception?> GetUnexpectedExitExceptionAsync(CancellationToken cancellationToken)
    {
        if (!StdioClientTransport.HasExited(_process))
        {
            return null;
        }

        Debug.Assert(StdioClientTransport.HasExited(_process));
        try
        {
            // The process has exited, but we still need to ensure stderr has been flushed.
#if NET
            await _process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
#else
            _process.WaitForExit();
#endif
        }
        catch { }

        string errorMessage = "MCP server process exited unexpectedly";

        string? exitCode = null;
        try
        {
            exitCode = $" (exit code: {(uint)_process.ExitCode})";
        }
        catch { }

        lock (_stderrRollingLog)
        {
            if (_stderrRollingLog.Count > 0)
            {
                errorMessage =
                    $"{errorMessage}{exitCode}{Environment.NewLine}" +
                    $"Server's stderr tail:{Environment.NewLine}" +
                    $"{string.Join(Environment.NewLine, _stderrRollingLog)}";
            }
        }

        return new IOException(errorMessage);
    }
}

using ModelContextProtocol.Protocol;
using System.Buffers;
using System.Net.ServerSentEvents;
using System.Text;
using System.Text.Json;
using System.Threading.Channels;

namespace ModelContextProtocol.Server;

internal sealed class SseWriter(string? messageEndpoint = null, BoundedChannelOptions? channelOptions = null) : IAsyncDisposable
{
    private readonly Channel<SseItem<JsonRpcMessage?>> _messages = Channel.CreateBounded<SseItem<JsonRpcMessage?>>(channelOptions ?? new BoundedChannelOptions(1)
    {
        SingleReader = true,
        SingleWriter = false,
    });

    private Utf8JsonWriter? _jsonWriter;
    private Task? _writeTask;
    private CancellationToken? _writeCancellationToken;

    private readonly SemaphoreSlim _disposeLock = new(1, 1);
    private bool _disposed;

    public Func<IAsyncEnumerable<SseItem<JsonRpcMessage?>>, CancellationToken, IAsyncEnumerable<SseItem<JsonRpcMessage?>>>? MessageFilter { get; set; }

    public Task WriteAllAsync(Stream sseResponseStream, CancellationToken cancellationToken)
    {
        // When messageEndpoint is set, the very first SSE event isn't really an IJsonRpcMessage, but there's no API to write a single
        // item of a different type, so we fib and special-case the "endpoint" event type in the formatter.
        if (messageEndpoint is not null && !_messages.Writer.TryWrite(new SseItem<JsonRpcMessage?>(null, "endpoint")))
        {
            throw new InvalidOperationException("You must call RunAsync before calling SendMessageAsync.");
        }

        _writeCancellationToken = cancellationToken;

        var messages = _messages.Reader.ReadAllAsync(cancellationToken);
        if (MessageFilter is not null)
        {
            messages = MessageFilter(messages, cancellationToken);
        }

        _writeTask = SseFormatter.WriteAsync(messages, sseResponseStream, WriteJsonRpcMessageToBuffer, cancellationToken);
        return _writeTask;
    }

    public async Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(message);

        using var _ = await _disposeLock.LockAsync(cancellationToken).ConfigureAwait(false);

        if (_disposed)
        {
            // Don't throw an ODE, because this is disposed internally when the transport disconnects due to an abort
            // or sending all the responses for the a give given Streamable HTTP POST request, so the user might not be at fault.
            // There's precedence for no-oping here similar to writing to the response body of an aborted request in ASP.NET Core.
            return;
        }

        // Emit redundant "event: message" lines for better compatibility with other SDKs.
        await _messages.Writer.WriteAsync(new SseItem<JsonRpcMessage?>(message, SseParser.EventTypeDefault), cancellationToken).ConfigureAwait(false);
    }

    public async ValueTask DisposeAsync()
    {
        using var _ = await _disposeLock.LockAsync().ConfigureAwait(false);

        if (_disposed)
        {
            return;
        }

        _messages.Writer.Complete();
        try
        {
            if (_writeTask is not null)
            {
                await _writeTask.ConfigureAwait(false);
            }
        }
        catch (OperationCanceledException) when (_writeCancellationToken?.IsCancellationRequested == true)
        {
            // Ignore exceptions caused by intentional cancellation during shutdown.
        }
        finally
        {
            _jsonWriter?.Dispose();
            _disposed = true;
        }
    }

    private void WriteJsonRpcMessageToBuffer(SseItem<JsonRpcMessage?> item, IBufferWriter<byte> writer)
    {
        if (item.EventType == "endpoint" && messageEndpoint is not null)
        {
            writer.Write(Encoding.UTF8.GetBytes(messageEndpoint));
            return;
        }

        JsonSerializer.Serialize(GetUtf8JsonWriter(writer), item.Data, McpJsonUtilities.JsonContext.Default.JsonRpcMessage!);
    }

    private Utf8JsonWriter GetUtf8JsonWriter(IBufferWriter<byte> writer)
    {
        if (_jsonWriter is null)
        {
            _jsonWriter = new Utf8JsonWriter(writer);
        }
        else
        {
            _jsonWriter.Reset(writer);
        }

        return _jsonWriter;
    }
}

using System.Runtime.InteropServices;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ModelContextProtocol.Server;

namespace ModelContextProtocol.AspNetCore;

internal sealed partial class IdleTrackingBackgroundService(
    StreamableHttpHandler handler,
    IOptions<HttpServerTransportOptions> options,
    IHostApplicationLifetime appLifetime,
    ILogger<IdleTrackingBackgroundService> logger) : BackgroundService
{
    // The compiler will complain about the parameter being unused otherwise despite the source generator.
    private readonly ILogger _logger = logger;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // Still run loop given infinite IdleTimeout to enforce the MaxIdleSessionCount and assist graceful shutdown.
        if (options.Value.IdleTimeout != Timeout.InfiniteTimeSpan)
        {
            ArgumentOutOfRangeException.ThrowIfLessThan(options.Value.IdleTimeout, TimeSpan.Zero);
        }

        ArgumentOutOfRangeException.ThrowIfLessThan(options.Value.MaxIdleSessionCount, 0);

        try
        {
            var timeProvider = options.Value.TimeProvider;
            using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5), timeProvider);

            var idleTimeoutTicks = options.Value.IdleTimeout.Ticks;
            var maxIdleSessionCount = options.Value.MaxIdleSessionCount;

            // Create two lists that will be reused between runs.
            // This assumes that the number of idle sessions is not breached frequently.
            // If the idle sessions often breach the maximum, a priority queue could be considered.
            var idleSessionsTimestamps = new List<long>();
            var idleSessionSessionIds = new List<string>();

            while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken))
            {
                var idleActivityCutoff = idleTimeoutTicks switch
                {
                    < 0 => long.MinValue,
                    var ticks => timeProvider.GetTimestamp() - ticks,
                };

                foreach (var (_, session) in handler.Sessions)
                {
                    if (session.IsActive || session.SessionClosed.IsCancellationRequested)
                    {
                        // There's a request currently active or the session is already being closed.
                        continue;
                    }

                    if (session.LastActivityTicks < idleActivityCutoff)
                    {
                        RemoveAndCloseSession(session.Id);
                        continue;
                    }

                    // Add the timestamp and the session
                    idleSessionsTimestamps.Add(session.LastActivityTicks);
                    idleSessionSessionIds.Add(session.Id);

                    // Emit critical log at most once every 5 seconds the idle count it exceeded,
                    // since the IdleTimeout will no longer be respected.
                    if (idleSessionsTimestamps.Count == maxIdleSessionCount + 1)
                    {
                        LogMaxSessionIdleCountExceeded(maxIdleSessionCount);
                    }
                }

                if (idleSessionsTimestamps.Count > maxIdleSessionCount)
                {
                    var timestamps = CollectionsMarshal.AsSpan(idleSessionsTimestamps);

                    // Sort only if the maximum is breached and sort solely by the timestamp. Sort both collections.
                    timestamps.Sort(CollectionsMarshal.AsSpan(idleSessionSessionIds));

                    var sessionsToPrune = CollectionsMarshal.AsSpan(idleSessionSessionIds)[..^maxIdleSessionCount];
                    foreach (var id in sessionsToPrune)
                    {
                        RemoveAndCloseSession(id);
                    }
                }

                idleSessionsTimestamps.Clear();
                idleSessionSessionIds.Clear();
            }
        }
        catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
        {
        }
        finally
        {
            try
            {
                List<Task> disposeSessionTasks = [];

                foreach (var (sessionKey, _) in handler.Sessions)
                {
                    if (handler.Sessions.TryRemove(sessionKey, out var session))
                    {
                        disposeSessionTasks.Add(DisposeSessionAsync(session));
                    }
                }

                await Task.WhenAll(disposeSessionTasks);
            }
            finally
            {
                if (!stoppingToken.IsCancellationRequested)
                {
                    // Something went terribly wrong. A very unexpected exception must be bubbling up, but let's ensure we also stop the application,
                    // so that it hopefully gets looked at and restarted. This shouldn't really be reachable.
                    appLifetime.StopApplication();
                    IdleTrackingBackgroundServiceStoppedUnexpectedly();
                }
            }
        }
    }

    private void RemoveAndCloseSession(string sessionId)
    {
        if (!handler.Sessions.TryRemove(sessionId, out var session))
        {
            return;
        }

        LogSessionIdle(session.Id);
        // Don't slow down the idle tracking loop. DisposeSessionAsync logs. We only await during graceful shutdown.
        _ = DisposeSessionAsync(session);
    }

    private async Task DisposeSessionAsync(HttpMcpSession<StreamableHttpServerTransport> session)
    {
        try
        {
            await session.DisposeAsync();
        }
        catch (Exception ex)
        {
            LogSessionDisposeError(session.Id, ex);
        }
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "Closing idle session {sessionId}.")]
    private partial void LogSessionIdle(string sessionId);

    [LoggerMessage(Level = LogLevel.Error, Message = "Error disposing session {sessionId}.")]
    private partial void LogSessionDisposeError(string sessionId, Exception ex);

    [LoggerMessage(Level = LogLevel.Critical, Message = "Exceeded maximum of {maxIdleSessionCount} idle sessions. Now closing sessions active more recently than configured IdleTimeout.")]
    private partial void LogMaxSessionIdleCountExceeded(int maxIdleSessionCount);

    [LoggerMessage(Level = LogLevel.Critical, Message = "The IdleTrackingBackgroundService has stopped unexpectedly.")]
    private partial void IdleTrackingBackgroundServiceStoppedUnexpectedly();
}

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Diagnostics;
using System.Threading.Channels;

namespace ModelContextProtocol.Protocol;

///
/// Provides a base class for implementing <see cref="ITransport"/>.
///
/// <remarks>
/// <para>
/// The <see cref="TransportBase"/> class provides core functionality required by most <see cref="ITransport"/>
/// implementations, including message channel management, connection state tracking, and logging support.
/// </para>
/// <para>
/// Custom transport implementations should inherit from this class and implement the abstract
/// <see cref="SendMessageAsync(JsonRpcMessage, CancellationToken)"/> and <see cref="DisposeAsync()"/> methods
/// to handle the specific transport mechanism being used.
/// </para>
/// </remarks>
public abstract partial class TransportBase : ITransport
{
    private readonly Channel<JsonRpcMessage> _messageChannel;
    private readonly ILogger _logger;
    private volatile int _state = StateInitial;

    /// The transport has not yet been connected.
    private const int StateInitial = 0;
    /// The transport is connected.
    private const int StateConnected = 1;
    /// The transport was previously connected and is now disconnected.
    private const int StateDisconnected = 2;

    ///
    /// Initializes a new instance of the <see cref="TransportBase"/> class.
    ///
    protected TransportBase(string name, ILoggerFactory? loggerFactory)
        : this(name, null, loggerFactory)
    {
    }

    ///
    /// Initializes a new instance of the <see cref="TransportBase"/> class with a specified channel to back <see cref="MessageReader"/>.
    ///
    internal TransportBase(string name, Channel<JsonRpcMessage>? messageChannel, ILoggerFactory? loggerFactory)
    {
        Name = name;
        _logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance;

        // Unbounded channel to prevent blocking on writes. Ensure AutoDetectingClientSessionTransport matches this.
        _messageChannel = messageChannel ?? Channel.CreateUnbounded<JsonRpcMessage>(new UnboundedChannelOptions
        {
            SingleReader = true,
            SingleWriter = false,
        });
    }

    /// Gets the logger used by this transport.
    private protected ILogger Logger => _logger;

    /// <inheritdoc/>
    public virtual string? SessionId { get; protected set; }

    ///
    /// Gets the name that identifies this transport endpoint in logs.
    ///
    /// <remarks>
    /// This name is used in log messages to identify the source of transport-related events.
    /// </remarks>
    protected string Name { get; }

    /// <inheritdoc/>
    public bool IsConnected => _state == StateConnected;

    /// <inheritdoc/>
    public ChannelReader<JsonRpcMessage> MessageReader => _messageChannel.Reader;

    /// <inheritdoc/>
    public abstract Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default);

    /// <inheritdoc/>
    public abstract ValueTask DisposeAsync();

    ///
    /// Writes a message to the message channel.
    ///
    /// <param name="message">The message to write.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    protected async Task WriteMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
    {
        if (!IsConnected)
        {
            throw new InvalidOperationException("Transport is not connected.");
        }

        if (_logger.IsEnabled(LogLevel.Debug))
        {
            var messageId = (message as JsonRpcMessageWithId)?.Id.ToString() ?? "(no id)";
            LogTransportReceivedMessage(Name, messageId);
        }

        bool wrote = _messageChannel.Writer.TryWrite(message);
        Debug.Assert(wrote || !IsConnected, "_messageChannel is unbounded; this should only ever return false if the channel has been closed.");
    }

    ///
    /// Sets the transport to a connected state.
    ///
    protected void SetConnected()
    {
        while (true)
        {
            int state = _state;
            switch (state)
            {
                case StateInitial:
                    if (Interlocked.CompareExchange(ref _state, StateConnected, StateInitial) == StateInitial)
                    {
                        return;
                    }
                    break;

                case StateConnected:
                    return;

                case StateDisconnected:
                    throw new IOException("Transport is already disconnected and can't be reconnected.");

                default:
                    Debug.Fail($"Unexpected state: {state}");
                    return;
            }
        }
    }

    ///
    /// Sets the transport to a disconnected state.
    ///
    /// <param name="error">Optional error information associated with the transport disconnecting. Should be <see langwor="null"/> if the disconnect was graceful and expected.</param>
    protected void SetDisconnected(Exception? error = null)
    {
        int state = _state;
        switch (state)
        {
            case StateInitial:
            case StateConnected:
                _state = StateDisconnected;
                _messageChannel.Writer.TryComplete(error);
                break;

            case StateDisconnected:
                return;

            default:
                Debug.Fail($"Unexpected state: {state}");
                break;
        }
    }

    [LoggerMessage(Level = LogLevel.Error, Message = "{EndpointName} transport connect failed.")]
    private protected partial void LogTransportConnectFailed(string endpointName, Exception exception);

    [LoggerMessage(Level = LogLevel.Error, Message = "{EndpointName} transport send failed for message ID '{MessageId}'.")]
    private protected partial void LogTransportSendFailed(string endpointName, string messageId, Exception exception);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} transport reading messages.")]
    private protected partial void LogTransportEnteringReadMessagesLoop(string endpointName);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} transport completed reading messages.")]
    private protected partial void LogTransportEndOfStream(string endpointName);

    [LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} transport received message. Message: '{Message}'.")]
    private protected partial void LogTransportReceivedMessageSensitive(string endpointName, string message);

    [LoggerMessage(Level = LogLevel.Debug, Message = "{EndpointName} transport received message with ID '{MessageId}'.")]
    private protected partial void LogTransportReceivedMessage(string endpointName, string messageId);

    [LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} transport received unexpected message. Message: '{Message}'.")]
    private protected partial void LogTransportMessageParseUnexpectedTypeSensitive(string endpointName, string message);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} transport message parsing failed.")]
    private protected partial void LogTransportMessageParseFailed(string endpointName, Exception exception);

    [LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} transport message parsing failed. Message: '{Message}'.")]
    private protected partial void LogTransportMessageParseFailedSensitive(string endpointName, string message, Exception exception);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} transport message reading canceled.")]
    private protected partial void LogTransportReadMessagesCancelled(string endpointName);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} transport message reading failed.")]
    private protected partial void LogTransportReadMessagesFailed(string endpointName, Exception exception);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} shutting down.")]
    private protected partial void LogTransportShuttingDown(string endpointName);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} shutdown failed.")]
    private protected partial void LogTransportShutdownFailed(string endpointName, Exception exception);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} shutdown failed waiting for message reading completion.")]
    private protected partial void LogTransportCleanupReadTaskFailed(string endpointName, Exception exception);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} shut down.")]
    private protected partial void LogTransportShutDown(string endpointName);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} received message before connected.")]
    private protected partial void LogTransportMessageReceivedBeforeConnected(string endpointName);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} endpoint event received out of order.")]
    private protected partial void LogTransportEndpointEventInvalid(string endpointName);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} failed to parse event.")]
    private protected partial void LogTransportEndpointEventParseFailed(string endpointName, Exception exception);

    [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} failed to parse event. Message: '{Message}'.")]
    private protected partial void LogTransportEndpointEventParseFailedSensitive(string endpointName, string message, Exception exception);
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

#nullable enable

namespace System.Text
{
    internal ref partial struct ValueStringBuilder
    {
        private char[]? _arrayToReturnToPool;
        private Span<char> _chars;
        private int _pos;

        public ValueStringBuilder(Span<char> initialBuffer)
        {
            _arrayToReturnToPool = null;
            _chars = initialBuffer;
            _pos = 0;
        }

        public ValueStringBuilder(int initialCapacity)
        {
            _arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity);
            _chars = _arrayToReturnToPool;
            _pos = 0;
        }

        public int Length
        {
            get => _pos;
            set
            {
                Debug.Assert(value >= 0);
                Debug.Assert(value <= _chars.Length);
                _pos = value;
            }
        }

        public int Capacity => _chars.Length;

        public void EnsureCapacity(int capacity)
        {
            // This is not expected to be called this with negative capacity
            Debug.Assert(capacity >= 0);

            // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception.
            if ((uint)capacity > (uint)_chars.Length)
                Grow(capacity - _pos);
        }

        /// <summary>
        /// Get a pinnable reference to the builder.
        /// Does not ensure there is a null char after <see cref="Length"/>
        /// This overload is pattern matched in the C# 7.3+ compiler so you can omit
        /// the explicit method call, and write eg "fixed (char* c = builder)"
        /// </summary>
        public ref char GetPinnableReference()
        {
            return ref MemoryMarshal.GetReference(_chars);
        }

        /// <summary>
        /// Get a pinnable reference to the builder.
        /// </summary>
        /// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
        public ref char GetPinnableReference(bool terminate)
        {
            if (terminate)
            {
                EnsureCapacity(Length + 1);
                _chars[Length] = '\0';
            }
            return ref MemoryMarshal.GetReference(_chars);
        }

        public ref char this[int index]
        {
            get
            {
                Debug.Assert(index < _pos);
                return ref _chars[index];
            }
        }

        public override string ToString()
        {
            string s = _chars.Slice(0, _pos).ToString();
            Dispose();
            return s;
        }

        /// <summary>Returns the underlying storage of the builder.</summary>
        public Span<char> RawChars => _chars;

        /// <summary>
        /// Returns a span around the contents of the builder.
        /// </summary>
        /// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
        public ReadOnlySpan<char> AsSpan(bool terminate)
        {
            if (terminate)
            {
                EnsureCapacity(Length + 1);
                _chars[Length] = '\0';
            }
            return _chars.Slice(0, _pos);
        }

        public ReadOnlySpan<char> AsSpan() => _chars.Slice(0, _pos);
        public ReadOnlySpan<char> AsSpan(int start) => _chars.Slice(start, _pos - start);
        public ReadOnlySpan<char> AsSpan(int start, int length) => _chars.Slice(start, length);

        public bool TryCopyTo(Span<char> destination, out int charsWritten)
        {
            if (_chars.Slice(0, _pos).TryCopyTo(destination))
            {
                charsWritten = _pos;
                Dispose();
                return true;
            }
            else
            {
                charsWritten = 0;
                Dispose();
                return false;
            }
        }

        public void Insert(int index, char value, int count)
        {
            if (_pos > _chars.Length - count)
            {
                Grow(count);
            }

            int remaining = _pos - index;
            _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
            _chars.Slice(index, count).Fill(value);
            _pos += count;
        }

        public void Insert(int index, string? s)
        {
            if (s == null)
            {
                return;
            }

            int count = s.Length;

            if (_pos > (_chars.Length - count))
            {
                Grow(count);
            }

            int remaining = _pos - index;
            _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
            s
#if !NET
                .AsSpan()
#endif
                .CopyTo(_chars.Slice(index));
            _pos += count;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Append(char c)
        {
            int pos = _pos;
            Span<char> chars = _chars;
            if ((uint)pos < (uint)chars.Length)
            {
                chars[pos] = c;
                _pos = pos + 1;
            }
            else
            {
                GrowAndAppend(c);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Append(string? s)
        {
            if (s == null)
            {
                return;
            }

            int pos = _pos;
            if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
            {
                _chars[pos] = s[0];
                _pos = pos + 1;
            }
            else
            {
                AppendSlow(s);
            }
        }

        private void AppendSlow(string s)
        {
            int pos = _pos;
            if (pos > _chars.Length - s.Length)
            {
                Grow(s.Length);
            }

            s
#if !NET
                .AsSpan()
#endif
                .CopyTo(_chars.Slice(pos));
            _pos += s.Length;
        }

        public void Append(char c, int count)
        {
            if (_pos > _chars.Length - count)
            {
                Grow(count);
            }

            Span<char> dst = _chars.Slice(_pos, count);
            for (int i = 0; i < dst.Length; i++)
            {
                dst[i] = c;
            }
            _pos += count;
        }

        public void Append(scoped ReadOnlySpan<char> value)
        {
            int pos = _pos;
            if (pos > _chars.Length - value.Length)
            {
                Grow(value.Length);
            }

            value.CopyTo(_chars.Slice(_pos));
            _pos += value.Length;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Span<char> AppendSpan(int length)
        {
            int origPos = _pos;
            if (origPos > _chars.Length - length)
            {
                Grow(length);
            }

            _pos = origPos + length;
            return _chars.Slice(origPos, length);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void GrowAndAppend(char c)
        {
            Grow(1);
            Append(c);
        }

        /// <summary>
        /// Resize the internal buffer either by doubling current buffer size or
        /// by adding <paramref name="additionalCapacityBeyondPos"/> to
        /// <see cref="_pos"/> whichever is greater.
        /// </summary>
        /// <param name="additionalCapacityBeyondPos">
        /// Number of chars requested beyond current position.
        /// </param>
        [MethodImpl(MethodImplOptions.NoInlining)]
        private void Grow(int additionalCapacityBeyondPos)
        {
            Debug.Assert(additionalCapacityBeyondPos > 0);
            Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed.");

            const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength

            // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try
            // to double the size if possible, bounding the doubling to not go beyond the max array length.
            int newCapacity = (int)Math.Max(
                (uint)(_pos + additionalCapacityBeyondPos),
                Math.Min((uint)_chars.Length * 2, ArrayMaxLength));

            // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative.
            // This could also go negative if the actual required length wraps around.
            char[] poolArray = ArrayPool<char>.Shared.Rent(newCapacity);

            _chars.Slice(0, _pos).CopyTo(poolArray);

            char[]? toReturn = _arrayToReturnToPool;
            _chars = _arrayToReturnToPool = poolArray;
            if (toReturn != null)
            {
                ArrayPool<char>.Shared.Return(toReturn);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Dispose()
        {
            char[]? toReturn = _arrayToReturnToPool;
            this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
            if (toReturn != null)
            {
                ArrayPool<char>.Shared.Return(toReturn);
            }
        }
    }
}

using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization.Metadata;

namespace ModelContextProtocol;

/// <summary>
/// Provides extension methods for interacting with an <see cref="IMcpEndpoint"/>.
/// </summary>
/// <remarks>
/// <para>
/// This class provides strongly-typed methods for working with the Model Context Protocol (MCP) endpoints,
/// simplifying JSON-RPC communication by handling serialization and deserialization of parameters and results.
/// </para>
/// <para>
/// These extension methods are designed to be used with both client (<see cref="IMcpClient"/>) and
/// server (<see cref="IMcpServer"/>) implementations of the <see cref="IMcpEndpoint"/> interface.
/// </para>
/// </remarks>
public static class McpEndpointExtensions
{
    /// <summary>
    /// Sends a JSON-RPC request and attempts to deserialize the result to <typeparamref name="TResult"/>.
    /// </summary>
    /// <typeparam name="TParameters">The type of the request parameters to serialize from.</typeparam>
    /// <typeparam name="TResult">The type of the result to deserialize to.</typeparam>
    /// <param name="endpoint">The MCP client or server instance.</param>
    /// <param name="method">The JSON-RPC method name to invoke.</param>
    /// <param name="parameters">Object representing the request parameters.</param>
    /// <param name="requestId">The request id for the request.</param>
    /// <param name="serializerOptions">The options governing request serialization.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the deserialized result.</returns>
    public static ValueTask<TResult> SendRequestAsync<TParameters, TResult>(
        this IMcpEndpoint endpoint,
        string method,
        TParameters parameters,
        JsonSerializerOptions? serializerOptions = null,
        RequestId requestId = default,
        CancellationToken cancellationToken = default)
        where TResult : notnull
    {
        serializerOptions ??= McpJsonUtilities.DefaultOptions;
        serializerOptions.MakeReadOnly();

        JsonTypeInfo<TParameters> paramsTypeInfo = serializerOptions.GetTypeInfo<TParameters>();
        JsonTypeInfo<TResult> resultTypeInfo = serializerOptions.GetTypeInfo<TResult>();
        return SendRequestAsync(endpoint, method, parameters, paramsTypeInfo, resultTypeInfo, requestId, cancellationToken);
    }

    /// <summary>
    /// Sends a JSON-RPC request and attempts to deserialize the result to <typeparamref name="TResult"/>.
    /// </summary>
    /// <typeparam name="TParameters">The type of the request parameters to serialize from.</typeparam>
    /// <typeparam name="TResult">The type of the result to deserialize to.</typeparam>
    /// <param name="endpoint">The MCP client or server instance.</param>
    /// <param name="method">The JSON-RPC method name to invoke.</param>
    /// <param name="parameters">Object representing the request parameters.</param>
    /// <param name="parametersTypeInfo">The type information for request parameter serialization.</param>
    /// <param name="resultTypeInfo">The type information for request parameter deserialization.</param>
    /// <param name="requestId">The request id for the request.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the deserialized result.</returns>
    internal static async ValueTask<TResult> SendRequestAsync<TParameters, TResult>(
        this IMcpEndpoint endpoint,
        string method,
        TParameters parameters,
        JsonTypeInfo<TParameters> parametersTypeInfo,
        JsonTypeInfo<TResult> resultTypeInfo,
        RequestId requestId = default,
        CancellationToken cancellationToken = default)
        where TResult : notnull
    {
        Throw.IfNull(endpoint);
        Throw.IfNullOrWhiteSpace(method);
        Throw.IfNull(parametersTypeInfo);
        Throw.IfNull(resultTypeInfo);

        JsonRpcRequest jsonRpcRequest = new()
        {
            Id = requestId,
            Method = method,
            Params = JsonSerializer.SerializeToNode(parameters, parametersTypeInfo),
        };

        JsonRpcResponse response = await endpoint.SendRequestAsync(jsonRpcRequest, cancellationToken).ConfigureAwait(false);
        return JsonSerializer.Deserialize(response.Result, resultTypeInfo) ?? throw new JsonException("Unexpected JSON result in response.");
    }

    /// <summary>
    /// Sends a parameterless notification to the connected endpoint.
    /// </summary>
    /// <param name="client">The MCP client or server instance.</param>
    /// <param name="method">The notification method name.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous send operation.</returns>
    /// <remarks>
    /// <para>
    /// This method sends a notification without any parameters. Notifications are one-way messages
    /// that don't expect a response. They are commonly used for events, status updates, or to signal
    /// changes in state.
    /// </para>
    /// </remarks>
    public static Task SendNotificationAsync(this IMcpEndpoint client, string method, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(client);
        Throw.IfNullOrWhiteSpace(method);
        return client.SendMessageAsync(new JsonRpcNotification { Method = method }, cancellationToken);
    }

    /// <summary>
    /// Sends a notification with parameters to the connected endpoint.
    /// </summary>
    /// <typeparam name="TParameters">The type of the notification parameters to serialize.</typeparam>
    /// <param name="endpoint">The MCP client or server instance.</param>
    /// <param name="method">The JSON-RPC method name for the notification.</param>
    /// <param name="parameters">Object representing the notification parameters.</param>
    /// <param name="serializerOptions">The options governing parameter serialization. If null, default options are used.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous send operation.</returns>
    /// <remarks>
    /// <para>
    /// This method sends a notification with parameters to the connected endpoint. Notifications are one-way
    /// messages that don't expect a response, commonly used for events, status updates, or signaling changes.
    /// </para>
    /// <para>
    /// The parameters object is serialized to JSON according to the provided serializer options or the default
    /// options if none are specified.
    /// </para>
    /// <para>
    /// The Model Context Protocol defines several standard notification methods in <see cref="NotificationMethods"/>,
    /// but custom methods can also be used for application-specific notifications.
    /// </para>
    /// </remarks>
    public static Task SendNotificationAsync<TParameters>(
        this IMcpEndpoint endpoint,
        string method,
        TParameters parameters,
        JsonSerializerOptions? serializerOptions = null,
        CancellationToken cancellationToken = default)
    {
        serializerOptions ??= McpJsonUtilities.DefaultOptions;
        serializerOptions.MakeReadOnly();

        JsonTypeInfo<TParameters> parametersTypeInfo = serializerOptions.GetTypeInfo<TParameters>();
        return SendNotificationAsync(endpoint, method, parameters, parametersTypeInfo, cancellationToken);
    }

    /// <summary>
    /// Sends a notification to the server with parameters.
    /// </summary>
    /// <param name="endpoint">The MCP client or server instance.</param>
    /// <param name="method">The JSON-RPC method name to invoke.</param>
    /// <param name="parameters">Object representing the request parameters.</param>
    /// <param name="parametersTypeInfo">The type information for request parameter serialization.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    internal static Task SendNotificationAsync<TParameters>(
        this IMcpEndpoint endpoint,
        string method,
        TParameters parameters,
        JsonTypeInfo<TParameters> parametersTypeInfo,
        CancellationToken cancellationToken = default)
    {
        Throw.IfNull(endpoint);
        Throw.IfNullOrWhiteSpace(method);
        Throw.IfNull(parametersTypeInfo);

        JsonNode? parametersJson = JsonSerializer.SerializeToNode(parameters, parametersTypeInfo);
        return endpoint.SendMessageAsync(new JsonRpcNotification { Method = method, Params = parametersJson }, cancellationToken);
    }

    /// <summary>
    /// Notifies the connected endpoint of progress for a long-running operation.
    /// </summary>
    /// <param name="endpoint">The endpoint issuing the notification.</param>
    /// <param name="progressToken">The <see cref="ProgressToken"/> identifying the operation for which progress is being reported.</param>
    /// <param name="progress">The progress update to send, containing information such as percentage complete or status message.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task representing the completion of the notification operation (not the operation being tracked).</returns>
    /// <exception cref="ArgumentNullException"><paramref name="endpoint"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// This method sends a progress notification to the connected endpoint using the Model Context Protocol's
    /// standardized progress notification format. Progress updates are identified by a <see cref="ProgressToken"/>
    /// that allows the recipient to correlate multiple updates with a specific long-running operation.
    /// </para>
    /// <para>
    /// Progress notifications are sent asynchronously and don't block the operation from continuing.
    /// </para>
    /// </remarks>
    public static Task NotifyProgressAsync(
        this IMcpEndpoint endpoint,
        ProgressToken progressToken,
        ProgressNotificationValue progress,
        CancellationToken cancellationToken = default)
    {
        Throw.IfNull(endpoint);

        return endpoint.SendNotificationAsync(
            NotificationMethods.ProgressNotification,
            new ProgressNotificationParams
            {
                ProgressToken = progressToken,
                Progress = progress,
            },
            McpJsonUtilities.JsonContext.Default.ProgressNotificationParams,
            cancellationToken);
    }
}

using System.Collections;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;

namespace ModelContextProtocol.Server;

/// Provides a thread-safe collection of <typeparamref name="T"/> instances, indexed by their names.
/// <typeparam name="T">Specifies the type of primitive stored in the collection.</typeparam>
public class McpServerPrimitiveCollection<T> : ICollection<T>, IReadOnlyCollection<T>
    where T : IMcpServerPrimitive
{
    /// Concurrent dictionary of primitives, indexed by their names.
    private readonly ConcurrentDictionary<string, T> _primitives;

    ///
    /// Initializes a new instance of the <see cref="McpServerPrimitiveCollection{T}"/> class.
    ///
    public McpServerPrimitiveCollection(IEqualityComparer<string>? keyComparer = null)
    {
        _primitives = new(keyComparer ?? EqualityComparer<string>.Default);
    }

    /// Occurs when the collection is changed.
    /// <remarks>
    /// By default, this is raised when a primitive is added or removed. However, a derived implementation
    /// may raise this event for other reasons, such as when a primitive is modified.
    /// </remarks>
    public event EventHandler? Changed;

    /// Gets the number of primitives in the collection.
    public int Count => _primitives.Count;

    /// Gets whether there are any primitives in the collection.
    public bool IsEmpty => _primitives.IsEmpty;

    /// Raises <see cref="Changed"/> if there are registered handlers.
    protected void RaiseChanged() => Changed?.Invoke(this, EventArgs.Empty);

    /// Gets the <typeparamref name="T"/> with the specified <paramref name="name"/> from the collection.
    /// <param name="name">The name of the primitive to retrieve.</param>
    /// <returns>The <typeparamref name="T"/> with the specified name.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
    /// <exception cref="KeyNotFoundException">An primitive with the specified name does not exist in the collection.</exception>
    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 <typeparamref name="T"/> to the collection.
    /// <param name="primitive">The primitive to be added.</param>
    /// <exception cref="ArgumentNullException"><paramref name="primitive"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException">A primitive with the same name as <paramref name="primitive"/> already exists in the collection.</exception>
    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 <typeparamref name="T"/> to the collection.
    /// <param name="primitive">The primitive to be added.</param>
    /// <returns><see langword="true"/> if the primitive was added; otherwise, <see langword="false"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="primitive"/> is <see langword="null"/>.</exception>
    public virtual bool TryAdd(T primitive)
    {
        Throw.IfNull(primitive);

        bool added = _primitives.TryAdd(primitive.Id, primitive);
        if (added)
        {
            RaiseChanged();
        }

        return added;
    }

    /// Removes the specified primitivefrom the collection.
    /// <param name="primitive">The primitive to be removed from the collection.</param>
    /// <returns>
    /// <see langword="true"/> if the primitive was found in the collection and removed; otherwise, <see langword="false"/> if it couldn't be found.
    /// </returns>
    /// <exception cref="ArgumentNullException"><paramref name="primitive"/> is <see langword="null"/>.</exception>
    public virtual bool Remove(T primitive)
    {
        Throw.IfNull(primitive);

        bool removed = ((ICollection<KeyValuePair<string, T>>)_primitives).Remove(new(primitive.Id, primitive));
        if (removed)
        {
            RaiseChanged();
        }

        return removed;
    }

    /// Attempts to get the primitive with the specified name from the collection.
    /// <param name="name">The name of the primitive to retrieve.</param>
    /// <param name="primitive">The primitive, if found; otherwise, <see langword="null"/>.</param>
    /// <returns>
    /// <see langword="true"/> if the primitive was found in the collection and return; otherwise, <see langword="false"/> if it couldn't be found.
    /// </returns>
    /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
    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.
    /// <param name="primitive">The primitive to search for in the collection.</param>
    /// <see langword="true"/> if the primitive was found in the collection and return; otherwise, <see langword="false"/> if it couldn't be found.
    /// <exception cref="ArgumentNullException"><paramref name="primitive"/> is <see langword="null"/>.</exception>
    public virtual bool Contains(T primitive)
    {
        Throw.IfNull(primitive);
        return ((ICollection<KeyValuePair<string, T>>)_primitives).Contains(new(primitive.Id, primitive));
    }

    /// Gets the names of all of the primitives in the collection.
    public virtual ICollection<string> PrimitiveNames => _primitives.Keys;

    /// Creates an array containing all of the primitives in the collection.
    /// <returns>An array containing all of the primitives in the collection.</returns>
    public virtual T[] ToArray() => _primitives.Values.ToArray();

    /// <inheritdoc/>
    public virtual void CopyTo(T[] array, int arrayIndex)
    {
        Throw.IfNull(array);

        _primitives.Values.CopyTo(array, arrayIndex);
    }

    /// <inheritdoc/>
    public virtual IEnumerator<T> GetEnumerator()
    {
        foreach (var entry in _primitives)
        {
            yield return entry.Value;
        }
    }

    /// <inheritdoc/>
    IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();

    /// <inheritdoc/>
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    /// <inheritdoc/>
    bool ICollection<T>.IsReadOnly => false;
}

using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using System.Net.Http.Headers;

namespace ModelContextProtocol.Authentication;

/// <summary>
/// A delegating handler that adds authentication tokens to requests and handles 401 responses.
/// </summary>
internal sealed class AuthenticatingMcpHttpClient(HttpClient httpClient, ClientOAuthProvider credentialProvider) : McpHttpClient(httpClient)
{
    // Select first supported scheme as the default
    private string _currentScheme = credentialProvider.SupportedSchemes.FirstOrDefault() ??
        throw new ArgumentException("Authorization provider must support at least one authentication scheme.", nameof(credentialProvider));

    /// <summary>
    /// Sends an HTTP request with authentication handling.
    /// </summary>
    internal override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, JsonRpcMessage? message, CancellationToken cancellationToken)
    {
        if (request.Headers.Authorization == null)
        {
            await AddAuthorizationHeaderAsync(request, _currentScheme, cancellationToken).ConfigureAwait(false);
        }

        var response = await base.SendAsync(request, message, cancellationToken).ConfigureAwait(false);

        if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
        {
            return await HandleUnauthorizedResponseAsync(request, message, response, cancellationToken).ConfigureAwait(false);
        }

        return response;
    }

    /// <summary>
    /// Handles a 401 Unauthorized response by attempting to authenticate and retry the request.
    /// </summary>
    private async Task<HttpResponseMessage> HandleUnauthorizedResponseAsync(
        HttpRequestMessage originalRequest,
        JsonRpcMessage? originalJsonRpcMessage,
        HttpResponseMessage response,
        CancellationToken cancellationToken)
    {
        // Gather the schemes the server wants us to use from WWW-Authenticate headers
        var serverSchemes = ExtractServerSupportedSchemes(response);

        if (!serverSchemes.Contains(_currentScheme))
        {
            // Find the first server scheme that's in our supported set
            var bestSchemeMatch = serverSchemes.Intersect(credentialProvider.SupportedSchemes, StringComparer.OrdinalIgnoreCase).FirstOrDefault();

            if (bestSchemeMatch is not null)
            {
                _currentScheme = bestSchemeMatch;
            }
            else if (serverSchemes.Count > 0)
            {
                // If no match was found, either throw an exception or use default
                throw new McpException(
                    $"The server does not support any of the provided authentication schemes." +
                    $"Server supports: [{string.Join(", ", serverSchemes)}], " +
                    $"Provider supports: [{string.Join(", ", credentialProvider.SupportedSchemes)}].");
            }
        }

        // Try to handle the 401 response with the selected scheme
        await credentialProvider.HandleUnauthorizedResponseAsync(_currentScheme, response, cancellationToken).ConfigureAwait(false);

        using var retryRequest = new HttpRequestMessage(originalRequest.Method, originalRequest.RequestUri);

        // Copy headers except Authorization which we'll set separately
        foreach (var header in originalRequest.Headers)
        {
            if (!header.Key.Equals("Authorization", StringComparison.OrdinalIgnoreCase))
            {
                retryRequest.Headers.TryAddWithoutValidation(header.Key, header.Value);
            }
        }

        await AddAuthorizationHeaderAsync(retryRequest, _currentScheme, cancellationToken).ConfigureAwait(false);
        return await base.SendAsync(retryRequest, originalJsonRpcMessage, cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    /// Extracts the authentication schemes that the server supports from the WWW-Authenticate headers.
    /// </summary>
    private static HashSet<string> ExtractServerSupportedSchemes(HttpResponseMessage response)
    {
        var serverSchemes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        foreach (var header in response.Headers.WwwAuthenticate)
        {
            serverSchemes.Add(header.Scheme);
        }

        return serverSchemes;
    }

    /// <summary>
    /// Adds an authorization header to the request.
    /// </summary>
    private async Task AddAuthorizationHeaderAsync(HttpRequestMessage request, string scheme, CancellationToken cancellationToken)
    {
        if (request.RequestUri is null)
        {
            return;
        }

        var token = await credentialProvider.GetCredentialAsync(scheme, request.RequestUri, cancellationToken).ConfigureAwait(false);
        if (string.IsNullOrEmpty(token))
        {
            return;
        }

        request.Headers.Authorization = new AuthenticationHeaderValue(scheme, token);
    }
}

using ModelContextProtocol.Protocol;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization.Metadata;

namespace ModelContextProtocol;

internal sealed class RequestHandlers : Dictionary<string, Func<JsonRpcRequest, CancellationToken, Task<JsonNode?>>>
{
    /// <summary>
    /// Registers a handler for incoming requests of a specific method in the MCP protocol.
    /// </summary>
    /// <typeparam name="TRequest">Type of request payload that will be deserialized from incoming JSON</typeparam>
    /// <typeparam name="TResponse">Type of response payload that will be serialized to JSON (not full RPC response)</typeparam>
    /// <param name="method">Method identifier to register for (e.g., "tools/list", "logging/setLevel")</param>
    /// <param name="handler">Handler function to be called when a request with the specified method identifier is received</param>
    /// <param name="requestTypeInfo">The JSON contract governing request parameter deserialization</param>
    /// <param name="responseTypeInfo">The JSON contract governing response serialization</param>
    /// <remarks>
    /// <para>
    /// This method is used internally by the MCP infrastructure to register handlers for various protocol methods.
    /// When an incoming request matches the specified method, the registered handler will be invoked with the
    /// deserialized request parameters.
    /// </para>
    /// <para>
    /// The handler function receives the deserialized request object and a cancellation token, and should return
    /// a response object that will be serialized back to the client.
    /// </para>
    /// </remarks>
    public void Set<TRequest, TResponse>(
        string method,
        Func<TRequest?, ITransport?, CancellationToken, ValueTask<TResponse>> handler,
        JsonTypeInfo<TRequest> requestTypeInfo,
        JsonTypeInfo<TResponse> responseTypeInfo)
    {
        Throw.IfNull(method);
        Throw.IfNull(handler);
        Throw.IfNull(requestTypeInfo);
        Throw.IfNull(responseTypeInfo);

        this[method] = async (request, cancellationToken) =>
        {
            TRequest? typedRequest = JsonSerializer.Deserialize(request.Params, requestTypeInfo);
            object? result = await handler(typedRequest, request.RelatedTransport, cancellationToken).ConfigureAwait(false);
            return JsonSerializer.SerializeToNode(result, responseTypeInfo);
        };
    }
}

using ModelContextProtocol.Protocol;
using System.IO.Pipelines;
using System.Net.ServerSentEvents;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading.Channels;

namespace ModelContextProtocol.Server;

/// <summary>
/// Handles processing the request/response body pairs for the Streamable HTTP transport.
/// This is typically used via <see cref="JsonRpcMessage.RelatedTransport"/>.
/// </summary>
internal sealed class StreamableHttpPostTransport(StreamableHttpServerTransport parentTransport, IDuplexPipe httpBodies) : ITransport
{
    private readonly SseWriter _sseWriter = new();
    private RequestId _pendingRequest;

    public ChannelReader<JsonRpcMessage> MessageReader => throw new NotSupportedException("JsonRpcMessage.RelatedTransport should only be used for sending messages.");

    string? ITransport.SessionId => parentTransport.SessionId;

    /// <returns>
    /// True, if data was written to the respond body.
    /// False, if nothing was written because the request body did not contain any <see cref="JsonRpcRequest"/> messages to respond to.
    /// The HTTP application should typically respond with an empty "202 Accepted" response in this scenario.
    /// </returns>
    public async ValueTask<bool> RunAsync(CancellationToken cancellationToken)
    {
        var message = await JsonSerializer.DeserializeAsync(httpBodies.Input.AsStream(),
            McpJsonUtilities.JsonContext.Default.JsonRpcMessage, cancellationToken).ConfigureAwait(false);
        await OnMessageReceivedAsync(message, cancellationToken).ConfigureAwait(false);

        if (_pendingRequest.Id is null)
        {
            return false;
        }

        _sseWriter.MessageFilter = StopOnFinalResponseFilter;
        await _sseWriter.WriteAllAsync(httpBodies.Output.AsStream(), cancellationToken).ConfigureAwait(false);
        return true;
    }

    public async Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
    {
        if (parentTransport.Stateless && message is JsonRpcRequest)
        {
            throw new InvalidOperationException("Server to client requests are not supported in stateless mode.");
        }

        await _sseWriter.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);
    }

    public async ValueTask DisposeAsync()
    {
        await _sseWriter.DisposeAsync().ConfigureAwait(false);
    }

    private async IAsyncEnumerable<SseItem<JsonRpcMessage?>> StopOnFinalResponseFilter(IAsyncEnumerable<SseItem<JsonRpcMessage?>> messages, [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        await foreach (var message in messages.WithCancellation(cancellationToken))
        {
            yield return message;

            if (message.Data is JsonRpcResponse or JsonRpcError && ((JsonRpcMessageWithId)message.Data).Id == _pendingRequest)
            {
                // Complete the SSE response stream now that all pending requests have been processed.
                break;
            }
        }
    }

    private async ValueTask OnMessageReceivedAsync(JsonRpcMessage? message, CancellationToken cancellationToken)
    {
        if (message is null)
        {
            throw new InvalidOperationException("Received invalid null message.");
        }

        if (message is JsonRpcRequest request)
        {
            _pendingRequest = request.Id;

            // Invoke the initialize request callback if applicable.
            if (parentTransport.OnInitRequestReceived is { } onInitRequest && request.Method == RequestMethods.Initialize)
            {
                var initializeRequest = JsonSerializer.Deserialize(request.Params, McpJsonUtilities.JsonContext.Default.InitializeRequestParams);
                await onInitRequest(initializeRequest).ConfigureAwait(false);
            }
        }

        message.RelatedTransport = this;

        if (parentTransport.FlowExecutionContextFromRequests)
        {
            message.ExecutionContext = ExecutionContext.Capture();
        }

        await parentTransport.MessageWriter.WriteAsync(message, cancellationToken).ConfigureAwait(false);
    }
}

using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Protocol;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Text.Json;

namespace ModelContextProtocol.Server;

/// Provides an <see cref="McpServerPrompt"/> that's implemented via an <see cref="AIFunction"/>.
internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
{
    ///
    /// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via a <see cref="Delegate"/> instance.
    ///
    public static new AIFunctionMcpServerPrompt Create(
        Delegate method,
        McpServerPromptCreateOptions? options)
    {
        Throw.IfNull(method);

        options = DeriveOptions(method.Method, options);

        return Create(method.Method, method.Target, options);
    }

    ///
    /// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via a <see cref="MethodInfo"/> instance.
    ///
    public static new AIFunctionMcpServerPrompt Create(
        MethodInfo method,
        object? target,
        McpServerPromptCreateOptions? options)
    {
        Throw.IfNull(method);

        options = DeriveOptions(method, options);

        return Create(
            AIFunctionFactory.Create(method, target, CreateAIFunctionFactoryOptions(method, options)),
            options);
    }

    ///
    /// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via a <see cref="MethodInfo"/> instance.
    ///
    public static new AIFunctionMcpServerPrompt Create(
        MethodInfo method,
        Func<RequestContext<GetPromptRequestParams>, object> createTargetFunc,
        McpServerPromptCreateOptions? options)
    {
        Throw.IfNull(method);
        Throw.IfNull(createTargetFunc);

        options = DeriveOptions(method, options);

        return Create(
            AIFunctionFactory.Create(method, args =>
            {
                Debug.Assert(args.Services is RequestServiceProvider<GetPromptRequestParams>, $"The service provider should be a {nameof(RequestServiceProvider<GetPromptRequestParams>)} for this method to work correctly.");
                return createTargetFunc(((RequestServiceProvider<GetPromptRequestParams>)args.Services!).Request);
            }, CreateAIFunctionFactoryOptions(method, options)),
            options);
    }

    private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
        MethodInfo method, McpServerPromptCreateOptions? options) =>
        new()
        {
            Name = options?.Name ?? method.GetCustomAttribute<McpServerPromptAttribute>()?.Name ?? AIFunctionMcpServerTool.DeriveName(method),
            Description = options?.Description,
            MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
            SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
            JsonSchemaCreateOptions = options?.SchemaCreateOptions,
            ConfigureParameterBinding = pi =>
            {
                if (RequestServiceProvider<GetPromptRequestParams>.IsAugmentedWith(pi.ParameterType) ||
                    (options?.Services?.GetService<IServiceProviderIsService>() is { } ispis &&
                     ispis.IsService(pi.ParameterType)))
                {
                    return new()
                    {
                        ExcludeFromSchema = true,
                        BindParameter = (pi, args) =>
                            args.Services?.GetService(pi.ParameterType) ??
                            (pi.HasDefaultValue ? null :
                             throw new ArgumentException("No service of the requested type was found.")),
                    };
                }

                if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
                {
                    return new()
                    {
                        ExcludeFromSchema = true,
                        BindParameter = (pi, args) =>
                            (args?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
                            (pi.HasDefaultValue ? null :
                             throw new ArgumentException("No service of the requested type was found.")),
                    };
                }

                return default;
            },
        };

    /// Creates an <see cref="McpServerPrompt"/> that wraps the specified <see cref="AIFunction"/>.
    public static new AIFunctionMcpServerPrompt Create(AIFunction function, McpServerPromptCreateOptions? options)
    {
        Throw.IfNull(function);

        List<PromptArgument> args = [];
        HashSet<string>? requiredProps = function.JsonSchema.TryGetProperty("required", out JsonElement required)
            ? new(required.EnumerateArray().Select(p => p.GetString()!), StringComparer.Ordinal)
            : null;

        if (function.JsonSchema.TryGetProperty("properties", out JsonElement properties))
        {
            foreach (var param in properties.EnumerateObject())
            {
                args.Add(new()
                {
                    Name = param.Name,
                    Description = param.Value.TryGetProperty("description", out JsonElement description) ? description.GetString() : null,
                    Required = requiredProps?.Contains(param.Name) ?? false,
                });
            }
        }

        Prompt prompt = new()
        {
            Name = options?.Name ?? function.Name,
            Title = options?.Title,
            Description = options?.Description ?? function.Description,
            Arguments = args,
        };

        return new AIFunctionMcpServerPrompt(function, prompt);
    }

    private static McpServerPromptCreateOptions DeriveOptions(MethodInfo method, McpServerPromptCreateOptions? options)
    {
        McpServerPromptCreateOptions newOptions = options?.Clone() ?? new();

        if (method.GetCustomAttribute<McpServerPromptAttribute>() is { } promptAttr)
        {
            newOptions.Name ??= promptAttr.Name;
            newOptions.Title ??= promptAttr.Title;
        }

        if (method.GetCustomAttribute<DescriptionAttribute>() is { } descAttr)
        {
            newOptions.Description ??= descAttr.Description;
        }

        return newOptions;
    }

    /// Gets the <see cref="AIFunction"/> wrapped by this prompt.
    internal AIFunction AIFunction { get; }

    /// Initializes a new instance of the <see cref="McpServerPrompt"/> class.
    private AIFunctionMcpServerPrompt(AIFunction function, Prompt prompt)
    {
        AIFunction = function;
        ProtocolPrompt = prompt;
    }

    /// <inheritdoc />
    public override Prompt ProtocolPrompt { get; }

    /// <inheritdoc />
    public override async ValueTask<GetPromptResult> GetAsync(
        RequestContext<GetPromptRequestParams> request, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(request);
        cancellationToken.ThrowIfCancellationRequested();

        request.Services = new RequestServiceProvider<GetPromptRequestParams>(request, request.Services);
        AIFunctionArguments arguments = new() { Services = request.Services };

        if (request.Params?.Arguments is { } argDict)
        {
            foreach (var kvp in argDict)
            {
                arguments[kvp.Key] = kvp.Value;
            }
        }

        object? result = await AIFunction.InvokeAsync(arguments, cancellationToken).ConfigureAwait(false);

        return result switch
        {
            GetPromptResult getPromptResult => getPromptResult,

            string text => new()
            {
                Description = ProtocolPrompt.Description,
                Messages = [new() { Role = Role.User, Content = new TextContentBlock { Text = text } }],
            },

            PromptMessage promptMessage => new()
            {
                Description = ProtocolPrompt.Description,
                Messages = [promptMessage],
            },

            IEnumerable<PromptMessage> promptMessages => new()
            {
                Description = ProtocolPrompt.Description,
                Messages = [.. promptMessages],
            },

            ChatMessage chatMessage => new()
            {
                Description = ProtocolPrompt.Description,
                Messages = [.. chatMessage.ToPromptMessages()],
            },

            IEnumerable<ChatMessage> chatMessages => new()
            {
                Description = ProtocolPrompt.Description,
                Messages = [.. chatMessages.SelectMany(chatMessage => chatMessage.ToPromptMessages())],
            },

            null => throw new InvalidOperationException("Null result returned from prompt function."),

            _ => throw new InvalidOperationException($"Unknown result type '{result.GetType()}' returned from prompt function."),
        };
    }
}

using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol;
using System.Collections.ObjectModel;
using System.Text.Json;

namespace ModelContextProtocol.Client;

///
/// Provides an <see cref="AIFunction"/> that calls a tool via an <see cref="IMcpClient"/>.
///
/// <remarks>
/// <para>
/// The <see cref="McpClientTool"/> class encapsulates an <see cref="IMcpClient"/> along with a description of
/// a tool available via that client, allowing it to be invoked as an <see cref="AIFunction"/>. This enables integration
/// with AI models that support function calling capabilities.
/// </para>
/// <para>
/// Tools retrieved from an MCP server can be customized for model presentation using methods like
/// <see cref="WithName"/> and <see cref="WithDescription"/> without changing the underlying tool functionality.
/// </para>
/// <para>
/// Typically, you would get instances of this class by calling the <see cref="McpClientExtensions.ListToolsAsync"/>
/// or <see cref="McpClientExtensions.EnumerateToolsAsync"/> extension methods on an <see cref="IMcpClient"/> instance.
/// </para>
/// </remarks>
public sealed class McpClientTool : AIFunction
{
    /// Additional properties exposed from tools.
    private static readonly ReadOnlyDictionary<string, object?> s_additionalProperties =
        new(new Dictionary<string, object?>()
        {
            ["Strict"] = false, // some MCP schemas may not meet "strict" requirements
        });

    private readonly IMcpClient _client;
    private readonly string _name;
    private readonly string _description;
    private readonly IProgress<ProgressNotificationValue>? _progress;

    internal McpClientTool(
        IMcpClient client,
        Tool tool,
        JsonSerializerOptions serializerOptions,
        string? name = null,
        string? description = null,
        IProgress<ProgressNotificationValue>? progress = null)
    {
        _client = client;
        ProtocolTool = tool;
        JsonSerializerOptions = serializerOptions;
        _name = name ?? tool.Name;
        _description = description ?? tool.Description ?? string.Empty;
        _progress = progress;
    }

    ///
    /// Gets the protocol <see cref="Tool"/> type for this instance.
    ///
    /// <remarks>
    /// This property provides direct access to the underlying protocol representation of the tool,
    /// which can be useful for advanced scenarios or when implementing custom MCP client extensions.
    /// It contains the original metadata about the tool as provided by the server, including its
    /// name, description, and schema information before any customizations applied through methods
    /// like <see cref="WithName"/> or <see cref="WithDescription"/>.
    /// </remarks>
    public Tool ProtocolTool { get; }

    /// <inheritdoc/>
    public override string Name => _name;

    /// Gets the tool's title.
    public string? Title => ProtocolTool.Title ?? ProtocolTool.Annotations?.Title;

    /// <inheritdoc/>
    public override string Description => _description;

    /// <inheritdoc/>
    public override JsonElement JsonSchema => ProtocolTool.InputSchema;

    /// <inheritdoc/>
    public override JsonElement? ReturnJsonSchema => ProtocolTool.OutputSchema;

    /// <inheritdoc/>
    public override JsonSerializerOptions JsonSerializerOptions { get; }

    /// <inheritdoc/>
    public override IReadOnlyDictionary<string, object?> AdditionalProperties => s_additionalProperties;

    /// <inheritdoc/>
    protected async override ValueTask<object?> InvokeCoreAsync(
        AIFunctionArguments arguments, CancellationToken cancellationToken)
    {
        CallToolResult result = await CallAsync(arguments, _progress, JsonSerializerOptions, cancellationToken).ConfigureAwait(false);
        return JsonSerializer.SerializeToElement(result, McpJsonUtilities.JsonContext.Default.CallToolResult);
    }

    ///
    /// Invokes the tool on the server.
    ///
    /// <param name="arguments">An optional dictionary of arguments to pass to the tool. Each key represents a parameter name,
    /// and its associated value represents the argument value.
    /// </param>
    /// <param name="progress">
    /// An optional <see cref="IProgress{T}"/> to have progress notifications reported to it. Setting this to a non-<see langword="null"/>
    /// value will result in a progress token being included in the call, and any resulting progress notifications during the operation
    /// routed to this instance.
    /// </param>
    /// <param name="serializerOptions">
    /// The JSON serialization options governing argument serialization. If <see langword="null"/>, the default serialization options will be used.
    /// </param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>
    /// A task containing the <see cref="CallToolResult"/> from the tool execution. The response includes
    /// the tool's output content, which may be structured data, text, or an error message.
    /// </returns>
    /// <remarks>
    /// The base <see cref="AIFunction.InvokeAsync"/> method is overridden to invoke this <see cref="CallAsync"/> method.
    /// The only difference in behavior is <see cref="AIFunction.InvokeAsync"/> will serialize the resulting <see cref="CallToolResult"/>"/>
    /// such that the <see cref="object"/> returned is a <see cref="JsonElement"/> containing the serialized <see cref="CallToolResult"/>.
    /// This <see cref="CallToolResult"/> method is intended to be called directly by user code, whereas the base <see cref="AIFunction.InvokeAsync"/>
    /// is intended to be used polymorphically via the base class, typically as part of an <see cref="IChatClient"/> operation.
    /// </remarks>
    /// <exception cref="McpException">The server could not find the requested tool, or the server encountered an error while processing the request.</exception>
    /// <example>
    ///
    /// var result = await tool.CallAsync(
    ///     new Dictionary<string, object?>
    ///     {
    ///         ["message"] = "Hello MCP!"
    ///     });
    ///
    /// </example>
    public ValueTask<CallToolResult> CallAsync(
        IReadOnlyDictionary<string, object?>? arguments = null,
        IProgress<ProgressNotificationValue>? progress = null,
        JsonSerializerOptions? serializerOptions = null,
        CancellationToken cancellationToken = default) =>
        _client.CallToolAsync(ProtocolTool.Name, arguments, progress, serializerOptions, cancellationToken);

    ///
    /// Creates a new instance of the tool but modified to return the specified name from its <see cref="Name"/> property.
    ///
    /// <param name="name">The model-facing name to give the tool.</param>
    /// <returns>A new instance of <see cref="McpClientTool"/> with the provided name.</returns>
    /// <remarks>
    /// <para>
    /// This is useful for optimizing the tool name for specific models or for prefixing the tool name
    /// with a namespace to avoid conflicts.
    /// </para>
    /// <para>
    /// Changing the name can help with:
    /// </para>
    /// <list type="bullet">
    ///   <item>Making the tool name more intuitive for the model</item>
    ///   <item>Preventing name collisions when using tools from multiple sources</item>
    ///   <item>Creating specialized versions of a general tool for specific contexts</item>
    /// </list>
    /// <para>
    /// When invoking <see cref="AIFunction.InvokeAsync"/>, the MCP server will still be called with
    /// the original tool name, so no mapping is required on the server side. This new name only affects
    /// the value returned from this instance's <see cref="AITool.Name"/>.
    /// </para>
    /// </remarks>
    public McpClientTool WithName(string name) =>
        new(_client, ProtocolTool, JsonSerializerOptions, name, _description, _progress);

    ///
    /// Creates a new instance of the tool but modified to return the specified description from its <see cref="Description"/> property.
    ///
    /// <param name="description">The description to give the tool.</param>
    /// <remarks>
    /// <para>
    /// Changing the description can help the model better understand the tool's purpose or provide more
    /// context about how the tool should be used. This is particularly useful when:
    /// </para>
    /// <list type="bullet">
    ///   <item>The original description is too technical or lacks clarity for the model</item>
    ///   <item>You want to add example usage scenarios to improve the model's understanding</item>
    ///   <item>You need to tailor the tool's description for specific model requirements</item>
    /// </list>
    /// <para>
    /// When invoking <see cref="AIFunction.InvokeAsync"/>, the MCP server will still be called with
    /// the original tool description, so no mapping is required on the server side. This new description only affects
    /// the value returned from this instance's <see cref="AITool.Description"/>.
    /// </para>
    /// </remarks>
    /// <returns>A new instance of <see cref="McpClientTool"/> with the provided description.</returns>
    public McpClientTool WithDescription(string description) =>
        new(_client, ProtocolTool, JsonSerializerOptions, _name, description, _progress);

    ///
    /// Creates a new instance of the tool but modified to report progress via the specified <see cref="IProgress{T}"/>.
    ///
    /// <param name="progress">The <see cref="IProgress{T}"/> to which progress notifications should be reported.</param>
    /// <remarks>
    /// <para>
    /// Adding an <see cref="IProgress{T}"/> to the tool does not impact how it is reported to any AI model.
    /// Rather, when the tool is invoked, the request to the MCP server will include a unique progress token,
    /// and any progress notifications issued by the server with that progress token while the operation is in
    /// flight will be reported to the <paramref name="progress"/> instance.
    /// </para>
    /// <para>
    /// Only one <see cref="IProgress{T}"/> can be specified at a time. Calling <see cref="WithProgress"/> again
    /// will overwrite any previously specified progress instance.
    /// </para>
    /// </remarks>
    /// <returns>A new instance of <see cref="McpClientTool"/>, configured with the provided progress instance.</returns>
    public McpClientTool WithProgress(IProgress<ProgressNotificationValue> progress)
    {
        Throw.IfNull(progress);

        return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, _description, progress);
    }
}

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol.Protocol;
using System.Net;
using System.Threading.Channels;

namespace ModelContextProtocol.Client;

/// <summary>
/// A transport that automatically detects whether to use Streamable HTTP or SSE transport
/// by trying Streamable HTTP first and falling back to SSE if that fails.
/// </summary>
internal sealed partial class AutoDetectingClientSessionTransport : ITransport
{
    private readonly SseClientTransportOptions _options;
    private readonly McpHttpClient _httpClient;
    private readonly ILoggerFactory? _loggerFactory;
    private readonly ILogger _logger;
    private readonly string _name;
    private readonly Channel<JsonRpcMessage> _messageChannel;

    public AutoDetectingClientSessionTransport(string endpointName, SseClientTransportOptions transportOptions, McpHttpClient httpClient, ILoggerFactory? loggerFactory)
    {
        Throw.IfNull(transportOptions);
        Throw.IfNull(httpClient);

        _options = transportOptions;
        _httpClient = httpClient;
        _loggerFactory = loggerFactory;
        _logger = (ILogger?)loggerFactory?.CreateLogger<AutoDetectingClientSessionTransport>() ?? NullLogger.Instance;
        _name = endpointName;

        // Same as TransportBase.cs.
        _messageChannel = Channel.CreateUnbounded<JsonRpcMessage>(new UnboundedChannelOptions
        {
            SingleReader = true,
            SingleWriter = false,
        });
    }

    /// <summary>
    /// Returns the active transport (either StreamableHttp or SSE)
    /// </summary>
    internal ITransport? ActiveTransport { get; private set; }

    public ChannelReader<JsonRpcMessage> MessageReader => _messageChannel.Reader;

    string? ITransport.SessionId => ActiveTransport?.SessionId;

    /// <inheritdoc/>
    public Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
    {
        if (ActiveTransport is null)
        {
            return InitializeAsync(message, cancellationToken);
        }

        return ActiveTransport.SendMessageAsync(message, cancellationToken);
    }

    private async Task InitializeAsync(JsonRpcMessage message, CancellationToken cancellationToken)
    {
        // Try StreamableHttp first
        var streamableHttpTransport = new StreamableHttpClientSessionTransport(_name, _options, _httpClient, _messageChannel, _loggerFactory);

        try
        {
            LogAttemptingStreamableHttp(_name);
            using var response = await streamableHttpTransport.SendHttpRequestAsync(message, cancellationToken).ConfigureAwait(false);

            if (response.IsSuccessStatusCode)
            {
                LogUsingStreamableHttp(_name);
                ActiveTransport = streamableHttpTransport;
            }
            else
            {
                // If the status code is not success, fall back to SSE
                LogStreamableHttpFailed(_name, response.StatusCode);

                await streamableHttpTransport.DisposeAsync().ConfigureAwait(false);
                await InitializeSseTransportAsync(message, cancellationToken).ConfigureAwait(false);
            }
        }
        catch
        {
            // If nothing threw inside the try block, we've either set streamableHttpTransport as the
            // ActiveTransport, or else we will have disposed it in the !IsSuccessStatusCode else block.
            await streamableHttpTransport.DisposeAsync().ConfigureAwait(false);
            throw;
        }
    }

    private async Task InitializeSseTransportAsync(JsonRpcMessage message, CancellationToken cancellationToken)
    {
        var sseTransport = new SseClientSessionTransport(_name, _options, _httpClient, _messageChannel, _loggerFactory);

        try
        {
            LogAttemptingSSE(_name);
            await sseTransport.ConnectAsync(cancellationToken).ConfigureAwait(false);
            await sseTransport.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);

            LogUsingSSE(_name);
            ActiveTransport = sseTransport;
        }
        catch
        {
            await sseTransport.DisposeAsync().ConfigureAwait(false);
            throw;
        }
    }

    public async ValueTask DisposeAsync()
    {
        try
        {
            if (ActiveTransport is not null)
            {
                await ActiveTransport.DisposeAsync().ConfigureAwait(false);
            }
        }
        finally
        {
            // In the majority of cases, either the Streamable HTTP transport or SSE transport has completed the channel by now.
            // However, this may not be the case if HttpClient throws during the initial request due to misconfiguration.
            _messageChannel.Writer.TryComplete();
        }
    }

    [LoggerMessage(Level = LogLevel.Debug, Message = "{EndpointName} attempting to connect using Streamable HTTP transport.")]
    private partial void LogAttemptingStreamableHttp(string endpointName);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} streamable HTTP transport failed with status code {StatusCode}, falling back to SSE transport.")]
    private partial void LogStreamableHttpFailed(string endpointName, HttpStatusCode statusCode);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} using Streamable HTTP transport.")]
    private partial void LogUsingStreamableHttp(string endpointName);

    [LoggerMessage(Level = LogLevel.Debug, Message = "{EndpointName} attempting to connect using SSE transport.")]
    private partial void LogAttemptingSSE(string endpointName);

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} using SSE transport.")]
    private partial void LogUsingSSE(string endpointName);
}

using ModelContextProtocol.Protocol;
using System.Threading.Channels;

namespace ModelContextProtocol.Server;

/// <summary>
/// Provides an <see cref="ITransport"/> implementation using Server-Sent Events (SSE) for server-to-client communication.
/// </summary>
/// <remarks>
/// <para>
/// This transport provides one-way communication from server to client using the SSE protocol over HTTP,
/// while receiving client messages through a separate mechanism. It writes messages as
/// SSE events to a response stream, typically associated with an HTTP response.
/// </para>
/// <para>
/// This transport is used in scenarios where the server needs to push messages to the client in real-time,
/// such as when streaming completion results or providing progress updates during long-running operations.
/// </para>
/// </remarks>
/// <param name="sseResponseStream">The response stream to write MCP JSON-RPC messages as SSE events to.</param>
/// <param name="messageEndpoint">
/// The relative or absolute URI the client should use to post MCP JSON-RPC messages for this session.
/// These messages should be passed to <see cref="OnMessageReceivedAsync(JsonRpcMessage, CancellationToken)"/>.
/// Defaults to "/message".
/// </param>
/// <param name="sessionId">The identifier corresponding to the current MCP session.</param>
public sealed class SseResponseStreamTransport(Stream sseResponseStream, string? messageEndpoint = "/message", string? sessionId = null) : ITransport
{
    private readonly SseWriter _sseWriter = new(messageEndpoint);
    private readonly Channel<JsonRpcMessage> _incomingChannel = Channel.CreateBounded<JsonRpcMessage>(new BoundedChannelOptions(1)
    {
        SingleReader = true,
        SingleWriter = false,
    });

    private bool _isConnected;

    /// <summary>
    /// Starts the transport and writes the JSON-RPC messages sent via <see cref="SendMessageAsync"/>
    /// to the SSE response stream until cancellation is requested or the transport is disposed.
    /// </summary>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task representing the send loop that writes JSON-RPC messages to the SSE response stream.</returns>
    public async Task RunAsync(CancellationToken cancellationToken)
    {
        _isConnected = true;
        await _sseWriter.WriteAllAsync(sseResponseStream, cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public ChannelReader<JsonRpcMessage> MessageReader => _incomingChannel.Reader;

    /// <inheritdoc/>
    public string? SessionId { get; } = sessionId;

    /// <inheritdoc/>
    public async ValueTask DisposeAsync()
    {
        _isConnected = false;
        _incomingChannel.Writer.TryComplete();
        await _sseWriter.DisposeAsync().ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
    {
        await _sseWriter.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    /// Handles incoming JSON-RPC messages received on the /message endpoint.
    /// </summary>
    /// <param name="message">The JSON-RPC message received from the client.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task representing the asynchronous operation to buffer the JSON-RPC message for processing.</returns>
    /// <exception cref="InvalidOperationException">Thrown when there is an attempt to process a message before calling <see cref="RunAsync(CancellationToken)"/>.</exception>
    /// <remarks>
    /// <para>
    /// This method is the entry point for processing client-to-server communication in the SSE transport model.
    /// While the SSE protocol itself is unidirectional (server to client), this method allows bidirectional
    /// communication by handling HTTP POST requests sent to the message endpoint.
    /// </para>
    /// <para>
    /// When a client sends a JSON-RPC message to the /message endpoint, the server calls this method to
    /// process the message and make it available to the MCP server via the <see cref="MessageReader"/> channel.
    /// </para>
    /// <para>
    /// This method validates that the transport is connected before processing the message, ensuring proper
    /// sequencing of operations in the transport lifecycle.
    /// </para>
    /// </remarks>
    public async Task OnMessageReceivedAsync(JsonRpcMessage message, CancellationToken cancellationToken)
    {
        Throw.IfNull(message);

        if (!_isConnected)
        {
            throw new InvalidOperationException($"Transport is not connected. Make sure to call {nameof(RunAsync)} first.");
        }

        await _incomingChannel.Writer.WriteAsync(message, cancellationToken).ConfigureAwait(false);
    }
}

using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;

namespace ModelContextProtocol;

///
/// Represents a client or server Model Context Protocol (MCP) endpoint.
///
/// <remarks>
/// <para>
/// The MCP endpoint provides the core communication functionality used by both clients and servers:
/// <list type="bullet">
///   <item>Sending JSON-RPC requests and receiving responses.</item>
///   <item>Sending notifications to the connected endpoint.</item>
///   <item>Registering handlers for receiving notifications.</item>
/// </list>
/// </para>
/// <para>
/// <see cref="IMcpEndpoint"/> serves as the base interface for both <see cref="IMcpClient"/> and
/// <see cref="IMcpServer"/> interfaces, providing the common functionality needed for MCP protocol
/// communication. Most applications will use these more specific interfaces rather than working with
/// <see cref="IMcpEndpoint"/> directly.
/// </para>
/// <para>
/// All MCP endpoints should be properly disposed after use as they implement <see cref="IAsyncDisposable"/>.
/// </para>
/// </remarks>
public interface IMcpEndpoint : IAsyncDisposable
{
    /// Gets an identifier associated with the current MCP session.
    /// <remarks>
    /// Typically populated in transports supporting multiple sessions such as Streamable HTTP or SSE.
    /// Can return <see langword="null"/> if the session hasn't initialized or if the transport doesn't
    /// support multiple sessions (as is the case with STDIO).
    /// </remarks>
    string? SessionId { get; }

    ///
    /// Sends a JSON-RPC request to the connected endpoint and waits for a response.
    ///
    /// <param name="request">The JSON-RPC request to send.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task containing the endpoint's response.</returns>
    /// <exception cref="InvalidOperationException">The transport is not connected, or another error occurs during request processing.</exception>
    /// <exception cref="McpException">An error occured during request processing.</exception>
    /// <remarks>
    /// This method provides low-level access to send raw JSON-RPC requests. For most use cases,
    /// consider using the strongly-typed extension methods that provide a more convenient API.
    /// </remarks>
    Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken = default);

    ///
    /// Sends a JSON-RPC message to the connected endpoint.
    ///
    /// <param name="message">
    /// The JSON-RPC message to send. This can be any type that implements JsonRpcMessage, such as
    /// JsonRpcRequest, JsonRpcResponse, JsonRpcNotification, or JsonRpcError.
    /// </param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous send operation.</returns>
    /// <exception cref="InvalidOperationException">The transport is not connected.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="message"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// <para>
    /// This method provides low-level access to send any JSON-RPC message. For specific message types,
    /// consider using the higher-level methods such as <see cref="SendRequestAsync"/> or extension methods
    /// like <see cref="McpEndpointExtensions.SendNotificationAsync(IMcpEndpoint, string, CancellationToken)"/>,
    /// which provide a simpler API.
    /// </para>
    /// <para>
    /// The method will serialize the message and transmit it using the underlying transport mechanism.
    /// </para>
    /// </remarks>
    Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default);

    /// Registers a handler to be invoked when a notification for the specified method is received.
    /// <param name="method">The notification method.</param>
    /// <param name="handler">The handler to be invoked.</param>
    /// <returns>An <see cref="IDisposable"/> that will remove the registered handler when disposed.</returns>
    IAsyncDisposable RegisterNotificationHandler(string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler);
}

using System.ComponentModel;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

///
/// Represents a progress token, which can be either a string or an integer.
///
[JsonConverter(typeof(Converter))]
public readonly struct ProgressToken : IEquatable<ProgressToken>
{
    /// The token, either a string or a boxed long or null.
    private readonly object? _token;

    /// Initializes a new instance of the <see cref="ProgressToken"/> with a specified value.
    /// <param name="value">The required ID value.</param>
    public ProgressToken(string value)
    {
        Throw.IfNull(value);
        _token = value;
    }

    /// Initializes a new instance of the <see cref="ProgressToken"/> with a specified value.
    /// <param name="value">The required ID value.</param>
    public ProgressToken(long value)
    {
        // Box the long. Progress tokens are almost always strings in practice, so this should be rare.
        _token = value;
    }

    /// Gets the underlying object for this token.
    /// <remarks>This will either be a <see cref="string"/>, a boxed <see cref="long"/>, or <see langword="null"/>.</remarks>
    public object? Token => _token;

    /// <inheritdoc />
    public override string? ToString() =>
        _token is string stringValue ? stringValue :
        _token is long longValue ? longValue.ToString(CultureInfo.InvariantCulture) :
        null;

    /// <inheritdoc />
    public bool Equals(ProgressToken other) => Equals(_token, other._token);

    /// <inheritdoc />
    public override bool Equals(object? obj) => obj is ProgressToken other && Equals(other);

    /// <inheritdoc />
    public override int GetHashCode() => _token?.GetHashCode() ?? 0;

    /// <inheritdoc />
    public static bool operator ==(ProgressToken left, ProgressToken right) => left.Equals(right);

    /// <inheritdoc />
    public static bool operator !=(ProgressToken left, ProgressToken right) => !left.Equals(right);

    ///
    /// Provides a <see cref="JsonConverter"/> for <see cref="ProgressToken"/> that handles both string and number values.
    ///
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed class Converter : JsonConverter<ProgressToken>
    {
        /// <inheritdoc />
        public override ProgressToken Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return reader.TokenType switch
            {
                JsonTokenType.String => new(reader.GetString()!),
                JsonTokenType.Number => new(reader.GetInt64()),
                _ => throw new JsonException("progressToken must be a string or an integer"),
            };
        }

        /// <inheritdoc />
        public override void Write(Utf8JsonWriter writer, ProgressToken value, JsonSerializerOptions options)
        {
            Throw.IfNull(writer);

            switch (value._token)
            {
                case string str:
                    writer.WriteStringValue(str);
                    return;

                case long longValue:
                    writer.WriteNumberValue(longValue);
                    return;

                case null:
                    writer.WriteStringValue(string.Empty);
                    return;
            }
        }
    }
}

using Microsoft.Extensions.Logging;
using ModelContextProtocol.Authentication;
using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Client;

/// <summary>
/// Provides an <see cref="IClientTransport"/> over HTTP using the Server-Sent Events (SSE) or Streamable HTTP protocol.
/// </summary>
/// <remarks>
/// This transport connects to an MCP server over HTTP using SSE or Streamable HTTP,
/// allowing for real-time server-to-client communication with a standard HTTP requests.
/// Unlike the <see cref="StdioClientTransport"/>, this transport connects to an existing server
/// rather than launching a new process.
/// </remarks>
public sealed class SseClientTransport : IClientTransport, IAsyncDisposable
{
    private readonly SseClientTransportOptions _options;
    private readonly McpHttpClient _mcpHttpClient;
    private readonly ILoggerFactory? _loggerFactory;

    private readonly HttpClient? _ownedHttpClient;

    /// <summary>
    /// Initializes a new instance of the <see cref="SseClientTransport"/> class.
    /// </summary>
    /// <param name="transportOptions">Configuration options for the transport.</param>
    /// <param name="loggerFactory">Logger factory for creating loggers used for diagnostic output during transport operations.</param>
    public SseClientTransport(SseClientTransportOptions transportOptions, ILoggerFactory? loggerFactory = null)
        : this(transportOptions, new HttpClient(), loggerFactory, ownsHttpClient: true)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="SseClientTransport"/> class with a provided HTTP client.
    /// </summary>
    /// <param name="transportOptions">Configuration options for the transport.</param>
    /// <param name="httpClient">The HTTP client instance used for requests.</param>
    /// <param name="loggerFactory">Logger factory for creating loggers used for diagnostic output during transport operations.</param>
    /// <param name="ownsHttpClient">
    /// <see langword="true"/> to dispose of <paramref name="httpClient"/> when the transport is disposed;
    /// <see langword="false"/> if the caller is retaining ownership of the <paramref name="httpClient"/>'s lifetime.
    /// </param>
    public SseClientTransport(SseClientTransportOptions transportOptions, HttpClient httpClient, ILoggerFactory? loggerFactory = null, bool ownsHttpClient = false)
    {
        Throw.IfNull(transportOptions);
        Throw.IfNull(httpClient);

        _options = transportOptions;
        _loggerFactory = loggerFactory;
        Name = transportOptions.Name ?? transportOptions.Endpoint.ToString();

        if (transportOptions.OAuth is { } clientOAuthOptions)
        {
            var oAuthProvider = new ClientOAuthProvider(_options.Endpoint, clientOAuthOptions, httpClient, loggerFactory);
            _mcpHttpClient = new AuthenticatingMcpHttpClient(httpClient, oAuthProvider);
        }
        else
        {
            _mcpHttpClient = new(httpClient);
        }

        if (ownsHttpClient)
        {
            _ownedHttpClient = httpClient;
        }
    }

    /// <inheritdoc />
    public string Name { get; }

    /// <inheritdoc />
    public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default)
    {
        return _options.TransportMode switch
        {
            HttpTransportMode.AutoDetect => new AutoDetectingClientSessionTransport(Name, _options, _mcpHttpClient, _loggerFactory),
            HttpTransportMode.StreamableHttp => new StreamableHttpClientSessionTransport(Name, _options, _mcpHttpClient, messageChannel: null, _loggerFactory),
            HttpTransportMode.Sse => await ConnectSseTransportAsync(cancellationToken).ConfigureAwait(false),
            _ => throw new InvalidOperationException($"Unsupported transport mode: {_options.TransportMode}"),
        };
    }

    private async Task<ITransport> ConnectSseTransportAsync(CancellationToken cancellationToken)
    {
        var sessionTransport = new SseClientSessionTransport(Name, _options, _mcpHttpClient, messageChannel: null, _loggerFactory);

        try
        {
            await sessionTransport.ConnectAsync(cancellationToken).ConfigureAwait(false);
            return sessionTransport;
        }
        catch
        {
            await sessionTransport.DisposeAsync().ConfigureAwait(false);
            throw;
        }
    }

    /// <inheritdoc />
    public ValueTask DisposeAsync()
    {
        _ownedHttpClient?.Dispose();
        return default;
    }
}

using ModelContextProtocol.Client;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a reference to a resource or prompt in the Model Context Protocol.
/// </summary>
/// <remarks>
/// <para>
/// References are commonly used with <see cref="McpClientExtensions.CompleteAsync"/> to request completion suggestions for arguments,
/// and with other methods that need to reference resources or prompts.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
[JsonConverter(typeof(Converter))]
public abstract class Reference
{
    /// <summary>Prevent external derivations.</summary>
    private protected Reference()
    {
    }

    /// <summary>
    /// Gets or sets the type of content.
    /// </summary>
    /// <remarks>
    /// This can be "ref/resource" or "ref/prompt".
    /// </remarks>
    [JsonPropertyName("type")]
    public string Type { get; set; } = string.Empty;

    /// <summary>
    /// Provides a <see cref="JsonConverter"/> for <see cref="Reference"/>.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed class Converter : JsonConverter<Reference>
    {
        /// <inheritdoc/>
        public override Reference? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.Null)
            {
                return null;
            }

            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            }

            string? type = null;
            string? name = null;
            string? title = null;
            string? uri = null;

            while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
            {
                if (reader.TokenType != JsonTokenType.PropertyName)
                {
                    continue;
                }

                string? propertyName = reader.GetString();
                bool success = reader.Read();
                Debug.Assert(success, "STJ must have buffered the entire object for us.");

                switch (propertyName)
                {
                    case "type":
                        type = reader.GetString();
                        break;

                    case "name":
                        name = reader.GetString();
                        break;

                    case "uri":
                        uri = reader.GetString();
                        break;

                    default:
                        break;
                }
            }

            // TODO: This converter exists due to the lack of downlevel support for AllowOutOfOrderMetadataProperties.

            switch (type)
            {
                case "ref/prompt":
                    if (name is null)
                    {
                        throw new JsonException("Prompt references must have a 'name' property.");
                    }

                    return new PromptReference { Name = name, Title = title };

                case "ref/resource":
                    if (uri is null)
                    {
                        throw new JsonException("Resource references must have a 'uri' property.");
                    }

                    return new ResourceTemplateReference { Uri = uri };

                default:
                    throw new JsonException($"Unknown content type: '{type}'");
            }
        }

        /// <inheritdoc/>
        public override void Write(Utf8JsonWriter writer, Reference value, JsonSerializerOptions options)
        {
            if (value is null)
            {
                writer.WriteNullValue();
                return;
            }

            writer.WriteStartObject();

            writer.WriteString("type", value.Type);

            switch (value)
            {
                case PromptReference pr:
                    writer.WriteString("name", pr.Name);
                    if (pr.Title is not null)
                    {
                        writer.WriteString("title", pr.Title);
                    }
                    break;

                case ResourceTemplateReference rtr:
                    writer.WriteString("uri", rtr.Uri);
                    break;
            }

            writer.WriteEndObject();
        }
    }
}

/// <summary>
/// Represents a reference to a prompt, identified by its name.
/// </summary>
public sealed class PromptReference : Reference, IBaseMetadata
{
    /// <summary>
    /// Initializes a new instance of the <see cref="PromptReference"/> class.
    /// </summary>
    public PromptReference() => Type = "ref/prompt";

    /// <inheritdoc />
    [JsonPropertyName("name")]
    public required string Name { get; set; }

    /// <inheritdoc />
    [JsonPropertyName("title")]
    public string? Title { get; set; }

    /// <inheritdoc />
    public override string ToString() => $"\"{Type}\": \"{Name}\"";
}

/// <summary>
/// Represents a reference to a resource or resource template definition.
/// </summary>
public sealed class ResourceTemplateReference : Reference
{
    /// <summary>
    /// Initializes a new instance of the <see cref="ResourceTemplateReference"/> class.
    /// </summary>
    public ResourceTemplateReference() => Type = "ref/resource";

    /// <summary>
    /// Gets or sets the URI or URI template of the resource.
    /// </summary>
    [JsonPropertyName("uri")]
    [StringSyntax(StringSyntaxAttribute.Uri)]
    public required string? Uri { get; set; }

    /// <inheritdoc />
    public override string ToString() => $"\"{Type}\": \"{Uri}\"";
}

using System.ComponentModel;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

///
/// Represents a JSON-RPC request identifier, which can be either a string or an integer.
///
[JsonConverter(typeof(Converter))]
public readonly struct RequestId : IEquatable<RequestId>
{
    /// The id, either a string or a boxed long or null.
    private readonly object? _id;

    /// Initializes a new instance of the <see cref="RequestId"/> with a specified value.
    /// <param name="value">The required ID value.</param>
    public RequestId(string value)
    {
        Throw.IfNull(value);
        _id = value;
    }

    /// Initializes a new instance of the <see cref="RequestId"/> with a specified value.
    /// <param name="value">The required ID value.</param>
    public RequestId(long value)
    {
        // Box the long. Request IDs are almost always strings in practice, so this should be rare.
        _id = value;
    }

    /// Gets the underlying object for this id.
    /// <remarks>This will either be a <see cref="string"/>, a boxed <see cref="long"/>, or <see langword="null"/>.</remarks>
    public object? Id => _id;

    /// <inheritdoc />
    public override string ToString() =>
        _id is string stringValue ? stringValue :
        _id is long longValue ? longValue.ToString(CultureInfo.InvariantCulture) :
        string.Empty;

    /// <inheritdoc />
    public bool Equals(RequestId other) => Equals(_id, other._id);

    /// <inheritdoc />
    public override bool Equals(object? obj) => obj is RequestId other && Equals(other);

    /// <inheritdoc />
    public override int GetHashCode() => _id?.GetHashCode() ?? 0;

    /// <inheritdoc />
    public static bool operator ==(RequestId left, RequestId right) => left.Equals(right);

    /// <inheritdoc />
    public static bool operator !=(RequestId left, RequestId right) => !left.Equals(right);

    ///
    /// Provides a <see cref="JsonConverter"/> for <see cref="RequestId"/> that handles both string and number values.
    ///
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed class Converter : JsonConverter<RequestId>
    {
        /// <inheritdoc />
        public override RequestId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return reader.TokenType switch
            {
                JsonTokenType.String => new(reader.GetString()!),
                JsonTokenType.Number => new(reader.GetInt64()),
                _ => throw new JsonException("requestId must be a string or an integer"),
            };
        }

        /// <inheritdoc />
        public override void Write(Utf8JsonWriter writer, RequestId value, JsonSerializerOptions options)
        {
            Throw.IfNull(writer);

            switch (value._id)
            {
                case string str:
                    writer.WriteStringValue(str);
                    return;

                case long longValue:
                    writer.WriteNumberValue(longValue);
                    return;

                case null:
                    writer.WriteStringValue(string.Empty);
                    return;
            }
        }
    }
}

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ModelContextProtocol.Authentication;
using System.Text.Encodings.Web;

namespace ModelContextProtocol.AspNetCore.Authentication;

/// <summary>
/// Authentication handler for MCP protocol that adds resource metadata to challenge responses
/// and handles resource metadata endpoint requests.
/// </summary>
public class McpAuthenticationHandler : AuthenticationHandler<McpAuthenticationOptions>, IAuthenticationRequestHandler
{
    /// <summary>
    /// Initializes a new instance of the <see cref="McpAuthenticationHandler"/> class.
    /// </summary>
    public McpAuthenticationHandler(
        IOptionsMonitor<McpAuthenticationOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder)
        : base(options, logger, encoder)
    {
    }

    /// <inheritdoc />
    public async Task<bool> HandleRequestAsync()
    {
        // Check if the request is for the resource metadata endpoint
        string requestPath = Request.Path.Value ?? string.Empty;

        string expectedMetadataPath = Options.ResourceMetadataUri?.ToString() ?? string.Empty;
        if (Options.ResourceMetadataUri != null && !Options.ResourceMetadataUri.IsAbsoluteUri)
        {
            // For relative URIs, it's just the path component.
            expectedMetadataPath = Options.ResourceMetadataUri.OriginalString;
        }

        // If the path doesn't match, let the request continue through the pipeline
        if (!string.Equals(requestPath, expectedMetadataPath, StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        return await HandleResourceMetadataRequestAsync();
    }

    /// <summary>
    /// Gets the base URL from the current request, including scheme, host, and path base.
    /// </summary>
    private string GetBaseUrl() => $"{Request.Scheme}://{Request.Host}{Request.PathBase}";

    /// <summary>
    /// Gets the absolute URI for the resource metadata endpoint.
    /// </summary>
    private string GetAbsoluteResourceMetadataUri()
    {
        var resourceMetadataUri = Options.ResourceMetadataUri;

        string currentPath = resourceMetadataUri?.ToString() ?? string.Empty;

        if (resourceMetadataUri != null && resourceMetadataUri.IsAbsoluteUri)
        {
            return currentPath;
        }

        // For relative URIs, combine with the base URL
        string baseUrl = GetBaseUrl();
        string relativePath = resourceMetadataUri?.OriginalString.TrimStart('/') ?? string.Empty;

        if (!Uri.TryCreate($"{baseUrl.TrimEnd('/')}/{relativePath}", UriKind.Absolute, out var absoluteUri))
        {
            throw new InvalidOperationException($"Could not create absolute URI for resource metadata. Base URL: {baseUrl}, Relative Path: {relativePath}");
        }

        return absoluteUri.ToString();
    }

    private async Task<bool> HandleResourceMetadataRequestAsync()
    {
        var resourceMetadata = Options.ResourceMetadata;

        if (Options.Events.OnResourceMetadataRequest is not null)
        {
            var context = new ResourceMetadataRequestContext(Request.HttpContext, Scheme, Options)
            {
                ResourceMetadata = CloneResourceMetadata(resourceMetadata),
            };

            await Options.Events.OnResourceMetadataRequest(context);

            if (context.Result is not null)
            {
                if (context.Result.Handled)
                {
                    return true;
                }
                else if (context.Result.Skipped)
                {
                    return false;
                }
                else if (context.Result.Failure is not null)
                {
                    throw new AuthenticationFailureException("An error occurred from the OnResourceMetadataRequest event.", context.Result.Failure);
                }
            }

            resourceMetadata = context.ResourceMetadata;
        }

        if (resourceMetadata == null)
        {
            throw new InvalidOperationException(
                "ResourceMetadata has not been configured. Please set McpAuthenticationOptions.ResourceMetadata or ensure context.ResourceMetadata is set inside McpAuthenticationOptions.Events.OnResourceMetadataRequest."
            );
        }

        await Results.Json(resourceMetadata, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ProtectedResourceMetadata))).ExecuteAsync(Context);
        return true;
    }

    /// <inheritdoc />
    // If no forwarding is configured, this handler doesn't perform authentication
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync() => AuthenticateResult.NoResult();

    /// <inheritdoc />
    protected override Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        // Get the absolute URI for the resource metadata
        string rawPrmDocumentUri = GetAbsoluteResourceMetadataUri();

        properties ??= new AuthenticationProperties();

        // Store the resource_metadata in properties in case other handlers need it
        properties.Items["resource_metadata"] = rawPrmDocumentUri;

        // Add the WWW-Authenticate header with Bearer scheme and resource metadata
        string headerValue = $"Bearer realm=\"{Scheme.Name}\", resource_metadata=\"{rawPrmDocumentUri}\"";
        Response.Headers.Append("WWW-Authenticate", headerValue);

        return base.HandleChallengeAsync(properties);
    }

    internal static ProtectedResourceMetadata? CloneResourceMetadata(ProtectedResourceMetadata? resourceMetadata)
    {
        if (resourceMetadata is null)
        {
            return null;
        }

        return new ProtectedResourceMetadata
        {
            Resource = resourceMetadata.Resource,
            AuthorizationServers = [.. resourceMetadata.AuthorizationServers],
            BearerMethodsSupported = [.. resourceMetadata.BearerMethodsSupported],
            ScopesSupported = [.. resourceMetadata.ScopesSupported],
            JwksUri = resourceMetadata.JwksUri,
            ResourceSigningAlgValuesSupported = resourceMetadata.ResourceSigningAlgValuesSupported is not null ? [.. resourceMetadata.ResourceSigningAlgValuesSupported] : null,
            ResourceName = resourceMetadata.ResourceName,
            ResourceDocumentation = resourceMetadata.ResourceDocumentation,
            ResourcePolicyUri = resourceMetadata.ResourcePolicyUri,
            ResourceTosUri = resourceMetadata.ResourceTosUri,
            TlsClientCertificateBoundAccessTokens = resourceMetadata.TlsClientCertificateBoundAccessTokens,
            AuthorizationDetailsTypesSupported = resourceMetadata.AuthorizationDetailsTypesSupported is not null ? [.. resourceMetadata.AuthorizationDetailsTypesSupported] : null,
            DpopSigningAlgValuesSupported = resourceMetadata.DpopSigningAlgValuesSupported is not null ? [.. resourceMetadata.DpopSigningAlgValuesSupported] : null,
            DpopBoundAccessTokensRequired = resourceMetadata.DpopBoundAccessTokensRequired
        };
    }

}

using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Server;

/// <summary>
/// Provides a container for handlers used in the creation of an MCP server.
/// </summary>
/// <remarks>
/// <para>
/// This class provides a centralized collection of delegates that implement various capabilities of the Model Context Protocol.
/// Each handler in this class corresponds to a specific endpoint in the Model Context Protocol and
/// is responsible for processing a particular type of request. The handlers are used to customize
/// the behavior of the MCP server by providing implementations for the various protocol operations.
/// </para>
/// <para>
/// Handlers can be configured individually using the extension methods in <see cref="McpServerBuilderExtensions"/>
/// such as <see cref="McpServerBuilderExtensions.WithListToolsHandler"/> and
/// <see cref="McpServerBuilderExtensions.WithCallToolHandler"/>.
/// </para>
/// <para>
/// When a client sends a request to the server, the appropriate handler is invoked to process the
/// request and produce a response according to the protocol specification. Which handler is selected
/// is done based on an ordinal, case-sensitive string comparison.
/// </para>
/// </remarks>
public sealed class McpServerHandlers
{
    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ToolsList"/> requests.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The handler should return a list of available tools when requested by a client.
    /// It supports pagination through the cursor mechanism, where the client can make
    /// repeated calls with the cursor returned by the previous call to retrieve more tools.
    /// </para>
    /// <para>
    /// This handler works alongside any tools defined in the <see cref="McpServerTool"/> collection.
    /// Tools from both sources will be combined when returning results to clients.
    /// </para>
    /// </remarks>
    public Func<RequestContext<ListToolsRequestParams>, CancellationToken, ValueTask<ListToolsResult>>? ListToolsHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ToolsCall"/> requests.
    /// </summary>
    /// <remarks>
    /// This handler is invoked when a client makes a call to a tool that isn't found in the <see cref="McpServerTool"/> collection.
    /// The handler should implement logic to execute the requested tool and return appropriate results.
    /// </remarks>
    public Func<RequestContext<CallToolRequestParams>, CancellationToken, ValueTask<CallToolResult>>? CallToolHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.PromptsList"/> requests.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The handler should return a list of available prompts when requested by a client.
    /// It supports pagination through the cursor mechanism, where the client can make
    /// repeated calls with the cursor returned by the previous call to retrieve more prompts.
    /// </para>
    /// <para>
    /// This handler works alongside any prompts defined in the <see cref="McpServerPrompt"/> collection.
    /// Prompts from both sources will be combined when returning results to clients.
    /// </para>
    /// </remarks>
    public Func<RequestContext<ListPromptsRequestParams>, CancellationToken, ValueTask<ListPromptsResult>>? ListPromptsHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.PromptsGet"/> requests.
    /// </summary>
    /// <remarks>
    /// This handler is invoked when a client requests details for a specific prompt that isn't found in the <see cref="McpServerPrompt"/> collection.
    /// The handler should implement logic to fetch or generate the requested prompt and return appropriate results.
    /// </remarks>
    public Func<RequestContext<GetPromptRequestParams>, CancellationToken, ValueTask<GetPromptResult>>? GetPromptHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ResourcesTemplatesList"/> requests.
    /// </summary>
    /// <remarks>
    /// The handler should return a list of available resource templates when requested by a client.
    /// It supports pagination through the cursor mechanism, where the client can make
    /// repeated calls with the cursor returned by the previous call to retrieve more resource templates.
    /// </remarks>
    public Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, ValueTask<ListResourceTemplatesResult>>? ListResourceTemplatesHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ResourcesList"/> requests.
    /// </summary>
    /// <remarks>
    /// The handler should return a list of available resources when requested by a client.
    /// It supports pagination through the cursor mechanism, where the client can make
    /// repeated calls with the cursor returned by the previous call to retrieve more resources.
    /// </remarks>
    public Func<RequestContext<ListResourcesRequestParams>, CancellationToken, ValueTask<ListResourcesResult>>? ListResourcesHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ResourcesRead"/> requests.
    /// </summary>
    /// <remarks>
    /// This handler is invoked when a client requests the content of a specific resource identified by its URI.
    /// The handler should implement logic to locate and retrieve the requested resource.
    /// </remarks>
    public Func<RequestContext<ReadResourceRequestParams>, CancellationToken, ValueTask<ReadResourceResult>>? ReadResourceHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.CompletionComplete"/> requests.
    /// </summary>
    /// <remarks>
    /// This handler provides auto-completion suggestions for prompt arguments or resource references in the Model Context Protocol.
    /// The handler processes auto-completion requests, returning a list of suggestions based on the
    /// reference type and current argument value.
    /// </remarks>
    public Func<RequestContext<CompleteRequestParams>, CancellationToken, ValueTask<CompleteResult>>? CompleteHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ResourcesSubscribe"/> requests.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This handler is invoked when a client wants to receive notifications about changes to specific resources or resource patterns.
    /// The handler should implement logic to register the client's interest in the specified resources
    /// and set up the necessary infrastructure to send notifications when those resources change.
    /// </para>
    /// <para>
    /// After a successful subscription, the server should send resource change notifications to the client
    /// whenever a relevant resource is created, updated, or deleted.
    /// </para>
    /// </remarks>
    public Func<RequestContext<SubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>>? SubscribeToResourcesHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ResourcesUnsubscribe"/> requests.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This handler is invoked when a client wants to stop receiving notifications about previously subscribed resources.
    /// The handler should implement logic to remove the client's subscriptions to the specified resources
    /// and clean up any associated resources.
    /// </para>
    /// <para>
    /// After a successful unsubscription, the server should no longer send resource change notifications
    /// to the client for the specified resources.
    /// </para>
    /// </remarks>
    public Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>>? UnsubscribeFromResourcesHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.LoggingSetLevel"/> requests.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This handler processes <see cref="RequestMethods.LoggingSetLevel"/> requests from clients. When set, it enables
    /// clients to control which log messages they receive by specifying a minimum severity threshold.
    /// </para>
    /// <para>
    /// After handling a level change request, the server typically begins sending log messages
    /// at or above the specified level to the client as notifications/message notifications.
    /// </para>
    /// </remarks>
    public Func<RequestContext<SetLevelRequestParams>, CancellationToken, ValueTask<EmptyResult>>? SetLoggingLevelHandler { get; set; }

    /// <summary>
    /// Overwrite any handlers in McpServerOptions with non-null handlers from this instance.
    /// </summary>
    /// <param name="options"></param>
    /// <returns></returns>
    internal void OverwriteWithSetHandlers(McpServerOptions options)
    {
        PromptsCapability? promptsCapability = options.Capabilities?.Prompts;
        if (ListPromptsHandler is not null || GetPromptHandler is not null)
        {
            promptsCapability ??= new();
            promptsCapability.ListPromptsHandler = ListPromptsHandler ?? promptsCapability.ListPromptsHandler;
            promptsCapability.GetPromptHandler = GetPromptHandler ?? promptsCapability.GetPromptHandler;
        }

        ResourcesCapability? resourcesCapability = options.Capabilities?.Resources;
        if (ListResourcesHandler is not null ||
            ReadResourceHandler is not null)
        {
            resourcesCapability ??= new();
            resourcesCapability.ListResourceTemplatesHandler = ListResourceTemplatesHandler ?? resourcesCapability.ListResourceTemplatesHandler;
            resourcesCapability.ListResourcesHandler = ListResourcesHandler ?? resourcesCapability.ListResourcesHandler;
            resourcesCapability.ReadResourceHandler = ReadResourceHandler ?? resourcesCapability.ReadResourceHandler;

            if (SubscribeToResourcesHandler is not null || UnsubscribeFromResourcesHandler is not null)
            {
                resourcesCapability.SubscribeToResourcesHandler = SubscribeToResourcesHandler ?? resourcesCapability.SubscribeToResourcesHandler;
                resourcesCapability.UnsubscribeFromResourcesHandler = UnsubscribeFromResourcesHandler ?? resourcesCapability.UnsubscribeFromResourcesHandler;
                resourcesCapability.Subscribe = true;
            }
        }

        ToolsCapability? toolsCapability = options.Capabilities?.Tools;
        if (ListToolsHandler is not null || CallToolHandler is not null)
        {
            toolsCapability ??= new();
            toolsCapability.ListToolsHandler = ListToolsHandler ?? toolsCapability.ListToolsHandler;
            toolsCapability.CallToolHandler = CallToolHandler ?? toolsCapability.CallToolHandler;
        }

        LoggingCapability? loggingCapability = options.Capabilities?.Logging;
        if (SetLoggingLevelHandler is not null)
        {
            loggingCapability ??= new();
            loggingCapability.SetLoggingLevelHandler = SetLoggingLevelHandler;
        }

        CompletionsCapability? completionsCapability = options.Capabilities?.Completions;
        if (CompleteHandler is not null)
        {
            completionsCapability ??= new();
            completionsCapability.CompleteHandler = CompleteHandler;
        }

        options.Capabilities ??= new();
        options.Capabilities.Prompts = promptsCapability;
        options.Capabilities.Resources = resourcesCapability;
        options.Capabilities.Tools = toolsCapability;
        options.Capabilities.Logging = loggingCapability;
        options.Capabilities.Completions = completionsCapability;
    }
}

using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using ModelContextProtocol.AspNetCore.Stateless;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

namespace ModelContextProtocol.AspNetCore;

internal sealed class StreamableHttpHandler(
    IOptions<McpServerOptions> mcpServerOptionsSnapshot,
    IOptionsFactory<McpServerOptions> mcpServerOptionsFactory,
    IOptions<HttpServerTransportOptions> httpServerTransportOptions,
    IDataProtectionProvider dataProtection,
    ILoggerFactory loggerFactory,
    IServiceProvider applicationServices)
{
    private const string McpSessionIdHeaderName = "Mcp-Session-Id";
    private static readonly JsonTypeInfo<JsonRpcError> s_errorTypeInfo = GetRequiredJsonTypeInfo<JsonRpcError>();

    public ConcurrentDictionary<string, HttpMcpSession<StreamableHttpServerTransport>> Sessions { get; } = new(StringComparer.Ordinal);

    public HttpServerTransportOptions HttpServerTransportOptions => httpServerTransportOptions.Value;

    private IDataProtector Protector { get; } = dataProtection.CreateProtector("Microsoft.AspNetCore.StreamableHttpHandler.StatelessSessionId");

    public async Task HandlePostRequestAsync(HttpContext context)
    {
        // The Streamable HTTP spec mandates the client MUST accept both application/json and text/event-stream.
        // ASP.NET Core Minimal APIs mostly try to stay out of the business of response content negotiation,
        // so we have to do this manually. The spec doesn't mandate that servers MUST reject these requests,
        // but it's probably good to at least start out trying to be strict.
        var typedHeaders = context.Request.GetTypedHeaders();
        if (!typedHeaders.Accept.Any(MatchesApplicationJsonMediaType) || !typedHeaders.Accept.Any(MatchesTextEventStreamMediaType))
        {
            await WriteJsonRpcErrorAsync(context,
                "Not Acceptable: Client must accept both application/json and text/event-stream",
                StatusCodes.Status406NotAcceptable);
            return;
        }

        var session = await GetOrCreateSessionAsync(context);
        if (session is null)
        {
            return;
        }

        try
        {
            using var _ = session.AcquireReference();

            InitializeSseResponse(context);
            var wroteResponse = await session.Transport.HandlePostRequest(new HttpDuplexPipe(context), context.RequestAborted);
            if (!wroteResponse)
            {
                // We wound up writing nothing, so there should be no Content-Type response header.
                context.Response.Headers.ContentType = (string?)null;
                context.Response.StatusCode = StatusCodes.Status202Accepted;
            }
        }
        finally
        {
            // Stateless sessions are 1:1 with HTTP requests and are outlived by the MCP session tracked by the Mcp-Session-Id.
            // Non-stateless sessions are 1:1 with the Mcp-Session-Id and outlive the POST request.
            // Non-stateless sessions get disposed by a DELETE request or the IdleTrackingBackgroundService.
            if (HttpServerTransportOptions.Stateless)
            {
                await session.DisposeAsync();
            }
        }
    }

    public async Task HandleGetRequestAsync(HttpContext context)
    {
        if (!context.Request.GetTypedHeaders().Accept.Any(MatchesTextEventStreamMediaType))
        {
            await WriteJsonRpcErrorAsync(context,
                "Not Acceptable: Client must accept text/event-stream",
                StatusCodes.Status406NotAcceptable);
            return;
        }

        var sessionId = context.Request.Headers[McpSessionIdHeaderName].ToString();
        var session = await GetSessionAsync(context, sessionId);
        if (session is null)
        {
            return;
        }

        if (!session.TryStartGetRequest())
        {
            await WriteJsonRpcErrorAsync(context,
                "Bad Request: This server does not support multiple GET requests. Start a new session to get a new GET SSE response.",
                StatusCodes.Status400BadRequest);
            return;
        }

        using var _ = session.AcquireReference();
        InitializeSseResponse(context);

        // We should flush headers to indicate a 200 success quickly, because the initialization response
        // will be sent in response to a different POST request. It might be a while before we send a message
        // over this response body.
        await context.Response.Body.FlushAsync(context.RequestAborted);
        await session.Transport.HandleGetRequest(context.Response.Body, context.RequestAborted);
    }

    public async Task HandleDeleteRequestAsync(HttpContext context)
    {
        var sessionId = context.Request.Headers[McpSessionIdHeaderName].ToString();
        if (Sessions.TryRemove(sessionId, out var session))
        {
            await session.DisposeAsync();
        }
    }

    private async ValueTask<HttpMcpSession<StreamableHttpServerTransport>?> GetSessionAsync(HttpContext context, string sessionId)
    {
        HttpMcpSession<StreamableHttpServerTransport>? session;

        if (HttpServerTransportOptions.Stateless)
        {
            var sessionJson = Protector.Unprotect(sessionId);
            var statelessSessionId = JsonSerializer.Deserialize(sessionJson, StatelessSessionIdJsonContext.Default.StatelessSessionId);
            var transport = new StreamableHttpServerTransport
            {
                Stateless = true,
                SessionId = sessionId,
            };
            session = await CreateSessionAsync(context, transport, sessionId, statelessSessionId);
        }
        else if (!Sessions.TryGetValue(sessionId, out session))
        {
            // -32001 isn't part of the MCP standard, but this is what the typescript-sdk currently does.
            // One of the few other usages I found was from some Ethereum JSON-RPC documentation and this
            // JSON-RPC library from Microsoft called StreamJsonRpc where it's called JsonRpcErrorCode.NoMarshaledObjectFound
            // https://learn.microsoft.com/dotnet/api/streamjsonrpc.protocol.jsonrpcerrorcode?view=streamjsonrpc-2.9#fields
            await WriteJsonRpcErrorAsync(context, "Session not found", StatusCodes.Status404NotFound, -32001);
            return null;
        }

        if (!session.HasSameUserId(context.User))
        {
            await WriteJsonRpcErrorAsync(context,
                "Forbidden: The currently authenticated user does not match the user who initiated the session.",
                StatusCodes.Status403Forbidden);
            return null;
        }

        context.Response.Headers[McpSessionIdHeaderName] = session.Id;
        context.Features.Set(session.Server);
        return session;
    }

    private async ValueTask<HttpMcpSession<StreamableHttpServerTransport>?> GetOrCreateSessionAsync(HttpContext context)
    {
        var sessionId = context.Request.Headers[McpSessionIdHeaderName].ToString();

        if (string.IsNullOrEmpty(sessionId))
        {
            return await StartNewSessionAsync(context);
        }
        else
        {
            return await GetSessionAsync(context, sessionId);
        }
    }

    private async ValueTask<HttpMcpSession<StreamableHttpServerTransport>> StartNewSessionAsync(HttpContext context)
    {
        string sessionId;
        StreamableHttpServerTransport transport;

        if (!HttpServerTransportOptions.Stateless)
        {
            sessionId = MakeNewSessionId();
            transport = new()
            {
                SessionId = sessionId,
                FlowExecutionContextFromRequests = !HttpServerTransportOptions.PerSessionExecutionContext,
            };
            context.Response.Headers[McpSessionIdHeaderName] = sessionId;
        }
        else
        {
            // "(uninitialized stateless id)" is not written anywhere. We delay writing the MCP-Session-Id
            // until after we receive the initialize request with the client info we need to serialize.
            sessionId = "(uninitialized stateless id)";
            transport = new()
            {
                Stateless = true,
            };
            ScheduleStatelessSessionIdWrite(context, transport);
        }

        var session = await CreateSessionAsync(context, transport, sessionId);

        // The HttpMcpSession is not stored between requests in stateless mode. Instead, the session is recreated from the MCP-Session-Id.
        if (!HttpServerTransportOptions.Stateless)
        {
            if (!Sessions.TryAdd(sessionId, session))
            {
                throw new UnreachableException($"Unreachable given good entropy! Session with ID '{sessionId}' has already been created.");
            }
        }

        return session;
    }

    private async ValueTask<HttpMcpSession<StreamableHttpServerTransport>> CreateSessionAsync(
        HttpContext context,
        StreamableHttpServerTransport transport,
        string sessionId,
        StatelessSessionId? statelessId = null)
    {
        var mcpServerServices = applicationServices;
        var mcpServerOptions = mcpServerOptionsSnapshot.Value;
        if (statelessId is not null || HttpServerTransportOptions.ConfigureSessionOptions is not null)
        {
            mcpServerOptions = mcpServerOptionsFactory.Create(Options.DefaultName);

            if (statelessId is not null)
            {
                // The session does not outlive the request in stateless mode.
                mcpServerServices = context.RequestServices;
                mcpServerOptions.ScopeRequests = false;
                mcpServerOptions.KnownClientInfo = statelessId.ClientInfo;
            }

            if (HttpServerTransportOptions.ConfigureSessionOptions is { } configureSessionOptions)
            {
                await configureSessionOptions(context, mcpServerOptions, context.RequestAborted);
            }
        }

        var server = McpServerFactory.Create(transport, mcpServerOptions, loggerFactory, mcpServerServices);
        context.Features.Set(server);

        var userIdClaim = statelessId?.UserIdClaim ?? GetUserIdClaim(context.User);
        var session = new HttpMcpSession<StreamableHttpServerTransport>(sessionId, transport, userIdClaim, HttpServerTransportOptions.TimeProvider)
        {
            Server = server,
        };

        var runSessionAsync = HttpServerTransportOptions.RunSessionHandler ?? RunSessionAsync;
        session.ServerRunTask = runSessionAsync(context, server, session.SessionClosed);

        return session;
    }

    private static Task WriteJsonRpcErrorAsync(HttpContext context, string errorMessage, int statusCode, int errorCode = -32000)
    {
        var jsonRpcError = new JsonRpcError
        {
            Error = new()
            {
                Code = errorCode,
                Message = errorMessage,
            },
        };
        return Results.Json(jsonRpcError, s_errorTypeInfo, statusCode: statusCode).ExecuteAsync(context);
    }

    internal static void InitializeSseResponse(HttpContext context)
    {
        context.Response.Headers.ContentType = "text/event-stream";
        context.Response.Headers.CacheControl = "no-cache,no-store";

        // Make sure we disable all response buffering for SSE.
        context.Response.Headers.ContentEncoding = "identity";
        context.Features.GetRequiredFeature<IHttpResponseBodyFeature>().DisableBuffering();
    }

    internal static string MakeNewSessionId()
    {
        Span<byte> buffer = stackalloc byte[16];
        RandomNumberGenerator.Fill(buffer);
        return WebEncoders.Base64UrlEncode(buffer);
    }

    private void ScheduleStatelessSessionIdWrite(HttpContext context, StreamableHttpServerTransport transport)
    {
        transport.OnInitRequestReceived = initRequestParams =>
        {
            var statelessId = new StatelessSessionId
            {
                ClientInfo = initRequestParams?.ClientInfo,
                UserIdClaim = GetUserIdClaim(context.User),
            };

            var sessionJson = JsonSerializer.Serialize(statelessId, StatelessSessionIdJsonContext.Default.StatelessSessionId);
            transport.SessionId = Protector.Protect(sessionJson);
            context.Response.Headers[McpSessionIdHeaderName] = transport.SessionId;
            return ValueTask.CompletedTask;
        };
    }

    internal static Task RunSessionAsync(HttpContext httpContext, IMcpServer session, CancellationToken requestAborted)
        => session.RunAsync(requestAborted);

    // SignalR only checks for ClaimTypes.NameIdentifier in HttpConnectionDispatcher, but AspNetCore.Antiforgery checks that plus the sub and UPN claims.
    // However, we short-circuit unlike antiforgery since we expect to call this to verify MCP messages a lot more frequently than
    // verifying antiforgery tokens from <form> posts.
    internal static UserIdClaim? GetUserIdClaim(ClaimsPrincipal user)
    {
        if (user?.Identity?.IsAuthenticated != true)
        {
            return null;
        }

        var claim = user.FindFirst(ClaimTypes.NameIdentifier) ?? user.FindFirst("sub") ?? user.FindFirst(ClaimTypes.Upn);

        if (claim is { } idClaim)
        {
            return new(idClaim.Type, idClaim.Value, idClaim.Issuer);
        }

        return null;
    }

    private static JsonTypeInfo<T> GetRequiredJsonTypeInfo<T>() => (JsonTypeInfo<T>)McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(T));

    private static bool MatchesApplicationJsonMediaType(MediaTypeHeaderValue acceptHeaderValue)
        => acceptHeaderValue.MatchesMediaType("application/json");

    private static bool MatchesTextEventStreamMediaType(MediaTypeHeaderValue acceptHeaderValue)
        => acceptHeaderValue.MatchesMediaType("text/event-stream");

    private sealed class HttpDuplexPipe(HttpContext context) : IDuplexPipe
    {
        public PipeReader Input => context.Request.BodyReader;
        public PipeWriter Output => context.Response.BodyWriter;
    }
}

using Microsoft.Extensions.Logging.Abstractions;
using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents an out-of-band notification used to inform the receiver of a progress update for a long-running request.
/// </summary>
/// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for more details.
/// </remarks>
[JsonConverter(typeof(Converter))]
public sealed class ProgressNotificationParams : NotificationParams
{
    /// <summary>
    /// Gets or sets the progress token which was given in the initial request, used to associate this notification with
    /// the corresponding request.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This token acts as a correlation identifier that links progress updates to their corresponding request.
    /// </para>
    /// <para>
    /// When an endpoint initiates a request with a <see cref="ProgressToken"/> in its metadata,
    /// the receiver can send progress notifications using this same token. This allows both sides to
    /// correlate the notifications with the original request.
    /// </para>
    /// </remarks>
    public required ProgressToken ProgressToken { get; init; }

    /// <summary>
    /// Gets or sets the progress thus far.
    /// </summary>
    /// <remarks>
    /// This should increase for each notification issued as part of the same request, even if the total is unknown.
    /// </remarks>
    public required ProgressNotificationValue Progress { get; init; }

    /// <summary>
    /// Provides a <see cref="JsonConverter"/> for <see cref="ProgressNotificationParams"/>.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed class Converter : JsonConverter<ProgressNotificationParams>
    {
        /// <inheritdoc />
        public override ProgressNotificationParams? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            ProgressToken? progressToken = null;
            float? progress = null;
            float? total = null;
            string? message = null;
            JsonObject? meta = null;

            while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
            {
                if (reader.TokenType == JsonTokenType.PropertyName)
                {
                    var propertyName = reader.GetString();
                    reader.Read();
                    switch (propertyName)
                    {
                        case "progressToken":
                            progressToken = (ProgressToken)JsonSerializer.Deserialize(ref reader, options.GetTypeInfo(typeof(ProgressToken)))!;
                            break;

                        case "progress":
                            progress = reader.GetSingle();
                            break;

                        case "total":
                            total = reader.GetSingle();
                            break;

                        case "message":
                            message = reader.GetString();
                            break;

                        case "_meta":
                            meta = JsonSerializer.Deserialize(ref reader, McpJsonUtilities.JsonContext.Default.JsonObject);
                            break;
                    }
                }
            }

            if (progress is null)
            {
                throw new JsonException("Missing required property 'progress'.");
            }

            if (progressToken is null)
            {
                throw new JsonException("Missing required property 'progressToken'.");
            }

            return new ProgressNotificationParams
            {
                ProgressToken = progressToken.GetValueOrDefault(),
                Progress = new ProgressNotificationValue
                {
                    Progress = progress.GetValueOrDefault(),
                    Total = total,
                    Message = message,
                },
                Meta = meta,
            };
        }

        /// <inheritdoc />
        public override void Write(Utf8JsonWriter writer, ProgressNotificationParams value, JsonSerializerOptions options)
        {
            writer.WriteStartObject();

            writer.WritePropertyName("progressToken");
            JsonSerializer.Serialize(writer, value.ProgressToken, options.GetTypeInfo(typeof(ProgressToken)));

            writer.WriteNumber("progress", value.Progress.Progress);

            if (value.Progress.Total is { } total)
            {
                writer.WriteNumber("total", total);
            }

            if (value.Progress.Message is { } message)
            {
                writer.WriteString("message", message);
            }

            if (value.Meta is { } meta)
            {
                writer.WritePropertyName("_meta");
                JsonSerializer.Serialize(writer, meta, McpJsonUtilities.JsonContext.Default.JsonObject);
            }

            writer.WriteEndObject();
        }
    }
}

using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

///
/// Represents a message issued from the server to elicit additional information from the user via the client.
///
public sealed class ElicitRequestParams
{
    ///
    /// Gets or sets the message to present to the user.
    ///
    [JsonPropertyName("message")]
    public string Message { get; set; } = string.Empty;

    ///
    /// Gets or sets the requested schema.
    ///
    /// <remarks>
    /// May be one of <see cref="StringSchema"/>, <see cref="NumberSchema"/>, <see cref="BooleanSchema"/>, or <see cref="EnumSchema"/>.
    /// </remarks>
    [JsonPropertyName("requestedSchema")]
    [field: MaybeNull]
    public RequestSchema RequestedSchema
    {
        get => field ??= new RequestSchema();
        set => field = value;
    }

    /// Represents a request schema used in an elicitation request.
    public class RequestSchema
    {
        /// Gets the type of the schema.
        /// <remarks>This is always "object".</remarks>
        [JsonPropertyName("type")]
        public string Type => "object";

        /// Gets or sets the properties of the schema.
        [JsonPropertyName("properties")]
        [field: MaybeNull]
        public IDictionary<string, PrimitiveSchemaDefinition> Properties
        {
            get => field ??= new Dictionary<string, PrimitiveSchemaDefinition>();
            set
            {
                Throw.IfNull(value);
                field = value;
            }
        }

        /// Gets or sets the required properties of the schema.
        [JsonPropertyName("required")]
        public IList<string>? Required { get; set; }
    }

    ///
    /// Represents restricted subset of JSON Schema:
    /// <see cref="StringSchema"/>, <see cref="NumberSchema"/>, <see cref="BooleanSchema"/>, or <see cref="EnumSchema"/>.
    ///
    [JsonConverter(typeof(Converter))] // TODO: This converter exists due to the lack of downlevel support for AllowOutOfOrderMetadataProperties.
    public abstract class PrimitiveSchemaDefinition
    {
        /// Prevent external derivations.
        protected private PrimitiveSchemaDefinition()
        {
        }

        /// Gets the type of the schema.
        [JsonPropertyName("type")]
        public abstract string Type { get; set; }

        /// Gets or sets a title for the schema.
        [JsonPropertyName("title")]
        public string? Title { get; set; }

        /// Gets or sets a description for the schema.
        [JsonPropertyName("description")]
        public string? Description { get; set; }

        ///
        /// Provides a <see cref="JsonConverter"/> for <see cref="ResourceContents"/>.
        ///
        [EditorBrowsable(EditorBrowsableState.Never)]
        public class Converter : JsonConverter<PrimitiveSchemaDefinition>
        {
            /// <inheritdoc/>
            public override PrimitiveSchemaDefinition? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
            {
                if (reader.TokenType == JsonTokenType.Null)
                {
                    return null;
                }

                if (reader.TokenType != JsonTokenType.StartObject)
                {
                    throw new JsonException();
                }

                string? type = null;
                string? title = null;
                string? description = null;
                int? minLength = null;
                int? maxLength = null;
                string? format = null;
                double? minimum = null;
                double? maximum = null;
                bool? defaultBool = null;
                IList<string>? enumValues = null;
                IList<string>? enumNames = null;

                while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
                {
                    if (reader.TokenType != JsonTokenType.PropertyName)
                    {
                        continue;
                    }

                    string? propertyName = reader.GetString();
                    bool success = reader.Read();
                    Debug.Assert(success, "STJ must have buffered the entire object for us.");

                    switch (propertyName)
                    {
                        case "type":
                            type = reader.GetString();
                            break;

                        case "title":
                            title = reader.GetString();
                            break;

                        case "description":
                            description = reader.GetString();
                            break;

                        case "minLength":
                            minLength = reader.GetInt32();
                            break;

                        case "maxLength":
                            maxLength = reader.GetInt32();
                            break;

                        case "format":
                            format = reader.GetString();
                            break;

                        case "minimum":
                            minimum = reader.GetDouble();
                            break;

                        case "maximum":
                            maximum = reader.GetDouble();
                            break;

                        case "default":
                            defaultBool = reader.GetBoolean();
                            break;

                        case "enum":
                            enumValues = JsonSerializer.Deserialize(ref reader, McpJsonUtilities.JsonContext.Default.IListString);
                            break;

                        case "enumNames":
                            enumNames = JsonSerializer.Deserialize(ref reader, McpJsonUtilities.JsonContext.Default.IListString);
                            break;

                        default:
                            break;
                    }
                }

                if (type is null)
                {
                    throw new JsonException("The 'type' property is required.");
                }

                PrimitiveSchemaDefinition? psd = null;
                switch (type)
                {
                    case "string":
                        if (enumValues is not null)
                        {
                            psd = new EnumSchema
                            {
                                Enum = enumValues,
                                EnumNames = enumNames
                            };
                        }
                        else
                        {
                            psd = new StringSchema
                            {
                                MinLength = minLength,
                                MaxLength = maxLength,
                                Format = format,
                            };
                        }
                        break;

                    case "integer":
                    case "number":
                        psd = new NumberSchema
                        {
                            Minimum = minimum,
                            Maximum = maximum,
                        };
                        break;

                    case "boolean":
                        psd = new BooleanSchema
                        {
                            Default = defaultBool,
                        };
                        break;
                }

                if (psd is not null)
                {
                    psd.Type = type;
                    psd.Title = title;
                    psd.Description = description;
                }

                return psd;
            }

            /// <inheritdoc/>
            public override void Write(Utf8JsonWriter writer, PrimitiveSchemaDefinition value, JsonSerializerOptions options)
            {
                if (value is null)
                {
                    writer.WriteNullValue();
                    return;
                }

                writer.WriteStartObject();

                writer.WriteString("type", value.Type);
                if (value.Title is not null)
                {
                    writer.WriteString("title", value.Title);
                }
                if (value.Description is not null)
                {
                    writer.WriteString("description", value.Description);
                }

                switch (value)
                {
                    case StringSchema stringSchema:
                        if (stringSchema.MinLength.HasValue)
                        {
                            writer.WriteNumber("minLength", stringSchema.MinLength.Value);
                        }
                        if (stringSchema.MaxLength.HasValue)
                        {
                            writer.WriteNumber("maxLength", stringSchema.MaxLength.Value);
                        }
                        if (stringSchema.Format is not null)
                        {
                            writer.WriteString("format", stringSchema.Format);
                        }
                        break;

                    case NumberSchema numberSchema:
                        if (numberSchema.Minimum.HasValue)
                        {
                            writer.WriteNumber("minimum", numberSchema.Minimum.Value);
                        }
                        if (numberSchema.Maximum.HasValue)
                        {
                            writer.WriteNumber("maximum", numberSchema.Maximum.Value);
                        }
                        break;

                    case BooleanSchema booleanSchema:
                        if (booleanSchema.Default.HasValue)
                        {
                            writer.WriteBoolean("default", booleanSchema.Default.Value);
                        }
                        break;

                    case EnumSchema enumSchema:
                        if (enumSchema.Enum is not null)
                        {
                            writer.WritePropertyName("enum");
                            JsonSerializer.Serialize(writer, enumSchema.Enum, McpJsonUtilities.JsonContext.Default.IListString);
                        }
                        if (enumSchema.EnumNames is not null)
                        {
                            writer.WritePropertyName("enumNames");
                            JsonSerializer.Serialize(writer, enumSchema.EnumNames, McpJsonUtilities.JsonContext.Default.IListString);
                        }
                        break;

                    default:
                        throw new JsonException($"Unexpected schema type: {value.GetType().Name}");
                }

                writer.WriteEndObject();
            }
        }
    }

    /// Represents a schema for a string type.
    public sealed class StringSchema : PrimitiveSchemaDefinition
    {
        /// <inheritdoc/>
        [JsonPropertyName("type")]
        public override string Type
        {
            get => "string";
            set
            {
                if (value is not "string")
                {
                    throw new ArgumentException("Type must be 'string'.", nameof(value));
                }
            }
        }

        /// Gets or sets the minimum length for the string.
        [JsonPropertyName("minLength")]
        public int? MinLength
        {
            get => field;
            set
            {
                if (value < 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(value), "Minimum length cannot be negative.");
                }

                field = value;
            }
        }

        /// Gets or sets the maximum length for the string.
        [JsonPropertyName("maxLength")]
        public int? MaxLength
        {
            get => field;
            set
            {
                if (value < 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(value), "Maximum length cannot be negative.");
                }

                field = value;
            }
        }

        /// Gets or sets a specific format for the string ("email", "uri", "date", or "date-time").
        [JsonPropertyName("format")]
        public string? Format
        {
            get => field;
            set
            {
                if (value is not (null or "email" or "uri" or "date" or "date-time"))
                {
                    throw new ArgumentException("Format must be 'email', 'uri', 'date', or 'date-time'.", nameof(value));
                }

                field = value;
            }
        }
    }

    /// Represents a schema for a number or integer type.
    public sealed class NumberSchema : PrimitiveSchemaDefinition
    {
        /// <inheritdoc/>
        [field: MaybeNull]
        public override string Type
        {
            get => field ??= "number";
            set
            {
                if (value is not ("number" or "integer"))
                {
                    throw new ArgumentException("Type must be 'number' or 'integer'.", nameof(value));
                }

                field = value;
            }
        }

        /// Gets or sets the minimum allowed value.
        [JsonPropertyName("minimum")]
        public double? Minimum { get; set; }

        /// Gets or sets the maximum allowed value.
        [JsonPropertyName("maximum")]
        public double? Maximum { get; set; }
    }

    /// Represents a schema for a Boolean type.
    public sealed class BooleanSchema : PrimitiveSchemaDefinition
    {
        /// <inheritdoc/>
        [JsonPropertyName("type")]
        public override string Type
        {
            get => "boolean";
            set
            {
                if (value is not "boolean")
                {
                    throw new ArgumentException("Type must be 'boolean'.", nameof(value));
                }
            }
        }

        /// Gets or sets the default value for the Boolean.
        [JsonPropertyName("default")]
        public bool? Default { get; set; }
    }

    /// Represents a schema for an enum type.
    public sealed class EnumSchema : PrimitiveSchemaDefinition
    {
        /// <inheritdoc/>
        [JsonPropertyName("type")]
        public override string Type
        {
            get => "string";
            set
            {
                if (value is not "string")
                {
                    throw new ArgumentException("Type must be 'string'.", nameof(value));
                }
            }
        }

        /// Gets or sets the list of allowed string values for the enum.
        [JsonPropertyName("enum")]
        [field: MaybeNull]
        public IList<string> Enum
        {
            get => field ??= [];
            set
            {
                Throw.IfNull(value);
                field = value;
            }
        }

        /// Gets or sets optional display names corresponding to the enum values.
        [JsonPropertyName("enumNames")]
        public IList<string>? EnumNames { get; set; }
    }
}

using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol;
#if !NET
using System.Runtime.InteropServices;
#endif
using System.Text.Json;

namespace ModelContextProtocol;

///
/// Provides extension methods for converting between Model Context Protocol (MCP) types and Microsoft.Extensions.AI types.
///
/// <remarks>
/// This class serves as an adapter layer between Model Context Protocol (MCP) types and the <see cref="AIContent"/> model types
/// from the Microsoft.Extensions.AI namespace.
/// </remarks>
public static class AIContentExtensions
{
    ///
    /// Converts a <see cref="PromptMessage"/> to a <see cref="ChatMessage"/> object.
    ///
    /// <param name="promptMessage">The prompt message to convert.</param>
    /// <returns>A <see cref="ChatMessage"/> object created from the prompt message.</returns>
    /// <remarks>
    /// This method transforms a protocol-specific <see cref="PromptMessage"/> from the Model Context Protocol
    /// into a standard <see cref="ChatMessage"/> object that can be used with AI client libraries.
    /// </remarks>
    public static ChatMessage ToChatMessage(this PromptMessage promptMessage)
    {
        Throw.IfNull(promptMessage);

        AIContent? content = ToAIContent(promptMessage.Content);

        return new()
        {
            RawRepresentation = promptMessage,
            Role = promptMessage.Role == Role.User ? ChatRole.User : ChatRole.Assistant,
            Contents = content is not null ? [content] : [],
        };
    }

    ///
    /// Converts a <see cref="CallToolResult"/> to a <see cref="ChatMessage"/> object.
    ///
    /// <param name="result">The tool result to convert.</param>
    /// <param name="callId">The identifier for the function call request that triggered the tool invocation.</param>
    /// <returns>A <see cref="ChatMessage"/> object created from the tool result.</returns>
    /// <remarks>
    /// This method transforms a protocol-specific <see cref="CallToolResult"/> from the Model Context Protocol
    /// into a standard <see cref="ChatMessage"/> object that can be used with AI client libraries. It produces a
    /// <see cref="ChatRole.Tool"/> message containing a <see cref="FunctionResultContent"/> with result as a
    /// serialized <see cref="JsonElement"/>.
    /// </remarks>
    public static ChatMessage ToChatMessage(this CallToolResult result, string callId)
    {
        Throw.IfNull(result);
        Throw.IfNull(callId);

        return new(ChatRole.Tool, [new FunctionResultContent(callId, JsonSerializer.SerializeToElement(result, McpJsonUtilities.JsonContext.Default.CallToolResult))
        {
             RawRepresentation = result,
        }]);
    }

    ///
    /// Converts a <see cref="GetPromptResult"/> to a list of <see cref="ChatMessage"/> objects.
    ///
    /// <param name="promptResult">The prompt result containing messages to convert.</param>
    /// <returns>A list of <see cref="ChatMessage"/> objects created from the prompt messages.</returns>
    /// <remarks>
    /// This method transforms protocol-specific <see cref="PromptMessage"/> objects from a Model Context Protocol
    /// prompt result into standard <see cref="ChatMessage"/> objects that can be used with AI client libraries.
    /// </remarks>
    public static IList<ChatMessage> ToChatMessages(this GetPromptResult promptResult)
    {
        Throw.IfNull(promptResult);

        return promptResult.Messages.Select(m => m.ToChatMessage()).ToList();
    }

    ///
    /// Converts a <see cref="ChatMessage"/> to a list of <see cref="PromptMessage"/> objects.
    ///
    /// <param name="chatMessage">The chat message to convert.</param>
    /// <returns>A list of <see cref="PromptMessage"/> objects created from the chat message's contents.</returns>
    /// <remarks>
    /// This method transforms standard <see cref="ChatMessage"/> objects used with AI client libraries into
    /// protocol-specific <see cref="PromptMessage"/> objects for the Model Context Protocol system.
    /// Only representable content items are processed.
    /// </remarks>
    public static IList<PromptMessage> ToPromptMessages(this ChatMessage chatMessage)
    {
        Throw.IfNull(chatMessage);

        Role r = chatMessage.Role == ChatRole.User ? Role.User : Role.Assistant;

        List<PromptMessage> messages = [];
        foreach (var content in chatMessage.Contents)
        {
            if (content is TextContent or DataContent)
            {
                messages.Add(new PromptMessage { Role = r, Content = content.ToContent() });
            }
        }

        return messages;
    }

    /// Creates a new <see cref="AIContent"/> from the content of a <see cref="ContentBlock"/>.
    /// <param name="content">The <see cref="ContentBlock"/> to convert.</param>
    /// <returns>
    /// The created <see cref="AIContent"/>. If the content can't be converted (such as when it's a resource link), <see langword="null"/> is returned.
    /// </returns>
    /// <remarks>
    /// This method converts Model Context Protocol content types to the equivalent Microsoft.Extensions.AI
    /// content types, enabling seamless integration between the protocol and AI client libraries.
    /// </remarks>
    public static AIContent? ToAIContent(this ContentBlock content)
    {
        Throw.IfNull(content);

        AIContent? ac = content switch
        {
            TextContentBlock textContent => new TextContent(textContent.Text),
            ImageContentBlock imageContent => new DataContent(Convert.FromBase64String(imageContent.Data), imageContent.MimeType),
            AudioContentBlock audioContent => new DataContent(Convert.FromBase64String(audioContent.Data), audioContent.MimeType),
            EmbeddedResourceBlock resourceContent => resourceContent.Resource.ToAIContent(),
            _ => null,
        };

        if (ac is not null)
        {
            ac.RawRepresentation = content;
        }

        return ac;
    }

    /// Creates a new <see cref="AIContent"/> from the content of a <see cref="ResourceContents"/>.
    /// <param name="content">The <see cref="ResourceContents"/> to convert.</param>
    /// <returns>The created <see cref="AIContent"/>.</returns>
    /// <remarks>
    /// This method converts Model Context Protocol resource types to the equivalent Microsoft.Extensions.AI
    /// content types, enabling seamless integration between the protocol and AI client libraries.
    /// </remarks>
    public static AIContent ToAIContent(this ResourceContents content)
    {
        Throw.IfNull(content);

        AIContent ac = content switch
        {
            BlobResourceContents blobResource => new DataContent(Convert.FromBase64String(blobResource.Blob), blobResource.MimeType ?? "application/octet-stream"),
            TextResourceContents textResource => new TextContent(textResource.Text),
            _ => throw new NotSupportedException($"Resource type '{content.GetType().Name}' is not supported.")
        };

        (ac.AdditionalProperties ??= [])["uri"] = content.Uri;
        ac.RawRepresentation = content;

        return ac;
    }

    /// Creates a list of <see cref="AIContent"/> from a sequence of <see cref="ContentBlock"/>.
    /// <param name="contents">The <see cref="ContentBlock"/> instances to convert.</param>
    /// <returns>The created <see cref="AIContent"/> instances.</returns>
    /// <remarks>
    /// <para>
    /// This method converts a collection of Model Context Protocol content objects into a collection of
    /// Microsoft.Extensions.AI content objects. It's useful when working with multiple content items, such as
    /// when processing the contents of a message or response.
    /// </para>
    /// <para>
    /// Each <see cref="ContentBlock"/> object is converted using <see cref="ToAIContent(ContentBlock)"/>,
    /// preserving the type-specific conversion logic for text, images, audio, and resources.
    /// </para>
    /// </remarks>
    public static IList<AIContent> ToAIContents(this IEnumerable<ContentBlock> contents)
    {
        Throw.IfNull(contents);

        return [.. contents.Select(ToAIContent).OfType<AIContent>()];
    }

    /// Creates a list of <see cref="AIContent"/> from a sequence of <see cref="ResourceContents"/>.
    /// <param name="contents">The <see cref="ResourceContents"/> instances to convert.</param>
    /// <returns>A list of <see cref="AIContent"/> objects created from the resource contents.</returns>
    /// <remarks>
    /// <para>
    /// This method converts a collection of Model Context Protocol resource objects into a collection of
    /// Microsoft.Extensions.AI content objects. It's useful when working with multiple resources, such as
    /// when processing the contents of a <see cref="ReadResourceResult"/>.
    /// </para>
    /// <para>
    /// Each <see cref="ResourceContents"/> object is converted using <see cref="ToAIContent(ResourceContents)"/>,
    /// preserving the type-specific conversion logic: text resources become <see cref="TextContentBlock"/> objects and
    /// binary resources become <see cref="DataContent"/> objects.
    /// </para>
    /// </remarks>
    public static IList<AIContent> ToAIContents(this IEnumerable<ResourceContents> contents)
    {
        Throw.IfNull(contents);

        return [.. contents.Select(ToAIContent)];
    }

    internal static ContentBlock ToContent(this AIContent content) =>
        content switch
        {
            TextContent textContent => new TextContentBlock
            {
                Text = textContent.Text,
            },

            DataContent dataContent when dataContent.HasTopLevelMediaType("image") => new ImageContentBlock
            {
                Data = dataContent.Base64Data.ToString(),
                MimeType = dataContent.MediaType,
            },

            DataContent dataContent when dataContent.HasTopLevelMediaType("audio") => new AudioContentBlock
            {
                Data = dataContent.Base64Data.ToString(),
                MimeType = dataContent.MediaType,
            },

            DataContent dataContent => new EmbeddedResourceBlock
            {
                Resource = new BlobResourceContents
                {
                    Blob = dataContent.Base64Data.ToString(),
                    MimeType = dataContent.MediaType,
                }
            },

            _ => new TextContentBlock
            {
                Text = JsonSerializer.Serialize(content, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object))),
            }
        };
}

using Microsoft.Extensions.AI;
using System.ComponentModel;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

///
/// Represents content within the Model Context Protocol (MCP).
///
/// <remarks>
/// <para>
/// The <see cref="ContentBlock"/> class is a fundamental type in the MCP that can represent different forms of content
/// based on the <see cref="Type"/> property. Derived types like <see cref="TextContentBlock"/>, <see cref="ImageContentBlock"/>,
/// and <see cref="EmbeddedResourceBlock"/> provide the type-specific content.
/// </para>
/// <para>
/// This class is used throughout the MCP for representing content in messages, tool responses,
/// and other communication between clients and servers.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for more details.
/// </para>
/// </remarks>
[JsonConverter(typeof(Converter))] // TODO: This converter exists due to the lack of downlevel support for AllowOutOfOrderMetadataProperties.
public abstract class ContentBlock
{
    /// Prevent external derivations.
    private protected ContentBlock()
    {
    }

    ///
    /// Gets or sets the type of content.
    ///
    /// <remarks>
    /// This determines the structure of the content object. Valid values include "image", "audio", "text", "resource", and "resource_link".
    /// </remarks>
    [JsonPropertyName("type")]
    public string Type { get; set; } = string.Empty;

    ///
    /// Gets or sets optional annotations for the content.
    ///
    /// <remarks>
    /// These annotations can be used to specify the intended audience (<see cref="Role.User"/>, <see cref="Role.Assistant"/>, or both)
    /// and the priority level of the content. Clients can use this information to filter or prioritize content for different roles.
    /// </remarks>
    [JsonPropertyName("annotations")]
    public Annotations? Annotations { get; init; }

    ///
    /// Provides a <see cref="JsonConverter"/> for <see cref="ContentBlock"/>.
    ///
    [EditorBrowsable(EditorBrowsableState.Never)]
    public class Converter : JsonConverter<ContentBlock>
    {
        /// <inheritdoc/>
        public override ContentBlock? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.Null)
            {
                return null;
            }

            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            }

            string? type = null;
            string? text = null;
            string? name = null;
            string? data = null;
            string? mimeType = null;
            string? uri = null;
            string? description = null;
            long? size = null;
            ResourceContents? resource = null;
            Annotations? annotations = null;
            JsonObject? meta = null;

            while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
            {
                if (reader.TokenType != JsonTokenType.PropertyName)
                {
                    continue;
                }

                string? propertyName = reader.GetString();
                bool success = reader.Read();
                Debug.Assert(success, "STJ must have buffered the entire object for us.");

                switch (propertyName)
                {
                    case "type":
                        type = reader.GetString();
                        break;

                    case "text":
                        text = reader.GetString();
                        break;

                    case "name":
                        name = reader.GetString();
                        break;

                    case "data":
                        data = reader.GetString();
                        break;

                    case "mimeType":
                        mimeType = reader.GetString();
                        break;

                    case "uri":
                        uri = reader.GetString();
                        break;

                    case "description":
                        description = reader.GetString();
                        break;

                    case "size":
                        size = reader.GetInt64();
                        break;

                    case "resource":
                        resource = JsonSerializer.Deserialize(ref reader, McpJsonUtilities.JsonContext.Default.ResourceContents);
                        break;

                    case "annotations":
                        annotations = JsonSerializer.Deserialize(ref reader, McpJsonUtilities.JsonContext.Default.Annotations);
                        break;

                    case "_meta":
                        meta = JsonSerializer.Deserialize(ref reader, McpJsonUtilities.JsonContext.Default.JsonObject);
                        break;

                    default:
                        break;
                }
            }

            return type switch
            {
                "text" => new TextContentBlock
                {
                    Text = text ?? throw new JsonException("Text contents must be provided for 'text' type."),
                    Annotations = annotations,
                    Meta = meta,
                },

                "image" => new ImageContentBlock
                {
                    Data = data ?? throw new JsonException("Image data must be provided for 'image' type."),
                    MimeType = mimeType ?? throw new JsonException("MIME type must be provided for 'image' type."),
                    Annotations = annotations,
                    Meta = meta,
                },

                "audio" => new AudioContentBlock
                {
                    Data = data ?? throw new JsonException("Audio data must be provided for 'audio' type."),
                    MimeType = mimeType ?? throw new JsonException("MIME type must be provided for 'audio' type."),
                    Annotations = annotations,
                    Meta = meta,
                },

                "resource" => new EmbeddedResourceBlock
                {
                    Resource = resource ?? throw new JsonException("Resource contents must be provided for 'resource' type."),
                    Annotations = annotations,
                    Meta = meta,
                },

                "resource_link" => new ResourceLinkBlock
                {
                    Uri = uri ?? throw new JsonException("URI must be provided for 'resource_link' type."),
                    Name = name ?? throw new JsonException("Name must be provided for 'resource_link' type."),
                    Description = description,
                    MimeType = mimeType,
                    Size = size,
                    Annotations = annotations,
                },

                _ => throw new JsonException($"Unknown content type: '{type}'"),
            };
        }

        /// <inheritdoc/>
        public override void Write(Utf8JsonWriter writer, ContentBlock value, JsonSerializerOptions options)
        {
            if (value is null)
            {
                writer.WriteNullValue();
                return;
            }

            writer.WriteStartObject();

            writer.WriteString("type", value.Type);

            switch (value)
            {
                case TextContentBlock textContent:
                    writer.WriteString("text", textContent.Text);
                    if (textContent.Meta is not null)
                    {
                        writer.WritePropertyName("_meta");
                        JsonSerializer.Serialize(writer, textContent.Meta, McpJsonUtilities.JsonContext.Default.JsonObject);
                    }
                    break;

                case ImageContentBlock imageContent:
                    writer.WriteString("data", imageContent.Data);
                    writer.WriteString("mimeType", imageContent.MimeType);
                    if (imageContent.Meta is not null)
                    {
                        writer.WritePropertyName("_meta");
                        JsonSerializer.Serialize(writer, imageContent.Meta, McpJsonUtilities.JsonContext.Default.JsonObject);
                    }
                    break;

                case AudioContentBlock audioContent:
                    writer.WriteString("data", audioContent.Data);
                    writer.WriteString("mimeType", audioContent.MimeType);
                    if (audioContent.Meta is not null)
                    {
                        writer.WritePropertyName("_meta");
                        JsonSerializer.Serialize(writer, audioContent.Meta, McpJsonUtilities.JsonContext.Default.JsonObject);
                    }
                    break;

                case EmbeddedResourceBlock embeddedResource:
                    writer.WritePropertyName("resource");
                    JsonSerializer.Serialize(writer, embeddedResource.Resource, McpJsonUtilities.JsonContext.Default.ResourceContents);
                    if (embeddedResource.Meta is not null)
                    {
                        writer.WritePropertyName("_meta");
                        JsonSerializer.Serialize(writer, embeddedResource.Meta, McpJsonUtilities.JsonContext.Default.JsonObject);
                    }
                    break;

                case ResourceLinkBlock resourceLink:
                    writer.WriteString("uri", resourceLink.Uri);
                    writer.WriteString("name", resourceLink.Name);
                    if (resourceLink.Description is not null)
                    {
                        writer.WriteString("description", resourceLink.Description);
                    }
                    if (resourceLink.MimeType is not null)
                    {
                        writer.WriteString("mimeType", resourceLink.MimeType);
                    }
                    if (resourceLink.Size.HasValue)
                    {
                        writer.WriteNumber("size", resourceLink.Size.Value);
                    }
                    break;
            }

            if (value.Annotations is { } annotations)
            {
                writer.WritePropertyName("annotations");
                JsonSerializer.Serialize(writer, annotations, McpJsonUtilities.JsonContext.Default.Annotations);
            }

            writer.WriteEndObject();
        }
    }
}

/// Represents text provided to or from an LLM.
public sealed class TextContentBlock : ContentBlock
{
    /// Initializes the instance of the <see cref="TextContentBlock"/> class.
    public TextContentBlock() => Type = "text";

    ///
    /// Gets or sets the text content of the message.
    ///
    [JsonPropertyName("text")]
    public required string Text { get; set; }

    ///
    /// Gets or sets metadata reserved by MCP for protocol-level metadata.
    ///
    /// <remarks>
    /// Implementations must not make assumptions about its contents.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonObject? Meta { get; set; }
}

/// Represents an image provided to or from an LLM.
public sealed class ImageContentBlock : ContentBlock
{
    /// Initializes the instance of the <see cref="ImageContentBlock"/> class.
    public ImageContentBlock() => Type = "image";

    ///
    /// Gets or sets the base64-encoded image data.
    ///
    [JsonPropertyName("data")]
    public required string Data { get; set; }

    ///
    /// Gets or sets the MIME type (or "media type") of the content, specifying the format of the data.
    ///
    /// <remarks>
    /// <para>
    /// Common values include "image/png" and "image/jpeg".
    /// </para>
    /// </remarks>
    [JsonPropertyName("mimeType")]
    public required string MimeType { get; set; }

    ///
    /// Gets or sets metadata reserved by MCP for protocol-level metadata.
    ///
    /// <remarks>
    /// Implementations must not make assumptions about its contents.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonObject? Meta { get; set; }
}

/// Represents audio provided to or from an LLM.
public sealed class AudioContentBlock : ContentBlock
{
    /// Initializes the instance of the <see cref="AudioContentBlock"/> class.
    public AudioContentBlock() => Type = "audio";

    ///
    /// Gets or sets the base64-encoded audio data.
    ///
    [JsonPropertyName("data")]
    public required string Data { get; set; }

    ///
    /// Gets or sets the MIME type (or "media type") of the content, specifying the format of the data.
    ///
    /// <remarks>
    /// <para>
    /// Common values include "audio/wav" and "audio/mp3".
    /// </para>
    /// </remarks>
    [JsonPropertyName("mimeType")]
    public required string MimeType { get; set; }

    ///
    /// Gets or sets metadata reserved by MCP for protocol-level metadata.
    ///
    /// <remarks>
    /// Implementations must not make assumptions about its contents.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonObject? Meta { get; set; }
}

/// Represents the contents of a resource, embedded into a prompt or tool call result.
/// <remarks>
/// It is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.
/// </remarks>
public sealed class EmbeddedResourceBlock : ContentBlock
{
    /// Initializes the instance of the <see cref="ResourceLinkBlock"/> class.
    public EmbeddedResourceBlock() => Type = "resource";

    ///
    /// Gets or sets the resource content of the message when <see cref="Type"/> is "resource".
    ///
    /// <remarks>
    /// <para>
    /// Resources can be either text-based (<see cref="TextResourceContents"/>) or
    /// binary (<see cref="BlobResourceContents"/>), allowing for flexible data representation.
    /// Each resource has a URI that can be used for identification and retrieval.
    /// </para>
    /// </remarks>
    [JsonPropertyName("resource")]
    public required ResourceContents Resource { get; set; }

    ///
    /// Gets or sets metadata reserved by MCP for protocol-level metadata.
    ///
    /// <remarks>
    /// Implementations must not make assumptions about its contents.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonObject? Meta { get; set; }
}

/// Represents a resource that the server is capable of reading, included in a prompt or tool call result.
/// <remarks>
/// Resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.
/// </remarks>
public sealed class ResourceLinkBlock : ContentBlock
{
    /// Initializes the instance of the <see cref="ResourceLinkBlock"/> class.
    public ResourceLinkBlock() => Type = "resource_link";

    ///
    /// Gets or sets the URI of this resource.
    ///
    [JsonPropertyName("uri")]
    public required string Uri { get; init; }

    ///
    /// Gets or sets a human-readable name for this resource.
    ///
    [JsonPropertyName("name")]
    public required string Name { get; init; }

    ///
    /// Gets or sets a description of what this resource represents.
    ///
    /// <remarks>
    /// <para>
    /// This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.
    /// </para>
    /// <para>
    /// The description should provide clear context about the resource's content, format, and purpose.
    /// This helps AI models make better decisions about when to access or reference the resource.
    /// </para>
    /// <para>
    /// Client applications can also use this description for display purposes in user interfaces
    /// or to help users understand the available resources.
    /// </para>
    /// </remarks>
    [JsonPropertyName("description")]
    public string? Description { get; init; }

    ///
    /// Gets or sets the MIME type of this resource.
    ///
    /// <remarks>
    /// <para>
    /// <see cref="MimeType"/> specifies the format of the resource content, helping clients to properly interpret and display the data.
    /// Common MIME types include "text/plain" for plain text, "application/pdf" for PDF documents,
    /// "image/png" for PNG images, and "application/json" for JSON data.
    /// </para>
    /// <para>
    /// This property may be <see langword="null"/> if the MIME type is unknown or not applicable for the resource.
    /// </para>
    /// </remarks>
    [JsonPropertyName("mimeType")]
    public string? MimeType { get; init; }

    ///
    /// Gets or sets the size of the raw resource content (before base64 encoding), in bytes, if known.
    ///
    /// <remarks>
    /// This can be used by applications to display file sizes and estimate context window usage.
    /// </remarks>
    [JsonPropertyName("size")]
    public long? Size { get; init; }
}

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Collections.Concurrent;
using System.Diagnostics;

namespace ModelContextProtocol.AspNetCore;

internal sealed class SseHandler(
    IOptions<McpServerOptions> mcpServerOptionsSnapshot,
    IOptionsFactory<McpServerOptions> mcpServerOptionsFactory,
    IOptions<HttpServerTransportOptions> httpMcpServerOptions,
    IHostApplicationLifetime hostApplicationLifetime,
    ILoggerFactory loggerFactory)
{
    private readonly ConcurrentDictionary<string, HttpMcpSession<SseResponseStreamTransport>> _sessions = new(StringComparer.Ordinal);

    public async Task HandleSseRequestAsync(HttpContext context)
    {
        var sessionId = StreamableHttpHandler.MakeNewSessionId();

        // If the server is shutting down, we need to cancel all SSE connections immediately without waiting for HostOptions.ShutdownTimeout
        // which defaults to 30 seconds.
        using var sseCts = CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted, hostApplicationLifetime.ApplicationStopping);
        var cancellationToken = sseCts.Token;

        StreamableHttpHandler.InitializeSseResponse(context);

        var requestPath = (context.Request.PathBase + context.Request.Path).ToString();
        var endpointPattern = requestPath[..(requestPath.LastIndexOf('/') + 1)];
        await using var transport = new SseResponseStreamTransport(context.Response.Body, $"{endpointPattern}message?sessionId={sessionId}", sessionId);

        var userIdClaim = StreamableHttpHandler.GetUserIdClaim(context.User);
        await using var httpMcpSession = new HttpMcpSession<SseResponseStreamTransport>(sessionId, transport, userIdClaim, httpMcpServerOptions.Value.TimeProvider);

        if (!_sessions.TryAdd(sessionId, httpMcpSession))
        {
            throw new UnreachableException($"Unreachable given good entropy! Session with ID '{sessionId}' has already been created.");
        }

        try
        {
            var mcpServerOptions = mcpServerOptionsSnapshot.Value;
            if (httpMcpServerOptions.Value.ConfigureSessionOptions is { } configureSessionOptions)
            {
                mcpServerOptions = mcpServerOptionsFactory.Create(Options.DefaultName);
                await configureSessionOptions(context, mcpServerOptions, cancellationToken);
            }

            var transportTask = transport.RunAsync(cancellationToken);

            try
            {
                await using var mcpServer = McpServerFactory.Create(transport, mcpServerOptions, loggerFactory, context.RequestServices);
                httpMcpSession.Server = mcpServer;
                context.Features.Set(mcpServer);

                var runSessionAsync = httpMcpServerOptions.Value.RunSessionHandler ?? StreamableHttpHandler.RunSessionAsync;
                httpMcpSession.ServerRunTask = runSessionAsync(context, mcpServer, cancellationToken);
                await httpMcpSession.ServerRunTask;
            }
            finally
            {
                await transport.DisposeAsync();
                await transportTask;
            }
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            // RequestAborted always triggers when the client disconnects before a complete response body is written,
            // but this is how SSE connections are typically closed.
        }
        finally
        {
            _sessions.TryRemove(sessionId, out _);
        }
    }

    public async Task HandleMessageRequestAsync(HttpContext context)
    {
        if (!context.Request.Query.TryGetValue("sessionId", out var sessionId))
        {
            await Results.BadRequest("Missing sessionId query parameter.").ExecuteAsync(context);
            return;
        }

        if (!_sessions.TryGetValue(sessionId.ToString(), out var httpMcpSession))
        {
            await Results.BadRequest($"Session ID not found.").ExecuteAsync(context);
            return;
        }

        if (!httpMcpSession.HasSameUserId(context.User))
        {
            await Results.Forbid().ExecuteAsync(context);
            return;
        }

        var message = (JsonRpcMessage?)await context.Request.ReadFromJsonAsync(McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonRpcMessage)), context.RequestAborted);
        if (message is null)
        {
            await Results.BadRequest("No message in request body.").ExecuteAsync(context);
            return;
        }

        await httpMcpSession.Transport.OnMessageReceivedAsync(message, context.RequestAborted);
        context.Response.StatusCode = StatusCodes.Status202Accepted;
        await context.Response.WriteAsync("Accepted");
    }
}

using ModelContextProtocol.AspNetCore.Stateless;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Security.Claims;

namespace ModelContextProtocol.AspNetCore;

internal sealed class HttpMcpSession<TTransport>(
    string sessionId,
    TTransport transport,
    UserIdClaim? userId,
    TimeProvider timeProvider) : IAsyncDisposable
    where TTransport : ITransport
{
    private int _referenceCount;
    private int _getRequestStarted;
    private CancellationTokenSource _disposeCts = new();

    public string Id { get; } = sessionId;
    public TTransport Transport { get; } = transport;
    public UserIdClaim? UserIdClaim { get; } = userId;

    public CancellationToken SessionClosed => _disposeCts.Token;

    public bool IsActive => !SessionClosed.IsCancellationRequested && _referenceCount > 0;
    public long LastActivityTicks { get; private set; } = timeProvider.GetTimestamp();

    private TimeProvider TimeProvider => timeProvider;

    public IMcpServer? Server { get; set; }
    public Task? ServerRunTask { get; set; }

    public IDisposable AcquireReference()
    {
        Interlocked.Increment(ref _referenceCount);
        return new UnreferenceDisposable(this);
    }

    public bool TryStartGetRequest() => Interlocked.Exchange(ref _getRequestStarted, 1) == 0;

    public async ValueTask DisposeAsync()
    {
        try
        {
            await _disposeCts.CancelAsync();

            if (ServerRunTask is not null)
            {
                await ServerRunTask;
            }
        }
        catch (OperationCanceledException)
        {
        }
        finally
        {
            try
            {
                if (Server is not null)
                {
                    await Server.DisposeAsync();
                }
            }
            finally
            {
                await Transport.DisposeAsync();
                _disposeCts.Dispose();
            }
        }
    }

    public bool HasSameUserId(ClaimsPrincipal user)
        => UserIdClaim == StreamableHttpHandler.GetUserIdClaim(user);

    private sealed class UnreferenceDisposable(HttpMcpSession<TTransport> session) : IDisposable
    {
        public void Dispose()
        {
            if (Interlocked.Decrement(ref session._referenceCount) == 0)
            {
                session.LastActivityTicks = session.TimeProvider.GetTimestamp();
            }
        }
    }
}

using ModelContextProtocol.Protocol;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace ModelContextProtocol;

internal static class Diagnostics
{
    internal static ActivitySource ActivitySource { get; } = new("Experimental.ModelContextProtocol");

    internal static Meter Meter { get; } = new("Experimental.ModelContextProtocol");

    internal static Histogram<double> CreateDurationHistogram(string name, string description, bool longBuckets) =>
        Meter.CreateHistogram<double>(name, "s", description
#if NET9_0_OR_GREATER
        , advice: longBuckets ? LongSecondsBucketBoundaries : ShortSecondsBucketBoundaries
#endif
        );

#if NET9_0_OR_GREATER
    /// <summary>
    /// Follows boundaries from http.server.request.duration/http.client.request.duration
    /// </summary>
    private static InstrumentAdvice<double> ShortSecondsBucketBoundaries { get; } = new()
    {
        HistogramBucketBoundaries = [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10],
    };

    /// <summary>
    /// Not based on a standard. Larger bucket sizes for longer lasting operations, e.g. HTTP connection duration.
    /// See https://github.com/open-telemetry/semantic-conventions/issues/336
    /// </summary>
    private static InstrumentAdvice<double> LongSecondsBucketBoundaries { get; } = new()
    {
        HistogramBucketBoundaries = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300],
    };
#endif

    internal static ActivityContext ExtractActivityContext(this DistributedContextPropagator propagator, JsonRpcMessage message)
    {
        propagator.ExtractTraceIdAndState(message, ExtractContext, out var traceparent, out var tracestate);
        ActivityContext.TryParse(traceparent, tracestate, true, out var activityContext);
        return activityContext;
    }

    private static void ExtractContext(object? message, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues)
    {
        fieldValues = null;
        fieldValue = null;

        JsonNode? meta = null;
        switch (message)
        {
            case JsonRpcRequest request:
                meta = request.Params?["_meta"];
                break;

            case JsonRpcNotification notification:
                meta = notification.Params?["_meta"];
                break;
        }

        if (meta?[fieldName] is JsonValue value && value.GetValueKind() == JsonValueKind.String)
        {
            fieldValue = value.GetValue<string>();
        }
    }

    internal static void InjectActivityContext(this DistributedContextPropagator propagator, Activity? activity, JsonRpcMessage message)
    {
        // noop if activity is null
        propagator.Inject(activity, message, InjectContext);
    }

    private static void InjectContext(object? message, string key, string value)
    {
        JsonNode? parameters = null;
        switch (message)
        {
            case JsonRpcRequest request:
                parameters = request.Params;
                break;

            case JsonRpcNotification notification:
                parameters = notification.Params;
                break;
        }

        // Replace any params._meta with the current value
        if (parameters is JsonObject jsonObject)
        {
            if (jsonObject["_meta"] is not JsonObject meta)
            {
                meta = new JsonObject();
                jsonObject["_meta"] = meta;
            }
            meta[key] = value;
        }
    }

    internal static bool ShouldInstrumentMessage(JsonRpcMessage message) =>
        ActivitySource.HasListeners() &&
        message switch
        {
            JsonRpcRequest => true,
            JsonRpcNotification notification => notification.Method != NotificationMethods.LoggingMessageNotification,
            _ => false
        };

    internal static ActivityLink[] ActivityLinkFromCurrent() => Activity.Current is null ? [] : [new ActivityLink(Activity.Current.Context)];
}

using System.ComponentModel;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Provides a base class representing contents of a resource in the Model Context Protocol.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="ResourceContents"/> serves as the base class for different types of resources that can be
/// exchanged through the Model Context Protocol. Resources are identified by URIs and can contain
/// different types of data.
/// </para>
/// <para>
/// This class is abstract and has two concrete implementations:
/// <list type="bullet">
///   <item><description><see cref="TextResourceContents"/> - For text-based resources</description></item>
///   <item><description><see cref="BlobResourceContents"/> - For binary data resources</description></item>
/// </list>
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for more details.
/// </para>
/// </remarks>
[JsonConverter(typeof(Converter))]
public abstract class ResourceContents
{
    /// <summary>Prevent external derivations.</summary>
    private protected ResourceContents()
    {
    }

    /// <summary>
    /// Gets or sets the URI of the resource.
    /// </summary>
    [JsonPropertyName("uri")]
    public string Uri { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the MIME type of the resource content.
    /// </summary>
    [JsonPropertyName("mimeType")]
    public string? MimeType { get; set; }

    /// <summary>
    /// Gets or sets metadata reserved by MCP for protocol-level metadata.
    /// </summary>
    /// <remarks>
    /// Implementations must not make assumptions about its contents.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonObject? Meta { get; set; }

    /// <summary>
    /// Provides a <see cref="JsonConverter"/> for <see cref="ResourceContents"/>.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public class Converter : JsonConverter<ResourceContents>
    {
        /// <inheritdoc/>
        public override ResourceContents? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.Null)
            {
                return null;
            }

            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            }

            string? uri = null;
            string? mimeType = null;
            string? blob = null;
            string? text = null;
            JsonObject? meta = null;

            while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
            {
                if (reader.TokenType != JsonTokenType.PropertyName)
                {
                    continue;
                }

                string? propertyName = reader.GetString();
                bool success = reader.Read();
                Debug.Assert(success, "STJ must have buffered the entire object for us.");

                switch (propertyName)
                {
                    case "uri":
                        uri = reader.GetString();
                        break;

                    case "mimeType":
                        mimeType = reader.GetString();
                        break;

                    case "blob":
                        blob = reader.GetString();
                        break;

                    case "text":
                        text = reader.GetString();
                        break;

                    case "_meta":
                        meta = JsonSerializer.Deserialize(ref reader, McpJsonUtilities.JsonContext.Default.JsonObject);
                        break;

                    default:
                        break;
                }
            }

            if (blob is not null)
            {
                return new BlobResourceContents
                {
                    Uri = uri ?? string.Empty,
                    MimeType = mimeType,
                    Blob = blob,
                    Meta = meta,
                };
            }

            if (text is not null)
            {
                return new TextResourceContents
                {
                    Uri = uri ?? string.Empty,
                    MimeType = mimeType,
                    Text = text,
                    Meta = meta,
                };
            }

            return null;
        }

        /// <inheritdoc/>
        public override void Write(Utf8JsonWriter writer, ResourceContents value, JsonSerializerOptions options)
        {
            if (value is null)
            {
                writer.WriteNullValue();
                return;
            }

            writer.WriteStartObject();
            writer.WriteString("uri", value.Uri);
            writer.WriteString("mimeType", value.MimeType);

            Debug.Assert(value is BlobResourceContents or TextResourceContents);
            if (value is BlobResourceContents blobResource)
            {
                writer.WriteString("blob", blobResource.Blob);
            }
            else if (value is TextResourceContents textResource)
            {
                writer.WriteString("text", textResource.Text);
            }

            if (value.Meta is not null)
            {
                writer.WritePropertyName("_meta");
                JsonSerializer.Serialize(writer, value.Meta, McpJsonUtilities.JsonContext.Default.JsonObject);
            }

            writer.WriteEndObject();
        }
    }
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// Copied from:
// https://github.com/dotnet/runtime/blob/d2650b6ae7023a2d9d2c74c56116f1f18472ab04/src/libraries/System.Private.CoreLib/src/System/PasteArguments.cs
// and changed from using ValueStringBuilder to StringBuilder.

#if !NET
using System.Text;

namespace System;

internal static partial class PasteArguments
{
    internal static void AppendArgument(StringBuilder stringBuilder, string argument)
    {
        if (stringBuilder.Length != 0)
        {
            stringBuilder.Append(' ');
        }

        // Parsing rules for non-argv[0] arguments:
        //   - Backslash is a normal character except followed by a quote.
        //   - 2N backslashes followed by a quote ==> N literal backslashes followed by unescaped quote
        //   - 2N+1 backslashes followed by a quote ==> N literal backslashes followed by a literal quote
        //   - Parsing stops at first whitespace outside of quoted region.
        //   - (post 2008 rule): A closing quote followed by another quote ==> literal quote, and parsing remains in quoting mode.
        if (argument.Length != 0 && ContainsNoWhitespaceOrQuotes(argument))
        {
            // Simple case - no quoting or changes needed.
            stringBuilder.Append(argument);
        }
        else
        {
            stringBuilder.Append(Quote);
            int idx = 0;
            while (idx < argument.Length)
            {
                char c = argument[idx++];
                if (c == Backslash)
                {
                    int numBackSlash = 1;
                    while (idx < argument.Length && argument[idx] == Backslash)
                    {
                        idx++;
                        numBackSlash++;
                    }

                    if (idx == argument.Length)
                    {
                        // We'll emit an end quote after this so must double the number of backslashes.
                        stringBuilder.Append(Backslash, numBackSlash * 2);
                    }
                    else if (argument[idx] == Quote)
                    {
                        // Backslashes will be followed by a quote. Must double the number of backslashes.
                        stringBuilder.Append(Backslash, numBackSlash * 2 + 1);
                        stringBuilder.Append(Quote);
                        idx++;
                    }
                    else
                    {
                        // Backslash will not be followed by a quote, so emit as normal characters.
                        stringBuilder.Append(Backslash, numBackSlash);
                    }

                    continue;
                }

                if (c == Quote)
                {
                    // Escape the quote so it appears as a literal. This also guarantees that we won't end up generating a closing quote followed
                    // by another quote (which parses differently pre-2008 vs. post-2008.)
                    stringBuilder.Append(Backslash);
                    stringBuilder.Append(Quote);
                    continue;
                }

                stringBuilder.Append(c);
            }

            stringBuilder.Append(Quote);
        }
    }

    private static bool ContainsNoWhitespaceOrQuotes(string s)
    {
        for (int i = 0; i < s.Length; i++)
        {
            char c = s[i];
            if (char.IsWhiteSpace(c) || c == Quote)
            {
                return false;
            }
        }

        return true;
    }

    private const char Quote = '\"';
    private const char Backslash = '\\';
}
#endif

using Microsoft.Extensions.Options;
using ModelContextProtocol.Server;

namespace ModelContextProtocol;

/// <summary>
/// Configures the McpServerOptions using addition services from DI.
/// </summary>
/// <param name="serverHandlers">The server handlers configuration options.</param>
/// <param name="serverTools">Tools individually registered.</param>
/// <param name="serverPrompts">Prompts individually registered.</param>
/// <param name="serverResources">Resources individually registered.</param>
internal sealed class McpServerOptionsSetup(
    IOptions<McpServerHandlers> serverHandlers,
    IEnumerable<McpServerTool> serverTools,
    IEnumerable<McpServerPrompt> serverPrompts,
    IEnumerable<McpServerResource> serverResources) : IConfigureOptions<McpServerOptions>
{
    /// <summary>
    /// Configures the given McpServerOptions instance by setting server information
    /// and applying custom server handlers and tools.
    /// </summary>
    /// <param name="options">The options instance to be configured.</param>
    public void Configure(McpServerOptions options)
    {
        Throw.IfNull(options);

        // Collect all of the provided tools into a tools collection. If the options already has
        // a collection, add to it, otherwise create a new one. We want to maintain the identity
        // of an existing collection in case someone has provided their own derived type, wants
        // change notifications, etc.
        McpServerPrimitiveCollection<McpServerTool> toolCollection = options.Capabilities?.Tools?.ToolCollection ?? [];
        foreach (var tool in serverTools)
        {
            toolCollection.TryAdd(tool);
        }

        if (!toolCollection.IsEmpty)
        {
            options.Capabilities ??= new();
            options.Capabilities.Tools ??= new();
            options.Capabilities.Tools.ToolCollection = toolCollection;
        }

        // Collect all of the provided prompts into a prompts collection. If the options already has
        // a collection, add to it, otherwise create a new one. We want to maintain the identity
        // of an existing collection in case someone has provided their own derived type, wants
        // change notifications, etc.
        McpServerPrimitiveCollection<McpServerPrompt> promptCollection = options.Capabilities?.Prompts?.PromptCollection ?? [];
        foreach (var prompt in serverPrompts)
        {
            promptCollection.TryAdd(prompt);
        }

        if (!promptCollection.IsEmpty)
        {
            options.Capabilities ??= new();
            options.Capabilities.Prompts ??= new();
            options.Capabilities.Prompts.PromptCollection = promptCollection;
        }

        // Collect all of the provided resources into a resources collection. If the options already has
        // a collection, add to it, otherwise create a new one. We want to maintain the identity
        // of an existing collection in case someone has provided their own derived type, wants
        // change notifications, etc.
        McpServerResourceCollection resourceCollection = options.Capabilities?.Resources?.ResourceCollection ?? [];
        foreach (var resource in serverResources)
        {
            resourceCollection.TryAdd(resource);
        }

        if (!resourceCollection.IsEmpty)
        {
            options.Capabilities ??= new();
            options.Capabilities.Resources ??= new();
            options.Capabilities.Resources.ResourceCollection = resourceCollection;
        }

        // Apply custom server handlers.
        serverHandlers.Value.OverwriteWithSetHandlers(options);
    }
}

using ModelContextProtocol.Server;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the capabilities that a client may support.
/// </summary>
/// <remarks>
/// <para>
/// Capabilities define the features and functionality that a client can handle when communicating with an MCP server.
/// These are advertised to the server during the initialize handshake.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class ClientCapabilities
{
    /// <summary>
    /// Gets or sets experimental, non-standard capabilities that the client supports.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The <see cref="Experimental"/> dictionary allows clients to advertise support for features that are not yet
    /// standardized in the Model Context Protocol specification. This extension mechanism enables
    /// future protocol enhancements while maintaining backward compatibility.
    /// </para>
    /// <para>
    /// Values in this dictionary are implementation-specific and should be coordinated between client
    /// and server implementations. Servers should not assume the presence of any experimental capability
    /// without checking for it first.
    /// </para>
    /// </remarks>
    [JsonPropertyName("experimental")]
    public IDictionary<string, object>? Experimental { get; set; }

    /// <summary>
    /// Gets or sets the client's roots capability, which are entry points for resource navigation.
    /// </summary>
    /// <remarks>
    /// <para>
    /// When <see cref="Roots"/> is non-<see langword="null"/>, the client indicates that it can respond to
    /// server requests for listing root URIs. Root URIs serve as entry points for resource navigation in the protocol.
    /// </para>
    /// <para>
    /// The server can use <see cref="McpServerExtensions.RequestRootsAsync"/> to request the list of
    /// available roots from the client, which will trigger the client's <see cref="RootsCapability.RootsHandler"/>.
    /// </para>
    /// </remarks>
    [JsonPropertyName("roots")]
    public RootsCapability? Roots { get; set; }

    /// <summary>
    /// Gets or sets the client's sampling capability, which indicates whether the client
    /// supports issuing requests to an LLM on behalf of the server.
    /// </summary>
    [JsonPropertyName("sampling")]
    public SamplingCapability? Sampling { get; set; }

    /// <summary>
    /// Gets or sets the client's elicitation capability, which indicates whether the client
    /// supports elicitation of additional information from the user on behalf of the server.
    /// </summary>
    [JsonPropertyName("elicitation")]
    public ElicitationCapability? Elicitation { get; set; }

    /// <summary>Gets or sets notification handlers to register with the client.</summary>
    /// <remarks>
    /// <para>
    /// When constructed, the client will enumerate these handlers once, which may contain multiple handlers per notification method key.
    /// The client will not re-enumerate the sequence after initialization.
    /// </para>
    /// <para>
    /// Notification handlers allow the client to respond to server-sent notifications for specific methods.
    /// Each key in the collection is a notification method name, and each value is a callback that will be invoked
    /// when a notification with that method is received.
    /// </para>
    /// <para>
    /// Handlers provided via <see cref="NotificationHandlers"/> will be registered with the client for the lifetime of the client.
    /// For transient handlers, <see cref="IMcpEndpoint.RegisterNotificationHandler"/> may be used to register a handler that can
    /// then be unregistered by disposing of the <see cref="IAsyncDisposable"/> returned from the method.
    /// </para>
    /// </remarks>
    [JsonIgnore]
    public IEnumerable<KeyValuePair<string, Func<JsonRpcNotification, CancellationToken, ValueTask>>>? NotificationHandlers { get; set; }
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
#if !NET9_0_OR_GREATER
using System.Reflection;
#endif
using System.Text.Json;
using System.Text.Json.Serialization;
#if !NET9_0_OR_GREATER
using ModelContextProtocol;
#endif

// NOTE:
// This is a workaround for lack of System.Text.Json's JsonStringEnumConverter<T>
// 9.x support for JsonStringEnumMemberNameAttribute. Once all builds use the System.Text.Json 9.x
// version, this whole file can be removed. Note that the type is public so that external source
// generators can use it, so removing it is a potential breaking change.

namespace ModelContextProtocol
{
    /// <summary>
    /// A JSON converter for enums that allows customizing the serialized string value of enum members
    /// using the <see cref="JsonStringEnumMemberNameAttribute"/>.
    /// </summary>
    /// <typeparam name="TEnum">The enum type to convert.</typeparam>
    /// <remarks>
    /// This is a temporary workaround for lack of System.Text.Json's JsonStringEnumConverter<T>
    /// 9.x support for custom enum member naming. It will be replaced by the built-in functionality
    /// once .NET 9 is fully adopted.
    /// </remarks>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed class CustomizableJsonStringEnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum> :
        JsonStringEnumConverter<TEnum> where TEnum : struct, Enum
    {
#if !NET9_0_OR_GREATER
        /// <summary>
        /// Initializes a new instance of the <see cref="CustomizableJsonStringEnumConverter{TEnum}"/> class.
        /// </summary>
        /// <remarks>
        /// The converter automatically detects any enum members decorated with <see cref="JsonStringEnumMemberNameAttribute"/>
        /// and uses those values during serialization and deserialization.
        /// </remarks>
        public CustomizableJsonStringEnumConverter() :
            base(namingPolicy: ResolveNamingPolicy())
        {
        }

        private static JsonNamingPolicy? ResolveNamingPolicy()
        {
            var map = typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static)
                .Select(f => (f.Name, AttributeName: f.GetCustomAttribute<JsonStringEnumMemberNameAttribute>()?.Name))
                .Where(pair => pair.AttributeName != null)
                .ToDictionary(pair => pair.Name, pair => pair.AttributeName);

            return map.Count > 0 ? new EnumMemberNamingPolicy(map!) : null;
        }

        private sealed class EnumMemberNamingPolicy(Dictionary<string, string> map) : JsonNamingPolicy
        {
            public override string ConvertName(string name) =>
                map.TryGetValue(name, out string? newName) ?
                    newName :
                    name;
        }
#endif
    }

    /// <summary>
    /// A JSON converter for enums that allows customizing the serialized string value of enum members
    /// using the <see cref="JsonStringEnumMemberNameAttribute"/>.
    /// </summary>
    /// <remarks>
    /// This is a temporary workaround for lack of System.Text.Json's JsonStringEnumConverter<T>
    /// 9.x support for custom enum member naming. It will be replaced by the built-in functionality
    /// once .NET 9 is fully adopted.
    /// </remarks>
    [EditorBrowsable(EditorBrowsableState.Never)]
    [RequiresUnreferencedCode("Requires unreferenced code to instantiate the generic enum converter.")]
    [RequiresDynamicCode("Requires dynamic code to instantiate the generic enum converter.")]
    public sealed class CustomizableJsonStringEnumConverter : JsonConverterFactory
    {
        /// <inheritdoc/>
        public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum;
        /// <inheritdoc/>
        public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            Type converterType = typeof(CustomizableJsonStringEnumConverter<>).MakeGenericType(typeToConvert)!;
            var factory = (JsonConverterFactory)Activator.CreateInstance(converterType)!;
            return factory.CreateConverter(typeToConvert, options);
        }
    }
}

#if !NET9_0_OR_GREATER
namespace System.Text.Json.Serialization
{
    /// <summary>
    /// Determines the custom string value that should be used when serializing an enum member using JSON.
    /// </summary>
    /// <remarks>
    /// This attribute is a temporary workaround for lack of System.Text.Json's support for custom enum member naming
    /// in versions prior to .NET 9. It works together with <see cref="CustomizableJsonStringEnumConverter{TEnum}"/>
    /// to provide customized string representations of enum values during JSON serialization and deserialization.
    /// </remarks>
    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
    internal sealed class JsonStringEnumMemberNameAttribute : Attribute
    {
        /// <summary>
        /// Creates new attribute instance with a specified enum member name.
        /// </summary>
        /// <param name="name">The name to apply to the current enum member when serialized to JSON.</param>
        public JsonStringEnumMemberNameAttribute(string name)
        {
            Name = name;
        }

        /// <summary>
        /// Gets the custom JSON name of the enum member.
        /// </summary>
        public string Name { get; }
    }
}
#endif

using ModelContextProtocol.Protocol;
using System.Text.Json;

namespace ModelContextProtocol.Client;

///
/// Represents a named prompt that can be retrieved from an MCP server and invoked with arguments.
///
/// <remarks>
/// <para>
/// This class provides a client-side wrapper around a prompt defined on an MCP server. It allows
/// retrieving the prompt's content by sending a request to the server with optional arguments.
/// Instances of this class are typically obtained by calling <see cref="McpClientExtensions.ListPromptsAsync"/>
/// or <see cref="McpClientExtensions.EnumeratePromptsAsync"/>.
/// </para>
/// <para>
/// Each prompt has a name and optionally a description, and it can be invoked with arguments
/// to produce customized prompt content from the server.
/// </para>
/// </remarks>
public sealed class McpClientPrompt
{
    private readonly IMcpClient _client;

    internal McpClientPrompt(IMcpClient client, Prompt prompt)
    {
        _client = client;
        ProtocolPrompt = prompt;
    }

    /// Gets the underlying protocol <see cref="Prompt"/> type for this instance.
    /// <remarks>
    /// <para>
    /// This property provides direct access to the underlying protocol representation of the prompt,
    /// which can be useful for advanced scenarios or when implementing custom MCP client extensions.
    /// </para>
    /// <para>
    /// For most common use cases, you can use the more convenient <see cref="Name"/> and
    /// <see cref="Description"/> properties instead of accessing the <see cref="ProtocolPrompt"/> directly.
    /// </para>
    /// </remarks>
    public Prompt ProtocolPrompt { get; }

    /// Gets the name of the prompt.
    public string Name => ProtocolPrompt.Name;

    /// Gets the title of the prompt.
    public string? Title => ProtocolPrompt.Title;

    /// Gets a description of the prompt.
    public string? Description => ProtocolPrompt.Description;

    ///
    /// Gets this prompt's content by sending a request to the server with optional arguments.
    ///
    /// <param name="arguments">Optional arguments to pass to the prompt. Keys are parameter names, and values are the argument values.</param>
    /// <param name="serializerOptions">The serialization options governing argument serialization.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A <see cref="ValueTask"/> containing the prompt's result with content and messages.</returns>
    /// <remarks>
    /// <para>
    /// This method sends a request to the MCP server to execute this prompt with the provided arguments.
    /// The server will process the request and return a result containing messages or other content.
    /// </para>
    /// <para>
    /// This is a convenience method that internally calls <see cref="McpClientExtensions.GetPromptAsync"/>
    /// with this prompt's name and arguments.
    /// </para>
    /// </remarks>
    public async ValueTask<GetPromptResult> GetAsync(
        IEnumerable<KeyValuePair<string, object?>>? arguments = null,
        JsonSerializerOptions? serializerOptions = null,
        CancellationToken cancellationToken = default)
    {
        IReadOnlyDictionary<string, object?>? argDict =
            arguments as IReadOnlyDictionary<string, object?> ??
            arguments?.ToDictionary();

        return await _client.GetPromptAsync(ProtocolPrompt.Name, argDict, serializerOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
    }
}

using Microsoft.Extensions.Logging;

namespace ModelContextProtocol.Client;

/// <summary>
/// Provides factory methods for creating Model Context Protocol (MCP) clients.
/// </summary>
/// <remarks>
/// This factory class is the primary way to instantiate <see cref="IMcpClient"/> instances
/// that connect to MCP servers. It handles the creation and connection
/// of appropriate implementations through the supplied transport.
/// </remarks>
public static partial class McpClientFactory
{
    /// <summary>Creates an <see cref="IMcpClient"/>, connecting it to the specified server.</summary>
    /// <param name="clientTransport">The transport instance used to communicate with the server.</param>
    /// <param name="clientOptions">
    /// A client configuration object which specifies client capabilities and protocol version.
    /// If <see langword="null"/>, details based on the current process will be employed.
    /// </param>
    /// <param name="loggerFactory">A logger factory for creating loggers for clients.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>An <see cref="IMcpClient"/> that's connected to the specified server.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="clientTransport"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="clientOptions"/> is <see langword="null"/>.</exception>
    public static async Task<IMcpClient> CreateAsync(
        IClientTransport clientTransport,
        McpClientOptions? clientOptions = null,
        ILoggerFactory? loggerFactory = null,
        CancellationToken cancellationToken = default)
    {
        Throw.IfNull(clientTransport);

        McpClient client = new(clientTransport, clientOptions, loggerFactory);
        try
        {
            await client.ConnectAsync(cancellationToken).ConfigureAwait(false);
            if (loggerFactory?.CreateLogger(typeof(McpClientFactory)) is ILogger logger)
            {
                logger.LogClientCreated(client.EndpointName);
            }
        }
        catch
        {
            await client.DisposeAsync().ConfigureAwait(false);
            throw;
        }

        return client;
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} client created and connected.")]
    private static partial void LogClientCreated(this ILogger logger, string endpointName);
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the capabilities that a server may support.
/// </summary>
/// <remarks>
/// <para>
/// Server capabilities define the features and functionality available when clients connect.
/// These capabilities are advertised to clients during the initialize handshake.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class ServerCapabilities
{
    /// <summary>
    /// Gets or sets experimental, non-standard capabilities that the server supports.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The <see cref="Experimental"/> dictionary allows servers to advertise support for features that are not yet
    /// standardized in the Model Context Protocol specification. This extension mechanism enables
    /// future protocol enhancements while maintaining backward compatibility.
    /// </para>
    /// <para>
    /// Values in this dictionary are implementation-specific and should be coordinated between client
    /// and server implementations. Clients should not assume the presence of any experimental capability
    /// without checking for it first.
    /// </para>
    /// </remarks>
    [JsonPropertyName("experimental")]
    public IDictionary<string, object>? Experimental { get; set; }

    /// <summary>
    /// Gets or sets a server's logging capability, supporting sending log messages to the client.
    /// </summary>
    [JsonPropertyName("logging")]
    public LoggingCapability? Logging { get; set; }

    /// <summary>
    /// Gets or sets a server's prompts capability for serving predefined prompt templates that clients can discover and use.
    /// </summary>
    [JsonPropertyName("prompts")]
    public PromptsCapability? Prompts { get; set; }

    /// <summary>
    /// Gets or sets a server's resources capability for serving predefined resources that clients can discover and use.
    /// </summary>
    [JsonPropertyName("resources")]
    public ResourcesCapability? Resources { get; set; }

    /// <summary>
    /// Gets or sets a server's tools capability for listing tools that a client is able to invoke.
    /// </summary>
    [JsonPropertyName("tools")]
    public ToolsCapability? Tools { get; set; }

    /// <summary>
    /// Gets or sets a server's completions capability for supporting argument auto-completion suggestions.
    /// </summary>
    [JsonPropertyName("completions")]
    public CompletionsCapability? Completions { get; set; }

    /// <summary>Gets or sets notification handlers to register with the server.</summary>
    /// <remarks>
    /// <para>
    /// When constructed, the server will enumerate these handlers once, which may contain multiple handlers per notification method key.
    /// The server will not re-enumerate the sequence after initialization.
    /// </para>
    /// <para>
    /// Notification handlers allow the server to respond to client-sent notifications for specific methods.
    /// Each key in the collection is a notification method name, and each value is a callback that will be invoked
    /// when a notification with that method is received.
    /// </para>
    /// <para>
    /// Handlers provided via <see cref="NotificationHandlers"/> will be registered with the server for the lifetime of the server.
    /// For transient handlers, <see cref="IMcpEndpoint.RegisterNotificationHandler"/> may be used to register a handler that can
    /// then be unregistered by disposing of the <see cref="IAsyncDisposable"/> returned from the method.
    /// </para>
    /// </remarks>
    [JsonIgnore]
    public IEnumerable<KeyValuePair<string, Func<JsonRpcNotification, CancellationToken, ValueTask>>>? NotificationHandlers { get; set; }
}

using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using System.Reflection;
using System.Text.Json;

namespace ModelContextProtocol.Server;

///
/// Represents an invocable tool used by Model Context Protocol clients and servers.
///
/// <remarks>
/// <para>
/// <see cref="McpServerTool"/> is an abstract base class that represents an MCP tool for use in the server (as opposed
/// to <see cref="Tool"/>, which provides the protocol representation of a tool, and <see cref="McpClientTool"/>, which
/// provides a client-side representation of a tool). Instances of <see cref="McpServerTool"/> can be added into a
/// <see cref="IServiceCollection"/> to be picked up automatically when <see cref="McpServerFactory"/> is used to create
/// an <see cref="IMcpServer"/>, or added into a <see cref="McpServerPrimitiveCollection{McpServerTool}"/>.
/// </para>
/// <para>
/// Most commonly, <see cref="McpServerTool"/> instances are created using the static <see cref="M:McpServerTool.Create"/> methods.
/// These methods enable creating an <see cref="McpServerTool"/> for a method, specified via a <see cref="Delegate"/> or
/// <see cref="MethodInfo"/>, and are what are used implicitly by WithToolsFromAssembly and WithTools. The <see cref="M:McpServerTool.Create"/> methods
/// create <see cref="McpServerTool"/> instances capable of working with a large variety of .NET method signatures, automatically handling
/// how parameters are marshaled into the method from the JSON received from the MCP client, and how the return value is marshaled back
/// into the <see cref="CallToolResult"/> that's then serialized and sent back to the client.
/// </para>
/// <para>
/// By default, parameters are sourced from the <see cref="CallToolRequestParams.Arguments"/> dictionary, which is a collection
/// of key/value pairs, and are represented in the JSON schema for the function, as exposed in the returned <see cref="McpServerTool"/>'s
/// <see cref="ProtocolTool"/>'s <see cref="Tool.InputSchema"/>. Those parameters are deserialized from the
/// <see cref="JsonElement"/> values in that collection. There are a few exceptions to this:
/// <list type="bullet">
///   <item>
///     <description>
///       <see cref="CancellationToken"/> parameters are automatically bound to a <see cref="CancellationToken"/> provided by the
///       <see cref="IMcpServer"/> and that respects any <see cref="CancelledNotificationParams"/>s sent by the client for this operation's
///       <see cref="RequestId"/>. The parameter is not included in the generated JSON schema.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IServiceProvider"/> parameters are bound from the <see cref="RequestContext{CallToolRequestParams}"/> for this request,
///       and are not included in the JSON schema.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IMcpServer"/> parameters are not included in the JSON schema and are bound directly to the <see cref="IMcpServer"/>
///       instance associated with this request's <see cref="RequestContext{CallToolRequestParams}"/>. Such parameters may be used to understand
///       what server is being used to process the request, and to interact with the client issuing the request to that server.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IProgress{ProgressNotificationValue}"/> parameters accepting <see cref="ProgressNotificationValue"/> values
///       are not included in the JSON schema and are bound to an <see cref="IProgress{ProgressNotificationValue}"/> instance manufactured
///       to forward progress notifications from the tool to the client. If the client included a <see cref="ProgressToken"/> in their request,
///       progress reports issued to this instance will propagate to the client as <see cref="NotificationMethods.ProgressNotification"/> notifications with
///       that token. If the client did not include a <see cref="ProgressToken"/>, the instance will ignore any progress reports issued to it.
///     </description>
///   </item>
///   <item>
///     <description>
///       When the <see cref="McpServerTool"/> is constructed, it may be passed an <see cref="IServiceProvider"/> via
///       <see cref="McpServerToolCreateOptions.Services"/>. Any parameter that can be satisfied by that <see cref="IServiceProvider"/>
///       according to <see cref="IServiceProviderIsService"/> will not be included in the generated JSON schema and will be resolved
///       from the <see cref="IServiceProvider"/> provided to <see cref="InvokeAsync"/> rather than from the argument collection.
///     </description>
///   </item>
///   <item>
///     <description>
///       Any parameter attributed with <see cref="FromKeyedServicesAttribute"/> will similarly be resolved from the
///       <see cref="IServiceProvider"/> provided to <see cref="InvokeAsync"/> rather than from the argument
///       collection, and will not be included in the generated JSON schema.
///     </description>
///   </item>
/// </list>
/// </para>
/// <para>
/// All other parameters are deserialized from the <see cref="JsonElement"/>s in the <see cref="CallToolRequestParams.Arguments"/> dictionary,
/// using the <see cref="JsonSerializerOptions"/> supplied in <see cref="McpServerToolCreateOptions.SerializerOptions"/>, or if none was provided,
/// using <see cref="McpJsonUtilities.DefaultOptions"/>.
/// </para>
/// <para>
/// In general, the data supplied via the <see cref="CallToolRequestParams.Arguments"/>'s dictionary is passed along from the caller and
/// should thus be considered unvalidated and untrusted. To provide validated and trusted data to the invocation of the tool, consider having
/// the tool be an instance method, referring to data stored in the instance, or using an instance or parameters resolved from the <see cref="IServiceProvider"/>
/// to provide data to the method.
/// </para>
/// <para>
/// Return values from a method are used to create the <see cref="CallToolResult"/> that is sent back to the client:
/// </para>
/// <list type="table">
///   <item>
///     <term><see langword="null"/></term>
///     <description>Returns an empty <see cref="CallToolResult.Content"/> list.</description>
///   </item>
///   <item>
///     <term><see cref="AIContent"/></term>
///     <description>Converted to a single <see cref="ContentBlock"/> object using <see cref="AIContentExtensions.ToContent(AIContent)"/>.</description>
///   </item>
///   <item>
///     <term><see cref="string"/></term>
///     <description>Converted to a single <see cref="TextContentBlock"/> object with its text set to the string value.</description>
///   </item>
///   <item>
///     <term><see cref="ContentBlock"/></term>
///     <description>Returned as a single-item <see cref="ContentBlock"/> list.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{String}"/> of <see cref="string"/></term>
///     <description>Each <see cref="string"/> is converted to a <see cref="TextContentBlock"/> object with its text set to the string value.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{AIContent}"/> of <see cref="AIContent"/></term>
///     <description>Each <see cref="AIContent"/> is converted to a <see cref="ContentBlock"/> object using <see cref="AIContentExtensions.ToContent(AIContent)"/>.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{ContentBlock}"/> of <see cref="ContentBlock"/></term>
///     <description>Returned as the <see cref="ContentBlock"/> list.</description>
///   </item>
///   <item>
///     <term><see cref="CallToolResult"/></term>
///     <description>Returned directly without modification.</description>
///   </item>
///   <item>
///     <term>Other types</term>
///     <description>Serialized to JSON and returned as a single <see cref="ContentBlock"/> object with <see cref="ContentBlock.Type"/> set to "text".</description>
///   </item>
/// </list>
/// </remarks>
public abstract class McpServerTool : IMcpServerPrimitive
{
    /// Initializes a new instance of the <see cref="McpServerTool"/> class.
    protected McpServerTool()
    {
    }

    /// Gets the protocol <see cref="Tool"/> type for this instance.
    public abstract Tool ProtocolTool { get; }

    /// Invokes the <see cref="McpServerTool"/>.
    /// <param name="request">The request information resulting in the invocation of this tool.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>The call response from invoking the tool.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="request"/> is <see langword="null"/>.</exception>
    public abstract ValueTask<CallToolResult> InvokeAsync(
        RequestContext<CallToolRequestParams> request,
        CancellationToken cancellationToken = default);

    ///
    /// Creates an <see cref="McpServerTool"/> instance for a method, specified via a <see cref="Delegate"/> instance.
    ///
    /// <param name="method">The method to be represented via the created <see cref="McpServerTool"/>.</param>
    /// <param name="options">Optional options used in the creation of the <see cref="McpServerTool"/> to control its behavior.</param>
    /// <returns>The created <see cref="McpServerTool"/> for invoking <paramref name="method"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
    public static McpServerTool Create(
        Delegate method,
        McpServerToolCreateOptions? options = null) =>
        AIFunctionMcpServerTool.Create(method, options);

    ///
    /// Creates an <see cref="McpServerTool"/> instance for a method, specified via a <see cref="Delegate"/> instance.
    ///
    /// <param name="method">The method to be represented via the created <see cref="McpServerTool"/>.</param>
    /// <param name="target">The instance if <paramref name="method"/> is an instance method; otherwise, <see langword="null"/>.</param>
    /// <param name="options">Optional options used in the creation of the <see cref="McpServerTool"/> to control its behavior.</param>
    /// <returns>The created <see cref="McpServerTool"/> for invoking <paramref name="method"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="method"/> is an instance method but <paramref name="target"/> is <see langword="null"/>.</exception>
    public static McpServerTool Create(
        MethodInfo method,
        object? target = null,
        McpServerToolCreateOptions? options = null) =>
        AIFunctionMcpServerTool.Create(method, target, options);

    ///
    /// Creates an <see cref="McpServerTool"/> instance for a method, specified via an <see cref="MethodInfo"/> for
    /// and instance method, along with a <see cref="Type"/> representing the type of the target object to
    /// instantiate each time the method is invoked.
    ///
    /// <param name="method">The instance method to be represented via the created <see cref="AIFunction"/>.</param>
    /// <param name="createTargetFunc">
    /// Callback used on each function invocation to create an instance of the type on which the instance method <paramref name="method"/>
    /// will be invoked. If the returned instance is <see cref="IAsyncDisposable"/> or <see cref="IDisposable"/>, it will
    /// be disposed of after method completes its invocation.
    /// </param>
    /// <param name="options">Optional options used in the creation of the <see cref="McpServerTool"/> to control its behavior.</param>
    /// <returns>The created <see cref="AIFunction"/> for invoking <paramref name="method"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
    public static McpServerTool Create(
        MethodInfo method,
        Func<RequestContext<CallToolRequestParams>, object> createTargetFunc,
        McpServerToolCreateOptions? options = null) =>
        AIFunctionMcpServerTool.Create(method, createTargetFunc, options);

    /// Creates an <see cref="McpServerTool"/> that wraps the specified <see cref="AIFunction"/>.
    /// <param name="function">The function to wrap.</param>
    /// <param name="options">Optional options used in the creation of the <see cref="McpServerTool"/> to control its behavior.</param>
    /// <exception cref="ArgumentNullException"><paramref name="function"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// Unlike the other overloads of Create, the <see cref="McpServerTool"/> created by <see cref="Create(AIFunction, McpServerToolCreateOptions)"/>
    /// does not provide all of the special parameter handling for MCP-specific concepts, like <see cref="IMcpServer"/>.
    /// </remarks>
    public static McpServerTool Create(
        AIFunction function,
        McpServerToolCreateOptions? options = null) =>
        AIFunctionMcpServerTool.Create(function, options);

    /// <inheritdoc />
    public override string ToString() => ProtocolTool.Name;

    /// <inheritdoc />
    string IMcpServerPrimitive.Id => ProtocolTool.Name;
}

using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using System.Reflection;
using System.Text.Json;

namespace ModelContextProtocol.Server;

///
/// Represents an invocable prompt used by Model Context Protocol clients and servers.
///
/// <remarks>
/// <para>
/// <see cref="McpServerPrompt"/> is an abstract base class that represents an MCP prompt for use in the server (as opposed
/// to <see cref="Prompt"/>, which provides the protocol representation of a prompt, and <see cref="McpClientPrompt"/>, which
/// provides a client-side representation of a prompt). Instances of <see cref="McpServerPrompt"/> can be added into a
/// <see cref="IServiceCollection"/> to be picked up automatically when <see cref="McpServerFactory"/> is used to create
/// an <see cref="IMcpServer"/>, or added into a <see cref="McpServerPrimitiveCollection{McpServerPrompt}"/>.
/// </para>
/// <para>
/// Most commonly, <see cref="McpServerPrompt"/> instances are created using the static <see cref="M:McpServerPrompt.Create"/> methods.
/// These methods enable creating an <see cref="McpServerPrompt"/> for a method, specified via a <see cref="Delegate"/> or
/// <see cref="MethodInfo"/>, and are what are used implicitly by WithPromptsFromAssembly and WithPrompts. The <see cref="M:McpServerPrompt.Create"/> methods
/// create <see cref="McpServerPrompt"/> instances capable of working with a large variety of .NET method signatures, automatically handling
/// how parameters are marshaled into the method from the JSON received from the MCP client, and how the return value is marshaled back
/// into the <see cref="GetPromptResult"/> that's then serialized and sent back to the client.
/// </para>
/// <para>
/// By default, parameters are sourced from the <see cref="GetPromptRequestParams.Arguments"/> dictionary, which is a collection
/// of key/value pairs. Those parameters are deserialized from the
/// <see cref="JsonElement"/> values in that collection. There are a few exceptions to this:
/// <list type="bullet">
///   <item>
///     <description>
///       <see cref="CancellationToken"/> parameters are automatically bound to a <see cref="CancellationToken"/> provided by the
///       <see cref="IMcpServer"/> and that respects any <see cref="CancelledNotificationParams"/>s sent by the client for this operation's
///       <see cref="RequestId"/>.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IServiceProvider"/> parameters are bound from the <see cref="RequestContext{GetPromptRequestParams}"/> for this request.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IMcpServer"/> parameters are bound directly to the <see cref="IMcpServer"/> instance associated
///       with this request's <see cref="RequestContext{CallPromptRequestParams}"/>. Such parameters may be used to understand
///       what server is being used to process the request, and to interact with the client issuing the request to that server.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IProgress{ProgressNotificationValue}"/> parameters accepting <see cref="ProgressNotificationValue"/> values
///       are bound to an <see cref="IProgress{ProgressNotificationValue}"/> instance manufactured to forward progress notifications
///       from the prompt to the client. If the client included a <see cref="ProgressToken"/> in their request, progress reports issued
///       to this instance will propagate to the client as <see cref="NotificationMethods.ProgressNotification"/> notifications with
///       that token. If the client did not include a <see cref="ProgressToken"/>, the instance will ignore any progress reports issued to it.
///     </description>
///   </item>
///   <item>
///     <description>
///       When the <see cref="McpServerPrompt"/> is constructed, it may be passed an <see cref="IServiceProvider"/> via
///       <see cref="McpServerPromptCreateOptions.Services"/>. Any parameter that can be satisfied by that <see cref="IServiceProvider"/>
///       according to <see cref="IServiceProviderIsService"/> will be resolved from the <see cref="IServiceProvider"/> provided to
///       <see cref="GetAsync"/> rather than from the argument collection.
///     </description>
///   </item>
///   <item>
///     <description>
///       Any parameter attributed with <see cref="FromKeyedServicesAttribute"/> will similarly be resolved from the
///       <see cref="IServiceProvider"/> provided to <see cref="GetAsync"/> rather than from the argument collection.
///     </description>
///   </item>
/// </list>
/// </para>
/// <para>
/// All other parameters are deserialized from the <see cref="JsonElement"/>s in the <see cref="GetPromptRequestParams.Arguments"/> dictionary.
/// </para>
/// <para>
/// In general, the data supplied via the <see cref="GetPromptRequestParams.Arguments"/>'s dictionary is passed along from the caller and
/// should thus be considered unvalidated and untrusted. To provide validated and trusted data to the invocation of the prompt, consider having
/// the prompt be an instance method, referring to data stored in the instance, or using an instance or parameters resolved from the <see cref="IServiceProvider"/>
/// to provide data to the method.
/// </para>
/// <para>
/// Return values from a method are used to create the <see cref="GetPromptResult"/> that is sent back to the client:
/// </para>
/// <list type="table">
///   <item>
///     <term><see cref="string"/></term>
///     <description>Converted to a list containing a single <see cref="PromptMessage"/> with its <see cref="PromptMessage.Content"/> set to contain the <see cref="string"/>.</description>
///   </item>
///   <item>
///     <term><see cref="PromptMessage"/></term>
///     <description>Converted to a list containing the single <see cref="PromptMessage"/>.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{PromptMessage}"/> of <see cref="PromptMessage"/></term>
///     <description>Converted to a list containing all of the returned <see cref="PromptMessage"/> instances.</description>
///   </item>
///   <item>
///     <term><see cref="ChatMessage"/></term>
///     <description>Converted to a list of <see cref="PromptMessage"/> instances derived from the <see cref="ChatMessage"/> with <see cref="AIContentExtensions.ToPromptMessages"/>.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{PromptMessage}"/> of <see cref="PromptMessage"/></term>
///     <description>Converted to a list of <see cref="PromptMessage"/> instances derived from all of the <see cref="ChatMessage"/> instances with <see cref="AIContentExtensions.ToPromptMessages"/>.</description>
///   </item>
/// </list>
/// <para>
/// Other returned types will result in an <see cref="InvalidOperationException"/> being thrown.
/// </para>
/// </remarks>
public abstract class McpServerPrompt : IMcpServerPrimitive
{
    /// Initializes a new instance of the <see cref="McpServerPrompt"/> class.
    protected McpServerPrompt()
    {
    }

    /// Gets the protocol <see cref="Prompt"/> type for this instance.
    /// <remarks>
    /// The ProtocolPrompt property represents the underlying prompt definition as defined in the
    /// Model Context Protocol specification. It contains metadata like the prompt's name,
    /// description, and acceptable arguments.
    /// </remarks>
    public abstract Prompt ProtocolPrompt { get; }

    ///
    /// Gets the prompt, rendering it with the provided request parameters and returning the prompt result.
    ///
    /// <param name="request">
    /// The request context containing information about the prompt invocation, including any arguments
    /// passed to the prompt. This object provides access to both the request parameters and the server context.
    /// </param>
    /// <param name="cancellationToken">
    /// The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.
    /// </param>
    /// <returns>
    /// A <see cref="Task"/> representing the asynchronous operation, containing a <see cref="GetPromptResult"/> with
    /// the prompt content and messages.
    /// </returns>
    /// <exception cref="ArgumentNullException"><paramref name="request"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The prompt implementation returns <see langword="null"/> or an unsupported result type.</exception>
    public abstract ValueTask<GetPromptResult> GetAsync(
        RequestContext<GetPromptRequestParams> request,
        CancellationToken cancellationToken = default);

    ///
    /// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via a <see cref="Delegate"/> instance.
    ///
    /// <param name="method">The method to be represented via the created <see cref="McpServerPrompt"/>.</param>
    /// <param name="options">Optional options used in the creation of the <see cref="McpServerPrompt"/> to control its behavior.</param>
    /// <returns>The created <see cref="McpServerPrompt"/> for invoking <paramref name="method"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
    public static McpServerPrompt Create(
        Delegate method,
        McpServerPromptCreateOptions? options = null) =>
        AIFunctionMcpServerPrompt.Create(method, options);

    ///
    /// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via a <see cref="Delegate"/> instance.
    ///
    /// <param name="method">The method to be represented via the created <see cref="McpServerPrompt"/>.</param>
    /// <param name="target">The instance if <paramref name="method"/> is an instance method; otherwise, <see langword="null"/>.</param>
    /// <param name="options">Optional options used in the creation of the <see cref="McpServerPrompt"/> to control its behavior.</param>
    /// <returns>The created <see cref="McpServerPrompt"/> for invoking <paramref name="method"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="method"/> is an instance method but <paramref name="target"/> is <see langword="null"/>.</exception>
    public static McpServerPrompt Create(
        MethodInfo method,
        object? target = null,
        McpServerPromptCreateOptions? options = null) =>
        AIFunctionMcpServerPrompt.Create(method, target, options);

    ///
    /// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via an <see cref="MethodInfo"/> for
    /// and instance method, along with a <see cref="Type"/> representing the type of the target object to
    /// instantiate each time the method is invoked.
    ///
    /// <param name="method">The instance method to be represented via the created <see cref="AIFunction"/>.</param>
    /// <param name="createTargetFunc">
    /// Callback used on each function invocation to create an instance of the type on which the instance method <paramref name="method"/>
    /// will be invoked. If the returned instance is <see cref="IAsyncDisposable"/> or <see cref="IDisposable"/>, it will
    /// be disposed of after method completes its invocation.
    /// </param>
    /// <param name="options">Optional options used in the creation of the <see cref="McpServerPrompt"/> to control its behavior.</param>
    /// <returns>The created <see cref="AIFunction"/> for invoking <paramref name="method"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
    public static McpServerPrompt Create(
        MethodInfo method,
        Func<RequestContext<GetPromptRequestParams>, object> createTargetFunc,
        McpServerPromptCreateOptions? options = null) =>
        AIFunctionMcpServerPrompt.Create(method, createTargetFunc, options);

    /// Creates an <see cref="McpServerPrompt"/> that wraps the specified <see cref="AIFunction"/>.
    /// <param name="function">The function to wrap.</param>
    /// <param name="options">Optional options used in the creation of the <see cref="McpServerPrompt"/> to control its behavior.</param>
    /// <exception cref="ArgumentNullException"><paramref name="function"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// Unlike the other overloads of Create, the <see cref="McpServerPrompt"/> created by <see cref="Create(AIFunction, McpServerPromptCreateOptions)"/>
    /// does not provide all of the special parameter handling for MCP-specific concepts, like <see cref="IMcpServer"/>.
    /// </remarks>
    public static McpServerPrompt Create(
        AIFunction function,
        McpServerPromptCreateOptions? options = null) =>
        AIFunctionMcpServerPrompt.Create(function, options);

    /// <inheritdoc />
    public override string ToString() => ProtocolPrompt.Name;

    /// <inheritdoc />
    string IMcpServerPrimitive.Id => ProtocolPrompt.Name;
}

using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Server;

/// <summary>
/// Provides an <see cref="ITransport"/> implemented via "stdio" (standard input/output).
/// </summary>
public sealed class StdioServerTransport : StreamServerTransport
{
    /// <summary>
    /// Initializes a new instance of the <see cref="StdioServerTransport"/> class.
    /// </summary>
    /// <param name="serverOptions">The server options.</param>
    /// <param name="loggerFactory">Optional logger factory used for logging employed by the transport.</param>
    /// <exception cref="ArgumentNullException"><paramref name="serverOptions"/> is <see langword="null"/> or contains a null name.</exception>
    public StdioServerTransport(McpServerOptions serverOptions, ILoggerFactory? loggerFactory = null)
        : this(GetServerName(serverOptions), loggerFactory: loggerFactory)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="StdioServerTransport"/> class.
    /// </summary>
    /// <param name="serverName">The name of the server.</param>
    /// <param name="loggerFactory">Optional logger factory used for logging employed by the transport.</param>
    /// <exception cref="ArgumentNullException"><paramref name="serverName"/> is <see langword="null"/>.</exception>
    public StdioServerTransport(string serverName, ILoggerFactory? loggerFactory = null)
        : base(new CancellableStdinStream(Console.OpenStandardInput()),
               new BufferedStream(Console.OpenStandardOutput()),
               serverName ?? throw new ArgumentNullException(nameof(serverName)),
               loggerFactory)
    {
    }

    private static string GetServerName(McpServerOptions serverOptions)
    {
        Throw.IfNull(serverOptions);

        return serverOptions.ServerInfo?.Name ?? McpServer.DefaultImplementation.Name;
    }

    // Neither WindowsConsoleStream nor UnixConsoleStream respect CancellationTokens or cancel any I/O on Dispose.
    // WindowsConsoleStream will return an EOS on Ctrl-C, but that is not the only reason the shutdownToken may fire.
    private sealed class CancellableStdinStream(Stream stdinStream) : Stream
    {
        public override bool CanRead => true;
        public override bool CanSeek => false;
        public override bool CanWrite => false;

        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
            => stdinStream.ReadAsync(buffer, offset, count, cancellationToken).WaitAsync(cancellationToken);

#if NET
        public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
        {
            ValueTask<int> vt = stdinStream.ReadAsync(buffer, cancellationToken);
            return vt.IsCompletedSuccessfully ? vt : new(vt.AsTask().WaitAsync(cancellationToken));
        }
#endif

        // The McpServer shouldn't call flush on the stdin Stream, but it doesn't need to throw just in case.
        public override void Flush() { }

        public override long Length => throw new NotSupportedException();

        public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }

        public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();

        public override void SetLength(long value) => throw new NotSupportedException();
        public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
    }
}

using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Protocol;
using System.Reflection;

namespace ModelContextProtocol.Server;

///
/// Represents an invocable resource used by Model Context Protocol clients and servers.
///
/// <remarks>
/// <para>
/// <see cref="McpServerResource"/> is an abstract base class that represents an MCP resource for use in the server (as opposed
/// to <see cref="Resource"/> or <see cref="ResourceTemplate"/>, which provide the protocol representations of a resource). Instances of
/// <see cref="McpServerResource"/> can be added into a <see cref="IServiceCollection"/> to be picked up automatically when
/// <see cref="McpServerFactory"/> is used to create an <see cref="IMcpServer"/>, or added into a <see cref="McpServerPrimitiveCollection{McpServerResource}"/>.
/// </para>
/// <para>
/// Most commonly, <see cref="McpServerResource"/> instances are created using the static <see cref="M:McpServerResource.Create"/> methods.
/// These methods enable creating an <see cref="McpServerResource"/> for a method, specified via a <see cref="Delegate"/> or
/// <see cref="MethodInfo"/>, and are what are used implicitly by WithResourcesFromAssembly and
/// <see cref="M:McpServerBuilderExtensions.WithResources"/>. The <see cref="M:McpServerResource.Create"/> methods
/// create <see cref="McpServerResource"/> instances capable of working with a large variety of .NET method signatures, automatically handling
/// how parameters are marshaled into the method from the URI received from the MCP client, and how the return value is marshaled back
/// into the <see cref="ReadResourceResult"/> that's then serialized and sent back to the client.
/// </para>
/// <para>
/// <see cref="McpServerResource"/> is used to represent both direct resources (e.g. "resource://example") and templated
/// resources (e.g. "resource://example/{id}").
/// </para>
/// <para>
/// Read resource requests do not contain separate arguments, only a URI. However, for templated resources, portions of that URI may be considered
/// as arguments and may be bound to parameters. Further, resource methods may accept parameters that will be bound to arguments based on their type.
/// <list type="bullet">
///   <item>
///     <description>
///       <see cref="CancellationToken"/> parameters are automatically bound to a <see cref="CancellationToken"/> provided by the
///       <see cref="IMcpServer"/> and that respects any <see cref="CancelledNotificationParams"/>s sent by the client for this operation's
///       <see cref="RequestId"/>.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IServiceProvider"/> parameters are bound from the <see cref="RequestContext{ReadResourceRequestParams}"/> for this request.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IMcpServer"/> parameters are bound directly to the <see cref="IMcpServer"/> instance associated
///       with this request's <see cref="RequestContext{ReadResourceRequestParams}"/>. Such parameters may be used to understand
///       what server is being used to process the request, and to interact with the client issuing the request to that server.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IProgress{ProgressNotificationValue}"/> parameters accepting <see cref="ProgressNotificationValue"/> values
///       are bound to an <see cref="IProgress{ProgressNotificationValue}"/> instance manufactured to forward progress notifications
///       from the resource to the client. If the client included a <see cref="ProgressToken"/> in their request, progress reports issued
///       to this instance will propagate to the client as <see cref="NotificationMethods.ProgressNotification"/> notifications with
///       that token. If the client did not include a <see cref="ProgressToken"/>, the instance will ignore any progress reports issued to it.
///     </description>
///   </item>
///   <item>
///     <description>
///       When the <see cref="McpServerResource"/> is constructed, it may be passed an <see cref="IServiceProvider"/> via
///       <see cref="McpServerResourceCreateOptions.Services"/>. Any parameter that can be satisfied by that <see cref="IServiceProvider"/>
///       according to <see cref="IServiceProviderIsService"/> will be resolved from the <see cref="IServiceProvider"/> provided to the
///       resource invocation rather than from the argument collection.
///     </description>
///   </item>
///   <item>
///     <description>
///       Any parameter attributed with <see cref="FromKeyedServicesAttribute"/> will similarly be resolved from the
///       <see cref="IServiceProvider"/> provided to the resource invocation rather than from the argument collection.
///     </description>
///   </item>
///   <item>
///     <description>
///       All other parameters are bound from the data in the URI.
///     </description>
///   </item>
/// </list>
/// </para>
/// <para>
/// Return values from a method are used to create the <see cref="ReadResourceResult"/> that is sent back to the client:
/// </para>
/// <list type="table">
///   <item>
///     <term><see cref="ResourceContents"/></term>
///     <description>Wrapped in a list containing the single <see cref="ResourceContents"/>.</description>
///   </item>
///   <item>
///     <term><see cref="TextContentBlock"/></term>
///     <description>Converted to a list containing a single <see cref="TextResourceContents"/>.</description>
///   </item>
///   <item>
///     <term><see cref="DataContent"/></term>
///     <description>Converted to a list containing a single <see cref="BlobResourceContents"/>.</description>
///   </item>
///   <item>
///     <term><see cref="string"/></term>
///     <description>Converted to a list containing a single <see cref="TextResourceContents"/>.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{ResourceContents}"/> of <see cref="ResourceContents"/></term>
///     <description>Returned directly as a list of <see cref="ResourceContents"/>.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{AIContent}"/> of <see cref="AIContent"/></term>
///     <description>Converted to a list containing a <see cref="TextResourceContents"/> for each <see cref="TextContentBlock"/> and a <see cref="BlobResourceContents"/> for each <see cref="DataContent"/>.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{String}"/> of <see cref="string"/></term>
///     <description>Converted to a list containing a <see cref="TextResourceContents"/>, one for each <see cref="string"/>.</description>
///   </item>
/// </list>
/// <para>
/// Other returned types will result in an <see cref="InvalidOperationException"/> being thrown.
/// </para>
/// </remarks>
public abstract class McpServerResource : IMcpServerPrimitive
{
    /// Initializes a new instance of the <see cref="McpServerResource"/> class.
    protected McpServerResource()
    {
    }

    /// Gets whether this resource is a URI template with parameters as opposed to a direct resource.
    public bool IsTemplated => ProtocolResourceTemplate.UriTemplate.Contains('{');

    /// Gets the protocol <see cref="ResourceTemplate"/> type for this instance.
    /// <remarks>
    /// <para>
    /// The <see cref="ProtocolResourceTemplate"/> property represents the underlying resource template definition as defined in the
    /// Model Context Protocol specification. It contains metadata like the resource templates's URI template, name, and description.
    /// </para>
    /// <para>
    /// Every valid resource URI is a valid resource URI template, and thus this property always returns an instance.
    /// In contrast, the <see cref="ProtocolResource"/> property may return <see langword="null"/> if the resource template
    /// contains a parameter, in which case the resource template URI is not a valid resource URI.
    /// </para>
    /// </remarks>
    public abstract ResourceTemplate ProtocolResourceTemplate { get; }

    /// Gets the protocol <see cref="Resource"/> type for this instance.
    /// <remarks>
    /// The ProtocolResourceTemplate property represents the underlying resource template definition as defined in the
    /// Model Context Protocol specification. It contains metadata like the resource templates's URI template, name, and description.
    /// </remarks>
    public virtual Resource? ProtocolResource => ProtocolResourceTemplate.AsResource();

    ///
    /// Gets the resource, rendering it with the provided request parameters and returning the resource result.
    ///
    /// <param name="request">
    /// The request context containing information about the resource invocation, including any arguments
    /// passed to the resource. This object provides access to both the request parameters and the server context.
    /// </param>
    /// <param name="cancellationToken">
    /// The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.
    /// </param>
    /// <returns>
    /// A <see cref="ValueTask{ReadResourceResult}"/> representing the asynchronous operation, containing a <see cref="ReadResourceResult"/> with
    /// the resource content and messages. If and only if this <see cref="McpServerResource"/> doesn't match the <see cref="ReadResourceRequestParams.Uri"/>,
    /// the method returns <see langword="null"/>.
    /// </returns>
    /// <exception cref="ArgumentNullException"><paramref name="request"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The resource implementation returned <see langword="null"/> or an unsupported result type.</exception>
    public abstract ValueTask<ReadResourceResult?> ReadAsync(
        RequestContext<ReadResourceRequestParams> request,
        CancellationToken cancellationToken = default);

    ///
    /// Creates an <see cref="McpServerResource"/> instance for a method, specified via a <see cref="Delegate"/> instance.
    ///
    /// <param name="method">The method to be represented via the created <see cref="McpServerResource"/>.</param>
    /// <param name="options">Optional options used in the creation of the <see cref="McpServerResource"/> to control its behavior.</param>
    /// <returns>The created <see cref="McpServerResource"/> for invoking <paramref name="method"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
    public static McpServerResource Create(
        Delegate method,
        McpServerResourceCreateOptions? options = null) =>
        AIFunctionMcpServerResource.Create(method, options);

    ///
    /// Creates an <see cref="McpServerResource"/> instance for a method, specified via a <see cref="Delegate"/> instance.
    ///
    /// <param name="method">The method to be represented via the created <see cref="McpServerResource"/>.</param>
    /// <param name="target">The instance if <paramref name="method"/> is an instance method; otherwise, <see langword="null"/>.</param>
    /// <param name="options">Optional options used in the creation of the <see cref="McpServerResource"/> to control its behavior.</param>
    /// <returns>The created <see cref="McpServerResource"/> for invoking <paramref name="method"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="method"/> is an instance method but <paramref name="target"/> is <see langword="null"/>.</exception>
    public static McpServerResource Create(
        MethodInfo method,
        object? target = null,
        McpServerResourceCreateOptions? options = null) =>
        AIFunctionMcpServerResource.Create(method, target, options);

    ///
    /// Creates an <see cref="McpServerResource"/> instance for a method, specified via an <see cref="MethodInfo"/> for
    /// and instance method, along with a <see cref="Type"/> representing the type of the target object to
    /// instantiate each time the method is invoked.
    ///
    /// <param name="method">The instance method to be represented via the created <see cref="AIFunction"/>.</param>
    /// <param name="createTargetFunc">
    /// Callback used on each function invocation to create an instance of the type on which the instance method <paramref name="method"/>
    /// will be invoked. If the returned instance is <see cref="IAsyncDisposable"/> or <see cref="IDisposable"/>, it will
    /// be disposed of after method completes its invocation.
    /// </param>
    /// <param name="options">Optional options used in the creation of the <see cref="McpServerResource"/> to control its behavior.</param>
    /// <returns>The created <see cref="AIFunction"/> for invoking <paramref name="method"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
    public static McpServerResource Create(
        MethodInfo method,
        Func<RequestContext<ReadResourceRequestParams>, object> createTargetFunc,
        McpServerResourceCreateOptions? options = null) =>
        AIFunctionMcpServerResource.Create(method, createTargetFunc, options);

    /// Creates an <see cref="McpServerResource"/> that wraps the specified <see cref="AIFunction"/>.
    /// <param name="function">The function to wrap.</param>
    /// <param name="options">Optional options used in the creation of the <see cref="McpServerResource"/> to control its behavior.</param>
    /// <exception cref="ArgumentNullException"><paramref name="function"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// Unlike the other overloads of Create, the <see cref="McpServerResource"/> created by <see cref="Create(AIFunction, McpServerResourceCreateOptions)"/>
    /// does not provide all of the special parameter handling for MCP-specific concepts, like <see cref="IMcpServer"/>.
    /// </remarks>
    public static McpServerResource Create(
        AIFunction function,
        McpServerResourceCreateOptions? options = null) =>
        AIFunctionMcpServerResource.Create(function, options);

    /// <inheritdoc />
    public override string ToString() => ProtocolResourceTemplate.UriTemplate;

    /// <inheritdoc />
    string IMcpServerPrimitive.Id => ProtocolResourceTemplate.UriTemplate;
}

namespace ModelContextProtocol.Server;

///
/// Provides a context container that provides access to the client request parameters and resources for the request.
///
/// <typeparam name="TParams">Type of the request parameters specific to each MCP operation.</typeparam>
/// <remarks>
/// The <see cref="RequestContext{TParams}"/> encapsulates all contextual information for handling an MCP request.
/// This type is typically received as a parameter in handler delegates registered with IMcpServerBuilder,
/// and may be injected as parameters into <see cref="McpServerTool"/>s.
/// </remarks>
public sealed class RequestContext<TParams>
{
    /// The server with which this instance is associated.
    private IMcpServer _server;

    ///
    /// Initializes a new instance of the <see cref="RequestContext{TParams}"/> class with the specified server.
    ///
    /// <param name="server">The server with which this instance is associated.</param>
    public RequestContext(IMcpServer server)
    {
        Throw.IfNull(server);

        _server = server;
        Services = server.Services;
    }

    /// Gets or sets the server with which this instance is associated.
    public IMcpServer Server
    {
        get => _server;
        set
        {
            Throw.IfNull(value);
            _server = value;
        }
    }

    /// Gets or sets the services associated with this request.
    /// <remarks>
    /// This may not be the same instance stored in <see cref="IMcpServer.Services"/>
    /// if <see cref="McpServerOptions.ScopeRequests"/> was true, in which case this
    /// might be a scoped <see cref="IServiceProvider"/> derived from the server's
    /// <see cref="IMcpServer.Services"/>.
    /// </remarks>
    public IServiceProvider? Services { get; set; }

    /// Gets or sets the parameters associated with this request.
    public TParams? Params { get; set; }
}

using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Protocol;
using System.Text.Json;

namespace ModelContextProtocol.Server;

/// <summary>
/// Used to indicate that a method should be considered an <see cref="McpServerTool"/>.
/// </summary>
/// <remarks>
/// <para>
/// This attribute is applied to methods that should be exposed as tools in the Model Context Protocol. When a class
/// containing methods marked with this attribute is registered with McpServerBuilderExtensions,
/// these methods become available as tools that can be called by MCP clients.
/// </para>
/// <para>
/// When methods are provided directly to <see cref="M:McpServerTool.Create"/>, the attribute is not required.
/// </para>
/// <para>
/// By default, parameters are sourced from the <see cref="CallToolRequestParams.Arguments"/> dictionary, which is a collection
/// of key/value pairs, and are represented in the JSON schema for the function, as exposed in the returned <see cref="McpServerTool"/>'s
/// <see cref="McpServerTool.ProtocolTool"/>'s <see cref="Tool.InputSchema"/>. Those parameters are deserialized from the
/// <see cref="JsonElement"/> values in that collection. There are a few exceptions to this:
/// <list type="bullet">
///   <item>
///     <description>
///       <see cref="CancellationToken"/> parameters are automatically bound to a <see cref="CancellationToken"/> provided by the
///       <see cref="IMcpServer"/> and that respects any <see cref="CancelledNotificationParams"/>s sent by the client for this operation's
///       <see cref="RequestId"/>. The parameter is not included in the generated JSON schema.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IServiceProvider"/> parameters are bound from the <see cref="RequestContext{CallToolRequestParams}"/> for this request,
///       and are not included in the JSON schema.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IMcpServer"/> parameters are not included in the JSON schema and are bound directly to the <see cref="IMcpServer"/>
///       instance associated with this request's <see cref="RequestContext{CallToolRequestParams}"/>. Such parameters may be used to understand
///       what server is being used to process the request, and to interact with the client issuing the request to that server.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IProgress{ProgressNotificationValue}"/> parameters accepting <see cref="ProgressNotificationValue"/> values
///       are not included in the JSON schema and are bound to an <see cref="IProgress{ProgressNotificationValue}"/> instance manufactured
///       to forward progress notifications from the tool to the client. If the client included a <see cref="ProgressToken"/> in their request,
///       progress reports issued to this instance will propagate to the client as <see cref="NotificationMethods.ProgressNotification"/> notifications with
///       that token. If the client did not include a <see cref="ProgressToken"/>, the instance will ignore any progress reports issued to it.
///     </description>
///   </item>
///   <item>
///     <description>
///       When the <see cref="McpServerTool"/> is constructed, it may be passed an <see cref="IServiceProvider"/> via
///       <see cref="McpServerToolCreateOptions.Services"/>. Any parameter that can be satisfied by that <see cref="IServiceProvider"/>
///       according to <see cref="IServiceProviderIsService"/> will not be included in the generated JSON schema and will be resolved
///       from the <see cref="IServiceProvider"/> provided to when the tool is invoked rather than from the argument collection.
///     </description>
///   </item>
///   <item>
///     <description>
///       Any parameter attributed with <see cref="FromKeyedServicesAttribute"/> will similarly be resolved from the
///       <see cref="IServiceProvider"/> provided when the tool is invoked rather than from the argument
///       collection, and will not be included in the generated JSON schema.
///     </description>
///   </item>
/// </list>
/// </para>
/// <para>
/// All other parameters are deserialized from the <see cref="JsonElement"/>s in the <see cref="CallToolRequestParams.Arguments"/> dictionary,
/// using the <see cref="JsonSerializerOptions"/> supplied in <see cref="McpServerToolCreateOptions.SerializerOptions"/>, or if none was provided,
/// using <see cref="McpJsonUtilities.DefaultOptions"/>.
/// </para>
/// <para>
/// In general, the data supplied via the <see cref="CallToolRequestParams.Arguments"/>'s dictionary is passed along from the caller and
/// should thus be considered unvalidated and untrusted. To provide validated and trusted data to the invocation of the tool, consider having
/// the tool be an instance method, referring to data stored in the instance, or using an instance or parameters resolved from the <see cref="IServiceProvider"/>
/// to provide data to the method.
/// </para>
/// <para>
/// Return values from a method are used to create the <see cref="CallToolResult"/> that is sent back to the client:
/// </para>
/// <list type="table">
///   <item>
///     <term><see langword="null"/></term>
///     <description>Returns an empty <see cref="CallToolResult.Content"/> list.</description>
///   </item>
///   <item>
///     <term><see cref="AIContent"/></term>
///     <description>Converted to a single <see cref="ContentBlock"/> object using <see cref="AIContentExtensions.ToContent(AIContent)"/>.</description>
///   </item>
///   <item>
///     <term><see cref="string"/></term>
///     <description>Converted to a single <see cref="TextContentBlock"/> object with its text set to the string value.</description>
///   </item>
///   <item>
///     <term><see cref="ContentBlock"/></term>
///     <description>Returned as a single-item <see cref="ContentBlock"/> list.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{String}"/> of <see cref="string"/></term>
///     <description>Each <see cref="string"/> is converted to a <see cref="ContentBlock"/> object with its text set to the string value.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{AIContent}"/> of <see cref="AIContent"/></term>
///     <description>Each <see cref="AIContent"/> is converted to a <see cref="ContentBlock"/> object using <see cref="AIContentExtensions.ToContent(AIContent)"/>.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{Content}"/> of <see cref="ContentBlock"/></term>
///     <description>Returned as the <see cref="ContentBlock"/> list.</description>
///   </item>
///   <item>
///     <term><see cref="CallToolResult"/></term>
///     <description>Returned directly without modification.</description>
///   </item>
///   <item>
///     <term>Other types</term>
///     <description>Serialized to JSON and returned as a single <see cref="ContentBlock"/> object with <see cref="ContentBlock.Type"/> set to "text".</description>
///   </item>
/// </list>
/// </remarks>
[AttributeUsage(AttributeTargets.Method)]
public sealed class McpServerToolAttribute : Attribute
{
    // Defaults based on the spec
    private const bool DestructiveDefault = true;
    private const bool IdempotentDefault = false;
    private const bool OpenWorldDefault = true;
    private const bool ReadOnlyDefault = false;

    // Nullable backing fields so we can distinguish
    internal bool? _destructive;
    internal bool? _idempotent;
    internal bool? _openWorld;
    internal bool? _readOnly;

    /// <summary>
    /// Initializes a new instance of the <see cref="McpServerToolAttribute"/> class.
    /// </summary>
    public McpServerToolAttribute()
    {
    }

    /// <summary>Gets the name of the tool.</summary>
    /// <remarks>If <see langword="null"/>, the method name will be used.</remarks>
    public string? Name { get; set; }

    /// <summary>
    /// Gets or sets a human-readable title for the tool that can be displayed to users.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The title provides a more descriptive, user-friendly name for the tool than the tool's
    /// programmatic name. It is intended for display purposes and to help users understand
    /// the tool's purpose at a glance.
    /// </para>
    /// <para>
    /// Unlike the tool name (which follows programmatic naming conventions), the title can
    /// include spaces, special characters, and be phrased in a more natural language style.
    /// </para>
    /// </remarks>
    public string? Title { get; set; }

    /// <summary>
    /// Gets or sets whether the tool may perform destructive updates to its environment.
    /// </summary>
    /// <remarks>
    /// <para>
    /// If <see langword="true"/>, the tool may perform destructive updates to its environment.
    /// If <see langword="false"/>, the tool performs only additive updates.
    /// This property is most relevant when the tool modifies its environment (ReadOnly = false).
    /// </para>
    /// <para>
    /// The default is <see langword="true"/>.
    /// </para>
    /// </remarks>
    public bool Destructive
    {
        get => _destructive ?? DestructiveDefault;
        set => _destructive = value;
    }

    /// <summary>
    /// Gets or sets whether calling the tool repeatedly with the same arguments
    /// will have no additional effect on its environment.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This property is most relevant when the tool modifies its environment (ReadOnly = false).
    /// </para>
    /// <para>
    /// The default is <see langword="false"/>.
    /// </para>
    /// </remarks>
    public bool Idempotent
    {
        get => _idempotent ?? IdempotentDefault;
        set => _idempotent = value;
    }

    /// <summary>
    /// Gets or sets whether this tool may interact with an "open world" of external entities.
    /// </summary>
    /// <remarks>
    /// <para>
    /// If <see langword="true"/>, the tool may interact with an unpredictable or dynamic set of entities (like web search).
    /// If <see langword="false"/>, the tool's domain of interaction is closed and well-defined (like memory access).
    /// </para>
    /// <para>
    /// The default is <see langword="true"/>.
    /// </para>
    /// </remarks>
    public bool OpenWorld
    {
        get => _openWorld ?? OpenWorldDefault;
        set => _openWorld = value;
    }

    /// <summary>
    /// Gets or sets whether this tool does not modify its environment.
    /// </summary>
    /// <remarks>
    /// <para>
    /// If <see langword="true"/>, the tool only performs read operations without changing state.
    /// If <see langword="false"/>, the tool may make modifications to its environment.
    /// </para>
    /// <para>
    /// Read-only tools do not have side effects beyond computational resource usage.
    /// They don't create, update, or delete data in any system.
    /// </para>
    /// <para>
    /// The default is <see langword="false"/>.
    /// </para>
    /// </remarks>
    public bool ReadOnly
    {
        get => _readOnly ?? ReadOnlyDefault;
        set => _readOnly = value;
    }

    /// <summary>
    /// Gets or sets whether the tool should report an output schema for structured content.
    /// </summary>
    /// <remarks>
    /// <para>
    /// When enabled, the tool will attempt to populate the <see cref="Tool.OutputSchema"/>
    /// and provide structured content in the <see cref="CallToolResult.StructuredContent"/> property.
    /// </para>
    /// <para>
    /// The default is <see langword="false"/>.
    /// </para>
    /// </remarks>
    public bool UseStructuredContent { get; set; }
}

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.AspNetCore;
using ModelContextProtocol.Protocol;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.AspNetCore.Builder;

/// <summary>
/// Provides extension methods for <see cref="IEndpointRouteBuilder"/> to add MCP endpoints.
/// </summary>
public static class McpEndpointRouteBuilderExtensions
{
    /// <summary>
    /// Sets up endpoints for handling MCP Streamable HTTP transport.
    /// See <see href="https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http">the 2025-06-18 protocol specification</see> for details about the Streamable HTTP transport.
    /// Also maps legacy SSE endpoints for backward compatibility at the path "/sse" and "/message". <see href="https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse">the 2024-11-05 protocol specification</see> for details about the HTTP with SSE transport.
    /// </summary>
    /// <param name="endpoints">The web application to attach MCP HTTP endpoints.</param>
    /// <param name="pattern">The route pattern prefix to map to.</param>
    /// <returns>Returns a builder for configuring additional endpoint conventions like authorization policies.</returns>
    public static IEndpointConventionBuilder MapMcp(this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern = "")
    {
        var streamableHttpHandler = endpoints.ServiceProvider.GetService<StreamableHttpHandler>() ??
            throw new InvalidOperationException("You must call WithHttpTransport(). Unable to find required services. Call builder.Services.AddMcpServer().WithHttpTransport() in application startup code.");

        var mcpGroup = endpoints.MapGroup(pattern);
        var streamableHttpGroup = mcpGroup.MapGroup("")
            .WithDisplayName(b => $"MCP Streamable HTTP | {b.DisplayName}")
            .WithMetadata(new ProducesResponseTypeMetadata(StatusCodes.Status404NotFound, typeof(JsonRpcError), contentTypes: ["application/json"]));

        streamableHttpGroup.MapPost("", streamableHttpHandler.HandlePostRequestAsync)
            .WithMetadata(new AcceptsMetadata(["application/json"]))
            .WithMetadata(new ProducesResponseTypeMetadata(StatusCodes.Status200OK, contentTypes: ["text/event-stream"]))
            .WithMetadata(new ProducesResponseTypeMetadata(StatusCodes.Status202Accepted));

        if (!streamableHttpHandler.HttpServerTransportOptions.Stateless)
        {
            // The GET and DELETE endpoints are not mapped in Stateless mode since there's no way to send unsolicited messages
            // for the GET to handle, and there is no server-side state for the DELETE to clean up.
            streamableHttpGroup.MapGet("", streamableHttpHandler.HandleGetRequestAsync)
                .WithMetadata(new ProducesResponseTypeMetadata(StatusCodes.Status200OK, contentTypes: ["text/event-stream"]));
            streamableHttpGroup.MapDelete("", streamableHttpHandler.HandleDeleteRequestAsync);

            // Map legacy HTTP with SSE endpoints only if not in Stateless mode, because we cannot guarantee the /message requests
            // will be handled by the same process as the /sse request.
            var sseHandler = endpoints.ServiceProvider.GetRequiredService<SseHandler>();
            var sseGroup = mcpGroup.MapGroup("")
                .WithDisplayName(b => $"MCP HTTP with SSE | {b.DisplayName}");

            sseGroup.MapGet("/sse", sseHandler.HandleSseRequestAsync)
                .WithMetadata(new ProducesResponseTypeMetadata(StatusCodes.Status200OK, contentTypes: ["text/event-stream"]));
            sseGroup.MapPost("/message", sseHandler.HandleMessageRequestAsync)
                .WithMetadata(new AcceptsMetadata(["application/json"]))
                .WithMetadata(new ProducesResponseTypeMetadata(StatusCodes.Status202Accepted));
        }

        return mcpGroup;
    }
}

using EverythingServer;
using EverythingServer.Prompts;
using EverythingServer.Resources;
using EverythingServer.Tools;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using OpenTelemetry;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(consoleLogOptions =>
{
    // Configure all logs to go to stderr
    consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});

HashSet<string> subscriptions = [];
var _minimumLoggingLevel = LoggingLevel.Debug;

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<AddTool>()
    .WithTools<AnnotatedMessageTool>()
    .WithTools<EchoTool>()
    .WithTools<LongRunningTool>()
    .WithTools<PrintEnvTool>()
    .WithTools<SampleLlmTool>()
    .WithTools<TinyImageTool>()
    .WithPrompts<ComplexPromptType>()
    .WithPrompts<SimplePromptType>()
    .WithResources<SimpleResourceType>()
    .WithSubscribeToResourcesHandler(async (ctx, ct) =>
    {
        var uri = ctx.Params?.Uri;

        if (uri is not null)
        {
            subscriptions.Add(uri);

            await ctx.Server.SampleAsync([
                new ChatMessage(ChatRole.System, "You are a helpful test server"),
                new ChatMessage(ChatRole.User, $"Resource {uri}, context: A new subscription was started"),
            ],
            options: new ChatOptions
            {
                MaxOutputTokens = 100,
                Temperature = 0.7f,
            },
            cancellationToken: ct);
        }

        return new EmptyResult();
    })
    .WithUnsubscribeFromResourcesHandler(async (ctx, ct) =>
    {
        var uri = ctx.Params?.Uri;
        if (uri is not null)
        {
            subscriptions.Remove(uri);
        }
        return new EmptyResult();
    })
    .WithCompleteHandler(async (ctx, ct) =>
    {
        var exampleCompletions = new Dictionary<string, IEnumerable<string>>
        {
            { "style", ["casual", "formal", "technical", "friendly"] },
            { "temperature", ["0", "0.5", "0.7", "1.0"] },
            { "resourceId", ["1", "2", "3", "4", "5"] }
        };

        if (ctx.Params is not { } @params)
        {
            throw new NotSupportedException($"Params are required.");
        }

        var @ref = @params.Ref;
        var argument = @params.Argument;

        if (@ref is ResourceTemplateReference rtr)
        {
            var resourceId = rtr.Uri?.Split("/").Last();

            if (resourceId is null)
            {
                return new CompleteResult();
            }

            var values = exampleCompletions["resourceId"].Where(id => id.StartsWith(argument.Value));

            return new CompleteResult
            {
                Completion = new Completion { Values = [.. values], HasMore = false, Total = values.Count() }
            };
        }

        if (@ref is PromptReference pr)
        {
            if (!exampleCompletions.TryGetValue(argument.Name, out IEnumerable<string>? value))
            {
                throw new NotSupportedException($"Unknown argument name: {argument.Name}");
            }

            var values = value.Where(value => value.StartsWith(argument.Value));
            return new CompleteResult
            {
                Completion = new Completion { Values = [.. values], HasMore = false, Total = values.Count() }
            };
        }

        throw new NotSupportedException($"Unknown reference type: {@ref.Type}");
    })
    .WithSetLoggingLevelHandler(async (ctx, ct) =>
    {
        if (ctx.Params?.Level is null)
        {
            throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams);
        }

        _minimumLoggingLevel = ctx.Params.Level;

        await ctx.Server.SendNotificationAsync("notifications/message", new
        {
            Level = "debug",
            Logger = "test-server",
            Data = $"Logging level set to {_minimumLoggingLevel}",
        }, cancellationToken: ct);

        return new EmptyResult();
    });

ResourceBuilder resource = ResourceBuilder.CreateDefault().AddService("everything-server");
builder.Services.AddOpenTelemetry()
    .WithTracing(b => b.AddSource("*").AddHttpClientInstrumentation().SetResourceBuilder(resource))
    .WithMetrics(b => b.AddMeter("*").AddHttpClientInstrumentation().SetResourceBuilder(resource))
    .WithLogging(b => b.SetResourceBuilder(resource))
    .UseOtlpExporter();

builder.Services.AddSingleton(subscriptions);
builder.Services.AddHostedService<SubscriptionMessageSender>();
builder.Services.AddHostedService<LoggingUpdateMessageSender>();

builder.Services.AddSingleton<Func<LoggingLevel>>(_ => () => _minimumLoggingLevel);

await builder.Build().RunAsync();

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Provides a base class for all request parameters.
/// </summary>
/// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public abstract class RequestParams
{
    /// <summary>Prevent external derivations.</summary>
    private protected RequestParams()
    {
    }

    /// <summary>
    /// Gets or sets metadata reserved by MCP for protocol-level metadata.
    /// </summary>
    /// <remarks>
    /// Implementations must not make assumptions about its contents.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonObject? Meta { get; set; }

    /// <summary>
    /// Gets or sets an opaque token that will be attached to any subsequent progress notifications.
    /// </summary>
    [JsonIgnore]
    public ProgressToken? ProgressToken
    {
        get
        {
            if (Meta?["progressToken"] is JsonValue progressToken)
            {
                if (progressToken.TryGetValue(out string? stringValue))
                {
                    return new ProgressToken(stringValue);
                }

                if (progressToken.TryGetValue(out long longValue))
                {
                    return new ProgressToken(longValue);
                }
            }

            return null;
        }
        set
        {
            if (value is null)
            {
                Meta?.Remove("progressToken");
            }
            else
            {
                (Meta ??= [])["progressToken"] = value.Value.Token switch
                {
                    string s => JsonValue.Create(s),
                    long l => JsonValue.Create(l),
                    _ => throw new InvalidOperationException("ProgressToken must be a string or a long.")
                };
            }
        }
    }
}

using Microsoft.Extensions.Logging;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Web;

var serverUrl = "http://localhost:7071/";

Console.WriteLine("Protected MCP Client");
Console.WriteLine($"Connecting to weather server at {serverUrl}...");
Console.WriteLine();

// We can customize a shared HttpClient with a custom handler if desired
var sharedHandler = new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(2),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1)
};
var httpClient = new HttpClient(sharedHandler);

var consoleLoggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddConsole();
});

var transport = new SseClientTransport(new()
{
    Endpoint = new Uri(serverUrl),
    Name = "Secure Weather Client",
    OAuth = new()
    {
        ClientName = "ProtectedMcpClient",
        RedirectUri = new Uri("http://localhost:1179/callback"),
        AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
    }
}, httpClient, consoleLoggerFactory);

var client = await McpClientFactory.CreateAsync(transport, loggerFactory: consoleLoggerFactory);

var tools = await client.ListToolsAsync();
if (tools.Count == 0)
{
    Console.WriteLine("No tools available on the server.");
    return;
}

Console.WriteLine($"Found {tools.Count} tools on the server.");
Console.WriteLine();

if (tools.Any(t => t.Name == "get_alerts"))
{
    Console.WriteLine("Calling get_alerts tool...");

    var result = await client.CallToolAsync(
        "get_alerts",
        new Dictionary<string, object?> { { "state", "WA" } }
    );

    Console.WriteLine("Result: " + ((TextContentBlock)result.Content[0]).Text);
    Console.WriteLine();
}

/// Handles the OAuth authorization URL by starting a local HTTP server and opening a browser.
/// This implementation demonstrates how SDK consumers can provide their own authorization flow.
///
/// <param name="authorizationUrl">The authorization URL to open in the browser.</param>
/// <param name="redirectUri">The redirect URI where the authorization code will be sent.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The authorization code extracted from the callback, or null if the operation failed.</returns>
static async Task<string?> HandleAuthorizationUrlAsync(Uri authorizationUrl, Uri redirectUri, CancellationToken cancellationToken)
{
    Console.WriteLine("Starting OAuth authorization flow...");
    Console.WriteLine($"Opening browser to: {authorizationUrl}");

    var listenerPrefix = redirectUri.GetLeftPart(UriPartial.Authority);
    if (!listenerPrefix.EndsWith("/")) listenerPrefix += "/";

    using var listener = new HttpListener();
    listener.Prefixes.Add(listenerPrefix);

    try
    {
        listener.Start();
        Console.WriteLine($"Listening for OAuth callback on: {listenerPrefix}");

        OpenBrowser(authorizationUrl);

        var context = await listener.GetContextAsync();
        var query = HttpUtility.ParseQueryString(context.Request.Url?.Query ?? string.Empty);
        var code = query["code"];
        var error = query["error"];

        string responseHtml = "
Authentication complete

You can close this window now.
";
        byte[] buffer = Encoding.UTF8.GetBytes(responseHtml);
        context.Response.ContentLength64 = buffer.Length;
        context.Response.ContentType = "text/html";
        context.Response.OutputStream.Write(buffer, 0, buffer.Length);
        context.Response.Close();

        if (!string.IsNullOrEmpty(error))
        {
            Console.WriteLine($"Auth error: {error}");
            return null;
        }

        if (string.IsNullOrEmpty(code))
        {
            Console.WriteLine("No authorization code received");
            return null;
        }

        Console.WriteLine("Authorization code received successfully.");
        return code;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error getting auth code: {ex.Message}");
        return null;
    }
    finally
    {
        if (listener.IsListening) listener.Stop();
    }
}

///
/// Opens the specified URL in the default browser.
///
/// <param name="url">The URL to open.</param>
static void OpenBrowser(Uri url)
{
    try
    {
        var psi = new ProcessStartInfo
        {
            FileName = url.ToString(),
            UseShellExecute = true
        };
        Process.Start(psi);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error opening browser. {ex.Message}");
        Console.WriteLine($"Please manually open this URL: {url}");
    }
}

namespace ModelContextProtocol;

internal static class SynchronizationExtensions
{
    /// <summary>
    /// Asynchronously acquires a lock on the semaphore and returns a disposable object that releases the lock when disposed.
    /// </summary>
    /// <param name="semaphore">The semaphore to acquire a lock on.</param>
    /// <param name="cancellationToken">A cancellation token to observe while waiting for the semaphore.</param>
    /// <returns>A disposable <see cref="Releaser"/> that releases the semaphore when disposed.</returns>
    /// <remarks>
    /// This extension method provides a convenient pattern for using a semaphore in asynchronous code,
    /// similar to how the `lock` statement is used in synchronous code.
    /// </remarks>
    /// <exception cref="OperationCanceledException">The <paramref name="cancellationToken"/> was canceled.</exception>
    public static async ValueTask<Releaser> LockAsync(this SemaphoreSlim semaphore, CancellationToken cancellationToken = default)
    {
        await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
        return new(semaphore);
    }

    /// <summary>
    /// A disposable struct that releases a semaphore when disposed.
    /// </summary>
    /// <remarks>
    /// This struct is used with the <see cref="LockAsync"/> extension method to provide
    /// a using-pattern for semaphore locking, similar to lock statements.
    /// </remarks>
    public readonly struct Releaser(SemaphoreSlim semaphore) : IDisposable
    {
        /// <summary>
        /// Releases the semaphore.
        /// </summary>
        /// <remarks>
        /// This method is called automatically when the <see cref="Releaser"/> goes out of scope
        /// in a using statement or expression.
        /// </remarks>
        public void Dispose() => semaphore.Release();
    }
}

using Microsoft.Extensions.Logging;
using ModelContextProtocol.Client;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Provides an <see cref="IClientTransport"/> implemented around a pair of input/output streams.
/// </summary>
/// <remarks>
/// This transport is useful for scenarios where you already have established streams for communication,
/// such as custom network protocols, pipe connections, or for testing purposes. It works with any
/// readable and writable streams.
/// </remarks>
public sealed class StreamClientTransport : IClientTransport
{
    private readonly Stream _serverInput;
    private readonly Stream _serverOutput;
    private readonly ILoggerFactory? _loggerFactory;

    /// <summary>
    /// Initializes a new instance of the <see cref="StreamClientTransport"/> class.
    /// </summary>
    /// <param name="serverInput">
    /// The stream representing the connected server's input.
    /// Writes to this stream will be sent to the server.
    /// </param>
    /// <param name="serverOutput">
    /// The stream representing the connected server's output.
    /// Reads from this stream will receive messages from the server.
    /// </param>
    /// <param name="loggerFactory">A logger factory for creating loggers.</param>
    public StreamClientTransport(
        Stream serverInput, Stream serverOutput, ILoggerFactory? loggerFactory = null)
    {
        Throw.IfNull(serverInput);
        Throw.IfNull(serverOutput);

        _serverInput = serverInput;
        _serverOutput = serverOutput;
        _loggerFactory = loggerFactory;
    }

    /// <inheritdoc />
    public string Name => "in-memory-stream";

    /// <inheritdoc />
    public Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default)
    {
        return Task.FromResult<ITransport>(new StreamClientSessionTransport(
            _serverInput,
            _serverOutput,
            encoding: null,
            "Client (stream)",
            _loggerFactory));
    }
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET
namespace System.Diagnostics.CodeAnalysis
{
    /// Specifies that null is allowed as an input even if the corresponding type disallows it.
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
    internal sealed class AllowNullAttribute : Attribute;

    /// Specifies that null is disallowed as an input even if the corresponding type allows it.
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
    internal sealed class DisallowNullAttribute : Attribute;

    /// Specifies that an output may be null even if the corresponding type disallows it.
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
    internal sealed class MaybeNullAttribute : Attribute;

    /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns.
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
    internal sealed class NotNullAttribute : Attribute;

    /// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.
    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
    internal sealed class MaybeNullWhenAttribute : Attribute
    {
        /// Initializes the attribute with the specified return value condition.
        /// <param name="returnValue">
        /// The return value condition. If the method returns this value, the associated parameter may be null.
        /// </param>
        public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;

        /// Gets the return value condition.
        public bool ReturnValue { get; }
    }

    /// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.
    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
    internal sealed class NotNullWhenAttribute : Attribute
    {
        /// Initializes the attribute with the specified return value condition.
        /// <param name="returnValue">
        /// The return value condition. If the method returns this value, the associated parameter will not be null.
        /// </param>
        public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;

        /// Gets the return value condition.
        public bool ReturnValue { get; }
    }

    /// Specifies that the output will be non-null if the named parameter is non-null.
    [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
    internal sealed class NotNullIfNotNullAttribute : Attribute
    {
        /// Initializes the attribute with the associated parameter name.
        /// <param name="parameterName">
        /// The associated parameter name.  The output will be non-null if the argument to the parameter specified is non-null.
        /// </param>
        public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;

        /// Gets the associated parameter name.
        public string ParameterName { get; }
    }

    /// Applied to a method that will never return under any circumstance.
    [AttributeUsage(AttributeTargets.Method, Inherited = false)]
    internal sealed class DoesNotReturnAttribute : Attribute;

    /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value.
    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
    internal sealed class DoesNotReturnIfAttribute : Attribute
    {
        /// Initializes the attribute with the specified parameter value.
        /// <param name="parameterValue">
        /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
        /// the associated parameter matches this value.
        /// </param>
        public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;

        /// Gets the condition parameter value.
        public bool ParameterValue { get; }
    }

    /// Specifies that the method or property will ensure that the listed field and property members have not-null values.
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
    internal sealed class MemberNotNullAttribute : Attribute
    {
        /// Initializes the attribute with a field or property member.
        /// <param name="member">
        /// The field or property member that is promised to be not-null.
        /// </param>
        public MemberNotNullAttribute(string member) => Members = [member];

        /// Initializes the attribute with the list of field and property members.
        /// <param name="members">
        /// The list of field and property members that are promised to be not-null.
        /// </param>
        public MemberNotNullAttribute(params string[] members) => Members = members;

        /// Gets field or property member names.
        public string[] Members { get; }
    }

    /// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition.
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
    internal sealed class MemberNotNullWhenAttribute : Attribute
    {
        /// Initializes the attribute with the specified return value condition and a field or property member.
        /// <param name="returnValue">
        /// The return value condition. If the method returns this value, the associated field or property member will not be null.
        /// </param>
        /// <param name="member">
        /// The field or property member that is promised to be not-null.
        /// </param>
        public MemberNotNullWhenAttribute(bool returnValue, string member)
        {
            ReturnValue = returnValue;
            Members = [member];
        }

        /// Initializes the attribute with the specified return value condition and list of field and property members.
        /// <param name="returnValue">
        /// The return value condition. If the method returns this value, the associated field and property members will not be null.
        /// </param>
        /// <param name="members">
        /// The list of field and property members that are promised to be not-null.
        /// </param>
        public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
        {
            ReturnValue = returnValue;
            Members = members;
        }

        /// Gets the return value condition.
        public bool ReturnValue { get; }

        /// Gets field or property member names.
        public string[] Members { get; }
    }
}
#endif

using Microsoft.Extensions.AI;
using ModelContextProtocol.Authentication;
using ModelContextProtocol.Protocol;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;

namespace ModelContextProtocol;

/// <summary>Provides a collection of utility methods for working with JSON data in the context of MCP.</summary>
public static partial class McpJsonUtilities
{
    /// <summary>
    /// Gets the <see cref="JsonSerializerOptions"/> singleton used as the default in JSON serialization operations.
    /// </summary>
    /// <remarks>
    /// <para>
    /// For Native AOT or applications disabling <see cref="JsonSerializer.IsReflectionEnabledByDefault"/>, this instance
    /// includes source generated contracts for all common exchange types contained in the ModelContextProtocol library.
    /// </para>
    /// <para>
    /// It additionally turns on the following settings:
    /// <list type="number">
    /// <item>Enables <see cref="JsonSerializerDefaults.Web"/> defaults.</item>
    /// <item>Enables <see cref="JsonIgnoreCondition.WhenWritingNull"/> as the default ignore condition for properties.</item>
    /// <item>Enables <see cref="JsonNumberHandling.AllowReadingFromString"/> as the default number handling for number types.</item>
    /// </list>
    /// </para>
    /// </remarks>
    public static JsonSerializerOptions DefaultOptions { get; } = CreateDefaultOptions();

    /// <summary>
    /// Creates default options to use for MCP-related serialization.
    /// </summary>
    /// <returns>The configured options.</returns>
    [UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050:RequiresDynamicCode", Justification = "Converter is guarded by IsReflectionEnabledByDefault check.")]
    [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access", Justification = "Converter is guarded by IsReflectionEnabledByDefault check.")]
    private static JsonSerializerOptions CreateDefaultOptions()
    {
        // Copy the configuration from the source generated context.
        JsonSerializerOptions options = new(JsonContext.Default.Options);

        // Chain with all supported types from MEAI.
        options.TypeInfoResolverChain.Add(AIJsonUtilities.DefaultOptions.TypeInfoResolver!);

        // Add a converter for user-defined enums, if reflection is enabled by default.
        if (JsonSerializer.IsReflectionEnabledByDefault)
        {
            options.Converters.Add(new CustomizableJsonStringEnumConverter());
        }

        options.MakeReadOnly();
        return options;
    }

    internal static JsonTypeInfo<T> GetTypeInfo<T>(this JsonSerializerOptions options) =>
        (JsonTypeInfo<T>)options.GetTypeInfo(typeof(T));

    internal static JsonElement DefaultMcpToolSchema { get; } = ParseJsonElement("""{"type":"object"}"""u8);
    internal static object? AsObject(this JsonElement element) => element.ValueKind is JsonValueKind.Null ? null : element;

    internal static bool IsValidMcpToolSchema(JsonElement element)
    {
        if (element.ValueKind is not JsonValueKind.Object)
        {
            return false;
        }

        foreach (JsonProperty property in element.EnumerateObject())
        {
            if (property.NameEquals("type"))
            {
                if (property.Value.ValueKind is not JsonValueKind.String ||
                    !property.Value.ValueEquals("object"))
                {
                    return false;
                }

                return true; // No need to check other properties
            }
        }

        return false; // No type keyword found.
    }

    // Keep in sync with CreateDefaultOptions above.
    [JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        NumberHandling = JsonNumberHandling.AllowReadingFromString)]

    // JSON-RPC
    [JsonSerializable(typeof(JsonRpcMessage))]
    [JsonSerializable(typeof(JsonRpcMessage[]))]
    [JsonSerializable(typeof(JsonRpcRequest))]
    [JsonSerializable(typeof(JsonRpcNotification))]
    [JsonSerializable(typeof(JsonRpcResponse))]
    [JsonSerializable(typeof(JsonRpcError))]

    // MCP Notification Params
    [JsonSerializable(typeof(CancelledNotificationParams))]
    [JsonSerializable(typeof(InitializedNotificationParams))]
    [JsonSerializable(typeof(LoggingMessageNotificationParams))]
    [JsonSerializable(typeof(ProgressNotificationParams))]
    [JsonSerializable(typeof(PromptListChangedNotificationParams))]
    [JsonSerializable(typeof(ResourceListChangedNotificationParams))]
    [JsonSerializable(typeof(ResourceUpdatedNotificationParams))]
    [JsonSerializable(typeof(RootsListChangedNotificationParams))]
    [JsonSerializable(typeof(ToolListChangedNotificationParams))]

    // MCP Request Params / Results
    [JsonSerializable(typeof(CallToolRequestParams))]
    [JsonSerializable(typeof(CallToolResult))]
    [JsonSerializable(typeof(CompleteRequestParams))]
    [JsonSerializable(typeof(CompleteResult))]
    [JsonSerializable(typeof(CreateMessageRequestParams))]
    [JsonSerializable(typeof(CreateMessageResult))]
    [JsonSerializable(typeof(ElicitRequestParams))]
    [JsonSerializable(typeof(ElicitResult))]
    [JsonSerializable(typeof(EmptyResult))]
    [JsonSerializable(typeof(GetPromptRequestParams))]
    [JsonSerializable(typeof(GetPromptResult))]
    [JsonSerializable(typeof(InitializeRequestParams))]
    [JsonSerializable(typeof(InitializeResult))]
    [JsonSerializable(typeof(ListPromptsRequestParams))]
    [JsonSerializable(typeof(ListPromptsResult))]
    [JsonSerializable(typeof(ListResourcesRequestParams))]
    [JsonSerializable(typeof(ListResourcesResult))]
    [JsonSerializable(typeof(ListResourceTemplatesRequestParams))]
    [JsonSerializable(typeof(ListResourceTemplatesResult))]
    [JsonSerializable(typeof(ListRootsRequestParams))]
    [JsonSerializable(typeof(ListRootsResult))]
    [JsonSerializable(typeof(ListToolsRequestParams))]
    [JsonSerializable(typeof(ListToolsResult))]
    [JsonSerializable(typeof(PingResult))]
    [JsonSerializable(typeof(ReadResourceRequestParams))]
    [JsonSerializable(typeof(ReadResourceResult))]
    [JsonSerializable(typeof(SetLevelRequestParams))]
    [JsonSerializable(typeof(SubscribeRequestParams))]
    [JsonSerializable(typeof(UnsubscribeRequestParams))]

    // MCP Content
    [JsonSerializable(typeof(ContentBlock))]
    [JsonSerializable(typeof(TextContentBlock))]
    [JsonSerializable(typeof(ImageContentBlock))]
    [JsonSerializable(typeof(AudioContentBlock))]
    [JsonSerializable(typeof(EmbeddedResourceBlock))]
    [JsonSerializable(typeof(ResourceLinkBlock))]
    [JsonSerializable(typeof(PromptReference))]
    [JsonSerializable(typeof(ResourceTemplateReference))]
    [JsonSerializable(typeof(BlobResourceContents))]
    [JsonSerializable(typeof(TextResourceContents))]

    // Other MCP Types
    [JsonSerializable(typeof(IReadOnlyDictionary<string, object>))]
    [JsonSerializable(typeof(ProgressToken))]

    [JsonSerializable(typeof(ProtectedResourceMetadata))]
    [JsonSerializable(typeof(AuthorizationServerMetadata))]
    [JsonSerializable(typeof(TokenContainer))]
    [JsonSerializable(typeof(DynamicClientRegistrationRequest))]
    [JsonSerializable(typeof(DynamicClientRegistrationResponse))]

    // Primitive types for use in consuming AIFunctions
    [JsonSerializable(typeof(string))]
    [JsonSerializable(typeof(byte))]
    [JsonSerializable(typeof(byte?))]
    [JsonSerializable(typeof(sbyte))]
    [JsonSerializable(typeof(sbyte?))]
    [JsonSerializable(typeof(ushort))]
    [JsonSerializable(typeof(ushort?))]
    [JsonSerializable(typeof(short))]
    [JsonSerializable(typeof(short?))]
    [JsonSerializable(typeof(uint))]
    [JsonSerializable(typeof(uint?))]
    [JsonSerializable(typeof(int))]
    [JsonSerializable(typeof(int?))]
    [JsonSerializable(typeof(ulong))]
    [JsonSerializable(typeof(ulong?))]
    [JsonSerializable(typeof(long))]
    [JsonSerializable(typeof(long?))]
    [JsonSerializable(typeof(nuint))]
    [JsonSerializable(typeof(nuint?))]
    [JsonSerializable(typeof(nint))]
    [JsonSerializable(typeof(nint?))]
    [JsonSerializable(typeof(bool))]
    [JsonSerializable(typeof(bool?))]
    [JsonSerializable(typeof(char))]
    [JsonSerializable(typeof(char?))]
    [JsonSerializable(typeof(float))]
    [JsonSerializable(typeof(float?))]
    [JsonSerializable(typeof(double))]
    [JsonSerializable(typeof(double?))]
    [JsonSerializable(typeof(decimal))]
    [JsonSerializable(typeof(decimal?))]
    [JsonSerializable(typeof(Guid))]
    [JsonSerializable(typeof(Guid?))]
    [JsonSerializable(typeof(Uri))]
    [JsonSerializable(typeof(Version))]
    [JsonSerializable(typeof(TimeSpan))]
    [JsonSerializable(typeof(TimeSpan?))]
    [JsonSerializable(typeof(DateTime))]
    [JsonSerializable(typeof(DateTime?))]
    [JsonSerializable(typeof(DateTimeOffset))]
    [JsonSerializable(typeof(DateTimeOffset?))]
#if NET
    [JsonSerializable(typeof(DateOnly))]
    [JsonSerializable(typeof(DateOnly?))]
    [JsonSerializable(typeof(TimeOnly))]
    [JsonSerializable(typeof(TimeOnly?))]
    [JsonSerializable(typeof(Half))]
    [JsonSerializable(typeof(Half?))]
    [JsonSerializable(typeof(Int128))]
    [JsonSerializable(typeof(Int128?))]
    [JsonSerializable(typeof(UInt128))]
    [JsonSerializable(typeof(UInt128?))]
#endif

    [ExcludeFromCodeCoverage]
    internal sealed partial class JsonContext : JsonSerializerContext;

    private static JsonElement ParseJsonElement(ReadOnlySpan<byte> utf8Json)
    {
        Utf8JsonReader reader = new(utf8Json);
        return JsonElement.ParseValue(ref reader);
    }
}

using ModelContextProtocol.Server;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the server's capability to provide predefined prompt templates that clients can use.
/// </summary>
/// <remarks>
/// <para>
/// The prompts capability allows a server to expose a collection of predefined prompt templates that clients
/// can discover and use. These prompts can be static (defined in the <see cref="PromptCollection"/>) or
/// dynamically generated through handlers.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class PromptsCapability
{
    /// <summary>
    /// Gets or sets whether this server supports notifications for changes to the prompt list.
    /// </summary>
    /// <remarks>
    /// When set to <see langword="true"/>, the server will send notifications using
    /// <see cref="NotificationMethods.PromptListChangedNotification"/> when prompts are added,
    /// removed, or modified. Clients can register handlers for these notifications to
    /// refresh their prompt cache. This capability enables clients to stay synchronized with server-side changes
    /// to available prompts.
    /// </remarks>
    [JsonPropertyName("listChanged")]
    public bool? ListChanged { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.PromptsList"/> requests.
    /// </summary>
    /// <remarks>
    /// This handler is invoked when a client requests a list of available prompts from the server
    /// via a <see cref="RequestMethods.PromptsList"/> request. Results from this handler are returned
    /// along with any prompts defined in <see cref="PromptCollection"/>.
    /// </remarks>
    [JsonIgnore]
    public Func<RequestContext<ListPromptsRequestParams>, CancellationToken, ValueTask<ListPromptsResult>>? ListPromptsHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.PromptsGet"/> requests.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This handler is invoked when a client requests details for a specific prompt by name and provides arguments
    /// for the prompt if needed. The handler receives the request context containing the prompt name and any arguments,
    /// and should return a <see cref="GetPromptResult"/> with the prompt messages and other details.
    /// </para>
    /// <para>
    /// This handler will be invoked if the requested prompt name is not found in the <see cref="PromptCollection"/>,
    /// allowing for dynamic prompt generation or retrieval from external sources.
    /// </para>
    /// </remarks>
    [JsonIgnore]
    public Func<RequestContext<GetPromptRequestParams>, CancellationToken, ValueTask<GetPromptResult>>? GetPromptHandler { get; set; }

    /// <summary>
    /// Gets or sets a collection of prompts that will be served by the server.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The <see cref="PromptCollection"/> contains the predefined prompts that clients can request from the server.
    /// This collection works in conjunction with <see cref="ListPromptsHandler"/> and <see cref="GetPromptHandler"/>
    /// when those are provided:
    /// </para>
    /// <para>
    /// - For <see cref="RequestMethods.PromptsList"/> requests: The server returns all prompts from this collection
    ///   plus any additional prompts provided by the <see cref="ListPromptsHandler"/> if it's set.
    /// </para>
    /// <para>
    /// - For <see cref="RequestMethods.PromptsGet"/> requests: The server first checks this collection for the requested prompt.
    ///   If not found, it will invoke the <see cref="GetPromptHandler"/> as a fallback if one is set.
    /// </para>
    /// </remarks>
    [JsonIgnore]
    public McpServerPrimitiveCollection<McpServerPrompt>? PromptCollection { get; set; }
}

using ModelContextProtocol.Authentication;

namespace ModelContextProtocol.Client;

/// <summary>
/// Provides options for configuring <see cref="SseClientTransport"/> instances.
/// </summary>
public sealed class SseClientTransportOptions
{
    /// <summary>
    /// Gets or sets the base address of the server for SSE connections.
    /// </summary>
    public required Uri Endpoint
    {
        get;
        set
        {
            if (value is null)
            {
                throw new ArgumentNullException(nameof(value), "Endpoint cannot be null.");
            }
            if (!value.IsAbsoluteUri)
            {
                throw new ArgumentException("Endpoint must be an absolute URI.", nameof(value));
            }
            if (value.Scheme != Uri.UriSchemeHttp && value.Scheme != Uri.UriSchemeHttps)
            {
                throw new ArgumentException("Endpoint must use HTTP or HTTPS scheme.", nameof(value));
            }

            field = value;
        }
    }

    /// <summary>
    /// Gets or sets the transport mode to use for the connection. Defaults to <see cref="HttpTransportMode.AutoDetect"/>.
    /// </summary>
    /// <remarks>
    /// <para>
    /// When set to <see cref="HttpTransportMode.AutoDetect"/> (the default), the client will first attempt to use
    /// Streamable HTTP transport and automatically fall back to SSE transport if the server doesn't support it.
    /// </para>
    /// <para>
    /// <see href="https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http">Streamable HTTP transport specification</see>.
    /// <see href="https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse">HTTP with SSE transport specification</see>.
    /// </para>
    /// </remarks>
    public HttpTransportMode TransportMode { get; set; } = HttpTransportMode.AutoDetect;

    /// <summary>
    /// Gets or sets a transport identifier used for logging purposes.
    /// </summary>
    public string? Name { get; set; }

    /// <summary>
    /// Gets or sets a timeout used to establish the initial connection to the SSE server. Defaults to 30 seconds.
    /// </summary>
    /// <remarks>
    /// This timeout controls how long the client waits for:
    /// <list type="bullet">
    ///   <item><description>The initial HTTP connection to be established with the SSE server</description></item>
    ///   <item><description>The endpoint event to be received, which indicates the message endpoint URL</description></item>
    /// </list>
    /// If the timeout expires before the connection is established, a <see cref="TimeoutException"/> will be thrown.
    /// </remarks>
    public TimeSpan ConnectionTimeout { get; set; } = TimeSpan.FromSeconds(30);

    /// <summary>
    /// Gets custom HTTP headers to include in requests to the SSE server.
    /// </summary>
    /// <remarks>
    /// Use this property to specify custom HTTP headers that should be sent with each request to the server.
    /// </remarks>
    public IDictionary<string, string>? AdditionalHeaders { get; set; }

    /// <summary>
    /// Gets sor sets the authorization provider to use for authentication.
    /// </summary>
    public ClientOAuthOptions? OAuth { get; set; }
}

using ModelContextProtocol.Protocol;
using System.Diagnostics;

namespace ModelContextProtocol.Server;

internal sealed class DestinationBoundMcpServer(McpServer server, ITransport? transport) : IMcpServer
{
    public string EndpointName => server.EndpointName;
    public string? SessionId => transport?.SessionId ?? server.SessionId;
    public ClientCapabilities? ClientCapabilities => server.ClientCapabilities;
    public Implementation? ClientInfo => server.ClientInfo;
    public McpServerOptions ServerOptions => server.ServerOptions;
    public IServiceProvider? Services => server.Services;
    public LoggingLevel? LoggingLevel => server.LoggingLevel;

    public ValueTask DisposeAsync() => server.DisposeAsync();

    public IAsyncDisposable RegisterNotificationHandler(string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler) => server.RegisterNotificationHandler(method, handler);

    // This will throw because the server must already be running for this class to be constructed, but it should give us a good Exception message.
    public Task RunAsync(CancellationToken cancellationToken) => server.RunAsync(cancellationToken);

    public Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
    {
        Debug.Assert(message.RelatedTransport is null);
        message.RelatedTransport = transport;
        return server.SendMessageAsync(message, cancellationToken);
    }

    public Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken = default)
    {
        Debug.Assert(request.RelatedTransport is null);
        request.RelatedTransport = transport;
        return server.SendRequestAsync(request, cancellationToken);
    }
}

using ModelContextProtocol.Server;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the resources capability configuration.
/// </summary>
/// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class ResourcesCapability
{
    /// <summary>
    /// Gets or sets whether this server supports subscribing to resource updates.
    /// </summary>
    [JsonPropertyName("subscribe")]
    public bool? Subscribe { get; set; }

    /// <summary>
    /// Gets or sets whether this server supports notifications for changes to the resource list.
    /// </summary>
    /// <remarks>
    /// When set to <see langword="true"/>, the server will send notifications using
    /// <see cref="NotificationMethods.ResourceListChangedNotification"/> when resources are added,
    /// removed, or modified. Clients can register handlers for these notifications to
    /// refresh their resource cache.
    /// </remarks>
    [JsonPropertyName("listChanged")]
    public bool? ListChanged { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ResourcesTemplatesList"/> requests.
    /// </summary>
    /// <remarks>
    /// This handler is called when clients request available resource templates that can be used
    /// to create resources within the Model Context Protocol server.
    /// Resource templates define the structure and URI patterns for resources accessible in the system,
    /// allowing clients to discover available resource types and their access patterns.
    /// </remarks>
    [JsonIgnore]
    public Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, ValueTask<ListResourceTemplatesResult>>? ListResourceTemplatesHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ResourcesList"/> requests.
    /// </summary>
    /// <remarks>
    /// This handler responds to client requests for available resources and returns information about resources accessible through the server.
    /// The implementation should return a <see cref="ListResourcesResult"/> with the matching resources.
    /// </remarks>
    [JsonIgnore]
    public Func<RequestContext<ListResourcesRequestParams>, CancellationToken, ValueTask<ListResourcesResult>>? ListResourcesHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ResourcesRead"/> requests.
    /// </summary>
    /// <remarks>
    /// This handler is responsible for retrieving the content of a specific resource identified by its URI in the Model Context Protocol.
    /// When a client sends a resources/read request, this handler is invoked with the resource URI.
    /// The handler should implement logic to locate and retrieve the requested resource, then return
    /// its contents in a ReadResourceResult object.
    /// </remarks>
    [JsonIgnore]
    public Func<RequestContext<ReadResourceRequestParams>, CancellationToken, ValueTask<ReadResourceResult>>? ReadResourceHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ResourcesSubscribe"/> requests.
    /// </summary>
    /// <remarks>
    /// When a client sends a <see cref="RequestMethods.ResourcesSubscribe"/> request, this handler is invoked with the resource URI
    /// to be subscribed to. The implementation should register the client's interest in receiving updates
    /// for the specified resource.
    /// Subscriptions allow clients to receive real-time notifications when resources change, without
    /// requiring polling.
    /// </remarks>
    [JsonIgnore]
    public Func<RequestContext<SubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>>? SubscribeToResourcesHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ResourcesUnsubscribe"/> requests.
    /// </summary>
    /// <remarks>
    /// When a client sends a <see cref="RequestMethods.ResourcesUnsubscribe"/> request, this handler is invoked with the resource URI
    /// to be unsubscribed from. The implementation should remove the client's registration for receiving updates
    /// about the specified resource.
    /// </remarks>
    [JsonIgnore]
    public Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>>? UnsubscribeFromResourcesHandler { get; set; }

    /// <summary>
    /// Gets or sets a collection of resources served by the server.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Resources specified via <see cref="ResourceCollection"/> augment the <see cref="ListResourcesHandler"/>, <see cref="ListResourceTemplatesHandler"/>
    /// and <see cref="ReadResourceHandler"/> handlers, if provided. Resources with template expressions in their URI templates are considered resource templates
    /// and are listed via ListResourceTemplate, whereas resources without template parameters are considered static resources and are listed with ListResources.
    /// </para>
    /// <para>
    /// ReadResource requests will first check the <see cref="ResourceCollection"/> for the exact resource being requested. If no match is found, they'll proceed to
    /// try to match the resource against each resource template in <see cref="ResourceCollection"/>. If no match is still found, the request will fall back to
    /// any handler registered for <see cref="ReadResourceHandler"/>.
    /// </para>
    /// </remarks>
    [JsonIgnore]
    public McpServerResourceCollection? ResourceCollection { get; set; }
}

using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Client;

///
/// Represents a named resource that can be retrieved from an MCP server.
///
/// <remarks>
/// <para>
/// This class provides a client-side wrapper around a resource defined on an MCP server. It allows
/// retrieving the resource's content by sending a request to the server with the resource's URI.
/// Instances of this class are typically obtained by calling <see cref="McpClientExtensions.ListResourcesAsync"/>
/// or <see cref="McpClientExtensions.EnumerateResourcesAsync"/>.
/// </para>
/// </remarks>
public sealed class McpClientResource
{
    private readonly IMcpClient _client;

    internal McpClientResource(IMcpClient client, Resource resource)
    {
        _client = client;
        ProtocolResource = resource;
    }

    /// Gets the underlying protocol <see cref="Resource"/> type for this instance.
    /// <remarks>
    /// <para>
    /// This property provides direct access to the underlying protocol representation of the resource,
    /// which can be useful for advanced scenarios or when implementing custom MCP client extensions.
    /// </para>
    /// <para>
    /// For most common use cases, you can use the more convenient <see cref="Name"/> and
    /// <see cref="Description"/> properties instead of accessing the <see cref="ProtocolResource"/> directly.
    /// </para>
    /// </remarks>
    public Resource ProtocolResource { get; }

    /// Gets the URI of the resource.
    public string Uri => ProtocolResource.Uri;

    /// Gets the name of the resource.
    public string Name => ProtocolResource.Name;

    /// Gets the title of the resource.
    public string? Title => ProtocolResource.Title;

    /// Gets a description of the resource.
    public string? Description => ProtocolResource.Description;

    /// Gets a media (MIME) type of the resource.
    public string? MimeType => ProtocolResource.MimeType;

    ///
    /// Gets this resource's content by sending a request to the server.
    ///
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A <see cref="ValueTask{ReadResourceResult}"/> containing the resource's result with content and messages.</returns>
    /// <remarks>
    /// <para>
    /// This is a convenience method that internally calls <see cref="McpClientExtensions.ReadResourceAsync(IMcpClient, string, CancellationToken)"/>.
    /// </para>
    /// </remarks>
    public ValueTask<ReadResourceResult> ReadAsync(
        CancellationToken cancellationToken = default) =>
        _client.ReadResourceAsync(Uri, cancellationToken);
}

using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Client;

///
/// Represents a named resource template that can be retrieved from an MCP server.
///
/// <remarks>
/// <para>
/// This class provides a client-side wrapper around a resource template defined on an MCP server. It allows
/// retrieving the resource template's content by sending a request to the server with the resource's URI.
/// Instances of this class are typically obtained by calling <see cref="McpClientExtensions.ListResourceTemplatesAsync"/>
/// or <see cref="McpClientExtensions.EnumerateResourceTemplatesAsync"/>.
/// </para>
/// </remarks>
public sealed class McpClientResourceTemplate
{
    private readonly IMcpClient _client;

    internal McpClientResourceTemplate(IMcpClient client, ResourceTemplate resourceTemplate)
    {
        _client = client;
        ProtocolResourceTemplate = resourceTemplate;
    }

    /// Gets the underlying protocol <see cref="ResourceTemplate"/> type for this instance.
    /// <remarks>
    /// <para>
    /// This property provides direct access to the underlying protocol representation of the resource template,
    /// which can be useful for advanced scenarios or when implementing custom MCP client extensions.
    /// </para>
    /// <para>
    /// For most common use cases, you can use the more convenient <see cref="UriTemplate"/> and
    /// <see cref="Description"/> properties instead of accessing the <see cref="ProtocolResourceTemplate"/> directly.
    /// </para>
    /// </remarks>
    public ResourceTemplate ProtocolResourceTemplate { get; }

    /// Gets the URI template of the resource template.
    public string UriTemplate => ProtocolResourceTemplate.UriTemplate;

    /// Gets the name of the resource template.
    public string Name => ProtocolResourceTemplate.Name;

    /// Gets the title of the resource template.
    public string? Title => ProtocolResourceTemplate.Title;

    /// Gets a description of the resource template.
    public string? Description => ProtocolResourceTemplate.Description;

    /// Gets a media (MIME) type of the resource template.
    public string? MimeType => ProtocolResourceTemplate.MimeType;

    ///
    /// Gets this resource template's content by formatting a URI from the template and supplied arguments
    /// and sending a request to the server.
    ///
    /// <param name="arguments">A dictionary of arguments to pass to the tool. Each key represents a parameter name,
    /// and its associated value represents the argument value.
    /// </param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A <see cref="ValueTask{ReadResourceResult}"/> containing the resource template's result with content and messages.</returns>
    public ValueTask<ReadResourceResult> ReadAsync(
        IReadOnlyDictionary<string, object?> arguments,
        CancellationToken cancellationToken = default) =>
        _client.ReadResourceAsync(UriTemplate, arguments, cancellationToken);
}

#if !NET
using ModelContextProtocol;

namespace System.Threading.Tasks;

internal static class TaskExtensions
{
    public static Task WaitAsync(this Task task, CancellationToken cancellationToken)
    {
        return WaitAsync(task, Timeout.InfiniteTimeSpan, cancellationToken);
    }

    public static Task<T> WaitAsync<T>(this Task<T> task, CancellationToken cancellationToken)
    {
        return WaitAsync(task, Timeout.InfiniteTimeSpan, cancellationToken);
    }

    public static async Task<T> WaitAsync<T>(this Task<T> task, TimeSpan timeout, CancellationToken cancellationToken = default)
    {
        await WaitAsync((Task)task, timeout, cancellationToken).ConfigureAwait(false);
        return task.Result;
    }

    public static async Task WaitAsync(this Task task, TimeSpan timeout, CancellationToken cancellationToken = default)
    {
        Throw.IfNull(task);

        if (timeout < TimeSpan.Zero && timeout != Timeout.InfiniteTimeSpan)
        {
            throw new ArgumentOutOfRangeException(nameof(timeout));
        }

        if (!task.IsCompleted)
        {
            cancellationToken.ThrowIfCancellationRequested();

            using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            cts.CancelAfter(timeout);

            var cancellationTask = new TaskCompletionSource<bool>();
            using var _ = cts.Token.Register(tcs => ((TaskCompletionSource<bool>)tcs!).TrySetResult(true), cancellationTask);
            await Task.WhenAny(task, cancellationTask.Task).ConfigureAwait(false);

            if (!task.IsCompleted)
            {
                cancellationToken.ThrowIfCancellationRequested();
                throw new TimeoutException();
            }
        }

        await task.ConfigureAwait(false);
    }
}
#endif

using ModelContextProtocol;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Text;

#if !NET
namespace System.IO;

internal static class StreamExtensions
{
    public static ValueTask WriteAsync(this Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
    {
        Throw.IfNull(stream);

        if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment))
        {
            return new ValueTask(stream.WriteAsync(segment.Array, segment.Offset, segment.Count, cancellationToken));
        }
        else
        {
            return WriteAsyncCore(stream, buffer, cancellationToken);

            static async ValueTask WriteAsyncCore(Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
            {
                byte[] array = ArrayPool<byte>.Shared.Rent(buffer.Length);
                try
                {
                    buffer.Span.CopyTo(array);
                    await stream.WriteAsync(array, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
                }
                finally
                {
                    ArrayPool<byte>.Shared.Return(array);
                }
            }
        }
    }

    public static ValueTask<int> ReadAsync(this Stream stream, Memory<byte> buffer, CancellationToken cancellationToken)
    {
        Throw.IfNull(stream);
        if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment))
        {
            return new ValueTask<int>(stream.ReadAsync(segment.Array, segment.Offset, segment.Count, cancellationToken));
        }
        else
        {
            return ReadAsyncCore(stream, buffer, cancellationToken);
            static async ValueTask<int> ReadAsyncCore(Stream stream, Memory<byte> buffer, CancellationToken cancellationToken)
            {
                byte[] array = ArrayPool<byte>.Shared.Rent(buffer.Length);
                try
                {
                    int bytesRead = await stream.ReadAsync(array, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
                    array.AsSpan(0, bytesRead).CopyTo(buffer.Span);
                    return bytesRead;
                }
                finally
                {
                    ArrayPool<byte>.Shared.Return(array);
                }
            }
        }
    }
}
#endif

using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Server;

/// Provides an <see cref="McpServerTool"/> that delegates all operations to an inner <see cref="McpServerTool"/>.
/// <remarks>
/// This is recommended as a base type when building tools that can be chained around an underlying <see cref="McpServerTool"/>.
/// The default implementation simply passes each call to the inner tool instance.
/// </remarks>
public abstract class DelegatingMcpServerTool : McpServerTool
{
    private readonly McpServerTool _innerTool;

    /// Initializes a new instance of the <see cref="DelegatingMcpServerTool"/> class around the specified <paramref name="innerTool"/>.
    /// <param name="innerTool">The inner tool wrapped by this delegating tool.</param>
    protected DelegatingMcpServerTool(McpServerTool innerTool)
    {
        Throw.IfNull(innerTool);
        _innerTool = innerTool;
    }

    /// <inheritdoc />
    public override Tool ProtocolTool => _innerTool.ProtocolTool;

    /// <inheritdoc />
    public override ValueTask<CallToolResult> InvokeAsync(
        RequestContext<CallToolRequestParams> request,
        CancellationToken cancellationToken = default) =>
        _innerTool.InvokeAsync(request, cancellationToken);

    /// <inheritdoc />
    public override string ToString() => _innerTool.ToString();
}

using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a tool that the server is capable of calling.
/// </summary>
public sealed class Tool : IBaseMetadata
{
    /// <inheritdoc />
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

    /// <inheritdoc />
    [JsonPropertyName("title")]
    public string? Title { get; set; }

    /// <summary>
    /// Gets or sets a human-readable description of the tool.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This description helps the AI model understand what the tool does and when to use it.
    /// It should be clear, concise, and accurately describe the tool's purpose and functionality.
    /// </para>
    /// <para>
    /// The description is typically presented to AI models to help them determine when
    /// and how to use the tool based on user requests.
    /// </para>
    /// </remarks>
    [JsonPropertyName("description")]
    public string? Description { get; set; }

    /// <summary>
    /// Gets or sets a JSON Schema object defining the expected parameters for the tool.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The schema must be a valid JSON Schema object with the "type" property set to "object".
    /// This is enforced by validation in the setter which will throw an <see cref="ArgumentException"/>
    /// if an invalid schema is provided.
    /// </para>
    /// <para>
    /// The schema typically defines the properties (parameters) that the tool accepts,
    /// their types, and which ones are required. This helps AI models understand
    /// how to structure their calls to the tool.
    /// </para>
    /// <para>
    /// If not explicitly set, a default minimal schema of <c>{"type":"object"}</c> is used.
    /// </para>
    /// </remarks>
    [JsonPropertyName("inputSchema")]
    public JsonElement InputSchema
    {
        get => field;
        set
        {
            if (!McpJsonUtilities.IsValidMcpToolSchema(value))
            {
                throw new ArgumentException("The specified document is not a valid MCP tool input JSON schema.", nameof(InputSchema));
            }

            field = value;
        }

    } = McpJsonUtilities.DefaultMcpToolSchema;

    /// <summary>
    /// Gets or sets a JSON Schema object defining the expected structured outputs for the tool.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The schema must be a valid JSON Schema object with the "type" property set to "object".
    /// This is enforced by validation in the setter which will throw an <see cref="ArgumentException"/>
    /// if an invalid schema is provided.
    /// </para>
    /// <para>
    /// The schema should describe the shape of the data as returned in <see cref="CallToolResult.StructuredContent"/>.
    /// </para>
    /// </remarks>
    [JsonPropertyName("outputSchema")]
    public JsonElement? OutputSchema
    {
        get => field;
        set
        {
            if (value is not null && !McpJsonUtilities.IsValidMcpToolSchema(value.Value))
            {
                throw new ArgumentException("The specified document is not a valid MCP tool output JSON schema.", nameof(OutputSchema));
            }

            field = value;
        }
    }

    /// <summary>
    /// Gets or sets optional additional tool information and behavior hints.
    /// </summary>
    /// <remarks>
    /// These annotations provide metadata about the tool's behavior, such as whether it's read-only,
    /// destructive, idempotent, or operates in an open world. They also can include a human-readable title.
    /// Note that these are hints and should not be relied upon for security decisions.
    /// </remarks>
    [JsonPropertyName("annotations")]
    public ToolAnnotations? Annotations { get; set; }

    /// <summary>
    /// Gets or sets metadata reserved by MCP for protocol-level metadata.
    /// </summary>
    /// <remarks>
    /// Implementations must not make assumptions about its contents.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonObject? Meta { get; set; }
}

using Anthropic.SDK;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Client;
using System.Diagnostics;
using System.Runtime.CompilerServices;

var builder = Host.CreateApplicationBuilder(args);

builder.Configuration
    .AddEnvironmentVariables()
    .AddUserSecrets<Program>();

var (command, arguments) = GetCommandAndArguments(args);

var clientTransport = new StdioClientTransport(new()
{
    Name = "Demo Server",
    Command = command,
    Arguments = arguments,
});

await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport);

var tools = await mcpClient.ListToolsAsync();
foreach (var tool in tools)
{
    Console.WriteLine($"Connected to server with tools: {tool.Name}");
}

using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
    .Messages
    .AsBuilder()
    .UseFunctionInvocation()
    .Build();

var options = new ChatOptions
{
    MaxOutputTokens = 1000,
    ModelId = "claude-3-5-sonnet-20241022",
    Tools = [.. tools]
};

Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("MCP Client Started!");
Console.ResetColor();

PromptForInput();
while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
{
    if (string.IsNullOrWhiteSpace(query))
    {
        PromptForInput();
        continue;
    }

    await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
    {
        Console.Write(message);
    }
    Console.WriteLine();

    PromptForInput();
}

static void PromptForInput()
{
    Console.WriteLine("Enter a command (or 'exit' to quit):");
    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.Write("> ");
    Console.ResetColor();
}

/// <summary>
/// Determines the command (executable) to run and the script/path to pass to it. This allows different
/// languages/runtime environments to be used as the MCP server.
/// </summary>
/// <remarks>
/// This method uses the file extension of the first argument to determine the command, if it's py, it'll run python,
/// if it's js, it'll run node, if it's a directory or a csproj file, it'll run dotnet.
///
/// If no arguments are provided, it defaults to running the QuickstartWeatherServer project from the current repo.
///
/// This method would only be required if you're creating a generic client, such as we use for the quickstart.
/// </remarks>
static (string command, string[] arguments) GetCommandAndArguments(string[] args)
{
    return args switch
    {
        [var script] when script.EndsWith(".py") => ("python", args),
        [var script] when script.EndsWith(".js") => ("node", args),
        [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script]),
        _ => ("dotnet", ["run", "--project", Path.Combine(GetCurrentSourceDirectory(), "../QuickstartWeatherServer")])
    };
}

static string GetCurrentSourceDirectory([CallerFilePath] string? currentFile = null)
{
    Debug.Assert(!string.IsNullOrWhiteSpace(currentFile));
    return Path.GetDirectoryName(currentFile) ?? throw new InvalidOperationException("Unable to determine source directory.");
}

using ModelContextProtocol.Server;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the tools capability configuration.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </summary>
public sealed class ToolsCapability
{
    /// <summary>
    /// Gets or sets whether this server supports notifications for changes to the tool list.
    /// </summary>
    /// <remarks>
    /// When set to <see langword="true"/>, the server will send notifications using
    /// <see cref="NotificationMethods.ToolListChangedNotification"/> when tools are added,
    /// removed, or modified. Clients can register handlers for these notifications to
    /// refresh their tool cache. This capability enables clients to stay synchronized with server-side
    /// changes to available tools.
    /// </remarks>
    [JsonPropertyName("listChanged")]
    public bool? ListChanged { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ToolsList"/> requests.
    /// </summary>
    /// <remarks>
    /// The handler should return a list of available tools when requested by a client.
    /// It supports pagination through the cursor mechanism, where the client can make
    /// repeated calls with the cursor returned by the previous call to retrieve more tools.
    /// When used in conjunction with <see cref="ToolCollection"/>, both the tools from this handler
    /// and the tools from the collection will be combined to form the complete list of available tools.
    /// </remarks>
    [JsonIgnore]
    public Func<RequestContext<ListToolsRequestParams>, CancellationToken, ValueTask<ListToolsResult>>? ListToolsHandler { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.ToolsCall"/> requests.
    /// </summary>
    /// <remarks>
    /// This handler is invoked when a client makes a call to a tool that isn't found in the <see cref="ToolCollection"/>.
    /// The handler should implement logic to execute the requested tool and return appropriate results.
    /// It receives a <see cref="RequestContext{CallToolRequestParams}"/> containing information about the tool
    /// being called and its arguments, and should return a <see cref="CallToolResult"/> with the execution results.
    /// </remarks>
    [JsonIgnore]
    public Func<RequestContext<CallToolRequestParams>, CancellationToken, ValueTask<CallToolResult>>? CallToolHandler { get; set; }

    /// <summary>
    /// Gets or sets a collection of tools served by the server.
    /// </summary>
    /// <remarks>
    /// Tools will specified via <see cref="ToolCollection"/> augment the <see cref="ListToolsHandler"/> and
    /// <see cref="CallToolHandler"/>, if provided. ListTools requests will output information about every tool
    /// in <see cref="ToolCollection"/> and then also any tools output by <see cref="ListToolsHandler"/>, if it's
    /// non-<see langword="null"/>. CallTool requests will first check <see cref="ToolCollection"/> for the tool
    /// being requested, and if the tool is not found in the <see cref="ToolCollection"/>, any specified <see cref="CallToolHandler"/>
    /// will be invoked as a fallback.
    /// </remarks>
    [JsonIgnore]
    public McpServerPrimitiveCollection<McpServerTool>? ToolCollection { get; set; }
}

using Microsoft.Extensions.Hosting;
using ModelContextProtocol;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;

namespace EverythingServer;

public class LoggingUpdateMessageSender(IMcpServer server, Func<LoggingLevel> getMinLevel) : BackgroundService
{
    readonly Dictionary<LoggingLevel, string> _loggingLevelMap = new()
    {
        { LoggingLevel.Debug, "Debug-level message" },
        { LoggingLevel.Info, "Info-level message" },
        { LoggingLevel.Notice, "Notice-level message" },
        { LoggingLevel.Warning, "Warning-level message" },
        { LoggingLevel.Error, "Error-level message" },
        { LoggingLevel.Critical, "Critical-level message" },
        { LoggingLevel.Alert, "Alert-level message" },
        { LoggingLevel.Emergency, "Emergency-level message" }
    };

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var newLevel = (LoggingLevel)Random.Shared.Next(_loggingLevelMap.Count);

            var message = new
                {
                    Level = newLevel.ToString().ToLower(),
                    Data = _loggingLevelMap[newLevel],
                };

            if (newLevel > getMinLevel())
            {
                await server.SendNotificationAsync("notifications/message", message, cancellationToken: stoppingToken);
            }

            await Task.Delay(15000, stoppingToken);
        }
    }
}

using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol;
using System.ComponentModel;
using System.Text.Json;

namespace ModelContextProtocol.Server;

/// <summary>
/// Provides options for controlling the creation of an <see cref="McpServerTool"/>.
/// </summary>
/// <remarks>
/// <para>
/// These options allow for customizing the behavior and metadata of tools created with
/// <see cref="M:McpServerTool.Create"/>. They provide control over naming, description,
/// tool properties, and dependency injection integration.
/// </para>
/// <para>
/// When creating tools programmatically rather than using attributes, these options
/// provide the same level of configuration flexibility.
/// </para>
/// </remarks>
public sealed class McpServerToolCreateOptions
{
    /// <summary>
    /// Gets or sets optional services used in the construction of the <see cref="McpServerTool"/>.
    /// </summary>
    /// <remarks>
    /// These services will be used to determine which parameters should be satisfied from dependency injection. As such,
    /// what services are satisfied via this provider should match what's satisfied via the provider passed in at invocation time.
    /// </remarks>
    public IServiceProvider? Services { get; set; }

    /// <summary>
    /// Gets or sets the name to use for the <see cref="McpServerTool"/>.
    /// </summary>
    /// <remarks>
    /// If <see langword="null"/>, but an <see cref="McpServerToolAttribute"/> is applied to the method,
    /// the name from the attribute will be used. If that's not present, a name based on the method's name will be used.
    /// </remarks>
    public string? Name { get; set; }

    /// <summary>
    /// Gets or set the description to use for the <see cref="McpServerTool"/>.
    /// </summary>
    /// <remarks>
    /// If <see langword="null"/>, but a <see cref="DescriptionAttribute"/> is applied to the method,
    /// the description from that attribute will be used.
    /// </remarks>
    public string? Description { get; set; }

    /// <summary>
    /// Gets or sets a human-readable title for the tool that can be displayed to users.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The title provides a more descriptive, user-friendly name for the tool than the tool's
    /// programmatic name. It is intended for display purposes and to help users understand
    /// the tool's purpose at a glance.
    /// </para>
    /// <para>
    /// Unlike the tool name (which follows programmatic naming conventions), the title can
    /// include spaces, special characters, and be phrased in a more natural language style.
    /// </para>
    /// </remarks>
    public string? Title { get; set; }

    /// <summary>
    /// Gets or sets whether the tool may perform destructive updates to its environment.
    /// </summary>
    /// <remarks>
    /// <para>
    /// If <see langword="true"/>, the tool may perform destructive updates to its environment.
    /// If <see langword="false"/>, the tool performs only additive updates.
    /// This property is most relevant when the tool modifies its environment (ReadOnly = false).
    /// </para>
    /// <para>
    /// The default is <see langword="true"/>.
    /// </para>
    /// </remarks>
    public bool? Destructive { get; set; }

    /// <summary>
    /// Gets or sets whether calling the tool repeatedly with the same arguments
    /// will have no additional effect on its environment.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This property is most relevant when the tool modifies its environment (ReadOnly = false).
    /// </para>
    /// <para>
    /// The default is <see langword="false"/>.
    /// </para>
    /// </remarks>
    public bool? Idempotent { get; set; }

    /// <summary>
    /// Gets or sets whether this tool may interact with an "open world" of external entities.
    /// </summary>
    /// <remarks>
    /// <para>
    /// If <see langword="true"/>, the tool may interact with an unpredictable or dynamic set of entities (like web search).
    /// If <see langword="false"/>, the tool's domain of interaction is closed and well-defined (like memory access).
    /// </para>
    /// <para>
    /// The default is <see langword="true"/>.
    /// </para>
    /// </remarks>
    public bool? OpenWorld { get; set; }

    /// <summary>
    /// Gets or sets whether this tool does not modify its environment.
    /// </summary>
    /// <remarks>
    /// <para>
    /// If <see langword="true"/>, the tool only performs read operations without changing state.
    /// If <see langword="false"/>, the tool may make modifications to its environment.
    /// </para>
    /// <para>
    /// Read-only tools do not have side effects beyond computational resource usage.
    /// They don't create, update, or delete data in any system.
    /// </para>
    /// <para>
    /// The default is <see langword="false"/>.
    /// </para>
    /// </remarks>
    public bool? ReadOnly { get; set; }

    /// <summary>
    /// Gets or sets whether the tool should report an output schema for structured content.
    /// </summary>
    /// <remarks>
    /// <para>
    /// When enabled, the tool will attempt to populate the <see cref="Tool.OutputSchema"/>
    /// and provide structured content in the <see cref="CallToolResult.StructuredContent"/> property.
    /// </para>
    /// <para>
    /// The default is <see langword="false"/>.
    /// </para>
    /// </remarks>
    public bool UseStructuredContent { get; set; }

    /// <summary>
    /// Gets or sets the JSON serializer options to use when marshalling data to/from JSON.
    /// </summary>
    /// <remarks>
    /// Defaults to <see cref="McpJsonUtilities.DefaultOptions"/> if left unspecified.
    /// </remarks>
    public JsonSerializerOptions? SerializerOptions { get; set; }

    /// <summary>
    /// Gets or sets the JSON schema options when creating <see cref="AIFunction"/> from a method.
    /// </summary>
    /// <remarks>
    /// Defaults to <see cref="AIJsonSchemaCreateOptions.Default"/> if left unspecified.
    /// </remarks>
    public AIJsonSchemaCreateOptions? SchemaCreateOptions { get; set; }

    /// <summary>
    /// Creates a shallow clone of the current <see cref="McpServerToolCreateOptions"/> instance.
    /// </summary>
    internal McpServerToolCreateOptions Clone() =>
        new McpServerToolCreateOptions
        {
            Services = Services,
            Name = Name,
            Description = Description,
            Title = Title,
            Destructive = Destructive,
            Idempotent = Idempotent,
            OpenWorld = OpenWorld,
            ReadOnly = ReadOnly,
            UseStructuredContent = UseStructuredContent,
            SerializerOptions = SerializerOptions,
            SchemaCreateOptions = SchemaCreateOptions,
        };
}

using Microsoft.AspNetCore.Http;
using ModelContextProtocol.Server;

namespace ModelContextProtocol.AspNetCore;

/// <summary>
/// Configuration options for <see cref="M:McpEndpointRouteBuilderExtensions.MapMcp"/>.
/// which implements the Streaming HTTP transport for the Model Context Protocol.
/// See the protocol specification for details on the Streamable HTTP transport. <see href="https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http"/>
/// </summary>
public class HttpServerTransportOptions
{
    /// <summary>
    /// Gets or sets an optional asynchronous callback to configure per-session <see cref="McpServerOptions"/>
    /// with access to the <see cref="HttpContext"/> of the request that initiated the session.
    /// </summary>
    public Func<HttpContext, McpServerOptions, CancellationToken, Task>? ConfigureSessionOptions { get; set; }

    /// <summary>
    /// Gets or sets an optional asynchronous callback for running new MCP sessions manually.
    /// This is useful for running logic before a sessions starts and after it completes.
    /// </summary>
    public Func<HttpContext, IMcpServer, CancellationToken, Task>? RunSessionHandler { get; set; }

    /// <summary>
    /// Gets or sets whether the server should run in a stateless mode that does not require all requests for a given session
    /// to arrive to the same ASP.NET Core application process.
    /// </summary>
    /// <remarks>
    /// If <see langword="true"/>, the "/sse" endpoint will be disabled, and client information will be round-tripped as part
    /// of the "MCP-Session-Id" header instead of stored in memory. Unsolicited server-to-client messages and all server-to-client
    /// requests are also unsupported, because any responses may arrive at another ASP.NET Core application process.
    /// Client sampling and roots capabilities are also disabled in stateless mode, because the server cannot make requests.
    /// Defaults to <see langword="false"/>.
    /// </remarks>
    public bool Stateless { get; set; }

    /// <summary>
    /// Gets or sets whether the server should use a single execution context for the entire session.
    /// If <see langword="false"/>, handlers like tools get called with the <see cref="ExecutionContext"/>
    /// belonging to the corresponding HTTP request which can change throughout the MCP session.
    /// If <see langword="true"/>, handlers will get called with the same <see cref="ExecutionContext"/>
    /// used to call <see cref="ConfigureSessionOptions" /> and <see cref="RunSessionHandler"/>.
    /// </summary>
    /// <remarks>
    /// Enabling a per-session <see cref="ExecutionContext"/> can be useful for setting <see cref="AsyncLocal{T}"/> variables
    /// that persist for the entire session, but it prevents you from using IHttpContextAccessor in handlers.
    /// Defaults to <see langword="false"/>.
    /// </remarks>
    public bool PerSessionExecutionContext { get; set; }

    /// <summary>
    /// Gets or sets the duration of time the server will wait between any active requests before timing out an MCP session.
    /// </summary>
    /// <remarks>
    /// This is checked in background every 5 seconds. A client trying to resume a session will receive a 404 status code
    /// and should restart their session. A client can keep their session open by keeping a GET request open.
    /// Defaults to 2 hours.
    /// </remarks>
    public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromHours(2);

    /// <summary>
    /// Gets or sets maximum number of idle sessions to track in memory. This is used to limit the number of sessions that can be idle at once.
    /// </summary>
    /// <remarks>
    /// Past this limit, the server will log a critical error and terminate the oldest idle sessions even if they have not reached
    /// their <see cref="IdleTimeout"/> until the idle session count is below this limit. Clients that keep their session open by
    /// keeping a GET request open will not count towards this limit.
    /// Defaults to 100,000 sessions.
    /// </remarks>
    public int MaxIdleSessionCount { get; set; } = 100_000;

    /// <summary>
    /// Used for testing the <see cref="IdleTimeout"/>.
    /// </summary>
    public TimeProvider TimeProvider { get; set; } = TimeProvider.System;
}

using ModelContextProtocol.Client;
using ModelContextProtocol.Server;
using System.Threading.Channels;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a transport mechanism for MCP (Model Context Protocol) communication between clients and servers.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="ITransport"/> interface is the core abstraction for bidirectional communication.
/// It provides methods for sending and receiving messages, abstracting away the underlying transport mechanism
/// and allowing protocol implementations to be decoupled from communication details.
/// </para>
/// <para>
/// Implementations of <see cref="ITransport"/> handle the serialization, transmission, and reception of
/// messages over various channels like standard input/output streams and HTTP (Server-Sent Events).
/// </para>
/// <para>
/// While <see cref="IClientTransport"/> is responsible for establishing a client's connection,
/// <see cref="ITransport"/> represents an established session. Client implementations typically obtain an
/// <see cref="ITransport"/> instance by calling <see cref="IClientTransport.ConnectAsync"/>.
/// </para>
/// </remarks>
public interface ITransport : IAsyncDisposable
{
    /// <summary>Gets an identifier associated with the current MCP session.</summary>
    /// <remarks>
    /// Typically populated in transports supporting multiple sessions such as Streamable HTTP or SSE.
    /// Can return <see langword="null"/> if the session hasn't initialized or if the transport doesn't
    /// support multiple sessions (as is the case with STDIO).
    /// </remarks>
    string? SessionId { get; }

    /// <summary>
    /// Gets a channel reader for receiving messages from the transport.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The <see cref="MessageReader"/> provides access to incoming JSON-RPC messages received by the transport.
    /// It returns a <see cref="ChannelReader{T}"/> which allows consuming messages in a thread-safe manner.
    /// </para>
    /// <para>
    /// The reader will continue to provide messages as long as the transport is connected. When the transport
    /// is disconnected or disposed, the channel will be completed and no more messages will be available after
    /// any already transmitted messages are consumed.
    /// </para>
    /// </remarks>
    ChannelReader<JsonRpcMessage> MessageReader { get; }

    /// <summary>
    /// Sends a JSON-RPC message through the transport.
    /// </summary>
    /// <param name="message">The JSON-RPC message to send.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous send operation.</returns>
    /// <exception cref="InvalidOperationException">The transport is not connected.</exception>
    /// <remarks>
    /// <para>
    /// This method serializes and sends the provided JSON-RPC message through the transport connection.
    /// </para>
    /// <para>
    /// This is a core method used by higher-level abstractions in the MCP protocol implementation.
    /// Most client code should use the higher-level methods provided by <see cref="IMcpEndpoint"/>,
    /// <see cref="McpEndpointExtensions"/>, <see cref="McpClientExtensions"/>, or <see cref="McpServerExtensions"/>,
    /// rather than accessing this method directly.
    /// </para>
    /// </remarks>
    Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default);
}

namespace ModelContextProtocol.Client;

/// <summary>
/// Provides options for configuring <see cref="StdioClientTransport"/> instances.
/// </summary>
public sealed class StdioClientTransportOptions
{
    /// <summary>
    /// Gets or sets the command to execute to start the server process.
    /// </summary>
    public required string Command
    {
        get;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentException("Command cannot be null or empty.", nameof(value));
            }

            field = value;
        }
    }

    /// <summary>
    /// Gets or sets the arguments to pass to the server process when it is started.
    /// </summary>
    public IList<string>? Arguments { get; set; }

    /// <summary>
    /// Gets or sets a transport identifier used for logging purposes.
    /// </summary>
    public string? Name { get; set; }

    /// <summary>
    /// Gets or sets the working directory for the server process.
    /// </summary>
    public string? WorkingDirectory { get; set; }

    /// <summary>
    /// Gets or sets environment variables to set for the server process.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This property allows you to specify environment variables that will be set in the server process's
    /// environment. This is useful for passing configuration, authentication information, or runtime flags
    /// to the server without modifying its code.
    /// </para>
    /// <para>
    /// By default, when starting the server process, the server process will inherit the current environment's variables,
    /// as discovered via <see cref="Environment.GetEnvironmentVariables()"/>. After those variables are found, the entries
    /// in this <see cref="EnvironmentVariables"/> dictionary are used to augment and overwrite the entries read from the environment.
    /// That includes removing the variables for any of this collection's entries with a null value.
    /// </para>
    /// </remarks>
    public IDictionary<string, string?>? EnvironmentVariables { get; set; }

    /// <summary>
    /// Gets or sets the timeout to wait for the server to shut down gracefully.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This property dictates how long the client should wait for the server process to exit cleanly during shutdown
    /// before forcibly terminating it. This balances between giving the server enough time to clean up
    /// resources and not hanging indefinitely if a server process becomes unresponsive.
    /// </para>
    /// <para>
    /// The default is five seconds.
    /// </para>
    /// </remarks>
    public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5);

    /// <summary>
    /// Gets or sets a callback that is invoked for each line of stderr received from the server process.
    /// </summary>
    public Action<string>? StandardErrorLines { get; set; }
}

using ModelContextProtocol;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace EverythingServer.Tools;

[McpServerToolType]
public class LongRunningTool
{
    [McpServerTool(Name = "longRunningOperation"), Description("Demonstrates a long running operation with progress updates")]
    public static async Task<string> LongRunningOperation(
        IMcpServer server,
        RequestContext<CallToolRequestParams> context,
        int duration = 10,
        int steps = 5)
    {
        var progressToken = context.Params?.ProgressToken;
        var stepDuration = duration / steps;

        for (int i = 1; i <= steps + 1; i++)
        {
            await Task.Delay(stepDuration * 1000);

            if (progressToken is not null)
            {
                await server.SendNotificationAsync("notifications/progress", new
                    {
                        Progress = i,
                        Total = steps,
                        progressToken
                    });
            }
        }

        return $"Long running operation completed. Duration: {duration} seconds. Steps: {steps}.";
    }
}

using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace TestServerWithHosting.Tools;

/// <summary>
/// This tool uses depenency injection and async method
/// </summary>
[McpServerToolType]
public sealed class SampleLlmTool
{
    [McpServerTool(Name = "sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")]
    public static async Task<string> SampleLLM(
        IMcpServer thisServer,
        [Description("The prompt to send to the LLM")] string prompt,
        [Description("Maximum number of tokens to generate")] int maxTokens,
        CancellationToken cancellationToken)
    {
        var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens);
        var sampleResult = await thisServer.SampleAsync(samplingParams, cancellationToken);

        return $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}";
    }

    private static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100)
    {
        return new CreateMessageRequestParams
        {
            Messages = [new SamplingMessage
                {
                    Role = Role.User,
                    Content = new TextContentBlock { Text = $"Resource {uri} context: {context}" },
                }],
            SystemPrompt = "You are a helpful test server.",
            MaxTokens = maxTokens,
            Temperature = 0.7f,
            IncludeContext = ContextInclusion.ThisServer
        };
    }
}

using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Server;

/// Provides an <see cref="McpServerPrompt"/> that delegates all operations to an inner <see cref="McpServerPrompt"/>.
/// <remarks>
/// This is recommended as a base type when building prompts that can be chained around an underlying <see cref="McpServerPrompt"/>.
/// The default implementation simply passes each call to the inner prompt instance.
/// </remarks>
public abstract class DelegatingMcpServerPrompt : McpServerPrompt
{
    private readonly McpServerPrompt _innerPrompt;

    /// Initializes a new instance of the <see cref="DelegatingMcpServerPrompt"/> class around the specified <paramref name="innerPrompt"/>.
    /// <param name="innerPrompt">The inner prompt wrapped by this delegating prompt.</param>
    protected DelegatingMcpServerPrompt(McpServerPrompt innerPrompt)
    {
        Throw.IfNull(innerPrompt);
        _innerPrompt = innerPrompt;
    }

    /// <inheritdoc />
    public override Prompt ProtocolPrompt => _innerPrompt.ProtocolPrompt;

    /// <inheritdoc />
    public override ValueTask<GetPromptResult> GetAsync(
        RequestContext<GetPromptRequestParams> request,
        CancellationToken cancellationToken = default) =>
        _innerPrompt.GetAsync(request, cancellationToken);

    /// <inheritdoc />
    public override string ToString() => _innerPrompt.ToString();
}

using ModelContextProtocol;
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Globalization;
using System.Text.Json;

namespace ProtectedMCPServer.Tools;

[McpServerToolType]
public sealed class WeatherTools
{
    private readonly IHttpClientFactory _httpClientFactory;

    public WeatherTools(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [McpServerTool, Description("Get weather alerts for a US state.")]
    public async Task<string> GetAlerts(
        [Description("The US state to get alerts for. Use the 2 letter abbreviation for the state (e.g. NY).")] string state)
    {
        var client = _httpClientFactory.CreateClient("WeatherApi");
        using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}");
        var jsonElement = jsonDocument.RootElement;
        var alerts = jsonElement.GetProperty("features").EnumerateArray();

        if (!alerts.Any())
        {
            return "No active alerts for this state.";
        }

        return string.Join("\n--\n", alerts.Select(alert =>
        {
            JsonElement properties = alert.GetProperty("properties");
            return $"""
                    Event: {properties.GetProperty("event").GetString()}
                    Area: {properties.GetProperty("areaDesc").GetString()}
                    Severity: {properties.GetProperty("severity").GetString()}
                    Description: {properties.GetProperty("description").GetString()}
                    Instruction: {properties.GetProperty("instruction").GetString()}
                    """;
        }));
    }

    [McpServerTool, Description("Get weather forecast for a location.")]
    public async Task<string> GetForecast(
        [Description("Latitude of the location.")] double latitude,
        [Description("Longitude of the location.")] double longitude)
    {
        var client = _httpClientFactory.CreateClient("WeatherApi");
        var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
        using var jsonDocument = await client.ReadJsonDocumentAsync(pointUrl);
        var forecastUrl = jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
            ?? throw new Exception($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");

        using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl);
        var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();

        return string.Join("\n---\n", periods.Select(period => $"""
                {period.GetProperty("name").GetString()}
                Temperature: {period.GetProperty("temperature").GetInt32()}°F
                Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
                Forecast: {period.GetProperty("detailedForecast").GetString()}
                """));
    }
}

using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Server;

/// Provides an <see cref="McpServerResource"/> that delegates all operations to an inner <see cref="McpServerResource"/>.
/// <remarks>
/// This is recommended as a base type when building resources that can be chained around an underlying <see cref="McpServerResource"/>.
/// The default implementation simply passes each call to the inner resource instance.
/// </remarks>
public abstract class DelegatingMcpServerResource : McpServerResource
{
    private readonly McpServerResource _innerResource;

    /// Initializes a new instance of the <see cref="DelegatingMcpServerResource"/> class around the specified <paramref name="innerResource"/>.
    /// <param name="innerResource">The inner resource wrapped by this delegating resource.</param>
    protected DelegatingMcpServerResource(McpServerResource innerResource)
    {
        Throw.IfNull(innerResource);
        _innerResource = innerResource;
    }

    /// <inheritdoc />
    public override Resource? ProtocolResource => _innerResource.ProtocolResource;

    /// <inheritdoc />
    public override ResourceTemplate ProtocolResourceTemplate => _innerResource.ProtocolResourceTemplate;

    /// <inheritdoc />
    public override ValueTask<ReadResourceResult?> ReadAsync(RequestContext<ReadResourceRequestParams> request, CancellationToken cancellationToken = default) =>
        _innerResource.ReadAsync(request, cancellationToken);

    /// <inheritdoc />
    public override string ToString() => _innerResource.ToString();
}

namespace ModelContextProtocol;

/// <summary>
/// Represents an exception that is thrown when an Model Context Protocol (MCP) error occurs.
/// </summary>
/// <remarks>
/// This exception is used to represent failures to do with protocol-level concerns, such as invalid JSON-RPC requests,
/// invalid parameters, or internal errors. It is not intended to be used for application-level errors.
/// <see cref="Exception.Message"/> or <see cref="ErrorCode"/> from a <see cref="McpException"/> may be
/// propagated to the remote endpoint; sensitive information should not be included. If sensitive details need
/// to be included, a different exception type should be used.
/// </remarks>
public class McpException : Exception
{
    /// <summary>
    /// Initializes a new instance of the <see cref="McpException"/> class.
    /// </summary>
    public McpException()
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="McpException"/> class with a specified error message.
    /// </summary>
    /// <param name="message">The message that describes the error.</param>
    public McpException(string message) : base(message)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="McpException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
    /// </summary>
    /// <param name="message">The message that describes the error.</param>
    /// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
    public McpException(string message, Exception? innerException) : base(message, innerException)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="McpException"/> class with a specified error message and JSON-RPC error code.
    /// </summary>
    /// <param name="message">The message that describes the error.</param>
    /// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
    public McpException(string message, McpErrorCode errorCode) : this(message, null, errorCode)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="McpException"/> class with a specified error message, inner exception, and JSON-RPC error code.
    /// </summary>
    /// <param name="message">The message that describes the error.</param>
    /// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
    /// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
    public McpException(string message, Exception? innerException, McpErrorCode errorCode) : base(message, innerException)
    {
        ErrorCode = errorCode;
    }

    /// <summary>
    /// Gets the error code associated with this exception.
    /// </summary>
    /// <remarks>
    /// This property contains a standard JSON-RPC error code as defined in the MCP specification. Common error codes include:
    /// <list type="bullet">
    /// <item><description>-32700: Parse error - Invalid JSON received</description></item>
    /// <item><description>-32600: Invalid request - The JSON is not a valid Request object</description></item>
    /// <item><description>-32601: Method not found - The method does not exist or is not available</description></item>
    /// <item><description>-32602: Invalid params - Invalid method parameters</description></item>
    /// <item><description>-32603: Internal error - Internal JSON-RPC error</description></item>
    /// </list>
    /// </remarks>
    public McpErrorCode ErrorCode { get; } = McpErrorCode.InternalError;
}

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// A request message in the JSON-RPC protocol.
/// </summary>
/// <remarks>
/// Requests are messages that require a response from the receiver. Each request includes a unique ID
/// that will be included in the corresponding response message (either a success response or an error).
///
/// The receiver of a request message is expected to execute the specified method with the provided parameters
/// and return either a <see cref="JsonRpcResponse"/> with the result, or a <see cref="JsonRpcError"/>
/// if the method execution fails.
/// </remarks>
public sealed class JsonRpcRequest : JsonRpcMessageWithId
{
    /// <summary>
    /// Name of the method to invoke.
    /// </summary>
    [JsonPropertyName("method")]
    public required string Method { get; init; }

    /// <summary>
    /// Optional parameters for the method.
    /// </summary>
    [JsonPropertyName("params")]
    public JsonNode? Params { get; init; }

    internal JsonRpcRequest WithId(RequestId id)
    {
        return new JsonRpcRequest
        {
            JsonRpc = JsonRpc,
            Id = id,
            Method = Method,
            Params = Params,
            RelatedTransport = RelatedTransport,
        };
    }
}

using ModelContextProtocol.Protocol;
using System.Diagnostics;

#if NET
using System.Net.Http.Json;
#else
using System.Text;
using System.Text.Json;
#endif

namespace ModelContextProtocol.Client;

internal class McpHttpClient(HttpClient httpClient)
{
    internal virtual async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, JsonRpcMessage? message, CancellationToken cancellationToken)
    {
        Debug.Assert(request.Content is null, "The request body should only be supplied as a JsonRpcMessage");
        Debug.Assert(message is null || request.Method == HttpMethod.Post, "All messages should be sent in POST requests.");

        using var content = CreatePostBodyContent(message);
        request.Content = content;
        return await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
    }

    private HttpContent? CreatePostBodyContent(JsonRpcMessage? message)
    {
        if (message is null)
        {
            return null;
        }

#if NET
        return JsonContent.Create(message, McpJsonUtilities.JsonContext.Default.JsonRpcMessage);
#else
        return new StringContent(
            JsonSerializer.Serialize(message, McpJsonUtilities.JsonContext.Default.JsonRpcMessage),
            Encoding.UTF8,
            "application/json"
        );
#endif
    }
}

using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace ModelContextProtocol;

/// <summary>Provides helper methods for throwing exceptions.</summary>
internal static class Throw
{
    // NOTE: Most of these should be replaced with extension statics for the relevant extension
    // type as downlevel polyfills once the C# 14 extension everything feature is available.

    public static void IfNull([NotNull] object? arg, [CallerArgumentExpression(nameof(arg))] string? parameterName = null)
    {
        if (arg is null)
        {
            ThrowArgumentNullException(parameterName);
        }
    }

    public static void IfNullOrWhiteSpace([NotNull] string? arg, [CallerArgumentExpression(nameof(arg))] string? parameterName = null)
    {
        if (arg is null || arg.AsSpan().IsWhiteSpace())
        {
            ThrowArgumentNullOrWhiteSpaceException(parameterName);
        }
    }

    public static void IfNegative(int arg, [CallerArgumentExpression(nameof(arg))] string? parameterName = null)
    {
        if (arg < 0)
        {
            Throw(parameterName);
            static void Throw(string? parameterName) => throw new ArgumentOutOfRangeException(parameterName, "must not be negative.");
        }
    }

    [DoesNotReturn]
    private static void ThrowArgumentNullOrWhiteSpaceException(string? parameterName)
    {
        if (parameterName is null)
        {
            ThrowArgumentNullException(parameterName);
        }

        throw new ArgumentException("Value cannot be empty or composed entirely of whitespace.", parameterName);
    }

    [DoesNotReturn]
    private static void ThrowArgumentNullException(string? parameterName) => throw new ArgumentNullException(parameterName);
}

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

///
/// Represents a known resource template that the server is capable of reading.
///
/// <remarks>
/// Resource templates provide metadata about resources available on the server,
/// including how to construct URIs for those resources.
/// </remarks>
public sealed class ResourceTemplate : IBaseMetadata
{
    /// <inheritdoc />
    [JsonPropertyName("name")]
    public required string Name { get; set; }

    /// <inheritdoc />
    [JsonPropertyName("title")]
    public string? Title { get; set; }

    ///
    /// Gets or sets the URI template (according to RFC 6570) that can be used to construct resource URIs.
    ///
    [JsonPropertyName("uriTemplate")]
    public required string UriTemplate { get; init; }

    ///
    /// Gets or sets a description of what this resource template represents.
    ///
    /// <remarks>
    /// <para>
    /// This description helps clients understand the purpose and content of resources
    /// that can be generated from this template. It can be used by client applications
    /// to provide context about available resource types or to display in user interfaces.
    /// </para>
    /// <para>
    /// For AI models, this description can serve as a hint about when and how to use
    /// the resource template, enhancing the model's ability to generate appropriate URIs.
    /// </para>
    /// </remarks>
    [JsonPropertyName("description")]
    public string? Description { get; init; }

    ///
    /// Gets or sets the MIME type of this resource template, if known.
    ///
    /// <remarks>
    /// <para>
    /// Specifies the expected format of resources that can be generated from this template.
    /// This helps clients understand what type of content to expect when accessing resources
    /// created using this template.
    /// </para>
    /// <para>
    /// Common MIME types include "text/plain" for plain text, "application/pdf" for PDF documents,
    /// "image/png" for PNG images, or "application/json" for JSON data.
    /// </para>
    /// </remarks>
    [JsonPropertyName("mimeType")]
    public string? MimeType { get; init; }

    ///
    /// Gets or sets optional annotations for the resource template.
    ///
    /// <remarks>
    /// These annotations can be used to specify the intended audience (<see cref="Role.User"/>, <see cref="Role.Assistant"/>, or both)
    /// and the priority level of the resource template. Clients can use this information to filter
    /// or prioritize resource templates for different roles.
    /// </remarks>
    [JsonPropertyName("annotations")]
    public Annotations? Annotations { get; init; }

    ///
    /// Gets or sets metadata reserved by MCP for protocol-level metadata.
    ///
    /// <remarks>
    /// Implementations must not make assumptions about its contents.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonObject? Meta { get; init; }

    /// Gets whether <see cref="UriTemplate"/> contains any template expressions.
    [JsonIgnore]
    public bool IsTemplated => UriTemplate.Contains('{');

    /// Converts the <see cref="ResourceTemplate"/> into a <see cref="Resource"/>.
    /// <returns>A <see cref="Resource"/> if <see cref="IsTemplated"/> is <see langword="false"/>; otherwise, <see langword="null"/>.</returns>
    public Resource? AsResource()
    {
        if (IsTemplated)
        {
            return null;
        }

        return new()
        {
            Uri = UriTemplate,
            Name = Name,
            Title = Title,
            Description = Description,
            MimeType = MimeType,
            Annotations = Annotations,
            Meta = Meta,
        };
    }
}

using Microsoft.Extensions.DependencyInjection.Extensions;
using ModelContextProtocol.AspNetCore;
using ModelContextProtocol.Server;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Provides methods for configuring HTTP MCP servers via dependency injection.
/// </summary>
public static class HttpMcpServerBuilderExtensions
{
    /// <summary>
    /// Adds the services necessary for <see cref="M:McpEndpointRouteBuilderExtensions.MapMcp"/>
    /// to handle MCP requests and sessions using the MCP Streamable HTTP transport. For more information on configuring the underlying HTTP server
    /// to control things like port binding custom TLS certificates, see the <see href="https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis">Minimal APIs quick reference</see>.
    /// </summary>
    /// <param name="builder">The builder instance.</param>
    /// <param name="configureOptions">Configures options for the Streamable HTTP transport. This allows configuring per-session
    /// <see cref="McpServerOptions"/> and running logic before and after a session.</param>
    /// <returns>The builder provided in <paramref name="builder"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
    public static IMcpServerBuilder WithHttpTransport(this IMcpServerBuilder builder, Action<HttpServerTransportOptions>? configureOptions = null)
    {
        ArgumentNullException.ThrowIfNull(builder);

        builder.Services.TryAddSingleton<StreamableHttpHandler>();
        builder.Services.TryAddSingleton<SseHandler>();
        builder.Services.AddHostedService<IdleTrackingBackgroundService>();
        builder.Services.AddDataProtection();

        if (configureOptions is not null)
        {
            builder.Services.Configure(configureOptions);
        }

        return builder;
    }
}

using Microsoft.Extensions.AI;
using System.ComponentModel;
using System.Text.Json;

namespace ModelContextProtocol.Server;

/// <summary>
/// Provides options for controlling the creation of an <see cref="McpServerPrompt"/>.
/// </summary>
/// <remarks>
/// <para>
/// These options allow for customizing the behavior and metadata of prompts created with
/// <see cref="M:McpServerPrompt.Create"/>. They provide control over naming, description,
/// and dependency injection integration.
/// </para>
/// <para>
/// When creating prompts programmatically rather than using attributes, these options
/// provide the same level of configuration flexibility.
/// </para>
/// </remarks>
public sealed class McpServerPromptCreateOptions
{
    /// <summary>
    /// Gets or sets optional services used in the construction of the <see cref="McpServerPrompt"/>.
    /// </summary>
    /// <remarks>
    /// These services will be used to determine which parameters should be satisifed from dependency injection. As such,
    /// what services are satisfied via this provider should match what's satisfied via the provider passed in at invocation time.
    /// </remarks>
    public IServiceProvider? Services { get; set; }

    /// <summary>
    /// Gets or sets the name to use for the <see cref="McpServerPrompt"/>.
    /// </summary>
    /// <remarks>
    /// If <see langword="null"/>, but an <see cref="McpServerPromptAttribute"/> is applied to the method,
    /// the name from the attribute will be used. If that's not present, a name based on the method's name will be used.
    /// </remarks>
    public string? Name { get; set; }

    /// <summary>
    /// Gets or sets the title to use for the <see cref="McpServerPrompt"/>.
    /// </summary>
    public string? Title { get; set; }

    /// <summary>
    /// Gets or set the description to use for the <see cref="McpServerPrompt"/>.
    /// </summary>
    /// <remarks>
    /// If <see langword="null"/>, but a <see cref="DescriptionAttribute"/> is applied to the method,
    /// the description from that attribute will be used.
    /// </remarks>
    public string? Description { get; set; }

    /// <summary>
    /// Gets or sets the JSON serializer options to use when marshalling data to/from JSON.
    /// </summary>
    /// <remarks>
    /// Defaults to <see cref="McpJsonUtilities.DefaultOptions"/> if left unspecified.
    /// </remarks>
    public JsonSerializerOptions? SerializerOptions { get; set; }

    /// <summary>
    /// Gets or sets the JSON schema options when creating <see cref="AIFunction"/> from a method.
    /// </summary>
    /// <remarks>
    /// Defaults to <see cref="AIJsonSchemaCreateOptions.Default"/> if left unspecified.
    /// </remarks>
    public AIJsonSchemaCreateOptions? SchemaCreateOptions { get; set; }

    /// <summary>
    /// Creates a shallow clone of the current <see cref="McpServerPromptCreateOptions"/> instance.
    /// </summary>
    internal McpServerPromptCreateOptions Clone() =>
        new McpServerPromptCreateOptions
        {
            Services = Services,
            Name = Name,
            Title = Title,
            Description = Description,
            SerializerOptions = SerializerOptions,
            SchemaCreateOptions = SchemaCreateOptions,
        };
}

using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using ModelContextProtocol;
using ModelContextProtocol.Server;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Provides extension methods for configuring MCP servers with dependency injection.
/// </summary>
public static class McpServerServiceCollectionExtensions
{
    /// <summary>
    /// Adds the Model Context Protocol (MCP) server to the service collection with default options.
    /// </summary>
    /// <param name="services">The <see cref="IServiceCollection"/> to add the server to.</param>
    /// <param name="configureOptions">Optional callback to configure the <see cref="McpServerOptions"/>.</param>
    /// <returns>An <see cref="IMcpServerBuilder"/> that can be used to further configure the MCP server.</returns>

    public static IMcpServerBuilder AddMcpServer(this IServiceCollection services, Action<McpServerOptions>? configureOptions = null)
    {
        services.AddOptions();
        services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<McpServerOptions>, McpServerOptionsSetup>());
        if (configureOptions is not null)
        {
            services.Configure(configureOptions);
        }

        return new DefaultMcpServerBuilder(services);
    }
}

using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Server;

namespace ModelContextProtocol;

/// <summary>
/// Hosted service for a single-session (e.g. stdio) MCP server.
/// </summary>
/// <param name="session">The server representing the session being hosted.</param>
/// <param name="lifetime">
/// The host's application lifetime. If available, it will have termination requested when the session's run completes.
/// </param>
internal sealed class SingleSessionMcpServerHostedService(IMcpServer session, IHostApplicationLifetime? lifetime = null) : BackgroundService
{
    /// <inheritdoc />
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            await session.RunAsync(stoppingToken).ConfigureAwait(false);
        }
        finally
        {
            lifetime?.StopApplication();
        }
    }
}

using Microsoft.Extensions.AI;
using System.ComponentModel;
using System.Text.Json;

namespace ModelContextProtocol.Server;

/// <summary>
/// Provides options for controlling the creation of an <see cref="McpServerResource"/>.
/// </summary>
/// <remarks>
/// <para>
/// These options allow for customizing the behavior and metadata of resources created with
/// <see cref="M:McpServerResource.Create"/>. They provide control over naming, description,
/// and dependency injection integration.
/// </para>
/// <para>
/// When creating resources programmatically rather than using attributes, these options
/// provide the same level of configuration flexibility.
/// </para>
/// </remarks>
public sealed class McpServerResourceCreateOptions
{
    /// <summary>
    /// Gets or sets optional services used in the construction of the <see cref="McpServerResource"/>.
    /// </summary>
    /// <remarks>
    /// These services will be used to determine which parameters should be satisifed from dependency injection. As such,
    /// what services are satisfied via this provider should match what's satisfied via the provider passed in at invocation time.
    /// </remarks>
    public IServiceProvider? Services { get; set; }

    /// <summary>
    /// Gets or sets the URI template of the <see cref="McpServerResource"/>.
    /// </summary>
    /// <remarks>
    /// If <see langword="null"/>, but an <see cref="McpServerResourceAttribute"/> is applied to the member,
    /// the <see cref="McpServerResourceAttribute.UriTemplate"/> from the attribute will be used. If that's not present,
    /// a URI template will be inferred from the member's signature.
    /// </remarks>
    public string? UriTemplate { get; set; }

    /// <summary>
    /// Gets or sets the name to use for the <see cref="McpServerResource"/>.
    /// </summary>
    /// <remarks>
    /// If <see langword="null"/>, but an <see cref="McpServerResourceAttribute"/> is applied to the member,
    /// the name from the attribute will be used. If that's not present, a name based on the members's name will be used.
    /// </remarks>
    public string? Name { get; set; }

    /// <summary>
    /// Gets or sets the title to use for the <see cref="McpServerPrompt"/>.
    /// </summary>
    public string? Title { get; set; }

    /// <summary>
    /// Gets or set the description to use for the <see cref="McpServerResource"/>.
    /// </summary>
    /// <remarks>
    /// If <see langword="null"/>, but a <see cref="DescriptionAttribute"/> is applied to the member,
    /// the description from that attribute will be used.
    /// </remarks>
    public string? Description { get; set; }

    /// <summary>
    /// Gets or sets the MIME (media) type of the <see cref="McpServerResource"/>.
    /// </summary>
    public string? MimeType { get; set; }

    /// <summary>
    /// Gets or sets the JSON serializer options to use when marshalling data to/from JSON.
    /// </summary>
    /// <remarks>
    /// Defaults to <see cref="McpJsonUtilities.DefaultOptions"/> if left unspecified.
    /// </remarks>
    public JsonSerializerOptions? SerializerOptions { get; set; }

    /// <summary>
    /// Gets or sets the JSON schema options when creating <see cref="AIFunction"/> from a method.
    /// </summary>
    /// <remarks>
    /// Defaults to <see cref="AIJsonSchemaCreateOptions.Default"/> if left unspecified.
    /// </remarks>
    public AIJsonSchemaCreateOptions? SchemaCreateOptions { get; set; }

    /// <summary>
    /// Creates a shallow clone of the current <see cref="McpServerResourceCreateOptions"/> instance.
    /// </summary>
    internal McpServerResourceCreateOptions Clone() =>
        new McpServerResourceCreateOptions
        {
            Services = Services,
            UriTemplate = UriTemplate,
            Name = Name,
            Title = Title,
            Description = Description,
            MimeType = MimeType,
            SerializerOptions = SerializerOptions,
            SchemaCreateOptions = SchemaCreateOptions,
        };
}

using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Server;

/// <summary>
/// Provides a factory for creating <see cref="IMcpServer"/> instances.
/// </summary>
/// <remarks>
/// This is the recommended way to create <see cref="IMcpServer"/> instances.
/// The factory handles proper initialization of server instances with the required dependencies.
/// </remarks>
public static class McpServerFactory
{
    /// <summary>
    /// Creates a new instance of an <see cref="IMcpServer"/>.
    /// </summary>
    /// <param name="transport">Transport to use for the server representing an already-established MCP session.</param>
    /// <param name="serverOptions">Configuration options for this server, including capabilities. </param>
    /// <param name="loggerFactory">Logger factory to use for logging. If null, logging will be disabled.</param>
    /// <param name="serviceProvider">Optional service provider to create new instances of tools and other dependencies.</param>
    /// <returns>An <see cref="IMcpServer"/> instance that should be disposed when no longer needed.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="transport"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="serverOptions"/> is <see langword="null"/>.</exception>
    public static IMcpServer Create(
        ITransport transport,
        McpServerOptions serverOptions,
        ILoggerFactory? loggerFactory = null,
        IServiceProvider? serviceProvider = null)
    {
        Throw.IfNull(transport);
        Throw.IfNull(serverOptions);

        return new McpServer(transport, serverOptions, loggerFactory, serviceProvider);
    }
}

using ModelContextProtocol.Protocol;

namespace EverythingServer;

static class ResourceGenerator
{
    private static readonly List<Resource> _resources = Enumerable.Range(1, 100).Select(i =>
        {
            var uri = $"test://template/resource/{i}";
            if (i % 2 != 0)
            {
                return new Resource
                {
                    Uri = uri,
                    Name = $"Resource {i}",
                    MimeType = "text/plain",
                    Description = $"Resource {i}: This is a plaintext resource"
                };
            }
            else
            {
                var buffer = System.Text.Encoding.UTF8.GetBytes($"Resource {i}: This is a base64 blob");
                return new Resource
                {
                    Uri = uri,
                    Name = $"Resource {i}",
                    MimeType = "application/octet-stream",
                    Description = Convert.ToBase64String(buffer)
                };
            }
        }).ToList();

    public static IReadOnlyList<Resource> Resources => _resources;
}

using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace EverythingServer.Tools;

[McpServerToolType]
public class SampleLlmTool
{
    [McpServerTool(Name = "sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")]
    public static async Task<string> SampleLLM(
        IMcpServer server,
        [Description("The prompt to send to the LLM")] string prompt,
        [Description("Maximum number of tokens to generate")] int maxTokens,
        CancellationToken cancellationToken)
    {
        var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens);
        var sampleResult = await server.SampleAsync(samplingParams, cancellationToken);

        return $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}";
    }

    private static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100)
    {
        return new CreateMessageRequestParams
        {
            Messages = [new SamplingMessage
                {
                    Role = Role.User,
                    Content = new TextContentBlock { Text = $"Resource {uri} context: {context}" },
                }],
            SystemPrompt = "You are a helpful test server.",
            MaxTokens = maxTokens,
            Temperature = 0.7f,
            IncludeContext = ContextInclusion.ThisServer
        };
    }
}

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Provides constants with the names of common notification methods used in the MCP protocol.
/// </summary>
public static class NotificationMethods
{
    /// <summary>
    /// The name of notification sent by a server when the list of available tools changes.
    /// </summary>
    /// <remarks>
    /// This notification informs clients that the set of available tools has been modified.
    /// Changes may include tools being added, removed, or updated. Upon receiving this
    /// notification, clients may refresh their tool list by calling the appropriate
    /// method to get the updated list of tools.
    /// </remarks>
    public const string ToolListChangedNotification = "notifications/tools/list_changed";

    /// <summary>
    /// The name of the notification sent by the server when the list of available prompts changes.
    /// </summary>
    /// <remarks>
    /// This notification informs clients that the set of available prompts has been modified.
    /// Changes may include prompts being added, removed, or updated. Upon receiving this
    /// notification, clients may refresh their prompt list by calling the appropriate
    /// method to get the updated list of prompts.
    /// </remarks>
    public const string PromptListChangedNotification = "notifications/prompts/list_changed";

    /// <summary>
    /// The name of the notification sent by the server when the list of available resources changes.
    /// </summary>
    /// <remarks>
    /// This notification informs clients that the set of available resources has been modified.
    /// Changes may include resources being added, removed, or updated. Upon receiving this
    /// notification, clients may refresh their resource list by calling the appropriate
    /// method to get the updated list of resources.
    /// </remarks>
    public const string ResourceListChangedNotification = "notifications/resources/list_changed";

    /// <summary>
    /// The name of the notification sent by the server when a resource is updated.
    /// </summary>
    /// <remarks>
    /// This notification is used to inform clients about changes to a specific resource they have subscribed to.
    /// When a resource is updated, the server sends this notification to all clients that have subscribed to that resource.
    /// </remarks>
    public const string ResourceUpdatedNotification = "notifications/resources/updated";

    /// <summary>
    /// The name of the notification sent by the client when roots have been updated.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This notification informs the server that the client's "roots" have changed.
    /// Roots define the boundaries of where servers can operate within the filesystem,
    /// allowing them to understand which directories and files they have access to. Servers
    /// can request the list of roots from supporting clients and receive notifications when that list changes.
    /// </para>
    /// <para>
    /// After receiving this notification, servers may refresh their knowledge of roots by calling the appropriate
    /// method to get the updated list of roots from the client.
    /// </para>
    /// </remarks>
    public const string RootsListChangedNotification = "notifications/roots/list_changed";

    /// <summary>
    /// The name of the notification sent by the server when a log message is generated.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This notification is used by the server to send log messages to clients. Log messages can include
    /// different severity levels, such as debug, info, warning, or error, and an optional logger name to
    /// identify the source component.
    /// </para>
    /// <para>
    /// The minimum logging level that triggers notifications can be controlled by clients using the
    /// <see cref="RequestMethods.LoggingSetLevel"/> request. If no level has been set by a client,
    /// the server may determine which messages to send based on its own configuration.
    /// </para>
    /// </remarks>
    public const string LoggingMessageNotification = "notifications/message";

    /// <summary>
    /// The name of the notification sent from the client to the server after initialization has finished.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This notification is sent by the client after it has received and processed the server's response to the
    /// <see cref="RequestMethods.Initialize"/> request. It signals that the client is ready to begin normal operation
    /// and that the initialization phase is complete.
    /// </para>
    /// <para>
    /// After receiving this notification, the server can begin sending notifications and processing
    /// further requests from the client.
    /// </para>
    /// </remarks>
    public const string InitializedNotification = "notifications/initialized";

    /// <summary>
    /// The name of the notification sent to inform the receiver of a progress update for a long-running request.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This notification provides updates on the progress of long-running operations. It includes
    /// a progress token that associates the notification with a specific request, the current progress value,
    /// and optionally, a total value and a descriptive message.
    /// </para>
    /// <para>
    /// Progress notifications may be sent by either the client or the server, depending on the context.
    /// Progress notifications enable clients to display progress indicators for operations that might take
    /// significant time to complete, such as large file uploads, complex computations, or resource-intensive
    /// processing tasks.
    /// </para>
    /// </remarks>
    public const string ProgressNotification = "notifications/progress";

    /// <summary>
    /// The name of the notification sent to indicate that a previously-issued request should be canceled.
    /// </summary>
    /// <remarks>
    /// <para>
    /// From the issuer's perspective, the request should still be in-flight. However, due to communication latency,
    /// it is always possible that this notification may arrive after the request has already finished.
    /// </para>
    /// <para>
    /// This notification indicates that the result will be unused, so any associated processing SHOULD cease.
    /// </para>
    /// <para>
    /// A client must not attempt to cancel its `initialize` request.
    /// </para>
    /// </remarks>
    public const string CancelledNotification = "notifications/cancelled";
}

using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Server;

///
/// Used to indicate that a method or property should be considered an <see cref="McpServerResource"/>.
///
/// <remarks>
/// <para>
/// This attribute is applied to methods or properties that should be exposed as resources in the Model Context Protocol. When a class
/// containing methods marked with this attribute is registered with McpServerBuilderExtensions,
/// these methods or properties become available as resources that can be called by MCP clients.
/// </para>
/// <para>
/// When methods are provided directly to <see cref="M:McpServerResource.Create"/>, the attribute is not required.
/// </para>
/// <para>
/// Read resource requests do not contain separate arguments, only a URI. However, for templated resources, portions of that URI may be considered
/// as arguments and may be bound to parameters. Further, resource methods may accept parameters that will be bound to arguments based on their type.
/// <list type="bullet">
///   <item>
///     <description>
///       <see cref="CancellationToken"/> parameters are automatically bound to a <see cref="CancellationToken"/> provided by the
///       <see cref="IMcpServer"/> and that respects any <see cref="CancelledNotificationParams"/>s sent by the client for this operation's
///       <see cref="RequestId"/>.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IServiceProvider"/> parameters are bound from the <see cref="RequestContext{ReadResourceRequestParams}"/> for this request.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IMcpServer"/> parameters are bound directly to the <see cref="IMcpServer"/> instance associated
///       with this request's <see cref="RequestContext{ReadResourceRequestParams}"/>. Such parameters may be used to understand
///       what server is being used to process the request, and to interact with the client issuing the request to that server.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IProgress{ProgressNotificationValue}"/> parameters accepting <see cref="ProgressNotificationValue"/> values
///       are bound to an <see cref="IProgress{ProgressNotificationValue}"/> instance manufactured to forward progress notifications
///       from the resource to the client. If the client included a <see cref="ProgressToken"/> in their request, progress reports issued
///       to this instance will propagate to the client as <see cref="NotificationMethods.ProgressNotification"/> notifications with
///       that token. If the client did not include a <see cref="ProgressToken"/>, the instance will ignore any progress reports issued to it.
///     </description>
///   </item>
///   <item>
///     <description>
///       When the <see cref="McpServerResource"/> is constructed, it may be passed an <see cref="IServiceProvider"/> via
///       <see cref="McpServerResourceCreateOptions.Services"/>. Any parameter that can be satisfied by that <see cref="IServiceProvider"/>
///       according to <see cref="IServiceProviderIsService"/> will be resolved from the <see cref="IServiceProvider"/> provided to the
///       resource invocation rather than from the argument collection.
///     </description>
///   </item>
///   <item>
///     <description>
///       Any parameter attributed with <see cref="FromKeyedServicesAttribute"/> will similarly be resolved from the
///       <see cref="IServiceProvider"/> provided to the resource invocation rather than from the argument collection.
///     </description>
///   </item>
///   <item>
///     <description>
///       All other parameters are bound from the data in the URI.
///     </description>
///   </item>
/// </list>
/// </para>
/// <para>
/// Return values from a method are used to create the <see cref="ReadResourceResult"/> that is sent back to the client:
/// </para>
/// <list type="table">
///   <item>
///     <term><see cref="ResourceContents"/></term>
///     <description>Wrapped in a list containing the single <see cref="ResourceContents"/>.</description>
///   </item>
///   <item>
///     <term><see cref="TextContentBlock"/></term>
///     <description>Converted to a list containing a single <see cref="TextResourceContents"/>.</description>
///   </item>
///   <item>
///     <term><see cref="DataContent"/></term>
///     <description>Converted to a list containing a single <see cref="BlobResourceContents"/>.</description>
///   </item>
///   <item>
///     <term><see cref="string"/></term>
///     <description>Converted to a list containing a single <see cref="TextResourceContents"/>.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{ResourceContents}"/> of <see cref="ResourceContents"/></term>
///     <description>Returned directly as a list of <see cref="ResourceContents"/>.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{AIContent}"/> of <see cref="AIContent"/></term>
///     <description>Converted to a list containing a <see cref="TextResourceContents"/> for each <see cref="TextContentBlock"/> and a <see cref="BlobResourceContents"/> for each <see cref="DataContent"/>.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{String}"/> of <see cref="string"/></term>
///     <description>Converted to a list containing a <see cref="TextResourceContents"/>, one for each <see cref="string"/>.</description>
///   </item>
/// </list>
/// <para>
/// Other returned types will result in an <see cref="InvalidOperationException"/> being thrown.
/// </para>
/// </remarks>
[AttributeUsage(AttributeTargets.Method)]
public sealed class McpServerResourceAttribute : Attribute
{
    ///
    /// Initializes a new instance of the <see cref="McpServerResourceAttribute"/> class.
    ///
    public McpServerResourceAttribute()
    {
    }

    /// Gets or sets the URI template of the resource.
    /// <remarks>
    /// If <see langword="null"/>, a URI will be derived from <see cref="Name"/> and the method's parameter names.
    /// This template may, but doesn't have to, include parameters; if it does, this <see cref="McpServerResource"/>
    /// will be considered a "resource template", and if it doesn't, it will be considered a "direct resource".
    /// The former will be listed with <see cref="RequestMethods.ResourcesTemplatesList"/> requests and the latter
    /// with <see cref="RequestMethods.ResourcesList"/> requests.
    /// </remarks>
    public string? UriTemplate { get; set; }

    /// Gets or sets the name of the resource.
    /// <remarks>If <see langword="null"/>, the method name will be used.</remarks>
    public string? Name { get; set; }

    /// Gets or sets the title of the resource.
    public string? Title { get; set; }

    /// Gets or sets the MIME (media) type of the resource.
    public string? MimeType { get; set; }
}

using Microsoft.Extensions.Hosting;
using ModelContextProtocol;
using ModelContextProtocol.Server;

internal class SubscriptionMessageSender(IMcpServer server, HashSet<string> subscriptions) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            foreach (var uri in subscriptions)
            {
                await server.SendNotificationAsync("notifications/resource/updated",
                    new
                    {
                        Uri = uri,
                    }, cancellationToken: stoppingToken);
            }

            await Task.Delay(5000, stoppingToken);
        }
    }
}

using ModelContextProtocol;
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Globalization;
using System.Text.Json;

namespace QuickstartWeatherServer.Tools;

[McpServerToolType]
public sealed class WeatherTools
{
    [McpServerTool, Description("Get weather alerts for a US state.")]
    public static async Task<string> GetAlerts(
        HttpClient client,
        [Description("The US state to get alerts for. Use the 2 letter abbreviation for the state (e.g. NY).")] string state)
    {
        using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}");
        var jsonElement = jsonDocument.RootElement;
        var alerts = jsonElement.GetProperty("features").EnumerateArray();

        if (!alerts.Any())
        {
            return "No active alerts for this state.";
        }

        return string.Join("\n--\n", alerts.Select(alert =>
        {
            JsonElement properties = alert.GetProperty("properties");
            return $"""
                    Event: {properties.GetProperty("event").GetString()}
                    Area: {properties.GetProperty("areaDesc").GetString()}
                    Severity: {properties.GetProperty("severity").GetString()}
                    Description: {properties.GetProperty("description").GetString()}
                    Instruction: {properties.GetProperty("instruction").GetString()}
                    """;
        }));
    }

    [McpServerTool, Description("Get weather forecast for a location.")]
    public static async Task<string> GetForecast(
        HttpClient client,
        [Description("Latitude of the location.")] double latitude,
        [Description("Longitude of the location.")] double longitude)
    {
        var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
        using var jsonDocument = await client.ReadJsonDocumentAsync(pointUrl);
        var forecastUrl = jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
            ?? throw new Exception($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");

        using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl);
        var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();

        return string.Join("\n---\n", periods.Select(period => $"""
                {period.GetProperty("name").GetString()}
                Temperature: {period.GetProperty("temperature").GetInt32()}°F
                Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
                Forecast: {period.GetProperty("detailedForecast").GetString()}
                """));
    }
}

using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace EverythingServer.Tools;

[McpServerToolType]
public class AnnotatedMessageTool
{
    public enum MessageType
    {
        Error,
        Success,
        Debug,
    }

    [McpServerTool(Name = "annotatedMessage"), Description("Generates an annotated message")]
    public static IEnumerable<ContentBlock> AnnotatedMessage(MessageType messageType, bool includeImage = true)
    {
        List<ContentBlock> contents = messageType switch
        {
            MessageType.Error => [new TextContentBlock
            {
                Text = "Error: Operation failed",
                Annotations = new() { Audience = [Role.User, Role.Assistant], Priority = 1.0f }
            }],
            MessageType.Success => [new TextContentBlock
            {
                Text = "Operation completed successfully",
                Annotations = new() { Audience = [Role.User], Priority = 0.7f }
            }],
            MessageType.Debug => [new TextContentBlock
            {
                Text = "Debug: Cache hit ratio 0.95, latency 150ms",
                Annotations = new() { Audience = [Role.Assistant], Priority = 0.3f }
            }],
            _ => throw new ArgumentOutOfRangeException(nameof(messageType), messageType, null)
        };

        if (includeImage)
        {
            contents.Add(new ImageContentBlock
            {
                Data = TinyImageTool.MCP_TINY_IMAGE.Split(",").Last(),
                MimeType = "image/png",
                Annotations = new() { Audience = [Role.User], Priority = 0.5f }
            });
        }

        return contents;
    }
}

using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Protocol;
using System.Text.Json;

namespace ModelContextProtocol.Server;

///
/// Used to indicate that a method should be considered an <see cref="McpServerPrompt"/>.
///
/// <remarks>
/// <para>
/// This attribute is applied to methods that should be exposed as prompts in the Model Context Protocol. When a class
/// containing methods marked with this attribute is registered with McpServerBuilderExtensions,
/// these methods become available as prompts that can be called by MCP clients.
/// </para>
/// <para>
/// When methods are provided directly to <see cref="M:McpServerPrompt.Create"/>, the attribute is not required.
/// </para>
/// <para>
/// By default, parameters are sourced from the <see cref="GetPromptRequestParams.Arguments"/> dictionary, which is a collection
/// of key/value pairs. Those parameters are deserialized from the
/// <see cref="JsonElement"/> values in that collection. There are a few exceptions to this:
/// <list type="bullet">
///   <item>
///     <description>
///       <see cref="CancellationToken"/> parameters are automatically bound to a <see cref="CancellationToken"/> provided by the
///       <see cref="IMcpServer"/> and that respects any <see cref="CancelledNotificationParams"/>s sent by the client for this operation's
///       <see cref="RequestId"/>.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IServiceProvider"/> parameters are bound from the <see cref="RequestContext{GetPromptRequestParams}"/> for this request.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IMcpServer"/> parameters are bound directly to the <see cref="IMcpServer"/> instance associated
///       with this request's <see cref="RequestContext{CallPromptRequestParams}"/>. Such parameters may be used to understand
///       what server is being used to process the request, and to interact with the client issuing the request to that server.
///     </description>
///   </item>
///   <item>
///     <description>
///       <see cref="IProgress{ProgressNotificationValue}"/> parameters accepting <see cref="ProgressNotificationValue"/> values
///       are bound to an <see cref="IProgress{ProgressNotificationValue}"/> instance manufactured to forward progress notifications
///       from the prompt to the client. If the client included a <see cref="ProgressToken"/> in their request, progress reports issued
///       to this instance will propagate to the client as <see cref="NotificationMethods.ProgressNotification"/> notifications with
///       that token. If the client did not include a <see cref="ProgressToken"/>, the instance will ignore any progress reports issued to it.
///     </description>
///   </item>
///   <item>
///     <description>
///       When the <see cref="McpServerPrompt"/> is constructed, it may be passed an <see cref="IServiceProvider"/> via
///       <see cref="McpServerPromptCreateOptions.Services"/>. Any parameter that can be satisfied by that <see cref="IServiceProvider"/>
///       according to <see cref="IServiceProviderIsService"/> will be resolved from the <see cref="IServiceProvider"/> provided to the
///       prompt invocation rather than from the argument collection.
///     </description>
///   </item>
///   <item>
///     <description>
///       Any parameter attributed with <see cref="FromKeyedServicesAttribute"/> will similarly be resolved from the
///       <see cref="IServiceProvider"/> provided to the prompt invocation rather than from the argument collection.
///     </description>
///   </item>
/// </list>
/// </para>
/// <para>
/// All other parameters are deserialized from the <see cref="JsonElement"/>s in the <see cref="GetPromptRequestParams.Arguments"/> dictionary.
/// </para>
/// <para>
/// In general, the data supplied via the <see cref="GetPromptRequestParams.Arguments"/>'s dictionary is passed along from the caller and
/// should thus be considered unvalidated and untrusted. To provide validated and trusted data to the invocation of the prompt, consider having
/// the prompt be an instance method, referring to data stored in the instance, or using an instance or parameters resolved from the <see cref="IServiceProvider"/>
/// to provide data to the method.
/// </para>
/// <para>
/// Return values from a method are used to create the <see cref="GetPromptResult"/> that is sent back to the client:
/// </para>
/// <list type="table">
///   <item>
///     <term><see cref="string"/></term>
///     <description>Converted to a list containing a single <see cref="PromptMessage"/> with its <see cref="PromptMessage.Content"/> set to contain the <see cref="string"/>.</description>
///   </item>
///   <item>
///     <term><see cref="PromptMessage"/></term>
///     <description>Converted to a list containing the single <see cref="PromptMessage"/>.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{PromptMessage}"/> of <see cref="PromptMessage"/></term>
///     <description>Converted to a list containing all of the returned <see cref="PromptMessage"/> instances.</description>
///   </item>
///   <item>
///     <term><see cref="ChatMessage"/></term>
///     <description>Converted to a list of <see cref="PromptMessage"/> instances derived from the <see cref="ChatMessage"/> with <see cref="AIContentExtensions.ToPromptMessages"/>.</description>
///   </item>
///   <item>
///     <term><see cref="IEnumerable{ChatMessage}"/> of <see cref="ChatMessage"/></term>
///     <description>Converted to a list of <see cref="PromptMessage"/> instances derived from all of the <see cref="ChatMessage"/> instances with <see cref="AIContentExtensions.ToPromptMessages"/>.</description>
///   </item>
/// </list>
/// <para>
/// Other returned types will result in an <see cref="InvalidOperationException"/> being thrown.
/// </para>
/// </remarks>
[AttributeUsage(AttributeTargets.Method)]
public sealed class McpServerPromptAttribute : Attribute
{
    ///
    /// Initializes a new instance of the <see cref="McpServerPromptAttribute"/> class.
    ///
    public McpServerPromptAttribute()
    {
    }

    /// Gets the name of the prompt.
    /// <remarks>If <see langword="null"/>, the method name will be used.</remarks>
    public string? Name { get; set; }

    /// Gets or sets the title of the prompt.
    public string? Title { get; set; }
}

using Microsoft.AspNetCore.Authentication;
using ModelContextProtocol.AspNetCore.Authentication;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Extension methods for adding MCP authorization support to ASP.NET Core applications.
/// </summary>
public static class McpAuthenticationExtensions
{
    /// <summary>
    /// Adds MCP authorization support to the application.
    /// </summary>
    /// <param name="builder">The authentication builder.</param>
    /// <param name="configureOptions">An action to configure MCP authentication options.</param>
    /// <returns>The authentication builder for chaining.</returns>
    public static AuthenticationBuilder AddMcp(
        this AuthenticationBuilder builder,
        Action<McpAuthenticationOptions>? configureOptions = null)
    {
        return AddMcp(
            builder,
            McpAuthenticationDefaults.AuthenticationScheme,
            McpAuthenticationDefaults.DisplayName,
            configureOptions);
    }

    /// <summary>
    /// Adds MCP authorization support to the application with a custom scheme name.
    /// </summary>
    /// <param name="builder">The authentication builder.</param>
    /// <param name="authenticationScheme">The authentication scheme name to use.</param>
    /// <param name="displayName">The display name for the authentication scheme.</param>
    /// <param name="configureOptions">An action to configure MCP authentication options.</param>
    /// <returns>The authentication builder for chaining.</returns>
    public static AuthenticationBuilder AddMcp(
        this AuthenticationBuilder builder,
        string authenticationScheme,
        string displayName,
        Action<McpAuthenticationOptions>? configureOptions = null)
    {
        return builder.AddScheme<McpAuthenticationOptions, McpAuthenticationHandler>(
            authenticationScheme,
            displayName,
            configureOptions);
    }
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET
namespace System.Diagnostics.CodeAnalysis;

/// <summary>
/// Indicates that certain members on a specified <see cref="Type"/> are accessed dynamically,
/// for example through <see cref="Reflection"/>.
/// </summary>
/// <remarks>
/// This allows tools to understand which members are being accessed during the execution
/// of a program.
///
/// This attribute is valid on members whose type is <see cref="Type"/> or <see cref="string"/>.
///
/// When this attribute is applied to a location of type <see cref="string"/>, the assumption is
/// that the string represents a fully qualified type name.
///
/// When this attribute is applied to a class, interface, or struct, the members specified
/// can be accessed dynamically on <see cref="Type"/> instances returned from calling
/// <see cref="object.GetType"/> on instances of that class, interface, or struct.
///
/// If the attribute is applied to a method it's treated as a special case and it implies
/// the attribute should be applied to the "this" parameter of the method. As such the attribute
/// should only be used on instance methods of types assignable to System.Type (or string, but no methods
/// will use it there).
/// </remarks>
[AttributeUsage(
    AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter |
    AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method |
    AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct,
    Inherited = false)]
internal sealed class DynamicallyAccessedMembersAttribute : Attribute
{
    /// <summary>
    /// Initializes a new instance of the <see cref="DynamicallyAccessedMembersAttribute"/> class
    /// with the specified member types.
    /// </summary>
    /// <param name="memberTypes">The types of members dynamically accessed.</param>
    public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes)
    {
        MemberTypes = memberTypes;
    }

    /// <summary>
    /// Gets the <see cref="DynamicallyAccessedMemberTypes"/> which specifies the type
    /// of members dynamically accessed.
    /// </summary>
    public DynamicallyAccessedMemberTypes MemberTypes { get; }
}
#endif

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET
namespace System.Diagnostics.CodeAnalysis;

/// <summary>
/// /// Suppresses reporting of a specific rule violation, allowing multiple suppressions on a
/// single code artifact.
/// </summary>
/// <remarks>
/// <see cref="UnconditionalSuppressMessageAttribute"/> is different than
/// <see cref="SuppressMessageAttribute"/> in that it doesn't have a
/// <see cref="ConditionalAttribute"/>. So it is always preserved in the compiled assembly.
/// </remarks>
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
internal sealed class UnconditionalSuppressMessageAttribute : Attribute
{
    /// <summary>
    /// Initializes a new instance of the <see cref="UnconditionalSuppressMessageAttribute"/>
    /// class, specifying the category of the tool and the identifier for an analysis rule.
    /// </summary>
    /// <param name="category">The category for the attribute.</param>
    /// <param name="checkId">The identifier of the analysis rule the attribute applies to.</param>
    public UnconditionalSuppressMessageAttribute(string category, string checkId)
    {
        Category = category;
        CheckId = checkId;
    }

    /// <summary>
    /// Gets the category identifying the classification of the attribute.
    /// </summary>
    /// <remarks>
    /// The <see cref="Category"/> property describes the tool or tool analysis category
    /// for which a message suppression attribute applies.
    /// </remarks>
    public string Category { get; }

    /// <summary>
    /// Gets the identifier of the analysis tool rule to be suppressed.
    /// </summary>
    /// <remarks>
    /// Concatenated together, the <see cref="Category"/> and <see cref="CheckId"/>
    /// properties form a unique check identifier.
    /// </remarks>
    public string CheckId { get; }

    /// <summary>
    /// Gets or sets the scope of the code that is relevant for the attribute.
    /// </summary>
    /// <remarks>
    /// The Scope property is an optional argument that specifies the metadata scope for which
    /// the attribute is relevant.
    /// </remarks>
    public string? Scope { get; set; }

    /// <summary>
    /// Gets or sets a fully qualified path that represents the target of the attribute.
    /// </summary>
    /// <remarks>
    /// The <see cref="Target"/> property is an optional argument identifying the analysis target
    /// of the attribute. An example value is "System.IO.Stream.ctor():System.Void".
    /// Because it is fully qualified, it can be long, particularly for targets such as parameters.
    /// The analysis tool user interface should be capable of automatically formatting the parameter.
    /// </remarks>
    public string? Target { get; set; }

    /// <summary>
    /// Gets or sets an optional argument expanding on exclusion criteria.
    /// </summary>
    /// <remarks>
    /// The <see cref="MessageId"/> property is an optional argument that specifies additional
    /// exclusion where the literal metadata target is not sufficiently precise. For example,
    /// the <see cref="UnconditionalSuppressMessageAttribute"/> cannot be applied within a method,
    /// and it may be desirable to suppress a violation against a statement in the method that will
    /// give a rule violation, but not against all statements in the method.
    /// </remarks>
    public string? MessageId { get; set; }

    /// <summary>
    /// Gets or sets the justification for suppressing the code analysis message.
    /// </summary>
    public string? Justification { get; set; }
}
#endif

using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Server;

/// Augments a service provider with additional request-related services.
internal sealed class RequestServiceProvider<TRequestParams>(
    RequestContext<TRequestParams> request, IServiceProvider? innerServices) :
    IServiceProvider,  IKeyedServiceProvider,
    IServiceProviderIsService, IServiceProviderIsKeyedService,
    IDisposable,  IAsyncDisposable
    where TRequestParams : RequestParams
{
    /// Gets the request associated with this instance.
    public RequestContext<TRequestParams> Request => request;

    /// Gets whether the specified type is in the list of additional types this service provider wraps around the one in a provided request's services.
    public static bool IsAugmentedWith(Type serviceType) =>
        serviceType == typeof(RequestContext<TRequestParams>) ||
        serviceType == typeof(IMcpServer) ||
        serviceType == typeof(IProgress<ProgressNotificationValue>);

    /// <inheritdoc />
    public object? GetService(Type serviceType) =>
        serviceType == typeof(RequestContext<TRequestParams>) ? request :
        serviceType == typeof(IMcpServer) ? request.Server :
        serviceType == typeof(IProgress<ProgressNotificationValue>) ?
            (request.Params?.ProgressToken is { } progressToken ? new TokenProgress(request.Server, progressToken) : NullProgress.Instance) :
        innerServices?.GetService(serviceType);

    /// <inheritdoc />
    public bool IsService(Type serviceType) =>
        IsAugmentedWith(serviceType) ||
        (innerServices as IServiceProviderIsService)?.IsService(serviceType) is true;

    /// <inheritdoc />
    public bool IsKeyedService(Type serviceType, object? serviceKey) =>
        (serviceKey is null && IsService(serviceType)) ||
        (innerServices as IServiceProviderIsKeyedService)?.IsKeyedService(serviceType, serviceKey) is true;

    /// <inheritdoc />
    public object? GetKeyedService(Type serviceType, object? serviceKey) =>
        serviceKey is null ? GetService(serviceType) :
        (innerServices as IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey);

    /// <inheritdoc />
    public object GetRequiredKeyedService(Type serviceType, object? serviceKey) =>
        GetKeyedService(serviceType, serviceKey) ??
        throw new InvalidOperationException($"No service of type '{serviceType}' with key '{serviceKey}' is registered.");

    /// <inheritdoc />
    public void Dispose() =>
        (innerServices as IDisposable)?.Dispose();

    /// <inheritdoc />
    public ValueTask DisposeAsync() =>
        innerServices is IAsyncDisposable asyncDisposable ? asyncDisposable.DisposeAsync() : default;
}

using Microsoft.AspNetCore.Authentication;
using ModelContextProtocol.Authentication;

namespace ModelContextProtocol.AspNetCore.Authentication;

/// <summary>
/// Options for the MCP authentication handler.
/// </summary>
public class McpAuthenticationOptions : AuthenticationSchemeOptions
{
    private static readonly Uri DefaultResourceMetadataUri = new("/.well-known/oauth-protected-resource", UriKind.Relative);

    /// <summary>
    /// Initializes a new instance of the <see cref="McpAuthenticationOptions"/> class.
    /// </summary>
    public McpAuthenticationOptions()
    {
        // "Bearer" is JwtBearerDefaults.AuthenticationScheme, but we don't have a reference to the JwtBearer package here.
        ForwardAuthenticate = "Bearer";
        ResourceMetadataUri = DefaultResourceMetadataUri;
        Events = new McpAuthenticationEvents();
    }

    /// <summary>
    /// Gets or sets the events used to handle authentication events.
    /// </summary>
    public new McpAuthenticationEvents Events
    {
        get { return (McpAuthenticationEvents)base.Events!; }
        set { base.Events = value; }
    }

    /// <summary>
    /// The URI to the resource metadata document.
    /// </summary>
    /// <remarks>
    /// This URI will be included in the WWW-Authenticate header when a 401 response is returned.
    /// </remarks>
    public Uri ResourceMetadataUri { get; set; }

    /// <summary>
    /// Gets or sets the protected resource metadata.
    /// </summary>
    /// <remarks>
    /// This contains the OAuth metadata for the protected resource, including authorization servers,
    /// supported scopes, and other information needed for clients to authenticate.
    /// </remarks>
    public ProtectedResourceMetadata? ResourceMetadata { get; set; }
}

using ModelContextProtocol;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Default implementation of <see cref="IMcpServerBuilder"/> that enables fluent configuration
/// of the Model Context Protocol (MCP) server. This builder is returned by the
/// <see cref="McpServerServiceCollectionExtensions.AddMcpServer"/> extension method and
/// provides access to the service collection for registering additional MCP components.
/// </summary>
internal sealed class DefaultMcpServerBuilder : IMcpServerBuilder
{
    /// <inheritdoc/>
    public IServiceCollection Services { get; }

    /// <summary>
    /// Initializes a new instance of the <see cref="DefaultMcpServerBuilder"/> class.
    /// </summary>
    /// <param name="services">The service collection to which MCP server services will be added. This collection
    /// is exposed through the <see cref="Services"/> property to allow additional configuration.</param>
    /// <exception cref="ArgumentNullException">Thrown when <paramref name="services"/> is null.</exception>
    public DefaultMcpServerBuilder(IServiceCollection services)
    {
        Throw.IfNull(services);

        Services = services;
    }
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET
namespace System.Runtime.CompilerServices
{
    /// <summary>
    /// Indicates that compiler support for a particular feature is required for the location where this attribute is applied.
    /// </summary>
    [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
    internal sealed class CompilerFeatureRequiredAttribute : Attribute
    {
        public CompilerFeatureRequiredAttribute(string featureName)
        {
            FeatureName = featureName;
        }

        /// <summary>
        /// The name of the compiler feature.
        /// </summary>
        public string FeatureName { get; }

        /// <summary>
        /// If true, the compiler can choose to allow access to the location where this attribute is applied if it does not understand <see cref="FeatureName"/>.
        /// </summary>
        public bool IsOptional { get; init; }

        /// <summary>
        /// The <see cref="FeatureName"/> used for the required members C# feature.
        /// </summary>
        public const string RequiredMembers = nameof(RequiredMembers);
    }
}
#endif

using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Client;

/// <summary>
/// Represents a transport mechanism for Model Context Protocol (MCP) client-to-server communication.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="IClientTransport"/> interface abstracts the communication layer between MCP clients
/// and servers, allowing different transport protocols to be used interchangeably.
/// </para>
/// <para>
/// When creating an <see cref="IMcpClient"/>, <see cref="McpClientFactory"/> is typically used, and is
/// provided with the <see cref="IClientTransport"/> based on expected server configuration.
/// </para>
/// </remarks>
public interface IClientTransport
{
    /// <summary>
    /// Gets a transport identifier, used for logging purposes.
    /// </summary>
    string Name { get; }

    /// <summary>
    /// Asynchronously establishes a transport session with an MCP server and returns a transport for the duplex message stream.
    /// </summary>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>Returns an interface for the duplex message stream.</returns>
    /// <remarks>
    /// <para>
    /// This method is responsible for initializing the connection to the server using the specific transport
    /// mechanism implemented by the derived class. The returned <see cref="ITransport"/> interface
    /// provides methods to send and receive messages over the established connection.
    /// </para>
    /// <para>
    /// The lifetime of the returned <see cref="ITransport"/> instance is typically managed by the
    /// <see cref="McpClient"/> that uses this transport. When the client is disposed, it will dispose
    /// the transport session as well.
    /// </para>
    /// <para>
    /// This method is used by <see cref="McpClientFactory"/> to initialize the connection.
    /// </para>
    /// </remarks>
    /// <exception cref="InvalidOperationException">The transport connection could not be established.</exception>
    Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default);
}

using Microsoft.Extensions.AI;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace TestServerWithHosting.Tools;

/// <summary>
/// This tool uses dependency injection and async method
/// </summary>
[McpServerToolType]
public sealed class SampleLlmTool
{
    [McpServerTool(Name = "sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")]
    public static async Task<string> SampleLLM(
        IMcpServer thisServer,
        [Description("The prompt to send to the LLM")] string prompt,
        [Description("Maximum number of tokens to generate")] int maxTokens,
        CancellationToken cancellationToken)
    {
        ChatOptions options = new()
        {
            Instructions = "You are a helpful test server.",
            MaxOutputTokens = maxTokens,
            Temperature = 0.7f,
        };

        var samplingResponse = await thisServer.AsSamplingChatClient().GetResponseAsync(prompt, options, cancellationToken);

        return $"LLM sampling result: {samplingResponse}";
    }
}

using Microsoft.Extensions.AI;
using ModelContextProtocol.Client;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the capability for a client to generate text or other content using an AI model.
/// </summary>
/// <remarks>
/// <para>
/// This capability enables the MCP client to respond to sampling requests from an MCP server.
/// </para>
/// <para>
/// When this capability is enabled, an MCP server can request the client to generate content
/// using an AI model. The client must set a <see cref="SamplingHandler"/> to process these requests.
/// </para>
/// </remarks>
public sealed class SamplingCapability
{
    // Currently empty in the spec, but may be extended in the future

    /// <summary>
    /// Gets or sets the handler for processing <see cref="RequestMethods.SamplingCreateMessage"/> requests.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This handler function is called when an MCP server requests the client to generate content
    /// using an AI model. The client must set this property for the sampling capability to work.
    /// </para>
    /// <para>
    /// The handler receives message parameters, a progress reporter for updates, and a
    /// cancellation token. It should return a <see cref="CreateMessageResult"/> containing the
    /// generated content.
    /// </para>
    /// <para>
    /// You can create a handler using the <see cref="McpClientExtensions.CreateSamplingHandler"/> extension
    /// method with any implementation of <see cref="IChatClient"/>.
    /// </para>
    /// </remarks>
    [JsonIgnore]
    public Func<CreateMessageRequestParams?, IProgress<ProgressNotificationValue>, CancellationToken, ValueTask<CreateMessageResult>>? SamplingHandler { get; set; }
}

using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Server;

/// <summary>
/// Provides configuration options for the MCP server.
/// </summary>
public sealed class McpServerOptions
{
    /// <summary>
    /// Gets or sets information about this server implementation, including its name and version.
    /// </summary>
    /// <remarks>
    /// This information is sent to the client during initialization to identify the server.
    /// It's displayed in client logs and can be used for debugging and compatibility checks.
    /// </remarks>
    public Implementation? ServerInfo { get; set; }

    /// <summary>
    /// Gets or sets server capabilities to advertise to the client.
    /// </summary>
    /// <remarks>
    /// These determine which features will be available when a client connects.
    /// Capabilities can include "tools", "prompts", "resources", "logging", and other
    /// protocol-specific functionality.
    /// </remarks>
    public ServerCapabilities? Capabilities { get; set; }

    /// <summary>
    /// Gets or sets the protocol version supported by this server, using a date-based versioning scheme.
    /// </summary>
    /// <remarks>
    /// The protocol version defines which features and message formats this server supports.
    /// This uses a date-based versioning scheme in the format "YYYY-MM-DD".
    /// If <see langword="null"/>, the server will advertize to the client the version requested
    /// by the client if that version is known to be supported, and otherwise will advertize the latest
    /// version supported by the server.
    /// </remarks>
    public string? ProtocolVersion { get; set; }

    /// <summary>
    /// Gets or sets a timeout used for the client-server initialization handshake sequence.
    /// </summary>
    /// <remarks>
    /// This timeout determines how long the server will wait for client responses during
    /// the initialization protocol handshake. If the client doesn't respond within this timeframe,
    /// the initialization process will be aborted.
    /// </remarks>
    public TimeSpan InitializationTimeout { get; set; } = TimeSpan.FromSeconds(60);

    /// <summary>
    /// Gets or sets optional server instructions to send to clients.
    /// </summary>
    /// <remarks>
    /// These instructions are sent to clients during the initialization handshake and provide
    /// guidance on how to effectively use the server's capabilities. They can include details
    /// about available tools, expected input formats, limitations, or other helpful information.
    /// Client applications typically use these instructions as system messages for LLM interactions
    /// to provide context about available functionality.
    /// </remarks>
    public string? ServerInstructions { get; set; }

    /// <summary>
    /// Gets or sets whether to create a new service provider scope for each handled request.
    /// </summary>
    /// <remarks>
    /// The default is <see langword="true"/>. When <see langword="true"/>, each invocation of a request
    /// handler will be invoked within a new service scope.
    /// </remarks>
    public bool ScopeRequests { get; set; } = true;

    /// <summary>
    /// Gets or sets preexisting knowledge about the client including its name and version to help support
    /// stateless Streamable HTTP servers that encode this knowledge in the mcp-session-id header.
    /// </summary>
    /// <remarks>
    /// <para>
    /// When not specified, this information is sourced from the client's initialize request.
    /// </para>
    /// </remarks>
    public Implementation? KnownClientInfo { get; set; }
}

namespace ModelContextProtocol.Authentication;

/// <summary>
/// Provides configuration options for the <see cref="ClientOAuthProvider"/>.
/// </summary>
public sealed class ClientOAuthOptions
{
    /// <summary>
    /// Gets or sets the OAuth redirect URI.
    /// </summary>
    public required Uri RedirectUri { get; set; }

    /// <summary>
    /// Gets or sets the OAuth client ID. If not provided, the client will attempt to register dynamically.
    /// </summary>
    public string? ClientId { get; set; }

    /// <summary>
    /// Gets or sets the OAuth client secret.
    /// </summary>
    /// <remarks>
    /// This is optional for public clients or when using PKCE without client authentication.
    /// </remarks>
    public string? ClientSecret { get; set; }

    /// <summary>
    /// Gets or sets the OAuth scopes to request.
    /// </summary>
    /// <remarks>
    /// <para>
    /// When specified, these scopes will be used instead of the scopes advertised by the protected resource.
    /// If not specified, the provider will use the scopes from the protected resource metadata.
    /// </para>
    /// <para>
    /// Common OAuth scopes include "openid", "profile", "email", etc.
    /// </para>
    /// </remarks>
    public IEnumerable<string>? Scopes { get; set; }

    /// <summary>
    /// Gets or sets the authorization redirect delegate for handling the OAuth authorization flow.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This delegate is responsible for handling the OAuth authorization URL and obtaining the authorization code.
    /// If not specified, a default implementation will be used that prompts the user to enter the code manually.
    /// </para>
    /// <para>
    /// Custom implementations might open a browser, start an HTTP listener, or use other mechanisms to capture
    /// the authorization code from the OAuth redirect.
    /// </para>
    /// </remarks>
    public AuthorizationRedirectDelegate? AuthorizationRedirectDelegate { get; set; }

    /// <summary>
    /// Gets or sets the authorization server selector function.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This function is used to select which authorization server to use when multiple servers are available.
    /// If not specified, the first available server will be selected.
    /// </para>
    /// <para>
    /// The function receives a list of available authorization server URIs and should return the selected server,
    /// or null if no suitable server is found.
    /// </para>
    /// </remarks>
    public Func<IReadOnlyList<Uri>, Uri?>? AuthServerSelector { get; set; }

    /// <summary>
    /// Gets or sets the client name to use during dynamic client registration.
    /// </summary>
    /// <remarks>
    /// This is a human-readable name for the client that may be displayed to users during authorization.
    /// Only used when a <see cref="ClientId"/> is not specified.
    /// </remarks>
    public string? ClientName { get; set; }

    /// <summary>
    /// Gets or sets the client URI to use during dynamic client registration.
    /// </summary>
    /// <remarks>
    /// This should be a URL pointing to the client's home page or information page.
    /// Only used when a <see cref="ClientId"/> is not specified.
    /// </remarks>
    public Uri? ClientUri { get; set; }

    /// <summary>
    /// Gets or sets additional parameters to include in the query string of the OAuth authorization request
    /// providing extra information or fulfilling specific requirements of the OAuth provider.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Parameters specified cannot override or append to any automatically set parameters like the "redirect_uri"
    /// which should instead be configured via <see cref="RedirectUri"/>.
    /// </para>
    /// </remarks>
    public IDictionary<string, string> AdditionalAuthorizationParameters { get; set; } = new Dictionary<string, string>();
}

using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Client;

/// <summary>
/// Provides configuration options for creating <see cref="IMcpClient"/> instances.
/// </summary>
/// <remarks>
/// These options are typically passed to <see cref="McpClientFactory.CreateAsync"/> when creating a client.
/// They define client capabilities, protocol version, and other client-specific settings.
/// </remarks>
public sealed class McpClientOptions
{
    /// <summary>
    /// Gets or sets information about this client implementation, including its name and version.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This information is sent to the server during initialization to identify the client.
    /// It's often displayed in server logs and can be used for debugging and compatibility checks.
    /// </para>
    /// <para>
    /// When not specified, information sourced from the current process will be used.
    /// </para>
    /// </remarks>
    public Implementation? ClientInfo { get; set; }

    /// <summary>
    /// Gets or sets the client capabilities to advertise to the server.
    /// </summary>
    public ClientCapabilities? Capabilities { get; set; }

    /// <summary>
    /// Gets or sets the protocol version to request from the server, using a date-based versioning scheme.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The protocol version is a key part of the initialization handshake. The client and server must
    /// agree on a compatible protocol version to communicate successfully.
    /// </para>
    /// <para>
    /// If non-<see langword="null"/>, this version will be sent to the server, and the handshake
    /// will fail if the version in the server's response does not match this version.
    /// If <see langword="null"/>, the client will request the latest version supported by the server
    /// but will allow any supported version that the server advertizes in its response.
    /// </para>
    /// </remarks>
    public string? ProtocolVersion { get; set; }

    /// <summary>
    /// Gets or sets a timeout for the client-server initialization handshake sequence.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This timeout determines how long the client will wait for the server to respond during
    /// the initialization protocol handshake. If the server doesn't respond within this timeframe,
    /// an exception will be thrown.
    /// </para>
    /// <para>
    /// Setting an appropriate timeout prevents the client from hanging indefinitely when
    /// connecting to unresponsive servers.
    /// </para>
    /// <para>The default value is 60 seconds.</para>
    /// </remarks>
    public TimeSpan InitializationTimeout { get; set; } = TimeSpan.FromSeconds(60);
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET
namespace System.Diagnostics.CodeAnalysis;

/// <summary>
/// Indicates that the specified method requires dynamic access to code that is not referenced
/// statically, for example through <see cref="Reflection"/>.
/// </summary>
/// <remarks>
/// This allows tools to understand which methods are unsafe to call when removing unreferenced
/// code from an application.
/// </remarks>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)]
internal sealed class RequiresUnreferencedCodeAttribute : Attribute
{
    /// <summary>
    /// Initializes a new instance of the <see cref="RequiresUnreferencedCodeAttribute"/> class
    /// with the specified message.
    /// </summary>
    /// <param name="message">
    /// A message that contains information about the usage of unreferenced code.
    /// </param>
    public RequiresUnreferencedCodeAttribute(string message)
    {
        Message = message;
    }

    /// <summary>
    /// Gets a message that contains information about the usage of unreferenced code.
    /// </summary>
    public string Message { get; }

    /// <summary>
    /// Gets or sets an optional URL that contains more information about the method,
    /// why it requires unreferenced code, and what options a consumer has to deal with it.
    /// </summary>
    public string? Url { get; set; }
}
#endif

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a client capability that enables root resource discovery in the Model Context Protocol.
/// </summary>
/// <remarks>
/// <para>
/// When present in <see cref="ClientCapabilities"/>, it indicates that the client supports listing
/// root URIs that serve as entry points for resource navigation.
/// </para>
/// <para>
/// The roots capability establishes a mechanism for servers to discover and access the hierarchical
/// structure of resources provided by a client. Root URIs represent top-level entry points from which
/// servers can navigate to access specific resources.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class RootsCapability
{
    /// <summary>
    /// Gets or sets whether the client supports notifications for changes to the roots list.
    /// </summary>
    /// <remarks>
    /// When set to <see langword="true"/>, the client can notify servers when roots are added,
    /// removed, or modified, allowing servers to refresh their roots cache accordingly.
    /// This enables servers to stay synchronized with client-side changes to available roots.
    /// </remarks>
    [JsonPropertyName("listChanged")]
    public bool? ListChanged { get; set; }

    /// <summary>
    /// Gets or sets the handler for <see cref="RequestMethods.RootsList"/> requests.
    /// </summary>
    /// <remarks>
    /// This handler is invoked when a client sends a <see cref="RequestMethods.RootsList"/> request to retrieve available roots.
    /// The handler receives request parameters and should return a <see cref="ListRootsResult"/> containing the collection of available roots.
    /// </remarks>
    [JsonIgnore]
    public Func<ListRootsRequestParams?, CancellationToken, ValueTask<ListRootsResult>>? RootsHandler { get; set; }
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET
namespace System.Diagnostics.CodeAnalysis;

/// <summary>
/// Indicates that the specified method requires the ability to generate new code at runtime,
/// for example through <see cref="Reflection"/>.
/// </summary>
/// <remarks>
/// This allows tools to understand which methods are unsafe to call when compiling ahead of time.
/// </remarks>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)]
internal sealed class RequiresDynamicCodeAttribute : Attribute
{
    /// <summary>
    /// Initializes a new instance of the <see cref="RequiresDynamicCodeAttribute"/> class
    /// with the specified message.
    /// </summary>
    /// <param name="message">
    /// A message that contains information about the usage of dynamic code.
    /// </param>
    public RequiresDynamicCodeAttribute(string message)
    {
        Message = message;
    }

    /// <summary>
    /// Gets a message that contains information about the usage of dynamic code.
    /// </summary>
    public string Message { get; }

    /// <summary>
    /// Gets or sets an optional URL that contains more information about the method,
    /// why it requires dynamic code, and what options a consumer has to deal with it.
    /// </summary>
    public string? Url { get; set; }
}
#endif

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents additional properties describing a <see cref="Tool"/> to clients.
/// </summary>
/// <remarks>
/// All properties in <see cref="ToolAnnotations"/> are hints.
/// They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).
/// Clients should never make tool use decisions based on <see cref="ToolAnnotations"/> received from untrusted servers.
/// </remarks>
public sealed class ToolAnnotations
{
    /// <summary>
    /// Gets or sets a human-readable title for the tool that can be displayed to users.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The title provides a more descriptive, user-friendly name for the tool than the tool's
    /// programmatic name. It is intended for display purposes and to help users understand
    /// the tool's purpose at a glance.
    /// </para>
    /// <para>
    /// Unlike the tool name (which follows programmatic naming conventions), the title can
    /// include spaces, special characters, and be phrased in a more natural language style.
    /// </para>
    /// </remarks>
    [JsonPropertyName("title")]
    public string? Title { get; set; }

    /// <summary>
    /// Gets or sets whether the tool may perform destructive updates to its environment.
    /// </summary>
    /// <remarks>
    /// <para>
    /// If <see langword="true"/>, the tool may perform destructive updates to its environment.
    /// If <see langword="false"/>, the tool performs only additive updates.
    /// This property is most relevant when the tool modifies its environment (ReadOnly = false).
    /// </para>
    /// <para>
    /// The default is <see langword="true"/>.
    /// </para>
    /// </remarks>
    [JsonPropertyName("destructiveHint")]
    public bool? DestructiveHint { get; set; }

    /// <summary>
    /// Gets or sets whether calling the tool repeatedly with the same arguments
    /// will have no additional effect on its environment.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This property is most relevant when the tool modifies its environment (ReadOnly = false).
    /// </para>
    /// <para>
    /// The default is <see langword="false"/>.
    /// </para>
    /// </remarks>
    [JsonPropertyName("idempotentHint")]
    public bool? IdempotentHint { get; set; }

    /// <summary>
    /// Gets or sets whether this tool may interact with an "open world" of external entities.
    /// </summary>
    /// <remarks>
    /// <para>
    /// If <see langword="true"/>, the tool may interact with an unpredictable or dynamic set of entities (like web search).
    /// If <see langword="false"/>, the tool's domain of interaction is closed and well-defined (like memory access).
    /// </para>
    /// <para>
    /// The default is <see langword="true"/>.
    /// </para>
    /// </remarks>
    [JsonPropertyName("openWorldHint")]
    public bool? OpenWorldHint { get; set; }

    /// <summary>
    /// Gets or sets whether this tool does not modify its environment.
    /// </summary>
    /// <remarks>
    /// <para>
    /// If <see langword="true"/>, the tool only performs read operations without changing state.
    /// If <see langword="false"/>, the tool may make modifications to its environment.
    /// </para>
    /// <para>
    /// Read-only tools do not have side effects beyond computational resource usage.
    /// They don't create, update, or delete data in any system.
    /// </para>
    /// <para>
    /// The default is <see langword="false"/>.
    /// </para>
    /// </remarks>
    [JsonPropertyName("readOnlyHint")]
    public bool? ReadOnlyHint { get; set; }
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET
namespace System.Diagnostics.CodeAnalysis;

/// Specifies the syntax used in a string.
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class StringSyntaxAttribute : Attribute
{
    /// Initializes the <see cref="StringSyntaxAttribute"/> with the identifier of the syntax used.
    /// <param name="syntax">The syntax identifier.</param>
    public StringSyntaxAttribute(string syntax)
    {
        Syntax = syntax;
        Arguments = Array.Empty<object?>();
    }

    /// Initializes the <see cref="StringSyntaxAttribute"/> with the identifier of the syntax used.
    /// <param name="syntax">The syntax identifier.</param>
    /// <param name="arguments">Optional arguments associated with the specific syntax employed.</param>
    public StringSyntaxAttribute(string syntax, params object?[] arguments)
    {
        Syntax = syntax;
        Arguments = arguments;
    }

    /// Gets the identifier of the syntax used.
    public string Syntax { get; }

    /// Optional arguments associated with the specific syntax employed.
    public object?[] Arguments { get; }

    /// The syntax identifier for strings containing composite formats for string formatting.
    public const string CompositeFormat = nameof(CompositeFormat);

    /// The syntax identifier for strings containing date format specifiers.
    public const string DateOnlyFormat = nameof(DateOnlyFormat);

    /// The syntax identifier for strings containing date and time format specifiers.
    public const string DateTimeFormat = nameof(DateTimeFormat);

    /// The syntax identifier for strings containing <see cref="Enum"/> format specifiers.
    public const string EnumFormat = nameof(EnumFormat);

    /// The syntax identifier for strings containing <see cref="Guid"/> format specifiers.
    public const string GuidFormat = nameof(GuidFormat);

    /// The syntax identifier for strings containing JavaScript Object Notation (JSON).
    public const string Json = nameof(Json);

    /// The syntax identifier for strings containing numeric format specifiers.
    public const string NumericFormat = nameof(NumericFormat);

    /// The syntax identifier for strings containing regular expressions.
    public const string Regex = nameof(Regex);

    /// The syntax identifier for strings containing time format specifiers.
    public const string TimeOnlyFormat = nameof(TimeOnlyFormat);

    /// The syntax identifier for strings containing <see cref="TimeSpan"/> format specifiers.
    public const string TimeSpanFormat = nameof(TimeSpanFormat);

    /// The syntax identifier for strings containing URIs.
    public const string Uri = nameof(Uri);

    /// The syntax identifier for strings containing XML.
    public const string Xml = nameof(Xml);
}
#endif

using ModelContextProtocol.Server;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the completions capability for providing auto-completion suggestions
/// for prompt arguments and resource references.
/// </summary>
/// <remarks>
/// <para>
/// When enabled, this capability allows a Model Context Protocol server to provide
/// auto-completion suggestions. This capability is advertised to clients during the initialize handshake.
/// </para>
/// <para>
/// The primary function of this capability is to improve the user experience by offering
/// contextual suggestions for argument values or resource identifiers based on partial input.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class CompletionsCapability
{
    // Currently empty in the spec, but may be extended in the future.

    /// <summary>
    /// Gets or sets the handler for completion requests.
    /// </summary>
    /// <remarks>
    /// This handler provides auto-completion suggestions for prompt arguments or resource references in the Model Context Protocol.
    /// The handler receives a reference type (e.g., "ref/prompt" or "ref/resource") and the current argument value,
    /// and should return appropriate completion suggestions.
    /// </remarks>
    [JsonIgnore]
    public Func<RequestContext<CompleteRequestParams>, CancellationToken, ValueTask<CompleteResult>>? CompleteHandler { get; set; }
}

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Provides a base class for result payloads that support cursor-based pagination.
/// </summary>
/// <remarks>
/// <para>
/// Pagination allows API responses to be broken into smaller, manageable chunks when
/// there are potentially many results to return or when dynamically-computed results
/// may incur measurable latency.
/// </para>
/// <para>
/// Classes that inherit from <see cref="PaginatedResult"/> implement cursor-based pagination,
/// where the <see cref="NextCursor"/> property serves as an opaque token pointing to the next
/// set of results.
/// </para>
/// </remarks>
public abstract class PaginatedResult : Result
{
    private protected PaginatedResult()
    {
    }

    /// <summary>
    /// Gets or sets an opaque token representing the pagination position after the last returned result.
    /// </summary>
    /// <remarks>
    /// When a paginated result has more data available, the <see cref="NextCursor"/>
    /// property will contain a non-<see langword="null"/> token that can be used in subsequent requests
    /// to fetch the next page. When there are no more results to return, the <see cref="NextCursor"/> property
    /// will be <see langword="null"/>.
    /// </remarks>
    public string? NextCursor { get; set; }
}

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using ModelContextProtocol.AspNetCore.Authentication;
using ProtectedMCPServer.Tools;
using System.Net.Http.Headers;
using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

var serverUrl = "http://localhost:7071/";
var inMemoryOAuthServerUrl = "https://localhost:7029";

builder.Services.AddAuthentication(options =>
{
    options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    // Configure to validate tokens from our in-memory OAuth server
    options.Authority = inMemoryOAuthServerUrl;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidAudience = serverUrl, // Validate that the audience matches the resource metadata as suggested in RFC 8707
        ValidIssuer = inMemoryOAuthServerUrl,
        NameClaimType = "name",
        RoleClaimType = "roles"
    };

    options.Events = new JwtBearerEvents
    {
        OnTokenValidated = context =>
        {
            var name = context.Principal?.Identity?.Name ?? "unknown";
            var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown";
            Console.WriteLine($"Token validated for: {name} ({email})");
            return Task.CompletedTask;
        },
        OnAuthenticationFailed = context =>
        {
            Console.WriteLine($"Authentication failed: {context.Exception.Message}");
            return Task.CompletedTask;
        },
        OnChallenge = context =>
        {
            Console.WriteLine($"Challenging client to authenticate with Entra ID");
            return Task.CompletedTask;
        }
    };
})
.AddMcp(options =>
{
    options.ResourceMetadata = new()
    {
        Resource = new Uri(serverUrl),
        ResourceDocumentation = new Uri("https://docs.example.com/api/weather"),
        AuthorizationServers = { new Uri(inMemoryOAuthServerUrl) },
        ScopesSupported = ["mcp:tools"],
    };
});

builder.Services.AddAuthorization();

builder.Services.AddHttpContextAccessor();
builder.Services.AddMcpServer()
    .WithTools<WeatherTools>()
    .WithHttpTransport();

// Configure HttpClientFactory for weather.gov API
builder.Services.AddHttpClient("WeatherApi", client =>
{
    client.BaseAddress = new Uri("https://api.weather.gov");
    client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
});

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// Use the default MCP policy name that we've configured
app.MapMcp().RequireAuthorization();

Console.WriteLine($"Starting MCP server with authorization at {serverUrl}");
Console.WriteLine($"Using in-memory OAuth server at {inMemoryOAuthServerUrl}");
Console.WriteLine($"Protected Resource Metadata URL: {serverUrl}.well-known/oauth-protected-resource");
Console.WriteLine("Press Ctrl+C to stop the server");

app.Run(serverUrl);

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a notification message in the JSON-RPC protocol.
/// </summary>
/// <remarks>
/// Notifications are messages that do not require a response and are not matched with a response message.
/// They are useful for one-way communication, such as log notifications and progress updates.
/// Unlike requests, notifications do not include an ID field, since there will be no response to match with it.
/// </remarks>
public sealed class JsonRpcNotification : JsonRpcMessage
{
    /// <summary>
    /// Gets or sets the name of the notification method.
    /// </summary>
    [JsonPropertyName("method")]
    public required string Method { get; init; }

    /// <summary>
    /// Gets or sets optional parameters for the notification.
    /// </summary>
    [JsonPropertyName("params")]
    public JsonNode? Params { get; init; }
}

using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace EverythingServer.Resources;

[McpServerResourceType]
public class SimpleResourceType
{
    [McpServerResource(UriTemplate = "test://direct/text/resource", Name = "Direct Text Resource", MimeType = "text/plain")]
    [Description("A direct text resource")]
    public static string DirectTextResource() => "This is a direct resource";

    [McpServerResource(UriTemplate = "test://template/resource/{id}", Name = "Template Resource")]
    [Description("A template resource with a numeric ID")]
    public static ResourceContents TemplateResource(RequestContext<ReadResourceRequestParams> requestContext, int id)
    {
        int index = id - 1;
        if ((uint)index >= ResourceGenerator.Resources.Count)
        {
            throw new NotSupportedException($"Unknown resource: {requestContext.Params?.Uri}");
        }

        var resource = ResourceGenerator.Resources[index];
        return resource.MimeType == "text/plain" ?
            new TextResourceContents
            {
                Text = resource.Description!,
                MimeType = resource.MimeType,
                Uri = resource.Uri,
            } :
            new BlobResourceContents
            {
                Blob = resource.Description!,
                MimeType = resource.MimeType,
                Uri = resource.Uri,
            };
    }
}

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Provides constants with the names of common request methods used in the MCP protocol.
/// </summary>
public static class RequestMethods
{
    /// <summary>
    /// The name of the request method sent from the client to request a list of the server's tools.
    /// </summary>
    public const string ToolsList = "tools/list";

    /// <summary>
    /// The name of the request method sent from the client to request that the server invoke a specific tool.
    /// </summary>
    public const string ToolsCall = "tools/call";

    /// <summary>
    /// The name of the request method sent from the client to request a list of the server's prompts.
    /// </summary>
    public const string PromptsList = "prompts/list";

    /// <summary>
    /// The name of the request method sent by the client to get a prompt provided by the server.
    /// </summary>
    public const string PromptsGet = "prompts/get";

    /// <summary>
    /// The name of the request method sent from the client to request a list of the server's resources.
    /// </summary>
    public const string ResourcesList = "resources/list";

    /// <summary>
    /// The name of the request method sent from the client to read a specific server resource.
    /// </summary>
    public const string ResourcesRead = "resources/read";

    /// <summary>
    /// The name of the request method sent from the client to request a list of the server's resource templates.
    /// </summary>
    public const string ResourcesTemplatesList = "resources/templates/list";

    /// <summary>
    /// The name of the request method sent from the client to request <see cref="NotificationMethods.ResourceUpdatedNotification"/>
    /// notifications from the server whenever a particular resource changes.
    /// </summary>
    public const string ResourcesSubscribe = "resources/subscribe";

    /// <summary>
    /// The name of the request method sent from the client to request unsubscribing from <see cref="NotificationMethods.ResourceUpdatedNotification"/>
    /// notifications from the server.
    /// </summary>
    public const string ResourcesUnsubscribe = "resources/unsubscribe";

    /// <summary>
    /// The name of the request method sent from the server to request a list of the client's roots.
    /// </summary>
    public const string RootsList = "roots/list";

    /// <summary>
    /// The name of the request method sent by either endpoint to check that the connected endpoint is still alive.
    /// </summary>
    public const string Ping = "ping";

    /// <summary>
    /// The name of the request method sent from the client to the server to adjust the logging level.
    /// </summary>
    /// <remarks>
    /// This request allows clients to control which log messages they receive from the server
    /// by setting a minimum severity threshold. After processing this request, the server will
    /// send log messages with severity at or above the specified level to the client as
    /// <see cref="NotificationMethods.LoggingMessageNotification"/> notifications.
    /// </remarks>
    public const string LoggingSetLevel = "logging/setLevel";

    /// <summary>
    /// The name of the request method sent from the client to the server to ask for completion suggestions.
    /// </summary>
    /// <remarks>
    /// This is used to provide autocompletion-like functionality for arguments in a resource reference or a prompt template.
    /// The client provides a reference (resource or prompt), argument name, and partial value, and the server
    /// responds with matching completion options.
    /// </remarks>
    public const string CompletionComplete = "completion/complete";

    /// <summary>
    /// The name of the request method sent from the server to sample an large language model (LLM) via the client.
    /// </summary>
    /// <remarks>
    /// This request allows servers to utilize an LLM available on the client side to generate text or image responses
    /// based on provided messages. It is part of the sampling capability in the Model Context Protocol and enables servers to access
    /// client-side AI models without needing direct API access to those models.
    /// </remarks>
    public const string SamplingCreateMessage = "sampling/createMessage";

    /// <summary>
    /// The name of the request method sent from the client to the server to elicit additional information from the user via the client.
    /// </summary>
    /// <remarks>
    /// This request is used when the server needs more information from the client to proceed with a task or interaction.
    /// Servers can request structured data from users, with optional JSON schemas to validate responses.
    /// </remarks>
    public const string ElicitationCreate = "elicitation/create";

    /// <summary>
    /// The name of the request method sent from the client to the server when it first connects, asking it initialize.
    /// </summary>
    /// <remarks>
    /// The initialize request is the first request sent by the client to the server. It provides client information
    /// and capabilities to the server during connection establishment. The server responds with its own capabilities
    /// and information, establishing the protocol version and available features for the session.
    /// </remarks>
    public const string Initialize = "initialize";
}

using ModelContextProtocol.Protocol;

namespace ModelContextProtocol;

/// <summary>
/// Provides an <see cref="IProgress{ProgressNotificationValue}"/> tied to a specific progress token and that will issue
/// progress notifications on the supplied endpoint.
/// </summary>
internal sealed class TokenProgress(IMcpEndpoint endpoint, ProgressToken progressToken) : IProgress<ProgressNotificationValue>
{
    /// <inheritdoc />
    public void Report(ProgressNotificationValue value)
    {
        _ = endpoint.NotifyProgressAsync(progressToken, value, CancellationToken.None);
    }
}

using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Client;
using OpenAI;
using OpenTelemetry;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

using var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddHttpClientInstrumentation()
    .AddSource("*")
    .AddOtlpExporter()
    .Build();
using var metricsProvider = Sdk.CreateMeterProviderBuilder()
    .AddHttpClientInstrumentation()
    .AddMeter("*")
    .AddOtlpExporter()
    .Build();
using var loggerFactory = LoggerFactory.Create(builder => builder.AddOpenTelemetry(opt => opt.AddOtlpExporter()));

// Connect to an MCP server
Console.WriteLine("Connecting client to MCP 'everything' server");

// Create OpenAI client (or any other compatible with IChatClient)
// Provide your own OPENAI_API_KEY via an environment variable.
var openAIClient = new OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY")).GetChatClient("gpt-4o-mini");

// Create a sampling client.
using IChatClient samplingClient = openAIClient.AsIChatClient()
    .AsBuilder()
    .UseOpenTelemetry(loggerFactory: loggerFactory, configure: o => o.EnableSensitiveData = true)
    .Build();

var mcpClient = await McpClientFactory.CreateAsync(
    new StdioClientTransport(new()
    {
        Command = "npx",
        Arguments = ["-y", "--verbose", "@modelcontextprotocol/server-everything"],
        Name = "Everything",
    }),
    clientOptions: new()
    {
        Capabilities = new() { Sampling = new() { SamplingHandler = samplingClient.CreateSamplingHandler() } },
    },
    loggerFactory: loggerFactory);

// Get all available tools
Console.WriteLine("Tools available:");
var tools = await mcpClient.ListToolsAsync();
foreach (var tool in tools)
{
    Console.WriteLine($"  {tool}");
}

Console.WriteLine();

// Create an IChatClient that can use the tools.
using IChatClient chatClient = openAIClient.AsIChatClient()
    .AsBuilder()
    .UseFunctionInvocation()
    .UseOpenTelemetry(loggerFactory: loggerFactory, configure: o => o.EnableSensitiveData = true)
    .Build();

// Have a conversation, making all tools available to the LLM.
List<ChatMessage> messages = [];
while (true)
{
    Console.Write("Q: ");
    messages.Add(new(ChatRole.User, Console.ReadLine()));

    List<ChatResponseUpdate> updates = [];
    await foreach (var update in chatClient.GetStreamingResponseAsync(messages, new() { Tools = [.. tools] }))
    {
        Console.Write(update);
        updates.Add(update);
    }
    Console.WriteLine();

    messages.AddMessages(updates);
}

#if !NET
using System.Runtime.CompilerServices;

namespace System.Threading.Channels;

internal static class ChannelExtensions
{
    public static async IAsyncEnumerable<T> ReadAllAsync<T>(this ChannelReader<T> reader, [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false))
        {
            while (reader.TryRead(out var item))
            {
                yield return item;
            }
        }
    }
}
#endif

namespace ModelContextProtocol;

/// Provides a progress value that can be sent using <see cref="IProgress{ProgressNotificationValue}"/>.
public sealed class ProgressNotificationValue
{
    ///
    /// Gets or sets the progress thus far.
    ///
    /// <remarks>
    /// <para>
    /// This value typically represents either a percentage (0-100) or the number of items processed so far (when used with the <see cref="Total"/> property).
    /// </para>
    /// <para>
    /// When reporting progress, this value should increase monotonically as the operation proceeds.
    /// Values are typically between 0 and 100 when representing percentages, or can be any positive number
    /// when representing completed items in combination with the <see cref="Total"/> property.
    /// </para>
    /// </remarks>
    public required float Progress { get; init; }

    /// Gets or sets the total number of items to process (or total progress required), if known.
    public float? Total { get; init; }

    /// Gets or sets an optional message describing the current progress.
    public string? Message { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Authentication;

/// <summary>
/// Represents the resource metadata for OAuth authorization as defined in RFC 9396.
/// Defined by <see href="https://datatracker.ietf.org/doc/rfc9728/">RFC 9728</see>.
/// </summary>
public sealed class ProtectedResourceMetadata
{
    /// <summary>
    /// The resource URI.
    /// </summary>
    /// <remarks>
    /// REQUIRED. The protected resource's resource identifier.
    /// </remarks>
    [JsonPropertyName("resource")]
    public required Uri Resource { get; set; }

    /// <summary>
    /// The list of authorization server URIs.
    /// </summary>
    /// <remarks>
    /// OPTIONAL. JSON array containing a list of OAuth authorization server issuer identifiers
    /// for authorization servers that can be used with this protected resource.
    /// </remarks>
    [JsonPropertyName("authorization_servers")]
    public List<Uri> AuthorizationServers { get; set; } = [];

    /// <summary>
    /// The supported bearer token methods.
    /// </summary>
    /// <remarks>
    /// OPTIONAL. JSON array containing a list of the supported methods of sending an OAuth 2.0 bearer token
    /// to the protected resource. Defined values are ["header", "body", "query"].
    /// </remarks>
    [JsonPropertyName("bearer_methods_supported")]
    public List<string> BearerMethodsSupported { get; set; } = ["header"];

    /// <summary>
    /// The supported scopes.
    /// </summary>
    /// <remarks>
    /// RECOMMENDED. JSON array containing a list of scope values that are used in authorization
    /// requests to request access to this protected resource.
    /// </remarks>
    [JsonPropertyName("scopes_supported")]
    public List<string> ScopesSupported { get; set; } = [];

    /// <summary>
    /// URL of the protected resource's JSON Web Key (JWK) Set document.
    /// </summary>
    /// <remarks>
    /// OPTIONAL. This contains public keys belonging to the protected resource, such as signing key(s)
    /// that the resource server uses to sign resource responses. This URL MUST use the https scheme.
    /// </remarks>
    [JsonPropertyName("jwks_uri")]
    public Uri? JwksUri { get; set; }

    /// <summary>
    /// List of the JWS signing algorithms supported by the protected resource for signing resource responses.
    /// </summary>
    /// <remarks>
    /// OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values) supported by the protected resource
    /// for signing resource responses. No default algorithms are implied if this entry is omitted. The value none MUST NOT be used.
    /// </remarks>
    [JsonPropertyName("resource_signing_alg_values_supported")]
    public List<string>? ResourceSigningAlgValuesSupported { get; set; }

    /// <summary>
    /// Human-readable name of the protected resource intended for display to the end user.
    /// </summary>
    /// <remarks>
    /// RECOMMENDED. It is recommended that protected resource metadata include this field.
    /// The value of this field MAY be internationalized.
    /// </remarks>
    [JsonPropertyName("resource_name")]
    public string? ResourceName { get; set; }

    /// <summary>
    /// The URI to the resource documentation.
    /// </summary>
    /// <remarks>
    /// OPTIONAL. URL of a page containing human-readable information that developers might want or need to know
    /// when using the protected resource.
    /// </remarks>
    [JsonPropertyName("resource_documentation")]
    public Uri? ResourceDocumentation { get; set; }

    /// <summary>
    /// URL of a page containing human-readable information about the protected resource's requirements.
    /// </summary>
    /// <remarks>
    /// OPTIONAL. Information about how the client can use the data provided by the protected resource.
    /// </remarks>
    [JsonPropertyName("resource_policy_uri")]
    public Uri? ResourcePolicyUri { get; set; }

    /// <summary>
    /// URL of a page containing human-readable information about the protected resource's terms of service.
    /// </summary>
    /// <remarks>
    /// OPTIONAL. The value of this field MAY be internationalized.
    /// </remarks>
    [JsonPropertyName("resource_tos_uri")]
    public Uri? ResourceTosUri { get; set; }

    /// <summary>
    /// Boolean value indicating protected resource support for mutual-TLS client certificate-bound access tokens.
    /// </summary>
    /// <remarks>
    /// OPTIONAL. If omitted, the default value is false.
    /// </remarks>
    [JsonPropertyName("tls_client_certificate_bound_access_tokens")]
    public bool? TlsClientCertificateBoundAccessTokens { get; set; }

    /// <summary>
    /// List of the authorization details type values supported by the resource server.
    /// </summary>
    /// <remarks>
    /// OPTIONAL. JSON array containing a list of the authorization details type values supported by the resource server
    /// when the authorization_details request parameter is used.
    /// </remarks>
    [JsonPropertyName("authorization_details_types_supported")]
    public List<string>? AuthorizationDetailsTypesSupported { get; set; }

    /// <summary>
    /// List of the JWS algorithm values supported by the resource server for validating DPoP proof JWTs.
    /// </summary>
    /// <remarks>
    /// OPTIONAL. JSON array containing a list of the JWS alg values supported by the resource server
    /// for validating Demonstrating Proof of Possession (DPoP) proof JWTs.
    /// </remarks>
    [JsonPropertyName("dpop_signing_alg_values_supported")]
    public List<string>? DpopSigningAlgValuesSupported { get; set; }

    /// <summary>
    /// Boolean value specifying whether the protected resource always requires the use of DPoP-bound access tokens.
    /// </summary>
    /// <remarks>
    /// OPTIONAL. If omitted, the default value is false.
    /// </remarks>
    [JsonPropertyName("dpop_bound_access_tokens_required")]
    public bool? DpopBoundAccessTokensRequired { get; set; }
}

using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Server;

/// <summary>
/// Represents an instance of a Model Context Protocol (MCP) server that connects to and communicates with an MCP client.
/// </summary>
public interface IMcpServer : IMcpEndpoint
{
    /// <summary>
    /// Gets the capabilities supported by the client.
    /// </summary>
    /// <remarks>
    /// <para>
    /// These capabilities are established during the initialization handshake and indicate
    /// which features the client supports, such as sampling, roots, and other
    /// protocol-specific functionality.
    /// </para>
    /// <para>
    /// Server implementations can check these capabilities to determine which features
    /// are available when interacting with the client.
    /// </para>
    /// </remarks>
    ClientCapabilities? ClientCapabilities { get; }

    /// <summary>
    /// Gets the version and implementation information of the connected client.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This property contains identification information about the client that has connected to this server,
    /// including its name and version. This information is provided by the client during initialization.
    /// </para>
    /// <para>
    /// Server implementations can use this information for logging, tracking client versions,
    /// or implementing client-specific behaviors.
    /// </para>
    /// </remarks>
    Implementation? ClientInfo { get; }

    /// <summary>
    /// Gets the options used to construct this server.
    /// </summary>
    /// <remarks>
    /// These options define the server's capabilities, protocol version, and other configuration
    /// settings that were used to initialize the server.
    /// </remarks>
    McpServerOptions ServerOptions { get; }

    /// <summary>
    /// Gets the service provider for the server.
    /// </summary>
    IServiceProvider? Services { get; }

    /// <summary>Gets the last logging level set by the client, or <see langword="null"/> if it's never been set.</summary>
    LoggingLevel? LoggingLevel { get; }

    /// <summary>
    /// Runs the server, listening for and handling client requests.
    /// </summary>
    Task RunAsync(CancellationToken cancellationToken = default);
}

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using ModelContextProtocol.Authentication;

namespace ModelContextProtocol.AspNetCore.Authentication;

/// <summary>
/// Context for resource metadata request events.
/// </summary>
public class ResourceMetadataRequestContext : HandleRequestContext<McpAuthenticationOptions>
{
    /// <summary>
    /// Initializes a new instance of the <see cref="ResourceMetadataRequestContext"/> class.
    /// </summary>
    /// <param name="context">The HTTP context.</param>
    /// <param name="scheme">The authentication scheme.</param>
    /// <param name="options">The authentication options.</param>
    public ResourceMetadataRequestContext(
        HttpContext context,
        AuthenticationScheme scheme,
        McpAuthenticationOptions options)
        : base(context, scheme, options)
    {
    }

    /// <summary>
    /// Gets or sets the protected resource metadata for the current request.
    /// </summary>
    public ProtectedResourceMetadata? ResourceMetadata { get; set; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the capability for a client to provide server-requested additional information during interactions.
/// </summary>
/// <remarks>
/// <para>
/// This capability enables the MCP client to respond to elicitation requests from an MCP server.
/// </para>
/// <para>
/// When this capability is enabled, an MCP server can request the client to provide additional information
/// during interactions. The client must set a <see cref="ElicitationHandler"/> to process these requests.
/// </para>
/// </remarks>
public sealed class ElicitationCapability
{
    // Currently empty in the spec, but may be extended in the future.

    /// <summary>
    /// Gets or sets the handler for processing <see cref="RequestMethods.ElicitationCreate"/> requests.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This handler function is called when an MCP server requests the client to provide additional
    /// information during interactions. The client must set this property for the elicitation capability to work.
    /// </para>
    /// <para>
    /// The handler receives message parameters and a cancellation token.
    /// It should return a <see cref="ElicitResult"/> containing the response to the elicitation request.
    /// </para>
    /// </remarks>
    [JsonIgnore]
    public Func<ElicitRequestParams?, CancellationToken, ValueTask<ElicitResult>>? ElicitationHandler { get; set; }
}

namespace ModelContextProtocol.Authentication;

/// <summary>
/// Represents a method that handles the OAuth authorization URL and returns the authorization code.
/// </summary>
/// <param name="authorizationUri">The authorization URL that the user needs to visit.</param>
/// <param name="redirectUri">The redirect URI where the authorization code will be sent.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the authorization code if successful, or null if the operation failed or was cancelled.</returns>
/// <remarks>
/// <para>
/// This delegate provides SDK consumers with full control over how the OAuth authorization flow is handled.
/// Implementers can choose to:
/// </para>
/// <list type="bullet">
/// <item><description>Start a local HTTP server and open a browser (default behavior)</description></item>
/// <item><description>Display the authorization URL to the user for manual handling</description></item>
/// <item><description>Integrate with a custom UI or authentication flow</description></item>
/// <item><description>Use a different redirect mechanism altogether</description></item>
/// </list>
/// <para>
/// The implementation should handle user interaction to visit the authorization URL and extract
/// the authorization code from the callback. The authorization code is typically provided as
/// a query parameter in the redirect URI callback.
/// </para>
/// </remarks>
public delegate Task<string?> AuthorizationRedirectDelegate(Uri authorizationUri, Uri redirectUri, CancellationToken cancellationToken);

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the result of a <see cref="RequestMethods.ToolsCall"/> request from a client to invoke a tool provided by the server.
/// </summary>
/// <remarks>
/// <para>
/// Any errors that originate from the tool should be reported inside the result
/// object, with <see cref="IsError"/> set to true, rather than as a <see cref="JsonRpcError"/>.
/// </para>
/// <para>
/// However, any errors in finding the tool, an error indicating that the
/// server does not support tool calls, or any other exceptional conditions,
/// should be reported as an MCP error response.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class CallToolResult : Result
{
    /// <summary>
    /// Gets or sets the response content from the tool call.
    /// </summary>
    [JsonPropertyName("content")]
    public IList<ContentBlock> Content { get; set; } = [];

    /// <summary>
    /// Gets or sets an optional JSON object representing the structured result of the tool call.
    /// </summary>
    [JsonPropertyName("structuredContent")]
    public JsonNode? StructuredContent { get; set; }

    /// <summary>
    /// Gets or sets an indication of whether the tool call was unsuccessful.
    /// </summary>
    /// <remarks>
    /// When set to <see langword="true"/>, it signifies that the tool execution failed.
    /// Tool errors are reported with this property set to <see langword="true"/> and details in the <see cref="Content"/>
    /// property, rather than as protocol-level errors. This allows LLMs to see that an error occurred
    /// and potentially self-correct in subsequent requests.
    /// </remarks>
    [JsonPropertyName("isError")]
    public bool? IsError { get; set; }
}

namespace System.IO;

internal static class TextReaderExtensions
{
    public static ValueTask<string> ReadLineAsync(this TextReader reader, CancellationToken cancellationToken)
    {
        if (reader is CancellableStreamReader cancellableReader)
        {
            return cancellableReader.ReadLineAsync(cancellationToken)!;
        }

        cancellationToken.ThrowIfCancellationRequested();
        return new ValueTask<string>(reader.ReadLineAsync());
    }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.Initialize"/> request sent by a client to a server during the protocol handshake.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="InitializeRequestParams"/> is the first message sent in the Model Context Protocol
/// communication flow. It establishes the connection between client and server, negotiates the protocol
/// version, and declares the client's capabilities.
/// </para>
/// <para>
/// After sending this request, the client should wait for an <see cref="InitializeResult"/> response
/// before sending an <see cref="NotificationMethods.InitializedNotification"/> notification to complete the handshake.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class InitializeRequestParams : RequestParams
{
    /// <summary>
    /// Gets or sets the version of the Model Context Protocol that the client wants to use.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Protocol version is specified using a date-based versioning scheme in the format "YYYY-MM-DD".
    /// The client and server must agree on a protocol version to communicate successfully.
    /// </para>
    /// <para>
    /// During initialization, the server will check if it supports this requested version. If there's a
    /// mismatch, the server will reject the connection with a version mismatch error.
    /// </para>
    /// <para>
    /// See the <see href="https://spec.modelcontextprotocol.io/specification/">protocol specification</see> for version details.
    /// </para>
    /// </remarks>
    [JsonPropertyName("protocolVersion")]
    public required string ProtocolVersion { get; init; }

    /// <summary>
    /// Gets or sets the client's capabilities.
    /// </summary>
    /// <remarks>
    /// Capabilities define the features the client supports, such as "sampling" or "roots".
    /// </remarks>
    [JsonPropertyName("capabilities")]
    public ClientCapabilities? Capabilities { get; init; }

    /// <summary>
    /// Gets or sets information about the client implementation, including its name and version.
    /// </summary>
    /// <remarks>
    /// This information is required during the initialization handshake to identify the client.
    /// Servers may use this information for logging, debugging, or compatibility checks.
    /// </remarks>
    [JsonPropertyName("clientInfo")]
    public required Implementation ClientInfo { get; init; }
}

using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="NotificationMethods.LoggingMessageNotification"/>
/// notification sent whenever a log message is generated.
/// </summary>
/// <remarks>
/// <para>
/// Logging notifications allow servers to communicate diagnostic information to clients with varying severity levels.
/// Clients can filter these messages based on the <see cref="Level"/> and <see cref="Logger"/> properties.
/// </para>
/// <para>
/// If no <see cref="RequestMethods.LoggingSetLevel"/> request has been sent from the client, the server may decide which
/// messages to send automatically.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class LoggingMessageNotificationParams : NotificationParams
{
    /// <summary>
    /// Gets or sets the severity of this log message.
    /// </summary>
    [JsonPropertyName("level")]
    public LoggingLevel Level { get; init; }

    /// <summary>
    /// Gets or sets an optional name of the logger issuing this message.
    /// </summary>
    /// <remarks>
    /// <para>
    /// <see cref="Logger"/> typically represents a category or component in the server's logging system.
    /// The logger name is useful for filtering and routing log messages in client applications.
    /// </para>
    /// <para>
    /// When implementing custom servers, choose clear, hierarchical logger names to help
    /// clients understand the source of log messages.
    /// </para>
    /// </remarks>
    [JsonPropertyName("logger")]
    public string? Logger { get; init; }

    /// <summary>
    /// Gets or sets the data to be logged, such as a string message.
    /// </summary>
    [JsonPropertyName("data")]
    public JsonElement? Data { get; init; }
}

using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.SamplingCreateMessage"/>
/// request from a server to sample an LLM via the client.
/// </summary>
/// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class CreateMessageRequestParams : RequestParams
{
    /// <summary>
    /// Gets or sets an indication as to which server contexts should be included in the prompt.
    /// </summary>
    /// <remarks>
    /// The client may ignore this request.
    /// </remarks>
    [JsonPropertyName("includeContext")]
    public ContextInclusion? IncludeContext { get; init; }

    /// <summary>
    /// Gets or sets the maximum number of tokens to generate in the LLM response, as requested by the server.
    /// </summary>
    /// <remarks>
    /// A token is generally a word or part of a word in the text. Setting this value helps control
    /// response length and computation time. The client may choose to sample fewer tokens than requested.
    /// </remarks>
    [JsonPropertyName("maxTokens")]
    public int? MaxTokens { get; init; }

    /// <summary>
    /// Gets or sets the messages requested by the server to be included in the prompt.
    /// </summary>
    [JsonPropertyName("messages")]
    public required IReadOnlyList<SamplingMessage> Messages { get; init; }

    /// <summary>
    /// Gets or sets optional metadata to pass through to the LLM provider.
    /// </summary>
    /// <remarks>
    /// The format of this metadata is provider-specific and can include model-specific settings or
    /// configuration that isn't covered by standard parameters. This allows for passing custom parameters
    /// that are specific to certain AI models or providers.
    /// </remarks>
    [JsonPropertyName("metadata")]
    public JsonElement? Metadata { get; init; }

    /// <summary>
    /// Gets or sets the server's preferences for which model to select.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The client may ignore these preferences.
    /// </para>
    /// <para>
    /// These preferences help the client make an appropriate model selection based on the server's priorities
    /// for cost, speed, intelligence, and specific model hints.
    /// </para>
    /// <para>
    /// When multiple dimensions are specified (cost, speed, intelligence), the client should balance these
    /// based on their relative values. If specific model hints are provided, the client should evaluate them
    /// in order and prioritize them over numeric priorities.
    /// </para>
    /// </remarks>
    [JsonPropertyName("modelPreferences")]
    public ModelPreferences? ModelPreferences { get; init; }

    /// <summary>
    /// Gets or sets optional sequences of characters that signal the LLM to stop generating text when encountered.
    /// </summary>
    /// <remarks>
    /// <para>
    /// When the model generates any of these sequences during sampling, text generation stops immediately,
    /// even if the maximum token limit hasn't been reached. This is useful for controlling generation
    /// endings or preventing the model from continuing beyond certain points.
    /// </para>
    /// <para>
    /// Stop sequences are typically case-sensitive, and typically the LLM will only stop generation when a produced
    /// sequence exactly matches one of the provided sequences. Common uses include ending markers like "END", punctuation
    /// like ".", or special delimiter sequences like "###".
    /// </para>
    /// </remarks>
    [JsonPropertyName("stopSequences")]
    public IReadOnlyList<string>? StopSequences { get; init; }

    /// <summary>
    /// Gets or sets an optional system prompt the server wants to use for sampling.
    /// </summary>
    /// <remarks>
    /// The client may modify or omit this prompt.
    /// </remarks>
    [JsonPropertyName("systemPrompt")]
    public string? SystemPrompt { get; init; }

    /// <summary>
    /// Gets or sets the temperature to use for sampling, as requested by the server.
    /// </summary>
    [JsonPropertyName("temperature")]
    public float? Temperature { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the result of a <see cref="RequestMethods.Initialize"/> request sent to the server during connection establishment.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="InitializeResult"/> is sent by the server in response to an <see cref="InitializeRequestParams"/>
/// message from the client. It contains information about the server, its capabilities, and the protocol version
/// that will be used for the session.
/// </para>
/// <para>
/// After receiving this response, the client should send an <see cref="NotificationMethods.InitializedNotification"/>
/// notification to complete the handshake.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class InitializeResult : Result
{
    /// <summary>
    /// Gets or sets the version of the Model Context Protocol that the server will use for this session.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This is the protocol version the server has agreed to use, which should match the client's
    /// requested version. If there's a mismatch, the client should throw an exception to prevent
    /// communication issues due to incompatible protocol versions.
    /// </para>
    /// <para>
    /// The protocol uses a date-based versioning scheme in the format "YYYY-MM-DD".
    /// </para>
    /// <para>
    /// See the <see href="https://spec.modelcontextprotocol.io/specification/">protocol specification</see> for version details.
    /// </para>
    /// </remarks>
    [JsonPropertyName("protocolVersion")]
    public required string ProtocolVersion { get; init; }

    /// <summary>
    /// Gets or sets the server's capabilities.
    /// </summary>
    /// <remarks>
    /// This defines the features the server supports, such as "tools", "prompts", "resources", or "logging",
    /// and other protocol-specific functionality.
    /// </remarks>
    [JsonPropertyName("capabilities")]
    public required ServerCapabilities Capabilities { get; init; }

    /// <summary>
    /// Gets or sets information about the server implementation, including its name and version.
    /// </summary>
    /// <remarks>
    /// This information identifies the server during the initialization handshake.
    /// Clients may use this information for logging, debugging, or compatibility checks.
    /// </remarks>
    [JsonPropertyName("serverInfo")]
    public required Implementation ServerInfo { get; init; }

    /// <summary>
    /// Gets or sets optional instructions for using the server and its features.
    /// </summary>
    /// <remarks>
    /// <para>
    /// These instructions provide guidance to clients on how to effectively use the server's capabilities.
    /// They can include details about available tools, expected input formats, limitations,
    /// or any other information that helps clients interact with the server properly.
    /// </para>
    /// <para>
    /// Client applications often use these instructions as system messages for LLM interactions
    /// to provide context about available functionality.
    /// </para>
    /// </remarks>
    [JsonPropertyName("instructions")]
    public string? Instructions { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a completion object in the server's response to a <see cref="RequestMethods.CompletionComplete"/> request.
/// </summary>
/// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class Completion
{
    /// <summary>
    /// Gets or sets an array of completion values (auto-suggestions) for the requested input.
    /// </summary>
    /// <remarks>
    /// This collection contains the actual text strings to be presented to users as completion suggestions.
    /// The array will be empty if no suggestions are available for the current input.
    /// Per the specification, this should not exceed 100 items.
    /// </remarks>
    [JsonPropertyName("values")]
    public IList<string> Values { get; set; } = [];

    /// <summary>
    /// Gets or sets the total number of completion options available.
    /// </summary>
    /// <remarks>
    /// This can exceed the number of values actually sent in the response.
    /// </remarks>
    [JsonPropertyName("total")]
    public int? Total { get; set; }

    /// <summary>
    /// Gets or sets an indicator as to whether there are additional completion options beyond
    /// those provided in the current response, even if the exact total is unknown.
    /// </summary>
    [JsonPropertyName("hasMore")]
    public bool? HasMore { get; set; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a notification indicating that a request has been cancelled by the client,
/// and that any associated processing should cease immediately.
/// </summary>
/// <remarks>
/// This class is typically used in conjunction with the <see cref="NotificationMethods.CancelledNotification"/>
/// method identifier. When a client sends this notification, the server should attempt to
/// cancel any ongoing operations associated with the specified request ID.
/// </remarks>
public sealed class CancelledNotificationParams : NotificationParams
{
    /// <summary>
    /// Gets or sets the ID of the request to cancel.
    /// </summary>
    /// <remarks>
    /// This must match the ID of an in-flight request that the sender wishes to cancel.
    /// </remarks>
    [JsonPropertyName("requestId")]
    public RequestId RequestId { get; set; }

    /// <summary>
    /// Gets or sets an optional string describing the reason for the cancellation request.
    /// </summary>
    [JsonPropertyName("reason")]
    public string? Reason { get; set; }
}

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a prompt that the server offers.
/// </summary>
/// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class Prompt : IBaseMetadata
{
    /// <inheritdoc />
    [JsonPropertyName("name")]
    public required string Name { get; set; }

    /// <inheritdoc />
    [JsonPropertyName("title")]
    public string? Title { get; set; }

    /// <summary>
    /// Gets or sets an optional description of what this prompt provides.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This description helps developers understand the purpose and use cases for the prompt.
    /// It should explain what the prompt is designed to accomplish and any important context.
    /// </para>
    /// <para>
    /// The description is typically used in documentation, UI displays, and for providing context
    /// to client applications that may need to choose between multiple available prompts.
    /// </para>
    /// </remarks>
    [JsonPropertyName("description")]
    public string? Description { get; set; }

    /// <summary>
    /// Gets or sets a list of arguments that this prompt accepts for templating and customization.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This list defines the arguments that can be provided when requesting the prompt.
    /// Each argument specifies metadata like name, description, and whether it's required.
    /// </para>
    /// <para>
    /// When a client makes a <see cref="RequestMethods.PromptsGet"/> request, it can provide values for these arguments
    /// which will be substituted into the prompt template or otherwise used to render the prompt.
    /// </para>
    /// </remarks>
    [JsonPropertyName("arguments")]
    public IList<PromptArgument>? Arguments { get; set; }

    /// <summary>
    /// Gets or sets metadata reserved by MCP for protocol-level metadata.
    /// </summary>
    /// <remarks>
    /// Implementations must not make assumptions about its contents.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonObject? Meta { get; set; }
}

using Microsoft.Extensions.AI;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a message within the Model Context Protocol (MCP) system, used for communication between clients and AI models.
/// </summary>
/// <remarks>
/// <para>
/// A <see cref="PromptMessage"/> encapsulates content sent to or received from AI models in the Model Context Protocol.
/// Each message has a specific role (<see cref="Role.User"/> or <see cref="Role.Assistant"/>) and contains content which can be
/// text, images, audio, or embedded resources.
/// </para>
/// <para>
/// This class is similar to <see cref="SamplingMessage"/>, but with enhanced support for embedding resources from the MCP server.
/// It serves as a core data structure in the MCP message exchange flow, particularly in prompt formation and model responses.
/// </para>
/// <para>
/// <see cref="PromptMessage"/> objects are typically used in collections within <see cref="GetPromptResult"/>
/// to represent complete conversations or prompt sequences. They can be converted to and from <see cref="ChatMessage"/>
/// objects using the extension methods <see cref="AIContentExtensions.ToChatMessage(PromptMessage)"/> and
/// <see cref="AIContentExtensions.ToPromptMessages(ChatMessage)"/>.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class PromptMessage
{
    /// <summary>
    /// Gets or sets the content of the message, which can be text, image, audio, or an embedded resource.
    /// </summary>
    /// <remarks>
    /// The <see cref="Content"/> object contains all the message payload, whether it's simple text,
    /// base64-encoded binary data (for images/audio), or a reference to an embedded resource.
    /// The <see cref="ContentBlock.Type"/> property indicates the specific content type.
    /// </remarks>
    [JsonPropertyName("content")]
    public ContentBlock Content { get; set; } = new TextContentBlock { Text = "" };

    /// <summary>
    /// Gets or sets the role of the message sender, specifying whether it's from a "user" or an "assistant".
    /// </summary>
    /// <remarks>
    /// In the Model Context Protocol, each message must have a clear role assignment to maintain
    /// the conversation flow. User messages represent queries or inputs from users, while assistant
    /// messages represent responses generated by AI models.
    /// </remarks>
    [JsonPropertyName("role")]
    public Role Role { get; set; } = Role.User;
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents an argument that a prompt can accept for templating and customization.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="PromptArgument"/> class defines metadata for arguments that can be provided
/// to a prompt. These arguments are used to customize or parameterize prompts when they are
/// retrieved using <see cref="RequestMethods.PromptsGet"/> requests.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class PromptArgument : IBaseMetadata
{
    /// <inheritdoc />
    [JsonPropertyName("name")]
    public required string Name { get; set; }

    /// <inheritdoc />
    [JsonPropertyName("title")]
    public string? Title { get; set; }

    /// <summary>
    /// Gets or sets a human-readable description of the argument's purpose and expected values.
    /// </summary>
    /// <remarks>
    /// This description helps developers understand what information should be provided
    /// for this argument and how it will affect the generated prompt.
    /// </remarks>
    [JsonPropertyName("description")]
    public string? Description { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets an indication as to whether this argument must be provided when requesting the prompt.
    /// </summary>
    /// <remarks>
    /// When set to <see langword="true"/>, the client must include this argument when making a <see cref="RequestMethods.PromptsGet"/> request.
    /// If a required argument is missing, the server should respond with an error.
    /// </remarks>
    [JsonPropertyName("required")]
    public bool? Required { get; set; }
}

namespace ModelContextProtocol.Server;

/// <summary>
/// Used to attribute a type containing members that should be exposed as <see cref="McpServerResource"/>s.
/// </summary>
/// <remarks>
/// <para>
/// This attribute is used to mark a class containing members that should be automatically
/// discovered and registered as <see cref="McpServerResource"/>s. When combined with discovery methods like
/// WithResourcesFromAssembly, it enables automatic registration  of resources without explicitly listing each
/// resource class. The attribute is not necessary when a reference to the type is provided directly to a method
/// like McpServerBuilderExtensions.WithResources.
/// </para>
/// <para>
/// Within a class marked with this attribute, individual members that should be exposed as
/// resources must be marked with the <see cref="McpServerResourceAttribute"/>.
/// </para>
/// </remarks>
[AttributeUsage(AttributeTargets.Class)]
public sealed class McpServerResourceTypeAttribute : Attribute;

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a known resource that the server is capable of reading.
/// </summary>
/// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class Resource : IBaseMetadata
{
    /// <inheritdoc />
    [JsonPropertyName("name")]
    public required string Name { get; set; }

    /// <inheritdoc />
    [JsonPropertyName("title")]
    public string? Title { get; set; }

    /// <summary>
    /// Gets or sets the URI of this resource.
    /// </summary>
    [JsonPropertyName("uri")]
    public required string Uri { get; init; }

    /// <summary>
    /// Gets or sets a description of what this resource represents.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.
    /// </para>
    /// <para>
    /// The description should provide clear context about the resource's content, format, and purpose.
    /// This helps AI models make better decisions about when to access or reference the resource.
    /// </para>
    /// <para>
    /// Client applications can also use this description for display purposes in user interfaces
    /// or to help users understand the available resources.
    /// </para>
    /// </remarks>
    [JsonPropertyName("description")]
    public string? Description { get; init; }

    /// <summary>
    /// Gets or sets the MIME type of this resource.
    /// </summary>
    /// <remarks>
    /// <para>
    /// <see cref="MimeType"/> specifies the format of the resource content, helping clients to properly interpret and display the data.
    /// Common MIME types include "text/plain" for plain text, "application/pdf" for PDF documents,
    /// "image/png" for PNG images, and "application/json" for JSON data.
    /// </para>
    /// <para>
    /// This property may be <see langword="null"/> if the MIME type is unknown or not applicable for the resource.
    /// </para>
    /// </remarks>
    [JsonPropertyName("mimeType")]
    public string? MimeType { get; init; }

    /// <summary>
    /// Gets or sets optional annotations for the resource.
    /// </summary>
    /// <remarks>
    /// These annotations can be used to specify the intended audience (<see cref="Role.User"/>, <see cref="Role.Assistant"/>, or both)
    /// and the priority level of the resource. Clients can use this information to filter or prioritize resources for different roles.
    /// </remarks>
    [JsonPropertyName("annotations")]
    public Annotations? Annotations { get; init; }

    /// <summary>
    /// Gets or sets the size of the raw resource content (before base64 encoding), in bytes, if known.
    /// </summary>
    /// <remarks>
    /// This can be used by applications to display file sizes and estimate context window usage.
    /// </remarks>
    [JsonPropertyName("size")]
    public long? Size { get; init; }

    /// <summary>
    /// Gets or sets metadata reserved by MCP for protocol-level metadata.
    /// </summary>
    /// <remarks>
    /// Implementations must not make assumptions about its contents.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonObject? Meta { get; init; }
}

namespace ModelContextProtocol;

/// <summary>
/// Represents standard JSON-RPC error codes as defined in the MCP specification.
/// </summary>
public enum McpErrorCode
{
    /// <summary>
    /// Indicates that the JSON received could not be parsed.
    /// </summary>
    /// <remarks>
    /// This error occurs when the input contains malformed JSON or incorrect syntax.
    /// </remarks>
    ParseError = -32700,

    /// <summary>
    /// Indicates that the JSON payload does not conform to the expected Request object structure.
    /// </summary>
    /// <remarks>
    /// The request is considered invalid if it lacks required fields or fails to follow the JSON-RPC protocol.
    /// </remarks>
    InvalidRequest = -32600,

    /// <summary>
    /// Indicates that the requested method does not exist or is not available on the server.
    /// </summary>
    /// <remarks>
    /// This error is returned when the method name specified in the request cannot be found.
    /// </remarks>
    MethodNotFound = -32601,

    /// <summary>
    /// Indicates that one or more parameters provided in the request are invalid.
    /// </summary>
    /// <remarks>
    /// This error is returned when the parameters do not match the expected method signature or constraints.
    /// This includes cases where required parameters are missing or not understood, such as when a name for
    /// a tool or prompt is not recognized.
    /// </remarks>
    InvalidParams = -32602,

    /// <summary>
    /// Indicates that an internal error occurred while processing the request.
    /// </summary>
    /// <remarks>
    /// This error is used when the endpoint encounters an unexpected condition that prevents it from fulfilling the request.
    /// </remarks>
    InternalError = -32603,
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>Provides a base interface for metadata with name (identifier) and title (display name) properties.</summary>
public interface IBaseMetadata
{
    /// <summary>
    /// Gets or sets the unique identifier for this item.
    /// </summary>
    [JsonPropertyName("name")]
    string Name { get; set; }

    /// <summary>
    /// Gets or sets a title.
    /// </summary>
    /// <remarks>
    /// This is intended for UI and end-user contexts. It is optimized to be human-readable and easily understood,
    /// even by those unfamiliar with domain-specific terminology.
    /// If not provided, <see cref="Name"/> may be used for display (except for tools, where <see cref="ToolAnnotations.Title"/>, if present,
    /// should be given precedence over using <see cref="Name"/>).
    /// </remarks>
    [JsonPropertyName("title")]
    string? Title { get; set; }
}

using ModelContextProtocol.Server;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Provides a builder for configuring <see cref="IMcpServer"/> instances.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="IMcpServerBuilder"/> interface provides a fluent API for configuring Model Context Protocol (MCP) servers
/// when using dependency injection. It exposes methods for registering tools, prompts, custom request handlers,
/// and server transports, allowing for comprehensive server configuration through a chain of method calls.
/// </para>
/// <para>
/// The builder is obtained from the <see cref="McpServerServiceCollectionExtensions.AddMcpServer"/> extension
/// method and provides access to the underlying service collection via the <see cref="Services"/> property.
/// </para>
/// </remarks>
public interface IMcpServerBuilder
{
    /// <summary>
    /// Gets the associated service collection.
    /// </summary>
    IServiceCollection Services { get; }
}

namespace ModelContextProtocol.Server;

/// <summary>
/// Used to attribute a type containing methods that should be exposed as <see cref="McpServerTool"/>s.
/// </summary>
/// <remarks>
/// <para>
/// This attribute is used to mark a class containing methods that should be automatically
/// discovered and registered as <see cref="McpServerTool"/>s. When combined with discovery methods like
/// WithToolsFromAssembly, it enables automatic registration of tools without explicitly listing each tool
/// class. The attribute is not necessary when a reference to the type is provided directly to a method like WithTools.
/// </para>
/// <para>
/// Within a class marked with this attribute, individual methods that should be exposed as
/// tools must be marked with the <see cref="McpServerToolAttribute"/>.
/// </para>
/// </remarks>
[AttributeUsage(AttributeTargets.Class)]
public sealed class McpServerToolTypeAttribute : Attribute;

namespace ModelContextProtocol.Server;

/// <summary>
/// Used to attribute a type containing methods that should be exposed as <see cref="McpServerPrompt"/>s.
/// </summary>
/// <remarks>
/// <para>
/// This attribute is used to mark a class containing methods that should be automatically
/// discovered and registered as <see cref="McpServerPrompt"/>s. When combined with discovery methods like
/// WithPromptsFromAssembly, it enables automatic registration of prompts without explicitly listing each prompt class.
/// The attribute is not necessary when a reference to the type is provided directly to a method like WithPrompts.
/// </para>
/// <para>
/// Within a class marked with this attribute, individual methods that should be exposed as
/// prompts must be marked with the <see cref="McpServerPromptAttribute"/>.
/// </para>
/// </remarks>
[AttributeUsage(AttributeTargets.Class)]
public sealed class McpServerPromptTypeAttribute : Attribute;

using EverythingServer.Tools;
using Microsoft.Extensions.AI;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace EverythingServer.Prompts;

[McpServerPromptType]
public class ComplexPromptType
{
    [McpServerPrompt(Name = "complex_prompt"), Description("A prompt with arguments")]
    public static IEnumerable<ChatMessage> ComplexPrompt(
        [Description("Temperature setting")] int temperature,
        [Description("Output style")] string? style = null)
    {
        return [
            new ChatMessage(ChatRole.User,$"This is a complex prompt with arguments: temperature={temperature}, style={style}"),
            new ChatMessage(ChatRole.Assistant, "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?"),
            new ChatMessage(ChatRole.User, [new DataContent(TinyImageTool.MCP_TINY_IMAGE)])
        ];
    }
}

using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="NotificationMethods.ResourceUpdatedNotification"/>
/// notification sent whenever a subscribed resource changes.
/// </summary>
/// <remarks>
/// <para>
/// When a client subscribes to resource updates using <see cref="SubscribeRequestParams"/>, the server will
/// send notifications with this payload whenever the subscribed resource is modified. These notifications
/// allow clients to maintain synchronized state without needing to poll the server for changes.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class ResourceUpdatedNotificationParams : NotificationParams
{
    /// <summary>
    /// Gets or sets the URI of the resource that was updated.
    /// </summary>
    /// <remarks>
    /// The URI can use any protocol; it is up to the server how to interpret it.
    /// </remarks>
    [JsonPropertyName("uri")]
    [StringSyntax(StringSyntaxAttribute.Uri)]
    public string? Uri { get; init; }
}

namespace ModelContextProtocol;

/// <summary>Provides an <see cref="IProgress{ProgressNotificationValue}"/> that's a nop.</summary>
internal sealed class NullProgress : IProgress<ProgressNotificationValue>
{
    /// <summary>
    /// Gets the singleton instance of the <see cref="NullProgress"/> class that performs no operations when progress is reported.
    /// </summary>
    /// <remarks>
    /// Use this property when you need to provide an <see cref="IProgress{T}"/> implementation
    /// but don't need to track or report actual progress.
    /// </remarks>
    public static NullProgress Instance { get; } = new();

    /// <inheritdoc />
    public void Report(ProgressNotificationValue value)
    {
    }
}

using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.ToolsCall"/> request from a client to invoke a tool provided by the server.
/// </summary>
/// <remarks>
/// The server will respond with a <see cref="CallToolResult"/> containing the result of the tool invocation.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class CallToolRequestParams : RequestParams
{
    /// <summary>Gets or sets the name of the tool to invoke.</summary>
    [JsonPropertyName("name")]
    public required string Name { get; init; }

    /// <summary>
    /// Gets or sets optional arguments to pass to the tool when invoking it on the server.
    /// </summary>
    /// <remarks>
    /// This dictionary contains the parameter values to be passed to the tool. Each key-value pair represents
    /// a parameter name and its corresponding argument value.
    /// </remarks>
    [JsonPropertyName("arguments")]
    public IReadOnlyDictionary<string, JsonElement>? Arguments { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a server's response to a <see cref="RequestMethods.ResourcesTemplatesList"/> request from the client,
/// containing available resource templates.
/// </summary>
/// <remarks>
/// <para>
/// This result is returned when a client sends a <see cref="RequestMethods.ResourcesTemplatesList"/> request to discover
/// available resource templates on the server.
/// </para>
/// <para>
/// It inherits from <see cref="PaginatedResult"/>, allowing for paginated responses when there are many resource templates.
/// The server can provide the <see cref="PaginatedResult.NextCursor"/> property to indicate there are more
/// resource templates available beyond what was returned in the current response.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class ListResourceTemplatesResult : PaginatedResult
{
    /// <summary>
    /// Gets or sets a list of resource templates that the server offers.
    /// </summary>
    /// <remarks>
    /// This collection contains all the resource templates returned in the current page of results.
    /// Each <see cref="ResourceTemplate"/> provides metadata about resources available on the server,
    /// including URI templates, names, descriptions, and MIME types.
    /// </remarks>
    [JsonPropertyName("resourceTemplates")]
    public IList<ResourceTemplate> ResourceTemplates { get; set; } = [];
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents annotations that can be attached to content, resources, and resource templates.
/// </summary>
/// <remarks>
/// Annotations enable filtering and prioritization of content for different audiences.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class Annotations
{
    /// <summary>
    /// Gets or sets the intended audience for this content as an array of <see cref="Role"/> values.
    /// </summary>
    [JsonPropertyName("audience")]
    public IList<Role>? Audience { get; init; }

    /// <summary>
    /// Gets or sets a value indicating how important this data is for operating the server.
    /// </summary>
    /// <remarks>
    /// The value is a floating-point number between 0 and 1, where 0 represents the lowest priority
    /// 1 represents highest priority.
    /// </remarks>
    [JsonPropertyName("priority")]
    public float? Priority { get; init; }

    /// <summary>
    /// Gets or sets the moment the resource was last modified.
    /// </summary>
    /// <remarks>
    /// The corresponding JSON should be an ISO 8601 formatted string (e.g., \"2025-01-12T15:00:58Z\").
    /// Examples: last activity timestamp in an open file, timestamp when the resource was attached, etc.
    /// </remarks>
    [JsonPropertyName("lastModified")]
    public DateTimeOffset? LastModified { get; set; }
}

using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Client;

/// <summary>
/// Represents an instance of a Model Context Protocol (MCP) client that connects to and communicates with an MCP server.
/// </summary>
public interface IMcpClient : IMcpEndpoint
{
    /// <summary>
    /// Gets the capabilities supported by the connected server.
    /// </summary>
    /// <exception cref="InvalidOperationException">The client is not connected.</exception>
    ServerCapabilities ServerCapabilities { get; }

    /// <summary>
    /// Gets the implementation information of the connected server.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This property provides identification details about the connected server, including its name and version.
    /// It is populated during the initialization handshake and is available after a successful connection.
    /// </para>
    /// <para>
    /// This information can be useful for logging, debugging, compatibility checks, and displaying server
    /// information to users.
    /// </para>
    /// </remarks>
    /// <exception cref="InvalidOperationException">The client is not connected.</exception>
    Implementation ServerInfo { get; }

    /// <summary>
    /// Gets any instructions describing how to use the connected server and its features.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This property contains instructions provided by the server during initialization that explain
    /// how to effectively use its capabilities. These instructions can include details about available
    /// tools, expected input formats, limitations, or any other helpful information.
    /// </para>
    /// <para>
    /// This can be used by clients to improve an LLM's understanding of available tools, prompts, and resources.
    /// It can be thought of like a "hint" to the model and may be added to a system prompt.
    /// </para>
    /// </remarks>
    string? ServerInstructions { get; }
}

using Microsoft.Extensions.AI;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a server's response to a <see cref="RequestMethods.PromptsGet"/> request from the client.
/// </summary>
/// <remarks>
/// <para>
/// For integration with AI client libraries, <see cref="GetPromptResult"/> can be converted to
/// a collection of <see cref="ChatMessage"/> objects using the <see cref="AIContentExtensions.ToChatMessages"/> extension method.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class GetPromptResult : Result
{
    /// <summary>
    /// Gets or sets an optional description for the prompt.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This description provides contextual information about the prompt's purpose and use cases.
    /// It helps developers understand what the prompt is designed for and how it should be used.
    /// </para>
    /// <para>
    /// When returned from a server in response to a <see cref="RequestMethods.PromptsGet"/> request,
    /// this description can be used by client applications to provide context about the prompt or to
    /// display in user interfaces.
    /// </para>
    /// </remarks>
    [JsonPropertyName("description")]
    public string? Description { get; set; }

    /// <summary>
    /// Gets or sets the prompt that the server offers.
    /// </summary>
    [JsonPropertyName("messages")]
    public IList<PromptMessage> Messages { get; set; } = [];
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a JSON-RPC message used in the Model Context Protocol (MCP) and that includes an ID.
/// </summary>
/// <remarks>
/// In the JSON-RPC protocol, messages with an ID require a response from the receiver.
/// This includes request messages (which expect a matching response) and response messages
/// (which include the ID of the original request they're responding to).
/// The ID is used to correlate requests with their responses, allowing asynchronous
/// communication where multiple requests can be sent without waiting for responses.
/// </remarks>
public abstract class JsonRpcMessageWithId : JsonRpcMessage
{
    /// <summary>Prevent external derivations.</summary>
    private protected JsonRpcMessageWithId()
    {
    }

    /// <summary>
    /// Gets the message identifier.
    /// </summary>
    /// <remarks>
    /// Each ID is expected to be unique within the context of a given session.
    /// </remarks>
    [JsonPropertyName("id")]
    public RequestId Id { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents detailed error information for JSON-RPC error responses.
/// </summary>
/// <remarks>
/// This class is used as part of the <see cref="JsonRpcError"/> message to provide structured
/// error information when a request cannot be fulfilled. The JSON-RPC 2.0 specification defines
/// a standard format for error responses that includes a numeric code, a human-readable message,
/// and optional additional data.
/// </remarks>
public sealed class JsonRpcErrorDetail
{
    /// <summary>
    /// Gets an integer error code according to the JSON-RPC specification.
    /// </summary>
    [JsonPropertyName("code")]
    public required int Code { get; init; }

    /// <summary>
    /// Gets a short description of the error.
    /// </summary>
    /// <remarks>
    /// This is expected to be a brief, human-readable explanation of what went wrong.
    /// For standard error codes, it's recommended to use the descriptions defined
    /// in the JSON-RPC 2.0 specification.
    /// </remarks>
    [JsonPropertyName("message")]
    public required string Message { get; init; }

    /// <summary>
    /// Gets optional additional error data.
    /// </summary>
    /// <remarks>
    /// This property can contain any additional information that might help the client
    /// understand or resolve the error. Common examples include validation errors,
    /// stack traces (in development environments), or contextual information about
    /// the error condition.
    /// </remarks>
    [JsonPropertyName("data")]
    public object? Data { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Authentication;

/// <summary>
/// Represents a client registration request for OAuth 2.0 Dynamic Client Registration (RFC 7591).
/// </summary>
internal sealed class DynamicClientRegistrationRequest
{
    /// <summary>
    /// Gets or sets the redirect URIs for the client.
    /// </summary>
    [JsonPropertyName("redirect_uris")]
    public required string[] RedirectUris { get; init; }

    /// <summary>
    /// Gets or sets the token endpoint authentication method.
    /// </summary>
    [JsonPropertyName("token_endpoint_auth_method")]
    public string? TokenEndpointAuthMethod { get; init; }

    /// <summary>
    /// Gets or sets the grant types that the client will use.
    /// </summary>
    [JsonPropertyName("grant_types")]
    public string[]? GrantTypes { get; init; }

    /// <summary>
    /// Gets or sets the response types that the client will use.
    /// </summary>
    [JsonPropertyName("response_types")]
    public string[]? ResponseTypes { get; init; }

    /// <summary>
    /// Gets or sets the human-readable name of the client.
    /// </summary>
    [JsonPropertyName("client_name")]
    public string? ClientName { get; init; }

    /// <summary>
    /// Gets or sets the URL of the client's home page.
    /// </summary>
    [JsonPropertyName("client_uri")]
    public string? ClientUri { get; init; }

    /// <summary>
    /// Gets or sets the scope values that the client will use.
    /// </summary>
    [JsonPropertyName("scope")]
    public string? Scope { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a server's preferences for model selection, requested of the client during sampling.
/// </summary>
/// <remarks>
/// <para>
/// Because LLMs can vary along multiple dimensions, choosing the "best" model is
/// rarely straightforward.  Different models excel in different areas—some are
/// faster but less capable, others are more capable but more expensive, and so
/// on. This class allows servers to express their priorities across multiple
/// dimensions to help clients make an appropriate selection for their use case.
/// </para>
/// <para>
/// These preferences are always advisory. The client may ignore them. It is also
/// up to the client to decide how to interpret these preferences and how to
/// balance them against other considerations.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class ModelPreferences
{
    /// <summary>
    /// Gets or sets how much to prioritize cost when selecting a model.
    /// </summary>
    /// <remarks>
    /// A value of 0 means cost is not important, while a value of 1 means cost is the most important factor.
    /// </remarks>
    [JsonPropertyName("costPriority")]
    public float? CostPriority { get; init; }

    /// <summary>
    /// Gets or sets optional hints to use for model selection.
    /// </summary>
    [JsonPropertyName("hints")]
    public IReadOnlyList<ModelHint>? Hints { get; init; }

    /// <summary>
    /// Gets or sets how much to prioritize sampling speed (latency) when selecting a model.
    /// </summary>
    /// <remarks>
    /// A value of 0 means speed is not important, while a value of 1 means speed is the most important factor.
    /// </remarks>
    [JsonPropertyName("speedPriority")]
    public float? SpeedPriority { get; init; }

    /// <summary>
    /// Gets or sets how much to prioritize intelligence and capabilities when selecting a model.
    /// </summary>
    /// <remarks>
    /// A value of 0 means intelligence is not important, while a value of 1 means intelligence is the most important factor.
    /// </remarks>
    [JsonPropertyName("intelligencePriority")]
    public float? IntelligencePriority { get; init; }
}

using ModelContextProtocol.Server;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the logging capability configuration for a Model Context Protocol server.
/// </summary>
/// <remarks>
/// This capability allows clients to set the logging level and receive log messages from the server.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class LoggingCapability
{
    // Currently empty in the spec, but may be extended in the future

    /// <summary>
    /// Gets or sets the handler for set logging level requests from clients.
    /// </summary>
    [JsonIgnore]
    public Func<RequestContext<SetLevelRequestParams>, CancellationToken, ValueTask<EmptyResult>>? SetLoggingLevelHandler { get; set; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the server's response to a <see cref="RequestMethods.CompletionComplete"/> request,
/// containing suggested values for a given argument.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="CompleteResult"/> is returned by the server in response to a <see cref="RequestMethods.CompletionComplete"/>
/// request from the client. It provides suggested completions or valid values for a specific argument in a tool or resource reference.
/// </para>
/// <para>
/// The result contains a <see cref="Completion"/> object with suggested values, pagination information,
/// and the total number of available completions. This is similar to auto-completion functionality in code editors.
/// </para>
/// <para>
/// Clients typically use this to implement auto-suggestion features when users are inputting parameters
/// for tool calls or resource references.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class CompleteResult : Result
{
    /// <summary>
    /// Gets or sets the completion object containing the suggested values and pagination information.
    /// </summary>
    /// <remarks>
    /// If no completions are available for the given input, the <see cref="Completion.Values"/>
    /// collection will be empty.
    /// </remarks>
    [JsonPropertyName("completion")]
    public Completion Completion { get; set; } = new Completion();
}

using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.PromptsGet"/> request from a client to get a prompt provided by a server.
/// </summary>
/// <remarks>
/// The server will respond with a <see cref="GetPromptResult"/> containing the resulting prompt.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class GetPromptRequestParams : RequestParams
{
    /// <summary>
    /// Gets or sets the name of the prompt.
    /// </summary>
    [JsonPropertyName("name")]
    public required string Name { get; init; }

    /// <summary>
    /// Gets or sets arguments to use for templating the prompt when retrieving it from the server.
    /// </summary>
    /// <remarks>
    /// Typically, these arguments are used to replace placeholders in prompt templates. The keys in this dictionary
    /// should match the names defined in the prompt's <see cref="Prompt.Arguments"/> list. However, the server may
    /// choose to use these arguments in any way it deems appropriate to generate the prompt.
    /// </remarks>
    [JsonPropertyName("arguments")]
    public IReadOnlyDictionary<string, JsonElement>? Arguments { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a client's response to a <see cref="RequestMethods.SamplingCreateMessage"/> from the server.
/// </summary>
/// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class CreateMessageResult : Result
{
    /// <summary>
    /// Gets or sets the content of the message.
    /// </summary>
    [JsonPropertyName("content")]
    public required ContentBlock Content { get; init; }

    /// <summary>
    /// Gets or sets the name of the model that generated the message.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This should contain the specific model identifier such as "claude-3-5-sonnet-20241022" or "o3-mini".
    /// </para>
    /// <para>
    /// This property allows the server to know which model was used to generate the response,
    /// enabling appropriate handling based on the model's capabilities and characteristics.
    /// </para>
    /// </remarks>
    [JsonPropertyName("model")]
    public required string Model { get; init; }

    /// <summary>
    /// Gets or sets the reason why message generation (sampling) stopped, if known.
    /// </summary>
    /// <remarks>
    /// Common values include:
    /// <list type="bullet">
    ///   <item><term>endTurn</term><description>The model naturally completed its response.</description></item>
    ///   <item><term>maxTokens</term><description>The response was truncated due to reaching token limits.</description></item>
    ///   <item><term>stopSequence</term><description>A specific stop sequence was encountered during generation.</description></item>
    /// </list>
    /// </remarks>
    [JsonPropertyName("stopReason")]
    public string? StopReason { get; init; }

    /// <summary>
    /// Gets or sets the role of the user who generated the message.
    /// </summary>
    [JsonPropertyName("role")]
    public required Role Role { get; init; }
}

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// A successful response message in the JSON-RPC protocol.
/// </summary>
/// <remarks>
/// <para>
/// Response messages are sent in reply to a request message and contain the result of the method execution.
/// Each response includes the same ID as the original request, allowing the sender to match responses
/// with their corresponding requests.
/// </para>
/// <para>
/// This class represents a successful response with a result. For error responses, see <see cref="JsonRpcError"/>.
/// </para>
/// </remarks>
public sealed class JsonRpcResponse : JsonRpcMessageWithId
{
    /// <summary>
    /// Gets the result of the method invocation.
    /// </summary>
    /// <remarks>
    /// This property contains the result data returned by the server in response to the JSON-RPC method request.
    /// </remarks>
    [JsonPropertyName("result")]
    public required JsonNode? Result { get; init; }
}

using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Sent from the client to cancel resource update notifications from the server for a specific resource.
/// </summary>
/// <remarks>
/// <para>
/// After a client has subscribed to resource updates using <see cref="SubscribeRequestParams"/>,
/// this message can be sent to stop receiving notifications for a specific resource.
/// This is useful for conserving resources and network bandwidth when
/// the client no longer needs to track changes to a particular resource.
/// </para>
/// <para>
/// The unsubscribe operation is idempotent, meaning it can be called multiple times
/// for the same resource without causing errors, even if there is no active subscription.
/// </para>
/// </remarks>
public sealed class UnsubscribeRequestParams : RequestParams
{
    /// <summary>
    /// The URI of the resource to unsubscribe from. The URI can use any protocol; it is up to the server how to interpret it.
    /// </summary>
    [JsonPropertyName("uri")]
    [StringSyntax(StringSyntaxAttribute.Uri)]
    public string? Uri { get; init; }
}

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using TestServerWithHosting.Tools;

Log.Logger = new LoggerConfiguration()
           .MinimumLevel.Verbose() // Capture all log levels
           .WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "TestServer_.log"),
               rollingInterval: RollingInterval.Day,
               outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
           .WriteTo.Debug()
           .WriteTo.Console(standardErrorFromLevel: Serilog.Events.LogEventLevel.Verbose)
           .CreateLogger();

try
{
    Log.Information("Starting server...");

    var builder = Host.CreateApplicationBuilder(args);
    builder.Services.AddSerilog();
    builder.Services.AddMcpServer()
        .WithStdioServerTransport()
        .WithTools<EchoTool>()
        .WithTools<SampleLlmTool>();

    var app = builder.Build();

    await app.RunAsync();
    return 0;
}
catch (Exception ex)
{
    Log.Fatal(ex, "Host terminated unexpectedly");
    return 1;
}
finally
{
    Log.CloseAndFlush();
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Authentication;

/// <summary>
/// Represents a client registration response for OAuth 2.0 Dynamic Client Registration (RFC 7591).
/// </summary>
internal sealed class DynamicClientRegistrationResponse
{
    /// <summary>
    /// Gets or sets the client identifier.
    /// </summary>
    [JsonPropertyName("client_id")]
    public required string ClientId { get; init; }

    /// <summary>
    /// Gets or sets the client secret.
    /// </summary>
    [JsonPropertyName("client_secret")]
    public string? ClientSecret { get; init; }

    /// <summary>
    /// Gets or sets the redirect URIs for the client.
    /// </summary>
    [JsonPropertyName("redirect_uris")]
    public string[]? RedirectUris { get; init; }

    /// <summary>
    /// Gets or sets the token endpoint authentication method.
    /// </summary>
    [JsonPropertyName("token_endpoint_auth_method")]
    public string? TokenEndpointAuthMethod { get; init; }

    /// <summary>
    /// Gets or sets the grant types that the client will use.
    /// </summary>
    [JsonPropertyName("grant_types")]
    public string[]? GrantTypes { get; init; }

    /// <summary>
    /// Gets or sets the response types that the client will use.
    /// </summary>
    [JsonPropertyName("response_types")]
    public string[]? ResponseTypes { get; init; }

    /// <summary>
    /// Gets or sets the client ID issued timestamp.
    /// </summary>
    [JsonPropertyName("client_id_issued_at")]
    public long? ClientIdIssuedAt { get; init; }

    /// <summary>
    /// Gets or sets the client secret expiration time.
    /// </summary>
    [JsonPropertyName("client_secret_expires_at")]
    public long? ClientSecretExpiresAt { get; init; }
}

using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.ResourcesSubscribe"/> request from a client
/// to request real-time notifications from the server whenever a particular resource changes.
/// </summary>
/// <remarks>
/// <para>
/// The subscription mechanism allows clients to be notified about changes to specific resources
/// identified by their URI. When a subscribed resource changes, the server sends a notification
/// to the client with the updated resource information.
/// </para>
/// <para>
/// Subscriptions remain active until explicitly canceled using <see cref="UnsubscribeRequestParams"/>
/// or until the connection is terminated.
/// </para>
/// <para>
/// The server may refuse or limit subscriptions based on its capabilities or resource constraints.
/// </para>
/// </remarks>
public sealed class SubscribeRequestParams : RequestParams
{
    /// <summary>
    /// Gets or sets the URI of the resource to subscribe to.
    /// </summary>
    /// <remarks>
    /// The URI can use any protocol; it is up to the server how to interpret it.
    /// </remarks>
    [JsonPropertyName("uri")]
    [StringSyntax(StringSyntaxAttribute.Uri)]
    public string? Uri { get; init; }
}

using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a client's response to a <see cref="RequestMethods.RootsList"/> request from the server,
/// containing available roots.
/// </summary>
/// <remarks>
/// <para>
/// This result is returned when a server sends a <see cref="RequestMethods.RootsList"/> request to discover
/// available roots on the client.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class ListRootsResult : Result
{
    /// <summary>
    /// Gets or sets the list of root URIs provided by the client.
    /// </summary>
    /// <remarks>
    /// This collection contains all available root URIs and their associated metadata.
    /// Each root serves as an entry point for resource navigation in the Model Context Protocol.
    /// </remarks>
    [JsonPropertyName("roots")]
    public required IReadOnlyList<Root> Roots { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a server's response to a <see cref="RequestMethods.PromptsList"/> request from the client, containing available prompts.
/// </summary>
/// <remarks>
/// <para>
/// This result is returned when a client sends a <see cref="RequestMethods.PromptsList"/> request to discover available prompts on the server.
/// </para>
/// <para>
/// It inherits from <see cref="PaginatedResult"/>, allowing for paginated responses when there are many prompts.
/// The server can provide the <see cref="PaginatedResult.NextCursor"/> property to indicate there are more
/// prompts available beyond what was returned in the current response.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class ListPromptsResult : PaginatedResult
{
    /// <summary>
    /// A list of prompts or prompt templates that the server offers.
    /// </summary>
    [JsonPropertyName("prompts")]
    public IList<Prompt> Prompts { get; set; } = [];
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.CompletionComplete"/> request from
/// a client to ask a server for auto-completion suggestions.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="RequestMethods.CompletionComplete"/> is used in the Model Context Protocol completion workflow
/// to provide intelligent suggestions for partial inputs related to resources, prompts, or other referenceable entities.
/// The completion mechanism in MCP allows clients to request suggestions based on partial inputs.
/// The server will respond with a <see cref="CompleteResult"/> containing matching values.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class CompleteRequestParams : RequestParams
{
    /// <summary>
    /// Gets or sets the reference's information.
    /// </summary>
    [JsonPropertyName("ref")]
    public required Reference Ref { get; init; }

    /// <summary>
    /// Gets or sets the argument information for the completion request, specifying what is being completed
    /// and the current partial input.
    /// </summary>
    [JsonPropertyName("argument")]
    public required Argument Argument { get; init; }

    /// <summary>
    /// Gets or sets additional, optional context for completions.
    /// </summary>
    [JsonPropertyName("context")]
    public CompleteContext? Context { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a server's response to a <see cref="RequestMethods.ResourcesList"/> request from the client, containing available resources.
/// </summary>
/// <remarks>
/// <para>
/// This result is returned when a client sends a <see cref="RequestMethods.ResourcesList"/> request to discover available resources on the server.
/// </para>
/// <para>
/// It inherits from <see cref="PaginatedResult"/>, allowing for paginated responses when there are many resources.
/// The server can provide the <see cref="PaginatedResult.NextCursor"/> property to indicate there are more
/// resources available beyond what was returned in the current response.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class ListResourcesResult : PaginatedResult
{
    /// <summary>
    /// A list of resources that the server offers.
    /// </summary>
    [JsonPropertyName("resources")]
    public IList<Resource> Resources { get; set; } = [];
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Provides a base class for paginated requests.
/// </summary>
/// <remarks>
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">See the schema for details</see>
/// </remarks>
public abstract class PaginatedRequestParams : RequestParams
{
    /// <summary>Prevent external derivations.</summary>
    private protected PaginatedRequestParams()
    {
    }

    /// <summary>
    /// Gets or sets an opaque token representing the current pagination position.
    /// </summary>
    /// <remarks>
    /// If provided, the server should return results starting after this cursor.
    /// This value should be obtained from the <see cref="PaginatedResult.NextCursor"/>
    /// property of a previous request's response.
    /// </remarks>
    [JsonPropertyName("cursor")]
    public string? Cursor { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Provides the name and version of an MCP implementation.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="Implementation"/> class is used to identify MCP clients and servers during the initialization handshake.
/// It provides version and name information that can be used for compatibility checks, logging, and debugging.
/// </para>
/// <para>
/// Both clients and servers provide this information during connection establishment.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class Implementation : IBaseMetadata
{
    /// <inheritdoc />
    [JsonPropertyName("name")]
    public required string Name { get; set; }

    /// <inheritdoc />
    [JsonPropertyName("title")]
    public string? Title { get; set; }

    /// <summary>
    /// Gets or sets the version of the implementation.
    /// </summary>
    /// <remarks>
    /// The version is used during client-server handshake to identify implementation versions,
    /// which can be important for troubleshooting compatibility issues or when reporting bugs.
    /// </remarks>
    [JsonPropertyName("version")]
    public required string Version { get; set; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a message issued to or received from an LLM API within the Model Context Protocol.
/// </summary>
/// <remarks>
/// <para>
/// A <see cref="SamplingMessage"/> encapsulates content sent to or received from AI models in the Model Context Protocol.
/// Each message has a specific role (<see cref="Role.User"/> or <see cref="Role.Assistant"/>) and contains content which can be text or images.
/// </para>
/// <para>
/// <see cref="SamplingMessage"/> objects are typically used in collections within <see cref="CreateMessageRequestParams"/>
/// to represent prompts or queries for LLM sampling. They form the core data structure for text generation requests
/// within the Model Context Protocol.
/// </para>
/// <para>
/// While similar to <see cref="PromptMessage"/>, the <see cref="SamplingMessage"/> is focused on direct LLM sampling
/// operations rather than the enhanced resource embedding capabilities provided by <see cref="PromptMessage"/>.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class SamplingMessage
{
    /// <summary>
    /// Gets or sets the content of the message.
    /// </summary>
    [JsonPropertyName("content")]
    public required ContentBlock Content { get; init; }

    /// <summary>
    /// Gets or sets the role of the message sender, indicating whether it's from a "user" or an "assistant".
    /// </summary>
    [JsonPropertyName("role")]
    public required Role Role { get; init; }
}

#if !NET
using ModelContextProtocol;

namespace System.Collections.Generic;

internal static class CollectionExtensions
{
    public static TValue? GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key)
    {
        return dictionary.GetValueOrDefault(key, default!);
    }

    public static TValue GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue)
    {
        Throw.IfNull(dictionary);

        return dictionary.TryGetValue(key, out TValue? value) ? value : defaultValue;
    }

    public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source) =>
        source.ToDictionary(kv => kv.Key, kv => kv.Value);
}
#endif

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="NotificationMethods.ResourceListChangedNotification"/>
/// notification from the server to the client, informing it that the list of resources it can read from has changed.
/// </summary>
/// <remarks>
/// <para>
/// This may be issued by servers without any previous subscription from the client.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class ResourceListChangedNotificationParams : NotificationParams;

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Provides a base class for notification parameters.
/// </summary>
public abstract class NotificationParams
{
    /// <summary>Prevent external derivations.</summary>
    private protected NotificationParams()
    {
    }

    /// <summary>
    /// Gets or sets metadata reserved by MCP for protocol-level metadata.
    /// </summary>
    /// <remarks>
    /// Implementations must not make assumptions about its contents.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonObject? Meta { get; set; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents an argument used in completion requests to provide context for auto-completion functionality.
/// </summary>
/// <remarks>
/// This class is used when requesting completion suggestions for a particular field or parameter.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class Argument
{
    /// <summary>
    /// Gets or sets the name of the argument being completed.
    /// </summary>
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the current partial text value for which completion suggestions are requested.
    /// </summary>
    /// <remarks>
    /// This represents the text that has been entered so far and for which completion
    /// options should be generated.
    /// </remarks>
    [JsonPropertyName("value")]
    public string Value { get; set; } = string.Empty;
}

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="NotificationMethods.PromptListChangedNotification"/>
/// notification from the server to the client, informing it that the list of prompts it offers has changed.
/// </summary>
/// <remarks>
/// <para>
/// This may be issued by servers without any previous subscription from the client.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class PromptListChangedNotificationParams : NotificationParams;

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the binary contents of a resource in the Model Context Protocol.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="BlobResourceContents"/> is used when binary data needs to be exchanged through
/// the Model Context Protocol. The binary data is represented as a base64-encoded string
/// in the <see cref="Blob"/> property.
/// </para>
/// <para>
/// This class inherits from <see cref="ResourceContents"/>, which also has a sibling implementation
/// <see cref="TextResourceContents"/> for text-based resources. When working with resources, the
/// appropriate type is chosen based on the nature of the content.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for more details.
/// </para>
/// </remarks>
public sealed class BlobResourceContents : ResourceContents
{
    /// <summary>
    /// Gets or sets the base64-encoded string representing the binary data of the item.
    /// </summary>
    [JsonPropertyName("blob")]
    public string Blob { get; set; } = string.Empty;
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents text-based contents of a resource in the Model Context Protocol.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="TextResourceContents"/> is used when textual data needs to be exchanged through
/// the Model Context Protocol. The text is stored directly in the <see cref="Text"/> property.
/// </para>
/// <para>
/// This class inherits from <see cref="ResourceContents"/>, which also has a sibling implementation
/// <see cref="BlobResourceContents"/> for binary resources. When working with resources, the
/// appropriate type is chosen based on the nature of the content.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for more details.
/// </para>
/// </remarks>
public sealed class TextResourceContents : ResourceContents
{
    /// <summary>
    /// Gets or sets the text of the item.
    /// </summary>
    [JsonPropertyName("text")]
    public string Text { get; set; } = string.Empty;
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a server's response to a <see cref="RequestMethods.ToolsList"/> request from the client, containing available tools.
/// </summary>
/// <remarks>
/// <para>
/// This result is returned when a client sends a <see cref="RequestMethods.ToolsList"/> request to discover available tools on the server.
/// </para>
/// <para>
/// It inherits from <see cref="PaginatedResult"/>, allowing for paginated responses when there are many tools.
/// The server can provide the <see cref="PaginatedResult.NextCursor"/> property to indicate there are more
/// tools available beyond what was returned in the current response.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class ListToolsResult : PaginatedResult
{
    /// <summary>
    /// The server's response to a tools/list request from the client.
    /// </summary>
    [JsonPropertyName("tools")]
    public IList<Tool> Tools { get; set; } = [];
}

using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the client's response to an elicitation request.
/// </summary>
public sealed class ElicitResult : Result
{
    /// <summary>
    /// Gets or sets the user action in response to the elicitation.
    /// </summary>
    /// <remarks>
    /// <list type="bullet">
    ///   <item>
    ///     <term>"accept"</term>
    ///     <description>User submitted the form/confirmed the action</description>
    ///   </item>
    ///   <item>
    ///     <term>"decline"</term>
    ///     <description>User explicitly declined the action</description>
    ///   </item>
    ///   <item>
    ///     <term>"cancel"</term>
    ///     <description>User dismissed without making an explicit choice</description>
    ///   </item>
    /// </list>
    /// </remarks>
    [JsonPropertyName("action")]
    public string Action { get; set; } = "cancel";

    /// <summary>
    /// Gets or sets the submitted form data.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This is typically omitted if the action is "cancel" or "decline".
    /// </para>
    /// <para>
    /// Values in the dictionary should be of types <see cref="JsonValueKind.String"/>, <see cref="JsonValueKind.Number"/>,
    /// <see cref="JsonValueKind.True"/>, or <see cref="JsonValueKind.False"/>.
    /// </para>
    /// </remarks>
    [JsonPropertyName("content")]
    public IDictionary<string, JsonElement>? Content { get; set; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Provides hints to use for model selection.
/// </summary>
/// <remarks>
/// <para>
/// When multiple hints are specified in <see cref="ModelPreferences.Hints"/>, they are evaluated in order,
/// with the first match taking precedence. Clients should prioritize these hints over numeric priorities.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class ModelHint
{
    /// <summary>
    /// Gets or sets a hint for a model name.
    /// </summary>
    /// <remarks>
    /// The specified string can be a partial or full model name. Clients may also
    /// map hints to equivalent models from different providers. Clients make the final model
    /// selection based on these preferences and their available models.
    /// </remarks>
    [JsonPropertyName("name")]
    public string? Name { get; init; }
}

#if !NET
using ModelContextProtocol;

namespace System.Net.Http;

internal static class HttpClientExtensions
{
    public static async Task<Stream> ReadAsStreamAsync(this HttpContent content, CancellationToken cancellationToken)
    {
        Throw.IfNull(content);

        cancellationToken.ThrowIfCancellationRequested();
        return await content.ReadAsStreamAsync();
    }

    public static async Task<string> ReadAsStringAsync(this HttpContent content, CancellationToken cancellationToken)
    {
        Throw.IfNull(content);

        cancellationToken.ThrowIfCancellationRequested();
        return await content.ReadAsStringAsync();
    }
}
#endif

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="NotificationMethods.ToolListChangedNotification"/>
/// notification from the server to the client, informing it that the list of tools it offers has changed.
/// </summary>
/// <remarks>
/// <para>
/// This may be issued by servers without any previous subscription from the client.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class ToolListChangedNotificationParams : NotificationParams;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="NotificationMethods.RootsListChangedNotification"/>
/// notification from the client to the server, informing it that the list of roots has changed.
/// </summary>
/// <remarks>
/// <para>
/// This may be issued by servers without any previous subscription from the client.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class RootsListChangedNotificationParams : NotificationParams;

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents an error response message in the JSON-RPC protocol.
/// </summary>
/// <remarks>
/// <para>
/// Error responses are sent when a request cannot be fulfilled or encounters an error during processing.
/// Like successful responses, error messages include the same ID as the original request, allowing the
/// sender to match errors with their corresponding requests.
/// </para>
/// <para>
/// Each error response contains a structured error detail object with a numeric code, descriptive message,
/// and optional additional data to provide more context about the error.
/// </para>
/// </remarks>
public sealed class JsonRpcError : JsonRpcMessageWithId
{
    /// <summary>
    /// Gets detailed error information for the failed request, containing an error code,
    /// message, and optional additional data
    /// </summary>
    [JsonPropertyName("error")]
    public required JsonRpcErrorDetail Error { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Authentication;

/// <summary>
/// Represents the metadata about an OAuth authorization server.
/// </summary>
internal sealed class AuthorizationServerMetadata
{
    /// <summary>
    /// The authorization endpoint URI.
    /// </summary>
    [JsonPropertyName("authorization_endpoint")]
    public Uri AuthorizationEndpoint { get; set; } = null!;

    /// <summary>
    /// The token endpoint URI.
    /// </summary>
    [JsonPropertyName("token_endpoint")]
    public Uri TokenEndpoint { get; set; } = null!;

    /// <summary>
    /// The registration endpoint URI.
    /// </summary>
    [JsonPropertyName("registration_endpoint")]
    public Uri? RegistrationEndpoint { get; set; }

    /// <summary>
    /// The revocation endpoint URI.
    /// </summary>
    [JsonPropertyName("revocation_endpoint")]
    public Uri? RevocationEndpoint { get; set; }

    /// <summary>
    /// The response types supported by the authorization server.
    /// </summary>
    [JsonPropertyName("response_types_supported")]
    public List<string>? ResponseTypesSupported { get; set; }

    /// <summary>
    /// The grant types supported by the authorization server.
    /// </summary>
    [JsonPropertyName("grant_types_supported")]
    public List<string>? GrantTypesSupported { get; set; }

    /// <summary>
    /// The token endpoint authentication methods supported by the authorization server.
    /// </summary>
    [JsonPropertyName("token_endpoint_auth_methods_supported")]
    public List<string>? TokenEndpointAuthMethodsSupported { get; set; }

    /// <summary>
    /// The code challenge methods supported by the authorization server.
    /// </summary>
    [JsonPropertyName("code_challenge_methods_supported")]
    public List<string>? CodeChallengeMethodsSupported { get; set; }

    /// <summary>
    /// The issuer URI of the authorization server.
    /// </summary>
    [JsonPropertyName("issuer")]
    public Uri? Issuer { get; set; }

    /// <summary>
    /// The scopes supported by the authorization server.
    /// </summary>
    [JsonPropertyName("scopes_supported")]
    public List<string>? ScopesSupported { get; set; }
}

using System.Text.Json;

namespace ModelContextProtocol;

internal static class HttpClientExt
{
    public static async Task<JsonDocument> ReadJsonDocumentAsync(this HttpClient client, string requestUri)
    {
        using var response = await client.GetAsync(requestUri);
        response.EnsureSuccessStatusCode();
        return await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
    }
}

namespace ModelContextProtocol.AspNetCore.Authentication;

/// <summary>
/// Represents the authentication events for Model Context Protocol.
/// </summary>
public class McpAuthenticationEvents
{
    /// <summary>
    /// Gets or sets the function that is invoked when resource metadata is requested.
    /// </summary>
    /// <remarks>
    /// This function is called when a resource metadata request is made to the protected resource metadata endpoint.
    /// The implementer should set the <see cref="ResourceMetadataRequestContext.ResourceMetadata"/> property
    /// to provide the appropriate metadata for the current request.
    /// </remarks>
    public Func<ResourceMetadataRequestContext, Task> OnResourceMetadataRequest { get; set; } = context => Task.CompletedTask;
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a server's response to a <see cref="RequestMethods.ResourcesRead"/> request from the client.
/// </summary>
/// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class ReadResourceResult : Result
{
    /// <summary>
    /// Gets or sets a list of <see cref="ResourceContents"/> objects that this resource contains.
    /// </summary>
    /// <remarks>
    /// This property contains the actual content of the requested resource, which can be
    /// either text-based (<see cref="TextResourceContents"/>) or binary (<see cref="BlobResourceContents"/>).
    /// The type of content included depends on the resource being accessed.
    /// </remarks>
    [JsonPropertyName("contents")]
    public IList<ResourceContents> Contents { get; set; } = [];
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

///
/// Indicates the severity of a log message.
///
/// <remarks>
/// These map to syslog message severities, as specified in <see href="https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1">RFC-5424</see>.
/// </remarks>
[JsonConverter(typeof(CustomizableJsonStringEnumConverter<LoggingLevel>))]
public enum LoggingLevel
{
    /// Detailed debug information, typically only valuable to developers.
    [JsonStringEnumMemberName("debug")]
    Debug,

    /// Normal operational messages that require no action.
    [JsonStringEnumMemberName("info")]
    Info,

    /// Normal but significant events that might deserve attention.
    [JsonStringEnumMemberName("notice")]
    Notice,

    /// Warning conditions that don't represent an error but indicate potential issues.
    [JsonStringEnumMemberName("warning")]
    Warning,

    /// Error conditions that should be addressed but don't require immediate action.
    [JsonStringEnumMemberName("error")]
    Error,

    /// Critical conditions that require immediate attention.
    [JsonStringEnumMemberName("critical")]
    Critical,

    /// Action must be taken immediately to address the condition.
    [JsonStringEnumMemberName("alert")]
    Alert,

    /// System is unusable and requires immediate attention.
    [JsonStringEnumMemberName("emergency")]
    Emergency
}

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the result of a <see cref="RequestMethods.Ping"/> request in the Model Context Protocol.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="PingResult"/> is returned in response to a <see cref="RequestMethods.Ping"/> request,
/// which is used to verify that the connection between client and server is still alive and responsive.
/// Since this is a simple connectivity check, the result is an empty object containing no data.
/// </para>
/// <para>
/// Ping requests can be initiated by either the client or the server to check if the other party
/// is still responsive.
/// </para>
/// </remarks>
public sealed class PingResult : Result;

using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents a root URI and its metadata in the Model Context Protocol.
/// </summary>
/// <remarks>
/// Root URIs serve as entry points for resource navigation, typically representing
/// top-level directories or container resources that can be accessed and traversed.
/// Roots provide a hierarchical structure for organizing and accessing resources within the protocol.
/// Each root has a URI that uniquely identifies it and optional metadata like a human-readable name.
/// </remarks>
public sealed class Root
{
    /// <summary>
    /// Gets or sets the URI of the root.
    /// </summary>
    [JsonPropertyName("uri")]
    public required string Uri { get; init; }

    /// <summary>
    /// Gets or sets a human-readable name for the root.
    /// </summary>
    [JsonPropertyName("name")]
    public string? Name { get; init; }

    /// <summary>
    /// Gets or sets additional metadata for the root.
    /// </summary>
    /// <remarks>
    /// This is reserved by the protocol for future use.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonElement? Meta { get; init; }
}

#if !NET
using ModelContextProtocol;

namespace System.Threading.Tasks;

internal static class CancellationTokenSourceExtensions
{
    public static Task CancelAsync(this CancellationTokenSource cancellationTokenSource)
    {
        Throw.IfNull(cancellationTokenSource);

        cancellationTokenSource.Cancel();
        return Task.CompletedTask;
    }
}
#endif

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.LoggingSetLevel"/> request from a client
/// to enable or adjust logging.
/// </summary>
/// <remarks>
/// This request allows clients to configure the level of logging information they want to receive from the server.
/// The server will send notifications for log events at the specified level and all higher (more severe) levels.
/// </remarks>
public sealed class SetLevelRequestParams : RequestParams
{
    /// <summary>
    /// Gets or sets the level of logging that the client wants to receive from the server.
    /// </summary>
    [JsonPropertyName("level")]
    public required LoggingLevel Level { get; init; }
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET
namespace System.Diagnostics.CodeAnalysis;

/// <summary>
/// Specifies the types of members that are dynamically accessed.
///
/// This enumeration has a <see cref="FlagsAttribute"/> attribute that allows a
/// bitwise combination of its member values.
/// </summary>
[Flags]
internal enum DynamicallyAccessedMemberTypes
{
    /// <summary>
    /// Specifies no members.
    /// </summary>
    None = 0,

    /// <summary>
    /// Specifies the default, parameterless public constructor.
    /// </summary>
    PublicParameterlessConstructor = 0x0001,

    /// <summary>
    /// Specifies all public constructors.
    /// </summary>
    PublicConstructors = 0x0002 | PublicParameterlessConstructor,

    /// <summary>
    /// Specifies all non-public constructors.
    /// </summary>
    NonPublicConstructors = 0x0004,

    /// <summary>
    /// Specifies all public methods.
    /// </summary>
    PublicMethods = 0x0008,

    /// <summary>
    /// Specifies all non-public methods.
    /// </summary>
    NonPublicMethods = 0x0010,

    /// <summary>
    /// Specifies all public fields.
    /// </summary>
    PublicFields = 0x0020,

    /// <summary>
    /// Specifies all non-public fields.
    /// </summary>
    NonPublicFields = 0x0040,

    /// <summary>
    /// Specifies all public nested types.
    /// </summary>
    PublicNestedTypes = 0x0080,

    /// <summary>
    /// Specifies all non-public nested types.
    /// </summary>
    NonPublicNestedTypes = 0x0100,

    /// <summary>
    /// Specifies all public properties.
    /// </summary>
    PublicProperties = 0x0200,

    /// <summary>
    /// Specifies all non-public properties.
    /// </summary>
    NonPublicProperties = 0x0400,

    /// <summary>
    /// Specifies all public events.
    /// </summary>
    PublicEvents = 0x0800,

    /// <summary>
    /// Specifies all non-public events.
    /// </summary>
    NonPublicEvents = 0x1000,

    /// <summary>
    /// Specifies all interfaces implemented by the type.
    /// </summary>
    Interfaces = 0x2000,

    /// <summary>
    /// Specifies all non-public constructors, including those inherited from base classes.
    /// </summary>
    NonPublicConstructorsWithInherited = NonPublicConstructors | 0x4000,

    /// <summary>
    /// Specifies all non-public methods, including those inherited from base classes.
    /// </summary>
    NonPublicMethodsWithInherited = NonPublicMethods | 0x8000,

    /// <summary>
    /// Specifies all non-public fields, including those inherited from base classes.
    /// </summary>
    NonPublicFieldsWithInherited = NonPublicFields | 0x10000,

    /// <summary>
    /// Specifies all non-public nested types, including those inherited from base classes.
    /// </summary>
    NonPublicNestedTypesWithInherited = NonPublicNestedTypes | 0x20000,

    /// <summary>
    /// Specifies all non-public properties, including those inherited from base classes.
    /// </summary>
    NonPublicPropertiesWithInherited = NonPublicProperties | 0x40000,

    /// <summary>
    /// Specifies all non-public events, including those inherited from base classes.
    /// </summary>
    NonPublicEventsWithInherited = NonPublicEvents | 0x80000,

    /// <summary>
    /// Specifies all public constructors, including those inherited from base classes.
    /// </summary>
    PublicConstructorsWithInherited = PublicConstructors | 0x100000,

    /// <summary>
    /// Specifies all public nested types, including those inherited from base classes.
    /// </summary>
    PublicNestedTypesWithInherited = PublicNestedTypes | 0x200000,

    /// <summary>
    /// Specifies all constructors, including those inherited from base classes.
    /// </summary>
    AllConstructors = PublicConstructorsWithInherited | NonPublicConstructorsWithInherited,

    /// <summary>
    /// Specifies all methods, including those inherited from base classes.
    /// </summary>
    AllMethods = PublicMethods | NonPublicMethodsWithInherited,

    /// <summary>
    /// Specifies all fields, including those inherited from base classes.
    /// </summary>
    AllFields = PublicFields | NonPublicFieldsWithInherited,

    /// <summary>
    /// Specifies all nested types, including those inherited from base classes.
    /// </summary>
    AllNestedTypes = PublicNestedTypesWithInherited | NonPublicNestedTypesWithInherited,

    /// <summary>
    /// Specifies all properties, including those inherited from base classes.
    /// </summary>
    AllProperties = PublicProperties | NonPublicPropertiesWithInherited,

    /// <summary>
    /// Specifies all events, including those inherited from base classes.
    /// </summary>
    AllEvents = PublicEvents | NonPublicEventsWithInherited,

    /// <summary>
    /// Specifies all members.
    /// </summary>
    All = ~None
}
#endif

using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Text.Json;

namespace EverythingServer.Tools;

[McpServerToolType]
public class PrintEnvTool
{
    private static readonly JsonSerializerOptions options = new()
    {
        WriteIndented = true
    };

    [McpServerTool(Name = "printEnv"), Description("Prints all environment variables, helpful for debugging MCP server configuration")]
    public static string PrintEnv() =>
        JsonSerializer.Serialize(Environment.GetEnvironmentVariables(), options);
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.ResourcesRead"/> request from a client to get a resource provided by a server.
/// </summary>
/// <remarks>
/// The server will respond with a <see cref="ReadResourceResult"/> containing the resulting resource data.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class ReadResourceRequestParams : RequestParams
{
    /// <summary>
    /// The URI of the resource to read. The URI can use any protocol; it is up to the server how to interpret it.
    /// </summary>
    [JsonPropertyName("uri")]
    public required string Uri { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Specifies the context inclusion options for a request in the Model Context Protocol (MCP).
/// </summary>
/// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
[JsonConverter(typeof(CustomizableJsonStringEnumConverter<ContextInclusion>))]
public enum ContextInclusion
{
    /// <summary>
    /// Indicates that no context should be included.
    /// </summary>
    [JsonStringEnumMemberName("none")]
    None,

    /// <summary>
    /// Indicates that context from the server that sent the request should be included.
    /// </summary>
    [JsonStringEnumMemberName("thisServer")]
    ThisServer,

    /// <summary>
    /// Indicates that context from all servers that the client is connected to should be included.
    /// </summary>
    [JsonStringEnumMemberName("allServers")]
    AllServers
}

#if !NET
namespace System.IO;

internal static class TextWriterExtensions
{
    public static async Task FlushAsync(this TextWriter writer, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        await writer.FlushAsync();
    }
}
#endif

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Provides a base class for result payloads.
/// </summary>
public abstract class Result
{
    /// <summary>Prevent external derivations.</summary>
    private protected Result()
    {
    }

    /// <summary>
    /// Gets or sets metadata reserved by MCP for protocol-level metadata.
    /// </summary>
    /// <remarks>
    /// Implementations must not make assumptions about its contents.
    /// </remarks>
    [JsonPropertyName("_meta")]
    public JsonObject? Meta { get; init; }
}

#if !NET
using System.Runtime.CompilerServices;

namespace System.Threading;

/// <summary>
/// await default(ForceYielding) to provide the same behavior as
/// await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding).
/// </summary>
internal readonly struct ForceYielding : INotifyCompletion, ICriticalNotifyCompletion
{
    public ForceYielding GetAwaiter() => this;

    public bool IsCompleted => false;
    public void OnCompleted(Action continuation) => ThreadPool.QueueUserWorkItem(a => ((Action)a!)(), continuation);
    public void UnsafeOnCompleted(Action continuation) => ThreadPool.UnsafeQueueUserWorkItem(a => ((Action)a!)(), continuation);
    public void GetResult() { }
}
#endif

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Provides metadata related to the request that provides additional protocol-level information.
/// </summary>
/// <remarks>
/// This class contains properties that are used by the Model Context Protocol
/// for features like progress tracking and other protocol-specific capabilities.
/// </remarks>
public sealed class RequestParamsMetadata
{
    /// <summary>
    /// Gets or sets an opaque token that will be attached to any subsequent progress notifications.
    /// </summary>
    /// <remarks>
    /// The receiver is not obligated to provide these notifications.
    /// </remarks>
    [JsonPropertyName("progressToken")]
    public ProgressToken? ProgressToken { get; set; } = default!;
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Authentication;

/// <summary>
/// Represents a token response from the OAuth server.
/// </summary>
internal sealed class TokenContainer
{
    /// <summary>
    /// Gets or sets the access token.
    /// </summary>
    [JsonPropertyName("access_token")]
    public string AccessToken { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the refresh token.
    /// </summary>
    [JsonPropertyName("refresh_token")]
    public string? RefreshToken { get; set; }

    /// <summary>
    /// Gets or sets the number of seconds until the access token expires.
    /// </summary>
    [JsonPropertyName("expires_in")]
    public int ExpiresIn { get; set; }

    /// <summary>
    /// Gets or sets the extended expiration time in seconds.
    /// </summary>
    [JsonPropertyName("ext_expires_in")]
    public int ExtExpiresIn { get; set; }

    /// <summary>
    /// Gets or sets the token type (typically "Bearer").
    /// </summary>
    [JsonPropertyName("token_type")]
    public string TokenType { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the scope of the access token.
    /// </summary>
    [JsonPropertyName("scope")]
    public string Scope { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the timestamp when the token was obtained.
    /// </summary>
    [JsonIgnore]
    public DateTimeOffset ObtainedAt { get; set; }

    /// <summary>
    /// Gets the timestamp when the token expires, calculated from ObtainedAt and ExpiresIn.
    /// </summary>
    [JsonIgnore]
    public DateTimeOffset ExpiresAt => ObtainedAt.AddSeconds(ExpiresIn);
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET
namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
internal sealed class CallerArgumentExpressionAttribute : Attribute
{
    public CallerArgumentExpressionAttribute(string parameterName)
    {
        ParameterName = parameterName;
    }

    public string ParameterName { get; }
}
#endif

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET
using System.ComponentModel;

namespace System.Runtime.CompilerServices
{
    /// <summary>
    /// Reserved to be used by the compiler for tracking metadata.
    /// This class should not be used by developers in source code.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    internal static class IsExternalInit;
}
#else
// The compiler emits a reference to the internal copy of this type in the non-.NET builds,
// so we must include a forward to be compatible.
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))]
#endif

namespace ModelContextProtocol.Client;

/// <summary>
/// Specifies the transport mode for HTTP client connections.
/// </summary>
public enum HttpTransportMode
{
    /// <summary>
    /// Automatically detect the appropriate transport by trying Streamable HTTP first, then falling back to SSE if that fails.
    /// This is the recommended mode for maximum compatibility.
    /// </summary>
    AutoDetect,

    /// <summary>
    /// Use only the Streamable HTTP transport.
    /// </summary>
    StreamableHttp,

    /// <summary>
    /// Use only the HTTP with SSE transport.
    /// </summary>
    Sse
}

using ModelContextProtocol.Server;
using System.ComponentModel;

namespace TestServerWithHosting.Tools;

[McpServerToolType]
public sealed class EchoTool
{
    [McpServerTool, Description("Echoes the input back to the client.")]
    public static string Echo(string message)
    {
        return "hello " + message;
    }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents additional context information for completion requests.
/// </summary>
/// <remarks>
/// This context provides information that helps the server generate more relevant
/// completion suggestions, such as previously resolved variables in a template.
/// </remarks>
public sealed class CompleteContext
{
    /// <summary>
    /// Gets or sets previously-resolved variables in a URI template or prompt.
    /// </summary>
    [JsonPropertyName("arguments")]
    public IDictionary<string, string>? Arguments { get; init; }
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET
using System.ComponentModel;

namespace System.Runtime.CompilerServices
{
    /// <summary>Specifies that a type has required members or that a member is required.</summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    internal sealed class RequiredMemberAttribute : Attribute;
}
#endif

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.PromptsList"/> request from a client to request
/// a list of prompts available from the server.
/// </summary>
/// <remarks>
/// The server responds with a <see cref="ListPromptsResult"/> containing the available prompts.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class ListPromptsRequestParams : PaginatedRequestParams;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.ResourcesTemplatesList"/> request from a server to request
/// a list of roots available from the client.
/// </summary>
/// <remarks>
/// The client responds with a <see cref="ListRootsResult"/> containing the client's roots.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class ListRootsRequestParams : RequestParams;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="NotificationMethods.InitializedNotification"/>
/// sent from the client to the server after initialization has finished.
/// </summary>
/// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class InitializedNotificationParams : NotificationParams;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.ToolsList"/> request from a client to request
/// a list of tools available from the server.
/// </summary>
/// <remarks>
/// The server responds with a <see cref="ListToolsResult"/> containing the available tools.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class ListToolsRequestParams : PaginatedRequestParams;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.ResourcesList"/> request from a client to request
/// a list of resources available from the server.
/// </summary>
/// <remarks>
/// The server responds with a <see cref="ListResourcesResult"/> containing the available resources.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class ListResourcesRequestParams : PaginatedRequestParams;

using Microsoft.Extensions.AI;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace EverythingServer.Tools;

[McpServerToolType]
public class TinyImageTool
{
    [McpServerTool(Name = "getTinyImage"), Description("Get a tiny image from the server")]
    public static IEnumerable<AIContent> GetTinyImage() => [
            new TextContent("This is a tiny image:"),
            new DataContent(MCP_TINY_IMAGE),
            new TextContent("The image above is the MCP tiny image.")
        ];

    internal const string MCP_TINY_IMAGE =
      "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAKsGlDQ1BJQ0MgUHJvZmlsZQAASImVlwdUU+kSgOfe9JDQEiIgJfQmSCeAlBBaAAXpYCMkAUKJMRBU7MriClZURLCs6KqIgo0idizYFsWC3QVZBNR1sWDDlXeBQ9jdd9575805c+a7c+efmf+e/z9nLgCdKZDJMlF1gCxpjjwyyI8dn5DIJvUABRiY0kBdIMyWcSMiwgCTUft3+dgGyJC9YzuU69/f/1fREImzhQBIBMbJomxhFsbHMe0TyuQ5ALg9mN9kbo5siK9gzJRjDWL8ZIhTR7hviJOHGY8fjomO5GGsDUCmCQTyVACaKeZn5wpTsTw0f4ztpSKJFGPsGbyzsmaLMMbqgiUWI8N4KD8n+S95Uv+WM1mZUyBIVfLIXoaF7C/JlmUK5v+fn+N/S1amYrSGOaa0NHlwJGaxvpAHGbNDlSxNnhI+yhLRcPwwpymCY0ZZmM1LHGWRwD9UuTZzStgop0gC+co8OfzoURZnB0SNsnx2pLJWipzHHWWBfKyuIiNG6U8T85X589Ki40Y5VxI7ZZSzM6JCx2J4Sr9cEansXywN8hurG6jce1b2X/Yr4SvX5qRFByv3LhjrXyzljuXMjlf2JhL7B4zFxCjjZTl+ylqyzAhlvDgzSOnPzo1Srs3BDuTY2gjlN0wXhESMMoRBELAhBjIhB+QggECQgBTEOeJ5Q2cUeLNl8+WS1LQcNhe7ZWI2Xyq0m8B2tHd0Bhi6syNH4j1r+C4irGtjvhWVAF4nBgcHT475Qm4BHEkCoNaO+SxnAKh3A1w5JVTIc0d8Q9cJCEAFNWCCDhiACViCLTiCK3iCLwRACIRDNCTATBBCGmRhnc+FhbAMCqAI1sNmKIOdsBv2wyE4CvVwCs7DZbgOt+AePIZ26IJX0AcfYQBBEBJCRxiIDmKImCE2iCPCQbyRACQMiUQSkCQkFZEiCmQhsgIpQoqRMmQXUokcQU4g55GrSCvyEOlAepF3yFcUh9JQJqqPmqMTUQ7KRUPRaHQGmorOQfPQfHQtWopWoAfROvQ8eh29h7ajr9B+HOBUcCycEc4Wx8HxcOG4RFwKTo5bjCvEleAqcNW4Rlwz7g6uHfca9wVPxDPwbLwt3hMfjI/BC/Fz8Ivxq/Fl+P34OvxF/B18B74P/51AJ+gRbAgeBD4hnpBKmEsoIJQQ9hJqCZcI9whdhI9EIpFFtCC6EYOJCcR04gLiauJ2Yg3xHLGV2EnsJ5FIOiQbkhcpnCQg5ZAKSFtJB0lnSbdJXaTPZBWyIdmRHEhOJEvJy8kl5APkM+Tb5G7yAEWdYkbxoIRTRJT5lHWUPZRGyk1KF2WAqkG1oHpRo6np1GXUUmo19RL1CfW9ioqKsYq7ylQVicpSlVKVwypXVDpUvtA0adY0Hm06TUFbS9tHO0d7SHtPp9PN6b70RHoOfS29kn6B/oz+WZWhaqfKVxWpLlEtV61Tva36Ro2iZqbGVZuplqdWonZM7abaa3WKurk6T12gvli9XP2E+n31fg2GhoNGuEaWxmqNAxpXNXo0SZrmmgGaIs18zd2aFzQ7GTiGCYPHEDJWMPYwLjG6mESmBZPPTGcWMQ8xW5h9WppazlqxWvO0yrVOa7WzcCxzFp+VyVrHOspqY30dpz+OO048btW46nG3x33SHq/tqy3WLtSu0b6n/VWHrROgk6GzQade56kuXtdad6ruXN0dupd0X49njvccLxxfOP7o+Ed6qJ61XqTeAr3dejf0+vUN9IP0Zfpb9S/ovzZgGfgapBtsMjhj0GvIMPQ2lBhuMjxr+JKtxeayM9ml7IvsPiM9o2AjhdEuoxajAWML4xjj5cY1xk9NqCYckxSTTSZNJn2mhqaTTReaVpk+MqOYcczSzLaYNZt9MrcwjzNfaV5v3mOhbcG3yLOosnhiSbf0sZxjWWF514poxbHKsNpudcsatXaxTrMut75pg9q42khsttu0TiBMcJ8gnVAx4b4tzZZrm2tbZdthx7ILs1tuV2/3ZqLpxMSJGyY2T/xu72Kfab/H/rGDpkOIw3KHRod3jtaOQsdyx7tOdKdApyVODU5vnW2cxc47nB+4MFwmu6x0aXL509XNVe5a7drrZuqW5LbN7T6HyYngrOZccSe4+7kvcT/l/sXD1SPH46jHH562nhmeBzx7JllMEk/aM6nTy9hL4LXLq92b7Z3k/ZN3u4+Rj8Cnwue5r4mvyHevbzfXipvOPch942fvJ/er9fvE8+At4p3zx/kH+Rf6twRoBsQElAU8CzQOTA2sCuwLcglaEHQumBAcGrwh+D5fny/kV/L7QtxCFoVcDKWFRoWWhT4Psw6ThzVORieHTN44+ckUsynSKfXhEM4P3xj+NMIiYk7EyanEqRFTy6e+iHSIXBjZHMWImhV1IOpjtF/0uujHMZYxipimWLXY6bGVsZ/i/OOK49rjJ8Yvir+eoJsgSWhIJCXGJu5N7J8WMG3ztK7pLtMLprfNsJgxb8bVmbozM2eenqU2SzDrWBIhKS7pQNI3QbigQtCfzE/eltwn5Am3CF+JfEWbRL1iL3GxuDvFK6U4pSfVK3Vjam+aT1pJ2msJT1ImeZsenL4z/VNGeMa+jMHMuMyaLHJWUtYJqaY0Q3pxtsHsebNbZTayAln7HI85m+f0yUPle7OR7BnZDTlMbDi6obBU/KDoyPXOLc/9PDd27rF5GvOk827Mt56/an53XmDezwvwC4QLmhYaLVy2sGMRd9Guxcji5MVNS0yW5C/pWhq0dP8y6rKMZb8st19evPzDirgVjfn6+UvzO38I+qGqQLVAXnB/pefKnT/if5T82LLKadXWVd8LRYXXiuyLSoq+rRauvrbGYU3pmsG1KWtb1rmu27GeuF66vm2Dz4b9xRrFecWdGydvrNvE3lS46cPmWZuvljiX7NxC3aLY0l4aVtqw1XTr+q3fytLK7pX7ldds09u2atun7aLtt3f47qjeqb+zaOfXnyQ/PdgVtKuuwryiZDdxd+7uF3ti9zT/zPm5cq/u3qK9f+6T7mvfH7n/YqVbZeUBvQPrqtAqRVXvwekHbx3yP9RQbVu9q4ZVU3QYDisOvzySdKTtaOjRpmOcY9XHzY5vq2XUFtYhdfPr+urT6tsbEhpaT4ScaGr0bKw9aXdy3ymjU+WntU6vO0M9k39m8Gze2f5zsnOvz6ee72ya1fT4QvyFuxenXmy5FHrpyuXAyxeauc1nr3hdOXXV4+qJa5xr9dddr9fdcLlR+4vLL7Utri11N91uNtzyv9XYOqn1zG2f2+fv+N+5fJd/9/q9Kfda22LaHtyffr/9gehBz8PMh28f5T4aeLz0CeFJ4VP1pyXP9J5V/Gr1a027a/vpDv+OG8+jnj/uFHa++i37t29d+S/oL0q6Dbsrexx7TvUG9t56Oe1l1yvZq4HXBb9r/L7tjeWb43/4/nGjL76v66387eC71e913u/74PyhqT+i/9nHrI8Dnwo/63ze/4Xzpflr3NfugbnfSN9K/7T6s/F76Pcng1mDgzKBXDA8CuAwRVNSAN7tA6AnADCwGYI6bWSmHhZk5D9gmOA/8cjcPSyuANWYGRqNeOcADmNqvhRAzRdgaCyK9gXUyUmpo/Pv8Kw+JAbYv8K0HECi2x6tebQU/iEjc/xf+v6nBWXWv9l/AV0EC6JTIblRAAAAeGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAJAAAAABAAAAkAAAAAEAAqACAAQAAAABAAAAFKADAAQAAAABAAAAFAAAAAAXNii1AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB82lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOllSZXNvbHV0aW9uPjE0NDwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+MTQ0PC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KReh49gAAAjRJREFUOBGFlD2vMUEUx2clvoNCcW8hCqFAo1dKhEQpvsF9KrWEBh/ALbQ0KkInBI3SWyGPCCJEQliXgsTLefaca/bBWjvJzs6cOf/fnDkzOQJIjWm06/XKBEGgD8c6nU5VIWgBtQDPZPWtJE8O63a7LBgMMo/Hw0ql0jPjcY4RvmqXy4XMjUYDUwLtdhtmsxnYbDbI5/O0djqdFFKmsEiGZ9jP9gem0yn0ej2Yz+fg9XpfycimAD7DttstQTDKfr8Po9GIIg6Hw1Cr1RTgB+A72GAwgMPhQLBMJgNSXsFqtUI2myUo18pA6QJogefsPrLBX4QdCVatViklw+EQRFGEj88P2O12pEUGATmsXq+TaLPZ0AXgMRF2vMEqlQoJTSYTpNNpApvNZliv1/+BHDaZTAi2Wq1A3Ig0xmMej7+RcZjdbodUKkWAaDQK+GHjHPnImB88JrZIJAKFQgH2+z2BOczhcMiwRCIBgUAA+NN5BP6mj2DYff35gk6nA61WCzBn2JxO5wPM7/fLz4vD0E+OECfn8xl/0Gw2KbLxeAyLxQIsFgt8p75pDSO7h/HbpUWpewCike9WLpfB7XaDy+WCYrFI/slk8i0MnRRAUt46hPMI4vE4+Hw+ec7t9/44VgWigEeby+UgFArJWjUYOqhWG6x50rpcSfR6PVUfNOgEVRlTX0HhrZBKz4MZjUYWi8VoA+lc9H/VaRZYjBKrtXR8tlwumcFgeMWRbZpA9ORQWfVm8A/FsrLaxebd5wAAAABJRU5ErkJggg==";
}

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the parameters used with a <see cref="RequestMethods.ResourcesTemplatesList"/> request from a client to request
/// a list of resource templates available from the server.
/// </summary>
/// <remarks>
/// The server responds with a <see cref="ListResourceTemplatesResult"/> containing the available resource templates.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public sealed class ListResourceTemplatesRequestParams : PaginatedRequestParams;

using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using TestServerWithHosting.Tools;
using TestServerWithHosting.Resources;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
    .WithHttpTransport()
    .WithTools<EchoTool>()
    .WithTools<SampleLlmTool>()
    .WithResources<SimpleResourceType>();

builder.Services.AddOpenTelemetry()
    .WithTracing(b => b.AddSource("*")
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation())
    .WithMetrics(b => b.AddMeter("*")
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation())
    .WithLogging()
    .UseOtlpExporter();

var app = builder.Build();

app.MapMcp();

app.Run();

namespace ModelContextProtocol.AspNetCore.Authentication;

/// <summary>
/// Default values used by MCP authentication.
/// </summary>
public static class McpAuthenticationDefaults
{
    /// <summary>
    /// The default value used for authentication scheme name.
    /// </summary>
    public const string AuthenticationScheme = "McpAuth";

    /// <summary>
    /// The default value used for authentication scheme display name.
    /// </summary>
    public const string DisplayName = "MCP Authentication";
}

namespace ModelContextProtocol.Server;

/// <summary>
/// Represents an MCP server primitive, like a tool or a prompt.
/// </summary>
public interface IMcpServerPrimitive
{
    /// <summary>Gets the unique identifier of the primitive.</summary>
    string Id { get; }
}

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using QuickstartWeatherServer.Tools;
using System.Net.Http.Headers;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<WeatherTools>();

builder.Logging.AddConsole(options =>
{
    options.LogToStandardErrorThreshold = LogLevel.Trace;
});

builder.Services.AddSingleton(_ =>
{
    var client = new HttpClient { BaseAddress = new Uri("https://api.weather.gov") };
    client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
    return client;
});

await builder.Build().RunAsync();

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents the type of role in the Model Context Protocol conversation.
/// </summary>
[JsonConverter(typeof(CustomizableJsonStringEnumConverter<Role>))]
public enum Role
{
    /// <summary>
    /// Corresponds to a human user in the conversation.
    /// </summary>
    [JsonStringEnumMemberName("user")]
    User,

    /// <summary>
    /// Corresponds to the AI assistant in the conversation.
    /// </summary>
    [JsonStringEnumMemberName("assistant")]
    Assistant
}

namespace ModelContextProtocol.Server;

/// <summary>Provides a thread-safe collection of <see cref="McpServerResource"/> instances, indexed by their URI templates.</summary>
public sealed class McpServerResourceCollection()
    : McpServerPrimitiveCollection<McpServerResource>(UriTemplate.UriTemplateComparer.Instance);

using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol;

/// <summary>
/// Represents an empty result object for operations that need to indicate successful completion
/// but don't need to return any specific data.
/// </summary>
public sealed class EmptyResult : Result
{
    [JsonIgnore]
    internal static EmptyResult Instance { get; } = new();
}

using ModelContextProtocol.Server;
using System.ComponentModel;

namespace EverythingServer.Tools;

[McpServerToolType]
public class AddTool
{
    [McpServerTool(Name = "add"), Description("Adds two numbers.")]
    public static string Add(int a, int b) => $"The sum of {a} and {b} is {a + b}";
}

using ModelContextProtocol.Server;
using System.ComponentModel;

namespace EverythingServer.Tools;

[McpServerToolType]
public class EchoTool
{
    [McpServerTool(Name = "echo"), Description("Echoes the message back to the client.")]
    public static string Echo(string message) => $"Echo: {message}";
}

using ModelContextProtocol.Server;
using System.ComponentModel;

namespace EverythingServer.Prompts;

[McpServerPromptType]
public class SimplePromptType
{
    [McpServerPrompt(Name = "simple_prompt"), Description("A prompt without arguments")]
    public static string SimplePrompt() => "This is a simple prompt without arguments";
}

#if !NET
namespace System.Diagnostics.CodeAnalysis
{
    [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
    internal sealed class SetsRequiredMembersAttribute : Attribute;
}
#endif

using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace TestServerWithHosting.Resources;

[McpServerResourceType]
public class SimpleResourceType
{
    [McpServerResource, Description("A direct text resource")]
    public static string DirectTextResource() => "This is a direct resource";
}

namespace ModelContextProtocol.AspNetCore.Stateless;

internal sealed record UserIdClaim(string Type, string Value, string Issuer);

using ModelContextProtocol.Protocol;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.AspNetCore.Stateless;

internal sealed class StatelessSessionId
{
    [JsonPropertyName("clientInfo")]
    public Implementation? ClientInfo { get; init; }

    [JsonPropertyName("userIdClaim")]
    public UserIdClaim? UserIdClaim { get; init; }
}

using System.Text.Json.Serialization;

namespace ModelContextProtocol.AspNetCore.Stateless;

[JsonSerializable(typeof(StatelessSessionId))]
internal sealed partial class StatelessSessionIdJsonContext : JsonSerializerContext;

private
sealed
class
Registration
(
        NotificationHandlers handlers, string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler, bool unregisterable)
: IAsyncDisposable
{
private static readonly AsyncLocal<int> s_invokingAncestor = new();
private readonly NotificationHandlers _handlers = handlers;
private readonly string _method = method;
private readonly Func<JsonRpcNotification, CancellationToken, ValueTask> _handler = handler;
private readonly bool _temporary = unregisterable;
private TaskCompletionSource<bool>? _disposeTcs;
private int _refCount = 1;
private bool _disposedCalled = false;
public Registration? Next;
public Registration? Prev;
public async ValueTask DisposeAsync () {}
public ValueTask InvokeAsync (JsonRpcNotification notification, CancellationToken cancellationToken) {}
private async ValueTask InvokeTemporaryAsync (JsonRpcNotification notification, CancellationToken cancellationToken) {}
}

This code segment functions to: Manage dynamic registration lifecycle for temporary event handlers by tracking active invocations and coordinating safe removal through reference counting and synchronization mechanisms to prevent premature disposal during concurrent operations.

```c_sharp
[TASK_BEGIN]
private sealed class Registration(
        NotificationHandlers handlers, string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler, bool unregisterable)
    : IAsyncDisposable
{
    private static readonly AsyncLocal<int> s_invokingAncestor = new();
    private readonly NotificationHandlers _handlers = handlers;
    private readonly string _method = method;
    private readonly Func<JsonRpcNotification, CancellationToken, ValueTask> _handler = handler;
    private readonly bool _temporary = unregisterable;
    private TaskCompletionSource<bool>? _disposeTcs;
    private int _refCount = 1;
    private bool _disposedCalled = false;
    public Registration? Next;
    public Registration? Prev;

    public async ValueTask DisposeAsync()
    {
        TaskCompletionSource<bool>? tcs = null;
        lock (_handlers.SyncObj)
        {
            if (_disposedCalled) return;
            _disposedCalled = true;

            if (!_temporary) return;

            // Remove from linked list if we're the current head
            if (_handlers._handlers.TryGetValue(_method, out var currentHead) && currentHead == this)
            {
                if (Next != null)
                {
                    _handlers._handlers[_method] = Next;
                    Next.Prev = null;
                }
                else
                {
                    _handlers._handlers.Remove(_method);
                }
            }

            if (--_refCount == 0)
            {
                tcs = _disposeTcs;
                _disposeTcs = null;
            }
            else
            {
                _disposeTcs = new TaskCompletionSource<bool>();
                tcs = _disposeTcs;
            }
        }

        if (tcs != null)
        {
            await tcs.Task.ConfigureAwait(false);
        }
    }

    public ValueTask InvokeAsync(JsonRpcNotification notification, CancellationToken cancellationToken)
    {
        if (s_invokingAncestor.Value != 0)
        {
            return _handler(notification, cancellationToken);
        }

        lock (_handlers.SyncObj)
        {
            if (_disposedCalled) return ValueTask.CompletedTask;
            _refCount++;
        }

        s_invokingAncestor.Value++;
        try
        {
            if (_temporary)
            {
                return InvokeTemporaryAsync(notification, cancellationToken);
            }
            return _handler(notification, cancellationToken);
        }
        finally
        {
            s_invokingAncestor.Value--;
            lock (_handlers.SyncObj)
            {
                if (--_refCount == 0 && _disposeTcs != null)
                {
                    _disposeTcs.TrySetResult(true);
                }
            }
        }
    }

    private async ValueTask InvokeTemporaryAsync(JsonRpcNotification notification, CancellationToken cancellationToken)
    {
        try
        {
            await _handler(notification, cancellationToken).ConfigureAwait(false);
        }
        finally
        {
            await DisposeAsync().ConfigureAwait(false);
        }
    }
}
[TASK_END]
```

private sealed class Registration(
        NotificationHandlers handlers, string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler, bool unregisterable) : IAsyncDisposable
    {
        private static readonly AsyncLocal<int> s_invokingAncestor = new();
        private readonly NotificationHandlers _handlers = handlers;
        private readonly string _method = method;
        private readonly Func<JsonRpcNotification, CancellationToken, ValueTask> _handler = handler;
        private readonly bool _temporary = unregisterable;
        private TaskCompletionSource<bool>? _disposeTcs;
        private int _refCount = 1;
        private bool _disposedCalled = false;
        public Registration? Next;
        public Registration? Prev;
        public async ValueTask DisposeAsync()
        {
            if (!_temporary)
            {
                return;
            }
            lock (_handlers.SyncObj)
            {
                if (!_disposedCalled)
                {
                    _disposedCalled = true;
                    if (_handlers._handlers.TryGetValue(_method, out var handlers) && handlers == this)
                    {
                        if (Next is not null)
                        {
                            _handlers._handlers[_method] = Next;
                        }
                        else
                        {
                            _handlers._handlers.Remove(_method);
                        }
                    }
                    if (Prev is not null)
                    {
                        Prev.Next = Next;
                    }
                    if (Next is not null)
                    {
                        Next.Prev = Prev;
                    }
                    if (--_refCount != 0)
                    {
                        Debug.Assert(_disposeTcs is null);
                        _disposeTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
                    }
                }
            }
            if (_disposeTcs is not null && s_invokingAncestor.Value == 0)
            {
                await _disposeTcs.Task.ConfigureAwait(false);
            }
        }
        public ValueTask InvokeAsync(JsonRpcNotification notification, CancellationToken cancellationToken)
        {
            if (!_temporary)
            {
                return _handler(notification, cancellationToken);
            }
            return InvokeTemporaryAsync(notification, cancellationToken);
        }
        private async ValueTask InvokeTemporaryAsync(JsonRpcNotification notification, CancellationToken cancellationToken)
        {
            lock (_handlers.SyncObj)
            {
                Debug.Assert(_refCount != 0 || _disposedCalled, $"Expected {nameof(_disposedCalled)} == true when {nameof(_refCount)} == 0");
                if (_disposedCalled)
                {
                    return;
                }
                Debug.Assert(_refCount > 0);
                _refCount++;
            }
            s_invokingAncestor.Value++;
            try
            {
                await _handler(notification, cancellationToken).ConfigureAwait(false);
            }
            finally
            {
                s_invokingAncestor.Value--;
                lock (_handlers.SyncObj)
                {
                    _refCount--;
                    if (_refCount == 0)
                    {
                        Debug.Assert(_disposedCalled);
                        Debug.Assert(_disposeTcs is not null);
                        bool completed = _disposeTcs!.TrySetResult(true);
                        Debug.Assert(completed);
                    }
                }
            }
        }
    }

private sealed class Registration(
        NotificationHandlers handlers, string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler, bool unregisterable)
    : IAsyncDisposable
{
    private static readonly AsyncLocal<int> s_invokingAncestor = new();
    private readonly NotificationHandlers _handlers = handlers;
    private readonly string _method = method;
    private readonly Func<JsonRpcNotification, CancellationToken, ValueTask> _handler = handler;
    private readonly bool _temporary = unregisterable;
    private TaskCompletionSource<bool>? _disposeTcs;
    private int _refCount = 1;
    private bool _disposedCalled = false;
    public Registration? Next;
    public Registration? Prev;
    public async ValueTask DisposeAsync()
    {
        TaskCompletionSource<bool>? tcs = null;
        lock (_handlers.SyncObj)
        {
            if (_disposedCalled) return;
            _disposedCalled = true;
            if (!_temporary) return;
            if (_handlers._handlers.TryGetValue(_method, out var currentHead) && currentHead == this)
            {
                if (Next != null)
                {
                    _handlers._handlers[_method] = Next;
                    Next.Prev = null;
                }
                else
                {
                    _handlers._handlers.Remove(_method);
                }
            }
            if (--_refCount == 0)
            {
                tcs = _disposeTcs;
                _disposeTcs = null;
            }
            else
            {
                _disposeTcs = new TaskCompletionSource<bool>();
                tcs = _disposeTcs;
            }
        }
        if (tcs != null)
        {
            await tcs.Task.ConfigureAwait(false);
        }
    }
    public ValueTask InvokeAsync(JsonRpcNotification notification, CancellationToken cancellationToken)
    {
        if (s_invokingAncestor.Value != 0)
        {
            return _handler(notification, cancellationToken);
        }
        lock (_handlers.SyncObj)
        {
            if (_disposedCalled) return ValueTask.CompletedTask;
            _refCount++;
        }
        s_invokingAncestor.Value++;
        try
        {
            if (_temporary)
            {
                return InvokeTemporaryAsync(notification, cancellationToken);
            }
            return _handler(notification, cancellationToken);
        }
        finally
        {
            s_invokingAncestor.Value--;
            lock (_handlers.SyncObj)
            {
                if (--_refCount == 0 && _disposeTcs != null)
                {
                    _disposeTcs.TrySetResult(true);
                }
            }
        }
    }
    private async ValueTask InvokeTemporaryAsync(JsonRpcNotification notification, CancellationToken cancellationToken)
    {
        try
        {
            await _handler(notification, cancellationToken).ConfigureAwait(false);
        }
        finally
        {
            await DisposeAsync().ConfigureAwait(false);
        }
    }
}