@enokdev/springdocs-mcp 1.2.8 → 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 +83 -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 +182 -68
- 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 +14 -9
- package/build/services/advanced-features.d.ts.map +1 -1
- package/build/services/advanced-features.js +205 -195
- 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 +26 -33
- package/build/services/spring-projects-config.d.ts.map +1 -1
- package/build/services/spring-projects-config.js +248 -79
- package/build/services/spring-projects-config.js.map +1 -1
- package/build/services/springboot-docs-optimized.d.ts +57 -16
- package/build/services/springboot-docs-optimized.d.ts.map +1 -1
- package/build/services/springboot-docs-optimized.js +315 -197
- 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 +376 -2
- package/build/tools/index.d.ts.map +1 -1
- package/build/tools/index.js +178 -11
- 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 +6 -6
- 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
|
}
|
|
@@ -87,35 +87,49 @@ echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}' | \
|
|
|
87
87
|
|
|
88
88
|
## ✨ Features & Tools
|
|
89
89
|
|
|
90
|
-
|
|
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)**
|
|
91
105
|
| Tool | Purpose | Example Usage |
|
|
92
106
|
|------|---------|---------------|
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
### 🆕 **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)**
|
|
102
115
|
| Tool | Purpose | Example Usage |
|
|
103
116
|
|------|---------|---------------|
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `get_spring_best_practices` | Expert guidance by category | "Get security best practices" |
|
|
108
|
-
| `diagnose_spring_issues` | Intelligent error diagnosis | "Diagnose port 8080 error" |
|
|
109
|
-
|
|
110
|
-
### ⚡ **Performance Features**
|
|
111
|
-
- **50-80% faster** with intelligent caching
|
|
112
|
-
- **85% cache hit rate** for popular queries
|
|
113
|
-
- **Auto-retry logic** with exponential backoff
|
|
114
|
-
- **Multiple data sources** for reliability
|
|
115
|
-
- **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" |
|
|
116
120
|
|
|
117
121
|
---
|
|
118
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
|
+
|
|
119
133
|
## 📖 Usage Examples
|
|
120
134
|
|
|
121
135
|
### Basic Search
|
|
@@ -123,6 +137,12 @@ echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}' | \
|
|
|
123
137
|
"Search for REST API documentation in Spring Boot"
|
|
124
138
|
```
|
|
125
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
|
+
|
|
126
146
|
### 🆕 Spring AI Support
|
|
127
147
|
```
|
|
128
148
|
"Get Spring AI ChatClient reference documentation"
|
|
@@ -195,6 +215,33 @@ echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}' | \
|
|
|
195
215
|
|
|
196
216
|
---
|
|
197
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
|
+
|
|
198
245
|
## 🧪 Testing & Development
|
|
199
246
|
|
|
200
247
|
### Quick Test
|
|
@@ -228,7 +275,7 @@ done
|
|
|
228
275
|
|
|
229
276
|
#### "Server failed to start"
|
|
230
277
|
```bash
|
|
231
|
-
# Check Node.js version (requires 18+)
|
|
278
|
+
# Check Node.js version (requires 20.18.1+)
|
|
232
279
|
node --version
|
|
233
280
|
|
|
234
281
|
# Update to latest
|
|
@@ -284,8 +331,8 @@ echo $? -eq 0 && echo "✅ Network: OK" || echo "❌ Network: FAILED"
|
|
|
284
331
|
|
|
285
332
|
### 🆕 **Major Enhancements**
|
|
286
333
|
- **5 new advanced tools** for comprehensive Spring ecosystem access
|
|
287
|
-
- **
|
|
288
|
-
- **
|
|
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
|
|
289
336
|
- **Clean architecture** with modular services and optimized code
|
|
290
337
|
|
|
291
338
|
### 🎯 **New Capabilities**
|
|
@@ -295,25 +342,21 @@ echo $? -eq 0 && echo "✅ Network: OK" || echo "❌ Network: FAILED"
|
|
|
295
342
|
- **Expert best practices** categorized by domain and experience level
|
|
296
343
|
- **Intelligent diagnostics** for common Spring Boot issues
|
|
297
344
|
|
|
298
|
-
### ⚡ **Performance
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
| Success Rate | 90% | 99.5% | **10x more reliable** |
|
|
304
|
-
| 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
|
|
305
350
|
|
|
306
351
|
---
|
|
307
352
|
|
|
308
353
|
## 🔮 Roadmap
|
|
309
354
|
|
|
310
|
-
### v1.
|
|
311
|
-
- Interactive Spring Boot project generator
|
|
312
|
-
- Real-time error analysis
|
|
313
|
-
- Spring Initializr integration
|
|
355
|
+
### v1.5.0 (Next)
|
|
356
|
+
- Interactive Spring Boot project generator (Initializr metadata and dependency lookup are already available)
|
|
314
357
|
- Custom tutorial creation
|
|
315
358
|
|
|
316
|
-
### v1.
|
|
359
|
+
### v1.6.0 (Future)
|
|
317
360
|
- AI-powered code suggestions
|
|
318
361
|
- Performance bottleneck detection
|
|
319
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":"AAAA,OAAO,EAAE,YAAY,IAAI,gBAAgB,EAA6C,MAAM,WAAW,CAAC;AAExG,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAEnG,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,CAAC;AACnC,MAAM,cAAc,GAAG,IAAI,CAAC;AAgB5B,SAAS,QAAQ,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAa,EAAE,UAAkC,EAAE;IACxG,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACxH,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC;AAED,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;AAE7G,iEAAiE;AACjE,SAAS,QAAQ,CAAC,GAAoB;IACpC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACvD,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,cAAc,EAAE,CAAC;YAC3D,OAAO,CAAC,IAAI,CAAC,CAAC;YACd,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YAC/B,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;YACrB,IAAI,IAAI,GAAG,cAAc,EAAE,CAAC;gBAC1B,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;gBAClB,OAAO,CAAC,IAAI,CAAC,CAAC;gBACd,OAAO;YACT,CAAC;YACD,IAAI,IAAI,IAAI,cAAc;gBAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACrE,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAA0B;IAC9D,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;IACvD,IAAI,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAEhC,MAAM,MAAM,GAAG,KAAK,EAAE,GAAoB,EAAE,GAAmB,EAAiB,EAAE;QAChF,uFAAuF;QACvF,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;QACpC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,EAAE,gCAAgC,CAAC,CAAC,CAAC;YACvE,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC,QAAQ,CAAC;QAClE,IAAI,IAAI,KAAK,UAAU,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAChD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;YACrD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACd,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;QAClC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,IAAI,UAAU,GAAG,EAAE,CAAC;YACpB,IAAI,CAAC;gBACH,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;YACpC,CAAC;YAAC,MAAM,CAAC;gBACP,2DAA2D;YAC7D,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC7B,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,EAAE,kCAAkC,CAAC,CAAC,CAAC;gBACzE,OAAO;YACT,CAAC;QACH,CAAC;QAED,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1B,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,EAAE,oBAAoB,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;YAC9E,OAAO;QACT,CAAC;QACD,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YACrE,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,EAAE,uCAAuC,CAAC,CAAC,CAAC;YAC9E,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,EAAE,oCAAoC,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;YACpG,OAAO;QACT,CAAC;QACD,IAAI,IAAa,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC;YACpD,OAAO;QACT,CAAC;QAED,sGAAsG;QACtG,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;QACnC,MAAM,SAAS,GAAG,IAAI,6BAA6B,CAAC,EAAE,kBAAkB,EAAE,SAAS,EAAE,CAAC,CAAC;QACvF,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACnB,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QACH,MAAM,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC7B,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,gBAAgB,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC3C,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YAC/B,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YACzF,IAAI,CAAC,GAAG,CAAC,WAAW;gBAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,EAAE,uBAAuB,CAAC,CAAC,CAAC;;gBAC/E,GAAG,CAAC,GAAG,EAAE,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE;YAC7C,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC5B,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;IACjC,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAClF,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,aAAa,IAAI,EAAE,EAAE,aAAa,IAAI,EAAE,EAAE,SAAS,IAAI,EAAE,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC;IAE9F,MAAM,KAAK,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO;QAClD,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE;QAC3F,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;IAE9B,OAAO;QACL,IAAI;QACJ,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACzC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9B,MAAM,CAAC,oBAAoB,EAAE,CAAC;YAC9B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,EAAE,EAAE,cAAc,CAAC,CAAC,KAAK,EAAE,CAAC;QACzE,CAAC,CAAC;KACH,CAAC;AACJ,CAAC"}
|