@tempad-dev/mcp 0.4.1 → 0.4.3
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 -3
- package/README.zh-Hans.md +2 -3
- package/dist/cli.mjs +1 -1
- package/dist/hub.mjs +54 -133
- package/dist/hub.mjs.map +1 -1
- package/dist/{shared-CnY7ym27.mjs → shared-Bubiv0fy.mjs} +10 -3
- package/dist/{shared-CnY7ym27.mjs.map → shared-Bubiv0fy.mjs.map} +1 -1
- package/package.json +25 -15
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -25,12 +25,11 @@ Supported tools/resources:
|
|
|
25
25
|
|
|
26
26
|
- `get_code`: Tailwind-first JSX/Vue markup plus assets and token references.
|
|
27
27
|
- `get_structure`: Hierarchy/geometry outline for the selection.
|
|
28
|
-
- `get_screenshot`: PNG capture with a downloadable asset link.
|
|
29
|
-
- `tempad-assets` resource template (`asset://tempad/{hash}`) for binaries referenced by tool responses.
|
|
30
28
|
|
|
31
29
|
Notes:
|
|
32
30
|
|
|
33
|
-
- Assets are ephemeral and tool-linked;
|
|
31
|
+
- Assets are ephemeral and tool-linked; image/SVG bytes are downloaded via HTTP `asset.url` from tool results.
|
|
32
|
+
- Asset resources are not exposed via MCP `resources/list`/`resources/read`.
|
|
34
33
|
- The HTTP fallback URL uses `/assets/{hash}` and may include an image extension (for example `/assets/{hash}.png`). Both forms are accepted.
|
|
35
34
|
|
|
36
35
|
## Configuration
|
package/README.zh-Hans.md
CHANGED
|
@@ -23,12 +23,11 @@
|
|
|
23
23
|
|
|
24
24
|
- `get_code`:以 Tailwind 优先的 JSX/Vue 标记输出,并附带资源和变量引用。
|
|
25
25
|
- `get_structure`:当前选中节点的层级/几何结构信息。
|
|
26
|
-
- `get_screenshot`:PNG 截图,包含可下载的资源链接。
|
|
27
|
-
- `tempad-assets` 资源模板(`asset://tempad/{hash}`),用于读取工具返回中引用的二进制资源。
|
|
28
26
|
|
|
29
27
|
说明:
|
|
30
28
|
|
|
31
|
-
-
|
|
29
|
+
- 资源是临时且与工具调用关联的;图片/SVG 请直接使用工具结果中的 HTTP `asset.url` 下载。
|
|
30
|
+
- MCP 不再暴露 `resources/list` / `resources/read` 用于 asset 内容读取。
|
|
32
31
|
- HTTP 回退 URL 使用 `/assets/{hash}`,也可能带图片扩展名(例如 `/assets/{hash}.png`),两种形式都支持。
|
|
33
32
|
|
|
34
33
|
## 配置
|
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-Bubiv0fy.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,9 +1,9 @@
|
|
|
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-Bubiv0fy.mjs";
|
|
2
2
|
import { createServer } from "node:net";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { URL } from "node:url";
|
|
5
5
|
import { chmodSync, createReadStream, createWriteStream, existsSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
|
-
import { McpServer
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { nanoid } from "nanoid";
|
|
@@ -23,9 +23,6 @@ const MCP_TOOL_TIMEOUT_MS = 15e3;
|
|
|
23
23
|
const MCP_AUTO_ACTIVATE_GRACE_MS = 1500;
|
|
24
24
|
const MCP_MAX_ASSET_BYTES = 8 * 1024 * 1024;
|
|
25
25
|
const MCP_ASSET_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
26
|
-
const MCP_ASSET_RESOURCE_NAME = "tempad-assets";
|
|
27
|
-
const MCP_ASSET_URI_PREFIX = "asset://tempad/";
|
|
28
|
-
const MCP_ASSET_URI_TEMPLATE = `${MCP_ASSET_URI_PREFIX}{hash}`;
|
|
29
26
|
const MCP_HASH_HEX_LENGTH = 8;
|
|
30
27
|
const MCP_HASH_PATTERN = new RegExp(`^[a-f0-9]{${MCP_HASH_HEX_LENGTH}}$`, "i");
|
|
31
28
|
const TEMPAD_MCP_ERROR_CODES = {
|
|
@@ -70,13 +67,11 @@ const ToolResultMessageSchema = z.object({
|
|
|
70
67
|
error: z.unknown().optional()
|
|
71
68
|
});
|
|
72
69
|
const MessageFromExtensionSchema = z.discriminatedUnion("type", [ActivateMessageSchema, ToolResultMessageSchema]);
|
|
73
|
-
const MCP_ASSET_RESOURCE_URI_PATTERN = new RegExp(`^${MCP_ASSET_URI_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[a-f0-9]{${MCP_HASH_HEX_LENGTH}}$`, "i");
|
|
74
70
|
const AssetDescriptorSchema = z.object({
|
|
75
71
|
hash: z.string().min(1),
|
|
76
72
|
url: z.string().url(),
|
|
77
73
|
mimeType: z.string().min(1),
|
|
78
74
|
size: z.number().int().nonnegative(),
|
|
79
|
-
resourceUri: z.string().regex(MCP_ASSET_RESOURCE_URI_PATTERN),
|
|
80
75
|
width: z.number().int().positive().optional(),
|
|
81
76
|
height: z.number().int().positive().optional()
|
|
82
77
|
});
|
|
@@ -578,7 +573,7 @@ function createAssetStore(options = {}) {
|
|
|
578
573
|
|
|
579
574
|
//#endregion
|
|
580
575
|
//#region src/instructions.md?raw
|
|
581
|
-
var instructions_default = "You are connected to a Figma design file via TemPad Dev MCP.\n\nTreat tool outputs as design facts. Refactor only to match the user’s repo conventions; do not invent key style values.\n\nRules:\n\n- Never output any `data-hint-*` attributes from tool outputs (hints only).\n- If `get_code` warns `depth-cap`, call `get_code` again for each listed `nodeId` before implementing.\n- Use `get_structure`
|
|
576
|
+
var instructions_default = "You are connected to a Figma design file via TemPad Dev MCP.\n\nTreat tool outputs as design facts. Refactor only to match the user’s repo conventions; do not invent key style values.\n\nRules:\n\n- Never output any `data-hint-*` attributes from tool outputs (hints only).\n- If `get_code` warns `depth-cap`, call `get_code` again for each listed `nodeId` before implementing.\n- Use `get_structure` only to resolve layout/overlap uncertainty; do not derive numeric values from images.\n- Tokens: `get_code.tokens` keys are canonical names (`--...`). Multi‑mode values use `${collectionName}:${modeName}`. Nodes may hint per-node overrides via `data-hint-variable-mode=\"Collection=Mode;...\"`.\n- Assets: download bytes via `asset.url`. Asset resources are not exposed via MCP `resources/read`. Preserve SVG/vector assets exactly; never redraw vectors.\n";
|
|
582
577
|
|
|
583
578
|
//#endregion
|
|
584
579
|
//#region src/request.ts
|
|
@@ -663,7 +658,7 @@ function hubTool(definition) {
|
|
|
663
658
|
const TOOL_DEFS = [
|
|
664
659
|
extTool({
|
|
665
660
|
name: "get_code",
|
|
666
|
-
description: "High-fidelity code snapshot for nodeId/current single selection (omit nodeId to use selection): JSX/Vue markup + Tailwind-like classes, plus assets/tokens metadata and codegen config. Start here, then refactor into repo conventions while preserving values/intent; strip any data-hint-* attributes (hints only). If warnings include depth-cap, call get_code again for each listed nodeId. If warnings include auto-layout (inferred), use get_structure
|
|
661
|
+
description: "High-fidelity code snapshot for nodeId/current single selection (omit nodeId to use selection): JSX/Vue markup + Tailwind-like classes, plus assets/tokens metadata and codegen config. Start here, then refactor into repo conventions while preserving values/intent; strip any data-hint-* attributes (hints only). If warnings include depth-cap, call get_code again for each listed nodeId. If warnings include auto-layout (inferred), use get_structure to confirm hierarchy/overlap (do not derive numeric values from pixels). Tokens are keyed by canonical names like `--color-primary` (multi-mode keys use `${collection}:${mode}`; node overrides may appear as data-hint-variable-mode).",
|
|
667
662
|
parameters: GetCodeParametersSchema,
|
|
668
663
|
target: "extension",
|
|
669
664
|
format: createCodeToolResponse
|
|
@@ -680,17 +675,18 @@ const TOOL_DEFS = [
|
|
|
680
675
|
description: "Capture a rendered PNG screenshot for nodeId/current single selection for visual verification (layering/overlap/masks/effects).",
|
|
681
676
|
parameters: GetScreenshotParametersSchema,
|
|
682
677
|
target: "extension",
|
|
683
|
-
format: createScreenshotToolResponse
|
|
678
|
+
format: createScreenshotToolResponse,
|
|
679
|
+
exposed: false
|
|
684
680
|
}),
|
|
685
681
|
extTool({
|
|
686
682
|
name: "get_structure",
|
|
687
|
-
description: "Get a structural + geometry outline for nodeId/current single selection to understand hierarchy and layout intent.",
|
|
683
|
+
description: "Get a compact structural + geometry outline for nodeId/current single selection to understand hierarchy and layout intent.",
|
|
688
684
|
parameters: GetStructureParametersSchema,
|
|
689
685
|
target: "extension"
|
|
690
686
|
}),
|
|
691
687
|
hubTool({
|
|
692
688
|
name: "get_assets",
|
|
693
|
-
description: "Resolve asset hashes to downloadable URLs
|
|
689
|
+
description: "Resolve asset hashes to downloadable URLs and metadata for assets referenced by tool responses (preserve vectors exactly).",
|
|
694
690
|
parameters: GetAssetsParametersSchema,
|
|
695
691
|
target: "hub",
|
|
696
692
|
outputSchema: GetAssetsResultSchema,
|
|
@@ -715,17 +711,20 @@ function extractToolErrorMessage(error) {
|
|
|
715
711
|
function createToolErrorResponse(toolName, error) {
|
|
716
712
|
const message = extractToolErrorMessage(error);
|
|
717
713
|
const code = extractToolErrorCode(error);
|
|
718
|
-
return {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
714
|
+
return {
|
|
715
|
+
isError: true,
|
|
716
|
+
content: [{
|
|
717
|
+
type: "text",
|
|
718
|
+
text: `Tool "${toolName}" failed${code ? ` [${code}]` : ""}: ${message}${(() => {
|
|
719
|
+
const help = [];
|
|
720
|
+
if (code === TEMPAD_MCP_ERROR_CODES.NO_ACTIVE_EXTENSION || code === TEMPAD_MCP_ERROR_CODES.EXTENSION_TIMEOUT || code === TEMPAD_MCP_ERROR_CODES.EXTENSION_DISCONNECTED || code === TEMPAD_MCP_ERROR_CODES.ASSET_SERVER_NOT_CONFIGURED || code === TEMPAD_MCP_ERROR_CODES.TRANSPORT_NOT_CONNECTED || /no active tempad dev extension/i.test(message) || /asset server url is not configured/i.test(message) || /mcp transport is not connected/i.test(message) || /websocket/i.test(message)) help.push("Troubleshooting:", "- In Figma, open TemPad Dev panel and enable MCP (Preferences → MCP server).", "- If multiple Figma tabs are open, click the MCP badge to activate this tab.", "- Keep the Figma tab active/foreground while running MCP tools.");
|
|
721
|
+
if (code === TEMPAD_MCP_ERROR_CODES.INVALID_SELECTION || code === TEMPAD_MCP_ERROR_CODES.NODE_NOT_VISIBLE || /select exactly one visible node/i.test(message) || /no visible node found/i.test(message)) help.push("Tip: Select exactly one visible node, or pass nodeId.");
|
|
722
|
+
return help.length ? `\n\n${help.join("\n")}` : "";
|
|
723
|
+
})()}`
|
|
724
|
+
}]
|
|
725
|
+
};
|
|
727
726
|
}
|
|
728
|
-
function formatBytes
|
|
727
|
+
function formatBytes(bytes) {
|
|
729
728
|
if (bytes < 1024) return `${bytes} B`;
|
|
730
729
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
731
730
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
@@ -734,52 +733,35 @@ function createCodeToolResponse(payload) {
|
|
|
734
733
|
if (!isCodeResult(payload)) throw new Error("Invalid get_code payload received from extension.");
|
|
735
734
|
const summary = [];
|
|
736
735
|
const codeSize = Buffer.byteLength(payload.code, "utf8");
|
|
737
|
-
summary.push(`Generated \`${payload.lang}\` snippet (${formatBytes
|
|
736
|
+
summary.push(`Generated \`${payload.lang}\` snippet (${formatBytes(codeSize)}).`);
|
|
738
737
|
if (payload.warnings?.length) {
|
|
739
738
|
const warningText = payload.warnings.map((warning) => warning.message).join(" ");
|
|
740
739
|
summary.push(warningText);
|
|
741
740
|
}
|
|
742
|
-
summary.push(payload.assets?.length ? `Assets attached: ${payload.assets.length}.
|
|
741
|
+
summary.push(payload.assets?.length ? `Assets attached: ${payload.assets.length}. Download bytes from each asset.url.` : "No binary assets were attached to this response.");
|
|
743
742
|
const tokenCount = payload.tokens ? Object.keys(payload.tokens).length : 0;
|
|
744
743
|
if (tokenCount) summary.push(`Token references included: ${tokenCount}.`);
|
|
745
744
|
summary.push("Read structuredContent for the full code string and asset metadata.");
|
|
746
|
-
const assetLinks = payload.assets?.length ? payload.assets.map((asset) => createAssetResourceLinkBlock$1(asset)) : [];
|
|
747
745
|
return {
|
|
748
746
|
content: [{
|
|
749
747
|
type: "text",
|
|
750
748
|
text: summary.join("\n")
|
|
751
|
-
}
|
|
749
|
+
}],
|
|
752
750
|
structuredContent: payload
|
|
753
751
|
};
|
|
754
752
|
}
|
|
755
753
|
function createScreenshotToolResponse(payload) {
|
|
756
754
|
if (!isScreenshotResult(payload)) throw new Error("Invalid get_screenshot payload received from extension.");
|
|
757
755
|
return {
|
|
758
|
-
content: [
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
},
|
|
763
|
-
{
|
|
764
|
-
type: "text",
|
|
765
|
-
text: ``
|
|
766
|
-
},
|
|
767
|
-
createResourceLinkBlock(payload.asset, payload)
|
|
768
|
-
],
|
|
756
|
+
content: [{
|
|
757
|
+
type: "text",
|
|
758
|
+
text: `${describeScreenshot(payload)} - Download: ${payload.asset.url}`
|
|
759
|
+
}],
|
|
769
760
|
structuredContent: payload
|
|
770
761
|
};
|
|
771
762
|
}
|
|
772
|
-
function createResourceLinkBlock(asset, result) {
|
|
773
|
-
return {
|
|
774
|
-
type: "resource_link",
|
|
775
|
-
name: "Screenshot",
|
|
776
|
-
uri: asset.resourceUri,
|
|
777
|
-
mimeType: asset.mimeType,
|
|
778
|
-
description: `Screenshot ${result.width}x${result.height} @${result.scale}x - Download: ${asset.url}`
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
763
|
function describeScreenshot(result) {
|
|
782
|
-
return `Screenshot ${result.width}x${result.height} @${result.scale}x (${formatBytes
|
|
764
|
+
return `Screenshot ${result.width}x${result.height} @${result.scale}x (${formatBytes(result.bytes)})`;
|
|
783
765
|
}
|
|
784
766
|
function isScreenshotResult(payload) {
|
|
785
767
|
if (typeof payload !== "object" || !payload) return false;
|
|
@@ -791,21 +773,6 @@ function isCodeResult(payload) {
|
|
|
791
773
|
const candidate = payload;
|
|
792
774
|
return typeof candidate.code === "string" && typeof candidate.lang === "string" && (candidate.assets === void 0 || Array.isArray(candidate.assets));
|
|
793
775
|
}
|
|
794
|
-
function createAssetResourceLinkBlock$1(asset) {
|
|
795
|
-
return {
|
|
796
|
-
type: "resource_link",
|
|
797
|
-
name: formatAssetResourceName$1(asset.hash),
|
|
798
|
-
uri: asset.resourceUri,
|
|
799
|
-
mimeType: asset.mimeType,
|
|
800
|
-
description: `${describeAsset$1(asset)} - Download: ${asset.url}`
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
function describeAsset$1(asset) {
|
|
804
|
-
return `${asset.mimeType} (${formatBytes$1(asset.size)})`;
|
|
805
|
-
}
|
|
806
|
-
function formatAssetResourceName$1(hash) {
|
|
807
|
-
return `asset:${hash.slice(0, 8)}`;
|
|
808
|
-
}
|
|
809
776
|
function coercePayloadToToolResponse(payload) {
|
|
810
777
|
if (payload && typeof payload === "object" && Array.isArray(payload.content)) return payload;
|
|
811
778
|
return { content: [{
|
|
@@ -877,14 +844,7 @@ function getToolDefinition(name) {
|
|
|
877
844
|
const assetStore = createAssetStore();
|
|
878
845
|
const assetHttpServer = createAssetHttpServer(assetStore);
|
|
879
846
|
await assetHttpServer.start();
|
|
880
|
-
registerAssetResources();
|
|
881
847
|
scheduleAssetCleanup();
|
|
882
|
-
function registerAssetResources() {
|
|
883
|
-
const template = new ResourceTemplate(MCP_ASSET_URI_TEMPLATE, { list: async () => ({ resources: [] }) });
|
|
884
|
-
mcp.registerResource(MCP_ASSET_RESOURCE_NAME, template, { description: "Exported PNG/SVG assets which can serve as screenshots or be referenced in code output." }, async (_uri, variables) => {
|
|
885
|
-
return readAssetResource(typeof variables.hash === "string" ? variables.hash : "");
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
848
|
function scheduleAssetCleanup() {
|
|
889
849
|
if (assetTtlMs <= 0) {
|
|
890
850
|
log.info("Asset TTL cleanup disabled (TEMPAD_MCP_ASSET_TTL_MS=0).");
|
|
@@ -898,7 +858,7 @@ function scheduleAssetCleanup() {
|
|
|
898
858
|
log.info({
|
|
899
859
|
ttlMs: assetTtlMs,
|
|
900
860
|
intervalMs
|
|
901
|
-
}, "Asset TTL cleanup enabled
|
|
861
|
+
}, "Asset TTL cleanup enabled.");
|
|
902
862
|
}
|
|
903
863
|
function pruneExpiredAssets(ttlMs) {
|
|
904
864
|
const now = Date.now();
|
|
@@ -919,40 +879,6 @@ function pruneExpiredAssets(ttlMs) {
|
|
|
919
879
|
ttlMs
|
|
920
880
|
}, "Asset TTL sweep completed.");
|
|
921
881
|
}
|
|
922
|
-
async function readAssetResource(hash) {
|
|
923
|
-
if (!hash) throw new Error("Missing asset hash in resource URI.");
|
|
924
|
-
const record = assetStore.get(hash);
|
|
925
|
-
if (!record) throw new Error(`Asset ${hash} not found.`);
|
|
926
|
-
if (!existsSync(record.filePath)) {
|
|
927
|
-
assetStore.remove(hash, { removeFile: false });
|
|
928
|
-
throw new Error(`Asset ${hash} file is missing.`);
|
|
929
|
-
}
|
|
930
|
-
const stat = statSync(record.filePath);
|
|
931
|
-
const estimatedSize = Math.ceil(stat.size / 3) * 4;
|
|
932
|
-
if (estimatedSize > maxPayloadBytes) throw new Error(`Asset ${hash} is too large (${formatBytes(stat.size)}, encoded: ${formatBytes(estimatedSize)}) to read via MCP protocol. Use HTTP download.`);
|
|
933
|
-
assetStore.touch(hash);
|
|
934
|
-
const buffer = readFileSync(record.filePath);
|
|
935
|
-
const resourceUri = buildAssetResourceUri(hash);
|
|
936
|
-
if (isTextualMime(record.mimeType)) return { contents: [{
|
|
937
|
-
uri: resourceUri,
|
|
938
|
-
mimeType: record.mimeType,
|
|
939
|
-
text: buffer.toString("utf8")
|
|
940
|
-
}] };
|
|
941
|
-
return { contents: [{
|
|
942
|
-
uri: resourceUri,
|
|
943
|
-
mimeType: record.mimeType,
|
|
944
|
-
blob: buffer.toString("base64")
|
|
945
|
-
}] };
|
|
946
|
-
}
|
|
947
|
-
function isTextualMime(mimeType) {
|
|
948
|
-
return mimeType === "image/svg+xml" || mimeType.startsWith("text/");
|
|
949
|
-
}
|
|
950
|
-
function buildAssetResourceUri(hash) {
|
|
951
|
-
return `${MCP_ASSET_URI_PREFIX}${hash}`;
|
|
952
|
-
}
|
|
953
|
-
function formatAssetResourceName(hash) {
|
|
954
|
-
return `asset:${hash.slice(0, 8)}`;
|
|
955
|
-
}
|
|
956
882
|
function buildAssetDescriptor(record) {
|
|
957
883
|
const filename = buildAssetFilename(record.hash, record.mimeType);
|
|
958
884
|
return {
|
|
@@ -960,23 +886,10 @@ function buildAssetDescriptor(record) {
|
|
|
960
886
|
url: `${assetHttpServer.getBaseUrl()}/assets/${filename}`,
|
|
961
887
|
mimeType: record.mimeType,
|
|
962
888
|
size: record.size,
|
|
963
|
-
resourceUri: buildAssetResourceUri(record.hash),
|
|
964
889
|
width: record.metadata?.width,
|
|
965
890
|
height: record.metadata?.height
|
|
966
891
|
};
|
|
967
892
|
}
|
|
968
|
-
function createAssetResourceLinkBlock(asset) {
|
|
969
|
-
return {
|
|
970
|
-
type: "resource_link",
|
|
971
|
-
name: formatAssetResourceName(asset.hash),
|
|
972
|
-
uri: asset.resourceUri,
|
|
973
|
-
mimeType: asset.mimeType,
|
|
974
|
-
description: `${describeAsset(asset)} - Download: ${asset.url}`
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
function describeAsset(asset) {
|
|
978
|
-
return `${asset.mimeType} (${formatBytes(asset.size)})`;
|
|
979
|
-
}
|
|
980
893
|
function registerTools() {
|
|
981
894
|
const registered = [];
|
|
982
895
|
for (const tool of TOOL_DEFINITIONS) {
|
|
@@ -995,14 +908,16 @@ function registerProxiedTool(tool) {
|
|
|
995
908
|
const registerToolFn = mcp.registerTool.bind(mcp);
|
|
996
909
|
const schema = tool.parameters;
|
|
997
910
|
const handler = async (args) => {
|
|
911
|
+
let requestId;
|
|
998
912
|
try {
|
|
999
913
|
const parsedArgs = schema.parse(args);
|
|
1000
914
|
const activeExt = extensions.find((e) => e.active);
|
|
1001
915
|
if (!activeExt) throw createCodedError(TEMPAD_MCP_ERROR_CODES.NO_ACTIVE_EXTENSION, "No active TemPad Dev extension available.");
|
|
1002
|
-
const
|
|
916
|
+
const registration = register(activeExt.id, toolTimeoutMs);
|
|
917
|
+
requestId = registration.requestId;
|
|
1003
918
|
const message = {
|
|
1004
919
|
type: "toolCall",
|
|
1005
|
-
id: requestId,
|
|
920
|
+
id: registration.requestId,
|
|
1006
921
|
payload: {
|
|
1007
922
|
name: tool.name,
|
|
1008
923
|
args: parsedArgs
|
|
@@ -1011,17 +926,20 @@ function registerProxiedTool(tool) {
|
|
|
1011
926
|
activeExt.ws.send(JSON.stringify(message));
|
|
1012
927
|
log.info({
|
|
1013
928
|
tool: tool.name,
|
|
1014
|
-
req: requestId,
|
|
929
|
+
req: registration.requestId,
|
|
1015
930
|
extId: activeExt.id
|
|
1016
931
|
}, "Forwarded tool call.");
|
|
1017
|
-
const payload = await promise;
|
|
932
|
+
const payload = await registration.promise;
|
|
1018
933
|
return createToolResponse(tool.name, payload);
|
|
1019
934
|
} catch (error) {
|
|
935
|
+
const normalized = coerceToolError(error);
|
|
1020
936
|
log.error({
|
|
1021
937
|
tool: tool.name,
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
938
|
+
req: requestId,
|
|
939
|
+
code: getRecordProperty(normalized, "code"),
|
|
940
|
+
message: normalized.message
|
|
941
|
+
}, "Tool invocation failed.");
|
|
942
|
+
return createToolErrorResponse(tool.name, normalized);
|
|
1025
943
|
}
|
|
1026
944
|
};
|
|
1027
945
|
registerToolFn(tool.name, {
|
|
@@ -1081,12 +999,12 @@ async function handleGetAssets({ hashes }) {
|
|
|
1081
999
|
const summary = [];
|
|
1082
1000
|
summary.push(payload.assets.length ? `Resolved ${payload.assets.length} asset${payload.assets.length === 1 ? "" : "s"}.` : "No assets were resolved for the requested hashes.");
|
|
1083
1001
|
if (payload.missing.length) summary.push(`Missing: ${payload.missing.join(", ")}`);
|
|
1084
|
-
summary.push("
|
|
1002
|
+
summary.push("Download bytes from each asset.url.");
|
|
1085
1003
|
return {
|
|
1086
1004
|
content: [{
|
|
1087
1005
|
type: "text",
|
|
1088
1006
|
text: summary.join("\n")
|
|
1089
|
-
}
|
|
1007
|
+
}],
|
|
1090
1008
|
structuredContent: payload
|
|
1091
1009
|
};
|
|
1092
1010
|
}
|
|
@@ -1144,11 +1062,6 @@ function rawDataToBuffer(raw) {
|
|
|
1144
1062
|
if (raw instanceof ArrayBuffer) return Buffer.from(raw);
|
|
1145
1063
|
return Buffer.concat(raw);
|
|
1146
1064
|
}
|
|
1147
|
-
function formatBytes(bytes) {
|
|
1148
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
1149
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1150
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1151
|
-
}
|
|
1152
1065
|
function shutdown() {
|
|
1153
1066
|
log.info("Hub is shutting down...");
|
|
1154
1067
|
assetStore.flush();
|
|
@@ -1303,8 +1216,16 @@ wss.on("connection", (ws) => {
|
|
|
1303
1216
|
break;
|
|
1304
1217
|
case "toolResult": {
|
|
1305
1218
|
const { id, payload, error } = msg;
|
|
1306
|
-
if (error)
|
|
1307
|
-
|
|
1219
|
+
if (error) {
|
|
1220
|
+
const normalized = coerceToolError(error);
|
|
1221
|
+
log.warn({
|
|
1222
|
+
toolReq: id,
|
|
1223
|
+
extId: ext.id,
|
|
1224
|
+
code: getRecordProperty(normalized, "code"),
|
|
1225
|
+
message: normalized.message
|
|
1226
|
+
}, "Received tool error from extension.");
|
|
1227
|
+
reject(id, normalized);
|
|
1228
|
+
} else resolve(id, payload);
|
|
1308
1229
|
break;
|
|
1309
1230
|
}
|
|
1310
1231
|
}
|
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, 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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"}
|
|
1
|
+
{"version":3,"file":"hub.mjs","names":["createServer","getRecordProperty","MCP_INSTRUCTIONS"],"sources":["../../shared/dist/index.js","../src/asset-utils.ts","../src/config.ts","../src/asset-http-server.ts","../src/asset-store.ts","../src/instructions.md?raw","../src/request.ts","../src/tools.ts","../src/hub.ts"],"sourcesContent":["import { z } from \"zod\";\n\n//#region src/mcp/constants.ts\nconst MCP_PORT_CANDIDATES = [\n\t6220,\n\t7431,\n\t8127\n];\nconst MCP_MAX_PAYLOAD_BYTES = 4 * 1024 * 1024;\nconst MCP_TOOL_TIMEOUT_MS = 15e3;\nconst MCP_AUTO_ACTIVATE_GRACE_MS = 1500;\nconst MCP_MAX_ASSET_BYTES = 8 * 1024 * 1024;\nconst MCP_ASSET_TTL_MS = 720 * 60 * 60 * 1e3;\nconst MCP_HASH_HEX_LENGTH = 8;\nconst MCP_HASH_PATTERN = new RegExp(`^[a-f0-9]{${MCP_HASH_HEX_LENGTH}}$`, \"i\");\n\n//#endregion\n//#region src/mcp/errors.ts\nconst TEMPAD_MCP_ERROR_CODES = {\n\tNO_ACTIVE_EXTENSION: \"NO_ACTIVE_EXTENSION\",\n\tEXTENSION_TIMEOUT: \"EXTENSION_TIMEOUT\",\n\tEXTENSION_DISCONNECTED: \"EXTENSION_DISCONNECTED\",\n\tINVALID_SELECTION: \"INVALID_SELECTION\",\n\tNODE_NOT_VISIBLE: \"NODE_NOT_VISIBLE\",\n\tASSET_SERVER_NOT_CONFIGURED: \"ASSET_SERVER_NOT_CONFIGURED\",\n\tTRANSPORT_NOT_CONNECTED: \"TRANSPORT_NOT_CONNECTED\"\n};\n\n//#endregion\n//#region src/mcp/protocol.ts\nconst RegisteredMessageSchema = z.object({\n\ttype: z.literal(\"registered\"),\n\tid: z.string()\n});\nconst StateMessageSchema = z.object({\n\ttype: z.literal(\"state\"),\n\tactiveId: z.string().nullable(),\n\tcount: z.number().nonnegative(),\n\tport: z.number().positive(),\n\tassetServerUrl: z.string().url()\n});\nconst ToolCallPayloadSchema = z.object({\n\tname: z.string(),\n\targs: z.unknown()\n});\nconst ToolCallMessageSchema = z.object({\n\ttype: z.literal(\"toolCall\"),\n\tid: z.string(),\n\tpayload: ToolCallPayloadSchema\n});\nconst MessageToExtensionSchema = z.discriminatedUnion(\"type\", [\n\tRegisteredMessageSchema,\n\tStateMessageSchema,\n\tToolCallMessageSchema\n]);\nconst ActivateMessageSchema = z.object({ type: z.literal(\"activate\") });\nconst ToolResultMessageSchema = z.object({\n\ttype: z.literal(\"toolResult\"),\n\tid: z.string(),\n\tpayload: z.unknown().optional(),\n\terror: z.unknown().optional()\n});\nconst MessageFromExtensionSchema = z.discriminatedUnion(\"type\", [ActivateMessageSchema, ToolResultMessageSchema]);\nfunction parseJsonWithSchema(data, schema) {\n\tlet parsed;\n\ttry {\n\t\tparsed = JSON.parse(data);\n\t} catch {\n\t\treturn null;\n\t}\n\tconst result = schema.safeParse(parsed);\n\treturn result.success ? result.data : null;\n}\nfunction parseMessageToExtension(data) {\n\treturn parseJsonWithSchema(data, MessageToExtensionSchema);\n}\nfunction parseMessageFromExtension(data) {\n\treturn parseJsonWithSchema(data, MessageFromExtensionSchema);\n}\n\n//#endregion\n//#region src/mcp/tools.ts\nconst AssetDescriptorSchema = z.object({\n\thash: z.string().min(1),\n\turl: z.string().url(),\n\tmimeType: z.string().min(1),\n\tsize: z.number().int().nonnegative(),\n\twidth: z.number().int().positive().optional(),\n\theight: z.number().int().positive().optional()\n});\nconst GetCodeParametersSchema = z.object({\n\tnodeId: z.string().describe(\"Optional target node id; omit to use the current single selection when pulling the baseline snapshot.\").optional(),\n\tpreferredLang: z.enum([\"jsx\", \"vue\"]).describe(\"Preferred output language to bias the snapshot; otherwise uses the design’s hint/detected language, then falls back to JSX.\").optional(),\n\tresolveTokens: z.boolean().describe(\"Inline token values instead of references for quick renders; default false returns token metadata so you can map into your theming system. When true, values are resolved per-node (mode-aware).\").optional()\n});\nconst GetTokenDefsParametersSchema = z.object({\n\tnames: z.array(z.string().regex(/^--[a-zA-Z0-9-_]+$/)).min(1).describe(\"Canonical token names (CSS variable form) from Object.keys(get_code.tokens) or your own list to resolve, e.g., --color-primary.\"),\n\tincludeAllModes: z.boolean().describe(\"Include all token modes (light/dark/etc.) instead of just the active one to mirror responsive tokens; default false.\").optional()\n});\nconst GetScreenshotParametersSchema = z.object({ nodeId: z.string().describe(\"Optional node id to screenshot; defaults to the current single selection. Useful when layout/overlap is uncertain (auto-layout none/inferred).\").optional() });\nconst GetStructureParametersSchema = z.object({\n\tnodeId: z.string().describe(\"Optional node id to outline; defaults to the current single selection. Useful when auto-layout hints are none/inferred or you need explicit geometry for refactors.\").optional(),\n\toptions: z.object({ depth: z.number().int().positive().describe(\"Limit traversal depth; defaults to full tree (subject to safety caps).\").optional() }).optional()\n});\nconst GetAssetsParametersSchema = z.object({ hashes: z.array(z.string().regex(MCP_HASH_PATTERN)).min(1).describe(\"Asset hashes returned from get_code (or other tools) to download/resolve exact bytes for rasterized images or SVGs before routing through your asset pipeline.\") });\nconst GetAssetsResultSchema = z.object({\n\tassets: z.array(AssetDescriptorSchema),\n\tmissing: z.array(z.string().min(1))\n});\n\n//#endregion\n//#region src/figma/color.ts\n/**\n* Color utilities for Figma styles\n*/\n/**\n* Formats a Figma color with opacity to hex notation\n* @param color RGB color object with values 0-1\n* @param opacity Optional opacity value 0-1\n* @returns Hex color string (e.g., \"#FF0000\" or \"#FF0000CC\")\n*/\nfunction formatHexAlpha(color, opacity = 1) {\n\tconst toHex = (n) => {\n\t\treturn Math.min(255, Math.max(0, Math.round(n * 255))).toString(16).padStart(2, \"0\").toUpperCase();\n\t};\n\tconst r = toHex(color.r);\n\tconst g = toHex(color.g);\n\tconst b = toHex(color.b);\n\tif (opacity >= .99) {\n\t\tif (r[0] === r[1] && g[0] === g[1] && b[0] === b[1]) return `#${r[0]}${g[0]}${b[0]}`;\n\t\treturn `#${r}${g}${b}`;\n\t}\n\tconst a = toHex(opacity);\n\tif (r[0] === r[1] && g[0] === g[1] && b[0] === b[1] && a[0] === a[1]) return `#${r[0]}${g[0]}${b[0]}${a[0]}`;\n\treturn `#${r}${g}${b}${a}`;\n}\n\n//#endregion\n//#region src/figma/gradient.ts\nconst RE_NON_ASCII = /\\P{ASCII}+/gu;\nconst RE_QUOTES = /['\"]/g;\nconst RE_SLASH = /\\//g;\nconst RE_SPACE_TAB = /[ \\t]+/g;\nconst RE_WHITESPACE = /\\s+/g;\nconst RE_FAST_PATH = /^[A-Za-z0-9_-]+$/;\nconst RE_BOUND_NON_ALPHANUM = /[^A-Za-z0-9]+/g;\nconst RE_HYPHENS = /-+/g;\nconst RE_BOUND_DIGIT = /([A-Za-z])([0-9])|([0-9])([A-Za-z])/g;\nconst RE_BOUND_CASE = /([a-z])([A-Z])|([A-Z])([A-Z][a-z])/g;\nconst RE_DIGIT = /^\\d+$/;\nconst RE_CAPS = /^[A-Z]+$/;\nconst RE_SINGLE = /^[A-Za-z]$/;\nfunction isVisiblePaint(paint) {\n\treturn !!paint && paint.visible !== false;\n}\nfunction isGradientPaint(paint) {\n\treturn \"gradientStops\" in paint && Array.isArray(paint.gradientStops);\n}\nfunction isSolidPaint(paint) {\n\treturn paint.type === \"SOLID\";\n}\nfunction hasGradientHandlePositions(paint) {\n\treturn \"gradientHandlePositions\" in paint && Array.isArray(paint.gradientHandlePositions);\n}\n/**\n* Resolves gradient from paint array\n* Returns CSS gradient string or null\n*/\nfunction resolveGradientFromPaints(paints, size) {\n\tif (!paints || !Array.isArray(paints)) return null;\n\tconst gradientPaint = paints.find((paint) => isVisiblePaint(paint) && isGradientPaint(paint));\n\tif (!gradientPaint) return null;\n\tconst fillOpacity = typeof gradientPaint.opacity === \"number\" ? gradientPaint.opacity : 1;\n\tconst stops = gradientPaint.gradientStops.map((stop) => {\n\t\tconst pct = formatPercent(stop.position);\n\t\treturn `${formatGradientStopColor(stop, fillOpacity)} ${pct}`;\n\t});\n\tswitch (gradientPaint.type) {\n\t\tcase \"GRADIENT_LINEAR\": {\n\t\t\tconst angle = resolveLinearGradientAngle(gradientPaint, size);\n\t\t\treturn `linear-gradient(${(angle == null ? stops : [`${angle}deg`, ...stops]).join(\", \")})`;\n\t\t}\n\t\tcase \"GRADIENT_RADIAL\":\n\t\tcase \"GRADIENT_DIAMOND\": return `radial-gradient(${stops.join(\", \")})`;\n\t\tcase \"GRADIENT_ANGULAR\": return `conic-gradient(${stops.join(\", \")})`;\n\t\tdefault: return null;\n\t}\n}\n/**\n* Resolves solid color from paint array\n* Returns hex color string or null\n*/\nfunction resolveSolidFromPaints(paints) {\n\tif (!paints || !Array.isArray(paints)) return null;\n\tconst solidPaint = paints.find((paint) => isVisiblePaint(paint) && isSolidPaint(paint));\n\tif (!solidPaint || !solidPaint.color) return null;\n\tconst bound = solidPaint.boundVariables?.color;\n\tif (bound && typeof bound === \"object\" && \"id\" in bound && bound.id) try {\n\t\tconst variable = figma.variables.getVariableById(bound.id);\n\t\tif (variable) {\n\t\t\tconst fallback = formatHexAlpha(solidPaint.color, solidPaint.opacity);\n\t\t\treturn `var(${getVariableCssCustomPropertyName(variable)}, ${fallback})`;\n\t\t}\n\t} catch {}\n\treturn formatHexAlpha(solidPaint.color, solidPaint.opacity);\n}\n/**\n* Formats a gradient stop color\n*/\nfunction formatGradientStopColor(stop, fillOpacity) {\n\tconst baseAlpha = stop.color?.a ?? 1;\n\tconst alpha = Math.max(0, Math.min(1, baseAlpha * fillOpacity));\n\tconst bound = stop.boundVariables?.color;\n\tif (bound && typeof bound === \"object\" && \"id\" in bound && bound.id) try {\n\t\tconst v = figma.variables.getVariableById(bound.id);\n\t\tif (v) {\n\t\t\tconst fallbackOpaque = formatHexAlpha(stop.color, 1);\n\t\t\tconst fallbackAlpha = formatHexAlpha(stop.color, alpha);\n\t\t\tconst cssVarName = getVariableCssCustomPropertyName(v);\n\t\t\tconst varName = `var(${cssVarName}, ${fallbackOpaque})`;\n\t\t\tif (alpha >= .99) return `var(${cssVarName}, ${fallbackAlpha})`;\n\t\t\treturn `color-mix(in srgb, ${varName} ${Math.round(alpha * 1e4) / 100}%, transparent)`;\n\t\t}\n\t} catch {}\n\treturn formatHexAlpha(stop.color, alpha);\n}\n/**\n* Resolves linear gradient angle from gradient paint\n*/\nfunction resolveLinearGradientAngle(paint, size) {\n\tif (hasGradientHandlePositions(paint) && paint.gradientHandlePositions.length >= 2) {\n\t\tconst start = paint.gradientHandlePositions[0];\n\t\tconst end = paint.gradientHandlePositions[1];\n\t\tif (start && end) {\n\t\t\tconst { width, height } = getGradientSize(size);\n\t\t\tconst angle = normalizeGradientAngle((end.x - start.x) * width, (end.y - start.y) * height);\n\t\t\tif (angle != null) return angle;\n\t\t}\n\t}\n\tconst extracted = extractLinearGradientVectorFromTransform(paint.gradientTransform, size);\n\tif (!extracted) return null;\n\tconst { dx, dy } = extracted;\n\treturn normalizeGradientAngle(dx, dy);\n}\nfunction extractLinearGradientVectorFromTransform(transform, size) {\n\tif (!transform || !Array.isArray(transform) || transform.length < 2) return null;\n\tconst row0 = transform[0];\n\tconst row1 = transform[1];\n\tif (!Array.isArray(row0) || !Array.isArray(row1) || row0.length < 2 || row1.length < 2) return null;\n\tconst a = row0[0];\n\tconst c = row0[1];\n\tconst e = row0[2] ?? 0;\n\tconst b = row1[0];\n\tconst d = row1[1];\n\tconst f = row1[2] ?? 0;\n\tif (![\n\t\ta,\n\t\tb,\n\t\tc,\n\t\td,\n\t\te,\n\t\tf\n\t].every((value) => Number.isFinite(value))) return null;\n\tconst det = a * d - b * c;\n\tif (!Number.isFinite(det) || Math.abs(det) < 1e-8) return null;\n\tconst invA = d / det;\n\tconst invC = -c / det;\n\tconst invE = (c * f - d * e) / det;\n\tconst invB = -b / det;\n\tconst invD = a / det;\n\tconst invF = (b * e - a * f) / det;\n\tconst start = applyTransform(invA, invC, invE, invB, invD, invF, 0, .5);\n\tconst end = applyTransform(invA, invC, invE, invB, invD, invF, 1, .5);\n\tconst { width, height } = getGradientSize(size);\n\treturn {\n\t\tdx: (end.x - start.x) * width,\n\t\tdy: (end.y - start.y) * height\n\t};\n}\nfunction applyTransform(a, c, e, b, d, f, x, y) {\n\treturn {\n\t\tx: a * x + c * y + e,\n\t\ty: b * x + d * y + f\n\t};\n}\nfunction getGradientSize(size) {\n\tconst width = size?.width;\n\tconst height = size?.height;\n\treturn {\n\t\twidth: typeof width === \"number\" && Number.isFinite(width) && width > 0 ? width : 1,\n\t\theight: typeof height === \"number\" && Number.isFinite(height) && height > 0 ? height : 1\n\t};\n}\n/**\n* Normalizes gradient angle to degrees\n*/\nfunction normalizeGradientAngle(dx, dy) {\n\tif (!Number.isFinite(dx) || !Number.isFinite(dy)) return null;\n\tif (dx === 0 && dy === 0) return null;\n\tlet angle = Math.atan2(dy, dx) * 180 / Math.PI + 90;\n\tangle = (angle % 360 + 360) % 360;\n\treturn Math.round(angle * 100) / 100;\n}\n/**\n* Formats position as percentage\n*/\nfunction formatPercent(pos) {\n\treturn `${Math.round(pos * 1e4) / 100}%`;\n}\n/**\n* Variable naming must match MCP token indexing semantics exactly.\n*/\nfunction getVariableCssCustomPropertyName(variable) {\n\treturn normalizeFigmaVarName(getVariableRawName(variable));\n}\nfunction getVariableRawName(variable) {\n\tconst cs = variable.codeSyntax?.WEB;\n\tif (typeof cs === \"string\" && cs.trim()) {\n\t\tconst canonical = canonicalizeVarName(cs.trim());\n\t\tif (canonical) return canonical.slice(2);\n\t\tconst ident = cs.trim();\n\t\tif (/^[A-Za-z0-9_-]+$/.test(ident)) return ident;\n\t}\n\tconst raw = variable.name?.trim?.() ?? \"\";\n\tif (raw.startsWith(\"--\")) return raw.slice(2);\n\treturn raw;\n}\nfunction canonicalizeVarName(value) {\n\tconst cleaned = value.trim();\n\tconst varMatch = cleaned.match(/^var\\(\\s*(--[A-Za-z0-9_-]+)(?:\\s*,[\\s\\S]*)?\\)$/);\n\tif (varMatch?.[1]) return normalizeCustomPropertyName(varMatch[1]);\n\tif (cleaned.startsWith(\"--\")) return normalizeCustomPropertyName(cleaned);\n\treturn null;\n}\nfunction normalizeCustomPropertyBody(name) {\n\tif (!name) return \"var\";\n\tlet raw = name.trim();\n\tif (raw.startsWith(\"--\")) raw = raw.slice(2);\n\traw = raw.replace(/^-+/, \"\");\n\traw = raw.replace(/[^A-Za-z0-9_-]/g, \"\");\n\treturn raw || \"var\";\n}\nfunction normalizeCustomPropertyName(name) {\n\treturn `--${normalizeCustomPropertyBody(name)}`;\n}\nfunction normalizeFigmaVarName(input) {\n\tlet raw = (input ?? \"\").trim();\n\tif (!raw) return \"--unnamed\";\n\tconst canonical = canonicalizeVarName(raw);\n\tif (canonical) return canonical;\n\tif (raw.startsWith(\"--\")) raw = raw.slice(2).trim();\n\traw = raw.replace(RE_NON_ASCII, \"\").replace(RE_QUOTES, \"\").replace(RE_SLASH, \"-\").replace(RE_SPACE_TAB, \"-\").replace(RE_WHITESPACE, \"\");\n\tif (RE_FAST_PATH.test(raw)) return `--${raw}`;\n\tconst parts = raw.replace(RE_BOUND_NON_ALPHANUM, \"-\").replace(RE_HYPHENS, \"-\").replace(RE_BOUND_DIGIT, \"$1-$2$3-$4\").replace(RE_BOUND_CASE, \"$1$3-$2$4\").split(\"-\").filter(Boolean);\n\tconst stack = [];\n\tfor (const part of parts) {\n\t\tconst prev = stack[stack.length - 1];\n\t\tif (prev && RE_DIGIT.test(prev) && RE_DIGIT.test(part)) stack[stack.length - 1] += part;\n\t\telse if (prev && RE_CAPS.test(prev) && RE_CAPS.test(part)) stack[stack.length - 1] += part;\n\t\telse stack.push(part);\n\t}\n\tconst merged = [];\n\tfor (let i = 0; i < stack.length;) {\n\t\tif (i === 0) {\n\t\t\tmerged.push(stack[0]);\n\t\t\ti += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (RE_SINGLE.test(stack[i])) {\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < stack.length && RE_SINGLE.test(stack[j])) j += 1;\n\t\t\tconst run = stack.slice(i, j);\n\t\t\tmerged.push(run.length >= 2 ? run.join(\"\") : stack[i]);\n\t\t\ti = j;\n\t\t\tcontinue;\n\t\t}\n\t\tmerged.push(stack[i]);\n\t\ti += 1;\n\t}\n\tconst out = merged.join(\"-\").toLowerCase();\n\treturn out ? `--${out}` : \"--unnamed\";\n}\n\n//#endregion\n//#region src/figma/style-resolver.ts\nconst BG_URL_LIGHTGRAY_RE = /url\\(.*?\\)\\s+lightgray/i;\nconst BG_URL_RE = /url\\(/i;\nfunction hasStyleId(value) {\n\treturn typeof value === \"string\" && value.length > 0;\n}\nfunction isPaintStyle(style) {\n\treturn !!style && \"paints\" in style && Array.isArray(style.paints);\n}\nfunction resolvePaintStyleFromPaints(paints, size) {\n\tif (!paints) return null;\n\tconst gradient = resolveGradientFromPaints(paints, size);\n\tif (gradient) return { gradient };\n\tconst solidColor = resolveSolidFromPaints(paints);\n\treturn solidColor ? { solidColor } : null;\n}\nfunction resolvePaintStyleFromStyleId(styleId, kind, size) {\n\tif (!hasStyleId(styleId)) return null;\n\ttry {\n\t\tconst style = figma.getStyleById(styleId);\n\t\tif (!isPaintStyle(style)) return null;\n\t\treturn resolvePaintStyleFromPaints(style.paints, size);\n\t} catch (error) {\n\t\tconsole.warn(`Failed to resolve ${kind} style:`, error);\n\t\treturn null;\n\t}\n}\nfunction getNodeFillStyleId(node) {\n\treturn \"fillStyleId\" in node ? node.fillStyleId : null;\n}\nfunction getNodeStrokeStyleId(node) {\n\treturn \"strokeStyleId\" in node ? node.strokeStyleId : null;\n}\nfunction getNodeFillPaints(node) {\n\tif (\"fills\" in node && Array.isArray(node.fills)) return node.fills;\n\treturn null;\n}\nfunction getNodeStrokePaints(node) {\n\tif (\"strokes\" in node && Array.isArray(node.strokes)) return node.strokes;\n\treturn null;\n}\nfunction resolveNodePaintStyle(styleId, paints, kind, size) {\n\treturn resolvePaintStyleFromStyleId(styleId, kind, size) ?? resolvePaintStyleFromPaints(paints, size);\n}\nfunction getNodeDimensions(node) {\n\tif (!(\"width\" in node) || !(\"height\" in node)) return void 0;\n\tconst width = node.width;\n\tconst height = node.height;\n\tif (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;\n\treturn {\n\t\twidth,\n\t\theight\n\t};\n}\nfunction splitByTopLevelWhitespace$1(input) {\n\tconst out = [];\n\tlet depth = 0;\n\tlet quote = null;\n\tlet buffer = \"\";\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst ch = input[i];\n\t\tif (quote) {\n\t\t\tif (ch === \"\\\\\") {\n\t\t\t\tbuffer += ch;\n\t\t\t\ti++;\n\t\t\t\tif (i < input.length) buffer += input[i];\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (ch === quote) quote = null;\n\t\t\tbuffer += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\"\" || ch === \"'\") {\n\t\t\tquote = ch;\n\t\t\tbuffer += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") depth = Math.max(0, depth - 1);\n\t\tif (/\\s/.test(ch) && depth === 0) {\n\t\t\tif (buffer) {\n\t\t\t\tout.push(buffer);\n\t\t\t\tbuffer = \"\";\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tbuffer += ch;\n\t}\n\tif (buffer) out.push(buffer);\n\treturn out;\n}\nfunction isVarFunctionToken(value) {\n\tconst trimmed = value.trim();\n\treturn trimmed.startsWith(\"var(\") && trimmed.endsWith(\")\");\n}\nfunction patchBorderVarColor$1(borderValue, color) {\n\tconst borderParts = splitByTopLevelWhitespace$1(borderValue);\n\tif (!borderParts.length) return null;\n\tconst lastIndex = borderParts.length - 1;\n\tif (!isVarFunctionToken(borderParts[lastIndex])) return null;\n\tborderParts[lastIndex] = color;\n\treturn borderParts.join(\" \");\n}\nfunction hasBorderChannels(style) {\n\treturn Object.entries(style).some(([key, value]) => {\n\t\tif (!value?.trim()) return false;\n\t\tif (!/^border(?:$|-)/.test(key)) return false;\n\t\tif (key.includes(\"radius\") || key === \"border-image\" || key === \"border-image-slice\") return false;\n\t\treturn true;\n\t});\n}\n/**\n* Resolves fill style for a Figma node\n* Handles both fillStyleId and direct fills\n*/\nfunction resolveFillStyleForNode(node) {\n\treturn resolveNodePaintStyle(getNodeFillStyleId(node), getNodeFillPaints(node), \"fill\", getNodeDimensions(node));\n}\n/**\n* Resolves stroke style for a Figma node\n* Handles both strokeStyleId and direct strokes\n*/\nfunction resolveStrokeStyleForNode(node) {\n\treturn resolveNodePaintStyle(getNodeStrokeStyleId(node), getNodeStrokePaints(node), \"stroke\", getNodeDimensions(node));\n}\n/**\n* Main function to resolve all styles from a node\n* Replaces CSS variable references with actual values\n*/\nasync function resolveStylesFromNode(cssStyles, node) {\n\tconst processed = { ...cssStyles };\n\tconst fillPaints = getNodeFillPaints(node);\n\tif (processed.background && BG_URL_LIGHTGRAY_RE.test(processed.background) && fillPaints) {\n\t\tconst solidFill = resolveSolidFromPaints(fillPaints);\n\t\tif (solidFill) processed[\"background-color\"] = solidFill;\n\t\tprocessed.background = processed.background.replace(/\\s*,?\\s*lightgray\\b/i, \"\").trim();\n\t}\n\tconst resolvedFill = resolveFillStyleForNode(node);\n\tconst hasUrlBackground = typeof processed.background === \"string\" && BG_URL_RE.test(processed.background);\n\tif (resolvedFill?.gradient) {\n\t\tif (processed.background && !hasUrlBackground) processed.background = resolvedFill.gradient;\n\t\telse if (processed[\"background-color\"]) {\n\t\t\tprocessed.background = resolvedFill.gradient;\n\t\t\tdelete processed[\"background-color\"];\n\t\t}\n\t} else if (resolvedFill?.solidColor) {\n\t\tif (processed.background && !hasUrlBackground) {\n\t\t\tprocessed[\"background-color\"] = resolvedFill.solidColor;\n\t\t\tdelete processed.background;\n\t\t}\n\t\tif (processed[\"background-color\"]) processed[\"background-color\"] = resolvedFill.solidColor;\n\t\tif (processed.color) processed.color = resolvedFill.solidColor;\n\t\tif (processed.fill) processed.fill = resolvedFill.solidColor;\n\t}\n\tconst resolvedStroke = resolveStrokeStyleForNode(node);\n\tif (resolvedStroke?.gradient && hasBorderChannels(processed)) {\n\t\tprocessed[\"border-image\"] = resolvedStroke.gradient;\n\t\tprocessed[\"border-image-slice\"] = \"1\";\n\t} else if (resolvedStroke?.solidColor) {\n\t\tif (processed[\"border-color\"]) processed[\"border-color\"] = resolvedStroke.solidColor;\n\t\telse if (processed.border) {\n\t\t\tconst patched = patchBorderVarColor$1(processed.border, resolvedStroke.solidColor);\n\t\t\tif (patched) processed.border = patched;\n\t\t}\n\t}\n\tif (processed.stroke && resolvedStroke?.gradient) processed.stroke = resolvedStroke.gradient;\n\telse if (processed.stroke && resolvedStroke?.solidColor) processed.stroke = resolvedStroke.solidColor;\n\treturn processed;\n}\n\n//#endregion\n//#region src/figma/stroke.ts\nfunction splitByTopLevelWhitespace(input) {\n\tconst out = [];\n\tlet depth = 0;\n\tlet quote = null;\n\tlet buffer = \"\";\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst ch = input[i];\n\t\tif (quote) {\n\t\t\tif (ch === \"\\\\\") {\n\t\t\t\tbuffer += ch;\n\t\t\t\ti++;\n\t\t\t\tif (i < input.length) buffer += input[i];\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (ch === quote) quote = null;\n\t\t\tbuffer += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\"\" || ch === \"'\") {\n\t\t\tquote = ch;\n\t\t\tbuffer += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") depth = Math.max(0, depth - 1);\n\t\tif (/\\s/.test(ch) && depth === 0) {\n\t\t\tif (buffer) {\n\t\t\t\tout.push(buffer);\n\t\t\t\tbuffer = \"\";\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tbuffer += ch;\n\t}\n\tif (buffer) out.push(buffer);\n\treturn out;\n}\nfunction patchBorderVarColor(borderValue, color) {\n\tconst borderParts = splitByTopLevelWhitespace(borderValue);\n\tif (!borderParts.length) return null;\n\tconst lastIndex = borderParts.length - 1;\n\tconst tail = borderParts[lastIndex].trim();\n\tif (!tail.startsWith(\"var(\") || !tail.endsWith(\")\")) return null;\n\tborderParts[lastIndex] = color;\n\treturn borderParts.join(\" \");\n}\n/**\n* Resolves stroke styles from paint array\n* Can handle both solid colors and gradients\n* @param paints Array of paint objects from strokes or stroke style\n* @returns Object with solidColor or gradient, or null\n*/\nfunction resolveStrokeFromPaints(paints) {\n\tif (!paints || !Array.isArray(paints)) return null;\n\tconst gradient = resolveGradientFromPaints(paints);\n\tif (gradient) return { gradient };\n\tconst solidColor = resolveSolidFromPaints(paints);\n\tif (solidColor) return { solidColor };\n\treturn null;\n}\n/**\n* Applies resolved stroke styles to CSS properties\n* Handles different CSS properties for stroke (border, stroke, outline)\n*/\nfunction applyStrokeToCSS(styles, resolved) {\n\tif (!resolved) return styles;\n\tconst processed = { ...styles };\n\tconst borderHasVar = processed.border?.includes(\"var(--\") ?? false;\n\tconst borderColorHasVar = processed[\"border-color\"]?.includes(\"var(--\") ?? false;\n\tif (borderHasVar || borderColorHasVar) {\n\t\tif (resolved.gradient) {\n\t\t\tprocessed[\"border-image\"] = `${resolved.gradient} 1`;\n\t\t\tprocessed[\"border-image-slice\"] = \"1\";\n\t\t\tdelete processed[\"border-color\"];\n\t\t} else if (resolved.solidColor) if (borderColorHasVar) processed[\"border-color\"] = resolved.solidColor;\n\t\telse {\n\t\t\tconst patched = patchBorderVarColor(processed.border, resolved.solidColor);\n\t\t\tif (patched) processed.border = patched;\n\t\t}\n\t}\n\tif (processed.stroke?.includes(\"var(--\")) {\n\t\tif (resolved.gradient) processed.stroke = resolved.gradient;\n\t\telse if (resolved.solidColor) processed.stroke = resolved.solidColor;\n\t}\n\tif (processed[\"outline-color\"]?.includes(\"var(--\")) {\n\t\tif (resolved.solidColor) processed[\"outline-color\"] = resolved.solidColor;\n\t}\n\treturn processed;\n}\n\n//#endregion\nexport { ActivateMessageSchema, AssetDescriptorSchema, GetAssetsParametersSchema, GetAssetsResultSchema, GetCodeParametersSchema, GetScreenshotParametersSchema, GetStructureParametersSchema, GetTokenDefsParametersSchema, MCP_ASSET_TTL_MS, MCP_AUTO_ACTIVATE_GRACE_MS, MCP_HASH_HEX_LENGTH, MCP_HASH_PATTERN, MCP_MAX_ASSET_BYTES, MCP_MAX_PAYLOAD_BYTES, MCP_PORT_CANDIDATES, MCP_TOOL_TIMEOUT_MS, MessageFromExtensionSchema, MessageToExtensionSchema, RegisteredMessageSchema, StateMessageSchema, TEMPAD_MCP_ERROR_CODES, ToolCallMessageSchema, ToolCallPayloadSchema, ToolResultMessageSchema, applyStrokeToCSS, formatHexAlpha, parseMessageFromExtension, parseMessageToExtension, resolveFillStyleForNode, resolveGradientFromPaints, resolveSolidFromPaints, resolveStrokeFromPaints, resolveStrokeStyleForNode, resolveStylesFromNode };","import { MCP_HASH_HEX_LENGTH } from '@tempad-dev/shared'\n\nconst HASH_FILENAME_PATTERN = new RegExp(\n `^([a-f0-9]{${MCP_HASH_HEX_LENGTH}})(?:\\\\.[a-z0-9]+)?$`,\n 'i'\n)\n\nconst MIME_EXTENSION_OVERRIDES = new Map<string, string>([['image/jpeg', 'jpg']])\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` only to resolve layout/overlap uncertainty; do not derive numeric values from images.\\n- Tokens: `get_code.tokens` keys are canonical names (`--...`). Multi‑mode values use `${collectionName}:${modeName}`. Nodes may hint per-node overrides via `data-hint-variable-mode=\\\"Collection=Mode;...\\\"`.\\n- Assets: download bytes via `asset.url`. Asset resources are not exposed via MCP `resources/read`. Preserve SVG/vector assets exactly; never redraw vectors.\\n\"","import type { TempadMcpErrorCode } from '@tempad-dev/shared'\n\nimport { TEMPAD_MCP_ERROR_CODES } from '@tempad-dev/shared'\nimport { nanoid } from 'nanoid'\n\nimport type { PendingToolCall } from './types'\n\nimport { log } from './shared'\n\nconst pendingCalls = new Map<string, PendingToolCall>()\n\nfunction createToolError(\n code: TempadMcpErrorCode,\n message: string\n): Error & { code: TempadMcpErrorCode } {\n const err = new Error(message) as Error & { code: TempadMcpErrorCode }\n err.code = code\n return err\n}\n\nexport function register<T>(\n extensionId: string,\n timeout: number\n): { promise: Promise<T>; requestId: string } {\n const requestId = nanoid()\n const promise = new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n pendingCalls.delete(requestId)\n reject(\n createToolError(\n TEMPAD_MCP_ERROR_CODES.EXTENSION_TIMEOUT,\n `Extension did not respond within ${timeout / 1000}s.`\n )\n )\n }, timeout)\n\n pendingCalls.set(requestId, {\n resolve: resolve as (value: unknown) => void,\n reject,\n timer,\n extensionId\n })\n })\n return { promise, requestId }\n}\n\nexport function resolve(requestId: string, payload: unknown): void {\n const call = pendingCalls.get(requestId)\n if (call) {\n const { timer, resolve: finish } = call\n clearTimeout(timer)\n finish(payload)\n pendingCalls.delete(requestId)\n } else {\n log.warn({ reqId: requestId }, 'Received result for unknown/timed-out call.')\n }\n}\n\nexport function reject(requestId: string, error: Error): void {\n const call = pendingCalls.get(requestId)\n if (call) {\n const { timer, reject: fail } = call\n clearTimeout(timer)\n fail(error)\n pendingCalls.delete(requestId)\n } else {\n log.warn({ reqId: requestId }, 'Received error for unknown/timed-out call.')\n }\n}\n\nexport function cleanupForExtension(extensionId: string): void {\n for (const [reqId, call] of pendingCalls.entries()) {\n const { timer, reject: fail, extensionId: extId } = call\n if (extId === extensionId) {\n clearTimeout(timer)\n fail(\n createToolError(\n TEMPAD_MCP_ERROR_CODES.EXTENSION_DISCONNECTED,\n 'Extension disconnected before providing a result.'\n )\n )\n pendingCalls.delete(reqId)\n log.warn({ reqId, extId: extensionId }, 'Rejected pending call from disconnected extension.')\n }\n }\n}\n\nexport function cleanupAll(): void {\n pendingCalls.forEach((call, reqId) => {\n const { timer, reject: fail } = call\n clearTimeout(timer)\n fail(new Error('Hub is shutting down.'))\n log.debug({ reqId }, 'Rejected pending tool call due to shutdown.')\n })\n pendingCalls.clear()\n}\n","import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport type {\n GetScreenshotResult,\n TempadMcpErrorCode,\n ToolName,\n ToolResultMap,\n ToolSchema\n} from '@tempad-dev/shared'\nimport type { ZodType } from 'zod'\n\nimport {\n GetAssetsParametersSchema,\n GetAssetsResultSchema,\n GetCodeParametersSchema,\n GetScreenshotParametersSchema,\n GetStructureParametersSchema,\n GetTokenDefsParametersSchema,\n TEMPAD_MCP_ERROR_CODES,\n type TempadMcpErrorPayload\n} from '@tempad-dev/shared'\n\nexport type {\n AssetDescriptor,\n GetAssetsParametersInput,\n GetAssetsResult,\n GetCodeParametersInput,\n GetCodeResult,\n GetScreenshotParametersInput,\n GetScreenshotResult,\n GetStructureParametersInput,\n GetStructureResult,\n GetTokenDefsParametersInput,\n GetTokenDefsResult,\n TokenEntry,\n ToolName,\n ToolResultMap,\n ToolSchema\n} from '@tempad-dev/shared'\n\ntype BaseToolMetadata<Name extends ToolName, Schema extends ZodType> = ToolSchema<Name> & {\n parameters: Schema\n format?: (payload: ToolResultMap[Name]) => CallToolResult\n}\n\ntype ExtensionToolMetadata<Name extends ToolName, Schema extends ZodType> = BaseToolMetadata<\n Name,\n Schema\n> & {\n target: 'extension'\n}\n\ntype HubToolMetadata<Name extends ToolName, Schema extends ZodType> = BaseToolMetadata<\n Name,\n Schema\n> & {\n target: 'hub'\n outputSchema?: ZodType\n}\n\nfunction getRecordProperty(record: unknown, key: string): unknown {\n if (!record || typeof record !== 'object') {\n return undefined\n }\n return Reflect.get(record, key)\n}\n\nfunction extTool<Name extends ToolName, Schema extends ZodType>(\n definition: ExtensionToolMetadata<Name, Schema>\n): ExtensionToolMetadata<Name, Schema> {\n return definition\n}\n\nfunction hubTool<Name extends ToolName, Schema extends ZodType>(\n definition: HubToolMetadata<Name, Schema>\n): HubToolMetadata<Name, Schema> {\n return definition\n}\n\nexport const TOOL_DEFS = [\n extTool({\n name: 'get_code',\n description:\n 'High-fidelity code snapshot for nodeId/current single selection (omit nodeId to use selection): JSX/Vue markup + Tailwind-like classes, plus assets/tokens metadata and codegen config. Start here, then refactor into repo conventions while preserving values/intent; strip any data-hint-* attributes (hints only). If warnings include depth-cap, call get_code again for each listed nodeId. If warnings include auto-layout (inferred), use get_structure to confirm hierarchy/overlap (do not derive numeric values from pixels). Tokens are keyed by canonical names like `--color-primary` (multi-mode keys use `${collection}:${mode}`; node overrides may appear as data-hint-variable-mode).',\n parameters: GetCodeParametersSchema,\n target: 'extension',\n format: createCodeToolResponse\n }),\n extTool({\n name: 'get_token_defs',\n description:\n 'Resolve canonical token names to literal values (optionally including all modes) for tokens referenced by get_code.',\n parameters: GetTokenDefsParametersSchema,\n target: 'extension',\n exposed: false\n }),\n extTool({\n name: 'get_screenshot',\n description:\n 'Capture a rendered PNG screenshot for nodeId/current single selection for visual verification (layering/overlap/masks/effects).',\n parameters: GetScreenshotParametersSchema,\n target: 'extension',\n format: createScreenshotToolResponse,\n exposed: false\n }),\n extTool({\n name: 'get_structure',\n description:\n 'Get a compact structural + geometry outline for nodeId/current single selection to understand hierarchy and layout intent.',\n parameters: GetStructureParametersSchema,\n target: 'extension'\n }),\n hubTool({\n name: 'get_assets',\n description:\n 'Resolve asset hashes to downloadable URLs and metadata for assets referenced by tool responses (preserve vectors exactly).',\n parameters: GetAssetsParametersSchema,\n target: 'hub',\n outputSchema: GetAssetsResultSchema,\n exposed: false\n })\n] as const\n\nfunction extractToolErrorCode(error: unknown): TempadMcpErrorCode | undefined {\n const code = getRecordProperty(error, 'code')\n if (typeof code === 'string') {\n return code as TempadMcpErrorCode\n }\n const cause = getRecordProperty(error, 'cause')\n const causeCode = getRecordProperty(cause, 'code')\n if (typeof causeCode === 'string') {\n return causeCode as TempadMcpErrorCode\n }\n return undefined\n}\n\nfunction extractToolErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message || 'Unknown error occurred.'\n if (typeof error === 'string') return error\n if (error && typeof error === 'object') {\n const candidate = error as Partial<TempadMcpErrorPayload & Record<string, unknown>>\n if (typeof candidate.message === 'string' && candidate.message.trim()) return candidate.message\n }\n return 'Unknown error occurred.'\n}\n\nfunction createToolErrorResponse(toolName: string, error: unknown): CallToolResult {\n const message = extractToolErrorMessage(error)\n const code = extractToolErrorCode(error)\n const codeLabel = code ? ` [${code}]` : ''\n\n const troubleshooting = (() => {\n const help: string[] = []\n\n const isConnectivityError =\n code === TEMPAD_MCP_ERROR_CODES.NO_ACTIVE_EXTENSION ||\n code === TEMPAD_MCP_ERROR_CODES.EXTENSION_TIMEOUT ||\n code === TEMPAD_MCP_ERROR_CODES.EXTENSION_DISCONNECTED ||\n code === TEMPAD_MCP_ERROR_CODES.ASSET_SERVER_NOT_CONFIGURED ||\n code === TEMPAD_MCP_ERROR_CODES.TRANSPORT_NOT_CONNECTED ||\n /no active tempad dev extension/i.test(message) ||\n /asset server url is not configured/i.test(message) ||\n /mcp transport is not connected/i.test(message) ||\n /websocket/i.test(message)\n\n if (isConnectivityError) {\n help.push(\n 'Troubleshooting:',\n '- In Figma, open TemPad Dev panel and enable MCP (Preferences → MCP server).',\n '- If multiple Figma tabs are open, click the MCP badge to activate this tab.',\n '- Keep the Figma tab active/foreground while running MCP tools.'\n )\n }\n\n const isSelectionError =\n code === TEMPAD_MCP_ERROR_CODES.INVALID_SELECTION ||\n code === TEMPAD_MCP_ERROR_CODES.NODE_NOT_VISIBLE ||\n /select exactly one visible node/i.test(message) ||\n /no visible node found/i.test(message)\n\n if (isSelectionError) {\n help.push('Tip: Select exactly one visible node, or pass nodeId.')\n }\n\n return help.length ? `\\n\\n${help.join('\\n')}` : ''\n })()\n\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: `Tool \"${toolName}\" failed${codeLabel}: ${message}${troubleshooting}`\n }\n ]\n }\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nexport function createCodeToolResponse(payload: ToolResultMap['get_code']): CallToolResult {\n if (!isCodeResult(payload)) {\n throw new Error('Invalid get_code payload received from extension.')\n }\n\n const summary: string[] = []\n const codeSize = Buffer.byteLength(payload.code, 'utf8')\n summary.push(`Generated \\`${payload.lang}\\` snippet (${formatBytes(codeSize)}).`)\n if (payload.warnings?.length) {\n const warningText = payload.warnings.map((warning) => warning.message).join(' ')\n summary.push(warningText)\n }\n summary.push(\n payload.assets?.length\n ? `Assets attached: ${payload.assets.length}. Download bytes from each asset.url.`\n : 'No binary assets were attached to this response.'\n )\n const tokenCount = payload.tokens ? Object.keys(payload.tokens).length : 0\n if (tokenCount) {\n summary.push(`Token references included: ${tokenCount}.`)\n }\n summary.push('Read structuredContent for the full code string and asset metadata.')\n\n return {\n content: [\n {\n type: 'text' as const,\n text: summary.join('\\n')\n }\n ],\n structuredContent: payload\n }\n}\n\nexport function createScreenshotToolResponse(\n payload: ToolResultMap['get_screenshot']\n): CallToolResult {\n if (!isScreenshotResult(payload)) {\n throw new Error('Invalid get_screenshot payload received from extension.')\n }\n\n const descriptionBlock = {\n type: 'text' as const,\n text: `${describeScreenshot(payload)} - Download: ${payload.asset.url}`\n }\n\n return {\n content: [descriptionBlock],\n structuredContent: payload\n }\n}\n\nfunction describeScreenshot(result: GetScreenshotResult): string {\n return `Screenshot ${result.width}x${result.height} @${result.scale}x (${formatBytes(result.bytes)})`\n}\n\nfunction isScreenshotResult(payload: unknown): payload is GetScreenshotResult {\n if (typeof payload !== 'object' || !payload) return false\n const candidate = payload as Partial<GetScreenshotResult & Record<string, unknown>>\n return (\n typeof candidate.asset === 'object' &&\n candidate.asset !== null &&\n typeof candidate.width === 'number' &&\n typeof candidate.height === 'number' &&\n typeof candidate.scale === 'number' &&\n typeof candidate.bytes === 'number' &&\n typeof candidate.format === 'string'\n )\n}\n\nfunction isCodeResult(payload: unknown): payload is ToolResultMap['get_code'] {\n if (typeof payload !== 'object' || !payload) return false\n const candidate = payload as Partial<ToolResultMap['get_code'] & Record<string, unknown>>\n return (\n typeof candidate.code === 'string' &&\n typeof candidate.lang === 'string' &&\n (candidate.assets === undefined || Array.isArray(candidate.assets))\n )\n}\n\nexport function coercePayloadToToolResponse(payload: unknown): CallToolResult {\n if (\n payload &&\n typeof payload === 'object' &&\n Array.isArray((payload as CallToolResult).content)\n ) {\n return payload as CallToolResult\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2)\n }\n ]\n }\n}\n\nexport { createToolErrorResponse }\n","import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport type {\n AssetDescriptor,\n GetAssetsParametersInput,\n GetAssetsResult,\n RegisteredMessage,\n StateMessage,\n ToolCallMessage,\n ToolName,\n ToolResultMap,\n ToolResultMessage\n} from '@tempad-dev/shared'\nimport type { RawData } from 'ws'\nimport type { ZodType } from 'zod'\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport {\n GetAssetsResultSchema,\n MessageFromExtensionSchema,\n TEMPAD_MCP_ERROR_CODES,\n type TempadMcpErrorCode\n} from '@tempad-dev/shared'\nimport { nanoid } from 'nanoid'\nimport { existsSync, rmSync, chmodSync } from 'node:fs'\nimport { createServer } from 'node:net'\nimport { WebSocketServer } from 'ws'\n\nimport type { AssetRecord, ExtensionConnection } from './types'\n\nimport { createAssetHttpServer } from './asset-http-server'\nimport { createAssetStore } from './asset-store'\nimport { buildAssetFilename } from './asset-utils'\nimport { getMcpServerConfig } from './config'\nimport MCP_INSTRUCTIONS from './instructions.md?raw'\nimport { register, resolve, reject, cleanupForExtension, cleanupAll } from './request'\nimport { PACKAGE_VERSION, log, RUNTIME_DIR, SOCK_PATH, ensureDir } from './shared'\nimport { TOOL_DEFS, coercePayloadToToolResponse, createToolErrorResponse } from './tools'\n\nconst SHUTDOWN_TIMEOUT = 2000\nconst { wsPortCandidates, toolTimeoutMs, maxPayloadBytes, autoActivateGraceMs, assetTtlMs } =\n getMcpServerConfig()\n\nlog.info({ version: PACKAGE_VERSION }, 'TemPad MCP Hub starting...')\n\nconst extensions: ExtensionConnection[] = []\nlet consumerCount = 0\ntype TimeoutHandle = ReturnType<typeof setTimeout>\nlet autoActivateTimer: TimeoutHandle | null = null\nlet selectedWsPort = 0\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()\nscheduleAssetCleanup()\n\nfunction scheduleAssetCleanup(): void {\n if (assetTtlMs <= 0) {\n log.info('Asset TTL cleanup disabled (TEMPAD_MCP_ASSET_TTL_MS=0).')\n return\n }\n pruneExpiredAssets(assetTtlMs)\n const intervalMs = Math.min(assetTtlMs, 24 * 60 * 60 * 1000)\n const timer = setInterval(() => {\n pruneExpiredAssets(assetTtlMs)\n }, intervalMs)\n unrefTimer(timer)\n log.info({ ttlMs: assetTtlMs, intervalMs }, 'Asset TTL cleanup enabled.')\n}\n\nfunction pruneExpiredAssets(ttlMs: number): void {\n const now = Date.now()\n let removed = 0\n let checked = 0\n for (const record of assetStore.list()) {\n checked += 1\n const lastAccess = Number.isFinite(record.lastAccess) ? record.lastAccess : record.uploadedAt\n if (!lastAccess) continue\n if (now - lastAccess > ttlMs) {\n assetStore.remove(record.hash)\n removed += 1\n }\n }\n log.info({ checked, removed, ttlMs }, 'Asset TTL sweep completed.')\n}\n\nfunction buildAssetDescriptor(record: AssetRecord): AssetDescriptor {\n const filename = buildAssetFilename(record.hash, record.mimeType)\n return {\n hash: record.hash,\n url: `${assetHttpServer.getBaseUrl()}/assets/${filename}`,\n mimeType: record.mimeType,\n size: record.size,\n width: record.metadata?.width,\n height: record.metadata?.height\n }\n}\n\nfunction 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 let requestId: string | undefined\n try {\n const parsedArgs = schema.parse(args)\n const activeExt = extensions.find((e) => e.active)\n if (!activeExt) {\n throw createCodedError(\n TEMPAD_MCP_ERROR_CODES.NO_ACTIVE_EXTENSION,\n 'No active TemPad Dev extension available.'\n )\n }\n\n const registration = register<Result>(activeExt.id, toolTimeoutMs)\n requestId = registration.requestId\n\n const message: ToolCallMessage = {\n type: 'toolCall',\n id: registration.requestId,\n payload: {\n name: tool.name,\n args: parsedArgs\n }\n }\n activeExt.ws.send(JSON.stringify(message))\n log.info(\n { tool: tool.name, req: registration.requestId, extId: activeExt.id },\n 'Forwarded tool call.'\n )\n\n const payload = await registration.promise\n return createToolResponse(tool.name, payload)\n } catch (error) {\n const normalized = coerceToolError(error)\n log.error(\n {\n tool: tool.name,\n req: requestId,\n code: getRecordProperty(normalized, 'code'),\n message: normalized.message\n },\n 'Tool invocation failed.'\n )\n return createToolErrorResponse(tool.name, normalized)\n }\n }\n\n registerToolFn(\n tool.name,\n {\n description: tool.description,\n inputSchema: schema as unknown as McpInputSchema\n },\n handler\n )\n}\n\nfunction registerLocalTool(tool: HubOnlyTool): void {\n const schema = tool.parameters\n const handler = tool.handler\n\n const registerToolFn = mcp.registerTool.bind(mcp) as (\n name: string,\n options: { description: string; inputSchema: ZodType; outputSchema?: ZodType },\n handler: (args: unknown) => Promise<CallToolResult>\n ) => unknown\n\n const registrationOptions: {\n description: string\n inputSchema: McpInputSchema\n outputSchema?: McpOutputSchema\n } = {\n description: tool.description,\n inputSchema: schema as unknown as McpInputSchema\n }\n\n if (tool.outputSchema) {\n registrationOptions.outputSchema = tool.outputSchema as unknown as McpOutputSchema\n }\n\n const registerHandler = async (args: unknown) => {\n try {\n const parsed = schema.parse(args)\n return await handler(parsed)\n } catch (error) {\n log.error({ tool: tool.name, error }, 'Local tool invocation failed.')\n return createToolErrorResponse(tool.name, error)\n }\n }\n\n registerToolFn(tool.name, registrationOptions, registerHandler)\n}\n\nfunction createToolResponse<Name extends ToolName>(\n toolName: Name,\n payload: ToolResultMap[Name]\n): ToolResponse {\n const definition = getToolDefinition(toolName)\n if (definition && hasFormatter(definition)) {\n try {\n const formatter = definition.format as (input: ToolResultMap[Name]) => ToolResponse\n return formatter(payload)\n } catch (error) {\n log.warn({ tool: toolName, error }, 'Failed to format tool result; returning raw payload.')\n return coercePayloadToToolResponse(payload)\n }\n }\n\n return coercePayloadToToolResponse(payload)\n}\n\nasync function handleGetAssets({ hashes }: GetAssetsParametersInput): Promise<ToolResponse> {\n if (hashes.length > 100) {\n throw new Error('Too many hashes requested. Limit is 100.')\n }\n const unique = Array.from(new Set(hashes))\n const records = assetStore.getMany(unique).filter((record) => {\n if (existsSync(record.filePath)) return true\n assetStore.remove(record.hash, { removeFile: false })\n return false\n })\n const found = new Set(records.map((record) => record.hash))\n const payload: GetAssetsResult = GetAssetsResultSchema.parse({\n assets: records.map((record) => buildAssetDescriptor(record)),\n missing: unique.filter((hash) => !found.has(hash))\n })\n\n const summary: string[] = []\n summary.push(\n payload.assets.length\n ? `Resolved ${payload.assets.length} asset${payload.assets.length === 1 ? '' : 's'}.`\n : 'No assets were resolved for the requested hashes.'\n )\n if (payload.missing.length) {\n summary.push(`Missing: ${payload.missing.join(', ')}`)\n }\n summary.push('Download bytes from each asset.url.')\n\n const content = [\n {\n type: 'text' as const,\n text: summary.join('\\n')\n }\n ]\n\n return {\n content,\n structuredContent: payload\n }\n}\n\nfunction getActiveId(): string | null {\n return extensions.find((e) => e.active)?.id ?? null\n}\n\nfunction setActive(targetId: string | null): void {\n extensions.forEach((e) => {\n e.active = targetId !== null && e.id === targetId\n })\n}\n\nfunction clearAutoActivateTimer(): void {\n if (autoActivateTimer) {\n clearTimeout(autoActivateTimer)\n autoActivateTimer = null\n }\n}\n\nfunction scheduleAutoActivate(): void {\n clearAutoActivateTimer()\n\n if (extensions.length !== 1 || getActiveId()) {\n return\n }\n\n const target = extensions[0]\n autoActivateTimer = setTimeout(() => {\n autoActivateTimer = null\n if (extensions.length === 1 && !getActiveId()) {\n setActive(target.id)\n log.info({ id: target.id }, 'Auto-activated sole extension after grace period.')\n broadcastState()\n }\n }, autoActivateGraceMs)\n}\n\nfunction unrefTimer(timer: TimeoutHandle): void {\n if (typeof timer === 'object' && timer !== null) {\n const handle = timer as NodeJS.Timeout\n if (typeof handle.unref === 'function') {\n handle.unref()\n }\n }\n}\n\nfunction broadcastState(): void {\n const activeId = getActiveId()\n const message: StateMessage = {\n type: 'state',\n activeId,\n count: extensions.length,\n port: selectedWsPort,\n assetServerUrl: assetHttpServer.getBaseUrl()\n }\n extensions.forEach((ext) => ext.ws.send(JSON.stringify(message)))\n log.debug({ activeId, count: extensions.length }, 'Broadcasted state.')\n}\n\nfunction rawDataToBuffer(raw: RawData): Buffer {\n if (typeof raw === 'string') return Buffer.from(raw)\n if (Buffer.isBuffer(raw)) return raw\n if (raw instanceof ArrayBuffer) return Buffer.from(raw)\n return Buffer.concat(raw)\n}\n\nfunction shutdown(): void {\n log.info('Hub is shutting down...')\n 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 const normalized = coerceToolError(error)\n log.warn(\n {\n toolReq: id,\n extId: ext.id,\n code: getRecordProperty(normalized, 'code'),\n message: normalized.message\n },\n 'Received tool error from extension.'\n )\n reject(id, normalized)\n } else {\n resolve(id, payload)\n }\n break\n }\n }\n })\n\n ws.on('close', () => {\n const index = extensions.findIndex((e) => e.id === ext.id)\n if (index > -1) extensions.splice(index, 1)\n\n log.info({ id: ext.id }, `Extension disconnected. Remaining: ${extensions.length}`)\n cleanupForExtension(ext.id)\n\n if (ext.active) {\n log.warn({ id: ext.id }, 'Active extension disconnected.')\n setActive(null)\n }\n\n broadcastState()\n scheduleAutoActivate()\n })\n})\n\nlog.info({ port: selectedWsPort }, 'WebSocket server ready.')\n\nprocess.on('SIGINT', shutdown)\nprocess.on('SIGTERM', shutdown)\n"],"mappings":"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"}
|
|
@@ -6,12 +6,18 @@ 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.3",
|
|
10
10
|
description: "MCP server for TemPad Dev.",
|
|
11
|
+
repository: {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/ecomfe/tempad-dev.git",
|
|
14
|
+
"directory": "packages/mcp-server"
|
|
15
|
+
},
|
|
11
16
|
bin: "dist/cli.mjs",
|
|
12
17
|
files: ["README.md", "dist/**/*"],
|
|
13
18
|
type: "module",
|
|
14
19
|
main: "dist/cli.mjs",
|
|
20
|
+
publishConfig: { "access": "public" },
|
|
15
21
|
scripts: {
|
|
16
22
|
"build": "tsdown",
|
|
17
23
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
@@ -40,7 +46,8 @@ var package_default = {
|
|
|
40
46
|
"@types/ws": "^8.5.12",
|
|
41
47
|
"tsdown": "^0.20.0",
|
|
42
48
|
"typescript": "^5.9.3",
|
|
43
|
-
"unplugin-raw": "^0.6.3"
|
|
49
|
+
"unplugin-raw": "^0.6.3",
|
|
50
|
+
"vitest": "4.0.18"
|
|
44
51
|
}
|
|
45
52
|
};
|
|
46
53
|
|
|
@@ -107,4 +114,4 @@ const SOCK_PATH = resolveSockPath();
|
|
|
107
114
|
|
|
108
115
|
//#endregion
|
|
109
116
|
export { SOCK_PATH as a, log as c, RUNTIME_DIR as i, LOCK_PATH as n, ensureDir as o, PACKAGE_VERSION as r, ensureFile as s, ASSET_DIR as t };
|
|
110
|
-
//# sourceMappingURL=shared-
|
|
117
|
+
//# sourceMappingURL=shared-Bubiv0fy.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared-
|
|
1
|
+
{"version":3,"file":"shared-Bubiv0fy.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,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tempad-dev/mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "MCP server for TemPad Dev.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/ecomfe/tempad-dev.git",
|
|
8
|
+
"directory": "packages/mcp-server"
|
|
9
|
+
},
|
|
5
10
|
"bin": "dist/cli.mjs",
|
|
6
11
|
"files": [
|
|
7
12
|
"README.md",
|
|
@@ -9,6 +14,21 @@
|
|
|
9
14
|
],
|
|
10
15
|
"type": "module",
|
|
11
16
|
"main": "dist/cli.mjs",
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsdown",
|
|
22
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
23
|
+
"test": "vitest --config vitest.config.ts",
|
|
24
|
+
"test:run": "vitest run --config vitest.config.ts",
|
|
25
|
+
"test:coverage": "vitest run --config vitest.config.ts --coverage",
|
|
26
|
+
"lint": "eslint . --ext .ts,.mts,.cts,.js,.mjs,.cjs",
|
|
27
|
+
"lint:fix": "eslint . --ext .ts,.mts,.cts,.js,.mjs,.cjs --fix",
|
|
28
|
+
"format": "oxfmt --ignore-path ../../.gitignore",
|
|
29
|
+
"format:check": "oxfmt --ignore-path ../../.gitignore --check",
|
|
30
|
+
"prepublishOnly": "pnpm run build"
|
|
31
|
+
},
|
|
12
32
|
"dependencies": {
|
|
13
33
|
"@modelcontextprotocol/sdk": "^1.24.0",
|
|
14
34
|
"nanoid": "^5.1.6",
|
|
@@ -19,23 +39,13 @@
|
|
|
19
39
|
"zod": "^4.1.12"
|
|
20
40
|
},
|
|
21
41
|
"devDependencies": {
|
|
22
|
-
"@tempad-dev/shared": "
|
|
42
|
+
"@tempad-dev/shared": "workspace:^0.1.0",
|
|
23
43
|
"@types/node": "^24.0.0",
|
|
24
44
|
"@types/proper-lockfile": "^4.1.4",
|
|
25
45
|
"@types/ws": "^8.5.12",
|
|
26
46
|
"tsdown": "^0.20.0",
|
|
27
47
|
"typescript": "^5.9.3",
|
|
28
|
-
"unplugin-raw": "^0.6.3"
|
|
29
|
-
|
|
30
|
-
"scripts": {
|
|
31
|
-
"build": "tsdown",
|
|
32
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
33
|
-
"test": "vitest --config vitest.config.ts",
|
|
34
|
-
"test:run": "vitest run --config vitest.config.ts",
|
|
35
|
-
"test:coverage": "vitest run --config vitest.config.ts --coverage",
|
|
36
|
-
"lint": "eslint . --ext .ts,.mts,.cts,.js,.mjs,.cjs",
|
|
37
|
-
"lint:fix": "eslint . --ext .ts,.mts,.cts,.js,.mjs,.cjs --fix",
|
|
38
|
-
"format": "oxfmt --ignore-path ../../.gitignore",
|
|
39
|
-
"format:check": "oxfmt --ignore-path ../../.gitignore --check"
|
|
48
|
+
"unplugin-raw": "^0.6.3",
|
|
49
|
+
"vitest": "4.0.18"
|
|
40
50
|
}
|
|
41
|
-
}
|
|
51
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024-Present Baidu EFE team
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|