Skip to content

Commit dd237c8

Browse files
authored
feat: add support for using as a CLI via fetch subcommand to retrieve design data directly (#331)
* test: add result serialization behavior tests Verify YAML and JSON serialization behavior matching the inline logic in get-figma-data-tool.ts. Tests cover the performance-critical YAML options (noRefs, lineWidth, noCompatMode, JSON_SCHEMA) and round-trip correctness for both formats. These tests will anchor a future extraction of serialization into a shared function. * test(config): add unit tests for config resolution helpers Export resolve, envStr, envInt, envBool (and Source, Resolved types) from config.ts and add 13 unit tests covering the priority chain and environment variable parsing. These are safety-net tests before an upcoming config refactor. * test: add process-level CLI startup tests Pin current binary startup behavior before rewriting bin.ts: - Verify NODE_ENV=cli triggers stdio mode without --stdio flag - Verify HTTP mode starts via bin.ts and accepts MCP requests * test: add edge case coverage for config resolution and YAML schema Add falsy-value preservation test for resolve() (false, 0 must not fall through to env/default). Add YAML schema quoting test verifying JSON_SCHEMA skips unnecessary quoting of ambiguous strings like "yes" and "2024-01-01". * docs: update plan with Phase 1 completion and simplifications * feat(utils): add Figma URL parser for fetch subcommand Parses Figma URLs into fileKey and nodeId components, handling /file/ and /design/ path formats and converting URL-style node IDs (dashes) to API-style (colons). * refactor(utils): extract serializeResult to shared utility Move YAML/JSON serialization logic from get-figma-data-tool.ts into utils/serialize.ts so it can be reused by the upcoming CLI fetch command. Update serialization tests to import from the new module. * refactor: restructure CLI as router with parameterized config Move CLI parsing (cleye) from config.ts to bin.ts, making bin.ts the entry-point router that dispatches to the server path or fetch subcommand. config.ts becomes a toolkit of composable helpers: getServerConfig(flags), resolveAuth(), loadEnvFile(). startServer() now accepts a ServerConfig object instead of calling getServerConfig() internally, completing the separation of parsing from execution. * feat: implement CLI fetch subcommand Add `figma-developer-mcp fetch` that performs a one-off Figma data fetch to stdout. Supports positional URL argument or explicit --file-key/--node-id flags, with flags overriding URL-derived values. Pipeline: parse URL → load .env → resolve auth → FigmaService → getRawNode/getRawFile → simplifyRawFigmaObject → serializeResult → stdout. * chore: address review feedback and mark plan complete - Invert empty if-branch in bin.ts for clarity - Narrow mcp-server.ts public API to only getServerConfig/ServerConfig (loadEnvFile, resolveAuth, ServerFlags are internal utilities) - Mark Phase 2 done in plan document * fix: handle malformed URL when file-key flag is provided parseFigmaUrl() threw before flags could override URL-derived values. Now URL parsing failures are non-fatal when --file-key is already set. Also tighten hostname validation to reject lookalike domains like notfigma.com (check exact match or .figma.com subdomain). * chore: remove plan doc from branch
1 parent 309c60e commit dd237c8

13 files changed

Lines changed: 574 additions & 134 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,75 +2,66 @@
22

33
## [0.8.1](https://github.com/GLips/Figma-Context-MCP/compare/v0.8.0...v0.8.1) (2026-04-07)
44

5-
65
### Bug Fixes
76

8-
* disambiguate named styles with duplicate names ([#319](https://github.com/GLips/Figma-Context-MCP/issues/319)) ([a077ace](https://github.com/GLips/Figma-Context-MCP/commit/a077ace9809bf6b14c4e4a9906065fb3cea2d24f))
9-
* include BOOLEAN_OPERATION in SVG container collapse ([354679e](https://github.com/GLips/Figma-Context-MCP/commit/354679eab17389c551a435ca7c5224a250446301))
10-
* include BOOLEAN_OPERATION in SVG container collapse ([19c50b3](https://github.com/GLips/Figma-Context-MCP/commit/19c50b3ad3ecf12ce4b4bedc0aefff718b3b89f9))
11-
* replace jimp with selective @jimp/* imports to fix ESM crash ([#333](https://github.com/GLips/Figma-Context-MCP/issues/333)) ([dd47ebf](https://github.com/GLips/Figma-Context-MCP/commit/dd47ebf82520c6147b913415db99c3b4caaa40b2)), closes [#329](https://github.com/GLips/Figma-Context-MCP/issues/329)
7+
- disambiguate named styles with duplicate names ([#319](https://github.com/GLips/Figma-Context-MCP/issues/319)) ([a077ace](https://github.com/GLips/Figma-Context-MCP/commit/a077ace9809bf6b14c4e4a9906065fb3cea2d24f))
8+
- include BOOLEAN_OPERATION in SVG container collapse ([354679e](https://github.com/GLips/Figma-Context-MCP/commit/354679eab17389c551a435ca7c5224a250446301))
9+
- include BOOLEAN_OPERATION in SVG container collapse ([19c50b3](https://github.com/GLips/Figma-Context-MCP/commit/19c50b3ad3ecf12ce4b4bedc0aefff718b3b89f9))
10+
- replace jimp with selective @jimp/\* imports to fix ESM crash ([#333](https://github.com/GLips/Figma-Context-MCP/issues/333)) ([dd47ebf](https://github.com/GLips/Figma-Context-MCP/commit/dd47ebf82520c6147b913415db99c3b4caaa40b2)), closes [#329](https://github.com/GLips/Figma-Context-MCP/issues/329)
1211

1312
## [0.8.0](https://github.com/GLips/Figma-Context-MCP/compare/v0.7.1...v0.8.0) (2026-03-24)
1413

15-
1614
### ⚠ BREAKING CHANGES
1715

18-
* switch to stateless HTTP transport ([#304](https://github.com/GLips/Figma-Context-MCP/issues/304))
16+
- switch to stateless HTTP transport ([#304](https://github.com/GLips/Figma-Context-MCP/issues/304))
1917

2018
### Features
2119

22-
* add progress notifications and async tree walker ([#305](https://github.com/GLips/Figma-Context-MCP/issues/305)) ([b5724ad](https://github.com/GLips/Figma-Context-MCP/commit/b5724ade8234e73fe94467c6bfad5e020552f0e2))
23-
20+
- add progress notifications and async tree walker ([#305](https://github.com/GLips/Figma-Context-MCP/issues/305)) ([b5724ad](https://github.com/GLips/Figma-Context-MCP/commit/b5724ade8234e73fe94467c6bfad5e020552f0e2))
2421

2522
### Performance Improvements
2623

27-
* fix O(n²) bottlenecks in simplification and YAML serialization ([#307](https://github.com/GLips/Figma-Context-MCP/issues/307)) ([29cff0c](https://github.com/GLips/Figma-Context-MCP/commit/29cff0cbd6d2fd0459900e9c3cbc49f64e47075d))
28-
24+
- fix O(n²) bottlenecks in simplification and YAML serialization ([#307](https://github.com/GLips/Figma-Context-MCP/issues/307)) ([29cff0c](https://github.com/GLips/Figma-Context-MCP/commit/29cff0cbd6d2fd0459900e9c3cbc49f64e47075d))
2925

3026
### Code Refactoring
3127

32-
* switch to stateless HTTP transport ([#304](https://github.com/GLips/Figma-Context-MCP/issues/304)) ([9dfb1cb](https://github.com/GLips/Figma-Context-MCP/commit/9dfb1cb65a081655d7dca5f076ab76f5d7e9edc0))
28+
- switch to stateless HTTP transport ([#304](https://github.com/GLips/Figma-Context-MCP/issues/304)) ([9dfb1cb](https://github.com/GLips/Figma-Context-MCP/commit/9dfb1cb65a081655d7dca5f076ab76f5d7e9edc0))
3329

3430
## [0.7.1](https://github.com/GLips/Figma-Context-MCP/compare/v0.7.0...v0.7.1) (2026-03-20)
3531

36-
3732
### Bug Fixes
3833

39-
* handle drive root paths in image directory security check ([#301](https://github.com/GLips/Figma-Context-MCP/issues/301)) ([9f32616](https://github.com/GLips/Figma-Context-MCP/commit/9f32616caa29b1dbdd5c5a9dcfafa3dd717070a3))
34+
- handle drive root paths in image directory security check ([#301](https://github.com/GLips/Figma-Context-MCP/issues/301)) ([9f32616](https://github.com/GLips/Figma-Context-MCP/commit/9f32616caa29b1dbdd5c5a9dcfafa3dd717070a3))
4035

4136
## [0.7.0](https://github.com/GLips/Figma-Context-MCP/compare/v0.6.6...v0.7.0) (2026-03-19)
4237

43-
4438
### ⚠ BREAKING CHANGES
4539

46-
* getServerConfig() no longer takes an isStdioMode parameter. It now detects stdio mode internally and returns it as part of ServerConfig.
40+
- getServerConfig() no longer takes an isStdioMode parameter. It now detects stdio mode internally and returns it as part of ServerConfig.
4741

4842
### Features
4943

50-
* add --image-dir config for image download path control ([#297](https://github.com/GLips/Figma-Context-MCP/issues/297)) ([0417766](https://github.com/GLips/Figma-Context-MCP/commit/0417766eb5fc1e0b76e55da497961f9aee2f62f7))
51-
* replace yargs with cleye for CLI flag parsing ([#285](https://github.com/GLips/Figma-Context-MCP/issues/285)) ([0092ee7](https://github.com/GLips/Figma-Context-MCP/commit/0092ee789fce01b9ef1dab5e8f32c52e71107dbb))
52-
* support gifRef for downloading animated GIF embeds ([#286](https://github.com/GLips/Figma-Context-MCP/issues/286)) ([f1ec913](https://github.com/GLips/Figma-Context-MCP/commit/f1ec9133c31a351b55651126c20ea2f842c0a9ee))
53-
44+
- add --image-dir config for image download path control ([#297](https://github.com/GLips/Figma-Context-MCP/issues/297)) ([0417766](https://github.com/GLips/Figma-Context-MCP/commit/0417766eb5fc1e0b76e55da497961f9aee2f62f7))
45+
- replace yargs with cleye for CLI flag parsing ([#285](https://github.com/GLips/Figma-Context-MCP/issues/285)) ([0092ee7](https://github.com/GLips/Figma-Context-MCP/commit/0092ee789fce01b9ef1dab5e8f32c52e71107dbb))
46+
- support gifRef for downloading animated GIF embeds ([#286](https://github.com/GLips/Figma-Context-MCP/issues/286)) ([f1ec913](https://github.com/GLips/Figma-Context-MCP/commit/f1ec9133c31a351b55651126c20ea2f842c0a9ee))
5447

5548
### Bug Fixes
5649

57-
* remove inline release-type so release-please reads config file ([a03cd68](https://github.com/GLips/Figma-Context-MCP/commit/a03cd68826da1c1596273a223a612eb919832397))
58-
* replace sharp dependency with js-native jimp for image manipulation ([#289](https://github.com/GLips/Figma-Context-MCP/issues/289)) ([62b9f94](https://github.com/GLips/Figma-Context-MCP/commit/62b9f94b1607dd08daeaa90e8ace0a896fe6eb50))
59-
* skip jimp processing for SVGs and prevent image-fill collapse ([#298](https://github.com/GLips/Figma-Context-MCP/issues/298)) ([a4a4b13](https://github.com/GLips/Figma-Context-MCP/commit/a4a4b13ec7cae5d603022b1c8719cc717749195b))
50+
- remove inline release-type so release-please reads config file ([a03cd68](https://github.com/GLips/Figma-Context-MCP/commit/a03cd68826da1c1596273a223a612eb919832397))
51+
- replace sharp dependency with js-native jimp for image manipulation ([#289](https://github.com/GLips/Figma-Context-MCP/issues/289)) ([62b9f94](https://github.com/GLips/Figma-Context-MCP/commit/62b9f94b1607dd08daeaa90e8ace0a896fe6eb50))
52+
- skip jimp processing for SVGs and prevent image-fill collapse ([#298](https://github.com/GLips/Figma-Context-MCP/issues/298)) ([a4a4b13](https://github.com/GLips/Figma-Context-MCP/commit/a4a4b13ec7cae5d603022b1c8719cc717749195b))
6053

6154
## [0.6.6](https://github.com/GLips/Figma-Context-MCP/compare/v0.6.5...v0.6.6) (2026-03-04)
6255

63-
6456
### Bug Fixes
6557

66-
* use Node 24 in release workflow for npm OIDC support ([11ba7c6](https://github.com/GLips/Figma-Context-MCP/commit/11ba7c6a2e22910c483592ba7cdc1966fcdc9166))
58+
- use Node 24 in release workflow for npm OIDC support ([11ba7c6](https://github.com/GLips/Figma-Context-MCP/commit/11ba7c6a2e22910c483592ba7cdc1966fcdc9166))
6759

6860
## [0.6.5](https://github.com/GLips/Figma-Context-MCP/compare/v0.6.4...v0.6.5) (2026-03-04)
6961

70-
7162
### Bug Fixes
7263

73-
* upgrade MCP SDK to 1.27.1 and modernize tool registration ([#282](https://github.com/GLips/Figma-Context-MCP/issues/282)) ([4153e5f](https://github.com/GLips/Figma-Context-MCP/commit/4153e5f857aa708ee9ee10156e553c1289f03cf7))
64+
- upgrade MCP SDK to 1.27.1 and modernize tool registration ([#282](https://github.com/GLips/Figma-Context-MCP/issues/282)) ([4153e5f](https://github.com/GLips/Figma-Context-MCP/commit/4153e5f857aa708ee9ee10156e553c1289f03cf7))
7465

7566
## 0.6.4
7667

‎src/bin.ts‎

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,66 @@
11
#!/usr/bin/env node
22

3+
import { cli } from "cleye";
4+
import { getServerConfig } from "./config.js";
35
import { startServer } from "./server.js";
6+
import { fetchCommand } from "./commands/fetch.js";
47

5-
startServer().catch((error) => {
6-
console.error("Failed to start server:", error);
7-
process.exit(1);
8+
const argv = cli({
9+
name: "figma-developer-mcp",
10+
version: process.env.NPM_PACKAGE_VERSION ?? "unknown",
11+
flags: {
12+
figmaApiKey: {
13+
type: String,
14+
description: "Figma API key (Personal Access Token)",
15+
},
16+
figmaOauthToken: {
17+
type: String,
18+
description: "Figma OAuth Bearer token",
19+
},
20+
env: {
21+
type: String,
22+
description: "Path to custom .env file to load environment variables from",
23+
},
24+
port: {
25+
type: Number,
26+
description: "Port to run the server on",
27+
},
28+
host: {
29+
type: String,
30+
description: "Host to run the server on",
31+
},
32+
json: {
33+
type: Boolean,
34+
description: "Output data from tools in JSON format instead of YAML",
35+
},
36+
skipImageDownloads: {
37+
type: Boolean,
38+
description: "Do not register the download_figma_images tool (skip image downloads)",
39+
},
40+
imageDir: {
41+
type: String,
42+
description:
43+
"Base directory for image downloads. The download tool will only write files within this directory. Defaults to the current working directory.",
44+
},
45+
proxy: {
46+
type: String,
47+
description: "HTTP proxy URL for networks that require a proxy (e.g. http://proxy:8080)",
48+
},
49+
stdio: {
50+
type: Boolean,
51+
description: "Run in stdio transport mode for MCP clients",
52+
},
53+
},
54+
commands: [fetchCommand],
855
});
56+
57+
// Subcommand callbacks execute during cli() — only start the server when no subcommand ran.
58+
if (!argv.command) {
59+
// NODE_ENV=cli is a legacy backdoor for stdio mode
60+
const isStdio = argv.flags.stdio === true || process.env.NODE_ENV === "cli";
61+
const config = getServerConfig({ ...argv.flags, stdio: isStdio });
62+
startServer(config).catch((error) => {
63+
console.error("Failed to start server:", error);
64+
process.exit(1);
65+
});
66+
}

‎src/commands/fetch.ts‎

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { type Command, command } from "cleye";
2+
import { loadEnvFile, resolveAuth } from "~/config.js";
3+
import { FigmaService } from "~/services/figma.js";
4+
import {
5+
simplifyRawFigmaObject,
6+
allExtractors,
7+
collapseSvgContainers,
8+
} from "~/extractors/index.js";
9+
import { serializeResult } from "~/utils/serialize.js";
10+
import { parseFigmaUrl } from "~/utils/figma-url.js";
11+
12+
export const fetchCommand: Command = command(
13+
{
14+
name: "fetch",
15+
description: "Fetch simplified Figma data and print to stdout",
16+
parameters: ["[url]"],
17+
flags: {
18+
fileKey: {
19+
type: String,
20+
description: "Figma file key (overrides URL)",
21+
},
22+
nodeId: {
23+
type: String,
24+
description: "Node ID, format 1234:5678 (overrides URL)",
25+
},
26+
depth: {
27+
type: Number,
28+
description: "Tree traversal depth",
29+
},
30+
json: {
31+
type: Boolean,
32+
description: "Output JSON instead of YAML",
33+
},
34+
figmaApiKey: {
35+
type: String,
36+
description: "Figma API key",
37+
},
38+
figmaOauthToken: {
39+
type: String,
40+
description: "Figma OAuth token",
41+
},
42+
env: {
43+
type: String,
44+
description: "Path to .env file",
45+
},
46+
},
47+
},
48+
(argv) => {
49+
run(argv.flags, argv._).catch((error) => {
50+
console.error(error instanceof Error ? error.message : String(error));
51+
process.exit(1);
52+
});
53+
},
54+
);
55+
56+
async function run(
57+
flags: {
58+
fileKey?: string;
59+
nodeId?: string;
60+
depth?: number;
61+
json?: boolean;
62+
figmaApiKey?: string;
63+
figmaOauthToken?: string;
64+
env?: string;
65+
},
66+
positionals: string[],
67+
) {
68+
const url = positionals[0];
69+
let fileKey = flags.fileKey;
70+
let nodeId = flags.nodeId;
71+
72+
if (url) {
73+
try {
74+
const parsed = parseFigmaUrl(url);
75+
fileKey ??= parsed.fileKey;
76+
nodeId ??= parsed.nodeId;
77+
} catch (error) {
78+
if (!fileKey) throw error;
79+
// fileKey provided via flag — malformed URL is non-fatal
80+
}
81+
}
82+
83+
if (!fileKey) {
84+
console.error("Either a Figma URL or --file-key is required");
85+
process.exit(1);
86+
}
87+
88+
loadEnvFile(flags.env);
89+
const auth = resolveAuth(flags);
90+
const figmaService = new FigmaService(auth);
91+
92+
const depth = flags.depth;
93+
const rawApiResponse = nodeId
94+
? await figmaService.getRawNode(fileKey, nodeId, depth)
95+
: await figmaService.getRawFile(fileKey, depth);
96+
97+
const simplifiedDesign = await simplifyRawFigmaObject(rawApiResponse, allExtractors, {
98+
maxDepth: depth,
99+
afterChildren: collapseSvgContainers,
100+
});
101+
102+
const { nodes, globalVars, ...metadata } = simplifiedDesign;
103+
const result = { metadata, nodes, globalVars };
104+
105+
const outputFormat = flags.json ? "json" : "yaml";
106+
console.log(serializeResult(result, outputFormat));
107+
}

0 commit comments

Comments
 (0)