palette-mcp 1.0.0 → 1.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/dist/components/OrderManagementGuide.d.ts +6 -0
- package/dist/components/OrderManagementGuide.js +266 -0
- package/dist/index-simple.d.ts +2 -0
- package/dist/index-simple.js +139 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +16 -226
- package/dist/requests/1762927928451-ajgna9b/SomaComponent.html +63 -0
- package/dist/requests/1762927928451-ajgna9b/SomaComponent.tsx +913 -0
- package/dist/requests/1762927928451-ajgna9b/metadata.json +9 -0
- package/dist/requests/1762931214963-cqlwvxn/SomaComponent.html +63 -0
- package/dist/requests/1762931214963-cqlwvxn/SomaComponent.tsx +525 -0
- package/dist/requests/1762931214963-cqlwvxn/metadata.json +9 -0
- package/dist/requests/1762932805663-m5wkk3a/SomaComponent.html +248 -0
- package/dist/requests/1762932805663-m5wkk3a/SomaComponent.tsx +1050 -0
- package/dist/requests/1762932805663-m5wkk3a/metadata.json +9 -0
- package/dist/requests/1762934645710-b67ldow/SomaComponent.html +193 -0
- package/dist/requests/1762934645710-b67ldow/SomaComponent.tsx +307 -0
- package/dist/requests/1762934645710-b67ldow/metadata.json +9 -0
- package/dist/requests/1762934961257-wwnmpvx/SomaComponent.html +193 -0
- package/dist/requests/1762934961257-wwnmpvx/SomaComponent.tsx +932 -0
- package/dist/requests/1762934961257-wwnmpvx/metadata.json +9 -0
- package/dist/requests/1762935126549-yjdcezr/SomaComponent.html +193 -0
- package/dist/requests/1762935126549-yjdcezr/SomaComponent.tsx +847 -0
- package/dist/requests/1762935126549-yjdcezr/metadata.json +9 -0
- package/dist/requests/1762935353759-fuokdeu/SomaComponent.html +193 -0
- package/dist/requests/1762935353759-fuokdeu/SomaComponent.tsx +334 -0
- package/dist/requests/1762935353759-fuokdeu/metadata.json +9 -0
- package/dist/requests/1762935378891-ckwbabn/SomaComponent.html +193 -0
- package/dist/requests/1762935378891-ckwbabn/SomaComponent.tsx +256 -0
- package/dist/requests/1762935378891-ckwbabn/metadata.json +9 -0
- package/dist/requests/1762935418352-181zqu4/SomaComponent.html +193 -0
- package/dist/requests/1762935418352-181zqu4/SomaComponent.tsx +45 -0
- package/dist/requests/1762935418352-181zqu4/metadata.json +9 -0
- package/dist/requests/1762935438157-vzkcbwy/SomaComponent.html +193 -0
- package/dist/requests/1762935438157-vzkcbwy/SomaComponent.tsx +238 -0
- package/dist/requests/1762935438157-vzkcbwy/metadata.json +9 -0
- package/dist/requests/1762935529749-ukzmiu3/SomaComponent.html +193 -0
- package/dist/requests/1762935529749-ukzmiu3/SomaComponent.tsx +138 -0
- package/dist/requests/1762935529749-ukzmiu3/metadata.json +9 -0
- package/dist/requests/1762935556527-jxelwj4/SomaComponent.html +193 -0
- package/dist/requests/1762935556527-jxelwj4/SomaComponent.tsx +138 -0
- package/dist/requests/1762935556527-jxelwj4/metadata.json +9 -0
- package/dist/requests/1762935579673-g39fqly/SomaComponent.html +193 -0
- package/dist/requests/1762935579673-g39fqly/SomaComponent.tsx +138 -0
- package/dist/requests/1762935579673-g39fqly/metadata.json +9 -0
- package/dist/requests/1762935613556-ogvsekd/SomaComponent.html +193 -0
- package/dist/requests/1762935613556-ogvsekd/SomaComponent.tsx +150 -0
- package/dist/requests/1762935613556-ogvsekd/metadata.json +9 -0
- package/dist/requests/1762935943631-hb2drgf/SomaComponent.html +193 -0
- package/dist/requests/1762935943631-hb2drgf/SomaComponent.tsx +150 -0
- package/dist/requests/1762935943631-hb2drgf/metadata.json +9 -0
- package/dist/requests/1762935954110-m7jb9m7/SomaComponent.html +193 -0
- package/dist/requests/1762935954110-m7jb9m7/SomaComponent.tsx +150 -0
- package/dist/requests/1762935954110-m7jb9m7/metadata.json +9 -0
- package/dist/requests/1762936003095-0jnmlga/SomaComponent.html +193 -0
- package/dist/requests/1762936003095-0jnmlga/SomaComponent.tsx +150 -0
- package/dist/requests/1762936003095-0jnmlga/metadata.json +9 -0
- package/dist/requests/1762937044748-7ubrrua/SomaComponent.html +193 -0
- package/dist/requests/1762937044748-7ubrrua/SomaComponent.tsx +138 -0
- package/dist/requests/1762937044748-7ubrrua/metadata.json +9 -0
- package/dist/server.d.ts +18 -0
- package/dist/server.js +246 -0
- package/dist/smithery.d.ts +50 -0
- package/dist/smithery.js +44 -0
- package/dist/test-figma-tools.d.ts +2 -0
- package/dist/test-figma-tools.js +55 -0
- package/dist/test-mcp-servers.d.ts +2 -0
- package/dist/test-mcp-servers.js +166 -0
- package/dist/test.d.ts +2 -0
- package/dist/test.js +76 -0
- package/package.json +4 -5
- package/smithery.yaml +8 -57
- package/src/components/OrderManagementGuide.tsx +459 -0
- package/src/index.ts +43 -0
- package/src/server.ts +301 -0
- package/src/services/code-generator.ts +1330 -0
- package/src/services/design-system.ts +2133 -0
- package/src/services/figma.ts +422 -0
- package/src/smithery.ts +54 -0
- package/src/test-figma-tools.ts +72 -0
- package/src/test-mcp-servers.ts +180 -0
- package/src/test.ts +89 -0
- package/src/utils/figma-mcp-client.ts +193 -0
- package/src/utils/request-manager.ts +101 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import dotenv from 'dotenv';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
|
|
11
|
+
dotenv.config({ path: join(__dirname, '..', '.env') });
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* HTTP 타입 MCP 서버 테스트
|
|
15
|
+
*/
|
|
16
|
+
async function testHttpMCPServer(name: string, url: string) {
|
|
17
|
+
console.log(`\n${'='.repeat(60)}`);
|
|
18
|
+
console.log(`🔍 ${name} MCP 서버 테스트`);
|
|
19
|
+
console.log(`URL: ${url}`);
|
|
20
|
+
console.log('='.repeat(60));
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const client = axios.create({
|
|
24
|
+
baseURL: url,
|
|
25
|
+
timeout: 10000,
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// JSON-RPC 2.0 요청 생성
|
|
32
|
+
const request = {
|
|
33
|
+
jsonrpc: '2.0',
|
|
34
|
+
id: 1,
|
|
35
|
+
method: 'tools/list',
|
|
36
|
+
params: {},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
console.log('\n📤 요청 전송 중...');
|
|
40
|
+
const response = await client.post('', request);
|
|
41
|
+
|
|
42
|
+
console.log('✅ 연결 성공!');
|
|
43
|
+
console.log('\n📥 응답 상태:', response.status);
|
|
44
|
+
console.log('📥 응답 데이터:', JSON.stringify(response.data, null, 2));
|
|
45
|
+
|
|
46
|
+
if (response.data.error) {
|
|
47
|
+
console.error('❌ MCP 에러:', response.data.error);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const tools = response.data.result?.tools || [];
|
|
52
|
+
console.log(`\n📋 도구 개수: ${tools.length}`);
|
|
53
|
+
|
|
54
|
+
if (tools.length > 0) {
|
|
55
|
+
console.log('\n📝 도구 목록:');
|
|
56
|
+
tools.forEach((tool: any, index: number) => {
|
|
57
|
+
console.log(`\n${index + 1}. ${tool.name}`);
|
|
58
|
+
if (tool.description) {
|
|
59
|
+
console.log(` 설명: ${tool.description}`);
|
|
60
|
+
}
|
|
61
|
+
if (tool.inputSchema?.properties) {
|
|
62
|
+
console.log(` 파라미터:`);
|
|
63
|
+
Object.entries(tool.inputSchema.properties).forEach(([key, value]: [string, any]) => {
|
|
64
|
+
console.log(` - ${key}: ${value.type || 'any'}`);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return true;
|
|
69
|
+
} else {
|
|
70
|
+
console.log('⚠️ 도구가 없습니다.');
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (axios.isAxiosError(error)) {
|
|
75
|
+
if (error.code === 'ECONNREFUSED') {
|
|
76
|
+
console.error('❌ 연결 거부됨 - 서버가 실행 중이지 않습니다.');
|
|
77
|
+
} else if (error.code === 'ETIMEDOUT') {
|
|
78
|
+
console.error('❌ 타임아웃 - 서버가 응답하지 않습니다.');
|
|
79
|
+
} else if (error.response) {
|
|
80
|
+
console.error(`❌ HTTP 에러: ${error.response.status} ${error.response.statusText}`);
|
|
81
|
+
console.error('응답 데이터:', error.response.data);
|
|
82
|
+
} else {
|
|
83
|
+
console.error('❌ 연결 에러:', error.message);
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
console.error('❌ 예상치 못한 에러:', error);
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* stdio 타입 MCP 서버는 직접 테스트하기 어려우므로 정보만 출력
|
|
94
|
+
*/
|
|
95
|
+
function testStdioMCPServer(name: string, config: any) {
|
|
96
|
+
console.log(`\n${'='.repeat(60)}`);
|
|
97
|
+
console.log(`ℹ️ ${name} MCP 서버 정보`);
|
|
98
|
+
console.log('='.repeat(60));
|
|
99
|
+
console.log('타입: stdio');
|
|
100
|
+
console.log('명령:', config.command);
|
|
101
|
+
console.log('인자:', config.args?.join(' ') || '없음');
|
|
102
|
+
console.log('\n💡 stdio 타입 MCP 서버는 Cursor가 직접 관리합니다.');
|
|
103
|
+
console.log(' "Loading tools" 상태가 지속되면:');
|
|
104
|
+
console.log(' 1. Cursor를 재시작해보세요');
|
|
105
|
+
console.log(' 2. MCP 서버 설정을 확인하세요');
|
|
106
|
+
console.log(' 3. 환경 변수가 올바르게 설정되었는지 확인하세요');
|
|
107
|
+
if (config.env) {
|
|
108
|
+
console.log('\n환경 변수:');
|
|
109
|
+
Object.keys(config.env).forEach(key => {
|
|
110
|
+
if (key.includes('TOKEN') || key.includes('KEY') || key.includes('SECRET')) {
|
|
111
|
+
console.log(` ${key}: *** (보안상 숨김)`);
|
|
112
|
+
} else {
|
|
113
|
+
console.log(` ${key}: ${config.env[key]}`);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function main() {
|
|
120
|
+
console.log('🚀 MCP 서버 연결 테스트 시작\n');
|
|
121
|
+
|
|
122
|
+
// mcp.json 파일 읽기
|
|
123
|
+
const mcpConfigPath = join(process.env.HOME || '', '.cursor', 'mcp.json');
|
|
124
|
+
let mcpConfig: any = {};
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const fs = await import('fs/promises');
|
|
128
|
+
const configContent = await fs.readFile(mcpConfigPath, 'utf-8');
|
|
129
|
+
mcpConfig = JSON.parse(configContent);
|
|
130
|
+
console.log(`✅ mcp.json 파일 로드 성공: ${mcpConfigPath}\n`);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(`❌ mcp.json 파일을 읽을 수 없습니다: ${mcpConfigPath}`);
|
|
133
|
+
console.error('에러:', error);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!mcpConfig.mcpServers) {
|
|
138
|
+
console.error('❌ mcpServers 설정을 찾을 수 없습니다.');
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const servers = mcpConfig.mcpServers;
|
|
143
|
+
const httpServers: Array<{ name: string; url: string }> = [];
|
|
144
|
+
const stdioServers: Array<{ name: string; config: any }> = [];
|
|
145
|
+
|
|
146
|
+
// 서버 분류
|
|
147
|
+
Object.entries(servers).forEach(([name, config]: [string, any]) => {
|
|
148
|
+
if (config.type === 'http' || config.url) {
|
|
149
|
+
httpServers.push({ name, url: config.url });
|
|
150
|
+
} else if (config.type === 'stdio' || config.command) {
|
|
151
|
+
stdioServers.push({ name, config });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// HTTP 타입 서버 테스트
|
|
156
|
+
if (httpServers.length > 0) {
|
|
157
|
+
console.log(`\n📡 HTTP 타입 MCP 서버 (${httpServers.length}개):`);
|
|
158
|
+
for (const server of httpServers) {
|
|
159
|
+
await testHttpMCPServer(server.name, server.url);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// stdio 타입 서버 정보 출력
|
|
164
|
+
if (stdioServers.length > 0) {
|
|
165
|
+
console.log(`\n\n📡 stdio 타입 MCP 서버 (${stdioServers.length}개):`);
|
|
166
|
+
for (const server of stdioServers) {
|
|
167
|
+
testStdioMCPServer(server.name, server.config);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(`\n${'='.repeat(60)}`);
|
|
172
|
+
console.log('✅ 테스트 완료');
|
|
173
|
+
console.log('='.repeat(60));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
main().catch((error) => {
|
|
177
|
+
console.error('❌ 치명적 오류:', error);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
});
|
|
180
|
+
|
package/src/test.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { DesignSystemService } from './services/design-system.js';
|
|
4
|
+
import { FigmaService } from './services/figma.js';
|
|
5
|
+
import { CodeGenerator } from './services/code-generator.js';
|
|
6
|
+
|
|
7
|
+
async function testServices() {
|
|
8
|
+
console.log('🧪 Testing Palette Services...\n');
|
|
9
|
+
|
|
10
|
+
// Test Design System Service
|
|
11
|
+
console.log('1. Testing Design System Service...');
|
|
12
|
+
const designSystemService = new DesignSystemService();
|
|
13
|
+
|
|
14
|
+
const reactComponents = await designSystemService.getAvailableComponents('react');
|
|
15
|
+
console.log(`✅ Found ${reactComponents.length} React components`);
|
|
16
|
+
|
|
17
|
+
const vueComponents = await designSystemService.getAvailableComponents('vue');
|
|
18
|
+
console.log(`✅ Found ${vueComponents.length} Vue components`);
|
|
19
|
+
|
|
20
|
+
// Test component matching
|
|
21
|
+
const buttonMatch = designSystemService.findBestMatch('button', 'react');
|
|
22
|
+
console.log(`✅ Button match: ${buttonMatch?.name || 'Not found'}`);
|
|
23
|
+
|
|
24
|
+
const inputMatch = designSystemService.findBestMatch('text-field', 'vue');
|
|
25
|
+
console.log(`✅ Input match: ${inputMatch?.name || 'Not found'}\n`);
|
|
26
|
+
|
|
27
|
+
// Test Figma Service (without actual API call)
|
|
28
|
+
console.log('2. Testing Figma Service...');
|
|
29
|
+
const figmaService = new FigmaService();
|
|
30
|
+
|
|
31
|
+
// Test URL parsing
|
|
32
|
+
try {
|
|
33
|
+
const fileId = figmaService['extractFileId']('https://www.figma.com/file/abc123/Test-File');
|
|
34
|
+
console.log(`✅ URL parsing works: ${fileId}`);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.log(`❌ URL parsing failed: ${error}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Test Code Generator
|
|
40
|
+
console.log('3. Testing Code Generator...');
|
|
41
|
+
const codeGenerator = new CodeGenerator(designSystemService);
|
|
42
|
+
|
|
43
|
+
// Mock Figma data
|
|
44
|
+
const mockFigmaData = {
|
|
45
|
+
document: {
|
|
46
|
+
id: 'root',
|
|
47
|
+
name: 'Test Component',
|
|
48
|
+
type: 'FRAME',
|
|
49
|
+
children: [
|
|
50
|
+
{
|
|
51
|
+
id: 'button-1',
|
|
52
|
+
name: 'Submit Button',
|
|
53
|
+
type: 'RECTANGLE',
|
|
54
|
+
characters: 'Submit',
|
|
55
|
+
absoluteBoundingBox: { x: 0, y: 0, width: 100, height: 40 },
|
|
56
|
+
cornerRadius: 8,
|
|
57
|
+
fills: [{
|
|
58
|
+
type: 'SOLID',
|
|
59
|
+
color: { r: 0.2, g: 0.4, b: 0.8, a: 1 }
|
|
60
|
+
}]
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
components: {},
|
|
65
|
+
styles: {},
|
|
66
|
+
name: 'Test File',
|
|
67
|
+
lastModified: '2024-01-01',
|
|
68
|
+
thumbnailUrl: ''
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const reactCode = await codeGenerator.generateReactComponent(mockFigmaData, 'TestComponent');
|
|
73
|
+
console.log('✅ React code generation works');
|
|
74
|
+
console.log('Generated React component preview:');
|
|
75
|
+
console.log(reactCode.split('\n').slice(0, 10).join('\n') + '...\n');
|
|
76
|
+
|
|
77
|
+
const vueCode = await codeGenerator.generateVueComponent(mockFigmaData, 'TestComponent');
|
|
78
|
+
console.log('✅ Vue code generation works');
|
|
79
|
+
console.log('Generated Vue component preview:');
|
|
80
|
+
console.log(vueCode.split('\n').slice(0, 10).join('\n') + '...\n');
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.log(`❌ Code generation failed: ${error}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log('🎉 All tests completed!');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Run tests
|
|
89
|
+
testServices().catch(console.error);
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Figma Desktop MCP 서버와 통신하는 클라이언트
|
|
5
|
+
* HTTP 기반 MCP 서버와 JSON-RPC 2.0 프로토콜로 통신
|
|
6
|
+
*/
|
|
7
|
+
export class FigmaMCPClient {
|
|
8
|
+
private client: AxiosInstance;
|
|
9
|
+
private baseUrl: string;
|
|
10
|
+
private requestId: number = 0;
|
|
11
|
+
|
|
12
|
+
constructor(baseUrl: string = 'http://127.0.0.1:3845/mcp') {
|
|
13
|
+
this.baseUrl = baseUrl;
|
|
14
|
+
this.client = axios.create({
|
|
15
|
+
baseURL: baseUrl,
|
|
16
|
+
timeout: 30000,
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* JSON-RPC 2.0 요청 생성
|
|
25
|
+
*/
|
|
26
|
+
private createRequest(method: string, params?: any): any {
|
|
27
|
+
return {
|
|
28
|
+
jsonrpc: '2.0',
|
|
29
|
+
id: ++this.requestId,
|
|
30
|
+
method,
|
|
31
|
+
params: params || {},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* MCP 서버에 요청 전송
|
|
37
|
+
* HTTP 기반 MCP 서버는 JSON-RPC 2.0 프로토콜을 사용
|
|
38
|
+
*/
|
|
39
|
+
private async sendRequest(method: string, params?: any): Promise<any> {
|
|
40
|
+
try {
|
|
41
|
+
const request = this.createRequest(method, params);
|
|
42
|
+
|
|
43
|
+
// HTTP 기반 MCP 서버에 POST 요청
|
|
44
|
+
const response = await this.client.post('', request);
|
|
45
|
+
|
|
46
|
+
if (response.data.error) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`MCP Error: ${response.data.error.message || 'Unknown error'}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return response.data.result;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (axios.isAxiosError(error)) {
|
|
55
|
+
// 연결 실패 시 null 반환 (폴백을 위해)
|
|
56
|
+
if (
|
|
57
|
+
error.code === 'ECONNREFUSED' ||
|
|
58
|
+
error.code === 'ETIMEDOUT' ||
|
|
59
|
+
error.response?.status === 404
|
|
60
|
+
) {
|
|
61
|
+
console.warn(`Figma MCP 서버 연결 실패 (${error.code || error.response?.status}), REST API로 폴백합니다.`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Figma MCP connection error: ${error.message}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 사용 가능한 도구 목록 가져오기
|
|
74
|
+
*/
|
|
75
|
+
async listTools(): Promise<any[]> {
|
|
76
|
+
const result = await this.sendRequest('tools/list');
|
|
77
|
+
return result?.tools || [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* MCP 도구 호출
|
|
82
|
+
*/
|
|
83
|
+
private async callTool(toolName: string, args: any): Promise<any> {
|
|
84
|
+
try {
|
|
85
|
+
const result = await this.sendRequest('tools/call', {
|
|
86
|
+
name: toolName,
|
|
87
|
+
arguments: args,
|
|
88
|
+
});
|
|
89
|
+
return result;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.warn(`Figma MCP 도구 ${toolName} 호출 실패:`, error);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Figma 파일 데이터 가져오기
|
|
98
|
+
* Figma MCP 서버의 도구를 사용하여 파일 정보 가져오기
|
|
99
|
+
*/
|
|
100
|
+
async getFileData(fileId: string, nodeId?: string): Promise<any> {
|
|
101
|
+
try {
|
|
102
|
+
// 먼저 도구 목록을 확인
|
|
103
|
+
const tools = await this.listTools();
|
|
104
|
+
|
|
105
|
+
if (!tools || tools.length === 0) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 파일 데이터를 가져오는 도구 찾기
|
|
110
|
+
// Figma Desktop MCP 서버가 제공하는 일반적인 도구 이름들 시도
|
|
111
|
+
const possibleToolNames = [
|
|
112
|
+
'get_file',
|
|
113
|
+
'getFile',
|
|
114
|
+
'fetch_file',
|
|
115
|
+
'fetchFile',
|
|
116
|
+
'get_figma_file',
|
|
117
|
+
'figma_get_file',
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
for (const toolName of possibleToolNames) {
|
|
121
|
+
const tool = tools.find((t: any) => t.name === toolName);
|
|
122
|
+
if (tool) {
|
|
123
|
+
const params: any = { fileId };
|
|
124
|
+
if (nodeId) {
|
|
125
|
+
params.nodeId = nodeId;
|
|
126
|
+
}
|
|
127
|
+
const result = await this.callTool(toolName, params);
|
|
128
|
+
if (result) {
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 도구를 찾지 못한 경우 null 반환 (폴백 사용)
|
|
135
|
+
return null;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.warn('Figma MCP 파일 데이터 가져오기 실패:', error);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Figma 노드 정보 가져오기
|
|
144
|
+
*/
|
|
145
|
+
async getNodeData(fileId: string, nodeId: string): Promise<any> {
|
|
146
|
+
try {
|
|
147
|
+
const tools = await this.listTools();
|
|
148
|
+
|
|
149
|
+
if (!tools || tools.length === 0) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 노드 데이터를 가져오는 도구 찾기
|
|
154
|
+
const possibleToolNames = [
|
|
155
|
+
'get_node',
|
|
156
|
+
'getNode',
|
|
157
|
+
'fetch_node',
|
|
158
|
+
'fetchNode',
|
|
159
|
+
'get_figma_node',
|
|
160
|
+
'figma_get_node',
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
for (const toolName of possibleToolNames) {
|
|
164
|
+
const tool = tools.find((t: any) => t.name === toolName);
|
|
165
|
+
if (tool) {
|
|
166
|
+
const result = await this.callTool(toolName, { fileId, nodeId });
|
|
167
|
+
if (result) {
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 노드 전용 도구가 없으면 파일 도구에 nodeId를 전달하여 시도
|
|
174
|
+
return await this.getFileData(fileId, nodeId);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.warn('Figma MCP 노드 데이터 가져오기 실패:', error);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* MCP 서버 연결 상태 확인
|
|
183
|
+
*/
|
|
184
|
+
async isAvailable(): Promise<boolean> {
|
|
185
|
+
try {
|
|
186
|
+
await this.listTools();
|
|
187
|
+
return true;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 요청 ID 생성 (타임스탬프 + 랜덤 문자열)
|
|
12
|
+
*/
|
|
13
|
+
export function generateRequestId(): string {
|
|
14
|
+
const timestamp = Date.now();
|
|
15
|
+
const random = Math.random().toString(36).substring(2, 9);
|
|
16
|
+
return `${timestamp}-${random}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 요청별 폴더 경로 생성
|
|
21
|
+
*/
|
|
22
|
+
export function getRequestFolderPath(requestId: string): string {
|
|
23
|
+
const projectRoot = join(__dirname, '..', '..');
|
|
24
|
+
return join(projectRoot, 'dist', 'requests', requestId);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 요청 폴더 생성
|
|
29
|
+
*/
|
|
30
|
+
export async function createRequestFolder(requestId: string): Promise<string> {
|
|
31
|
+
const folderPath = getRequestFolderPath(requestId);
|
|
32
|
+
|
|
33
|
+
if (!existsSync(folderPath)) {
|
|
34
|
+
await mkdir(folderPath, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return folderPath;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 파일 저장
|
|
42
|
+
*/
|
|
43
|
+
export async function saveFile(
|
|
44
|
+
requestId: string,
|
|
45
|
+
filename: string,
|
|
46
|
+
content: string
|
|
47
|
+
): Promise<string> {
|
|
48
|
+
const folderPath = await createRequestFolder(requestId);
|
|
49
|
+
const filePath = join(folderPath, filename);
|
|
50
|
+
await writeFile(filePath, content, 'utf-8');
|
|
51
|
+
return filePath;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 바이너리 파일 저장 (이미지 등)
|
|
56
|
+
*/
|
|
57
|
+
export async function saveBinaryFile(
|
|
58
|
+
requestId: string,
|
|
59
|
+
filename: string,
|
|
60
|
+
content: Buffer
|
|
61
|
+
): Promise<string> {
|
|
62
|
+
const folderPath = await createRequestFolder(requestId);
|
|
63
|
+
const filePath = join(folderPath, filename);
|
|
64
|
+
await writeFile(filePath, content);
|
|
65
|
+
return filePath;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 요청 메타데이터 저장
|
|
70
|
+
*/
|
|
71
|
+
export interface RequestMetadata {
|
|
72
|
+
requestId: string;
|
|
73
|
+
type: 'react' | 'vue';
|
|
74
|
+
componentName: string;
|
|
75
|
+
figmaUrl: string;
|
|
76
|
+
nodeId?: string;
|
|
77
|
+
createdAt: string;
|
|
78
|
+
files: Array<{
|
|
79
|
+
name: string;
|
|
80
|
+
path: string;
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function saveMetadata(
|
|
85
|
+
requestId: string,
|
|
86
|
+
metadata: Omit<RequestMetadata, 'requestId' | 'createdAt' | 'files'>
|
|
87
|
+
): Promise<string> {
|
|
88
|
+
const folderPath = await createRequestFolder(requestId);
|
|
89
|
+
const metadataPath = join(folderPath, 'metadata.json');
|
|
90
|
+
|
|
91
|
+
const fullMetadata: RequestMetadata = {
|
|
92
|
+
requestId,
|
|
93
|
+
createdAt: new Date().toISOString(),
|
|
94
|
+
files: [],
|
|
95
|
+
...metadata,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
await writeFile(metadataPath, JSON.stringify(fullMetadata, null, 2), 'utf-8');
|
|
99
|
+
return metadataPath;
|
|
100
|
+
}
|
|
101
|
+
|