Skip to content

Add plugin REST API, thv plugin CLI, and HTTP client (Phase 4)#5782

Open
JAORMX wants to merge 4 commits into
mainfrom
plugins/phase4-thv-0077-5528
Open

Add plugin REST API, thv plugin CLI, and HTTP client (Phase 4)#5782
JAORMX wants to merge 4 commits into
mainfrom
plugins/phase4-thv-0077-5528

Conversation

@JAORMX

@JAORMX JAORMX commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Exposes the frozen PluginService interface (Phases 2–3) over REST, CLI, and HTTP client — Phase 4 of the plugin lifecycle management effort (RFC THV-0077).

This is a "mirror the skills system" change: the PluginService interface was already complete from Phases 2–3; this phase adds the exposure layer.

Changes

REST API (pkg/api/v1/plugins.go + plugins_types.go)

  • 10 routes at /api/v1beta/plugins: list, install, uninstall, info, validate, build, push, builds, builds/remove, content
  • Input validation on push/build/validate (empty field → 400), nil-slice guard on list
  • ValidatePluginName on path-param endpoints (uninstall, info)
  • Swagger annotations on all handlers

HTTP client (pkg/plugins/client/)

  • Client implementing plugins.PluginService over HTTP
  • NewDefaultClient (env var → server discovery → default URL)
  • ErrServerUnreachable sentinel error
  • Compile-time interface check: var _ plugins.PluginService = (*Client)(nil)

CLI (cmd/thv/app/plugin*.go)

  • 11 cobra command files mirroring skill*.go: install, uninstall, list, info, validate, build, push, builds, builds/remove
  • plugin info text output surfaces the executable surface inventory (commands/agents/skills/hooks) separately from declared-but-unmanaged components (mcpServers/lspServers) with a Declared (NOT managed): annotation — per the issue exit gate

Server wiring (pkg/api/server.go)

  • pluginManager field on ServerBuilder and Server
  • createDefaultPluginManager block: SQLite plugin store, OCI store, packager, registry client, Claude Code + Codex materialization adapters, group manager
  • devModePluginRegistryClient mirrors the skills dev-mode OCI registry dispatch
  • Route mount at /api/v1beta/plugins + plugin store cleanup

Tests

  • pkg/api/v1/plugins_test.go — table-driven, all 10 handlers + JSON key assertions + edge cases (empty fields, group filter, nil-slice)
  • pkg/plugins/client/client_test.go — all 10 methods via httptest.Server + connection error + URL encoding

Large PR Justification

This PR is large because it is a templated exposure layer — it mirrors the existing skills system (API + CLI + client) for plugins in a single atomic change. The 6700 lines break down as:

  • ~3600 lines of generated/swagger docs (docs/server/docs.go, swagger.json, swagger.yaml) — auto-generated by task docs, cannot be split or hand-edited.
  • ~1200 lines of CLI doc files (docs/cli/thv_plugin*.md) — auto-generated by task docs.
  • ~860 lines of tests (plugins_test.go + client_test.go) — test-only, exempt per contributing guidelines.
  • ~1000 lines of new Go production code across 20 files — this is the actual exposure layer (API handlers, HTTP client, cobra commands, server wiring). These files are structurally identical to their skill counterparts (the templates are skills.go, skill*.go, client.go); splitting them would produce non-functional partials (e.g., CLI commands without their server route, or an API handler without the client). The issue ([plugins] Phase 4: REST API + thv plugin CLI + content preview (THV-0077) #5528) explicitly scopes this as one deliverable.

The change is purely additive — no existing files are modified in their logic (only server.go and commands.go get new wiring). Per .claude/rules/pr-creation.md: "Large PRs acceptable for: generated code, dependency updates, docs-only, test-only changes."

Related Issues

Part of #5525 (Epic: Plugin lifecycle management)
Closes #5528 (Phase 4: REST API + thv plugin CLI + content preview)
Refs RFC PR stacklok/toolhive-rfcs#77

Testing

  • task build
  • task test ✅ (all unit tests pass)
  • task lint-fix ✅ (0 issues)
  • task docs ✅ (CLI docs + swagger regenerated)
  • CI ✅ (all checks green except CodeQL infra issue)

Expose the frozen PluginService interface over REST at /api/v1beta/plugins,
add the thv plugin cobra command tree mirroring thv skill, and implement
an HTTP client in pkg/plugins/client. Wire the plugin service into the API
server with Claude Code and Codex materialization adapters, SQLite plugin
store, and OCI registry client.

The plugin info text output surfaces the executable surface inventory
(hooks, commands, agents, skills) separately from declared-but-unmanaged
components (mcpServers, lspServers) per the issue exit gate.

Closes #5528
@github-actions github-actions Bot added the size/XL Extra large PR: 1000+ lines changed label Jul 13, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Large PR Detected

This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.

How to unblock this PR:

Add a section to your PR description with the following format:

## Large PR Justification

[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformation

Alternative:

Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.

See our Contributing Guidelines for more details.


This review will be automatically dismissed once you add the justification section.

The plugin store opens the same SQLite DB file as the skill store.
TestNewServer_ReadTimeoutConfigured already injected a mock skill manager
to avoid SQLITE_BUSY; it now also injects a mock plugin manager for the
same reason.
@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Jul 13, 2026
@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.59806% with 76 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.85%. Comparing base (687ce42) to head (a45b64d).

Files with missing lines Patch % Lines
pkg/api/server.go 54.08% 34 Missing and 11 partials ⚠️
pkg/plugins/client/client.go 83.22% 20 Missing and 5 partials ⚠️
pkg/api/v1/plugins.go 96.38% 3 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5782      +/-   ##
==========================================
+ Coverage   70.78%   70.85%   +0.06%     
==========================================
  Files         684      686       +2     
  Lines       69353    69729     +376     
==========================================
+ Hits        49091    49404     +313     
- Misses      16654    16717      +63     
  Partials     3608     3608              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

rdimitrov
rdimitrov previously approved these changes Jul 13, 2026
@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Jul 13, 2026
@github-actions
github-actions Bot dismissed their stale review July 13, 2026 12:52

Large PR justification has been provided. Thank you!

@github-actions

Copy link
Copy Markdown
Contributor

✅ Large PR justification has been provided. The size review has been dismissed and this PR can now proceed with normal review.

- Fix 'Declared (NOT managed):' to 'Declared (NOT managed by ToolHive):'
  per issue #5528 exit gate requirement
- Delete parsePluginInstallClients (byte-identical to parseSkillInstallClients),
  call the existing parseSkillInstallClients instead
- Replace custom sortedKeys generic with slices.Sorted(maps.Keys(m)) from Go
  stdlib
@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Jul 13, 2026
@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Jul 13, 2026
@JAORMX

JAORMX commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator Author

CodeQL false positives

The CodeQL check reports 20 "Uncontrolled data used in path expression" (go/path-injection) alerts. These are false positives — the same pattern that has been dismissed 49 times previously in this repo (see the dismissed alerts list).

Why they're false positives

The taint flow is: REST API request → PluginService.InstallMaterializationAdapter.MaterializeClientManager.GetPluginPath → filesystem operations (filepath.Join, os.MkdirAll, os.ReadFile, etc.).

CodeQL can't see through the validation barrier, but the input is sanitized at multiple layers:

  1. API layer (pkg/api/v1/plugins.go): ValidatePluginName on uninstall/info path params
  2. Service layer (pkg/plugins/pluginsvc/install.go:67): ValidatePluginName on plain-name installs
  3. Client manager (pkg/client/plugins.go:55): ValidateSkillName in GetPluginPath itself
  4. OCI/git install paths: the name comes from the artifact's manifest (not user input), validated at install_oci.go:63 and install_git.go:60

ValidatePluginName delegates to skills.ValidateSkillName which enforces ^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$ — rejecting .., /, and any path traversal characters.

What's affected

All 20 alerts are on pre-existing files from Phases 2-3 (not files changed by this PR):

  • pkg/plugins/adapters/claudecode.go (9 alerts)
  • pkg/plugins/adapters/codex.go (4 alerts)
  • pkg/plugins/parser.go (2 alerts)
  • pkg/skills/installer.go (3 alerts)
  • pkg/fileutils/atomic.go (2 alerts)

This PR adds new REST API call paths that reach these existing functions, which is why CodeQL reports them as "new."

Resolution

Following the established repo pattern (49 previously dismissed go/path-injection false positives), these alerts will be dismissed as false positives after merge. CodeQL is not a required check (branch protection only requires CRD Schema Compatibility).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Extra large PR: 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[plugins] Phase 4: REST API + thv plugin CLI + content preview (THV-0077)

2 participants