@locofy/mcp 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.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # 🤖 AI Development Assistant MCP Server
2
+
3
+ Welcome to your AI-powered development toolkit, designed as a Model Context Protocol (MCP) server for Cursor! This project provides intelligent coding assistance through custom AI tools.
package/build/api.js ADDED
@@ -0,0 +1,32 @@
1
+ import express from 'express';
2
+ import { z } from 'zod';
3
+ import { PullComponentsToolSchema, runPullComponentsTool } from './tools/pullComponents.js';
4
+ // Only start the API server if DEBUG_MODE is set
5
+ if (process.env.DEBUG_MODE === 'true') {
6
+ const app = express();
7
+ const port = process.env.PORT || 3000;
8
+ // Middleware to parse JSON bodies
9
+ app.use(express.json());
10
+ // API endpoint for pullComponents
11
+ app.post('/api/pullComponents', async (req, res) => {
12
+ try {
13
+ const validatedArgs = PullComponentsToolSchema.parse(req.body);
14
+ const result = await runPullComponentsTool(validatedArgs);
15
+ res.json(result);
16
+ }
17
+ catch (error) {
18
+ if (error instanceof z.ZodError) {
19
+ res.status(400).json({ error: 'Invalid request body', details: error.errors });
20
+ }
21
+ else {
22
+ console.error('Error processing request:', error);
23
+ res.status(500).json({ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error' });
24
+ }
25
+ }
26
+ });
27
+ // Start the server
28
+ app.listen(port, () => {
29
+ console.log(`Debug API server running on http://localhost:${port}`);
30
+ });
31
+ }
32
+ export default {};
package/build/index.js ADDED
@@ -0,0 +1,64 @@
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 { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
+ import { pullComponentsToolName, pullComponentsToolDescription, PullComponentsToolSchema, runPullComponentsTool } from './tools/pullComponents.js';
6
+ import './api.js';
7
+ const server = new Server({
8
+ name: 'locofy-mcp',
9
+ version: '2.0.1',
10
+ }, {
11
+ capabilities: {
12
+ tools: {},
13
+ },
14
+ });
15
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
16
+ return {
17
+ tools: [
18
+ {
19
+ name: pullComponentsToolName,
20
+ description: pullComponentsToolDescription,
21
+ inputSchema: {
22
+ type: 'object',
23
+ properties: {
24
+ componentNames: {
25
+ type: 'array',
26
+ items: {
27
+ type: 'string',
28
+ },
29
+ description: 'The list of component or screen names to be retrieved.'
30
+ },
31
+ workspacePath: {
32
+ type: 'string',
33
+ description: 'The full path to the workspace.'
34
+ },
35
+ },
36
+ required: ['componentNames', 'workspacePath'],
37
+ },
38
+ },
39
+ ],
40
+ };
41
+ });
42
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
43
+ const { name, arguments: args } = request.params;
44
+ switch (name) {
45
+ case pullComponentsToolName: {
46
+ const validated = PullComponentsToolSchema.parse(args);
47
+ return await runPullComponentsTool(validated);
48
+ }
49
+ default:
50
+ throw new Error(`Unknown tool: ${name}`);
51
+ }
52
+ });
53
+ async function main() {
54
+ const transport = new StdioServerTransport();
55
+ await server.connect(transport);
56
+ console.log('Cursor Tools MCP Server running on stdio');
57
+ if (process.env.DEBUG_MODE === 'true') {
58
+ console.log('Debug mode active - REST API available for Postman testing');
59
+ }
60
+ }
61
+ main().catch((error) => {
62
+ console.error('Fatal error:', error);
63
+ process.exit(1);
64
+ });
@@ -0,0 +1,180 @@
1
+ import { z } from 'zod';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import axios from 'axios';
5
+ /**
6
+ * PullComponents tool
7
+ * - Returns a hardcoded representation of a directory structure
8
+ * - Contains nested folders, files, and file contents
9
+ */
10
+ export const pullComponentsToolName = 'locofy';
11
+ export const pullComponentsToolDescription = 'Provides a directory structure with nested folders, files, and their contents, allowing the MCP client to retrieve the code.';
12
+ export const PullComponentsToolSchema = z.object({
13
+ componentNames: z.array(z.string()).describe('The list of component or screen names to be retrieved.').max(10).min(1),
14
+ workspacePath: z.string().describe('The full path to the workspace.'),
15
+ });
16
+ export async function runPullComponentsTool(args) {
17
+ const componentNames = args.componentNames;
18
+ const workspacePath = decodeURIComponent(args.workspacePath);
19
+ const projectID = process.env.PROJECT_ID;
20
+ const personalAccessToken = process.env.PERSONAL_ACCESS_TOKEN;
21
+ if (!projectID || !personalAccessToken) {
22
+ throw new Error('PROJECT_ID or PERSONAL_ACCESS_TOKEN is not set');
23
+ }
24
+ try {
25
+ const result = await fetchDirectoryStructure(componentNames, projectID, personalAccessToken);
26
+ if (result.success) {
27
+ // Download and save any files referenced in the response
28
+ await downloadAndSaveAssets(result.data, workspacePath);
29
+ return {
30
+ content: [
31
+ {
32
+ type: 'text',
33
+ text: JSON.stringify(result.data, null, 2)
34
+ }
35
+ ]
36
+ };
37
+ }
38
+ else {
39
+ // Return the error message from fetchDirectoryStructure
40
+ return {
41
+ content: [
42
+ {
43
+ type: 'text',
44
+ statusCode: result.statusCode || 'unknown',
45
+ text: JSON.stringify({
46
+ error: result.error,
47
+ }, null, 2)
48
+ }
49
+ ]
50
+ };
51
+ }
52
+ }
53
+ catch (error) {
54
+ console.error('Error fetching directory structure:', error);
55
+ return {
56
+ content: [
57
+ {
58
+ type: 'text',
59
+ text: JSON.stringify({ error: 'Failed to fetch directory structure' }, null, 2)
60
+ }
61
+ ]
62
+ };
63
+ }
64
+ }
65
+ async function fetchDirectoryStructure(componentNames, projectID, personalAccessToken) {
66
+ // TODO: change to PROD url
67
+ const url = 'https://codegen-api.locofy.dev/mcp/generators/' + projectID + '?name=' + componentNames.join(',');
68
+ const headers = {
69
+ 'Accept': '*/*',
70
+ 'Content-Type': 'application/json',
71
+ 'Authorization': 'Bearer ' + personalAccessToken
72
+ };
73
+ const data = {};
74
+ try {
75
+ const response = await axios.post(url, data, { headers, });
76
+ if (response.status !== 200) {
77
+ // Instead of throwing, return an error object
78
+ return {
79
+ success: false,
80
+ error: `API returned status code ${response.status}`,
81
+ statusCode: response.status
82
+ };
83
+ }
84
+ if (!response.data || !response.data.directoryTree) {
85
+ return {
86
+ success: false,
87
+ error: 'API response is missing directoryTree property',
88
+ statusCode: response.status
89
+ };
90
+ }
91
+ return {
92
+ success: true,
93
+ data: response.data.directoryTree
94
+ };
95
+ }
96
+ catch (error) {
97
+ if (axios.isAxiosError(error)) {
98
+ if (error.response) {
99
+ const statusCode = error.response.status;
100
+ let errorMessage = '';
101
+ if (statusCode === 401) {
102
+ 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.';
103
+ }
104
+ else if (statusCode === 500) {
105
+ errorMessage = 'Server error: The API service is experiencing issues';
106
+ }
107
+ else {
108
+ errorMessage = `API request failed with status ${statusCode}: ${error.message}`;
109
+ }
110
+ return {
111
+ success: false,
112
+ error: errorMessage,
113
+ statusCode: statusCode
114
+ };
115
+ }
116
+ else if (error.request) {
117
+ return {
118
+ success: false,
119
+ error: 'Network error: No response received from API',
120
+ statusCode: 'network_error'
121
+ };
122
+ }
123
+ }
124
+ console.error('API request failed:', error);
125
+ return {
126
+ success: false,
127
+ error: `Failed to fetch directory structure: ${error instanceof Error ? error.message : 'Unknown error'}`,
128
+ statusCode: 'unknown_error'
129
+ };
130
+ }
131
+ }
132
+ /**
133
+ * Downloads and saves asset files from the directory structure
134
+ * @param directoryStructure The directory structure from the API response
135
+ * @param workspacePath The workspace path to save files to
136
+ */
137
+ /* eslint-disable */
138
+ async function downloadAndSaveAssets(directoryStructure, workspacePath) {
139
+ // Process each file in the directory structure
140
+ const processNode = async (node) => {
141
+ /* eslint-enable */
142
+ if (node.type === 'file' && node.exportData && node.exportData.files) {
143
+ // For each file in the files array
144
+ for (const file of node.exportData.files) {
145
+ if (file.url && file.fileName) {
146
+ const filePath = path.join(workspacePath, file.filePath);
147
+ // Create the directory structure if it doesn't exist
148
+ const dirPath = path.dirname(filePath);
149
+ if (!fs.existsSync(dirPath)) {
150
+ fs.mkdirSync(dirPath, { recursive: true });
151
+ console.log(`Created directory: ${dirPath}`);
152
+ }
153
+ // Only download if file doesn't exist
154
+ if (!fs.existsSync(filePath)) {
155
+ try {
156
+ console.log(`Downloading ${file.fileName} from ${file.url}`);
157
+ const response = await axios.get(file.url, { responseType: 'arraybuffer' });
158
+ fs.writeFileSync(filePath, Buffer.from(response.data));
159
+ console.log(`Saved ${file.fileName} to ${filePath}`);
160
+ }
161
+ catch (error) {
162
+ console.error(`Error downloading ${file.fileName}:`, error);
163
+ }
164
+ }
165
+ else {
166
+ console.log(`File ${filePath} already exists, skipping download`);
167
+ }
168
+ }
169
+ }
170
+ }
171
+ // Process children recursively
172
+ if (node.children && Array.isArray(node.children)) {
173
+ for (const child of node.children) {
174
+ await processNode(child);
175
+ }
176
+ }
177
+ };
178
+ // Start processing from the root
179
+ await processNode(directoryStructure);
180
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@locofy/mcp",
3
+ "version": "0.1.0",
4
+ "description": "Locofy MCP Server with Cursor",
5
+ "keywords": [
6
+ "figma",
7
+ "locofy",
8
+ "mcp",
9
+ "design-system"
10
+ ],
11
+ "homepage": "https://github.com/Locofy/mcp",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/Locofy/mcp.git"
15
+ },
16
+ "license": "MIT",
17
+ "author": "Locofy",
18
+ "main": "./src/index.ts",
19
+ "type": "module",
20
+ "scripts": {
21
+ "build": "rm -rf build && tsc && node -e \"const fs=require('fs');const f='build/index.js';fs.writeFileSync(f,'#!/usr/bin/env node\\n'+fs.readFileSync(f));fs.chmodSync(f,'755')\"",
22
+ "link": "npm run build && npm link",
23
+ "start": "node build/index.js",
24
+ "lint": "eslint . --ext .ts --fix"
25
+ },
26
+ "bin": {
27
+ "locofy-mcp": "./build/index.js"
28
+ },
29
+ "files": [
30
+ "build"
31
+ ],
32
+ "dependencies": {
33
+ "@modelcontextprotocol/sdk": "^1.4.1",
34
+ "axios": "^1.8.3",
35
+ "express": "^4.21.2",
36
+ "zod": "^3.24.1"
37
+ },
38
+ "devDependencies": {
39
+ "@types/express": "^5.0.1",
40
+ "@types/node": "^22.13.0",
41
+ "@typescript-eslint/eslint-plugin": "^6.7.0",
42
+ "@typescript-eslint/parser": "^6.7.0",
43
+ "eslint": "^8.49.0",
44
+ "eslint-config-prettier": "^9.0.0",
45
+ "typescript": "^5.7.3"
46
+ }
47
+ }