@linktr.ee/arbor-mcp 0.5.0 → 0.6.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/dist/index.js +193 -27
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -6,7 +6,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
8
|
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
-
|
|
9
|
+
try {
|
|
10
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
+
} catch (e) {
|
|
12
|
+
throw mod = 0, e;
|
|
13
|
+
}
|
|
10
14
|
};
|
|
11
15
|
var __export = (target, all) => {
|
|
12
16
|
for (var name in all)
|
|
@@ -5532,7 +5536,7 @@ ZodNaN.create = (params) => {
|
|
|
5532
5536
|
...processCreateParams(params)
|
|
5533
5537
|
});
|
|
5534
5538
|
};
|
|
5535
|
-
var BRAND = Symbol("zod_brand");
|
|
5539
|
+
var BRAND = /* @__PURE__ */ Symbol("zod_brand");
|
|
5536
5540
|
var ZodBranded = class extends ZodType {
|
|
5537
5541
|
_parse(input) {
|
|
5538
5542
|
const { ctx } = this._processInputParams(input);
|
|
@@ -6130,6 +6134,68 @@ async function resolveVersionId(version, deps = {}) {
|
|
|
6130
6134
|
return { ok: true, versionId, versionName };
|
|
6131
6135
|
}
|
|
6132
6136
|
|
|
6137
|
+
// src/lib/intentions.ts
|
|
6138
|
+
var INTENTIONS_ARTIFACT = "component-intentions.json";
|
|
6139
|
+
var INTENTION_STATUSES = [
|
|
6140
|
+
"shipped",
|
|
6141
|
+
"in-progress",
|
|
6142
|
+
"design-only",
|
|
6143
|
+
"deprecated",
|
|
6144
|
+
"researched-not-shipped"
|
|
6145
|
+
];
|
|
6146
|
+
var STATUS_SET = new Set(INTENTION_STATUSES);
|
|
6147
|
+
function normalizeComponentKey(name) {
|
|
6148
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
6149
|
+
}
|
|
6150
|
+
function asStringArray(v) {
|
|
6151
|
+
return Array.isArray(v) ? v.filter((s) => typeof s === "string") : [];
|
|
6152
|
+
}
|
|
6153
|
+
function asDisambiguations(v) {
|
|
6154
|
+
if (!Array.isArray(v)) return [];
|
|
6155
|
+
const out = [];
|
|
6156
|
+
for (const d of v) {
|
|
6157
|
+
if (d && typeof d === "object") {
|
|
6158
|
+
const component = d.component;
|
|
6159
|
+
const distinction = d.distinction;
|
|
6160
|
+
if (typeof component === "string" && typeof distinction === "string") {
|
|
6161
|
+
out.push({ component, distinction });
|
|
6162
|
+
}
|
|
6163
|
+
}
|
|
6164
|
+
}
|
|
6165
|
+
return out;
|
|
6166
|
+
}
|
|
6167
|
+
function coerceEntry(raw) {
|
|
6168
|
+
if (!raw || typeof raw !== "object") return null;
|
|
6169
|
+
const r = raw;
|
|
6170
|
+
const name = typeof r.name === "string" ? r.name.trim() : "";
|
|
6171
|
+
const status = typeof r.status === "string" ? r.status : "";
|
|
6172
|
+
if (!name || !STATUS_SET.has(status)) return null;
|
|
6173
|
+
return {
|
|
6174
|
+
name,
|
|
6175
|
+
status,
|
|
6176
|
+
shippedVersion: typeof r.shippedVersion === "string" ? r.shippedVersion : null,
|
|
6177
|
+
useWhen: asStringArray(r.useWhen),
|
|
6178
|
+
notWhen: asStringArray(r.notWhen),
|
|
6179
|
+
disambiguateFrom: asDisambiguations(r.disambiguateFrom),
|
|
6180
|
+
guideline: typeof r.guideline === "string" ? r.guideline : null,
|
|
6181
|
+
decisions: asStringArray(r.decisions)
|
|
6182
|
+
};
|
|
6183
|
+
}
|
|
6184
|
+
async function loadIntentions(load = loadPublishedJson) {
|
|
6185
|
+
const res = await load(INTENTIONS_ARTIFACT);
|
|
6186
|
+
if (!res.ok) return { ok: false, reason: res.reason };
|
|
6187
|
+
const data = res.data;
|
|
6188
|
+
if (data.degraded === true || !Array.isArray(data.components)) {
|
|
6189
|
+
return { ok: false, reason: "degraded" };
|
|
6190
|
+
}
|
|
6191
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
6192
|
+
for (const raw of data.components) {
|
|
6193
|
+
const entry = coerceEntry(raw);
|
|
6194
|
+
if (entry) byKey.set(normalizeComponentKey(entry.name), entry);
|
|
6195
|
+
}
|
|
6196
|
+
return { ok: true, byKey, generatedAt: typeof data.generatedAt === "string" ? data.generatedAt : null };
|
|
6197
|
+
}
|
|
6198
|
+
|
|
6133
6199
|
// src/tools/list-components.ts
|
|
6134
6200
|
var TOOL_NAME3 = "arbor_list_components";
|
|
6135
6201
|
var TOOL_TITLE3 = "List Arbor components";
|
|
@@ -6146,7 +6212,16 @@ var inputSchema3 = external_exports.object({
|
|
|
6146
6212
|
var outputSchema3 = external_exports.object({
|
|
6147
6213
|
source: external_exports.enum(["remote", "unavailable"]),
|
|
6148
6214
|
total: external_exports.number(),
|
|
6149
|
-
components: external_exports.array(
|
|
6215
|
+
components: external_exports.array(
|
|
6216
|
+
external_exports.object({
|
|
6217
|
+
id: external_exports.string(),
|
|
6218
|
+
name: external_exports.string(),
|
|
6219
|
+
description: external_exports.string().nullable(),
|
|
6220
|
+
// Lifecycle status merged from the component-intentions manifest (DNG-726).
|
|
6221
|
+
// null when the manifest has no entry for this component or could not be loaded.
|
|
6222
|
+
status: external_exports.enum(INTENTION_STATUSES).nullable()
|
|
6223
|
+
})
|
|
6224
|
+
),
|
|
6150
6225
|
detail: external_exports.string().nullable()
|
|
6151
6226
|
});
|
|
6152
6227
|
var annotations3 = { readOnlyHint: true, idempotentHint: true, openWorldHint: true, destructiveHint: false };
|
|
@@ -6196,7 +6271,13 @@ async function handleListComponents(args, deps = {}) {
|
|
|
6196
6271
|
(c) => c.name.toLowerCase().includes(q) || (c.description ? c.description.toLowerCase().includes(q) : false)
|
|
6197
6272
|
);
|
|
6198
6273
|
}
|
|
6199
|
-
|
|
6274
|
+
const intentionsRes = await (deps.loadIntentions ?? (() => loadIntentions()))();
|
|
6275
|
+
const byKey = intentionsRes.ok ? intentionsRes.byKey : null;
|
|
6276
|
+
const withStatus = components.map((c) => ({
|
|
6277
|
+
...c,
|
|
6278
|
+
status: byKey ? byKey.get(normalizeComponentKey(c.name))?.status ?? null : null
|
|
6279
|
+
}));
|
|
6280
|
+
out = { source: "remote", total: withStatus.length, components: withStatus, detail: null };
|
|
6200
6281
|
}
|
|
6201
6282
|
const summary = out.source === "unavailable" ? `Could not load components (${out.detail}). Treat as unavailable \u2014 do not assume there are none.` : `${out.total} component${out.total === 1 ? "" : "s"}${args.query ? ` matching "${args.query}"` : ""}.`;
|
|
6202
6283
|
return { content: [{ type: "text", text: summary }], structuredContent: out };
|
|
@@ -6210,7 +6291,7 @@ var listComponentsTool = {
|
|
|
6210
6291
|
// src/tools/get-component.ts
|
|
6211
6292
|
var TOOL_NAME4 = "arbor_get_component";
|
|
6212
6293
|
var TOOL_TITLE4 = "Get one Arbor component";
|
|
6213
|
-
var TOOL_DESCRIPTION4 = "Get a single Arbor component's metadata by name (case-insensitive) or id
|
|
6294
|
+
var TOOL_DESCRIPTION4 = "Get a single Arbor component's metadata by name (case-insensitive) or id (name, id, description) PLUS its intention overlay \u2014 lifecycle status (shipped | in-progress | design-only | deprecated | researched-not-shipped), when to use it, when NOT to (and what to use instead), and what it is commonly confused with. Intention comes from Arbor's source of truth, so a design-only component absent from the live design data still resolves. Read-only; matches top-level components, not Figma variants. Does NOT return token values or rendered source \u2014 use the shadcn registry / Storybook for implementation and arbor_open_in_playroom for a runnable snippet. Does NOT change anything.";
|
|
6214
6295
|
var inputSchema4 = external_exports.object({
|
|
6215
6296
|
name: external_exports.string().trim().min(1).max(100).optional().describe('Component name (case-insensitive), e.g. "Button"'),
|
|
6216
6297
|
id: external_exports.string().trim().min(1).max(100).optional().describe("Supernova component id"),
|
|
@@ -6221,53 +6302,114 @@ var inputSchema4 = external_exports.object({
|
|
|
6221
6302
|
'Read a specific released version, e.g. "15.1.0" \u2014 resolves to that frozen Supernova version and reads the component there ("what shipped in 15.1.0"). Takes precedence over versionSelector. See arbor_list_versions for the versions that have been cut.'
|
|
6222
6303
|
)
|
|
6223
6304
|
}).strict();
|
|
6305
|
+
var disambiguationSchema = external_exports.object({
|
|
6306
|
+
component: external_exports.string().describe("The thing to disambiguate from \u2014 an Arbor component or an external concept (chip, snackbar, \u2026)"),
|
|
6307
|
+
distinction: external_exports.string().describe("How they differ and which to pick when")
|
|
6308
|
+
});
|
|
6309
|
+
var intentionSchema = external_exports.object({
|
|
6310
|
+
status: external_exports.enum(["shipped", "in-progress", "design-only", "deprecated", "researched-not-shipped"]).describe("Lifecycle/availability in the @linktr.ee/arbor package"),
|
|
6311
|
+
shippedVersion: external_exports.string().nullable().describe("The arbor version where this became available, when known"),
|
|
6312
|
+
useWhen: external_exports.array(external_exports.string()).describe("Situations this is the right choice for"),
|
|
6313
|
+
notWhen: external_exports.array(external_exports.string()).describe("When NOT to reach for it, ideally naming the better alternative"),
|
|
6314
|
+
disambiguateFrom: external_exports.array(disambiguationSchema).describe("Things this is commonly confused with"),
|
|
6315
|
+
guideline: external_exports.string().nullable().describe("Repo-relative path to the long-form guideline, when one exists"),
|
|
6316
|
+
decisions: external_exports.array(external_exports.string()).describe("Decision-log ids (DL-NN) that explain this intention")
|
|
6317
|
+
});
|
|
6224
6318
|
var outputSchema4 = external_exports.object({
|
|
6225
|
-
source: external_exports.enum(["remote", "unavailable"]),
|
|
6226
|
-
found: external_exports.boolean(),
|
|
6319
|
+
source: external_exports.enum(["remote", "unavailable"]).describe("Live design-data (Supernova) availability"),
|
|
6320
|
+
found: external_exports.boolean().describe("True when a component OR an intention entry matched"),
|
|
6227
6321
|
component: external_exports.object({ id: external_exports.string(), name: external_exports.string(), description: external_exports.string().nullable() }).nullable(),
|
|
6322
|
+
intention: intentionSchema.nullable().describe("Intention overlay from the source-of-truth manifest; null when no entry matched or it could not be loaded"),
|
|
6323
|
+
intentionSource: external_exports.enum(["remote", "absent", "unavailable"]).describe("'remote' = matched, 'absent' = manifest loaded but no entry, 'unavailable' = manifest could not be loaded"),
|
|
6228
6324
|
detail: external_exports.string().nullable()
|
|
6229
6325
|
});
|
|
6230
6326
|
var annotations4 = { readOnlyHint: true, idempotentHint: true, openWorldHint: true, destructiveHint: false };
|
|
6327
|
+
async function resolveIntention(lookupName, load) {
|
|
6328
|
+
const res = await load();
|
|
6329
|
+
if (!res.ok) return { intention: null, intentionSource: "unavailable" };
|
|
6330
|
+
if (!lookupName) return { intention: null, intentionSource: "absent" };
|
|
6331
|
+
const match = res.byKey.get(normalizeComponentKey(lookupName));
|
|
6332
|
+
if (!match) return { intention: null, intentionSource: "absent" };
|
|
6333
|
+
const intention = {
|
|
6334
|
+
status: match.status,
|
|
6335
|
+
shippedVersion: match.shippedVersion,
|
|
6336
|
+
useWhen: match.useWhen,
|
|
6337
|
+
notWhen: match.notWhen,
|
|
6338
|
+
disambiguateFrom: match.disambiguateFrom.map((d) => ({ component: d.component, distinction: d.distinction })),
|
|
6339
|
+
guideline: match.guideline,
|
|
6340
|
+
decisions: match.decisions
|
|
6341
|
+
};
|
|
6342
|
+
return { intention, intentionSource: "remote" };
|
|
6343
|
+
}
|
|
6231
6344
|
async function handleGetComponent(args, deps = {}) {
|
|
6232
6345
|
const name = args.name?.trim();
|
|
6233
6346
|
const id = args.id?.trim();
|
|
6347
|
+
const loadIntentionsFn = deps.loadIntentions ?? (() => loadIntentions());
|
|
6234
6348
|
if (!name && !id) {
|
|
6235
|
-
const out2 = {
|
|
6349
|
+
const out2 = {
|
|
6350
|
+
source: "remote",
|
|
6351
|
+
found: false,
|
|
6352
|
+
component: null,
|
|
6353
|
+
intention: null,
|
|
6354
|
+
intentionSource: "absent",
|
|
6355
|
+
detail: "provide name or id"
|
|
6356
|
+
};
|
|
6236
6357
|
return {
|
|
6237
6358
|
content: [{ type: "text", text: 'Provide a component name or id \u2014 e.g. {"name":"Button"} or {"id":"35988997"}.' }],
|
|
6238
6359
|
structuredContent: out2
|
|
6239
6360
|
};
|
|
6240
6361
|
}
|
|
6241
6362
|
const query = deps.query ?? ((r, opts) => querySupernova(r, opts));
|
|
6242
|
-
let
|
|
6363
|
+
let supernovaSource = "remote";
|
|
6364
|
+
let component = null;
|
|
6365
|
+
let supernovaDetail = null;
|
|
6366
|
+
let queryOpts = null;
|
|
6243
6367
|
if (args.version) {
|
|
6244
6368
|
const resolveVersion = deps.resolveVersion ?? ((v) => resolveVersionId(v));
|
|
6245
6369
|
const resolved = await resolveVersion(args.version);
|
|
6246
|
-
if (
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
};
|
|
6370
|
+
if (resolved.ok) {
|
|
6371
|
+
queryOpts = { versionId: resolved.versionId };
|
|
6372
|
+
} else {
|
|
6373
|
+
supernovaSource = "unavailable";
|
|
6374
|
+
supernovaDetail = resolved.detail;
|
|
6252
6375
|
}
|
|
6253
|
-
queryOpts = { versionId: resolved.versionId };
|
|
6254
6376
|
} else {
|
|
6255
6377
|
queryOpts = { versionSelector: args.versionSelector };
|
|
6256
6378
|
}
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
const components = extractComponents(result.data);
|
|
6263
|
-
if (components === null) {
|
|
6264
|
-
out = { source: "unavailable", found: false, component: null, detail: "proxy returned an unrecognized components payload" };
|
|
6379
|
+
if (queryOpts) {
|
|
6380
|
+
const result = await query("components", queryOpts);
|
|
6381
|
+
if (!result.ok) {
|
|
6382
|
+
supernovaSource = "unavailable";
|
|
6383
|
+
supernovaDetail = `could not load components (${result.reason})`;
|
|
6265
6384
|
} else {
|
|
6266
|
-
const
|
|
6267
|
-
|
|
6385
|
+
const components = extractComponents(result.data);
|
|
6386
|
+
if (components === null) {
|
|
6387
|
+
supernovaSource = "unavailable";
|
|
6388
|
+
supernovaDetail = "proxy returned an unrecognized components payload";
|
|
6389
|
+
} else {
|
|
6390
|
+
const target = name ? normalizeComponentKey(name) : null;
|
|
6391
|
+
const match = id ? components.find((c) => c.id === id) : components.find((c) => normalizeComponentKey(c.name) === target);
|
|
6392
|
+
if (match) component = match;
|
|
6393
|
+
else supernovaDetail = `no component ${id ? `with id ${id}` : `named "${name}"`}`;
|
|
6394
|
+
}
|
|
6268
6395
|
}
|
|
6269
6396
|
}
|
|
6270
|
-
const
|
|
6397
|
+
const lookupName = component?.name ?? name ?? null;
|
|
6398
|
+
const { intention, intentionSource } = await resolveIntention(lookupName, loadIntentionsFn);
|
|
6399
|
+
const found = component !== null || intention !== null;
|
|
6400
|
+
let detail = supernovaDetail;
|
|
6401
|
+
if (!component && intention) {
|
|
6402
|
+
detail = supernovaSource === "unavailable" ? `live design data unavailable (${supernovaDetail}); intention found \u2014 status "${intention.status}"` : `not in the live design data, but tracked as "${intention.status}" in Arbor's component intentions`;
|
|
6403
|
+
}
|
|
6404
|
+
const out = { source: supernovaSource, found, component, intention, intentionSource, detail };
|
|
6405
|
+
let summary;
|
|
6406
|
+
if (!found) {
|
|
6407
|
+
summary = supernovaSource === "unavailable" ? `Could not load components (${supernovaDetail}). Treat as unavailable.` : `No Arbor component ${id ? `with id ${id}` : `named "${name}"`} exists.`;
|
|
6408
|
+
} else {
|
|
6409
|
+
const head = component ? `${component.name}${component.description ? ` \u2014 ${component.description}` : " (no description)"}.` : `${lookupName} \u2014 ${detail}.`;
|
|
6410
|
+
const intentionLine = intention ? ` Status: ${intention.status}.${intention.useWhen.length ? ` Use when: ${intention.useWhen[0]}` : ""}` : intentionSource === "unavailable" ? " (intention overlay unavailable)" : "";
|
|
6411
|
+
summary = `${head}${intentionLine}`;
|
|
6412
|
+
}
|
|
6271
6413
|
return { content: [{ type: "text", text: summary }], structuredContent: out };
|
|
6272
6414
|
}
|
|
6273
6415
|
var getComponentTool = {
|
|
@@ -7177,6 +7319,30 @@ var compositions_default = [
|
|
|
7177
7319
|
code: `<RadioGroup defaultValue="option-1">
|
|
7178
7320
|
<RadioButton value="option-1" variant="pill">Option 1</RadioButton>
|
|
7179
7321
|
<RadioButton value="option-2" variant="pill">Option 2</RadioButton>
|
|
7322
|
+
</RadioGroup>`
|
|
7323
|
+
},
|
|
7324
|
+
// ── Radio ──
|
|
7325
|
+
// Maps to figma-mappings `Radio` (canonical control, node 9232-27291). The bare
|
|
7326
|
+
// 20px control must render inside a RadioGroup; pair each with an accessible
|
|
7327
|
+
// name (aria-label here, or a sibling <label htmlFor> in real usage).
|
|
7328
|
+
{
|
|
7329
|
+
group: "Radio",
|
|
7330
|
+
name: "Radio",
|
|
7331
|
+
code: `<RadioGroup defaultValue="email" aria-label="Notifications">
|
|
7332
|
+
<Radio value="email" aria-label="Email" />
|
|
7333
|
+
<Radio value="sms" aria-label="SMS" />
|
|
7334
|
+
<Radio value="push" aria-label="Push notification" />
|
|
7335
|
+
</RadioGroup>`
|
|
7336
|
+
},
|
|
7337
|
+
// ── RadioItem ──
|
|
7338
|
+
// Maps to figma-mappings `RadioItem` (canonical control + label, node 12360-58265).
|
|
7339
|
+
{
|
|
7340
|
+
group: "RadioItem",
|
|
7341
|
+
name: "RadioItem",
|
|
7342
|
+
code: `<RadioGroup defaultValue="email" aria-label="Notifications">
|
|
7343
|
+
<RadioItem value="email" description="We'll send a confirmation">Email</RadioItem>
|
|
7344
|
+
<RadioItem value="sms">SMS</RadioItem>
|
|
7345
|
+
<RadioItem value="push" disabled>Push notification</RadioItem>
|
|
7180
7346
|
</RadioGroup>`
|
|
7181
7347
|
},
|
|
7182
7348
|
// ── SearchInput ──
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/arbor-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Model Context Protocol server exposing Arbor design system tools: Playroom snippets, Figma→code sync health, UX-writing checks, decision-log lookup, and federated component/version metadata.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arbor",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"esbuild": "^0.
|
|
48
|
+
"esbuild": "^0.28.1",
|
|
49
49
|
"tsx": "^4.7.0",
|
|
50
50
|
"typescript": "^5.7.0",
|
|
51
51
|
"zod": "3.25.76"
|