Plugins on your machines
A plugin's hooks and MCP servers are commands. Review them like code before you save a plugin.
Saving or removing one needs the orgplugins:manage capability (security_admin and the admin
token), so an operator can still write Skills but not plugins.
Save a plugin
Use the standard folders. Only .claude-plugin/plugin.json is required.
| Path | Holds |
|---|---|
.claude-plugin/plugin.json | A JSON object. name is the plugin id and version is its version |
commands/*.md, agents/*.md | Slash commands and subagents |
skills/<folder>/SKILL.md | Skills, each with a name and a description in its front matter |
hooks/hooks.json | Hooks, in Claude Code's hook shape |
.mcp.json | Local (stdio) MCP servers |
The body is one JSON object: the id, a display name, a description, the version, "kind": "plugin",
and files, a map of path to text.
curl -X PUT https://<your-anyray-gateway>/admin/v1/desktop-plugins \
-H "Authorization: Bearer $ANYRAY_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d @review-kit.json
{
"id": "review-kit",
"name": "Review kit",
"description": "Review helpers for the platform team.",
"version": "1.0.0",
"kind": "plugin",
"files": {
".claude-plugin/plugin.json": "{\"name\":\"review-kit\",\"version\":\"1.0.0\"}",
"commands/review.md": "---\ndescription: Review a diff\n---\nReview the diff."
}
}
Admin API keys cannot save or remove plugins, whatever scopes they hold. Use a signed-in admin or the admin token.
Machines pick the plugin up on their next sync. See check it arrived for the Claude Code and Claude Desktop checks, which read the same marketplace.
The gateway answers 400 with the reason when the plugin breaks a rule below, 403 without
orgplugins:manage, and 409 when a skill in the plugin would take the id of another item.
Which machines get what
Claude Code and Claude Desktop install the whole plugin from the gateway's marketplace. Other tools
that Connect writes skills for (Codex, Cursor and the rest) get the skills only: each
skills/<folder>/SKILL.md lands as a skill named <plugin>-<folder>, and a root SKILL.md lands
under the plugin id.
What the gateway checks on a save
nameinplugin.jsonequals the id, andversionequals the item version.hooksandmcpServersinplugin.jsonare inline or absent. A path to another file is refused.hooks/hooks.jsonholds ahooksmap of events to groups. Each group lists hooks that have atype, and acommandhook has acommand.- An id keeps its kind. Saving a Skill or Guidance item under a plugin's id, or a plugin under theirs, answers
409. Remove the old item first. - Item ids that differ only in letter case, or a skill folder that matches another item's id, are refused.
- Every
.mcp.jsonserver is stdio. Aurlor atypeother thanstdiois refused: add remote MCP servers under Connectors, where they get credentials and governance. - Every
skills/<folder>/SKILL.mdopens with front matter that sets a name and a description. Folder names use letters, digits, dots, underscores and hyphens. - Files are text, up to 256 KB per plugin and 2 MB for all Skills, Guidance and plugins together.
- Marketplace and archive URLs are public, so a value shaped like a key or token is refused. In
plugin.json,hooks.jsonand.mcp.jsonthat includes a key such asAPI_KEYorpasswordwith a literal value. Reference an environment variable instead, like${API_KEY}. - The archive stores every file without the execute bit. Start scripts through their interpreter, like
sh scripts/start.sh.
Machines that lock Claude Code hooks
When a machine's managed Claude Code settings set allowManagedHooksOnly, plugin hooks run only if
the managed enabledPlugins lists the plugin. Without that entry the plugin installs and its
hooks stay off.