palette-mcp 1.1.2 → 1.2.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/server.d.ts +15 -0
- package/dist/server.js +214 -21
- package/dist/services/auth.d.ts +22 -0
- package/dist/services/auth.js +138 -0
- package/dist/services/code-generator.js +20 -7
- package/dist/services/design-system.d.ts +43 -1
- package/dist/services/design-system.js +84 -1
- package/dist/smithery.d.ts +57 -32
- package/dist/smithery.js +59 -23
- package/dist/utils/request-manager.js +12 -3
- package/package.json +15 -7
- package/smithery.yaml +2 -9
- package/src/server.ts +261 -26
- package/src/services/auth.ts +162 -0
- package/src/services/code-generator.ts +21 -6
- package/src/services/design-system.ts +106 -1
- package/src/smithery.ts +63 -23
- package/src/utils/request-manager.ts +14 -7
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ComponentSyncService, type SyncResult } from '../sync/index.js';
|
|
2
|
+
|
|
1
3
|
export interface DesignSystemComponent {
|
|
2
4
|
name: string;
|
|
3
5
|
description: string;
|
|
@@ -22,14 +24,117 @@ export interface ComponentExample {
|
|
|
22
24
|
description: string;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
export interface SyncConfig {
|
|
28
|
+
/** 서버 시작 시 자동 동기화 여부 (기본값: true) */
|
|
29
|
+
autoSync?: boolean;
|
|
30
|
+
/** 동기화 상세 로그 출력 여부 */
|
|
31
|
+
verbose?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
export class DesignSystemService {
|
|
26
35
|
private reactComponents: DesignSystemComponent[] = [];
|
|
27
36
|
private vueComponents: DesignSystemComponent[] = [];
|
|
37
|
+
private syncService: ComponentSyncService;
|
|
38
|
+
private syncConfig: SyncConfig;
|
|
39
|
+
private initialized: boolean = false;
|
|
40
|
+
private lastSyncResult: SyncResult | null = null;
|
|
28
41
|
|
|
29
|
-
constructor() {
|
|
42
|
+
constructor(syncConfig: SyncConfig = {}) {
|
|
43
|
+
this.syncConfig = {
|
|
44
|
+
autoSync: syncConfig.autoSync ?? true,
|
|
45
|
+
verbose: syncConfig.verbose ?? false,
|
|
46
|
+
};
|
|
47
|
+
this.syncService = new ComponentSyncService();
|
|
48
|
+
|
|
49
|
+
// 하드코딩된 기본 컴포넌트 초기화 (폴백용)
|
|
30
50
|
this.initializeComponents();
|
|
31
51
|
}
|
|
32
52
|
|
|
53
|
+
/**
|
|
54
|
+
* 서비스 초기화 (비동기)
|
|
55
|
+
* 서버 시작 시 호출하여 GitHub에서 최신 컴포넌트 동기화
|
|
56
|
+
*/
|
|
57
|
+
async initialize(): Promise<void> {
|
|
58
|
+
if (this.initialized) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (this.syncConfig.autoSync) {
|
|
63
|
+
try {
|
|
64
|
+
console.log('[DesignSystem] 컴포넌트 동기화 시작...');
|
|
65
|
+
const result = await this.syncService.sync({
|
|
66
|
+
verbose: this.syncConfig.verbose,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.lastSyncResult = result;
|
|
70
|
+
|
|
71
|
+
if (result.success) {
|
|
72
|
+
if (result.reactComponents.length > 0) {
|
|
73
|
+
this.reactComponents = result.reactComponents;
|
|
74
|
+
}
|
|
75
|
+
if (result.vueComponents.length > 0) {
|
|
76
|
+
this.vueComponents = result.vueComponents;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(
|
|
80
|
+
`[DesignSystem] 동기화 완료: React ${this.reactComponents.length}개, Vue ${this.vueComponents.length}개` +
|
|
81
|
+
(result.fromCache ? ' (캐시에서 로드)' : ' (GitHub에서 동기화)')
|
|
82
|
+
);
|
|
83
|
+
} else {
|
|
84
|
+
console.warn('[DesignSystem] 동기화 실패, 하드코딩된 데이터 사용:', result.error);
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.warn('[DesignSystem] 동기화 중 오류, 하드코딩된 데이터 사용:', error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.initialized = true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 수동 동기화 실행
|
|
96
|
+
*/
|
|
97
|
+
async syncComponents(force: boolean = false): Promise<SyncResult> {
|
|
98
|
+
const result = await this.syncService.sync({
|
|
99
|
+
force,
|
|
100
|
+
verbose: this.syncConfig.verbose,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
this.lastSyncResult = result;
|
|
104
|
+
|
|
105
|
+
if (result.success) {
|
|
106
|
+
if (result.reactComponents.length > 0) {
|
|
107
|
+
this.reactComponents = result.reactComponents;
|
|
108
|
+
}
|
|
109
|
+
if (result.vueComponents.length > 0) {
|
|
110
|
+
this.vueComponents = result.vueComponents;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 마지막 동기화 결과 조회
|
|
119
|
+
*/
|
|
120
|
+
getLastSyncResult(): SyncResult | null {
|
|
121
|
+
return this.lastSyncResult;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 캐시 상태 조회
|
|
126
|
+
*/
|
|
127
|
+
async getCacheStatus() {
|
|
128
|
+
return this.syncService.getCacheStatus();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 캐시 삭제
|
|
133
|
+
*/
|
|
134
|
+
async clearCache(): Promise<void> {
|
|
135
|
+
await this.syncService.clearCache();
|
|
136
|
+
}
|
|
137
|
+
|
|
33
138
|
/**
|
|
34
139
|
* 디자인 시스템(React, Vue) 컴포넌트 메타데이터 제공 필요.
|
|
35
140
|
* 1. 메타데이터 구조 정의
|
package/src/smithery.ts
CHANGED
|
@@ -2,53 +2,93 @@
|
|
|
2
2
|
* Palette MCP Server - Smithery Remote 배포용
|
|
3
3
|
*
|
|
4
4
|
* Smithery.ai에서 호스팅될 때 사용됩니다.
|
|
5
|
-
*
|
|
5
|
+
* @smithery/sdk를 사용하여 HTTP 서버로 실행됩니다.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { createStatelessServer } from '@smithery/sdk/server/stateless.js';
|
|
8
9
|
import { z } from 'zod';
|
|
9
|
-
import { createPaletteServer
|
|
10
|
+
import { createPaletteServer } from './server.js';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Smithery 설정 스키마 정의
|
|
14
|
+
*
|
|
15
|
+
* Palette MCP Server Configuration
|
|
16
|
+
* Figma 디자인을 Design System 컴포넌트로 변환하기 위한 설정
|
|
17
|
+
*
|
|
18
|
+
* 모든 필드는 optional로 설정하여 Smithery Optional config 점수 획득
|
|
19
|
+
*/
|
|
12
20
|
export const configSchema = z.object({
|
|
21
|
+
// Figma Access Token - Figma API 호출에 필요
|
|
13
22
|
FIGMA_ACCESS_TOKEN: z
|
|
14
23
|
.string()
|
|
15
|
-
.
|
|
24
|
+
.min(1)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Figma Personal Access Token. Required for accessing Figma designs. Get yours at https://www.figma.com/developers/api#access-tokens'),
|
|
27
|
+
|
|
28
|
+
// GitHub Token - 비공개 디자인 시스템 패키지 접근용
|
|
16
29
|
GITHUB_TOKEN: z
|
|
17
30
|
.string()
|
|
18
|
-
.
|
|
31
|
+
.min(1)
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('GitHub Personal Access Token for accessing private design system packages. Required only for private repositories.'),
|
|
34
|
+
|
|
35
|
+
// Figma MCP Server URL - 로컬 개발용 (Remote 모드에서는 사용 안함)
|
|
19
36
|
FIGMA_MCP_SERVER_URL: z
|
|
20
37
|
.string()
|
|
38
|
+
.url('Must be a valid URL')
|
|
39
|
+
.optional()
|
|
21
40
|
.default('http://127.0.0.1:3845/mcp')
|
|
22
|
-
.describe('Figma Dev Mode MCP server URL'),
|
|
23
|
-
});
|
|
41
|
+
.describe('Figma Dev Mode MCP server URL. Only needed for local development with Figma Desktop app.'),
|
|
42
|
+
}).describe('Configuration options for Palette MCP Server - Figma to Design System converter');
|
|
24
43
|
|
|
25
44
|
// Smithery 설정 타입
|
|
26
45
|
type SmitheryConfig = z.infer<typeof configSchema>;
|
|
27
46
|
|
|
47
|
+
// 기본 설정
|
|
48
|
+
const defaultConfig: SmitheryConfig = {
|
|
49
|
+
FIGMA_ACCESS_TOKEN: undefined,
|
|
50
|
+
GITHUB_TOKEN: undefined,
|
|
51
|
+
FIGMA_MCP_SERVER_URL: 'http://127.0.0.1:3845/mcp',
|
|
52
|
+
};
|
|
53
|
+
|
|
28
54
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* @param config - Smithery에서 전달받은 사용자 설정
|
|
32
|
-
* @returns MCP 서버 인스턴스
|
|
55
|
+
* MCP 서버 생성 함수 - Smithery가 호출
|
|
33
56
|
*/
|
|
34
|
-
export default function
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
57
|
+
export default function createMcpServer({ config }: { config?: SmitheryConfig } = {}) {
|
|
58
|
+
// config가 없거나 불완전해도 서버 초기화 가능하도록 방어적 처리
|
|
59
|
+
const safeConfig: SmitheryConfig = { ...defaultConfig, ...config };
|
|
60
|
+
|
|
61
|
+
// 환경변수 설정 (값이 있는 경우에만)
|
|
62
|
+
if (safeConfig.FIGMA_ACCESS_TOKEN) {
|
|
63
|
+
process.env.FIGMA_ACCESS_TOKEN = safeConfig.FIGMA_ACCESS_TOKEN;
|
|
64
|
+
}
|
|
65
|
+
if (safeConfig.GITHUB_TOKEN) {
|
|
66
|
+
process.env.GITHUB_TOKEN = safeConfig.GITHUB_TOKEN;
|
|
67
|
+
}
|
|
68
|
+
if (safeConfig.FIGMA_MCP_SERVER_URL) {
|
|
69
|
+
process.env.FIGMA_MCP_SERVER_URL = safeConfig.FIGMA_MCP_SERVER_URL;
|
|
70
|
+
}
|
|
39
71
|
|
|
40
72
|
// 공통 서버 생성 로직 사용
|
|
73
|
+
// Remote 모드에서는 Figma Desktop MCP 클라이언트를 사용하지 않음 (로컬호스트 접근 불가)
|
|
41
74
|
const server = createPaletteServer({
|
|
42
|
-
figmaAccessToken:
|
|
43
|
-
githubToken:
|
|
44
|
-
figmaMcpServerUrl:
|
|
75
|
+
figmaAccessToken: safeConfig.FIGMA_ACCESS_TOKEN,
|
|
76
|
+
githubToken: safeConfig.GITHUB_TOKEN,
|
|
77
|
+
figmaMcpServerUrl: safeConfig.FIGMA_MCP_SERVER_URL,
|
|
78
|
+
useFigmaMcp: false, // Remote 모드에서는 Figma REST API만 사용
|
|
45
79
|
});
|
|
46
80
|
|
|
47
|
-
console.error('Palette server created for Smithery (Remote mode)');
|
|
81
|
+
console.error('Palette MCP server created for Smithery (Remote mode)');
|
|
48
82
|
|
|
49
|
-
// Smithery가 기대하는 형식으로 반환
|
|
50
83
|
return server;
|
|
51
84
|
}
|
|
52
85
|
|
|
53
|
-
//
|
|
54
|
-
|
|
86
|
+
// 직접 실행 시에만 서버 시작 (Smithery에서 import할 때는 실행 안됨)
|
|
87
|
+
// Smithery는 createStatelessServer를 내부적으로 호출함
|
|
88
|
+
if (process.env.SMITHERY_STANDALONE === 'true') {
|
|
89
|
+
const { app } = createStatelessServer(createMcpServer);
|
|
90
|
+
const port = process.env.PORT || 3000;
|
|
91
|
+
app.listen(port, () => {
|
|
92
|
+
console.error(`Palette MCP server listening on port ${port}`);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -4,8 +4,16 @@ import { join } from 'path';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { dirname } from 'path';
|
|
6
6
|
|
|
7
|
+
// ESM과 CJS 모두 호환되는 __dirname 구현
|
|
8
|
+
let __dirname_resolved: string;
|
|
9
|
+
try {
|
|
10
|
+
// ESM 환경
|
|
7
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
-
|
|
12
|
+
__dirname_resolved = dirname(__filename);
|
|
13
|
+
} catch {
|
|
14
|
+
// CJS 환경 또는 번들러 환경 - process.cwd() 사용
|
|
15
|
+
__dirname_resolved = process.cwd();
|
|
16
|
+
}
|
|
9
17
|
|
|
10
18
|
/**
|
|
11
19
|
* 요청 ID 생성 (타임스탬프 + 랜덤 문자열)
|
|
@@ -20,7 +28,7 @@ export function generateRequestId(): string {
|
|
|
20
28
|
* 요청별 폴더 경로 생성
|
|
21
29
|
*/
|
|
22
30
|
export function getRequestFolderPath(requestId: string): string {
|
|
23
|
-
const projectRoot = join(
|
|
31
|
+
const projectRoot = join(__dirname_resolved, '..', '..');
|
|
24
32
|
return join(projectRoot, 'dist', 'requests', requestId);
|
|
25
33
|
}
|
|
26
34
|
|
|
@@ -29,11 +37,11 @@ export function getRequestFolderPath(requestId: string): string {
|
|
|
29
37
|
*/
|
|
30
38
|
export async function createRequestFolder(requestId: string): Promise<string> {
|
|
31
39
|
const folderPath = getRequestFolderPath(requestId);
|
|
32
|
-
|
|
40
|
+
|
|
33
41
|
if (!existsSync(folderPath)) {
|
|
34
42
|
await mkdir(folderPath, { recursive: true });
|
|
35
43
|
}
|
|
36
|
-
|
|
44
|
+
|
|
37
45
|
return folderPath;
|
|
38
46
|
}
|
|
39
47
|
|
|
@@ -87,15 +95,14 @@ export async function saveMetadata(
|
|
|
87
95
|
): Promise<string> {
|
|
88
96
|
const folderPath = await createRequestFolder(requestId);
|
|
89
97
|
const metadataPath = join(folderPath, 'metadata.json');
|
|
90
|
-
|
|
98
|
+
|
|
91
99
|
const fullMetadata: RequestMetadata = {
|
|
92
100
|
requestId,
|
|
93
101
|
createdAt: new Date().toISOString(),
|
|
94
102
|
files: [],
|
|
95
103
|
...metadata,
|
|
96
104
|
};
|
|
97
|
-
|
|
105
|
+
|
|
98
106
|
await writeFile(metadataPath, JSON.stringify(fullMetadata, null, 2), 'utf-8');
|
|
99
107
|
return metadataPath;
|
|
100
108
|
}
|
|
101
|
-
|