A self-hosted skill server for managing AI agent skills internally within organizations. Similar to self-hosted package registries (BaGet for NuGet, Verdaccio for npm, Docker Registry), SkillServer enables companies to:
- Host proprietary skills behind their firewall
- Control skill discovery and distribution
- Version skills with full history
- Integrate with NetClaw CLI and other AgentSkills.io-compatible agents
This repository contains three components:
| Component | Description | Install |
|---|---|---|
| SkillServer | Self-hosted skill registry (web server) | docker pull ghcr.io/netclaw-dev/skillserver |
| skillserver CLI | Command-line tool for publishing and managing skills | dotnet tool install -g Netclaw.SkillServer.Cli |
| Netclaw.SkillClient | Typed .NET client library | dotnet add package Netclaw.SkillClient |
SkillServer implements Agent Skills standards and defines native extensions for NetClaw-aware clients:
- AgentSkills.io - The SKILL.md format standard (originally by Anthropic)
- Cloudflare Agent Skills Discovery RFC v0.2.0 - Discovery via
/.well-known/agent-skills/index.json - SkillServer specifications - Native skill, sub-agent, and manifest sync specs
| Specification | Purpose |
|---|---|
| Skill Packages | How to author and publish AgentSkills.io-compatible SkillServer skills. |
| Sub-Agent Packages | How NetClaw sub-agent definitions are authored and how SkillServer will publish them. |
| Native Manifest | HATEOAS-style sync feed for skills, sub-agents, and future native resources with API version negotiation. |
| Security And Trust Model | Trust boundaries, authentication, digest verification, and sync safety for native sync. |
| Sub-Agent Sync Epic | Requirements and proposed GitHub issue breakdown for native manifest and sub-agent sync. |
Generate and save a strong random secret in your password manager. Supply it before the first startup; a database with no API keys leaves publishing, deletion, and key management unauthenticated.
In a private Bash terminal, load the secret without putting it in shell history:
read -r -s -p "Bootstrap API key: " SKILLSERVER__APIKEY
printf '\n'
export SKILLSERVER__APIKEYFor automated deployments, inject the value through your deployment secret manager. Keep it out of committed Compose files and .env files.
# The included Compose file maps SKILLSERVER_APIKEY to the server variable.
export SKILLSERVER_APIKEY="$SKILLSERVER__APIKEY"
docker compose -f docker/docker-compose.yml up -d
unset SKILLSERVER_APIKEY SKILLSERVER__APIKEYdotnet run --project src/SkillServer --urls http://localhost:8080
# After stopping the server:
unset SKILLSERVER__APIKEYThe server will start at http://localhost:8080. Reads and discovery remain unauthenticated; use a private network or proxy access policy for private content.
Install the host CLI, then authenticate with the bootstrap key (or another existing valid key):
export SKILLSERVER_URL=http://localhost:8080
read -r -s -p "Existing SkillServer API key: " SKILLSERVER_API_KEY
printf '\n'
export SKILLSERVER_API_KEY
skillserver api-key list
skillserver api-key create --label ci-publish
unset SKILLSERVER_API_KEYRun creation in a private terminal: it prints the new sk-... key once. Save the actual key immediately in your secret manager. The label is a name you choose; the key is generated and registered by the server. An arbitrary string saved in CI will not authenticate.
For GitHub Actions, save the returned key as the SKILLSERVER_API_KEY secret and pass it to the CLI:
- name: Publish skills
env:
SKILLSERVER_URL: https://skills.example.com
SKILLSERVER_API_KEY: ${{ secrets.SKILLSERVER_API_KEY }}
run: skillserver publish-all ./skillsCreate the secret under repository Settings β Secrets and variables β Actions, or under the deployment environment if the job uses one. See GitHub secret setup.
Install the CLI and configure network access in preceding workflow steps. Avoid running key creation in CI logs. Every valid key can publish, delete, and manage other keys; a dedicated label helps rotation but does not restrict permissions. See key management and rotation.
Configuration is via environment variables or appsettings.json:
| Variable | Default | Description |
|---|---|---|
SKILLSERVER__DATAPATH |
./data |
Directory for SQLite database and blobs |
SKILLSERVER__BASEURL |
http://localhost:8080 |
Base URL for generating absolute URLs in indexes |
SKILLSERVER__APIKEY |
(none) | Initial API key, seeded on first run if no keys exist in DB |
| Endpoint | Description |
|---|---|
GET /.well-known/agent-skills/index.json |
RFC-compliant skill index |
GET /manifest.json |
Native manifest with API version negotiation; see Native Manifest |
| Endpoint | Description |
|---|---|
GET /skills |
List all skills |
GET /skills?q={query} |
Search skills (full-text) |
GET /skills/{name} |
Get skill info (all versions) |
GET /skills/{name}/latest |
Get latest version |
GET /skills/{name}/{version} |
Get specific version metadata |
GET /skills/{name}/{version}/SKILL.md |
Download SKILL.md |
GET /skills/{name}/{version}/{path} |
Download resource file |
POST /skills/check-updates |
Batch update check |
POST /skills |
Upload new skill version (multipart/form-data) π |
DELETE /skills/{name}/{version} |
Delete version π |
| Endpoint | Description |
|---|---|
GET /blobs/sha256/{digest} |
Download blob by digest |
HEAD /blobs/sha256/{digest} |
Check if blob exists |
| Endpoint | Description |
|---|---|
POST /api/v1/api-keys |
Create a new API key π |
GET /api/v1/api-keys |
List all API keys (without secrets) π |
DELETE /api/v1/api-keys/{id} |
Revoke an API key π |
π = Requires Authorization: Bearer <key> header (when API keys are configured)
| Endpoint | Description |
|---|---|
GET /health |
Health check |
The skillserver CLI is the recommended way to publish and manage skills.
# .NET global tool
dotnet tool install --global Netclaw.SkillServer.Cli
# Or standalone binary (Linux/macOS)
curl -fsSL https://raw.githubusercontent.com/netclaw-dev/skill-server/dev/scripts/install-skillserver.sh | bash
# Or standalone binary (Windows PowerShell)
iwr -useb https://raw.githubusercontent.com/netclaw-dev/skill-server/dev/scripts/install-skillserver.ps1 | iex# Configure
skillserver config init
# Publish a skill
skillserver publish ./my-skill
# Batch publish
skillserver publish-all ./skills
# List, search, verify, delete
skillserver list --search kubernetes
skillserver verify ./my-skill
skillserver delete my-skill 1.0.0 --yesSee the CLI README for the full command reference.
The Netclaw.SkillClient NuGet package provides a typed .NET client for SkillServer.
dotnet add package Netclaw.SkillClientusing Netclaw.SkillClient;
using var client = new SkillServerClient("http://localhost:8080", apiKey: "sk-your-api-key");
// Full-text search
var results = await client.SearchSkillsAsync("kubernetes deployment");
// Get latest version of a skill
var latest = await client.GetLatestVersionAsync("my-skill");
// Batch update check
var updates = await client.CheckUpdatesAsync([
new CheckUpdateRequest { Name = "my-skill", Version = "1.0.0" }
]);See the client library README for full API documentation.
The native manifest (/manifest.json) is a HATEOAS-style discovery endpoint for SkillServer-aware clients. It provides API version negotiation and linked navigation to skills and sub-agents.
The manifest declares available API versions. Clients negotiate to the most recent compatible version:
{
"apiVersion": "v1",
"versions": {
"v1": {
"skills": { "href": "/skills/v1/index.json" },
"subagents": { "href": "/subagents/v1/index.json" },
"skillSearch": { "href": "/api/v1/skills" },
"subagentSearch": { "href": "/api/v1/subagents" }
}
}
}Clients follow links to traverse collections. The server owns pagination boundaries β clients never construct page URLs.
The manifest exposes search endpoints for discovery:
/api/v1/skills?q={query}&skip={skip}&take={take}
/api/v1/subagents?q={query}&skip={skip}&take={take}
Search is the primary discovery mechanism. Full enumeration via the collection index is also supported.
The NativeManifestClient class provides version-aware manifest access:
using var manifestClient = new NativeManifestClient("http://localhost:8080");
// Fetch manifest with automatic version negotiation
var manifest = await manifestClient.GetNativeManifestAsync();
// Resolve best supported version
var skillLinks = manifestClient.ResolveVersion(manifest, "v1");
// Browse skills
var skillIndex = await manifestClient.GetNativeSkillIndexAsync(skillLinks);
var skillDetail = await manifestClient.GetNativeSkillDetailAsync(skillLinks, "my-skill", "1.0.0");Current NetClaw feed sync uses the RFC skill discovery endpoint. The native manifest provides richer skill metadata, sub-agent sync, and API version negotiation.
Add SkillServer as a skill source using the configured feed URL for your NetClaw version:
netclaw skill source add my-server --feed http://localhost:8080- .NET 10 SDK
dotnet builddotnet testThe project uses .NET's built-in container publishing:
dotnet publish src/SkillServer -c Release /t:PublishContainer- SQLite for metadata (skill names, versions, file references)
- File system for blobs (content-addressable storage using SHA-256)
- Dapper for database access (AOT-compatible)
- System.Text.Json with source generators (AOT-compatible)
SkillServer uses API key authentication to protect write operations. Read and discovery endpoints remain open so agents can fetch skills without credentials.
- API keys are SHA-256 hashed before storage β raw keys are never persisted
- Keys are compared using constant-time comparison to prevent timing attacks
- Keys use the format
sk-{random}(256 bits of entropy, base64url-encoded) - Raw keys are shown only once at creation time and cannot be recovered
Follow Quick Start to inject SKILLSERVER__APIKEY before first startup. The server stores its hash as the "bootstrap" key only if the database contains no keys. Changing that environment variable later does not rotate an existing key.
SKILLSERVER__APIKEY configures the server bootstrap. SKILLSERVER_API_KEY authenticates the host CLI or CI client. They are different variables.
Authenticate the host CLI with an existing valid key as shown in Quick Start, then:
skillserver api-key create --label ci-publish
skillserver api-key list
skillserver api-key delete 2Creation returns a new secret once; listing returns IDs, labels, and dates, never the raw key or hash. --expires-at <date> optionally sets an expiration on creation. All valid keys have the same permissions, including key management.
To rotate a key, create a replacement, save it in your client or CI secret store, verify an authenticated command such as skillserver api-key list with the replacement, then delete the old key by its ID. No server restart or database reset is needed. The last remaining key cannot be deleted. If a newly created key is lost, create another using an existing valid key and revoke the lost key.
When no API keys exist in the database, authentication is disabled and all endpoints are open. This preserves the original v1 behavior for existing deployments.
- Rate limiting
- Audit logging
Apache-2.0 - Copyright 2025 Petabridge, LLC