rm schema-only structs; use RawMcpServerConfig instead

This commit is contained in:
Sayan Sisodiya
2026-01-08 20:03:12 -08:00
parent eede02a5ef
commit fcb4398a19
5 changed files with 140 additions and 251 deletions

View File

@@ -690,7 +690,6 @@ pub fn set_default_oss_provider(codex_home: &Path, provider: &str) -> std::io::R
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct ConfigToml {
pub test: Option<String>,
/// Optional override of model selection.
pub model: Option<String>,
/// Review model override used by the `/review` feature.
@@ -748,6 +747,7 @@ pub struct ConfigToml {
/// Definition for MCP servers that Codex can reach out to for tool calls.
#[serde(default)]
// Uses the raw MCP input shape (custom deserialization) rather than `McpServerConfig`.
#[schemars(schema_with = "crate::config::schema::mcp_servers_schema")]
pub mcp_servers: HashMap<String, McpServerConfig>,
@@ -816,6 +816,7 @@ pub struct ConfigToml {
/// Centralized feature flags (new). Prefer this over individual toggles.
#[serde(default)]
// Injects known feature keys into the schema and forbids unknown keys.
#[schemars(schema_with = "crate::config::schema::features_schema")]
pub features: Option<FeaturesToml>,

View File

@@ -34,6 +34,7 @@ pub struct ConfigProfile {
pub analytics: Option<crate::config::types::AnalyticsConfigToml>,
/// Optional feature toggles scoped to this profile.
#[serde(default)]
// Injects known feature keys into the schema and forbids unknown keys.
#[schemars(schema_with = "crate::config::schema::features_schema")]
pub features: Option<crate::features::FeaturesToml>,
pub oss_provider: Option<String>,

View File

@@ -1,7 +1,7 @@
#[cfg(test)]
use crate::config::ConfigToml;
use crate::config::types::RawMcpServerConfig;
use crate::features::FEATURES;
use schemars::JsonSchema;
use schemars::r#gen::SchemaGenerator;
#[cfg(test)]
use schemars::r#gen::SchemaSettings;
@@ -11,13 +11,10 @@ use schemars::schema::ObjectValidation;
use schemars::schema::RootSchema;
use schemars::schema::Schema;
use schemars::schema::SchemaObject;
use schemars::schema::SubschemaValidation;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
#[cfg(test)]
use std::path::Path;
/// Build the config schema used by the fixture test.
#[cfg(test)]
pub(crate) fn config_schema() -> RootSchema {
SchemaSettings::draft07()
@@ -28,6 +25,7 @@ pub(crate) fn config_schema() -> RootSchema {
.into_root_schema_for::<ConfigToml>()
}
/// Write the config schema fixture to disk.
#[cfg(test)]
pub(crate) fn write_config_schema(out_path: &Path) -> anyhow::Result<()> {
let schema = config_schema();
@@ -36,6 +34,7 @@ pub(crate) fn write_config_schema(out_path: &Path) -> anyhow::Result<()> {
Ok(())
}
/// Schema for the `[features]` map with known keys only.
pub(crate) fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
let mut object = SchemaObject {
instance_type: Some(InstanceType::Object.into()),
@@ -54,6 +53,7 @@ pub(crate) fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
Schema::Object(object)
}
/// Schema for the `[mcp_servers]` map using the raw input shape.
pub(crate) fn mcp_servers_schema(schema_gen: &mut SchemaGenerator) -> Schema {
let mut object = SchemaObject {
instance_type: Some(InstanceType::Object.into()),
@@ -61,7 +61,7 @@ pub(crate) fn mcp_servers_schema(schema_gen: &mut SchemaGenerator) -> Schema {
};
let validation = ObjectValidation {
additional_properties: Some(Box::new(mcp_server_schema(schema_gen))),
additional_properties: Some(Box::new(schema_gen.subschema_for::<RawMcpServerConfig>())),
..Default::default()
};
object.object = Some(Box::new(validation));
@@ -69,70 +69,6 @@ pub(crate) fn mcp_servers_schema(schema_gen: &mut SchemaGenerator) -> Schema {
Schema::Object(object)
}
fn mcp_server_schema(schema_gen: &mut SchemaGenerator) -> Schema {
let server = SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
one_of: Some(vec![
schema_gen.subschema_for::<McpServerStdioSchema>(),
schema_gen.subschema_for::<McpServerStreamableHttpSchema>(),
]),
..Default::default()
})),
..Default::default()
};
Schema::Object(server)
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
struct McpServerStdioSchema {
command: String,
#[serde(default)]
args: Option<Vec<String>>,
#[serde(default)]
env: Option<HashMap<String, String>>,
#[serde(default)]
env_vars: Option<Vec<String>>,
#[serde(default)]
cwd: Option<String>,
#[serde(default)]
enabled: Option<bool>,
#[serde(default)]
startup_timeout_sec: Option<f64>,
#[serde(default)]
startup_timeout_ms: Option<u64>,
#[serde(default)]
tool_timeout_sec: Option<f64>,
#[serde(default)]
enabled_tools: Option<Vec<String>>,
#[serde(default)]
disabled_tools: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
struct McpServerStreamableHttpSchema {
url: String,
#[serde(default)]
bearer_token_env_var: Option<String>,
#[serde(default)]
http_headers: Option<HashMap<String, String>>,
#[serde(default)]
env_http_headers: Option<HashMap<String, String>>,
#[serde(default)]
enabled: Option<bool>,
#[serde(default)]
startup_timeout_sec: Option<f64>,
#[serde(default)]
startup_timeout_ms: Option<u64>,
#[serde(default)]
tool_timeout_sec: Option<f64>,
#[serde(default)]
enabled_tools: Option<Vec<String>>,
#[serde(default)]
disabled_tools: Option<Vec<String>>,
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -49,47 +49,51 @@ pub struct McpServerConfig {
pub disabled_tools: Option<Vec<String>>,
}
// Raw MCP config shape used for deserialization and JSON Schema generation.
// Keep this in sync with the validation logic in `McpServerConfig`.
#[derive(Deserialize, Clone, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub(crate) struct RawMcpServerConfig {
// stdio
pub command: Option<String>,
#[serde(default)]
pub args: Option<Vec<String>>,
#[serde(default)]
pub env: Option<HashMap<String, String>>,
#[serde(default)]
pub env_vars: Option<Vec<String>>,
#[serde(default)]
pub cwd: Option<PathBuf>,
pub http_headers: Option<HashMap<String, String>>,
#[serde(default)]
pub env_http_headers: Option<HashMap<String, String>>,
// streamable_http
pub url: Option<String>,
pub bearer_token: Option<String>,
pub bearer_token_env_var: Option<String>,
// shared
#[serde(default)]
pub startup_timeout_sec: Option<f64>,
#[serde(default)]
pub startup_timeout_ms: Option<u64>,
#[serde(default, with = "option_duration_secs")]
#[schemars(with = "Option<f64>")]
pub tool_timeout_sec: Option<Duration>,
#[serde(default)]
pub enabled: Option<bool>,
#[serde(default)]
pub enabled_tools: Option<Vec<String>>,
#[serde(default)]
pub disabled_tools: Option<Vec<String>>,
}
impl<'de> Deserialize<'de> for McpServerConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize, Clone)]
struct RawMcpServerConfig {
// stdio
command: Option<String>,
#[serde(default)]
args: Option<Vec<String>>,
#[serde(default)]
env: Option<HashMap<String, String>>,
#[serde(default)]
env_vars: Option<Vec<String>>,
#[serde(default)]
cwd: Option<PathBuf>,
http_headers: Option<HashMap<String, String>>,
#[serde(default)]
env_http_headers: Option<HashMap<String, String>>,
// streamable_http
url: Option<String>,
bearer_token: Option<String>,
bearer_token_env_var: Option<String>,
// shared
#[serde(default)]
startup_timeout_sec: Option<f64>,
#[serde(default)]
startup_timeout_ms: Option<u64>,
#[serde(default, with = "option_duration_secs")]
tool_timeout_sec: Option<Duration>,
#[serde(default)]
enabled: Option<bool>,
#[serde(default)]
enabled_tools: Option<Vec<String>>,
#[serde(default)]
disabled_tools: Option<Vec<String>>,
}
let mut raw = RawMcpServerConfig::deserialize(deserializer)?;
let startup_timeout_sec = match (raw.startup_timeout_sec, raw.startup_timeout_ms) {

View File

@@ -182,14 +182,7 @@
"default": {},
"type": "object",
"additionalProperties": {
"oneOf": [
{
"$ref": "#/definitions/McpServerStdioSchema"
},
{
"$ref": "#/definitions/McpServerStreamableHttpSchema"
}
]
"$ref": "#/definitions/RawMcpServerConfig"
}
},
"model": {
@@ -626,142 +619,6 @@
}
]
},
"McpServerStdioSchema": {
"description": "Schema-only representations of MCP server configs.\n\nKeep these in sync with the custom MCP config parsing in `crate::config::types::McpServerConfig`.",
"type": "object",
"required": [
"command"
],
"properties": {
"args": {
"default": null,
"type": "array",
"items": {
"type": "string"
}
},
"command": {
"type": "string"
},
"cwd": {
"default": null,
"type": "string"
},
"disabled_tools": {
"default": null,
"type": "array",
"items": {
"type": "string"
}
},
"enabled": {
"default": null,
"type": "boolean"
},
"enabled_tools": {
"default": null,
"type": "array",
"items": {
"type": "string"
}
},
"env": {
"default": null,
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"env_vars": {
"default": null,
"type": "array",
"items": {
"type": "string"
}
},
"startup_timeout_ms": {
"default": null,
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"startup_timeout_sec": {
"default": null,
"type": "number",
"format": "double"
},
"tool_timeout_sec": {
"default": null,
"type": "number",
"format": "double"
}
},
"additionalProperties": false
},
"McpServerStreamableHttpSchema": {
"type": "object",
"required": [
"url"
],
"properties": {
"bearer_token_env_var": {
"default": null,
"type": "string"
},
"disabled_tools": {
"default": null,
"type": "array",
"items": {
"type": "string"
}
},
"enabled": {
"default": null,
"type": "boolean"
},
"enabled_tools": {
"default": null,
"type": "array",
"items": {
"type": "string"
}
},
"env_http_headers": {
"default": null,
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"http_headers": {
"default": null,
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"startup_timeout_ms": {
"default": null,
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"startup_timeout_sec": {
"default": null,
"type": "number",
"format": "double"
},
"tool_timeout_sec": {
"default": null,
"type": "number",
"format": "double"
},
"url": {
"type": "string"
}
},
"additionalProperties": false
},
"ModelProviderInfo": {
"description": "Serializable representation of a provider definition.",
"type": "object",
@@ -1080,6 +937,96 @@
},
"additionalProperties": false
},
"RawMcpServerConfig": {
"type": "object",
"properties": {
"args": {
"default": null,
"type": "array",
"items": {
"type": "string"
}
},
"bearer_token": {
"type": "string"
},
"bearer_token_env_var": {
"type": "string"
},
"command": {
"type": "string"
},
"cwd": {
"default": null,
"type": "string"
},
"disabled_tools": {
"default": null,
"type": "array",
"items": {
"type": "string"
}
},
"enabled": {
"default": null,
"type": "boolean"
},
"enabled_tools": {
"default": null,
"type": "array",
"items": {
"type": "string"
}
},
"env": {
"default": null,
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"env_http_headers": {
"default": null,
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"env_vars": {
"default": null,
"type": "array",
"items": {
"type": "string"
}
},
"http_headers": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"startup_timeout_ms": {
"default": null,
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"startup_timeout_sec": {
"default": null,
"type": "number",
"format": "double"
},
"tool_timeout_sec": {
"default": null,
"type": "number",
"format": "double"
},
"url": {
"type": "string"
}
},
"additionalProperties": false
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"type": "string",