mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
## What changed - Add an opt-in `mcp_2026_07_28` protocol mode while preserving the legacy lifecycle by default. - Negotiate the new protocol over streamable HTTP with `server/discover`, including bounded responses, redirect protection, and fallback only when a response establishes that the endpoint is legacy-only. - Require stdio servers to opt in with `CODEX_MCP_PROTOCOL_VERSION=2026-07-28`, and add a bounded local stdio transport for the modern lifecycle. - Consume paginated tool, resource, and resource-template catalogs in modern mode, reject repeated cursors, and retain discovered server identity. - Reconnect reusable MCP clients when their selected protocol mode changes. ## Testing - Cover HTTP JSON and SSE discovery, legacy fallback and rejection cases, redirects, retries, response limits, and pagination. - Cover local and executor stdio discovery, protocol markers, message limits, and legacy compatibility. GitOrigin-RevId: f6a78816e127d2a482292d63b91c8384f1595903
129 lines
4.1 KiB
Rust
129 lines
4.1 KiB
Rust
use std::ffi::OsStr;
|
|
use std::io;
|
|
|
|
use rmcp::model::ProtocolVersion;
|
|
use rmcp::service::ClientLifecycleMode;
|
|
|
|
/// MCP compatibility policy selected once when a Codex session is created.
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub enum McpProtocolMode {
|
|
/// Preserve the existing MCP initialization and OAuth behavior.
|
|
#[default]
|
|
Legacy,
|
|
/// Allow the MCP 2026-07-28 discovery and request lifecycle.
|
|
V20260728,
|
|
}
|
|
|
|
impl McpProtocolMode {
|
|
/// Returns the newest protocol version this compatibility policy can use.
|
|
pub fn preferred_protocol_version(self) -> ProtocolVersion {
|
|
match self {
|
|
Self::Legacy => ProtocolVersion::V_2025_06_18,
|
|
Self::V20260728 => ProtocolVersion::V_2026_07_28,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn client_lifecycle(self) -> ClientLifecycleMode {
|
|
match self {
|
|
Self::Legacy => ClientLifecycleMode::Initialize,
|
|
Self::V20260728 => ClientLifecycleMode::Auto {
|
|
preferred_versions: vec![ProtocolVersion::V_2026_07_28],
|
|
legacy_version: Some(ProtocolVersion::V_2025_06_18),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(crate) fn stdio_mode(self, requested_version: Option<&OsStr>) -> io::Result<Self> {
|
|
match (self, requested_version) {
|
|
(Self::Legacy, _) => Ok(Self::Legacy),
|
|
(_, None) => Ok(Self::Legacy),
|
|
(Self::V20260728, Some(version)) if version == OsStr::new("2026-07-28") => {
|
|
Ok(Self::V20260728)
|
|
}
|
|
(_, Some(version)) => Err(io::Error::new(
|
|
io::ErrorKind::InvalidInput,
|
|
format!(
|
|
"unsupported CODEX_MCP_PROTOCOL_VERSION `{}` for stdio MCP server; expected `2026-07-28`",
|
|
version.to_string_lossy()
|
|
),
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn protocol_modes_select_compatible_sdk_lifecycles() {
|
|
assert_eq!(
|
|
McpProtocolMode::Legacy.preferred_protocol_version(),
|
|
ProtocolVersion::V_2025_06_18
|
|
);
|
|
assert_eq!(
|
|
McpProtocolMode::Legacy.client_lifecycle(),
|
|
ClientLifecycleMode::Initialize
|
|
);
|
|
assert_eq!(
|
|
McpProtocolMode::V20260728.preferred_protocol_version(),
|
|
ProtocolVersion::V_2026_07_28
|
|
);
|
|
assert_eq!(
|
|
McpProtocolMode::V20260728.client_lifecycle(),
|
|
ClientLifecycleMode::Auto {
|
|
preferred_versions: vec![ProtocolVersion::V_2026_07_28],
|
|
legacy_version: Some(ProtocolVersion::V_2025_06_18),
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn stdio_requires_both_the_modern_feature_and_a_server_opt_in() {
|
|
let modern_version = Some(OsStr::new("2026-07-28"));
|
|
|
|
assert_eq!(
|
|
McpProtocolMode::Legacy
|
|
.stdio_mode(/*requested_version*/ None)
|
|
.unwrap(),
|
|
McpProtocolMode::Legacy
|
|
);
|
|
assert_eq!(
|
|
McpProtocolMode::Legacy.stdio_mode(modern_version).unwrap(),
|
|
McpProtocolMode::Legacy
|
|
);
|
|
assert_eq!(
|
|
McpProtocolMode::V20260728
|
|
.stdio_mode(/*requested_version*/ None)
|
|
.unwrap(),
|
|
McpProtocolMode::Legacy
|
|
);
|
|
assert_eq!(
|
|
McpProtocolMode::V20260728
|
|
.stdio_mode(modern_version)
|
|
.unwrap(),
|
|
McpProtocolMode::V20260728
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn stdio_rejects_unknown_protocol_markers() {
|
|
let error = McpProtocolMode::V20260728
|
|
.stdio_mode(Some(OsStr::new("1999-01-01")))
|
|
.expect_err("an unknown protocol marker must fail");
|
|
|
|
assert_eq!(error.kind(), io::ErrorKind::InvalidInput);
|
|
assert!(error.to_string().contains("1999-01-01"));
|
|
}
|
|
|
|
#[test]
|
|
fn legacy_stdio_does_not_interpret_existing_protocol_markers() {
|
|
assert_eq!(
|
|
McpProtocolMode::Legacy
|
|
.stdio_mode(Some(OsStr::new("1999-01-01")))
|
|
.unwrap(),
|
|
McpProtocolMode::Legacy
|
|
);
|
|
}
|
|
}
|