@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.
- package/README.md +108 -40
- package/build/config.d.ts +9 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +32 -0
- package/build/config.js.map +1 -0
- package/build/format.d.ts +6 -0
- package/build/format.d.ts.map +1 -0
- package/build/format.js +23 -0
- package/build/format.js.map +1 -0
- package/build/http-server.d.ts +15 -0
- package/build/http-server.d.ts.map +1 -0
- package/build/http-server.js +131 -0
- package/build/http-server.js.map +1 -0
- package/build/index.js +184 -69
- package/build/index.js.map +1 -1
- package/build/prompts.d.ts +23 -0
- package/build/prompts.d.ts.map +1 -0
- package/build/prompts.js +112 -0
- package/build/prompts.js.map +1 -0
- package/build/resources.d.ts +31 -0
- package/build/resources.d.ts.map +1 -0
- package/build/resources.js +66 -0
- package/build/resources.js.map +1 -0
- package/build/services/advanced-features.d.ts +19 -9
- package/build/services/advanced-features.d.ts.map +1 -1
- package/build/services/advanced-features.js +266 -191
- package/build/services/advanced-features.js.map +1 -1
- package/build/services/boot-wiki.d.ts +25 -0
- package/build/services/boot-wiki.d.ts.map +1 -0
- package/build/services/boot-wiki.js +103 -0
- package/build/services/boot-wiki.js.map +1 -0
- package/build/services/cache.d.ts +18 -3
- package/build/services/cache.d.ts.map +1 -1
- package/build/services/cache.js +84 -3
- package/build/services/cache.js.map +1 -1
- package/build/services/dependency-finder.d.ts +46 -0
- package/build/services/dependency-finder.d.ts.map +1 -0
- package/build/services/dependency-finder.js +181 -0
- package/build/services/dependency-finder.js.map +1 -0
- package/build/services/diagnosis.d.ts +46 -0
- package/build/services/diagnosis.d.ts.map +1 -0
- package/build/services/diagnosis.js +481 -0
- package/build/services/diagnosis.js.map +1 -0
- package/build/services/guides-list.d.ts +16 -0
- package/build/services/guides-list.d.ts.map +1 -0
- package/build/services/guides-list.js +45 -0
- package/build/services/guides-list.js.map +1 -0
- package/build/services/http.d.ts +21 -0
- package/build/services/http.d.ts.map +1 -0
- package/build/services/http.js +168 -0
- package/build/services/http.js.map +1 -0
- package/build/services/initializr.d.ts +41 -0
- package/build/services/initializr.d.ts.map +1 -0
- package/build/services/initializr.js +135 -0
- package/build/services/initializr.js.map +1 -0
- package/build/services/markdown.d.ts +56 -0
- package/build/services/markdown.d.ts.map +1 -0
- package/build/services/markdown.js +146 -0
- package/build/services/markdown.js.map +1 -0
- package/build/services/migration-sources.d.ts +13 -0
- package/build/services/migration-sources.d.ts.map +1 -0
- package/build/services/migration-sources.js +48 -0
- package/build/services/migration-sources.js.map +1 -0
- package/build/services/release-notes.d.ts +23 -0
- package/build/services/release-notes.d.ts.map +1 -0
- package/build/services/release-notes.js +94 -0
- package/build/services/release-notes.js.map +1 -0
- package/build/services/search-index.d.ts +33 -0
- package/build/services/search-index.d.ts.map +1 -0
- package/build/services/search-index.js +147 -0
- package/build/services/search-index.js.map +1 -0
- package/build/services/spring-projects-config.d.ts +98 -0
- package/build/services/spring-projects-config.d.ts.map +1 -0
- package/build/services/spring-projects-config.js +397 -0
- package/build/services/spring-projects-config.js.map +1 -0
- package/build/services/springboot-docs-optimized.d.ts +72 -21
- package/build/services/springboot-docs-optimized.d.ts.map +1 -1
- package/build/services/springboot-docs-optimized.js +354 -208
- package/build/services/springboot-docs-optimized.js.map +1 -1
- package/build/services/url.d.ts +15 -0
- package/build/services/url.d.ts.map +1 -0
- package/build/services/url.js +40 -0
- package/build/services/url.js.map +1 -0
- package/build/tools/index.d.ts +393 -2
- package/build/tools/index.d.ts.map +1 -1
- package/build/tools/index.js +188 -15
- package/build/tools/index.js.map +1 -1
- package/build/validation.d.ts +7 -0
- package/build/validation.d.ts.map +1 -0
- package/build/validation.js +50 -0
- package/build/validation.js.map +1 -0
- package/build/version.d.ts +3 -0
- package/build/version.d.ts.map +1 -0
- package/build/version.js +6 -0
- package/build/version.js.map +1 -0
- package/package.json +9 -7
- package/build/debug.d.ts +0 -3
- package/build/debug.d.ts.map +0 -1
- package/build/debug.js +0 -292
- package/build/debug.js.map +0 -1
- package/build/services/advanced-features-old.d.ts +0 -45
- package/build/services/advanced-features-old.d.ts.map +0 -1
- package/build/services/advanced-features-old.js +0 -792
- package/build/services/advanced-features-old.js.map +0 -1
- package/build/services/springboot-docs-optimized-old.d.ts +0 -55
- package/build/services/springboot-docs-optimized-old.d.ts.map +0 -1
- package/build/services/springboot-docs-optimized-old.js +0 -588
- package/build/services/springboot-docs-optimized-old.js.map +0 -1
- package/build/services/springboot-docs.d.ts +0 -65
- package/build/services/springboot-docs.d.ts.map +0 -1
- package/build/services/springboot-docs.js +0 -708
- package/build/services/springboot-docs.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/js/@enokdev%2Fspringdocs-mcp)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://nodejs.org/)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
6
|
[](https://modelcontextprotocol.io/)
|
|
7
7
|
|
|
8
|
-
> **🚀 Enhanced v1.
|
|
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
|
|
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
|
-
|
|
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
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
|
|
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
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
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
|
-
- **
|
|
263
|
-
- **
|
|
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
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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.
|
|
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.
|
|
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"}
|
package/build/config.js
ADDED
|
@@ -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"}
|
package/build/format.js
ADDED
|
@@ -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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"}
|