@ncds/ui-admin-mcp 1.6.4-alpha.6 → 1.6.4-alpha.8
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/bin/server.js +10 -15
- package/bin/tools/ping.js +0 -1
- package/bin/tools/renderToHtml.d.ts +5 -1
- package/bin/tools/renderToHtml.js +44 -52
- package/bin/types.d.ts +2 -0
- package/data/badge-group.json +2 -0
- package/data/badge.json +2 -0
- package/data/bread-crumb.json +2 -0
- package/data/breadcrumb.json +2 -0
- package/data/button-group.json +2 -0
- package/data/button.json +2 -0
- package/data/carousel-arrow.json +2 -0
- package/data/carousel-number-group.json +2 -0
- package/data/carousel.json +2 -0
- package/data/checkbox.json +2 -0
- package/data/combobox.json +2 -0
- package/data/date-picker.json +2 -0
- package/data/divider.json +2 -0
- package/data/dot.json +2 -0
- package/data/dropdown.json +2 -0
- package/data/empty-state.json +2 -0
- package/data/featured-icon.json +2 -0
- package/data/file-input.json +2 -0
- package/data/horizontal-tab.json +2 -0
- package/data/image-file-input.json +2 -0
- package/data/input.json +2 -0
- package/data/modal.json +2 -0
- package/data/notification.json +2 -0
- package/data/number-input.json +2 -0
- package/data/pagination.json +2 -0
- package/data/password-input.json +2 -0
- package/data/progress-bar.json +2 -0
- package/data/progress-circle.json +2 -0
- package/data/radio.json +2 -0
- package/data/range-date-picker-with-buttons.json +2 -0
- package/data/range-date-picker.json +2 -0
- package/data/select-dropdown.json +2 -0
- package/data/select.json +2 -0
- package/data/selectbox.json +2 -0
- package/data/slider.json +2 -0
- package/data/spinner.json +2 -0
- package/data/switch.json +2 -0
- package/data/tab.json +2 -0
- package/data/tag.json +2 -0
- package/data/textarea.json +2 -0
- package/data/toggle.json +2 -0
- package/data/tooltip.json +2 -0
- package/data/vertical-tab.json +2 -0
- package/package.json +5 -1
package/bin/server.js
CHANGED
|
@@ -14,7 +14,6 @@ const dataLoader_js_1 = require("./utils/dataLoader.js");
|
|
|
14
14
|
const logger_js_1 = require("./utils/logger.js");
|
|
15
15
|
const listComponents_js_1 = require("./tools/listComponents.js");
|
|
16
16
|
const searchComponent_js_1 = require("./tools/searchComponent.js");
|
|
17
|
-
const getComponentHtml_js_1 = require("./tools/getComponentHtml.js");
|
|
18
17
|
const getComponentProps_js_1 = require("./tools/getComponentProps.js");
|
|
19
18
|
const validateHtml_js_1 = require("./tools/validateHtml.js");
|
|
20
19
|
const ping_js_1 = require("./tools/ping.js");
|
|
@@ -31,7 +30,10 @@ const main = async () => {
|
|
|
31
30
|
const componentMap = (0, dataLoader_js_1.loadComponentsFromDir)(dataLoader_js_1.DEFAULT_DATA_DIR);
|
|
32
31
|
const cdnMeta = (0, dataLoader_js_1.loadCdnMeta)(dataLoader_js_1.DEFAULT_DATA_DIR);
|
|
33
32
|
const iconData = (0, dataLoader_js_1.loadIconData)(dataLoader_js_1.DEFAULT_DATA_DIR);
|
|
34
|
-
const bundlePath = path_1.default.resolve(__dirname, '
|
|
33
|
+
const bundlePath = path_1.default.resolve(__dirname, '..', 'bin', 'components.bundle.js');
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
35
|
+
const bundle = require(bundlePath);
|
|
36
|
+
logger_js_1.logger.info(`컴포넌트 번들 로딩 완료`);
|
|
35
37
|
server.registerTool('ping', {
|
|
36
38
|
description: 'NCUA MCP 서버 연결 확인 + 버전 + capabilities/rules 조회',
|
|
37
39
|
}, () => (0, ping_js_1.ping)(componentMap, cdnMeta, version_js_1.VERSION));
|
|
@@ -49,16 +51,6 @@ const main = async () => {
|
|
|
49
51
|
description: '이름이나 설명으로 UI 컴포넌트를 찾습니다.',
|
|
50
52
|
inputSchema: { query: zod_1.z.string().describe('검색 키워드 (대소문자 무관)') },
|
|
51
53
|
}, ({ query }) => (0, searchComponent_js_1.searchComponent)(componentMap, query));
|
|
52
|
-
server.registerTool('get_component_html', {
|
|
53
|
-
description: '컴포넌트의 HTML 마크업 예시를 가져옵니다. variant를 지정하면 해당 옵션이 적용된 HTML을 반환합니다.',
|
|
54
|
-
inputSchema: {
|
|
55
|
-
name: zod_1.z.string().min(1).describe('컴포넌트명 (예: "button", "badge")'),
|
|
56
|
-
variant: zod_1.z
|
|
57
|
-
.record(zod_1.z.string(), zod_1.z.string())
|
|
58
|
-
.optional()
|
|
59
|
-
.describe('옵션 조합 (예: { hierarchy: "primary", size: "md" })'),
|
|
60
|
-
},
|
|
61
|
-
}, ({ name, variant }) => (0, getComponentHtml_js_1.getComponentHtml)(componentMap, name, variant));
|
|
62
54
|
server.registerTool('get_component_props', {
|
|
63
55
|
description: '컴포넌트에서 사용할 수 있는 옵션(속성) 목록을 가져옵니다.',
|
|
64
56
|
inputSchema: { name: zod_1.z.string().min(1).describe('컴포넌트명 (예: "button", "badge")') },
|
|
@@ -73,12 +65,15 @@ const main = async () => {
|
|
|
73
65
|
},
|
|
74
66
|
}, ({ html }) => (0, validateHtml_js_1.validateHtml)(componentMap, html));
|
|
75
67
|
server.registerTool('render_to_html', {
|
|
76
|
-
description: '컴포넌트명과
|
|
68
|
+
description: '컴포넌트명과 속성을 전달하면 정확한 HTML을 반환합니다.',
|
|
77
69
|
inputSchema: {
|
|
78
70
|
name: zod_1.z.string().min(1).describe('컴포넌트명 (예: "button", "modal", "input")'),
|
|
79
|
-
props: zod_1.z
|
|
71
|
+
props: zod_1.z
|
|
72
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
73
|
+
.optional()
|
|
74
|
+
.describe('컴포넌트 props (예: { label: "확인", hierarchy: "primary" })'),
|
|
80
75
|
},
|
|
81
|
-
}, ({ name, props }) => (0, renderToHtml_js_1.renderToHtml)(componentMap,
|
|
76
|
+
}, ({ name, props }) => (0, renderToHtml_js_1.renderToHtml)(componentMap, bundle, cdnMeta, name, props));
|
|
82
77
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
83
78
|
await server.connect(transport);
|
|
84
79
|
logger_js_1.logger.info(`NCUA MCP 서버 시작됨 (ncds-ui-admin v${version_js_1.VERSION})`);
|
package/bin/tools/ping.js
CHANGED
|
@@ -8,7 +8,6 @@ const CAPABILITIES = [
|
|
|
8
8
|
{ tool: 'search_component', description: '키워드로 컴포넌트 검색' },
|
|
9
9
|
{ tool: 'list_icons', description: '전체 아이콘 목록 조회' },
|
|
10
10
|
{ tool: 'search_icon', description: '키워드로 아이콘 검색' },
|
|
11
|
-
{ tool: 'get_component_html', description: '컴포넌트 HTML 마크업 조회 (variant 지원)' },
|
|
12
11
|
{ tool: 'get_component_props', description: '컴포넌트 Props 스펙 조회' },
|
|
13
12
|
{ tool: 'validate_html', description: 'HTML BEM 클래스 유효성 검증 + 자동 수정' },
|
|
14
13
|
{ tool: 'render_to_html', description: 'Props 기반 동적 HTML 렌더링' },
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render_to_html tool — 컴포넌트 속성을 전달하면 정확한 HTML을 반환
|
|
3
|
+
*/
|
|
1
4
|
import type { ComponentData } from '../types.js';
|
|
5
|
+
import type { CdnMeta } from '../utils/dataLoader.js';
|
|
2
6
|
import { type McpToolResponse } from '../utils/response.js';
|
|
3
|
-
export declare const renderToHtml: (componentMap: Map<string, ComponentData>,
|
|
7
|
+
export declare const renderToHtml: (componentMap: Map<string, ComponentData>, bundle: Record<string, unknown>, cdnMeta: CdnMeta | null, name: string, props?: Record<string, unknown>) => McpToolResponse;
|
|
@@ -4,54 +4,43 @@ exports.renderToHtml = void 0;
|
|
|
4
4
|
const response_js_1 = require("../utils/response.js");
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
6
6
|
const { JSDOM } = require('jsdom');
|
|
7
|
+
let domInitialized = false;
|
|
8
|
+
let portalMocked = false;
|
|
7
9
|
/** jsdom + createPortal mock 환경을 세팅한다 */
|
|
8
10
|
const setupDomEnvironment = () => {
|
|
9
|
-
if (
|
|
11
|
+
if (!domInitialized) {
|
|
10
12
|
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
|
|
11
|
-
globalThis.document
|
|
12
|
-
|
|
13
|
-
if (typeof globalThis.window === 'undefined')
|
|
13
|
+
if (typeof globalThis.document === 'undefined')
|
|
14
|
+
globalThis.document = dom.window.document;
|
|
15
|
+
if (typeof globalThis.window === 'undefined')
|
|
14
16
|
globalThis.window = dom.window;
|
|
15
|
-
}
|
|
16
17
|
if (typeof globalThis.navigator === 'undefined') {
|
|
17
18
|
Object.defineProperty(globalThis, 'navigator', { value: dom.window.navigator, writable: true });
|
|
18
19
|
}
|
|
20
|
+
domInitialized = true;
|
|
19
21
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
if (!portalMocked) {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
24
|
+
const ReactDOM = require('react-dom');
|
|
23
25
|
ReactDOM.createPortal = (children) => children;
|
|
24
|
-
|
|
26
|
+
portalMocked = true;
|
|
25
27
|
}
|
|
26
28
|
};
|
|
27
|
-
/**
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
Input: 'InputBase',
|
|
41
|
-
PasswordInput: 'PasswordInput',
|
|
42
|
-
NumberInput: 'NumberInput',
|
|
43
|
-
Textarea: 'Textarea',
|
|
44
|
-
};
|
|
45
|
-
const special = SPECIAL_MAPPINGS[pascalName];
|
|
46
|
-
if (special && bundle[special])
|
|
47
|
-
return { Component: bundle[special], exportName: special };
|
|
48
|
-
// 대소문자 무관 검색
|
|
49
|
-
const key = Object.keys(bundle).find((k) => k.toLowerCase() === pascalName.toLowerCase());
|
|
50
|
-
if (key && bundle[key])
|
|
51
|
-
return { Component: bundle[key], exportName: key };
|
|
52
|
-
return null;
|
|
29
|
+
/** componentData.exportName으로 번들에서 컴포넌트를 찾는다 */
|
|
30
|
+
const findComponent = (bundle, exportName) => {
|
|
31
|
+
return bundle[exportName] ?? null;
|
|
32
|
+
};
|
|
33
|
+
/** componentMap의 props 스펙에서 defaultsUsed를 자동 계산 */
|
|
34
|
+
const calcDefaultsUsed = (propsSpec, userProps) => {
|
|
35
|
+
const defaults = {};
|
|
36
|
+
for (const [key, spec] of Object.entries(propsSpec)) {
|
|
37
|
+
if (spec.default !== undefined && !(key in userProps)) {
|
|
38
|
+
defaults[key] = spec.default;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return defaults;
|
|
53
42
|
};
|
|
54
|
-
const renderToHtml = (componentMap,
|
|
43
|
+
const renderToHtml = (componentMap, bundle, cdnMeta, name, props) => {
|
|
55
44
|
const normalized = (0, response_js_1.normalizeName)(name);
|
|
56
45
|
const componentData = componentMap.get(normalized);
|
|
57
46
|
if (!componentData)
|
|
@@ -61,31 +50,34 @@ const renderToHtml = (componentMap, bundlePath, name, props) => {
|
|
|
61
50
|
const React = require('react');
|
|
62
51
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
63
52
|
const { renderToStaticMarkup } = require('react-dom/server');
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
53
|
+
const exportName = componentData.exportName;
|
|
54
|
+
if (!exportName) {
|
|
55
|
+
return (0, response_js_1.errorResponse)('EXPORT_NAME_MISSING', `'${normalized}' 컴포넌트에 exportName이 없습니다.`, '데이터를 재추출해주세요 (yarn extract).');
|
|
56
|
+
}
|
|
57
|
+
const Component = findComponent(bundle, exportName);
|
|
58
|
+
if (!Component) {
|
|
59
|
+
return (0, response_js_1.errorResponse)('COMPONENT_NOT_IN_BUNDLE', `'${normalized}' (${exportName}) 컴포넌트가 번들에 없습니다.`, '번들을 재빌드해주세요 (yarn build:bundle).');
|
|
72
60
|
}
|
|
73
61
|
try {
|
|
74
|
-
const
|
|
62
|
+
const userProps = props ?? {};
|
|
63
|
+
const element = React.createElement(Component, userProps);
|
|
75
64
|
const html = renderToStaticMarkup(element);
|
|
65
|
+
const defaultsUsed = componentData.props ? calcDefaultsUsed(componentData.props, userProps) : {};
|
|
76
66
|
return (0, response_js_1.successResponse)({
|
|
77
67
|
html,
|
|
78
68
|
component: normalized,
|
|
79
|
-
exportName
|
|
69
|
+
exportName,
|
|
70
|
+
appliedProps: userProps,
|
|
71
|
+
defaultsUsed,
|
|
72
|
+
js: componentData.jsRequired
|
|
73
|
+
? { required: true, description: '이 컴포넌트는 인터랙션을 위해 CDN JS가 필요합니다' }
|
|
74
|
+
: { required: false },
|
|
75
|
+
cdn: cdnMeta ?? undefined,
|
|
80
76
|
});
|
|
81
77
|
}
|
|
82
78
|
catch (err) {
|
|
83
|
-
const
|
|
84
|
-
return (0, response_js_1.
|
|
85
|
-
error: 'RENDER_FAILED',
|
|
86
|
-
message: `'${normalized}' 렌더링 실패: ${message}`,
|
|
87
|
-
component: normalized,
|
|
88
|
-
});
|
|
79
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
80
|
+
return (0, response_js_1.errorResponse)('RENDER_FAILED', `'${normalized}' 렌더링 실패: ${errMessage}`, '사용 가능한 속성을 확인하려면 get_component_props를 사용하세요.');
|
|
89
81
|
}
|
|
90
82
|
};
|
|
91
83
|
exports.renderToHtml = renderToHtml;
|
package/bin/types.d.ts
CHANGED
package/data/badge-group.json
CHANGED
package/data/badge.json
CHANGED
package/data/bread-crumb.json
CHANGED
package/data/breadcrumb.json
CHANGED
package/data/button-group.json
CHANGED
package/data/button.json
CHANGED
package/data/carousel-arrow.json
CHANGED
package/data/carousel.json
CHANGED
package/data/checkbox.json
CHANGED
package/data/combobox.json
CHANGED
package/data/date-picker.json
CHANGED
package/data/divider.json
CHANGED
package/data/dot.json
CHANGED
package/data/dropdown.json
CHANGED
package/data/empty-state.json
CHANGED
package/data/featured-icon.json
CHANGED
package/data/file-input.json
CHANGED
package/data/horizontal-tab.json
CHANGED
package/data/input.json
CHANGED
package/data/modal.json
CHANGED
package/data/notification.json
CHANGED
package/data/number-input.json
CHANGED
package/data/pagination.json
CHANGED
package/data/password-input.json
CHANGED
package/data/progress-bar.json
CHANGED
package/data/radio.json
CHANGED
package/data/select.json
CHANGED
package/data/selectbox.json
CHANGED
package/data/slider.json
CHANGED
package/data/spinner.json
CHANGED
package/data/switch.json
CHANGED
package/data/tab.json
CHANGED
package/data/tag.json
CHANGED
package/data/textarea.json
CHANGED
package/data/toggle.json
CHANGED
package/data/tooltip.json
CHANGED
package/data/vertical-tab.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ncds/ui-admin-mcp",
|
|
3
|
-
"version": "1.6.4-alpha.
|
|
3
|
+
"version": "1.6.4-alpha.8",
|
|
4
4
|
"description": "NCDS UI Admin MCP 서버 — AI 에이전트가 NCUA 컴포넌트를 조회하고 HTML을 검증할 수 있는 MCP 서버",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ncua-mcp": "./bin/server.mjs"
|
|
@@ -35,10 +35,14 @@
|
|
|
35
35
|
"private": false,
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
38
|
+
"flatpickr": "4.6.13",
|
|
38
39
|
"jsdom": "^26.1.0",
|
|
40
|
+
"lodash-es": "^4.17.21",
|
|
41
|
+
"moment": "^2.30.1",
|
|
39
42
|
"node-html-parser": "^7.1.0",
|
|
40
43
|
"react": "^18.2.0",
|
|
41
44
|
"react-dom": "^18.2.0",
|
|
45
|
+
"swiper": "^11.1.1",
|
|
42
46
|
"zod": "^4.3.6"
|
|
43
47
|
}
|
|
44
48
|
}
|