Rescanned against openai/codex main on March 11, 2026.
The plugin system is active in source, but it is still explicitly under development.
features.pluginsexists, is stageunderDevelopment, and defaults tofalsein features.rs.codex app-servernow exposesplugin/list,plugin/install, andplugin/uninstall, and the README still marks them under development in codex-rs/app-server/README.md.- The contract has continued to evolve after the March 6 scan: marketplace policies now exist, curated plugin state can sync from a remote ChatGPT endpoint, and uninstall is now a real API.
A plugin is still a local bundle of optional:
- skills
- MCP servers
- app connector IDs
- manifest-declared UI metadata
Codex still runs plugins from a local cache under $CODEX_HOME/plugins/cache/... via store.rs.
A marketplace is now more than just local discovery metadata. It is the source of:
- plugin source locations
- install policy
- auth policy
- category taxonomy
- plugin interface data surfaced by
plugin/list
Codex can also mirror an official curated marketplace from https://github.com/openai/plugins.git into $CODEX_HOME/.tmp/plugins via curated_repo.rs.
Plugins remain user-config only.
- The plugin feature gate is read only from the user config layer in manager.rs.
- Plugin entries are also read only from the user config layer in manager.rs.
- Project
.codex/config.tomlfiles cannot enable plugin loading or define plugin state for a checkout.
Current user config shape:
[features]
plugins = true
[plugins."my-plugin@debug"]
enabled = trueImportant details:
- The config key is still
<plugin>@<marketplace>. PluginConfigstill only containsenabledin types.rs.- Plugin and marketplace names must be non-empty and limited to ASCII letters, digits,
_, and-in store.rs.
Installed plugins still live under:
$CODEX_HOME/plugins/cache/<marketplace>/<plugin>/local
Current store behavior from store.rs:
- install version is still hardcoded to
"local" - reinstall overwrites the existing cache entry
is_installed()is now a first-class cache presence check used byplugin/listuninstall()removes the cached plugin directory for that plugin key
Separately, the curated marketplace mirror lives under:
$CODEX_HOME/.tmp/plugins
$CODEX_HOME/.tmp/plugins.sha
Curated sync behavior from curated_repo.rs and manager.rs:
- source repo is
https://github.com/openai/plugins.git - the sync is gated by
features.plugins - background sync can be disabled with
CODEX_DISABLE_CURATED_PLUGIN_SYNC
Default layout is still recognized:
my-plugin/
.codex-plugin/
plugin.json
skills/
my-skill/
SKILL.md
.mcp.json
.app.json
But the manifest is now richer and can override those default component paths.
Current manifest fields from manifest.rs:
namedescriptionskillsmcpServersappsinterface
Minimal manifest:
{
"name": "my-plugin"
}Manifest with path overrides and UI metadata:
{
"name": "my-plugin",
"description": "Plugin summary for instructions and listings.",
"skills": "./plugin-skills",
"mcpServers": "./plugin.mcp.json",
"apps": "./plugin.apps.json",
"interface": {
"displayName": "My Plugin",
"shortDescription": "Shown in listings",
"longDescription": "Shown in richer UI details",
"developerName": "Example, Inc.",
"category": "Productivity",
"capabilities": ["Interactive", "Write"],
"websiteURL": "https://example.com/",
"privacyPolicyURL": "https://example.com/privacy",
"termsOfServiceURL": "https://example.com/terms",
"defaultPrompt": "Starter prompt for trying this plugin",
"brandColor": "#3B82F6",
"composerIcon": "./assets/icon.png",
"logo": "./assets/logo.png",
"screenshots": ["./assets/shot1.png", "./assets/shot2.png"]
}
}Path rules from manifest.rs:
- manifest paths must start with
./ - manifest paths cannot be
./ - manifest paths cannot contain
.. - manifest paths are resolved relative to the plugin root
- invalid override paths are ignored with warnings rather than crashing plugin load
The same rule applies to interface assets such as composerIcon, logo, and screenshots.
Plugin skill loading is still rooted in the plugin directory, but it is now more configurable and more visible to the model.
- If the manifest provides
skills, Codex uses that path; otherwise it falls back toskills/in manager.rs. - Skills are namespaced using the plugin manifest name in manager.rs and skills/loader.rs.
- The global
## Pluginsinstruction block is still injected into user instructions via render.rs and project_doc.rs.
Explicit plugin mentions now inject targeted developer guidance for that plugin via plugins/injection.rs and render.rs.
Tested behavior in codex-rs/core/tests/suite/plugins.rs:
- explicit plugin mention adds guidance about skill prefixes
- explicit plugin mention adds guidance about visible MCP servers
- explicit plugin mention adds guidance about visible apps
- plugin-associated tool descriptions include plugin provenance
Plugin MCP loading still mirrors normal mcpServers config, but manifest path overrides now apply here too.
- If the manifest provides
mcpServers, Codex loads that file; otherwise it falls back to.mcp.jsonin manager.rs. - Relative
cwdvalues inside plugin MCP configs are still rebased to the plugin root in manager.rs. oauth.callbackPortis still ignored for plugin MCP configs in manager.rs.- Duplicate MCP server names across plugins are still skipped in manager.rs.
- User-configured MCP servers still win over plugin MCP servers in mcp/mod.rs.
Plugin app loading also supports manifest path overrides now.
- If the manifest provides
apps, Codex loads that file; otherwise it falls back to.app.jsonin manager.rs. - The file contents are still a mapping of arbitrary entry names to connector IDs.
- Connectors now carry
plugin_display_namesprovenance in connectors.rs. - Accessible app connectors inherit plugin provenance from MCP tools in connectors.rs.
Install-time behavior from codex_message_processor.rs:
plugin/installreturnsappsNeedingAuthfor plugin apps that exist in the app directory but are not yet accessibleplugin/installalso returns the marketplace-declared plugin auth policy
Marketplace files must satisfy the contract in marketplace.rs:
- the file must live at
<root>/.agents/plugins/marketplace.json - source paths are local only
- source paths must start with
./ - source paths are resolved relative to
<root>, not relative to.agents/plugins/
Current marketplace format:
{
"name": "debug",
"plugins": [
{
"name": "my-plugin",
"source": {
"source": "local",
"path": "./plugins/my-plugin"
},
"installPolicy": "AVAILABLE",
"authPolicy": "ON_USE",
"category": "Productivity"
}
]
}Marketplace-driven policy and taxonomy fields from marketplace.rs:
installPolicyauthPolicycategory
Current install policy enum values:
NOT_AVAILABLEAVAILABLEINSTALLED_BY_DEFAULT
Current auth policy enum values:
ON_INSTALLON_USE
Important marketplace behavior:
plugin/installrejects plugins markedNOT_AVAILABLE- marketplace
categoryoverrides manifest interface category when both are present authPolicyis carried through install and returned to the client
plugin/list is now the authoritative discovery surface.
Discovery rules come from marketplace.rs and manager.rs:
- home marketplace discovery is always considered first
- additional repo roots come from
plugin/list.cwds - the curated repo root is appended automatically when the synced mirror exists
- marketplace files with the same
namecan coexist as separate entries if they come from different paths - duplicate plugin keys are deduped in
plugin/list, and the first discovered source wins
In practice:
- home-scoped duplicates beat repo-scoped duplicates
- later marketplaces may still appear in the response, but duplicate plugin entries are removed once an earlier source already claimed that plugin key
Current protocol shape from v2.rs:
{
"method": "plugin/list",
"id": 1,
"params": {
"cwds": ["/absolute/workspace/path"],
"forceRemoteSync": false
}
}cwds is optional. If omitted, only home-scoped marketplaces and the curated marketplace are considered.
forceRemoteSync is new:
- when
true, app-server attempts to reconcile the curated marketplace against remote ChatGPT plugin state before returning results - if that sync fails,
plugin/liststill returns local marketplace data and setsremoteSyncError
Current response additions from v2.rs:
remoteSyncErrorinstallPolicyon each pluginauthPolicyon each plugin
Trimmed response shape:
{
"marketplaces": [
{
"name": "debug",
"path": "/abs/path/to/.agents/plugins/marketplace.json",
"plugins": [
{
"id": "my-plugin@debug",
"name": "my-plugin",
"source": {
"type": "local",
"path": "/abs/path/to/plugin/root"
},
"installed": false,
"enabled": false,
"installPolicy": "AVAILABLE",
"authPolicy": "ON_USE",
"interface": {
"displayName": "My Plugin"
}
}
]
}
],
"remoteSyncError": null
}This is implemented in codex_message_processor.rs and tested in plugin_list.rs.
Current protocol shape from v2.rs:
{
"method": "plugin/install",
"id": 2,
"params": {
"marketplacePath": "/abs/path/to/.agents/plugins/marketplace.json",
"pluginName": "my-plugin"
}
}Current response shape:
{
"authPolicy": "ON_USE",
"appsNeedingAuth": [
{
"id": "calendar",
"name": "Google Calendar",
"description": "Plan events",
"installUrl": "https://chatgpt.com/apps/google-calendar/calendar"
}
]
}Important behavior from codex_message_processor.rs and plugin_install.rs:
- install still copies into the local cache and writes
enabled = trueto user config - install now returns marketplace
authPolicy - install rejects marketplace entries marked
NOT_AVAILABLE - install still computes
appsNeedingAuthfor plugin apps that are present but not accessible
Uninstall is now a real API.
Current protocol shape from v2.rs:
{
"method": "plugin/uninstall",
"id": 3,
"params": {
"pluginId": "my-plugin@debug"
}
}Current response:
{}Behavior from manager.rs, store.rs, and plugin_uninstall.rs:
- removes the plugin cache directory
- clears the plugin’s user-level config entry
- is idempotent enough to allow repeated uninstall calls
This is the biggest new capability since the March 6 scan.
plugin/list(forceRemoteSync: true) can reconcile the local openai-curated marketplace against remote ChatGPT plugin status via manager.rs.
High-level behavior:
- only the curated marketplace participates
- ChatGPT auth is required
- API key auth is explicitly unsupported
- remote state can cause installs, enables, disables, and uninstalls in the local curated cache and config
- sync is fail-open for
plugin/list: on sync failure, app-server still returns local data and fillsremoteSyncError
Remote sync result categories in manager.rs:
installed_plugin_idsenabled_plugin_idsdisabled_plugin_idsuninstalled_plugin_ids
Remote sync is exercised by tests in plugin_list.rs and manager.rs.
The app-server README still documents plugin mentions directly.
- UI text can include a plugin mention token like
@sample - the structured mention path should be
plugin://<plugin-name>@<marketplace-name> - explicit plugin mention remains the mechanism for injecting plugin-scoped guidance into a turn
Relevant docs and tests:
Example structured input:
{
"type": "mention",
"name": "sample",
"path": "plugin://sample@test"
}Current install flow:
- Call
plugin/list. - Pick a marketplace entry and plugin entry.
- Call
plugin/installwith the returnedmarketplacePathand plugin name. - Codex copies the plugin into the cache.
- Codex writes
enabled = trueunderplugins."<plugin>@<marketplace>". - Codex clears plugin and skill caches.
- Codex returns
authPolicyand, if applicable,appsNeedingAuth.
Current update story:
- Change the local source plugin directory.
- Call
plugin/installagain. - Codex overwrites the cached
localversion.
Disable is still config-driven:
[plugins."my-plugin@debug"]
enabled = falseRemove is now API-backed:
- Call
plugin/uninstall. - Codex deletes the cached plugin directory.
- Codex removes the plugin entry from user config.
What Codex has as of March 11, 2026:
- a user-config-only plugin feature gate
- cache-backed installed plugins
- manifest-declared metadata and component path overrides
- marketplace-declared install and auth policies
- a first-class
plugin/listAPI - an explicit-path
plugin/installAPI - a real
plugin/uninstallAPI - install-time app auth hints and auth policy return values
- explicit plugin mention guidance injection
- a synced curated catalog from
openai/plugins - optional remote reconciliation of curated plugin state via
plugin/list(forceRemoteSync: true)
What it still does not have:
- remote plugin package downloads beyond marketplace-declared local source paths
- semver or multi-version installs
- a fully documented stable production contract
- codex-rs/core/src/plugins/manager.rs
- codex-rs/core/src/plugins/manifest.rs
- codex-rs/core/src/plugins/marketplace.rs
- codex-rs/core/src/plugins/curated_repo.rs
- codex-rs/core/src/plugins/injection.rs
- codex-rs/core/src/plugins/render.rs
- codex-rs/core/src/plugins/store.rs
- codex-rs/core/src/project_doc.rs
- codex-rs/core/src/mcp/mod.rs
- codex-rs/core/src/connectors.rs
- codex-rs/core/src/skills/loader.rs
- codex-rs/app-server/src/codex_message_processor.rs
- codex-rs/app-server-protocol/src/protocol/v2.rs
- codex-rs/app-server/README.md
- codex-rs/app-server/tests/suite/v2/plugin_list.rs
- codex-rs/app-server/tests/suite/v2/plugin_install.rs
- codex-rs/app-server/tests/suite/v2/plugin_uninstall.rs
- codex-rs/core/tests/suite/plugins.rs