@bridge-ds/mcp 0.7.0 → 0.7.2

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.
@@ -4916,6 +4916,7 @@ function figmaDraftBrief(d, ctx) {
4916
4916
  ...d.reasons?.length ? ["Why this needs code:", ...d.reasons.map((r) => `- ${r}`), ""] : [],
4917
4917
  `Spec: ${ctx.specPath} ${suggested ? "in the suggestion" : "on the branch"} has the bound variables of every variant (token paths), the layers of every variant and what code gave Figma before.`,
4918
4918
  ...d.figma?.url ? [`Design: ${d.figma.url}`] : [],
4919
+ ...suggested && d.figma?.fileKey && d.figma.nodeId ? [`Read the design straight from Figma: Figma MCP get_design_context { fileKey: "${d.figma.fileKey}", nodeId: "${d.figma.nodeId}" }${d.figma.before?.nodeId ? ` (before: node ${d.figma.before.nodeId})` : ""}, or the REST API GET https://api.figma.com/v1/files/${d.figma.fileKey}/nodes?ids=${encodeURIComponent(d.figma.nodeId)}.`] : [],
4919
4920
  ...d.images?.length ? ["Screenshot:", ...d.images.map((u) => `- ${u}`)] : [],
4920
4921
  "",
4921
4922
  "Steps:",
@@ -5224,6 +5225,7 @@ var SUGGESTION_STATUS_LABEL = {
5224
5225
  declined: "Declined",
5225
5226
  outdated: "Outdated"
5226
5227
  };
5228
+ var figmaNodeUrl = (fileKey, nodeId) => `https://www.figma.com/design/${fileKey}/?node-id=${encodeURIComponent(nodeId.replace(/:/g, "-"))}`;
5227
5229
  var SUGGESTION_LIMITS = {
5228
5230
  body: 65536,
5229
5231
  /** The readable part of the body (tables are cut with "and N more"). */
@@ -5530,6 +5532,19 @@ function linksSection(p) {
5530
5532
  const out = [];
5531
5533
  if (l.neededBy) out.push(`- Needed by ${l.neededBy.productName}: [#${l.neededBy.pr} ${cell3(l.neededBy.title)}](${l.neededBy.url})`);
5532
5534
  if (l.figma?.url) out.push(`- Design: [Open in Figma](${l.figma.url})`);
5535
+ const key = l.figma?.fileKey;
5536
+ const nodes = (l.figma?.nodes ?? []).filter((n) => n && typeof n.nodeId === "string");
5537
+ for (const n of nodes) {
5538
+ const url = n.url ?? (key ? figmaNodeUrl(key, n.nodeId) : void 0);
5539
+ const what = `${n.state === "before" ? "Before" : n.state === "after" ? "After" : "Node"}${n.name ? ` (${cell3(n.name)})` : ""}`;
5540
+ out.push(`- ${what}: ${url ? `[node ${n.nodeId}](${url})` : `node ${n.nodeId}`}`);
5541
+ }
5542
+ if (key && (nodes.length || l.figma?.nodeId)) {
5543
+ const ids = nodes.length ? nodes.map((n) => n.nodeId) : [l.figma.nodeId];
5544
+ out.push(
5545
+ `- Claude Code reads this design straight from Figma: the Figma MCP server (\`get_design_context\` with file key \`${key}\` and node ${ids.map((i) => `\`${i}\``).join(", ")}) or the REST API (\`GET https://api.figma.com/v1/files/${key}/nodes?ids=${ids.map(encodeURIComponent).join(",")}\`). No image hosting is needed.`
5546
+ );
5547
+ }
5533
5548
  if (l.exploration) out.push(`- Exploration: \`${l.exploration}\``);
5534
5549
  if (l.supersedes) out.push(`- Replaces suggestion #${l.supersedes}`);
5535
5550
  for (const v of p.previews ?? []) out.push(`- ${cell3(v.label)}: ${v.url}`);
@@ -5768,6 +5783,8 @@ function suggestionShapeProblems(raw) {
5768
5783
  if (p.docs !== void 0 && !(Array.isArray(p.docs) && p.docs.every((d) => d && typeof d.component === "string" && typeof d.path === "string"))) out.push('"docs" must be a list of { "component", "path", "after" }.');
5769
5784
  const spec = p.componentSpec;
5770
5785
  if (spec !== void 0 && (!spec || typeof spec.component !== "string" || typeof spec.specPath !== "string" || typeof spec.brief !== "string")) out.push('"componentSpec" must be { "component", "specPath", "spec", "brief" }.');
5786
+ const nodes = p.links?.figma?.nodes;
5787
+ if (nodes !== void 0 && !(Array.isArray(nodes) && nodes.every((n) => n && typeof n.nodeId === "string"))) out.push('"links.figma.nodes" must be a list of { "nodeId", "url"?, "name"?, "state"? }.');
5771
5788
  if (p.files !== void 0) {
5772
5789
  if (!Array.isArray(p.files)) out.push('"files" must be a list.');
5773
5790
  else {
@@ -6564,7 +6581,8 @@ function componentSpecSuggestionPayload(d, plan, ctx) {
6564
6581
  const spec = JSON.parse(formatFigmaSpec(d, plan));
6565
6582
  const brief = figmaDraftBrief({ ...d, tokenEdits: plan.edits, reasons: plan.reasons }, { suggestion: true, specPath, componentsDir: ctx.componentsDir });
6566
6583
  const by = (ctx.suggestedBy ?? d.requestedBy)?.replace(/^@/, "");
6567
- const figma = d.figma?.fileKey || d.figma?.nodeId || d.figma?.url ? { ...d.figma.fileKey ? { fileKey: d.figma.fileKey } : {}, ...d.figma.nodeId ? { nodeId: d.figma.nodeId } : {}, ...d.figma.url ? { url: d.figma.url } : {} } : void 0;
6584
+ const figma = d.figma?.fileKey || d.figma?.nodeId || d.figma?.url ? { ...d.figma.fileKey ? { fileKey: d.figma.fileKey } : {}, ...d.figma.nodeId ? { nodeId: d.figma.nodeId } : {}, ...d.figma.url ? { url: d.figma.url } : {}, ...figmaNodes(d) } : void 0;
6585
+ const previews = (d.images ?? []).map((url, i) => ({ label: `${d.component} in Figma${(d.images?.length ?? 0) > 1 ? ` ${i + 1}` : ""}`, url }));
6568
6586
  return {
6569
6587
  schema: SUGGESTION_SCHEMA,
6570
6588
  kind: "component-spec",
@@ -6577,9 +6595,19 @@ function componentSpecSuggestionPayload(d, plan, ctx) {
6577
6595
  tier: specTier(d),
6578
6596
  componentSpec: { component: d.component, specPath, spec, brief },
6579
6597
  checks: { newErrors: 0, contrast: [], blocked: null, impact: 0 },
6580
- ...figma ? { links: { figma } } : {}
6598
+ ...figma ? { links: { figma } } : {},
6599
+ ...previews.length ? { previews } : {}
6581
6600
  };
6582
6601
  }
6602
+ function figmaNodes(d) {
6603
+ const f = d.figma;
6604
+ if (!f) return {};
6605
+ const url = (id, given) => given ?? (f.fileKey ? figmaNodeUrl(f.fileKey, id) : void 0);
6606
+ const nodes = [];
6607
+ if (f.before?.nodeId) nodes.push({ nodeId: f.before.nodeId, state: "before", ...url(f.before.nodeId, f.before.url) ? { url: url(f.before.nodeId, f.before.url) } : {}, ...f.before.nodeName ? { name: f.before.nodeName } : {} });
6608
+ if (f.nodeId) nodes.push({ nodeId: f.nodeId, state: "after", ...url(f.nodeId, f.url) ? { url: url(f.nodeId, f.url) } : {}, ...f.nodeName ? { name: f.nodeName } : {} });
6609
+ return nodes.length ? { nodes } : {};
6610
+ }
6583
6611
  async function openComponentPushOnGitHub(gh, input) {
6584
6612
  const d = input.diff;
6585
6613
  if (!input.plan && !input.tokens) throw new Error("Route the change first: pass the token facts or the plan.");
@@ -6600,7 +6628,7 @@ async function openComponentPushOnGitHub(gh, input) {
6600
6628
  };
6601
6629
  if (plan.route === "token-change") {
6602
6630
  if (!input.writeTokens) throw new GitHubError(422, "This change is a token change: send it where the token files are read (the DS Bridge plugin, bridge_push_figma_component or `bridge figma push`).");
6603
- const figma = { ...origin.fileKey ? { fileKey: origin.fileKey } : {}, ...origin.nodeId ? { nodeId: origin.nodeId } : {}, ...origin.url ? { url: origin.url } : {} };
6631
+ const figma = { ...origin.fileKey ? { fileKey: origin.fileKey } : {}, ...origin.nodeId ? { nodeId: origin.nodeId } : {}, ...origin.url ? { url: origin.url } : {}, ...figmaNodes(d) };
6604
6632
  const r = await input.writeTokens({ edits: plan.edits, title: figmaBranchTitle(d), summary: d.summary.trim() || d.title, bodyExtra: [figmaOriginSection(origin)], mode, figma });
6605
6633
  await gh.addLabels(r.number, [FROM_FIGMA_LABEL]);
6606
6634
  if (r.status === "suggested") return { status: "suggested", plan, link: null, suggestion: { route: "token-change", number: r.number, url: r.url } };
@@ -11717,7 +11745,7 @@ async function visualImpact(ctx, a, run2) {
11717
11745
  if (ctx.mode !== "local" || !ctx.root) throw new Error("Visual impact needs a local checkout (start Bridge with --root): it builds Storybook and the app at both refs.");
11718
11746
  if (!ctx.isGit) throw new Error(`${ctx.root} is not a git repository.`);
11719
11747
  const out = a.out ? resolve3(ctx.root, a.out) : join5(ctx.root, "visual-impact");
11720
- const runImpact = run2 ?? (await import("./src-ABI7LARD.js")).runImpact;
11748
+ const runImpact = run2 ?? (await import("./src-D6YUOJC7.js")).runImpact;
11721
11749
  const r = await runImpact({
11722
11750
  root: ctx.root,
11723
11751
  base: a.base ?? "main",
@@ -12783,7 +12811,7 @@ async function pushFigmaComponentOp(ctx, a) {
12783
12811
  }
12784
12812
 
12785
12813
  // src/server.ts
12786
- var VERSION = true ? "0.7.0" : "dev";
12814
+ var VERSION = true ? "0.7.2" : "dev";
12787
12815
  var ok = (data) => ({ content: [{ type: "text", text: typeof data === "string" ? data : JSON.stringify(data, null, 2) }] });
12788
12816
  var fail2 = (e) => ({ content: [{ type: "text", text: `Error: ${e.message ?? String(e)}` }], isError: true });
12789
12817
  var run = (fn) => async (a) => {
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  VERSION,
4
4
  optionsFromArgv,
5
5
  serveStdio
6
- } from "./chunk-MFV5WHB5.js";
6
+ } from "./chunk-FAB4HMPJ.js";
7
7
  import "./chunk-BZ3KXXMM.js";
8
8
 
9
9
  // src/index.ts
package/dist/lib.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  serveStdio,
29
29
  studio_exports,
30
30
  suggestions_exports
31
- } from "./chunk-MFV5WHB5.js";
31
+ } from "./chunk-FAB4HMPJ.js";
32
32
  import {
33
33
  CONFIG_FILE,
34
34
  loadConfig,
@@ -127,6 +127,81 @@ function impactedModules(index, input) {
127
127
  return { modules, globalHits };
128
128
  }
129
129
 
130
+ // ../core/src/storybook.ts
131
+ var StorybookIndexError = class extends Error {
132
+ constructor(code, message, status) {
133
+ super(message);
134
+ this.code = code;
135
+ this.status = status;
136
+ this.name = "StorybookIndexError";
137
+ }
138
+ code;
139
+ status;
140
+ };
141
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
142
+ var str = (v) => typeof v === "string" && v.trim() ? v : void 0;
143
+ function entryFrom(key, raw, v3) {
144
+ if (!isObj(raw)) return null;
145
+ const id = str(raw.id) ?? key;
146
+ const title = str(raw.title) ?? str(raw.kind);
147
+ const name = str(raw.name) ?? str(raw.story);
148
+ if (!id || !title || !name) return null;
149
+ const params = isObj(raw.parameters) ? raw.parameters : {};
150
+ const type = raw.type === "docs" || raw.type === "story" ? raw.type : v3 && params.docsOnly === true ? "docs" : "story";
151
+ const e = {
152
+ id,
153
+ title,
154
+ name,
155
+ type,
156
+ importPath: str(raw.importPath) ?? str(params.fileName) ?? "",
157
+ tags: Array.isArray(raw.tags) ? raw.tags.filter((t) => typeof t === "string") : []
158
+ };
159
+ const subtype = str(raw.subtype);
160
+ if (subtype) e.subtype = subtype;
161
+ const exportName = str(raw.exportName);
162
+ if (exportName) e.exportName = exportName;
163
+ const componentPath = str(raw.componentPath);
164
+ if (componentPath) e.componentPath = componentPath;
165
+ return e;
166
+ }
167
+ function groupStorybookComponents(entries) {
168
+ const byTitle = /* @__PURE__ */ new Map();
169
+ for (const e of entries) {
170
+ let c = byTitle.get(e.title);
171
+ if (!c) {
172
+ const parts = e.title.split("/").map((p) => p.trim()).filter(Boolean);
173
+ c = { title: e.title, name: parts[parts.length - 1] ?? e.title, group: parts.slice(0, -1), stories: [], docs: [], tags: [] };
174
+ byTitle.set(e.title, c);
175
+ }
176
+ if (e.type === "docs") c.docs.push(e);
177
+ else if (e.subtype !== "test") c.stories.push(e);
178
+ for (const t of e.tags) if (!c.tags.includes(t)) c.tags.push(t);
179
+ }
180
+ const out = [...byTitle.values()].filter((c) => c.stories.length || c.docs.length);
181
+ for (const c of out) c.tags.sort();
182
+ return out.sort((a, b) => a.title.localeCompare(b.title));
183
+ }
184
+ function parseStorybookIndex(json, source) {
185
+ if (!isObj(json)) throw new StorybookIndexError("not-json", "The Storybook index is not a JSON object.");
186
+ if (typeof json.loginUrl === "string" && !("entries" in json) && !("stories" in json)) {
187
+ throw new StorybookIndexError("private", "This Storybook is private: it asks for a sign-in before showing its index.");
188
+ }
189
+ const map = isObj(json.entries) ? json.entries : isObj(json.stories) ? json.stories : null;
190
+ if (!map) throw new StorybookIndexError("empty", "This file has no Storybook entries.");
191
+ const v3 = !isObj(json.entries);
192
+ const entries = Object.entries(map).map(([k, raw]) => entryFrom(k, raw, v3)).filter((e) => e !== null);
193
+ if (!entries.length) throw new StorybookIndexError("empty", "This Storybook index lists no stories or docs.");
194
+ const version = typeof json.v === "number" ? json.v : v3 ? 3 : 4;
195
+ return { version, source: source ?? (v3 ? "stories.json" : "index.json"), entries, components: groupStorybookComponents(entries) };
196
+ }
197
+ var pairs = (o) => Object.entries(o).map(([k, v]) => `${encodeURIComponent(k)}:${encodeURIComponent(v)}`).join(";");
198
+ function storybookIframeUrl(base, id, o = {}) {
199
+ let url = `${base.replace(/\/+$/, "")}/iframe.html?id=${encodeURIComponent(id)}&viewMode=${o.viewMode ?? "story"}`;
200
+ if (o.globals && Object.keys(o.globals).length) url += `&globals=${pairs(o.globals)}`;
201
+ if (o.args && Object.keys(o.args).length) url += `&args=${pairs(o.args)}`;
202
+ return url;
203
+ }
204
+
130
205
  // ../visual/src/capture.ts
131
206
  import { mkdir, writeFile } from "node:fs/promises";
132
207
  import { dirname } from "node:path";
@@ -521,7 +596,7 @@ function routeUrl(base, route, tenant, theme) {
521
596
  return `${base.replace(/\/$/, "")}/?tenant=${encodeURIComponent(tenant)}&theme=${encodeURIComponent(theme)}&density=comfortable&lang=en#${route.pathByTenant?.[tenant] ?? route.path}`;
522
597
  }
523
598
  function storyUrl(base, id, brand, theme) {
524
- return `${base.replace(/\/$/, "")}/iframe.html?id=${encodeURIComponent(id)}&viewMode=story&globals=brand:${brand};theme:${theme};density:comfortable`;
599
+ return storybookIframeUrl(base, id, { globals: { brand, theme, density: "comfortable" } });
525
600
  }
526
601
  var shotKey = (...parts) => parts.map((p) => p.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "")).join("__");
527
602
 
@@ -812,8 +887,8 @@ function changedTokens(before, after) {
812
887
  return [...modes.entries()].map(([path, m]) => ({ path, kind: direct.has(path) ? "direct" : "alias", modes: m })).sort((a, b) => Number(a.kind === "alias") - Number(b.kind === "alias") || a.path.localeCompare(b.path));
813
888
  }
814
889
  async function readIndexJson(dir) {
815
- const raw = JSON.parse(await readFile3(join3(dir, "index.json"), "utf8"));
816
- return Object.values(raw.entries).filter((e) => e.type === "story").map(({ id, title, name, importPath }) => ({ id, title, name, importPath }));
890
+ const index = parseStorybookIndex(JSON.parse(await readFile3(join3(dir, "index.json"), "utf8")), "index.json");
891
+ return index.entries.filter((e) => e.type === "story").map(({ id, title, name, importPath }) => ({ id, title, name, importPath }));
817
892
  }
818
893
  async function planImpact(o) {
819
894
  const root = resolve(o.root);
@@ -154,6 +154,7 @@ export declare function getSuggestionOp(ctx: BridgeContext, a: {
154
154
  fileKey?: string | undefined;
155
155
  nodeId?: string | undefined;
156
156
  url?: string | undefined;
157
+ nodes?: import("@bridge-ds/core").SuggestionFigmaNode[] | undefined;
157
158
  } | undefined;
158
159
  exploration?: string | undefined;
159
160
  supersedes?: number | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge-ds/mcp",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "type": "module",
5
5
  "description": "Bridge MCP server: lets Claude (Claude Code, Claude Desktop or any MCP client) read, check and propose changes to a DTCG design system repo through pull requests.",
6
6
  "license": "MIT",
@@ -54,8 +54,8 @@
54
54
  "build:package": "node ../../scripts/build-packages.mjs mcp"
55
55
  },
56
56
  "dependencies": {
57
- "@bridge-ds/core": "^0.10.0",
58
- "@bridge-ds/visual": "^0.3.4",
57
+ "@bridge-ds/core": "^0.12.0",
58
+ "@bridge-ds/visual": "^0.3.6",
59
59
  "@modelcontextprotocol/sdk": "1.31.0",
60
60
  "typescript": "5.9.3",
61
61
  "zod": "3.25.76"