mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
## What changed
Allow `mcpServer/resource/read` to specify `target: {connectorId, linkId}` for direct hosted app reads without tool discovery, when supported by the backend. Require an explicit `linkId`: a string selects an account, while `null` requests no-auth access subject to backend policy.
Restrict direct targets to `codex_apps` and reject empty connector IDs, empty link IDs, and synthetic link IDs. Preserve originating app/account scope when `originCallId` and `threadId` are supplied, and retain discovery when `target` is omitted.
Update protocol schemas and TypeScript and Python models, preserving required-but-nullable `linkId` semantics in Python code generation.
## Testing
Extend resource-read tests to check target metadata with and without a thread, including explicit no-auth targets and omitted targets. Add Python model tests for string and null `linkId` values and rejection of a missing `linkId`.
GitOrigin-RevId: 8cf5595fbdd937193aeb529901fc95299f6e18ad
919 lines
31 KiB
Rust
919 lines
31 KiB
Rust
use super::shared::v2_enum_from_core;
|
|
use crate::JsonSchema;
|
|
use crate::TS;
|
|
use codex_experimental_api_macros::ExperimentalApi;
|
|
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 serde::Deserialize;
|
|
use serde::Serialize;
|
|
use serde_json::Value as JsonValue;
|
|
use std::collections::BTreeMap;
|
|
|
|
v2_enum_from_core!(
|
|
pub enum McpAuthStatus from codex_protocol::protocol::McpAuthStatus {
|
|
Unknown,
|
|
Unsupported,
|
|
NotLoggedIn,
|
|
BearerToken,
|
|
OAuth
|
|
}
|
|
);
|
|
|
|
v2_enum_from_core!(
|
|
pub enum McpServerStartupFailureReason from codex_protocol::protocol::McpStartupFailureReason {
|
|
ReauthenticationRequired
|
|
}
|
|
);
|
|
|
|
v2_enum_from_core!(
|
|
#[ts(rename_all = "camelCase")]
|
|
pub enum McpServerConnectionStatus from codex_protocol::mcp::McpServerConnectionStatus {
|
|
NotStarted,
|
|
Starting,
|
|
Connected,
|
|
AuthenticationRequired,
|
|
Failed,
|
|
Cancelled,
|
|
Disabled
|
|
}
|
|
);
|
|
|
|
#[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,
|
|
/// Current thread-runtime connection state; null when unavailable or the configuration changed.
|
|
pub runtime_status: Option<McpServerConnectionStatus>,
|
|
pub plugin_id: Option<String>,
|
|
/// HTTP origin of the effective configured endpoint, including plugin servers.
|
|
/// Excludes credentials, path, query, and fragment; null for non-HTTP transports.
|
|
pub http_origin: Option<String>,
|
|
pub server_info: Option<McpServerInfo>,
|
|
/// Capabilities advertised by the initialized MCP server; null when unavailable.
|
|
pub server_capabilities: Option<serde_json::Value>,
|
|
pub tools: std::collections::HashMap<String, McpTool>,
|
|
/// Tool discovery failed and no catalog was returned.
|
|
/// Null when a catalog is returned, including cached or empty catalogs.
|
|
pub tools_error: Option<String>,
|
|
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>,
|
|
/// Originating MCP tool call used to select the resource's app.
|
|
#[ts(optional = nullable)]
|
|
pub origin_call_id: Option<String>,
|
|
pub server: String,
|
|
pub uri: String,
|
|
#[ts(optional = nullable)]
|
|
pub connector_id: Option<String>,
|
|
/// Explicit hosted app/account. Omit to retain legacy resource discovery.
|
|
#[ts(optional = nullable)]
|
|
pub target: Option<McpResourceReadTarget>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct McpResourceReadTarget {
|
|
pub connector_id: String,
|
|
/// Null explicitly requests no-auth access, subject to the app's resource policy.
|
|
#[serde(deserialize_with = "Option::deserialize")]
|
|
#[schemars(
|
|
required,
|
|
schema_with = "crate::protocol::serde_helpers::nullable_string_schema"
|
|
)]
|
|
#[ts(type = "string | null")]
|
|
pub link_id: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct McpResourceReadResponse {
|
|
pub contents: Vec<McpResourceContent>,
|
|
/// Originating call when the server applied app-specific resource scoping.
|
|
pub origin_call_id: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct McpServerEventStreamStartParams {
|
|
pub thread_id: String,
|
|
pub server: String,
|
|
pub subscription_id: String,
|
|
pub name: String,
|
|
pub arguments: JsonValue,
|
|
#[serde(rename = "_meta")]
|
|
#[ts(rename = "_meta", optional = nullable)]
|
|
pub meta: Option<JsonValue>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct McpServerEventStreamStartResponse {}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct McpServerEventStreamStopParams {
|
|
pub subscription_id: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct McpServerEventStreamStopResponse {}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct McpServerEventNotification {
|
|
pub method: String,
|
|
pub params: JsonValue,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct McpServerEventStreamNotification {
|
|
pub subscription_id: String,
|
|
pub notification: McpServerEventNotification,
|
|
}
|
|
|
|
#[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>,
|
|
/// Registration strategy for this login only; omission selects automatic discovery.
|
|
#[ts(optional = nullable)]
|
|
pub client_registration: Option<McpServerOauthClientRegistration>,
|
|
#[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, Copy, PartialEq, Eq, Default, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(rename_all = "camelCase", export_to = "v2/")]
|
|
pub enum McpServerOauthClientRegistration {
|
|
#[default]
|
|
Auto,
|
|
Cimd,
|
|
Dcr,
|
|
}
|
|
|
|
#[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,
|
|
_ => Self::Cancel,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
|
#[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)]
|
|
#[experimental(nested)]
|
|
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),
|
|
#[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/")]
|
|
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, ExperimentalApi)]
|
|
#[serde(tag = "mode", rename_all = "camelCase")]
|
|
#[ts(tag = "mode")]
|
|
#[ts(export_to = "v2/")]
|
|
pub enum McpServerElicitationRequest {
|
|
/// A device-authenticated approval; accepted responses contain the proof in `content`.
|
|
#[experimental("mcpServer/elicitation/request.userVerification")]
|
|
#[serde(rename = "openai/userVerification", rename_all = "camelCase")]
|
|
#[ts(rename = "openai/userVerification", rename_all = "camelCase")]
|
|
UserVerification {
|
|
#[serde(rename = "_meta")]
|
|
#[ts(rename = "_meta")]
|
|
meta: Option<JsonValue>,
|
|
title: String,
|
|
description: String,
|
|
challenge: String,
|
|
},
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(rename_all = "camelCase")]
|
|
Form {
|
|
#[serde(rename = "_meta")]
|
|
#[ts(rename = "_meta")]
|
|
meta: Option<JsonValue>,
|
|
message: String,
|
|
requested_schema: McpElicitationSchema,
|
|
},
|
|
// TODO(victor): Deprecate once migrated to `openai/elicitation/create`.
|
|
#[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 = "openaiForm", rename_all = "camelCase")]
|
|
#[ts(rename = "openaiForm", rename_all = "camelCase")]
|
|
OpenAiElicitationForm {
|
|
#[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::UserVerification {
|
|
meta,
|
|
title,
|
|
description,
|
|
challenge,
|
|
} => Ok(Self::UserVerification {
|
|
meta,
|
|
title,
|
|
description,
|
|
challenge,
|
|
}),
|
|
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::OpenAiElicitationForm {
|
|
meta,
|
|
message,
|
|
requested_schema,
|
|
} => Ok(Self::OpenAiElicitationForm {
|
|
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::ElicitResult {
|
|
fn from(value: McpServerElicitationRequestResponse) -> Self {
|
|
let mut result = Self::new(value.action.into());
|
|
result.content = value.content;
|
|
result
|
|
}
|
|
}
|
|
|
|
impl From<rmcp::model::ElicitResult> for McpServerElicitationRequestResponse {
|
|
fn from(value: rmcp::model::ElicitResult) -> Self {
|
|
Self {
|
|
action: value.action.into(),
|
|
content: value.content,
|
|
meta: None,
|
|
}
|
|
}
|
|
}
|