okf-hub
You can use this MCP server as a read-only knowledge-base hub with a proposal-submission channel, but it currently reports no registered bases.
List available knowledge bases and optionally inspect pending proposal concerns.
Search a base’s corpus with keyword (AND, with OR fallback) or regex mode.
Read a whole document or a specific section, with table-of-headings fallback for large documents.
Read a base’s governance rules and frontmatter schema.
Submit proposals for manager review without directly modifying the corpus.
Rescan
bases-dirto discover newly imported bundles in the current session only.Note: the exposed schema lacks the README’s
kb_findandkb_proposal_statustools, and no bases are registered at present.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@okf-hub Search the knowledge base for 'bundle spec' and summarize the key points."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
OKF Bundle Hub
Serveur MCP donnant à des sessions Claude multiples et isolées un accès en lecture à des bases de connaissance markdown, et un moyen de les alimenter sans jamais les modifier directement.
Implémente la spécification « OKF Bundle Hub v0 » (identifiant de version dans
les manifestes : bundle-spec: "0.1").
Documentation
Document | Contenu |
La spécification. Elle fait autorité ; tous les renvois « § x.y » du code y renvoient. | |
Conception : choix d'implémentation, mécanismes de correction, écarts assumés, traçabilité test ↔ exigence. | |
Contrat des huit outils | |
La spec OKF externe et ses trois divergences avec celle du hub. | |
Orientation pour une session ouvrant ce dépôt. | |
Déroulé de revue du rôle gestionnaire. |
Related MCP server: Claude PM MCP
Principes
Git est canonique. Tout l'état vit dans les dépôts git des bases. Aucune base de données. Tout index ou cache est dérivé et régénérable.
Frontière de confiance à l'écriture. Les sessions consommatrices ne modifient jamais le corpus : elles déposent des propositions. Seul le rôle gestionnaire intègre.
Une base sans le hub reste utilisable. Un bundle est un dépôt markdown lisible par un humain ou n'importe quel outil.
Optimisation = économie de tokens. Les outils retournent le minimum pertinent ; toute sortie volumineuse est plafonnée (~4 000 tokens) avec troncature signalée.
Démarrage
Tout en une commande
Une fois les dépendances installées (uv sync, déjà fait par le
post-create du devcontainer), okf-hub setup enchaîne les cinq étapes
détaillées plus bas — sans en remplacer aucune : c'est le même résultat,
automatisé pour les cas qu'il peut détecter sans deviner un identifiant ou un
secret externe.
uv run okf-hub setupIdentité git — configurée si elle manque encore (interactif ;
--yespour sauter la saisie plutôt qu'attendre une réponse).Clé(s) SSH — dans un devcontainer, délègue à
.devcontainer/deploy-keys.sh(idempotent : relancerokf-hub setupaprès avoir enregistré une clé sur GitHub la valide).Client MCP — enregistre
okf-hubauprès de Claude Code (claude mcp add) si la commandeclaudeest dans le PATH, et met à jour la config de Claude Desktop si elle est détectée sur la machine.Bases livrées — installe ce qui manque dans
bases/(déjà fait automatiquement au premier démarrage du serveur ; utile ici pour une confirmation immédiate, sans attendre une connexion cliente).Hooks de confidentialité — active
.githooks/sur le hub et ses bases publiques : un commit ou une poussée qui publierait un terme privé est refusé (voir « Configuration locale, jamais versionnée »). Le chemin posé est relatif, donc valable dans un devcontainer comme sur l'hôte qui partage le clone. Les hooks prennent l'interpréteur deOKF_HUB_PYTHON, à défaut celui de.venv/, à défautpython3: sur un hôte dont le.venv/est celui du conteneur, poserOKF_HUB_PYTHON, sans quoi tout commit est refusé.
Chaque étape sans objet dans l'environnement courant (pas de devcontainer,
aucun client MCP détecté, Claude Desktop absent, bootstrap-bundles: false)
est signalée comme telle dans le rapport final — jamais silencieuse — et
renvoie vers la procédure manuelle correspondante ci-dessous : hub hors
devcontainer, client MCP configuré à la main, hub derrière docker exec.
Dans le devcontainer (recommandé)
Ouvrir le dépôt dans VS Code → Reopen in Container. Le post-create installe
ripgrep, uv et les dépendances, pose l'identité git, prépare les deploy keys,
puis lance les tests.
Identité git
Un commit poussé sous une adresse qui n'existe pas n'est rattaché à aucun compte
GitHub : ni auteur identifiable dans l'historique, ni contribution comptée. Le
conteneur lit donc .devcontainer/git-identity.env, non versionné — chaque
mainteneur a la sienne :
cat > .devcontainer/git-identity.env <<'EOF'
OKF_GIT_NAME="Prénom NOM"
OKF_GIT_EMAIL="<id>+<login>@users.noreply.github.com"
EOFL'adresse noreply de GitHub (Settings > Emails) garantit le rattachement au
compte sans publier d'adresse personnelle. Sans ce fichier, post-create.sh
retombe sur une identité locale et le signale bruyamment : c'est un défaut
de repli, pas un réglage.
Pousser depuis le devcontainer
Un remote en git@github.com: n'a dans un conteneur ni clé ni agent —
git push échoue en « Permission denied (publickey) », et le commit reste sur
place sans que rien ne le signale. Les remotes en https:// semblent marcher
sans rien faire, mais par le helper de credentials de VS Code : ils tombent dès
qu'un processus git tourne hors de cette session — bootstrap.py au démarrage
du serveur, un docker exec.
.devcontainer/deploy-keys.sh règle les deux cas avec une deploy key par
dépôt, dans un volume nommé monté sur ~/.ssh (elles survivent aux rebuilds).
Il inventorie les dépôts — origin du hub, amonts de bundles/upstreams.yaml,
remotes des bases clonées —, génère la clé manquante de chacun, écrit le
~/.ssh/config associant dépôt et clé, et affiche les clés publiques restant à
enregistrer avec l'URL exacte où le faire (Settings > Deploy keys > Add deploy
key, cocher Allow write access pour pouvoir pousser). Il est idempotent :
relancez-le après chaque enregistrement.
Jamais de clé de compte (Settings > SSH and GPG keys) : elle ouvrirait tous les dépôts du compte, en écriture, depuis un conteneur où tournent des sessions Claude. Une deploy key ne vaut que son dépôt et se révoque seule.
Les URL versionnées ne sont pas touchées : bundles/upstreams.yaml doit rester
clonable depuis n'importe quelle machine. L'aiguillage vers la bonne clé passe
par des url.<alias>.insteadOf dans la config git globale du conteneur,
posés uniquement pour les deploy keys déjà acceptées par GitHub — tant qu'une
clé n'est pas enregistrée, l'accès existant continue de fonctionner. La
migration se fait donc dépôt par dépôt, sans coupure.
Sans aucun matériel de clé dans le conteneur : faire tourner un ssh-agent
sur l'hôte avant d'attacher VS Code, qui en transmet la socket. Le montage
devient alors inutile et peut être retiré — mais le hub ne peut plus rien
pousser quand VS Code est détaché.
Ce montage est un écart assumé à la lettre du § 4.3 de la spec (« montage : le
répertoire du hub uniquement ») : motif, mesure de ce qu'il ouvre et manière de
l'annuler sont dans docs/ARCHITECTURE.md § 5.3. C'est
un volume, jamais un bind sur un répertoire de l'hôte — la distinction est
gardée par un test.
Enregistrement automatique de la clé (GitHub App, optionnel)
L'enregistrement d'une deploy key reste, par défaut, la procédure manuelle ci-dessus : coller la clé publique affichée dans Settings > Deploy keys. Pour l'automatiser — utile dès qu'on ajoute plusieurs bases — installer une GitHub App dédiée, une fois, hors du conteneur :
Créer la GitHub App (github.com → Settings > Developer settings > GitHub Apps > New GitHub App), avec la permission de dépôt Administration : Read & write, et aucune autre. C'est la permission GitHub App la plus étroite qui couvre la gestion des deploy keys par API — GitHub n'expose pas de permission plus fine dédiée aux seules deploy keys ;
Administrationpermet aussi des actions plus larges sur le dépôt concerné (renommer, gérer la protection de branches, etc.). Aucun webhook n'est nécessaire.Installer l'App sur le ou les dépôts concernés (le hub, et/ou les bases dont on gère aussi le dépôt) — ce choix borne exactement les dépôts couverts, comme pour une deploy key classique.
Sur votre machine, jamais dans le conteneur, fabriquer un jeton d'installation de courte durée (~1h, révocable en désinstallant l'App) à partir de la clé privée de l'App téléchargée à l'étape 1 :
APP_ID=<identifiant de la GitHub App> INSTALLATION_ID=<identifiant de l'installation — visible dans l'URL après l'installation> PEM=<chemin vers la clé privée .pem de l'App> now=$(date +%s) b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; } jwt_header=$(printf '{"alg":"RS256","typ":"JWT"}' | b64url) jwt_payload=$(printf '{"iat":%d,"exp":%d,"iss":"%s"}' "$((now-60))" "$((now+300))" "$APP_ID" | b64url) jwt_sig=$(printf '%s.%s' "$jwt_header" "$jwt_payload" | openssl dgst -sha256 -sign "$PEM" | b64url) jwt="$jwt_header.$jwt_payload.$jwt_sig" curl -sS -X POST -H "Authorization: Bearer $jwt" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/app/installations/$INSTALLATION_ID/access_tokens" \ | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])'Dans le conteneur, exporter ce jeton pour la durée d'une seule invocation, puis relancer l'enregistrement :
export OKF_HUB_GH_APP_TOKEN=<jeton collé depuis l'étape 3> .devcontainer/deploy-keys.shLe jeton n'est jamais écrit sur disque ni journalisé — il ne vit que dans cette variable, le temps de l'appel API, et expire de lui-même. Sans lui (cas par défaut), le script se comporte exactement comme avant : rien n'est cassé si vous ignorez cette section.
Ce que ce choix n'est pas : jamais un jeton de compte GitHub, jamais un
scope repo global, et surtout — la clé privée de la GitHub App elle-même
n'entre jamais dans ce conteneur, où tournent des sessions Claude qui
exécutent du code ; seul un jeton d'installation déjà limité et expirant y
transite, une fois, à l'initiative de l'opérateur. Détail du raisonnement et
de ce que la permission Administration ouvre au-delà des deploy keys :
docs/ARCHITECTURE.md § 5.3 bis.
Sur une machine, sans conteneur
Prérequis : Python ≥ 3.11, git, ripgrep.
curl -LsSf https://astral.sh/uv/install.sh | sh # si uv n'est pas installé
uv sync
uv run pytest -qOutils MCP
Outil | Rôle |
| Bases disponibles, avec titre, objet, nombre de documents et de propositions en attente, et date de la plus ancienne. |
| Recherche plein texte dans une ou plusieurs bases. Mode |
| Lecture d'un document, ou d'une seule section. Au-delà du seuil, retourne la table des headings — |
| Documents par critères structurés — texte, type, tags, champs, présence ou absence d'une section, sous-chemin, documents qui en citent un autre (jusqu'au 3e degré), amont modifié depuis, ancienneté — triés au besoin par citations ou priorité de ré-audit, ou effectifs par valeur ( |
| Golden rules et schéma de frontmatter d'une base. Signale par un bandeau une gouvernance en |
| Dépose une proposition dans |
| État et résolution des propositions : intégrée (avec les documents modifiés) ou rejetée (avec le motif). Lecture pure. |
| Rapport de découverte : bundles rejetés avec motif, collisions de |
Le paramètre base est toujours le champ name du manifeste, jamais le nom du
répertoire dans bases/ — les deux diffèrent dès qu'un clone est renommé.
Le schema.yaml d'une base décrit le frontmatter de son corpus, pas celui des
propositions. Une proposition n'a pas à s'y conformer : soumettez
l'information, sa mise en forme conforme au schéma relève du gestionnaire à
l'intégration. Les champs de kb_propose sont le seul format requis.
Consulter les bases dans un navigateur
uv run okf-hub web # http://127.0.0.1:8077/, lecture seule
uv run okf-hub web --port 9000Un processus à part des instances MCP, sur le même moteur de lecture que
kb_find : liste des bases, navigation dans le corpus, documents rendus avec
leurs sources, leurs liens entrants et sortants — entre bases compris —, leur
histoire (propositions intégrées, commits, propositions en attente qui les
nomment), leur impact et la dérive de ce qu'ils citent. L'Explorer réunit
recherche plein texte et critères de kb_find, avec la répartition du résultat,
et cherche aussi dans les propositions en attente, par le même moteur que
kb_search avec include_pending ;
s'y ajoutent les vues déclarées par le profil d'une base, sa gouvernance, ses
manques, ses propositions et son journal, une chronologie de tout le hub, le
graphe des bases, l'état de la découverte et des dépôts, et une page « à
propos ».
Toutes les routes sont en GET, rien n'est jamais écrit. Ctrl+K saute vers une
base, un document ou une proposition depuis n'importe quelle page, et propose
les titres qui répondent quand rien n'est unique ; la boîte des propositions
réunit celles en attente de toutes les bases, et la page de chacune montre ce
qu'il faut pour la trancher. Les adresses
(/b/<base>/<chemin>, /b/<base>/-/propositions…) sont stables : une autre
interface y renvoie pour la lecture et passe par MCP pour les données
(spec § 5 bis).
Mode local (défaut) : pas d'authentification, toutes les bases visibles,
écoute sur une adresse de bouclage seulement. Mode équipe : un proxy inverse
authentifie (SSO) et transmet l'identité dans un en-tête ; dans
hub-config.local.yaml :
web:
host: 0.0.0.0 # hors bouclage : mode équipe seulement
identity-header: X-Forwarded-User
groups-header: X-Forwarded-Groups # facultatif
trusted-proxies: [172.18.0.5] # défaut : le bouclage
visibility: # une base absente n'est servie à personne
ma-base: ["*"]
mon-autre-base: [equipe-a]L'en-tête n'est cru que sur une connexion venue d'un proxy de
trusted-proxies ; toute autre connexion est refusée. Un lien entrant n'est
montré que si le visiteur peut lire la base qui cite.
Au démarrage de la session, par un service systemd utilisateur — à adapter aux chemins de la machine :
# ~/.config/systemd/user/okf-hub-web.service
[Unit]
Description=okf-hub : consultation web des bases (lecture seule)
[Service]
# ripgrep doit être dans ce PATH : celui des services utilisateur est minimal.
Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=<venv du hub>/bin/okf-hub --hub-root <racine du hub> web
Restart=on-failure
RestartPreventExitStatus=2 # configuration invalide : relancer n'y change rien
[Install]
WantedBy=default.targetsystemctl --user enable --now okf-hub-webLe processus lit la copie de travail du hub : après une mise à jour, le
relancer. Sous WSL, une écoute sur 127.0.0.1 est joignable depuis Windows par
http://localhost:<port>/.
Une base améliore son affichage en déclarant un profil dans son
schema.yaml — provenance et sa preuve, facettes de tags, sections à rôle,
vues (spec § 3.5) — et ses dépendances dans cites (§ 3.3). Sans profil, tout
s'affiche quand même.
Connecter un client Claude
Le transport est stdio : chaque client connecté lance sa propre instance du serveur.
Claude Code
claude mcp add okf-hub -- /chemin/vers/okf-hub/.venv/bin/python -m okf_hub \
--hub-root /chemin/vers/okf-hubOu dans .mcp.json à la racine d'un projet :
{
"mcpServers": {
"okf-hub": {
"command": "/chemin/vers/okf-hub/.venv/bin/python",
"args": ["-m", "okf_hub", "--hub-root", "/chemin/vers/okf-hub"],
"env": { "PYTHONPATH": "/chemin/vers/okf-hub/src" }
}
}
}Claude Desktop
Dans claude_desktop_config.json : mêmes command et args.
Depuis Windows, hub dans WSL
La commande doit traverser la frontière WSL. wsl.exe prend le relais :
{
"mcpServers": {
"okf-hub": {
"command": "wsl.exe",
"args": [
"-d", "Ubuntu", "--cd", "/home/<utilisateur>/okf-hub", "--",
"/home/<utilisateur>/okf-hub/.venv/bin/python", "-m", "okf_hub"
]
}
}
}Points de vigilance : utiliser des chemins Linux après --, et vérifier le
nom de la distribution avec wsl.exe -l -q.
Hub dans un devcontainer
Le client doit lancer le serveur à l'intérieur du conteneur :
{
"mcpServers": {
"okf-hub": {
"command": "docker",
"args": [
"exec", "-i", "<nom-ou-id-du-conteneur>",
"/workspaces/okf-hub/.venv/bin/python", "-m", "okf_hub"
]
}
}
}-i est indispensable : sans lui, stdin est fermé et la poignée de main MCP
échoue sans message.
Vérifier
.venv/bin/python -m okf_hub --hub-root . --verboseLe serveur attend sur stdin ; le journal part sur stderr. Ctrl-D pour sortir.
Si rien n'apparaît, hub.log porte la trace du démarrage.
Importer une base
git clone <url> bases/<nom>Puis kb_hub_rescan depuis une session connectée. Il n'y a pas d'autre
étape — c'est un invariant du produit.
Pour créer une base : partir du template
okf-bundle-template et dérouler son
INSTANTIATE.md.
Choisir dans un catalogue plutôt que taper une URL
bundles/upstreams.yaml peut porter, en plus de l'URL, un titre, une
description et des tags par base connue :
uv run okf-hub catalog list # ce qui est connu, déployé ou non
uv run okf-hub catalog list --tag rh # filtré par tag
uv run okf-hub catalog show <nom> # détail d'une entrée
uv run okf-hub catalog add <nom> <url> --title "…" --description "…" --tag rh
uv run okf-hub catalog import <nom> # git clone <url> bases/<nom> — rien d'autrecatalog import exécute exactement la commande ci-dessus : c'est le même
invariant, seulement l'URL exacte à ne plus connaître ou taper. catalog add/remove ne font qu'éditer bundles/upstreams.yaml ; une entrée sans
titre ni description ni tag reste une simple URL, comme avant ce catalogue
(compatibilité intégrale avec le format historique).
⚠ Avant d'importer un bundle tiers
Importer un bundle en v0 est sans risque d'exécution — tools/ et
skills/ ne sont pas chargés — mais pas sans risque d'influence. Trois
vecteurs d'injection de prompt existent :
titleetdescriptiondu manifeste, injectés dans les descriptions d'outils MCP, donc dans le contexte de toutes les sessions connectées, sans même que quiconque ouvre le bundle ;GOVERNANCE.md, injecté dans le contexte du gestionnaire ;CLAUDE.md, dans celui de toute session ouvrant le dépôt.
La validation du manifeste limite la surface (pas de retour à la ligne dans
title, description normalisée et plafonnée à 500 caractères) sans
l'éliminer.
Consigne v0 : n'importer que des bundles de confiance, et relire le
manifeste, GOVERNANCE.md et CLAUDE.md avant le premier usage de tout
bundle tiers.
Retirer une base
Avant ce cycle, retirer une base n'était qu'une suppression manuelle de
répertoire, documentée en prose (okf-hub-guide, cycle de vie § 5), jamais
outillée :
uv run okf-hub catalog retire <nom> # garde-fous, puis suppression
uv run okf-hub catalog retire <nom> --forget # + oublie l'entrée du catalogueDeux garde-fous, jamais bloquants avec --force : aucune proposition ne doit
dormir dans proposals/pending/ de la base, et — si elle a un remote — sa
branche amont suivie ne doit pas être en retard sur HEAD (vérification
locale uniquement, sans nouvel accès réseau ; un dépôt purement local n'est
jamais concerné). La suppression elle-même reste ce qu'elle a toujours été : le
répertoire du bundle sous bases/, jamais l'entrée du catalogue (séparée,
--forget pour l'oublier aussi) ni le dépôt distant. Rien n'est perdu tant que
le dépôt existe ailleurs — un bundle est un dépôt git autonome. Le retrait est
visible dès le prochain kb_list ou kb_hub_rescan.
Modèle multi-instances — à lire avant d'exploiter
Le transport stdio implique qu'une instance du serveur tourne par client connecté. Plusieurs processus opèrent donc simultanément sur les mêmes dépôts git. Conséquences pratiques :
Chaque instance découvre les bases pour elle-même
Il n'y a ni état partagé ni démon : la vérité est sur le disque, chaque instance la relit. Deux déclencheurs automatiques, chacun sous un cooldown de 5 s par instance, font qu'une base importée après le démarrage d'une session lui devient visible sans intervention :
tout
kb_listdéclenche la découverte avant de répondre ;une erreur
UNKNOWN_BASEdéclenche un re-scan silencieux, puis retente l'appel.
Les deux comptent leur cooldown séparément : lister puis appeler dans la foulée une base importée entre-temps fonctionne, le premier appel ne consomme pas le re-scan du second.
kb_hub_rescan reste utile pour voir le rapport d'un import — bundles rejetés
avec leur motif, collisions de name — pas pour rafraîchir.
Un rescan « partagé au niveau du hub » a été demandé et refusé : il supposerait précisément l'état partagé que ce modèle exclut.
Certains clients ignorent tools/list_changed
Le serveur émet la notification MCP tools/list_changed quand la liste des
bases change. Claude Desktop l'a historiquement ignorée. L'implémentation ne
compte pas dessus : ce sont les re-scans ci-dessus qui garantissent le
fonctionnement.
Conséquence visible, et purement cosmétique : la description de kb_list,
qui énumère les bases connues, peut rester périmée dans le contexte d'une
session. Le contenu que l'outil retourne, lui, est à jour.
Les écritures sont sérialisées, les lectures ne le sont pas
Toute écriture git prend un verrou flock() exclusif sur
bases/<nom>/.okf-hub.lock — libéré automatiquement à la mort du processus, et
partagé entre le serveur et le script okf-lock. Au-delà de 15 s d'attente, une
erreur BASE_BUSY invite à réessayer.
Les lectures (kb_search, kb_read) ne prennent aucun verrou. Une lecture
pendant une intégration peut donc voir un état intermédiaire du worktree.
Accepté en v0.
Le rôle gestionnaire
Le gestionnaire n'est pas un démon : c'est une session Claude invoquée à la
demande, outillée par la skill kb-review.
Installer la skill pour Claude Code :
mkdir -p ~/.claude/skills
ln -s "$PWD/skills/kb-review" ~/.claude/skills/kb-reviewPuis, dans une session : « passe en revue les propositions de la base <name> ».
Le moteur sous-jacent est utilisable seul :
bin/okf-review reconcile <base> # étape 0, rattrapage
bin/okf-review reconcile <base> --apply
bin/okf-review context <base> # golden rules + schéma + corpus
bin/okf-review inventory <base> --full # propositions en attente
bin/okf-review resolve <base> --plan plan.json --dry-run
bin/okf-review resolve <base> --plan 01.json --plan 02.json --dry-run
bin/okf-review verify <base> --plan 01.json --plan 02.json # sur une copie
bin/okf-review resolve <base> --plan plan.json
bin/okf-review resolve <base> --plan 01.json --plan 02.json --checks--dry-run fait tous les contrôles de l'exécution sans rien écrire :
propositions présentes dans pending/, sections trouvées, fusion de frontmatter
acceptée. Répété, --plan vérifie une cascade de plans comme s'ils étaient
exécutés dans l'ordre ; l'exécution, elle, n'accepte qu'un plan à la fois.
verify va plus loin : il rejoue les plans pour de vrai sur une copie
jetable de tout le hub (chaque base clonée depuis son HEAD), puis lance les
contrôles que la base déclare dans son manifeste — ce qu'aucun --dry-run ne
peut voir, puisque ces règles sont propres à la base :
# okf-bundle.yaml de la base
governance:
rules: ./GOVERNANCE.md
checks:
- python3 _work/scripts/gen_index.py
- python3 _work/scripts/check_all.pyRien n'est lancé sans l'accord de l'opérateur : la base doit figurer dans
trusted-checks de hub-config.local.yaml (voir « Configuration »), après
relecture de ses scripts. Le serveur MCP ne lance jamais ces commandes. Les
contrôles tournent après le dernier plan ; --each les lance après chaque plan
pour désigner le fautif ; --plan <base>:<chemin> vérifie un lot qui touche
plusieurs bases, dans l'ordre d'exécution. En cas d'échec, la copie est
conservée avec un journal par commande.
resolve exécute ensuite la même suite pour de vrai : un commit par plan,
dans l'ordre, en sautant ceux dont les propositions sont déjà résolues — après
une interruption, on relance la même commande. Avec --checks, les contrôles
approuvés tournent ensuite dans la base, et ce qu'ils ont régénéré est commité
à part (regen:), sous le verrou. Le répertoire de travail doit être propre
hors proposals/ ; un contrôle en échec ne commite rien de plus.
resolve et reconcile --apply prennent eux-mêmes le verrou, à la granularité
imposée : une résolution complète = une acquisition. Ne pas les envelopper
dans okf-lock.
Pour toute autre séquence git sur une base, passer par le wrapper :
root=$(bin/okf-base-path ma-base root)
bin/okf-lock ma-base -- sh -c "git -C '$root' … && git -C '$root' commit -m '…'"Le verrou doit couvrir la séquence complète, jamais commande par commande.
Limitations v0 assumées
Le corps d'une proposition résolue n'est pas relisible via MCP
kb_proposal_status rend l'état, la résolution, integrated-into et le motif de
rejet — c'est la boucle complète du contributeur, sans accès git. Ce qu'il ne
rend pas, délibérément, c'est le corps de la proposition : il peut peser
16 Ko, et une fois intégrée, ce qui compte est le corpus, lisible par kb_read
en suivant integrated-into.
Pour relire le texte exact d'une proposition rejetée :
git -C bases/<nom> log --grep "Proposal: prop-2026-08-30-a3f2"
cat bases/<nom>/proposals/rejected/prop-2026-08-30-a3f2.mdsubmitted_by n'est pas authentifié
C'est un champ déclaratif. Il ne doit peser dans aucune décision d'intégration.
Aucun push automatique
Le clone présent dans bases/ est la copie canonique. Au démarrage, chaque
instance avance une base sur son remote en fast-forward seulement (voir
« Elles se synchronisent avec leur remote au démarrage ») ; elle ne pousse
jamais, et une divergence est signalée, jamais résolue. Pousser reste manuel et
hors garanties.
Pratique recommandée : pousser après chaque session de revue.
git -C bases/<nom> pushBases par défaut
Deux bases documentent le hub lui-même. Ce sont des bundles ordinaires —
aucun traitement de faveur dans le code, et le hub tourne sans elles — mais elles
comblent une lacune structurelle : une session connectée en MCP ne voit ni ce
README, ni docs/API.md, ni CLAUDE.md. Elle ne dispose que des outils et de
leurs descriptions.
Le serveur les annonce dans son champ instructions, le seul texte qu'une
session reçoit sans dépenser d'appel — et seulement si elles sont déployées.
okf-hub-guide — mode d'emploi pour une session
Séquences d'appels, stratégie de recherche et de lecture, rôles et frontière de confiance à l'écriture, ce qu'est une proposition recevable, et le cycle de vie complet d'une base : créer, déployer, alimenter, réviser, retirer — avec à chaque étape le rôle qui l'exécute et le moyen employé.
Elle ne contient aucun schéma d'outil, par golden rule. La référence vit dans
les descriptions d'outils et dans docs/API.md ; une troisième copie serait la
seule qu'aucun test ne garde, et une base se met à jour par le circuit de
propositions quand une référence d'API doit bouger en verrou avec le code.
Cette exclusion n'est pas qu'une intention : tests/test_bases_meta.py lit les
SCHEMA du code et échoue si un corpus meta cite un outil inexistant, attribue à
un outil un paramètre absent de son schéma, ou introduit un tableau de référence.
okf-hub-feedback — retours sur l'outillage
Le hub est son propre premier cas d'usage : les retours sur les outils — pas sur le contenu métier des autres bases — arrivent par le circuit standard.
kb_governance base=okf-hub-feedback → ce qu'un retour recevable contient
kb_propose base=okf-hub-feedback … → le dépôt
kb_proposal_status base=okf-hub-feedback id=… → le verdict, plus tardDeux golden rules décident de la recevabilité : citer l'outil concerné et décrire le comportement observé (entrées, base, sortie obtenue, sortie attendue) avant toute demande d'évolution. Son corpus porte la roadmap des évolutions — décidées, reportées, refusées, avec le motif de chaque arbitrage — et les limitations connues.
Elles s'installent au premier lancement
Leur source est versionnée dans bundles/. Au démarrage, le
serveur installe dans bases/ celles qui manquent : un git clone du hub suffit
donc à disposer du guide, sans second dépôt à cloner.
bin/okf-bootstrap --list # ce qui est livré, et ce qui est déployé
bin/okf-bootstrap # installe ce qui manque, sans rien écraserPour maîtriser entièrement le contenu de bases/, mettre
bootstrap-bundles: false dans hub-config.yaml.
Elles se synchronisent avec leur remote au démarrage
Une base clonée depuis un dépôt canonique (bundles/upstreams.yaml, ou
importée manuellement avec son propre remote) est mise à jour en
fast-forward-only à chaque démarrage d'une instance de serveur, avant sa
première découverte. Jamais de push : le § 4.5 de la spec confie toute
synchronisation à l'opérateur, ce mécanisme ne fait qu'automatiser le pull
quand il est sûr.
Une divergence — des propositions locales commitées par kb_propose mais
jamais poussées, pendant que le dépôt canonique a lui aussi avancé — n'est
jamais écrasée : elle est signalée dans hub.log, et la synchronisation de
cette base attend une résolution manuelle. Un remote absent, injoignable, ou
une base occupée par une écriture en cours ne bloque jamais le démarrage.
Désactivable par sync-on-start: false dans hub-config.yaml, pour un
opérateur qui gère ses pulls lui-même.
Pourquoi deux emplacements. Une base doit être son propre dépôt git. Si
elle n'était qu'un sous-répertoire du dépôt du hub, gitops.commit_paths
exécuterait git -C dans le dépôt englobant, et un kb_propose de n'importe
quelle session commiterait sur la branche main du hub — sans erreur. Le
détail est dans bundles/README.md.
La source de vérité diffère ensuite selon la base.
okf-hub-guide est rédigée par les mainteneurs, en verrou avec le code :
bundles/ fait foi, elle est semée de là, et un test vérifie que la copie
déployée n'en diverge pas.
okf-hub-feedback est alimentée par les sessions : son dépôt publié est
l'original, et elle est donc clonée, pas semée.
https://github.com/Movida/okf-hub-feedback
Semer une base qui a un dépôt canonique produirait sur chaque machine une
histoire git sans rapport avec la sienne, et les propositions qu'on y déposerait
seraient irrécupérables. Les dépôts canoniques sont déclarés dans
bundles/upstreams.yaml ; si le clone échoue, la base
n'est pas installée et le journal dit comment rattraper — absente vaut mieux
qu'orpheline.
Configuration
Le hub lit hub-config.yaml à sa racine (§ 4.1 de la spec). Cinq paramètres
configurables : bases-dir (emplacement des bundles), read-toc-threshold
(seuil pour retourner la table des matières au lieu du contenu complet),
log-file (journal partagé par toutes les instances), bootstrap-bundles et
sync-on-start (mentionnés plus haut).
Profils de configuration
Plutôt que de configurer chaque paramètre individuellement, on peut choisir un
profil prédéfini via le champ profile de hub-config.yaml :
profile: devProfils disponibles :
solo(par défaut) : comportement actuel — bases dans./bases, seuil 8 Ko, journal activé, bootstrap et sync automatiques au démarrage.dev: développement local —bootstrap-bundles: false,sync-on-start: false; reste identique àsolo.ci: CI/tests — seuil 16 Ko,log-filedésactivé ; reste identique àsolo.
Chaque paramètre explicite dans hub-config.yaml surcharge la valeur du profil.
Un opérateur qui ne configure rien (fichier absent ou vide) bénéficie du profil
solo, garantissant le comportement actuel.
Exemple combinant profil et surcharge :
profile: dev
read-toc-threshold: 4096 # surcharge du profil devConfiguration locale, jamais versionnée
hub-config.local.yaml, à côté de hub-config.yaml, est ignoré par git : ses
clés remplacent celles du fichier versionné. Il porte ce qui appartient à
l'opérateur de la machine — et nomme ses bases, qui n'ont rien à faire dans
le dépôt public du hub :
# Bases dont `okf-review verify` peut lancer les contrôles, après relecture.
trusted-checks: [ma-base, mon-autre-base]
# Termes qu'aucun dépôt public ne doit contenir, en plus des noms des bases
# privées installées (déduits d'office).
private-terms: [MonOrganisation, NOM_DE_PROJET]
# Bases publiques en plus de celles livrées avec le hub (facultatif).
public-bases: []Sont publics le hub et ses bases livrées qui ont un dépôt canonique
(okf-hub-feedback). Trois gardes y tiennent les termes privés à l'écart :
kb_propose refuse de les déposer dans une base publique, les hooks de
.githooks/ (activés par okf-hub setup) refusent de les commiter ou de les
pousser, et tests/test_neutralite.py les cherche dans les fichiers versionnés.
Invariants d'audit
Toute proposition apparaît dans exactement deux commits : un de soumission, un de résolution (éventuellement partagé avec d'autres propositions du même sujet).
git -C bases/<nom> log --grep "Proposal: <id>" # histoire d'une proposition
git -C bases/<nom> log --grep "Submitted-By: <qui>" # contributions d'un auteurCes invariants reposent sur le rejet des retours à la ligne dans concerns,
submitted_by et sources : sans cette validation, un contributeur pourrait
forger de faux trailers.
Architecture
src/okf_hub/
├── __main__.py point d'entrée stdio
├── server.py câblage MCP, re-scan (kb_list et UNKNOWN_BASE), notifications
├── config.py hub-config.yaml
├── registry.py découverte, corpus, exclusions, confinement des chemins
├── manifest.py validation de okf-bundle.yaml
├── locking.py flock() — fd neuf par acquisition
├── gitops.py index git temporaire initialisé depuis HEAD, identité explicite
├── search.py ripgrep, ET strict puis repli OU
├── bootstrap.py installation des bases livrées (bundles/ → bases/)
├── remote_sync.py synchronisation fast-forward-only avec le remote, au démarrage
├── governance.py statut draft/stable d'un GOVERNANCE.md
├── mdutil.py frontmatter, headings, sections
├── frontmatter.py fusion de frontmatter à représentation préservée, ou refus
├── textutil.py plafonnement des sorties
├── review.py moteur du rôle gestionnaire
└── tools/ un module par outil kb_*Langage, SDK, bibliothèque YAML, accès git, forme du wrapper de verrouillage :
ces choix étaient laissés ouverts par la spec (§ 10.1). Le raisonnement derrière
chacun, la carte détaillée des modules et les parcours d'appel sont dans
docs/ARCHITECTURE.md.
Écarts assumés par rapport à la spec
Trois, tous mesurés, documentés et réversibles :
Déclassement de
index.mdetlog.mddanskb_search— sur un corpus réel de 856 documents, 28 % des résultats étaient des sommaires générés ; 2 % après déclassement.Synchronisation de l'index git partagé après commit — sans elle,
git statusaffiche toutes les propositions commitées comme supprimées, et l'étape de réconciliation les re-commite, cassant l'invariant d'audit.ruamel.yamlpour fusionner un frontmatter — le principe § 1.7 exige une bibliothèque YAML, pas nommément PyYAML ; réémettre tout le frontmatter avecsafe_dumpreformatait les champs non ciblés (tags: [a, b]repassé en style bloc, timestamp ISO réécrit), ce qui égarait sans erreur les parseurs mono-ligne des corpus réels. La fidélité est vérifiée avant écriture ; ce qui n'est pas vérifiable est refusé.
Motif complet, mesure et manière de les annuler :
docs/ARCHITECTURE.md, section « Écarts assumés ».
Tests
uv run pytest -q # tout, dont la boucle de stress
uv run pytest -q -m "not slow" # sans le test deux instances × 25 itérationsCouvre notamment : validation de manifeste et collisions de name, confinement
des chemins et symlinks sortants, repli ET→OU, headings dupliqués et formatés,
plafonnement des sorties, non-destruction du tree, worktree sale, dépôt sans
HEAD, collision d'identifiant, tentatives d'injection de trailers, exclusion
mutuelle entre okf-lock et le serveur, et deux instances proposant en
parallèle. Pour la consultation (rév. 4.5) : classement et extraction des liens,
profil, invalidation du cache, liens entrants entre bases, kb_find, rendu
sans HTML ni lien dangereux, images confinées, mode équipe (connexion directe,
identité, visibilité, liens masqués), et lecture seule de bout en bout.
tests/test_boucle_contribution.py déroule le critère d'acceptation de la
rév. 4.1 : dépôt d'une proposition par un vrai client MCP en stdio, résolution
par okf-review, puis relecture du verdict — intégration ou motif de rejet —
toujours en MCP seul. Il tourne sur une copie du bundle okf-hub-feedback
réellement déployé quand il est présent.
Hors périmètre v0
Extensions tools/skills ; review: agent|auto ; validation automatique de
schéma ; authentification des contributeurs ; politique d'incrément de version ;
index de recherche dérivé ; revue d'import outillée ; synchronisation remote ;
multi-hub. Reporté en v1 optionnelle : kb_search multi-bases.
Licence
Apache 2.0. Voir NOTICE.
La spécification transcrite dans docs/ est de David Morvan et suit la même
licence. Le format OKF auquel le hub se réfère est publié séparément par
Google Cloud Platform, également sous Apache 2.0.
Contribuer
CONTRIBUTING.md — et lire d'abord la section « Ce qui ne se
négocie pas ».
Vulnérabilité : SECURITY.md, advisory privée, jamais d'issue
publique.
Available Tools
6 toolskb_governanceA
Retourne les règles de gouvernance d'une base (GOVERNANCE.md) et son schéma de frontmatter (schema.yaml) s'il existe. À lire avant de soumettre une proposition via kb_propose, et indispensable au rôle gestionnaire. Bases disponibles : aucune.
Inputs: base (required).
kb_hub_rescanA
Relance la découverte des bases dans bases-dir : à appeler après avoir importé un bundle (git clone <url> bases/<nom>). PORTÉE MONO-INSTANCE : ce rescan n'affecte que la session qui l'appelle. Les autres sessions Claude connectées à ce hub continueront d'ignorer une base nouvellement importée jusqu'à leur propre rescan ou redémarrage.
Inputs: no named parameters.
kb_listB
Liste les bases de connaissance disponibles sur ce hub, avec leur objet, leur nombre de documents et le nombre de propositions en attente. Aucune base n'est actuellement enregistrée.
Inputs: include_pending_concerns.
kb_proposeA
Dépose une proposition d'ajout ou de correction dans une base. La proposition n'est PAS intégrée automatiquement : elle est déposée dans proposals/pending/ et attend la revue du gestionnaire, qui l'intègre ou la rejette selon les règles de la base (kb_governance). Limitation v0 : la résolution (intégration ou motif de rejet) n'est pas consultable via MCP — elle l'est par accès git direct au dépôt. kb_list avec include_pending_concerns permet de vérifier avant soumission qu'une proposition proche n'est pas déjà en attente. Aucune base n'est actuellement enregistrée.
Inputs: base (required), type (required), content (required), sources (required), +3 more.
kb_readA
Lit un document du corpus d'une base : document complet, ou une seule section si section est fourni. Au-delà d'un certain volume, un document lu sans section retourne sa table des headings plutôt que son contenu — rappelez alors kb_read avec la section voulue, ou force: true pour tout obtenir. Bases disponibles : aucune.
Inputs: base (required), path (required), force, section.
kb_searchA
Recherche plein texte dans le corpus d'une base. Retourne les chemins, titres et extraits pertinents — jamais les documents entiers. Utilisez ensuite kb_read pour lire un document ou une section. Aucune base n'est actuellement enregistrée.
Inputs: base (required), query (required), mode, max_results.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 6 tools
Each tool targets a distinct hub operation: listing bases, searching a corpus, reading documents, retrieving governance rules, submitting proposals, and rescaming the hub. There is no meaningful overlap between the six tools.
All tools share the kb_ prefix and use snake_case, and five of six follow a clear verb-first pattern. kb_governance is the only noun-style name, but it still fits the overall naming scheme without causing confusion.
Six tools is a well-scoped size for a knowledge-base hub: discovery, reading, searching, governance, contribution, and maintenance each have a dedicated tool. No tool feels redundant or unnecessary.
The core workflows are covered: exploring bases, reading documents, checking governance rules, and submitting proposals are all supported. The main gap is that proposal resolution is not visible through the MCP tools, and base creation happens outside the hub via git, but these are documented limitations rather than fatal omissions.
Maintenance
Related MCP Connectors
Shared work memory for Claude Code, Codex, Cursor and chat, scoped to each repository.
Persistent, governed institutional memory for Claude Code — specs, decisions, learnings.
Your organization's knowledge as OKF trees in Git, for AI assistants to search, read, and cite
Notes and memory assistant: one Markdown vault that Claude, ChatGPT and any MCP client can share.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to read and write a local-first knowledge base of plain markdown files in git, with governance gates for safe, hash-anchored edits.1Apache 2.0
- AlicenseNot gradedqualityCmaintenanceTurns Claude Code into an engineering project manager by orchestrating feature development with isolated Git worktrees, structured task validation, and approval-gated integration.1MIT
- AlicenseNot gradedqualityAmaintenanceEnables Claude to maintain a team knowledge base stored as markdown in a GitHub repository, with tools for search, edit, linking, and interactive views.3 npm2AGPL 3.0
- AlicenseNot gradedqualityAmaintenanceEnables coordinating Claude Code and Codex across separate Git worktrees with shared issue ownership, file reservations, messages, and explicit handoffs.156 PyPI3MIT