Add support for URL mode elicitation (#1021)

This commit is contained in:
Stephen Halter 2025-12-05 00:03:16 -08:00 • committed by GitHub
commit ae17ba0b4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -181,23 +181,31 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
{
LogRequestHandlerException(EndpointName, request.Method, ex);
JsonRpcErrorDetail detail = ex is McpProtocolException mcpProtocolException ?
new()
JsonRpcErrorDetail detail = ex switch
{
UrlElicitationRequiredException urlException => new()
{
Code = (int)urlException.ErrorCode,
Message = urlException.Message,
Data = urlException.CreateErrorDataNode(),
},
McpProtocolException mcpProtocolException => new()
{
Code = (int)mcpProtocolException.ErrorCode,
Message = mcpProtocolException.Message,
Data = ConvertExceptionData(mcpProtocolException.Data),
} : ex is McpException mcpException ?
new()
},
McpException mcpException => new()
{
Code = (int)McpErrorCode.InternalError,
Message = mcpException.Message,
} :
new()
},
_ => new()
{
Code = (int)McpErrorCode.InternalError,
Message = "An error occurred.",
};
},
};
var errorMessage = new JsonRpcError
{
@ -453,40 +461,7 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
if (response is JsonRpcError error)
{
LogSendingRequestFailed(EndpointName, request.Method, error.Error.Message, error.Error.Code);
var exception = new McpProtocolException($"Request failed (remote): {error.Error.Message}", (McpErrorCode)error.Error.Code);
// Populate exception.Data with the error data if present.
// When deserializing JSON, Data will be a JsonElement.
// We extract primitive values (strings, numbers, bools) for broader compatibility,
// as JsonElement is not [Serializable] and cannot be stored in Exception.Data on .NET Framework.
if (error.Error.Data is JsonElement jsonElement && jsonElement.ValueKind == JsonValueKind.Object)
{
foreach (var property in jsonElement.EnumerateObject())
{
object? value = property.Value.ValueKind switch
{
JsonValueKind.String => property.Value.GetString(),
JsonValueKind.Number => property.Value.GetDouble(),
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => null,
#if NET
// Objects and arrays are stored as JsonElement on .NET Core only
_ => property.Value,
#else
// Skip objects/arrays on .NET Framework as JsonElement is not serializable
_ => (object?)null,
#endif
};
if (value is not null || property.Value.ValueKind == JsonValueKind.Null)
{
exception.Data[property.Name] = value;
}
}
}
throw exception;
throw CreateRemoteProtocolException(error);
}
if (response is JsonRpcResponse success)
@ -803,6 +778,56 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
return null;
}
private static McpProtocolException CreateRemoteProtocolException(JsonRpcError error)
{
string formattedMessage = $"Request failed (remote): {error.Error.Message}";
var errorCode = (McpErrorCode)error.Error.Code;
McpProtocolException exception;
if (errorCode == McpErrorCode.UrlElicitationRequired &&
UrlElicitationRequiredException.TryCreateFromError(formattedMessage, error.Error, out var urlException))
{
exception = urlException;
}
else
{
exception = new McpProtocolException(formattedMessage, errorCode);
}
// Populate exception.Data with the error data if present.
// When deserializing JSON, Data will be a JsonElement.
// We extract primitive values (strings, numbers, bools) for broader compatibility,
// as JsonElement is not [Serializable] and cannot be stored in Exception.Data on .NET Framework.
if (error.Error.Data is JsonElement jsonElement && jsonElement.ValueKind == JsonValueKind.Object)
{
foreach (var property in jsonElement.EnumerateObject())
{
object? value = property.Value.ValueKind switch
{
JsonValueKind.String => property.Value.GetString(),
JsonValueKind.Number => property.Value.GetDouble(),
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => null,
#if NET
// Objects and arrays are stored as JsonElement on .NET Core only
_ => property.Value,
#else
// Skip objects/arrays on .NET Framework as JsonElement is not serializable
_ => (object?)null,
#endif
};
if (value is not null || property.Value.ValueKind == JsonValueKind.Null)
{
exception.Data[property.Name] = value;
}
}
}
return exception;
}
/// <summary>
/// Converts the <see cref="Exception.Data"/> dictionary to a serializable <see cref="Dictionary{TKey, TValue}"/>.
/// Returns null if the data dictionary is empty or contains no string keys with serializable values.