Default draft protocol support: sessionless + handshake-less (SEP-2575 + SEP-2567) (#1610)
This commit is contained in:
parent
3368bb7349
commit
6c323e783b
1 changed files with 5704 additions and 712 deletions
|
|
@ -1174,7 +1174,10 @@ public abstract partial class McpClient : McpSession
|
|||
{
|
||||
Name = requestParams.Name,
|
||||
Arguments = requestParams.Arguments,
|
||||
Meta = GetMetaWithTaskCapability(requestParams.Meta),
|
||||
// The SEP-2663 Tasks extension is draft-only. On a legacy session, send a plain tools/call
|
||||
// (no task capability envelope) so the server returns a direct CallToolResult and never
|
||||
// creates a task.
|
||||
Meta = IsDraftProtocol() ? GetMetaWithTaskCapability(requestParams.Meta) : requestParams.Meta,
|
||||
};
|
||||
|
||||
JsonRpcRequest jsonRpcRequest = new()
|
||||
|
|
@ -1285,6 +1288,7 @@ public abstract partial class McpClient : McpSession
|
|||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(requestParams);
|
||||
ThrowIfTasksNotSupported(nameof(GetTaskAsync));
|
||||
|
||||
return SendRequestAsync(
|
||||
RequestMethods.TasksGet,
|
||||
|
|
@ -1307,6 +1311,7 @@ public abstract partial class McpClient : McpSession
|
|||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(requestParams);
|
||||
ThrowIfTasksNotSupported(nameof(UpdateTaskAsync));
|
||||
|
||||
return SendRequestAsync(
|
||||
RequestMethods.TasksUpdate,
|
||||
|
|
@ -1346,6 +1351,7 @@ public abstract partial class McpClient : McpSession
|
|||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(requestParams);
|
||||
ThrowIfTasksNotSupported(nameof(CancelTaskAsync));
|
||||
|
||||
return SendRequestAsync(
|
||||
RequestMethods.TasksCancel,
|
||||
|
|
@ -1376,30 +1382,42 @@ public abstract partial class McpClient : McpSession
|
|||
|
||||
// Per SEP-2663 §51, the per-request opt-in uses the SEP-2575 capabilities envelope:
|
||||
// _meta/io.modelcontextprotocol/clientCapabilities/extensions/io.modelcontextprotocol/tasks = {}
|
||||
// TODO: replace the literal with a shared NotificationMethods.ClientCapabilitiesMetaKey once
|
||||
// the SEP-2575 plumbing lands and drop the local consts.
|
||||
private const string ClientCapabilitiesMetaKey = "io.modelcontextprotocol/clientCapabilities";
|
||||
private const string ExtensionsKey = "extensions";
|
||||
|
||||
private static JsonObject GetMetaWithTaskCapability(JsonObject? existingMeta)
|
||||
{
|
||||
JsonObject meta = existingMeta is not null
|
||||
? (JsonObject)existingMeta.DeepClone()
|
||||
: [];
|
||||
|
||||
if (meta[ClientCapabilitiesMetaKey] is not JsonObject capsRoot)
|
||||
if (meta[MetaKeys.ClientCapabilities] is not JsonObject capsRoot)
|
||||
{
|
||||
capsRoot = [];
|
||||
meta[ClientCapabilitiesMetaKey] = capsRoot;
|
||||
meta[MetaKeys.ClientCapabilities] = capsRoot;
|
||||
}
|
||||
|
||||
if (capsRoot[ExtensionsKey] is not JsonObject extensionsRoot)
|
||||
if (capsRoot["extensions"] is not JsonObject extensionsRoot)
|
||||
{
|
||||
extensionsRoot = [];
|
||||
capsRoot[ExtensionsKey] = extensionsRoot;
|
||||
capsRoot["extensions"] = extensionsRoot;
|
||||
}
|
||||
|
||||
extensionsRoot.TryAdd(McpExtensions.Tasks, new JsonObject());
|
||||
return meta;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws when the negotiated protocol version is not the draft revision. The SEP-2663 Tasks
|
||||
/// extension is draft-only, and a task id only ever exists when the session negotiated draft, so
|
||||
/// invoking <c>tasks/get</c>, <c>tasks/update</c>, or <c>tasks/cancel</c> on a legacy session is a
|
||||
/// programming error rather than a recoverable protocol condition.
|
||||
/// </summary>
|
||||
private void ThrowIfTasksNotSupported(string operationName)
|
||||
{
|
||||
if (!IsDraftProtocol())
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"'{operationName}' requires the draft protocol revision ('{DraftProtocolVersion}'). " +
|
||||
$"The negotiated protocol version is '{NegotiatedProtocolVersion ?? "(none)"}'. " +
|
||||
"The Tasks extension is only available under the draft revision.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue