Skip to content

Commit c452dc8

Browse files
authored
Reduce dependencies to 8.x versions of packages (#195)
* Reduce dependencies to 8.x versions of packages When building for netstandard2.0 or net8.0, the ModelContextProtocol library now only depends on 8.x versions of nuget packages, rather than using the newer 9.x versions. The exception to this are packages that don't have 8.x versions. This also introduces a net9.0 tfm. When building for net9.0, it'll use 9.x versions of packages. The PR temporarily removes the Microsoft.Extensions.AI.nupkg reference (the Microsoft.Extensions.AI.Abstractions reference is still very much there). The latest Microsoft.Extensions.AI package on nuget has a System.Text.Json 9.x reference, but the next build to be published drops that down to 8.x as well. Once that build is published, the dependency will be added back, and temporary polyfills (in particular TemporaryAIFunctionFactory) will be deleted. * Fix StdioClientSessionTransport shutdown StreamClientSessionTransport's ReadMessageAsync method does await CleanupAsync in its finally. CleanupAsync awaits _readTask. _readTask _is_ ReadMessageAsync. So it's waiting for itself to complete, which will then always timeout. We may want to refactor how this works more in the future, but for now, make sure it doesn't wait for itself. * Fix dangling requests when server unexpectedly shuts down If the client makes a request to the server and while waiting for the response the server shuts down, the client hangs. Make sure that when the client's message loops ends due to the server's output it's reading from ends, any pending requests are notified. * Add net9.0 testing * Avoid docker port conflicts from test suites running concurrently
1 parent 1c145a5 commit c452dc8

25 files changed

+247
-91
lines changed

Directory.Packages.props

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,59 @@
11
<Project>
22
<PropertyGroup>
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
4-
<SystemVersion>9.0.3</SystemVersion>
54
<System10Version>10.0.0-preview.2.25163.2</System10Version>
6-
<MicrosoftExtensionsVersion>9.0.3</MicrosoftExtensionsVersion>
75
<MicrosoftExtensionsAIVersion>9.3.0-preview.1.25161.3</MicrosoftExtensionsAIVersion>
86
</PropertyGroup>
7+
8+
<!-- Product dependencies netstandard -->
9+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
10+
<PackageVersion Include="Microsoft.Bcl.Memory" Version="9.0.0" />
11+
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
12+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
13+
<PackageVersion Include="System.IO.Pipelines" Version="8.0.0" />
14+
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
15+
<PackageVersion Include="System.Threading.Channels" Version="8.0.0" />
16+
</ItemGroup>
17+
18+
<!-- Product dependencies LTS -->
19+
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
20+
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
21+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
22+
<PackageVersion Include="System.IO.Pipelines" Version="8.0.0" />
23+
</ItemGroup>
24+
25+
<!-- Product dependencies .NET 9 -->
26+
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
27+
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" />
28+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
29+
<PackageVersion Include="System.IO.Pipelines" Version="9.0.0" />
30+
</ItemGroup>
31+
32+
<!-- Product dependencies shared -->
933
<ItemGroup>
10-
<!-- Product dependencies -->
11-
<PackageVersion Include="coverlet.collector" Version="6.0.4">
12-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
13-
<PrivateAssets>all</PrivateAssets>
14-
</PackageVersion>
15-
<PackageVersion Include="Microsoft.Bcl.Memory" Version="$(SystemVersion)" />
16-
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(MicrosoftExtensionsAIVersion)" />
1734
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="$(MicrosoftExtensionsAIVersion)" />
18-
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(MicrosoftExtensionsVersion)" />
19-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsVersion)" />
35+
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(MicrosoftExtensionsAIVersion)" />
2036
<PackageVersion Include="System.Net.ServerSentEvents" Version="$(System10Version)" />
21-
<PackageVersion Include="System.Text.Json" Version="$(SystemVersion)" />
22-
<PackageVersion Include="System.Threading.Channels" Version="$(SystemVersion)" />
37+
</ItemGroup>
38+
39+
<ItemGroup>
2340

2441
<!-- Build Infra & Packaging -->
2542
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
2643

2744
<!-- Testing dependencies -->
2845
<PackageVersion Include="Anthropic.SDK" Version="5.0.0" />
46+
<PackageVersion Include="coverlet.collector" Version="6.0.4">
47+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
48+
<PrivateAssets>all</PrivateAssets>
49+
</PackageVersion>
2950
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
3051
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="$(MicrosoftExtensionsAIVersion)" />
31-
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsVersion)" />
32-
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsVersion)" />
33-
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsVersion)" />
52+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
53+
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
54+
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.3" />
55+
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.3" />
56+
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.3" />
3457
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
3558
<PackageVersion Include="Moq" Version="4.20.72" />
3659
<PackageVersion Include="OpenTelemetry" Version="1.11.2" />

samples/QuickstartClient/QuickstartClient.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<ItemGroup>
1616
<PackageReference Include="Anthropic.SDK" />
1717
<PackageReference Include="Microsoft.Extensions.Hosting" />
18+
<PackageReference Include="Microsoft.Extensions.AI" />
1819
</ItemGroup>
1920

2021
</Project>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Runtime.CompilerServices;
2+
3+
namespace System.Threading.Channels;
4+
5+
internal static class ChannelExtensions
6+
{
7+
public static async IAsyncEnumerable<T> ReadAllAsync<T>(this ChannelReader<T> reader, [EnumeratorCancellation] CancellationToken cancellationToken)
8+
{
9+
while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false))
10+
{
11+
while (reader.TryRead(out var item))
12+
{
13+
yield return item;
14+
}
15+
}
16+
}
17+
}

src/ModelContextProtocol.AspNetCore/ModelContextProtocol.AspNetCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net8.0</TargetFramework>
4+
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<GenerateDocumentationFile>true</GenerateDocumentationFile>

src/ModelContextProtocol/Client/McpClientExtensions.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public static Task PingAsync(this IMcpClient client, CancellationToken cancellat
2424
return client.SendRequestAsync(
2525
RequestMethods.Ping,
2626
parameters: null,
27-
McpJsonUtilities.JsonContext.Default.Object,
27+
McpJsonUtilities.JsonContext.Default.Object!,
2828
McpJsonUtilities.JsonContext.Default.Object,
2929
cancellationToken: cancellationToken);
3030
}
@@ -52,7 +52,7 @@ public static async Task<IList<McpClientTool>> ListToolsAsync(
5252
{
5353
var toolResults = await client.SendRequestAsync(
5454
RequestMethods.ToolsList,
55-
CreateCursorDictionary(cursor),
55+
CreateCursorDictionary(cursor)!,
5656
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
5757
McpJsonUtilities.JsonContext.Default.ListToolsResult,
5858
cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -96,7 +96,7 @@ public static async IAsyncEnumerable<McpClientTool> EnumerateToolsAsync(
9696
{
9797
var toolResults = await client.SendRequestAsync(
9898
RequestMethods.ToolsList,
99-
CreateCursorDictionary(cursor),
99+
CreateCursorDictionary(cursor)!,
100100
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
101101
McpJsonUtilities.JsonContext.Default.ListToolsResult,
102102
cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -128,7 +128,7 @@ public static async Task<IList<McpClientPrompt>> ListPromptsAsync(
128128
{
129129
var promptResults = await client.SendRequestAsync(
130130
RequestMethods.PromptsList,
131-
CreateCursorDictionary(cursor),
131+
CreateCursorDictionary(cursor)!,
132132
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
133133
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
134134
cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -166,7 +166,7 @@ public static async IAsyncEnumerable<Prompt> EnumeratePromptsAsync(
166166
{
167167
var promptResults = await client.SendRequestAsync(
168168
RequestMethods.PromptsList,
169-
CreateCursorDictionary(cursor),
169+
CreateCursorDictionary(cursor)!,
170170
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
171171
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
172172
cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -230,7 +230,7 @@ public static async Task<IList<ResourceTemplate>> ListResourceTemplatesAsync(
230230
{
231231
var templateResults = await client.SendRequestAsync(
232232
RequestMethods.ResourcesTemplatesList,
233-
CreateCursorDictionary(cursor),
233+
CreateCursorDictionary(cursor)!,
234234
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
235235
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
236236
cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -271,7 +271,7 @@ public static async IAsyncEnumerable<ResourceTemplate> EnumerateResourceTemplate
271271
{
272272
var templateResults = await client.SendRequestAsync(
273273
RequestMethods.ResourcesTemplatesList,
274-
CreateCursorDictionary(cursor),
274+
CreateCursorDictionary(cursor)!,
275275
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
276276
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
277277
cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -304,7 +304,7 @@ public static async Task<IList<Resource>> ListResourcesAsync(
304304
{
305305
var resourceResults = await client.SendRequestAsync(
306306
RequestMethods.ResourcesList,
307-
CreateCursorDictionary(cursor),
307+
CreateCursorDictionary(cursor)!,
308308
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
309309
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
310310
cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -345,7 +345,7 @@ public static async IAsyncEnumerable<Resource> EnumerateResourcesAsync(
345345
{
346346
var resourceResults = await client.SendRequestAsync(
347347
RequestMethods.ResourcesList,
348-
CreateCursorDictionary(cursor),
348+
CreateCursorDictionary(cursor)!,
349349
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
350350
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
351351
cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -374,7 +374,7 @@ public static Task<ReadResourceResult> ReadResourceAsync(
374374

375375
return client.SendRequestAsync(
376376
RequestMethods.ResourcesRead,
377-
new Dictionary<string, object?> { ["uri"] = uri },
377+
new Dictionary<string, object> { ["uri"] = uri },
378378
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
379379
McpJsonUtilities.JsonContext.Default.ReadResourceResult,
380380
cancellationToken: cancellationToken);
@@ -401,7 +401,7 @@ public static Task<CompleteResult> GetCompletionAsync(this IMcpClient client, Re
401401

402402
return client.SendRequestAsync(
403403
RequestMethods.CompletionComplete,
404-
new Dictionary<string, object?>
404+
new Dictionary<string, object>
405405
{
406406
["ref"] = reference,
407407
["argument"] = new Argument { Name = argumentName, Value = argumentValue }
@@ -424,7 +424,7 @@ public static Task SubscribeToResourceAsync(this IMcpClient client, string uri,
424424

425425
return client.SendRequestAsync(
426426
RequestMethods.ResourcesSubscribe,
427-
new Dictionary<string, object?> { ["uri"] = uri },
427+
new Dictionary<string, object> { ["uri"] = uri },
428428
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
429429
McpJsonUtilities.JsonContext.Default.EmptyResult,
430430
cancellationToken: cancellationToken);
@@ -443,7 +443,7 @@ public static Task UnsubscribeFromResourceAsync(this IMcpClient client, string u
443443

444444
return client.SendRequestAsync(
445445
RequestMethods.ResourcesUnsubscribe,
446-
new Dictionary<string, object?> { ["uri"] = uri },
446+
new Dictionary<string, object> { ["uri"] = uri },
447447
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
448448
McpJsonUtilities.JsonContext.Default.EmptyResult,
449449
cancellationToken: cancellationToken);
@@ -629,7 +629,7 @@ public static Task SetLoggingLevel(this IMcpClient client, LoggingLevel level, C
629629

630630
return client.SendRequestAsync(
631631
RequestMethods.LoggingSetLevel,
632-
new Dictionary<string, object?> { ["level"] = level },
632+
new Dictionary<string, object> { ["level"] = level },
633633
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
634634
McpJsonUtilities.JsonContext.Default.EmptyResult,
635635
cancellationToken: cancellationToken);

src/ModelContextProtocol/Diagnostics.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,20 @@ internal static class Diagnostics
77
{
88
internal static ActivitySource ActivitySource { get; } = new("Experimental.ModelContextProtocol");
99

10+
internal static Meter Meter { get; } = new("Experimental.ModelContextProtocol");
11+
12+
internal static Histogram<double> CreateDurationHistogram(string name, string description, bool longBuckets) =>
13+
Diagnostics.Meter.CreateHistogram<double>(name, "s", description
14+
#if NET9_0_OR_GREATER
15+
, advice: longBuckets ? LongSecondsBucketBoundaries : ShortSecondsBucketBoundaries
16+
#endif
17+
);
18+
19+
#if NET9_0_OR_GREATER
1020
/// <summary>
1121
/// Follows boundaries from http.server.request.duration/http.client.request.duration
1222
/// </summary>
13-
internal static InstrumentAdvice<double> ShortSecondsBucketBoundaries { get; } = new()
23+
private static InstrumentAdvice<double> ShortSecondsBucketBoundaries { get; } = new()
1424
{
1525
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],
1626
};
@@ -19,11 +29,9 @@ internal static class Diagnostics
1929
/// Not based on a standard. Larger bucket sizes for longer lasting operations, e.g. HTTP connection duration.
2030
/// See https://github.com/open-telemetry/semantic-conventions/issues/336
2131
/// </summary>
22-
internal static InstrumentAdvice<double> LongSecondsBucketBoundaries { get; } = new()
32+
private static InstrumentAdvice<double> LongSecondsBucketBoundaries { get; } = new()
2333
{
2434
HistogramBucketBoundaries = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300],
2535
};
26-
27-
internal static Meter Meter { get; } = new("Experimental.ModelContextProtocol");
28-
36+
#endif
2937
}

src/ModelContextProtocol/ModelContextProtocol.csproj

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net8.0;netstandard2.0</TargetFrameworks>
4+
<TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
55
<GenerateDocumentationFile>true</GenerateDocumentationFile>
66
<IsPackable>true</IsPackable>
77
<PackageId>ModelContextProtocol</PackageId>
@@ -12,22 +12,33 @@
1212
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">
1313
<IsAotCompatible>true</IsAotCompatible>
1414
</PropertyGroup>
15-
16-
<ItemGroup>
17-
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
18-
<PackageReference Include="Microsoft.Extensions.AI" />
19-
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
20-
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
21-
<PackageReference Include="System.Net.ServerSentEvents" />
22-
</ItemGroup>
2315

16+
<!-- Dependencies only needed by netstandard2.0 -->
2417
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
2518
<Compile Include="..\Common\Polyfills\**\*.cs" />
2619
<PackageReference Include="Microsoft.Bcl.Memory" />
2720
<PackageReference Include="System.Text.Json" />
2821
<PackageReference Include="System.Threading.Channels" />
2922
</ItemGroup>
3023

24+
<!-- Dependencies only needed by netstandard2.0 or net8.0 -->
25+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net8.0'">
26+
<PackageReference Include="System.IO.Pipelines" />
27+
</ItemGroup>
28+
29+
<!-- Dependencies needed by all -->
30+
<ItemGroup>
31+
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
32+
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
33+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
34+
<PackageReference Include="System.Net.ServerSentEvents" />
35+
36+
<!-- Temporarily removed until new version can be picked up that
37+
has reduced dependencies:
38+
<PackageReference Include="Microsoft.Extensions.AI" />
39+
-->
40+
</ItemGroup>
41+
3142
<ItemGroup>
3243
<None Include="..\..\README.md" pack="true" PackagePath="\" />
3344
</ItemGroup>

src/ModelContextProtocol/Protocol/Transport/SseResponseStreamTransport.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ private void WriteJsonRpcMessageToBuffer(SseItem<IJsonRpcMessage?> item, IBuffer
5353
return;
5454
}
5555

56-
JsonSerializer.Serialize(GetUtf8JsonWriter(writer), item.Data, McpJsonUtilities.JsonContext.Default.IJsonRpcMessage);
56+
JsonSerializer.Serialize(GetUtf8JsonWriter(writer), item.Data, McpJsonUtilities.JsonContext.Default.IJsonRpcMessage!);
5757
}
5858

5959
/// <inheritdoc/>

src/ModelContextProtocol/Protocol/Transport/StreamClientSessionTransport.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,16 @@ public StreamClientSessionTransport(
2929
_serverInput = serverInput;
3030
EndpointName = endpointName;
3131

32-
// Start reading messages in the background
32+
// Start reading messages in the background. We use the rarer pattern of new Task + Start
33+
// in order to ensure that the body of the task will always see _readTask initialized.
34+
// It is then able to reliably null it out on completion.
3335
Logger.TransportReadingMessages(endpointName);
34-
_readTask = Task.Run(() => ReadMessagesAsync(_shutdownCts.Token), CancellationToken.None);
36+
var readTask = new Task<Task>(
37+
thisRef => ((StreamClientSessionTransport)thisRef!).ReadMessagesAsync(_shutdownCts.Token),
38+
this,
39+
TaskCreationOptions.DenyChildAttach);
40+
_readTask = readTask.Unwrap();
41+
readTask.Start();
3542

3643
SetConnected(true);
3744
}
@@ -117,6 +124,7 @@ private async Task ReadMessagesAsync(CancellationToken cancellationToken)
117124
}
118125
finally
119126
{
127+
_readTask = null;
120128
await CleanupAsync(cancellationToken).ConfigureAwait(false);
121129
}
122130
}

src/ModelContextProtocol/Protocol/Types/ContextInclusion.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace ModelContextProtocol.Protocol.Types;
66
/// A request to include context from one or more MCP servers (including the caller), to be attached to the prompt.
77
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">See the schema for details</see>
88
/// </summary>
9-
[JsonConverter(typeof(JsonStringEnumConverter<ContextInclusion>))]
9+
[JsonConverter(typeof(CustomizableJsonStringEnumConverter<ContextInclusion>))]
1010
public enum ContextInclusion
1111
{
1212
/// <summary>

0 commit comments

Comments
 (0)