Skip to content

Commit 3082e35

Browse files
Copilotstephentoub
andcommitted
Revert SerializableJsonElement wrapper, restore #if NET approach
Co-authored-by: stephentoub <[email protected]>
1 parent f757558 commit 3082e35

File tree

4 files changed

+35
-69
lines changed

4 files changed

+35
-69
lines changed

src/ModelContextProtocol.Core/McpSessionHandler.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -455,17 +455,19 @@ public async Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, Canc
455455
LogSendingRequestFailed(EndpointName, request.Method, error.Error.Message, error.Error.Code);
456456
var exception = new McpProtocolException($"Request failed (remote): {error.Error.Message}", (McpErrorCode)error.Error.Code);
457457

458+
#if NET
458459
// Populate exception.Data with the error data if present.
459460
// When deserializing JSON, Data will be a JsonElement.
461+
// Note: This is not supported on .NET Framework because Exception.Data uses ListDictionaryInternal
462+
// which requires values to be marked with [Serializable], and JsonElement is not serializable.
460463
if (error.Error.Data is JsonElement jsonElement && jsonElement.ValueKind == JsonValueKind.Object)
461464
{
462465
foreach (var property in jsonElement.EnumerateObject())
463466
{
464-
// On .NET Framework, Exception.Data requires values to be [Serializable].
465-
// JsonElement is not serializable, so we wrap it in a serializable container.
466-
exception.Data[property.Name] = SerializableJsonElement.Wrap(property.Value);
467+
exception.Data[property.Name] = property.Value;
467468
}
468469
}
470+
#endif
469471

470472
throw exception;
471473
}

src/ModelContextProtocol.Core/Protocol/SerializableJsonElement.cs

Lines changed: 0 additions & 48 deletions
This file was deleted.

tests/ModelContextProtocol.Tests/McpProtocolExceptionDataTests.cs

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ namespace ModelContextProtocol.Tests;
1010
/// <summary>
1111
/// Tests for McpProtocolException.Data propagation to JSON-RPC error responses.
1212
/// </summary>
13+
/// <remarks>
14+
/// Note: On .NET Framework, Exception.Data requires values to be serializable with [Serializable].
15+
/// Since JsonElement is not marked as serializable, the data population on the client side is skipped
16+
/// on that platform. These tests verify the error message and code are correct regardless of platform.
17+
/// </remarks>
1318
public class McpProtocolExceptionDataTests : ClientServerTestBase
1419
{
1520
public McpProtocolExceptionDataTests(ITestOutputHelper testOutputHelper)
@@ -63,20 +68,6 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer
6368
});
6469
}
6570

66-
/// <summary>
67-
/// Gets the JsonElement from an Exception.Data value, handling both direct JsonElement (on .NET Core)
68-
/// and SerializableJsonElement wrapper (on .NET Framework).
69-
/// </summary>
70-
private static JsonElement GetJsonElement(object? value)
71-
{
72-
return value switch
73-
{
74-
JsonElement je => je,
75-
SerializableJsonElement sje => sje.Value,
76-
_ => throw new InvalidOperationException($"Expected JsonElement or SerializableJsonElement, got {value?.GetType().Name ?? "null"}")
77-
};
78-
}
79-
8071
[Fact]
8172
public async Task Exception_With_Serializable_Data_Propagates_To_Client()
8273
{
@@ -87,6 +78,12 @@ public async Task Exception_With_Serializable_Data_Propagates_To_Client()
8778

8879
Assert.Equal("Request failed (remote): Resource not found", exception.Message);
8980
Assert.Equal((McpErrorCode)(-32002), exception.ErrorCode);
81+
82+
// Skip data verification on .NET Framework since JsonElement cannot be stored in Exception.Data
83+
if (PlatformDetection.IsNetFramework)
84+
{
85+
return;
86+
}
9087

9188
// Verify the data was propagated to the exception
9289
// The Data collection should contain the expected keys
@@ -103,11 +100,11 @@ public async Task Exception_With_Serializable_Data_Propagates_To_Client()
103100
Assert.True(hasUri, "Exception.Data should contain 'uri' key");
104101
Assert.True(hasCode, "Exception.Data should contain 'code' key");
105102

106-
// Verify the values (they are JsonElements on .NET Core, SerializableJsonElement on .NET Framework)
107-
var uriValue = GetJsonElement(exception.Data["uri"]);
103+
// Verify the values (they should be JsonElements)
104+
var uriValue = Assert.IsType<JsonElement>(exception.Data["uri"]);
108105
Assert.Equal("file:///path/to/resource", uriValue.GetString());
109106

110-
var codeValue = GetJsonElement(exception.Data["code"]);
107+
var codeValue = Assert.IsType<JsonElement>(exception.Data["code"]);
111108
Assert.Equal(404, codeValue.GetInt32());
112109
}
113110

@@ -124,6 +121,12 @@ public async Task Exception_With_NonSerializable_Data_Still_Propagates_Error_To_
124121

125122
Assert.Equal("Request failed (remote): Resource not found", exception.Message);
126123
Assert.Equal((McpErrorCode)(-32002), exception.ErrorCode);
124+
125+
// Skip data verification on .NET Framework since JsonElement cannot be stored in Exception.Data
126+
if (PlatformDetection.IsNetFramework)
127+
{
128+
return;
129+
}
127130

128131
// Verify that only the serializable data was propagated (non-serializable was filtered out)
129132
var hasUri = false;
@@ -139,7 +142,7 @@ public async Task Exception_With_NonSerializable_Data_Still_Propagates_Error_To_
139142
Assert.True(hasUri, "Exception.Data should contain 'uri' key");
140143
Assert.False(hasNonSerializable, "Exception.Data should not contain 'nonSerializable' key");
141144

142-
var uriValue = GetJsonElement(exception.Data["uri"]);
145+
var uriValue = Assert.IsType<JsonElement>(exception.Data["uri"]);
143146
Assert.Equal("file:///path/to/resource", uriValue.GetString());
144147
}
145148

tests/ModelContextProtocol.Tests/PlatformDetection.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,13 @@ internal static class PlatformDetection
66
{
77
public static bool IsMonoRuntime { get; } = Type.GetType("Mono.Runtime") is not null;
88
public static bool IsWindows { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
9+
10+
// On .NET Framework, Exception.Data requires values to be serializable with [Serializable].
11+
// JsonElement is not marked as serializable, so certain features are not available on that platform.
12+
public static bool IsNetFramework { get; } =
13+
#if NET
14+
false;
15+
#else
16+
true;
17+
#endif
918
}

0 commit comments

Comments
 (0)