Skip to content

Commit 03fdd7f

Browse files
authored
Merge branch 'main' into stvansolano/add-more-tests
2 parents dc2221b + c452dc8 commit 03fdd7f

28 files changed

Lines changed: 333 additions & 139 deletions

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.DefaultOptions.GetTypeInfo<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)