figmanage 1.2.9 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/dist/cli/analytics.js +3 -2
- package/dist/cli/branching.js +9 -3
- package/dist/cli/comments.js +10 -4
- package/dist/cli/components.js +21 -4
- package/dist/cli/compound-commands.js +13 -12
- package/dist/cli/export.js +3 -2
- package/dist/cli/files.js +14 -8
- package/dist/cli/helpers.d.ts +1 -0
- package/dist/cli/helpers.js +10 -0
- package/dist/cli/libraries.js +2 -1
- package/dist/cli/navigate.js +11 -10
- package/dist/cli/org.js +13 -12
- package/dist/cli/permissions.js +13 -7
- package/dist/cli/projects.js +12 -6
- package/dist/cli/reading.js +3 -2
- package/dist/cli/teams.js +9 -3
- package/dist/cli/variables.js +29 -7
- package/dist/cli/versions.js +3 -2
- package/dist/cli/webhooks.js +14 -4
- package/dist/helpers.d.ts +11 -0
- package/dist/helpers.js +41 -0
- package/dist/mcp.js +18 -6
- package/dist/operations/analytics.js +1 -1
- package/dist/operations/components.d.ts +8 -2
- package/dist/operations/components.js +4 -2
- package/dist/operations/compound-manager.js +8 -9
- package/dist/operations/compound.d.ts +3 -0
- package/dist/operations/compound.js +14 -8
- package/dist/operations/files.js +1 -1
- package/dist/operations/libraries.js +1 -1
- package/dist/operations/org.js +1 -1
- package/dist/operations/reading.js +2 -0
- package/dist/operations/teams.js +1 -1
- package/dist/operations/variables.js +11 -0
- package/dist/operations/webhooks.d.ts +4 -1
- package/dist/operations/webhooks.js +2 -1
- package/dist/tools/analytics.js +6 -5
- package/dist/tools/branching.js +7 -6
- package/dist/tools/comments.js +14 -9
- package/dist/tools/components.js +24 -15
- package/dist/tools/compound-manager.js +10 -7
- package/dist/tools/compound.js +34 -22
- package/dist/tools/export.js +6 -5
- package/dist/tools/files.js +13 -12
- package/dist/tools/libraries.js +6 -3
- package/dist/tools/navigate.js +29 -18
- package/dist/tools/org.js +25 -24
- package/dist/tools/permissions.js +14 -11
- package/dist/tools/projects.js +10 -9
- package/dist/tools/reading.js +11 -7
- package/dist/tools/register.d.ts +8 -2
- package/dist/tools/register.js +9 -14
- package/dist/tools/setup.d.ts +10 -0
- package/dist/tools/setup.js +181 -0
- package/dist/tools/teams.js +7 -6
- package/dist/tools/variables.js +12 -8
- package/dist/tools/versions.js +6 -5
- package/dist/tools/webhooks.js +15 -12
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { defineTool, toolResult, toolError, figmaId } from './register.js';
|
|
2
|
+
import { defineTool, toolResult, toolError, toolSummary, figmaId } from './register.js';
|
|
3
|
+
import { formatApiError } from '../helpers.js';
|
|
3
4
|
import { getPermissions, setPermissions, share, revokeAccess, listRoleRequests, approveRoleRequest, denyRoleRequest, } from '../operations/permissions.js';
|
|
4
5
|
// -- get_permissions --
|
|
5
6
|
defineTool({
|
|
@@ -15,10 +16,10 @@ defineTool({
|
|
|
15
16
|
}, async ({ resource_type, resource_id }) => {
|
|
16
17
|
try {
|
|
17
18
|
const result = await getPermissions(config, { resource_type, resource_id });
|
|
18
|
-
return
|
|
19
|
+
return toolSummary(`${result.length} user(s) with access.`, result, 'Use set_permissions or revoke_access to modify.');
|
|
19
20
|
}
|
|
20
21
|
catch (e) {
|
|
21
|
-
return toolError(`Failed to get permissions: ${e
|
|
22
|
+
return toolError(`Failed to get permissions: ${formatApiError(e)}`);
|
|
22
23
|
}
|
|
23
24
|
});
|
|
24
25
|
},
|
|
@@ -43,7 +44,7 @@ defineTool({
|
|
|
43
44
|
return toolResult(msg);
|
|
44
45
|
}
|
|
45
46
|
catch (e) {
|
|
46
|
-
return toolError(
|
|
47
|
+
return toolError(`Failed to set permissions: ${formatApiError(e)}`);
|
|
47
48
|
}
|
|
48
49
|
});
|
|
49
50
|
},
|
|
@@ -71,7 +72,7 @@ defineTool({
|
|
|
71
72
|
return toolResult(msg);
|
|
72
73
|
}
|
|
73
74
|
catch (e) {
|
|
74
|
-
return toolError(`Failed to share: ${e
|
|
75
|
+
return toolError(`Failed to share: ${formatApiError(e)}`);
|
|
75
76
|
}
|
|
76
77
|
});
|
|
77
78
|
},
|
|
@@ -84,7 +85,7 @@ defineTool({
|
|
|
84
85
|
destructive: true,
|
|
85
86
|
register(server, config) {
|
|
86
87
|
server.registerTool('revoke_access', {
|
|
87
|
-
description: "Remove
|
|
88
|
+
description: "Remove a user's access to a file, project, or team. The user loses access immediately.",
|
|
88
89
|
inputSchema: {
|
|
89
90
|
resource_type: z.enum(['file', 'folder', 'team']).describe('Type of resource'),
|
|
90
91
|
resource_id: figmaId.describe('Resource ID'),
|
|
@@ -96,7 +97,7 @@ defineTool({
|
|
|
96
97
|
return toolResult(msg);
|
|
97
98
|
}
|
|
98
99
|
catch (e) {
|
|
99
|
-
return toolError(
|
|
100
|
+
return toolError(`Failed to revoke access: ${formatApiError(e)}`);
|
|
100
101
|
}
|
|
101
102
|
});
|
|
102
103
|
},
|
|
@@ -112,10 +113,12 @@ defineTool({
|
|
|
112
113
|
}, async () => {
|
|
113
114
|
try {
|
|
114
115
|
const result = await listRoleRequests(config);
|
|
115
|
-
|
|
116
|
+
if (result.length === 0)
|
|
117
|
+
return toolResult('No pending access requests.');
|
|
118
|
+
return toolSummary(`${result.length} pending request(s).`, result, 'Use approve_role_request or deny_role_request.');
|
|
116
119
|
}
|
|
117
120
|
catch (e) {
|
|
118
|
-
return toolError(`Failed to list role requests: ${e
|
|
121
|
+
return toolError(`Failed to list role requests: ${formatApiError(e)}`);
|
|
119
122
|
}
|
|
120
123
|
});
|
|
121
124
|
},
|
|
@@ -137,7 +140,7 @@ defineTool({
|
|
|
137
140
|
return toolResult(msg);
|
|
138
141
|
}
|
|
139
142
|
catch (e) {
|
|
140
|
-
return toolError(`Failed to approve request: ${e
|
|
143
|
+
return toolError(`Failed to approve request: ${formatApiError(e)}`);
|
|
141
144
|
}
|
|
142
145
|
});
|
|
143
146
|
},
|
|
@@ -159,7 +162,7 @@ defineTool({
|
|
|
159
162
|
return toolResult(msg);
|
|
160
163
|
}
|
|
161
164
|
catch (e) {
|
|
162
|
-
return toolError(`Failed to deny request: ${e
|
|
165
|
+
return toolError(`Failed to deny request: ${formatApiError(e)}`);
|
|
163
166
|
}
|
|
164
167
|
});
|
|
165
168
|
},
|
package/dist/tools/projects.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { defineTool, toolResult, toolError, figmaId } from './register.js';
|
|
2
|
+
import { defineTool, toolResult, toolError, toolSummary, figmaId } from './register.js';
|
|
3
|
+
import { formatApiError } from '../helpers.js';
|
|
3
4
|
import { createProject, renameProject, moveProject, trashProject, restoreProject, setProjectDescription, } from '../operations/projects.js';
|
|
4
5
|
// -- create_project --
|
|
5
6
|
defineTool({
|
|
@@ -16,10 +17,10 @@ defineTool({
|
|
|
16
17
|
}, async ({ team_id, name }) => {
|
|
17
18
|
try {
|
|
18
19
|
const result = await createProject(config, { team_id, name });
|
|
19
|
-
return
|
|
20
|
+
return toolSummary(`Created project "${result.name}".`, result, 'Use list_files to see files, or create_file to add one.');
|
|
20
21
|
}
|
|
21
22
|
catch (e) {
|
|
22
|
-
return toolError(`Failed to create project: ${e
|
|
23
|
+
return toolError(`Failed to create project: ${formatApiError(e)}`);
|
|
23
24
|
}
|
|
24
25
|
});
|
|
25
26
|
},
|
|
@@ -42,7 +43,7 @@ defineTool({
|
|
|
42
43
|
return toolResult(`Renamed project ${project_id} to "${name}"`);
|
|
43
44
|
}
|
|
44
45
|
catch (e) {
|
|
45
|
-
return toolError(`Failed to rename project: ${e
|
|
46
|
+
return toolError(`Failed to rename project: ${formatApiError(e)}`);
|
|
46
47
|
}
|
|
47
48
|
});
|
|
48
49
|
},
|
|
@@ -65,7 +66,7 @@ defineTool({
|
|
|
65
66
|
return toolResult(`Moved project ${project_id} to team ${destination_team_id}`);
|
|
66
67
|
}
|
|
67
68
|
catch (e) {
|
|
68
|
-
return toolError(`Failed to move project: ${e
|
|
69
|
+
return toolError(`Failed to move project: ${formatApiError(e)}`);
|
|
69
70
|
}
|
|
70
71
|
});
|
|
71
72
|
},
|
|
@@ -78,7 +79,7 @@ defineTool({
|
|
|
78
79
|
destructive: true,
|
|
79
80
|
register(server, config) {
|
|
80
81
|
server.registerTool('trash_project', {
|
|
81
|
-
description: 'Move a project to trash.',
|
|
82
|
+
description: 'Move a project and all its files to trash. Recoverable via restore_project.',
|
|
82
83
|
inputSchema: {
|
|
83
84
|
project_id: figmaId.describe('Project ID to trash'),
|
|
84
85
|
},
|
|
@@ -88,7 +89,7 @@ defineTool({
|
|
|
88
89
|
return toolResult(`Trashed project ${project_id}`);
|
|
89
90
|
}
|
|
90
91
|
catch (e) {
|
|
91
|
-
return toolError(`Failed to trash project: ${e
|
|
92
|
+
return toolError(`Failed to trash project: ${formatApiError(e)}`);
|
|
92
93
|
}
|
|
93
94
|
});
|
|
94
95
|
},
|
|
@@ -110,7 +111,7 @@ defineTool({
|
|
|
110
111
|
return toolResult(`Restored project ${project_id}`);
|
|
111
112
|
}
|
|
112
113
|
catch (e) {
|
|
113
|
-
return toolError(`Failed to restore project: ${e
|
|
114
|
+
return toolError(`Failed to restore project: ${formatApiError(e)}`);
|
|
114
115
|
}
|
|
115
116
|
});
|
|
116
117
|
},
|
|
@@ -133,7 +134,7 @@ defineTool({
|
|
|
133
134
|
return toolResult(`Updated description for project ${project_id}`);
|
|
134
135
|
}
|
|
135
136
|
catch (e) {
|
|
136
|
-
return toolError(`Failed to set description: ${e
|
|
137
|
+
return toolError(`Failed to set description: ${formatApiError(e)}`);
|
|
137
138
|
}
|
|
138
139
|
});
|
|
139
140
|
},
|
package/dist/tools/reading.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { defineTool,
|
|
2
|
+
import { defineTool, toolError, toolSummary, figmaId } from './register.js';
|
|
3
|
+
import { formatApiError } from '../helpers.js';
|
|
3
4
|
import { getFile, getNodes } from '../operations/reading.js';
|
|
4
5
|
// -- get_file --
|
|
5
6
|
defineTool({
|
|
@@ -7,7 +8,7 @@ defineTool({
|
|
|
7
8
|
auth: 'pat',
|
|
8
9
|
register(server, config) {
|
|
9
10
|
server.registerTool('get_file', {
|
|
10
|
-
description: 'Read file contents as a node tree. To read a branch, use the branch file key from list_branches.',
|
|
11
|
+
description: 'Read file contents as a node tree. Use depth to limit response size (full trees can be very large). depth=1 returns pages only. To read a branch, use the branch file key from list_branches.',
|
|
11
12
|
inputSchema: {
|
|
12
13
|
file_key: figmaId.describe('File key (or branch file key from list_branches)'),
|
|
13
14
|
depth: z.number().int().optional().describe('Tree depth limit. 0=root, 1=pages, 2=top-level frames. Omit for full tree.'),
|
|
@@ -16,10 +17,12 @@ defineTool({
|
|
|
16
17
|
}, async ({ file_key, depth, node_id }) => {
|
|
17
18
|
try {
|
|
18
19
|
const result = await getFile(config, { file_key, depth, node_id });
|
|
19
|
-
|
|
20
|
+
const name = result.name || 'Untitled';
|
|
21
|
+
const pages = result.document?.children?.length || 0;
|
|
22
|
+
return toolSummary(`${name}: ${pages} page(s). Use depth parameter to limit response size.`, result);
|
|
20
23
|
}
|
|
21
24
|
catch (e) {
|
|
22
|
-
return toolError(`Failed to get file: ${e
|
|
25
|
+
return toolError(`Failed to get file: ${formatApiError(e)}`);
|
|
23
26
|
}
|
|
24
27
|
});
|
|
25
28
|
},
|
|
@@ -30,7 +33,7 @@ defineTool({
|
|
|
30
33
|
auth: 'pat',
|
|
31
34
|
register(server, config) {
|
|
32
35
|
server.registerTool('get_nodes', {
|
|
33
|
-
description: 'Read specific nodes from a file. Returns node
|
|
36
|
+
description: 'Read specific nodes from a file. Returns the full node tree for each ID including type, name, layout properties, fills, strokes, and children.',
|
|
34
37
|
inputSchema: {
|
|
35
38
|
file_key: figmaId.describe('File key'),
|
|
36
39
|
node_ids: z.array(figmaId).min(1).describe('Node IDs to fetch'),
|
|
@@ -39,10 +42,11 @@ defineTool({
|
|
|
39
42
|
}, async ({ file_key, node_ids, depth }) => {
|
|
40
43
|
try {
|
|
41
44
|
const result = await getNodes(config, { file_key, node_ids, depth });
|
|
42
|
-
|
|
45
|
+
const nodeCount = result.nodes ? Object.keys(result.nodes).length : 0;
|
|
46
|
+
return toolSummary(`${nodeCount} node(s) returned.`, result);
|
|
43
47
|
}
|
|
44
48
|
catch (e) {
|
|
45
|
-
return toolError(`Failed to get nodes: ${e
|
|
49
|
+
return toolError(`Failed to get nodes: ${formatApiError(e)}`);
|
|
46
50
|
}
|
|
47
51
|
});
|
|
48
52
|
},
|
package/dist/tools/register.d.ts
CHANGED
|
@@ -20,6 +20,13 @@ export declare function toolResult(text: string): {
|
|
|
20
20
|
text: string;
|
|
21
21
|
}>;
|
|
22
22
|
};
|
|
23
|
+
/** Format a tool response with a summary line, JSON data, and optional next-step guidance. */
|
|
24
|
+
export declare function toolSummary(summary: string, data: unknown, nextStep?: string): {
|
|
25
|
+
content: Array<{
|
|
26
|
+
type: 'text';
|
|
27
|
+
text: string;
|
|
28
|
+
}>;
|
|
29
|
+
};
|
|
23
30
|
export declare function toolError(message: string): {
|
|
24
31
|
isError: true;
|
|
25
32
|
content: Array<{
|
|
@@ -27,6 +34,5 @@ export declare function toolError(message: string): {
|
|
|
27
34
|
text: string;
|
|
28
35
|
}>;
|
|
29
36
|
};
|
|
30
|
-
export
|
|
31
|
-
export declare function requireOrgId(config: AuthConfig, explicit?: string): string;
|
|
37
|
+
export { resolveOrgId, requireOrgId } from '../helpers.js';
|
|
32
38
|
//# sourceMappingURL=register.d.ts.map
|
package/dist/tools/register.js
CHANGED
|
@@ -56,21 +56,16 @@ export function registerTools(server, config, enabledToolsets, readOnly) {
|
|
|
56
56
|
export function toolResult(text) {
|
|
57
57
|
return { content: [{ type: 'text', text }] };
|
|
58
58
|
}
|
|
59
|
+
/** Format a tool response with a summary line, JSON data, and optional next-step guidance. */
|
|
60
|
+
export function toolSummary(summary, data, nextStep) {
|
|
61
|
+
const parts = [summary, '', JSON.stringify(data, null, 2)];
|
|
62
|
+
if (nextStep)
|
|
63
|
+
parts.push('', nextStep);
|
|
64
|
+
return { content: [{ type: 'text', text: parts.join('\n') }] };
|
|
65
|
+
}
|
|
59
66
|
export function toolError(message) {
|
|
60
67
|
return { isError: true, content: [{ type: 'text', text: message }] };
|
|
61
68
|
}
|
|
62
|
-
export
|
|
63
|
-
|
|
64
|
-
if (id && !/^[\w.:-]+$/.test(id))
|
|
65
|
-
throw new Error('Invalid org ID format');
|
|
66
|
-
return id;
|
|
67
|
-
}
|
|
68
|
-
export function requireOrgId(config, explicit) {
|
|
69
|
-
const id = explicit || config.orgId;
|
|
70
|
-
if (!id)
|
|
71
|
-
throw new Error('Org context required. Run list_orgs to see available workspaces, or set FIGMA_ORG_ID.');
|
|
72
|
-
if (!/^[\w.:-]+$/.test(id))
|
|
73
|
-
throw new Error('Invalid org ID format');
|
|
74
|
-
return id;
|
|
75
|
-
}
|
|
69
|
+
// Re-export from helpers so existing tool imports still work
|
|
70
|
+
export { resolveOrgId, requireOrgId } from '../helpers.js';
|
|
76
71
|
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive setup tools for first-run MCP configuration.
|
|
3
|
+
*
|
|
4
|
+
* Guides the user through cookie extraction and PAT creation
|
|
5
|
+
* via conversational tool calls. Registered when auth is missing;
|
|
6
|
+
* replaced by the full toolset once setup completes.
|
|
7
|
+
*/
|
|
8
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
|
+
export declare function registerSetupTools(server: McpServer, onSetupComplete: () => void): void;
|
|
10
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive setup tools for first-run MCP configuration.
|
|
3
|
+
*
|
|
4
|
+
* Guides the user through cookie extraction and PAT creation
|
|
5
|
+
* via conversational tool calls. Registered when auth is missing;
|
|
6
|
+
* replaced by the full toolset once setup completes.
|
|
7
|
+
*/
|
|
8
|
+
import { platform } from 'node:os';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { extractCookies, validateSession, validatePat, resolveAccountInfo, } from '../auth/cookie.js';
|
|
11
|
+
import { setActiveWorkspace, getActiveWorkspace } from '../config.js';
|
|
12
|
+
import { loadAuthConfig, hasPat, hasCookie } from '../auth/client.js';
|
|
13
|
+
// State shared across setup steps within a single session
|
|
14
|
+
let pendingAccounts = [];
|
|
15
|
+
export function registerSetupTools(server, onSetupComplete) {
|
|
16
|
+
server.tool('setup_status', 'Check figmanage authentication status and get setup instructions. Always call this before using any other tool.', {}, async () => {
|
|
17
|
+
const config = loadAuthConfig();
|
|
18
|
+
if (hasPat(config) && hasCookie(config)) {
|
|
19
|
+
return { content: [{ type: 'text', text: 'figmanage is fully configured. All tools are available.' }] };
|
|
20
|
+
}
|
|
21
|
+
const os = platform();
|
|
22
|
+
const lines = [
|
|
23
|
+
'figmanage needs two credentials to give you full access to all 85 Figma workspace tools:',
|
|
24
|
+
'',
|
|
25
|
+
'1. Browser cookie -- extracted automatically from Chrome',
|
|
26
|
+
'2. Personal Access Token (PAT) -- created in Figma settings',
|
|
27
|
+
'',
|
|
28
|
+
];
|
|
29
|
+
if (!hasCookie(config)) {
|
|
30
|
+
lines.push('NEXT: Cookie extraction');
|
|
31
|
+
lines.push('The user must be logged into figma.com in Chrome before proceeding.');
|
|
32
|
+
if (os === 'darwin') {
|
|
33
|
+
lines.push('');
|
|
34
|
+
lines.push('When they proceed, a macOS Keychain prompt will appear asking to access');
|
|
35
|
+
lines.push('"Chrome Safe Storage". They need to click Allow.');
|
|
36
|
+
}
|
|
37
|
+
else if (os === 'linux') {
|
|
38
|
+
lines.push('');
|
|
39
|
+
lines.push('On Linux, Chrome cookies are decrypted using the system keyring or a default key.');
|
|
40
|
+
}
|
|
41
|
+
lines.push('');
|
|
42
|
+
lines.push('Ask the user to confirm they are logged into figma.com in Chrome, then call setup_extract_cookies.');
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
lines.push('Cookie auth is configured.');
|
|
46
|
+
lines.push('');
|
|
47
|
+
lines.push('NEXT: Personal Access Token');
|
|
48
|
+
lines.push('Ask the user to create a PAT at: https://www.figma.com/settings');
|
|
49
|
+
lines.push('(Security > Personal access tokens)');
|
|
50
|
+
lines.push('Then call setup_save_pat with the token value.');
|
|
51
|
+
}
|
|
52
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
53
|
+
});
|
|
54
|
+
server.tool('setup_extract_cookies', 'Extract Figma session cookies from Chrome. On macOS this triggers a Keychain prompt. IMPORTANT: Before calling, confirm the user is logged into figma.com in Chrome and knows a system prompt may appear.', {}, async () => {
|
|
55
|
+
try {
|
|
56
|
+
const accounts = extractCookies();
|
|
57
|
+
if (accounts.length === 0) {
|
|
58
|
+
return {
|
|
59
|
+
content: [{
|
|
60
|
+
type: 'text',
|
|
61
|
+
text: 'No Figma cookies found in Chrome. The user needs to log into figma.com in Chrome first, then try again.',
|
|
62
|
+
}],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
pendingAccounts = accounts;
|
|
66
|
+
const infos = await Promise.all(accounts.map(a => resolveAccountInfo(a)));
|
|
67
|
+
const lines = [
|
|
68
|
+
`Found ${accounts.length} Figma account${accounts.length > 1 ? 's' : ''}:`,
|
|
69
|
+
'',
|
|
70
|
+
];
|
|
71
|
+
for (let i = 0; i < accounts.length; i++) {
|
|
72
|
+
const info = infos[i];
|
|
73
|
+
const label = info.figmaEmail || `User ${accounts[i].userId}`;
|
|
74
|
+
lines.push(` ${i + 1}. ${label} (Chrome profile: ${info.profileName})`);
|
|
75
|
+
}
|
|
76
|
+
lines.push('');
|
|
77
|
+
lines.push('Ask the user which account to use, then call setup_select_account with the chosen number.');
|
|
78
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return {
|
|
82
|
+
isError: true,
|
|
83
|
+
content: [{ type: 'text', text: `Cookie extraction failed: ${e.message}` }],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
server.tool('setup_select_account', 'Select a Figma account and validate the session. Call after setup_extract_cookies.', {
|
|
88
|
+
account_index: z.number().int().min(1).describe('Account number from the list returned by setup_extract_cookies'),
|
|
89
|
+
}, async ({ account_index }) => {
|
|
90
|
+
if (pendingAccounts.length === 0) {
|
|
91
|
+
return {
|
|
92
|
+
isError: true,
|
|
93
|
+
content: [{ type: 'text', text: 'No accounts available. Call setup_extract_cookies first.' }],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const idx = account_index - 1;
|
|
97
|
+
if (idx < 0 || idx >= pendingAccounts.length) {
|
|
98
|
+
return {
|
|
99
|
+
isError: true,
|
|
100
|
+
content: [{ type: 'text', text: `Invalid selection. Choose 1-${pendingAccounts.length}.` }],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const account = pendingAccounts[idx];
|
|
104
|
+
try {
|
|
105
|
+
const session = await validateSession(account.cookieValue, account.userId);
|
|
106
|
+
let orgId = session.orgId;
|
|
107
|
+
if (!orgId && session.orgs.length > 0) {
|
|
108
|
+
orgId = session.orgs[0].id;
|
|
109
|
+
}
|
|
110
|
+
const workspaceName = orgId || account.userId || 'default';
|
|
111
|
+
setActiveWorkspace(workspaceName, {
|
|
112
|
+
cookie: account.cookieValue,
|
|
113
|
+
user_id: account.userId,
|
|
114
|
+
org_id: orgId || undefined,
|
|
115
|
+
cookie_extracted_at: new Date().toISOString(),
|
|
116
|
+
});
|
|
117
|
+
const lines = [`Session valid (user ${account.userId}).`];
|
|
118
|
+
if (session.orgs.length > 0) {
|
|
119
|
+
const orgName = session.orgs.find(o => o.id === orgId)?.name;
|
|
120
|
+
lines.push(`Workspace: ${orgName ? `${orgName} (${orgId})` : orgId}`);
|
|
121
|
+
}
|
|
122
|
+
if (session.teams.length > 0) {
|
|
123
|
+
lines.push(`Teams: ${session.teams.map(t => t.name).join(', ')}`);
|
|
124
|
+
}
|
|
125
|
+
lines.push('');
|
|
126
|
+
lines.push('Cookie saved. Now need a Personal Access Token for full access.');
|
|
127
|
+
lines.push('Ask the user to create one at: https://www.figma.com/settings');
|
|
128
|
+
lines.push('(Security > Personal access tokens)');
|
|
129
|
+
lines.push('Then call setup_save_pat with the token.');
|
|
130
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
const status = e.response?.status;
|
|
134
|
+
if (status === 401 || status === 403) {
|
|
135
|
+
return {
|
|
136
|
+
isError: true,
|
|
137
|
+
content: [{ type: 'text', text: 'Cookie expired or invalid. Log into figma.com in Chrome and run setup_extract_cookies again.' }],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
isError: true,
|
|
142
|
+
content: [{ type: 'text', text: `Session validation failed: ${e.message}` }],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
server.tool('setup_save_pat', 'Validate and save a Figma Personal Access Token. Completes setup and activates all tools.', {
|
|
147
|
+
pat: z.string().min(1).describe('Figma Personal Access Token (starts with figd_)'),
|
|
148
|
+
}, async ({ pat }) => {
|
|
149
|
+
try {
|
|
150
|
+
const patUser = await validatePat(pat);
|
|
151
|
+
// Update the active workspace with the PAT
|
|
152
|
+
const workspace = getActiveWorkspace();
|
|
153
|
+
if (!workspace) {
|
|
154
|
+
return {
|
|
155
|
+
isError: true,
|
|
156
|
+
content: [{ type: 'text', text: 'No workspace configured. Run setup_extract_cookies and setup_select_account first.' }],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
workspace.pat = pat;
|
|
160
|
+
const workspaceName = workspace.org_id || workspace.user_id || 'default';
|
|
161
|
+
setActiveWorkspace(workspaceName, workspace);
|
|
162
|
+
// Clear setup state
|
|
163
|
+
pendingAccounts = [];
|
|
164
|
+
// Trigger full tool registration
|
|
165
|
+
onSetupComplete();
|
|
166
|
+
return {
|
|
167
|
+
content: [{
|
|
168
|
+
type: 'text',
|
|
169
|
+
text: `PAT valid (${patUser}). Setup complete -- all 85 figmanage tools are now available.`,
|
|
170
|
+
}],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return {
|
|
175
|
+
isError: true,
|
|
176
|
+
content: [{ type: 'text', text: 'PAT invalid or expired. Check the token and try again.' }],
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=setup.js.map
|
package/dist/tools/teams.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { defineTool, toolResult, toolError, figmaId } from './register.js';
|
|
2
|
+
import { defineTool, toolResult, toolError, toolSummary, figmaId } from './register.js';
|
|
3
|
+
import { formatApiError } from '../helpers.js';
|
|
3
4
|
import { createTeam, renameTeam, deleteTeam } from '../operations/teams.js';
|
|
4
5
|
// -- create_team --
|
|
5
6
|
defineTool({
|
|
@@ -16,10 +17,10 @@ defineTool({
|
|
|
16
17
|
}, async ({ name, org_id }) => {
|
|
17
18
|
try {
|
|
18
19
|
const result = await createTeam(config, { name, org_id });
|
|
19
|
-
return
|
|
20
|
+
return toolSummary(`Created team.`, result, 'Use create_project to add projects to this team.');
|
|
20
21
|
}
|
|
21
22
|
catch (e) {
|
|
22
|
-
return toolError(`Failed to create team: ${e
|
|
23
|
+
return toolError(`Failed to create team: ${formatApiError(e)}`);
|
|
23
24
|
}
|
|
24
25
|
});
|
|
25
26
|
},
|
|
@@ -42,7 +43,7 @@ defineTool({
|
|
|
42
43
|
return toolResult(msg);
|
|
43
44
|
}
|
|
44
45
|
catch (e) {
|
|
45
|
-
return toolError(`Failed to rename team: ${e
|
|
46
|
+
return toolError(`Failed to rename team: ${formatApiError(e)}`);
|
|
46
47
|
}
|
|
47
48
|
});
|
|
48
49
|
},
|
|
@@ -55,7 +56,7 @@ defineTool({
|
|
|
55
56
|
destructive: true,
|
|
56
57
|
register(server, config) {
|
|
57
58
|
server.registerTool('delete_team', {
|
|
58
|
-
description: '
|
|
59
|
+
description: 'Permanently delete a team and all its projects/files. This cannot be undone. All team members lose access.',
|
|
59
60
|
inputSchema: {
|
|
60
61
|
team_id: figmaId.describe('Team ID'),
|
|
61
62
|
},
|
|
@@ -65,7 +66,7 @@ defineTool({
|
|
|
65
66
|
return toolResult(msg);
|
|
66
67
|
}
|
|
67
68
|
catch (e) {
|
|
68
|
-
return toolError(`Failed to delete team: ${e
|
|
69
|
+
return toolError(`Failed to delete team: ${formatApiError(e)}`);
|
|
69
70
|
}
|
|
70
71
|
});
|
|
71
72
|
},
|
package/dist/tools/variables.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { defineTool,
|
|
2
|
+
import { defineTool, toolError, toolSummary, figmaId } from './register.js';
|
|
3
|
+
import { formatApiError } from '../helpers.js';
|
|
3
4
|
import { listLocalVariables, listPublishedVariables, updateVariables, isEnterpriseScopeError, ENTERPRISE_ERROR, } from '../operations/variables.js';
|
|
4
5
|
// -- list_local_variables --
|
|
5
6
|
defineTool({
|
|
@@ -14,12 +15,14 @@ defineTool({
|
|
|
14
15
|
}, async ({ file_key }) => {
|
|
15
16
|
try {
|
|
16
17
|
const result = await listLocalVariables(config, { file_key });
|
|
17
|
-
|
|
18
|
+
const vars = result.variables ? Object.keys(result.variables).length : 0;
|
|
19
|
+
const collections = result.variableCollections ? Object.keys(result.variableCollections).length : 0;
|
|
20
|
+
return toolSummary(`${vars} variable(s) in ${collections} collection(s).`, result);
|
|
18
21
|
}
|
|
19
22
|
catch (e) {
|
|
20
23
|
if (isEnterpriseScopeError(e))
|
|
21
24
|
return toolError(ENTERPRISE_ERROR);
|
|
22
|
-
return toolError(`Failed to list local variables: ${e
|
|
25
|
+
return toolError(`Failed to list local variables: ${formatApiError(e)}`);
|
|
23
26
|
}
|
|
24
27
|
});
|
|
25
28
|
},
|
|
@@ -37,12 +40,13 @@ defineTool({
|
|
|
37
40
|
}, async ({ file_key }) => {
|
|
38
41
|
try {
|
|
39
42
|
const result = await listPublishedVariables(config, { file_key });
|
|
40
|
-
|
|
43
|
+
const vars = result.variables ? Object.keys(result.variables).length : 0;
|
|
44
|
+
return toolSummary(`${vars} published variable(s).`, result);
|
|
41
45
|
}
|
|
42
46
|
catch (e) {
|
|
43
47
|
if (isEnterpriseScopeError(e))
|
|
44
48
|
return toolError(ENTERPRISE_ERROR);
|
|
45
|
-
return toolError(`Failed to list published variables: ${e
|
|
49
|
+
return toolError(`Failed to list published variables: ${formatApiError(e)}`);
|
|
46
50
|
}
|
|
47
51
|
});
|
|
48
52
|
},
|
|
@@ -55,7 +59,7 @@ defineTool({
|
|
|
55
59
|
destructive: true,
|
|
56
60
|
register(server, config) {
|
|
57
61
|
server.registerTool('update_variables', {
|
|
58
|
-
description: 'Bulk create, update, or delete variables, collections, modes, and mode values. Requires Enterprise plan.',
|
|
62
|
+
description: 'Bulk create, update, or delete variables, collections, modes, and mode values. Requires Enterprise plan. Each operation object needs an action field (CREATE, UPDATE, or DELETE). Deletions are immediate and cannot be undone -- list variables first to verify IDs.',
|
|
59
63
|
inputSchema: {
|
|
60
64
|
file_key: figmaId.describe('File key'),
|
|
61
65
|
variable_collections: z.array(z.record(z.any())).optional().describe('Collection operations (action: CREATE, UPDATE, or DELETE)'),
|
|
@@ -72,12 +76,12 @@ defineTool({
|
|
|
72
76
|
variables,
|
|
73
77
|
variable_mode_values,
|
|
74
78
|
});
|
|
75
|
-
return
|
|
79
|
+
return toolSummary('Variables updated.', result, 'Use list_local_variables to verify changes.');
|
|
76
80
|
}
|
|
77
81
|
catch (e) {
|
|
78
82
|
if (isEnterpriseScopeError(e))
|
|
79
83
|
return toolError(ENTERPRISE_ERROR);
|
|
80
|
-
return toolError(`Failed to update variables: ${e
|
|
84
|
+
return toolError(`Failed to update variables: ${formatApiError(e)}`);
|
|
81
85
|
}
|
|
82
86
|
});
|
|
83
87
|
},
|
package/dist/tools/versions.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { defineTool, toolResult, toolError, figmaId } from './register.js';
|
|
2
|
+
import { defineTool, toolResult, toolError, toolSummary, figmaId } from './register.js';
|
|
3
|
+
import { formatApiError } from '../helpers.js';
|
|
3
4
|
import { listVersions, createVersion } from '../operations/versions.js';
|
|
4
5
|
// -- list_versions --
|
|
5
6
|
defineTool({
|
|
@@ -16,10 +17,10 @@ defineTool({
|
|
|
16
17
|
const result = await listVersions(config, { file_key });
|
|
17
18
|
if (result.length === 0)
|
|
18
19
|
return toolResult('No versions found.');
|
|
19
|
-
return
|
|
20
|
+
return toolSummary(`${result.length} version(s).`, result, 'Use create_version to add a named checkpoint.');
|
|
20
21
|
}
|
|
21
22
|
catch (e) {
|
|
22
|
-
return toolError(`Failed to list versions: ${e
|
|
23
|
+
return toolError(`Failed to list versions: ${formatApiError(e)}`);
|
|
23
24
|
}
|
|
24
25
|
});
|
|
25
26
|
},
|
|
@@ -40,10 +41,10 @@ defineTool({
|
|
|
40
41
|
}, async ({ file_key, title, description }) => {
|
|
41
42
|
try {
|
|
42
43
|
const result = await createVersion(config, { file_key, title, description });
|
|
43
|
-
return
|
|
44
|
+
return toolSummary(`Created version "${result.label}".`, result);
|
|
44
45
|
}
|
|
45
46
|
catch (e) {
|
|
46
|
-
return toolError(`Failed to create version: ${e
|
|
47
|
+
return toolError(`Failed to create version: ${formatApiError(e)}`);
|
|
47
48
|
}
|
|
48
49
|
});
|
|
49
50
|
},
|