skill-search
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@skill-searchsearch for skills related to accessibility auditing"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
skill-search
Semantic, on-demand skill retrieval for Claude Code. Claude Code injects a short blurb for every installed skill into context on every turn so it can decide which to use. As your skill count grows, that listing becomes a large recurring token tax — and because the match is essentially name/description keyword overlap, a skill whose name doesn't echo the user's words quietly never fires.
skill-search replaces that with a vector retriever over the full skill
descriptions. Skills are set to name-only (name stays visible and invocable,
the description leaves the budget), and an MCP tool returns just the few skills
that semantically match the task at hand.
Where it shines: this pays off once you have a lot of skills installed — roughly hundreds. With only a handful, the native listing is already cheap and you don't need the extra round-trip.
Proof of value
All numbers below are measured, not estimated by vibes — on a real setup of 117 active skills. You can reproduce them (see Reproduce).
1. It reclaims a measurable chunk of every turn
The native skill listing injects name + description for all skills, every turn.
At name-only, only the names remain and the retriever supplies descriptions
on demand. Counted with a real BPE tokenizer (tiktoken cl100k_base), modeling
each skill as it appears in the listing (- name: description):
Tokens injected per turn | % of a 200K window | |
Native full listing (name + description) | 7,267 | 3.63% |
| 887 | 0.44% |
Saved, every turn | 6,380 | 3.19% |
That's ~53 tokens/skill of description you stop paying for on turns that don't
need them. The worst offenders are generic-named skills whose value lives
entirely in the description (context-mode:context-mode alone is 235 tokens) —
exactly the skills name-only + retrieval handles best. (cl100k_base
approximates Claude's tokenizer; reproduce with
scripts/measure_tokens.py.)
2. It fixes the name-bias miss (real, unedited output)
search_skills ranks by meaning, so skills match even when they share no words
with the query:
$ query: "debug a failing test"
0.672 gsd-debug
0.659 superpowers:systematic-debugging
0.658 gsd-forensics
$ query: "review my UI for accessibility"
0.689 chrome-devtools-mcp:a11y-debugging ← "a11y" never appears in the query
0.667 frontend-design:frontend-design
0.653 web-design-guidelines ← no shared keywords at all
$ query: "set up a supabase database with auth"
0.717 supabase:supabase
0.575 supabase:supabase-postgres-best-practices
0.542 superpowers:executing-plansBeyond cherry-picked examples: on a 24-query labeled set
(eval/labeled_queries.jsonl) the default
service-free embedder (bge-small, 384-dim) scores:
recall@1 | recall@3 | recall@6 | |
bge-small (default) | 0.67 | 0.79 | 0.79 |
That's a conservative floor — a few counted "misses" are arguably valid
alternatives the strict labels don't list (e.g. gsd-verify-work for "confirm
my change works"). The genuine misses are short, generic-named skills
(keybindings-help, loop) where a small embedder struggles; the opt-in Ollama
tier (768-dim) typically lifts recall further. Reproduce — and the misses —
with python eval/run_eval.py.
3. It stays fast as the index grows
Reindex is incremental: each point stores a content hash, so only changed skills re-embed and deleted skills are dropped.
Operation (117 skills) | Time |
Full rebuild ( | ~18.8s¹ |
Incremental reindex, nothing changed | ~0.07s |
¹ includes the one-time embedding-model download on first run.
4. It fails loudly, not silently
Because the retriever becomes the only discovery path, a stale or broken index would otherwise hide skills with no symptom. Guards:
search_skillsappends awarningwhen skills changed on disk since the last index.healthreports embedder/store reachability and lists any dark (on-disk but unindexed) or stale (indexed but deleted) skills, and exits non-zero when degraded (cron/CI-safe).
Related MCP server: claude-skills-mcp
How it works (two pieces, useless apart)
generate_overrides.py→ sets ~all skills toname-onlyin.claude/settings.local.json→ frees the budget. A tiny allowlist (the router skill) stays fully"on".server.py(MCP) → embeds full descriptions into a vector store; returns the top-k relevant skills → Claude invokes them by name (works atname-only).
Skip step 1 and you pay the native tax and the retriever. Do both.
File | Role |
| MCP server: |
| Shared skill discovery — one source of truth for both halves |
| Frees the budget by setting skills to |
Install
The default tier is service-free — embedded on-disk Qdrant + local ONNX embeddings (fastembed). No Docker, no Ollama, no manual model pull (the model downloads once, then runs offline).
pipx install skill-search-mcp # one command — installs the skill-search CLI
# 1. build the index once (incremental afterwards; --force for a full rebuild)
skill-search --reindex
# 2. register the MCP with Claude Code (no-arg console script = stdio server)
claude mcp add --transport stdio skill-search -- skill-search
# 3. install the router skill (the always-on trigger — see "The router skill")
mkdir -p ~/.claude/skills/skill-search
curl -sL https://raw.githubusercontent.com/sowhan/skill-search/main/skills/skill-search/SKILL.md \
-o ~/.claude/skills/skill-search/SKILL.md
# 4. free the budget: set all OTHER skills to name-only
skill-search-overrides # project scope; --global targets ~/.claude
# 5. (optional) confirm inside a Claude Code session
# /mcp and /doctorOpt into the faster tier (only if you already run them):
docker run -p 6333:6333 qdrant/qdrant
export SKILL_QDRANT_URL=http://localhost:6333 # Qdrant server
ollama pull embeddinggemma
export SKILL_EMBED_BACKEND=ollama # Ollama embeddingsPin these as --env flags on claude mcp add to keep them for the registered server.
From source (dev / contributing): git clone https://github.com/sowhan/skill-search && cd skill-search && pipx install . — then cp -r skills/skill-search ~/.claude/skills/ for the router skill.
The router skill (keep this one "on")
The always-visible entry point that tells Claude to retrieve before guessing
ships in this repo as an installable skill at skills/skill-search/.
Install it (done automatically by step 4 of Install):
cp -r skills/skill-search ~/.claude/skills/It's deliberately tiny — frontmatter plus a few lines of instruction — so it
costs almost nothing to keep "on" while every other skill goes name-only.
Its whole job: on a new task, call search_skills, then invoke the 2-4 genuinely
relevant results by name. Keep this skill (and skill-search itself) in the
generate_overrides keep-on allowlist.
Configuration
All config is env-var overridable (SKILL_* prefix). Selection: set
SKILL_QDRANT_URL for a Qdrant server (else embedded); SKILL_EMBED_BACKEND
defaults to fastembed.
Concern | Default (service-free) | Opt-in (faster) |
Vector store | embedded on-disk Qdrant at |
|
Embedder | fastembed |
|
Results |
| — |
Switching embedders changes the vector dimension; an existing collection can't
take it. This is guarded both ways — reindex raises a clear "run --rebuild"
error, and health flags the mismatch.
Tests
pip install -e ".[dev]"
pytest -m "not integration" # fast, offline (no network/model) — 13 tests
pytest -m integration # end-to-end: real embed → search → incremental skipUnit tests pin the highest-risk logic: skill discovery (parsing, plugin
namespacing, dedup precedence — the shared source of truth both halves depend on),
point-ID validity, content-hash determinism, and the staleness/manifest guards.
The integration marker gates the one test that loads the embedder.
If a broken third-party pytest plugin in your env fails collection, run with
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1.
Reproduce the numbers
# token savings on YOUR skill set (chars/3.7 proxy; descriptions-only, so a floor)
python3 -c "
import skills_discovery as d
s = d.discover_skills()
tok = lambda x: round(len(x)/3.7)
desc = sum(tok(x['description']) for x in s)
print(f'{len(s)} skills | ~{desc} tokens saved/turn | {desc/200000*100:.2f}% of 200K')
"
# semantic ranking + incremental-reindex timing
skill-search --reindex # full build, then run again to see the incremental skip
skill-search --health # indexed vs on-disk, dark/stale skills, dimsCaveats
Retriever is the sole discovery path. At
name-only, Claude can't auto-match on description. Ifsearch_skillsmisses, the skill goes dark. TuneSKILL_TOP_Kup if recall feels low; keep critical skills"on".Re-index on change. New/edited skills aren't searchable until
reindexruns — but it's incremental and cheap, and drift is surfaced bysearch_skillswarnings +health, so the failure mode is visible, not silent.Embedded Qdrant locks its dir to one process. Don't run a CLI
--reindexwhile the MCP server is up in that mode — use thereindex()tool, or the Qdrant-server tier.Tail-scale. The payoff scales with how many skills you have installed — worth it at hundreds, overkill at a handful.
License
MIT — see LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/sowhan/skill-search'
If you have feedback or need assistance with the MCP directory API, please join our Discord server