figma-spec-mcp 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +158 -0
  3. package/dist/figma/client.d.ts +53 -0
  4. package/dist/figma/client.d.ts.map +1 -0
  5. package/dist/figma/client.js +193 -0
  6. package/dist/figma/client.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +32 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/shared.d.ts +9 -0
  12. package/dist/shared.d.ts.map +1 -0
  13. package/dist/shared.js +10 -0
  14. package/dist/shared.js.map +1 -0
  15. package/dist/tools/analyze-frame.d.ts +9 -0
  16. package/dist/tools/analyze-frame.d.ts.map +1 -0
  17. package/dist/tools/analyze-frame.js +117 -0
  18. package/dist/tools/analyze-frame.js.map +1 -0
  19. package/dist/tools/audit-accessibility.d.ts +20 -0
  20. package/dist/tools/audit-accessibility.d.ts.map +1 -0
  21. package/dist/tools/audit-accessibility.js +186 -0
  22. package/dist/tools/audit-accessibility.js.map +1 -0
  23. package/dist/tools/bridge-to-codebase.d.ts +23 -0
  24. package/dist/tools/bridge-to-codebase.d.ts.map +1 -0
  25. package/dist/tools/bridge-to-codebase.js +154 -0
  26. package/dist/tools/bridge-to-codebase.js.map +1 -0
  27. package/dist/tools/diff-versions.d.ts +23 -0
  28. package/dist/tools/diff-versions.d.ts.map +1 -0
  29. package/dist/tools/diff-versions.js +133 -0
  30. package/dist/tools/diff-versions.js.map +1 -0
  31. package/dist/tools/export-images.d.ts +26 -0
  32. package/dist/tools/export-images.d.ts.map +1 -0
  33. package/dist/tools/export-images.js +53 -0
  34. package/dist/tools/export-images.js.map +1 -0
  35. package/dist/tools/extract-design-tokens.d.ts +20 -0
  36. package/dist/tools/extract-design-tokens.d.ts.map +1 -0
  37. package/dist/tools/extract-design-tokens.js +230 -0
  38. package/dist/tools/extract-design-tokens.js.map +1 -0
  39. package/dist/tools/extract-flows.d.ts +20 -0
  40. package/dist/tools/extract-flows.d.ts.map +1 -0
  41. package/dist/tools/extract-flows.js +127 -0
  42. package/dist/tools/extract-flows.js.map +1 -0
  43. package/dist/tools/extract-variants.d.ts +20 -0
  44. package/dist/tools/extract-variants.d.ts.map +1 -0
  45. package/dist/tools/extract-variants.js +90 -0
  46. package/dist/tools/extract-variants.js.map +1 -0
  47. package/dist/tools/inspect-layout.d.ts +26 -0
  48. package/dist/tools/inspect-layout.d.ts.map +1 -0
  49. package/dist/tools/inspect-layout.js +271 -0
  50. package/dist/tools/inspect-layout.js.map +1 -0
  51. package/dist/tools/map-to-unity.d.ts +26 -0
  52. package/dist/tools/map-to-unity.d.ts.map +1 -0
  53. package/dist/tools/map-to-unity.js +173 -0
  54. package/dist/tools/map-to-unity.js.map +1 -0
  55. package/dist/tools/register-all.d.ts +12 -0
  56. package/dist/tools/register-all.d.ts.map +1 -0
  57. package/dist/tools/register-all.js +13 -0
  58. package/dist/tools/register-all.js.map +1 -0
  59. package/dist/tools/registry.d.ts +13 -0
  60. package/dist/tools/registry.d.ts.map +1 -0
  61. package/dist/tools/registry.js +26 -0
  62. package/dist/tools/registry.js.map +1 -0
  63. package/dist/tools/resolve-components.d.ts +20 -0
  64. package/dist/tools/resolve-components.d.ts.map +1 -0
  65. package/dist/tools/resolve-components.js +97 -0
  66. package/dist/tools/resolve-components.js.map +1 -0
  67. package/dist/tools/simplify-context.d.ts +26 -0
  68. package/dist/tools/simplify-context.d.ts.map +1 -0
  69. package/dist/tools/simplify-context.js +198 -0
  70. package/dist/tools/simplify-context.js.map +1 -0
  71. package/dist/types/figma.d.ts +132 -0
  72. package/dist/types/figma.d.ts.map +1 -0
  73. package/dist/types/figma.js +2 -0
  74. package/dist/types/figma.js.map +1 -0
  75. package/dist/types/tools.d.ts +397 -0
  76. package/dist/types/tools.d.ts.map +1 -0
  77. package/dist/types/tools.js +2 -0
  78. package/dist/types/tools.js.map +1 -0
  79. package/package.json +71 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zafer Dace
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.
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # figma-spec-mcp
2
+
3
+ ![Version: 1.0.0-beta.1](https://img.shields.io/badge/version-1.0.0--beta.1-orange)
4
+ ![License: MIT](https://img.shields.io/badge/license-MIT-blue)
5
+ ![MCP Compatible](https://img.shields.io/badge/MCP-compatible-brightgreen)
6
+
7
+ <p align="center">
8
+ <img src="assets/banner.png" alt="figma-spec-mcp — Bridge Figma to Game Engines" width="720" />
9
+ </p>
10
+
11
+ **Engineering-grade Figma specs for AI agents.** Layout audit, design tokens, accessibility checks, prototype flows, version diffs, and platform-ready mapping for Unity, React, SwiftUI, and more — all through MCP.
12
+
13
+ Works with **any MCP-compatible client**: Claude Code, Claude Desktop, Cursor, VS Code + Copilot, Windsurf, Cline, Continue.dev, Zed.
14
+
15
+ > **Security note:** Your Figma access token is passed as a tool argument. Never commit it to version control. Use environment variables or your AI client's secret management to supply it at runtime.
16
+
17
+ ---
18
+
19
+ ## Quick Start
20
+
21
+ **1. Get a Figma access token**
22
+ → [figma.com/developers/api#access-tokens](https://www.figma.com/developers/api#access-tokens)
23
+
24
+ **2. Add to your MCP config** (Claude Desktop, Cursor, VS Code, or any MCP client):
25
+ ```json
26
+ {
27
+ "mcpServers": {
28
+ "figma-spec-mcp": {
29
+ "command": "npx",
30
+ "args": ["-y", "figma-spec-mcp@beta"]
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ **3. Restart your AI client and use the tools.**
37
+
38
+ Your file key is in the Figma URL: `figma.com/file/<FILE_KEY>/...`
39
+
40
+ ---
41
+
42
+ ## Why figma-spec-mcp?
43
+
44
+ Most Figma MCP tools forward raw API responses. `figma-spec-mcp` adds stable envelopes, focused derivations, and reusable engineering outputs:
45
+
46
+ - Deterministic JSON responses with a shared response envelope
47
+ - Built-in disk cache with freshness metadata on every result
48
+ - Source traceability for tokens, mappings, and extracted relationships
49
+ - Platform-ready outputs for Unity, codebase mapping, and image export workflows
50
+
51
+ ---
52
+
53
+ ## Tools
54
+
55
+ - `inspect_layout` — Inspects a Figma frame and returns hierarchy, layout structure, spacing, constraints, annotations, and basic accessibility warnings.
56
+ - `extract_design_tokens` — Extracts color, typography, and spacing tokens from a Figma file and exports them as CSS variables, Style Dictionary JSON, or Tailwind config.
57
+ - `map_to_unity` — Produces a Unity UGUI-oriented mapping with RectTransform data, layout groups, suggested components, notes, and warnings.
58
+ - `resolve_components` — Resolves instance nodes to their backing component definitions and returns source file and source node references.
59
+ - `extract_flows` — Extracts prototype transitions from a page or frame and returns directed flow connections plus a deterministic frame order.
60
+ - `bridge_to_codebase` — Scans a local project and maps Figma component names to likely implementation files using filename heuristics.
61
+ - `diff_versions` — Compares two Figma file versions and reports added, removed, and modified nodes.
62
+ - `extract_variants` — Reads a component set and returns structured variant metadata, parsed properties, dimensions, layout details, fills, and typography.
63
+ - `export_images` — Exports one or more Figma nodes as PNG, JPG, SVG, or PDF and returns the image URLs.
64
+ - `audit_accessibility` — Audits a frame for accessibility issues such as contrast, touch targets, font size, missing alt text, and color-only distinctions.
65
+ - `simplify_context` — Produces a token-efficient, LLM-oriented summary tree by collapsing wrappers, grouping repeated nodes, and truncating deep hierarchies.
66
+
67
+ ---
68
+
69
+ ## Features
70
+
71
+ ### v0.1 — Core
72
+ - `inspect_layout`, `extract_design_tokens`, `map_to_unity`
73
+ - Disk cache with SHA-256 keying and 1h TTL
74
+
75
+ ### v0.2 — Intelligence
76
+ - Token name preservation from Figma styles
77
+ - Depth-limited chunking for large files
78
+ - Mixed/rich text runs extraction
79
+ - Annotation extraction, framework-aware hints (Unity, React, SwiftUI, Web)
80
+
81
+ ### v0.3 — Workflows
82
+ - `resolve_components` — multi-file component traversal
83
+ - `extract_flows` — prototype flow graph
84
+ - `bridge_to_codebase` — Figma → repo file matching
85
+ - `diff_versions` — structured version diff
86
+ - `extract_variants` — component set batch extraction
87
+
88
+ ### v0.4 — Quality & DX
89
+ - `export_images` — PNG/JPG/SVG/PDF export
90
+ - `audit_accessibility` — WCAG 2.1 contrast, touch targets, font size
91
+ - `simplify_context` — AI-optimized, token-efficient output
92
+ - Tool registry pattern for easy contribution
93
+ - Rate limit handling (429 + Retry-After)
94
+
95
+ ---
96
+
97
+ ## Response Shape
98
+
99
+ All 11 tools return a consistent top-level envelope:
100
+
101
+ ```json
102
+ {
103
+ "schema_version": "0.1.0",
104
+ "source": { "file_key": "abc123", "node_id": "1:23" },
105
+ "freshness": {
106
+ "fresh": true,
107
+ "timestamp": "2026-03-26T10:00:00.000Z",
108
+ "ttl_ms": 3600000
109
+ },
110
+ "warnings": [],
111
+ "data": {}
112
+ }
113
+ ```
114
+
115
+ Tool-specific results live in `data`, and most tools also include low-level cache metadata there.
116
+
117
+ ---
118
+
119
+ ## Caching
120
+
121
+ Responses are cached to disk (default: `$TMPDIR/figma-spec-mcp-cache/`) by file key and request shape with a 1-hour TTL. Cache metadata is included in responses:
122
+
123
+ ```json
124
+ "cache": {
125
+ "cachedAt": "2026-03-26T10:00:00.000Z",
126
+ "expiresAt": "2026-03-26T11:00:00.000Z",
127
+ "fileVersion": "123456789",
128
+ "fresh": true
129
+ }
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Development
135
+
136
+ ```bash
137
+ git clone https://github.com/zaferdace/figma-spec-mcp
138
+ cd figma-spec-mcp
139
+ npm install
140
+ npm run build
141
+ node dist/index.js
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Roadmap
147
+
148
+ - [ ] Export to React Native StyleSheet
149
+ - [ ] Export to Flutter ThemeData
150
+ - [ ] Semantic component detection (button/card/nav inference)
151
+ - [ ] Webhook-triggered spec generation
152
+ - [ ] Plugin API companion for live document access
153
+
154
+ ---
155
+
156
+ ## License
157
+
158
+ MIT © Zafer Dace
@@ -0,0 +1,53 @@
1
+ import type { FigmaComponentResponse, FigmaFileResponse, FigmaNode } from "../types/figma.js";
2
+ export interface CacheMetadata {
3
+ cachedAt: string;
4
+ expiresAt: string;
5
+ fileVersion: string;
6
+ fresh: boolean;
7
+ }
8
+ export interface CachedResult<T> {
9
+ data: T;
10
+ cache: CacheMetadata;
11
+ }
12
+ export interface FigmaClientOptions {
13
+ ttlMs?: number;
14
+ cacheDir?: string;
15
+ disableCache?: boolean;
16
+ }
17
+ export declare class FigmaRateLimitError extends Error {
18
+ readonly retryAfterMs: number;
19
+ constructor(retryAfterMs: number);
20
+ }
21
+ export declare class FigmaClient {
22
+ private readonly accessToken;
23
+ private readonly ttlMs;
24
+ private readonly cacheDir;
25
+ private readonly disableCache;
26
+ constructor(accessToken: string, options?: FigmaClientOptions);
27
+ private cacheKey;
28
+ private cachePath;
29
+ private getFileCacheKey;
30
+ private getFileNodesCacheKey;
31
+ private getImagesCacheKey;
32
+ private readCache;
33
+ private writeCache;
34
+ invalidateCache(fileKey: string, nodeId?: string): void;
35
+ private buildCacheMetadata;
36
+ private buildFreshMetadata;
37
+ private request;
38
+ private parseRetryAfterMs;
39
+ getFile(fileKey: string, version?: string): Promise<CachedResult<FigmaFileResponse>>;
40
+ getFileNodes(fileKey: string, nodeIds: string[], fileVersion?: string): Promise<CachedResult<{
41
+ nodes: Record<string, {
42
+ document: FigmaNode;
43
+ } | null>;
44
+ }>>;
45
+ getStyles(fileKey: string): Promise<CachedResult<{
46
+ styles: Record<string, unknown>;
47
+ }>>;
48
+ getComponent(componentKey: string): Promise<CachedResult<FigmaComponentResponse>>;
49
+ getImages(fileKey: string, nodeIds: string[], format: "png" | "jpg" | "svg" | "pdf", scale: number): Promise<CachedResult<{
50
+ images: Record<string, string | null>;
51
+ }>>;
52
+ }
53
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/figma/client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAY9F,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,YAAY,EAAE,MAAM;CAKjC;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;gBAE3B,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;IAWjE,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,UAAU;IAYlB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAWvD,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,kBAAkB;YAUZ,OAAO;IAoBrB,OAAO,CAAC,iBAAiB;IAkBnB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;IAgBpF,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,YAAY,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,QAAQ,EAAE,SAAS,CAAA;SAAE,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC,CAAC;IAqB7E,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAetF,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC;IAejF,SAAS,CACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EAAE,EACjB,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,EACrC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,YAAY,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC,CAAC;CAkBpE"}
@@ -0,0 +1,193 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ const FIGMA_API_BASE = "https://api.figma.com/v1";
6
+ const DEFAULT_TTL_MS = 60 * 60 * 1000; // 1 hour
7
+ export class FigmaRateLimitError extends Error {
8
+ retryAfterMs;
9
+ constructor(retryAfterMs) {
10
+ super(`Figma API rate limited. Retry after ${retryAfterMs}ms`);
11
+ this.name = "FigmaRateLimitError";
12
+ this.retryAfterMs = retryAfterMs;
13
+ }
14
+ }
15
+ export class FigmaClient {
16
+ accessToken;
17
+ ttlMs;
18
+ cacheDir;
19
+ disableCache;
20
+ constructor(accessToken, options = {}) {
21
+ this.accessToken = accessToken;
22
+ this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
23
+ this.cacheDir = options.cacheDir ?? join(tmpdir(), "figma-spec-cache");
24
+ this.disableCache = options.disableCache ?? false;
25
+ if (!this.disableCache) {
26
+ mkdirSync(this.cacheDir, { recursive: true });
27
+ }
28
+ }
29
+ cacheKey(parts) {
30
+ return createHash("sha256").update(parts.join("|")).digest("hex").slice(0, 16);
31
+ }
32
+ cachePath(key) {
33
+ return join(this.cacheDir, `${key}.json`);
34
+ }
35
+ getFileCacheKey(fileKey, version) {
36
+ return this.cacheKey([fileKey, "file", version ?? "latest"]);
37
+ }
38
+ getFileNodesCacheKey(fileKey, nodeIds, fileVersion) {
39
+ const sortedIds = [...nodeIds].sort();
40
+ return this.cacheKey([fileKey, ...sortedIds, fileVersion ?? "latest"]);
41
+ }
42
+ getImagesCacheKey(fileKey, nodeIds, format, scale) {
43
+ const sortedIds = [...nodeIds].sort();
44
+ return this.cacheKey([fileKey, "images", format, String(scale), ...sortedIds]);
45
+ }
46
+ readCache(key) {
47
+ if (this.disableCache)
48
+ return null;
49
+ const path = this.cachePath(key);
50
+ if (!existsSync(path))
51
+ return null;
52
+ try {
53
+ return JSON.parse(readFileSync(path, "utf-8"));
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ writeCache(key, data, fileVersion) {
60
+ if (this.disableCache)
61
+ return;
62
+ const now = Date.now();
63
+ const entry = {
64
+ data,
65
+ cachedAt: now,
66
+ expiresAt: now + this.ttlMs,
67
+ fileVersion,
68
+ };
69
+ writeFileSync(this.cachePath(key), JSON.stringify(entry));
70
+ }
71
+ invalidateCache(fileKey, nodeId) {
72
+ if (this.disableCache)
73
+ return;
74
+ const key = nodeId ? this.getFileNodesCacheKey(fileKey, [nodeId]) : this.getFileCacheKey(fileKey);
75
+ const path = this.cachePath(key);
76
+ if (existsSync(path)) {
77
+ unlinkSync(path);
78
+ }
79
+ }
80
+ buildCacheMetadata(entry) {
81
+ return {
82
+ cachedAt: new Date(entry.cachedAt).toISOString(),
83
+ expiresAt: new Date(entry.expiresAt).toISOString(),
84
+ fileVersion: entry.fileVersion,
85
+ fresh: Date.now() < entry.expiresAt,
86
+ };
87
+ }
88
+ buildFreshMetadata(fileVersion) {
89
+ const now = Date.now();
90
+ return {
91
+ cachedAt: new Date(now).toISOString(),
92
+ expiresAt: new Date(now + this.ttlMs).toISOString(),
93
+ fileVersion,
94
+ fresh: true,
95
+ };
96
+ }
97
+ async request(path) {
98
+ const response = await fetch(`${FIGMA_API_BASE}${path}`, {
99
+ headers: { "X-Figma-Token": this.accessToken },
100
+ });
101
+ if (!response.ok) {
102
+ if (response.status === 429) {
103
+ const retryAfterHeader = response.headers.get("Retry-After");
104
+ const retryAfterMs = this.parseRetryAfterMs(retryAfterHeader);
105
+ throw new FigmaRateLimitError(retryAfterMs);
106
+ }
107
+ const body = await response.text();
108
+ const sanitized = body.replace(/figd_[A-Za-z0-9_-]+/g, "[REDACTED]");
109
+ throw new Error(`Figma API error ${response.status}: ${sanitized}`);
110
+ }
111
+ return response.json();
112
+ }
113
+ parseRetryAfterMs(retryAfterHeader) {
114
+ if (!retryAfterHeader) {
115
+ return 1000;
116
+ }
117
+ const seconds = Number(retryAfterHeader);
118
+ if (Number.isFinite(seconds) && seconds >= 0) {
119
+ return seconds * 1000;
120
+ }
121
+ const retryAt = Date.parse(retryAfterHeader);
122
+ if (!Number.isNaN(retryAt)) {
123
+ return Math.max(retryAt - Date.now(), 0);
124
+ }
125
+ return 1000;
126
+ }
127
+ async getFile(fileKey, version) {
128
+ const key = this.getFileCacheKey(fileKey, version);
129
+ const cached = this.readCache(key);
130
+ if (cached && Date.now() < cached.expiresAt) {
131
+ return { data: cached.data, cache: this.buildCacheMetadata(cached) };
132
+ }
133
+ const params = version ? `?version=${encodeURIComponent(version)}` : "";
134
+ const data = await this.request(`/files/${fileKey}${params}`);
135
+ this.writeCache(key, data, data.version);
136
+ const entry = this.readCache(key);
137
+ const cache = entry ? this.buildCacheMetadata(entry) : this.buildFreshMetadata(data.version);
138
+ return { data, cache };
139
+ }
140
+ async getFileNodes(fileKey, nodeIds, fileVersion) {
141
+ const key = this.getFileNodesCacheKey(fileKey, nodeIds, fileVersion);
142
+ const cached = this.readCache(key);
143
+ if (cached && Date.now() < cached.expiresAt) {
144
+ return { data: cached.data, cache: this.buildCacheMetadata(cached) };
145
+ }
146
+ const ids = nodeIds.join(",");
147
+ const versionParam = fileVersion ? `&version=${encodeURIComponent(fileVersion)}` : "";
148
+ const data = await this.request(`/files/${fileKey}/nodes?ids=${encodeURIComponent(ids)}${versionParam}`);
149
+ const version = fileVersion ?? "unknown";
150
+ this.writeCache(key, data, version);
151
+ const entry = this.readCache(key);
152
+ const cache = entry ? this.buildCacheMetadata(entry) : this.buildFreshMetadata(version);
153
+ return { data, cache };
154
+ }
155
+ async getStyles(fileKey) {
156
+ const key = this.cacheKey([fileKey, "styles"]);
157
+ const cached = this.readCache(key);
158
+ if (cached && Date.now() < cached.expiresAt) {
159
+ return { data: cached.data, cache: this.buildCacheMetadata(cached) };
160
+ }
161
+ const data = await this.request(`/files/${fileKey}/styles`);
162
+ this.writeCache(key, data, "unknown");
163
+ const entry = this.readCache(key);
164
+ const cache = entry ? this.buildCacheMetadata(entry) : this.buildFreshMetadata("unknown");
165
+ return { data, cache };
166
+ }
167
+ async getComponent(componentKey) {
168
+ const key = this.cacheKey(["component", componentKey]);
169
+ const cached = this.readCache(key);
170
+ if (cached && Date.now() < cached.expiresAt) {
171
+ return { data: cached.data, cache: this.buildCacheMetadata(cached) };
172
+ }
173
+ const data = await this.request(`/components/${componentKey}`);
174
+ this.writeCache(key, data, "unknown");
175
+ const entry = this.readCache(key);
176
+ const cache = entry ? this.buildCacheMetadata(entry) : this.buildFreshMetadata("unknown");
177
+ return { data, cache };
178
+ }
179
+ async getImages(fileKey, nodeIds, format, scale) {
180
+ const key = this.getImagesCacheKey(fileKey, nodeIds, format, scale);
181
+ const cached = this.readCache(key);
182
+ if (cached && Date.now() < cached.expiresAt) {
183
+ return { data: cached.data, cache: this.buildCacheMetadata(cached) };
184
+ }
185
+ const ids = nodeIds.join(",");
186
+ const data = await this.request(`/images/${fileKey}?ids=${encodeURIComponent(ids)}&format=${encodeURIComponent(format)}&scale=${encodeURIComponent(String(scale))}`);
187
+ this.writeCache(key, data, "unknown");
188
+ const entry = this.readCache(key);
189
+ const cache = entry ? this.buildCacheMetadata(entry) : this.buildFreshMetadata("unknown");
190
+ return { data, cache };
191
+ }
192
+ }
193
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/figma/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,MAAM,cAAc,GAAG,0BAA0B,CAAC;AAClD,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,SAAS;AA2BhD,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACnC,YAAY,CAAS;IAE9B,YAAY,YAAoB;QAC9B,KAAK,CAAC,uCAAuC,YAAY,IAAI,CAAC,CAAC;QAC/D,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACnC,CAAC;CACF;AAED,MAAM,OAAO,WAAW;IACL,WAAW,CAAS;IACpB,KAAK,CAAS;IACd,QAAQ,CAAS;IACjB,YAAY,CAAU;IAEvC,YAAY,WAAmB,EAAE,UAA8B,EAAE;QAC/D,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,cAAc,CAAC;QAC7C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC;QACvE,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,KAAK,CAAC;QAElD,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAEO,QAAQ,CAAC,KAAe;QAC9B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjF,CAAC;IAEO,SAAS,CAAC,GAAW;QAC3B,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;IAC5C,CAAC;IAEO,eAAe,CAAC,OAAe,EAAE,OAAgB;QACvD,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,IAAI,QAAQ,CAAC,CAAC,CAAC;IAC/D,CAAC;IAEO,oBAAoB,CAAC,OAAe,EAAE,OAAiB,EAAE,WAAoB;QACnF,MAAM,SAAS,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,GAAG,SAAS,EAAE,WAAW,IAAI,QAAQ,CAAC,CAAC,CAAC;IACzE,CAAC;IAEO,iBAAiB,CACvB,OAAe,EACf,OAAiB,EACjB,MAAqC,EACrC,KAAa;QAEb,MAAM,SAAS,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;IACjF,CAAC;IAEO,SAAS,CAAI,GAAW;QAC9B,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACnC,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAkB,CAAC;QAClE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEO,UAAU,CAAI,GAAW,EAAE,IAAO,EAAE,WAAmB;QAC7D,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,KAAK,GAAkB;YAC3B,IAAI;YACJ,QAAQ,EAAE,GAAG;YACb,SAAS,EAAE,GAAG,GAAG,IAAI,CAAC,KAAK;YAC3B,WAAW;SACZ,CAAC;QACF,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,eAAe,CAAC,OAAe,EAAE,MAAe;QAC9C,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO;QAE9B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAElG,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,UAAU,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAEO,kBAAkB,CAAC,KAA0B;QACnD,OAAO;YACL,QAAQ,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE;YAChD,SAAS,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE;YAClD,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS;SACpC,CAAC;IACJ,CAAC;IAEO,kBAAkB,CAAC,WAAmB;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,OAAO;YACL,QAAQ,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE;YACrC,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE;YACnD,WAAW;YACX,KAAK,EAAE,IAAI;SACZ,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,OAAO,CAAI,IAAY;QACnC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,cAAc,GAAG,IAAI,EAAE,EAAE;YACvD,OAAO,EAAE,EAAE,eAAe,EAAE,IAAI,CAAC,WAAW,EAAE;SAC/C,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC5B,MAAM,gBAAgB,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;gBAC7D,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;gBAC9D,MAAM,IAAI,mBAAmB,CAAC,YAAY,CAAC,CAAC;YAC9C,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,YAAY,CAAC,CAAC;YACrE,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC,CAAC;QACtE,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,EAAgB,CAAC;IACvC,CAAC;IAEO,iBAAiB,CAAC,gBAA+B;QACvD,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;QACzC,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;YAC7C,OAAO,OAAO,GAAG,IAAI,CAAC;QACxB,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;QAC3C,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAe,EAAE,OAAgB;QAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAoB,GAAG,CAAC,CAAC;QAEtD,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;YAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;QACvE,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,YAAY,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAoB,UAAU,OAAO,GAAG,MAAM,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAoB,GAAG,CAAC,CAAC;QACrD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7F,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,YAAY,CAChB,OAAe,EACf,OAAiB,EACjB,WAAoB;QAEpB,MAAM,GAAG,GAAG,IAAI,CAAC,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;QACrE,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAA4D,GAAG,CAAC,CAAC;QAE9F,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;YAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;QACvE,CAAC;QAED,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,CAAC,YAAY,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAC7B,UAAU,OAAO,cAAc,kBAAkB,CAAC,GAAG,CAAC,GAAG,YAAY,EAAE,CACxE,CAAC;QAEF,MAAM,OAAO,GAAG,WAAW,IAAI,SAAS,CAAC;QACzC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAA4D,GAAG,CAAC,CAAC;QAC7F,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QACxF,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,OAAe;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAsC,GAAG,CAAC,CAAC;QAExE,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;YAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;QACvE,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAsC,UAAU,OAAO,SAAS,CAAC,CAAC;QACjG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAsC,GAAG,CAAC,CAAC;QACvE,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC1F,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,YAAoB;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAyB,GAAG,CAAC,CAAC;QAE3D,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;YAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;QACvE,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAyB,eAAe,YAAY,EAAE,CAAC,CAAC;QACvF,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAyB,GAAG,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC1F,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,SAAS,CACb,OAAe,EACf,OAAiB,EACjB,MAAqC,EACrC,KAAa;QAEb,MAAM,GAAG,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAA4C,GAAG,CAAC,CAAC;QAE9E,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;YAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;QACvE,CAAC;QAED,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAC7B,WAAW,OAAO,QAAQ,kBAAkB,CAAC,GAAG,CAAC,WAAW,kBAAkB,CAAC,MAAM,CAAC,UAAU,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CACpI,CAAC;QAEF,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAA4C,GAAG,CAAC,CAAC;QAC7E,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC1F,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACzB,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import "./tools/register-all.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAMA,OAAO,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
+ import { SERVER_VERSION } from "./shared.js";
6
+ import "./tools/register-all.js";
7
+ import { executeTool, getToolDefinitions } from "./tools/registry.js";
8
+ const server = new Server({ name: "figma-spec-mcp", version: SERVER_VERSION }, { capabilities: { tools: {} } });
9
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
10
+ const tools = getToolDefinitions();
11
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
12
+ const { name, arguments: args } = request.params;
13
+ try {
14
+ const result = await executeTool(name, args);
15
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
16
+ }
17
+ catch (error) {
18
+ if (error instanceof Error && error.name === "ZodError") {
19
+ throw new Error(`Invalid input: ${error.message}`, { cause: error });
20
+ }
21
+ throw error;
22
+ }
23
+ });
24
+ async function main() {
25
+ const transport = new StdioServerTransport();
26
+ await server.connect(transport);
27
+ }
28
+ main().catch((error) => {
29
+ console.error("Fatal error:", error);
30
+ process.exit(1);
31
+ });
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AACnG,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,yBAAyB,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEtE,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AAEhH,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAC1E,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;AAEnC,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAEjD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC7C,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAChF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACxD,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { CacheMetadata } from "./figma/client.js";
2
+ export declare const SCHEMA_VERSION: "0.1.0";
3
+ export declare const SERVER_VERSION = "1.0.0-beta.1";
4
+ export declare function buildFreshness(cache: CacheMetadata): {
5
+ fresh: boolean;
6
+ timestamp: string;
7
+ ttl_ms: number;
8
+ };
9
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,eAAO,MAAM,cAAc,EAAG,OAAgB,CAAC;AAC/C,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAE7C,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAM1G"}
package/dist/shared.js ADDED
@@ -0,0 +1,10 @@
1
+ export const SCHEMA_VERSION = "0.1.0";
2
+ export const SERVER_VERSION = "1.0.0-beta.1";
3
+ export function buildFreshness(cache) {
4
+ return {
5
+ fresh: cache.fresh,
6
+ timestamp: cache.cachedAt,
7
+ ttl_ms: new Date(cache.expiresAt).getTime() - new Date(cache.cachedAt).getTime(),
8
+ };
9
+ }
10
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAG,OAAgB,CAAC;AAC/C,MAAM,CAAC,MAAM,cAAc,GAAG,cAAc,CAAC;AAE7C,MAAM,UAAU,cAAc,CAAC,KAAoB;IACjD,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,SAAS,EAAE,KAAK,CAAC,QAAQ;QACzB,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE;KACjF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ import type { AnalyzeFrameInput, AnalyzeFrameResult } from "../types/tools.js";
3
+ export declare const analyzeFrameSchema: z.ZodObject<{
4
+ file_key: z.ZodString;
5
+ node_id: z.ZodString;
6
+ access_token: z.ZodString;
7
+ }, z.core.$strip>;
8
+ export declare function analyzeFrame(input: AnalyzeFrameInput): Promise<AnalyzeFrameResult>;
9
+ //# sourceMappingURL=analyze-frame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze-frame.d.ts","sourceRoot":"","sources":["../../src/tools/analyze-frame.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAKnB,MAAM,mBAAmB,CAAC;AAE3B,eAAO,MAAM,kBAAkB;;;;iBAI7B,CAAC;AAmFH,wBAAsB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA0CxF"}
@@ -0,0 +1,117 @@
1
+ import { z } from "zod";
2
+ import { FigmaClient } from "../figma/client.js";
3
+ export const analyzeFrameSchema = z.object({
4
+ file_key: z.string().describe("The Figma file key (from the file URL)"),
5
+ node_id: z.string().describe("The node ID of the frame to analyze"),
6
+ access_token: z.string().describe("Your Figma personal access token"),
7
+ });
8
+ function collectComponents(node, results) {
9
+ const isComponent = node.type === "COMPONENT" || node.type === "COMPONENT_SET";
10
+ const isInstance = node.type === "INSTANCE";
11
+ if (isComponent || isInstance) {
12
+ results.push({
13
+ id: node.id,
14
+ name: node.name,
15
+ type: node.type,
16
+ isComponent,
17
+ isInstance,
18
+ });
19
+ }
20
+ node.children?.forEach((child) => collectComponents(child, results));
21
+ }
22
+ function collectLayouts(node, results) {
23
+ if (node.layoutMode && node.layoutMode !== "NONE") {
24
+ results[node.id] = {
25
+ mode: node.layoutMode === "HORIZONTAL" ? "horizontal" : "vertical",
26
+ primaryAxisAlign: node.primaryAxisAlignItems ?? "MIN",
27
+ counterAxisAlign: node.counterAxisAlignItems ?? "MIN",
28
+ padding: {
29
+ top: node.paddingTop ?? 0,
30
+ right: node.paddingRight ?? 0,
31
+ bottom: node.paddingBottom ?? 0,
32
+ left: node.paddingLeft ?? 0,
33
+ },
34
+ gap: node.itemSpacing ?? 0,
35
+ sizing: {
36
+ width: node.primaryAxisSizingMode === "AUTO" ? "hug" : "fixed",
37
+ height: node.counterAxisSizingMode === "AUTO" ? "hug" : "fixed",
38
+ },
39
+ };
40
+ }
41
+ node.children?.forEach((child) => collectLayouts(child, results));
42
+ }
43
+ function collectConstraints(node, results) {
44
+ if (node.constraints && node.absoluteBoundingBox) {
45
+ results[node.id] = {
46
+ horizontal: node.constraints.horizontal,
47
+ vertical: node.constraints.vertical,
48
+ bounds: node.absoluteBoundingBox,
49
+ };
50
+ }
51
+ node.children?.forEach((child) => collectConstraints(child, results));
52
+ }
53
+ function collectAccessibilityWarnings(node, warnings) {
54
+ if (node.type === "TEXT" && node.style) {
55
+ if (node.style.fontSize < 12) {
56
+ warnings.push({
57
+ nodeId: node.id,
58
+ nodeName: node.name,
59
+ severity: "warning",
60
+ message: `Text node "${node.name}" has a font size of ${node.style.fontSize}px, which may be too small for accessibility.`,
61
+ });
62
+ }
63
+ }
64
+ if (node.fills && node.fills.length === 0 && node.type === "FRAME") {
65
+ warnings.push({
66
+ nodeId: node.id,
67
+ nodeName: node.name,
68
+ severity: "info",
69
+ message: `Frame "${node.name}" has no background fill — ensure this is intentional.`,
70
+ });
71
+ }
72
+ node.children?.forEach((child) => collectAccessibilityWarnings(child, warnings));
73
+ }
74
+ function countNodesByType(node, counts) {
75
+ counts[node.type] = (counts[node.type] ?? 0) + 1;
76
+ node.children?.forEach((child) => countNodesByType(child, counts));
77
+ }
78
+ export async function analyzeFrame(input) {
79
+ const client = new FigmaClient(input.access_token);
80
+ const normalizedId = input.node_id.replace("-", ":");
81
+ const response = await client.getFileNodes(input.file_key, [normalizedId]);
82
+ const nodeData = response.nodes[normalizedId];
83
+ if (!nodeData) {
84
+ throw new Error(`Node "${input.node_id}" not found in file "${input.file_key}"`);
85
+ }
86
+ const frame = nodeData.document;
87
+ const components = [];
88
+ const layouts = {};
89
+ const constraints = {};
90
+ const accessibilityWarnings = [];
91
+ const typeCounts = {};
92
+ collectComponents(frame, components);
93
+ collectLayouts(frame, layouts);
94
+ collectConstraints(frame, constraints);
95
+ collectAccessibilityWarnings(frame, accessibilityWarnings);
96
+ countNodesByType(frame, typeCounts);
97
+ return {
98
+ frameId: frame.id,
99
+ frameName: frame.name,
100
+ dimensions: {
101
+ width: frame.absoluteBoundingBox?.width ?? 0,
102
+ height: frame.absoluteBoundingBox?.height ?? 0,
103
+ },
104
+ components,
105
+ layouts,
106
+ constraints,
107
+ accessibilityWarnings,
108
+ stats: {
109
+ totalNodes: Object.values(typeCounts).reduce((a, b) => a + b, 0),
110
+ componentCount: components.filter((c) => c.isComponent).length,
111
+ instanceCount: components.filter((c) => c.isInstance).length,
112
+ textNodeCount: typeCounts["TEXT"] ?? 0,
113
+ imageNodeCount: typeCounts["RECTANGLE"] ?? 0,
114
+ },
115
+ };
116
+ }
117
+ //# sourceMappingURL=analyze-frame.js.map