@monoharada/wcf-mcp 0.9.1 → 0.11.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.
package/core/tokens.mjs CHANGED
@@ -9,6 +9,19 @@ const TOKEN_MISUSE_ALLOWED_TYPES = Object.freeze(new Set(['color', 'spacing']));
9
9
  const TOKEN_THEMES = Object.freeze(new Set(['light', 'dark', 'all']));
10
10
  const WCAG_LEVELS = Object.freeze(new Set(['A', 'AA', 'AAA', 'all']));
11
11
 
12
+ function getThemeConfig(designTokensData) {
13
+ const available = Array.isArray(designTokensData?.themes?.available) && designTokensData.themes.available.length > 0
14
+ ? [...new Set(designTokensData.themes.available.map((theme) => String(theme).trim().toLowerCase()).filter(Boolean))]
15
+ : ['light'];
16
+ const defaultTheme = typeof designTokensData?.themes?.default === 'string' && available.includes(String(designTokensData.themes.default).toLowerCase())
17
+ ? String(designTokensData.themes.default).toLowerCase()
18
+ : available[0];
19
+ return {
20
+ defaultTheme,
21
+ available,
22
+ };
23
+ }
24
+
12
25
  export function normalizeTokenValue(value) {
13
26
  if (typeof value === 'string') return value.trim().toLowerCase().replace(/\s+/g, ' ');
14
27
  if (typeof value === 'number') return String(value);
@@ -54,7 +67,7 @@ export function normalizeTokenIdentifier(value) {
54
67
  return `--${raw.replace(/^[-]+/, '')}`;
55
68
  }
56
69
 
57
- export function resolveTokenTheme(theme) {
70
+ export function resolveTokenTheme(theme, designTokensData) {
58
71
  const requested = String(theme ?? 'light').trim().toLowerCase() || 'light';
59
72
  if (!TOKEN_THEMES.has(requested)) {
60
73
  return {
@@ -63,29 +76,46 @@ export function resolveTokenTheme(theme) {
63
76
  message: `Unsupported theme: ${requested}. Allowed values are light, dark, all.`,
64
77
  };
65
78
  }
66
- if (requested !== 'light') {
79
+
80
+ const themeConfig = getThemeConfig(designTokensData);
81
+ if (requested === 'all') {
82
+ return {
83
+ ok: true,
84
+ requested,
85
+ resolved: themeConfig.defaultTheme,
86
+ available: themeConfig.available,
87
+ mode: 'all',
88
+ };
89
+ }
90
+
91
+ if (!themeConfig.available.includes(requested)) {
67
92
  return {
68
93
  ok: false,
69
94
  errorCode: 'INVALID_THEME',
70
- message: `Theme "${requested}" is not available yet. Use theme="light" (NG-06).`,
95
+ message: `Theme "${requested}" is not supported. Available themes in current data: ${themeConfig.available.join(', ')}.`,
71
96
  };
72
97
  }
98
+
73
99
  return {
74
100
  ok: true,
75
101
  requested,
76
- resolved: 'light',
77
- available: ['light'],
102
+ resolved: requested,
103
+ available: themeConfig.available,
104
+ mode: 'single',
78
105
  };
79
106
  }
80
107
 
81
108
  export function extractReferencedTokenNames(value) {
82
109
  if (typeof value !== 'string') return [];
83
110
  const refs = [];
84
- const re = /var\(\s*(--[^,\s)]+)\s*(?:,\s*[^)]+)?\)/g;
111
+ const re = /var\(([^)]*)\)/g;
85
112
  let match;
86
113
  while ((match = re.exec(value))) {
87
- const tokenName = normalizeTokenIdentifier(match[1]);
88
- if (tokenName) refs.push(tokenName);
114
+ const candidates = String(match[1] ?? '').match(/--[^,\s)]+/g) ?? [];
115
+ for (const candidate of candidates) {
116
+ const tokenName = normalizeTokenIdentifier(candidate);
117
+ if (tokenName) refs.push(tokenName);
118
+ }
89
119
  }
90
120
  return [...new Set(refs)];
91
121
  }
@@ -141,12 +171,10 @@ export function buildTokenRelationshipIndex(designTokensData) {
141
171
  /**
142
172
  * Extract which components reference which design tokens via var() in CEM cssProperties.
143
173
  * Returns Map<tokenName, Set<componentTagName>>.
144
- * DD-25: var(--token, fallback) fallback values are not extracted (known limitation).
145
174
  */
146
175
  export function buildComponentTokenReferencedBy(manifest) {
147
176
  const result = new Map();
148
177
  const modules = Array.isArray(manifest?.modules) ? manifest.modules : [];
149
- const varRe = /var\((--[\w-]+)/g;
150
178
  for (const mod of modules) {
151
179
  const declarations = Array.isArray(mod?.declarations) ? mod.declarations : [];
152
180
  for (const decl of declarations) {
@@ -155,9 +183,8 @@ export function buildComponentTokenReferencedBy(manifest) {
155
183
  const cssProps = Array.isArray(decl?.cssProperties) ? decl.cssProperties : [];
156
184
  for (const prop of cssProps) {
157
185
  const defaultVal = typeof prop?.default === 'string' ? prop.default : '';
158
- let m;
159
- while ((m = varRe.exec(defaultVal)) !== null) {
160
- const tokenName = normalizeTokenIdentifier(m[1]);
186
+ for (const tokenRef of extractReferencedTokenNames(defaultVal)) {
187
+ const tokenName = normalizeTokenIdentifier(tokenRef);
161
188
  if (!tokenName) continue;
162
189
  if (!result.has(tokenName)) result.set(tokenName, new Set());
163
190
  result.get(tokenName).add(tag);
@@ -235,6 +262,30 @@ function buildUsageExamples(token) {
235
262
  return [`.example { --token-value: ${cssVar}; }`];
236
263
  }
237
264
 
265
+ function resolveTokenValueForTheme(token, themeInfo) {
266
+ if (token?.themeValues && typeof token.themeValues === 'object') {
267
+ const themedValue = token.themeValues[themeInfo.resolved];
268
+ if (typeof themedValue === 'string' && themedValue.trim() !== '') {
269
+ return themedValue;
270
+ }
271
+ }
272
+ return token?.value;
273
+ }
274
+
275
+ function serializeTokenForTheme(token, themeInfo, { includeThemeValues = false } = {}) {
276
+ const serialized = {
277
+ ...toTokenSummary({
278
+ ...token,
279
+ value: resolveTokenValueForTheme(token, themeInfo),
280
+ }),
281
+ };
282
+ if (token?.group !== undefined) serialized.group = token.group;
283
+ if (includeThemeValues && token?.themeValues && typeof token.themeValues === 'object') {
284
+ serialized.themeValues = token.themeValues;
285
+ }
286
+ return serialized;
287
+ }
288
+
238
289
  function buildTokenErrorPayload(code, message, extra = {}) {
239
290
  return {
240
291
  isError: true,
@@ -253,7 +304,7 @@ export function buildDesignTokenDetailPayload(designTokensData, name, theme) {
253
304
  );
254
305
  }
255
306
 
256
- const themeInfo = resolveTokenTheme(theme);
307
+ const themeInfo = resolveTokenTheme(theme, designTokensData);
257
308
  if (!themeInfo.ok) {
258
309
  return buildTokenErrorPayload(themeInfo.errorCode, themeInfo.message);
259
310
  }
@@ -294,7 +345,7 @@ export function buildDesignTokenDetailPayload(designTokensData, name, theme) {
294
345
  isError: false,
295
346
  payload: {
296
347
  token: {
297
- ...toTokenSummary(token),
348
+ ...serializeTokenForTheme(token, themeInfo, { includeThemeValues: true }),
298
349
  group: token?.group ?? null,
299
350
  },
300
351
  references,
@@ -318,7 +369,7 @@ export function buildDesignTokensPayload(designTokensData, { type, category, que
318
369
  );
319
370
  }
320
371
 
321
- const themeInfo = resolveTokenTheme(theme);
372
+ const themeInfo = resolveTokenTheme(theme, designTokensData);
322
373
  if (!themeInfo.ok) {
323
374
  return buildTokenErrorPayload(themeInfo.errorCode, themeInfo.message);
324
375
  }
@@ -335,7 +386,9 @@ export function buildDesignTokensPayload(designTokensData, { type, category, que
335
386
  isError: false,
336
387
  payload: {
337
388
  total: tokens.length,
338
- tokens,
389
+ tokens: tokens.map((token) =>
390
+ serializeTokenForTheme(token, themeInfo, { includeThemeValues: themeInfo.requested === 'all' })
391
+ ),
339
392
  summary: designTokensData.summary,
340
393
  theme: {
341
394
  requested: themeInfo.requested,
package/core.mjs CHANGED
@@ -8,6 +8,7 @@
8
8
  * Actual logic lives in core/ sub-modules (DD-02, DD-09).
9
9
  */
10
10
 
11
+ import fs from 'node:fs/promises';
11
12
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
13
  import { registerAll } from './core/register.mjs';
13
14
 
@@ -20,8 +21,10 @@ export {
20
21
  PLUGIN_TOOL_NOTICE,
21
22
  CATEGORY_MAP,
22
23
  FIGMA_TO_WCF_PROMPT,
24
+ BUILD_PAGE_PROMPT,
23
25
  WCF_RESOURCE_URIS,
24
26
  IDE_SETUP_TEMPLATES,
27
+ buildServerInstructions,
25
28
  } from './core/constants.mjs';
26
29
 
27
30
  // --- core/response.mjs ---
@@ -102,7 +105,7 @@ export {
102
105
  import { normalizePlugins, buildPluginDataSourceMap } from './core/plugins.mjs';
103
106
  import { buildIndexes, extractPrefixFromIndexes, loadPatternRegistryShape, buildRelatedComponentMap, buildPatternFrequencyMap } from './core/cem.mjs';
104
107
  import { buildTokenSuggestionMap, buildComponentTokenReferencedBy } from './core/tokens.mjs';
105
- import { MAX_TOOL_RESULT_BYTES, PACKAGE_VERSION } from './core/constants.mjs';
108
+ import { MAX_TOOL_RESULT_BYTES, PACKAGE_VERSION, buildServerInstructions } from './core/constants.mjs';
106
109
 
107
110
  // ---------------------------------------------------------------------------
108
111
  // createMcpServer — builds the McpServer with all tools registered, but does
@@ -118,6 +121,9 @@ export async function createMcpServer(loadJsonData, loadValidator, options = {})
118
121
  const loadTextData = typeof options?.loadTextData === 'function'
119
122
  ? options.loadTextData
120
123
  : null;
124
+ const loadTextDataFromPath = typeof options?.loadTextDataFromPath === 'function'
125
+ ? options.loadTextDataFromPath
126
+ : null;
121
127
 
122
128
  const loadJson = async (fileName) => {
123
129
  const override = pluginDataSourceMap.get(fileName);
@@ -128,6 +134,13 @@ export async function createMcpServer(loadJsonData, loadValidator, options = {})
128
134
  return loadJsonDataFromPath(override.path, fileName, override.pluginName);
129
135
  };
130
136
  const loadText = async (fileName) => {
137
+ const override = pluginDataSourceMap.get(fileName);
138
+ if (override) {
139
+ if (loadTextDataFromPath) {
140
+ return loadTextDataFromPath(override.path, fileName, override.pluginName);
141
+ }
142
+ return fs.readFile(override.path, 'utf8');
143
+ }
131
144
  if (!loadTextData) throw new Error(`Text data loader not configured for ${fileName}`);
132
145
  return loadTextData(fileName);
133
146
  };
@@ -145,6 +158,7 @@ export async function createMcpServer(loadJsonData, loadValidator, options = {})
145
158
  buildSlotNameMap = () => new Map(),
146
159
  detectInvalidSlotName = () => [],
147
160
  detectMissingRequiredAttributes = () => [],
161
+ detectDuplicateIdsInMarkup = () => [],
148
162
  detectOrphanedChildComponents = () => [],
149
163
  detectEmptyInteractiveElement = () => [],
150
164
  detectNonLowercaseAttributes = () => [],
@@ -190,10 +204,15 @@ export async function createMcpServer(loadJsonData, loadValidator, options = {})
190
204
  // component-selector-guide.json may not exist yet
191
205
  }
192
206
 
193
- const server = new McpServer({
194
- name: 'web-components-factory-design-system',
195
- version: PACKAGE_VERSION,
196
- });
207
+ const server = new McpServer(
208
+ {
209
+ name: 'web-components-factory-design-system',
210
+ version: PACKAGE_VERSION,
211
+ },
212
+ {
213
+ instructions: buildServerInstructions(detectedPrefix, installRegistry, patterns),
214
+ },
215
+ );
197
216
 
198
217
  // Delegate all tool / resource / prompt registration to register.mjs (DD-08)
199
218
  registerAll({
@@ -226,6 +245,7 @@ export async function createMcpServer(loadJsonData, loadValidator, options = {})
226
245
  buildSlotNameMap,
227
246
  detectInvalidSlotName,
228
247
  detectMissingRequiredAttributes,
248
+ detectDuplicateIdsInMarkup,
229
249
  detectOrphanedChildComponents,
230
250
  detectEmptyInteractiveElement,
231
251
  detectNonLowercaseAttributes,