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
55 changes: 39 additions & 16 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,36 +1,59 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<SystemVersion>9.0.3</SystemVersion>
<System10Version>10.0.0-preview.2.25163.2</System10Version>
<MicrosoftExtensionsVersion>9.0.3</MicrosoftExtensionsVersion>
<MicrosoftExtensionsAIVersion>9.3.0-preview.1.25161.3</MicrosoftExtensionsAIVersion>
</PropertyGroup>

<!-- Product dependencies netstandard -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageVersion Include="Microsoft.Bcl.Memory" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageVersion Include="System.IO.Pipelines" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="System.Threading.Channels" Version="8.0.0" />
</ItemGroup>

<!-- Product dependencies LTS -->
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageVersion Include="System.IO.Pipelines" Version="8.0.0" />
</ItemGroup>

<!-- Product dependencies .NET 9 -->
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageVersion Include="System.IO.Pipelines" Version="9.0.0" />
</ItemGroup>

<!-- Product dependencies shared -->
<ItemGroup>
<!-- Product dependencies -->
<PackageVersion Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageVersion>
<PackageVersion Include="Microsoft.Bcl.Memory" Version="$(SystemVersion)" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(MicrosoftExtensionsAIVersion)" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="$(MicrosoftExtensionsAIVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(MicrosoftExtensionsVersion)" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsVersion)" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(MicrosoftExtensionsAIVersion)" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="$(System10Version)" />
<PackageVersion Include="System.Text.Json" Version="$(SystemVersion)" />
<PackageVersion Include="System.Threading.Channels" Version="$(SystemVersion)" />
</ItemGroup>

<ItemGroup>

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

<!-- Testing dependencies -->
<PackageVersion Include="Anthropic.SDK" Version="5.0.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="$(MicrosoftExtensionsAIVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsVersion)" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsVersion)" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsVersion)" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.3" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="OpenTelemetry" Version="1.11.2" />
Expand Down
1 change: 1 addition & 0 deletions samples/QuickstartClient/QuickstartClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<ItemGroup>
<PackageReference Include="Anthropic.SDK" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.Extensions.AI" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Runtime.CompilerServices;

namespace System.Threading.Channels;

internal static class ChannelExtensions
{
public static async IAsyncEnumerable<T> ReadAllAsync<T>(this ChannelReader<T> reader, [EnumeratorCancellation] CancellationToken cancellationToken)
{
while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false))
{
while (reader.TryRead(out var item))
{
yield return item;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand Down
28 changes: 14 additions & 14 deletions src/ModelContextProtocol/Client/McpClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static Task PingAsync(this IMcpClient client, CancellationToken cancellat
return client.SendRequestAsync(
RequestMethods.Ping,
parameters: null,
McpJsonUtilities.JsonContext.Default.Object,
McpJsonUtilities.JsonContext.Default.Object!,
McpJsonUtilities.JsonContext.Default.Object,
cancellationToken: cancellationToken);
}
Expand Down Expand Up @@ -52,7 +52,7 @@ public static async Task<IList<McpClientTool>> ListToolsAsync(
{
var toolResults = await client.SendRequestAsync(
RequestMethods.ToolsList,
CreateCursorDictionary(cursor),
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
McpJsonUtilities.JsonContext.Default.ListToolsResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -96,7 +96,7 @@ public static async IAsyncEnumerable<McpClientTool> EnumerateToolsAsync(
{
var toolResults = await client.SendRequestAsync(
RequestMethods.ToolsList,
CreateCursorDictionary(cursor),
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
McpJsonUtilities.JsonContext.Default.ListToolsResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -128,7 +128,7 @@ public static async Task<IList<McpClientPrompt>> ListPromptsAsync(
{
var promptResults = await client.SendRequestAsync(
RequestMethods.PromptsList,
CreateCursorDictionary(cursor),
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -166,7 +166,7 @@ public static async IAsyncEnumerable<Prompt> EnumeratePromptsAsync(
{
var promptResults = await client.SendRequestAsync(
RequestMethods.PromptsList,
CreateCursorDictionary(cursor),
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -230,7 +230,7 @@ public static async Task<IList<ResourceTemplate>> ListResourceTemplatesAsync(
{
var templateResults = await client.SendRequestAsync(
RequestMethods.ResourcesTemplatesList,
CreateCursorDictionary(cursor),
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -271,7 +271,7 @@ public static async IAsyncEnumerable<ResourceTemplate> EnumerateResourceTemplate
{
var templateResults = await client.SendRequestAsync(
RequestMethods.ResourcesTemplatesList,
CreateCursorDictionary(cursor),
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -304,7 +304,7 @@ public static async Task<IList<Resource>> ListResourcesAsync(
{
var resourceResults = await client.SendRequestAsync(
RequestMethods.ResourcesList,
CreateCursorDictionary(cursor),
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -345,7 +345,7 @@ public static async IAsyncEnumerable<Resource> EnumerateResourcesAsync(
{
var resourceResults = await client.SendRequestAsync(
RequestMethods.ResourcesList,
CreateCursorDictionary(cursor),
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -374,7 +374,7 @@ public static Task<ReadResourceResult> ReadResourceAsync(

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

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

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

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

return client.SendRequestAsync(
RequestMethods.LoggingSetLevel,
new Dictionary<string, object?> { ["level"] = level },
new Dictionary<string, object> { ["level"] = level },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken);
Expand Down
18 changes: 13 additions & 5 deletions src/ModelContextProtocol/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@ internal static class Diagnostics
{
internal static ActivitySource ActivitySource { get; } = new("Experimental.ModelContextProtocol");

internal static Meter Meter { get; } = new("Experimental.ModelContextProtocol");

internal static Histogram<double> CreateDurationHistogram(string name, string description, bool longBuckets) =>
Diagnostics.Meter.CreateHistogram<double>(name, "s", description
#if NET9_0_OR_GREATER
, advice: longBuckets ? LongSecondsBucketBoundaries : ShortSecondsBucketBoundaries
#endif
);

#if NET9_0_OR_GREATER
/// <summary>
/// Follows boundaries from http.server.request.duration/http.client.request.duration
/// </summary>
internal static InstrumentAdvice<double> ShortSecondsBucketBoundaries { get; } = new()
private static InstrumentAdvice<double> ShortSecondsBucketBoundaries { get; } = new()
{
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],
};
Expand All @@ -19,11 +29,9 @@ internal static class Diagnostics
/// Not based on a standard. Larger bucket sizes for longer lasting operations, e.g. HTTP connection duration.
/// See https://github.com/open-telemetry/semantic-conventions/issues/336
/// </summary>
internal static InstrumentAdvice<double> LongSecondsBucketBoundaries { get; } = new()
private static InstrumentAdvice<double> LongSecondsBucketBoundaries { get; } = new()
{
HistogramBucketBoundaries = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300],
};

internal static Meter Meter { get; } = new("Experimental.ModelContextProtocol");

#endif
}
29 changes: 20 additions & 9 deletions src/ModelContextProtocol/ModelContextProtocol.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsPackable>true</IsPackable>
<PackageId>ModelContextProtocol</PackageId>
Expand All @@ -12,22 +12,33 @@
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
<PackageReference Include="Microsoft.Extensions.AI" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="System.Net.ServerSentEvents" />
</ItemGroup>

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

<!-- Dependencies only needed by netstandard2.0 or net8.0 -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net8.0'">
<PackageReference Include="System.IO.Pipelines" />
</ItemGroup>

<!-- Dependencies needed by all -->
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="System.Net.ServerSentEvents" />

<!-- Temporarily removed until new version can be picked up that
has reduced dependencies:
<PackageReference Include="Microsoft.Extensions.AI" />
-->
</ItemGroup>

<ItemGroup>
<None Include="..\..\README.md" pack="true" PackagePath="\" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private void WriteJsonRpcMessageToBuffer(SseItem<IJsonRpcMessage?> item, IBuffer
return;
}

JsonSerializer.Serialize(GetUtf8JsonWriter(writer), item.Data, McpJsonUtilities.JsonContext.Default.IJsonRpcMessage);
JsonSerializer.Serialize(GetUtf8JsonWriter(writer), item.Data, McpJsonUtilities.JsonContext.Default.IJsonRpcMessage!);
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,16 @@ public StreamClientSessionTransport(
_serverInput = serverInput;
EndpointName = endpointName;

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

SetConnected(true);
}
Expand Down Expand Up @@ -117,6 +124,7 @@ private async Task ReadMessagesAsync(CancellationToken cancellationToken)
}
finally
{
_readTask = null;
await CleanupAsync(cancellationToken).ConfigureAwait(false);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace ModelContextProtocol.Protocol.Types;
/// A request to include context from one or more MCP servers (including the caller), to be attached to the prompt.
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">See the schema for details</see>
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<ContextInclusion>))]
[JsonConverter(typeof(CustomizableJsonStringEnumConverter<ContextInclusion>))]
public enum ContextInclusion
{
/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/ModelContextProtocol/Protocol/Types/LoggingLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace ModelContextProtocol.Protocol.Types;
/// These map to syslog message severities, as specified in RFC-5424:
/// https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<LoggingLevel>))]
[JsonConverter(typeof(CustomizableJsonStringEnumConverter<LoggingLevel>))]
public enum LoggingLevel
{
/// <summary>Detailed debug information, typically only valuable to developers.</summary>
Expand Down
2 changes: 1 addition & 1 deletion src/ModelContextProtocol/Protocol/Types/Role.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace ModelContextProtocol.Protocol.Types;
/// Represents the type of role in the conversation.
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">See the schema for details</see>
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<Role>))]
[JsonConverter(typeof(CustomizableJsonStringEnumConverter<Role>))]
public enum Role
{
/// <summary>
Expand Down
Loading