feat: replace custom mcp-types crate with equivalents from rmcp

This commit is contained in:
Michael Bolin
2026-02-01 17:03:29 -08:00
parent 3dd9a37e0b
commit 136c14de8c
66 changed files with 1280 additions and 8652 deletions
-1
View File
@@ -19,7 +19,6 @@ codex-utils-image = { workspace = true }
icu_decimal = { workspace = true }
icu_locale_core = { workspace = true }
icu_provider = { workspace = true, features = ["sync"] }
mcp-types = { workspace = true }
mime_guess = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
+2 -1
View File
@@ -1,9 +1,9 @@
use std::collections::HashMap;
use std::path::PathBuf;
use crate::mcp::RequestId;
use crate::parse_command::ParsedCommand;
use crate::protocol::FileChange;
use mcp_types::RequestId;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
@@ -62,6 +62,7 @@ pub struct ExecApprovalRequestEvent {
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct ElicitationRequestEvent {
pub server_name: String,
#[ts(type = "string | number")]
pub id: RequestId,
pub message: String,
// TODO: MCP servers can request we fill out a schema for the elicitation. We don't support
+1
View File
@@ -6,6 +6,7 @@ pub mod config_types;
pub mod custom_prompts;
pub mod dynamic_tools;
pub mod items;
pub mod mcp;
pub mod message_history;
pub mod models;
pub mod num_format;
+115
View File
@@ -0,0 +1,115 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
/// Types used when representing Model Context Protocol (MCP) values inside the
/// Codex protocol.
///
/// We intentionally keep these types TS/JSON-schema friendly (via `ts-rs` and
/// `schemars`) so they can be embedded in Codex's own protocol structures.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum RequestId {
String(String),
#[ts(type = "number")]
Integer(i64),
}
impl std::fmt::Display for RequestId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RequestId::String(s) => f.write_str(s),
RequestId::Integer(i) => i.fmt(f),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct Tool {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
pub input_schema: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub output_schema: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub annotations: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub icons: Option<Vec<serde_json::Value>>,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub meta: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct Resource {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub annotations: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mime_type: Option<String>,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
#[ts(type = "number")]
pub size: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
pub uri: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub icons: Option<Vec<serde_json::Value>>,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub meta: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ResourceTemplate {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub annotations: Option<serde_json::Value>,
pub uri_template: String,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mime_type: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct CallToolResult {
pub content: Vec<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub structured_content: Option<serde_json::Value>,
#[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<serde_json::Value>,
}
+40 -37
View File
@@ -2,8 +2,6 @@ use std::collections::HashMap;
use std::path::Path;
use codex_utils_image::load_and_resize_to_fit;
use mcp_types::CallToolResult;
use mcp_types::ContentBlock;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
@@ -24,6 +22,8 @@ use codex_git::GhostCommit;
use codex_utils_image::error::ImageProcessingError;
use schemars::JsonSchema;
use crate::mcp::CallToolResult;
/// Controls whether a command should use the session sandbox or bypass it.
#[derive(
Debug, Clone, Copy, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS,
@@ -805,6 +805,7 @@ impl From<&CallToolResult> for FunctionCallOutputPayload {
content,
structured_content,
is_error,
meta: _,
} = call_tool_result;
let is_success = is_error != &Some(true);
@@ -841,7 +842,7 @@ impl From<&CallToolResult> for FunctionCallOutputPayload {
}
};
let content_items = convert_content_blocks_to_items(content);
let content_items = convert_mcp_content_to_items(content);
FunctionCallOutputPayload {
content: serialized_content,
@@ -851,32 +852,44 @@ impl From<&CallToolResult> for FunctionCallOutputPayload {
}
}
fn convert_content_blocks_to_items(
blocks: &[ContentBlock],
fn convert_mcp_content_to_items(
contents: &[serde_json::Value],
) -> Option<Vec<FunctionCallOutputContentItem>> {
let mut saw_image = false;
let mut items = Vec::with_capacity(blocks.len());
tracing::warn!("Blocks: {:?}", blocks);
for block in blocks {
match block {
ContentBlock::TextContent(text) => {
items.push(FunctionCallOutputContentItem::InputText {
text: text.text.clone(),
});
}
ContentBlock::ImageContent(image) => {
let mut items = Vec::with_capacity(contents.len());
for content in contents {
let item = match content.get("type").and_then(serde_json::Value::as_str) {
Some("text") => FunctionCallOutputContentItem::InputText {
text: content
.get("text")
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
.to_string(),
},
Some("image") => {
saw_image = true;
// Just in case the content doesn't include a data URL, add it.
let image_url = if image.data.starts_with("data:") {
image.data.clone()
let mime_type = content
.get("mimeType")
.or_else(|| content.get("mime_type"))
.and_then(serde_json::Value::as_str)
.unwrap_or("application/octet-stream");
let data = content
.get("data")
.and_then(serde_json::Value::as_str)
.unwrap_or_default();
let image_url = if data.starts_with("data:") {
data.to_string()
} else {
format!("data:{};base64,{}", image.mime_type, image.data)
format!("data:{mime_type};base64,{data}")
};
items.push(FunctionCallOutputContentItem::InputImage { image_url });
FunctionCallOutputContentItem::InputImage { image_url }
}
// TODO: render audio, resource, and embedded resource content to the model.
_ => return None,
}
_ => FunctionCallOutputContentItem::InputText {
text: serde_json::to_string(content).unwrap_or_else(|_| "<content>".to_string()),
},
};
items.push(item);
}
if saw_image { Some(items) } else { None }
@@ -908,8 +921,6 @@ mod tests {
use crate::protocol::AskForApproval;
use anyhow::Result;
use codex_execpolicy::Policy;
use mcp_types::ImageContent;
use mcp_types::TextContent;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
use tempfile::tempdir;
@@ -1040,20 +1051,12 @@ mod tests {
fn serializes_image_outputs_as_array() -> Result<()> {
let call_tool_result = CallToolResult {
content: vec![
ContentBlock::TextContent(TextContent {
annotations: None,
text: "caption".into(),
r#type: "text".into(),
}),
ContentBlock::ImageContent(ImageContent {
annotations: None,
data: "BASE64".into(),
mime_type: "image/png".into(),
r#type: "image".into(),
}),
serde_json::json!({"type":"text","text":"caption"}),
serde_json::json!({"type":"image","data":"BASE64","mimeType":"image/png"}),
],
is_error: None,
structured_content: None,
is_error: Some(false),
meta: None,
};
let payload = FunctionCallOutputPayload::from(&call_tool_result);
+5 -5
View File
@@ -23,6 +23,11 @@ use crate::dynamic_tools::DynamicToolCallRequest;
use crate::dynamic_tools::DynamicToolResponse;
use crate::dynamic_tools::DynamicToolSpec;
use crate::items::TurnItem;
use crate::mcp::CallToolResult;
use crate::mcp::RequestId;
use crate::mcp::Resource as McpResource;
use crate::mcp::ResourceTemplate as McpResourceTemplate;
use crate::mcp::Tool as McpTool;
use crate::message_history::HistoryEntry;
use crate::models::BaseInstructions;
use crate::models::ContentItem;
@@ -35,11 +40,6 @@ use crate::plan_tool::UpdatePlanArgs;
use crate::request_user_input::RequestUserInputResponse;
use crate::user_input::UserInput;
use codex_utils_absolute_path::AbsolutePathBuf;
use mcp_types::CallToolResult;
use mcp_types::RequestId;
use mcp_types::Resource as McpResource;
use mcp_types::ResourceTemplate as McpResourceTemplate;
use mcp_types::Tool as McpTool;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;