@axis-ds/mcp 1.1.5 → 1.2.0

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 (3) hide show
  1. package/README.md +13 -0
  2. package/dist/index.js +478 -8
  3. package/package.json +5 -3
package/README.md CHANGED
@@ -25,6 +25,19 @@ Claude Desktop 또는 기타 MCP 호환 클라이언트에서 설정:
25
25
  }
26
26
  ```
27
27
 
28
+ ## 브랜드 도구
29
+
30
+ 대상 프로젝트에 설치된 `@axis-ds/tokens`(1.3.0 이상)의 브랜드를 읽습니다. 패키지에 데이터를 번들하지 않으며, CLI `axis theme list` · `axis theme info` 와 같은 내용을 돌려줍니다.
31
+
32
+ | 도구 | 인자 | 응답 |
33
+ |------|------|------|
34
+ | `axis.list_brands` | `targetDir?` | 브랜드 이름 · 설명 · 테마 · import 경로 |
35
+ | `axis.get_brand` | `name`, `targetDir?` | import 경로 · Provider 예시(`defaultBrand`) · 주요 토큰(라이트 · 다크) · 보정 내역(a11y 메타) |
36
+
37
+ - `targetDir` 를 주지 않으면 서버의 현재 디렉터리를 봅니다.
38
+ - `@axis-ds/tokens` 가 없거나 1.3.0 미만이거나 빌드 산출물이 없으면 오류(`isError`)가 아니라 설치 · 업그레이드 안내를 돌려줍니다. 빈 목록으로 답하지 않습니다.
39
+ - 프로젝트에 적용하는 일(import 추가 · `axis.config.json` 의 `brand`)은 CLI `axis theme add <brand>` 가 맡습니다.
40
+
28
41
  ## 문서
29
42
 
30
43
  자세한 사용법은 [AXIS Design System 문서](https://axis.minu.best)를 참고하세요.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
+ import { createRequire as createRequire2 } from "module";
4
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
7
  import { z } from "zod";
@@ -64,10 +65,10 @@ function listTokens(category) {
64
65
  }
65
66
  return registry.tokens;
66
67
  }
67
- function getToken(path4) {
68
+ function getToken(path5) {
68
69
  const registry = getRegistry();
69
70
  return registry.tokens.find(
70
- (t) => t.path.toLowerCase() === path4.toLowerCase()
71
+ (t) => t.path.toLowerCase() === path5.toLowerCase()
71
72
  );
72
73
  }
73
74
  function getComponentStats() {
@@ -151,11 +152,11 @@ function handleGetComponent(params) {
151
152
  return component;
152
153
  }
153
154
  function handleGetToken(params) {
154
- const { path: path4 } = params;
155
- if (!path4 || path4.trim() === "") {
155
+ const { path: path5 } = params;
156
+ if (!path5 || path5.trim() === "") {
156
157
  throw new Error("\uD1A0\uD070 \uACBD\uB85C(path)\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.");
157
158
  }
158
- const token = getToken(path4);
159
+ const token = getToken(path5);
159
160
  if (!token) {
160
161
  return null;
161
162
  }
@@ -2068,6 +2069,251 @@ function formatSaveResult(result) {
2068
2069
  import fetch from "node-fetch";
2069
2070
  import fs3 from "fs-extra";
2070
2071
  import path3 from "path";
2072
+
2073
+ // src/tools/brand-core.ts
2074
+ import { existsSync as existsSync4, readFileSync as readFileSync5, statSync } from "fs";
2075
+ import { createRequire } from "module";
2076
+ import { dirname as dirname4, join as join4, relative, resolve as resolve2, sep } from "path";
2077
+ var TOKENS_PACKAGE = "@axis-ds/tokens";
2078
+ var MIN_BRAND_TOKENS_VERSION = "1.3.0";
2079
+ var KEY_TOKENS = [
2080
+ "--primary",
2081
+ "--primary-foreground",
2082
+ "--background",
2083
+ "--foreground",
2084
+ "--ring",
2085
+ "--radius",
2086
+ "--axis-surface-default",
2087
+ "--axis-text-primary",
2088
+ "--axis-text-brand",
2089
+ "--axis-button-bg-default",
2090
+ "--axis-border-focus",
2091
+ "--axis-font-family-sans"
2092
+ ];
2093
+ var BRAND_ADJUSTMENT_REFERENCE = "WI-0025 PRD \uBD80\uB85D B (docs/workitems/WI-0025-brand-theme/prd.md)";
2094
+ function readJsonFile(file) {
2095
+ return JSON.parse(readFileSync5(file, "utf8"));
2096
+ }
2097
+ function errorCode(err) {
2098
+ return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
2099
+ }
2100
+ function findTokensPackageJson(baseDir) {
2101
+ let dir = resolve2(baseDir);
2102
+ for (; ; ) {
2103
+ const candidate = join4(dir, "node_modules", TOKENS_PACKAGE, "package.json");
2104
+ if (existsSync4(candidate)) return candidate;
2105
+ const parent = dirname4(dir);
2106
+ if (parent === dir) return void 0;
2107
+ dir = parent;
2108
+ }
2109
+ }
2110
+ function projectRequire(baseDir) {
2111
+ return createRequire(join4(resolve2(baseDir), "package.json"));
2112
+ }
2113
+ function tokensSpecifier(subpath) {
2114
+ return `${TOKENS_PACKAGE}/${subpath.replace(/^\.\//, "")}`;
2115
+ }
2116
+ function resolveTokens(baseDir) {
2117
+ const pkgJson = findTokensPackageJson(baseDir);
2118
+ if (!pkgJson) return { status: "not-installed" };
2119
+ const pkg = readJsonFile(pkgJson);
2120
+ const version = typeof pkg.version === "string" ? pkg.version : void 0;
2121
+ try {
2122
+ const indexPath = projectRequire(baseDir).resolve(tokensSpecifier("./brands"));
2123
+ return { status: "ok", version, indexPath };
2124
+ } catch (err) {
2125
+ const code = errorCode(err);
2126
+ if (code === "ERR_PACKAGE_PATH_NOT_EXPORTED") return { status: "unsupported", version };
2127
+ if (code === "MODULE_NOT_FOUND") return { status: "not-built", version };
2128
+ throw err;
2129
+ }
2130
+ }
2131
+ function brandImportPath(entry) {
2132
+ return tokensSpecifier(entry.css);
2133
+ }
2134
+ var GLOBAL_CSS_CANDIDATES = ["app/globals.css", "src/app/globals.css", "globals.css"];
2135
+ function isFile(file) {
2136
+ try {
2137
+ return statSync(file).isFile();
2138
+ } catch {
2139
+ return false;
2140
+ }
2141
+ }
2142
+ function findGlobalCss(baseDir, config) {
2143
+ const root = resolve2(baseDir);
2144
+ const result = {};
2145
+ const configured = config?.globalCss;
2146
+ if (typeof configured === "string" && configured.trim() !== "") {
2147
+ const file = resolve2(root, configured);
2148
+ if (isFile(file)) return { path: file };
2149
+ result.configuredMissing = configured;
2150
+ }
2151
+ for (const candidate of GLOBAL_CSS_CANDIDATES) {
2152
+ const file = join4(root, candidate);
2153
+ if (isFile(file)) {
2154
+ result.path = file;
2155
+ break;
2156
+ }
2157
+ }
2158
+ return result;
2159
+ }
2160
+ function findBrandImports(css) {
2161
+ const names = [];
2162
+ const pattern = /@import\s+(?:url\(\s*)?["']?@axis-ds\/tokens\/css\/brands\/([a-z][a-z0-9-]*)(?:\.css)?["']?\s*\)?\s*(?=[;\s]|$)/g;
2163
+ for (const m of stripComments(css).matchAll(pattern)) {
2164
+ names.push(m[1]);
2165
+ }
2166
+ return names;
2167
+ }
2168
+ var AXIS_CONFIG_FILE_NAME = "axis.config.json";
2169
+ function readBrandConfig(baseDir) {
2170
+ let parsed;
2171
+ try {
2172
+ parsed = JSON.parse(readFileSync5(join4(resolve2(baseDir), AXIS_CONFIG_FILE_NAME), "utf8"));
2173
+ } catch {
2174
+ return null;
2175
+ }
2176
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
2177
+ return parsed;
2178
+ }
2179
+ function inspectBrandImport(baseDir) {
2180
+ const config = readBrandConfig(baseDir);
2181
+ const brand = config?.brand;
2182
+ if (typeof brand !== "string" || brand.trim() === "") return null;
2183
+ const root = resolve2(baseDir);
2184
+ const location = findGlobalCss(root, config);
2185
+ const base = { brand };
2186
+ if (location.configuredMissing !== void 0) base.configuredMissing = location.configuredMissing;
2187
+ if (!location.path) {
2188
+ return { ...base, state: "css-missing", imports: [] };
2189
+ }
2190
+ const cssFile = relative(root, location.path).split(sep).join("/");
2191
+ const imports = [...new Set(findBrandImports(readFileSync5(location.path, "utf8")))];
2192
+ let state;
2193
+ if (imports.includes(brand)) state = "match";
2194
+ else if (imports.length === 0) state = "import-missing";
2195
+ else state = "other-brand";
2196
+ return { ...base, state, cssFile, imports };
2197
+ }
2198
+ function isMissingBuildFile(err) {
2199
+ const code = errorCode(err);
2200
+ return code === "MODULE_NOT_FOUND" || code === "ENOENT";
2201
+ }
2202
+ function readBrandIndex(indexPath) {
2203
+ const data = readJsonFile(indexPath);
2204
+ if (!Array.isArray(data.brands)) {
2205
+ throw new Error(`${indexPath}: "brands" \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4`);
2206
+ }
2207
+ return data.brands;
2208
+ }
2209
+ function readSummary(baseDir, entry) {
2210
+ const metaPath = projectRequire(baseDir).resolve(tokensSpecifier(entry.meta));
2211
+ const meta = readJsonFile(metaPath);
2212
+ return {
2213
+ name: entry.name,
2214
+ description: typeof meta.description === "string" ? meta.description : "",
2215
+ themes: Array.isArray(meta.themes) ? meta.themes.map(String) : [],
2216
+ a11y: typeof meta.a11y === "object" && meta.a11y !== null ? meta.a11y : {},
2217
+ importPath: brandImportPath(entry)
2218
+ };
2219
+ }
2220
+ function listBrands(baseDir) {
2221
+ const tokens = resolveTokens(baseDir);
2222
+ if (tokens.status !== "ok" || !tokens.indexPath) {
2223
+ return { status: tokens.status, version: tokens.version, brands: [] };
2224
+ }
2225
+ const entries = readBrandIndex(tokens.indexPath);
2226
+ let brands;
2227
+ try {
2228
+ brands = entries.map((entry) => readSummary(baseDir, entry));
2229
+ } catch (err) {
2230
+ if (isMissingBuildFile(err)) return { status: "not-built", version: tokens.version, brands: [] };
2231
+ throw err;
2232
+ }
2233
+ return { status: "ok", version: tokens.version, brands };
2234
+ }
2235
+ function stripComments(css) {
2236
+ return css.replace(/\/\*[\s\S]*?\*\//g, "");
2237
+ }
2238
+ function parseDeclarations(body) {
2239
+ const out = {};
2240
+ for (const m of body.matchAll(/(--[A-Za-z0-9_-]+)\s*:\s*([^;]+);/g)) {
2241
+ out[m[1]] = m[2].trim();
2242
+ }
2243
+ return out;
2244
+ }
2245
+ function isDarkSelector(selector) {
2246
+ return /\.dark\b/.test(selector) || /\[data-theme=["']?dark["']?\]/.test(selector);
2247
+ }
2248
+ function parseBrandCss(css, name) {
2249
+ const result = { light: {}, dark: {} };
2250
+ let lightFound = false;
2251
+ let darkFound = false;
2252
+ const brandAttr = new RegExp(`\\[data-brand=["']?${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["']?\\]`);
2253
+ for (const m of stripComments(css).matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
2254
+ const selectors = m[1].split(",").map((s) => s.trim());
2255
+ if (!selectors.some((s) => brandAttr.test(s))) continue;
2256
+ if (selectors.some(isDarkSelector)) {
2257
+ if (!darkFound) {
2258
+ result.dark = parseDeclarations(m[2]);
2259
+ darkFound = true;
2260
+ }
2261
+ } else if (!lightFound) {
2262
+ result.light = parseDeclarations(m[2]);
2263
+ lightFound = true;
2264
+ }
2265
+ }
2266
+ return result;
2267
+ }
2268
+ function pickKeyTokens(decls) {
2269
+ const out = {};
2270
+ for (const key of KEY_TOKENS) {
2271
+ if (Object.prototype.hasOwnProperty.call(decls, key)) out[key] = decls[key];
2272
+ }
2273
+ return out;
2274
+ }
2275
+ function providerExample(name) {
2276
+ return [
2277
+ 'import { ThemeProvider } from "@axis-ds/theme";',
2278
+ "",
2279
+ `<ThemeProvider defaultBrand="${name}">{children}</ThemeProvider>`
2280
+ ].join("\n");
2281
+ }
2282
+ function getBrand(baseDir, name) {
2283
+ const tokens = resolveTokens(baseDir);
2284
+ if (tokens.status !== "ok" || !tokens.indexPath) {
2285
+ return { status: tokens.status, version: tokens.version, brand: null, available: [] };
2286
+ }
2287
+ const entries = readBrandIndex(tokens.indexPath);
2288
+ const available = entries.map((e) => e.name);
2289
+ const entry = entries.find((e) => e.name === name);
2290
+ if (!entry) {
2291
+ return { status: "ok", version: tokens.version, brand: null, available };
2292
+ }
2293
+ let summary;
2294
+ let parsed;
2295
+ try {
2296
+ summary = readSummary(baseDir, entry);
2297
+ const cssPath = projectRequire(baseDir).resolve(summary.importPath);
2298
+ parsed = parseBrandCss(readFileSync5(cssPath, "utf8"), name);
2299
+ } catch (err) {
2300
+ if (isMissingBuildFile(err)) return { status: "not-built", version: tokens.version, brand: null, available: [] };
2301
+ throw err;
2302
+ }
2303
+ return {
2304
+ status: "ok",
2305
+ version: tokens.version,
2306
+ available,
2307
+ brand: {
2308
+ ...summary,
2309
+ tokens: { light: pickKeyTokens(parsed.light), dark: pickKeyTokens(parsed.dark) },
2310
+ providerExample: providerExample(name),
2311
+ adjustments: { a11y: summary.a11y, reference: BRAND_ADJUSTMENT_REFERENCE }
2312
+ }
2313
+ };
2314
+ }
2315
+
2316
+ // src/tools/template.ts
2071
2317
  var TEMPLATE_REGISTRY_URL = process.env.AXIS_TEMPLATE_URL || "https://axis.minu.best/templates";
2072
2318
  async function fetchTemplateIndex() {
2073
2319
  const url = `${TEMPLATE_REGISTRY_URL}/index.json`;
@@ -2188,6 +2434,36 @@ async function handleDiffTemplate(params) {
2188
2434
  };
2189
2435
  return { templateName: detail.name, entries, summary };
2190
2436
  }
2437
+ function brandCheckResult(check) {
2438
+ const { brand } = check;
2439
+ const suffix = check.configuredMissing !== void 0 ? ` (globalCss ${check.configuredMissing} \uC5C6\uC74C)` : "";
2440
+ switch (check.state) {
2441
+ case "match":
2442
+ return {
2443
+ name: "brand",
2444
+ severity: "pass",
2445
+ message: `brand "${brand}" import \uAC00 ${check.cssFile}\uC5D0 \uC788\uC2B5\uB2C8\uB2E4${suffix}`
2446
+ };
2447
+ case "css-missing":
2448
+ return {
2449
+ name: "brand",
2450
+ severity: "warn",
2451
+ message: `brand "${brand}" \uAC00 \uC124\uC815\uB410\uC9C0\uB9CC CSS \uD30C\uC77C\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4${suffix}`
2452
+ };
2453
+ case "import-missing":
2454
+ return {
2455
+ name: "brand",
2456
+ severity: "warn",
2457
+ message: `brand "${brand}" import \uAC00 ${check.cssFile}\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4 \u2192 'axis theme add ${brand}'${suffix}`
2458
+ };
2459
+ case "other-brand":
2460
+ return {
2461
+ name: "brand",
2462
+ severity: "warn",
2463
+ message: `brand "${brand}" \uC640 import(${check.imports.join(", ")})\uAC00 \uB2E4\uB985\uB2C8\uB2E4${suffix}`
2464
+ };
2465
+ }
2466
+ }
2191
2467
  async function handleCheckProject(params) {
2192
2468
  const baseDir = path3.resolve(params.targetDir || ".");
2193
2469
  const results = [];
@@ -2312,6 +2588,8 @@ async function handleCheckProject(params) {
2312
2588
  message: "package.json \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4"
2313
2589
  });
2314
2590
  }
2591
+ const brandCheck = inspectBrandImport(baseDir);
2592
+ if (brandCheck) results.push(brandCheckResult(brandCheck));
2315
2593
  const summary = {
2316
2594
  pass: results.filter((r) => r.severity === "pass").length,
2317
2595
  warn: results.filter((r) => r.severity === "warn").length,
@@ -2426,10 +2704,137 @@ function formatCheckProjectResult(result) {
2426
2704
  return lines.join("\n");
2427
2705
  }
2428
2706
 
2707
+ // src/tools/brand.ts
2708
+ import path4 from "path";
2709
+ var TOKENS_INSTALL_COMMAND = `pnpm add ${TOKENS_PACKAGE}@^${MIN_BRAND_TOKENS_VERSION}`;
2710
+ function baseDirOf(targetDir) {
2711
+ return path4.resolve(targetDir || process.cwd());
2712
+ }
2713
+ async function handleListBrands(params) {
2714
+ return listBrands(baseDirOf(params.targetDir));
2715
+ }
2716
+ async function handleGetBrand(params) {
2717
+ const { name } = params;
2718
+ if (!name || name.trim() === "") {
2719
+ throw new Error("\uBE0C\uB79C\uB4DC \uC774\uB984(name)\uC740 \uD544\uC218\uC785\uB2C8\uB2E4.");
2720
+ }
2721
+ return getBrand(baseDirOf(params.targetDir), name);
2722
+ }
2723
+ function formatTokensStatusGuide(status, version) {
2724
+ switch (status) {
2725
+ case "not-installed":
2726
+ return [
2727
+ `${TOKENS_PACKAGE} \uAC00 \uC124\uCE58\uB3FC \uC788\uC9C0 \uC54A\uC544 \uBE0C\uB79C\uB4DC\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`,
2728
+ "",
2729
+ `- \uC124\uCE58: \`${TOKENS_INSTALL_COMMAND}\``
2730
+ ].join("\n");
2731
+ case "unsupported":
2732
+ return [
2733
+ `${TOKENS_PACKAGE} ${version ?? "(\uBC84\uC804 \uBAA8\uB984)"} \uC740 \uBE0C\uB79C\uB4DC\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4(${MIN_BRAND_TOKENS_VERSION} \uC774\uC0C1 \uD544\uC694).`,
2734
+ "",
2735
+ `- \uC5C5\uADF8\uB808\uC774\uB4DC: \`${TOKENS_INSTALL_COMMAND}\``
2736
+ ].join("\n");
2737
+ case "not-built":
2738
+ return [
2739
+ `${TOKENS_PACKAGE}${version ? ` ${version}` : ""} \uB294 \uC788\uC9C0\uB9CC \uBE4C\uB4DC \uC0B0\uCD9C\uBB3C\uC774 \uC5C6\uC5B4 \uBE0C\uB79C\uB4DC\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`,
2740
+ "",
2741
+ `- \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uBA74 \`pnpm --filter ${TOKENS_PACKAGE} build\`, \uC544\uB2C8\uBA74 \uC7AC\uC124\uCE58: \`${TOKENS_INSTALL_COMMAND}\``
2742
+ ].join("\n");
2743
+ }
2744
+ }
2745
+ function formatListBrandsResult(result) {
2746
+ const lines = ["## AXIS \uBE0C\uB79C\uB4DC\n"];
2747
+ if (result.status !== "ok") {
2748
+ lines.push(formatTokensStatusGuide(result.status, result.version));
2749
+ return lines.join("\n");
2750
+ }
2751
+ if (result.brands.length === 0) {
2752
+ lines.push(`\uC124\uCE58\uB41C ${TOKENS_PACKAGE} ${result.version ?? ""} \uC5D0 \uBE0C\uB79C\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.`);
2753
+ return lines.join("\n");
2754
+ }
2755
+ lines.push(`${TOKENS_PACKAGE} ${result.version ?? ""} \uC5D0 \uBE0C\uB79C\uB4DC ${result.brands.length}\uAC1C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.`, "");
2756
+ for (const b of result.brands) {
2757
+ lines.push(`- **${b.name}**: ${b.description} (${b.themes.join(" \xB7 ")})`);
2758
+ lines.push(` - import: \`${b.importPath}\``);
2759
+ }
2760
+ lines.push("", "\uC0C1\uC138: `axis.get_brand` (name)");
2761
+ return lines.join("\n");
2762
+ }
2763
+ function a11yValue(value) {
2764
+ return value === null || value === void 0 || value === "" ? "-" : String(value);
2765
+ }
2766
+ function tokenTable(brand) {
2767
+ const rows = KEY_TOKENS.filter((k) => k in brand.tokens.light || k in brand.tokens.dark);
2768
+ const missing = KEY_TOKENS.filter((k) => !(k in brand.tokens.light) && !(k in brand.tokens.dark));
2769
+ const lines = [];
2770
+ if (rows.length > 0) {
2771
+ lines.push("| \uD1A0\uD070 | \uB77C\uC774\uD2B8 | \uB2E4\uD06C |", "|---|---|---|");
2772
+ for (const k of rows) {
2773
+ lines.push(`| \`${k}\` | ${brand.tokens.light[k] ?? "-"} | ${brand.tokens.dark[k] ?? "-"} |`);
2774
+ }
2775
+ }
2776
+ if (missing.length > 0) {
2777
+ if (lines.length > 0) lines.push("");
2778
+ lines.push(`\uBE0C\uB79C\uB4DC\uAC00 \uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uC74C: ${missing.join(", ")}`);
2779
+ }
2780
+ return lines;
2781
+ }
2782
+ function formatGetBrandResult(result, name) {
2783
+ if (result.status !== "ok") {
2784
+ return [`## \uBE0C\uB79C\uB4DC: ${name}
2785
+ `, formatTokensStatusGuide(result.status, result.version)].join("\n");
2786
+ }
2787
+ const brand = result.brand;
2788
+ if (!brand) {
2789
+ return [
2790
+ `## \uBE0C\uB79C\uB4DC: ${name}
2791
+ `,
2792
+ `\uBE0C\uB79C\uB4DC '${name}' \uAC00 \uC124\uCE58\uB41C ${TOKENS_PACKAGE} ${result.version ?? ""} \uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4.`,
2793
+ "",
2794
+ `- \uC124\uCE58\uB41C \uBE0C\uB79C\uB4DC: ${result.available.length > 0 ? result.available.join(", ") : "(\uC5C6\uC74C)"}`,
2795
+ "- \uBAA9\uB85D: `axis.list_brands`"
2796
+ ].join("\n");
2797
+ }
2798
+ return [
2799
+ `## \uBE0C\uB79C\uB4DC: ${brand.name}
2800
+ `,
2801
+ `- \uC124\uBA85: ${brand.description}`,
2802
+ `- \uD14C\uB9C8: ${brand.themes.join(" \xB7 ")}`,
2803
+ "",
2804
+ "### Import (globals.css, @charset \xB7 \uB2E4\uB978 @import \uB2E4\uC74C)",
2805
+ "",
2806
+ "```css",
2807
+ `@import "${brand.importPath}";`,
2808
+ "```",
2809
+ "",
2810
+ "### Provider (@axis-ds/theme 1.3.0 \uC774\uC0C1)",
2811
+ "",
2812
+ "```tsx",
2813
+ brand.providerExample,
2814
+ "```",
2815
+ "",
2816
+ `ThemeProvider \uB098 <html data-brand="${brand.name}"> \uAC00 \uC5C6\uC73C\uBA74 import \uB9CC\uC73C\uB85C\uB294 \uD654\uBA74\uC774 \uBC14\uB00C\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`,
2817
+ "",
2818
+ "### \uC8FC\uC694 \uD1A0\uD070",
2819
+ "",
2820
+ ...tokenTable(brand),
2821
+ "",
2822
+ "### \uBCF4\uC815 \uB0B4\uC5ED",
2823
+ "",
2824
+ `- a11y \uC0C1\uD0DC: ${a11yValue(brand.adjustments.a11y.status)}`,
2825
+ `- \uC810\uAC80\uC77C: ${a11yValue(brand.adjustments.a11y.checkedAt)}`,
2826
+ `- \uAC8C\uC774\uD2B8: ${a11yValue(brand.adjustments.a11y.gate)}`,
2827
+ `- \uADFC\uAC70 \uBB38\uC11C: ${brand.adjustments.reference}`,
2828
+ "",
2829
+ "\uBCF4\uC815 \uC804 \xB7 \uD6C4 \uAC12\uC740 \uC774 \uBC84\uC804\uC5D0\uC11C \uCD9C\uB825\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."
2830
+ ].join("\n");
2831
+ }
2832
+
2429
2833
  // src/index.ts
2834
+ var { version: SERVER_VERSION } = createRequire2(import.meta.url)("../package.json");
2430
2835
  var server = new McpServer({
2431
2836
  name: "axis-design-system",
2432
- version: "0.1.0"
2837
+ version: SERVER_VERSION
2433
2838
  });
2434
2839
  var ComponentCategorySchema = z.enum(["core", "agentic", "form", "layout"]).optional().describe("\uCEF4\uD3EC\uB10C\uD2B8 \uCE74\uD14C\uACE0\uB9AC \uD544\uD130");
2435
2840
  var TokenCategorySchema = z.enum(["color", "typography", "spacing", "radius", "shadow", "animation"]).optional().describe("\uD1A0\uD070 \uCE74\uD14C\uACE0\uB9AC \uD544\uD130");
@@ -2575,9 +2980,9 @@ server.tool(
2575
2980
  {
2576
2981
  path: z.string().describe("\uD1A0\uD070 \uACBD\uB85C (\uC608: colors.primary, spacing.4)")
2577
2982
  },
2578
- async ({ path: path4 }) => {
2983
+ async ({ path: path5 }) => {
2579
2984
  try {
2580
- const token = handleGetToken({ path: path4 });
2985
+ const token = handleGetToken({ path: path5 });
2581
2986
  const formatted = formatTokenDetail(token);
2582
2987
  return {
2583
2988
  content: [
@@ -2978,6 +3383,71 @@ server.tool(
2978
3383
  }
2979
3384
  }
2980
3385
  );
3386
+ server.tool(
3387
+ "axis.list_brands",
3388
+ "\uB300\uC0C1 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC124\uCE58\uB41C @axis-ds/tokens \uC758 \uBE0C\uB79C\uB4DC \uBAA9\uB85D(\uC774\uB984 \xB7 \uC124\uBA85 \xB7 \uD14C\uB9C8 \xB7 import \uACBD\uB85C)\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.",
3389
+ {
3390
+ targetDir: z.string().optional().describe("\uB300\uC0C1 \uD504\uB85C\uC81D\uD2B8 \uB514\uB809\uD1A0\uB9AC (\uAE30\uBCF8: \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC)")
3391
+ },
3392
+ async ({ targetDir }) => {
3393
+ try {
3394
+ const result = await handleListBrands({ targetDir });
3395
+ const formatted = formatListBrandsResult(result);
3396
+ return {
3397
+ content: [
3398
+ {
3399
+ type: "text",
3400
+ text: formatted
3401
+ }
3402
+ ]
3403
+ };
3404
+ } catch (error) {
3405
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
3406
+ return {
3407
+ content: [
3408
+ {
3409
+ type: "text",
3410
+ text: `\uC624\uB958: ${message}`
3411
+ }
3412
+ ],
3413
+ isError: true
3414
+ };
3415
+ }
3416
+ }
3417
+ );
3418
+ server.tool(
3419
+ "axis.get_brand",
3420
+ "\uBE0C\uB79C\uB4DC \uD558\uB098\uC758 import \uACBD\uB85C \xB7 Provider \uC124\uC815 \uC608\uC2DC(defaultBrand) \xB7 \uC8FC\uC694 \uD1A0\uD070 \xB7 \uBCF4\uC815 \uB0B4\uC5ED\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.",
3421
+ {
3422
+ name: z.string().describe("\uBE0C\uB79C\uB4DC \uC774\uB984 (\uC608: ontology)"),
3423
+ targetDir: z.string().optional().describe("\uB300\uC0C1 \uD504\uB85C\uC81D\uD2B8 \uB514\uB809\uD1A0\uB9AC (\uAE30\uBCF8: \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC)")
3424
+ },
3425
+ async ({ name, targetDir }) => {
3426
+ try {
3427
+ const result = await handleGetBrand({ name, targetDir });
3428
+ const formatted = formatGetBrandResult(result, name);
3429
+ return {
3430
+ content: [
3431
+ {
3432
+ type: "text",
3433
+ text: formatted
3434
+ }
3435
+ ]
3436
+ };
3437
+ } catch (error) {
3438
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
3439
+ return {
3440
+ content: [
3441
+ {
3442
+ type: "text",
3443
+ text: `\uC624\uB958: ${message}`
3444
+ }
3445
+ ],
3446
+ isError: true
3447
+ };
3448
+ }
3449
+ }
3450
+ );
2981
3451
  async function main() {
2982
3452
  const transport = new StdioServerTransport();
2983
3453
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axis-ds/mcp",
3
- "version": "1.1.5",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "AXIS Design System - MCP Server for AI-assisted component discovery",
6
6
  "bin": {
@@ -25,7 +25,8 @@
25
25
  "@types/fs-extra": "^11.0.0",
26
26
  "@types/node": "^22.0.0",
27
27
  "tsup": "^8.0.0",
28
- "typescript": "^5.7.0"
28
+ "typescript": "^5.7.0",
29
+ "vitest": "^2.1.0"
29
30
  },
30
31
  "keywords": [
31
32
  "mcp",
@@ -45,6 +46,7 @@
45
46
  "build": "tsup src/index.ts --format esm --dts --clean && npm run copy-data",
46
47
  "copy-data": "node -e \"const fs=require('fs');fs.cpSync('src/registry/data','dist/data',{recursive:true})\"",
47
48
  "dev": "tsup src/index.ts --format esm --watch",
48
- "clean": "rm -rf dist"
49
+ "clean": "rm -rf dist",
50
+ "test": "vitest run"
49
51
  }
50
52
  }