diff --git a/packages/dev/mcp/s2/scripts/build-data.mjs b/packages/dev/mcp/s2/scripts/build-data.mjs
index 8145ee77aa6..bec0322c838 100644
--- a/packages/dev/mcp/s2/scripts/build-data.mjs
+++ b/packages/dev/mcp/s2/scripts/build-data.mjs
@@ -1,8 +1,11 @@
#!/usr/bin/env node
+import {createRequire} from 'module';
import fg from 'fast-glob';
import {fileURLToPath, pathToFileURL} from 'url';
import fs from 'fs';
import path from 'path';
+// eslint-disable-next-line rulesdir/imports
+import * as ts from 'typescript';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -13,6 +16,7 @@ const ICONS_DIR = path.resolve(REPO_ROOT, 'packages/@react-spectrum/s2/s2wf-icon
const ILLUSTRATIONS_DIR = path.resolve(REPO_ROOT, 'packages/@react-spectrum/s2/spectrum-illustrations/linear');
const ICON_ALIASES_JS = path.resolve(REPO_ROOT, 'packages/dev/s2-docs/src/iconAliases.js');
const ILLUSTRATION_ALIASES_JS = path.resolve(REPO_ROOT, 'packages/dev/s2-docs/src/illustrationAliases.js');
+const STYLE_PROPERTIES_TS = path.resolve(REPO_ROOT, 'packages/dev/s2-docs/src/styleProperties.ts');
function ensureDir(p) {
fs.mkdirSync(p, {recursive: true});
@@ -24,6 +28,23 @@ function writeJson(file, data) {
console.log('Wrote', path.relative(REPO_ROOT, file));
}
+async function importTsModule(tsFilePath) {
+ if (!fs.existsSync(tsFilePath)) {
+ throw new Error(`TS module not found: ${tsFilePath}`);
+ }
+ const sourceText = fs.readFileSync(tsFilePath, 'utf8');
+ const result = ts.transpileModule(sourceText, {
+ fileName: tsFilePath,
+ compilerOptions: {
+ target: ts.ScriptTarget.ES2022,
+ module: ts.ModuleKind.ESNext,
+ esModuleInterop: true
+ }
+ });
+ const url = `data:text/javascript;base64,${Buffer.from(result.outputText, 'utf8').toString('base64')}`;
+ return import(url);
+}
+
function buildIconNames() {
if (!fs.existsSync(ICONS_DIR)) {
throw new Error(`Icons directory not found: ${ICONS_DIR}`);
@@ -61,6 +82,107 @@ async function loadAliases(modPath, exportName) {
return mod[exportName] ?? {};
}
+function buildBaseColorKeysFromSpectrumTokens(tokens) {
+ const keys = new Set();
+
+ // Matches spectrum-theme.ts
+ keys.add('transparent');
+ keys.add('black');
+ keys.add('white');
+
+ const addScale = (scale) => {
+ const re = new RegExp(`^${scale}-\\d+$`);
+ for (const tokenName of Object.keys(tokens)) {
+ if (re.test(tokenName)) {
+ // Match @react-spectrum/s2/style/tokens.ts behavior: strip "-color" in the middle.
+ keys.add(tokenName.replace('-color', ''));
+ }
+ }
+ };
+
+ // Global color scales
+ for (const scale of [
+ 'gray', 'blue', 'red', 'orange', 'yellow', 'chartreuse', 'celery', 'green',
+ 'seafoam', 'cyan', 'indigo', 'purple', 'fuchsia', 'magenta', 'pink',
+ 'turquoise', 'brown', 'silver', 'cinnamon'
+ ]) {
+ addScale(scale);
+ }
+
+ // Semantic color scales
+ for (const scale of ['accent-color', 'informative-color', 'negative-color', 'notice-color', 'positive-color']) {
+ addScale(scale);
+ }
+
+ // Simple transparent scales (names remain unchanged)
+ for (const scale of ['transparent-white', 'transparent-black']) {
+ const re = new RegExp(`^${scale}-\\d+$`);
+ for (const tokenName of Object.keys(tokens)) {
+ if (re.test(tokenName)) {
+ keys.add(tokenName);
+ }
+ }
+ }
+
+ // Overlay scale keys (derived in tokens.ts, we only need the names here)
+ for (const n of [25, 50, 75, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]) {
+ keys.add(`transparent-overlay-${n}`);
+ }
+
+ // High contrast keywords (matches spectrum-theme.ts)
+ for (const k of ['Background', 'ButtonBorder', 'ButtonFace', 'ButtonText', 'Field', 'Highlight', 'HighlightText', 'GrayText', 'Mark', 'LinkText']) {
+ keys.add(k);
+ }
+
+ return Array.from(keys).sort((a, b) => a.localeCompare(b));
+}
+
+function buildExpandedStyleMacroPropertyValues(styleProperties, spacingTypeValues, baseColorKeys) {
+ const out = {};
+
+ for (const [propertyName, def] of Object.entries(styleProperties)) {
+ const values = [];
+ const seen = new Set();
+
+ const pushUnique = (items) => {
+ for (const v of items) {
+ const s = String(v);
+ if (!seen.has(s)) {
+ seen.add(s);
+ values.push(s);
+ }
+ }
+ };
+
+ // Expand 'baseColors' placeholder into actual color token names.
+ const expandedBase = [];
+ for (const v of def.values ?? []) {
+ if (v === 'baseColors') {
+ expandedBase.push(...baseColorKeys);
+ } else {
+ expandedBase.push(v);
+ }
+ }
+ pushUnique(expandedBase);
+
+ // Expand spacing type placeholders into the actual numeric values shown in docs.
+ const additionalTypes = Array.isArray(def.additionalTypes) ? def.additionalTypes : [];
+ if (additionalTypes.includes('baseSpacing')) {
+ pushUnique(spacingTypeValues?.baseSpacing ?? []);
+ }
+ if (additionalTypes.includes('negativeSpacing')) {
+ pushUnique(spacingTypeValues?.negativeSpacing ?? []);
+ }
+
+ out[propertyName] = {
+ values,
+ additionalTypes
+ };
+ }
+
+ return out;
+}
+
async function main() {
const icons = buildIconNames();
const illustrations = buildIllustrationNames();
@@ -71,6 +193,22 @@ async function main() {
writeJson(path.join(OUT_DIR, 'illustrations.json'), illustrations);
writeJson(path.join(OUT_DIR, 'iconAliases.json'), iconAliases);
writeJson(path.join(OUT_DIR, 'illustrationAliases.json'), illustrationAliases);
+
+ // Style macro property definitions
+ const stylePropsMod = await importTsModule(STYLE_PROPERTIES_TS);
+ const propertyCategories = ['color', 'dimensions', 'text', 'effects', 'layout', 'misc', 'conditions'];
+ const styleProperties = {};
+ for (const category of propertyCategories) {
+ Object.assign(styleProperties, stylePropsMod.getPropertyDefinitions(category));
+ }
+ Object.assign(styleProperties, stylePropsMod.getShorthandDefinitions());
+ writeJson(path.join(OUT_DIR, 'styleProperties.json'), styleProperties);
+
+ const require = createRequire(import.meta.url);
+ const spectrumTokens = require('@adobe/spectrum-tokens/dist/json/variables.json');
+ const baseColorKeys = buildBaseColorKeysFromSpectrumTokens(spectrumTokens);
+ const expanded = buildExpandedStyleMacroPropertyValues(styleProperties, stylePropsMod.spacingTypeValues, baseColorKeys);
+ writeJson(path.join(OUT_DIR, 'styleMacroPropertyValues.json'), expanded);
}
main().catch((err) => {
diff --git a/packages/dev/mcp/s2/src/index.ts b/packages/dev/mcp/s2/src/index.ts
index e71b5ad9c0c..b8a8418cd76 100644
--- a/packages/dev/mcp/s2/src/index.ts
+++ b/packages/dev/mcp/s2/src/index.ts
@@ -1,7 +1,7 @@
#!/usr/bin/env node
///
import {errorToString} from '../../shared/src/utils.js';
-import {listIconNames, listIllustrationNames, loadIconAliases, loadIllustrationAliases} from './s2-data.js';
+import {listIconNames, listIllustrationNames, loadIconAliases, loadIllustrationAliases, loadStyleMacroPropertyValues} from './s2-data.js';
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
import {startServer} from '../../shared/src/server.js';
import {z} from 'zod';
@@ -87,6 +87,39 @@ import {z} from 'zod';
return {content: [{type: 'text', text: JSON.stringify(Array.from(results).sort((a, b) => a.localeCompare(b)), null, 2)}]};
}
);
+
+ server.registerTool(
+ 'get_style_macro_property_values',
+ {
+ title: 'Get style macro property values',
+ description: 'Returns the allowed values for a given S2 style macro property (including expanded color/spacing value lists where applicable).',
+ inputSchema: {propertyName: z.string()}
+ },
+ async ({propertyName}) => {
+ const name = String(propertyName ?? '').trim();
+ if (!name) {
+ throw new Error('Provide a non-empty propertyName.');
+ }
+
+ const all = loadStyleMacroPropertyValues();
+ let def = all[name];
+ if (!def) {
+ // fallback to case-insensitive lookup
+ const lower = name.toLowerCase();
+ const matchKey = Object.keys(all).find(k => k.toLowerCase() === lower);
+ if (matchKey) {
+ def = all[matchKey];
+ }
+ }
+
+ if (!def) {
+ const available = Object.keys(all).sort((a, b) => a.localeCompare(b));
+ throw new Error(`Unknown style macro property '${name}'. Available properties: ${available.join(', ')}`);
+ }
+
+ return {content: [{type: 'text', text: JSON.stringify(def, null, 2)}]};
+ }
+ );
});
} catch (err) {
console.error(errorToString(err));
diff --git a/packages/dev/mcp/s2/src/s2-data.ts b/packages/dev/mcp/s2/src/s2-data.ts
index 8b37337cf18..7e3fdcc5a87 100644
--- a/packages/dev/mcp/s2/src/s2-data.ts
+++ b/packages/dev/mcp/s2/src/s2-data.ts
@@ -9,6 +9,7 @@ let iconIdCache: string[] | null = null;
let illustrationIdCache: string[] | null = null;
let iconAliasesCache: Record | null = null;
let illustrationAliasesCache: Record | null = null;
+let styleMacroPropertyValuesCache: Record | null = null;
function readBundledJson(filename: string): any | null {
try {
@@ -45,3 +46,12 @@ export async function loadIllustrationAliases(): Promise {
+ if (styleMacroPropertyValuesCache) {return styleMacroPropertyValuesCache;}
+ const bundled = readBundledJson('styleMacroPropertyValues.json');
+ if (!bundled || typeof bundled !== 'object' || Array.isArray(bundled)) {
+ return (styleMacroPropertyValuesCache = {});
+ }
+ return (styleMacroPropertyValuesCache = bundled as any);
+}