@tempad-dev/mcp 0.4.5 → 0.5.1
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/README.md +1 -1
- package/README.zh-Hans.md +1 -0
- package/dist/cli.mjs +1 -1
- package/dist/hub.mjs +311 -75
- package/dist/hub.mjs.map +1 -1
- package/dist/{shared-C_McgtWb.mjs → shared-BljdV6bG.mjs} +5 -5
- package/dist/{shared-C_McgtWb.mjs.map → shared-BljdV6bG.mjs.map} +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Supported tools/resources:
|
|
|
28
28
|
|
|
29
29
|
Notes:
|
|
30
30
|
|
|
31
|
-
- When a selection is too large for the `get_code` budget, TemPad Dev may return a shell response instead of failing. The shell keeps the current node wrapper and lists omitted direct child ids in an inline code comment so agents can request them one by one.
|
|
31
|
+
- Tool responses use a shared `64 KiB` inline budget measured on the `CallToolResult` body. When a selection is too large for the `get_code` budget, TemPad Dev may return a shell response instead of failing. The shell keeps the current node wrapper and lists omitted direct child ids in an inline code comment so agents can request them one by one. The accompanying warning stays lightweight and only points agents to that comment.
|
|
32
32
|
- Assets are ephemeral and tool-linked; image/SVG bytes are downloaded via HTTP `asset.url` from tool results.
|
|
33
33
|
- Asset resources are not exposed via MCP `resources/list`/`resources/read`.
|
|
34
34
|
- The HTTP fallback URL uses `/assets/{hash}` and may include an image extension (for example `/assets/{hash}.png`). Both forms are accepted.
|
package/README.zh-Hans.md
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
说明:
|
|
28
28
|
|
|
29
|
+
- 工具响应共用 `64 KiB` 的 inline budget,按 `CallToolResult` 整体响应体积计算。若选区过大而超出 `get_code` 的预算,TemPad Dev 可能返回 shell response 而不是直接失败。shell 会保留当前节点的包裹结构,并在内联代码注释中列出被省略的直接子节点 id,方便 agent 逐个继续拉取;配套 warning 只保留最小化的提示信息,用来指向这条注释。
|
|
29
30
|
- 资源是临时且与工具调用关联的;图片/SVG 请直接使用工具结果中的 HTTP `asset.url` 下载。
|
|
30
31
|
- MCP 不再暴露 `resources/list` / `resources/read` 用于 asset 内容读取。
|
|
31
32
|
- HTTP 回退 URL 使用 `/assets/{hash}`,也可能带图片扩展名(例如 `/assets/{hash}.png`),两种形式都支持。
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as SOCK_PATH, c as log, i as RUNTIME_DIR, n as LOCK_PATH, o as ensureDir, r as PACKAGE_VERSION } from "./shared-
|
|
2
|
+
import { a as SOCK_PATH, c as log, i as RUNTIME_DIR, n as LOCK_PATH, o as ensureDir, r as PACKAGE_VERSION } from "./shared-BljdV6bG.mjs";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { connect } from "node:net";
|
|
5
5
|
import { join } from "node:path";
|
package/dist/hub.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as SOCK_PATH, c as log, i as RUNTIME_DIR, o as ensureDir, r as PACKAGE_VERSION, s as ensureFile, t as ASSET_DIR } from "./shared-
|
|
1
|
+
import { a as SOCK_PATH, c as log, i as RUNTIME_DIR, o as ensureDir, r as PACKAGE_VERSION, s as ensureFile, t as ASSET_DIR } from "./shared-BljdV6bG.mjs";
|
|
2
2
|
import { createServer } from "node:net";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { URL } from "node:url";
|
|
@@ -19,6 +19,7 @@ const MCP_PORT_CANDIDATES = [
|
|
|
19
19
|
8127
|
|
20
20
|
];
|
|
21
21
|
const MCP_MAX_PAYLOAD_BYTES = 4 * 1024 * 1024;
|
|
22
|
+
const MCP_TOOL_INLINE_BUDGET_BYTES = 64 * 1024;
|
|
22
23
|
const MCP_TOOL_TIMEOUT_MS = 15e3;
|
|
23
24
|
const MCP_AUTO_ACTIVATE_GRACE_MS = 1500;
|
|
24
25
|
const MCP_MAX_ASSET_BYTES = 8 * 1024 * 1024;
|
|
@@ -34,6 +35,118 @@ const TEMPAD_MCP_ERROR_CODES = {
|
|
|
34
35
|
ASSET_SERVER_NOT_CONFIGURED: "ASSET_SERVER_NOT_CONFIGURED",
|
|
35
36
|
TRANSPORT_NOT_CONNECTED: "TRANSPORT_NOT_CONNECTED"
|
|
36
37
|
};
|
|
38
|
+
const SERVER_NAME = "tempad-dev";
|
|
39
|
+
const SERVER_COMMAND = "npx";
|
|
40
|
+
const SERVER_ARGS = ["-y", "@tempad-dev/mcp@latest"];
|
|
41
|
+
const stdioConfig = {
|
|
42
|
+
type: "stdio",
|
|
43
|
+
command: SERVER_COMMAND,
|
|
44
|
+
args: [...SERVER_ARGS]
|
|
45
|
+
};
|
|
46
|
+
const commandConfig = {
|
|
47
|
+
command: SERVER_COMMAND,
|
|
48
|
+
args: [...SERVER_ARGS]
|
|
49
|
+
};
|
|
50
|
+
function toBase64(input) {
|
|
51
|
+
if (typeof globalThis.btoa === "function") return globalThis.btoa(input);
|
|
52
|
+
const bufferLike = globalThis.Buffer;
|
|
53
|
+
if (bufferLike) return bufferLike.from(input, "utf8").toString("base64");
|
|
54
|
+
throw new Error("Base64 encoding not supported in this environment.");
|
|
55
|
+
}
|
|
56
|
+
function buildVscodeDeepLink() {
|
|
57
|
+
return `vscode:mcp/install?${encodeURIComponent(JSON.stringify({
|
|
58
|
+
name: SERVER_NAME,
|
|
59
|
+
...stdioConfig
|
|
60
|
+
}))}`;
|
|
61
|
+
}
|
|
62
|
+
function buildCursorConfigBase64() {
|
|
63
|
+
return encodeURIComponent(toBase64(JSON.stringify(commandConfig)));
|
|
64
|
+
}
|
|
65
|
+
function buildCursorDeepLink() {
|
|
66
|
+
return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(SERVER_NAME)}&config=${buildCursorConfigBase64()}`;
|
|
67
|
+
}
|
|
68
|
+
function buildTraeDeepLink(protocol) {
|
|
69
|
+
return `${protocol}://trae.ai-ide/mcp-import?type=stdio&name=${encodeURIComponent(SERVER_NAME)}&config=${buildCursorConfigBase64()}`;
|
|
70
|
+
}
|
|
71
|
+
function buildWindsurfConfigSnippet() {
|
|
72
|
+
return JSON.stringify({ mcpServers: { [SERVER_NAME]: commandConfig } }, null, 2);
|
|
73
|
+
}
|
|
74
|
+
function buildCodexConfigSnippet() {
|
|
75
|
+
return [
|
|
76
|
+
`[mcp_servers.${SERVER_NAME}]`,
|
|
77
|
+
`command = ${JSON.stringify(SERVER_COMMAND)}`,
|
|
78
|
+
`args = [${SERVER_ARGS.map((arg) => JSON.stringify(arg)).join(", ")}]`
|
|
79
|
+
].join("\n");
|
|
80
|
+
}
|
|
81
|
+
function buildCliCommand(prefix) {
|
|
82
|
+
const args = `${SERVER_COMMAND} ${SERVER_ARGS.join(" ")}`;
|
|
83
|
+
if (prefix === "claude") return `claude mcp add --transport stdio "${SERVER_NAME}" -- ${args}`;
|
|
84
|
+
return `codex mcp add "${SERVER_NAME}" -- ${args}`;
|
|
85
|
+
}
|
|
86
|
+
const MCP_SERVER = {
|
|
87
|
+
name: SERVER_NAME,
|
|
88
|
+
command: SERVER_COMMAND,
|
|
89
|
+
args: [...SERVER_ARGS]
|
|
90
|
+
};
|
|
91
|
+
const MCP_DEFAULT_CONFIG_SNIPPET = JSON.stringify({ [SERVER_NAME]: commandConfig }, null, 2);
|
|
92
|
+
const MCP_CLIENTS_BY_ID = {
|
|
93
|
+
vscode: {
|
|
94
|
+
id: "vscode",
|
|
95
|
+
name: "VS Code",
|
|
96
|
+
brandColor: "#0098ff",
|
|
97
|
+
supportsDeepLink: true,
|
|
98
|
+
deepLink: buildVscodeDeepLink()
|
|
99
|
+
},
|
|
100
|
+
cursor: {
|
|
101
|
+
id: "cursor",
|
|
102
|
+
name: "Cursor",
|
|
103
|
+
brandColor: ["#000", "#fff"],
|
|
104
|
+
supportsDeepLink: true,
|
|
105
|
+
deepLink: buildCursorDeepLink()
|
|
106
|
+
},
|
|
107
|
+
windsurf: {
|
|
108
|
+
id: "windsurf",
|
|
109
|
+
name: "Windsurf",
|
|
110
|
+
brandColor: ["#0B100F", "#F0F3F2"],
|
|
111
|
+
supportsDeepLink: false,
|
|
112
|
+
copyText: buildWindsurfConfigSnippet(),
|
|
113
|
+
copyKind: "config"
|
|
114
|
+
},
|
|
115
|
+
claude: {
|
|
116
|
+
id: "claude",
|
|
117
|
+
name: "Claude Code",
|
|
118
|
+
brandColor: "#D97757",
|
|
119
|
+
supportsDeepLink: false,
|
|
120
|
+
copyText: buildCliCommand("claude"),
|
|
121
|
+
copyKind: "command"
|
|
122
|
+
},
|
|
123
|
+
codex: {
|
|
124
|
+
id: "codex",
|
|
125
|
+
name: "Codex CLI",
|
|
126
|
+
brandColor: ["#0d0d0d", "#fff"],
|
|
127
|
+
supportsDeepLink: false,
|
|
128
|
+
copyText: buildCliCommand("codex"),
|
|
129
|
+
copyKind: "command",
|
|
130
|
+
alternateCopyText: buildCodexConfigSnippet(),
|
|
131
|
+
alternateCopyKind: "config"
|
|
132
|
+
},
|
|
133
|
+
trae: {
|
|
134
|
+
id: "trae",
|
|
135
|
+
name: "TRAE",
|
|
136
|
+
brandColor: ["#0fdc78", "#32f08c"],
|
|
137
|
+
supportsDeepLink: true,
|
|
138
|
+
deepLink: buildTraeDeepLink("trae"),
|
|
139
|
+
fallbackDeepLink: buildTraeDeepLink("trae-cn")
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const MCP_CLIENTS = [
|
|
143
|
+
MCP_CLIENTS_BY_ID.vscode,
|
|
144
|
+
MCP_CLIENTS_BY_ID.cursor,
|
|
145
|
+
MCP_CLIENTS_BY_ID.windsurf,
|
|
146
|
+
MCP_CLIENTS_BY_ID.claude,
|
|
147
|
+
MCP_CLIENTS_BY_ID.codex,
|
|
148
|
+
MCP_CLIENTS_BY_ID.trae
|
|
149
|
+
];
|
|
37
150
|
const RegisteredMessageSchema = z.object({
|
|
38
151
|
type: z.literal("registered"),
|
|
39
152
|
id: z.string()
|
|
@@ -67,18 +180,92 @@ const ToolResultMessageSchema = z.object({
|
|
|
67
180
|
error: z.unknown().optional()
|
|
68
181
|
});
|
|
69
182
|
const MessageFromExtensionSchema = z.discriminatedUnion("type", [ActivateMessageSchema, ToolResultMessageSchema]);
|
|
183
|
+
const ENCODER = new TextEncoder();
|
|
184
|
+
function utf8Bytes(value) {
|
|
185
|
+
return ENCODER.encode(serializeUtf8Value(value)).length;
|
|
186
|
+
}
|
|
187
|
+
function measureCallToolResultBytes(result) {
|
|
188
|
+
return utf8Bytes(result);
|
|
189
|
+
}
|
|
190
|
+
function buildGetCodeToolResult(payload) {
|
|
191
|
+
const summary = [];
|
|
192
|
+
const codeSize = utf8Bytes(payload.code);
|
|
193
|
+
summary.push(`Generated \`${payload.lang}\` snippet (${formatBytes(codeSize)}).`);
|
|
194
|
+
if (payload.warnings?.length) summary.push(...payload.warnings.map((warning) => warning.message));
|
|
195
|
+
summary.push(payload.assets?.length ? `Assets attached: ${payload.assets.length}. Download bytes from each asset.url.` : "No binary assets were attached to this response.");
|
|
196
|
+
const tokenCount = payload.tokens ? Object.keys(payload.tokens).length : 0;
|
|
197
|
+
if (tokenCount) summary.push(`Token references included: ${tokenCount}.`);
|
|
198
|
+
summary.push("Read structuredContent for the full code string and metadata.");
|
|
199
|
+
return buildTextToolResult(summary.join("\n"), payload);
|
|
200
|
+
}
|
|
201
|
+
function buildGetStructureToolResult(payload) {
|
|
202
|
+
const roots = payload.roots.length;
|
|
203
|
+
const nodeCount = countOutlineNodes(payload.roots);
|
|
204
|
+
return buildTextToolResult(`${roots === 0 ? "No structure nodes were returned." : `Returned structure outline with ${formatCount(roots, "root")} and ${formatCount(nodeCount, "node")}.`}\nRead structuredContent for the full outline payload.`, payload);
|
|
205
|
+
}
|
|
206
|
+
function buildGetTokenDefsToolResult(payload) {
|
|
207
|
+
const count = Object.keys(payload).length;
|
|
208
|
+
return buildTextToolResult(`${count === 0 ? "No token definitions were resolved." : `Resolved ${formatCount(count, "token definition")}.`}\nRead structuredContent for token values and aliases.`, payload);
|
|
209
|
+
}
|
|
210
|
+
function buildGetScreenshotToolResult(payload) {
|
|
211
|
+
return buildTextToolResult(`${describeScreenshot(payload)} - Download: ${payload.asset.url}`, payload);
|
|
212
|
+
}
|
|
213
|
+
function buildGetAssetsToolResult(payload) {
|
|
214
|
+
const summary = [];
|
|
215
|
+
summary.push(payload.assets.length ? `Resolved ${formatCount(payload.assets.length, "asset")}.` : "No assets were resolved for the requested hashes.");
|
|
216
|
+
if (payload.missing.length) summary.push(`Missing: ${payload.missing.join(", ")}`);
|
|
217
|
+
summary.push("Download bytes from each asset.url.");
|
|
218
|
+
return buildTextToolResult(summary.join("\n"), payload);
|
|
219
|
+
}
|
|
220
|
+
function buildTextToolResult(text, structuredContent) {
|
|
221
|
+
return {
|
|
222
|
+
content: [{
|
|
223
|
+
type: "text",
|
|
224
|
+
text
|
|
225
|
+
}],
|
|
226
|
+
structuredContent
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function serializeUtf8Value(value) {
|
|
230
|
+
if (typeof value === "string") return value;
|
|
231
|
+
return JSON.stringify(value, null, 0) ?? "undefined";
|
|
232
|
+
}
|
|
233
|
+
function countOutlineNodes(nodes) {
|
|
234
|
+
let count = 0;
|
|
235
|
+
const stack = [...nodes];
|
|
236
|
+
while (stack.length) {
|
|
237
|
+
const current = stack.pop();
|
|
238
|
+
if (!current) continue;
|
|
239
|
+
count += 1;
|
|
240
|
+
if (current.children?.length) stack.push(...current.children);
|
|
241
|
+
}
|
|
242
|
+
return count;
|
|
243
|
+
}
|
|
244
|
+
function formatBytes(bytes) {
|
|
245
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
246
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
247
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
248
|
+
}
|
|
249
|
+
function formatCount(count, singular) {
|
|
250
|
+
return `${count} ${count === 1 ? singular : `${singular}s`}`;
|
|
251
|
+
}
|
|
252
|
+
function describeScreenshot(result) {
|
|
253
|
+
return `Screenshot ${result.width}x${result.height} @${result.scale}x (${formatBytes(result.bytes)})`;
|
|
254
|
+
}
|
|
70
255
|
const AssetDescriptorSchema = z.object({
|
|
71
256
|
hash: z.string().min(1),
|
|
72
257
|
url: z.string().url(),
|
|
73
258
|
mimeType: z.string().min(1),
|
|
74
259
|
size: z.number().int().nonnegative(),
|
|
75
260
|
width: z.number().int().positive().optional(),
|
|
76
|
-
height: z.number().int().positive().optional()
|
|
261
|
+
height: z.number().int().positive().optional(),
|
|
262
|
+
themeable: z.boolean().optional()
|
|
77
263
|
});
|
|
78
264
|
const GetCodeParametersSchema = z.object({
|
|
79
265
|
nodeId: z.string().describe("Optional target node id; omit to use the current single selection when pulling the baseline snapshot.").optional(),
|
|
80
266
|
preferredLang: z.enum(["jsx", "vue"]).describe("Preferred output language to bias the snapshot; otherwise uses the design’s hint/detected language, then falls back to JSX.").optional(),
|
|
81
|
-
resolveTokens: z.boolean().describe("Inline token values instead of references for quick renders; default false returns token metadata so you can map into your theming system. When true, values are resolved per-node (mode-aware).").optional()
|
|
267
|
+
resolveTokens: z.boolean().describe("Inline token values instead of references for quick renders; default false returns token metadata so you can map into your theming system. When true, values are resolved per-node (mode-aware).").optional(),
|
|
268
|
+
vectorMode: z.enum(["smart", "snapshot"]).describe("Vector output mode. `smart` (default) emits `<svg data-src=\"...\">` placeholders in code and preserves themeable instance color on the emitted SVG root markup for downstream adaptation; if asset upload fails after export, the tool may inline the SVG as a fallback to preserve source of truth. `snapshot` preserves vector assets for fidelity. Final vector delivery may still be adapted to the Host app’s SVG policy.").optional()
|
|
82
269
|
});
|
|
83
270
|
const GetTokenDefsParametersSchema = z.object({
|
|
84
271
|
names: z.array(z.string().regex(/^--[a-zA-Z0-9-_]+$/)).min(1).describe("Canonical token names (CSS variable form) from Object.keys(get_code.tokens) or your own list to resolve, e.g., --color-primary."),
|
|
@@ -208,7 +395,7 @@ function createAssetHttpServer(store) {
|
|
|
208
395
|
});
|
|
209
396
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
210
397
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
211
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Asset-Width, X-Asset-Height");
|
|
398
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Asset-Width, X-Asset-Height, X-Asset-Themeable");
|
|
212
399
|
if (req.method === "OPTIONS") {
|
|
213
400
|
res.writeHead(204);
|
|
214
401
|
res.end();
|
|
@@ -291,10 +478,13 @@ function createAssetHttpServer(store) {
|
|
|
291
478
|
const filePath = join(ASSET_DIR, buildAssetFilename(hash, mimeType));
|
|
292
479
|
const width = parseInt(req.headers["x-asset-width"], 10);
|
|
293
480
|
const height = parseInt(req.headers["x-asset-height"], 10);
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
481
|
+
const themeableHeader = req.headers["x-asset-themeable"];
|
|
482
|
+
const themeable = (Array.isArray(themeableHeader) ? themeableHeader[0] : themeableHeader) === "true";
|
|
483
|
+
const metadata = {};
|
|
484
|
+
if (Number.isFinite(width) && width > 0) metadata.width = width;
|
|
485
|
+
if (Number.isFinite(height) && height > 0) metadata.height = height;
|
|
486
|
+
if (themeable) metadata.themeable = true;
|
|
487
|
+
const assetMetadata = Object.keys(metadata).length ? metadata : void 0;
|
|
298
488
|
const existing = store.get(hash);
|
|
299
489
|
if (existing) {
|
|
300
490
|
let existingPath = existing.filePath;
|
|
@@ -313,7 +503,10 @@ function createAssetHttpServer(store) {
|
|
|
313
503
|
}, "Failed to rename existing asset to include extension.");
|
|
314
504
|
}
|
|
315
505
|
req.resume();
|
|
316
|
-
if (
|
|
506
|
+
if (assetMetadata) existing.metadata = {
|
|
507
|
+
...existing.metadata,
|
|
508
|
+
...assetMetadata
|
|
509
|
+
};
|
|
317
510
|
if (existing.mimeType !== mimeType) existing.mimeType = mimeType;
|
|
318
511
|
existing.lastAccess = Date.now();
|
|
319
512
|
store.upsert(existing);
|
|
@@ -380,7 +573,7 @@ function createAssetHttpServer(store) {
|
|
|
380
573
|
filePath,
|
|
381
574
|
mimeType,
|
|
382
575
|
size,
|
|
383
|
-
metadata
|
|
576
|
+
metadata: assetMetadata
|
|
384
577
|
});
|
|
385
578
|
log.info({
|
|
386
579
|
hash,
|
|
@@ -576,7 +769,7 @@ function createAssetStore(options = {}) {
|
|
|
576
769
|
|
|
577
770
|
//#endregion
|
|
578
771
|
//#region src/instructions.md?raw
|
|
579
|
-
var instructions_default = "You are connected to a Figma design file via TemPad Dev MCP.\n\nTreat tool outputs as design facts. Refactor only to match the user’s repo conventions; do not invent key style values.\n\nRules:\n\n- Never output any `data-hint-*` attributes from tool outputs (hints only).\n- If `get_code` warns `depth-cap`,
|
|
772
|
+
var instructions_default = "You are connected to a Figma design file via TemPad Dev MCP.\n\nTreat tool outputs as design facts. Refactor only to match the user’s repo conventions; do not invent key style values.\n\nRules:\n\n- Never output any `data-hint-*` attributes from tool outputs (hints only).\n- If `get_code` warns `depth-cap`, keep the returned parent code as composition evidence and use returned `data-hint-id` values to choose narrower `get_code` follow-ups.\n- If `get_code` warns `shell`, read the inline code comment for omitted direct child ids, then call `get_code` for those ids in order and fill the results back into the returned shell.\n- Use `get_structure` only to resolve layout/overlap uncertainty; do not derive numeric values from images.\n- Tokens: `get_code.tokens` keys are canonical names (`--...`). Multi‑mode values use `${collectionName}:${modeName}`. Nodes may hint per-node overrides via `data-hint-variable-mode=\"Collection=Mode;...\"`.\n- Vectors: `vectorMode=smart` is the default. Treat the emitted markup as the source of truth for the current response; vector code is emitted as `<svg data-src=\"...\">` placeholders, but if asset upload fails after export the tool may inline the SVG as a fallback to preserve source of truth.\n- Themeable vectors: `themeable=true` means the SVG can safely adopt one contextual color channel. In `smart` mode, that color is typically already evidenced on the emitted `svg` root markup for the placeholder. It does not mean the SVG exposes multiple independent color parameters.\n- Assets: download bytes via `asset.url`. Asset resources are not exposed via MCP `resources/read`. Use `asset.themeable` only when an SVG still needs repo asset handling after you account for the Host app's vector policy.\n";
|
|
580
773
|
|
|
581
774
|
//#endregion
|
|
582
775
|
//#region src/request.ts
|
|
@@ -648,6 +841,21 @@ function cleanupAll() {
|
|
|
648
841
|
|
|
649
842
|
//#endregion
|
|
650
843
|
//#region src/tools.ts
|
|
844
|
+
const CONNECTIVITY_ERROR_CODES = new Set([
|
|
845
|
+
TEMPAD_MCP_ERROR_CODES.NO_ACTIVE_EXTENSION,
|
|
846
|
+
TEMPAD_MCP_ERROR_CODES.EXTENSION_TIMEOUT,
|
|
847
|
+
TEMPAD_MCP_ERROR_CODES.EXTENSION_DISCONNECTED,
|
|
848
|
+
TEMPAD_MCP_ERROR_CODES.ASSET_SERVER_NOT_CONFIGURED,
|
|
849
|
+
TEMPAD_MCP_ERROR_CODES.TRANSPORT_NOT_CONNECTED
|
|
850
|
+
]);
|
|
851
|
+
const SELECTION_ERROR_CODES = new Set([TEMPAD_MCP_ERROR_CODES.INVALID_SELECTION, TEMPAD_MCP_ERROR_CODES.NODE_NOT_VISIBLE]);
|
|
852
|
+
const CONNECTIVITY_TROUBLESHOOTING_LINES = [
|
|
853
|
+
"Troubleshooting:",
|
|
854
|
+
"- In Figma, open TemPad Dev panel and enable MCP (Preferences → MCP server).",
|
|
855
|
+
"- If multiple Figma tabs are open, click the MCP badge to activate this tab.",
|
|
856
|
+
"- Keep the Figma tab active/foreground while running MCP tools."
|
|
857
|
+
];
|
|
858
|
+
const SELECTION_TROUBLESHOOTING_LINE = "Tip: Select exactly one visible node, or pass nodeId.";
|
|
651
859
|
function getRecordProperty$1(record, key) {
|
|
652
860
|
if (!record || typeof record !== "object") return;
|
|
653
861
|
return Reflect.get(record, key);
|
|
@@ -661,7 +869,7 @@ function hubTool(definition) {
|
|
|
661
869
|
const TOOL_DEFS = [
|
|
662
870
|
extTool({
|
|
663
871
|
name: "get_code",
|
|
664
|
-
description: "High-fidelity code snapshot for nodeId/current single selection (omit nodeId to use selection): JSX/Vue markup + Tailwind-like classes, plus assets/tokens metadata and codegen config. Start here, then refactor into repo conventions while preserving values/intent; strip any data-hint-* attributes (hints only). If warnings include depth-cap,
|
|
872
|
+
description: "High-fidelity code snapshot for nodeId/current single selection (omit nodeId to use selection): JSX/Vue markup + Tailwind-like classes, plus assets/tokens metadata and codegen config. `vectorMode=smart` (default) emits `<svg data-src=\"...\">` placeholders in code and preserves themeable instance color on the emitted SVG root markup for downstream adaptation; if asset upload fails after export, the tool may inline the SVG as a fallback to preserve source of truth. `vectorMode=snapshot` preserves vector assets for fidelity. Host apps should still refactor vector delivery to repo policy where needed (existing icon/component primitives, import-time SVG transforms, inline SVG, or asset-backed SVG usage). SVG asset metadata may include `themeable=true`, meaning the exported asset can safely adopt one contextual color channel. Start here, then refactor into repo conventions while preserving values/intent; strip any data-hint-* attributes (hints only). If warnings include depth-cap, use returned data-hint-id values to continue with narrower get_code calls. If warnings include shell, read the inline comment for omitted direct child ids and fetch them in order. If warnings include auto-layout (inferred), use get_structure to confirm hierarchy/overlap (do not derive numeric values from pixels). Tokens are keyed by canonical names like `--color-primary` (multi-mode keys use `${collection}:${mode}`; node overrides may appear as data-hint-variable-mode).",
|
|
665
873
|
parameters: GetCodeParametersSchema,
|
|
666
874
|
target: "extension",
|
|
667
875
|
format: createCodeToolResponse
|
|
@@ -671,6 +879,7 @@ const TOOL_DEFS = [
|
|
|
671
879
|
description: "Resolve canonical token names to literal values (optionally including all modes) for tokens referenced by get_code.",
|
|
672
880
|
parameters: GetTokenDefsParametersSchema,
|
|
673
881
|
target: "extension",
|
|
882
|
+
format: createTokenDefsToolResponse,
|
|
674
883
|
exposed: false
|
|
675
884
|
}),
|
|
676
885
|
extTool({
|
|
@@ -685,11 +894,12 @@ const TOOL_DEFS = [
|
|
|
685
894
|
name: "get_structure",
|
|
686
895
|
description: "Get a compact structural + geometry outline for nodeId/current single selection to understand hierarchy and layout intent.",
|
|
687
896
|
parameters: GetStructureParametersSchema,
|
|
688
|
-
target: "extension"
|
|
897
|
+
target: "extension",
|
|
898
|
+
format: createStructureToolResponse
|
|
689
899
|
}),
|
|
690
900
|
hubTool({
|
|
691
901
|
name: "get_assets",
|
|
692
|
-
description: "Resolve asset hashes to downloadable URLs and metadata for assets referenced by tool responses
|
|
902
|
+
description: "Resolve asset hashes to downloadable URLs and metadata for assets referenced by tool responses. SVG asset metadata may include `themeable=true` when the underlying vector can safely adopt one contextual color channel.",
|
|
693
903
|
parameters: GetAssetsParametersSchema,
|
|
694
904
|
target: "hub",
|
|
695
905
|
outputSchema: GetAssetsResultSchema,
|
|
@@ -718,53 +928,37 @@ function createToolErrorResponse(toolName, error) {
|
|
|
718
928
|
isError: true,
|
|
719
929
|
content: [{
|
|
720
930
|
type: "text",
|
|
721
|
-
text: `Tool "${toolName}" failed${code ? ` [${code}]` : ""}: ${message}${(
|
|
722
|
-
const help = [];
|
|
723
|
-
if (code === TEMPAD_MCP_ERROR_CODES.NO_ACTIVE_EXTENSION || code === TEMPAD_MCP_ERROR_CODES.EXTENSION_TIMEOUT || code === TEMPAD_MCP_ERROR_CODES.EXTENSION_DISCONNECTED || code === TEMPAD_MCP_ERROR_CODES.ASSET_SERVER_NOT_CONFIGURED || code === TEMPAD_MCP_ERROR_CODES.TRANSPORT_NOT_CONNECTED || /no active tempad dev extension/i.test(message) || /asset server url is not configured/i.test(message) || /mcp transport is not connected/i.test(message) || /websocket/i.test(message)) help.push("Troubleshooting:", "- In Figma, open TemPad Dev panel and enable MCP (Preferences → MCP server).", "- If multiple Figma tabs are open, click the MCP badge to activate this tab.", "- Keep the Figma tab active/foreground while running MCP tools.");
|
|
724
|
-
if (code === TEMPAD_MCP_ERROR_CODES.INVALID_SELECTION || code === TEMPAD_MCP_ERROR_CODES.NODE_NOT_VISIBLE || /select exactly one visible node/i.test(message) || /no visible node found/i.test(message)) help.push("Tip: Select exactly one visible node, or pass nodeId.");
|
|
725
|
-
return help.length ? `\n\n${help.join("\n")}` : "";
|
|
726
|
-
})()}`
|
|
931
|
+
text: `Tool "${toolName}" failed${code ? ` [${code}]` : ""}: ${message}${buildTroubleshootingText(code, message)}`
|
|
727
932
|
}]
|
|
728
933
|
};
|
|
729
934
|
}
|
|
730
|
-
function
|
|
731
|
-
|
|
732
|
-
if (
|
|
733
|
-
|
|
935
|
+
function buildTroubleshootingText(code, message) {
|
|
936
|
+
const help = [];
|
|
937
|
+
if (isConnectivityToolError(code, message)) help.push(...CONNECTIVITY_TROUBLESHOOTING_LINES);
|
|
938
|
+
if (isSelectionToolError(code, message)) help.push(SELECTION_TROUBLESHOOTING_LINE);
|
|
939
|
+
return help.length ? `\n\n${help.join("\n")}` : "";
|
|
940
|
+
}
|
|
941
|
+
function isConnectivityToolError(code, message) {
|
|
942
|
+
return (code ? CONNECTIVITY_ERROR_CODES.has(code) : false) || /no active tempad dev extension/i.test(message) || /asset server url is not configured/i.test(message) || /mcp transport is not connected/i.test(message) || /websocket/i.test(message);
|
|
943
|
+
}
|
|
944
|
+
function isSelectionToolError(code, message) {
|
|
945
|
+
return (code ? SELECTION_ERROR_CODES.has(code) : false) || /select exactly one visible node/i.test(message) || /no visible node found/i.test(message);
|
|
734
946
|
}
|
|
735
947
|
function createCodeToolResponse(payload) {
|
|
736
948
|
if (!isCodeResult(payload)) throw new Error("Invalid get_code payload received from extension.");
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
if (payload.
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
if (tokenCount) summary.push(`Token references included: ${tokenCount}.`);
|
|
747
|
-
summary.push("Read structuredContent for the full code string and asset metadata.");
|
|
748
|
-
return {
|
|
749
|
-
content: [{
|
|
750
|
-
type: "text",
|
|
751
|
-
text: summary.join("\n")
|
|
752
|
-
}],
|
|
753
|
-
structuredContent: payload
|
|
754
|
-
};
|
|
949
|
+
return toCallToolResult(buildGetCodeToolResult(payload));
|
|
950
|
+
}
|
|
951
|
+
function createStructureToolResponse(payload) {
|
|
952
|
+
if (!isStructureResult(payload)) throw new Error("Invalid get_structure payload received from extension.");
|
|
953
|
+
return toCallToolResult(buildGetStructureToolResult(payload));
|
|
954
|
+
}
|
|
955
|
+
function createTokenDefsToolResponse(payload) {
|
|
956
|
+
if (!isTokenDefsResult(payload)) throw new Error("Invalid get_token_defs payload received from extension.");
|
|
957
|
+
return toCallToolResult(buildGetTokenDefsToolResult(payload));
|
|
755
958
|
}
|
|
756
959
|
function createScreenshotToolResponse(payload) {
|
|
757
960
|
if (!isScreenshotResult(payload)) throw new Error("Invalid get_screenshot payload received from extension.");
|
|
758
|
-
return
|
|
759
|
-
content: [{
|
|
760
|
-
type: "text",
|
|
761
|
-
text: `${describeScreenshot(payload)} - Download: ${payload.asset.url}`
|
|
762
|
-
}],
|
|
763
|
-
structuredContent: payload
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
function describeScreenshot(result) {
|
|
767
|
-
return `Screenshot ${result.width}x${result.height} @${result.scale}x (${formatBytes(result.bytes)})`;
|
|
961
|
+
return toCallToolResult(buildGetScreenshotToolResult(payload));
|
|
768
962
|
}
|
|
769
963
|
function isScreenshotResult(payload) {
|
|
770
964
|
if (typeof payload !== "object" || !payload) return false;
|
|
@@ -776,6 +970,21 @@ function isCodeResult(payload) {
|
|
|
776
970
|
const candidate = payload;
|
|
777
971
|
return typeof candidate.code === "string" && typeof candidate.lang === "string" && (candidate.assets === void 0 || Array.isArray(candidate.assets));
|
|
778
972
|
}
|
|
973
|
+
function isStructureResult(payload) {
|
|
974
|
+
if (typeof payload !== "object" || !payload) return false;
|
|
975
|
+
const candidate = payload;
|
|
976
|
+
return Array.isArray(candidate.roots);
|
|
977
|
+
}
|
|
978
|
+
function isTokenDefsResult(payload) {
|
|
979
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
|
|
980
|
+
for (const value of Object.values(payload)) {
|
|
981
|
+
if (!value || typeof value !== "object") return false;
|
|
982
|
+
const token = value;
|
|
983
|
+
if (typeof token.kind !== "string") return false;
|
|
984
|
+
if (token.value === void 0) return false;
|
|
985
|
+
}
|
|
986
|
+
return true;
|
|
987
|
+
}
|
|
779
988
|
function coercePayloadToToolResponse(payload) {
|
|
780
989
|
if (payload && typeof payload === "object" && Array.isArray(payload.content)) return payload;
|
|
781
990
|
return { content: [{
|
|
@@ -783,6 +992,31 @@ function coercePayloadToToolResponse(payload) {
|
|
|
783
992
|
text: typeof payload === "string" ? payload : JSON.stringify(payload, null, 2)
|
|
784
993
|
}] };
|
|
785
994
|
}
|
|
995
|
+
function createAssetsToolResponse(payload) {
|
|
996
|
+
return toCallToolResult(buildGetAssetsToolResult(payload));
|
|
997
|
+
}
|
|
998
|
+
function createInlineBudgetExceededToolResponse(toolName, actualBytes) {
|
|
999
|
+
return {
|
|
1000
|
+
isError: true,
|
|
1001
|
+
content: [{
|
|
1002
|
+
type: "text",
|
|
1003
|
+
text: `Tool "${toolName}" exceeded the 64 KiB inline budget (${actualBytes} UTF-8 bytes > ${MCP_TOOL_INLINE_BUDGET_BYTES}). ${getBudgetRetryGuidance(toolName)}`
|
|
1004
|
+
}]
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
function toCallToolResult(result) {
|
|
1008
|
+
return result;
|
|
1009
|
+
}
|
|
1010
|
+
function getBudgetRetryGuidance(toolName) {
|
|
1011
|
+
switch (toolName) {
|
|
1012
|
+
case "get_code": return "Reduce selection size or request a smaller nodeId subtree and retry.";
|
|
1013
|
+
case "get_structure": return "Reduce selection size or pass a smaller depth and retry.";
|
|
1014
|
+
case "get_token_defs": return "Reduce requested names or split them into smaller batches and retry.";
|
|
1015
|
+
case "get_screenshot": return "Reduce selection size or scale and retry.";
|
|
1016
|
+
case "get_assets": return "Request fewer hashes in a single call and retry.";
|
|
1017
|
+
default: return "Retry with a narrower request.";
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
786
1020
|
|
|
787
1021
|
//#endregion
|
|
788
1022
|
//#region src/hub.ts
|
|
@@ -887,7 +1121,8 @@ function buildAssetDescriptor(record) {
|
|
|
887
1121
|
mimeType: record.mimeType,
|
|
888
1122
|
size: record.size,
|
|
889
1123
|
width: record.metadata?.width,
|
|
890
|
-
height: record.metadata?.height
|
|
1124
|
+
height: record.metadata?.height,
|
|
1125
|
+
...record.metadata?.themeable ? { themeable: true } : {}
|
|
891
1126
|
};
|
|
892
1127
|
}
|
|
893
1128
|
function createMcpServer() {
|
|
@@ -974,18 +1209,30 @@ function registerLocalTool(mcp, tool) {
|
|
|
974
1209
|
registerToolFn(tool.name, registrationOptions, registerHandler);
|
|
975
1210
|
}
|
|
976
1211
|
function createToolResponse(toolName, payload) {
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1212
|
+
const rawResult = (() => {
|
|
1213
|
+
const definition = getToolDefinition(toolName);
|
|
1214
|
+
if (definition && hasFormatter(definition)) try {
|
|
1215
|
+
const formatter = definition.format;
|
|
1216
|
+
return formatter(payload);
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
log.warn({
|
|
1219
|
+
tool: toolName,
|
|
1220
|
+
error
|
|
1221
|
+
}, "Failed to format tool result; returning raw payload.");
|
|
1222
|
+
return coercePayloadToToolResponse(payload);
|
|
1223
|
+
}
|
|
1224
|
+
return coercePayloadToToolResponse(payload);
|
|
1225
|
+
})();
|
|
1226
|
+
const resultBytes = measureCallToolResultBytes(rawResult);
|
|
1227
|
+
if (resultBytes > MCP_TOOL_INLINE_BUDGET_BYTES) {
|
|
982
1228
|
log.warn({
|
|
983
1229
|
tool: toolName,
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1230
|
+
resultBytes,
|
|
1231
|
+
inlineBudgetBytes: MCP_TOOL_INLINE_BUDGET_BYTES
|
|
1232
|
+
}, "Tool result exceeded inline budget; returning compact error response.");
|
|
1233
|
+
return createInlineBudgetExceededToolResponse(toolName, resultBytes);
|
|
987
1234
|
}
|
|
988
|
-
return
|
|
1235
|
+
return rawResult;
|
|
989
1236
|
}
|
|
990
1237
|
async function handleGetAssets({ hashes }) {
|
|
991
1238
|
if (hashes.length > 100) throw new Error("Too many hashes requested. Limit is 100.");
|
|
@@ -996,21 +1243,10 @@ async function handleGetAssets({ hashes }) {
|
|
|
996
1243
|
return false;
|
|
997
1244
|
});
|
|
998
1245
|
const found = new Set(records.map((record) => record.hash));
|
|
999
|
-
|
|
1246
|
+
return createAssetsToolResponse(GetAssetsResultSchema.parse({
|
|
1000
1247
|
assets: records.map((record) => buildAssetDescriptor(record)),
|
|
1001
1248
|
missing: unique.filter((hash) => !found.has(hash))
|
|
1002
|
-
});
|
|
1003
|
-
const summary = [];
|
|
1004
|
-
summary.push(payload.assets.length ? `Resolved ${payload.assets.length} asset${payload.assets.length === 1 ? "" : "s"}.` : "No assets were resolved for the requested hashes.");
|
|
1005
|
-
if (payload.missing.length) summary.push(`Missing: ${payload.missing.join(", ")}`);
|
|
1006
|
-
summary.push("Download bytes from each asset.url.");
|
|
1007
|
-
return {
|
|
1008
|
-
content: [{
|
|
1009
|
-
type: "text",
|
|
1010
|
-
text: summary.join("\n")
|
|
1011
|
-
}],
|
|
1012
|
-
structuredContent: payload
|
|
1013
|
-
};
|
|
1249
|
+
}));
|
|
1014
1250
|
}
|
|
1015
1251
|
function getActiveId() {
|
|
1016
1252
|
return extensions.find((e) => e.active)?.id ?? null;
|
package/dist/hub.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hub.mjs","names":["createServer","getRecordProperty","MCP_INSTRUCTIONS"],"sources":["../../shared/dist/index.js","../src/asset-utils.ts","../src/config.ts","../src/asset-http-server.ts","../src/asset-store.ts","../src/instructions.md?raw","../src/request.ts","../src/tools.ts","../src/hub.ts"],"sourcesContent":["import { z } from \"zod\";\n\n//#region src/mcp/constants.ts\nconst MCP_PORT_CANDIDATES = [\n\t6220,\n\t7431,\n\t8127\n];\nconst MCP_MAX_PAYLOAD_BYTES = 4 * 1024 * 1024;\nconst MCP_TOOL_TIMEOUT_MS = 15e3;\nconst MCP_AUTO_ACTIVATE_GRACE_MS = 1500;\nconst MCP_MAX_ASSET_BYTES = 8 * 1024 * 1024;\nconst MCP_ASSET_TTL_MS = 720 * 60 * 60 * 1e3;\nconst MCP_HASH_HEX_LENGTH = 8;\nconst MCP_HASH_PATTERN = new RegExp(`^[a-f0-9]{${MCP_HASH_HEX_LENGTH}}$`, \"i\");\n\n//#endregion\n//#region src/mcp/errors.ts\nconst TEMPAD_MCP_ERROR_CODES = {\n\tNO_ACTIVE_EXTENSION: \"NO_ACTIVE_EXTENSION\",\n\tEXTENSION_TIMEOUT: \"EXTENSION_TIMEOUT\",\n\tEXTENSION_DISCONNECTED: \"EXTENSION_DISCONNECTED\",\n\tINVALID_SELECTION: \"INVALID_SELECTION\",\n\tNODE_NOT_VISIBLE: \"NODE_NOT_VISIBLE\",\n\tASSET_SERVER_NOT_CONFIGURED: \"ASSET_SERVER_NOT_CONFIGURED\",\n\tTRANSPORT_NOT_CONNECTED: \"TRANSPORT_NOT_CONNECTED\"\n};\n\n//#endregion\n//#region src/mcp/protocol.ts\nconst RegisteredMessageSchema = z.object({\n\ttype: z.literal(\"registered\"),\n\tid: z.string()\n});\nconst StateMessageSchema = z.object({\n\ttype: z.literal(\"state\"),\n\tactiveId: z.string().nullable(),\n\tcount: z.number().nonnegative(),\n\tport: z.number().positive(),\n\tassetServerUrl: z.string().url()\n});\nconst ToolCallPayloadSchema = z.object({\n\tname: z.string(),\n\targs: z.unknown()\n});\nconst ToolCallMessageSchema = z.object({\n\ttype: z.literal(\"toolCall\"),\n\tid: z.string(),\n\tpayload: ToolCallPayloadSchema\n});\nconst MessageToExtensionSchema = z.discriminatedUnion(\"type\", [\n\tRegisteredMessageSchema,\n\tStateMessageSchema,\n\tToolCallMessageSchema\n]);\nconst ActivateMessageSchema = z.object({ type: z.literal(\"activate\") });\nconst ToolResultMessageSchema = z.object({\n\ttype: z.literal(\"toolResult\"),\n\tid: z.string(),\n\tpayload: z.unknown().optional(),\n\terror: z.unknown().optional()\n});\nconst MessageFromExtensionSchema = z.discriminatedUnion(\"type\", [ActivateMessageSchema, ToolResultMessageSchema]);\nfunction parseJsonWithSchema(data, schema) {\n\tlet parsed;\n\ttry {\n\t\tparsed = JSON.parse(data);\n\t} catch {\n\t\treturn null;\n\t}\n\tconst result = schema.safeParse(parsed);\n\treturn result.success ? result.data : null;\n}\nfunction parseMessageToExtension(data) {\n\treturn parseJsonWithSchema(data, MessageToExtensionSchema);\n}\nfunction parseMessageFromExtension(data) {\n\treturn parseJsonWithSchema(data, MessageFromExtensionSchema);\n}\n\n//#endregion\n//#region src/mcp/tools.ts\nconst AssetDescriptorSchema = z.object({\n\thash: z.string().min(1),\n\turl: z.string().url(),\n\tmimeType: z.string().min(1),\n\tsize: z.number().int().nonnegative(),\n\twidth: z.number().int().positive().optional(),\n\theight: z.number().int().positive().optional()\n});\nconst GetCodeParametersSchema = z.object({\n\tnodeId: z.string().describe(\"Optional target node id; omit to use the current single selection when pulling the baseline snapshot.\").optional(),\n\tpreferredLang: z.enum([\"jsx\", \"vue\"]).describe(\"Preferred output language to bias the snapshot; otherwise uses the design’s hint/detected language, then falls back to JSX.\").optional(),\n\tresolveTokens: z.boolean().describe(\"Inline token values instead of references for quick renders; default false returns token metadata so you can map into your theming system. When true, values are resolved per-node (mode-aware).\").optional()\n});\nconst GetTokenDefsParametersSchema = z.object({\n\tnames: z.array(z.string().regex(/^--[a-zA-Z0-9-_]+$/)).min(1).describe(\"Canonical token names (CSS variable form) from Object.keys(get_code.tokens) or your own list to resolve, e.g., --color-primary.\"),\n\tincludeAllModes: z.boolean().describe(\"Include all token modes (light/dark/etc.) instead of just the active one to mirror responsive tokens; default false.\").optional()\n});\nconst GetScreenshotParametersSchema = z.object({ nodeId: z.string().describe(\"Optional node id to screenshot; defaults to the current single selection. Useful when layout/overlap is uncertain (auto-layout none/inferred).\").optional() });\nconst GetStructureParametersSchema = z.object({\n\tnodeId: z.string().describe(\"Optional node id to outline; defaults to the current single selection. Useful when auto-layout hints are none/inferred or you need explicit geometry for refactors.\").optional(),\n\toptions: z.object({ depth: z.number().int().positive().describe(\"Limit traversal depth; defaults to full tree (subject to safety caps).\").optional() }).optional()\n});\nconst GetAssetsParametersSchema = z.object({ hashes: z.array(z.string().regex(MCP_HASH_PATTERN)).min(1).describe(\"Asset hashes returned from get_code (or other tools) to download/resolve exact bytes for rasterized images or SVGs before routing through your asset pipeline.\") });\nconst GetAssetsResultSchema = z.object({\n\tassets: z.array(AssetDescriptorSchema),\n\tmissing: z.array(z.string().min(1))\n});\n\n//#endregion\n//#region src/figma/color.ts\n/**\n* Color utilities for Figma styles\n*/\n/**\n* Formats a Figma color with opacity to hex notation\n* @param color RGB color object with values 0-1\n* @param opacity Optional opacity value 0-1\n* @returns Hex color string (e.g., \"#FF0000\" or \"#FF0000CC\")\n*/\nfunction formatHexAlpha(color, opacity = 1) {\n\tconst toHex = (n) => {\n\t\treturn Math.min(255, Math.max(0, Math.round(n * 255))).toString(16).padStart(2, \"0\").toUpperCase();\n\t};\n\tconst r = toHex(color.r);\n\tconst g = toHex(color.g);\n\tconst b = toHex(color.b);\n\tif (opacity >= .99) {\n\t\tif (r[0] === r[1] && g[0] === g[1] && b[0] === b[1]) return `#${r[0]}${g[0]}${b[0]}`;\n\t\treturn `#${r}${g}${b}`;\n\t}\n\tconst a = toHex(opacity);\n\tif (r[0] === r[1] && g[0] === g[1] && b[0] === b[1] && a[0] === a[1]) return `#${r[0]}${g[0]}${b[0]}${a[0]}`;\n\treturn `#${r}${g}${b}${a}`;\n}\n\n//#endregion\n//#region src/figma/gradient.ts\nconst RE_NON_ASCII = /\\P{ASCII}+/gu;\nconst RE_QUOTES = /['\"]/g;\nconst RE_SLASH = /\\//g;\nconst RE_SPACE_TAB = /[ \\t]+/g;\nconst RE_WHITESPACE = /\\s+/g;\nconst RE_FAST_PATH = /^[A-Za-z0-9_-]+$/;\nconst RE_BOUND_NON_ALPHANUM = /[^A-Za-z0-9]+/g;\nconst RE_HYPHENS = /-+/g;\nconst RE_BOUND_DIGIT = /([A-Za-z])([0-9])|([0-9])([A-Za-z])/g;\nconst RE_BOUND_CASE = /([a-z])([A-Z])|([A-Z])([A-Z][a-z])/g;\nconst RE_DIGIT = /^\\d+$/;\nconst RE_CAPS = /^[A-Z]+$/;\nconst RE_SINGLE = /^[A-Za-z]$/;\nfunction isVisiblePaint(paint) {\n\treturn !!paint && paint.visible !== false;\n}\nfunction isGradientPaint(paint) {\n\treturn \"gradientStops\" in paint && Array.isArray(paint.gradientStops);\n}\nfunction isSolidPaint(paint) {\n\treturn paint.type === \"SOLID\";\n}\nfunction hasGradientHandlePositions(paint) {\n\treturn \"gradientHandlePositions\" in paint && Array.isArray(paint.gradientHandlePositions);\n}\n/**\n* Resolves gradient from paint array\n* Returns CSS gradient string or null\n*/\nfunction resolveGradientFromPaints(paints, size) {\n\tif (!paints || !Array.isArray(paints)) return null;\n\tconst gradientPaint = paints.find((paint) => isVisiblePaint(paint) && isGradientPaint(paint));\n\tif (!gradientPaint) return null;\n\tconst fillOpacity = typeof gradientPaint.opacity === \"number\" ? gradientPaint.opacity : 1;\n\tconst stops = gradientPaint.gradientStops.map((stop) => {\n\t\tconst pct = formatPercent(stop.position);\n\t\treturn `${formatGradientStopColor(stop, fillOpacity)} ${pct}`;\n\t});\n\tswitch (gradientPaint.type) {\n\t\tcase \"GRADIENT_LINEAR\": {\n\t\t\tconst angle = resolveLinearGradientAngle(gradientPaint, size);\n\t\t\treturn `linear-gradient(${(angle == null ? stops : [`${angle}deg`, ...stops]).join(\", \")})`;\n\t\t}\n\t\tcase \"GRADIENT_RADIAL\":\n\t\tcase \"GRADIENT_DIAMOND\": return `radial-gradient(${stops.join(\", \")})`;\n\t\tcase \"GRADIENT_ANGULAR\": return `conic-gradient(${stops.join(\", \")})`;\n\t\tdefault: return null;\n\t}\n}\n/**\n* Resolves solid color from paint array\n* Returns hex color string or null\n*/\nfunction resolveSolidFromPaints(paints) {\n\tif (!paints || !Array.isArray(paints)) return null;\n\tconst solidPaint = paints.find((paint) => isVisiblePaint(paint) && isSolidPaint(paint));\n\tif (!solidPaint || !solidPaint.color) return null;\n\tconst bound = solidPaint.boundVariables?.color;\n\tif (bound && typeof bound === \"object\" && \"id\" in bound && bound.id) try {\n\t\tconst variable = figma.variables.getVariableById(bound.id);\n\t\tif (variable) {\n\t\t\tconst fallback = formatHexAlpha(solidPaint.color, solidPaint.opacity);\n\t\t\treturn `var(${getVariableCssCustomPropertyName(variable)}, ${fallback})`;\n\t\t}\n\t} catch {}\n\treturn formatHexAlpha(solidPaint.color, solidPaint.opacity);\n}\n/**\n* Formats a gradient stop color\n*/\nfunction formatGradientStopColor(stop, fillOpacity) {\n\tconst baseAlpha = stop.color?.a ?? 1;\n\tconst alpha = Math.max(0, Math.min(1, baseAlpha * fillOpacity));\n\tconst bound = stop.boundVariables?.color;\n\tif (bound && typeof bound === \"object\" && \"id\" in bound && bound.id) try {\n\t\tconst v = figma.variables.getVariableById(bound.id);\n\t\tif (v) {\n\t\t\tconst fallbackOpaque = formatHexAlpha(stop.color, 1);\n\t\t\tconst fallbackAlpha = formatHexAlpha(stop.color, alpha);\n\t\t\tconst cssVarName = getVariableCssCustomPropertyName(v);\n\t\t\tconst varName = `var(${cssVarName}, ${fallbackOpaque})`;\n\t\t\tif (alpha >= .99) return `var(${cssVarName}, ${fallbackAlpha})`;\n\t\t\treturn `color-mix(in srgb, ${varName} ${Math.round(alpha * 1e4) / 100}%, transparent)`;\n\t\t}\n\t} catch {}\n\treturn formatHexAlpha(stop.color, alpha);\n}\n/**\n* Resolves linear gradient angle from gradient paint\n*/\nfunction resolveLinearGradientAngle(paint, size) {\n\tif (hasGradientHandlePositions(paint) && paint.gradientHandlePositions.length >= 2) {\n\t\tconst start = paint.gradientHandlePositions[0];\n\t\tconst end = paint.gradientHandlePositions[1];\n\t\tif (start && end) {\n\t\t\tconst { width, height } = getGradientSize(size);\n\t\t\tconst angle = normalizeGradientAngle((end.x - start.x) * width, (end.y - start.y) * height);\n\t\t\tif (angle != null) return angle;\n\t\t}\n\t}\n\tconst extracted = extractLinearGradientVectorFromTransform(paint.gradientTransform, size);\n\tif (!extracted) return null;\n\tconst { dx, dy } = extracted;\n\treturn normalizeGradientAngle(dx, dy);\n}\nfunction extractLinearGradientVectorFromTransform(transform, size) {\n\tif (!transform || !Array.isArray(transform) || transform.length < 2) return null;\n\tconst row0 = transform[0];\n\tconst row1 = transform[1];\n\tif (!Array.isArray(row0) || !Array.isArray(row1) || row0.length < 2 || row1.length < 2) return null;\n\tconst a = row0[0];\n\tconst c = row0[1];\n\tconst e = row0[2] ?? 0;\n\tconst b = row1[0];\n\tconst d = row1[1];\n\tconst f = row1[2] ?? 0;\n\tif (![\n\t\ta,\n\t\tb,\n\t\tc,\n\t\td,\n\t\te,\n\t\tf\n\t].every((value) => Number.isFinite(value))) return null;\n\tconst det = a * d - b * c;\n\tif (!Number.isFinite(det) || Math.abs(det) < 1e-8) return null;\n\tconst invA = d / det;\n\tconst invC = -c / det;\n\tconst invE = (c * f - d * e) / det;\n\tconst invB = -b / det;\n\tconst invD = a / det;\n\tconst invF = (b * e - a * f) / det;\n\tconst start = applyTransform(invA, invC, invE, invB, invD, invF, 0, .5);\n\tconst end = applyTransform(invA, invC, invE, invB, invD, invF, 1, .5);\n\tconst { width, height } = getGradientSize(size);\n\treturn {\n\t\tdx: (end.x - start.x) * width,\n\t\tdy: (end.y - start.y) * height\n\t};\n}\nfunction applyTransform(a, c, e, b, d, f, x, y) {\n\treturn {\n\t\tx: a * x + c * y + e,\n\t\ty: b * x + d * y + f\n\t};\n}\nfunction getGradientSize(size) {\n\tconst width = size?.width;\n\tconst height = size?.height;\n\treturn {\n\t\twidth: typeof width === \"number\" && Number.isFinite(width) && width > 0 ? width : 1,\n\t\theight: typeof height === \"number\" && Number.isFinite(height) && height > 0 ? height : 1\n\t};\n}\n/**\n* Normalizes gradient angle to degrees\n*/\nfunction normalizeGradientAngle(dx, dy) {\n\tif (!Number.isFinite(dx) || !Number.isFinite(dy)) return null;\n\tif (dx === 0 && dy === 0) return null;\n\tlet angle = Math.atan2(dy, dx) * 180 / Math.PI + 90;\n\tangle = (angle % 360 + 360) % 360;\n\treturn Math.round(angle * 100) / 100;\n}\n/**\n* Formats position as percentage\n*/\nfunction formatPercent(pos) {\n\treturn `${Math.round(pos * 1e4) / 100}%`;\n}\n/**\n* Variable naming must match MCP token indexing semantics exactly.\n*/\nfunction getVariableCssCustomPropertyName(variable) {\n\treturn normalizeFigmaVarName(getVariableRawName(variable));\n}\nfunction getVariableRawName(variable) {\n\tconst cs = variable.codeSyntax?.WEB;\n\tif (typeof cs === \"string\" && cs.trim()) {\n\t\tconst canonical = canonicalizeVarName(cs.trim());\n\t\tif (canonical) return canonical.slice(2);\n\t\tconst ident = cs.trim();\n\t\tif (/^[A-Za-z0-9_-]+$/.test(ident)) return ident;\n\t}\n\tconst raw = variable.name?.trim?.() ?? \"\";\n\tif (raw.startsWith(\"--\")) return raw.slice(2);\n\treturn raw;\n}\nfunction canonicalizeVarName(value) {\n\tconst cleaned = value.trim();\n\tconst varMatch = cleaned.match(/^var\\(\\s*(--[A-Za-z0-9_-]+)(?:\\s*,[\\s\\S]*)?\\)$/);\n\tif (varMatch?.[1]) return normalizeCustomPropertyName(varMatch[1]);\n\tif (cleaned.startsWith(\"--\")) return normalizeCustomPropertyName(cleaned);\n\treturn null;\n}\nfunction normalizeCustomPropertyBody(name) {\n\tif (!name) return \"var\";\n\tlet raw = name.trim();\n\tif (raw.startsWith(\"--\")) raw = raw.slice(2);\n\traw = raw.replace(/^-+/, \"\");\n\traw = raw.replace(/[^A-Za-z0-9_-]/g, \"\");\n\treturn raw || \"var\";\n}\nfunction normalizeCustomPropertyName(name) {\n\treturn `--${normalizeCustomPropertyBody(name)}`;\n}\nfunction normalizeFigmaVarName(input) {\n\tlet raw = (input ?? \"\").trim();\n\tif (!raw) return \"--unnamed\";\n\tconst canonical = canonicalizeVarName(raw);\n\tif (canonical) return canonical;\n\tif (raw.startsWith(\"--\")) raw = raw.slice(2).trim();\n\traw = raw.replace(RE_NON_ASCII, \"\").replace(RE_QUOTES, \"\").replace(RE_SLASH, \"-\").replace(RE_SPACE_TAB, \"-\").replace(RE_WHITESPACE, \"\");\n\tif (RE_FAST_PATH.test(raw)) return `--${raw}`;\n\tconst parts = raw.replace(RE_BOUND_NON_ALPHANUM, \"-\").replace(RE_HYPHENS, \"-\").replace(RE_BOUND_DIGIT, \"$1-$2$3-$4\").replace(RE_BOUND_CASE, \"$1$3-$2$4\").split(\"-\").filter(Boolean);\n\tconst stack = [];\n\tfor (const part of parts) {\n\t\tconst prev = stack[stack.length - 1];\n\t\tif (prev && RE_DIGIT.test(prev) && RE_DIGIT.test(part)) stack[stack.length - 1] += part;\n\t\telse if (prev && RE_CAPS.test(prev) && RE_CAPS.test(part)) stack[stack.length - 1] += part;\n\t\telse stack.push(part);\n\t}\n\tconst merged = [];\n\tfor (let i = 0; i < stack.length;) {\n\t\tif (i === 0) {\n\t\t\tmerged.push(stack[0]);\n\t\t\ti += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (RE_SINGLE.test(stack[i])) {\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < stack.length && RE_SINGLE.test(stack[j])) j += 1;\n\t\t\tconst run = stack.slice(i, j);\n\t\t\tmerged.push(run.length >= 2 ? run.join(\"\") : stack[i]);\n\t\t\ti = j;\n\t\t\tcontinue;\n\t\t}\n\t\tmerged.push(stack[i]);\n\t\ti += 1;\n\t}\n\tconst out = merged.join(\"-\").toLowerCase();\n\treturn out ? `--${out}` : \"--unnamed\";\n}\n\n//#endregion\n//#region src/figma/style-resolver.ts\nconst BG_URL_LIGHTGRAY_RE = /url\\(.*?\\)\\s+lightgray/i;\nconst BG_URL_RE = /url\\(/i;\nfunction hasStyleId(value) {\n\treturn typeof value === \"string\" && value.length > 0;\n}\nfunction isPaintStyle(style) {\n\treturn !!style && \"paints\" in style && Array.isArray(style.paints);\n}\nfunction resolvePaintStyleFromPaints(paints, size) {\n\tif (!paints) return null;\n\tconst gradient = resolveGradientFromPaints(paints, size);\n\tif (gradient) return { gradient };\n\tconst solidColor = resolveSolidFromPaints(paints);\n\treturn solidColor ? { solidColor } : null;\n}\nfunction resolvePaintStyleFromStyleId(styleId, kind, size) {\n\tif (!hasStyleId(styleId)) return null;\n\ttry {\n\t\tconst style = figma.getStyleById(styleId);\n\t\tif (!isPaintStyle(style)) return null;\n\t\treturn resolvePaintStyleFromPaints(style.paints, size);\n\t} catch (error) {\n\t\tconsole.warn(`Failed to resolve ${kind} style:`, error);\n\t\treturn null;\n\t}\n}\nfunction getNodeFillStyleId(node) {\n\treturn \"fillStyleId\" in node ? node.fillStyleId : null;\n}\nfunction getNodeStrokeStyleId(node) {\n\treturn \"strokeStyleId\" in node ? node.strokeStyleId : null;\n}\nfunction getNodeFillPaints(node) {\n\tif (\"fills\" in node && Array.isArray(node.fills)) return node.fills;\n\treturn null;\n}\nfunction getNodeStrokePaints(node) {\n\tif (\"strokes\" in node && Array.isArray(node.strokes)) return node.strokes;\n\treturn null;\n}\nfunction resolveNodePaintStyle(styleId, paints, kind, size) {\n\treturn resolvePaintStyleFromStyleId(styleId, kind, size) ?? resolvePaintStyleFromPaints(paints, size);\n}\nfunction getNodeDimensions(node) {\n\tif (!(\"width\" in node) || !(\"height\" in node)) return void 0;\n\tconst width = node.width;\n\tconst height = node.height;\n\tif (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;\n\treturn {\n\t\twidth,\n\t\theight\n\t};\n}\nfunction splitByTopLevelWhitespace$1(input) {\n\tconst out = [];\n\tlet depth = 0;\n\tlet quote = null;\n\tlet buffer = \"\";\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst ch = input[i];\n\t\tif (quote) {\n\t\t\tif (ch === \"\\\\\") {\n\t\t\t\tbuffer += ch;\n\t\t\t\ti++;\n\t\t\t\tif (i < input.length) buffer += input[i];\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (ch === quote) quote = null;\n\t\t\tbuffer += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\"\" || ch === \"'\") {\n\t\t\tquote = ch;\n\t\t\tbuffer += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") depth = Math.max(0, depth - 1);\n\t\tif (/\\s/.test(ch) && depth === 0) {\n\t\t\tif (buffer) {\n\t\t\t\tout.push(buffer);\n\t\t\t\tbuffer = \"\";\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tbuffer += ch;\n\t}\n\tif (buffer) out.push(buffer);\n\treturn out;\n}\nfunction isVarFunctionToken(value) {\n\tconst trimmed = value.trim();\n\treturn trimmed.startsWith(\"var(\") && trimmed.endsWith(\")\");\n}\nfunction patchBorderVarColor$1(borderValue, color) {\n\tconst borderParts = splitByTopLevelWhitespace$1(borderValue);\n\tif (!borderParts.length) return null;\n\tconst lastIndex = borderParts.length - 1;\n\tif (!isVarFunctionToken(borderParts[lastIndex])) return null;\n\tborderParts[lastIndex] = color;\n\treturn borderParts.join(\" \");\n}\nfunction hasBorderChannels(style) {\n\treturn Object.entries(style).some(([key, value]) => {\n\t\tif (!value?.trim()) return false;\n\t\tif (!/^border(?:$|-)/.test(key)) return false;\n\t\tif (key.includes(\"radius\") || key === \"border-image\" || key === \"border-image-slice\") return false;\n\t\treturn true;\n\t});\n}\n/**\n* Resolves fill style for a Figma node\n* Handles both fillStyleId and direct fills\n*/\nfunction resolveFillStyleForNode(node) {\n\treturn resolveNodePaintStyle(getNodeFillStyleId(node), getNodeFillPaints(node), \"fill\", getNodeDimensions(node));\n}\n/**\n* Resolves stroke style for a Figma node\n* Handles both strokeStyleId and direct strokes\n*/\nfunction resolveStrokeStyleForNode(node) {\n\treturn resolveNodePaintStyle(getNodeStrokeStyleId(node), getNodeStrokePaints(node), \"stroke\", getNodeDimensions(node));\n}\n/**\n* Main function to resolve all styles from a node\n* Replaces CSS variable references with actual values\n*/\nasync function resolveStylesFromNode(cssStyles, node) {\n\tconst processed = { ...cssStyles };\n\tconst fillPaints = getNodeFillPaints(node);\n\tif (processed.background && BG_URL_LIGHTGRAY_RE.test(processed.background) && fillPaints) {\n\t\tconst solidFill = resolveSolidFromPaints(fillPaints);\n\t\tif (solidFill) processed[\"background-color\"] = solidFill;\n\t\tprocessed.background = processed.background.replace(/\\s*,?\\s*lightgray\\b/i, \"\").trim();\n\t}\n\tconst resolvedFill = resolveFillStyleForNode(node);\n\tconst hasUrlBackground = typeof processed.background === \"string\" && BG_URL_RE.test(processed.background);\n\tif (resolvedFill?.gradient) {\n\t\tif (processed.background && !hasUrlBackground) processed.background = resolvedFill.gradient;\n\t\telse if (processed[\"background-color\"]) {\n\t\t\tprocessed.background = resolvedFill.gradient;\n\t\t\tdelete processed[\"background-color\"];\n\t\t}\n\t} else if (resolvedFill?.solidColor) {\n\t\tif (processed.background && !hasUrlBackground) {\n\t\t\tprocessed[\"background-color\"] = resolvedFill.solidColor;\n\t\t\tdelete processed.background;\n\t\t}\n\t\tif (processed[\"background-color\"]) processed[\"background-color\"] = resolvedFill.solidColor;\n\t\tif (processed.color) processed.color = resolvedFill.solidColor;\n\t\tif (processed.fill) processed.fill = resolvedFill.solidColor;\n\t}\n\tconst resolvedStroke = resolveStrokeStyleForNode(node);\n\tif (resolvedStroke?.gradient && hasBorderChannels(processed)) {\n\t\tprocessed[\"border-image\"] = resolvedStroke.gradient;\n\t\tprocessed[\"border-image-slice\"] = \"1\";\n\t} else if (resolvedStroke?.solidColor) {\n\t\tif (processed[\"border-color\"]) processed[\"border-color\"] = resolvedStroke.solidColor;\n\t\telse if (processed.border) {\n\t\t\tconst patched = patchBorderVarColor$1(processed.border, resolvedStroke.solidColor);\n\t\t\tif (patched) processed.border = patched;\n\t\t}\n\t}\n\tif (processed.stroke && resolvedStroke?.gradient) processed.stroke = resolvedStroke.gradient;\n\telse if (processed.stroke && resolvedStroke?.solidColor) processed.stroke = resolvedStroke.solidColor;\n\treturn processed;\n}\n\n//#endregion\n//#region src/figma/stroke.ts\nfunction splitByTopLevelWhitespace(input) {\n\tconst out = [];\n\tlet depth = 0;\n\tlet quote = null;\n\tlet buffer = \"\";\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst ch = input[i];\n\t\tif (quote) {\n\t\t\tif (ch === \"\\\\\") {\n\t\t\t\tbuffer += ch;\n\t\t\t\ti++;\n\t\t\t\tif (i < input.length) buffer += input[i];\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (ch === quote) quote = null;\n\t\t\tbuffer += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\"\" || ch === \"'\") {\n\t\t\tquote = ch;\n\t\t\tbuffer += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") depth = Math.max(0, depth - 1);\n\t\tif (/\\s/.test(ch) && depth === 0) {\n\t\t\tif (buffer) {\n\t\t\t\tout.push(buffer);\n\t\t\t\tbuffer = \"\";\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tbuffer += ch;\n\t}\n\tif (buffer) out.push(buffer);\n\treturn out;\n}\nfunction patchBorderVarColor(borderValue, color) {\n\tconst borderParts = splitByTopLevelWhitespace(borderValue);\n\tif (!borderParts.length) return null;\n\tconst lastIndex = borderParts.length - 1;\n\tconst tail = borderParts[lastIndex].trim();\n\tif (!tail.startsWith(\"var(\") || !tail.endsWith(\")\")) return null;\n\tborderParts[lastIndex] = color;\n\treturn borderParts.join(\" \");\n}\n/**\n* Resolves stroke styles from paint array\n* Can handle both solid colors and gradients\n* @param paints Array of paint objects from strokes or stroke style\n* @returns Object with solidColor or gradient, or null\n*/\nfunction resolveStrokeFromPaints(paints) {\n\tif (!paints || !Array.isArray(paints)) return null;\n\tconst gradient = resolveGradientFromPaints(paints);\n\tif (gradient) return { gradient };\n\tconst solidColor = resolveSolidFromPaints(paints);\n\tif (solidColor) return { solidColor };\n\treturn null;\n}\n/**\n* Applies resolved stroke styles to CSS properties\n* Handles different CSS properties for stroke (border, stroke, outline)\n*/\nfunction applyStrokeToCSS(styles, resolved) {\n\tif (!resolved) return styles;\n\tconst processed = { ...styles };\n\tconst borderHasVar = processed.border?.includes(\"var(--\") ?? false;\n\tconst borderColorHasVar = processed[\"border-color\"]?.includes(\"var(--\") ?? false;\n\tif (borderHasVar || borderColorHasVar) {\n\t\tif (resolved.gradient) {\n\t\t\tprocessed[\"border-image\"] = `${resolved.gradient} 1`;\n\t\t\tprocessed[\"border-image-slice\"] = \"1\";\n\t\t\tdelete processed[\"border-color\"];\n\t\t} else if (resolved.solidColor) if (borderColorHasVar) processed[\"border-color\"] = resolved.solidColor;\n\t\telse {\n\t\t\tconst patched = patchBorderVarColor(processed.border, resolved.solidColor);\n\t\t\tif (patched) processed.border = patched;\n\t\t}\n\t}\n\tif (processed.stroke?.includes(\"var(--\")) {\n\t\tif (resolved.gradient) processed.stroke = resolved.gradient;\n\t\telse if (resolved.solidColor) processed.stroke = resolved.solidColor;\n\t}\n\tif (processed[\"outline-color\"]?.includes(\"var(--\")) {\n\t\tif (resolved.solidColor) processed[\"outline-color\"] = resolved.solidColor;\n\t}\n\treturn processed;\n}\n\n//#endregion\nexport { ActivateMessageSchema, AssetDescriptorSchema, GetAssetsParametersSchema, GetAssetsResultSchema, GetCodeParametersSchema, GetScreenshotParametersSchema, GetStructureParametersSchema, GetTokenDefsParametersSchema, MCP_ASSET_TTL_MS, MCP_AUTO_ACTIVATE_GRACE_MS, MCP_HASH_HEX_LENGTH, MCP_HASH_PATTERN, MCP_MAX_ASSET_BYTES, MCP_MAX_PAYLOAD_BYTES, MCP_PORT_CANDIDATES, MCP_TOOL_TIMEOUT_MS, MessageFromExtensionSchema, MessageToExtensionSchema, RegisteredMessageSchema, StateMessageSchema, TEMPAD_MCP_ERROR_CODES, ToolCallMessageSchema, ToolCallPayloadSchema, ToolResultMessageSchema, applyStrokeToCSS, formatHexAlpha, parseMessageFromExtension, parseMessageToExtension, resolveFillStyleForNode, resolveGradientFromPaints, resolveSolidFromPaints, resolveStrokeFromPaints, resolveStrokeStyleForNode, resolveStylesFromNode };","import { MCP_HASH_HEX_LENGTH } from '@tempad-dev/shared'\n\nconst HASH_FILENAME_PATTERN = new RegExp(\n `^([a-f0-9]{${MCP_HASH_HEX_LENGTH}})(?:\\\\.[a-z0-9]+)?$`,\n 'i'\n)\n\nconst MIME_EXTENSION_OVERRIDES = new Map<string, string>([['image/jpeg', 'jpg']])\nconst SAFE_IMAGE_EXTENSION_PATTERN = /^[a-z0-9-]+$/\n\nexport function normalizeMimeType(mimeType: string | undefined): string {\n if (!mimeType) return 'application/octet-stream'\n const [normalized] = mimeType.split(';', 1)\n return (normalized || 'application/octet-stream').trim().toLowerCase()\n}\n\nexport function getImageExtension(mimeType: string): string {\n const normalized = normalizeMimeType(mimeType)\n if (!normalized.startsWith('image/')) return ''\n const override = MIME_EXTENSION_OVERRIDES.get(normalized)\n if (override) return `.${override}`\n const subtype = normalized.slice('image/'.length)\n if (!subtype) return ''\n const ext = subtype.split('+', 1)[0] || subtype\n if (!SAFE_IMAGE_EXTENSION_PATTERN.test(ext)) return ''\n return `.${ext}`\n}\n\nexport function buildAssetFilename(hash: string, mimeType: string): string {\n const ext = getImageExtension(mimeType)\n return ext ? `${hash}${ext}` : hash\n}\n\nexport function getHashFromAssetFilename(filename: string): string | null {\n const match = HASH_FILENAME_PATTERN.exec(filename)\n return match ? match[1] : null\n}\n","import {\n MCP_AUTO_ACTIVATE_GRACE_MS,\n MCP_ASSET_TTL_MS,\n MCP_MAX_ASSET_BYTES,\n MCP_MAX_PAYLOAD_BYTES,\n MCP_PORT_CANDIDATES,\n MCP_TOOL_TIMEOUT_MS\n} from '@tempad-dev/shared'\n\nfunction parsePositiveInt(envValue: string | undefined, fallback: number): number {\n const parsed = envValue ? Number.parseInt(envValue, 10) : Number.NaN\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback\n}\n\nfunction parseNonNegativeInt(envValue: string | undefined, fallback: number): number {\n const parsed = envValue ? Number.parseInt(envValue, 10) : Number.NaN\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback\n}\n\nfunction resolveToolTimeoutMs(): number {\n return parsePositiveInt(process.env.TEMPAD_MCP_TOOL_TIMEOUT, MCP_TOOL_TIMEOUT_MS)\n}\n\nfunction resolveAutoActivateGraceMs(): number {\n return parsePositiveInt(process.env.TEMPAD_MCP_AUTO_ACTIVATE_GRACE, MCP_AUTO_ACTIVATE_GRACE_MS)\n}\n\nfunction resolveMaxAssetSizeBytes(): number {\n return parsePositiveInt(process.env.TEMPAD_MCP_MAX_ASSET_BYTES, MCP_MAX_ASSET_BYTES)\n}\n\nfunction resolveAssetTtlMs(): number {\n return parseNonNegativeInt(process.env.TEMPAD_MCP_ASSET_TTL_MS, MCP_ASSET_TTL_MS)\n}\n\nexport function getMcpServerConfig() {\n return {\n wsPortCandidates: [...MCP_PORT_CANDIDATES],\n toolTimeoutMs: resolveToolTimeoutMs(),\n maxPayloadBytes: MCP_MAX_PAYLOAD_BYTES,\n autoActivateGraceMs: resolveAutoActivateGraceMs(),\n maxAssetSizeBytes: resolveMaxAssetSizeBytes(),\n assetTtlMs: resolveAssetTtlMs()\n }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport { MCP_HASH_HEX_LENGTH } from '@tempad-dev/shared'\nimport { nanoid } from 'nanoid'\nimport { createHash } from 'node:crypto'\nimport {\n createReadStream,\n createWriteStream,\n existsSync,\n renameSync,\n statSync,\n unlinkSync\n} from 'node:fs'\nimport { createServer } from 'node:http'\nimport { join } from 'node:path'\nimport { pipeline, Transform } from 'node:stream'\nimport { URL } from 'node:url'\n\nimport type { AssetStore } from './asset-store'\n\nimport { buildAssetFilename, getHashFromAssetFilename, normalizeMimeType } from './asset-utils'\nimport { getMcpServerConfig } from './config'\nimport { ASSET_DIR, log } from './shared'\n\nconst LOOPBACK_HOST = '127.0.0.1'\nconst HASH_HEX_PATTERN = new RegExp(`^[a-f0-9]{${MCP_HASH_HEX_LENGTH}}$`, 'i')\nconst { maxAssetSizeBytes } = getMcpServerConfig()\n\nexport interface AssetHttpServer {\n start(): Promise<void>\n stop(): void\n getBaseUrl(): string\n}\n\nexport function createAssetHttpServer(store: AssetStore): AssetHttpServer {\n const server = createServer(handleRequest)\n let port: number | null = null\n\n async function start(): Promise<void> {\n if (port !== null) return\n await new Promise<void>((resolve, reject) => {\n const onError = (error: Error) => {\n server.off('listening', onListening)\n reject(error)\n }\n const onListening = () => {\n server.off('error', onError)\n const address = server.address()\n if (address && typeof address === 'object') {\n port = address.port\n resolve()\n } else {\n reject(new Error('Failed to determine HTTP server port.'))\n }\n }\n server.once('error', onError)\n server.once('listening', onListening)\n server.listen(0, LOOPBACK_HOST)\n })\n log.info({ port }, 'Asset HTTP server ready.')\n }\n\n function stop(): void {\n if (port === null) return\n server.close()\n port = null\n }\n\n function getBaseUrl(): string {\n if (port === null) throw new Error('Asset HTTP server is not running.')\n return `http://${LOOPBACK_HOST}:${port}`\n }\n\n function handleRequest(req: IncomingMessage, res: ServerResponse): void {\n const startedAt = Date.now()\n res.on('finish', () => {\n log.info(\n {\n method: req.method,\n url: req.url,\n status: res.statusCode,\n durationMs: Date.now() - startedAt\n },\n 'HTTP asset request completed.'\n )\n })\n\n res.setHeader('Access-Control-Allow-Origin', '*')\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Asset-Width, X-Asset-Height')\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204)\n res.end()\n return\n }\n\n if (!req.url) {\n sendError(res, 400, 'Missing URL')\n return\n }\n\n const url = new URL(req.url, getBaseUrl())\n const segments = url.pathname.split('/').filter(Boolean)\n if (segments.length !== 2 || segments[0] !== 'assets') {\n sendError(res, 404, 'Not Found')\n return\n }\n\n const filename = segments[1]\n const hash = getHashFromAssetFilename(filename)\n if (!hash) {\n sendError(res, 404, 'Not Found')\n return\n }\n\n if (req.method === 'POST') {\n handleUpload(req, res, hash)\n return\n }\n\n if (req.method === 'GET') {\n handleDownload(req, res, hash)\n return\n }\n\n sendError(res, 405, 'Method Not Allowed')\n }\n\n function handleDownload(req: IncomingMessage, res: ServerResponse, hash: string): void {\n const record = store.get(hash)\n if (!record) {\n sendError(res, 404, 'Asset Not Found')\n return\n }\n\n let stat\n try {\n stat = statSync(record.filePath)\n } catch (error) {\n const err = error as NodeJS.ErrnoException\n if (err.code === 'ENOENT') {\n store.remove(hash, { removeFile: false })\n sendError(res, 404, 'Asset Not Found')\n } else {\n log.error({ error, hash }, 'Failed to stat asset file.')\n sendError(res, 500, 'Internal Server Error')\n }\n return\n }\n\n res.writeHead(200, {\n 'Content-Type': record.mimeType,\n 'Content-Length': stat.size.toString(),\n 'Cache-Control': 'public, max-age=31536000, immutable'\n })\n\n const stream = createReadStream(record.filePath)\n stream.on('error', (error) => {\n log.warn({ error, hash }, 'Failed to stream asset file.')\n if (!res.headersSent) {\n sendError(res, 500, 'Internal Server Error')\n } else {\n res.end()\n }\n })\n stream.on('open', () => {\n store.touch(hash)\n })\n stream.pipe(res)\n }\n\n function handleUpload(req: IncomingMessage, res: ServerResponse, hash: string): void {\n if (!HASH_HEX_PATTERN.test(hash)) {\n req.resume()\n sendError(res, 400, 'Invalid Hash')\n return\n }\n\n const contentTypeHeader = req.headers['content-type']\n const mimeType = normalizeMimeType(\n Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader\n )\n const filename = buildAssetFilename(hash, mimeType)\n const filePath = join(ASSET_DIR, filename)\n\n const width = parseInt(req.headers['x-asset-width'] as string, 10)\n const height = parseInt(req.headers['x-asset-height'] as string, 10)\n const metadata =\n !isNaN(width) && !isNaN(height) && width > 0 && height > 0 ? { width, height } : undefined\n\n const existing = store.get(hash)\n if (existing) {\n let existingPath = existing.filePath\n if (!existsSync(existingPath) && existsSync(filePath)) {\n existing.filePath = filePath\n existingPath = filePath\n }\n\n if (existsSync(existingPath)) {\n if (existingPath !== filePath) {\n try {\n renameSync(existingPath, filePath)\n existing.filePath = filePath\n } catch (error) {\n log.warn({ error, hash }, 'Failed to rename existing asset to include extension.')\n }\n }\n\n // Drain request to ensure connection is clean\n req.resume()\n\n if (metadata) existing.metadata = metadata\n if (existing.mimeType !== mimeType) existing.mimeType = mimeType\n existing.lastAccess = Date.now()\n store.upsert(existing)\n sendOk(res, 200, 'Asset Already Exists')\n return\n }\n }\n\n const tmpPath = `${filePath}.tmp.${nanoid()}`\n const writeStream = createWriteStream(tmpPath)\n const hasher = createHash('sha256')\n let size = 0\n\n const cleanup = () => {\n if (existsSync(tmpPath)) {\n try {\n unlinkSync(tmpPath)\n } catch (e) {\n log.warn({ error: e, tmpPath }, 'Failed to cleanup temp file.')\n }\n }\n }\n\n const monitor = new Transform({\n transform(chunk, encoding, callback) {\n size += chunk.length\n if (size > maxAssetSizeBytes) {\n callback(new Error('PayloadTooLarge'))\n return\n }\n hasher.update(chunk)\n callback(null, chunk)\n }\n })\n\n pipeline(req, monitor, writeStream, (err) => {\n if (err) {\n cleanup()\n if (err.message === 'PayloadTooLarge') {\n sendError(res, 413, 'Payload Too Large')\n } else if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') {\n log.warn({ hash }, 'Upload request closed prematurely.')\n sendError(res, 400, 'Upload Incomplete')\n } else {\n log.error({ error: err, hash }, 'Upload pipeline failed.')\n if (!res.headersSent) {\n sendError(res, 500, 'Internal Server Error')\n }\n }\n return\n }\n\n const computedHash = hasher.digest('hex').slice(0, MCP_HASH_HEX_LENGTH)\n if (computedHash !== hash) {\n cleanup()\n sendError(res, 400, 'Hash Mismatch')\n return\n }\n\n try {\n renameSync(tmpPath, filePath)\n } catch (error) {\n log.error({ error, hash }, 'Failed to rename temp file to asset.')\n cleanup()\n sendError(res, 500, 'Internal Server Error')\n return\n }\n\n store.upsert({\n hash,\n filePath,\n mimeType,\n size,\n metadata\n })\n log.info({ hash, size }, 'Stored uploaded asset via HTTP.')\n sendOk(res, 201, 'Created', { hash, size })\n })\n }\n\n function sendError(\n res: ServerResponse,\n status: number,\n message: string,\n details?: Record<string, unknown>\n ): void {\n if (!res.headersSent) {\n res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' })\n }\n res.end(\n JSON.stringify({\n error: message,\n ...details\n })\n )\n }\n\n function sendOk(\n res: ServerResponse,\n status: number,\n message: string,\n data?: Record<string, unknown>\n ): void {\n if (!res.headersSent) {\n res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' })\n }\n res.end(\n JSON.stringify({\n message,\n ...data\n })\n )\n }\n\n return {\n start,\n stop,\n getBaseUrl\n }\n}\n","import { existsSync, readFileSync, rmSync, writeFileSync, readdirSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport type { AssetRecord } from './types'\n\nimport { getHashFromAssetFilename } from './asset-utils'\nimport { ASSET_DIR, ensureDir, ensureFile, log } from './shared'\n\nconst INDEX_FILENAME = 'assets.json'\nconst DEFAULT_INDEX_PATH = join(ASSET_DIR, INDEX_FILENAME)\n\nexport interface AssetStoreOptions {\n indexPath?: string\n}\n\nexport interface AssetStore {\n list(): AssetRecord[]\n has(hash: string): boolean\n get(hash: string): AssetRecord | undefined\n getMany(hashes: string[]): AssetRecord[]\n upsert(\n input: Omit<AssetRecord, 'uploadedAt' | 'lastAccess'> &\n Partial<Pick<AssetRecord, 'uploadedAt' | 'lastAccess'>>\n ): AssetRecord\n touch(hash: string): AssetRecord | undefined\n remove(hash: string, opts?: { removeFile?: boolean }): void\n reconcile(): void\n flush(): void\n}\n\nfunction readIndex(indexPath: string): AssetRecord[] {\n if (!existsSync(indexPath)) return []\n try {\n const raw = readFileSync(indexPath, 'utf8').trim()\n if (!raw) return []\n const parsed = JSON.parse(raw)\n return Array.isArray(parsed) ? (parsed as AssetRecord[]) : []\n } catch (error) {\n log.warn({ error, indexPath }, 'Failed to read asset catalog; starting fresh.')\n return []\n }\n}\n\nfunction writeIndex(indexPath: string, values: AssetRecord[]): void {\n const payload = JSON.stringify(values, null, 2)\n writeFileSync(indexPath, payload, 'utf8')\n}\n\nexport function createAssetStore(options: AssetStoreOptions = {}): AssetStore {\n ensureDir(ASSET_DIR)\n const indexPath = options.indexPath ?? DEFAULT_INDEX_PATH\n ensureFile(indexPath)\n const records = new Map<string, AssetRecord>()\n let persistTimer: NodeJS.Timeout | null = null\n\n function loadExisting(): void {\n const list = readIndex(indexPath)\n for (const record of list) {\n if (record?.hash && record?.filePath) {\n records.set(record.hash, record)\n }\n }\n }\n\n function persist(): void {\n if (persistTimer) return\n persistTimer = setTimeout(() => {\n persistTimer = null\n writeIndex(indexPath, [...records.values()])\n }, 5000)\n if (typeof persistTimer.unref === 'function') {\n persistTimer.unref()\n }\n }\n\n function flush(): void {\n if (persistTimer) {\n clearTimeout(persistTimer)\n persistTimer = null\n }\n writeIndex(indexPath, [...records.values()])\n }\n\n function list(): AssetRecord[] {\n return [...records.values()]\n }\n\n function has(hash: string): boolean {\n return records.has(hash)\n }\n\n function get(hash: string): AssetRecord | undefined {\n return records.get(hash)\n }\n\n function getMany(hashes: string[]): AssetRecord[] {\n return hashes\n .map((hash) => records.get(hash))\n .filter((record): record is AssetRecord => !!record)\n }\n\n function upsert(\n input: Omit<AssetRecord, 'uploadedAt' | 'lastAccess'> &\n Partial<Pick<AssetRecord, 'uploadedAt' | 'lastAccess'>>\n ): AssetRecord {\n const now = Date.now()\n const record: AssetRecord = {\n ...input,\n uploadedAt: input.uploadedAt ?? now,\n lastAccess: input.lastAccess ?? now\n }\n records.set(record.hash, record)\n persist()\n return record\n }\n\n function touch(hash: string): AssetRecord | undefined {\n const existing = records.get(hash)\n if (!existing) return undefined\n existing.lastAccess = Date.now()\n persist()\n return existing\n }\n\n function remove(hash: string, { removeFile = true } = {}): void {\n const record = records.get(hash)\n if (!record) return\n records.delete(hash)\n persist()\n\n if (removeFile) {\n try {\n rmSync(record.filePath, { force: true })\n } catch (error) {\n log.warn({ hash, error }, 'Failed to remove asset file on delete.')\n }\n }\n }\n\n function reconcile(): void {\n let changed = false\n for (const [hash, record] of records) {\n if (!existsSync(record.filePath)) {\n records.delete(hash)\n changed = true\n }\n }\n\n try {\n const files = readdirSync(ASSET_DIR)\n const now = Date.now()\n for (const file of files) {\n if (file === INDEX_FILENAME) continue\n\n // Cleanup stale tmp files (> 1 hour)\n if (file.includes('.tmp.')) {\n try {\n const filePath = join(ASSET_DIR, file)\n const stat = statSync(filePath)\n if (now - stat.mtimeMs > 3600 * 1000) {\n rmSync(filePath, { force: true })\n log.info({ file }, 'Cleaned up stale temp file.')\n }\n } catch (e) {\n // Ignore errors during cleanup\n log.debug({ error: e, file }, 'Failed to cleanup stale temp file.')\n }\n continue\n }\n\n const hash = getHashFromAssetFilename(file)\n if (!hash) continue\n\n if (!records.has(hash)) {\n const filePath = join(ASSET_DIR, file)\n try {\n const stat = statSync(filePath)\n records.set(hash, {\n hash,\n filePath,\n mimeType: 'application/octet-stream',\n size: stat.size,\n uploadedAt: stat.birthtimeMs,\n lastAccess: stat.atimeMs\n })\n changed = true\n log.info({ hash }, 'Recovered orphan asset file.')\n } catch (e) {\n log.warn({ error: e, file }, 'Failed to stat orphan file.')\n }\n }\n }\n } catch (error) {\n log.warn({ error }, 'Failed to scan asset directory for orphans.')\n }\n\n if (changed) flush()\n }\n\n loadExisting()\n reconcile()\n\n return {\n list,\n has,\n get,\n getMany,\n upsert,\n touch,\n remove,\n reconcile,\n flush\n }\n}\n","export default \"You are connected to a Figma design file via TemPad Dev MCP.\\n\\nTreat tool outputs as design facts. Refactor only to match the user’s repo conventions; do not invent key style values.\\n\\nRules:\\n\\n- Never output any `data-hint-*` attributes from tool outputs (hints only).\\n- If `get_code` warns `depth-cap`, call `get_code` again for each listed `nodeId` before implementing.\\n- If `get_code` warns `shell`, read the inline code comment for omitted direct child ids, then call `get_code` for those ids in order and fill the results back into the returned shell.\\n- Use `get_structure` only to resolve layout/overlap uncertainty; do not derive numeric values from images.\\n- Tokens: `get_code.tokens` keys are canonical names (`--...`). Multi‑mode values use `${collectionName}:${modeName}`. Nodes may hint per-node overrides via `data-hint-variable-mode=\\\"Collection=Mode;...\\\"`.\\n- Assets: download bytes via `asset.url`. Asset resources are not exposed via MCP `resources/read`. Preserve SVG/vector assets exactly; never redraw vectors.\\n\"","import type { TempadMcpErrorCode } from '@tempad-dev/shared'\n\nimport { TEMPAD_MCP_ERROR_CODES } from '@tempad-dev/shared'\nimport { nanoid } from 'nanoid'\n\nimport type { PendingToolCall } from './types'\n\nimport { log } from './shared'\n\nconst pendingCalls = new Map<string, PendingToolCall>()\n\nfunction createToolError(\n code: TempadMcpErrorCode,\n message: string\n): Error & { code: TempadMcpErrorCode } {\n const err = new Error(message) as Error & { code: TempadMcpErrorCode }\n err.code = code\n return err\n}\n\nexport function register<T>(\n extensionId: string,\n timeout: number\n): { promise: Promise<T>; requestId: string } {\n const requestId = nanoid()\n const promise = new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n pendingCalls.delete(requestId)\n reject(\n createToolError(\n TEMPAD_MCP_ERROR_CODES.EXTENSION_TIMEOUT,\n `Extension did not respond within ${timeout / 1000}s.`\n )\n )\n }, timeout)\n\n pendingCalls.set(requestId, {\n resolve: resolve as (value: unknown) => void,\n reject,\n timer,\n extensionId\n })\n })\n return { promise, requestId }\n}\n\nexport function resolve(requestId: string, payload: unknown): void {\n const call = pendingCalls.get(requestId)\n if (call) {\n const { timer, resolve: finish } = call\n clearTimeout(timer)\n finish(payload)\n pendingCalls.delete(requestId)\n } else {\n log.warn({ reqId: requestId }, 'Received result for unknown/timed-out call.')\n }\n}\n\nexport function reject(requestId: string, error: Error): void {\n const call = pendingCalls.get(requestId)\n if (call) {\n const { timer, reject: fail } = call\n clearTimeout(timer)\n fail(error)\n pendingCalls.delete(requestId)\n } else {\n log.warn({ reqId: requestId }, 'Received error for unknown/timed-out call.')\n }\n}\n\nexport function cleanupForExtension(extensionId: string): void {\n for (const [reqId, call] of pendingCalls.entries()) {\n const { timer, reject: fail, extensionId: extId } = call\n if (extId === extensionId) {\n clearTimeout(timer)\n fail(\n createToolError(\n TEMPAD_MCP_ERROR_CODES.EXTENSION_DISCONNECTED,\n 'Extension disconnected before providing a result.'\n )\n )\n pendingCalls.delete(reqId)\n log.warn({ reqId, extId: extensionId }, 'Rejected pending call from disconnected extension.')\n }\n }\n}\n\nexport function cleanupAll(): void {\n pendingCalls.forEach((call, reqId) => {\n const { timer, reject: fail } = call\n clearTimeout(timer)\n fail(new Error('Hub is shutting down.'))\n log.debug({ reqId }, 'Rejected pending tool call due to shutdown.')\n })\n pendingCalls.clear()\n}\n","import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport type {\n GetScreenshotResult,\n TempadMcpErrorCode,\n ToolName,\n ToolResultMap,\n ToolSchema\n} from '@tempad-dev/shared'\nimport type { ZodType } from 'zod'\n\nimport {\n GetAssetsParametersSchema,\n GetAssetsResultSchema,\n GetCodeParametersSchema,\n GetScreenshotParametersSchema,\n GetStructureParametersSchema,\n GetTokenDefsParametersSchema,\n TEMPAD_MCP_ERROR_CODES,\n type TempadMcpErrorPayload\n} from '@tempad-dev/shared'\n\nexport type {\n AssetDescriptor,\n GetAssetsParametersInput,\n GetAssetsResult,\n GetCodeParametersInput,\n GetCodeResult,\n GetScreenshotParametersInput,\n GetScreenshotResult,\n GetStructureParametersInput,\n GetStructureResult,\n GetTokenDefsParametersInput,\n GetTokenDefsResult,\n TokenEntry,\n ToolName,\n ToolResultMap,\n ToolSchema\n} from '@tempad-dev/shared'\n\ntype BaseToolMetadata<Name extends ToolName, Schema extends ZodType> = ToolSchema<Name> & {\n parameters: Schema\n format?: (payload: ToolResultMap[Name]) => CallToolResult\n}\n\ntype ExtensionToolMetadata<Name extends ToolName, Schema extends ZodType> = BaseToolMetadata<\n Name,\n Schema\n> & {\n target: 'extension'\n}\n\ntype HubToolMetadata<Name extends ToolName, Schema extends ZodType> = BaseToolMetadata<\n Name,\n Schema\n> & {\n target: 'hub'\n outputSchema?: ZodType\n}\n\nfunction getRecordProperty(record: unknown, key: string): unknown {\n if (!record || typeof record !== 'object') {\n return undefined\n }\n return Reflect.get(record, key)\n}\n\nfunction extTool<Name extends ToolName, Schema extends ZodType>(\n definition: ExtensionToolMetadata<Name, Schema>\n): ExtensionToolMetadata<Name, Schema> {\n return definition\n}\n\nfunction hubTool<Name extends ToolName, Schema extends ZodType>(\n definition: HubToolMetadata<Name, Schema>\n): HubToolMetadata<Name, Schema> {\n return definition\n}\n\nexport const TOOL_DEFS = [\n extTool({\n name: 'get_code',\n description:\n 'High-fidelity code snapshot for nodeId/current single selection (omit nodeId to use selection): JSX/Vue markup + Tailwind-like classes, plus assets/tokens metadata and codegen config. Start here, then refactor into repo conventions while preserving values/intent; strip any data-hint-* attributes (hints only). If warnings include depth-cap, call get_code again for each listed nodeId. If warnings include shell, read the inline comment for omitted direct child ids and fetch them in order. If warnings include auto-layout (inferred), use get_structure to confirm hierarchy/overlap (do not derive numeric values from pixels). Tokens are keyed by canonical names like `--color-primary` (multi-mode keys use `${collection}:${mode}`; node overrides may appear as data-hint-variable-mode).',\n parameters: GetCodeParametersSchema,\n target: 'extension',\n format: createCodeToolResponse\n }),\n extTool({\n name: 'get_token_defs',\n description:\n 'Resolve canonical token names to literal values (optionally including all modes) for tokens referenced by get_code.',\n parameters: GetTokenDefsParametersSchema,\n target: 'extension',\n exposed: false\n }),\n extTool({\n name: 'get_screenshot',\n description:\n 'Capture a rendered PNG screenshot for nodeId/current single selection for visual verification (layering/overlap/masks/effects).',\n parameters: GetScreenshotParametersSchema,\n target: 'extension',\n format: createScreenshotToolResponse,\n exposed: false\n }),\n extTool({\n name: 'get_structure',\n description:\n 'Get a compact structural + geometry outline for nodeId/current single selection to understand hierarchy and layout intent.',\n parameters: GetStructureParametersSchema,\n target: 'extension'\n }),\n hubTool({\n name: 'get_assets',\n description:\n 'Resolve asset hashes to downloadable URLs and metadata for assets referenced by tool responses (preserve vectors exactly).',\n parameters: GetAssetsParametersSchema,\n target: 'hub',\n outputSchema: GetAssetsResultSchema,\n exposed: false\n })\n] as const\n\nfunction extractToolErrorCode(error: unknown): TempadMcpErrorCode | undefined {\n const code = getRecordProperty(error, 'code')\n if (typeof code === 'string') {\n return code as TempadMcpErrorCode\n }\n const cause = getRecordProperty(error, 'cause')\n const causeCode = getRecordProperty(cause, 'code')\n if (typeof causeCode === 'string') {\n return causeCode as TempadMcpErrorCode\n }\n return undefined\n}\n\nfunction extractToolErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message || 'Unknown error occurred.'\n if (typeof error === 'string') return error\n if (error && typeof error === 'object') {\n const candidate = error as Partial<TempadMcpErrorPayload & Record<string, unknown>>\n if (typeof candidate.message === 'string' && candidate.message.trim()) return candidate.message\n }\n return 'Unknown error occurred.'\n}\n\nfunction createToolErrorResponse(toolName: string, error: unknown): CallToolResult {\n const message = extractToolErrorMessage(error)\n const code = extractToolErrorCode(error)\n const codeLabel = code ? ` [${code}]` : ''\n\n const troubleshooting = (() => {\n const help: string[] = []\n\n const isConnectivityError =\n code === TEMPAD_MCP_ERROR_CODES.NO_ACTIVE_EXTENSION ||\n code === TEMPAD_MCP_ERROR_CODES.EXTENSION_TIMEOUT ||\n code === TEMPAD_MCP_ERROR_CODES.EXTENSION_DISCONNECTED ||\n code === TEMPAD_MCP_ERROR_CODES.ASSET_SERVER_NOT_CONFIGURED ||\n code === TEMPAD_MCP_ERROR_CODES.TRANSPORT_NOT_CONNECTED ||\n /no active tempad dev extension/i.test(message) ||\n /asset server url is not configured/i.test(message) ||\n /mcp transport is not connected/i.test(message) ||\n /websocket/i.test(message)\n\n if (isConnectivityError) {\n help.push(\n 'Troubleshooting:',\n '- In Figma, open TemPad Dev panel and enable MCP (Preferences → MCP server).',\n '- If multiple Figma tabs are open, click the MCP badge to activate this tab.',\n '- Keep the Figma tab active/foreground while running MCP tools.'\n )\n }\n\n const isSelectionError =\n code === TEMPAD_MCP_ERROR_CODES.INVALID_SELECTION ||\n code === TEMPAD_MCP_ERROR_CODES.NODE_NOT_VISIBLE ||\n /select exactly one visible node/i.test(message) ||\n /no visible node found/i.test(message)\n\n if (isSelectionError) {\n help.push('Tip: Select exactly one visible node, or pass nodeId.')\n }\n\n return help.length ? `\\n\\n${help.join('\\n')}` : ''\n })()\n\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: `Tool \"${toolName}\" failed${codeLabel}: ${message}${troubleshooting}`\n }\n ]\n }\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nexport function createCodeToolResponse(payload: ToolResultMap['get_code']): CallToolResult {\n if (!isCodeResult(payload)) {\n throw new Error('Invalid get_code payload received from extension.')\n }\n\n const summary: string[] = []\n const codeSize = Buffer.byteLength(payload.code, 'utf8')\n summary.push(`Generated \\`${payload.lang}\\` snippet (${formatBytes(codeSize)}).`)\n if (payload.warnings?.length) {\n const warningText = payload.warnings.map((warning) => warning.message).join(' ')\n summary.push(warningText)\n }\n summary.push(\n payload.assets?.length\n ? `Assets attached: ${payload.assets.length}. Download bytes from each asset.url.`\n : 'No binary assets were attached to this response.'\n )\n const tokenCount = payload.tokens ? Object.keys(payload.tokens).length : 0\n if (tokenCount) {\n summary.push(`Token references included: ${tokenCount}.`)\n }\n summary.push('Read structuredContent for the full code string and asset metadata.')\n\n return {\n content: [\n {\n type: 'text' as const,\n text: summary.join('\\n')\n }\n ],\n structuredContent: payload\n }\n}\n\nexport function createScreenshotToolResponse(\n payload: ToolResultMap['get_screenshot']\n): CallToolResult {\n if (!isScreenshotResult(payload)) {\n throw new Error('Invalid get_screenshot payload received from extension.')\n }\n\n const descriptionBlock = {\n type: 'text' as const,\n text: `${describeScreenshot(payload)} - Download: ${payload.asset.url}`\n }\n\n return {\n content: [descriptionBlock],\n structuredContent: payload\n }\n}\n\nfunction describeScreenshot(result: GetScreenshotResult): string {\n return `Screenshot ${result.width}x${result.height} @${result.scale}x (${formatBytes(result.bytes)})`\n}\n\nfunction isScreenshotResult(payload: unknown): payload is GetScreenshotResult {\n if (typeof payload !== 'object' || !payload) return false\n const candidate = payload as Partial<GetScreenshotResult & Record<string, unknown>>\n return (\n typeof candidate.asset === 'object' &&\n candidate.asset !== null &&\n typeof candidate.width === 'number' &&\n typeof candidate.height === 'number' &&\n typeof candidate.scale === 'number' &&\n typeof candidate.bytes === 'number' &&\n typeof candidate.format === 'string'\n )\n}\n\nfunction isCodeResult(payload: unknown): payload is ToolResultMap['get_code'] {\n if (typeof payload !== 'object' || !payload) return false\n const candidate = payload as Partial<ToolResultMap['get_code'] & Record<string, unknown>>\n return (\n typeof candidate.code === 'string' &&\n typeof candidate.lang === 'string' &&\n (candidate.assets === undefined || Array.isArray(candidate.assets))\n )\n}\n\nexport function coercePayloadToToolResponse(payload: unknown): CallToolResult {\n if (\n payload &&\n typeof payload === 'object' &&\n Array.isArray((payload as CallToolResult).content)\n ) {\n return payload as CallToolResult\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2)\n }\n ]\n }\n}\n\nexport { createToolErrorResponse }\n","import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport type {\n AssetDescriptor,\n GetAssetsParametersInput,\n GetAssetsResult,\n RegisteredMessage,\n StateMessage,\n ToolCallMessage,\n ToolName,\n ToolResultMap,\n ToolResultMessage\n} from '@tempad-dev/shared'\nimport type { RawData } from 'ws'\nimport type { ZodType } from 'zod'\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport {\n GetAssetsResultSchema,\n MessageFromExtensionSchema,\n TEMPAD_MCP_ERROR_CODES,\n type TempadMcpErrorCode\n} from '@tempad-dev/shared'\nimport { nanoid } from 'nanoid'\nimport { existsSync, rmSync, chmodSync } from 'node:fs'\nimport { createServer } from 'node:net'\nimport { WebSocketServer } from 'ws'\n\nimport type { AssetRecord, ExtensionConnection } from './types'\n\nimport { createAssetHttpServer } from './asset-http-server'\nimport { createAssetStore } from './asset-store'\nimport { buildAssetFilename } from './asset-utils'\nimport { getMcpServerConfig } from './config'\nimport MCP_INSTRUCTIONS from './instructions.md?raw'\nimport { register, resolve, reject, cleanupForExtension, cleanupAll } from './request'\nimport { PACKAGE_VERSION, log, RUNTIME_DIR, SOCK_PATH, ensureDir } from './shared'\nimport { TOOL_DEFS, coercePayloadToToolResponse, createToolErrorResponse } from './tools'\n\nconst SHUTDOWN_TIMEOUT = 2000\nconst { wsPortCandidates, toolTimeoutMs, maxPayloadBytes, autoActivateGraceMs, assetTtlMs } =\n getMcpServerConfig()\n\nlog.info({ version: PACKAGE_VERSION }, 'TemPad MCP Hub starting...')\n\nconst extensions: ExtensionConnection[] = []\nlet consumerCount = 0\ntype TimeoutHandle = ReturnType<typeof setTimeout>\nlet autoActivateTimer: TimeoutHandle | null = null\nlet selectedWsPort = 0\nconst consumerSessions = new Set<McpServer>()\ntype RegisterToolOptions = Parameters<McpServer['registerTool']>[1]\ntype McpInputSchema = RegisterToolOptions['inputSchema']\ntype McpOutputSchema = RegisterToolOptions['outputSchema']\ntype ToolResponse = CallToolResult\ntype SchemaOutput<Schema extends ZodType> = Schema['_output']\ntype ToolMetadataEntry = (typeof TOOL_DEFS)[number]\ntype ExtensionToolMetadata = Extract<ToolMetadataEntry, { target: 'extension' }>\ntype HubToolMetadata = Extract<ToolMetadataEntry, { target: 'hub' }>\n\ntype HubToolWithHandler<T extends HubToolMetadata = HubToolMetadata> = T & {\n handler: (args: SchemaOutput<T['parameters']>) => Promise<ToolResponse>\n}\n\nfunction getRecordProperty(record: unknown, key: string): unknown {\n if (!record || typeof record !== 'object') {\n return undefined\n }\n return Reflect.get(record, key)\n}\n\ntype RegisteredToolDefinition = ExtensionToolMetadata | HubToolWithHandler\n\nfunction enrichToolDefinition(tool: ToolMetadataEntry): RegisteredToolDefinition {\n if (tool.target === 'extension') {\n return tool\n }\n\n switch (tool.name) {\n case 'get_assets':\n return {\n ...tool,\n handler: handleGetAssets\n } satisfies HubToolWithHandler\n default:\n throw new Error('No handler configured for hub tool.')\n }\n}\n\nconst TOOL_DEFINITIONS: ReadonlyArray<RegisteredToolDefinition> = TOOL_DEFS.map((tool) =>\n enrichToolDefinition(tool)\n)\n\ntype RegisteredTool = (typeof TOOL_DEFINITIONS)[number]\ntype ExtensionTool = Extract<RegisteredTool, { target: 'extension' }>\ntype HubOnlyTool = Extract<RegisteredTool, { target: 'hub' }>\n\nfunction createCodedError(code: TempadMcpErrorCode, message: string): Error & { code: string } {\n const err = new Error(message) as Error & { code: string }\n err.code = code\n return err\n}\n\nfunction coerceToolError(error: unknown): Error {\n if (error instanceof Error) return error\n if (typeof error === 'string') return new Error(error)\n const messageValue = getRecordProperty(error, 'message')\n const codeValue = getRecordProperty(error, 'code')\n if (error && typeof error === 'object') {\n const message = typeof messageValue === 'string' ? messageValue : safeStringify(error)\n const err = new Error(message) as Error & { code?: string }\n if (typeof codeValue === 'string') err.code = codeValue\n return err\n }\n return new Error(String(error))\n}\n\nfunction safeStringify(input: unknown): string {\n try {\n return JSON.stringify(input)\n } catch {\n return String(input)\n }\n}\n\nfunction hasFormatter(tool: RegisteredToolDefinition): tool is ExtensionTool & {\n format: (payload: unknown) => ToolResponse\n} {\n return tool.target === 'extension' && 'format' in tool\n}\n\ntype ToolDefinitionByName = {\n [T in RegisteredToolDefinition as T['name']]: T\n}\n\nconst TOOL_BY_NAME: ToolDefinitionByName = Object.fromEntries(\n TOOL_DEFINITIONS.map((tool) => [tool.name, tool] as const)\n) as ToolDefinitionByName\n\nfunction getToolDefinition<Name extends ToolName>(name: Name): ToolDefinitionByName[Name] {\n return TOOL_BY_NAME[name]\n}\n\nconst assetStore = createAssetStore()\nconst assetHttpServer = createAssetHttpServer(assetStore)\nawait assetHttpServer.start()\nscheduleAssetCleanup()\n\nfunction scheduleAssetCleanup(): void {\n if (assetTtlMs <= 0) {\n log.info('Asset TTL cleanup disabled (TEMPAD_MCP_ASSET_TTL_MS=0).')\n return\n }\n pruneExpiredAssets(assetTtlMs)\n const intervalMs = Math.min(assetTtlMs, 24 * 60 * 60 * 1000)\n const timer = setInterval(() => {\n pruneExpiredAssets(assetTtlMs)\n }, intervalMs)\n unrefTimer(timer)\n log.info({ ttlMs: assetTtlMs, intervalMs }, 'Asset TTL cleanup enabled.')\n}\n\nfunction pruneExpiredAssets(ttlMs: number): void {\n const now = Date.now()\n let removed = 0\n let checked = 0\n for (const record of assetStore.list()) {\n checked += 1\n const lastAccess = Number.isFinite(record.lastAccess) ? record.lastAccess : record.uploadedAt\n if (!lastAccess) continue\n if (now - lastAccess > ttlMs) {\n assetStore.remove(record.hash)\n removed += 1\n }\n }\n log.info({ checked, removed, ttlMs }, 'Asset TTL sweep completed.')\n}\n\nfunction buildAssetDescriptor(record: AssetRecord): AssetDescriptor {\n const filename = buildAssetFilename(record.hash, record.mimeType)\n return {\n hash: record.hash,\n url: `${assetHttpServer.getBaseUrl()}/assets/${filename}`,\n mimeType: record.mimeType,\n size: record.size,\n width: record.metadata?.width,\n height: record.metadata?.height\n }\n}\n\nfunction createMcpServer(): McpServer {\n const mcp = new McpServer(\n { name: 'tempad-dev-mcp', version: PACKAGE_VERSION },\n MCP_INSTRUCTIONS ? { instructions: MCP_INSTRUCTIONS } : undefined\n )\n\n const registered: string[] = []\n for (const tool of TOOL_DEFINITIONS) {\n if ('exposed' in tool && tool.exposed === false) continue\n registerTool(mcp, tool)\n registered.push(tool.name)\n }\n log.info({ tools: registered }, 'Registered tools.')\n\n return mcp\n}\n\nfunction registerTool(mcp: McpServer, tool: RegisteredTool): void {\n if (tool.target === 'extension') {\n registerProxiedTool(mcp, tool)\n } else {\n registerLocalTool(mcp, tool)\n }\n}\n\nfunction registerProxiedTool<T extends ExtensionTool>(mcp: McpServer, tool: T): void {\n type Name = T['name']\n type Result = ToolResultMap[Name]\n\n const registerToolFn = mcp.registerTool.bind(mcp) as (\n name: string,\n options: { description: string; inputSchema: ZodType; outputSchema?: ZodType },\n handler: (args: unknown) => Promise<CallToolResult>\n ) => unknown\n\n const schema = tool.parameters\n const handler = async (args: unknown) => {\n let requestId: string | undefined\n try {\n const parsedArgs = schema.parse(args)\n const activeExt = extensions.find((e) => e.active)\n if (!activeExt) {\n throw createCodedError(\n TEMPAD_MCP_ERROR_CODES.NO_ACTIVE_EXTENSION,\n 'No active TemPad Dev extension available.'\n )\n }\n\n const registration = register<Result>(activeExt.id, toolTimeoutMs)\n requestId = registration.requestId\n\n const message: ToolCallMessage = {\n type: 'toolCall',\n id: registration.requestId,\n payload: {\n name: tool.name,\n args: parsedArgs\n }\n }\n activeExt.ws.send(JSON.stringify(message))\n log.info(\n { tool: tool.name, req: registration.requestId, extId: activeExt.id },\n 'Forwarded tool call.'\n )\n\n const payload = await registration.promise\n return createToolResponse(tool.name, payload)\n } catch (error) {\n const normalized = coerceToolError(error)\n log.error(\n {\n tool: tool.name,\n req: requestId,\n code: getRecordProperty(normalized, 'code'),\n message: normalized.message\n },\n 'Tool invocation failed.'\n )\n return createToolErrorResponse(tool.name, normalized)\n }\n }\n\n registerToolFn(\n tool.name,\n {\n description: tool.description,\n inputSchema: schema as unknown as McpInputSchema\n },\n handler\n )\n}\n\nfunction registerLocalTool(mcp: McpServer, tool: HubOnlyTool): void {\n const schema = tool.parameters\n const handler = tool.handler\n\n const registerToolFn = mcp.registerTool.bind(mcp) as (\n name: string,\n options: { description: string; inputSchema: ZodType; outputSchema?: ZodType },\n handler: (args: unknown) => Promise<CallToolResult>\n ) => unknown\n\n const registrationOptions: {\n description: string\n inputSchema: McpInputSchema\n outputSchema?: McpOutputSchema\n } = {\n description: tool.description,\n inputSchema: schema as unknown as McpInputSchema\n }\n\n if (tool.outputSchema) {\n registrationOptions.outputSchema = tool.outputSchema as unknown as McpOutputSchema\n }\n\n const registerHandler = async (args: unknown) => {\n try {\n const parsed = schema.parse(args)\n return await handler(parsed)\n } catch (error) {\n log.error({ tool: tool.name, error }, 'Local tool invocation failed.')\n return createToolErrorResponse(tool.name, error)\n }\n }\n\n registerToolFn(tool.name, registrationOptions, registerHandler)\n}\n\nfunction createToolResponse<Name extends ToolName>(\n toolName: Name,\n payload: ToolResultMap[Name]\n): ToolResponse {\n const definition = getToolDefinition(toolName)\n if (definition && hasFormatter(definition)) {\n try {\n const formatter = definition.format as (input: ToolResultMap[Name]) => ToolResponse\n return formatter(payload)\n } catch (error) {\n log.warn({ tool: toolName, error }, 'Failed to format tool result; returning raw payload.')\n return coercePayloadToToolResponse(payload)\n }\n }\n\n return coercePayloadToToolResponse(payload)\n}\n\nasync function handleGetAssets({ hashes }: GetAssetsParametersInput): Promise<ToolResponse> {\n if (hashes.length > 100) {\n throw new Error('Too many hashes requested. Limit is 100.')\n }\n const unique = Array.from(new Set(hashes))\n const records = assetStore.getMany(unique).filter((record) => {\n if (existsSync(record.filePath)) return true\n assetStore.remove(record.hash, { removeFile: false })\n return false\n })\n const found = new Set(records.map((record) => record.hash))\n const payload: GetAssetsResult = GetAssetsResultSchema.parse({\n assets: records.map((record) => buildAssetDescriptor(record)),\n missing: unique.filter((hash) => !found.has(hash))\n })\n\n const summary: string[] = []\n summary.push(\n payload.assets.length\n ? `Resolved ${payload.assets.length} asset${payload.assets.length === 1 ? '' : 's'}.`\n : 'No assets were resolved for the requested hashes.'\n )\n if (payload.missing.length) {\n summary.push(`Missing: ${payload.missing.join(', ')}`)\n }\n summary.push('Download bytes from each asset.url.')\n\n const content = [\n {\n type: 'text' as const,\n text: summary.join('\\n')\n }\n ]\n\n return {\n content,\n structuredContent: payload\n }\n}\n\nfunction getActiveId(): string | null {\n return extensions.find((e) => e.active)?.id ?? null\n}\n\nfunction setActive(targetId: string | null): void {\n extensions.forEach((e) => {\n e.active = targetId !== null && e.id === targetId\n })\n}\n\nfunction clearAutoActivateTimer(): void {\n if (autoActivateTimer) {\n clearTimeout(autoActivateTimer)\n autoActivateTimer = null\n }\n}\n\nfunction scheduleAutoActivate(): void {\n clearAutoActivateTimer()\n\n if (extensions.length !== 1 || getActiveId()) {\n return\n }\n\n const target = extensions[0]\n autoActivateTimer = setTimeout(() => {\n autoActivateTimer = null\n if (extensions.length === 1 && !getActiveId()) {\n setActive(target.id)\n log.info({ id: target.id }, 'Auto-activated sole extension after grace period.')\n broadcastState()\n }\n }, autoActivateGraceMs)\n}\n\nfunction unrefTimer(timer: TimeoutHandle): void {\n if (typeof timer === 'object' && timer !== null) {\n const handle = timer as NodeJS.Timeout\n if (typeof handle.unref === 'function') {\n handle.unref()\n }\n }\n}\n\nfunction broadcastState(): void {\n const activeId = getActiveId()\n const message: StateMessage = {\n type: 'state',\n activeId,\n count: extensions.length,\n port: selectedWsPort,\n assetServerUrl: assetHttpServer.getBaseUrl()\n }\n extensions.forEach((ext) => ext.ws.send(JSON.stringify(message)))\n log.debug({ activeId, count: extensions.length }, 'Broadcasted state.')\n}\n\nfunction rawDataToBuffer(raw: RawData): Buffer {\n if (typeof raw === 'string') return Buffer.from(raw)\n if (Buffer.isBuffer(raw)) return raw\n if (raw instanceof ArrayBuffer) return Buffer.from(raw)\n return Buffer.concat(raw)\n}\n\nfunction shutdown(): void {\n log.info('Hub is shutting down...')\n consumerSessions.forEach((session) => {\n session.close().catch((err) => {\n log.warn({ err }, 'Failed to close MCP session during shutdown.')\n })\n })\n consumerSessions.clear()\n assetStore.flush()\n assetHttpServer.stop()\n netServer.close(() => log.info('Net server closed.'))\n wss?.close(() => log.info('WebSocket server closed.'))\n cleanupAll()\n const timer = setTimeout(() => {\n log.warn('Shutdown timed out. Forcing exit.')\n process.exit(1)\n }, SHUTDOWN_TIMEOUT)\n unrefTimer(timer)\n}\n\ntry {\n ensureDir(RUNTIME_DIR)\n if (process.platform !== 'win32' && existsSync(SOCK_PATH)) {\n log.warn({ sock: SOCK_PATH }, 'Removing stale socket file.')\n rmSync(SOCK_PATH)\n }\n} catch (error: unknown) {\n log.error({ err: error }, 'Failed to initialize runtime environment.')\n process.exit(1)\n}\n\nconst netServer = createServer((sock) => {\n const mcp = createMcpServer()\n consumerSessions.add(mcp)\n consumerCount++\n log.info(`Consumer connected. Total: ${consumerCount}`)\n const transport = new StdioServerTransport(sock, sock)\n mcp.connect(transport).catch((err) => {\n log.error({ err }, 'Failed to attach MCP transport.')\n consumerSessions.delete(mcp)\n mcp.close().catch((closeErr) => log.warn({ err: closeErr }, 'MCP session close failed.'))\n transport.close().catch((closeErr) => log.warn({ err: closeErr }, 'Transport close failed.'))\n sock.destroy()\n })\n sock.on('error', (err) => {\n log.warn({ err }, 'Consumer socket error.')\n transport.close().catch((closeErr) => log.warn({ err: closeErr }, 'Transport close failed.'))\n })\n sock.on('close', async () => {\n await transport\n .close()\n .catch((closeErr) => log.warn({ err: closeErr }, 'Transport close failed.'))\n await mcp.close().catch((closeErr) => log.warn({ err: closeErr }, 'MCP session close failed.'))\n consumerSessions.delete(mcp)\n consumerCount--\n log.info(`Consumer disconnected. Remaining: ${consumerCount}`)\n if (consumerCount === 0) {\n log.info('Last consumer disconnected. Shutting down.')\n shutdown()\n }\n })\n})\nnetServer.on('error', (err) => {\n log.error({ err }, 'Net server error.')\n process.exit(1)\n})\nnetServer.listen(SOCK_PATH, () => {\n try {\n if (process.platform !== 'win32') chmodSync(SOCK_PATH, 0o600)\n } catch (err) {\n log.error({ err }, 'Failed to set socket permissions. Shutting down.')\n process.exit(1)\n }\n log.info({ sock: SOCK_PATH }, 'Hub socket ready.')\n})\n\nasync function startWebSocketServer(): Promise<{ wss: WebSocketServer; port: number }> {\n for (const candidate of wsPortCandidates) {\n const server = new WebSocketServer({\n host: '127.0.0.1',\n port: candidate,\n maxPayload: maxPayloadBytes\n })\n\n try {\n await new Promise<void>((resolve, reject) => {\n const onError = (err: NodeJS.ErrnoException) => {\n server.off('listening', onListening)\n reject(err)\n }\n const onListening = () => {\n server.off('error', onError)\n resolve()\n }\n server.once('error', onError)\n server.once('listening', onListening)\n })\n return { wss: server, port: candidate }\n } catch (err) {\n server.close()\n const errno = err as NodeJS.ErrnoException\n if (errno.code === 'EADDRINUSE') {\n log.warn({ port: candidate }, 'WebSocket port in use, trying next candidate.')\n continue\n }\n log.error({ err: errno, port: candidate }, 'Failed to start WebSocket server.')\n process.exit(1)\n }\n }\n\n log.error(\n { candidates: wsPortCandidates },\n 'Unable to start WebSocket server on any candidate port.'\n )\n process.exit(1)\n}\n\nconst { wss, port } = await startWebSocketServer()\nselectedWsPort = port\n\n// Add an error handler to prevent crashes from port conflicts, etc.\nwss.on('error', (err) => {\n log.error({ err }, 'WebSocket server critical error. Exiting.')\n process.exit(1)\n})\n\nwss.on('connection', (ws) => {\n const ext: ExtensionConnection = { id: nanoid(), ws, active: false }\n extensions.push(ext)\n log.info({ id: ext.id }, `Extension connected. Total: ${extensions.length}`)\n\n const message: RegisteredMessage = { type: 'registered', id: ext.id }\n ws.send(JSON.stringify(message))\n broadcastState()\n scheduleAutoActivate()\n\n ws.on('message', (raw: RawData, isBinary: boolean) => {\n if (isBinary) {\n log.warn({ extId: ext.id }, 'Unexpected binary message received.')\n return\n }\n\n const messageBuffer = rawDataToBuffer(raw)\n\n let parsedJson: unknown\n try {\n parsedJson = JSON.parse(messageBuffer.toString('utf-8'))\n } catch (e: unknown) {\n log.warn({ err: e, extId: ext.id }, 'Failed to parse message.')\n return\n }\n\n const parseResult = MessageFromExtensionSchema.safeParse(parsedJson)\n if (!parseResult.success) {\n log.warn({ error: parseResult.error.flatten(), extId: ext.id }, 'Invalid message shape.')\n return\n }\n const msg = parseResult.data\n\n switch (msg.type) {\n case 'activate': {\n setActive(ext.id)\n log.info({ id: ext.id }, 'Extension activated.')\n broadcastState()\n scheduleAutoActivate()\n break\n }\n case 'toolResult': {\n const { id, payload, error } = msg as ToolResultMessage\n if (error) {\n const normalized = coerceToolError(error)\n log.warn(\n {\n toolReq: id,\n extId: ext.id,\n code: getRecordProperty(normalized, 'code'),\n message: normalized.message\n },\n 'Received tool error from extension.'\n )\n reject(id, normalized)\n } else {\n resolve(id, payload)\n }\n break\n }\n }\n })\n\n ws.on('close', () => {\n const index = extensions.findIndex((e) => e.id === ext.id)\n if (index > -1) extensions.splice(index, 1)\n\n log.info({ id: ext.id }, `Extension disconnected. Remaining: ${extensions.length}`)\n cleanupForExtension(ext.id)\n\n if (ext.active) {\n log.warn({ id: ext.id }, 'Active extension disconnected.')\n setActive(null)\n }\n\n broadcastState()\n scheduleAutoActivate()\n })\n})\n\nlog.info({ port: selectedWsPort }, 'WebSocket server ready.')\n\nprocess.on('SIGINT', shutdown)\nprocess.on('SIGTERM', shutdown)\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hub.mjs","names":["createServer","getRecordProperty","MCP_INSTRUCTIONS"],"sources":["../../shared/dist/index.js","../src/asset-utils.ts","../src/config.ts","../src/asset-http-server.ts","../src/asset-store.ts","../src/instructions.md?raw","../src/request.ts","../src/tools.ts","../src/hub.ts"],"sourcesContent":["import { z } from \"zod\";\n\n//#region src/mcp/constants.ts\nconst MCP_PORT_CANDIDATES = [\n\t6220,\n\t7431,\n\t8127\n];\nconst MCP_MAX_PAYLOAD_BYTES = 4 * 1024 * 1024;\nconst MCP_TOOL_INLINE_BUDGET_BYTES = 64 * 1024;\nconst MCP_TOOL_TIMEOUT_MS = 15e3;\nconst MCP_AUTO_ACTIVATE_GRACE_MS = 1500;\nconst MCP_MAX_ASSET_BYTES = 8 * 1024 * 1024;\nconst MCP_ASSET_TTL_MS = 720 * 60 * 60 * 1e3;\nconst MCP_HASH_HEX_LENGTH = 8;\nconst MCP_HASH_PATTERN = new RegExp(`^[a-f0-9]{${MCP_HASH_HEX_LENGTH}}$`, \"i\");\n\n//#endregion\n//#region src/mcp/errors.ts\nconst TEMPAD_MCP_ERROR_CODES = {\n\tNO_ACTIVE_EXTENSION: \"NO_ACTIVE_EXTENSION\",\n\tEXTENSION_TIMEOUT: \"EXTENSION_TIMEOUT\",\n\tEXTENSION_DISCONNECTED: \"EXTENSION_DISCONNECTED\",\n\tINVALID_SELECTION: \"INVALID_SELECTION\",\n\tNODE_NOT_VISIBLE: \"NODE_NOT_VISIBLE\",\n\tASSET_SERVER_NOT_CONFIGURED: \"ASSET_SERVER_NOT_CONFIGURED\",\n\tTRANSPORT_NOT_CONNECTED: \"TRANSPORT_NOT_CONNECTED\"\n};\n\n//#endregion\n//#region src/mcp/install.ts\nconst SERVER_NAME = \"tempad-dev\";\nconst SERVER_COMMAND = \"npx\";\nconst SERVER_ARGS = [\"-y\", \"@tempad-dev/mcp@latest\"];\nconst SKILL_INSTALL_COMMAND = \"npx skills add https://github.com/ecomfe/tempad-dev/tree/main/skill\";\nconst stdioConfig = {\n\ttype: \"stdio\",\n\tcommand: SERVER_COMMAND,\n\targs: [...SERVER_ARGS]\n};\nconst commandConfig = {\n\tcommand: SERVER_COMMAND,\n\targs: [...SERVER_ARGS]\n};\nfunction toBase64(input) {\n\tif (typeof globalThis.btoa === \"function\") return globalThis.btoa(input);\n\tconst bufferLike = globalThis.Buffer;\n\tif (bufferLike) return bufferLike.from(input, \"utf8\").toString(\"base64\");\n\tthrow new Error(\"Base64 encoding not supported in this environment.\");\n}\nfunction buildVscodeDeepLink() {\n\treturn `vscode:mcp/install?${encodeURIComponent(JSON.stringify({\n\t\tname: SERVER_NAME,\n\t\t...stdioConfig\n\t}))}`;\n}\nfunction buildCursorConfigBase64() {\n\treturn encodeURIComponent(toBase64(JSON.stringify(commandConfig)));\n}\nfunction buildCursorDeepLink() {\n\treturn `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(SERVER_NAME)}&config=${buildCursorConfigBase64()}`;\n}\nfunction buildTraeDeepLink(protocol) {\n\treturn `${protocol}://trae.ai-ide/mcp-import?type=stdio&name=${encodeURIComponent(SERVER_NAME)}&config=${buildCursorConfigBase64()}`;\n}\nfunction buildWindsurfConfigSnippet() {\n\treturn JSON.stringify({ mcpServers: { [SERVER_NAME]: commandConfig } }, null, 2);\n}\nfunction buildCodexConfigSnippet() {\n\treturn [\n\t\t`[mcp_servers.${SERVER_NAME}]`,\n\t\t`command = ${JSON.stringify(SERVER_COMMAND)}`,\n\t\t`args = [${SERVER_ARGS.map((arg) => JSON.stringify(arg)).join(\", \")}]`\n\t].join(\"\\n\");\n}\nfunction buildCliCommand(prefix) {\n\tconst args = `${SERVER_COMMAND} ${SERVER_ARGS.join(\" \")}`;\n\tif (prefix === \"claude\") return `claude mcp add --transport stdio \"${SERVER_NAME}\" -- ${args}`;\n\treturn `codex mcp add \"${SERVER_NAME}\" -- ${args}`;\n}\nfunction getMcpClientCopyPayload(client, variant = \"primary\") {\n\tif (variant === \"alternate\" && client.alternateCopyText && client.alternateCopyKind) return {\n\t\ttext: client.alternateCopyText,\n\t\tkind: client.alternateCopyKind\n\t};\n\tif (!client.copyText) return null;\n\treturn {\n\t\ttext: client.copyText,\n\t\tkind: client.copyKind === \"config\" ? \"config\" : \"command\"\n\t};\n}\nfunction getNextMcpClientCopyVariant(client, currentVariant = \"primary\") {\n\tif (!client.alternateCopyText || !client.alternateCopyKind) return \"primary\";\n\treturn currentVariant === \"alternate\" ? \"primary\" : \"alternate\";\n}\nconst MCP_SERVER = {\n\tname: SERVER_NAME,\n\tcommand: SERVER_COMMAND,\n\targs: [...SERVER_ARGS]\n};\nconst MCP_DEFAULT_CONFIG_SNIPPET = JSON.stringify({ [SERVER_NAME]: commandConfig }, null, 2);\nconst MCP_SKILL_INSTALL_COMMAND = SKILL_INSTALL_COMMAND;\nconst MCP_CLIENTS_BY_ID = {\n\tvscode: {\n\t\tid: \"vscode\",\n\t\tname: \"VS Code\",\n\t\tbrandColor: \"#0098ff\",\n\t\tsupportsDeepLink: true,\n\t\tdeepLink: buildVscodeDeepLink()\n\t},\n\tcursor: {\n\t\tid: \"cursor\",\n\t\tname: \"Cursor\",\n\t\tbrandColor: [\"#000\", \"#fff\"],\n\t\tsupportsDeepLink: true,\n\t\tdeepLink: buildCursorDeepLink()\n\t},\n\twindsurf: {\n\t\tid: \"windsurf\",\n\t\tname: \"Windsurf\",\n\t\tbrandColor: [\"#0B100F\", \"#F0F3F2\"],\n\t\tsupportsDeepLink: false,\n\t\tcopyText: buildWindsurfConfigSnippet(),\n\t\tcopyKind: \"config\"\n\t},\n\tclaude: {\n\t\tid: \"claude\",\n\t\tname: \"Claude Code\",\n\t\tbrandColor: \"#D97757\",\n\t\tsupportsDeepLink: false,\n\t\tcopyText: buildCliCommand(\"claude\"),\n\t\tcopyKind: \"command\"\n\t},\n\tcodex: {\n\t\tid: \"codex\",\n\t\tname: \"Codex CLI\",\n\t\tbrandColor: [\"#0d0d0d\", \"#fff\"],\n\t\tsupportsDeepLink: false,\n\t\tcopyText: buildCliCommand(\"codex\"),\n\t\tcopyKind: \"command\",\n\t\talternateCopyText: buildCodexConfigSnippet(),\n\t\talternateCopyKind: \"config\"\n\t},\n\ttrae: {\n\t\tid: \"trae\",\n\t\tname: \"TRAE\",\n\t\tbrandColor: [\"#0fdc78\", \"#32f08c\"],\n\t\tsupportsDeepLink: true,\n\t\tdeepLink: buildTraeDeepLink(\"trae\"),\n\t\tfallbackDeepLink: buildTraeDeepLink(\"trae-cn\")\n\t}\n};\nconst MCP_CLIENTS = [\n\tMCP_CLIENTS_BY_ID.vscode,\n\tMCP_CLIENTS_BY_ID.cursor,\n\tMCP_CLIENTS_BY_ID.windsurf,\n\tMCP_CLIENTS_BY_ID.claude,\n\tMCP_CLIENTS_BY_ID.codex,\n\tMCP_CLIENTS_BY_ID.trae\n];\n\n//#endregion\n//#region src/mcp/protocol.ts\nconst RegisteredMessageSchema = z.object({\n\ttype: z.literal(\"registered\"),\n\tid: z.string()\n});\nconst StateMessageSchema = z.object({\n\ttype: z.literal(\"state\"),\n\tactiveId: z.string().nullable(),\n\tcount: z.number().nonnegative(),\n\tport: z.number().positive(),\n\tassetServerUrl: z.string().url()\n});\nconst ToolCallPayloadSchema = z.object({\n\tname: z.string(),\n\targs: z.unknown()\n});\nconst ToolCallMessageSchema = z.object({\n\ttype: z.literal(\"toolCall\"),\n\tid: z.string(),\n\tpayload: ToolCallPayloadSchema\n});\nconst MessageToExtensionSchema = z.discriminatedUnion(\"type\", [\n\tRegisteredMessageSchema,\n\tStateMessageSchema,\n\tToolCallMessageSchema\n]);\nconst ActivateMessageSchema = z.object({ type: z.literal(\"activate\") });\nconst ToolResultMessageSchema = z.object({\n\ttype: z.literal(\"toolResult\"),\n\tid: z.string(),\n\tpayload: z.unknown().optional(),\n\terror: z.unknown().optional()\n});\nconst MessageFromExtensionSchema = z.discriminatedUnion(\"type\", [ActivateMessageSchema, ToolResultMessageSchema]);\nfunction parseJsonWithSchema(data, schema) {\n\tlet parsed;\n\ttry {\n\t\tparsed = JSON.parse(data);\n\t} catch {\n\t\treturn null;\n\t}\n\tconst result = schema.safeParse(parsed);\n\treturn result.success ? result.data : null;\n}\nfunction parseMessageToExtension(data) {\n\treturn parseJsonWithSchema(data, MessageToExtensionSchema);\n}\nfunction parseMessageFromExtension(data) {\n\treturn parseJsonWithSchema(data, MessageFromExtensionSchema);\n}\n\n//#endregion\n//#region src/mcp/responses.ts\nconst ENCODER = new TextEncoder();\nfunction utf8Bytes(value) {\n\treturn ENCODER.encode(serializeUtf8Value(value)).length;\n}\nfunction measureCallToolResultBytes(result) {\n\treturn utf8Bytes(result);\n}\nfunction buildGetCodeToolResult(payload) {\n\tconst summary = [];\n\tconst codeSize = utf8Bytes(payload.code);\n\tsummary.push(`Generated \\`${payload.lang}\\` snippet (${formatBytes(codeSize)}).`);\n\tif (payload.warnings?.length) summary.push(...payload.warnings.map((warning) => warning.message));\n\tsummary.push(payload.assets?.length ? `Assets attached: ${payload.assets.length}. Download bytes from each asset.url.` : \"No binary assets were attached to this response.\");\n\tconst tokenCount = payload.tokens ? Object.keys(payload.tokens).length : 0;\n\tif (tokenCount) summary.push(`Token references included: ${tokenCount}.`);\n\tsummary.push(\"Read structuredContent for the full code string and metadata.\");\n\treturn buildTextToolResult(summary.join(\"\\n\"), payload);\n}\nfunction buildGetStructureToolResult(payload) {\n\tconst roots = payload.roots.length;\n\tconst nodeCount = countOutlineNodes(payload.roots);\n\treturn buildTextToolResult(`${roots === 0 ? \"No structure nodes were returned.\" : `Returned structure outline with ${formatCount(roots, \"root\")} and ${formatCount(nodeCount, \"node\")}.`}\\nRead structuredContent for the full outline payload.`, payload);\n}\nfunction buildGetTokenDefsToolResult(payload) {\n\tconst count = Object.keys(payload).length;\n\treturn buildTextToolResult(`${count === 0 ? \"No token definitions were resolved.\" : `Resolved ${formatCount(count, \"token definition\")}.`}\\nRead structuredContent for token values and aliases.`, payload);\n}\nfunction buildGetScreenshotToolResult(payload) {\n\treturn buildTextToolResult(`${describeScreenshot(payload)} - Download: ${payload.asset.url}`, payload);\n}\nfunction buildGetAssetsToolResult(payload) {\n\tconst summary = [];\n\tsummary.push(payload.assets.length ? `Resolved ${formatCount(payload.assets.length, \"asset\")}.` : \"No assets were resolved for the requested hashes.\");\n\tif (payload.missing.length) summary.push(`Missing: ${payload.missing.join(\", \")}`);\n\tsummary.push(\"Download bytes from each asset.url.\");\n\treturn buildTextToolResult(summary.join(\"\\n\"), payload);\n}\nfunction buildTextToolResult(text, structuredContent) {\n\treturn {\n\t\tcontent: [{\n\t\t\ttype: \"text\",\n\t\t\ttext\n\t\t}],\n\t\tstructuredContent\n\t};\n}\nfunction serializeUtf8Value(value) {\n\tif (typeof value === \"string\") return value;\n\treturn JSON.stringify(value, null, 0) ?? \"undefined\";\n}\nfunction countOutlineNodes(nodes) {\n\tlet count = 0;\n\tconst stack = [...nodes];\n\twhile (stack.length) {\n\t\tconst current = stack.pop();\n\t\tif (!current) continue;\n\t\tcount += 1;\n\t\tif (current.children?.length) stack.push(...current.children);\n\t}\n\treturn count;\n}\nfunction formatBytes(bytes) {\n\tif (bytes < 1024) return `${bytes} B`;\n\tif (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\nfunction formatCount(count, singular) {\n\treturn `${count} ${count === 1 ? singular : `${singular}s`}`;\n}\nfunction describeScreenshot(result) {\n\treturn `Screenshot ${result.width}x${result.height} @${result.scale}x (${formatBytes(result.bytes)})`;\n}\n\n//#endregion\n//#region src/mcp/tools.ts\nconst AssetDescriptorSchema = z.object({\n\thash: z.string().min(1),\n\turl: z.string().url(),\n\tmimeType: z.string().min(1),\n\tsize: z.number().int().nonnegative(),\n\twidth: z.number().int().positive().optional(),\n\theight: z.number().int().positive().optional(),\n\tthemeable: z.boolean().optional()\n});\nconst GetCodeParametersSchema = z.object({\n\tnodeId: z.string().describe(\"Optional target node id; omit to use the current single selection when pulling the baseline snapshot.\").optional(),\n\tpreferredLang: z.enum([\"jsx\", \"vue\"]).describe(\"Preferred output language to bias the snapshot; otherwise uses the design’s hint/detected language, then falls back to JSX.\").optional(),\n\tresolveTokens: z.boolean().describe(\"Inline token values instead of references for quick renders; default false returns token metadata so you can map into your theming system. When true, values are resolved per-node (mode-aware).\").optional(),\n\tvectorMode: z.enum([\"smart\", \"snapshot\"]).describe(\"Vector output mode. `smart` (default) emits `<svg data-src=\\\"...\\\">` placeholders in code and preserves themeable instance color on the emitted SVG root markup for downstream adaptation; if asset upload fails after export, the tool may inline the SVG as a fallback to preserve source of truth. `snapshot` preserves vector assets for fidelity. Final vector delivery may still be adapted to the Host app’s SVG policy.\").optional()\n});\nconst GetTokenDefsParametersSchema = z.object({\n\tnames: z.array(z.string().regex(/^--[a-zA-Z0-9-_]+$/)).min(1).describe(\"Canonical token names (CSS variable form) from Object.keys(get_code.tokens) or your own list to resolve, e.g., --color-primary.\"),\n\tincludeAllModes: z.boolean().describe(\"Include all token modes (light/dark/etc.) instead of just the active one to mirror responsive tokens; default false.\").optional()\n});\nconst GetScreenshotParametersSchema = z.object({ nodeId: z.string().describe(\"Optional node id to screenshot; defaults to the current single selection. Useful when layout/overlap is uncertain (auto-layout none/inferred).\").optional() });\nconst GetStructureParametersSchema = z.object({\n\tnodeId: z.string().describe(\"Optional node id to outline; defaults to the current single selection. Useful when auto-layout hints are none/inferred or you need explicit geometry for refactors.\").optional(),\n\toptions: z.object({ depth: z.number().int().positive().describe(\"Limit traversal depth; defaults to full tree (subject to safety caps).\").optional() }).optional()\n});\nconst GetAssetsParametersSchema = z.object({ hashes: z.array(z.string().regex(MCP_HASH_PATTERN)).min(1).describe(\"Asset hashes returned from get_code (or other tools) to download/resolve exact bytes for rasterized images or SVGs before routing through your asset pipeline.\") });\nconst GetAssetsResultSchema = z.object({\n\tassets: z.array(AssetDescriptorSchema),\n\tmissing: z.array(z.string().min(1))\n});\n\n//#endregion\n//#region src/figma/color.ts\n/**\n* Color utilities for Figma styles\n*/\n/**\n* Formats a Figma color with opacity to hex notation\n* @param color RGB color object with values 0-1\n* @param opacity Optional opacity value 0-1\n* @returns Hex color string (e.g., \"#FF0000\" or \"#FF0000CC\")\n*/\nfunction formatHexAlpha(color, opacity = 1) {\n\tconst toHex = (n) => {\n\t\treturn Math.min(255, Math.max(0, Math.round(n * 255))).toString(16).padStart(2, \"0\").toUpperCase();\n\t};\n\tconst r = toHex(color.r);\n\tconst g = toHex(color.g);\n\tconst b = toHex(color.b);\n\tif (opacity >= .99) {\n\t\tif (r[0] === r[1] && g[0] === g[1] && b[0] === b[1]) return `#${r[0]}${g[0]}${b[0]}`;\n\t\treturn `#${r}${g}${b}`;\n\t}\n\tconst a = toHex(opacity);\n\tif (r[0] === r[1] && g[0] === g[1] && b[0] === b[1] && a[0] === a[1]) return `#${r[0]}${g[0]}${b[0]}${a[0]}`;\n\treturn `#${r}${g}${b}${a}`;\n}\n\n//#endregion\n//#region src/figma/gradient.ts\nconst RE_NON_ASCII = /\\P{ASCII}+/gu;\nconst RE_QUOTES = /['\"]/g;\nconst RE_SLASH = /\\//g;\nconst RE_SPACE_TAB = /[ \\t]+/g;\nconst RE_WHITESPACE = /\\s+/g;\nconst RE_FAST_PATH = /^[A-Za-z0-9_-]+$/;\nconst RE_BOUND_NON_ALPHANUM = /[^A-Za-z0-9]+/g;\nconst RE_HYPHENS = /-+/g;\nconst RE_BOUND_DIGIT = /([A-Za-z])([0-9])|([0-9])([A-Za-z])/g;\nconst RE_BOUND_CASE = /([a-z])([A-Z])|([A-Z])([A-Z][a-z])/g;\nconst RE_DIGIT = /^\\d+$/;\nconst RE_CAPS = /^[A-Z]+$/;\nconst RE_SINGLE = /^[A-Za-z]$/;\nfunction isVisiblePaint(paint) {\n\treturn !!paint && paint.visible !== false;\n}\nfunction isGradientPaint(paint) {\n\treturn \"gradientStops\" in paint && Array.isArray(paint.gradientStops);\n}\nfunction isSolidPaint(paint) {\n\treturn paint.type === \"SOLID\";\n}\nconst DEFAULT_READERS$1 = {\n\tgetStyleById: (id) => figma.getStyleById(id),\n\tgetVariableById: (id) => figma.variables.getVariableById(id)\n};\nfunction hasGradientHandlePositions(paint) {\n\treturn \"gradientHandlePositions\" in paint && Array.isArray(paint.gradientHandlePositions);\n}\n/**\n* Resolves gradient from paint array\n* Returns CSS gradient string or null\n*/\nfunction resolveGradientFromPaints(paints, size, readers = DEFAULT_READERS$1) {\n\tif (!paints || !Array.isArray(paints)) return null;\n\tconst gradientPaint = paints.find((paint) => isVisiblePaint(paint) && isGradientPaint(paint));\n\tif (!gradientPaint) return null;\n\tconst fillOpacity = typeof gradientPaint.opacity === \"number\" ? gradientPaint.opacity : 1;\n\tconst stops = gradientPaint.gradientStops.map((stop) => {\n\t\tconst pct = formatPercent(stop.position);\n\t\treturn `${formatGradientStopColor(stop, fillOpacity, readers)} ${pct}`;\n\t});\n\tswitch (gradientPaint.type) {\n\t\tcase \"GRADIENT_LINEAR\": {\n\t\t\tconst angle = resolveLinearGradientAngle(gradientPaint, size);\n\t\t\treturn `linear-gradient(${(angle == null ? stops : [`${angle}deg`, ...stops]).join(\", \")})`;\n\t\t}\n\t\tcase \"GRADIENT_RADIAL\":\n\t\tcase \"GRADIENT_DIAMOND\": return `radial-gradient(${stops.join(\", \")})`;\n\t\tcase \"GRADIENT_ANGULAR\": return `conic-gradient(${stops.join(\", \")})`;\n\t\tdefault: return null;\n\t}\n}\n/**\n* Resolves solid color from paint array\n* Returns hex color string or null\n*/\nfunction resolveSolidFromPaints(paints, readers = DEFAULT_READERS$1) {\n\tif (!paints || !Array.isArray(paints)) return null;\n\tconst solidPaint = paints.find((paint) => isVisiblePaint(paint) && isSolidPaint(paint));\n\tif (!solidPaint || !solidPaint.color) return null;\n\tconst bound = solidPaint.boundVariables?.color;\n\tif (bound && typeof bound === \"object\" && \"id\" in bound && bound.id) try {\n\t\tconst variable = readers.getVariableById(bound.id);\n\t\tif (variable) {\n\t\t\tconst fallback = formatHexAlpha(solidPaint.color, solidPaint.opacity);\n\t\t\treturn `var(${getVariableCssCustomPropertyName(variable)}, ${fallback})`;\n\t\t}\n\t} catch {}\n\treturn formatHexAlpha(solidPaint.color, solidPaint.opacity);\n}\n/**\n* Formats a gradient stop color\n*/\nfunction formatGradientStopColor(stop, fillOpacity, readers) {\n\tconst baseAlpha = stop.color?.a ?? 1;\n\tconst alpha = Math.max(0, Math.min(1, baseAlpha * fillOpacity));\n\tconst bound = stop.boundVariables?.color;\n\tif (bound && typeof bound === \"object\" && \"id\" in bound && bound.id) try {\n\t\tconst v = readers.getVariableById(bound.id);\n\t\tif (v) {\n\t\t\tconst fallbackOpaque = formatHexAlpha(stop.color, 1);\n\t\t\tconst fallbackAlpha = formatHexAlpha(stop.color, alpha);\n\t\t\tconst cssVarName = getVariableCssCustomPropertyName(v);\n\t\t\tconst varName = `var(${cssVarName}, ${fallbackOpaque})`;\n\t\t\tif (alpha >= .99) return `var(${cssVarName}, ${fallbackAlpha})`;\n\t\t\treturn `color-mix(in srgb, ${varName} ${Math.round(alpha * 1e4) / 100}%, transparent)`;\n\t\t}\n\t} catch {}\n\treturn formatHexAlpha(stop.color, alpha);\n}\n/**\n* Resolves linear gradient angle from gradient paint\n*/\nfunction resolveLinearGradientAngle(paint, size) {\n\tif (hasGradientHandlePositions(paint) && paint.gradientHandlePositions.length >= 2) {\n\t\tconst start = paint.gradientHandlePositions[0];\n\t\tconst end = paint.gradientHandlePositions[1];\n\t\tif (start && end) {\n\t\t\tconst { width, height } = getGradientSize(size);\n\t\t\tconst angle = normalizeGradientAngle((end.x - start.x) * width, (end.y - start.y) * height);\n\t\t\tif (angle != null) return angle;\n\t\t}\n\t}\n\tconst extracted = extractLinearGradientVectorFromTransform(paint.gradientTransform, size);\n\tif (!extracted) return null;\n\tconst { dx, dy } = extracted;\n\treturn normalizeGradientAngle(dx, dy);\n}\nfunction extractLinearGradientVectorFromTransform(transform, size) {\n\tif (!transform || !Array.isArray(transform) || transform.length < 2) return null;\n\tconst row0 = transform[0];\n\tconst row1 = transform[1];\n\tif (!Array.isArray(row0) || !Array.isArray(row1) || row0.length < 2 || row1.length < 2) return null;\n\tconst a = row0[0];\n\tconst c = row0[1];\n\tconst e = row0[2] ?? 0;\n\tconst b = row1[0];\n\tconst d = row1[1];\n\tconst f = row1[2] ?? 0;\n\tif (![\n\t\ta,\n\t\tb,\n\t\tc,\n\t\td,\n\t\te,\n\t\tf\n\t].every((value) => Number.isFinite(value))) return null;\n\tconst det = a * d - b * c;\n\tif (!Number.isFinite(det) || Math.abs(det) < 1e-8) return null;\n\tconst invA = d / det;\n\tconst invC = -c / det;\n\tconst invE = (c * f - d * e) / det;\n\tconst invB = -b / det;\n\tconst invD = a / det;\n\tconst invF = (b * e - a * f) / det;\n\tconst start = applyTransform(invA, invC, invE, invB, invD, invF, 0, .5);\n\tconst end = applyTransform(invA, invC, invE, invB, invD, invF, 1, .5);\n\tconst { width, height } = getGradientSize(size);\n\treturn {\n\t\tdx: (end.x - start.x) * width,\n\t\tdy: (end.y - start.y) * height\n\t};\n}\nfunction applyTransform(a, c, e, b, d, f, x, y) {\n\treturn {\n\t\tx: a * x + c * y + e,\n\t\ty: b * x + d * y + f\n\t};\n}\nfunction getGradientSize(size) {\n\tconst width = size?.width;\n\tconst height = size?.height;\n\treturn {\n\t\twidth: typeof width === \"number\" && Number.isFinite(width) && width > 0 ? width : 1,\n\t\theight: typeof height === \"number\" && Number.isFinite(height) && height > 0 ? height : 1\n\t};\n}\n/**\n* Normalizes gradient angle to degrees\n*/\nfunction normalizeGradientAngle(dx, dy) {\n\tif (!Number.isFinite(dx) || !Number.isFinite(dy)) return null;\n\tif (dx === 0 && dy === 0) return null;\n\tlet angle = Math.atan2(dy, dx) * 180 / Math.PI + 90;\n\tangle = (angle % 360 + 360) % 360;\n\treturn Math.round(angle * 100) / 100;\n}\n/**\n* Formats position as percentage\n*/\nfunction formatPercent(pos) {\n\treturn `${Math.round(pos * 1e4) / 100}%`;\n}\n/**\n* Variable naming must match MCP token indexing semantics exactly.\n*/\nfunction getVariableCssCustomPropertyName(variable) {\n\treturn normalizeFigmaVarName(getVariableRawName(variable));\n}\nfunction getVariableRawName(variable) {\n\tconst cs = variable.codeSyntax?.WEB;\n\tif (typeof cs === \"string\" && cs.trim()) {\n\t\tconst canonical = canonicalizeVarName(cs.trim());\n\t\tif (canonical) return canonical.slice(2);\n\t\tconst ident = cs.trim();\n\t\tif (/^[A-Za-z0-9_-]+$/.test(ident)) return ident;\n\t}\n\tconst raw = variable.name?.trim?.() ?? \"\";\n\tif (raw.startsWith(\"--\")) return raw.slice(2);\n\treturn raw;\n}\nfunction canonicalizeVarName(value) {\n\tconst cleaned = value.trim();\n\tconst varMatch = cleaned.match(/^var\\(\\s*(--[A-Za-z0-9_-]+)(?:\\s*,[\\s\\S]*)?\\)$/);\n\tif (varMatch?.[1]) return normalizeCustomPropertyName(varMatch[1]);\n\tif (cleaned.startsWith(\"--\")) return normalizeCustomPropertyName(cleaned);\n\treturn null;\n}\nfunction normalizeCustomPropertyBody(name) {\n\tif (!name) return \"var\";\n\tlet raw = name.trim();\n\tif (raw.startsWith(\"--\")) raw = raw.slice(2);\n\traw = raw.replace(/^-+/, \"\");\n\traw = raw.replace(/[^A-Za-z0-9_-]/g, \"\");\n\treturn raw || \"var\";\n}\nfunction normalizeCustomPropertyName(name) {\n\treturn `--${normalizeCustomPropertyBody(name)}`;\n}\nfunction normalizeFigmaVarName(input) {\n\tlet raw = (input ?? \"\").trim();\n\tif (!raw) return \"--unnamed\";\n\tconst canonical = canonicalizeVarName(raw);\n\tif (canonical) return canonical;\n\tif (raw.startsWith(\"--\")) raw = raw.slice(2).trim();\n\traw = raw.replace(RE_NON_ASCII, \"\").replace(RE_QUOTES, \"\").replace(RE_SLASH, \"-\").replace(RE_SPACE_TAB, \"-\").replace(RE_WHITESPACE, \"\");\n\tif (RE_FAST_PATH.test(raw)) return `--${raw}`;\n\tconst parts = raw.replace(RE_BOUND_NON_ALPHANUM, \"-\").replace(RE_HYPHENS, \"-\").replace(RE_BOUND_DIGIT, \"$1-$2$3-$4\").replace(RE_BOUND_CASE, \"$1$3-$2$4\").split(\"-\").filter(Boolean);\n\tconst stack = [];\n\tfor (const part of parts) {\n\t\tconst prev = stack[stack.length - 1];\n\t\tif (prev && RE_DIGIT.test(prev) && RE_DIGIT.test(part)) stack[stack.length - 1] += part;\n\t\telse if (prev && RE_CAPS.test(prev) && RE_CAPS.test(part)) stack[stack.length - 1] += part;\n\t\telse stack.push(part);\n\t}\n\tconst merged = [];\n\tfor (let i = 0; i < stack.length;) {\n\t\tif (i === 0) {\n\t\t\tmerged.push(stack[0]);\n\t\t\ti += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (RE_SINGLE.test(stack[i])) {\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < stack.length && RE_SINGLE.test(stack[j])) j += 1;\n\t\t\tconst run = stack.slice(i, j);\n\t\t\tmerged.push(run.length >= 2 ? run.join(\"\") : stack[i]);\n\t\t\ti = j;\n\t\t\tcontinue;\n\t\t}\n\t\tmerged.push(stack[i]);\n\t\ti += 1;\n\t}\n\tconst out = merged.join(\"-\").toLowerCase();\n\treturn out ? `--${out}` : \"--unnamed\";\n}\n\n//#endregion\n//#region src/figma/style-resolver.ts\nconst BG_URL_LIGHTGRAY_RE = /url\\(.*?\\)\\s+lightgray\\b/i;\nconst BG_URL_RE = /url\\(/i;\nconst DEFAULT_READERS = {\n\tgetStyleById: (id) => figma.getStyleById(id),\n\tgetVariableById: (id) => figma.variables.getVariableById(id)\n};\nfunction hasStyleId(value) {\n\treturn typeof value === \"string\" && value.length > 0;\n}\nfunction isPaintStyle(style) {\n\treturn !!style && \"paints\" in style && Array.isArray(style.paints);\n}\nfunction resolvePaintStyleFromPaints(paints, size, readers = DEFAULT_READERS) {\n\tif (!paints) return null;\n\tconst gradient = resolveGradientFromPaints(paints, size, readers);\n\tif (gradient) return { gradient };\n\tconst solidColor = resolveSolidFromPaints(paints, readers);\n\treturn solidColor ? { solidColor } : null;\n}\nfunction resolvePaintStyleFromStyleId(styleId, kind, size, readers = DEFAULT_READERS) {\n\tif (!hasStyleId(styleId)) return null;\n\ttry {\n\t\tconst style = readers.getStyleById(styleId);\n\t\tif (!isPaintStyle(style)) return null;\n\t\treturn resolvePaintStyleFromPaints(style.paints, size, readers);\n\t} catch (error) {\n\t\tconsole.warn(`Failed to resolve ${kind} style:`, error);\n\t\treturn null;\n\t}\n}\nfunction getFillStyleId(input) {\n\treturn input.fillStyleId ?? null;\n}\nfunction getStrokeStyleId(input) {\n\treturn input.strokeStyleId ?? null;\n}\nfunction getFillPaints(input) {\n\treturn input.fills ?? null;\n}\nfunction getStrokePaints(input) {\n\treturn input.strokes ?? null;\n}\nfunction resolveNodePaintStyle(styleId, paints, kind, size, readers = DEFAULT_READERS) {\n\treturn resolvePaintStyleFromStyleId(styleId, kind, size, readers) ?? resolvePaintStyleFromPaints(paints, size, readers);\n}\nfunction getNodeDimensions(node) {\n\tif (!(\"width\" in node) || !(\"height\" in node)) return void 0;\n\tconst width = node.width;\n\tconst height = node.height;\n\tif (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;\n\treturn {\n\t\twidth,\n\t\theight\n\t};\n}\nfunction createNodePaintStyleInput(node) {\n\treturn {\n\t\tfillStyleId: \"fillStyleId\" in node ? node.fillStyleId : null,\n\t\tstrokeStyleId: \"strokeStyleId\" in node ? node.strokeStyleId : null,\n\t\tfills: \"fills\" in node && Array.isArray(node.fills) ? node.fills : null,\n\t\tstrokes: \"strokes\" in node && Array.isArray(node.strokes) ? node.strokes : null,\n\t\tdimensions: getNodeDimensions(node)\n\t};\n}\nfunction splitByTopLevelWhitespace$1(input) {\n\tconst out = [];\n\tlet depth = 0;\n\tlet quote = null;\n\tlet buffer = \"\";\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst ch = input[i];\n\t\tif (quote) {\n\t\t\tif (ch === \"\\\\\") {\n\t\t\t\tbuffer += ch;\n\t\t\t\ti++;\n\t\t\t\tif (i < input.length) buffer += input[i];\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (ch === quote) quote = null;\n\t\t\tbuffer += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\"\" || ch === \"'\") {\n\t\t\tquote = ch;\n\t\t\tbuffer += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") depth = Math.max(0, depth - 1);\n\t\tif (/\\s/.test(ch) && depth === 0) {\n\t\t\tif (buffer) {\n\t\t\t\tout.push(buffer);\n\t\t\t\tbuffer = \"\";\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tbuffer += ch;\n\t}\n\tif (buffer) out.push(buffer);\n\treturn out;\n}\nfunction isVarFunctionToken(value) {\n\tconst trimmed = value.trim();\n\treturn trimmed.startsWith(\"var(\") && trimmed.endsWith(\")\");\n}\nfunction patchBorderVarColor$1(borderValue, color) {\n\tconst borderParts = splitByTopLevelWhitespace$1(borderValue);\n\tif (!borderParts.length) return null;\n\tconst lastIndex = borderParts.length - 1;\n\tif (!isVarFunctionToken(borderParts[lastIndex])) return null;\n\tborderParts[lastIndex] = color;\n\treturn borderParts.join(\" \");\n}\nfunction hasBorderChannels(style) {\n\treturn Object.entries(style).some(([key, value]) => {\n\t\tif (!value?.trim()) return false;\n\t\tif (!/^border(?:$|-)/.test(key)) return false;\n\t\tif (key.includes(\"radius\") || key === \"border-image\" || key === \"border-image-slice\") return false;\n\t\treturn true;\n\t});\n}\n/**\n* Resolves fill style for a Figma node\n* Handles both fillStyleId and direct fills\n*/\nfunction resolveFillStyle(input, readers) {\n\treturn resolveNodePaintStyle(getFillStyleId(input), getFillPaints(input), \"fill\", input.dimensions, readers);\n}\n/**\n* Resolves stroke style for a Figma node\n* Handles both strokeStyleId and direct strokes\n*/\nfunction resolveStrokeStyle(input, readers) {\n\treturn resolveNodePaintStyle(getStrokeStyleId(input), getStrokePaints(input), \"stroke\", input.dimensions, readers);\n}\nfunction resolveFillStyleForNode(node, readers) {\n\treturn resolveFillStyle(createNodePaintStyleInput(node), readers);\n}\nfunction resolveStrokeStyleForNode(node, readers) {\n\treturn resolveStrokeStyle(createNodePaintStyleInput(node), readers);\n}\n/**\n* Main function to resolve all styles from a node\n* Replaces CSS variable references with actual values\n*/\nasync function resolveStylesFromNodeData(cssStyles, input, readers = DEFAULT_READERS) {\n\tconst processed = { ...cssStyles };\n\tconst fillPaints = getFillPaints(input);\n\tif (processed.background && BG_URL_LIGHTGRAY_RE.test(processed.background) && fillPaints) {\n\t\tconst solidFill = resolveSolidFromPaints(fillPaints, readers);\n\t\tif (solidFill) processed[\"background-color\"] = solidFill;\n\t\tprocessed.background = processed.background.replace(/\\s*,?\\s*lightgray\\b/i, \"\").trim();\n\t}\n\tconst resolvedFill = resolveFillStyle(input, readers);\n\tconst hasUrlBackground = typeof processed.background === \"string\" && BG_URL_RE.test(processed.background);\n\tif (resolvedFill?.gradient) {\n\t\tif (processed.background && !hasUrlBackground) processed.background = resolvedFill.gradient;\n\t\telse if (processed[\"background-color\"]) {\n\t\t\tprocessed.background = resolvedFill.gradient;\n\t\t\tdelete processed[\"background-color\"];\n\t\t}\n\t} else if (resolvedFill?.solidColor) {\n\t\tif (processed.background && !hasUrlBackground) {\n\t\t\tprocessed[\"background-color\"] = resolvedFill.solidColor;\n\t\t\tdelete processed.background;\n\t\t}\n\t\tif (processed[\"background-color\"]) processed[\"background-color\"] = resolvedFill.solidColor;\n\t\tif (processed.color) processed.color = resolvedFill.solidColor;\n\t\tif (processed.fill) processed.fill = resolvedFill.solidColor;\n\t}\n\tconst resolvedStroke = resolveStrokeStyle(input, readers);\n\tif (resolvedStroke?.gradient && hasBorderChannels(processed)) {\n\t\tprocessed[\"border-image\"] = resolvedStroke.gradient;\n\t\tprocessed[\"border-image-slice\"] = \"1\";\n\t} else if (resolvedStroke?.solidColor) {\n\t\tif (processed[\"border-color\"]) processed[\"border-color\"] = resolvedStroke.solidColor;\n\t\telse if (processed.border) {\n\t\t\tconst patched = patchBorderVarColor$1(processed.border, resolvedStroke.solidColor);\n\t\t\tif (patched) processed.border = patched;\n\t\t}\n\t}\n\tif (processed.stroke && resolvedStroke?.gradient) processed.stroke = resolvedStroke.gradient;\n\telse if (processed.stroke && resolvedStroke?.solidColor) processed.stroke = resolvedStroke.solidColor;\n\treturn processed;\n}\nasync function resolveStylesFromNode(cssStyles, node, readers) {\n\treturn resolveStylesFromNodeData(cssStyles, createNodePaintStyleInput(node), readers);\n}\n\n//#endregion\n//#region src/figma/stroke.ts\nfunction splitByTopLevelWhitespace(input) {\n\tconst out = [];\n\tlet depth = 0;\n\tlet quote = null;\n\tlet buffer = \"\";\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst ch = input[i];\n\t\tif (quote) {\n\t\t\tif (ch === \"\\\\\") {\n\t\t\t\tbuffer += ch;\n\t\t\t\ti++;\n\t\t\t\tif (i < input.length) buffer += input[i];\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (ch === quote) quote = null;\n\t\t\tbuffer += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\"\" || ch === \"'\") {\n\t\t\tquote = ch;\n\t\t\tbuffer += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") depth = Math.max(0, depth - 1);\n\t\tif (/\\s/.test(ch) && depth === 0) {\n\t\t\tif (buffer) {\n\t\t\t\tout.push(buffer);\n\t\t\t\tbuffer = \"\";\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tbuffer += ch;\n\t}\n\tif (buffer) out.push(buffer);\n\treturn out;\n}\nfunction patchBorderVarColor(borderValue, color) {\n\tconst borderParts = splitByTopLevelWhitespace(borderValue);\n\tif (!borderParts.length) return null;\n\tconst lastIndex = borderParts.length - 1;\n\tconst tail = borderParts[lastIndex].trim();\n\tif (!tail.startsWith(\"var(\") || !tail.endsWith(\")\")) return null;\n\tborderParts[lastIndex] = color;\n\treturn borderParts.join(\" \");\n}\n/**\n* Resolves stroke styles from paint array\n* Can handle both solid colors and gradients\n* @param paints Array of paint objects from strokes or stroke style\n* @returns Object with solidColor or gradient, or null\n*/\nfunction resolveStrokeFromPaints(paints) {\n\tif (!paints || !Array.isArray(paints)) return null;\n\tconst gradient = resolveGradientFromPaints(paints);\n\tif (gradient) return { gradient };\n\tconst solidColor = resolveSolidFromPaints(paints);\n\tif (solidColor) return { solidColor };\n\treturn null;\n}\n/**\n* Applies resolved stroke styles to CSS properties\n* Handles different CSS properties for stroke (border, stroke, outline)\n*/\nfunction applyStrokeToCSS(styles, resolved) {\n\tif (!resolved) return styles;\n\tconst processed = { ...styles };\n\tconst borderHasVar = processed.border?.includes(\"var(--\") ?? false;\n\tconst borderColorHasVar = processed[\"border-color\"]?.includes(\"var(--\") ?? false;\n\tif (borderHasVar || borderColorHasVar) {\n\t\tif (resolved.gradient) {\n\t\t\tprocessed[\"border-image\"] = `${resolved.gradient} 1`;\n\t\t\tprocessed[\"border-image-slice\"] = \"1\";\n\t\t\tdelete processed[\"border-color\"];\n\t\t} else if (resolved.solidColor) if (borderColorHasVar) processed[\"border-color\"] = resolved.solidColor;\n\t\telse {\n\t\t\tconst patched = patchBorderVarColor(processed.border, resolved.solidColor);\n\t\t\tif (patched) processed.border = patched;\n\t\t}\n\t}\n\tif (processed.stroke?.includes(\"var(--\")) {\n\t\tif (resolved.gradient) processed.stroke = resolved.gradient;\n\t\telse if (resolved.solidColor) processed.stroke = resolved.solidColor;\n\t}\n\tif (processed[\"outline-color\"]?.includes(\"var(--\")) {\n\t\tif (resolved.solidColor) processed[\"outline-color\"] = resolved.solidColor;\n\t}\n\treturn processed;\n}\n\n//#endregion\nexport { ActivateMessageSchema, AssetDescriptorSchema, GetAssetsParametersSchema, GetAssetsResultSchema, GetCodeParametersSchema, GetScreenshotParametersSchema, GetStructureParametersSchema, GetTokenDefsParametersSchema, MCP_ASSET_TTL_MS, MCP_AUTO_ACTIVATE_GRACE_MS, MCP_CLIENTS, MCP_CLIENTS_BY_ID, MCP_DEFAULT_CONFIG_SNIPPET, MCP_HASH_HEX_LENGTH, MCP_HASH_PATTERN, MCP_MAX_ASSET_BYTES, MCP_MAX_PAYLOAD_BYTES, MCP_PORT_CANDIDATES, MCP_SERVER, MCP_SKILL_INSTALL_COMMAND, MCP_TOOL_INLINE_BUDGET_BYTES, MCP_TOOL_TIMEOUT_MS, MessageFromExtensionSchema, MessageToExtensionSchema, RegisteredMessageSchema, StateMessageSchema, TEMPAD_MCP_ERROR_CODES, ToolCallMessageSchema, ToolCallPayloadSchema, ToolResultMessageSchema, applyStrokeToCSS, buildGetAssetsToolResult, buildGetCodeToolResult, buildGetScreenshotToolResult, buildGetStructureToolResult, buildGetTokenDefsToolResult, formatHexAlpha, getMcpClientCopyPayload, getNextMcpClientCopyVariant, measureCallToolResultBytes, parseMessageFromExtension, parseMessageToExtension, resolveFillStyle, resolveFillStyleForNode, resolveGradientFromPaints, resolveSolidFromPaints, resolveStrokeFromPaints, resolveStrokeStyle, resolveStrokeStyleForNode, resolveStylesFromNode, resolveStylesFromNodeData, utf8Bytes };","import { MCP_HASH_HEX_LENGTH } from '@tempad-dev/shared'\n\nconst HASH_FILENAME_PATTERN = new RegExp(\n `^([a-f0-9]{${MCP_HASH_HEX_LENGTH}})(?:\\\\.[a-z0-9]+)?$`,\n 'i'\n)\n\nconst MIME_EXTENSION_OVERRIDES = new Map<string, string>([['image/jpeg', 'jpg']])\nconst SAFE_IMAGE_EXTENSION_PATTERN = /^[a-z0-9-]+$/\n\nexport function normalizeMimeType(mimeType: string | undefined): string {\n if (!mimeType) return 'application/octet-stream'\n const [normalized] = mimeType.split(';', 1)\n return (normalized || 'application/octet-stream').trim().toLowerCase()\n}\n\nexport function getImageExtension(mimeType: string): string {\n const normalized = normalizeMimeType(mimeType)\n if (!normalized.startsWith('image/')) return ''\n const override = MIME_EXTENSION_OVERRIDES.get(normalized)\n if (override) return `.${override}`\n const subtype = normalized.slice('image/'.length)\n if (!subtype) return ''\n const ext = subtype.split('+', 1)[0] || subtype\n if (!SAFE_IMAGE_EXTENSION_PATTERN.test(ext)) return ''\n return `.${ext}`\n}\n\nexport function buildAssetFilename(hash: string, mimeType: string): string {\n const ext = getImageExtension(mimeType)\n return ext ? `${hash}${ext}` : hash\n}\n\nexport function getHashFromAssetFilename(filename: string): string | null {\n const match = HASH_FILENAME_PATTERN.exec(filename)\n return match ? match[1] : null\n}\n","import {\n MCP_AUTO_ACTIVATE_GRACE_MS,\n MCP_ASSET_TTL_MS,\n MCP_MAX_ASSET_BYTES,\n MCP_MAX_PAYLOAD_BYTES,\n MCP_PORT_CANDIDATES,\n MCP_TOOL_TIMEOUT_MS\n} from '@tempad-dev/shared'\n\nfunction parsePositiveInt(envValue: string | undefined, fallback: number): number {\n const parsed = envValue ? Number.parseInt(envValue, 10) : Number.NaN\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback\n}\n\nfunction parseNonNegativeInt(envValue: string | undefined, fallback: number): number {\n const parsed = envValue ? Number.parseInt(envValue, 10) : Number.NaN\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback\n}\n\nfunction resolveToolTimeoutMs(): number {\n return parsePositiveInt(process.env.TEMPAD_MCP_TOOL_TIMEOUT, MCP_TOOL_TIMEOUT_MS)\n}\n\nfunction resolveAutoActivateGraceMs(): number {\n return parsePositiveInt(process.env.TEMPAD_MCP_AUTO_ACTIVATE_GRACE, MCP_AUTO_ACTIVATE_GRACE_MS)\n}\n\nfunction resolveMaxAssetSizeBytes(): number {\n return parsePositiveInt(process.env.TEMPAD_MCP_MAX_ASSET_BYTES, MCP_MAX_ASSET_BYTES)\n}\n\nfunction resolveAssetTtlMs(): number {\n return parseNonNegativeInt(process.env.TEMPAD_MCP_ASSET_TTL_MS, MCP_ASSET_TTL_MS)\n}\n\nexport function getMcpServerConfig() {\n return {\n wsPortCandidates: [...MCP_PORT_CANDIDATES],\n toolTimeoutMs: resolveToolTimeoutMs(),\n maxPayloadBytes: MCP_MAX_PAYLOAD_BYTES,\n autoActivateGraceMs: resolveAutoActivateGraceMs(),\n maxAssetSizeBytes: resolveMaxAssetSizeBytes(),\n assetTtlMs: resolveAssetTtlMs()\n }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport { MCP_HASH_HEX_LENGTH } from '@tempad-dev/shared'\nimport { nanoid } from 'nanoid'\nimport { createHash } from 'node:crypto'\nimport {\n createReadStream,\n createWriteStream,\n existsSync,\n renameSync,\n statSync,\n unlinkSync\n} from 'node:fs'\nimport { createServer } from 'node:http'\nimport { join } from 'node:path'\nimport { pipeline, Transform } from 'node:stream'\nimport { URL } from 'node:url'\n\nimport type { AssetStore } from './asset-store'\nimport type { AssetRecord } from './types'\n\nimport { buildAssetFilename, getHashFromAssetFilename, normalizeMimeType } from './asset-utils'\nimport { getMcpServerConfig } from './config'\nimport { ASSET_DIR, log } from './shared'\n\nconst LOOPBACK_HOST = '127.0.0.1'\nconst HASH_HEX_PATTERN = new RegExp(`^[a-f0-9]{${MCP_HASH_HEX_LENGTH}}$`, 'i')\nconst { maxAssetSizeBytes } = getMcpServerConfig()\n\nexport interface AssetHttpServer {\n start(): Promise<void>\n stop(): void\n getBaseUrl(): string\n}\n\nexport function createAssetHttpServer(store: AssetStore): AssetHttpServer {\n const server = createServer(handleRequest)\n let port: number | null = null\n\n async function start(): Promise<void> {\n if (port !== null) return\n await new Promise<void>((resolve, reject) => {\n const onError = (error: Error) => {\n server.off('listening', onListening)\n reject(error)\n }\n const onListening = () => {\n server.off('error', onError)\n const address = server.address()\n if (address && typeof address === 'object') {\n port = address.port\n resolve()\n } else {\n reject(new Error('Failed to determine HTTP server port.'))\n }\n }\n server.once('error', onError)\n server.once('listening', onListening)\n server.listen(0, LOOPBACK_HOST)\n })\n log.info({ port }, 'Asset HTTP server ready.')\n }\n\n function stop(): void {\n if (port === null) return\n server.close()\n port = null\n }\n\n function getBaseUrl(): string {\n if (port === null) throw new Error('Asset HTTP server is not running.')\n return `http://${LOOPBACK_HOST}:${port}`\n }\n\n function handleRequest(req: IncomingMessage, res: ServerResponse): void {\n const startedAt = Date.now()\n res.on('finish', () => {\n log.info(\n {\n method: req.method,\n url: req.url,\n status: res.statusCode,\n durationMs: Date.now() - startedAt\n },\n 'HTTP asset request completed.'\n )\n })\n\n res.setHeader('Access-Control-Allow-Origin', '*')\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')\n res.setHeader(\n 'Access-Control-Allow-Headers',\n 'Content-Type, X-Asset-Width, X-Asset-Height, X-Asset-Themeable'\n )\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204)\n res.end()\n return\n }\n\n if (!req.url) {\n sendError(res, 400, 'Missing URL')\n return\n }\n\n const url = new URL(req.url, getBaseUrl())\n const segments = url.pathname.split('/').filter(Boolean)\n if (segments.length !== 2 || segments[0] !== 'assets') {\n sendError(res, 404, 'Not Found')\n return\n }\n\n const filename = segments[1]\n const hash = getHashFromAssetFilename(filename)\n if (!hash) {\n sendError(res, 404, 'Not Found')\n return\n }\n\n if (req.method === 'POST') {\n handleUpload(req, res, hash)\n return\n }\n\n if (req.method === 'GET') {\n handleDownload(req, res, hash)\n return\n }\n\n sendError(res, 405, 'Method Not Allowed')\n }\n\n function handleDownload(req: IncomingMessage, res: ServerResponse, hash: string): void {\n const record = store.get(hash)\n if (!record) {\n sendError(res, 404, 'Asset Not Found')\n return\n }\n\n let stat\n try {\n stat = statSync(record.filePath)\n } catch (error) {\n const err = error as NodeJS.ErrnoException\n if (err.code === 'ENOENT') {\n store.remove(hash, { removeFile: false })\n sendError(res, 404, 'Asset Not Found')\n } else {\n log.error({ error, hash }, 'Failed to stat asset file.')\n sendError(res, 500, 'Internal Server Error')\n }\n return\n }\n\n res.writeHead(200, {\n 'Content-Type': record.mimeType,\n 'Content-Length': stat.size.toString(),\n 'Cache-Control': 'public, max-age=31536000, immutable'\n })\n\n const stream = createReadStream(record.filePath)\n stream.on('error', (error) => {\n log.warn({ error, hash }, 'Failed to stream asset file.')\n if (!res.headersSent) {\n sendError(res, 500, 'Internal Server Error')\n } else {\n res.end()\n }\n })\n stream.on('open', () => {\n store.touch(hash)\n })\n stream.pipe(res)\n }\n\n function handleUpload(req: IncomingMessage, res: ServerResponse, hash: string): void {\n if (!HASH_HEX_PATTERN.test(hash)) {\n req.resume()\n sendError(res, 400, 'Invalid Hash')\n return\n }\n\n const contentTypeHeader = req.headers['content-type']\n const mimeType = normalizeMimeType(\n Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader\n )\n const filename = buildAssetFilename(hash, mimeType)\n const filePath = join(ASSET_DIR, filename)\n\n const width = parseInt(req.headers['x-asset-width'] as string, 10)\n const height = parseInt(req.headers['x-asset-height'] as string, 10)\n const themeableHeader = req.headers['x-asset-themeable']\n const themeable =\n (Array.isArray(themeableHeader) ? themeableHeader[0] : themeableHeader) === 'true'\n const metadata: NonNullable<AssetRecord['metadata']> = {}\n if (Number.isFinite(width) && width > 0) metadata.width = width\n if (Number.isFinite(height) && height > 0) metadata.height = height\n if (themeable) metadata.themeable = true\n const assetMetadata = Object.keys(metadata).length ? metadata : undefined\n\n const existing = store.get(hash)\n if (existing) {\n let existingPath = existing.filePath\n if (!existsSync(existingPath) && existsSync(filePath)) {\n existing.filePath = filePath\n existingPath = filePath\n }\n\n if (existsSync(existingPath)) {\n if (existingPath !== filePath) {\n try {\n renameSync(existingPath, filePath)\n existing.filePath = filePath\n } catch (error) {\n log.warn({ error, hash }, 'Failed to rename existing asset to include extension.')\n }\n }\n\n // Drain request to ensure connection is clean\n req.resume()\n\n if (assetMetadata) {\n existing.metadata = {\n ...existing.metadata,\n ...assetMetadata\n }\n }\n if (existing.mimeType !== mimeType) existing.mimeType = mimeType\n existing.lastAccess = Date.now()\n store.upsert(existing)\n sendOk(res, 200, 'Asset Already Exists')\n return\n }\n }\n\n const tmpPath = `${filePath}.tmp.${nanoid()}`\n const writeStream = createWriteStream(tmpPath)\n const hasher = createHash('sha256')\n let size = 0\n\n const cleanup = () => {\n if (existsSync(tmpPath)) {\n try {\n unlinkSync(tmpPath)\n } catch (e) {\n log.warn({ error: e, tmpPath }, 'Failed to cleanup temp file.')\n }\n }\n }\n\n const monitor = new Transform({\n transform(chunk, encoding, callback) {\n size += chunk.length\n if (size > maxAssetSizeBytes) {\n callback(new Error('PayloadTooLarge'))\n return\n }\n hasher.update(chunk)\n callback(null, chunk)\n }\n })\n\n pipeline(req, monitor, writeStream, (err) => {\n if (err) {\n cleanup()\n if (err.message === 'PayloadTooLarge') {\n sendError(res, 413, 'Payload Too Large')\n } else if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') {\n log.warn({ hash }, 'Upload request closed prematurely.')\n sendError(res, 400, 'Upload Incomplete')\n } else {\n log.error({ error: err, hash }, 'Upload pipeline failed.')\n if (!res.headersSent) {\n sendError(res, 500, 'Internal Server Error')\n }\n }\n return\n }\n\n const computedHash = hasher.digest('hex').slice(0, MCP_HASH_HEX_LENGTH)\n if (computedHash !== hash) {\n cleanup()\n sendError(res, 400, 'Hash Mismatch')\n return\n }\n\n try {\n renameSync(tmpPath, filePath)\n } catch (error) {\n log.error({ error, hash }, 'Failed to rename temp file to asset.')\n cleanup()\n sendError(res, 500, 'Internal Server Error')\n return\n }\n\n store.upsert({\n hash,\n filePath,\n mimeType,\n size,\n metadata: assetMetadata\n })\n log.info({ hash, size }, 'Stored uploaded asset via HTTP.')\n sendOk(res, 201, 'Created', { hash, size })\n })\n }\n\n function sendError(\n res: ServerResponse,\n status: number,\n message: string,\n details?: Record<string, unknown>\n ): void {\n if (!res.headersSent) {\n res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' })\n }\n res.end(\n JSON.stringify({\n error: message,\n ...details\n })\n )\n }\n\n function sendOk(\n res: ServerResponse,\n status: number,\n message: string,\n data?: Record<string, unknown>\n ): void {\n if (!res.headersSent) {\n res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' })\n }\n res.end(\n JSON.stringify({\n message,\n ...data\n })\n )\n }\n\n return {\n start,\n stop,\n getBaseUrl\n }\n}\n","import { existsSync, readFileSync, rmSync, writeFileSync, readdirSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport type { AssetRecord } from './types'\n\nimport { getHashFromAssetFilename } from './asset-utils'\nimport { ASSET_DIR, ensureDir, ensureFile, log } from './shared'\n\nconst INDEX_FILENAME = 'assets.json'\nconst DEFAULT_INDEX_PATH = join(ASSET_DIR, INDEX_FILENAME)\n\nexport interface AssetStoreOptions {\n indexPath?: string\n}\n\nexport interface AssetStore {\n list(): AssetRecord[]\n has(hash: string): boolean\n get(hash: string): AssetRecord | undefined\n getMany(hashes: string[]): AssetRecord[]\n upsert(\n input: Omit<AssetRecord, 'uploadedAt' | 'lastAccess'> &\n Partial<Pick<AssetRecord, 'uploadedAt' | 'lastAccess'>>\n ): AssetRecord\n touch(hash: string): AssetRecord | undefined\n remove(hash: string, opts?: { removeFile?: boolean }): void\n reconcile(): void\n flush(): void\n}\n\nfunction readIndex(indexPath: string): AssetRecord[] {\n if (!existsSync(indexPath)) return []\n try {\n const raw = readFileSync(indexPath, 'utf8').trim()\n if (!raw) return []\n const parsed = JSON.parse(raw)\n return Array.isArray(parsed) ? (parsed as AssetRecord[]) : []\n } catch (error) {\n log.warn({ error, indexPath }, 'Failed to read asset catalog; starting fresh.')\n return []\n }\n}\n\nfunction writeIndex(indexPath: string, values: AssetRecord[]): void {\n const payload = JSON.stringify(values, null, 2)\n writeFileSync(indexPath, payload, 'utf8')\n}\n\nexport function createAssetStore(options: AssetStoreOptions = {}): AssetStore {\n ensureDir(ASSET_DIR)\n const indexPath = options.indexPath ?? DEFAULT_INDEX_PATH\n ensureFile(indexPath)\n const records = new Map<string, AssetRecord>()\n let persistTimer: NodeJS.Timeout | null = null\n\n function loadExisting(): void {\n const list = readIndex(indexPath)\n for (const record of list) {\n if (record?.hash && record?.filePath) {\n records.set(record.hash, record)\n }\n }\n }\n\n function persist(): void {\n if (persistTimer) return\n persistTimer = setTimeout(() => {\n persistTimer = null\n writeIndex(indexPath, [...records.values()])\n }, 5000)\n if (typeof persistTimer.unref === 'function') {\n persistTimer.unref()\n }\n }\n\n function flush(): void {\n if (persistTimer) {\n clearTimeout(persistTimer)\n persistTimer = null\n }\n writeIndex(indexPath, [...records.values()])\n }\n\n function list(): AssetRecord[] {\n return [...records.values()]\n }\n\n function has(hash: string): boolean {\n return records.has(hash)\n }\n\n function get(hash: string): AssetRecord | undefined {\n return records.get(hash)\n }\n\n function getMany(hashes: string[]): AssetRecord[] {\n return hashes\n .map((hash) => records.get(hash))\n .filter((record): record is AssetRecord => !!record)\n }\n\n function upsert(\n input: Omit<AssetRecord, 'uploadedAt' | 'lastAccess'> &\n Partial<Pick<AssetRecord, 'uploadedAt' | 'lastAccess'>>\n ): AssetRecord {\n const now = Date.now()\n const record: AssetRecord = {\n ...input,\n uploadedAt: input.uploadedAt ?? now,\n lastAccess: input.lastAccess ?? now\n }\n records.set(record.hash, record)\n persist()\n return record\n }\n\n function touch(hash: string): AssetRecord | undefined {\n const existing = records.get(hash)\n if (!existing) return undefined\n existing.lastAccess = Date.now()\n persist()\n return existing\n }\n\n function remove(hash: string, { removeFile = true } = {}): void {\n const record = records.get(hash)\n if (!record) return\n records.delete(hash)\n persist()\n\n if (removeFile) {\n try {\n rmSync(record.filePath, { force: true })\n } catch (error) {\n log.warn({ hash, error }, 'Failed to remove asset file on delete.')\n }\n }\n }\n\n function reconcile(): void {\n let changed = false\n for (const [hash, record] of records) {\n if (!existsSync(record.filePath)) {\n records.delete(hash)\n changed = true\n }\n }\n\n try {\n const files = readdirSync(ASSET_DIR)\n const now = Date.now()\n for (const file of files) {\n if (file === INDEX_FILENAME) continue\n\n // Cleanup stale tmp files (> 1 hour)\n if (file.includes('.tmp.')) {\n try {\n const filePath = join(ASSET_DIR, file)\n const stat = statSync(filePath)\n if (now - stat.mtimeMs > 3600 * 1000) {\n rmSync(filePath, { force: true })\n log.info({ file }, 'Cleaned up stale temp file.')\n }\n } catch (e) {\n // Ignore errors during cleanup\n log.debug({ error: e, file }, 'Failed to cleanup stale temp file.')\n }\n continue\n }\n\n const hash = getHashFromAssetFilename(file)\n if (!hash) continue\n\n if (!records.has(hash)) {\n const filePath = join(ASSET_DIR, file)\n try {\n const stat = statSync(filePath)\n records.set(hash, {\n hash,\n filePath,\n mimeType: 'application/octet-stream',\n size: stat.size,\n uploadedAt: stat.birthtimeMs,\n lastAccess: stat.atimeMs\n })\n changed = true\n log.info({ hash }, 'Recovered orphan asset file.')\n } catch (e) {\n log.warn({ error: e, file }, 'Failed to stat orphan file.')\n }\n }\n }\n } catch (error) {\n log.warn({ error }, 'Failed to scan asset directory for orphans.')\n }\n\n if (changed) flush()\n }\n\n loadExisting()\n reconcile()\n\n return {\n list,\n has,\n get,\n getMany,\n upsert,\n touch,\n remove,\n reconcile,\n flush\n }\n}\n","export default \"You are connected to a Figma design file via TemPad Dev MCP.\\n\\nTreat tool outputs as design facts. Refactor only to match the user’s repo conventions; do not invent key style values.\\n\\nRules:\\n\\n- Never output any `data-hint-*` attributes from tool outputs (hints only).\\n- If `get_code` warns `depth-cap`, keep the returned parent code as composition evidence and use returned `data-hint-id` values to choose narrower `get_code` follow-ups.\\n- If `get_code` warns `shell`, read the inline code comment for omitted direct child ids, then call `get_code` for those ids in order and fill the results back into the returned shell.\\n- Use `get_structure` only to resolve layout/overlap uncertainty; do not derive numeric values from images.\\n- Tokens: `get_code.tokens` keys are canonical names (`--...`). Multi‑mode values use `${collectionName}:${modeName}`. Nodes may hint per-node overrides via `data-hint-variable-mode=\\\"Collection=Mode;...\\\"`.\\n- Vectors: `vectorMode=smart` is the default. Treat the emitted markup as the source of truth for the current response; vector code is emitted as `<svg data-src=\\\"...\\\">` placeholders, but if asset upload fails after export the tool may inline the SVG as a fallback to preserve source of truth.\\n- Themeable vectors: `themeable=true` means the SVG can safely adopt one contextual color channel. In `smart` mode, that color is typically already evidenced on the emitted `svg` root markup for the placeholder. It does not mean the SVG exposes multiple independent color parameters.\\n- Assets: download bytes via `asset.url`. Asset resources are not exposed via MCP `resources/read`. Use `asset.themeable` only when an SVG still needs repo asset handling after you account for the Host app's vector policy.\\n\"","import type { TempadMcpErrorCode } from '@tempad-dev/shared'\n\nimport { TEMPAD_MCP_ERROR_CODES } from '@tempad-dev/shared'\nimport { nanoid } from 'nanoid'\n\nimport type { PendingToolCall } from './types'\n\nimport { log } from './shared'\n\nconst pendingCalls = new Map<string, PendingToolCall>()\n\nfunction createToolError(\n code: TempadMcpErrorCode,\n message: string\n): Error & { code: TempadMcpErrorCode } {\n const err = new Error(message) as Error & { code: TempadMcpErrorCode }\n err.code = code\n return err\n}\n\nexport function register<T>(\n extensionId: string,\n timeout: number\n): { promise: Promise<T>; requestId: string } {\n const requestId = nanoid()\n const promise = new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n pendingCalls.delete(requestId)\n reject(\n createToolError(\n TEMPAD_MCP_ERROR_CODES.EXTENSION_TIMEOUT,\n `Extension did not respond within ${timeout / 1000}s.`\n )\n )\n }, timeout)\n\n pendingCalls.set(requestId, {\n resolve: resolve as (value: unknown) => void,\n reject,\n timer,\n extensionId\n })\n })\n return { promise, requestId }\n}\n\nexport function resolve(requestId: string, payload: unknown): void {\n const call = pendingCalls.get(requestId)\n if (call) {\n const { timer, resolve: finish } = call\n clearTimeout(timer)\n finish(payload)\n pendingCalls.delete(requestId)\n } else {\n log.warn({ reqId: requestId }, 'Received result for unknown/timed-out call.')\n }\n}\n\nexport function reject(requestId: string, error: Error): void {\n const call = pendingCalls.get(requestId)\n if (call) {\n const { timer, reject: fail } = call\n clearTimeout(timer)\n fail(error)\n pendingCalls.delete(requestId)\n } else {\n log.warn({ reqId: requestId }, 'Received error for unknown/timed-out call.')\n }\n}\n\nexport function cleanupForExtension(extensionId: string): void {\n for (const [reqId, call] of pendingCalls.entries()) {\n const { timer, reject: fail, extensionId: extId } = call\n if (extId === extensionId) {\n clearTimeout(timer)\n fail(\n createToolError(\n TEMPAD_MCP_ERROR_CODES.EXTENSION_DISCONNECTED,\n 'Extension disconnected before providing a result.'\n )\n )\n pendingCalls.delete(reqId)\n log.warn({ reqId, extId: extensionId }, 'Rejected pending call from disconnected extension.')\n }\n }\n}\n\nexport function cleanupAll(): void {\n pendingCalls.forEach((call, reqId) => {\n const { timer, reject: fail } = call\n clearTimeout(timer)\n fail(new Error('Hub is shutting down.'))\n log.debug({ reqId }, 'Rejected pending tool call due to shutdown.')\n })\n pendingCalls.clear()\n}\n","import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport type {\n GetAssetsResult,\n GetScreenshotResult,\n TempadMcpErrorCode,\n ToolName,\n ToolResponseLike,\n ToolResultMap,\n ToolSchema\n} from '@tempad-dev/shared'\nimport type { ZodType } from 'zod'\n\nimport {\n MCP_TOOL_INLINE_BUDGET_BYTES,\n buildGetAssetsToolResult,\n buildGetCodeToolResult,\n buildGetScreenshotToolResult,\n buildGetStructureToolResult,\n buildGetTokenDefsToolResult,\n GetAssetsParametersSchema,\n GetAssetsResultSchema,\n GetCodeParametersSchema,\n GetScreenshotParametersSchema,\n GetStructureParametersSchema,\n GetTokenDefsParametersSchema,\n TEMPAD_MCP_ERROR_CODES,\n measureCallToolResultBytes,\n type TempadMcpErrorPayload\n} from '@tempad-dev/shared'\n\nexport type {\n AssetDescriptor,\n GetAssetsParametersInput,\n GetAssetsResult,\n GetCodeParametersInput,\n GetCodeResult,\n GetScreenshotParametersInput,\n GetScreenshotResult,\n GetStructureParametersInput,\n GetStructureResult,\n GetTokenDefsParametersInput,\n GetTokenDefsResult,\n TokenEntry,\n ToolName,\n ToolResultMap,\n ToolSchema\n} from '@tempad-dev/shared'\n\ntype BaseToolMetadata<Name extends ToolName, Schema extends ZodType> = ToolSchema<Name> & {\n parameters: Schema\n format?: (payload: ToolResultMap[Name]) => CallToolResult\n}\n\ntype ExtensionToolMetadata<Name extends ToolName, Schema extends ZodType> = BaseToolMetadata<\n Name,\n Schema\n> & {\n target: 'extension'\n}\n\ntype HubToolMetadata<Name extends ToolName, Schema extends ZodType> = BaseToolMetadata<\n Name,\n Schema\n> & {\n target: 'hub'\n outputSchema?: ZodType\n}\n\nconst CONNECTIVITY_ERROR_CODES = new Set<TempadMcpErrorCode>([\n TEMPAD_MCP_ERROR_CODES.NO_ACTIVE_EXTENSION,\n TEMPAD_MCP_ERROR_CODES.EXTENSION_TIMEOUT,\n TEMPAD_MCP_ERROR_CODES.EXTENSION_DISCONNECTED,\n TEMPAD_MCP_ERROR_CODES.ASSET_SERVER_NOT_CONFIGURED,\n TEMPAD_MCP_ERROR_CODES.TRANSPORT_NOT_CONNECTED\n])\n\nconst SELECTION_ERROR_CODES = new Set<TempadMcpErrorCode>([\n TEMPAD_MCP_ERROR_CODES.INVALID_SELECTION,\n TEMPAD_MCP_ERROR_CODES.NODE_NOT_VISIBLE\n])\n\nconst CONNECTIVITY_TROUBLESHOOTING_LINES = [\n 'Troubleshooting:',\n '- In Figma, open TemPad Dev panel and enable MCP (Preferences → MCP server).',\n '- If multiple Figma tabs are open, click the MCP badge to activate this tab.',\n '- Keep the Figma tab active/foreground while running MCP tools.'\n]\n\nconst SELECTION_TROUBLESHOOTING_LINE = 'Tip: Select exactly one visible node, or pass nodeId.'\n\nfunction getRecordProperty(record: unknown, key: string): unknown {\n if (!record || typeof record !== 'object') {\n return undefined\n }\n return Reflect.get(record, key)\n}\n\nfunction extTool<Name extends ToolName, Schema extends ZodType>(\n definition: ExtensionToolMetadata<Name, Schema>\n): ExtensionToolMetadata<Name, Schema> {\n return definition\n}\n\nfunction hubTool<Name extends ToolName, Schema extends ZodType>(\n definition: HubToolMetadata<Name, Schema>\n): HubToolMetadata<Name, Schema> {\n return definition\n}\n\nexport const TOOL_DEFS = [\n extTool({\n name: 'get_code',\n description:\n 'High-fidelity code snapshot for nodeId/current single selection (omit nodeId to use selection): JSX/Vue markup + Tailwind-like classes, plus assets/tokens metadata and codegen config. `vectorMode=smart` (default) emits `<svg data-src=\"...\">` placeholders in code and preserves themeable instance color on the emitted SVG root markup for downstream adaptation; if asset upload fails after export, the tool may inline the SVG as a fallback to preserve source of truth. `vectorMode=snapshot` preserves vector assets for fidelity. Host apps should still refactor vector delivery to repo policy where needed (existing icon/component primitives, import-time SVG transforms, inline SVG, or asset-backed SVG usage). SVG asset metadata may include `themeable=true`, meaning the exported asset can safely adopt one contextual color channel. Start here, then refactor into repo conventions while preserving values/intent; strip any data-hint-* attributes (hints only). If warnings include depth-cap, use returned data-hint-id values to continue with narrower get_code calls. If warnings include shell, read the inline comment for omitted direct child ids and fetch them in order. If warnings include auto-layout (inferred), use get_structure to confirm hierarchy/overlap (do not derive numeric values from pixels). Tokens are keyed by canonical names like `--color-primary` (multi-mode keys use `${collection}:${mode}`; node overrides may appear as data-hint-variable-mode).',\n parameters: GetCodeParametersSchema,\n target: 'extension',\n format: createCodeToolResponse\n }),\n extTool({\n name: 'get_token_defs',\n description:\n 'Resolve canonical token names to literal values (optionally including all modes) for tokens referenced by get_code.',\n parameters: GetTokenDefsParametersSchema,\n target: 'extension',\n format: createTokenDefsToolResponse,\n exposed: false\n }),\n extTool({\n name: 'get_screenshot',\n description:\n 'Capture a rendered PNG screenshot for nodeId/current single selection for visual verification (layering/overlap/masks/effects).',\n parameters: GetScreenshotParametersSchema,\n target: 'extension',\n format: createScreenshotToolResponse,\n exposed: false\n }),\n extTool({\n name: 'get_structure',\n description:\n 'Get a compact structural + geometry outline for nodeId/current single selection to understand hierarchy and layout intent.',\n parameters: GetStructureParametersSchema,\n target: 'extension',\n format: createStructureToolResponse\n }),\n hubTool({\n name: 'get_assets',\n description:\n 'Resolve asset hashes to downloadable URLs and metadata for assets referenced by tool responses. SVG asset metadata may include `themeable=true` when the underlying vector can safely adopt one contextual color channel.',\n parameters: GetAssetsParametersSchema,\n target: 'hub',\n outputSchema: GetAssetsResultSchema,\n exposed: false\n })\n] as const\n\nfunction extractToolErrorCode(error: unknown): TempadMcpErrorCode | undefined {\n const code = getRecordProperty(error, 'code')\n if (typeof code === 'string') {\n return code as TempadMcpErrorCode\n }\n const cause = getRecordProperty(error, 'cause')\n const causeCode = getRecordProperty(cause, 'code')\n if (typeof causeCode === 'string') {\n return causeCode as TempadMcpErrorCode\n }\n return undefined\n}\n\nfunction extractToolErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message || 'Unknown error occurred.'\n if (typeof error === 'string') return error\n if (error && typeof error === 'object') {\n const candidate = error as Partial<TempadMcpErrorPayload & Record<string, unknown>>\n if (typeof candidate.message === 'string' && candidate.message.trim()) return candidate.message\n }\n return 'Unknown error occurred.'\n}\n\nfunction createToolErrorResponse(toolName: string, error: unknown): CallToolResult {\n const message = extractToolErrorMessage(error)\n const code = extractToolErrorCode(error)\n const codeLabel = code ? ` [${code}]` : ''\n const troubleshooting = buildTroubleshootingText(code, message)\n\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: `Tool \"${toolName}\" failed${codeLabel}: ${message}${troubleshooting}`\n }\n ]\n }\n}\n\nfunction buildTroubleshootingText(code: TempadMcpErrorCode | undefined, message: string): string {\n const help: string[] = []\n\n if (isConnectivityToolError(code, message)) {\n help.push(...CONNECTIVITY_TROUBLESHOOTING_LINES)\n }\n\n if (isSelectionToolError(code, message)) {\n help.push(SELECTION_TROUBLESHOOTING_LINE)\n }\n\n return help.length ? `\\n\\n${help.join('\\n')}` : ''\n}\n\nfunction isConnectivityToolError(code: TempadMcpErrorCode | undefined, message: string): boolean {\n return (\n (code ? CONNECTIVITY_ERROR_CODES.has(code) : false) ||\n /no active tempad dev extension/i.test(message) ||\n /asset server url is not configured/i.test(message) ||\n /mcp transport is not connected/i.test(message) ||\n /websocket/i.test(message)\n )\n}\n\nfunction isSelectionToolError(code: TempadMcpErrorCode | undefined, message: string): boolean {\n return (\n (code ? SELECTION_ERROR_CODES.has(code) : false) ||\n /select exactly one visible node/i.test(message) ||\n /no visible node found/i.test(message)\n )\n}\n\nexport function createCodeToolResponse(payload: ToolResultMap['get_code']): CallToolResult {\n if (!isCodeResult(payload)) {\n throw new Error('Invalid get_code payload received from extension.')\n }\n\n return toCallToolResult(buildGetCodeToolResult(payload))\n}\n\nexport function createStructureToolResponse(\n payload: ToolResultMap['get_structure']\n): CallToolResult {\n if (!isStructureResult(payload)) {\n throw new Error('Invalid get_structure payload received from extension.')\n }\n\n return toCallToolResult(buildGetStructureToolResult(payload))\n}\n\nexport function createTokenDefsToolResponse(\n payload: ToolResultMap['get_token_defs']\n): CallToolResult {\n if (!isTokenDefsResult(payload)) {\n throw new Error('Invalid get_token_defs payload received from extension.')\n }\n\n return toCallToolResult(buildGetTokenDefsToolResult(payload))\n}\n\nexport function createScreenshotToolResponse(\n payload: ToolResultMap['get_screenshot']\n): CallToolResult {\n if (!isScreenshotResult(payload)) {\n throw new Error('Invalid get_screenshot payload received from extension.')\n }\n\n return toCallToolResult(buildGetScreenshotToolResult(payload))\n}\n\nfunction isScreenshotResult(payload: unknown): payload is GetScreenshotResult {\n if (typeof payload !== 'object' || !payload) return false\n const candidate = payload as Partial<GetScreenshotResult & Record<string, unknown>>\n return (\n typeof candidate.asset === 'object' &&\n candidate.asset !== null &&\n typeof candidate.width === 'number' &&\n typeof candidate.height === 'number' &&\n typeof candidate.scale === 'number' &&\n typeof candidate.bytes === 'number' &&\n typeof candidate.format === 'string'\n )\n}\n\nfunction isCodeResult(payload: unknown): payload is ToolResultMap['get_code'] {\n if (typeof payload !== 'object' || !payload) return false\n const candidate = payload as Partial<ToolResultMap['get_code'] & Record<string, unknown>>\n return (\n typeof candidate.code === 'string' &&\n typeof candidate.lang === 'string' &&\n (candidate.assets === undefined || Array.isArray(candidate.assets))\n )\n}\n\nfunction isStructureResult(payload: unknown): payload is ToolResultMap['get_structure'] {\n if (typeof payload !== 'object' || !payload) return false\n const candidate = payload as Partial<ToolResultMap['get_structure'] & Record<string, unknown>>\n return Array.isArray(candidate.roots)\n}\n\nfunction isTokenDefsResult(payload: unknown): payload is ToolResultMap['get_token_defs'] {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return false\n for (const value of Object.values(payload as Record<string, unknown>)) {\n if (!value || typeof value !== 'object') return false\n const token = value as Partial<Record<'kind' | 'value', unknown>>\n if (typeof token.kind !== 'string') return false\n if (token.value === undefined) return false\n }\n return true\n}\n\nexport function coercePayloadToToolResponse(payload: unknown): CallToolResult {\n if (\n payload &&\n typeof payload === 'object' &&\n Array.isArray((payload as CallToolResult).content)\n ) {\n return payload as CallToolResult\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2)\n }\n ]\n }\n}\n\nexport function createAssetsToolResponse(payload: GetAssetsResult): CallToolResult {\n return toCallToolResult(buildGetAssetsToolResult(payload))\n}\n\nexport function createInlineBudgetExceededToolResponse(\n toolName: ToolName,\n actualBytes: number\n): CallToolResult {\n const guidance = getBudgetRetryGuidance(toolName)\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: `Tool \"${toolName}\" exceeded the 64 KiB inline budget (${actualBytes} UTF-8 bytes > ${MCP_TOOL_INLINE_BUDGET_BYTES}). ${guidance}`\n }\n ]\n }\n}\n\nexport function isWithinInlineBudget(result: ToolResponseLike): boolean {\n return measureCallToolResultBytes(result) <= MCP_TOOL_INLINE_BUDGET_BYTES\n}\n\nfunction toCallToolResult(result: ToolResponseLike): CallToolResult {\n return result as CallToolResult\n}\n\nfunction getBudgetRetryGuidance(toolName: ToolName): string {\n switch (toolName) {\n case 'get_code':\n return 'Reduce selection size or request a smaller nodeId subtree and retry.'\n case 'get_structure':\n return 'Reduce selection size or pass a smaller depth and retry.'\n case 'get_token_defs':\n return 'Reduce requested names or split them into smaller batches and retry.'\n case 'get_screenshot':\n return 'Reduce selection size or scale and retry.'\n case 'get_assets':\n return 'Request fewer hashes in a single call and retry.'\n default:\n return 'Retry with a narrower request.'\n }\n}\n\nexport { createToolErrorResponse }\n","import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport type {\n AssetDescriptor,\n GetAssetsParametersInput,\n GetAssetsResult,\n RegisteredMessage,\n StateMessage,\n ToolCallMessage,\n ToolName,\n ToolResultMap,\n ToolResultMessage\n} from '@tempad-dev/shared'\nimport type { RawData } from 'ws'\nimport type { ZodType } from 'zod'\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport {\n GetAssetsResultSchema,\n MCP_TOOL_INLINE_BUDGET_BYTES,\n MessageFromExtensionSchema,\n TEMPAD_MCP_ERROR_CODES,\n measureCallToolResultBytes,\n type TempadMcpErrorCode\n} from '@tempad-dev/shared'\nimport { nanoid } from 'nanoid'\nimport { existsSync, rmSync, chmodSync } from 'node:fs'\nimport { createServer } from 'node:net'\nimport { WebSocketServer } from 'ws'\n\nimport type { AssetRecord, ExtensionConnection } from './types'\n\nimport { createAssetHttpServer } from './asset-http-server'\nimport { createAssetStore } from './asset-store'\nimport { buildAssetFilename } from './asset-utils'\nimport { getMcpServerConfig } from './config'\nimport MCP_INSTRUCTIONS from './instructions.md?raw'\nimport { register, resolve, reject, cleanupForExtension, cleanupAll } from './request'\nimport { PACKAGE_VERSION, log, RUNTIME_DIR, SOCK_PATH, ensureDir } from './shared'\nimport {\n TOOL_DEFS,\n coercePayloadToToolResponse,\n createAssetsToolResponse,\n createInlineBudgetExceededToolResponse,\n createToolErrorResponse\n} from './tools'\n\nconst SHUTDOWN_TIMEOUT = 2000\nconst { wsPortCandidates, toolTimeoutMs, maxPayloadBytes, autoActivateGraceMs, assetTtlMs } =\n getMcpServerConfig()\n\nlog.info({ version: PACKAGE_VERSION }, 'TemPad MCP Hub starting...')\n\nconst extensions: ExtensionConnection[] = []\nlet consumerCount = 0\ntype TimeoutHandle = ReturnType<typeof setTimeout>\nlet autoActivateTimer: TimeoutHandle | null = null\nlet selectedWsPort = 0\nconst consumerSessions = new Set<McpServer>()\ntype RegisterToolOptions = Parameters<McpServer['registerTool']>[1]\ntype McpInputSchema = RegisterToolOptions['inputSchema']\ntype McpOutputSchema = RegisterToolOptions['outputSchema']\ntype ToolResponse = CallToolResult\ntype SchemaOutput<Schema extends ZodType> = Schema['_output']\ntype ToolMetadataEntry = (typeof TOOL_DEFS)[number]\ntype ExtensionToolMetadata = Extract<ToolMetadataEntry, { target: 'extension' }>\ntype HubToolMetadata = Extract<ToolMetadataEntry, { target: 'hub' }>\n\ntype HubToolWithHandler<T extends HubToolMetadata = HubToolMetadata> = T & {\n handler: (args: SchemaOutput<T['parameters']>) => Promise<ToolResponse>\n}\n\nfunction getRecordProperty(record: unknown, key: string): unknown {\n if (!record || typeof record !== 'object') {\n return undefined\n }\n return Reflect.get(record, key)\n}\n\ntype RegisteredToolDefinition = ExtensionToolMetadata | HubToolWithHandler\n\nfunction enrichToolDefinition(tool: ToolMetadataEntry): RegisteredToolDefinition {\n if (tool.target === 'extension') {\n return tool\n }\n\n switch (tool.name) {\n case 'get_assets':\n return {\n ...tool,\n handler: handleGetAssets\n } satisfies HubToolWithHandler\n default:\n throw new Error('No handler configured for hub tool.')\n }\n}\n\nconst TOOL_DEFINITIONS: ReadonlyArray<RegisteredToolDefinition> = TOOL_DEFS.map((tool) =>\n enrichToolDefinition(tool)\n)\n\ntype RegisteredTool = (typeof TOOL_DEFINITIONS)[number]\ntype ExtensionTool = Extract<RegisteredTool, { target: 'extension' }>\ntype HubOnlyTool = Extract<RegisteredTool, { target: 'hub' }>\n\nfunction createCodedError(code: TempadMcpErrorCode, message: string): Error & { code: string } {\n const err = new Error(message) as Error & { code: string }\n err.code = code\n return err\n}\n\nfunction coerceToolError(error: unknown): Error {\n if (error instanceof Error) return error\n if (typeof error === 'string') return new Error(error)\n const messageValue = getRecordProperty(error, 'message')\n const codeValue = getRecordProperty(error, 'code')\n if (error && typeof error === 'object') {\n const message = typeof messageValue === 'string' ? messageValue : safeStringify(error)\n const err = new Error(message) as Error & { code?: string }\n if (typeof codeValue === 'string') err.code = codeValue\n return err\n }\n return new Error(String(error))\n}\n\nfunction safeStringify(input: unknown): string {\n try {\n return JSON.stringify(input)\n } catch {\n return String(input)\n }\n}\n\nfunction hasFormatter(tool: RegisteredToolDefinition): tool is ExtensionTool & {\n format: (payload: unknown) => ToolResponse\n} {\n return tool.target === 'extension' && 'format' in tool\n}\n\ntype ToolDefinitionByName = {\n [T in RegisteredToolDefinition as T['name']]: T\n}\n\nconst TOOL_BY_NAME: ToolDefinitionByName = Object.fromEntries(\n TOOL_DEFINITIONS.map((tool) => [tool.name, tool] as const)\n) as ToolDefinitionByName\n\nfunction getToolDefinition<Name extends ToolName>(name: Name): ToolDefinitionByName[Name] {\n return TOOL_BY_NAME[name]\n}\n\nconst assetStore = createAssetStore()\nconst assetHttpServer = createAssetHttpServer(assetStore)\nawait assetHttpServer.start()\nscheduleAssetCleanup()\n\nfunction scheduleAssetCleanup(): void {\n if (assetTtlMs <= 0) {\n log.info('Asset TTL cleanup disabled (TEMPAD_MCP_ASSET_TTL_MS=0).')\n return\n }\n pruneExpiredAssets(assetTtlMs)\n const intervalMs = Math.min(assetTtlMs, 24 * 60 * 60 * 1000)\n const timer = setInterval(() => {\n pruneExpiredAssets(assetTtlMs)\n }, intervalMs)\n unrefTimer(timer)\n log.info({ ttlMs: assetTtlMs, intervalMs }, 'Asset TTL cleanup enabled.')\n}\n\nfunction pruneExpiredAssets(ttlMs: number): void {\n const now = Date.now()\n let removed = 0\n let checked = 0\n for (const record of assetStore.list()) {\n checked += 1\n const lastAccess = Number.isFinite(record.lastAccess) ? record.lastAccess : record.uploadedAt\n if (!lastAccess) continue\n if (now - lastAccess > ttlMs) {\n assetStore.remove(record.hash)\n removed += 1\n }\n }\n log.info({ checked, removed, ttlMs }, 'Asset TTL sweep completed.')\n}\n\nfunction buildAssetDescriptor(record: AssetRecord): AssetDescriptor {\n const filename = buildAssetFilename(record.hash, record.mimeType)\n return {\n hash: record.hash,\n url: `${assetHttpServer.getBaseUrl()}/assets/${filename}`,\n mimeType: record.mimeType,\n size: record.size,\n width: record.metadata?.width,\n height: record.metadata?.height,\n ...(record.metadata?.themeable ? { themeable: true } : {})\n }\n}\n\nfunction createMcpServer(): McpServer {\n const mcp = new McpServer(\n { name: 'tempad-dev-mcp', version: PACKAGE_VERSION },\n MCP_INSTRUCTIONS ? { instructions: MCP_INSTRUCTIONS } : undefined\n )\n\n const registered: string[] = []\n for (const tool of TOOL_DEFINITIONS) {\n if ('exposed' in tool && tool.exposed === false) continue\n registerTool(mcp, tool)\n registered.push(tool.name)\n }\n log.info({ tools: registered }, 'Registered tools.')\n\n return mcp\n}\n\nfunction registerTool(mcp: McpServer, tool: RegisteredTool): void {\n if (tool.target === 'extension') {\n registerProxiedTool(mcp, tool)\n } else {\n registerLocalTool(mcp, tool)\n }\n}\n\nfunction registerProxiedTool<T extends ExtensionTool>(mcp: McpServer, tool: T): void {\n type Name = T['name']\n type Result = ToolResultMap[Name]\n\n const registerToolFn = mcp.registerTool.bind(mcp) as (\n name: string,\n options: { description: string; inputSchema: ZodType; outputSchema?: ZodType },\n handler: (args: unknown) => Promise<CallToolResult>\n ) => unknown\n\n const schema = tool.parameters\n const handler = async (args: unknown) => {\n let requestId: string | undefined\n try {\n const parsedArgs = schema.parse(args)\n const activeExt = extensions.find((e) => e.active)\n if (!activeExt) {\n throw createCodedError(\n TEMPAD_MCP_ERROR_CODES.NO_ACTIVE_EXTENSION,\n 'No active TemPad Dev extension available.'\n )\n }\n\n const registration = register<Result>(activeExt.id, toolTimeoutMs)\n requestId = registration.requestId\n\n const message: ToolCallMessage = {\n type: 'toolCall',\n id: registration.requestId,\n payload: {\n name: tool.name,\n args: parsedArgs\n }\n }\n activeExt.ws.send(JSON.stringify(message))\n log.info(\n { tool: tool.name, req: registration.requestId, extId: activeExt.id },\n 'Forwarded tool call.'\n )\n\n const payload = await registration.promise\n return createToolResponse(tool.name, payload)\n } catch (error) {\n const normalized = coerceToolError(error)\n log.error(\n {\n tool: tool.name,\n req: requestId,\n code: getRecordProperty(normalized, 'code'),\n message: normalized.message\n },\n 'Tool invocation failed.'\n )\n return createToolErrorResponse(tool.name, normalized)\n }\n }\n\n registerToolFn(\n tool.name,\n {\n description: tool.description,\n inputSchema: schema as unknown as McpInputSchema\n },\n handler\n )\n}\n\nfunction registerLocalTool(mcp: McpServer, tool: HubOnlyTool): void {\n const schema = tool.parameters\n const handler = tool.handler\n\n const registerToolFn = mcp.registerTool.bind(mcp) as (\n name: string,\n options: { description: string; inputSchema: ZodType; outputSchema?: ZodType },\n handler: (args: unknown) => Promise<CallToolResult>\n ) => unknown\n\n const registrationOptions: {\n description: string\n inputSchema: McpInputSchema\n outputSchema?: McpOutputSchema\n } = {\n description: tool.description,\n inputSchema: schema as unknown as McpInputSchema\n }\n\n if (tool.outputSchema) {\n registrationOptions.outputSchema = tool.outputSchema as unknown as McpOutputSchema\n }\n\n const registerHandler = async (args: unknown) => {\n try {\n const parsed = schema.parse(args)\n return await handler(parsed)\n } catch (error) {\n log.error({ tool: tool.name, error }, 'Local tool invocation failed.')\n return createToolErrorResponse(tool.name, error)\n }\n }\n\n registerToolFn(tool.name, registrationOptions, registerHandler)\n}\n\nfunction createToolResponse<Name extends ToolName>(\n toolName: Name,\n payload: ToolResultMap[Name]\n): ToolResponse {\n const rawResult = (() => {\n const definition = getToolDefinition(toolName)\n if (definition && hasFormatter(definition)) {\n try {\n const formatter = definition.format as (input: ToolResultMap[Name]) => ToolResponse\n return formatter(payload)\n } catch (error) {\n log.warn({ tool: toolName, error }, 'Failed to format tool result; returning raw payload.')\n return coercePayloadToToolResponse(payload)\n }\n }\n\n return coercePayloadToToolResponse(payload)\n })()\n\n const resultBytes = measureCallToolResultBytes(rawResult)\n if (resultBytes > MCP_TOOL_INLINE_BUDGET_BYTES) {\n log.warn(\n { tool: toolName, resultBytes, inlineBudgetBytes: MCP_TOOL_INLINE_BUDGET_BYTES },\n 'Tool result exceeded inline budget; returning compact error response.'\n )\n return createInlineBudgetExceededToolResponse(toolName, resultBytes)\n }\n\n return rawResult\n}\n\nasync function handleGetAssets({ hashes }: GetAssetsParametersInput): Promise<ToolResponse> {\n if (hashes.length > 100) {\n throw new Error('Too many hashes requested. Limit is 100.')\n }\n const unique = Array.from(new Set(hashes))\n const records = assetStore.getMany(unique).filter((record) => {\n if (existsSync(record.filePath)) return true\n assetStore.remove(record.hash, { removeFile: false })\n return false\n })\n const found = new Set(records.map((record) => record.hash))\n const payload: GetAssetsResult = GetAssetsResultSchema.parse({\n assets: records.map((record) => buildAssetDescriptor(record)),\n missing: unique.filter((hash) => !found.has(hash))\n })\n\n return createAssetsToolResponse(payload)\n}\n\nfunction getActiveId(): string | null {\n return extensions.find((e) => e.active)?.id ?? null\n}\n\nfunction setActive(targetId: string | null): void {\n extensions.forEach((e) => {\n e.active = targetId !== null && e.id === targetId\n })\n}\n\nfunction clearAutoActivateTimer(): void {\n if (autoActivateTimer) {\n clearTimeout(autoActivateTimer)\n autoActivateTimer = null\n }\n}\n\nfunction scheduleAutoActivate(): void {\n clearAutoActivateTimer()\n\n if (extensions.length !== 1 || getActiveId()) {\n return\n }\n\n const target = extensions[0]\n autoActivateTimer = setTimeout(() => {\n autoActivateTimer = null\n if (extensions.length === 1 && !getActiveId()) {\n setActive(target.id)\n log.info({ id: target.id }, 'Auto-activated sole extension after grace period.')\n broadcastState()\n }\n }, autoActivateGraceMs)\n}\n\nfunction unrefTimer(timer: TimeoutHandle): void {\n if (typeof timer === 'object' && timer !== null) {\n const handle = timer as NodeJS.Timeout\n if (typeof handle.unref === 'function') {\n handle.unref()\n }\n }\n}\n\nfunction broadcastState(): void {\n const activeId = getActiveId()\n const message: StateMessage = {\n type: 'state',\n activeId,\n count: extensions.length,\n port: selectedWsPort,\n assetServerUrl: assetHttpServer.getBaseUrl()\n }\n extensions.forEach((ext) => ext.ws.send(JSON.stringify(message)))\n log.debug({ activeId, count: extensions.length }, 'Broadcasted state.')\n}\n\nfunction rawDataToBuffer(raw: RawData): Buffer {\n if (typeof raw === 'string') return Buffer.from(raw)\n if (Buffer.isBuffer(raw)) return raw\n if (raw instanceof ArrayBuffer) return Buffer.from(raw)\n return Buffer.concat(raw)\n}\n\nfunction shutdown(): void {\n log.info('Hub is shutting down...')\n consumerSessions.forEach((session) => {\n session.close().catch((err) => {\n log.warn({ err }, 'Failed to close MCP session during shutdown.')\n })\n })\n consumerSessions.clear()\n assetStore.flush()\n assetHttpServer.stop()\n netServer.close(() => log.info('Net server closed.'))\n wss?.close(() => log.info('WebSocket server closed.'))\n cleanupAll()\n const timer = setTimeout(() => {\n log.warn('Shutdown timed out. Forcing exit.')\n process.exit(1)\n }, SHUTDOWN_TIMEOUT)\n unrefTimer(timer)\n}\n\ntry {\n ensureDir(RUNTIME_DIR)\n if (process.platform !== 'win32' && existsSync(SOCK_PATH)) {\n log.warn({ sock: SOCK_PATH }, 'Removing stale socket file.')\n rmSync(SOCK_PATH)\n }\n} catch (error: unknown) {\n log.error({ err: error }, 'Failed to initialize runtime environment.')\n process.exit(1)\n}\n\nconst netServer = createServer((sock) => {\n const mcp = createMcpServer()\n consumerSessions.add(mcp)\n consumerCount++\n log.info(`Consumer connected. Total: ${consumerCount}`)\n const transport = new StdioServerTransport(sock, sock)\n mcp.connect(transport).catch((err) => {\n log.error({ err }, 'Failed to attach MCP transport.')\n consumerSessions.delete(mcp)\n mcp.close().catch((closeErr) => log.warn({ err: closeErr }, 'MCP session close failed.'))\n transport.close().catch((closeErr) => log.warn({ err: closeErr }, 'Transport close failed.'))\n sock.destroy()\n })\n sock.on('error', (err) => {\n log.warn({ err }, 'Consumer socket error.')\n transport.close().catch((closeErr) => log.warn({ err: closeErr }, 'Transport close failed.'))\n })\n sock.on('close', async () => {\n await transport\n .close()\n .catch((closeErr) => log.warn({ err: closeErr }, 'Transport close failed.'))\n await mcp.close().catch((closeErr) => log.warn({ err: closeErr }, 'MCP session close failed.'))\n consumerSessions.delete(mcp)\n consumerCount--\n log.info(`Consumer disconnected. Remaining: ${consumerCount}`)\n if (consumerCount === 0) {\n log.info('Last consumer disconnected. Shutting down.')\n shutdown()\n }\n })\n})\nnetServer.on('error', (err) => {\n log.error({ err }, 'Net server error.')\n process.exit(1)\n})\nnetServer.listen(SOCK_PATH, () => {\n try {\n if (process.platform !== 'win32') chmodSync(SOCK_PATH, 0o600)\n } catch (err) {\n log.error({ err }, 'Failed to set socket permissions. Shutting down.')\n process.exit(1)\n }\n log.info({ sock: SOCK_PATH }, 'Hub socket ready.')\n})\n\nasync function startWebSocketServer(): Promise<{ wss: WebSocketServer; port: number }> {\n for (const candidate of wsPortCandidates) {\n const server = new WebSocketServer({\n host: '127.0.0.1',\n port: candidate,\n maxPayload: maxPayloadBytes\n })\n\n try {\n await new Promise<void>((resolve, reject) => {\n const onError = (err: NodeJS.ErrnoException) => {\n server.off('listening', onListening)\n reject(err)\n }\n const onListening = () => {\n server.off('error', onError)\n resolve()\n }\n server.once('error', onError)\n server.once('listening', onListening)\n })\n return { wss: server, port: candidate }\n } catch (err) {\n server.close()\n const errno = err as NodeJS.ErrnoException\n if (errno.code === 'EADDRINUSE') {\n log.warn({ port: candidate }, 'WebSocket port in use, trying next candidate.')\n continue\n }\n log.error({ err: errno, port: candidate }, 'Failed to start WebSocket server.')\n process.exit(1)\n }\n }\n\n log.error(\n { candidates: wsPortCandidates },\n 'Unable to start WebSocket server on any candidate port.'\n )\n process.exit(1)\n}\n\nconst { wss, port } = await startWebSocketServer()\nselectedWsPort = port\n\n// Add an error handler to prevent crashes from port conflicts, etc.\nwss.on('error', (err) => {\n log.error({ err }, 'WebSocket server critical error. Exiting.')\n process.exit(1)\n})\n\nwss.on('connection', (ws) => {\n const ext: ExtensionConnection = { id: nanoid(), ws, active: false }\n extensions.push(ext)\n log.info({ id: ext.id }, `Extension connected. Total: ${extensions.length}`)\n\n const message: RegisteredMessage = { type: 'registered', id: ext.id }\n ws.send(JSON.stringify(message))\n broadcastState()\n scheduleAutoActivate()\n\n ws.on('message', (raw: RawData, isBinary: boolean) => {\n if (isBinary) {\n log.warn({ extId: ext.id }, 'Unexpected binary message received.')\n return\n }\n\n const messageBuffer = rawDataToBuffer(raw)\n\n let parsedJson: unknown\n try {\n parsedJson = JSON.parse(messageBuffer.toString('utf-8'))\n } catch (e: unknown) {\n log.warn({ err: e, extId: ext.id }, 'Failed to parse message.')\n return\n }\n\n const parseResult = MessageFromExtensionSchema.safeParse(parsedJson)\n if (!parseResult.success) {\n log.warn({ error: parseResult.error.flatten(), extId: ext.id }, 'Invalid message shape.')\n return\n }\n const msg = parseResult.data\n\n switch (msg.type) {\n case 'activate': {\n setActive(ext.id)\n log.info({ id: ext.id }, 'Extension activated.')\n broadcastState()\n scheduleAutoActivate()\n break\n }\n case 'toolResult': {\n const { id, payload, error } = msg as ToolResultMessage\n if (error) {\n const normalized = coerceToolError(error)\n log.warn(\n {\n toolReq: id,\n extId: ext.id,\n code: getRecordProperty(normalized, 'code'),\n message: normalized.message\n },\n 'Received tool error from extension.'\n )\n reject(id, normalized)\n } else {\n resolve(id, payload)\n }\n break\n }\n }\n })\n\n ws.on('close', () => {\n const index = extensions.findIndex((e) => e.id === ext.id)\n if (index > -1) extensions.splice(index, 1)\n\n log.info({ id: ext.id }, `Extension disconnected. Remaining: ${extensions.length}`)\n cleanupForExtension(ext.id)\n\n if (ext.active) {\n log.warn({ id: ext.id }, 'Active extension disconnected.')\n setActive(null)\n }\n\n broadcastState()\n scheduleAutoActivate()\n })\n})\n\nlog.info({ port: selectedWsPort }, 'WebSocket server ready.')\n\nprocess.on('SIGINT', shutdown)\nprocess.on('SIGTERM', shutdown)\n"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import pino from "pino";
|
|
|
6
6
|
//#region package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@tempad-dev/mcp",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.5.1",
|
|
10
10
|
description: "MCP server for TemPad Dev.",
|
|
11
11
|
repository: {
|
|
12
12
|
"type": "git",
|
|
@@ -25,9 +25,9 @@ var package_default = {
|
|
|
25
25
|
"test:run": "vitest run --config vitest.config.ts",
|
|
26
26
|
"test:coverage": "vitest run --config vitest.config.ts --coverage",
|
|
27
27
|
"lint": "eslint . --ext .ts,.mts,.cts,.js,.mjs,.cjs",
|
|
28
|
-
"lint:fix": "eslint . --ext .ts,.mts,.cts,.js,.mjs,.cjs
|
|
29
|
-
"format": "oxfmt --ignore-path ../../.gitignore",
|
|
30
|
-
"format:check": "oxfmt --ignore-path ../../.gitignore --check",
|
|
28
|
+
"lint:fix": "eslint . --fix --ext .ts,.mts,.cts,.js,.mjs,.cjs",
|
|
29
|
+
"format": "oxfmt -c ../../.oxfmtrc.json --ignore-path ../../.gitignore",
|
|
30
|
+
"format:check": "oxfmt -c ../../.oxfmtrc.json --ignore-path ../../.gitignore --check",
|
|
31
31
|
"prepublishOnly": "pnpm run build"
|
|
32
32
|
},
|
|
33
33
|
dependencies: {
|
|
@@ -114,4 +114,4 @@ const SOCK_PATH = resolveSockPath();
|
|
|
114
114
|
|
|
115
115
|
//#endregion
|
|
116
116
|
export { SOCK_PATH as a, log as c, RUNTIME_DIR as i, LOCK_PATH as n, ensureDir as o, PACKAGE_VERSION as r, ensureFile as s, ASSET_DIR as t };
|
|
117
|
-
//# sourceMappingURL=shared-
|
|
117
|
+
//# sourceMappingURL=shared-BljdV6bG.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared-
|
|
1
|
+
{"version":3,"file":"shared-BljdV6bG.mjs","names":["packageJson"],"sources":["../package.json","../src/shared.ts"],"sourcesContent":["","import { closeSync, mkdirSync, openSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport pino from 'pino'\n\nimport packageJson from '../package.json' assert { type: 'json' }\n\nexport function normalizePackageVersion(version: unknown): string {\n return typeof version === 'string' ? version : '0.0.0'\n}\n\nexport function ensureDir(dirPath: string): void {\n mkdirSync(dirPath, { recursive: true, mode: 0o700 })\n}\n\nfunction getRecordProperty(record: unknown, key: string): unknown {\n if (!record || typeof record !== 'object') {\n return undefined\n }\n return Reflect.get(record, key)\n}\n\nexport const PACKAGE_VERSION = normalizePackageVersion(getRecordProperty(packageJson, 'version'))\n\nexport function resolveRuntimeDir(\n env: NodeJS.ProcessEnv = process.env,\n systemTmpDir: string = tmpdir()\n): string {\n if (env.TEMPAD_MCP_RUNTIME_DIR) return env.TEMPAD_MCP_RUNTIME_DIR\n return join(systemTmpDir, 'tempad-dev', 'run')\n}\n\nexport function resolveLogDir(\n env: NodeJS.ProcessEnv = process.env,\n systemTmpDir: string = tmpdir()\n): string {\n if (env.TEMPAD_MCP_LOG_DIR) return env.TEMPAD_MCP_LOG_DIR\n return join(systemTmpDir, 'tempad-dev', 'log')\n}\n\nexport function resolveAssetDir(\n env: NodeJS.ProcessEnv = process.env,\n systemTmpDir: string = tmpdir()\n): string {\n if (env.TEMPAD_MCP_ASSET_DIR) return env.TEMPAD_MCP_ASSET_DIR\n return join(systemTmpDir, 'tempad-dev', 'assets')\n}\n\nexport function resolveLogLevel(\n debugValue: string | undefined = process.env.DEBUG\n): 'debug' | 'info' {\n return debugValue ? 'debug' : 'info'\n}\n\nexport function resolveSockPath(\n platform: NodeJS.Platform = process.platform,\n runtimeDir: string = RUNTIME_DIR\n): string {\n return platform === 'win32' ? '\\\\\\\\.\\\\pipe\\\\tempad-mcp' : join(runtimeDir, 'mcp.sock')\n}\n\nexport const RUNTIME_DIR = resolveRuntimeDir()\nexport const LOG_DIR = resolveLogDir()\nexport const ASSET_DIR = resolveAssetDir()\n\nensureDir(RUNTIME_DIR)\nensureDir(LOG_DIR)\nensureDir(ASSET_DIR)\n\nexport function ensureFile(filePath: string): void {\n const fd = openSync(filePath, 'a')\n closeSync(fd)\n}\n\nexport const LOCK_PATH = join(RUNTIME_DIR, 'mcp.lock')\nensureFile(LOCK_PATH)\n\nconst timestamp = new Date().toISOString().replaceAll(':', '-').replaceAll('.', '-')\nconst pid = process.pid\nconst LOG_FILE = join(LOG_DIR, `mcp-${timestamp}-${pid}.log`)\n\nconst prettyTransport = pino.transport({\n target: 'pino-pretty',\n options: {\n translateTime: 'SYS:HH:MM:ss',\n destination: LOG_FILE\n }\n})\n\nexport const log = pino(\n {\n level: resolveLogLevel(),\n msgPrefix: '[tempad-dev/mcp] '\n },\n prettyTransport\n)\n\nexport const SOCK_PATH = resolveSockPath()\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACOA,SAAgB,wBAAwB,SAA0B;AAChE,QAAO,OAAO,YAAY,WAAW,UAAU;;AAGjD,SAAgB,UAAU,SAAuB;AAC/C,WAAU,SAAS;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;;AAGtD,SAAS,kBAAkB,QAAiB,KAAsB;AAChE,KAAI,CAAC,UAAU,OAAO,WAAW,SAC/B;AAEF,QAAO,QAAQ,IAAI,QAAQ,IAAI;;AAGjC,MAAa,kBAAkB,wBAAwB,kBAAkBA,iBAAa,UAAU,CAAC;AAEjG,SAAgB,kBACd,MAAyB,QAAQ,KACjC,eAAuB,QAAQ,EACvB;AACR,KAAI,IAAI,uBAAwB,QAAO,IAAI;AAC3C,QAAO,KAAK,cAAc,cAAc,MAAM;;AAGhD,SAAgB,cACd,MAAyB,QAAQ,KACjC,eAAuB,QAAQ,EACvB;AACR,KAAI,IAAI,mBAAoB,QAAO,IAAI;AACvC,QAAO,KAAK,cAAc,cAAc,MAAM;;AAGhD,SAAgB,gBACd,MAAyB,QAAQ,KACjC,eAAuB,QAAQ,EACvB;AACR,KAAI,IAAI,qBAAsB,QAAO,IAAI;AACzC,QAAO,KAAK,cAAc,cAAc,SAAS;;AAGnD,SAAgB,gBACd,aAAiC,QAAQ,IAAI,OAC3B;AAClB,QAAO,aAAa,UAAU;;AAGhC,SAAgB,gBACd,WAA4B,QAAQ,UACpC,aAAqB,aACb;AACR,QAAO,aAAa,UAAU,4BAA4B,KAAK,YAAY,WAAW;;AAGxF,MAAa,cAAc,mBAAmB;AAC9C,MAAa,UAAU,eAAe;AACtC,MAAa,YAAY,iBAAiB;AAE1C,UAAU,YAAY;AACtB,UAAU,QAAQ;AAClB,UAAU,UAAU;AAEpB,SAAgB,WAAW,UAAwB;AAEjD,WADW,SAAS,UAAU,IAAI,CACrB;;AAGf,MAAa,YAAY,KAAK,aAAa,WAAW;AACtD,WAAW,UAAU;AAErB,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa,CAAC,WAAW,KAAK,IAAI,CAAC,WAAW,KAAK,IAAI;AACpF,MAAM,MAAM,QAAQ;AACpB,MAAM,WAAW,KAAK,SAAS,OAAO,UAAU,GAAG,IAAI,MAAM;AAE7D,MAAM,kBAAkB,KAAK,UAAU;CACrC,QAAQ;CACR,SAAS;EACP,eAAe;EACf,aAAa;EACd;CACF,CAAC;AAEF,MAAa,MAAM,KACjB;CACE,OAAO,iBAAiB;CACxB,WAAW;CACZ,EACD,gBACD;AAED,MAAa,YAAY,iBAAiB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tempad-dev/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "MCP server for TemPad Dev.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"test:run": "vitest run --config vitest.config.ts",
|
|
25
25
|
"test:coverage": "vitest run --config vitest.config.ts --coverage",
|
|
26
26
|
"lint": "eslint . --ext .ts,.mts,.cts,.js,.mjs,.cjs",
|
|
27
|
-
"lint:fix": "eslint . --ext .ts,.mts,.cts,.js,.mjs,.cjs
|
|
28
|
-
"format": "oxfmt --ignore-path ../../.gitignore",
|
|
29
|
-
"format:check": "oxfmt --ignore-path ../../.gitignore --check",
|
|
27
|
+
"lint:fix": "eslint . --fix --ext .ts,.mts,.cts,.js,.mjs,.cjs",
|
|
28
|
+
"format": "oxfmt -c ../../.oxfmtrc.json --ignore-path ../../.gitignore",
|
|
29
|
+
"format:check": "oxfmt -c ../../.oxfmtrc.json --ignore-path ../../.gitignore --check",
|
|
30
30
|
"prepublishOnly": "pnpm run build"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|