Codex Plugin Packaging skill for Codex
Ship skills and MCP servers as one install. Codex assembles finished skills and MCP servers into a plugin with a portable plugin.json, skills folder and mcp.json, declares server authentication without shipping secrets, and lists the plugin in a repo or personal marketplace. It installs the result on a throwaway CODEX_HOME with codex plugin commands, checks every skill and server loads, then prepares versioning and workspace or public publishing.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- codex-plugin-packaging Installs to ~/.agents/skills/codex-plugin-packaging/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install codex-plugin-packaging The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project codex-plugin-packaging Installs to .agents/skills/codex-plugin-packaging/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/codex-plugin-packaging.zipunzip -o codex-plugin-packaging.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) codex-plugin-packaging PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $codex-plugin-packaging, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Packages Codex skills, bundled MCP servers, registered connector mappings and optional lifecycle hooks into a plugin with a portable plugin.json, skills/ and mcp.json (or the .codex-plugin compatibility layout from $plugin-creator), exposes it through a repo or personal marketplace.json, installs and tests it with codex plugin and codex plugin marketplace commands, and prepares it for workspace or public publishing. Use when bundling several skills or a skill plus an MCP server for other people, building a team plugin catalog, or debugging a plugin that does not appear or load.
Use it when
- Distributing several skills, or a skill plus MCP server, to other people
- Building a curated plugin catalog for a repo or team
- Converting a $plugin-creator scaffold to the portable layout
- A plugin fails to appear, install or load its servers
Use something else when
- A single skill for yourself or one repo: use skill-scripts-packaging
- Only connecting an existing MCP server to your own Codex: use codex-mcp-servers
How to check the result
Install from the marketplace into a fresh CODEX_HOME with codex plugin add, then confirm /plugins, /skills and /mcp list every bundled component and each skill runs on a sample prompt.
The mistake it prevents
Renaming a compatibility .mcp.json to mcp.json without adding the transport type and schema the portable format requires.
A plugin is ready when it has a stable kebab-case name and version, its skills and MCP servers sit at the fixed portable paths, a marketplace entry installs it on a clean machine, its servers authenticate without secrets in the package, and every bundled skill triggers and works after install.
When a plugin is the right unit
| Need | Use |
|---|---|
| One workflow for yourself or one repo | A plain skill in .agents/skills/ or ~/.agents/skills/ |
| Several related skills, versioned, for other teams | Plugin |
| A skill that depends on an MCP server or connector | Plugin bundling both |
| A catalog of plugins for a team or repo | Marketplace file listing several plugins |
| Workspace-wide availability by role | Publish the plugin to the ChatGPT workspace (admin) |
| Public listing in the shared ChatGPT and Codex directory | Submission portal review |
Portable layout (recommended for new plugins)
acme-support/
├── plugin.json # identity + optional extensions.com.openai
├── skills/
│ ├── triage-ticket/SKILL.md
│ └── draft-reply/SKILL.md
├── mcp.json # bundled MCP servers (optional)
├── hooks/hooks.json # lifecycle hooks (optional, desktop manual installs only)
└── assets/ # icons, logo, screenshots
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "acme-support",
"version": "0.3.0",
"description": "Triage Acme support tickets and draft replies.",
"author": { "name": "Acme Platform Team", "email": "[email protected]" },
"license": "MIT",
"keywords": ["support", "tickets"],
"extensions": {
"com.openai": {
"interface": {
"displayName": "Acme Support",
"shortDescription": "Ticket triage and reply drafting",
"developerName": "Acme Platform Team",
"category": "Productivity",
"capabilities": ["Read", "Write"],
"defaultPrompt": ["Use Acme Support to triage the newest open tickets."],
"composerIcon": "./assets/icon.png",
"logo": "./assets/logo.png"
},
"onboardingSkill": "./skills/setup/SKILL.md"
}
}
}
Skills are discovered from root skills/; the manifest needs no skills field. Paths
inside extensions.com.openai start with ./, resolve from the plugin root, and stay
inside it. When extensions.com.openai is present it replaces any
.codex-plugin/plugin.json overlay entirely; the two are not merged. Only reference an
onboardingSkill that exists in the package.
mcp.json uses its own schema and a transport type per server:
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
"mcpServers": {
"tickets": {
"type": "streamable-http",
"url": "https://mcp.acme.example/mcp",
"extensions": { "com.openai": { "auth": { "type": "oauth", "client": { "mode": "dcr" } } } }
},
"local-index": {
"type": "stdio",
"command": "node",
"args": ["./server/index.js"],
"cwd": "${PLUGIN_ROOT}"
}
}
}
Auth types: none, oauth (client mode provided with clientId, dcr, or cimd),
mixed, api_key with headerScheme. Never put a client secret or token in the
package; the manifest has no clientSecret field and secrets are entered during
connection setup. Public submission needs a remote HTTPS MCP endpoint; local stdio
servers make a plugin desktop-only.
Compatibility layout ($plugin-creator)
$plugin-creator in Codex (or @plugin-creator in ChatGPT Work) scaffolds
.codex-plugin/plugin.json plus optional .mcp.json, .app.json (registered connector
mappings, IDs starting plugin_asdk_app), skills/, hooks/, scripts/, assets/,
and can create a personal marketplace entry for testing. It is supported; to make it
portable, move to root plugin.json and mcp.json with their schemas. Do not just
rename .mcp.json: portable servers need a type.
Workflow
- [ ] 1. Finish and lint each skill on its own first
- [ ] 2. Create the plugin folder and manifest
- [ ] 3. Add MCP servers with auth declared, no secrets
- [ ] 4. Add a marketplace entry
- [ ] 5. Install on a clean profile and test every skill and server
- [ ] 6. Version, then publish to the intended audience
4. Marketplace
Repo catalog at $REPO_ROOT/.agents/plugins/marketplace.json, personal catalog at
~/.agents/plugins/marketplace.json:
{
"name": "acme-internal",
"interface": { "displayName": "Acme Internal Plugins" },
"plugins": [
{
"name": "acme-support",
"source": { "source": "local", "path": "./plugins/acme-support" },
"policy": { "installation": "AVAILABLE", "authentication": "ON_INSTALL" },
"category": "Productivity"
},
{
"name": "acme-oncall",
"source": { "source": "git-subdir", "url": "https://github.com/acme/codex-plugins.git", "path": "./plugins/oncall", "ref": "v1.2.0" },
"policy": { "installation": "AVAILABLE", "authentication": "ON_INSTALL" },
"category": "Productivity"
}
]
}
source.pathis relative to the marketplace root (the repo root for a repo catalog), starts with./, and stays inside that root.- Every entry needs
policy.installation(AVAILABLE,INSTALLED_BY_DEFAULT,NOT_AVAILABLE),policy.authentication, andcategory. - Other sources:
"source": "url"for a plugin at a Git repo root,"source": "npm"withpackageand optionalversion/registry(downloaded without lifecycle scripts). - Entries Codex cannot resolve are skipped, not fatal; check the list after changes.
5. Install and test
codex plugin marketplace add ./ # or owner/repo, owner/repo@ref, a Git URL
codex plugin marketplace list
codex plugin list --marketplace acme-internal --json --available
codex plugin add acme-support@acme-internal --json
codex plugin remove acme-support@acme-internal
codex plugin marketplace upgrade acme-internal # refresh Git-backed catalogs
Installed copies live under ~/.codex/plugins/cache/<marketplace>/<plugin>/<version>/
(local for local sources); edit the source, then reinstall or restart so the cache
refreshes. In the TUI, /plugins shows installed plugins and their tools, /skills
should list the bundled skills, and /mcp the bundled servers. The ChatGPT desktop app
reads the same marketplace files after a restart.
Per-repo enablement and server policy go in config, not the package:
[plugins."acme-support@acme-internal"]
enabled = true
[plugins."acme-support@acme-internal".mcp_servers.tickets]
default_tools_approval_mode = "prompt"
enabled_tools = ["get_ticket", "search_tickets"]
Test on a clean CODEX_HOME so leftovers from development do not hide missing files:
export CODEX_HOME="$(mktemp -d)" # throwaway home; run codex login inside it
codex plugin marketplace add ./
codex plugin add acme-support@acme-internal
codex # check /plugins, /skills, /mcp
unset CODEX_HOME
6. Publish
- Bump
versionfor every release; Git-backed entries can pinreforsha. - Workspace: a workspace admin publishes from ChatGPT Plugins > Personal > Publish and chooses roles. This does not list it publicly.
- Public directory: submit through the plugin submission portal. Plugins with lifecycle hooks are not eligible.
Gotchas
- Plugin hooks run only for plugins installed manually in the desktop app, are skipped
until the user reviews and trusts them, and receive
PLUGIN_ROOTandPLUGIN_DATA. Hook scripts must exist where Codex executes; a web install does not deploy them. - An explicit
hooksvalue in the manifest replaces defaulthooks/hooks.jsondiscovery. - Skill names inside a plugin are namespaced by the plugin name, but keep them distinct from popular standalone skills anyway to avoid confusing implicit matching.
- Project
.codex/config.tomlplugin settings apply only in trusted projects. - Marketplace refresh can install files for configured plugins even when
enabled = false; disabling is not uninstalling. - Review third-party plugins like dependencies: skills are instructions, servers are code.
Output contract
**Plugin:** acme-support 0.3.0 (portable layout; 2 skills, 1 HTTP MCP server with DCR OAuth)
**Marketplace:** .agents/plugins/marketplace.json (acme-internal)
**Clean install test:** codex plugin add ok; /skills lists 2; /mcp shows tickets connected after login
**Publish:** workspace roles Support, Platform (pending admin) What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Codex Plugin Packaging"
short_description: "Ship skills and MCP servers as one install"
default_prompt: "$codex-plugin-packaging Package our three support skills and the tickets MCP server as a Codex plugin" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.