csharp-sdk/tests/Common/Utils/TestServerTransport.cs
Eirik Tsarpalis 10e4f5b20f
Implement MCP task support (SEP-1686) (#1170)
Co-authored-by: Stephen Halter <halter73@gmail.com>
2026-01-27 19:21:02 -08:00

252 lines
9.2 KiB
C#

using ModelContextProtocol.Protocol;
using System.Text.Json;
using System.Threading.Channels;
namespace ModelContextProtocol.Tests.Utils;
public class TestServerTransport : ITransport
{
private readonly Channel<JsonRpcMessage> _messageChannel;
public bool IsConnected { get; set; }
public ChannelReader<JsonRpcMessage> MessageReader => _messageChannel;
public IList<JsonRpcMessage> SentMessages { get; } = [];
public Action<JsonRpcMessage>? OnMessageSent { get; set; }
public string? SessionId => null;
public TestServerTransport()
{
_messageChannel = Channel.CreateUnbounded<JsonRpcMessage>(new UnboundedChannelOptions
{
SingleReader = true,
SingleWriter = true,
});
IsConnected = true;
}
public ValueTask DisposeAsync()
{
_messageChannel.Writer.TryComplete();
IsConnected = false;
return default;
}
public async Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
{
SentMessages.Add(message);
if (message is JsonRpcRequest request)
{
if (request.Method == RequestMethods.RootsList)
await ListRootsAsync(request, cancellationToken);
else if (request.Method == RequestMethods.SamplingCreateMessage)
await SamplingAsync(request, cancellationToken);
else if (request.Method == RequestMethods.ElicitationCreate)
await ElicitAsync(request, cancellationToken);
else if (request.Method == RequestMethods.TasksGet)
await TasksGetAsync(request, cancellationToken);
else if (request.Method == RequestMethods.TasksResult)
await TasksResultAsync(request, cancellationToken);
else if (request.Method == RequestMethods.TasksList)
await TasksListAsync(request, cancellationToken);
else if (request.Method == RequestMethods.TasksCancel)
await TasksCancelAsync(request, cancellationToken);
else
await WriteMessageAsync(request, cancellationToken);
}
else if (message is JsonRpcNotification notification)
{
await WriteMessageAsync(notification, cancellationToken);
}
OnMessageSent?.Invoke(message);
}
private async Task ListRootsAsync(JsonRpcRequest request, CancellationToken cancellationToken)
{
await WriteMessageAsync(new JsonRpcResponse
{
Id = request.Id,
Result = JsonSerializer.SerializeToNode(new ListRootsResult
{
Roots = []
}, McpJsonUtilities.DefaultOptions),
}, cancellationToken);
}
private async Task SamplingAsync(JsonRpcRequest request, CancellationToken cancellationToken)
{
// Check if the request is task-augmented (has Task metadata)
var requestParams = JsonSerializer.Deserialize<CreateMessageRequestParams>(request.Params, McpJsonUtilities.DefaultOptions);
if (requestParams?.Task is not null && MockTask is not null)
{
// Return a task-augmented response
await WriteMessageAsync(new JsonRpcResponse
{
Id = request.Id,
Result = JsonSerializer.SerializeToNode(new CreateTaskResult { Task = MockTask }, McpJsonUtilities.DefaultOptions),
}, cancellationToken);
}
else
{
// Return a normal sampling response
await WriteMessageAsync(new JsonRpcResponse
{
Id = request.Id,
Result = JsonSerializer.SerializeToNode(new CreateMessageResult { Content = [new TextContentBlock { Text = "" }], Model = "model" }, McpJsonUtilities.DefaultOptions),
}, cancellationToken);
}
}
private async Task ElicitAsync(JsonRpcRequest request, CancellationToken cancellationToken)
{
// Check if the request is task-augmented (has Task metadata)
var requestParams = JsonSerializer.Deserialize<ElicitRequestParams>(request.Params, McpJsonUtilities.DefaultOptions);
if (requestParams?.Task is not null && MockTask is not null)
{
// Return a task-augmented response
await WriteMessageAsync(new JsonRpcResponse
{
Id = request.Id,
Result = JsonSerializer.SerializeToNode(new CreateTaskResult { Task = MockTask }, McpJsonUtilities.DefaultOptions),
}, cancellationToken);
}
else
{
// Return a normal elicitation response
await WriteMessageAsync(new JsonRpcResponse
{
Id = request.Id,
Result = JsonSerializer.SerializeToNode(new ElicitResult { Action = "decline" }, McpJsonUtilities.DefaultOptions),
}, cancellationToken);
}
}
/// <summary>
/// Gets or sets the task to return from tasks/get requests.
/// </summary>
public McpTask? MockTask { get; set; }
/// <summary>
/// Gets or sets the result to return from tasks/result requests.
/// </summary>
public object? MockTaskResult { get; set; }
/// <summary>
/// Gets or sets the list of tasks to return from tasks/list requests.
/// </summary>
public McpTask[]? MockTaskList { get; set; }
private async Task TasksGetAsync(JsonRpcRequest request, CancellationToken cancellationToken)
{
var task = MockTask ?? new McpTask
{
TaskId = "test-task-id",
Status = McpTaskStatus.Completed,
CreatedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
LastUpdatedAt = DateTimeOffset.UtcNow,
};
await WriteMessageAsync(new JsonRpcResponse
{
Id = request.Id,
Result = JsonSerializer.SerializeToNode(new GetTaskResult
{
TaskId = task.TaskId,
Status = task.Status,
StatusMessage = task.StatusMessage,
CreatedAt = task.CreatedAt,
LastUpdatedAt = task.LastUpdatedAt,
TimeToLive = task.TimeToLive,
PollInterval = task.PollInterval
}, McpJsonUtilities.DefaultOptions),
}, cancellationToken);
}
private async Task TasksResultAsync(JsonRpcRequest request, CancellationToken cancellationToken)
{
var result = MockTaskResult ?? new CreateMessageResult
{
Content = [new TextContentBlock { Text = "Task result" }],
Model = "test-model"
};
await WriteMessageAsync(new JsonRpcResponse
{
Id = request.Id,
Result = JsonSerializer.SerializeToNode(result, McpJsonUtilities.DefaultOptions),
}, cancellationToken);
}
private async Task TasksListAsync(JsonRpcRequest request, CancellationToken cancellationToken)
{
var tasks = MockTaskList ?? [
new McpTask
{
TaskId = "task-1",
Status = McpTaskStatus.Completed,
CreatedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
LastUpdatedAt = DateTimeOffset.UtcNow,
},
new McpTask
{
TaskId = "task-2",
Status = McpTaskStatus.Working,
CreatedAt = DateTimeOffset.UtcNow.AddMinutes(-3),
LastUpdatedAt = DateTimeOffset.UtcNow,
}
];
await WriteMessageAsync(new JsonRpcResponse
{
Id = request.Id,
Result = JsonSerializer.SerializeToNode(new ListTasksResult
{
Tasks = tasks,
}, McpJsonUtilities.DefaultOptions),
}, cancellationToken);
}
private async Task TasksCancelAsync(JsonRpcRequest request, CancellationToken cancellationToken)
{
var task = MockTask ?? new McpTask
{
TaskId = "test-task-id",
Status = McpTaskStatus.Cancelled,
StatusMessage = "Task cancelled by request",
CreatedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
LastUpdatedAt = DateTimeOffset.UtcNow,
};
await WriteMessageAsync(new JsonRpcResponse
{
Id = request.Id,
Result = JsonSerializer.SerializeToNode(new CancelMcpTaskResult
{
TaskId = task.TaskId,
Status = McpTaskStatus.Cancelled,
StatusMessage = task.StatusMessage ?? "Task cancelled",
CreatedAt = task.CreatedAt,
LastUpdatedAt = DateTimeOffset.UtcNow,
TimeToLive = task.TimeToLive,
PollInterval = task.PollInterval
}, McpJsonUtilities.DefaultOptions),
}, cancellationToken);
}
private async Task WriteMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
{
await _messageChannel.Writer.WriteAsync(message, cancellationToken);
}
/// <summary>
/// Sends a message from the client to the server (simulating client-to-server communication).
/// </summary>
public async Task SendClientMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
{
await _messageChannel.Writer.WriteAsync(message, cancellationToken);
}
}