@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.
- package/README.md +13 -0
- package/dist/index.js +478 -8
- 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(
|
|
68
|
+
function getToken(path5) {
|
|
68
69
|
const registry = getRegistry();
|
|
69
70
|
return registry.tokens.find(
|
|
70
|
-
(t) => t.path.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:
|
|
155
|
-
if (!
|
|
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(
|
|
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:
|
|
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:
|
|
2983
|
+
async ({ path: path5 }) => {
|
|
2579
2984
|
try {
|
|
2580
|
-
const token = handleGetToken({ path:
|
|
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.
|
|
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
|
}
|