@ncds/ui-admin-mcp 1.0.0-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 (97) hide show
  1. package/README.md +113 -0
  2. package/bin/components.bundle.js +21 -0
  3. package/bin/definitions/compliance-rules.json +64 -0
  4. package/bin/definitions/instructions.md +96 -0
  5. package/bin/definitions/rules.json +57 -0
  6. package/bin/definitions/token-descriptions.json +27 -0
  7. package/bin/definitions/tool-definitions.json +42 -0
  8. package/bin/instructions.d.ts +1 -0
  9. package/bin/instructions.js +14 -0
  10. package/bin/server.d.ts +1 -0
  11. package/bin/server.js +164 -0
  12. package/bin/server.mjs +8 -0
  13. package/bin/tools/getComponentHtml.d.ts +3 -0
  14. package/bin/tools/getComponentHtml.js +30 -0
  15. package/bin/tools/getComponentProps.d.ts +4 -0
  16. package/bin/tools/getComponentProps.js +17 -0
  17. package/bin/tools/getDesignTokens.d.ts +13 -0
  18. package/bin/tools/getDesignTokens.js +20 -0
  19. package/bin/tools/listComponents.d.ts +16 -0
  20. package/bin/tools/listComponents.js +24 -0
  21. package/bin/tools/listIcons.d.ts +22 -0
  22. package/bin/tools/listIcons.js +23 -0
  23. package/bin/tools/ping.d.ts +17 -0
  24. package/bin/tools/ping.js +20 -0
  25. package/bin/tools/renderToHtml.d.ts +21 -0
  26. package/bin/tools/renderToHtml.js +177 -0
  27. package/bin/tools/searchComponent.d.ts +4 -0
  28. package/bin/tools/searchComponent.js +33 -0
  29. package/bin/tools/searchIcon.d.ts +7 -0
  30. package/bin/tools/searchIcon.js +19 -0
  31. package/bin/tools/validateHtml.d.ts +18 -0
  32. package/bin/tools/validateHtml.js +85 -0
  33. package/bin/types.d.ts +111 -0
  34. package/bin/types.js +5 -0
  35. package/bin/utils/bemValidator.d.ts +36 -0
  36. package/bin/utils/bemValidator.js +198 -0
  37. package/bin/utils/compliance.d.ts +52 -0
  38. package/bin/utils/compliance.js +199 -0
  39. package/bin/utils/dataLoader.d.ts +33 -0
  40. package/bin/utils/dataLoader.js +174 -0
  41. package/bin/utils/domEnvironment.d.ts +9 -0
  42. package/bin/utils/domEnvironment.js +25 -0
  43. package/bin/utils/fuzzyMatch.d.ts +21 -0
  44. package/bin/utils/fuzzyMatch.js +110 -0
  45. package/bin/utils/logger.d.ts +18 -0
  46. package/bin/utils/logger.js +27 -0
  47. package/bin/utils/response.d.ts +26 -0
  48. package/bin/utils/response.js +28 -0
  49. package/bin/utils/tokenValidator.d.ts +24 -0
  50. package/bin/utils/tokenValidator.js +162 -0
  51. package/bin/version.d.ts +4 -0
  52. package/bin/version.js +7 -0
  53. package/data/_icons.json +12361 -0
  54. package/data/_meta.json +12 -0
  55. package/data/_tokens.json +661 -0
  56. package/data/badge-group.json +121 -0
  57. package/data/badge.json +130 -0
  58. package/data/bread-crumb.json +51 -0
  59. package/data/button-group.json +94 -0
  60. package/data/button.json +143 -0
  61. package/data/carousel-arrow.json +87 -0
  62. package/data/carousel-number-group.json +87 -0
  63. package/data/checkbox.json +99 -0
  64. package/data/combo-box.json +157 -0
  65. package/data/date-picker.json +109 -0
  66. package/data/divider.json +91 -0
  67. package/data/dot.json +103 -0
  68. package/data/dropdown.json +123 -0
  69. package/data/empty-state.json +64 -0
  70. package/data/featured-icon.json +125 -0
  71. package/data/file-input.json +161 -0
  72. package/data/horizontal-tab.json +114 -0
  73. package/data/image-file-input.json +185 -0
  74. package/data/input-base.json +145 -0
  75. package/data/modal.json +131 -0
  76. package/data/notification.json +176 -0
  77. package/data/number-input.json +141 -0
  78. package/data/pagination.json +101 -0
  79. package/data/password-input.json +45 -0
  80. package/data/progress-bar.json +90 -0
  81. package/data/progress-circle.json +96 -0
  82. package/data/radio.json +86 -0
  83. package/data/range-date-picker-with-buttons.json +101 -0
  84. package/data/range-date-picker.json +87 -0
  85. package/data/select-box.json +177 -0
  86. package/data/select.json +116 -0
  87. package/data/slider.json +100 -0
  88. package/data/spinner.json +94 -0
  89. package/data/switch.json +109 -0
  90. package/data/tag.json +101 -0
  91. package/data/textarea.json +96 -0
  92. package/data/toggle.json +102 -0
  93. package/data/tooltip.json +185 -0
  94. package/data/vertical-tab.json +99 -0
  95. package/package.json +71 -0
  96. package/templates/.mcp.json.example +8 -0
  97. package/templates/README.md +30 -0
@@ -0,0 +1,22 @@
1
+ /**
2
+ * list_icons tool — 사전 계산된 아이콘 요약 반환 (순수 함수)
3
+ *
4
+ * 아이콘 이름을 전부 반환하면 토큰 낭비이므로,
5
+ * 요약 정보만 반환하고 search_icon으로 검색을 유도한다.
6
+ */
7
+ import type { IconData } from '../types.js';
8
+ import { type McpToolResponse } from '../utils/response.js';
9
+ /** 아이콘 요약 데이터 타입 */
10
+ export interface IconSummary {
11
+ totalCount: number;
12
+ fillCount: number;
13
+ uniqueIcons: number;
14
+ hint: string;
15
+ sample: string[];
16
+ }
17
+ /** iconData에서 요약을 사전 계산 — server.ts에서 1회 호출 */
18
+ export declare const buildIconSummary: (iconData: IconData) => IconSummary;
19
+ /** IconSummary를 사전 직렬화 — server.ts에서 1회 호출 */
20
+ export declare const buildListIconsResponse: (summary: IconSummary) => McpToolResponse;
21
+ /** list_icons tool — 사전 직렬화된 응답을 그대로 반환 */
22
+ export declare const listIcons: (prebuilt: McpToolResponse) => McpToolResponse;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listIcons = exports.buildListIconsResponse = exports.buildIconSummary = void 0;
4
+ const response_js_1 = require("../utils/response.js");
5
+ const ICON_SAMPLE_SIZE = 20;
6
+ /** iconData에서 요약을 사전 계산 — server.ts에서 1회 호출 */
7
+ const buildIconSummary = (iconData) => {
8
+ const prefixes = new Set(iconData.icons.map((i) => i.name.replace(/Fill$/, '')));
9
+ return {
10
+ totalCount: iconData.totalCount,
11
+ fillCount: iconData.fillCount,
12
+ uniqueIcons: prefixes.size,
13
+ hint: '아이콘이 많으므로 search_icon tool로 키워드 검색을 권장합니다. 예: search_icon("arrow"), search_icon("check")',
14
+ sample: iconData.icons.slice(0, ICON_SAMPLE_SIZE).map((i) => i.name),
15
+ };
16
+ };
17
+ exports.buildIconSummary = buildIconSummary;
18
+ /** IconSummary를 사전 직렬화 — server.ts에서 1회 호출 */
19
+ const buildListIconsResponse = (summary) => (0, response_js_1.successResponse)(summary);
20
+ exports.buildListIconsResponse = buildListIconsResponse;
21
+ /** list_icons tool — 사전 직렬화된 응답을 그대로 반환 */
22
+ const listIcons = (prebuilt) => prebuilt;
23
+ exports.listIcons = listIcons;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * ping tool — NCUA MCP 서버 health check + capabilities/rules 제공 (순수 함수)
3
+ */
4
+ import type { ComponentData, Capability } from '../types.js';
5
+ import type { CdnMeta, IconMeta } from '../utils/dataLoader.js';
6
+ import { type McpToolResponse } from '../utils/response.js';
7
+ /** ping tool 파라미터 — server.ts에서 모든 데이터를 소유하고 전달한다 */
8
+ export interface PingParams {
9
+ componentMap: Map<string, ComponentData>;
10
+ cdnMeta: CdnMeta | null;
11
+ iconMeta: IconMeta | null;
12
+ version: string;
13
+ rules: string[];
14
+ capabilities: Capability[];
15
+ }
16
+ /** ping tool — 서버 상태, capabilities, rules를 반환 */
17
+ export declare const ping: (params: PingParams) => McpToolResponse;
@@ -0,0 +1,20 @@
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
+ /** ping tool — 서버 상태, capabilities, rules를 반환 */
6
+ const ping = (params) => {
7
+ const { componentMap, cdnMeta, iconMeta, version, rules, capabilities } = params;
8
+ return (0, response_js_1.successResponse)({
9
+ status: 'ok',
10
+ name: 'ncds-ui-admin',
11
+ description: 'NCDS UI Admin component library',
12
+ version,
13
+ componentCount: componentMap.size,
14
+ cdn: cdnMeta ?? undefined,
15
+ icon: iconMeta ?? undefined,
16
+ capabilities,
17
+ rules,
18
+ });
19
+ };
20
+ exports.ping = ping;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * render_to_html tool — 컴포넌트 속성을 전달하면 정확한 HTML + React 매핑을 반환 (순수 함수)
3
+ *
4
+ * DOM 환경과 React/ReactDOM은 server.ts에서 초기화하고 파라미터로 전달한다.
5
+ * 이 파일에는 fs, path, require, let이 없다.
6
+ */
7
+ import type { ComponentData, ReactRuntime } from '../types.js';
8
+ import type { CdnMeta, IconMeta } from '../utils/dataLoader.js';
9
+ import { type McpToolResponse } from '../utils/response.js';
10
+ /** render_to_html tool 파라미터 */
11
+ export interface RenderToHtmlParams {
12
+ componentMap: Map<string, ComponentData>;
13
+ bundle: Record<string, unknown>;
14
+ cdnMeta: CdnMeta | null;
15
+ iconMeta: IconMeta | null;
16
+ reactRuntime: ReactRuntime;
17
+ name: string;
18
+ props?: Record<string, unknown>;
19
+ }
20
+ /** render_to_html tool — props로 React 컴포넌트를 렌더링하여 HTML + React 매핑 반환 */
21
+ export declare const renderToHtml: (params: RenderToHtmlParams) => McpToolResponse;
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderToHtml = void 0;
4
+ const response_js_1 = require("../utils/response.js");
5
+ // ── 상수 ──────────────────────────────────────────────────────────
6
+ /** React 특수 props 차단 — injection 방지 */
7
+ const BLOCKED_PROPS = new Set(['dangerouslySetInnerHTML', 'ref', '__self', '__source']);
8
+ /** 아이콘 관련 props — React 변환 시 import 생성 대상 */
9
+ const ICON_PROP_NAMES = new Set(['leadingIcon', 'trailingIcon', 'icon']);
10
+ // ── 순수 함수 (헬퍼) ──────────────────────────────────────────────
11
+ /** componentData.exportName으로 번들에서 컴포넌트를 찾는다 */
12
+ const findComponent = (bundle, exportName) => {
13
+ return bundle[exportName] ?? null;
14
+ };
15
+ /** componentMap의 props 스펙에서 defaultsUsed를 자동 계산 */
16
+ const calcDefaultsUsed = (propsSpec, userProps) => {
17
+ const defaults = {};
18
+ for (const [key, spec] of Object.entries(propsSpec)) {
19
+ if (spec.default !== undefined && !(key in userProps)) {
20
+ defaults[key] = spec.default;
21
+ }
22
+ }
23
+ return defaults;
24
+ };
25
+ /** BLOCKED_PROPS를 제거 — props spec 유무와 무관하게 항상 적용 */
26
+ const removeBlockedProps = (props) => {
27
+ const safe = {};
28
+ for (const [key, value] of Object.entries(props)) {
29
+ if (!BLOCKED_PROPS.has(key))
30
+ safe[key] = value;
31
+ }
32
+ return safe;
33
+ };
34
+ /** componentData.props에 정의된 키 + children만 허용하는 화이트리스트 필터 */
35
+ const sanitizeProps = (userProps, propsSpec) => {
36
+ const allowedKeys = new Set([...Object.keys(propsSpec), 'children']);
37
+ const sanitized = {};
38
+ for (const [key, value] of Object.entries(userProps)) {
39
+ if (allowedKeys.has(key))
40
+ sanitized[key] = value;
41
+ }
42
+ return sanitized;
43
+ };
44
+ /** JSX attribute 값에서 " 를 이스케이프 */
45
+ const escapeJsxAttrValue = (value) => value.replace(/"/g, '&quot;');
46
+ /** props 값을 JSX attribute 문자열로 변환 */
47
+ const propsToJsxAttrs = (props, propsSpec) => {
48
+ const attrs = [];
49
+ for (const [key, value] of Object.entries(props)) {
50
+ if (key === 'children')
51
+ continue;
52
+ const spec = propsSpec[key];
53
+ if (typeof value === 'boolean' && value) {
54
+ attrs.push(key);
55
+ }
56
+ else if (typeof value === 'string') {
57
+ attrs.push(`${key}="${escapeJsxAttrValue(value)}"`);
58
+ }
59
+ else if (typeof value === 'number') {
60
+ attrs.push(`${key}={${value}}`);
61
+ }
62
+ else if (ICON_PROP_NAMES.has(key) && typeof value === 'object' && value !== null) {
63
+ const iconObj = value;
64
+ if (iconObj.type === 'icon' && typeof iconObj.icon === 'string') {
65
+ attrs.push(`${key}={{ type: 'icon', icon: ${iconObj.icon} }}`);
66
+ }
67
+ else {
68
+ attrs.push(`${key}={${JSON.stringify(value)}}`);
69
+ }
70
+ }
71
+ else if (spec?.type === 'enum' || spec?.type === 'string') {
72
+ attrs.push(`${key}="${String(value)}"`);
73
+ }
74
+ else {
75
+ attrs.push(`${key}={${JSON.stringify(value)}}`);
76
+ }
77
+ }
78
+ return attrs.join(' ');
79
+ };
80
+ /** appliedProps에서 아이콘 이름을 추출 (PascalCase) */
81
+ const extractIconNames = (props) => {
82
+ const icons = [];
83
+ for (const [key, value] of Object.entries(props)) {
84
+ if (!ICON_PROP_NAMES.has(key))
85
+ continue;
86
+ if (typeof value === 'object' && value !== null) {
87
+ const iconObj = value;
88
+ if (iconObj.type === 'icon' && typeof iconObj.icon === 'string') {
89
+ icons.push(iconObj.icon);
90
+ }
91
+ }
92
+ else if (typeof value === 'string' && value.length > 0) {
93
+ icons.push(value);
94
+ }
95
+ }
96
+ return icons;
97
+ };
98
+ /** React 변환 매핑 정보를 생성 */
99
+ const buildReactOutput = (componentData, appliedProps, iconMeta) => {
100
+ const { exportName, importPath, props: propsSpec } = componentData;
101
+ const imports = [`import { ${exportName} } from '${importPath}';`];
102
+ const dependencies = [importPath];
103
+ const iconNames = extractIconNames(appliedProps);
104
+ if (iconNames.length > 0 && iconMeta) {
105
+ const iconImport = `import { ${iconNames.join(', ')} } from '${iconMeta.packageName}';`;
106
+ imports.push(iconImport);
107
+ if (!dependencies.includes(iconMeta.packageName)) {
108
+ dependencies.push(iconMeta.packageName);
109
+ }
110
+ }
111
+ const attrsStr = propsSpec ? propsToJsxAttrs(appliedProps, propsSpec) : '';
112
+ const children = appliedProps.children;
113
+ const jsx = children
114
+ ? `<${exportName}${attrsStr ? ' ' + attrsStr : ''}>${String(children)}</${exportName}>`
115
+ : `<${exportName}${attrsStr ? ' ' + attrsStr : ''} />`;
116
+ return {
117
+ imports,
118
+ jsx,
119
+ cssImport: `import '${importPath}/style.css';`,
120
+ dependencies,
121
+ };
122
+ };
123
+ /** dataVersion 객체를 생성 */
124
+ const buildDataVersion = (cdnMeta, iconMeta) => {
125
+ const versions = {};
126
+ if (cdnMeta)
127
+ versions['@ncds/ui-admin'] = cdnMeta.version;
128
+ if (iconMeta)
129
+ versions[iconMeta.packageName] = iconMeta.version;
130
+ return versions;
131
+ };
132
+ // ── 진입점 ────────────────────────────────────────────────────────
133
+ /** render_to_html tool — props로 React 컴포넌트를 렌더링하여 HTML + React 매핑 반환 */
134
+ const renderToHtml = (params) => {
135
+ const { componentMap, bundle, cdnMeta, iconMeta, reactRuntime, name, props } = params;
136
+ const normalized = (0, response_js_1.normalizeName)(name);
137
+ const componentData = componentMap.get(normalized);
138
+ if (!componentData)
139
+ return (0, response_js_1.componentNotFoundResponse)(normalized);
140
+ const exportName = componentData.exportName;
141
+ if (!exportName) {
142
+ return (0, response_js_1.errorResponse)('EXPORT_NAME_MISSING', `'${normalized}' 컴포넌트에 exportName이 없습니다.`, '데이터를 재추출해주세요 (yarn extract).');
143
+ }
144
+ const Component = findComponent(bundle, exportName);
145
+ if (!Component) {
146
+ return (0, response_js_1.errorResponse)('COMPONENT_NOT_IN_BUNDLE', `'${normalized}' (${exportName}) 컴포넌트가 번들에 없습니다.`, '번들을 재빌드해주세요 (yarn build:bundle).');
147
+ }
148
+ try {
149
+ const rawProps = props ?? {};
150
+ const safeProps = removeBlockedProps(rawProps);
151
+ const userProps = componentData.props ? sanitizeProps(safeProps, componentData.props) : safeProps;
152
+ const element = reactRuntime.createElement(Component, userProps);
153
+ const rawHtml = reactRuntime.renderToStaticMarkup(element);
154
+ const html = `<!-- ncua:${normalized} -->\n${rawHtml}\n<!-- /ncua:${normalized} -->`;
155
+ const defaultsUsed = componentData.props ? calcDefaultsUsed(componentData.props, userProps) : {};
156
+ const react = buildReactOutput(componentData, userProps, iconMeta);
157
+ const dataVersion = buildDataVersion(cdnMeta, iconMeta);
158
+ return (0, response_js_1.successResponse)({
159
+ html,
160
+ component: normalized,
161
+ exportName,
162
+ importPath: componentData.importPath,
163
+ appliedProps: userProps,
164
+ ...(Object.keys(defaultsUsed).length > 0 && { defaultsUsed }),
165
+ js: componentData.jsRequired
166
+ ? { required: true, description: '이 컴포넌트는 인터랙션을 위해 CDN JS가 필요합니다' }
167
+ : { required: false },
168
+ cdn: cdnMeta ?? undefined,
169
+ dataVersion,
170
+ react,
171
+ });
172
+ }
173
+ catch (err) {
174
+ return (0, response_js_1.errorResponse)('RENDER_FAILED', `'${normalized}' 렌더링 실패: ${(0, response_js_1.toErrorMessage)(err)}`, '사용 가능한 속성을 확인하려면 get_component_props를 사용하세요.');
175
+ }
176
+ };
177
+ exports.renderToHtml = renderToHtml;
@@ -0,0 +1,4 @@
1
+ import type { ComponentData } from '../types.js';
2
+ import { type McpToolResponse } from '../utils/response.js';
3
+ /** search_component tool — name/description/aliases 퍼지 검색 후 점수순 반환 */
4
+ export declare const searchComponent: (componentMap: Map<string, ComponentData>, query: string) => McpToolResponse;
@@ -0,0 +1,33 @@
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
+ * Calculator: searchComponent — 입력(componentMap, query) → 출력(매칭 결과, 점수순 정렬)
8
+ */
9
+ const dataLoader_js_1 = require("../utils/dataLoader.js");
10
+ const response_js_1 = require("../utils/response.js");
11
+ const fuzzyMatch_js_1 = require("../utils/fuzzyMatch.js");
12
+ /** search_component tool — name/description/aliases 퍼지 검색 후 점수순 반환 */
13
+ const searchComponent = (componentMap, query) => {
14
+ if (!query.trim())
15
+ return (0, response_js_1.successResponse)([]);
16
+ const scored = (0, dataLoader_js_1.getAllComponents)(componentMap)
17
+ .map((c) => {
18
+ // name, description, aliases 모두에 대해 퍼지 매칭 → 최고 점수 채택
19
+ const targets = [c.name, c.description, ...c.aliases];
20
+ const match = (0, fuzzyMatch_js_1.bestFuzzyMatch)(query, targets);
21
+ return { component: c, score: match?.score ?? 0 };
22
+ })
23
+ .filter((item) => item.score > 0)
24
+ .sort((a, b) => b.score - a.score);
25
+ const results = scored.map(({ component: { name, category, description, aliases } }) => ({
26
+ name,
27
+ category,
28
+ description,
29
+ aliases,
30
+ }));
31
+ return (0, response_js_1.successResponse)(results);
32
+ };
33
+ exports.searchComponent = searchComponent;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * search_icon tool — 키워드로 아이콘 검색 (순수 함수)
3
+ */
4
+ import type { IconData } from '../types.js';
5
+ import { type McpToolResponse } from '../utils/response.js';
6
+ /** search_icon tool — 키워드로 아이콘 이름/kebab을 검색 */
7
+ export declare const searchIcon: (iconData: IconData, query: string) => McpToolResponse;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.searchIcon = void 0;
4
+ const response_js_1 = require("../utils/response.js");
5
+ /** search_icon tool — 키워드로 아이콘 이름/kebab을 검색 */
6
+ const searchIcon = (iconData, query) => {
7
+ const lower = query.trim().toLowerCase();
8
+ if (!lower)
9
+ return (0, response_js_1.successResponse)([]);
10
+ const results = iconData.icons
11
+ .filter((icon) => icon.name.toLowerCase().includes(lower) || icon.kebab.includes(lower))
12
+ .map((icon) => ({
13
+ name: icon.name,
14
+ kebab: icon.kebab,
15
+ fill: icon.fill,
16
+ }));
17
+ return (0, response_js_1.successResponse)(results);
18
+ };
19
+ exports.searchIcon = searchIcon;
@@ -0,0 +1,18 @@
1
+ import type { ComponentData, ComplianceRulesData, TokenData } from '../types.js';
2
+ import type { CdnMeta } from '../utils/dataLoader.js';
3
+ import { type McpToolResponse } from '../utils/response.js';
4
+ export type { ValidationError } from '../utils/bemValidator.js';
5
+ export { buildRootClassMap } from '../utils/bemValidator.js';
6
+ export { buildTokenValueMap } from '../utils/tokenValidator.js';
7
+ /** validate_html tool 파라미터 */
8
+ export interface ValidateHtmlParams {
9
+ componentMap: Map<string, ComponentData>;
10
+ rootClassMap: Map<string, string>;
11
+ html: string;
12
+ cdnMeta?: CdnMeta | null;
13
+ tokenData?: TokenData | null;
14
+ complianceRules?: ComplianceRulesData | null;
15
+ tokenValueMap?: Map<string, string[]> | null;
16
+ }
17
+ /** validate_html tool — HTML의 NCUA BEM 클래스 정합성 + 디자인 시스템 준수도 검증 */
18
+ export declare const validateHtml: (params: ValidateHtmlParams) => McpToolResponse;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateHtml = exports.buildTokenValueMap = exports.buildRootClassMap = void 0;
4
+ /**
5
+ * validate_html tool — NCUA HTML BEM 검증 + 디자인 시스템 준수도 검증 진입점
6
+ *
7
+ * BEM 검증: utils/bemValidator.ts
8
+ * Compliance 검증: utils/compliance.ts
9
+ * 이 파일은 오케스트레이션만 담당한다.
10
+ */
11
+ const node_html_parser_1 = require("node-html-parser");
12
+ const response_js_1 = require("../utils/response.js");
13
+ const bemValidator_js_1 = require("../utils/bemValidator.js");
14
+ const compliance_js_1 = require("../utils/compliance.js");
15
+ var bemValidator_js_2 = require("../utils/bemValidator.js");
16
+ Object.defineProperty(exports, "buildRootClassMap", { enumerable: true, get: function () { return bemValidator_js_2.buildRootClassMap; } });
17
+ var tokenValidator_js_1 = require("../utils/tokenValidator.js");
18
+ Object.defineProperty(exports, "buildTokenValueMap", { enumerable: true, get: function () { return tokenValidator_js_1.buildTokenValueMap; } });
19
+ /** validate_html tool — HTML의 NCUA BEM 클래스 정합성 + 디자인 시스템 준수도 검증 */
20
+ const validateHtml = (params) => {
21
+ const { componentMap, rootClassMap, html, cdnMeta, tokenData, complianceRules, tokenValueMap } = params;
22
+ const trimmed = html.trim();
23
+ if (trimmed.length === 0)
24
+ return (0, response_js_1.successResponse)({ valid: true });
25
+ const root = (0, node_html_parser_1.parse)(trimmed);
26
+ const elements = root.querySelectorAll('[class]');
27
+ const { errors, warnings, invalidClassesSet, hasNcuaClasses } = (0, bemValidator_js_1.collectBemErrors)({
28
+ elements,
29
+ rootClassMap,
30
+ componentMap,
31
+ });
32
+ if (!hasNcuaClasses)
33
+ return (0, response_js_1.successResponse)({ valid: true });
34
+ if (cdnMeta)
35
+ errors.push(...(0, bemValidator_js_1.checkCdnInclusion)(trimmed, cdnMeta));
36
+ const compliance = buildCompliance({
37
+ root,
38
+ componentMap,
39
+ rootClassMap,
40
+ warnings,
41
+ tokenData,
42
+ complianceRules,
43
+ tokenValueMap,
44
+ });
45
+ return buildResponse({ errors, warnings, invalidClassesSet, root, compliance });
46
+ };
47
+ exports.validateHtml = validateHtml;
48
+ /** compliance 검증 오케스트레이션 — tokenData 또는 complianceRules가 있을 때만 실행 */
49
+ const buildCompliance = (params) => {
50
+ const { root, componentMap, rootClassMap, warnings, tokenData, complianceRules, tokenValueMap } = params;
51
+ if (!complianceRules && !tokenData)
52
+ return undefined;
53
+ const ncuaResult = complianceRules
54
+ ? (0, compliance_js_1.detectNcuaOmission)({ root, complianceRules, componentMap })
55
+ : { errors: [], ncuaUsage: { used: 0, available: 0 } };
56
+ const tokenResult = tokenData && tokenValueMap
57
+ ? (0, compliance_js_1.detectTokenIssues)({ root, tokenData, tokenValueMap })
58
+ : { errors: [], tokenUsage: { correct: 0, missing: 0, invalid: 0 } };
59
+ const customResult = (0, compliance_js_1.detectCustomNotSeparated)({ root, rootClassMap, warnings });
60
+ return (0, compliance_js_1.buildComplianceSummary)({
61
+ ncuaErrors: ncuaResult.errors,
62
+ tokenErrors: tokenResult.errors,
63
+ customErrors: customResult.errors,
64
+ ncuaUsage: ncuaResult.ncuaUsage,
65
+ tokenUsage: tokenResult.tokenUsage,
66
+ customSeparation: customResult.customSeparation,
67
+ });
68
+ };
69
+ /** 최종 응답 빌드 */
70
+ const buildResponse = (params) => {
71
+ const { errors, warnings, invalidClassesSet, root, compliance } = params;
72
+ if (errors.length === 0 && warnings.length === 0 && !compliance)
73
+ return (0, response_js_1.successResponse)({ valid: true });
74
+ if (errors.length === 0 && warnings.length === 0 && compliance)
75
+ return (0, response_js_1.successResponse)({ valid: true, compliance });
76
+ if (errors.length === 0)
77
+ return (0, response_js_1.successResponse)({ valid: true, warnings, ...(compliance && { compliance }) });
78
+ return (0, response_js_1.successResponse)({
79
+ valid: false,
80
+ errors,
81
+ ...(warnings.length > 0 && { warnings }),
82
+ fixed_html: (0, bemValidator_js_1.buildFixedHtml)(root, invalidClassesSet),
83
+ ...(compliance && { compliance }),
84
+ });
85
+ };
package/bin/types.d.ts ADDED
@@ -0,0 +1,111 @@
1
+ /**
2
+ * MCP 서버 공통 타입 정의
3
+ */
4
+ export interface PropSpec {
5
+ type: 'string' | 'enum' | 'boolean' | 'number' | 'ReactNode' | 'function' | 'object';
6
+ required: boolean;
7
+ default?: unknown;
8
+ values?: string[];
9
+ rawType?: string;
10
+ }
11
+ export interface ComponentUsage {
12
+ import: string;
13
+ react: Record<string, string>;
14
+ }
15
+ export interface ComponentData {
16
+ name: string;
17
+ exportName: string;
18
+ importPath: string;
19
+ jsRequired: boolean;
20
+ category: string;
21
+ description: string;
22
+ aliases: string[];
23
+ hasChildren: boolean;
24
+ whenToUse: string[];
25
+ forbiddenRules: string[];
26
+ seeAlso: string[];
27
+ /** Props가 확장하는 HTML 요소 — 에이전트가 표준 HTML 속성 사용 가능 여부를 판단 */
28
+ htmlElement?: string;
29
+ props: Record<string, PropSpec>;
30
+ html?: Record<string, string>;
31
+ bemClasses: string[];
32
+ usage: ComponentUsage;
33
+ }
34
+ export interface IconEntry {
35
+ name: string;
36
+ kebab: string;
37
+ fill: boolean;
38
+ }
39
+ export interface IconData {
40
+ totalCount: number;
41
+ fillCount: number;
42
+ icons: IconEntry[];
43
+ }
44
+ /** server.ts에서 주입하는 React 런타임 의존성 */
45
+ export interface ReactRuntime {
46
+ createElement: (type: unknown, props?: Record<string, unknown>) => unknown;
47
+ renderToStaticMarkup: (element: unknown) => string;
48
+ }
49
+ export interface Capability {
50
+ tool: string;
51
+ description: string;
52
+ }
53
+ export type McpErrorCode = 'COMPONENT_NOT_FOUND' | 'EXPORT_NAME_MISSING' | 'COMPONENT_NOT_IN_BUNDLE' | 'RENDER_FAILED' | 'INVALID_TOKEN_CATEGORY';
54
+ export interface TokenEntry {
55
+ name: string;
56
+ value: string;
57
+ description: string;
58
+ }
59
+ export interface TokenGroup {
60
+ name: string;
61
+ category: string;
62
+ description: string;
63
+ tokens: TokenEntry[];
64
+ }
65
+ export interface TokenData {
66
+ totalCount: number;
67
+ categories: string[];
68
+ groups: TokenGroup[];
69
+ }
70
+ export interface ComplianceRuleHint {
71
+ attr: string;
72
+ values: string[];
73
+ }
74
+ export interface ComplianceRuleMatch {
75
+ tag: string;
76
+ hints?: ComplianceRuleHint[];
77
+ }
78
+ export interface ComplianceRule {
79
+ match: ComplianceRuleMatch;
80
+ ncuaComponent: string;
81
+ confidence: 'high' | 'medium';
82
+ }
83
+ export interface ComplianceRulesData {
84
+ patterns: ComplianceRule[];
85
+ }
86
+ export interface ComplianceError {
87
+ type: 'ncua_not_used' | 'token_not_used' | 'invalid_token' | 'custom_not_separated';
88
+ severity?: 'warning';
89
+ target: string;
90
+ component: string;
91
+ message: string;
92
+ suggestion: string;
93
+ }
94
+ export interface ComplianceSummary {
95
+ score: number;
96
+ errors: ComplianceError[];
97
+ ncuaUsage: {
98
+ used: number;
99
+ available: number;
100
+ };
101
+ tokenUsage: {
102
+ correct: number;
103
+ missing: number;
104
+ invalid: number;
105
+ };
106
+ customSeparation: {
107
+ clean: number;
108
+ violated: number;
109
+ };
110
+ totalViolations: number;
111
+ }
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,36 @@
1
+ /**
2
+ * BEM 클래스 검증 순수 함수 — NCUA HTML의 BEM 클래스 정합성 검증 + CDN 링크 검증
3
+ *
4
+ * validateHtml.ts에서 분리. 모든 함수는 순수 함수(Calculation).
5
+ */
6
+ import type { parse } from 'node-html-parser';
7
+ import type { ComponentData } from '../types.js';
8
+ import type { CdnMeta } from './dataLoader.js';
9
+ export interface ValidationError {
10
+ type: 'invalid_modifier' | 'invalid_element' | 'unknown_component' | 'missing_root_class' | 'mixed_namespace' | 'missing_cdn';
11
+ class: string;
12
+ component: string;
13
+ message: string;
14
+ suggestion: string;
15
+ }
16
+ /** 엘리먼트에서 class 목록을 파싱 */
17
+ export declare const getClassList: (el: {
18
+ getAttribute: (name: string) => string | undefined;
19
+ }) => string[];
20
+ /** root class → component name 매핑 빌드 — server.ts에서 1회 호출 */
21
+ export declare const buildRootClassMap: (componentMap: Map<string, ComponentData>) => Map<string, string>;
22
+ /** 엘리먼트별 BEM 클래스 검증 → errors + warnings + invalidClasses 수집 */
23
+ export declare const collectBemErrors: (params: {
24
+ elements: ReturnType<ReturnType<typeof parse>['querySelectorAll']>;
25
+ rootClassMap: Map<string, string>;
26
+ componentMap: Map<string, ComponentData>;
27
+ }) => {
28
+ errors: ValidationError[];
29
+ warnings: ValidationError[];
30
+ invalidClassesSet: Set<string>;
31
+ hasNcuaClasses: boolean;
32
+ };
33
+ /** 잘못된 클래스를 제거하여 fixed_html 생성 */
34
+ export declare const buildFixedHtml: (root: ReturnType<typeof parse>, invalidClassesSet: Set<string>) => string;
35
+ /** CDN CSS/JS 링크 포함 여부 검증 */
36
+ export declare const checkCdnInclusion: (html: string, cdnMeta: CdnMeta) => ValidationError[];