From f09792fd399fbadb66ba7840a762188dfb2a89c3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:42:32 +0000 Subject: [PATCH 1/5] Update stacklok/toolhive-registry-server to v1.5.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/upstream-projects.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/upstream-projects.yaml b/.github/upstream-projects.yaml index 4a2f7e91..64fb0028 100644 --- a/.github/upstream-projects.yaml +++ b/.github/upstream-projects.yaml @@ -29,7 +29,7 @@ projects: - id: toolhive-registry-server repo: stacklok/toolhive-registry-server - version: v1.5.0 + version: v1.5.1 docs_paths: - docs/toolhive/guides-registry - docs/toolhive/concepts/registry-criteria.mdx From e63973cfe4f90308c7462e3130bf92181eb9bad5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 4 Aug 2026 18:43:20 +0000 Subject: [PATCH 2/5] Refresh reference assets for toolhive-registry-server v1.5.1 --- static/api-specs/toolhive-registry-api.yaml | 410 +++++++++++++++++++- 1 file changed, 405 insertions(+), 5 deletions(-) diff --git a/static/api-specs/toolhive-registry-api.yaml b/static/api-specs/toolhive-registry-api.yaml index 87982090..32cdaaa7 100644 --- a/static/api-specs/toolhive-registry-api.yaml +++ b/static/api-specs/toolhive-registry-api.yaml @@ -167,6 +167,86 @@ components: version: type: string type: object + github_com_stacklok_toolhive-registry-server_internal_service.Plugin: + properties: + _meta: + additionalProperties: {} + type: object + createdAt: + type: string + description: + type: string + icons: + items: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.PluginIcon' + type: array + uniqueItems: false + id: + type: string + isLatest: + type: boolean + license: + type: string + metadata: + additionalProperties: {} + type: object + name: + type: string + namespace: + type: string + packages: + items: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.PluginPackage' + type: array + uniqueItems: false + repository: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.PluginRepository' + status: + type: string + title: + type: string + updatedAt: + type: string + version: + type: string + type: object + github_com_stacklok_toolhive-registry-server_internal_service.PluginIcon: + properties: + label: + type: string + size: + type: string + src: + type: string + type: + type: string + type: object + github_com_stacklok_toolhive-registry-server_internal_service.PluginPackage: + properties: + commit: + type: string + digest: + type: string + identifier: + type: string + mediaType: + type: string + ref: + type: string + registryType: + type: string + subfolder: + type: string + url: + type: string + type: object + github_com_stacklok_toolhive-registry-server_internal_service.PluginRepository: + properties: + type: + type: string + url: + type: string + type: object github_com_stacklok_toolhive-registry-server_internal_service.RegistryCreateRequest: properties: claims: @@ -413,6 +493,9 @@ components: phase: description: complete, syncing, failed type: string + pluginCount: + description: Number of plugins in registry + type: integer serverCount: description: Number of servers in registry type: integer @@ -441,6 +524,8 @@ components: claims: additionalProperties: {} type: object + plugin: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.Plugin' server: $ref: '#/components/schemas/v0.ServerJSON' skill: @@ -460,6 +545,23 @@ components: additionalProperties: {} type: object type: object + internal_api_x_plugins.PluginListMetadata: + properties: + count: + type: integer + nextCursor: + type: string + type: object + internal_api_x_plugins.PluginListResponse: + properties: + metadata: + $ref: '#/components/schemas/internal_api_x_plugins.PluginListMetadata' + plugins: + items: + $ref: '#/components/schemas/registry.Plugin' + type: array + uniqueItems: false + type: object internal_api_x_skills.SkillListMetadata: properties: count: @@ -710,6 +812,66 @@ components: $ref: '#/components/schemas/model.Input' type: object type: object + registry.Plugin: + properties: + _meta: + additionalProperties: {} + description: Meta is an opaque payload with extended meta data details of + the plugin. + type: object + description: + description: Description is the description of the plugin. + type: string + icons: + description: Icons is the list of icons for the plugin. + items: + $ref: '#/components/schemas/registry.SkillIcon' + type: array + uniqueItems: false + license: + description: License is the SPDX license identifier of the plugin. + type: string + metadata: + additionalProperties: {} + description: |- + Metadata is the official metadata of the plugin as reported in the + plugin manifest file. + type: object + name: + description: |- + Name is the name of the plugin. + The format is that of identifiers, e.g. "my-plugin". + type: string + namespace: + description: |- + Namespace is the namespace of the plugin. + The format is reverse-DNS, e.g. "io.github.user". + type: string + packages: + description: Packages is the list of packages for the plugin. + items: + $ref: '#/components/schemas/registry.SkillPackage' + type: array + uniqueItems: false + repository: + $ref: '#/components/schemas/registry.SkillRepository' + status: + description: |- + Status is the status of the plugin. + Can be one of "active", "deprecated", or "archived". + type: string + title: + description: |- + Title is the title of the plugin. + This is for human consumption, not an identifier. + type: string + version: + description: |- + Version is the version of the plugin. + Any non-empty string is valid, but ideally it should be either a + semantic version or a commit hash. + type: string + type: object registry.Skill: properties: _meta: @@ -1169,6 +1331,244 @@ paths: summary: Get specific MCP server version in specific registry tags: - registry + /registry/{registryName}/v0.1/x/dev.toolhive/plugins: + get: + description: List plugins in a registry (paginated, latest versions). + parameters: + - description: Registry name + in: path + name: registryName + required: true + schema: + type: string + - description: Filter by name/description substring + in: query + name: search + schema: + type: string + - description: Filter by status (comma-separated, e.g. active,deprecated) + in: query + name: status + schema: + type: string + - description: Max results (default 50, max 100) + in: query + name: limit + schema: + type: integer + - description: Pagination cursor + in: query + name: cursor + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_api_x_plugins.PluginListResponse' + description: List of plugins + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + security: + - BearerAuth: [] + summary: List plugins in registry + tags: + - plugins + /registry/{registryName}/v0.1/x/dev.toolhive/plugins/{namespace}/{name}: + get: + description: Get the latest version of a plugin by namespace and name. + parameters: + - description: Registry name + in: path + name: registryName + required: true + schema: + type: string + - description: Plugin namespace (reverse-DNS) + in: path + name: namespace + required: true + schema: + type: string + - description: Plugin name + in: path + name: name + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/registry.Plugin' + description: Plugin details + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Plugin not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + security: + - BearerAuth: [] + summary: Get latest plugin version + tags: + - plugins + /registry/{registryName}/v0.1/x/dev.toolhive/plugins/{namespace}/{name}/versions: + get: + description: List all versions of a plugin. + parameters: + - description: Registry name + in: path + name: registryName + required: true + schema: + type: string + - description: Plugin namespace (reverse-DNS) + in: path + name: namespace + required: true + schema: + type: string + - description: Plugin name + in: path + name: name + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_api_x_plugins.PluginListResponse' + description: List of plugin versions + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Plugin not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + security: + - BearerAuth: [] + summary: List plugin versions + tags: + - plugins + /registry/{registryName}/v0.1/x/dev.toolhive/plugins/{namespace}/{name}/versions/{version}: + get: + description: Get a specific version of a plugin. + parameters: + - description: Registry name + in: path + name: registryName + required: true + schema: + type: string + - description: Plugin namespace (reverse-DNS) + in: path + name: namespace + required: true + schema: + type: string + - description: Plugin name + in: path + name: name + required: true + schema: + type: string + - description: Plugin version + in: path + name: version + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/registry.Plugin' + description: Plugin details + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Plugin or version not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + security: + - BearerAuth: [] + summary: Get specific plugin version + tags: + - plugins /registry/{registryName}/v0.1/x/dev.toolhive/skills: get: description: List skills in a registry (paginated, latest versions). @@ -1409,8 +1809,8 @@ paths: - skills /v1/entries: post: - description: Publish a new server or skill entry. Exactly one of 'server' or - 'skill' must be provided. + description: Publish a new server, skill, or plugin entry. Exactly one of 'server', + 'skill', or 'plugin' must be provided. requestBody: content: application/json: @@ -1418,9 +1818,9 @@ paths: oneOf: - type: object - $ref: '#/components/schemas/internal_api_v1.publishEntryRequest' - description: Entry to publish (server or skill) + description: Entry to publish (server, skill, or plugin) summary: request - description: Entry to publish (server or skill) + description: Entry to publish (server, skill, or plugin) required: true responses: "201": @@ -1603,7 +2003,7 @@ paths: delete: description: Delete a published entry version parameters: - - description: Entry Type (server or skill) + - description: Entry Type (server, skill, or plugin) in: path name: type required: true From cadfdfac3bebbed6b0a56bb569b8dabf973d4e57 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:54:37 +0000 Subject: [PATCH 3/5] Document registry-server v1.5.1 plugins API and metrics rename - Add Manage plugins guide for the new /x/dev.toolhive/plugins extension API and plugin payload on /v1/entries; wire it into the Registry Server sidebar. - Rewrite Telemetry and metrics for the stacklok_registry_* / OTel-semconv rename, the /metrics endpoint on :8081, the new constant labels, and the new plugins/errors/build-info series. - Add plugin.* audit event rows in Audit logging. - Extend Publish MCP servers to cover the data.plugins array. - Sweep 'servers and skills' -> 'servers, skills, and plugins' across the intro, index, configuration, and authorization pages. Co-Authored-By: Claude Opus 4.7 --- SUMMARY.md | 5 + .../guides-registry/audit-logging.mdx | 10 + .../guides-registry/authorization.mdx | 32 +-- .../guides-registry/configuration.mdx | 18 +- docs/toolhive/guides-registry/index.mdx | 10 +- docs/toolhive/guides-registry/intro.mdx | 35 +-- docs/toolhive/guides-registry/plugins.mdx | 254 ++++++++++++++++++ .../guides-registry/publish-servers.mdx | 17 +- docs/toolhive/guides-registry/skills.mdx | 1 + .../guides-registry/telemetry-metrics.mdx | 85 ++++-- sidebars.ts | 1 + 11 files changed, 395 insertions(+), 73 deletions(-) create mode 100644 SUMMARY.md create mode 100644 docs/toolhive/guides-registry/plugins.mdx diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 00000000..b1486577 --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,5 @@ +- Added new plugins guide at `docs/toolhive/guides-registry/plugins.mdx` covering the `/registry/{name}/v0.1/x/dev.toolhive/plugins` extension API and the `plugin` payload on `/v1/entries`, wired into the Registry Server sidebar. +- Rewrote `docs/toolhive/guides-registry/telemetry-metrics.mdx` for the `stacklok_registry_*` / OTel-semconv rename: renamed every metric, updated labels (`registry` → `source`, `success` bool → `outcome` label), documented the new `/metrics` scrape endpoint on `:8081`, the constant `stacklok_component` / `stacklok_product` labels, and the new `stacklok_registry_plugins`, `stacklok_registry_errors_total`, and `stacklok_build_info_ratio` series. +- Updated `docs/toolhive/guides-registry/audit-logging.mdx` to document the new `plugin.list` / `plugin.read` / `plugin.versions.list` / `plugin.version.read` events and include `plugin.list` in the high-frequency-read exclusion example. +- Updated `docs/toolhive/guides-registry/publish-servers.mdx` to describe the `data.plugins` array in the registry file format and cross-link to the new plugins guide. +- Swept "MCP servers and skills" → "MCP servers, skills, and plugins" across `intro.mdx`, `index.mdx`, `configuration.mdx`, and `authorization.mdx` where the release now includes plugins. diff --git a/docs/toolhive/guides-registry/audit-logging.mdx b/docs/toolhive/guides-registry/audit-logging.mdx index 0a01612e..4e2fcfa3 100644 --- a/docs/toolhive/guides-registry/audit-logging.mdx +++ b/docs/toolhive/guides-registry/audit-logging.mdx @@ -96,6 +96,15 @@ Security events: | `skill.versions.list` | Skill version list requested | | `skill.version.read` | Specific skill version retrieved | +### Plugins extension API (`/x/dev.toolhive/plugins`) + +| Event type | Trigger | +| ---------------------- | --------------------------------- | +| `plugin.list` | Plugin list requested | +| `plugin.read` | Plugin retrieved | +| `plugin.versions.list` | Plugin version list requested | +| `plugin.version.read` | Specific plugin version retrieved | + ## Filter events By default, every event type is logged. Use `eventTypes` and `excludeEventTypes` @@ -129,6 +138,7 @@ audit: - registry.list - server.list - skill.list + - plugin.list - user.info ``` diff --git a/docs/toolhive/guides-registry/authorization.mdx b/docs/toolhive/guides-registry/authorization.mdx index 9d71e1d5..20828e93 100644 --- a/docs/toolhive/guides-registry/authorization.mdx +++ b/docs/toolhive/guides-registry/authorization.mdx @@ -78,12 +78,12 @@ auth: ### Available roles -| Role | Grants access to | -| ------------------ | -------------------------------------------------------------------- | -| `superAdmin` | All operations; bypasses all claim checks | -| `manageSources` | Create, update, delete, and list sources via the admin API | -| `manageRegistries` | Create, update, delete, and list registries via the admin API | -| `manageEntries` | Publish, delete, and manage claims on MCP server versions and skills | +| Role | Grants access to | +| ------------------ | ------------------------------------------------------------------------------ | +| `superAdmin` | All operations; bypasses all claim checks | +| `manageSources` | Create, update, delete, and list sources via the admin API | +| `manageRegistries` | Create, update, delete, and list registries via the admin API | +| `manageEntries` | Publish, delete, and manage claims on MCP server versions, skills, and plugins | ### Role rule matching @@ -129,8 +129,8 @@ satisfy the resource's claims can access it. ### Source claims For synced sources (Git, API, File), claims on a source are **inherited by all -entries** during sync. Every MCP server or skill ingested from that source -carries the source's claims. +entries** during sync. Every MCP server, skill, or plugin ingested from that +source carries the source's claims. For Kubernetes and managed sources, source claims are **not** inherited by entries. Kubernetes entries get claims from the @@ -339,10 +339,10 @@ server payload schema, including `packages`, `remotes`, and `_meta` fields. Use `GET /v1/entries/{type}/{name}/claims` to fetch the claims on a previously published entry, and `PUT /v1/entries/{type}/{name}/claims` to change them. The -`type` path parameter is either `server` or `skill`, and the endpoints require -the `manageEntries` role. Because entry names contain a slash separating -namespace from server name, URL-encode the slash as `%2F` so the path is treated -as a single `{name}` path parameter. +`type` path parameter is `server`, `skill`, or `plugin`, and the endpoints +require the `manageEntries` role. Because entry names contain a slash separating +namespace from name, URL-encode the slash as `%2F` so the path is treated as a +single `{name}` path parameter. ```bash title="Read claims on a published server" curl -X GET \ @@ -405,10 +405,10 @@ registries are also scoped by claims: caller can't see the resource. This prevents information disclosure about resources the caller cannot access. - **List source/registry entries**: `GET /v1/sources/{name}/entries` and - `GET /v1/registries/{name}/entries` return the raw entries (servers and skills - with versions and claims) in a source or registry. These endpoints follow the - same claim scoping: the parent source or registry must be accessible to the - caller. + `GET /v1/registries/{name}/entries` return the raw entries (servers, skills, + and plugins with versions and claims) in a source or registry. These endpoints + follow the same claim scoping: the parent source or registry must be + accessible to the caller. - **Create source/registry**: The request claims must be a subset of the caller's JWT claims (subset rule). - **Update source/registry**: The caller must be able to see the existing diff --git a/docs/toolhive/guides-registry/configuration.mdx b/docs/toolhive/guides-registry/configuration.mdx index b15f2f90..1005ae5b 100644 --- a/docs/toolhive/guides-registry/configuration.mdx +++ b/docs/toolhive/guides-registry/configuration.mdx @@ -319,8 +319,8 @@ The fields `path` and `url` are mutually exclusive. ### Managed source -API-managed source for directly publishing and deleting MCP servers and skills -via the API. Does not sync from external sources. +API-managed source for directly publishing and deleting MCP servers, skills, and +plugins via the API. Does not sync from external sources. ```yaml title="config-managed.yaml" sources: @@ -349,13 +349,15 @@ exists in the database. **Supported operations:** -- `POST /v1/entries` - Publish new server or skill versions +- `POST /v1/entries` - Publish new server, skill, or plugin versions - `DELETE /v1/entries/{type}/{name}/versions/{version}` - Delete a specific version - `PUT /v1/entries/{type}/{name}/claims` - Update authorization claims on a published entry -- `GET /registry/{registryName}/v0.1/...` - List and retrieve servers and skills -- [Skills management](./skills.mdx) via the extensions API +- `GET /registry/{registryName}/v0.1/...` - List and retrieve servers, skills, + and plugins +- [Skills management](./skills.mdx) and [plugins management](./plugins.mdx) via + the extensions API When authentication is enabled, the caller needs the `manageEntries` role, and publish requests must include a `claims` object whose values are backed by a JWT @@ -566,9 +568,9 @@ configuration. Managed and Kubernetes sources do not require sync policies. ## Entry filtering Optionally filter which entries are exposed through the API on a per-source -basis. Only applicable to synced sources (Git, API, File). Filters apply to both -MCP servers and skills. Skills are matched using `namespace/name` against the -name patterns. +basis. Only applicable to synced sources (Git, API, File). Filters apply to MCP +servers, skills, and plugins. Skills and plugins are matched using +`namespace/name` against the name patterns. ```yaml sources: diff --git a/docs/toolhive/guides-registry/index.mdx b/docs/toolhive/guides-registry/index.mdx index 6c3a3767..bccf1489 100644 --- a/docs/toolhive/guides-registry/index.mdx +++ b/docs/toolhive/guides-registry/index.mdx @@ -1,8 +1,8 @@ --- title: ToolHive Registry Server description: - Deploy, configure, and secure the ToolHive Registry Server for MCP server and - skill discovery + Deploy, configure, and secure the ToolHive Registry Server for MCP server, + skill, and plugin discovery --- import DocCardList from '@theme/DocCardList'; @@ -10,9 +10,9 @@ import DocCardList from '@theme/DocCardList'; ## Introduction The ToolHive Registry Server helps you publish and govern a catalog of MCP -servers or skills for your team. Use it when you want a trusted catalog that -aggregates entries from Kubernetes clusters, Git repositories, API endpoints, -and local files behind a standard MCP Registry API. +servers, skills, and plugins for your team. Use it when you want a trusted +catalog that aggregates entries from Kubernetes clusters, Git repositories, API +endpoints, and local files behind a standard MCP Registry API. Within the ToolHive platform, the Registry Server is the self-hosted catalog service. It is separate from the built-in registry in the ToolHive CLI and UI. diff --git a/docs/toolhive/guides-registry/intro.mdx b/docs/toolhive/guides-registry/intro.mdx index 2e627b13..81eaf9b2 100644 --- a/docs/toolhive/guides-registry/intro.mdx +++ b/docs/toolhive/guides-registry/intro.mdx @@ -6,9 +6,9 @@ description: --- The ToolHive Registry Server gives you a way to publish, curate, and control a -catalog of MCP servers and skills for your organization. Use it when you want a -trusted catalog for discoverability, team-scoped access, publishing, or -multi-source aggregation. +catalog of MCP servers, skills, and plugins for your organization. Use it when +you want a trusted catalog for discoverability, team-scoped access, publishing, +or multi-source aggregation. It pulls entries from multiple sources, stores them centrally, and exposes them through a standard MCP Registry API that ToolHive and other clients can consume. @@ -18,7 +18,7 @@ a browser-based management console for the Registry Server. :::note[Registry Server vs. built-in registry] This section covers the **Registry Server** for hosting and curating your own -MCP server and skills catalog. +MCP server, skills, and plugins catalog. ToolHive also ships with a **built-in registry** for browsing and discovering MCP servers from the default catalog. That's a different feature: see @@ -33,7 +33,8 @@ Use the Registry Server when you need to: - Curate a private or team-specific catalog instead of relying only on the default public catalog -- Publish MCP servers or [skills](../concepts/skills.mdx) for internal use +- Publish MCP servers, [skills](../concepts/skills.mdx), or plugins for internal + use - Aggregate entries from multiple sources behind one registry API - Apply authentication and authorization policies to registry access - Surface cluster-hosted workloads to users through a managed catalog @@ -44,9 +45,9 @@ The Registry Server is built around three core concepts: - A **source** is where data comes from: a Git repository, an upstream API, a local file, a Kubernetes cluster, or a managed API endpoint. Each source syncs - or receives MCP servers and skills. -- An **entry** is an individual MCP server or skill stored in the database. - Sources produce entries; each entry belongs to exactly one source. + or receives MCP servers, skills, and plugins. +- An **entry** is an individual MCP server, skill, or plugin stored in the + database. Sources produce entries; each entry belongs to exactly one source. - A **registry** is a named API surface that aggregates entries from one or more sources. Clients access entries through a registry name in the URL (for example, `GET /registry/default/v0.1/servers`). You can create multiple @@ -55,9 +56,9 @@ The Registry Server is built around three core concepts: ## How the Registry Server works -The Registry Server aggregates MCP server and skills metadata from various -sources and exposes it through a standardized API. When you start the server, -it: +The Registry Server aggregates MCP server, skill, and plugin metadata from +various sources and exposes it through a standardized API. When you start the +server, it: 1. Loads configuration from a YAML file 2. Runs database migrations automatically @@ -113,6 +114,9 @@ flowchart LR administration, enabling team-scoped registries and entry visibility - **Skills registry**: Publish and discover reusable [skills](../concepts/skills.mdx) via the extensions API and synced sources +- **Plugins registry**: Publish and discover plugin bundles (slash commands, + subagents, Agent Skills, hooks, MCP server configs, LSP servers) via the + extensions API and synced sources ## Registry sources @@ -122,11 +126,12 @@ The server supports five source types: - Ideal for hosting private MCP server catalogs - Automatically exposes entries following the MCP Registry format with ToolHive-specific extensions - - Supports publishing MCP servers and skills via the `/v1/entries` admin API + - Supports publishing MCP servers, skills, and plugins via the `/v1/entries` + admin API 2. **Upstream API** - Sync from upstream MCP Registry APIs - Supports federation and aggregation scenarios - - Syncs both MCP servers and skills from upstream sources + - Syncs MCP servers, skills, and plugins from upstream sources - Does not support publishing 3. **Kubernetes** - Automatically creates registry entries for running workloads @@ -138,12 +143,12 @@ The server supports five source types: 4. **Git** - Clone and sync from Git repositories - Supports branch, tag, or commit pinning - - Syncs both MCP servers and skills + - Syncs MCP servers, skills, and plugins - Does not support publishing 5. **File** - Read from filesystem - Ideal for local development and testing - - Syncs both MCP servers and skills + - Syncs MCP servers, skills, and plugins - Does not support publishing ## Registry Server and the rest of ToolHive diff --git a/docs/toolhive/guides-registry/plugins.mdx b/docs/toolhive/guides-registry/plugins.mdx new file mode 100644 index 00000000..8b17bb68 --- /dev/null +++ b/docs/toolhive/guides-registry/plugins.mdx @@ -0,0 +1,254 @@ +--- +title: Manage plugins +description: + Publish, list, retrieve, and delete plugins in the ToolHive Registry Server + through the admin API or a synced source. +--- + +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 the +bundle unit pioneered by Claude Code: a directory declared by a +`.claude-plugin/plugin.json` manifest that groups slash commands, subagents, +Agent Skills, hooks, MCP server configs, and LSP servers into a single +distributable artifact. 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](./publish-servers.mdx#registry-file-format) 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](./authentication.mdx)) + +## 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](./configuration.mdx) (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](#claims) below. + +::: + +```bash title="Publish a plugin" +curl -X POST \ + https://registry.example.com/v1/entries \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -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](./authentication.mdx) 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: + +```json +{ + "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](./authorization.mdx#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: + +```bash title="List all plugins" +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 + +```bash title="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 + +```json +{ + "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 + +```bash title="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 + +```bash title="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 + +```bash title="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: + +```bash title="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 " +``` + +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: + +| 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](./authentication.mdx) to secure access to your + registry + +## Related information + +- [Manage skills](./skills.mdx) - the parallel API for the skills extension +- [Registry Server introduction](./intro.mdx) diff --git a/docs/toolhive/guides-registry/publish-servers.mdx b/docs/toolhive/guides-registry/publish-servers.mdx index 44266fc2..1e6be99b 100644 --- a/docs/toolhive/guides-registry/publish-servers.mdx +++ b/docs/toolhive/guides-registry/publish-servers.mdx @@ -14,8 +14,9 @@ The right path depends on how you want to manage your catalog: | Managed source (API) | Programmatic or UI-driven publishing, per-version releases, and dynamic content. | This guide focuses on MCP servers. For skills, see -[Manage skills](./skills.mdx), which uses the same `/v1/entries` admin API with -a `skill` payload instead of a `server` payload. +[Manage skills](./skills.mdx); for plugins, see [Manage plugins](./plugins.mdx). +Both use the same `/v1/entries` admin API with a `skill` or `plugin` payload +instead of a `server` payload. ## Prerequisites @@ -30,8 +31,8 @@ a `skill` payload instead of a `server` payload. Git and file sources both read a single JSON file in the [ToolHive registry format](../reference/registry-schema-upstream.mdx). The file has top-level `version` and `meta` fields, plus a `data` object that holds -`servers` and optionally `skills` arrays. A single file can carry servers, -skills, or both: +`servers` and optionally `skills` and `plugins` arrays. A single file can carry +any combination of the three: ```json title="registry.json" { @@ -114,6 +115,12 @@ at least one `packages` entry referencing the skill's Git repository or OCI artifact. See [Manage skills](./skills.mdx) for the full skill-specific field reference and the managed-source admin API for skills. +Entries in the `data.plugins` array follow the same shape as skills - they need +`namespace`, `name`, `version`, and at least one `packages` entry - but omit the +skill-only `compatibility` and `allowedTools` fields. See +[Manage plugins](./plugins.mdx) for the plugin-specific field reference and the +managed-source admin API for plugins. + See the [registry schema](../reference/registry-schema-upstream.mdx) for the full field catalog, including optional metadata, tool definitions, and the ToolHive extensions under @@ -377,6 +384,8 @@ for the full authorization rules. - [Manage skills](./skills.mdx) using the same admin API for the skills extension +- [Manage plugins](./plugins.mdx) using the same admin API for the plugins + extension - [Configure authentication](./authentication.mdx) to secure access to your registry diff --git a/docs/toolhive/guides-registry/skills.mdx b/docs/toolhive/guides-registry/skills.mdx index ad1da945..8992e540 100644 --- a/docs/toolhive/guides-registry/skills.mdx +++ b/docs/toolhive/guides-registry/skills.mdx @@ -254,4 +254,5 @@ The API returns standard HTTP status codes: model - [Install skills with the CLI](../guides-cli/skills-management.mdx) - install skills from the registry onto your local machine +- [Manage plugins](./plugins.mdx) - the parallel API for the plugins extension - [Registry Server introduction](./intro.mdx) diff --git a/docs/toolhive/guides-registry/telemetry-metrics.mdx b/docs/toolhive/guides-registry/telemetry-metrics.mdx index ab6fb3ce..4d6069b4 100644 --- a/docs/toolhive/guides-registry/telemetry-metrics.mdx +++ b/docs/toolhive/guides-registry/telemetry-metrics.mdx @@ -7,13 +7,15 @@ description: The ToolHive Registry Server provides comprehensive observability through OpenTelemetry (OTel), supporting both distributed tracing and metrics collection -via OTLP exporters. This enables you to monitor system behavior, diagnose -issues, and improve performance. +via OTLP exporters. When metrics are enabled, the same metrics are also directly +scrapable from a Prometheus-format `/metrics` endpoint on the internal server. ## Architecture overview The Registry Server exports telemetry data (traces and metrics) via OTLP HTTP to -an OpenTelemetry Collector, which can then forward to various backends: +an OpenTelemetry Collector, which can forward to various backends. Metrics are +additionally exposed on the internal server's `/metrics` endpoint for direct +Prometheus scrape: ```mermaid flowchart TB @@ -23,12 +25,14 @@ flowchart TB registry["Registry metrics"] http & sync & registry --> otlp["OTLP HTTP"] + http & sync & registry --> scrape["/metrics on :8081"] end otlp --> collector["OTel Collector"] + scrape --> prometheus["Prometheus"] collector --> tempo["Tempo"] - collector --> prometheus["Prometheus"] + collector --> prometheus collector --> grafana["Grafana"] ``` @@ -74,37 +78,66 @@ path (e.g., use `api.honeycomb.io` or `api.honeycomb.io:443`, not ## Metrics -The Registry Server exposes metrics prefixed with `thv_reg_srv_` for easy -identification. +Registry-specific metrics use the `stacklok_registry_` prefix. The two HTTP +server metrics that have a direct OpenTelemetry semantic-convention equivalent +(`http.server.request.duration` and `http.server.active_requests`) keep their +unprefixed spec names instead, so they stay joinable with the same metric +emitted by any other semconv-instrumented service. -### Available metrics +Every series carries the constant labels `stacklok_component="registry"` and +`stacklok_product="stacklok-platform"`, promoted from OTel resource attributes +so dashboards can filter by component without relying on Prometheus `job` or +`instance` labels. + +### Prometheus scrape endpoint -| Metric | Type | Labels | Description | -| ------------------------------------------- | ------------- | -------------------------------- | ----------------------------- | -| `thv_reg_srv_http_request_duration_seconds` | Histogram | `method`, `route`, `status_code` | Duration of HTTP requests | -| `thv_reg_srv_http_requests_total` | Counter | `method`, `route`, `status_code` | Total number of HTTP requests | -| `thv_reg_srv_http_active_requests` | UpDownCounter | - | Number of in-flight requests | -| `thv_reg_srv_servers_total` | Gauge | `source` | Number of servers per source | -| `thv_reg_srv_skills_total` | Gauge | `source` | Number of skills per source | -| `thv_reg_srv_sync_duration_seconds` | Histogram | `registry`, `success` | Duration of sync operations | +When `metrics.enabled` is `true`, the same metrics are exposed at `/metrics` on +the internal server (default port `8081`), independent of the OTLP export path. +The Helm chart's Service publishes port `8081` by default, so `/metrics` is +reachable through the Service (set `service.exposeInternalPort: false` to keep +it pod-local). :::warning -In Registry Server v1.4.3, the label on `thv_reg_srv_servers_total` and -`thv_reg_srv_skills_total` was renamed from `registry` to `source`. If you have -existing Grafana queries, Prometheus alerts, or dashboards that reference the -`registry` label on these two metrics, switch them to `source`. The -`thv_reg_srv_sync_duration_seconds` metric still uses the `registry` label. +The `/metrics` endpoint has no authentication, consistent with the other +internal-server routes (`/health`, `/readiness`, `/version`). Restrict access at +the network level. In a shared cluster, apply a NetworkPolicy that limits which +pods can reach the Service's internal port. The chart README's +[Internal Port Exposure](https://github.com/stacklok/toolhive-registry-server/blob/main/deploy/charts/toolhive-registry-server/README.md) +section includes a sample policy. ::: -### Histogram buckets +### Available metrics + +| Metric | Type | Labels | Description | +| ----------------------------------------- | ------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | +| `http_server_request_duration_seconds` | Histogram | `http_request_method`, `url_scheme`, `http_route`, `http_response_status_code` | Duration of HTTP requests. Uses the OTel semconv name `http.server.request.duration`. | +| `stacklok_registry_http_requests_total` | Counter | `http_request_method`, `url_scheme`, `http_route`, `http_response_status_code` | Total number of HTTP requests. | +| `http_server_active_requests` | UpDownCounter | `http_request_method`, `url_scheme` | Number of in-flight HTTP requests. Uses the OTel semconv name `http.server.active_requests`. | +| `stacklok_registry_servers` | Gauge | `source` | Number of distinct servers per source. | +| `stacklok_registry_skills` | Gauge | `source` | Number of distinct skills per source. | +| `stacklok_registry_plugins` | Gauge | `source` | Number of distinct plugins per source. | +| `stacklok_registry_sync_duration_seconds` | Histogram | `source`, `outcome` | Duration of sync operations. `outcome` is `success` or `error`. | +| `stacklok_registry_errors_total` | Counter | `error_type`, `area` | Error-by-type classification for the sync (`area="sync"`) and HTTP (`area="http"`) paths. | +| `stacklok_build_info_ratio` | Gauge | `component`, `version`, `commit` | Always `1`; build identity is carried on labels. The OTel Prometheus exporter appends `_ratio` to gauges with unit `1`. | -The metrics use the following histogram bucket boundaries: +:::note + +The `http_request_method` label is normalized to `_OTHER` for any method outside +the nine standard HTTP methods (`GET`, `HEAD`, `POST`, `PUT`, `DELETE`, +`CONNECT`, `OPTIONS`, `TRACE`, `PATCH`). This prevents unbounded cardinality on +the unauthenticated `/metrics` endpoint. Matching is case-sensitive per the +OpenTelemetry semantic conventions, so a lowercase `get` reports as `_OTHER` +rather than being silently repaired. + +::: + +### Histogram buckets - **HTTP request duration**: 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10 seconds -- **Sync duration**: 0.1, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 300 seconds +- **Sync duration**: 0.1, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 180, 300 seconds ## Distributed tracing @@ -124,7 +157,7 @@ HTTP Request Span (root) :::note Background sync operations are monitored through metrics (see the -`thv_reg_srv_sync_duration_seconds` metric above) rather than distributed +`stacklok_registry_sync_duration_seconds` metric above) rather than distributed traces, as they are internal operations without incoming request context. ::: @@ -185,7 +218,9 @@ sufficient data for identifying issues. The `/health`, `/readiness`, and `/version` endpoints are served on a separate internal server (default port 8081) and are not included in distributed tracing or HTTP metrics from the main API server. This separates Kubernetes probe -traffic from application telemetry. +traffic from application telemetry. The `/metrics` endpoint lives on the same +internal server but does emit HTTP metrics on itself, so scrapes are visible in +the `http_server_*` series with `http_route="/metrics"`. ## Next steps diff --git a/sidebars.ts b/sidebars.ts index 2d0be563..586f4b8f 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -277,6 +277,7 @@ const mcpSidebar: SidebarsConfig[string] = [ 'toolhive/guides-registry/configuration', 'toolhive/guides-registry/publish-servers', 'toolhive/guides-registry/skills', + 'toolhive/guides-registry/plugins', 'toolhive/guides-registry/authentication', 'toolhive/guides-registry/authorization', 'toolhive/guides-registry/audit-logging', From d70796c2c2642a12665e45a7c5ff90cdaefba927 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:01:44 +0000 Subject: [PATCH 4/5] Tighten wording in registry-server v1.5.1 docs - Plugins guide: describe the plugin bundle format directly instead of narrating that Claude Code pioneered it, per the style guide's guidance against history framing when documenting current behavior. - Configuration: normalize link text to match target page titles ("Manage skills" / "Manage plugins") for consistent sentence-case references. - Telemetry: rephrase the trailing note about /metrics being instrumented; the previous "does emit HTTP metrics on itself" read awkwardly. Co-Authored-By: Claude Opus 4.7 --- docs/toolhive/guides-registry/configuration.mdx | 4 ++-- docs/toolhive/guides-registry/plugins.mdx | 12 ++++++------ docs/toolhive/guides-registry/telemetry-metrics.mdx | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/toolhive/guides-registry/configuration.mdx b/docs/toolhive/guides-registry/configuration.mdx index 1005ae5b..f84901ab 100644 --- a/docs/toolhive/guides-registry/configuration.mdx +++ b/docs/toolhive/guides-registry/configuration.mdx @@ -356,8 +356,8 @@ exists in the database. published entry - `GET /registry/{registryName}/v0.1/...` - List and retrieve servers, skills, and plugins -- [Skills management](./skills.mdx) and [plugins management](./plugins.mdx) via - the extensions API +- [Manage skills](./skills.mdx) and [Manage plugins](./plugins.mdx) via the + extensions API When authentication is enabled, the caller needs the `manageEntries` role, and publish requests must include a `claims` object whose values are backed by a JWT diff --git a/docs/toolhive/guides-registry/plugins.mdx b/docs/toolhive/guides-registry/plugins.mdx index 8b17bb68..69ec3c1c 100644 --- a/docs/toolhive/guides-registry/plugins.mdx +++ b/docs/toolhive/guides-registry/plugins.mdx @@ -6,12 +6,12 @@ description: --- 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 the -bundle unit pioneered by Claude Code: a directory declared by a -`.claude-plugin/plugin.json` manifest that groups slash commands, subagents, -Agent Skills, hooks, MCP server configs, and LSP servers into a single -distributable artifact. This guide covers the full lifecycle: publishing, -listing, retrieving, and deleting plugins. +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: diff --git a/docs/toolhive/guides-registry/telemetry-metrics.mdx b/docs/toolhive/guides-registry/telemetry-metrics.mdx index 4d6069b4..71e66316 100644 --- a/docs/toolhive/guides-registry/telemetry-metrics.mdx +++ b/docs/toolhive/guides-registry/telemetry-metrics.mdx @@ -219,8 +219,8 @@ The `/health`, `/readiness`, and `/version` endpoints are served on a separate internal server (default port 8081) and are not included in distributed tracing or HTTP metrics from the main API server. This separates Kubernetes probe traffic from application telemetry. The `/metrics` endpoint lives on the same -internal server but does emit HTTP metrics on itself, so scrapes are visible in -the `http_server_*` series with `http_route="/metrics"`. +internal server but is not excluded, so scrapes appear in the `http_server_*` +series with `http_route="/metrics"`. ## Next steps From 9e36e167f1e0b473e0062a90a418e3778fbab455 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:02:33 +0000 Subject: [PATCH 5/5] Add upstream-release-docs content for toolhive-registry-server v1.5.1 --- SUMMARY.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 SUMMARY.md diff --git a/SUMMARY.md b/SUMMARY.md deleted file mode 100644 index b1486577..00000000 --- a/SUMMARY.md +++ /dev/null @@ -1,5 +0,0 @@ -- Added new plugins guide at `docs/toolhive/guides-registry/plugins.mdx` covering the `/registry/{name}/v0.1/x/dev.toolhive/plugins` extension API and the `plugin` payload on `/v1/entries`, wired into the Registry Server sidebar. -- Rewrote `docs/toolhive/guides-registry/telemetry-metrics.mdx` for the `stacklok_registry_*` / OTel-semconv rename: renamed every metric, updated labels (`registry` → `source`, `success` bool → `outcome` label), documented the new `/metrics` scrape endpoint on `:8081`, the constant `stacklok_component` / `stacklok_product` labels, and the new `stacklok_registry_plugins`, `stacklok_registry_errors_total`, and `stacklok_build_info_ratio` series. -- Updated `docs/toolhive/guides-registry/audit-logging.mdx` to document the new `plugin.list` / `plugin.read` / `plugin.versions.list` / `plugin.version.read` events and include `plugin.list` in the high-frequency-read exclusion example. -- Updated `docs/toolhive/guides-registry/publish-servers.mdx` to describe the `data.plugins` array in the registry file format and cross-link to the new plugins guide. -- Swept "MCP servers and skills" → "MCP servers, skills, and plugins" across `intro.mdx`, `index.mdx`, `configuration.mdx`, and `authorization.mdx` where the release now includes plugins.