@tempad-dev/mcp 0.3.11 → 0.3.12

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/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-H4sL6Ltt.mjs";
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-cWtEAcwU.mjs";
3
3
  import { spawn } from "node:child_process";
4
4
  import { connect } from "node:net";
5
5
  import { join } from "node:path";
package/dist/hub.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as SOCK_PATH, c as log, i as RUNTIME_DIR, o as ensureDir, r as PACKAGE_VERSION, s as ensureFile, t as ASSET_DIR } from "./shared-H4sL6Ltt.mjs";
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-cWtEAcwU.mjs";
2
2
  import { createServer } from "node:net";
3
3
  import { join } from "node:path";
4
4
  import { URL as URL$1 } from "node:url";
@@ -3893,6 +3893,15 @@ const MCP_ASSET_URI_PREFIX = "asset://tempad/";
3893
3893
  const MCP_ASSET_URI_TEMPLATE = `${MCP_ASSET_URI_PREFIX}{hash}`;
3894
3894
  const MCP_HASH_HEX_LENGTH = 8;
3895
3895
  const MCP_HASH_PATTERN = new RegExp(`^[a-f0-9]{${MCP_HASH_HEX_LENGTH}}$`, "i");
3896
+ const TEMPAD_MCP_ERROR_CODES = {
3897
+ NO_ACTIVE_EXTENSION: "NO_ACTIVE_EXTENSION",
3898
+ EXTENSION_TIMEOUT: "EXTENSION_TIMEOUT",
3899
+ EXTENSION_DISCONNECTED: "EXTENSION_DISCONNECTED",
3900
+ INVALID_SELECTION: "INVALID_SELECTION",
3901
+ NODE_NOT_VISIBLE: "NODE_NOT_VISIBLE",
3902
+ ASSET_SERVER_NOT_CONFIGURED: "ASSET_SERVER_NOT_CONFIGURED",
3903
+ TRANSPORT_NOT_CONNECTED: "TRANSPORT_NOT_CONNECTED"
3904
+ };
3896
3905
  const RegisteredMessageSchema = object({
3897
3906
  type: literal("registered"),
3898
3907
  id: string()
@@ -4162,7 +4171,6 @@ function createAssetHttpServer(store) {
4162
4171
  if (existingPath !== filePath) try {
4163
4172
  renameSync(existingPath, filePath);
4164
4173
  existing.filePath = filePath;
4165
- existingPath = filePath;
4166
4174
  } catch (error) {
4167
4175
  log.warn({
4168
4176
  error,
@@ -4433,18 +4441,23 @@ function createAssetStore(options = {}) {
4433
4441
 
4434
4442
  //#endregion
4435
4443
  //#region src/instructions.md?raw
4436
- var instructions_default = "You are connected to a Figma design file via the MCP server. Convert design elements into production code, preserving design intent while fitting the user’s codebase conventions.\n\n- Start from `get_code` as the baseline (omit `nodeId` to use the current single selection), then refactor to match project conventions (components, styling system, file structure, naming). Treat `get_code` as authoritative.\n- If `get_code` returns a `depth-cap` warning, call `get_code` again once per listed `nodeId` to fetch each omitted subtree.\n- Layout confidence:\n - If `data-hint-auto-layout=\"inferred\"` appears, layout may be less certain. You can call `get_structure` or `get_screenshot` with a specific `nodeId` (both accept `nodeId`, otherwise they use the current single selection) to improve layout understanding, but keep `get_code` as the baseline.\n - Otherwise, assume layout is explicit and implement directly from `get_code`.\n- If `data-hint-design-component` plus repetition supports it, extract reusable components/variants aligned with project patterns. Do not preserve hint strings in output.\n- Tokens: `get_code.tokens` is a single map keyed by canonical token name. Multi‑mode values use `${collectionName}:${modeName}` keys. Nodes with explicit overrides include `data-hint-variable-mode=\"Collection=Mode;Collection=Mode\"`; use this to pick the correct mode for a given node. Collection names are assumed unique.\n- Assets: follow the project’s existing conventions/practices (icon system, asset pipeline, import/path rules, optimization) to decide how to represent and reference assets. If `get_code` uses resource URIs, you may replace them with the project’s canonical references when appropriate without changing rendering.\n- Do not output any `data-*` attributes returned by `get_code` (they are hints only).\n- For SVG/vector assets: use the exact provided asset, preserving `path` data, `viewBox`, and full SVG structure. Never redraw or approximate vectors.\n";
4444
+ 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` / `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";
4437
4445
 
4438
4446
  //#endregion
4439
4447
  //#region src/request.ts
4440
4448
  const pendingCalls = /* @__PURE__ */ new Map();
4449
+ function createToolError(code, message) {
4450
+ const err = new Error(message);
4451
+ err.code = code;
4452
+ return err;
4453
+ }
4441
4454
  function register(extensionId, timeout) {
4442
4455
  const requestId = nanoid();
4443
4456
  return {
4444
4457
  promise: new Promise((resolve$1, reject$1) => {
4445
4458
  const timer = setTimeout(() => {
4446
4459
  pendingCalls.delete(requestId);
4447
- reject$1(/* @__PURE__ */ new Error(`Extension did not respond within ${timeout / 1e3}s.`));
4460
+ reject$1(createToolError(TEMPAD_MCP_ERROR_CODES.EXTENSION_TIMEOUT, `Extension did not respond within ${timeout / 1e3}s.`));
4448
4461
  }, timeout);
4449
4462
  pendingCalls.set(requestId, {
4450
4463
  resolve: resolve$1,
@@ -4479,7 +4492,7 @@ function cleanupForExtension(extensionId) {
4479
4492
  const { timer, reject: fail, extensionId: extId } = call;
4480
4493
  if (extId === extensionId) {
4481
4494
  clearTimeout(timer);
4482
- fail(/* @__PURE__ */ new Error("Extension disconnected before providing a result."));
4495
+ fail(createToolError(TEMPAD_MCP_ERROR_CODES.EXTENSION_DISCONNECTED, "Extension disconnected before providing a result."));
4483
4496
  pendingCalls.delete(reqId);
4484
4497
  log.warn({
4485
4498
  reqId,
@@ -4509,44 +4522,71 @@ function hubTool(definition) {
4509
4522
  const TOOL_DEFS = [
4510
4523
  extTool({
4511
4524
  name: "get_code",
4512
- description: "Get a high-fidelity code snapshot for a nodeId/current selection (omit nodeId to use the current single selection), including assets/tokens and codegen plugin/config. Start here, then refactor into your component/styling/file/naming conventions; strip any data-* hints. Treat get_code as authoritative. If warnings include depth-cap, call get_code again once per listed nodeId to fetch each omitted subtree. If any data-hint-auto-layout=\"inferred\" appears, you may call get_structure or get_screenshot with a nodeId (both accept nodeId; omit it to use the current single selection) to confirm hierarchy/overlap, but keep get_code as the baseline. Use data-hint-design-component plus repetition to decide on reusable components. Replace resource URIs with your canonical asset system as needed. Tokens: get_code.tokens is a single map keyed by canonical token name; multi-mode values use `${collectionName}:${modeName}` keys. Nodes with explicit mode overrides include data-hint-variable-mode=\"Collection=Mode;Collection=Mode\". Collection names are assumed unique.",
4525
+ 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/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).",
4513
4526
  parameters: GetCodeParametersSchema,
4514
4527
  target: "extension",
4515
4528
  format: createCodeToolResponse
4516
4529
  }),
4517
4530
  extTool({
4518
4531
  name: "get_token_defs",
4519
- description: "Resolve canonical token names to values (including modes) for tokens referenced by get_code. Use this to map into your design token/theming system, including responsive tokens.",
4532
+ description: "Resolve canonical token names to literal values (optionally including all modes) for tokens referenced by get_code.",
4520
4533
  parameters: GetTokenDefsParametersSchema,
4521
4534
  target: "extension",
4522
4535
  exposed: false
4523
4536
  }),
4524
4537
  extTool({
4525
4538
  name: "get_screenshot",
4526
- description: "Capture a rendered screenshot for a nodeId/current selection for visual verification. Useful for confirming layering/overlap/masks/shadows/translucency when auto-layout hints are none/inferred.",
4539
+ description: "Capture a rendered PNG screenshot for nodeId/current single selection for visual verification (layering/overlap/masks/effects).",
4527
4540
  parameters: GetScreenshotParametersSchema,
4528
4541
  target: "extension",
4529
4542
  format: createScreenshotToolResponse
4530
4543
  }),
4531
4544
  extTool({
4532
4545
  name: "get_structure",
4533
- description: "Get a structural + geometry outline for a nodeId/current selection to understand hierarchy and layout intent. Use when auto-layout hints are none/inferred or you need explicit bounds for refactors/component extraction.",
4546
+ description: "Get a structural + geometry outline for nodeId/current single selection to understand hierarchy and layout intent.",
4534
4547
  parameters: GetStructureParametersSchema,
4535
4548
  target: "extension"
4536
4549
  }),
4537
4550
  hubTool({
4538
4551
  name: "get_assets",
4539
- description: "Resolve asset hashes to downloadable URLs/URIs for assets referenced by get_code, preserving vectors exactly. Pull bytes before routing through your asset/icon pipeline.",
4552
+ description: "Resolve asset hashes to downloadable URLs/URIs for assets referenced by tool responses (preserve vectors exactly).",
4540
4553
  parameters: GetAssetsParametersSchema,
4541
4554
  target: "hub",
4542
4555
  outputSchema: GetAssetsResultSchema,
4543
4556
  exposed: false
4544
4557
  })
4545
4558
  ];
4559
+ function extractToolErrorCode(error) {
4560
+ if (!error || typeof error !== "object") return void 0;
4561
+ if ("code" in error && typeof error.code === "string") return error.code;
4562
+ if ("cause" in error) {
4563
+ const cause = error.cause;
4564
+ if (cause && typeof cause === "object") {
4565
+ const causeCode = cause.code;
4566
+ if (typeof causeCode === "string") return causeCode;
4567
+ }
4568
+ }
4569
+ }
4570
+ function extractToolErrorMessage(error) {
4571
+ if (error instanceof Error) return error.message || "Unknown error occurred.";
4572
+ if (typeof error === "string") return error;
4573
+ if (error && typeof error === "object") {
4574
+ const candidate = error;
4575
+ if (typeof candidate.message === "string" && candidate.message.trim()) return candidate.message;
4576
+ }
4577
+ return "Unknown error occurred.";
4578
+ }
4546
4579
  function createToolErrorResponse(toolName, error) {
4580
+ const message = extractToolErrorMessage(error);
4581
+ const code = extractToolErrorCode(error);
4547
4582
  return { content: [{
4548
4583
  type: "text",
4549
- text: `Tool "${toolName}" failed: ${error instanceof Error ? error.message || "Unknown error occurred." : typeof error === "string" ? error : "Unknown error occurred."}`
4584
+ text: `Tool "${toolName}" failed: ${message}${(() => {
4585
+ const help = [];
4586
+ 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.");
4587
+ 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.");
4588
+ return help.length ? `\n\n${help.join("\n")}` : "";
4589
+ })()}`
4550
4590
  }] };
4551
4591
  }
4552
4592
  function formatBytes$1(bytes) {
@@ -4662,6 +4702,30 @@ function enrichToolDefinition(tool) {
4662
4702
  }
4663
4703
  }
4664
4704
  const TOOL_DEFINITIONS = TOOL_DEFS.map((tool) => enrichToolDefinition(tool));
4705
+ function createCodedError(code, message) {
4706
+ const err = new Error(message);
4707
+ err.code = code;
4708
+ return err;
4709
+ }
4710
+ function coerceToolError(error) {
4711
+ if (error instanceof Error) return error;
4712
+ if (typeof error === "string") return new Error(error);
4713
+ if (error && typeof error === "object") {
4714
+ const candidate = error;
4715
+ const message = typeof candidate.message === "string" ? candidate.message : safeStringify(error);
4716
+ const err = new Error(message);
4717
+ if (typeof candidate.code === "string") err.code = candidate.code;
4718
+ return err;
4719
+ }
4720
+ return new Error(String(error));
4721
+ }
4722
+ function safeStringify(input) {
4723
+ try {
4724
+ return JSON.stringify(input);
4725
+ } catch {
4726
+ return String(input);
4727
+ }
4728
+ }
4665
4729
  function hasFormatter(tool) {
4666
4730
  return tool.target === "extension" && "format" in tool;
4667
4731
  }
@@ -4793,7 +4857,7 @@ function registerProxiedTool(tool) {
4793
4857
  try {
4794
4858
  const parsedArgs = schema.parse(args);
4795
4859
  const activeExt = extensions.find((e) => e.active);
4796
- if (!activeExt) throw new Error("No active TemPad Dev extension available.");
4860
+ if (!activeExt) throw createCodedError(TEMPAD_MCP_ERROR_CODES.NO_ACTIVE_EXTENSION, "No active TemPad Dev extension available.");
4797
4861
  const { promise, requestId } = register(activeExt.id, toolTimeoutMs);
4798
4862
  const message = {
4799
4863
  type: "toolCall",
@@ -5098,7 +5162,7 @@ wss.on("connection", (ws) => {
5098
5162
  break;
5099
5163
  case "toolResult": {
5100
5164
  const { id, payload, error } = msg;
5101
- if (error) reject(id, error instanceof Error ? error : new Error(String(error)));
5165
+ if (error) reject(id, coerceToolError(error));
5102
5166
  else resolve(id, payload);
5103
5167
  break;
5104
5168
  }