@mp3wizard/figma-console-mcp 1.17.3 → 1.19.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 +13 -12
- package/dist/cloudflare/core/annotation-tools.js +230 -0
- package/dist/cloudflare/core/cloud-websocket-connector.js +93 -0
- package/dist/cloudflare/core/deep-component-tools.js +128 -0
- package/dist/cloudflare/core/design-code-tools.js +65 -7
- package/dist/cloudflare/core/enrichment/enrichment-service.js +108 -12
- package/dist/cloudflare/core/figjam-tools.js +485 -0
- package/dist/cloudflare/core/figma-api.js +7 -4
- package/dist/cloudflare/core/figma-desktop-connector.js +108 -0
- package/dist/cloudflare/core/figma-tools.js +445 -55
- package/dist/cloudflare/core/port-discovery.js +88 -0
- package/dist/cloudflare/core/resolve-package-root.js +11 -0
- package/dist/cloudflare/core/slides-tools.js +607 -0
- package/dist/cloudflare/core/websocket-connector.js +93 -0
- package/dist/cloudflare/core/websocket-server.js +18 -9
- package/dist/cloudflare/index.js +164 -41
- package/dist/core/annotation-tools.d.ts +14 -0
- package/dist/core/annotation-tools.d.ts.map +1 -0
- package/dist/core/annotation-tools.js +231 -0
- package/dist/core/annotation-tools.js.map +1 -0
- package/dist/core/deep-component-tools.d.ts +14 -0
- package/dist/core/deep-component-tools.d.ts.map +1 -0
- package/dist/core/deep-component-tools.js +129 -0
- package/dist/core/deep-component-tools.js.map +1 -0
- package/dist/core/design-code-tools.d.ts.map +1 -1
- package/dist/core/design-code-tools.js +65 -7
- package/dist/core/design-code-tools.js.map +1 -1
- package/dist/core/enrichment/enrichment-service.d.ts.map +1 -1
- package/dist/core/enrichment/enrichment-service.js +108 -12
- package/dist/core/enrichment/enrichment-service.js.map +1 -1
- package/dist/core/figma-api.d.ts +1 -1
- package/dist/core/figma-api.d.ts.map +1 -1
- package/dist/core/figma-api.js +7 -4
- package/dist/core/figma-api.js.map +1 -1
- package/dist/core/figma-connector.d.ts +5 -0
- package/dist/core/figma-connector.d.ts.map +1 -1
- package/dist/core/figma-desktop-connector.d.ts +20 -0
- package/dist/core/figma-desktop-connector.d.ts.map +1 -1
- package/dist/core/figma-desktop-connector.js +83 -0
- package/dist/core/figma-desktop-connector.js.map +1 -1
- package/dist/core/figma-tools.d.ts.map +1 -1
- package/dist/core/figma-tools.js +355 -26
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/port-discovery.d.ts +21 -0
- package/dist/core/port-discovery.d.ts.map +1 -1
- package/dist/core/port-discovery.js +88 -0
- package/dist/core/port-discovery.js.map +1 -1
- package/dist/core/resolve-package-root.d.ts +2 -0
- package/dist/core/resolve-package-root.d.ts.map +1 -0
- package/dist/core/resolve-package-root.js +12 -0
- package/dist/core/resolve-package-root.js.map +1 -0
- package/dist/core/types/design-code.d.ts +1 -0
- package/dist/core/types/design-code.d.ts.map +1 -1
- package/dist/core/websocket-connector.d.ts +5 -0
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-connector.js +18 -0
- package/dist/core/websocket-connector.js.map +1 -1
- package/dist/core/websocket-server.d.ts.map +1 -1
- package/dist/core/websocket-server.js +7 -9
- package/dist/core/websocket-server.js.map +1 -1
- package/dist/local.d.ts +6 -0
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +58 -1
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +906 -4
- package/figma-desktop-bridge/ui-full.html +80 -0
- package/figma-desktop-bridge/ui.html +82 -0
- package/package.json +1 -1
|
@@ -134,6 +134,24 @@ export class WebSocketConnector {
|
|
|
134
134
|
async setNodeDescription(nodeId, description, descriptionMarkdown) {
|
|
135
135
|
return this.wsServer.sendCommand('SET_NODE_DESCRIPTION', { nodeId, description, descriptionMarkdown });
|
|
136
136
|
}
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// Annotation operations
|
|
139
|
+
// ============================================================================
|
|
140
|
+
async getAnnotations(nodeId, includeChildren, depth) {
|
|
141
|
+
return this.wsServer.sendCommand('GET_ANNOTATIONS', { nodeId, includeChildren, depth }, 10000);
|
|
142
|
+
}
|
|
143
|
+
async setAnnotations(nodeId, annotations, mode) {
|
|
144
|
+
return this.wsServer.sendCommand('SET_ANNOTATIONS', { nodeId, annotations, mode: mode || 'replace' });
|
|
145
|
+
}
|
|
146
|
+
async getAnnotationCategories() {
|
|
147
|
+
return this.wsServer.sendCommand('GET_ANNOTATION_CATEGORIES', {}, 5000);
|
|
148
|
+
}
|
|
149
|
+
async deepGetComponent(nodeId, depth) {
|
|
150
|
+
return this.wsServer.sendCommand('DEEP_GET_COMPONENT', { nodeId, depth: depth || 10 }, 30000);
|
|
151
|
+
}
|
|
152
|
+
async analyzeComponentSet(nodeId) {
|
|
153
|
+
return this.wsServer.sendCommand('ANALYZE_COMPONENT_SET', { nodeId }, 30000);
|
|
154
|
+
}
|
|
137
155
|
async addComponentProperty(nodeId, propertyName, type, defaultValue, options) {
|
|
138
156
|
const params = { nodeId, propertyName, propertyType: type, defaultValue };
|
|
139
157
|
if (options?.preferredValues)
|
|
@@ -248,6 +266,81 @@ export class WebSocketConnector {
|
|
|
248
266
|
return this.wsServer.sendCommand('LINT_DESIGN', params, 120000);
|
|
249
267
|
}
|
|
250
268
|
// ============================================================================
|
|
269
|
+
// FigJam operations
|
|
270
|
+
// ============================================================================
|
|
271
|
+
async createSticky(params) {
|
|
272
|
+
return this.wsServer.sendCommand('CREATE_STICKY', params);
|
|
273
|
+
}
|
|
274
|
+
async createStickies(params) {
|
|
275
|
+
return this.wsServer.sendCommand('CREATE_STICKIES', params, 30000);
|
|
276
|
+
}
|
|
277
|
+
async createConnector(params) {
|
|
278
|
+
return this.wsServer.sendCommand('CREATE_CONNECTOR', params);
|
|
279
|
+
}
|
|
280
|
+
async createShapeWithText(params) {
|
|
281
|
+
return this.wsServer.sendCommand('CREATE_SHAPE_WITH_TEXT', params);
|
|
282
|
+
}
|
|
283
|
+
async createTable(params) {
|
|
284
|
+
return this.wsServer.sendCommand('CREATE_TABLE', params, 30000);
|
|
285
|
+
}
|
|
286
|
+
async createCodeBlock(params) {
|
|
287
|
+
return this.wsServer.sendCommand('CREATE_CODE_BLOCK', params);
|
|
288
|
+
}
|
|
289
|
+
async getBoardContents(params) {
|
|
290
|
+
return this.wsServer.sendCommand('GET_BOARD_CONTENTS', params, 30000);
|
|
291
|
+
}
|
|
292
|
+
async getConnections() {
|
|
293
|
+
return this.wsServer.sendCommand('GET_CONNECTIONS', {}, 15000);
|
|
294
|
+
}
|
|
295
|
+
// ============================================================================
|
|
296
|
+
// Slides operations
|
|
297
|
+
// ============================================================================
|
|
298
|
+
async listSlides() {
|
|
299
|
+
return this.wsServer.sendCommand('LIST_SLIDES', {}, 10000);
|
|
300
|
+
}
|
|
301
|
+
async getSlideContent(params) {
|
|
302
|
+
return this.wsServer.sendCommand('GET_SLIDE_CONTENT', params, 10000);
|
|
303
|
+
}
|
|
304
|
+
async createSlide(params) {
|
|
305
|
+
return this.wsServer.sendCommand('CREATE_SLIDE', params, 10000);
|
|
306
|
+
}
|
|
307
|
+
async deleteSlide(params) {
|
|
308
|
+
return this.wsServer.sendCommand('DELETE_SLIDE', params, 5000);
|
|
309
|
+
}
|
|
310
|
+
async duplicateSlide(params) {
|
|
311
|
+
return this.wsServer.sendCommand('DUPLICATE_SLIDE', params, 5000);
|
|
312
|
+
}
|
|
313
|
+
async getSlideGrid() {
|
|
314
|
+
return this.wsServer.sendCommand('GET_SLIDE_GRID', {}, 10000);
|
|
315
|
+
}
|
|
316
|
+
async reorderSlides(params) {
|
|
317
|
+
return this.wsServer.sendCommand('REORDER_SLIDES', params, 15000);
|
|
318
|
+
}
|
|
319
|
+
async setSlideTransition(params) {
|
|
320
|
+
return this.wsServer.sendCommand('SET_SLIDE_TRANSITION', params, 5000);
|
|
321
|
+
}
|
|
322
|
+
async getSlideTransition(params) {
|
|
323
|
+
return this.wsServer.sendCommand('GET_SLIDE_TRANSITION', params, 5000);
|
|
324
|
+
}
|
|
325
|
+
async setSlidesViewMode(params) {
|
|
326
|
+
return this.wsServer.sendCommand('SET_SLIDES_VIEW_MODE', params, 5000);
|
|
327
|
+
}
|
|
328
|
+
async getFocusedSlide() {
|
|
329
|
+
return this.wsServer.sendCommand('GET_FOCUSED_SLIDE', {}, 5000);
|
|
330
|
+
}
|
|
331
|
+
async focusSlide(params) {
|
|
332
|
+
return this.wsServer.sendCommand('FOCUS_SLIDE', params, 5000);
|
|
333
|
+
}
|
|
334
|
+
async skipSlide(params) {
|
|
335
|
+
return this.wsServer.sendCommand('SKIP_SLIDE', params, 5000);
|
|
336
|
+
}
|
|
337
|
+
async addTextToSlide(params) {
|
|
338
|
+
return this.wsServer.sendCommand('ADD_TEXT_TO_SLIDE', params, 10000);
|
|
339
|
+
}
|
|
340
|
+
async addShapeToSlide(params) {
|
|
341
|
+
return this.wsServer.sendCommand('ADD_SHAPE_TO_SLIDE', params, 5000);
|
|
342
|
+
}
|
|
343
|
+
// ============================================================================
|
|
251
344
|
// Cache management (no-op for WebSocket — no frame cache)
|
|
252
345
|
// ============================================================================
|
|
253
346
|
clearFrameCache() {
|
|
@@ -18,12 +18,12 @@ import { createServer as createHttpServer } from 'http';
|
|
|
18
18
|
import { readFileSync, existsSync } from 'fs';
|
|
19
19
|
import { join } from 'path';
|
|
20
20
|
import { createChildLogger } from './logger.js';
|
|
21
|
-
|
|
22
|
-
//
|
|
21
|
+
import { PACKAGE_ROOT } from './resolve-package-root.js';
|
|
22
|
+
// Read version from package.json using the resolved package root.
|
|
23
|
+
// PACKAGE_ROOT uses import.meta.url in ESM (production) and __dirname in CJS (Jest).
|
|
23
24
|
let SERVER_VERSION = '0.0.0';
|
|
24
25
|
try {
|
|
25
|
-
|
|
26
|
-
SERVER_VERSION = JSON.parse(readFileSync(join(base, 'package.json'), 'utf-8')).version;
|
|
26
|
+
SERVER_VERSION = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf-8')).version;
|
|
27
27
|
}
|
|
28
28
|
catch {
|
|
29
29
|
// Non-critical — version will show as 0.0.0
|
|
@@ -34,11 +34,9 @@ catch {
|
|
|
34
34
|
*/
|
|
35
35
|
function loadPluginUIContent() {
|
|
36
36
|
const candidates = [
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
: join(process.cwd(), 'figma-desktop-bridge', 'ui-full.html'),
|
|
41
|
-
// Direct from project root
|
|
37
|
+
// Primary: relative to package root (works in both CJS and ESM)
|
|
38
|
+
join(PACKAGE_ROOT, 'figma-desktop-bridge', 'ui-full.html'),
|
|
39
|
+
// Fallback: relative to cwd (development / monorepo setups)
|
|
42
40
|
join(process.cwd(), 'figma-desktop-bridge', 'ui-full.html'),
|
|
43
41
|
];
|
|
44
42
|
for (const path of candidates) {
|
|
@@ -415,6 +413,7 @@ export class FigmaWebSocketServer extends EventEmitter {
|
|
|
415
413
|
fileKey,
|
|
416
414
|
currentPage: data.currentPage,
|
|
417
415
|
currentPageId: data.currentPageId || null,
|
|
416
|
+
editorType: data.editorType || 'figma',
|
|
418
417
|
connectedAt: Date.now(),
|
|
419
418
|
},
|
|
420
419
|
selection: existing?.selection || null,
|
|
@@ -707,6 +706,16 @@ export class FigmaWebSocketServer extends EventEmitter {
|
|
|
707
706
|
getActiveFileKey() {
|
|
708
707
|
return this._activeFileKey;
|
|
709
708
|
}
|
|
709
|
+
/**
|
|
710
|
+
* Get the editor type of the currently active file.
|
|
711
|
+
* Returns 'figma' if no file is connected or editorType wasn't reported.
|
|
712
|
+
*/
|
|
713
|
+
getEditorType() {
|
|
714
|
+
if (!this._activeFileKey)
|
|
715
|
+
return 'figma';
|
|
716
|
+
const client = this.clients.get(this._activeFileKey);
|
|
717
|
+
return client?.fileInfo?.editorType || 'figma';
|
|
718
|
+
}
|
|
710
719
|
// ============================================================================
|
|
711
720
|
// Cleanup
|
|
712
721
|
// ============================================================================
|
package/dist/cloudflare/index.js
CHANGED
|
@@ -20,15 +20,45 @@ import { FigmaAPI, extractFileKey } from "./core/figma-api.js";
|
|
|
20
20
|
import { registerFigmaAPITools } from "./core/figma-tools.js";
|
|
21
21
|
import { registerDesignCodeTools } from "./core/design-code-tools.js";
|
|
22
22
|
import { registerCommentTools } from "./core/comment-tools.js";
|
|
23
|
+
import { registerAnnotationTools } from "./core/annotation-tools.js";
|
|
24
|
+
import { registerDeepComponentTools } from "./core/deep-component-tools.js";
|
|
23
25
|
import { registerDesignSystemTools } from "./core/design-system-tools.js";
|
|
24
26
|
import { generatePairingCode } from "./core/cloud-websocket-relay.js";
|
|
25
27
|
import { CloudWebSocketConnector } from "./core/cloud-websocket-connector.js";
|
|
26
28
|
import { registerWriteTools } from "./core/write-tools.js";
|
|
29
|
+
import { registerFigJamTools } from "./core/figjam-tools.js";
|
|
30
|
+
import { registerSlidesTools } from "./core/slides-tools.js";
|
|
27
31
|
// Re-export PluginRelayDO so Cloudflare Workers can bind it as a Durable Object
|
|
28
32
|
export { PluginRelayDO } from "./core/cloud-websocket-relay.js";
|
|
29
33
|
// Note: MCP Apps (Token Browser, Dashboard) are only available in local mode
|
|
30
34
|
// They require Node.js file system APIs for serving HTML that don't work in Cloudflare Workers
|
|
31
35
|
const logger = createChildLogger({ component: "mcp-server" });
|
|
36
|
+
/**
|
|
37
|
+
* Validate a Figma Personal Access Token (PAT) by calling the Figma API.
|
|
38
|
+
* PATs start with 'figd_' and require the X-Figma-Token header (not Bearer).
|
|
39
|
+
* Returns the user info if valid, null if invalid/expired.
|
|
40
|
+
*/
|
|
41
|
+
async function validateFigmaPAT(token) {
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetch("https://api.figma.com/v1/me", {
|
|
44
|
+
headers: { "X-Figma-Token": token },
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok)
|
|
47
|
+
return null;
|
|
48
|
+
const data = await response.json();
|
|
49
|
+
return data?.id ? data : null;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if a token is a Figma Personal Access Token.
|
|
57
|
+
* PATs start with 'figd_' — OAuth tokens start with 'figu_'.
|
|
58
|
+
*/
|
|
59
|
+
function isFigmaPAT(token) {
|
|
60
|
+
return token.startsWith("figd_");
|
|
61
|
+
}
|
|
32
62
|
/**
|
|
33
63
|
* Figma Console MCP Agent
|
|
34
64
|
* Extends McpAgent to provide Figma-specific debugging tools
|
|
@@ -38,7 +68,7 @@ export class FigmaConsoleMCPv3 extends McpAgent {
|
|
|
38
68
|
super(...arguments);
|
|
39
69
|
this.server = new McpServer({
|
|
40
70
|
name: "Figma Console MCP",
|
|
41
|
-
version: "1.
|
|
71
|
+
version: "1.19.1",
|
|
42
72
|
});
|
|
43
73
|
this.browserManager = null;
|
|
44
74
|
this.consoleMonitor = null;
|
|
@@ -745,6 +775,14 @@ export class FigmaConsoleMCPv3 extends McpAgent {
|
|
|
745
775
|
};
|
|
746
776
|
// Register all write/manipulation tools via shared function
|
|
747
777
|
registerWriteTools(this.server, getCloudDesktopConnector);
|
|
778
|
+
// Register FigJam-specific tools (sticky notes, connectors, tables, etc.)
|
|
779
|
+
registerFigJamTools(this.server, getCloudDesktopConnector);
|
|
780
|
+
// Register Annotation tools (read/write design annotations via Desktop Bridge)
|
|
781
|
+
registerAnnotationTools(this.server, getCloudDesktopConnector);
|
|
782
|
+
// Register Deep Component tools (Plugin API tree extraction for code generation)
|
|
783
|
+
registerDeepComponentTools(this.server, getCloudDesktopConnector);
|
|
784
|
+
// Register Figma Slides tools (slide management, transitions, content)
|
|
785
|
+
registerSlidesTools(this.server, getCloudDesktopConnector);
|
|
748
786
|
// Register Figma API tools (Tools 8-14)
|
|
749
787
|
// Pass isRemoteMode: true to suppress Desktop Bridge mentions in tool descriptions
|
|
750
788
|
registerFigmaAPITools(this.server, async () => await this.getFigmaAPI(), () => this.browserManager?.getCurrentUrl() || null, () => this.consoleMonitor || null, () => this.browserManager || null, () => this.ensureInitialized(), undefined, // variablesCache
|
|
@@ -829,6 +867,38 @@ export default {
|
|
|
829
867
|
});
|
|
830
868
|
}
|
|
831
869
|
const bearerToken = authHeader.substring(7); // Remove "Bearer " prefix
|
|
870
|
+
// PAT support: Figma Personal Access Tokens (figd_*) are passed as Bearer
|
|
871
|
+
// tokens by MCP clients like Lovable, but they aren't stored in our OAuth KV.
|
|
872
|
+
// Validate them directly against Figma's API instead.
|
|
873
|
+
if (isFigmaPAT(bearerToken)) {
|
|
874
|
+
logger.info({ pathname: url.pathname }, "SSE request with Figma PAT — validating against Figma API");
|
|
875
|
+
const patUser = await validateFigmaPAT(bearerToken);
|
|
876
|
+
if (!patUser) {
|
|
877
|
+
logger.warn({ pathname: url.pathname }, "SSE request with invalid Figma PAT");
|
|
878
|
+
const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
|
|
879
|
+
return new Response(JSON.stringify({
|
|
880
|
+
error: "invalid_token",
|
|
881
|
+
error_description: "Figma Personal Access Token is invalid or expired"
|
|
882
|
+
}), {
|
|
883
|
+
status: 401,
|
|
884
|
+
headers: {
|
|
885
|
+
"Content-Type": "application/json",
|
|
886
|
+
"WWW-Authenticate": `Bearer resource_metadata="${resourceMetadataUrl}", error="invalid_token"`
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
// Store PAT in KV so the Durable Object's getFigmaAPI() can retrieve it
|
|
891
|
+
const patSessionId = "figma-console-mcp-default-session";
|
|
892
|
+
const patTokenKey = `oauth_token:${patSessionId}`;
|
|
893
|
+
await env.OAUTH_TOKENS.put(patTokenKey, JSON.stringify({
|
|
894
|
+
accessToken: bearerToken,
|
|
895
|
+
expiresAt: Date.now() + 3600_000, // 1-hour TTL for PAT session
|
|
896
|
+
}), { expirationTtl: 3600 });
|
|
897
|
+
logger.info({ pathname: url.pathname, user: patUser.handle }, "SSE request authenticated via Figma PAT");
|
|
898
|
+
// Proceed with SSE connection
|
|
899
|
+
return FigmaConsoleMCPv3.serveSSE("/sse").fetch(request, env, ctx);
|
|
900
|
+
}
|
|
901
|
+
// OAuth token path: look up in KV store
|
|
832
902
|
const bearerKey = `bearer_token:${bearerToken}`;
|
|
833
903
|
try {
|
|
834
904
|
const tokenDataJson = await env.OAUTH_TOKENS.get(bearerKey);
|
|
@@ -897,15 +967,18 @@ export default {
|
|
|
897
967
|
});
|
|
898
968
|
}
|
|
899
969
|
const bearerToken = authHeader.substring(7);
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
970
|
+
// PAT support: Figma Personal Access Tokens (figd_*) are passed as Bearer
|
|
971
|
+
// tokens by MCP clients like Lovable, v0, and Replit. They bypass OAuth
|
|
972
|
+
// and aren't stored in KV — validate directly against Figma's API.
|
|
973
|
+
if (isFigmaPAT(bearerToken)) {
|
|
974
|
+
logger.info({ pathname: url.pathname }, "MCP request with Figma PAT — validating against Figma API");
|
|
975
|
+
const patUser = await validateFigmaPAT(bearerToken);
|
|
976
|
+
if (!patUser) {
|
|
977
|
+
logger.warn({ pathname: url.pathname }, "MCP request with invalid Figma PAT");
|
|
905
978
|
const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
|
|
906
979
|
return new Response(JSON.stringify({
|
|
907
980
|
error: "invalid_token",
|
|
908
|
-
error_description: "
|
|
981
|
+
error_description: "Figma Personal Access Token is invalid or expired. Ensure your PAT (figd_...) is valid and has not been revoked."
|
|
909
982
|
}), {
|
|
910
983
|
status: 401,
|
|
911
984
|
headers: {
|
|
@@ -914,35 +987,58 @@ export default {
|
|
|
914
987
|
}
|
|
915
988
|
});
|
|
916
989
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
990
|
+
logger.info({ pathname: url.pathname, user: patUser.handle }, "MCP request authenticated via Figma PAT");
|
|
991
|
+
}
|
|
992
|
+
else {
|
|
993
|
+
// OAuth token path: look up in KV store
|
|
994
|
+
const bearerKey = `bearer_token:${bearerToken}`;
|
|
995
|
+
try {
|
|
996
|
+
const tokenDataJson = await env.OAUTH_TOKENS.get(bearerKey);
|
|
997
|
+
if (!tokenDataJson) {
|
|
998
|
+
logger.warn({ pathname: url.pathname }, "MCP request with invalid Bearer token");
|
|
999
|
+
const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
|
|
1000
|
+
return new Response(JSON.stringify({
|
|
1001
|
+
error: "invalid_token",
|
|
1002
|
+
error_description: "Bearer token is invalid or expired"
|
|
1003
|
+
}), {
|
|
1004
|
+
status: 401,
|
|
1005
|
+
headers: {
|
|
1006
|
+
"Content-Type": "application/json",
|
|
1007
|
+
"WWW-Authenticate": `Bearer resource_metadata="${resourceMetadataUrl}", error="invalid_token"`
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
const tokenData = JSON.parse(tokenDataJson);
|
|
1012
|
+
if (tokenData.expiresAt < Date.now()) {
|
|
1013
|
+
logger.warn({ pathname: url.pathname, sessionId: tokenData.sessionId }, "MCP request with expired Bearer token");
|
|
1014
|
+
const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
|
|
1015
|
+
return new Response(JSON.stringify({
|
|
1016
|
+
error: "invalid_token",
|
|
1017
|
+
error_description: "Bearer token has expired"
|
|
1018
|
+
}), {
|
|
1019
|
+
status: 401,
|
|
1020
|
+
headers: {
|
|
1021
|
+
"Content-Type": "application/json",
|
|
1022
|
+
"WWW-Authenticate": `Bearer resource_metadata="${resourceMetadataUrl}", error="invalid_token"`
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
logger.info({ pathname: url.pathname, sessionId: tokenData.sessionId }, "MCP request authenticated successfully");
|
|
1027
|
+
}
|
|
1028
|
+
catch (error) {
|
|
1029
|
+
logger.error({ error, pathname: url.pathname }, "Error validating Bearer token for MCP endpoint");
|
|
921
1030
|
return new Response(JSON.stringify({
|
|
922
|
-
error: "
|
|
923
|
-
error_description: "
|
|
1031
|
+
error: "server_error",
|
|
1032
|
+
error_description: "Failed to validate authorization"
|
|
924
1033
|
}), {
|
|
925
|
-
status:
|
|
926
|
-
headers: {
|
|
927
|
-
"Content-Type": "application/json",
|
|
928
|
-
"WWW-Authenticate": `Bearer resource_metadata="${resourceMetadataUrl}", error="invalid_token"`
|
|
929
|
-
}
|
|
1034
|
+
status: 500,
|
|
1035
|
+
headers: { "Content-Type": "application/json" }
|
|
930
1036
|
});
|
|
931
1037
|
}
|
|
932
|
-
logger.info({ pathname: url.pathname, sessionId: tokenData.sessionId }, "MCP request authenticated successfully");
|
|
933
|
-
}
|
|
934
|
-
catch (error) {
|
|
935
|
-
logger.error({ error, pathname: url.pathname }, "Error validating Bearer token for MCP endpoint");
|
|
936
|
-
return new Response(JSON.stringify({
|
|
937
|
-
error: "server_error",
|
|
938
|
-
error_description: "Failed to validate authorization"
|
|
939
|
-
}), {
|
|
940
|
-
status: 500,
|
|
941
|
-
headers: { "Content-Type": "application/json" }
|
|
942
|
-
});
|
|
943
1038
|
}
|
|
944
1039
|
// Token is valid — use stateless transport (no Durable Objects)
|
|
945
1040
|
// The Bearer token IS the Figma access token, so we use it directly
|
|
1041
|
+
// FigmaAPI handles PAT vs OAuth header selection internally
|
|
946
1042
|
const figmaAccessToken = bearerToken;
|
|
947
1043
|
const statelessApi = new FigmaAPI({ accessToken: figmaAccessToken });
|
|
948
1044
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
@@ -950,7 +1046,7 @@ export default {
|
|
|
950
1046
|
});
|
|
951
1047
|
const statelessServer = new McpServer({
|
|
952
1048
|
name: "Figma Console MCP",
|
|
953
|
-
version: "1.
|
|
1049
|
+
version: "1.19.1",
|
|
954
1050
|
});
|
|
955
1051
|
// ================================================================
|
|
956
1052
|
// Cloud Write Relay — Pairing Tool (stateless /mcp path)
|
|
@@ -1029,6 +1125,14 @@ export default {
|
|
|
1029
1125
|
}
|
|
1030
1126
|
// Register all write/manipulation tools via shared function
|
|
1031
1127
|
registerWriteTools(statelessServer, getCloudDesktopConnector);
|
|
1128
|
+
// Register FigJam-specific tools
|
|
1129
|
+
registerFigJamTools(statelessServer, getCloudDesktopConnector);
|
|
1130
|
+
// Register Annotation tools
|
|
1131
|
+
registerAnnotationTools(statelessServer, getCloudDesktopConnector);
|
|
1132
|
+
// Register Deep Component tools
|
|
1133
|
+
registerDeepComponentTools(statelessServer, getCloudDesktopConnector);
|
|
1134
|
+
// Register Figma Slides tools
|
|
1135
|
+
registerSlidesTools(statelessServer, getCloudDesktopConnector);
|
|
1032
1136
|
// Register REST API tools with the authenticated Figma API
|
|
1033
1137
|
registerFigmaAPITools(statelessServer, async () => statelessApi, getCloudFileUrl, () => null, // No console monitor
|
|
1034
1138
|
() => null, // No browser manager
|
|
@@ -1579,7 +1683,7 @@ export default {
|
|
|
1579
1683
|
return new Response(JSON.stringify({
|
|
1580
1684
|
status: "healthy",
|
|
1581
1685
|
service: "Figma Console MCP",
|
|
1582
|
-
version: "1.
|
|
1686
|
+
version: "1.19.1",
|
|
1583
1687
|
endpoints: {
|
|
1584
1688
|
mcp: ["/sse", "/mcp"],
|
|
1585
1689
|
oauth_mcp_spec: ["/.well-known/oauth-authorization-server", "/authorize", "/token", "/oauth/register"],
|
|
@@ -1625,13 +1729,13 @@ export default {
|
|
|
1625
1729
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1626
1730
|
<title>Figma Console MCP - The Most Comprehensive MCP Server for Figma</title>
|
|
1627
1731
|
<link rel="icon" type="image/svg+xml" href="https://docs.figma-console-mcp.southleft.com/favicon.svg">
|
|
1628
|
-
<meta name="description" content="Turn your Figma design system into a living API.
|
|
1732
|
+
<meta name="description" content="Turn your Figma design system into a living API. 89+ tools give AI assistants deep access to design tokens, component specs, variables, and programmatic design creation.">
|
|
1629
1733
|
|
|
1630
1734
|
<!-- Open Graph -->
|
|
1631
1735
|
<meta property="og:type" content="website">
|
|
1632
1736
|
<meta property="og:url" content="https://figma-console-mcp.southleft.com">
|
|
1633
1737
|
<meta property="og:title" content="Figma Console MCP - Turn Your Design System Into a Living API">
|
|
1634
|
-
<meta property="og:description" content="The most comprehensive MCP server for Figma.
|
|
1738
|
+
<meta property="og:description" content="The most comprehensive MCP server for Figma. 89+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
|
|
1635
1739
|
<meta property="og:image" content="https://docs.figma-console-mcp.southleft.com/images/og-image.jpg">
|
|
1636
1740
|
<meta property="og:image:width" content="1200">
|
|
1637
1741
|
<meta property="og:image:height" content="630">
|
|
@@ -1639,7 +1743,7 @@ export default {
|
|
|
1639
1743
|
<!-- Twitter -->
|
|
1640
1744
|
<meta name="twitter:card" content="summary_large_image">
|
|
1641
1745
|
<meta name="twitter:title" content="Figma Console MCP - Turn Your Design System Into a Living API">
|
|
1642
|
-
<meta name="twitter:description" content="The most comprehensive MCP server for Figma.
|
|
1746
|
+
<meta name="twitter:description" content="The most comprehensive MCP server for Figma. 89+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
|
|
1643
1747
|
<meta name="twitter:image" content="https://docs.figma-console-mcp.southleft.com/images/og-image.jpg">
|
|
1644
1748
|
|
|
1645
1749
|
<meta name="theme-color" content="#0D9488">
|
|
@@ -1918,20 +2022,20 @@ export default {
|
|
|
1918
2022
|
}
|
|
1919
2023
|
|
|
1920
2024
|
.capability-list {
|
|
1921
|
-
display:
|
|
1922
|
-
|
|
1923
|
-
gap:
|
|
2025
|
+
display: grid;
|
|
2026
|
+
grid-template-columns: 1fr 1fr;
|
|
2027
|
+
gap: 10px;
|
|
1924
2028
|
}
|
|
1925
2029
|
|
|
1926
2030
|
.capability-item {
|
|
1927
2031
|
display: flex;
|
|
1928
2032
|
align-items: center;
|
|
1929
|
-
gap:
|
|
1930
|
-
padding:
|
|
2033
|
+
gap: 10px;
|
|
2034
|
+
padding: 10px 14px;
|
|
1931
2035
|
background: var(--color-bg-elevated);
|
|
1932
2036
|
border: 1px solid var(--color-border);
|
|
1933
2037
|
border-radius: var(--radius-md);
|
|
1934
|
-
font-size:
|
|
2038
|
+
font-size: 13px;
|
|
1935
2039
|
color: var(--color-text-secondary);
|
|
1936
2040
|
transition: all var(--transition);
|
|
1937
2041
|
}
|
|
@@ -2406,6 +2510,9 @@ export default {
|
|
|
2406
2510
|
.showcase-cell {
|
|
2407
2511
|
padding-top: 32px;
|
|
2408
2512
|
}
|
|
2513
|
+
.capability-list {
|
|
2514
|
+
grid-template-columns: 1fr;
|
|
2515
|
+
}
|
|
2409
2516
|
.capability-card {
|
|
2410
2517
|
grid-column: span 12;
|
|
2411
2518
|
padding: 0 !important;
|
|
@@ -2523,7 +2630,7 @@ export default {
|
|
|
2523
2630
|
<div class="grid-cell showcase-cell rule-left">
|
|
2524
2631
|
<div class="showcase-label">What AI Can Access</div>
|
|
2525
2632
|
<div class="showcase-stat">
|
|
2526
|
-
<span class="number">
|
|
2633
|
+
<span class="number">87+</span>
|
|
2527
2634
|
<span class="label">MCP tools for Figma</span>
|
|
2528
2635
|
</div>
|
|
2529
2636
|
<div class="capability-list">
|
|
@@ -2551,6 +2658,14 @@ export default {
|
|
|
2551
2658
|
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
|
2552
2659
|
<span>Visual debugging and screenshots</span>
|
|
2553
2660
|
</div>
|
|
2661
|
+
<div class="capability-item">
|
|
2662
|
+
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"/></svg>
|
|
2663
|
+
<span>Design annotations and dev specs</span>
|
|
2664
|
+
</div>
|
|
2665
|
+
<div class="capability-item">
|
|
2666
|
+
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="4" rx="1"/><rect x="2" y="9" width="9" height="4" rx="1"/><rect x="13" y="9" width="9" height="4" rx="1"/><rect x="2" y="15" width="20" height="4" rx="1"/></svg>
|
|
2667
|
+
<span>FigJam boards and Slides presentations</span>
|
|
2668
|
+
</div>
|
|
2554
2669
|
</div>
|
|
2555
2670
|
</div>
|
|
2556
2671
|
</div>
|
|
@@ -2634,6 +2749,14 @@ export default {
|
|
|
2634
2749
|
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
|
2635
2750
|
<span>"Connect to my Figma plugin and create a card component"</span>
|
|
2636
2751
|
</div>
|
|
2752
|
+
<div class="prompt-item">
|
|
2753
|
+
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
|
2754
|
+
<span>"Create a retrospective board with colored stickies on FigJam"</span>
|
|
2755
|
+
</div>
|
|
2756
|
+
<div class="prompt-item">
|
|
2757
|
+
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
|
2758
|
+
<span>"List my slides and set a dissolve transition on each one"</span>
|
|
2759
|
+
</div>
|
|
2637
2760
|
</div>
|
|
2638
2761
|
</div>
|
|
2639
2762
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Figma Annotations MCP Tools
|
|
3
|
+
* Tools for reading, writing, and managing design annotations on Figma nodes.
|
|
4
|
+
* Annotations are a Plugin API feature — requires Desktop Bridge plugin connection.
|
|
5
|
+
*
|
|
6
|
+
* Annotations are distinct from comments: they are node-level design specs that
|
|
7
|
+
* can pin specific properties (fills, width, typography, etc.) and support
|
|
8
|
+
* markdown-formatted labels. Designers use them to communicate animation timings,
|
|
9
|
+
* accessibility requirements, interaction specs, and other implementation details
|
|
10
|
+
* that don't fit in the description field.
|
|
11
|
+
*/
|
|
12
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
|
+
export declare function registerAnnotationTools(server: McpServer, getDesktopConnector: () => Promise<any>): void;
|
|
14
|
+
//# sourceMappingURL=annotation-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotation-tools.d.ts","sourceRoot":"","sources":["../../src/core/annotation-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA4EzE,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,SAAS,EACjB,mBAAmB,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,GACrC,IAAI,CA0LN"}
|