skill-router
Click on "Deploy 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-routerfind skills for reviewing a pull request"
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-mcp
Your agent has skills — and still ignores them. skill-mcp finds the right SKILL.md, puts it in front of the model and preloads the likely match — the model still does the loading.
A local MCP (Model Context Protocol,
modelcontextprotocol.io) server plus an opencode
plugin: BM25 search over your SKILL.md files, up to 5 candidates in the
model's context, the likely match's SKILL.md preloaded, and a rule that makes
the agent call find before it starts working. No network, no LLM, no API keys.
Names, once: the package name is skill-finder-mcp (kept in
package.json; the bare skill-mcp is taken on npm), the MCP server and its
bin are skill-mcp, the plugin id is skill-mcp-hint. Current
release: v0.6.4.
Why
opencode shows the model each skill's id, name and description. Whether the model ever opens one depends on the wording of the request: a skill whose description does not overlap the request is invisible to it.
"No auto-match" reads as "no skills". When nothing matches, the agent proceeds with general knowledge and never asks whether a relevant skill exists somewhere in the folder.
Hidden skills, kept out of the advertised catalog, never surface through normal listing at all.
Manual loading requires the user to know the skill id and to type the load command at the right moment.
Even when the model decides to look, discovery costs turns:
find, thenoutline, thenread-section, and the model has to remember to start.
Without skill-mcp, a request like "review the supply contract" goes straight to a general answer:
user: проверь договор поставки
agent: (answers from general knowledge; supply-contract-review sits in the skills folder, unopened)With skill-mcp, the model's context carries candidates before the first answer:
<skill_hint source="skill-mcp" note="посмотри описания кандидатов; если один подходит — загрузи его через skill; если нет — не загружай.">
- supply-contract-review: Review of supply contracts against the playbook; открыть: инструмент skill, name=supply-contract-review
</skill_hint>The agent reads the description, loads the skill, and the answer follows the playbook.
Related MCP server: skill-router-mcp
How it works
skill-mcp indexes the SKILL.md files it finds in the standard skill roots:
name, description, body headings, plus RU↔EN dictionary aliases. A query
runs BM25 over that in-memory index. Every hit carries a confident flag
(does the skill cover enough of the query's meaningful tokens?) and its
description, so the model judges candidates without extra calls. The model
stays the only brain: the product supplies candidates and calls no model
itself.
skill roots: ~/.config/opencode/skills, ~/.opencode/skills, <project>/.opencode/skills, …
│ read-only: name, description, headings from each SKILL.md
▼
in-memory BM25 index (name ×3, description ×2, headings ×1, aliases ×1)
│ query → ranked candidates, each flagged confident yes/no
▼
three consumers, one index:
├─> MCP server: find / outline / read-section — top candidates for the model
├─> plugin preload: the top confident candidate's SKILL.md as <skill_content>
└─> plugin hint: up to 5 candidates as a <skill_hint> system part
│
find-first rule in front of the system prompt:
"call find as the first action of every task"(Unconfirmed candidates in the hint are marked with «(возможно)» — see the plugin section below.)
Three output layers share the index: the find tool for the model, the
preload and the hint inside every request, and the rule that tells the model
to start with find.
Quick start
Requires Node.js ≥ 20.
npm install -g https://github.com/Klaus101-ai/skill-mcp/releases/download/v0.6.4/skill-finder-mcp-0.6.4.tgz
skill-finder-mcp initThe tarball comes from the GitHub Release — no npm account needed.
init finds your opencode config, adds the MCP server entry and the plugin,
shows the diff, and writes a .bak-YYYYMMDD-HHMM backup before touching
anything. It preserves JSONC comments and is safe to re-run. Then restart
opencode: config is read once at startup.
Give the agent a normal task ("проверь договор поставки" or "review my supply
contract"). The first request carries the find-first rule, so the agent calls
find before answering. You can print the <skill_hint> block without
opencode running:
node cli/plugin-demo.mjs "review my supply contract"
node cli/plugin-demo.mjs "проверь договор поставки" # RU query — shows the bilingual bridgeBoth demo commands run from a repository checkout (they call cli/plugin-demo.mjs
directly; after a global install you have skill-finder-mcp on the PATH).
To verify the install:
skill-finder-mcp doctorAfter a global install the skill-finder-mcp bin is on the PATH; doctor
exits 0 when healthy and names the broken piece otherwise. Without a global
install, run it by full path to the unpacked package:
node <prefix>/node_modules/skill-finder-mcp/dist/cli.mjs doctor (on Windows
<prefix> is usually %APPDATA%\npm; the CLI resolves the package root from
its own location — cli/main.mjs:23-28).
Not for you if…
you need semantic or embedding search — this is lexical BM25 over tokens;
your skills live outside the roots listed under Configuration → Skill roots;
your client is not opencode — the find-first rule, the preload and the hint are opencode plugin layers.
What you get
Component | Name | What it does |
MCP server |
|
|
Context-hint plugin |
| find-first rule, preload of the likely match, ≤5-candidate |
CLI |
|
|
The MCP server
A local stdio server: JSON-RPC 2.0, one message per line. stdout carries
protocol only, every log line goes to stderr, and the process exits when
stdin closes. Handshake revisions 2025-11-25 and 2025-06-18. The index
builds lazily on the first tool call and is cached for the life of the
process.
Tool | Input | Output |
|
|
|
|
| frontmatter plus headings (H1–H6) with line numbers (≤40 headings) |
|
| the section's lines, from the heading to the next heading of the same level |
confident: true means a coverage-proven match (strong); false means
possible. Every item carries the skill's description, so one call is enough
to choose.
Example:
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"find","arguments":{"query":"bug bounty report","limit":3}}}
// -> {"ok":true,"items":[{"name":"bugbounty","path":"…","score":116.1,
// "why":"matched name+description: bug, bounty","description":"…","confident":true}],
// "next":"likely match: bugbounty — verify its description and load it"}Contract strings stay Russian. Translations:
note:"пустой запрос"— "empty query";note:"ничего не найдено"— "nothing found";note:"уверенных нет, вот возможные"— "none confident; here are the possible ones".
Errors. Tool level: {ok:false, error:{code, message}}.
Code | Meaning |
| out-of-range input; |
| no such skill name or path in the index |
| path outside the skill roots |
| heading not found in the skill |
| no skill root; the server stays up and opencode still starts |
Transport level (JSON-RPC):
Code | Meaning |
| unknown tool |
| unknown method |
| malformed line |
| invalid request |
| unexpected failure |
Code Mode. opencode's Code Mode catalog renders each tool as one listing
line, and find is built to be the cheapest entry point: its description
leads with "Call this FIRST…". Inside execute, call it first:
const r = await tools["skill-mcp"].find({ query: "review a commercial contract" })The context-hint plugin
The plugin registers a context hook: right before each model dispatch it
appends system parts, and nothing else. Messages, history, tools and options
are never modified. Three layers, in the order the model meets them:
# | Layer | Injected | Budget and switch |
1 | find-first rule |
| ≤500 chars (the budget is stated in code comments, not enforced by a constant); off: |
2 | preload |
| ≤20 000 chars with a truncation marker; off: |
3 | hint |
| ≤5 items, ≤1000 chars, ≤150 ms; off: |
The rule (v0.6.4), verbatim. It fires even when the candidate hint stays silent, because "no auto-match" is not "no skills":
<skill_router_rule>
Первое действие в задаче — вызови find сервера skill-mcp с запросом из задачи. До этого не начинай правки, проверки и содержательные ответы. Если кандидат подходит — загрузи его через skill; скрытые из каталога скиллы тоже доступны через find. Нет блока <skill_hint> — значит «автосовпадения нет», а не «скиллов нет»: проверь сам. Пропуск = задача не выполнена.
</skill_router_rule>English in one line: call the skill-mcp find tool first, before edits,
checks or substantive answers; a missing <skill_hint> block means "no
auto-match", not "no skills"; skipping this means the task is not done.
The hint block. The wrapper and the note are contract strings (R1) and never claim that a skill is loaded:
<skill_hint source="skill-mcp" note="посмотри описания кандидатов; если один подходит — загрузи его через skill; если нет — не загружай.">
- supply-contract-review: Review of supply contracts; открыть: инструмент skill, name=supply-contract-review
- supply-contract-templates (возможно): Templates for supply contracts; открыть: инструмент skill, name=supply-contract-templates
</skill_hint>The note reads: "read the candidate descriptions; if one fits, load it with
the skill tool; if none fits, load nothing." «(возможно)» ("possibly")
marks a candidate the coverage rule did not prove. Candidates, not a
claim: the block presents options and the model chooses; the plugin never
asserts which skill is the right one.
Two silences run before a block forms. Both leave the rule untouched:
tool-silence: a question about the plugin or the server itself ("почему плагин не нашёл…") gets no candidates; the model should answer the user;
confident gate: no
confident: truein the candidate pool means no block. Unconfirmed candidates appear only behind a proven one.
Ordering and cost control:
The rule sits at the front of
system, the preload follows, the hint is last; the note therefore never contradicts what is already in the context.Every part is idempotent (at most once per request) and fail-open: an internal error drops the part instead of failing the request.
The index is pre-warmed at plugin load (
setImmediate, outside any user request), so the first hint pays only the search (~1.5 ms on the test fixture), not the build.Demo without opencode (from a repository checkout):
node cli/plugin-demo.mjs ["query..."]prints the exact<skill_hint>block and its formation time.
Configuration
Environment variables
Variable | Values | Effect |
|
| disable the rule, the preload and the hint (default: enabled) |
|
| disable the preload only; the hint keeps working |
|
| disable the find-first rule only; config |
|
| one-line diagnostics to stderr: index build, dropped hints with timing, fail-open reasons |
| a file path | override the deny/allow config location |
Legacy names SKILL_ROUTER_HINT / SKILL_ROUTER_DEBUG (deprecated since
v0.5.0) still work as fallbacks when the new name is unset; SKILL_MCP_*
wins when both are set.
deny/allow — ~/.config/opencode/skill-mcp.config.json
{
"deny": { "tenderwriter": ["contract-review*"] },
"allow": { "some-agent": ["skill-a", "skill-b*"] }
}Keys are agent ids; values are skill names with optional * globs. By
default every skill is hintable except the agent's deny entries; an allow
list narrows that agent to those names (minus deny). The file must be
strict JSON, comments are not allowed; a missing or malformed file falls
back to the defaults (fail-open, never an error). Resolution order:
SKILL_MCP_CONFIG → ~/.config/opencode/skill-mcp.config.json (the
default) → a skill-mcp.config.json next to the plugin module (a repo
convenience) → in-code defaults. Changes apply after an opencode restart.
Skill roots (read-only)
~/.config/opencode/skills,~/.config/opencode/skill~/.opencode/skills~/.claude/skills,~/.agents/skills<project>/.opencode/skills(from the current working directory, if present)
Every path is resolved to its real path and must stay inside one of these roots.
Manual install
init is the supported route; the manual equivalent:
// ~/.config/opencode/opencode.json(c) or <project>/opencode.json(c)
"mcp": {
"skill-mcp": { "type": "local", "command": ["node", "<abs>/dist/server.mjs"], "enabled": true }
},
"plugins": [
"<abs>/dist/plugin" // a DIRECTORY — a single-file path is dropped by the loader
]Then restart opencode. From the release tarball (GitHub Release — no npm account):
npm install -g https://github.com/Klaus101-ai/skill-mcp/releases/download/v0.6.4/skill-finder-mcp-0.6.4.tgz
skill-finder-mcp initContributors with a repository checkout can pack the tarball locally instead:
npm pack (runs the build via prepack), then
npm install -g ./skill-finder-mcp-0.6.4.tgz.
Uninstall and upgrade
To remove the product, drop both entries — the mcp.skill-mcp server and the
plugins entry — and restart opencode:
skill-finder-mcp init --uninstallTo upgrade, download the newer tarball from the GitHub Releases page, run
npm install -g https://github.com/Klaus101-ai/skill-mcp/releases/download/<tag>/skill-finder-mcp-<version>.tgz
again, and restart opencode: config is read once at startup.
Known limitations
Lexical search, no semantics. BM25 over normalized, lightly stemmed tokens; a query phrased differently from the skill's description can miss it. The RU↔EN bridge is a dictionary, not a translation model.
False positives are marked, not eliminated. An unconfirmed candidate carries «(возможно)»; a confident but irrelevant hit can still reach the model unmarked, and the model is expected to judge it against the candidate's description.
The preload costs context. The likely match's SKILL.md body is added on every dispatch that forms a confident hint: up to 20 000 chars (roughly 5k tokens in the worst case, typically a few hundred).
SKILL_MCP_PRELOAD=offremoves it and leaves the hint alone.The domain boost uses a hardcoded list. Six skill names (
BOOST_ORDERincore/index.mjs) are reordered to the front on domain queries; new domain skills are added to that list by hand. The boost reorders the pool the search already produced and never adds candidates.The bilingual bridge is the dictionary. A term outside the 177-pair
core/lexicon.ru-en.jsondoes not cross languages.Measured on one machine (a 365-skill corpus, 60-query batches, three runs): treat the latency numbers as a single-machine reference.
Troubleshooting
To verify the install:
skill-finder-mcp doctorIt checks Node ≥ 20; your config path and that it parses; the
mcp.skill-mcp entry and that the server file exists; the plugins entry and
that the directory has an index.mjs; the skill roots, how many SKILL.md
files they hold, and a probe search; the plugin config path and the
SKILL_MCP_* variables; opencode --version (informational). Exit 0 means
healthy; any ✖ line makes it exit 1.
Symptom | Fix |
Nothing changes after editing config or env | opencode reads config once at startup: restart it |
Warning | the |
| no skill root was found; check the roots list above. The server stays up and opencode still starts |
Hint behavior is opaque | set |
The agent does not call | confirm the rule layer is on ( |
Security & privacy
By contract (docs/contract.md, R5):
No network, no LLM, no API keys, no telemetry. The server speaks stdio; the plugin runs BM25 over a local index; nothing calls out.
Read-only. Skill files are opened for reading, the index lives in memory; the product never writes, moves or deletes a file.
Root boundary. Every path resolves to its canonical real path inside a skill root; anything outside is refused with
E_FORBIDDEN. Symlink escapes are reported and skipped.No side effects in opencode. The plugin appends system text only and never calls the
skilltool, so it cannot create or fake skill-gate events (R3, R4).Fail-open (R6): a broken hint drops the hint, never the request.
Third-party components are bundled at build time:
minisearch (MIT) and
jsonc-parser (MIT);
esbuild is a devDependency and does not ship. See
THIRD_PARTY_NOTICES.md.
Development & verification
npm run build # esbuild → self-contained dist/ bundles (also runs on prepack)
npm test # unit tests (node:test), including the spawned MCP server and the CLI
npm run smoke # build the index over the real skill roots, run five fixed queries
npm run measure # held-out metrics over the fixed query batches (see measure/README.md)
npm run latency # hint-formation latency p50/p95/max along the plugin pathProgrammatic API (core/, no build needed):
import { defaultRoots, discoverSkills } from "./core/sources.mjs";
import { buildIndex, search } from "./core/index.mjs";
const { skills } = discoverSkills(defaultRoots());
const index = buildIndex(skills);
const hits = search(index, "bug bounty report", { limit: 5 });
// -> [{ name, path, score, why, description, confident }, ...]The product contract (hint rules R1–R6, tool contracts, safety guarantees) lives in docs/contract.md.
License
MIT — see LICENSE. Bundled third-party licenses: THIRD_PARTY_NOTICES.md.
Version history: docs/CHANGELOG.md.
This server cannot be deployed
Maintenance
Related MCP Connectors
Search verified Claude Code plugins and skills; fetch portable SKILL.md sources. Read-only.
Search and read free, scanned AI agent skills and configs. Read-only, no account.
Search, fetch, lint, and install Agent Skills (SKILL.md) from the SkillMD registry.
Search and fetch vibe-coding Agent Skills (SKILL.md files) and prompts from TheSkillVault library.
Related MCP Servers
- AlicenseBqualityCmaintenanceProvides read-only hybrid RAG search and discovery over a local-first AI knowledge corpus, enabling semantic and keyword search, browse, digest, and status tools.41PolyForm Noncommercial 1.0.0
- AlicenseAqualityDmaintenanceIndexes installed SKILL.md files and provides deterministic search and recommendation tools to help AI agents select relevant skills without executing them.42 npm1MIT
- AlicenseAqualityCmaintenanceEnables local-first hybrid knowledge retrieval from authorized Markdown and plain-text files, combining full-text and vector search with reranking and traceable source references via a single search tool.1MIT
- AlicenseBqualityBmaintenanceEnables read-only access to a local-first catalog of portable agent skills, exposing tools to discover ranked matches, inspect stored artifacts, traverse declared relationships, and view configuration.5MIT