Votre configuration en MCP
sovrium mcp remet la configuration de votre projet à l'assistant d'IA que vous utilisez déjà, pour que le modèle qui modifie votre app.yaml voie ce que le fichier veut dire au lieu de le deviner à partir des octets.
sovrium mcp --project ~/apps/crmVoilà toute l'installation. Aucun serveur à monter, aucun bloc auth: à écrire, aucune clé à émettre.
Ce n'est pas le mode serveur. Celui-là expose vos données — tables, automatisations, actions — en HTTP, à un client authentifié. Celui-ci expose votre configuration, sur un tube, à un processus que vous avez lancé vous-même. Ce sont deux surfaces distinctes et aucune n'implique l'autre.
Ce qui tourne, et ce qui ne tourne pas
Ce verbe est un processus éphémère. Il ne démarre aucun serveur, n'écrit aucun fichier de verrou et n'occupe aucun port : il lit un fichier de configuration, répond en JSON-RPC ligne à ligne sur l'entrée standard, et s'arrête à la fermeture de celle-ci.
Il n'ouvre pas davantage de connexion à la base de données, à une exception près : lorsque l'écriture est activée et qu'une instance tourne, une écriture demande d'abord à cette base ce que le changement coûterait, en lecture, avant de toucher au fichier.
Comme la sortie standard ne transporte que des messages MCP, tout le reste — bannières, avis, erreurs — part sur la sortie d'erreur, y compris la ligne qui vous dit quel fichier de configuration a été trouvé.
MCP_TRANSPORT=stdio, ce n'est pas cela, et ne l'a jamais été. Cette variable ne fait que supprimer le montage HTTP de /mcp ; rien, sur le chemin de sovrium start, ne lit l'entrée standard. Si vous aviez configuré un IDE là-dessus, vous obteniez un processus qui ne répond à rien. Le point d'entrée stdio est ce verbe — on l'atteint en lançant le binaire, pas en définissant une variable. MCP_TRANSPORT=stdio démonte toujours la route, ce qu'un opérateur veut légitimement faire ; simplement, ce n'est pas une façon de servir quoi que ce soit.
Il n'y a aucun identifiant, et rien à configurer
Le processus a été lancé par vous, s'exécute sous votre compte et lit un répertoire que vous avez nommé : la frontière de processus du système d'exploitation constitue donc à elle seule toute l'authentification. Poser un jeton sur un tube entre deux processus du même utilisateur relèverait du cérémonial, pas de la sécurité.
Deux conséquences en découlent, et il vaut mieux les connaître avant de partir en quête d'un réglage qui n'existe pas :
- Une configuration sans bloc
auth:fonctionne. Aucune instance d'authentification n'est jamais construite. MCP_ENABLEDn'a aucun effet ici. La combinaison qui fait refuser le démarrage àsovrium start—MCP_ENABLED=truesansapp.auth— ne concerne pas ce verbe.
Y diriger un client
Tous les clients ci-dessous lisent les deux mêmes valeurs : la commande à lancer et ses arguments.
Claude Code
claude mcp add sovrium -- sovrium mcp --project ~/apps/crmLe -- est obligatoire : tout ce qui le suit constitue la commande du serveur, transmise telle quelle. Ajoutez --scope project pour écrire l'entrée dans un .mcp.json partagé à la racine du projet plutôt que dans vos réglages personnels.
La forme fichier, c'est le même bloc écrit à la main :
{
"mcpServers": {
"sovrium": {
"command": "sovrium",
"args": ["mcp", "--project", "/Users/moi/apps/crm"]
}
}
}Claude Desktop
Ajoutez la même entrée mcpServers à claude_desktop_config.json, puis redémarrez l'application.
Cursor
Ajoutez la même entrée à .cursor/mcp.json pour un seul projet, ou à ~/.cursor/mcp.json pour tous.
Le serveur parle les deux révisions du protocole, et vous n'avez pas à choisir. Le message d'ouverture décide : un client qui ouvre par initialize obtient l'ancienne ère pour toute la durée du processus, un client qui ouvre par une enveloppe 2026-07-28 obtient celle-ci. Cela compte parce que Claude Code se connecte aux serveurs stdio sur l'ancien moteur par défaut : un serveur qui ne parlerait que la nouvelle révision échouerait face à une installation par défaut.
Quelle configuration il lit
Le répertoire est résolu du plus spécifique au plus général :
--project <dir>SOVRIUM_PROJECT_DIR- Le répertoire de travail — lequel, quand c'est un client qui a lancé le processus, relève de son choix et non du vôtre. Nommez le répertoire explicitement.
À l'intérieur, le fichier de configuration est trouvé exactement comme sovrium start le trouve : SOVRIUM_CONFIG_FILE s'il est défini, sinon le premier de app.yaml, app.yml, app.ts.
Un répertoire sans configuration ne provoque pas de plantage. La poignée de main et la liste des outils répondent quand même, et chaque outil renvoie le constat de configuration manquante comme un résultat ordinaire — l'assistant dirigé vers le mauvais dossier l'apprend donc dans quelque chose qu'il est déjà en train de lire :
{
"valid": false,
"findings": [
{
"path": "",
"message": "No config file found. `sovrium mcp` reads app.yaml, app.yml or app.ts from the project directory — pass --project <dir> to point it at the right one.",
"severity": "error"
}
],
"notices": []
}Les quatre outils
Les outils portent le nom de votre application : un app.yaml dont le name vaut crm donne crm_config_read et ses trois voisins. Tous les quatre sont des lectures, et tous le déclarent — ils portent readOnlyHint, si bien qu'un client peut les appeler sans s'arrêter pour demander.
| Outil | Arguments | Renvoie |
|---|---|---|
_config_read |
aucun | { config, files, configHash } — la configuration telle que démarrée, secrets déclarés caviardés |
_config_validate |
aucun | { valid, findings, notices } — dans le vocabulaire de sovrium validate --json |
_config_schema |
path facultatif |
Le JSON Schema Draft 2020-12, ou le sous-schéma à un chemin de configuration pointé |
_config_status |
aucun | Le document d'état qu'une instance en marche publie, ou { "state": "not-running" } |
_config_read caviarde. Il sérialise par le caviardeur qui sert déjà la vue « configuration » de la console d'administration : un secret écrit en dur revient sous forme de marqueur, la structure autour restant intacte. Un jeton $env.X, lui, survit — un nom de variable n'est pas un secret, et c'est précisément ce qui vous dit quelle variable alimente le champ.
_config_validate lit le disque, pas l'instance en marche. C'est tout l'intérêt : un assistant qui vient de réécrire config/tables/contacts.yaml a besoin du verdict sur ce qu'il a écrit. Une configuration invalide est ici un résultat normal, pas une erreur d'outil.
_config_schema prend un chemin de configuration pointé, pas un pointeur JSON — tables.fields, et non /properties/tables/items/properties/fields. La forme pointée est le vocabulaire que vous écrivez déjà dans votre configuration. Un chemin qui ne se résout pas est refusé par un message nommant les clés qui, elles, sont disponibles au dernier segment résolu.
_config_status est une lecture de fichier. Il rapporte le fichier d'état qu'un serveur en marche publie à son propre sujet, et répond { "state": "not-running" } quand il n'y en a pas.
Ces quatre outils sont en lecture seule, et ils sont tout ce qu'une session propose tant que vous n'en décidez pas autrement.
Le laisser écrire
Réglez MCP_CONFIG_WRITE=1 et quatre outils supplémentaires apparaissent, capables de modifier le fichier de configuration lui-même. Ils sont désactivés par défaut, et c'est délibérément une variable d'environnement plutôt qu'une clé de configuration : une configuration qui pourrait autoriser sa propre modification serait une configuration qui s'autorise elle-même.
La variable n'est honorée que si les deux conditions tiennent : elle est définie, et le répertoire du projet a été nommé explicitement, par --project ou par un SOVRIUM_PROJECT_DIR hérité. Une session retombée sur le répertoire de travail n'obtient que les lectures, et le dit sur la sortie d'erreur. Une surface d'écriture se confine à un seul dossier ; encore faut-il que ce dossier ait été choisi exprès, et non celui dans lequel un client s'est trouvé démarrer.
{
"mcpServers": {
"sovrium": {
"command": "sovrium",
"args": ["mcp", "--project", "/Users/moi/apps/crm"],
"env": { "MCP_CONFIG_WRITE": "1" }
}
}
}Sur une application déployée, la variable ne fait rien. Les outils d'écriture n'existent que sur le tube et ne sont jamais enregistrés sur un point de terminaison HTTP. La définir là n'est pas une erreur, l'instance démarre et sert normalement ; simplement, le démarrage avertit qu'elle ne vous a rien apporté et nomme ce verbe à la place, parce qu'un opérateur qui croit avoir activé l'écriture de configuration par le réseau ne l'a pas fait.
| Outil | Arguments | Renvoie |
|---|---|---|
_config_list_files |
aucun | { files: [{ path, sha256, bytes }] }, chaque chemin relatif au projet |
_config_read_file |
path |
{ path, content, sha256 }, les octets tels quels |
_config_write_file |
path, content, expectedSha, acknowledgeDataLoss facultatif |
{ path, sha256, snapshot, reloadHint } |
_config_undo |
aucun | { snapshot, files }, quel instantané, et ce qu'il a changé |
Les deux outils de lecture portent readOnlyHint. Les deux outils d'écriture sont marqués destructifs : un client qui vous demande confirmation avant un outil destructif vous la demandera donc, car écraser un fichier est une modification destructrice de ce fichier quoi que l'outil refuse ensuite de faire à une table, et l'annulation en écrase plusieurs d'un coup.
Les octets du fichier entier, jamais un correctif. Il n'y a ici aucun rédacteur de configuration et aucun sérialiseur : content remplace le fichier, donc vos commentaires, l'ordre de vos clés, vos ancres et vos lignes vides survivent intacts à une modification. Le prix, c'est que l'appelant doit lire avant d'écrire, et expectedSha est ce qui rend ce prix réel plutôt qu'indicatif.
Ce pour quoi une écriture est refusée
Chaque écriture passe les mêmes contrôles, dans le même ordre, et chaque refus nomme ce qu'il a refusé. Les contrôles structurels, les moins chers, viennent d'abord ; ceux qui ouvrent une base de données viennent en dernier.
- Hors du projet. Le chemin est résolu depuis le répertoire du projet et doit tomber à l'intérieur.
- Pas un fichier de configuration.
.yaml,.ymlet.jsonuniquement, et jamais un lien symbolique : un lien symbolique peut pointer n'importe où, y compris hors du dossier. - Un emplacement protégé.
.envet ses voisins,.git/,.claude/, le répertoire de données et le marqueur de modèle.sovrium-template.json. Un outil capable d'écrire.envest un outil qui écrit des identifiants ; un outil capable d'écrire.git/réécrit l'historique. - Un
expectedShapérimé. L'empreinte contre laquelle vous avez édité ne correspond plus au fichier : quelque chose a donc enregistré par-dessus, très possiblement vous, dans votre propre éditeur. Le refus demande de relire, parce qu'un assistant à qui l'on répond seulement « non » réessaie indéfiniment le même appel. - Le résultat ne se décoderait pas. Le candidat est superposé en mémoire au graphe de
$refrésolu, et l'application entière est décodée avant que quoi que ce soit ne soit écrit. Une configuration invalide n'atteint jamais le disque ; ce sont les constats qui reviennent à sa place, dans le vocabulaire que_config_validateparle déjà. C'est ce qui rend sûre la modification d'un seul partiel extrait : il est jugé comme un morceau de l'application à laquelle il appartient, et non comme un document qui se trouve à être bien formé. - Une nouvelle référence hors du dossier. Un candidat qui introduit un
$refse résolvant hors du répertoire du projet est refusé, sans quoi le fichier lu pourrait sortir du dossier que le fichier écrit n'a pas le droit de quitter. - La base de données en marche le refuserait. Là où un serveur tourne, le changement passe par le même planificateur de migration que rapporte
sovrium migrate --dry-run, et un refus de sa part est le refus de l'écriture, avant que le fichier ne change plutôt qu'après que le serveur a renoncé à l'appliquer. - Il détruit des données. Un candidat qui introduit
allowDestructive: trueexigeacknowledgeDataLoss: trueà ses côtés, et l'outil ne pose jamais ni l'un ni l'autre de lui-même. Supprimer une colonne supprime les lignes qu'elle contient : la décision reste la vôtre, chaque fois. - Il renumérote une table. L'
idd'un champ est facultatif, et unidomis vaut la position du champ. Insérer un champ au-dessus de l'un d'eux décale tous les id suivants et repointe les données derrière eux : l'insertion est donc refusée jusqu'à ce que les id de la table soient explicites. La même insertion dans une table qui écrit chaque id en toutes lettres est acceptée, puisque rien ne bouge.
Revenir en arrière
Une écriture acceptée copie l'état d'avant l'écriture dans l'historique des configurations avant de changer un seul octet, si bien que _config_undo a quelque part où revenir même lorsque aucun serveur ne tourne et que rien n'a jamais atteint l'état « accepté ». La copie est omise lorsque l'entrée la plus récente contient déjà exactement ces octets : un projet sous surveillance se retrouve donc avec une entrée avant la modification et une après, plutôt que trois.
_config_undo restaure l'instantané le plus récent dont les fichiers diffèrent de ce qui est sur le disque, et répond avec les fichiers qu'il a changés. Lorsque aucun ne diffère, il n'y a nulle part où revenir, et il refuse plutôt que de rapporter un succès qui n'a rien changé.
L'annulation remet le fichier en place sans condition. Qu'une instance en marche la suive est une autre question : revenir sur un champ que vous venez d'ajouter est une suppression de colonne, donc le contrôle en pré-vol de la surveillance refuse ce rechargement sans allowDestructive, conserve la configuration qu'elle sert déjà, et publie la raison. Votre application reste debout et vos lignes restent où elles sont.
Une écriture ne met rien en service. Elle pose des octets sur le disque ; _config_status est la façon de savoir si une instance les a pris. Les appels sont traités un à la fois, dans l'ordre où vous les avez envoyés : une écriture suivie d'une annulation se produit donc dans cet ordre.
Vérifier à la main
Le serveur est un tube : vous pouvez donc le piloter avec printf. Envoyez un message d'ouverture et lisez la réponse :
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}\n' \
| sovrium mcp --project ~/apps/crm[mcp] Using app.yaml (auto-discovered)
{"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":false}},"serverInfo":{"name":"sovrium","version":"0.25.0"}},"jsonrpc":"2.0","id":1}La première ligne est sur la sortie d'erreur, la seconde sur la sortie standard. Cette séparation est le contrat : détournez la sortie d'erreur et il ne reste sur la sortie standard que du JSON-RPC valide, rien d'autre.
Ajoutez d'autres messages, un par ligne, pour aller plus loin — ils sont traités dans la même session :
printf '%s\n%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"crm_config_validate","arguments":{}}}' \
| sovrium mcp --project ~/apps/crm 2>/dev/nullLa réponse de l'outil arrive sous forme de contenu textuel ; une fois déballée, elle se lit ainsi :
{
"valid": true,
"findings": [],
"notices": [
"field-id-implicit: table \"contacts\" — \"email\", \"full_name\" declare no id, so the id is the field's position in the list. Inserting a field above one of them shifts every id after it, and the migration diff reads that as a rename. Give each field an explicit id — keep the ones it has today, and give new fields the next unused number."
]
}Une méthode que le serveur ne sert pas reçoit l'erreur JSON-RPC -32601 plutôt qu'une fermeture du tube : un client qui sonde une fonctionnalité que Sovrium n'offre pas reste donc connecté.
Les mêmes quatre outils en HTTP
Une application déployée qui fait tourner le mode serveur sert ces quatre mêmes outils sur son point de terminaison /mcp — où il y a, cette fois, une session à vérifier. Ils y sont donc réservés aux administrateurs, et filtrés hors de la liste d'outils pour tous les autres rôles. Le chemin local décrit sur cette page n'a pas de session, et n'en invente pas.
Les quatre outils d'écriture, eux, n'y figurent pas : une instance servie en HTTP n'en enregistre aucun, quelle que soit la valeur de MCP_CONFIG_WRITE.
Pages associées
- Présentation de MCP — les variables d'environnement, et la différence entre servir des outils et en consommer.
- Mode serveur — les outils de données, et ces quatre-là en HTTP.
- Connecter un client — diriger un assistant vers une application déployée, en HTTP.
- Validation et schéma — les mêmes constats depuis la ligne de commande.
- Cycle de vie du serveur — le fichier d'état, et l'historique des configurations où l'annulation puise.
- Présentation de la CLI — toutes les commandes du binaire.
Dernière mise à jour 23 septembre 2026
Cette documentation a été rédigée avec de l'IA : des erreurs ou du contenu obsolète sont donc possibles. Sovrium est en bêta. Les contributions et corrections sont les bienvenues.