@adobe/design-data-mcp 1.1.0 → 1.2.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/LICENSE +1 -1
- package/package.json +4 -2
- package/src/tools/design-data.js +124 -66
package/LICENSE
CHANGED
|
@@ -186,7 +186,7 @@ file or class name and description of purpose be included on the
|
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
|
187
187
|
identification within third-party archives.
|
|
188
188
|
|
|
189
|
-
Copyright
|
|
189
|
+
Copyright 2026 Adobe
|
|
190
190
|
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
192
|
you may not use this file except in compliance with the License.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/design-data-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "MCP server for Spectrum design tokens and component schemas via the design-data CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
"provenance": true
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@modelcontextprotocol/sdk": "^1.27.1"
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
31
|
+
"@adobe/design-data-wasm": "0.1.0",
|
|
32
|
+
"@adobe/spectrum-design-data": "0.3.0"
|
|
31
33
|
},
|
|
32
34
|
"devDependencies": {
|
|
33
35
|
"ava": "^6.0.1"
|
package/src/tools/design-data.js
CHANGED
|
@@ -9,57 +9,83 @@
|
|
|
9
9
|
// governing permissions and limitations under the License.
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* MCP tool definitions
|
|
12
|
+
* MCP tool definitions for @adobe/design-data-mcp.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* or the configured source/version if one exists in the project.
|
|
14
|
+
* All tools run in-process via @adobe/design-data-wasm — no CLI binary or npx
|
|
15
|
+
* required. Dataset.embedded() provides the canonical Spectrum snapshot with
|
|
16
|
+
* zero configuration.
|
|
18
17
|
*/
|
|
19
18
|
|
|
20
|
-
import {
|
|
19
|
+
import { createRequire } from "module";
|
|
20
|
+
import { readFileSync, existsSync } from "fs";
|
|
21
|
+
import { join, dirname } from "path";
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
let _wasm;
|
|
24
|
+
/** Lazy-load and cache the wasm module (nodejs target, no init() required). */
|
|
25
|
+
async function getWasm() {
|
|
26
|
+
if (!_wasm) _wasm = await import("@adobe/design-data-wasm");
|
|
27
|
+
return _wasm;
|
|
28
|
+
}
|
|
24
29
|
|
|
30
|
+
let _dataset;
|
|
25
31
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* Uses spawnSync (blocking) intentionally: this MCP server serves a single
|
|
29
|
-
* stdio client and processes requests sequentially, so blocking the event
|
|
30
|
-
* loop is safe. The -y flag suppresses the interactive "install?" prompt on
|
|
31
|
-
* first run so the server doesn't hang waiting for user input.
|
|
32
|
+
* Return the embedded Spectrum dataset, caching it after first access.
|
|
32
33
|
*
|
|
33
|
-
*
|
|
34
|
+
* Dataset.embedded() clones the in-memory graph on every call; caching here
|
|
35
|
+
* avoids that per-request cost.
|
|
34
36
|
*/
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
timeout: 30_000,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// result.error is set when the process can't start at all (binary not found,
|
|
43
|
-
// timeout exceeded, etc.) — check before result.status.
|
|
44
|
-
if (result.error) throw result.error;
|
|
45
|
-
|
|
46
|
-
if (result.status !== 0) {
|
|
47
|
-
const msg = (result.stderr || result.stdout || "").trim();
|
|
48
|
-
throw new Error(`design-data exited ${result.status}: ${msg}`);
|
|
37
|
+
async function getDataset() {
|
|
38
|
+
if (!_dataset) {
|
|
39
|
+
const wasm = await getWasm();
|
|
40
|
+
_dataset = wasm.Dataset.embedded();
|
|
49
41
|
}
|
|
42
|
+
return _dataset;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Score tokens by keyword-overlap against an intent string.
|
|
47
|
+
*
|
|
48
|
+
* Pure function — accepts the token array so it can be tested independently.
|
|
49
|
+
*
|
|
50
|
+
* @param {object[]} tokens - Array of token result objects with a `name` string.
|
|
51
|
+
* @param {string} intent - Natural-language intent to match against.
|
|
52
|
+
* @param {number} limit - Maximum results to return.
|
|
53
|
+
* @returns {{ name: string, confidence: number, uuid: string, raw: unknown }[]}
|
|
54
|
+
*/
|
|
55
|
+
export function scoreTokensByKeyword(tokens, intent, limit = 5) {
|
|
56
|
+
const words = intent.toLowerCase().split(/\s+/).filter(Boolean);
|
|
57
|
+
if (words.length === 0) return [];
|
|
58
|
+
return tokens
|
|
59
|
+
.map((token) => {
|
|
60
|
+
const nameStr = token.name?.toLowerCase() ?? "";
|
|
61
|
+
const matches = words.filter((w) => nameStr.includes(w)).length;
|
|
62
|
+
const confidence = matches / words.length;
|
|
63
|
+
return { token, confidence };
|
|
64
|
+
})
|
|
65
|
+
.filter(({ confidence }) => confidence > 0)
|
|
66
|
+
.sort((a, b) => b.confidence - a.confidence)
|
|
67
|
+
.slice(0, limit)
|
|
68
|
+
.map(({ token, confidence }) => ({
|
|
69
|
+
name: token.name,
|
|
70
|
+
confidence: Math.round(confidence * 100) / 100,
|
|
71
|
+
uuid: token.uuid,
|
|
72
|
+
raw: token.raw,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
50
75
|
|
|
76
|
+
/** Return the @adobe/spectrum-design-data package root directory, or null. */
|
|
77
|
+
function resolveSpectrumDataPackage() {
|
|
51
78
|
try {
|
|
52
|
-
|
|
79
|
+
const req = createRequire(import.meta.url);
|
|
80
|
+
return dirname(req.resolve("@adobe/spectrum-design-data/package.json"));
|
|
53
81
|
} catch {
|
|
54
|
-
|
|
55
|
-
// If JSON.parse fails, return the raw string so the caller can decide.
|
|
56
|
-
return result.stdout.trim();
|
|
82
|
+
return null;
|
|
57
83
|
}
|
|
58
84
|
}
|
|
59
85
|
|
|
60
86
|
export function createDesignDataTools() {
|
|
61
87
|
return [
|
|
62
|
-
// ── primer
|
|
88
|
+
// ── primer ─────────────────────────────────────────────────────────────
|
|
63
89
|
{
|
|
64
90
|
name: "design-data-primer",
|
|
65
91
|
description:
|
|
@@ -72,12 +98,28 @@ export function createDesignDataTools() {
|
|
|
72
98
|
properties: {},
|
|
73
99
|
additionalProperties: false,
|
|
74
100
|
},
|
|
75
|
-
handler() {
|
|
76
|
-
|
|
101
|
+
async handler() {
|
|
102
|
+
const [wasm, ds] = await Promise.all([getWasm(), getDataset()]);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
source: "embedded",
|
|
106
|
+
tokenCount: ds.tokenCount(),
|
|
107
|
+
modeSets: {
|
|
108
|
+
colorScheme: wasm.getFieldValues("colorScheme") ?? [],
|
|
109
|
+
scale: wasm.getFieldValues("scale") ?? [],
|
|
110
|
+
contrast: wasm.getFieldValues("contrast") ?? [],
|
|
111
|
+
},
|
|
112
|
+
taxonomyFields: {
|
|
113
|
+
indexed: wasm.getIndexedFields(),
|
|
114
|
+
advisory: wasm.getAdvisoryFields() ?? [],
|
|
115
|
+
},
|
|
116
|
+
components: wasm.getFieldValues("component") ?? [],
|
|
117
|
+
properties: wasm.getFieldValues("property") ?? [],
|
|
118
|
+
};
|
|
77
119
|
},
|
|
78
120
|
},
|
|
79
121
|
|
|
80
|
-
// ── query
|
|
122
|
+
// ── query ───────────────────────────────────────────────────────────────
|
|
81
123
|
{
|
|
82
124
|
name: "design-data-query",
|
|
83
125
|
description:
|
|
@@ -97,18 +139,20 @@ export function createDesignDataTools() {
|
|
|
97
139
|
required: ["filter"],
|
|
98
140
|
additionalProperties: false,
|
|
99
141
|
},
|
|
100
|
-
handler({ filter }) {
|
|
101
|
-
|
|
142
|
+
async handler({ filter }) {
|
|
143
|
+
const ds = await getDataset();
|
|
144
|
+
return ds.query(filter);
|
|
102
145
|
},
|
|
103
146
|
},
|
|
104
147
|
|
|
105
|
-
// ── suggest
|
|
148
|
+
// ── suggest ─────────────────────────────────────────────────────────────
|
|
106
149
|
{
|
|
107
150
|
name: "design-data-suggest",
|
|
108
151
|
description:
|
|
109
|
-
"Suggest Spectrum tokens matching a natural-language intent
|
|
110
|
-
"Returns ranked
|
|
111
|
-
"Use when the user describes what they need rather than knowing the token name."
|
|
152
|
+
"Suggest Spectrum tokens matching a natural-language intent using keyword-overlap " +
|
|
153
|
+
"scoring. Returns matches ranked by confidence, token names, and values. " +
|
|
154
|
+
"Use when the user describes what they need rather than knowing the token name. " +
|
|
155
|
+
"(TODO: swap to wasm NLP suggest when available for higher-quality ranking.)",
|
|
112
156
|
inputSchema: {
|
|
113
157
|
type: "object",
|
|
114
158
|
properties: {
|
|
@@ -126,20 +170,14 @@ export function createDesignDataTools() {
|
|
|
126
170
|
required: ["intent"],
|
|
127
171
|
additionalProperties: false,
|
|
128
172
|
},
|
|
129
|
-
handler({ intent, limit = 5 }) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
intent,
|
|
134
|
-
"--format",
|
|
135
|
-
"json",
|
|
136
|
-
"--limit",
|
|
137
|
-
String(limit),
|
|
138
|
-
]);
|
|
173
|
+
async handler({ intent, limit = 5 }) {
|
|
174
|
+
const ds = await getDataset();
|
|
175
|
+
const allTokens = ds.query("");
|
|
176
|
+
return scoreTokensByKeyword(allTokens, intent, limit);
|
|
139
177
|
},
|
|
140
178
|
},
|
|
141
179
|
|
|
142
|
-
// ── component
|
|
180
|
+
// ── component ───────────────────────────────────────────────────────────
|
|
143
181
|
{
|
|
144
182
|
name: "design-data-component",
|
|
145
183
|
description:
|
|
@@ -159,13 +197,26 @@ export function createDesignDataTools() {
|
|
|
159
197
|
required: ["id"],
|
|
160
198
|
additionalProperties: false,
|
|
161
199
|
},
|
|
162
|
-
handler({ id }) {
|
|
163
|
-
|
|
164
|
-
|
|
200
|
+
async handler({ id }) {
|
|
201
|
+
const pkgRoot = resolveSpectrumDataPackage();
|
|
202
|
+
if (!pkgRoot) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`@adobe/spectrum-design-data is not installed — cannot load component "${id}". ` +
|
|
205
|
+
`Install it with: pnpm add @adobe/spectrum-design-data`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
const componentFile = join(pkgRoot, "components", `${id}.json`);
|
|
209
|
+
if (!existsSync(componentFile)) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`Component not found: "${id}". ` +
|
|
212
|
+
`Call design-data-primer to see available component IDs.`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return JSON.parse(readFileSync(componentFile, "utf-8"));
|
|
165
216
|
},
|
|
166
217
|
},
|
|
167
218
|
|
|
168
|
-
// ── resolve
|
|
219
|
+
// ── resolve ─────────────────────────────────────────────────────────────
|
|
169
220
|
{
|
|
170
221
|
name: "design-data-resolve",
|
|
171
222
|
description:
|
|
@@ -186,22 +237,29 @@ export function createDesignDataTools() {
|
|
|
186
237
|
},
|
|
187
238
|
scale: {
|
|
188
239
|
type: "string",
|
|
189
|
-
description: 'Scale mode, e.g. "
|
|
240
|
+
description: 'Scale mode, e.g. "desktop" or "mobile"',
|
|
190
241
|
},
|
|
191
242
|
contrast: {
|
|
192
243
|
type: "string",
|
|
193
|
-
description: 'Contrast mode, e.g. "
|
|
244
|
+
description: 'Contrast mode, e.g. "regular" or "high"',
|
|
194
245
|
},
|
|
195
246
|
},
|
|
196
247
|
required: ["property"],
|
|
197
248
|
additionalProperties: false,
|
|
198
249
|
},
|
|
199
|
-
handler({ property, colorScheme, scale, contrast }) {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
if (
|
|
203
|
-
if (
|
|
204
|
-
|
|
250
|
+
async handler({ property, colorScheme, scale, contrast }) {
|
|
251
|
+
const ds = await getDataset();
|
|
252
|
+
const context = {};
|
|
253
|
+
if (colorScheme) context.colorScheme = colorScheme;
|
|
254
|
+
if (scale) context.scale = scale;
|
|
255
|
+
if (contrast) context.contrast = contrast;
|
|
256
|
+
const result = ds.resolve(property, context);
|
|
257
|
+
if (!result) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`No token found for property "${property}" in context ${JSON.stringify(context)}`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
205
263
|
},
|
|
206
264
|
},
|
|
207
265
|
];
|