using System.Buffers;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace ModelContextProtocol.Protocol;
///
/// Provides a base class representing contents of a resource in the Model Context Protocol.
///
///
///
/// serves as the base class for different types of resources that can be
/// exchanged through the Model Context Protocol. Resources are identified by URIs and can contain
/// different types of data.
///
///
/// This class is abstract and has two concrete implementations:
///
/// - - For text-based resources
/// - - For binary data resources
///
///
///
/// See the schema for more details.
///
///
[JsonConverter(typeof(Converter))]
public abstract class ResourceContents
{
/// Prevent external derivations.
private protected ResourceContents()
{
}
///
/// Gets or sets the URI of the resource.
///
[JsonPropertyName("uri")]
[StringSyntax(StringSyntaxAttribute.Uri)]
public required string Uri { get; set; }
///
/// Gets or sets the MIME type of the resource content.
///
[JsonPropertyName("mimeType")]
public string? MimeType { get; set; }
///
/// Gets or sets metadata reserved by MCP for protocol-level metadata.
///
///
/// Implementations must not make assumptions about its contents.
///
[JsonPropertyName("_meta")]
public JsonObject? Meta { get; set; }
///
/// Provides a for .
///
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class Converter : JsonConverter
{
///
public override ResourceContents? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return null;
}
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
string? uri = null;
string? mimeType = null;
ReadOnlyMemory? blob = null;
ReadOnlyMemory? decodedBlob = null;
string? text = null;
JsonObject? meta = null;
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
{
if (reader.TokenType != JsonTokenType.PropertyName)
{
continue;
}
string? propertyName = reader.GetString();
bool success = reader.Read();
Debug.Assert(success, "STJ must have buffered the entire object for us.");
switch (propertyName)
{
case "uri":
uri = reader.GetString();
break;
case "mimeType":
mimeType = reader.GetString();
break;
case "blob":
if (!reader.ValueIsEscaped)
{
blob = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan.ToArray();
}
else
{
decodedBlob = reader.GetBytesFromBase64();
}
break;
case "text":
text = reader.GetString();
break;
case "_meta":
meta = JsonSerializer.Deserialize(ref reader, McpJsonUtilities.JsonContext.Default.JsonObject);
break;
default:
reader.Skip();
break;
}
}
if (decodedBlob is not null)
{
var blobResource = BlobResourceContents.FromBytes(decodedBlob.Value, uri ?? string.Empty, mimeType);
blobResource.Meta = meta;
return blobResource;
}
if (blob is not null)
{
return new BlobResourceContents
{
Uri = uri ?? string.Empty,
MimeType = mimeType,
Blob = blob.Value,
Meta = meta,
};
}
if (text is not null)
{
return new TextResourceContents
{
Uri = uri ?? string.Empty,
MimeType = mimeType,
Text = text,
Meta = meta,
};
}
return null;
}
///
public override void Write(Utf8JsonWriter writer, ResourceContents value, JsonSerializerOptions options)
{
if (value is null)
{
writer.WriteNullValue();
return;
}
writer.WriteStartObject();
writer.WriteString("uri", value.Uri);
if (value.MimeType is not null)
{
writer.WriteString("mimeType", value.MimeType);
}
Debug.Assert(value is BlobResourceContents or TextResourceContents);
if (value is BlobResourceContents blobResource)
{
writer.WriteString("blob", blobResource.Blob.Span);
}
else if (value is TextResourceContents textResource)
{
writer.WriteString("text", textResource.Text);
}
if (value.Meta is not null)
{
writer.WritePropertyName("_meta");
JsonSerializer.Serialize(writer, value.Meta, McpJsonUtilities.JsonContext.Default.JsonObject);
}
writer.WriteEndObject();
}
}
}