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.

AspectSkillsMCP Servers
PurposeAgent instructions and knowledgeRemote tools and resources
ProtocolAgent Skills spec (SKILL.md)Model Context Protocol (JSON-RPC)
FormatMarkdown with YAML frontmatterContainer images or remote endpoints
RuntimeRead by AI client at prompt timeExecuted as running processes
DistributionOCI 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:

FieldRequiredDescription
nameYes2-64 chars; lowercase alphanumeric and hyphens; must start and end with alphanumeric; no consecutive hyphens
descriptionYesHuman-readable description (max 1024 chars)
versionNoSemantic version
allowed-toolsNoSpace or comma-delimited tool names
toolhive.requiresNoOCI references for skill dependencies
licenseNoSPDX license identifier
compatibilityNoClient compatibility string (max 500 chars)
metadataNoArbitrary 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 --scope flag 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-root or 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 SkillsClient queries the ToolHive Registry API at /v0.1/x/dev.toolhive/skills with pagination and search support.
  • Browsing API: The GET /registry/{name}/v0.1/x/dev.toolhive/skills endpoint 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:

  1. Load and parse SKILL.md from the directory
  2. Validate the skill definition (name, frontmatter, filesystem safety)
  3. Package all files into a tar.gz OCI layer
  4. 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:

  1. 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)
  2. Per-skill locking: A mutex map keyed by (scope, name, projectRoot) prevents concurrent installs of the same skill.

  3. Supply chain validation: For OCI installs, the skill name in the artifact must match the repository name in the reference.

  4. Client targeting: When no --clients flag is provided, all skill-supporting clients detected on the host are targeted by default. Specify --clients claude-code to 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:

  1. Parse the git reference (host, repo, ref, path)
  2. Resolve authentication (GITHUB_TOKEN for github.com, GITLAB_TOKEN for gitlab.com — both host-scoped to prevent credential exfiltration; GIT_TOKEN as an unscoped fallback sent to any host)
  3. Clone the repository (2-minute timeout; shallow clone when a branch or tag is specified)
  4. Extract the skill directory files
  5. 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):

MethodPathDescription
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/validateValidate a SKILL.md
POST/buildBuild skill to OCI artifact
POST/pushPush built skill to registry
GET/buildsList local builds
DELETE/builds/{tag}Delete a local build
GET/contentGet 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):

MethodPathDescription
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 (not Stat) 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:

PackagePurpose
oci/skills.StoreLocal OCI artifact storage
oci/skills.SkillPackagerBuilding OCI artifacts from skill files
oci/skills.RegistryClientPush/pull artifacts to/from OCI registries
oci/skills.DecompressWithLimitSafe gzip decompression with size bounds
oci/skills.ExtractTarWithLimitSafe tar extraction rejecting symlinks/traversal
registry/types.SkillCanonical 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

ResponsibilityFiles
Type definitionspkg/skills/types.go
Service interfacepkg/skills/service.go
Service implementationpkg/skills/skillsvc/
Options / DTOspkg/skills/options.go
Validationpkg/skills/validator.go
Parsingpkg/skills/parser.go
Extractionpkg/skills/installer.go
Git resolutionpkg/skills/gitresolver/
Storage interfacepkg/storage/interfaces.go
SQLite backendpkg/storage/sqlite/skill_store.go
REST APIpkg/api/v1/skills.go
Registry browsing APIpkg/api/v1/registry_v01_skills.go
HTTP clientpkg/skills/client/
CLI commandscmd/thv/app/skill*.go
Group integrationpkg/groups/skills.go