@ncds/ui-admin-mcp 1.0.0-alpha.5 → 1.0.0-alpha.7

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.
@@ -5,12 +5,12 @@ You are an agent that builds UI using NCUA (NCDS UI Admin) design system compone
5
5
  ## Absolute Rules (VIOLATION = CRITICAL FAILURE)
6
6
 
7
7
  1. Call ping ONCE at the start of every session before using any other tool. This loads version info and usage rules.
8
- 2. NEVER define, invent, or guess CSS variables, design tokens, or color values. Use ONLY tokens returned by get_design_tokens. If you write a custom CSS variable (e.g. --custom-anything) or hardcode a hex/rgb value (e.g. #5B5BD6, rgb(91,91,214)), you have FAILED.
9
- 3. NEVER write SVG icons or icon markup manually. ALL icons MUST come from search_icon or list_icons. If you write a single <svg> tag by hand, you have FAILED.
8
+ 2. NEVER define or guess NCUA CSS variables (--ncua-*). NCUA component styles are controlled by CDN CSS only.
9
+ 3. NEVER write SVG icons or icon markup manually. ALL icons MUST come from search_icon or list_icons. If you write a single svg tag by hand, you have FAILED.
10
10
  4. NEVER use emoji in generated HTML, CSS, or any output. No exceptions.
11
11
  5. You MUST generate NCUA component HTML using render_to_html or render_to_html_batch only. Never write component HTML/CSS manually.
12
12
  6. If an NCUA component exists for the use case, you MUST use it. Do NOT recreate it manually.
13
- 7. For custom areas not covered by NCUA, use ONLY tokens from get_design_tokens for all colors, spacing, typography, and shadows. Call get_design_tokens BEFORE writing any custom CSS.
13
+ 7. For custom areas, call get_design_tokens FIRST and prefer NCUA tokens for colors, spacing, typography, and shadows to maintain visual consistency. If no suitable token exists, you may use standard CSS values (hex, rgb, px).
14
14
 
15
15
  ## Required Workflow (follow this order strictly)
16
16
 
@@ -0,0 +1,165 @@
1
+ {
2
+ "Modal": {
3
+ "className": "Modal",
4
+ "constructor": "new window.ncua.Modal(options?)",
5
+ "constructorParams": {
6
+ "size": "'sm' | 'md' | 'lg' (default: 'md')",
7
+ "closeOnBackdropClick": "boolean (default: false)",
8
+ "closeOnEsc": "boolean (default: true)",
9
+ "onClose": "() => void"
10
+ },
11
+ "methods": ["open()", "close()", "destroy()", "setContent(html: string | HTMLElement)", "isModalOpen()"],
12
+ "example": "const modal = new window.ncua.Modal({ size: 'sm', onClose: () => {} });\nmodal.setContent('<p>Content</p>');\nmodal.open();"
13
+ },
14
+ "SelectBox": {
15
+ "className": "SelectBox",
16
+ "constructor": "new window.ncua.SelectBox(element, config?)",
17
+ "constructorParams": {
18
+ "element": "HTMLElement — target container",
19
+ "config.options": "Array<{ id: string, text: string, disabled?: boolean }>",
20
+ "config.placeholder": "string (default: '선택하세요')",
21
+ "config.value": "string | number",
22
+ "config.onChange": "(value) => void",
23
+ "config.size": "'xs' | 'sm' | 'md'",
24
+ "config.multiple": "boolean",
25
+ "config.disabled": "boolean"
26
+ },
27
+ "methods": ["getValues()", "setValues(options[])", "getSelectedCount()"],
28
+ "example": "const select = new window.ncua.SelectBox(document.getElementById('my-select'), {\n options: [{ id: '1', text: 'Option 1' }],\n onChange: (value) => console.log(value)\n});"
29
+ },
30
+ "ComboBox": {
31
+ "className": "ComboBox",
32
+ "constructor": "new window.ncua.ComboBox(element, config?)",
33
+ "constructorParams": {
34
+ "element": "HTMLElement — target container",
35
+ "config.options": "Array<{ id: string, label: string, disabled?: boolean }>",
36
+ "config.placeholder": "string (default: '검색하고 선택하세요')",
37
+ "config.value": "string | number",
38
+ "config.onChange": "(value) => void",
39
+ "config.onSearch": "(searchValue: string) => void",
40
+ "config.size": "'xs' | 'sm'",
41
+ "config.multiple": "boolean"
42
+ },
43
+ "methods": ["getValues()", "setValues(options[])", "clearInput()", "getSelectedCount()"],
44
+ "example": "const combo = new window.ncua.ComboBox(document.getElementById('my-combo'), {\n options: [{ id: '1', label: 'React' }],\n onSearch: (q) => console.log(q)\n});"
45
+ },
46
+ "DatePicker": {
47
+ "className": "DatePicker",
48
+ "constructor": "new window.ncua.DatePicker(wrapper, options)",
49
+ "constructorParams": {
50
+ "wrapper": "HTMLElement — container element",
51
+ "options.size": "'xs' | 'sm'",
52
+ "options.datePickerOptions": "Array<{ element: string, placeholder?: string, options: FlatpickrOptions }>",
53
+ "options.buttons": "Array<{ text: string, period: number, unit: 'days'|'months', isCurrent: boolean }>",
54
+ "options.onValidationError": "(error) => void"
55
+ },
56
+ "methods": ["getDates()", "setDate(dates: string[])", "setMultipleDates(dates: string[])"],
57
+ "example": "const dp = new window.ncua.DatePicker(document.getElementById('my-dp'), {\n size: 'xs',\n datePickerOptions: [{ element: 'date-input', placeholder: 'Select date', options: { dateFormat: 'Y-m-d' } }]\n});"
58
+ },
59
+ "Slider": {
60
+ "className": "Slider",
61
+ "constructor": "new window.ncua.Slider(element, options)",
62
+ "constructorParams": {
63
+ "element": "HTMLElement — container",
64
+ "options.min": "number (default: 0)",
65
+ "options.max": "number (default: 100)",
66
+ "options.step": "number (default: 1)",
67
+ "options.value": "number | [number, number]",
68
+ "options.onChange": "(value) => void",
69
+ "options.disabled": "boolean"
70
+ },
71
+ "methods": ["getValue()", "setValue(value)", "enable()", "disable()", "destroy()"],
72
+ "example": "const slider = new window.ncua.Slider(document.getElementById('my-slider'), {\n min: 0, max: 100, value: 50,\n onChange: (v) => console.log(v)\n});"
73
+ },
74
+ "Tab": {
75
+ "className": "Tab",
76
+ "constructor": "new window.ncua.Tab(element)",
77
+ "constructorParams": {
78
+ "element": "HTMLElement — container with swiper structure"
79
+ },
80
+ "methods": ["destroy()"],
81
+ "example": "const tab = new window.ncua.Tab(document.getElementById('my-tab'));"
82
+ },
83
+ "Tooltip": {
84
+ "className": "Tooltip",
85
+ "constructor": "window.ncua.Tooltip.createShort(title, content?, options?)",
86
+ "constructorParams": {
87
+ "title": "string",
88
+ "content": "string (HTML supported)",
89
+ "options.position": "'top' | 'bottom' | 'left' | 'right' (default: 'top')"
90
+ },
91
+ "methods": ["getElement()", "showTooltip()", "hideTooltip()", "destroy()"],
92
+ "example": "const tip = window.ncua.Tooltip.createShort('Help', 'Tooltip content');\ndocument.getElementById('container').appendChild(tip.getElement());"
93
+ },
94
+ "Notification": {
95
+ "className": "Notification",
96
+ "constructor": "window.ncua.Notification.success(title, supportingText?, options?)",
97
+ "constructorParams": {
98
+ "title": "string",
99
+ "supportingText": "string",
100
+ "options.autoClose": "number (ms, 0 = no auto-close)"
101
+ },
102
+ "methods": ["show(parent?)", "remove()", "destroy()"],
103
+ "staticMethods": ["success()", "error()", "warning()", "info()", "neutral()"],
104
+ "example": "window.ncua.Notification.success('Saved', 'Changes saved successfully');"
105
+ },
106
+ "ProgressBar": {
107
+ "className": "ProgressBar",
108
+ "constructor": "new window.ncua.ProgressBar(options?)",
109
+ "constructorParams": {
110
+ "options.value": "number (0-100)",
111
+ "options.label": "'right' | 'bottom' | 'top-float' | 'bottom-float'"
112
+ },
113
+ "methods": ["getElement()", "updateOptions(newOptions)", "toHTML()"],
114
+ "example": "const bar = new window.ncua.ProgressBar({ value: 65, label: 'right' });\ndocument.getElementById('container').appendChild(bar.getElement());"
115
+ },
116
+ "Tag": {
117
+ "className": "Tag",
118
+ "constructor": "new window.ncua.Tag(options)",
119
+ "constructorParams": {
120
+ "options.text": "string",
121
+ "options.size": "'sm' | 'md' (default: 'sm')",
122
+ "options.close": "boolean — show close button",
123
+ "options.onButtonClick": "() => void — close handler"
124
+ },
125
+ "methods": ["getElement()", "setText(text)", "setCount(count)", "destroy()"],
126
+ "example": "const tag = new window.ncua.Tag({ text: 'Label', close: true, onButtonClick: () => tag.destroy() });\ndocument.getElementById('container').appendChild(tag.getElement());"
127
+ },
128
+ "FileInput": {
129
+ "className": "FileInput",
130
+ "constructor": "new window.ncua.FileInput(options)",
131
+ "constructorParams": {
132
+ "options.container": "HTMLElement | string — auto-append target",
133
+ "options.onChange": "(files: File[]) => void",
134
+ "options.onFail": "(invalidFiles: File[]) => void",
135
+ "options.disabled": "boolean"
136
+ },
137
+ "methods": ["getElement()", "setDisabled(boolean)", "destroy()"],
138
+ "example": "const fi = new window.ncua.FileInput({\n container: 'file-container',\n onChange: (files) => console.log(files)\n});"
139
+ },
140
+ "ImageFileInput": {
141
+ "className": "ImageFileInput",
142
+ "constructor": "new window.ncua.ImageFileInput(options)",
143
+ "constructorParams": {
144
+ "options.container": "HTMLElement | string — auto-append target",
145
+ "options.onChange": "(files: File[]) => void",
146
+ "options.maxFileCount": "number",
147
+ "options.acceptedFileTypes": "string[]",
148
+ "options.maxFileSize": "number"
149
+ },
150
+ "methods": ["getElement()", "getFiles()", "clearFiles()", "setDisabled(boolean)", "destroy()"],
151
+ "example": "const img = new window.ncua.ImageFileInput({\n container: 'image-container',\n maxFileCount: 5,\n onChange: (files) => console.log(files)\n});"
152
+ },
153
+ "FeaturedIcon": {
154
+ "className": "FeaturedIcon",
155
+ "constructor": "new window.ncua.FeaturedIcon(options)",
156
+ "constructorParams": {
157
+ "options.svgString": "string — SVG content",
158
+ "options.theme": "'light-circle' | 'dark-circle' | 'outline-circle' | 'square-outline'",
159
+ "options.color": "'neutral' | 'error' | 'warning' | 'success'",
160
+ "options.size": "'sm' | 'md' | 'lg' | 'xl'"
161
+ },
162
+ "methods": ["getElement()", "updateColor(color)", "updateSize(size)", "destroy()"],
163
+ "example": "const icon = new window.ncua.FeaturedIcon({ svgString: '<svg>...</svg>', color: 'success', size: 'md' });\ndocument.getElementById('container').appendChild(icon.getElement());"
164
+ }
165
+ }
package/bin/server.js CHANGED
@@ -81,6 +81,7 @@ const main = async () => {
81
81
  const rules = loadRules(definitionsDir);
82
82
  const instructions = (0, dataLoader_js_1.loadInstructions)(definitionsDir);
83
83
  const complianceRules = (0, dataLoader_js_1.loadComplianceRules)(definitionsDir);
84
+ const jsApiMap = (0, dataLoader_js_1.loadJsApi)(definitionsDir);
84
85
  // ── 데이터 로딩 ──
85
86
  const componentMap = (0, dataLoader_js_1.loadComponentsFromDir)(dataLoader_js_1.DEFAULT_DATA_DIR);
86
87
  const { cdn: cdnMeta, icon: iconMeta } = (0, dataLoader_js_1.loadMeta)(dataLoader_js_1.DEFAULT_DATA_DIR);
@@ -143,6 +144,7 @@ const main = async () => {
143
144
  cdnMeta,
144
145
  iconMeta,
145
146
  reactRuntime,
147
+ jsApiMap,
146
148
  name,
147
149
  props: props,
148
150
  }));
@@ -165,6 +167,7 @@ const main = async () => {
165
167
  cdnMeta,
166
168
  iconMeta,
167
169
  reactRuntime,
170
+ jsApiMap,
168
171
  name,
169
172
  props: props,
170
173
  }));
@@ -5,7 +5,7 @@
5
5
  * 이 파일에는 fs, path, require, let이 없다.
6
6
  */
7
7
  import type { ComponentData, ReactRuntime } from '../types.js';
8
- import type { CdnMeta, IconMeta } from '../utils/dataLoader.js';
8
+ import type { CdnMeta, IconMeta, JsApiInfo } from '../utils/dataLoader.js';
9
9
  import { type McpToolResponse } from '../utils/response.js';
10
10
  /** render_to_html tool 파라미터 */
11
11
  export interface RenderToHtmlParams {
@@ -14,6 +14,7 @@ export interface RenderToHtmlParams {
14
14
  cdnMeta: CdnMeta | null;
15
15
  iconMeta: IconMeta | null;
16
16
  reactRuntime: ReactRuntime;
17
+ jsApiMap: Map<string, JsApiInfo>;
17
18
  name: string;
18
19
  props?: Record<string, unknown>;
19
20
  }
@@ -132,7 +132,7 @@ const buildDataVersion = (cdnMeta, iconMeta) => {
132
132
  // ── 진입점 ────────────────────────────────────────────────────────
133
133
  /** render_to_html tool — props로 React 컴포넌트를 렌더링하여 HTML + React 매핑 반환 */
134
134
  const renderToHtml = (params) => {
135
- const { componentMap, bundle, cdnMeta, iconMeta, reactRuntime, name, props } = params;
135
+ const { componentMap, bundle, cdnMeta, iconMeta, reactRuntime, jsApiMap, name, props } = params;
136
136
  const normalized = (0, response_js_1.normalizeName)(name);
137
137
  const componentData = componentMap.get(normalized);
138
138
  if (!componentData)
@@ -155,6 +155,22 @@ const renderToHtml = (params) => {
155
155
  const defaultsUsed = componentData.props ? calcDefaultsUsed(componentData.props, userProps) : {};
156
156
  const react = buildReactOutput(componentData, userProps, iconMeta);
157
157
  const dataVersion = buildDataVersion(cdnMeta, iconMeta);
158
+ const jsApi = jsApiMap.get(exportName);
159
+ const js = componentData.jsRequired
160
+ ? {
161
+ required: true,
162
+ description: '이 컴포넌트는 인터랙션을 위해 CDN JS가 필요합니다',
163
+ ...(jsApi && {
164
+ api: {
165
+ className: jsApi.className,
166
+ constructor: jsApi.constructor,
167
+ constructorParams: jsApi.constructorParams,
168
+ methods: jsApi.methods,
169
+ example: jsApi.example,
170
+ },
171
+ }),
172
+ }
173
+ : { required: false };
158
174
  return (0, response_js_1.successResponse)({
159
175
  html,
160
176
  component: normalized,
@@ -162,9 +178,7 @@ const renderToHtml = (params) => {
162
178
  importPath: componentData.importPath,
163
179
  appliedProps: userProps,
164
180
  ...(Object.keys(defaultsUsed).length > 0 && { defaultsUsed }),
165
- js: componentData.jsRequired
166
- ? { required: true, description: '이 컴포넌트는 인터랙션을 위해 CDN JS가 필요합니다' }
167
- : { required: false },
181
+ js,
168
182
  cdn: cdnMeta ?? undefined,
169
183
  dataVersion,
170
184
  react,
@@ -27,6 +27,17 @@ export declare const loadTokenData: (dataDir: string) => TokenData;
27
27
  export declare const loadComplianceRules: (definitionsDir: string) => ComplianceRulesData | null;
28
28
  /** definitions/instructions.md를 로드하여 문자열 반환 */
29
29
  export declare const loadInstructions: (definitionsDir: string) => string;
30
+ /** JS API 정보 타입 */
31
+ export interface JsApiInfo {
32
+ className: string;
33
+ constructor: string;
34
+ constructorParams: Record<string, string>;
35
+ methods: string[];
36
+ staticMethods?: string[];
37
+ example: string;
38
+ }
39
+ /** definitions/js-api.json을 로드하여 exportName→JsApiInfo Map 반환 */
40
+ export declare const loadJsApi: (definitionsDir: string) => Map<string, JsApiInfo>;
30
41
  /** componentMap에서 이름으로 단일 컴포넌트 조회 */
31
42
  export declare const getComponent: (map: Map<string, ComponentData>, name: string) => ComponentData | undefined;
32
43
  /** componentMap의 모든 컴포넌트를 배열로 반환 */
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getAllComponents = exports.getComponent = exports.loadInstructions = exports.loadComplianceRules = exports.loadTokenData = exports.loadMeta = exports.loadIconData = exports.loadComponentsFromDir = exports.DEFAULT_DATA_DIR = void 0;
6
+ exports.getAllComponents = exports.getComponent = exports.loadJsApi = exports.loadInstructions = exports.loadComplianceRules = exports.loadTokenData = exports.loadMeta = exports.loadIconData = exports.loadComponentsFromDir = exports.DEFAULT_DATA_DIR = void 0;
7
7
  /**
8
8
  * dataLoader — mcp/data/*.json + definitions/ 파일을 읽어 반환
9
9
  *
@@ -165,6 +165,24 @@ const loadInstructions = (definitionsDir) => {
165
165
  return fs_1.default.readFileSync(instructionsPath, 'utf-8').trim();
166
166
  };
167
167
  exports.loadInstructions = loadInstructions;
168
+ /** definitions/js-api.json을 로드하여 exportName→JsApiInfo Map 반환 */
169
+ const loadJsApi = (definitionsDir) => {
170
+ const filePath = path_1.default.resolve(definitionsDir, 'js-api.json');
171
+ if (!fs_1.default.existsSync(filePath)) {
172
+ logger_js_1.logger.info('js-api.json이 없습니다 — JS API 힌트 비활성화');
173
+ return new Map();
174
+ }
175
+ try {
176
+ const raw = fs_1.default.readFileSync(filePath, 'utf-8');
177
+ const data = JSON.parse(raw);
178
+ return new Map(Object.entries(data));
179
+ }
180
+ catch (err) {
181
+ logger_js_1.logger.error(`js-api.json 파싱 실패: ${(0, response_js_1.toErrorMessage)(err)}`);
182
+ return new Map();
183
+ }
184
+ };
185
+ exports.loadJsApi = loadJsApi;
168
186
  // ── 순수 함수 (외부 상태 없음, Map을 파라미터로 받음) ──────────────────────
169
187
  /** componentMap에서 이름으로 단일 컴포넌트 조회 */
170
188
  const getComponent = (map, name) => map.get(name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ncds/ui-admin-mcp",
3
- "version": "1.0.0-alpha.5",
3
+ "version": "1.0.0-alpha.7",
4
4
  "description": "NCDS UI Admin MCP 서버 — AI 에이전트가 NCUA 컴포넌트를 조회하고 HTML을 검증할 수 있는 MCP 서버",
5
5
  "bin": {
6
6
  "ncua-mcp": "./bin/server.mjs"