using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol;
#if !NET
using System.Runtime.InteropServices;
#endif
using System.Text.Json;
namespace ModelContextProtocol;
///
/// Provides extension methods for converting between Model Context Protocol (MCP) types and Microsoft.Extensions.AI types.
///
///
/// This class serves as an adapter layer between Model Context Protocol (MCP) types and the model types
/// from the Microsoft.Extensions.AI namespace.
///
public static class AIContentExtensions
{
///
/// Converts a to a object.
///
/// The prompt message to convert.
/// A object created from the prompt message.
///
/// This method transforms a protocol-specific from the Model Context Protocol
/// into a standard object that can be used with AI client libraries.
///
public static ChatMessage ToChatMessage(this PromptMessage promptMessage)
{
Throw.IfNull(promptMessage);
return new()
{
RawRepresentation = promptMessage,
Role = promptMessage.Role == Role.User ? ChatRole.User : ChatRole.Assistant,
Contents = [ToAIContent(promptMessage.Content)]
};
}
///
/// Converts a to a list of objects.
///
/// The prompt result containing messages to convert.
/// A list of objects created from the prompt messages.
///
/// This method transforms protocol-specific objects from a Model Context Protocol
/// prompt result into standard objects that can be used with AI client libraries.
///
public static IList ToChatMessages(this GetPromptResult promptResult)
{
Throw.IfNull(promptResult);
return promptResult.Messages.Select(m => m.ToChatMessage()).ToList();
}
///
/// Converts a to a list of objects.
///
/// The chat message to convert.
/// A list of objects created from the chat message's contents.
///
/// This method transforms standard objects used with AI client libraries into
/// protocol-specific objects for the Model Context Protocol system.
/// Only representable content items are processed.
///
public static IList ToPromptMessages(this ChatMessage chatMessage)
{
Throw.IfNull(chatMessage);
Role r = chatMessage.Role == ChatRole.User ? Role.User : Role.Assistant;
List messages = [];
foreach (var content in chatMessage.Contents)
{
if (content is TextContent or DataContent)
{
messages.Add(new PromptMessage { Role = r, Content = content.ToContent() });
}
}
return messages;
}
/// Creates a new from the content of a .
/// The to convert.
/// The created .
///
/// This method converts Model Context Protocol content types to the equivalent Microsoft.Extensions.AI
/// content types, enabling seamless integration between the protocol and AI client libraries.
///
public static AIContent ToAIContent(this Content content)
{
Throw.IfNull(content);
AIContent ac;
if (content is { Type: "image" or "audio", MimeType: not null, Data: not null })
{
ac = new DataContent(Convert.FromBase64String(content.Data), content.MimeType);
}
else if (content is { Type: "resource" } && content.Resource is { } resourceContents)
{
ac = resourceContents.ToAIContent();
}
else
{
ac = new TextContent(content.Text);
}
ac.RawRepresentation = content;
return ac;
}
/// Creates a new from the content of a .
/// The to convert.
/// The created .
///
/// This method converts Model Context Protocol resource types to the equivalent Microsoft.Extensions.AI
/// content types, enabling seamless integration between the protocol and AI client libraries.
///
public static AIContent ToAIContent(this ResourceContents content)
{
Throw.IfNull(content);
AIContent ac = content switch
{
BlobResourceContents blobResource => new DataContent(Convert.FromBase64String(blobResource.Blob), blobResource.MimeType ?? "application/octet-stream"),
TextResourceContents textResource => new TextContent(textResource.Text),
_ => throw new NotSupportedException($"Resource type '{content.GetType().Name}' is not supported.")
};
(ac.AdditionalProperties ??= [])["uri"] = content.Uri;
ac.RawRepresentation = content;
return ac;
}
/// Creates a list of from a sequence of .
/// The instances to convert.
/// The created instances.
///
///
/// This method converts a collection of Model Context Protocol content objects into a collection of
/// Microsoft.Extensions.AI content objects. It's useful when working with multiple content items, such as
/// when processing the contents of a message or response.
///
///
/// Each object is converted using ,
/// preserving the type-specific conversion logic for text, images, audio, and resources.
///
///
public static IList ToAIContents(this IEnumerable contents)
{
Throw.IfNull(contents);
return [.. contents.Select(ToAIContent)];
}
/// Creates a list of from a sequence of .
/// The instances to convert.
/// A list of objects created from the resource contents.
///
///
/// This method converts a collection of Model Context Protocol resource objects into a collection of
/// Microsoft.Extensions.AI content objects. It's useful when working with multiple resources, such as
/// when processing the contents of a .
///
///
/// Each object is converted using ,
/// preserving the type-specific conversion logic: text resources become objects and
/// binary resources become objects.
///
///
public static IList ToAIContents(this IEnumerable contents)
{
Throw.IfNull(contents);
return [.. contents.Select(ToAIContent)];
}
/// Extracts the data from a as a Base64 string.
internal static string GetBase64Data(this DataContent dataContent)
{
#if NET
return Convert.ToBase64String(dataContent.Data.Span);
#else
return MemoryMarshal.TryGetArray(dataContent.Data, out ArraySegment segment) ?
Convert.ToBase64String(segment.Array!, segment.Offset, segment.Count) :
Convert.ToBase64String(dataContent.Data.ToArray());
#endif
}
internal static Content ToContent(this AIContent content) =>
content switch
{
TextContent textContent => new()
{
Text = textContent.Text,
Type = "text",
},
DataContent dataContent => new()
{
Data = dataContent.GetBase64Data(),
MimeType = dataContent.MediaType,
Type =
dataContent.HasTopLevelMediaType("image") ? "image" :
dataContent.HasTopLevelMediaType("audio") ? "audio" :
"resource",
},
_ => new()
{
Text = JsonSerializer.Serialize(content, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object))),
Type = "text",
}
};
}