@ncds/ui-admin-mcp 1.6.4-alpha.2

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.
Files changed (72) hide show
  1. package/bin/server.d.ts +1 -0
  2. package/bin/server.js +67 -0
  3. package/bin/server.mjs +8 -0
  4. package/bin/tools/getComponentHtml.d.ts +3 -0
  5. package/bin/tools/getComponentHtml.js +30 -0
  6. package/bin/tools/getComponentProps.d.ts +3 -0
  7. package/bin/tools/getComponentProps.js +16 -0
  8. package/bin/tools/listComponents.d.ts +3 -0
  9. package/bin/tools/listComponents.js +15 -0
  10. package/bin/tools/ping.d.ts +11 -0
  11. package/bin/tools/ping.js +30 -0
  12. package/bin/tools/searchComponent.d.ts +3 -0
  13. package/bin/tools/searchComponent.js +20 -0
  14. package/bin/tools/validateHtml.d.ts +10 -0
  15. package/bin/tools/validateHtml.js +142 -0
  16. package/bin/types.d.ts +28 -0
  17. package/bin/types.js +5 -0
  18. package/bin/utils/dataLoader.d.ts +11 -0
  19. package/bin/utils/dataLoader.js +68 -0
  20. package/bin/utils/logger.d.ts +18 -0
  21. package/bin/utils/logger.js +27 -0
  22. package/bin/utils/response.d.ts +23 -0
  23. package/bin/utils/response.js +31 -0
  24. package/bin/version.d.ts +1 -0
  25. package/bin/version.js +4 -0
  26. package/data/_meta.json +7 -0
  27. package/data/badge-group.json +81 -0
  28. package/data/badge.json +101 -0
  29. package/data/bread-crumb.json +22 -0
  30. package/data/breadcrumb.json +31 -0
  31. package/data/button-group.json +70 -0
  32. package/data/button.json +119 -0
  33. package/data/carousel-arrow.json +65 -0
  34. package/data/carousel-number-group.json +62 -0
  35. package/data/carousel.json +22 -0
  36. package/data/checkbox.json +31 -0
  37. package/data/combobox.json +117 -0
  38. package/data/date-picker.json +80 -0
  39. package/data/divider.json +66 -0
  40. package/data/dot.json +67 -0
  41. package/data/dropdown.json +84 -0
  42. package/data/empty-state.json +34 -0
  43. package/data/featured-icon.json +98 -0
  44. package/data/file-input.json +76 -0
  45. package/data/horizontal-tab.json +80 -0
  46. package/data/image-file-input.json +97 -0
  47. package/data/input.json +107 -0
  48. package/data/modal.json +91 -0
  49. package/data/notification.json +126 -0
  50. package/data/number-input.json +134 -0
  51. package/data/pagination.json +68 -0
  52. package/data/password-input.json +47 -0
  53. package/data/progress-bar.json +56 -0
  54. package/data/progress-circle.json +63 -0
  55. package/data/radio.json +31 -0
  56. package/data/range-date-picker-with-buttons.json +64 -0
  57. package/data/range-date-picker.json +58 -0
  58. package/data/select-dropdown.json +32 -0
  59. package/data/select.json +77 -0
  60. package/data/selectbox.json +139 -0
  61. package/data/slider.json +62 -0
  62. package/data/spinner.json +60 -0
  63. package/data/switch.json +71 -0
  64. package/data/tab.json +140 -0
  65. package/data/tag.json +69 -0
  66. package/data/textarea.json +91 -0
  67. package/data/toggle.json +62 -0
  68. package/data/tooltip.json +148 -0
  69. package/data/vertical-tab.json +71 -0
  70. package/package.json +35 -0
  71. package/templates/.mcp.json.example +8 -0
  72. package/templates/README.md +31 -0
@@ -0,0 +1 @@
1
+ export {};
package/bin/server.js ADDED
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * MCP 서버 진입점 — componentMap 소유, tool 등록, transport 연결
5
+ */
6
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
7
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
8
+ const zod_1 = require("zod");
9
+ const dataLoader_js_1 = require("./utils/dataLoader.js");
10
+ const logger_js_1 = require("./utils/logger.js");
11
+ const listComponents_js_1 = require("./tools/listComponents.js");
12
+ const searchComponent_js_1 = require("./tools/searchComponent.js");
13
+ const getComponentHtml_js_1 = require("./tools/getComponentHtml.js");
14
+ const getComponentProps_js_1 = require("./tools/getComponentProps.js");
15
+ const validateHtml_js_1 = require("./tools/validateHtml.js");
16
+ const ping_js_1 = require("./tools/ping.js");
17
+ const version_js_1 = require("./version.js");
18
+ const server = new mcp_js_1.McpServer({
19
+ name: 'ncds-ui-admin',
20
+ version: version_js_1.VERSION,
21
+ });
22
+ const main = async () => {
23
+ // server.ts가 componentMap 소유 — tool 핸들러에 파라미터로 전달
24
+ const componentMap = (0, dataLoader_js_1.loadComponentsFromDir)(dataLoader_js_1.DEFAULT_DATA_DIR);
25
+ const cdnMeta = (0, dataLoader_js_1.loadCdnMeta)(dataLoader_js_1.DEFAULT_DATA_DIR);
26
+ server.registerTool('ping', {
27
+ description: 'NCUA MCP 서버 연결 확인 + 버전 + capabilities/rules 조회',
28
+ }, () => (0, ping_js_1.ping)(componentMap, cdnMeta, version_js_1.VERSION));
29
+ server.registerTool('list_components', {
30
+ description: '사용 가능한 전체 UI 컴포넌트 목록을 가져옵니다.',
31
+ }, () => (0, listComponents_js_1.listComponents)(componentMap));
32
+ server.registerTool('search_component', {
33
+ description: '이름이나 설명으로 UI 컴포넌트를 찾습니다.',
34
+ inputSchema: { query: zod_1.z.string().describe('검색 키워드 (대소문자 무관)') },
35
+ }, ({ query }) => (0, searchComponent_js_1.searchComponent)(componentMap, query));
36
+ server.registerTool('get_component_html', {
37
+ description: '컴포넌트의 HTML 마크업 예시를 가져옵니다. variant를 지정하면 해당 옵션이 적용된 HTML을 반환합니다.',
38
+ inputSchema: {
39
+ name: zod_1.z.string().min(1).describe('컴포넌트명 (예: "button", "badge")'),
40
+ variant: zod_1.z
41
+ .record(zod_1.z.string(), zod_1.z.string())
42
+ .optional()
43
+ .describe('옵션 조합 (예: { hierarchy: "primary", size: "md" })'),
44
+ },
45
+ }, ({ name, variant }) => (0, getComponentHtml_js_1.getComponentHtml)(componentMap, name, variant));
46
+ server.registerTool('get_component_props', {
47
+ description: '컴포넌트에서 사용할 수 있는 옵션(속성) 목록을 가져옵니다.',
48
+ inputSchema: { name: zod_1.z.string().min(1).describe('컴포넌트명 (예: "button", "badge")') },
49
+ }, ({ name }) => (0, getComponentProps_js_1.getComponentProps)(componentMap, name));
50
+ server.registerTool('validate_html', {
51
+ description: 'HTML 마크업에 사용된 컴포넌트 클래스가 유효한지 검증합니다. 잘못된 클래스 사용 시 오류 위치와 자동 수정본을 반환합니다.',
52
+ inputSchema: {
53
+ html: zod_1.z
54
+ .string()
55
+ .min(1)
56
+ .describe('검증할 HTML 마크업 (예: \'<button class="ncua-btn ncua-btn--primary"></button>\')'),
57
+ },
58
+ }, ({ html }) => (0, validateHtml_js_1.validateHtml)(componentMap, html));
59
+ const transport = new stdio_js_1.StdioServerTransport();
60
+ await server.connect(transport);
61
+ logger_js_1.logger.info(`NCUA MCP 서버 시작됨 (ncds-ui-admin v${version_js_1.VERSION})`);
62
+ };
63
+ main().catch((err) => {
64
+ const message = err instanceof Error ? err.message : String(err);
65
+ process.stderr.write(`[ERROR] 서버 시작 실패: ${message}\n`);
66
+ process.exit(1);
67
+ });
package/bin/server.mjs ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * NCUA MCP Server Entry Point
4
+ *
5
+ * @ncds/ui-admin-mcp 패키지의 진입점.
6
+ * npx @ncds/ui-admin-mcp 으로 실행됩니다.
7
+ */
8
+ import './server.js';
@@ -0,0 +1,3 @@
1
+ import type { ComponentData } from '../types.js';
2
+ import { type McpToolResponse } from '../utils/response.js';
3
+ export declare const getComponentHtml: (componentMap: Map<string, ComponentData>, name: string, variant?: Record<string, string>) => McpToolResponse;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getComponentHtml = void 0;
4
+ /**
5
+ * get_component_html tool — 컴포넌트 HTML 스니펫 반환 (순수 함수)
6
+ */
7
+ const dataLoader_js_1 = require("../utils/dataLoader.js");
8
+ const response_js_1 = require("../utils/response.js");
9
+ /** default HTML에서 태그명 추출 */
10
+ const extractTag = (defaultHtml) => {
11
+ const match = defaultHtml.match(/^<(\w+)/);
12
+ return match ? match[1] : 'div';
13
+ };
14
+ const getComponentHtml = (componentMap, name, variant) => {
15
+ const normalized = (0, response_js_1.normalizeName)(name);
16
+ const component = (0, dataLoader_js_1.getComponent)(componentMap, normalized);
17
+ if (!component)
18
+ return (0, response_js_1.componentNotFoundResponse)(normalized);
19
+ if (!variant || Object.keys(variant).length === 0) {
20
+ return (0, response_js_1.successResponse)(component.html);
21
+ }
22
+ const rootClass = component.bemClasses.find((c) => !c.includes('--') && !c.includes('__')) ?? `ncua-${normalized}`;
23
+ const tag = extractTag(component.html.default);
24
+ const modifiers = Object.values(variant).map((v) => `${rootClass}--${v}`);
25
+ const classes = [rootClass, ...modifiers].join(' ');
26
+ const isVoid = /^<\w+\s[^>]*\/>$/.test(component.html.default);
27
+ const html = isVoid ? `<${tag} class="${classes}" />` : `<${tag} class="${classes}"></${tag}>`;
28
+ return (0, response_js_1.successResponse)({ variant, html });
29
+ };
30
+ exports.getComponentHtml = getComponentHtml;
@@ -0,0 +1,3 @@
1
+ import type { ComponentData } from '../types.js';
2
+ import { type McpToolResponse } from '../utils/response.js';
3
+ export declare const getComponentProps: (componentMap: Map<string, ComponentData>, name: string) => McpToolResponse;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getComponentProps = void 0;
4
+ /**
5
+ * get_component_props tool — 컴포넌트 props 스펙 반환 (순수 함수)
6
+ */
7
+ const dataLoader_js_1 = require("../utils/dataLoader.js");
8
+ const response_js_1 = require("../utils/response.js");
9
+ const getComponentProps = (componentMap, name) => {
10
+ const normalized = (0, response_js_1.normalizeName)(name);
11
+ const component = (0, dataLoader_js_1.getComponent)(componentMap, normalized);
12
+ if (!component)
13
+ return (0, response_js_1.componentNotFoundResponse)(normalized);
14
+ return (0, response_js_1.successResponse)(component.props);
15
+ };
16
+ exports.getComponentProps = getComponentProps;
@@ -0,0 +1,3 @@
1
+ import type { ComponentData } from '../types.js';
2
+ import { type McpToolResponse } from '../utils/response.js';
3
+ export declare const listComponents: (componentMap: Map<string, ComponentData>) => McpToolResponse;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listComponents = void 0;
4
+ /**
5
+ * list_components tool — 전체 컴포넌트 이름 목록 반환 (순수 함수)
6
+ */
7
+ const dataLoader_js_1 = require("../utils/dataLoader.js");
8
+ const response_js_1 = require("../utils/response.js");
9
+ const listComponents = (componentMap) => {
10
+ const names = (0, dataLoader_js_1.getAllComponents)(componentMap)
11
+ .map((c) => c.name)
12
+ .sort();
13
+ return (0, response_js_1.successResponse)(names);
14
+ };
15
+ exports.listComponents = listComponents;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ping tool — NCUA MCP 서버 health check + capabilities/rules 제공 (순수 함수)
3
+ */
4
+ import type { ComponentData } from '../types.js';
5
+ import type { CdnMeta } from '../utils/dataLoader.js';
6
+ import { type McpToolResponse } from '../utils/response.js';
7
+ export interface Capability {
8
+ tool: string;
9
+ description: string;
10
+ }
11
+ export declare const ping: (componentMap: Map<string, ComponentData>, cdnMeta: CdnMeta | null, version: string) => McpToolResponse;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ping = void 0;
4
+ const response_js_1 = require("../utils/response.js");
5
+ const CAPABILITIES = [
6
+ { tool: 'ping', description: '서버 연결 확인 + 버전 + capabilities/rules 조회' },
7
+ { tool: 'list_components', description: '전체 컴포넌트 목록 조회' },
8
+ { tool: 'search_component', description: '키워드로 컴포넌트 검색' },
9
+ { tool: 'get_component_html', description: '컴포넌트 HTML 마크업 조회 (variant 지원)' },
10
+ { tool: 'get_component_props', description: '컴포넌트 Props 스펙 조회' },
11
+ { tool: 'validate_html', description: 'HTML BEM 클래스 유효성 검증 + 자동 수정' },
12
+ ];
13
+ const RULES = [
14
+ '이 라이브러리의 컴포넌트를 사용할 때는 CDN CSS/JS를 포함하세요',
15
+ 'HTML 스니펫은 get_component_html에서 받은 구조를 그대로 사용하세요',
16
+ '커스텀 CSS로 ncua- 클래스를 재정의하지 마세요',
17
+ ];
18
+ const ping = (componentMap, cdnMeta, version) => {
19
+ return (0, response_js_1.successResponse)({
20
+ status: 'ok',
21
+ name: 'ncds-ui-admin',
22
+ description: 'NCDS UI Admin 컴포넌트 라이브러리',
23
+ version,
24
+ componentCount: componentMap.size,
25
+ cdn: cdnMeta ?? undefined,
26
+ capabilities: CAPABILITIES,
27
+ rules: RULES,
28
+ });
29
+ };
30
+ exports.ping = ping;
@@ -0,0 +1,3 @@
1
+ import type { ComponentData } from '../types.js';
2
+ import { type McpToolResponse } from '../utils/response.js';
3
+ export declare const searchComponent: (componentMap: Map<string, ComponentData>, query: string) => McpToolResponse;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.searchComponent = void 0;
4
+ /**
5
+ * search_component tool — name/description/aliases 키워드 검색 (순수 함수)
6
+ */
7
+ const dataLoader_js_1 = require("../utils/dataLoader.js");
8
+ const response_js_1 = require("../utils/response.js");
9
+ const searchComponent = (componentMap, query) => {
10
+ const lower = (0, response_js_1.normalizeName)(query);
11
+ if (!lower)
12
+ return (0, response_js_1.successResponse)([]);
13
+ const results = (0, dataLoader_js_1.getAllComponents)(componentMap)
14
+ .filter((c) => c.name.toLowerCase().includes(lower) ||
15
+ c.description.toLowerCase().includes(lower) ||
16
+ c.aliases.some((a) => a.toLowerCase().includes(lower)))
17
+ .map(({ name, description, aliases }) => ({ name, description, aliases }));
18
+ return (0, response_js_1.successResponse)(results);
19
+ };
20
+ exports.searchComponent = searchComponent;
@@ -0,0 +1,10 @@
1
+ import type { ComponentData } from '../types.js';
2
+ import { type McpToolResponse } from '../utils/response.js';
3
+ export interface ValidationError {
4
+ type: 'invalid_modifier' | 'invalid_element' | 'unknown_component';
5
+ class: string;
6
+ component: string;
7
+ message: string;
8
+ suggestion: string;
9
+ }
10
+ export declare const validateHtml: (componentMap: Map<string, ComponentData>, html: string) => McpToolResponse;
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateHtml = void 0;
4
+ /**
5
+ * validate_html tool — NCUA HTML 마크업 검증 (순수 함수)
6
+ */
7
+ const node_html_parser_1 = require("node-html-parser");
8
+ const response_js_1 = require("../utils/response.js");
9
+ // ── 순수 함수 (헬퍼) ──────────────────────────────────────────────────────
10
+ /** root class → component name 매핑 빌드 */
11
+ const buildRootClassMap = (componentMap) => {
12
+ const rootClassMap = new Map();
13
+ for (const [name, data] of componentMap) {
14
+ for (const cls of data.bemClasses) {
15
+ if (!cls.includes('--') && !cls.includes('__')) {
16
+ rootClassMap.set(cls, name);
17
+ }
18
+ }
19
+ }
20
+ return rootClassMap;
21
+ };
22
+ /** ncua- 클래스에서 root class를 결정 (modifier/element 제외, 가장 짧은 것) */
23
+ const findRootClass = (ncuaClasses) => {
24
+ const roots = ncuaClasses.filter((c) => !c.includes('--') && !c.includes('__'));
25
+ if (roots.length === 0)
26
+ return undefined;
27
+ return roots.sort((a, b) => a.length - b.length)[0];
28
+ };
29
+ /** modifier/element 클래스에서 root class를 추론 (예: ncua-btn--primary → ncua-btn) */
30
+ const inferRootClass = (ncuaClasses, rootClassMap) => {
31
+ for (const cls of ncuaClasses) {
32
+ const dashIdx = cls.indexOf('--');
33
+ const underIdx = cls.indexOf('__');
34
+ const sepIdx = dashIdx >= 0 && underIdx >= 0 ? Math.min(dashIdx, underIdx) : dashIdx >= 0 ? dashIdx : underIdx;
35
+ if (sepIdx > 0) {
36
+ const candidate = cls.substring(0, sepIdx);
37
+ if (rootClassMap.has(candidate))
38
+ return candidate;
39
+ }
40
+ }
41
+ return undefined;
42
+ };
43
+ /** 문자열 유사도 — 공통 접두사 길이 기반 */
44
+ const similarity = (a, b) => {
45
+ let i = 0;
46
+ while (i < a.length && i < b.length && a[i] === b[i])
47
+ i++;
48
+ return i;
49
+ };
50
+ /** 유사도 상위 N개 제안 — modifier 오류면 modifier만, element 오류면 element만 */
51
+ const suggestSimilar = (invalidClass, validClasses, max = 3) => {
52
+ const isElement = invalidClass.includes('__');
53
+ return validClasses
54
+ .filter((c) => (isElement ? c.includes('__') : c.includes('--')))
55
+ .map((c) => ({ class: c, score: similarity(invalidClass, c) }))
56
+ .sort((a, b) => b.score - a.score)
57
+ .slice(0, max)
58
+ .map((item) => item.class);
59
+ };
60
+ // ── 순수 함수 (검증 로직) ──────────────────────────────────────────────────
61
+ /** HTML에서 ncua- 클래스 포함 여부 확인 */
62
+ const hasNcuaClasses = (elements) => elements.some((el) => {
63
+ const classes = el.getAttribute('class')?.split(/\s+/) ?? [];
64
+ return classes.some((c) => c.startsWith('ncua-'));
65
+ });
66
+ /** 엘리먼트별 BEM 클래스 검증 → errors + invalidClasses 수집 */
67
+ const collectErrors = (elements, rootClassMap, componentMap) => {
68
+ const errors = [];
69
+ const invalidClassesSet = new Set();
70
+ for (const el of elements) {
71
+ const classes = el.getAttribute('class')?.split(/\s+/) ?? [];
72
+ const ncuaClasses = classes.filter((c) => c.startsWith('ncua-'));
73
+ if (ncuaClasses.length === 0)
74
+ continue;
75
+ let rootClass = findRootClass(ncuaClasses);
76
+ if (!rootClass || !rootClassMap.has(rootClass)) {
77
+ const inferred = inferRootClass(ncuaClasses, rootClassMap);
78
+ if (inferred)
79
+ rootClass = inferred;
80
+ }
81
+ if (!rootClass || !rootClassMap.has(rootClass)) {
82
+ for (const cls of ncuaClasses) {
83
+ errors.push({
84
+ type: 'unknown_component',
85
+ class: cls,
86
+ component: 'unknown',
87
+ message: `Unknown component class '${cls}'. No matching component found.`,
88
+ suggestion: 'Use list_components to see all available components.',
89
+ });
90
+ invalidClassesSet.add(cls);
91
+ }
92
+ continue;
93
+ }
94
+ const componentName = rootClassMap.get(rootClass);
95
+ const component = componentMap.get(componentName);
96
+ const validSet = new Set(component.bemClasses);
97
+ for (const cls of ncuaClasses) {
98
+ if (!validSet.has(cls)) {
99
+ const similar = suggestSimilar(cls, component.bemClasses);
100
+ errors.push({
101
+ type: cls.includes('__') ? 'invalid_element' : 'invalid_modifier',
102
+ class: cls,
103
+ component: componentName,
104
+ message: `Invalid BEM class '${cls}' for component '${componentName}'.`,
105
+ suggestion: similar.length > 0 ? `Similar: ${similar.join(', ')}` : 'Use get_component_html to see valid classes.',
106
+ });
107
+ invalidClassesSet.add(cls);
108
+ }
109
+ }
110
+ }
111
+ return { errors, invalidClassesSet };
112
+ };
113
+ /** 잘못된 클래스를 제거하여 fixed_html 생성 */
114
+ const buildFixedHtml = (root, invalidClassesSet) => {
115
+ for (const el of root.querySelectorAll('[class]')) {
116
+ const classes = el.getAttribute('class')?.split(/\s+/) ?? [];
117
+ const filtered = classes.filter((c) => !invalidClassesSet.has(c));
118
+ if (filtered.length > 0) {
119
+ el.setAttribute('class', filtered.join(' '));
120
+ }
121
+ else {
122
+ el.removeAttribute('class');
123
+ }
124
+ }
125
+ return root.toString();
126
+ };
127
+ // ── 진입점 ────────────────────────────────────────────────────────────────
128
+ const validateHtml = (componentMap, html) => {
129
+ const trimmed = html.trim();
130
+ if (trimmed.length === 0)
131
+ return (0, response_js_1.successResponse)({ valid: true });
132
+ const root = (0, node_html_parser_1.parse)(trimmed);
133
+ const elements = root.querySelectorAll('[class]');
134
+ if (!hasNcuaClasses(elements))
135
+ return (0, response_js_1.successResponse)({ valid: true });
136
+ const rootClassMap = buildRootClassMap(componentMap);
137
+ const { errors, invalidClassesSet } = collectErrors(elements, rootClassMap, componentMap);
138
+ if (errors.length === 0)
139
+ return (0, response_js_1.successResponse)({ valid: true });
140
+ return (0, response_js_1.successResponse)({ valid: false, errors, fixed_html: buildFixedHtml(root, invalidClassesSet) });
141
+ };
142
+ exports.validateHtml = validateHtml;
package/bin/types.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * MCP 서버 공통 타입 정의
3
+ */
4
+ export interface PropSpec {
5
+ type: 'string' | 'enum' | 'boolean' | 'number' | 'ReactNode';
6
+ required: boolean;
7
+ default?: unknown;
8
+ values?: string[];
9
+ }
10
+ export interface ComponentUsage {
11
+ import: string;
12
+ react: Record<string, string>;
13
+ }
14
+ export interface ComponentData {
15
+ name: string;
16
+ description: string;
17
+ aliases: string[];
18
+ props: Record<string, PropSpec>;
19
+ html: Record<string, string>;
20
+ bemClasses: string[];
21
+ usage: ComponentUsage;
22
+ }
23
+ export type McpErrorCode = 'COMPONENT_NOT_FOUND' | 'INVALID_PROP' | 'INVALID_HTML' | 'MISSING_REQUIRED_PROP' | 'DATA_LOAD_ERROR';
24
+ export interface McpErrorResponse {
25
+ code: McpErrorCode;
26
+ message: string;
27
+ suggestion?: string;
28
+ }
package/bin/types.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * MCP 서버 공통 타입 정의
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,11 @@
1
+ import type { ComponentData } from '../types.js';
2
+ export declare const DEFAULT_DATA_DIR: string;
3
+ export declare const loadComponentsFromDir: (dataDir: string) => Map<string, ComponentData>;
4
+ export declare const getComponent: (map: Map<string, ComponentData>, name: string) => ComponentData | undefined;
5
+ export declare const getAllComponents: (map: Map<string, ComponentData>) => ComponentData[];
6
+ export interface CdnMeta {
7
+ version: string;
8
+ css: string;
9
+ js: string;
10
+ }
11
+ export declare const loadCdnMeta: (dataDir: string) => CdnMeta | null;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadCdnMeta = exports.getAllComponents = exports.getComponent = exports.loadComponentsFromDir = exports.DEFAULT_DATA_DIR = void 0;
7
+ /**
8
+ * dataLoader — mcp/data/*.json 파일을 읽어 Map으로 반환
9
+ *
10
+ * 함수 분류:
11
+ * loadComponentsFromDir → 부수효과 (fs I/O, logger, process.exit)
12
+ * getComponent → 순수 함수 (Map 조회, 외부 상태 없음)
13
+ * getAllComponents → 순수 함수 (Map 전체 반환, 외부 상태 없음)
14
+ *
15
+ * Map 소유권은 server.ts가 가진다. 이 파일은 상태를 보유하지 않는다.
16
+ */
17
+ const fs_1 = __importDefault(require("fs"));
18
+ const path_1 = __importDefault(require("path"));
19
+ const logger_js_1 = require("./logger.js");
20
+ exports.DEFAULT_DATA_DIR = path_1.default.resolve(__dirname, '../../data');
21
+ // ── 부수효과 함수 (fs I/O) ────────────────────────────────────────────────
22
+ const loadComponentsFromDir = (dataDir) => {
23
+ if (!fs_1.default.existsSync(dataDir)) {
24
+ logger_js_1.logger.error(`mcp/data/ 디렉토리가 없습니다: ${dataDir}`);
25
+ process.exit(1);
26
+ }
27
+ const jsonFiles = fs_1.default.readdirSync(dataDir).filter((f) => f.endsWith('.json') && !f.startsWith('_'));
28
+ if (jsonFiles.length === 0) {
29
+ logger_js_1.logger.error(`mcp/data/ 디렉토리에 JSON 파일이 없습니다: ${dataDir}`);
30
+ process.exit(1);
31
+ }
32
+ const map = new Map();
33
+ for (const file of jsonFiles) {
34
+ const filePath = path_1.default.join(dataDir, file);
35
+ try {
36
+ const raw = fs_1.default.readFileSync(filePath, 'utf-8');
37
+ const component = JSON.parse(raw);
38
+ map.set(component.name, component);
39
+ }
40
+ catch (err) {
41
+ const message = err instanceof Error ? err.message : String(err);
42
+ logger_js_1.logger.error(`JSON 파싱 실패 (${file}): ${message}`);
43
+ process.exit(1);
44
+ }
45
+ }
46
+ logger_js_1.logger.info(`컴포넌트 ${map.size}개 로딩 완료`);
47
+ return map;
48
+ };
49
+ exports.loadComponentsFromDir = loadComponentsFromDir;
50
+ // ── 순수 함수 (외부 상태 없음, Map을 파라미터로 받음) ──────────────────────
51
+ const getComponent = (map, name) => map.get(name);
52
+ exports.getComponent = getComponent;
53
+ const getAllComponents = (map) => Array.from(map.values());
54
+ exports.getAllComponents = getAllComponents;
55
+ const loadCdnMeta = (dataDir) => {
56
+ const metaPath = path_1.default.join(dataDir, '_meta.json');
57
+ if (!fs_1.default.existsSync(metaPath))
58
+ return null;
59
+ try {
60
+ const raw = fs_1.default.readFileSync(metaPath, 'utf-8');
61
+ const meta = JSON.parse(raw);
62
+ return meta.cdn ?? null;
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ };
68
+ exports.loadCdnMeta = loadCdnMeta;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * stderr 전용 logger 유틸리티 — 부수효과 함수 (stderr I/O)
3
+ *
4
+ * ⚠️ console.log / console.warn / console.error 직접 사용 금지
5
+ *
6
+ * MCP stdio 프로토콜은 채널 역할이 엄격히 분리됩니다.
7
+ * stdin → 클라이언트 → 서버 요청(JSON-RPC)
8
+ * stdout → 서버 → 클라이언트 응답(JSON-RPC) ← 프로토콜 전용 채널
9
+ * stderr → 로그 전용 (프로토콜 밖)
10
+ *
11
+ * console.log는 stdout에 출력되어 JSON-RPC 스트림을 오염시킵니다.
12
+ * 모든 로깅은 반드시 이 logger를 통해 stderr로 출력해야 합니다.
13
+ */
14
+ export declare const logger: {
15
+ info: (msg: string) => void;
16
+ warn: (msg: string) => void;
17
+ error: (msg: string) => void;
18
+ };
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logger = void 0;
4
+ /**
5
+ * stderr 전용 logger 유틸리티 — 부수효과 함수 (stderr I/O)
6
+ *
7
+ * ⚠️ console.log / console.warn / console.error 직접 사용 금지
8
+ *
9
+ * MCP stdio 프로토콜은 채널 역할이 엄격히 분리됩니다.
10
+ * stdin → 클라이언트 → 서버 요청(JSON-RPC)
11
+ * stdout → 서버 → 클라이언트 응답(JSON-RPC) ← 프로토콜 전용 채널
12
+ * stderr → 로그 전용 (프로토콜 밖)
13
+ *
14
+ * console.log는 stdout에 출력되어 JSON-RPC 스트림을 오염시킵니다.
15
+ * 모든 로깅은 반드시 이 logger를 통해 stderr로 출력해야 합니다.
16
+ */
17
+ exports.logger = {
18
+ info: (msg) => {
19
+ process.stderr.write(`[INFO] ${msg}\n`);
20
+ },
21
+ warn: (msg) => {
22
+ process.stderr.write(`[WARN] ${msg}\n`);
23
+ },
24
+ error: (msg) => {
25
+ process.stderr.write(`[ERROR] ${msg}\n`);
26
+ },
27
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * MCP tool 응답 헬퍼 — 순수 함수
3
+ *
4
+ * 모든 tool이 동일한 응답 구조를 사용하도록 통일합니다.
5
+ * AI가 tool 코드를 읽을 때 응답 구조를 이 파일 하나로 파악할 수 있습니다.
6
+ */
7
+ /** MCP tool 응답 타입 — registerTool 콜백 반환 타입과 호환 */
8
+ export type McpToolResponse = {
9
+ [key: string]: unknown;
10
+ content: Array<{
11
+ type: 'text';
12
+ text: string;
13
+ }>;
14
+ isError?: true;
15
+ };
16
+ /** 성공 응답 — data를 JSON.stringify하여 반환 */
17
+ export declare const successResponse: (data: unknown) => McpToolResponse;
18
+ /** 오류 응답 — code + message + suggestion 구조로 isError: true 반환 */
19
+ export declare const errorResponse: (code: string, message: string, suggestion: string) => McpToolResponse;
20
+ /** COMPONENT_NOT_FOUND 오류 — 여러 tool에서 공통 사용 */
21
+ export declare const componentNotFoundResponse: (name: string) => McpToolResponse;
22
+ /** 입력 정규화 — name.trim().toLowerCase() */
23
+ export declare const normalizeName: (name: string) => string;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ /**
3
+ * MCP tool 응답 헬퍼 — 순수 함수
4
+ *
5
+ * 모든 tool이 동일한 응답 구조를 사용하도록 통일합니다.
6
+ * AI가 tool 코드를 읽을 때 응답 구조를 이 파일 하나로 파악할 수 있습니다.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.normalizeName = exports.componentNotFoundResponse = exports.errorResponse = exports.successResponse = void 0;
10
+ /** 성공 응답 — data를 JSON.stringify하여 반환 */
11
+ const successResponse = (data) => ({
12
+ content: [{ type: 'text', text: JSON.stringify(data) }],
13
+ });
14
+ exports.successResponse = successResponse;
15
+ /** 오류 응답 — code + message + suggestion 구조로 isError: true 반환 */
16
+ const errorResponse = (code, message, suggestion) => ({
17
+ content: [
18
+ {
19
+ type: 'text',
20
+ text: JSON.stringify({ code, message, suggestion }),
21
+ },
22
+ ],
23
+ isError: true,
24
+ });
25
+ exports.errorResponse = errorResponse;
26
+ /** COMPONENT_NOT_FOUND 오류 — 여러 tool에서 공통 사용 */
27
+ const componentNotFoundResponse = (name) => (0, exports.errorResponse)('COMPONENT_NOT_FOUND', `Component '${name}' not found.`, 'Use search_component to find similar components, or list_components to see all available.');
28
+ exports.componentNotFoundResponse = componentNotFoundResponse;
29
+ /** 입력 정규화 — name.trim().toLowerCase() */
30
+ const normalizeName = (name) => name.trim().toLowerCase();
31
+ exports.normalizeName = normalizeName;
@@ -0,0 +1 @@
1
+ export declare const VERSION = "1.6.4-alpha.2";
package/bin/version.js ADDED
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VERSION = void 0;
4
+ exports.VERSION = '1.6.4-alpha.2';
@@ -0,0 +1,7 @@
1
+ {
2
+ "cdn": {
3
+ "version": "1.6",
4
+ "css": "https://fe-sdk.cdn-nhncommerce.com/@ncds/ui-admin/1.6/main.min.css",
5
+ "js": "https://fe-sdk.cdn-nhncommerce.com/@ncds/ui-admin/1.6/main.min.js"
6
+ }
7
+ }