Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<System9Version>9.0.5</System9Version>
<System10Version>10.0.0-preview.4.25258.110</System10Version>
<MicrosoftExtensionsAIVersion>9.9.1</MicrosoftExtensionsAIVersion>
<System9Version>9.0.10</System9Version>
<System10Version>10.0.0-rc.2.25502.107</System10Version>
<MicrosoftExtensionsAIVersion>9.10.0</MicrosoftExtensionsAIVersion>
</PropertyGroup>

<!-- Product dependencies netstandard -->
Expand All @@ -28,19 +28,17 @@
<!-- Product dependencies .NET 9 -->
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(System9Version)" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.9.0" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(System9Version)" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(System9Version)" />
<PackageVersion Include="System.IO.Pipelines" Version="$(System9Version)" />
</ItemGroup>

<!-- Product dependencies .NET 10 -->
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(System10Version)" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.9.0" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(System10Version)" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(System10Version)" />
<PackageVersion Include="System.IO.Pipelines" Version="$(System10Version)" />
</ItemGroup>

<!-- Product dependencies shared -->
Expand All @@ -56,19 +54,19 @@
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />

<!-- Testing dependencies -->
<PackageVersion Include="Anthropic.SDK" Version="5.5.1" />
<PackageVersion Include="Anthropic.SDK" Version="5.6.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageVersion>
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.9.1-preview.1.25474.6" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.10.0-preview.1.25513.3" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="$(System9Version)" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(System9Version)" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(System9Version)" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="$(System9Version)" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="$(System9Version)" />
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="9.5.0" />
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="9.10.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="OpenTelemetry" Version="1.12.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ CancellationToken token
string? playerName = nameResponse.Content?["Name"].GetString();

// Generate a random number between 1 and 10
Random random = new Random();
Random random = new();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not Random.Shared?

int targetNumber = random.Next(1, 11); // 1 to 10 inclusive
int attempts = 0;

Expand Down
2 changes: 1 addition & 1 deletion samples/AspNetCoreMcpPerSessionTools/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
{
mcpOptions.Capabilities = new();
mcpOptions.Capabilities.Tools = new();
var toolCollection = mcpOptions.ToolCollection = new();
var toolCollection = mcpOptions.ToolCollection = [];

foreach (var tool in tools)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Common/CancellableStreamReader/CancellableStreamReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ public override string ReadToEnd()
CheckAsyncTaskInProgress();

// Call ReadBuffer, then pull data out of charBuffer.
StringBuilder sb = new StringBuilder(_charLen - _charPos);
StringBuilder sb = new(_charLen - _charPos);
do
{
sb.Append(_charBuffer, _charPos, _charLen - _charPos);
Expand Down Expand Up @@ -953,7 +953,7 @@ public virtual Task<string> ReadToEndAsync(CancellationToken cancellationToken)
private async Task<string> ReadToEndAsyncInternal(CancellationToken cancellationToken)
{
// Call ReadBuffer, then pull data out of charBuffer.
StringBuilder sb = new StringBuilder(_charLen - _charPos);
StringBuilder sb = new(_charLen - _charPos);
do
{
int tmpCharPos = _charPos;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ public McpAuthenticationOptions()
/// Gets or sets the events used to handle authentication events.
/// </summary>
public new McpAuthenticationEvents Events
{
get { return (McpAuthenticationEvents)base.Events!; }
set { base.Events = value; }
{
get => (McpAuthenticationEvents)base.Events!;
set => base.Events = value;
}

/// <summary>
Expand Down
14 changes: 7 additions & 7 deletions src/ModelContextProtocol.AspNetCore/AuthorizationFilterSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ await FilterAuthorizedItemsAsync(
});
}

private void CheckListToolsFilter(McpServerOptions options)
private static void CheckListToolsFilter(McpServerOptions options)
{
options.Filters.ListToolsFilters.Add(next => async (context, cancellationToken) =>
{
Expand Down Expand Up @@ -87,7 +87,7 @@ private void ConfigureCallToolFilter(McpServerOptions options)
});
}

private void CheckCallToolFilter(McpServerOptions options)
private static void CheckCallToolFilter(McpServerOptions options)
{
options.Filters.CallToolFilters.Add(next => async (context, cancellationToken) =>
{
Expand Down Expand Up @@ -115,7 +115,7 @@ await FilterAuthorizedItemsAsync(
});
}

private void CheckListResourcesFilter(McpServerOptions options)
private static void CheckListResourcesFilter(McpServerOptions options)
{
options.Filters.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
{
Expand Down Expand Up @@ -145,7 +145,7 @@ await FilterAuthorizedItemsAsync(
});
}

private void CheckListResourceTemplatesFilter(McpServerOptions options)
private static void CheckListResourceTemplatesFilter(McpServerOptions options)
{
options.Filters.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
{
Expand Down Expand Up @@ -177,7 +177,7 @@ private void ConfigureReadResourceFilter(McpServerOptions options)
});
}

private void CheckReadResourceFilter(McpServerOptions options)
private static void CheckReadResourceFilter(McpServerOptions options)
{
options.Filters.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
{
Expand Down Expand Up @@ -205,7 +205,7 @@ await FilterAuthorizedItemsAsync(
});
}

private void CheckListPromptsFilter(McpServerOptions options)
private static void CheckListPromptsFilter(McpServerOptions options)
{
options.Filters.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
{
Expand Down Expand Up @@ -237,7 +237,7 @@ private void ConfigureGetPromptFilter(McpServerOptions options)
});
}

private void CheckGetPromptFilter(McpServerOptions options)
private static void CheckGetPromptFilter(McpServerOptions options)
{
options.Filters.GetPromptFilters.Add(next => async (context, cancellationToken) =>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
<TargetFrameworks>net10.0;net9.0;net8.0</TargetFrameworks>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub Should we be publishing net10.0 assets for this package before .NET 10 goes GA?

<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand Down
5 changes: 1 addition & 4 deletions src/ModelContextProtocol.Core/AIContentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,7 @@ public static IList<PromptMessage> ToPromptMessages(this ChatMessage chatMessage
_ => null,
};

if (ac is not null)
{
ac.RawRepresentation = content;
}
ac?.RawRepresentation = content;

return ac;
}
Expand Down
36 changes: 22 additions & 14 deletions src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -238,14 +241,17 @@ private async Task PerformOAuthAuthorizationAsync(
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
{
Expand Down Expand Up @@ -540,11 +546,7 @@ private static string NormalizeUri(Uri uri)
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('/');
}
Expand Down Expand Up @@ -633,18 +635,18 @@ private async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(H
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();
}
}

Expand All @@ -664,12 +666,18 @@ private static string GenerateCodeVerifier()

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.");
Expand Down
6 changes: 2 additions & 4 deletions src/ModelContextProtocol.Core/Client/McpClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,15 @@
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)
{
Expand Down Expand Up @@ -56,7 +50,7 @@ protected override async ValueTask CleanupAsync(Exception? error = null, Cancell
// 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)
{
Expand Down
14 changes: 11 additions & 3 deletions src/ModelContextProtocol.Core/Client/StdioClientTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public StdioClientTransport(StdioClientTransportOptions options, ILoggerFactory?

_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 />
Expand Down Expand Up @@ -189,7 +189,7 @@ public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken =

try
{
DisposeProcess(process, processStarted, _options.ShutdownTimeout, endpointName);
DisposeProcess(process, processStarted, _options.ShutdownTimeout);
}
catch (Exception ex2)
{
Expand All @@ -201,7 +201,7 @@ public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken =
}

internal static void DisposeProcess(
Process? process, bool processRunning, TimeSpan shutdownTimeout, string endpointName)
Process? process, bool processRunning, TimeSpan shutdownTimeout)
{
if (process is not null)
{
Expand Down Expand Up @@ -279,4 +279,12 @@ private static string EscapeArgumentString(string argument) =>

[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
}
4 changes: 2 additions & 2 deletions src/ModelContextProtocol.Core/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ private static void InjectContext(object? message, string key, string value)
{
if (jsonObject["_meta"] is not JsonObject meta)
{
meta = new JsonObject();
jsonObject["_meta"] = meta;
jsonObject["_meta"] = meta = [];
}

meta[key] = value;
}
}
Expand Down
Loading