@adobe/design-data-agent-mcp 1.8.26 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,17 +26,26 @@ https://github.com/adobe/spectrum-design-data/tree/main/tools/design-data-agent-
26
26
  ### npm (MCP server)
27
27
 
28
28
  ```sh
29
- npx @adobe/design-data-agent-mcp
29
+ npx @adobe/design-data-agent-mcp@latest
30
30
  ```
31
31
 
32
32
  The `@adobe/design-data` CLI binary is **only** needed for `authoring_session_step_intent`. All other tools run in-process. Set `DESIGN_DATA_BIN` if the binary is not on `PATH`.
33
33
 
34
+ ### Staying current
35
+
36
+ The embedded Spectrum snapshot is baked into the wasm at build time and travels with the
37
+ package version — there's no separate data update to run. Pin `@latest` (as above) so `npx`
38
+ fetches the newest published version rather than reusing whatever it last cached; `-y` alone
39
+ only skips the install confirmation, it doesn't force a re-fetch. Already on `/plugin`? Run
40
+ `/plugin update design-data-agent@spectrum-design-data`. Call the primer tool and check
41
+ `provenance.designDataVersion` to see which dataset version is embedded.
42
+
34
43
  ## MCP server
35
44
 
36
45
  Configure your MCP client to run:
37
46
 
38
47
  ```sh
39
- npx -y @adobe/design-data-agent-mcp
48
+ npx -y @adobe/design-data-agent-mcp@latest
40
49
  ```
41
50
 
42
51
  Or from a repo clone:
@@ -47,15 +56,27 @@ node tools/design-data-agent-mcp/src/index.js
47
56
 
48
57
  ### Environment variables
49
58
 
50
- | Variable | Default | Description |
51
- | ------------------------ | ------------- | ------------------------------------------------- |
52
- | `DESIGN_DATA_BIN` | `design-data` | Path to the `design-data` binary (authoring only) |
53
- | `DESIGN_DATA_ROOT` | — | Absolute root that relative paths are anchored to |
54
- | `DESIGN_DATA_PATH` | `.` | Dataset root path |
55
- | `DESIGN_DATA_COMPONENTS` | — | Override components directory |
56
- | `DESIGN_DATA_FIELDS` | — | Override fields directory |
57
- | `DESIGN_DATA_SCHEMAS` | — | Override schema path (for `validate`) |
58
- | `DESIGN_DATA_EXCEPTIONS` | — | Override exceptions path (for `validate`) |
59
+ | Variable | Default | Description |
60
+ | ------------------------ | ------------- | ------------------------------------------------------------------------------ |
61
+ | `DESIGN_DATA_BIN` | `design-data` | Path to the `design-data` binary (authoring only) |
62
+ | `DESIGN_DATA_ROOT` | — | Absolute root that relative paths are anchored to |
63
+ | `DESIGN_DATA_PATH` | `.` | Dataset root path |
64
+ | `DESIGN_DATA_COMPONENTS` | — | Override components directory |
65
+ | `DESIGN_DATA_FIELDS` | — | Override fields directory |
66
+ | `DESIGN_DATA_SCHEMAS` | — | Override schema path (for `validate`) |
67
+ | `DESIGN_DATA_EXCEPTIONS` | — | Override exceptions path (for `validate`) |
68
+ | `DESIGN_DATA_CONFIG` | — | Path to a `.design-data.toml` (or its directory) to resolve a platform cascade |
69
+
70
+ > **Platform cascade.** If `DESIGN_DATA_CONFIG` is set, the server shells out to
71
+ > the `design-data` CLI once at startup (`design-data query --filter "" --format
72
+ > json`, run from the config's directory) to resolve its `.design-data.toml`
73
+ > source (path/npm/github/git) and any top-level `manifest` cascade, then
74
+ > materializes the result to a temp dir and points `primer` / `resolve_token` /
75
+ > `query_tokens` / `validate_usage` at it instead of the embedded Spectrum
76
+ > snapshot. `describe_component` is unaffected — it always reads components from
77
+ > `@adobe/spectrum-design-data` regardless of cascade state. If resolution fails
78
+ > (e.g. no network for a github source), the server logs a warning and falls
79
+ > back to the embedded/local dataset rather than crashing.
59
80
 
60
81
  > **Path resolution.** The MCP client launches this server with the working
61
82
  > directory inherited from wherever the editor was opened — which may be a
@@ -88,7 +109,7 @@ node tools/design-data-agent-mcp/src/index.js
88
109
  "mcpServers": {
89
110
  "design-data-agent": {
90
111
  "command": "npx",
91
- "args": ["-y", "@adobe/design-data-agent-mcp"],
112
+ "args": ["-y", "@adobe/design-data-agent-mcp@latest"],
92
113
  "env": {
93
114
  "DESIGN_DATA_ROOT": "/abs/path/to/your/repo",
94
115
  "DESIGN_DATA_PATH": "packages/design-data/tokens",
@@ -107,7 +128,7 @@ node tools/design-data-agent-mcp/src/index.js
107
128
  "mcpServers": {
108
129
  "design-data-agent": {
109
130
  "command": "npx",
110
- "args": ["-y", "@adobe/design-data-agent-mcp"],
131
+ "args": ["-y", "@adobe/design-data-agent-mcp@latest"],
111
132
  "env": {
112
133
  "DESIGN_DATA_BIN": "design-data",
113
134
  "DESIGN_DATA_PATH": "/path/to/your/dataset"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/design-data-agent-mcp",
3
- "version": "1.8.26",
3
+ "version": "1.9.1",
4
4
  "description": "MCP server and Claude Code skill for the design-data agent surface — read tools run in-process via wasm",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -30,9 +30,9 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@modelcontextprotocol/sdk": "^1.27.1",
33
- "@adobe/design-data": "3.0.6",
34
- "@adobe/design-data-wasm": "0.6.0",
35
- "@adobe/spectrum-design-data": "2.2.0"
33
+ "@adobe/design-data": "3.0.7",
34
+ "@adobe/design-data-wasm": "0.7.0",
35
+ "@adobe/spectrum-design-data": "2.3.1"
36
36
  },
37
37
  "devDependencies": {
38
38
  "ava": "^6.0.1"
@@ -6,7 +6,7 @@ description: >
6
6
  system, token lookup, spec-conformance, drift detection, or token authoring on custom data.
7
7
  metadata:
8
8
  author: adobe
9
- version: "1.8.26"
9
+ version: "1.9.1"
10
10
  when_to_use: >
11
11
  Trigger on: design system, design tokens, spec-conformant, drift, validate tokens, token
12
12
  authoring, custom dataset, DESIGN_DATA_PATH, design-data validate, design-data diff,
@@ -38,7 +38,7 @@ Add `@adobe/design-data-agent-mcp` to your `.cursor/mcp.json`:
38
38
  "mcpServers": {
39
39
  "design-data-agent": {
40
40
  "command": "npx",
41
- "args": ["-y", "@adobe/design-data-agent-mcp"],
41
+ "args": ["-y", "@adobe/design-data-agent-mcp@latest"],
42
42
  "env": {
43
43
  "DESIGN_DATA_PATH": "./packages/tokens/src",
44
44
  "DESIGN_DATA_COMPONENTS": "./packages/design-data/components",
@@ -49,7 +49,9 @@ Add `@adobe/design-data-agent-mcp` to your `.cursor/mcp.json`:
49
49
  }
50
50
  ```
51
51
 
52
- Adjust paths to match your dataset layout.
52
+ Adjust paths to match your dataset layout. Pin `@latest` — the embedded Spectrum snapshot is
53
+ baked into the wasm at build time and travels with the package version, so there's no separate
54
+ data update to run, but plain `npx -y <pkg>` (no tag) can reuse a cached older build.
53
55
 
54
56
  ***
55
57
 
@@ -0,0 +1,90 @@
1
+ // Copyright 2026 Adobe. All rights reserved.
2
+ // This file is licensed to you under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License. You may obtain a copy
4
+ // of the License at http://www.apache.org/licenses/LICENSE-2.0
5
+ //
6
+ // Unless required by applicable law or agreed to in writing, software distributed under
7
+ // the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8
+ // OF ANY KIND, either express or implied. See the License for the specific language
9
+ // governing permissions and limitations under the License.
10
+
11
+ /**
12
+ * Resolves a `.design-data.toml` platform source + manifest cascade once at
13
+ * startup, so the MCP's in-process read/validate tools (which only know how
14
+ * to read a local directory — see config.dataPath) see the platform-resolved
15
+ * dataset instead of the embedded Spectrum snapshot or a raw local dir.
16
+ *
17
+ * The CLI already does config discovery, github fetch/cache, and manifest-cascade
18
+ * application on every data-touching subcommand — this just shells out to it once
19
+ * (`design-data query --filter "" --format json`, cwd = the config's directory)
20
+ * and materializes the result as a single `*.tokens.json` cascade-array file in a
21
+ * temp dir, which loadDataset()/validateDataset() already know how to read.
22
+ */
23
+
24
+ import { existsSync, mkdtempSync, statSync, writeFileSync } from "node:fs";
25
+ import { tmpdir } from "node:os";
26
+ import { dirname, join } from "node:path";
27
+ import { runCli } from "./cli.js";
28
+
29
+ /** Directory containing `.design-data.toml`, given either path. */
30
+ function resolveConfigDir(configPath) {
31
+ if (!existsSync(configPath)) return null;
32
+ return statSync(configPath).isDirectory() ? configPath : dirname(configPath);
33
+ }
34
+
35
+ /**
36
+ * Resolve `config.designDataConfig`'s cascade and populate
37
+ * `config.cascadeDataPath` / `config.cascadeActive` with the result.
38
+ * Deliberately does not touch `config.dataPath`/`config.dataRoot` — those
39
+ * remain the fallback anchors for unrelated write/authoring/data tools,
40
+ * which must keep targeting the real dataset root, not the cascade's
41
+ * resolved snapshot. No-ops if `designDataConfig` is unset. Never throws —
42
+ * a failed resolve (e.g. no network for a github source) logs to stderr and
43
+ * leaves config untouched, so the server still starts against the
44
+ * embedded/local fallback.
45
+ *
46
+ * @param {object} config - The mutable config object from ./config.js.
47
+ * @param {{ run?: typeof runCli }} [opts] - `run` is injectable for tests.
48
+ */
49
+ export async function bootstrapCascade(config, { run = runCli } = {}) {
50
+ if (!config.designDataConfig) return;
51
+
52
+ const configDir = resolveConfigDir(config.designDataConfig);
53
+ if (!configDir) {
54
+ console.error(
55
+ `[design-data-mcp] DESIGN_DATA_CONFIG "${config.designDataConfig}" not found; ` +
56
+ "ignoring, falling back to embedded/local dataset.",
57
+ );
58
+ return;
59
+ }
60
+
61
+ try {
62
+ const { exitCode, stdout, stderr } = await run(
63
+ ["query", "--filter", "", "--format", "json"],
64
+ { timeout: 60_000, cwd: configDir },
65
+ );
66
+ if (exitCode !== 0) {
67
+ throw new Error(stderr || `design-data exited with code ${exitCode}`);
68
+ }
69
+ const tokens = JSON.parse(stdout);
70
+
71
+ const dir = mkdtempSync(join(tmpdir(), "design-data-mcp-cascade-"));
72
+ writeFileSync(
73
+ join(dir, "resolved.tokens.json"),
74
+ JSON.stringify(tokens),
75
+ "utf-8",
76
+ );
77
+
78
+ config.cascadeDataPath = dir;
79
+ config.cascadeActive = true;
80
+ console.error(
81
+ `[design-data-mcp] cascade resolved from ${configDir} ` +
82
+ `(${tokens.length} tokens) -> ${dir}`,
83
+ );
84
+ } catch (err) {
85
+ console.error(
86
+ `[design-data-mcp] cascade bootstrap failed, falling back to embedded/local ` +
87
+ `dataset: ${err.message}`,
88
+ );
89
+ }
90
+ }
package/src/cli.js CHANGED
@@ -11,13 +11,15 @@
11
11
  import { spawn } from "child_process";
12
12
  import { config } from "./config.js";
13
13
 
14
- export function runCli(args, { timeout = 10_000, stdin } = {}) {
14
+ export function runCli(args, { timeout = 10_000, stdin, cwd } = {}) {
15
15
  return new Promise((resolve, reject) => {
16
16
  const proc = spawn(config.bin, args, {
17
17
  // Anchor the CLI's working directory to the resolved data root so its own
18
18
  // tier/probe logic (data_source::resolve, is_in_repo) resolves correctly
19
19
  // even when Claude Code launched the server from a monorepo subdirectory.
20
- cwd: config.dataRoot,
20
+ // `cwd` override lets the cascade bootstrap point the CLI at a
21
+ // `.design-data.toml` directory that differs from config.dataRoot.
22
+ cwd: cwd ?? config.dataRoot,
21
23
  // isolates CLI stdout from the MCP JSON-RPC stream on the parent's stdout
22
24
  stdio: [stdin != null ? "pipe" : "ignore", "pipe", "pipe"],
23
25
  });
package/src/config.js CHANGED
@@ -78,4 +78,22 @@ export const config = {
78
78
  anchorPath(process.env.DESIGN_DATA_FIELDS) ??
79
79
  resolveDataPackageDir("fields") ??
80
80
  null,
81
+ // Path to a `.design-data.toml` (or its containing directory) declaring a
82
+ // platform source + manifest cascade. When set, cascade-bootstrap.js
83
+ // resolves it once at startup (via the CLI, which already knows how to
84
+ // fetch/cache a github source and apply the manifest) and populates
85
+ // cascadeDataPath/cascadeActive below. Unset by default so an in-repo run's
86
+ // ambient .design-data.toml (if any) doesn't surprise it.
87
+ designDataConfig: anchorPath(process.env.DESIGN_DATA_CONFIG ?? null),
88
+ // Set by cascade-bootstrap.js on a successful resolve: the temp dir holding
89
+ // the materialized cascade dataset. Deliberately separate from dataPath —
90
+ // dataPath/dataRoot are also the fallback anchors for unrelated
91
+ // write/authoring/data tools (write.js, data.js, authoring.js), which must
92
+ // keep targeting the real dataset root regardless of cascade state. Only
93
+ // read.js/validate.js consult cascadeDataPath.
94
+ cascadeDataPath: null,
95
+ // Flipped to true by cascade-bootstrap.js on a successful resolve. Tools
96
+ // that otherwise default to the embedded Spectrum snapshot (primer,
97
+ // describe_component) check this to switch to cascadeDataPath instead.
98
+ cascadeActive: false,
81
99
  };
package/src/index.js CHANGED
@@ -24,6 +24,8 @@ import { createReadTools } from "./tools/read.js";
24
24
  import { createValidateTools } from "./tools/validate.js";
25
25
  import { createDiffTools } from "./tools/diff.js";
26
26
  import { createWriteTools } from "./tools/write.js";
27
+ import { bootstrapCascade } from "./cascade-bootstrap.js";
28
+ import { config } from "./config.js";
27
29
 
28
30
  export function createAllTools() {
29
31
  return [
@@ -46,7 +48,17 @@ export function createMCPServer() {
46
48
  const allTools = createAllTools();
47
49
  const server = new Server(
48
50
  { name: "design-data-agent-mcp", version: pkg.version },
49
- { capabilities: { tools: {} } },
51
+ {
52
+ capabilities: { tools: {} },
53
+ instructions:
54
+ "This server bundles a snapshot of Spectrum design data (tokens, components, " +
55
+ "guidelines) baked into its wasm at build time. The data version travels with the " +
56
+ "package version, so there is nothing to update separately. To get newer Spectrum " +
57
+ "data, install the latest published package (npx -y @adobe/design-data-agent-mcp@latest " +
58
+ '— plain "npx -y @adobe/design-data-agent-mcp" can reuse a cached older build). Call ' +
59
+ "the design-data primer tool and read provenance.designDataVersion to see the " +
60
+ "embedded dataset version.",
61
+ },
50
62
  );
51
63
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
52
64
  tools: allTools.map(({ name, description, inputSchema }) => ({
@@ -85,6 +97,7 @@ export function createMCPServer() {
85
97
  }
86
98
 
87
99
  async function startServer() {
100
+ await bootstrapCascade(config);
88
101
  const server = createMCPServer();
89
102
  const transport = new StdioServerTransport();
90
103
  await server.connect(transport);
package/src/tools/read.js CHANGED
@@ -16,6 +16,16 @@
16
16
  *
17
17
  * Note: authoring_session_step_intent in authoring.js still uses the CLI because
18
18
  * the NLP suggest ranking is not yet on the wasm surface.
19
+ *
20
+ * Cascade scope (see cascade-bootstrap.js / spectrum-design-data-h890.14): once a
21
+ * `.design-data.toml` cascade is resolved, primer/resolve_token/query_tokens/
22
+ * validate_usage all reflect it (they read config.cascadeDataPath instead of
23
+ * config.dataPath when config.cascadeActive). describe_component
24
+ * does not — components/relationships still come from config.componentsDir /
25
+ * config.relationshipsDir, which resolve from the embedded @adobe/spectrum-design-data
26
+ * package regardless of cascade state. A platform source repo generally carries a
27
+ * token cascade only, not its own component schemas, so this is left out of scope
28
+ * rather than guessed at; revisit if a platform manifest starts declaring components.
19
29
  */
20
30
 
21
31
  import { readFileSync, existsSync, readdirSync } from "fs";
@@ -23,6 +33,13 @@ import { join } from "path";
23
33
  import { loadDataset } from "@adobe/design-data/load";
24
34
  import { config } from "../config.js";
25
35
 
36
+ // Note: this module intentionally never spawns the native binary — primer and
37
+ // describe_component must keep working with no CLI on PATH at all (see the
38
+ // structural test in read.test.js). Cascade resolution happens once at startup
39
+ // in cascade-bootstrap.js (which does spawn the binary); by the time these
40
+ // handlers run, a cascade dataset is just another local directory at
41
+ // config.dataPath.
42
+
26
43
  let _wasm;
27
44
  /** Lazy-load and cache the wasm module (nodejs target, no init() required). */
28
45
  async function getWasm() {
@@ -32,15 +49,22 @@ async function getWasm() {
32
49
 
33
50
  let _dataset;
34
51
  /**
35
- * Return the embedded Spectrum dataset, caching it after first access.
52
+ * Return the active dataset, caching it after first access.
53
+ *
54
+ * When cascade-bootstrap.js resolved a `.design-data.toml` platform source at
55
+ * startup (config.cascadeActive), config.cascadeDataPath is a local dir holding
56
+ * the resolved cascade — load that instead of the embedded Spectrum snapshot,
57
+ * the same way resolve_token/query_tokens already do. cascadeActive is decided
58
+ * once at startup before any request runs, so caching here is safe.
36
59
  *
37
60
  * Dataset.embedded() clones the in-memory graph on every call; caching here
38
- * avoids that per-request cost.
61
+ * avoids that per-request cost either way.
39
62
  */
40
63
  async function getDataset() {
41
64
  if (!_dataset) {
42
- const wasm = await getWasm();
43
- _dataset = wasm.Dataset.embedded();
65
+ _dataset = config.cascadeActive
66
+ ? await loadDataset(config.cascadeDataPath)
67
+ : (await getWasm()).Dataset.embedded();
44
68
  }
45
69
  return _dataset;
46
70
  }
@@ -87,7 +111,7 @@ export function createReadTools() {
87
111
  // top-level source is the legacy skill-contract field; provenance.source
88
112
  // duplicates it intentionally — provenance is the richer metrics object
89
113
  // and consumers should prefer it going forward.
90
- source: "embedded",
114
+ source: config.cascadeActive ? "cascade" : "embedded",
91
115
  tokenCount: ds.tokenCount(),
92
116
  modeSets: {
93
117
  colorScheme: wasm.getFieldValues("colorScheme") ?? [],
@@ -136,7 +160,9 @@ export function createReadTools() {
136
160
  additionalProperties: false,
137
161
  },
138
162
  async handler({ property, colorScheme, scale, contrast }) {
139
- const ds = await loadDataset(config.dataPath);
163
+ const ds = await loadDataset(
164
+ config.cascadeActive ? config.cascadeDataPath : config.dataPath,
165
+ );
140
166
  const context = {};
141
167
  if (colorScheme) context.colorScheme = colorScheme;
142
168
  if (scale) context.scale = scale;
@@ -167,7 +193,9 @@ export function createReadTools() {
167
193
  additionalProperties: false,
168
194
  },
169
195
  async handler({ filter }) {
170
- const ds = await loadDataset(config.dataPath);
196
+ const ds = await loadDataset(
197
+ config.cascadeActive ? config.cascadeDataPath : config.dataPath,
198
+ );
171
199
  return ds.query(filter);
172
200
  },
173
201
  },
@@ -38,7 +38,9 @@ export function createValidateTools() {
38
38
  additionalProperties: false,
39
39
  },
40
40
  async handler({ path, strict, schema_path } = {}) {
41
- const target = path ?? config.dataPath;
41
+ const target =
42
+ path ??
43
+ (config.cascadeActive ? config.cascadeDataPath : config.dataPath);
42
44
  const schemaPath = schema_path ?? config.schemaPath ?? null;
43
45
  // NOTE: exceptionsPath (DESIGN_DATA_EXCEPTIONS / --exceptions-path) applies to the
44
46
  // SPEC-007 naming rule in the relational layer. The in-process wasm validate() does