Skip to content

Commit d4ee787

Browse files
authored
Ignore unexpected properties when deserializing a ContentBlock (#956)
* Ignore unexpected properties when deserializing a ContentBlock * Fix deserialization of Reference.Title and more bugs for unknown props * Remove comments about skipping additional properties
1 parent 98a0261 commit d4ee787

File tree

10 files changed

+1762
-1
lines changed

10 files changed

+1762
-1
lines changed

src/ModelContextProtocol.Core/Protocol/ContentBlock.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ public class Converter : JsonConverter<ContentBlock>
141141
break;
142142

143143
default:
144+
reader.Skip();
144145
break;
145146
}
146147
}

src/ModelContextProtocol.Core/Protocol/ElicitRequestParams.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ public class Converter : JsonConverter<PrimitiveSchemaDefinition>
188188
break;
189189

190190
default:
191+
reader.Skip();
191192
break;
192193
}
193194
}

src/ModelContextProtocol.Core/Protocol/ProgressNotificationParams.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ public sealed class Converter : JsonConverter<ProgressNotificationParams>
8181
case "_meta":
8282
meta = JsonSerializer.Deserialize(ref reader, McpJsonUtilities.JsonContext.Default.JsonObject);
8383
break;
84+
85+
default:
86+
reader.Skip();
87+
break;
8488
}
8589
}
8690
}

src/ModelContextProtocol.Core/Protocol/Reference.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,16 @@ public sealed class Converter : JsonConverter<Reference>
8181
name = reader.GetString();
8282
break;
8383

84+
case "title":
85+
title = reader.GetString();
86+
break;
87+
8488
case "uri":
8589
uri = reader.GetString();
8690
break;
8791

8892
default:
93+
reader.Skip();
8994
break;
9095
}
9196
}

src/ModelContextProtocol.Core/Protocol/ResourceContents.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public class Converter : JsonConverter<ResourceContents>
116116
break;
117117

118118
default:
119+
reader.Skip();
119120
break;
120121
}
121122
}

tests/ModelContextProtocol.Tests/Protocol/ContentBlockTests.cs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using System.Text.Json;
1+
using Microsoft.Extensions.AI;
22
using ModelContextProtocol.Protocol;
3+
using System.Text.Json;
34

45
namespace ModelContextProtocol.Tests.Protocol;
56

@@ -80,4 +81,48 @@ public void ResourceLinkBlock_DeserializationWithoutName_ThrowsJsonException()
8081

8182
Assert.Contains("Name must be provided for 'resource_link' type", exception.Message);
8283
}
84+
85+
[Fact]
86+
public void Deserialize_IgnoresUnknownArrayProperty()
87+
{
88+
// This is a regression test where a server returned an unexpected response with
89+
// `structuredContent` as an array nested inside a content block. This should be
90+
// permitted with the `structuredContent` gracefully ignored in that location.
91+
string responseJson = @"{
92+
""type"": ""text"",
93+
""text"": ""[\n {\n \""Data\"": \""1234567890\""\n }\n]"",
94+
""structuredContent"": [
95+
{
96+
""Data"": ""1234567890""
97+
}
98+
]
99+
}";
100+
101+
var contentBlock = JsonSerializer.Deserialize<ContentBlock>(responseJson, McpJsonUtilities.DefaultOptions);
102+
Assert.NotNull(contentBlock);
103+
104+
var textBlock = Assert.IsType<TextContentBlock>(contentBlock);
105+
Assert.Contains("1234567890", textBlock.Text);
106+
}
107+
108+
[Fact]
109+
public void Deserialize_IgnoresUnknownObjectProperties()
110+
{
111+
string responseJson = @"{
112+
""type"": ""text"",
113+
""text"": ""Sample text"",
114+
""unknownObject"": {
115+
""nestedProp1"": ""value1"",
116+
""nestedProp2"": {
117+
""deeplyNested"": true
118+
}
119+
}
120+
}";
121+
122+
var contentBlock = JsonSerializer.Deserialize<ContentBlock>(responseJson, McpJsonUtilities.DefaultOptions);
123+
Assert.NotNull(contentBlock);
124+
125+
var textBlock = Assert.IsType<TextContentBlock>(contentBlock);
126+
Assert.Contains("Sample text", textBlock.Text);
127+
}
83128
}

0 commit comments

Comments
 (0)