all: expose JSON-RPC error codes, and support URLElicitationRequired errors (#655)

Expose the jsonrpc.Error type to allow access to underlying JSON-RPC
error codes. Also, expose common JSON-RPC error codes in the jsonrpc
package, and MCP error codes in the mcp package.

Also, implement support for the MCP URL elicitation required error
(-32042), enabling servers to request out-of-band user authorization and
clients to automatically handle these requests with retry logic.

Server-side:
- Add CodeURLElicitationRequired constant
- Add URLElicitationRequiredError() constructor.

Client-side:
- Add urlElicitationMiddleware to intercept URLElicitationRequired
  errors and retry calls.
- Update callElicitationCompleteHandler to signal waiting operations.
- Fix capability advertisement to support both form and URL modes.

Fixes #452
Fixes #623
This commit is contained in:
Robert Findley 2025-12-01 11:29:17 -05:00 • committed by GitHub
commit 2ce44d45e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -222,6 +222,61 @@ func TestClientCapabilities(t *testing.T) {
Sampling: &SamplingCapabilities{},
},
},
{
name: "With form elicitation",
configureClient: func(s *Client) {},
clientOpts: ClientOptions{
ElicitationModes: []string{"form"},
ElicitationHandler: func(context.Context, *ElicitRequest) (*ElicitResult, error) {
return nil, nil
},
},
wantCapabilities: &ClientCapabilities{
Roots: struct {
ListChanged bool "json:\"listChanged,omitempty\""
}{ListChanged: true},
Elicitation: &ElicitationCapabilities{
Form: &FormElicitationCapabilities{},
},
},
},
{
name: "With URL elicitation",
configureClient: func(s *Client) {},
clientOpts: ClientOptions{
ElicitationModes: []string{"url"},
ElicitationHandler: func(context.Context, *ElicitRequest) (*ElicitResult, error) {
return nil, nil
},
},
wantCapabilities: &ClientCapabilities{
Roots: struct {
ListChanged bool "json:\"listChanged,omitempty\""
}{ListChanged: true},
Elicitation: &ElicitationCapabilities{
URL: &URLElicitationCapabilities{},
},
},
},
{
name: "With both form and URL elicitation",
configureClient: func(s *Client) {},
clientOpts: ClientOptions{
ElicitationModes: []string{"form", "url"},
ElicitationHandler: func(context.Context, *ElicitRequest) (*ElicitResult, error) {
return nil, nil
},
},
wantCapabilities: &ClientCapabilities{
Roots: struct {
ListChanged bool "json:\"listChanged,omitempty\""
}{ListChanged: true},
Elicitation: &ElicitationCapabilities{
Form: &FormElicitationCapabilities{},
URL: &URLElicitationCapabilities{},
},
},
},
}
for _, tc := range testCases {