Codex Handbook
app-server-protocol/src/protocol/v2/mcp.rs 709 lines
use super::shared::v2_enum_from_core;use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest;use codex_protocol::items::McpToolCallError as CoreMcpToolCallError;use codex_protocol::mcp::CallToolResult as CoreMcpCallToolResult;use codex_protocol::mcp::McpServerInfo;use codex_protocol::mcp::Resource as McpResource;pub use codex_protocol::mcp::ResourceContent as McpResourceContent;use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate;use codex_protocol::mcp::Tool as McpTool;use schemars::JsonSchema;use serde::Deserialize;use serde::Serialize;use serde_json::Value as JsonValue;use std::collections::BTreeMap;use ts_rs::TS;v2_enum_from_core!(    pub enum McpAuthStatus from codex_protocol::protocol::McpAuthStatus {        Unsupported,        NotLoggedIn,        BearerToken,        OAuth    });#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct ListMcpServerStatusParams {    /// Opaque pagination cursor returned by a previous call.    #[ts(optional = nullable)]    pub cursor: Option<String>,    /// Optional page size; defaults to a server-defined value.    #[ts(optional = nullable)]    pub limit: Option<u32>,    /// Controls how much MCP inventory data to fetch for each server.    /// Defaults to `Full` when omitted.    #[ts(optional = nullable)]    pub detail: Option<McpServerStatusDetail>,    #[ts(optional = nullable)]    pub thread_id: Option<String>,}#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(rename_all = "camelCase", export_to = "v2/")]pub enum McpServerStatusDetail {    Full,    ToolsAndAuthOnly,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpServerStatus {    pub name: String,    pub server_info: Option<McpServerInfo>,    pub tools: std::collections::HashMap<String, McpTool>,    pub resources: Vec<McpResource>,    pub resource_templates: Vec<McpResourceTemplate>,    pub auth_status: McpAuthStatus,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct ListMcpServerStatusResponse {    pub data: Vec<McpServerStatus>,    /// Opaque cursor to pass to the next call to continue after the last item.    /// If None, there are no more items to return.    pub next_cursor: Option<String>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpResourceReadParams {    #[ts(optional = nullable)]    pub thread_id: Option<String>,    pub server: String,    pub uri: String,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpResourceReadResponse {    pub contents: Vec<McpResourceContent>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpServerToolCallParams {    pub thread_id: String,    pub server: String,    pub tool: String,    #[serde(default, skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub arguments: Option<JsonValue>,    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub meta: Option<JsonValue>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpServerToolCallResponse {    pub content: Vec<JsonValue>,    #[serde(default, skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub structured_content: Option<JsonValue>,    #[serde(default, skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub is_error: Option<bool>,    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub meta: Option<JsonValue>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpToolCallResult {    // NOTE: `rmcp::model::Content` (and its `RawContent` variants) would be a more precise Rust    // representation of MCP content blocks. We intentionally use `serde_json::Value` here because    // this crate exports JSON schema + TS types (`schemars`/`ts-rs`), and the rmcp model types    // aren't set up to be schema/TS friendly (and would introduce heavier coupling to rmcp's Rust    // representations). Using `JsonValue` keeps the payload wire-shaped and easy to export.    pub content: Vec<JsonValue>,    pub structured_content: Option<JsonValue>,    #[serde(rename = "_meta")]    #[ts(rename = "_meta")]    pub meta: Option<JsonValue>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpToolCallError {    pub message: String,}impl From<CoreMcpCallToolResult> for McpServerToolCallResponse {    fn from(result: CoreMcpCallToolResult) -> Self {        Self {            content: result.content,            structured_content: result.structured_content,            is_error: result.is_error,            meta: result.meta,        }    }}impl From<CoreMcpCallToolResult> for McpToolCallResult {    fn from(result: CoreMcpCallToolResult) -> Self {        Self {            content: result.content,            structured_content: result.structured_content,            meta: result.meta,        }    }}impl From<CoreMcpToolCallError> for McpToolCallError {    fn from(error: CoreMcpToolCallError) -> Self {        Self {            message: error.message,        }    }}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpServerRefreshParams {}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpServerRefreshResponse {}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpServerOauthLoginParams {    pub name: String,    #[serde(default, skip_serializing_if = "Option::is_none")]    #[ts(optional = nullable)]    pub scopes: Option<Vec<String>>,    #[serde(default, skip_serializing_if = "Option::is_none")]    #[ts(optional = nullable)]    pub timeout_secs: Option<i64>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpServerOauthLoginResponse {    pub authorization_url: String,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpToolCallProgressNotification {    pub thread_id: String,    pub turn_id: String,    pub item_id: String,    pub message: String,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpServerOauthLoginCompletedNotification {    pub name: String,    pub success: bool,    #[serde(default, skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub error: Option<String>,}#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub enum McpServerStartupState {    Starting,    Ready,    Failed,    Cancelled,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpServerStatusUpdatedNotification {    pub thread_id: Option<String>,    pub name: String,    pub status: McpServerStartupState,    pub error: Option<String>,}#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub enum McpServerElicitationAction {    Accept,    Decline,    Cancel,}impl McpServerElicitationAction {    pub fn to_core(self) -> codex_protocol::approvals::ElicitationAction {        match self {            Self::Accept => codex_protocol::approvals::ElicitationAction::Accept,            Self::Decline => codex_protocol::approvals::ElicitationAction::Decline,            Self::Cancel => codex_protocol::approvals::ElicitationAction::Cancel,        }    }}impl From<McpServerElicitationAction> for rmcp::model::ElicitationAction {    fn from(value: McpServerElicitationAction) -> Self {        match value {            McpServerElicitationAction::Accept => Self::Accept,            McpServerElicitationAction::Decline => Self::Decline,            McpServerElicitationAction::Cancel => Self::Cancel,        }    }}impl From<rmcp::model::ElicitationAction> for McpServerElicitationAction {    fn from(value: rmcp::model::ElicitationAction) -> Self {        match value {            rmcp::model::ElicitationAction::Accept => Self::Accept,            rmcp::model::ElicitationAction::Decline => Self::Decline,            rmcp::model::ElicitationAction::Cancel => Self::Cancel,        }    }}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpServerElicitationRequestParams {    pub thread_id: String,    /// Active Codex turn when this elicitation was observed, if app-server could correlate one.    ///    /// This is nullable because MCP models elicitation as a standalone server-to-client request    /// identified by the MCP server request id. It may be triggered during a turn, but turn    /// context is app-server correlation rather than part of the protocol identity of the    /// elicitation itself.    pub turn_id: Option<String>,    pub server_name: String,    #[serde(flatten)]    pub request: McpServerElicitationRequest,    // TODO: When core can correlate an elicitation with an MCP tool call, expose the associated    // McpToolCall item id here as an optional field. The current core event does not carry that    // association.}/// Typed form schema for MCP `elicitation/create` requests.////// This matches the `requestedSchema` shape from the MCP 2025-11-25/// `ElicitRequestFormParams` schema.#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase", deny_unknown_fields)]#[ts(export_to = "v2/")]pub struct McpElicitationSchema {    #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]    #[ts(optional, rename = "$schema")]    pub schema_uri: Option<String>,    #[serde(rename = "type")]    #[ts(rename = "type")]    pub type_: McpElicitationObjectType,    pub properties: BTreeMap<String, McpElicitationPrimitiveSchema>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub required: Option<Vec<String>>,}#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "lowercase")]#[ts(export_to = "v2/")]pub enum McpElicitationObjectType {    Object,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(untagged)]#[ts(export_to = "v2/")]pub enum McpElicitationPrimitiveSchema {    Enum(McpElicitationEnumSchema),    String(McpElicitationStringSchema),    Number(McpElicitationNumberSchema),    Boolean(McpElicitationBooleanSchema),}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase", deny_unknown_fields)]#[ts(export_to = "v2/")]pub struct McpElicitationStringSchema {    #[serde(rename = "type")]    #[ts(rename = "type")]    pub type_: McpElicitationStringType,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub title: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub description: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub min_length: Option<u32>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub max_length: Option<u32>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub format: Option<McpElicitationStringFormat>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub default: Option<String>,}#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "lowercase")]#[ts(export_to = "v2/")]pub enum McpElicitationStringType {    String,}#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "kebab-case")]#[ts(rename_all = "kebab-case", export_to = "v2/")]pub enum McpElicitationStringFormat {    Email,    Uri,    Date,    DateTime,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase", deny_unknown_fields)]#[ts(export_to = "v2/")]pub struct McpElicitationNumberSchema {    #[serde(rename = "type")]    #[ts(rename = "type")]    pub type_: McpElicitationNumberType,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub title: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub description: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub minimum: Option<f64>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub maximum: Option<f64>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub default: Option<f64>,}#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "lowercase")]#[ts(export_to = "v2/")]pub enum McpElicitationNumberType {    Number,    Integer,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase", deny_unknown_fields)]#[ts(export_to = "v2/")]pub struct McpElicitationBooleanSchema {    #[serde(rename = "type")]    #[ts(rename = "type")]    pub type_: McpElicitationBooleanType,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub title: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub description: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub default: Option<bool>,}#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "lowercase")]#[ts(export_to = "v2/")]pub enum McpElicitationBooleanType {    Boolean,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(untagged)]#[ts(export_to = "v2/")]pub enum McpElicitationEnumSchema {    SingleSelect(McpElicitationSingleSelectEnumSchema),    MultiSelect(McpElicitationMultiSelectEnumSchema),    Legacy(McpElicitationLegacyTitledEnumSchema),}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase", deny_unknown_fields)]#[ts(export_to = "v2/")]pub struct McpElicitationLegacyTitledEnumSchema {    #[serde(rename = "type")]    #[ts(rename = "type")]    pub type_: McpElicitationStringType,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub title: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub description: Option<String>,    #[serde(rename = "enum")]    #[ts(rename = "enum")]    pub enum_: Vec<String>,    #[serde(rename = "enumNames", skip_serializing_if = "Option::is_none")]    #[ts(optional, rename = "enumNames")]    pub enum_names: Option<Vec<String>>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub default: Option<String>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(untagged)]#[ts(export_to = "v2/")]pub enum McpElicitationSingleSelectEnumSchema {    Untitled(McpElicitationUntitledSingleSelectEnumSchema),    Titled(McpElicitationTitledSingleSelectEnumSchema),}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase", deny_unknown_fields)]#[ts(export_to = "v2/")]pub struct McpElicitationUntitledSingleSelectEnumSchema {    #[serde(rename = "type")]    #[ts(rename = "type")]    pub type_: McpElicitationStringType,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub title: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub description: Option<String>,    #[serde(rename = "enum")]    #[ts(rename = "enum")]    pub enum_: Vec<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub default: Option<String>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase", deny_unknown_fields)]#[ts(export_to = "v2/")]pub struct McpElicitationTitledSingleSelectEnumSchema {    #[serde(rename = "type")]    #[ts(rename = "type")]    pub type_: McpElicitationStringType,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub title: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub description: Option<String>,    #[serde(rename = "oneOf")]    #[ts(rename = "oneOf")]    pub one_of: Vec<McpElicitationConstOption>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub default: Option<String>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(untagged)]#[ts(export_to = "v2/")]pub enum McpElicitationMultiSelectEnumSchema {    Untitled(McpElicitationUntitledMultiSelectEnumSchema),    Titled(McpElicitationTitledMultiSelectEnumSchema),}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase", deny_unknown_fields)]#[ts(export_to = "v2/")]pub struct McpElicitationUntitledMultiSelectEnumSchema {    #[serde(rename = "type")]    #[ts(rename = "type")]    pub type_: McpElicitationArrayType,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub title: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub description: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub min_items: Option<u64>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub max_items: Option<u64>,    pub items: McpElicitationUntitledEnumItems,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub default: Option<Vec<String>>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase", deny_unknown_fields)]#[ts(export_to = "v2/")]pub struct McpElicitationTitledMultiSelectEnumSchema {    #[serde(rename = "type")]    #[ts(rename = "type")]    pub type_: McpElicitationArrayType,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub title: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub description: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub min_items: Option<u64>,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub max_items: Option<u64>,    pub items: McpElicitationTitledEnumItems,    #[serde(skip_serializing_if = "Option::is_none")]    #[ts(optional)]    pub default: Option<Vec<String>>,}#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "lowercase")]#[ts(export_to = "v2/")]pub enum McpElicitationArrayType {    Array,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(deny_unknown_fields)]#[ts(export_to = "v2/")]pub struct McpElicitationUntitledEnumItems {    #[serde(rename = "type")]    #[ts(rename = "type")]    pub type_: McpElicitationStringType,    #[serde(rename = "enum")]    #[ts(rename = "enum")]    pub enum_: Vec<String>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(deny_unknown_fields)]#[ts(export_to = "v2/")]pub struct McpElicitationTitledEnumItems {    #[serde(rename = "anyOf", alias = "oneOf")]    #[ts(rename = "anyOf")]    pub any_of: Vec<McpElicitationConstOption>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(deny_unknown_fields)]#[ts(export_to = "v2/")]pub struct McpElicitationConstOption {    #[serde(rename = "const")]    #[ts(rename = "const")]    pub const_: String,    pub title: String,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(tag = "mode", rename_all = "camelCase")]#[ts(tag = "mode")]#[ts(export_to = "v2/")]pub enum McpServerElicitationRequest {    #[serde(rename_all = "camelCase")]    #[ts(rename_all = "camelCase")]    Form {        #[serde(rename = "_meta")]        #[ts(rename = "_meta")]        meta: Option<JsonValue>,        message: String,        requested_schema: McpElicitationSchema,    },    #[serde(rename_all = "camelCase")]    #[ts(rename_all = "camelCase")]    Url {        #[serde(rename = "_meta")]        #[ts(rename = "_meta")]        meta: Option<JsonValue>,        message: String,        url: String,        elicitation_id: String,    },}impl TryFrom<CoreElicitationRequest> for McpServerElicitationRequest {    type Error = serde_json::Error;    fn try_from(value: CoreElicitationRequest) -> Result<Self, Self::Error> {        match value {            CoreElicitationRequest::Form {                meta,                message,                requested_schema,            } => Ok(Self::Form {                meta,                message,                requested_schema: serde_json::from_value(requested_schema)?,            }),            CoreElicitationRequest::Url {                meta,                message,                url,                elicitation_id,            } => Ok(Self::Url {                meta,                message,                url,                elicitation_id,            }),        }    }}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct McpServerElicitationRequestResponse {    pub action: McpServerElicitationAction,    /// Structured user input for accepted elicitations, mirroring RMCP `CreateElicitationResult`.    ///    /// This is nullable because decline/cancel responses have no content.    pub content: Option<JsonValue>,    /// Optional client metadata for form-mode action handling.    #[serde(rename = "_meta")]    #[ts(rename = "_meta")]    pub meta: Option<JsonValue>,}impl From<McpServerElicitationRequestResponse> for rmcp::model::CreateElicitationResult {    fn from(value: McpServerElicitationRequestResponse) -> Self {        Self {            action: value.action.into(),            content: value.content,            meta: None,        }    }}impl From<rmcp::model::CreateElicitationResult> for McpServerElicitationRequestResponse {    fn from(value: rmcp::model::CreateElicitationResult) -> Self {        Self {            action: value.action.into(),            content: value.content,            meta: None,        }    }}