@adobe/design-data-agent-mcp 1.8.25 → 1.9.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 +21 -9
- package/package.json +4 -4
- package/skills/design-data/SKILL.md +1 -1
- package/src/cascade-bootstrap.js +90 -0
- package/src/cli.js +4 -2
- package/src/config.js +18 -0
- package/src/index.js +3 -0
- package/src/tools/read.js +35 -7
- package/src/tools/validate.js +3 -1
package/README.md
CHANGED
|
@@ -47,15 +47,27 @@ node tools/design-data-agent-mcp/src/index.js
|
|
|
47
47
|
|
|
48
48
|
### Environment variables
|
|
49
49
|
|
|
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`)
|
|
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
|
+
| `DESIGN_DATA_CONFIG` | — | Path to a `.design-data.toml` (or its directory) to resolve a platform cascade |
|
|
60
|
+
|
|
61
|
+
> **Platform cascade.** If `DESIGN_DATA_CONFIG` is set, the server shells out to
|
|
62
|
+
> the `design-data` CLI once at startup (`design-data query --filter "" --format
|
|
63
|
+
> json`, run from the config's directory) to resolve its `.design-data.toml`
|
|
64
|
+
> source (path/npm/github/git) and any top-level `manifest` cascade, then
|
|
65
|
+
> materializes the result to a temp dir and points `primer` / `resolve_token` /
|
|
66
|
+
> `query_tokens` / `validate_usage` at it instead of the embedded Spectrum
|
|
67
|
+
> snapshot. `describe_component` is unaffected — it always reads components from
|
|
68
|
+
> `@adobe/spectrum-design-data` regardless of cascade state. If resolution fails
|
|
69
|
+
> (e.g. no network for a github source), the server logs a warning and falls
|
|
70
|
+
> back to the embedded/local dataset rather than crashing.
|
|
59
71
|
|
|
60
72
|
> **Path resolution.** The MCP client launches this server with the working
|
|
61
73
|
> directory inherited from wherever the editor was opened — which may be a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/design-data-agent-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.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.0.
|
|
34
|
-
"@adobe/design-data-wasm": "0.
|
|
35
|
-
"@adobe/spectrum-design-data": "2.
|
|
33
|
+
"@adobe/design-data": "3.0.7",
|
|
34
|
+
"@adobe/design-data-wasm": "0.7.0",
|
|
35
|
+
"@adobe/spectrum-design-data": "2.3.0"
|
|
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.
|
|
9
|
+
version: "1.9.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,
|
|
@@ -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
|
|
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 [
|
|
@@ -85,6 +87,7 @@ export function createMCPServer() {
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
async function startServer() {
|
|
90
|
+
await bootstrapCascade(config);
|
|
88
91
|
const server = createMCPServer();
|
|
89
92
|
const transport = new StdioServerTransport();
|
|
90
93
|
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
|
|
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
|
-
|
|
43
|
-
|
|
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(
|
|
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(
|
|
196
|
+
const ds = await loadDataset(
|
|
197
|
+
config.cascadeActive ? config.cascadeDataPath : config.dataPath,
|
|
198
|
+
);
|
|
171
199
|
return ds.query(filter);
|
|
172
200
|
},
|
|
173
201
|
},
|
package/src/tools/validate.js
CHANGED
|
@@ -38,7 +38,9 @@ export function createValidateTools() {
|
|
|
38
38
|
additionalProperties: false,
|
|
39
39
|
},
|
|
40
40
|
async handler({ path, strict, schema_path } = {}) {
|
|
41
|
-
const target =
|
|
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
|