@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/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.7.9";
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
- * Fuzzy search implementation
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
- calculateFuzzyScore(query, target) {
502
- if (target.includes(query))
503
- return 1;
504
- if (query.length === 0)
505
- return 0;
506
- let score = 0;
507
- let queryIndex = 0;
508
- for (let i = 0; i < target.length && queryIndex < query.length; i++) {
509
- if (target[i] === query[queryIndex]) {
510
- score += 1 / target.length;
511
- queryIndex++;
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
- return queryIndex === query.length ? score : 0;
515
- }
516
- /**
517
- * Search in documentation index with fuzzy matching
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
- if (maxScore > 0.3) {
538
- results.push({ item, score: maxScore });
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 results.sort((a, b) => b.score - a.score).slice(0, 10).map((r) => r.item);
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
- - \`list_all_docs\` - See everything available
591
- - \`smart_search\` - Find specific features
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: "list_all_docs",
635
- description: "Get a complete, categorized index of ALL available documentation in Maz-UI. This is the best starting point to understand what's available. Returns components, guides, composables, directives, plugins, and helpers with descriptions and search tags.",
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
- category: {
1334
+ query: {
640
1335
  type: "string",
641
- enum: ["all", "components", "guides", "composables", "directives", "plugins", "helpers"],
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
- includeDescriptions: {
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
- description: 'Search term - can be partial, fuzzy, or describe functionality. Examples: "button", "modal", "form", "tooltip", "validation", "date picker"'
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: "get_installation_guide",
696
- description: "Get comprehensive installation and setup instructions for Maz-UI, including npm installation, Vue.js integration, and initial configuration.",
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", "form", "layout", "navigation", "overlay", "data", "feedback", "display", "animation", "general"],
711
- description: "Component category to filter by",
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 "list_all_docs": {
739
- const category = args?.category || "all";
740
- const includeDescriptions = args?.includeDescriptions ?? true;
741
- let filteredIndex = this.documentationIndex;
742
- if (category !== "all") {
743
- filteredIndex = this.documentationIndex.filter(
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: list_all_docs, smart_search, get_doc, get_installation_guide, get_components_by_category, suggest_similar`);
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: `\u274C **Error**: ${getErrorMessage(error)}
1409
+ text: `Error: ${getErrorMessage(error)}
1122
1410
 
1123
1411
  **Available tools:**
1124
- - \`list_all_docs\` - See all documentation
1125
- - \`smart_search\` - Search by keyword
1412
+ - \`search\` - Search across all documentation
1126
1413
  - \`get_doc\` - Get specific documentation
1127
- - \`get_installation_guide\` - Setup instructions
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
  };