@mp3wizard/figma-console-mcp 1.25.1 → 1.27.2
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 +49 -33
- package/dist/cloudflare/core/config.js +0 -8
- package/dist/cloudflare/core/console-monitor.js +3 -3
- package/dist/cloudflare/core/diagnose-tool.js +96 -0
- package/dist/cloudflare/core/figma-tools.js +69 -229
- package/dist/cloudflare/core/identity.js +96 -0
- package/dist/cloudflare/core/tokens/alias-resolver.js +98 -0
- package/dist/cloudflare/core/tokens/config.js +284 -0
- package/dist/cloudflare/core/tokens/figma-converter.js +195 -0
- package/dist/cloudflare/core/tokens/formatters/css-vars.js +329 -0
- package/dist/cloudflare/core/tokens/formatters/dtcg.js +300 -0
- package/dist/cloudflare/core/tokens/formatters/index.js +45 -0
- package/dist/cloudflare/core/tokens/formatters/json.js +7 -0
- package/dist/cloudflare/core/tokens/formatters/less.js +4 -0
- package/dist/cloudflare/core/tokens/formatters/scss.js +4 -0
- package/dist/cloudflare/core/tokens/formatters/stubs.js +11 -0
- package/dist/cloudflare/core/tokens/formatters/style-dictionary-v3.js +4 -0
- package/dist/cloudflare/core/tokens/formatters/tailwind-v3.js +4 -0
- package/dist/cloudflare/core/tokens/formatters/tailwind-v4.js +4 -0
- package/dist/cloudflare/core/tokens/formatters/tokens-studio.js +4 -0
- package/dist/cloudflare/core/tokens/formatters/ts-module.js +4 -0
- package/dist/cloudflare/core/tokens/index.js +15 -0
- package/dist/cloudflare/core/tokens/parsers/css-vars.js +4 -0
- package/dist/cloudflare/core/tokens/parsers/dtcg.js +253 -0
- package/dist/cloudflare/core/tokens/parsers/index.js +138 -0
- package/dist/cloudflare/core/tokens/parsers/json.js +7 -0
- package/dist/cloudflare/core/tokens/parsers/scss.js +4 -0
- package/dist/cloudflare/core/tokens/parsers/stubs.js +13 -0
- package/dist/cloudflare/core/tokens/parsers/style-dictionary-v3.js +4 -0
- package/dist/cloudflare/core/tokens/parsers/tailwind-v3.js +4 -0
- package/dist/cloudflare/core/tokens/parsers/tailwind-v4.js +4 -0
- package/dist/cloudflare/core/tokens/parsers/tokens-studio.js +4 -0
- package/dist/cloudflare/core/tokens/schemas.js +148 -0
- package/dist/cloudflare/core/tokens/transforms/color.js +12 -0
- package/dist/cloudflare/core/tokens/transforms/index.js +29 -0
- package/dist/cloudflare/core/tokens/transforms/size.js +7 -0
- package/dist/cloudflare/core/tokens/types.js +18 -0
- package/dist/cloudflare/core/tokens-tools.js +849 -0
- package/dist/cloudflare/core/websocket-server.js +5 -55
- package/dist/cloudflare/index.js +37 -26
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +0 -8
- package/dist/core/config.js.map +1 -1
- package/dist/core/console-monitor.d.ts +2 -2
- package/dist/core/console-monitor.d.ts.map +1 -1
- package/dist/core/console-monitor.js +3 -3
- package/dist/core/console-monitor.js.map +1 -1
- package/dist/core/diagnose-tool.d.ts +33 -0
- package/dist/core/diagnose-tool.d.ts.map +1 -0
- package/dist/core/diagnose-tool.js +97 -0
- package/dist/core/diagnose-tool.js.map +1 -0
- package/dist/core/figma-connector.d.ts +1 -1
- package/dist/core/figma-connector.d.ts.map +1 -1
- package/dist/core/figma-tools.d.ts +1 -2
- package/dist/core/figma-tools.d.ts.map +1 -1
- package/dist/core/figma-tools.js +69 -229
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/identity.d.ts +41 -0
- package/dist/core/identity.d.ts.map +1 -0
- package/dist/core/identity.js +97 -0
- package/dist/core/identity.js.map +1 -0
- package/dist/core/tokens/alias-resolver.d.ts +40 -0
- package/dist/core/tokens/alias-resolver.d.ts.map +1 -0
- package/dist/core/tokens/alias-resolver.js +99 -0
- package/dist/core/tokens/alias-resolver.js.map +1 -0
- package/dist/core/tokens/config.d.ts +352 -0
- package/dist/core/tokens/config.d.ts.map +1 -0
- package/dist/core/tokens/config.js +285 -0
- package/dist/core/tokens/config.js.map +1 -0
- package/dist/core/tokens/figma-converter.d.ts +81 -0
- package/dist/core/tokens/figma-converter.d.ts.map +1 -0
- package/dist/core/tokens/figma-converter.js +196 -0
- package/dist/core/tokens/figma-converter.js.map +1 -0
- package/dist/core/tokens/formatters/css-vars.d.ts +24 -0
- package/dist/core/tokens/formatters/css-vars.d.ts.map +1 -0
- package/dist/core/tokens/formatters/css-vars.js +330 -0
- package/dist/core/tokens/formatters/css-vars.js.map +1 -0
- package/dist/core/tokens/formatters/dtcg.d.ts +28 -0
- package/dist/core/tokens/formatters/dtcg.d.ts.map +1 -0
- package/dist/core/tokens/formatters/dtcg.js +301 -0
- package/dist/core/tokens/formatters/dtcg.js.map +1 -0
- package/dist/core/tokens/formatters/index.d.ts +30 -0
- package/dist/core/tokens/formatters/index.d.ts.map +1 -0
- package/dist/core/tokens/formatters/index.js +46 -0
- package/dist/core/tokens/formatters/index.js.map +1 -0
- package/dist/core/tokens/formatters/json.d.ts +5 -0
- package/dist/core/tokens/formatters/json.d.ts.map +1 -0
- package/dist/core/tokens/formatters/json.js +8 -0
- package/dist/core/tokens/formatters/json.js.map +1 -0
- package/dist/core/tokens/formatters/less.d.ts +4 -0
- package/dist/core/tokens/formatters/less.d.ts.map +1 -0
- package/dist/core/tokens/formatters/less.js +5 -0
- package/dist/core/tokens/formatters/less.js.map +1 -0
- package/dist/core/tokens/formatters/scss.d.ts +4 -0
- package/dist/core/tokens/formatters/scss.d.ts.map +1 -0
- package/dist/core/tokens/formatters/scss.js +5 -0
- package/dist/core/tokens/formatters/scss.js.map +1 -0
- package/dist/core/tokens/formatters/stubs.d.ts +9 -0
- package/dist/core/tokens/formatters/stubs.d.ts.map +1 -0
- package/dist/core/tokens/formatters/stubs.js +12 -0
- package/dist/core/tokens/formatters/stubs.js.map +1 -0
- package/dist/core/tokens/formatters/style-dictionary-v3.d.ts +4 -0
- package/dist/core/tokens/formatters/style-dictionary-v3.d.ts.map +1 -0
- package/dist/core/tokens/formatters/style-dictionary-v3.js +5 -0
- package/dist/core/tokens/formatters/style-dictionary-v3.js.map +1 -0
- package/dist/core/tokens/formatters/tailwind-v3.d.ts +4 -0
- package/dist/core/tokens/formatters/tailwind-v3.d.ts.map +1 -0
- package/dist/core/tokens/formatters/tailwind-v3.js +5 -0
- package/dist/core/tokens/formatters/tailwind-v3.js.map +1 -0
- package/dist/core/tokens/formatters/tailwind-v4.d.ts +4 -0
- package/dist/core/tokens/formatters/tailwind-v4.d.ts.map +1 -0
- package/dist/core/tokens/formatters/tailwind-v4.js +5 -0
- package/dist/core/tokens/formatters/tailwind-v4.js.map +1 -0
- package/dist/core/tokens/formatters/tokens-studio.d.ts +4 -0
- package/dist/core/tokens/formatters/tokens-studio.d.ts.map +1 -0
- package/dist/core/tokens/formatters/tokens-studio.js +5 -0
- package/dist/core/tokens/formatters/tokens-studio.js.map +1 -0
- package/dist/core/tokens/formatters/ts-module.d.ts +4 -0
- package/dist/core/tokens/formatters/ts-module.d.ts.map +1 -0
- package/dist/core/tokens/formatters/ts-module.js +5 -0
- package/dist/core/tokens/formatters/ts-module.js.map +1 -0
- package/dist/core/tokens/index.d.ts +17 -0
- package/dist/core/tokens/index.d.ts.map +1 -0
- package/dist/core/tokens/index.js +16 -0
- package/dist/core/tokens/index.js.map +1 -0
- package/dist/core/tokens/parsers/css-vars.d.ts +3 -0
- package/dist/core/tokens/parsers/css-vars.d.ts.map +1 -0
- package/dist/core/tokens/parsers/css-vars.js +5 -0
- package/dist/core/tokens/parsers/css-vars.js.map +1 -0
- package/dist/core/tokens/parsers/dtcg.d.ts +21 -0
- package/dist/core/tokens/parsers/dtcg.d.ts.map +1 -0
- package/dist/core/tokens/parsers/dtcg.js +254 -0
- package/dist/core/tokens/parsers/dtcg.js.map +1 -0
- package/dist/core/tokens/parsers/index.d.ts +37 -0
- package/dist/core/tokens/parsers/index.d.ts.map +1 -0
- package/dist/core/tokens/parsers/index.js +139 -0
- package/dist/core/tokens/parsers/index.js.map +1 -0
- package/dist/core/tokens/parsers/json.d.ts +4 -0
- package/dist/core/tokens/parsers/json.d.ts.map +1 -0
- package/dist/core/tokens/parsers/json.js +8 -0
- package/dist/core/tokens/parsers/json.js.map +1 -0
- package/dist/core/tokens/parsers/scss.d.ts +3 -0
- package/dist/core/tokens/parsers/scss.d.ts.map +1 -0
- package/dist/core/tokens/parsers/scss.js +5 -0
- package/dist/core/tokens/parsers/scss.js.map +1 -0
- package/dist/core/tokens/parsers/stubs.d.ts +11 -0
- package/dist/core/tokens/parsers/stubs.d.ts.map +1 -0
- package/dist/core/tokens/parsers/stubs.js +14 -0
- package/dist/core/tokens/parsers/stubs.js.map +1 -0
- package/dist/core/tokens/parsers/style-dictionary-v3.d.ts +3 -0
- package/dist/core/tokens/parsers/style-dictionary-v3.d.ts.map +1 -0
- package/dist/core/tokens/parsers/style-dictionary-v3.js +5 -0
- package/dist/core/tokens/parsers/style-dictionary-v3.js.map +1 -0
- package/dist/core/tokens/parsers/tailwind-v3.d.ts +3 -0
- package/dist/core/tokens/parsers/tailwind-v3.d.ts.map +1 -0
- package/dist/core/tokens/parsers/tailwind-v3.js +5 -0
- package/dist/core/tokens/parsers/tailwind-v3.js.map +1 -0
- package/dist/core/tokens/parsers/tailwind-v4.d.ts +3 -0
- package/dist/core/tokens/parsers/tailwind-v4.d.ts.map +1 -0
- package/dist/core/tokens/parsers/tailwind-v4.js +5 -0
- package/dist/core/tokens/parsers/tailwind-v4.js.map +1 -0
- package/dist/core/tokens/parsers/tokens-studio.d.ts +3 -0
- package/dist/core/tokens/parsers/tokens-studio.d.ts.map +1 -0
- package/dist/core/tokens/parsers/tokens-studio.js +5 -0
- package/dist/core/tokens/parsers/tokens-studio.js.map +1 -0
- package/dist/core/tokens/schemas.d.ts +152 -0
- package/dist/core/tokens/schemas.d.ts.map +1 -0
- package/dist/core/tokens/schemas.js +149 -0
- package/dist/core/tokens/schemas.js.map +1 -0
- package/dist/core/tokens/transforms/color.d.ts +9 -0
- package/dist/core/tokens/transforms/color.d.ts.map +1 -0
- package/dist/core/tokens/transforms/color.js +13 -0
- package/dist/core/tokens/transforms/color.js.map +1 -0
- package/dist/core/tokens/transforms/index.d.ts +36 -0
- package/dist/core/tokens/transforms/index.d.ts.map +1 -0
- package/dist/core/tokens/transforms/index.js +30 -0
- package/dist/core/tokens/transforms/index.js.map +1 -0
- package/dist/core/tokens/transforms/size.d.ts +7 -0
- package/dist/core/tokens/transforms/size.d.ts.map +1 -0
- package/dist/core/tokens/transforms/size.js +8 -0
- package/dist/core/tokens/transforms/size.js.map +1 -0
- package/dist/core/tokens/types.d.ts +228 -0
- package/dist/core/tokens/types.d.ts.map +1 -0
- package/dist/core/tokens/types.js +19 -0
- package/dist/core/tokens/types.js.map +1 -0
- package/dist/core/tokens-tools.d.ts +42 -0
- package/dist/core/tokens-tools.d.ts.map +1 -0
- package/dist/core/tokens-tools.js +850 -0
- package/dist/core/tokens-tools.js.map +1 -0
- package/dist/core/types/index.d.ts +0 -8
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/core/websocket-connector.d.ts +1 -1
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-server.d.ts +4 -3
- package/dist/core/websocket-server.d.ts.map +1 -1
- package/dist/core/websocket-server.js +5 -55
- package/dist/core/websocket-server.js.map +1 -1
- package/dist/local.d.ts +0 -12
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +959 -3406
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +11 -63
- package/figma-desktop-bridge/ui.html +72 -11
- package/package.json +10 -9
- package/figma-desktop-bridge/ui-full.html +0 -1353
|
@@ -7,8 +7,8 @@ import * as fs from "fs";
|
|
|
7
7
|
import * as path from "path";
|
|
8
8
|
import { extractFileKey, extractFigmaUrlInfo, formatVariables, formatComponentData, withTimeout } from "./figma-api.js";
|
|
9
9
|
import { createChildLogger } from "./logger.js";
|
|
10
|
+
import { identifiedError, withIdentity } from "./identity.js";
|
|
10
11
|
import { EnrichmentService } from "./enrichment/index.js";
|
|
11
|
-
import { SnippetInjector } from "./snippet-injector.js";
|
|
12
12
|
import { extractNodeSpec, validateReconstructionSpec, listVariants } from "./figma-reconstruction-spec.js";
|
|
13
13
|
const logger = createChildLogger({ component: "figma-tools" });
|
|
14
14
|
// Initialize enrichment service
|
|
@@ -81,8 +81,6 @@ function scanCodebaseComponents(componentsDir) {
|
|
|
81
81
|
}
|
|
82
82
|
return registry;
|
|
83
83
|
}
|
|
84
|
-
// Initialize snippet injector
|
|
85
|
-
const snippetInjector = new SnippetInjector();
|
|
86
84
|
// ============================================================================
|
|
87
85
|
// Cache Management & Data Processing Helpers
|
|
88
86
|
// ============================================================================
|
|
@@ -132,14 +130,19 @@ function calculateSizeKB(data) {
|
|
|
132
130
|
* @returns Response content array with optional AI instruction
|
|
133
131
|
*/
|
|
134
132
|
function adaptiveResponse(responseData, options) {
|
|
135
|
-
|
|
133
|
+
// Tag every response with our MCP identity so LLMs can attribute it
|
|
134
|
+
// unambiguously when other Figma-related MCPs are also connected.
|
|
135
|
+
const tagged = responseData && typeof responseData === "object" && !Array.isArray(responseData)
|
|
136
|
+
? withIdentity(responseData)
|
|
137
|
+
: { _mcp: "figma-console-mcp", data: responseData };
|
|
138
|
+
const sizeKB = calculateSizeKB(tagged);
|
|
136
139
|
// No compression needed
|
|
137
140
|
if (sizeKB <= RESPONSE_SIZE_THRESHOLDS.IDEAL_SIZE_KB) {
|
|
138
141
|
return {
|
|
139
142
|
content: [
|
|
140
143
|
{
|
|
141
144
|
type: "text",
|
|
142
|
-
text: JSON.stringify(
|
|
145
|
+
text: JSON.stringify(tagged),
|
|
143
146
|
},
|
|
144
147
|
],
|
|
145
148
|
};
|
|
@@ -200,11 +203,14 @@ function adaptiveResponse(responseData, options) {
|
|
|
200
203
|
});
|
|
201
204
|
}
|
|
202
205
|
}
|
|
203
|
-
// Build response content
|
|
206
|
+
// Build response content (tagged with identity so cross-MCP attribution is clear)
|
|
207
|
+
const taggedFinal = finalData && typeof finalData === "object" && !Array.isArray(finalData)
|
|
208
|
+
? withIdentity(finalData)
|
|
209
|
+
: { _mcp: "figma-console-mcp", data: finalData };
|
|
204
210
|
const content = [
|
|
205
211
|
{
|
|
206
212
|
type: "text",
|
|
207
|
-
text: JSON.stringify(
|
|
213
|
+
text: JSON.stringify(taggedFinal),
|
|
208
214
|
},
|
|
209
215
|
];
|
|
210
216
|
// Add AI instruction as separate content block if needed
|
|
@@ -647,8 +653,21 @@ function resolveVariableAliases(variables, allVariablesMap, collectionsMap) {
|
|
|
647
653
|
/**
|
|
648
654
|
* Register Figma API tools with the MCP server
|
|
649
655
|
*/
|
|
650
|
-
export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl,
|
|
656
|
+
export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, variablesCache, options, getDesktopConnector) {
|
|
651
657
|
const isRemoteMode = options?.isRemoteMode ?? false;
|
|
658
|
+
/**
|
|
659
|
+
* Build a designer-readable REST-auth error tagged with our MCP identity.
|
|
660
|
+
* Shows only the remediation path that applies to the caller's mode — local
|
|
661
|
+
* users never see OAuth instructions, cloud users never see env-var
|
|
662
|
+
* instructions. Identity prefix lets LLMs disambiguate this error from
|
|
663
|
+
* errors thrown by other Figma-related MCP servers running in parallel.
|
|
664
|
+
*/
|
|
665
|
+
function restAuthError(context, originalError, extra) {
|
|
666
|
+
const remediation = isRemoteMode
|
|
667
|
+
? "Re-authenticate via the OAuth flow in your MCP client, or pass a Figma personal access token (figd_...) as a Bearer token."
|
|
668
|
+
: "Set FIGMA_ACCESS_TOKEN in your MCP client config to a Figma personal access token. Generate one at https://www.figma.com/developers/api#access-tokens.";
|
|
669
|
+
return identifiedError(`${context}\nError: ${originalError}\n\nTo fix: ${remediation}${extra ? `\n\n${extra}` : ""}`);
|
|
670
|
+
}
|
|
652
671
|
// Tool 8: Get File Data (General Purpose)
|
|
653
672
|
// NOTE: For specific use cases, consider using specialized tools:
|
|
654
673
|
// - figma_get_component_for_development: For UI component implementation
|
|
@@ -688,13 +707,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
688
707
|
}
|
|
689
708
|
catch (apiError) {
|
|
690
709
|
const errorMessage = apiError instanceof Error ? apiError.message : String(apiError);
|
|
691
|
-
throw
|
|
692
|
-
`Error: ${errorMessage}\n\n` +
|
|
693
|
-
`To fix:\n` +
|
|
694
|
-
`1. Local mode: Set FIGMA_ACCESS_TOKEN environment variable\n` +
|
|
695
|
-
`2. Cloud mode: Authenticate via OAuth\n\n` +
|
|
696
|
-
`Note: figma_get_file_data requires REST API access. ` +
|
|
697
|
-
`For component-specific data, use figma_get_component which has Desktop Bridge fallback.`);
|
|
710
|
+
throw restAuthError("Cannot retrieve file data. REST API authentication required.", errorMessage, "Note: figma_get_file_data requires REST API access. For component-specific data, use figma_get_component which has Desktop Bridge fallback.");
|
|
698
711
|
}
|
|
699
712
|
// Use provided URL or current URL from browser
|
|
700
713
|
const url = fileUrl || getCurrentUrl();
|
|
@@ -843,18 +856,17 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
843
856
|
/**
|
|
844
857
|
* Tool 9: Get Variables (Design Tokens)
|
|
845
858
|
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
* 1. First call: Returns snippet + instructions (useConsoleFallback: true, default)
|
|
852
|
-
* 2. User runs snippet in Figma plugin console
|
|
853
|
-
* 3. Second call: Parses captured data (parseFromConsole: true)
|
|
859
|
+
* RESOLUTION ORDER (in order, first success wins):
|
|
860
|
+
* 1. Cache hit (if fresh and refreshCache=false)
|
|
861
|
+
* 2. Desktop Bridge plugin via WebSocket — works on any Figma plan
|
|
862
|
+
* 3. REST API — requires Enterprise plan (returns 403 otherwise)
|
|
863
|
+
* 4. Styles API — partial fallback for non-Enterprise (styles, not variables)
|
|
854
864
|
*
|
|
855
|
-
*
|
|
865
|
+
* The legacy `parseFromConsole` two-call console-snippet workflow was
|
|
866
|
+
* removed in the Phase 3 cleanup. Setting parseFromConsole=true now
|
|
867
|
+
* throws an identified error pointing the caller at the bridge.
|
|
856
868
|
*/
|
|
857
|
-
server.tool("figma_get_variables", "Extract design tokens and variables from a Figma file with code export support (CSS, Tailwind, TypeScript, Sass). Use when user asks for: design system tokens, variables, color/spacing values, theme data, or code exports. Handles multi-mode variables (Light/Dark themes). NOT for component metadata (use figma_get_component). Supports filtering by collection/mode/name and verbosity control to prevent token exhaustion.
|
|
869
|
+
server.tool("figma_get_variables", "Extract design tokens and variables from a Figma file with code export support (CSS, Tailwind, TypeScript, Sass). Use when user asks for: design system tokens, variables, color/spacing values, theme data, or code exports. Handles multi-mode variables (Light/Dark themes). NOT for component metadata (use figma_get_component). Supports filtering by collection/mode/name and verbosity control to prevent token exhaustion. Resolution order: Desktop Bridge plugin (works on any plan) → Variables REST API (Enterprise only) → Styles API as a partial fallback. TIP: For full design system extraction (tokens + components + styles combined), prefer figma_get_design_system_kit instead — it returns everything in one optimized call.", {
|
|
858
870
|
fileUrl: z
|
|
859
871
|
.string()
|
|
860
872
|
.url()
|
|
@@ -912,7 +924,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
912
924
|
.boolean()
|
|
913
925
|
.optional()
|
|
914
926
|
.default(false)
|
|
915
|
-
.describe("Return variables as resource_link references instead of full data. Drastically reduces payload size (100+ variables = ~20KB vs >1MB).
|
|
927
|
+
.describe("Return variables as resource_link references instead of full data. Drastically reduces payload size (100+ variables = ~20KB vs >1MB). Recommended for large variable sets — combine with format='filtered' + namePattern/collection/mode to fetch only the variables you need. Default: false"),
|
|
916
928
|
refreshCache: z
|
|
917
929
|
.boolean()
|
|
918
930
|
.optional()
|
|
@@ -922,20 +934,12 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
922
934
|
.boolean()
|
|
923
935
|
.optional()
|
|
924
936
|
.default(true)
|
|
925
|
-
.describe("
|
|
926
|
-
"When enabled, provides a JavaScript snippet that users run in Figma's plugin console. " +
|
|
927
|
-
"This is STEP 1 of a two-call workflow. After receiving the snippet, instruct the user to run it, then call this tool again with parseFromConsole=true. " +
|
|
928
|
-
"Default: true. Set to false only to disable the fallback entirely."),
|
|
937
|
+
.describe("DEPRECATED — has no effect. The console-snippet workflow was removed in the Phase 3 CDP cleanup; the Desktop Bridge plugin now handles all non-REST variable extraction automatically. Kept for parameter compatibility only — safe to ignore."),
|
|
929
938
|
parseFromConsole: z
|
|
930
939
|
.boolean()
|
|
931
940
|
.optional()
|
|
932
941
|
.default(false)
|
|
933
|
-
.describe("
|
|
934
|
-
"This is STEP 2 of the two-call workflow. Set to true ONLY after: " +
|
|
935
|
-
"(1) you received a console snippet from the first call, " +
|
|
936
|
-
"(2) instructed the user to run it in Figma's PLUGIN console (Plugins → Development → Open Console or existing plugin), " +
|
|
937
|
-
"(3) user confirmed they ran the snippet and saw '✅ Variables data captured!' message. " +
|
|
938
|
-
"Default: false. Never set to true on the first call."),
|
|
942
|
+
.describe("DEPRECATED — setting this to true now raises an explicit error. The Puppeteer-based console parser no longer exists. Open the Figma Console MCP Desktop Bridge plugin in Figma Desktop and call figma_get_variables() without parseFromConsole; the plugin returns full variable data through the WebSocket bridge."),
|
|
939
943
|
page: z
|
|
940
944
|
.number()
|
|
941
945
|
.int()
|
|
@@ -1271,13 +1275,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
1271
1275
|
// Check if REST API token is available
|
|
1272
1276
|
const hasToken = !!process.env.FIGMA_ACCESS_TOKEN;
|
|
1273
1277
|
let restApiSucceeded = false;
|
|
1274
|
-
|
|
1275
|
-
if (ensureInitialized && !getDesktopConnector && !parseFromConsole) {
|
|
1276
|
-
logger.info("Calling ensureInitialized to initialize browser manager (legacy path)");
|
|
1277
|
-
await ensureInitialized();
|
|
1278
|
-
}
|
|
1279
|
-
const browserManager = getBrowserManager?.();
|
|
1280
|
-
const hasDesktopConnection = !!getDesktopConnector || !!browserManager;
|
|
1278
|
+
const hasDesktopConnection = !!getDesktopConnector;
|
|
1281
1279
|
// PRIORITY LOGIC:
|
|
1282
1280
|
// 1. If Desktop Bridge connected → Try Desktop Bridge FIRST (instant, all plans, full Plugin API data)
|
|
1283
1281
|
// 2. If no Desktop Bridge OR it fails → Try REST API as fallback (Enterprise users)
|
|
@@ -1434,7 +1432,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
1434
1432
|
const content = [
|
|
1435
1433
|
{
|
|
1436
1434
|
type: "text",
|
|
1437
|
-
text: `Variables for file ${fileKey} (${localFormatted.variables.length} variables).
|
|
1435
|
+
text: `Variables for file ${fileKey} (${localFormatted.variables.length} variables). Call figma_get_variables again with format='filtered' and a namePattern/collection/mode filter to fetch specific variables:\n\n`,
|
|
1438
1436
|
},
|
|
1439
1437
|
];
|
|
1440
1438
|
for (const variable of localFormatted.variables) {
|
|
@@ -1529,21 +1527,11 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
1529
1527
|
}
|
|
1530
1528
|
// PRIMARY: Try Desktop Bridge (instant, all plans, full Plugin API data including aliases)
|
|
1531
1529
|
// Also used as fallback when REST API fails (403, timeout, rate limit)
|
|
1532
|
-
if (hasDesktopConnection && !parseFromConsole && !restApiSucceeded) {
|
|
1530
|
+
if (hasDesktopConnection && !parseFromConsole && !restApiSucceeded && getDesktopConnector) {
|
|
1533
1531
|
try {
|
|
1534
1532
|
logger.info({ fileKey }, "Attempting to get variables via Desktop connection");
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
connector = await getDesktopConnector();
|
|
1538
|
-
}
|
|
1539
|
-
else {
|
|
1540
|
-
// Fallback: direct connector (legacy path)
|
|
1541
|
-
const { FigmaDesktopConnector } = await import('./figma-desktop-connector.js');
|
|
1542
|
-
const page = await browserManager.getPage();
|
|
1543
|
-
connector = new FigmaDesktopConnector(page);
|
|
1544
|
-
await connector.initialize();
|
|
1545
|
-
}
|
|
1546
|
-
logger.info({ transport: connector.getTransportType?.() || 'unknown' }, "Desktop connector ready");
|
|
1533
|
+
const connector = await getDesktopConnector();
|
|
1534
|
+
logger.info({ transport: connector.getTransportType?.() || 'websocket' }, "Desktop connector ready");
|
|
1547
1535
|
// When refreshCache is requested, bypass the plugin UI's stale snapshot
|
|
1548
1536
|
// and fetch live data directly from the Figma Plugin API
|
|
1549
1537
|
const desktopResult = refreshCache
|
|
@@ -1551,7 +1539,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
1551
1539
|
: await connector.getVariablesFromPluginUI(fileKey);
|
|
1552
1540
|
// EXECUTE_CODE responses come back wrapped one level deeper:
|
|
1553
1541
|
// `{ success: true, result: { success: true, variables, ... } }`
|
|
1554
|
-
// because handleResult in ui
|
|
1542
|
+
// because handleResult in ui.html nests the script return value
|
|
1555
1543
|
// under `result`. The plugin-UI cache path (GET_VARIABLES_DATA) does
|
|
1556
1544
|
// not nest. Unwrap when we detect the EXECUTE_CODE shape so both
|
|
1557
1545
|
// paths produce a uniform { success, variables, ... } below. See #68.
|
|
@@ -1742,21 +1730,6 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
1742
1730
|
message: errorMessage,
|
|
1743
1731
|
stack: errorStack
|
|
1744
1732
|
}, "Desktop connection failed, falling back to other methods");
|
|
1745
|
-
// Try to log to browser console if we have access to page
|
|
1746
|
-
try {
|
|
1747
|
-
if (browserManager) {
|
|
1748
|
-
const page = await browserManager.getPage();
|
|
1749
|
-
await page.evaluate((msg, stack) => {
|
|
1750
|
-
console.error('[FIGMA_TOOLS] ❌ Desktop connection failed:', msg);
|
|
1751
|
-
if (stack) {
|
|
1752
|
-
console.error('[FIGMA_TOOLS] Stack trace:', stack);
|
|
1753
|
-
}
|
|
1754
|
-
}, errorMessage, errorStack);
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
catch (logError) {
|
|
1758
|
-
// Ignore logging errors
|
|
1759
|
-
}
|
|
1760
1733
|
// Continue to try REST API fallback
|
|
1761
1734
|
}
|
|
1762
1735
|
}
|
|
@@ -1827,53 +1800,18 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
1827
1800
|
logger.warn({ error: msg, fileKey }, "REST API fallback failed");
|
|
1828
1801
|
}
|
|
1829
1802
|
}
|
|
1830
|
-
// LAST RESORT:
|
|
1803
|
+
// LAST RESORT: parseFromConsole was a Puppeteer-era workflow that read
|
|
1804
|
+
// the magic-string output of a console snippet from the browser's
|
|
1805
|
+
// console buffer. After the Phase 3 CDP cleanup there is no longer a
|
|
1806
|
+
// Puppeteer-attached console for the snippet's output to land in, so
|
|
1807
|
+
// the flag has become a no-op. Tell the caller what to do instead.
|
|
1831
1808
|
if (parseFromConsole) {
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
const logs = consoleMonitor.getLogs({ count: 100, level: "log" });
|
|
1839
|
-
const varLog = snippetInjector.findVariablesLog(logs);
|
|
1840
|
-
if (!varLog) {
|
|
1841
|
-
throw new Error("No variables found in console logs.\n\n" +
|
|
1842
|
-
"Did you run the snippet in Figma's plugin console? Here's the correct workflow:\n\n" +
|
|
1843
|
-
"1. Call figma_get_variables() without parameters (you may have already done this)\n" +
|
|
1844
|
-
"2. Copy the provided snippet\n" +
|
|
1845
|
-
"3. Open Figma Desktop → Plugins → Development → Open Console\n" +
|
|
1846
|
-
"4. Paste and run the snippet in the PLUGIN console (not browser DevTools)\n" +
|
|
1847
|
-
"5. Wait for '✅ Variables data captured!' confirmation\n" +
|
|
1848
|
-
"6. Then call figma_get_variables({ parseFromConsole: true })\n\n" +
|
|
1849
|
-
"Note: The browser console won't work - you need a plugin console for the figma.variables API.");
|
|
1850
|
-
}
|
|
1851
|
-
// Parse variables from log
|
|
1852
|
-
const parsedData = snippetInjector.parseVariablesFromLog(varLog);
|
|
1853
|
-
if (!parsedData) {
|
|
1854
|
-
throw new Error("Failed to parse variables from console log");
|
|
1855
|
-
}
|
|
1856
|
-
return {
|
|
1857
|
-
content: [
|
|
1858
|
-
{
|
|
1859
|
-
type: "text",
|
|
1860
|
-
text: JSON.stringify({
|
|
1861
|
-
fileKey,
|
|
1862
|
-
source: "console_capture",
|
|
1863
|
-
local: {
|
|
1864
|
-
summary: {
|
|
1865
|
-
total_variables: parsedData.variables.length,
|
|
1866
|
-
total_collections: parsedData.variableCollections.length,
|
|
1867
|
-
},
|
|
1868
|
-
collections: parsedData.variableCollections,
|
|
1869
|
-
variables: parsedData.variables,
|
|
1870
|
-
},
|
|
1871
|
-
timestamp: parsedData.timestamp,
|
|
1872
|
-
enriched: false,
|
|
1873
|
-
}),
|
|
1874
|
-
},
|
|
1875
|
-
],
|
|
1876
|
-
};
|
|
1809
|
+
throw identifiedError("parseFromConsole is no longer supported.\n\n" +
|
|
1810
|
+
"The console-snippet workflow it relied on required a Puppeteer browser " +
|
|
1811
|
+
"connection to Figma Desktop, which was removed in Phase 3 of the cleanup. " +
|
|
1812
|
+
"To extract variables, open the Figma Console MCP Desktop Bridge plugin in " +
|
|
1813
|
+
"Figma Desktop and call figma_get_variables() without parseFromConsole — " +
|
|
1814
|
+
"the plugin returns full variable data through the WebSocket bridge.");
|
|
1877
1815
|
}
|
|
1878
1816
|
// No more fallback options available
|
|
1879
1817
|
throw new Error(`Cannot retrieve variables. All methods failed.\n\n` +
|
|
@@ -1882,8 +1820,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
1882
1820
|
`✗ Desktop Bridge (failed or not available)\n` +
|
|
1883
1821
|
`\nTo fix:\n` +
|
|
1884
1822
|
`1. If you have FIGMA_ACCESS_TOKEN: Check your token permissions\n` +
|
|
1885
|
-
`2. Install and run the Figma Desktop Bridge plugin
|
|
1886
|
-
`3. Alternative: Use parseFromConsole=true with console snippet workflow`);
|
|
1823
|
+
`2. Install and run the Figma Desktop Bridge plugin and re-run this tool`);
|
|
1887
1824
|
}
|
|
1888
1825
|
catch (error) {
|
|
1889
1826
|
logger.error({ error }, "Failed to get variables");
|
|
@@ -1898,11 +1835,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
1898
1835
|
}
|
|
1899
1836
|
catch (apiError) {
|
|
1900
1837
|
const errorMessage = apiError instanceof Error ? apiError.message : String(apiError);
|
|
1901
|
-
throw
|
|
1902
|
-
`Error: ${errorMessage}\n\n` +
|
|
1903
|
-
`To fix:\n` +
|
|
1904
|
-
`1. Local mode: Set FIGMA_ACCESS_TOKEN environment variable\n` +
|
|
1905
|
-
`2. Cloud mode: Authenticate via OAuth`);
|
|
1838
|
+
throw restAuthError("Cannot retrieve variables or styles. REST API authentication required for both.", errorMessage);
|
|
1906
1839
|
}
|
|
1907
1840
|
// Use the Styles API directly - much faster than getFile!
|
|
1908
1841
|
const stylesData = await api.getStyles(fileKey);
|
|
@@ -2006,26 +1939,10 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
2006
1939
|
}
|
|
2007
1940
|
logger.info({ fileKey, nodeId, format, enrich }, "Fetching component data");
|
|
2008
1941
|
// PRIORITY 1: Try Desktop Bridge plugin UI first (has reliable description field!)
|
|
2009
|
-
if (getDesktopConnector
|
|
1942
|
+
if (getDesktopConnector) {
|
|
2010
1943
|
try {
|
|
2011
1944
|
logger.info({ nodeId }, "Attempting to get component via Desktop Bridge plugin UI");
|
|
2012
|
-
|
|
2013
|
-
if (getDesktopConnector) {
|
|
2014
|
-
connector = await getDesktopConnector();
|
|
2015
|
-
}
|
|
2016
|
-
else {
|
|
2017
|
-
// Fallback: direct connector (legacy path)
|
|
2018
|
-
if (ensureInitialized)
|
|
2019
|
-
await ensureInitialized();
|
|
2020
|
-
const browserManager = getBrowserManager?.();
|
|
2021
|
-
if (!browserManager) {
|
|
2022
|
-
throw new Error("Browser manager not available after initialization");
|
|
2023
|
-
}
|
|
2024
|
-
const { FigmaDesktopConnector } = await import('./figma-desktop-connector.js');
|
|
2025
|
-
const page = await browserManager.getPage();
|
|
2026
|
-
connector = new FigmaDesktopConnector(page);
|
|
2027
|
-
await connector.initialize();
|
|
2028
|
-
}
|
|
1945
|
+
const connector = await getDesktopConnector();
|
|
2029
1946
|
const desktopResult = await connector.getComponentFromPluginUI(nodeId);
|
|
2030
1947
|
if (desktopResult.success && desktopResult.component) {
|
|
2031
1948
|
logger.info({
|
|
@@ -2132,13 +2049,10 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
2132
2049
|
}
|
|
2133
2050
|
catch (apiError) {
|
|
2134
2051
|
const errorMessage = apiError instanceof Error ? apiError.message : String(apiError);
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
`1. Local mode: Set FIGMA_ACCESS_TOKEN environment variable, OR ensure Figma Desktop Bridge plugin is running\n` +
|
|
2140
|
-
`2. Cloud mode: Authenticate via OAuth\n` +
|
|
2141
|
-
`3. Ensure the Desktop Bridge plugin is running in Figma Desktop`);
|
|
2052
|
+
const dbStatus = getDesktopConnector
|
|
2053
|
+
? "Failed (see logs above)"
|
|
2054
|
+
: "Not available";
|
|
2055
|
+
throw restAuthError("Cannot retrieve component data. Both Desktop Bridge and REST API are unavailable.", `Desktop Bridge: ${dbStatus}; REST API: ${errorMessage}`, "Alternatively: open the Figma Desktop Bridge plugin in Figma Desktop to enable the plugin-based fallback.");
|
|
2142
2056
|
}
|
|
2143
2057
|
const componentData = await api.getComponentData(fileKey, nodeId);
|
|
2144
2058
|
if (!componentData) {
|
|
@@ -2267,11 +2181,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
2267
2181
|
}
|
|
2268
2182
|
catch (apiError) {
|
|
2269
2183
|
const errorMessage = apiError instanceof Error ? apiError.message : String(apiError);
|
|
2270
|
-
throw
|
|
2271
|
-
`Error: ${errorMessage}\n\n` +
|
|
2272
|
-
`To fix:\n` +
|
|
2273
|
-
`1. Local mode: Set FIGMA_ACCESS_TOKEN environment variable\n` +
|
|
2274
|
-
`2. Cloud mode: Authenticate via OAuth`);
|
|
2184
|
+
throw restAuthError("Cannot retrieve styles. REST API authentication required.", errorMessage);
|
|
2275
2185
|
}
|
|
2276
2186
|
const url = fileUrl || getCurrentUrl();
|
|
2277
2187
|
if (!url) {
|
|
@@ -2406,13 +2316,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
2406
2316
|
}
|
|
2407
2317
|
catch (apiError) {
|
|
2408
2318
|
const errorMessage = apiError instanceof Error ? apiError.message : String(apiError);
|
|
2409
|
-
throw
|
|
2410
|
-
`Error: ${errorMessage}\n\n` +
|
|
2411
|
-
`To fix:\n` +
|
|
2412
|
-
`1. Local mode: Set FIGMA_ACCESS_TOKEN environment variable\n` +
|
|
2413
|
-
`2. Cloud mode: Authenticate via OAuth\n\n` +
|
|
2414
|
-
`Note: For component screenshots, figma_capture_screenshot may work as an alternative ` +
|
|
2415
|
-
`if the Desktop Bridge plugin is connected.`);
|
|
2319
|
+
throw restAuthError("Cannot render component image. REST API authentication required.", errorMessage, "Note: For component screenshots, figma_capture_screenshot may work as an alternative if the Desktop Bridge plugin is connected.");
|
|
2416
2320
|
}
|
|
2417
2321
|
const url = fileUrl || getCurrentUrl();
|
|
2418
2322
|
if (!url) {
|
|
@@ -2525,13 +2429,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
2525
2429
|
}
|
|
2526
2430
|
catch (apiError) {
|
|
2527
2431
|
const errorMessage = apiError instanceof Error ? apiError.message : String(apiError);
|
|
2528
|
-
throw
|
|
2529
|
-
`Error: ${errorMessage}\n\n` +
|
|
2530
|
-
`To fix:\n` +
|
|
2531
|
-
`1. Local mode: Set FIGMA_ACCESS_TOKEN environment variable\n` +
|
|
2532
|
-
`2. Cloud mode: Authenticate via OAuth\n\n` +
|
|
2533
|
-
`Note: For component metadata, figma_get_component has Desktop Bridge fallback ` +
|
|
2534
|
-
`that works without token (requires the Desktop Bridge plugin to be connected).`);
|
|
2432
|
+
throw restAuthError("Cannot retrieve component for development. REST API authentication required.", errorMessage, "Note: For component metadata, figma_get_component has a Desktop Bridge fallback that works without a token (requires the Desktop Bridge plugin to be connected).");
|
|
2535
2433
|
}
|
|
2536
2434
|
const url = fileUrl || getCurrentUrl();
|
|
2537
2435
|
if (!url) {
|
|
@@ -2971,11 +2869,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
2971
2869
|
}
|
|
2972
2870
|
catch (apiError) {
|
|
2973
2871
|
const errorMessage = apiError instanceof Error ? apiError.message : String(apiError);
|
|
2974
|
-
throw
|
|
2975
|
-
`Error: ${errorMessage}\n\n` +
|
|
2976
|
-
`To fix:\n` +
|
|
2977
|
-
`1. Local mode: Set FIGMA_ACCESS_TOKEN environment variable\n` +
|
|
2978
|
-
`2. Cloud mode: Authenticate via OAuth`);
|
|
2872
|
+
throw restAuthError("Cannot retrieve file data for plugin development. REST API authentication required.", errorMessage);
|
|
2979
2873
|
}
|
|
2980
2874
|
const url = fileUrl || getCurrentUrl();
|
|
2981
2875
|
if (!url) {
|
|
@@ -3166,33 +3060,6 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
3166
3060
|
result = { success: true, image: result };
|
|
3167
3061
|
}
|
|
3168
3062
|
}
|
|
3169
|
-
// Legacy CDP fallback (only when no connector factory is available)
|
|
3170
|
-
if (!result && !getDesktopConnector) {
|
|
3171
|
-
const browserManager = getBrowserManager?.();
|
|
3172
|
-
if (!browserManager) {
|
|
3173
|
-
throw new Error("Desktop Bridge not available. To capture screenshots:\n" +
|
|
3174
|
-
"1. Open your Figma file in Figma Desktop\n" +
|
|
3175
|
-
"2. Install and run the 'Figma Console MCP' plugin\n" +
|
|
3176
|
-
"3. Ensure the plugin shows 'MCP ready' status");
|
|
3177
|
-
}
|
|
3178
|
-
if (ensureInitialized) {
|
|
3179
|
-
await ensureInitialized();
|
|
3180
|
-
}
|
|
3181
|
-
const page = await browserManager.getPage();
|
|
3182
|
-
const frames = page.frames();
|
|
3183
|
-
for (const frame of frames) {
|
|
3184
|
-
try {
|
|
3185
|
-
const hasFunction = await frame.evaluate('typeof window.captureScreenshot === "function"');
|
|
3186
|
-
if (hasFunction) {
|
|
3187
|
-
result = await frame.evaluate(`window.captureScreenshot(${JSON.stringify(nodeId || '')}, ${JSON.stringify({ format, scale })})`);
|
|
3188
|
-
break;
|
|
3189
|
-
}
|
|
3190
|
-
}
|
|
3191
|
-
catch {
|
|
3192
|
-
continue;
|
|
3193
|
-
}
|
|
3194
|
-
}
|
|
3195
|
-
}
|
|
3196
3063
|
if (!result) {
|
|
3197
3064
|
throw new Error("Desktop Bridge plugin not found. Ensure the 'Figma Console MCP' plugin is running in Figma Desktop.");
|
|
3198
3065
|
}
|
|
@@ -3271,33 +3138,6 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
|
|
|
3271
3138
|
logger.info({ transport: connector.getTransportType?.() || 'unknown' }, "Instance properties via connector");
|
|
3272
3139
|
result = await connector.setInstanceProperties(nodeId, properties);
|
|
3273
3140
|
}
|
|
3274
|
-
// Legacy CDP fallback (only when no connector factory is available)
|
|
3275
|
-
if (!result && !getDesktopConnector) {
|
|
3276
|
-
const browserManager = getBrowserManager?.();
|
|
3277
|
-
if (!browserManager) {
|
|
3278
|
-
throw new Error("Desktop Bridge not available. To set instance properties:\n" +
|
|
3279
|
-
"1. Open your Figma file in Figma Desktop\n" +
|
|
3280
|
-
"2. Install and run the 'Figma Console MCP' plugin\n" +
|
|
3281
|
-
"3. Ensure the plugin shows 'MCP ready' status");
|
|
3282
|
-
}
|
|
3283
|
-
if (ensureInitialized) {
|
|
3284
|
-
await ensureInitialized();
|
|
3285
|
-
}
|
|
3286
|
-
const page = await browserManager.getPage();
|
|
3287
|
-
const frames = page.frames();
|
|
3288
|
-
for (const frame of frames) {
|
|
3289
|
-
try {
|
|
3290
|
-
const hasFunction = await frame.evaluate('typeof window.setInstanceProperties === "function"');
|
|
3291
|
-
if (hasFunction) {
|
|
3292
|
-
result = await frame.evaluate(`window.setInstanceProperties(${JSON.stringify(nodeId)}, ${JSON.stringify(properties)})`);
|
|
3293
|
-
break;
|
|
3294
|
-
}
|
|
3295
|
-
}
|
|
3296
|
-
catch {
|
|
3297
|
-
continue;
|
|
3298
|
-
}
|
|
3299
|
-
}
|
|
3300
|
-
}
|
|
3301
3141
|
if (!result) {
|
|
3302
3142
|
throw new Error("Desktop Bridge plugin not found. Ensure the 'Figma Console MCP' plugin is running in Figma Desktop.");
|
|
3303
3143
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server identity helpers.
|
|
3
|
+
*
|
|
4
|
+
* When a user has multiple Figma-related MCP servers configured at once
|
|
5
|
+
* (e.g. figma-console-mcp alongside Figma's native codegen MCP), an LLM can
|
|
6
|
+
* conflate errors from one server with the troubleshooting copy of another —
|
|
7
|
+
* producing remediation advice that points at the wrong tool. Tagging our
|
|
8
|
+
* responses with an explicit `[figma-console-mcp]` prefix and an `_mcp`
|
|
9
|
+
* field makes attribution unambiguous.
|
|
10
|
+
*/
|
|
11
|
+
export const MCP_NAME = "figma-console-mcp";
|
|
12
|
+
export const ERROR_PREFIX = `[${MCP_NAME}]`;
|
|
13
|
+
/**
|
|
14
|
+
* Prefix a thrown-error message with our MCP identity so cross-tool errors
|
|
15
|
+
* can't be mistakenly attributed to this server.
|
|
16
|
+
*/
|
|
17
|
+
export function identifiedError(message) {
|
|
18
|
+
return new Error(`${ERROR_PREFIX} ${message}`);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Tag a response payload with our MCP identity at the top level.
|
|
22
|
+
* The `_mcp` field is read by LLMs alongside the rest of the response and
|
|
23
|
+
* gives them a reliable signal for "which server produced this output".
|
|
24
|
+
*/
|
|
25
|
+
export function withIdentity(data) {
|
|
26
|
+
return { _mcp: MCP_NAME, ...data };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Tag every text/JSON content block in a tool response with our MCP identity.
|
|
30
|
+
* Idempotent — already-tagged content (from adaptiveResponse or explicit
|
|
31
|
+
* withIdentity calls) is left alone. Non-JSON text content is left alone.
|
|
32
|
+
*/
|
|
33
|
+
function tagToolResponse(result) {
|
|
34
|
+
if (!result ||
|
|
35
|
+
typeof result !== "object" ||
|
|
36
|
+
!("content" in result) ||
|
|
37
|
+
!Array.isArray(result.content)) {
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
const r = result;
|
|
41
|
+
const newContent = r.content.map((item) => {
|
|
42
|
+
if (item.type !== "text" || typeof item.text !== "string")
|
|
43
|
+
return item;
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(item.text);
|
|
46
|
+
if (parsed &&
|
|
47
|
+
typeof parsed === "object" &&
|
|
48
|
+
!Array.isArray(parsed) &&
|
|
49
|
+
!("_mcp" in parsed)) {
|
|
50
|
+
return { ...item, text: JSON.stringify({ _mcp: MCP_NAME, ...parsed }) };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Not JSON — leave the text untouched (e.g. AI instruction blocks
|
|
55
|
+
// emitted by adaptiveResponse, or plain-text error messages).
|
|
56
|
+
}
|
|
57
|
+
return item;
|
|
58
|
+
});
|
|
59
|
+
return { ...r, content: newContent };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Monkey-patch an MCP server instance so every tool registered on it gets
|
|
63
|
+
* identity tagging applied to its responses and an identity prefix on any
|
|
64
|
+
* Error it throws — without modifying the ~97 individual tool handlers.
|
|
65
|
+
*
|
|
66
|
+
* Call this once, immediately after constructing the McpServer, BEFORE any
|
|
67
|
+
* tool registration calls run. The wrap is idempotent at the response level
|
|
68
|
+
* (tools that already tag themselves via withIdentity or adaptiveResponse
|
|
69
|
+
* won't get double-tagged).
|
|
70
|
+
*
|
|
71
|
+
* Adds attribution coverage to every response path uniformly — see
|
|
72
|
+
* project_lauren_cross_mcp_confusion for why this matters.
|
|
73
|
+
*/
|
|
74
|
+
export function wrapServerForIdentity(server) {
|
|
75
|
+
const target = server;
|
|
76
|
+
const originalTool = target.tool.bind(target);
|
|
77
|
+
target.tool = function (...args) {
|
|
78
|
+
if (args.length === 0 || typeof args[args.length - 1] !== "function") {
|
|
79
|
+
return originalTool(...args);
|
|
80
|
+
}
|
|
81
|
+
const handler = args[args.length - 1];
|
|
82
|
+
const wrappedHandler = async (...handlerArgs) => {
|
|
83
|
+
try {
|
|
84
|
+
const result = await handler(...handlerArgs);
|
|
85
|
+
return tagToolResponse(result);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
if (err instanceof Error && !err.message.startsWith(ERROR_PREFIX)) {
|
|
89
|
+
err.message = `${ERROR_PREFIX} ${err.message}`;
|
|
90
|
+
}
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
return originalTool(...args.slice(0, -1), wrappedHandler);
|
|
95
|
+
};
|
|
96
|
+
}
|