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.
@@ -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
- * Smithery가 파일을 로드하고 createServer 함수를 호출합니다.
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, tools } from './server.js';
10
+ import { createPaletteServer } from './server.js';
10
11
 
11
- // Smithery 설정 스키마 정의
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
- .describe('Figma Personal Access Token (https://www.figma.com/developers/api#access-tokens)'),
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
- .describe('GitHub Personal Access Token for design system packages'),
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
- * Smithery에서 호출하는 서버 생성 함수
30
- *
31
- * @param config - Smithery에서 전달받은 사용자 설정
32
- * @returns MCP 서버 인스턴스
55
+ * MCP 서버 생성 함수 - Smithery가 호출
33
56
  */
34
- export default function createServer({ config }: { config: SmitheryConfig }) {
35
- // 환경변수 설정
36
- process.env.FIGMA_ACCESS_TOKEN = config.FIGMA_ACCESS_TOKEN;
37
- process.env.GITHUB_TOKEN = config.GITHUB_TOKEN;
38
- process.env.FIGMA_MCP_SERVER_URL = config.FIGMA_MCP_SERVER_URL;
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: config.FIGMA_ACCESS_TOKEN,
43
- githubToken: config.GITHUB_TOKEN,
44
- figmaMcpServerUrl: config.FIGMA_MCP_SERVER_URL,
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
- // Tools 정보도 export (Smithery capabilities 탐지에 사용할 수 있음)
54
- export { tools };
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
- const __dirname = dirname(__filename);
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(__dirname, '..', '..');
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
-