@mp3wizard/figma-console-mcp 1.27.2 → 1.29.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 (83) hide show
  1. package/README.md +21 -12
  2. package/dist/apps/design-system-dashboard/mcp-app.html +78 -78
  3. package/dist/apps/token-browser/mcp-app.html +60 -59
  4. package/dist/cloudflare/core/design-system-tools.js +1 -1
  5. package/dist/cloudflare/core/figma-api.js +19 -0
  6. package/dist/cloudflare/core/library-tools.js +580 -0
  7. package/dist/cloudflare/core/tokens/alias-resolver.js +37 -0
  8. package/dist/cloudflare/core/tokens/formatters/json.js +185 -5
  9. package/dist/cloudflare/core/tokens/formatters/scss.js +251 -3
  10. package/dist/cloudflare/core/tokens/formatters/stubs.js +6 -4
  11. package/dist/cloudflare/core/tokens/formatters/style-dictionary-v3.js +206 -3
  12. package/dist/cloudflare/core/tokens/formatters/tailwind-v3.js +236 -3
  13. package/dist/cloudflare/core/tokens/formatters/tailwind-v4.js +329 -3
  14. package/dist/cloudflare/core/tokens/formatters/tokens-studio.js +249 -3
  15. package/dist/cloudflare/core/tokens/formatters/ts-module.js +197 -3
  16. package/dist/cloudflare/core/tokens/index.js +1 -1
  17. package/dist/cloudflare/core/tokens/parsers/stubs.js +13 -6
  18. package/dist/cloudflare/core/tokens-tools.js +12 -2
  19. package/dist/cloudflare/index.js +15 -7
  20. package/dist/core/design-system-tools.d.ts +50 -0
  21. package/dist/core/design-system-tools.d.ts.map +1 -1
  22. package/dist/core/design-system-tools.js +1 -1
  23. package/dist/core/design-system-tools.js.map +1 -1
  24. package/dist/core/figma-api.d.ts +15 -0
  25. package/dist/core/figma-api.d.ts.map +1 -1
  26. package/dist/core/figma-api.js +19 -0
  27. package/dist/core/figma-api.js.map +1 -1
  28. package/dist/core/library-tools.d.ts +17 -0
  29. package/dist/core/library-tools.d.ts.map +1 -0
  30. package/dist/core/library-tools.js +581 -0
  31. package/dist/core/library-tools.js.map +1 -0
  32. package/dist/core/tokens/alias-resolver.d.ts +15 -0
  33. package/dist/core/tokens/alias-resolver.d.ts.map +1 -1
  34. package/dist/core/tokens/alias-resolver.js +37 -0
  35. package/dist/core/tokens/alias-resolver.js.map +1 -1
  36. package/dist/core/tokens/formatters/json.d.ts +34 -2
  37. package/dist/core/tokens/formatters/json.d.ts.map +1 -1
  38. package/dist/core/tokens/formatters/json.js +185 -5
  39. package/dist/core/tokens/formatters/json.js.map +1 -1
  40. package/dist/core/tokens/formatters/scss.d.ts +23 -1
  41. package/dist/core/tokens/formatters/scss.d.ts.map +1 -1
  42. package/dist/core/tokens/formatters/scss.js +251 -3
  43. package/dist/core/tokens/formatters/scss.js.map +1 -1
  44. package/dist/core/tokens/formatters/stubs.d.ts +3 -3
  45. package/dist/core/tokens/formatters/stubs.d.ts.map +1 -1
  46. package/dist/core/tokens/formatters/stubs.js +6 -4
  47. package/dist/core/tokens/formatters/stubs.js.map +1 -1
  48. package/dist/core/tokens/formatters/style-dictionary-v3.d.ts +42 -1
  49. package/dist/core/tokens/formatters/style-dictionary-v3.d.ts.map +1 -1
  50. package/dist/core/tokens/formatters/style-dictionary-v3.js +206 -3
  51. package/dist/core/tokens/formatters/style-dictionary-v3.js.map +1 -1
  52. package/dist/core/tokens/formatters/tailwind-v3.d.ts +34 -1
  53. package/dist/core/tokens/formatters/tailwind-v3.d.ts.map +1 -1
  54. package/dist/core/tokens/formatters/tailwind-v3.js +236 -3
  55. package/dist/core/tokens/formatters/tailwind-v3.js.map +1 -1
  56. package/dist/core/tokens/formatters/tailwind-v4.d.ts +38 -1
  57. package/dist/core/tokens/formatters/tailwind-v4.d.ts.map +1 -1
  58. package/dist/core/tokens/formatters/tailwind-v4.js +329 -3
  59. package/dist/core/tokens/formatters/tailwind-v4.js.map +1 -1
  60. package/dist/core/tokens/formatters/tokens-studio.d.ts +41 -1
  61. package/dist/core/tokens/formatters/tokens-studio.d.ts.map +1 -1
  62. package/dist/core/tokens/formatters/tokens-studio.js +249 -3
  63. package/dist/core/tokens/formatters/tokens-studio.js.map +1 -1
  64. package/dist/core/tokens/formatters/ts-module.d.ts +32 -1
  65. package/dist/core/tokens/formatters/ts-module.d.ts.map +1 -1
  66. package/dist/core/tokens/formatters/ts-module.js +197 -3
  67. package/dist/core/tokens/formatters/ts-module.js.map +1 -1
  68. package/dist/core/tokens/index.d.ts +1 -1
  69. package/dist/core/tokens/index.d.ts.map +1 -1
  70. package/dist/core/tokens/index.js +1 -1
  71. package/dist/core/tokens/index.js.map +1 -1
  72. package/dist/core/tokens/parsers/stubs.d.ts +9 -5
  73. package/dist/core/tokens/parsers/stubs.d.ts.map +1 -1
  74. package/dist/core/tokens/parsers/stubs.js +13 -6
  75. package/dist/core/tokens/parsers/stubs.js.map +1 -1
  76. package/dist/core/tokens-tools.d.ts.map +1 -1
  77. package/dist/core/tokens-tools.js +12 -2
  78. package/dist/core/tokens-tools.js.map +1 -1
  79. package/dist/local.d.ts.map +1 -1
  80. package/dist/local.js +6 -0
  81. package/dist/local.js.map +1 -1
  82. package/figma-desktop-bridge/code.js +1 -1
  83. package/package.json +18 -3
@@ -1,4 +1,250 @@
1
- import { FormatterNotImplementedError } from "./stubs.js";
2
- export function formatTokensStudio(_doc, _opts) {
3
- throw new FormatterNotImplementedError("Tokens Studio");
1
+ /**
2
+ * Tokens Studio for Figma JSON formatter.
3
+ *
4
+ * Tokens Studio uses a multi-file layout:
5
+ *
6
+ * $themes.json — array of theme definitions with selectedTokenSets
7
+ * $metadata.json — list of token set names + tokenSetOrder
8
+ * primitives.json — one file per token set
9
+ * semantic.json
10
+ * theme/light.json
11
+ * theme/dark.json
12
+ *
13
+ * Each per-set file holds tokens with the Tokens Studio shape:
14
+ *
15
+ * {
16
+ * "color": {
17
+ * "primary": {
18
+ * "value": "#4085F2",
19
+ * "type": "color",
20
+ * "description": "Primary brand color"
21
+ * }
22
+ * }
23
+ * }
24
+ *
25
+ * Tokens Studio uses bare `value` / `type` (no `$` prefix — same as
26
+ * Style Dictionary v3, which the plugin's format is based on). Aliases
27
+ * use `{path.to.token}` syntax.
28
+ *
29
+ * `$themes.json` carries the Figma collection/mode bindings that make
30
+ * Tokens Studio's "send to Figma" feature work — preserved here for
31
+ * round-trip with Tokens Studio plugin users (notably Altitude).
32
+ *
33
+ * Output strategy:
34
+ * - One file per TokenSet, named after the set (slugified).
35
+ * - Multi-mode sets emit one file per (set, mode) pair (Tokens Studio
36
+ * convention).
37
+ * - `$metadata.json` enumerates the token set names in order.
38
+ * - `$themes.json` builds a theme entry per mode with selectedTokenSets
39
+ * and the figma-console-mcp metadata stamped onto it.
40
+ */
41
+ import { FIGMA_MCP_EXTENSION_KEY } from "../types.js";
42
+ export function formatTokensStudio(doc, opts) {
43
+ const warnings = [];
44
+ const files = [];
45
+ // Track which (setName, mode) → filename pairs exist so $metadata + $themes
46
+ // can reference them.
47
+ const setNamesByMode = [];
48
+ // 1. Per-set per-mode token files.
49
+ for (const set of doc.sets) {
50
+ for (const mode of set.modes) {
51
+ const fileSetName = set.modes.length > 1 ? `${slugify(set.name)}/${slugify(mode)}` : slugify(set.name);
52
+ const filename = `${fileSetName}.json`;
53
+ setNamesByMode.push({ setName: fileSetName, mode, filename });
54
+ files.push({
55
+ path: filename,
56
+ content: renderSetFile(set, mode, warnings),
57
+ });
58
+ }
59
+ }
60
+ // 2. $metadata.json — ordered list of token sets.
61
+ const tokenSetOrder = setNamesByMode.map((e) => e.setName);
62
+ files.push({
63
+ path: "$metadata.json",
64
+ content: JSON.stringify({ tokenSetOrder }, null, 2) + "\n",
65
+ });
66
+ // 3. $themes.json — one theme entry per mode, with figma-console-mcp
67
+ // metadata so the Tokens Studio plugin's Figma sync features know
68
+ // which Figma collection/mode each theme maps to.
69
+ const themes = buildThemes(doc, setNamesByMode);
70
+ files.push({
71
+ path: "$themes.json",
72
+ content: JSON.stringify(themes, null, 2) + "\n",
73
+ });
74
+ return { files, warnings };
75
+ }
76
+ function slugify(s) {
77
+ return s
78
+ .trim()
79
+ .toLowerCase()
80
+ .replace(/[^a-z0-9]+/g, "-")
81
+ .replace(/^-+|-+$/g, "");
82
+ }
83
+ /**
84
+ * Render a single set's tokens for a specific mode in Tokens Studio's
85
+ * bare-key JSON format.
86
+ */
87
+ function renderSetFile(set, mode, warnings) {
88
+ const tree = {};
89
+ for (const token of set.tokens) {
90
+ const value = token.values[mode];
91
+ if (!value)
92
+ continue;
93
+ const tsValue = tsValueFor(value, token, warnings);
94
+ if (tsValue === undefined)
95
+ continue;
96
+ // Walk path creating nested groups.
97
+ let cursor = tree;
98
+ for (let i = 0; i < token.path.length - 1; i++) {
99
+ const segment = token.path[i];
100
+ if (!cursor[segment] || typeof cursor[segment] !== "object") {
101
+ cursor[segment] = {};
102
+ }
103
+ cursor = cursor[segment];
104
+ }
105
+ const leafKey = token.path[token.path.length - 1];
106
+ const leaf = {
107
+ value: tsValue,
108
+ type: tsTypeFor(token),
109
+ };
110
+ if (token.description)
111
+ leaf.description = token.description;
112
+ cursor[leafKey] = leaf;
113
+ }
114
+ return JSON.stringify(sortKeys(tree), null, 2) + "\n";
115
+ }
116
+ /**
117
+ * Build the $themes.json structure. One theme entry per (set, mode) tuple.
118
+ * For multi-mode sets, the theme name combines set + mode.
119
+ */
120
+ function buildThemes(doc, setNamesByMode) {
121
+ const themes = [];
122
+ // Group by mode so each mode gets one theme entry pulling in the
123
+ // appropriate set files.
124
+ const allModes = new Set();
125
+ for (const e of setNamesByMode)
126
+ allModes.add(e.mode);
127
+ for (const mode of allModes) {
128
+ const selectedTokenSets = {};
129
+ for (const e of setNamesByMode) {
130
+ if (e.mode === mode) {
131
+ selectedTokenSets[e.setName] = "enabled";
132
+ }
133
+ else {
134
+ // Other-mode sets are disabled in this theme (Tokens Studio
135
+ // convention: only one mode of each set is "enabled" per theme).
136
+ selectedTokenSets[e.setName] = "source";
137
+ }
138
+ }
139
+ // Find the Figma collection + mode IDs for this mode (preserved
140
+ // from the export converter's $extensions metadata).
141
+ const figmaCollectionId = findCollectionIdForMode(doc, mode);
142
+ const figmaModeId = findModeId(doc, mode);
143
+ const theme = {
144
+ id: `${slugify(mode)}-${Math.random().toString(36).slice(2, 10)}`,
145
+ name: mode,
146
+ selectedTokenSets,
147
+ };
148
+ // Stash Figma metadata for round-trip with Tokens Studio plugin.
149
+ if (figmaCollectionId)
150
+ theme.$figmaCollectionId = figmaCollectionId;
151
+ if (figmaModeId)
152
+ theme.$figmaModeId = figmaModeId;
153
+ // Also stash our own extension namespace for tools that read it.
154
+ if (figmaCollectionId || figmaModeId) {
155
+ theme.$extensions = {
156
+ [FIGMA_MCP_EXTENSION_KEY]: {
157
+ ...(figmaCollectionId ? { figmaCollectionId } : {}),
158
+ ...(figmaModeId ? { figmaModeId } : {}),
159
+ },
160
+ };
161
+ }
162
+ themes.push(theme);
163
+ }
164
+ return themes;
165
+ }
166
+ function findCollectionIdForMode(doc, _mode) {
167
+ // The mode-to-collection mapping requires knowing which set the mode
168
+ // belongs to. For simplicity, return the first collection ID we find;
169
+ // multi-collection support requires per-set theme entries which the
170
+ // Tokens Studio plugin does support.
171
+ for (const set of doc.sets) {
172
+ if (set.meta?.figmaCollectionId)
173
+ return set.meta.figmaCollectionId;
174
+ }
175
+ return undefined;
176
+ }
177
+ function findModeId(_doc, _mode) {
178
+ // We don't carry Figma's modeId through the internal model in this
179
+ // version — it's computed lazily during apply via the
180
+ // (collectionId, modeName) lookup map. Future enhancement: stash the
181
+ // modeId in TokenSet.meta during export so it round-trips through
182
+ // Tokens Studio.
183
+ return undefined;
184
+ }
185
+ /**
186
+ * Map DTCG types to Tokens Studio's type names. Tokens Studio uses SD-style
187
+ * type names with some additions (e.g. "sizing", "borderRadius",
188
+ * "boxShadow", "typography", "opacity", "fontFamilies", "fontWeights").
189
+ */
190
+ function tsTypeFor(token) {
191
+ if (token.type === "color")
192
+ return "color";
193
+ if (token.type === "fontFamily")
194
+ return "fontFamilies";
195
+ if (token.type === "fontWeight")
196
+ return "fontWeights";
197
+ if (token.type === "typography")
198
+ return "typography";
199
+ if (token.type === "shadow")
200
+ return "boxShadow";
201
+ if (token.type === "duration")
202
+ return "time";
203
+ if (token.type === "dimension") {
204
+ const lower = token.path[0]?.toLowerCase() ?? "";
205
+ if (lower.includes("border") || lower.includes("radius"))
206
+ return "borderRadius";
207
+ if (lower.startsWith("space") || lower.startsWith("spacing"))
208
+ return "spacing";
209
+ if (lower.includes("size") || lower.includes("width") || lower.includes("height"))
210
+ return "sizing";
211
+ return "sizing";
212
+ }
213
+ return token.type;
214
+ }
215
+ function tsValueFor(value, token, warnings) {
216
+ if (value.reference) {
217
+ const bare = value.reference.replace(/^\{|\}$/g, "");
218
+ if (bare.startsWith("__library:") || bare === "unknown") {
219
+ warnings.push(`Skipped ${token.path.join(".")} in Tokens Studio — cross-library alias unresolved.`);
220
+ return undefined;
221
+ }
222
+ return `{${bare}}`;
223
+ }
224
+ if (value.literal === undefined || value.literal === null)
225
+ return undefined;
226
+ if (typeof value.literal === "number" && token.type === "dimension") {
227
+ return `${value.literal}px`;
228
+ }
229
+ return value.literal;
230
+ }
231
+ function sortKeys(node) {
232
+ if (node === null || typeof node !== "object" || Array.isArray(node)) {
233
+ return node;
234
+ }
235
+ const obj = node;
236
+ const sorted = {};
237
+ // $-prefixed keys first (matches Tokens Studio convention), then alphabetical.
238
+ const keys = Object.keys(obj).sort((a, b) => {
239
+ const aDollar = a.startsWith("$");
240
+ const bDollar = b.startsWith("$");
241
+ if (aDollar && !bDollar)
242
+ return -1;
243
+ if (!aDollar && bDollar)
244
+ return 1;
245
+ return a.localeCompare(b);
246
+ });
247
+ for (const k of keys)
248
+ sorted[k] = sortKeys(obj[k]);
249
+ return sorted;
4
250
  }
@@ -1,4 +1,198 @@
1
- import { FormatterNotImplementedError } from "./stubs.js";
2
- export function formatTsModule(_doc, _opts) {
3
- throw new FormatterNotImplementedError("TypeScript module");
1
+ /**
2
+ * TypeScript module formatter.
3
+ *
4
+ * Output shape:
5
+ *
6
+ * // tokens.ts
7
+ * export const tokens = {
8
+ * color: {
9
+ * primary: "#4085F2",
10
+ * brand: { primary: "#FF00AA" }
11
+ * },
12
+ * spacing: {
13
+ * md: "16px"
14
+ * }
15
+ * } as const;
16
+ *
17
+ * export type Tokens = typeof tokens;
18
+ *
19
+ * Multi-mode tokens emit as nested objects keyed by mode name:
20
+ *
21
+ * export const tokens = {
22
+ * mode: {
23
+ * primary: { Light: "#FFFFFF", Dark: "#000000" }
24
+ * }
25
+ * } as const;
26
+ *
27
+ * Aliases get resolved at write time to the literal value (TypeScript
28
+ * can't natively express references). Cross-library aliases get emitted
29
+ * as `null` with a "TODO: cross-library alias unresolved" comment so
30
+ * consumers see the gap.
31
+ */
32
+ import { buildTokenIndex, resolveAliasChain } from "../alias-resolver.js";
33
+ export function formatTsModule(doc, opts) {
34
+ const warnings = [];
35
+ const files = [];
36
+ const splitByCollection = opts.target.splitByCollection ?? false;
37
+ // splitByMode doesn't make conceptual sense for a TypeScript module
38
+ // (the value is held in-memory at runtime, not selected by CSS cascade).
39
+ // TypeScript modules can't natively express alias references — runtime
40
+ // code reading tokens.color.X gets a string at runtime, no resolution
41
+ // logic. Build a document-wide token index so aliases resolve to their
42
+ // literal target at export time. The index spans every set even when
43
+ // splitByCollection writes single-set files, because aliases often
44
+ // cross set boundaries (semantic → primitives).
45
+ const tokenIndex = buildTokenIndex(doc);
46
+ if (splitByCollection) {
47
+ for (const set of doc.sets) {
48
+ files.push({
49
+ path: filenameFor(opts, set),
50
+ content: renderTsFile([set], opts, tokenIndex, warnings),
51
+ });
52
+ }
53
+ }
54
+ else {
55
+ files.push({
56
+ path: filenameFor(opts),
57
+ content: renderTsFile(doc.sets, opts, tokenIndex, warnings),
58
+ });
59
+ }
60
+ return { files, warnings };
61
+ }
62
+ function filenameFor(opts, set) {
63
+ if (opts.target.filename)
64
+ return opts.target.filename;
65
+ if (set)
66
+ return `${slugify(set.name)}.tokens.ts`;
67
+ return "tokens.ts";
68
+ }
69
+ function slugify(s) {
70
+ return s
71
+ .trim()
72
+ .toLowerCase()
73
+ .replace(/[^a-z0-9]+/g, "-")
74
+ .replace(/^-+|-+$/g, "");
75
+ }
76
+ /**
77
+ * Convert a path segment to a valid JS identifier or quoted-key form.
78
+ * Numeric leading chars, hyphens, etc. → quoted string key.
79
+ */
80
+ function jsKey(segment) {
81
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(segment))
82
+ return segment;
83
+ return JSON.stringify(segment);
84
+ }
85
+ function renderTsFile(sets, opts, tokenIndex, warnings) {
86
+ const prefix = opts.target.prefix ?? "";
87
+ const lines = [];
88
+ lines.push("/**");
89
+ lines.push(" * Generated by figma-console-mcp — do not edit by hand.");
90
+ lines.push(" * Regenerate with: figma_export_tokens (or your npm tokens:export script)");
91
+ lines.push(" */");
92
+ lines.push("");
93
+ const constName = prefix
94
+ ? `${prefix.replace(/-+$/, "").replace(/-(\w)/g, (_, c) => c.toUpperCase())}Tokens`
95
+ : "tokens";
96
+ // Build the nested object structure by walking each set's tokens.
97
+ const tree = {};
98
+ for (const set of sets) {
99
+ for (const token of set.tokens) {
100
+ writeTokenIntoTree(tree, token, set, tokenIndex, warnings);
101
+ }
102
+ }
103
+ lines.push(`export const ${constName} = ${renderTree(tree, 0)} as const;`);
104
+ lines.push("");
105
+ lines.push(`export type ${constName[0].toUpperCase()}${constName.slice(1)} = typeof ${constName};`);
106
+ lines.push("");
107
+ return lines.join("\n");
108
+ }
109
+ function writeTokenIntoTree(tree, token, set, tokenIndex, warnings) {
110
+ let cursor = tree;
111
+ for (let i = 0; i < token.path.length - 1; i++) {
112
+ const segment = token.path[i];
113
+ let next = cursor[segment];
114
+ if (!next || isLeaf(next)) {
115
+ next = {};
116
+ cursor[segment] = next;
117
+ }
118
+ cursor = next;
119
+ }
120
+ const leafKey = token.path[token.path.length - 1];
121
+ const isMulti = set.modes.length > 1;
122
+ const resolvedValues = {};
123
+ for (const [modeName, value] of Object.entries(token.values)) {
124
+ let effective = value;
125
+ if (value.reference) {
126
+ effective = resolveAliasChain(value, modeName, tokenIndex);
127
+ if (!effective) {
128
+ const bare = value.reference.replace(/^\{|\}$/g, "");
129
+ const reason = bare.startsWith("__library:") || bare === "unknown"
130
+ ? "cross-library alias"
131
+ : "alias target not found";
132
+ warnings.push(`Skipped ${token.path.join(".")} (mode "${modeName}") in TS module — ${reason}: ${value.reference}.`);
133
+ resolvedValues[modeName] = null;
134
+ continue;
135
+ }
136
+ }
137
+ if (!effective || effective.literal === undefined || effective.literal === null)
138
+ continue;
139
+ resolvedValues[modeName] = formatLiteral(effective.literal, token.type);
140
+ }
141
+ cursor[leafKey] = {
142
+ __leaf: true,
143
+ values: resolvedValues,
144
+ type: token.type,
145
+ isMultiMode: isMulti,
146
+ description: token.description,
147
+ };
148
+ }
149
+ function isLeaf(node) {
150
+ return node.__leaf === true;
151
+ }
152
+ function renderTree(tree, indent) {
153
+ const ind = " ".repeat(indent);
154
+ const innerInd = " ".repeat(indent + 1);
155
+ const entries = [];
156
+ for (const [key, value] of Object.entries(tree)) {
157
+ const k = jsKey(key);
158
+ if (isLeaf(value)) {
159
+ const leaf = value;
160
+ if (leaf.isMultiMode) {
161
+ // Emit as { Mode: value } object
162
+ const modeEntries = [];
163
+ for (const [m, v] of Object.entries(leaf.values)) {
164
+ modeEntries.push(`${jsKey(m)}: ${renderValue(v)}`);
165
+ }
166
+ entries.push(`${innerInd}${k}: { ${modeEntries.join(", ")} }`);
167
+ }
168
+ else {
169
+ // Single-mode: emit value directly
170
+ const onlyValue = Object.values(leaf.values)[0];
171
+ entries.push(`${innerInd}${k}: ${renderValue(onlyValue)}`);
172
+ }
173
+ }
174
+ else {
175
+ entries.push(`${innerInd}${k}: ${renderTree(value, indent + 1)}`);
176
+ }
177
+ }
178
+ if (entries.length === 0)
179
+ return "{}";
180
+ return `{\n${entries.join(",\n")},\n${ind}}`;
181
+ }
182
+ function renderValue(v) {
183
+ if (v === null)
184
+ return "null /* TODO: cross-library alias unresolved */";
185
+ if (typeof v === "string")
186
+ return JSON.stringify(v);
187
+ if (typeof v === "number" || typeof v === "boolean")
188
+ return String(v);
189
+ return JSON.stringify(v);
190
+ }
191
+ function formatLiteral(value, type) {
192
+ if (typeof value === "number") {
193
+ if (type === "dimension")
194
+ return `${value}px`;
195
+ return value;
196
+ }
197
+ return value;
4
198
  }
@@ -11,5 +11,5 @@ 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, validateAliases, formatDtcgReference, parseDtcgReference, } from "./alias-resolver.js";
14
+ export { buildTokenIndex, resolveReference, resolveAliasChain, validateAliases, formatDtcgReference, parseDtcgReference, } from "./alias-resolver.js";
15
15
  export { convertFigmaVariablesToDocument, } from "./figma-converter.js";
@@ -1,13 +1,20 @@
1
1
  /**
2
- * Shared NotImplementedError for parsers/formatters that are scaffolded but
3
- * not yet implemented. DTCG round-trip is the canonical fully-implemented
4
- * path; CSS variables is also fully implemented as a formatter output.
5
- * Everything else returns a helpful error pointing the user at the canonical
6
- * format.
2
+ * Shared NotImplementedError for parser stubs.
3
+ *
4
+ * Only DTCG is implemented as a parser today — that's the canonical
5
+ * round-trip format for `figma_import_tokens`. Other parsers are
6
+ * scaffolding for potential future import-source support; the way to get
7
+ * tokens INTO Figma now is via DTCG JSON (you can generate that JSON from
8
+ * any source with your own tooling). The export side (Figma → code) is
9
+ * fully covered for all 10 formats — see formatters/stubs.ts for that
10
+ * surface.
7
11
  */
8
12
  export class TokenFormatNotImplementedError extends Error {
9
13
  constructor(formatName, kind) {
10
- super(`[figma-console-mcp] The ${formatName} ${kind} is scaffolded but not yet implemented. Fully implemented: DTCG JSON (parser + formatter) and CSS custom properties (formatter only). For now, export to 'dtcg' as the canonical format and either consume the JSON directly or convert downstream — or open an issue with your use case to prioritize this format.`);
14
+ const guidance = kind === "parser"
15
+ ? `Only the DTCG parser is implemented today. To import tokens into Figma from ${formatName}, convert to DTCG JSON first (or open an issue if you need direct ${formatName} import support).`
16
+ : `Implemented formatters: dtcg, css-vars, tailwind-v4, tailwind-v3, scss, ts-module, json-flat, json-nested, style-dictionary-v3, tokens-studio. If you need ${formatName}, open an issue with your use case.`;
17
+ super(`[figma-console-mcp] The ${formatName} ${kind} is not implemented. ${guidance}`);
11
18
  this.name = "TokenFormatNotImplementedError";
12
19
  }
13
20
  }
@@ -30,10 +30,20 @@ const logger = createChildLogger({ component: "tokens-tools" });
30
30
  * on every exported token document. Kept in sync with package.json by
31
31
  * scripts/release.sh — see step 3 of the release flow.
32
32
  */
33
- const MCP_VERSION = "1.27.1";
33
+ const MCP_VERSION = "1.29.0";
34
34
  const EXPORT_TOOL_DESCRIPTION = `Export Figma variables to design token files in your codebase. Bidirectional with figma_import_tokens — together they replace Style Dictionary and Tokens Studio's export pipeline for the popular styling methods.
35
35
 
36
- CANONICAL OUTPUT IS DTCG JSON (https://tr.designtokens.org/format/). CSS custom properties is also fully implemented and produces \`:root\` + \`.dark\` + \`[data-theme=...]\` mode selectors with proper string quoting and composite-token expansion. Remaining formats (Tokens Studio, Tailwind v4 @theme, Tailwind v3 config, SCSS, Less, TypeScript module, JSON flat/nested, Style Dictionary v3) are scaffolded but throw TokenFormatNotImplementedError — convert via DTCG for now or open an issue with your specific styling stack.
36
+ FULLY-IMPLEMENTED OUTPUT FORMATS:
37
+ • dtcg — W3C DTCG JSON. Canonical pivot format. Round-trip safe via \`$extensions["figma-console-mcp"]\`.
38
+ • css-vars — CSS custom properties with mode-aware selectors (\`:root\`, \`.dark\`, \`[data-theme=...]\`).
39
+ • tailwind-v4 — Tailwind v4 \`@theme inline\` block. Token-to-namespace mapping (color/*, spacing/*, radius/*, etc.) generates Tailwind utility classes.
40
+ • tailwind-v3 — \`tailwind.config.js\` theme.extend object grouped under Tailwind's theme keys (colors, spacing, fontFamily, etc.).
41
+ • scss — \`$var: value;\` declarations. Multi-mode emits a primary variable + a mode-keyed SCSS map for runtime access.
42
+ • ts-module — \`export const tokens = { ... } as const\` with derived \`Tokens\` type. Multi-mode tokens emit as \`{ Light: ..., Dark: ... }\` objects.
43
+ • json-flat — flat key-value JSON (\`{"ds-color-primary": "#4085F2"}\`) for custom build scripts.
44
+ • json-nested — nested object JSON mirroring the token path tree.
45
+ • style-dictionary-v3 — SD v3 source format with bare \`value\`/\`type\` keys (back-compat for existing SD users).
46
+ • tokens-studio — Tokens Studio multi-file layout (\`$themes.json\` + \`$metadata.json\` + per-set files). Preserves Figma collection/mode bindings for round-trip with the TS plugin.
37
47
 
38
48
  ZERO-ARG USAGE: With a tokens.config.json at your project root, just call the tool with no args — it picks up source dir, output formats, modes, prefix, etc. from config. See the response's \`suggestedScaffold\` payload when no config is detected — present it to the user, write the scaffold via your file tools, then call again.
39
49
 
@@ -23,6 +23,7 @@ import { registerVersionTools } from "./core/version-tools.js";
23
23
  import { registerAnnotationTools } from "./core/annotation-tools.js";
24
24
  import { registerDeepComponentTools } from "./core/deep-component-tools.js";
25
25
  import { registerDesignSystemTools } from "./core/design-system-tools.js";
26
+ import { registerLibraryTools, registerLibraryVariableTools } from "./core/library-tools.js";
26
27
  import { registerAccessibilityTools } from "./core/accessibility-tools.js";
27
28
  import { registerDiagnoseTool } from "./core/diagnose-tool.js";
28
29
  import { wrapServerForIdentity } from "./core/identity.js";
@@ -73,7 +74,7 @@ export class FigmaConsoleMCPv3 extends McpAgent {
73
74
  this.server = (() => {
74
75
  const s = new McpServer({
75
76
  name: "Figma Console MCP",
76
- version: "1.27.1",
77
+ version: "1.29.0",
77
78
  });
78
79
  // Identity wrap — every tool's response and thrown error gets stamped
79
80
  // with our MCP name so cross-MCP attribution is unambiguous.
@@ -809,6 +810,11 @@ export class FigmaConsoleMCPv3 extends McpAgent {
809
810
  // Register Design System Kit tool
810
811
  registerDesignSystemTools(this.server, async () => await this.getFigmaAPI(), () => this.browserManager?.getCurrentUrl() || null, undefined, // variablesCache
811
812
  { isRemoteMode: true });
813
+ // Register Library Tools (key-based component inspection across shared libraries)
814
+ registerLibraryTools(this.server, async () => await this.getFigmaAPI());
815
+ // Register Library Variable Tools (Plugin-API based — list + import variables
816
+ // from subscribed team libraries; routes through the cloud Desktop Bridge)
817
+ registerLibraryVariableTools(this.server, getCloudDesktopConnector);
812
818
  // Register code-side accessibility scanning (axe-core + JSDOM)
813
819
  // Note: May not work in Cloudflare Workers due to JSDOM dependency
814
820
  try {
@@ -1077,7 +1083,7 @@ export default {
1077
1083
  });
1078
1084
  const statelessServer = new McpServer({
1079
1085
  name: "Figma Console MCP",
1080
- version: "1.27.1",
1086
+ version: "1.29.0",
1081
1087
  });
1082
1088
  wrapServerForIdentity(statelessServer);
1083
1089
  // ================================================================
@@ -1175,6 +1181,8 @@ export default {
1175
1181
  registerVersionTools(statelessServer, async () => statelessApi, getCloudFileUrl);
1176
1182
  registerDesignSystemTools(statelessServer, async () => statelessApi, getCloudFileUrl, new Map(), // Fresh variables cache per request
1177
1183
  { isRemoteMode: true });
1184
+ registerLibraryTools(statelessServer, async () => statelessApi);
1185
+ registerLibraryVariableTools(statelessServer, getCloudDesktopConnector);
1178
1186
  await statelessServer.connect(transport);
1179
1187
  const response = await transport.handleRequest(request);
1180
1188
  if (response) {
@@ -1712,7 +1720,7 @@ export default {
1712
1720
  return new Response(JSON.stringify({
1713
1721
  status: "healthy",
1714
1722
  service: "Figma Console MCP",
1715
- version: "1.27.1",
1723
+ version: "1.29.0",
1716
1724
  endpoints: {
1717
1725
  mcp: ["/sse", "/mcp"],
1718
1726
  oauth_mcp_spec: ["/.well-known/oauth-authorization-server", "/authorize", "/token", "/oauth/register"],
@@ -1751,13 +1759,13 @@ export default {
1751
1759
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1752
1760
  <title>Figma Console MCP - The Most Comprehensive MCP Server for Figma</title>
1753
1761
  <link rel="icon" type="image/svg+xml" href="https://docs.figma-console-mcp.southleft.com/favicon.svg">
1754
- <meta name="description" content="Turn your Figma design system into a living API. 103+ tools give AI assistants deep access to design tokens, component specs, variables, and programmatic design creation.">
1762
+ <meta name="description" content="Turn your Figma design system into a living API. 106+ tools give AI assistants deep access to design tokens, component specs, variables, and programmatic design creation.">
1755
1763
 
1756
1764
  <!-- Open Graph -->
1757
1765
  <meta property="og:type" content="website">
1758
1766
  <meta property="og:url" content="https://figma-console-mcp.southleft.com">
1759
1767
  <meta property="og:title" content="Figma Console MCP - Turn Your Design System Into a Living API">
1760
- <meta property="og:description" content="The most comprehensive MCP server for Figma. 103+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
1768
+ <meta property="og:description" content="The most comprehensive MCP server for Figma. 106+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
1761
1769
  <meta property="og:image" content="https://docs.figma-console-mcp.southleft.com/images/og-image.jpg">
1762
1770
  <meta property="og:image:width" content="1200">
1763
1771
  <meta property="og:image:height" content="630">
@@ -1765,7 +1773,7 @@ export default {
1765
1773
  <!-- Twitter -->
1766
1774
  <meta name="twitter:card" content="summary_large_image">
1767
1775
  <meta name="twitter:title" content="Figma Console MCP - Turn Your Design System Into a Living API">
1768
- <meta name="twitter:description" content="The most comprehensive MCP server for Figma. 103+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
1776
+ <meta name="twitter:description" content="The most comprehensive MCP server for Figma. 106+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
1769
1777
  <meta name="twitter:image" content="https://docs.figma-console-mcp.southleft.com/images/og-image.jpg">
1770
1778
 
1771
1779
  <meta name="theme-color" content="#0D9488">
@@ -2652,7 +2660,7 @@ export default {
2652
2660
  <div class="grid-cell showcase-cell rule-left">
2653
2661
  <div class="showcase-label">What AI Can Access</div>
2654
2662
  <div class="showcase-stat">
2655
- <span class="number">103+</span>
2663
+ <span class="number">106+</span>
2656
2664
  <span class="label">MCP tools for Figma</span>
2657
2665
  </div>
2658
2666
  <div class="capability-list">
@@ -8,10 +8,60 @@
8
8
  */
9
9
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
10
  import type { FigmaAPI } from "./figma-api.js";
11
+ interface VisualSpec {
12
+ fills?: Array<{
13
+ type: string;
14
+ color?: string;
15
+ opacity?: number;
16
+ }>;
17
+ strokes?: Array<{
18
+ type: string;
19
+ color?: string;
20
+ weight?: number;
21
+ align?: string;
22
+ }>;
23
+ effects?: Array<{
24
+ type: string;
25
+ color?: string;
26
+ offset?: {
27
+ x: number;
28
+ y: number;
29
+ };
30
+ radius?: number;
31
+ spread?: number;
32
+ }>;
33
+ cornerRadius?: number;
34
+ rectangleCornerRadii?: number[];
35
+ opacity?: number;
36
+ layout?: {
37
+ mode?: string;
38
+ paddingTop?: number;
39
+ paddingRight?: number;
40
+ paddingBottom?: number;
41
+ paddingLeft?: number;
42
+ itemSpacing?: number;
43
+ primaryAxisAlign?: string;
44
+ counterAxisAlign?: string;
45
+ };
46
+ typography?: {
47
+ fontFamily?: string;
48
+ fontSize?: number;
49
+ fontWeight?: number;
50
+ lineHeight?: any;
51
+ letterSpacing?: any;
52
+ textAlignHorizontal?: string;
53
+ };
54
+ }
55
+ /**
56
+ * Extract a compact visual specification from a Figma node.
57
+ * Captures the essential CSS-equivalent properties an AI needs to reproduce the component.
58
+ */
59
+ export declare function extractVisualSpec(node: any): VisualSpec | undefined;
11
60
  export declare function registerDesignSystemTools(server: McpServer, getFigmaAPI: () => Promise<FigmaAPI>, getCurrentUrl: () => string | null, variablesCache?: Map<string, {
12
61
  data: any;
13
62
  timestamp: number;
14
63
  }>, options?: {
15
64
  isRemoteMode?: boolean;
16
65
  }): void;
66
+ export {};
17
67
  //# sourceMappingURL=design-system-tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"design-system-tools.d.ts","sourceRoot":"","sources":["../../src/core/design-system-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA0hB/C,wBAAgB,yBAAyB,CACxC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,EACpC,aAAa,EAAE,MAAM,MAAM,GAAG,IAAI,EAClC,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,EAC9D,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAClC,IAAI,CAogBN"}
1
+ {"version":3,"file":"design-system-tools.d.ts","sourceRoot":"","sources":["../../src/core/design-system-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAwB/C,UAAU,UAAU;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClE,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnF,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF,UAAU,CAAC,EAAE;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,GAAG,CAAC;QACjB,aAAa,CAAC,EAAE,GAAG,CAAC;QACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;KAC7B,CAAC;CACF;AAuFD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,GAAG,UAAU,GAAG,SAAS,CA6FnE;AAiTD,wBAAgB,yBAAyB,CACxC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,EACpC,aAAa,EAAE,MAAM,MAAM,GAAG,IAAI,EAClC,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,EAC9D,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAClC,IAAI,CAogBN"}