@reglow/reui-mcp 0.1.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 +204 -0
- package/catalog.mjs +158 -0
- package/cli-utils.mjs +29 -0
- package/index.mjs +169 -0
- package/integrations.mjs +273 -0
- package/knowledge.mjs +923 -0
- package/package.json +46 -0
- package/server.mjs +710 -0
- package/setup.mjs +400 -0
- package/site.mjs +155 -0
- package/skills/reui-components/BEST_PRACTICES.md +219 -0
- package/skills/reui-components/COMPONENTS.md +141 -0
- package/skills/reui-components/SKILL.md +172 -0
- package/skills/reui-components/WORKFLOWS.md +264 -0
- package/skills.mjs +197 -0
- package/tar.mjs +115 -0
package/server.mjs
ADDED
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReUI MCP 服务器:注册工具并桥接 stdio 传输
|
|
3
|
+
*
|
|
4
|
+
* 工具能力对齐 Semi MCP,并针对 ReUI(Vue 3 + Tailwind v4 组件库)补充检索与指南:
|
|
5
|
+
* - get_reui_document 组件文档 / 组件总览
|
|
6
|
+
* - get_reui_code_examples 文档中的可复制示例
|
|
7
|
+
* - list_component_files 组件源码文件清单
|
|
8
|
+
* - get_file_code 读取组件内文件内容
|
|
9
|
+
* - get_function_code 提取函数 / 常量 / 类定义
|
|
10
|
+
* - search_reui_docs 跨文档与源码检索
|
|
11
|
+
* - get_reui_guide 库级指南(安装 / 主题 / i18n / Nuxt / 构建 / 约定)
|
|
12
|
+
*
|
|
13
|
+
* 注意:stdio 模式下 stdout 仅用于协议通信,所有日志必须走 stderr。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import {
|
|
22
|
+
CATEGORIES,
|
|
23
|
+
DOCS_BASE,
|
|
24
|
+
componentLabel,
|
|
25
|
+
docsUrlFor,
|
|
26
|
+
} from "./catalog.mjs";
|
|
27
|
+
import { createKnowledge } from "./knowledge.mjs";
|
|
28
|
+
|
|
29
|
+
const PACKAGE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const OWN_PACKAGE = JSON.parse(
|
|
31
|
+
fs.readFileSync(path.join(PACKAGE_DIR, "package.json"), "utf8"),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const GUIDE_TOPICS = [
|
|
35
|
+
"overview",
|
|
36
|
+
"install",
|
|
37
|
+
"theme",
|
|
38
|
+
"i18n",
|
|
39
|
+
"nuxt",
|
|
40
|
+
"build",
|
|
41
|
+
"conventions",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/** 文本结果 */
|
|
45
|
+
function textResult(text) {
|
|
46
|
+
return { content: [{ type: "text", text }] };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 错误结果(让 AI 明确失败原因并自行纠正参数) */
|
|
50
|
+
function errorResult(text) {
|
|
51
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function sourceLabel(source) {
|
|
55
|
+
return (
|
|
56
|
+
{
|
|
57
|
+
workspace: "仓库工作区(--root / 仓库内运行)",
|
|
58
|
+
local: "项目本地已安装包",
|
|
59
|
+
version: "指定 npm 版本(缓存)",
|
|
60
|
+
site: "官方文档站点兜底(最新版)",
|
|
61
|
+
none: "无可用知识源",
|
|
62
|
+
}[source] ?? source
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 响应头:知识来源与版本,避免 AI 误判文档对应的版本 */
|
|
67
|
+
function metaHeader(knowledge, extraLines = []) {
|
|
68
|
+
const meta = knowledge.meta();
|
|
69
|
+
const lines = [
|
|
70
|
+
`> 知识源:${sourceLabel(meta.source)}|组件库版本:\`${meta.version}\`${meta.root ? `|路径:\`${meta.root}\`` : ""
|
|
71
|
+
}`,
|
|
72
|
+
];
|
|
73
|
+
if (meta.warning) lines.push(`> ⚠️ ${meta.warning}`);
|
|
74
|
+
lines.push(...extraLines);
|
|
75
|
+
return lines.join("\n");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** 从组件 index 文件推导导入方式 */
|
|
79
|
+
function importInfo(knowledge, entry) {
|
|
80
|
+
const candidates = ["index.ts", "index.d.ts", "index.js"];
|
|
81
|
+
let text = null;
|
|
82
|
+
let used = null;
|
|
83
|
+
for (const file of candidates) {
|
|
84
|
+
const result = knowledge.readComponentFile(entry.name, file);
|
|
85
|
+
if (result?.text) {
|
|
86
|
+
text = result.text;
|
|
87
|
+
used = file;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!text) return null;
|
|
92
|
+
const names = new Set();
|
|
93
|
+
for (const match of text.matchAll(/export\s*\{([^}]*)\}/g)) {
|
|
94
|
+
for (const part of match[1].split(",")) {
|
|
95
|
+
const alias = part.trim().match(/as\s+([A-Za-z_$][\w$]*)$/);
|
|
96
|
+
const raw = alias
|
|
97
|
+
? alias[1]
|
|
98
|
+
: part.trim().match(/^([A-Za-z_$][\w$]*)/)?.[1];
|
|
99
|
+
if (raw) names.add(raw);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const symbolList = [...names];
|
|
103
|
+
if (symbolList.length === 0) return null;
|
|
104
|
+
const subpath =
|
|
105
|
+
entry.layer === "ui"
|
|
106
|
+
? `@reglow/reui/ui/${entry.name}`
|
|
107
|
+
: `@reglow/reui/reui/${entry.name}`;
|
|
108
|
+
return {
|
|
109
|
+
file: used,
|
|
110
|
+
symbols: symbolList,
|
|
111
|
+
main: `import { ${symbolList.join(", ")} } from "@reglow/reui";`,
|
|
112
|
+
subpath: `import { ${symbolList.join(", ")} } from "${subpath}";`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 组件概览(按官网分类组织) */
|
|
117
|
+
function renderOverview(knowledge, category) {
|
|
118
|
+
const entries = knowledge.listComponents({ category });
|
|
119
|
+
const byName = new Map(entries.map((entry) => [entry.name, entry]));
|
|
120
|
+
const blocks = [];
|
|
121
|
+
const knownCategories = CATEGORIES.map((item) => item.key);
|
|
122
|
+
for (const item of CATEGORIES) {
|
|
123
|
+
if (category && item.key !== category) continue;
|
|
124
|
+
const rows = item.components
|
|
125
|
+
.map((component) => {
|
|
126
|
+
const entry = byName.get(component.name);
|
|
127
|
+
if (!entry) return null;
|
|
128
|
+
const importName = entry.files.includes("index.ts") ? "✅" : "(dist)";
|
|
129
|
+
return `| \`${component.name}\` ${component.cn} | ${component.desc} | ${importName} | [文档](${docsUrlFor(component.name)}) |`;
|
|
130
|
+
})
|
|
131
|
+
.filter(Boolean);
|
|
132
|
+
if (rows.length === 0) continue;
|
|
133
|
+
blocks.push(
|
|
134
|
+
`## ${item.label}\n\n| 组件 | 说明 | 源码 | 官网 |\n| --- | --- | --- | --- |\n${rows.join("\n")}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
// 官网未收录但仓库内存在的组件
|
|
138
|
+
const extra = entries.filter(
|
|
139
|
+
(entry) => !knownCategories.includes(entry.category),
|
|
140
|
+
);
|
|
141
|
+
if (extra.length > 0 && !category) {
|
|
142
|
+
blocks.push(
|
|
143
|
+
`## 其他(官网未收录 / 基础组件)\n\n| 组件 | 说明 | 来源 |\n| --- | --- | --- |\n${extra
|
|
144
|
+
.map(
|
|
145
|
+
(entry) =>
|
|
146
|
+
`| \`${entry.name}\` | ${entry.description || "—"} | ${entry.layer} |`,
|
|
147
|
+
)
|
|
148
|
+
.join("\n")}`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return blocks.join("\n\n");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 创建 MCP 服务器
|
|
156
|
+
*
|
|
157
|
+
* @param {{ resolveKnowledge: (version?: string) => Promise<object>, defaultKnowledge: object, version?: string }} options
|
|
158
|
+
*/
|
|
159
|
+
export function createServer({ resolveKnowledge, defaultKnowledge }) {
|
|
160
|
+
const server = new McpServer({
|
|
161
|
+
name: "reui-mcp",
|
|
162
|
+
version: OWN_PACKAGE.version,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const withKnowledge = async (version) => resolveKnowledge(version);
|
|
166
|
+
|
|
167
|
+
// ============================ 1. 组件文档 ============================
|
|
168
|
+
server.registerTool(
|
|
169
|
+
"get_reui_document",
|
|
170
|
+
{
|
|
171
|
+
title: "获取 ReUI 组件文档",
|
|
172
|
+
description:
|
|
173
|
+
"获取 ReUI 组件库的组件文档。不传 component 时返回按分类组织的组件总览(组件名/说明/源码可用性/官网链接);" +
|
|
174
|
+
"传 component 时返回该组件的完整文档(README:安装用法、代码示例、Props/Events/Slots 表格、Semantic DOM)。" +
|
|
175
|
+
"包内缺少 README 时会自动回退官方文档站点并在回复中标注来源。",
|
|
176
|
+
inputSchema: {
|
|
177
|
+
component: z
|
|
178
|
+
.string()
|
|
179
|
+
.optional()
|
|
180
|
+
.describe(
|
|
181
|
+
"组件名,kebab-case,如 splitter、date-picker、table;不传则返回组件总览",
|
|
182
|
+
),
|
|
183
|
+
category: z
|
|
184
|
+
.enum([
|
|
185
|
+
"general",
|
|
186
|
+
"layout",
|
|
187
|
+
"navigation",
|
|
188
|
+
"data-entry",
|
|
189
|
+
"data-display",
|
|
190
|
+
"feedback",
|
|
191
|
+
"others",
|
|
192
|
+
])
|
|
193
|
+
.optional()
|
|
194
|
+
.describe("分类过滤(仅总览生效)"),
|
|
195
|
+
version: z
|
|
196
|
+
.string()
|
|
197
|
+
.optional()
|
|
198
|
+
.describe(
|
|
199
|
+
"指定 @reglow/reui 版本(如 0.1.10、latest);不传则使用项目本地已安装版本",
|
|
200
|
+
),
|
|
201
|
+
source: z
|
|
202
|
+
.enum(["auto", "local", "site"])
|
|
203
|
+
.optional()
|
|
204
|
+
.describe(
|
|
205
|
+
"auto(默认):本地优先、缺失时回退官网;local:仅本地;site:强制取官网最新文档",
|
|
206
|
+
),
|
|
207
|
+
},
|
|
208
|
+
annotations: {
|
|
209
|
+
readOnlyHint: true,
|
|
210
|
+
idempotentHint: true,
|
|
211
|
+
openWorldHint: true,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
async ({ component, category, version, source }) => {
|
|
215
|
+
try {
|
|
216
|
+
const knowledge = await withKnowledge(version);
|
|
217
|
+
const meta = knowledge.meta();
|
|
218
|
+
if (!component) {
|
|
219
|
+
const overview = renderOverview(knowledge, category);
|
|
220
|
+
if (!overview) {
|
|
221
|
+
const siteOverview = await knowledge.siteOverview();
|
|
222
|
+
if (siteOverview) {
|
|
223
|
+
return textResult(
|
|
224
|
+
`${metaHeader(knowledge, ["", `> 官网总览:${siteOverview.url}`])}\n\n${siteOverview.markdown}`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return errorResult(
|
|
228
|
+
`未找到任何组件信息。\n\n${metaHeader(knowledge)}\n\n` +
|
|
229
|
+
`请使用 \`--root <reui 仓库路径>\` 启动,或在已安装 @reglow/reui 的项目中运行;也可用 \`version\` 指定 npm 版本。`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
return textResult(
|
|
233
|
+
`${metaHeader(knowledge, ["", `> 官网总览:${DOCS_BASE}`])}\n\n# ReUI 组件总览\n\n${overview}`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const entry = knowledge.getComponent(component);
|
|
238
|
+
if (source === "site" || (meta.source === "site" && !entry)) {
|
|
239
|
+
const { fetchSiteDocument } = await import("./site.mjs");
|
|
240
|
+
const siteDoc = await fetchSiteDocument(component);
|
|
241
|
+
if (siteDoc) {
|
|
242
|
+
return textResult(
|
|
243
|
+
`> 知识源:官方文档站点(最新版)|链接:${siteDoc.url}\n\n# ${componentLabel(component)}\n\n${siteDoc.markdown}`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
return errorResult(
|
|
247
|
+
`官方文档站点暂无「${component}」页面(可能是新组件或名称不符)。\n可用组件见 \`get_reui_document\`(不传 component)。`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const doc = await knowledge.getDocument(component);
|
|
252
|
+
if (!entry && doc.kind === "none") {
|
|
253
|
+
const names = knowledge
|
|
254
|
+
.listComponents()
|
|
255
|
+
.map((item) => item.name)
|
|
256
|
+
.slice(0, 60);
|
|
257
|
+
return errorResult(
|
|
258
|
+
`未找到组件「${component}」。\n\n可用组件(部分):${names.join(", ")}\n\n` +
|
|
259
|
+
`提示:组件名为 kebab-case;\`PageTable\` → \`table\`,\`QRCode\` → \`qr-code\`。`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const lines = [`# ${componentLabel(entry?.name ?? component)}`];
|
|
264
|
+
const facts = [];
|
|
265
|
+
if (entry) {
|
|
266
|
+
facts.push(
|
|
267
|
+
`> 分类:${entry.categoryLabel}|源码目录:\`${entry.dir}\``,
|
|
268
|
+
);
|
|
269
|
+
const imports = importInfo(knowledge, entry);
|
|
270
|
+
if (imports) {
|
|
271
|
+
facts.push(
|
|
272
|
+
"> 导入方式:",
|
|
273
|
+
"",
|
|
274
|
+
"```ts",
|
|
275
|
+
imports.main,
|
|
276
|
+
imports.subpath,
|
|
277
|
+
"```",
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
const mainFiles = entry.files.filter(
|
|
281
|
+
(file) => file.endsWith(".vue") || file.endsWith("context.ts"),
|
|
282
|
+
);
|
|
283
|
+
if (mainFiles.length > 0) {
|
|
284
|
+
facts.push(
|
|
285
|
+
`> 源码文件:${mainFiles.map((file) => `\`${file}\``).join("、")}`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (doc.url) facts.push(`> 官方文档:${doc.url}`);
|
|
290
|
+
if (doc.kind === "site") {
|
|
291
|
+
facts.push(
|
|
292
|
+
"> ⚠️ 该版本包内未包含组件 README,以下正文来自官方文档站点(最新版),示例可能与指定版本存在差异。",
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
if (doc.kind === "types") {
|
|
296
|
+
facts.push(
|
|
297
|
+
"> ⚠️ 该版本包内未包含组件 README,以下内容由 `dist/**/*.d.ts` 汇总而成(仅类型与注释,无示例)。",
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
lines.push(
|
|
301
|
+
"",
|
|
302
|
+
metaHeader(knowledge, facts),
|
|
303
|
+
"",
|
|
304
|
+
"---",
|
|
305
|
+
"",
|
|
306
|
+
doc.markdown || "(无文档内容)",
|
|
307
|
+
);
|
|
308
|
+
return textResult(lines.join("\n"));
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return errorResult(`获取文档失败:${error.message}`);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
// ============================ 2. 代码示例 ============================
|
|
316
|
+
server.registerTool(
|
|
317
|
+
"get_reui_code_examples",
|
|
318
|
+
{
|
|
319
|
+
title: "获取 ReUI 组件代码示例",
|
|
320
|
+
description:
|
|
321
|
+
"从组件文档中提取可直接复制的代码示例(Vue SFC / TS),保留示例所属小节标题。" +
|
|
322
|
+
"用于快速拿到「官方推荐写法」,避免臆造 API。可用 index 取第 N 个示例,lang 过滤语言。",
|
|
323
|
+
inputSchema: {
|
|
324
|
+
component: z.string().describe("组件名,kebab-case"),
|
|
325
|
+
index: z
|
|
326
|
+
.number()
|
|
327
|
+
.int()
|
|
328
|
+
.min(0)
|
|
329
|
+
.optional()
|
|
330
|
+
.describe("示例下标(从 0 开始);不传则返回全部"),
|
|
331
|
+
lang: z
|
|
332
|
+
.enum(["all", "vue", "ts", "js", "bash", "json", "text"])
|
|
333
|
+
.optional()
|
|
334
|
+
.describe("按语言过滤,默认 all"),
|
|
335
|
+
version: z.string().optional().describe("指定 @reglow/reui 版本"),
|
|
336
|
+
},
|
|
337
|
+
annotations: {
|
|
338
|
+
readOnlyHint: true,
|
|
339
|
+
idempotentHint: true,
|
|
340
|
+
openWorldHint: true,
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
async ({ component, index, lang, version }) => {
|
|
344
|
+
try {
|
|
345
|
+
const knowledge = await withKnowledge(version);
|
|
346
|
+
const result = await knowledge.getCodeExamples(component, {
|
|
347
|
+
index,
|
|
348
|
+
lang: lang ?? "all",
|
|
349
|
+
});
|
|
350
|
+
if (result.examples.length === 0) {
|
|
351
|
+
return errorResult(
|
|
352
|
+
`未找到「${component}」的代码示例(来源:${result.source})。\n` +
|
|
353
|
+
`可先用 \`get_reui_document\` 确认组件是否存在,或改用 \`get_reui_code_examples\` 的 lang=all。\n` +
|
|
354
|
+
`官网文档:${result.url ?? "无"}`,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
const blocks = result.examples.map(
|
|
358
|
+
(example, i) =>
|
|
359
|
+
`### ${index !== undefined ? index : i}. ${example.title}\n\n\`\`\`${example.lang}\n${example.code}\n\`\`\``,
|
|
360
|
+
);
|
|
361
|
+
const origin =
|
|
362
|
+
result.source === "readme"
|
|
363
|
+
? `组件 README(\`${result.path}\`,与项目版本一致)`
|
|
364
|
+
: result.source === "site"
|
|
365
|
+
? "官方文档站点(最新版,示例可能高于当前版本)"
|
|
366
|
+
: "未知来源";
|
|
367
|
+
return textResult(
|
|
368
|
+
`# ${componentLabel(component)} 代码示例(共 ${result.examples.length} 个)\n\n` +
|
|
369
|
+
`> 来源:${origin}${result.url ? `|官方文档:${result.url}` : ""}\n\n` +
|
|
370
|
+
metaHeader(knowledge) +
|
|
371
|
+
"\n\n---\n\n" +
|
|
372
|
+
blocks.join("\n\n"),
|
|
373
|
+
);
|
|
374
|
+
} catch (error) {
|
|
375
|
+
return errorResult(`获取代码示例失败:${error.message}`);
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
// ============================ 3. 文件清单 ============================
|
|
381
|
+
server.registerTool(
|
|
382
|
+
"list_component_files",
|
|
383
|
+
{
|
|
384
|
+
title: "列出 ReUI 组件源码文件",
|
|
385
|
+
description:
|
|
386
|
+
"列出组件目录下的源码文件清单(相对路径 / 行数 / 字节数),用于了解组件结构,再配合 get_file_code 查看具体实现。" +
|
|
387
|
+
"组件通常包含:Xxx.vue(组件实现)、context.ts / types.ts(类型与上下文)、index.ts(导出)、README.md(文档)、__tests__(单测,默认不列出)。",
|
|
388
|
+
inputSchema: {
|
|
389
|
+
component: z.string().describe("组件名,kebab-case"),
|
|
390
|
+
version: z.string().optional().describe("指定 @reglow/reui 版本"),
|
|
391
|
+
},
|
|
392
|
+
annotations: {
|
|
393
|
+
readOnlyHint: true,
|
|
394
|
+
idempotentHint: true,
|
|
395
|
+
openWorldHint: true,
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
async ({ component, version }) => {
|
|
399
|
+
try {
|
|
400
|
+
const knowledge = await withKnowledge(version);
|
|
401
|
+
const entry = knowledge.getComponent(component);
|
|
402
|
+
if (!entry)
|
|
403
|
+
return errorResult(
|
|
404
|
+
`未找到组件「${component}」。可用组件见 get_reui_document(不传 component)。`,
|
|
405
|
+
);
|
|
406
|
+
const files = knowledge.listFiles(component) ?? [];
|
|
407
|
+
const rows = files
|
|
408
|
+
.map(
|
|
409
|
+
(file) =>
|
|
410
|
+
`| \`${file.path}\` | ${file.lines || "—"} | ${file.size} |`,
|
|
411
|
+
)
|
|
412
|
+
.join("\n");
|
|
413
|
+
const imports = importInfo(knowledge, entry);
|
|
414
|
+
return textResult(
|
|
415
|
+
`# ${componentLabel(entry.name)} 源码文件\n\n` +
|
|
416
|
+
metaHeader(knowledge, [
|
|
417
|
+
`> 分类:${entry.categoryLabel}|目录:\`${entry.dir}\`|文件数:${files.length}`,
|
|
418
|
+
...(imports ? [`> 导入:\`${imports.main}\``] : []),
|
|
419
|
+
]) +
|
|
420
|
+
`\n\n| 文件 | 行数 | 字节 |\n| --- | --- | --- |\n${rows}\n\n` +
|
|
421
|
+
`> 下一步:\`get_file_code\` 读取具体文件,\`get_function_code\` 直接取某个函数实现。`,
|
|
422
|
+
);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
return errorResult(`列出文件失败:${error.message}`);
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
// ============================ 4. 文件内容 ============================
|
|
430
|
+
server.registerTool(
|
|
431
|
+
"get_file_code",
|
|
432
|
+
{
|
|
433
|
+
title: "读取 ReUI 组件文件内容",
|
|
434
|
+
description:
|
|
435
|
+
"读取组件目录内某个文件的代码(支持 startLine/endLine 只取片段)。用于查看组件实现细节、类型定义、样式与默认值。" +
|
|
436
|
+
"文件路径来自 list_component_files 的结果,禁止越出组件目录。",
|
|
437
|
+
inputSchema: {
|
|
438
|
+
component: z.string().describe("组件名,kebab-case"),
|
|
439
|
+
file: z
|
|
440
|
+
.string()
|
|
441
|
+
.describe(
|
|
442
|
+
"组件目录内的相对路径,如 Splitter.vue、context.ts、index.ts",
|
|
443
|
+
),
|
|
444
|
+
version: z.string().optional().describe("指定 @reglow/reui 版本"),
|
|
445
|
+
startLine: z
|
|
446
|
+
.number()
|
|
447
|
+
.int()
|
|
448
|
+
.min(1)
|
|
449
|
+
.optional()
|
|
450
|
+
.describe("起始行(从 1 开始,含)"),
|
|
451
|
+
endLine: z.number().int().min(1).optional().describe("结束行(含)"),
|
|
452
|
+
},
|
|
453
|
+
annotations: {
|
|
454
|
+
readOnlyHint: true,
|
|
455
|
+
idempotentHint: true,
|
|
456
|
+
openWorldHint: true,
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
async ({ component, file, version, startLine, endLine }) => {
|
|
460
|
+
try {
|
|
461
|
+
const knowledge = await withKnowledge(version);
|
|
462
|
+
const result = knowledge.readComponentFile(component, file, {
|
|
463
|
+
startLine,
|
|
464
|
+
endLine,
|
|
465
|
+
});
|
|
466
|
+
if (result.error)
|
|
467
|
+
return errorResult(`${result.error}\n\n${metaHeader(knowledge)}`);
|
|
468
|
+
const ext = path.extname(result.path).replace(".", "") || "ts";
|
|
469
|
+
const lang = ext === "vue" ? "vue" : ext === "d.ts" ? "ts" : ext;
|
|
470
|
+
return textResult(
|
|
471
|
+
`# \`${result.path}\`\n\n` +
|
|
472
|
+
metaHeader(knowledge, [
|
|
473
|
+
`> 组件:${componentLabel(component)}|总行数:${result.totalLines}${startLine || endLine
|
|
474
|
+
? `|当前片段:${result.startLine}-${result.endLine}`
|
|
475
|
+
: ""
|
|
476
|
+
}`,
|
|
477
|
+
]) +
|
|
478
|
+
`\n\n\`\`\`${lang}\n${result.text}\n\`\`\``,
|
|
479
|
+
);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
return errorResult(`读取文件失败:${error.message}`);
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
// ============================ 5. 函数实现 ============================
|
|
487
|
+
server.registerTool(
|
|
488
|
+
"get_function_code",
|
|
489
|
+
{
|
|
490
|
+
title: "获取 ReUI 组件函数实现",
|
|
491
|
+
description:
|
|
492
|
+
"提取组件源码中某个函数 / 常量 / 类 / 接口的完整实现(含起止行号),用于深入理解组件逻辑与可复用工具。" +
|
|
493
|
+
"未命中时会返回该组件可用符号清单,便于纠正名称后重试。",
|
|
494
|
+
inputSchema: {
|
|
495
|
+
component: z.string().describe("组件名,kebab-case"),
|
|
496
|
+
name: z
|
|
497
|
+
.string()
|
|
498
|
+
.describe("符号名,如 startDrag、equalizeFrPanels、useResizable"),
|
|
499
|
+
file: z
|
|
500
|
+
.string()
|
|
501
|
+
.optional()
|
|
502
|
+
.describe(
|
|
503
|
+
"限定文件(如 Splitter.vue);不传则在组件内所有 .vue/.ts 中查找",
|
|
504
|
+
),
|
|
505
|
+
version: z.string().optional().describe("指定 @reglow/reui 版本"),
|
|
506
|
+
},
|
|
507
|
+
annotations: {
|
|
508
|
+
readOnlyHint: true,
|
|
509
|
+
idempotentHint: true,
|
|
510
|
+
openWorldHint: true,
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
async ({ component, name, file, version }) => {
|
|
514
|
+
try {
|
|
515
|
+
const knowledge = await withKnowledge(version);
|
|
516
|
+
const result = knowledge.findFunction(component, name, { file });
|
|
517
|
+
if (result.error) {
|
|
518
|
+
const similar = (result.similarSymbols ?? []).join(", ");
|
|
519
|
+
const symbols = (result.availableSymbols ?? [])
|
|
520
|
+
.slice(0, 200)
|
|
521
|
+
.join(", ");
|
|
522
|
+
return errorResult(
|
|
523
|
+
`${result.error}\n\n` +
|
|
524
|
+
(similar ? `最相近的符号:${similar}\n\n` : "") +
|
|
525
|
+
(result.searched?.length
|
|
526
|
+
? `已检索:${result.searched.map((item) => `\`${item}\``).join("、")}\n\n`
|
|
527
|
+
: "") +
|
|
528
|
+
(symbols
|
|
529
|
+
? `该组件可用符号(共 ${result.allSymbolsCount ?? "?"} 个,展示前 80 个):${symbols}`
|
|
530
|
+
: "") +
|
|
531
|
+
`\n\n提示:也可用 \`search_reui_docs\` 以关键词定位,再用 \`get_file_code\` 读取上下文。`,
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
return textResult(
|
|
535
|
+
`# \`${result.name}\`(${result.kind})\n\n` +
|
|
536
|
+
metaHeader(knowledge, [
|
|
537
|
+
`> 组件:${componentLabel(component)}|文件:\`${result.path}\`|行号:${result.startLine}-${result.endLine}`,
|
|
538
|
+
]) +
|
|
539
|
+
`\n\n\`\`\`ts\n${result.text}\n\`\`\``,
|
|
540
|
+
);
|
|
541
|
+
} catch (error) {
|
|
542
|
+
return errorResult(`提取函数失败:${error.message}`);
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
// ============================ 6. 文档检索 ============================
|
|
548
|
+
server.registerTool(
|
|
549
|
+
"search_reui_docs",
|
|
550
|
+
{
|
|
551
|
+
title: "检索 ReUI 文档与源码",
|
|
552
|
+
description:
|
|
553
|
+
"在组件文档、源码与库级文档中做关键词检索(多关键词空格分隔,按“全部命中”过滤),返回「组件 / 文件:行号 + 上下文片段 + 文档链接」。" +
|
|
554
|
+
"适合在不确定用哪个组件、或想找某个 prop / 事件 / CSS 变量在何处实现时使用。",
|
|
555
|
+
inputSchema: {
|
|
556
|
+
query: z
|
|
557
|
+
.string()
|
|
558
|
+
.describe("关键词,支持多词(空格分隔,如「折叠 collapsible」)"),
|
|
559
|
+
component: z.string().optional().describe("限定单个组件"),
|
|
560
|
+
category: z
|
|
561
|
+
.enum([
|
|
562
|
+
"general",
|
|
563
|
+
"layout",
|
|
564
|
+
"navigation",
|
|
565
|
+
"data-entry",
|
|
566
|
+
"data-display",
|
|
567
|
+
"feedback",
|
|
568
|
+
"others",
|
|
569
|
+
])
|
|
570
|
+
.optional()
|
|
571
|
+
.describe("限定分类"),
|
|
572
|
+
limit: z
|
|
573
|
+
.number()
|
|
574
|
+
.int()
|
|
575
|
+
.min(1)
|
|
576
|
+
.max(60)
|
|
577
|
+
.optional()
|
|
578
|
+
.describe("最多返回条数,默认 20"),
|
|
579
|
+
version: z.string().optional().describe("指定 @reglow/reui 版本"),
|
|
580
|
+
},
|
|
581
|
+
annotations: {
|
|
582
|
+
readOnlyHint: true,
|
|
583
|
+
idempotentHint: true,
|
|
584
|
+
openWorldHint: true,
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
async ({ query, component, category, limit, version }) => {
|
|
588
|
+
try {
|
|
589
|
+
const knowledge = await withKnowledge(version);
|
|
590
|
+
const { hits, scanned, terms } = knowledge.search(query, {
|
|
591
|
+
component,
|
|
592
|
+
category,
|
|
593
|
+
limit,
|
|
594
|
+
});
|
|
595
|
+
if (hits.length === 0) {
|
|
596
|
+
return textResult(
|
|
597
|
+
`未检索到与「${query}」匹配的内容(已扫描 ${scanned} 个文件)。\n\n` +
|
|
598
|
+
`${metaHeader(knowledge)}\n\n` +
|
|
599
|
+
`建议:减少关键词数量、换用英文标识(如 \`allowClear\`)、或先用 \`get_reui_document\` 浏览组件总览。`,
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
const grouped = new Map();
|
|
603
|
+
for (const hit of hits) {
|
|
604
|
+
if (!grouped.has(hit.component)) grouped.set(hit.component, []);
|
|
605
|
+
grouped.get(hit.component).push(hit);
|
|
606
|
+
}
|
|
607
|
+
const blocks = [...grouped.entries()].map(([name, items]) => {
|
|
608
|
+
const link = items[0].docsUrl
|
|
609
|
+
? `|[官方文档](${items[0].docsUrl})`
|
|
610
|
+
: "";
|
|
611
|
+
const body = items
|
|
612
|
+
.map(
|
|
613
|
+
(hit) =>
|
|
614
|
+
`- \`${hit.path}:${hit.line}\`\n\n\`\`\`\n${hit.text}\n\`\`\``,
|
|
615
|
+
)
|
|
616
|
+
.join("\n");
|
|
617
|
+
return `## ${componentLabel(name)}${link}\n\n${body}`;
|
|
618
|
+
});
|
|
619
|
+
return textResult(
|
|
620
|
+
`# 检索「${query}」\n\n` +
|
|
621
|
+
`${metaHeader(knowledge, [
|
|
622
|
+
`> 命中 ${hits.length} 处(扫描 ${scanned} 个文件,关键词:${terms.join(" + ")})`,
|
|
623
|
+
])}\n\n---\n\n${blocks.join("\n\n")}`,
|
|
624
|
+
);
|
|
625
|
+
} catch (error) {
|
|
626
|
+
return errorResult(`检索失败:${error.message}`);
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
// ============================ 7. 库级指南 ============================
|
|
632
|
+
server.registerTool(
|
|
633
|
+
"get_reui_guide",
|
|
634
|
+
{
|
|
635
|
+
title: "获取 ReUI 使用指南",
|
|
636
|
+
description:
|
|
637
|
+
"获取库级指南:安装与引入(含按需子路径、样式)、主题定制、国际化、Nuxt 接入、构建发布与项目工程约定。" +
|
|
638
|
+
"在动手前用它对齐「正确引入方式 / 主题变量 / i18n 铁律 / Nuxt 配置」,避免踩坑。",
|
|
639
|
+
inputSchema: {
|
|
640
|
+
topic: z
|
|
641
|
+
.enum(GUIDE_TOPICS)
|
|
642
|
+
.optional()
|
|
643
|
+
.describe(
|
|
644
|
+
"主题:overview(总览)| install(安装引入)| theme(主题)| i18n(国际化)| nuxt(Nuxt 接入)| build(构建发布)| conventions(工程约定)",
|
|
645
|
+
),
|
|
646
|
+
version: z.string().optional().describe("指定 @reglow/reui 版本"),
|
|
647
|
+
},
|
|
648
|
+
annotations: {
|
|
649
|
+
readOnlyHint: true,
|
|
650
|
+
idempotentHint: true,
|
|
651
|
+
openWorldHint: true,
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
async ({ topic, version }) => {
|
|
655
|
+
try {
|
|
656
|
+
const knowledge = await withKnowledge(version);
|
|
657
|
+
const guide = knowledge.getGuide(topic ?? "overview");
|
|
658
|
+
if (!guide || guide.sections.length === 0) {
|
|
659
|
+
const doc = await knowledge.getDocument("README");
|
|
660
|
+
return textResult(
|
|
661
|
+
`${metaHeader(knowledge)}\n\n` +
|
|
662
|
+
`未取到本地指南章节(topic=${topic ?? "overview"})。\n` +
|
|
663
|
+
`可用方式:指定 \`--root <reui 仓库路径>\` 启动以读取 \`guide.md\`;或先调用 \`get_reui_document\`。\n` +
|
|
664
|
+
(doc?.markdown ? `\n---\n\n${doc.markdown}` : ""),
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
const blocks = guide.sections.map(
|
|
668
|
+
(section) => `<!-- 来源:${section.file} -->\n\n${section.text}`,
|
|
669
|
+
);
|
|
670
|
+
return textResult(
|
|
671
|
+
`# ReUI 指南(${topic ?? "overview"})\n\n` +
|
|
672
|
+
metaHeader(knowledge, [
|
|
673
|
+
`> 章节数:${guide.sections.length}|官方文档:${guide.docsBase}`,
|
|
674
|
+
]) +
|
|
675
|
+
"\n\n---\n\n" +
|
|
676
|
+
blocks.join("\n\n---\n\n"),
|
|
677
|
+
);
|
|
678
|
+
} catch (error) {
|
|
679
|
+
return errorResult(`获取指南失败:${error.message}`);
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
return server;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* 默认知识解析器:按 version 缓存(同一进程内复用)
|
|
689
|
+
*
|
|
690
|
+
* @param {{ root?: string, logger?: (message: string) => void }} options
|
|
691
|
+
*/
|
|
692
|
+
export function createKnowledgeResolver(options = {}) {
|
|
693
|
+
const cache = new Map();
|
|
694
|
+
return async function resolveKnowledge(version) {
|
|
695
|
+
const key = version ? `version:${version}` : `local:${options.root ?? ""}`;
|
|
696
|
+
if (!cache.has(key)) {
|
|
697
|
+
cache.set(
|
|
698
|
+
key,
|
|
699
|
+
createKnowledge({
|
|
700
|
+
root: options.root,
|
|
701
|
+
version,
|
|
702
|
+
logger: options.logger ?? (() => { }),
|
|
703
|
+
}),
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
return cache.get(key);
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
export { createKnowledge };
|