@higinio/design-mcp 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3,7 +3,8 @@
3
3
  * design-lint — CLI de los validadores puros del MCP (validate_code, validate_motion,
4
4
  * check_slop, checklist_preflight) para correr sobre archivos locales (pre-commit, CI).
5
5
  *
6
- * Uso: design-lint --system celeste|nocturna [--lang css|html|js] [--json] <archivos o globs...>
6
+ * Uso: design-lint --system <id> [--lang css|html|js] [--json] <archivos o globs...>
7
+ * (la lista de ids la imprime USAGE).
7
8
  *
8
9
  * Exit codes: 0 = sin hallazgos de severidad alta · 1 = hay al menos un hallazgo de
9
10
  * severidad alta (level "error") · 2 = error de uso (argumentos inválidos, sin archivos).
@@ -21,6 +22,7 @@ import { validateCode } from "./validate.js";
21
22
  import { auditMotion } from "./motion.js";
22
23
  import { checkSlop } from "./slop.js";
23
24
  import { checkPreflight } from "./preflight.js";
25
+ import { SYSTEM_IDS } from "./systems.js";
24
26
  const LANG_BY_EXT = {
25
27
  ".css": "css",
26
28
  ".html": "html",
@@ -190,7 +192,7 @@ export function formatText(summary) {
190
192
  `en ${n} archivo${n === 1 ? "" : "s"} (sistema: ${summary.system}).`);
191
193
  return lines.join("\n");
192
194
  }
193
- const USAGE = `Uso: design-lint --system celeste|nocturna [--lang css|html|js] [--json] <archivos o globs...>
195
+ const USAGE = `Uso: design-lint --system ${SYSTEM_IDS.join("|")} [--lang css|html|js] [--json] <archivos o globs...>
194
196
 
195
197
  Corre validate_code, validate_motion, check_slop y checklist_preflight sobre cada archivo.
196
198
  El lenguaje se infiere por extensión (.css / .html|.htm / .js|.mjs|.cjs|.ts|.jsx|.tsx) salvo que se pase --lang.
@@ -217,9 +219,9 @@ export function parseArgs(argv) {
217
219
  }
218
220
  if (!opts.help) {
219
221
  if (!opts.system)
220
- opts.errors.push("Falta --system (celeste|nocturna).");
221
- else if (opts.system !== "celeste" && opts.system !== "nocturna")
222
- opts.errors.push(`Sistema desconocido: ${opts.system} (usa celeste o nocturna).`);
222
+ opts.errors.push(`Falta --system (${SYSTEM_IDS.join("|")}).`);
223
+ else if (!SYSTEM_IDS.includes(opts.system))
224
+ opts.errors.push(`Sistema desconocido: ${opts.system} (usa ${SYSTEM_IDS.join(" o ")}).`);
223
225
  if (opts.lang && !["css", "html", "js"].includes(opts.lang))
224
226
  opts.errors.push(`Lenguaje desconocido: ${opts.lang} (usa css, html o js).`);
225
227
  if (opts.patterns.length === 0)
package/dist/doc.js CHANGED
@@ -43,10 +43,24 @@ export function listComponents(sections) {
43
43
  }
44
44
  return out;
45
45
  }
46
- /** Devuelve la sección de un componente por slug exacto o por inclusión. */
46
+ /**
47
+ * Devuelve la sección de un componente por slug exacto o por inclusión. El `body` incluye sus
48
+ * subsecciones (encabezados de nivel mayor que la siguen), con su línea de encabezado, hasta el
49
+ * siguiente encabezado de nivel igual o menor.
50
+ */
47
51
  export function getComponentSpec(sections, name) {
48
52
  const target = slugify(name);
49
- return (sections.find((s) => s.slug === target) ??
50
- sections.find((s) => s.slug.includes(target)) ??
51
- null);
53
+ let idx = sections.findIndex((s) => s.slug === target);
54
+ if (idx === -1)
55
+ idx = sections.findIndex((s) => s.slug.includes(target));
56
+ if (idx === -1)
57
+ return null;
58
+ const found = sections[idx];
59
+ const parts = [found.body];
60
+ for (const sub of sections.slice(idx + 1)) {
61
+ if (sub.level <= found.level)
62
+ break;
63
+ parts.push(`${"#".repeat(sub.level)} ${sub.heading}\n${sub.body}`);
64
+ }
65
+ return { ...found, body: parts.filter(Boolean).join("\n\n") };
52
66
  }
package/dist/preflight.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { SYSTEMS, SYSTEM_IDS } from "./systems.js";
1
2
  /** Verifica estáticamente la estructura de un documento HTML/CSS antes de producción (SEO, a11y, perf estático). */
2
3
  export function checkPreflight(code, lang) {
3
4
  const out = [];
@@ -43,7 +44,7 @@ export function checkPreflight(code, lang) {
43
44
  if (!/<meta\b[^>]*name\s*=\s*["']theme-color["']/i.test(code)) {
44
45
  out.push({ level: "info", rule: "sin-theme-color", line: 0,
45
46
  message: 'No hay <meta name="theme-color">.',
46
- fix: "Añade theme-color con el fondo de la marca (Nocturna #010203, Celeste #f7f8fa); sin él la barra del navegador queda clara sobre una app oscura." });
47
+ fix: `Añade theme-color con el fondo de la marca (${SYSTEM_IDS.map((id) => `${SYSTEMS[id].name} ${SYSTEMS[id].themeColor}`).join(", ")}); sin él la barra del navegador queda clara sobre una app oscura.` });
47
48
  }
48
49
  for (const meta of code.match(/<meta\b[^>]*>/gi) ?? []) {
49
50
  if (/name\s*=\s*["']robots["']/i.test(meta) && /noindex/i.test(meta)) {
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
- import { SYSTEM_IDS } from "./systems.js";
3
+ import { SYSTEMS, SYSTEM_IDS } from "./systems.js";
4
4
  import { resolveToken, searchTokens } from "./tokens.js";
5
5
  import { listComponents, getComponentSpec } from "./doc.js";
6
6
  import { validateCode } from "./validate.js";
@@ -8,13 +8,13 @@ import { generateSnippet } from "./snippet.js";
8
8
  import { auditMotion } from "./motion.js";
9
9
  import { checkSlop } from "./slop.js";
10
10
  import { checkPreflight } from "./preflight.js";
11
- const systemEnum = z.enum(["celeste", "nocturna"]);
11
+ const systemEnum = z.enum(SYSTEM_IDS);
12
12
  function text(payload) {
13
13
  const body = typeof payload === "string" ? payload : JSON.stringify(payload, null, 2);
14
14
  return { content: [{ type: "text", text: body }] };
15
15
  }
16
16
  export function buildServer(store) {
17
- const server = new McpServer({ name: "design-mcp", version: "0.1.0" });
17
+ const server = new McpServer({ name: "design-mcp", version: "0.2.1" });
18
18
  server.registerTool("list_design_systems", {
19
19
  description: "Lista los sistemas de diseño disponibles con su metadata (filosofía, paleta, fuentes).",
20
20
  inputSchema: {},
@@ -177,7 +177,7 @@ export function buildServer(store) {
177
177
  });
178
178
  server.registerPrompt("recomendar_direccion", {
179
179
  title: "Recomendar dirección de diseño desde un brief",
180
- description: "Dado un brief de producto, recomienda el sistema (Celeste vs Nocturna), los valores de dial (variación/motion/densidad) y una dirección de diseño sobria.",
180
+ description: "Dado un brief de producto, recomienda el sistema (entre los sistemas registrados), los valores de dial (variación/motion/densidad) y una dirección de diseño sobria.",
181
181
  argsSchema: { brief: z.string() },
182
182
  }, ({ brief }) => ({
183
183
  messages: [
@@ -186,8 +186,8 @@ export function buildServer(store) {
186
186
  content: {
187
187
  type: "text",
188
188
  text: `Brief: "${brief}".\n\n` +
189
- `Recomienda una dirección de diseño para uno de los dos sistemas de Higinio y justifícalo:\n` +
190
- `- Elige "Celeste Clásica" (light-first, sereno, profesional) o "Nocturna Eléctrica" (dark-first, técnico, alto contraste), en una línea con el porqué.\n` +
189
+ `Recomienda una dirección de diseño para uno de los sistemas de Higinio y justifícalo:\n` +
190
+ `- Elige ${SYSTEM_IDS.map((id) => `"${SYSTEMS[id].name}" (${SYSTEMS[id].pitch})`).join(", ")}, en una línea con el porqué.\n` +
191
191
  `- Propón valores de dial acotados a la sobriedad de la marca: VARIANCE 1-6 (1=simétrico, 6=asimetría contenida), MOTION 1-5 (1=estático, 5=reveals sobrios), DENSITY por superficie (aireada en landings, densa en dashboards).\n` +
192
192
  `- Da 2-3 decisiones clave (tipografía, uso del acento ≤10%, layout), dentro de las reglas duras del sistema elegido.\n\n` +
193
193
  `Ancla la recomendación con list_design_systems y get_rules. No propongas gradientes, glassmorphism ni fuentes genéricas (Inter/Roboto).`,
@@ -208,7 +208,7 @@ export function buildServer(store) {
208
208
  text: `Datos: "${datos}".\n\n` +
209
209
  `Recomienda cómo visualizarlos con la skill dataviz-marca de Higinio:\n` +
210
210
  `- El tipo de gráfica según la función del dato (magnitud → barras; tiempo → línea/área; parte de un todo → barra apilada; relación → scatter; un solo número → stat tile). Si no es una gráfica, dilo. Para tareas más específicas (waterfall, funnel, heatmap, treemap, box plot, bullet en vez de gauge, waffle, banda de confianza, anomalías) la tabla ampliada —con umbral de volumen, grado de accesibilidad y fallback por tipo— vive en la skill dataviz-marca (references/paletas-graficas.md); no la repitas aquí.\n` +
211
- `- Qué paleta del sistema activo usar: categórica (identidad), secuencial (magnitud, una hue claro→oscuro), divergente (polaridad, dos hues + gris al centro) o estado (con icono + etiqueta). Usa la paleta del sistema (Celeste o Nocturna); no inventes hex.\n` +
211
+ `- Qué paleta del sistema activo usar: categórica (identidad), secuencial (magnitud, una hue claro→oscuro), divergente (polaridad, dos hues + gris al centro) o estado (con icono + etiqueta). Usa la paleta del sistema activo; no inventes hex.\n` +
212
212
  `- El fallback de accesibilidad: leyenda + etiquetas directas, vista de tabla, y patrón/textura para daltonismo. La identidad nunca es solo por color.\n\n` +
213
213
  `Nunca dual-axis, ni arcoíris, ni 3D. El texto lleva tokens de ink, no el color de la serie.`,
214
214
  },
package/dist/snippet.js CHANGED
@@ -1,45 +1,54 @@
1
1
  const CSS = {
2
2
  button: `.btn-primary {
3
- background: var(--accent);
4
- color: var(--on-accent, #fff);
3
+ background: var(--action-bg);
4
+ color: var(--text-on-action);
5
5
  border: 1px solid transparent;
6
- border-radius: var(--radius-md, 10px);
7
- padding: 10px 16px;
6
+ border-radius: var(--radius-md);
7
+ min-height: var(--control-height-md);
8
+ padding: var(--button-padding-md);
8
9
  font-family: var(--font-body);
9
10
  }
10
- .btn-primary:hover { background: var(--accent-hover); }`,
11
+ .btn-primary:hover:not(:disabled) { background: var(--action-hover); }
12
+ .btn-primary:active:not(:disabled) { background: var(--action-active); }
13
+ .btn-primary:focus-visible { outline: 2px solid var(--focus-color); outline-offset: 2px; }
14
+ .btn-primary:disabled { opacity: .5; cursor: not-allowed; }`,
11
15
  card: `.card {
12
16
  background: var(--surface);
13
17
  border: 1px solid var(--border);
14
- border-radius: var(--radius-lg, 14px);
15
- padding: 20px;
18
+ border-radius: var(--radius-lg);
19
+ padding: var(--card-padding);
16
20
  color: var(--text-primary);
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: var(--card-content-gap);
17
24
  }`,
18
25
  input: `.input {
19
26
  background: var(--surface);
20
- border: 1px solid var(--border);
21
- border-radius: var(--radius-md, 10px);
22
- padding: 10px 12px;
27
+ border: 1px solid var(--control-border);
28
+ border-radius: var(--radius-sm);
29
+ min-height: var(--control-height-md);
30
+ padding: var(--space-3);
23
31
  color: var(--text-primary);
24
32
  }
25
- .input:focus { outline: 2px solid var(--accent); }`,
33
+ .input::placeholder { color: var(--placeholder); }
34
+ .input:focus-visible { outline: 2px solid var(--focus-color); outline-offset: 2px; }`,
26
35
  };
27
36
  const REACT = {
28
37
  button: `export function Button({ children, ...props }) {
29
- return <button className="btn-primary" {...props}>{children}</button>;
38
+ return <button type="button" {...props} className={['btn', 'btn--primary', props.className].filter(Boolean).join(' ')}>{children}</button>;
30
39
  }`,
31
40
  card: `export function Card({ children }) {
32
41
  return <div className="card">{children}</div>;
33
42
  }`,
34
- input: `export function Input(props) {
35
- return <input className="input" {...props} />;
43
+ input: `export function Input({ className, ...props }) {
44
+ return <input {...props} className={['input', className].filter(Boolean).join(' ')} />;
36
45
  }`,
37
46
  };
38
47
  function tailwindFor(component) {
39
48
  const map = {
40
- button: `<button class="bg-[var(--accent)] text-[var(--on-accent)] rounded-[10px] px-4 py-2.5 font-[var(--font-body)] hover:bg-[var(--accent-hover)]">Acción</button>`,
41
- card: `<div class="bg-[var(--surface)] border border-[var(--border)] rounded-[14px] p-5 text-[var(--text-primary)]">…</div>`,
42
- input: `<input class="bg-[var(--surface)] border border-[var(--border)] rounded-[10px] px-3 py-2.5 text-[var(--text-primary)] focus:outline-2 focus:outline-[var(--accent)]" />`,
49
+ button: `<button type="button" class="bg-[var(--action-bg)] text-[var(--text-on-action)] rounded-[var(--radius-md)] min-h-[var(--control-height-md)] px-5 py-3 font-[family-name:var(--font-body)] hover:enabled:bg-[var(--action-hover)] active:enabled:bg-[var(--action-active)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--focus-color)] disabled:opacity-50">Acción</button>`,
50
+ card: `<div class="flex flex-col gap-[var(--card-content-gap)] bg-[var(--surface)] border border-[var(--border)] rounded-[var(--radius-lg)] p-[var(--card-padding)] text-[var(--text-primary)]">…</div>`,
51
+ input: `<label class="flex flex-col gap-[var(--field-gap)]">Correo<input type="email" autocomplete="email" class="block w-full bg-[var(--surface)] border border-[var(--control-border)] rounded-[var(--radius-sm)] min-h-[var(--control-height-md)] p-3 text-[var(--text-primary)] placeholder:text-[var(--placeholder)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--focus-color)]" /></label>`,
43
52
  };
44
53
  return map[component] ?? `<div class="bg-[var(--surface)] text-[var(--text-primary)] p-4">${component}</div>`;
45
54
  }
package/dist/systems.js CHANGED
@@ -5,6 +5,8 @@ export const SYSTEMS = {
5
5
  name: "Celeste Clásica",
6
6
  mode: "light",
7
7
  philosophy: "Light-first, sereno y profesional. Banca privada boutique, no startup ruidosa.",
8
+ pitch: "light-first, sereno, profesional",
9
+ themeColor: "#f7f8fa",
8
10
  palette: ["#a0c0d4", "#7192ba", "#31343e"],
9
11
  fonts: { display: "Fraunces", body: "Public Sans", mono: "IBM Plex Mono" },
10
12
  },
@@ -14,8 +16,32 @@ export const SYSTEMS = {
14
16
  name: "Nocturna Eléctrica",
15
17
  mode: "dark",
16
18
  philosophy: "Dark-first, técnico y de alto contraste.",
19
+ pitch: "dark-first, técnico, alto contraste",
20
+ themeColor: "#010203",
17
21
  palette: ["#52baed", "#353d7c", "#010203"],
18
22
  fonts: { display: "Space Grotesk", body: "Manrope", mono: "JetBrains Mono" },
19
23
  },
24
+ salvia: {
25
+ id: "salvia",
26
+ dir: "salvia-serena",
27
+ name: "Salvia Serena",
28
+ mode: "light",
29
+ philosophy: "Light-first, natural y relajante. Lino al sol y hojas de salvia; nada urge.",
30
+ pitch: "light-first, natural, relajante",
31
+ themeColor: "#f3f2ec",
32
+ palette: ["#4f6b58", "#2f7d6a", "#2c3630"],
33
+ fonts: { display: "Lora", body: "Nunito Sans", mono: "Source Code Pro" },
34
+ },
35
+ bruma: {
36
+ id: "bruma",
37
+ dir: "bruma-lavanda",
38
+ name: "Bruma Lavanda",
39
+ mode: "light",
40
+ philosophy: "Light-first, en calma, ordenado y legible. Una mañana de niebla en orden; todo en su sitio.",
41
+ pitch: "light-first, en calma, ordenado y legible",
42
+ themeColor: "#f2f1f6",
43
+ palette: ["#5b578a", "#2f7a68", "#2c2b38"],
44
+ fonts: { display: "Bricolage Grotesque", body: "Atkinson Hyperlegible Next", mono: "Atkinson Hyperlegible Mono" },
45
+ },
20
46
  };
21
- export const SYSTEM_IDS = ["celeste", "nocturna"];
47
+ export const SYSTEM_IDS = ["celeste", "nocturna", "salvia", "bruma"];
package/dist/tokens.js CHANGED
@@ -1,6 +1,34 @@
1
- function valueToString(value) {
2
- if (value && typeof value === "object" && "hex" in value) {
3
- return String(value.hex);
1
+ /**
2
+ * Serializa magnitudes y valores compuestos DTCG como valores CSS.
3
+ * @param value - Valor del token con sus unidades y componentes.
4
+ * @param type - Tipo efectivo, incluido el heredado del grupo.
5
+ * @returns Valor CSS; los objetos de tipos desconocidos conservan su JSON.
6
+ */
7
+ function valueToString(value, type) {
8
+ if (Array.isArray(value)) {
9
+ if (type === "cubicBezier")
10
+ return `cubic-bezier(${value.join(", ")})`;
11
+ if (type === "fontFamily")
12
+ return value.map((font) => /^(serif|sans-serif|monospace|system-ui|ui-sans-serif|ui-serif|ui-monospace)$/.test(String(font)) ? font : JSON.stringify(font)).join(", ");
13
+ return value.map((item) => valueToString(item, type)).join(", ");
14
+ }
15
+ if (value && typeof value === "object") {
16
+ const obj = value;
17
+ if ("value" in obj && "unit" in obj)
18
+ return `${obj.value}${obj.unit}`;
19
+ if ("hex" in obj) {
20
+ if (typeof obj.alpha === "number" && obj.alpha < 1 && typeof obj.hex === "string") {
21
+ const hex = obj.hex.replace("#", "");
22
+ const rgb = [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16));
23
+ return `rgba(${rgb.join(", ")}, ${obj.alpha})`;
24
+ }
25
+ return String(obj.hex);
26
+ }
27
+ if (type === "shadow") {
28
+ const dimensions = ["offsetX", "offsetY", "blur", "spread"].map((key) => valueToString(obj[key], "dimension"));
29
+ return `${obj.inset ? "inset " : ""}${dimensions.join(" ")} ${valueToString(obj.color, "color")}`;
30
+ }
31
+ return JSON.stringify(value);
4
32
  }
5
33
  return String(value);
6
34
  }
@@ -15,7 +43,7 @@ export function parseDtcgTokens(json) {
15
43
  if ("$value" in obj) {
16
44
  out.push({
17
45
  path: path.join("."),
18
- value: valueToString(obj.$value),
46
+ value: valueToString(obj.$value, type),
19
47
  type,
20
48
  description: typeof obj.$description === "string" ? obj.$description : undefined,
21
49
  });
@@ -28,7 +56,16 @@ export function parseDtcgTokens(json) {
28
56
  }
29
57
  };
30
58
  walk(json, [], "unknown");
31
- return out;
59
+ const index = new Map(out.map((token) => [token.path, token]));
60
+ const resolve = (path, seen = new Set()) => {
61
+ const token = index.get(path);
62
+ if (!token || seen.has(path))
63
+ return `{${path}}`;
64
+ const next = new Set(seen).add(path);
65
+ return token.value.replace(/\{([^{}]+)\}/g, (_, reference) => resolve(reference, next));
66
+ };
67
+ const resolved = out.map((token) => ({ ...token, value: resolve(token.path) }));
68
+ return resolved;
32
69
  }
33
70
  /** Extrae variables CSS `--nombre: valor;` del bloque :root canónico (ignora overrides de tema). */
34
71
  export function parseCssAliases(css) {
package/dist/validate.js CHANGED
@@ -1,3 +1,9 @@
1
+ /** primary-300 de los sistemas que lo reservan para decoración: nunca como color de texto. */
2
+ const PRIMARY_300_NO_TEXTO = {
3
+ celeste: "#a0c0d4",
4
+ salvia: "#97b09e",
5
+ bruma: "#9d99c4",
6
+ };
1
7
  function relLuminance(hex) {
2
8
  const h = hex.replace("#", "");
3
9
  const n = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
@@ -26,15 +32,17 @@ export function validateCode(sys, code, lang) {
26
32
  const lines = code.split("\n");
27
33
  const paletteHex = new Set(sys.tokens.filter((t) => t.type === "color").map((t) => t.value.toLowerCase()));
28
34
  const aliasByHex = hexToAlias(sys);
35
+ const primary300 = PRIMARY_300_NO_TEXTO[sys.meta.id];
29
36
  lines.forEach((line, i) => {
30
37
  const ln = i + 1;
31
38
  const lower = line.toLowerCase();
32
- const primary300AsText = sys.meta.id === "celeste" && /(?<![\w-])color\s*:\s*#a0c0d4/.test(lower);
39
+ const primary300AsText = primary300 !== undefined
40
+ && new RegExp(`(?<![\\w-])color\\s*:\\s*${primary300}`).test(lower);
33
41
  for (const hex of lower.match(/#[0-9a-f]{6}\b/g) ?? []) {
34
- if (hex === "#a0c0d4" && primary300AsText) {
42
+ if (hex === primary300 && primary300AsText) {
35
43
  out.push({ level: "error", rule: "primary-300-no-texto", line: ln,
36
- message: "primary-300 (#a0c0d4) no debe usarse como color de texto.",
37
- fix: "Usa --text-primary o --link; reserva #a0c0d4 para decoración." });
44
+ message: `primary-300 (${primary300}) no debe usarse como color de texto.`,
45
+ fix: `Usa --text-primary o --link; reserva ${primary300} para decoración.` });
38
46
  }
39
47
  else if (aliasByHex.has(hex)) {
40
48
  out.push({ level: "warn", rule: "hex-directo", line: ln,
@@ -84,7 +92,7 @@ export function validateCode(sys, code, lang) {
84
92
  }
85
93
  // Escala tipográfica derivada de los aliases --text-* en rem (mismo enfoque que drift-radio con
86
94
  // --radius-*). Si los tokens no la expusieran, cae a {12,14,16,18,20,25,31,39,49}: la escala 1.25
87
- // base 16 de §4.2, idéntica en ambos sistemas. Solo se evalúan px; rem/em y 0 quedan exentos.
95
+ // base 16 de §4.2, idéntica en todos los sistemas. Solo se evalúan px; rem/em y 0 quedan exentos.
88
96
  const allowedFontPx = new Set([0]);
89
97
  for (const [alias, val] of sys.aliases) {
90
98
  if (!/^--text-/.test(alias))
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@higinio/design-mcp",
3
- "version": "0.1.1",
4
- "description": "MCP server de los design systems de Higinio (Celeste Clásica y Nocturna Eléctrica).",
3
+ "version": "0.2.1",
4
+ "description": "MCP server de los design systems de Higinio (Celeste Clásica, Nocturna Eléctrica, Salvia Serena y Bruma Lavanda).",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
7
7
  "author": "Higinio Sosa",
@@ -19,7 +19,9 @@
19
19
  "design-system",
20
20
  "design-tokens",
21
21
  "celeste-clasica",
22
- "nocturna-electrica"
22
+ "nocturna-electrica",
23
+ "salvia-serena",
24
+ "bruma-lavanda"
23
25
  ],
24
26
  "bin": {
25
27
  "design-mcp": "dist/index.js",