Skip to content

Commit 7fb9021

Browse files
committed
Update content according to latest spec
1 parent 1e8fb04 commit 7fb9021

File tree

52 files changed

+726
-512
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+726
-512
lines changed

samples/EverythingServer/Tools/AnnotatedMessageTool.cs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,22 @@ public enum MessageType
1515
}
1616

1717
[McpServerTool(Name = "annotatedMessage"), Description("Generates an annotated message")]
18-
public static IEnumerable<Content> AnnotatedMessage(MessageType messageType, bool includeImage = true)
18+
public static IEnumerable<ContentBlock> AnnotatedMessage(MessageType messageType, bool includeImage = true)
1919
{
20-
List<Content> contents = messageType switch
20+
List<ContentBlock> contents = messageType switch
2121
{
22-
MessageType.Error => [new()
22+
MessageType.Error => [new TextContentBlock()
2323
{
24-
Type = "text",
2524
Text = "Error: Operation failed",
2625
Annotations = new() { Audience = [Role.User, Role.Assistant], Priority = 1.0f }
2726
}],
28-
MessageType.Success => [new()
27+
MessageType.Success => [new TextContentBlock()
2928
{
30-
Type = "text",
3129
Text = "Operation completed successfully",
3230
Annotations = new() { Audience = [Role.User], Priority = 0.7f }
3331
}],
34-
MessageType.Debug => [new()
32+
MessageType.Debug => [new TextContentBlock()
3533
{
36-
Type = "text",
3734
Text = "Debug: Cache hit ratio 0.95, latency 150ms",
3835
Annotations = new() { Audience = [Role.Assistant], Priority = 0.3f }
3936
}],
@@ -42,9 +39,8 @@ public static IEnumerable<Content> AnnotatedMessage(MessageType messageType, boo
4239

4340
if (includeImage)
4441
{
45-
contents.Add(new()
42+
contents.Add(new ImageContentBlock()
4643
{
47-
Type = "image",
4844
Data = TinyImageTool.MCP_TINY_IMAGE.Split(",").Last(),
4945
MimeType = "image/png",
5046
Annotations = new() { Audience = [Role.User], Priority = 0.5f }

samples/EverythingServer/Tools/SampleLlmTool.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public static async Task<string> SampleLLM(
1717
var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens);
1818
var sampleResult = await server.SampleAsync(samplingParams, cancellationToken);
1919

20-
return $"LLM sampling result: {sampleResult.Content.Text}";
20+
return $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}";
2121
}
2222

2323
private static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100)
@@ -27,11 +27,7 @@ private static CreateMessageRequestParams CreateRequestSamplingParams(string con
2727
Messages = [new SamplingMessage()
2828
{
2929
Role = Role.User,
30-
Content = new Content()
31-
{
32-
Type = "text",
33-
Text = $"Resource {uri} context: {context}"
34-
}
30+
Content = new TextContentBlock() { Text = $"Resource {uri} context: {context}" },
3531
}],
3632
SystemPrompt = "You are a helpful test server.",
3733
MaxTokens = maxTokens,

samples/TestServerWithHosting/Tools/SampleLlmTool.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public static async Task<string> SampleLLM(
2020
var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens);
2121
var sampleResult = await thisServer.SampleAsync(samplingParams, cancellationToken);
2222

23-
return $"LLM sampling result: {sampleResult.Content.Text}";
23+
return $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}";
2424
}
2525

2626
private static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100)
@@ -30,11 +30,7 @@ private static CreateMessageRequestParams CreateRequestSamplingParams(string con
3030
Messages = [new SamplingMessage()
3131
{
3232
Role = Role.User,
33-
Content = new Content()
34-
{
35-
Type = "text",
36-
Text = $"Resource {uri} context: {context}"
37-
}
33+
Content = new TextContentBlock() { Text = $"Resource {uri} context: {context}" },
3834
}],
3935
SystemPrompt = "You are a helpful test server.",
4036
MaxTokens = maxTokens,

src/Common/Polyfills/System/Diagnostics/CodeAnalysis/RequiresUnreferencedCode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace System.Diagnostics.CodeAnalysis;
55

66
/// <summary>
77
/// Indicates that the specified method requires dynamic access to code that is not referenced
8-
/// statically, for example through <see cref="System.Reflection"/>.
8+
/// statically, for example through <see cref="Reflection"/>.
99
/// </summary>
1010
/// <remarks>
1111
/// This allows tools to understand which methods are unsafe to call when removing unreferenced

src/ModelContextProtocol.Core/AIContentExtensions.cs

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ public static ChatMessage ToChatMessage(this PromptMessage promptMessage)
2929
{
3030
Throw.IfNull(promptMessage);
3131

32+
AIContent? content = ToAIContent(promptMessage.Content);
33+
3234
return new()
3335
{
3436
RawRepresentation = promptMessage,
3537
Role = promptMessage.Role == Role.User ? ChatRole.User : ChatRole.Assistant,
36-
Contents = [ToAIContent(promptMessage.Content)]
38+
Contents = content is not null ? [content] : [],
3739
};
3840
}
3941

@@ -81,33 +83,33 @@ public static IList<PromptMessage> ToPromptMessages(this ChatMessage chatMessage
8183
return messages;
8284
}
8385

84-
/// <summary>Creates a new <see cref="AIContent"/> from the content of a <see cref="Content"/>.</summary>
85-
/// <param name="content">The <see cref="Content"/> to convert.</param>
86-
/// <returns>The created <see cref="AIContent"/>.</returns>
86+
/// <summary>Creates a new <see cref="AIContent"/> from the content of a <see cref="ContentBlock"/>.</summary>
87+
/// <param name="content">The <see cref="ContentBlock"/> to convert.</param>
88+
/// <returns>
89+
/// The created <see cref="AIContent"/>. If the content can't be converted (such as when it's a resource link), <see langword="null"/> is returned.
90+
/// </returns>
8791
/// <remarks>
8892
/// This method converts Model Context Protocol content types to the equivalent Microsoft.Extensions.AI
8993
/// content types, enabling seamless integration between the protocol and AI client libraries.
9094
/// </remarks>
91-
public static AIContent ToAIContent(this Content content)
95+
public static AIContent? ToAIContent(this ContentBlock content)
9296
{
9397
Throw.IfNull(content);
9498

95-
AIContent ac;
96-
if (content is { Type: "image" or "audio", MimeType: not null, Data: not null })
99+
AIContent? ac = content switch
97100
{
98-
ac = new DataContent(Convert.FromBase64String(content.Data), content.MimeType);
99-
}
100-
else if (content is { Type: "resource" } && content.Resource is { } resourceContents)
101-
{
102-
ac = resourceContents.ToAIContent();
103-
}
104-
else
101+
TextContentBlock textContent => new TextContent(textContent.Text),
102+
ImageContentBlock imageContent => new DataContent(Convert.FromBase64String(imageContent.Data), imageContent.MimeType),
103+
AudioContentBlock audioContent => new DataContent(Convert.FromBase64String(audioContent.Data), audioContent.MimeType),
104+
EmbeddedResourceBlock resourceContent => resourceContent.Resource.ToAIContent(),
105+
_ => null,
106+
};
107+
108+
if (ac is not null)
105109
{
106-
ac = new TextContent(content.Text);
110+
ac.RawRepresentation = content;
107111
}
108112

109-
ac.RawRepresentation = content;
110-
111113
return ac;
112114
}
113115

@@ -135,8 +137,8 @@ public static AIContent ToAIContent(this ResourceContents content)
135137
return ac;
136138
}
137139

138-
/// <summary>Creates a list of <see cref="AIContent"/> from a sequence of <see cref="Content"/>.</summary>
139-
/// <param name="contents">The <see cref="Content"/> instances to convert.</param>
140+
/// <summary>Creates a list of <see cref="AIContent"/> from a sequence of <see cref="ContentBlock"/>.</summary>
141+
/// <param name="contents">The <see cref="ContentBlock"/> instances to convert.</param>
140142
/// <returns>The created <see cref="AIContent"/> instances.</returns>
141143
/// <remarks>
142144
/// <para>
@@ -145,15 +147,15 @@ public static AIContent ToAIContent(this ResourceContents content)
145147
/// when processing the contents of a message or response.
146148
/// </para>
147149
/// <para>
148-
/// Each <see cref="Content"/> object is converted using <see cref="ToAIContent(Content)"/>,
150+
/// Each <see cref="ContentBlock"/> object is converted using <see cref="ToAIContent(ContentBlock)"/>,
149151
/// preserving the type-specific conversion logic for text, images, audio, and resources.
150152
/// </para>
151153
/// </remarks>
152-
public static IList<AIContent> ToAIContents(this IEnumerable<Content> contents)
154+
public static IList<AIContent> ToAIContents(this IEnumerable<ContentBlock> contents)
153155
{
154156
Throw.IfNull(contents);
155157

156-
return [.. contents.Select(ToAIContent)];
158+
return [.. contents.Select(ToAIContent).OfType<AIContent>()];
157159
}
158160

159161
/// <summary>Creates a list of <see cref="AIContent"/> from a sequence of <see cref="ResourceContents"/>.</summary>
@@ -167,7 +169,7 @@ public static IList<AIContent> ToAIContents(this IEnumerable<Content> contents)
167169
/// </para>
168170
/// <para>
169171
/// Each <see cref="ResourceContents"/> object is converted using <see cref="ToAIContent(ResourceContents)"/>,
170-
/// preserving the type-specific conversion logic: text resources become <see cref="TextContent"/> objects and
172+
/// preserving the type-specific conversion logic: text resources become <see cref="TextContentBlock"/> objects and
171173
/// binary resources become <see cref="DataContent"/> objects.
172174
/// </para>
173175
/// </remarks>
@@ -178,29 +180,38 @@ public static IList<AIContent> ToAIContents(this IEnumerable<ResourceContents> c
178180
return [.. contents.Select(ToAIContent)];
179181
}
180182

181-
internal static Content ToContent(this AIContent content) =>
183+
internal static ContentBlock ToContent(this AIContent content) =>
182184
content switch
183185
{
184-
TextContent textContent => new()
186+
TextContent textContent => new TextContentBlock()
185187
{
186188
Text = textContent.Text,
187-
Type = "text",
188189
},
189190

190-
DataContent dataContent => new()
191+
DataContent dataContent when dataContent.HasTopLevelMediaType("image") => new ImageContentBlock()
191192
{
192193
Data = dataContent.Base64Data.ToString(),
193194
MimeType = dataContent.MediaType,
194-
Type =
195-
dataContent.HasTopLevelMediaType("image") ? "image" :
196-
dataContent.HasTopLevelMediaType("audio") ? "audio" :
197-
"resource",
198195
},
199-
200-
_ => new()
196+
197+
DataContent dataContent when dataContent.HasTopLevelMediaType("audio") => new AudioContentBlock()
198+
{
199+
Data = dataContent.Base64Data.ToString(),
200+
MimeType = dataContent.MediaType,
201+
},
202+
203+
DataContent dataContent => new EmbeddedResourceBlock()
204+
{
205+
Resource = new BlobResourceContents()
206+
{
207+
Blob = dataContent.Base64Data.ToString(),
208+
MimeType = dataContent.MediaType,
209+
}
210+
},
211+
212+
_ => new TextContentBlock()
201213
{
202214
Text = JsonSerializer.Serialize(content, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object))),
203-
Type = "text",
204215
}
205216
};
206217
}

src/ModelContextProtocol.Core/Client/McpClientExtensions.cs

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,7 @@ public static Task UnsubscribeFromResourceAsync(this IMcpClient client, Uri uri,
812812
/// </param>
813813
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
814814
/// <returns>
815-
/// A task containing the <see cref="CallToolResponse"/> from the tool execution. The response includes
815+
/// A task containing the <see cref="CallToolResult"/> from the tool execution. The response includes
816816
/// the tool's output content, which may be structured data, text, or an error message.
817817
/// </returns>
818818
/// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
@@ -829,7 +829,7 @@ public static Task UnsubscribeFromResourceAsync(this IMcpClient client, Uri uri,
829829
/// });
830830
/// </code>
831831
/// </example>
832-
public static ValueTask<CallToolResponse> CallToolAsync(
832+
public static ValueTask<CallToolResult> CallToolAsync(
833833
this IMcpClient client,
834834
string toolName,
835835
IReadOnlyDictionary<string, object?>? arguments = null,
@@ -855,10 +855,10 @@ public static ValueTask<CallToolResponse> CallToolAsync(
855855
Arguments = ToArgumentsDictionary(arguments, serializerOptions),
856856
},
857857
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
858-
McpJsonUtilities.JsonContext.Default.CallToolResponse,
858+
McpJsonUtilities.JsonContext.Default.CallToolResult,
859859
cancellationToken: cancellationToken);
860860

861-
static async ValueTask<CallToolResponse> SendRequestWithProgressAsync(
861+
static async ValueTask<CallToolResult> SendRequestWithProgressAsync(
862862
IMcpClient client,
863863
string toolName,
864864
IReadOnlyDictionary<string, object?>? arguments,
@@ -889,7 +889,7 @@ static async ValueTask<CallToolResponse> SendRequestWithProgressAsync(
889889
Meta = new() { ProgressToken = progressToken },
890890
},
891891
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
892-
McpJsonUtilities.JsonContext.Default.CallToolResponse,
892+
McpJsonUtilities.JsonContext.Default.CallToolResult,
893893
cancellationToken: cancellationToken).ConfigureAwait(false);
894894
}
895895
}
@@ -924,29 +924,12 @@ internal static (IList<ChatMessage> Messages, ChatOptions? Options) ToChatClient
924924
(options ??= new()).StopSequences = stopSequences.ToArray();
925925
}
926926

927-
List<ChatMessage> messages = [];
928-
foreach (SamplingMessage sm in requestParams.Messages)
929-
{
930-
ChatMessage message = new()
931-
{
932-
Role = sm.Role == Role.User ? ChatRole.User : ChatRole.Assistant,
933-
};
934-
935-
if (sm.Content is { Type: "text" })
936-
{
937-
message.Contents.Add(new TextContent(sm.Content.Text));
938-
}
939-
else if (sm.Content is { Type: "image" or "audio", MimeType: not null, Data: not null })
940-
{
941-
message.Contents.Add(new DataContent(Convert.FromBase64String(sm.Content.Data), sm.Content.MimeType));
942-
}
943-
else if (sm.Content is { Type: "resource", Resource: not null })
944-
{
945-
message.Contents.Add(sm.Content.Resource.ToAIContent());
946-
}
947-
948-
messages.Add(message);
949-
}
927+
List<ChatMessage> messages =
928+
(from sm in requestParams.Messages
929+
let aiContent = sm.Content.ToAIContent()
930+
where aiContent is not null
931+
select new ChatMessage(sm.Role == Role.Assistant ? ChatRole.Assistant : ChatRole.User, [aiContent]))
932+
.ToList();
950933

951934
return (messages, options);
952935
}
@@ -965,32 +948,21 @@ internal static CreateMessageResult ToCreateMessageResult(this ChatResponse chat
965948

966949
ChatMessage? lastMessage = chatResponse.Messages.LastOrDefault();
967950

968-
Content? content = null;
951+
ContentBlock? content = null;
969952
if (lastMessage is not null)
970953
{
971954
foreach (var lmc in lastMessage.Contents)
972955
{
973956
if (lmc is DataContent dc && (dc.HasTopLevelMediaType("image") || dc.HasTopLevelMediaType("audio")))
974957
{
975-
content = new()
976-
{
977-
Type = dc.HasTopLevelMediaType("image") ? "image" : "audio",
978-
MimeType = dc.MediaType,
979-
Data = dc.Base64Data.ToString(),
980-
};
958+
content = dc.ToContent();
981959
}
982960
}
983961
}
984962

985-
content ??= new()
986-
{
987-
Text = lastMessage?.Text ?? string.Empty,
988-
Type = "text",
989-
};
990-
991963
return new()
992964
{
993-
Content = content,
965+
Content = content ?? new TextContentBlock() { Text = lastMessage?.Text ?? string.Empty },
994966
Model = chatResponse.ModelId ?? "unknown",
995967
Role = lastMessage?.Role == ChatRole.User ? Role.User : Role.Assistant,
996968
StopReason = chatResponse.FinishReason == ChatFinishReason.Length ? "maxTokens" : "endTurn",
@@ -1107,7 +1079,7 @@ public static Task SetLoggingLevel(this IMcpClient client, LogLevel level, Cance
11071079
SetLoggingLevel(client, McpServer.ToLoggingLevel(level), cancellationToken);
11081080

11091081
/// <summary>Convers a dictionary with <see cref="object"/> values to a dictionary with <see cref="JsonElement"/> values.</summary>
1110-
private static IReadOnlyDictionary<string, JsonElement>? ToArgumentsDictionary(
1082+
private static Dictionary<string, JsonElement>? ToArgumentsDictionary(
11111083
IReadOnlyDictionary<string, object?>? arguments, JsonSerializerOptions options)
11121084
{
11131085
var typeInfo = options.GetTypeInfo<object?>();

0 commit comments

Comments
 (0)