diff --git a/samples/AspNetCoreSseServer/McpEndpointRouteBuilderExtensions.cs b/samples/AspNetCoreSseServer/McpEndpointRouteBuilderExtensions.cs index 6329d618..ce949d7d 100644 --- a/samples/AspNetCoreSseServer/McpEndpointRouteBuilderExtensions.cs +++ b/samples/AspNetCoreSseServer/McpEndpointRouteBuilderExtensions.cs @@ -45,7 +45,7 @@ public static IEndpointConventionBuilder MapMcpSse(this IEndpointRouteBuilder en return; } - var message = await context.Request.ReadFromJsonAsync(JsonSerializerOptionsExtensions.DefaultOptions, context.RequestAborted); + var message = await context.Request.ReadFromJsonAsync(McpJsonUtilities.DefaultOptions, context.RequestAborted); if (message is null) { await Results.BadRequest("No message in request body.").ExecuteAsync(context); diff --git a/samples/AspNetCoreSseServer/SseServerStreamTransport.cs b/samples/AspNetCoreSseServer/SseServerStreamTransport.cs index a6447619..52fc688c 100644 --- a/samples/AspNetCoreSseServer/SseServerStreamTransport.cs +++ b/samples/AspNetCoreSseServer/SseServerStreamTransport.cs @@ -28,7 +28,7 @@ void WriteJsonRpcMessageToBuffer(SseItem item, IBufferWriter protected async override Task InvokeCoreAsync( @@ -501,7 +501,7 @@ private sealed class McpAIFunction(IMcpClient client, Tool tool) : AIFunction } 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); } } } \ No newline at end of file diff --git a/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.Tools.cs b/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.Tools.cs index e1483190..af1de386 100644 --- a/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.Tools.cs +++ b/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.Tools.cs @@ -96,7 +96,7 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params { Name = function.Name, 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) => diff --git a/src/ModelContextProtocol/ModelContextProtocol.csproj b/src/ModelContextProtocol/ModelContextProtocol.csproj index f1b5f6c5..9b9a89ee 100644 --- a/src/ModelContextProtocol/ModelContextProtocol.csproj +++ b/src/ModelContextProtocol/ModelContextProtocol.csproj @@ -12,12 +12,11 @@ true - + - - + diff --git a/src/ModelContextProtocol/Protocol/Transport/HttpListenerSseServerTransport.cs b/src/ModelContextProtocol/Protocol/Transport/HttpListenerSseServerTransport.cs index 9a756d96..f385476c 100644 --- a/src/ModelContextProtocol/Protocol/Transport/HttpListenerSseServerTransport.cs +++ b/src/ModelContextProtocol/Protocol/Transport/HttpListenerSseServerTransport.cs @@ -42,7 +42,7 @@ public HttpListenerSseServerTransport(string serverName, int port, ILoggerFactor { _serverName = serverName; _logger = loggerFactory.CreateLogger(); - _jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions; + _jsonOptions = McpJsonUtilities.DefaultOptions; _httpServerProvider = new HttpListenerServerProvider(port); } diff --git a/src/ModelContextProtocol/Protocol/Transport/SseClientTransport.cs b/src/ModelContextProtocol/Protocol/Transport/SseClientTransport.cs index 577c5639..5d72d0c0 100644 --- a/src/ModelContextProtocol/Protocol/Transport/SseClientTransport.cs +++ b/src/ModelContextProtocol/Protocol/Transport/SseClientTransport.cs @@ -65,7 +65,7 @@ public SseClientTransport(SseClientTransportOptions transportOptions, McpServerC _httpClient = httpClient; _connectionCts = new CancellationTokenSource(); _logger = (ILogger?)loggerFactory?.CreateLogger() ?? NullLogger.Instance; - _jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions; + _jsonOptions = McpJsonUtilities.DefaultOptions; _connectionEstablished = new TaskCompletionSource(); _ownsHttpClient = ownsHttpClient; } diff --git a/src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs b/src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs index f9e13a61..d8986de6 100644 --- a/src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs +++ b/src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs @@ -43,7 +43,7 @@ public StdioClientTransport(StdioClientTransportOptions options, McpServerConfig _options = options; _serverConfig = serverConfig; _logger = (ILogger?)loggerFactory?.CreateLogger() ?? NullLogger.Instance; - _jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions; + _jsonOptions = McpJsonUtilities.DefaultOptions; } /// diff --git a/src/ModelContextProtocol/Protocol/Transport/StdioServerTransport.cs b/src/ModelContextProtocol/Protocol/Transport/StdioServerTransport.cs index 98252d21..2e816345 100644 --- a/src/ModelContextProtocol/Protocol/Transport/StdioServerTransport.cs +++ b/src/ModelContextProtocol/Protocol/Transport/StdioServerTransport.cs @@ -21,7 +21,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport private readonly string _serverName; private readonly ILogger _logger; - private readonly JsonSerializerOptions _jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions; + private readonly JsonSerializerOptions _jsonOptions = McpJsonUtilities.DefaultOptions; private readonly TextReader _stdin = Console.In; private readonly TextWriter _stdout = Console.Out; diff --git a/src/ModelContextProtocol/Shared/McpJsonRpcEndpoint.cs b/src/ModelContextProtocol/Shared/McpJsonRpcEndpoint.cs index 5f24d818..c8fc983f 100644 --- a/src/ModelContextProtocol/Shared/McpJsonRpcEndpoint.cs +++ b/src/ModelContextProtocol/Shared/McpJsonRpcEndpoint.cs @@ -46,7 +46,7 @@ protected McpJsonRpcEndpoint(ITransport transport, ILoggerFactory? loggerFactory _pendingRequests = new(); _notificationHandlers = new(); _nextRequestId = 1; - _jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions; + _jsonOptions = McpJsonUtilities.DefaultOptions; _logger = (ILogger?)loggerFactory?.CreateLogger() ?? NullLogger.Instance; } diff --git a/src/ModelContextProtocol/Utils/Json/JsonSerializerOptionsExtensions.cs b/src/ModelContextProtocol/Utils/Json/McpJsonUtilities.cs similarity index 60% rename from src/ModelContextProtocol/Utils/Json/JsonSerializerOptionsExtensions.cs rename to src/ModelContextProtocol/Utils/Json/McpJsonUtilities.cs index 0559856a..3c4837ee 100644 --- a/src/ModelContextProtocol/Utils/Json/JsonSerializerOptionsExtensions.cs +++ b/src/ModelContextProtocol/Utils/Json/McpJsonUtilities.cs @@ -1,6 +1,7 @@ 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; @@ -8,11 +9,31 @@ namespace ModelContextProtocol.Utils.Json; -/// -/// Extensions for configuring System.Text.Json serialization options for MCP. -/// -internal static partial class JsonSerializerOptionsExtensions +/// Provides a collection of utility methods for working with JSON data in the context of MCP. +public static partial class McpJsonUtilities { + /// + /// Gets the singleton used as the default in JSON serialization operations. + /// + /// + /// + /// For Native AOT or applications disabling , this instance + /// includes source generated contracts for all common exchange types contained in the ModelContextProtocol library. + /// + /// + /// It additionally turns on the following settings: + /// + /// Enables string-based enum serialization as implemented by . + /// Enables as the default ignore condition for properties. + /// Enables as the default number handling for number types. + /// + /// Enables 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. + /// + /// + /// + /// public static JsonSerializerOptions DefaultOptions { get; } = CreateDefaultOptions(); /// @@ -26,23 +47,31 @@ 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. - JsonSerializerOptions options = new(JsonSerializerDefaults.Web) + options = new(JsonSerializerDefaults.Web) { TypeInfoResolver = new DefaultJsonTypeInfoResolver(), Converters = { new JsonStringEnumConverter() }, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - NumberHandling = JsonNumberHandling.AllowReadingFromString + 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; } - return JsonContext.Default.Options; + options.MakeReadOnly(); + return options; } internal static JsonTypeInfo GetTypeInfo(this JsonSerializerOptions options) => @@ -62,6 +91,7 @@ internal static JsonTypeInfo GetTypeInfo(this JsonSerializerOptions option [JsonSerializable(typeof(JsonRpcResponse))] [JsonSerializable(typeof(JsonRpcError))] [JsonSerializable(typeof(ServerCapabilities))] + [JsonSerializable(typeof(ClientCapabilities))] [JsonSerializable(typeof(Implementation))] [JsonSerializable(typeof(CreateMessageResult))] [JsonSerializable(typeof(ListRootsResult))] diff --git a/tests/ModelContextProtocol.Tests/Transport/StdioServerTransportTests.cs b/tests/ModelContextProtocol.Tests/Transport/StdioServerTransportTests.cs index 44f60cd7..76efb6d7 100644 --- a/tests/ModelContextProtocol.Tests/Transport/StdioServerTransportTests.cs +++ b/tests/ModelContextProtocol.Tests/Transport/StdioServerTransportTests.cs @@ -78,7 +78,7 @@ public async Task SendMessageAsync_Should_Send_Message() await transport.SendMessageAsync(message); var result = output.ToString()?.Trim(); - var expected = JsonSerializer.Serialize(message, JsonSerializerOptionsExtensions.DefaultOptions); + var expected = JsonSerializer.Serialize(message, McpJsonUtilities.DefaultOptions); Assert.Equal(expected, result); } @@ -113,7 +113,7 @@ public async Task DisposeAsync_Should_Dispose_Resources() public async Task ReadMessagesAsync_Should_Read_Messages() { 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; TextWriter oldOut = Console.Out;