From 3617a54512cb1deba05286f69ec4353f9ba4c85d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 23:57:52 +0000 Subject: [PATCH 1/3] Initial plan From f98dc145a97a92aa83a40a697a5917be8a1c6415 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Jun 2026 00:02:48 +0000 Subject: [PATCH 2/3] Rename TtlMs/DefaultTtlMs to TimeToLiveMs/DefaultTimeToLiveMs in public APIs Co-authored-by: PranavSenthilnathan <12225508+PranavSenthilnathan@users.noreply.github.com> --- .../Protocol/CreateTaskResult.cs | 2 +- .../Protocol/GetTaskResult.cs | 8 ++++---- .../Protocol/TaskStatusNotificationParams.cs | 8 ++++---- .../Server/InMemoryMcpTaskStore.cs | 4 ++-- .../Server/McpServerImpl.cs | 12 ++++++------ src/ModelContextProtocol.Core/Server/McpTaskInfo.cs | 2 +- .../Protocol/TaskSerializationTests.cs | 6 +++--- .../Server/InMemoryMcpTaskStoreTests.cs | 6 +++--- .../Server/TaskCancellationIntegrationTests.cs | 2 +- 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/ModelContextProtocol.Core/Protocol/CreateTaskResult.cs b/src/ModelContextProtocol.Core/Protocol/CreateTaskResult.cs index 2e5bc0c41..8e68ba071 100644 --- a/src/ModelContextProtocol.Core/Protocol/CreateTaskResult.cs +++ b/src/ModelContextProtocol.Core/Protocol/CreateTaskResult.cs @@ -58,7 +58,7 @@ public sealed class CreateTaskResult : Result /// Gets or sets the time-to-live duration from creation in milliseconds, or for unlimited. /// [JsonPropertyName("ttlMs")] - public long? TtlMs { get; set; } + public long? TimeToLiveMs { get; set; } /// /// Gets or sets the suggested polling interval in milliseconds. diff --git a/src/ModelContextProtocol.Core/Protocol/GetTaskResult.cs b/src/ModelContextProtocol.Core/Protocol/GetTaskResult.cs index 9366f2374..e7d29ea6f 100644 --- a/src/ModelContextProtocol.Core/Protocol/GetTaskResult.cs +++ b/src/ModelContextProtocol.Core/Protocol/GetTaskResult.cs @@ -67,7 +67,7 @@ private protected GetTaskResult() /// Gets or sets the time-to-live duration from creation in milliseconds, or for unlimited. /// [JsonPropertyName("ttlMs")] - public long? TtlMs { get; set; } + public long? TimeToLiveMs { get; set; } /// /// Gets or sets the suggested polling interval in milliseconds. @@ -245,7 +245,7 @@ internal sealed class Converter : JsonConverter }; taskResult.StatusMessage = statusMessage; - taskResult.TtlMs = ttlMs; + taskResult.TimeToLiveMs = ttlMs; taskResult.PollIntervalMs = pollIntervalMs; taskResult.ResultType = resultType; taskResult.Meta = meta; @@ -287,9 +287,9 @@ public override void Write(Utf8JsonWriter writer, GetTaskResult value, JsonSeria writer.WriteString("createdAt", value.CreatedAt); writer.WriteString("lastUpdatedAt", value.LastUpdatedAt); - if (value.TtlMs is not null) + if (value.TimeToLiveMs is not null) { - writer.WriteNumber("ttlMs", value.TtlMs.Value); + writer.WriteNumber("ttlMs", value.TimeToLiveMs.Value); } if (value.PollIntervalMs is not null) diff --git a/src/ModelContextProtocol.Core/Protocol/TaskStatusNotificationParams.cs b/src/ModelContextProtocol.Core/Protocol/TaskStatusNotificationParams.cs index e1bbb2f73..b3094d85a 100644 --- a/src/ModelContextProtocol.Core/Protocol/TaskStatusNotificationParams.cs +++ b/src/ModelContextProtocol.Core/Protocol/TaskStatusNotificationParams.cs @@ -73,7 +73,7 @@ private protected TaskStatusNotificationParams() /// Gets or sets the time-to-live duration from creation in milliseconds, or for unlimited. /// [JsonPropertyName("ttlMs")] - public long? TtlMs { get; set; } + public long? TimeToLiveMs { get; set; } /// /// Gets or sets the suggested polling interval in milliseconds. @@ -247,7 +247,7 @@ internal sealed class Converter : JsonConverter }; notification.StatusMessage = statusMessage; - notification.TtlMs = ttlMs; + notification.TimeToLiveMs = ttlMs; notification.PollIntervalMs = pollIntervalMs; notification.Meta = meta; @@ -283,9 +283,9 @@ public override void Write(Utf8JsonWriter writer, TaskStatusNotificationParams v writer.WriteString("createdAt", value.CreatedAt); writer.WriteString("lastUpdatedAt", value.LastUpdatedAt); - if (value.TtlMs is not null) + if (value.TimeToLiveMs is not null) { - writer.WriteNumber("ttlMs", value.TtlMs.Value); + writer.WriteNumber("ttlMs", value.TimeToLiveMs.Value); } if (value.PollIntervalMs is not null) diff --git a/src/ModelContextProtocol.Core/Server/InMemoryMcpTaskStore.cs b/src/ModelContextProtocol.Core/Server/InMemoryMcpTaskStore.cs index a13af13e9..7be53c17d 100644 --- a/src/ModelContextProtocol.Core/Server/InMemoryMcpTaskStore.cs +++ b/src/ModelContextProtocol.Core/Server/InMemoryMcpTaskStore.cs @@ -34,7 +34,7 @@ public class InMemoryMcpTaskStore : IMcpTaskStore /// /// Gets or sets the default time-to-live in milliseconds for new tasks, or for unlimited. /// - public long? DefaultTtlMs { get; set; } + public long? DefaultTimeToLiveMs { get; set; } /// public Task CreateTaskAsync(CancellationToken cancellationToken = default) @@ -42,7 +42,7 @@ public Task CreateTaskAsync(CancellationToken cancellationToken = d var taskId = Guid.NewGuid().ToString("N"); var now = DateTimeOffset.UtcNow; - var info = new McpTaskInfo(taskId, McpTaskStatus.Working, now, now, DefaultTtlMs, DefaultPollIntervalMs); + var info = new McpTaskInfo(taskId, McpTaskStatus.Working, now, now, DefaultTimeToLiveMs, DefaultPollIntervalMs); _tasks[taskId] = info; return Task.FromResult(info); diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs index 85b1cc26a..84f0b7e6b 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs @@ -1009,7 +1009,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) Status = info.Status, CreatedAt = info.CreatedAt, LastUpdatedAt = info.LastUpdatedAt, - TtlMs = info.TtlMs, + TimeToLiveMs = info.TimeToLiveMs, PollIntervalMs = info.PollIntervalMs, StatusMessage = info.StatusMessage, ResultType = "task", @@ -1022,7 +1022,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) TaskId = info.TaskId, CreatedAt = info.CreatedAt, LastUpdatedAt = info.LastUpdatedAt, - TtlMs = info.TtlMs, + TimeToLiveMs = info.TimeToLiveMs, PollIntervalMs = info.PollIntervalMs, StatusMessage = info.StatusMessage, ResultType = "complete", @@ -1032,7 +1032,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) TaskId = info.TaskId, CreatedAt = info.CreatedAt, LastUpdatedAt = info.LastUpdatedAt, - TtlMs = info.TtlMs, + TimeToLiveMs = info.TimeToLiveMs, PollIntervalMs = info.PollIntervalMs, StatusMessage = info.StatusMessage, Result = info.Result ?? throw new InvalidOperationException($"Task '{info.TaskId}' is completed but has no result."), @@ -1043,7 +1043,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) TaskId = info.TaskId, CreatedAt = info.CreatedAt, LastUpdatedAt = info.LastUpdatedAt, - TtlMs = info.TtlMs, + TimeToLiveMs = info.TimeToLiveMs, PollIntervalMs = info.PollIntervalMs, StatusMessage = info.StatusMessage, Error = info.Error ?? throw new InvalidOperationException($"Task '{info.TaskId}' is failed but has no error."), @@ -1054,7 +1054,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) TaskId = info.TaskId, CreatedAt = info.CreatedAt, LastUpdatedAt = info.LastUpdatedAt, - TtlMs = info.TtlMs, + TimeToLiveMs = info.TimeToLiveMs, PollIntervalMs = info.PollIntervalMs, StatusMessage = info.StatusMessage, ResultType = "complete", @@ -1064,7 +1064,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) TaskId = info.TaskId, CreatedAt = info.CreatedAt, LastUpdatedAt = info.LastUpdatedAt, - TtlMs = info.TtlMs, + TimeToLiveMs = info.TimeToLiveMs, PollIntervalMs = info.PollIntervalMs, StatusMessage = info.StatusMessage, // McpTaskInfo.InputRequests is IReadOnlyDictionary (covers immutable store diff --git a/src/ModelContextProtocol.Core/Server/McpTaskInfo.cs b/src/ModelContextProtocol.Core/Server/McpTaskInfo.cs index ab71d6505..855116c97 100644 --- a/src/ModelContextProtocol.Core/Server/McpTaskInfo.cs +++ b/src/ModelContextProtocol.Core/Server/McpTaskInfo.cs @@ -20,7 +20,7 @@ public sealed record McpTaskInfo( McpTaskStatus Status, DateTimeOffset CreatedAt, DateTimeOffset LastUpdatedAt, - long? TtlMs = null, + long? TimeToLiveMs = null, long? PollIntervalMs = null, string? StatusMessage = null, JsonElement? Result = null, diff --git a/tests/ModelContextProtocol.Tests/Protocol/TaskSerializationTests.cs b/tests/ModelContextProtocol.Tests/Protocol/TaskSerializationTests.cs index 556403add..b57980660 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/TaskSerializationTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/TaskSerializationTests.cs @@ -21,7 +21,7 @@ public static void CreateTaskResult_SerializationRoundTrip_PreservesAllPropertie StatusMessage = "Processing...", CreatedAt = new DateTimeOffset(2025, 6, 1, 12, 0, 0, TimeSpan.Zero), LastUpdatedAt = new DateTimeOffset(2025, 6, 1, 12, 5, 0, TimeSpan.Zero), - TtlMs = 3600000, + TimeToLiveMs = 3600000, PollIntervalMs = 5000, ResultType = "task", Meta = new JsonObject { ["key"] = "value" } @@ -36,7 +36,7 @@ public static void CreateTaskResult_SerializationRoundTrip_PreservesAllPropertie Assert.Equal("Processing...", deserialized.StatusMessage); Assert.Equal(original.CreatedAt, deserialized.CreatedAt); Assert.Equal(original.LastUpdatedAt, deserialized.LastUpdatedAt); - Assert.Equal(3600000, deserialized.TtlMs); + Assert.Equal(3600000, deserialized.TimeToLiveMs); Assert.Equal(5000, deserialized.PollIntervalMs); Assert.Equal("task", deserialized.ResultType); Assert.NotNull(deserialized.Meta); @@ -52,7 +52,7 @@ public static void CreateTaskResult_UsesCorrectWireFieldNames() Status = McpTaskStatus.Working, CreatedAt = DateTimeOffset.UtcNow, LastUpdatedAt = DateTimeOffset.UtcNow, - TtlMs = 60000, + TimeToLiveMs = 60000, PollIntervalMs = 1000, ResultType = "task", }; diff --git a/tests/ModelContextProtocol.Tests/Server/InMemoryMcpTaskStoreTests.cs b/tests/ModelContextProtocol.Tests/Server/InMemoryMcpTaskStoreTests.cs index 7c87786eb..09a230747 100644 --- a/tests/ModelContextProtocol.Tests/Server/InMemoryMcpTaskStoreTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/InMemoryMcpTaskStoreTests.cs @@ -55,13 +55,13 @@ public async Task CreateTaskAsync_UsesDefaultPollInterval() } [Fact] - public async Task CreateTaskAsync_UsesDefaultTtl() + public async Task CreateTaskAsync_UsesDefaultTimeToLive() { - var store = new InMemoryMcpTaskStore { DefaultTtlMs = 30000 }; + var store = new InMemoryMcpTaskStore { DefaultTimeToLiveMs = 30000 }; var result = await store.CreateTaskAsync(CT); - Assert.Equal(30000, result.TtlMs); + Assert.Equal(30000, result.TimeToLiveMs); } [Fact] diff --git a/tests/ModelContextProtocol.Tests/Server/TaskCancellationIntegrationTests.cs b/tests/ModelContextProtocol.Tests/Server/TaskCancellationIntegrationTests.cs index 1b424d002..91609d817 100644 --- a/tests/ModelContextProtocol.Tests/Server/TaskCancellationIntegrationTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/TaskCancellationIntegrationTests.cs @@ -34,7 +34,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer options.TaskStore = new InMemoryMcpTaskStore { DefaultPollIntervalMs = 50, - DefaultTtlMs = 5000, + DefaultTimeToLiveMs = 5000, }; }); From 326342d4616378ecf77de883e11636cbef9dff7a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Jun 2026 01:01:20 +0000 Subject: [PATCH 3/3] Change TimeToLiveMs (long?) to TimeToLive (TimeSpan?) with TimeSpanMillisecondsConverter Co-authored-by: PranavSenthilnathan <12225508+PranavSenthilnathan@users.noreply.github.com> --- .../Protocol/CreateTaskResult.cs | 5 +++-- .../Protocol/GetTaskResult.cs | 10 +++++----- .../Protocol/TaskStatusNotificationParams.cs | 10 +++++----- .../Protocol/TimeSpanMillisecondsConverter.cs | 19 +++++++++++++++++++ .../Server/InMemoryMcpTaskStore.cs | 6 +++--- .../Server/McpServerImpl.cs | 12 ++++++------ .../Server/McpTaskInfo.cs | 2 +- .../Protocol/TaskSerializationTests.cs | 6 +++--- .../Server/InMemoryMcpTaskStoreTests.cs | 4 ++-- .../TaskCancellationIntegrationTests.cs | 2 +- 10 files changed, 48 insertions(+), 28 deletions(-) create mode 100644 src/ModelContextProtocol.Core/Protocol/TimeSpanMillisecondsConverter.cs diff --git a/src/ModelContextProtocol.Core/Protocol/CreateTaskResult.cs b/src/ModelContextProtocol.Core/Protocol/CreateTaskResult.cs index 8e68ba071..6d9bac23c 100644 --- a/src/ModelContextProtocol.Core/Protocol/CreateTaskResult.cs +++ b/src/ModelContextProtocol.Core/Protocol/CreateTaskResult.cs @@ -55,10 +55,11 @@ public sealed class CreateTaskResult : Result public required DateTimeOffset LastUpdatedAt { get; set; } /// - /// Gets or sets the time-to-live duration from creation in milliseconds, or for unlimited. + /// Gets or sets the time-to-live duration from creation, or for unlimited. /// [JsonPropertyName("ttlMs")] - public long? TimeToLiveMs { get; set; } + [JsonConverter(typeof(TimeSpanMillisecondsConverter))] + public TimeSpan? TimeToLive { get; set; } /// /// Gets or sets the suggested polling interval in milliseconds. diff --git a/src/ModelContextProtocol.Core/Protocol/GetTaskResult.cs b/src/ModelContextProtocol.Core/Protocol/GetTaskResult.cs index e7d29ea6f..3fea8cc6b 100644 --- a/src/ModelContextProtocol.Core/Protocol/GetTaskResult.cs +++ b/src/ModelContextProtocol.Core/Protocol/GetTaskResult.cs @@ -64,10 +64,10 @@ private protected GetTaskResult() public required DateTimeOffset LastUpdatedAt { get; set; } /// - /// Gets or sets the time-to-live duration from creation in milliseconds, or for unlimited. + /// Gets or sets the time-to-live duration from creation, or for unlimited. /// [JsonPropertyName("ttlMs")] - public long? TimeToLiveMs { get; set; } + public TimeSpan? TimeToLive { get; set; } /// /// Gets or sets the suggested polling interval in milliseconds. @@ -245,7 +245,7 @@ internal sealed class Converter : JsonConverter }; taskResult.StatusMessage = statusMessage; - taskResult.TimeToLiveMs = ttlMs; + taskResult.TimeToLive = ttlMs is null ? null : TimeSpan.FromMilliseconds(ttlMs.Value); taskResult.PollIntervalMs = pollIntervalMs; taskResult.ResultType = resultType; taskResult.Meta = meta; @@ -287,9 +287,9 @@ public override void Write(Utf8JsonWriter writer, GetTaskResult value, JsonSeria writer.WriteString("createdAt", value.CreatedAt); writer.WriteString("lastUpdatedAt", value.LastUpdatedAt); - if (value.TimeToLiveMs is not null) + if (value.TimeToLive is not null) { - writer.WriteNumber("ttlMs", value.TimeToLiveMs.Value); + writer.WriteNumber("ttlMs", (long)value.TimeToLive.Value.TotalMilliseconds); } if (value.PollIntervalMs is not null) diff --git a/src/ModelContextProtocol.Core/Protocol/TaskStatusNotificationParams.cs b/src/ModelContextProtocol.Core/Protocol/TaskStatusNotificationParams.cs index b3094d85a..4e859a22c 100644 --- a/src/ModelContextProtocol.Core/Protocol/TaskStatusNotificationParams.cs +++ b/src/ModelContextProtocol.Core/Protocol/TaskStatusNotificationParams.cs @@ -70,10 +70,10 @@ private protected TaskStatusNotificationParams() public required DateTimeOffset LastUpdatedAt { get; set; } /// - /// Gets or sets the time-to-live duration from creation in milliseconds, or for unlimited. + /// Gets or sets the time-to-live duration from creation, or for unlimited. /// [JsonPropertyName("ttlMs")] - public long? TimeToLiveMs { get; set; } + public TimeSpan? TimeToLive { get; set; } /// /// Gets or sets the suggested polling interval in milliseconds. @@ -247,7 +247,7 @@ internal sealed class Converter : JsonConverter }; notification.StatusMessage = statusMessage; - notification.TimeToLiveMs = ttlMs; + notification.TimeToLive = ttlMs is null ? null : TimeSpan.FromMilliseconds(ttlMs.Value); notification.PollIntervalMs = pollIntervalMs; notification.Meta = meta; @@ -283,9 +283,9 @@ public override void Write(Utf8JsonWriter writer, TaskStatusNotificationParams v writer.WriteString("createdAt", value.CreatedAt); writer.WriteString("lastUpdatedAt", value.LastUpdatedAt); - if (value.TimeToLiveMs is not null) + if (value.TimeToLive is not null) { - writer.WriteNumber("ttlMs", value.TimeToLiveMs.Value); + writer.WriteNumber("ttlMs", (long)value.TimeToLive.Value.TotalMilliseconds); } if (value.PollIntervalMs is not null) diff --git a/src/ModelContextProtocol.Core/Protocol/TimeSpanMillisecondsConverter.cs b/src/ModelContextProtocol.Core/Protocol/TimeSpanMillisecondsConverter.cs new file mode 100644 index 000000000..9ad646b48 --- /dev/null +++ b/src/ModelContextProtocol.Core/Protocol/TimeSpanMillisecondsConverter.cs @@ -0,0 +1,19 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace ModelContextProtocol.Protocol; + +/// +/// Provides a JSON converter for values that serializes and deserializes +/// them as a whole-millisecond integer. +/// +public sealed class TimeSpanMillisecondsConverter : JsonConverter +{ + /// + public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => TimeSpan.FromMilliseconds(reader.GetDouble()); + + /// + public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) + => writer.WriteNumberValue((long)value.TotalMilliseconds); +} diff --git a/src/ModelContextProtocol.Core/Server/InMemoryMcpTaskStore.cs b/src/ModelContextProtocol.Core/Server/InMemoryMcpTaskStore.cs index 7be53c17d..00c69844b 100644 --- a/src/ModelContextProtocol.Core/Server/InMemoryMcpTaskStore.cs +++ b/src/ModelContextProtocol.Core/Server/InMemoryMcpTaskStore.cs @@ -32,9 +32,9 @@ public class InMemoryMcpTaskStore : IMcpTaskStore public long DefaultPollIntervalMs { get; set; } = 1000; /// - /// Gets or sets the default time-to-live in milliseconds for new tasks, or for unlimited. + /// Gets or sets the default time-to-live for new tasks, or for unlimited. /// - public long? DefaultTimeToLiveMs { get; set; } + public TimeSpan? DefaultTimeToLive { get; set; } /// public Task CreateTaskAsync(CancellationToken cancellationToken = default) @@ -42,7 +42,7 @@ public Task CreateTaskAsync(CancellationToken cancellationToken = d var taskId = Guid.NewGuid().ToString("N"); var now = DateTimeOffset.UtcNow; - var info = new McpTaskInfo(taskId, McpTaskStatus.Working, now, now, DefaultTimeToLiveMs, DefaultPollIntervalMs); + var info = new McpTaskInfo(taskId, McpTaskStatus.Working, now, now, DefaultTimeToLive, DefaultPollIntervalMs); _tasks[taskId] = info; return Task.FromResult(info); diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs index 84f0b7e6b..1cce71c70 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs @@ -1009,7 +1009,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) Status = info.Status, CreatedAt = info.CreatedAt, LastUpdatedAt = info.LastUpdatedAt, - TimeToLiveMs = info.TimeToLiveMs, + TimeToLive = info.TimeToLive, PollIntervalMs = info.PollIntervalMs, StatusMessage = info.StatusMessage, ResultType = "task", @@ -1022,7 +1022,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) TaskId = info.TaskId, CreatedAt = info.CreatedAt, LastUpdatedAt = info.LastUpdatedAt, - TimeToLiveMs = info.TimeToLiveMs, + TimeToLive = info.TimeToLive, PollIntervalMs = info.PollIntervalMs, StatusMessage = info.StatusMessage, ResultType = "complete", @@ -1032,7 +1032,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) TaskId = info.TaskId, CreatedAt = info.CreatedAt, LastUpdatedAt = info.LastUpdatedAt, - TimeToLiveMs = info.TimeToLiveMs, + TimeToLive = info.TimeToLive, PollIntervalMs = info.PollIntervalMs, StatusMessage = info.StatusMessage, Result = info.Result ?? throw new InvalidOperationException($"Task '{info.TaskId}' is completed but has no result."), @@ -1043,7 +1043,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) TaskId = info.TaskId, CreatedAt = info.CreatedAt, LastUpdatedAt = info.LastUpdatedAt, - TimeToLiveMs = info.TimeToLiveMs, + TimeToLive = info.TimeToLive, PollIntervalMs = info.PollIntervalMs, StatusMessage = info.StatusMessage, Error = info.Error ?? throw new InvalidOperationException($"Task '{info.TaskId}' is failed but has no error."), @@ -1054,7 +1054,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) TaskId = info.TaskId, CreatedAt = info.CreatedAt, LastUpdatedAt = info.LastUpdatedAt, - TimeToLiveMs = info.TimeToLiveMs, + TimeToLive = info.TimeToLive, PollIntervalMs = info.PollIntervalMs, StatusMessage = info.StatusMessage, ResultType = "complete", @@ -1064,7 +1064,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) TaskId = info.TaskId, CreatedAt = info.CreatedAt, LastUpdatedAt = info.LastUpdatedAt, - TimeToLiveMs = info.TimeToLiveMs, + TimeToLive = info.TimeToLive, PollIntervalMs = info.PollIntervalMs, StatusMessage = info.StatusMessage, // McpTaskInfo.InputRequests is IReadOnlyDictionary (covers immutable store diff --git a/src/ModelContextProtocol.Core/Server/McpTaskInfo.cs b/src/ModelContextProtocol.Core/Server/McpTaskInfo.cs index 855116c97..87fa8ef1c 100644 --- a/src/ModelContextProtocol.Core/Server/McpTaskInfo.cs +++ b/src/ModelContextProtocol.Core/Server/McpTaskInfo.cs @@ -20,7 +20,7 @@ public sealed record McpTaskInfo( McpTaskStatus Status, DateTimeOffset CreatedAt, DateTimeOffset LastUpdatedAt, - long? TimeToLiveMs = null, + TimeSpan? TimeToLive = null, long? PollIntervalMs = null, string? StatusMessage = null, JsonElement? Result = null, diff --git a/tests/ModelContextProtocol.Tests/Protocol/TaskSerializationTests.cs b/tests/ModelContextProtocol.Tests/Protocol/TaskSerializationTests.cs index b57980660..86acc57f6 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/TaskSerializationTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/TaskSerializationTests.cs @@ -21,7 +21,7 @@ public static void CreateTaskResult_SerializationRoundTrip_PreservesAllPropertie StatusMessage = "Processing...", CreatedAt = new DateTimeOffset(2025, 6, 1, 12, 0, 0, TimeSpan.Zero), LastUpdatedAt = new DateTimeOffset(2025, 6, 1, 12, 5, 0, TimeSpan.Zero), - TimeToLiveMs = 3600000, + TimeToLive = TimeSpan.FromHours(1), PollIntervalMs = 5000, ResultType = "task", Meta = new JsonObject { ["key"] = "value" } @@ -36,7 +36,7 @@ public static void CreateTaskResult_SerializationRoundTrip_PreservesAllPropertie Assert.Equal("Processing...", deserialized.StatusMessage); Assert.Equal(original.CreatedAt, deserialized.CreatedAt); Assert.Equal(original.LastUpdatedAt, deserialized.LastUpdatedAt); - Assert.Equal(3600000, deserialized.TimeToLiveMs); + Assert.Equal(TimeSpan.FromHours(1), deserialized.TimeToLive); Assert.Equal(5000, deserialized.PollIntervalMs); Assert.Equal("task", deserialized.ResultType); Assert.NotNull(deserialized.Meta); @@ -52,7 +52,7 @@ public static void CreateTaskResult_UsesCorrectWireFieldNames() Status = McpTaskStatus.Working, CreatedAt = DateTimeOffset.UtcNow, LastUpdatedAt = DateTimeOffset.UtcNow, - TimeToLiveMs = 60000, + TimeToLive = TimeSpan.FromMinutes(1), PollIntervalMs = 1000, ResultType = "task", }; diff --git a/tests/ModelContextProtocol.Tests/Server/InMemoryMcpTaskStoreTests.cs b/tests/ModelContextProtocol.Tests/Server/InMemoryMcpTaskStoreTests.cs index 09a230747..83afbfe23 100644 --- a/tests/ModelContextProtocol.Tests/Server/InMemoryMcpTaskStoreTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/InMemoryMcpTaskStoreTests.cs @@ -57,11 +57,11 @@ public async Task CreateTaskAsync_UsesDefaultPollInterval() [Fact] public async Task CreateTaskAsync_UsesDefaultTimeToLive() { - var store = new InMemoryMcpTaskStore { DefaultTimeToLiveMs = 30000 }; + var store = new InMemoryMcpTaskStore { DefaultTimeToLive = TimeSpan.FromSeconds(30) }; var result = await store.CreateTaskAsync(CT); - Assert.Equal(30000, result.TimeToLiveMs); + Assert.Equal(TimeSpan.FromSeconds(30), result.TimeToLive); } [Fact] diff --git a/tests/ModelContextProtocol.Tests/Server/TaskCancellationIntegrationTests.cs b/tests/ModelContextProtocol.Tests/Server/TaskCancellationIntegrationTests.cs index 91609d817..0e8693353 100644 --- a/tests/ModelContextProtocol.Tests/Server/TaskCancellationIntegrationTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/TaskCancellationIntegrationTests.cs @@ -34,7 +34,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer options.TaskStore = new InMemoryMcpTaskStore { DefaultPollIntervalMs = 50, - DefaultTimeToLiveMs = 5000, + DefaultTimeToLive = TimeSpan.FromSeconds(5), }; });