MCP Tool Index
Prerequisites
The index itself is JSON — no dependency needed to read, tag, intent, or list it. PyYAML is
only needed to read a project's not-yet-migrated legacy mcp-tools.yaml:
python3 -m pip install pyyaml # also in $AI_BADGER/engine/requirements.txt
Without it, a legacy-YAML-only project falls back to a stricter built-in parser and, if that
can't safely read the file, refuses with a hint rather than a traceback (see migrate below).
Manage .ai-badger/mcp-tools.json — a machine-readable index that maps every MCP server tool to tags (for filtering) and intent (for semantic matching). The index feeds the ai_badger_hooks.py plugin's pre_llm_call hook, which injects relevant tool recommendations into every LLM turn.
Overview
MCP servers expose 40+ tools per server. Agents scan ALL tool definitions in the system prompt, wasting tokens and sometimes picking the wrong tool (e.g., search_text when search_in_files_by_text is faster). The index solves this by:
- Tagging each tool with category labels (
[build],[database, sql],[diagnostic]) - Intent description for semantic disambiguation ("Compile the solution" vs "List project run configs")
- Hook-driven recommendation — the
pre_llm_callhook loads the index, extracts domain keywords from the user's message, and injects top-N matching tools as a context hint
Tags and intents come from three places, in descending authority — and each entry records which
one spoke, in an origin field:
origin |
source | survives update? |
|---|---|---|
manual |
you, via mcp-index tag / mcp-index intent |
yes — a human outranks both |
catalog |
features/<stack>/mcp/<server>/tools.json in the framework |
refreshed from the catalog |
heuristic |
_auto_tags guessing from the tool name |
replaced as soon as the catalog covers the tool |
The catalog is a curation library, not a completeness claim: it applies to a server however that
server arrived (project .mcp.json, user-global config, a plugin, a cloud connector), and the
heuristics are the last resort for the servers it does not know.
When to Use
- After
hermes mcp add— runmcp-index updateto index new tools - Before complex multi-tool tasks — run
mcp-index validateto ensure the index is complete - When the agent picks the wrong tool — run
mcp-index tag <tool> <correct-tags...>to fix tagging - After removing MCP servers — run
mcp-index updateto mark stale tools
When NOT to Use
- A one-off tool lookup — read the index JSON (
.ai-badger/mcp-tools.json) directly - Writing a brand-new MCP server — use
hermes-mcp-setup - No MCP servers in the project — there is nothing to index
- A wrong-tool call that is a one-off — tag it, don't re-architect
Tag Taxonomy
Tags come from a closed set in features/common/mcp-tags.json:
| Category | Tags |
|---|---|
| Language | csharp, typescript, javascript, python, sql, css, html |
| Action | navigation, diagnostic, build, run, refactoring, search, read, write, terminal |
| Domain | database, tracing, opentelemetry, browser, dotnet, semantic, files |
| Meta | batch, slow, unsafe |
Tools auto-tagged as [general] need manual curation.
Commands
init — create the index
python3 .ai-badger/skills/mcp-index/scripts/mcp_index.py init --target <project-root>
python3 .ai-badger/skills/mcp-index/scripts/mcp_index.py init --target <project-root> --host hermes
Asks the host CLIs for their MCP servers (see Where the server list comes from below), describes
each tool from the catalog where it can and by name heuristics otherwise, seeds a server the
listing named without tool detail from the catalog (see update), records each server's
status, and writes .ai-badger/mcp-tools.json. Prints which listing answered and which sources
were skipped, then how many tools were tagged as general and which servers reported no tools.
Completion criterion: .ai-badger/mcp-tools.json exists with all current MCP tools indexed.
update — sync index with current MCP state
python3 .ai-badger/skills/mcp-index/scripts/mcp_index.py update --target <project-root>
Adds new tools, marks vanished ones with status: removed (preserving their curation), adds new
MCP servers, and restates every server's status. Preserves manually-set tags and intents on
existing tools; a tool the catalog describes is re-described from it unless origin is manual,
and the tools that changed are printed by name. Takes --host like init.
A listing that carries no tool detail at all (every source but hermes mcp list --json) cannot
tell "this server is gone" from "this is another host's listing", so a source it does not name is
left untouched — same status, same tools — and named in the output. Only a listing that carries
tools can move a source to absent and its tools to removed.
A server such a listing does name is seeded from the mcp catalog: the catalog stands in for
a host that declined to enumerate, so a curated server is not stranded with tools: {}. The seed
is a floor, never an override — an existing entry wins whatever its origin, a removed tool
stays removed, an uncatalogued server stays empty, and a listing that carries tool detail is taken
as the whole truth even when it reports none.
Completion criterion: All current MCP tools appear in the index; removed tools have status: removed.
validate — check index quality
python3 .ai-badger/skills/mcp-index/scripts/mcp_index.py validate --target <project-root>
Fails (exit code 1) if any tool has [general] tags, empty tags, missing intent, or invalid tags.
Completion criterion: Exit 0 with "OK: N tool(s) validated".
tag — set tags for a tool
python3 .ai-badger/skills/mcp-index/scripts/mcp_index.py tag rider:search_symbol semantic search --target <project-root>
Validates tags against the taxonomy. Rejects unknown tags.
Completion criterion: mcp-index list shows the tool with the new tags.
intent — set intent for a tool
python3 .ai-badger/skills/mcp-index/scripts/mcp_index.py intent rider:get_file_problems "Check a file for Rider code analysis errors and warnings" --target <project-root>
Requires ≥10 characters. Use a concise one-sentence description that would help an agent pick this tool from a list of candidates.
Completion criterion: mcp-index list shows the tool with the new intent.
list — display tools
python3 .ai-badger/skills/mcp-index/scripts/mcp_index.py list --target <project-root>
python3 .ai-badger/skills/mcp-index/scripts/mcp_index.py list --tag diagnostic --target <project-root>
python3 .ai-badger/skills/mcp-index/scripts/mcp_index.py list --untagged --target <project-root>
Completion criterion: All matching tools are displayed with server, tags, and intent.
migrate — one-shot legacy YAML to JSON conversion
python3 .ai-badger/skills/mcp-index/scripts/mcp_index.py migrate --target <project-root>
Converts a legacy .ai-badger/mcp-tools.yaml to .ai-badger/mcp-tools.json, preserving every
curated tag and intent. A no-op (exit 0) if the project already has mcp-tools.json. Any other
write command (init/update/tag/intent) migrates a legacy file the same way as a side
effect — migrate exists for a project that only wants the conversion, without also running
init/update against a live MCP source. The old file is renamed to mcp-tools.yaml.migrated,
never deleted.
If PyYAML is absent and the legacy file falls outside the built-in parser's verified subset,
migrate refuses rather than risk a silently wrong conversion, and prints two remedies:
install PyYAML and re-run, or regenerate via mcp-index init --from-json (which loses curated
tags and intents — stated so the cost is explicit before choosing it).
Completion criterion: .ai-badger/mcp-tools.json exists with the same tools, tags, and
intents the legacy file had; .ai-badger/mcp-tools.yaml.migrated exists.
Where the server list comes from
hermes mcp list --json — the source this skill was built on — no longer exists: the installed
hermes answers error: unrecognized arguments: --json (measured 2026-07, issue #188). init and
update therefore ask three sources in order and take the first that lists a server:
| order | source | what it carries |
|---|---|---|
| 1 | hermes mcp list --json |
server names and their tools — the only listing that can |
| 2 | claude mcp list |
every server, plus a reachability phrase per server; no tools. Health-checks each server first (~14s for 17) |
| 3 | hermes mcp list |
server names and an enabled flag, from the text table; no tools |
--host hermes or --host claude restricts the chain to one CLI — use it when the other is slow,
noisy, or listing the wrong project's servers. --from-json <document> skips the hosts entirely and
reads a saved hermes mcp list --json document.
--discover — ask each server for its own tools
No remaining listing carries tool names, but hermes mcp test <server> does. Pass --discover
to init or update and every server the listing left unenumerated is asked directly:
python3 .ai-badger/skills/mcp-index/scripts/mcp_index.py init --target <project-root> --discover
It is opt-in because it costs one connection per server (measured: 11 servers, ~20s, 128 tools
recovered from a listing that carried none). A server hermes does not have in its own config — a
plugin- or connector-provided one — cannot be tested; it keeps tools_known False, is named in
the output, and falls back to the catalog seed. hermes mcp test exits 0 even when it fails,
so only its printed Tools discovered block is treated as an answer.
If no source answers, both commands refuse and print what each one said — a missing CLI, a non-zero exit with its error line, or an empty listing. They never write a half-index.
Status meanings: read references/status.md if
updatereports a status other thanok(or when a silent server needs explaining).
Auto-tagging rules: read references/heuristics.md when a tool came back
[general]and you are deciding whether to curate it or extend the catalog.
Gotchas
- Auto-tagging covers only ~60% of tools. Expect 10-20 tools tagged as
[general]afterinit. Curate them withmcp-index tag, or — better, if the server is worth describing for every project — add itstools.jsonto the framework's mcp catalog. - The first
updateafter upgrading rewrites heuristic tags. Any tool the catalog describes gets the curated tags and intent, because an entry with noorigincannot be told apart from a guess. Tools curated withmcp-index tag/intentfrom now on are markedmanualand left alone. - Index goes stale after adding MCP servers. Run
mcp-index updateafter everyhermes mcp addorhermes mcp remove. - Tags aren't free-form. Use only tags from the taxonomy.
mcp-index tagrejects unknown tags. - Intent field is for disambiguation, not documentation. A 10-30 word sentence beats a paragraph. Write it to answer: "why would I pick this tool over a sibling with the same tags?"
- The
listfilter uses substring matching on tool names. Avoid naming tools with names that are substrings of each other in tests. --targetis required. The script does not default to.— always pass--target <path>.- The two hosts name the same server differently.
claude mcp listdecorates a server with where it routes it from —plugin:<plugin>:<server>for a plugin-provided server,claude.ai <Name>for a connector — wherehermes mcp listprints the bare name. A source keeps whatever the host called it, so switching--hostadds sources rather than renaming them, and the sources the new listing cannot speak for are left untouched rather than removed. The mcp catalog does reach through the decoration: a listing name is matched against the decorated name first and the undecorated server second, soplugin:ai-badger:code-review-graphpicks upfeatures/common/mcp/code-review-graph/tools.json. Curating a specific plugin's copy is possible — set"server": "plugin:<plugin>:<server>"in itstools.jsonand the exact key wins — because two plugins may ship same-named servers, which is also why the bare name is never rewritten.
Verification Checklist
-
mcp-index initproduces.ai-badger/mcp-tools.jsonwith all current MCP servers -
mcp-index validateexits 0 - No tools are tagged
[general](all manually curated) - Every tool has a meaningful intent (≥10 chars, describes what it does)
-
mcp-index listshows all expected tools - All tests pass:
python3 -m pytest tests/test_mcp_index.py -q