Codex CLI Plugin System: Bundling Skills, MCP Servers, and App Connectors

Codex CLI Plugin System: Bundling Skills, MCP Servers, and App Connectors
Codex CLI v0.117.0 (released March 26, 2026) elevated plugins to a first-class workflow primitive.1 Where previously you might wire up an MCP server in config.toml, add a SKILL.md to a directory, and configure an app connector separately, plugins collapse all three into a single installable, shareable package. The 20+ first-party integrations OpenAI shipped — Slack, Figma, Notion, Gmail, Google Drive, Cloudflare — demonstrate the model, but the more interesting story is the infrastructure underneath them, which is now available to any developer.2 This article is a complete technical reference for building, distributing, and managing Codex plugins.
Why Plugins Matter
Before the plugin system, sharing a Codex workflow meant handing someone:
- A copy of your
SKILL.mdfiles - A snippet of
~/.codex/config.tomlfor MCP servers - Manual instructions for setting up app integrations
Plugins collapse that into a single installable bundle. Install once; Codex wires up the skills, MCP servers, and app connectors automatically. The guiding principle: prototype locally, promote to plugin when sharing.
| Situation | Recommended approach |
|---|---|
| Single project, personal use | Local SKILL.md + config |
| Team-wide standards | Plugin in repo marketplace |
| Cross-project reuse | Plugin in personal marketplace |
| Distributing to other teams / OSS | Plugin in official directory |
What Plugins Are — and What They Are Not
A Codex plugin is a manifest-driven bundle that can package three types of component:3
- Skills — Markdown instruction files that Codex loads contextually to guide behaviour on specific tasks.
- MCP Servers — External tool-provider processes defined in
.mcp.jsonand registered with theMcpConnectionManager, with tool names prefixed asmcp__<server>__<tool>to avoid collisions.4 - App Connectors — Authenticated connections to external platforms (GitHub, Slack, Linear, etc.) defined in
.app.json.
The key distinction: skills are the authoring format; plugins are the distribution format.5 A workflow is designed as a skill, then wrapped in a plugin when it needs to be shared.
A plugin is not a replacement for an MCP server. An MCP server is still an independent process; a plugin is the packaging layer on top of it that makes it discoverable, installable, and removable as a named unit.6 The distinction matters: you can run an MCP server without a plugin, but you cannot ship a plugin without understanding what its components do.
Skills: The Authoring Primitive
Before covering plugin packaging, it helps to understand the skill format that plugins wrap.
Skill Directory Structure
A skill is a directory with a required SKILL.md and optional supporting files:7
my-skill/
├── SKILL.md # Required — instructions + YAML front matter
├── scripts/ # Optional — executable helpers
│ └── validate.sh
├── references/ # Optional — documentation Codex can consult
│ └── api-spec.yaml
├── assets/ # Optional — templates, fixtures
│ └── template.hbs
└── agents/
└── openai.yaml # Optional — appearance + MCP dependencies
SKILL.md Format
The front matter requires name and description. The body contains imperative instructions:
---
name: deploy-preview
description: >
Build and deploy a preview environment for the current branch.
Trigger when the user asks to deploy, preview, or stage changes.
---
## Steps
1. Run `scripts/validate.sh` to check prerequisites.
2. Build the container image using the project's Dockerfile.
3. Push to the preview registry at `$PREVIEW_REGISTRY`.
4. Output the preview URL.
## Constraints
- Never deploy to production.
- Always run validation before building.
The description field is critical — Codex uses it for implicit invocation, matching incoming tasks against skill descriptions to decide which skill to load.7
Progressive Disclosure
Codex uses a two-phase loading strategy to manage context efficiently:7
- Metadata phase — Codex reads only
name,description, file path, and optionalagents/openai.yamlfor all discovered skills. - Full load phase — When Codex decides a skill matches the current task, it loads the complete
SKILL.mdinstructions.
This means a repository with fifty skills does not consume fifty skills’ worth of context tokens — only the metadata is loaded until a skill is actually needed.
Discovery Locations
Codex scans these paths in priority order:7
$CWD/.agents/skills # Current directory
$CWD/../.agents/skills # Parent directory
$REPO_ROOT/.agents/skills # Repository root
$HOME/.agents/skills # User home
/etc/codex/skills # System admin
Built-in skills # OpenAI bundled
Invocation
Two modes:7
- Explicit — Type
$skill-namein the prompt, or use/skillsin the TUI to browse. - Implicit — Codex matches the task description against skill
descriptionfields and selects automatically.
Optional Metadata: agents/openai.yaml
For richer integration, add appearance and dependency metadata:
interface:
display_name: "Deploy Preview"
short_description: "Build and deploy preview environments"
icon_small: "./assets/icon.svg"
brand_color: "#3B82F6"
default_prompt: "Deploy a preview of the current branch"
policy:
allow_implicit_invocation: true
dependencies:
tools:
- type: "mcp"
value: "container-registry"
The dependencies.tools block tells Codex which MCP servers the skill needs — Codex can install and wire them automatically when the skill is invoked.8
Plugin Anatomy
Every plugin has a mandatory entry point at .codex-plugin/plugin.json. All other artefacts live at the plugin root, not inside .codex-plugin/.
my-plugin/
├── .codex-plugin/
│ └── plugin.json ← required
├── skills/
│ └── my-skill/
│ └── SKILL.md
├── .mcp.json ← optional MCP server config
├── .app.json ← optional app connector config
└── assets/ ← optional icons, logos, screenshots
The plugin.json Manifest
The manifest has three responsibilities: identify the plugin, point to its bundled components, and provide install-surface metadata.9
Minimal manifest (skills-only plugin):
{
"name": "my-first-plugin",
"version": "1.0.0",
"description": "Reusable greeting workflow",
"skills": "./skills/"
}
Complete manifest (all component types, full interface metadata):
{
"name": "my-plugin",
"version": "1.2.0",
"description": "Full-featured plugin example",
"author": {
"name": "Your Name",
"email": "you@example.com",
"url": "https://yoursite.example"
},
"homepage": "https://yoursite.example/my-plugin",
"repository": "https://github.com/yourorg/my-plugin",
"license": "MIT",
"keywords": ["workflow", "automation"],
"skills": "./skills/",
"mcpServers": "./.mcp.json",
"apps": "./.app.json",
"interface": {
"displayName": "My Plugin",
"shortDescription": "One-line pitch for the plugin browser",
"longDescription": "Longer markdown description shown on the detail page",
"developerName": "YourOrg",
"category": "Productivity",
"capabilities": ["code", "search"],
"websiteURL": "https://yoursite.example/my-plugin",
"privacyPolicyURL": "https://yoursite.example/privacy",
"termsOfServiceURL": "https://yoursite.example/terms",
"defaultPrompt": [
"Summarise the changes in this PR",
"Create a release note for version ${VERSION}"
],
"brandColor": "#4A90D9",
"composerIcon": "./assets/icon.png",
"logo": "./assets/logo.png",
"screenshots": ["./assets/screenshot.png"]
}
}
Key rules:
namemust be a stable, kebab-case identifier. Codex uses it as the plugin identifier and component namespace throughout the session.9versionfollows semantic versioning. Codex uses the version as part of the cache key, so a version bump forces a reinstall on next sync.- All paths must be relative to the plugin root and prefixed with
./. Omit any pointer whose component does not exist — Codex will not complain about missing optional keys. defaultPromptentries surface as starter suggestions after install — worth populating for discoverability.
Bundling MCP Servers
If your plugin wraps one or more MCP servers, point mcpServers at a .mcp.json file in the plugin root:
{
"mcpServers": {
"my-service": {
"command": "npx",
"args": ["-y", "@myorg/my-mcp-server"],
"env": {
"API_KEY": "${MY_SERVICE_API_KEY}"
}
}
}
}
The McpConnectionManager prefixes every tool from this server as mcp__my-service__<tool_name>, preventing collisions when multiple plugins are active.4 If the server requires OAuth or additional setup at runtime, Codex triggers an Elicitation Request — an interactive prompt that appears before the tool is first called.10
Writing Skills for Plugins
Skills within a plugin follow the standard SKILL.md format, placed under skills/<skill-name>/SKILL.md:
---
name: summarise-pr
description: Summarises a GitHub pull request with context from linked issues
---
When asked to summarise a PR:
1. Retrieve the PR description, commits, and any linked issue titles.
2. Identify the change type: feature, fix, refactor, or chore.
3. Write a three-sentence summary: what changed, why, and what to watch for in review.
The skills field in plugin.json points to the directory; Codex discovers all SKILL.md files within it automatically.7
MCP Server Configuration In Depth
MCP (Model Context Protocol) is the bridge between plugins and external systems. Codex supports two transport types.10
STDIO Servers (Local Process)
[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
startup_timeout_sec = 15
tool_timeout_sec = 120
[mcp_servers.context7.env]
API_KEY = "sk-..."
Streamable HTTP Servers (Remote)
[mcp_servers.figma]
url = "https://mcp.figma.com/mcp"
bearer_token_env_var = "FIGMA_OAUTH_TOKEN"
http_headers = { "X-Figma-Region" = "us-east-1" }
Universal Server Options
These apply to both transport types:10
| Key | Default | Purpose |
|---|---|---|
startup_timeout_sec |
10 | Server initialisation timeout |
tool_timeout_sec |
60 | Tool execution timeout |
enabled |
true | Enable/disable without deletion |
required |
false | Fail startup if server cannot initialise |
enabled_tools |
all | Tool allowlist |
disabled_tools |
none | Tool denylist (applied after allowlist) |
OAuth for MCP
For servers requiring OAuth authentication:10
mcp_oauth_callback_port = 5555
mcp_oauth_callback_url = "https://devbox.example.internal/callback"
Authenticate with:
codex mcp login <server-name>
Codex uses server-advertised scopes when available. The custom callback URL supports remote devbox scenarios where localhost is not reachable.
CLI MCP Management
# Add a server
codex mcp add sentry --env SENTRY_TOKEN=sk-... -- npx @sentry/mcp-server
# View active servers in TUI
/mcp
Plugin Lifecycle
flowchart TD
A[Marketplace JSON] -->|PluginsManager reads| B[Discovery]
B -->|Policy check: AVAILABLE / INSTALLED_BY_DEFAULT| C{User installs?}
C -- Yes --> D[Cache: ~/.codex/plugins/cache/marketplace/plugin/version/]
D --> E[config.toml updated]
C -- No --> F[Not loaded]
E -->|Session start| G[Skills → SkillsManager]
E -->|Session start| H[MCP Servers → McpConnectionManager]
E -->|Session start| I[App Connectors → AppsManager]
G & H & I --> J[Active Session]
Session injection is the critical step: every time a new Codex thread starts, the PluginsManager provides its LoadedPlugin set to McpManager, SkillsManager, and AppsManager simultaneously.4 There is no hot-reload; plugin changes take effect on the next session.
Distributing Plugins via Marketplaces
Plugins are surfaced through marketplace manifests. Three scopes are supported:11
| Scope | File location | Who sees it |
|---|---|---|
| OpenAI Curated | Built-in | All Codex users |
| Repository | $REPO_ROOT/.agents/plugins/marketplace.json |
Anyone opening that repo in Codex |
| Personal | ~/.agents/plugins/marketplace.json |
You only |
Marketplace JSON Structure
{
"name": "my-team-marketplace",
"interface": {
"displayName": "My Team Plugins"
},
"plugins": [
{
"name": "my-plugin",
"source": {
"source": "local",
"path": "./plugins/my-plugin"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
},
{
"name": "onboarding-plugin",
"source": {
"source": "local",
"path": "./plugins/onboarding"
},
"policy": {
"installation": "INSTALLED_BY_DEFAULT",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
}
]
}
Installation Policies
The policy.installation field controls how plugins are surfaced:11
AVAILABLE— browseable and installable, not auto-installed.INSTALLED_BY_DEFAULT— installed automatically when Codex opens the repo.NOT_AVAILABLE— hidden from the browser (useful for staged rollouts).
INSTALLED_BY_DEFAULT is the key enterprise primitive. Commit a repo-scoped marketplace.json with this policy for core platform plugins, and every engineer who clones the repo gets them automatically without browsing a directory or running a setup command.
Authentication Timing
The policy.authentication field controls when credential prompts appear:12
| Value | Behaviour |
|---|---|
ON_INSTALL |
Prompt for credentials immediately on install |
ON_FIRST_USE |
Defer the auth prompt until the plugin is first invoked |
ON_FIRST_USE produces a smoother onboarding experience for optional integrations; ON_INSTALL is preferable for plugins that are useless without credentials.
Paths in source.path must be relative to the marketplace root and prefixed with ./.
Installation
Via the CLI /plugins command
From any Codex CLI session:
/plugins
This opens an interactive plugin directory. Navigate to your target plugin, select Install plugin, complete any authentication prompts, then start a new thread.13 The PluginsManager resolves the source, validates the policy, and writes the resolved bundle to ~/.codex/plugins/cache/$MARKETPLACE_NAME/$PLUGIN_NAME/local/.
For local plugins, $VERSION is local. Updates require reinstallation because Codex loads from the cache path, not directly from the marketplace entry.
Manual Installation (repository-scoped)
# 1. Create plugin in repo
mkdir -p .codex/plugins/my-plugin
cp -r /path/to/my-plugin/* .codex/plugins/my-plugin/
# 2. Create or update marketplace manifest
cat > .agents/plugins/marketplace.json <<'EOF'
{
"name": "repo-marketplace",
"interface": { "displayName": "Repo Plugins" },
"plugins": [
{
"name": "my-plugin",
"source": { "source": "local", "path": "./plugins/my-plugin" },
"policy": { "installation": "AVAILABLE", "authentication": "ON_INSTALL" },
"category": "Developer Tools"
}
]
}
EOF
# 3. Restart Codex — plugin appears in /plugins on next session
Configuration Management
Installed plugins appear in ~/.codex/config.toml under the [plugins] table:13
[plugins."my-plugin@repo-marketplace"]
enabled = true
[plugins."another-plugin@openai-curated"]
enabled = false
The config layer follows the standard resolution hierarchy: CLI arguments > environment variables > project .codex/config.toml > global ~/.codex/config.toml > defaults.4 Setting enabled = false keeps the plugin installed but prevents it from contributing to sessions — useful for temporarily disabling a noisy MCP server without losing your auth state.
To uninstall completely, use the plugin browser: Uninstall plugin removes the bundle from ~/.codex/plugins/cache/, but any bundled app connectors remain installed in ChatGPT until removed separately.13
Scaffolding Plugins with $plugin-creator
The built-in $plugin-creator skill is the fastest path from idea to testable plugin:11
@plugin-creator scaffold a plugin for our internal Jira instance that wraps the mcp-jira server
$plugin-creator generates:
.codex-plugin/plugin.jsonwith metadata stubsskills/directory with a starterSKILL.md.mcp.jsonreferencing the target server- A
marketplace.jsonentry for local testing
Review the scaffolded output before sharing — in particular verify name is stable and unique, and that source.path values are relative to the marketplace root. @plugin-creator can also generate a marketplace entry for a GitHub-hosted plugin when given a repo URL as context.
Schema Generation and Validation
Two commands generate version-locked type definitions for tooling:12
codex app-server generate-ts # TypeScript types
codex app-server generate-json-schema # JSON Schema bundle
Use the JSON Schema output in CI to validate manifests before distribution:
ajv validate \
-s <(codex app-server generate-json-schema) \
-d .codex-plugin/plugin.json
Increment version in plugin.json for every change that affects skills or MCP configuration. Codex uses the version as part of the cache key, so a version bump forces a reinstall on next sync.
Using Installed Plugins
Once installed and a new thread is started, plugins surface in two ways:
- Contextual loading — Codex loads relevant skills automatically based on the task.
- Explicit
@invocation — Type@in the composer to browse installed plugins and skills by name.13
@my-plugin summarise the last 10 commits on this branch
Plugin-backed MCP tools are available transparently; you do not need to invoke them by their prefixed mcp__ name unless you want to reference a specific tool explicitly.
Building a Private Enterprise Plugin Registry
For organisations managing dozens of plugins, the repo + personal marketplace approach extends into a proper internal registry:12
flowchart TD
A[Plugin Source Code] -->|git push| B[Internal Git Repo]
B -->|CI pipeline| C[Plugin Validation]
C -->|pass| D[Enterprise marketplace.json]
D --> E[Company dotfiles / onboarding script]
E --> F[~/.agents/plugins/marketplace.json]
E --> G[.agents/plugins/marketplace.json in project repos]
F & G --> H[Codex Plugin Directory]
The CI validation step should verify manifest schema (via codex app-server generate-json-schema), confirm no unexpected MCP endpoints, and require a version bump for every content change. Distribute the company-wide marketplace.json via onboarding dotfiles; teams add project-specific plugins at $REPO_ROOT/.agents/plugins/marketplace.json.
Distribution Options
OpenAI’s official plugin directory hosts the 20+ first-party integrations; self-serve third-party submission is expected soon.3 In the meantime, four distribution paths are available:
- Repo marketplace — commit
.agents/plugins/marketplace.json; anyone who clones the repo gets access. - Personal marketplace —
~/.agents/plugins/marketplace.jsonfor individual tooling. - GitHub-hosted — a
marketplace.jsonin a public repo that others reference manually. - Enterprise registry — internal CI/CD pipeline as described above.
End-to-End Example: A Sentry Triage Plugin
Combining all three layers into a practical plugin:14
sentry-triage/
├── .codex-plugin/
│ └── plugin.json
├── skills/
│ └── triage-errors/
│ ├── SKILL.md
│ └── scripts/
│ └── format-report.sh
├── .mcp.json
└── assets/
└── icon.png
.mcp.json — wires the Sentry MCP server:
{
"sentry": {
"command": "npx",
"args": ["-y", "@sentry/mcp-server"],
"env": {
"SENTRY_TOKEN": "${SENTRY_AUTH_TOKEN}"
}
}
}
skills/triage-errors/SKILL.md:
---
name: triage-errors
description: >
Fetch recent unresolved Sentry errors, group by root cause,
and generate a prioritised triage report.
---
1. Use the Sentry MCP tool to fetch unresolved issues from the last 24 hours.
2. Group issues by stack trace similarity.
3. For each group, identify the likely root cause from the code.
4. Run `scripts/format-report.sh` to produce a markdown report.
5. Present the report sorted by frequency × severity.
agents/openai.yaml:
dependencies:
tools:
- type: "mcp"
value: "sentry"
This plugin gives any team member a one-command Sentry triage workflow — install the plugin, and $triage-errors or even “check today’s errors” triggers the full pipeline.
Multi-Agent v2 and Plugin Propagation
With multi-agent v2 (also introduced in v0.117.0), spawned subagents at path-based addresses like /root/agent_a inherit the parent session’s loaded plugins.1 This means a plugin-provided MCP server available in the root session is also available to worker agents without additional configuration — the PluginsManager injects the full LoadedPlugin set at session initialisation for every agent in the tree.
If a subagent requires a different plugin set, you can override via a custom agent file under .codex/agents/:
# .codex/agents/restricted-worker.toml
name = "restricted-worker"
description = "Runs with minimal plugin surface"
developer_instructions = "..."
[plugins."noisy-plugin@repo-marketplace"]
enabled = false
Cross-Platform Compatibility
The skill format is converging across vendors. The same SKILL.md file works with Codex, Gemini CLI, and Claude Code’s equivalent system.15 MCP is the shared protocol standard that Anthropic championed and OpenAI has adopted.10 Building on these standards means plugin investments are not locked to a single vendor’s ecosystem.
Practical Considerations
Skill loading is lazy. Skills inside a plugin follow the same progressive disclosure model as standalone SKILL.md files — they do not inflate the context window on startup.16
MCP servers start on demand. The server defined in .mcp.json starts when a skill or prompt references it. Keep MCP startup time in mind for latency-sensitive workflows.
Test before distributing. Use the local marketplace entry generated by @plugin-creator to verify the full install-to-invoke cycle. The most common failure modes are incorrect relative paths in component pointers and missing authentication configuration.
Namespace collisions. Plugin names must be unique within a marketplace. Across marketplaces, Codex disambiguates by $PLUGIN_NAME@$MARKETPLACE_NAME — renaming a published plugin is a breaking change for any automation that references it.
Current Limitations
- Self-serve publishing to the official Plugin Directory is not yet available — OpenAI currently curates submissions.9
- Plugin discovery in the CLI is functional but less polished than the app experience — use
/pluginsto browse.3 - Hooks are not yet Windows-compatible — skills relying on hook-based pre/post processing may not work on Windows installations.17
- No version pinning for marketplace plugins — teams relying on stability should use repo-scoped local marketplaces with vendored plugin directories.
Summary
The Codex plugin system turns the previous ad-hoc combination of config.toml MCP entries, scattered SKILL.md files, and manual app configuration into a single, versioned, discoverable unit. The key principles:
- One manifest (
plugin.json) governs identity, components, and install-surface metadata. - Skills are the authoring primitive; plugins are the distribution primitive.
- Marketplaces control discoverability and default installation policy.
- Session injection is synchronous at thread start — changes require a new session.
config.tomlis the source of truth for per-installation enable/disable state.- Subagents in multi-agent v2 trees inherit the parent plugin set by default.
- The underlying skill and MCP standards are cross-platform, reducing vendor lock-in.
Citations
-
Codex Plugins System architecture — DeepWiki openai/codex ↩ ↩2 ↩3 ↩4
-
Agent Skills — Codex Developer Documentation ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Model Context Protocol — Codex Developer Documentation ↩ ↩2 ↩3 ↩4 ↩5
-
Codex by OpenAI — Release Notes March 2026 (Releasebot) ↩ ↩2 ↩3 ↩4
-
Morph LLM — Claude Code Skills vs MCP vs Plugins: Complete Guide 2026 ↩
-
Augment Code — OpenAI Codex CLI ships v0.116.0 with enterprise features ↩