@mp3wizard/figma-console-mcp 1.14.0

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 (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +816 -0
  3. package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts +14 -0
  4. package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts.map +1 -0
  5. package/dist/apps/design-system-dashboard/scoring/accessibility.js +278 -0
  6. package/dist/apps/design-system-dashboard/scoring/accessibility.js.map +1 -0
  7. package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts +29 -0
  8. package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts.map +1 -0
  9. package/dist/apps/design-system-dashboard/scoring/component-metadata.js +358 -0
  10. package/dist/apps/design-system-dashboard/scoring/component-metadata.js.map +1 -0
  11. package/dist/apps/design-system-dashboard/scoring/consistency.d.ts +14 -0
  12. package/dist/apps/design-system-dashboard/scoring/consistency.d.ts.map +1 -0
  13. package/dist/apps/design-system-dashboard/scoring/consistency.js +342 -0
  14. package/dist/apps/design-system-dashboard/scoring/consistency.js.map +1 -0
  15. package/dist/apps/design-system-dashboard/scoring/coverage.d.ts +14 -0
  16. package/dist/apps/design-system-dashboard/scoring/coverage.d.ts.map +1 -0
  17. package/dist/apps/design-system-dashboard/scoring/coverage.js +231 -0
  18. package/dist/apps/design-system-dashboard/scoring/coverage.js.map +1 -0
  19. package/dist/apps/design-system-dashboard/scoring/engine.d.ts +27 -0
  20. package/dist/apps/design-system-dashboard/scoring/engine.d.ts.map +1 -0
  21. package/dist/apps/design-system-dashboard/scoring/engine.js +93 -0
  22. package/dist/apps/design-system-dashboard/scoring/engine.js.map +1 -0
  23. package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts +14 -0
  24. package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts.map +1 -0
  25. package/dist/apps/design-system-dashboard/scoring/naming-semantics.js +309 -0
  26. package/dist/apps/design-system-dashboard/scoring/naming-semantics.js.map +1 -0
  27. package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts +14 -0
  28. package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts.map +1 -0
  29. package/dist/apps/design-system-dashboard/scoring/token-architecture.js +350 -0
  30. package/dist/apps/design-system-dashboard/scoring/token-architecture.js.map +1 -0
  31. package/dist/apps/design-system-dashboard/scoring/types.d.ts +89 -0
  32. package/dist/apps/design-system-dashboard/scoring/types.d.ts.map +1 -0
  33. package/dist/apps/design-system-dashboard/scoring/types.js +41 -0
  34. package/dist/apps/design-system-dashboard/scoring/types.js.map +1 -0
  35. package/dist/apps/design-system-dashboard/server.d.ts +24 -0
  36. package/dist/apps/design-system-dashboard/server.d.ts.map +1 -0
  37. package/dist/apps/design-system-dashboard/server.js +160 -0
  38. package/dist/apps/design-system-dashboard/server.js.map +1 -0
  39. package/dist/apps/token-browser/server.d.ts +26 -0
  40. package/dist/apps/token-browser/server.d.ts.map +1 -0
  41. package/dist/apps/token-browser/server.js +137 -0
  42. package/dist/apps/token-browser/server.js.map +1 -0
  43. package/dist/browser/base.d.ts +58 -0
  44. package/dist/browser/base.d.ts.map +1 -0
  45. package/dist/browser/base.js +6 -0
  46. package/dist/browser/base.js.map +1 -0
  47. package/dist/browser/local.d.ts +87 -0
  48. package/dist/browser/local.d.ts.map +1 -0
  49. package/dist/browser/local.js +318 -0
  50. package/dist/browser/local.js.map +1 -0
  51. package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +277 -0
  52. package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +357 -0
  53. package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +341 -0
  54. package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +230 -0
  55. package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +92 -0
  56. package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +308 -0
  57. package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +349 -0
  58. package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +40 -0
  59. package/dist/cloudflare/apps/design-system-dashboard/server.js +159 -0
  60. package/dist/cloudflare/apps/token-browser/server.js +136 -0
  61. package/dist/cloudflare/browser/base.js +5 -0
  62. package/dist/cloudflare/browser/cloudflare.js +156 -0
  63. package/dist/cloudflare/browser-manager.js +157 -0
  64. package/dist/cloudflare/core/cloud-websocket-connector.js +267 -0
  65. package/dist/cloudflare/core/cloud-websocket-relay.js +199 -0
  66. package/dist/cloudflare/core/comment-tools.js +292 -0
  67. package/dist/cloudflare/core/config.js +161 -0
  68. package/dist/cloudflare/core/console-monitor.js +427 -0
  69. package/dist/cloudflare/core/design-code-tools.js +2504 -0
  70. package/dist/cloudflare/core/design-system-manifest.js +260 -0
  71. package/dist/cloudflare/core/design-system-tools.js +863 -0
  72. package/dist/cloudflare/core/enrichment/enrichment-service.js +272 -0
  73. package/dist/cloudflare/core/enrichment/index.js +7 -0
  74. package/dist/cloudflare/core/enrichment/relationship-mapper.js +351 -0
  75. package/dist/cloudflare/core/enrichment/style-resolver.js +326 -0
  76. package/dist/cloudflare/core/figma-api.js +409 -0
  77. package/dist/cloudflare/core/figma-connector.js +7 -0
  78. package/dist/cloudflare/core/figma-desktop-connector.js +1184 -0
  79. package/dist/cloudflare/core/figma-reconstruction-spec.js +402 -0
  80. package/dist/cloudflare/core/figma-style-extractor.js +311 -0
  81. package/dist/cloudflare/core/figma-tools.js +2947 -0
  82. package/dist/cloudflare/core/logger.js +53 -0
  83. package/dist/cloudflare/core/port-discovery.js +282 -0
  84. package/dist/cloudflare/core/snippet-injector.js +96 -0
  85. package/dist/cloudflare/core/types/design-code.js +4 -0
  86. package/dist/cloudflare/core/types/enriched.js +5 -0
  87. package/dist/cloudflare/core/types/index.js +4 -0
  88. package/dist/cloudflare/core/websocket-connector.js +256 -0
  89. package/dist/cloudflare/core/websocket-server.js +646 -0
  90. package/dist/cloudflare/core/write-tools.js +2091 -0
  91. package/dist/cloudflare/index.js +2899 -0
  92. package/dist/cloudflare/test-browser.js +88 -0
  93. package/dist/core/comment-tools.d.ts +11 -0
  94. package/dist/core/comment-tools.d.ts.map +1 -0
  95. package/dist/core/comment-tools.js +293 -0
  96. package/dist/core/comment-tools.js.map +1 -0
  97. package/dist/core/config.d.ts +17 -0
  98. package/dist/core/config.d.ts.map +1 -0
  99. package/dist/core/config.js +162 -0
  100. package/dist/core/config.js.map +1 -0
  101. package/dist/core/console-monitor.d.ts +82 -0
  102. package/dist/core/console-monitor.d.ts.map +1 -0
  103. package/dist/core/console-monitor.js +428 -0
  104. package/dist/core/console-monitor.js.map +1 -0
  105. package/dist/core/design-code-tools.d.ts +127 -0
  106. package/dist/core/design-code-tools.d.ts.map +1 -0
  107. package/dist/core/design-code-tools.js +2505 -0
  108. package/dist/core/design-code-tools.js.map +1 -0
  109. package/dist/core/design-system-manifest.d.ts +272 -0
  110. package/dist/core/design-system-manifest.d.ts.map +1 -0
  111. package/dist/core/design-system-manifest.js +261 -0
  112. package/dist/core/design-system-manifest.js.map +1 -0
  113. package/dist/core/design-system-tools.d.ts +17 -0
  114. package/dist/core/design-system-tools.d.ts.map +1 -0
  115. package/dist/core/design-system-tools.js +864 -0
  116. package/dist/core/design-system-tools.js.map +1 -0
  117. package/dist/core/enrichment/enrichment-service.d.ts +52 -0
  118. package/dist/core/enrichment/enrichment-service.d.ts.map +1 -0
  119. package/dist/core/enrichment/enrichment-service.js +273 -0
  120. package/dist/core/enrichment/enrichment-service.js.map +1 -0
  121. package/dist/core/enrichment/index.d.ts +8 -0
  122. package/dist/core/enrichment/index.d.ts.map +1 -0
  123. package/dist/core/enrichment/index.js +8 -0
  124. package/dist/core/enrichment/index.js.map +1 -0
  125. package/dist/core/enrichment/relationship-mapper.d.ts +106 -0
  126. package/dist/core/enrichment/relationship-mapper.d.ts.map +1 -0
  127. package/dist/core/enrichment/relationship-mapper.js +352 -0
  128. package/dist/core/enrichment/relationship-mapper.js.map +1 -0
  129. package/dist/core/enrichment/style-resolver.d.ts +80 -0
  130. package/dist/core/enrichment/style-resolver.d.ts.map +1 -0
  131. package/dist/core/enrichment/style-resolver.js +327 -0
  132. package/dist/core/enrichment/style-resolver.js.map +1 -0
  133. package/dist/core/figma-api.d.ts +201 -0
  134. package/dist/core/figma-api.d.ts.map +1 -0
  135. package/dist/core/figma-api.js +410 -0
  136. package/dist/core/figma-api.js.map +1 -0
  137. package/dist/core/figma-connector.d.ts +48 -0
  138. package/dist/core/figma-connector.d.ts.map +1 -0
  139. package/dist/core/figma-connector.js +8 -0
  140. package/dist/core/figma-connector.js.map +1 -0
  141. package/dist/core/figma-desktop-connector.d.ts +265 -0
  142. package/dist/core/figma-desktop-connector.d.ts.map +1 -0
  143. package/dist/core/figma-desktop-connector.js +1184 -0
  144. package/dist/core/figma-desktop-connector.js.map +1 -0
  145. package/dist/core/figma-reconstruction-spec.d.ts +166 -0
  146. package/dist/core/figma-reconstruction-spec.d.ts.map +1 -0
  147. package/dist/core/figma-reconstruction-spec.js +403 -0
  148. package/dist/core/figma-reconstruction-spec.js.map +1 -0
  149. package/dist/core/figma-style-extractor.d.ts +76 -0
  150. package/dist/core/figma-style-extractor.d.ts.map +1 -0
  151. package/dist/core/figma-style-extractor.js +312 -0
  152. package/dist/core/figma-style-extractor.js.map +1 -0
  153. package/dist/core/figma-tools.d.ts +23 -0
  154. package/dist/core/figma-tools.d.ts.map +1 -0
  155. package/dist/core/figma-tools.js +2948 -0
  156. package/dist/core/figma-tools.js.map +1 -0
  157. package/dist/core/logger.d.ts +22 -0
  158. package/dist/core/logger.d.ts.map +1 -0
  159. package/dist/core/logger.js +54 -0
  160. package/dist/core/logger.js.map +1 -0
  161. package/dist/core/port-discovery.d.ts +110 -0
  162. package/dist/core/port-discovery.d.ts.map +1 -0
  163. package/dist/core/port-discovery.js +283 -0
  164. package/dist/core/port-discovery.js.map +1 -0
  165. package/dist/core/snippet-injector.d.ts +24 -0
  166. package/dist/core/snippet-injector.d.ts.map +1 -0
  167. package/dist/core/snippet-injector.js +97 -0
  168. package/dist/core/snippet-injector.js.map +1 -0
  169. package/dist/core/types/design-code.d.ts +262 -0
  170. package/dist/core/types/design-code.d.ts.map +1 -0
  171. package/dist/core/types/design-code.js +5 -0
  172. package/dist/core/types/design-code.js.map +1 -0
  173. package/dist/core/types/enriched.d.ts +213 -0
  174. package/dist/core/types/enriched.d.ts.map +1 -0
  175. package/dist/core/types/enriched.js +6 -0
  176. package/dist/core/types/enriched.js.map +1 -0
  177. package/dist/core/types/index.d.ts +112 -0
  178. package/dist/core/types/index.d.ts.map +1 -0
  179. package/dist/core/types/index.js +5 -0
  180. package/dist/core/types/index.js.map +1 -0
  181. package/dist/core/websocket-connector.d.ts +55 -0
  182. package/dist/core/websocket-connector.d.ts.map +1 -0
  183. package/dist/core/websocket-connector.js +257 -0
  184. package/dist/core/websocket-connector.js.map +1 -0
  185. package/dist/core/websocket-server.d.ts +191 -0
  186. package/dist/core/websocket-server.d.ts.map +1 -0
  187. package/dist/core/websocket-server.js +647 -0
  188. package/dist/core/websocket-server.js.map +1 -0
  189. package/dist/core/write-tools.d.ts +7 -0
  190. package/dist/core/write-tools.d.ts.map +1 -0
  191. package/dist/core/write-tools.js +2092 -0
  192. package/dist/core/write-tools.js.map +1 -0
  193. package/dist/local.d.ts +84 -0
  194. package/dist/local.d.ts.map +1 -0
  195. package/dist/local.js +5039 -0
  196. package/dist/local.js.map +1 -0
  197. package/figma-desktop-bridge/README.md +313 -0
  198. package/figma-desktop-bridge/code.js +2818 -0
  199. package/figma-desktop-bridge/manifest.json +67 -0
  200. package/figma-desktop-bridge/ui.html +1236 -0
  201. package/package.json +87 -0
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Token Architecture Scorer (weight: 0.20)
3
+ *
4
+ * Evaluates the depth and organization of the token system.
5
+ * Checks collection organization, mode coverage, alias usage,
6
+ * token tier depth, type distribution, and description coverage.
7
+ */
8
+ import { buildCollectionNameMap, clamp, getSeverity } from "./types.js";
9
+ /** Maximum examples to include in a finding. */
10
+ const MAX_EXAMPLES = 5;
11
+ /**
12
+ * Check if a value entry is a variable alias reference.
13
+ */
14
+ function isAlias(value) {
15
+ return (typeof value === "object" &&
16
+ value !== null &&
17
+ value.type === "VARIABLE_ALIAS");
18
+ }
19
+ /** Check if variable data was unavailable (vs. genuinely empty). */
20
+ function variableDataUnavailable(data) {
21
+ return (data.dataAvailability !== undefined && !data.dataAvailability.variables);
22
+ }
23
+ /** Message explaining why variable data is missing. */
24
+ function variableUnavailableMessage(data) {
25
+ const reason = data.dataAvailability?.variableError ||
26
+ "Figma Enterprise plan or Desktop Bridge plugin required";
27
+ return `Variable data unavailable: ${reason}. Score reflects missing data, not actual design system quality.`;
28
+ }
29
+ /**
30
+ * Score collection organization.
31
+ * Variables should be organized into collections.
32
+ */
33
+ function scoreCollectionOrganization(data) {
34
+ const count = data.collections.length;
35
+ if (count === 0 && variableDataUnavailable(data)) {
36
+ return {
37
+ id: "token-collection-org",
38
+ label: "Collection organization",
39
+ score: 0,
40
+ severity: "info",
41
+ tooltip: "Variables should be grouped into collections by concern (colors, spacing, typography). More collections = better organization.",
42
+ details: variableUnavailableMessage(data),
43
+ };
44
+ }
45
+ let score;
46
+ if (count >= 3) {
47
+ score = 100;
48
+ }
49
+ else if (count === 2) {
50
+ score = 80;
51
+ }
52
+ else if (count === 1) {
53
+ score = 50;
54
+ }
55
+ else {
56
+ score = 0;
57
+ }
58
+ return {
59
+ id: "token-collection-org",
60
+ label: "Collection organization",
61
+ score: clamp(score),
62
+ severity: getSeverity(score),
63
+ tooltip: "Variables should be grouped into collections by concern (colors, spacing, typography). More collections = better organization.",
64
+ details: count === 0
65
+ ? "No variable collections found. Organize variables into collections."
66
+ : `${count} collection${count === 1 ? "" : "s"} found.`,
67
+ };
68
+ }
69
+ /**
70
+ * Score mode coverage.
71
+ * Collections should have multiple modes (e.g., light/dark).
72
+ */
73
+ function scoreModeCoverage(data) {
74
+ if (data.collections.length === 0) {
75
+ return {
76
+ id: "token-mode-coverage",
77
+ label: "Mode coverage",
78
+ score: 0,
79
+ severity: variableDataUnavailable(data) ? "info" : "fail",
80
+ tooltip: "Collections should support multiple modes (e.g. Light/Dark). This enables theming without duplicating tokens.",
81
+ details: variableDataUnavailable(data)
82
+ ? variableUnavailableMessage(data)
83
+ : "No collections found to evaluate mode coverage.",
84
+ };
85
+ }
86
+ const maxModes = Math.max(...data.collections.map((c) => c.modes?.length ?? 0));
87
+ let score;
88
+ if (maxModes >= 2) {
89
+ score = 100;
90
+ }
91
+ else if (maxModes === 1) {
92
+ score = 50;
93
+ }
94
+ else {
95
+ score = 0;
96
+ }
97
+ return {
98
+ id: "token-mode-coverage",
99
+ label: "Mode coverage",
100
+ score: clamp(score),
101
+ severity: getSeverity(score),
102
+ tooltip: "Collections should support multiple modes (e.g. Light/Dark). This enables theming without duplicating tokens.",
103
+ details: maxModes >= 2
104
+ ? `Collections support up to ${maxModes} modes (e.g., light/dark).`
105
+ : maxModes === 1
106
+ ? "Collections have only 1 mode. Consider adding light/dark modes."
107
+ : "No modes detected in collections.",
108
+ };
109
+ }
110
+ /**
111
+ * Score alias usage.
112
+ * Higher alias percentage indicates better token layering.
113
+ */
114
+ function scoreAliasUsage(data) {
115
+ if (data.variables.length === 0) {
116
+ return {
117
+ id: "token-alias-usage",
118
+ label: "Alias usage",
119
+ score: 0,
120
+ severity: variableDataUnavailable(data) ? "info" : "fail",
121
+ tooltip: "Semantic tokens should reference primitive tokens via aliases rather than hard-coding values. Aliases enable single-source-of-truth updates.",
122
+ details: variableDataUnavailable(data)
123
+ ? variableUnavailableMessage(data)
124
+ : "No variables to evaluate alias usage.",
125
+ };
126
+ }
127
+ const collectionNames = buildCollectionNameMap(data.collections);
128
+ let aliasCount = 0;
129
+ let totalValues = 0;
130
+ const rawValueVars = [];
131
+ for (const variable of data.variables) {
132
+ if (!variable.valuesByMode)
133
+ continue;
134
+ let hasAnyAlias = false;
135
+ for (const modeValues of Object.values(variable.valuesByMode)) {
136
+ totalValues++;
137
+ if (isAlias(modeValues)) {
138
+ aliasCount++;
139
+ hasAnyAlias = true;
140
+ }
141
+ }
142
+ if (!hasAnyAlias) {
143
+ rawValueVars.push(variable);
144
+ }
145
+ }
146
+ if (totalValues === 0) {
147
+ return {
148
+ id: "token-alias-usage",
149
+ label: "Alias usage",
150
+ score: 0,
151
+ severity: "fail",
152
+ tooltip: "Semantic tokens should reference primitive tokens via aliases rather than hard-coding values. Aliases enable single-source-of-truth updates.",
153
+ details: "No variable values found to evaluate.",
154
+ };
155
+ }
156
+ const ratio = aliasCount / totalValues;
157
+ const score = clamp(ratio * 100);
158
+ return {
159
+ id: "token-alias-usage",
160
+ label: "Alias usage",
161
+ score,
162
+ severity: getSeverity(score),
163
+ tooltip: "Semantic tokens should reference primitive tokens via aliases rather than hard-coding values. Aliases enable single-source-of-truth updates.",
164
+ details: `${aliasCount} of ${totalValues} values are aliases (${Math.round(ratio * 100)}%). Higher alias usage indicates better token layering.`,
165
+ examples: rawValueVars.length > 0 ? rawValueVars.slice(0, MAX_EXAMPLES).map((v) => v.name) : undefined,
166
+ locations: rawValueVars.length > 0
167
+ ? rawValueVars.slice(0, MAX_EXAMPLES).map((v) => ({
168
+ name: v.name,
169
+ collection: collectionNames.get(v.variableCollectionId),
170
+ type: "variable",
171
+ }))
172
+ : undefined,
173
+ };
174
+ }
175
+ /**
176
+ * Build a map of variable ID to variable for alias chain tracing.
177
+ */
178
+ function buildVariableMap(variables) {
179
+ const map = new Map();
180
+ for (const v of variables) {
181
+ if (v.id) {
182
+ map.set(v.id, v);
183
+ }
184
+ }
185
+ return map;
186
+ }
187
+ /**
188
+ * Trace the depth of an alias chain starting from a given variable.
189
+ * Returns the number of tiers (1 = raw value, 2 = one alias hop, etc.).
190
+ */
191
+ function traceAliasDepth(variable, variableMap, visited) {
192
+ if (!variable.valuesByMode)
193
+ return 1;
194
+ const values = Object.values(variable.valuesByMode);
195
+ let maxDepth = 1;
196
+ for (const value of values) {
197
+ if (isAlias(value)) {
198
+ const targetId = value.id;
199
+ if (visited.has(targetId))
200
+ continue; // Prevent circular references
201
+ const target = variableMap.get(targetId);
202
+ if (target) {
203
+ visited.add(targetId);
204
+ const depth = 1 + traceAliasDepth(target, variableMap, visited);
205
+ maxDepth = Math.max(maxDepth, depth);
206
+ }
207
+ }
208
+ }
209
+ return maxDepth;
210
+ }
211
+ /**
212
+ * Score token tier depth.
213
+ * Deeper alias chains indicate more sophisticated token architecture.
214
+ */
215
+ function scoreTokenTierDepth(data) {
216
+ if (data.variables.length === 0) {
217
+ return {
218
+ id: "token-tier-depth",
219
+ label: "Token tier depth",
220
+ score: 0,
221
+ severity: variableDataUnavailable(data) ? "info" : "fail",
222
+ tooltip: "A layered token system (primitive -> semantic -> component) enables scalable theming. 3+ tiers indicates mature architecture.",
223
+ details: variableDataUnavailable(data)
224
+ ? variableUnavailableMessage(data)
225
+ : "No variables to evaluate tier depth.",
226
+ };
227
+ }
228
+ const variableMap = buildVariableMap(data.variables);
229
+ let maxDepth = 1;
230
+ for (const variable of data.variables) {
231
+ const visited = new Set();
232
+ if (variable.id)
233
+ visited.add(variable.id);
234
+ const depth = traceAliasDepth(variable, variableMap, visited);
235
+ maxDepth = Math.max(maxDepth, depth);
236
+ }
237
+ let score;
238
+ if (maxDepth >= 3) {
239
+ score = 100;
240
+ }
241
+ else if (maxDepth === 2) {
242
+ score = 67;
243
+ }
244
+ else {
245
+ score = 33;
246
+ }
247
+ return {
248
+ id: "token-tier-depth",
249
+ label: "Token tier depth",
250
+ score: clamp(score),
251
+ severity: getSeverity(score),
252
+ tooltip: "A layered token system (primitive -> semantic -> component) enables scalable theming. 3+ tiers indicates mature architecture.",
253
+ details: `Maximum alias chain depth: ${maxDepth} tier${maxDepth === 1 ? "" : "s"}. 3+ tiers indicates a well-layered token architecture.`,
254
+ };
255
+ }
256
+ /**
257
+ * Score type distribution across variables.
258
+ * A healthy system includes COLOR, FLOAT, and STRING variables.
259
+ */
260
+ function scoreTypeDistribution(data) {
261
+ if (data.variables.length === 0) {
262
+ return {
263
+ id: "token-type-distribution",
264
+ label: "Type distribution",
265
+ score: 0,
266
+ severity: variableDataUnavailable(data) ? "info" : "fail",
267
+ tooltip: "A complete token system includes COLOR, FLOAT, and STRING variables. Missing types indicate gaps in the design language.",
268
+ details: variableDataUnavailable(data)
269
+ ? variableUnavailableMessage(data)
270
+ : "No variables to evaluate type distribution.",
271
+ };
272
+ }
273
+ const expectedTypes = ["COLOR", "FLOAT", "STRING"];
274
+ const presentTypes = new Set(data.variables.map((v) => v.resolvedType));
275
+ const matchCount = expectedTypes.filter((t) => presentTypes.has(t)).length;
276
+ const score = clamp((matchCount / expectedTypes.length) * 100);
277
+ return {
278
+ id: "token-type-distribution",
279
+ label: "Type distribution",
280
+ score,
281
+ severity: getSeverity(score),
282
+ tooltip: "A complete token system includes COLOR, FLOAT, and STRING variables. Missing types indicate gaps in the design language.",
283
+ details: `${matchCount} of ${expectedTypes.length} expected types (COLOR, FLOAT, STRING) present. Found: ${[...presentTypes].join(", ") || "none"}.`,
284
+ };
285
+ }
286
+ /**
287
+ * Score description coverage for variables.
288
+ * Well-documented variables should have non-empty descriptions.
289
+ */
290
+ function scoreDescriptionCoverage(data) {
291
+ if (data.variables.length === 0) {
292
+ return {
293
+ id: "token-description-coverage",
294
+ label: "Description coverage",
295
+ score: 0,
296
+ severity: variableDataUnavailable(data) ? "info" : "fail",
297
+ tooltip: "Variables should have descriptions explaining their purpose and usage context. Descriptions help consumers choose the right token.",
298
+ details: variableDataUnavailable(data)
299
+ ? variableUnavailableMessage(data)
300
+ : "No variables to evaluate description coverage.",
301
+ };
302
+ }
303
+ const collectionNames = buildCollectionNameMap(data.collections);
304
+ const withDesc = data.variables.filter((v) => v.description && v.description.trim().length > 0);
305
+ const withoutDesc = data.variables.filter((v) => !v.description || v.description.trim().length === 0);
306
+ const ratio = withDesc.length / data.variables.length;
307
+ const score = clamp(ratio * 100);
308
+ return {
309
+ id: "token-description-coverage",
310
+ label: "Description coverage",
311
+ score,
312
+ severity: getSeverity(score),
313
+ tooltip: "Variables should have descriptions explaining their purpose and usage context. Descriptions help consumers choose the right token.",
314
+ details: `${withDesc.length} of ${data.variables.length} variables have descriptions (${Math.round(ratio * 100)}%).`,
315
+ examples: withoutDesc.length > 0
316
+ ? withoutDesc.slice(0, MAX_EXAMPLES).map((v) => v.name)
317
+ : undefined,
318
+ locations: withoutDesc.length > 0
319
+ ? withoutDesc.slice(0, MAX_EXAMPLES).map((v) => ({
320
+ name: v.name,
321
+ collection: collectionNames.get(v.variableCollectionId),
322
+ type: "variable",
323
+ }))
324
+ : undefined,
325
+ };
326
+ }
327
+ /**
328
+ * Token Architecture category scorer.
329
+ * Returns the average score across all token architecture checks.
330
+ */
331
+ export function scoreTokenArchitecture(data) {
332
+ const findings = [
333
+ scoreCollectionOrganization(data),
334
+ scoreModeCoverage(data),
335
+ scoreAliasUsage(data),
336
+ scoreTokenTierDepth(data),
337
+ scoreTypeDistribution(data),
338
+ scoreDescriptionCoverage(data),
339
+ ];
340
+ const score = clamp(findings.reduce((sum, f) => sum + f.score, 0) / findings.length);
341
+ return {
342
+ id: "token-architecture",
343
+ label: "Token Architecture",
344
+ shortLabel: "Tokens",
345
+ score,
346
+ weight: 0.2,
347
+ findings,
348
+ };
349
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Design System Dashboard — Shared Types
3
+ *
4
+ * Defines the JSON contract between the scoring engine (server-side),
5
+ * the server registration (tools), and the UI (client-side rendering).
6
+ */
7
+ /** Build a map of collection ID → collection name for location context. */
8
+ export function buildCollectionNameMap(collections) {
9
+ const map = new Map();
10
+ for (const col of collections) {
11
+ if (col.id && col.name)
12
+ map.set(col.id, col.name);
13
+ }
14
+ return map;
15
+ }
16
+ // ---------------------------------------------------------------------------
17
+ // Thresholds and helpers
18
+ // ---------------------------------------------------------------------------
19
+ export const THRESHOLDS = {
20
+ GOOD: 90,
21
+ NEEDS_WORK: 50,
22
+ };
23
+ export function getStatus(score) {
24
+ if (score >= THRESHOLDS.GOOD)
25
+ return "good";
26
+ if (score >= THRESHOLDS.NEEDS_WORK)
27
+ return "needs-work";
28
+ return "poor";
29
+ }
30
+ export function getSeverity(score) {
31
+ if (score >= THRESHOLDS.GOOD)
32
+ return "pass";
33
+ if (score >= THRESHOLDS.NEEDS_WORK)
34
+ return "warning";
35
+ return "fail";
36
+ }
37
+ /** Clamp a number to 0-100. */
38
+ export function clamp(value) {
39
+ return Math.max(0, Math.min(100, Math.round(value)));
40
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Design System Dashboard MCP App - Server Registration
3
+ *
4
+ * Registers tools and resource for the Design System Dashboard MCP App.
5
+ * Uses the official @modelcontextprotocol/ext-apps helpers for proper
6
+ * MCP Apps protocol compatibility with Claude Desktop.
7
+ *
8
+ * Data flow:
9
+ * 1. LLM calls figma_audit_design_system → server fetches + scores data,
10
+ * returns SHORT summary to LLM (avoids context exhaustion)
11
+ * 2. UI opens, connects, calls ds_dashboard_refresh (app-only visibility)
12
+ * 3. ds_dashboard_refresh returns full JSON → UI renders
13
+ */
14
+ import { readFile } from "node:fs/promises";
15
+ import { dirname, resolve } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+ import { RESOURCE_MIME_TYPE, registerAppResource, registerAppTool, } from "@modelcontextprotocol/ext-apps/server";
18
+ import { z } from "zod";
19
+ import { scoreDesignSystem } from "./scoring/engine.js";
20
+ const DASHBOARD_URI = "ui://figma-console/design-system-dashboard";
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = dirname(__filename);
23
+ // Shared state
24
+ let lastFileUrl;
25
+ /**
26
+ * Register the Design System Dashboard MCP App with the server.
27
+ *
28
+ * @param server - The MCP server instance
29
+ * @param getDesignSystemData - Function to fetch raw design system data from Figma
30
+ * @param getCurrentUrl - Optional function to get the current browser URL (for lastFileUrl tracking)
31
+ */
32
+ export function registerDesignSystemDashboardApp(server, getDesignSystemData, getCurrentUrl) {
33
+ // Tool: fetches + scores data, returns SHORT summary to LLM
34
+ registerAppTool(server, "figma_audit_design_system", {
35
+ title: "Audit Design System Health",
36
+ description: "Analyze the health and AI-readiness of a Figma file's design system. Produces a scored dashboard evaluating naming conventions, token architecture, component metadata, accessibility, consistency, and coverage. Results are displayed in the dashboard UI.",
37
+ inputSchema: {
38
+ fileUrl: z
39
+ .string()
40
+ .url()
41
+ .optional()
42
+ .describe("Figma file URL. If not provided, uses the currently active file."),
43
+ },
44
+ _meta: {
45
+ ui: { resourceUri: DASHBOARD_URI },
46
+ },
47
+ }, async ({ fileUrl }) => {
48
+ try {
49
+ // Track the actual URL used (explicit or current browser URL)
50
+ // This ensures ds_dashboard_refresh uses the correct file
51
+ lastFileUrl = fileUrl || getCurrentUrl?.() || undefined;
52
+ const data = await getDesignSystemData(fileUrl);
53
+ const scored = scoreDesignSystem(data);
54
+ const categorySummaries = scored.categories
55
+ .map((c) => `${c.label}: ${c.score}/100`)
56
+ .join(", ");
57
+ const fileName = scored.fileInfo?.name || "Unknown file";
58
+ const unavailableNote = scored.dataAvailability && !scored.dataAvailability.variables
59
+ ? " Note: Variable/token data was unavailable (requires Enterprise plan or Desktop Bridge). Token Architecture scores reflect missing data, not actual quality."
60
+ : "";
61
+ return {
62
+ content: [
63
+ {
64
+ type: "text",
65
+ text: `Design System: ${fileName}. Health: ${scored.overall}/100 — ${scored.status}. ${categorySummaries}.${unavailableNote} The dashboard UI is now showing detailed results.`,
66
+ },
67
+ ],
68
+ };
69
+ }
70
+ catch (error) {
71
+ const errorMessage = error instanceof Error ? error.message : String(error);
72
+ return {
73
+ content: [
74
+ {
75
+ type: "text",
76
+ text: `Design System Dashboard error: ${errorMessage}`,
77
+ },
78
+ ],
79
+ isError: true,
80
+ };
81
+ }
82
+ });
83
+ // Tool: returns full JSON data (app-only, hidden from LLM)
84
+ registerAppTool(server, "ds_dashboard_refresh", {
85
+ title: "Dashboard Refresh",
86
+ description: "Refresh dashboard data (called from MCP App UI)",
87
+ inputSchema: {
88
+ fileUrl: z
89
+ .string()
90
+ .url()
91
+ .optional()
92
+ .describe("Figma file URL to refresh data for."),
93
+ },
94
+ _meta: {
95
+ ui: {
96
+ resourceUri: DASHBOARD_URI,
97
+ visibility: ["app"],
98
+ },
99
+ },
100
+ }, async ({ fileUrl }) => {
101
+ try {
102
+ const url = fileUrl || lastFileUrl;
103
+ const data = await getDesignSystemData(url);
104
+ const scored = scoreDesignSystem(data);
105
+ return {
106
+ content: [
107
+ {
108
+ type: "text",
109
+ text: JSON.stringify(scored),
110
+ },
111
+ ],
112
+ };
113
+ }
114
+ catch (error) {
115
+ const errorMessage = error instanceof Error ? error.message : String(error);
116
+ return {
117
+ content: [
118
+ {
119
+ type: "text",
120
+ text: JSON.stringify({
121
+ error: errorMessage,
122
+ overall: 0,
123
+ status: "poor",
124
+ categories: [],
125
+ summary: [],
126
+ meta: {
127
+ componentCount: 0,
128
+ variableCount: 0,
129
+ collectionCount: 0,
130
+ styleCount: 0,
131
+ componentSetCount: 0,
132
+ standaloneCount: 0,
133
+ variantCount: 0,
134
+ timestamp: Date.now(),
135
+ },
136
+ }),
137
+ },
138
+ ],
139
+ isError: true,
140
+ };
141
+ }
142
+ });
143
+ // Resource: serves the Vite-built HTML
144
+ registerAppResource(server, "Design System Dashboard App", DASHBOARD_URI, {
145
+ description: "Interactive dashboard for evaluating design system health and AI-readiness",
146
+ }, async () => {
147
+ const htmlPath = resolve(__dirname, "mcp-app.html");
148
+ const html = await readFile(htmlPath, "utf-8");
149
+ return {
150
+ contents: [
151
+ {
152
+ uri: DASHBOARD_URI,
153
+ mimeType: RESOURCE_MIME_TYPE,
154
+ text: html,
155
+ },
156
+ ],
157
+ };
158
+ });
159
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Token Browser MCP App - Server Registration
3
+ *
4
+ * Registers tools and resource for the Token Browser MCP App.
5
+ * Uses the official @modelcontextprotocol/ext-apps helpers for proper
6
+ * MCP Apps protocol compatibility with Claude Desktop.
7
+ *
8
+ * Data flow:
9
+ * 1. LLM calls figma_browse_tokens → server fetches + caches data,
10
+ * returns SHORT summary to LLM (avoids context exhaustion)
11
+ * 2. UI opens, connects, calls token_browser_refresh (app-only visibility)
12
+ * 3. token_browser_refresh returns full JSON → UI renders
13
+ */
14
+ import { readFile } from "node:fs/promises";
15
+ import { dirname, resolve } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+ import { RESOURCE_MIME_TYPE, registerAppResource, registerAppTool, } from "@modelcontextprotocol/ext-apps/server";
18
+ import { z } from "zod";
19
+ const TOKEN_BROWSER_URI = "ui://figma-console/token-browser";
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = dirname(__filename);
22
+ /**
23
+ * Register the Token Browser MCP App with the server.
24
+ *
25
+ * @param server - The MCP server instance
26
+ * @param getVariablesHandler - Function to fetch variables data
27
+ */
28
+ export function registerTokenBrowserApp(server, getVariablesHandler) {
29
+ // Shared state: remember last URL so refresh tool can reuse it
30
+ let lastFileUrl;
31
+ // Tool: fetches + caches data, returns SHORT summary to LLM
32
+ registerAppTool(server, "figma_browse_tokens", {
33
+ title: "Browse Design Tokens",
34
+ description: "Open an interactive browser to explore design tokens (variables) from a Figma file. Shows tokens organized by collection with search, filtering, and mode switching. Data is displayed in the UI — do not attempt to list or describe the tokens in chat.",
35
+ inputSchema: {
36
+ fileUrl: z
37
+ .string()
38
+ .url()
39
+ .optional()
40
+ .describe("Figma file URL. If not provided, uses the currently active file."),
41
+ },
42
+ _meta: {
43
+ ui: { resourceUri: TOKEN_BROWSER_URI },
44
+ },
45
+ }, async ({ fileUrl }) => {
46
+ try {
47
+ lastFileUrl = fileUrl;
48
+ const result = await getVariablesHandler(fileUrl);
49
+ const varCount = result.variables?.length || 0;
50
+ const colCount = result.collections?.length || 0;
51
+ // Count by type
52
+ const colors = result.variables?.filter((v) => v.resolvedType === "COLOR")
53
+ .length || 0;
54
+ const numbers = result.variables?.filter((v) => v.resolvedType === "FLOAT")
55
+ .length || 0;
56
+ return {
57
+ content: [
58
+ {
59
+ type: "text",
60
+ text: `Token Browser opened: ${varCount} tokens across ${colCount} collections (${colors} colors, ${numbers} numbers). The interactive UI is now displaying the results — users can search, filter by type, and browse by collection.`,
61
+ },
62
+ ],
63
+ };
64
+ }
65
+ catch (error) {
66
+ const errorMessage = error instanceof Error ? error.message : String(error);
67
+ return {
68
+ content: [
69
+ {
70
+ type: "text",
71
+ text: `Token Browser error: ${errorMessage}`,
72
+ },
73
+ ],
74
+ isError: true,
75
+ };
76
+ }
77
+ });
78
+ // Tool: returns full JSON data (app-only, hidden from LLM)
79
+ registerAppTool(server, "token_browser_refresh", {
80
+ title: "Token Browser Refresh",
81
+ description: "Refresh token data (called from MCP App UI)",
82
+ inputSchema: {
83
+ fileUrl: z
84
+ .string()
85
+ .url()
86
+ .optional()
87
+ .describe("Figma file URL to refresh data for."),
88
+ },
89
+ _meta: {
90
+ ui: {
91
+ resourceUri: TOKEN_BROWSER_URI,
92
+ visibility: ["app"],
93
+ },
94
+ },
95
+ }, async ({ fileUrl }) => {
96
+ try {
97
+ const url = fileUrl || lastFileUrl;
98
+ const result = await getVariablesHandler(url);
99
+ return {
100
+ content: [{ type: "text", text: JSON.stringify(result) }],
101
+ };
102
+ }
103
+ catch (error) {
104
+ const errorMessage = error instanceof Error ? error.message : String(error);
105
+ return {
106
+ content: [
107
+ {
108
+ type: "text",
109
+ text: JSON.stringify({
110
+ error: errorMessage,
111
+ variables: [],
112
+ collections: [],
113
+ }),
114
+ },
115
+ ],
116
+ isError: true,
117
+ };
118
+ }
119
+ });
120
+ // Resource: serves the Vite-built HTML
121
+ registerAppResource(server, "Token Browser App", TOKEN_BROWSER_URI, {
122
+ description: "Interactive browser for exploring Figma design tokens and variables",
123
+ }, async () => {
124
+ const htmlPath = resolve(__dirname, "mcp-app.html");
125
+ const html = await readFile(htmlPath, "utf-8");
126
+ return {
127
+ contents: [
128
+ {
129
+ uri: TOKEN_BROWSER_URI,
130
+ mimeType: RESOURCE_MIME_TYPE,
131
+ text: html,
132
+ },
133
+ ],
134
+ };
135
+ });
136
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Browser Manager Interface
3
+ * Abstract interface for browser automation across different runtimes
4
+ */
5
+ export {};