Skip to main content

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)
  • curl or 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/entries path

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:

note

The example below omits claims. When authentication is enabled, requests must also include a top-level claims object - see Claims below.

Publish a plugin
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:

List all plugins
curl https://registry.example.com/registry/my-registry/v0.1/x/dev.toolhive/plugins

Query parameters

ParameterTypeDefaultDescription
searchstring-Filter by name or description substring
statusstring-Filter by status (comma-separated: active, deprecated, archived)
limitint50Maximum results per page (1-100)
cursorstring-Pagination cursor from a previous response

Search example

Search for plugins by keyword
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

Get the latest version of a plugin
curl https://registry.example.com/registry/my-registry/v0.1/x/dev.toolhive/plugins/io.github.acme/release-toolkit

Get a specific version

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

List all versions of a plugin
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:

Delete a plugin version
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.

warning

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:

CodeMeaning
400Invalid request (missing required fields, invalid parameters)
401Authentication required
403Insufficient permissions or claims, or registry is read-only
404Registry, plugin, or version not found
409Version already exists
500Internal server error

Next steps