Serve your Agent Skills through MCP instead of loading them all into every prompt.
A skill library is progressive by design — ~100 tokens of metadata to find a skill, the full body only once you've chosen it. A flat listing throws that away and pays for the whole catalogue on every request. This plugin restores it.
On a real inventory of 277 installed skills:
| Flat listing of every name + description | ~22,000 tokens, on every single request |
| Skills ever invoked, across 143 sessions | 7 |
| Invocation rate | 0.14% of all tool calls |
| Listing cost per skill that ever gets used | ~3,100 tokens |
Twenty-two thousand tokens of permanent context overhead to advertise a library that fires roughly once per seven hundred tool calls — all spent before the model knows whether the task needs a skill at all.
Worse, that cost is linear in inventory size, so the catalogue itself has a ceiling. At ~80 tokens per skill a thousand skills would be ~80k tokens of system prompt, and a few thousand simply will not fit.
Measured against the same inventory, through the MCP server:
| Call | Result | Cost |
|---|---|---|
skills_list (no arguments) |
all 263 skills, complete | ~1,900 tokens |
skills_list({query: "supabase"}) |
9 matches with descriptions | ~900 tokens |
activate_skill({name: "supabase"}) |
the one skill you chose | ~2,300 tokens |
| Nothing needed this turn | — | 0 tokens |
~7 tokens per skill instead of ~80, because browsing needs names and only the narrowed set needs prose. A thousand skills is ~7k tokens; ten thousand is ~70k. The inventory stops being bounded by the context window, and nothing is paid at all on turns where no skill is used.
(Your numbers will differ — the audit below prints them for your machine.)
The Agent Skills format defines three disclosure stages. This maps each onto an MCP primitive, so the cost is paid when a stage is reached, not upfront:
| Stage | Cost | MCP primitive |
|---|---|---|
| Discovery — what exists | ~7 tokens/skill, on request | skills_list tool |
Activation — full SKILL.md body |
~2,300 tokens, one skill | activate_skill tool |
| Resources — bundled scripts, references, assets | on demand, per file | skill://{name}/{path} |
The catalogue leaves the system prompt entirely. The model calls skills_list
when it wants to know what's available, activate_skill once it has chosen, and
reads bundled files through skill:// URIs only if the skill's instructions
point at them.
Discovery has three levels, so breadth is cheap and prose is bought only where it's needed:
detail: "names"(default with no query) — every skill name, grouped by root, uncapped. The complete catalogue for less than one activated skill.detail: "summary"(default with a query) — matching skills with descriptions truncated to 200 characters. Enough to choose between hits.detail: "full"— untruncated descriptions and bundle metadata.
Two stable tools plus one resource template — not one tool per skill, which would reintroduce the same bloat through the tool list instead of the prompt.
/plugin marketplace add JesseVent/agent-experience
/plugin install agent-experience@agent-experience
The MCP server starts automatically. It auto-discovers skills from
~/.agents/skills, ~/.claude/skills, <cwd>/.agents/skills, and plugin skill
directories — resolving symlinks and de-duplicating by canonical path.
| Tool | Transport | What it does |
|---|---|---|
skills_list |
stdio + HTTP | Discovery — filter by query/root, paginated, cacheable (5-min TTL) |
activate_skill |
stdio + HTTP | Activation — loads the full SKILL.md into context |
skill_provenance |
stdio + HTTP | Where a skill came from: source repo, hash, install/update times |
skill_experience |
stdio + HTTP | What's been recorded about past use of a skill |
skill_updates_available |
stdio | Detect locally-modified skills via folder-hash comparison |
skill_update |
stdio | Apply upstream updates (destructive — replaces skill files) |
experience_record |
stdio | Record an outcome after using a skill |
Read-only tools work on both transports. Anything with a local side effect is
stdio-only, and skill_update is explicitly destructive.
Resources: skill://{name}/{path} for any bundled file, skill://{name}/provenance
for lockfile data. Path traversal is rejected at three layers.
The measurement above is reproducible, and it's a tool in its own right — because knowing why 270 of 277 skills never fire is a prerequisite for fixing it.
node "${CLAUDE_PLUGIN_ROOT}/scripts/audit.cjs" --out ~/.agents/experienceOr just ask Claude to "audit my skills" — the bundled skill-audit skill
handles it. The report covers:
- Context cost — the numbers above, for your inventory
- Invoked — with mandated invocations flagged separately, so forced calls don't flatter the figures
- Near-misses — unused skills whose vocabulary overlaps your real prompts, ranked by prevalence × distinctiveness, always showing the matched terms
- Duplicates across roots — resolution is first-match-wins, so the second
copy is unreachable;
diverged: truemeans the copy you edit may never load - Description problems —
too_short,no_prompt_overlap,no_trigger_vocabulary - Dormant — the deletion shortlist, and only a shortlist
It reads session transcripts, which Claude Code already writes. No telemetry is collected — the transcript is the record.
Scoring is deliberately mechanical and always shows its matched terms: a match means the words line up, not that the skill would have helped. The script never decides a skill is dead; judgement belongs to the skill reading the report, and deletion belongs to you.
A UserPromptSubmit hook that surfaces a never-used skill when your prompt
strongly matches one — discovery fails at prompt time, so that's where it acts.
Conservative by design; a prompt-path hook that cries wolf is worse than none:
- reads the precomputed
audit.json, never scans the filesystem (~48ms) - fires only on 3+ distinct term matches, on prompts of 25+ characters
- suggests at most 2 skills, phrased as availability rather than instruction
- 6-hour per-skill cooldown, so it cannot nag
- prints nothing and exits 0 on any error — never blocks a prompt
Disable with AGENT_EXPERIENCE_SUGGEST=0. Silent until you generate an
audit.json with --out.
Everything runs locally; nothing is transmitted. The audit reads prompt text to extract term frequencies and never retains or writes it — only counts and normalised terms leave the scanner, and a term survives only if it already appears in one of your skill descriptions.
audit.json contains your skill names and matched terms. It lives under
~/.agents/experience and should not be committed anywhere public.
scripts/*.cjs are esbuild bundles of a TypeScript source tree — bundled rather
than published as packages so the plugin has no install step and no dependency
resolution at hook or server startup.
esbuild bin/mcp-stdio.ts --bundle --platform=node --format=cjs --target=node20 \
--outfile=plugins/agent-experience/scripts/mcp-server.cjs
esbuild bin/audit.ts --bundle --platform=node --format=cjs --target=node20 \
--outfile=plugins/agent-experience/scripts/audit.cjsMIT