Skip to content

Support detect and import MCP, Subagents, hooks, commands from external#19949

Merged
alexsong-oai merged 15 commits intomainfrom
alexs/extend-migrate
Apr 29, 2026
Merged

Support detect and import MCP, Subagents, hooks, commands from external#19949
alexsong-oai merged 15 commits intomainfrom
alexs/extend-migrate

Conversation

@alexsong-oai
Copy link
Copy Markdown
Collaborator

@alexsong-oai alexsong-oai commented Apr 28, 2026

Why

This PR expands the migration path so Codex can detect and import MCP server config, hooks, commands, and subagents configs in a Codex-native shape.

What changed

  • Added a codex-external-agent-migration crate that owns conversion logic for external-agent MCP servers, hooks, commands, and subagents.
  • Extended the app-server external-agent config detection/import API with migration item types for MCP server config, hooks, commands, and subagents.

Migration strategy

The migration is intentionally conservative: Codex only imports external-agent config that can be represented safely in Codex today. Unsupported or ambiguous config is skipped instead of being partially translated into behavior that may not match the source system.

  • MCP servers: import supported stdio and HTTP MCP server definitions into mcp_servers. Disabled servers and servers filtered out by source enabledMcpjsonServers / disabledMcpjsonServers are skipped. Project-scoped MCP entries from .claude.json are included when they match the repo path.

  • Hooks: import only supported command hooks into .codex/hooks.json. Unsupported hook features such as conditional groups, async handlers, prompt/http hooks, or unknown fields are skipped. Referenced hook scripts are copied into .codex/hooks/, preserving any existing target scripts.

  • Commands: import supported external commands as Codex skills under .agents/skills/source-command-*. Commands that rely on source runtime expansion such as $ARGUMENTS, $1, @file references, shell interpolation, or colliding generated names are skipped.

  • Subagents: import valid subagent Markdown files into .codex/agents/*.toml when they have the minimum Codex agent fields. Source model names are not migrated, so imported agents keep the user’s Codex default model; compatible reasoning effort and sandbox mode are migrated when present.

  • Skills and project guidance: copy missing skill directories into .agents/skills and migrate CLAUDE.md guidance into AGENTS.md, rewriting source-agent terminology to Codex terminology where appropriate.

  • Detection details: detected migration items include lightweight details for UI preview, such as MCP server names, hook event names, generated command skill names, and subagent names. Import still recomputes from disk instead of trusting details as the source of truth.

  • Adds focused coverage for the new migration behavior and app-server import flow.

Verification

  • cargo test -p codex-external-agent-migration
  • cargo test -p codex-hooks
  • cargo test -p codex-app-server external_agent_config
  • just bazel-lock-check

@alexsong-oai
Copy link
Copy Markdown
Collaborator Author

@codex review

@alexsong-oai
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0fb610fff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/external-agent-migration/src/lib.rs
Comment on lines +439 to +441
fn append_convertible_hook_groups(settings: &JsonValue, migration: &mut HookMigration) {
let Some(hooks_config) = settings.get("hooks").and_then(JsonValue::as_object) else {
return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Honor disableAllHooks when importing hooks

Hook migration ignores the source disableAllHooks flag and still imports active hook handlers from hooks. That changes behavior: users who explicitly disabled hooks in external config will get hooks enabled after migration. Detection/import should skip hook migration (or preserve disabled state) when this flag is true.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

Comment on lines +773 to +777
fn command_skill_name(source_commands: &Path, source_file: &Path) -> String {
slugify_name(&format!(
"{COMMAND_SKILL_PREFIX}-{}",
command_source_name(source_commands, source_file)
))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Disambiguate colliding command skill slugs

command_skill_name normalizes paths through slugify_name, so different commands can collapse to the same skill directory (e.g. foo_bar.md and foo-bar.md). import_commands treats target_dir.exists() as already migrated and silently skips later collisions, losing commands. Use a collision-safe naming scheme or fail with an explicit error.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commands with name collisions will be ignored.

Comment thread codex-rs/app-server/src/config/external_agent_config_migration.rs Outdated
@alexsong-oai
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

let source_settings = repo_root.map_or_else(
|| self.external_agent_home.join("settings.json"),
|repo_root| repo_root.join(EXTERNAL_AGENT_DIR).join("settings.json"),
);
let settings = read_external_settings(&source_settings)?;

P2 Badge Read MCP allow/deny lists from settings.local.json

MCP migration loads only .claude/settings.json for enabledMcpjsonServers/disabledMcpjsonServers. Those are hierarchical settings, and .claude/settings.local.json can override them. Ignoring local overrides produces wrong enabled=false decisions and incorrect detect/import results.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/app-server/src/config/external_agent_config_migration.rs Outdated
Comment thread codex-rs/app-server/src/config/external_agent_config_migration.rs Outdated
Comment thread codex-rs/app-server/src/config/external_agent_config_migration.rs Outdated
Comment thread codex-rs/app-server/src/config/external_agent_config_migration.rs Outdated
Comment thread codex-rs/app-server/src/config/external_agent_config_migration.rs Outdated
# Conflicts:
#	codex-rs/app-server-protocol/schema/json/ClientRequest.json
#	codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json
#	codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json
#	codex-rs/app-server-protocol/schema/json/v2/ExternalAgentConfigDetectResponse.json
#	codex-rs/app-server-protocol/schema/json/v2/ExternalAgentConfigImportParams.json
#	codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigMigrationItemType.ts
#	codex-rs/app-server-protocol/src/protocol/v2.rs
#	codex-rs/app-server/src/config/external_agent_config.rs
#	codex-rs/app-server/src/external_agent_config_api.rs
@alexsong-oai
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c0508e55d0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/external-agent-migration/src/lib.rs
Comment thread codex-rs/app-server/src/config/external_agent_config.rs
Comment thread codex-rs/external-agent-migration/src/lib.rs
@alexsong-oai
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

let has_plugin_imports = params.migration_items.iter().any(|item| {
matches!(
item.item_type,
ExternalAgentConfigMigrationItemType::Plugins
)
});
let pending_session_imports = self
.external_agent_config_api
.prepare_pending_session_imports(&params)?;
let pending_plugin_imports = self.external_agent_config_api.import(params).await?;
if has_plugin_imports {
self.handle_config_mutation().await;

P1 Badge Refresh config after non-plugin migration imports

handle_external_agent_config_import() only treats Plugins as config-mutating. New import types (notably McpServerConfig and Hooks) update .codex/config.toml / hooks.json, but handle_config_mutation() is skipped, so running sessions can keep stale configuration until restart.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/app-server/src/config/external_agent_config.rs
Comment thread codex-rs/external-agent-migration/src/lib.rs Outdated
Comment thread codex-rs/external-agent-migration/src/lib.rs
@alexsong-oai
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5cab4cfa4a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/app-server/src/message_processor.rs
Comment thread codex-rs/external-agent-migration/src/lib.rs
Comment thread codex-rs/external-agent-migration/src/lib.rs
@alexsong-oai alexsong-oai enabled auto-merge (squash) April 28, 2026 23:21
@alexsong-oai
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0762f5fcff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1125 to +1126
JsonValue::Bool(value) => Some(value.to_string()),
JsonValue::Number(value) => Some(value.to_string()),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-string MCP command/url values

json_string coerces booleans and numbers into strings, and mcp_server_toml_table accepts that output for MCP command/url. Invalid source config like { "command": true } is migrated as command = "true" instead of being skipped, creating unusable MCP entries and violating the commit’s conservative-import behavior.

Useful? React with 👍 / 👎.


let enabled_servers = settings
.and_then(|settings| settings.get("enabledMcpjsonServers"))
.map(json_string_vec)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore non-string MCP allow/deny list entries

enabledMcpjsonServers and disabledMcpjsonServers are parsed through json_string_vec, which stringifies booleans/numbers. A malformed value like enabledMcpjsonServers: true creates a non-empty allow-list ("true"), causing mcp_server_is_disabled to filter out every real server. Non-string entries should be ignored, not treated as server names.

Useful? React with 👍 / 👎.

@alexsong-oai alexsong-oai merged commit cb8b1bb into main Apr 29, 2026
28 checks passed
@alexsong-oai alexsong-oai deleted the alexs/extend-migrate branch April 29, 2026 00:45
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants