Implement SEP-2663 Tasks Extension (#1579)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
ed07fdb6f4
commit
dbb7a20176
19 changed files with 8190 additions and 11405 deletions
|
|
@ -17,7 +17,6 @@ public static class CallToolRequestParamsTests
|
|||
["city"] = JsonDocument.Parse("\"Seattle\"").RootElement.Clone(),
|
||||
["units"] = JsonDocument.Parse("\"metric\"").RootElement.Clone()
|
||||
},
|
||||
Task = new McpTaskMetadata { TimeToLive = TimeSpan.FromHours(1) },
|
||||
Meta = new JsonObject { ["progressToken"] = "token-123" }
|
||||
};
|
||||
|
||||
|
|
@ -30,8 +29,6 @@ public static class CallToolRequestParamsTests
|
|||
Assert.Equal(2, deserialized.Arguments.Count);
|
||||
Assert.Equal("Seattle", deserialized.Arguments["city"].GetString());
|
||||
Assert.Equal("metric", deserialized.Arguments["units"].GetString());
|
||||
Assert.NotNull(deserialized.Task);
|
||||
Assert.Equal(original.Task.TimeToLive, deserialized.Task.TimeToLive);
|
||||
Assert.NotNull(deserialized.Meta);
|
||||
Assert.Equal("token-123", (string)deserialized.Meta["progressToken"]!);
|
||||
}
|
||||
|
|
@ -50,7 +47,6 @@ public static class CallToolRequestParamsTests
|
|||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(original.Name, deserialized.Name);
|
||||
Assert.Null(deserialized.Arguments);
|
||||
Assert.Null(deserialized.Task);
|
||||
Assert.Null(deserialized.Meta);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,6 @@ public static class CallToolResultTests
|
|||
Content = [new TextContentBlock { Text = "Result text" }],
|
||||
StructuredContent = JsonElement.Parse("""{"temperature":72}"""),
|
||||
IsError = false,
|
||||
Task = new McpTask
|
||||
{
|
||||
TaskId = "task-1",
|
||||
Status = McpTaskStatus.Completed,
|
||||
CreatedAt = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero),
|
||||
LastUpdatedAt = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero)
|
||||
},
|
||||
Meta = new JsonObject { ["key"] = "value" }
|
||||
};
|
||||
|
||||
|
|
@ -34,8 +27,6 @@ public static class CallToolResultTests
|
|||
Assert.NotNull(deserialized.StructuredContent);
|
||||
Assert.Equal(72, deserialized.StructuredContent.Value.GetProperty("temperature").GetInt32());
|
||||
Assert.False(deserialized.IsError);
|
||||
Assert.NotNull(deserialized.Task);
|
||||
Assert.Equal("task-1", deserialized.Task.TaskId);
|
||||
Assert.NotNull(deserialized.Meta);
|
||||
Assert.Equal("value", (string)deserialized.Meta["key"]!);
|
||||
}
|
||||
|
|
@ -52,7 +43,6 @@ public static class CallToolResultTests
|
|||
Assert.Empty(deserialized.Content);
|
||||
Assert.Null(deserialized.StructuredContent);
|
||||
Assert.Null(deserialized.IsError);
|
||||
Assert.Null(deserialized.Task);
|
||||
Assert.Null(deserialized.Meta);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class CancelMcpTaskRequestParamsTests
|
||||
{
|
||||
[Fact]
|
||||
public static void CancelMcpTaskRequestParams_SerializationRoundTrip()
|
||||
{
|
||||
// Arrange
|
||||
var original = new CancelMcpTaskRequestParams
|
||||
{
|
||||
TaskId = "cancel-task-456"
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CancelMcpTaskRequestParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(original.TaskId, deserialized.TaskId);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class CancelMcpTaskResultTests
|
||||
{
|
||||
[Fact]
|
||||
public static void CancelMcpTaskResult_SerializationRoundTrip()
|
||||
{
|
||||
// Arrange
|
||||
var original = new CancelMcpTaskResult
|
||||
{
|
||||
TaskId = "cancelled-789",
|
||||
Status = McpTaskStatus.Cancelled,
|
||||
StatusMessage = "Cancelled by user",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
TimeToLive = null,
|
||||
PollInterval = null
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CancelMcpTaskResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(original.TaskId, deserialized.TaskId);
|
||||
Assert.Equal(original.Status, deserialized.Status);
|
||||
Assert.Equal(original.StatusMessage, deserialized.StatusMessage);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,6 @@ public static class ClientCapabilitiesTests
|
|||
Form = new FormElicitationCapability(),
|
||||
Url = new UrlElicitationCapability()
|
||||
},
|
||||
Tasks = new McpTasksCapability(),
|
||||
Extensions = new Dictionary<string, object>
|
||||
{
|
||||
["io.modelcontextprotocol/test"] = new object()
|
||||
|
|
@ -40,7 +39,6 @@ public static class ClientCapabilitiesTests
|
|||
Assert.NotNull(deserialized.Elicitation);
|
||||
Assert.NotNull(deserialized.Elicitation.Form);
|
||||
Assert.NotNull(deserialized.Elicitation.Url);
|
||||
Assert.NotNull(deserialized.Tasks);
|
||||
Assert.NotNull(deserialized.Extensions);
|
||||
Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/test"));
|
||||
}
|
||||
|
|
@ -58,7 +56,6 @@ public static class ClientCapabilitiesTests
|
|||
Assert.Null(deserialized.Roots);
|
||||
Assert.Null(deserialized.Sampling);
|
||||
Assert.Null(deserialized.Elicitation);
|
||||
Assert.Null(deserialized.Tasks);
|
||||
Assert.Null(deserialized.Extensions);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class CreateTaskResultTests
|
||||
{
|
||||
[Fact]
|
||||
public static void CreateTaskResult_SerializationRoundTrip_PreservesAllProperties()
|
||||
{
|
||||
var original = new CreateTaskResult
|
||||
{
|
||||
Task = new McpTask
|
||||
{
|
||||
TaskId = "task-123",
|
||||
Status = McpTaskStatus.Working,
|
||||
StatusMessage = "Processing",
|
||||
CreatedAt = new DateTimeOffset(2025, 6, 1, 12, 0, 0, TimeSpan.Zero),
|
||||
LastUpdatedAt = new DateTimeOffset(2025, 6, 1, 12, 5, 0, TimeSpan.Zero),
|
||||
TimeToLive = TimeSpan.FromHours(1),
|
||||
PollInterval = TimeSpan.FromSeconds(5)
|
||||
},
|
||||
Meta = new JsonObject { ["key"] = "value" }
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateTaskResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal("task-123", deserialized.Task.TaskId);
|
||||
Assert.Equal(McpTaskStatus.Working, deserialized.Task.Status);
|
||||
Assert.Equal("Processing", deserialized.Task.StatusMessage);
|
||||
Assert.Equal(original.Task.CreatedAt, deserialized.Task.CreatedAt);
|
||||
Assert.Equal(original.Task.LastUpdatedAt, deserialized.Task.LastUpdatedAt);
|
||||
Assert.Equal(original.Task.TimeToLive, deserialized.Task.TimeToLive);
|
||||
Assert.Equal(original.Task.PollInterval, deserialized.Task.PollInterval);
|
||||
Assert.NotNull(deserialized.Meta);
|
||||
Assert.Equal("value", (string)deserialized.Meta["key"]!);
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ public static class ElicitRequestParamsTests
|
|||
["age"] = new ElicitRequestParams.NumberSchema { Description = "Your age" }
|
||||
}
|
||||
},
|
||||
Task = new McpTaskMetadata { TimeToLive = TimeSpan.FromMinutes(10) },
|
||||
Meta = new JsonObject { ["progressToken"] = "tok-1" }
|
||||
};
|
||||
|
||||
|
|
@ -37,8 +36,6 @@ public static class ElicitRequestParamsTests
|
|||
Assert.Equal("Please provide your details", deserialized.Message);
|
||||
Assert.NotNull(deserialized.RequestedSchema);
|
||||
Assert.Equal(2, deserialized.RequestedSchema.Properties.Count);
|
||||
Assert.NotNull(deserialized.Task);
|
||||
Assert.Equal(TimeSpan.FromMinutes(10), deserialized.Task.TimeToLive);
|
||||
Assert.NotNull(deserialized.Meta);
|
||||
Assert.Equal("tok-1", (string)deserialized.Meta["progressToken"]!);
|
||||
}
|
||||
|
|
@ -63,7 +60,6 @@ public static class ElicitRequestParamsTests
|
|||
Assert.Equal("https://example.com/auth", deserialized.Url);
|
||||
Assert.Equal("Please authenticate", deserialized.Message);
|
||||
Assert.Null(deserialized.RequestedSchema);
|
||||
Assert.Null(deserialized.Task);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -83,7 +79,6 @@ public static class ElicitRequestParamsTests
|
|||
Assert.Null(deserialized.ElicitationId);
|
||||
Assert.Null(deserialized.Url);
|
||||
Assert.Null(deserialized.RequestedSchema);
|
||||
Assert.Null(deserialized.Task);
|
||||
Assert.Null(deserialized.Meta);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class GetTaskPayloadRequestParamsTests
|
||||
{
|
||||
[Fact]
|
||||
public static void GetTaskPayloadRequestParams_SerializationRoundTrip()
|
||||
{
|
||||
// Arrange
|
||||
var original = new GetTaskPayloadRequestParams
|
||||
{
|
||||
TaskId = "payload-task-999"
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<GetTaskPayloadRequestParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(original.TaskId, deserialized.TaskId);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class GetTaskRequestParamsTests
|
||||
{
|
||||
[Fact]
|
||||
public static void GetTaskRequestParams_SerializationRoundTrip()
|
||||
{
|
||||
// Arrange
|
||||
var original = new GetTaskRequestParams
|
||||
{
|
||||
TaskId = "get-task-123"
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<GetTaskRequestParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(original.TaskId, deserialized.TaskId);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class GetTaskResultTests
|
||||
{
|
||||
[Fact]
|
||||
public static void GetTaskResult_SerializationRoundTrip()
|
||||
{
|
||||
// Arrange
|
||||
var original = new GetTaskResult
|
||||
{
|
||||
TaskId = "result-123",
|
||||
Status = McpTaskStatus.Completed,
|
||||
StatusMessage = "Done",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
TimeToLive = TimeSpan.FromHours(1),
|
||||
PollInterval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(original.TaskId, deserialized.TaskId);
|
||||
Assert.Equal(original.Status, deserialized.Status);
|
||||
Assert.Equal(original.StatusMessage, deserialized.StatusMessage);
|
||||
Assert.Equal(original.CreatedAt, deserialized.CreatedAt);
|
||||
Assert.Equal(original.LastUpdatedAt, deserialized.LastUpdatedAt);
|
||||
Assert.Equal(original.TimeToLive, deserialized.TimeToLive);
|
||||
Assert.Equal(original.PollInterval, deserialized.PollInterval);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class ListTasksRequestParamsTests
|
||||
{
|
||||
[Fact]
|
||||
public static void ListTasksRequestParams_SerializationRoundTrip()
|
||||
{
|
||||
// Arrange
|
||||
var original = new ListTasksRequestParams
|
||||
{
|
||||
Cursor = "cursor-abc123"
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<ListTasksRequestParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(original.Cursor, deserialized.Cursor);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class ListTasksResultTests
|
||||
{
|
||||
[Fact]
|
||||
public static void ListTasksResult_SerializationRoundTrip()
|
||||
{
|
||||
// Arrange
|
||||
var original = new ListTasksResult
|
||||
{
|
||||
Tasks =
|
||||
[
|
||||
new McpTask
|
||||
{
|
||||
TaskId = "task-1",
|
||||
Status = McpTaskStatus.Working,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow
|
||||
},
|
||||
new McpTask
|
||||
{
|
||||
TaskId = "task-2",
|
||||
Status = McpTaskStatus.Completed,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow
|
||||
}
|
||||
],
|
||||
NextCursor = "next-page-token"
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<ListTasksResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.NotNull(deserialized.Tasks);
|
||||
Assert.Equal(2, deserialized.Tasks.Count);
|
||||
Assert.Equal(original.Tasks[0].TaskId, deserialized.Tasks[0].TaskId);
|
||||
Assert.Equal(original.Tasks[1].TaskId, deserialized.Tasks[1].TaskId);
|
||||
Assert.Equal(original.NextCursor, deserialized.NextCursor);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class McpTaskMetadataTests
|
||||
{
|
||||
[Fact]
|
||||
public static void McpTaskMetadata_SerializationRoundTrip_WithTimeToLive()
|
||||
{
|
||||
// Arrange
|
||||
var original = new McpTaskMetadata
|
||||
{
|
||||
TimeToLive = TimeSpan.FromHours(2)
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<McpTaskMetadata>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(original.TimeToLive, deserialized.TimeToLive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void McpTaskMetadata_SerializationRoundTrip_WithNullTimeToLive()
|
||||
{
|
||||
// Arrange
|
||||
var original = new McpTaskMetadata();
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<McpTaskMetadata>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Null(deserialized.TimeToLive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void McpTaskMetadata_HasCorrectJsonPropertyNames()
|
||||
{
|
||||
var metadata = new McpTaskMetadata
|
||||
{
|
||||
TimeToLive = TimeSpan.FromMinutes(15)
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(metadata, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.Contains("\"ttl\":", json);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class McpTaskStatusNotificationParamsTests
|
||||
{
|
||||
[Fact]
|
||||
public static void McpTaskStatusNotificationParams_SerializationRoundTrip()
|
||||
{
|
||||
// Arrange
|
||||
var original = new McpTaskStatusNotificationParams
|
||||
{
|
||||
TaskId = "notification-task",
|
||||
Status = McpTaskStatus.Completed,
|
||||
StatusMessage = "Task completed successfully",
|
||||
CreatedAt = new DateTimeOffset(2025, 12, 9, 10, 0, 0, TimeSpan.Zero),
|
||||
LastUpdatedAt = new DateTimeOffset(2025, 12, 9, 10, 30, 0, TimeSpan.Zero),
|
||||
TimeToLive = TimeSpan.FromHours(1),
|
||||
PollInterval = TimeSpan.FromSeconds(2)
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<McpTaskStatusNotificationParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(original.TaskId, deserialized.TaskId);
|
||||
Assert.Equal(original.Status, deserialized.Status);
|
||||
Assert.Equal(original.StatusMessage, deserialized.StatusMessage);
|
||||
Assert.Equal(original.CreatedAt, deserialized.CreatedAt);
|
||||
Assert.Equal(original.LastUpdatedAt, deserialized.LastUpdatedAt);
|
||||
Assert.Equal(original.TimeToLive, deserialized.TimeToLive);
|
||||
Assert.Equal(original.PollInterval, deserialized.PollInterval);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class McpTaskTests
|
||||
{
|
||||
[Fact]
|
||||
public static void McpTask_SerializationRoundTrip_PreservesAllProperties()
|
||||
{
|
||||
// Arrange
|
||||
var original = new McpTask
|
||||
{
|
||||
TaskId = "task-12345",
|
||||
Status = McpTaskStatus.Working,
|
||||
StatusMessage = "Processing request",
|
||||
CreatedAt = new DateTimeOffset(2025, 12, 9, 10, 30, 0, TimeSpan.Zero),
|
||||
LastUpdatedAt = new DateTimeOffset(2025, 12, 9, 10, 35, 0, TimeSpan.Zero),
|
||||
TimeToLive = TimeSpan.FromHours(24),
|
||||
PollInterval = TimeSpan.FromSeconds(5)
|
||||
};
|
||||
|
||||
// Act - Serialize to JSON
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Act - Deserialize back from JSON
|
||||
var deserialized = JsonSerializer.Deserialize<McpTask>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(original.TaskId, deserialized.TaskId);
|
||||
Assert.Equal(original.Status, deserialized.Status);
|
||||
Assert.Equal(original.StatusMessage, deserialized.StatusMessage);
|
||||
Assert.Equal(original.CreatedAt, deserialized.CreatedAt);
|
||||
Assert.Equal(original.LastUpdatedAt, deserialized.LastUpdatedAt);
|
||||
Assert.Equal(original.TimeToLive, deserialized.TimeToLive);
|
||||
Assert.Equal(original.PollInterval, deserialized.PollInterval);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void McpTask_SerializationRoundTrip_WithMinimalProperties()
|
||||
{
|
||||
// Arrange
|
||||
var original = new McpTask
|
||||
{
|
||||
TaskId = "task-minimal",
|
||||
Status = McpTaskStatus.Completed,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
// Act - Serialize to JSON
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Act - Deserialize back from JSON
|
||||
var deserialized = JsonSerializer.Deserialize<McpTask>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(original.TaskId, deserialized.TaskId);
|
||||
Assert.Equal(original.Status, deserialized.Status);
|
||||
Assert.Null(deserialized.StatusMessage);
|
||||
Assert.Equal(original.CreatedAt, deserialized.CreatedAt);
|
||||
Assert.Equal(original.LastUpdatedAt, deserialized.LastUpdatedAt);
|
||||
Assert.Null(deserialized.TimeToLive);
|
||||
Assert.Null(deserialized.PollInterval);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void McpTask_HasCorrectJsonPropertyNames()
|
||||
{
|
||||
var task = new McpTask
|
||||
{
|
||||
TaskId = "test-task",
|
||||
Status = McpTaskStatus.Working,
|
||||
StatusMessage = "Test message",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
TimeToLive = TimeSpan.FromMinutes(30),
|
||||
PollInterval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(task, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.Contains("\"taskId\":", json);
|
||||
Assert.Contains("\"status\":", json);
|
||||
Assert.Contains("\"statusMessage\":", json);
|
||||
Assert.Contains("\"createdAt\":", json);
|
||||
Assert.Contains("\"lastUpdatedAt\":", json);
|
||||
Assert.Contains("\"ttl\":", json);
|
||||
Assert.Contains("\"pollInterval\":", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void McpTask_TimeToLive_SerializesAsMilliseconds()
|
||||
{
|
||||
var task = new McpTask
|
||||
{
|
||||
TaskId = "test-ttl",
|
||||
Status = McpTaskStatus.Working,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
TimeToLive = TimeSpan.FromSeconds(60)
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(task, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.Contains("\"ttl\":60000", json);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(McpTaskStatus.Working)]
|
||||
[InlineData(McpTaskStatus.InputRequired)]
|
||||
[InlineData(McpTaskStatus.Completed)]
|
||||
[InlineData(McpTaskStatus.Failed)]
|
||||
[InlineData(McpTaskStatus.Cancelled)]
|
||||
public static void McpTaskStatus_SerializesCorrectly(McpTaskStatus status)
|
||||
{
|
||||
var task = new McpTask
|
||||
{
|
||||
TaskId = "status-test",
|
||||
Status = status,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(task, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<McpTask>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(status, deserialized.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void McpTaskStatus_HasCorrectJsonValues()
|
||||
{
|
||||
var statuses = new[]
|
||||
{
|
||||
(McpTaskStatus.Working, "working"),
|
||||
(McpTaskStatus.InputRequired, "input_required"),
|
||||
(McpTaskStatus.Completed, "completed"),
|
||||
(McpTaskStatus.Failed, "failed"),
|
||||
(McpTaskStatus.Cancelled, "cancelled")
|
||||
};
|
||||
|
||||
foreach (var (status, expectedJson) in statuses)
|
||||
{
|
||||
var task = new McpTask
|
||||
{
|
||||
TaskId = "test",
|
||||
Status = status,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(task, McpJsonUtilities.DefaultOptions);
|
||||
Assert.Contains($"\"status\":\"{expectedJson}\"", json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class McpTasksCapabilityTests
|
||||
{
|
||||
[Fact]
|
||||
public static void McpTasksCapability_SerializationRoundTrip_WithAllProperties()
|
||||
{
|
||||
// Arrange
|
||||
var original = new McpTasksCapability
|
||||
{
|
||||
List = new ListMcpTasksCapability(),
|
||||
Cancel = new CancelMcpTasksCapability(),
|
||||
Requests = new RequestMcpTasksCapability
|
||||
{
|
||||
Tools = new ToolsMcpTasksCapability
|
||||
{
|
||||
Call = new CallToolMcpTasksCapability()
|
||||
},
|
||||
Sampling = new SamplingMcpTasksCapability
|
||||
{
|
||||
CreateMessage = new CreateMessageMcpTasksCapability()
|
||||
},
|
||||
Elicitation = new ElicitationMcpTasksCapability
|
||||
{
|
||||
Create = new CreateElicitationMcpTasksCapability()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<McpTasksCapability>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.NotNull(deserialized.List);
|
||||
Assert.NotNull(deserialized.Cancel);
|
||||
Assert.NotNull(deserialized.Requests);
|
||||
Assert.NotNull(deserialized.Requests.Tools);
|
||||
Assert.NotNull(deserialized.Requests.Tools.Call);
|
||||
Assert.NotNull(deserialized.Requests.Sampling);
|
||||
Assert.NotNull(deserialized.Requests.Sampling.CreateMessage);
|
||||
Assert.NotNull(deserialized.Requests.Elicitation);
|
||||
Assert.NotNull(deserialized.Requests.Elicitation.Create);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void McpTasksCapability_SerializationRoundTrip_WithMinimalProperties()
|
||||
{
|
||||
// Arrange
|
||||
var original = new McpTasksCapability();
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<McpTasksCapability>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Null(deserialized.List);
|
||||
Assert.Null(deserialized.Cancel);
|
||||
Assert.Null(deserialized.Requests);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void McpTasksCapability_HasCorrectJsonPropertyNames()
|
||||
{
|
||||
var capability = new McpTasksCapability
|
||||
{
|
||||
List = new ListMcpTasksCapability(),
|
||||
Cancel = new CancelMcpTasksCapability(),
|
||||
Requests = new RequestMcpTasksCapability
|
||||
{
|
||||
Tools = new ToolsMcpTasksCapability
|
||||
{
|
||||
Call = new CallToolMcpTasksCapability()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(capability, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.Contains("\"list\":", json);
|
||||
Assert.Contains("\"cancel\":", json);
|
||||
Assert.Contains("\"requests\":", json);
|
||||
Assert.Contains("\"tools\":", json);
|
||||
Assert.Contains("\"call\":", json);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class RequestMcpTasksCapabilityTests
|
||||
{
|
||||
[Fact]
|
||||
public static void RequestMcpTasksCapability_SerializationRoundTrip_ToolsOnly()
|
||||
{
|
||||
// Arrange
|
||||
var original = new RequestMcpTasksCapability
|
||||
{
|
||||
Tools = new ToolsMcpTasksCapability
|
||||
{
|
||||
Call = new CallToolMcpTasksCapability()
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<RequestMcpTasksCapability>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.NotNull(deserialized.Tools);
|
||||
Assert.NotNull(deserialized.Tools.Call);
|
||||
Assert.Null(deserialized.Sampling);
|
||||
Assert.Null(deserialized.Elicitation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void RequestMcpTasksCapability_SerializationRoundTrip_SamplingOnly()
|
||||
{
|
||||
// Arrange
|
||||
var original = new RequestMcpTasksCapability
|
||||
{
|
||||
Sampling = new SamplingMcpTasksCapability
|
||||
{
|
||||
CreateMessage = new CreateMessageMcpTasksCapability()
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<RequestMcpTasksCapability>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Null(deserialized.Tools);
|
||||
Assert.NotNull(deserialized.Sampling);
|
||||
Assert.NotNull(deserialized.Sampling.CreateMessage);
|
||||
Assert.Null(deserialized.Elicitation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void RequestMcpTasksCapability_SerializationRoundTrip_ElicitationOnly()
|
||||
{
|
||||
// Arrange
|
||||
var original = new RequestMcpTasksCapability
|
||||
{
|
||||
Elicitation = new ElicitationMcpTasksCapability
|
||||
{
|
||||
Create = new CreateElicitationMcpTasksCapability()
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<RequestMcpTasksCapability>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Null(deserialized.Tools);
|
||||
Assert.Null(deserialized.Sampling);
|
||||
Assert.NotNull(deserialized.Elicitation);
|
||||
Assert.NotNull(deserialized.Elicitation.Create);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void RequestMcpTasksCapability_HasCorrectJsonPropertyNames()
|
||||
{
|
||||
var capability = new RequestMcpTasksCapability
|
||||
{
|
||||
Tools = new ToolsMcpTasksCapability
|
||||
{
|
||||
Call = new CallToolMcpTasksCapability()
|
||||
},
|
||||
Sampling = new SamplingMcpTasksCapability
|
||||
{
|
||||
CreateMessage = new CreateMessageMcpTasksCapability()
|
||||
},
|
||||
Elicitation = new ElicitationMcpTasksCapability
|
||||
{
|
||||
Create = new CreateElicitationMcpTasksCapability()
|
||||
}
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(capability, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.Contains("\"tools\":", json);
|
||||
Assert.Contains("\"sampling\":", json);
|
||||
Assert.Contains("\"elicitation\":", json);
|
||||
Assert.Contains("\"call\":", json);
|
||||
Assert.Contains("\"createMessage\":", json);
|
||||
Assert.Contains("\"create\":", json);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ public static class ServerCapabilitiesTests
|
|||
Resources = new ResourcesCapability { Subscribe = true, ListChanged = true },
|
||||
Tools = new ToolsCapability { ListChanged = false },
|
||||
Completions = new CompletionsCapability(),
|
||||
Tasks = new McpTasksCapability(),
|
||||
Extensions = new Dictionary<string, object>
|
||||
{
|
||||
["io.modelcontextprotocol/apps"] = new object()
|
||||
|
|
@ -35,7 +34,6 @@ public static class ServerCapabilitiesTests
|
|||
Assert.NotNull(deserialized.Tools);
|
||||
Assert.False(deserialized.Tools.ListChanged);
|
||||
Assert.NotNull(deserialized.Completions);
|
||||
Assert.NotNull(deserialized.Tasks);
|
||||
Assert.NotNull(deserialized.Extensions);
|
||||
Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/apps"));
|
||||
}
|
||||
|
|
@ -55,7 +53,6 @@ public static class ServerCapabilitiesTests
|
|||
Assert.Null(deserialized.Resources);
|
||||
Assert.Null(deserialized.Tools);
|
||||
Assert.Null(deserialized.Completions);
|
||||
Assert.Null(deserialized.Tasks);
|
||||
Assert.Null(deserialized.Extensions);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,517 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// Serialization and deserialization tests for SEP-2663 task protocol types.
|
||||
/// </summary>
|
||||
public static class TaskSerializationTests
|
||||
{
|
||||
#region CreateTaskResult
|
||||
|
||||
[Fact]
|
||||
public static void CreateTaskResult_SerializationRoundTrip_PreservesAllProperties()
|
||||
{
|
||||
var original = new CreateTaskResult
|
||||
{
|
||||
TaskId = "task-123",
|
||||
Status = McpTaskStatus.Working,
|
||||
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,
|
||||
PollIntervalMs = 5000,
|
||||
ResultType = "task",
|
||||
Meta = new JsonObject { ["key"] = "value" }
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateTaskResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal("task-123", deserialized.TaskId);
|
||||
Assert.Equal(McpTaskStatus.Working, deserialized.Status);
|
||||
Assert.Equal("Processing...", deserialized.StatusMessage);
|
||||
Assert.Equal(original.CreatedAt, deserialized.CreatedAt);
|
||||
Assert.Equal(original.LastUpdatedAt, deserialized.LastUpdatedAt);
|
||||
Assert.Equal(3600000, deserialized.TtlMs);
|
||||
Assert.Equal(5000, deserialized.PollIntervalMs);
|
||||
Assert.Equal("task", deserialized.ResultType);
|
||||
Assert.NotNull(deserialized.Meta);
|
||||
Assert.Equal("value", (string)deserialized.Meta["key"]!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void CreateTaskResult_UsesCorrectWireFieldNames()
|
||||
{
|
||||
var result = new CreateTaskResult
|
||||
{
|
||||
TaskId = "t1",
|
||||
Status = McpTaskStatus.Working,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
TtlMs = 60000,
|
||||
PollIntervalMs = 1000,
|
||||
ResultType = "task",
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Must use camelCase wire names
|
||||
Assert.Contains("\"ttlMs\":", json);
|
||||
Assert.Contains("\"pollIntervalMs\":", json);
|
||||
Assert.Contains("\"taskId\":", json);
|
||||
Assert.Contains("\"resultType\":\"task\"", json);
|
||||
|
||||
// Must NOT contain legacy field names
|
||||
Assert.DoesNotContain("\"ttl\":", json);
|
||||
Assert.DoesNotContain("\"pollInterval\":", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void CreateTaskResult_ResultType_SerializesAsTask()
|
||||
{
|
||||
var result = new CreateTaskResult
|
||||
{
|
||||
TaskId = "t1",
|
||||
Status = McpTaskStatus.Working,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
ResultType = "task",
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions);
|
||||
var node = JsonNode.Parse(json)!;
|
||||
|
||||
Assert.Equal("task", (string)node["resultType"]!);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetTaskResult Subtypes
|
||||
|
||||
[Fact]
|
||||
public static void GetTaskResult_Working_RoundTrip()
|
||||
{
|
||||
var original = new WorkingTaskResult
|
||||
{
|
||||
TaskId = "w1",
|
||||
CreatedAt = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero),
|
||||
LastUpdatedAt = new DateTimeOffset(2025, 1, 1, 0, 1, 0, TimeSpan.Zero),
|
||||
StatusMessage = "In progress",
|
||||
PollIntervalMs = 2000,
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize<GetTaskResult>(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var working = Assert.IsType<WorkingTaskResult>(deserialized);
|
||||
Assert.Equal("w1", working.TaskId);
|
||||
Assert.Equal(McpTaskStatus.Working, working.Status);
|
||||
Assert.Equal("In progress", working.StatusMessage);
|
||||
Assert.Equal(2000, working.PollIntervalMs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void GetTaskResult_Completed_RoundTrip_IncludesResult()
|
||||
{
|
||||
var resultPayload = JsonElement.Parse("""{"content":[{"type":"text","text":"done"}]}""");
|
||||
var original = new CompletedTaskResult
|
||||
{
|
||||
TaskId = "c1",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
Result = resultPayload,
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize<GetTaskResult>(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var completed = Assert.IsType<CompletedTaskResult>(deserialized);
|
||||
Assert.Equal("c1", completed.TaskId);
|
||||
Assert.Equal(McpTaskStatus.Completed, completed.Status);
|
||||
Assert.Equal(JsonValueKind.Object, completed.Result.ValueKind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void GetTaskResult_Failed_RoundTrip_IncludesError()
|
||||
{
|
||||
var errorPayload = JsonElement.Parse("""{"code":-32000,"message":"internal error"}""");
|
||||
var original = new FailedTaskResult
|
||||
{
|
||||
TaskId = "f1",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
Error = errorPayload,
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize<GetTaskResult>(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var failed = Assert.IsType<FailedTaskResult>(deserialized);
|
||||
Assert.Equal("f1", failed.TaskId);
|
||||
Assert.Equal(McpTaskStatus.Failed, failed.Status);
|
||||
Assert.Equal(-32000, failed.Error.GetProperty("code").GetInt32());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void GetTaskResult_Cancelled_RoundTrip()
|
||||
{
|
||||
var original = new CancelledTaskResult
|
||||
{
|
||||
TaskId = "x1",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
StatusMessage = "User cancelled",
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize<GetTaskResult>(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var cancelled = Assert.IsType<CancelledTaskResult>(deserialized);
|
||||
Assert.Equal("x1", cancelled.TaskId);
|
||||
Assert.Equal(McpTaskStatus.Cancelled, cancelled.Status);
|
||||
Assert.Equal("User cancelled", cancelled.StatusMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void GetTaskResult_InputRequired_RoundTrip_IncludesInputRequests()
|
||||
{
|
||||
var inputRequests = new Dictionary<string, InputRequest>
|
||||
{
|
||||
["req-1"] = new InputRequest
|
||||
{
|
||||
Method = "elicitation/create",
|
||||
Params = JsonElement.Parse("""{"message":"Confirm?"}"""),
|
||||
}
|
||||
};
|
||||
var original = new InputRequiredTaskResult
|
||||
{
|
||||
TaskId = "i1",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
InputRequests = inputRequests,
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize<GetTaskResult>(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var inputRequired = Assert.IsType<InputRequiredTaskResult>(deserialized);
|
||||
Assert.Equal("i1", inputRequired.TaskId);
|
||||
Assert.Equal(McpTaskStatus.InputRequired, inputRequired.Status);
|
||||
Assert.NotNull(inputRequired.InputRequests);
|
||||
Assert.Single(inputRequired.InputRequests);
|
||||
Assert.True(inputRequired.InputRequests.ContainsKey("req-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void GetTaskResult_Converter_DispatchesToCorrectSubtypeByStatus()
|
||||
{
|
||||
var statuses = new (string status, Type expectedType)[]
|
||||
{
|
||||
("working", typeof(WorkingTaskResult)),
|
||||
("completed", typeof(CompletedTaskResult)),
|
||||
("failed", typeof(FailedTaskResult)),
|
||||
("cancelled", typeof(CancelledTaskResult)),
|
||||
("input_required", typeof(InputRequiredTaskResult)),
|
||||
};
|
||||
|
||||
foreach (var (status, expectedType) in statuses)
|
||||
{
|
||||
var json = status switch
|
||||
{
|
||||
"completed" => """{"taskId":"t","status":"completed","createdAt":"2025-01-01T00:00:00Z","lastUpdatedAt":"2025-01-01T00:00:00Z","result":{}}""",
|
||||
"failed" => """{"taskId":"t","status":"failed","createdAt":"2025-01-01T00:00:00Z","lastUpdatedAt":"2025-01-01T00:00:00Z","error":{}}""",
|
||||
"input_required" => """{"taskId":"t","status":"input_required","createdAt":"2025-01-01T00:00:00Z","lastUpdatedAt":"2025-01-01T00:00:00Z","inputRequests":{}}""",
|
||||
_ => $$$"""{"taskId":"t","status":"{{{status}}}","createdAt":"2025-01-01T00:00:00Z","lastUpdatedAt":"2025-01-01T00:00:00Z"}""",
|
||||
};
|
||||
|
||||
var result = JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType(expectedType, result);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void GetTaskResult_MissingTaskId_ThrowsJsonException()
|
||||
{
|
||||
var json = """{"status":"working","createdAt":"2025-01-01T00:00:00Z","lastUpdatedAt":"2025-01-01T00:00:00Z"}""";
|
||||
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void GetTaskResult_MissingStatus_ThrowsJsonException()
|
||||
{
|
||||
var json = """{"taskId":"t","createdAt":"2025-01-01T00:00:00Z","lastUpdatedAt":"2025-01-01T00:00:00Z"}""";
|
||||
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void GetTaskResult_UnknownStatus_ThrowsJsonException()
|
||||
{
|
||||
var json = """{"taskId":"t","status":"exploded","createdAt":"2025-01-01T00:00:00Z","lastUpdatedAt":"2025-01-01T00:00:00Z"}""";
|
||||
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void GetTaskResult_CompletedMissingResult_ThrowsJsonException()
|
||||
{
|
||||
var json = """{"taskId":"t","status":"completed","createdAt":"2025-01-01T00:00:00Z","lastUpdatedAt":"2025-01-01T00:00:00Z"}""";
|
||||
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void GetTaskResult_FailedMissingError_ThrowsJsonException()
|
||||
{
|
||||
var json = """{"taskId":"t","status":"failed","createdAt":"2025-01-01T00:00:00Z","lastUpdatedAt":"2025-01-01T00:00:00Z"}""";
|
||||
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void GetTaskResult_InputRequiredMissingInputRequests_ThrowsJsonException()
|
||||
{
|
||||
var json = """{"taskId":"t","status":"input_required","createdAt":"2025-01-01T00:00:00Z","lastUpdatedAt":"2025-01-01T00:00:00Z"}""";
|
||||
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<GetTaskResult>(json, McpJsonUtilities.DefaultOptions));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(WorkingTaskResult))]
|
||||
[InlineData(typeof(CompletedTaskResult))]
|
||||
[InlineData(typeof(FailedTaskResult))]
|
||||
[InlineData(typeof(CancelledTaskResult))]
|
||||
[InlineData(typeof(InputRequiredTaskResult))]
|
||||
public static void GetTaskResult_WireResultType_IsComplete_WhenSet(Type subType)
|
||||
{
|
||||
// SEP-2663: standard task responses (tasks/get, tasks/update, tasks/cancel) use resultType="complete".
|
||||
var created = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
GetTaskResult value = subType switch
|
||||
{
|
||||
Type t when t == typeof(WorkingTaskResult) => new WorkingTaskResult { TaskId = "t", CreatedAt = created, LastUpdatedAt = created, ResultType = "complete" },
|
||||
Type t when t == typeof(CompletedTaskResult) => new CompletedTaskResult { TaskId = "t", CreatedAt = created, LastUpdatedAt = created, Result = JsonElement.Parse("""{"ok":true}"""), ResultType = "complete" },
|
||||
Type t when t == typeof(FailedTaskResult) => new FailedTaskResult { TaskId = "t", CreatedAt = created, LastUpdatedAt = created, Error = JsonElement.Parse("""{"code":-32603,"message":"boom"}"""), ResultType = "complete" },
|
||||
Type t when t == typeof(CancelledTaskResult) => new CancelledTaskResult { TaskId = "t", CreatedAt = created, LastUpdatedAt = created, ResultType = "complete" },
|
||||
Type t when t == typeof(InputRequiredTaskResult) => new InputRequiredTaskResult
|
||||
{
|
||||
TaskId = "t",
|
||||
CreatedAt = created,
|
||||
LastUpdatedAt = created,
|
||||
ResultType = "complete",
|
||||
InputRequests = new Dictionary<string, InputRequest>
|
||||
{
|
||||
["k"] = new InputRequest
|
||||
{
|
||||
Method = "test/method",
|
||||
Params = JsonSerializer.SerializeToElement("ask", McpJsonUtilities.DefaultOptions),
|
||||
},
|
||||
},
|
||||
},
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(value, McpJsonUtilities.DefaultOptions);
|
||||
var node = JsonNode.Parse(json)!;
|
||||
|
||||
Assert.Equal("complete", (string?)node["resultType"]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region McpTaskStatus Enum
|
||||
|
||||
[Theory]
|
||||
[InlineData(McpTaskStatus.Working, "working")]
|
||||
[InlineData(McpTaskStatus.InputRequired, "input_required")]
|
||||
[InlineData(McpTaskStatus.Completed, "completed")]
|
||||
[InlineData(McpTaskStatus.Cancelled, "cancelled")]
|
||||
[InlineData(McpTaskStatus.Failed, "failed")]
|
||||
public static void McpTaskStatus_SerializesAsSnakeCase(McpTaskStatus status, string expectedWireValue)
|
||||
{
|
||||
string json = JsonSerializer.Serialize(status, McpJsonUtilities.DefaultOptions);
|
||||
Assert.Equal($"\"{expectedWireValue}\"", json);
|
||||
|
||||
var deserialized = JsonSerializer.Deserialize<McpTaskStatus>(json, McpJsonUtilities.DefaultOptions);
|
||||
Assert.Equal(status, deserialized);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TaskStatusNotificationParams
|
||||
|
||||
[Fact]
|
||||
public static void TaskStatusNotificationParams_Working_RoundTrip()
|
||||
{
|
||||
var original = new WorkingTaskNotificationParams
|
||||
{
|
||||
TaskId = "n1",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
StatusMessage = "Working on it",
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize<TaskStatusNotificationParams>(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<TaskStatusNotificationParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var working = Assert.IsType<WorkingTaskNotificationParams>(deserialized);
|
||||
Assert.Equal("n1", working.TaskId);
|
||||
Assert.Equal("Working on it", working.StatusMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void TaskStatusNotificationParams_Completed_RoundTrip()
|
||||
{
|
||||
var resultPayload = JsonElement.Parse("""{"text":"done"}""");
|
||||
var original = new CompletedTaskNotificationParams
|
||||
{
|
||||
TaskId = "n2",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
Result = resultPayload,
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize<TaskStatusNotificationParams>(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<TaskStatusNotificationParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var completed = Assert.IsType<CompletedTaskNotificationParams>(deserialized);
|
||||
Assert.Equal("n2", completed.TaskId);
|
||||
Assert.Equal("done", completed.Result.GetProperty("text").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void TaskStatusNotificationParams_Failed_RoundTrip()
|
||||
{
|
||||
var errorPayload = JsonElement.Parse("""{"code":-1,"message":"boom"}""");
|
||||
var original = new FailedTaskNotificationParams
|
||||
{
|
||||
TaskId = "n3",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
Error = errorPayload,
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize<TaskStatusNotificationParams>(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<TaskStatusNotificationParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var failed = Assert.IsType<FailedTaskNotificationParams>(deserialized);
|
||||
Assert.Equal("n3", failed.TaskId);
|
||||
Assert.Equal("boom", failed.Error.GetProperty("message").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void TaskStatusNotificationParams_Cancelled_RoundTrip()
|
||||
{
|
||||
var original = new CancelledTaskNotificationParams
|
||||
{
|
||||
TaskId = "n4",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize<TaskStatusNotificationParams>(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<TaskStatusNotificationParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.IsType<CancelledTaskNotificationParams>(deserialized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void TaskStatusNotificationParams_InputRequired_RoundTrip()
|
||||
{
|
||||
var inputRequests = new Dictionary<string, InputRequest>
|
||||
{
|
||||
["r1"] = new InputRequest { Method = "sampling/createMessage" }
|
||||
};
|
||||
var original = new InputRequiredTaskNotificationParams
|
||||
{
|
||||
TaskId = "n5",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
InputRequests = inputRequests,
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize<TaskStatusNotificationParams>(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<TaskStatusNotificationParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var inputRequired = Assert.IsType<InputRequiredTaskNotificationParams>(deserialized);
|
||||
Assert.NotNull(inputRequired.InputRequests);
|
||||
Assert.Single(inputRequired.InputRequests);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ResultOrCreatedTask
|
||||
|
||||
[Fact]
|
||||
public static void ResultOrCreatedTask_ImplicitConversion_FromResult()
|
||||
{
|
||||
CallToolResult callResult = new() { Content = [new TextContentBlock { Text = "hi" }] };
|
||||
|
||||
ResultOrCreatedTask<CallToolResult> augmented = callResult;
|
||||
|
||||
Assert.False(augmented.IsTask);
|
||||
Assert.Same(callResult, augmented.Result);
|
||||
Assert.Null(augmented.TaskCreated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void ResultOrCreatedTask_ImplicitConversion_FromCreateTaskResult()
|
||||
{
|
||||
CreateTaskResult taskCreated = new()
|
||||
{
|
||||
TaskId = "t1",
|
||||
Status = McpTaskStatus.Working,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
ResultOrCreatedTask<CallToolResult> augmented = taskCreated;
|
||||
|
||||
Assert.True(augmented.IsTask);
|
||||
Assert.Same(taskCreated, augmented.TaskCreated);
|
||||
Assert.Null(augmented.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void ResultOrCreatedTask_IsTask_FalseForResult_TrueForTask()
|
||||
{
|
||||
var result = new ResultOrCreatedTask<CallToolResult>(new CallToolResult());
|
||||
var task = new ResultOrCreatedTask<CallToolResult>(new CreateTaskResult
|
||||
{
|
||||
TaskId = "t",
|
||||
Status = McpTaskStatus.Working,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
});
|
||||
|
||||
Assert.False(result.IsTask);
|
||||
Assert.True(task.IsTask);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateTaskResult / CancelTaskResult Wire Format
|
||||
|
||||
[Fact]
|
||||
public static void UpdateTaskResult_WireResultType_IsComplete_WhenSet()
|
||||
{
|
||||
// SEP-2663: tasks/update responses use resultType="complete".
|
||||
var result = new UpdateTaskResult { ResultType = "complete" };
|
||||
string json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions);
|
||||
var node = JsonNode.Parse(json)!;
|
||||
|
||||
Assert.Equal("complete", (string?)node["resultType"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void CancelTaskResult_WireResultType_IsComplete_WhenSet()
|
||||
{
|
||||
// SEP-2663: tasks/cancel responses use resultType="complete".
|
||||
var result = new CancelTaskResult { ResultType = "complete" };
|
||||
string json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions);
|
||||
var node = JsonNode.Parse(json)!;
|
||||
|
||||
Assert.Equal("complete", (string?)node["resultType"]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Loading…
Reference in a new issue