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;
/// 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();
///
/// Creates default options to use for MCP-related serialization.
///
/// The configured options.
[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 GetTypeInfo(this JsonSerializerOptions options) =>
(JsonTypeInfo)options.GetTypeInfo(typeof(T));
internal static JsonElement DefaultMcpToolSchema = ParseJsonElement("{\"type\":\"object\"}"u8);
internal static bool IsValidMcpToolSchema(JsonElement element)
{
if (element.ValueKind is not JsonValueKind.Object)
{
return false;
}
foreach (JsonProperty property in element.EnumerateObject())
{
if (property.NameEquals("type"))
{
if (property.Value.ValueKind is not JsonValueKind.String ||
!property.Value.ValueEquals("object"))
{
return false;
}
return true; // No need to check other properties
}
}
return false; // No type keyword found.
}
// 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(CallToolResponse))]
internal sealed partial class JsonContext : JsonSerializerContext;
private static JsonElement ParseJsonElement(ReadOnlySpan utf8Json)
{
Utf8JsonReader reader = new(utf8Json);
return JsonElement.ParseValue(ref reader);
}
}