csharp-sdk/src/ModelContextProtocol.Core/Protocol/ResourceContents.cs
Stephen Toub 2bcff315f8
Update content according to latest spec (#513)
* Update content according to latest spec

* Add Title to a bunch of types

* Add _meta to lots of types

* Address feedback, more cleanup, fix NAOT test failure
2025-06-16 19:39:41 -04:00

178 lines
5.6 KiB
C#

using System.ComponentModel;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace ModelContextProtocol.Protocol;
/// <summary>
/// Provides a base class representing contents of a resource in the Model Context Protocol.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="ResourceContents"/> 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.
/// </para>
/// <para>
/// This class is abstract and has two concrete implementations:
/// <list type="bullet">
/// <item><description><see cref="TextResourceContents"/> - For text-based resources</description></item>
/// <item><description><see cref="BlobResourceContents"/> - For binary data resources</description></item>
/// </list>
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for more details.
/// </para>
/// </remarks>
[JsonConverter(typeof(Converter))]
public abstract class ResourceContents
{
/// <summary>Prevent external derivations.</summary>
private protected ResourceContents()
{
}
/// <summary>
/// Gets or sets the URI of the resource.
/// </summary>
[JsonPropertyName("uri")]
public string Uri { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the MIME type of the resource content.
/// </summary>
[JsonPropertyName("mimeType")]
public string? MimeType { get; set; }
/// <summary>
/// Gets or sets metadata reserved by MCP for protocol-level metadata.
/// </summary>
/// <remarks>
/// Implementations must not make assumptions about its contents.
/// </remarks>
[JsonPropertyName("_meta")]
public JsonObject? Meta { get; set; }
/// <summary>
/// Provides a <see cref="JsonConverter"/> for <see cref="ResourceContents"/>.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public class Converter : JsonConverter<ResourceContents>
{
/// <inheritdoc/>
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;
string? blob = 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":
blob = reader.GetString();
break;
case "text":
text = reader.GetString();
break;
case "_meta":
meta = JsonSerializer.Deserialize(ref reader, McpJsonUtilities.JsonContext.Default.JsonObject);
break;
default:
break;
}
}
if (blob is not null)
{
return new BlobResourceContents
{
Uri = uri ?? string.Empty,
MimeType = mimeType,
Blob = blob,
Meta = meta,
};
}
if (text is not null)
{
return new TextResourceContents
{
Uri = uri ?? string.Empty,
MimeType = mimeType,
Text = text,
Meta = meta,
};
}
return null;
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, ResourceContents value, JsonSerializerOptions options)
{
if (value is null)
{
writer.WriteNullValue();
return;
}
writer.WriteStartObject();
writer.WriteString("uri", value.Uri);
writer.WriteString("mimeType", value.MimeType);
Debug.Assert(value is BlobResourceContents or TextResourceContents);
if (value is BlobResourceContents blobResource)
{
writer.WriteString("blob", blobResource.Blob);
}
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();
}
}
}