react-docs-mcp 1.0.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.
@@ -0,0 +1,261 @@
1
+ /**
2
+ * searchEngine.ts
3
+ * Implement search functionality over documentation
4
+ */
5
+ import { parseMarkdown } from './markdownParser.js';
6
+ import { EmbeddingService } from './embeddingService.js';
7
+ import CONFIG from './config.js';
8
+ export class SearchEngine {
9
+ docsManager;
10
+ embeddingService;
11
+ documentIndex = new Map();
12
+ indexed = false;
13
+ embeddingsGenerated = false;
14
+ /**
15
+ * Initialize search engine
16
+ * @param docsManager - Instance of DocsManager
17
+ */
18
+ constructor(docsManager) {
19
+ this.docsManager = docsManager;
20
+ this.embeddingService = new EmbeddingService();
21
+ }
22
+ /**
23
+ * Index all documents for searching
24
+ * Should be called after repo update
25
+ */
26
+ async indexDocuments() {
27
+ console.log('Indexing documents...');
28
+ this.documentIndex.clear();
29
+ const allDocs = await this.docsManager.getAllDocs();
30
+ for (const docPath of allDocs) {
31
+ try {
32
+ const content = await this.docsManager.readDoc(docPath);
33
+ const parsedDoc = await parseMarkdown(content, docPath);
34
+ this.documentIndex.set(parsedDoc.path, parsedDoc);
35
+ }
36
+ catch (error) {
37
+ console.warn(`Failed to index document ${docPath}:`, error);
38
+ }
39
+ }
40
+ this.indexed = true;
41
+ console.log(`Indexed ${this.documentIndex.size} documents`);
42
+ }
43
+ /**
44
+ * Generate embeddings for all documents
45
+ * Called lazily when semantic search is first used
46
+ */
47
+ async generateEmbeddings() {
48
+ if (this.embeddingsGenerated)
49
+ return;
50
+ console.log('Generating embeddings for documents (first run may take 1-2 minutes)...');
51
+ try {
52
+ await this.embeddingService.initialize();
53
+ let count = 0;
54
+ for (const doc of this.documentIndex.values()) {
55
+ // Create embedding text from title + description + first 1000 chars
56
+ const embeddingText = `${doc.metadata.title}. ${doc.metadata.description || ''}. ${doc.plainText.slice(0, 1000)}`;
57
+ const embedding = await this.embeddingService.generateEmbedding(embeddingText);
58
+ doc.embedding = embedding;
59
+ count++;
60
+ if (count % 10 === 0) {
61
+ console.log(`Generated embeddings for ${count}/${this.documentIndex.size} documents...`);
62
+ }
63
+ }
64
+ this.embeddingsGenerated = true;
65
+ console.log(`Embeddings generated for all ${this.documentIndex.size} documents`);
66
+ }
67
+ catch (error) {
68
+ console.error('Failed to generate embeddings:', error);
69
+ throw error;
70
+ }
71
+ }
72
+ /**
73
+ * Search documents
74
+ * @param query - Search query string
75
+ * @param options - Search options (section filter, limit, etc.)
76
+ * @returns Ranked search results
77
+ */
78
+ async search(query, options) {
79
+ // Ensure documents are indexed
80
+ if (!this.indexed) {
81
+ await this.indexDocuments();
82
+ }
83
+ // Handle empty query
84
+ if (!query.trim()) {
85
+ return [];
86
+ }
87
+ const useSemanticSearch = options?.useSemanticSearch ?? CONFIG.search.semanticSearchEnabled;
88
+ // Use semantic or hybrid search if enabled
89
+ if (useSemanticSearch) {
90
+ return await this.semanticSearch(query, options);
91
+ }
92
+ // Fall back to keyword search
93
+ return await this.keywordSearch(query, options);
94
+ }
95
+ /**
96
+ * Keyword-based search
97
+ */
98
+ async keywordSearch(query, options) {
99
+ const limit = Math.min(options?.limit || CONFIG.search.defaultLimit, CONFIG.search.maxLimit);
100
+ const minScore = options?.minScore ?? CONFIG.search.minScore;
101
+ const sectionFilter = options?.section?.toLowerCase();
102
+ // Normalize query
103
+ const queryTerms = query.toLowerCase().split(/\s+/).filter(Boolean);
104
+ const results = [];
105
+ // Score each document
106
+ for (const doc of this.documentIndex.values()) {
107
+ // Apply section filter
108
+ if (sectionFilter && doc.section.toLowerCase() !== sectionFilter) {
109
+ continue;
110
+ }
111
+ const score = this.scoreDocument(doc, queryTerms);
112
+ if (score >= minScore) {
113
+ const snippet = this.generateSnippet(doc, queryTerms);
114
+ results.push({ doc, score, snippet });
115
+ }
116
+ }
117
+ // Sort by score descending
118
+ results.sort((a, b) => b.score - a.score);
119
+ // Return top results
120
+ return results.slice(0, limit);
121
+ }
122
+ /**
123
+ * Semantic search using embeddings (hybrid with keyword search)
124
+ */
125
+ async semanticSearch(query, options) {
126
+ // Ensure embeddings are generated
127
+ if (!this.embeddingsGenerated) {
128
+ await this.generateEmbeddings();
129
+ }
130
+ const limit = Math.min(options?.limit || CONFIG.search.defaultLimit, CONFIG.search.maxLimit);
131
+ const sectionFilter = options?.section?.toLowerCase();
132
+ // Generate query embedding
133
+ const queryEmbedding = await this.embeddingService.generateEmbedding(query);
134
+ // Get all docs with embeddings, filtered by section
135
+ const docs = Array.from(this.documentIndex.values()).filter(doc => {
136
+ if (sectionFilter && doc.section.toLowerCase() !== sectionFilter) {
137
+ return false;
138
+ }
139
+ return doc.embedding !== undefined;
140
+ });
141
+ // Calculate hybrid scores (keyword + semantic)
142
+ const queryTerms = query.toLowerCase().split(/\s+/).filter(Boolean);
143
+ const results = [];
144
+ for (const doc of docs) {
145
+ // Keyword score (normalized to 0-1)
146
+ const keywordScore = this.scoreDocument(doc, queryTerms) / 100;
147
+ // Semantic similarity score (0-1)
148
+ const semanticScore = this.embeddingService.cosineSimilarity(queryEmbedding, doc.embedding);
149
+ // Hybrid score (weighted combination)
150
+ const hybridScore = CONFIG.search.hybridKeywordWeight * keywordScore +
151
+ CONFIG.search.hybridSemanticWeight * semanticScore;
152
+ if (semanticScore >= CONFIG.search.semanticMinSimilarity) {
153
+ const snippet = this.generateSnippet(doc, queryTerms);
154
+ results.push({ doc, score: hybridScore, snippet });
155
+ }
156
+ }
157
+ // Sort by hybrid score descending
158
+ results.sort((a, b) => b.score - a.score);
159
+ // Return top results
160
+ return results.slice(0, limit);
161
+ }
162
+ /**
163
+ * Score a document based on query terms
164
+ */
165
+ scoreDocument(doc, queryTerms) {
166
+ let score = 0;
167
+ const titleLower = doc.metadata.title.toLowerCase();
168
+ const plainTextLower = doc.plainText.toLowerCase();
169
+ const pathLower = doc.path.toLowerCase();
170
+ for (const term of queryTerms) {
171
+ // Title match (high weight)
172
+ if (titleLower.includes(term)) {
173
+ score += 10;
174
+ }
175
+ // Path match (medium weight)
176
+ if (pathLower.includes(term)) {
177
+ score += 5;
178
+ }
179
+ // Count occurrences in plain text
180
+ const regex = new RegExp(term, 'gi');
181
+ const matches = plainTextLower.match(regex);
182
+ if (matches) {
183
+ score += matches.length * 0.5;
184
+ }
185
+ // Description match (medium weight)
186
+ if (doc.metadata.description?.toLowerCase().includes(term)) {
187
+ score += 3;
188
+ }
189
+ }
190
+ return score;
191
+ }
192
+ /**
193
+ * Generate context snippet showing matched text
194
+ */
195
+ generateSnippet(doc, queryTerms) {
196
+ const plainText = doc.plainText;
197
+ // Find first occurrence of any query term
198
+ let firstMatchIndex = -1;
199
+ let matchedTerm = '';
200
+ for (const term of queryTerms) {
201
+ const index = plainText.toLowerCase().indexOf(term);
202
+ if (index !== -1 && (firstMatchIndex === -1 || index < firstMatchIndex)) {
203
+ firstMatchIndex = index;
204
+ matchedTerm = term;
205
+ }
206
+ }
207
+ if (firstMatchIndex === -1) {
208
+ // No match in content, use description or first 150 chars
209
+ return doc.metadata.description || plainText.slice(0, 150) + '...';
210
+ }
211
+ // Extract context around match (±75 chars)
212
+ const contextRadius = 75;
213
+ const start = Math.max(0, firstMatchIndex - contextRadius);
214
+ const end = Math.min(plainText.length, firstMatchIndex + matchedTerm.length + contextRadius);
215
+ let snippet = plainText.slice(start, end);
216
+ // Add ellipsis if truncated
217
+ if (start > 0)
218
+ snippet = '...' + snippet;
219
+ if (end < plainText.length)
220
+ snippet = snippet + '...';
221
+ return snippet.trim();
222
+ }
223
+ /**
224
+ * Get document by exact path
225
+ * @param path - Document path relative to content root
226
+ * @returns Parsed document or null if not found
227
+ */
228
+ async getDocByPath(path) {
229
+ // Ensure documents are indexed
230
+ if (!this.indexed) {
231
+ await this.indexDocuments();
232
+ }
233
+ // Normalize path (remove .md if present)
234
+ const normalizedPath = path.replace(/\.md$/, '');
235
+ return this.documentIndex.get(normalizedPath) || null;
236
+ }
237
+ /**
238
+ * List all available sections
239
+ */
240
+ getSections() {
241
+ return [...CONFIG.sections];
242
+ }
243
+ /**
244
+ * Get all documents in a section
245
+ */
246
+ async getDocsBySection(section) {
247
+ // Ensure documents are indexed
248
+ if (!this.indexed) {
249
+ await this.indexDocuments();
250
+ }
251
+ const sectionLower = section.toLowerCase();
252
+ const docs = [];
253
+ for (const doc of this.documentIndex.values()) {
254
+ if (doc.section.toLowerCase() === sectionLower) {
255
+ docs.push(doc);
256
+ }
257
+ }
258
+ return docs;
259
+ }
260
+ }
261
+ //# sourceMappingURL=searchEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searchEngine.js","sourceRoot":"","sources":["../src/searchEngine.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,MAAM,MAAM,aAAa,CAAC;AAGjC,MAAM,OAAO,YAAY;IACf,WAAW,CAAc;IACzB,gBAAgB,CAAmB;IACnC,aAAa,GAA2B,IAAI,GAAG,EAAE,CAAC;IAClD,OAAO,GAAY,KAAK,CAAC;IACzB,mBAAmB,GAAY,KAAK,CAAC;IAE7C;;;OAGG;IACH,YAAY,WAAwB;QAClC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,gBAAgB,GAAG,IAAI,gBAAgB,EAAE,CAAC;IACjD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,cAAc;QAClB,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACrC,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;QAE3B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC;QAEpD,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBACxD,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACxD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACpD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,CAAC,4BAA4B,OAAO,GAAG,EAAE,KAAK,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,aAAa,CAAC,IAAI,YAAY,CAAC,CAAC;IAC9D,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,kBAAkB;QACtB,IAAI,IAAI,CAAC,mBAAmB;YAAE,OAAO;QAErC,OAAO,CAAC,GAAG,CAAC,yEAAyE,CAAC,CAAC;QAEvF,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,CAAC;YAEzC,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC9C,oEAAoE;gBACpE,MAAM,aAAa,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,KAAK,GAAG,CAAC,QAAQ,CAAC,WAAW,IAAI,EAAE,KAAK,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC;gBAClH,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC;gBAC/E,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC;gBAC1B,KAAK,EAAE,CAAC;gBAER,IAAI,KAAK,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC;oBACrB,OAAO,CAAC,GAAG,CAAC,4BAA4B,KAAK,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,eAAe,CAAC,CAAC;gBAC3F,CAAC;YACH,CAAC;YAED,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;YAChC,OAAO,CAAC,GAAG,CAAC,gCAAgC,IAAI,CAAC,aAAa,CAAC,IAAI,YAAY,CAAC,CAAC;QACnF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC;YACvD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,MAAM,CACV,KAAa,EACb,OAAuB;QAEvB,+BAA+B;QAC/B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAC9B,CAAC;QAED,qBAAqB;QACrB,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YAClB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,iBAAiB,GAAG,OAAO,EAAE,iBAAiB,IAAI,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC;QAE5F,2CAA2C;QAC3C,IAAI,iBAAiB,EAAE,CAAC;YACtB,OAAO,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACnD,CAAC;QAED,8BAA8B;QAC9B,OAAO,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,aAAa,CACzB,KAAa,EACb,OAAuB;QAEvB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,OAAO,EAAE,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,YAAY,EAC5C,MAAM,CAAC,MAAM,CAAC,QAAQ,CACvB,CAAC;QACF,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC7D,MAAM,aAAa,GAAG,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;QAEtD,kBAAkB;QAClB,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAEpE,MAAM,OAAO,GAAmB,EAAE,CAAC;QAEnC,sBAAsB;QACtB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;YAC9C,uBAAuB;YACvB,IAAI,aAAa,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,aAAa,EAAE,CAAC;gBACjE,SAAS;YACX,CAAC;YAED,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YAElD,IAAI,KAAK,IAAI,QAAQ,EAAE,CAAC;gBACtB,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;gBACtD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;QAED,2BAA2B;QAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAE1C,qBAAqB;QACrB,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,cAAc,CAC1B,KAAa,EACb,OAAuB;QAEvB,kCAAkC;QAClC,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC9B,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAClC,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,OAAO,EAAE,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,YAAY,EAC5C,MAAM,CAAC,MAAM,CAAC,QAAQ,CACvB,CAAC;QACF,MAAM,aAAa,GAAG,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;QAEtD,2BAA2B;QAC3B,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAE5E,oDAAoD;QACpD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YAChE,IAAI,aAAa,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,aAAa,EAAE,CAAC;gBACjE,OAAO,KAAK,CAAC;YACf,CAAC;YACD,OAAO,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC;QACrC,CAAC,CAAC,CAAC;QAEH,+CAA+C;QAC/C,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACpE,MAAM,OAAO,GAAmB,EAAE,CAAC;QAEnC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,oCAAoC;YACpC,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,UAAU,CAAC,GAAG,GAAG,CAAC;YAE/D,kCAAkC;YAClC,MAAM,aAAa,GAAG,IAAI,CAAC,gBAAgB,CAAC,gBAAgB,CAC1D,cAAc,EACd,GAAG,CAAC,SAAU,CACf,CAAC;YAEF,sCAAsC;YACtC,MAAM,WAAW,GACf,MAAM,CAAC,MAAM,CAAC,mBAAmB,GAAG,YAAY;gBAChD,MAAM,CAAC,MAAM,CAAC,oBAAoB,GAAG,aAAa,CAAC;YAErD,IAAI,aAAa,IAAI,MAAM,CAAC,MAAM,CAAC,qBAAqB,EAAE,CAAC;gBACzD,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;gBACtD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QAED,kCAAkC;QAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAE1C,qBAAqB;QACrB,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,GAAc,EAAE,UAAoB;QACxD,IAAI,KAAK,GAAG,CAAC,CAAC;QAEd,MAAM,UAAU,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QACpD,MAAM,cAAc,GAAG,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;QACnD,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAEzC,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,4BAA4B;YAC5B,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9B,KAAK,IAAI,EAAE,CAAC;YACd,CAAC;YAED,6BAA6B;YAC7B,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7B,KAAK,IAAI,CAAC,CAAC;YACb,CAAC;YAED,kCAAkC;YAClC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACrC,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC5C,IAAI,OAAO,EAAE,CAAC;gBACZ,KAAK,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC;YAChC,CAAC;YAED,oCAAoC;YACpC,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3D,KAAK,IAAI,CAAC,CAAC;YACb,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACK,eAAe,CAAC,GAAc,EAAE,UAAoB;QAC1D,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC;QAEhC,0CAA0C;QAC1C,IAAI,eAAe,GAAG,CAAC,CAAC,CAAC;QACzB,IAAI,WAAW,GAAG,EAAE,CAAC;QAErB,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACpD,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,CAAC,IAAI,KAAK,GAAG,eAAe,CAAC,EAAE,CAAC;gBACxE,eAAe,GAAG,KAAK,CAAC;gBACxB,WAAW,GAAG,IAAI,CAAC;YACrB,CAAC;QACH,CAAC;QAED,IAAI,eAAe,KAAK,CAAC,CAAC,EAAE,CAAC;YAC3B,0DAA0D;YAC1D,OAAO,GAAG,CAAC,QAAQ,CAAC,WAAW,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;QACrE,CAAC;QAED,2CAA2C;QAC3C,MAAM,aAAa,GAAG,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,GAAG,aAAa,CAAC,CAAC;QAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC;QAE7F,IAAI,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAE1C,4BAA4B;QAC5B,IAAI,KAAK,GAAG,CAAC;YAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;QACzC,IAAI,GAAG,GAAG,SAAS,CAAC,MAAM;YAAE,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;QAEtD,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;IACxB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,YAAY,CAAC,IAAY;QAC7B,+BAA+B;QAC/B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAC9B,CAAC;QAED,yCAAyC;QACzC,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAEjD,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC;IACxD,CAAC;IAED;;OAEG;IACH,WAAW;QACT,OAAO,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,gBAAgB,CAAC,OAAe;QACpC,+BAA+B;QAC/B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAC9B,CAAC;QAED,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAgB,EAAE,CAAC;QAE7B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;YAC9C,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,YAAY,EAAE,CAAC;gBAC/C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACjB,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * summarizer.ts
3
+ * Extract concise summaries from markdown content
4
+ */
5
+ /**
6
+ * Extract a concise summary from markdown content
7
+ * @param content - Full markdown content
8
+ * @param maxLength - Maximum summary length in characters
9
+ * @returns Concise summary
10
+ */
11
+ export declare function summarizeContent(content: string, maxLength?: number): string;
12
+ /**
13
+ * Extract key sections from markdown (headings + first line of each section)
14
+ * @param content - Full markdown content
15
+ * @returns Structured overview
16
+ */
17
+ export declare function extractStructure(content: string): string;
18
+ //# sourceMappingURL=summarizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarizer.d.ts","sourceRoot":"","sources":["../src/summarizer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,GAAE,MAAa,GAAG,MAAM,CA0ClF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA2BxD"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * summarizer.ts
3
+ * Extract concise summaries from markdown content
4
+ */
5
+ /**
6
+ * Extract a concise summary from markdown content
7
+ * @param content - Full markdown content
8
+ * @param maxLength - Maximum summary length in characters
9
+ * @returns Concise summary
10
+ */
11
+ export function summarizeContent(content, maxLength = 1500) {
12
+ // Remove code blocks (can be very long)
13
+ let summary = content.replace(/```[\s\S]*?```/g, '[code example]');
14
+ // Remove frontmatter if present
15
+ summary = summary.replace(/^---[\s\S]*?---\n/, '');
16
+ // Extract first few meaningful paragraphs
17
+ const paragraphs = summary
18
+ .split(/\n\n+/)
19
+ .map(p => p.trim())
20
+ .filter(p => p.length > 20 && !p.startsWith('#'));
21
+ // Take first few paragraphs up to maxLength
22
+ let result = '';
23
+ let headings = [];
24
+ // Extract headings for structure
25
+ const headingMatches = content.match(/^#{1,3}\s+(.+)$/gm);
26
+ if (headingMatches) {
27
+ headings = headingMatches.slice(0, 5).map(h => h.replace(/^#+\s*/, '- '));
28
+ }
29
+ // Build summary
30
+ for (const para of paragraphs.slice(0, 3)) {
31
+ if (result.length + para.length > maxLength) {
32
+ break;
33
+ }
34
+ result += para + '\n\n';
35
+ }
36
+ // Add structure if we have headings
37
+ if (headings.length > 0 && result.length < maxLength * 0.7) {
38
+ result += '\n**Content structure:**\n' + headings.join('\n');
39
+ }
40
+ // Truncate if still too long
41
+ if (result.length > maxLength) {
42
+ result = result.slice(0, maxLength) + '...';
43
+ }
44
+ return result.trim();
45
+ }
46
+ /**
47
+ * Extract key sections from markdown (headings + first line of each section)
48
+ * @param content - Full markdown content
49
+ * @returns Structured overview
50
+ */
51
+ export function extractStructure(content) {
52
+ const lines = content.split('\n');
53
+ const structure = [];
54
+ let currentHeading = '';
55
+ let capturedFirstLine = false;
56
+ for (const line of lines) {
57
+ // Match headings
58
+ const headingMatch = line.match(/^(#{1,3})\s+(.+)$/);
59
+ if (headingMatch) {
60
+ currentHeading = headingMatch[2];
61
+ structure.push(`\n**${currentHeading}**`);
62
+ capturedFirstLine = false;
63
+ continue;
64
+ }
65
+ // Capture first meaningful line after heading
66
+ if (currentHeading && !capturedFirstLine && line.trim().length > 20) {
67
+ const cleaned = line.replace(/[*_`]/g, '').trim();
68
+ if (!cleaned.startsWith('<') && !cleaned.startsWith('[')) {
69
+ structure.push(cleaned.slice(0, 100));
70
+ capturedFirstLine = true;
71
+ }
72
+ }
73
+ }
74
+ return structure.join('\n');
75
+ }
76
+ //# sourceMappingURL=summarizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarizer.js","sourceRoot":"","sources":["../src/summarizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * types.ts
3
+ * Define all TypeScript interfaces and types used across the project
4
+ */
5
+ /**
6
+ * Document metadata parsed from frontmatter
7
+ */
8
+ export interface DocMetadata {
9
+ title: string;
10
+ description?: string;
11
+ date?: string;
12
+ author?: string;
13
+ tags?: string[];
14
+ [key: string]: unknown;
15
+ }
16
+ /**
17
+ * Parsed document with content and metadata
18
+ */
19
+ export interface ParsedDoc {
20
+ path: string;
21
+ section: string;
22
+ metadata: DocMetadata;
23
+ content: string;
24
+ plainText: string;
25
+ embedding?: number[];
26
+ }
27
+ /**
28
+ * Search result with relevance
29
+ */
30
+ export interface SearchResult {
31
+ doc: ParsedDoc;
32
+ score: number;
33
+ snippet: string;
34
+ }
35
+ /**
36
+ * Search options
37
+ */
38
+ export interface SearchOptions {
39
+ section?: string;
40
+ limit?: number;
41
+ minScore?: number;
42
+ useSemanticSearch?: boolean;
43
+ }
44
+ /**
45
+ * Git repository status
46
+ */
47
+ export interface RepoStatus {
48
+ isCloned: boolean;
49
+ lastUpdated?: Date;
50
+ currentCommit?: string;
51
+ }
52
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,WAAW,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,SAAS,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB"}
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * types.ts
3
+ * Define all TypeScript interfaces and types used across the project
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "react-docs-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server providing AI agents with semantic search over React documentation",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "react-docs-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx src/index.ts",
13
+ "start": "node dist/index.js",
14
+ "prepublishOnly": "npm run build",
15
+ "postinstall": "node dist/index.js --version || echo 'Run: npx react-docs-mcp'"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "react",
21
+ "react-docs",
22
+ "documentation",
23
+ "ai",
24
+ "claude",
25
+ "cursor",
26
+ "semantic-search",
27
+ "embeddings",
28
+ "vector-search"
29
+ ],
30
+ "author": "Alex",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/yourusername/react-docs-mcp.git"
35
+ },
36
+ "homepage": "https://github.com/yourusername/react-docs-mcp#readme",
37
+ "bugs": {
38
+ "url": "https://github.com/yourusername/react-docs-mcp/issues"
39
+ },
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "README.md",
46
+ "LICENSE"
47
+ ],
48
+ "dependencies": {
49
+ "@modelcontextprotocol/sdk": "^1.20.0",
50
+ "@xenova/transformers": "^2.17.2",
51
+ "fast-glob": "^3.3.3",
52
+ "gray-matter": "^4.0.3",
53
+ "remark": "^15.0.1",
54
+ "simple-git": "^3.28.0",
55
+ "strip-markdown": "^6.0.0",
56
+ "zod": "^3.25.76"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^24.7.2",
60
+ "tsx": "^4.20.6",
61
+ "typescript": "^5.9.3"
62
+ }
63
+ }