From 55bd4b3bc77a5d6bf3a1a4df8d84e28c27007dbf Mon Sep 17 00:00:00 2001 From: dfedoryshchev Date: Fri, 21 Aug 2026 12:21:10 +0100 Subject: [PATCH] Fix RequestOptions writing an integer progress token as a JSON string --- src/ModelContextProtocol.Core/RequestOptions.cs | 7 ++++++- .../RequestOptionsTests.cs | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/ModelContextProtocol.Core/RequestOptions.cs b/src/ModelContextProtocol.Core/RequestOptions.cs index bde8cecbf..572cbadf4 100644 --- a/src/ModelContextProtocol.Core/RequestOptions.cs +++ b/src/ModelContextProtocol.Core/RequestOptions.cs @@ -79,7 +79,12 @@ internal RequestOptions Clone() => if (ProgressToken is not null) { meta = (JsonObject?)meta?.DeepClone() ?? []; - meta["progressToken"] = ProgressToken.ToString(); + + // Serialize through ProgressToken's own converter rather than ToString(), so that an integer + // token is written as a JSON number. Stringifying it changes the token the peer echoes back + // in progress notifications, and the caller then no longer recognizes its own token. + meta["progressToken"] = JsonSerializer.SerializeToNode( + ProgressToken.Value, McpJsonUtilities.JsonContext.Default.ProgressToken); } return meta; diff --git a/tests/ModelContextProtocol.Tests/RequestOptionsTests.cs b/tests/ModelContextProtocol.Tests/RequestOptionsTests.cs index 1df78608a..639ce337f 100644 --- a/tests/ModelContextProtocol.Tests/RequestOptionsTests.cs +++ b/tests/ModelContextProtocol.Tests/RequestOptionsTests.cs @@ -140,6 +140,21 @@ public static void GetMetaForRequest_OnlyProgressTokenSetAsLong_ReturnsNewObject Assert.NotSame(actual, options.GetMetaForRequest()); } + [Fact] + public static void GetMetaForRequest_LongProgressToken_IsWrittenAsJsonNumber() + { + RequestOptions options = new() { ProgressToken = new ProgressToken(42L) }; + + var actual = options.GetMetaForRequest(); + + Assert.NotNull(actual); + Assert.Equal(JsonValueKind.Number, actual["progressToken"]!.GetValueKind()); + + // The token the peer reads back must be the token the caller asked for. + RequestParams request = new CallToolRequestParams { Name = "tool", Meta = actual }; + Assert.Equal(new ProgressToken(42L), request.ProgressToken); + } + [Fact] public static void GetMetaForRequest_BothSet_ReturnsCloneWithProgressToken() {