Skills System
May 28, 2026 · View on GitHub
The skills system lets ToolHive discover, build, distribute, install, and manage Agent Skills for AI coding assistants like Claude Code. Skills are not MCP servers -- they are markdown-based instructions (SKILL.md files) that extend an AI assistant's capabilities, packaged and distributed as OCI artifacts through the same registry infrastructure that serves MCP servers.
Why This Exists
MCP servers provide tools and resources that AI assistants can call. Skills fill a different gap: they provide instructions and knowledge that shape how an AI assistant approaches tasks. A skill might teach Claude Code how to review PRs in your organization's style, how to run your test suite, or how to follow your team's coding conventions.
Without ToolHive's skill system, teams would need to manually copy SKILL.md files between machines, track versions by hand, and have no central catalog for discovery. ToolHive brings the same managed lifecycle to skills that it already provides for MCP servers: a registry for discovery, OCI for distribution, scoped installation, and multi-client support.
Key design decision: Skills and MCP servers are separate systems that share infrastructure (registry, groups, OCI distribution) but have distinct purposes, formats, and lifecycles.
| Aspect | Skills | MCP Servers |
|---|---|---|
| Purpose | Agent instructions and knowledge | Remote tools and resources |
| Protocol | Agent Skills spec (SKILL.md) | Model Context Protocol (JSON-RPC) |
| Format | Markdown with YAML frontmatter | Container images or remote endpoints |
| Runtime | Read by AI client at prompt time | Executed as running processes |
| Distribution | OCI artifacts (tar.gz layers) | Container images |
Architecture
graph TB
subgraph "Skill Sources"
OCI[OCI Registry<br/>ghcr.io, Docker Hub]
Git[Git Repository<br/>git://github.com/org/repo]
Local[Local Directory<br/>SKILL.md + files]
RegistryAPI[Registry API<br/>Skill Catalog]
end
subgraph "ToolHive Skills Service"
SVC[SkillService<br/>pkg/skills/skillsvc]
Lookup[SkillLookup<br/>Registry name resolution]
GitRes[GitResolver<br/>Git clone + extract]
OCIClient[OCI Registry Client<br/>Pull/push artifacts]
Packager[SkillPackager<br/>Build OCI artifacts]
Installer[Installer<br/>Extract + validate]
Store[SkillStore<br/>SQLite persistence]
end
subgraph "Client Filesystem"
UserSkills["~/.claude/skills/<br/>(user scope)"]
ProjectSkills[".claude/skills/<br/>(project scope)"]
end
subgraph "Access Layer"
CLI[thv skill CLI]
API[REST API<br/>/api/v1beta/skills]
HTTPClient[Skills HTTP Client]
end
OCI --> OCIClient
Git --> GitRes
RegistryAPI --> Lookup
Local --> Packager
CLI --> SVC
API --> SVC
HTTPClient --> API
SVC --> Lookup
SVC --> GitRes
SVC --> OCIClient
SVC --> Packager
SVC --> Installer
SVC --> Store
Installer --> UserSkills
Installer --> ProjectSkills
style SVC fill:#90caf9,stroke:#1565c0,stroke-width:2px
style Store fill:#e3f2fd
style UserSkills fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
style ProjectSkills fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
style CLI fill:#fff9c4
style API fill:#fff9c4
Core Concepts
SKILL.md Format
A skill is defined by a SKILL.md file with YAML frontmatter and a markdown body:
---
name: code-review
description: Reviews code for best practices and security patterns
version: 1.0.0
allowed-tools: Read Glob Grep
toolhive.requires: ghcr.io/org/base-skill:v1
license: Apache-2.0
compatibility: claude-code >= 1.0
metadata:
author: team-name
---
# Code Review Skill
Instructions for how the AI assistant should perform code reviews...
Frontmatter fields:
| Field | Required | Description |
|---|---|---|
name | Yes | 2-64 chars; lowercase alphanumeric and hyphens; must start and end with alphanumeric; no consecutive hyphens |
description | Yes | Human-readable description (max 1024 chars) |
version | No | Semantic version |
allowed-tools | No | Space or comma-delimited tool names |
toolhive.requires | No | OCI references for skill dependencies |
license | No | SPDX license identifier |
compatibility | No | Client compatibility string (max 500 chars) |
metadata | No | Arbitrary string-keyed, string-valued metadata |
Implementation: pkg/skills/types.go (SkillFrontmatter), pkg/skills/parser.go, pkg/skills/validator.go
Installation Scopes
Skills install to one of two scopes:
User scope (~/.claude/skills/<skill-name>/SKILL.md):
- Available across all projects for the current user
- Default scope when no
--scopeflag is provided - Useful for general-purpose skills (code review, testing, etc.)
Project scope (<project-root>/.claude/skills/<skill-name>/SKILL.md):
- Available only within a specific project
- Requires
--project-rootor auto-detected git root - Useful for project-specific conventions and workflows
Implementation: pkg/skills/types.go (Scope, PathResolver)
Multi-Client Support
Skills can be installed for multiple AI clients simultaneously. Each client has its own skill directory structure, so installing a skill for claude-code places files differently than for cursor.
# Install for all skill-supporting clients (default)
thv skill install code-review
# Install for specific clients
thv skill install code-review --clients claude-code
The PathResolver interface maps (client, skill-name, scope, project-root) to the correct filesystem path for each client.
Implementation: pkg/skills/types.go (PathResolver), pkg/client/
Skill Lifecycle
1. Discovery
Skills are discovered through the registry system:
- Registry API: The
SkillsClientqueries the ToolHive Registry API at/v0.1/x/dev.toolhive/skillswith pagination and search support. - Browsing API: The
GET /registry/{name}/v0.1/x/dev.toolhive/skillsendpoint on the local API server exposes skills from the configured registry provider. - Local catalog: The embedded registry includes curated skills.
Implementation: pkg/registry/api/skills_client.go (SkillsClient), pkg/api/v1/registry_v01_skills.go
2. Building
Build a local skill directory into an OCI artifact:
thv skill build ./my-skill/ # Build with auto-detected tag
thv skill build ./my-skill/ --tag v1.0.0
Build process:
- Load and parse
SKILL.mdfrom the directory - Validate the skill definition (name, frontmatter, filesystem safety)
- Package all files into a tar.gz OCI layer
- Store in the local OCI store with the specified tag
Implementation: pkg/skills/skillsvc/build.go (Build), toolhive-core/oci/skills (SkillPackager)
3. Publishing
Push a locally-built artifact to a remote OCI registry:
thv skill push ghcr.io/org/my-skill:v1.0.0
Implementation: pkg/skills/skillsvc/build.go (Push), toolhive-core/oci/skills (RegistryClient)
4. Installation
thv skill install code-review # By name (registry lookup)
thv skill install ghcr.io/org/skill:v1.0.0 # By OCI reference
thv skill install git://github.com/org/repo@v1#skills/my-skill # From git
Installation flow:
flowchart TD
A[Install Request] --> B{Reference Type?}
B -->|git://| C[Git Resolver]
B -->|OCI ref| D[OCI Pull]
B -->|Plain name| E[Registry Lookup]
C --> F[Clone repo with timeout]
F --> G[Extract skill files]
E --> H{Found in local store?}
H -->|Yes| I[Use local artifact]
H -->|No| J[Query registry/index]
J --> D
D --> K[Pull from registry]
K --> L[Decompress + extract tar.gz]
G --> L
I --> L
L --> M[Validate: no symlinks, path traversal]
M --> N[Sanitize permissions]
N --> O[Write to client skill directory]
O --> P[Create DB record]
P --> Q{Group specified?}
Q -->|Yes| R[Add to group]
Q -->|No| S[Done]
R --> S
style A fill:#e3f2fd
style S fill:#c8e6c9
style M fill:#fff3e0
style N fill:#fff3e0
Key details:
-
Reference parsing: The service determines the source type from the reference format:
- Starts with
git://-> git resolver - Contains
/,:, or@-> OCI reference - Otherwise -> plain name (registry lookup)
- Starts with
-
Per-skill locking: A mutex map keyed by (scope, name, projectRoot) prevents concurrent installs of the same skill.
-
Supply chain validation: For OCI installs, the skill name in the artifact must match the repository name in the reference.
-
Client targeting: When no
--clientsflag is provided, all skill-supporting clients detected on the host are targeted by default. Specify--clients claude-codeto target a particular client.
Implementation: pkg/skills/skillsvc/install.go (Install)
5. Uninstallation
thv skill uninstall code-review
Removes the skill files from the filesystem, deletes the database record, and removes the skill from all groups.
Implementation: pkg/skills/skillsvc/uninstall.go (Uninstall), pkg/groups/skills.go (RemoveSkillFromAllGroups)
Git-Based Skill Resolution
Skills can be installed directly from git repositories using the git:// scheme:
git://github.com/org/repo # Repo root, default branch
git://github.com/org/repo@v1.0.0 # Specific tag
git://github.com/org/repo#skills/my-skill # Subdirectory
git://github.com/org/repo@main#skills/my-skill # Branch + subdirectory
Resolution process:
- Parse the git reference (host, repo, ref, path)
- Resolve authentication (
GITHUB_TOKENfor github.com,GITLAB_TOKENfor gitlab.com — both host-scoped to prevent credential exfiltration;GIT_TOKENas an unscoped fallback sent to any host) - Clone the repository (2-minute timeout; shallow clone when a branch or tag is specified)
- Extract the skill directory files
- Validate and install as normal
Security: The resolver validates hosts against SSRF (no localhost, no private IPs unless in dev mode), validates refs against shell injection, and rejects path traversal.
Implementation: pkg/skills/gitresolver/
Storage
Skill installation records are persisted in SQLite across four tables. The entries table is a shared parent for all entry types (skills share it with future entry kinds); installed_skills holds skill-specific columns and references entries via a foreign key; oci_tags is reserved for caching OCI reference-to-digest mappings but is not currently populated:
entries table
├── id (INTEGER PRIMARY KEY)
├── entry_type (TEXT, e.g. "skill")
├── name (TEXT, skill name)
├── created_at (TEXT, ISO 8601)
├── updated_at (TEXT, ISO 8601)
└── UNIQUE(entry_type, name)
installed_skills table
├── id (INTEGER PRIMARY KEY)
├── entry_id (FK → entries.id, CASCADE delete)
├── scope (user | project)
├── project_root (path, empty for user scope)
├── reference (OCI ref or git URL)
├── tag (OCI tag)
├── digest (OCI digest for upgrade detection)
├── version (semantic version)
├── description (TEXT)
├── author (TEXT)
├── tags (BLOB, JSONB-encoded []string)
├── client_apps (BLOB, JSONB-encoded []string)
├── status (installed | pending | failed)
├── installed_at (TEXT, ISO 8601)
└── UNIQUE(entry_id, scope, project_root)
skill_dependencies table
├── installed_skill_id (FK → installed_skills.id, CASCADE delete)
├── dep_name (TEXT)
├── dep_reference (OCI ref)
├── dep_digest (TEXT)
└── PRIMARY KEY(installed_skill_id, dep_reference)
oci_tags table (reserved; not currently populated)
├── reference (TEXT, PRIMARY KEY — OCI reference string)
└── digest (TEXT NOT NULL — content digest)
Implementation: pkg/storage/sqlite/skill_store.go, pkg/storage/interfaces.go (SkillStore), pkg/storage/sqlite/migrations/001_create_entries_and_skills.sql
API
REST Endpoints
Skill management (mounted at /api/v1beta/skills):
| Method | Path | Description |
|---|---|---|
GET | / | List installed skills (filter by scope, client, project_root, group) |
POST | / | Install a skill |
GET | /{name} | Get skill info |
DELETE | /{name} | Uninstall a skill |
POST | /validate | Validate a SKILL.md |
POST | /build | Build skill to OCI artifact |
POST | /push | Push built skill to registry |
GET | /builds | List local builds |
DELETE | /builds/{tag} | Delete a local build |
GET | /content | Get a skill's SKILL.md body and file listing for a reference |
Implementation: pkg/api/v1/skills.go
Skill browsing (mounted at /registry/{name}/v0.1/x/dev.toolhive/skills):
| Method | Path | Description |
|---|---|---|
GET | / | List available skills from registry (search, pagination) |
GET | /{namespace}/{skillName} | Get a specific skill from registry |
Implementation: pkg/api/v1/registry_v01_skills.go
CLI Commands
thv skill
├── install [name] Install a skill from registry, OCI, or git
├── uninstall [name] Remove an installed skill
├── list List installed skills (text or JSON output)
├── info [name] Show detailed skill information
├── validate [path] Validate a SKILL.md file
├── build [path] Build skill to OCI artifact
├── push [reference] Push built skill to registry
├── builds List locally-built OCI artifacts
└── builds remove [tag] Delete a locally-built artifact
Implementation: cmd/thv/app/skill*.go
HTTP Client
The pkg/skills/client/ package provides an HTTP client that implements the SkillService interface, allowing remote skill management through the REST API. It auto-discovers the API server via TOOLHIVE_API_URL or a local discovery file.
Group Integration
Skills can be organized into groups alongside MCP servers:
thv skill install code-review --group dev-tools
thv skill list --group dev-tools
AddSkillToGroup()adds a skill name to a group's Skills slice (deduplicated)RemoveSkillFromAllGroups()cleans up group references on uninstall
Groups provide a shared organizational model for both skills and workloads.
Implementation: pkg/groups/skills.go
Security Model
The skills system applies defense-in-depth across multiple layers:
Archive Extraction Safety
- Size limits: 500MB total decompressed, 100MB per file, 1000 files max
- Symlink rejection: Archives containing symlinks or hardlinks are rejected
- Path traversal prevention: No
..components, no absolute paths in archives - Permission sanitization: Strips setuid/setgid/sticky bits, caps at 0644
- Pre-extraction validation: Walks parent path components checking for symlinks before writing
- Post-extraction verification: Scans the extracted directory for filesystem anomalies
Dangerous Path Protection
- Refuses to remove filesystem roots, home directories, or shallow paths (< 4 components)
- Uses
Lstat(notStat) to detect symlinks without following them - Resolves symlinks in parent components before applying depth checks
Supply Chain
- OCI artifact skill name must match the last path component of the OCI repository
- Git authentication is host-scoped (GitHub token only sent to github.com)
- SSRF prevention: rejects localhost and private IPs in git references
Input Validation
- Skill names: 2-64 chars, lowercase alphanumeric + hyphens, no consecutive hyphens
- Frontmatter size limit: 64KB
- Dependency limit: 100 per skill
- Git refs validated against shell injection characters
Implementation: pkg/skills/installer.go, pkg/skills/validator.go, pkg/skills/gitresolver/reference.go, pkg/skills/gitresolver/auth.go
Dependency on toolhive-core
The skills system depends on github.com/stacklok/toolhive-core for shared primitives:
| Package | Purpose |
|---|---|
oci/skills.Store | Local OCI artifact storage |
oci/skills.SkillPackager | Building OCI artifacts from skill files |
oci/skills.RegistryClient | Push/pull artifacts to/from OCI registries |
oci/skills.DecompressWithLimit | Safe gzip decompression with size bounds |
oci/skills.ExtractTarWithLimit | Safe tar extraction rejecting symlinks/traversal |
registry/types.Skill | Canonical skill type for registry discovery |
ToolHive owns the installation lifecycle, scoping model, CLI/API interfaces, and group integration. toolhive-core owns the OCI artifact format, registry protocol types, and low-level extraction utilities.
Key Files
| Responsibility | Files |
|---|---|
| Type definitions | pkg/skills/types.go |
| Service interface | pkg/skills/service.go |
| Service implementation | pkg/skills/skillsvc/ |
| Options / DTOs | pkg/skills/options.go |
| Validation | pkg/skills/validator.go |
| Parsing | pkg/skills/parser.go |
| Extraction | pkg/skills/installer.go |
| Git resolution | pkg/skills/gitresolver/ |
| Storage interface | pkg/storage/interfaces.go |
| SQLite backend | pkg/storage/sqlite/skill_store.go |
| REST API | pkg/api/v1/skills.go |
| Registry browsing API | pkg/api/v1/registry_v01_skills.go |
| HTTP client | pkg/skills/client/ |
| CLI commands | cmd/thv/app/skill*.go |
| Group integration | pkg/groups/skills.go |
Related Documentation
- Core Concepts - Platform nouns and verbs
- Registry System - Registry architecture shared by skills and servers
- Groups - Group concept used to organize skills and workloads
- Architecture Overview - Platform overview