Add support for URL mode elicitation (#1021)
This commit is contained in:
parent
c447973972
commit
ae17ba0b4b
8 changed files with 1241 additions and 92 deletions
|
|
@ -9,17 +9,79 @@ namespace ModelContextProtocol.Protocol;
|
|||
/// <summary>
|
||||
/// Represents a message issued from the server to elicit additional information from the user via the client.
|
||||
/// </summary>
|
||||
public sealed class ElicitRequestParams
|
||||
public sealed class ElicitRequestParams : RequestParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the elicitation mode: "form" for in-band data collection or "url" for out-of-band URL navigation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><b>form</b>: Client collects structured data via a form interface. Data is exposed to the client.</description></item>
|
||||
/// <item><description><b>url</b>: Client navigates user to a URL for out-of-band interaction. Sensitive data is not exposed to the client.</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[JsonPropertyName("mode")]
|
||||
[field: MaybeNull]
|
||||
public string Mode
|
||||
{
|
||||
get => field ??= "form";
|
||||
set
|
||||
{
|
||||
if (value is not ("form" or "url"))
|
||||
{
|
||||
throw new ArgumentException("Mode must be 'form' or 'url'.", nameof(value));
|
||||
}
|
||||
field = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a unique identifier for this elicitation request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Used to track and correlate the elicitation across multiple messages, especially for out-of-band flows
|
||||
/// that may complete asynchronously.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Required for url mode elicitation to enable progress tracking and completion detection.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[JsonPropertyName("elicitationId")]
|
||||
public string? ElicitationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL to navigate to for out-of-band elicitation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Required when <see cref="Mode"/> is "url". The client should prompt the user for consent
|
||||
/// and then navigate to this URL in a user-agent (browser) where the user completes
|
||||
/// the required interaction.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// URLs must not appear in any other field of the elicitation request for security reasons.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[JsonPropertyName("url")]
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message to present to the user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For form mode, this describes what information is being requested.
|
||||
/// For url mode, this explains why the user needs to navigate to the URL.
|
||||
/// </remarks>
|
||||
[JsonPropertyName("message")]
|
||||
public required string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the requested schema.
|
||||
/// Gets or sets the requested schema for form mode elicitation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only applicable when <see cref="Mode"/> is "form".
|
||||
/// </remarks>
|
||||
/// <value>
|
||||
/// Possible values are <see cref="StringSchema"/>, <see cref="NumberSchema"/>, <see cref="BooleanSchema"/>,
|
||||
/// <see cref="UntitledSingleSelectEnumSchema"/>, <see cref="TitledSingleSelectEnumSchema"/>,
|
||||
|
|
@ -27,14 +89,9 @@ public sealed class ElicitRequestParams
|
|||
/// and <see cref="LegacyTitledEnumSchema"/> (deprecated).
|
||||
/// </value>
|
||||
[JsonPropertyName("requestedSchema")]
|
||||
[field: MaybeNull]
|
||||
public RequestSchema RequestedSchema
|
||||
{
|
||||
get => field ??= new RequestSchema();
|
||||
set => field = value;
|
||||
}
|
||||
public RequestSchema? RequestedSchema { get; set; }
|
||||
|
||||
/// <summary>Represents a request schema used in an elicitation request.</summary>
|
||||
/// <summary>Represents a request schema used in a form mode elicitation request.</summary>
|
||||
public class RequestSchema
|
||||
{
|
||||
/// <summary>Gets the type of the schema.</summary>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using ModelContextProtocol.Client;
|
||||
|
||||
|
|
@ -10,17 +11,117 @@ namespace ModelContextProtocol.Protocol;
|
|||
/// <remarks>
|
||||
/// <para>
|
||||
/// This capability enables the MCP client to respond to elicitation requests from an MCP server.
|
||||
/// Clients must support at least one elicitation mode: form (in-band) or url (out-of-band via URL).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When this capability is enabled, an MCP server can request the client to provide additional information
|
||||
/// during interactions. The client must set a <see cref="McpClientHandlers.ElicitationHandler"/> to process these requests.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This class is intentionally empty as the Model Context Protocol specification does not
|
||||
/// currently define additional properties for sampling capabilities. Future versions of the
|
||||
/// specification may extend this capability with additional configuration options.
|
||||
/// Two modes of elicitation are supported:
|
||||
/// <list type="bullet">
|
||||
/// <item><description><b>form</b>: In-band elicitation where data is collected via a form and exposed to the client</description></item>
|
||||
/// <item><description><b>url</b>: URL mode (out-of-band) elicitation via navigation where sensitive data is not exposed to the client</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[JsonConverter(typeof(Converter))]
|
||||
public sealed class ElicitationCapability
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the form mode elicitation (in-band) capability, indicating support for in-band elicitation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When present, indicates the client supports form mode elicitation where structured data
|
||||
/// is collected through a form interface and returned to the server.
|
||||
/// </remarks>
|
||||
[JsonPropertyName("form")]
|
||||
public FormElicitationCapability? Form { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL mode (out-of-band) elicitation capability.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When present, indicates the client supports URL mode elicitation for secure out-of-band
|
||||
/// interactions such as OAuth flows, payments, or collecting sensitive credentials.
|
||||
/// </remarks>
|
||||
[JsonPropertyName("url")]
|
||||
public UrlElicitationCapability? Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides a converter that normalizes blank capability objects to imply form support.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class Converter : JsonConverter<ElicitationCapability>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override ElicitationCapability? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using var document = JsonDocument.ParseValue(ref reader);
|
||||
|
||||
if (document.RootElement.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
throw new JsonException("elicitation capability must be an object.");
|
||||
}
|
||||
|
||||
var capability = new ElicitationCapability();
|
||||
bool hasForm = false;
|
||||
bool hasUrl = false;
|
||||
|
||||
foreach (var property in document.RootElement.EnumerateObject())
|
||||
{
|
||||
if (property.NameEquals("form"))
|
||||
{
|
||||
capability.Form = property.Value.ValueKind == JsonValueKind.Null
|
||||
? null
|
||||
: capability.Form ?? new FormElicitationCapability();
|
||||
hasForm = true;
|
||||
}
|
||||
else if (property.NameEquals("url"))
|
||||
{
|
||||
capability.Url = property.Value.ValueKind == JsonValueKind.Null
|
||||
? null
|
||||
: capability.Url ?? new UrlElicitationCapability();
|
||||
hasUrl = capability.Url is not null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasForm && !hasUrl)
|
||||
{
|
||||
capability.Form = new FormElicitationCapability();
|
||||
}
|
||||
|
||||
return capability;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, ElicitationCapability value, JsonSerializerOptions options)
|
||||
{
|
||||
Throw.IfNull(writer);
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
bool writeForm = value.Form is not null || value.Url is null;
|
||||
if (writeForm)
|
||||
{
|
||||
writer.WritePropertyName("form");
|
||||
writer.WriteStartObject();
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
if (value.Url is not null)
|
||||
{
|
||||
writer.WritePropertyName("url");
|
||||
writer.WriteStartObject();
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ModelContextProtocol.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the parameters used with a <see cref="NotificationMethods.ElicitationCompleteNotification"/>
|
||||
/// notification emitted after a URL-mode elicitation finishes out-of-band.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The payload references the original elicitation by ID so that clients can resume deferred
|
||||
/// requests or update pending UI once the external flow completes.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class ElicitationCompleteNotificationParams : NotificationParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the elicitation that completed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This matches <see cref="ElicitRequestParams.ElicitationId"/> from the originating request and allows
|
||||
/// clients to correlate the completion notification with previously issued prompts.
|
||||
/// </remarks>
|
||||
[JsonPropertyName("elicitationId")]
|
||||
public required string ElicitationId { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace ModelContextProtocol.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the capability for form-based (in-band) elicitation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This capability enables the client to collect structured data from users through form interfaces
|
||||
/// where the data is visible to and processed by the client before being sent to the server.
|
||||
/// </remarks>
|
||||
public sealed class FormElicitationCapability;
|
||||
|
|
@ -81,6 +81,15 @@ public static class NotificationMethods
|
|||
/// </remarks>
|
||||
public const string LoggingMessageNotification = "notifications/message";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the notification sent by the server when a URL-mode elicitation flow completes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This notification references the original elicitation by ID, allowing clients to retry blocked requests
|
||||
/// or update their UI state once the out-of-band interaction finishes.
|
||||
/// </remarks>
|
||||
public const string ElicitationCompleteNotification = "notifications/elicitation/complete";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the notification sent from the client to the server after initialization has finished.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -41,13 +41,13 @@ public static class RequestMethods
|
|||
public const string ResourcesTemplatesList = "resources/templates/list";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the request method sent from the client to request <see cref="NotificationMethods.ResourceUpdatedNotification"/>
|
||||
/// The name of the request method sent from the client to request <see cref="NotificationMethods.ResourceUpdatedNotification"/>
|
||||
/// notifications from the server whenever a particular resource changes.
|
||||
/// </summary>
|
||||
public const string ResourcesSubscribe = "resources/subscribe";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the request method sent from the client to request unsubscribing from <see cref="NotificationMethods.ResourceUpdatedNotification"/>
|
||||
/// The name of the request method sent from the client to request unsubscribing from <see cref="NotificationMethods.ResourceUpdatedNotification"/>
|
||||
/// notifications from the server.
|
||||
/// </summary>
|
||||
public const string ResourcesUnsubscribe = "resources/unsubscribe";
|
||||
|
|
@ -78,7 +78,7 @@ public static class RequestMethods
|
|||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used to provide autocompletion-like functionality for arguments in a resource reference or a prompt template.
|
||||
/// The client provides a reference (resource or prompt), argument name, and partial value, and the server
|
||||
/// The client provides a reference (resource or prompt), argument name, and partial value, and the server
|
||||
/// responds with matching completion options.
|
||||
/// </remarks>
|
||||
public const string CompletionComplete = "completion/complete";
|
||||
|
|
@ -94,11 +94,21 @@ public static class RequestMethods
|
|||
public const string SamplingCreateMessage = "sampling/createMessage";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the request method sent from the client to the server to elicit additional information from the user via the client.
|
||||
/// The name of the request method sent from the server to elicit additional information from the user via the client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This request is used when the server needs more information from the client to proceed with a task or interaction.
|
||||
/// Servers can request structured data from users, with optional JSON schemas to validate responses.
|
||||
/// Servers can request structured data from users, with optional JSON schemas to validate responses (form mode),
|
||||
/// or request URL mode (out-of-band) user interaction via navigation for sensitive operations.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Two modes are supported:
|
||||
/// <list type="bullet">
|
||||
/// <item><description><b>form</b>: In-band elicitation where structured data is collected and returned to the server</description></item>
|
||||
/// <item><description><b>url</b>: URL mode (out-of-band) elicitation for sensitive operations like OAuth or payments</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public const string ElicitationCreate = "elicitation/create";
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
namespace ModelContextProtocol.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the capability for URL mode (out-of-band) elicitation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This capability enables secure out-of-band interactions where the user is directed to a URL
|
||||
/// (typically opened in a browser) to complete sensitive operations like OAuth authorization,
|
||||
/// payments, or credential entry.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Unlike form mode, sensitive data in URL mode is never exposed to the MCP client, providing
|
||||
/// better security for sensitive interactions.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class UrlElicitationCapability;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ModelContextProtocol.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the payload for the <c>URL_ELICITATION_REQUIRED</c> JSON-RPC error.
|
||||
/// </summary>
|
||||
public sealed class UrlElicitationRequiredErrorData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the elicitations that must be completed before retrying the original request.
|
||||
/// </summary>
|
||||
[JsonPropertyName("elicitations")]
|
||||
public required IReadOnlyList<ElicitRequestParams> Elicitations { get; init; }
|
||||
}
|
||||
Loading…
Reference in a new issue