Formats
Enconvo checks these in order and uses the first that matches. An Enconvo
package.json always wins, so an Enconvo plugin can also carry a plugin.json for other apps. A package.json without Enconvo’s schema or commands, such as the one an MCP server’s Node.js project has, doesn’t hide a plugin.json next to it.
Codex and OpenAI’s portal read a root plugin.json as a portable plugin only when it gives "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", the one Agent Plugins schema Codex supports. Without it, Codex reads .codex-plugin/plugin.json or .claude-plugin/plugin.json instead, and installs nothing when neither is there. Enconvo installs the plugin either way, and plugin validate reports plugin_schema_missing. A portable plugin’s name has up to 64 lowercase letters, digits and single dots or hyphens, and starts and ends with a letter or digit, such as acme-tools or acme.tools. OpenAI’s portal refuses the dot, so name a plugin you mean to submit there acme-tools. Leave out a field you don’t fill in rather than setting it to null: Codex and Claude Code refuse a plugin.json with a null version, description, author, homepage, repository or license, and both want repository and license as text and keywords as a list of text.
One package for Enconvo, Codex and Claude Code
plugin create sets up a package that installs in all three apps:
--template command, the default, makes an Enconvo plugin with a TypeScript command; see Developing Extensions.
name, version and description the same in the three manifests, and raise version for every release. plugin validate warns when they differ, and plugin publish refuses.
The commands on this page need an
@enconvo/api release newer than 0.1.174, and installing needs Enconvo 2.5.6 or later. Inside a project that depends on @enconvo/api, npx enconvo … is enough.How Enconvo reads the manifests
When a folder has more than oneplugin.json, Enconvo reads all of them. For each field, the first manifest that sets it wins, in this order: .claude-plugin/plugin.json, .codex-plugin/plugin.json, then the root plugin.json. The others fill in what it leaves out, so a Codex manifest can add the listing to a portable one. extensions are merged per vendor, and extensions["com.openai"].interface fills in the interface fields the Codex manifest doesn’t set. Codex itself reads the listing from only one of them, so keep it in one place: .codex-plugin/plugin.json, or extensions["com.openai"] in plugin.json.
A portable plugin’s skills are the folders in skills/ and its MCP servers are the ones in mcp.json, in Codex, OpenAI’s portal and Enconvo alike. skills and mcpServers in its plugin.json, in extensions["com.openai"] or in .codex-plugin/plugin.json are ignored, and plugin validate warns component_declaration_ignored. Only .claude-plugin/plugin.json still declares its own, since Claude Code reads it.
Name, title and description
Listing (interface)
Enconvo reads the same interface fields as Codex and OpenAI’s plugin portal, from .codex-plugin/plugin.json or from extensions["com.openai"].interface in a portable plugin.json.
Paths start at the plugin’s root, such as
./assets/logo.png. A link has to start with http:// or https:// to be shown.
A Claude Code plugin can give its icon and links as top-level fields in .claude-plugin/plugin.json, the way Anthropic’s plugin directory reads them. Enconvo uses each one when interface doesn’t set it, both for the installed plugin and for the plugin store:
plugin validate only warns about a bad one. Keep them in plugin.json, not in a marketplace entry, where claude plugin validate reports them as unknown.
OpenAI’s portal takes one of its own categories (Productivity, Creativity, Developer Tools, Business & Operations, Data & Analytics, Communication, Education & Research, Security, Finance, Healthcare, Travel, Entertainment or Other), example prompts that don’t @mention an MCP server, and screenshots only for a plugin with an MCP server. plugin validate --openai checks all three.
The plugin store sends the website, support, privacy policy and terms of service links with each version, so its page shows them before anyone installs the plugin. It sends only https:// links; plugin review flags the others. An Enconvo package.json can hold the same interface block for these links.
translations to extensions["com.openai"].publication, the text OpenAI’s portal imports for each locale. Enconvo shows the one for its interface language in place of shortDescription and longDescription: fr-FR or fr for French, zh-CN for Simplified Chinese and zh-TW or zh-HK for Traditional Chinese. Without a match, the English listing stays.
Onboarding skill
Setextensions["com.openai"].onboardingSkill to one of the plugin’s skills, and Enconvo offers to run it right after the plugin is installed for the first time. Use it to walk people through signing in or setting up.
Point it at the skill’s SKILL.md, such as ./skills/get-started/SKILL.md, which is what OpenAI’s portal takes. Enconvo also takes the skill’s folder.
MCP servers
Enconvo takes a plugin’s MCP servers from the first of these that has any:mcpServersin the manifest: the servers themselves, or a path to a file of them. In a portable plugin, only.claude-plugin/plugin.json’s counts.mcp.jsonat the root (the portable format)..mcp.jsonat the root (Claude Code).mcpServersinpackage.json.
mcpServers or mcp_servers, or be the list itself.
- Transports:
stdio,http(also writtenstreamable-httporstreamable_http) andsse. Servers of other types, such asws, are skipped. - Claude Code reads strictly: it drops a server with a
urlbut notype, an unknowntype, or a field of the wrong shape, such as an emptycommandor atimeoutthat isn’t a positive whole number. Itstoolsis a list of{"name", "permission_policy"}, while Codex’s is an object of tool settings by tool name, so a server with either form fails in the other app: give each its own MCP file, or leavetoolsout.enconvo plugin validatereports all of these (see MCP servers and apps). - Working folder: a relative
cwdstarts at the plugin’s folder. - Remote servers: use
https://(orwss://) for anything but this computer, and never commit a token or key inheaders: everyone who installs the plugin can read it. Reference a sensitive setting or an environment variable instead, such as"Authorization": "Bearer ${user_config.api_token}".enconvo plugin validatewarns about both, asclaude plugin validatedoes (see MCP servers and apps). - Variables in
command,args,env,cwd,urlandheaders:
Claude Code starts a local server from the current project, so its
.mcp.json names files through ${CLAUDE_PLUGIN_ROOT}. Codex and Enconvo start it from the plugin’s folder. When a plugin has both files, keep them in step.
When the plugin also has .codex-plugin/plugin.json, Codex doesn’t start the servers it declares (or those in .mcp.json) but reads their env_vars, and Enconvo does the same: a local server in mcp.json gets the variables its namesake there lists, except those with "source": "remote". An env entry that only forwards one of them, such as "API_TOKEN": "${API_TOKEN}", is dropped, so the server gets the variable when it’s set and doesn’t get a literal ${API_TOKEN} when it isn’t. enconvo plugin scan starts servers the same way.
Signing in to a remote server
A remote server that asks for OAuth gets a Sign in button, and Enconvo registers itself with the server’s authorization server. Limit what it asks for withscopes (Codex, a list) or oauth.scopes (Claude Code, one space-separated string).
When the authorization server doesn’t register clients, ship the OAuth app people sign in through, in either tool’s fields:
- Claude Code writes
clientIdandcallbackPort, and the sign-in comes back tohttp://localhost:<callbackPort>/callback. - Codex writes
client_id,callback_port,callback_urland, for a confidential app,client_secret. - Register the app’s redirect as written: Enconvo listens on that port while someone signs in, then finishes the sign-in itself. If another app is using the port, the sign-in asks the person to close it and try again.
- The redirect has to be an
httpaddress on the same computer (127.0.0.1,localhostor[::1]). - While the app’s fields still hold placeholders such as
<CLIENT_ID>or an unset${VAR}, Enconvo ignores the app and signs in the usual way.
oauth_resource(Codex, next tourl) is the resource the token is for. Enconvo sends it to the authorization server exactly as written, in the sign-in and in every renewal, so a server that compares it as a string (https://mcp.notion.comwithout a trailing slash) accepts the token. It has to be on the server’s own origin; otherwise Enconvo signs in without it.oauth.authServerMetadataUrl(Claude Code) is where the authorization server’s metadata is, for a server that doesn’t publish it. Enconvo reads it instead of looking at the server’s well-known addresses, and shows Sign in for the server even when they answer nothing. It has to usehttps://; Enconvo also readshttp://fromlocalhostwhile you test, which Claude Code doesn’t. When the address can’t be read, Enconvo looks the usual way.
enconvo plugin validate warns about an oauth_resource on another origin and an authServerMetadataUrl that isn’t https://.
Which tools run, and when Enconvo asks
Codex’s fields for a server’s tools work in Enconvo too:enabled_toolsoffers only the tools it lists;disabled_toolshides the ones it lists.default_tools_approval_modesets how all the server’s tools are approved, andtools.<tool>.approval_modesets it for one tool:
Each answer covers one call; Enconvo doesn’t offer to remember it for the chat. When the Permissions chip is Full access, Enconvo doesn’t ask.
enconvo plugin validate reports a mode it doesn’t know as an error, since Codex won’t load it, and warns about approve.
Skills
Enconvo loads everyskills/<name>/SKILL.md, the skill folders named by the manifest’s skills field (not in a portable plugin), and a SKILL.md at the plugin’s root. A skill’s SKILL.md starts with its name and a description of when to use it:
description that wraps over several lines, starts on the line below description:, uses quotes and escapes, or is folded with > reaches the agent as Claude Code shows it. The same goes for a Claude Code command’s frontmatter. A list becomes its text, as in Claude Code: argument-hint: [file] reads file, so quote it (argument-hint: "[file]") to keep the brackets. Frontmatter that isn’t valid YAML is still read a line at a time. enconvo plugin validate reports it, because Claude Code then loads the file without its description (see validation codes). A plugin installed before then picks up the new descriptions when it updates or is installed again.
An agents/openai.yaml next to it sets how Codex and Enconvo list the skill:
display_name becomes the skill’s title in Enconvo. With allow_implicit_invocation: false, or Claude Code’s disable-model-invocation: true in SKILL.md, the agent doesn’t pick the skill on its own and uses it only when someone asks for it.
From Enconvo 2.5.7, short_description is the line the / and $ menus and the SmartBar’s skill search show under the skill’s name, and those searches match it. Without it, Enconvo uses short-description under metadata in the SKILL.md frontmatter, as Codex does, and then the description:
short_description longer than 1,024 characters, and so does Enconvo. The agent always reads the full description to decide when to use the skill.
From Enconvo 2.5.7, the MCP servers a skill needs, listed under dependencies.tools the way Codex lists them, work in Enconvo too:
stdio, that command), whatever you named it, even if it’s switched off, and gives the agent its tools. As in Codex, a server counts by what it runs, not by its name. For a server you haven’t added, one that needs you to sign in, or one that can’t connect, the agent is told which it is and how to fix it, and asks you before it adds a server or opens a sign-in. Other transport values are skipped, as they are in Codex.
From Enconvo 2.5.7, two more Claude Code fields in SKILL.md work as they do in Claude Code:
when_to_useis added to the description the agent sees (description - when_to_use), and skill search matches it too.user-invocable: falsekeeps the skill out of the/and$menus and the SmartBar’s skill search, for skills that only make sense when the agent picks them. The agent still sees and uses it.
true, yes, on and 1 mean yes, and false, no, off and 0 mean no. Any other user-invocable value also hides the skill from the menus.
From Enconvo 2.5.7, the agent reads a skill with Claude Code’s variables filled in, so a skill can run its own scripts:
Only these names, written with braces, are filled in:
$HOME and other ${…} text reaches the agent as written. As in Claude Code, a name is filled in wherever it appears, including in examples.
Skills keep their plain names. When two plugins both ship a skill with the same name, such as configure, the agent sees each one as <plugin>:<name> (telegram:configure, discord:configure), the name Claude Code gives it. A skill that names another as /telegram:access loads that plugin’s copy.
Claude Code commands
From Enconvo 2.5.7, a Claude Code plugin’s commands install as skills, the way Claude Code treats them: everycommands/<name>.md. The file name is the skill’s name (a command in commands/git/push.md becomes git-push), and a skill with the same name wins. The description, or the file’s first line, describes it.
As in Claude Code, a commands field in the manifest replaces the commands/ folder: list the folders or .md files to install ([] installs none), or map each command’s name to a source file or inline content:
source and content; its description and argumentHint win over the file’s frontmatter. enconvo plugin validate reports entries Claude Code would reject, and warns when the field leaves out a commands/ folder the plugin ships. It also reads the frontmatter of every command, agent and skill as Claude Code does and reports YAML Claude Code can’t parse, since Claude Code then loads the file without its description and other fields (see validation codes).
${CLAUDE_PLUGIN_ROOT}and${CLAUDE_PLUGIN_DATA}become the plugin’s installed folder and its data folder, and${user_config.KEY}the plugin’s setting, as in skills.$ARGUMENTS,$1and namedargumentsstand for what the person wrote with the command, and the agent runs!`command`lines and```!blocks with its shell tool before it follows the rest.disable-model-invocation: true(or the olderhide-from-slash-command-tool: "true") makes it a skill only people start.user-invocable: falsekeeps it out of the/and$menus, andwhen_to_useis added to what the agent sees, as in skills.allowed-toolsandmodelare ignored: the agent keeps its usual tools, model and approvals.
<plugin>:<name>, as Claude Code names them (see Skills). A specific name such as commit-push-pr still reads better than help.
Agents
From Enconvo 2.5.7, a Claude Code plugin’s agents (everyagents/<name>.md, or only the .md files the manifest’s agents field lists, which replaces the folder as in Claude Code) become helpers that an agent can hand work to, in any chat running in Agent mode. They also appear in the composer’s @ menu after Explorer and Subagent. Enconvo reads the same frontmatter Claude Code does:
descriptiontells the agent when to use the helper.<example>blocks are left out of what it sees.- A
toolslist with only reading tools (Read,Grep,Glob,WebFetch, …), orpermissionMode: plan, makes the helper read-only. Other tool lists limit what it may call. modelis used when the chat’s model provider has a matching model, and the helper otherwise runs on the chat’s model.${CLAUDE_PLUGIN_ROOT}and${CLAUDE_PLUGIN_DATA}become the plugin’s installed folder and its data folder.
.claude/agents/, .codex/agents/ or .enconvo/agents/) keeps its name. A plugin helper whose name is already taken, by a project helper, another plugin or a built-in role such as explorer, gets the plugin’s name in front (pr_review_toolkit_code_reviewer). An agent is offered at most 20 helpers, the project’s first.
Hooks
A Claude Code or Codex plugin’s hooks run commands on the person’s Mac when something happens in a chat. Enconvo finds them where Claude Code and Codex do: the manifest’shooks field (a path, several paths, or the hooks themselves), extensions["com.openai"].hooks, hooks/hooks.json, and a hooks.json at the plugin’s root.
- Matchers: a tool event’s
matcheris a regular expression tested against the Enconvo tool’s name, title and command. ASessionStartmatcher that doesn’t matchstartupnever fires, since Enconvo only starts new chats. - Commands: a command that starts with
./runs the file in your plugin. Each command getsPLUGIN_ROOTandPLUGIN_DATA(and theCLAUDE_PLUGIN_ROOTandCLAUDE_PLUGIN_DATAnames) in its environment, the plugin’s settings asCLAUDE_PLUGIN_OPTION_<KEY>(see Settings), and the event as JSON on stdin. A hook that runs in a chat with a working folder starts there and gets it asCLAUDE_PROJECT_DIR. - Timeouts:
timeoutis in seconds, 60 by default and at most 600. A hook markedasyncstarts and isn’t waited for. - Skipped: other events (such as
PostToolBatchorPreCompact), hooks whosetypeisn’tcommand, hooks with anifpermission rule, and shell commands that name${user_config.KEY}don’t run in Enconvo. The preview and the plugin’s page list them.
hooks/hooks.json, which both read (Codex only when its manifest’s hooks names nothing else), and keep to what both take: command hooks with the whole command line in command, since Codex has no args, each ${CLAUDE_PLUGIN_ROOT} path in double quotes, a whole number of seconds in timeout, and only description and hooks at the top of the file. Hooks written in a manifest differ between the two: Claude Code’s hooks holds the events themselves, Codex’s a whole hooks file, {"hooks": {…}}. Claude Code doesn’t load a plugin whose hooks file isn’t valid JSON, and applies none of a file’s hooks when it can’t read one under PreToolUse or PermissionRequest; Codex skips a whole file over a top-level field other than description and hooks, or a hook type it doesn’t know. plugin validate reports each of these with what each app does (see Hooks).
Enconvo’s store takes a plugin with hooks, but OpenAI’s portal can’t take one yet, and neither can it take app references (apps or .app.json). plugin validate --openai flags both, so leave them out of the package you upload there.
Settings
Enconvo turns a Claude Code plugin’suserConfig, and a desktop extension’s user_config, into the plugin’s settings. Until required settings are filled in, the plugin shows Need configure on the Plugins page, and plugin install says which settings it needs.
.claude-plugin/plugin.json
-
Types:
stringbecomes a text field, or a password field whensensitive; astringwithoptionsbecomes a dropdown.number,boolean,fileanddirectorykeep their types, andrequired,defaultandmultiplecarry over. -
In MCP servers:
${user_config.KEY}in a server’scommand,args,env,cwd,urlorheadersbecomes the setting’s value. A setting with several values is joined with commas, or, when it’s the whole argument, becomes one argument per value. - Sensitive settings are filled in only as the server starts, so they never appear in Enconvo’s saved server settings.
- Missing settings: a server that needs a required setting you haven’t filled in doesn’t start, and says which setting to fill in. An optional setting left empty becomes an empty value.
- Changes: saving a setting on the plugin’s page restarts the servers that use it, with the new value.
-
Checks: Claude Code doesn’t load a plugin with a setting outside its rules, such as one without a
titleanddescription, or a${user_config.KEY}in a server for a key no setting declares. Enconvo is more lenient, so runenconvo plugin validateto catch them before you share the plugin (see Claude Code settings and channels). -
In skills and commands:
${user_config.KEY}in a skill’s or command’s text becomes the setting’s value when the agent loads it (see Skills). A sensitive setting stays out of the conversation. -
In hooks: each setting is in a hook’s environment as
CLAUDE_PLUGIN_OPTION_<KEY>, the key in capitals (api_tokenbecomesCLAUDE_PLUGIN_OPTION_API_TOKEN), with the saved value or the default.true/falseand numbers are written as text, and several values are joined with commas.${user_config.KEY}works in a hook’sargs, but not in a shellcommand, where a value could run as code: as in Claude Code, such a hook doesn’t run.
hooks/hooks.json
PreToolUse, Stop and SubagentStop). The other events’ hooks run without it, and a hook that names it in args doesn’t start for those events.
Channel settings
A Claude Code channel binds a message channel, such as a chat-app bridge, to one of the plugin’s MCP servers. Settings a channel asks for in its ownuserConfig appear on the plugin’s settings page after the plugin’s own, titled with the channel’s displayName (such as Telegram: Bot token), and fill ${user_config.KEY} only in that server’s env:
.claude-plugin/plugin.json
args, url and headers, like every other server, still use the plugin’s own settings. Hooks see a channel setting as CLAUDE_PLUGIN_OPTION_CHANNEL__<SERVER>__<KEY>.
What Enconvo doesn’t load
Check a package
plugin validate reads every format above and names each problem with a code. plugin scan starts the MCP servers the package declares, wherever it declares them, the way Enconvo starts them once the plugin is installed, so a server that works only when started from your project folder fails there too. See Validation Codes for what each one means, and Installing Plugins to try the package in Enconvo.