Dr. Skill: Auditing Your Agent's Skill and MCP Loadout
Dr. Skill (drskill) is a read-only Python CLI that works out which skills, plugin skills, and MCP servers each coding agent on a machine actually loads, then flags duplicates, shadowed names, colliding descriptions, injection surfaces, and unpinned servers. Every finding ends in a fix command or an acknowledgement recorded in a committed ledger.
This guide is for developers who maintain their own agent setup and tech leads who own a team’s shared .claude/, .agents/, and .mcp.json. The symptom that brings you here is familiar: the agent reaches for the wrong skill, ignores one you installed last week, or a teammate gets different behaviour from the same repository. Nobody can say what the agent loads, because it is spread across a dozen directories, three plugin caches, and every MCP config on the machine.
What you’ll walk away with from a loadout audit
Section titled “What you’ll walk away with from a loadout audit”- A resolved inventory of what each agent loads, including skills that arrive inside plugins, with approximate token costs
- A usage-backed delete list: which skills and MCP tools were invoked in the last 30 days, and which never were
- Routing tests (
[[queries]]) that fail CI when a request would go to the wrong skill, so you verify routing without rereading every description - A CI job that fails a pull request on an unacknowledged loadout warning, safe to run on contributor code
- Four copy-paste prompts: triage a scan, build a delete list, fix colliding descriptions, and add the gate to CI
Why does a large skill catalog make your agent pick the wrong skill?
Section titled “Why does a large skill catalog make your agent pick the wrong skill?”A skill catalog is a tool loadout, and it costs you in two separate ways.
The token bill. In Claude Code, every skill’s description sits in context from the start of the session so the model knows the skill exists; the body loads only when the skill is invoked. The listing’s budget is 1% of the model’s context window, and each entry’s description plus when_to_use text is cut at 1,536 characters. When the listing overflows, Claude Code drops descriptions starting with the skills you invoke least. After auto-compaction it re-attaches the most recent invocation of each skill, keeping the first 5,000 tokens of each within a combined 25,000-token budget, newest first, so older skills can drop out mid-task. Before deleting skills, you can raise the budget with skillListingBudgetFraction (for example, 0.02 for 2%) or list low-priority skills without a description by setting them to "name-only" in skillOverrides (plugin skills are not covered by skillOverrides); /doctor estimates the listing’s context cost and names its biggest contributors.
The routing tax. The description is the whole basis on which the model chooses a skill. Two skills whose descriptions share the same trigger phrases are a routing problem, not a cosmetic one, and an overflowing listing that strips descriptions makes it worse. Drew Breunig describes the pattern in How to Fix Your Context (June 2025): tool-selection accuracy falls as the list of tools grows.
drskill measures the first cost and detects the second, across every agent on the machine and across MCP servers, whose tool definitions compete for the same routing decision.
Where drskill fits next to /skill-doctor, the skills CLI, and pre-install scanners
Section titled “Where drskill fits next to /skill-doctor, the skills CLI, and pre-install scanners”Claude Code now ships its own usage report, so pick the tool by the question you are asking.
| Tool | Question it answers | Scope |
|---|---|---|
npx skills | How do I install, update, and pin this skill? | One skill or repo at a time; changes your skill directories |
| A pre-install scanner such as Snyk Agent Scan (skill supply-chain security) | Should I install this at all? | Skills and MCP configs, security-focused, needs a Snyk API token |
/skill-doctor (Claude Code v2.1.252 or later) | What does each skill cost, and which ones have I never used? | Claude Code only; skips bundled and enterprise skills |
/context and /doctor in Claude Code | How big is the skill listing in this session? | One Claude Code session |
drskill | What does every agent effectively load, does any of it conflict, and what did I actually use? | Every detected agent, skills and MCP servers, after install |
/skill-doctor is the quickest answer when you use only Claude Code. drskill earns its place when you run several agents, share skills through .agents/skills, or need a CI gate: it resolves precedence rules, symlinks, nested directories, machine-level skills, and plugin stores per agent. It is also the only one of these tools that tests routing and gates the loadout in CI. Snyk Agent Scan reads MCP configs too, but only for security findings.
Install drskill and run your first scan
Section titled “Install drskill and run your first scan”drskill is a Python CLI, not an Agent Skill. It does not install through npx skills add, and your agent never loads it, so the diagnostic adds nothing to the context it diagnoses.
-
Install it in your terminal. The full package includes the model-judged
--deepchecks and--mcp-connect:Terminal window uv tool install drskillFor CI, or anywhere you will not use those two flags, install the core package. It leaves out the
deepextra, which pulls in DSPy and LiteLLM, and theconnectextra, which pulls in the MCP SDK:Terminal window uv tool install drskill-corepipx install drskillworks too ifuvis not in your toolchain. -
From the project root, write a starter ledger:
Terminal window drskill initThis creates
drskill.tomlwith these defaults in 0.8.1:[budget]catalog_tokens_max = 6000 # per-harness startup catalog budget (approximate tokens)body_tokens_warn = 20000 # per-skill body ceiling (approximate tokens)[thresholds]near_duplicate = 0.85 # Jaccard similarity that counts as a near duplicatedescription_overlap = 0.6 # cosine similarity that clusters descriptionsgeneric_min_distinct_tokens = 2 # fewer distinctive words than this is too vague -
Run the scan:
Terminal window drskill scanIt detects every agent it can find, resolves each one’s effective skill set, and prints errors first, then warnings. Our run opened like this (home paths shortened):
drskill scan — 5 harnesses, 673 skills, 2 MCP serversERRORS[8ecc] new double-load: Claude Code loads the same skill 'agents-sdk' 2 times:~/.claude/plugins/cache/claude-plugins-official/cloudflare/1.0.0/skills/agents-sdk/SKILL.md,~/.claude/plugins/cache/cloudflare/cloudflare/1.0.0/skills/agents-sdk/SKILL.mdharnesses: claude-codefix: Disable or uninstall one of the plugins shipping this skill (cloudflare)WARNINGS[505d] new budget-catalog-tokens: Claude Code startup catalog is ~64153 tokens (budget 6000)harnesses: claude-codefix: drskill list --tokens --harness claude-codeThe first error is the classic case: the same plugin installed from two marketplaces, loaded twice.
claude plugin uninstallon one copy removes the duplicate. -
Read one finding in full, or walk them all:
Terminal window drskill show 8eccdrskill reviewreviewruns only in a real terminal. It takes one keypress per finding:aacks,nacks with a note,fqueues the fix commands,sskips, andqquits.
Exit codes let local runs stay quiet while CI does not: 0 means clean or everything acknowledged, 1 means an error-level finding is active, and 2 means only warnings are active and you passed --ci. Without --ci, warnings alone exit 0.
What does drskill scan check?
Section titled “What does drskill scan check?”drskill’s README (the project description on its PyPI page) documents 38 scan checks, plus 17 more that run only under drskill lint, as of 0.8.1. They fall into five groups that need different responses.
| Group | Example check ids | What to do about a finding |
|---|---|---|
| Skill hygiene | name-shadow, double-load, exact-duplicate, near-duplicate (MinHash Jaccard ≥ 0.85), spec-name-mismatch, spec-description-too-long (over 1,024 characters), broken-symlink, lockfile-drift, budget-catalog-tokens | Remove the extra copy or fix the file; these are rarely judgment calls |
| Description quality | description-overlap, missing-activation, generic-description, opposing-imperatives | Rewrite one description, merge the skills, or ack a deliberate overlap |
| Injection surfaces | injection-unicode, injection-credential-read, injection-override, injection-egress, injection-remote-fetch, injection-shell-unreviewed, injection-shell-dangerous | Read the quoted lines; the verdict is yours |
| MCP configuration | mcp-secret-in-config, mcp-unpinned-server, mcp-insecure-url, mcp-dead-server, mcp-diverged-server, mcp-tools-unreviewed, mcp-tool-poisoning | Pin the package, move the secret to an environment variable, review the tool text |
| Routing expectations | query-routing | Fix the description the query should have matched |
missing-activation deserves the most attention: a description that never says when to use the skill is a frequent reason a good skill never fires, because the model decides whether to load a skill from its description and when_to_use text alone (Claude Code skills docs). On first --mcp-connect, mcp-tools-unreviewed prints a note listing the server’s tools; acking it records that set as the approved baseline, and any later rewrite of a tool description or schema becomes a warning that fails --ci.
Which skills do your agents actually use?
Section titled “Which skills do your agents actually use?”scan looks at the loadout as configured. drskill audit reads the session traces that Claude Code, Codex, Pi, and Copilot already write to disk and ranks what was actually invoked.
drskill audit # this projectdrskill audit --global --since 30d # every project on the machine, last 30 daysdrskill audit --last # only the most recent sessiondrskill audit overturemaps # drill into one skill: the query that triggered it, and the trace linedrskill audit browser:get_screenshot --global # server:tool when a skill and a tool share a nameThe report ends with an Unused section: skills, commands, and MCP tools with zero invocations. It lists an entry only when the harness’s trace history covers at least [usage] unused_days (default 90) and the install is older than that, so a new skill is not called dead after a week. Override the window with --unused-days.
Read the numbers with four caveats. On Codex and Pi, a skill count comes from seeing the agent read the SKILL.md, not from an invocation event, and those rows carry a ~. Codex encrypts its reasoning, so drill-downs show no reasoning there. Copilot records neither reasoning nor tool arguments. Each harness keeps traces for a different length of time, so the cross-harness rollup ranks by invocations per week inside each harness’s own window.
Audit writes nothing to the ledger and has no effect on --ci. It caches extracted text, including your prompts, under ~/.drskill/cache/audit/; that cache is machine state and is never committed.
Will this request route to the right skill?
Section titled “Will this request route to the right skill?”drskill explain simulates where a request would go:
drskill explain "review this pull request for security issues" --harness claude-codeClaude Code routes to security-review 1. 0.45 security-review Security code review for vulnerabilities. Use when asked to "security… 2. 0.30 gha-security-review GitHub Actions security review for workflow exploitation vulnerabilit…
Scores are drskill's own similarity model, not the harness router.The verdict is “routes to”, “contested” (the top two are too close to call), or “no skill matches”. The score is text similarity, not your agent’s model; add --deep to have a model judge the ranking too.
The part that changes how you work is turning these into tests. Add the requests your team relies on to drskill.toml:
[[queries]]query = "review this pull request for security issues"expect = "security-review"
[[queries]]query = "write a migration for the orders table"Every drskill scan then checks each query against each harness and raises a query-routing warning when a query is contested or routes to the wrong skill. With --ci, that warning fails the build. A new skill whose description steals an existing route now shows up in the pull request that adds it, and nobody has to reread every description to find out.
How drskill reads Claude Code, Codex, and Cursor
Section titled “How drskill reads Claude Code, Codex, and Cursor”The command is the same for every agent; --harness scopes it to one. What differs is which files are read, whether precedence is verified, and whether audit has traces.
drskill scan --harness claude-code- Skills:
.claude/skillsand~/.claude/skills, recursively, plus skills from enabled plugins, read from~/.claude/plugins. Disabled plugins and stale cached versions are skipped. - Commands:
.claude/commandsand~/.claude/commandsjoin the injection checks only, because they are invoked explicitly. - MCP:
.mcp.jsonand~/.claude.json. - Verification: paths and precedence both verified, so findings carry no
?. - Audit: traces under
~/.claude/projects/, with the agent’s reasoning before each invocation.
Pair it with the built-in view: /context shows the skill listing’s size in the current session, and claude plugin details <plugin> shows a plugin’s projected token cost before you keep it. For plugin choices, see the plugins worth installing.
drskill scan --harness codex- Skills:
.codex/skillsand.agents/skillsin the project,~/.codex/skillsand~/.agents/skillson the machine, plus enabled plugins from~/.codex/plugins/cache/. - MCP:
~/.codex/config.toml. - Verification: paths and precedence both verified. Codex keeps every same-name copy visible instead of picking a winner, so
drskilldoes not raise shadowing findings for it; duplicates still show up asexact-duplicateornear-duplicate. - Audit: traces under
~/.codex/sessions/. Skill counts are inferred fromSKILL.mdreads (~), and reasoning is encrypted.
drskill scan --harness cursor- Skills:
.cursor/skillsand.agents/skillsin the project,~/.cursor/skillsand~/.agents/skillson the machine, recursively. - MCP:
.cursor/mcp.jsonand~/.cursor/mcp.json. - Verification: paths only. Cursor’s docs do not say which copy wins a name collision, so shadowing and
double-loadfindings carry a?on the harness name. Findings without?rest on verified rules. - Not covered in 0.8.1: the README lists plugin-store scanning for Claude Code, Codex, Gemini CLI, Copilot, and droid, not Cursor, and
auditdoes not read Cursor traces. Your Cursor usage data has to come from your other agents.
An unknown harness id is an error that lists the valid ids. drskill scan --global analyses machine-level skills only.
Gate pull requests on the committed loadout
Section titled “Gate pull requests on the committed loadout”Loadouts drift without anyone editing them: a git pull brings in a teammate’s skill, an unpinned MCP package publishes a new version, a server rewrites a tool description. The gate catches the drift in the pull request that causes it.
drskill ack appends to the end of drskill.toml and touches nothing else:
[[ack]]check = "near-duplicate"skills = ["docx-report", "documentation-writer"]fingerprint = "sha256:..."note = "docx is output format specific; keeping both"date = 2026-07-19The fingerprint hashes the check id plus the content of every skill involved. Edit one of those skills and the ack stops applying, so an ack means “this exact situation is fine”, never “stop checking this pair”. Acks that involve only machine-level skills go to ~/.drskill.toml; --local and --global-ack override the routing.
Run --deep and --mcp-connect locally, then commit .drskill/cache/. Model verdicts and MCP tool snapshots live there, and every later scan reads them, so CI gets the results without an API key and without launching a server.
name: loadouton: pull_request: paths: ['.claude/**', '.agents/**', '.codex/**', '.cursor/**', '.mcp.json', 'drskill.toml', '.drskill/**']permissions: contents: readjobs: drskill: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false - name: Scan the committed skill and MCP loadout run: pipx run --spec drskill-core==0.8.1 drskill scan --ciThe job holds no secret and runs the core package, which cannot connect to servers or call a model. Keep it that way: --mcp-connect launches the stdio commands named in the config, and on a pull request that config is the contributor’s.
Who signs off: a finding fails the check, and the fix is either a change to the loadout or an ack with a note. Put drskill.toml, .drskill/ and .github/workflows/ under CODEOWNERS and turn on required code-owner review in branch protection or a ruleset, so a tech lead approves every ack and every change to this job, because anyone who can commit can otherwise silence a warning. On pull_request, GitHub runs the workflow YAML from the pull request’s merge ref, so without that review a pull request can also edit the job itself.
If you publish skills, plugins, or a marketplace, drskill lint runs the same checks on what you author and adds manifest checks against the Agent Plugins specification or Claude Code’s plugin format:
drskill lint ./my-plugin --fail-on warn --jsonlint exits 0 when clean, 1 for findings at or above --fail-on, and 2 for a usage error such as a path it cannot lint. For how to write a skill that passes, see building and publishing your own skills.
Why shell commands inside skills get their own check
Section titled “Why shell commands inside skills get their own check”Claude Code lets a skill carry !`command` lines or a ```! block that run when the skill is invoked, before the model sees the file, with the output spliced into the prompt. It is a documented feature, dynamic context injection, and it is useful for pulling in a live git diff. The exposure is the update path: a git pull or a skill manager can swap in different commands. drskill’s README reports, from its author’s own testing, that commands which do run are not gated by a permission prompt.
drskill treats the command set like an MCP tool list. injection-shell-unreviewed is a note on first sight listing every command; once you ack that baseline, any change becomes a warning that shows what was added and removed. injection-shell-dangerous fires immediately on credential paths, credential-named environment variables, network egress, or a download piped to a shell.
To switch the feature off, set "disableSkillShellExecution": true in Claude Code settings. It covers user, project, plugin, and additional-directory skills, not bundled or managed ones, and it is most useful in managed settings, where users cannot override it.
When a drskill scan misleads you
Section titled “When a drskill scan misleads you”The first scan buries the signal. On a machine with hundreds of skills, expect hundreds of near-duplicate findings, most of them one root cause repeated: a plugin installed from two marketplaces, or a skill copied into two directories. Recovery: run drskill show near-duplicate, fix the source of the copies, and rescan before acking anything. Ack a whole check class (drskill ack <check-id>) only after you have read it.
A clean scan is not a clean loadout. Cursor plugin skills are outside 0.8.1’s plugin-store list, about 65 harnesses inherited from the vercel-labs/skills project are unverified on both paths and precedence, and binary files or files over 1 MiB are recorded but not content-scanned. Script-only checks such as egress and credential reads skip files that do not look like scripts. Recovery: pair the scan with a pre-install review and the security gates for agent-written code.
Injection checks flag surfaces, not intent. A finding is not an accusation, and silence is not a guarantee. Every injection finding quotes the exact lines; read them.
Description checks are heuristics. opposing-imperatives matches strictly and misses paraphrased conflicts; description-overlap flags judgment calls. Recovery: drskill scan --deep asks a model to judge each flagged pair, and a cluster judged fully distinct drops to a note that no longer fails --ci. A skill with an active injection finding never gets that downgrade, which is usually why one cluster downgraded and another did not.
The ledger and cache are unsigned. An edit to drskill.toml or .drskill/cache/ can silence any warning. Recovery: CODEOWNERS on both, and review those diffs as code.
Lockfile drift reports one warning instead of many. When none of the hashes in skills-lock.json match what drskill computes, it prints a single “could not verify” warning rather than accusing every skill. Per-skill drift appears only after at least one hash matches.