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.
Files changed (60) hide show
  1. package/README.md +10 -8
  2. package/dist/cli/analytics.js +3 -2
  3. package/dist/cli/branching.js +9 -3
  4. package/dist/cli/comments.js +10 -4
  5. package/dist/cli/components.js +21 -4
  6. package/dist/cli/compound-commands.js +13 -12
  7. package/dist/cli/export.js +3 -2
  8. package/dist/cli/files.js +14 -8
  9. package/dist/cli/helpers.d.ts +1 -0
  10. package/dist/cli/helpers.js +10 -0
  11. package/dist/cli/libraries.js +2 -1
  12. package/dist/cli/navigate.js +11 -10
  13. package/dist/cli/org.js +13 -12
  14. package/dist/cli/permissions.js +13 -7
  15. package/dist/cli/projects.js +12 -6
  16. package/dist/cli/reading.js +3 -2
  17. package/dist/cli/teams.js +9 -3
  18. package/dist/cli/variables.js +29 -7
  19. package/dist/cli/versions.js +3 -2
  20. package/dist/cli/webhooks.js +14 -4
  21. package/dist/helpers.d.ts +11 -0
  22. package/dist/helpers.js +41 -0
  23. package/dist/mcp.js +18 -6
  24. package/dist/operations/analytics.js +1 -1
  25. package/dist/operations/components.d.ts +8 -2
  26. package/dist/operations/components.js +4 -2
  27. package/dist/operations/compound-manager.js +8 -9
  28. package/dist/operations/compound.d.ts +3 -0
  29. package/dist/operations/compound.js +14 -8
  30. package/dist/operations/files.js +1 -1
  31. package/dist/operations/libraries.js +1 -1
  32. package/dist/operations/org.js +1 -1
  33. package/dist/operations/reading.js +2 -0
  34. package/dist/operations/teams.js +1 -1
  35. package/dist/operations/variables.js +11 -0
  36. package/dist/operations/webhooks.d.ts +4 -1
  37. package/dist/operations/webhooks.js +2 -1
  38. package/dist/tools/analytics.js +6 -5
  39. package/dist/tools/branching.js +7 -6
  40. package/dist/tools/comments.js +14 -9
  41. package/dist/tools/components.js +24 -15
  42. package/dist/tools/compound-manager.js +10 -7
  43. package/dist/tools/compound.js +34 -22
  44. package/dist/tools/export.js +6 -5
  45. package/dist/tools/files.js +13 -12
  46. package/dist/tools/libraries.js +6 -3
  47. package/dist/tools/navigate.js +29 -18
  48. package/dist/tools/org.js +25 -24
  49. package/dist/tools/permissions.js +14 -11
  50. package/dist/tools/projects.js +10 -9
  51. package/dist/tools/reading.js +11 -7
  52. package/dist/tools/register.d.ts +8 -2
  53. package/dist/tools/register.js +9 -14
  54. package/dist/tools/setup.d.ts +10 -0
  55. package/dist/tools/setup.js +181 -0
  56. package/dist/tools/teams.js +7 -6
  57. package/dist/tools/variables.js +12 -8
  58. package/dist/tools/versions.js +6 -5
  59. package/dist/tools/webhooks.js +15 -12
  60. 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 toolResult(JSON.stringify(result, null, 2));
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.response?.status || e.message}`);
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(e.response?.status ? `Failed to set permissions: ${e.response.status}` : e.message);
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.response?.status || e.message}`);
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 someone's access to a file, project, or team. Looks up the role by user_id.",
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(e.response?.status ? `Failed to revoke access: ${e.response.status}` : e.message);
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
- return toolResult(JSON.stringify(result, null, 2));
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.response?.status || e.message}`);
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.response?.status || e.message}`);
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.response?.status || e.message}`);
165
+ return toolError(`Failed to deny request: ${formatApiError(e)}`);
163
166
  }
164
167
  });
165
168
  },
@@ -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 toolResult(JSON.stringify(result, null, 2));
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.response?.status || e.message}`);
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.response?.status || e.message}`);
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.response?.status || e.message}`);
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.response?.status || e.message}`);
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.response?.status || e.message}`);
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.response?.status || e.message}`);
137
+ return toolError(`Failed to set description: ${formatApiError(e)}`);
137
138
  }
138
139
  });
139
140
  },
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { defineTool, toolResult, toolError, figmaId } from './register.js';
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
- return toolResult(JSON.stringify(result, null, 2));
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.response?.status || e.message}`);
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 data for each requested node ID.',
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
- return toolResult(JSON.stringify(result, null, 2));
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.response?.status || e.message}`);
49
+ return toolError(`Failed to get nodes: ${formatApiError(e)}`);
46
50
  }
47
51
  });
48
52
  },
@@ -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 declare function resolveOrgId(config: AuthConfig, explicit?: string): string | undefined;
31
- export declare function requireOrgId(config: AuthConfig, explicit?: string): string;
37
+ export { resolveOrgId, requireOrgId } from '../helpers.js';
32
38
  //# sourceMappingURL=register.d.ts.map
@@ -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 function resolveOrgId(config, explicit) {
63
- const id = explicit || config.orgId;
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
@@ -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 toolResult(JSON.stringify(result, null, 2));
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.response?.status || e.message}`);
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.response?.status || e.message}`);
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: 'Delete a team. This is destructive and cannot be undone.',
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.response?.status || e.message}`);
69
+ return toolError(`Failed to delete team: ${formatApiError(e)}`);
69
70
  }
70
71
  });
71
72
  },
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { defineTool, toolResult, toolError, figmaId } from './register.js';
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
- return toolResult(JSON.stringify(result, null, 2));
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.response?.status || e.message}`);
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
- return toolResult(JSON.stringify(result, null, 2));
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.response?.status || e.message}`);
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 toolResult(JSON.stringify(result, null, 2));
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.response?.status || e.message}`);
84
+ return toolError(`Failed to update variables: ${formatApiError(e)}`);
81
85
  }
82
86
  });
83
87
  },
@@ -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 toolResult(JSON.stringify(result, null, 2));
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.response?.status || e.message}`);
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 toolResult(JSON.stringify(result, null, 2));
44
+ return toolSummary(`Created version "${result.label}".`, result);
44
45
  }
45
46
  catch (e) {
46
- return toolError(`Failed to create version: ${e.response?.status || e.message}`);
47
+ return toolError(`Failed to create version: ${formatApiError(e)}`);
47
48
  }
48
49
  });
49
50
  },