using Microsoft.Extensions.AI; using ModelContextProtocol.Protocol.Messages; using ModelContextProtocol.Protocol.Types; using ModelContextProtocol.Utils; using ModelContextProtocol.Utils.Json; using System.Runtime.CompilerServices; using System.Text; namespace ModelContextProtocol.Server; /// Provides extension methods for interacting with an . public static class McpServerExtensions { /// /// Requests to sample an LLM via the client. /// /// is . /// The client does not support sampling. public static Task RequestSamplingAsync( this IMcpServer server, CreateMessageRequestParams request, CancellationToken cancellationToken) { Throw.IfNull(server); if (server.ClientCapabilities?.Sampling is null) { throw new ArgumentException("Client connected to the server does not support sampling.", nameof(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. /// /// The server issueing the request. /// The messages to send as part of the request. /// The options to use for the request. /// The to monitor for cancellation requests. The default is . /// A task containing the response from the client. /// is . /// is . /// The client does not support sampling. public static async Task RequestSamplingAsync( this IMcpServer server, IEnumerable messages, ChatOptions? options = default, CancellationToken cancellationToken = default) { Throw.IfNull(server); Throw.IfNull(messages); StringBuilder? systemPrompt = null; List 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() { Type = "text", Text = textContent.Text, }, }); break; case DataContent dataContent when dataContent.HasTopLevelMediaType("image") || dataContent.HasTopLevelMediaType("audio"): samplingMessages.Add(new() { Role = role, Content = new() { Type = dataContent.HasTopLevelMediaType("image") ? "image" : "audio", MimeType = dataContent.MediaType, Data = dataContent.GetBase64Data(), }, }); break; } } } } ModelPreferences? modelPreferences = null; if (options?.ModelId is { } modelId) { modelPreferences = new() { Hints = [new() { Name = modelId }] }; } var result = await server.RequestSamplingAsync(new() { Messages = samplingMessages, MaxTokens = options?.MaxOutputTokens, StopSequences = options?.StopSequences?.ToArray(), SystemPrompt = systemPrompt?.ToString(), Temperature = options?.Temperature, ModelPreferences = modelPreferences, }, cancellationToken).ConfigureAwait(false); return new(new ChatMessage(new(result.Role), [result.Content.ToAIContent()])) { ModelId = result.Model, FinishReason = result.StopReason switch { "maxTokens" => ChatFinishReason.Length, "endTurn" or "stopSequence" or _ => ChatFinishReason.Stop, } }; } /// Creates an that can be used to send sampling requests to the client. /// The server to be wrapped as an . /// The that can be used to issue sampling requests to the client. /// is . /// The client does not support sampling. public static IChatClient AsSamplingChatClient(this IMcpServer server) { Throw.IfNull(server); if (server.ClientCapabilities?.Sampling is null) { throw new ArgumentException("Client connected to the server does not support sampling.", nameof(server)); } return new SamplingChatClient(server); } /// /// Requests the client to list the roots it exposes. /// /// is . /// The client does not support roots. public static Task RequestRootsAsync( this IMcpServer server, ListRootsRequestParams request, CancellationToken cancellationToken) { Throw.IfNull(server); if (server.ClientCapabilities?.Roots is null) { throw new ArgumentException("Client connected to the server does not support roots.", nameof(server)); } return server.SendRequestAsync( RequestMethods.RootsList, request, McpJsonUtilities.JsonContext.Default.ListRootsRequestParams, McpJsonUtilities.JsonContext.Default.ListRootsResult, cancellationToken: cancellationToken); } /// Provides an implementation that's implemented via client sampling. /// private sealed class SamplingChatClient(IMcpServer server) : IChatClient { /// public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) => server.RequestSamplingAsync(messages, options, cancellationToken); /// async IAsyncEnumerable IChatClient.GetStreamingResponseAsync( IEnumerable messages, ChatOptions? options, [EnumeratorCancellation] CancellationToken cancellationToken) { var response = await GetResponseAsync(messages, options, cancellationToken).ConfigureAwait(false); foreach (var update in response.ToChatResponseUpdates()) { yield return update; } } /// 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; } /// void IDisposable.Dispose() { } // nop } }