@hallaxius/skills
by orgShtora-1
README.md
# @shtora/skills
[](https://github.com/orgShtora-1/skills/actions/workflows/ci.yml)
[](./LICENSE)
[](https://nodejs.org)
[](https://modelcontextprotocol.io)
An unofficial [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that exposes [skills.sh](https://skills.sh) — The Agent Skills Directory — as tools for AI agents and coding assistants.
> **Not affiliated.** This is an independent community project maintained by [Shtora](https://github.com/orgShtora-1). It is NOT associated with, endorsed by, or supported by skills.sh or Vercel. Directory data comes from skills.sh and its users; local management reads your configured filesystem and GitHub sources.
## Table of contents
- [Overview](#overview)
- [What you get](#what-you-get)
- [Quick start](#quick-start)
- [Requirements](#requirements)
- [Environment variables](#environment-variables)
- [Tools](#tools)
- [search_skills](#search_skills)
- [get_top_skills](#get_top_skills)
- [get_skill](#get_skill)
- [get_skill_audits](#get_skill_audits)
- [get_official_skills](#get_official_skills)
- [list_installed_skills](#list_installed_skills)
- [check_skill_updates](#check_skill_updates)
- [remove_skill and update_skill](#remove_skill-and-update_skill)
- [install_skill](#install_skill)
- [Validation caps quick reference](#validation-caps-quick-reference)
- [MCP client configuration](#mcp-client-configuration)
- [How it works](#how-it-works)
- [Data sources](#data-sources)
- [Skill id format](#skill-id-format)
- [Caching](#caching)
- [Rate limiting](#rate-limiting)
- [Security and privacy](#security-and-privacy)
- [Error handling](#error-handling)
- [Troubleshooting](#troubleshooting)
- [Limitations](#limitations)
- [Legal notes](#legal-notes)
- [License](#license)
## Overview
This server lets any MCP-capable client query skills.sh directly:
- **Discover** skills by keyword, by owner, or through the official leaderboards (all-time, trending, hot).
- **Inspect** a skill: full description, install count, topics, related skills, ready-to-use install command.
- **Vet** a skill before installing it: aggregated security audits from Socket, Snyk, Gen Agent Trust Hub, Runlayer and ZeroLeaks.
- **Browse** the official/curated directory of trusted owners (Vercel, Anthropic, Microsoft, Google, Supabase, Cloudflare, ...).
- **Install** what you find with the official [`npx skills`](https://github.com/vercel-labs/skills) CLI — every result includes the exact command to run.
- **Install and manage skills** in `.agents/skills`: install one GitHub skill (preview first, never overwriting), list installed skills, check GitHub updates, and preview a selected removal or update. Writes require explicit host opt-in and confirmation, and every mutating tool defaults to a read-only preview.
Everything runs over the standard MCP stdio transport. There is no database, backend of our own, or telemetry. Discovery talks to skills.sh; update checks and downloads use `api.github.com`.
## What you get
| Tool | Description | Token needed? |
|---|---|---|
| `search_skills` | Search the directory by keyword (fuzzy; semantic with a token) | optional |
| `get_top_skills` | Leaderboards: `all-time`, `trending`, or `hot` | no |
| `get_skill` | Full details for one skill (description, installs, topics, related, optional file contents) | only for `include_files` |
| `get_skill_audits` | Third-party security audits for a skill | no |
| `get_official_skills` | Official/curated skills grouped by owner | no |
| `list_installed_skills` | Inventory of canonical project/global `.agents/skills` directories | no |
| `check_skill_updates` | Read-only update/local-change assessment for installed GitHub skills | optional GitHub token |
| `remove_skill` | Preview or explicitly confirm removal of one tracked canonical skill | no |
| `update_skill` | Preview or explicitly confirm updating one tracked canonical GitHub skill | optional GitHub token |
| `install_skill` | Preview or explicitly confirm installing one GitHub skill into canonical `.agents/skills` (never overwrites an existing directory or lock entry) | optional GitHub token |
There are ten tools: seven are read-only; `remove_skill`, `update_skill`, and `install_skill` can write but default to a read-only preview. All return a single pretty-printed JSON text block and report failures as MCP tool errors with a stable `[CODE] message` prefix.
## Quick start
Run the published package directly — nothing to install:
```bash
npx -y @shtora/skills
# or, with Bun:
bunx @shtora/skills
```
The server starts on stdio and waits for an MCP client. Point your client's MCP config at the command above (see [MCP client configuration](#mcp-client-configuration)), then ask your agent things like *"find a skill for React performance auditing on skills.sh"*.
## Requirements
- Node.js 18 or newer (for `npx`), or [Bun](https://bun.sh)
- An MCP-capable client (Claude Desktop, Claude Code, Cursor, OpenCode, ...)
- Internet access to `skills.sh` for discovery, and `api.github.com` for update checks/downloads
That's all — the server has no other dependencies or services.
## Environment variables
Discovery works without configuration. Project-local management requires `SKILLS_PROJECT_ROOT`; writes also require `SKILLS_ALLOW_MUTATIONS=true`. Add configuration as an `env` block in your MCP client config (see [Variants](#variants) below).
| Variable | Default | Allowed range | Description |
|---|---|---|---|
| `VERCEL_OIDC_TOKEN` | none | — | Vercel OIDC token for the [documented skills.sh v1 API](https://skills.sh/docs/api). Enables semantic (multi-word) search via `search_skills` and skill file contents via `get_skill` (`include_files: true`). To obtain one: install the Vercel CLI (`npm i -g vercel`), enable OIDC Federation for any project (dashboard → Settings → Security → Secure backend access), then run `vercel link` followed by `vercel env pull` (writes it to `.env.local`, valid for roughly 12 hours — re-pull to refresh, and never commit that file). If the token expires, search automatically falls back to the public endpoint. |
| `SKILLS_MIN_INTERVAL_MS` | `500` | 0–60000 | Minimum gap between requests, in separate skills.sh and GitHub queues. |
| `SKILLS_TIMEOUT_MS` | `10000` | 1000–60000 | Timeout per request, including GitHub response bodies. |
| `SKILLS_PROJECT_ROOT` | none | existing absolute directory | Approved project root. Never inferred from the server's working directory or supplied as a tool argument. |
| `SKILLS_ALLOW_MUTATIONS` | `false` | exact `true` enables writes | Host authorization for confirmed removals, updates, and installs. Previews still work when disabled. |
| `GITHUB_TOKEN` / `GH_TOKEN` | none | — | Optional GitHub API token for update checks/downloads; `GITHUB_TOKEN` takes precedence. Never sent to skills.sh. |
| `XDG_STATE_HOME` | none | existing absolute directory | When set, global CLI metadata is read/written at `$XDG_STATE_HOME/skills/.skill-lock.json`. Skill contents still live in `~/.agents/skills`. |
Without a Vercel token, discovery uses the public search endpoint, public audit API, and public leaderboard/official/skill pages. GitHub operations work without a GitHub token but have lower upstream rate limits.
## Tools
Discovery examples are abridged from real live calls — install counts and leaderboards change over time. Local-management examples illustrate the configuration and confirmation flow.
### search_skills
Search skills.sh for skills matching a query.
**Arguments**
| Parameter | Type | Constraints | Default |
|---|---|---|---|
| `query` | string | 2–100 characters after trimming | required |
| `limit` | integer | 1–50 | 20 |
| `owner` | string | GitHub-style owner/organization name (letters, digits, hyphens; max 39 chars) | — |
**Example call**
```json
{
"query": "react",
"limit": 3
}
```
**Example response** (public mode, abridged — the response is a JSON *array*)
```json
[
{
"id": "vercel-labs/json-render/react",
"source": "vercel-labs/json-render",
"skillId": "react",
"name": "react",
"installs": 11446,
"installCommand": "npx skills add https://github.com/vercel-labs/json-render --skill react"
},
{
"id": "lobehub/lobehub/react",
"source": "lobehub/lobehub",
"skillId": "react",
"name": "react",
"installs": 5516,
"installCommand": "npx skills add https://github.com/lobehub/lobehub --skill react"
}
]
```
**Response fields**
| Field | Type | Notes |
|---|---|---|
| `id` | string | Canonical id `{source}/{skillId}` — feed it to `get_skill` / `get_skill_audits` |
| `source` | string | GitHub `owner/repo` or well-known domain |
| `skillId` | string | Skill slug within the source |
| `name` | string | Display name |
| `installs` | number | Deduplicated install count |
| `installCommand` | string? | Derived `npx skills add ...` command (GitHub-hosted skills only in public mode) |
| `sourceType` | string? | v1 mode only: `github` or `well-known` |
| `installUrl` | string? | v1 mode only |
| `url` | string? | v1 mode only: canonical skills.sh page |
| `isDuplicate` | boolean? | v1 mode only: fork/copy flag |
Notes:
- Without a token: fuzzy search via the public endpoint (the same one the official CLI uses), no descriptions in results (the endpoint does not return them).
- With `VERCEL_OIDC_TOKEN`: searches go through the documented v1 API, which adds semantic multi-word ranking and the v1-only fields above. If the token is rejected (expired), the server logs a warning to stderr and transparently falls back to the public endpoint.
### get_top_skills
Read a skills.sh leaderboard. Each leaderboard page embeds up to 600 entries; use `offset` to page through them.
**Arguments**
| Parameter | Type | Constraints | Default |
|---|---|---|---|
| `view` | enum | `all-time`, `trending`, or `hot` | `all-time` |
| `limit` | integer | 1–50 | 20 |
| `offset` | integer | 0–290 | 0 |
**Example call**
```json
{
"view": "hot",
"limit": 3
}
```
**Example response** (abridged)
```json
{
"view": "hot",
"offset": 0,
"count": 3,
"totalAvailable": 600,
"skills": [
{
"id": "uizze.sh/ui-taste",
"source": "uizze.sh",
"skillId": "ui-taste",
"name": "ui-taste",
"installs": 762,
"installsYesterday": 222,
"change": 540
}
]
}
```
**Response fields**
| Field | Type | Notes |
|---|---|---|
| `view` | enum | Echo of the requested view |
| `offset` | number | Echo of the requested offset |
| `count` | number | Entries in this slice |
| `totalAvailable` | number | Entries embedded in the page (600 observed) |
| `skills[].id` / `source` / `skillId` / `name` / `installs` | — | Same identity fields as `search_skills` |
| `skills[].installCommand` | string? | Present for GitHub-hosted skills |
| `skills[].weeklyInstalls` | number[]? | `all-time` view only: 8 trailing values (presumed daily install counts, passed through as-is) |
| `skills[].isOfficial` | boolean? | `all-time` view only: official publisher flag |
| `skills[].installsYesterday` | number? | `hot` view only: installs during the last day |
| `skills[].change` | number? | `hot` view only: change vs. yesterday |
### get_skill
Full details for a single skill.
**Arguments**
| Parameter | Type | Constraints | Default |
|---|---|---|---|
| `id` | string | 3–200 chars; `{owner}/{repo}/{skill}` or `{domain}/{skill}` (see [Skill id format](#skill-id-format)) | required |
| `include_files` | boolean | requires `VERCEL_OIDC_TOKEN` | false |
**Example call**
```json
{
"id": "vercel-labs/skills/find-skills"
}
```
**Example response** (abridged from a live call)
```json
{
"id": "vercel-labs/skills/find-skills",
"source": "vercel-labs/skills",
"slug": "find-skills",
"name": "find-skills",
"description": "Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\" ...",
"owner": "vercel-labs",
"installs": 3539262,
"url": "https://www.skills.sh/vercel-labs/skills/find-skills",
"installCommand": "npx skills add https://github.com/vercel-labs/skills --skill find-skills",
"topics": ["agents"],
"related": [
{
"id": "anthropics/skills/frontend-design",
"name": "frontend-design",
"description": "..."
}
]
}
```
**Response fields**
| Field | Type | Notes |
|---|---|---|
| `id` / `source` / `slug` | string | Canonical identity |
| `name` | string | Skill name |
| `description` | string | Full, untruncated description |
| `owner` | string | Publisher (GitHub owner or domain) |
| `installs` | number | Install count |
| `url` | string | Canonical skills.sh page URL |
| `installCommand` | string or null | Ready-to-run install command; null when unavailable |
| `topics` | string[] | Topics of this skill |
| `related` | `{id, name, description}[]` | Related skills (up to 20) |
| `files` | array or null? | Only with `include_files: true` plus a token: `{path, contents}[]` (e.g. the raw `SKILL.md`) |
| `hash` | string or null? | Content hash (v1 API) |
| `filesNote` | string? | Present when the API reports no published file bundle for the skill |
Calling `include_files: true` without a token returns `[INVALID_INPUT]` with instructions on how to enable it (metadata above is still available without a token).
### get_skill_audits
Security audits recorded for a skill. Audits are produced by third parties after a skill's first install; a skill that was never installed/audited returns `NOT_FOUND` with a message explaining that.
**Arguments**
| Parameter | Type | Constraints | Default |
|---|---|---|---|
| `id` | string | 3–200 chars; same format as `get_skill` | required |
**Example call**
```json
{
"id": "vercel-labs/skills/find-skills"
}
```
**Example response** (abridged from a live call — 5 providers were returned)
```json
{
"id": "vercel-labs/skills/find-skills",
"source": "vercel-labs/skills",
"slug": "find-skills",
"audits": [
{
"provider": "Gen Agent Trust Hub",
"slug": "agent-trust-hub",
"status": "pass",
"summary": "This skill facilitates the discovery and installation of agent extensions using a dedicated command-line interface ...",
"auditedAt": "2026-09-15T08:00:05.922Z",
"riskLevel": "SAFE",
"categories": ["..."]
}
]
}
```
**Response fields**
| Field | Type | Notes |
|---|---|---|
| `audits[].provider` | string | Observed: `Gen Agent Trust Hub`, `Socket`, `Snyk`, `Runlayer`, `ZeroLeaks` |
| `audits[].slug` | string | Provider slug |
| `audits[].status` | enum | `pass`, `warn`, or `fail` |
| `audits[].summary` | string | Provider's assessment text |
| `audits[].auditedAt` | string | ISO 8601 timestamp |
| `audits[].riskLevel` | string? | Risk label from the provider — observed values include `NONE`, `LOW`, `SAFE`, `MEDIUM`, `HIGH`, `CRITICAL` |
| `audits[].categories` | string[]? | Only emitted by some providers (observed on Gen Agent Trust Hub) |
This tool works **without** any token (the audit endpoint is public).
### get_official_skills
Official/curated skills grouped by owner — the same dataset as the [skills.sh /official](https://skills.sh/official) page. The real list currently has roughly 100 owners.
**Arguments**
| Parameter | Type | Constraints | Default |
|---|---|---|---|
| `limit` | integer | 1–100 (number of owners) | 20 |
**Example call**
```json
{
"limit": 2
}
```
**Example response** (shape, abridged)
```json
{
"count": 2,
"owners": [
{
"owner": "aave",
"totalInstalls": 10,
"featuredRepo": "aave/skills",
"featuredSkill": "deleverage",
"repos": [
{
"repo": "aave/skills",
"totalInstalls": 10,
"skills": [
{ "name": "deleverage", "installs": 2 }
]
}
]
}
]
}
```
**Response fields**
| Field | Type | Notes |
|---|---|---|
| `count` | number | Owners in this slice |
| `owners[].owner` | string | GitHub owner |
| `owners[].totalInstalls` | number | Aggregate installs for the owner |
| `owners[].featuredRepo` / `featuredSkill` | string | Highlighted repo and skill |
| `owners[].repos[].repo` | string | `owner/repo` |
| `owners[].repos[].totalInstalls` | number | Aggregate for the repo |
| `owners[].repos[].skills[]` | `{name, installs}[]` | Skills in the repo |
### list_installed_skills
Lists skills on the **machine running this MCP server**, not automatically on a remote client's machine. No OpenCode installation or OpenCode MCP is needed.
- `scope`: `project` (default) or explicitly `global`.
- `limit`: 1–100, default 50; `offset`: 0–2000, default 0.
- Project: `$SKILLS_PROJECT_ROOT/.agents/skills/<name>/SKILL.md`.
- Global: `~/.agents/skills/<name>/SKILL.md`.
Returns `scope`, `directory`, `total`, pagination, `skills`, and `warnings`. Entries include `name`, `path`, `managed`, and available source metadata. Untracked regular directories can be listed, but cannot be mutated. Symlinked skills are not managed. Other agent-specific copies are outside this scope.
### check_skill_updates
Checks existing canonical skills without writing anything. Accepts `scope`, optional exact directory `name`, `limit` (1–100, default 20), and `offset` (0–2000).
```json
{ "scope": "project", "name": "mcp-builder" }
```
Each result has one of: `up_to_date`, `update_available`, `local_changes`, `unsupported`, or `unknown`, plus an explanatory `reason` when available. A network failure or missing baseline is **not** reported as up-to-date.
Updates support GitHub CLI lock entries with a recorded `skillPath` ending in `SKILL.md` and a usable content baseline. Project metadata uses `skills-lock.json` version 1; global metadata uses `.agents/.skill-lock.json` version 3, or the XDG location above. Recorded refs are respected; otherwise GitHub's `HEAD` is used. Local, well-known, missing-baseline, and unsupported lock formats are not automatically upgraded or overwritten.
**Legacy project restriction:** the CLI's project `computedHash` concatenates paths and bytes without framing, so equality alone cannot prove that local file boundaries are unchanged. Content replacement requires a recorded immutable `skillFolderHash` as well. Without it, exact matches to current upstream are reported `up_to_date`; differences are `unknown` and updates are refused, even with confirmation. No baseline is silently created from today's potentially edited files. Global CLI installations normally include the immutable baseline; do not fabricate one to bypass a refusal.
### remove_skill and update_skill
Select **one installed directory name**, not a skills.sh id or filesystem path. Both accept:
| Parameter | Default | Meaning |
|---|---|---|
| `scope` | `project` | `project` or explicitly `global` |
| `name` | required | Portable directory basename, 1–100 ASCII letters/digits/`.`/`_`/`-`, starting with a letter or digit; Windows device names and ambiguous suffixes are refused |
| `dry_run` | `true` | Preview only; no files, locks, or staging directories are written |
| `confirm` | `false` | Must be `true` for execution |
| `confirmation_token` | absent | SHA-256 freshness token returned by the corresponding preview |
First preview the operation:
```json
{ "name": "mcp-builder", "scope": "project" }
```
Review the returned source, destination, file count, warnings, and (for updates) immutable upstream hash. After **human approval** and enabling `SKILLS_ALLOW_MUTATIONS=true` in the server environment, call the **same tool**:
```json
{
"name": "mcp-builder",
"scope": "project",
"dry_run": false,
"confirm": true,
"confirmation_token": "<copy the 64-character token from that tool's fresh preview>"
}
```
The token binds the operation, scope, selected path, current lock and files, and upstream snapshot. It is a freshness check, **not authentication or proof of human consent**; the MCP client must enforce approval. Changed state requires a new preview. Updates refuse local content changes and verify downloaded bytes against immutable Git blob hashes. Removal deletes only the selected canonical directory and its lock entry; other agents' copies/links are left alone.
Windows Git checkouts can convert LF text to CRLF. A uniform, valid UTF-8 CRLF file is accepted only when converting it back to LF reproduces the exact recorded Git blob hash. Updates preserve existing CRLF text formatting; new files use upstream bytes. Binary files, mixed line endings, and other content changes are not normalized away.
Management uses Node built-ins and GitHub's Git Data API rather than spawning the Skills CLI. This preserves Node 18 support without requiring the newer runtime needed by Skills CLI 1.7.2. No skill-bundled script, `git`, `npm`, shell, or OpenCode command is executed. Files are staged on the same filesystem and renames are rolled back on an exception; existing lock metadata is preserved. **The directory and lock are not a crash-atomic filesystem transaction.** Stop other installers/editors before a mutation: the MCP guard serializes cooperating MCP writes, not unrelated CLI processes or hostile local writers.
If a process is interrupted or rollback cannot finish, preserve `.agents/.skills-mcp.lock`, `.agents/.skills-mcp-txn-*`, and any `.skills-mcp-*.tmp` lock file. Stop the server and other writers, inspect `recovery.json`, the `previous` backup, and the CLI lock, and restore consistent files/metadata before manually clearing a stale guard. There is no automatic recovery or stale-lock stealing.
Supported snapshots are bounded to 256 files, 2 MiB per file, 16 MiB total (including checkout conversion), and portable paths up to 500 characters/20 components. Symlinks, junctions, submodules, case/Unicode-colliding paths, Windows device names, special files, and nested `.git`/`node_modules` data are refused rather than silently discarded.
### install_skill
Installs one GitHub-hosted skill into the canonical `.agents/skills` directory from immutable Git data (`api.github.com`). Accepts:
| Parameter | Default | Meaning |
|---|---|---|
| `id` | required | `{owner}/{repo}/{skill}`. GitHub only; well-known `{domain}/{skill}` ids are installed with the official CLI |
| `path` | absent | Repo-relative folder containing `SKILL.md`, used to disambiguate a repository with several same-named skills |
| `scope` | `project` | `project` or explicitly `global` |
| `dry_run` | `true` | Preview only; nothing is written |
| `confirm` | `false` | Must be `true` for execution |
| `confirmation_token` | absent | SHA-256 freshness token returned by the preview |
First preview:
```json
{ "id": "vercel-labs/skills/find-skills", "scope": "project" }
```
The preview resolves the repository default branch (`HEAD`), requires a complete, non-truncated, bounded tree, and returns the source, destination, resolved `skillPath`, immutable `latest_hash`, file count and bytes, warnings, and a fresh token. Execution follows the same flow described above: `SKILLS_ALLOW_MUTATIONS=true` in the server environment, then the same call with `dry_run: false`, `confirm: true`, and the fresh `confirmation_token`.
Install is additive by design. It refuses an existing directory or lock entry with the same name (`CONFLICT`) instead of overwriting, creates missing parent directories only, and commits the files and a new lock entry (`computedHash` and `skillFolderHash`) through the same staged, guarded transaction as removals and updates. Every downloaded file is verified against its immutable Git blob hash and size before anything is written. Well-known domains, local paths, non-GitHub sources, symbol links, oversized snapshots, and unsafe paths are refused. No skill-bundled script is executed.
### Validation caps quick reference
| Input | Rule |
|---|---|
| `query` | 2–100 chars (after trimming) |
| `limit` (search, top) | integer 1–50 |
| `offset` (top) | integer 0–290 |
| `limit` (official) | integer 1–100 |
| `limit` / `offset` (local tools) | integer 1–100 / 0–2000 |
| `view` | exactly `all-time`, `trending`, or `hot` |
| `owner` | `^[a-z0-9](?:[a-z0-9-]{0,38})$/i` |
| `id` | 3–200 chars total; 2 or 3 `/`-separated segments, each 2–100 chars (letters, digits, `.`, `_`, `-`), no `..` |
Violations are rejected before any network activity and surface as tool errors whose text starts with `Input validation error`.
## MCP client configuration
**Claude Desktop** (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"skills-sh": {
"command": "npx",
"args": ["-y", "@shtora/skills"]
}
}
}
```
**Cursor** (`.cursor/mcp.json`):
```json
{
"mcpServers": {
"skills-sh": {
"command": "npx",
"args": ["-y", "@shtora/skills"]
}
}
}
```
**OpenCode** (`opencode.json` in the project, or `~/.config/opencode/opencode.json` globally):
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"servers": {
"skills-sh": {
"type": "local",
"command": ["npx", "-y", "@shtora/skills"],
"disabled": false
}
}
}
}
```
**Claude Code (CLI)**:
```bash
claude mcp add skills-sh -- npx -y @shtora/skills
```
**Any other MCP client** that supports local stdio servers: use the standard `mcpServers` shape from the Claude Desktop example above.
### Variants
- Prefer Bun? Replace `npx -y` with `bunx` (`["bunx", "@shtora/skills"]`).
- Need the token or tuning knobs? Add an `env` block to the server entry, for example:
```json
{
"mcpServers": {
"skills-sh": {
"command": "npx",
"args": ["-y", "@shtora/skills"],
"env": {
"VERCEL_OIDC_TOKEN": "your-token-here",
"SKILLS_MIN_INTERVAL_MS": "500"
}
}
}
}
```
Avoid pasting the token into the file when the client supports environment substitution (OpenCode: `"VERCEL_OIDC_TOKEN": "{env:VERCEL_OIDC_TOKEN}"`).
## How it works
### Data sources
Discovery uses the following skills.sh sources:
1. **Public search API** — `GET https://skills.sh/api/search` (unauthenticated). The same endpoint used by the official `npx skills` CLI (`skills find`).
2. **Documented v1 API** — `https://skills.sh/api/v1/*` per [skills.sh/docs/api](https://skills.sh/docs/api). Search and skill files require `VERCEL_OIDC_TOKEN` (Bearer auth); the audit endpoint works unauthenticated. With a token configured, `search_skills` upgrades to v1 search (fuzzy/semantic).
3. **Public site pages** — `/`, `/trending`, `/hot`, `/official`, and skill pages, read from their embedded structured data (never visual scraping of markup).
Local management reads the configured canonical filesystem and compatible CLI locks. GitHub updates use `GET https://api.github.com/repos/{owner}/{repo}/git/trees/{ref-or-sha}?recursive=1` and immutable `/git/blobs/{sha}`. These requests do not use the Vercel token, follow redirects, or execute source URLs recorded in metadata.
Which tool uses what:
| Tool | Without token | With `VERCEL_OIDC_TOKEN` |
|---|---|---|
| `search_skills` | public `/api/search` (fuzzy) | v1 search (semantic capable; falls back to public on auth errors) |
| `get_top_skills` | site leaderboards | same |
| `get_skill` | skill page | same, plus v1 API for `include_files` |
| `get_skill_audits` | v1 audit endpoint (public) | same |
| `get_official_skills` | site `/official` page | same |
A token therefore only unlocks two things: semantic search and file contents.
### Skill id format
Every id is `{source}/{slug}`:
| Form | Example |
|---|---|
| GitHub: `{owner}/{repo}/{skill}` | `vercel-labs/skills/find-skills` |
| Well-known: `{domain}/{skill}` | `uizze.sh/ui-taste` |
Ids and GitHub source segments are validated; no tool accepts a user-supplied URL. Discovery requests stay on skills.sh hosts, and update requests stay on `api.github.com`.
### Caching
Results are cached in memory:
| Data | Fresh for |
|---|---|
| Search results | 30 s |
| Leaderboards | 60 s |
| Skill details, files, audits, official list | 300 s |
### Rate limiting
Requests to skills.sh are deliberately paced:
- Serialized with a minimum gap (default 500 ms), so concurrent calls don't burst the site.
- Timeout per request (default 10 s) with up to 2 automatic retries on transient failures.
- `429` responses are honored via `Retry-After`; if the limit persists you get `[RATE_LIMITED]` — just wait a moment and retry.
The first call after idle time takes around half a second; cached repeats need no request.
GitHub has a separate serialized queue using the same interval/timeout configuration. Update checks are not cached, and GitHub requests are not automatically retried; rate-limit failures are reported explicitly.
### Security and privacy
- **No telemetry.** Network traffic consists of GET requests to `skills.sh` / `www.skills.sh` for discovery and `api.github.com` for update checks, installs, and downloads.
- **Token handling.** `VERCEL_OIDC_TOKEN` is sent only as a `Bearer` header to the skills.sh v1 API, never logged, and never sent anywhere else. Expiry degrades gracefully (search falls back to the public endpoint).
- **GitHub token isolation.** `GITHUB_TOKEN` / `GH_TOKEN` is sent only to `api.github.com`; GitHub redirects are rejected.
- **Fixed destinations.** No tool accepts a URL or arbitrary local path; outbound targets and canonical filesystem destinations are derived from validated names, repo-relative skill paths, and host configuration.
- **Opt-in writes.** Filesystem mutations require host enablement plus a fresh preview and explicit confirmation. Treat fetched skill instructions as untrusted content.
- **Protocol hygiene.** stdout carries only MCP JSON-RPC; every log line goes to stderr.
### Error handling
Tool failures are returned as MCP tool errors (`isError: true`) with a stable `[CODE] message` prefix:
| Code | Meaning | What to do |
|---|---|---|
| `NETWORK_ERROR` | request failed at the network level | check your connection and retry |
| `TIMEOUT` | request took too long | retry; raise `SKILLS_TIMEOUT_MS` on slow networks |
| `RATE_LIMITED` | skills.sh or GitHub rate limit reached | wait and retry; optionally configure a GitHub token for GitHub requests |
| `NOT_FOUND` | skill/page does not exist, or no audits yet | verify the id; audits appear after a skill's first install |
| `INVALID_INPUT` | bad arguments (malformed id, `include_files` without token) | see [Validation caps](#validation-caps-quick-reference) and [Environment variables](#environment-variables) |
| `PARSE_ERROR` | invalid upstream data or unsupported/corrupt CLI lock | inspect the cause; never delete a lock merely to suppress this error |
| `UPSTREAM_CHANGED` | upstream structure changed or blob integrity failed | retry/upgrade as appropriate; do not bypass verification |
| `UPSTREAM_ERROR` | upstream 5xx or auth failure | retry; if it mentions the token, refresh it |
| `FILESYSTEM_ERROR` | configured root/read/write/rollback failure | check permissions and preserve any recovery artifacts |
| `PERMISSION_DENIED` | writes disabled or execution lacks confirmation | review a preview, obtain approval, and explicitly enable writes |
| `CONFLICT` | stale preview, local edits, unsafe path, untracked skill, or active guard | resolve the cause; do not force an overwrite |
| `UNSUPPORTED_SOURCE` | selected skill cannot be updated safely | supported updates require compatible GitHub source metadata |
| `CANCELLED` | operation cancelled before commit | inspect current state before requesting a new preview |
Example error result:
```json
{
"content": [
{
"type": "text",
"text": "[NOT_FOUND] no security audits recorded for \"vercel-labs/skills/find-skills\" yet — audits are generated automatically after a skill's first install"
}
],
"isError": true
}
```
## Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| `[INVALID_INPUT] invalid skill id ...` | id does not match the two accepted forms | use `owner/repo/skill` or `domain/skill` (see [Skill id format](#skill-id-format)) |
| `[INVALID_INPUT] include_files requires the documented v1 API ...` | `include_files: true` without a token | set `VERCEL_OIDC_TOKEN` (see [Environment variables](#environment-variables)), or drop `include_files` |
| Error text starts with `Input validation error` | argument outside the caps | check [Validation caps quick reference](#validation-caps-quick-reference) |
| `[NOT_FOUND] ... audits are generated automatically ...` | skill has never been installed/audited | expected; pick another skill or install it first |
| `[NOT_FOUND] skill ... not found` | wrong id or removed skill | verify the id on skills.sh |
| `[RATE_LIMITED] skills.sh is rate limiting us ...` | upstream rate limit persisted | wait a moment and retry; raise `SKILLS_MIN_INTERVAL_MS` |
| `[UPSTREAM_ERROR] ... check VERCEL_OIDC_TOKEN` | expired/invalid token on v1 calls | refresh the token (`vercel link` + `vercel env pull`); search keeps working via the public fallback |
| `[UPSTREAM_CHANGED] ...` | skills.sh changed its data structure | upgrade the package |
| `[TIMEOUT]` / `[NETWORK_ERROR]` | connectivity or slow upstream | retry; raise `SKILLS_TIMEOUT_MS` |
| `npx: command not found` | Node.js missing | install Node.js 18+ (or use `bunx`) |
| First tool call feels slow | deliberate pacing plus possible retries | expected: requests are serialized with a minimum gap |
## Limitations
- The public search endpoint is undocumented (it is the one the official CLI uses); if skills.sh changes it, no-token search may report `UPSTREAM_CHANGED` until the package is updated.
- Without a token: search is fuzzy only (no semantic multi-word ranking), search results carry no descriptions, and `get_skill` cannot return file contents.
- Leaderboard pagination is capped at what pages embed (`offset` 0–290).
- `weeklyInstalls` values are passed through as-is from the site.
- Data freshness follows the [Caching](#caching) TTLs; install counts change continuously on skills.sh itself.
- Management covers canonical `.agents/skills` only. Symlink installs and other agents' copies are not synchronized or removed.
- `install_skill` supports GitHub sources only and always resolves the repository default branch (`HEAD`); pinning a ref, installing from well-known domains, and migrating other agents' copies remain the official Skills CLI's job.
- Update checks need compatible GitHub metadata and a verifiable baseline. GitHub outages/rate limits and unsupported sources are reported rather than guessed.
- Local mutation guards do not lock out unrelated CLI processes. Permissions must keep untrusted local writers out of configured roots; path checks do not eliminate all filesystem check/use races.
## Legal notes
- This project respects `robots.txt`: only generally-allowed pages (`/`, `/trending`, `/hot`, `/official`, skill and owner pages) are retrieved.
- skills.sh's terms of use describe a public API that is rate-limited per IP and state: "Reasonable use, including caching results on your own infrastructure, is encouraged and not restricted." This server is built exactly around that contract — rate limiting, caching, an identifiable user agent, and no scraping behind authentication or anti-bot mechanisms.
- This project is unofficial and not affiliated with skills.sh or Vercel. If the operators object to any aspect of it, the right move is to take it down.
## License
[MIT](./LICENSE) © Shtora
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues