Make McpJsonUtilities public and remove IVT to sample (#5)
Also adds in the same encoder used by AIJsonUtilities.
This commit is contained in:
parent
555933a06e
commit
6b8c288029
13 changed files with 56 additions and 27 deletions
|
|
@ -45,7 +45,7 @@ public static class McpEndpointRouteBuilderExtensions
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var message = await context.Request.ReadFromJsonAsync<IJsonRpcMessage>(JsonSerializerOptionsExtensions.DefaultOptions, context.RequestAborted);
|
var message = await context.Request.ReadFromJsonAsync<IJsonRpcMessage>(McpJsonUtilities.DefaultOptions, context.RequestAborted);
|
||||||
if (message is null)
|
if (message is null)
|
||||||
{
|
{
|
||||||
await Results.BadRequest("No message in request body.").ExecuteAsync(context);
|
await Results.BadRequest("No message in request body.").ExecuteAsync(context);
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ public class SseServerStreamTransport(Stream sseResponseStream) : ITransport
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonSerializer.Serialize(GetUtf8JsonWriter(writer), item.Data, JsonSerializerOptionsExtensions.DefaultOptions);
|
JsonSerializer.Serialize(GetUtf8JsonWriter(writer), item.Data, McpJsonUtilities.DefaultOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The very first SSE event isn't really an IJsonRpcMessage, but there's no API to write a single item of a different type,
|
// The very first SSE event isn't really an IJsonRpcMessage, but there's no API to write a single item of a different type,
|
||||||
|
|
|
||||||
|
|
@ -139,8 +139,8 @@ internal sealed class McpClient : McpJsonRpcEndpoint, IMcpClient
|
||||||
|
|
||||||
// Store server information
|
// Store server information
|
||||||
_logger.ServerCapabilitiesReceived(EndpointName,
|
_logger.ServerCapabilitiesReceived(EndpointName,
|
||||||
capabilities: JsonSerializer.Serialize(initializeResponse.Capabilities, JsonSerializerOptionsExtensions.JsonContext.Default.ServerCapabilities),
|
capabilities: JsonSerializer.Serialize(initializeResponse.Capabilities, McpJsonUtilities.JsonContext.Default.ServerCapabilities),
|
||||||
serverInfo: JsonSerializer.Serialize(initializeResponse.ServerInfo, JsonSerializerOptionsExtensions.JsonContext.Default.Implementation));
|
serverInfo: JsonSerializer.Serialize(initializeResponse.ServerInfo, McpJsonUtilities.JsonContext.Default.Implementation));
|
||||||
|
|
||||||
ServerCapabilities = initializeResponse.Capabilities;
|
ServerCapabilities = initializeResponse.Capabilities;
|
||||||
ServerInfo = initializeResponse.ServerInfo;
|
ServerInfo = initializeResponse.ServerInfo;
|
||||||
|
|
|
||||||
|
|
@ -483,7 +483,7 @@ public static class McpClientExtensions
|
||||||
["description"] = tool.Description ?? string.Empty,
|
["description"] = tool.Description ?? string.Empty,
|
||||||
["properties"] = tool.InputSchema?.Properties ?? [],
|
["properties"] = tool.InputSchema?.Properties ?? [],
|
||||||
["required"] = tool.InputSchema?.Required ?? []
|
["required"] = tool.InputSchema?.Required ?? []
|
||||||
}, JsonSerializerOptionsExtensions.JsonContext.Default.DictionaryStringObject);
|
}, McpJsonUtilities.JsonContext.Default.DictionaryStringObject);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected async override Task<object?> InvokeCoreAsync(
|
protected async override Task<object?> InvokeCoreAsync(
|
||||||
|
|
@ -501,7 +501,7 @@ public static class McpClientExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
CallToolResponse result = await client.CallToolAsync(tool.Name, argDict, cancellationToken).ConfigureAwait(false);
|
CallToolResponse result = await client.CallToolAsync(tool.Name, argDict, cancellationToken).ConfigureAwait(false);
|
||||||
return JsonSerializer.SerializeToElement(result, JsonSerializerOptionsExtensions.JsonContext.Default.CallToolResponse);
|
return JsonSerializer.SerializeToElement(result, McpJsonUtilities.JsonContext.Default.CallToolResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +96,7 @@ public static partial class McpServerBuilderExtensions
|
||||||
{
|
{
|
||||||
Name = function.Name,
|
Name = function.Name,
|
||||||
Description = function.Description,
|
Description = function.Description,
|
||||||
InputSchema = JsonSerializer.Deserialize(function.JsonSchema, JsonSerializerOptionsExtensions.JsonContext.Default.JsonSchema),
|
InputSchema = JsonSerializer.Deserialize(function.JsonSchema, McpJsonUtilities.JsonContext.Default.JsonSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
callbacks.Add(function.Name, async (request, cancellationToken) =>
|
callbacks.Add(function.Name, async (request, cancellationToken) =>
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,11 @@
|
||||||
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">
|
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">
|
||||||
<IsAotCompatible>true</IsAotCompatible>
|
<IsAotCompatible>true</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<InternalsVisibleTo Include="ModelContextProtocol.Tests" />
|
<InternalsVisibleTo Include="ModelContextProtocol.Tests" />
|
||||||
<InternalsVisibleTo Include="AspNetCoreSseServer" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.AI.Abstractions"/>
|
<PackageReference Include="Microsoft.Extensions.AI.Abstractions"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.AI" />
|
<PackageReference Include="Microsoft.Extensions.AI" />
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ public sealed class HttpListenerSseServerTransport : TransportBase, IServerTrans
|
||||||
{
|
{
|
||||||
_serverName = serverName;
|
_serverName = serverName;
|
||||||
_logger = loggerFactory.CreateLogger<HttpListenerSseServerTransport>();
|
_logger = loggerFactory.CreateLogger<HttpListenerSseServerTransport>();
|
||||||
_jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions;
|
_jsonOptions = McpJsonUtilities.DefaultOptions;
|
||||||
_httpServerProvider = new HttpListenerServerProvider(port);
|
_httpServerProvider = new HttpListenerServerProvider(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_connectionCts = new CancellationTokenSource();
|
_connectionCts = new CancellationTokenSource();
|
||||||
_logger = (ILogger?)loggerFactory?.CreateLogger<SseClientTransport>() ?? NullLogger.Instance;
|
_logger = (ILogger?)loggerFactory?.CreateLogger<SseClientTransport>() ?? NullLogger.Instance;
|
||||||
_jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions;
|
_jsonOptions = McpJsonUtilities.DefaultOptions;
|
||||||
_connectionEstablished = new TaskCompletionSource<bool>();
|
_connectionEstablished = new TaskCompletionSource<bool>();
|
||||||
_ownsHttpClient = ownsHttpClient;
|
_ownsHttpClient = ownsHttpClient;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
|
||||||
_options = options;
|
_options = options;
|
||||||
_serverConfig = serverConfig;
|
_serverConfig = serverConfig;
|
||||||
_logger = (ILogger?)loggerFactory?.CreateLogger<StdioClientTransport>() ?? NullLogger.Instance;
|
_logger = (ILogger?)loggerFactory?.CreateLogger<StdioClientTransport>() ?? NullLogger.Instance;
|
||||||
_jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions;
|
_jsonOptions = McpJsonUtilities.DefaultOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
|
||||||
private readonly string _serverName;
|
private readonly string _serverName;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
private readonly JsonSerializerOptions _jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions;
|
private readonly JsonSerializerOptions _jsonOptions = McpJsonUtilities.DefaultOptions;
|
||||||
private readonly TextReader _stdin = Console.In;
|
private readonly TextReader _stdin = Console.In;
|
||||||
private readonly TextWriter _stdout = Console.Out;
|
private readonly TextWriter _stdout = Console.Out;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
|
||||||
_pendingRequests = new();
|
_pendingRequests = new();
|
||||||
_notificationHandlers = new();
|
_notificationHandlers = new();
|
||||||
_nextRequestId = 1;
|
_nextRequestId = 1;
|
||||||
_jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions;
|
_jsonOptions = McpJsonUtilities.DefaultOptions;
|
||||||
_logger = (ILogger?)loggerFactory?.CreateLogger<McpClient>() ?? NullLogger.Instance;
|
_logger = (ILogger?)loggerFactory?.CreateLogger<McpClient>() ?? NullLogger.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
102
src/ModelContextProtocol/Utils/Json/McpJsonUtilities.cs
Normal file
102
src/ModelContextProtocol/Utils/Json/McpJsonUtilities.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
|
using ModelContextProtocol.Protocol.Types;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
|
||||||
|
namespace ModelContextProtocol.Utils.Json;
|
||||||
|
|
||||||
|
/// <summary>Provides a collection of utility methods for working with JSON data in the context of MCP.</summary>
|
||||||
|
public static partial class McpJsonUtilities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="JsonSerializerOptions"/> singleton used as the default in JSON serialization operations.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// For Native AOT or applications disabling <see cref="JsonSerializer.IsReflectionEnabledByDefault"/>, this instance
|
||||||
|
/// includes source generated contracts for all common exchange types contained in the ModelContextProtocol library.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// It additionally turns on the following settings:
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Enables string-based enum serialization as implemented by <see cref="JsonStringEnumConverter"/>.</item>
|
||||||
|
/// <item>Enables <see cref="JsonIgnoreCondition.WhenWritingNull"/> as the default ignore condition for properties.</item>
|
||||||
|
/// <item>Enables <see cref="JsonNumberHandling.AllowReadingFromString"/> as the default number handling for number types.</item>
|
||||||
|
/// <item>
|
||||||
|
/// Enables <see cref="JavaScriptEncoder.UnsafeRelaxedJsonEscaping"/> when escaping JSON strings.
|
||||||
|
/// Consuming applications must ensure that JSON outputs are adequately escaped before embedding in other document formats,
|
||||||
|
/// such as HTML and XML.
|
||||||
|
/// </item>
|
||||||
|
/// </list>
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public static JsonSerializerOptions DefaultOptions { get; } = CreateDefaultOptions();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates default options to use for MCP-related serialization.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The configured options.</returns>
|
||||||
|
[UnconditionalSuppressMessage("AotAnalysis", "IL3050", Justification = "DefaultJsonTypeInfoResolver is only used when reflection-based serialization is enabled")]
|
||||||
|
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "DefaultJsonTypeInfoResolver is only used when reflection-based serialization is enabled")]
|
||||||
|
private static JsonSerializerOptions CreateDefaultOptions()
|
||||||
|
{
|
||||||
|
// If reflection-based serialization is enabled by default, use it, as it's the most permissive in terms of what it can serialize,
|
||||||
|
// and we want to be flexible in terms of what can be put into the various collections in the object model.
|
||||||
|
// Otherwise, use the source-generated options to enable trimming and Native AOT.
|
||||||
|
JsonSerializerOptions options;
|
||||||
|
|
||||||
|
if (JsonSerializer.IsReflectionEnabledByDefault)
|
||||||
|
{
|
||||||
|
// Keep in sync with the JsonSourceGenerationOptions attribute on JsonContext below.
|
||||||
|
options = new(JsonSerializerDefaults.Web)
|
||||||
|
{
|
||||||
|
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
|
||||||
|
Converters = { new JsonStringEnumConverter() },
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
|
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
options = new(JsonContext.Default.Options)
|
||||||
|
{
|
||||||
|
// Compile-time encoder setting not yet available
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
options.MakeReadOnly();
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static JsonTypeInfo<T> GetTypeInfo<T>(this JsonSerializerOptions options) =>
|
||||||
|
(JsonTypeInfo<T>)options.GetTypeInfo(typeof(T));
|
||||||
|
|
||||||
|
// Keep in sync with CreateDefaultOptions above.
|
||||||
|
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
|
||||||
|
UseStringEnumConverter = true,
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
|
NumberHandling = JsonNumberHandling.AllowReadingFromString)]
|
||||||
|
[JsonSerializable(typeof(JsonDocument))]
|
||||||
|
[JsonSerializable(typeof(JsonElement))]
|
||||||
|
[JsonSerializable(typeof(JsonNode))]
|
||||||
|
[JsonSerializable(typeof(IJsonRpcMessage))]
|
||||||
|
[JsonSerializable(typeof(JsonRpcRequest))]
|
||||||
|
[JsonSerializable(typeof(JsonRpcNotification))]
|
||||||
|
[JsonSerializable(typeof(JsonRpcResponse))]
|
||||||
|
[JsonSerializable(typeof(JsonRpcError))]
|
||||||
|
[JsonSerializable(typeof(ServerCapabilities))]
|
||||||
|
[JsonSerializable(typeof(ClientCapabilities))]
|
||||||
|
[JsonSerializable(typeof(Implementation))]
|
||||||
|
[JsonSerializable(typeof(CreateMessageResult))]
|
||||||
|
[JsonSerializable(typeof(ListRootsResult))]
|
||||||
|
[JsonSerializable(typeof(InitializeResult))]
|
||||||
|
[JsonSerializable(typeof(JsonSchema))]
|
||||||
|
[JsonSerializable(typeof(CallToolResponse))]
|
||||||
|
internal sealed partial class JsonContext : JsonSerializerContext;
|
||||||
|
}
|
||||||
|
|
@ -78,7 +78,7 @@ public class StdioServerTransportTests
|
||||||
await transport.SendMessageAsync(message);
|
await transport.SendMessageAsync(message);
|
||||||
|
|
||||||
var result = output.ToString()?.Trim();
|
var result = output.ToString()?.Trim();
|
||||||
var expected = JsonSerializer.Serialize(message, JsonSerializerOptionsExtensions.DefaultOptions);
|
var expected = JsonSerializer.Serialize(message, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
Assert.Equal(expected, result);
|
Assert.Equal(expected, result);
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +113,7 @@ public class StdioServerTransportTests
|
||||||
public async Task ReadMessagesAsync_Should_Read_Messages()
|
public async Task ReadMessagesAsync_Should_Read_Messages()
|
||||||
{
|
{
|
||||||
var message = new JsonRpcRequest { Method = "test", Id = RequestId.FromNumber(44) };
|
var message = new JsonRpcRequest { Method = "test", Id = RequestId.FromNumber(44) };
|
||||||
var json = JsonSerializer.Serialize(message, JsonSerializerOptionsExtensions.DefaultOptions);
|
var json = JsonSerializer.Serialize(message, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
TextReader oldIn = Console.In;
|
TextReader oldIn = Console.In;
|
||||||
TextWriter oldOut = Console.Out;
|
TextWriter oldOut = Console.Out;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue