@mp3wizard/figma-console-mcp 1.32.2 → 1.34.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.
Files changed (141) hide show
  1. package/README.md +26 -17
  2. package/dist/cloudflare/core/cloud-websocket-connector.js +18 -0
  3. package/dist/cloudflare/core/design-code-tools.js +60 -17
  4. package/dist/cloudflare/core/design-system-manifest.js +19 -14
  5. package/dist/cloudflare/core/design-system-tools.js +43 -34
  6. package/dist/cloudflare/core/diagnose-tool.js +4 -0
  7. package/dist/cloudflare/core/enrichment/enrichment-service.js +11 -5
  8. package/dist/cloudflare/core/enrichment/style-resolver.js +38 -18
  9. package/dist/cloudflare/core/figma-api.js +118 -54
  10. package/dist/cloudflare/core/figma-tools.js +179 -63
  11. package/dist/cloudflare/core/port-discovery.js +404 -31
  12. package/dist/cloudflare/core/tokens/alias-resolver.js +75 -5
  13. package/dist/cloudflare/core/tokens/config.js +10 -0
  14. package/dist/cloudflare/core/tokens/dialect.js +232 -0
  15. package/dist/cloudflare/core/tokens/figma-converter.js +144 -16
  16. package/dist/cloudflare/core/tokens/formatters/css-vars.js +21 -12
  17. package/dist/cloudflare/core/tokens/formatters/dtcg.js +106 -30
  18. package/dist/cloudflare/core/tokens/formatters/json.js +28 -10
  19. package/dist/cloudflare/core/tokens/formatters/scss.js +19 -13
  20. package/dist/cloudflare/core/tokens/formatters/style-dictionary-v3.js +15 -9
  21. package/dist/cloudflare/core/tokens/formatters/tailwind-v4.js +14 -9
  22. package/dist/cloudflare/core/tokens/formatters/tokens-studio.js +11 -5
  23. package/dist/cloudflare/core/tokens/index.js +2 -1
  24. package/dist/cloudflare/core/tokens/parsers/dtcg.js +32 -5
  25. package/dist/cloudflare/core/tokens/schemas.js +4 -0
  26. package/dist/cloudflare/core/tokens-tools.js +1017 -88
  27. package/dist/cloudflare/core/version-tools.js +44 -3
  28. package/dist/cloudflare/core/websocket-connector.js +42 -0
  29. package/dist/cloudflare/core/websocket-server.js +99 -8
  30. package/dist/cloudflare/core/write-tools.js +355 -86
  31. package/dist/cloudflare/index.js +7 -7
  32. package/dist/core/design-code-tools.d.ts.map +1 -1
  33. package/dist/core/design-code-tools.js +60 -17
  34. package/dist/core/design-code-tools.js.map +1 -1
  35. package/dist/core/design-system-manifest.d.ts +1 -0
  36. package/dist/core/design-system-manifest.d.ts.map +1 -1
  37. package/dist/core/design-system-manifest.js +19 -14
  38. package/dist/core/design-system-manifest.js.map +1 -1
  39. package/dist/core/design-system-tools.d.ts.map +1 -1
  40. package/dist/core/design-system-tools.js +43 -34
  41. package/dist/core/design-system-tools.js.map +1 -1
  42. package/dist/core/diagnose-tool.d.ts +8 -0
  43. package/dist/core/diagnose-tool.d.ts.map +1 -1
  44. package/dist/core/diagnose-tool.js +4 -0
  45. package/dist/core/diagnose-tool.js.map +1 -1
  46. package/dist/core/enrichment/enrichment-service.d.ts.map +1 -1
  47. package/dist/core/enrichment/enrichment-service.js +11 -5
  48. package/dist/core/enrichment/enrichment-service.js.map +1 -1
  49. package/dist/core/enrichment/style-resolver.d.ts +7 -2
  50. package/dist/core/enrichment/style-resolver.d.ts.map +1 -1
  51. package/dist/core/enrichment/style-resolver.js +38 -18
  52. package/dist/core/enrichment/style-resolver.js.map +1 -1
  53. package/dist/core/figma-api.d.ts +18 -9
  54. package/dist/core/figma-api.d.ts.map +1 -1
  55. package/dist/core/figma-api.js +118 -54
  56. package/dist/core/figma-api.js.map +1 -1
  57. package/dist/core/figma-connector.d.ts +12 -0
  58. package/dist/core/figma-connector.d.ts.map +1 -1
  59. package/dist/core/figma-tools.d.ts.map +1 -1
  60. package/dist/core/figma-tools.js +179 -63
  61. package/dist/core/figma-tools.js.map +1 -1
  62. package/dist/core/port-discovery.d.ts +40 -0
  63. package/dist/core/port-discovery.d.ts.map +1 -1
  64. package/dist/core/port-discovery.js +404 -31
  65. package/dist/core/port-discovery.js.map +1 -1
  66. package/dist/core/tokens/alias-resolver.d.ts +45 -3
  67. package/dist/core/tokens/alias-resolver.d.ts.map +1 -1
  68. package/dist/core/tokens/alias-resolver.js +75 -5
  69. package/dist/core/tokens/alias-resolver.js.map +1 -1
  70. package/dist/core/tokens/config.d.ts +28 -0
  71. package/dist/core/tokens/config.d.ts.map +1 -1
  72. package/dist/core/tokens/config.js +10 -0
  73. package/dist/core/tokens/config.js.map +1 -1
  74. package/dist/core/tokens/dialect.d.ts +107 -0
  75. package/dist/core/tokens/dialect.d.ts.map +1 -0
  76. package/dist/core/tokens/dialect.js +233 -0
  77. package/dist/core/tokens/dialect.js.map +1 -0
  78. package/dist/core/tokens/figma-converter.d.ts +23 -2
  79. package/dist/core/tokens/figma-converter.d.ts.map +1 -1
  80. package/dist/core/tokens/figma-converter.js +144 -16
  81. package/dist/core/tokens/figma-converter.js.map +1 -1
  82. package/dist/core/tokens/formatters/css-vars.d.ts.map +1 -1
  83. package/dist/core/tokens/formatters/css-vars.js +21 -12
  84. package/dist/core/tokens/formatters/css-vars.js.map +1 -1
  85. package/dist/core/tokens/formatters/dtcg.d.ts +2 -2
  86. package/dist/core/tokens/formatters/dtcg.d.ts.map +1 -1
  87. package/dist/core/tokens/formatters/dtcg.js +106 -30
  88. package/dist/core/tokens/formatters/dtcg.js.map +1 -1
  89. package/dist/core/tokens/formatters/json.d.ts.map +1 -1
  90. package/dist/core/tokens/formatters/json.js +28 -10
  91. package/dist/core/tokens/formatters/json.js.map +1 -1
  92. package/dist/core/tokens/formatters/scss.d.ts.map +1 -1
  93. package/dist/core/tokens/formatters/scss.js +19 -13
  94. package/dist/core/tokens/formatters/scss.js.map +1 -1
  95. package/dist/core/tokens/formatters/style-dictionary-v3.d.ts.map +1 -1
  96. package/dist/core/tokens/formatters/style-dictionary-v3.js +15 -9
  97. package/dist/core/tokens/formatters/style-dictionary-v3.js.map +1 -1
  98. package/dist/core/tokens/formatters/tailwind-v4.d.ts.map +1 -1
  99. package/dist/core/tokens/formatters/tailwind-v4.js +14 -9
  100. package/dist/core/tokens/formatters/tailwind-v4.js.map +1 -1
  101. package/dist/core/tokens/formatters/tokens-studio.d.ts.map +1 -1
  102. package/dist/core/tokens/formatters/tokens-studio.js +11 -5
  103. package/dist/core/tokens/formatters/tokens-studio.js.map +1 -1
  104. package/dist/core/tokens/index.d.ts +2 -1
  105. package/dist/core/tokens/index.d.ts.map +1 -1
  106. package/dist/core/tokens/index.js +2 -1
  107. package/dist/core/tokens/index.js.map +1 -1
  108. package/dist/core/tokens/parsers/dtcg.js +32 -5
  109. package/dist/core/tokens/parsers/dtcg.js.map +1 -1
  110. package/dist/core/tokens/schemas.d.ts +3 -0
  111. package/dist/core/tokens/schemas.d.ts.map +1 -1
  112. package/dist/core/tokens/schemas.js +4 -0
  113. package/dist/core/tokens/schemas.js.map +1 -1
  114. package/dist/core/tokens/types.d.ts +57 -1
  115. package/dist/core/tokens/types.d.ts.map +1 -1
  116. package/dist/core/tokens/types.js.map +1 -1
  117. package/dist/core/tokens-tools.d.ts +250 -7
  118. package/dist/core/tokens-tools.d.ts.map +1 -1
  119. package/dist/core/tokens-tools.js +1017 -88
  120. package/dist/core/tokens-tools.js.map +1 -1
  121. package/dist/core/version-tools.d.ts.map +1 -1
  122. package/dist/core/version-tools.js +44 -3
  123. package/dist/core/version-tools.js.map +1 -1
  124. package/dist/core/websocket-connector.d.ts +38 -0
  125. package/dist/core/websocket-connector.d.ts.map +1 -1
  126. package/dist/core/websocket-connector.js +42 -0
  127. package/dist/core/websocket-connector.js.map +1 -1
  128. package/dist/core/websocket-server.d.ts +23 -0
  129. package/dist/core/websocket-server.d.ts.map +1 -1
  130. package/dist/core/websocket-server.js +99 -8
  131. package/dist/core/websocket-server.js.map +1 -1
  132. package/dist/core/write-tools.d.ts.map +1 -1
  133. package/dist/core/write-tools.js +355 -86
  134. package/dist/core/write-tools.js.map +1 -1
  135. package/dist/local.d.ts +0 -1
  136. package/dist/local.d.ts.map +1 -1
  137. package/dist/local.js +253 -63
  138. package/dist/local.js.map +1 -1
  139. package/figma-desktop-bridge/code.js +382 -28
  140. package/figma-desktop-bridge/ui.html +578 -292
  141. package/package.json +2 -2
@@ -35,9 +35,13 @@
35
35
  * path verbatim (with prefix if configured). They're still valid CSS vars
36
36
  * — they just don't generate Tailwind utility classes.
37
37
  */
38
+ import { buildTokenIndex, referenceTargetPath } from "../alias-resolver.js";
38
39
  export function formatTailwindV4(doc, opts) {
39
40
  const warnings = [];
40
41
  const files = [];
42
+ // Whole-document index so set-qualified alias references resolve to the
43
+ // target token's own path when generating var(--...) names.
44
+ const tokenIndex = buildTokenIndex(doc, warnings);
41
45
  const splitByMode = opts.target.splitByMode ?? false;
42
46
  const splitByCollection = opts.target.splitByCollection ?? false;
43
47
  const prefix = opts.target.prefix ?? "";
@@ -46,7 +50,7 @@ export function formatTailwindV4(doc, opts) {
46
50
  for (const mode of set.modes) {
47
51
  files.push({
48
52
  path: filenameFor(opts, set, mode),
49
- content: renderTailwindFile(doc.sets.filter((s) => s.name === set.name), [mode], prefix, warnings),
53
+ content: renderTailwindFile(doc.sets.filter((s) => s.name === set.name), [mode], prefix, tokenIndex, warnings),
50
54
  });
51
55
  }
52
56
  }
@@ -59,7 +63,7 @@ export function formatTailwindV4(doc, opts) {
59
63
  for (const mode of allModes) {
60
64
  files.push({
61
65
  path: filenameFor(opts, undefined, mode),
62
- content: renderTailwindFile(doc.sets, [mode], prefix, warnings),
66
+ content: renderTailwindFile(doc.sets, [mode], prefix, tokenIndex, warnings),
63
67
  });
64
68
  }
65
69
  }
@@ -67,7 +71,7 @@ export function formatTailwindV4(doc, opts) {
67
71
  for (const set of doc.sets) {
68
72
  files.push({
69
73
  path: filenameFor(opts, set),
70
- content: renderTailwindFile([set], set.modes, prefix, warnings),
74
+ content: renderTailwindFile([set], set.modes, prefix, tokenIndex, warnings),
71
75
  });
72
76
  }
73
77
  }
@@ -79,7 +83,7 @@ export function formatTailwindV4(doc, opts) {
79
83
  allModes.add(m);
80
84
  files.push({
81
85
  path: filenameFor(opts),
82
- content: renderTailwindFile(doc.sets, [...allModes], prefix, warnings),
86
+ content: renderTailwindFile(doc.sets, [...allModes], prefix, tokenIndex, warnings),
83
87
  });
84
88
  }
85
89
  return { files, warnings };
@@ -110,7 +114,7 @@ function slugify(s) {
110
114
  * selectors (`.dark`, `[data-theme="..."]`) so the user can swap modes
111
115
  * at runtime.
112
116
  */
113
- function renderTailwindFile(sets, modes, prefix, warnings) {
117
+ function renderTailwindFile(sets, modes, prefix, tokenIndex, warnings) {
114
118
  const lines = [];
115
119
  lines.push("/* Generated by figma-console-mcp — do not edit by hand */");
116
120
  lines.push("");
@@ -125,7 +129,7 @@ function renderTailwindFile(sets, modes, prefix, warnings) {
125
129
  const value = token.values[primaryMode];
126
130
  if (!value)
127
131
  continue;
128
- emitTailwindTokenLines(token, value, prefix, lines, warnings);
132
+ emitTailwindTokenLines(token, value, prefix, tokenIndex, lines, warnings);
129
133
  }
130
134
  }
131
135
  lines.push("}");
@@ -142,7 +146,7 @@ function renderTailwindFile(sets, modes, prefix, warnings) {
142
146
  const value = token.values[mode];
143
147
  if (!value)
144
148
  continue;
145
- emitTailwindTokenLines(token, value, prefix, lines, warnings);
149
+ emitTailwindTokenLines(token, value, prefix, tokenIndex, lines, warnings);
146
150
  }
147
151
  }
148
152
  lines.push("}");
@@ -160,7 +164,7 @@ function selectorFor(mode) {
160
164
  * Emit one or more CSS custom property declarations for a token, using
161
165
  * Tailwind v4's namespace conventions where possible.
162
166
  */
163
- function emitTailwindTokenLines(token, value, prefix, out, warnings) {
167
+ function emitTailwindTokenLines(token, value, prefix, tokenIndex, out, warnings) {
164
168
  const cssName = `--${prefix}${pathToTailwindName(token.path, token.type)}`;
165
169
  if (value.reference) {
166
170
  // Cross-library alias: skip with a comment.
@@ -173,7 +177,8 @@ function emitTailwindTokenLines(token, value, prefix, out, warnings) {
173
177
  return;
174
178
  }
175
179
  // Local alias → var() reference using the same namespace mapping.
176
- const refPath = bareRef.split(".");
180
+ // Resolve set-qualified references to the target token's own path.
181
+ const refPath = referenceTargetPath(value.reference, tokenIndex);
177
182
  const targetName = pathToTailwindName(refPath, token.type);
178
183
  out.push(` ${cssName}: var(--${prefix}${targetName});`);
179
184
  return;
@@ -39,9 +39,13 @@
39
39
  * and the figma-console-mcp metadata stamped onto it.
40
40
  */
41
41
  import { FIGMA_MCP_EXTENSION_KEY } from "../types.js";
42
+ import { buildTokenIndex, referenceTargetPath } from "../alias-resolver.js";
42
43
  export function formatTokensStudio(doc, opts) {
43
44
  const warnings = [];
44
45
  const files = [];
46
+ // Whole-document index so set-qualified alias references resolve to the
47
+ // target token's own path (Tokens Studio references are set-relative).
48
+ const tokenIndex = buildTokenIndex(doc, warnings);
45
49
  // Track which (setName, mode) → filename pairs exist so $metadata + $themes
46
50
  // can reference them.
47
51
  const setNamesByMode = [];
@@ -53,7 +57,7 @@ export function formatTokensStudio(doc, opts) {
53
57
  setNamesByMode.push({ setName: fileSetName, mode, filename });
54
58
  files.push({
55
59
  path: filename,
56
- content: renderSetFile(set, mode, warnings),
60
+ content: renderSetFile(set, mode, tokenIndex, warnings),
57
61
  });
58
62
  }
59
63
  }
@@ -84,13 +88,13 @@ function slugify(s) {
84
88
  * Render a single set's tokens for a specific mode in Tokens Studio's
85
89
  * bare-key JSON format.
86
90
  */
87
- function renderSetFile(set, mode, warnings) {
91
+ function renderSetFile(set, mode, tokenIndex, warnings) {
88
92
  const tree = {};
89
93
  for (const token of set.tokens) {
90
94
  const value = token.values[mode];
91
95
  if (!value)
92
96
  continue;
93
- const tsValue = tsValueFor(value, token, warnings);
97
+ const tsValue = tsValueFor(value, token, tokenIndex, warnings);
94
98
  if (tsValue === undefined)
95
99
  continue;
96
100
  // Walk path creating nested groups.
@@ -212,14 +216,16 @@ function tsTypeFor(token) {
212
216
  }
213
217
  return token.type;
214
218
  }
215
- function tsValueFor(value, token, warnings) {
219
+ function tsValueFor(value, token, tokenIndex, warnings) {
216
220
  if (value.reference) {
217
221
  const bare = value.reference.replace(/^\{|\}$/g, "");
218
222
  if (bare.startsWith("__library:") || bare === "unknown") {
219
223
  warnings.push(`Skipped ${token.path.join(".")} in Tokens Studio — cross-library alias unresolved.`);
220
224
  return undefined;
221
225
  }
222
- return `{${bare}}`;
226
+ // Tokens Studio references are token-path based (no set qualifier) —
227
+ // resolve set-qualified references to the target token's own path.
228
+ return `{${referenceTargetPath(value.reference, tokenIndex).join(".")}}`;
223
229
  }
224
230
  if (value.literal === undefined || value.literal === null)
225
231
  return undefined;
@@ -11,5 +11,6 @@ export { TokensConfigSchema, loadTokensConfig, findTokensConfig, DEFAULT_TOKENS_
11
11
  export { ExportTokensInputSchema, ImportTokensInputSchema, ExportFormatSchema, ImportFormatSchema, SyncStrategySchema, ConflictResolutionSchema, } from "./schemas.js";
12
12
  export { parse, detectFormat, } from "./parsers/index.js";
13
13
  export { format, } from "./formatters/index.js";
14
- export { buildTokenIndex, resolveReference, resolveAliasChain, validateAliases, formatDtcgReference, parseDtcgReference, } from "./alias-resolver.js";
14
+ export { buildTokenIndex, buildTokenLookup, resolveReference, resolveAliasChain, validateAliases, formatDtcgReference, parseDtcgReference, referenceTargetPath, slugifySetName, } from "./alias-resolver.js";
15
15
  export { convertFigmaVariablesToDocument, } from "./figma-converter.js";
16
+ export { canonicalizeTokenValueForComparison, colorLiteralToCanonicalHex, colorValueTo2025, dimensionLiteralTo2025, hexToRawRgba, stripRawColorFromValues, clamp01, } from "./dialect.js";
@@ -132,7 +132,14 @@ function walkGroup(node, path, inheritedType, tokens, modes, warnings, fileMode)
132
132
  warnings.push(`Non-group, non-token entry at ${[...path, key].join(".")}; skipping.`);
133
133
  continue;
134
134
  }
135
- const childPath = [...path, key];
135
+ // Reserved "@" key: the formatter emits a leaf under "@" when its name
136
+ // collided with a group of the same name (leaf/group conflict). The
137
+ // `leafRemap` flag in our extension marks it; strip the synthetic "@"
138
+ // segment so the token gets its original path back.
139
+ const isRemappedLeaf = key === "@" &&
140
+ isLeafToken(value) &&
141
+ hasLeafRemapFlag(value);
142
+ const childPath = isRemappedLeaf ? [...path] : [...path, key];
136
143
  if (isLeafToken(value)) {
137
144
  const token = extractToken(childPath, value, groupType, warnings, fileMode);
138
145
  tokens.push(token);
@@ -147,6 +154,19 @@ function walkGroup(node, path, inheritedType, tokens, modes, warnings, fileMode)
147
154
  function isLeafToken(node) {
148
155
  return "$value" in node;
149
156
  }
157
+ /**
158
+ * True when a leaf node carries the `leafRemap` marker our formatter sets
159
+ * on tokens emitted under the reserved "@" key (leaf/group name conflicts).
160
+ */
161
+ function hasLeafRemapFlag(node) {
162
+ const ext = node.$extensions;
163
+ if (!ext || typeof ext !== "object")
164
+ return false;
165
+ const mcp = ext[FIGMA_MCP_EXTENSION_KEY];
166
+ if (!mcp || typeof mcp !== "object")
167
+ return false;
168
+ return mcp.leafRemap === true;
169
+ }
150
170
  function extractToken(path, node, inheritedType, warnings, fileMode) {
151
171
  const rawType = node.$type;
152
172
  let type;
@@ -169,12 +189,15 @@ function extractToken(path, node, inheritedType, warnings, fileMode) {
169
189
  ? ext[FIGMA_MCP_EXTENSION_KEY]
170
190
  : undefined;
171
191
  const stashedModes = mcpExt?.modes;
192
+ const stashedPrimaryMode = typeof mcpExt?.primaryMode === "string" ? mcpExt.primaryMode : undefined;
172
193
  // Decide which mode name to assign to the primary $value.
173
194
  // 1. If the file declares a fileMode (splitByMode output), use that.
174
- // 2. Otherwise fall back to "Default" the parser can't know the
175
- // mode without that hint.
195
+ // 2. Otherwise use the token-level `primaryMode` our formatter stashes
196
+ // in $extensions["figma-console-mcp"] (one-file-multi-mode output).
197
+ // 3. Otherwise fall back to "Default" — the parser can't know the
198
+ // mode without a hint.
176
199
  // Then absorb any stashedModes (one-file-multi-mode output) verbatim.
177
- const primaryMode = fileMode ?? "Default";
200
+ const primaryMode = fileMode ?? stashedPrimaryMode ?? "Default";
178
201
  values[primaryMode] = decodeValue(node.$value);
179
202
  if (stashedModes) {
180
203
  for (const [modeName, modeValue] of Object.entries(stashedModes)) {
@@ -192,9 +215,13 @@ function extractToken(path, node, inheritedType, warnings, fileMode) {
192
215
  if (vendor === FIGMA_MCP_EXTENSION_KEY &&
193
216
  payload &&
194
217
  typeof payload === "object") {
195
- // Strip the "modes" we already absorbed into values.
218
+ // Strip transient round-trip markers we already absorbed:
219
+ // "modes"/"primaryMode" were folded into `values`, "leafRemap"
220
+ // was consumed when the "@" path segment got stripped.
196
221
  const cleaned = { ...payload };
197
222
  delete cleaned.modes;
223
+ delete cleaned.primaryMode;
224
+ delete cleaned.leafRemap;
198
225
  if (Object.keys(cleaned).length > 0) {
199
226
  extensions[vendor] = cleaned;
200
227
  }
@@ -88,6 +88,10 @@ export const ExportTokensInputSchema = z.object({
88
88
  .boolean()
89
89
  .optional()
90
90
  .describe("Emit one file per Figma collection. Default false. Useful when collections map to different runtime themes."),
91
+ dtcgDialect: z
92
+ .enum(["legacy", "2025"])
93
+ .optional()
94
+ .describe("DTCG dialect for dtcg/json-flat/json-nested outputs. legacy (default): hex-string colors, maximum compatibility (Style Dictionary v4, Tokens Studio). 2025: DTCG 2025.10 object colors/dimensions (Style Dictionary v5+). Other formats (css-vars, scss, tailwind, ts-module) render final code and ignore this option."),
91
95
  colorFormat: ColorFormatSchema.optional().describe("Color value format in the output. Default: 'hex'. Use 'oklch' for modern Tailwind v4 charts."),
92
96
  sizeUnit: SizeUnitSchema.optional().describe("Unit for dimension tokens. Default: 'rem' for web outputs, 'pt' for iOS, 'dp' for Android."),
93
97
  remBase: z