@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/README.md +24 -3
- package/core/cem.mjs +16 -5
- package/core/constants.mjs +49 -1
- package/core/plugins.mjs +185 -3
- package/core/register.mjs +1112 -152
- package/core/tokens.mjs +70 -17
- package/core.mjs +25 -5
- package/data/design-tokens.json +932 -2
- package/data/guidelines-index.json +88 -24
- package/examples/plugins/custom-validation-plugin.mjs +61 -0
- package/package.json +1 -1
- package/runtime-data.mjs +41 -5
- package/server.mjs +5 -0
- package/validator.mjs +209 -31
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
|
-
|
|
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
|
|
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:
|
|
77
|
-
available:
|
|
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\(
|
|
111
|
+
const re = /var\(([^)]*)\)/g;
|
|
85
112
|
let match;
|
|
86
113
|
while ((match = re.exec(value))) {
|
|
87
|
-
const
|
|
88
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
195
|
-
|
|
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,
|