@lism-css/mcp 0.11.0 → 0.13.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 (61) hide show
  1. package/README.ja.md +4 -2
  2. package/README.md +4 -2
  3. package/dist/data/docs-index.json +456 -178
  4. package/dist/data/guides/SKILL.md +154 -0
  5. package/dist/data/guides/base-styles.md +106 -0
  6. package/dist/data/guides/components-core.md +292 -0
  7. package/dist/data/guides/components-ui.md +351 -0
  8. package/dist/data/guides/css-rules.md +166 -0
  9. package/dist/data/guides/primitive-class.md +148 -0
  10. package/dist/data/guides/primitives/a--decorator.md +45 -0
  11. package/dist/data/guides/primitives/a--divider.md +69 -0
  12. package/dist/data/guides/primitives/a--icon.md +105 -0
  13. package/dist/data/guides/primitives/a--spacer.md +63 -0
  14. package/dist/data/guides/primitives/is--boxLink.md +97 -0
  15. package/dist/data/guides/primitives/is--container.md +46 -0
  16. package/dist/data/guides/primitives/is--layer.md +71 -0
  17. package/dist/data/guides/primitives/is--vertical.md +52 -0
  18. package/dist/data/guides/primitives/is--wrapper.md +87 -0
  19. package/dist/data/guides/primitives/l--box.md +31 -0
  20. package/dist/data/guides/primitives/l--center.md +55 -0
  21. package/dist/data/guides/primitives/l--cluster.md +38 -0
  22. package/dist/data/guides/primitives/l--columns.md +72 -0
  23. package/dist/data/guides/primitives/l--flex.md +74 -0
  24. package/dist/data/guides/primitives/l--flow.md +134 -0
  25. package/dist/data/guides/primitives/l--fluidCols.md +68 -0
  26. package/dist/data/guides/primitives/l--frame.md +94 -0
  27. package/dist/data/guides/primitives/l--grid.md +68 -0
  28. package/dist/data/guides/primitives/l--sideMain.md +102 -0
  29. package/dist/data/guides/primitives/l--stack.md +56 -0
  30. package/dist/data/guides/primitives/l--switchCols.md +69 -0
  31. package/dist/data/guides/primitives/l--tileGrid.md +61 -0
  32. package/dist/data/guides/prop-responsive.md +54 -0
  33. package/dist/data/guides/property-class.md +401 -0
  34. package/dist/data/guides/set-class.md +192 -0
  35. package/dist/data/guides/tokens.md +228 -0
  36. package/dist/data/guides/utility-class.md +82 -0
  37. package/dist/data/meta.js +2 -2
  38. package/dist/index.js +4 -0
  39. package/dist/lib/load-data.js +2 -11
  40. package/dist/lib/load-markdown.d.ts +7 -0
  41. package/dist/lib/load-markdown.js +47 -0
  42. package/dist/lib/markdown-utils.d.ts +42 -0
  43. package/dist/lib/markdown-utils.js +158 -0
  44. package/dist/lib/schemas.d.ts +0 -242
  45. package/dist/lib/schemas.js +0 -64
  46. package/dist/lib/search.d.ts +2 -16
  47. package/dist/lib/search.js +29 -69
  48. package/dist/lib/types.d.ts +0 -64
  49. package/dist/tools/convert-css.js +96 -55
  50. package/dist/tools/get-component.js +150 -30
  51. package/dist/tools/get-guide.d.ts +2 -0
  52. package/dist/tools/get-guide.js +45 -0
  53. package/dist/tools/get-overview.js +26 -39
  54. package/dist/tools/get-props-system.js +45 -33
  55. package/dist/tools/get-tokens.js +9 -14
  56. package/dist/tools/search-docs.js +28 -10
  57. package/package.json +2 -2
  58. package/dist/data/components.json +0 -564
  59. package/dist/data/overview.json +0 -114
  60. package/dist/data/props-system.json +0 -1147
  61. package/dist/data/tokens.json +0 -148
@@ -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,12 +1,70 @@
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 { getGuideFilenames, 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
+ /** 入力を正規化してエイリアス検索のキーに変換する。
6
+ * `<Flex>` / `Flex` / `l--flex` / `flex` をすべて `flex` に揃える。 */
7
+ function normalizeComponentKey(input) {
8
+ return input
9
+ .trim()
10
+ .toLowerCase()
11
+ .replace(/^<|>$/g, '')
12
+ .replace(/^(l--|is--|a--|c--)/, '');
13
+ }
14
+ /** 入力が `<ComponentName>` 形式(React コンポーネントとしての問い合わせ)か判定する */
15
+ function isAngleBracketNotation(input) {
16
+ return /^<[^<>]+>$/.test(input.trim());
17
+ }
18
+ /** primitives/*.md の先頭行 `# l--flex / \`<Flex>\`` からクラス名とコンポーネント名を抽出する */
19
+ function parsePrimitiveHeading(md) {
20
+ const firstLine = md.split('\n', 1)[0] ?? '';
21
+ const match = firstLine.match(/^#\s+((?:l|is|a|c)--[A-Za-z0-9]+)(?:\s*\/\s*`<([A-Za-z0-9]+)>`)?/);
22
+ if (!match)
23
+ return null;
24
+ return { className: match[1], componentName: match[2] };
25
+ }
26
+ let classAliasMap = null;
27
+ let componentAliasMap = null;
28
+ /** primitives/*.md を走査して、クラス名由来 / React コンポーネント名由来の alias map を構築する(遅延初期化・キャッシュ)。
29
+ * angle-bracket 形式の問い合わせ(`<Vertical>` など)には componentAliasMap のみを照合し、
30
+ * クラス専用プリミティブ(例: is--vertical)を誤ってヒットさせないために分離している。 */
31
+ function buildAliasMaps() {
32
+ if (classAliasMap && componentAliasMap)
33
+ return;
34
+ const classMap = new Map();
35
+ const componentMap = new Map();
36
+ for (const filename of getGuideFilenames()) {
37
+ if (!filename.startsWith('primitives/'))
38
+ continue;
39
+ const parsed = parsePrimitiveHeading(loadMarkdown(filename));
40
+ if (!parsed)
41
+ continue;
42
+ classMap.set(normalizeComponentKey(parsed.className), filename);
43
+ if (parsed.componentName) {
44
+ componentMap.set(normalizeComponentKey(parsed.componentName), filename);
45
+ }
46
+ }
47
+ classAliasMap = classMap;
48
+ componentAliasMap = componentMap;
49
+ }
50
+ function getClassAliasMap() {
51
+ buildAliasMaps();
52
+ return classAliasMap;
53
+ }
54
+ function getComponentAliasMap() {
55
+ buildAliasMaps();
56
+ return componentAliasMap;
57
+ }
5
58
  export function registerGetComponent(server) {
6
59
  server.registerTool('get_component', {
7
- description: '特定のlism-cssコンポーネントに関する詳細情報(プロパティ、使用例、カテゴリー)を取得します。該当するものが見つからない場合は、より広範なキーワードで search_docs を実行してください。',
60
+ description: 'Get detailed information about a specific lism-css component: purpose, props, and usage examples.\n' +
61
+ 'Use this when you need documentation for a known component by name (e.g. "Box", "Flex", "Accordion", "Lism", "HTML").\n' +
62
+ 'Accepts multiple notations: "Flex", "<Flex>", "l--flex", "flex" all resolve to the same entry.\n' +
63
+ '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' +
64
+ 'If the component is not found, suggestions will be provided — follow up with search_docs for a broader query.\n' +
65
+ 'The response is pre-formatted Markdown. Output it verbatim. Do NOT summarize or omit code examples.',
8
66
  inputSchema: {
9
- name: z.string().describe('Component name to look up (e.g. "Box", "Flex", "Accordion").'),
67
+ name: z.string().describe('Component name to look up (e.g. "Box", "Flex", "Accordion", "l--flex", "<Flex>").'),
10
68
  package: z
11
69
  .enum(['lism-css', '@lism-css/ui'])
12
70
  .optional()
@@ -15,42 +73,104 @@ export function registerGetComponent(server) {
15
73
  annotations: READ_ONLY_ANNOTATIONS,
16
74
  }, ({ name, package: pkg }) => {
17
75
  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);
76
+ const normalizedKey = normalizeComponentKey(name);
77
+ const rawLower = name.toLowerCase();
78
+ const isAngle = isAngleBracketNotation(name);
79
+ // --- 1) lism-css コア: primitives/ の個別ファイルを alias map で解決 ---
80
+ // angle-bracket 形式 (`<Vertical>`) は React コンポーネント alias のみと照合し、
81
+ // クラス専用プリミティブ (is--vertical 等) への false positive を避ける。
82
+ if (!pkg || pkg === 'lism-css') {
83
+ const componentHit = getComponentAliasMap().get(normalizedKey);
84
+ if (componentHit) {
85
+ return markdownResponse(loadMarkdown(componentHit));
86
+ }
87
+ if (!isAngle) {
88
+ const classHit = getClassAliasMap().get(normalizedKey);
89
+ if (classHit) {
90
+ return markdownResponse(loadMarkdown(classHit));
91
+ }
92
+ }
93
+ }
94
+ // --- 2) lism-css コア: components-core.md のセマンティック/コアコンポーネント ---
95
+ if (!pkg || pkg === 'lism-css') {
96
+ const coreMd = loadMarkdown('components-core.md');
97
+ // 見出しにコンポーネント名を含むセクションを検索(Lism, HTML 等)
98
+ const coreLines = coreMd.split('\n');
99
+ const headingLine = coreLines.find((l) => /^#{2,3}\s/.test(l) && l.toLowerCase().includes(`<${rawLower}>`));
100
+ if (headingLine) {
101
+ const headingText = headingLine.replace(/^#+\s*/, '').trim();
102
+ const section = findComponentByHeading(coreMd, headingText);
103
+ if (section)
104
+ return markdownResponse(section);
105
+ }
106
+ // テーブル内の `<ComponentName>` で検索
107
+ const coreSection = findComponentInTables(coreMd, name);
108
+ if (coreSection) {
109
+ return markdownResponse(coreSection);
110
+ }
111
+ }
112
+ // --- 3) @lism-css/ui: components-ui.md を検索 ---
113
+ if (!pkg || pkg === '@lism-css/ui') {
114
+ const uiMd = loadMarkdown('components-ui.md');
115
+ // ## ComponentName 形式の見出しで検索(完全一致)
116
+ const uiSection = findComponentByHeading(uiMd, name);
117
+ if (uiSection) {
118
+ return markdownResponse(uiSection);
119
+ }
120
+ // 大文字小文字を無視した見出し検索
121
+ const uiLines = uiMd.split('\n');
122
+ const headingLine = uiLines.find((l) => l.startsWith('## ') && l.slice(3).trim().toLowerCase() === rawLower);
123
+ if (headingLine) {
124
+ const section = findComponentByHeading(uiMd, headingLine.slice(3).trim());
125
+ if (section)
126
+ return markdownResponse(section);
127
+ }
22
128
  }
23
- 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 });
129
+ // --- 4) 部分一致で候補を提示 ---
130
+ const uiMd = loadMarkdown('components-ui.md');
131
+ const coreMd = loadMarkdown('components-core.md');
132
+ const moduleCandidates = [];
133
+ if (!pkg || pkg === 'lism-css') {
134
+ const seenFiles = new Set();
135
+ for (const map of [getComponentAliasMap(), getClassAliasMap()]) {
136
+ for (const [key, file] of map) {
137
+ if (key.includes(normalizedKey) && !seenFiles.has(file)) {
138
+ seenFiles.add(file);
139
+ moduleCandidates.push(file.replace(/^primitives\//, '').replace(/\.md$/, ''));
140
+ }
141
+ }
142
+ }
28
143
  }
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' });
144
+ const coreCandidates = [];
145
+ if (!pkg || pkg === 'lism-css') {
146
+ for (const line of coreMd.split('\n')) {
147
+ if (line.startsWith('|') && line.toLowerCase().includes(rawLower)) {
148
+ const match = line.match(/`<([^>]+)>`/);
149
+ if (match)
150
+ coreCandidates.push(match[1]);
151
+ }
152
+ }
33
153
  }
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 });
154
+ const uiCandidates = [];
155
+ if (!pkg || pkg === '@lism-css/ui') {
156
+ for (const l of uiMd.split('\n')) {
157
+ if (l.startsWith('## ') && l.toLowerCase().includes(rawLower)) {
158
+ uiCandidates.push(l.slice(3).trim());
159
+ }
160
+ }
41
161
  }
42
- // Step 3: 名前の部分一致
43
- const suggestions = candidates.filter((c) => c.name.toLowerCase().includes(nameLower)).map((c) => ({ name: c.name, package: c.package }));
162
+ const suggestions = [...new Set([...moduleCandidates, ...coreCandidates, ...uiCandidates])];
44
163
  if (suggestions.length > 0) {
45
164
  return notFound(`Component "${name}" not found. Did you mean one of these? Or try search_docs with a broader query.`, {
46
165
  suggestions,
47
166
  });
48
167
  }
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 });
168
+ return notFound(`Component "${name}" not found. Try search_docs or get_guide to find related pages.`, {
169
+ tip: 'Use get_guide with topic="components-core" or "components-ui" to see all available components.',
170
+ });
51
171
  }
52
172
  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.`);
173
+ 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
174
  }
55
175
  });
56
176
  }
@@ -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
+ 'primitive-class': { file: 'primitive-class.md', label: 'Primitive class prefixes (is--, l--, a--) and Component class (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
  }