@enokdev/springdocs-mcp 1.2.4 → 1.2.5
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/build/index.js +1 -1
- package/build/services/advanced-features-old.d.ts +45 -0
- package/build/services/advanced-features-old.d.ts.map +1 -0
- package/build/services/advanced-features-old.js +792 -0
- package/build/services/advanced-features-old.js.map +1 -0
- package/build/services/advanced-features.d.ts +16 -14
- package/build/services/advanced-features.d.ts.map +1 -1
- package/build/services/advanced-features.js +316 -474
- package/build/services/advanced-features.js.map +1 -1
- package/build/services/springboot-docs-optimized-old.d.ts +55 -0
- package/build/services/springboot-docs-optimized-old.d.ts.map +1 -0
- package/build/services/springboot-docs-optimized-old.js +588 -0
- package/build/services/springboot-docs-optimized-old.js.map +1 -0
- package/build/services/springboot-docs-optimized.d.ts +24 -20
- package/build/services/springboot-docs-optimized.d.ts.map +1 -1
- package/build/services/springboot-docs-optimized.js +170 -366
- package/build/services/springboot-docs-optimized.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,8 @@ import * as cheerio from 'cheerio';
|
|
|
3
3
|
import TurndownService from 'turndown';
|
|
4
4
|
import { CacheService } from './cache.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Clean Spring Boot Documentation Service - uses ONLY real Spring documentation APIs
|
|
7
|
+
* No mock data whatsoever - everything is fetched from actual Spring sources
|
|
7
8
|
*/
|
|
8
9
|
export class SpringBootDocsServiceOptimized {
|
|
9
10
|
baseUrl = 'https://docs.spring.io';
|
|
@@ -24,7 +25,7 @@ export class SpringBootDocsServiceOptimized {
|
|
|
24
25
|
setInterval(() => this.cache.cleanup(), 60 * 60 * 1000);
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
|
-
* Search Spring projects with caching and retry logic
|
|
28
|
+
* Search Spring projects with caching and retry logic - REAL API ONLY
|
|
28
29
|
*/
|
|
29
30
|
async searchSpringProjects(query, limit = 10) {
|
|
30
31
|
const cacheKey = `projects:${query}:${limit}`;
|
|
@@ -42,7 +43,8 @@ export class SpringBootDocsServiceOptimized {
|
|
|
42
43
|
const html = await response.text();
|
|
43
44
|
const $ = cheerio.load(html);
|
|
44
45
|
const projects = [];
|
|
45
|
-
|
|
46
|
+
// Parse actual Spring projects page
|
|
47
|
+
$('.project-list .project, .project-item, .card, .project-card, .project').each((_, element) => {
|
|
46
48
|
const $project = $(element);
|
|
47
49
|
const title = $project.find('h2, h3, .title, .project-title').first().text().trim();
|
|
48
50
|
const description = $project.find('p, .description, .summary').first().text().trim();
|
|
@@ -57,29 +59,17 @@ export class SpringBootDocsServiceOptimized {
|
|
|
57
59
|
});
|
|
58
60
|
}
|
|
59
61
|
});
|
|
60
|
-
// If few results, add popular projects
|
|
61
|
-
if (projects.length < 3) {
|
|
62
|
-
const popularProjects = this.getPopularSpringProjects()
|
|
63
|
-
.filter(project => project.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
64
|
-
project.description.toLowerCase().includes(query.toLowerCase()) ||
|
|
65
|
-
project.keywords.some((k) => k.toLowerCase().includes(query.toLowerCase())));
|
|
66
|
-
projects.push(...popularProjects);
|
|
67
|
-
}
|
|
68
62
|
const results = projects.slice(0, limit);
|
|
69
63
|
this.cache.set(cacheKey, results);
|
|
70
64
|
return results;
|
|
71
65
|
}
|
|
72
66
|
catch (error) {
|
|
73
67
|
console.error('Error searching Spring projects:', error);
|
|
74
|
-
|
|
75
|
-
return this.getPopularSpringProjects()
|
|
76
|
-
.filter(project => project.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
77
|
-
project.description.toLowerCase().includes(query.toLowerCase()))
|
|
78
|
-
.slice(0, limit);
|
|
68
|
+
return [];
|
|
79
69
|
}
|
|
80
70
|
}
|
|
81
71
|
/**
|
|
82
|
-
* Get Spring project details
|
|
72
|
+
* Get Spring project details - REAL API ONLY
|
|
83
73
|
*/
|
|
84
74
|
async getSpringProject(projectName) {
|
|
85
75
|
const cacheKey = `project:${projectName}`;
|
|
@@ -90,7 +80,7 @@ export class SpringBootDocsServiceOptimized {
|
|
|
90
80
|
}
|
|
91
81
|
console.error(`🔍 Fetching project: ${projectName}`);
|
|
92
82
|
try {
|
|
93
|
-
const url = `${this.springProjectsUrl}/${projectName}`;
|
|
83
|
+
const url = `${this.springProjectsUrl}/${projectName.toLowerCase().replace(/\s+/g, '-')}`;
|
|
94
84
|
const response = await this.fetchWithRetry(url);
|
|
95
85
|
if (!response.ok) {
|
|
96
86
|
throw new Error(`Project not found: ${projectName}`);
|
|
@@ -99,28 +89,20 @@ export class SpringBootDocsServiceOptimized {
|
|
|
99
89
|
const $ = cheerio.load(html);
|
|
100
90
|
const content = $('.project-overview, .content, main, .project-details').first();
|
|
101
91
|
if (content.length === 0) {
|
|
102
|
-
throw new Error('
|
|
92
|
+
throw new Error('No content found for project');
|
|
103
93
|
}
|
|
104
|
-
this.cleanHtml($, content);
|
|
105
94
|
const markdown = this.turndownService.turndown(content.html() || '');
|
|
106
|
-
const
|
|
107
|
-
const status = $('.status, .project-status').first().text().trim();
|
|
108
|
-
let result = `# Spring Project: ${projectName}\n\n`;
|
|
109
|
-
if (version)
|
|
110
|
-
result += `**Current Version:** ${version}\n\n`;
|
|
111
|
-
if (status)
|
|
112
|
-
result += `**Status:** ${status}\n\n`;
|
|
113
|
-
result += markdown;
|
|
95
|
+
const result = `# ${projectName}\n\n${markdown}`;
|
|
114
96
|
this.cache.setLongTerm(cacheKey, result);
|
|
115
97
|
return result;
|
|
116
98
|
}
|
|
117
99
|
catch (error) {
|
|
118
|
-
console.error(`Error
|
|
119
|
-
throw
|
|
100
|
+
console.error(`Error fetching project ${projectName}:`, error);
|
|
101
|
+
throw error;
|
|
120
102
|
}
|
|
121
103
|
}
|
|
122
104
|
/**
|
|
123
|
-
* Get all Spring guides
|
|
105
|
+
* Get all Spring guides - REAL API ONLY
|
|
124
106
|
*/
|
|
125
107
|
async getAllSpringGuides(category, limit = 20) {
|
|
126
108
|
const cacheKey = `guides:${category || 'all'}:${limit}`;
|
|
@@ -138,39 +120,36 @@ export class SpringBootDocsServiceOptimized {
|
|
|
138
120
|
const html = await response.text();
|
|
139
121
|
const $ = cheerio.load(html);
|
|
140
122
|
const guides = [];
|
|
141
|
-
|
|
123
|
+
// Parse actual Spring guides page
|
|
124
|
+
$('.guide-item, .card, .guide-card, .list-item, .guide').each((_, element) => {
|
|
142
125
|
const $guide = $(element);
|
|
143
|
-
const title = $guide.find('h2, h3, .title, .guide-title').first().text().trim();
|
|
126
|
+
const title = $guide.find('h2, h3, .title, .guide-title, a').first().text().trim();
|
|
144
127
|
const description = $guide.find('p, .description, .summary').first().text().trim();
|
|
128
|
+
const guideCategory = $guide.find('.category, .badge, .label').first().text().trim();
|
|
145
129
|
const link = $guide.find('a').first().attr('href');
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
130
|
+
if (title) {
|
|
131
|
+
if (!category || guideCategory.toLowerCase().includes(category.toLowerCase())) {
|
|
132
|
+
guides.push({
|
|
133
|
+
type: 'spring-guide',
|
|
134
|
+
title: title,
|
|
135
|
+
description: description || 'Spring guide',
|
|
136
|
+
category: guideCategory || 'General',
|
|
137
|
+
url: link?.startsWith('http') ? link : `https://spring.io${link}`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
156
140
|
}
|
|
157
141
|
});
|
|
158
|
-
// If few results, add popular guides
|
|
159
|
-
if (guides.length < 5) {
|
|
160
|
-
const popularGuides = this.getPopularSpringGuides();
|
|
161
|
-
guides.push(...popularGuides);
|
|
162
|
-
}
|
|
163
142
|
const results = guides.slice(0, limit);
|
|
164
143
|
this.cache.set(cacheKey, results);
|
|
165
144
|
return results;
|
|
166
145
|
}
|
|
167
146
|
catch (error) {
|
|
168
147
|
console.error('Error retrieving Spring guides:', error);
|
|
169
|
-
return
|
|
148
|
+
return [];
|
|
170
149
|
}
|
|
171
150
|
}
|
|
172
151
|
/**
|
|
173
|
-
* Get specific guide
|
|
152
|
+
* Get specific guide content - REAL API ONLY
|
|
174
153
|
*/
|
|
175
154
|
async getGuide(guideId) {
|
|
176
155
|
const cacheKey = `guide:${guideId}`;
|
|
@@ -180,62 +159,55 @@ export class SpringBootDocsServiceOptimized {
|
|
|
180
159
|
return cached;
|
|
181
160
|
}
|
|
182
161
|
console.error(`🔍 Fetching guide: ${guideId}`);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
console.error(`❌ ${source.name} failed: ${response.status}`);
|
|
204
|
-
}
|
|
162
|
+
// Try multiple sources for guides
|
|
163
|
+
const sources = [
|
|
164
|
+
{ name: 'Spring.io', url: `${this.springGuideUrl}/${guideId}/` },
|
|
165
|
+
{ name: 'GitHub', url: `https://github.com/spring-guides/${guideId}` },
|
|
166
|
+
{ name: 'Spring.io alt', url: `${this.springGuideUrl}/gs/${guideId}/` }
|
|
167
|
+
];
|
|
168
|
+
let content = '';
|
|
169
|
+
let sourceUrl = '';
|
|
170
|
+
for (const source of sources) {
|
|
171
|
+
try {
|
|
172
|
+
console.error(`Trying ${source.name}: ${source.url}`);
|
|
173
|
+
const response = await this.fetchWithRetry(source.url);
|
|
174
|
+
if (response.ok) {
|
|
175
|
+
content = await response.text();
|
|
176
|
+
sourceUrl = source.url;
|
|
177
|
+
console.error(`✅ Success with ${source.name} (${content.length} chars)`);
|
|
178
|
+
break;
|
|
205
179
|
}
|
|
206
|
-
|
|
207
|
-
console.error(`❌ ${source.name}
|
|
180
|
+
else {
|
|
181
|
+
console.error(`❌ ${source.name} failed: ${response.status}`);
|
|
208
182
|
}
|
|
209
183
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
let result = '';
|
|
214
|
-
// Process different content types
|
|
215
|
-
if (content.includes('<html') || content.includes('<!DOCTYPE')) {
|
|
216
|
-
// HTML content
|
|
217
|
-
result = this.processHtmlGuide(content, guideId, sourceUrl);
|
|
218
|
-
}
|
|
219
|
-
else if (content.includes('=') || content.includes(':toc:') || content.includes('== ')) {
|
|
220
|
-
// AsciiDoc content
|
|
221
|
-
result = this.processAsciiDocGuide(content, guideId, sourceUrl);
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
// Plain text
|
|
225
|
-
result = `# Spring Guide: ${guideId}\n\n*Source: ${sourceUrl}*\n\n\`\`\`\n${content}\n\`\`\``;
|
|
184
|
+
catch (error) {
|
|
185
|
+
console.error(`❌ ${source.name} error:`, error instanceof Error ? error.message : 'Unknown');
|
|
226
186
|
}
|
|
187
|
+
}
|
|
188
|
+
if (!content) {
|
|
189
|
+
throw new Error(`Guide not found: ${guideId}`);
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
const result = this.processHtmlGuide(content, guideId, sourceUrl);
|
|
227
193
|
this.cache.setLongTerm(cacheKey, result);
|
|
228
194
|
return result;
|
|
229
195
|
}
|
|
230
196
|
catch (error) {
|
|
231
|
-
console.error(
|
|
232
|
-
|
|
197
|
+
console.error('Error processing guide content:', error);
|
|
198
|
+
throw error;
|
|
233
199
|
}
|
|
234
200
|
}
|
|
235
201
|
/**
|
|
236
|
-
* Get reference documentation
|
|
202
|
+
* Get Spring reference documentation - alias for backward compatibility
|
|
237
203
|
*/
|
|
238
204
|
async getReference(section, subsection) {
|
|
205
|
+
return this.getSpringReference(section, subsection);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get Spring reference documentation - REAL API ONLY
|
|
209
|
+
*/
|
|
210
|
+
async getSpringReference(section, subsection) {
|
|
239
211
|
const cacheKey = `reference:${section}:${subsection || 'main'}`;
|
|
240
212
|
const cached = this.cache.get(cacheKey);
|
|
241
213
|
if (cached) {
|
|
@@ -244,13 +216,7 @@ export class SpringBootDocsServiceOptimized {
|
|
|
244
216
|
}
|
|
245
217
|
console.error(`🔍 Fetching reference: ${section}`);
|
|
246
218
|
try {
|
|
247
|
-
|
|
248
|
-
if (subsection) {
|
|
249
|
-
url += `${section}.html#${subsection}`;
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
url += `${section}.html`;
|
|
253
|
-
}
|
|
219
|
+
const url = `${this.baseUrl}/spring-boot/docs/${this.springBootVersion}/reference/html/${section}.html`;
|
|
254
220
|
const response = await this.fetchWithRetry(url);
|
|
255
221
|
if (!response.ok) {
|
|
256
222
|
throw new Error(`Reference section not found: ${section}`);
|
|
@@ -259,84 +225,148 @@ export class SpringBootDocsServiceOptimized {
|
|
|
259
225
|
const $ = cheerio.load(html);
|
|
260
226
|
const content = $('.content, .section, main').first();
|
|
261
227
|
if (content.length === 0) {
|
|
262
|
-
throw new Error('
|
|
228
|
+
throw new Error('No content found in reference documentation');
|
|
263
229
|
}
|
|
264
|
-
this.cleanHtml($, content);
|
|
265
230
|
const markdown = this.turndownService.turndown(content.html() || '');
|
|
266
231
|
const result = `# Spring Boot Reference: ${section}\n\n${markdown}`;
|
|
267
232
|
this.cache.setLongTerm(cacheKey, result);
|
|
268
233
|
return result;
|
|
269
234
|
}
|
|
270
235
|
catch (error) {
|
|
271
|
-
console.error(`Error
|
|
272
|
-
throw
|
|
236
|
+
console.error(`Error fetching reference ${section}:`, error);
|
|
237
|
+
throw error;
|
|
273
238
|
}
|
|
274
239
|
}
|
|
275
240
|
/**
|
|
276
|
-
* Search concepts
|
|
241
|
+
* Search Spring concepts - alias for backward compatibility
|
|
277
242
|
*/
|
|
278
243
|
async searchConcepts(concept, category) {
|
|
244
|
+
return this.searchSpringConcepts(concept, category);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Search Spring concepts - using real documentation
|
|
248
|
+
*/
|
|
249
|
+
async searchSpringConcepts(concept, category) {
|
|
279
250
|
const cacheKey = `concepts:${concept}:${category || 'all'}`;
|
|
280
251
|
const cached = this.cache.get(cacheKey);
|
|
281
|
-
if (cached)
|
|
282
|
-
console.error(`✅ Cache hit for concepts: ${concept}`);
|
|
252
|
+
if (cached)
|
|
283
253
|
return cached;
|
|
284
|
-
}
|
|
285
|
-
console.error(`🔍 Searching concepts: ${concept}`);
|
|
286
254
|
try {
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
255
|
+
// Search in Spring Boot reference documentation
|
|
256
|
+
const searchUrl = `${this.baseUrl}/spring-boot/docs/current/reference/html/`;
|
|
257
|
+
const response = await this.fetchWithRetry(searchUrl);
|
|
258
|
+
if (!response.ok) {
|
|
259
|
+
throw new Error('Unable to access Spring Boot documentation');
|
|
260
|
+
}
|
|
261
|
+
const html = await response.text();
|
|
262
|
+
const $ = cheerio.load(html);
|
|
263
|
+
// Look for concept in documentation
|
|
264
|
+
let conceptContent = '';
|
|
265
|
+
$('h1, h2, h3, h4').each((_, element) => {
|
|
266
|
+
const heading = $(element);
|
|
267
|
+
const headingText = heading.text().toLowerCase();
|
|
268
|
+
if (headingText.includes(concept.toLowerCase())) {
|
|
269
|
+
const section = heading.parent();
|
|
270
|
+
conceptContent += this.turndownService.turndown(section.html() || '');
|
|
297
271
|
}
|
|
272
|
+
});
|
|
273
|
+
if (!conceptContent) {
|
|
274
|
+
conceptContent = `# Spring Concept: ${concept}\n\nConcept not found in documentation. Try searching for more specific terms.`;
|
|
298
275
|
}
|
|
299
|
-
|
|
300
|
-
|
|
276
|
+
else {
|
|
277
|
+
conceptContent = `# Spring Concept: ${concept}\n\n${conceptContent}`;
|
|
278
|
+
}
|
|
279
|
+
this.cache.set(cacheKey, conceptContent);
|
|
280
|
+
return conceptContent;
|
|
301
281
|
}
|
|
302
282
|
catch (error) {
|
|
303
283
|
console.error('Error searching concepts:', error);
|
|
304
|
-
|
|
284
|
+
return `# Spring Concept Search Error\n\nUnable to search for concept "${concept}": ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
305
285
|
}
|
|
306
286
|
}
|
|
307
287
|
/**
|
|
308
|
-
*
|
|
288
|
+
* Search documentation with real API - alias for backward compatibility
|
|
309
289
|
*/
|
|
310
290
|
async searchDocumentation(query, docType = 'all', limit = 10) {
|
|
291
|
+
return this.searchSpringDocs(query, docType, limit);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Search documentation with real API
|
|
295
|
+
*/
|
|
296
|
+
async searchSpringDocs(query, docType = 'all', limit = 10) {
|
|
311
297
|
const cacheKey = `docs:${query}:${docType}:${limit}`;
|
|
312
298
|
const cached = this.cache.get(cacheKey);
|
|
313
|
-
if (cached)
|
|
314
|
-
console.error(`✅ Cache hit for documentation search: ${query}`);
|
|
299
|
+
if (cached)
|
|
315
300
|
return cached;
|
|
316
|
-
|
|
317
|
-
console.error(`🔍 Searching documentation: ${query}`);
|
|
301
|
+
const results = [];
|
|
318
302
|
try {
|
|
319
|
-
const searchPromises = [];
|
|
320
303
|
if (docType === 'all' || docType === 'guides') {
|
|
321
|
-
|
|
304
|
+
const guides = await this.getAllSpringGuides(undefined, limit);
|
|
305
|
+
const filteredGuides = guides.filter(guide => guide.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
306
|
+
guide.description.toLowerCase().includes(query.toLowerCase()));
|
|
307
|
+
results.push(...filteredGuides);
|
|
322
308
|
}
|
|
323
|
-
if (docType === 'all' || docType === '
|
|
324
|
-
|
|
309
|
+
if (docType === 'all' || docType === 'projects') {
|
|
310
|
+
const projects = await this.searchSpringProjects(query, limit);
|
|
311
|
+
results.push(...projects);
|
|
325
312
|
}
|
|
326
|
-
if (docType === 'all' || docType === '
|
|
327
|
-
|
|
313
|
+
if (docType === 'all' || docType === 'reference') {
|
|
314
|
+
// Search in reference documentation
|
|
315
|
+
const refResults = await this.searchInReference(query, limit);
|
|
316
|
+
results.push(...refResults);
|
|
328
317
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
this.cache.set(cacheKey, flatResults);
|
|
332
|
-
return flatResults;
|
|
318
|
+
this.cache.set(cacheKey, results);
|
|
319
|
+
return results.slice(0, limit);
|
|
333
320
|
}
|
|
334
321
|
catch (error) {
|
|
335
322
|
console.error('Error searching documentation:', error);
|
|
336
|
-
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async searchInReference(query, limit) {
|
|
327
|
+
try {
|
|
328
|
+
const response = await this.fetchWithRetry(`${this.baseUrl}/spring-boot/docs/current/reference/html/`);
|
|
329
|
+
if (!response.ok)
|
|
330
|
+
return [];
|
|
331
|
+
const html = await response.text();
|
|
332
|
+
const $ = cheerio.load(html);
|
|
333
|
+
const results = [];
|
|
334
|
+
$('nav a, .toc a, .nav-link').each((_, element) => {
|
|
335
|
+
const link = $(element);
|
|
336
|
+
const text = link.text().trim();
|
|
337
|
+
const href = link.attr('href');
|
|
338
|
+
if (text && href && text.toLowerCase().includes(query.toLowerCase())) {
|
|
339
|
+
results.push({
|
|
340
|
+
type: 'reference',
|
|
341
|
+
title: `Spring Boot: ${text}`,
|
|
342
|
+
url: href.startsWith('http') ? href : `${this.baseUrl}/spring-boot/docs/current/reference/html/${href}`,
|
|
343
|
+
description: `Reference documentation section`
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
return results.slice(0, limit);
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
console.error('Error searching reference:', error);
|
|
351
|
+
return [];
|
|
337
352
|
}
|
|
338
353
|
}
|
|
339
|
-
|
|
354
|
+
processHtmlGuide(content, guideId, sourceUrl) {
|
|
355
|
+
console.log('Processing HTML content...');
|
|
356
|
+
const $ = cheerio.load(content);
|
|
357
|
+
// Remove navigation and footer elements
|
|
358
|
+
$('nav, footer, .navbar, .sidebar, #js-sidebar').remove();
|
|
359
|
+
// Get main content
|
|
360
|
+
const mainContent = $('.content, .guide-content, main, .markdown-body, .guide-body, article').first();
|
|
361
|
+
if (mainContent.length === 0) {
|
|
362
|
+
console.log('No main content found, using body');
|
|
363
|
+
$('script, style').remove();
|
|
364
|
+
const bodyMarkdown = this.turndownService.turndown($('body').html() || '');
|
|
365
|
+
return `# Spring Guide: ${guideId}\n\n**Source:** ${sourceUrl}\n\n${bodyMarkdown}`;
|
|
366
|
+
}
|
|
367
|
+
const markdown = this.turndownService.turndown(mainContent.html() || '');
|
|
368
|
+
return `# Spring Guide: ${guideId}\n\n**Source:** ${sourceUrl}\n\n${markdown}`;
|
|
369
|
+
}
|
|
340
370
|
async fetchWithRetry(url, timeout = this.REQUEST_TIMEOUT, retries = this.MAX_RETRIES) {
|
|
341
371
|
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
342
372
|
const controller = new AbortController();
|
|
@@ -345,7 +375,7 @@ export class SpringBootDocsServiceOptimized {
|
|
|
345
375
|
const response = await fetch(url, {
|
|
346
376
|
signal: controller.signal,
|
|
347
377
|
headers: {
|
|
348
|
-
'User-Agent': 'Spring-Docs-MCP/1.2.
|
|
378
|
+
'User-Agent': 'Spring-Docs-MCP/1.2.4',
|
|
349
379
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
350
380
|
'Accept-Language': 'en-US,en;q=0.9'
|
|
351
381
|
}
|
|
@@ -363,231 +393,5 @@ export class SpringBootDocsServiceOptimized {
|
|
|
363
393
|
}
|
|
364
394
|
throw new Error('All retry attempts failed');
|
|
365
395
|
}
|
|
366
|
-
processHtmlGuide(content, guideId, sourceUrl) {
|
|
367
|
-
console.log('Processing HTML content...');
|
|
368
|
-
const $ = cheerio.load(content);
|
|
369
|
-
let mainContent = $('.guide-content, .content, main, .container .row .col-md-8, .markdown-body, article').first();
|
|
370
|
-
if (mainContent.length === 0) {
|
|
371
|
-
const bodyContent = $('.guide-body, .guide-content, .content').first();
|
|
372
|
-
if (bodyContent.length > 0) {
|
|
373
|
-
mainContent = bodyContent;
|
|
374
|
-
}
|
|
375
|
-
else {
|
|
376
|
-
mainContent = $('body');
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
if (mainContent.length > 0) {
|
|
380
|
-
this.cleanHtml($, mainContent);
|
|
381
|
-
const markdown = this.turndownService.turndown(mainContent.html() || '');
|
|
382
|
-
return `# Spring Guide: ${guideId}\n\n*Source: ${sourceUrl}*\n\n${markdown}`;
|
|
383
|
-
}
|
|
384
|
-
return this.generateGuideNotFoundMessage(guideId);
|
|
385
|
-
}
|
|
386
|
-
processAsciiDocGuide(content, guideId, sourceUrl) {
|
|
387
|
-
console.log('Processing AsciiDoc content...');
|
|
388
|
-
let processedContent = content
|
|
389
|
-
.replace(/^= (.+)$/gm, '# $1')
|
|
390
|
-
.replace(/^== (.+)$/gm, '## $1')
|
|
391
|
-
.replace(/^=== (.+)$/gm, '### $1')
|
|
392
|
-
.replace(/^==== (.+)$/gm, '#### $1')
|
|
393
|
-
.replace(/^\[source,(\w+)\]$/gm, '')
|
|
394
|
-
.replace(/^----$/gm, '```')
|
|
395
|
-
.replace(/^\+\+\+\+$/gm, '```');
|
|
396
|
-
return `# Spring Guide: ${guideId}\n\n*Source: ${sourceUrl}*\n\n${processedContent}`;
|
|
397
|
-
}
|
|
398
|
-
generateGuideNotFoundMessage(guideId) {
|
|
399
|
-
return `# Spring Guide: ${guideId}
|
|
400
|
-
|
|
401
|
-
❌ **Guide not found**
|
|
402
|
-
|
|
403
|
-
The guide "${guideId}" could not be retrieved from any source.
|
|
404
|
-
|
|
405
|
-
## 📚 Popular guides available:
|
|
406
|
-
- \`gs-rest-service\` - Building a RESTful Web Service
|
|
407
|
-
- \`gs-spring-boot\` - Building an Application with Spring Boot
|
|
408
|
-
- \`gs-accessing-data-jpa\` - Accessing Data with JPA
|
|
409
|
-
- \`gs-securing-web\` - Securing a Web Application
|
|
410
|
-
- \`gs-actuator-service\` - Building a RESTful Web Service with Spring Boot Actuator
|
|
411
|
-
- \`gs-testing-web\` - Testing the Web Layer
|
|
412
|
-
|
|
413
|
-
## 🔧 Alternative tools:
|
|
414
|
-
1. \`get_all_spring_guides\` - See all available guides
|
|
415
|
-
2. \`search_spring_docs\` - Search documentation
|
|
416
|
-
|
|
417
|
-
## 💡 Identifier format:
|
|
418
|
-
The identifier should be in format: \`gs-guide-name\``;
|
|
419
|
-
}
|
|
420
|
-
generateGuideErrorMessage(guideId, error) {
|
|
421
|
-
return `# Spring Guide: ${guideId}
|
|
422
|
-
|
|
423
|
-
❌ **Error retrieving guide**
|
|
424
|
-
|
|
425
|
-
The guide "${guideId}" could not be retrieved.
|
|
426
|
-
|
|
427
|
-
## 🔧 Possible solutions:
|
|
428
|
-
1. Check the guide identifier (format: \`gs-guide-name\`)
|
|
429
|
-
2. Use \`get_all_spring_guides\` to see all available guides
|
|
430
|
-
3. Use \`search_spring_docs\` to search documentation
|
|
431
|
-
|
|
432
|
-
## 📚 Popular guides:
|
|
433
|
-
- \`gs-rest-service\` - Creating a RESTful Web Service
|
|
434
|
-
- \`gs-spring-boot\` - Building an Application with Spring Boot
|
|
435
|
-
- \`gs-accessing-data-jpa\` - Accessing Data with JPA
|
|
436
|
-
- \`gs-securing-web\` - Securing a Web Application
|
|
437
|
-
|
|
438
|
-
**Technical error:** ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
439
|
-
}
|
|
440
|
-
cleanHtml($, content) {
|
|
441
|
-
content.find('script, style, nav, .sidebar, .toc, .feedback').remove();
|
|
442
|
-
content.find('*').each((index, element) => {
|
|
443
|
-
$(element).removeAttr('class').removeAttr('style').removeAttr('id');
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
// Static data methods (unchanged from original)
|
|
447
|
-
getPopularSpringProjects() {
|
|
448
|
-
return [
|
|
449
|
-
{
|
|
450
|
-
type: 'spring-project',
|
|
451
|
-
title: 'Spring Boot',
|
|
452
|
-
description: 'Create stand-alone, production-grade Spring applications easily',
|
|
453
|
-
url: 'https://spring.io/projects/spring-boot',
|
|
454
|
-
keywords: ['boot', 'microservices', 'web', 'standalone'],
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
type: 'spring-project',
|
|
458
|
-
title: 'Spring Framework',
|
|
459
|
-
description: 'The most popular Java framework for building enterprise applications',
|
|
460
|
-
url: 'https://spring.io/projects/spring-framework',
|
|
461
|
-
keywords: ['framework', 'core', 'mvc', 'ioc', 'dependency injection'],
|
|
462
|
-
},
|
|
463
|
-
{
|
|
464
|
-
type: 'spring-project',
|
|
465
|
-
title: 'Spring Data',
|
|
466
|
-
description: 'Provides a familiar programming model for data access',
|
|
467
|
-
url: 'https://spring.io/projects/spring-data',
|
|
468
|
-
keywords: ['data', 'jpa', 'mongodb', 'redis', 'repository'],
|
|
469
|
-
},
|
|
470
|
-
{
|
|
471
|
-
type: 'spring-project',
|
|
472
|
-
title: 'Spring Security',
|
|
473
|
-
description: 'Powerful and highly customizable security framework',
|
|
474
|
-
url: 'https://spring.io/projects/spring-security',
|
|
475
|
-
keywords: ['security', 'authentication', 'authorization', 'oauth'],
|
|
476
|
-
},
|
|
477
|
-
{
|
|
478
|
-
type: 'spring-project',
|
|
479
|
-
title: 'Spring Cloud',
|
|
480
|
-
description: 'Tools for rapidly building distributed system patterns',
|
|
481
|
-
url: 'https://spring.io/projects/spring-cloud',
|
|
482
|
-
keywords: ['cloud', 'microservices', 'distributed', 'netflix'],
|
|
483
|
-
},
|
|
484
|
-
];
|
|
485
|
-
}
|
|
486
|
-
getPopularSpringGuides() {
|
|
487
|
-
return [
|
|
488
|
-
{
|
|
489
|
-
type: 'spring-guide',
|
|
490
|
-
title: 'Building a RESTful Web Service',
|
|
491
|
-
description: 'Learn how to create a RESTful web service with Spring Boot',
|
|
492
|
-
category: 'Web',
|
|
493
|
-
url: 'https://spring.io/guides/gs/rest-service/',
|
|
494
|
-
id: 'gs-rest-service',
|
|
495
|
-
},
|
|
496
|
-
{
|
|
497
|
-
type: 'spring-guide',
|
|
498
|
-
title: 'Building an Application with Spring Boot',
|
|
499
|
-
description: 'Learn how to build an application with minimal configuration',
|
|
500
|
-
category: 'Getting Started',
|
|
501
|
-
url: 'https://spring.io/guides/gs/spring-boot/',
|
|
502
|
-
id: 'gs-spring-boot',
|
|
503
|
-
},
|
|
504
|
-
{
|
|
505
|
-
type: 'spring-guide',
|
|
506
|
-
title: 'Accessing Data with JPA',
|
|
507
|
-
description: 'Learn how to work with JPA data persistence',
|
|
508
|
-
category: 'Data',
|
|
509
|
-
url: 'https://spring.io/guides/gs/accessing-data-jpa/',
|
|
510
|
-
id: 'gs-accessing-data-jpa',
|
|
511
|
-
},
|
|
512
|
-
];
|
|
513
|
-
}
|
|
514
|
-
async searchGuides(query, limit) {
|
|
515
|
-
const guides = [
|
|
516
|
-
{ id: 'gs-rest-service', title: 'Building a RESTful Web Service', description: 'Learn how to create a RESTful web service with Spring Boot' },
|
|
517
|
-
{ id: 'gs-spring-boot', title: 'Building an Application with Spring Boot', description: 'Learn how to build an application with minimal configuration' },
|
|
518
|
-
{ id: 'gs-accessing-data-jpa', title: 'Accessing Data with JPA', description: 'Learn how to work with JPA data persistence' },
|
|
519
|
-
];
|
|
520
|
-
return guides
|
|
521
|
-
.filter(guide => guide.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
522
|
-
guide.description.toLowerCase().includes(query.toLowerCase()))
|
|
523
|
-
.slice(0, limit)
|
|
524
|
-
.map(guide => ({
|
|
525
|
-
type: 'guide',
|
|
526
|
-
title: guide.title,
|
|
527
|
-
url: `${this.springGuideUrl}/${guide.id}/`,
|
|
528
|
-
description: guide.description,
|
|
529
|
-
id: guide.id,
|
|
530
|
-
}));
|
|
531
|
-
}
|
|
532
|
-
async searchReference(query, limit) {
|
|
533
|
-
const sections = [
|
|
534
|
-
{ name: 'web', title: 'Web Applications', description: 'Building web applications with Spring Boot' },
|
|
535
|
-
{ name: 'data', title: 'Data Access', description: 'Working with databases and data sources' },
|
|
536
|
-
{ name: 'security', title: 'Security', description: 'Securing Spring Boot applications' },
|
|
537
|
-
];
|
|
538
|
-
return sections
|
|
539
|
-
.filter(section => section.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
540
|
-
section.description.toLowerCase().includes(query.toLowerCase()))
|
|
541
|
-
.slice(0, limit)
|
|
542
|
-
.map(section => ({
|
|
543
|
-
type: 'reference',
|
|
544
|
-
title: section.title,
|
|
545
|
-
url: `${this.baseUrl}/spring-boot/docs/${this.springBootVersion}/reference/html/${section.name}.html`,
|
|
546
|
-
description: section.description,
|
|
547
|
-
section: section.name,
|
|
548
|
-
}));
|
|
549
|
-
}
|
|
550
|
-
async searchAPI(query, limit) {
|
|
551
|
-
const apiSections = [
|
|
552
|
-
{ name: 'Configuration Properties', description: 'Spring Boot configuration properties reference' },
|
|
553
|
-
{ name: 'Auto-configuration Classes', description: 'List of auto-configuration classes' },
|
|
554
|
-
];
|
|
555
|
-
return apiSections
|
|
556
|
-
.filter(section => section.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
557
|
-
section.description.toLowerCase().includes(query.toLowerCase()))
|
|
558
|
-
.slice(0, limit)
|
|
559
|
-
.map(section => ({
|
|
560
|
-
type: 'api',
|
|
561
|
-
title: section.name,
|
|
562
|
-
url: `${this.baseUrl}/spring-boot/docs/${this.springBootVersion}/api/`,
|
|
563
|
-
description: section.description,
|
|
564
|
-
}));
|
|
565
|
-
}
|
|
566
|
-
getSpringBootConcepts() {
|
|
567
|
-
return {
|
|
568
|
-
core: [
|
|
569
|
-
{
|
|
570
|
-
name: 'Auto-configuration',
|
|
571
|
-
description: 'Mechanism that automatically configures Spring Boot based on present dependencies',
|
|
572
|
-
keywords: ['auto-configuration', '@EnableAutoConfiguration', 'conditional'],
|
|
573
|
-
examples: ['@SpringBootApplication', '@Conditional', 'spring.factories'],
|
|
574
|
-
},
|
|
575
|
-
{
|
|
576
|
-
name: 'Profiles',
|
|
577
|
-
description: 'Allows separating parts of application configuration for different environments',
|
|
578
|
-
keywords: ['profiles', '@Profile', 'application.properties'],
|
|
579
|
-
examples: ['application-dev.properties', '@Profile("prod")', 'spring.profiles.active'],
|
|
580
|
-
},
|
|
581
|
-
],
|
|
582
|
-
web: [
|
|
583
|
-
{
|
|
584
|
-
name: 'Spring MVC',
|
|
585
|
-
description: 'Integrated web framework for building web applications and REST APIs',
|
|
586
|
-
keywords: ['mvc', '@Controller', '@RestController', 'web'],
|
|
587
|
-
examples: ['@GetMapping', '@PostMapping', '@RequestMapping'],
|
|
588
|
-
},
|
|
589
|
-
],
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
396
|
}
|
|
593
397
|
//# sourceMappingURL=springboot-docs-optimized.js.map
|