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
This commit is contained in:
parent
1c145a5d52
commit
c452dc8e26
25 changed files with 247 additions and 91 deletions
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Anthropic.SDK" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public static class McpClientExtensions
|
|||
return client.SendRequestAsync(
|
||||
RequestMethods.Ping,
|
||||
parameters: null,
|
||||
McpJsonUtilities.JsonContext.Default.Object,
|
||||
McpJsonUtilities.JsonContext.Default.Object!,
|
||||
McpJsonUtilities.JsonContext.Default.Object,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ public static class McpClientExtensions
|
|||
{
|
||||
var toolResults = await client.SendRequestAsync(
|
||||
RequestMethods.ToolsList,
|
||||
CreateCursorDictionary(cursor),
|
||||
CreateCursorDictionary(cursor)!,
|
||||
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||
McpJsonUtilities.JsonContext.Default.ListToolsResult,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -96,7 +96,7 @@ public static class McpClientExtensions
|
|||
{
|
||||
var toolResults = await client.SendRequestAsync(
|
||||
RequestMethods.ToolsList,
|
||||
CreateCursorDictionary(cursor),
|
||||
CreateCursorDictionary(cursor)!,
|
||||
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||
McpJsonUtilities.JsonContext.Default.ListToolsResult,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -128,7 +128,7 @@ public static class McpClientExtensions
|
|||
{
|
||||
var promptResults = await client.SendRequestAsync(
|
||||
RequestMethods.PromptsList,
|
||||
CreateCursorDictionary(cursor),
|
||||
CreateCursorDictionary(cursor)!,
|
||||
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -166,7 +166,7 @@ public static class McpClientExtensions
|
|||
{
|
||||
var promptResults = await client.SendRequestAsync(
|
||||
RequestMethods.PromptsList,
|
||||
CreateCursorDictionary(cursor),
|
||||
CreateCursorDictionary(cursor)!,
|
||||
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -230,7 +230,7 @@ public static class McpClientExtensions
|
|||
{
|
||||
var templateResults = await client.SendRequestAsync(
|
||||
RequestMethods.ResourcesTemplatesList,
|
||||
CreateCursorDictionary(cursor),
|
||||
CreateCursorDictionary(cursor)!,
|
||||
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -271,7 +271,7 @@ public static class McpClientExtensions
|
|||
{
|
||||
var templateResults = await client.SendRequestAsync(
|
||||
RequestMethods.ResourcesTemplatesList,
|
||||
CreateCursorDictionary(cursor),
|
||||
CreateCursorDictionary(cursor)!,
|
||||
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -304,7 +304,7 @@ public static class McpClientExtensions
|
|||
{
|
||||
var resourceResults = await client.SendRequestAsync(
|
||||
RequestMethods.ResourcesList,
|
||||
CreateCursorDictionary(cursor),
|
||||
CreateCursorDictionary(cursor)!,
|
||||
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -345,7 +345,7 @@ public static class McpClientExtensions
|
|||
{
|
||||
var resourceResults = await client.SendRequestAsync(
|
||||
RequestMethods.ResourcesList,
|
||||
CreateCursorDictionary(cursor),
|
||||
CreateCursorDictionary(cursor)!,
|
||||
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -374,7 +374,7 @@ public static class McpClientExtensions
|
|||
|
||||
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);
|
||||
|
|
@ -401,7 +401,7 @@ public static class McpClientExtensions
|
|||
|
||||
return client.SendRequestAsync(
|
||||
RequestMethods.CompletionComplete,
|
||||
new Dictionary<string, object?>
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ref"] = reference,
|
||||
["argument"] = new Argument { Name = argumentName, Value = argumentValue }
|
||||
|
|
@ -424,7 +424,7 @@ public static class McpClientExtensions
|
|||
|
||||
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);
|
||||
|
|
@ -443,7 +443,7 @@ public static class McpClientExtensions
|
|||
|
||||
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);
|
||||
|
|
@ -629,7 +629,7 @@ public static class McpClientExtensions
|
|||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
};
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -12,15 +12,8 @@
|
|||
<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" />
|
||||
|
|
@ -28,6 +21,24 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public sealed class SseResponseStreamTransport(Stream sseResponseStream, string
|
|||
return;
|
||||
}
|
||||
|
||||
JsonSerializer.Serialize(GetUtf8JsonWriter(writer), item.Data, McpJsonUtilities.JsonContext.Default.IJsonRpcMessage);
|
||||
JsonSerializer.Serialize(GetUtf8JsonWriter(writer), item.Data, McpJsonUtilities.JsonContext.Default.IJsonRpcMessage!);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
|
|
@ -29,9 +29,16 @@ internal class StreamClientSessionTransport : TransportBase
|
|||
_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);
|
||||
}
|
||||
|
|
@ -117,6 +124,7 @@ internal class StreamClientSessionTransport : TransportBase
|
|||
}
|
||||
finally
|
||||
{
|
||||
_readTask = null;
|
||||
await CleanupAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -133,9 +133,9 @@ internal static partial class TemporaryAIFunctionFactory
|
|||
/// <remarks>
|
||||
/// <para>
|
||||
/// Return values are serialized to <see cref="JsonElement"/> using <paramref name="options"/>'s
|
||||
/// <see cref="AIFunctionFactoryOptions.SerializerOptions"/>. Arguments that are not already of the expected type are
|
||||
/// <see cref="TemporaryAIFunctionFactoryOptions.SerializerOptions"/>. Arguments that are not already of the expected type are
|
||||
/// marshaled to the expected type via JSON and using <paramref name="options"/>'s
|
||||
/// <see cref="AIFunctionFactoryOptions.SerializerOptions"/>. If the argument is a <see cref="JsonElement"/>,
|
||||
/// <see cref="TemporaryAIFunctionFactoryOptions.SerializerOptions"/>. If the argument is a <see cref="JsonElement"/>,
|
||||
/// <see cref="JsonDocument"/>, or <see cref="JsonNode"/>, it is deserialized directly. If the argument is anything else unknown,
|
||||
/// it is round-tripped through JSON, serializing the object as JSON and then deserializing it to the expected type.
|
||||
/// </para>
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ internal sealed class TemporaryAIFunctionFactoryOptions
|
|||
/// <summary>Gets or sets a delegate used to determine the <see cref="object"/> returned by <see cref="AIFunction.InvokeAsync"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// By default, the return value of invoking the method wrapped into an <see cref="AIFunction"/> by <see cref="AIFunctionFactory"/>
|
||||
/// By default, the return value of invoking the method wrapped into an <see cref="AIFunction"/> by <see cref="TemporaryAIFunctionFactory"/>
|
||||
/// is then JSON serialized, with the resulting <see cref="JsonElement"/> returned from the <see cref="AIFunction.InvokeAsync"/> method.
|
||||
/// This default behavior is ideal for the common case where the result will be passed back to an AI service. However, if the caller
|
||||
/// requires more control over the result's marshaling, the <see cref="MarshalResult"/> property may be set to a delegate that is
|
||||
|
|
@ -82,7 +82,7 @@ internal sealed class TemporaryAIFunctionFactoryOptions
|
|||
/// <para>
|
||||
/// When set, the delegate is invoked even for <see langword="void"/>-returning methods, in which case it is invoked with
|
||||
/// a <see langword="null"/> argument. By default, <see langword="null"/> is returned from the <see cref="AIFunction.InvokeAsync"/>
|
||||
/// method for <see cref="AIFunction"/> instances produced by <see cref="AIFunctionFactory"/> to wrap
|
||||
/// method for <see cref="AIFunction"/> instances produced by <see cref="TemporaryAIFunctionFactory"/> to wrap
|
||||
/// <see langword="void"/>-returning methods).
|
||||
/// </para>
|
||||
/// <para>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using System.Diagnostics;
|
|||
using System.Diagnostics.Metrics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace ModelContextProtocol.Shared;
|
||||
|
||||
|
|
@ -20,15 +21,14 @@ namespace ModelContextProtocol.Shared;
|
|||
/// </summary>
|
||||
internal sealed class McpSession : IDisposable
|
||||
{
|
||||
private static readonly Histogram<double> s_clientSessionDuration = Diagnostics.Meter.CreateHistogram<double>(
|
||||
"mcp.client.session.duration", "s", "Measures the duration of a client session.", advice: Diagnostics.LongSecondsBucketBoundaries);
|
||||
private static readonly Histogram<double> s_serverSessionDuration = Diagnostics.Meter.CreateHistogram<double>(
|
||||
"mcp.server.session.duration", "s", "Measures the duration of a server session.", advice: Diagnostics.LongSecondsBucketBoundaries);
|
||||
|
||||
private static readonly Histogram<double> s_serverRequestDuration = Diagnostics.Meter.CreateHistogram<double>(
|
||||
"rpc.server.duration", "s", "Measures the duration of inbound RPC.", advice: Diagnostics.ShortSecondsBucketBoundaries);
|
||||
private static readonly Histogram<double> s_clientRequestDuration = Diagnostics.Meter.CreateHistogram<double>(
|
||||
"rpc.client.duration", "s", "Measures the duration of outbound RPC.", advice: Diagnostics.ShortSecondsBucketBoundaries);
|
||||
private static readonly Histogram<double> s_clientSessionDuration = Diagnostics.CreateDurationHistogram(
|
||||
"mcp.client.session.duration", "Measures the duration of a client session.", longBuckets: true);
|
||||
private static readonly Histogram<double> s_serverSessionDuration = Diagnostics.CreateDurationHistogram(
|
||||
"mcp.server.session.duration", "Measures the duration of a server session.", longBuckets: true);
|
||||
private static readonly Histogram<double> s_clientRequestDuration = Diagnostics.CreateDurationHistogram(
|
||||
"rpc.client.duration", "Measures the duration of outbound RPC.", longBuckets: false);
|
||||
private static readonly Histogram<double> s_serverRequestDuration = Diagnostics.CreateDurationHistogram(
|
||||
"rpc.server.duration", "Measures the duration of inbound RPC.", longBuckets: false);
|
||||
|
||||
private readonly bool _isServer;
|
||||
private readonly string _transportKind;
|
||||
|
|
@ -174,6 +174,14 @@ internal sealed class McpSession : IDisposable
|
|||
// Normal shutdown
|
||||
_logger.EndpointMessageProcessingCancelled(EndpointName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Fail any pending requests, as they'll never be satisfied.
|
||||
foreach (var entry in _pendingRequests)
|
||||
{
|
||||
entry.Value.TrySetException(new InvalidOperationException("The server shut down unexpectedly."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
|
||||
// NOTE:
|
||||
// This is a temporary workaround for lack of System.Text.Json's JsonStringEnumConverter<T>
|
||||
// 9.x support for JsonStringEnumMemberNameAttribute. Once all builds use the System.Text.Json 9.x
|
||||
// version, this whole file can be removed.
|
||||
|
||||
namespace System.Text.Json.Serialization;
|
||||
|
||||
internal sealed class CustomizableJsonStringEnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum> :
|
||||
JsonStringEnumConverter<TEnum> where TEnum : struct, Enum
|
||||
{
|
||||
#if !NET9_0_OR_GREATER
|
||||
public CustomizableJsonStringEnumConverter() :
|
||||
base(namingPolicy: ResolveNamingPolicy())
|
||||
{
|
||||
}
|
||||
|
||||
private static JsonNamingPolicy? ResolveNamingPolicy()
|
||||
{
|
||||
var map = typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static)
|
||||
.Select(f => (f.Name, AttributeName: f.GetCustomAttribute<JsonStringEnumMemberNameAttribute>()?.Name))
|
||||
.Where(pair => pair.AttributeName != null)
|
||||
.ToDictionary(pair => pair.Name, pair => pair.AttributeName);
|
||||
|
||||
return map.Count > 0 ? new EnumMemberNamingPolicy(map!) : null;
|
||||
}
|
||||
|
||||
private sealed class EnumMemberNamingPolicy(Dictionary<string, string> map) : JsonNamingPolicy
|
||||
{
|
||||
public override string ConvertName(string name) =>
|
||||
map.TryGetValue(name, out string? newName) ?
|
||||
newName :
|
||||
name;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !NET9_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Determines the string value that should be used when serializing an enum member.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
|
||||
internal sealed class JsonStringEnumMemberNameAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates new attribute instance with a specified enum member name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name to apply to the current enum member.</param>
|
||||
public JsonStringEnumMemberNameAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the enum member.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
}
|
||||
#endif
|
||||
|
|
@ -78,7 +78,6 @@ public static partial class McpJsonUtilities
|
|||
|
||||
// Keep in sync with CreateDefaultOptions above.
|
||||
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
|
||||
UseStringEnumConverter = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString)]
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>TestServer</AssemblyName>
|
||||
|
|
@ -10,8 +10,11 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>TestSseServer</AssemblyName>
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -377,10 +377,12 @@ public class Program
|
|||
{
|
||||
Console.WriteLine("Starting server...");
|
||||
|
||||
int port = args.Length > 0 && uint.TryParse(args[0], out var parsedPort) ? (int)parsedPort : 3001;
|
||||
|
||||
var builder = WebApplication.CreateSlimBuilder(args);
|
||||
builder.WebHost.ConfigureKestrel(options =>
|
||||
{
|
||||
options.ListenLocalhost(3001);
|
||||
options.ListenLocalhost(port);
|
||||
});
|
||||
|
||||
ConfigureSerilog(builder.Logging);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue