use std::{collections::BTreeMap, ffi::OsString, path::PathBuf, time::Duration};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tokio::sync::{mpsc, oneshot};
pub const METHOD_INITIALIZE: &str = "initialize";
pub const METHOD_SHUTDOWN: &str = "shutdown";
pub const METHOD_EXIT: &str = "exit";
pub const METHOD_CANCEL: &str = "$/cancelRequest";
pub const METHOD_CODEX: &str = "tools/call";
pub const METHOD_CODEX_REPLY: &str = "tools/call";
pub const METHOD_CODEX_EVENT: &str = "codex/event";
pub const METHOD_CODEX_APPROVAL: &str = "codex/approval";
pub const METHOD_THREAD_START: &str = "thread/start";
pub const METHOD_THREAD_RESUME: &str = "thread/resume";
pub const METHOD_THREAD_LIST: &str = "thread/list";
pub const METHOD_THREAD_FORK: &str = "thread/fork";
pub const METHOD_TURN_START: &str = "turn/start";
pub const METHOD_TURN_INTERRUPT: &str = "turn/interrupt";
pub type RequestId = u64;
pub type EventStream<T> = mpsc::UnboundedReceiver<T>;
#[derive(Clone, Debug)]
pub struct StdioServerConfig {
pub binary: PathBuf,
pub code_home: Option<PathBuf>,
pub current_dir: Option<PathBuf>,
pub env: Vec<(OsString, OsString)>,
pub app_server_analytics_default_enabled: bool,
pub mirror_stdio: bool,
pub startup_timeout: Duration,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ClientInfo {
pub name: String,
pub version: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InitializeParams {
#[serde(rename = "clientInfo")]
pub client: ClientInfo,
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
#[serde(default)]
pub capabilities: Value,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct CodexCallParams {
pub prompt: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cwd: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sandbox: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub approval_policy: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub config: BTreeMap<String, Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CodexReplyParams {
#[serde(rename = "conversationId")]
pub conversation_id: String,
pub prompt: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum ApprovalKind {
Exec,
Apply,
Unknown(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ApprovalRequest {
pub approval_id: String,
pub kind: ApprovalKind,
pub payload: Value,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ApprovalDecision {
Approve {
approval_id: String,
},
Reject {
approval_id: String,
reason: Option<String>,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CodexEvent {
TaskComplete {
conversation_id: String,
result: Value,
},
ApprovalRequired(ApprovalRequest),
Cancelled {
conversation_id: Option<String>,
reason: Option<String>,
},
Error {
message: String,
data: Option<Value>,
},
Raw {
method: String,
params: Value,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CodexCallResult {
#[serde(default, rename = "conversationId", alias = "conversation_id")]
pub conversation_id: Option<String>,
#[serde(default, rename = "content", alias = "output")]
pub output: Value,
}
pub struct CodexCallHandle {
pub request_id: RequestId,
pub events: EventStream<CodexEvent>,
pub response: oneshot::Receiver<Result<CodexCallResult, super::McpError>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ThreadStartParams {
pub thread_id: Option<String>,
#[serde(default)]
pub metadata: Value,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThreadResumeParams {
pub thread_id: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ThreadListSortKey {
CreatedAt,
UpdatedAt,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThreadListParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub cwd: Option<PathBuf>,
pub cursor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sort_key: Option<ThreadListSortKey>,
#[serde(skip_serializing_if = "Option::is_none")]
pub archived: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_providers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_kinds: Option<Vec<String>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThreadListResponse {
pub data: Vec<ThreadSummary>,
pub next_cursor: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThreadSummary {
pub id: String,
pub created_at: i64,
pub updated_at: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cwd: Option<PathBuf>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty", flatten)]
pub extra: BTreeMap<String, Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThreadForkParams {
pub thread_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub cwd: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub approval_policy: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sandbox: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub persist_extended_history: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ThreadForkResponse {
pub thread: ForkedThread,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ForkedThread {
pub id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TurnStartParams {
pub thread_id: String,
#[serde(rename = "input", alias = "prompt")]
pub input: Vec<TurnInput>,
pub model: Option<String>,
#[serde(default)]
pub config: BTreeMap<String, Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TurnInput {
#[serde(rename = "type")]
pub kind: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TurnStartParamsV2 {
pub thread_id: String,
pub input: Vec<UserInputV2>,
#[serde(skip_serializing_if = "Option::is_none")]
pub approval_policy: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cwd: Option<PathBuf>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum UserInputV2 {
Text {
text: String,
#[serde(default)]
text_elements: Vec<Value>,
},
}
impl UserInputV2 {
pub fn text(text: impl Into<String>) -> Self {
Self::Text {
text: text.into(),
text_elements: Vec::new(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TurnInterruptParams {
pub thread_id: Option<String>,
pub turn_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AppNotification {
TaskComplete {
thread_id: String,
turn_id: Option<String>,
result: Value,
},
Item {
thread_id: String,
turn_id: Option<String>,
item: Value,
},
Error {
message: String,
data: Option<Value>,
},
Raw {
method: String,
params: Value,
},
}
pub struct AppCallHandle {
pub request_id: RequestId,
pub events: EventStream<AppNotification>,
pub response: oneshot::Receiver<Result<Value, super::McpError>>,
}