@lism-css/mcp 0.10.4 → 0.12.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 (39) hide show
  1. package/README.ja.md +4 -2
  2. package/README.md +4 -2
  3. package/dist/data/docs-index.json +282 -64
  4. package/dist/data/guides/SKILL.md +113 -0
  5. package/dist/data/guides/base-styles.md +106 -0
  6. package/dist/data/guides/components-core.md +338 -0
  7. package/dist/data/guides/components-ui.md +351 -0
  8. package/dist/data/guides/css-rules.md +146 -0
  9. package/dist/data/guides/module-class.md +162 -0
  10. package/dist/data/guides/prop-responsive.md +54 -0
  11. package/dist/data/guides/property-class.md +400 -0
  12. package/dist/data/guides/set-class.md +190 -0
  13. package/dist/data/guides/tokens.md +210 -0
  14. package/dist/data/guides/utility-class.md +81 -0
  15. package/dist/data/meta.js +2 -2
  16. package/dist/index.js +4 -0
  17. package/dist/lib/load-data.js +2 -11
  18. package/dist/lib/load-markdown.d.ts +6 -0
  19. package/dist/lib/load-markdown.js +29 -0
  20. package/dist/lib/markdown-utils.d.ts +42 -0
  21. package/dist/lib/markdown-utils.js +158 -0
  22. package/dist/lib/schemas.d.ts +0 -242
  23. package/dist/lib/schemas.js +0 -64
  24. package/dist/lib/search.d.ts +2 -16
  25. package/dist/lib/search.js +9 -68
  26. package/dist/lib/types.d.ts +0 -64
  27. package/dist/tools/convert-css.js +96 -55
  28. package/dist/tools/get-component.js +60 -29
  29. package/dist/tools/get-guide.d.ts +2 -0
  30. package/dist/tools/get-guide.js +45 -0
  31. package/dist/tools/get-overview.js +26 -39
  32. package/dist/tools/get-props-system.js +45 -33
  33. package/dist/tools/get-tokens.js +9 -14
  34. package/dist/tools/search-docs.js +27 -9
  35. package/package.json +2 -2
  36. package/dist/data/components.json +0 -564
  37. package/dist/data/overview.json +0 -114
  38. package/dist/data/props-system.json +0 -1154
  39. package/dist/data/tokens.json +0 -152
@@ -1,74 +1,107 @@
1
1
  import { z } from 'zod';
2
- import { loadJSON } from '../lib/load-data.js';
3
- import { PropsSystemDataSchema } from '../lib/schemas.js';
2
+ import { loadMarkdown } from '../lib/load-markdown.js';
3
+ import { parsePropRows } from '../lib/markdown-utils.js';
4
4
  import { success, error, READ_ONLY_ANNOTATIONS } from '../lib/response.js';
5
5
  // ----------------------------------------------------------------
6
6
  // CSS パース
7
7
  // ----------------------------------------------------------------
8
+ /** @ルール(@media 等)が含まれていないか検査する */
9
+ function detectAtRules(cssText) {
10
+ const atRuleMatch = cssText.match(/^@(\w[\w-]*)/m);
11
+ if (atRuleMatch) {
12
+ return `@${atRuleMatch[1]} ルールは未対応です。CSS 宣言(property: value;)のみを入力してください。`;
13
+ }
14
+ return null;
15
+ }
16
+ /**
17
+ * CSS テキストから宣言を抽出する。
18
+ * `;` で分割する際に `url()` 等の括弧内の `;` を無視する。
19
+ */
8
20
  function parseCssDeclarations(cssText) {
9
21
  // コメント除去
10
22
  let cleaned = cssText.replace(/\/\*[\s\S]*?\*\//g, '');
11
23
  // セレクタ + ブレースを除去(裸の宣言リストも受け付ける)
12
24
  cleaned = cleaned.replace(/[^{}]*\{/g, '').replace(/\}/g, '');
25
+ // 括弧のネストを考慮して `;` で分割
26
+ const segments = [];
27
+ let current = '';
28
+ let parenDepth = 0;
29
+ for (const ch of cleaned) {
30
+ if (ch === '(')
31
+ parenDepth++;
32
+ if (ch === ')')
33
+ parenDepth = Math.max(0, parenDepth - 1);
34
+ if (ch === ';' && parenDepth === 0) {
35
+ segments.push(current.trim());
36
+ current = '';
37
+ }
38
+ else {
39
+ current += ch;
40
+ }
41
+ }
42
+ if (current.trim())
43
+ segments.push(current.trim());
13
44
  const declarations = [];
14
- for (const line of cleaned.split(';')) {
15
- const trimmed = line.trim();
16
- if (!trimmed)
45
+ for (const segment of segments) {
46
+ if (!segment)
17
47
  continue;
18
- const colonIdx = trimmed.indexOf(':');
48
+ const colonIdx = segment.indexOf(':');
19
49
  if (colonIdx === -1)
20
50
  continue;
21
- const property = trimmed.substring(0, colonIdx).trim().toLowerCase();
22
- const value = trimmed.substring(colonIdx + 1).trim();
51
+ const property = segment.substring(0, colonIdx).trim().toLowerCase();
52
+ const value = segment.substring(colonIdx + 1).trim();
23
53
  if (property && value) {
24
54
  declarations.push({ property, value });
25
55
  }
26
56
  }
27
57
  return declarations;
28
58
  }
29
- // ----------------------------------------------------------------
30
- // CSSプロパティ PropEntry マップ
31
- // ----------------------------------------------------------------
32
- function buildCssPropertyMap(categories) {
59
+ /** プリセット値列から値を抽出する(例: "-fz:root, -fz:base" → ["root", "base"]) */
60
+ function extractPresetValues(presetColumn, propName) {
61
+ if (!presetColumn || presetColumn === '—' || presetColumn === '-')
62
+ return [];
63
+ const values = [];
64
+ // -{prop}:{value} パターンを全て抽出
65
+ const escaped = propName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
66
+ const regex = new RegExp(`-${escaped}:([^,\\s\`〜]+)`, 'g');
67
+ let match;
68
+ while ((match = regex.exec(presetColumn)) !== null) {
69
+ values.push(match[1]);
70
+ }
71
+ return values;
72
+ }
73
+ function buildMappings(md) {
74
+ return parsePropRows(md).map((row) => ({
75
+ prop: row.prop,
76
+ cssProperty: row.cssProperty,
77
+ presetValues: extractPresetValues(row.presetColumn, row.prop),
78
+ sectionName: row.sectionName,
79
+ }));
80
+ }
81
+ function buildCssPropertyMap(mappings) {
33
82
  const map = new Map();
34
- for (const cat of categories) {
35
- for (const prop of cat.props) {
36
- const normalized = normalizeCssPropertyName(prop.cssProperty);
37
- if (normalized)
38
- map.set(normalized, prop);
83
+ for (const mapping of mappings) {
84
+ const normalized = mapping.cssProperty.toLowerCase();
85
+ // CSS カスタムプロパティ形式はスキップ("--hl" 等)
86
+ if (!normalized.startsWith('(class:')) {
87
+ map.set(normalized, mapping);
39
88
  }
40
89
  }
41
90
  return map;
42
91
  }
43
- /** cssProperty フィールドの正規化 */
44
- function normalizeCssPropertyName(raw) {
45
- // "(class: is--container)" → スキップ(CSS プロパティではない)
46
- if (raw.startsWith('(class:'))
47
- return null;
48
- // "--hl (CSS変数)" → "--hl"
49
- return raw
50
- .replace(/\s*\(.*\)$/, '')
51
- .trim()
52
- .toLowerCase();
53
- }
54
92
  // ----------------------------------------------------------------
55
93
  // 値のマッピング
56
94
  // ----------------------------------------------------------------
57
95
  /** よくある CSS 値 → Lism トークン値の変換テーブル */
58
96
  const VALUE_ALIASES = {
59
- column: 'col',
60
- 'column-reverse': 'col-r',
61
- 'row-reverse': 'row-r',
62
97
  'space-between': 'between',
63
- 'flex-start': 'flex-s',
64
- 'flex-end': 'flex-e',
65
- currentcolor: 'cc',
98
+ currentcolor: 'current',
66
99
  uppercase: 'upper',
67
100
  lowercase: 'lower',
68
101
  };
69
- function suggestValue(propEntry, cssValue) {
70
- const tokens = propEntry.values;
71
- if (!tokens || tokens.length === 0)
102
+ function suggestValue(mapping, cssValue) {
103
+ const tokens = mapping.presetValues;
104
+ if (tokens.length === 0)
72
105
  return null;
73
106
  // 直接一致
74
107
  if (tokens.includes(cssValue))
@@ -124,12 +157,9 @@ function detectComponent(declarations) {
124
157
  // ----------------------------------------------------------------
125
158
  // 変換メイン
126
159
  // ----------------------------------------------------------------
127
- function findCategory(categories, propName) {
128
- for (const cat of categories) {
129
- if (cat.props.some((p) => p.prop === propName))
130
- return cat.category;
131
- }
132
- return 'unknown';
160
+ function findCategory(mappings, propName) {
161
+ const found = mappings.find((m) => m.prop === propName);
162
+ return found?.sectionName ?? 'unknown';
133
163
  }
134
164
  function buildExample(conversions, component) {
135
165
  const tagName = component?.name ?? 'Lism';
@@ -167,44 +197,55 @@ function buildExample(conversions, component) {
167
197
  // ----------------------------------------------------------------
168
198
  export function registerConvertCss(server) {
169
199
  server.registerTool('convert_css', {
170
- description: 'Convert CSS code to lism-css props, utility classes, and component suggestions. Accepts CSS declarations (with or without selectors) and returns the equivalent lism-css representation. Use this to migrate existing CSS to lism-css, or to understand how CSS properties map to lism-css.',
200
+ description: 'Convert CSS code to lism-css props, utility classes, and component suggestions. Accepts CSS declarations (with or without selectors) and returns the equivalent lism-css representation as structured JSON.\n' +
201
+ 'Use this when migrating existing CSS to lism-css in bulk, or when you need to understand how multiple CSS properties map to lism-css at once.\n' +
202
+ 'Do NOT use this for single prop lookups (use get_props_system instead). Note: @media and other at-rules are NOT supported — an error will be returned if detected.\n' +
203
+ 'Returns JSON with conversions (prop mappings with confidence), component suggestions, and a JSX usage example.',
171
204
  inputSchema: {
172
205
  css: z
173
206
  .string()
174
- .describe('CSS code to convert. Accepts a full rule block with selector (e.g. ".foo { padding: 1rem; }") or bare declarations (e.g. "padding: 1rem; font-size: 16px;").'),
207
+ .describe('CSS code to convert. Accepts a full rule block with selector (e.g. ".foo { padding: 1rem; }") or bare declarations (e.g. "padding: 1rem; font-size: 16px;"). @media and other at-rules are not supported.'),
175
208
  },
176
209
  annotations: READ_ONLY_ANNOTATIONS,
177
210
  }, ({ css }) => {
178
211
  try {
179
- const propsData = loadJSON('props-system.json', PropsSystemDataSchema);
180
- const cssPropertyMap = buildCssPropertyMap(propsData.categories);
212
+ // @ ルール検出
213
+ const atRuleError = detectAtRules(css);
214
+ if (atRuleError) {
215
+ return error(atRuleError);
216
+ }
217
+ const md = loadMarkdown('property-class.md');
218
+ const mappings = buildMappings(md);
219
+ const cssPropertyMap = buildCssPropertyMap(mappings);
181
220
  const declarations = parseCssDeclarations(css);
182
221
  if (declarations.length === 0) {
183
222
  return error('CSS 宣言が見つかりません。"property: value;" 形式の CSS を入力してください。');
184
223
  }
185
224
  // 各宣言を変換
186
225
  const conversions = declarations.map((decl) => {
187
- const propEntry = cssPropertyMap.get(decl.property);
188
- if (!propEntry) {
226
+ const mapping = cssPropertyMap.get(decl.property);
227
+ if (!mapping) {
189
228
  return {
190
229
  css: `${decl.property}: ${decl.value}`,
191
230
  lismProp: null,
192
231
  suggestedValue: null,
193
232
  availableTokens: null,
233
+ confidence: 'unmapped',
194
234
  note: 'Lism Props に該当なし。style で直接指定してください。',
195
235
  };
196
236
  }
197
- const suggested = suggestValue(propEntry, decl.value);
198
- const category = findCategory(propsData.categories, propEntry.prop);
237
+ const suggested = suggestValue(mapping, decl.value);
238
+ const category = findCategory(mappings, mapping.prop);
199
239
  return {
200
240
  css: `${decl.property}: ${decl.value}`,
201
- lismProp: propEntry.prop,
241
+ lismProp: mapping.prop,
202
242
  suggestedValue: suggested,
203
- availableTokens: propEntry.values ?? null,
243
+ availableTokens: mapping.presetValues.length > 0 ? mapping.presetValues : null,
244
+ confidence: suggested ? 'exact' : 'approximate',
204
245
  note: suggested
205
246
  ? `トークン値 '${suggested}' を使用(カテゴリ: ${category})`
206
- : propEntry.values && propEntry.values.length > 0
207
- ? `カスタム値。利用可能なトークン: ${propEntry.values.join(', ')}(カテゴリ: ${category})`
247
+ : mapping.presetValues.length > 0
248
+ ? `カスタム値。利用可能なトークン: ${mapping.presetValues.join(', ')}(カテゴリ: ${category})`
208
249
  : `カスタム値として指定(カテゴリ: ${category})`,
209
250
  };
210
251
  });
@@ -1,10 +1,14 @@
1
1
  import { z } from 'zod';
2
- import { loadJSON } from '../lib/load-data.js';
3
- import { ComponentInfoSchema } from '../lib/schemas.js';
4
- import { success, error, notFound, READ_ONLY_ANNOTATIONS } from '../lib/response.js';
2
+ import { loadMarkdown } from '../lib/load-markdown.js';
3
+ import { findComponentByHeading, findComponentInTables } from '../lib/markdown-utils.js';
4
+ import { markdownResponse, error, notFound, READ_ONLY_ANNOTATIONS } from '../lib/response.js';
5
5
  export function registerGetComponent(server) {
6
6
  server.registerTool('get_component', {
7
- description: '特定のlism-cssコンポーネントに関する詳細情報(プロパティ、使用例、カテゴリー)を取得します。該当するものが見つからない場合は、より広範なキーワードで search_docs を実行してください。',
7
+ description: 'Get detailed information about a specific lism-css component: purpose, props, and usage examples.\n' +
8
+ 'Use this when you need documentation for a known component by name (e.g. "Box", "Flex", "Accordion", "Lism", "HTML").\n' +
9
+ 'Do NOT use this for broad topic guides (use get_guide with "components-core" or "components-ui") or keyword search across all docs (use search_docs).\n' +
10
+ 'If the component is not found, suggestions will be provided — follow up with search_docs for a broader query.\n' +
11
+ 'The response is pre-formatted Markdown. Output it verbatim. Do NOT summarize or omit code examples.',
8
12
  inputSchema: {
9
13
  name: z.string().describe('Component name to look up (e.g. "Box", "Flex", "Accordion").'),
10
14
  package: z
@@ -15,42 +19,69 @@ export function registerGetComponent(server) {
15
19
  annotations: READ_ONLY_ANNOTATIONS,
16
20
  }, ({ name, package: pkg }) => {
17
21
  try {
18
- const data = loadJSON('components.json', z.array(ComponentInfoSchema));
19
- let candidates = data;
20
- if (pkg) {
21
- candidates = data.filter((c) => c.package === pkg);
22
- }
23
22
  const nameLower = name.toLowerCase();
24
- // Step 1: 名前の完全一致
25
- const exact = candidates.find((c) => c.name.toLowerCase() === nameLower);
26
- if (exact) {
27
- return success({ component: exact });
23
+ // --- @lism-css/ui コンポーネントを検索 ---
24
+ if (!pkg || pkg === '@lism-css/ui') {
25
+ const uiMd = loadMarkdown('components-ui.md');
26
+ // ## ComponentName 形式の見出しで検索(完全一致)
27
+ const uiSection = findComponentByHeading(uiMd, name);
28
+ if (uiSection) {
29
+ return markdownResponse(uiSection);
30
+ }
31
+ // 大文字小文字を無視した見出し検索
32
+ const uiLines = uiMd.split('\n');
33
+ const headingLine = uiLines.find((l) => l.startsWith('## ') && l.slice(3).trim().toLowerCase() === nameLower);
34
+ if (headingLine) {
35
+ const section = findComponentByHeading(uiMd, headingLine.slice(3).trim());
36
+ if (section)
37
+ return markdownResponse(section);
38
+ }
28
39
  }
29
- // Step 2: aliases マッチ(用途ベースの検索、完全一致)
30
- const aliasMatches = candidates.filter((c) => c.aliases?.some((a) => a.toLowerCase() === nameLower));
31
- if (aliasMatches.length === 1) {
32
- return success({ component: aliasMatches[0], matchedBy: 'alias' });
40
+ // --- lism-css コアコンポーネントを検索 ---
41
+ if (!pkg || pkg === 'lism-css') {
42
+ const coreMd = loadMarkdown('components-core.md');
43
+ // 見出しにコンポーネント名を含むセクションを検索(Lism, HTML 等)
44
+ const coreLines = coreMd.split('\n');
45
+ const headingLine = coreLines.find((l) => /^#{2,3}\s/.test(l) && l.toLowerCase().includes(`<${nameLower}>`));
46
+ if (headingLine) {
47
+ const headingText = headingLine.replace(/^#+\s*/, '').trim();
48
+ const section = findComponentByHeading(coreMd, headingText);
49
+ if (section)
50
+ return markdownResponse(section);
51
+ }
52
+ // テーブル内の `<ComponentName>` で検索
53
+ const coreSection = findComponentInTables(coreMd, name);
54
+ if (coreSection) {
55
+ return markdownResponse(coreSection);
56
+ }
33
57
  }
34
- if (aliasMatches.length > 1) {
35
- const suggestions = aliasMatches.map((c) => ({
36
- name: c.name,
37
- package: c.package,
38
- description: c.description,
39
- }));
40
- return notFound(`Component "${name}" not found by name, but ${aliasMatches.length} components matched by alias. Did you mean one of these?`, { suggestions });
58
+ // --- 部分一致で候補を提示 ---
59
+ const uiMd = loadMarkdown('components-ui.md');
60
+ const coreMd = loadMarkdown('components-core.md');
61
+ const uiCandidates = uiMd
62
+ .split('\n')
63
+ .filter((l) => l.startsWith('## ') && l.toLowerCase().includes(nameLower))
64
+ .map((l) => l.slice(3).trim());
65
+ const coreCandidates = [];
66
+ for (const line of coreMd.split('\n')) {
67
+ if (line.startsWith('|') && line.toLowerCase().includes(nameLower)) {
68
+ const match = line.match(/`<([^>]+)>`/);
69
+ if (match)
70
+ coreCandidates.push(match[1]);
71
+ }
41
72
  }
42
- // Step 3: 名前の部分一致
43
- const suggestions = candidates.filter((c) => c.name.toLowerCase().includes(nameLower)).map((c) => ({ name: c.name, package: c.package }));
73
+ const suggestions = [...new Set([...uiCandidates, ...coreCandidates])];
44
74
  if (suggestions.length > 0) {
45
75
  return notFound(`Component "${name}" not found. Did you mean one of these? Or try search_docs with a broader query.`, {
46
76
  suggestions,
47
77
  });
48
78
  }
49
- const available = candidates.map((c) => ({ name: c.name, package: c.package }));
50
- return notFound(`Component "${name}" not found. Try search_docs to find related pages.`, { availableComponents: available });
79
+ return notFound(`Component "${name}" not found. Try search_docs or get_guide to find related pages.`, {
80
+ tip: 'Use get_guide with topic="components-core" or "components-ui" to see all available components.',
81
+ });
51
82
  }
52
83
  catch (e) {
53
- return error(`Failed to load component data: ${e instanceof Error ? e.message : String(e)}. The data files may not be built yet. Ensure the server was installed correctly.`);
84
+ return error(`Failed to load component data: ${e instanceof Error ? e.message : String(e)}. The data files may not be built yet. Run "pnpm build" in packages/mcp first.`);
54
85
  }
55
86
  });
56
87
  }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerGetGuide(server: McpServer): void;
@@ -0,0 +1,45 @@
1
+ import { z } from 'zod';
2
+ import { loadMarkdown } from '../lib/load-markdown.js';
3
+ import { markdownResponse, error, READ_ONLY_ANNOTATIONS } from '../lib/response.js';
4
+ const GUIDE_TOPICS = {
5
+ overview: { file: 'SKILL.md', label: 'Framework overview, packages, implementation rules' },
6
+ tokens: { file: 'tokens.md', label: 'Design tokens (spacing, colors, font sizes, etc.)' },
7
+ 'property-class': { file: 'property-class.md', label: 'Property Class system, all props reference table' },
8
+ 'components-core': {
9
+ file: 'components-core.md',
10
+ label: 'Core component system (Lism, Box, Flex, Stack, Grid, etc.)',
11
+ },
12
+ 'components-ui': {
13
+ file: 'components-ui.md',
14
+ label: 'UI components (Accordion, Modal, Tabs, Button, etc.)',
15
+ },
16
+ 'base-styles': { file: 'base-styles.md', label: 'Base styling, reset CSS, HTML element styles' },
17
+ 'set-class': { file: 'set-class.md', label: 'Set classes (set--plain, set--shadow, set--hov, etc.)' },
18
+ 'module-class': { file: 'module-class.md', label: 'Module class prefixes (is--, l--, a--, c--)' },
19
+ 'utility-class': { file: 'utility-class.md', label: 'Utility classes (u--trim, u--cbox, etc.)' },
20
+ 'css-rules': { file: 'css-rules.md', label: 'CSS methodology, layer structure, naming conventions' },
21
+ responsive: { file: 'prop-responsive.md', label: 'Responsive design, breakpoints, container queries' },
22
+ };
23
+ const TOPIC_DESCRIPTION = Object.entries(GUIDE_TOPICS)
24
+ .map(([key, { label }]) => `- ${key}: ${label}`)
25
+ .join('\n');
26
+ export function registerGetGuide(server) {
27
+ server.registerTool('get_guide', {
28
+ description: 'Get a detailed guide on a specific lism-css topic. Use this when you need comprehensive documentation on a broad topic rather than a specific component or prop.\n' +
29
+ 'For individual component lookup, get_component is more direct. For individual prop lookup, use get_props_system.\n' +
30
+ 'The response is the full guide as pre-formatted Markdown. Output it verbatim. Do NOT summarize or omit sections.\n' +
31
+ `\nAvailable topics:\n${TOPIC_DESCRIPTION}`,
32
+ inputSchema: {
33
+ topic: z.enum(Object.keys(GUIDE_TOPICS)).describe('The guide topic to retrieve.'),
34
+ },
35
+ annotations: READ_ONLY_ANNOTATIONS,
36
+ }, ({ topic }) => {
37
+ try {
38
+ const { file } = GUIDE_TOPICS[topic];
39
+ return markdownResponse(loadMarkdown(file));
40
+ }
41
+ catch (e) {
42
+ return error(`Failed to load guide "${topic}": ${e instanceof Error ? e.message : String(e)}. The data files may not be built yet. Run "pnpm build" in packages/mcp first.`);
43
+ }
44
+ });
45
+ }
@@ -1,53 +1,40 @@
1
- import { loadJSON } from '../lib/load-data.js';
2
- import { OverviewDataSchema } from '../lib/schemas.js';
1
+ import { loadMarkdown } from '../lib/load-markdown.js';
2
+ import { extractSection } from '../lib/markdown-utils.js';
3
3
  import { markdownResponse, error, READ_ONLY_ANNOTATIONS } from '../lib/response.js';
4
- import { meta } from '../data/meta.js';
5
- function toMarkdown(data) {
6
- const lines = [];
7
- lines.push(`# lism-css Overview`);
8
- lines.push('');
9
- lines.push(`> Generated at: ${meta.generatedAt} | Source commit: ${meta.sourceCommit} | Docs version: ${meta.docsVersion}`);
10
- lines.push('');
11
- lines.push(`## Description`);
12
- lines.push('');
13
- lines.push(data.description);
14
- lines.push('');
15
- lines.push(`## Architecture`);
16
- lines.push('');
17
- lines.push(data.architecture);
18
- lines.push('');
19
- lines.push(`## Packages`);
20
- lines.push('');
21
- for (const pkg of data.packages) {
22
- lines.push(`- **${pkg.name}** (\`${pkg.npmName}\` v${pkg.version}): ${pkg.description}`);
4
+ /**
5
+ * SKILL.md を中核に、css-rules.md の Layer 構造セクションと
6
+ * prop-responsive.md のブレークポイントセクションを付加して返す。
7
+ */
8
+ function buildOverviewMarkdown() {
9
+ const skill = loadMarkdown('SKILL.md');
10
+ const cssRules = loadMarkdown('css-rules.md');
11
+ const responsive = loadMarkdown('prop-responsive.md');
12
+ const layerSection = extractSection(cssRules, 'CSS Layer 構造');
13
+ const bpSection = extractSection(responsive, 'ブレークポイント');
14
+ const parts = [skill];
15
+ if (layerSection) {
16
+ parts.push('\n---\n');
17
+ parts.push(layerSection);
23
18
  }
24
- lines.push('');
25
- lines.push(`## Breakpoints`);
26
- lines.push('');
27
- for (const [key, value] of Object.entries(data.breakpoints)) {
28
- lines.push(`- \`${key}\`: ${value}`);
19
+ if (bpSection) {
20
+ parts.push('\n---\n');
21
+ parts.push(bpSection);
29
22
  }
30
- lines.push('');
31
- lines.push(`## CSS Layers`);
32
- lines.push('');
33
- lines.push(data.cssLayers);
34
- lines.push('');
35
- lines.push(`## Installation`);
36
- lines.push('');
37
- lines.push(data.installation);
38
- return lines.join('\n');
23
+ return parts.join('\n');
39
24
  }
40
25
  export function registerGetOverview(server) {
41
26
  server.registerTool('get_overview', {
42
- description: 'Get an overview of the lism-css framework: architecture, design philosophy, packages, breakpoints, installation guide, and CSS layers. Start here to understand the framework before using other tools.',
27
+ description: 'Get an overview of the lism-css framework: architecture, design philosophy, packages, breakpoints, CSS layers, and implementation rules.\n' +
28
+ 'Use this as your FIRST call when starting any lism-css task — it provides the foundational context needed to use other tools effectively.\n' +
29
+ 'Do NOT use this to look up specific components (use get_component), individual props (use get_props_system), or design tokens (use get_tokens).\n' +
30
+ 'The response is pre-formatted Markdown. Output it verbatim to the user. Do NOT summarize or omit sections.',
43
31
  annotations: READ_ONLY_ANNOTATIONS,
44
32
  }, () => {
45
33
  try {
46
- const data = loadJSON('overview.json', OverviewDataSchema);
47
- return markdownResponse(toMarkdown(data));
34
+ return markdownResponse(buildOverviewMarkdown());
48
35
  }
49
36
  catch (e) {
50
- return error(`Failed to load overview data: ${e instanceof Error ? e.message : String(e)}. The data files may not be built yet. Ensure the server was installed correctly.`);
37
+ return error(`Failed to load overview data: ${e instanceof Error ? e.message : String(e)}. The data files may not be built yet. Run "pnpm build" in packages/mcp first.`);
51
38
  }
52
39
  });
53
40
  }
@@ -1,61 +1,73 @@
1
1
  import { z } from 'zod';
2
- import { loadJSON } from '../lib/load-data.js';
3
- import { PropsSystemDataSchema } from '../lib/schemas.js';
2
+ import { loadMarkdown } from '../lib/load-markdown.js';
3
+ import { parsePropRows } from '../lib/markdown-utils.js';
4
4
  import { parsePropClassName } from '../lib/search.js';
5
- import { success, error, notFound, READ_ONLY_ANNOTATIONS } from '../lib/response.js';
6
- /** cssProperty フィールドからコア名を抽出(例: "--hl (CSS変数)" → "--hl") */
5
+ import { markdownResponse, error, notFound, READ_ONLY_ANNOTATIONS } from '../lib/response.js';
6
+ /** cssProperty フィールドからコア名を抽出("--hl 変数のみ" → "--hl") */
7
7
  function normalizeCssProperty(raw) {
8
- // "(class: is--container)" → "is--container"
9
- const classMatch = raw.match(/\(class:\s*(.+?)\)/);
10
- if (classMatch)
11
- return classMatch[1].trim().toLowerCase();
12
- // "--hl (CSS変数)" → "--hl"
13
8
  return raw
14
9
  .replace(/\s*\(.*\)$/, '')
10
+ .replace(/([^)]*)$/, '')
15
11
  .trim()
16
12
  .toLowerCase();
17
13
  }
18
14
  export function registerGetPropsSystem(server) {
19
15
  server.registerTool('get_props_system', {
20
- description: 'Get the lism-css Props system reference: how React/Astro props map to CSS classes and styles. Supports lookup by lism prop name (e.g. "p", "fz") OR by CSS property name (e.g. "padding", "font-size"). Use this for CSS-to-lism reverse lookup. Related: use convert_css for bulk CSS conversion, get_component to see how props are used in components.',
16
+ description: 'Get the lism-css Props system reference: how React/Astro props map to CSS classes and styles. Supports lookup by lism prop name (e.g. "p", "fz") OR by CSS property name (e.g. "padding", "font-size"). Omit the prop parameter to get the full reference.\n' +
17
+ 'Use this when you need to find a specific prop mapping, understand the Property Class system, or check what CSS property a lism prop corresponds to.\n' +
18
+ 'For bulk CSS-to-lism conversion, convert_css is more efficient. For component-specific documentation, use get_component.\n' +
19
+ 'The response is pre-formatted Markdown. Output it verbatim. Do NOT summarize the prop tables.',
21
20
  inputSchema: {
22
21
  prop: z
23
22
  .string()
24
23
  .optional()
25
- .describe('Prop name or CSS property name to look up. Accepts lism prop names (e.g. "p", "fz", "bgc") and standard CSS property names (e.g. "padding", "font-size", "background-color"). Omit to get the full system overview.'),
24
+ .describe('Prop name or CSS property name to look up. Accepts lism prop names (e.g. "p", "fz", "bgc") and standard CSS property names (e.g. "padding", "font-size", "background-color"). Omit to get the full reference.'),
26
25
  },
27
26
  annotations: READ_ONLY_ANNOTATIONS,
28
27
  }, ({ prop }) => {
29
28
  try {
30
- const data = loadJSON('props-system.json', PropsSystemDataSchema);
29
+ const md = loadMarkdown('property-class.md');
30
+ // prop 未指定: 全文を返す
31
31
  if (!prop) {
32
- return success(data);
32
+ return markdownResponse(md);
33
33
  }
34
34
  const queryLower = parsePropClassName(prop) ?? prop.toLowerCase();
35
- const matched = [];
36
- for (const cat of data.categories) {
37
- const found = cat.props.filter((p) => {
38
- // Lism prop 名で一致
39
- if (p.prop.toLowerCase() === queryLower)
40
- return true;
41
- // CSS プロパティ名で一致(逆引き)
42
- const normalizedCss = normalizeCssProperty(p.cssProperty);
43
- if (normalizedCss === queryLower)
44
- return true;
45
- return false;
46
- });
47
- if (found.length > 0) {
48
- matched.push({ ...cat, props: found });
49
- }
50
- }
35
+ const rows = parsePropRows(md);
36
+ // 一致する行を探す
37
+ const matched = rows.filter((row) => {
38
+ if (row.prop.toLowerCase() === queryLower)
39
+ return true;
40
+ const normalizedCss = normalizeCssProperty(row.cssProperty);
41
+ if (normalizedCss === queryLower)
42
+ return true;
43
+ return false;
44
+ });
51
45
  if (matched.length === 0) {
52
- const allProps = data.categories.flatMap((c) => c.props.map((p) => `${p.prop} (${p.cssProperty})`));
53
- return notFound(`"${prop}" に一致する Prop が見つかりません。Lism prop 名 (例: "p", "fz") または CSS プロパティ名 (例: "padding", "font-size") で検索できます。`, { availableProps: allProps });
46
+ const availableProps = rows.map((r) => `${r.prop} (${r.cssProperty})`);
47
+ return notFound(`"${prop}" に一致する Prop が見つかりません。Lism prop 名 (例: "p", "fz") または CSS プロパティ名 (例: "padding", "font-size") で検索できます。`, { availableProps });
48
+ }
49
+ // 一致したセクション名のユニーク一覧を取得
50
+ const sections = [...new Set(matched.map((r) => r.sectionName))];
51
+ // property-class.md から対象セクションを抽出して結合
52
+ const lines = md.split('\n');
53
+ const resultParts = [`## 検索結果: "${prop}"\n`];
54
+ for (const sectionName of sections) {
55
+ // セクション内の一致する行だけを含む簡易 Markdown を生成
56
+ const sectionRows = matched.filter((r) => r.sectionName === sectionName);
57
+ const header = `### ${sectionName}\n`;
58
+ const tableHeader = '| Prop | CSS プロパティ | プリセット値クラス | BP クラス |\n|------|--------------|-------------|-----|';
59
+ // 対応する元テーブル行を探して取得
60
+ const tableRows = sectionRows.map((row) => {
61
+ const pattern = new RegExp(`^\\|\\s*\`${row.prop.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')}\`\\s*\\|`);
62
+ const originalLine = lines.find((l) => pattern.test(l));
63
+ return originalLine ?? `| \`${row.prop}\` | \`${row.cssProperty}\` | — | — |`;
64
+ });
65
+ resultParts.push(header + tableHeader + '\n' + tableRows.join('\n'));
54
66
  }
55
- return success({ description: data.description, categories: matched });
67
+ return markdownResponse(resultParts.join('\n\n'));
56
68
  }
57
69
  catch (e) {
58
- return error(`Failed to load props system data: ${e instanceof Error ? e.message : String(e)}. The data files may not be built yet. Ensure the server was installed correctly.`);
70
+ return error(`Failed to load props system data: ${e instanceof Error ? e.message : String(e)}. The data files may not be built yet. Run "pnpm build" in packages/mcp first.`);
59
71
  }
60
72
  });
61
73
  }
@@ -1,23 +1,18 @@
1
- import { z } from 'zod';
2
- import { loadJSON } from '../lib/load-data.js';
3
- import { TokenCategorySchema } from '../lib/schemas.js';
4
- import { success, error, READ_ONLY_ANNOTATIONS } from '../lib/response.js';
5
- const TOKEN_CATEGORIES = ['all', 'color', 'spacing', 'fontSize', 'shadow', 'radius', 'lineHeight', 'letterSpacing', 'fontFamily', 'zIndex'];
1
+ import { loadMarkdown } from '../lib/load-markdown.js';
2
+ import { markdownResponse, error, READ_ONLY_ANNOTATIONS } from '../lib/response.js';
6
3
  export function registerGetTokens(server) {
7
4
  server.registerTool('get_tokens', {
8
- description: 'Get design tokens (colors, spacing, font sizes, shadows, etc.) used in lism-css. Use get_overview first to understand the framework, then use this tool to explore specific token categories.',
9
- inputSchema: {
10
- category: z.enum(TOKEN_CATEGORIES).default('all').describe('Token category to retrieve. Use "all" to get all categories.'),
11
- },
5
+ description: 'Get design tokens (colors, spacing, font sizes, shadows, etc.) used in lism-css. Returns the full token reference including CSS variable names and available values.\n' +
6
+ 'Use this when you need to check available token values, variable names, or design scales (e.g. "what spacing values exist?", "what are the font size tokens?").\n' +
7
+ 'For prop-to-CSS mappings, get_props_system is more suitable. For CSS conversion, use convert_css. Call get_overview first if you have not yet.\n' +
8
+ 'The response is pre-formatted Markdown. Output it verbatim. Do NOT summarize or omit token values.',
12
9
  annotations: READ_ONLY_ANNOTATIONS,
13
- }, ({ category }) => {
10
+ }, () => {
14
11
  try {
15
- const data = loadJSON('tokens.json', z.array(TokenCategorySchema));
16
- const filtered = category === 'all' ? data : data.filter((c) => c.category === category);
17
- return success({ tokens: filtered });
12
+ return markdownResponse(loadMarkdown('tokens.md'));
18
13
  }
19
14
  catch (e) {
20
- return error(`Failed to load tokens data: ${e instanceof Error ? e.message : String(e)}. The data files may not be built yet. Ensure the server was installed correctly.`);
15
+ return error(`Failed to load tokens data: ${e instanceof Error ? e.message : String(e)}. The data files may not be built yet. Run "pnpm build" in packages/mcp first.`);
21
16
  }
22
17
  });
23
18
  }