@locofy/mcp 1.0.5 → 1.0.6

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/build/api.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import express from 'express';
2
2
  import { z } from 'zod';
3
3
  import { PullComponentsToolSchema, runPullComponentsTool } from './tools/pullComponents.js';
4
+ import { PullFilesToolSchema, runPullFilesTool } from './tools/pullFiles.js';
4
5
  // Only start the API server if DEBUG_MODE is set
5
6
  if (process.env.DEBUG_MODE === 'true') {
6
7
  const app = express();
@@ -24,6 +25,23 @@ if (process.env.DEBUG_MODE === 'true') {
24
25
  }
25
26
  }
26
27
  });
28
+ // API endpoint for pullFiles
29
+ app.post('/api/pullFiles', async (req, res) => {
30
+ try {
31
+ const validatedArgs = PullFilesToolSchema.parse(req.body);
32
+ const result = await runPullFilesTool(validatedArgs);
33
+ res.json(result);
34
+ }
35
+ catch (error) {
36
+ if (error instanceof z.ZodError) {
37
+ res.status(400).json({ error: 'Invalid request body', details: error.errors });
38
+ }
39
+ else {
40
+ console.error('Error processing request:', error);
41
+ res.status(500).json({ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error' });
42
+ }
43
+ }
44
+ });
27
45
  // Start the server
28
46
  app.listen(port, () => {
29
47
  console.log(`Debug API server running on http://localhost:${port}`);
package/build/index.js CHANGED
@@ -3,6 +3,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { pullComponentsToolName, pullComponentsToolDescription, PullComponentsToolSchema, runPullComponentsTool } from './tools/pullComponents.js';
6
+ import { pullFilesToolName, pullFilesToolDescription, PullFilesToolSchema, runPullFilesTool } from './tools/pullFiles.js';
6
7
  import './api.js';
7
8
  const server = new Server({
8
9
  name: 'locofy-mcp',
@@ -36,6 +37,26 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
36
37
  required: ['componentNames', 'workspacePath'],
37
38
  },
38
39
  },
40
+ {
41
+ name: pullFilesToolName,
42
+ description: pullFilesToolDescription,
43
+ inputSchema: {
44
+ type: 'object',
45
+ properties: {
46
+ fileNames: {
47
+ type: 'array',
48
+ items: {
49
+ type: 'string',
50
+ },
51
+ description: 'The list of specific file names to be retrieved, without resolving their component dependencies.'
52
+ },
53
+ workspacePath: {
54
+ type: 'string',
55
+ description: 'The full path to the workspace.'
56
+ },
57
+ },
58
+ }
59
+ }
39
60
  ],
40
61
  };
41
62
  });
@@ -46,6 +67,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
46
67
  const validated = PullComponentsToolSchema.parse(args);
47
68
  return await runPullComponentsTool(validated);
48
69
  }
70
+ case pullFilesToolName: {
71
+ const validated = PullFilesToolSchema.parse(args);
72
+ return await runPullFilesTool(validated);
73
+ }
49
74
  default:
50
75
  throw new Error(`Unknown tool: ${name}`);
51
76
  }
@@ -489,10 +489,10 @@ function processDirectoryStructure(directoryStructure) {
489
489
  collectChangeStatus(directoryStructure);
490
490
  // Then clean the structure
491
491
  cleanNode(directoryStructure);
492
- // Get the first child (the actual component data we want)
493
- const result = directoryStructure.children;
494
492
  // Add the component lists to the component data
495
- result.componentsToUpdate = [...new Set(componentsToUpdate)];
496
- result.componentsUnchanged = [...new Set(componentsUnchanged)];
497
- return result;
493
+ return {
494
+ directoryStructure: directoryStructure.children,
495
+ componentsToUpdate: [...new Set(componentsToUpdate)],
496
+ componentsUnchanged: [...new Set(componentsUnchanged)]
497
+ };
498
498
  }
@@ -0,0 +1,496 @@
1
+ import { z } from 'zod';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import axios from 'axios';
5
+ /**
6
+ * PullFiles tool
7
+ * - Returns a hardcoded representation of a directory structure
8
+ * - Contains nested folders, files, and file contents
9
+ */
10
+ export const pullFilesToolName = 'getLatestFileCode';
11
+ export const pullFilesToolDescription = `Cursor IDE uses this tool to retrieve individual file contents without component dependency management. Unlike the component tool that handles components and their dependencies as a unit, this tool focuses on specific files by name, retrieving only their content without dependency resolution. Use this tool when you need the raw content of specific files rather than complete components with their dependency trees. If codeChanged is true, the tool will prompt the user to accept the changes before updating the file. If codeChanged is false, the tool will not update the files.
12
+ `;
13
+ export const PullFilesToolSchema = z.object({
14
+ fileNames: z.array(z.string()).describe('The list of specific file names to be retrieved, without resolving their component dependencies.').max(10).min(1),
15
+ workspacePath: z.string().describe('The full path to the workspace.'),
16
+ });
17
+ export async function runPullFilesTool(args) {
18
+ let fileNames = args.fileNames;
19
+ // clean the file names, remove extensions
20
+ fileNames = fileNames.map(name => name.replace(/\.[^/.]+$/, ''));
21
+ const workspacePath = decodeURIComponent(args.workspacePath);
22
+ const projectID = process.env.PROJECT_ID;
23
+ const personalAccessToken = process.env.PERSONAL_ACCESS_TOKEN;
24
+ if (!projectID || !personalAccessToken) {
25
+ throw new Error('PROJECT_ID or PERSONAL_ACCESS_TOKEN is not set');
26
+ }
27
+ try {
28
+ const result = await fetchDirectoryStructure(fileNames, projectID, personalAccessToken);
29
+ if (result.success) {
30
+ // Prepend workspacePath to all filePath attributes
31
+ prepareFilePaths(result.data, workspacePath);
32
+ // Download and save any files referenced in the response
33
+ await downloadAndSaveAssets(result.data);
34
+ // Check for code changes and mark changed files
35
+ await checkForCodeChanges(result.data);
36
+ // clean the directory structure
37
+ const cleanedResult = processDirectoryStructure(result.data);
38
+ // write resultJSON to a file
39
+ if (process.env.DEBUG_MODE === 'true') {
40
+ fs.writeFileSync('result.json', JSON.stringify(cleanedResult, null, 2));
41
+ }
42
+ return {
43
+ content: [
44
+ {
45
+ type: 'text',
46
+ text: JSON.stringify(cleanedResult, null, 2)
47
+ }
48
+ ]
49
+ };
50
+ }
51
+ else {
52
+ // Return the error message from fetchDirectoryStructure
53
+ return {
54
+ content: [
55
+ {
56
+ type: 'text',
57
+ statusCode: result.statusCode || 'unknown',
58
+ text: JSON.stringify({
59
+ error: result.error,
60
+ }, null, 2)
61
+ }
62
+ ]
63
+ };
64
+ }
65
+ }
66
+ catch (error) {
67
+ console.error('Error fetching directory structure:', error);
68
+ return {
69
+ content: [
70
+ {
71
+ type: 'text',
72
+ text: JSON.stringify({ error: 'Failed to fetch directory structure ' + error }, null, 2)
73
+ }
74
+ ]
75
+ };
76
+ }
77
+ }
78
+ async function fetchDirectoryStructure(fileNames, projectID, personalAccessToken) {
79
+ const encodedNames = fileNames.map(name => encodeURIComponent(name)).join(',');
80
+ let baseURL = 'https://codegen-api.locofy.ai/mcp/generators/';
81
+ if (process.env.IS_DEV === 'true') {
82
+ baseURL = 'https://codegen-api.locofy.dev/mcp/generators/';
83
+ }
84
+ const url = baseURL + projectID + '?name=' + encodedNames;
85
+ const headers = {
86
+ 'Accept': '*/*',
87
+ 'Content-Type': 'application/json',
88
+ 'Authorization': 'Bearer ' + personalAccessToken
89
+ };
90
+ const data = {};
91
+ try {
92
+ const response = await axios.post(url, data, { headers, });
93
+ if (response.status !== 200) {
94
+ // Instead of throwing, return an error object
95
+ return {
96
+ success: false,
97
+ error: `API returned status code ${response.status}`,
98
+ statusCode: response.status
99
+ };
100
+ }
101
+ if (!response.data || !response.data.directoryTree) {
102
+ return {
103
+ success: false,
104
+ error: 'API response is missing directoryTree property',
105
+ statusCode: response.status
106
+ };
107
+ }
108
+ return {
109
+ success: true,
110
+ data: response.data.directoryTree
111
+ };
112
+ }
113
+ catch (error) {
114
+ if (axios.isAxiosError(error)) {
115
+ if (error.response) {
116
+ const statusCode = error.response.status;
117
+ let errorMessage = '';
118
+ if (statusCode === 401) {
119
+ errorMessage = 'Authentication failed: Invalid or expired token. Please check your mcp.json configuration in the .cursor folder to ensure your PERSONAL_ACCESS_TOKEN is correct. You can visit the Locofy project settings page to check or regenerate your token if needed.';
120
+ }
121
+ else if (statusCode === 500) {
122
+ errorMessage = 'Server error: The API service is experiencing issues';
123
+ }
124
+ else {
125
+ errorMessage = `API request failed with status ${statusCode}: ${error.message}`;
126
+ }
127
+ return {
128
+ success: false,
129
+ error: errorMessage,
130
+ statusCode: statusCode
131
+ };
132
+ }
133
+ else if (error.request) {
134
+ return {
135
+ success: false,
136
+ error: 'Network error: No response received from API',
137
+ statusCode: 'network_error'
138
+ };
139
+ }
140
+ }
141
+ console.error('API request failed:', error);
142
+ return {
143
+ success: false,
144
+ error: `Failed to fetch directory structure: ${error instanceof Error ? error.message : 'Unknown error'}`,
145
+ statusCode: 'unknown_error'
146
+ };
147
+ }
148
+ }
149
+ /**
150
+ * Traverses the directory structure and prepends workspacePath to all filePath attributes
151
+ * @param directoryStructure The directory structure from the API response
152
+ * @param workspacePath The workspace path to prepend to file paths
153
+ */
154
+ function prepareFilePaths(directoryStructure, workspacePath) {
155
+ const processNode = (node) => {
156
+ if (node.filePath) {
157
+ if (!path.isAbsolute(node.filePath)) {
158
+ node.filePath = path.join(workspacePath, node.filePath);
159
+ }
160
+ }
161
+ // Process all file paths in exportData
162
+ if (node.exportData) {
163
+ // Handle exportData.filePath
164
+ if (node.exportData.filePath) {
165
+ if (!path.isAbsolute(node.exportData.filePath)) {
166
+ node.exportData.filePath = path.join(workspacePath, node.exportData.filePath);
167
+ }
168
+ }
169
+ // Handle exportData.files array
170
+ if (node.exportData.files && Array.isArray(node.exportData.files)) {
171
+ for (const file of node.exportData.files) {
172
+ if (file.filePath && !path.isAbsolute(file.filePath)) {
173
+ file.filePath = path.join(workspacePath, file.filePath);
174
+ }
175
+ }
176
+ }
177
+ // Handle dependencies array
178
+ if (node.exportData.dependencies && Array.isArray(node.exportData.dependencies)) {
179
+ for (const dependency of node.exportData.dependencies) {
180
+ // Process dependency filePath
181
+ if (dependency.filePath && !path.isAbsolute(dependency.filePath)) {
182
+ dependency.filePath = path.join(workspacePath, dependency.filePath);
183
+ }
184
+ // Process dependency files array
185
+ if (dependency.files && Array.isArray(dependency.files)) {
186
+ for (const file of dependency.files) {
187
+ if (file.filePath && !path.isAbsolute(file.filePath)) {
188
+ file.filePath = path.join(workspacePath, file.filePath);
189
+ }
190
+ }
191
+ }
192
+ // Recursively process any nested dependencies if they exist
193
+ if (dependency.dependencies && Array.isArray(dependency.dependencies)) {
194
+ for (const nestedDep of dependency.dependencies) {
195
+ processNode(nestedDep); // Recursively process nested dependencies
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ // Process content.filePath if it exists
202
+ if (node.content && node.content.filePath) {
203
+ if (!path.isAbsolute(node.content.filePath)) {
204
+ node.content.filePath = path.join(workspacePath, node.content.filePath);
205
+ }
206
+ }
207
+ // Process children recursively
208
+ if (node.children && Array.isArray(node.children)) {
209
+ for (const child of node.children) {
210
+ processNode(child);
211
+ }
212
+ }
213
+ };
214
+ // Start processing from the root
215
+ processNode(directoryStructure);
216
+ }
217
+ /**
218
+ * Downloads and saves asset files from the directory structure
219
+ * @param directoryStructure The directory structure from the API response
220
+ */
221
+ async function downloadAndSaveAssets(directoryStructure) {
222
+ // Process each file in the directory structure
223
+ const processNode = async (node) => {
224
+ /* eslint-enable */
225
+ if (node.type === 'file' && node.exportData && node.exportData.files) {
226
+ // For each file in the files array
227
+ for (const file of node.exportData.files) {
228
+ if (file.url && file.fileName) {
229
+ const filePath = file.filePath;
230
+ // Create the directory structure if it doesn't exist
231
+ const dirPath = path.dirname(filePath);
232
+ if (!fs.existsSync(dirPath)) {
233
+ fs.mkdirSync(dirPath, { recursive: true });
234
+ console.log(`Created directory: ${dirPath}`);
235
+ }
236
+ // Only download if file doesn't exist
237
+ if (!fs.existsSync(filePath)) {
238
+ try {
239
+ console.log(`Downloading ${file.fileName} from ${file.url}`);
240
+ const response = await axios.get(file.url, { responseType: 'arraybuffer' });
241
+ fs.writeFileSync(filePath, Buffer.from(response.data));
242
+ console.log(`Saved ${file.fileName} to ${filePath}`);
243
+ }
244
+ catch (error) {
245
+ console.error(`Error downloading ${file.fileName}:`, error);
246
+ }
247
+ }
248
+ else {
249
+ console.log(`File ${filePath} already exists, skipping download`);
250
+ }
251
+ }
252
+ }
253
+ }
254
+ // Process children recursively
255
+ if (node.children && Array.isArray(node.children)) {
256
+ for (const child of node.children) {
257
+ await processNode(child);
258
+ }
259
+ }
260
+ };
261
+ // Start processing from the root
262
+ await processNode(directoryStructure);
263
+ }
264
+ /**
265
+ * Checks if the current code on disk differs from the code received from the API
266
+ * @param directoryStructure The directory structure from the API response
267
+ */
268
+ async function checkForCodeChanges(directoryStructure) {
269
+ // Process a single file and check if its content has changed
270
+ function checkFile(filePath, content) {
271
+ if (!fs.existsSync(filePath)) {
272
+ console.log(`New file: ${filePath}`);
273
+ return true;
274
+ }
275
+ try {
276
+ const fileContent = fs.readFileSync(filePath, 'utf8');
277
+ const codeChanged = hasCodeChanged(fileContent, content);
278
+ if (codeChanged) {
279
+ console.log(`File changed: ${filePath}`);
280
+ }
281
+ return codeChanged;
282
+ }
283
+ catch (error) {
284
+ console.error(`Error reading file ${filePath}:`, error);
285
+ return true; // Consider file as changed if we can't read it
286
+ }
287
+ }
288
+ // Check CSS module file if it exists
289
+ function checkCssFile(basePath, cssFileName, cssContent) {
290
+ if (!cssFileName || !cssContent)
291
+ return false;
292
+ const cssFilePath = path.join(path.dirname(basePath), cssFileName);
293
+ if (fs.existsSync(cssFilePath)) {
294
+ const cssFileContent = fs.readFileSync(cssFilePath, 'utf8');
295
+ const cssChanged = hasCodeChanged(cssFileContent, cssContent);
296
+ if (cssChanged) {
297
+ console.log(`CSS module changed: ${cssFilePath}`);
298
+ return true;
299
+ }
300
+ else {
301
+ return false;
302
+ }
303
+ }
304
+ return true; // CSS file doesn't exist, consider as new/changed
305
+ }
306
+ // Update node with change status and instructions
307
+ function updateNodeStatus(node, fileName, hasChanged, isCss = false) {
308
+ if (!hasChanged) {
309
+ const message = `This file ${fileName} has no changes, no need to update`;
310
+ if (isCss) {
311
+ node.cssContent = message;
312
+ node.instructionCSSForIDE = message;
313
+ }
314
+ else {
315
+ node.instructionForIDE = message;
316
+ node.content = message;
317
+ }
318
+ }
319
+ else if (!fs.existsSync(node.filePath)) {
320
+ node.instructionForIDE = 'This file does not exist, please create it';
321
+ }
322
+ return hasChanged;
323
+ }
324
+ // The main recursive function to process nodes
325
+ function processNode(node) {
326
+ let hasChanges = false;
327
+ // Remove experimental fields first
328
+ if (node.exportData) {
329
+ if (node.filePath) {
330
+ const nodeContent = node.name.includes('.css') ?
331
+ node.exportData.cssContent :
332
+ node.exportData.content;
333
+ const fileChanged = checkFile(node.filePath, nodeContent);
334
+ node.codeChanged = fileChanged;
335
+ hasChanges = hasChanges || fileChanged;
336
+ updateNodeStatus(node, node.name, fileChanged);
337
+ // Check associated CSS module
338
+ if (node.exportData.cssFileName && node.exportData.cssContent) {
339
+ const cssChanged = checkCssFile(node.filePath, node.exportData.cssFileName, node.exportData.cssContent);
340
+ hasChanges = hasChanges || cssChanged;
341
+ updateNodeStatus(node.exportData, node.exportData.cssFileName, cssChanged, true);
342
+ }
343
+ }
344
+ // Process dependencies
345
+ if (node.exportData.dependencies && Array.isArray(node.exportData.dependencies)) {
346
+ for (const dependency of node.exportData.dependencies) {
347
+ // Process dependency file
348
+ if (dependency.filePath) {
349
+ const isCSS = dependency.name && dependency.name.includes('.css');
350
+ const depContent = isCSS ? dependency.cssContent : dependency.content;
351
+ const fileChanged = checkFile(dependency.filePath, depContent);
352
+ dependency.codeChanged = fileChanged;
353
+ hasChanges = hasChanges || fileChanged;
354
+ updateNodeStatus(dependency, dependency.compName, fileChanged);
355
+ // Check associated CSS module
356
+ if (dependency.cssFileName && dependency.cssContent) {
357
+ const cssChanged = checkCssFile(dependency.filePath, dependency.cssFileName, dependency.cssContent);
358
+ hasChanges = hasChanges || cssChanged;
359
+ updateNodeStatus(dependency, dependency.cssFileName, cssChanged, true);
360
+ }
361
+ }
362
+ // Process nested dependencies recursively
363
+ if (dependency.dependencies && Array.isArray(dependency.dependencies)) {
364
+ for (const nestedDep of dependency.dependencies) {
365
+ const nestedChanged = processNode(nestedDep);
366
+ hasChanges = hasChanges || nestedChanged;
367
+ }
368
+ }
369
+ }
370
+ }
371
+ }
372
+ // Process children recursively
373
+ if (node.children && Array.isArray(node.children)) {
374
+ for (const child of node.children) {
375
+ const childChanged = processNode(child);
376
+ hasChanges = hasChanges || childChanged;
377
+ }
378
+ }
379
+ return hasChanges;
380
+ }
381
+ // Start processing from the root
382
+ return processNode(directoryStructure);
383
+ }
384
+ /**
385
+ * Compares two code snippets to determine if they are functionally different
386
+ * @param fileContent The code currently in the file
387
+ * @param apiCode The code provided by the API
388
+ * @returns True if the code has changed, false otherwise
389
+ */
390
+ function hasCodeChanged(fileContent, apiCode) {
391
+ if (!fileContent || !apiCode) {
392
+ return true;
393
+ }
394
+ // Normalize both code snippets
395
+ const normalizedFile = normalizeForComparison(fileContent);
396
+ const normalizedApi = normalizeForComparison(apiCode);
397
+ // Simple equality check on normalized versions
398
+ return normalizedFile !== normalizedApi;
399
+ }
400
+ /**
401
+ * Normalizes code for comparison by removing irrelevant differences
402
+ * @param code The code to normalize
403
+ * @returns Normalized code string
404
+ */
405
+ function normalizeForComparison(code) {
406
+ return code
407
+ .replace(/\s+/g, '') // Remove all whitespace (spaces, tabs, newlines)
408
+ .replace(/[;,'"]/g, '') // Remove semicolons, commas, and quotes
409
+ .replace(/\/\/.*$/gm, '') // Remove single-line comments
410
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
411
+ .toLowerCase(); // Convert to lowercase for case-insensitive comparison
412
+ }
413
+ /**
414
+ * Cleans the directory structure by removing unwanted properties from all nodes
415
+ * @param directoryStructure The directory structure to clean
416
+ * @returns The cleaned directory structure
417
+ */
418
+ function processDirectoryStructure(directoryStructure) {
419
+ const propertiesToRemove = [
420
+ 'files',
421
+ 'component',
422
+ 'jsLC',
423
+ 'assetCount',
424
+ 'cssLC',
425
+ 'componentCount',
426
+ 'pageMetaData',
427
+ 'isHomePage',
428
+ 'storybookFileName',
429
+ 'isAutoSyncNode'
430
+ ];
431
+ const filesToUpdate = [];
432
+ const filesUnchanged = [];
433
+ function collectChangeStatus(node) {
434
+ // Check if this is a component node with a name and change status
435
+ if (node.name && node.codeChanged !== undefined) {
436
+ if (node.codeChanged) {
437
+ filesToUpdate.push(node.name);
438
+ }
439
+ else {
440
+ filesUnchanged.push(node.name);
441
+ }
442
+ }
443
+ // Check dependencies
444
+ if (node.exportData && node.exportData.dependencies) {
445
+ node.exportData.dependencies.forEach((dep) => {
446
+ if (dep.compName && dep.codeChanged !== undefined) {
447
+ if (dep.codeChanged) {
448
+ filesToUpdate.push(dep.compName);
449
+ }
450
+ else {
451
+ filesUnchanged.push(dep.compName);
452
+ }
453
+ }
454
+ // Check nested dependencies
455
+ if (dep.dependencies && Array.isArray(dep.dependencies)) {
456
+ dep.dependencies.forEach((nestedDep) => collectChangeStatus(nestedDep));
457
+ }
458
+ });
459
+ }
460
+ // Process children recursively
461
+ if (node.children && Array.isArray(node.children)) {
462
+ node.children.forEach((child) => collectChangeStatus(child));
463
+ }
464
+ }
465
+ function cleanNode(node) {
466
+ // Remove unwanted properties from the current node
467
+ propertiesToRemove.forEach(prop => {
468
+ if (node[prop] !== undefined)
469
+ delete node[prop];
470
+ });
471
+ // Clean exportData if it exists
472
+ if (node.exportData) {
473
+ propertiesToRemove.forEach(prop => {
474
+ if (node.exportData[prop] !== undefined)
475
+ delete node.exportData[prop];
476
+ });
477
+ // Process dependencies in exportData
478
+ if (node.exportData.dependencies && Array.isArray(node.exportData.dependencies)) {
479
+ node.exportData.dependencies.forEach((dep) => cleanNode(dep));
480
+ }
481
+ }
482
+ // Process children recursively
483
+ if (node.children && Array.isArray(node.children)) {
484
+ node.children.forEach((child) => cleanNode(child));
485
+ }
486
+ }
487
+ // First collect all change statuses
488
+ collectChangeStatus(directoryStructure);
489
+ // Then clean the structure
490
+ cleanNode(directoryStructure);
491
+ return {
492
+ directoryStructure: directoryStructure.children,
493
+ filesToUpdate: [...new Set(filesToUpdate)],
494
+ filesUnchanged: [...new Set(filesUnchanged)]
495
+ };
496
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locofy/mcp",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Locofy MCP Server with Cursor",
5
5
  "keywords": [
6
6
  "figma",