npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aidalinfo/scaleway-mcp

v1.4.0

Published

Scaleway MCP server (read-only by default, optional restricted-write mode) — projects, instances, managed databases, DNS, transactional email, VPC/network, Kubernetes, scoped Object Storage access and pull-only Container Registry access, dedicated RDB dat

Readme

scaleway-mcp

MCP server pour explorer l'infrastructure Scaleway : projets, instances (VMs), bases de données managées (RDB), DNS, email transactionnel (TEM), réseau (VPC / private networks / load balancers) et Kubernetes (Kapsule).

Par défaut le serveur est en lecture seule : tous les appels sont des GET sur https://api.scaleway.com authentifiés via le header X-Auth-Token (clé secrète), et aucun tool ne crée, modifie ou supprime quoi que ce soit. Un mode restricted-write optionnel ajoute un petit lot d'outils d'écriture ciblés (voir Mode restricted-write).

Configuration

Variables d'environnement (voir .env) :

| Variable | Requis | Description | |---|---|---| | SCW_SECRET_KEY | oui | Clé secrète Scaleway (envoyée en X-Auth-Token) | | SCW_ACCESS_KEY | recommandé | Access key — sert à auto-découvrir l'organization ID | | SCW_DEFAULT_ORGANIZATION_ID | optionnel | Court-circuite la découverte auto | | SCW_MODE | optionnel | readonly (défaut) ou restricted-write. Toute autre valeur = readonly | | SCW_ALLOWED_PROJECT_IDS | en écriture | Projets où les mutations sont permises (séparés par des virgules) | | SCW_ALLOWED_TOOL_GROUPS | en écriture | Groupes d'outils d'écriture à enregistrer (voir plus bas) | | SCW_OSA_HANDOFF_DIR | groupe object-storage-access | Répertoire absolu (0700) où sont déposés les secrets des clés créées | | SCW_OSA_ALLOWED_BUCKETS | groupe object-storage-access | Buckets autorisés (obligatoire, sinon groupe désactivé) | | SCW_OSA_ALLOWED_S3_PREFIXES | groupe object-storage-access | Préfixes S3 autorisés ; un préfixe demandé doit être dessous (obligatoire) | | SCW_OSA_APPLICATION_PREFIXES | groupe object-storage-access | Préfixes de noms d'applications IAM autorisés (obligatoire) | | SCW_OSA_POLICY_SINGLE_WRITER | groupe object-storage-access | acknowledged obligatoire : l'opérateur atteste que ce MCP est le seul à écrire les politiques des buckets autorisés (sinon groupe désactivé) | | SCW_OSA_MAX_KEY_DAYS | optionnel | Durée de vie maximale d'une clé (1–365, défaut 365 ; valeur invalide = groupe désactivé) |

Si l'org n'est pas fournie, elle est découverte : API key → user/application → organization_id.

Build & lancement

npm install
npm run build
SCW_SECRET_KEY=... SCW_ACCESS_KEY=... npm start

Test de fumée bout-en-bout en lecture seule (lit .env) : node smoke.mjs

Tests unitaires (aucun appel réseau, fetch simulé) : npm test

Enregistrement dans Claude Code

claude mcp add scaleway --scope user \
  -e SCW_ACCESS_KEY=... -e SCW_SECRET_KEY=... \
  -- npx -y @aidalinfo/scaleway-mcp

Tools

| Tool | Description | |---|---| | scw_organization | Vue d'ensemble org (projets, users, apps, API keys) | | scw_projects | Liste des projets | | scw_instances | VMs sur toutes les zones (filtre zone/projet) | | scw_instance_info | Détail d'une VM | | scw_flexible_ips | IPs publiques flottantes et leur rattachement | | scw_volumes | Volumes de stockage Instance | | scw_databases | Bases managées RDB (PostgreSQL/MySQL) toutes régions | | scw_database_info | Détail d'une base + bases logiques + users | | scw_dns_zones | Zones DNS | | scw_dns_records | Enregistrements d'une zone | | scw_domains | Noms de domaine enregistrés (registrar) | | scw_email_domains | Domaines d'envoi TEM + statut/réputation | | scw_email_domain_info | Détail TEM (SPF/DKIM) | | scw_vpcs | VPCs toutes régions | | scw_private_networks | Réseaux privés toutes régions | | scw_load_balancers | Load balancers toutes zones | | scw_k8s_clusters | Clusters Kubernetes | | scw_k8s_cluster_info | Détail cluster + pools + nodes | | scw_raw_get | Échappatoire GET arbitraire (Redis, Registry, Secret Manager, etc.) | | scw_regions | Référence régions/zones |

Régions : fr-par, nl-ams, pl-waw. Zones : *-1, *-2, *-3.

Mode restricted-write

SCW_MODE=restricted-write
SCW_ALLOWED_PROJECT_IDS=<project-id>[,<project-id>...]
SCW_ALLOWED_TOOL_GROUPS=dns,instance-actions,snapshots,flexible-ips,rdb-backups,object-storage-access,registry-access,rdb-provisioning,object-storage-buckets

Le mode est fail-closed : valeur de SCW_MODE inconnue, SCW_ALLOWED_PROJECT_IDS vide ou aucun groupe valide → le serveur démarre en lecture seule (avertissement sur stderr). Les groupes inconnus sont ignorés. Seuls les groupes listés sont enregistrés.

| Groupe | Tool | Action | |---|---|---| | dns | scw_dns_record_create | Ajoute un enregistrement (A, AAAA, CNAME, TXT, MX, SRV, CAA) dans une zone existante | | dns | scw_dns_record_update | Change valeur/TTL/priorité d'un enregistrement identifié par son id | | dns | scw_dns_record_delete | Supprime un enregistrement par id, si expectedName + expectedType correspondent | | instance-actions | scw_instance_power | poweron / poweroff | | instance-actions | scw_instance_sensitive_action | reboot / stop_in_place (instance running uniquement) | | snapshots | scw_volume_snapshot_create | Snapshot d'un volume Instance ou Block Storage (SBS) | | snapshots | scw_instance_backup | Image de sauvegarde d'une instance (action backup) | | flexible-ips | scw_flexible_ip_attach | Attache une IP flexible libre à une instance de la même zone | | flexible-ips | scw_flexible_ip_detach | Détache une IP de l'instance attendue (serverId) | | rdb-backups | scw_rdb_backup_create | Sauvegarde manuelle d'une base logique RDB (expiration 1–365 j, 30 par défaut) | | rdb-provisioning | scw_rdb_database_user_provision | Base logique + utilisateur non admin + privilège exact sur cette seule base, mot de passe uniquement dans un handoff 0600 (voir plus bas) | | rdb-provisioning | scw_rdb_database_user_rotate | Nouveau mot de passe pour un utilisateur géré, handoff remplacé atomiquement | | rdb-provisioning | scw_rdb_database_user_revoke | Supprime un utilisateur géré (la base et ses données restent) | | rdb-provisioning | scw_rdb_database_user_adopt | Migration : reprend un utilisateur du build local (handoff JSON) sans rotation ni écriture Scaleway | | rdb-provisioning | scw_rdb_database_user_status | (lecture) Utilisateur, privilèges et état de gestion | | object-storage-access | scw_object_storage_access_provision | Application IAM dédiée + clé expirante + accès limité à des préfixes d'un bucket existant, prouvé par canari (voir plus bas) | | object-storage-buckets | scw_object_storage_bucket_baseline | Crée (si absent) un bucket autorisé avec l'identité du MCP et pose sa configuration de base : chiffrement AES256, versioning, CORS borné, lifecycle des multiparts (voir plus bas) | | object-storage-access | scw_object_storage_access_verify | Rejoue le canari pour une clé existante | | object-storage-access | scw_object_storage_access_key_revoke | Révoque une clé par access key exacte, si expectedApplicationId correspond | | object-storage-access | scw_object_storage_access_status | (lecture) Clés d'une application gérée et préfixes accordés sur un bucket | | registry-access | scw_registry_pull_access_provision | Application IAM dédiée + clé expirante avec seulement ContainerRegistryReadOnly sur un projet autorisé, prouvée par un canari en lecture (voir plus bas) | | registry-access | scw_registry_pull_access_verify | (lecture) Rejoue le canari pour une clé existante, désignée par sa référence | | registry-access | scw_registry_pull_access_key_revoke | Révoque une clé par référence ou access key exacte, si expectedApplicationId correspond ; supprime son handoff | | registry-access | scw_registry_pull_access_status | (lecture) Politique et clés (référence, access key masquée, expiration) d'une application gérée |

Garde-fous

  • Chaque outil mutatif est marqué [WRITE] dans sa description.
  • Il n'existe aucun outil d'écriture générique : le client refuse toute mutation dont la méthode + le chemin ne figurent pas dans une liste fermée (WRITE_ROUTES), avec un contrôle du corps (ex. action terminate impossible, zone DNS jamais créée). scw_raw_get reste strictement en GET.
  • Région, zone, identifiants (UUID), nom de zone DNS et champs d'enregistrement sont validés avant tout appel.
  • La ressource est lue avant la mutation : son projet doit figurer dans SCW_ALLOWED_PROJECT_IDS, sinon refus sans appel d'écriture. Pour un attachement d'IP, l'IP et l'instance doivent toutes deux être dans un projet autorisé.
  • La ressource exacte est relue après la mutation ; si l'état attendu n'apparaît pas, l'outil renvoie une erreur Verification FAILED.
  • Idempotence : enregistrement identique déjà présent, instance déjà dans l'état cible, snapshot/image/sauvegarde de même nom, IP déjà (dé)tachée → rien n'est envoyé.
  • Les erreurs Scaleway sont nettoyées : clé secrète, access key, header X-Auth-Token et champs secret_key / password / token sont masqués.
  • Hors périmètre : suppression d'instances, volumes, bases (hors rollback de l'appel qui l'a créée) ou clusters ; IAM en dehors des groupes object-storage-access et registry-access (qui ne créent qu'une politique bornée à un projet) ; push, suppression d'images ou de tags, gestion des namespaces du Container Registry ; mutations Kubernetes (la prod reste pilotée par GitOps) ; création de ressources coûteuses.

Les confirmations humaines restent du ressort de l'agent consommateur ; le serveur reste sûr lorsqu'il est appelé directement.

Accès Object Storage dédiés (object-storage-access)

Provisionne sans console un accès S3 dédié, par exemple pour une sauvegarde RustDesk limitée à backup-global/backup/rustdesk/** :

SCW_MODE=restricted-write
SCW_ALLOWED_PROJECT_IDS=<projet du bucket>
SCW_ALLOWED_TOOL_GROUPS=object-storage-access
SCW_OSA_HANDOFF_DIR=/srv/scaleway-mcp/handoff        # mkdir -m 700
SCW_OSA_ALLOWED_BUCKETS=backup-global                # obligatoire
SCW_OSA_ALLOWED_S3_PREFIXES=backup/                  # obligatoire
SCW_OSA_APPLICATION_PREFIXES=backup-                 # obligatoire
SCW_OSA_POLICY_SINGLE_WRITER=acknowledged           # obligatoire (voir « Écriture de la politique »)
SCW_OSA_MAX_KEY_DAYS=180                             # optionnel
SCW_OSA_WHOLE_BUCKETS=                               # optionnel : exception « bucket entier » (voir plus bas)
scw_object_storage_access_provision
  projectId=<projet> region=fr-par bucket=backup-global
  prefixes=["backup/rustdesk/**"] applicationName=backup-rustdesk keyDescription=initial expiresInDays=90

Modèle d'accès Scaleway (bucket policy 2023-04-17) : une application n'accède à Object Storage que si une politique IAM le lui permet sur le projet ; la bucket policy ne fait ensuite que restreindre cet accès dans le bucket. Les droits IAM couvrent tous les buckets du projet : un bucket sans politique leur est entièrement ouvert.

Déroulé :

  1. Contrôles avant toute mutation :

    • projet dans SCW_ALLOWED_PROJECT_IDS ; bucket, préfixes, nom d'application et durée dans les allowlists SCW_OSA_* ;
    • répertoire de handoff réel (pas de lien), à soi, en 0700 ;
    • bucket existant dans le projet (jamais créé), avec une politique déjà présente (en créer une couperait l'accès à tous les principaux non listés) ;
    • isolation du projet : tous les autres buckets du projet, dans toutes les régions Object Storage (fr-par, nl-ams, pl-waw, it-mil), doivent avoir une politique, sans principal joker *. Sinon les droits IAM de l'application les atteindraient ;
    • déclarations gérées intactes : celles qui portent un Sid scaleway-mcp-osa-<application>-* (bucket cible et autres buckets du projet) doivent être exactement deux et identiques à ce que le MCP écrit (principal, effet, actions, ressources, condition, préfixes valides). Toute altération est refusée au lieu d'être reprise. Les préfixes déjà accordés doivent rester dans SCW_OSA_ALLOWED_S3_PREFIXES ;
    • lectures strictes : chaque page d'une liste IAM doit porter le tableau attendu et un total_count entier stable, égal au nombre d'éléments reçus, sans troncature. Chaque élément est validé avant tout filtre de sécurité : identifiants présents et bien typés, un seul principal par politique, un seul porteur par clé, pas de doublon. Un élément incomplet fait échouer l'appel ; il n'est jamais ignoré. Les réponses S3 sont validées par un parseur XML (fast-xml-parser) : document complet et bien formé, racine unique fermée, rien hors racine. ListBuckets doit être non tronqué, avec un seul conteneur <Buckets> et un nom valide par bucket. ListObjectsV2 doit avoir un <KeyCount> unique égal au nombre de <Contents>, et jamais « vide mais tronqué » ;
    • une application par bucket : une application qui a déjà des déclarations gérées sur un autre bucket est refusée ;
    • aucun droit parallèle : ni la politique du bucket cible ni celle d'un autre bucket du projet ne doivent contenir de déclaration Allow non gérée par le MCP qui vise l'application (son principal ou un joker). Ces droits échapperaient au canari ;
    • permission sets attendus connus d'IAM ;
    • application existante gérée seulement si elle porte le tag scaleway-mcp=object-storage-access, n'est membre d'aucun groupe IAM (pas de droits hérités), n'est pas celle du MCP et ne possède au plus que sa politique gérée. Cette politique doit contenir exactement une règle : les permission sets ci-dessous, sur ce seul projet, avec permission_sets_scope_type=projects, sans organization_id, account_root_user_id ni condition. Le champ de métadonnée srn est accepté uniquement au format srn://iam.scw.eu/rules/<uuid> ; tout autre champ inconnu reste refusé. Le schéma de règle est strict : un champ inconnu est refusé. Les politiques sont lues avec le filtre application_ids et revérifiées côté client.
  2. Application IAM retrouvée par nom exact, ou créée avec le tag. Un identifiant renvoyé par un POST (application ou politique) n'est rendu supprimable par le rollback qu'après relecture et preuve de causalité : marqueur [op:…] de l'appel, nom, tag, organisation, et application pour la politique. Sinon, il est signalé et jamais supprimé.

  3. Politique IAM gérée scaleway-mcp-osa-<application> : une seule règle, ObjectStorageBucketsRead, ObjectStorageObjectsRead, ObjectStorageObjectsWrite et ObjectStorageObjectsDelete, sur le seul projet autorisé. Jamais d'écriture ou de suppression de bucket, jamais de droit sur les politiques de bucket. Le client refuse tout autre contenu.

  4. Clé applicative expirante (projet par défaut = projet du bucket), idempotente par keyDescription. Les clés d'une application sont listées avec bearer_id + bearer_type=application. Une clé réutilisée doit être la seule clé valide portant cette description, être une clé de cette application (pas d'un utilisateur), sur le projet demandé, et expirer dans la limite de expiresInDays et de SCW_OSA_MAX_KEY_DAYS (sinon refus : faire une rotation). Son fichier de handoff doit décrire exactement la même clé, la même application et le même projet. L'expiration renvoyée pour une clé créée est vérifiée. Le secret est écrit uniquement dans $SCW_OSA_HANDOFF_DIR/<application>.<access key>.env : création exclusive en 0600, sans suivre de lien, jamais écrasé. Variables SCW_*, AWS_* et S3_*. S3_PREFIXES y contient le périmètre effectif, c'est-à-dire l'union des préfixes déjà accordés et demandés. Une clé n'est réutilisée que si son fichier décrit exactement le bucket, la région et ce périmètre ; sinon il faut faire une rotation. À la réutilisation, le fichier n'est lu que s'il est régulier, à soi et en 0600 exactement : contrôle par lstat, ouverture O_NOFOLLOW, puis fstat sur le même inode. Le secret n'apparaît jamais dans la réponse, les erreurs ou les logs.

  5. Politique de bucket fusionnée : deux déclarations scaleway-mcp-osa-<app>-* sont ajoutées ou mises à jour : objets Get/Put/Delete/multipart sur bucket/prefixe/*, et ListBucket conditionné à s3:prefix. Les autres déclarations restent intactes et dans l'ordre, et les préfixes déjà accordés sont conservés. La politique est relue juste avant l'écriture : si elle a changé depuis la lecture initiale, refus. Après l'écriture, elle est relue et comparée structurellement en entier : déclarations, principaux, actions, ressources, conditions. Les champs S3 qui acceptent officiellement une chaîne ou une liste (Action, Resource, principal et valeurs de condition) sont comparés après normalisation scalaire/liste ; leurs valeurs restent strictement identiques et toute autre modification est refusée.

  6. Canari avec la clé de l'application :

    • list / put / head / delete d'un objet .mcp-canary-* dans chaque préfixe accordé, y compris ceux accordés lors d'appels précédents ;
    • refus (403) hors préfixe, à la racine du bucket : list, head, put et delete ;
    • refus (403) des quatre mêmes opérations sur chacun des autres buckets du projet.

    Après le canari, toutes les régions sont réinventoriées : l'ensemble des buckets du projet doit être identique à celui inspecté, sinon échec et rollback.

    Un objet canari reste suivi tant que son absence n'est pas prouvée par une relecture : liste de cette clé avec KeyCount=0, ou HEAD → 404. Une réponse DELETE 200/204 n'est pas une preuve.

    Tout objet canari éventuellement créé, y compris un put hors préfixe accepté à tort, est supprimé dans un finally. Le client S3 n'a aucune opération de suppression de bucket, de politique ou d'objet métier.

  7. Rollback sur toute erreur ou exception après la première mutation, y compris réseau. Il annule exactement ce que l'appel a créé : clé, politique IAM, application (suppression possible uniquement pour des ids créés par ce processus), fichier de handoff. Si le résultat d'une création IAM est ambigu (erreur de transport ou réponse sans identifiant), la ressource est recherchée avec des relectures bornées. Une erreur de relecture, ou une ressource encore invisible après la dernière tentative, n'est jamais traitée comme une absence : l'état est déclaré indéterminé. Une application ou une politique n'est adoptée par le rollback que si elle porte le marqueur unique de l'appel ([op:…] dans sa description). Une clé ne peut porter de marqueur et l'API n'a pas de jeton d'idempotence : une clé ambiguë n'est jamais révoquée automatiquement, les nouvelles clés de l'application sont listées pour une révision explicite. Tout ce qui n'est pas adopté est signalé sans être supprimé. Chaque DELETE est suivi d'une relecture, même après une erreur de transport (404 attendu). L'absence du fichier de handoff est vérifiée (un fichier partiellement écrit est supprimé). Un objet canari restant, non supprimé avec la clé de l'application ni avec l'identité de gestion (clés canari uniquement), rend aussi le rollback incomplet. Le résultat indique « rolled back and verified » ou « ROLLBACK INCOMPLETE ». La révocation est vérifiée par lecture directe et par la liste des clés de l'application. La politique de bucket n'est restaurée que si elle est toujours exactement celle écrite par l'appel ; sinon le rollback la laisse et le signale pour une vérification manuelle. Elle est relue après restauration.

Rotation : relancer scw_object_storage_access_provision avec une nouvelle keyDescription (ex. rotation-2026-10). Une nouvelle clé est créée et prouvée, et les anciennes sont listées. Une fois les consommateurs basculés, lancer scw_object_storage_access_key_revoke accessKey=SCW… expectedApplicationId=<id>. C'est une opération distincte : clé exacte, application attendue et gérée, projet autorisé, jamais la clé du MCP. Elle est idempotente et relue après.

scw_object_storage_access_verify refait les mêmes contrôles (application gérée, bucket dans le projet autorisé de la clé, isolation du projet, déclarations gérées intactes) avant toute écriture canari. Il exige que prefixes soit exactement l'ensemble accordé par la politique et nettoie lui aussi avec l'identité de gestion. Un résidu fait échouer la vérification. Il prend le même verrou que le provisionnement. Après le canari, il exige que la politique relue soit identique à celle inspectée et que l'inventaire des buckets n'ait pas changé.

Limites :

  • le bucket est résolu via le projet par défaut de la clé du MCP ; SCW_ACCESS_KEY est obligatoire et signe aussi les requêtes S3 ;
  • le MCP doit pouvoir lister les buckets et lire les politiques de tous les buckets du projet, ainsi qu'écrire celle du bucket cible. Pour nettoyer et prouver l'absence des objets canari, il doit aussi avoir s3:ListBucket, s3:GetObject et s3:DeleteObject dans les politiques de ces buckets (le client ne supprime que des clés .mcp-canary-*) ;
  • un bucket créé plus tard dans le projet sans politique serait atteint par les droits IAM de l'application : toujours poser une politique sur les nouveaux buckets, et relancer _verify, qui le détecte ;
  • Écriture de la politique : S3 n'offre aucune écriture conditionnelle (ni ETag, ni version) pour PutBucketPolicy. Le groupe exige donc SCW_OSA_POLICY_SINGLE_WRITER=acknowledged, par lequel l'opérateur atteste que ce MCP est le seul à écrire les politiques des buckets autorisés. Un verrou local ($SCW_OSA_HANDOFF_DIR/.lock-<région>-<bucket>), pris par provision comme par verify, sérialise ses propres appels. C'est un répertoire jamais vide tant qu'il est tenu : il contient owner-<jeton>. La prise se fait par rename atomique d'un répertoire de réclamation privé ; elle échoue si le verrou est tenu. La libération supprime d'abord son propre fichier owner-<jeton>, un nom que personne d'autre n'utilise, puis fait un rmdir. Comme rmdir ne supprime qu'un répertoire vide, il est impossible de retirer le verrou d'un successeur ; il n'y a pas de fenêtre « vérifier puis supprimer ». Un verrou perdu ou impossible à libérer fait passer le résultat en erreur. Un verrou restant n'est jamais cassé automatiquement. Toutes les instances du MCP qui écrivent ces politiques doivent partager le même SCW_OSA_HANDOFF_DIR, sur un système de fichiers où O_EXCL est atomique. La relecture juste avant l'écriture et la restauration conditionnelle restent en place pour détecter un écrivain non déclaré, sans pouvoir l'exclure.

Accès Container Registry pull-only (registry-access)

Crée sans console une identité technique qui peut uniquement récupérer les images du Container Registry d'un projet autorisé (ex. PROD), et remet son secret dans un fichier local protégé :

SCW_MODE=restricted-write
SCW_ALLOWED_PROJECT_IDS=<projet PROD>
SCW_ALLOWED_TOOL_GROUPS=registry-access
SCW_REGISTRY_HANDOFF_DIR=/srv/scaleway-mcp/registry-handoff   # mkdir -m 700 — obligatoire
SCW_REGISTRY_APPLICATION_PREFIXES=registry-                   # obligatoire
SCW_REGISTRY_MAX_KEY_DAYS=180                                 # optionnel (1–365, 365 par défaut)

Toute variable absente ou invalide (répertoire relatif, préfixe hors [a-z0-9-], durée hors 1–365) désactive le groupe ; le serveur l'indique sur stderr.

scw_registry_pull_access_provision
  projectId=<projet PROD> applicationName=registry-prod-pull keyDescription=initial expiresInDays=90
  [region=fr-par] [canaryImage=<namespace>/<image>]

Déroulé :

  1. Contrôles avant toute mutation : projet dans SCW_ALLOWED_PROJECT_IDS, nom d'application commençant par un préfixe de SCW_REGISTRY_APPLICATION_PREFIXES, durée ≤ SCW_REGISTRY_MAX_KEY_DAYS, répertoire de handoff réel, à soi, en 0700, verrou local par application (.lock-registry-<application>), permission set ContainerRegistryReadOnly connu d'IAM. Une application existante n'est reprise que si elle porte le tag scaleway-mcp=registry-access, n'est pas celle du MCP, n'appartient à aucun groupe IAM et ne possède au plus que sa politique scaleway-mcp-registry-<application>, dont l'unique règle est exactement ContainerRegistryReadOnly sur ce seul projet. Une application de l'autre groupe (tag Object Storage) est refusée. Mêmes lectures IAM strictes que pour object-storage-access.

  2. Application, politique et clé expirante créées comme pour object-storage-access : marqueur [op:…], relecture et preuve de causalité, réconciliation bornée d'une création ambiguë, idempotence par keyDescription. Le client refuse toute politique dont la règle contient un autre permission set que ContainerRegistryReadOnly (aucun push, suppression ni administration de namespace), ou qui porte les deux tags gérés.

  3. Secret : écrit uniquement dans $SCW_REGISTRY_HANDOFF_DIR/<application>.registry-<référence>.env (création exclusive 0600, sans suivre de lien) : SCW_*, REGISTRY_USERNAME=nologin, REGISTRY_PASSWORD, REGISTRY_ENDPOINTS. La référence de clé est un condensat SHA-256 (16 caractères hexadécimaux) de l'access key : elle identifie la clé sans l'exposer. La réponse ne contient que des métadonnées : application, politique, projet, référence et access key masquée (SCW…ABCD), expiration, chemin du handoff.

  4. Canari en lecture seule, avec la clé de l'application (retenté le temps de la propagation IAM) :

    • API Registry : liste des namespaces du projet ; une liste à l'échelle de l'organisation doit être refusée ou ne montrer que ce projet ;
    • protocole Docker Registry v2 sur rg.<région>.scw.cloud : authentification nologin + secret, puis liste des tags d'une image privée du projet (canaryImage, ou la première trouvée ; une image publique ne prouverait rien et est refusée). Les identifiants ne sont envoyés qu'à un point de jeton Scaleway en HTTPS (api.scaleway.com ou *.scw.cloud) ;
    • un jeton demandé avec pull,push ne doit accorder que pull (quand le jeton est un JWT inspectable ; sinon l'absence de push repose sur la règle IAM vérifiée) ;
    • sans image privée dans la région, seule l'authentification est prouvée et le résultat le dit.

    Aucune image, aucun tag ni namespace n'est créé, modifié ou supprimé : il n'existe d'ailleurs aucune route d'écriture Registry dans WRITE_ROUTES.

  5. Rollback de la clé, de la politique, de l'application et du handoff créés par l'appel, si une étape ou le canari échoue, avec relecture de chaque suppression. Une application ou une politique préexistante n'est jamais supprimée ; une clé ambiguë n'est jamais révoquée automatiquement.

Rédaction : toute sortie des quatre outils (succès comme erreur) passe par redactCredentials : secret de la clé, jetons de registre, valeurs Authorization (Basic/Bearer), JWT, paramètres d'URL signées (X-Amz-*, signature, token) et toute access key complète (masquée).

Rotation : relancer _provision avec une nouvelle keyDescription ; les autres clés sont listées par référence. Puis scw_registry_pull_access_key_revoke expectedApplicationId=<id> keyRef=<référence> (ou accessKey=SCW… exacte) : application attendue et gérée par ce groupe, projet autorisé, jamais la clé du MCP, idempotente, relue après. Le fichier de handoff de la clé révoquée est supprimé.

Limites : le handoff est le seul mode de remise du secret (pas encore de coffre ni d'injection) ; la politique couvre le Registry de toutes les régions du projet, le canari n'en lit qu'une (region).

Bases et utilisateurs RDB dédiés (rdb-provisioning)

Donne à une application sa propre base sur une instance Managed Database explicitement autorisée :

SCW_MODE=restricted-write
SCW_ALLOWED_PROJECT_IDS=<projet de l'instance>
SCW_ALLOWED_TOOL_GROUPS=rdb-provisioning
SCW_RDB_HANDOFF_DIR=/srv/scaleway-mcp/rdb-handoff        # mkdir -m 700 — obligatoire
SCW_RDB_ALLOWED_INSTANCES=fr-par/<instance-uuid>         # région/uuid, liste — obligatoire
SCW_RDB_NAME_PREFIXES=app_                               # préfixes des bases ET des utilisateurs — obligatoire
SCW_RDB_LEGACY_HANDOFF_DIR=/chemin/absolu                 # optionnel : handoffs JSON de l'ancien build, pour _adopt

Toute variable absente ou invalide désactive le groupe. Aucune instance, base ou chemin n'est codé dans les sources.

scw_rdb_database_user_provision region=fr-par instanceId=<uuid> databaseName=app_billing userName=app_billing_rw [permission=all]
  • Avant toute mutation : instance dans SCW_RDB_ALLOWED_INSTANCES (région comprise), noms dans SCW_RDB_NAME_PREFIXES (mêmes jeux de caractères que l'ancien build : lettres, chiffres, _, et - pour les bases), instance ready dans un projet autorisé, répertoire de handoff réel, à soi, en 0700, verrou par instance (.lock-rdb-<instance>), listes strictes (total_count, éléments validés, doublons refusés).
  • Utilisateur existant : refusé s'il est admin ; refusé s'il n'a pas de marqueur de gestion (rdb.<région>.<instance>.<utilisateur>.managed, 0600, sans secret) décrivant exactement cette instance et cette base : jamais de reprise d'un utilisateur créé ailleurs. Son handoff doit exister, sinon arrêt sûr. Il est réutilisé sans rotation. Un privilège sur une autre base est refusé.
  • Création : base logique si absente ; mot de passe de 32 caractères généré par le MCP et écrit dans rdb.<région>.<instance>.<utilisateur>.env (0600, exclusif) avant la création de l'utilisateur, pour qu'il ne puisse jamais être perdu ; utilisateur is_admin=false (le client refuse tout autre corps) ; marqueur ; puis privilège readonly, readwrite ou all (jamais custom) sur cette base.
  • Contrat de privilèges relu : exactement la permission demandée sur la base, none partout ailleurs.
  • Rollback de ce que l'appel a créé : utilisateur, base (le client ne peut supprimer une base que si ce processus a créé ce chemin exact), marqueur, handoff ; privilège restauré pour un utilisateur préexistant. Chaque étape est relue. Une création ambiguë (réponse perdue) est signalée, jamais supprimée ; le handoff correspondant est conservé.
  • Rotation (_rotate) : nouveau mot de passe écrit dans un fichier temporaire 0600, appliqué (PATCH limité au mot de passe), puis renommé atomiquement sur le handoff. En cas d'échec ambigu, les deux fichiers sont conservés et signalés.
  • Adoption (_adopt) : pour un utilisateur créé par l'ancien build local, lit son handoff JSON <instance>-<utilisateur>.json (0600) dans SCW_RDB_LEGACY_HANDOFF_DIR, vérifie qu'il est non admin avec exactement la permission attendue sur sa base, puis écrit handoff et marqueur officiels. Aucun appel d'écriture Scaleway, aucune rotation ; l'ancien fichier est laissé en place.
  • Révocation (_revoke) : utilisateur géré exact, base attendue, suppression relue, handoff et marqueur supprimés, base et données intactes.

Le handoff contient DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD, DB_PERMISSION et DATABASE_URL. Aucune réponse, erreur ou log ne contient le mot de passe.

Migration depuis le build local Vulcain : voir docs/scaleway-vulcain-migration.md à la racine du dépôt.

Exception « bucket entier » : les préfixes restent obligatoires par défaut. Pour un bucket dédié à une seule application qui utilise réellement plusieurs racines de clés, wholeBucket=true (à la place de prefixes) accorde tout le bucket, aux conditions suivantes :

  • le bucket figure dans SCW_OSA_WHOLE_BUCKETS, une sous-liste de SCW_OSA_ALLOWED_BUCKETS, faute de quoi le groupe est désactivé ;
  • c'est le seul bucket de son projet, toutes régions confondues ; le contrôle est refait à chaque appel et par _verify.

Les déclarations gérées deviennent alors bucket/* pour les objets, et ListBucket sans condition. Le canari prouve list/put/head/delete à la racine. Un accès bucket entier déjà accordé absorbe les préfixes demandés ensuite. Une déclaration « bucket entier » trouvée sur un bucket absent de SCW_OSA_WHOLE_BUCKETS est refusée avant toute mutation. Le handoff porte S3_SCOPE=whole-bucket.

Création et configuration de base d'un bucket (object-storage-buckets)

SCW_MODE=restricted-write
SCW_ALLOWED_PROJECT_IDS=<projet du bucket = projet par défaut de la clé du MCP>
SCW_ALLOWED_TOOL_GROUPS=object-storage-buckets
SCW_OSB_ALLOWED_BUCKETS=helpdesk-files                # obligatoire
SCW_OSB_CORS_ORIGINS=https://app.example.com          # optionnel : origines HTTPS, sans chemin ni joker
SCW_OSB_ADMIN_PRINCIPALS=user_id:<uuid>               # optionnel : administrateurs ajoutés à la politique de base
scw_object_storage_bucket_baseline projectId=<projet> region=fr-par bucket=helpdesk-files
  [corsOrigins=["https://app.example.com"]] [versioning=true] [abortMultipartDays=7]
  • Identité du MCP uniquement, jamais celle d'une application : aucune application ne reçoit de droit d'administration de bucket, même temporairement.
  • Création si le bucket est absent du projet (projet par défaut de la clé du MCP, qui doit être le projet demandé). Un nom détenu par un autre projet est refusé.
  • Politique de base, uniquement sur un bucket créé par l'appel : le MCP et SCW_OSB_ADMIN_PRINCIPALS, personne d'autre. Elle prépare object-storage-access. Un bucket existant sans politique n'en reçoit pas (ses utilisateurs actuels seraient coupés) ; c'est signalé.
  • Réglages : chiffrement AES256 par défaut ; versioning activé (versioning=false laisse l'état actuel ; jamais suspendu) ; règle CORS scaleway-mcp-baseline (GET/HEAD/PUT, origines de l'allowlist) si corsOrigins est donné ; règle lifecycle scaleway-mcp-abort-multipart (abandon des multiparts incomplets). Chaque réglage est lu, écrit seulement s'il diffère, puis relu. Une configuration CORS ou lifecycle non écrite par le MCP n'est jamais remplacée : refus.
  • Rollback : si une étape échoue sur un bucket créé par l'appel, ce bucket, vide, est supprimé et son absence vérifiée. Le client ne peut supprimer qu'un bucket créé par cette même instance. Sur un bucket préexistant, rien n'est supprimé ; les réglages déjà vérifiés restent, et une relance est idempotente.
  • Hors périmètre : création de projet, ACL publiques, suppression ou vidage d'un bucket existant.

Application IAM dédiée

Créer une application IAM propre au MCP (pas une clé utilisateur) et lui attacher une politique minimale : lecture sur l'organisation, écriture uniquement sur les projets de l'allowlist et uniquement pour les produits des groupes activés. Exemple (noms de permission sets à vérifier dans la console) :

| Règle | Portée | Permission sets | |---|---|---| | Lecture | Organisation | ProjectReadOnly, IAMReadOnly, InstancesReadOnly, RelationalDatabasesReadOnly, DomainsDNSReadOnly, VPCReadOnly, LoadBalancersReadOnly, KubernetesReadOnly, TransactionalEmailReadOnly, BlockStorageReadOnly | | Écriture (dns) | Projets autorisés | DomainsDNSFullAccess | | Écriture (instance-actions, snapshots, flexible-ips) | Projets autorisés | InstancesFullAccess, BlockStorageFullAccess | | Écriture (rdb-backups) | Projets autorisés | RelationalDatabasesFullAccess | | Écriture (rdb-provisioning) | Projets autorisés | RelationalDatabasesFullAccess | | Écriture (object-storage-buckets) | Projets autorisés | ObjectStorageFullAccess (création de bucket, réglages, politique de base) | | Écriture (object-storage-access) | Organisation | IAMManager (applications, politiques, clés) | | Écriture (registry-access) | Organisation | IAMManager (applications, politiques, clés) | | Écriture (object-storage-access) | Projets autorisés | ObjectStorageBucketsRead, ObjectStorageBucketPolicyFullAccess — plus une déclaration pour l'application du MCP dans la politique de chaque bucket du projet (s3:ListBucket, lecture de politique ; écriture de politique sur les buckets cibles) |

Les permission sets Scaleway sont plus larges que les outils exposés (ex. InstancesFullAccess permet la suppression) : c'est le MCP qui restreint aux seules actions listées. N'accordez que les règles des groupes réellement activés.

Canari d'écriture

Sur un projet non critique avec le groupe dns activé :

node canary.mjs canary.example.com   # ajoute un TXT _mcp-canary, vérifie l'idempotence, le supprime