diff --git a/src/ModelContextProtocol.Core/CompatibilitySuppressions.xml b/src/ModelContextProtocol.Core/CompatibilitySuppressions.xml
index 994399364..e686b454f 100644
--- a/src/ModelContextProtocol.Core/CompatibilitySuppressions.xml
+++ b/src/ModelContextProtocol.Core/CompatibilitySuppressions.xml
@@ -827,13 +827,6 @@
lib/net10.0/ModelContextProtocol.Core.dll
true
-
- CP0002
- M:ModelContextProtocol.Protocol.GetTaskResult.get_TimeToLive
- lib/net10.0/ModelContextProtocol.Core.dll
- lib/net10.0/ModelContextProtocol.Core.dll
- true
-
CP0002
M:ModelContextProtocol.Protocol.GetTaskResult.set_PollInterval(System.Nullable{System.TimeSpan})
@@ -848,13 +841,6 @@
lib/net10.0/ModelContextProtocol.Core.dll
true
-
- CP0002
- M:ModelContextProtocol.Protocol.GetTaskResult.set_TimeToLive(System.Nullable{System.TimeSpan})
- lib/net10.0/ModelContextProtocol.Core.dll
- lib/net10.0/ModelContextProtocol.Core.dll
- true
-
CP0002
M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks
@@ -1212,13 +1198,6 @@
lib/net8.0/ModelContextProtocol.Core.dll
true
-
- CP0002
- M:ModelContextProtocol.Protocol.GetTaskResult.get_TimeToLive
- lib/net8.0/ModelContextProtocol.Core.dll
- lib/net8.0/ModelContextProtocol.Core.dll
- true
-
CP0002
M:ModelContextProtocol.Protocol.GetTaskResult.set_PollInterval(System.Nullable{System.TimeSpan})
@@ -1233,13 +1212,6 @@
lib/net8.0/ModelContextProtocol.Core.dll
true
-
- CP0002
- M:ModelContextProtocol.Protocol.GetTaskResult.set_TimeToLive(System.Nullable{System.TimeSpan})
- lib/net8.0/ModelContextProtocol.Core.dll
- lib/net8.0/ModelContextProtocol.Core.dll
- true
-
CP0002
M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks
@@ -1597,13 +1569,6 @@
lib/net9.0/ModelContextProtocol.Core.dll
true
-
- CP0002
- M:ModelContextProtocol.Protocol.GetTaskResult.get_TimeToLive
- lib/net9.0/ModelContextProtocol.Core.dll
- lib/net9.0/ModelContextProtocol.Core.dll
- true
-
CP0002
M:ModelContextProtocol.Protocol.GetTaskResult.set_PollInterval(System.Nullable{System.TimeSpan})
@@ -1618,13 +1583,6 @@
lib/net9.0/ModelContextProtocol.Core.dll
true
-
- CP0002
- M:ModelContextProtocol.Protocol.GetTaskResult.set_TimeToLive(System.Nullable{System.TimeSpan})
- lib/net9.0/ModelContextProtocol.Core.dll
- lib/net9.0/ModelContextProtocol.Core.dll
- true
-
CP0002
M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks
@@ -1982,13 +1940,6 @@
lib/netstandard2.0/ModelContextProtocol.Core.dll
true
-
- CP0002
- M:ModelContextProtocol.Protocol.GetTaskResult.get_TimeToLive
- lib/netstandard2.0/ModelContextProtocol.Core.dll
- lib/netstandard2.0/ModelContextProtocol.Core.dll
- true
-
CP0002
M:ModelContextProtocol.Protocol.GetTaskResult.set_PollInterval(System.Nullable{System.TimeSpan})
@@ -2003,13 +1954,6 @@
lib/netstandard2.0/ModelContextProtocol.Core.dll
true
-
- CP0002
- M:ModelContextProtocol.Protocol.GetTaskResult.set_TimeToLive(System.Nullable{System.TimeSpan})
- lib/netstandard2.0/ModelContextProtocol.Core.dll
- lib/netstandard2.0/ModelContextProtocol.Core.dll
- true
-
CP0002
M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks
diff --git a/src/ModelContextProtocol.Core/Protocol/CreateTaskResult.cs b/src/ModelContextProtocol.Core/Protocol/CreateTaskResult.cs
index 2e5bc0c41..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? TtlMs { 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 9366f2374..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? TtlMs { 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.TtlMs = 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.TtlMs is not null)
+ if (value.TimeToLive is not null)
{
- writer.WriteNumber("ttlMs", value.TtlMs.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 e1bbb2f73..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? TtlMs { 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.TtlMs = 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.TtlMs is not null)
+ if (value.TimeToLive is not null)
{
- writer.WriteNumber("ttlMs", value.TtlMs.Value);
+ writer.WriteNumber("ttlMs", (long)value.TimeToLive.Value.TotalMilliseconds);
}
if (value.PollIntervalMs is not null)
diff --git a/src/ModelContextProtocol.Core/Server/InMemoryMcpTaskStore.cs b/src/ModelContextProtocol.Core/Server/InMemoryMcpTaskStore.cs
index a13af13e9..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? DefaultTtlMs { 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, DefaultTtlMs, 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 c66b24c79..4e0140893 100644
--- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs
+++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs
@@ -1022,7 +1022,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
Status = info.Status,
CreatedAt = info.CreatedAt,
LastUpdatedAt = info.LastUpdatedAt,
- TtlMs = info.TtlMs,
+ TimeToLive = info.TimeToLive,
PollIntervalMs = info.PollIntervalMs,
StatusMessage = info.StatusMessage,
ResultType = "task",
@@ -1035,7 +1035,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
TaskId = info.TaskId,
CreatedAt = info.CreatedAt,
LastUpdatedAt = info.LastUpdatedAt,
- TtlMs = info.TtlMs,
+ TimeToLive = info.TimeToLive,
PollIntervalMs = info.PollIntervalMs,
StatusMessage = info.StatusMessage,
ResultType = "complete",
@@ -1045,7 +1045,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
TaskId = info.TaskId,
CreatedAt = info.CreatedAt,
LastUpdatedAt = info.LastUpdatedAt,
- TtlMs = info.TtlMs,
+ TimeToLive = info.TimeToLive,
PollIntervalMs = info.PollIntervalMs,
StatusMessage = info.StatusMessage,
Result = info.Result ?? throw new InvalidOperationException($"Task '{info.TaskId}' is completed but has no result."),
@@ -1056,7 +1056,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
TaskId = info.TaskId,
CreatedAt = info.CreatedAt,
LastUpdatedAt = info.LastUpdatedAt,
- TtlMs = info.TtlMs,
+ TimeToLive = info.TimeToLive,
PollIntervalMs = info.PollIntervalMs,
StatusMessage = info.StatusMessage,
Error = info.Error ?? throw new InvalidOperationException($"Task '{info.TaskId}' is failed but has no error."),
@@ -1067,7 +1067,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
TaskId = info.TaskId,
CreatedAt = info.CreatedAt,
LastUpdatedAt = info.LastUpdatedAt,
- TtlMs = info.TtlMs,
+ TimeToLive = info.TimeToLive,
PollIntervalMs = info.PollIntervalMs,
StatusMessage = info.StatusMessage,
ResultType = "complete",
@@ -1077,7 +1077,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
TaskId = info.TaskId,
CreatedAt = info.CreatedAt,
LastUpdatedAt = info.LastUpdatedAt,
- TtlMs = info.TtlMs,
+ 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 ab71d6505..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? TtlMs = 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 556403add..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),
- TtlMs = 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.TtlMs);
+ 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,
- TtlMs = 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 7c87786eb..83afbfe23 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 { DefaultTimeToLive = TimeSpan.FromSeconds(30) };
var result = await store.CreateTaskAsync(CT);
- Assert.Equal(30000, result.TtlMs);
+ 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 1b424d002..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,
- DefaultTtlMs = 5000,
+ DefaultTimeToLive = TimeSpan.FromSeconds(5),
};
});