Skip to content

Conversation

@halter73
Copy link
Contributor

@halter73 halter73 commented Nov 24, 2025

This PR adds support URL mode elicitation requests defined by SEP-1036 which is for out-of-band server-to-client elicitations that may involve sensitive data the MCP server doesn't want to give to the MCP client or host.

Fixes #750

return urlException;
}

return new McpProtocolException(formattedMessage, errorCode);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing we could do in addition to or instead of throwing a UrlElicitationRequiredException is to always attach the JsonRpcError to a nullable property on McpProtocolExceptions if it was created from one.

Copy link
Contributor

@stephentoub stephentoub Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems better / more general-purpose than a custom exception type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you not like the ability to specifically catch a UrlElicitationRequiredException? The alternative would be to catch all McpProtocolException and then parse the JsonNode Data property.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I've misunderstood. Why does someone need to catch it? Are we using it for control flow?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This is tested by UrlElicitationRequired_Exception_Propagates_To_Client. I don't have a proper client sample, but if I did, it would look a lot like the TypeScript one in https://github.com/modelcontextprotocol/typescript-sdk/blob/e6c71bbab1dff7bf0c84eee96e74ef87f82a1dbe/src/examples/client/elicitationUrlExample.ts#L711-L719.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean the mcp spec dictates that url elicitations be modeled as json rpc errors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's either an error or a request. Section 4.2 and 4.3 of the new elicitation spec go over the two flows for URL elicitation.

URL Mode Flow

sequenceDiagram
    participant UserAgent as User Agent (Browser)
    participant User
    participant Client
    participant Server

    Note over Server: Server initiates elicitation
    Server->>Client: elicitation/create (mode: url)

    Client->>User: Present consent to open URL
    User-->>Client: Provide consent

    Client->>UserAgent: Open URL
    Client->>Server: Accept response

    Note over User,UserAgent: User interaction
    UserAgent-->>Server: Interaction complete
    Server-->>Client: notifications/elicitation/complete (optional)

    Note over Server: Continue processing with new information
Loading

URL Mode With Elicitation Required Error Flow

sequenceDiagram
    participant UserAgent as User Agent (Browser)
    participant User
    participant Client
    participant Server

    Client->>Server: tools/call

    Note over Server: Server needs authorization
    Server->>Client: URLElicitationRequiredError
    Note over Client: Client notes the original request can be retried after elicitation

    Client->>User: Present consent to open URL
    User-->>Client: Provide consent

    Client->>UserAgent: Open URL

    Note over User,UserAgent: User interaction

    UserAgent-->>Server: Interaction complete
    Server-->>Client: notifications/elicitation/complete (optional)

    Client->>Server: Retry tools/call (optional)
Loading

https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#message-flow

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, in that case it makes sense to keep the exception. I think it's unfortunate that elicitation requests are being modeled as errors.


If the user provides the requested information, the ElicitationHandler should return an <xref:ModelContextProtocol.Protocol.ElicitResult> with the action set to "accept" and the content containing the user's input.
If the user does not provide the requested information, the ElicitationHandler should return an [<xref:ModelContextProtocol.Protocol.ElicitResult> with the action set to "reject" and no content.
Elicitation is an optional feature so clients declare their support for it in their capabilities as part of the `initialize` request. Clients can support `Form` (in-band), `Url` (out-of-band), or both.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional capability? It's a little redundant as almost every capability is optional. Maybe just "Clients declare their support.." ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Elicitation is an optional feature so clients declare their support for it in their capabilities as part of the `initialize` request. Clients can support `Form` (in-band), `Url` (out-of-band), or both.
Clients declare their support for elicitation in their capabilities as part of the `initialize` request. Clients can support `Form` (in-band), `Url` (out-of-band), or both.

@PederHP
Copy link
Member

PederHP commented Nov 24, 2025

Really like the example. Only downside is it might distract from the fact url mode will usually point at an url that is distinct from the mcp server. I think an ideal sample should be MCP server and separate blazor server with some communication mechanism between them. Just to show it's out of band and the security pattern of the sensitive information never passing through the mcp server. But that's something that can be done later.

@halter73
Copy link
Contributor Author

Really like the example. Only downside is it might distract from the fact url mode will usually point at an url that is distinct from the mcp server. I think an ideal sample should be MCP server and separate blazor server with some communication mechanism between them. Just to show it's out of band and the security pattern of the sensitive information never passing through the mcp server. But that's something that can be done later.

I accidently pushed the sample. I removed it from this PR. It wasn't fully functional yet, and it didn't use auth for the Blazor form. Later we can add a sample that leverages AddOpenIdConnect and AddCookie pointing to the same OAuth provider as AddMcp, possibly in a different project to show that the URL doesn't need to be hosted by the MCP server. I don't think it's really a problem to have the MCP server host the elicitation URL though. It still serves a purpose of collecting sensitive information from the user without exposing it to the host or client.

{
// If ElicitationCapability is set but both modes are null, default to form mode for backward compatibility
_options.Capabilities.Elicitation.Form = new();
}
Copy link
Contributor

@stephentoub stephentoub Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this could be simplified slightly to:

_options.Capabilities.Elicitation ??= new();
if (_options.Capabilities.Elicitation.Form is null &&
    _options.Capabilities.Elicitation.Url is null)
{
    // If both modes are null, default to form mode for backward compatibility.
    _options.Capabilities.Elicitation.Form = new();
}


var capability = new ElicitationCapability();
bool hasForm = false;
bool hasUrl = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need these bools rather than just checking whether Form/Url are set?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SEP-1036: Add support for URL Mode Elicitation for secure out-of-band interactions

4 participants