figmanage 0.1.0

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +338 -0
  3. package/dist/auth/client.d.ts +15 -0
  4. package/dist/auth/client.js +29 -0
  5. package/dist/auth/health.d.ts +5 -0
  6. package/dist/auth/health.js +93 -0
  7. package/dist/clients/internal-api.d.ts +4 -0
  8. package/dist/clients/internal-api.js +50 -0
  9. package/dist/clients/public-api.d.ts +4 -0
  10. package/dist/clients/public-api.js +47 -0
  11. package/dist/index.d.ts +19 -0
  12. package/dist/index.js +100 -0
  13. package/dist/setup.d.ts +3 -0
  14. package/dist/setup.js +565 -0
  15. package/dist/tools/analytics.d.ts +2 -0
  16. package/dist/tools/analytics.js +58 -0
  17. package/dist/tools/branching.d.ts +2 -0
  18. package/dist/tools/branching.js +103 -0
  19. package/dist/tools/comments.d.ts +2 -0
  20. package/dist/tools/comments.js +147 -0
  21. package/dist/tools/components.d.ts +2 -0
  22. package/dist/tools/components.js +104 -0
  23. package/dist/tools/compound.d.ts +2 -0
  24. package/dist/tools/compound.js +334 -0
  25. package/dist/tools/export.d.ts +2 -0
  26. package/dist/tools/export.js +70 -0
  27. package/dist/tools/files.d.ts +2 -0
  28. package/dist/tools/files.js +241 -0
  29. package/dist/tools/libraries.d.ts +2 -0
  30. package/dist/tools/libraries.js +31 -0
  31. package/dist/tools/navigate.d.ts +2 -0
  32. package/dist/tools/navigate.js +436 -0
  33. package/dist/tools/org.d.ts +2 -0
  34. package/dist/tools/org.js +311 -0
  35. package/dist/tools/permissions.d.ts +2 -0
  36. package/dist/tools/permissions.js +246 -0
  37. package/dist/tools/projects.d.ts +2 -0
  38. package/dist/tools/projects.js +160 -0
  39. package/dist/tools/reading.d.ts +2 -0
  40. package/dist/tools/reading.js +60 -0
  41. package/dist/tools/register.d.ts +32 -0
  42. package/dist/tools/register.js +76 -0
  43. package/dist/tools/teams.d.ts +2 -0
  44. package/dist/tools/teams.js +81 -0
  45. package/dist/tools/variables.d.ts +2 -0
  46. package/dist/tools/variables.js +102 -0
  47. package/dist/tools/versions.d.ts +2 -0
  48. package/dist/tools/versions.js +69 -0
  49. package/dist/tools/webhooks.d.ts +2 -0
  50. package/dist/tools/webhooks.js +126 -0
  51. package/dist/types/figma.d.ts +58 -0
  52. package/dist/types/figma.js +2 -0
  53. package/package.json +55 -0
@@ -0,0 +1,160 @@
1
+ import { z } from 'zod';
2
+ import { internalClient } from '../clients/internal-api.js';
3
+ import { defineTool, toolResult, toolError, figmaId } from './register.js';
4
+ // -- create_project --
5
+ defineTool({
6
+ toolset: 'projects',
7
+ auth: 'cookie',
8
+ mutates: true,
9
+ register(server, config) {
10
+ server.registerTool('create_project', {
11
+ description: 'Create a new project (folder) in a team.',
12
+ inputSchema: {
13
+ team_id: figmaId.describe('Team ID to create the project in'),
14
+ name: z.string().describe('Project name'),
15
+ },
16
+ }, async ({ team_id, name }) => {
17
+ try {
18
+ const res = await internalClient(config).post('/api/folders', {
19
+ team_id,
20
+ path: name,
21
+ sharing_audience_control: 'org_view',
22
+ team_access: 'team_edit',
23
+ });
24
+ const meta = res.data?.meta;
25
+ const p = Array.isArray(meta) ? meta[0] : (meta?.folder || meta || res.data);
26
+ return toolResult(JSON.stringify({
27
+ id: String(p.id),
28
+ name: p.name || p.path || name,
29
+ team_id,
30
+ }, null, 2));
31
+ }
32
+ catch (e) {
33
+ return toolError(`Failed to create project: ${e.response?.status || e.message}`);
34
+ }
35
+ });
36
+ },
37
+ });
38
+ // -- rename_project --
39
+ defineTool({
40
+ toolset: 'projects',
41
+ auth: 'cookie',
42
+ mutates: true,
43
+ register(server, config) {
44
+ server.registerTool('rename_project', {
45
+ description: 'Rename a project (folder).',
46
+ inputSchema: {
47
+ project_id: figmaId.describe('Project ID'),
48
+ name: z.string().describe('New name'),
49
+ },
50
+ }, async ({ project_id, name }) => {
51
+ try {
52
+ await internalClient(config).put('/api/folders/rename', {
53
+ folder_id: project_id,
54
+ name,
55
+ });
56
+ return toolResult(`Renamed project ${project_id} to "${name}"`);
57
+ }
58
+ catch (e) {
59
+ return toolError(`Failed to rename project: ${e.response?.status || e.message}`);
60
+ }
61
+ });
62
+ },
63
+ });
64
+ // -- move_project --
65
+ defineTool({
66
+ toolset: 'projects',
67
+ auth: 'cookie',
68
+ mutates: true,
69
+ register(server, config) {
70
+ server.registerTool('move_project', {
71
+ description: 'Move a project to a different team.',
72
+ inputSchema: {
73
+ project_id: figmaId.describe('Project ID to move'),
74
+ destination_team_id: figmaId.describe('Destination team ID'),
75
+ },
76
+ }, async ({ project_id, destination_team_id }) => {
77
+ try {
78
+ await internalClient(config).put('/api/folders/move', {
79
+ folder_id: project_id,
80
+ team_id: destination_team_id,
81
+ });
82
+ return toolResult(`Moved project ${project_id} to team ${destination_team_id}`);
83
+ }
84
+ catch (e) {
85
+ return toolError(`Failed to move project: ${e.response?.status || e.message}`);
86
+ }
87
+ });
88
+ },
89
+ });
90
+ // -- trash_project --
91
+ defineTool({
92
+ toolset: 'projects',
93
+ auth: 'cookie',
94
+ mutates: true,
95
+ destructive: true,
96
+ register(server, config) {
97
+ server.registerTool('trash_project', {
98
+ description: 'Move a project to trash.',
99
+ inputSchema: {
100
+ project_id: figmaId.describe('Project ID to trash'),
101
+ },
102
+ }, async ({ project_id }) => {
103
+ try {
104
+ await internalClient(config).put(`/api/folders/trash/${project_id}`);
105
+ return toolResult(`Trashed project ${project_id}`);
106
+ }
107
+ catch (e) {
108
+ return toolError(`Failed to trash project: ${e.response?.status || e.message}`);
109
+ }
110
+ });
111
+ },
112
+ });
113
+ // -- restore_project --
114
+ defineTool({
115
+ toolset: 'projects',
116
+ auth: 'cookie',
117
+ mutates: true,
118
+ register(server, config) {
119
+ server.registerTool('restore_project', {
120
+ description: 'Restore a project from trash.',
121
+ inputSchema: {
122
+ project_id: figmaId.describe('Project ID to restore'),
123
+ },
124
+ }, async ({ project_id }) => {
125
+ try {
126
+ await internalClient(config).put(`/api/folders/restore/${project_id}`);
127
+ return toolResult(`Restored project ${project_id}`);
128
+ }
129
+ catch (e) {
130
+ return toolError(`Failed to restore project: ${e.response?.status || e.message}`);
131
+ }
132
+ });
133
+ },
134
+ });
135
+ // -- set_project_description --
136
+ defineTool({
137
+ toolset: 'projects',
138
+ auth: 'cookie',
139
+ mutates: true,
140
+ register(server, config) {
141
+ server.registerTool('set_project_description', {
142
+ description: 'Set or update a project description.',
143
+ inputSchema: {
144
+ project_id: figmaId.describe('Project ID'),
145
+ description: z.string().describe('New description text'),
146
+ },
147
+ }, async ({ project_id, description }) => {
148
+ try {
149
+ await internalClient(config).put(`/api/folders/${project_id}/description`, {
150
+ description,
151
+ });
152
+ return toolResult(`Updated description for project ${project_id}`);
153
+ }
154
+ catch (e) {
155
+ return toolError(`Failed to set description: ${e.response?.status || e.message}`);
156
+ }
157
+ });
158
+ },
159
+ });
160
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=reading.d.ts.map
@@ -0,0 +1,60 @@
1
+ import { z } from 'zod';
2
+ import { publicClient } from '../clients/public-api.js';
3
+ import { defineTool, toolResult, toolError, figmaId } from './register.js';
4
+ // -- get_file --
5
+ defineTool({
6
+ toolset: 'reading',
7
+ auth: 'pat',
8
+ register(server, config) {
9
+ 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
+ inputSchema: {
12
+ file_key: figmaId.describe('File key (or branch file key from list_branches)'),
13
+ depth: z.number().int().optional().describe('Tree depth limit. 0=root, 1=pages, 2=top-level frames. Omit for full tree.'),
14
+ node_id: figmaId.optional().describe('Start from a specific node instead of document root'),
15
+ },
16
+ }, async ({ file_key, depth, node_id }) => {
17
+ try {
18
+ const params = {};
19
+ if (depth !== undefined)
20
+ params.depth = String(depth);
21
+ if (node_id)
22
+ params['node-id'] = node_id;
23
+ const res = await publicClient(config).get(`/v1/files/${file_key}`, { params });
24
+ return toolResult(JSON.stringify(res.data, null, 2));
25
+ }
26
+ catch (e) {
27
+ return toolError(`Failed to get file: ${e.response?.status || e.message}`);
28
+ }
29
+ });
30
+ },
31
+ });
32
+ // -- get_nodes --
33
+ defineTool({
34
+ toolset: 'reading',
35
+ auth: 'pat',
36
+ register(server, config) {
37
+ server.registerTool('get_nodes', {
38
+ description: 'Read specific nodes from a file. Returns node data for each requested node ID.',
39
+ inputSchema: {
40
+ file_key: figmaId.describe('File key'),
41
+ node_ids: z.array(figmaId).min(1).describe('Node IDs to fetch'),
42
+ depth: z.number().int().optional().describe('Depth limit per node'),
43
+ },
44
+ }, async ({ file_key, node_ids, depth }) => {
45
+ try {
46
+ const params = {
47
+ ids: node_ids.join(','),
48
+ };
49
+ if (depth !== undefined)
50
+ params.depth = String(depth);
51
+ const res = await publicClient(config).get(`/v1/files/${file_key}/nodes`, { params });
52
+ return toolResult(JSON.stringify(res.data, null, 2));
53
+ }
54
+ catch (e) {
55
+ return toolError(`Failed to get nodes: ${e.response?.status || e.message}`);
56
+ }
57
+ });
58
+ },
59
+ });
60
+ //# sourceMappingURL=reading.js.map
@@ -0,0 +1,32 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { AuthConfig } from '../auth/client.js';
3
+ import type { Toolset } from '../types/figma.js';
4
+ import { z } from 'zod';
5
+ /** Validates that a string is a safe Figma ID (alphanumeric, hyphens, underscores, dots, colons) */
6
+ export declare const figmaId: z.ZodString;
7
+ export type AuthRequirement = 'pat' | 'cookie' | 'either';
8
+ export interface ToolDef {
9
+ toolset: Toolset;
10
+ auth: AuthRequirement;
11
+ mutates?: boolean;
12
+ destructive?: boolean;
13
+ register: (server: McpServer, config: AuthConfig) => void;
14
+ }
15
+ export declare function defineTool(def: ToolDef): void;
16
+ export declare function registerTools(server: McpServer, config: AuthConfig, enabledToolsets: Set<Toolset>, readOnly: boolean): void;
17
+ export declare function toolResult(text: string): {
18
+ content: Array<{
19
+ type: 'text';
20
+ text: string;
21
+ }>;
22
+ };
23
+ export declare function toolError(message: string): {
24
+ isError: true;
25
+ content: Array<{
26
+ type: 'text';
27
+ text: string;
28
+ }>;
29
+ };
30
+ export declare function resolveOrgId(config: AuthConfig, explicit?: string): string | undefined;
31
+ export declare function requireOrgId(config: AuthConfig, explicit?: string): string;
32
+ //# sourceMappingURL=register.d.ts.map
@@ -0,0 +1,76 @@
1
+ import { hasPat, hasCookie } from '../auth/client.js';
2
+ import { z } from 'zod';
3
+ /** Validates that a string is a safe Figma ID (alphanumeric, hyphens, underscores, dots, colons) */
4
+ export const figmaId = z.string().regex(/^[\w.:-]+$/, 'Invalid ID format');
5
+ const allTools = [];
6
+ export function defineTool(def) {
7
+ allTools.push(def);
8
+ }
9
+ /** Build MCP tool annotations from ToolDef flags. */
10
+ function buildAnnotations(tool) {
11
+ const annotations = { openWorldHint: true };
12
+ if (!tool.mutates) {
13
+ annotations.readOnlyHint = true;
14
+ }
15
+ else {
16
+ annotations.destructiveHint = !!tool.destructive;
17
+ }
18
+ return annotations;
19
+ }
20
+ /**
21
+ * Wrap an McpServer so that every registerTool call automatically
22
+ * injects annotations derived from the ToolDef metadata.
23
+ */
24
+ function withAnnotations(server, tool) {
25
+ const annotations = buildAnnotations(tool);
26
+ return new Proxy(server, {
27
+ get(target, prop, receiver) {
28
+ if (prop === 'registerTool') {
29
+ return (name, config, ...rest) => {
30
+ const augmented = { ...config, annotations: { ...annotations, ...(config.annotations || {}) } };
31
+ return target.registerTool.call(target, name, augmented, ...rest);
32
+ };
33
+ }
34
+ return Reflect.get(target, prop, receiver);
35
+ },
36
+ });
37
+ }
38
+ export function registerTools(server, config, enabledToolsets, readOnly) {
39
+ for (const tool of allTools) {
40
+ // Filter by enabled toolsets
41
+ if (!enabledToolsets.has(tool.toolset))
42
+ continue;
43
+ // Filter by read-only mode
44
+ if (readOnly && tool.mutates)
45
+ continue;
46
+ // Filter by available auth
47
+ if (tool.auth === 'pat' && !hasPat(config))
48
+ continue;
49
+ if (tool.auth === 'cookie' && !hasCookie(config))
50
+ continue;
51
+ if (tool.auth === 'either' && !hasPat(config) && !hasCookie(config))
52
+ continue;
53
+ tool.register(withAnnotations(server, tool), config);
54
+ }
55
+ }
56
+ export function toolResult(text) {
57
+ return { content: [{ type: 'text', text }] };
58
+ }
59
+ export function toolError(message) {
60
+ return { isError: true, content: [{ type: 'text', text: message }] };
61
+ }
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
+ }
76
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=teams.d.ts.map
@@ -0,0 +1,81 @@
1
+ import { z } from 'zod';
2
+ import { internalClient } from '../clients/internal-api.js';
3
+ import { defineTool, toolResult, toolError, figmaId, requireOrgId } from './register.js';
4
+ // -- create_team --
5
+ defineTool({
6
+ toolset: 'teams',
7
+ auth: 'cookie',
8
+ mutates: true,
9
+ register(server, config) {
10
+ server.registerTool('create_team', {
11
+ description: 'Create a new team in the org.',
12
+ inputSchema: {
13
+ name: z.string().describe('Team name'),
14
+ org_id: figmaId.optional().describe('Org ID override (defaults to current workspace)'),
15
+ },
16
+ }, async ({ name, org_id }) => {
17
+ try {
18
+ let orgId;
19
+ try {
20
+ orgId = requireOrgId(config, org_id);
21
+ }
22
+ catch (e) {
23
+ return toolError(e.message);
24
+ }
25
+ const res = await internalClient(config).post('/api/teams/create', { team_name: name, org_id: orgId, sharing_audience_control: 'org_view' });
26
+ const team = res.data?.meta?.team || res.data?.meta || res.data;
27
+ return toolResult(JSON.stringify({ id: String(team.id), name: team.name }, null, 2));
28
+ }
29
+ catch (e) {
30
+ return toolError(`Failed to create team: ${e.response?.status || e.message}`);
31
+ }
32
+ });
33
+ },
34
+ });
35
+ // -- rename_team --
36
+ defineTool({
37
+ toolset: 'teams',
38
+ auth: 'cookie',
39
+ mutates: true,
40
+ register(server, config) {
41
+ server.registerTool('rename_team', {
42
+ description: 'Rename an existing team.',
43
+ inputSchema: {
44
+ team_id: figmaId.describe('Team ID'),
45
+ name: z.string().describe('New team name'),
46
+ },
47
+ }, async ({ team_id, name }) => {
48
+ try {
49
+ await internalClient(config).put(`/api/teams/${team_id}`, { name });
50
+ return toolResult(`Team ${team_id} renamed to "${name}".`);
51
+ }
52
+ catch (e) {
53
+ return toolError(`Failed to rename team: ${e.response?.status || e.message}`);
54
+ }
55
+ });
56
+ },
57
+ });
58
+ // -- delete_team --
59
+ defineTool({
60
+ toolset: 'teams',
61
+ auth: 'cookie',
62
+ mutates: true,
63
+ destructive: true,
64
+ register(server, config) {
65
+ server.registerTool('delete_team', {
66
+ description: 'Delete a team. This is destructive and cannot be undone.',
67
+ inputSchema: {
68
+ team_id: figmaId.describe('Team ID'),
69
+ },
70
+ }, async ({ team_id }) => {
71
+ try {
72
+ await internalClient(config).delete(`/api/teams/${team_id}`);
73
+ return toolResult(`Team ${team_id} deleted.`);
74
+ }
75
+ catch (e) {
76
+ return toolError(`Failed to delete team: ${e.response?.status || e.message}`);
77
+ }
78
+ });
79
+ },
80
+ });
81
+ //# sourceMappingURL=teams.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=variables.d.ts.map
@@ -0,0 +1,102 @@
1
+ import { z } from 'zod';
2
+ import { publicClient } from '../clients/public-api.js';
3
+ import { defineTool, toolResult, toolError, figmaId } from './register.js';
4
+ const ENTERPRISE_ERROR = 'Variables API requires Figma Enterprise plan. The file_variables:read/write scopes are not available on standard plans.';
5
+ function isEnterpriseScopeError(e) {
6
+ if (e.response?.status !== 403)
7
+ return false;
8
+ const data = e.response?.data;
9
+ if (typeof data !== 'object' || !data)
10
+ return false;
11
+ const msg = String(data.message || data.error || data.err || '');
12
+ return msg.toLowerCase().includes('scope');
13
+ }
14
+ // -- list_local_variables --
15
+ defineTool({
16
+ toolset: 'variables',
17
+ auth: 'pat',
18
+ register(server, config) {
19
+ server.registerTool('list_local_variables', {
20
+ description: 'List local variables and variable collections in a file. Requires Enterprise plan.',
21
+ inputSchema: {
22
+ file_key: figmaId.describe('File key'),
23
+ },
24
+ }, async ({ file_key }) => {
25
+ try {
26
+ const res = await publicClient(config).get(`/v1/files/${file_key}/variables/local`);
27
+ return toolResult(JSON.stringify(res.data?.meta ?? {}, null, 2));
28
+ }
29
+ catch (e) {
30
+ if (isEnterpriseScopeError(e))
31
+ return toolError(ENTERPRISE_ERROR);
32
+ return toolError(`Failed to list local variables: ${e.response?.status || e.message}`);
33
+ }
34
+ });
35
+ },
36
+ });
37
+ // -- list_published_variables --
38
+ defineTool({
39
+ toolset: 'variables',
40
+ auth: 'pat',
41
+ register(server, config) {
42
+ server.registerTool('list_published_variables', {
43
+ description: 'List published variables from a library file. Requires Enterprise plan.',
44
+ inputSchema: {
45
+ file_key: figmaId.describe('File key'),
46
+ },
47
+ }, async ({ file_key }) => {
48
+ try {
49
+ const res = await publicClient(config).get(`/v1/files/${file_key}/variables/published`);
50
+ return toolResult(JSON.stringify(res.data?.meta ?? {}, null, 2));
51
+ }
52
+ catch (e) {
53
+ if (isEnterpriseScopeError(e))
54
+ return toolError(ENTERPRISE_ERROR);
55
+ return toolError(`Failed to list published variables: ${e.response?.status || e.message}`);
56
+ }
57
+ });
58
+ },
59
+ });
60
+ // -- update_variables --
61
+ defineTool({
62
+ toolset: 'variables',
63
+ auth: 'pat',
64
+ mutates: true,
65
+ destructive: true,
66
+ register(server, config) {
67
+ server.registerTool('update_variables', {
68
+ description: 'Bulk create, update, or delete variables, collections, modes, and mode values. Requires Enterprise plan.',
69
+ inputSchema: {
70
+ file_key: figmaId.describe('File key'),
71
+ variable_collections: z.array(z.record(z.any())).optional().describe('Collection operations (action: CREATE, UPDATE, or DELETE)'),
72
+ variable_modes: z.array(z.record(z.any())).optional().describe('Mode operations (action: CREATE, UPDATE, or DELETE)'),
73
+ variables: z.array(z.record(z.any())).optional().describe('Variable operations (action: CREATE, UPDATE, or DELETE)'),
74
+ variable_mode_values: z.array(z.record(z.any())).optional().describe('Value assignments (action: CREATE, UPDATE, or DELETE)'),
75
+ },
76
+ }, async ({ file_key, variable_collections, variable_modes, variables, variable_mode_values }) => {
77
+ try {
78
+ if (!variable_collections?.length && !variable_modes?.length &&
79
+ !variables?.length && !variable_mode_values?.length) {
80
+ return toolError('At least one operation array is required.');
81
+ }
82
+ const body = {};
83
+ if (variable_collections)
84
+ body.variableCollections = variable_collections;
85
+ if (variable_modes)
86
+ body.variableModes = variable_modes;
87
+ if (variables)
88
+ body.variables = variables;
89
+ if (variable_mode_values)
90
+ body.variableModeValues = variable_mode_values;
91
+ const res = await publicClient(config).post(`/v1/files/${file_key}/variables`, body);
92
+ return toolResult(JSON.stringify(res.data, null, 2));
93
+ }
94
+ catch (e) {
95
+ if (isEnterpriseScopeError(e))
96
+ return toolError(ENTERPRISE_ERROR);
97
+ return toolError(`Failed to update variables: ${e.response?.status || e.message}`);
98
+ }
99
+ });
100
+ },
101
+ });
102
+ //# sourceMappingURL=variables.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=versions.d.ts.map
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod';
2
+ import { publicClient } from '../clients/public-api.js';
3
+ import { internalClient } from '../clients/internal-api.js';
4
+ import { defineTool, toolResult, toolError, figmaId } from './register.js';
5
+ // -- list_versions --
6
+ defineTool({
7
+ toolset: 'versions',
8
+ auth: 'pat',
9
+ register(server, config) {
10
+ server.registerTool('list_versions', {
11
+ description: 'List version history for a file. Returns named snapshots and auto-saves.',
12
+ inputSchema: {
13
+ file_key: figmaId.describe('File key'),
14
+ },
15
+ }, async ({ file_key }) => {
16
+ try {
17
+ const res = await publicClient(config).get(`/v1/files/${file_key}/versions`);
18
+ const versions = res.data?.versions || [];
19
+ const mapped = versions.map((v) => ({
20
+ id: v.id,
21
+ label: v.label || null,
22
+ description: v.description || null,
23
+ created_at: v.created_at,
24
+ user: v.user?.handle || v.user?.id || null,
25
+ }));
26
+ if (mapped.length === 0)
27
+ return toolResult('No versions found.');
28
+ return toolResult(JSON.stringify(mapped, null, 2));
29
+ }
30
+ catch (e) {
31
+ return toolError(`Failed to list versions: ${e.response?.status || e.message}`);
32
+ }
33
+ });
34
+ },
35
+ });
36
+ // -- create_version --
37
+ defineTool({
38
+ toolset: 'versions',
39
+ auth: 'cookie',
40
+ mutates: true,
41
+ register(server, config) {
42
+ server.registerTool('create_version', {
43
+ description: 'Create a named version (checkpoint) in a file\'s history.',
44
+ inputSchema: {
45
+ file_key: figmaId.describe('File key'),
46
+ title: z.string().describe('Version title/label'),
47
+ description: z.string().optional().describe('Version description'),
48
+ },
49
+ }, async ({ file_key, title, description }) => {
50
+ try {
51
+ const res = await internalClient(config).post(`/api/multiplayer/${file_key}/create_savepoint`, {
52
+ label: title,
53
+ description: description || '',
54
+ });
55
+ const v = res.data?.meta || {};
56
+ return toolResult(JSON.stringify({
57
+ id: v.id || null,
58
+ label: v.label || title,
59
+ description: v.description || null,
60
+ created_at: v.created_at || null,
61
+ }, null, 2));
62
+ }
63
+ catch (e) {
64
+ return toolError(`Failed to create version: ${e.response?.status || e.message}`);
65
+ }
66
+ });
67
+ },
68
+ });
69
+ //# sourceMappingURL=versions.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=webhooks.d.ts.map