Skip to main content
Glama
README.md
# @shtora/skills

[![CI](https://github.com/orgShtora-1/skills/actions/workflows/ci.yml/badge.svg)](https://github.com/orgShtora-1/skills/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![Node.js >= 18](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
[![MCP](https://img.shields.io/badge/MCP-stdio-blue.svg)](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

Maintenance

ActivityMaintained
ResponsivenessNo issues