figma-claude-connector 1.0.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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { ListToolsRequestSchema, CallToolRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { WSBridge } from './ws-bridge.js';
6
+ import { registerAllTools } from './tools/index.js';
7
+ import { existsSync, mkdirSync, copyFileSync } from 'fs';
8
+ import { resolve, dirname, join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ // ── --setup: Copy Figma plugin files to local directory ──
11
+ if (process.argv.includes('--setup')) {
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const pluginSrc = resolve(__dirname, '..', 'figma-plugin');
14
+ const dest = resolve(process.cwd(), 'claude-connector-plugin');
15
+ if (!existsSync(pluginSrc)) {
16
+ console.error('Error: Plugin files not found in package.');
17
+ process.exit(1);
18
+ }
19
+ mkdirSync(dest, { recursive: true });
20
+ for (const file of ['manifest.json', 'ui.html', 'code.js']) {
21
+ const src = join(pluginSrc, file);
22
+ if (existsSync(src)) {
23
+ copyFileSync(src, join(dest, file));
24
+ }
25
+ }
26
+ // Update manifest main path (no dist/ prefix in flat structure)
27
+ const manifestPath = join(dest, 'manifest.json');
28
+ const manifestContent = await import('fs').then(fs => fs.readFileSync(manifestPath, 'utf-8').replace('"main": "dist/code.js"', '"main": "code.js"'));
29
+ await import('fs').then(fs => fs.writeFileSync(manifestPath, manifestContent));
30
+ console.log(`
31
+ ┌─────────────────────────────────────────────┐
32
+ │ Claude Connector - Setup │
33
+ ├─────────────────────────────────────────────┤
34
+ │ │
35
+ │ Plugin files copied to: │
36
+ │ ${dest}
37
+ │ │
38
+ │ Next steps: │
39
+ │ │
40
+ │ 1. Open Figma Desktop │
41
+ │ 2. Plugins → Development │
42
+ │ → Import plugin from manifest... │
43
+ │ 3. Select: ${dest}/manifest.json
44
+ │ │
45
+ │ 4. Add to .mcp.json: │
46
+ │ { │
47
+ │ "mcpServers": { │
48
+ │ "figma": { │
49
+ │ "command": "npx", │
50
+ │ "args": ["figma-claude-connector"] │
51
+ │ } │
52
+ │ } │
53
+ │ } │
54
+ │ │
55
+ │ 5. Run plugin in Figma → Done! │
56
+ │ │
57
+ └─────────────────────────────────────────────┘
58
+ `);
59
+ process.exit(0);
60
+ }
61
+ const bridge = new WSBridge();
62
+ const tools = registerAllTools(bridge);
63
+ const server = new Server({ name: 'figma-mcp-bridge', version: '1.0.0' }, { capabilities: { tools: {} } });
64
+ // List all available tools
65
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
66
+ tools: tools.map(({ name, description, inputSchema }) => ({
67
+ name,
68
+ description,
69
+ inputSchema,
70
+ })),
71
+ }));
72
+ // Handle tool calls
73
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
74
+ const { name, arguments: args } = request.params;
75
+ const tool = tools.find((t) => t.name === name);
76
+ if (!tool) {
77
+ return {
78
+ content: [{ type: 'text', text: `Unknown tool: ${name}` }],
79
+ isError: true,
80
+ };
81
+ }
82
+ if (!bridge.isConnected) {
83
+ return {
84
+ content: [{
85
+ type: 'text',
86
+ text: 'Figma plugin not connected. Please open the Figma MCP Bridge plugin in Figma Desktop.',
87
+ }],
88
+ isError: true,
89
+ };
90
+ }
91
+ try {
92
+ const result = await tool.handler(args || {});
93
+ return {
94
+ content: [{
95
+ type: 'text',
96
+ text: JSON.stringify(result, null, 2),
97
+ }],
98
+ };
99
+ }
100
+ catch (err) {
101
+ return {
102
+ content: [{
103
+ type: 'text',
104
+ text: `Error: ${err.message}`,
105
+ }],
106
+ isError: true,
107
+ };
108
+ }
109
+ });
110
+ // Start server
111
+ async function main() {
112
+ await bridge.start(3055);
113
+ const transport = new StdioServerTransport();
114
+ await server.connect(transport);
115
+ console.error('[MCP] Figma MCP Bridge server started');
116
+ }
117
+ main().catch((err) => {
118
+ console.error('[MCP] Fatal error:', err);
119
+ process.exit(1);
120
+ });
121
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ import { ToolDefinition } from '../types.js';
2
+ import { WSBridge } from '../ws-bridge.js';
3
+ export declare function createAnnotationTools(bridge: WSBridge): ToolDefinition[];
@@ -0,0 +1,55 @@
1
+ export function createAnnotationTools(bridge) {
2
+ return [
3
+ {
4
+ name: 'figma_add_annotation',
5
+ description: 'Add a visual annotation (comment bubble) next to a node on the canvas. Supports color presets: amber (default), red (issue), blue (info), green (done), purple (question).',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ nodeId: { type: 'string', description: 'Target node ID to annotate' },
10
+ text: { type: 'string', description: 'Comment text' },
11
+ author: { type: 'string', description: 'Author name (default: "Claude MCP")' },
12
+ color: {
13
+ type: 'string',
14
+ enum: ['#FBBF24', '#EF4444', '#3B82F6', '#10B981', '#8B5CF6'],
15
+ description: 'Color: amber (#FBBF24, default), red (#EF4444, issue), blue (#3B82F6, info), green (#10B981, done), purple (#8B5CF6, question)',
16
+ },
17
+ position: {
18
+ type: 'string',
19
+ enum: ['right', 'left', 'top', 'bottom'],
20
+ description: 'Position relative to target node (default: "right")',
21
+ },
22
+ maxWidth: { type: 'number', description: 'Max annotation width (default: 240)' },
23
+ timestamp: { type: 'string', description: 'Custom timestamp string (default: auto-generated)' },
24
+ },
25
+ required: ['nodeId', 'text'],
26
+ },
27
+ handler: async (params) => bridge.sendAction('addAnnotation', params),
28
+ },
29
+ {
30
+ name: 'figma_get_annotations',
31
+ description: 'Get all annotations attached to a node.',
32
+ inputSchema: {
33
+ type: 'object',
34
+ properties: {
35
+ nodeId: { type: 'string', description: 'Target node ID' },
36
+ },
37
+ required: ['nodeId'],
38
+ },
39
+ handler: async (params) => bridge.sendAction('getAnnotations', params),
40
+ },
41
+ {
42
+ name: 'figma_delete_annotation',
43
+ description: 'Delete an annotation by its ID.',
44
+ inputSchema: {
45
+ type: 'object',
46
+ properties: {
47
+ annotationId: { type: 'string', description: 'Annotation node ID to delete' },
48
+ },
49
+ required: ['annotationId'],
50
+ },
51
+ handler: async (params) => bridge.sendAction('deleteAnnotation', params),
52
+ },
53
+ ];
54
+ }
55
+ //# sourceMappingURL=annotations.js.map
@@ -0,0 +1,3 @@
1
+ import { ToolDefinition } from '../types.js';
2
+ import { WSBridge } from '../ws-bridge.js';
3
+ export declare function createCommentTools(bridge: WSBridge): ToolDefinition[];
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Figma REST API를 통한 네이티브 댓글 기능
3
+ * 환경변수 FIGMA_TOKEN과 FIGMA_FILE_KEY 필요
4
+ */
5
+ async function figmaApi(method, path, body) {
6
+ const token = process.env.FIGMA_TOKEN;
7
+ if (!token)
8
+ throw new Error('FIGMA_TOKEN 환경변수가 설정되지 않았습니다. Figma Personal Access Token을 설정하세요.');
9
+ const url = `https://api.figma.com/v1${path}`;
10
+ const res = await fetch(url, {
11
+ method,
12
+ headers: {
13
+ 'X-Figma-Token': token,
14
+ 'Content-Type': 'application/json',
15
+ },
16
+ body: body ? JSON.stringify(body) : undefined,
17
+ });
18
+ if (!res.ok) {
19
+ const text = await res.text();
20
+ throw new Error(`Figma API ${res.status}: ${text}`);
21
+ }
22
+ return res.json();
23
+ }
24
+ let cachedFileKey = null;
25
+ async function getFileKey(bridge) {
26
+ // 1. 환경변수 우선
27
+ if (process.env.FIGMA_FILE_KEY)
28
+ return process.env.FIGMA_FILE_KEY;
29
+ // 2. 캐시
30
+ if (cachedFileKey)
31
+ return cachedFileKey;
32
+ // 3. 플러그인에서 자동 추출
33
+ try {
34
+ const info = await bridge.sendAction('getFileInfo', {});
35
+ if (info.fileKey) {
36
+ cachedFileKey = info.fileKey;
37
+ return info.fileKey;
38
+ }
39
+ }
40
+ catch { }
41
+ throw new Error('File key를 가져올 수 없습니다. 플러그인이 연결되어 있는지 확인하세요.');
42
+ }
43
+ export function createCommentTools(bridge) {
44
+ return [
45
+ {
46
+ name: 'figma_post_comment',
47
+ description: 'Post a native Figma comment on the file. Shows in the Figma comment panel and sends notifications. Requires FIGMA_TOKEN and FIGMA_FILE_KEY env vars. Optionally pin to a specific node or canvas coordinates.',
48
+ inputSchema: {
49
+ type: 'object',
50
+ properties: {
51
+ message: { type: 'string', description: 'Comment text (supports @mentions with Figma user IDs)' },
52
+ nodeId: { type: 'string', description: 'Pin comment to a specific node (optional)' },
53
+ x: { type: 'number', description: 'Canvas X coordinate for pinned comment (optional, used with nodeId or standalone)' },
54
+ y: { type: 'number', description: 'Canvas Y coordinate for pinned comment (optional)' },
55
+ },
56
+ required: ['message'],
57
+ },
58
+ handler: async (params) => {
59
+ const fileKey = await getFileKey(bridge);
60
+ const body = { message: params.message };
61
+ if (params.nodeId) {
62
+ // 노드에 핀 달기 — 노드 위치 조회
63
+ let x = params.x ?? 0;
64
+ let y = params.y ?? 0;
65
+ if (params.x === undefined && params.y === undefined) {
66
+ // 플러그인에서 노드 위치 가져오기
67
+ try {
68
+ const nodeInfo = await bridge.sendAction('getNodeDetails', { nodeId: params.nodeId });
69
+ x = (nodeInfo.x || 0) + (nodeInfo.width || 0) / 2;
70
+ y = (nodeInfo.y || 0);
71
+ }
72
+ catch {
73
+ // fallback: 0,0
74
+ }
75
+ }
76
+ body.client_meta = {
77
+ node_id: params.nodeId,
78
+ node_offset: { x, y },
79
+ };
80
+ }
81
+ else if (params.x !== undefined && params.y !== undefined) {
82
+ body.client_meta = {
83
+ x: params.x,
84
+ y: params.y,
85
+ };
86
+ }
87
+ const result = await figmaApi('POST', `/files/${fileKey}/comments`, body);
88
+ return {
89
+ id: result.id,
90
+ message: result.message,
91
+ user: result.user?.handle,
92
+ createdAt: result.created_at,
93
+ nodeId: params.nodeId,
94
+ };
95
+ },
96
+ },
97
+ {
98
+ name: 'figma_get_comments',
99
+ description: 'Get all comments on the current Figma file. Requires FIGMA_TOKEN and FIGMA_FILE_KEY env vars.',
100
+ inputSchema: {
101
+ type: 'object',
102
+ properties: {
103
+ asMarkdown: { type: 'boolean', description: 'Format output as readable markdown (default: false)' },
104
+ },
105
+ },
106
+ handler: async (params) => {
107
+ const fileKey = await getFileKey(bridge);
108
+ const result = await figmaApi('GET', `/files/${fileKey}/comments`);
109
+ const comments = (result.comments || []).map((c) => ({
110
+ id: c.id,
111
+ message: c.message,
112
+ user: c.user?.handle,
113
+ createdAt: c.created_at,
114
+ resolvedAt: c.resolved_at,
115
+ nodeId: c.client_meta?.node_id,
116
+ orderId: c.order_id,
117
+ parentId: c.parent_id,
118
+ }));
119
+ if (params.asMarkdown) {
120
+ // 스레드 구조로 그룹핑
121
+ const roots = comments.filter((c) => !c.parentId);
122
+ const replies = comments.filter((c) => c.parentId);
123
+ let md = `# Comments (${comments.length})\n\n`;
124
+ for (const root of roots) {
125
+ const status = root.resolvedAt ? '✅' : '💬';
126
+ md += `${status} **${root.user}** — ${root.createdAt}\n`;
127
+ md += `> ${root.message}\n`;
128
+ if (root.nodeId)
129
+ md += `📌 Node: ${root.nodeId}\n`;
130
+ const childReplies = replies.filter((r) => r.parentId === root.id);
131
+ for (const reply of childReplies) {
132
+ md += ` ↳ **${reply.user}**: ${reply.message}\n`;
133
+ }
134
+ md += '\n';
135
+ }
136
+ return { markdown: md, count: comments.length };
137
+ }
138
+ return { comments, count: comments.length };
139
+ },
140
+ },
141
+ {
142
+ name: 'figma_reply_comment',
143
+ description: 'Reply to an existing Figma comment thread. Requires FIGMA_TOKEN and FIGMA_FILE_KEY env vars.',
144
+ inputSchema: {
145
+ type: 'object',
146
+ properties: {
147
+ commentId: { type: 'string', description: 'Parent comment ID to reply to' },
148
+ message: { type: 'string', description: 'Reply message' },
149
+ },
150
+ required: ['commentId', 'message'],
151
+ },
152
+ handler: async (params) => {
153
+ const fileKey = await getFileKey(bridge);
154
+ const result = await figmaApi('POST', `/files/${fileKey}/comments`, {
155
+ message: params.message,
156
+ comment_id: params.commentId,
157
+ });
158
+ return {
159
+ id: result.id,
160
+ message: result.message,
161
+ user: result.user?.handle,
162
+ parentId: params.commentId,
163
+ createdAt: result.created_at,
164
+ };
165
+ },
166
+ },
167
+ {
168
+ name: 'figma_resolve_comment',
169
+ description: 'Resolve (close) a Figma comment. Requires FIGMA_TOKEN and FIGMA_FILE_KEY env vars.',
170
+ inputSchema: {
171
+ type: 'object',
172
+ properties: {
173
+ commentId: { type: 'string', description: 'Comment ID to resolve' },
174
+ },
175
+ required: ['commentId'],
176
+ },
177
+ handler: async (params) => {
178
+ const fileKey = await getFileKey(bridge);
179
+ // Figma REST API doesn't have a direct resolve endpoint — use DELETE to remove
180
+ // Actually, there's no resolve via REST API; we can delete the comment
181
+ await figmaApi('DELETE', `/files/${fileKey}/comments/${params.commentId}`);
182
+ return { deleted: params.commentId };
183
+ },
184
+ },
185
+ ];
186
+ }
187
+ //# sourceMappingURL=comments.js.map
@@ -0,0 +1,3 @@
1
+ import { ToolDefinition } from '../types.js';
2
+ import { WSBridge } from '../ws-bridge.js';
3
+ export declare function createComponentTools(bridge: WSBridge): ToolDefinition[];
@@ -0,0 +1,124 @@
1
+ export function createComponentTools(bridge) {
2
+ return [
3
+ {
4
+ name: 'figma_create_button',
5
+ description: 'Create a button component with Auto Layout. Supports primary, secondary, and outline variants.',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ label: { type: 'string', description: 'Button text (default: "Button")' },
10
+ variant: { type: 'string', enum: ['primary', 'secondary', 'outline'], description: 'Button style variant (default: "primary")' },
11
+ width: { type: 'number', description: 'Width (default: auto)' },
12
+ height: { type: 'number', description: 'Height (default: 40)' },
13
+ cornerRadius: { type: 'number', description: 'Corner radius (default: 8)' },
14
+ x: { type: 'number', description: 'X position' },
15
+ y: { type: 'number', description: 'Y position' },
16
+ parentId: { type: 'string', description: 'Parent node ID' },
17
+ },
18
+ },
19
+ handler: async (params) => bridge.sendAction('createButton', params),
20
+ },
21
+ {
22
+ name: 'figma_create_card',
23
+ description: 'Create a card layout with optional header, content, and footer sections using Auto Layout.',
24
+ inputSchema: {
25
+ type: 'object',
26
+ properties: {
27
+ name: { type: 'string', description: 'Card name' },
28
+ header: { type: 'string', description: 'Header text' },
29
+ content: { type: 'string', description: 'Content/body text' },
30
+ footer: { type: 'string', description: 'Footer text' },
31
+ width: { type: 'number', description: 'Width (default: 320)' },
32
+ fill: { type: 'string', description: 'Background color (default: "#FFFFFF")' },
33
+ cornerRadius: { type: 'number', description: 'Corner radius (default: 12)' },
34
+ x: { type: 'number', description: 'X position' },
35
+ y: { type: 'number', description: 'Y position' },
36
+ parentId: { type: 'string', description: 'Parent node ID' },
37
+ },
38
+ },
39
+ handler: async (params) => bridge.sendAction('createCard', params),
40
+ },
41
+ {
42
+ name: 'figma_create_input',
43
+ description: 'Create an input field component with optional label.',
44
+ inputSchema: {
45
+ type: 'object',
46
+ properties: {
47
+ name: { type: 'string', description: 'Input name' },
48
+ placeholder: { type: 'string', description: 'Placeholder text (default: "Placeholder")' },
49
+ label: { type: 'string', description: 'Label text above the input' },
50
+ width: { type: 'number', description: 'Width (default: 240)' },
51
+ height: { type: 'number', description: 'Height (default: 40)' },
52
+ x: { type: 'number', description: 'X position' },
53
+ y: { type: 'number', description: 'Y position' },
54
+ parentId: { type: 'string', description: 'Parent node ID' },
55
+ },
56
+ },
57
+ handler: async (params) => bridge.sendAction('createInput', params),
58
+ },
59
+ {
60
+ name: 'figma_create_component',
61
+ description: 'Create a new Component node, or convert an existing frame to a Component. Components are reusable and support properties.',
62
+ inputSchema: {
63
+ type: 'object',
64
+ properties: {
65
+ name: { type: 'string', description: 'Component name' },
66
+ fromNodeId: { type: 'string', description: 'Convert existing frame to component (optional)' },
67
+ width: { type: 'number', description: 'Width (when creating new)' },
68
+ height: { type: 'number', description: 'Height (when creating new)' },
69
+ fill: { type: 'string', description: 'Background color hex' },
70
+ cornerRadius: { type: 'number', description: 'Corner radius' },
71
+ x: { type: 'number', description: 'X position' },
72
+ y: { type: 'number', description: 'Y position' },
73
+ parentId: { type: 'string', description: 'Parent node ID' },
74
+ },
75
+ },
76
+ handler: async (params) => bridge.sendAction('createComponentFromFrame', params),
77
+ },
78
+ {
79
+ name: 'figma_add_component_property',
80
+ description: 'Add a property to a Component node. Supports TEXT, BOOLEAN, VARIANT, and INSTANCE_SWAP types.',
81
+ inputSchema: {
82
+ type: 'object',
83
+ properties: {
84
+ nodeId: { type: 'string', description: 'Component node ID' },
85
+ propertyName: { type: 'string', description: 'Property name (e.g., "Label", "Disabled", "Style")' },
86
+ propertyType: { type: 'string', enum: ['TEXT', 'BOOLEAN', 'VARIANT', 'INSTANCE_SWAP'], description: 'Property type' },
87
+ defaultValue: { type: ['string', 'boolean'], description: 'Default value for the property' },
88
+ },
89
+ required: ['nodeId', 'propertyName', 'propertyType'],
90
+ },
91
+ handler: async (params) => bridge.sendAction('addComponentProperty', params),
92
+ },
93
+ {
94
+ name: 'figma_create_variants',
95
+ description: 'Combine multiple Component nodes into a single Variant Set (Component Set).',
96
+ inputSchema: {
97
+ type: 'object',
98
+ properties: {
99
+ name: { type: 'string', description: 'Variant set name' },
100
+ nodeIds: { type: 'array', items: { type: 'string' }, description: 'Array of Component node IDs to combine' },
101
+ },
102
+ required: ['nodeIds'],
103
+ },
104
+ handler: async (params) => bridge.sendAction('createComponentVariants', params),
105
+ },
106
+ {
107
+ name: 'figma_create_instance',
108
+ description: 'Create an instance of a Component. Optionally override component properties.',
109
+ inputSchema: {
110
+ type: 'object',
111
+ properties: {
112
+ componentId: { type: 'string', description: 'Source Component node ID' },
113
+ properties: { type: 'object', description: 'Property overrides (key-value pairs)' },
114
+ x: { type: 'number', description: 'X position' },
115
+ y: { type: 'number', description: 'Y position' },
116
+ parentId: { type: 'string', description: 'Parent node ID' },
117
+ },
118
+ required: ['componentId'],
119
+ },
120
+ handler: async (params) => bridge.sendAction('createInstance', params),
121
+ },
122
+ ];
123
+ }
124
+ //# sourceMappingURL=components.js.map
@@ -0,0 +1,3 @@
1
+ import { ToolDefinition } from '../types.js';
2
+ import { WSBridge } from '../ws-bridge.js';
3
+ export declare function createExportTools(bridge: WSBridge): ToolDefinition[];
@@ -0,0 +1,19 @@
1
+ export function createExportTools(bridge) {
2
+ return [
3
+ {
4
+ name: 'figma_export_image',
5
+ description: 'Export a node as PNG, SVG, or PDF. Returns base64-encoded data.',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ nodeId: { type: 'string', description: 'Node ID to export' },
10
+ format: { type: 'string', enum: ['PNG', 'SVG', 'PDF'], description: 'Export format (default: "PNG")' },
11
+ scale: { type: 'number', description: 'Scale factor for PNG (default: 2)' },
12
+ },
13
+ required: ['nodeId'],
14
+ },
15
+ handler: async (params) => bridge.sendAction('exportNode', params),
16
+ },
17
+ ];
18
+ }
19
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1,3 @@
1
+ import { ToolDefinition } from '../types.js';
2
+ import { WSBridge } from '../ws-bridge.js';
3
+ export declare function registerAllTools(bridge: WSBridge): ToolDefinition[];
@@ -0,0 +1,25 @@
1
+ import { createNodeTools } from './nodes.js';
2
+ import { createComponentTools } from './components.js';
3
+ import { createLayoutTools } from './layout.js';
4
+ import { createStyleTools } from './styles.js';
5
+ import { createTextTools } from './text.js';
6
+ import { createQueryTools } from './query.js';
7
+ import { createExportTools } from './export.js';
8
+ import { createMetadataTools } from './metadata.js';
9
+ import { createAnnotationTools } from './annotations.js';
10
+ import { createCommentTools } from './comments.js';
11
+ export function registerAllTools(bridge) {
12
+ return [
13
+ ...createNodeTools(bridge),
14
+ ...createComponentTools(bridge),
15
+ ...createLayoutTools(bridge),
16
+ ...createStyleTools(bridge),
17
+ ...createTextTools(bridge),
18
+ ...createQueryTools(bridge),
19
+ ...createExportTools(bridge),
20
+ ...createMetadataTools(bridge),
21
+ ...createAnnotationTools(bridge),
22
+ ...createCommentTools(bridge),
23
+ ];
24
+ }
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ import { ToolDefinition } from '../types.js';
2
+ import { WSBridge } from '../ws-bridge.js';
3
+ export declare function createLayoutTools(bridge: WSBridge): ToolDefinition[];