MCP-Server, der kleine Scripts (scripts/<name>/) als MCP-Tools bereitstellt — mit
Run-Dirs, Pipeline-Runner und einer zentralen Script-Registry, sodass neue/entfernte
Scripts ohne Neustart verfügbar werden.
zentraler Host Client (Mac etc.)
┌──────────────────────────────┐ ┌─────────────────────────────────┐
│ Registry-Server (Docker) │ HTTP GET │ tools-registry-Client (stdio) │
│ = tools-registry-Container │ ◀──────────── │ - zieht Katalog, cacht lokal │
│ serve scripts/ über │ Poll (5s) │ ~/.cache/tools-registry/... │
│ /registry + /file │ ─────────────▶│ - registriert Tools live │
│ Quelle = Bind-Mount scripts/│ Bytes │ - führt Scripts LOKAL aus │
└──────────────────────────────┘ └─────────────────────────────────┘
- Registry-Server (
src/registry-server.ts): liefertscripts/per HTTP aus. Läuft als Container auf einem zentralen Host und bind-mountet das Repo-scripts/-Verzeichnis (kein Image-Rebuild bei Script-Änderungen). Ein vom Netz-Mount unabhängiger Pfad an die Dateien. - MCP-Client (
src/index.ts,src/registry.ts): spiegelt den Katalog in einen lokalen Cache und führt von dort aus. Ein Poll-Loop (keinfs.watch— funktioniert nicht über SMB) gleicht die Tool-Liste live ab: neu → registrieren, entfernt →remove(), geändertes Manifest → re-registrieren. Das SDK sendet je Mutationnotifications/tools/list_changed.
Identifier eines Scripts ist der Verzeichnisname (für Datei-Pfade); der MCP-Tool-Name
kommt aus manifest.name (das der Client lokal aus dem Cache liest).
Manche Tools brauchen schwere Helfer (Chromium, LibreOffice, WeasyPrint, poppler). Die würden
sonst auf jedem Client liegen müssen — denn Scripts laufen Client-lokal, nicht im
Registry-Container. Stattdessen bündelt ein zustandsloser Konvertier-Dienst (convert-service/,
eigener Container auf dem zentralen Host) diese Helfer an einem Ort. Die Scripts sind dann
dünne curl-Wrapper: lokale Datei rein → fertige Datei raus.
Client (Mac/Linux/…) zentraler Host (LAN)
run.sh = curl-Wrapper --POST Datei--> tools-convert (Docker)
liest lokale Datei ?params chromium · libreoffice · weasyprint
schreibt output_path <--Bytes----- · pandoc · poppler + stdlib-HTTP-Server
tools-registry (Docker, Fileserver scripts/)
- Vorteil: Clients brauchen nur
curl— kein Tool-Zoo pro Host, kein Snap-Confinement. Lokaldatei-Zugriff bleibt, weil der Wrapper lokal läuft und nur die Bytes hoch-/runterlädt. - Trade-off (bewusst): Konvertierung braucht Netz zum Dienst; offline = Fehler (kein Local-Fallback, sonst wären die Host-Deps zurück).
- Endpunkte (
convert-service/server.py, Body = rohe Datei-Bytes, Antwort = Ergebnis-Bytes):POST /html_to_pdf?theme=&landscape=&wait_ms=,POST /md_to_pdf?design=,POST /docx_to_pdf(Metadaten inX-Converter/X-Warning),POST /pdf_to_text?layout=,GET /health. - Diese Tools nutzen den Dienst:
html_to_pdf,md_to_pdf,docx_to_pdf,pdf_to_text.
Deploy (Host via Env oder untracked .deploy.env):
HOST=my-host ./deploy-convert.sh # scp convert-service/ → docker compose up -d --build
# Endpoint: http://<host>:3459 (Host-Port; 3458 = mykeyvault-mcp auf mystorage)scripts/<name>/manifest.yaml + ausführbares exec-Script:
name: mein_tool # MCP-Tool-Name (mcp__tools__mein_tool)
description: Was es tut.
exec: ./run.sh
inputs:
scope:
type: string # string | number | integer | boolean
description: "..."
required: false
outputs: # optional, informativ
report_file: { type: string, description: Pfad zum Ergebnis. }
ai_rem_entity: tool_mein_tool # Konvention: korrespondierende ai-rem Tool-EntityDas Exec-Script bekommt pro Input INPUT_<NAME> als Env-Var sowie TOOLS_RUN_DIR
(Schreib-Verzeichnis). Benannte Outputs werden über <run_dir>/outputs.json zurückgegeben.
Auf dem zentralen Host editiert (per Netz-Mount oder direkt) → die Registry liefert die Änderung sofort aus, der Client übernimmt sie beim nächsten Poll (≤5 s) ohne MCP-Neustart.
Ein Script kann Dateien beilegen (assets/, werden mitgespiegelt) und sie lokal installieren —
so lässt sich ein Claude-Code-Skill systemübergreifend verteilen, ohne manuelles Kopieren.
Siehe scripts/magic3-design/: legt SKILL.md + Spec + Theme als Assets bei und schreibt sie via
mode=apply nach ~/.claude/skills/magic3-design/ (mode=check zeigt nur den Diff).
list_scripts— listet alle aktuell registrierten Scripts.pipeline_run— verkettet mehrere Script-Aufrufe (${var}-Interpolation ausoutputs_as).
Registry-Server auf dem zentralen Host bauen/deployen (Host via Env oder untracked
.deploy.env setzen — HOST=<ssh-host> REMOTE_DIR=<pfad>):
HOST=my-host ./deploy-registry.sh # tsc → scp → docker compose up -d --build
# Endpoint: http://<host>:3457 (/health, /registry, /registry/file)Client: als Plugin tools@tools-registry einrichten (siehe
Setup: tools-Server als Plugin) —
registry_url/token setzen den Registry-Modus.
| Variable | Wirkung |
|---|---|
TOOLS_REGISTRY_URL |
aktiviert den Registry-Client (Katalog-Quelle). Ungesetzt → lokales scripts/. |
TOOLS_REGISTRY_TOKEN |
Shared Bearer-Token. Server: gesetzt → alle Endpoints außer /health verlangen Authorization: Bearer <token>. Client: schickt es mit. |
TOOLS_SCRIPTS_DIR |
lokales Script-Verzeichnis (Dev-Fallback / Quelle des Registry-Servers). |
TOOLS_CACHE_DIR |
lokaler Cache (Default ~/.cache/tools-registry/scripts). |
TOOLS_SCRIPT_ENV_PASSTHROUGH |
Komma-Liste zusätzlicher Env-Vars an Scripts (Default-Whitelist: PATH, HOME, LANG, LC_*, TMPDIR, TERM, TZ, USER, SHELL). |
TOOLS_POLL_MS |
Poll-Intervall des Live-Reloads (Default 5000). |
TOOLS_RUNS_DIR |
Wurzel der Run-Dirs (Default /tmp/tools-runs). |
TOOLS_SCRIPT_TIMEOUT_MS |
Hard-Timeout je Script-Ausführung (Default 60000). |
TOOLS_RUN_TTL_MS |
Max-Alter eines Run-Dirs; ältere werden beim Start entfernt (Default 86400000 = 24h). |
PORT / HOST |
Registry-Server (Default 3457 / 0.0.0.0). |
TOOLS_CONVERT_URL |
Basis-URL des Konvertier-Dienstes für die curl-Wrapper (Default http://192.168.2.15:3459). Override braucht Eintrag in TOOLS_SCRIPT_ENV_PASSTHROUGH. |
TOOLS_CONVERT_TOKEN |
Bearer-Token, das die Wrapper an den Dienst schicken (muss CONVERT_TOKEN des Dienstes entsprechen). |
CONVERT_TOKEN / CONVERT_PORT |
Konvertier-Dienst: optionales Bearer-Token (leer = aus) / Host-Port (Default 3459; 3458 belegt). |
Sicherheit: Der Client führt Katalog-Scripts lokal aus — die Registry darf daher nur im vertrauenswürdigen LAN erreichbar sein, nie auf einem öffentlichen Interface.
HOSTauf die LAN-IP statt0.0.0.0binden undTOOLS_REGISTRY_TOKENsetzen. (Katalog-Integrität per Signatur ist bewusst nicht implementiert — Upgrade-Pfad, falls die Registry je außerhalb des LAN läuft.)
hooks/tool-discovery.sh (Python) ist ein Claude-Code-UserPromptSubmit-Hook. Er
schickt den Prompt an den ai-rem-/discover-Endpoint und injiziert dessen Antwort als
Kontext, damit Claude passende Tools/Playbooks nutzt statt Eigenlösungen zu bauen:
<routines>— gepinnte Meta-Regeln aus ai-rem (unabhängig vom Keyword-Match). Beim ersten Prompt einer Session und danach alleROUTINES_EVERY(10) Prompts — bei jedem Prompt waren sie ~1.800 Zeichen Wiederholung, über eine 20-Prompt-Session rund 13.600 Token. Der Zähler liegt prosession_idin$XDG_RUNTIME_DIR; ohnesession_idoder bei Schreibfehler werden sie ausgegeben (lieber redundant als regelfrei).<available-tools>/<relevant-knowledge>— relevanz-gematchte Treffer, je höchstensMAX_HITS(5) Einträge, Summaries aufSUMMARY_LEN(120) Zeichen gekürzt.<active-context>/<context-uncertain>— der aufgelöste privat/work-Kontext.
Endpoint + Bearer-Token werden aus AI_REM_ENDPOINT bzw. ~/.claude.json
(mcpServers.ai-rem) abgeleitet. Der privat/work-Kontext wird hybrid bestimmt:
- Override
~/.claude/.active-context(Inhaltworkoderprivate) — höchste Priorität. - sonst
cwdgegen~/.claude/context-map.json(Pfad-Präfixe → Kontext, längster Treffer gewinnt). - kein Treffer →
private+<context-uncertain>-Marker.
~/.claude/context-map.json (Beispiel):
{ "work": ["/pfad/zu/work-root"], "private": ["/Users/<user>", "/pfad/zu/privat-root"] }Der Hook ist fail-silent (nie exit != 0) — bei Fehler/leerem Prompt/kein Treffer
bleibt er still und blockiert den Prompt nie.
dotclaude_install (kind hooks oder all, mode=apply) kopiert tool-discovery.sh
an den stabilen Pfad ~/.claude/hooks/tool-discovery.sh — so hängt settings.json
nicht mehr am Repo-Checkout-Pfad (/Volumes vs /home, Repo-Umbenennungen).
Die Registrierung in ~/.claude/settings.json unter hooks.UserPromptSubmit bleibt
ein einmaliger manueller Schritt (der Installer editiert bewusst keine fremden Hook-Arrays).
Ist der Hook deployt, aber noch nicht registriert, gibt der Lauf einen prominenten
AKTION ERFORDERLICH-Block mit fertigem JSON aus (registration_pending: true). Zu
registrierender Block:
{ "matcher": "", "hooks": [ { "type": "command",
"command": "/Users/<user>/.claude/hooks/tool-discovery.sh", "timeout": 10 } ] }Absoluter Pfad (kein ~), da der Hook via /bin/sh läuft. settings-sync (mode=check)
meldet das fehlende UserPromptSubmit-Event fortlaufend, bis registriert.
Zum Angleichen zweier Systeme (Plugins + Marketplaces, inkl. Plugin-Hooks) dient
env-align — check/apply gegen eine Referenz (SSH-Host oder settings.json),
siehe scripts/env-align/README.md.
Dieses Repo ist zusätzlich ein nativer Claude-Code-Plugin-Marketplace — ein Katalog aller eigenen MCP-Server, aus dem sich jeder Server mit einem Kommando in den passenden Scope installieren lässt. Struktur getrennt vom Script-Registry-Teil:
.claude-plugin/marketplace.json— der Katalog (Marketplace-Nametools-registry).plugins/<name>/.claude-plugin/plugin.json— ein Plugin = ein MCP-Server. Hosts, Tokens und Pfade sind ausschließlichuserConfig-Platzhalter bzw. (ai-rem, mykeyvault) CLI-Wrapper → keine Secrets im Repo.
Der Katalog ersetzt nicht die Regel „MCP minimal pro Repo": global bleibt schlank,
projektspezifische Server werden per --scope project on-demand gezogen.
claude plugin marketplace add markus7h/tools-registry # oder lokaler Pfad
claude plugin install doc-graph@tools-registry --scope project # Token via ai-rem svc pairDie Plugins ai-rem und mykeyvault (ab 2.0.0) brauchen keine Tokens im Plugin-Dialog:
sie starten die stdio-Wrapper der ai-rem-CLI (ai-rem mcp-proxy, ai-rem vault-mcp), die
Geräte-Token (OS-Keychain) und Vault-Zugang (/api/client-config) selbst auflösen.
Voraussetzungen: ai-rem ≥ 1.7.0 (ai-rem install bzw. ai-rem update), Geräte-Token im
Keychain (Kopplung oder ai-rem token --store) und ~/.local/bin im PATH (dort liegt der
ai-rem-Shim; Plugin-Manifeste expandieren ~/${HOME} nicht, daher command: "ai-rem").
claude plugin install ai-rem@tools-registry --scope user # ohne --config
claude plugin install mykeyvault@tools-registry --scope userdoc-graph und case-assist (ab 2.0.0) laufen genauso über die CLI: ai-rem svc proxy <name>
liest den Token des Dienstes aus dem Keychain (Konto svc-<name>). Voraussetzung ist ai-rem
≥ 1.12.0; einmalig pro Gerät koppeln:
ai-rem svc pair doc-graph --url https://docgraph.lan # Freigabe im Browser
ai-rem svc pair case-assist --url https://case-assist.lanuserConfig.url ist die https-Basis ohne /mcp.
Migration von 1.x: alte userConfig-Werte (url, token, dist) unter pluginConfigs
in ~/.claude/settings.json löschen (der Token stand dort im Klartext) und das alte Token
rotieren. Ein parallel von ai-rem install in ~/.claude.json eingetragener
mcpServers.ai-rem/mykeyvault dupliziert das Plugin — nur eine der beiden Varianten nutzen.
Andere Server mit Secret beziehen den Token zur Install-Zeit aus mykeyvault
(vault_write_secret schreibt ihn in eine chmod-600-Datei, gibt nur den Pfad zurück);
sensitive: true-Felder landen im System-Keychain, nicht in settings.json. Neuen Server
ergänzen: plugins/<name>/.claude-plugin/plugin.json anlegen + Eintrag in marketplace.json,
dann claude plugin marketplace update tools-registry.
Das Plugin tools liefert zusätzlich den Skill tools-registry mit
(plugins/tools/skills/tools-registry/SKILL.md): ein generierter Katalog aller
registrierten Tools, dessen Skill-Description bei jedem Prompt gematcht wird — Claude wird
so aktiv an die vorhandenen Tools erinnert, statt Bash-/Ad-hoc-Eigenlösungen zu bauen.
Die SKILL.md wird nicht von Hand gepflegt, sondern aus scripts/*/manifest.yaml
generiert (npm run gen:skill, läuft automatisch in deploy-registry.sh). Nach einer
Manifest-Änderung: generieren, committen, dann verteilt claude plugin update den Skill
mit dem Plugin.
Der tools-MCP-Server läuft als Plugin tools@tools-registry, nicht als direkter
mcpServers-Eintrag in ~/.claude.json — nur so wird der Skill mitverteilt. Der Server
liegt gebündelt im Plugin (plugins/tools/server/index.mjs, eine Datei ohne
node_modules); auf dem Rechner braucht es nur node ≥ 20, keinen Klon und keinen Build:
claude plugin marketplace add markus7h/tools-registry
claude plugin install tools@tools-registry --scope user \
--config registry_url=http://<registry-host>:3457 \
--config token=<TOOLS_REGISTRY_TOKEN> # muss dem Server-Token entsprechen
claude mcp remove tools -s user # alten Direkt-Eintrag entfernen, sonst läuft der Server doppeltUpdate-Flow nach Script-/Manifest-Änderung:
npm run gen:skill # oder ./deploy-registry.sh (macht es mit)
git commit -am "…"
claude plugin update tools@tools-registry # neue Session → Skill + Server aktuellBundle mitcommitten: Nach jeder
src/-Änderungnpm run build(erzeugt auchplugins/tools/server/index.mjs), Bundle committen und die Plugin-Version anheben — sonst verteiltclaude plugin updateden alten Server-Code. Die Plugin-Configdistaus Versionen < 1.3 ist überflüssig und kann auspluginConfigsgelöscht werden.
opencode kennt keinen Plugin-Marketplace. Das Script opencode_sync (scripts/opencode-sync/)
übersetzt die mcpServers der Plugins in mcp-Einträge einer opencode.json — global
(~/.config/opencode/opencode.json) oder pro Projekt (target=<projekt>/opencode.json).
Hat die Ziel-Datei schon mcp.servers (opencode 2.x), wird dort geschrieben, sonst flach
(von 1.x und 2.x gelesen). ai-rem und mykeyvault richtet weiterhin ai-rem install ein.
${CLAUDE_PLUGIN_ROOT} (Plugin tools) zeigt bei lokaler source auf den Klon, sonst auf
die in Claude Code installierte Plugin-Version.
Werte für ${user_config.*} in ~/.config/opencode/tools-registry.json (sonst Claude-Code-
pluginConfigs, sonst Default). Geheime Felder als {file:…}/{env:…}-Referenz; ein
Klartextwert wird in <ziel-dir>/secrets/<plugin>.<feld> (chmod 600) abgelegt und referenziert.
{ "plugins": {
"paperless": { "dist": "/pfad/paperless-mcp/dist/index.js", "url": "https://paperless",
"token": "{file:~/.config/opencode/secrets/paperless.token}" },
"paperless-ai": { "dist": "/pfad/paperless-ai/mcp/dist/index.js",
"_environment": { "NODE_EXTRA_CA_CERTS": "/pfad/caddy-root.crt" } } } }Aufruf über den tools-MCP (mode=check zeigt den Diff, mode=apply schreibt mit Backup),
danach opencode neu starten.
npm install
npm run build # tsc → dist/ (dist/index.js, dist/registry-server.js)
# + esbuild → plugins/tools/server/index.mjs (Plugin-Bundle, wird committet)- ai-rem — Langzeit-Gedächtnis als Knowledge-Graph-MCP. Pro Script wird per
ai_rem_entity-Konvention eineTool-Entity gepflegt, damit der Katalog auffindbar bleibt. - mykeyvault — self-hosted Secrets-Vault (Vaultwarden + REST/MCP). Script-Secrets werden über die mykeyvault-vault-api bezogen, statt sie in Scripts oder Configs abzulegen.