My skills library lives on my laptop. Over 100 of them. Claude Desktop could see them, but claude.ai never could, because a cloud app has no route to localhost:8080 on my machine.
Every new agent setup meant the same ritual. Re-register skills one by one, then watch thousands of tokens burn as the model waded through all of them to find the three that mattered. I tolerated that for months because fixing it properly looked like a weekend project, and my weekends kept filling up with all the urgent nonsense that never makes it into any retrospective.
Skills MCP Server is the fix. It's a Python MCP server, MIT licensed, built on MCP SDK 2.2.0. Point it at your skills folder and it exposes the library two ways. STDIO for local hosts. HTTP/SSE for remote ones.
The claude.ai part is the one people ask about. Short version. One SSH command. You run ssh -R 80:localhost:8080 tinyfi.sh and get a public URL like https://.tinyfi.sh/mcp/sse. Paste it into Claude.ai under Settings, then Integrations, as a remote MCP server. Add the bearer token if you enabled auth. Your local library is now searchable from a browser tab. ngrok or a Cloudflare Tunnel works the same way if you prefer those.
Day to day, the model searches with search_skills and loads with get_skill. Only the relevant skill enters context. That's the entire idea. It also runs profiles, named bundles like product-builder or researcher that load several skills in one call. Right now it talks to Claude Desktop, Cursor, Zed, Neovim, Windsurf, Cline, Roo Code, Tasklet, and the Claude Code CLI.
It's a month old and 14 good first issues are labeled, all small and self-contained. The repo is linked below. PRs get reviewed fast.
Top comments (5)
The search_skills / get_skill split is the right call. Loading 100+ skill descriptions up front is where most of the token burn comes from. The same principle matters for us at Auten (I'm on the team), where learned tasks pile up fast: only the one that matches should enter context.
One thing I'd flag on the claude.ai path: with ssh -R to a public tunnel, the URL is reachable by anyone who finds it, and auth is optional. A skills library often holds internal prompts and file paths, so I'd make the token on by default whenever the HTTP transport is enabled, and refuse to start SSE without one unless someone passes an explicit --no-auth.
How does search_skills rank today: keyword match on name and description, or embeddings? With 100+ skills, near-duplicate descriptions look like the place it would pick the wrong one.
Good questions. The ranking one has a boring answer. It's keyword scoring, no embeddings. Each query term scores 10 for a name hit, 8 for tags, 7 for triggers, 5 for description, 3 for category, and 2 for id, with results sorted by total score afterward. You're right that near-duplicate descriptions are the weak spot. Two skills with similar descriptions and no distinguishing tags will tie, and then it's just sort order.
On the auth flag, fair point and it's a real gap. Today the bearer token is opt-in through AUTH_TOKEN, and without it the server logs a warning and serves the SSE endpoint open. Your fail-closed shape is better. Token on by default for the HTTP transport, refuse to start without one unless someone passes an explicit opt-out. I'll open an issue for it.
Same lesson on our side. The search/load split only pays off if search stays cheap and precise.
Thanks for the straight answer on the scoring, that table is useful to see.
For the tie case, two cheap things that don't need embeddings: when the top two scores are within a point or two, return both with their descriptions and let the model pick, instead of silently taking sort order. And log every case where the user corrects the pick (query, chosen skill, the one they actually wanted). After a week that log points at exactly which pairs need a distinguishing tag or a "not for X" line in the description.
Glad the fail-closed auth is going in.
Both of these are sharp, and they're cheap enough to go in together.
On ties, the tool already returns the ranked list, so the model does see both candidates today. The gap is real though. Nothing marks the tie as a tie. Flagging it explicitly when the top two land within a couple of points is a small change, and I'll fold it into one issue with the auth default so everything lands together.
The correction log is the better idea of the two. No new UI needed, either. A search followed by get_skill on anything but the top result is the correction event. That mismatch is already visible in the call sequence, so the log writes itself. A week of that tells you exactly which pairs need a distinguishing tag.
Agreed, the call sequence is the cleanest signal you'll get. One thing worth separating in that log: get_skill on the second result and then done is a correction, but get_skill followed by another search is a miss, meaning neither candidate fit. Those two point at different fixes. The first needs a distinguishing tag, the second usually means a skill is missing or badly described. Thanks for walking through it.