Support openai/form extended form elicitations (#27500)

# Summary
Allow App Server clients to opt into `openai/form` MCP elicitations.
This commit is contained in:
Gabriel Peal authored and GitHub committed 2026-06-18 11:54:49 -07:00
1 parent 32a696dbac
commit 21a599fa56
67 files changed
+1486 -328

No files matched your search

@@ -2150,7 +2150,7 @@ mod tests {
}
#[test]
fn serialize_initialize_with_opt_out_notification_methods() -> Result<()> {
fn serialize_initialize_capabilities() -> Result<()> {
let request = ClientRequest::Initialize {
request_id: RequestId::Integer(42),
params: v1::InitializeParams {
@@ -2162,6 +2162,7 @@ mod tests {
capabilities: Some(v1::InitializeCapabilities {
experimental_api: true,
request_attestation: true,
mcp_server_openai_form_elicitation: true,
opt_out_notification_methods: Some(vec![
"thread/started".to_string(),
"item/agentMessage/delta".to_string(),
@@ -2183,6 +2184,7 @@ mod tests {
"capabilities": {
"experimentalApi": true,
"requestAttestation": true,
"mcpServerOpenaiFormElicitation": true,
"optOutNotificationMethods": [
"thread/started",
"item/agentMessage/delta"
@@ -2196,7 +2198,7 @@ mod tests {
}
#[test]
fn deserialize_initialize_with_opt_out_notification_methods() -> Result<()> {
fn deserialize_initialize_capabilities() -> Result<()> {
let request: ClientRequest = serde_json::from_value(json!({
"method": "initialize",
"id": 42,
@@ -2209,6 +2211,7 @@ mod tests {
"capabilities": {
"experimentalApi": true,
"requestAttestation": true,
"mcpServerOpenaiFormElicitation": true,
"optOutNotificationMethods": [
"thread/started",
"item/agentMessage/delta"
@@ -2230,6 +2233,7 @@ mod tests {
capabilities: Some(v1::InitializeCapabilities {
experimental_api: true,
request_attestation: true,
mcp_server_openai_form_elicitation: true,
opt_out_notification_methods: Some(vec![
"thread/started".to_string(),
"item/agentMessage/delta".to_string(),
@@ -50,6 +50,9 @@ pub struct InitializeCapabilities {
/// Opt into `attestation/generate` requests for upstream `x-oai-attestation`.
#[serde(default)]
pub request_attestation: bool,
/// Allow downstream MCP servers to request OpenAI extended form elicitations.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub mcp_server_openai_form_elicitation: bool,
/// Exact notification method names that should be suppressed for this
/// connection (for example `thread/started`).
#[ts(optional = nullable)]
@@ -632,6 +632,15 @@ pub enum McpServerElicitationRequest {
message: String,
requested_schema: McpElicitationSchema,
},
#[serde(rename = "openai/form", rename_all = "camelCase")]
#[ts(rename = "openai/form", rename_all = "camelCase")]
OpenAiForm {
#[serde(rename = "_meta")]
#[ts(rename = "_meta")]
meta: Option<JsonValue>,
message: String,
requested_schema: JsonValue,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Url {
@@ -658,6 +667,15 @@ impl TryFrom<CoreElicitationRequest> for McpServerElicitationRequest {
message,
requested_schema: serde_json::from_value(requested_schema)?,
}),
CoreElicitationRequest::OpenAiForm {
meta,
message,
requested_schema,
} => Ok(Self::OpenAiForm {
meta,
message,
requested_schema,
}),
CoreElicitationRequest::Url {
meta,
message,
@@ -1901,6 +1901,40 @@ fn mcp_server_elicitation_request_from_core_form_request() {
);
}
#[test]
fn mcp_server_elicitation_request_from_core_openai_form_request() {
let requested_schema = json!({
"type": "object",
"properties": {
"template": {
"type": "openai/imagePicker",
"title": "Template",
"items": [{
"id": "monthly-review",
"title": "Monthly review",
"image": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=",
}],
},
},
"required": ["template"],
});
let request = McpServerElicitationRequest::try_from(CoreElicitationRequest::OpenAiForm {
meta: None,
message: "Choose a report".to_string(),
requested_schema: requested_schema.clone(),
})
.expect("OpenAI form request should convert");
assert_eq!(
request,
McpServerElicitationRequest::OpenAiForm {
meta: None,
message: "Choose a report".to_string(),
requested_schema,
}
);
}
#[test]
fn mcp_elicitation_schema_matches_mcp_2025_11_25_primitives() {
let schema: McpElicitationSchema = serde_json::from_value(json!({