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

@imenam/mcp-design

v1.0.4

Published

MCP server providing a simple design canvas: the agent creates shapes and text, the user refines them in a GUI, and the agent picks the edits back up.

Readme

@imenam/mcp-design

Un canvas de design pilotable par un agent, avec une interface graphique où l'utilisateur reprend la main.

L'agent compose une planche (formes, texte) via des outils MCP. L'utilisateur ouvre la GUI, déplace, retaille, recolore, réécrit. L'agent relit ensuite exactement ce qui a changé et l'intègre dans le code de l'application.

Agent ──add_nodes/update_nodes──▶  Design  ◀──glisser-déposer──  Utilisateur (GUI)
  ▲                                                                    │
  └──────────── get_changes ──── diff depuis la dernière baseline ──────┘

Installation

npm install -g @imenam/mcp-design

Ou directement via npx, sans installation.

Déclaration dans un projet

npx -y @imenam/mcp-design --claude-setup-mcp

La commande crée (ou complète) le .mcp.json du répertoire courant avec une entrée mcp-design dont les variables sont pré-remplies. Ajoutez --force pour remplacer une entrée existante ; les autres serveurs sont préservés.

Exemple d'entrée générée :

{
  "mcpServers": {
    "mcp-design": {
      "command": "npx",
      "args": ["-y", "@imenam/mcp-design"],
      "env": {
        "PROXY_URL": "http://localhost:3000",
        "APP_GROUP": "Mon Projet",
        "APP_NAME": "Design Mon Projet",
        "APP_PATH": "/design-mon-projet",
        "MCP_DESIGN_DATA_DIR": "C:/dev/mon-projet/.design-data",
        "MCP_LOG_DIR": ""
      }
    }
  }
}

Variables d'environnement

| Variable | Rôle | |---|---| | PROXY_URL | URL du proxy central. Sans elle, la GUI est simplement désactivée — les outils MCP continuent de fonctionner. | | APP_PATH | Chemin de montage de la GUI derrière le proxy (défaut /design-gui). | | APP_NAME | Nom affiché dans le dashboard du proxy. | | APP_GROUP | Section repliable du dashboard du proxy (facultatif). | | MCP_DESIGN_DATA_DIR | Répertoire de stockage des designs. MCP_DATA_DIR est accepté en repli. Défaut : <package>/.design-data. | | MCP_LOG_DIR | Répertoire des logs. Défaut : <data>/logs. |

Un design est un simple fichier JSON dans <data>/designs/<id>.json — lisible, diffable, versionnable.


Les outils

Planches

| Outil | Effet | |---|---| | list_designs | Liste les planches, leur taille, leur nombre d'éléments et le nombre de modifications non encore récupérées. | | create_design | Crée une planche vide (name, et facultativement width, height, background). | | get_design | Lit une planche : format: "summary" (inventaire compact, défaut) ou format: "json" (valeurs exactes). | | update_design | Renomme la planche, change ses dimensions ou son fond. | | delete_design | Supprime définitivement une planche. |

Éléments

| Outil | Effet | |---|---| | add_nodes | Ajoute un ou plusieurs éléments par-dessus les autres. | | update_nodes | Patche des éléments existants — seuls les champs fournis changent. | | delete_nodes | Supprime des éléments par identifiant. Supprimer un groupe emporte son contenu. | | group_nodes | Enveloppe des éléments dans un nouveau groupe, sans rien déplacer à l'écran. | | ungroup_nodes | Dissout des groupes : leur contenu remonte d'un niveau, toujours sans bouger. |

Cinq types d'éléments : rect, ellipse, line, text et group. Seul type est obligatoire ; tout le reste tombe sur des valeurs par défaut visibles, donc { "type": "rect" } suffit à créer quelque chose qu'on voit à l'écran.

Champs communsname, parentId, x, y, width, height (en pixels, origine en haut à gauche), rotation (degrés, autour du centre de la boîte), opacity (0–1).

Toute longueur est en pixels, sans exception : x, y, width, height, strokeWidth, radius, fontSize et lineHeight. Chaque champ porte sa description et son unité dans shared/node-fields.ts, d'où le schéma zod et l'inputSchema MCP les tirent tous les deux — une unité n'est écrite qu'à un seul endroit.

| Type | Champs propres | |---|---| | rect | fill, stroke, strokeWidth, radius | | ellipse | fill, stroke, strokeWidth | | line | stroke, strokeWidth — le trait va de (x, y) à (x + width, y + height), donc les deltas négatifs encodent la direction | | text | text, color, fontSize, fontFamily, fontWeight, fontStyle, textAlign, lineHeight (en px) | | group | aucun — c'est un pur conteneur |

Hiérarchie

Chaque élément porte un parentId : null à la racine de la planche, sinon l'identifiant d'un group. Les coordonnées sont exprimées dans l'espace du parent : un élément à la racine se positionne par rapport à la planche, un élément dans un groupe par rapport à l'origine de ce groupe.

C'est ce qui rend un déplacement de groupe lisible côté agent — un seul champ change, pas les coordonnées de chaque membre :

- g_a1b2c3 — "Carte produit" (group)
    x: 100 -> 260

La taille d'un groupe est déduite de son contenu : width et height sont recalculés à chaque mutation, ne peuvent pas être écrits, et n'apparaissent jamais dans un diff. Le stockage reste une liste plate, ordonnée en parcours préfixe — chaque parent suivi de son sous-arbre — ce qui garde le fichier JSON lisible et diffable.

Le texte n'est jamais re-wrappé automatiquement : seuls les \n explicites créent une nouvelle ligne. Le comportement est ainsi identique à l'écran et à l'export.

lineHeight est une distance en pixels entre deux lignes de base, pas un multiplicateur de fontSize : avec fontSize: 64, on écrit 76, pas 1.2. C'est l'unité que les agents emploient spontanément, et la seule qui se lise dans la même échelle que le reste. Une valeur sous 4 est donc refusée à l'écriture avec un message explicite, et les fichiers écrits avant ce changement sont convertis à la lecture (ratio x fontSize) par src/core/migrate.ts. Champ omis = fontSize x 1.35.

Ordre d'empilement

L'index dans la liste est l'ordre d'empilement : le dernier élément d'une fratrie est celui du dessus. add_nodes empile au-dessus, et reorder_nodes déplace ensuite ce qui existe déjà.

| Outil | Effet | |---|---| | reorder_nodes | position: front / back / forward / backward sur un ou plusieurs éléments. Un groupe emporte son contenu ; plusieurs éléments gardent leur ordre relatif. |

L'empilement est relatif à la fratrie : un élément ne passe jamais devant le contenu d'un autre groupe — c'est ce groupe qu'il faut alors réordonner, ou l'élément qu'il faut en sortir via parentId. La logique vit dans shared/tree.ts et sert aussi bien l'outil que le panneau de calques de la GUI : les deux ne peuvent pas diverger.

Un lot est intégralement validé avant la moindre écriture : une entrée invalide ne laisse jamais un design à moitié modifié.

Récupérer le travail de l'utilisateur

| Outil | Effet | |---|---| | get_changes | Liste tout ce qui a été ajouté, modifié, supprimé ou réordonné depuis la dernière baseline, champ par champ (x: 40 -> 240). | | mark_synced | Fige l'état courant comme nouvelle baseline. | | export_design | Rend la planche en html, jsx, svg, json ou summary. output_path écrit dans un fichier au lieu de renvoyer le contenu. | | screenshot_design | Rasterise la planche en PNG et le renvoie en image à l'agent. node_id cadre la capture sur un groupe (ou n'importe quel élément), scale zoome, padding ajoute de la marge, output_path écrit un .png au lieu de renvoyer l'image. |

get_changes n'acquitte jamais ce qu'il montre. Seul mark_synced déplace la baseline, et il ne doit être appelé qu'une fois les modifications réellement reportées dans le code. Un simple appel de lecture ne peut donc pas faire disparaître un diff que l'agent n'a pas encore traité.

GUI

| Outil | Effet | |---|---| | reconnect_gui | Relance le worker GUI et le réenregistre auprès du proxy. force: true redémarre même s'il semble vivant. |


Boucle de travail type

create_design { name: "Landing" }
add_nodes     { design_id: "landing", nodes: [...] }
mark_synced   { design_id: "landing" }        ← point de départ de la comparaison

  … l'utilisateur ouvre la GUI et retouche la maquette …

get_changes      { design_id: "landing" }     ← ce qu'il a changé, champ par champ
screenshot_design { design_id: "landing" }    ← ce que ça donne réellement, en image
export_design { design_id: "landing", format: "html" }
  … l'agent reporte les valeurs dans le code de l'application …
mark_synced   { design_id: "landing" }        ← acquittement

L'interface

Trois colonnes : la liste des planches, le canvas, l'inspecteur et les calques.

  • Outils — sélection, rectangle, ellipse, ligne, texte. Glisser pour dimensionner, cliquer pour poser à la taille par défaut.
  • Édition — déplacement au glisser, 8 poignées de redimensionnement (2 extrémités pour une ligne), sélection multiple au rectangle d'encadrement ou au Maj+clic.
  • GroupesCtrl+G groupe la sélection, Ctrl+Maj+G la dissout. Un clic dans le canvas sélectionne le groupe le plus haut ; Alt+clic vise l'élément exact à l'intérieur. Déplacer un groupe déplace tout son contenu.
  • Texte — double-clic pour éditer en place, avec la même métrique qu'au rendu final.
  • Inspecteur — position, taille, rotation, opacité, couleurs, épaisseur, arrondi, contenu, taille et graisse de police, interligne (en px), alignement. Pour un groupe, la taille est affichée en lecture seule puisqu'elle suit son contenu.
  • Calques — arbre repliable, ordre d'empilement, sélection, montée/descente d'un cran.

Naviguer dans la planche

  • Zoom : Ctrl/Cmd + molette, ancré sous le curseur — le point visé ne bouge pas.
  • Déplacement : molette seule, ou barre espace maintenue + glisser. Tant que la barre espace est enfoncée, aucun geste ne peut déplacer un élément, même en partant de l'un d'eux. Le clic molette fait la même chose.
  • Le sélecteur de zoom de la barre d'outils reste disponible pour revenir à une valeur ronde.

Raccourcis : V R O L T (outils), Ctrl+Z / Ctrl+Y (annuler/rétablir), Ctrl+G / Ctrl+Maj+G (grouper / dissoudre), Ctrl+D (dupliquer), Ctrl+A (tout sélectionner), Suppr, flèches (déplacer d'1 px, 10 px avec Maj), Échap.

La sauvegarde est automatique et différée. Une modification faite par l'agent pendant que la GUI est ouverte y apparaît en direct, via SSE ; une modification faite dans la GUI est enregistrée comme venant de l'utilisateur, et c'est ce que get_changes restitue.

Le badge « n à synchroniser » d'une planche indique le nombre de modifications que l'agent n'a pas encore acquittées.


Architecture

Deux processus, conformément au standard de l'écosystème (@imenam/mcp-gui-interface) :

  • MCP Master (src/index.ts) — protocole JSON-RPC sur stdio, stockage des designs, cycle de vie du worker via GuiLauncher.
  • GUI Worker (src/gui-worker.ts) — serveur Hono, enregistrement auprès du proxy via ProxyClient, API REST + SSE, sert la SPA React.

Les deux communiquent par IPC. Le worker se termine proprement quand le master disparaît (canal IPC fermé, PID parent absent) et quand une autre instance est déjà enregistrée (HTTP 409), ce qui évite les GUI orphelines.

shared/render.ts contient le rendu SVG d'un élément, shared/tree.ts la résolution de la hiérarchie en coordonnées document, et shared/node-defaults.ts les valeurs par défaut. Les trois sont importés à la fois par l'exporteur côté serveur et par le canvas côté navigateur : ce que l'utilisateur voit est exactement ce que export_design — et donc screenshot_design, qui rasterise ce même SVG via resvg — produit.

Les invariants de hiérarchie — parent existant et de type group, absence de cycle, taille des groupes recalculée, ordre préfixe — sont appliqués dans DesignStore.mutate, donc sur tout chemin d'écriture : outil MCP comme sauvegarde depuis la GUI.

Limite assumée de cette version : un groupe se déplace, se pivote et change d'opacité, mais ne se redimensionne pas — mettre son contenu à l'échelle demanderait de propager un facteur dans tout le sous-arbre, ce qui dépasse le cadre d'un premier jet sobre.


Développement

npm install
npm run build          # backend (tsc) + GUI (vite)
npm test               # suite complète

La suite comprend :

  • des tests unitaires du store, de la fabrique d'éléments, du moteur de diff et de l'export ;
  • des tests des outils MCP appelés directement ;
  • une suite de bout en bout (test/e2e.test.ts) qui lance un vrai serveur MCP sur stdio, attend l'enregistrement de son worker auprès du proxy, puis vérifie l'aller-retour complet agent → GUI → agent.

La suite de bout en bout a besoin du proxy (http://localhost:3000, ou E2E_PROXY_URL). S'il est absent, elle est ignorée plutôt qu'en échec.

Pour inspecter le worker sans traverser le proxy, ajoutez ?__direct=1 à son port local : les assets ne sont alors pas préfixés par le chemin de montage.

Méta-configurations du proxy

mcp-design n'expose pas GET /api/configurations : il n'a pas de configuration métier (ni token, ni identifiant de ressource) qui justifierait des profils. Le proxy ignore simplement les MCPs qui n'implémentent pas ces routes.