@enokdev/springdocs-mcp 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/publish.sh ADDED
@@ -0,0 +1,130 @@
1
+ #!/bin/bash
2
+
3
+ # Script de publication automatique pour @enokdev/springdocs-mcp
4
+ # Auteur: EnokDev (https://github.com/tky0065)
5
+
6
+ set -e
7
+
8
+ echo "🌱 Publication de Spring Documentation MCP Server"
9
+ echo "=================================================="
10
+ echo ""
11
+
12
+ # Vérifications préliminaires
13
+ echo "🔍 Vérifications pré-publication..."
14
+
15
+ # Vérifier que nous sommes connectés à npm
16
+ NPM_USER=$(npm whoami 2>/dev/null || echo "")
17
+ if [ "$NPM_USER" != "enokdev" ]; then
18
+ echo "❌ Erreur: Vous devez être connecté à npm en tant qu'enokdev"
19
+ echo "Exécutez: npm login"
20
+ exit 1
21
+ fi
22
+
23
+ echo "✅ Connecté à npm en tant que: $NPM_USER"
24
+
25
+ # Vérifier que le build fonctionne
26
+ echo "🔨 Compilation du projet..."
27
+ npm run build
28
+
29
+ # Exécuter les tests
30
+ echo "🧪 Exécution des tests..."
31
+ npm test
32
+
33
+ # Vérifier la configuration Git
34
+ if ! git remote get-url origin | grep -q "tky0065/springdocs-mcp"; then
35
+ echo "⚠️ Attention: L'origin Git ne semble pas pointer vers tky0065/springdocs-mcp"
36
+ echo "Origin actuel: $(git remote get-url origin)"
37
+ read -p "Continuer quand même? (y/N): " -n 1 -r
38
+ echo
39
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
40
+ exit 1
41
+ fi
42
+ fi
43
+
44
+ # Afficher la version actuelle
45
+ CURRENT_VERSION=$(node -p "require('./package.json').version")
46
+ echo "📦 Version actuelle: $CURRENT_VERSION"
47
+
48
+ # Demander le type de mise à jour
49
+ echo ""
50
+ echo "Type de mise à jour:"
51
+ echo "1) patch (1.1.0 -> 1.1.1) - Corrections de bugs"
52
+ echo "2) minor (1.1.0 -> 1.2.0) - Nouvelles fonctionnalités"
53
+ echo "3) major (1.1.0 -> 2.0.0) - Changements incompatibles"
54
+ echo "4) Garder la version actuelle"
55
+
56
+ read -p "Votre choix (1-4): " -n 1 -r
57
+ echo
58
+
59
+ case $REPLY in
60
+ 1) VERSION_TYPE="patch" ;;
61
+ 2) VERSION_TYPE="minor" ;;
62
+ 3) VERSION_TYPE="major" ;;
63
+ 4) VERSION_TYPE="" ;;
64
+ *) echo "❌ Choix invalide"; exit 1 ;;
65
+ esac
66
+
67
+ # Mise à jour de version si nécessaire
68
+ if [ ! -z "$VERSION_TYPE" ]; then
69
+ echo "📝 Mise à jour de version ($VERSION_TYPE)..."
70
+ npm version $VERSION_TYPE --no-git-tag-version
71
+ NEW_VERSION=$(node -p "require('./package.json').version")
72
+ echo "✅ Nouvelle version: $NEW_VERSION"
73
+ else
74
+ NEW_VERSION=$CURRENT_VERSION
75
+ echo "📌 Conservation de la version: $NEW_VERSION"
76
+ fi
77
+
78
+ # Test final du package
79
+ echo "🔍 Test final du package..."
80
+ npm pack --dry-run > /dev/null
81
+
82
+ # Confirmation finale
83
+ echo ""
84
+ echo "🚀 Prêt pour la publication:"
85
+ echo " Package: @enokdev/springdocs-mcp@$NEW_VERSION"
86
+ echo " Registry: https://www.npmjs.com/package/@enokdev/springdocs-mcp"
87
+ echo " Repository: https://github.com/tky0065/springdocs-mcp"
88
+ echo ""
89
+
90
+ read -p "Confirmer la publication? (y/N): " -n 1 -r
91
+ echo
92
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
93
+ echo "❌ Publication annulée"
94
+ exit 1
95
+ fi
96
+
97
+ # Git commit et push
98
+ if [ ! -z "$VERSION_TYPE" ]; then
99
+ echo "📤 Envoi vers GitHub..."
100
+ git add .
101
+ git commit -m "Release v$NEW_VERSION"
102
+ git tag "v$NEW_VERSION"
103
+ git push origin main --tags
104
+ echo "✅ Code envoyé vers GitHub avec tag v$NEW_VERSION"
105
+ fi
106
+
107
+ # Publication npm
108
+ echo "📦 Publication sur npm..."
109
+ npm publish --access public
110
+
111
+ echo ""
112
+ echo "🎉 Publication réussie!"
113
+ echo ""
114
+ echo "📋 Informations de publication:"
115
+ echo " 📦 Package: https://www.npmjs.com/package/@enokdev/springdocs-mcp"
116
+ echo " 🐙 GitHub: https://github.com/tky0065/springdocs-mcp"
117
+ echo " 📊 Stats: https://npm-stat.com/charts.html?package=@enokdev/springdocs-mcp"
118
+ echo ""
119
+ echo "📥 Installation pour les utilisateurs:"
120
+ echo " npm install -g @enokdev/springdocs-mcp"
121
+ echo ""
122
+ echo "🔧 Configuration Claude Desktop:"
123
+ echo ' {"mcpServers": {"spring-docs": {"command": "springdocs-mcp"}}}'
124
+ echo ""
125
+ echo "🌟 N'oubliez pas de:"
126
+ echo " • Annoncer sur les réseaux sociaux"
127
+ echo " • Mettre à jour la documentation si nécessaire"
128
+ echo " • Répondre aux issues GitHub"
129
+ echo ""
130
+ echo "Merci d'avoir publié un outil utile pour la communauté Spring! 🌱"
package/src/index.ts ADDED
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { CallToolRequestSchema, ListToolsRequestSchema, CallToolRequest, ListToolsRequest } from "@modelcontextprotocol/sdk/types.js";
6
+ import { SpringBootDocsService } from "./services/springboot-docs.js";
7
+ import { ToolDefinitions } from "./tools/index.js";
8
+
9
+ /**
10
+ * Serveur MCP pour accéder à la documentation Spring Boot
11
+ */
12
+ class SpringBootMCPServer {
13
+ private server: Server;
14
+ private docsService: SpringBootDocsService;
15
+
16
+ constructor() {
17
+ this.server = new Server(
18
+ {
19
+ name: "springboot-mcp-server",
20
+ version: "1.1.0",
21
+ },
22
+ {
23
+ capabilities: {
24
+ tools: {},
25
+ },
26
+ }
27
+ );
28
+
29
+ this.docsService = new SpringBootDocsService();
30
+ this.setupToolHandlers();
31
+ }
32
+
33
+ private setupToolHandlers() {
34
+ // Gestionnaire pour lister les outils disponibles
35
+ this.server.setRequestHandler(ListToolsRequestSchema, async (request: ListToolsRequest) => {
36
+ return {
37
+ tools: ToolDefinitions.getToolList(),
38
+ };
39
+ });
40
+
41
+ // Gestionnaire pour exécuter un outil
42
+ this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
43
+ const { name, arguments: args } = request.params;
44
+
45
+ try {
46
+ switch (name) {
47
+ case "search_spring_docs":
48
+ return await this.handleSearchDocs(args);
49
+
50
+ case "search_spring_projects":
51
+ return await this.handleSearchProjects(args);
52
+
53
+ case "get_spring_project":
54
+ return await this.handleGetProject(args);
55
+
56
+ case "get_all_spring_guides":
57
+ return await this.handleGetAllGuides(args);
58
+
59
+ case "get_spring_guide":
60
+ return await this.handleGetGuide(args);
61
+
62
+ case "get_spring_reference":
63
+ return await this.handleGetReference(args);
64
+
65
+ case "search_spring_concepts":
66
+ return await this.handleSearchConcepts(args);
67
+
68
+ default:
69
+ throw new Error(`Outil inconnu: ${name}`);
70
+ }
71
+ } catch (error) {
72
+ const errorMessage = error instanceof Error ? error.message : "Erreur inconnue";
73
+ return {
74
+ content: [
75
+ {
76
+ type: "text",
77
+ text: `Erreur lors de l'exécution de l'outil ${name}: ${errorMessage}`,
78
+ },
79
+ ],
80
+ isError: true,
81
+ };
82
+ }
83
+ });
84
+ }
85
+
86
+ private async handleSearchProjects(args: any) {
87
+ const { query, limit = 10 } = args;
88
+
89
+ if (!query || typeof query !== "string") {
90
+ throw new Error("Le paramètre 'query' est requis et doit être une chaîne de caractères");
91
+ }
92
+
93
+ const results = await this.docsService.searchSpringProjects(query, limit);
94
+
95
+ return {
96
+ content: [
97
+ {
98
+ type: "text",
99
+ text: `Projets Spring trouvés pour "${query}":\n\n${this.formatSearchResults(results)}`,
100
+ },
101
+ ],
102
+ };
103
+ }
104
+
105
+ private async handleGetProject(args: any) {
106
+ const { projectName } = args;
107
+
108
+ if (!projectName || typeof projectName !== "string") {
109
+ throw new Error("Le paramètre 'projectName' est requis et doit être une chaîne de caractères");
110
+ }
111
+
112
+ const project = await this.docsService.getSpringProject(projectName);
113
+
114
+ return {
115
+ content: [
116
+ {
117
+ type: "text",
118
+ text: project,
119
+ },
120
+ ],
121
+ };
122
+ }
123
+
124
+ private async handleGetAllGuides(args: any) {
125
+ const { category, limit = 20 } = args;
126
+
127
+ const results = await this.docsService.getAllSpringGuides(category, limit);
128
+
129
+ return {
130
+ content: [
131
+ {
132
+ type: "text",
133
+ text: `Guides Spring disponibles${category ? ` dans la catégorie "${category}"` : ""}:\n\n${this.formatSearchResults(results)}`,
134
+ },
135
+ ],
136
+ };
137
+ }
138
+
139
+ private async handleSearchDocs(args: any) {
140
+ const { query, docType = "all", limit = 10 } = args;
141
+
142
+ if (!query || typeof query !== "string") {
143
+ throw new Error("Le paramètre 'query' est requis et doit être une chaîne de caractères");
144
+ }
145
+
146
+ const results = await this.docsService.searchDocumentation(query, docType, limit);
147
+
148
+ return {
149
+ content: [
150
+ {
151
+ type: "text",
152
+ text: `Résultats de recherche pour "${query}":\n\n${this.formatSearchResults(results)}`,
153
+ },
154
+ ],
155
+ };
156
+ }
157
+
158
+ private async handleGetGuide(args: any) {
159
+ const { guideId } = args;
160
+
161
+ if (!guideId || typeof guideId !== "string") {
162
+ throw new Error("Le paramètre 'guideId' est requis et doit être une chaîne de caractères");
163
+ }
164
+
165
+ const guide = await this.docsService.getGuide(guideId);
166
+
167
+ return {
168
+ content: [
169
+ {
170
+ type: "text",
171
+ text: guide,
172
+ },
173
+ ],
174
+ };
175
+ }
176
+
177
+ private async handleGetReference(args: any) {
178
+ const { section, subsection } = args;
179
+
180
+ if (!section || typeof section !== "string") {
181
+ throw new Error("Le paramètre 'section' est requis et doit être une chaîne de caractères");
182
+ }
183
+
184
+ const reference = await this.docsService.getReference(section, subsection);
185
+
186
+ return {
187
+ content: [
188
+ {
189
+ type: "text",
190
+ text: reference,
191
+ },
192
+ ],
193
+ };
194
+ }
195
+
196
+ private async handleSearchConcepts(args: any) {
197
+ const { concept, category } = args;
198
+
199
+ if (!concept || typeof concept !== "string") {
200
+ throw new Error("Le paramètre 'concept' est requis et doit être une chaîne de caractères");
201
+ }
202
+
203
+ const results = await this.docsService.searchConcepts(concept, category);
204
+
205
+ return {
206
+ content: [
207
+ {
208
+ type: "text",
209
+ text: this.formatConceptResults(results),
210
+ },
211
+ ],
212
+ };
213
+ }
214
+
215
+ private formatSearchResults(results: any[]): string {
216
+ if (results.length === 0) {
217
+ return "Aucun résultat trouvé.";
218
+ }
219
+
220
+ return results
221
+ .map((result, index) => {
222
+ return `${index + 1}. **${result.title}**
223
+ Type: ${result.type}
224
+ URL: ${result.url}
225
+ Description: ${result.description || "Aucune description disponible"}
226
+
227
+ `;
228
+ })
229
+ .join("\n");
230
+ }
231
+
232
+ private formatConceptResults(results: any): string {
233
+ if (!results || Object.keys(results).length === 0) {
234
+ return "Aucun concept trouvé.";
235
+ }
236
+
237
+ let formatted = `# Concepts Spring Boot\n\n`;
238
+
239
+ for (const [category, concepts] of Object.entries(results)) {
240
+ formatted += `## ${category}\n\n`;
241
+
242
+ if (Array.isArray(concepts)) {
243
+ concepts.forEach((concept: any) => {
244
+ formatted += `- **${concept.name}**: ${concept.description}\n`;
245
+ if (concept.examples && concept.examples.length > 0) {
246
+ formatted += ` Exemples: ${concept.examples.join(", ")}\n`;
247
+ }
248
+ });
249
+ }
250
+
251
+ formatted += "\n";
252
+ }
253
+
254
+ return formatted;
255
+ }
256
+
257
+ async run() {
258
+ const transport = new StdioServerTransport();
259
+ await this.server.connect(transport);
260
+ console.error("Serveur MCP Spring Boot démarré sur stdio");
261
+ }
262
+ }
263
+
264
+ // Point d'entrée principal
265
+ async function main() {
266
+ const server = new SpringBootMCPServer();
267
+ await server.run();
268
+ }
269
+
270
+ // Gestion des erreurs
271
+ process.on("SIGINT", async () => {
272
+ console.error("Arrêt du serveur...");
273
+ process.exit(0);
274
+ });
275
+
276
+ process.on("unhandledRejection", (reason: any, promise: Promise<any>) => {
277
+ console.error("Rejet de promesse non géré:", reason);
278
+ process.exit(1);
279
+ });
280
+
281
+ // Démarrage du serveur
282
+ if (import.meta.url === `file://${process.argv[1]}`) {
283
+ main().catch((error) => {
284
+ console.error("Erreur fatale:", error);
285
+ process.exit(1);
286
+ });
287
+ }