Manage plugins
The Registry Server exposes plugins through an extensions API and accepts plugin
entries from the same sources as MCP servers and skills. A plugin is a
Claude Code plugin bundle: a directory declared by a
.claude-plugin/plugin.json manifest that packages slash commands, subagents,
Agent Skills, hooks, MCP server configs, and LSP servers together for
distribution. This guide covers the full lifecycle: publishing, listing,
retrieving, and deleting plugins.
Plugins can come from two paths:
- Publish to a managed source via the admin API (covered in depth below). Good for programmatic or UI-driven publishing with per-version control.
- Sync from external sources (Git, upstream API, or file) that contain plugins in the ToolHive registry format. Good for catalogs that live in a repository with code review and CI.
For the file-based path, the registry JSON format is identical to the one used
for MCP servers and skills: plugins go under data.plugins alongside
data.servers and data.skills. See
Publish MCP servers for the file
format, Git repository layout, and how changes reach the server. The rest of
this page focuses on the admin API.
Prerequisites
- A running Registry Server with at least one managed source configured (required for publishing via the API; synced sources provide plugins automatically)
curlor another HTTP client- If authentication is enabled, a valid bearer token (see Authentication)
API paths
Plugins use two API path families:
- Browse endpoints (list, get, search) use the registry-scoped path:
/registry/{registryName}/v0.1/x/dev.toolhive/plugins - Admin endpoints (publish, delete) use the
/v1/entriespath
Replace {registryName} with the name of the registry as defined in your
configuration file (for example, my-registry if your
config has registries: [{name: my-registry, ...}]).
Publish a plugin
To publish a new plugin version, send a POST request to the /v1/entries
endpoint with the plugin metadata wrapped in a plugin object:
The example below omits claims. When authentication is enabled, requests must
also include a top-level claims object - see Claims below.
curl -X POST \
https://registry.example.com/v1/entries \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <TOKEN>" \
-d '{
"plugin": {
"namespace": "io.github.acme",
"name": "release-toolkit",
"description": "Release automation commands, hooks, and Agent Skills",
"version": "1.0.0",
"status": "active",
"title": "Release Toolkit",
"license": "Apache-2.0",
"packages": [
{
"registryType": "oci",
"identifier": "ghcr.io/acme/plugins/release-toolkit:1.0.0"
}
],
"repository": {
"url": "https://github.com/acme/plugins",
"type": "git"
}
}
}'
Required fields in the plugin object: namespace, name, version
A successful response returns 201 Created with the published plugin. If the
version already exists, the server returns 409 Conflict.
The status field accepts active, deprecated, or archived (case
insensitive). The server normalizes status values to uppercase internally.
Claims
When authentication is enabled, publish requests must
include a claims object. Skipping it returns 400 Bad Request. Attach claims
at the top level of the request body, alongside the plugin object:
{
"plugin": {
"namespace": "io.github.acme",
"name": "release-toolkit",
"version": "1.0.0"
},
"claims": { "org": "acme", "team": "platform" }
}
Publishing requires the manageEntries role. Your JWT must satisfy the managed
source's claims, and the publish claims must be a subset of your JWT claims. All
subsequent versions of the same plugin must carry the same claims as the first
version. See
Claims on published entries
for details.
Versioning behavior
When you publish a new version, the registry compares it against the current
latest version. If the new version is newer, the latest pointer updates
automatically. Publishing an older version (for example, backfilling 0.9.0
after 1.0.0 exists) does not change the latest pointer.
List plugins
To list plugins in a registry:
curl https://registry.example.com/registry/my-registry/v0.1/x/dev.toolhive/plugins
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
search | string | - | Filter by name or description substring |
status | string | - | Filter by status (comma-separated: active, deprecated, archived) |
limit | int | 50 | Maximum results per page (1-100) |
cursor | string | - | Pagination cursor from a previous response |
Search example
curl "https://registry.example.com/registry/my-registry/v0.1/x/dev.toolhive/plugins?search=release&limit=10"
Response format
{
"plugins": [
{
"namespace": "io.github.acme",
"name": "release-toolkit",
"description": "Release automation commands, hooks, and Agent Skills",
"version": "1.0.0",
"status": "ACTIVE",
"title": "Release Toolkit",
"license": "Apache-2.0",
"packages": [
{
"registryType": "oci",
"identifier": "ghcr.io/acme/plugins/release-toolkit:1.0.0"
}
],
"repository": {
"url": "https://github.com/acme/plugins",
"type": "git"
}
}
],
"metadata": {
"count": 1,
"nextCursor": ""
}
}
Use the nextCursor value to fetch the next page of results. An empty
nextCursor indicates there are no more pages.
Retrieve a plugin
Get the latest version
curl https://registry.example.com/registry/my-registry/v0.1/x/dev.toolhive/plugins/io.github.acme/release-toolkit
Get a specific version
curl https://registry.example.com/registry/my-registry/v0.1/x/dev.toolhive/plugins/io.github.acme/release-toolkit/versions/1.0.0
List all versions
curl https://registry.example.com/registry/my-registry/v0.1/x/dev.toolhive/plugins/io.github.acme/release-toolkit/versions
Delete a plugin version
To delete a specific version, use the /v1/entries endpoint:
curl -X DELETE \
"https://registry.example.com/v1/entries/plugin/io.github.acme%2Frelease-toolkit/versions/1.0.0" \
-H "Authorization: Bearer <TOKEN>"
The plugin name includes the namespace prefix. URL-encode the slash as %2F so
the full namespace/name is captured as a single {name} path parameter.
A successful delete returns 204 No Content. Deleting a non-existent version
returns 404 Not Found.
Deleting a plugin version is permanent. If the deleted version was the latest, the server automatically reassigns the latest pointer to the next-highest remaining version.
Error responses
The API returns standard HTTP status codes:
| Code | Meaning |
|---|---|
| 400 | Invalid request (missing required fields, invalid parameters) |
| 401 | Authentication required |
| 403 | Insufficient permissions or claims, or registry is read-only |
| 404 | Registry, plugin, or version not found |
| 409 | Version already exists |
| 500 | Internal server error |
Next steps
- Configure authentication to secure access to your registry
Related information
- Manage skills - the parallel API for the skills extension
- Registry Server introduction