A CLI + MCP server for tracking, rating, and managing AI models across three local backends: Ollama (via Docker), llama.cpp (.gguf files), and ComfyUI (.safetensors, .ckpt, etc.). State is stored in a local SQLite database.
Three files, one engine:
mr_core.py— engine/logic (no UI)mr.py— interactive CLI (click + rich)mr_mcp.py— MCP server so agents can manage the registry over the LAN without shell/filesystem access
- Scan all three backends in one command and keep a unified registry
- Rate models 1–5 and assign statuses (
active,favorite,on_hold,blacklisted, …) - Pull models from HuggingFace or CivitAI with download tracking
- Enrich registry entries with HF download counts, likes, architecture, and CivitAI trigger words
- Blacklist models with a reason so you're warned before accidentally re-downloading them
- Cross-backend duplicate detection (same
hf_repoon multiple backends) - Audit log: every mutating operation is recorded in an
eventstable - Model tagging system for custom categorization
- Copy models between GGUF backends
- Restore missing ComfyUI models from their recorded source
- Automatic directory flattening for HuggingFace downloads
- Python 3.10+
- Docker (for the Ollama backend)
- pip packages:
click,rich,requests,huggingface_hub,gguf,mcp[streamable-http](seerequirements.txt)
git clone https://github.com/TorvaldUtne/model-registry.git
cd model-registry
python -m venv venv
# Windows
venv\Scripts\activate
# macOS / Linux
source venv/bin/activate
pip install -r requirements.txtOn Windows you can copy or symlink mr.py and create a small wrapper so you can type mr anywhere:
@echo off
python "C:\path\to\model-registry\mr.py" %*Save it as mr.bat somewhere on your PATH.
# First-time setup (interactive wizard, writes config.json)
python mr.py init
# Scan all enabled backends
python mr.py scan
# List locally installed models
python mr.py list
# Show full details for a model
python mr.py show llama3
# Rate a model interactively
python mr.py rate llama3
# Pull a model from HuggingFace (llama.cpp backend)
python mr.py pull bartowski/Llama-3.2-3B-Instruct-GGUF
# Pull from CivitAI into ComfyUI
python mr.py pull "urn:air:sdxl:checkpoint:civitai:2218365@2741096"
# Fetch HuggingFace + CivitAI metadata for all registry entries
python mr.py enrich
# Summary report
python mr.py reportmr init generates config.json next to mr.py. You can also copy config.example.json as a starting point:
{
"registry_db": "",
"backends": {
"ollama": {
"enabled": true,
"mode": "docker",
"docker_container": "ollama"
},
"llamacpp": {
"enabled": true,
"model_dir": "/path/to/gguf/models",
"extensions": [".gguf"]
},
"comfyui": {
"enabled": false,
"base_dir": "/path/to/ComfyUI/models",
"extensions": [".safetensors", ".ckpt", ".pt", ".pth", ".bin"]
}
},
"huggingface": {
"token_env_var": "HF_TOKEN"
},
"civitai": {
"token_env_var": "CIVITAI_API_KEY",
"web_host": "civitai.red",
"api_host": "civitai.com"
},
"display": {
"date_format": "%Y-%m-%d",
"max_name_width": 60
},
"mcp": {
"host": "0.0.0.0",
"port": 8321,
"mode": "readonly",
"stateful": false
}
}Set registry_db to an absolute path to store the database somewhere other than the script directory. Leave blank to use registry.db next to mr.py.
CivitAI split its web frontend: civitai.green (SFW only) and civitai.red (includes NSFW; serves all models). civitai.web_host controls the browse links shown by mr show (default civitai.red); civitai.api_host controls API calls (default civitai.com, except that a CivitAI URL ref uses the host from that URL).
The optional mcp section configures the MCP server:
"mcp": {
"host": "0.0.0.0",
"port": 8321,
"mode": "readonly",
"stateful": false
}Start the MCP server so agents (e.g. Hermes) can use mr without being given shell or filesystem access:
python mr_mcp.py # read-only server on 0.0.0.0:8321
python mr_mcp.py --read-write # also expose write tools
mr-serve.bat --read-write # same via launcher- Endpoint:
http://<host>:8321/mcp - Stateless by default: the Streamable HTTP transport mints no session id and ignores any
mcp-session-idheader, so a client that cached a session id from a previous run (or after the 30-minute idle expiry) can never hit a hard404. These tools are pure request/response, so nothing depends on session state. Pass--stateful(or set"stateful": truein themcpconfig) to opt back into stateful sessions if a client needs resumability or server-initiated requests. - Read-only by default: exposes
mr_list,mr_show,mr_report,mr_search,mr_backendsonly. - Read-write (
--read-writeor"mode": "readwrite") additionally exposesmr_scan,mr_enrich,mr_rate,mr_status,mr_note,mr_touch,mr_tag,mr_untag,mr_pull,mr_delete,mr_remove,mr_removeall,mr_rename,mr_copy,mr_blacklist,mr_restore. - Destructive tools (
pull,delete,remove,removeall,rename,copy,blacklist,restore) requireconfirm=Truein the call, so an agent always states intent explicitly. - No authentication — run it only on a trusted local network.
Tools return structured JSON (never raw shell). Each engine_* function in mr_core.py is registered once as an mr_<name> tool.
Tokens are read from environment variables (not stored in config):
export HF_TOKEN=hf_... # HuggingFace (required for gated repos)
export CIVITAI_API_KEY=... # CivitAI (required for gated models)| Command | Description |
|---|---|
init |
Interactive setup wizard. Writes config.json and initialises the DB. |
scan |
Scan all enabled backends and update the registry. |
list |
List registry entries (local & non-blacklisted by default). |
show MODEL |
Full detail panel for a model. |
rate MODEL |
Set rating (1–5), status, and an optional note interactively. |
status MODEL STATUS |
Change status without touching the rating. |
note MODEL [TEXT] |
Append a timestamped note. |
touch MODEL |
Mark last_used = now (for tracking use outside this tool). |
report |
Summary: totals by backend/status, unrated list, blacklisted list, cross-backend duplicates. |
pull REF [VARIANT] |
Download a model and register it. See below. |
rename MODEL NEW_NAME |
For GGUF backends, renames the directory containing the model (file name unchanged). For ComfyUI, renames the file in place (subdir stays put) and enriches the new name with CivitAI metadata when available. |
delete MODEL |
Remove a model from Ollama or disk; DB record is kept with status=deleted. |
remove MODEL |
Hard delete a model from the registry (completely removes DB entry). |
removeall |
Purge all models with status='deleted' from the registry (hard delete). |
tag MODEL TAGS... |
Add one or more tags to a model. |
untag MODEL [TAGS...] |
Remove specific tags or all tags if none provided. |
backends |
List configured backend names with their status. |
copy SRC_BACKEND DST_BACKEND MODEL_NAME |
Copy a model between GGUF backends (Ollama/ComfyUI are rejected). |
restore |
Re-download all missing ComfyUI models that have a known source. --dry-run to preview. |
blacklist MODEL [REASON] |
Blacklist and auto-delete a model. Future pulls warn before proceeding. |
enrich |
Fetch HuggingFace and CivitAI metadata for all registered models. Unenriched ComfyUI files are also matched on CivitAI by SHA-256 hash. |
search TERM |
Search registry by name, HF repo, notes, or tags. |
--backend ollama | llamacpp | comfyui
--status active | unrated | blacklisted | deleted | on_hold | testing | keep | favorite
--unrated Show only models with no rating
--all Include non-local and blacklisted models
--deleted Show only deleted models
Backend is auto-detected from the ref format:
| Format | Example | Backend |
|---|---|---|
| Plain Ollama name | llama3.2:latest |
ollama |
org/repo (HF) |
bartowski/Llama-3.2-3B-GGUF |
llamacpp |
.gguf filename |
model.Q4_K_M.gguf |
llamacpp |
hf.co/org/repo:tag |
hf.co/bartowski/Llama-3.2-3B-GGUF:Q4_K_M |
ollama |
org/repo VARIANT (new) |
mradermacher/RolePlayer-V6-LLaMa-70B-GGUF Q5_K_M |
llamacpp |
| HF resolve URL | https://huggingface.co/org/repo/resolve/main/file.safetensors |
comfyui |
| AIR tag | urn:air:sdxl:checkpoint:civitai:1234@5678 |
comfyui |
| CivitAI version ID | civitai:12345 |
comfyui |
| CivitAI URL | https://civitai.com/models/1234?modelVersionId=5678 |
comfyui |
Use --backend to override auto-detection. For ComfyUI downloads, --subdir is required (e.g. checkpoints, loras, vae); it is auto-detected from AIR tags, the HF file path, and the CivitAI API when possible, and the command fails with a clear error when it cannot be determined. --rename saves CivitAI pulls under a metadata-rich name (name + version + base model + fp + CivitAI id) instead of the upstream filename.
# Force a HF repo into ComfyUI/loras
python mr.py pull stabilityai/stable-diffusion-xl-base-1.0 --backend comfyui --subdir checkpoints
# Specific GGUF variant via glob pattern
python mr.py pull bartowski/Llama-3.2-3B-Instruct-GGUF --file "*Q4_K_M*"| Status | Meaning |
|---|---|
unrated |
Default; not yet evaluated |
active |
In regular use |
favorite |
Top-tier model |
keep |
Worth keeping, not actively used |
testing |
Under evaluation |
on_hold |
Paused — may reconsider later |
blacklisted |
Do not use; warns on future pull attempts |
deleted |
Removed from disk; DB record retained (or removed entirely with mr remove) |
The SQLite database (registry.db) has two tables:
models— one row per model with all metadata (rating, status, size, source URL, HF stats, CivitAI trigger words, tags, etc.)events— append-only audit log of every mutating operation
The schema migrates automatically when new columns are added, so existing databases are never broken by upgrades.
Models can be tagged with custom categories:
python mr.py tag gembrain RP General Coding Uncensored
python mr.py untag gembrain uncensoredTags appear in mr list and mr show output, and can be searched:
python mr.py search RPpip install -r requirements-dev.txt
python -m pytest tests -qThe suite runs entirely against a temporary SQLite database and temporary model directories, so it never touches your real registry or model files.
The version lives in mr_core.__version__ (re-exported by mr.py). Run mr without arguments to see it:
$ mr
Model Registry v1.5.0
Run mr --help for available commands.MIT — see LICENSE.
mr enrichnow matches unenriched ComfyUI files on CivitAI by SHA-256 hash, backfilling provenance (source_url/source_type), base model, and trigger words. Addsfile_hash/hash_checkedcolumns; unmatched files are cached so they aren't re-looked-up (--allre-checks).- CivitAI browse links are configurable:
civitai.web_host(defaultcivitai.red;civitai.greenis SFW-only) andcivitai.api_host(defaultcivitai.com). mr renamefor ComfyUI now renames the file in place (subdir stays put) and enriches the new name with CivitAI metadata; GGUF backends still rename the containing directory. Model resolution tolerates a filename passed with its extension (foo.safetensors→foo).mr pull --backend comfyuiauto-detects the subdir from the repo file path or CivitAI file type and no longer nestsmodels/loras/loras/foo; meaningful extra folders (e.g.loras/author/foo) are preserved. Bundled multi-version AIR tags (civitai:ID@VER+VER2) are supported.- CivitAI pull fixes: correct file id selection, subdir, trigger-word capture, and sanitized filenames; HF pulls show per-file download progress.
- MCP transport is stateless by default (
stateful: false), so a stale or expiredmcp-session-idcan never cause a hard404; pass--stateful(or"stateful": true) to opt back in. - Scan fixes: ComfyUI duplicate detection, no re-fetching of known GGUF context windows, and no no-op
scan_updatedevents when nothing changed. - Fixed a batch of silent-wrong-behavior bugs across the engine, CLI, and MCP server; added a pytest suite under
tests/.
- New architecture: engine extracted to
mr_core.py;mr.pyis the CLI; addedmr_mcp.py, an MCP (Streamable HTTP) server for agent access over the LAN. - Implemented read-only / read-write modes with opt-in gating so testing write paths is safe (see "Agent access via MCP").
- Existing CLI behavior and output preserved; interactive prompts still live in the CLI, engines never prompt.
- Fixed
param_countenrichment (HuggingFacesafetensors.totalkey was never read) - Fixed
mr pullto ComfyUI overwriting an unrelated Ollama row that shared the samehf_repo - Rewrote the GGUF context-length reader: the raw header parser (now tried first, ~5ms) conforms to GGUF v1/v2/v3, and the
gguflibrary is the fallback. Previously the binary parser read wrong offsets and never foundcontext_length mr enrichnow commits after each model so progress survives interruption; GGUF context extraction no longer needs network calls for local filesmr delete/mr blacklistnow remove the whole model directory when it is exclusively that model's (including multi-shard GGUF siblings); backend roots and ComfyUI category subdirs are never removedmr removeallnow actually purges deleted rows (previously a no-op that re-logged them)- Scan no longer re-fetches HuggingFace context windows for models that already have one, and no longer writes no-op
scan_updatedevents when nothing changed mr restorenow reports real failures instead of counting every pull as success- CivitAI download filenames are sanitized (no path separators or unsafe characters)
mr copyrejects Ollama/ComfyUI sources and destinations with clear errors- Fixed a crash when a ComfyUI HuggingFace download was flattened before its size was read
- Fixed broken INSERT for top-level single-file GGUF models (missing
currently_localvalue since v1.2.7)
- Added
mr tagandmr untagcommands for model categorization - Added
mr copycommand to duplicate models between backends - Added
mr removeandmr removeallfor hard deletion - Added
mr backendsto list configured backends - Fixed
mr renameto only rename directories, not files - Fixed HuggingFace downloads to flatten nested subdirectories
- Added new download syntax:
mr pull org/repo variant(instead oforg/repo:variant) - Added
--deletedflag tomr listto show deleted models - Added
--deletedflag tomr searchto include tags in search
- Initial release with scan, rate, pull, blacklist, enrich, and other commands