Upgrade rmcp to 3.0.0-beta.3 (#35720)

## What changed

- Update the `rmcp` model and transport integrations for `3.0.0-beta.3`.
- Preserve legacy `elicitation/create` form requests, schema defaults, metadata, and wire-format compatibility while adopting the new elicitation types.
- Harden OAuth discovery by using GET-first discovery without starting an MCP session, preventing cross-origin header redirects, validating authorization-server and callback issuers, and retaining compatibility with metadata that omits an issuer.
- Keep stored OAuth credentials readable and avoid requiring reauthorization after transient refresh failures.

## Testing

- Add coverage for legacy elicitation round trips, OAuth discovery variants, callback issuer validation, credential migration, and refresh failures.

GitOrigin-RevId: 756197d26f3fd347c28c400228f6b3d06ed493b7
This commit is contained in:
thomas authored and copyberry committed 2026-07-28 05:17:25 +00:00
1 parent 899539c03a
commit 61de0d8fe8
48 files changed
+1391 -639

No files matched your search

@@ -287,6 +287,7 @@ impl From<rmcp::model::ElicitationAction> for McpServerElicitationAction {
rmcp::model::ElicitationAction::Accept => Self::Accept,
rmcp::model::ElicitationAction::Decline => Self::Decline,
rmcp::model::ElicitationAction::Cancel => Self::Cancel,
_ => Self::Cancel,
}
}
}
@@ -344,10 +345,38 @@ pub enum McpElicitationObjectType {
pub enum McpElicitationPrimitiveSchema {
Enum(McpElicitationEnumSchema),
String(McpElicitationStringSchema),
#[serde(serialize_with = "serialize_mcp_elicitation_number_schema")]
Number(McpElicitationNumberSchema),
Boolean(McpElicitationBooleanSchema),
}
fn serialize_mcp_elicitation_number_schema<S>(
schema: &McpElicitationNumberSchema,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if schema.type_ != McpElicitationNumberType::Integer {
return schema.serialize(serializer);
}
let mut value = serde_json::to_value(schema).map_err(serde::ser::Error::custom)?;
if let Some(object) = value.as_object_mut() {
for key in ["minimum", "maximum", "default"] {
if let Some(value) = object.get_mut(key)
&& let Some(number) = value.as_f64()
&& number.fract() == 0.0
&& number >= i64::MIN as f64
&& number < -(i64::MIN as f64)
{
*value = serde_json::Value::from(number as i64);
}
}
}
value.serialize(serializer)
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
@@ -716,18 +745,16 @@ pub struct McpServerElicitationRequestResponse {
pub meta: Option<JsonValue>,
}
impl From<McpServerElicitationRequestResponse> for rmcp::model::CreateElicitationResult {
impl From<McpServerElicitationRequestResponse> for rmcp::model::ElicitResult {
fn from(value: McpServerElicitationRequestResponse) -> Self {
Self {
action: value.action.into(),
content: value.content,
meta: None,
}
let mut result = Self::new(value.action.into());
result.content = value.content;
result
}
}
impl From<rmcp::model::CreateElicitationResult> for McpServerElicitationRequestResponse {
fn from(value: rmcp::model::CreateElicitationResult) -> Self {
impl From<rmcp::model::ElicitResult> for McpServerElicitationRequestResponse {
fn from(value: rmcp::model::ElicitResult) -> Self {
Self {
action: value.action.into(),
content: value.content,
@@ -1989,13 +1989,10 @@ fn client_request_turn_start_granular_approval_policy_is_marked_experimental() {
#[test]
fn mcp_server_elicitation_response_round_trips_rmcp_result() {
let rmcp_result = rmcp::model::CreateElicitationResult {
action: rmcp::model::ElicitationAction::Accept,
content: Some(json!({
let rmcp_result = rmcp::model::ElicitResult::new(rmcp::model::ElicitationAction::Accept)
.with_content(json!({
"confirmed": true,
})),
meta: None,
};
}));
let v2_response = McpServerElicitationRequestResponse::from(rmcp_result.clone());
assert_eq!(
@@ -2008,10 +2005,7 @@ fn mcp_server_elicitation_response_round_trips_rmcp_result() {
meta: None,
}
);
assert_eq!(
rmcp::model::CreateElicitationResult::from(v2_response),
rmcp_result
);
assert_eq!(rmcp::model::ElicitResult::from(v2_response), rmcp_result);
}
#[test]
@@ -2204,6 +2198,45 @@ fn mcp_elicitation_schema_matches_mcp_2025_11_25_primitives() {
);
}
#[test]
fn mcp_elicitation_preserves_integer_and_number_schema_wire_values() {
for (number_type, expected_type, expected_minimum, expected_maximum, expected_default) in [
(
McpElicitationNumberType::Integer,
"integer",
json!(1),
json!(99),
json!(30),
),
(
McpElicitationNumberType::Number,
"number",
json!(1.0),
json!(99.0),
json!(30.0),
),
] {
let schema = McpElicitationPrimitiveSchema::Number(McpElicitationNumberSchema {
type_: number_type,
title: None,
description: None,
minimum: Some(1.0),
maximum: Some(99.0),
default: Some(30.0),
});
assert_eq!(
serde_json::to_value(schema).expect("numeric elicitation schema must serialize"),
json!({
"type": expected_type,
"minimum": expected_minimum,
"maximum": expected_maximum,
"default": expected_default,
})
);
}
}
#[test]
fn mcp_server_elicitation_request_rejects_null_core_form_schema() {
let result = McpServerElicitationRequest::try_from(CoreElicitationRequest::Form {