@adobe/design-data-agent-mcp 1.5.0 → 1.6.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
@@ -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. Shells out to the `design-data` CLI all logic stays in the Rust SDK.
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`) 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
 
@@ -29,7 +29,7 @@ https://github.com/adobe/spectrum-design-data/tree/main/tools/design-data-agent-
29
29
  npx @adobe/design-data-agent-mcp
30
30
  ```
31
31
 
32
- Requires the `@adobe/design-data` CLI on `PATH` (or set `DESIGN_DATA_BIN`).
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
34
  ## MCP server
35
35
 
@@ -49,7 +49,7 @@ node tools/design-data-agent-mcp/src/index.js
49
49
 
50
50
  | Variable | Default | Description |
51
51
  | ------------------------ | ------------- | ------------------------------------------------- |
52
- | `DESIGN_DATA_BIN` | `design-data` | Path to the `design-data` binary |
52
+ | `DESIGN_DATA_BIN` | `design-data` | Path to the `design-data` binary (authoring only) |
53
53
  | `DESIGN_DATA_ROOT` | — | Absolute root that relative paths are anchored to |
54
54
  | `DESIGN_DATA_PATH` | `.` | Dataset root path |
55
55
  | `DESIGN_DATA_COMPONENTS` | — | Override components directory |
@@ -74,8 +74,9 @@ node tools/design-data-agent-mcp/src/index.js
74
74
  > `packages/design-data`; when published it uses the installed dependency. This
75
75
  > is independent of the working directory.
76
76
  > 3. **Fallback.** `dataPath` falls back to the (anchored) current directory; the
77
- > component/field overrides fall back to the `design-data` binary's own
78
- > discovery (including its embedded snapshot).
77
+ > component/field overrides fall back to `null` (not supplied), which means
78
+ > `describe_component` will throw an error if `@adobe/spectrum-design-data` is
79
+ > not resolvable.
79
80
  >
80
81
  > In a monorepo checkout you typically need no `DESIGN_DATA_*` env vars at all —
81
82
  > resolution via the workspace package handles it.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@adobe/design-data-agent-mcp",
3
- "version": "1.5.0",
4
- "description": "MCP server and Claude Code skill for the design-data agent surface — shells out to the design-data CLI",
3
+ "version": "1.6.1",
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",
7
7
  "bin": {
@@ -30,8 +30,8 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@modelcontextprotocol/sdk": "^1.27.1",
33
- "@adobe/design-data": "2.0.0",
34
- "@adobe/design-data-wasm": "0.1.0",
33
+ "@adobe/design-data": "2.0.1",
34
+ "@adobe/design-data-wasm": "0.2.0",
35
35
  "@adobe/spectrum-design-data": "0.3.0"
36
36
  },
37
37
  "devDependencies": {
package/src/tools/read.js CHANGED
@@ -11,19 +11,54 @@
11
11
  /**
12
12
  * Read tools for design-data-agent-mcp.
13
13
  *
14
- * query_tokens and resolve_token use @adobe/design-data (loadDataset) + the
15
- * wasm Dataset to run in-process without spawning the CLI binary.
14
+ * All read tools run fully in-process via @adobe/design-data-wasm no CLI binary
15
+ * required. primer and describe_component were migrated in issue m1r.
16
16
  *
17
- * primer and describe_component still invoke the CLI: primer aggregates complex
18
- * catalog metadata (components, fields) not yet on the wasm surface, and
19
- * describe_component requires the components catalog path resolution that the CLI
20
- * handles. These will be ported when those APIs are added to the wasm surface.
17
+ * Note: authoring_session_step_intent in authoring.js still uses the CLI because
18
+ * the NLP suggest ranking is not yet on the wasm surface.
21
19
  */
22
20
 
21
+ import { readFileSync, existsSync, readdirSync } from "fs";
22
+ import { join } from "path";
23
23
  import { loadDataset } from "@adobe/design-data/load";
24
- import { runCli } from "../cli.js";
25
24
  import { config } from "../config.js";
26
25
 
26
+ let _wasm;
27
+ /** Lazy-load and cache the wasm module (nodejs target, no init() required). */
28
+ async function getWasm() {
29
+ if (!_wasm) _wasm = await import("@adobe/design-data-wasm");
30
+ return _wasm;
31
+ }
32
+
33
+ let _dataset;
34
+ /**
35
+ * Return the embedded Spectrum dataset, caching it after first access.
36
+ *
37
+ * Dataset.embedded() clones the in-memory graph on every call; caching here
38
+ * avoids that per-request cost.
39
+ */
40
+ async function getDataset() {
41
+ if (!_dataset) {
42
+ const wasm = await getWasm();
43
+ _dataset = wasm.Dataset.embedded();
44
+ }
45
+ return _dataset;
46
+ }
47
+
48
+ /**
49
+ * Validate a component ID against the same rule as the Rust SDK.
50
+ * See sdk/core/src/component.rs:validate_id — prevents path traversal.
51
+ */
52
+ const COMPONENT_ID_RE = /^[a-z][a-z0-9-]*$/;
53
+ function validateComponentId(id) {
54
+ if (!COMPONENT_ID_RE.test(id)) {
55
+ throw new Error(
56
+ `Invalid component ID "${id}". IDs must be kebab-case: start with a lowercase ` +
57
+ `letter and contain only lowercase letters, digits, and hyphens.`,
58
+ );
59
+ }
60
+ }
61
+
27
62
  export function createReadTools() {
28
63
  return [
29
64
  {
@@ -36,14 +71,31 @@ export function createReadTools() {
36
71
  additionalProperties: false,
37
72
  },
38
73
  async handler() {
39
- const args = ["primer", config.dataPath, "--format", "json"];
40
- if (config.componentsDir)
41
- args.push("--components-dir", config.componentsDir);
42
- if (config.fieldsDir) args.push("--fields-dir", config.fieldsDir);
43
- const { exitCode, stdout, stderr } = await runCli(args);
44
- if (exitCode !== 0)
45
- throw new Error(stderr || `primer exited ${exitCode}`);
46
- return JSON.parse(stdout);
74
+ // Shape note: this response intentionally diverges from the CLI PrimerData
75
+ // struct (sdk/core/src/primer.rs). The CLI emits modeSets as an array of
76
+ // {name, values} objects and taxonomyFields as a flat array. This in-process
77
+ // shape uses keyed objects (matching the sibling design-data-mcp), which agents
78
+ // and the SKILL.md skill prompt consume by key name. Skill contract:
79
+ // tokenCount, modeSets.{colorScheme,scale,contrast}, components[],
80
+ // taxonomyFields.{indexed,advisory}. CLI-only fields (specVersion, manifest,
81
+ // provenance) are not present — no SKILL.md reference or consumer relies on them.
82
+ const wasm = await getWasm();
83
+ const ds = await getDataset();
84
+ return {
85
+ source: "embedded",
86
+ tokenCount: ds.tokenCount(),
87
+ modeSets: {
88
+ colorScheme: wasm.getFieldValues("colorScheme") ?? [],
89
+ scale: wasm.getFieldValues("scale") ?? [],
90
+ contrast: wasm.getFieldValues("contrast") ?? [],
91
+ },
92
+ taxonomyFields: {
93
+ indexed: wasm.getIndexedFields(),
94
+ advisory: wasm.getAdvisoryFields() ?? [],
95
+ },
96
+ components: wasm.getFieldValues("component") ?? [],
97
+ properties: wasm.getFieldValues("property") ?? [],
98
+ };
47
99
  },
48
100
  },
49
101
 
@@ -127,13 +179,32 @@ export function createReadTools() {
127
179
  additionalProperties: false,
128
180
  },
129
181
  async handler({ id }) {
130
- const args = ["component", id];
131
- if (config.componentsDir)
132
- args.push("--components-dir", config.componentsDir);
133
- const { exitCode, stdout, stderr } = await runCli(args);
134
- if (exitCode !== 0)
135
- throw new Error(stderr || `component exited ${exitCode}`);
136
- return JSON.parse(stdout);
182
+ validateComponentId(id);
183
+ const componentsDir = config.componentsDir;
184
+ if (!componentsDir) {
185
+ throw new Error(
186
+ `@adobe/spectrum-design-data is not installed — cannot load component "${id}". ` +
187
+ `Install it with: pnpm add @adobe/spectrum-design-data`,
188
+ );
189
+ }
190
+ const componentFile = join(componentsDir, `${id}.json`);
191
+ if (!existsSync(componentFile)) {
192
+ let available;
193
+ try {
194
+ available = readdirSync(componentsDir)
195
+ .filter((f) => f.endsWith(".json"))
196
+ .map((f) => f.replace(/\.json$/, ""))
197
+ .sort()
198
+ .join(", ");
199
+ } catch {
200
+ available = null;
201
+ }
202
+ const hint = available
203
+ ? `Available components: ${available}`
204
+ : `Call primer to see available component IDs.`;
205
+ throw new Error(`Component not found: "${id}". ${hint}`);
206
+ }
207
+ return JSON.parse(readFileSync(componentFile, "utf-8"));
137
208
  },
138
209
  },
139
210
  ];