Add DebuggerDisplay attributes to Protocol types (#1068)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: Stephen Halter <halter73@gmail.com>
This commit is contained in:
Copilot 2025-12-05 15:09:46 -08:00 • committed by GitHub
commit 1b52ddacba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 175 additions and 0 deletions

View file

@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace ModelContextProtocol.Protocol; namespace ModelContextProtocol.Protocol;
@ -20,6 +21,7 @@ namespace ModelContextProtocol.Protocol;
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for more details. /// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for more details.
/// </para> /// </para>
/// </remarks> /// </remarks>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class BlobResourceContents : ResourceContents public sealed class BlobResourceContents : ResourceContents
{ {
/// <summary> /// <summary>
@ -27,4 +29,15 @@ public sealed class BlobResourceContents : ResourceContents
/// </summary> /// </summary>
[JsonPropertyName("blob")] [JsonPropertyName("blob")]
public required string Blob { get; set; } public required string Blob { get; set; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay
{
get
{
string lengthDisplay = DebuggerDisplayHelper.GetBase64LengthDisplay(Blob);
string mimeInfo = MimeType is not null ? $", MimeType = {MimeType}" : "";
return $"Uri = \"{Uri}\"{mimeInfo}, Length = {lengthDisplay}";
}
}
} }

View file

@ -356,6 +356,7 @@ public abstract class ContentBlock
} }
/// <summary>Represents text provided to or from an LLM.</summary> /// <summary>Represents text provided to or from an LLM.</summary>
[DebuggerDisplay("Text = \"{Text}\"")]
public sealed class TextContentBlock : ContentBlock public sealed class TextContentBlock : ContentBlock
{ {
/// <inheritdoc/> /// <inheritdoc/>
@ -366,9 +367,13 @@ public sealed class TextContentBlock : ContentBlock
/// </summary> /// </summary>
[JsonPropertyName("text")] [JsonPropertyName("text")]
public required string Text { get; set; } public required string Text { get; set; }
/// <inheritdoc/>
public override string ToString() => Text ?? "";
} }
/// <summary>Represents an image provided to or from an LLM.</summary> /// <summary>Represents an image provided to or from an LLM.</summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class ImageContentBlock : ContentBlock public sealed class ImageContentBlock : ContentBlock
{ {
/// <inheritdoc/> /// <inheritdoc/>
@ -388,9 +393,13 @@ public sealed class ImageContentBlock : ContentBlock
/// </remarks> /// </remarks>
[JsonPropertyName("mimeType")] [JsonPropertyName("mimeType")]
public required string MimeType { get; set; } public required string MimeType { get; set; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => $"MimeType = {MimeType}, Length = {DebuggerDisplayHelper.GetBase64LengthDisplay(Data)}";
} }
/// <summary>Represents audio provided to or from an LLM.</summary> /// <summary>Represents audio provided to or from an LLM.</summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class AudioContentBlock : ContentBlock public sealed class AudioContentBlock : ContentBlock
{ {
/// <inheritdoc/> /// <inheritdoc/>
@ -410,12 +419,16 @@ public sealed class AudioContentBlock : ContentBlock
/// </remarks> /// </remarks>
[JsonPropertyName("mimeType")] [JsonPropertyName("mimeType")]
public required string MimeType { get; set; } public required string MimeType { get; set; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => $"MimeType = {MimeType}, Length = {DebuggerDisplayHelper.GetBase64LengthDisplay(Data)}";
} }
/// <summary>Represents the contents of a resource, embedded into a prompt or tool call result.</summary> /// <summary>Represents the contents of a resource, embedded into a prompt or tool call result.</summary>
/// <remarks> /// <remarks>
/// It is up to the client how best to render embedded resources for the benefit of the LLM and/or the user. /// It is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.
/// </remarks> /// </remarks>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class EmbeddedResourceBlock : ContentBlock public sealed class EmbeddedResourceBlock : ContentBlock
{ {
/// <inheritdoc/> /// <inheritdoc/>
@ -433,12 +446,16 @@ public sealed class EmbeddedResourceBlock : ContentBlock
/// </remarks> /// </remarks>
[JsonPropertyName("resource")] [JsonPropertyName("resource")]
public required ResourceContents Resource { get; set; } public required ResourceContents Resource { get; set; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => $"Uri = \"{Resource.Uri}\"";
} }
/// <summary>Represents a resource that the server is capable of reading, included in a prompt or tool call result.</summary> /// <summary>Represents a resource that the server is capable of reading, included in a prompt or tool call result.</summary>
/// <remarks> /// <remarks>
/// Resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. /// Resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.
/// </remarks> /// </remarks>
[DebuggerDisplay("Name = {Name}, Uri = \"{Uri}\"")]
public sealed class ResourceLinkBlock : ContentBlock public sealed class ResourceLinkBlock : ContentBlock
{ {
/// <inheritdoc/> /// <inheritdoc/>
@ -503,6 +520,7 @@ public sealed class ResourceLinkBlock : ContentBlock
} }
/// <summary>Represents a request from the assistant to call a tool.</summary> /// <summary>Represents a request from the assistant to call a tool.</summary>
[DebuggerDisplay("Name = {Name}, Id = {Id}")]
public sealed class ToolUseContentBlock : ContentBlock public sealed class ToolUseContentBlock : ContentBlock
{ {
/// <inheritdoc/> /// <inheritdoc/>
@ -531,6 +549,7 @@ public sealed class ToolUseContentBlock : ContentBlock
} }
/// <summary>Represents the result of a tool use, provided by the user back to the assistant.</summary> /// <summary>Represents the result of a tool use, provided by the user back to the assistant.</summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class ToolResultContentBlock : ContentBlock public sealed class ToolResultContentBlock : ContentBlock
{ {
/// <inheritdoc/> /// <inheritdoc/>
@ -575,4 +594,37 @@ public sealed class ToolResultContentBlock : ContentBlock
/// </remarks> /// </remarks>
[JsonPropertyName("isError")] [JsonPropertyName("isError")]
public bool? IsError { get; set; } public bool? IsError { get; set; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay
{
get
{
if (IsError == true)
{
return $"ToolUseId = {ToolUseId}, IsError = true";
}
// Try to show the result content
if (Content.Count == 1 && Content[0] is TextContentBlock textBlock)
{
return $"ToolUseId = {ToolUseId}, Result = \"{textBlock.Text}\"";
}
if (StructuredContent.HasValue)
{
try
{
string json = StructuredContent.Value.GetRawText();
return $"ToolUseId = {ToolUseId}, Result = {json}";
}
catch
{
// Fall back to content count if GetRawText fails
}
}
return $"ToolUseId = {ToolUseId}, ContentCount = {Content.Count}";
}
}
} }

View file

@ -0,0 +1,30 @@
using System;
namespace ModelContextProtocol.Protocol;
/// <summary>
/// Internal helper methods for DebuggerDisplay implementations.
/// </summary>
internal static class DebuggerDisplayHelper
{
/// <summary>
/// Gets the decoded length of base64 data for debugger display.
/// </summary>
internal static string GetBase64LengthDisplay(string base64Data)
{
#if NET
if (System.Buffers.Text.Base64.IsValid(base64Data, out int decodedLength))
{
return $"{decodedLength} bytes";
}
#else
try
{
return $"{Convert.FromBase64String(base64Data).Length} bytes";
}
catch { }
#endif
return "invalid base64";
}
}

View file

@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace ModelContextProtocol.Protocol; namespace ModelContextProtocol.Protocol;
@ -28,6 +29,7 @@ namespace ModelContextProtocol.Protocol;
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details. /// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para> /// </para>
/// </remarks> /// </remarks>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class Icon public sealed class Icon
{ {
/// <summary> /// <summary>
@ -82,4 +84,14 @@ public sealed class Icon
/// </remarks> /// </remarks>
[JsonPropertyName("theme")] [JsonPropertyName("theme")]
public string? Theme { get; set; } public string? Theme { get; set; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay
{
get
{
string mimeInfo = MimeType is not null ? $", MimeType = {MimeType}" : "";
return $"Source = \"{Source}\"{mimeInfo}";
}
}
} }

View file

@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using ModelContextProtocol.Server; using ModelContextProtocol.Server;
@ -10,6 +11,7 @@ namespace ModelContextProtocol.Protocol;
/// <remarks> /// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details. /// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks> /// </remarks>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class Prompt : IBaseMetadata public sealed class Prompt : IBaseMetadata
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -75,4 +77,14 @@ public sealed class Prompt : IBaseMetadata
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
public McpServerPrompt? McpServerPrompt { get; set; } public McpServerPrompt? McpServerPrompt { get; set; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay
{
get
{
string desc = Description is not null ? $", Description = \"{Description}\"" : "";
return $"Name = {Name}{desc}";
}
}
} }

View file

@ -1,3 +1,4 @@
using System.Diagnostics;
using Microsoft.Extensions.AI; using Microsoft.Extensions.AI;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@ -26,6 +27,7 @@ namespace ModelContextProtocol.Protocol;
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details. /// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para> /// </para>
/// </remarks> /// </remarks>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class PromptMessage public sealed class PromptMessage
{ {
/// <summary> /// <summary>
@ -49,4 +51,19 @@ public sealed class PromptMessage
/// </remarks> /// </remarks>
[JsonPropertyName("role")] [JsonPropertyName("role")]
public required Role Role { get; set; } public required Role Role { get; set; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay
{
get
{
// Show actual text content if it's a TextContentBlock
if (Content is TextContentBlock textBlock)
{
return $"Role = {Role}, Text = \"{textBlock.Text}\"";
}
return $"Role = {Role}, ContentType = {Content.Type}";
}
}
} }

View file

@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@ -11,6 +12,7 @@ namespace ModelContextProtocol.Protocol;
/// <remarks> /// <remarks>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details. /// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks> /// </remarks>
[DebuggerDisplay("Name = {Name}, Uri = {Uri}")]
public sealed class Resource : IBaseMetadata public sealed class Resource : IBaseMetadata
{ {
/// <inheritdoc /> /// <inheritdoc />

View file

@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@ -26,6 +27,7 @@ namespace ModelContextProtocol.Protocol;
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details. /// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para> /// </para>
/// </remarks> /// </remarks>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class SamplingMessage public sealed class SamplingMessage
{ {
/// <summary> /// <summary>
@ -49,4 +51,22 @@ public sealed class SamplingMessage
/// </remarks> /// </remarks>
[JsonPropertyName("_meta")] [JsonPropertyName("_meta")]
public JsonObject? Meta { get; set; } public JsonObject? Meta { get; set; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay
{
get
{
// Show actual text content if it's a single TextContentBlock
if (Content.Count == 1 && Content[0] is TextContentBlock textBlock)
{
return $"Role = {Role}, Text = \"{textBlock.Text}\"";
}
string contentTypes = Content.Count == 1
? Content[0].Type
: $"{Content.Count} items";
return $"Role = {Role}, Content = {contentTypes}";
}
}
} }

View file

@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace ModelContextProtocol.Protocol; namespace ModelContextProtocol.Protocol;
@ -19,6 +20,7 @@ namespace ModelContextProtocol.Protocol;
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for more details. /// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for more details.
/// </para> /// </para>
/// </remarks> /// </remarks>
[DebuggerDisplay("Uri = \"{Uri}\", Text = \"{Text}\"")]
public sealed class TextResourceContents : ResourceContents public sealed class TextResourceContents : ResourceContents
{ {
/// <summary> /// <summary>
@ -26,4 +28,7 @@ public sealed class TextResourceContents : ResourceContents
/// </summary> /// </summary>
[JsonPropertyName("text")] [JsonPropertyName("text")]
public required string Text { get; set; } public required string Text { get; set; }
/// <inheritdoc/>
public override string ToString() => Text ?? "";
} }

View file

@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@ -8,6 +9,7 @@ namespace ModelContextProtocol.Protocol;
/// <summary> /// <summary>
/// Represents a tool that the server is capable of calling. /// Represents a tool that the server is capable of calling.
/// </summary> /// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class Tool : IBaseMetadata public sealed class Tool : IBaseMetadata
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -130,4 +132,14 @@ public sealed class Tool : IBaseMetadata
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
public McpServerTool? McpServerTool { get; set; } public McpServerTool? McpServerTool { get; set; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay
{
get
{
string desc = Description is not null ? $", Description = \"{Description}\"" : "";
return $"Name = {Name}{desc}";
}
}
} }