@enokdev/springdocs-mcp 1.2.7 → 1.4.1

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.
Files changed (112) hide show
  1. package/README.md +108 -40
  2. package/build/config.d.ts +9 -0
  3. package/build/config.d.ts.map +1 -0
  4. package/build/config.js +32 -0
  5. package/build/config.js.map +1 -0
  6. package/build/format.d.ts +6 -0
  7. package/build/format.d.ts.map +1 -0
  8. package/build/format.js +23 -0
  9. package/build/format.js.map +1 -0
  10. package/build/http-server.d.ts +15 -0
  11. package/build/http-server.d.ts.map +1 -0
  12. package/build/http-server.js +131 -0
  13. package/build/http-server.js.map +1 -0
  14. package/build/index.js +184 -69
  15. package/build/index.js.map +1 -1
  16. package/build/prompts.d.ts +23 -0
  17. package/build/prompts.d.ts.map +1 -0
  18. package/build/prompts.js +112 -0
  19. package/build/prompts.js.map +1 -0
  20. package/build/resources.d.ts +31 -0
  21. package/build/resources.d.ts.map +1 -0
  22. package/build/resources.js +66 -0
  23. package/build/resources.js.map +1 -0
  24. package/build/services/advanced-features.d.ts +19 -9
  25. package/build/services/advanced-features.d.ts.map +1 -1
  26. package/build/services/advanced-features.js +266 -191
  27. package/build/services/advanced-features.js.map +1 -1
  28. package/build/services/boot-wiki.d.ts +25 -0
  29. package/build/services/boot-wiki.d.ts.map +1 -0
  30. package/build/services/boot-wiki.js +103 -0
  31. package/build/services/boot-wiki.js.map +1 -0
  32. package/build/services/cache.d.ts +18 -3
  33. package/build/services/cache.d.ts.map +1 -1
  34. package/build/services/cache.js +84 -3
  35. package/build/services/cache.js.map +1 -1
  36. package/build/services/dependency-finder.d.ts +46 -0
  37. package/build/services/dependency-finder.d.ts.map +1 -0
  38. package/build/services/dependency-finder.js +181 -0
  39. package/build/services/dependency-finder.js.map +1 -0
  40. package/build/services/diagnosis.d.ts +46 -0
  41. package/build/services/diagnosis.d.ts.map +1 -0
  42. package/build/services/diagnosis.js +481 -0
  43. package/build/services/diagnosis.js.map +1 -0
  44. package/build/services/guides-list.d.ts +16 -0
  45. package/build/services/guides-list.d.ts.map +1 -0
  46. package/build/services/guides-list.js +45 -0
  47. package/build/services/guides-list.js.map +1 -0
  48. package/build/services/http.d.ts +21 -0
  49. package/build/services/http.d.ts.map +1 -0
  50. package/build/services/http.js +168 -0
  51. package/build/services/http.js.map +1 -0
  52. package/build/services/initializr.d.ts +41 -0
  53. package/build/services/initializr.d.ts.map +1 -0
  54. package/build/services/initializr.js +135 -0
  55. package/build/services/initializr.js.map +1 -0
  56. package/build/services/markdown.d.ts +56 -0
  57. package/build/services/markdown.d.ts.map +1 -0
  58. package/build/services/markdown.js +146 -0
  59. package/build/services/markdown.js.map +1 -0
  60. package/build/services/migration-sources.d.ts +13 -0
  61. package/build/services/migration-sources.d.ts.map +1 -0
  62. package/build/services/migration-sources.js +48 -0
  63. package/build/services/migration-sources.js.map +1 -0
  64. package/build/services/release-notes.d.ts +23 -0
  65. package/build/services/release-notes.d.ts.map +1 -0
  66. package/build/services/release-notes.js +94 -0
  67. package/build/services/release-notes.js.map +1 -0
  68. package/build/services/search-index.d.ts +33 -0
  69. package/build/services/search-index.d.ts.map +1 -0
  70. package/build/services/search-index.js +147 -0
  71. package/build/services/search-index.js.map +1 -0
  72. package/build/services/spring-projects-config.d.ts +98 -0
  73. package/build/services/spring-projects-config.d.ts.map +1 -0
  74. package/build/services/spring-projects-config.js +397 -0
  75. package/build/services/spring-projects-config.js.map +1 -0
  76. package/build/services/springboot-docs-optimized.d.ts +72 -21
  77. package/build/services/springboot-docs-optimized.d.ts.map +1 -1
  78. package/build/services/springboot-docs-optimized.js +354 -208
  79. package/build/services/springboot-docs-optimized.js.map +1 -1
  80. package/build/services/url.d.ts +15 -0
  81. package/build/services/url.d.ts.map +1 -0
  82. package/build/services/url.js +40 -0
  83. package/build/services/url.js.map +1 -0
  84. package/build/tools/index.d.ts +393 -2
  85. package/build/tools/index.d.ts.map +1 -1
  86. package/build/tools/index.js +188 -15
  87. package/build/tools/index.js.map +1 -1
  88. package/build/validation.d.ts +7 -0
  89. package/build/validation.d.ts.map +1 -0
  90. package/build/validation.js +50 -0
  91. package/build/validation.js.map +1 -0
  92. package/build/version.d.ts +3 -0
  93. package/build/version.d.ts.map +1 -0
  94. package/build/version.js +6 -0
  95. package/build/version.js.map +1 -0
  96. package/package.json +9 -7
  97. package/build/debug.d.ts +0 -3
  98. package/build/debug.d.ts.map +0 -1
  99. package/build/debug.js +0 -292
  100. package/build/debug.js.map +0 -1
  101. package/build/services/advanced-features-old.d.ts +0 -45
  102. package/build/services/advanced-features-old.d.ts.map +0 -1
  103. package/build/services/advanced-features-old.js +0 -792
  104. package/build/services/advanced-features-old.js.map +0 -1
  105. package/build/services/springboot-docs-optimized-old.d.ts +0 -55
  106. package/build/services/springboot-docs-optimized-old.d.ts.map +0 -1
  107. package/build/services/springboot-docs-optimized-old.js +0 -588
  108. package/build/services/springboot-docs-optimized-old.js.map +0 -1
  109. package/build/services/springboot-docs.d.ts +0 -65
  110. package/build/services/springboot-docs.d.ts.map +0 -1
  111. package/build/services/springboot-docs.js +0 -708
  112. package/build/services/springboot-docs.js.map +0 -1
package/README.md CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/@enokdev%2Fspringdocs-mcp.svg)](https://badge.fury.io/js/@enokdev%2Fspringdocs-mcp)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org/)
5
+ [![Node.js](https://img.shields.io/badge/Node.js-20.18+-green.svg)](https://nodejs.org/)
6
6
  [![MCP Compatible](https://img.shields.io/badge/MCP-Universal%20Compatible-brightgreen.svg)](https://modelcontextprotocol.io/)
7
7
 
8
- > **🚀 Enhanced v1.2.4:** 12 powerful tools with intelligent caching, advanced tutorials, and comprehensive Spring ecosystem access
8
+ > **🚀 Enhanced v1.4.1:** 17 powerful tools with **Spring AI support**, intelligent caching, advanced tutorials, and comprehensive Spring ecosystem access
9
9
  >
10
10
  > **🌐 Universal MCP Compatibility:** Works with Claude Code, Gemini CLI, VS Code, JetBrains IDEs, and all MCP-compatible clients!
11
11
 
@@ -22,7 +22,7 @@ This server works with **ALL MCP-compatible clients**:
22
22
  "spring-docs": {
23
23
  "command": "npx",
24
24
  "args": ["@enokdev/springdocs-mcp@latest"],
25
- "description": "Spring Documentation MCP Server with 12 powerful tools"
25
+ "description": "Spring Documentation MCP Server with 17 powerful tools"
26
26
  }
27
27
  }
28
28
  }
@@ -60,6 +60,23 @@ npm install -g @enokdev/springdocs-mcp
60
60
  # Then use: springdocs-mcp
61
61
  ```
62
62
 
63
+ #### Docker (Coming Soon - Docker MCP Catalog)
64
+ ```bash
65
+ # Via Docker MCP CLI (when available in catalog)
66
+ docker mcp add springdocs-mcp
67
+
68
+ # Via Docker directly
69
+ docker pull mcp/springdocs-mcp:latest
70
+ echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}' | \
71
+ docker run -i mcp/springdocs-mcp:latest
72
+ ```
73
+
74
+ **Benefits of Docker distribution:**
75
+ - Enhanced security with cryptographic signatures and SBOMs
76
+ - Isolated execution environment
77
+ - Reduced token usage in Docker Desktop
78
+ - Automatic security updates
79
+
63
80
  **Config file locations:**
64
81
  - **Claude Desktop:** `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) / `%APPDATA%\Claude\claude_desktop_config.json` (Windows)
65
82
  - **Claude Code:** `~/.claude-code/mcp-config.json`
@@ -70,35 +87,49 @@ npm install -g @enokdev/springdocs-mcp
70
87
 
71
88
  ## ✨ Features & Tools
72
89
 
73
- ### 📚 **Core Documentation (7 Enhanced Tools)**
90
+ The server exposes **17 tools**.
91
+
92
+ ### 📚 **Core Documentation (8 Tools)**
93
+ | Tool | Purpose | Example Usage |
94
+ |------|---------|---------------|
95
+ | `search_spring_docs` | Search Spring documentation (`docType` all, guides, projects, reference, content; optional `version`) | "Search docs for WebClient" |
96
+ | `search_spring_projects` | Find Spring projects | "Find projects about security" |
97
+ | `get_spring_project` | Project page as markdown (paginated with `offset`) | "Show the Spring Data project" |
98
+ | `get_all_spring_guides` | List the getting-started guides | "List the guides" |
99
+ | `get_spring_guide` | Complete guide content | "Get the rest-service guide" |
100
+ | `get_spring_reference` | Reference documentation for 11 projects (`version`, `offset`) | "Boot reference, web section" |
101
+ | `get_migration_guide` | Spring Boot / Framework / Batch migration guides and upgrade notes | "Migration guide to Boot 3.4" |
102
+ | `search_spring_concepts` | Explore Spring concepts (optional `version`) | "Explain auto-configuration" |
103
+
104
+ ### 🚀 **Advanced Features (6 Tools)**
74
105
  | Tool | Purpose | Example Usage |
75
106
  |------|---------|---------------|
76
- | `search_spring_docs` | Search documentation with caching | "Search for REST API security" |
77
- | `search_spring_projects` | Find Spring projects | "Search for microservices projects" |
78
- | `get_spring_project` | Get project details | "Get Spring Boot project info" |
79
- | `get_all_spring_guides` | List available guides | "Show all security guides" |
80
- | `get_spring_guide` | Get complete guide content | "Get gs-rest-service guide" |
81
- | `get_spring_reference` | Reference documentation | "Get web reference docs" |
82
- | `search_spring_concepts` | Explore Spring concepts | "Explain auto-configuration" |
83
-
84
- ### 🆕 **Advanced Tools (5 New)**
107
+ | `search_spring_ecosystem` | Search the whole ecosystem, Spring AI included | "Find Spring AI vector stores" |
108
+ | `get_spring_tutorial` | Step-by-step tutorials | "Tutorial on Spring Security" |
109
+ | `compare_spring_versions` | Version comparison and migration notes | "Compare 3.3 and 3.4" |
110
+ | `get_release_notes` | GitHub release notes with focus filter | "Release notes of Boot 3.5" |
111
+ | `get_spring_best_practices` | Expert guidance by category | "Best practices for testing" |
112
+ | `diagnose_spring_issues` | Offline stack trace and error diagnosis | "Diagnose this DataSource error" |
113
+
114
+ ### 🧰 **Tooling (3 Tools)**
85
115
  | Tool | Purpose | Example Usage |
86
116
  |------|---------|---------------|
87
- | `search_spring_ecosystem` | Search entire ecosystem | "Find reactive programming resources" |
88
- | `get_spring_tutorial` | Step-by-step tutorials | "Get intermediate REST API tutorial" |
89
- | `compare_spring_versions` | Version comparison & migration | "Compare Spring Boot 2.7 vs 3.0" |
90
- | `get_spring_best_practices` | Expert guidance by category | "Get security best practices" |
91
- | `diagnose_spring_issues` | Intelligent error diagnosis | "Diagnose port 8080 error" |
92
-
93
- ### ⚡ **Performance Features**
94
- - **50-80% faster** with intelligent caching
95
- - **85% cache hit rate** for popular queries
96
- - **Auto-retry logic** with exponential backoff
97
- - **Multiple data sources** for reliability
98
- - **Parallel processing** for complex searches
117
+ | `get_spring_initializr` | Spring Initializr metadata: Boot versions, dependencies (start.spring.io) | "Which Boot versions does Initializr offer?" |
118
+ | `find_spring_dependency` | Find starters for a need, with Maven and Gradle snippets (optional `bootVersion` as X.Y.Z) | "Which dependency for Redis caching?" |
119
+ | `spring_cache_stats` | In-memory cache statistics and optional purge | "Show cache stats" |
99
120
 
100
121
  ---
101
122
 
123
+ ### 📎 **Resources**
124
+
125
+ - `spring://project/<slug>` : Spring project page as complete markdown (e.g. `spring://project/spring-boot`); `resources/list` enumerates the 11 projects of the registry, the template accepts any project of spring.io
126
+ - `spring://guide/<id>` : Spring getting-started guide as complete markdown (e.g. `spring://guide/rest-service`), available through the resource template
127
+
128
+ ### 💬 **Prompts**
129
+
130
+ - `migrate-boot-version` (`to_version`, optional `from_version`): plans a Spring Boot upgrade by chaining `get_migration_guide`, `get_release_notes` and `get_spring_reference`
131
+ - `explain-error` (`error_message`, optional `stack_trace`): explains a Spring error by chaining `diagnose_spring_issues` and `get_spring_reference`
132
+
102
133
  ## 📖 Usage Examples
103
134
 
104
135
  ### Basic Search
@@ -106,6 +137,20 @@ npm install -g @enokdev/springdocs-mcp
106
137
  "Search for REST API documentation in Spring Boot"
107
138
  ```
108
139
 
140
+ `search_spring_docs` and `search_spring_concepts` accept an optional `version` (`3.4` or `3.4.2`, the patch is ignored; `current` or omitted = latest) that targets the Spring Boot reference documentation of that version (a version that is not published gives a dedicated error). Guides and projects are not versioned and ignore it; with `docType=content`, Boot reference pages of other versions are left out of the results.
141
+
142
+ `get_migration_guide` accepts `project` (`spring-boot` by default, `spring-framework`, `spring-batch`): Framework serves the release notes of its minor version (`6.2`, section "Upgrading From ..."), Batch its migration guide (`5.0`, `6.0`), both read as raw markdown from the project wiki; `section` and `offset` work as for Boot. Spring Security and Spring AI (docs.spring.io pages) are not covered yet.
143
+
144
+ `search_spring_docs` accepts `docType=content`: full-text search (BM25 ranking) over the pages the server has already read (`get_spring_project`, `get_spring_reference`, `get_spring_guide`). It is included in `docType=all` and stays empty until a page has been read.
145
+
146
+ ### 🆕 Spring AI Support
147
+ ```
148
+ "Get Spring AI ChatClient reference documentation"
149
+ "Search for RAG and embeddings in Spring AI"
150
+ "Show me Spring AI vector store documentation"
151
+ "Find Spring AI LLM integration examples"
152
+ ```
153
+
109
154
  ### Ecosystem Exploration
110
155
  ```
111
156
  "Search the Spring ecosystem for microservices patterns"
@@ -170,6 +215,33 @@ npm install -g @enokdev/springdocs-mcp
170
215
 
171
216
  ---
172
217
 
218
+ ## Transport HTTP (optionnel)
219
+
220
+ Par défaut le serveur parle stdio. Pour l'héberger en local ou en conteneur :
221
+
222
+ ```bash
223
+ npx @enokdev/springdocs-mcp --transport http --port 3000 # écoute sur 127.0.0.1
224
+ ```
225
+
226
+ | Option | Variable | Défaut |
227
+ |---|---|---|
228
+ | `--transport stdio\|http` | `MCP_TRANSPORT` | `stdio` |
229
+ | `--port` | `MCP_PORT` | `3000` |
230
+ | `--host` | `MCP_HOST` | `127.0.0.1` |
231
+ | (aucune) | `MCP_ALLOWED_HOSTS` | vide : liste de `Host` supplémentaires, séparés par des virgules |
232
+
233
+ Endpoint MCP : `POST /mcp` (mode sans état) ; santé : `GET /healthz`. **Aucune authentification** : l'écoute reste sur loopback par défaut, et les en-têtes `Host`/`Origin` sont contrôlés contre le DNS rebinding. Pour un conteneur, `MCP_TRANSPORT=http MCP_HOST=0.0.0.0` avec le port publié (`-p 127.0.0.1:3000:3000`) ; si le port publié diffère du port interne, déclarer le `Host` utilisé par le client, par exemple `MCP_ALLOWED_HOSTS=localhost:8080`.
234
+
235
+ Le `Host` doit être de la forme `nom:port` : un `Host` sans port (client sur le port 80 ou 443 derrière un reverse proxy, par exemple `Host: mcp.example.com`) est refusé en 403. Il faut alors le déclarer tel que le proxy le transmet, via `MCP_ALLOWED_HOSTS=mcp.example.com` (liste séparée par des virgules, comparaison exacte avec l'en-tête reçu).
236
+
237
+ ### Jeton GitHub (optionnel)
238
+
239
+ `get_release_notes` et `compare_spring_versions` interrogent l'API GitHub, limitée à 60 requêtes/heure sans authentification. Définir `GITHUB_TOKEN` (jeton sans scope particulier suffit) relève cette limite à 5000/heure ; le jeton n'est envoyé qu'à `api.github.com` (jamais à un autre hôte ni après une redirection) et n'est jamais journalisé. En cas de limite atteinte, l'outil échoue immédiatement avec un message clair (délai de reprise inclus) au lieu d'attendre. `get_release_notes` accepte `version` en `X.Y` (ex. `3.5`) pour obtenir la dernière release stable de cette mineure ; `compare_spring_versions` applique `focus` (`breaking-changes`, `new-features`, `deprecations`).
240
+
241
+ ```json
242
+ { "mcpServers": { "springdocs": { "command": "npx", "args": ["@enokdev/springdocs-mcp@latest"], "env": { "GITHUB_TOKEN": "ghp_..." } } } }
243
+ ```
244
+
173
245
  ## 🧪 Testing & Development
174
246
 
175
247
  ### Quick Test
@@ -203,7 +275,7 @@ done
203
275
 
204
276
  #### "Server failed to start"
205
277
  ```bash
206
- # Check Node.js version (requires 18+)
278
+ # Check Node.js version (requires 20.18.1+)
207
279
  node --version
208
280
 
209
281
  # Update to latest
@@ -259,8 +331,8 @@ echo $? -eq 0 && echo "✅ Network: OK" || echo "❌ Network: FAILED"
259
331
 
260
332
  ### 🆕 **Major Enhancements**
261
333
  - **5 new advanced tools** for comprehensive Spring ecosystem access
262
- - **50-80% performance improvement** with intelligent caching
263
- - **99.5% reliability** with auto-retry and fallback mechanisms
334
+ - **In-memory caching** of repeated requests (30 min TTL, 24 h for stable content)
335
+ - **Auto-retry** with exponential backoff and a 5 MiB response size cap
264
336
  - **Clean architecture** with modular services and optimized code
265
337
 
266
338
  ### 🎯 **New Capabilities**
@@ -270,25 +342,21 @@ echo $? -eq 0 && echo "✅ Network: OK" || echo "❌ Network: FAILED"
270
342
  - **Expert best practices** categorized by domain and experience level
271
343
  - **Intelligent diagnostics** for common Spring Boot issues
272
344
 
273
- ### ⚡ **Performance Improvements**
274
- | Metric | Before v1.2.3 | After v1.2.3 | Improvement |
275
- |--------|---------------|--------------|-------------|
276
- | Response Time | 2-5 seconds | 0.5-2 seconds | **50-80% faster** |
277
- | Cache Hit Rate | 0% | 85% | **New feature** |
278
- | Success Rate | 90% | 99.5% | **10x more reliable** |
279
- | Memory Usage | High | Optimized | **40% reduction** |
345
+ ### ⚡ **Performance & Resilience**
346
+ - Repeated requests are served from the in-memory cache (30 min TTL, 24 h for stable content) without a new network call
347
+ - The cache is bounded by an estimated memory budget (64 MiB by default, LRU eviction, a single value larger than the budget is not cached); override with `MCP_CACHE_MAX_MB` (e.g. `MCP_CACHE_MAX_MB=32`). `spring_cache_stats` reports entries and estimated memory used / budget
348
+ - Retry with exponential backoff and request timeouts on all external HTTP calls
349
+ - Responses larger than 5 MiB are rejected
280
350
 
281
351
  ---
282
352
 
283
353
  ## 🔮 Roadmap
284
354
 
285
- ### v1.3.0 (Next)
286
- - Interactive Spring Boot project generator
287
- - Real-time error analysis
288
- - Spring Initializr integration
355
+ ### v1.5.0 (Next)
356
+ - Interactive Spring Boot project generator (Initializr metadata and dependency lookup are already available)
289
357
  - Custom tutorial creation
290
358
 
291
- ### v1.4.0 (Future)
359
+ ### v1.6.0 (Future)
292
360
  - AI-powered code suggestions
293
361
  - Performance bottleneck detection
294
362
  - Security vulnerability scanning
@@ -0,0 +1,9 @@
1
+ export type TransportMode = "stdio" | "http";
2
+ export interface ServerConfig {
3
+ transport: TransportMode;
4
+ host: string;
5
+ port: number;
6
+ allowedHosts: string[];
7
+ }
8
+ export declare function parseConfig(argv: string[], env: NodeJS.ProcessEnv): ServerConfig;
9
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,aAAa,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAoBD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,YAAY,CAgBhF"}
@@ -0,0 +1,32 @@
1
+ const TRANSPORTS = ["stdio", "http"];
2
+ /** Value of `--name value` or `--name=value`; undefined when the option is absent. */
3
+ function readOption(argv, name) {
4
+ for (let i = 0; i < argv.length; i++) {
5
+ const arg = argv[i];
6
+ if (arg === name) {
7
+ const value = argv[i + 1];
8
+ if (value === undefined || value.startsWith("--")) {
9
+ throw new Error(`${name} requires a value`);
10
+ }
11
+ return value;
12
+ }
13
+ if (arg.startsWith(`${name}=`))
14
+ return arg.slice(name.length + 1);
15
+ }
16
+ return undefined;
17
+ }
18
+ export function parseConfig(argv, env) {
19
+ const transport = readOption(argv, "--transport") ?? env.MCP_TRANSPORT ?? "stdio";
20
+ if (!TRANSPORTS.includes(transport)) {
21
+ throw new Error(`Invalid transport "${transport}". Allowed: ${TRANSPORTS.join(", ")}`);
22
+ }
23
+ const rawPort = readOption(argv, "--port") ?? env.MCP_PORT ?? "3000";
24
+ const port = Number(rawPort);
25
+ if (rawPort.trim() === "" || !Number.isInteger(port) || port < 0 || port > 65535) {
26
+ throw new Error(`Invalid port "${rawPort}": expected an integer between 0 and 65535`);
27
+ }
28
+ const host = readOption(argv, "--host") ?? env.MCP_HOST ?? "127.0.0.1";
29
+ const allowedHosts = (env.MCP_ALLOWED_HOSTS ?? "").split(",").map(value => value.trim()).filter(Boolean);
30
+ return { transport: transport, host, port, allowedHosts };
31
+ }
32
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,GAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAEtD,sFAAsF;AACtF,SAAS,UAAU,CAAC,IAAc,EAAE,IAAY;IAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClD,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,mBAAmB,CAAC,CAAC;YAC9C,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAc,EAAE,GAAsB;IAChE,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC;IAClF,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,SAA0B,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,sBAAsB,SAAS,eAAe,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,IAAI,MAAM,CAAC;IACrE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7B,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;QACjF,MAAM,IAAI,KAAK,CAAC,iBAAiB,OAAO,4CAA4C,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,IAAI,WAAW,CAAC;IACvE,MAAM,YAAY,GAAG,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEzG,OAAO,EAAE,SAAS,EAAE,SAA0B,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;AAC7E,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Formats search results for the MCP text output. Pure helper (no network).
3
+ * Results are numbered; `note` entries (e.g. empty content index) are appended unnumbered.
4
+ */
5
+ export declare function formatSearchResults(results: any[]): string;
6
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,MAAM,CAmB1D"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Formats search results for the MCP text output. Pure helper (no network).
3
+ * Results are numbered; `note` entries (e.g. empty content index) are appended unnumbered.
4
+ */
5
+ export function formatSearchResults(results) {
6
+ if (results.length === 0) {
7
+ return "No results found.";
8
+ }
9
+ const notes = results.filter((result) => result.type === "note");
10
+ const items = results.filter((result) => result.type !== "note");
11
+ const numbered = items.map((result, index) => `${index + 1}. **${result.title}**
12
+ Type: ${result.type}
13
+ URL: ${result.url}
14
+ Description: ${result.description || "No description available"}
15
+
16
+ `);
17
+ const trailing = notes.map((note) => `**${note.title}**
18
+ ${note.description}
19
+
20
+ `);
21
+ return [...numbered, ...trailing].join("\n");
22
+ }
23
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAc;IAChD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IAEjE,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,GAAG,CAAC,OAAO,MAAM,CAAC,KAAK;WACpE,MAAM,CAAC,IAAI;UACZ,MAAM,CAAC,GAAG;kBACF,MAAM,CAAC,WAAW,IAAI,0BAA0B;;CAEjE,CAAC,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,KAAK;KACjD,IAAI,CAAC,WAAW;;CAEpB,CAAC,CAAC;IACD,OAAO,CAAC,GAAG,QAAQ,EAAE,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ export interface HttpServerOptions {
3
+ host: string;
4
+ port: number;
5
+ createServer: () => Server;
6
+ allowedHosts?: string[];
7
+ }
8
+ export interface RunningHttpServer {
9
+ port: number;
10
+ /** Address actually bound, as `ip:port` (`[ip]:port` for IPv6), not the configured host name. */
11
+ address: string;
12
+ close(): Promise<void>;
13
+ }
14
+ export declare function startHttpServer(options: HttpServerOptions): Promise<RunningHttpServer>;
15
+ //# sourceMappingURL=http-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAMxE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,iGAAiG;IACjG,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAkCD,wBAAsB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAsG5F"}
@@ -0,0 +1,131 @@
1
+ import { createServer as createHttpServer } from "node:http";
2
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3
+ const MAX_BODY_BYTES = 1024 * 1024;
4
+ const CLOSE_GRACE_MS = 2000;
5
+ function sendJson(res, status, body, headers = {}) {
6
+ const payload = JSON.stringify(body);
7
+ res.writeHead(status, { "content-type": "application/json", "content-length": Buffer.byteLength(payload), ...headers });
8
+ res.end(payload);
9
+ }
10
+ const rpcError = (code, message) => ({ jsonrpc: "2.0", error: { code, message }, id: null });
11
+ /** Reads the body, giving up as soon as it exceeds the limit. */
12
+ function readBody(req) {
13
+ return new Promise((resolve, reject) => {
14
+ const declared = Number(req.headers["content-length"]);
15
+ if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
16
+ resolve(null);
17
+ return;
18
+ }
19
+ const chunks = [];
20
+ let size = 0;
21
+ req.on("data", (chunk) => {
22
+ size += chunk.length;
23
+ if (size > MAX_BODY_BYTES) {
24
+ chunks.length = 0;
25
+ resolve(null);
26
+ return;
27
+ }
28
+ if (size <= MAX_BODY_BYTES)
29
+ chunks.push(chunk);
30
+ });
31
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
32
+ req.on("error", reject);
33
+ });
34
+ }
35
+ export async function startHttpServer(options) {
36
+ const extraHosts = new Set(options.allowedHosts ?? []);
37
+ let allowed = new Set();
38
+ const handle = async (req, res) => {
39
+ // DNS rebinding: the Host header must be a known loopback name (or explicitly allowed)
40
+ const host = req.headers.host ?? "";
41
+ if (!allowed.has(host)) {
42
+ sendJson(res, 403, rpcError(-32000, "Forbidden: invalid Host header"));
43
+ return;
44
+ }
45
+ const path = new URL(req.url ?? "/", "http://localhost").pathname;
46
+ if (path === "/healthz" && req.method === "GET") {
47
+ res.writeHead(200, { "content-type": "text/plain" });
48
+ res.end("ok");
49
+ return;
50
+ }
51
+ const origin = req.headers.origin;
52
+ if (origin !== undefined) {
53
+ let originHost = "";
54
+ try {
55
+ originHost = new URL(origin).host;
56
+ }
57
+ catch {
58
+ // an unparsable Origin ("null", garbage) is rejected below
59
+ }
60
+ if (!allowed.has(originHost)) {
61
+ sendJson(res, 403, rpcError(-32000, "Forbidden: invalid Origin header"));
62
+ return;
63
+ }
64
+ }
65
+ if (path !== "/mcp") {
66
+ sendJson(res, 404, rpcError(-32000, "Not found"));
67
+ return;
68
+ }
69
+ if (req.method !== "POST") {
70
+ sendJson(res, 405, rpcError(-32000, "Method not allowed"), { allow: "POST" });
71
+ return;
72
+ }
73
+ if (!/^application\/json\b/i.test(req.headers["content-type"] ?? "")) {
74
+ sendJson(res, 415, rpcError(-32000, "Content-Type must be application/json"));
75
+ return;
76
+ }
77
+ const raw = await readBody(req);
78
+ if (raw === null) {
79
+ sendJson(res, 413, rpcError(-32000, "Request body too large (max 1 MiB)"), { connection: "close" });
80
+ return;
81
+ }
82
+ let body;
83
+ try {
84
+ body = JSON.parse(raw);
85
+ }
86
+ catch {
87
+ sendJson(res, 400, rpcError(-32700, "Parse error"));
88
+ return;
89
+ }
90
+ // Stateless: one MCP server and transport per request, shared services live in createServer's closure
91
+ const mcp = options.createServer();
92
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
93
+ res.on("close", () => {
94
+ void mcp.close().catch(() => { });
95
+ });
96
+ await mcp.connect(transport);
97
+ await transport.handleRequest(req, res, body);
98
+ };
99
+ const server = createHttpServer((req, res) => {
100
+ handle(req, res).catch((error) => {
101
+ console.error("💥 HTTP request failed:", error instanceof Error ? error.message : error);
102
+ if (!res.headersSent)
103
+ sendJson(res, 500, rpcError(-32603, "Internal server error"));
104
+ else
105
+ res.end();
106
+ });
107
+ });
108
+ await new Promise((resolve, reject) => {
109
+ server.once("error", reject);
110
+ server.listen(options.port, options.host, () => {
111
+ server.off("error", reject);
112
+ resolve();
113
+ });
114
+ });
115
+ const address = server.address();
116
+ const port = typeof address === "object" && address ? address.port : options.port;
117
+ allowed = new Set([`localhost:${port}`, `127.0.0.1:${port}`, `[::1]:${port}`, ...extraHosts]);
118
+ const bound = typeof address === "object" && address
119
+ ? `${address.family === "IPv6" ? `[${address.address}]` : address.address}:${address.port}`
120
+ : `${options.host}:${port}`;
121
+ return {
122
+ port,
123
+ address: bound,
124
+ close: () => new Promise((resolve) => {
125
+ server.close(() => resolve());
126
+ server.closeIdleConnections();
127
+ setTimeout(() => server.closeAllConnections(), CLOSE_GRACE_MS).unref();
128
+ }),
129
+ };
130
+ }
131
+ //# sourceMappingURL=http-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.js","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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"}