@linktr.ee/arbor-mcp 0.5.0 → 0.7.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.
Files changed (2) hide show
  1. package/dist/index.js +243 -27
  2. 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
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
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,69 @@ 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
+ implementation: typeof r.implementation === "string" ? r.implementation : null,
6182
+ decisions: asStringArray(r.decisions)
6183
+ };
6184
+ }
6185
+ async function loadIntentions(load = loadPublishedJson) {
6186
+ const res = await load(INTENTIONS_ARTIFACT);
6187
+ if (!res.ok) return { ok: false, reason: res.reason };
6188
+ const data = res.data;
6189
+ if (data.degraded === true || !Array.isArray(data.components)) {
6190
+ return { ok: false, reason: "degraded" };
6191
+ }
6192
+ const byKey = /* @__PURE__ */ new Map();
6193
+ for (const raw of data.components) {
6194
+ const entry = coerceEntry(raw);
6195
+ if (entry) byKey.set(normalizeComponentKey(entry.name), entry);
6196
+ }
6197
+ return { ok: true, byKey, generatedAt: typeof data.generatedAt === "string" ? data.generatedAt : null };
6198
+ }
6199
+
6133
6200
  // src/tools/list-components.ts
6134
6201
  var TOOL_NAME3 = "arbor_list_components";
6135
6202
  var TOOL_TITLE3 = "List Arbor components";
@@ -6146,7 +6213,16 @@ var inputSchema3 = external_exports.object({
6146
6213
  var outputSchema3 = external_exports.object({
6147
6214
  source: external_exports.enum(["remote", "unavailable"]),
6148
6215
  total: external_exports.number(),
6149
- components: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string(), description: external_exports.string().nullable() })),
6216
+ components: external_exports.array(
6217
+ external_exports.object({
6218
+ id: external_exports.string(),
6219
+ name: external_exports.string(),
6220
+ description: external_exports.string().nullable(),
6221
+ // Lifecycle status merged from the component-intentions manifest (DNG-726).
6222
+ // null when the manifest has no entry for this component or could not be loaded.
6223
+ status: external_exports.enum(INTENTION_STATUSES).nullable()
6224
+ })
6225
+ ),
6150
6226
  detail: external_exports.string().nullable()
6151
6227
  });
6152
6228
  var annotations3 = { readOnlyHint: true, idempotentHint: true, openWorldHint: true, destructiveHint: false };
@@ -6196,7 +6272,13 @@ async function handleListComponents(args, deps = {}) {
6196
6272
  (c) => c.name.toLowerCase().includes(q) || (c.description ? c.description.toLowerCase().includes(q) : false)
6197
6273
  );
6198
6274
  }
6199
- out = { source: "remote", total: components.length, components, detail: null };
6275
+ const intentionsRes = await (deps.loadIntentions ?? (() => loadIntentions()))();
6276
+ const byKey = intentionsRes.ok ? intentionsRes.byKey : null;
6277
+ const withStatus = components.map((c) => ({
6278
+ ...c,
6279
+ status: byKey ? byKey.get(normalizeComponentKey(c.name))?.status ?? null : null
6280
+ }));
6281
+ out = { source: "remote", total: withStatus.length, components: withStatus, detail: null };
6200
6282
  }
6201
6283
  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
6284
  return { content: [{ type: "text", text: summary }], structuredContent: out };
@@ -6210,7 +6292,7 @@ var listComponentsTool = {
6210
6292
  // src/tools/get-component.ts
6211
6293
  var TOOL_NAME4 = "arbor_get_component";
6212
6294
  var TOOL_TITLE4 = "Get one Arbor component";
6213
- var TOOL_DESCRIPTION4 = "Get a single Arbor component's metadata by name (case-insensitive) or id from Supernova via the Arbor federation proxy (name, id, description). 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.";
6295
+ 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), what it is commonly confused with, and repo paths to its guideline and (when present) agent implementation guidance. 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
6296
  var inputSchema4 = external_exports.object({
6215
6297
  name: external_exports.string().trim().min(1).max(100).optional().describe('Component name (case-insensitive), e.g. "Button"'),
6216
6298
  id: external_exports.string().trim().min(1).max(100).optional().describe("Supernova component id"),
@@ -6221,53 +6303,116 @@ var inputSchema4 = external_exports.object({
6221
6303
  '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
6304
  )
6223
6305
  }).strict();
6306
+ var disambiguationSchema = external_exports.object({
6307
+ component: external_exports.string().describe("The thing to disambiguate from \u2014 an Arbor component or an external concept (chip, snackbar, \u2026)"),
6308
+ distinction: external_exports.string().describe("How they differ and which to pick when")
6309
+ });
6310
+ var intentionSchema = external_exports.object({
6311
+ status: external_exports.enum(["shipped", "in-progress", "design-only", "deprecated", "researched-not-shipped"]).describe("Lifecycle/availability in the @linktr.ee/arbor package"),
6312
+ shippedVersion: external_exports.string().nullable().describe("The arbor version where this became available, when known"),
6313
+ useWhen: external_exports.array(external_exports.string()).describe("Situations this is the right choice for"),
6314
+ notWhen: external_exports.array(external_exports.string()).describe("When NOT to reach for it, ideally naming the better alternative"),
6315
+ disambiguateFrom: external_exports.array(disambiguationSchema).describe("Things this is commonly confused with"),
6316
+ guideline: external_exports.string().nullable().describe("Repo-relative path to the long-form guideline, when one exists"),
6317
+ implementation: external_exports.string().nullable().describe("Repo-relative path to the agent-facing implementation guidance (code-correctness + a11y rules), when one exists"),
6318
+ decisions: external_exports.array(external_exports.string()).describe("Decision-log ids (DL-NN) that explain this intention")
6319
+ });
6224
6320
  var outputSchema4 = external_exports.object({
6225
- source: external_exports.enum(["remote", "unavailable"]),
6226
- found: external_exports.boolean(),
6321
+ source: external_exports.enum(["remote", "unavailable"]).describe("Live design-data (Supernova) availability"),
6322
+ found: external_exports.boolean().describe("True when a component OR an intention entry matched"),
6227
6323
  component: external_exports.object({ id: external_exports.string(), name: external_exports.string(), description: external_exports.string().nullable() }).nullable(),
6324
+ intention: intentionSchema.nullable().describe("Intention overlay from the source-of-truth manifest; null when no entry matched or it could not be loaded"),
6325
+ intentionSource: external_exports.enum(["remote", "absent", "unavailable"]).describe("'remote' = matched, 'absent' = manifest loaded but no entry, 'unavailable' = manifest could not be loaded"),
6228
6326
  detail: external_exports.string().nullable()
6229
6327
  });
6230
6328
  var annotations4 = { readOnlyHint: true, idempotentHint: true, openWorldHint: true, destructiveHint: false };
6329
+ async function resolveIntention(lookupName, load) {
6330
+ const res = await load();
6331
+ if (!res.ok) return { intention: null, intentionSource: "unavailable" };
6332
+ if (!lookupName) return { intention: null, intentionSource: "absent" };
6333
+ const match = res.byKey.get(normalizeComponentKey(lookupName));
6334
+ if (!match) return { intention: null, intentionSource: "absent" };
6335
+ const intention = {
6336
+ status: match.status,
6337
+ shippedVersion: match.shippedVersion,
6338
+ useWhen: match.useWhen,
6339
+ notWhen: match.notWhen,
6340
+ disambiguateFrom: match.disambiguateFrom.map((d) => ({ component: d.component, distinction: d.distinction })),
6341
+ guideline: match.guideline,
6342
+ implementation: match.implementation,
6343
+ decisions: match.decisions
6344
+ };
6345
+ return { intention, intentionSource: "remote" };
6346
+ }
6231
6347
  async function handleGetComponent(args, deps = {}) {
6232
6348
  const name = args.name?.trim();
6233
6349
  const id = args.id?.trim();
6350
+ const loadIntentionsFn = deps.loadIntentions ?? (() => loadIntentions());
6234
6351
  if (!name && !id) {
6235
- const out2 = { source: "remote", found: false, component: null, detail: "provide name or id" };
6352
+ const out2 = {
6353
+ source: "remote",
6354
+ found: false,
6355
+ component: null,
6356
+ intention: null,
6357
+ intentionSource: "absent",
6358
+ detail: "provide name or id"
6359
+ };
6236
6360
  return {
6237
6361
  content: [{ type: "text", text: 'Provide a component name or id \u2014 e.g. {"name":"Button"} or {"id":"35988997"}.' }],
6238
6362
  structuredContent: out2
6239
6363
  };
6240
6364
  }
6241
6365
  const query = deps.query ?? ((r, opts) => querySupernova(r, opts));
6242
- let queryOpts;
6366
+ let supernovaSource = "remote";
6367
+ let component = null;
6368
+ let supernovaDetail = null;
6369
+ let queryOpts = null;
6243
6370
  if (args.version) {
6244
6371
  const resolveVersion = deps.resolveVersion ?? ((v) => resolveVersionId(v));
6245
6372
  const resolved = await resolveVersion(args.version);
6246
- if (!resolved.ok) {
6247
- const out2 = { source: "unavailable", found: false, component: null, detail: resolved.detail };
6248
- return {
6249
- content: [{ type: "text", text: `Could not read version ${args.version}: ${resolved.detail}. Treat as unavailable.` }],
6250
- structuredContent: out2
6251
- };
6373
+ if (resolved.ok) {
6374
+ queryOpts = { versionId: resolved.versionId };
6375
+ } else {
6376
+ supernovaSource = "unavailable";
6377
+ supernovaDetail = resolved.detail;
6252
6378
  }
6253
- queryOpts = { versionId: resolved.versionId };
6254
6379
  } else {
6255
6380
  queryOpts = { versionSelector: args.versionSelector };
6256
6381
  }
6257
- const result = await query("components", queryOpts);
6258
- let out;
6259
- if (!result.ok) {
6260
- out = { source: "unavailable", found: false, component: null, detail: `could not load components (${result.reason})` };
6261
- } else {
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" };
6382
+ if (queryOpts) {
6383
+ const result = await query("components", queryOpts);
6384
+ if (!result.ok) {
6385
+ supernovaSource = "unavailable";
6386
+ supernovaDetail = `could not load components (${result.reason})`;
6265
6387
  } else {
6266
- const match = id ? components.find((c) => c.id === id) : components.find((c) => c.name.toLowerCase() === name.toLowerCase());
6267
- out = match ? { source: "remote", found: true, component: match, detail: null } : { source: "remote", found: false, component: null, detail: `no component ${id ? `with id ${id}` : `named "${name}"`}` };
6388
+ const components = extractComponents(result.data);
6389
+ if (components === null) {
6390
+ supernovaSource = "unavailable";
6391
+ supernovaDetail = "proxy returned an unrecognized components payload";
6392
+ } else {
6393
+ const target = name ? normalizeComponentKey(name) : null;
6394
+ const match = id ? components.find((c) => c.id === id) : components.find((c) => normalizeComponentKey(c.name) === target);
6395
+ if (match) component = match;
6396
+ else supernovaDetail = `no component ${id ? `with id ${id}` : `named "${name}"`}`;
6397
+ }
6268
6398
  }
6269
6399
  }
6270
- const summary = out.source === "unavailable" ? `Could not load components (${out.detail}). Treat as unavailable.` : out.found ? `${out.component.name}${out.component.description ? ` \u2014 ${out.component.description}` : " (no description)"}.` : `No Arbor component ${args.id ? `with id ${args.id.trim()}` : `named "${args.name?.trim()}"`} exists.`;
6400
+ const lookupName = component?.name ?? name ?? null;
6401
+ const { intention, intentionSource } = await resolveIntention(lookupName, loadIntentionsFn);
6402
+ const found = component !== null || intention !== null;
6403
+ let detail = supernovaDetail;
6404
+ if (!component && intention) {
6405
+ 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`;
6406
+ }
6407
+ const out = { source: supernovaSource, found, component, intention, intentionSource, detail };
6408
+ let summary;
6409
+ if (!found) {
6410
+ summary = supernovaSource === "unavailable" ? `Could not load components (${supernovaDetail}). Treat as unavailable.` : `No Arbor component ${id ? `with id ${id}` : `named "${name}"`} exists.`;
6411
+ } else {
6412
+ const head = component ? `${component.name}${component.description ? ` \u2014 ${component.description}` : " (no description)"}.` : `${lookupName} \u2014 ${detail}.`;
6413
+ const intentionLine = intention ? ` Status: ${intention.status}.${intention.useWhen.length ? ` Use when: ${intention.useWhen[0]}` : ""}` : intentionSource === "unavailable" ? " (intention overlay unavailable)" : "";
6414
+ summary = `${head}${intentionLine}`;
6415
+ }
6271
6416
  return { content: [{ type: "text", text: summary }], structuredContent: out };
6272
6417
  }
6273
6418
  var getComponentTool = {
@@ -6790,6 +6935,22 @@ var compositions_default = [
6790
6935
  name: "NotificationBadge",
6791
6936
  code: "<NotificationBadge count={3} />"
6792
6937
  },
6938
+ // ── IndicatorBadge ──
6939
+ {
6940
+ group: "IndicatorBadge",
6941
+ name: "IndicatorBadge (count)",
6942
+ code: "<IndicatorBadge count={5} />"
6943
+ },
6944
+ {
6945
+ group: "IndicatorBadge",
6946
+ name: "IndicatorBadge (dot)",
6947
+ code: '<IndicatorBadge dot aria-label="Unread" />'
6948
+ },
6949
+ {
6950
+ group: "IndicatorBadge",
6951
+ name: "IndicatorBadge (custom color)",
6952
+ code: '<IndicatorBadge count={5} color="#7B61FF" />'
6953
+ },
6793
6954
  // ── Button ──
6794
6955
  {
6795
6956
  group: "Button",
@@ -6823,6 +6984,31 @@ var compositions_default = [
6823
6984
  <Button variant="secondary">Cancel</Button>
6824
6985
  <Button>Save changes</Button>
6825
6986
  </div>`
6987
+ },
6988
+ // ── ButtonGroup ──
6989
+ {
6990
+ group: "ButtonGroup",
6991
+ name: "ButtonGroup (confirm / cancel)",
6992
+ code: `<ButtonGroup aria-label="Save changes">
6993
+ <Button variant="secondary">Cancel</Button>
6994
+ <Button>Save changes</Button>
6995
+ </ButtonGroup>`
6996
+ },
6997
+ {
6998
+ group: "ButtonGroup",
6999
+ name: "ButtonGroup (stacked)",
7000
+ code: `<ButtonGroup stacked aria-label="Start trial or decide later">
7001
+ <Button variant="accent">Start 7-day free trial</Button>
7002
+ <Button variant="ghost">Decide later</Button>
7003
+ </ButtonGroup>`
7004
+ },
7005
+ {
7006
+ group: "ButtonGroup",
7007
+ name: "ButtonGroup (auto width)",
7008
+ code: `<ButtonGroup fullWidth={false} aria-label="Discard or update">
7009
+ <Button variant="ghost">Discard changes</Button>
7010
+ <Button>Update everywhere</Button>
7011
+ </ButtonGroup>`
6826
7012
  },
6827
7013
  // ── CardSelect ──
6828
7014
  {
@@ -7053,6 +7239,12 @@ var compositions_default = [
7053
7239
  </RadioGroup>
7054
7240
  </Fieldset>`
7055
7241
  },
7242
+ // ── FileUpload ──
7243
+ {
7244
+ group: "FileUpload",
7245
+ name: "FileUpload",
7246
+ code: '<div className="w-full max-w-md"><FileUpload label="Select files to upload" supportingText="PDF, PNG, or JPG \u2014 up to 100MB each" /></div>'
7247
+ },
7056
7248
  // ── HeaderBar ──
7057
7249
  // Maps to figma-mappings `HeaderBar` (node 5223-5837). Title + back-button
7058
7250
  // is the canonical small variant; HeaderBar.stories.tsx covers the full
@@ -7177,6 +7369,30 @@ var compositions_default = [
7177
7369
  code: `<RadioGroup defaultValue="option-1">
7178
7370
  <RadioButton value="option-1" variant="pill">Option 1</RadioButton>
7179
7371
  <RadioButton value="option-2" variant="pill">Option 2</RadioButton>
7372
+ </RadioGroup>`
7373
+ },
7374
+ // ── Radio ──
7375
+ // Maps to figma-mappings `Radio` (canonical control, node 9232-27291). The bare
7376
+ // 20px control must render inside a RadioGroup; pair each with an accessible
7377
+ // name (aria-label here, or a sibling <label htmlFor> in real usage).
7378
+ {
7379
+ group: "Radio",
7380
+ name: "Radio",
7381
+ code: `<RadioGroup defaultValue="email" aria-label="Notifications">
7382
+ <Radio value="email" aria-label="Email" />
7383
+ <Radio value="sms" aria-label="SMS" />
7384
+ <Radio value="push" aria-label="Push notification" />
7385
+ </RadioGroup>`
7386
+ },
7387
+ // ── RadioItem ──
7388
+ // Maps to figma-mappings `RadioItem` (canonical control + label, node 12360-58265).
7389
+ {
7390
+ group: "RadioItem",
7391
+ name: "RadioItem",
7392
+ code: `<RadioGroup defaultValue="email" aria-label="Notifications">
7393
+ <RadioItem value="email" description="We'll send a confirmation">Email</RadioItem>
7394
+ <RadioItem value="sms">SMS</RadioItem>
7395
+ <RadioItem value="push" disabled>Push notification</RadioItem>
7180
7396
  </RadioGroup>`
7181
7397
  },
7182
7398
  // ── SearchInput ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/arbor-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.7.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.25.0",
48
+ "esbuild": "^0.28.1",
49
49
  "tsx": "^4.7.0",
50
50
  "typescript": "^5.7.0",
51
51
  "zod": "3.25.76"