skills-api
v0.5.0
Published
REST API serving Claude Code skills from Azure Blob Storage
Readme
Skills API
Node + TypeScript REST API that exposes Claude Code skills, agents and MCP definitions stored in Azure Blob Storage containers.
Storage layout
Blobs live in one container (default skills):
<category>/[<sub>/...]/<skill-name>/SKILL.md
<category>/[<sub>/...]/<skill-name>/skill.yml (optional)
<category>/[<sub>/...]/<skill-name>/... (any extra files)Any directory containing SKILL.md is treated as a skill. See
../docs/skill-yml-schema.md for the metadata
format.
Setup
cp .env.example .env
# edit .env — supply AZURE_STORAGE_CONNECTION_STRING and an API_KEYS list
npm install
npm run dev # ts-node-dev with auto-reload
# or
npm run build && npm startSeeding example content
The repo ships with bundled examples under ../examples/:
After Azurite (or a real Azure account) is reachable via your .env, upload them
in one shot to the skills, agents and mcps containers:
npm run seed # uploads ../examples
npm run seed -- ./my-examples # custom examples root
npm run seed -- ./my-examples --wipe # delete every existing blob firstThe script creates missing containers if needed, walks each examples subtree
recursively, skips dot-files, and sets a sensible Content-Type per
extension (.md → markdown, .yml → yaml, .json → json, etc.).
Auth
Two schemes are supported, selected via AUTH_MODE (apikey | entra | both).
API key (legacy)
Provide the key via either header:
x-api-key: <key>
Authorization: Bearer <key>Keys are configured in API_KEYS (comma-separated).
Entra ID (Microsoft / Azure AD SSO)
Need step-by-step setup? See the dedicated walkthrough:
../docs/entra-setup.md— covers the two App Registrations, exposing the API scope, env vars, extension config, and rollout.
When AUTH_MODE=entra (or both), the API also accepts JWT access tokens issued
by your Entra ID tenant:
Authorization: Bearer eyJ0eXAi... (RS256 JWT)The token is validated against the tenant's JWKS endpoint (iss, aud, exp,
signature, optional scp/roles). Required env vars:
ENTRA_TENANT_ID— your tenant GUID.ENTRA_API_AUDIENCE— App ID URI of the API App Registration (e.g.api://<api-app-id>). Must equal the JWT'saud.ENTRA_REQUIRED_SCOPE— optional, e.g.Skills.Access. Validated against delegatedscp(user tokens) androles(app-only tokens).ENTRA_ALLOWED_TENANTS— optional comma list (defaults toENTRA_TENANT_ID).
In both mode the middleware tries Entra first if the value looks like a JWT,
then falls back to API key — useful while rolling out SSO to clients.
GET /auth/mode
Public discovery endpoint (no auth). Returns the server's authMode and — when
running in entra / both mode — the Entra parameters needed by clients to
auto-configure themselves:
{
"authMode": "entra",
"entra": {
"tenantId": "e8a1f527-…",
"audience": "api://skills.example.com",
"scope": "api://skills.example.com/Skills.Access",
"clientId": "b3c9c8d2-…"
}
}clientId is only included if ENTRA_CLIENT_ID is set. scope is empty when
ENTRA_REQUIRED_SCOPE is unset on the server. All values here are non-secret
(they end up in client-side OAuth requests anyway).
Endpoints
| Method | Path | Description |
|--------|---------------------------------|------------------------------------------|
| GET | /health | Liveness probe (no auth). |
| GET | /auth/verify | Returns { ok: true } if key is valid. |
| GET | /skills | List all skills. ?refresh=1 busts cache. |
| GET | /skills/:id | Skill detail + file list. |
| GET | /skills/:id/download | ZIP archive of all skill files. |
| GET | /skills/:id/files/<path> | Stream a single file. |
| GET | /agents | List all agents. ?refresh=1 busts cache. |
| GET | /agents/:id | Agent detail + file list. |
| GET | /agents/:id/download | ZIP archive of the agent marker file. |
| GET | /agents/:id/files/<path> | Stream a single file. |
| GET | /mcps | List all MCP entries. ?refresh=1 busts cache. |
| GET | /mcps/:id | MCP detail + file list. |
| GET | /mcps/:id/files/<path> | Stream a single file. |
:id is the URL-encoded path inside the container, e.g.
general%2Fgit%2Fcommit-helper.
Example responses
GET /skills:
{
"skills": [
{
"id": "general/git/commit-helper",
"category": ["general", "git"],
"directoryName": "commit-helper",
"metadata": {
"name": "Commit Helper",
"version": "1.2.0",
"author": "Jane Doe",
"description": "Generates conventional commit messages.",
"tags": ["git", "workflow"]
},
"hasMetadataFile": true
},
{
"id": "misc/no-meta-skill",
"category": ["misc"],
"directoryName": "no-meta-skill",
"metadata": {
"name": "no-meta-skill",
"version": "unknown",
"author": "unknown"
},
"hasMetadataFile": false
}
]
}Caching
Skill metadata is cached in memory for CATALOGUE_TTL_SECONDS (default 60s).
Set to 0 to disable, or pass ?refresh=1 on /skills to force a rebuild.
