palette-mcp 1.0.0 → 1.1.1
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/index.d.ts +6 -0
- package/dist/index.js +16 -226
- 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/package.json +11 -1
- package/smithery.yaml +8 -57
- 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/utils/figma-mcp-client.ts +193 -0
- package/src/utils/request-manager.ts +101 -0
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Palette MCP Server - Local 실행 모드
|
|
4
|
+
*
|
|
5
|
+
* npx palette-mcp 또는 직접 실행 시 사용됩니다.
|
|
6
|
+
* stdio transport를 사용하여 MCP 클라이언트와 통신합니다.
|
|
7
|
+
*/
|
|
2
8
|
// .env 파일 로드
|
|
3
9
|
import dotenv from 'dotenv';
|
|
4
10
|
import { fileURLToPath } from 'url';
|
|
@@ -7,236 +13,20 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
7
13
|
const __dirname = dirname(__filename);
|
|
8
14
|
// 프로젝트 루트의 .env 파일 로드
|
|
9
15
|
dotenv.config({ path: join(__dirname, '..', '.env') });
|
|
10
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
16
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
import { FigmaService } from './services/figma.js';
|
|
15
|
-
import { DesignSystemService } from './services/design-system.js';
|
|
16
|
-
import { CodeGenerator } from './services/code-generator.js';
|
|
17
|
-
// MCP 서버 초기화
|
|
18
|
-
const server = new Server({
|
|
19
|
-
name: 'palette',
|
|
20
|
-
version: '1.0.0',
|
|
21
|
-
});
|
|
22
|
-
// Figma 및 Design System 서비스 초기화
|
|
23
|
-
const figmaService = new FigmaService();
|
|
24
|
-
const designSystemService = new DesignSystemService();
|
|
25
|
-
const codeGenerator = new CodeGenerator(designSystemService);
|
|
26
|
-
// 사용 가능한 도구 목록
|
|
27
|
-
const tools = [
|
|
28
|
-
{
|
|
29
|
-
name: 'convert_figma_to_react',
|
|
30
|
-
description: 'Convert Figma design to React component using existing design system',
|
|
31
|
-
inputSchema: {
|
|
32
|
-
type: 'object',
|
|
33
|
-
properties: {
|
|
34
|
-
figmaUrl: {
|
|
35
|
-
type: 'string',
|
|
36
|
-
description: 'Figma file URL or file ID',
|
|
37
|
-
},
|
|
38
|
-
nodeId: {
|
|
39
|
-
type: 'string',
|
|
40
|
-
description: 'Specific node ID to convert (optional)',
|
|
41
|
-
},
|
|
42
|
-
componentName: {
|
|
43
|
-
type: 'string',
|
|
44
|
-
description: 'Name for the generated component',
|
|
45
|
-
},
|
|
46
|
-
previewType: {
|
|
47
|
-
type: 'string',
|
|
48
|
-
enum: ['html', 'image', 'both'],
|
|
49
|
-
description: 'Preview type: "html" for HTML file, "image" for PNG image, "both" for both (default: "both")',
|
|
50
|
-
default: 'both',
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
required: ['figmaUrl', 'componentName'],
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
name: 'convert_figma_to_vue',
|
|
58
|
-
description: 'Convert Figma design to Vue component using existing design system',
|
|
59
|
-
inputSchema: {
|
|
60
|
-
type: 'object',
|
|
61
|
-
properties: {
|
|
62
|
-
figmaUrl: {
|
|
63
|
-
type: 'string',
|
|
64
|
-
description: 'Figma file URL or file ID',
|
|
65
|
-
},
|
|
66
|
-
nodeId: {
|
|
67
|
-
type: 'string',
|
|
68
|
-
description: 'Specific node ID to convert (optional)',
|
|
69
|
-
},
|
|
70
|
-
componentName: {
|
|
71
|
-
type: 'string',
|
|
72
|
-
description: 'Name for the generated component',
|
|
73
|
-
},
|
|
74
|
-
previewType: {
|
|
75
|
-
type: 'string',
|
|
76
|
-
enum: ['html', 'image', 'both'],
|
|
77
|
-
description: 'Preview type: "html" for HTML file, "image" for PNG image, "both" for both (default: "both")',
|
|
78
|
-
default: 'both',
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
required: ['figmaUrl', 'componentName'],
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: 'list_design_system_components',
|
|
86
|
-
description: 'List available components in the design system',
|
|
87
|
-
inputSchema: {
|
|
88
|
-
type: 'object',
|
|
89
|
-
properties: {
|
|
90
|
-
framework: {
|
|
91
|
-
type: 'string',
|
|
92
|
-
enum: ['react', 'vue'],
|
|
93
|
-
description: 'Framework to list components for',
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
required: ['framework'],
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
name: 'analyze_figma_file',
|
|
101
|
-
description: 'Analyze Figma file structure and available components',
|
|
102
|
-
inputSchema: {
|
|
103
|
-
type: 'object',
|
|
104
|
-
properties: {
|
|
105
|
-
figmaUrl: {
|
|
106
|
-
type: 'string',
|
|
107
|
-
description: 'Figma file URL or file ID',
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
required: ['figmaUrl'],
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
];
|
|
114
|
-
// 도구 목록 핸들러
|
|
115
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
116
|
-
return { tools };
|
|
117
|
-
});
|
|
118
|
-
// 도구 실행 핸들러
|
|
119
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
120
|
-
const { name, arguments: args } = request.params;
|
|
121
|
-
try {
|
|
122
|
-
switch (name) {
|
|
123
|
-
case 'convert_figma_to_react': {
|
|
124
|
-
const { figmaUrl, nodeId, componentName, previewType = 'both' } = args;
|
|
125
|
-
const figmaData = await figmaService.getFigmaData(figmaUrl, nodeId);
|
|
126
|
-
const files = await codeGenerator.generateAndSaveReactComponent(figmaData, componentName, figmaUrl, nodeId, previewType);
|
|
127
|
-
// 결과 메시지 구성
|
|
128
|
-
let fileList = `- 컴포넌트: \`${files.componentFile}\`\n`;
|
|
129
|
-
if (files.htmlFile) {
|
|
130
|
-
fileList += `- HTML 미리보기: \`${files.htmlFile}\`\n`;
|
|
131
|
-
}
|
|
132
|
-
if (files.imageFile) {
|
|
133
|
-
fileList += `- 이미지 미리보기: \`${files.imageFile}\`\n`;
|
|
134
|
-
}
|
|
135
|
-
fileList += `- 메타데이터: \`${files.metadataFile}\``;
|
|
136
|
-
const content = [
|
|
137
|
-
{
|
|
138
|
-
type: 'text',
|
|
139
|
-
text: `# React Component Generated\n\n**요청 ID:** \`${files.requestId}\`\n**저장 경로:** \`${files.folderPath}\`\n\n## 생성된 파일\n${fileList}\n\n## 컴포넌트 코드\n\n\`\`\`tsx\n${await readFile(files.componentFile, 'utf-8')}\n\`\`\``,
|
|
140
|
-
},
|
|
141
|
-
];
|
|
142
|
-
// 이미지가 있으면 이미지도 포함
|
|
143
|
-
if (files.imageFile) {
|
|
144
|
-
try {
|
|
145
|
-
const imageBuffer = await readFile(files.imageFile);
|
|
146
|
-
content.push({
|
|
147
|
-
type: 'image',
|
|
148
|
-
data: imageBuffer.toString('base64'),
|
|
149
|
-
mimeType: 'image/png'
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
console.warn('이미지 읽기 실패:', error);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return { content };
|
|
157
|
-
}
|
|
158
|
-
case 'convert_figma_to_vue': {
|
|
159
|
-
const { figmaUrl, nodeId, componentName, previewType = 'both' } = args;
|
|
160
|
-
const figmaData = await figmaService.getFigmaData(figmaUrl, nodeId);
|
|
161
|
-
const files = await codeGenerator.generateAndSaveVueComponent(figmaData, componentName, figmaUrl, nodeId, previewType);
|
|
162
|
-
// 결과 메시지 구성
|
|
163
|
-
let fileList = `- 컴포넌트: \`${files.componentFile}\`\n`;
|
|
164
|
-
if (files.htmlFile) {
|
|
165
|
-
fileList += `- HTML 미리보기: \`${files.htmlFile}\`\n`;
|
|
166
|
-
}
|
|
167
|
-
if (files.imageFile) {
|
|
168
|
-
fileList += `- 이미지 미리보기: \`${files.imageFile}\`\n`;
|
|
169
|
-
}
|
|
170
|
-
fileList += `- 메타데이터: \`${files.metadataFile}\``;
|
|
171
|
-
const content = [
|
|
172
|
-
{
|
|
173
|
-
type: 'text',
|
|
174
|
-
text: `# Vue Component Generated\n\n**요청 ID:** \`${files.requestId}\`\n**저장 경로:** \`${files.folderPath}\`\n\n## 생성된 파일\n${fileList}\n\n## 컴포넌트 코드\n\n\`\`\`vue\n${await readFile(files.componentFile, 'utf-8')}\n\`\`\``,
|
|
175
|
-
},
|
|
176
|
-
];
|
|
177
|
-
// 이미지가 있으면 이미지도 포함
|
|
178
|
-
if (files.imageFile) {
|
|
179
|
-
try {
|
|
180
|
-
const imageBuffer = await readFile(files.imageFile);
|
|
181
|
-
content.push({
|
|
182
|
-
type: 'image',
|
|
183
|
-
data: imageBuffer.toString('base64'),
|
|
184
|
-
mimeType: 'image/png'
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
catch (error) {
|
|
188
|
-
console.warn('이미지 읽기 실패:', error);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return { content };
|
|
192
|
-
}
|
|
193
|
-
case 'list_design_system_components': {
|
|
194
|
-
const { framework } = args;
|
|
195
|
-
const components = await designSystemService.getAvailableComponents(framework);
|
|
196
|
-
return {
|
|
197
|
-
content: [
|
|
198
|
-
{
|
|
199
|
-
type: 'text',
|
|
200
|
-
text: `# Available ${framework} Components\n\n${components
|
|
201
|
-
.map((comp) => `- **${comp.name}**: ${comp.description}`)
|
|
202
|
-
.join('\n')}`,
|
|
203
|
-
},
|
|
204
|
-
],
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
case 'analyze_figma_file': {
|
|
208
|
-
const { figmaUrl } = args;
|
|
209
|
-
const analysis = await figmaService.analyzeFigmaFile(figmaUrl);
|
|
210
|
-
return {
|
|
211
|
-
content: [
|
|
212
|
-
{
|
|
213
|
-
type: 'text',
|
|
214
|
-
text: `# Figma File Analysis\n\n${analysis}`,
|
|
215
|
-
},
|
|
216
|
-
],
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
default:
|
|
220
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
return {
|
|
225
|
-
content: [
|
|
226
|
-
{
|
|
227
|
-
type: 'text',
|
|
228
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
229
|
-
},
|
|
230
|
-
],
|
|
231
|
-
isError: true,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
// 서버 시작
|
|
17
|
+
import { createPaletteServer } from './server.js';
|
|
18
|
+
// 서버 시작 (Local 모드)
|
|
236
19
|
async function main() {
|
|
20
|
+
// 환경변수에서 설정 읽기
|
|
21
|
+
const server = createPaletteServer({
|
|
22
|
+
figmaAccessToken: process.env.FIGMA_ACCESS_TOKEN,
|
|
23
|
+
githubToken: process.env.GITHUB_TOKEN,
|
|
24
|
+
figmaMcpServerUrl: process.env.FIGMA_MCP_SERVER_URL,
|
|
25
|
+
});
|
|
26
|
+
// stdio transport로 연결
|
|
237
27
|
const transport = new StdioServerTransport();
|
|
238
28
|
await server.connect(transport);
|
|
239
|
-
console.error('Palette server running on stdio');
|
|
29
|
+
console.error('Palette server running on stdio (Local mode)');
|
|
240
30
|
}
|
|
241
31
|
main().catch((error) => {
|
|
242
32
|
console.error('Server error:', error);
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Palette MCP Server - 공통 로직
|
|
3
|
+
*
|
|
4
|
+
* Local(stdio) 모드와 Remote(Smithery) 모드에서 공유되는 핵심 기능을 정의합니다.
|
|
5
|
+
*/
|
|
6
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
|
+
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
export interface ServerConfig {
|
|
9
|
+
figmaAccessToken?: string;
|
|
10
|
+
githubToken?: string;
|
|
11
|
+
figmaMcpServerUrl?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const tools: Tool[];
|
|
14
|
+
/**
|
|
15
|
+
* MCP 서버를 생성하고 핸들러를 등록합니다.
|
|
16
|
+
* Local 모드와 Remote 모드에서 공통으로 사용됩니다.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createPaletteServer(config?: ServerConfig): Server;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Palette MCP Server - 공통 로직
|
|
3
|
+
*
|
|
4
|
+
* Local(stdio) 모드와 Remote(Smithery) 모드에서 공유되는 핵심 기능을 정의합니다.
|
|
5
|
+
*/
|
|
6
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import { readFile } from 'fs/promises';
|
|
9
|
+
import { FigmaService } from './services/figma.js';
|
|
10
|
+
import { DesignSystemService } from './services/design-system.js';
|
|
11
|
+
import { CodeGenerator } from './services/code-generator.js';
|
|
12
|
+
// Tools 정의
|
|
13
|
+
export const tools = [
|
|
14
|
+
{
|
|
15
|
+
name: 'convert_figma_to_react',
|
|
16
|
+
description: 'Figma 디자인을 디자인 시스템을 사용하여 React 컴포넌트로 변환합니다',
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
figmaUrl: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Figma 파일 URL 또는 파일 ID',
|
|
23
|
+
},
|
|
24
|
+
nodeId: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: '변환할 특정 노드 ID (선택사항)',
|
|
27
|
+
},
|
|
28
|
+
componentName: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: '생성할 컴포넌트 이름',
|
|
31
|
+
},
|
|
32
|
+
previewType: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
enum: ['html', 'image', 'both'],
|
|
35
|
+
description: '미리보기 타입: "html"은 HTML 파일, "image"는 PNG 이미지, "both"는 둘 다 (기본값: "both")',
|
|
36
|
+
default: 'both',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ['figmaUrl', 'componentName'],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'convert_figma_to_vue',
|
|
44
|
+
description: 'Figma 디자인을 디자인 시스템을 사용하여 Vue 컴포넌트로 변환합니다',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
figmaUrl: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'Figma 파일 URL 또는 파일 ID',
|
|
51
|
+
},
|
|
52
|
+
nodeId: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: '변환할 특정 노드 ID (선택사항)',
|
|
55
|
+
},
|
|
56
|
+
componentName: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: '생성할 컴포넌트 이름',
|
|
59
|
+
},
|
|
60
|
+
previewType: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
enum: ['html', 'image', 'both'],
|
|
63
|
+
description: '미리보기 타입: "html"은 HTML 파일, "image"는 PNG 이미지, "both"는 둘 다 (기본값: "both")',
|
|
64
|
+
default: 'both',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
required: ['figmaUrl', 'componentName'],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'list_design_system_components',
|
|
72
|
+
description: '디자인 시스템에서 사용 가능한 컴포넌트 목록을 조회합니다',
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
framework: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
enum: ['react', 'vue'],
|
|
79
|
+
description: '컴포넌트를 조회할 프레임워크',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
required: ['framework'],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'analyze_figma_file',
|
|
87
|
+
description: 'Figma 파일 구조와 사용 가능한 컴포넌트를 분석합니다',
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: 'object',
|
|
90
|
+
properties: {
|
|
91
|
+
figmaUrl: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: 'Figma 파일 URL 또는 파일 ID',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
required: ['figmaUrl'],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
/**
|
|
101
|
+
* MCP 서버를 생성하고 핸들러를 등록합니다.
|
|
102
|
+
* Local 모드와 Remote 모드에서 공통으로 사용됩니다.
|
|
103
|
+
*/
|
|
104
|
+
export function createPaletteServer(config = {}) {
|
|
105
|
+
// 환경변수에서 설정 읽기 (config가 우선)
|
|
106
|
+
if (config.figmaAccessToken) {
|
|
107
|
+
process.env.FIGMA_ACCESS_TOKEN = config.figmaAccessToken;
|
|
108
|
+
}
|
|
109
|
+
if (config.githubToken) {
|
|
110
|
+
process.env.GITHUB_TOKEN = config.githubToken;
|
|
111
|
+
}
|
|
112
|
+
if (config.figmaMcpServerUrl) {
|
|
113
|
+
process.env.FIGMA_MCP_SERVER_URL = config.figmaMcpServerUrl;
|
|
114
|
+
}
|
|
115
|
+
// MCP 서버 초기화
|
|
116
|
+
const server = new Server({
|
|
117
|
+
name: 'palette',
|
|
118
|
+
version: '1.0.0',
|
|
119
|
+
});
|
|
120
|
+
// 서비스 초기화
|
|
121
|
+
const figmaService = new FigmaService();
|
|
122
|
+
const designSystemService = new DesignSystemService();
|
|
123
|
+
const codeGenerator = new CodeGenerator(designSystemService);
|
|
124
|
+
// 도구 목록 핸들러
|
|
125
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
126
|
+
return { tools };
|
|
127
|
+
});
|
|
128
|
+
// 도구 실행 핸들러
|
|
129
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
130
|
+
const { name, arguments: args } = request.params;
|
|
131
|
+
try {
|
|
132
|
+
switch (name) {
|
|
133
|
+
case 'convert_figma_to_react': {
|
|
134
|
+
const { figmaUrl, nodeId, componentName, previewType = 'both' } = args;
|
|
135
|
+
const figmaData = await figmaService.getFigmaData(figmaUrl, nodeId);
|
|
136
|
+
const files = await codeGenerator.generateAndSaveReactComponent(figmaData, componentName, figmaUrl, nodeId, previewType);
|
|
137
|
+
// 결과 메시지 구성
|
|
138
|
+
let fileList = `- 컴포넌트: \`${files.componentFile}\`\n`;
|
|
139
|
+
if (files.htmlFile) {
|
|
140
|
+
fileList += `- HTML 미리보기: \`${files.htmlFile}\`\n`;
|
|
141
|
+
}
|
|
142
|
+
if (files.imageFile) {
|
|
143
|
+
fileList += `- 이미지 미리보기: \`${files.imageFile}\`\n`;
|
|
144
|
+
}
|
|
145
|
+
fileList += `- 메타데이터: \`${files.metadataFile}\``;
|
|
146
|
+
const content = [
|
|
147
|
+
{
|
|
148
|
+
type: 'text',
|
|
149
|
+
text: `# React Component Generated\n\n**요청 ID:** \`${files.requestId}\`\n**저장 경로:** \`${files.folderPath}\`\n\n## 생성된 파일\n${fileList}\n\n## 컴포넌트 코드\n\n\`\`\`tsx\n${await readFile(files.componentFile, 'utf-8')}\n\`\`\``,
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
// 이미지가 있으면 이미지도 포함
|
|
153
|
+
if (files.imageFile) {
|
|
154
|
+
try {
|
|
155
|
+
const imageBuffer = await readFile(files.imageFile);
|
|
156
|
+
content.push({
|
|
157
|
+
type: 'image',
|
|
158
|
+
data: imageBuffer.toString('base64'),
|
|
159
|
+
mimeType: 'image/png'
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.warn('이미지 읽기 실패:', error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return { content };
|
|
167
|
+
}
|
|
168
|
+
case 'convert_figma_to_vue': {
|
|
169
|
+
const { figmaUrl, nodeId, componentName, previewType = 'both' } = args;
|
|
170
|
+
const figmaData = await figmaService.getFigmaData(figmaUrl, nodeId);
|
|
171
|
+
const files = await codeGenerator.generateAndSaveVueComponent(figmaData, componentName, figmaUrl, nodeId, previewType);
|
|
172
|
+
// 결과 메시지 구성
|
|
173
|
+
let fileList = `- 컴포넌트: \`${files.componentFile}\`\n`;
|
|
174
|
+
if (files.htmlFile) {
|
|
175
|
+
fileList += `- HTML 미리보기: \`${files.htmlFile}\`\n`;
|
|
176
|
+
}
|
|
177
|
+
if (files.imageFile) {
|
|
178
|
+
fileList += `- 이미지 미리보기: \`${files.imageFile}\`\n`;
|
|
179
|
+
}
|
|
180
|
+
fileList += `- 메타데이터: \`${files.metadataFile}\``;
|
|
181
|
+
const content = [
|
|
182
|
+
{
|
|
183
|
+
type: 'text',
|
|
184
|
+
text: `# Vue Component Generated\n\n**요청 ID:** \`${files.requestId}\`\n**저장 경로:** \`${files.folderPath}\`\n\n## 생성된 파일\n${fileList}\n\n## 컴포넌트 코드\n\n\`\`\`vue\n${await readFile(files.componentFile, 'utf-8')}\n\`\`\``,
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
// 이미지가 있으면 이미지도 포함
|
|
188
|
+
if (files.imageFile) {
|
|
189
|
+
try {
|
|
190
|
+
const imageBuffer = await readFile(files.imageFile);
|
|
191
|
+
content.push({
|
|
192
|
+
type: 'image',
|
|
193
|
+
data: imageBuffer.toString('base64'),
|
|
194
|
+
mimeType: 'image/png'
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
console.warn('이미지 읽기 실패:', error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return { content };
|
|
202
|
+
}
|
|
203
|
+
case 'list_design_system_components': {
|
|
204
|
+
const { framework } = args;
|
|
205
|
+
const components = await designSystemService.getAvailableComponents(framework);
|
|
206
|
+
return {
|
|
207
|
+
content: [
|
|
208
|
+
{
|
|
209
|
+
type: 'text',
|
|
210
|
+
text: `# Available ${framework} Components\n\n${components
|
|
211
|
+
.map((comp) => `- **${comp.name}**: ${comp.description}`)
|
|
212
|
+
.join('\n')}`,
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
case 'analyze_figma_file': {
|
|
218
|
+
const { figmaUrl } = args;
|
|
219
|
+
const analysis = await figmaService.analyzeFigmaFile(figmaUrl);
|
|
220
|
+
return {
|
|
221
|
+
content: [
|
|
222
|
+
{
|
|
223
|
+
type: 'text',
|
|
224
|
+
text: `# Figma File Analysis\n\n${analysis}`,
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
default:
|
|
230
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
return {
|
|
235
|
+
content: [
|
|
236
|
+
{
|
|
237
|
+
type: 'text',
|
|
238
|
+
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
isError: true,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
return server;
|
|
246
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Palette MCP Server - Smithery Remote 배포용
|
|
3
|
+
*
|
|
4
|
+
* Smithery.ai에서 호스팅될 때 사용됩니다.
|
|
5
|
+
* Smithery가 이 파일을 로드하고 createServer 함수를 호출합니다.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { tools } from './server.js';
|
|
9
|
+
export declare const configSchema: z.ZodObject<{
|
|
10
|
+
FIGMA_ACCESS_TOKEN: z.ZodString;
|
|
11
|
+
GITHUB_TOKEN: z.ZodString;
|
|
12
|
+
FIGMA_MCP_SERVER_URL: z.ZodDefault<z.ZodString>;
|
|
13
|
+
}, "strip", z.ZodTypeAny, {
|
|
14
|
+
FIGMA_ACCESS_TOKEN: string;
|
|
15
|
+
FIGMA_MCP_SERVER_URL: string;
|
|
16
|
+
GITHUB_TOKEN: string;
|
|
17
|
+
}, {
|
|
18
|
+
FIGMA_ACCESS_TOKEN: string;
|
|
19
|
+
GITHUB_TOKEN: string;
|
|
20
|
+
FIGMA_MCP_SERVER_URL?: string | undefined;
|
|
21
|
+
}>;
|
|
22
|
+
type SmitheryConfig = z.infer<typeof configSchema>;
|
|
23
|
+
/**
|
|
24
|
+
* Smithery에서 호출하는 서버 생성 함수
|
|
25
|
+
*
|
|
26
|
+
* @param config - Smithery에서 전달받은 사용자 설정
|
|
27
|
+
* @returns MCP 서버 인스턴스
|
|
28
|
+
*/
|
|
29
|
+
export default function createServer({ config }: {
|
|
30
|
+
config: SmitheryConfig;
|
|
31
|
+
}): import("@modelcontextprotocol/sdk/server/index.js").Server<{
|
|
32
|
+
method: string;
|
|
33
|
+
params?: z.objectOutputType<{
|
|
34
|
+
_meta: z.ZodOptional<z.ZodObject<{
|
|
35
|
+
progressToken: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
36
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
37
|
+
progressToken: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
38
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
39
|
+
progressToken: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
40
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
41
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
42
|
+
}, {
|
|
43
|
+
method: string;
|
|
44
|
+
params?: z.objectOutputType<{
|
|
45
|
+
_meta: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
|
|
46
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
47
|
+
}, z.objectOutputType<{
|
|
48
|
+
_meta: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
|
|
49
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
50
|
+
export { tools };
|
package/dist/smithery.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Palette MCP Server - Smithery Remote 배포용
|
|
3
|
+
*
|
|
4
|
+
* Smithery.ai에서 호스팅될 때 사용됩니다.
|
|
5
|
+
* Smithery가 이 파일을 로드하고 createServer 함수를 호출합니다.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { createPaletteServer, tools } from './server.js';
|
|
9
|
+
// Smithery 설정 스키마 정의
|
|
10
|
+
export const configSchema = z.object({
|
|
11
|
+
FIGMA_ACCESS_TOKEN: z
|
|
12
|
+
.string()
|
|
13
|
+
.describe('Figma Personal Access Token (https://www.figma.com/developers/api#access-tokens)'),
|
|
14
|
+
GITHUB_TOKEN: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe('GitHub Personal Access Token for design system packages'),
|
|
17
|
+
FIGMA_MCP_SERVER_URL: z
|
|
18
|
+
.string()
|
|
19
|
+
.default('http://127.0.0.1:3845/mcp')
|
|
20
|
+
.describe('Figma Dev Mode MCP server URL'),
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Smithery에서 호출하는 서버 생성 함수
|
|
24
|
+
*
|
|
25
|
+
* @param config - Smithery에서 전달받은 사용자 설정
|
|
26
|
+
* @returns MCP 서버 인스턴스
|
|
27
|
+
*/
|
|
28
|
+
export default function createServer({ config }) {
|
|
29
|
+
// 환경변수 설정
|
|
30
|
+
process.env.FIGMA_ACCESS_TOKEN = config.FIGMA_ACCESS_TOKEN;
|
|
31
|
+
process.env.GITHUB_TOKEN = config.GITHUB_TOKEN;
|
|
32
|
+
process.env.FIGMA_MCP_SERVER_URL = config.FIGMA_MCP_SERVER_URL;
|
|
33
|
+
// 공통 서버 생성 로직 사용
|
|
34
|
+
const server = createPaletteServer({
|
|
35
|
+
figmaAccessToken: config.FIGMA_ACCESS_TOKEN,
|
|
36
|
+
githubToken: config.GITHUB_TOKEN,
|
|
37
|
+
figmaMcpServerUrl: config.FIGMA_MCP_SERVER_URL,
|
|
38
|
+
});
|
|
39
|
+
console.error('Palette server created for Smithery (Remote mode)');
|
|
40
|
+
// Smithery가 기대하는 형식으로 반환
|
|
41
|
+
return server;
|
|
42
|
+
}
|
|
43
|
+
// Tools 정보도 export (Smithery가 capabilities 탐지에 사용할 수 있음)
|
|
44
|
+
export { tools };
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "palette-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "MCP server for converting Figma designs to React/Vue components using design system",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"module": "src/smithery.ts",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"bin": {
|
|
8
9
|
"palette": "./dist/index.js",
|
|
@@ -11,8 +12,17 @@
|
|
|
11
12
|
"files": [
|
|
12
13
|
"dist/index.js",
|
|
13
14
|
"dist/index.d.ts",
|
|
15
|
+
"dist/server.js",
|
|
16
|
+
"dist/server.d.ts",
|
|
17
|
+
"dist/smithery.js",
|
|
18
|
+
"dist/smithery.d.ts",
|
|
14
19
|
"dist/services/**/*",
|
|
15
20
|
"dist/utils/**/*",
|
|
21
|
+
"src/index.ts",
|
|
22
|
+
"src/server.ts",
|
|
23
|
+
"src/smithery.ts",
|
|
24
|
+
"src/services/**/*",
|
|
25
|
+
"src/utils/**/*",
|
|
16
26
|
"README.md",
|
|
17
27
|
"LICENSE",
|
|
18
28
|
"smithery.yaml"
|