@adobe/design-data-agent-mcp 1.10.1 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # `@adobe/design-data-agent-mcp`
2
2
 
3
- MCP server and Claude Code skill for the [Spectrum Design Data](../../packages/design-data/) agent surface. Read tools (`primer`, `resolve_token`, `query_tokens`, `describe_component`, `describe_guideline`) run fully in-process via `@adobe/design-data-wasm` — no CLI binary required for those. Only `authoring_session_step_intent` still invokes the native binary (for NLP suggest ranking, not yet on the wasm surface).
3
+ MCP server and Claude Code skill for the [Spectrum Design Data](../../packages/design-data/) agent surface. Read tools (`primer`, `resolve_token`, `query_tokens`, `describe_component`, `describe_guideline`, `list_guidelines`) run fully in-process via `@adobe/design-data-wasm` — no CLI binary required for those. Only `authoring_session_step_intent` still invokes the native binary (for NLP suggest ranking, not yet on the wasm surface).
4
4
 
5
5
  ## Install
6
6
 
@@ -77,10 +77,11 @@ node tools/design-data-agent-mcp/src/index.js
77
77
  > json`, run from the config's directory) to resolve its `.design-data.toml`
78
78
  > source (path/npm/github/git) and any top-level `manifest` cascade, then
79
79
  > materializes the result to a temp dir and points `primer` / `resolve_token` /
80
- > `query_tokens` / `validate_usage` at it instead of the embedded Spectrum
81
- > snapshot. `describe_component` / `describe_guideline` are unaffected — they always
82
- > read components/guidelines from `@adobe/spectrum-design-data` regardless of
83
- > cascade state. If resolution fails
80
+ > `query_tokens` / `validate_usage` / `describe_component` /
81
+ > `describe_guideline` / `list_guidelines` at it instead of the embedded
82
+ > Spectrum snapshot. Component, relationship, and guideline catalogs are copied
83
+ > from the fallback dataset and overlaid with platform manifest extensions. If
84
+ > resolution fails
84
85
  > (e.g. no network for a github source), the server logs a warning and falls
85
86
  > back to the embedded/local dataset rather than crashing.
86
87
 
@@ -153,6 +154,7 @@ node tools/design-data-agent-mcp/src/index.js
153
154
  | `query_tokens` | Filter tokens by expression |
154
155
  | `describe_component` | Fetch component schema and token bindings |
155
156
  | `describe_guideline` | Fetch guideline documentBlocks content and metadata |
157
+ | `list_guidelines` | List guideline slugs and metadata, optionally by category |
156
158
  | `validate_usage` | Validate token usage and return a diagnostic report |
157
159
  | `diff_datasets` | Compare two datasets and return a semantic diff |
158
160
  | `write` | Write agent-generated product context to the dataset |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/design-data-agent-mcp",
3
- "version": "1.10.1",
3
+ "version": "1.11.0",
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.1.0",
33
+ "@adobe/design-data": "3.2.0",
34
34
  "@adobe/design-data-wasm": "0.9.0",
35
- "@adobe/spectrum-design-data": "3.2.4"
35
+ "@adobe/spectrum-design-data": "3.2.5"
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.10.1"
9
+ version: "1.11.0"
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,
@@ -17,11 +17,22 @@
17
17
  * The CLI already does config discovery, github fetch/cache, and manifest-cascade
18
18
  * application on every data-touching subcommand — this just shells out to it once
19
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.
20
+ * and materializes the result as a cascade dataset in a temp dir, which
21
+ * loadDataset()/validateDataset() already know how to read. Catalog directories
22
+ * are copied from the configured fallback dataset and then overlaid with any
23
+ * manifest extensions so component/guideline reads see the same cascade.
22
24
  */
23
25
 
24
- import { existsSync, mkdtempSync, statSync, writeFileSync } from "node:fs";
26
+ import {
27
+ cpSync,
28
+ existsSync,
29
+ mkdirSync,
30
+ mkdtempSync,
31
+ readdirSync,
32
+ readFileSync,
33
+ statSync,
34
+ writeFileSync,
35
+ } from "node:fs";
25
36
  import { tmpdir } from "node:os";
26
37
  import { dirname, join } from "node:path";
27
38
  import { runCli } from "./cli.js";
@@ -32,6 +43,80 @@ function resolveConfigDir(configPath) {
32
43
  return statSync(configPath).isDirectory() ? configPath : dirname(configPath);
33
44
  }
34
45
 
46
+ function resolveManifestPath(configDir) {
47
+ const configPath = join(configDir, ".design-data.toml");
48
+ if (!existsSync(configPath)) return null;
49
+
50
+ const configText = readFileSync(configPath, "utf-8");
51
+ const match = configText.match(/^\s*manifest\s*=\s*["']([^"']+)["']\s*$/m);
52
+ const manifestPath = join(configDir, match?.[1] ?? "manifest.json");
53
+ return existsSync(manifestPath) ? manifestPath : null;
54
+ }
55
+
56
+ function materializeCatalogs(config, cascadeDir, configDir) {
57
+ const manifestPath = resolveManifestPath(configDir);
58
+ let extensionsDir = null;
59
+ if (manifestPath) {
60
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
61
+ extensionsDir = join(
62
+ dirname(manifestPath),
63
+ manifest.extensionsDir ?? "extensions",
64
+ );
65
+ }
66
+
67
+ for (const [key, subdir] of [
68
+ ["componentsDir", "components"],
69
+ ["relationshipsDir", "relationships"],
70
+ ["guidelinesDir", "guidelines"],
71
+ ]) {
72
+ const targetDir = join(cascadeDir, subdir);
73
+ mkdirSync(targetDir, { recursive: true });
74
+ const fallbackDir = config[key];
75
+ if (fallbackDir && existsSync(fallbackDir)) {
76
+ cpSync(fallbackDir, targetDir, { recursive: true, force: true });
77
+ }
78
+ const extensionDir = extensionsDir && join(extensionsDir, subdir);
79
+ if (extensionDir && existsSync(extensionDir)) {
80
+ cpSync(extensionDir, targetDir, { recursive: true, force: true });
81
+ if (subdir === "guidelines") {
82
+ mergeGuidelineManifest(targetDir, extensionDir);
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ function mergeGuidelineManifest(targetDir, extensionDir) {
89
+ const manifestPath = join(targetDir, "manifest.json");
90
+ const manifest = existsSync(manifestPath)
91
+ ? JSON.parse(readFileSync(manifestPath, "utf-8"))
92
+ : { guidelines: [] };
93
+ const entries = new Map(
94
+ (manifest.guidelines ?? []).map((entry) => [entry.slug, entry]),
95
+ );
96
+
97
+ for (const file of readdirSync(extensionDir)) {
98
+ if (!file.endsWith(".json") || file === "manifest.json") continue;
99
+ const document = JSON.parse(
100
+ readFileSync(join(extensionDir, file), "utf-8"),
101
+ );
102
+ const slug = document.name ?? file.replace(/\.json$/, "");
103
+ entries.set(slug, {
104
+ slug,
105
+ ...(document.title && { title: document.title }),
106
+ ...(document.category && { category: document.category }),
107
+ ...(document.status && { status: document.status }),
108
+ ...(document.sourceUrl && { sourceUrl: document.sourceUrl }),
109
+ file: `guidelines/${file}`,
110
+ });
111
+ }
112
+
113
+ writeFileSync(
114
+ manifestPath,
115
+ JSON.stringify({ ...manifest, guidelines: [...entries.values()] }),
116
+ "utf-8",
117
+ );
118
+ }
119
+
35
120
  /**
36
121
  * Resolve `config.designDataConfig`'s cascade and populate
37
122
  * `config.cascadeDataPath` / `config.cascadeActive` with the result.
@@ -74,6 +159,7 @@ export async function bootstrapCascade(config, { run = runCli } = {}) {
74
159
  JSON.stringify(tokens),
75
160
  "utf-8",
76
161
  );
162
+ materializeCatalogs(config, dir, configDir);
77
163
 
78
164
  config.cascadeDataPath = dir;
79
165
  config.cascadeActive = true;
package/src/tools/read.js CHANGED
@@ -15,8 +15,8 @@
15
15
  * required. primer and describe_component were migrated in issue m1r.
16
16
  * describe_guideline (spectrum-design-data-9fe.7) is the read-back counterpart to
17
17
  * data_create/data_edit's "guidelines" category, closing the same read/write
18
- * asymmetry describe_component already closes for components. Its guideline-loading
19
- * logic (including the path-traversal guard) is shared with the sibling
18
+ * asymmetry describe_component already closes for components. Its guideline
19
+ * loading and listing logic is shared with the sibling
20
20
  * @adobe/design-data-mcp package's `design-data-guideline` tool via the
21
21
  * `@adobe/design-data/guideline` module, rather than duplicated here.
22
22
  *
@@ -26,19 +26,17 @@
26
26
  * Cascade scope (see cascade-bootstrap.js / spectrum-design-data-h890.14): once a
27
27
  * `.design-data.toml` cascade is resolved, primer/resolve_token/query_tokens/
28
28
  * validate_usage all reflect it (they read config.cascadeDataPath instead of
29
- * config.dataPath when config.cascadeActive). describe_component/describe_guideline
30
- * do not — components/relationships/guidelines still come from config.componentsDir /
31
- * config.relationshipsDir / config.guidelinesDir, which resolve from the embedded
32
- * @adobe/spectrum-design-data package regardless of cascade state. A platform source
33
- * repo generally carries a token cascade only, not its own component/guideline
34
- * schemas, so this is left out of scope rather than guessed at; revisit if a platform
35
- * manifest starts declaring components or guidelines.
29
+ * config.dataPath when config.cascadeActive). Cascade bootstrap copies the
30
+ * fallback component/relationship/guideline catalogs into that snapshot and
31
+ * overlays manifest extensions, so describe_component/describe_guideline/list_guidelines
32
+ * read the same active cascade while preserving the embedded fallback for inactive
33
+ * cascades.
36
34
  */
37
35
 
38
36
  import { readFileSync, existsSync, readdirSync } from "fs";
39
37
  import { join } from "path";
40
38
  import { loadDataset } from "@adobe/design-data/load";
41
- import { loadGuideline } from "@adobe/design-data/guideline";
39
+ import { listGuidelines, loadGuideline } from "@adobe/design-data/guideline";
42
40
  import { config } from "../config.js";
43
41
  import { checkDatasetFreshness } from "../dataset-freshness.js";
44
42
 
@@ -100,6 +98,15 @@ function validateGuidelineId(id) {
100
98
  validateKebabId(id, "guideline");
101
99
  }
102
100
 
101
+ function getCascadeAwareDir(baseDir, subdir) {
102
+ if (!baseDir || !config.cascadeActive || !config.cascadeDataPath) {
103
+ return baseDir;
104
+ }
105
+
106
+ const cascadeDir = join(config.cascadeDataPath, subdir);
107
+ return existsSync(cascadeDir) ? cascadeDir : baseDir;
108
+ }
109
+
103
110
  export function createReadTools() {
104
111
  return [
105
112
  {
@@ -238,7 +245,10 @@ export function createReadTools() {
238
245
  },
239
246
  async handler({ id }) {
240
247
  validateComponentId(id);
241
- const componentsDir = config.componentsDir;
248
+ const componentsDir = getCascadeAwareDir(
249
+ config.componentsDir,
250
+ "components",
251
+ );
242
252
  if (!componentsDir) {
243
253
  throw new Error(
244
254
  `@adobe/spectrum-design-data is not installed — cannot load component "${id}". ` +
@@ -268,7 +278,10 @@ export function createReadTools() {
268
278
  // of the component file into relationships/<id>.json — merge them back
269
279
  // in so callers relying on this tool for a component's token bindings
270
280
  // still see them (see spectrum-design-data-x29.4).
271
- const relationshipsDir = config.relationshipsDir;
281
+ const relationshipsDir = getCascadeAwareDir(
282
+ config.relationshipsDir,
283
+ "relationships",
284
+ );
272
285
  const relationshipFile = relationshipsDir
273
286
  ? join(relationshipsDir, `${id}.json`)
274
287
  : null;
@@ -302,7 +315,10 @@ export function createReadTools() {
302
315
  },
303
316
  async handler({ id }) {
304
317
  validateGuidelineId(id);
305
- const guidelinesDir = config.guidelinesDir;
318
+ const guidelinesDir = getCascadeAwareDir(
319
+ config.guidelinesDir,
320
+ "guidelines",
321
+ );
306
322
  if (!guidelinesDir) {
307
323
  throw new Error(
308
324
  `@adobe/spectrum-design-data is not installed — cannot load guideline "${id}". ` +
@@ -315,9 +331,8 @@ export function createReadTools() {
315
331
  if (err.message.startsWith("Not found:")) {
316
332
  let available;
317
333
  try {
318
- available = readdirSync(guidelinesDir)
319
- .filter((f) => f.endsWith(".json") && f !== "manifest.json")
320
- .map((f) => f.replace(/\.json$/, ""))
334
+ available = listGuidelines(guidelinesDir)
335
+ .map((guideline) => guideline.slug)
321
336
  .sort()
322
337
  .join(", ");
323
338
  } catch {
@@ -332,5 +347,39 @@ export function createReadTools() {
332
347
  }
333
348
  },
334
349
  },
350
+
351
+ {
352
+ name: "list_guidelines",
353
+ description:
354
+ "List available Spectrum design guidelines and their metadata. Optionally " +
355
+ "filter the catalog by category before calling describe_guideline.",
356
+ inputSchema: {
357
+ type: "object",
358
+ properties: {
359
+ category: {
360
+ type: "string",
361
+ description:
362
+ "Optional guideline category, e.g. designing or implementing",
363
+ },
364
+ },
365
+ additionalProperties: false,
366
+ },
367
+ async handler({ category } = {}) {
368
+ const guidelinesDir = getCascadeAwareDir(
369
+ config.guidelinesDir,
370
+ "guidelines",
371
+ );
372
+ if (!guidelinesDir) {
373
+ throw new Error(
374
+ `@adobe/spectrum-design-data is not installed — cannot list guidelines. ` +
375
+ `Install it with: pnpm add @adobe/spectrum-design-data`,
376
+ );
377
+ }
378
+ const guidelines = listGuidelines(guidelinesDir);
379
+ return category
380
+ ? guidelines.filter((guideline) => guideline.category === category)
381
+ : guidelines;
382
+ },
383
+ },
335
384
  ];
336
385
  }