Skip to content

Commit 2bcff31

Browse files
authored
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
1 parent 28d6547 commit 2bcff31

File tree

158 files changed

+1700
-853
lines changed

Some content is hidden

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

158 files changed

+1700
-853
lines changed

samples/EverythingServer/Program.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ await ctx.Server.SampleAsync([
8686
var @ref = @params.Ref;
8787
var argument = @params.Argument;
8888

89-
if (@ref.Type == "ref/resource")
89+
if (@ref is ResourceTemplateReference rtr)
9090
{
91-
var resourceId = @ref.Uri?.Split("/").Last();
91+
var resourceId = rtr.Uri?.Split("/").Last();
9292

9393
if (resourceId is null)
9494
{
@@ -103,7 +103,7 @@ await ctx.Server.SampleAsync([
103103
};
104104
}
105105

106-
if (@ref.Type == "ref/prompt")
106+
if (@ref is PromptReference pr)
107107
{
108108
if (!exampleCompletions.TryGetValue(argument.Name, out IEnumerable<string>? value))
109109
{

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/LongRunningTool.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public static async Task<string> LongRunningOperation(
1515
int duration = 10,
1616
int steps = 5)
1717
{
18-
var progressToken = context.Params?.Meta?.ProgressToken;
18+
var progressToken = context.Params?.ProgressToken;
1919
var stepDuration = duration / steps;
2020

2121
for (int i = 1; i <= steps + 1; i++)

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/QuickstartWeatherServer/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
builder.Services.AddSingleton(_ =>
1919
{
20-
var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") };
20+
var client = new HttpClient { BaseAddress = new Uri("https://api.weather.gov") };
2121
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
2222
return client;
2323
});

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
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Diagnostics.CodeAnalysis;
5+
6+
/// <summary>Specifies the syntax used in a string.</summary>
7+
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
8+
internal sealed class StringSyntaxAttribute : Attribute
9+
{
10+
/// <summary>Initializes the <see cref="StringSyntaxAttribute"/> with the identifier of the syntax used.</summary>
11+
/// <param name="syntax">The syntax identifier.</param>
12+
public StringSyntaxAttribute(string syntax)
13+
{
14+
Syntax = syntax;
15+
Arguments = Array.Empty<object?>();
16+
}
17+
18+
/// <summary>Initializes the <see cref="StringSyntaxAttribute"/> with the identifier of the syntax used.</summary>
19+
/// <param name="syntax">The syntax identifier.</param>
20+
/// <param name="arguments">Optional arguments associated with the specific syntax employed.</param>
21+
public StringSyntaxAttribute(string syntax, params object?[] arguments)
22+
{
23+
Syntax = syntax;
24+
Arguments = arguments;
25+
}
26+
27+
/// <summary>Gets the identifier of the syntax used.</summary>
28+
public string Syntax { get; }
29+
30+
/// <summary>Optional arguments associated with the specific syntax employed.</summary>
31+
public object?[] Arguments { get; }
32+
33+
/// <summary>The syntax identifier for strings containing composite formats for string formatting.</summary>
34+
public const string CompositeFormat = nameof(CompositeFormat);
35+
36+
/// <summary>The syntax identifier for strings containing date format specifiers.</summary>
37+
public const string DateOnlyFormat = nameof(DateOnlyFormat);
38+
39+
/// <summary>The syntax identifier for strings containing date and time format specifiers.</summary>
40+
public const string DateTimeFormat = nameof(DateTimeFormat);
41+
42+
/// <summary>The syntax identifier for strings containing <see cref="Enum"/> format specifiers.</summary>
43+
public const string EnumFormat = nameof(EnumFormat);
44+
45+
/// <summary>The syntax identifier for strings containing <see cref="Guid"/> format specifiers.</summary>
46+
public const string GuidFormat = nameof(GuidFormat);
47+
48+
/// <summary>The syntax identifier for strings containing JavaScript Object Notation (JSON).</summary>
49+
public const string Json = nameof(Json);
50+
51+
/// <summary>The syntax identifier for strings containing numeric format specifiers.</summary>
52+
public const string NumericFormat = nameof(NumericFormat);
53+
54+
/// <summary>The syntax identifier for strings containing regular expressions.</summary>
55+
public const string Regex = nameof(Regex);
56+
57+
/// <summary>The syntax identifier for strings containing time format specifiers.</summary>
58+
public const string TimeOnlyFormat = nameof(TimeOnlyFormat);
59+
60+
/// <summary>The syntax identifier for strings containing <see cref="TimeSpan"/> format specifiers.</summary>
61+
public const string TimeSpanFormat = nameof(TimeSpanFormat);
62+
63+
/// <summary>The syntax identifier for strings containing URIs.</summary>
64+
public const string Uri = nameof(Uri);
65+
66+
/// <summary>The syntax identifier for strings containing XML.</summary>
67+
public const string Xml = nameof(Xml);
68+
}

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/McpClient.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using Microsoft.Extensions.Logging;
22
using ModelContextProtocol.Protocol;
3-
using System.Diagnostics;
43
using System.Text.Json;
54

65
namespace ModelContextProtocol.Client;
@@ -58,7 +57,7 @@ public McpClient(IClientTransport clientTransport, McpClientOptions? options, IL
5857
RequestMethods.SamplingCreateMessage,
5958
(request, _, cancellationToken) => samplingHandler(
6059
request,
61-
request?.Meta?.ProgressToken is { } token ? new TokenProgress(this, token) : NullProgress.Instance,
60+
request?.ProgressToken is { } token ? new TokenProgress(this, token) : NullProgress.Instance,
6261
cancellationToken),
6362
McpJsonUtilities.JsonContext.Default.CreateMessageRequestParams,
6463
McpJsonUtilities.JsonContext.Default.CreateMessageResult);
@@ -180,9 +179,12 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
180179
}
181180

182181
// Send initialized notification
183-
await SendMessageAsync(
184-
new JsonRpcNotification { Method = NotificationMethods.InitializedNotification },
185-
initializationCts.Token).ConfigureAwait(false);
182+
await this.SendNotificationAsync(
183+
NotificationMethods.InitializedNotification,
184+
new InitializedNotificationParams(),
185+
McpJsonUtilities.JsonContext.Default.InitializedNotificationParams,
186+
cancellationToken: initializationCts.Token).ConfigureAwait(false);
187+
186188
}
187189
catch (OperationCanceledException oce) when (initializationCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
188190
{

0 commit comments

Comments
 (0)