@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,64 @@
1
+ {
2
+ "patterns": [
3
+ {
4
+ "match": { "tag": "button" },
5
+ "ncuaComponent": "button",
6
+ "confidence": "high"
7
+ },
8
+ {
9
+ "match": { "tag": "input", "hints": [{ "attr": "type", "values": ["text", "search", "email", "url", "tel"] }] },
10
+ "ncuaComponent": "input-base",
11
+ "confidence": "high"
12
+ },
13
+ {
14
+ "match": { "tag": "input", "hints": [{ "attr": "type", "values": ["checkbox"] }] },
15
+ "ncuaComponent": "checkbox",
16
+ "confidence": "high"
17
+ },
18
+ {
19
+ "match": { "tag": "input", "hints": [{ "attr": "type", "values": ["radio"] }] },
20
+ "ncuaComponent": "radio",
21
+ "confidence": "high"
22
+ },
23
+ {
24
+ "match": { "tag": "input", "hints": [{ "attr": "type", "values": ["range"] }] },
25
+ "ncuaComponent": "slider",
26
+ "confidence": "high"
27
+ },
28
+ {
29
+ "match": { "tag": "textarea" },
30
+ "ncuaComponent": "textarea",
31
+ "confidence": "high"
32
+ },
33
+ {
34
+ "match": { "tag": "select" },
35
+ "ncuaComponent": "select",
36
+ "confidence": "high"
37
+ },
38
+ {
39
+ "match": { "tag": "dialog" },
40
+ "ncuaComponent": "modal",
41
+ "confidence": "high"
42
+ },
43
+ {
44
+ "match": { "tag": "progress" },
45
+ "ncuaComponent": "progress-bar",
46
+ "confidence": "high"
47
+ },
48
+ {
49
+ "match": { "tag": "nav", "hints": [{ "attr": "aria-label", "values": ["breadcrumb"] }] },
50
+ "ncuaComponent": "bread-crumb",
51
+ "confidence": "high"
52
+ },
53
+ {
54
+ "match": { "tag": "nav", "hints": [{ "attr": "aria-label", "values": ["pagination"] }] },
55
+ "ncuaComponent": "pagination",
56
+ "confidence": "medium"
57
+ },
58
+ {
59
+ "match": { "tag": "table" },
60
+ "ncuaComponent": "table",
61
+ "confidence": "medium"
62
+ }
63
+ ]
64
+ }
@@ -0,0 +1,96 @@
1
+ # NCUA MCP Server — Required Workflow
2
+
3
+ You are an agent that builds UI using NCUA (NCDS UI Admin) design system components from NHN Commerce.
4
+
5
+ ## Absolute Rules
6
+
7
+ 1. You MUST generate NCUA component HTML using the render_to_html tool only. Never write HTML/CSS manually.
8
+ 2. Do NOT define or guess CSS variables (--ncua-\*), color values, or BEM classes. They are all included in the CDN CSS.
9
+ 3. Do NOT write SVG icons manually. Use search_icon or list_icons to find icons.
10
+ 4. If an NCUA component exists for the use case, you MUST use it. Do NOT recreate it manually.
11
+
12
+ ## Required Workflow (follow this order strictly)
13
+
14
+ ### Step 0: Version Check (do this once per session)
15
+
16
+ 1. Call `ping` to get the MCP server info. Note `cdn.version` (ui-admin) and `icon.version` (ui-admin-icon).
17
+ 2. Check the project's `package.json` for `@ncds/ui-admin`:
18
+ - **Not found** → The project uses HTML+CDN mode. Skip version comparison and proceed to Step 1.
19
+ - **Found** → Compare the project's `@ncds/ui-admin` major.minor version with `cdn.version` (ignore patch).
20
+ 3. If versions **match** → Proceed to Step 1.
21
+ 4. If versions **differ**:
22
+ - Warn the user: "MCP data is based on @ncds/ui-admin X.Y, but your project uses Z.W. Generated code may not match your installed version."
23
+ - Wait for the user's confirmation before proceeding. Do NOT upgrade or downgrade package versions on your own.
24
+ 5. For **React projects**, also compare `@ncds/ui-admin-icon` major.minor in `package.json` with `icon.version` from ping. Apply the same mismatch rules.
25
+ 6. `ping.version` is the MCP server's own release version — do NOT compare it with any project package. Only `cdn.version` and `icon.version` matter.
26
+ 7. The `dataVersion` field in `render_to_html` responses shows the same versions as ping. One ping call per session is sufficient — no need to re-check on every `render_to_html` call.
27
+
28
+ ### Step 1: Component Discovery
29
+
30
+ - Use **search_component** with Korean/English keywords to find the right component
31
+ - Example: "비밀번호" → password-input, "모달" → modal
32
+ - **IMPORTANT**: input is for plain text only. For passwords, always use password-input.
33
+
34
+ ### Step 2: Props Check
35
+
36
+ - Use **get_component_props** to see available properties
37
+ - Pass all required props. Choose enum values only from the allowed list.
38
+
39
+ ### Step 3: HTML Generation
40
+
41
+ - Use **render_to_html** to get the exact HTML for each component
42
+ - Use the returned html as-is. Do NOT modify class names, structure, or attributes.
43
+
44
+ ### Step 4: CDN Inclusion
45
+
46
+ - Get CSS/JS URLs from the **cdn** field in the render_to_html response
47
+ - Include them in <head>/<body> of the final HTML
48
+ - When **js.required is true**, include the CDN JS <script> tag. This is mandatory.
49
+
50
+ ### Step 5: Composition
51
+
52
+ - Combine render_to_html results to build the final page
53
+ - Do NOT modify NCUA component styles
54
+
55
+ ## Building Custom Areas (not covered by NCUA)
56
+
57
+ When NCUA does not have a component for your needs, you may write custom HTML/CSS with these rules:
58
+
59
+ 1. **No ncua- prefix** — Use a unique BEM prefix to avoid confusion with NCUA components (e.g. pw-form, order-summary)
60
+ 2. **BEM naming** — Write custom classes using BEM convention (e.g. pw-form**title, pw-form**field--error)
61
+ 3. **Reuse NCUA design tokens ONLY** — Use ONLY CSS variables that actually exist in the CDN CSS. Do NOT invent tokens.
62
+ - Colors: var(--color-text-primary), var(--color-border-secondary), var(--color-background-primary), etc.
63
+ - Fonts: var(--font-size-_), var(--font-weight-_), etc.
64
+ - Spacing: var(--spacing-\*), etc.
65
+ - Do NOT hardcode hex values like #5B5BD6 or rgb values. Always use existing token variables.
66
+ - If no exact token exists for your need, choose the closest available token. Do NOT create new tokens or hex values.
67
+ 4. **Clear separation** — NCUA component areas (from render_to_html) and custom areas must be clearly distinguishable
68
+
69
+ ## Step 6: Design System Compliance Check (after Step 5)
70
+
71
+ After composing the final HTML, validate design system compliance:
72
+
73
+ 1. Call **validate_html** with the generated HTML
74
+ 2. Check `compliance.score` in the response:
75
+ - **score = 1.0** → All design system rules followed. Proceed.
76
+ - **score < 1.0** → Fix the reported errors and re-validate.
77
+ 3. **Self-correction loop** (max 3 attempts):
78
+ - Read `compliance.errors` — each error has a `type`, `target`, and `suggestion`
79
+ - Fix based on error type:
80
+ - `ncua_not_used` → Replace native HTML element with the suggested NCUA component (call search_component)
81
+ - `token_not_used` → Replace hardcoded color with the suggested CSS variable
82
+ - `invalid_token` → Call get_design_tokens to find the correct token name
83
+ - `custom_not_separated` → Move custom styles to a wrapper outside the NCUA component
84
+ - Re-call validate_html with the fixed HTML
85
+ - Repeat until score = 1.0 or 3 attempts reached
86
+
87
+ **Note:** `compliance.score` is independent from `valid`. `valid=true` with `score<1.0` means BEM classes are correct but design system usage can be improved.
88
+
89
+ ## Component Selection Guide
90
+
91
+ - Password → **password-input** (never use input)
92
+ - Numbers → **number-input**
93
+ - File upload → **file-input**
94
+ - Image upload → **image-file-input**
95
+ - Plain text → **input**
96
+ - Each component's description includes usage guidance and alternatives.
@@ -0,0 +1,57 @@
1
+ {
2
+ "workflow": [
3
+ "You MUST generate component HTML using the render_to_html tool. Never write HTML/CSS manually.",
4
+ "Use the HTML returned by render_to_html as-is. Do NOT modify class names, structure, or attributes.",
5
+ "Use search_component to find the right component by Korean/English keywords before building any UI.",
6
+ "Use list_components to browse available components by category and choose the appropriate one."
7
+ ],
8
+ "componentSelection": [
9
+ "For password fields, you MUST use password-input. The input component is for plain text only.",
10
+ "For numbers use number-input, for file uploads use file-input, for image uploads use image-file-input.",
11
+ "Each component's description lists its intended use case and alternative components. Always read it."
12
+ ],
13
+ "version": [
14
+ "The MCP server's own version (ping.version) is independent from the design system packages. Do NOT compare it with project packages.",
15
+ "ping.cdn.version shows which @ncds/ui-admin version this MCP data was built from. ping.icon.version shows the @ncds/ui-admin-icon version.",
16
+ "The dataVersion field in render_to_html response contains the same values as ping.cdn.version and ping.icon.version. One ping per session is sufficient.",
17
+ "HTML (CDN) mode: if the project does not have @ncds/ui-admin in package.json, it uses CDN-only mode. Skip version comparison.",
18
+ "If the project has @ncds/ui-admin in package.json, compare its major.minor version with ping.cdn.version (ignore patch). For React projects, also compare @ncds/ui-admin-icon with ping.icon.version.",
19
+ "If versions match, proceed normally. If versions differ, warn the user and wait for their confirmation BEFORE proceeding.",
20
+ "Do NOT upgrade or downgrade @ncds/ui-admin or @ncds/ui-admin-icon versions on your own. Version changes require explicit team approval."
21
+ ],
22
+ "cdn": [
23
+ "You MUST include CDN CSS and JS URLs in the final HTML. The cdn field in render_to_html response has the exact URLs.",
24
+ "When js.required is true in render_to_html response, include the CDN JS <script> tag. This is mandatory.",
25
+ "In HTML mode, icons are rendered as SVG via CDN JS. No separate package installation is needed.",
26
+ "When js.required is true, the component needs its own JS for interactivity — ensure the CDN JS script is loaded."
27
+ ],
28
+ "react": [
29
+ "React projects require @ncds/ui-admin and @ncds/ui-admin-icon packages. Install ONLY packages listed in react.dependencies.",
30
+ "For non-developer users, the agent should check package.json and auto-install required packages.",
31
+ "Use react.jsx code as-is from render_to_html response. Do NOT write JSX manually — this prevents hallucination.",
32
+ "Icons in React are imported as components from @ncds/ui-admin-icon (e.g. import { ChevronDown } from '@ncds/ui-admin-icon')."
33
+ ],
34
+ "forbidden": [
35
+ "Do NOT override ncua-* classes with custom CSS. Control appearance through component props only.",
36
+ "Do NOT define CSS variables (--ncua-*) yourself. They are already included in the CDN CSS.",
37
+ "Do NOT write SVG icons manually. Use search_icon to find icons from the icon library."
38
+ ],
39
+ "customArea": [
40
+ "When building areas not covered by NCUA components, do NOT use the ncua- prefix. Use a unique BEM prefix (e.g. pw-form, order-summary).",
41
+ "Custom areas MUST use ONLY existing NCUA design tokens (CSS variables from CDN) for colors, fonts, spacing, and shadows. Do NOT hardcode hex values like #5B5BD6 or rgb values.",
42
+ "Do NOT invent token names. If no exact token exists, choose the closest available token. Never fall back to raw color/size values.",
43
+ "Before writing custom CSS, call get_design_tokens to see available tokens. Filter by category (color, typography, spacing, shadow) to reduce response size.",
44
+ "For spacing, use spacing tokens (--spacing-xs through --spacing-xxl) instead of hardcoded px values. Follow the principle: inner-block spacing < between-block spacing."
45
+ ],
46
+ "compliance": [
47
+ "After generating HTML, call validate_html to check the compliance score. If compliance.score < 1.0, fix the reported errors and re-validate.",
48
+ "Self-correction loop: generate HTML → validate_html → fix errors → re-validate. Repeat until score = 1.0 (max 3 attempts).",
49
+ "If validate_html reports ncua_not_used, replace native HTML elements with the suggested NCUA component using search_component.",
50
+ "If validate_html reports token_not_used, replace hardcoded color values with the suggested CSS variable (var(--token-name)).",
51
+ "If validate_html reports invalid_token, call get_design_tokens to find the correct token name.",
52
+ "If validate_html reports custom_not_separated, move custom styles to a wrapper element outside the NCUA component."
53
+ ],
54
+ "category": [
55
+ "Component categories follow the design team standard (DES INDEX): action, input, icon, overlay, navigation, feedback, data-display."
56
+ ]
57
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "categoryDescriptions": {
3
+ "color": "Design system color tokens. Use these CSS variables instead of hex/rgb values in custom areas.",
4
+ "typography": "Typography tokens: font-size, line-height, font-weight, font-family.",
5
+ "shadow": "Shadow and focus-ring tokens for elevation and focus states."
6
+ },
7
+ "groupDescriptions": {
8
+ "primary-red": "Brand primary. 450 is the default CTA color.",
9
+ "secondary-gray-blue": "Secondary accent color.",
10
+ "base": "Pure white (#fff) and black (#000).",
11
+ "gray": "Neutral color. Text (700-500), border (200-300), background (50-100).",
12
+ "green": "Success / positive state.",
13
+ "cyan": "Informational accent.",
14
+ "blue": "Information / link state.",
15
+ "violet": "Accent / decorative.",
16
+ "pink": "Accent / decorative.",
17
+ "orange": "Warning state.",
18
+ "yellow": "Caution / highlight.",
19
+ "font-families": "Base font family (Commerce Sans).",
20
+ "font-size": "Font size scale: 0 (12px) to 10 (72px).",
21
+ "line-heights": "Line height scale: 0 (90) to 10 (18). Pair with font-size.",
22
+ "font-weights": "Font weight: 0 (Regular), 1 (Medium), 2 (Bold).",
23
+ "focus-ring": "Focus ring shadows for interactive elements.",
24
+ "shadow": "Elevation shadow scale: xs to 3xl.",
25
+ "shadow-portfolio-mockup": "Portfolio mockup layout shadows."
26
+ }
27
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "tools": {
3
+ "ping": {
4
+ "description": "Check NCUA MCP server health, version, capabilities, and usage rules."
5
+ },
6
+ "list_icons": {
7
+ "description": "List all available icons from the @ncds/ui-admin-icon package."
8
+ },
9
+ "search_icon": {
10
+ "description": "Search icons by keyword. Supports name and kebab-case lookups."
11
+ },
12
+ "list_components": {
13
+ "description": "List all available UI components grouped by category. Check this before building any UI to understand what components exist."
14
+ },
15
+ "search_component": {
16
+ "description": "Search components by Korean/English keyword. Returns name, category, description, aliases."
17
+ },
18
+ "get_component_props": {
19
+ "description": "Get the available props (properties) for a component. Use this before render_to_html to understand what props you can pass."
20
+ },
21
+ "validate_html": {
22
+ "description": "Validate HTML markup for NCUA BEM class correctness AND design system compliance (component usage, token usage, custom separation). Returns errors, auto-fix suggestions, and a compliance score (0-1)."
23
+ },
24
+ "render_to_html": {
25
+ "description": "Generate exact HTML for an NCUA component with given props. Returns html, appliedProps, defaultsUsed, js, cdn."
26
+ },
27
+ "get_design_tokens": {
28
+ "description": "Get available design tokens (CSS variables) for colors, typography, spacing, and shadows. Use when building custom areas to pick real tokens instead of guessing."
29
+ }
30
+ },
31
+ "capabilities": [
32
+ { "tool": "ping", "description": "Server health check + version + capabilities/rules" },
33
+ { "tool": "list_components", "description": "Browse all components by category" },
34
+ { "tool": "search_component", "description": "Find components by keyword" },
35
+ { "tool": "list_icons", "description": "Browse all available icons" },
36
+ { "tool": "search_icon", "description": "Find icons by keyword" },
37
+ { "tool": "get_component_props", "description": "Get component props spec" },
38
+ { "tool": "validate_html", "description": "Validate HTML BEM classes + design system compliance score + auto-fix" },
39
+ { "tool": "render_to_html", "description": "Props-based dynamic HTML rendering" },
40
+ { "tool": "get_design_tokens", "description": "Design token (CSS variable) lookup by category" }
41
+ ]
42
+ }
@@ -0,0 +1 @@
1
+ export declare const INSTRUCTIONS: string;
@@ -0,0 +1,14 @@
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.INSTRUCTIONS = void 0;
7
+ /**
8
+ * AI 에이전트가 MCP 서버 연결 시 자동으로 수신하는 필수 지침.
9
+ * definitions/instructions.md에서 로드한다.
10
+ */
11
+ const path_1 = __importDefault(require("path"));
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const INSTRUCTIONS_PATH = path_1.default.resolve(__dirname, 'definitions', 'instructions.md');
14
+ exports.INSTRUCTIONS = fs_1.default.readFileSync(INSTRUCTIONS_PATH, 'utf-8').trim();
@@ -0,0 +1 @@
1
+ export {};
package/bin/server.js ADDED
@@ -0,0 +1,164 @@
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
+ /**
7
+ * MCP 서버 진입점 — 모든 데이터 소유, tool 등록, transport 연결
8
+ *
9
+ * 이 파일만 Action(부수효과)을 포함한다. tool 핸들러는 모두 Calculation.
10
+ */
11
+ const path_1 = __importDefault(require("path"));
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
14
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
15
+ const zod_1 = require("zod");
16
+ const dataLoader_js_1 = require("./utils/dataLoader.js");
17
+ const logger_js_1 = require("./utils/logger.js");
18
+ const listComponents_js_1 = require("./tools/listComponents.js");
19
+ const searchComponent_js_1 = require("./tools/searchComponent.js");
20
+ const getComponentProps_js_1 = require("./tools/getComponentProps.js");
21
+ const validateHtml_js_1 = require("./tools/validateHtml.js");
22
+ const ping_js_1 = require("./tools/ping.js");
23
+ const listIcons_js_1 = require("./tools/listIcons.js");
24
+ const searchIcon_js_1 = require("./tools/searchIcon.js");
25
+ const renderToHtml_js_1 = require("./tools/renderToHtml.js");
26
+ const getDesignTokens_js_1 = require("./tools/getDesignTokens.js");
27
+ const version_js_1 = require("./version.js");
28
+ const domEnvironment_js_1 = require("./utils/domEnvironment.js");
29
+ // ── definitions/ 로딩 헬퍼 (Action) ─────────────────────────────────────────
30
+ /** tool-definitions.json을 1회 로딩하여 descriptions + capabilities를 분리 반환 */
31
+ const loadToolDefinitions = (definitionsDir) => {
32
+ const raw = fs_1.default.readFileSync(path_1.default.resolve(definitionsDir, 'tool-definitions.json'), 'utf-8');
33
+ const data = JSON.parse(raw);
34
+ // JSON 구조 검증 — 타입 단언만으로 신뢰하지 않음 (§ 2.5)
35
+ const tools = data.tools;
36
+ if (!tools || typeof tools !== 'object') {
37
+ throw new Error('tool-definitions.json에 tools 키가 없거나 올바르지 않습니다');
38
+ }
39
+ const descriptions = {};
40
+ for (const [name, def] of Object.entries(tools)) {
41
+ if (def.description)
42
+ descriptions[name] = def.description;
43
+ }
44
+ const capabilities = data.capabilities;
45
+ if (!Array.isArray(capabilities)) {
46
+ throw new Error('tool-definitions.json에 capabilities 배열이 없습니다');
47
+ }
48
+ return { descriptions, capabilities };
49
+ };
50
+ /** rules.json을 로딩하여 flat 배열로 반환 */
51
+ const loadRules = (definitionsDir) => {
52
+ const raw = fs_1.default.readFileSync(path_1.default.join(definitionsDir, 'rules.json'), 'utf-8');
53
+ const data = JSON.parse(raw);
54
+ const RULE_KEYS = [
55
+ 'workflow',
56
+ 'componentSelection',
57
+ 'version',
58
+ 'cdn',
59
+ 'react',
60
+ 'forbidden',
61
+ 'customArea',
62
+ 'compliance',
63
+ 'category',
64
+ ];
65
+ const rules = [];
66
+ for (const key of RULE_KEYS) {
67
+ const arr = data[key];
68
+ if (!Array.isArray(arr)) {
69
+ throw new Error(`rules.json에 ${key} 배열이 없습니다`);
70
+ }
71
+ rules.push(...arr);
72
+ }
73
+ return rules;
74
+ };
75
+ // ── 진입점 ───────────────────────────────────────────────────────────────────
76
+ const main = async () => {
77
+ const definitionsDir = path_1.default.resolve(__dirname, 'definitions');
78
+ // ── definitions/ 로딩 (main() 안에서 실행하여 catch에 포함) ──
79
+ const { descriptions, capabilities } = loadToolDefinitions(definitionsDir);
80
+ const rules = loadRules(definitionsDir);
81
+ const instructions = (0, dataLoader_js_1.loadInstructions)(definitionsDir);
82
+ const complianceRules = (0, dataLoader_js_1.loadComplianceRules)(definitionsDir);
83
+ // ── 데이터 로딩 ──
84
+ const componentMap = (0, dataLoader_js_1.loadComponentsFromDir)(dataLoader_js_1.DEFAULT_DATA_DIR);
85
+ const { cdn: cdnMeta, icon: iconMeta } = (0, dataLoader_js_1.loadMeta)(dataLoader_js_1.DEFAULT_DATA_DIR);
86
+ const iconData = (0, dataLoader_js_1.loadIconData)(dataLoader_js_1.DEFAULT_DATA_DIR);
87
+ const tokenData = (0, dataLoader_js_1.loadTokenData)(dataLoader_js_1.DEFAULT_DATA_DIR);
88
+ const bundlePath = path_1.default.resolve(__dirname, '..', 'bin', 'components.bundle.js');
89
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
90
+ const bundle = require(bundlePath);
91
+ logger_js_1.logger.info('컴포넌트 번들 로딩 완료');
92
+ // ── DOM + React 런타임 초기화 (유틸로 추출 § 3.9) ──
93
+ const reactRuntime = (0, domEnvironment_js_1.setupDomEnvironment)();
94
+ logger_js_1.logger.info('DOM + React 런타임 초기화 완료');
95
+ // ── 파생 데이터 사전 계산 (code-guide § 5.1 반복 계산 제거) ──
96
+ const rootClassMap = (0, validateHtml_js_1.buildRootClassMap)(componentMap);
97
+ const tokenValueMap = (0, validateHtml_js_1.buildTokenValueMap)(tokenData);
98
+ const groupedComponents = (0, listComponents_js_1.buildGroupedComponents)(componentMap);
99
+ const iconSummary = (0, listIcons_js_1.buildIconSummary)(iconData);
100
+ const listComponentsResponse = (0, listComponents_js_1.buildListComponentsResponse)(groupedComponents);
101
+ const listIconsResponse = (0, listIcons_js_1.buildListIconsResponse)(iconSummary);
102
+ // ── MCP 서버 생성 + tool 등록 ──
103
+ const server = new mcp_js_1.McpServer({ name: 'ncds-ui-admin', version: version_js_1.VERSION }, { instructions });
104
+ server.registerTool('ping', { description: descriptions['ping'] }, () => (0, ping_js_1.ping)({ componentMap, cdnMeta, iconMeta, version: version_js_1.VERSION, rules, capabilities }));
105
+ server.registerTool('list_icons', { description: descriptions['list_icons'] }, () => (0, listIcons_js_1.listIcons)(listIconsResponse));
106
+ server.registerTool('search_icon', {
107
+ description: descriptions['search_icon'],
108
+ inputSchema: { query: zod_1.z.string().describe('Search keyword (e.g. "search", "alert", "arrow")') },
109
+ }, ({ query }) => (0, searchIcon_js_1.searchIcon)(iconData, query));
110
+ server.registerTool('list_components', { description: descriptions['list_components'] }, () => (0, listComponents_js_1.listComponents)(listComponentsResponse));
111
+ server.registerTool('search_component', {
112
+ description: descriptions['search_component'],
113
+ inputSchema: { query: zod_1.z.string().describe('Search keyword (Korean/English, case-insensitive)') },
114
+ }, ({ query }) => (0, searchComponent_js_1.searchComponent)(componentMap, query));
115
+ server.registerTool('get_component_props', {
116
+ description: descriptions['get_component_props'],
117
+ inputSchema: { name: zod_1.z.string().min(1).describe('Component name (e.g. "button", "password-input")') },
118
+ }, ({ name }) => (0, getComponentProps_js_1.getComponentProps)(componentMap, name));
119
+ server.registerTool('validate_html', {
120
+ description: descriptions['validate_html'],
121
+ inputSchema: {
122
+ html: zod_1.z
123
+ .string()
124
+ .min(1)
125
+ .describe('HTML markup to validate (e.g. \'<button class="ncua-btn ncua-btn--primary"></button>\')'),
126
+ },
127
+ }, ({ html }) => (0, validateHtml_js_1.validateHtml)({ componentMap, rootClassMap, html, cdnMeta, tokenData, complianceRules, tokenValueMap }));
128
+ server.registerTool('render_to_html', {
129
+ description: descriptions['render_to_html'],
130
+ inputSchema: {
131
+ name: zod_1.z.string().min(1).describe('Component name (e.g. "button", "modal", "password-input")'),
132
+ props: zod_1.z
133
+ .record(zod_1.z.string(), zod_1.z.unknown())
134
+ .optional()
135
+ .describe('Component props (e.g. { label: "OK", hierarchy: "primary" })'),
136
+ },
137
+ },
138
+ // MCP SDK의 zod 추론 타입이 Record<string, unknown> | undefined와 미스매치하여 as 필요
139
+ ({ name, props }) => (0, renderToHtml_js_1.renderToHtml)({
140
+ componentMap,
141
+ bundle,
142
+ cdnMeta,
143
+ iconMeta,
144
+ reactRuntime,
145
+ name,
146
+ props: props,
147
+ }));
148
+ server.registerTool('get_design_tokens', {
149
+ description: descriptions['get_design_tokens'],
150
+ inputSchema: {
151
+ category: zod_1.z
152
+ .string()
153
+ .optional()
154
+ .describe('Token category filter (e.g. "color", "typography", "shadow", "spacing"). Omit to get all tokens.'),
155
+ },
156
+ }, ({ category }) => (0, getDesignTokens_js_1.getDesignTokens)({ tokenData, category }));
157
+ const transport = new stdio_js_1.StdioServerTransport();
158
+ await server.connect(transport);
159
+ logger_js_1.logger.info(`NCUA MCP 서버 시작됨 (ncds-ui-admin v${version_js_1.VERSION})`);
160
+ };
161
+ main().catch((err) => {
162
+ process.stderr.write(`[ERROR] 서버 시작 실패: ${err instanceof Error ? err.message : String(err)}\n`);
163
+ process.exit(1);
164
+ });
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,4 @@
1
+ import type { ComponentData } from '../types.js';
2
+ import { type McpToolResponse } from '../utils/response.js';
3
+ /** get_component_props tool — 컴포넌트의 props 스펙을 JSON으로 반환 */
4
+ export declare const getComponentProps: (componentMap: Map<string, ComponentData>, name: string) => McpToolResponse;
@@ -0,0 +1,17 @@
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
+ /** get_component_props tool — 컴포넌트의 props 스펙을 JSON으로 반환 */
10
+ const getComponentProps = (componentMap, name) => {
11
+ const normalized = (0, response_js_1.normalizeName)(name);
12
+ const component = (0, dataLoader_js_1.getComponent)(componentMap, normalized);
13
+ if (!component)
14
+ return (0, response_js_1.componentNotFoundResponse)(normalized);
15
+ return (0, response_js_1.successResponse)(component.props);
16
+ };
17
+ exports.getComponentProps = getComponentProps;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * get_design_tokens tool — 디자인 토큰(CSS 변수) 조회
3
+ *
4
+ * 커스텀 영역 작성 시 에이전트가 실제 존재하는 토큰만 사용하도록
5
+ * 빌드타임 추출된 토큰 목록을 카테고리별로 제공한다.
6
+ */
7
+ import type { TokenData } from '../types.js';
8
+ import { type McpToolResponse } from '../utils/response.js';
9
+ export interface GetDesignTokensParams {
10
+ tokenData: TokenData;
11
+ category?: string;
12
+ }
13
+ export declare const getDesignTokens: ({ tokenData, category }: GetDesignTokensParams) => McpToolResponse;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDesignTokens = void 0;
4
+ const response_js_1 = require("../utils/response.js");
5
+ const getDesignTokens = ({ tokenData, category }) => {
6
+ if (!category) {
7
+ return (0, response_js_1.successResponse)(tokenData);
8
+ }
9
+ if (!tokenData.categories.includes(category)) {
10
+ return (0, response_js_1.errorResponse)('INVALID_TOKEN_CATEGORY', `Category '${category}' not found. Available: ${tokenData.categories.join(', ')}`, 'Use get_design_tokens without category to see all available categories and tokens.');
11
+ }
12
+ const filteredGroups = tokenData.groups.filter((group) => group.category === category);
13
+ const filteredCount = filteredGroups.reduce((sum, group) => sum + group.tokens.length, 0);
14
+ return (0, response_js_1.successResponse)({
15
+ totalCount: filteredCount,
16
+ categories: [category],
17
+ groups: filteredGroups,
18
+ });
19
+ };
20
+ exports.getDesignTokens = getDesignTokens;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * list_components tool — 사전 계산된 카테고리별 컴포넌트 목록 반환 (순수 함수)
3
+ */
4
+ import type { ComponentData } from '../types.js';
5
+ import { type McpToolResponse } from '../utils/response.js';
6
+ /** 카테고리별 컴포넌트 그룹 타입 */
7
+ export type GroupedComponents = Record<string, Array<{
8
+ name: string;
9
+ description: string;
10
+ }>>;
11
+ /** componentMap에서 카테고리별 그룹을 사전 계산 — server.ts에서 1회 호출 */
12
+ export declare const buildGroupedComponents: (componentMap: Map<string, ComponentData>) => GroupedComponents;
13
+ /** GroupedComponents를 사전 직렬화 — server.ts에서 1회 호출 */
14
+ export declare const buildListComponentsResponse: (grouped: GroupedComponents) => McpToolResponse;
15
+ /** list_components tool — 사전 직렬화된 응답을 그대로 반환 */
16
+ export declare const listComponents: (prebuilt: McpToolResponse) => McpToolResponse;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listComponents = exports.buildListComponentsResponse = exports.buildGroupedComponents = void 0;
4
+ const response_js_1 = require("../utils/response.js");
5
+ /** componentMap에서 카테고리별 그룹을 사전 계산 — server.ts에서 1회 호출 */
6
+ const buildGroupedComponents = (componentMap) => {
7
+ const sorted = [...componentMap.values()]
8
+ .map((c) => ({ name: c.name, category: c.category, description: c.description }))
9
+ .sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
10
+ const grouped = {};
11
+ for (const c of sorted) {
12
+ if (!grouped[c.category])
13
+ grouped[c.category] = [];
14
+ grouped[c.category].push({ name: c.name, description: c.description });
15
+ }
16
+ return grouped;
17
+ };
18
+ exports.buildGroupedComponents = buildGroupedComponents;
19
+ /** GroupedComponents를 사전 직렬화 — server.ts에서 1회 호출 */
20
+ const buildListComponentsResponse = (grouped) => (0, response_js_1.successResponse)(grouped);
21
+ exports.buildListComponentsResponse = buildListComponentsResponse;
22
+ /** list_components tool — 사전 직렬화된 응답을 그대로 반환 */
23
+ const listComponents = (prebuilt) => prebuilt;
24
+ exports.listComponents = listComponents;