Add net10.0 assets and run some fixers (#875)

This commit is contained in:
Stephen Toub 2025-10-15 15:27:43 -04:00 • committed by GitHub
commit bfc0980ef5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 168 additions and 194 deletions

View file

@ -128,10 +128,7 @@ public static class AIContentExtensions
_ => null,
};
if (ac is not null)
{
ac.RawRepresentation = content;
}
ac?.RawRepresentation = content;
return ac;
}

View file

@ -1,5 +1,8 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
#if NET9_0_OR_GREATER
using System.Buffers.Text;
#endif
using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Headers;
using System.Security.Cryptography;
@ -238,14 +241,17 @@ internal sealed partial class ClientOAuthProvider
LogOAuthAuthorizationCompleted();
}
private static readonly string[] s_wellKnownPaths = [".well-known/openid-configuration", ".well-known/oauth-authorization-server"];
private async Task<AuthorizationServerMetadata> GetAuthServerMetadataAsync(Uri authServerUri, CancellationToken cancellationToken)
{
if (!authServerUri.OriginalString.EndsWith("/"))
if (authServerUri.OriginalString.Length == 0 ||
authServerUri.OriginalString[authServerUri.OriginalString.Length - 1] != '/')
{
authServerUri = new Uri(authServerUri.OriginalString + "/");
authServerUri = new Uri($"{authServerUri.OriginalString}/");
}
foreach (var path in new[] { ".well-known/openid-configuration", ".well-known/oauth-authorization-server" })
foreach (var path in s_wellKnownPaths)
{
try
{
@ -540,11 +546,7 @@ internal sealed partial class ClientOAuthProvider
Port = -1 // Always remove port
};
if (builder.Path == "/")
{
builder.Path = string.Empty;
}
else if (builder.Path.Length > 1 && builder.Path.EndsWith("/"))
if (builder.Path.Length > 0 && builder.Path[builder.Path.Length - 1] == '/')
{
builder.Path = builder.Path.TrimEnd('/');
}
@ -633,18 +635,18 @@ internal sealed partial class ClientOAuthProvider
continue;
}
string key = trimmedPart.Substring(0, equalsIndex).Trim();
ReadOnlySpan<char> key = trimmedPart.AsSpan().Slice(0, equalsIndex).Trim();
if (string.Equals(key, parameterName, StringComparison.OrdinalIgnoreCase))
if (key.Equals(parameterName, StringComparison.OrdinalIgnoreCase))
{
string value = trimmedPart.Substring(equalsIndex + 1).Trim();
ReadOnlySpan<char> value = trimmedPart.AsSpan(equalsIndex + 1).Trim();
if (value.StartsWith("\"") && value.EndsWith("\""))
if (value.Length > 0 && value[0] == '"' && value[value.Length - 1] == '"')
{
value = value.Substring(1, value.Length - 2);
value = value.Slice(1, value.Length - 2);
}
return value;
return value.ToString();
}
}
@ -664,12 +666,18 @@ internal sealed partial class ClientOAuthProvider
private static string GenerateCodeChallenge(string codeVerifier)
{
#if NET9_0_OR_GREATER
Span<byte> hash = stackalloc byte[SHA256.HashSizeInBytes];
SHA256.HashData(Encoding.UTF8.GetBytes(codeVerifier), hash);
return Base64Url.EncodeToString(hash);
#else
using var sha256 = SHA256.Create();
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
return Convert.ToBase64String(challengeBytes)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
#endif
}
private string GetClientIdOrThrow() => _clientId ?? throw new InvalidOperationException("Client ID is not available. This may indicate an issue with dynamic client registration.");

View file

@ -11,8 +11,6 @@ namespace ModelContextProtocol.Client;
/// </remarks>
public sealed class McpClientOptions
{
private McpClientHandlers? _handlers;
/// <summary>
/// Gets or sets information about this client implementation, including its name and version.
/// </summary>
@ -71,11 +69,11 @@ public sealed class McpClientOptions
/// </summary>
public McpClientHandlers Handlers
{
get => _handlers ??= new();
get => field ??= new();
set
{
Throw.IfNull(value);
_handlers = value;
field = value;
}
}
}

View file

@ -5,21 +5,15 @@ using System.Diagnostics;
namespace ModelContextProtocol.Client;
/// <summary>Provides the client side of a stdio-based session transport.</summary>
internal sealed class StdioClientSessionTransport : StreamClientSessionTransport
internal sealed class StdioClientSessionTransport(
StdioClientTransportOptions options, Process process, string endpointName, Queue<string> stderrRollingLog, ILoggerFactory? loggerFactory) :
StreamClientSessionTransport(process.StandardInput.BaseStream, process.StandardOutput.BaseStream, encoding: null, endpointName, loggerFactory)
{
private readonly StdioClientTransportOptions _options;
private readonly Process _process;
private readonly Queue<string> _stderrRollingLog;
private readonly StdioClientTransportOptions _options = options;
private readonly Process _process = process;
private readonly Queue<string> _stderrRollingLog = 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)
{
@ -56,7 +50,7 @@ internal sealed class StdioClientSessionTransport : StreamClientSessionTransport
// Now terminate the server process.
try
{
StdioClientTransport.DisposeProcess(_process, processRunning: true, _options.ShutdownTimeout, Name);
StdioClientTransport.DisposeProcess(_process, processRunning: true, shutdownTimeout: _options.ShutdownTimeout);
}
catch (Exception ex)
{

View file

@ -39,7 +39,7 @@ public sealed partial class StdioClientTransport : IClientTransport
_options = options;
_loggerFactory = loggerFactory;
Name = options.Name ?? $"stdio-{Regex.Replace(Path.GetFileName(options.Command), @"[\s\.]+", "-")}";
Name = options.Name ?? $"stdio-{WhitespaceAndPeriods().Replace(Path.GetFileName(options.Command), "-")}";
}
/// <inheritdoc />
@ -189,7 +189,7 @@ public sealed partial class StdioClientTransport : IClientTransport
try
{
DisposeProcess(process, processStarted, _options.ShutdownTimeout, endpointName);
DisposeProcess(process, processStarted, _options.ShutdownTimeout);
}
catch (Exception ex2)
{
@ -201,7 +201,7 @@ public sealed partial class StdioClientTransport : IClientTransport
}
internal static void DisposeProcess(
Process? process, bool processRunning, TimeSpan shutdownTimeout, string endpointName)
Process? process, bool processRunning, TimeSpan shutdownTimeout)
{
if (process is not null)
{
@ -279,4 +279,12 @@ public sealed partial class StdioClientTransport : IClientTransport
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} shutdown failed.")]
private static partial void LogTransportShutdownFailed(ILogger logger, string endpointName, Exception exception);
#if NET
[GeneratedRegex(@"[\s\.]+")]
private static partial Regex WhitespaceAndPeriods();
#else
private static Regex WhitespaceAndPeriods() => s_whitespaceAndPeriods;
private static readonly Regex s_whitespaceAndPeriods = new(@"[\s\.]+", RegexOptions.Compiled);
#endif
}

View file

@ -93,9 +93,9 @@ internal static class Diagnostics
{
if (jsonObject["_meta"] is not JsonObject meta)
{
meta = new JsonObject();
jsonObject["_meta"] = meta;
jsonObject["_meta"] = meta = [];
}
meta[key] = value;
}
}

View file

@ -405,7 +405,7 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
long? startingTimestamp = durationMetric.Enabled ? Stopwatch.GetTimestamp() : null;
using Activity? activity = Diagnostics.ShouldInstrumentMessage(request) ?
Diagnostics.ActivitySource.StartActivity(CreateActivityName(method), ActivityKind.Client) :
Diagnostics.ActivitySource.StartActivity(McpSessionHandler.CreateActivityName(method), ActivityKind.Client) :
null;
// Set request ID
@ -501,7 +501,7 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
long? startingTimestamp = durationMetric.Enabled ? Stopwatch.GetTimestamp() : null;
using Activity? activity = Diagnostics.ShouldInstrumentMessage(message) ?
Diagnostics.ActivitySource.StartActivity(CreateActivityName(method), ActivityKind.Client) :
Diagnostics.ActivitySource.StartActivity(McpSessionHandler.CreateActivityName(method), ActivityKind.Client) :
null;
TagList tags = default;
@ -567,7 +567,7 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
}
}
private string CreateActivityName(string method) => method;
private static string CreateActivityName(string method) => method;
private static string GetMethodName(JsonRpcMessage message) =>
message switch

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net10.0;net9.0;net8.0;netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsPackable>true</IsPackable>
<PackageId>ModelContextProtocol.Core</PackageId>
@ -27,16 +27,20 @@
<PackageReference Include="System.Threading.Channels" />
</ItemGroup>
<!-- Dependencies only needed by netstandard2.0 or net8.0 -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net8.0'">
<!-- Dependencies only needed by pre-net9.0 -->
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net9.0'))">
<PackageReference Include="System.IO.Pipelines" />
</ItemGroup>
<!-- Dependencies only needed pre-net10.0 -->
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net10.0'))">
<PackageReference Include="System.Net.ServerSentEvents" />
</ItemGroup>
<!-- Dependencies needed by all -->
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="System.Net.ServerSentEvents" />
</ItemGroup>
<ItemGroup>

View file

@ -240,14 +240,8 @@ internal sealed class NotificationHandlers
// to point past this one. Importantly, we do not modify this node's Next or Prev.
// We want to ensure that an enumeration through all of the registrations can still
// progress through this one.
if (Prev is not null)
{
Prev.Next = Next;
}
if (Next is not null)
{
Next.Prev = Prev;
}
Prev?.Next = Next;
Next?.Prev = Prev;
// Decrement the ref count. In the common case, there's no in-flight invocation for
// this handler. However, in the uncommon case that there is, we need to wait for

View file

@ -11,15 +11,12 @@ namespace ModelContextProtocol.Protocol;
[JsonConverter(typeof(Converter))]
public readonly struct ProgressToken : IEquatable<ProgressToken>
{
/// <summary>The token, either a string or a boxed long or null.</summary>
private readonly object? _token;
/// <summary>Initializes a new instance of the <see cref="ProgressToken"/> with a specified value.</summary>
/// <param name="value">The required ID value.</param>
public ProgressToken(string value)
{
Throw.IfNull(value);
_token = value;
Token = value;
}
/// <summary>Initializes a new instance of the <see cref="ProgressToken"/> with a specified value.</summary>
@ -27,27 +24,27 @@ public readonly struct ProgressToken : IEquatable<ProgressToken>
public ProgressToken(long value)
{
// Box the long. Progress tokens are almost always strings in practice, so this should be rare.
_token = value;
Token = value;
}
/// <summary>Gets the underlying object for this token.</summary>
/// <remarks>This will either be a <see cref="string"/>, a boxed <see cref="long"/>, or <see langword="null"/>.</remarks>
public object? Token => _token;
public object? Token { get; }
/// <inheritdoc />
public override string? ToString() =>
_token is string stringValue ? stringValue :
_token is long longValue ? longValue.ToString(CultureInfo.InvariantCulture) :
Token is string stringValue ? stringValue :
Token is long longValue ? longValue.ToString(CultureInfo.InvariantCulture) :
null;
/// <inheritdoc />
public bool Equals(ProgressToken other) => Equals(_token, other._token);
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;
public override int GetHashCode() => Token?.GetHashCode() ?? 0;
/// <inheritdoc />
public static bool operator ==(ProgressToken left, ProgressToken right) => left.Equals(right);
@ -77,7 +74,7 @@ public readonly struct ProgressToken : IEquatable<ProgressToken>
{
Throw.IfNull(writer);
switch (value._token)
switch (value.Token)
{
case string str:
writer.WriteStringValue(str);

View file

@ -11,15 +11,12 @@ namespace ModelContextProtocol.Protocol;
[JsonConverter(typeof(Converter))]
public readonly struct RequestId : IEquatable<RequestId>
{
/// <summary>The id, either a string or a boxed long or null.</summary>
private readonly object? _id;
/// <summary>Initializes a new instance of the <see cref="RequestId"/> with a specified value.</summary>
/// <param name="value">The required ID value.</param>
public RequestId(string value)
{
Throw.IfNull(value);
_id = value;
Id = value;
}
/// <summary>Initializes a new instance of the <see cref="RequestId"/> with a specified value.</summary>
@ -27,27 +24,27 @@ public readonly struct RequestId : IEquatable<RequestId>
public RequestId(long value)
{
// Box the long. Request IDs are almost always strings in practice, so this should be rare.
_id = value;
Id = value;
}
/// <summary>Gets the underlying object for this id.</summary>
/// <remarks>This will either be a <see cref="string"/>, a boxed <see cref="long"/>, or <see langword="null"/>.</remarks>
public object? Id => _id;
public object? Id { get; }
/// <inheritdoc />
public override string ToString() =>
_id is string stringValue ? stringValue :
_id is long longValue ? longValue.ToString(CultureInfo.InvariantCulture) :
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);
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;
public override int GetHashCode() => Id?.GetHashCode() ?? 0;
/// <inheritdoc />
public static bool operator ==(RequestId left, RequestId right) => left.Equals(right);
@ -77,7 +74,7 @@ public readonly struct RequestId : IEquatable<RequestId>
{
Throw.IfNull(writer);
switch (value._id)
switch (value.Id)
{
case string str:
writer.WriteStringValue(str);

View file

@ -59,7 +59,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
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.");
Debug.Assert(args.Services is RequestServiceProvider<GetPromptRequestParams>, $"The service provider should be a {nameof(RequestServiceProvider<>)} for this method to work correctly.");
return createTargetFunc(((RequestServiceProvider<GetPromptRequestParams>)args.Services!).Request);
}, CreateAIFunctionFactoryOptions(method, options)),
options);

View file

@ -67,7 +67,7 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
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.");
Debug.Assert(args.Services is RequestServiceProvider<ReadResourceRequestParams>, $"The service provider should be a {nameof(RequestServiceProvider<>)} for this method to work correctly.");
return createTargetFunc(((RequestServiceProvider<ReadResourceRequestParams>)args.Services!).Request);
}, CreateAIFunctionFactoryOptions(method, options)),
options);

View file

@ -63,7 +63,7 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
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.");
Debug.Assert(args.Services is RequestServiceProvider<CallToolRequestParams>, $"The service provider should be a {nameof(RequestServiceProvider<>)} for this method to work correctly.");
return createTargetFunc(((RequestServiceProvider<CallToolRequestParams>)args.Services!).Request);
}, CreateAIFunctionFactoryOptions(method, options)),
options);

View file

@ -27,8 +27,11 @@ internal sealed class DestinationBoundMcpServer(McpServerImpl server, ITransport
throw new ArgumentException("Only transports can provide a JsonRpcMessageContext.");
}
message.Context = new JsonRpcMessageContext();
message.Context.RelatedTransport = transport;
message.Context = new()
{
RelatedTransport = transport
};
return server.SendMessageAsync(message, cancellationToken);
}
@ -39,8 +42,11 @@ internal sealed class DestinationBoundMcpServer(McpServerImpl server, ITransport
throw new ArgumentException("Only transports can provide a JsonRpcMessageContext.");
}
request.Context = new JsonRpcMessageContext();
request.Context.RelatedTransport = transport;
request.Context = new()
{
RelatedTransport = transport
};
return server.SendRequestAsync(request, cancellationToken);
}
}

View file

@ -341,13 +341,9 @@ public abstract partial class McpServer : McpSession, IMcpServer
throw new McpProtocolException(error);
}
var primitiveSchemaDefinition =
jsonElement.Deserialize(McpJsonUtilities.JsonContext.Default.PrimitiveSchemaDefinition);
if (primitiveSchemaDefinition is null)
return
jsonElement.Deserialize(McpJsonUtilities.JsonContext.Default.PrimitiveSchemaDefinition) ??
throw new McpProtocolException($"Type '{type.FullName}' is not a supported property type for elicitation requests.");
return primitiveSchemaDefinition;
}
/// <summary>
@ -452,11 +448,9 @@ public abstract partial class McpServer : McpSession, IMcpServer
}
/// <summary>Provides an <see cref="IChatClient"/> implementation that's implemented via client sampling.</summary>
private sealed class SamplingChatClient : IChatClient
private sealed class SamplingChatClient(McpServer server) : IChatClient
{
private readonly McpServer _server;
public SamplingChatClient(McpServer server) => _server = server;
private readonly McpServer _server = server;
/// <inheritdoc/>
public Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default) =>
@ -493,11 +487,9 @@ public abstract partial class McpServer : McpSession, IMcpServer
/// Provides an <see cref="ILoggerProvider"/> implementation for creating loggers
/// that send logging message notifications to the client for logged messages.
/// </summary>
private sealed class ClientLoggerProvider : ILoggerProvider
private sealed class ClientLoggerProvider(McpServer server) : ILoggerProvider
{
private readonly McpServer _server;
public ClientLoggerProvider(McpServer server) => _server = server;
private readonly McpServer _server = server;
/// <inheritdoc />
public ILogger CreateLogger(string categoryName)
@ -510,16 +502,10 @@ public abstract partial class McpServer : McpSession, IMcpServer
/// <inheritdoc />
void IDisposable.Dispose() { }
private sealed class ClientLogger : ILogger
private sealed class ClientLogger(McpServer server, string categoryName) : ILogger
{
private readonly McpServer _server;
private readonly string _categoryName;
public ClientLogger(McpServer server, string categoryName)
{
_server = server;
_categoryName = categoryName;
}
private readonly McpServer _server = server;
private readonly string _categoryName = categoryName;
/// <inheritdoc />
public IDisposable? BeginScope<TState>(TState state) where TState : notnull =>

View file

@ -26,7 +26,7 @@ public sealed class McpServerFilters
/// Tools from both sources will be combined when returning results to clients.
/// </para>
/// </remarks>
public List<McpRequestFilter<ListToolsRequestParams, ListToolsResult>> ListToolsFilters { get; } = new();
public List<McpRequestFilter<ListToolsRequestParams, ListToolsResult>> ListToolsFilters { get; } = [];
/// <summary>
/// Gets the filters for the call tool handler pipeline.
@ -36,7 +36,7 @@ public sealed class McpServerFilters
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.ToolsCall"/> requests. The handler should implement logic to execute the requested tool and return appropriate results.
/// </remarks>
public List<McpRequestFilter<CallToolRequestParams, CallToolResult>> CallToolFilters { get; } = new();
public List<McpRequestFilter<CallToolRequestParams, CallToolResult>> CallToolFilters { get; } = [];
/// <summary>
/// Gets the filters for the list prompts handler pipeline.
@ -53,7 +53,7 @@ public sealed class McpServerFilters
/// Prompts from both sources will be combined when returning results to clients.
/// </para>
/// </remarks>
public List<McpRequestFilter<ListPromptsRequestParams, ListPromptsResult>> ListPromptsFilters { get; } = new();
public List<McpRequestFilter<ListPromptsRequestParams, ListPromptsResult>> ListPromptsFilters { get; } = [];
/// <summary>
/// Gets the filters for the get prompt handler pipeline.
@ -63,7 +63,7 @@ public sealed class McpServerFilters
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.PromptsGet"/> requests. The handler should implement logic to fetch or generate the requested prompt and return appropriate results.
/// </remarks>
public List<McpRequestFilter<GetPromptRequestParams, GetPromptResult>> GetPromptFilters { get; } = new();
public List<McpRequestFilter<GetPromptRequestParams, GetPromptResult>> GetPromptFilters { get; } = [];
/// <summary>
/// Gets the filters for the list resource templates handler pipeline.
@ -74,7 +74,7 @@ public sealed class McpServerFilters
/// <see cref="RequestMethods.ResourcesTemplatesList"/> requests. 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 List<McpRequestFilter<ListResourceTemplatesRequestParams, ListResourceTemplatesResult>> ListResourceTemplatesFilters { get; } = new();
public List<McpRequestFilter<ListResourceTemplatesRequestParams, ListResourceTemplatesResult>> ListResourceTemplatesFilters { get; } = [];
/// <summary>
/// Gets the filters for the list resources handler pipeline.
@ -85,7 +85,7 @@ public sealed class McpServerFilters
/// <see cref="RequestMethods.ResourcesList"/> requests. 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 List<McpRequestFilter<ListResourcesRequestParams, ListResourcesResult>> ListResourcesFilters { get; } = new();
public List<McpRequestFilter<ListResourcesRequestParams, ListResourcesResult>> ListResourcesFilters { get; } = [];
/// <summary>
/// Gets the filters for the read resource handler pipeline.
@ -95,7 +95,7 @@ public sealed class McpServerFilters
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.ResourcesRead"/> requests. The handler should implement logic to locate and retrieve the requested resource.
/// </remarks>
public List<McpRequestFilter<ReadResourceRequestParams, ReadResourceResult>> ReadResourceFilters { get; } = new();
public List<McpRequestFilter<ReadResourceRequestParams, ReadResourceResult>> ReadResourceFilters { get; } = [];
/// <summary>
/// Gets the filters for the complete handler pipeline.
@ -106,7 +106,7 @@ public sealed class McpServerFilters
/// <see cref="RequestMethods.CompletionComplete"/> requests. The handler processes auto-completion requests, returning a list of suggestions based on the
/// reference type and current argument value.
/// </remarks>
public List<McpRequestFilter<CompleteRequestParams, CompleteResult>> CompleteFilters { get; } = new();
public List<McpRequestFilter<CompleteRequestParams, CompleteResult>> CompleteFilters { get; } = [];
/// <summary>
/// Gets the filters for the subscribe to resources handler pipeline.
@ -123,7 +123,7 @@ public sealed class McpServerFilters
/// whenever a relevant resource is created, updated, or deleted.
/// </para>
/// </remarks>
public List<McpRequestFilter<SubscribeRequestParams, EmptyResult>> SubscribeToResourcesFilters { get; } = new();
public List<McpRequestFilter<SubscribeRequestParams, EmptyResult>> SubscribeToResourcesFilters { get; } = [];
/// <summary>
/// Gets the filters for the unsubscribe from resources handler pipeline.
@ -140,7 +140,7 @@ public sealed class McpServerFilters
/// to the client for the specified resources.
/// </para>
/// </remarks>
public List<McpRequestFilter<UnsubscribeRequestParams, EmptyResult>> UnsubscribeFromResourcesFilters { get; } = new();
public List<McpRequestFilter<UnsubscribeRequestParams, EmptyResult>> UnsubscribeFromResourcesFilters { get; } = [];
/// <summary>
/// Gets the filters for the set logging level handler pipeline.
@ -157,5 +157,5 @@ public sealed class McpServerFilters
/// at or above the specified level to the client as notifications/message notifications.
/// </para>
/// </remarks>
public List<McpRequestFilter<SetLevelRequestParams, EmptyResult>> SetLoggingLevelFilters { get; } = new();
public List<McpRequestFilter<SetLevelRequestParams, EmptyResult>> SetLoggingLevelFilters { get; } = [];
}

View file

@ -207,12 +207,9 @@ internal sealed partial class McpServerImpl : McpServer
// 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 && McpSessionHandler.SupportedProtocolVersions.Contains(clientProtocolVersion) ?
clientProtocolVersion :
McpSessionHandler.LatestProtocolVersion;
}
protocolVersion ??= request?.ProtocolVersion is string clientProtocolVersion && McpSessionHandler.SupportedProtocolVersions.Contains(clientProtocolVersion) ?
clientProtocolVersion :
McpSessionHandler.LatestProtocolVersion;
_negotiatedProtocolVersion = protocolVersion;

View file

@ -7,8 +7,6 @@ namespace ModelContextProtocol.Server;
/// </summary>
public sealed class McpServerOptions
{
private McpServerHandlers? _handlers;
/// <summary>
/// Gets or sets information about this server implementation, including its name and version.
/// </summary>
@ -97,11 +95,11 @@ public sealed class McpServerOptions
/// </summary>
public McpServerHandlers Handlers
{
get => _handlers ??= new();
get => field ??= new();
set
{
Throw.IfNull(value);
_handlers = value;
field = value;
}
}

View file

@ -90,7 +90,7 @@ public sealed class McpServerPromptCreateOptions
/// Creates a shallow clone of the current <see cref="McpServerPromptCreateOptions"/> instance.
/// </summary>
internal McpServerPromptCreateOptions Clone() =>
new McpServerPromptCreateOptions
new()
{
Services = Services,
Name = Name,

Some files were not shown because too many files have changed in this diff Show more