Add initial task extension conformance coverage (#1734)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7c99928c-a943-49f4-9a63-8b04a0e4d18b
This commit is contained in:
parent
f7c61fafdb
commit
83523b0a42
4 changed files with 463 additions and 43 deletions
|
|
@ -104,7 +104,7 @@ public static class McpTasksClientExtensions
|
|||
/// <summary>
|
||||
/// Retrieves a task using explicit request parameters.
|
||||
/// </summary>
|
||||
public static ValueTask<GetTaskResult> GetTaskAsync(
|
||||
public static async ValueTask<GetTaskResult> GetTaskAsync(
|
||||
this McpClient client,
|
||||
GetTaskRequestParams requestParams,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
|
@ -118,11 +118,19 @@ public static class McpTasksClientExtensions
|
|||
#endif
|
||||
|
||||
ThrowIfTasksNotSupported(client, nameof(GetTaskAsync));
|
||||
return client.SendRequestAsync<GetTaskRequestParams, GetTaskResult>(
|
||||
requestParams = new GetTaskRequestParams
|
||||
{
|
||||
TaskId = requestParams.TaskId,
|
||||
Meta = GetMetaWithTaskCapability(requestParams.Meta),
|
||||
};
|
||||
JsonRpcRequest jsonRpcRequest = CreateTaskRequest(
|
||||
TasksProtocol.MethodTasksGet,
|
||||
requestParams,
|
||||
McpTasksJsonContext.Default.Options,
|
||||
cancellationToken: cancellationToken);
|
||||
JsonSerializer.SerializeToNode(requestParams, McpTasksJsonContext.Default.GetTaskRequestParams),
|
||||
requestParams.TaskId);
|
||||
|
||||
JsonRpcResponse response = await client.SendRequestAsync(jsonRpcRequest, cancellationToken).ConfigureAwait(false);
|
||||
return response.Result?.Deserialize(McpTasksJsonContext.Default.GetTaskResult)
|
||||
?? throw new JsonException("Unexpected JSON result in response.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -148,6 +156,7 @@ public static class McpTasksClientExtensions
|
|||
JsonObject paramsObj = new()
|
||||
{
|
||||
["taskId"] = requestParams.TaskId,
|
||||
["_meta"] = GetMetaWithTaskCapability(requestParams.Meta),
|
||||
};
|
||||
|
||||
if (requestParams.InputResponses is { Count: > 0 } inputResponses)
|
||||
|
|
@ -157,11 +166,10 @@ public static class McpTasksClientExtensions
|
|||
McpJsonUtilities.DefaultOptions.GetTypeInfo<IDictionary<string, InputResponse>>());
|
||||
}
|
||||
|
||||
JsonRpcRequest jsonRpcRequest = new()
|
||||
{
|
||||
Method = TasksProtocol.MethodTasksUpdate,
|
||||
Params = paramsObj,
|
||||
};
|
||||
JsonRpcRequest jsonRpcRequest = CreateTaskRequest(
|
||||
TasksProtocol.MethodTasksUpdate,
|
||||
paramsObj,
|
||||
requestParams.TaskId);
|
||||
|
||||
JsonRpcResponse response = await client.SendRequestAsync(jsonRpcRequest, cancellationToken).ConfigureAwait(false);
|
||||
return response.Result?.Deserialize(McpTasksJsonContext.Default.UpdateTaskResult)
|
||||
|
|
@ -190,7 +198,7 @@ public static class McpTasksClientExtensions
|
|||
/// <summary>
|
||||
/// Requests task cancellation using explicit request parameters.
|
||||
/// </summary>
|
||||
public static ValueTask<CancelTaskResult> CancelTaskAsync(
|
||||
public static async ValueTask<CancelTaskResult> CancelTaskAsync(
|
||||
this McpClient client,
|
||||
CancelTaskRequestParams requestParams,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
|
@ -204,13 +212,32 @@ public static class McpTasksClientExtensions
|
|||
#endif
|
||||
|
||||
ThrowIfTasksNotSupported(client, nameof(CancelTaskAsync));
|
||||
return client.SendRequestAsync<CancelTaskRequestParams, CancelTaskResult>(
|
||||
requestParams = new CancelTaskRequestParams
|
||||
{
|
||||
TaskId = requestParams.TaskId,
|
||||
Meta = GetMetaWithTaskCapability(requestParams.Meta),
|
||||
};
|
||||
JsonRpcRequest jsonRpcRequest = CreateTaskRequest(
|
||||
TasksProtocol.MethodTasksCancel,
|
||||
requestParams,
|
||||
McpTasksJsonContext.Default.Options,
|
||||
cancellationToken: cancellationToken);
|
||||
JsonSerializer.SerializeToNode(requestParams, McpTasksJsonContext.Default.CancelTaskRequestParams),
|
||||
requestParams.TaskId);
|
||||
|
||||
JsonRpcResponse response = await client.SendRequestAsync(jsonRpcRequest, cancellationToken).ConfigureAwait(false);
|
||||
return response.Result?.Deserialize(McpTasksJsonContext.Default.CancelTaskResult)
|
||||
?? new CancelTaskResult();
|
||||
}
|
||||
|
||||
private static JsonRpcRequest CreateTaskRequest(string method, JsonNode? parameters, string taskId) =>
|
||||
new()
|
||||
{
|
||||
Method = method,
|
||||
Params = parameters,
|
||||
Context = new JsonRpcMessageContext
|
||||
{
|
||||
RoutingName = taskId,
|
||||
},
|
||||
};
|
||||
|
||||
private static async ValueTask<CallToolResult> PollTaskToCompletionAsync(
|
||||
McpClient client,
|
||||
CreateTaskResult taskCreated,
|
||||
|
|
|
|||
|
|
@ -20,4 +20,9 @@ namespace ModelContextProtocol.Extensions.Tasks;
|
|||
/// </remarks>
|
||||
public sealed class CancelTaskResult : Result
|
||||
{
|
||||
/// <summary>Initializes a new task cancellation acknowledgement.</summary>
|
||||
public CancelTaskResult()
|
||||
{
|
||||
ResultType = "complete";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,4 +19,9 @@ namespace ModelContextProtocol.Extensions.Tasks;
|
|||
/// </remarks>
|
||||
public sealed class UpdateTaskResult : Result
|
||||
{
|
||||
/// <summary>Initializes a new task update acknowledgement.</summary>
|
||||
public UpdateTaskResult()
|
||||
{
|
||||
ResultType = "complete";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,9 +75,24 @@ public static class McpTasksBuilderExtensions
|
|||
}
|
||||
|
||||
options.RequestHandlers ??= new List<McpServerRequestHandler>();
|
||||
options.RequestHandlers.Add(new McpServerRequestHandler { Method = TasksProtocol.MethodTasksGet, Handler = HandleGetTask });
|
||||
options.RequestHandlers.Add(new McpServerRequestHandler { Method = TasksProtocol.MethodTasksUpdate, Handler = HandleUpdateTask });
|
||||
options.RequestHandlers.Add(new McpServerRequestHandler { Method = TasksProtocol.MethodTasksCancel, Handler = HandleCancelTask });
|
||||
options.RequestHandlers.Add(new McpServerRequestHandler
|
||||
{
|
||||
Method = TasksProtocol.MethodTasksGet,
|
||||
RoutingNameParameter = "taskId",
|
||||
Handler = HandleGetTask,
|
||||
});
|
||||
options.RequestHandlers.Add(new McpServerRequestHandler
|
||||
{
|
||||
Method = TasksProtocol.MethodTasksUpdate,
|
||||
RoutingNameParameter = "taskId",
|
||||
Handler = HandleUpdateTask,
|
||||
});
|
||||
options.RequestHandlers.Add(new McpServerRequestHandler
|
||||
{
|
||||
Method = TasksProtocol.MethodTasksCancel,
|
||||
RoutingNameParameter = "taskId",
|
||||
Handler = HandleCancelTask,
|
||||
});
|
||||
|
||||
if (options.Filters.Request.CallToolFilters.Count > 0)
|
||||
{
|
||||
|
|
@ -93,7 +108,8 @@ public static class McpTasksBuilderExtensions
|
|||
options.Filters.Request.CallToolWithAlternateFilters.Count,
|
||||
async (request, next, cancellationToken) =>
|
||||
{
|
||||
if (!IsJuly2026OrLaterProtocolRequest(request.JsonRpcRequest) || !HasTaskExtensionOptIn(request.Params?.Meta))
|
||||
if (!IsJuly2026OrLaterProtocolRequest(request.JsonRpcRequest) ||
|
||||
!HasTaskExtensionOptIn(request.JsonRpcRequest))
|
||||
{
|
||||
return await next(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
|
@ -257,6 +273,7 @@ public static class McpTasksBuilderExtensions
|
|||
private async ValueTask<JsonNode?> HandleGetTask(JsonRpcRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
GateToJuly2026OrLaterProtocol(request, TasksProtocol.MethodTasksGet);
|
||||
GateToTasksCapability(request);
|
||||
|
||||
var requestParams = request.Params?.Deserialize(McpTasksJsonContext.Default.GetTaskRequestParams)
|
||||
?? throw new McpProtocolException("Missing params for tasks/get", McpErrorCode.InvalidParams);
|
||||
|
|
@ -273,6 +290,7 @@ public static class McpTasksBuilderExtensions
|
|||
private async ValueTask<JsonNode?> HandleUpdateTask(JsonRpcRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
GateToJuly2026OrLaterProtocol(request, TasksProtocol.MethodTasksUpdate);
|
||||
GateToTasksCapability(request);
|
||||
|
||||
var taskId = request.Params?["taskId"]?.GetValue<string>()
|
||||
?? throw new McpProtocolException("Missing params.taskId for tasks/update", McpErrorCode.InvalidParams);
|
||||
|
|
@ -292,6 +310,7 @@ public static class McpTasksBuilderExtensions
|
|||
private async ValueTask<JsonNode?> HandleCancelTask(JsonRpcRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
GateToJuly2026OrLaterProtocol(request, TasksProtocol.MethodTasksCancel);
|
||||
GateToTasksCapability(request);
|
||||
|
||||
var requestParams = request.Params?.Deserialize(McpTasksJsonContext.Default.CancelTaskRequestParams)
|
||||
?? throw new McpProtocolException("Missing params for tasks/cancel", McpErrorCode.InvalidParams);
|
||||
|
|
@ -319,11 +338,29 @@ public static class McpTasksBuilderExtensions
|
|||
}
|
||||
}
|
||||
|
||||
private static bool HasTaskExtensionOptIn(JsonObject? meta) =>
|
||||
meta is not null &&
|
||||
meta[MetaKeys.ClientCapabilities] is JsonObject caps &&
|
||||
caps["extensions"] is JsonObject exts &&
|
||||
exts.ContainsKey(TasksProtocol.ExtensionId);
|
||||
private static void GateToTasksCapability(JsonRpcRequest request)
|
||||
{
|
||||
if (!HasTaskExtensionOptIn(request))
|
||||
{
|
||||
throw CreateMissingTasksCapabilityException();
|
||||
}
|
||||
}
|
||||
|
||||
private static MissingRequiredClientCapabilityException CreateMissingTasksCapabilityException() =>
|
||||
new(
|
||||
new ClientCapabilities
|
||||
{
|
||||
Extensions = new Dictionary<string, object>
|
||||
{
|
||||
[TasksProtocol.ExtensionId] = new JsonObject(),
|
||||
},
|
||||
},
|
||||
$"The request requires the '{TasksProtocol.ExtensionId}' client extension capability.");
|
||||
|
||||
private static bool HasTaskExtensionOptIn(JsonRpcRequest request) =>
|
||||
request.Context?.ClientCapabilities?.Extensions?.ContainsKey(TasksProtocol.ExtensionId) is true ||
|
||||
request.Params?["_meta"]?[MetaKeys.ClientCapabilities]?["extensions"] is JsonObject extensions &&
|
||||
extensions.ContainsKey(TasksProtocol.ExtensionId);
|
||||
|
||||
private static bool IsJuly2026OrLaterProtocolRequest(JsonRpcRequest? request) =>
|
||||
McpProtocolVersions.IsJuly2026OrLaterProtocolVersion(request?.Context?.ProtocolVersion);
|
||||
|
|
|
|||
Loading…
Reference in a new issue