> ## Documentation Index
> Fetch the complete documentation index at: https://docs.enconvo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin Formats

> The plugin packages Enconvo installs, including Codex, Claude Code and portable plugins, and the files and fields it reads from each.

Enconvo installs its own plugins and the plugins other AI apps use. A Codex or Claude Code plugin installs as it is, without changes, and one folder can be a plugin for Enconvo, Codex and Claude Code at the same time.

## Formats

| Format | Enconvo recognizes it by | What Enconvo loads |
| - | - | - |
| Enconvo plugin | `package.json` with Enconvo's `$schema` or a `commands` list | Commands, Local API routes, AI model and voice providers, settings, skills and MCP servers |
| Claude Code plugin | `.claude-plugin/plugin.json` | Skills, MCP servers, and settings from `userConfig` and `channels` |
| Codex plugin | `.codex-plugin/plugin.json` | Skills, MCP servers, and the listing in `interface` |
| Portable plugin ([Agent Plugins](https://agent-plugins.org)) | `plugin.json` at the root, with a `name` | Skills, MCP servers, and the listing in `extensions["com.openai"]` |
| Desktop extension (MCPB, formerly DXT) | `manifest.json` | Its MCP server, and `user_config` as settings |

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:

```sh theme={null}
npx -p @enconvo/api enconvo plugin create notes --template skills   # a skill
npx -p @enconvo/api enconvo plugin create notes --template mcp      # an MCP server
```

`--template command`, the default, makes an Enconvo plugin with a TypeScript command; see [Developing Extensions](/extensions/developing).

```text theme={null}
notes/
├── plugin.json                  # portable manifest: name, version, description
├── .codex-plugin/plugin.json    # the Codex listing (interface)
├── .claude-plugin/plugin.json   # the Claude Code manifest
├── skills/hello/
│   ├── SKILL.md
│   └── agents/openai.yaml       # the skill's name and example prompt
├── mcp.json                     # MCP servers for Codex and Enconvo (mcp template)
├── .mcp.json                    # the same servers for Claude Code (mcp template)
├── mcp/server.mjs               # a dependency-free MCP server (mcp template)
└── assets/icon.png
```

Keep `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.

<Note>
  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.
</Note>

## How Enconvo reads the manifests

When a folder has more than one `plugin.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

| Enconvo shows | From |
| - | - |
| Name (the plugin's id) | `name` |
| Title | `displayName` (Claude Code), then `interface.displayName`, then `name` |
| Description | `description`, then `interface.shortDescription`, then `interface.longDescription` |
| Version | `version` (`0.0.1` when missing) |
| Category | `category`, then `interface.category` |

### 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`.

| Field | Shown in Enconvo as |
| - | - |
| `shortDescription`, `longDescription` | The plugin's summary and details |
| `defaultPrompt` | Example prompts under **Try asking** (up to 4). A prompt that names the plugin as `@<displayName>` or `@<name>`, as Codex's do, shows the plugin there and opens the chat with it attached; any other prompt gets the plugin in front |
| `capabilities` | What the plugin can do (up to 8) |
| `developerName` | The developer; `author.name` when missing |
| `websiteURL` | The website; `homepage`, then `repository` when missing |
| `supportURL`, `privacyPolicyURL`, `termsOfServiceURL` | Links on the plugin's page |
| `brandColor`, `brandColorDark` | Accent colors, as hex such as `#0A84FF` |
| `logo`, `composerIcon`, `logoDark`, `composerIconDark` | The icon: the first of these that is an image inside the package. In dark mode, the Plugins page shows `logoDark` (else `composerIconDark`) instead |
| `screenshots` | Screenshots under the plugin's description (up to 8), each an image inside the package; click one to see it larger |

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:

| Claude Code field | Used as |
| - | - |
| `icon` | The icon, after the `interface` logos. A path inside the plugin, such as `./logo.png` |
| `documentationUrl` | The website, after `homepage` |
| `supportUrl`, `privacyPolicyUrl`, `termsOfServiceUrl` | `supportURL`, `privacyPolicyURL`, `termsOfServiceURL` |

```json theme={null}
{
  "name": "deploy-tools",
  "version": "1.0.0",
  "icon": "./logo.png",
  "documentationUrl": "https://example.com/docs",
  "supportUrl": "https://example.com/support",
  "privacyPolicyUrl": "https://example.com/privacy",
  "termsOfServiceUrl": "https://example.com/terms"
}
```

Claude Code ignores these fields when it loads the plugin, so `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.

```json theme={null}
{
  "name": "notes",
  "version": "1.2.0",
  "description": "Search and summarize your notes.",
  "author": { "name": "Ada Lovelace" },
  "interface": {
    "displayName": "Notes",
    "shortDescription": "Search your notes",
    "developerName": "Ada Lovelace",
    "category": "Productivity",
    "defaultPrompt": ["Find my notes about the Q3 launch"],
    "websiteURL": "https://example.com/notes",
    "privacyPolicyURL": "https://example.com/privacy",
    "brandColor": "#0A84FF",
    "logo": "./assets/icon.png"
  }
}
```

Write the listing in English. For other languages, add `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.

```json theme={null}
"extensions": {
  "com.openai": {
    "publication": {
      "translations": {
        "fr-FR": { "subtitle": "Cherchez vos notes", "description": "Cherchez vos notes depuis le chat." }
      }
    }
  }
}
```

### Onboarding skill

Set `extensions["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:

1. `mcpServers` in the manifest: the servers themselves, or a path to a file of them. In a portable plugin, only `.claude-plugin/plugin.json`'s counts.
2. `mcp.json` at the root (the portable format).
3. `.mcp.json` at the root (Claude Code).
4. `mcpServers` in `package.json`.

A file can list the servers under `mcpServers` or `mcp_servers`, or be the list itself.

```json theme={null}
{
  "mcpServers": {
    "notes": { "type": "stdio", "command": "node", "args": ["./mcp/server.mjs"], "cwd": "./" },
    "notes-cloud": { "type": "streamable-http", "url": "https://example.com/mcp" }
  }
}
```

* **Transports**: `stdio`, `http` (also written `streamable-http` or `streamable_http`) and `sse`. Servers of other types, such as `ws`, are skipped.
* **Claude Code reads strictly**: it drops a server with a `url` but no `type`, an unknown `type`, or a field of the wrong shape, such as an empty `command` or a `timeout` that isn't a positive whole number. Its `tools` is 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 leave `tools` out. `enconvo plugin validate` reports all of these (see [MCP servers and apps](/extensions/validation-codes#mcp-servers-and-apps)).
* **Working folder**: a relative `cwd` starts at the plugin's folder.
* **Remote servers**: use `https://` (or `wss://`) for anything but this computer, and never commit a token or key in `headers`: 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 validate` warns about both, as `claude plugin validate` does (see [MCP servers and apps](/extensions/validation-codes#mcp-servers-and-apps)).
* **Variables** in `command`, `args`, `env`, `cwd`, `url` and `headers`:

| Variable | Becomes |
| - | - |
| `${CLAUDE_PLUGIN_ROOT}`, `${CODEX_PLUGIN_ROOT}`, `${PLUGIN_ROOT}` | The plugin's installed folder |
| `${CLAUDE_PLUGIN_DATA}`, `${PLUGIN_DATA}` | A folder for the plugin's own data, kept across updates |
| `${NAME}`, `${NAME:-default}` | An environment variable, or the default when it isn't set |
| `${user_config.KEY}` | The plugin's setting `KEY` (see [Settings](#settings)) |

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 with `scopes` (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:

```json theme={null}
{
  "mcpServers": {
    "tracker": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "oauth": {
        "client_id": "your-app-id",
        "callback_port": 3118,
        "callback_url": "http://127.0.0.1:3118/callback"
      }
    }
  }
}
```

* Claude Code writes `clientId` and `callbackPort`, and the sign-in comes back to `http://localhost:<callbackPort>/callback`.
* Codex writes `client_id`, `callback_port`, `callback_url` and, 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 `http` address on the same computer (`127.0.0.1`, `localhost` or `[::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.

Two more fields tell the sign-in what the server itself doesn't say:

* `oauth_resource` (Codex, next to `url`) 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.com` without 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 use `https://`; Enconvo also reads `http://` from `localhost` while 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:

```json theme={null}
{
  "mcpServers": {
    "tracker": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "disabled_tools": ["delete_project"],
      "default_tools_approval_mode": "writes",
      "tools": {
        "search": { "approval_mode": "auto" },
        "send_invoice": { "approval_mode": "prompt" }
      }
    }
  }
}
```

* `enabled_tools` offers only the tools it lists; `disabled_tools` hides the ones it lists.
* `default_tools_approval_mode` sets how all the server's tools are approved, and `tools.<tool>.approval_mode` sets it for one tool:

| Mode | In Enconvo |
| - | - |
| `prompt` | Asks before every call. |
| `writes` | Asks before every call to a tool that doesn't declare `readOnlyHint: true`. |
| `auto` | Asks as for any tool: by its annotations and the Agent's [Permissions](/ai/agents) setting. |
| `approve` | The same as `auto`. Codex runs the tools without asking; in Enconvo only the person turns questions off, with the Permissions chip. |

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 every `skills/<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:

```markdown theme={null}
---
name: summarize-notes
description: Summarizes a set of notes into key points. Use when the user asks for a summary of their notes.
---

Instructions the agent follows when it uses the skill.
```

From Enconvo 2.5.7, Enconvo reads this frontmatter as YAML, the way Claude Code does. A `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](/extensions/validation-codes#claude-code-agents-and-commands)). 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:

```yaml theme={null}
interface:
  display_name: "Summarize Notes"
  short_description: "Key points from your notes"
  default_prompt: "Summarize my notes from this week"
policy:
  allow_implicit_invocation: false   # only when the user picks it
```

`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`:

```markdown theme={null}
---
name: summarize-notes
description: Summarizes the user's notes for a date range, grouped by project, with open questions and decisions listed separately.
metadata:
  short-description: Key points from your notes
---
```

Keep it short; Codex ignores a `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:

```yaml theme={null}
dependencies:
  tools:
    - type: "mcp"
      value: "linear"
      description: "Linear MCP server"
      transport: "streamable_http"   # the default; or "stdio" with a command
      url: "https://mcp.linear.app/mcp"
```

When the agent loads the skill, Enconvo connects the MCP server you added with that URL (or, for `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:

```markdown theme={null}
---
name: triage-crash
description: Reads a crash log and names the likely cause.
when_to_use: Use when the user pastes a stack trace or a crash report.
user-invocable: false
---
```

* `when_to_use` is added to the description the agent sees (`description - when_to_use`), and skill search matches it too.
* `user-invocable: false` keeps 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.

Yes-or-no fields are read as Claude Code reads them: `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:

```markdown theme={null}
Run `python3 ${CLAUDE_SKILL_DIR}/scripts/report.py --region ${user_config.region}`.
```

| Variable | Becomes |
| - | - |
| `${CLAUDE_SKILL_DIR}` | The skill's own folder, in any skill |
| `${CLAUDE_PLUGIN_ROOT}` | The plugin's installed folder |
| `${CLAUDE_PLUGIN_DATA}` | The plugin's data folder, kept across updates (created when a skill names it) |
| `${user_config.KEY}` | The plugin's setting `KEY`. A sensitive setting shows as `[hidden: the "<title>" setting]`, and one that's empty as `[not set: the "<title>" setting]` |

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: every `commands/<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`:

```json theme={null}
{
  "commands": {
    "status": { "source": "./commands/status.md", "description": "Show the deployment status", "argumentHint": "[env]" },
    "about": { "content": "Describe what this plugin does.", "description": "Describe this plugin" }
  }
}
```

An entry needs exactly one of `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](/extensions/validation-codes#claude-code-agents-and-commands)).

```markdown theme={null}
---
description: Create a git commit
argument-hint: [message]
---

Current status: !`git status`

Commit the staged changes with the message $ARGUMENTS.
```

* `${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](#skills).
* `$ARGUMENTS`, `$1` and named `arguments` stand 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 older `hide-from-slash-command-tool: "true"`) makes it a skill only people start.
* `user-invocable: false` keeps it out of the `/` and `$` menus, and `when_to_use` is added to what the agent sees, as in [skills](#skills).
* `allowed-tools` and `model` are ignored: the agent keeps its usual tools, model and approvals.

A plugin installed with an earlier Enconvo gets its commands the next time Enconvo starts.

When another plugin has a command or skill of the same name, the agent sees each one as `<plugin>:<name>`, as Claude Code names them (see [Skills](#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 (every `agents/<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:

```markdown theme={null}
---
name: code-reviewer
description: Reviews a diff for bugs. Use after editing code.
tools: Read, Grep, Glob
model: sonnet
---

Review the changes for bugs. Read ${CLAUDE_PLUGIN_ROOT}/checklist.md first.
```

* `description` tells the agent when to use the helper. `<example>` blocks are left out of what it sees.
* A `tools` list with only reading tools (`Read`, `Grep`, `Glob`, `WebFetch`, …), or `permissionMode: plan`, makes the helper read-only. Other tool lists limit what it may call.
* `model` is 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.

A project's own helper (in `.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's `hooks` 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.

```json theme={null}
{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [{ "type": "command", "command": "./scripts/guard.sh", "timeout": 10 }] }
    ],
    "SessionStart": [
      { "hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-start.mjs\"" }] }
    ]
  }
}
```

Because they run commands, a plugin's hooks install turned off. People allow them in the install preview, or later with the **Allow hooks** switch on the plugin's page. Allowing covers the hooks as they are: when an update changes them, they turn off again until the person checks the new commands and allows them.

| Event | When it runs |
| - | - |
| `SessionStart` | A new chat starts |
| `UserPromptSubmit` | A message is sent |
| `PreToolUse` | Before a tool runs; it can block the tool |
| `PermissionRequest` | A tool asks for approval |
| `PostToolUse` | After a tool runs |
| `Stop`, `SubagentStop` | The reply, or a subagent, finishes; it can ask the agent to continue |

* **Matchers**: a tool event's `matcher` is a regular expression tested against the Enconvo tool's name, title and command. A `SessionStart` matcher that doesn't match `startup` never fires, since Enconvo only starts new chats.
* **Commands**: a command that starts with `./` runs the file in your plugin. Each command gets `PLUGIN_ROOT` and `PLUGIN_DATA` (and the `CLAUDE_PLUGIN_ROOT` and `CLAUDE_PLUGIN_DATA` names) in its environment, the plugin's settings as `CLAUDE_PLUGIN_OPTION_<KEY>` (see [Settings](#settings)), and the event as JSON on stdin. A hook that runs in a chat with a working folder starts there and gets it as `CLAUDE_PROJECT_DIR`.
* **Timeouts**: `timeout` is in seconds, 60 by default and at most 600. A hook marked `async` starts and isn't waited for.
* **Skipped**: other events (such as `PostToolBatch` or `PreCompact`), hooks whose `type` isn't `command`, hooks with an `if` permission rule, and shell commands that name `${user_config.KEY}` don't run in Enconvo. The preview and the plugin's page list them.

For hooks that also work in Claude Code and Codex, put them in `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](/extensions/validation-codes#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's `userConfig`, 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.

```json .claude-plugin/plugin.json theme={null}
{
  "name": "my-database",
  "userConfig": {
    "region": { "type": "string", "title": "Region", "description": "Where your database runs", "options": ["us-east1", "europe-west1"], "default": "us-east1" },
    "password": { "type": "string", "title": "Password", "description": "Your database password", "sensitive": true, "required": true }
  },
  "mcpServers": {
    "db": {
      "command": "npx",
      "args": ["-y", "my-database-mcp", "--region", "${user_config.region}"],
      "env": { "DB_PASSWORD": "${user_config.password}" }
    }
  }
}
```

* **Types**: `string` becomes a text field, or a password field when `sensitive`; a `string` with `options` becomes a dropdown. `number`, `boolean`, `file` and `directory` keep their types, and `required`, `default` and `multiple` carry over.

* **In MCP servers**: `${user_config.KEY}` in a server's `command`, `args`, `env`, `cwd`, `url` or `headers` becomes 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 `title` and `description`, or a `${user_config.KEY}` in a server for a key no setting declares. Enconvo is more lenient, so run `enconvo plugin validate` to catch them before you share the plugin (see [Claude Code settings and channels](/extensions/validation-codes#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](#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_token` becomes `CLAUDE_PLUGIN_OPTION_API_TOKEN`), with the saved value or the default. `true`/`false` and numbers are written as text, and several values are joined with commas. `${user_config.KEY}` works in a hook's `args`, but not in a shell `command`, where a value could run as code: as in Claude Code, such a hook doesn't run.

```json hooks/hooks.json theme={null}
{
  "hooks": {
    "Stop": [
      { "hooks": [{ "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/report.mjs", "--region=${user_config.region}"] }] }
    ]
  }
}
```

A sensitive setting reaches the hooks that can change what the agent does (`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 own `userConfig` 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`:

```json .claude-plugin/plugin.json theme={null}
{
  "name": "chat-bridge",
  "mcpServers": {
    "telegram": {
      "command": "node",
      "args": ["${CLAUDE_PLUGIN_ROOT}/server.js"],
      "env": { "BOT_TOKEN": "${user_config.bot_token}" }
    }
  },
  "channels": [
    {
      "server": "telegram",
      "displayName": "Telegram",
      "userConfig": {
        "bot_token": { "type": "string", "title": "Bot token", "description": "Telegram bot token", "sensitive": true, "required": true }
      }
    }
  ]
}
```

A channel's setting can share its key with one of the plugin's own settings or another channel's: each is saved apart, and the server's `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

| Part | What happens |
| - | - |
| ChatGPT apps (`.app.json`) | The plugin's page says it needs a ChatGPT app, and Enconvo installs the rest of the plugin. An app counts as needed unless its entry says `"required": false` or `"optional": true`, as OpenAI's own plugins mark the apps they can do without. |
| Claude Code LSP servers, monitors, output styles, workflows, themes and syntax highlighting languages (`lspServers`, `.lsp.json`, `experimental.monitors`, `outputStyles`, `workflows`, `experimental.themes`, `experimental.syntaxHighlighting`) | Not loaded; Enconvo installs the rest of the plugin. `plugin validate` still checks them as Claude Code does, so the plugin keeps loading there, and warns that Enconvo skips them (`plugin_component_not_loaded`). Declare themes and monitors under `experimental`, and keep `${user_config.*}` out of a monitor's command: Claude Code won't start it. |
| Claude Code `binaries` | Not fetched. Claude Code downloads the files `binaries` lists into the plugin's `bin/` when it installs the plugin; Enconvo installs the plugin without them, so hooks and servers that run them work only in Claude Code (`plugin_binaries_not_fetched`). |

## Check a package

```sh theme={null}
npx -p @enconvo/api enconvo plugin validate            # Enconvo, Codex and Claude Code rules
npx -p @enconvo/api enconvo plugin validate --openai   # also OpenAI's portal rules
npx -p @enconvo/api enconvo plugin scan                # starts the MCP servers and checks their tools
```

`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](/extensions/validation-codes) for what each one means, and [Installing Plugins](/extensions/installing-plugins) to try the package in Enconvo.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.