mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
## Summary - distinguish expired, non-refreshable stored MCP OAuth credentials from first-time missing credentials - carry a typed `failureReason: "reauthenticationRequired"` on the existing `mcpServer/startupStatus/updated` notification only when user action is required - keep the public MCP auth-status API unchanged and regenerate the app-server protocol schemas and documentation ## Why An MCP server with an expired access token and no usable refresh token currently fails startup without giving clients a reliable, typed recovery signal. The existing startup-status notification is the natural place to carry this state. Its nullable `failureReason` keeps the recovery reason attached to the failed startup transition without adding a one-off notification. Internally, Codex distinguishes first-time login from reauthentication and emits the reason only when the startup error itself requires authentication. ## User impact App clients can prompt an existing user to reconnect an MCP server when automatic recovery is impossible by handling a failed `mcpServer/startupStatus/updated` notification whose `failureReason` is `reauthenticationRequired`. Starting, ready, cancelled, unrelated failures, and first-time setup carry no reauthentication reason. ## Companion app PR - openai/openai#1069582 ## Validation - `just test -p codex-app-server-protocol` — 248 passed; schema fixture tests passed - `cargo check -p codex-app-server -p codex-tui` - `just test -p codex-rmcp-client -p codex-mcp` — 184 passed, 2 skipped - `just test -p codex-protocol -p codex-app-server-protocol -p codex-mcp` — 579 passed - `just write-app-server-schema` - `just fmt`
738 lines
24 KiB
Rust
738 lines
24 KiB
Rust
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
|
|
}
|
|
);
|
|
|
|
v2_enum_from_core!(
|
|
pub enum McpServerStartupFailureReason from codex_protocol::protocol::McpStartupFailureReason {
|
|
ReauthenticationRequired
|
|
}
|
|
);
|
|
|
|
#[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,
|
|
#[ts(optional = nullable)]
|
|
pub thread_id: Option<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 thread_id: Option<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>,
|
|
pub failure_reason: Option<McpServerStartupFailureReason>,
|
|
}
|
|
|
|
#[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 = "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 {
|
|
#[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::OpenAiForm {
|
|
meta,
|
|
message,
|
|
requested_schema,
|
|
} => Ok(Self::OpenAiForm {
|
|
meta,
|
|
message,
|
|
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,
|
|
}
|
|
}
|
|
}
|