@unpunnyfuns/swatchbook-mcp 0.55.0 → 0.56.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/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as createServer, t as loadFromConfig } from "./load-config-Bs56f0SF.mjs";
2
+ import { n as createServer, t as loadFromConfig } from "./load-config-9eaemurM.mjs";
3
3
  import { basename, dirname, isAbsolute, resolve } from "node:path";
4
4
  import { watch } from "node:fs";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -37,7 +37,7 @@ Usage:
37
37
  swatchbook-mcp --config <path> --no-watch Disable live-reload on token / config edits.
38
38
 
39
39
  Tools exposed:
40
- describe_project Orientation — counts, axes, permutations, presets, diagnostics.
40
+ describe_project Orientation — counts, axes, themes, presets, diagnostics.
41
41
  list_tokens List tokens by path glob / \`$type\`.
42
42
  search_tokens Fuzzy search across paths, descriptions, values.
43
43
  resolve_theme Full resolved token map for an axis tuple.
@@ -48,7 +48,7 @@ Tools exposed:
48
48
  get_color_formats hex / rgb / hsl / oklch / raw for a color token.
49
49
  get_color_contrast Pair-wise contrast (WCAG 2.1 ratio or APCA Lc) + pass flags.
50
50
  get_axis_variance Classify token variance (constant / single / multi) per axis.
51
- list_axes Axes + contexts + permutations + presets.
51
+ list_axes Axes + contexts + themes + presets.
52
52
  get_diagnostics Project diagnostics (optional severity filter).
53
53
  emit_css Full project stylesheet.`);
54
54
  process.exit(0);
@@ -117,8 +117,8 @@ async function main() {
117
117
  async function reload() {
118
118
  const { project: next } = await loadFromConfig(configAbsolute, args.cwd);
119
119
  server.setProject(next);
120
- const themeCount = next.permutations.length;
121
- const tokenCount = Object.keys(next.permutationsResolved[next.permutations[0]?.name ?? ""] ?? {}).length;
120
+ const themeCount = 1 + next.axes.reduce((acc, a) => acc + Math.max(0, a.contexts.length - 1), 0) + next.presets.length;
121
+ const tokenCount = next.varianceByPath.size;
122
122
  console.error(`swatchbook-mcp: project reloaded — ${tokenCount} tokens across ${themeCount} theme${themeCount === 1 ? "" : "s"}.`);
123
123
  stopWatchers();
124
124
  stopWatchers = setupReload(next.sourceFiles, configAbsolute, () => reload());
package/dist/bin.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.mjs","names":["fsWatch"],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Stdio entry for `npx @unpunnyfuns/swatchbook-mcp --config <path>`.\n *\n * Parses `--config <path>`, optional `--cwd <path>`, loads the project, and\n * binds an MCP server to stdio. Watches the resolved source files + config\n * file so token edits land in subsequent tool calls without restarting the\n * transport. Pass `--no-watch` to opt out (e.g. CI). Loader / watcher errors\n * print to stderr — stdout is reserved for MCP protocol frames.\n */\nimport { type FSWatcher, watch as fsWatch } from 'node:fs';\nimport { basename, dirname, isAbsolute, resolve } from 'node:path';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { loadFromConfig } from '#/load-config.ts';\nimport { createServer } from '#/server.ts';\n\ninterface CliArgs {\n config?: string;\n cwd?: string;\n watch: boolean;\n}\n\nfunction parseArgs(argv: readonly string[]): CliArgs {\n const out: CliArgs = { watch: true };\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = argv[i + 1];\n if ((arg === '--config' || arg === '-c') && next) {\n out.config = next;\n i++;\n } else if (arg === '--cwd' && next) {\n out.cwd = next;\n i++;\n } else if (arg === '--no-watch') {\n out.watch = false;\n } else if (arg === '--help' || arg === '-h') {\n console.log(`swatchbook-mcp — Model Context Protocol server for swatchbook projects\n\nUsage:\n swatchbook-mcp --config <path> Point at a swatchbook.config.{ts,mts,js,mjs}\n or a DTCG resolver.json directly. Bare\n resolvers boot with every other config\n option at defaults.\n swatchbook-mcp --config <path> --cwd <path> Override the project cwd for relative paths.\n swatchbook-mcp --config <path> --no-watch Disable live-reload on token / config edits.\n\nTools exposed:\n describe_project Orientation — counts, axes, permutations, presets, diagnostics.\n list_tokens List tokens by path glob / \\`$type\\`.\n search_tokens Fuzzy search across paths, descriptions, values.\n resolve_theme Full resolved token map for an axis tuple.\n get_token Full detail for one token path.\n get_alias_chain Forward alias chain per theme.\n get_aliased_by Backward alias tree.\n get_consumer_output CSS var + data-attribute activation for a tuple.\n get_color_formats hex / rgb / hsl / oklch / raw for a color token.\n get_color_contrast Pair-wise contrast (WCAG 2.1 ratio or APCA Lc) + pass flags.\n get_axis_variance Classify token variance (constant / single / multi) per axis.\n list_axes Axes + contexts + permutations + presets.\n get_diagnostics Project diagnostics (optional severity filter).\n emit_css Full project stylesheet.`);\n process.exit(0);\n }\n }\n return out;\n}\n\n/**\n * Watch the project's source files + config path for edits. Debounces\n * filesystem events (editors fire 2-3 per save) into a single reload per\n * 100 ms burst; on each settle, calls `loadFromConfig` again and swaps the\n * fresh project into the already-connected MCP server.\n *\n * Watches parent directories rather than files themselves. Atomic-save\n * editors unlink + recreate the target inode, which kills file-level\n * watchers on the first save; a dir watcher with filename filtering\n * survives that dance. Mirrors the addon's plugin strategy.\n */\nfunction setupReload(\n initialSourceFiles: readonly string[],\n configPath: string,\n reload: () => Promise<void>,\n): () => void {\n const dirs = new Map<string, Set<string>>();\n const add = (file: string): void => {\n const dir = dirname(file);\n const set = dirs.get(dir) ?? new Set<string>();\n set.add(basename(file));\n dirs.set(dir, set);\n };\n for (const file of initialSourceFiles) add(file);\n add(configPath);\n\n let pending: ReturnType<typeof setTimeout> | null = null;\n const schedule = (): void => {\n if (pending) clearTimeout(pending);\n pending = setTimeout(() => {\n pending = null;\n reload().catch((err) => {\n console.error('swatchbook-mcp: reload failed —', err);\n });\n }, 100);\n };\n\n const watchers: FSWatcher[] = [];\n for (const [dir, names] of dirs) {\n try {\n const w = fsWatch(dir, { persistent: false }, (eventType, filename) => {\n if (!filename) return;\n if (!names.has(filename)) return;\n if (eventType === 'change' || eventType === 'rename') schedule();\n });\n watchers.push(w);\n } catch {\n // unwatchable dir — skip. The initial load already succeeded, so the\n // server keeps serving the current snapshot.\n }\n }\n return () => {\n if (pending) clearTimeout(pending);\n for (const w of watchers) w.close();\n };\n}\n\nasync function main(): Promise<void> {\n const args = parseArgs(process.argv.slice(2));\n if (!args.config) {\n console.error('swatchbook-mcp: --config <path> is required. Use --help for usage.');\n process.exit(1);\n }\n const configAbsolute = isAbsolute(args.config)\n ? args.config\n : resolve(process.cwd(), args.config);\n\n const { project } = await loadFromConfig(configAbsolute, args.cwd);\n const server = createServer(project);\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n if (args.watch) {\n let stopWatchers = setupReload(project.sourceFiles, configAbsolute, () => reload());\n async function reload(): Promise<void> {\n const { project: next } = await loadFromConfig(configAbsolute, args.cwd);\n server.setProject(next);\n const themeCount = next.permutations.length;\n const tokenCount = Object.keys(\n next.permutationsResolved[next.permutations[0]?.name ?? ''] ?? {},\n ).length;\n console.error(\n `swatchbook-mcp: project reloaded — ${tokenCount} tokens across ${themeCount} theme${themeCount === 1 ? '' : 's'}.`,\n );\n // Rebind watchers against the fresh source-file set so newly-referenced\n // tokens / resolvers pick up edits from here on.\n stopWatchers();\n stopWatchers = setupReload(next.sourceFiles, configAbsolute, () => reload());\n }\n }\n}\n\nmain().catch((err) => {\n console.error('swatchbook-mcp failed to start:', err);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;;;AAsBA,SAAS,UAAU,MAAkC;CACnD,MAAM,MAAe,EAAE,OAAO,MAAM;AACpC,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EACjB,MAAM,OAAO,KAAK,IAAI;AACtB,OAAK,QAAQ,cAAc,QAAQ,SAAS,MAAM;AAChD,OAAI,SAAS;AACb;aACS,QAAQ,WAAW,MAAM;AAClC,OAAI,MAAM;AACV;aACS,QAAQ,aACjB,KAAI,QAAQ;WACH,QAAQ,YAAY,QAAQ,MAAM;AAC3C,WAAQ,IAAI;;;;;;;;;;;;;;;;;;;;;;;;gDAwB8B;AAC1C,WAAQ,KAAK,EAAE;;;AAGnB,QAAO;;;;;;;;;;;;;AAcT,SAAS,YACP,oBACA,YACA,QACY;CACZ,MAAM,uBAAO,IAAI,KAA0B;CAC3C,MAAM,OAAO,SAAuB;EAClC,MAAM,MAAM,QAAQ,KAAK;EACzB,MAAM,MAAM,KAAK,IAAI,IAAI,oBAAI,IAAI,KAAa;AAC9C,MAAI,IAAI,SAAS,KAAK,CAAC;AACvB,OAAK,IAAI,KAAK,IAAI;;AAEpB,MAAK,MAAM,QAAQ,mBAAoB,KAAI,KAAK;AAChD,KAAI,WAAW;CAEf,IAAI,UAAgD;CACpD,MAAM,iBAAuB;AAC3B,MAAI,QAAS,cAAa,QAAQ;AAClC,YAAU,iBAAiB;AACzB,aAAU;AACV,WAAQ,CAAC,OAAO,QAAQ;AACtB,YAAQ,MAAM,mCAAmC,IAAI;KACrD;KACD,IAAI;;CAGT,MAAM,WAAwB,EAAE;AAChC,MAAK,MAAM,CAAC,KAAK,UAAU,KACzB,KAAI;EACF,MAAM,IAAIA,MAAQ,KAAK,EAAE,YAAY,OAAO,GAAG,WAAW,aAAa;AACrE,OAAI,CAAC,SAAU;AACf,OAAI,CAAC,MAAM,IAAI,SAAS,CAAE;AAC1B,OAAI,cAAc,YAAY,cAAc,SAAU,WAAU;IAChE;AACF,WAAS,KAAK,EAAE;SACV;AAKV,cAAa;AACX,MAAI,QAAS,cAAa,QAAQ;AAClC,OAAK,MAAM,KAAK,SAAU,GAAE,OAAO;;;AAIvC,eAAe,OAAsB;CACnC,MAAM,OAAO,UAAU,QAAQ,KAAK,MAAM,EAAE,CAAC;AAC7C,KAAI,CAAC,KAAK,QAAQ;AAChB,UAAQ,MAAM,qEAAqE;AACnF,UAAQ,KAAK,EAAE;;CAEjB,MAAM,iBAAiB,WAAW,KAAK,OAAO,GAC1C,KAAK,SACL,QAAQ,QAAQ,KAAK,EAAE,KAAK,OAAO;CAEvC,MAAM,EAAE,YAAY,MAAM,eAAe,gBAAgB,KAAK,IAAI;CAClE,MAAM,SAAS,aAAa,QAAQ;CACpC,MAAM,YAAY,IAAI,sBAAsB;AAC5C,OAAM,OAAO,QAAQ,UAAU;AAE/B,KAAI,KAAK,OAAO;EACd,IAAI,eAAe,YAAY,QAAQ,aAAa,sBAAsB,QAAQ,CAAC;EACnF,eAAe,SAAwB;GACrC,MAAM,EAAE,SAAS,SAAS,MAAM,eAAe,gBAAgB,KAAK,IAAI;AACxE,UAAO,WAAW,KAAK;GACvB,MAAM,aAAa,KAAK,aAAa;GACrC,MAAM,aAAa,OAAO,KACxB,KAAK,qBAAqB,KAAK,aAAa,IAAI,QAAQ,OAAO,EAAE,CAClE,CAAC;AACF,WAAQ,MACN,sCAAsC,WAAW,iBAAiB,WAAW,QAAQ,eAAe,IAAI,KAAK,IAAI,GAClH;AAGD,iBAAc;AACd,kBAAe,YAAY,KAAK,aAAa,sBAAsB,QAAQ,CAAC;;;;AAKlF,MAAM,CAAC,OAAO,QAAQ;AACpB,SAAQ,MAAM,mCAAmC,IAAI;AACrD,SAAQ,KAAK,EAAE;EACf"}
1
+ {"version":3,"file":"bin.mjs","names":["fsWatch"],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Stdio entry for `npx @unpunnyfuns/swatchbook-mcp --config <path>`.\n *\n * Parses `--config <path>`, optional `--cwd <path>`, loads the project, and\n * binds an MCP server to stdio. Watches the resolved source files + config\n * file so token edits land in subsequent tool calls without restarting the\n * transport. Pass `--no-watch` to opt out (e.g. CI). Loader / watcher errors\n * print to stderr — stdout is reserved for MCP protocol frames.\n */\nimport { type FSWatcher, watch as fsWatch } from 'node:fs';\nimport { basename, dirname, isAbsolute, resolve } from 'node:path';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { loadFromConfig } from '#/load-config.ts';\nimport { createServer } from '#/server.ts';\n\ninterface CliArgs {\n config?: string;\n cwd?: string;\n watch: boolean;\n}\n\nfunction parseArgs(argv: readonly string[]): CliArgs {\n const out: CliArgs = { watch: true };\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = argv[i + 1];\n if ((arg === '--config' || arg === '-c') && next) {\n out.config = next;\n i++;\n } else if (arg === '--cwd' && next) {\n out.cwd = next;\n i++;\n } else if (arg === '--no-watch') {\n out.watch = false;\n } else if (arg === '--help' || arg === '-h') {\n console.log(`swatchbook-mcp — Model Context Protocol server for swatchbook projects\n\nUsage:\n swatchbook-mcp --config <path> Point at a swatchbook.config.{ts,mts,js,mjs}\n or a DTCG resolver.json directly. Bare\n resolvers boot with every other config\n option at defaults.\n swatchbook-mcp --config <path> --cwd <path> Override the project cwd for relative paths.\n swatchbook-mcp --config <path> --no-watch Disable live-reload on token / config edits.\n\nTools exposed:\n describe_project Orientation — counts, axes, themes, presets, diagnostics.\n list_tokens List tokens by path glob / \\`$type\\`.\n search_tokens Fuzzy search across paths, descriptions, values.\n resolve_theme Full resolved token map for an axis tuple.\n get_token Full detail for one token path.\n get_alias_chain Forward alias chain per theme.\n get_aliased_by Backward alias tree.\n get_consumer_output CSS var + data-attribute activation for a tuple.\n get_color_formats hex / rgb / hsl / oklch / raw for a color token.\n get_color_contrast Pair-wise contrast (WCAG 2.1 ratio or APCA Lc) + pass flags.\n get_axis_variance Classify token variance (constant / single / multi) per axis.\n list_axes Axes + contexts + themes + presets.\n get_diagnostics Project diagnostics (optional severity filter).\n emit_css Full project stylesheet.`);\n process.exit(0);\n }\n }\n return out;\n}\n\n/**\n * Watch the project's source files + config path for edits. Debounces\n * filesystem events (editors fire 2-3 per save) into a single reload per\n * 100 ms burst; on each settle, calls `loadFromConfig` again and swaps the\n * fresh project into the already-connected MCP server.\n *\n * Watches parent directories rather than files themselves. Atomic-save\n * editors unlink + recreate the target inode, which kills file-level\n * watchers on the first save; a dir watcher with filename filtering\n * survives that dance. Mirrors the addon's plugin strategy.\n */\nfunction setupReload(\n initialSourceFiles: readonly string[],\n configPath: string,\n reload: () => Promise<void>,\n): () => void {\n const dirs = new Map<string, Set<string>>();\n const add = (file: string): void => {\n const dir = dirname(file);\n const set = dirs.get(dir) ?? new Set<string>();\n set.add(basename(file));\n dirs.set(dir, set);\n };\n for (const file of initialSourceFiles) add(file);\n add(configPath);\n\n let pending: ReturnType<typeof setTimeout> | null = null;\n const schedule = (): void => {\n if (pending) clearTimeout(pending);\n pending = setTimeout(() => {\n pending = null;\n reload().catch((err) => {\n console.error('swatchbook-mcp: reload failed —', err);\n });\n }, 100);\n };\n\n const watchers: FSWatcher[] = [];\n for (const [dir, names] of dirs) {\n try {\n const w = fsWatch(dir, { persistent: false }, (eventType, filename) => {\n if (!filename) return;\n if (!names.has(filename)) return;\n if (eventType === 'change' || eventType === 'rename') schedule();\n });\n watchers.push(w);\n } catch {\n // unwatchable dir — skip. The initial load already succeeded, so the\n // server keeps serving the current snapshot.\n }\n }\n return () => {\n if (pending) clearTimeout(pending);\n for (const w of watchers) w.close();\n };\n}\n\nasync function main(): Promise<void> {\n const args = parseArgs(process.argv.slice(2));\n if (!args.config) {\n console.error('swatchbook-mcp: --config <path> is required. Use --help for usage.');\n process.exit(1);\n }\n const configAbsolute = isAbsolute(args.config)\n ? args.config\n : resolve(process.cwd(), args.config);\n\n const { project } = await loadFromConfig(configAbsolute, args.cwd);\n const server = createServer(project);\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n if (args.watch) {\n let stopWatchers = setupReload(project.sourceFiles, configAbsolute, () => reload());\n async function reload(): Promise<void> {\n const { project: next } = await loadFromConfig(configAbsolute, args.cwd);\n server.setProject(next);\n // Same set the resolver loader's singleton enumeration produces:\n // default tuple + per-axis non-default cells + presets.\n const themeCount =\n 1 +\n next.axes.reduce((acc, a) => acc + Math.max(0, a.contexts.length - 1), 0) +\n next.presets.length;\n const tokenCount = next.varianceByPath.size;\n console.error(\n `swatchbook-mcp: project reloaded — ${tokenCount} tokens across ${themeCount} theme${themeCount === 1 ? '' : 's'}.`,\n );\n // Rebind watchers against the fresh source-file set so newly-referenced\n // tokens / resolvers pick up edits from here on.\n stopWatchers();\n stopWatchers = setupReload(next.sourceFiles, configAbsolute, () => reload());\n }\n }\n}\n\nmain().catch((err) => {\n console.error('swatchbook-mcp failed to start:', err);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;;;AAsBA,SAAS,UAAU,MAAkC;CACnD,MAAM,MAAe,EAAE,OAAO,MAAM;AACpC,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EACjB,MAAM,OAAO,KAAK,IAAI;AACtB,OAAK,QAAQ,cAAc,QAAQ,SAAS,MAAM;AAChD,OAAI,SAAS;AACb;aACS,QAAQ,WAAW,MAAM;AAClC,OAAI,MAAM;AACV;aACS,QAAQ,aACjB,KAAI,QAAQ;WACH,QAAQ,YAAY,QAAQ,MAAM;AAC3C,WAAQ,IAAI;;;;;;;;;;;;;;;;;;;;;;;;gDAwB8B;AAC1C,WAAQ,KAAK,EAAE;;;AAGnB,QAAO;;;;;;;;;;;;;AAcT,SAAS,YACP,oBACA,YACA,QACY;CACZ,MAAM,uBAAO,IAAI,KAA0B;CAC3C,MAAM,OAAO,SAAuB;EAClC,MAAM,MAAM,QAAQ,KAAK;EACzB,MAAM,MAAM,KAAK,IAAI,IAAI,oBAAI,IAAI,KAAa;AAC9C,MAAI,IAAI,SAAS,KAAK,CAAC;AACvB,OAAK,IAAI,KAAK,IAAI;;AAEpB,MAAK,MAAM,QAAQ,mBAAoB,KAAI,KAAK;AAChD,KAAI,WAAW;CAEf,IAAI,UAAgD;CACpD,MAAM,iBAAuB;AAC3B,MAAI,QAAS,cAAa,QAAQ;AAClC,YAAU,iBAAiB;AACzB,aAAU;AACV,WAAQ,CAAC,OAAO,QAAQ;AACtB,YAAQ,MAAM,mCAAmC,IAAI;KACrD;KACD,IAAI;;CAGT,MAAM,WAAwB,EAAE;AAChC,MAAK,MAAM,CAAC,KAAK,UAAU,KACzB,KAAI;EACF,MAAM,IAAIA,MAAQ,KAAK,EAAE,YAAY,OAAO,GAAG,WAAW,aAAa;AACrE,OAAI,CAAC,SAAU;AACf,OAAI,CAAC,MAAM,IAAI,SAAS,CAAE;AAC1B,OAAI,cAAc,YAAY,cAAc,SAAU,WAAU;IAChE;AACF,WAAS,KAAK,EAAE;SACV;AAKV,cAAa;AACX,MAAI,QAAS,cAAa,QAAQ;AAClC,OAAK,MAAM,KAAK,SAAU,GAAE,OAAO;;;AAIvC,eAAe,OAAsB;CACnC,MAAM,OAAO,UAAU,QAAQ,KAAK,MAAM,EAAE,CAAC;AAC7C,KAAI,CAAC,KAAK,QAAQ;AAChB,UAAQ,MAAM,qEAAqE;AACnF,UAAQ,KAAK,EAAE;;CAEjB,MAAM,iBAAiB,WAAW,KAAK,OAAO,GAC1C,KAAK,SACL,QAAQ,QAAQ,KAAK,EAAE,KAAK,OAAO;CAEvC,MAAM,EAAE,YAAY,MAAM,eAAe,gBAAgB,KAAK,IAAI;CAClE,MAAM,SAAS,aAAa,QAAQ;CACpC,MAAM,YAAY,IAAI,sBAAsB;AAC5C,OAAM,OAAO,QAAQ,UAAU;AAE/B,KAAI,KAAK,OAAO;EACd,IAAI,eAAe,YAAY,QAAQ,aAAa,sBAAsB,QAAQ,CAAC;EACnF,eAAe,SAAwB;GACrC,MAAM,EAAE,SAAS,SAAS,MAAM,eAAe,gBAAgB,KAAK,IAAI;AACxE,UAAO,WAAW,KAAK;GAGvB,MAAM,aACJ,IACA,KAAK,KAAK,QAAQ,KAAK,MAAM,MAAM,KAAK,IAAI,GAAG,EAAE,SAAS,SAAS,EAAE,EAAE,EAAE,GACzE,KAAK,QAAQ;GACf,MAAM,aAAa,KAAK,eAAe;AACvC,WAAQ,MACN,sCAAsC,WAAW,iBAAiB,WAAW,QAAQ,eAAe,IAAI,KAAK,IAAI,GAClH;AAGD,iBAAc;AACd,kBAAe,YAAY,KAAK,aAAa,sBAAsB,QAAQ,CAAC;;;;AAKlF,MAAM,CAAC,OAAO,QAAQ;AACpB,SAAQ,MAAM,mCAAmC,IAAI;AACrD,SAAQ,KAAK,EAAE;EACf"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as createServer, t as loadFromConfig } from "./load-config-Bs56f0SF.mjs";
1
+ import { n as createServer, t as loadFromConfig } from "./load-config-9eaemurM.mjs";
2
2
  export { createServer, loadFromConfig };
@@ -191,6 +191,7 @@ function matchPath(path, filter) {
191
191
  function createServer(initial) {
192
192
  let project = initial;
193
193
  let tupleByName = buildTupleByName(initial);
194
+ let defaultThemeName = tupleToName(initial.axes, initial.defaultTuple);
194
195
  const server = new McpServer({
195
196
  name: "@unpunnyfuns/swatchbook-mcp",
196
197
  version: project.config.cssVarPrefix ? `project:${project.config.cssVarPrefix}` : "project"
@@ -198,25 +199,37 @@ function createServer(initial) {
198
199
  server.setProject = (next) => {
199
200
  project = next;
200
201
  tupleByName = buildTupleByName(next);
202
+ defaultThemeName = tupleToName(next.axes, next.defaultTuple);
201
203
  };
202
204
  /**
203
205
  * Resolve tokens for a `theme` name parameter. Falls back to the
204
206
  * project's default tuple when the name doesn't match any known
205
- * permutation (also covers the no-theme-provided path).
207
+ * theme (also covers the no-theme-provided path).
206
208
  */
207
209
  const tokensForTheme = (themeName) => {
208
210
  const tuple = (themeName !== void 0 ? tupleByName.get(themeName) : void 0) ?? project.defaultTuple;
209
211
  return project.resolveAt(tuple);
210
212
  };
213
+ /**
214
+ * Iterate every `(themeName, tuple)` pair the project surfaces —
215
+ * default + singletons + presets. Order is insertion order of
216
+ * `tupleByName` (default first, then singletons per axis, then presets).
217
+ */
218
+ const eachTheme = function* () {
219
+ for (const [name, tuple] of tupleByName) yield {
220
+ name,
221
+ tuple
222
+ };
223
+ };
211
224
  server.registerTool("describe_project", {
212
225
  description: "High-level summary of the project — total token count per theme, axes (with contexts) and how they compose, preset list, diagnostic counts by severity, css-var prefix, and the DTCG `$type`s present. Good first call for an agent that needs an orientation before querying specifics.",
213
226
  inputSchema: {}
214
227
  }, () => {
215
228
  const typeCounts = {};
216
229
  const tokensPerTheme = {};
217
- for (const theme of project.permutations) {
218
- const tokens = project.resolveAt(theme.input);
219
- tokensPerTheme[theme.name] = Object.keys(tokens).length;
230
+ for (const { name, tuple } of eachTheme()) {
231
+ const tokens = project.resolveAt(tuple);
232
+ tokensPerTheme[name] = Object.keys(tokens).length;
220
233
  for (const token of Object.values(tokens)) if (token.$type) typeCounts[token.$type] = (typeCounts[token.$type] ?? 0) + 1;
221
234
  }
222
235
  const diagBySeverity = {
@@ -232,8 +245,8 @@ function createServer(initial) {
232
245
  contexts: a.contexts,
233
246
  default: a.default
234
247
  })),
235
- permutations: project.permutations.map((t) => t.name),
236
- defaultPermutation: project.permutations[0]?.name ?? null,
248
+ themes: [...tupleByName.keys()],
249
+ defaultTheme: defaultThemeName,
237
250
  presets: project.presets.map((p) => p.name),
238
251
  tokensPerTheme,
239
252
  types: typeCounts,
@@ -252,11 +265,10 @@ function createServer(initial) {
252
265
  inputSchema: {
253
266
  filter: z.string().optional().describe("Dot-path glob, e.g. `color.*` or `color.palette.**`. Omit for all tokens."),
254
267
  type: z.string().optional().describe("DTCG `$type` to scope the result, e.g. `color`, `dimension`, `typography`."),
255
- theme: z.string().optional().describe("Permutation name to read values from. Defaults to the project default theme.")
268
+ theme: z.string().optional().describe("Theme name to read values from. Defaults to the project default theme.")
256
269
  }
257
270
  }, ({ filter, type, theme }) => {
258
- const themeName = theme ?? project.permutations[0]?.name;
259
- if (!themeName) return textResult("No permutations in project.");
271
+ const themeName = theme ?? defaultThemeName;
260
272
  const tokens = tokensForTheme(themeName);
261
273
  const rows = [];
262
274
  for (const [path, token] of Object.entries(tokens)) {
@@ -284,14 +296,14 @@ function createServer(initial) {
284
296
  let description;
285
297
  let aliasedBy;
286
298
  let found = false;
287
- for (const theme of project.permutations) {
288
- const token = project.resolveAt(theme.input)[path];
299
+ for (const { name, tuple } of eachTheme()) {
300
+ const token = project.resolveAt(tuple)[path];
289
301
  if (!token) continue;
290
302
  found = true;
291
303
  type ??= token.$type;
292
304
  description ??= token.$description;
293
305
  aliasedBy ??= token.aliasedBy;
294
- perTheme[theme.name] = {
306
+ perTheme[name] = {
295
307
  value: stringifyValue(token.$value),
296
308
  ...token.aliasOf !== void 0 && { aliasOf: token.aliasOf },
297
309
  ...token.aliasChain !== void 0 && { aliasChain: token.aliasChain }
@@ -310,7 +322,7 @@ function createServer(initial) {
310
322
  });
311
323
  });
312
324
  server.registerTool("list_axes", {
313
- description: "List the project axes — each axis has a name, its contexts (discrete values like `Light` / `Dark`), a default, and a source (`resolver` for DTCG-resolver-driven, `layered` for authored layered axes, `synthetic` for single-theme projects). Also returns the named permutations (every axis tuple combination) and any presets defined in the project config.",
325
+ description: "List the project axes — each axis has a name, its contexts (discrete values like `Light` / `Dark`), a default, and a source (`resolver` for DTCG-resolver-driven, `layered` for authored layered axes, `synthetic` for single-theme projects). Also returns the named themes (one per default tuple + per-axis non-default singleton + preset) and any presets defined in the project config.",
314
326
  inputSchema: {}
315
327
  }, () => jsonResult({
316
328
  axes: project.axes.map((axis) => ({
@@ -321,9 +333,9 @@ function createServer(initial) {
321
333
  source: axis.source
322
334
  })),
323
335
  disabledAxes: project.disabledAxes,
324
- permutations: project.permutations.map((t) => ({
325
- name: t.name,
326
- input: t.input
336
+ themes: [...eachTheme()].map(({ name, tuple }) => ({
337
+ name,
338
+ input: tuple
327
339
  })),
328
340
  presets: project.presets.map((p) => ({
329
341
  name: p.name,
@@ -337,14 +349,14 @@ function createServer(initial) {
337
349
  }, ({ path }) => {
338
350
  const perTheme = {};
339
351
  let found = false;
340
- for (const theme of project.permutations) {
341
- const token = project.resolveAt(theme.input)[path];
352
+ for (const { name, tuple } of eachTheme()) {
353
+ const token = project.resolveAt(tuple)[path];
342
354
  if (!token) continue;
343
355
  found = true;
344
356
  const chain = [path];
345
357
  if (token.aliasChain && token.aliasChain.length > 0) chain.push(...token.aliasChain);
346
358
  else if (token.aliasOf) chain.push(token.aliasOf);
347
- perTheme[theme.name] = {
359
+ perTheme[name] = {
348
360
  ...token.aliasOf !== void 0 && { aliasOf: token.aliasOf },
349
361
  chain
350
362
  };
@@ -363,9 +375,7 @@ function createServer(initial) {
363
375
  }
364
376
  }, ({ path, maxDepth }) => {
365
377
  const depth = maxDepth ?? 6;
366
- const themeName = project.permutations[0]?.name;
367
- if (!themeName) return textResult("No permutations in project.");
368
- const tokens = tokensForTheme(themeName);
378
+ const tokens = tokensForTheme(defaultThemeName);
369
379
  if (!tokens[path]) return textResult(`Token not found: ${path}`);
370
380
  const visited = new Set([path]);
371
381
  const walk = (current, d) => {
@@ -399,11 +409,10 @@ function createServer(initial) {
399
409
  description: "For a color token, return its value rendered in every format the addon toolbar exposes — `hex`, `rgb`, `hsl`, `oklch`, and the raw JSON. Each entry carries an `outOfGamut` flag when the chosen colorspace can't losslessly represent the token (wide-gamut tokens rendered in sRGB, for example). Skips non-color tokens.",
400
410
  inputSchema: {
401
411
  path: z.string().describe("Dot-path of a color token, e.g. `color.accent.bg`."),
402
- theme: z.string().optional().describe("Permutation name to read the value from. Defaults to the project default theme.")
412
+ theme: z.string().optional().describe("Theme name to read the value from. Defaults to the project default theme.")
403
413
  }
404
414
  }, ({ path, theme }) => {
405
- const themeName = theme ?? project.permutations[0]?.name;
406
- if (!themeName) return textResult("No permutations in project.");
415
+ const themeName = theme ?? defaultThemeName;
407
416
  const token = tokensForTheme(themeName)[path];
408
417
  if (!token) return textResult(`Token not found: ${path}`);
409
418
  if (token.$type !== "color") return textResult(`Token ${path} is not a color (got $type=${token.$type ?? "unknown"}).`);
@@ -418,12 +427,11 @@ function createServer(initial) {
418
427
  inputSchema: {
419
428
  foreground: z.string().describe("Dot-path of the foreground color token, e.g. `color.text.default`."),
420
429
  background: z.string().describe("Dot-path of the background color token, e.g. `color.surface.default`."),
421
- theme: z.string().optional().describe("Permutation name. Defaults to the project default theme."),
430
+ theme: z.string().optional().describe("Theme name. Defaults to the project default theme."),
422
431
  algorithm: z.enum(["wcag21", "apca"]).optional().describe("Contrast algorithm. `wcag21` is the classic 4.5:1 ratio; `apca` is the perceptually-weighted Silver-draft successor. Defaults to `wcag21`.")
423
432
  }
424
433
  }, ({ foreground, background, theme, algorithm }) => {
425
- const themeName = theme ?? project.permutations[0]?.name;
426
- if (!themeName) return textResult("No permutations in project.");
434
+ const themeName = theme ?? defaultThemeName;
427
435
  const tokens = tokensForTheme(themeName);
428
436
  const fgTok = tokens[foreground];
429
437
  const bgTok = tokens[background];
@@ -450,7 +458,6 @@ function createServer(initial) {
450
458
  description: "Classify how a token's resolved value depends on the project's axes. Returns `kind` — `constant` (same across every tuple), `single` (varies with exactly one axis, e.g. mode only), or `multi` (varies across two or more axes). Also returns `varyingAxes` / `constantAcrossAxes` plus a `perAxis` breakdown with each context's stringified value (holding other axes at their defaults). Use when reasoning about whether a token is theme-independent, whether a refactor changed an axis dependency, or to confirm that (say) a role token only varies with `contrast`.",
451
459
  inputSchema: { path: z.string().describe("Dot-path of the token to analyse, e.g. `color.text.default`.") }
452
460
  }, ({ path }) => {
453
- if (project.permutations.length === 0) return textResult("No permutations in project.");
454
461
  const cached = project.varianceByPath.get(path);
455
462
  if (!cached) return textResult(`Token not found in any theme: ${path}`);
456
463
  return jsonResult(cached);
@@ -459,12 +466,11 @@ function createServer(initial) {
459
466
  description: "Fuzzy search across token paths, `$description`, and stringified values. Case-insensitive, tolerates a single-character typo per term, and accepts out-of-order terms (`\"blue palette\"` finds `color.palette.blue.500`). Returns matches ranked by relevance with a short snippet pointing at where the match hit. Use when you know what you want but not the exact path. Scopes to a single theme (default: project default).",
460
467
  inputSchema: {
461
468
  query: z.string().min(1).describe("Fuzzy query (case-insensitive)."),
462
- theme: z.string().optional().describe("Permutation name to search within. Defaults to the project default."),
469
+ theme: z.string().optional().describe("Theme name to search within. Defaults to the project default."),
463
470
  limit: z.number().int().positive().optional().describe("Cap the result count. Default 50.")
464
471
  }
465
472
  }, ({ query, theme, limit }) => {
466
- const themeName = theme ?? project.permutations[0]?.name;
467
- if (!themeName) return textResult("No permutations in project.");
473
+ const themeName = theme ?? defaultThemeName;
468
474
  const tokens = tokensForTheme(themeName);
469
475
  const max = limit ?? 50;
470
476
  const hits = fuzzyFilter(Object.entries(tokens).map(([path, token]) => {
@@ -513,11 +519,7 @@ function createServer(initial) {
513
519
  const candidate = tuple[axis.name];
514
520
  active[axis.name] = candidate && axis.contexts.includes(candidate) ? candidate : axis.default;
515
521
  }
516
- const themeName = project.permutations.find((t) => {
517
- for (const axis of project.axes) if (t.input[axis.name] !== active[axis.name]) return false;
518
- return true;
519
- })?.name ?? project.permutations[0]?.name;
520
- if (!themeName) return textResult("No matching theme.");
522
+ const themeName = tupleToName(project.axes, active);
521
523
  const tokens = project.resolveAt(active);
522
524
  const resolved = {};
523
525
  let count = 0;
@@ -552,10 +554,7 @@ function createServer(initial) {
552
554
  const candidate = tuple?.[axis.name];
553
555
  activeTuple[axis.name] = candidate && axis.contexts.includes(candidate) ? candidate : axis.default;
554
556
  }
555
- const themeName = project.permutations.find((t) => {
556
- for (const axis of project.axes) if (t.input[axis.name] !== activeTuple[axis.name]) return false;
557
- return true;
558
- })?.name ?? project.permutations[0]?.name ?? "";
557
+ const themeName = tupleToName(project.axes, activeTuple);
559
558
  const token = project.resolveAt(activeTuple)[path];
560
559
  if (!token) return textResult(`Token not found: ${path}`);
561
560
  const cssVar = `var(--${prefix ? `${prefix}-` : ""}${path.replaceAll(".", "-")})`;
@@ -598,15 +597,39 @@ function createServer(initial) {
598
597
  return server;
599
598
  }
600
599
  /**
601
- * Build a `Map<permutationName, axisTuple>` so the per-theme tool
602
- * handlers can avoid an `Array.find` scan per request. Bounded by the
603
- * permutation count.
600
+ * Build a `Map<themeName, axisTuple>` covering the same set the
601
+ * resolver loader's singleton enumeration produces: default tuple +
602
+ * one per non-default cell on each axis + each preset. Bounded by
603
+ * `1 + Σ(axes × (contexts - 1)) + presets.length` — linear in axis
604
+ * cardinality, independent of the cartesian product.
604
605
  */
605
606
  function buildTupleByName(project) {
606
607
  const out = /* @__PURE__ */ new Map();
607
- for (const perm of project.permutations) out.set(perm.name, perm.input);
608
+ const defaultName = tupleToName(project.axes, project.defaultTuple);
609
+ out.set(defaultName, { ...project.defaultTuple });
610
+ for (const axis of project.axes) for (const ctx of axis.contexts) {
611
+ if (ctx === axis.default) continue;
612
+ const tuple = {
613
+ ...project.defaultTuple,
614
+ [axis.name]: ctx
615
+ };
616
+ out.set(tupleToName(project.axes, tuple), tuple);
617
+ }
618
+ for (const preset of project.presets) {
619
+ const tuple = { ...project.defaultTuple };
620
+ for (const [axis, ctx] of Object.entries(preset.axes)) if (ctx !== void 0) tuple[axis] = ctx;
621
+ out.set(tupleToName(project.axes, tuple), tuple);
622
+ }
608
623
  return out;
609
624
  }
625
+ /**
626
+ * Synthesize the tuple's stable name — axis values joined by ` · ` in
627
+ * axis order. Inlined here rather than imported so MCP doesn't depend
628
+ * on `permutationID` staying in core's public API.
629
+ */
630
+ function tupleToName(axes, tuple) {
631
+ return axes.map((a) => tuple[a.name] ?? a.default).join(" · ");
632
+ }
610
633
  function stringifyValue(value) {
611
634
  if (value === null || value === void 0) return "—";
612
635
  if (typeof value === "string") return value;
@@ -666,4 +689,4 @@ async function loadTsConfig(absolute) {
666
689
  //#endregion
667
690
  export { createServer as n, loadFromConfig as t };
668
691
 
669
- //# sourceMappingURL=load-config-Bs56f0SF.mjs.map
692
+ //# sourceMappingURL=load-config-9eaemurM.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-config-9eaemurM.mjs","names":[],"sources":["../src/contrast.ts","../src/format-color.ts","../src/match.ts","../src/server.ts","../src/load-config.ts"],"sourcesContent":["import Color from 'colorjs.io';\n\n/**\n * Pair-wise contrast between two DTCG color `$value`s. Wraps\n * `colorjs.io`'s contrast primitives with pass/fail grids for the\n * thresholds agents typically reason against (WCAG 2.1 AA/AAA\n * normal/large; APCA body / large text / non-text).\n *\n * Kept separate from `format-color.ts` even though both build\n * `Color` instances from the same DTCG shape — the color-format\n * formatter stringifies, this one does arithmetic, and they're\n * called from different tool paths.\n */\n\ninterface ColorInput {\n colorSpace?: string;\n components?: readonly (number | null)[];\n channels?: readonly (number | null)[];\n alpha?: number;\n}\n\nfunction toColor(raw: unknown): Color | null {\n if (!raw || typeof raw !== 'object') return null;\n const n = raw as ColorInput;\n const components = n.components ?? n.channels;\n if (!components || !n.colorSpace) return null;\n const coords = components.map((c) => (c == null ? 0 : c));\n const [r = 0, g = 0, b = 0] = coords;\n try {\n return new Color(n.colorSpace, [r, g, b], n.alpha ?? 1);\n } catch {\n return null;\n }\n}\n\nexport type ContrastAlgorithm = 'wcag21' | 'apca';\n\nexport interface WcagPasses {\n aa: { normal: boolean; large: boolean };\n aaa: { normal: boolean; large: boolean };\n}\n\nexport interface ApcaPasses {\n lc: number;\n body: boolean;\n largeText: boolean;\n nonText: boolean;\n}\n\nexport interface ContrastResult {\n algorithm: ContrastAlgorithm;\n /**\n * WCAG 2.1 returns the contrast ratio (1–21). APCA returns a signed\n * Lc value (-108..+106); the sign marks polarity (negative = dark\n * text on light background; positive = light on dark). Consumers\n * usually care about `Math.abs(ratio)` for threshold checks.\n */\n ratio: number;\n wcag?: WcagPasses;\n apca?: ApcaPasses;\n}\n\nexport function computeContrast(\n foreground: unknown,\n background: unknown,\n algorithm: ContrastAlgorithm = 'wcag21',\n): ContrastResult | null {\n const fg = toColor(foreground);\n const bg = toColor(background);\n if (!fg || !bg) return null;\n\n if (algorithm === 'wcag21') {\n const ratio = fg.contrast(bg, 'WCAG21');\n return {\n algorithm: 'wcag21',\n ratio,\n wcag: {\n aa: { normal: ratio >= 4.5, large: ratio >= 3 },\n aaa: { normal: ratio >= 7, large: ratio >= 4.5 },\n },\n };\n }\n\n /**\n * APCA thresholds from the Silver draft guidance. These are the\n * \"body text / large text / non-text\" bronze tier thresholds most\n * tooling settles on; the full table has more gradations but\n * agents rarely need them. Non-text = icons, focus rings, UI\n * borders.\n */\n const lc = fg.contrast(bg, 'APCA');\n const absLc = Math.abs(lc);\n return {\n algorithm: 'apca',\n ratio: lc,\n apca: {\n lc,\n body: absLc >= 75,\n largeText: absLc >= 60,\n nonText: absLc >= 45,\n },\n };\n}\n","import Color from 'colorjs.io';\n\n/**\n * Convert a DTCG color `$value` into the same format menu the\n * addon's toolbar exposes — hex / rgb / hsl / oklch / raw JSON. Mirrors\n * the narrower version in `@unpunnyfuns/swatchbook-blocks/format-color.ts`\n * without pulling blocks (and React) into the MCP server.\n *\n * Out-of-gamut colours still stringify — the caller gets both the\n * rendered string and an `outOfGamut` flag so agents can warn.\n */\n\nexport type ColorFormat = 'hex' | 'rgb' | 'hsl' | 'oklch' | 'raw';\n\nexport interface FormatColorResult {\n format: ColorFormat;\n value: string;\n outOfGamut: boolean;\n}\n\ninterface NormalizedColor {\n colorSpace?: string;\n components?: readonly (number | null)[];\n channels?: readonly (number | null)[];\n alpha?: number;\n hex?: string;\n}\n\nexport function formatColor(raw: unknown, format: ColorFormat): FormatColorResult | null {\n if (!raw || typeof raw !== 'object') return null;\n const normalized = raw as NormalizedColor;\n\n if (format === 'raw') {\n return { format, value: JSON.stringify(raw), outOfGamut: false };\n }\n\n const components = normalized.components ?? normalized.channels;\n if (!components || !normalized.colorSpace) return null;\n\n let color: Color;\n try {\n const coords = components.map((c) => (c == null ? 0 : c));\n const [r = 0, g = 0, b = 0] = coords;\n color = new Color(normalized.colorSpace, [r, g, b], normalized.alpha ?? 1);\n } catch {\n return null;\n }\n\n if (format === 'hex') {\n try {\n const inSrgb = color.to('srgb');\n const outOfGamut = !inSrgb.inGamut();\n if (outOfGamut) {\n return { format, value: inSrgb.toString({ format: 'rgb' }), outOfGamut: true };\n }\n return { format, value: inSrgb.toString({ format: 'hex' }), outOfGamut: false };\n } catch {\n return null;\n }\n }\n\n if (format === 'rgb') {\n try {\n const inSrgb = color.to('srgb');\n return {\n format,\n value: inSrgb.toString({ format: 'rgb' }),\n outOfGamut: !inSrgb.inGamut(),\n };\n } catch {\n return null;\n }\n }\n\n if (format === 'hsl') {\n try {\n const inHsl = color.to('hsl');\n return {\n format,\n value: inHsl.toString(),\n outOfGamut: !color.to('srgb').inGamut(),\n };\n } catch {\n return null;\n }\n }\n\n if (format === 'oklch') {\n try {\n const inOklch = color.to('oklch');\n return { format, value: inOklch.toString(), outOfGamut: false };\n } catch {\n return null;\n }\n }\n\n return null;\n}\n\nexport const ALL_COLOR_FORMATS: readonly ColorFormat[] = ['hex', 'rgb', 'hsl', 'oklch', 'raw'];\n\nexport function formatColorEveryWay(raw: unknown): Partial<Record<ColorFormat, FormatColorResult>> {\n const out: Partial<Record<ColorFormat, FormatColorResult>> = {};\n for (const format of ALL_COLOR_FORMATS) {\n const result = formatColor(raw, format);\n if (result) out[format] = result;\n }\n return out;\n}\n","/**\n * Minimal DTCG-flavoured path matcher. Accepts exact paths (`color.bg`), single-\n * segment globs (`color.*`), multi-segment globs (`color.**`), or a trailing `*`\n * mid-segment (`color.palette.blue.*`). No brace expansion, no regex — DTCG\n * paths are dot-delimited and this matches the parity the blocks' `globMatch`\n * ships. Case-sensitive.\n */\nexport function matchPath(path: string, filter: string | undefined): boolean {\n if (!filter) return true;\n if (filter === '**' || filter === '*') return true;\n\n const pathSegments = path.split('.');\n const filterSegments = filter.split('.');\n\n let pi = 0;\n let fi = 0;\n while (fi < filterSegments.length) {\n const fseg = filterSegments[fi];\n if (fseg === '**') {\n // Match zero or more path segments. The next filter segment must match\n // somewhere in the remaining path, or we accept the tail.\n if (fi === filterSegments.length - 1) return true;\n const remaining = filterSegments.slice(fi + 1);\n for (let k = pi; k <= pathSegments.length; k++) {\n if (matchPath(pathSegments.slice(k).join('.'), remaining.join('.'))) {\n return true;\n }\n }\n return false;\n }\n if (pi >= pathSegments.length) return false;\n const pseg = pathSegments[pi];\n if (fseg === '*') {\n // pass\n } else if (fseg !== pseg) {\n return false;\n }\n pi++;\n fi++;\n }\n return pi === pathSegments.length;\n}\n","import type { Project, TokenMap } from '@unpunnyfuns/swatchbook-core';\nimport { emitAxisProjectedCss } from '@unpunnyfuns/swatchbook-core';\nimport { fuzzyFilter, fuzzyMatches } from '@unpunnyfuns/swatchbook-core/fuzzy';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { computeContrast } from '#/contrast.ts';\nimport { formatColorEveryWay } from '#/format-color.ts';\nimport { matchPath } from '#/match.ts';\n\n/**\n * Build a swatchbook MCP server bound to a loaded project. Tools expose the\n * project's tokens, axes, and diagnostics so an AI agent can query them\n * without running Storybook. Tool handlers close over a live `project`\n * reference — call the returned `setProject` to swap in a freshly loaded\n * project (e.g. after token edits) without re-binding the transport.\n */\nexport function createServer(initial: Project): McpServer & {\n setProject: (next: Project) => void;\n} {\n let project = initial;\n // Reverse lookup `themeName → axisTuple`, refreshed on each\n // `setProject` swap. Tool handlers that accept a `theme` name\n // parameter use this to map it to a tuple in O(1) before calling\n // `project.resolveAt(tuple)`. The set carries the default tuple +\n // every singleton (one per non-default cell on each axis) + every\n // preset — enumerated from `axes` + `presets` + `defaultTuple`.\n let tupleByName = buildTupleByName(initial);\n let defaultThemeName = tupleToName(initial.axes, initial.defaultTuple);\n const server = new McpServer(\n {\n name: '@unpunnyfuns/swatchbook-mcp',\n version: project.config.cssVarPrefix ? `project:${project.config.cssVarPrefix}` : 'project',\n },\n {\n instructions:\n 'Query a swatchbook DTCG project: list tokens by path glob, inspect individual tokens (value, $type, alias chain, per-theme resolved values), read axes / presets, and inspect diagnostics.',\n },\n ) as McpServer & { setProject: (next: Project) => void };\n server.setProject = (next: Project) => {\n project = next;\n tupleByName = buildTupleByName(next);\n defaultThemeName = tupleToName(next.axes, next.defaultTuple);\n };\n\n /**\n * Resolve tokens for a `theme` name parameter. Falls back to the\n * project's default tuple when the name doesn't match any known\n * theme (also covers the no-theme-provided path).\n */\n const tokensForTheme = (themeName: string | undefined): TokenMap => {\n const tuple =\n (themeName !== undefined ? tupleByName.get(themeName) : undefined) ?? project.defaultTuple;\n return project.resolveAt(tuple);\n };\n\n /**\n * Iterate every `(themeName, tuple)` pair the project surfaces —\n * default + singletons + presets. Order is insertion order of\n * `tupleByName` (default first, then singletons per axis, then presets).\n */\n const eachTheme = function* (): Generator<{ name: string; tuple: Record<string, string> }> {\n for (const [name, tuple] of tupleByName) yield { name, tuple };\n };\n\n server.registerTool(\n 'describe_project',\n {\n description:\n 'High-level summary of the project — total token count per theme, axes (with contexts) and how they compose, preset list, diagnostic counts by severity, css-var prefix, and the DTCG `$type`s present. Good first call for an agent that needs an orientation before querying specifics.',\n inputSchema: {},\n },\n () => {\n const typeCounts: Record<string, number> = {};\n const tokensPerTheme: Record<string, number> = {};\n for (const { name, tuple } of eachTheme()) {\n const tokens = project.resolveAt(tuple);\n tokensPerTheme[name] = Object.keys(tokens).length;\n for (const token of Object.values(tokens)) {\n if (token.$type) typeCounts[token.$type] = (typeCounts[token.$type] ?? 0) + 1;\n }\n }\n const diagBySeverity = { error: 0, warn: 0, info: 0 } as Record<string, number>;\n for (const d of project.diagnostics) {\n diagBySeverity[d.severity] = (diagBySeverity[d.severity] ?? 0) + 1;\n }\n return jsonResult({\n cssVarPrefix: project.config.cssVarPrefix ?? '',\n axes: project.axes.map((a) => ({ name: a.name, contexts: a.contexts, default: a.default })),\n themes: [...tupleByName.keys()],\n defaultTheme: defaultThemeName,\n presets: project.presets.map((p) => p.name),\n tokensPerTheme,\n types: typeCounts,\n diagnostics: {\n counts: diagBySeverity,\n total: project.diagnostics.length,\n },\n });\n },\n );\n\n server.registerTool(\n 'emit_css',\n {\n description:\n 'Return the full project CSS — a `:root` baseline + per-axis singleton cells (`[data-<prefix>-<axis>=\"<context>\"]`) + compound joint-override blocks for tokens whose value at a multi-axis combination diverges from cascade composition + a trailing chrome alias block. Same output the addon injects into Storybook and the docs-site chrome pipeline writes to disk. Useful when an agent needs to inline the stylesheet into a generated artifact.',\n inputSchema: {},\n },\n () => textResult(emitAxisProjectedCss(project)),\n );\n\n server.registerTool(\n 'list_tokens',\n {\n description:\n 'List token paths in the project, optionally filtered by path glob (`color.*`, `color.palette.**`) and/or DTCG `$type` (color, dimension, typography, …). Returns path + $type + stringified value from the default theme. Use this first to discover what tokens exist; follow with get_token for details.',\n inputSchema: {\n filter: z\n .string()\n .optional()\n .describe('Dot-path glob, e.g. `color.*` or `color.palette.**`. Omit for all tokens.'),\n type: z\n .string()\n .optional()\n .describe('DTCG `$type` to scope the result, e.g. `color`, `dimension`, `typography`.'),\n theme: z\n .string()\n .optional()\n .describe('Theme name to read values from. Defaults to the project default theme.'),\n },\n },\n ({ filter, type, theme }) => {\n const themeName = theme ?? defaultThemeName;\n const tokens = tokensForTheme(themeName);\n const rows: { path: string; type?: string; value: string }[] = [];\n for (const [path, token] of Object.entries(tokens)) {\n if (type && token.$type !== type) continue;\n if (!matchPath(path, filter)) continue;\n rows.push({\n path,\n ...(token.$type !== undefined && { type: token.$type }),\n value: stringifyValue(token.$value),\n });\n }\n rows.sort((a, b) => a.path.localeCompare(b.path, undefined, { numeric: true }));\n return jsonResult({ theme: themeName, count: rows.length, tokens: rows });\n },\n );\n\n server.registerTool(\n 'get_token',\n {\n description:\n 'Get full details for a single token: resolved value in every theme, DTCG `$type`, `$description`, alias chain, aliased-by list, and CSS var reference. Use after `list_tokens` to inspect a specific path.',\n inputSchema: {\n path: z.string().describe('Dot-path of the token, e.g. `color.accent.bg`.'),\n },\n },\n ({ path }) => {\n const perTheme: Record<\n string,\n { value: string; aliasOf?: string; aliasChain?: readonly string[] }\n > = {};\n let type: string | undefined;\n let description: string | undefined;\n let aliasedBy: readonly string[] | undefined;\n let found = false;\n\n for (const { name, tuple } of eachTheme()) {\n const token = project.resolveAt(tuple)[path];\n if (!token) continue;\n found = true;\n type ??= token.$type;\n description ??= token.$description;\n aliasedBy ??= token.aliasedBy;\n perTheme[name] = {\n value: stringifyValue(token.$value),\n ...(token.aliasOf !== undefined && { aliasOf: token.aliasOf }),\n ...(token.aliasChain !== undefined && { aliasChain: token.aliasChain }),\n };\n }\n\n if (!found) return textResult(`Token not found: ${path}`);\n\n const prefix = project.config.cssVarPrefix ?? '';\n const cssVar = `var(--${prefix ? `${prefix}-` : ''}${path.replaceAll('.', '-')})`;\n\n return jsonResult({\n path,\n type,\n description,\n cssVar,\n aliasedBy,\n perTheme,\n });\n },\n );\n\n server.registerTool(\n 'list_axes',\n {\n description:\n 'List the project axes — each axis has a name, its contexts (discrete values like `Light` / `Dark`), a default, and a source (`resolver` for DTCG-resolver-driven, `layered` for authored layered axes, `synthetic` for single-theme projects). Also returns the named themes (one per default tuple + per-axis non-default singleton + preset) and any presets defined in the project config.',\n inputSchema: {},\n },\n () =>\n jsonResult({\n axes: project.axes.map((axis) => ({\n name: axis.name,\n contexts: axis.contexts,\n default: axis.default,\n description: axis.description,\n source: axis.source,\n })),\n disabledAxes: project.disabledAxes,\n themes: [...eachTheme()].map(({ name, tuple }) => ({ name, input: tuple })),\n presets: project.presets.map((p) => ({\n name: p.name,\n axes: p.axes,\n description: p.description,\n })),\n }),\n );\n\n server.registerTool(\n 'get_alias_chain',\n {\n description:\n 'Forward alias chain for a token — the sequence of paths it resolves through on the way to a primitive value (e.g. `color.accent.bg → color.brand.blue.700 → color.palette.blue.700`). Returns the chain per theme because aliases can resolve through different paths per axis context. Empty chain when the token is a primitive (no aliases) or missing.',\n inputSchema: {\n path: z.string().describe('Dot-path of the token, e.g. `color.accent.bg`.'),\n },\n },\n ({ path }) => {\n const perTheme: Record<string, { aliasOf?: string; chain: readonly string[] }> = {};\n let found = false;\n for (const { name, tuple } of eachTheme()) {\n const token = project.resolveAt(tuple)[path];\n if (!token) continue;\n found = true;\n const chain: string[] = [path];\n if (token.aliasChain && token.aliasChain.length > 0) chain.push(...token.aliasChain);\n else if (token.aliasOf) chain.push(token.aliasOf);\n perTheme[name] = {\n ...(token.aliasOf !== undefined && { aliasOf: token.aliasOf }),\n chain,\n };\n }\n if (!found) return textResult(`Token not found: ${path}`);\n return jsonResult({ path, perTheme });\n },\n );\n\n server.registerTool(\n 'get_aliased_by',\n {\n description:\n 'Backward alias tree for a token — every token that resolves through this path at any depth. Breadth-first walk with cycle protection; `maxDepth` caps recursion (default 6). Empty when nothing aliases the token.',\n inputSchema: {\n path: z.string().describe('Dot-path of the token, e.g. `color.palette.blue.500`.'),\n maxDepth: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('Maximum recursion depth. Default 6.'),\n },\n },\n ({ path, maxDepth }) => {\n const depth = maxDepth ?? 6;\n const tokens = tokensForTheme(defaultThemeName);\n if (!tokens[path]) return textResult(`Token not found: ${path}`);\n\n interface Node {\n path: string;\n depth: number;\n children: Node[];\n truncated?: boolean;\n }\n const visited = new Set<string>([path]);\n const walk = (current: string, d: number): Node => {\n const tok = tokens[current];\n const direct = tok?.aliasedBy ?? [];\n if (direct.length === 0) return { path: current, depth: d, children: [] };\n if (d >= depth) return { path: current, depth: d, children: [], truncated: true };\n const children: Node[] = [];\n for (const p of direct) {\n if (visited.has(p)) continue;\n visited.add(p);\n children.push(walk(p, d + 1));\n }\n return { path: current, depth: d, children };\n };\n const root = walk(path, 0);\n return jsonResult(root);\n },\n );\n\n server.registerTool(\n 'get_color_formats',\n {\n description:\n \"For a color token, return its value rendered in every format the addon toolbar exposes — `hex`, `rgb`, `hsl`, `oklch`, and the raw JSON. Each entry carries an `outOfGamut` flag when the chosen colorspace can't losslessly represent the token (wide-gamut tokens rendered in sRGB, for example). Skips non-color tokens.\",\n inputSchema: {\n path: z.string().describe('Dot-path of a color token, e.g. `color.accent.bg`.'),\n theme: z\n .string()\n .optional()\n .describe('Theme name to read the value from. Defaults to the project default theme.'),\n },\n },\n ({ path, theme }) => {\n const themeName = theme ?? defaultThemeName;\n const token = tokensForTheme(themeName)[path];\n if (!token) return textResult(`Token not found: ${path}`);\n if (token.$type !== 'color') {\n return textResult(`Token ${path} is not a color (got $type=${token.$type ?? 'unknown'}).`);\n }\n return jsonResult({\n path,\n theme: themeName,\n formats: formatColorEveryWay(token.$value),\n });\n },\n );\n\n server.registerTool(\n 'get_color_contrast',\n {\n description:\n 'Compute the contrast between two color tokens for a given theme. WCAG 2.1 returns the ratio (1–21) plus AA/AAA pass flags for normal + large text; APCA returns the signed Lc value plus body / large-text / non-text pass flags (absolute-value thresholds 75 / 60 / 45). Use this when reasoning about text legibility, focus-ring visibility, border contrast, etc., without having to reimplement the luminance math in the agent. Per-theme so the same pair can be checked against Light, Dark, High-contrast, etc.',\n inputSchema: {\n foreground: z\n .string()\n .describe('Dot-path of the foreground color token, e.g. `color.text.default`.'),\n background: z\n .string()\n .describe('Dot-path of the background color token, e.g. `color.surface.default`.'),\n theme: z.string().optional().describe('Theme name. Defaults to the project default theme.'),\n algorithm: z\n .enum(['wcag21', 'apca'])\n .optional()\n .describe(\n 'Contrast algorithm. `wcag21` is the classic 4.5:1 ratio; `apca` is the perceptually-weighted Silver-draft successor. Defaults to `wcag21`.',\n ),\n },\n },\n ({ foreground, background, theme, algorithm }) => {\n const themeName = theme ?? defaultThemeName;\n const tokens = tokensForTheme(themeName);\n const fgTok = tokens[foreground];\n const bgTok = tokens[background];\n if (!fgTok) return textResult(`Foreground token not found: ${foreground}`);\n if (!bgTok) return textResult(`Background token not found: ${background}`);\n if (fgTok.$type !== 'color') {\n return textResult(\n `Foreground ${foreground} is not a color (got $type=${fgTok.$type ?? 'unknown'}).`,\n );\n }\n if (bgTok.$type !== 'color') {\n return textResult(\n `Background ${background} is not a color (got $type=${bgTok.$type ?? 'unknown'}).`,\n );\n }\n const result = computeContrast(fgTok.$value, bgTok.$value, algorithm ?? 'wcag21');\n if (!result) {\n return textResult(\n 'Could not compute contrast — one or both values failed to parse as a color.',\n );\n }\n return jsonResult({\n theme: themeName,\n foreground: { path: foreground, value: stringifyValue(fgTok.$value) },\n background: { path: background, value: stringifyValue(bgTok.$value) },\n ...result,\n });\n },\n );\n\n server.registerTool(\n 'get_axis_variance',\n {\n description:\n \"Classify how a token's resolved value depends on the project's axes. Returns `kind` — `constant` (same across every tuple), `single` (varies with exactly one axis, e.g. mode only), or `multi` (varies across two or more axes). Also returns `varyingAxes` / `constantAcrossAxes` plus a `perAxis` breakdown with each context's stringified value (holding other axes at their defaults). Use when reasoning about whether a token is theme-independent, whether a refactor changed an axis dependency, or to confirm that (say) a role token only varies with `contrast`.\",\n inputSchema: {\n path: z.string().describe('Dot-path of the token to analyse, e.g. `color.text.default`.'),\n },\n },\n ({ path }) => {\n const cached = project.varianceByPath.get(path);\n if (!cached) return textResult(`Token not found in any theme: ${path}`);\n return jsonResult(cached);\n },\n );\n\n server.registerTool(\n 'search_tokens',\n {\n description:\n 'Fuzzy search across token paths, `$description`, and stringified values. Case-insensitive, tolerates a single-character typo per term, and accepts out-of-order terms (`\"blue palette\"` finds `color.palette.blue.500`). Returns matches ranked by relevance with a short snippet pointing at where the match hit. Use when you know what you want but not the exact path. Scopes to a single theme (default: project default).',\n inputSchema: {\n query: z.string().min(1).describe('Fuzzy query (case-insensitive).'),\n theme: z\n .string()\n .optional()\n .describe('Theme name to search within. Defaults to the project default.'),\n limit: z.number().int().positive().optional().describe('Cap the result count. Default 50.'),\n },\n },\n ({ query, theme, limit }) => {\n const themeName = theme ?? defaultThemeName;\n const tokens = tokensForTheme(themeName);\n const max = limit ?? 50;\n\n const candidates = Object.entries(tokens).map(([path, token]) => {\n const description = token.$description ?? '';\n const value = stringifyValue(token.$value);\n return { path, token, description, value, composite: `${path} ${description} ${value}` };\n });\n const ranked = fuzzyFilter(candidates, query, (c) => c.composite, { limit: max });\n const hits = ranked.map((c) => {\n const matchedIn: ('path' | 'description' | 'value')[] = [];\n if (fuzzyMatches(c.path, query)) matchedIn.push('path');\n if (c.description && fuzzyMatches(c.description, query)) matchedIn.push('description');\n if (fuzzyMatches(c.value, query)) matchedIn.push('value');\n if (matchedIn.length === 0) matchedIn.push('path');\n const snippet = matchedIn.includes('description')\n ? (c.token.$description ?? c.path)\n : matchedIn.includes('value')\n ? `${c.path} = ${c.value}`\n : c.path;\n const entry: { path: string; type?: string; matchedIn: typeof matchedIn; snippet: string } =\n {\n path: c.path,\n matchedIn,\n snippet,\n };\n if (c.token.$type !== undefined) entry.type = c.token.$type;\n return entry;\n });\n return jsonResult({\n query,\n theme: themeName,\n count: hits.length,\n truncated: hits.length === max,\n hits,\n });\n },\n );\n\n server.registerTool(\n 'resolve_theme',\n {\n description:\n 'Resolve the full token map for a given axis tuple. Agent passes a partial tuple (`{ mode: \"Dark\", brand: \"Brand A\" }`); any axis omitted falls back to that axis\\'s default. Returns the matching theme name, the complete tuple after filling defaults, and the resolved `{ path: { value, type, aliasOf?, aliasChain? } }` map — effectively \"what do all tokens look like if I pin this combination\".',\n inputSchema: {\n tuple: z\n .record(z.string(), z.string())\n .describe('Partial axis tuple, e.g. `{ mode: \"Dark\", brand: \"Brand A\" }`.'),\n filter: z.string().optional().describe('Optional path glob to scope the returned map.'),\n type: z.string().optional().describe('Optional DTCG `$type` to scope the returned map.'),\n },\n },\n ({ tuple, filter, type }) => {\n const active: Record<string, string> = {};\n for (const axis of project.axes) {\n const candidate = tuple[axis.name];\n active[axis.name] =\n candidate && axis.contexts.includes(candidate) ? candidate : axis.default;\n }\n const themeName = tupleToName(project.axes, active);\n const tokens = project.resolveAt(active);\n const resolved: Record<\n string,\n { value: string; type?: string; aliasOf?: string; aliasChain?: readonly string[] }\n > = {};\n let count = 0;\n for (const [path, token] of Object.entries(tokens)) {\n if (type && token.$type !== type) continue;\n if (!matchPath(path, filter)) continue;\n resolved[path] = {\n value: stringifyValue(token.$value),\n ...(token.$type !== undefined && { type: token.$type }),\n ...(token.aliasOf !== undefined && { aliasOf: token.aliasOf }),\n ...(token.aliasChain !== undefined && { aliasChain: token.aliasChain }),\n };\n count++;\n }\n return jsonResult({ theme: themeName, tuple: active, count, tokens: resolved });\n },\n );\n\n server.registerTool(\n 'get_consumer_output',\n {\n description:\n 'CSS var reference + resolved value + HTML data-attribute activation for a token under an optional axis tuple. Tells an agent everything it needs to write a stylesheet or JSX snippet that pins a particular theme combination — `selector` is the compound CSS selector that matches the tuple on `<html>`, `attrs` is the same information as HTML attributes, `cssVar` is the `var(--…)` reference. Tuple defaults to the project default when omitted.',\n inputSchema: {\n path: z.string().describe('Dot-path of the token, e.g. `color.accent.bg`.'),\n tuple: z\n .record(z.string(), z.string())\n .optional()\n .describe(\n 'Optional axis tuple (e.g. `{ mode: \"Dark\", brand: \"Brand A\" }`). Defaults to each axis\\'s own default.',\n ),\n },\n },\n ({ path, tuple }) => {\n const prefix = project.config.cssVarPrefix ?? '';\n const activeTuple: Record<string, string> = {};\n for (const axis of project.axes) {\n const candidate = tuple?.[axis.name];\n activeTuple[axis.name] =\n candidate && axis.contexts.includes(candidate) ? candidate : axis.default;\n }\n const themeName = tupleToName(project.axes, activeTuple);\n const token = project.resolveAt(activeTuple)[path];\n if (!token) return textResult(`Token not found: ${path}`);\n\n const cssVar = `var(--${prefix ? `${prefix}-` : ''}${path.replaceAll('.', '-')})`;\n const attrName = (axis: string): string =>\n prefix ? `data-${prefix}-${axis}` : `data-${axis}`;\n const attrs: Record<string, string> = {};\n const selectorParts: string[] = [];\n for (const axis of project.axes) {\n const value = activeTuple[axis.name];\n if (value !== undefined) {\n attrs[attrName(axis.name)] = value;\n selectorParts.push(`[${attrName(axis.name)}=\"${value}\"]`);\n }\n }\n\n return jsonResult({\n path,\n cssVar,\n value: stringifyValue(token.$value),\n type: token.$type,\n theme: themeName,\n tuple: activeTuple,\n attrs,\n selector: selectorParts.join('') || ':root',\n usageSnippet: `color: ${cssVar};`,\n });\n },\n );\n\n server.registerTool(\n 'get_diagnostics',\n {\n description:\n 'List parser / resolver / validation diagnostics for the project. Each entry carries a severity (`error`, `warn`, `info`), group, message, and optional filename / line / column for locating the issue.',\n inputSchema: {\n severity: z\n .enum(['error', 'warn', 'info'])\n .optional()\n .describe('Optional severity filter. Omit for all diagnostics.'),\n },\n },\n ({ severity }) => {\n const rows = severity\n ? project.diagnostics.filter((d) => d.severity === severity)\n : project.diagnostics;\n return jsonResult({ count: rows.length, diagnostics: rows });\n },\n );\n\n return server;\n}\n\n/**\n * Build a `Map<themeName, axisTuple>` covering the same set the\n * resolver loader's singleton enumeration produces: default tuple +\n * one per non-default cell on each axis + each preset. Bounded by\n * `1 + Σ(axes × (contexts - 1)) + presets.length` — linear in axis\n * cardinality, independent of the cartesian product.\n */\nfunction buildTupleByName(project: Project): Map<string, Record<string, string>> {\n const out = new Map<string, Record<string, string>>();\n const defaultName = tupleToName(project.axes, project.defaultTuple);\n out.set(defaultName, { ...project.defaultTuple });\n for (const axis of project.axes) {\n for (const ctx of axis.contexts) {\n if (ctx === axis.default) continue;\n const tuple = { ...project.defaultTuple, [axis.name]: ctx };\n out.set(tupleToName(project.axes, tuple), tuple);\n }\n }\n for (const preset of project.presets) {\n const tuple: Record<string, string> = { ...project.defaultTuple };\n for (const [axis, ctx] of Object.entries(preset.axes)) {\n if (ctx !== undefined) tuple[axis] = ctx;\n }\n out.set(tupleToName(project.axes, tuple), tuple);\n }\n return out;\n}\n\n/**\n * Synthesize the tuple's stable name — axis values joined by ` · ` in\n * axis order. Inlined here rather than imported so MCP doesn't depend\n * on `permutationID` staying in core's public API.\n */\nfunction tupleToName(\n axes: readonly { name: string; default: string }[],\n tuple: Readonly<Record<string, string>>,\n): string {\n return axes.map((a) => tuple[a.name] ?? a.default).join(' · ');\n}\n\nfunction stringifyValue(value: unknown): string {\n if (value === null || value === undefined) return '—';\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\nfunction textResult(text: string): { content: { type: 'text'; text: string }[] } {\n return { content: [{ type: 'text', text }] };\n}\n\nfunction jsonResult(data: unknown): { content: { type: 'text'; text: string }[] } {\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(data, null, 2),\n },\n ],\n };\n}\n","import { extname, isAbsolute, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { Config, Project } from '@unpunnyfuns/swatchbook-core';\nimport { loadProject } from '@unpunnyfuns/swatchbook-core';\nimport { createJiti } from 'jiti';\n\n/**\n * Load a swatchbook project from either a full config module or a bare\n * DTCG resolver JSON.\n *\n * `.ts` / `.mts` / `.js` / `.mjs` → jiti imports the module and uses\n * its default export as the swatchbook {@link Config}.\n *\n * `.json` → treated as a DTCG resolver file; the CLI constructs a\n * minimal `{ resolver: path }` config so agents can point at a raw\n * resolver without authoring a wrapper. Every other config option\n * (presets, chrome map, disabled axes, css-var prefix) falls back to\n * `loadProject` defaults.\n *\n * The `cwd` returned is the directory the config / resolver lives in.\n * `loadProject` uses it to resolve relative token references.\n */\nexport async function loadFromConfig(\n configPath: string,\n cwdOverride?: string,\n): Promise<{ project: Project; cwd: string; config: Config }> {\n const absolute = isAbsolute(configPath) ? configPath : resolve(process.cwd(), configPath);\n const cwd = cwdOverride ?? resolve(absolute, '..');\n const ext = extname(absolute).toLowerCase();\n\n const config =\n ext === '.json' ? ({ resolver: absolute } satisfies Config) : await loadTsConfig(absolute);\n\n const project = await loadProject(config, cwd);\n return { project, cwd, config };\n}\n\nasync function loadTsConfig(absolute: string): Promise<Config> {\n /**\n * jiti's first arg is a directory-shaped \"from\" URL it uses to resolve\n * the target's relative imports. Passing a file URL leaves jiti\n * unsure whether to treat the path as a dir, which on some Node\n * versions falls through to a plain JSON read and surfaces as\n * `Unexpected token 'i', \"import { d\"...`. A trailing slash on a\n * directory URL avoids the ambiguity.\n */\n const fromUrl = new URL('./', pathToFileURL(absolute));\n const jiti = createJiti(fromUrl.href, {\n interopDefault: true,\n moduleCache: false,\n });\n return (await jiti.import(absolute, { default: true })) as Config;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unpunnyfuns/swatchbook-mcp",
3
- "version": "0.55.0",
3
+ "version": "0.56.0",
4
4
  "description": "Model Context Protocol server for swatchbook — exposes a DTCG project's tokens, axes, and diagnostics to AI agents without running Storybook.",
5
5
  "license": "MIT",
6
6
  "author": "unpunnyfuns <unpunnyfuns@gmail.com>",
@@ -51,7 +51,7 @@
51
51
  "colorjs.io": "0.6.1",
52
52
  "jiti": "^2.4.0",
53
53
  "zod": "^3.23.8",
54
- "@unpunnyfuns/swatchbook-core": "0.55.0"
54
+ "@unpunnyfuns/swatchbook-core": "0.56.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@modelcontextprotocol/inspector": "^0.21.2",
@@ -1 +0,0 @@
1
- {"version":3,"file":"load-config-Bs56f0SF.mjs","names":[],"sources":["../src/contrast.ts","../src/format-color.ts","../src/match.ts","../src/server.ts","../src/load-config.ts"],"sourcesContent":["import Color from 'colorjs.io';\n\n/**\n * Pair-wise contrast between two DTCG color `$value`s. Wraps\n * `colorjs.io`'s contrast primitives with pass/fail grids for the\n * thresholds agents typically reason against (WCAG 2.1 AA/AAA\n * normal/large; APCA body / large text / non-text).\n *\n * Kept separate from `format-color.ts` even though both build\n * `Color` instances from the same DTCG shape — the color-format\n * formatter stringifies, this one does arithmetic, and they're\n * called from different tool paths.\n */\n\ninterface ColorInput {\n colorSpace?: string;\n components?: readonly (number | null)[];\n channels?: readonly (number | null)[];\n alpha?: number;\n}\n\nfunction toColor(raw: unknown): Color | null {\n if (!raw || typeof raw !== 'object') return null;\n const n = raw as ColorInput;\n const components = n.components ?? n.channels;\n if (!components || !n.colorSpace) return null;\n const coords = components.map((c) => (c == null ? 0 : c));\n const [r = 0, g = 0, b = 0] = coords;\n try {\n return new Color(n.colorSpace, [r, g, b], n.alpha ?? 1);\n } catch {\n return null;\n }\n}\n\nexport type ContrastAlgorithm = 'wcag21' | 'apca';\n\nexport interface WcagPasses {\n aa: { normal: boolean; large: boolean };\n aaa: { normal: boolean; large: boolean };\n}\n\nexport interface ApcaPasses {\n lc: number;\n body: boolean;\n largeText: boolean;\n nonText: boolean;\n}\n\nexport interface ContrastResult {\n algorithm: ContrastAlgorithm;\n /**\n * WCAG 2.1 returns the contrast ratio (1–21). APCA returns a signed\n * Lc value (-108..+106); the sign marks polarity (negative = dark\n * text on light background; positive = light on dark). Consumers\n * usually care about `Math.abs(ratio)` for threshold checks.\n */\n ratio: number;\n wcag?: WcagPasses;\n apca?: ApcaPasses;\n}\n\nexport function computeContrast(\n foreground: unknown,\n background: unknown,\n algorithm: ContrastAlgorithm = 'wcag21',\n): ContrastResult | null {\n const fg = toColor(foreground);\n const bg = toColor(background);\n if (!fg || !bg) return null;\n\n if (algorithm === 'wcag21') {\n const ratio = fg.contrast(bg, 'WCAG21');\n return {\n algorithm: 'wcag21',\n ratio,\n wcag: {\n aa: { normal: ratio >= 4.5, large: ratio >= 3 },\n aaa: { normal: ratio >= 7, large: ratio >= 4.5 },\n },\n };\n }\n\n /**\n * APCA thresholds from the Silver draft guidance. These are the\n * \"body text / large text / non-text\" bronze tier thresholds most\n * tooling settles on; the full table has more gradations but\n * agents rarely need them. Non-text = icons, focus rings, UI\n * borders.\n */\n const lc = fg.contrast(bg, 'APCA');\n const absLc = Math.abs(lc);\n return {\n algorithm: 'apca',\n ratio: lc,\n apca: {\n lc,\n body: absLc >= 75,\n largeText: absLc >= 60,\n nonText: absLc >= 45,\n },\n };\n}\n","import Color from 'colorjs.io';\n\n/**\n * Convert a DTCG color `$value` into the same format menu the\n * addon's toolbar exposes — hex / rgb / hsl / oklch / raw JSON. Mirrors\n * the narrower version in `@unpunnyfuns/swatchbook-blocks/format-color.ts`\n * without pulling blocks (and React) into the MCP server.\n *\n * Out-of-gamut colours still stringify — the caller gets both the\n * rendered string and an `outOfGamut` flag so agents can warn.\n */\n\nexport type ColorFormat = 'hex' | 'rgb' | 'hsl' | 'oklch' | 'raw';\n\nexport interface FormatColorResult {\n format: ColorFormat;\n value: string;\n outOfGamut: boolean;\n}\n\ninterface NormalizedColor {\n colorSpace?: string;\n components?: readonly (number | null)[];\n channels?: readonly (number | null)[];\n alpha?: number;\n hex?: string;\n}\n\nexport function formatColor(raw: unknown, format: ColorFormat): FormatColorResult | null {\n if (!raw || typeof raw !== 'object') return null;\n const normalized = raw as NormalizedColor;\n\n if (format === 'raw') {\n return { format, value: JSON.stringify(raw), outOfGamut: false };\n }\n\n const components = normalized.components ?? normalized.channels;\n if (!components || !normalized.colorSpace) return null;\n\n let color: Color;\n try {\n const coords = components.map((c) => (c == null ? 0 : c));\n const [r = 0, g = 0, b = 0] = coords;\n color = new Color(normalized.colorSpace, [r, g, b], normalized.alpha ?? 1);\n } catch {\n return null;\n }\n\n if (format === 'hex') {\n try {\n const inSrgb = color.to('srgb');\n const outOfGamut = !inSrgb.inGamut();\n if (outOfGamut) {\n return { format, value: inSrgb.toString({ format: 'rgb' }), outOfGamut: true };\n }\n return { format, value: inSrgb.toString({ format: 'hex' }), outOfGamut: false };\n } catch {\n return null;\n }\n }\n\n if (format === 'rgb') {\n try {\n const inSrgb = color.to('srgb');\n return {\n format,\n value: inSrgb.toString({ format: 'rgb' }),\n outOfGamut: !inSrgb.inGamut(),\n };\n } catch {\n return null;\n }\n }\n\n if (format === 'hsl') {\n try {\n const inHsl = color.to('hsl');\n return {\n format,\n value: inHsl.toString(),\n outOfGamut: !color.to('srgb').inGamut(),\n };\n } catch {\n return null;\n }\n }\n\n if (format === 'oklch') {\n try {\n const inOklch = color.to('oklch');\n return { format, value: inOklch.toString(), outOfGamut: false };\n } catch {\n return null;\n }\n }\n\n return null;\n}\n\nexport const ALL_COLOR_FORMATS: readonly ColorFormat[] = ['hex', 'rgb', 'hsl', 'oklch', 'raw'];\n\nexport function formatColorEveryWay(raw: unknown): Partial<Record<ColorFormat, FormatColorResult>> {\n const out: Partial<Record<ColorFormat, FormatColorResult>> = {};\n for (const format of ALL_COLOR_FORMATS) {\n const result = formatColor(raw, format);\n if (result) out[format] = result;\n }\n return out;\n}\n","/**\n * Minimal DTCG-flavoured path matcher. Accepts exact paths (`color.bg`), single-\n * segment globs (`color.*`), multi-segment globs (`color.**`), or a trailing `*`\n * mid-segment (`color.palette.blue.*`). No brace expansion, no regex — DTCG\n * paths are dot-delimited and this matches the parity the blocks' `globMatch`\n * ships. Case-sensitive.\n */\nexport function matchPath(path: string, filter: string | undefined): boolean {\n if (!filter) return true;\n if (filter === '**' || filter === '*') return true;\n\n const pathSegments = path.split('.');\n const filterSegments = filter.split('.');\n\n let pi = 0;\n let fi = 0;\n while (fi < filterSegments.length) {\n const fseg = filterSegments[fi];\n if (fseg === '**') {\n // Match zero or more path segments. The next filter segment must match\n // somewhere in the remaining path, or we accept the tail.\n if (fi === filterSegments.length - 1) return true;\n const remaining = filterSegments.slice(fi + 1);\n for (let k = pi; k <= pathSegments.length; k++) {\n if (matchPath(pathSegments.slice(k).join('.'), remaining.join('.'))) {\n return true;\n }\n }\n return false;\n }\n if (pi >= pathSegments.length) return false;\n const pseg = pathSegments[pi];\n if (fseg === '*') {\n // pass\n } else if (fseg !== pseg) {\n return false;\n }\n pi++;\n fi++;\n }\n return pi === pathSegments.length;\n}\n","import type { Project, TokenMap } from '@unpunnyfuns/swatchbook-core';\nimport { emitAxisProjectedCss } from '@unpunnyfuns/swatchbook-core';\nimport { fuzzyFilter, fuzzyMatches } from '@unpunnyfuns/swatchbook-core/fuzzy';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { computeContrast } from '#/contrast.ts';\nimport { formatColorEveryWay } from '#/format-color.ts';\nimport { matchPath } from '#/match.ts';\n\n/**\n * Build a swatchbook MCP server bound to a loaded project. Tools expose the\n * project's tokens, axes, and diagnostics so an AI agent can query them\n * without running Storybook. Tool handlers close over a live `project`\n * reference — call the returned `setProject` to swap in a freshly loaded\n * project (e.g. after token edits) without re-binding the transport.\n */\nexport function createServer(initial: Project): McpServer & {\n setProject: (next: Project) => void;\n} {\n let project = initial;\n // Reverse lookup `permutationName → axisTuple`, refreshed on each\n // `setProject` swap. Tool handlers that accept a `theme` name\n // parameter use this to map it to a tuple in O(1) before calling\n // `project.resolveAt(tuple)` — replaces the direct\n // `project.permutationsResolved[name]` indexing the tools used\n // before the cells migration.\n let tupleByName = buildTupleByName(initial);\n const server = new McpServer(\n {\n name: '@unpunnyfuns/swatchbook-mcp',\n version: project.config.cssVarPrefix ? `project:${project.config.cssVarPrefix}` : 'project',\n },\n {\n instructions:\n 'Query a swatchbook DTCG project: list tokens by path glob, inspect individual tokens (value, $type, alias chain, per-theme resolved values), read axes / presets, and inspect diagnostics.',\n },\n ) as McpServer & { setProject: (next: Project) => void };\n server.setProject = (next: Project) => {\n project = next;\n tupleByName = buildTupleByName(next);\n };\n\n /**\n * Resolve tokens for a `theme` name parameter. Falls back to the\n * project's default tuple when the name doesn't match any known\n * permutation (also covers the no-theme-provided path).\n */\n const tokensForTheme = (themeName: string | undefined): TokenMap => {\n const tuple =\n (themeName !== undefined ? tupleByName.get(themeName) : undefined) ?? project.defaultTuple;\n return project.resolveAt(tuple);\n };\n\n server.registerTool(\n 'describe_project',\n {\n description:\n 'High-level summary of the project — total token count per theme, axes (with contexts) and how they compose, preset list, diagnostic counts by severity, css-var prefix, and the DTCG `$type`s present. Good first call for an agent that needs an orientation before querying specifics.',\n inputSchema: {},\n },\n () => {\n const typeCounts: Record<string, number> = {};\n const tokensPerTheme: Record<string, number> = {};\n for (const theme of project.permutations) {\n const tokens = project.resolveAt(theme.input as Record<string, string>);\n tokensPerTheme[theme.name] = Object.keys(tokens).length;\n for (const token of Object.values(tokens)) {\n if (token.$type) typeCounts[token.$type] = (typeCounts[token.$type] ?? 0) + 1;\n }\n }\n const diagBySeverity = { error: 0, warn: 0, info: 0 } as Record<string, number>;\n for (const d of project.diagnostics) {\n diagBySeverity[d.severity] = (diagBySeverity[d.severity] ?? 0) + 1;\n }\n return jsonResult({\n cssVarPrefix: project.config.cssVarPrefix ?? '',\n axes: project.axes.map((a) => ({ name: a.name, contexts: a.contexts, default: a.default })),\n permutations: project.permutations.map((t) => t.name),\n defaultPermutation: project.permutations[0]?.name ?? null,\n presets: project.presets.map((p) => p.name),\n tokensPerTheme,\n types: typeCounts,\n diagnostics: {\n counts: diagBySeverity,\n total: project.diagnostics.length,\n },\n });\n },\n );\n\n server.registerTool(\n 'emit_css',\n {\n description:\n 'Return the full project CSS — a `:root` baseline + per-axis singleton cells (`[data-<prefix>-<axis>=\"<context>\"]`) + compound joint-override blocks for tokens whose value at a multi-axis combination diverges from cascade composition + a trailing chrome alias block. Same output the addon injects into Storybook and the docs-site chrome pipeline writes to disk. Useful when an agent needs to inline the stylesheet into a generated artifact.',\n inputSchema: {},\n },\n () => textResult(emitAxisProjectedCss(project)),\n );\n\n server.registerTool(\n 'list_tokens',\n {\n description:\n 'List token paths in the project, optionally filtered by path glob (`color.*`, `color.palette.**`) and/or DTCG `$type` (color, dimension, typography, …). Returns path + $type + stringified value from the default theme. Use this first to discover what tokens exist; follow with get_token for details.',\n inputSchema: {\n filter: z\n .string()\n .optional()\n .describe('Dot-path glob, e.g. `color.*` or `color.palette.**`. Omit for all tokens.'),\n type: z\n .string()\n .optional()\n .describe('DTCG `$type` to scope the result, e.g. `color`, `dimension`, `typography`.'),\n theme: z\n .string()\n .optional()\n .describe('Permutation name to read values from. Defaults to the project default theme.'),\n },\n },\n ({ filter, type, theme }) => {\n const themeName = theme ?? project.permutations[0]?.name;\n if (!themeName) {\n return textResult('No permutations in project.');\n }\n const tokens = tokensForTheme(themeName);\n const rows: { path: string; type?: string; value: string }[] = [];\n for (const [path, token] of Object.entries(tokens)) {\n if (type && token.$type !== type) continue;\n if (!matchPath(path, filter)) continue;\n rows.push({\n path,\n ...(token.$type !== undefined && { type: token.$type }),\n value: stringifyValue(token.$value),\n });\n }\n rows.sort((a, b) => a.path.localeCompare(b.path, undefined, { numeric: true }));\n return jsonResult({ theme: themeName, count: rows.length, tokens: rows });\n },\n );\n\n server.registerTool(\n 'get_token',\n {\n description:\n 'Get full details for a single token: resolved value in every theme, DTCG `$type`, `$description`, alias chain, aliased-by list, and CSS var reference. Use after `list_tokens` to inspect a specific path.',\n inputSchema: {\n path: z.string().describe('Dot-path of the token, e.g. `color.accent.bg`.'),\n },\n },\n ({ path }) => {\n const perTheme: Record<\n string,\n { value: string; aliasOf?: string; aliasChain?: readonly string[] }\n > = {};\n let type: string | undefined;\n let description: string | undefined;\n let aliasedBy: readonly string[] | undefined;\n let found = false;\n\n for (const theme of project.permutations) {\n const token = project.resolveAt(theme.input as Record<string, string>)[path];\n if (!token) continue;\n found = true;\n type ??= token.$type;\n description ??= token.$description;\n aliasedBy ??= token.aliasedBy;\n perTheme[theme.name] = {\n value: stringifyValue(token.$value),\n ...(token.aliasOf !== undefined && { aliasOf: token.aliasOf }),\n ...(token.aliasChain !== undefined && { aliasChain: token.aliasChain }),\n };\n }\n\n if (!found) return textResult(`Token not found: ${path}`);\n\n const prefix = project.config.cssVarPrefix ?? '';\n const cssVar = `var(--${prefix ? `${prefix}-` : ''}${path.replaceAll('.', '-')})`;\n\n return jsonResult({\n path,\n type,\n description,\n cssVar,\n aliasedBy,\n perTheme,\n });\n },\n );\n\n server.registerTool(\n 'list_axes',\n {\n description:\n 'List the project axes — each axis has a name, its contexts (discrete values like `Light` / `Dark`), a default, and a source (`resolver` for DTCG-resolver-driven, `layered` for authored layered axes, `synthetic` for single-theme projects). Also returns the named permutations (every axis tuple combination) and any presets defined in the project config.',\n inputSchema: {},\n },\n () =>\n jsonResult({\n axes: project.axes.map((axis) => ({\n name: axis.name,\n contexts: axis.contexts,\n default: axis.default,\n description: axis.description,\n source: axis.source,\n })),\n disabledAxes: project.disabledAxes,\n permutations: project.permutations.map((t) => ({ name: t.name, input: t.input })),\n presets: project.presets.map((p) => ({\n name: p.name,\n axes: p.axes,\n description: p.description,\n })),\n }),\n );\n\n server.registerTool(\n 'get_alias_chain',\n {\n description:\n 'Forward alias chain for a token — the sequence of paths it resolves through on the way to a primitive value (e.g. `color.accent.bg → color.brand.blue.700 → color.palette.blue.700`). Returns the chain per theme because aliases can resolve through different paths per axis context. Empty chain when the token is a primitive (no aliases) or missing.',\n inputSchema: {\n path: z.string().describe('Dot-path of the token, e.g. `color.accent.bg`.'),\n },\n },\n ({ path }) => {\n const perTheme: Record<string, { aliasOf?: string; chain: readonly string[] }> = {};\n let found = false;\n for (const theme of project.permutations) {\n const token = project.resolveAt(theme.input as Record<string, string>)[path];\n if (!token) continue;\n found = true;\n const chain: string[] = [path];\n if (token.aliasChain && token.aliasChain.length > 0) chain.push(...token.aliasChain);\n else if (token.aliasOf) chain.push(token.aliasOf);\n perTheme[theme.name] = {\n ...(token.aliasOf !== undefined && { aliasOf: token.aliasOf }),\n chain,\n };\n }\n if (!found) return textResult(`Token not found: ${path}`);\n return jsonResult({ path, perTheme });\n },\n );\n\n server.registerTool(\n 'get_aliased_by',\n {\n description:\n 'Backward alias tree for a token — every token that resolves through this path at any depth. Breadth-first walk with cycle protection; `maxDepth` caps recursion (default 6). Empty when nothing aliases the token.',\n inputSchema: {\n path: z.string().describe('Dot-path of the token, e.g. `color.palette.blue.500`.'),\n maxDepth: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('Maximum recursion depth. Default 6.'),\n },\n },\n ({ path, maxDepth }) => {\n const depth = maxDepth ?? 6;\n const themeName = project.permutations[0]?.name;\n if (!themeName) return textResult('No permutations in project.');\n const tokens = tokensForTheme(themeName);\n if (!tokens[path]) return textResult(`Token not found: ${path}`);\n\n interface Node {\n path: string;\n depth: number;\n children: Node[];\n truncated?: boolean;\n }\n const visited = new Set<string>([path]);\n const walk = (current: string, d: number): Node => {\n const tok = tokens[current];\n const direct = tok?.aliasedBy ?? [];\n if (direct.length === 0) return { path: current, depth: d, children: [] };\n if (d >= depth) return { path: current, depth: d, children: [], truncated: true };\n const children: Node[] = [];\n for (const p of direct) {\n if (visited.has(p)) continue;\n visited.add(p);\n children.push(walk(p, d + 1));\n }\n return { path: current, depth: d, children };\n };\n const root = walk(path, 0);\n return jsonResult(root);\n },\n );\n\n server.registerTool(\n 'get_color_formats',\n {\n description:\n \"For a color token, return its value rendered in every format the addon toolbar exposes — `hex`, `rgb`, `hsl`, `oklch`, and the raw JSON. Each entry carries an `outOfGamut` flag when the chosen colorspace can't losslessly represent the token (wide-gamut tokens rendered in sRGB, for example). Skips non-color tokens.\",\n inputSchema: {\n path: z.string().describe('Dot-path of a color token, e.g. `color.accent.bg`.'),\n theme: z\n .string()\n .optional()\n .describe(\n 'Permutation name to read the value from. Defaults to the project default theme.',\n ),\n },\n },\n ({ path, theme }) => {\n const themeName = theme ?? project.permutations[0]?.name;\n if (!themeName) return textResult('No permutations in project.');\n const token = tokensForTheme(themeName)[path];\n if (!token) return textResult(`Token not found: ${path}`);\n if (token.$type !== 'color') {\n return textResult(`Token ${path} is not a color (got $type=${token.$type ?? 'unknown'}).`);\n }\n return jsonResult({\n path,\n theme: themeName,\n formats: formatColorEveryWay(token.$value),\n });\n },\n );\n\n server.registerTool(\n 'get_color_contrast',\n {\n description:\n 'Compute the contrast between two color tokens for a given theme. WCAG 2.1 returns the ratio (1–21) plus AA/AAA pass flags for normal + large text; APCA returns the signed Lc value plus body / large-text / non-text pass flags (absolute-value thresholds 75 / 60 / 45). Use this when reasoning about text legibility, focus-ring visibility, border contrast, etc., without having to reimplement the luminance math in the agent. Per-theme so the same pair can be checked against Light, Dark, High-contrast, etc.',\n inputSchema: {\n foreground: z\n .string()\n .describe('Dot-path of the foreground color token, e.g. `color.text.default`.'),\n background: z\n .string()\n .describe('Dot-path of the background color token, e.g. `color.surface.default`.'),\n theme: z\n .string()\n .optional()\n .describe('Permutation name. Defaults to the project default theme.'),\n algorithm: z\n .enum(['wcag21', 'apca'])\n .optional()\n .describe(\n 'Contrast algorithm. `wcag21` is the classic 4.5:1 ratio; `apca` is the perceptually-weighted Silver-draft successor. Defaults to `wcag21`.',\n ),\n },\n },\n ({ foreground, background, theme, algorithm }) => {\n const themeName = theme ?? project.permutations[0]?.name;\n if (!themeName) return textResult('No permutations in project.');\n const tokens = tokensForTheme(themeName);\n const fgTok = tokens[foreground];\n const bgTok = tokens[background];\n if (!fgTok) return textResult(`Foreground token not found: ${foreground}`);\n if (!bgTok) return textResult(`Background token not found: ${background}`);\n if (fgTok.$type !== 'color') {\n return textResult(\n `Foreground ${foreground} is not a color (got $type=${fgTok.$type ?? 'unknown'}).`,\n );\n }\n if (bgTok.$type !== 'color') {\n return textResult(\n `Background ${background} is not a color (got $type=${bgTok.$type ?? 'unknown'}).`,\n );\n }\n const result = computeContrast(fgTok.$value, bgTok.$value, algorithm ?? 'wcag21');\n if (!result) {\n return textResult(\n 'Could not compute contrast — one or both values failed to parse as a color.',\n );\n }\n return jsonResult({\n theme: themeName,\n foreground: { path: foreground, value: stringifyValue(fgTok.$value) },\n background: { path: background, value: stringifyValue(bgTok.$value) },\n ...result,\n });\n },\n );\n\n server.registerTool(\n 'get_axis_variance',\n {\n description:\n \"Classify how a token's resolved value depends on the project's axes. Returns `kind` — `constant` (same across every tuple), `single` (varies with exactly one axis, e.g. mode only), or `multi` (varies across two or more axes). Also returns `varyingAxes` / `constantAcrossAxes` plus a `perAxis` breakdown with each context's stringified value (holding other axes at their defaults). Use when reasoning about whether a token is theme-independent, whether a refactor changed an axis dependency, or to confirm that (say) a role token only varies with `contrast`.\",\n inputSchema: {\n path: z.string().describe('Dot-path of the token to analyse, e.g. `color.text.default`.'),\n },\n },\n ({ path }) => {\n if (project.permutations.length === 0) return textResult('No permutations in project.');\n // `varianceByPath` covers every path that appears in any\n // permutation's resolved map; presence here is the same\n // existence signal the prior `permutations.some` scan provided.\n const cached = project.varianceByPath.get(path);\n if (!cached) return textResult(`Token not found in any theme: ${path}`);\n return jsonResult(cached);\n },\n );\n\n server.registerTool(\n 'search_tokens',\n {\n description:\n 'Fuzzy search across token paths, `$description`, and stringified values. Case-insensitive, tolerates a single-character typo per term, and accepts out-of-order terms (`\"blue palette\"` finds `color.palette.blue.500`). Returns matches ranked by relevance with a short snippet pointing at where the match hit. Use when you know what you want but not the exact path. Scopes to a single theme (default: project default).',\n inputSchema: {\n query: z.string().min(1).describe('Fuzzy query (case-insensitive).'),\n theme: z\n .string()\n .optional()\n .describe('Permutation name to search within. Defaults to the project default.'),\n limit: z.number().int().positive().optional().describe('Cap the result count. Default 50.'),\n },\n },\n ({ query, theme, limit }) => {\n const themeName = theme ?? project.permutations[0]?.name;\n if (!themeName) return textResult('No permutations in project.');\n const tokens = tokensForTheme(themeName);\n const max = limit ?? 50;\n\n const candidates = Object.entries(tokens).map(([path, token]) => {\n const description = token.$description ?? '';\n const value = stringifyValue(token.$value);\n return { path, token, description, value, composite: `${path} ${description} ${value}` };\n });\n const ranked = fuzzyFilter(candidates, query, (c) => c.composite, { limit: max });\n const hits = ranked.map((c) => {\n const matchedIn: ('path' | 'description' | 'value')[] = [];\n if (fuzzyMatches(c.path, query)) matchedIn.push('path');\n if (c.description && fuzzyMatches(c.description, query)) matchedIn.push('description');\n if (fuzzyMatches(c.value, query)) matchedIn.push('value');\n if (matchedIn.length === 0) matchedIn.push('path');\n const snippet = matchedIn.includes('description')\n ? (c.token.$description ?? c.path)\n : matchedIn.includes('value')\n ? `${c.path} = ${c.value}`\n : c.path;\n const entry: { path: string; type?: string; matchedIn: typeof matchedIn; snippet: string } =\n {\n path: c.path,\n matchedIn,\n snippet,\n };\n if (c.token.$type !== undefined) entry.type = c.token.$type;\n return entry;\n });\n return jsonResult({\n query,\n theme: themeName,\n count: hits.length,\n truncated: hits.length === max,\n hits,\n });\n },\n );\n\n server.registerTool(\n 'resolve_theme',\n {\n description:\n 'Resolve the full token map for a given axis tuple. Agent passes a partial tuple (`{ mode: \"Dark\", brand: \"Brand A\" }`); any axis omitted falls back to that axis\\'s default. Returns the matching theme name, the complete tuple after filling defaults, and the resolved `{ path: { value, type, aliasOf?, aliasChain? } }` map — effectively \"what do all tokens look like if I pin this combination\".',\n inputSchema: {\n tuple: z\n .record(z.string(), z.string())\n .describe('Partial axis tuple, e.g. `{ mode: \"Dark\", brand: \"Brand A\" }`.'),\n filter: z.string().optional().describe('Optional path glob to scope the returned map.'),\n type: z.string().optional().describe('Optional DTCG `$type` to scope the returned map.'),\n },\n },\n ({ tuple, filter, type }) => {\n const active: Record<string, string> = {};\n for (const axis of project.axes) {\n const candidate = tuple[axis.name];\n active[axis.name] =\n candidate && axis.contexts.includes(candidate) ? candidate : axis.default;\n }\n const themeName =\n project.permutations.find((t) => {\n for (const axis of project.axes) {\n if ((t.input as Record<string, string>)[axis.name] !== active[axis.name]) {\n return false;\n }\n }\n return true;\n })?.name ?? project.permutations[0]?.name;\n if (!themeName) return textResult('No matching theme.');\n const tokens = project.resolveAt(active);\n const resolved: Record<\n string,\n { value: string; type?: string; aliasOf?: string; aliasChain?: readonly string[] }\n > = {};\n let count = 0;\n for (const [path, token] of Object.entries(tokens)) {\n if (type && token.$type !== type) continue;\n if (!matchPath(path, filter)) continue;\n resolved[path] = {\n value: stringifyValue(token.$value),\n ...(token.$type !== undefined && { type: token.$type }),\n ...(token.aliasOf !== undefined && { aliasOf: token.aliasOf }),\n ...(token.aliasChain !== undefined && { aliasChain: token.aliasChain }),\n };\n count++;\n }\n return jsonResult({ theme: themeName, tuple: active, count, tokens: resolved });\n },\n );\n\n server.registerTool(\n 'get_consumer_output',\n {\n description:\n 'CSS var reference + resolved value + HTML data-attribute activation for a token under an optional axis tuple. Tells an agent everything it needs to write a stylesheet or JSX snippet that pins a particular theme combination — `selector` is the compound CSS selector that matches the tuple on `<html>`, `attrs` is the same information as HTML attributes, `cssVar` is the `var(--…)` reference. Tuple defaults to the project default when omitted.',\n inputSchema: {\n path: z.string().describe('Dot-path of the token, e.g. `color.accent.bg`.'),\n tuple: z\n .record(z.string(), z.string())\n .optional()\n .describe(\n 'Optional axis tuple (e.g. `{ mode: \"Dark\", brand: \"Brand A\" }`). Defaults to each axis\\'s own default.',\n ),\n },\n },\n ({ path, tuple }) => {\n const prefix = project.config.cssVarPrefix ?? '';\n const activeTuple: Record<string, string> = {};\n for (const axis of project.axes) {\n const candidate = tuple?.[axis.name];\n activeTuple[axis.name] =\n candidate && axis.contexts.includes(candidate) ? candidate : axis.default;\n }\n const themeName =\n project.permutations.find((t) => {\n for (const axis of project.axes) {\n if ((t.input as Record<string, string>)[axis.name] !== activeTuple[axis.name]) {\n return false;\n }\n }\n return true;\n })?.name ??\n project.permutations[0]?.name ??\n '';\n const token = project.resolveAt(activeTuple)[path];\n if (!token) return textResult(`Token not found: ${path}`);\n\n const cssVar = `var(--${prefix ? `${prefix}-` : ''}${path.replaceAll('.', '-')})`;\n const attrName = (axis: string): string =>\n prefix ? `data-${prefix}-${axis}` : `data-${axis}`;\n const attrs: Record<string, string> = {};\n const selectorParts: string[] = [];\n for (const axis of project.axes) {\n const value = activeTuple[axis.name];\n if (value !== undefined) {\n attrs[attrName(axis.name)] = value;\n selectorParts.push(`[${attrName(axis.name)}=\"${value}\"]`);\n }\n }\n\n return jsonResult({\n path,\n cssVar,\n value: stringifyValue(token.$value),\n type: token.$type,\n theme: themeName,\n tuple: activeTuple,\n attrs,\n selector: selectorParts.join('') || ':root',\n usageSnippet: `color: ${cssVar};`,\n });\n },\n );\n\n server.registerTool(\n 'get_diagnostics',\n {\n description:\n 'List parser / resolver / validation diagnostics for the project. Each entry carries a severity (`error`, `warn`, `info`), group, message, and optional filename / line / column for locating the issue.',\n inputSchema: {\n severity: z\n .enum(['error', 'warn', 'info'])\n .optional()\n .describe('Optional severity filter. Omit for all diagnostics.'),\n },\n },\n ({ severity }) => {\n const rows = severity\n ? project.diagnostics.filter((d) => d.severity === severity)\n : project.diagnostics;\n return jsonResult({ count: rows.length, diagnostics: rows });\n },\n );\n\n return server;\n}\n\n/**\n * Build a `Map<permutationName, axisTuple>` so the per-theme tool\n * handlers can avoid an `Array.find` scan per request. Bounded by the\n * permutation count.\n */\nfunction buildTupleByName(project: Project): Map<string, Record<string, string>> {\n const out = new Map<string, Record<string, string>>();\n for (const perm of project.permutations) {\n out.set(perm.name, perm.input as Record<string, string>);\n }\n return out;\n}\n\nfunction stringifyValue(value: unknown): string {\n if (value === null || value === undefined) return '—';\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\nfunction textResult(text: string): { content: { type: 'text'; text: string }[] } {\n return { content: [{ type: 'text', text }] };\n}\n\nfunction jsonResult(data: unknown): { content: { type: 'text'; text: string }[] } {\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(data, null, 2),\n },\n ],\n };\n}\n","import { extname, isAbsolute, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { Config, Project } from '@unpunnyfuns/swatchbook-core';\nimport { loadProject } from '@unpunnyfuns/swatchbook-core';\nimport { createJiti } from 'jiti';\n\n/**\n * Load a swatchbook project from either a full config module or a bare\n * DTCG resolver JSON.\n *\n * `.ts` / `.mts` / `.js` / `.mjs` → jiti imports the module and uses\n * its default export as the swatchbook {@link Config}.\n *\n * `.json` → treated as a DTCG resolver file; the CLI constructs a\n * minimal `{ resolver: path }` config so agents can point at a raw\n * resolver without authoring a wrapper. Every other config option\n * (presets, chrome map, disabled axes, css-var prefix) falls back to\n * `loadProject` defaults.\n *\n * The `cwd` returned is the directory the config / resolver lives in.\n * `loadProject` uses it to resolve relative token references.\n */\nexport async function loadFromConfig(\n configPath: string,\n cwdOverride?: string,\n): Promise<{ project: Project; cwd: string; config: Config }> {\n const absolute = isAbsolute(configPath) ? configPath : resolve(process.cwd(), configPath);\n const cwd = cwdOverride ?? resolve(absolute, '..');\n const ext = extname(absolute).toLowerCase();\n\n const config =\n ext === '.json' ? ({ resolver: absolute } satisfies Config) : await loadTsConfig(absolute);\n\n const project = await loadProject(config, cwd);\n return { project, cwd, config };\n}\n\nasync function loadTsConfig(absolute: string): Promise<Config> {\n /**\n * jiti's first arg is a directory-shaped \"from\" URL it uses to resolve\n * the target's relative imports. Passing a file URL leaves jiti\n * unsure whether to treat the path as a dir, which on some Node\n * versions falls through to a plain JSON read and surfaces as\n * `Unexpected token 'i', \"import { d\"...`. A trailing slash on a\n * directory URL avoids the ambiguity.\n */\n const fromUrl = new URL('./', pathToFileURL(absolute));\n const jiti = createJiti(fromUrl.href, {\n interopDefault: true,\n moduleCache: false,\n });\n return (await jiti.import(absolute, { default: true })) as Config;\n}\n"],"mappings":"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"}