@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-
|
|
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.
|
|
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
package/dist/lib.js
CHANGED
|
@@ -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
|
|
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
|
|
816
|
-
return
|
|
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);
|
package/dist/suggestions.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
58
|
-
"@bridge-ds/visual": "^0.3.
|
|
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"
|