@tempad-dev/mcp 0.4.0 → 0.4.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 +2 -0
- package/README.zh-Hans.md +48 -0
- package/dist/cli.mjs +1 -1
- package/dist/hub.mjs +1 -1
- package/dist/hub.mjs.map +1 -1
- package/dist/{shared-C8rMJ76Z.mjs → shared-CnY7ym27.mjs} +3 -3
- package/dist/{shared-C8rMJ76Z.mjs.map → shared-CnY7ym27.mjs.map} +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @tempad-dev/mcp
|
|
2
|
+
|
|
3
|
+
## 用法
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"mcpServers": {
|
|
8
|
+
"TemPad Dev": {
|
|
9
|
+
"command": "npx",
|
|
10
|
+
"args": ["-y", "@tempad-dev/mcp@latest"]
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
快速配置方式:
|
|
17
|
+
|
|
18
|
+
- VS Code / Cursor / TRAE:使用 TemPad Dev(Preferences → MCP server)中的深链接。
|
|
19
|
+
- Windsurf:从同一面板复制 JSON 片段。
|
|
20
|
+
- CLI:`claude mcp add --transport stdio "TemPad Dev" -- npx -y @tempad-dev/mcp@latest` 或 `codex mcp add "TemPad Dev" -- npx -y @tempad-dev/mcp@latest`。
|
|
21
|
+
|
|
22
|
+
支持的工具和资源:
|
|
23
|
+
|
|
24
|
+
- `get_code`:以 Tailwind 优先的 JSX/Vue 标记输出,并附带资源和变量引用。
|
|
25
|
+
- `get_structure`:当前选中节点的层级/几何结构信息。
|
|
26
|
+
- `get_screenshot`:PNG 截图,包含可下载的资源链接。
|
|
27
|
+
- `tempad-assets` 资源模板(`asset://tempad/{hash}`),用于读取工具返回中引用的二进制资源。
|
|
28
|
+
|
|
29
|
+
说明:
|
|
30
|
+
|
|
31
|
+
- 资源是临时且与工具调用关联的;`resources/list` 故意保持为空,以避免跨会话/跨设计文件污染。请使用工具结果中的 `resource_link`,再通过 `resources/read`(或 HTTP 回退 URL)获取二进制内容。
|
|
32
|
+
- HTTP 回退 URL 使用 `/assets/{hash}`,也可能带图片扩展名(例如 `/assets/{hash}.png`),两种形式都支持。
|
|
33
|
+
|
|
34
|
+
## 配置
|
|
35
|
+
|
|
36
|
+
可选环境变量:
|
|
37
|
+
|
|
38
|
+
- `TEMPAD_MCP_TOOL_TIMEOUT`:工具调用超时时间(毫秒,默认 `15000`)。
|
|
39
|
+
- `TEMPAD_MCP_AUTO_ACTIVATE_GRACE`:仅一个扩展连接时自动激活前的延迟(默认 `1500`)。
|
|
40
|
+
- `TEMPAD_MCP_MAX_ASSET_BYTES`:截图/资源捕获的最大上传体积(字节,默认 `8388608`)。
|
|
41
|
+
- `TEMPAD_MCP_ASSET_TTL_MS`:资源基于最近访问时间的清理 TTL(毫秒);设置为 `0` 表示禁用(默认 `2592000000`)。
|
|
42
|
+
- `TEMPAD_MCP_RUNTIME_DIR`:运行时目录覆盖(默认在系统临时目录下的 `tempad-dev/run`)。
|
|
43
|
+
- `TEMPAD_MCP_LOG_DIR`:日志目录覆盖(默认在系统临时目录下的 `tempad-dev/log`)。
|
|
44
|
+
- `TEMPAD_MCP_ASSET_DIR`:资源存储目录覆盖(默认在系统临时目录下的 `tempad-dev/assets`)。
|
|
45
|
+
|
|
46
|
+
## 要求
|
|
47
|
+
|
|
48
|
+
- Node.js 18+
|
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-CnY7ym27.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-CnY7ym27.mjs";
|
|
2
2
|
import { createServer } from "node:net";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { URL } from "node:url";
|
package/dist/hub.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hub.mjs","names":["createServer","getRecordProperty","formatBytes","createAssetResourceLinkBlock","formatAssetResourceName","describeAsset","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_ASSET_RESOURCE_NAME = \"tempad-assets\";\nconst MCP_ASSET_URI_PREFIX = \"asset://tempad/\";\nconst MCP_ASSET_URI_TEMPLATE = `${MCP_ASSET_URI_PREFIX}{hash}`;\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 MCP_ASSET_RESOURCE_URI_PATTERN = new RegExp(`^${MCP_ASSET_URI_PREFIX.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}[a-f0-9]{${MCP_HASH_HEX_LENGTH}}$`, \"i\");\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\tresourceUri: z.string().regex(MCP_ASSET_RESOURCE_URI_PATTERN),\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) {\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);\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) {\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 angle = normalizeGradientAngle(end.x - start.x, end.y - start.y);\n\t\t\tif (angle != null) return angle;\n\t\t}\n\t}\n\tconst transform = paint.gradientTransform;\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 dx = row0[0];\n\tconst dy = row1[0];\n\treturn normalizeGradientAngle(dx, dy);\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 += 180;\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) {\n\tif (!paints) return null;\n\tconst gradient = resolveGradientFromPaints(paints);\n\tif (gradient) return { gradient };\n\tconst solidColor = resolveSolidFromPaints(paints);\n\treturn solidColor ? { solidColor } : null;\n}\nfunction resolvePaintStyleFromStyleId(styleId, kind) {\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);\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) {\n\treturn resolvePaintStyleFromStyleId(styleId, kind) ?? resolvePaintStyleFromPaints(paints);\n}\nfunction patchBorderVarColor(borderValue, color) {\n\tconst borderParts = borderValue.split(/\\s+/);\n\tconst varIndex = borderParts.findIndex((part) => part.includes(\"var(--\"));\n\tif (varIndex < 0) return null;\n\tborderParts[varIndex] = 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\");\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\");\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(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\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 borderParts = processed.border.split(/\\s+/);\n\t\t\tconst varIndex = borderParts.findIndex((part) => part.includes(\"var(--\"));\n\t\t\tborderParts[varIndex] = resolved.solidColor;\n\t\t\tprocessed.border = borderParts.join(\" \");\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_RESOURCE_NAME, MCP_ASSET_TTL_MS, MCP_ASSET_URI_PREFIX, MCP_ASSET_URI_TEMPLATE, 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']])\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 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- Use `get_structure` / `get_screenshot` only to resolve layout/overlap/masks/effects uncertainty. Screenshots are for visual verification only; do not derive numeric values from pixels.\\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: fetch bytes via `resources/read` using `resourceUri` when possible; fall back to `asset.url` for large blobs. 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 AssetDescriptor,\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 auto-layout (inferred), use get_structure/get_screenshot 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 }),\n extTool({\n name: 'get_structure',\n description:\n 'Get a 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/URIs 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\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 content: [\n {\n type: 'text' as const,\n text: `Tool \"${toolName}\" failed: ${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}. Fetch bytes via resources/read using resourceUri.`\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 const assetLinks = payload.assets?.length\n ? payload.assets.map((asset) => createAssetResourceLinkBlock(asset))\n : []\n\n return {\n content: [\n {\n type: 'text' as const,\n text: summary.join('\\n')\n },\n ...assetLinks\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)\n }\n\n return {\n content: [\n descriptionBlock,\n {\n type: 'text' as const,\n text: ``\n },\n createResourceLinkBlock(payload.asset, payload)\n ],\n structuredContent: payload\n }\n}\n\nfunction createResourceLinkBlock(asset: AssetDescriptor, result: GetScreenshotResult) {\n return {\n type: 'resource_link' as const,\n name: 'Screenshot',\n uri: asset.resourceUri,\n mimeType: asset.mimeType,\n description: `Screenshot ${result.width}x${result.height} @${result.scale}x - Download: ${asset.url}`\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\nfunction createAssetResourceLinkBlock(asset: AssetDescriptor) {\n return {\n type: 'resource_link' as const,\n name: formatAssetResourceName(asset.hash),\n uri: asset.resourceUri,\n mimeType: asset.mimeType,\n description: `${describeAsset(asset)} - Download: ${asset.url}`\n }\n}\n\nfunction describeAsset(asset: AssetDescriptor): string {\n return `${asset.mimeType} (${formatBytes(asset.size)})`\n}\n\nfunction formatAssetResourceName(hash: string): string {\n return `asset:${hash.slice(0, 8)}`\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, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport {\n GetAssetsResultSchema,\n MCP_ASSET_RESOURCE_NAME,\n MCP_ASSET_URI_PREFIX,\n MCP_ASSET_URI_TEMPLATE,\n MessageFromExtensionSchema,\n TEMPAD_MCP_ERROR_CODES,\n type TempadMcpErrorCode\n} from '@tempad-dev/shared'\nimport { nanoid } from 'nanoid'\nimport { existsSync, rmSync, chmodSync, readFileSync, statSync } 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\n\nconst mcp = new McpServer(\n { name: 'tempad-dev-mcp', version: PACKAGE_VERSION },\n MCP_INSTRUCTIONS ? { instructions: MCP_INSTRUCTIONS } : undefined\n)\ntype McpInputSchema = Parameters<typeof mcp.registerTool>[1]['inputSchema']\ntype McpOutputSchema = Parameters<typeof mcp.registerTool>[1]['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()\nregisterAssetResources()\nscheduleAssetCleanup()\n\nfunction registerAssetResources(): void {\n const template = new ResourceTemplate(MCP_ASSET_URI_TEMPLATE, {\n list: async () => ({\n // Intentionally keep resources/list empty: assets are ephemeral, tool-linked blobs.\n // Resource discovery would leak across sessions/design files and add UI noise.\n // Hosts should use resource_link from tool responses to access assets on demand.\n resources: []\n })\n })\n\n mcp.registerResource(\n MCP_ASSET_RESOURCE_NAME,\n template,\n {\n description:\n 'Exported PNG/SVG assets which can serve as screenshots or be referenced in code output.'\n },\n async (_uri, variables) => {\n const hash = typeof variables.hash === 'string' ? variables.hash : ''\n return readAssetResource(hash)\n }\n )\n}\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(\n { ttlMs: assetTtlMs, intervalMs },\n 'Asset TTL cleanup enabled (list remains empty; assets are tool-linked).'\n )\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\nasync function readAssetResource(hash: string) {\n if (!hash) {\n throw new Error('Missing asset hash in resource URI.')\n }\n const record = assetStore.get(hash)\n if (!record) {\n throw new Error(`Asset ${hash} not found.`)\n }\n\n if (!existsSync(record.filePath)) {\n assetStore.remove(hash, { removeFile: false })\n throw new Error(`Asset ${hash} file is missing.`)\n }\n\n const stat = statSync(record.filePath)\n // Base64 encoding increases size by ~33% (4 bytes for every 3 bytes)\n const estimatedSize = Math.ceil(stat.size / 3) * 4\n if (estimatedSize > maxPayloadBytes) {\n throw new Error(\n `Asset ${hash} is too large (${formatBytes(stat.size)}, encoded: ${formatBytes(estimatedSize)}) to read via MCP protocol. Use HTTP download.`\n )\n }\n\n assetStore.touch(hash)\n const buffer = readFileSync(record.filePath)\n const resourceUri = buildAssetResourceUri(hash)\n\n if (isTextualMime(record.mimeType)) {\n return {\n contents: [\n {\n uri: resourceUri,\n mimeType: record.mimeType,\n text: buffer.toString('utf8')\n }\n ]\n }\n }\n\n return {\n contents: [\n {\n uri: resourceUri,\n mimeType: record.mimeType,\n blob: buffer.toString('base64')\n }\n ]\n }\n}\n\nfunction isTextualMime(mimeType: string): boolean {\n return mimeType === 'image/svg+xml' || mimeType.startsWith('text/')\n}\n\nfunction buildAssetResourceUri(hash: string): string {\n return `${MCP_ASSET_URI_PREFIX}${hash}`\n}\n\nfunction formatAssetResourceName(hash: string): string {\n return `asset:${hash.slice(0, 8)}`\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 resourceUri: buildAssetResourceUri(record.hash),\n width: record.metadata?.width,\n height: record.metadata?.height\n }\n}\n\nfunction createAssetResourceLinkBlock(asset: AssetDescriptor) {\n return {\n type: 'resource_link' as const,\n name: formatAssetResourceName(asset.hash),\n uri: asset.resourceUri,\n mimeType: asset.mimeType,\n description: `${describeAsset(asset)} - Download: ${asset.url}`\n }\n}\n\nfunction describeAsset(asset: AssetDescriptor): string {\n return `${asset.mimeType} (${formatBytes(asset.size)})`\n}\n\nfunction registerTools(): void {\n const registered: string[] = []\n for (const tool of TOOL_DEFINITIONS) {\n if ('exposed' in tool && tool.exposed === false) continue\n registerTool(tool)\n registered.push(tool.name)\n }\n log.info({ tools: registered }, 'Registered tools.')\n}\n\nregisterTools()\nfunction registerTool(tool: RegisteredTool): void {\n if (tool.target === 'extension') {\n registerProxiedTool(tool)\n } else {\n registerLocalTool(tool)\n }\n}\n\nfunction registerProxiedTool<T extends ExtensionTool>(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 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 { promise, requestId } = register<Result>(activeExt.id, toolTimeoutMs)\n\n const message: ToolCallMessage = {\n type: 'toolCall',\n id: requestId,\n payload: {\n name: tool.name,\n args: parsedArgs\n }\n }\n activeExt.ws.send(JSON.stringify(message))\n log.info({ tool: tool.name, req: requestId, extId: activeExt.id }, 'Forwarded tool call.')\n\n const payload = await promise\n return createToolResponse(tool.name, payload)\n } catch (error) {\n log.error({ tool: tool.name, error }, 'Tool invocation failed before reaching extension.')\n return createToolErrorResponse(tool.name, error)\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(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(\n 'Use resources/read with each resourceUri or fetch the fallback URL to download bytes.'\n )\n\n const content = [\n {\n type: 'text' as const,\n text: summary.join('\\n')\n },\n ...payload.assets.map((asset) => createAssetResourceLinkBlock(asset))\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 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\nfunction shutdown(): void {\n log.info('Hub is shutting down...')\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 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 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.close()\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 reject(id, coerceToolError(error))\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","formatBytes","createAssetResourceLinkBlock","formatAssetResourceName","describeAsset","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_ASSET_RESOURCE_NAME = \"tempad-assets\";\nconst MCP_ASSET_URI_PREFIX = \"asset://tempad/\";\nconst MCP_ASSET_URI_TEMPLATE = `${MCP_ASSET_URI_PREFIX}{hash}`;\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 MCP_ASSET_RESOURCE_URI_PATTERN = new RegExp(`^${MCP_ASSET_URI_PREFIX.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}[a-f0-9]{${MCP_HASH_HEX_LENGTH}}$`, \"i\");\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\tresourceUri: z.string().regex(MCP_ASSET_RESOURCE_URI_PATTERN),\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_RESOURCE_NAME, MCP_ASSET_TTL_MS, MCP_ASSET_URI_PREFIX, MCP_ASSET_URI_TEMPLATE, 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']])\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 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- Use `get_structure` / `get_screenshot` only to resolve layout/overlap/masks/effects uncertainty. Screenshots are for visual verification only; do not derive numeric values from pixels.\\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: fetch bytes via `resources/read` using `resourceUri` when possible; fall back to `asset.url` for large blobs. 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 AssetDescriptor,\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 auto-layout (inferred), use get_structure/get_screenshot 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 }),\n extTool({\n name: 'get_structure',\n description:\n 'Get a 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/URIs 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\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 content: [\n {\n type: 'text' as const,\n text: `Tool \"${toolName}\" failed: ${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}. Fetch bytes via resources/read using resourceUri.`\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 const assetLinks = payload.assets?.length\n ? payload.assets.map((asset) => createAssetResourceLinkBlock(asset))\n : []\n\n return {\n content: [\n {\n type: 'text' as const,\n text: summary.join('\\n')\n },\n ...assetLinks\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)\n }\n\n return {\n content: [\n descriptionBlock,\n {\n type: 'text' as const,\n text: ``\n },\n createResourceLinkBlock(payload.asset, payload)\n ],\n structuredContent: payload\n }\n}\n\nfunction createResourceLinkBlock(asset: AssetDescriptor, result: GetScreenshotResult) {\n return {\n type: 'resource_link' as const,\n name: 'Screenshot',\n uri: asset.resourceUri,\n mimeType: asset.mimeType,\n description: `Screenshot ${result.width}x${result.height} @${result.scale}x - Download: ${asset.url}`\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\nfunction createAssetResourceLinkBlock(asset: AssetDescriptor) {\n return {\n type: 'resource_link' as const,\n name: formatAssetResourceName(asset.hash),\n uri: asset.resourceUri,\n mimeType: asset.mimeType,\n description: `${describeAsset(asset)} - Download: ${asset.url}`\n }\n}\n\nfunction describeAsset(asset: AssetDescriptor): string {\n return `${asset.mimeType} (${formatBytes(asset.size)})`\n}\n\nfunction formatAssetResourceName(hash: string): string {\n return `asset:${hash.slice(0, 8)}`\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, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport {\n GetAssetsResultSchema,\n MCP_ASSET_RESOURCE_NAME,\n MCP_ASSET_URI_PREFIX,\n MCP_ASSET_URI_TEMPLATE,\n MessageFromExtensionSchema,\n TEMPAD_MCP_ERROR_CODES,\n type TempadMcpErrorCode\n} from '@tempad-dev/shared'\nimport { nanoid } from 'nanoid'\nimport { existsSync, rmSync, chmodSync, readFileSync, statSync } 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\n\nconst mcp = new McpServer(\n { name: 'tempad-dev-mcp', version: PACKAGE_VERSION },\n MCP_INSTRUCTIONS ? { instructions: MCP_INSTRUCTIONS } : undefined\n)\ntype McpInputSchema = Parameters<typeof mcp.registerTool>[1]['inputSchema']\ntype McpOutputSchema = Parameters<typeof mcp.registerTool>[1]['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()\nregisterAssetResources()\nscheduleAssetCleanup()\n\nfunction registerAssetResources(): void {\n const template = new ResourceTemplate(MCP_ASSET_URI_TEMPLATE, {\n list: async () => ({\n // Intentionally keep resources/list empty: assets are ephemeral, tool-linked blobs.\n // Resource discovery would leak across sessions/design files and add UI noise.\n // Hosts should use resource_link from tool responses to access assets on demand.\n resources: []\n })\n })\n\n mcp.registerResource(\n MCP_ASSET_RESOURCE_NAME,\n template,\n {\n description:\n 'Exported PNG/SVG assets which can serve as screenshots or be referenced in code output.'\n },\n async (_uri, variables) => {\n const hash = typeof variables.hash === 'string' ? variables.hash : ''\n return readAssetResource(hash)\n }\n )\n}\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(\n { ttlMs: assetTtlMs, intervalMs },\n 'Asset TTL cleanup enabled (list remains empty; assets are tool-linked).'\n )\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\nasync function readAssetResource(hash: string) {\n if (!hash) {\n throw new Error('Missing asset hash in resource URI.')\n }\n const record = assetStore.get(hash)\n if (!record) {\n throw new Error(`Asset ${hash} not found.`)\n }\n\n if (!existsSync(record.filePath)) {\n assetStore.remove(hash, { removeFile: false })\n throw new Error(`Asset ${hash} file is missing.`)\n }\n\n const stat = statSync(record.filePath)\n // Base64 encoding increases size by ~33% (4 bytes for every 3 bytes)\n const estimatedSize = Math.ceil(stat.size / 3) * 4\n if (estimatedSize > maxPayloadBytes) {\n throw new Error(\n `Asset ${hash} is too large (${formatBytes(stat.size)}, encoded: ${formatBytes(estimatedSize)}) to read via MCP protocol. Use HTTP download.`\n )\n }\n\n assetStore.touch(hash)\n const buffer = readFileSync(record.filePath)\n const resourceUri = buildAssetResourceUri(hash)\n\n if (isTextualMime(record.mimeType)) {\n return {\n contents: [\n {\n uri: resourceUri,\n mimeType: record.mimeType,\n text: buffer.toString('utf8')\n }\n ]\n }\n }\n\n return {\n contents: [\n {\n uri: resourceUri,\n mimeType: record.mimeType,\n blob: buffer.toString('base64')\n }\n ]\n }\n}\n\nfunction isTextualMime(mimeType: string): boolean {\n return mimeType === 'image/svg+xml' || mimeType.startsWith('text/')\n}\n\nfunction buildAssetResourceUri(hash: string): string {\n return `${MCP_ASSET_URI_PREFIX}${hash}`\n}\n\nfunction formatAssetResourceName(hash: string): string {\n return `asset:${hash.slice(0, 8)}`\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 resourceUri: buildAssetResourceUri(record.hash),\n width: record.metadata?.width,\n height: record.metadata?.height\n }\n}\n\nfunction createAssetResourceLinkBlock(asset: AssetDescriptor) {\n return {\n type: 'resource_link' as const,\n name: formatAssetResourceName(asset.hash),\n uri: asset.resourceUri,\n mimeType: asset.mimeType,\n description: `${describeAsset(asset)} - Download: ${asset.url}`\n }\n}\n\nfunction describeAsset(asset: AssetDescriptor): string {\n return `${asset.mimeType} (${formatBytes(asset.size)})`\n}\n\nfunction registerTools(): void {\n const registered: string[] = []\n for (const tool of TOOL_DEFINITIONS) {\n if ('exposed' in tool && tool.exposed === false) continue\n registerTool(tool)\n registered.push(tool.name)\n }\n log.info({ tools: registered }, 'Registered tools.')\n}\n\nregisterTools()\nfunction registerTool(tool: RegisteredTool): void {\n if (tool.target === 'extension') {\n registerProxiedTool(tool)\n } else {\n registerLocalTool(tool)\n }\n}\n\nfunction registerProxiedTool<T extends ExtensionTool>(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 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 { promise, requestId } = register<Result>(activeExt.id, toolTimeoutMs)\n\n const message: ToolCallMessage = {\n type: 'toolCall',\n id: requestId,\n payload: {\n name: tool.name,\n args: parsedArgs\n }\n }\n activeExt.ws.send(JSON.stringify(message))\n log.info({ tool: tool.name, req: requestId, extId: activeExt.id }, 'Forwarded tool call.')\n\n const payload = await promise\n return createToolResponse(tool.name, payload)\n } catch (error) {\n log.error({ tool: tool.name, error }, 'Tool invocation failed before reaching extension.')\n return createToolErrorResponse(tool.name, error)\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(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(\n 'Use resources/read with each resourceUri or fetch the fallback URL to download bytes.'\n )\n\n const content = [\n {\n type: 'text' as const,\n text: summary.join('\\n')\n },\n ...payload.assets.map((asset) => createAssetResourceLinkBlock(asset))\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 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\nfunction shutdown(): void {\n log.info('Hub is shutting down...')\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 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 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.close()\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 reject(id, coerceToolError(error))\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.4.
|
|
9
|
+
version: "0.4.1",
|
|
10
10
|
description: "MCP server for TemPad Dev.",
|
|
11
11
|
bin: "dist/cli.mjs",
|
|
12
12
|
files: ["README.md", "dist/**/*"],
|
|
@@ -26,7 +26,6 @@ var package_default = {
|
|
|
26
26
|
},
|
|
27
27
|
dependencies: {
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.24.0",
|
|
29
|
-
"@tempad-dev/shared": "workspace:^0.1.0",
|
|
30
29
|
"nanoid": "^5.1.6",
|
|
31
30
|
"pino": "^10.0.0",
|
|
32
31
|
"pino-pretty": "^13.0.0",
|
|
@@ -35,6 +34,7 @@ var package_default = {
|
|
|
35
34
|
"zod": "^4.1.12"
|
|
36
35
|
},
|
|
37
36
|
devDependencies: {
|
|
37
|
+
"@tempad-dev/shared": "workspace:^0.1.0",
|
|
38
38
|
"@types/node": "^24.0.0",
|
|
39
39
|
"@types/proper-lockfile": "^4.1.4",
|
|
40
40
|
"@types/ws": "^8.5.12",
|
|
@@ -107,4 +107,4 @@ const SOCK_PATH = resolveSockPath();
|
|
|
107
107
|
|
|
108
108
|
//#endregion
|
|
109
109
|
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 };
|
|
110
|
-
//# sourceMappingURL=shared-
|
|
110
|
+
//# sourceMappingURL=shared-CnY7ym27.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared-
|
|
1
|
+
{"version":3,"file":"shared-CnY7ym27.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.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "MCP server for TemPad Dev.",
|
|
5
5
|
"bin": "dist/cli.mjs",
|
|
6
6
|
"files": [
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"main": "dist/cli.mjs",
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@modelcontextprotocol/sdk": "^1.24.0",
|
|
14
|
-
"@tempad-dev/shared": "^0.1.0",
|
|
15
14
|
"nanoid": "^5.1.6",
|
|
16
15
|
"pino": "^10.0.0",
|
|
17
16
|
"pino-pretty": "^13.0.0",
|
|
@@ -20,6 +19,7 @@
|
|
|
20
19
|
"zod": "^4.1.12"
|
|
21
20
|
},
|
|
22
21
|
"devDependencies": {
|
|
22
|
+
"@tempad-dev/shared": "^0.1.0",
|
|
23
23
|
"@types/node": "^24.0.0",
|
|
24
24
|
"@types/proper-lockfile": "^4.1.4",
|
|
25
25
|
"@types/ws": "^8.5.12",
|