Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions codex-rs/app-server/src/codex_message_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4070,7 +4070,11 @@ impl CodexMessageProcessor {
}
};

let mcp_servers = match serde_json::to_value(config.mcp_servers.get()) {
let configured_servers = self
.thread_manager
.mcp_manager()
.configured_servers(&config);
let mcp_servers = match serde_json::to_value(configured_servers) {
Ok(value) => value,
Err(err) => {
let error = JSONRPCErrorError {
Expand Down Expand Up @@ -4131,7 +4135,11 @@ impl CodexMessageProcessor {
timeout_secs,
} = params;

let Some(server) = config.mcp_servers.get().get(&name) else {
let configured_servers = self
.thread_manager
.mcp_manager()
.configured_servers(&config);
let Some(server) = configured_servers.get(&name) else {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!("No MCP server named '{name}' found."),
Expand Down
28 changes: 17 additions & 11 deletions codex-rs/cli/src/mcp_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::HashMap;
use std::sync::Arc;

use anyhow::Context;
use anyhow::Result;
Expand All @@ -11,9 +12,11 @@ use codex_core::config::find_codex_home;
use codex_core::config::load_global_mcp_servers;
use codex_core::config::types::McpServerConfig;
use codex_core::config::types::McpServerTransportConfig;
use codex_core::mcp::McpManager;
use codex_core::mcp::auth::McpOAuthLoginSupport;
use codex_core::mcp::auth::compute_auth_statuses;
use codex_core::mcp::auth::oauth_login_support;
use codex_core::plugins::PluginsManager;
use codex_protocol::protocol::McpAuthStatus;
use codex_rmcp_client::delete_oauth_tokens;
use codex_rmcp_client::perform_oauth_login;
Expand Down Expand Up @@ -329,10 +332,12 @@ async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs)
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(config.codex_home.clone())));
let mcp_servers = mcp_manager.effective_servers(&config, None);

let LoginArgs { name, scopes } = login_args;

let Some(server) = config.mcp_servers.get().get(&name) else {
let Some(server) = mcp_servers.get(&name) else {
bail!("No MCP server named '{name}' found.");
};

Expand Down Expand Up @@ -374,12 +379,12 @@ async fn run_logout(config_overrides: &CliConfigOverrides, logout_args: LogoutAr
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(config.codex_home.clone())));
let mcp_servers = mcp_manager.effective_servers(&config, None);

let LogoutArgs { name } = logout_args;

let server = config
.mcp_servers
.get()
let server = mcp_servers
.get(&name)
.ok_or_else(|| anyhow!("No MCP server named '{name}' found in configuration."))?;

Expand All @@ -404,14 +409,13 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(config.codex_home.clone())));
let mcp_servers = mcp_manager.effective_servers(&config, None);

let mut entries: Vec<_> = config.mcp_servers.iter().collect();
let mut entries: Vec<_> = mcp_servers.iter().collect();
entries.sort_by(|(a, _), (b, _)| a.cmp(b));
let auth_statuses = compute_auth_statuses(
config.mcp_servers.iter(),
config.mcp_oauth_credentials_store_mode,
)
.await;
let auth_statuses =
compute_auth_statuses(mcp_servers.iter(), config.mcp_oauth_credentials_store_mode).await;

if list_args.json {
let json_entries: Vec<_> = entries
Expand Down Expand Up @@ -654,8 +658,10 @@ async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Re
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(config.codex_home.clone())));
let mcp_servers = mcp_manager.effective_servers(&config, None);

let Some(server) = config.mcp_servers.get().get(&get_args.name) else {
let Some(server) = mcp_servers.get(&get_args.name) else {
bail!("No MCP server named '{name}' found.", name = get_args.name);
};

Expand Down
30 changes: 30 additions & 0 deletions codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@
"personality": {
"type": "boolean"
},
"plugins": {
"type": "boolean"
},
"powershell_utf8": {
"type": "boolean"
},
Expand Down Expand Up @@ -1069,6 +1072,22 @@
],
"type": "string"
},
"PluginConfig": {
"additionalProperties": false,
"properties": {
"enabled": {
"default": true,
"type": "boolean"
},
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
}
},
"required": [
"path"
],
"type": "object"
},
"ProjectConfig": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -1693,6 +1712,9 @@
"personality": {
"type": "boolean"
},
"plugins": {
"type": "boolean"
},
"powershell_utf8": {
"type": "boolean"
},
Expand Down Expand Up @@ -1990,6 +2012,14 @@
"plan_mode_reasoning_effort": {
"$ref": "#/definitions/ReasoningEffort"
},
"plugins": {
"additionalProperties": {
"$ref": "#/definitions/PluginConfig"
},
"default": {},
"description": "User-level plugin config entries keyed by plugin name.",
"type": "object"
},
"profile": {
"description": "Profile to use from the `profiles` map.",
"type": "string"
Expand Down
51 changes: 44 additions & 7 deletions codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ use crate::file_watcher::FileWatcherEvent;
use crate::git_info::get_git_repo_root;
use crate::instructions::UserInstructions;
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
use crate::mcp::McpManager;
use crate::mcp::auth::compute_auth_statuses;
use crate::mcp::effective_mcp_servers;
use crate::mcp::maybe_prompt_and_install_mcp_dependencies;
use crate::mcp::with_codex_apps_mcp;
use crate::mcp_connection_manager::McpConnectionManager;
Expand All @@ -184,6 +184,7 @@ use crate::mentions::build_skill_name_counts;
use crate::mentions::collect_explicit_app_ids;
use crate::mentions::collect_tool_mentions_from_messages;
use crate::network_policy_decision::execpolicy_network_rule_amendment;
use crate::plugins::PluginsManager;
use crate::project_doc::get_user_instructions;
use crate::protocol::AgentMessageContentDeltaEvent;
use crate::protocol::AgentReasoningSectionBreakEvent;
Expand Down Expand Up @@ -317,6 +318,8 @@ impl Codex {
auth_manager: Arc<AuthManager>,
models_manager: Arc<ModelsManager>,
skills_manager: Arc<SkillsManager>,
plugins_manager: Arc<PluginsManager>,
mcp_manager: Arc<McpManager>,
file_watcher: Arc<FileWatcher>,
conversation_history: InitialHistory,
session_source: SessionSource,
Expand All @@ -328,6 +331,7 @@ impl Codex {
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
let (tx_event, rx_event) = async_channel::unbounded();

plugins_manager.plugins_for_config(&config);
let loaded_skills = skills_manager.skills_for_config(&config);

for err in &loaded_skills.errors {
Expand Down Expand Up @@ -465,6 +469,8 @@ impl Codex {
conversation_history,
session_source_clone,
skills_manager,
plugins_manager,
mcp_manager,
file_watcher,
agent_control,
)
Expand Down Expand Up @@ -1106,6 +1112,8 @@ impl Session {
initial_history: InitialHistory,
session_source: SessionSource,
skills_manager: Arc<SkillsManager>,
plugins_manager: Arc<PluginsManager>,
mcp_manager: Arc<McpManager>,
file_watcher: Arc<FileWatcher>,
agent_control: AgentControl,
) -> anyhow::Result<Arc<Self>> {
Expand Down Expand Up @@ -1197,9 +1205,10 @@ impl Session {
};
let auth_manager_clone = Arc::clone(&auth_manager);
let config_for_mcp = Arc::clone(&config);
let mcp_manager_for_mcp = Arc::clone(&mcp_manager);
let auth_and_mcp_fut = async move {
let auth = auth_manager_clone.auth().await;
let mcp_servers = effective_mcp_servers(&config_for_mcp, auth.as_ref());
let mcp_servers = mcp_manager_for_mcp.effective_servers(&config_for_mcp, auth.as_ref());
let auth_statuses = compute_auth_statuses(
mcp_servers.iter(),
config_for_mcp.mcp_oauth_credentials_store_mode,
Expand Down Expand Up @@ -1449,6 +1458,8 @@ impl Session {
tool_approvals: Mutex::new(ApprovalStore::default()),
execve_session_approvals: RwLock::new(HashMap::new()),
skills_manager,
plugins_manager,
mcp_manager,
file_watcher,
agent_control,
network_proxy,
Expand Down Expand Up @@ -2365,6 +2376,8 @@ impl Session {
.config_layer_stack
.with_user_config(&config_toml_path, user_config);
state.session_configuration.original_config_do_not_use = Arc::new(config);
self.services.skills_manager.clear_cache();
self.services.plugins_manager.clear_cache();
}

pub(crate) async fn new_default_turn_with_sub_id(&self, sub_id: String) -> Arc<TurnContext> {
Expand Down Expand Up @@ -3864,7 +3877,6 @@ mod handlers {

use crate::mcp::auth::compute_auth_statuses;
use crate::mcp::collect_mcp_snapshot_from_manager;
use crate::mcp::effective_mcp_servers;
use crate::review_prompts::resolve_review_request;
use crate::rollout::session_index;
use crate::tasks::CompactTask;
Expand Down Expand Up @@ -4194,7 +4206,10 @@ mod handlers {
pub async fn list_mcp_tools(sess: &Session, config: &Arc<Config>, sub_id: String) {
let mcp_connection_manager = sess.services.mcp_connection_manager.read().await;
let auth = sess.services.auth_manager.auth().await;
let mcp_servers = effective_mcp_servers(config, auth.as_ref());
let mcp_servers = sess
.services
.mcp_manager
.effective_servers(config, auth.as_ref());
let snapshot = collect_mcp_snapshot_from_manager(
&mcp_connection_manager,
compute_auth_statuses(mcp_servers.iter(), config.mcp_oauth_credentials_store_mode)
Expand Down Expand Up @@ -8384,6 +8399,12 @@ mod tests {

let (tx_event, _rx_event) = async_channel::unbounded();
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone()));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_manager = Arc::new(SkillsManager::new(
config.codex_home.clone(),
Arc::clone(&plugins_manager),
));
let result = Session::new(
session_configuration,
Arc::clone(&config),
Expand All @@ -8394,7 +8415,9 @@ mod tests {
agent_status_tx,
InitialHistory::New,
SessionSource::Exec,
Arc::new(SkillsManager::new(config.codex_home.clone())),
skills_manager,
plugins_manager,
mcp_manager,
Arc::new(FileWatcher::noop()),
AgentControl::default(),
)
Expand Down Expand Up @@ -8476,7 +8499,12 @@ mod tests {
);

let state = SessionState::new(session_configuration.clone());
let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone()));
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone()));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_manager = Arc::new(SkillsManager::new(
config.codex_home.clone(),
Arc::clone(&plugins_manager),
));
let network_approval = Arc::new(NetworkApprovalService::default());

let file_watcher = Arc::new(FileWatcher::noop());
Expand Down Expand Up @@ -8510,6 +8538,8 @@ mod tests {
tool_approvals: Mutex::new(ApprovalStore::default()),
execve_session_approvals: RwLock::new(HashMap::new()),
skills_manager,
plugins_manager,
mcp_manager,
file_watcher,
agent_control,
network_proxy: None,
Expand Down Expand Up @@ -8636,7 +8666,12 @@ mod tests {
);

let state = SessionState::new(session_configuration.clone());
let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone()));
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone()));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_manager = Arc::new(SkillsManager::new(
config.codex_home.clone(),
Arc::clone(&plugins_manager),
));
let network_approval = Arc::new(NetworkApprovalService::default());

let file_watcher = Arc::new(FileWatcher::noop());
Expand Down Expand Up @@ -8670,6 +8705,8 @@ mod tests {
tool_approvals: Mutex::new(ApprovalStore::default()),
execve_session_approvals: RwLock::new(HashMap::new()),
skills_manager,
plugins_manager,
mcp_manager,
file_watcher,
agent_control,
network_proxy: None,
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/codex_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub(crate) async fn run_codex_thread_interactive(
auth_manager,
models_manager,
Arc::clone(&parent_session.services.skills_manager),
Arc::clone(&parent_session.services.plugins_manager),
Arc::clone(&parent_session.services.mcp_manager),
Arc::clone(&parent_session.services.file_watcher),
initial_history.unwrap_or(InitialHistory::New),
SessionSource::SubAgent(SubAgentSource::Review),
Expand Down
5 changes: 5 additions & 0 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::config::types::Notifications;
use crate::config::types::OtelConfig;
use crate::config::types::OtelConfigToml;
use crate::config::types::OtelExporterKind;
use crate::config::types::PluginConfig;
use crate::config::types::SandboxWorkspaceWrite;
use crate::config::types::ShellEnvironmentPolicy;
use crate::config::types::ShellEnvironmentPolicyToml;
Expand Down Expand Up @@ -1207,6 +1208,10 @@ pub struct ConfigToml {
/// User-level skill config entries keyed by SKILL.md path.
pub skills: Option<SkillsConfig>,

/// User-level plugin config entries keyed by plugin name.
#[serde(default)]
pub plugins: HashMap<String, PluginConfig>,

/// Centralized feature flags (new). Prefer this over individual toggles.
#[serde(default)]
// Injects known feature keys into the schema and forbids unknown keys.
Expand Down
8 changes: 8 additions & 0 deletions codex-rs/core/src/config/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,14 @@ pub struct SkillConfig {
pub enabled: bool,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct PluginConfig {
pub path: AbsolutePathBuf,
#[serde(default = "default_enabled")]
pub enabled: bool,
}

#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct SkillsConfig {
Expand Down
Loading
Loading