@maz-ui/mcp 4.8.0 → 4.9.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 +3 -7
- package/dist/mcp.d.mts +36 -10
- package/dist/mcp.d.ts +36 -10
- package/dist/mcp.mjs +768 -483
- package/package.json +1 -1
package/dist/mcp.mjs
CHANGED
|
@@ -7,7 +7,237 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
|
7
7
|
import { resolve, join, dirname } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
|
|
10
|
-
const version = "4.
|
|
10
|
+
const version = "4.8.0";
|
|
11
|
+
|
|
12
|
+
class MetadataExtractor {
|
|
13
|
+
extract(name, type, content, manualTags = []) {
|
|
14
|
+
const frontmatter = this.extractFrontmatter(content);
|
|
15
|
+
const sections = this.extractSections(content);
|
|
16
|
+
const props = this.extractProps(content);
|
|
17
|
+
const slots = this.extractSlots(content);
|
|
18
|
+
const events = this.extractEvents(content);
|
|
19
|
+
const displayName = frontmatter.title ?? this.nameToDisplayName(name);
|
|
20
|
+
const description = frontmatter.description ?? this.extractFirstParagraph(content);
|
|
21
|
+
const extractedTags = this.buildTags(name, displayName, props, slots, events);
|
|
22
|
+
const tags = this.mergeTags(manualTags, extractedTags);
|
|
23
|
+
return {
|
|
24
|
+
name,
|
|
25
|
+
displayName,
|
|
26
|
+
type,
|
|
27
|
+
description,
|
|
28
|
+
tags,
|
|
29
|
+
...props.length > 0 && { props },
|
|
30
|
+
...slots.length > 0 && { slots },
|
|
31
|
+
...events.length > 0 && { events },
|
|
32
|
+
sections
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
extractFrontmatter(content) {
|
|
36
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
37
|
+
if (!match) {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
const result = {};
|
|
41
|
+
const yaml = match[1];
|
|
42
|
+
for (const line of yaml.split("\n")) {
|
|
43
|
+
const trimmed = line.trim();
|
|
44
|
+
if (trimmed.startsWith("title:")) {
|
|
45
|
+
result.title = trimmed.slice("title:".length).trim();
|
|
46
|
+
} else if (trimmed.startsWith("description:")) {
|
|
47
|
+
result.description = trimmed.slice("description:".length).trim();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
extractSections(content) {
|
|
53
|
+
const sections = [];
|
|
54
|
+
const contentWithoutFrontmatter = this.stripFrontmatter(content);
|
|
55
|
+
const lines = contentWithoutFrontmatter.split("\n");
|
|
56
|
+
let currentSection;
|
|
57
|
+
const flushSection = () => {
|
|
58
|
+
if (currentSection) {
|
|
59
|
+
const sectionContent = currentSection.contentLines.join("\n").trim();
|
|
60
|
+
sections.push({
|
|
61
|
+
title: currentSection.title,
|
|
62
|
+
level: currentSection.level,
|
|
63
|
+
content: this.extractSectionLeadText(sectionContent)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
const headingMatch = line.match(/^(#{2,3}) +(\S.*)$/);
|
|
69
|
+
if (headingMatch) {
|
|
70
|
+
flushSection();
|
|
71
|
+
currentSection = {
|
|
72
|
+
level: headingMatch[1].length,
|
|
73
|
+
title: this.cleanHeadingText(headingMatch[2]),
|
|
74
|
+
contentLines: []
|
|
75
|
+
};
|
|
76
|
+
} else if (currentSection) {
|
|
77
|
+
currentSection.contentLines.push(line);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
flushSection();
|
|
81
|
+
return sections;
|
|
82
|
+
}
|
|
83
|
+
extractProps(content) {
|
|
84
|
+
return this.extractTableColumn(content, "Props", "Name");
|
|
85
|
+
}
|
|
86
|
+
extractSlots(content) {
|
|
87
|
+
return this.extractTableColumn(content, "Slots", "Name");
|
|
88
|
+
}
|
|
89
|
+
extractEvents(content) {
|
|
90
|
+
return this.extractTableColumn(content, "Events", "Event name");
|
|
91
|
+
}
|
|
92
|
+
mergeTags(manualTags, extractedTags) {
|
|
93
|
+
const tagSet = /* @__PURE__ */ new Set();
|
|
94
|
+
for (const tag of manualTags) {
|
|
95
|
+
tagSet.add(tag.toLowerCase());
|
|
96
|
+
}
|
|
97
|
+
for (const tag of extractedTags) {
|
|
98
|
+
tagSet.add(tag.toLowerCase());
|
|
99
|
+
}
|
|
100
|
+
return Array.from(tagSet);
|
|
101
|
+
}
|
|
102
|
+
extractTableColumn(content, sectionName, columnName) {
|
|
103
|
+
const sectionRegex = new RegExp(`## ${sectionName} *\\n`, "i");
|
|
104
|
+
const sectionMatch = content.match(sectionRegex);
|
|
105
|
+
if (!sectionMatch || sectionMatch.index === void 0) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
const afterSection = content.slice(sectionMatch.index + sectionMatch[0].length);
|
|
109
|
+
const lines = afterSection.split("\n");
|
|
110
|
+
const headerLine = lines.find((l) => l.includes("|") && l.toLowerCase().includes(columnName.toLowerCase()));
|
|
111
|
+
if (!headerLine) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
const headerIndex = lines.indexOf(headerLine);
|
|
115
|
+
const columns = headerLine.split("|").map((c) => c.trim()).filter(Boolean);
|
|
116
|
+
const nameColumnIndex = columns.findIndex((c) => c.toLowerCase().includes(columnName.toLowerCase()));
|
|
117
|
+
if (nameColumnIndex === -1) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const results = [];
|
|
121
|
+
for (let i = headerIndex + 2; i < lines.length; i++) {
|
|
122
|
+
const line = lines[i];
|
|
123
|
+
if (!line.includes("|") || line.match(/^#{1,6}\s/)) {
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
if (line.match(/^ *\|[-\s|]+\| *$/)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
|
|
130
|
+
if (cells[nameColumnIndex]) {
|
|
131
|
+
const name = cells[nameColumnIndex].replace(/\*\*/g, "").trim();
|
|
132
|
+
if (name) {
|
|
133
|
+
results.push(name);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return results;
|
|
138
|
+
}
|
|
139
|
+
stripFrontmatter(content) {
|
|
140
|
+
return content.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
141
|
+
}
|
|
142
|
+
cleanHeadingText(text) {
|
|
143
|
+
let result = this.removeTemplateTags(text);
|
|
144
|
+
result = this.resolveMarkdownLinks(result);
|
|
145
|
+
return result.trim();
|
|
146
|
+
}
|
|
147
|
+
removeTemplateTags(text) {
|
|
148
|
+
let result = "";
|
|
149
|
+
let i = 0;
|
|
150
|
+
while (i < text.length) {
|
|
151
|
+
if (text[i] === "{" && text[i + 1] === "{") {
|
|
152
|
+
const end = text.indexOf("}}", i + 2);
|
|
153
|
+
i = end === -1 ? text.length : end + 2;
|
|
154
|
+
} else {
|
|
155
|
+
result += text[i];
|
|
156
|
+
i++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
resolveMarkdownLinks(text) {
|
|
162
|
+
let result = "";
|
|
163
|
+
let i = 0;
|
|
164
|
+
while (i < text.length) {
|
|
165
|
+
if (text[i] === "[") {
|
|
166
|
+
const closeBracket = text.indexOf("]", i + 1);
|
|
167
|
+
if (closeBracket !== -1 && text[closeBracket + 1] === "(") {
|
|
168
|
+
const closeParen = text.indexOf(")", closeBracket + 2);
|
|
169
|
+
if (closeParen !== -1) {
|
|
170
|
+
result += text.slice(i + 1, closeBracket);
|
|
171
|
+
i = closeParen + 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
result += text[i];
|
|
177
|
+
i++;
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
extractSectionLeadText(content) {
|
|
182
|
+
const lines = content.split("\n");
|
|
183
|
+
const paragraphLines = [];
|
|
184
|
+
for (const line of lines) {
|
|
185
|
+
const trimmed = line.trim();
|
|
186
|
+
if (!trimmed) {
|
|
187
|
+
if (paragraphLines.length > 0) {
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("<") || trimmed.startsWith("```") || trimmed.startsWith(":::") || trimmed.startsWith("|")) {
|
|
193
|
+
if (paragraphLines.length > 0) {
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
paragraphLines.push(trimmed);
|
|
199
|
+
}
|
|
200
|
+
return paragraphLines.join(" ");
|
|
201
|
+
}
|
|
202
|
+
extractFirstParagraph(content) {
|
|
203
|
+
const stripped = this.stripFrontmatter(content);
|
|
204
|
+
const lines = stripped.split("\n");
|
|
205
|
+
const paragraphLines = [];
|
|
206
|
+
for (const line of lines) {
|
|
207
|
+
const trimmed = line.trim();
|
|
208
|
+
if (!trimmed) {
|
|
209
|
+
if (paragraphLines.length > 0) {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("<") || trimmed.startsWith("```") || trimmed.startsWith(":::") || trimmed.startsWith("|") || trimmed.startsWith("{{")) {
|
|
215
|
+
if (paragraphLines.length > 0) {
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
paragraphLines.push(trimmed);
|
|
221
|
+
}
|
|
222
|
+
return paragraphLines.join(" ");
|
|
223
|
+
}
|
|
224
|
+
nameToDisplayName(name) {
|
|
225
|
+
return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
226
|
+
}
|
|
227
|
+
buildTags(name, displayName, props, slots, events) {
|
|
228
|
+
const tags = [name, displayName.toLowerCase()];
|
|
229
|
+
for (const prop of props.slice(0, 10)) {
|
|
230
|
+
tags.push(`prop:${prop}`);
|
|
231
|
+
}
|
|
232
|
+
for (const slot of slots.slice(0, 10)) {
|
|
233
|
+
tags.push(`slot:${slot}`);
|
|
234
|
+
}
|
|
235
|
+
for (const event of events.slice(0, 10)) {
|
|
236
|
+
tags.push(`event:${event}`);
|
|
237
|
+
}
|
|
238
|
+
return tags;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
11
241
|
|
|
12
242
|
const _dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
243
|
class DocumentationService {
|
|
@@ -19,6 +249,7 @@ class DocumentationService {
|
|
|
19
249
|
directivesDir;
|
|
20
250
|
pluginsDir;
|
|
21
251
|
helpersDir;
|
|
252
|
+
metadataExtractor = new MetadataExtractor();
|
|
22
253
|
constructor() {
|
|
23
254
|
const localDocsRoot = resolve(_dirname, "../docs/src");
|
|
24
255
|
const localGeneratedDocsDir = resolve(_dirname, "../docs/generated-docs");
|
|
@@ -130,12 +361,45 @@ ${generatedDoc}`;
|
|
|
130
361
|
getAllHelpers() {
|
|
131
362
|
return this.listMarkdownFiles(this.helpersDir);
|
|
132
363
|
}
|
|
364
|
+
// ========== UNIFIED LOADING ==========
|
|
365
|
+
getAllDocuments() {
|
|
366
|
+
const documents = [];
|
|
367
|
+
for (const name of this.getAllComponents()) {
|
|
368
|
+
const content = this.getComponentDocumentation(name);
|
|
369
|
+
if (content) {
|
|
370
|
+
documents.push(this.buildDocument(name, "component", content));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const categories = [
|
|
374
|
+
{ type: "guide", names: this.getAllGuides(), getContent: (name) => this.getGuideDocumentation(name) },
|
|
375
|
+
{ type: "composable", names: this.getAllComposables(), getContent: (name) => this.getComposableDocumentation(name) },
|
|
376
|
+
{ type: "directive", names: this.getAllDirectives(), getContent: (name) => this.getDirectiveDocumentation(name) },
|
|
377
|
+
{ type: "plugin", names: this.getAllPlugins(), getContent: (name) => this.getPluginDocumentation(name) },
|
|
378
|
+
{ type: "helper", names: this.getAllHelpers(), getContent: (name) => this.getHelperDocumentation(name) }
|
|
379
|
+
];
|
|
380
|
+
for (const category of categories) {
|
|
381
|
+
for (const name of category.names) {
|
|
382
|
+
const content = category.getContent(name);
|
|
383
|
+
if (content) {
|
|
384
|
+
documents.push(this.buildDocument(name, category.type, content));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return documents;
|
|
389
|
+
}
|
|
390
|
+
buildDocument(name, type, content) {
|
|
391
|
+
return {
|
|
392
|
+
name,
|
|
393
|
+
type,
|
|
394
|
+
content,
|
|
395
|
+
metadata: this.metadataExtractor.extract(name, type, content)
|
|
396
|
+
};
|
|
397
|
+
}
|
|
133
398
|
// ========== UTILITAIRES ==========
|
|
134
399
|
getOverview() {
|
|
135
400
|
const overviewPath = join(this.docsRoot, "index.md");
|
|
136
401
|
return this.readMarkdownFile(overviewPath);
|
|
137
402
|
}
|
|
138
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
139
403
|
searchDocumentation(query) {
|
|
140
404
|
const searchTerm = query.toLowerCase();
|
|
141
405
|
const results = [];
|
|
@@ -240,9 +504,293 @@ ${generatedDoc}`;
|
|
|
240
504
|
}
|
|
241
505
|
}
|
|
242
506
|
|
|
507
|
+
const SNIPPET_LENGTH = 150;
|
|
508
|
+
class SearchEngine {
|
|
509
|
+
documents = [];
|
|
510
|
+
invertedIndex = /* @__PURE__ */ new Map();
|
|
511
|
+
documentLengths = [];
|
|
512
|
+
buildIndex(documents) {
|
|
513
|
+
this.documents = documents;
|
|
514
|
+
this.invertedIndex.clear();
|
|
515
|
+
this.documentLengths = [];
|
|
516
|
+
for (let docIndex = 0; docIndex < documents.length; docIndex++) {
|
|
517
|
+
const tokens = this.tokenize(documents[docIndex].content);
|
|
518
|
+
this.documentLengths.push(tokens.length);
|
|
519
|
+
const termFrequencies = /* @__PURE__ */ new Map();
|
|
520
|
+
for (let pos = 0; pos < tokens.length; pos++) {
|
|
521
|
+
const token = tokens[pos];
|
|
522
|
+
const entry = termFrequencies.get(token);
|
|
523
|
+
if (entry) {
|
|
524
|
+
entry.count++;
|
|
525
|
+
entry.positions.push(pos);
|
|
526
|
+
} else {
|
|
527
|
+
termFrequencies.set(token, { count: 1, positions: [pos] });
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
for (const [term, { count, positions }] of termFrequencies) {
|
|
531
|
+
let postings = this.invertedIndex.get(term);
|
|
532
|
+
if (!postings) {
|
|
533
|
+
postings = [];
|
|
534
|
+
this.invertedIndex.set(term, postings);
|
|
535
|
+
}
|
|
536
|
+
postings.push({ docIndex, frequency: count, positions });
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
search(query, maxResults = 20) {
|
|
541
|
+
if (this.documents.length === 0 || !query.trim()) {
|
|
542
|
+
return [];
|
|
543
|
+
}
|
|
544
|
+
const exactPhrases = this.extractExactPhrases(query);
|
|
545
|
+
const remainingQuery = query.replace(/"[^"]*"/g, "").trim();
|
|
546
|
+
const singleTerms = remainingQuery ? this.tokenize(remainingQuery) : [];
|
|
547
|
+
const scores = /* @__PURE__ */ new Map();
|
|
548
|
+
const matchPositions = /* @__PURE__ */ new Map();
|
|
549
|
+
for (const term of singleTerms) {
|
|
550
|
+
this.scoreTerm(term, scores, matchPositions);
|
|
551
|
+
}
|
|
552
|
+
for (const phrase of exactPhrases) {
|
|
553
|
+
this.scorePhrase(phrase, scores, matchPositions);
|
|
554
|
+
}
|
|
555
|
+
return Array.from(scores.entries()).filter(([, score]) => score > 0).sort(([, a], [, b]) => b - a).slice(0, maxResults).map(([docIndex, score]) => {
|
|
556
|
+
const doc = this.documents[docIndex];
|
|
557
|
+
const positions = matchPositions.get(docIndex) ?? [];
|
|
558
|
+
return {
|
|
559
|
+
id: doc.id,
|
|
560
|
+
type: doc.type,
|
|
561
|
+
name: doc.name,
|
|
562
|
+
score: Math.round(score * 1e4) / 1e4,
|
|
563
|
+
snippet: this.extractSnippet(doc.content, positions)
|
|
564
|
+
};
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
getIndexedTermCount() {
|
|
568
|
+
return this.invertedIndex.size;
|
|
569
|
+
}
|
|
570
|
+
getDocumentCount() {
|
|
571
|
+
return this.documents.length;
|
|
572
|
+
}
|
|
573
|
+
scoreTerm(term, scores, matchPositions) {
|
|
574
|
+
const postings = this.invertedIndex.get(term);
|
|
575
|
+
if (!postings) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const totalDocs = this.documents.length;
|
|
579
|
+
const df = postings.length;
|
|
580
|
+
const idf = Math.log(1 + totalDocs / df);
|
|
581
|
+
for (const posting of postings) {
|
|
582
|
+
const docLength = this.documentLengths[posting.docIndex];
|
|
583
|
+
const tf = docLength > 0 ? posting.frequency / docLength : 0;
|
|
584
|
+
const tfidf = tf * idf;
|
|
585
|
+
scores.set(posting.docIndex, (scores.get(posting.docIndex) ?? 0) + tfidf);
|
|
586
|
+
const existing = matchPositions.get(posting.docIndex) ?? [];
|
|
587
|
+
existing.push(posting.positions[0]);
|
|
588
|
+
matchPositions.set(posting.docIndex, existing);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
scorePhrase(phrase, scores, matchPositions) {
|
|
592
|
+
const phraseTokens = this.tokenize(phrase);
|
|
593
|
+
if (phraseTokens.length === 0) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
const phraseMatches = this.findPhraseMatches(phraseTokens);
|
|
597
|
+
const matchedDocs = new Set(phraseMatches.map((m) => m.docIndex));
|
|
598
|
+
if (matchedDocs.size === 0) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const totalDocs = this.documents.length;
|
|
602
|
+
const idf = Math.log(1 + totalDocs / matchedDocs.size);
|
|
603
|
+
for (const docIndex of matchedDocs) {
|
|
604
|
+
const docMatches = phraseMatches.filter((m) => m.docIndex === docIndex);
|
|
605
|
+
const docLength = this.documentLengths[docIndex];
|
|
606
|
+
const tf = docLength > 0 ? docMatches.length / docLength : 0;
|
|
607
|
+
const tfidf = tf * idf * phraseTokens.length;
|
|
608
|
+
scores.set(docIndex, (scores.get(docIndex) ?? 0) + tfidf);
|
|
609
|
+
const existing = matchPositions.get(docIndex) ?? [];
|
|
610
|
+
existing.push(docMatches[0].position);
|
|
611
|
+
matchPositions.set(docIndex, existing);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
findPhraseMatches(phraseTokens) {
|
|
615
|
+
const firstTermPostings = this.invertedIndex.get(phraseTokens[0]);
|
|
616
|
+
if (!firstTermPostings) {
|
|
617
|
+
return [];
|
|
618
|
+
}
|
|
619
|
+
const matches = [];
|
|
620
|
+
for (const posting of firstTermPostings) {
|
|
621
|
+
for (const startPos of posting.positions) {
|
|
622
|
+
if (this.isPhraseAtPosition(phraseTokens, posting.docIndex, startPos)) {
|
|
623
|
+
matches.push({ docIndex: posting.docIndex, position: startPos });
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return matches;
|
|
628
|
+
}
|
|
629
|
+
isPhraseAtPosition(phraseTokens, docIndex, startPos) {
|
|
630
|
+
for (let i = 1; i < phraseTokens.length; i++) {
|
|
631
|
+
const nextPostings = this.invertedIndex.get(phraseTokens[i]);
|
|
632
|
+
if (!nextPostings) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
const nextPosting = nextPostings.find((p) => p.docIndex === docIndex);
|
|
636
|
+
if (!nextPosting || !nextPosting.positions.includes(startPos + i)) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
extractExactPhrases(query) {
|
|
643
|
+
const phrases = [];
|
|
644
|
+
const regex = /"([^"]+)"/g;
|
|
645
|
+
for (const match of query.matchAll(regex)) {
|
|
646
|
+
if (match[1].trim()) {
|
|
647
|
+
phrases.push(match[1].trim());
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return phrases;
|
|
651
|
+
}
|
|
652
|
+
tokenize(text) {
|
|
653
|
+
return text.toLowerCase().replace(/[#*`|[\](){}!@$%^&+=~<>/\\,;:.?'"_-]+/g, " ").split(/\s+/).filter((token) => token.length > 1);
|
|
654
|
+
}
|
|
655
|
+
extractSnippet(content, tokenPositions) {
|
|
656
|
+
if (!content || tokenPositions.length === 0) {
|
|
657
|
+
return content.slice(0, SNIPPET_LENGTH).trim();
|
|
658
|
+
}
|
|
659
|
+
const tokens = this.tokenize(content);
|
|
660
|
+
if (tokens.length === 0) {
|
|
661
|
+
return content.slice(0, SNIPPET_LENGTH).trim();
|
|
662
|
+
}
|
|
663
|
+
const bestPosition = tokenPositions[0];
|
|
664
|
+
const targetToken = tokens[Math.min(bestPosition, tokens.length - 1)];
|
|
665
|
+
const contentLower = content.toLowerCase();
|
|
666
|
+
const charIndex = contentLower.indexOf(targetToken);
|
|
667
|
+
if (charIndex === -1) {
|
|
668
|
+
return content.slice(0, SNIPPET_LENGTH).trim();
|
|
669
|
+
}
|
|
670
|
+
const halfSnippet = Math.floor(SNIPPET_LENGTH / 2);
|
|
671
|
+
let start = Math.max(0, charIndex - halfSnippet);
|
|
672
|
+
let end = Math.min(content.length, start + SNIPPET_LENGTH);
|
|
673
|
+
if (end === content.length) {
|
|
674
|
+
start = Math.max(0, end - SNIPPET_LENGTH);
|
|
675
|
+
}
|
|
676
|
+
if (start > 0) {
|
|
677
|
+
const spaceIndex = content.indexOf(" ", start);
|
|
678
|
+
if (spaceIndex !== -1 && spaceIndex < start + 20) {
|
|
679
|
+
start = spaceIndex + 1;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (end < content.length) {
|
|
683
|
+
const spaceIndex = content.lastIndexOf(" ", end);
|
|
684
|
+
if (spaceIndex !== -1 && spaceIndex > end - 20) {
|
|
685
|
+
end = spaceIndex;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
let snippet = content.slice(start, end).trim();
|
|
689
|
+
if (start > 0) {
|
|
690
|
+
snippet = `...${snippet}`;
|
|
691
|
+
}
|
|
692
|
+
if (end < content.length) {
|
|
693
|
+
snippet = `${snippet}...`;
|
|
694
|
+
}
|
|
695
|
+
return snippet;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const NAME_BOOST = 3;
|
|
700
|
+
const TAG_BOOST = 2;
|
|
701
|
+
const CONTENT_BOOST = 1;
|
|
702
|
+
const DEFAULT_MAX_RESULTS = 10;
|
|
703
|
+
class UnifiedSearchService {
|
|
704
|
+
searchEngine = new SearchEngine();
|
|
705
|
+
documents = [];
|
|
706
|
+
initialize(documents) {
|
|
707
|
+
this.documents = documents;
|
|
708
|
+
this.searchEngine.buildIndex(
|
|
709
|
+
documents.map((doc) => ({
|
|
710
|
+
id: doc.name,
|
|
711
|
+
type: doc.type,
|
|
712
|
+
name: doc.name,
|
|
713
|
+
content: doc.content
|
|
714
|
+
}))
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
search(query, options = {}) {
|
|
718
|
+
const { category, maxResults = DEFAULT_MAX_RESULTS } = options;
|
|
719
|
+
if (!query.trim()) {
|
|
720
|
+
return [];
|
|
721
|
+
}
|
|
722
|
+
const normalizedQuery = query.toLowerCase().trim();
|
|
723
|
+
const queryTokens = this.searchEngine.tokenize(query);
|
|
724
|
+
let candidates = this.documents;
|
|
725
|
+
if (category) {
|
|
726
|
+
candidates = candidates.filter((doc) => doc.type === category);
|
|
727
|
+
}
|
|
728
|
+
const tfidfResults = this.searchEngine.search(query, candidates.length || this.documents.length);
|
|
729
|
+
const tfidfScoreMap = /* @__PURE__ */ new Map();
|
|
730
|
+
for (const result of tfidfResults) {
|
|
731
|
+
tfidfScoreMap.set(result.id, { score: result.score, snippet: result.snippet });
|
|
732
|
+
}
|
|
733
|
+
const scored = [];
|
|
734
|
+
for (const doc of candidates) {
|
|
735
|
+
const nameScore = this.scoreNameMatch(normalizedQuery, doc) * NAME_BOOST;
|
|
736
|
+
const tagScore = this.scoreTagMatch(normalizedQuery, queryTokens, doc) * TAG_BOOST;
|
|
737
|
+
const tfidfEntry = tfidfScoreMap.get(doc.name);
|
|
738
|
+
const contentScore = (tfidfEntry?.score ?? 0) * CONTENT_BOOST;
|
|
739
|
+
const totalScore = nameScore + tagScore + contentScore;
|
|
740
|
+
if (totalScore > 0) {
|
|
741
|
+
scored.push({
|
|
742
|
+
name: doc.name,
|
|
743
|
+
displayName: doc.metadata.displayName,
|
|
744
|
+
type: doc.type,
|
|
745
|
+
description: doc.metadata.description,
|
|
746
|
+
score: Math.round(totalScore * 1e4) / 1e4,
|
|
747
|
+
snippet: tfidfEntry?.snippet ?? doc.content.slice(0, 150).trim()
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return scored.sort((a, b) => b.score - a.score).slice(0, maxResults);
|
|
752
|
+
}
|
|
753
|
+
scoreNameMatch(normalizedQuery, doc) {
|
|
754
|
+
const name = doc.name.toLowerCase();
|
|
755
|
+
const displayName = doc.metadata.displayName.toLowerCase();
|
|
756
|
+
if (name === normalizedQuery || displayName === normalizedQuery) {
|
|
757
|
+
return 1;
|
|
758
|
+
}
|
|
759
|
+
if (name.includes(normalizedQuery) || displayName.includes(normalizedQuery)) {
|
|
760
|
+
return 0.7;
|
|
761
|
+
}
|
|
762
|
+
if (normalizedQuery.includes(name)) {
|
|
763
|
+
return 0.5;
|
|
764
|
+
}
|
|
765
|
+
return 0;
|
|
766
|
+
}
|
|
767
|
+
scoreTagMatch(normalizedQuery, queryTokens, doc) {
|
|
768
|
+
const tags = doc.metadata.tags;
|
|
769
|
+
for (const tag of tags) {
|
|
770
|
+
if (tag === normalizedQuery) {
|
|
771
|
+
return 1;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (queryTokens.length === 0) {
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
let matchedTokens = 0;
|
|
778
|
+
for (const token of queryTokens) {
|
|
779
|
+
for (const tag of tags) {
|
|
780
|
+
if (tag.includes(token) || token.includes(tag)) {
|
|
781
|
+
matchedTokens++;
|
|
782
|
+
break;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return matchedTokens / queryTokens.length * 0.8;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
243
790
|
class MazUiMcpServer {
|
|
244
791
|
server;
|
|
245
792
|
documentationService = new DocumentationService();
|
|
793
|
+
unifiedSearchService = new UnifiedSearchService();
|
|
246
794
|
documentationIndex = [];
|
|
247
795
|
constructor() {
|
|
248
796
|
this.server = new Server(
|
|
@@ -399,6 +947,7 @@ class MazUiMcpServer {
|
|
|
399
947
|
});
|
|
400
948
|
}
|
|
401
949
|
this.documentationIndex = index;
|
|
950
|
+
this.unifiedSearchService.initialize(this.documentationService.getAllDocuments());
|
|
402
951
|
}
|
|
403
952
|
/**
|
|
404
953
|
* Generate additional search tags based on common patterns
|
|
@@ -486,59 +1035,205 @@ class MazUiMcpServer {
|
|
|
486
1035
|
return "general";
|
|
487
1036
|
}
|
|
488
1037
|
/**
|
|
489
|
-
*
|
|
490
|
-
|
|
491
|
-
fuzzySearch(query, targets) {
|
|
492
|
-
const normalizedQuery = query.toLowerCase().trim();
|
|
493
|
-
return targets.map((target) => ({
|
|
494
|
-
target,
|
|
495
|
-
score: this.calculateFuzzyScore(normalizedQuery, target.toLowerCase())
|
|
496
|
-
})).filter((item) => item.score > 0.3).sort((a, b) => b.score - a.score).map((item) => item.target);
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Calculate fuzzy match score
|
|
1038
|
+
* Resolve a document name using intelligent matching:
|
|
1039
|
+
* PascalCase (MazBtn), kebab-case (maz-btn), short name (btn), aliases
|
|
500
1040
|
*/
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
1041
|
+
resolveDocumentName(name, type) {
|
|
1042
|
+
const normalizedName = name.toLowerCase().trim();
|
|
1043
|
+
const candidates = type && type !== "auto" ? this.documentationIndex.filter((item) => item.type === type) : this.documentationIndex;
|
|
1044
|
+
const exactMatch = candidates.find((item) => item.name.toLowerCase() === normalizedName);
|
|
1045
|
+
if (exactMatch) {
|
|
1046
|
+
return exactMatch;
|
|
1047
|
+
}
|
|
1048
|
+
if (/[A-Z]/.test(name)) {
|
|
1049
|
+
const kebabName = name.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
|
|
1050
|
+
const kebabMatch = candidates.find((item) => item.name.toLowerCase() === kebabName);
|
|
1051
|
+
if (kebabMatch) {
|
|
1052
|
+
return kebabMatch;
|
|
512
1053
|
}
|
|
513
1054
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
searchInIndex(query) {
|
|
520
|
-
const normalizedQuery = query.toLowerCase().trim();
|
|
521
|
-
const results = [];
|
|
522
|
-
for (const item of this.documentationIndex) {
|
|
523
|
-
let maxScore = 0;
|
|
524
|
-
if (item.name.toLowerCase() === normalizedQuery) {
|
|
525
|
-
maxScore = 1;
|
|
526
|
-
} else if (item.displayName.toLowerCase().includes(normalizedQuery)) {
|
|
527
|
-
maxScore = Math.max(maxScore, 0.9);
|
|
528
|
-
} else {
|
|
529
|
-
const tagMatches = this.fuzzySearch(normalizedQuery, item.tags);
|
|
530
|
-
if (tagMatches.length > 0) {
|
|
531
|
-
maxScore = Math.max(maxScore, 0.8);
|
|
532
|
-
}
|
|
533
|
-
const nameScore = this.calculateFuzzyScore(normalizedQuery, item.name.toLowerCase());
|
|
534
|
-
const displayScore = this.calculateFuzzyScore(normalizedQuery, item.displayName.toLowerCase());
|
|
535
|
-
maxScore = Math.max(maxScore, nameScore * 0.7, displayScore * 0.6);
|
|
1055
|
+
if (!normalizedName.startsWith("maz-")) {
|
|
1056
|
+
const prefixedName = `maz-${normalizedName}`;
|
|
1057
|
+
const prefixMatch = candidates.find((item) => item.name.toLowerCase() === prefixedName);
|
|
1058
|
+
if (prefixMatch) {
|
|
1059
|
+
return prefixMatch;
|
|
536
1060
|
}
|
|
537
|
-
|
|
538
|
-
|
|
1061
|
+
}
|
|
1062
|
+
const displayMatch = candidates.find(
|
|
1063
|
+
(item) => item.displayName.toLowerCase() === normalizedName
|
|
1064
|
+
);
|
|
1065
|
+
if (displayMatch) {
|
|
1066
|
+
return displayMatch;
|
|
1067
|
+
}
|
|
1068
|
+
const tagMatch = candidates.find(
|
|
1069
|
+
(item) => item.tags.some((tag) => tag.toLowerCase() === normalizedName)
|
|
1070
|
+
);
|
|
1071
|
+
if (tagMatch) {
|
|
1072
|
+
return tagMatch;
|
|
1073
|
+
}
|
|
1074
|
+
const partialMatch = candidates.find(
|
|
1075
|
+
(item) => item.name.toLowerCase().includes(normalizedName) || item.displayName.toLowerCase().includes(normalizedName)
|
|
1076
|
+
);
|
|
1077
|
+
if (partialMatch) {
|
|
1078
|
+
return partialMatch;
|
|
1079
|
+
}
|
|
1080
|
+
return void 0;
|
|
1081
|
+
}
|
|
1082
|
+
handleSearch(args) {
|
|
1083
|
+
const query = args?.query;
|
|
1084
|
+
const category = args?.category;
|
|
1085
|
+
const maxResults = args?.maxResults || 10;
|
|
1086
|
+
if (!query) {
|
|
1087
|
+
throw new Error("Search query is required. Provide a component name, description, prop name, or use case.");
|
|
1088
|
+
}
|
|
1089
|
+
const results = this.unifiedSearchService.search(query, {
|
|
1090
|
+
category,
|
|
1091
|
+
maxResults
|
|
1092
|
+
});
|
|
1093
|
+
if (results.length === 0) {
|
|
1094
|
+
return {
|
|
1095
|
+
content: [{
|
|
1096
|
+
type: "text",
|
|
1097
|
+
text: `No results found for "${query}".${category ? ` (filtered by category: ${category})` : ""}
|
|
1098
|
+
|
|
1099
|
+
**Suggestions:**
|
|
1100
|
+
- Try broader or different terms (e.g. "button" instead of "btn")
|
|
1101
|
+
- Remove the category filter to search all documentation
|
|
1102
|
+
- Use \`list\` to browse all available documentation`
|
|
1103
|
+
}]
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
let resultText = `# Search Results for "${query}" (${results.length} found)
|
|
1107
|
+
|
|
1108
|
+
`;
|
|
1109
|
+
for (const item of results) {
|
|
1110
|
+
resultText += `## ${item.displayName}
|
|
1111
|
+
`;
|
|
1112
|
+
resultText += `- **Name**: \`${item.name}\`
|
|
1113
|
+
`;
|
|
1114
|
+
resultText += `- **Type**: ${item.type}
|
|
1115
|
+
`;
|
|
1116
|
+
resultText += `- **Description**: ${item.description}
|
|
1117
|
+
`;
|
|
1118
|
+
resultText += `- **Score**: ${item.score}
|
|
1119
|
+
`;
|
|
1120
|
+
resultText += `- **Snippet**: ${item.snippet}
|
|
1121
|
+
`;
|
|
1122
|
+
resultText += `- **Get full doc**: Use \`get_doc\` with name \`${item.name}\`
|
|
1123
|
+
|
|
1124
|
+
`;
|
|
1125
|
+
}
|
|
1126
|
+
return {
|
|
1127
|
+
content: [{
|
|
1128
|
+
type: "text",
|
|
1129
|
+
text: resultText
|
|
1130
|
+
}]
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
handleList(args) {
|
|
1134
|
+
const category = args?.category || "all";
|
|
1135
|
+
const filteredIndex = category === "all" ? this.documentationIndex : this.documentationIndex.filter((item) => item.type === category);
|
|
1136
|
+
const groupedDocs = filteredIndex.reduce((acc, item) => {
|
|
1137
|
+
if (!acc[item.type])
|
|
1138
|
+
acc[item.type] = [];
|
|
1139
|
+
acc[item.type].push(item);
|
|
1140
|
+
return acc;
|
|
1141
|
+
}, {});
|
|
1142
|
+
const typeLabels = {
|
|
1143
|
+
component: "Components",
|
|
1144
|
+
guide: "Guides",
|
|
1145
|
+
composable: "Composables",
|
|
1146
|
+
directive: "Directives",
|
|
1147
|
+
plugin: "Plugins",
|
|
1148
|
+
helper: "Helpers"
|
|
1149
|
+
};
|
|
1150
|
+
const typeOrder = ["component", "guide", "composable", "directive", "plugin", "helper"];
|
|
1151
|
+
let result = `# Maz-UI Documentation (${filteredIndex.length} items)
|
|
1152
|
+
|
|
1153
|
+
`;
|
|
1154
|
+
for (const type of typeOrder) {
|
|
1155
|
+
if (!groupedDocs[type])
|
|
1156
|
+
continue;
|
|
1157
|
+
const items = groupedDocs[type].sort((a, b) => a.name.localeCompare(b.name));
|
|
1158
|
+
result += `## ${typeLabels[type]} (${items.length})
|
|
1159
|
+
|
|
1160
|
+
`;
|
|
1161
|
+
for (const item of items) {
|
|
1162
|
+
result += `- **${item.displayName}** (\`${item.name}\`) \u2014 ${item.description}
|
|
1163
|
+
`;
|
|
539
1164
|
}
|
|
1165
|
+
result += "\n";
|
|
540
1166
|
}
|
|
541
|
-
return
|
|
1167
|
+
return {
|
|
1168
|
+
content: [{
|
|
1169
|
+
type: "text",
|
|
1170
|
+
text: result.trim()
|
|
1171
|
+
}]
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
handleGetDoc(args) {
|
|
1175
|
+
const docName = args?.name;
|
|
1176
|
+
const docType = args?.type || "auto";
|
|
1177
|
+
if (!docName) {
|
|
1178
|
+
throw new Error("Name is required");
|
|
1179
|
+
}
|
|
1180
|
+
const found = this.resolveDocumentName(docName, docType);
|
|
1181
|
+
if (!found) {
|
|
1182
|
+
return this.handleDocNotFound(docName);
|
|
1183
|
+
}
|
|
1184
|
+
const content = this.getDocumentContent(found);
|
|
1185
|
+
if (!content) {
|
|
1186
|
+
return {
|
|
1187
|
+
content: [{
|
|
1188
|
+
type: "text",
|
|
1189
|
+
text: `Found "${found.displayName}" but documentation content is not available. This might be a documentation file that needs to be created.`
|
|
1190
|
+
}]
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
return {
|
|
1194
|
+
content: [{
|
|
1195
|
+
type: "text",
|
|
1196
|
+
text: content
|
|
1197
|
+
}]
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
handleDocNotFound(docName) {
|
|
1201
|
+
const searchResults = this.unifiedSearchService.search(docName, { maxResults: 3 });
|
|
1202
|
+
if (searchResults.length === 0) {
|
|
1203
|
+
return {
|
|
1204
|
+
content: [{
|
|
1205
|
+
type: "text",
|
|
1206
|
+
text: `Documentation not found for "${docName}".
|
|
1207
|
+
|
|
1208
|
+
**Available options:**
|
|
1209
|
+
- Use \`search\` to find similar items
|
|
1210
|
+
- Use \`list\` to see all available documentation`
|
|
1211
|
+
}]
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
const suggestions = searchResults.map((item) => `- **${item.displayName}** (\`${item.name}\`) - ${item.description}`).join("\n");
|
|
1215
|
+
return {
|
|
1216
|
+
content: [{
|
|
1217
|
+
type: "text",
|
|
1218
|
+
text: `Exact match not found for "${docName}". Did you mean:
|
|
1219
|
+
|
|
1220
|
+
${suggestions}
|
|
1221
|
+
|
|
1222
|
+
**Use \`get_doc\` with the exact name from above.**`
|
|
1223
|
+
}]
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
getDocumentContent(doc) {
|
|
1227
|
+
const contentMethods = {
|
|
1228
|
+
component: (name) => this.documentationService.getComponentDocumentation(name),
|
|
1229
|
+
guide: (name) => this.documentationService.getGuideDocumentation(name),
|
|
1230
|
+
composable: (name) => this.documentationService.getComposableDocumentation(name),
|
|
1231
|
+
directive: (name) => this.documentationService.getDirectiveDocumentation(name),
|
|
1232
|
+
plugin: (name) => this.documentationService.getPluginDocumentation(name),
|
|
1233
|
+
helper: (name) => this.documentationService.getHelperDocumentation(name)
|
|
1234
|
+
};
|
|
1235
|
+
const getContent = contentMethods[doc.type];
|
|
1236
|
+
return getContent ? getContent(doc.name) : "";
|
|
542
1237
|
}
|
|
543
1238
|
setupHandlers() {
|
|
544
1239
|
this.server.setRequestHandler(ListResourcesRequestSchema, () => {
|
|
@@ -587,8 +1282,8 @@ class MazUiMcpServer {
|
|
|
587
1282
|
|
|
588
1283
|
## Getting Started
|
|
589
1284
|
Use the MCP tools to explore components and features:
|
|
590
|
-
- \`
|
|
591
|
-
- \`
|
|
1285
|
+
- \`list\` - See everything available
|
|
1286
|
+
- \`search\` - Find specific features
|
|
592
1287
|
- \`get_doc\` - Get detailed documentation`;
|
|
593
1288
|
}
|
|
594
1289
|
break;
|
|
@@ -631,38 +1326,23 @@ Use the MCP tools to explore components and features:
|
|
|
631
1326
|
return {
|
|
632
1327
|
tools: [
|
|
633
1328
|
{
|
|
634
|
-
name: "
|
|
635
|
-
description:
|
|
1329
|
+
name: "search",
|
|
1330
|
+
description: 'Search across ALL Maz-UI documentation using a powerful unified search engine. Combines exact name matching, tag/alias matching, and full-text content search with TF-IDF scoring. Use this tool for any search query: component names (e.g. "MazBtn"), functional descriptions (e.g. "date picker"), prop names (e.g. "model-value"), use cases (e.g. "form validation"), or aliases (e.g. "modal" for dialog). Returns ranked results with contextual snippets.',
|
|
636
1331
|
inputSchema: {
|
|
637
1332
|
type: "object",
|
|
638
1333
|
properties: {
|
|
639
|
-
|
|
1334
|
+
query: {
|
|
640
1335
|
type: "string",
|
|
641
|
-
|
|
642
|
-
description: 'Filter by category (optional). Use "all" or omit to see everything.',
|
|
643
|
-
default: "all"
|
|
1336
|
+
description: 'Search query \u2014 can be a component name, description, prop name, use case, or alias. Examples: "button", "MazDialog", "model-value", "phone number input", "lazy loading"'
|
|
644
1337
|
},
|
|
645
|
-
|
|
646
|
-
type: "boolean",
|
|
647
|
-
description: "Include detailed descriptions for each item",
|
|
648
|
-
default: true
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
},
|
|
653
|
-
{
|
|
654
|
-
name: "smart_search",
|
|
655
|
-
description: `Intelligent search across ALL Maz-UI documentation with fuzzy matching and suggestions. Searches names, descriptions, and tags. Perfect when you know roughly what you're looking for but not the exact name (e.g., "button", "dropdown", "form validation", "dark mode").`,
|
|
656
|
-
inputSchema: {
|
|
657
|
-
type: "object",
|
|
658
|
-
properties: {
|
|
659
|
-
query: {
|
|
1338
|
+
category: {
|
|
660
1339
|
type: "string",
|
|
661
|
-
|
|
1340
|
+
enum: ["component", "guide", "composable", "directive", "plugin", "helper"],
|
|
1341
|
+
description: "Optional filter to restrict results to a specific documentation category"
|
|
662
1342
|
},
|
|
663
1343
|
maxResults: {
|
|
664
1344
|
type: "number",
|
|
665
|
-
description: "Maximum number of results to return",
|
|
1345
|
+
description: "Maximum number of results to return (default: 10)",
|
|
666
1346
|
default: 10,
|
|
667
1347
|
minimum: 1,
|
|
668
1348
|
maximum: 50
|
|
@@ -692,41 +1372,19 @@ Use the MCP tools to explore components and features:
|
|
|
692
1372
|
}
|
|
693
1373
|
},
|
|
694
1374
|
{
|
|
695
|
-
name: "
|
|
696
|
-
description: "
|
|
697
|
-
inputSchema: {
|
|
698
|
-
type: "object",
|
|
699
|
-
properties: {}
|
|
700
|
-
}
|
|
701
|
-
},
|
|
702
|
-
{
|
|
703
|
-
name: "get_components_by_category",
|
|
704
|
-
description: "Get components organized by functional category (form, layout, navigation, overlay, data, feedback, display, animation). Helps discover related components and understand the library structure.",
|
|
1375
|
+
name: "list",
|
|
1376
|
+
description: "Browse all available Maz-UI documentation grouped by category. Returns a structured list with counters per category and for each item: name, displayName, description. Use this tool to discover what components, composables, directives, plugins, helpers, and guides are available. Examples: list all docs, list only components, list composables.",
|
|
705
1377
|
inputSchema: {
|
|
706
1378
|
type: "object",
|
|
707
1379
|
properties: {
|
|
708
1380
|
category: {
|
|
709
1381
|
type: "string",
|
|
710
|
-
enum: ["all", "
|
|
711
|
-
description:
|
|
1382
|
+
enum: ["all", "component", "guide", "composable", "directive", "plugin", "helper"],
|
|
1383
|
+
description: 'Filter by documentation category. Use "all" or omit to see everything.',
|
|
712
1384
|
default: "all"
|
|
713
1385
|
}
|
|
714
1386
|
}
|
|
715
1387
|
}
|
|
716
|
-
},
|
|
717
|
-
{
|
|
718
|
-
name: "suggest_similar",
|
|
719
|
-
description: "When you can't find exactly what you're looking for, this tool suggests similar or related documentation based on functionality, naming patterns, and common use cases.",
|
|
720
|
-
inputSchema: {
|
|
721
|
-
type: "object",
|
|
722
|
-
properties: {
|
|
723
|
-
description: {
|
|
724
|
-
type: "string",
|
|
725
|
-
description: `Describe what you're trying to achieve or find. Example: "I need a way to show notifications", "looking for form validation", "need a date selector"`
|
|
726
|
-
}
|
|
727
|
-
},
|
|
728
|
-
required: ["description"]
|
|
729
|
-
}
|
|
730
1388
|
}
|
|
731
1389
|
]
|
|
732
1390
|
};
|
|
@@ -735,398 +1393,25 @@ Use the MCP tools to explore components and features:
|
|
|
735
1393
|
const { name, arguments: args } = request.params;
|
|
736
1394
|
try {
|
|
737
1395
|
switch (name) {
|
|
738
|
-
case "
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
(item) => category === "components" ? item.type === "component" : item.type === category
|
|
745
|
-
);
|
|
746
|
-
}
|
|
747
|
-
const groupedDocs = filteredIndex.reduce((acc, item) => {
|
|
748
|
-
if (!acc[item.type])
|
|
749
|
-
acc[item.type] = [];
|
|
750
|
-
acc[item.type].push(item);
|
|
751
|
-
return acc;
|
|
752
|
-
}, {});
|
|
753
|
-
let result = `# Maz-UI Documentation Index (${filteredIndex.length} items)
|
|
754
|
-
|
|
755
|
-
`;
|
|
756
|
-
const typeOrder = ["component", "guide", "composable", "directive", "plugin", "helper"];
|
|
757
|
-
const typeLabels = {
|
|
758
|
-
component: "Vue Components",
|
|
759
|
-
guide: "Documentation Guides",
|
|
760
|
-
composable: "Vue 3 Composables",
|
|
761
|
-
directive: "Vue Directives",
|
|
762
|
-
plugin: "Vue Plugins",
|
|
763
|
-
helper: "Utility Helpers"
|
|
764
|
-
};
|
|
765
|
-
for (const type of typeOrder) {
|
|
766
|
-
if (!groupedDocs[type])
|
|
767
|
-
continue;
|
|
768
|
-
result += `## ${typeLabels[type]} (${groupedDocs[type].length})
|
|
769
|
-
|
|
770
|
-
`;
|
|
771
|
-
for (const item of groupedDocs[type].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
772
|
-
result += `### ${item.displayName}
|
|
773
|
-
`;
|
|
774
|
-
result += `- **Name**: \`${item.name}\`
|
|
775
|
-
`;
|
|
776
|
-
if (includeDescriptions) {
|
|
777
|
-
result += `- **Description**: ${item.description}
|
|
778
|
-
`;
|
|
779
|
-
result += `- **Tags**: ${item.tags.slice(0, 5).join(", ")}
|
|
780
|
-
`;
|
|
781
|
-
}
|
|
782
|
-
result += `- **Get docs**: Use \`get_doc\` with name \`${item.name}\`
|
|
783
|
-
|
|
784
|
-
`;
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
result += `
|
|
788
|
-
---
|
|
789
|
-
**\u{1F4A1} Tips:**
|
|
790
|
-
- Use \`smart_search\` to find specific functionality
|
|
791
|
-
- Use \`get_doc\` with any name from above
|
|
792
|
-
- Use \`get_components_by_category\` to explore by function`;
|
|
793
|
-
return {
|
|
794
|
-
content: [{
|
|
795
|
-
type: "text",
|
|
796
|
-
text: result
|
|
797
|
-
}]
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
case "smart_search": {
|
|
801
|
-
const query = args?.query;
|
|
802
|
-
const maxResults = args?.maxResults || 10;
|
|
803
|
-
if (!query) {
|
|
804
|
-
throw new Error("Search query is required");
|
|
805
|
-
}
|
|
806
|
-
const results = this.searchInIndex(query).slice(0, maxResults);
|
|
807
|
-
if (results.length === 0) {
|
|
808
|
-
const suggestions = this.documentationIndex.map((item) => item.name).slice(0, 10).join(", ");
|
|
809
|
-
return {
|
|
810
|
-
content: [{
|
|
811
|
-
type: "text",
|
|
812
|
-
text: `No results found for "${query}".
|
|
813
|
-
|
|
814
|
-
**Suggestions:**
|
|
815
|
-
- Try broader terms like "button", "form", "modal"
|
|
816
|
-
- Check available items: ${suggestions}
|
|
817
|
-
- Use \`list_all_docs\` to see everything available
|
|
818
|
-
- Use \`suggest_similar\` if you're looking for specific functionality`
|
|
819
|
-
}]
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
let resultText = `# Search Results for "${query}" (${results.length} found)
|
|
823
|
-
|
|
824
|
-
`;
|
|
825
|
-
for (const item of results) {
|
|
826
|
-
resultText += `## ${item.displayName}
|
|
827
|
-
`;
|
|
828
|
-
resultText += `- **Type**: ${item.type}
|
|
829
|
-
`;
|
|
830
|
-
resultText += `- **Name**: \`${item.name}\`
|
|
831
|
-
`;
|
|
832
|
-
resultText += `- **Description**: ${item.description}
|
|
833
|
-
`;
|
|
834
|
-
resultText += `- **Use**: \`get_doc\` with name \`${item.name}\`
|
|
835
|
-
|
|
836
|
-
`;
|
|
837
|
-
}
|
|
838
|
-
resultText += `
|
|
839
|
-
**\u{1F4A1} Use \`get_doc\` with any name above to see full documentation.**`;
|
|
840
|
-
return {
|
|
841
|
-
content: [{
|
|
842
|
-
type: "text",
|
|
843
|
-
text: resultText
|
|
844
|
-
}]
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
case "get_doc": {
|
|
848
|
-
const name2 = args?.name;
|
|
849
|
-
const type = args?.type || "auto";
|
|
850
|
-
if (!name2) {
|
|
851
|
-
throw new Error("Name is required");
|
|
852
|
-
}
|
|
853
|
-
let found = this.documentationIndex.find(
|
|
854
|
-
(item) => item.name.toLowerCase() === name2.toLowerCase() || item.displayName.toLowerCase() === name2.toLowerCase() || item.tags.some((tag) => tag.toLowerCase() === name2.toLowerCase())
|
|
855
|
-
);
|
|
856
|
-
if (!found && type !== "auto") {
|
|
857
|
-
found = this.documentationIndex.find(
|
|
858
|
-
(item) => item.type === type && (item.name.toLowerCase().includes(name2.toLowerCase()) || item.displayName.toLowerCase().includes(name2.toLowerCase()))
|
|
859
|
-
);
|
|
860
|
-
}
|
|
861
|
-
if (!found) {
|
|
862
|
-
const fuzzyResults = this.searchInIndex(name2).slice(0, 5);
|
|
863
|
-
const allNames = this.documentationIndex.map((item) => item.name);
|
|
864
|
-
const fuzzyNameMatches = this.fuzzySearch(name2, allNames).slice(0, 3);
|
|
865
|
-
const combinedResults = /* @__PURE__ */ new Set([
|
|
866
|
-
...fuzzyResults.map((item) => item.name),
|
|
867
|
-
...fuzzyNameMatches
|
|
868
|
-
]);
|
|
869
|
-
if (combinedResults.size === 0) {
|
|
870
|
-
return {
|
|
871
|
-
content: [{
|
|
872
|
-
type: "text",
|
|
873
|
-
text: `Documentation not found for "${name2}".
|
|
874
|
-
|
|
875
|
-
**Available options:**
|
|
876
|
-
- Use \`smart_search\` to find similar items
|
|
877
|
-
- Use \`list_all_docs\` to see all available documentation
|
|
878
|
-
- Use \`suggest_similar\` to describe what you're looking for`
|
|
879
|
-
}]
|
|
880
|
-
};
|
|
881
|
-
}
|
|
882
|
-
const suggestions = Array.from(combinedResults).slice(0, 5).map((itemName) => {
|
|
883
|
-
const item = this.documentationIndex.find((i) => i.name === itemName);
|
|
884
|
-
return item ? `- **${item.displayName}** (\`${item.name}\`) - ${item.description}` : `- \`${itemName}\``;
|
|
885
|
-
}).join("\n");
|
|
886
|
-
return {
|
|
887
|
-
content: [{
|
|
888
|
-
type: "text",
|
|
889
|
-
text: `Exact match not found for "${name2}". Did you mean:
|
|
890
|
-
|
|
891
|
-
${suggestions}
|
|
892
|
-
|
|
893
|
-
**\u{1F4A1} Use \`get_doc\` with the exact name from above.**`
|
|
894
|
-
}]
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
let content = "";
|
|
898
|
-
const docName = found.name;
|
|
899
|
-
switch (found.type) {
|
|
900
|
-
case "component":
|
|
901
|
-
content = this.documentationService.getComponentDocumentation(docName);
|
|
902
|
-
break;
|
|
903
|
-
case "guide":
|
|
904
|
-
content = this.documentationService.getGuideDocumentation(docName);
|
|
905
|
-
break;
|
|
906
|
-
case "composable":
|
|
907
|
-
content = this.documentationService.getComposableDocumentation(docName);
|
|
908
|
-
break;
|
|
909
|
-
case "directive":
|
|
910
|
-
content = this.documentationService.getDirectiveDocumentation(docName);
|
|
911
|
-
break;
|
|
912
|
-
case "plugin":
|
|
913
|
-
content = this.documentationService.getPluginDocumentation(docName);
|
|
914
|
-
break;
|
|
915
|
-
case "helper":
|
|
916
|
-
content = this.documentationService.getHelperDocumentation(docName);
|
|
917
|
-
break;
|
|
918
|
-
}
|
|
919
|
-
if (!content) {
|
|
920
|
-
return {
|
|
921
|
-
content: [{
|
|
922
|
-
type: "text",
|
|
923
|
-
text: `Found "${found.displayName}" but documentation content is not available. This might be a documentation file that needs to be created.`
|
|
924
|
-
}]
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
return {
|
|
928
|
-
content: [{
|
|
929
|
-
type: "text",
|
|
930
|
-
text: content
|
|
931
|
-
}]
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
case "get_installation_guide": {
|
|
935
|
-
const installationGuides = ["getting-started", "installation", "setup", "quick-start"];
|
|
936
|
-
let guide = "";
|
|
937
|
-
for (const guideName of installationGuides) {
|
|
938
|
-
guide = this.documentationService.getGuideDocumentation(guideName);
|
|
939
|
-
if (guide)
|
|
940
|
-
break;
|
|
941
|
-
}
|
|
942
|
-
if (!guide) {
|
|
943
|
-
guide = this.documentationService.getOverview();
|
|
944
|
-
}
|
|
945
|
-
if (!guide) {
|
|
946
|
-
guide = `# Maz-UI Installation
|
|
947
|
-
|
|
948
|
-
## Quick Start
|
|
949
|
-
|
|
950
|
-
\`\`\`bash
|
|
951
|
-
npm install maz-ui
|
|
952
|
-
# or
|
|
953
|
-
yarn add maz-ui
|
|
954
|
-
# or
|
|
955
|
-
pnpm add maz-ui
|
|
956
|
-
\`\`\`
|
|
957
|
-
|
|
958
|
-
## Vue.js Integration
|
|
959
|
-
|
|
960
|
-
\`\`\`javascript
|
|
961
|
-
import { createApp } from 'vue'
|
|
962
|
-
import MazUI from 'maz-ui'
|
|
963
|
-
import 'maz-ui/dist/style.css'
|
|
964
|
-
|
|
965
|
-
const app = createApp(App)
|
|
966
|
-
app.use(MazUI)
|
|
967
|
-
app.mount('#app')
|
|
968
|
-
\`\`\`
|
|
969
|
-
|
|
970
|
-
**\u{1F4A1} Use \`smart_search\` with "setup" or "configuration" for more detailed guides.**`;
|
|
971
|
-
}
|
|
972
|
-
return {
|
|
973
|
-
content: [{
|
|
974
|
-
type: "text",
|
|
975
|
-
text: guide
|
|
976
|
-
}]
|
|
977
|
-
};
|
|
978
|
-
}
|
|
979
|
-
case "get_components_by_category": {
|
|
980
|
-
const category = args?.category || "all";
|
|
981
|
-
const componentsByCategory = this.documentationIndex.filter((item) => item.type === "component").reduce((acc, item) => {
|
|
982
|
-
const componentType = this.getComponentType(item.name);
|
|
983
|
-
if (!acc[componentType])
|
|
984
|
-
acc[componentType] = [];
|
|
985
|
-
acc[componentType].push(item);
|
|
986
|
-
return acc;
|
|
987
|
-
}, {});
|
|
988
|
-
let result = "";
|
|
989
|
-
if (category === "all") {
|
|
990
|
-
result = `# Components by Category
|
|
991
|
-
|
|
992
|
-
`;
|
|
993
|
-
const categoryDescriptions = {
|
|
994
|
-
form: "Input elements and form controls",
|
|
995
|
-
layout: "Structure and positioning components",
|
|
996
|
-
navigation: "Navigation and wayfinding components",
|
|
997
|
-
overlay: "Modal dialogs and overlay components",
|
|
998
|
-
data: "Data visualization and table components",
|
|
999
|
-
feedback: "Loading states and progress indicators",
|
|
1000
|
-
display: "Media display and visual components",
|
|
1001
|
-
animation: "Animated and interactive elements",
|
|
1002
|
-
general: "General purpose components"
|
|
1003
|
-
};
|
|
1004
|
-
for (const [cat, components] of Object.entries(componentsByCategory)) {
|
|
1005
|
-
result += `## ${cat.charAt(0).toUpperCase() + cat.slice(1)} Components (${components.length})
|
|
1006
|
-
`;
|
|
1007
|
-
result += `*${categoryDescriptions[cat]}*
|
|
1008
|
-
|
|
1009
|
-
`;
|
|
1010
|
-
for (const component of components.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1011
|
-
result += `- **${component.displayName}** (\`${component.name}\`) - ${component.description.split(" - ")[1]}
|
|
1012
|
-
`;
|
|
1013
|
-
}
|
|
1014
|
-
result += "\n";
|
|
1015
|
-
}
|
|
1016
|
-
} else {
|
|
1017
|
-
const components = componentsByCategory[category] || [];
|
|
1018
|
-
result = `# ${category.charAt(0).toUpperCase() + category.slice(1)} Components (${components.length})
|
|
1019
|
-
|
|
1020
|
-
`;
|
|
1021
|
-
for (const component of components.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1022
|
-
result += `## ${component.displayName}
|
|
1023
|
-
`;
|
|
1024
|
-
result += `- **Name**: \`${component.name}\`
|
|
1025
|
-
`;
|
|
1026
|
-
result += `- **Description**: ${component.description}
|
|
1027
|
-
`;
|
|
1028
|
-
result += `- **Get docs**: Use \`get_doc\` with name \`${component.name}\`
|
|
1029
|
-
|
|
1030
|
-
`;
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
result += `
|
|
1034
|
-
**\u{1F4A1} Use \`get_doc\` with any component name to see full documentation.**`;
|
|
1035
|
-
return {
|
|
1036
|
-
content: [{
|
|
1037
|
-
type: "text",
|
|
1038
|
-
text: result
|
|
1039
|
-
}]
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
case "suggest_similar": {
|
|
1043
|
-
const description = args?.description;
|
|
1044
|
-
if (!description) {
|
|
1045
|
-
throw new Error("Description is required");
|
|
1046
|
-
}
|
|
1047
|
-
const keywords = description.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((word) => word.length > 2);
|
|
1048
|
-
const suggestions = /* @__PURE__ */ new Map();
|
|
1049
|
-
for (const item of this.documentationIndex) {
|
|
1050
|
-
let score = 0;
|
|
1051
|
-
const itemText = `${item.name} ${item.displayName} ${item.description} ${item.tags.join(" ")}`.toLowerCase();
|
|
1052
|
-
for (const keyword of keywords) {
|
|
1053
|
-
if (itemText.includes(keyword)) {
|
|
1054
|
-
score += 1;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
for (const keyword of keywords) {
|
|
1058
|
-
const fuzzyMatches = this.fuzzySearch(keyword, item.tags);
|
|
1059
|
-
if (fuzzyMatches.length > 0) {
|
|
1060
|
-
score += 0.5 * fuzzyMatches.length;
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
if (score > 0) {
|
|
1064
|
-
suggestions.set(`${item.type}:${item.name}`, score);
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
const fuzzyResults = this.searchInIndex(description);
|
|
1068
|
-
const allSuggestions = /* @__PURE__ */ new Set([
|
|
1069
|
-
...Array.from(suggestions.entries()).sort(([, a], [, b]) => b - a).slice(0, 5).map(([key]) => key),
|
|
1070
|
-
...fuzzyResults.slice(0, 3).map((item) => `${item.type}:${item.name}`)
|
|
1071
|
-
]);
|
|
1072
|
-
if (allSuggestions.size === 0) {
|
|
1073
|
-
return {
|
|
1074
|
-
content: [{
|
|
1075
|
-
type: "text",
|
|
1076
|
-
text: `No direct suggestions found for "${description}".
|
|
1077
|
-
|
|
1078
|
-
**Try these approaches:**
|
|
1079
|
-
- Use \`list_all_docs\` to browse all available features
|
|
1080
|
-
- Use \`smart_search\` with simpler keywords
|
|
1081
|
-
- Use \`get_components_by_category\` to explore by function
|
|
1082
|
-
|
|
1083
|
-
Common categories: form, layout, navigation, feedback, display, action`
|
|
1084
|
-
}]
|
|
1085
|
-
};
|
|
1086
|
-
}
|
|
1087
|
-
let result = `# Suggestions based on: "${description}"
|
|
1088
|
-
|
|
1089
|
-
`;
|
|
1090
|
-
for (const suggestionKey of Array.from(allSuggestions).slice(0, 8)) {
|
|
1091
|
-
const [type, name2] = suggestionKey.split(":");
|
|
1092
|
-
const item = this.documentationIndex.find((i) => i.type === type && i.name === name2);
|
|
1093
|
-
if (item) {
|
|
1094
|
-
result += `## ${item.displayName}
|
|
1095
|
-
`;
|
|
1096
|
-
result += `- **Type**: ${item.type}
|
|
1097
|
-
`;
|
|
1098
|
-
result += `- **Description**: ${item.description}
|
|
1099
|
-
`;
|
|
1100
|
-
result += `- **Get docs**: Use \`get_doc\` with name \`${item.name}\`
|
|
1101
|
-
|
|
1102
|
-
`;
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
result += `
|
|
1106
|
-
**\u{1F4A1} Use \`get_doc\` with any name above, or try \`smart_search\` with specific terms.**`;
|
|
1107
|
-
return {
|
|
1108
|
-
content: [{
|
|
1109
|
-
type: "text",
|
|
1110
|
-
text: result
|
|
1111
|
-
}]
|
|
1112
|
-
};
|
|
1113
|
-
}
|
|
1396
|
+
case "search":
|
|
1397
|
+
return this.handleSearch(args);
|
|
1398
|
+
case "list":
|
|
1399
|
+
return this.handleList(args);
|
|
1400
|
+
case "get_doc":
|
|
1401
|
+
return this.handleGetDoc(args);
|
|
1114
1402
|
default:
|
|
1115
|
-
throw new Error(`Unknown tool: ${name}. Available tools:
|
|
1403
|
+
throw new Error(`Unknown tool: ${name}. Available tools: search, get_doc, list`);
|
|
1116
1404
|
}
|
|
1117
1405
|
} catch (error) {
|
|
1118
1406
|
return {
|
|
1119
1407
|
content: [{
|
|
1120
1408
|
type: "text",
|
|
1121
|
-
text:
|
|
1409
|
+
text: `Error: ${getErrorMessage(error)}
|
|
1122
1410
|
|
|
1123
1411
|
**Available tools:**
|
|
1124
|
-
- \`
|
|
1125
|
-
- \`smart_search\` - Search by keyword
|
|
1412
|
+
- \`search\` - Search across all documentation
|
|
1126
1413
|
- \`get_doc\` - Get specific documentation
|
|
1127
|
-
- \`
|
|
1128
|
-
- \`get_components_by_category\` - Browse by function
|
|
1129
|
-
- \`suggest_similar\` - Find related items`
|
|
1414
|
+
- \`list\` - Browse all available documentation`
|
|
1130
1415
|
}],
|
|
1131
1416
|
isError: true
|
|
1132
1417
|
};
|