agent-task-manager-mcp 1.0.0 → 1.0.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 +187 -22
- package/dist/certs.d.ts +11 -0
- package/dist/certs.d.ts.map +1 -0
- package/dist/certs.js +56 -0
- package/dist/certs.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +35 -0
- package/dist/cli.js.map +1 -0
- package/dist/hostValidation.d.ts +3 -0
- package/dist/hostValidation.d.ts.map +1 -0
- package/dist/hostValidation.js +58 -0
- package/dist/hostValidation.js.map +1 -0
- package/dist/http.server.d.ts +13 -0
- package/dist/http.server.d.ts.map +1 -0
- package/dist/http.server.js +188 -0
- package/dist/http.server.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -62
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +36 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +64 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/adapters/json.adapter.d.ts +3 -0
- package/dist/storage/adapters/json.adapter.d.ts.map +1 -0
- package/dist/storage/adapters/json.adapter.js +314 -0
- package/dist/storage/adapters/json.adapter.js.map +1 -0
- package/dist/storage/adapters/mongodb.adapter.d.ts +3 -0
- package/dist/storage/adapters/mongodb.adapter.d.ts.map +1 -0
- package/dist/storage/adapters/mongodb.adapter.js +232 -0
- package/dist/storage/adapters/mongodb.adapter.js.map +1 -0
- package/dist/storage/adapters/postgres.adapter.d.ts +3 -0
- package/dist/storage/adapters/postgres.adapter.d.ts.map +1 -0
- package/dist/storage/adapters/postgres.adapter.js +440 -0
- package/dist/storage/adapters/postgres.adapter.js.map +1 -0
- package/dist/storage/adapters/sqlite.adapter.d.ts +3 -0
- package/dist/storage/adapters/sqlite.adapter.d.ts.map +1 -0
- package/dist/storage/adapters/sqlite.adapter.js +437 -0
- package/dist/storage/adapters/sqlite.adapter.js.map +1 -0
- package/dist/storage/index.d.ts +17 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +22 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/interface.d.ts +72 -0
- package/dist/storage/interface.d.ts.map +1 -0
- package/dist/storage/interface.js +3 -0
- package/dist/storage/interface.js.map +1 -0
- package/dist/storage/json.adapter.d.ts +3 -0
- package/dist/storage/json.adapter.d.ts.map +1 -0
- package/dist/storage/json.adapter.js +314 -0
- package/dist/storage/json.adapter.js.map +1 -0
- package/dist/storage/mongodb.adapter.d.ts +3 -0
- package/dist/storage/mongodb.adapter.d.ts.map +1 -0
- package/dist/storage/mongodb.adapter.js +232 -0
- package/dist/storage/mongodb.adapter.js.map +1 -0
- package/dist/storage/router.d.ts +30 -0
- package/dist/storage/router.d.ts.map +1 -0
- package/dist/storage/router.js +100 -0
- package/dist/storage/router.js.map +1 -0
- package/dist/storage/sqlite.adapter.d.ts +3 -0
- package/dist/storage/sqlite.adapter.d.ts.map +1 -0
- package/dist/storage/sqlite.adapter.js +437 -0
- package/dist/storage/sqlite.adapter.js.map +1 -0
- package/dist/storage/types.d.ts +80 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +7 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/tools/checkpoint.tools.d.ts.map +1 -1
- package/dist/tools/checkpoint.tools.js +8 -21
- package/dist/tools/checkpoint.tools.js.map +1 -1
- package/dist/tools/session.tools.d.ts.map +1 -1
- package/dist/tools/session.tools.js +21 -30
- package/dist/tools/session.tools.js.map +1 -1
- package/dist/tools/subtask.tools.d.ts.map +1 -1
- package/dist/tools/subtask.tools.js +9 -32
- package/dist/tools/subtask.tools.js.map +1 -1
- package/dist/tools/task.tools.d.ts.map +1 -1
- package/dist/tools/task.tools.js +23 -50
- package/dist/tools/task.tools.js.map +1 -1
- package/package.json +18 -4
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.typescriptlang.org/)
|
|
6
6
|
[](https://modelcontextprotocol.io/)
|
|
7
|
-
[]()
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install -g agent-task-manager-mcp
|
|
@@ -93,7 +93,7 @@ AI agents operate within a fixed **context window** (e.g., 100K–200K tokens).
|
|
|
93
93
|
│ │ │
|
|
94
94
|
│ ▼ │
|
|
95
95
|
│ ┌───────────────────────────────┐ │
|
|
96
|
-
│ │ MongoDB
|
|
96
|
+
│ │ Storage (MongoDB/SQLite/JSON) │ │
|
|
97
97
|
│ │ Tasks • Subtasks • Sessions │ │
|
|
98
98
|
│ │ Checkpoints • Progress │ │
|
|
99
99
|
│ └───────────────────────────────┘ │
|
|
@@ -120,11 +120,11 @@ flowchart TB
|
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
subgraph Storage["Persistence"]
|
|
123
|
-
|
|
123
|
+
DB[(MongoDB / SQLite / JSON)]
|
|
124
124
|
end
|
|
125
125
|
|
|
126
|
-
Agent <-->|stdio
|
|
127
|
-
Server <-->|
|
|
126
|
+
Agent <-->|stdio or HTTP| Server
|
|
127
|
+
Server <-->|Storage Adapter| DB
|
|
128
128
|
|
|
129
129
|
subgraph ToolsDetail["Tool Categories"]
|
|
130
130
|
T1[task_*]
|
|
@@ -238,7 +238,7 @@ flowchart LR
|
|
|
238
238
|
## Features
|
|
239
239
|
|
|
240
240
|
- **14 MCP tools** for full task lifecycle
|
|
241
|
-
- **MongoDB
|
|
241
|
+
- **MongoDB, SQLite, or JSON file** storage (configurable)
|
|
242
242
|
- **Zod validation** on all tool inputs
|
|
243
243
|
- **Task locking** for multi-agent coordination
|
|
244
244
|
- **Evidence-based verification** (no passing without proof)
|
|
@@ -252,8 +252,8 @@ flowchart LR
|
|
|
252
252
|
### Prerequisites
|
|
253
253
|
|
|
254
254
|
- **Node.js** 18+
|
|
255
|
-
- **
|
|
256
|
-
- **MCP-compatible client** (Claude Desktop, Cursor, etc.)
|
|
255
|
+
- **Storage backend** (choose one): MongoDB 6+, PostgreSQL 12+, SQLite (file-based), or JSON file
|
|
256
|
+
- **MCP-compatible client** (Claude Desktop, Cursor, ChatGPT Desktop, etc.)
|
|
257
257
|
|
|
258
258
|
### Installation
|
|
259
259
|
|
|
@@ -265,11 +265,9 @@ npm install
|
|
|
265
265
|
|
|
266
266
|
### Environment Setup
|
|
267
267
|
|
|
268
|
-
(optional and can be define in MCP config of the agent in MONGODB_URI)
|
|
269
|
-
|
|
270
268
|
```bash
|
|
271
269
|
cp .env.example .env
|
|
272
|
-
# Edit .env and set
|
|
270
|
+
# Edit .env and set STORAGE (mongodb | postgres | sqlite | json) and the corresponding backend config
|
|
273
271
|
```
|
|
274
272
|
|
|
275
273
|
### Run
|
|
@@ -277,18 +275,66 @@ cp .env.example .env
|
|
|
277
275
|
```bash
|
|
278
276
|
# Production (compiled)
|
|
279
277
|
npm run build
|
|
278
|
+
|
|
279
|
+
# Stdio mode (default) — for Claude Desktop, Cursor
|
|
280
280
|
npm start
|
|
281
|
+
node dist/index.js
|
|
282
|
+
|
|
283
|
+
# HTTP mode (default 8000; auto-finds free port if busy)
|
|
284
|
+
npm run start:http
|
|
285
|
+
node dist/index.js --http
|
|
286
|
+
|
|
287
|
+
# HTTPS mode (default 8443; auto-finds free port if busy)
|
|
288
|
+
npm run start:https
|
|
289
|
+
node dist/index.js --https
|
|
290
|
+
|
|
291
|
+
# Custom port (node directly — npm eats --port)
|
|
292
|
+
node dist/index.js --http --port=3000
|
|
293
|
+
|
|
294
|
+
# HTTPS with user-provided cert and key (separate files)
|
|
295
|
+
agent-task-manager-mcp --https --cert=./certs/cert.pem --key=./certs/key.pem
|
|
296
|
+
|
|
297
|
+
# HTTPS with combined cert+key file (mkcert, or single PEM with both blocks)
|
|
298
|
+
agent-task-manager-mcp --https --cert-key=./certs/localhost.pem
|
|
299
|
+
agent-task-manager-mcp --https --cert=./certs/combined.pem
|
|
281
300
|
```
|
|
282
301
|
|
|
283
302
|
---
|
|
284
303
|
|
|
285
304
|
## Configuration
|
|
286
305
|
|
|
306
|
+
### Storage Backends
|
|
307
|
+
|
|
308
|
+
| `STORAGE` | Use when | Config |
|
|
309
|
+
|-----------|----------|--------|
|
|
310
|
+
| `mongodb` | Production, multi-agent, document store | `MONGODB_URI` required |
|
|
311
|
+
| `postgres` | Enterprise, high concurrency, swarm of agents | `POSTGRES_URL` or `DATABASE_URL` required |
|
|
312
|
+
| `sqlite` | Local dev, no server, single-file | `SQLITE_PATH` (default: `./data/agent-tasks.db`) |
|
|
313
|
+
| `json` | Quick testing, single agent | `JSON_STORAGE_PATH` (default: `./data/agent-tasks.json`) |
|
|
314
|
+
|
|
315
|
+
### Choosing a Backend
|
|
316
|
+
|
|
317
|
+
| Scenario | Recommended |
|
|
318
|
+
|----------|-------------|
|
|
319
|
+
| Swarm of agents, production | `postgres` or `mongodb` |
|
|
320
|
+
| Single agent, local dev | `sqlite` |
|
|
321
|
+
| Quick test, no DB setup | `json` |
|
|
322
|
+
| Existing MongoDB/Postgres infra | Use matching backend |
|
|
323
|
+
|
|
324
|
+
### Swarm / Multi-Agent
|
|
325
|
+
|
|
326
|
+
For multiple agents working on tasks concurrently: use **PostgreSQL** or **MongoDB**. Both support task locking and concurrent writes. SQLite and JSON are single-writer; fine for one agent but may conflict with multiple.
|
|
327
|
+
|
|
287
328
|
### Environment Variables
|
|
288
329
|
|
|
289
|
-
| Variable
|
|
290
|
-
|
|
291
|
-
| `
|
|
330
|
+
| Variable | Required when | Description | Example |
|
|
331
|
+
|----------|---------------|-------------|---------|
|
|
332
|
+
| `STORAGE` | No | Backend: `mongodb` \| `postgres` \| `sqlite` \| `json` (default: `mongodb`) | `postgres` |
|
|
333
|
+
| `MONGODB_URI` | `STORAGE=mongodb` | MongoDB connection string | `mongodb://localhost:27017/agent-tasks` |
|
|
334
|
+
| `POSTGRES_URL` or `DATABASE_URL` | `STORAGE=postgres` | PostgreSQL connection string | `postgresql://user:pass@localhost:5432/agent_tasks` |
|
|
335
|
+
| `SQLITE_PATH` | `STORAGE=sqlite` | SQLite database file path | `./data/agent-tasks.db` |
|
|
336
|
+
| `JSON_STORAGE_PATH` | `STORAGE=json` | JSON file path | `./data/agent-tasks.json` |
|
|
337
|
+
| `MCP_ALLOWED_HOSTS` | No | Comma-separated hosts for HTTP/HTTPS (ngrok, etc) | `myapp.ngrok.io,custom.local` |
|
|
292
338
|
|
|
293
339
|
### MongoDB URI Examples
|
|
294
340
|
|
|
@@ -303,15 +349,27 @@ MONGODB_URI=mongodb://user:password@IP2:27017/agent-tasks?authSource=admin
|
|
|
303
349
|
MONGODB_URI=mongodb+srv://user:password@cluster.mongodb.net/agent-tasks?retryWrites=true&w=majority
|
|
304
350
|
```
|
|
305
351
|
|
|
352
|
+
### PostgreSQL URI Examples
|
|
353
|
+
|
|
354
|
+
```env
|
|
355
|
+
# Local
|
|
356
|
+
POSTGRES_URL=postgresql://user:password@localhost:5432/agent_tasks
|
|
357
|
+
|
|
358
|
+
# Supabase, Neon, Railway, etc.
|
|
359
|
+
DATABASE_URL=postgresql://user:password@host:5432/dbname?sslmode=require
|
|
360
|
+
```
|
|
361
|
+
|
|
306
362
|
> **Note:** URL-encode special characters in passwords (e.g., `@` → `%40`).
|
|
307
363
|
|
|
308
364
|
---
|
|
309
365
|
|
|
310
366
|
## Integration
|
|
311
367
|
|
|
312
|
-
###
|
|
368
|
+
### Option 1: Global install (recommended)
|
|
369
|
+
|
|
370
|
+
After `npm install -g agent-task-manager-mcp`, use the binary name. No path or `npx` needed.
|
|
313
371
|
|
|
314
|
-
|
|
372
|
+
**Claude Desktop** — add to `claude_desktop_config.json`:
|
|
315
373
|
|
|
316
374
|
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
317
375
|
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
@@ -320,9 +378,10 @@ Add to `claude_desktop_config.json`:
|
|
|
320
378
|
{
|
|
321
379
|
"mcpServers": {
|
|
322
380
|
"agent-task-manager-mcp": {
|
|
323
|
-
"command": "
|
|
324
|
-
"args": [
|
|
381
|
+
"command": "agent-task-manager-mcp",
|
|
382
|
+
"args": [],
|
|
325
383
|
"env": {
|
|
384
|
+
"STORAGE": "mongodb",
|
|
326
385
|
"MONGODB_URI": "mongodb://localhost:27017/agent-tasks"
|
|
327
386
|
}
|
|
328
387
|
}
|
|
@@ -330,9 +389,80 @@ Add to `claude_desktop_config.json`:
|
|
|
330
389
|
}
|
|
331
390
|
```
|
|
332
391
|
|
|
333
|
-
|
|
392
|
+
**Cursor** — add to MCP settings or `.cursor/mcp.json`:
|
|
393
|
+
|
|
394
|
+
```json
|
|
395
|
+
{
|
|
396
|
+
"mcpServers": {
|
|
397
|
+
"agent-task-manager-mcp": {
|
|
398
|
+
"command": "agent-task-manager-mcp",
|
|
399
|
+
"args": [],
|
|
400
|
+
"env": {
|
|
401
|
+
"STORAGE": "sqlite",
|
|
402
|
+
"SQLITE_PATH": "./data/agent-tasks.db"
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**PostgreSQL** (swarm of agents, production):
|
|
410
|
+
|
|
411
|
+
```json
|
|
412
|
+
{
|
|
413
|
+
"mcpServers": {
|
|
414
|
+
"agent-task-manager-mcp": {
|
|
415
|
+
"command": "agent-task-manager-mcp",
|
|
416
|
+
"args": [],
|
|
417
|
+
"env": {
|
|
418
|
+
"STORAGE": "postgres",
|
|
419
|
+
"POSTGRES_URL": "postgresql://user:password@localhost:5432/agent_tasks"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**No DB server?** Use SQLite or JSON:
|
|
427
|
+
|
|
428
|
+
```json
|
|
429
|
+
{
|
|
430
|
+
"mcpServers": {
|
|
431
|
+
"agent-task-manager-mcp": {
|
|
432
|
+
"command": "agent-task-manager-mcp",
|
|
433
|
+
"args": [],
|
|
434
|
+
"env": {
|
|
435
|
+
"STORAGE": "json",
|
|
436
|
+
"JSON_STORAGE_PATH": "./data/agent-tasks.json"
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
See **[docs/STORAGE.md](docs/STORAGE.md)** for per-backend setup, migration, and troubleshooting.
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
### Option 2: From path
|
|
448
|
+
|
|
449
|
+
**Production (compiled)** — run `npm run build` first, then use `node`:
|
|
450
|
+
|
|
451
|
+
```json
|
|
452
|
+
{
|
|
453
|
+
"mcpServers": {
|
|
454
|
+
"agent-task-manager-mcp": {
|
|
455
|
+
"command": "node",
|
|
456
|
+
"args": ["C:/path/to/agent-task-manager-mcp/dist/index.js"],
|
|
457
|
+
"env": {
|
|
458
|
+
"MONGODB_URI": "mongodb://localhost:27017/agent-tasks"
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
334
464
|
|
|
335
|
-
|
|
465
|
+
**Development (source)** — no build; uses `tsx` to run TypeScript directly:
|
|
336
466
|
|
|
337
467
|
```json
|
|
338
468
|
{
|
|
@@ -350,6 +480,39 @@ Add to Cursor MCP settings (or `.cursor/mcp.json`):
|
|
|
350
480
|
|
|
351
481
|
---
|
|
352
482
|
|
|
483
|
+
### Option 3: HTTP/HTTPS mode (ChatGPT Desktop, ngrok)
|
|
484
|
+
|
|
485
|
+
MCP Server URL requires an HTTP(S) endpoint. Supports HTTP, HTTPS, self-signed, mkcert, and user-provided certs.
|
|
486
|
+
|
|
487
|
+
**HTTP (with ngrok):**
|
|
488
|
+
```bash
|
|
489
|
+
agent-task-manager-mcp --http
|
|
490
|
+
ngrok http 8000
|
|
491
|
+
# Connector URL: https://<subdomain>.ngrok.app/mcp
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**HTTPS (local, no ngrok):**
|
|
495
|
+
```bash
|
|
496
|
+
# Auto-generated self-signed cert
|
|
497
|
+
agent-task-manager-mcp --https
|
|
498
|
+
# Connector URL: https://localhost:8443/mcp
|
|
499
|
+
|
|
500
|
+
# With mkcert (locally trusted)
|
|
501
|
+
mkcert -install && mkcert localhost 127.0.0.1
|
|
502
|
+
agent-task-manager-mcp --https --cert-key=./localhost+1.pem
|
|
503
|
+
|
|
504
|
+
# With your own cert and key
|
|
505
|
+
agent-task-manager-mcp --https --cert=./cert.pem --key=./key.pem
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
**Host validation:** localhost, 127.0.0.1, ngrok domains (`*.ngrok-free.app`, `*.ngrok.app`), and `MCP_ALLOWED_HOSTS` (comma-separated env var).
|
|
509
|
+
|
|
510
|
+
**ChatGPT Desktop** — Settings → Apps & Connectors → Create:
|
|
511
|
+
- **Connector URL:** `https://localhost:8443/mcp` (HTTPS) or `https://<ngrok-subdomain>.ngrok.app/mcp` (HTTP + ngrok)
|
|
512
|
+
- **Authentication:** None
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
353
516
|
## Tool Reference
|
|
354
517
|
|
|
355
518
|
### Task Tools (7)
|
|
@@ -433,7 +596,8 @@ Checkpoint
|
|
|
433
596
|
|
|
434
597
|
| Issue | Check |
|
|
435
598
|
| ----------------------------- | ------------------------------------------------------------------------ |
|
|
436
|
-
| `MONGODB_URI is not set` |
|
|
599
|
+
| `MONGODB_URI is not set` | When `STORAGE=mongodb`; or switch to `STORAGE=sqlite` or `STORAGE=json` |
|
|
600
|
+
| `Unknown STORAGE type` | Use `mongodb`, `sqlite`, or `json` |
|
|
437
601
|
| Connection refused | MongoDB running? Correct host/port? |
|
|
438
602
|
| Auth failed | Verify username/password; URL-encode special chars |
|
|
439
603
|
| Tool returns `success: false` | Inspect `error` field in JSON response |
|
|
@@ -447,7 +611,8 @@ Checkpoint
|
|
|
447
611
|
agent-task-manager-mcp/
|
|
448
612
|
├── src/
|
|
449
613
|
│ ├── index.ts # MCP server entry point
|
|
450
|
-
│ ├── db.ts # MongoDB connection
|
|
614
|
+
│ ├── db.ts # MongoDB connection (when STORAGE=mongodb)
|
|
615
|
+
│ ├── storage/ # Storage abstraction (MongoDB, SQLite, JSON)
|
|
451
616
|
│ ├── models/
|
|
452
617
|
│ │ ├── Task.ts
|
|
453
618
|
│ │ ├── Subtask.ts
|
package/dist/certs.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type CertKeyPair = {
|
|
2
|
+
cert: string;
|
|
3
|
+
key: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const loadCertKey: (opts: {
|
|
6
|
+
cert?: string;
|
|
7
|
+
key?: string;
|
|
8
|
+
certKey?: string;
|
|
9
|
+
}) => CertKeyPair;
|
|
10
|
+
export declare const generateSelfSigned: () => Promise<CertKeyPair>;
|
|
11
|
+
//# sourceMappingURL=certs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certs.d.ts","sourceRoot":"","sources":["../src/certs.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAkBvD,eAAO,MAAM,WAAW,GAAI,MAAM;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,KAAG,WAmBH,CAAA;AAED,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,WAAW,CAU9D,CAAA"}
|
package/dist/certs.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateSelfSigned = exports.loadCertKey = void 0;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_fs_2 = require("node:fs");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const selfsigned_1 = __importDefault(require("selfsigned"));
|
|
11
|
+
const PEM_PRIVATE_KEY = /-----BEGIN\s+(?:(?:RSA|EC)\s+)?PRIVATE KEY-----[\s\S]+?-----END\s+(?:(?:RSA|EC)\s+)?PRIVATE KEY-----/;
|
|
12
|
+
const PEM_CERTIFICATE = /-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/;
|
|
13
|
+
const loadFile = (path) => {
|
|
14
|
+
const resolved = (0, node_path_1.resolve)(path);
|
|
15
|
+
if (!(0, node_fs_2.existsSync)(resolved))
|
|
16
|
+
throw new Error(`File not found: ${resolved}`);
|
|
17
|
+
return (0, node_fs_1.readFileSync)(resolved, 'utf8');
|
|
18
|
+
};
|
|
19
|
+
const parseCombinedPem = (content) => {
|
|
20
|
+
const keyMatch = content.match(PEM_PRIVATE_KEY);
|
|
21
|
+
const certMatch = content.match(PEM_CERTIFICATE);
|
|
22
|
+
if (keyMatch && certMatch)
|
|
23
|
+
return { key: keyMatch[0], cert: certMatch[0] };
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
26
|
+
const loadCertKey = (opts) => {
|
|
27
|
+
const { cert, key, certKey } = opts;
|
|
28
|
+
if (cert && key) {
|
|
29
|
+
return { cert: loadFile(cert), key: loadFile(key) };
|
|
30
|
+
}
|
|
31
|
+
const path = certKey ?? cert;
|
|
32
|
+
if (path) {
|
|
33
|
+
const content = loadFile(path);
|
|
34
|
+
const parsed = parseCombinedPem(content);
|
|
35
|
+
if (parsed)
|
|
36
|
+
return parsed;
|
|
37
|
+
if (key)
|
|
38
|
+
return { cert: content, key: loadFile(key) };
|
|
39
|
+
throw new Error(`File ${path} must contain both PRIVATE KEY and CERTIFICATE PEM blocks, or use --key for separate key file`);
|
|
40
|
+
}
|
|
41
|
+
throw new Error('Certificate required for HTTPS. Use --cert, --key, or --cert-key');
|
|
42
|
+
};
|
|
43
|
+
exports.loadCertKey = loadCertKey;
|
|
44
|
+
const generateSelfSigned = async () => {
|
|
45
|
+
const attrs = [{ name: 'commonName', value: 'localhost' }];
|
|
46
|
+
const notAfter = new Date();
|
|
47
|
+
notAfter.setFullYear(notAfter.getFullYear() + 1);
|
|
48
|
+
const pems = await selfsigned_1.default.generate(attrs, {
|
|
49
|
+
keySize: 2048,
|
|
50
|
+
algorithm: 'sha256',
|
|
51
|
+
notAfterDate: notAfter,
|
|
52
|
+
});
|
|
53
|
+
return { cert: pems.cert, key: pems.private };
|
|
54
|
+
};
|
|
55
|
+
exports.generateSelfSigned = generateSelfSigned;
|
|
56
|
+
//# sourceMappingURL=certs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certs.js","sourceRoot":"","sources":["../src/certs.ts"],"names":[],"mappings":";;;;;;AAAA,qCAAsC;AACtC,qCAAoC;AACpC,yCAAmC;AACnC,4DAAmC;AAInC,MAAM,eAAe,GAAG,sGAAsG,CAAA;AAC9H,MAAM,eAAe,GAAG,8DAA8D,CAAA;AAEtF,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAU,EAAE;IACxC,MAAM,QAAQ,GAAG,IAAA,mBAAO,EAAC,IAAI,CAAC,CAAA;IAC9B,IAAI,CAAC,IAAA,oBAAU,EAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAA;IACzE,OAAO,IAAA,sBAAY,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;AACvC,CAAC,CAAA;AAED,MAAM,gBAAgB,GAAG,CAAC,OAAe,EAAsB,EAAE;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;IAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;IAChD,IAAI,QAAQ,IAAI,SAAS;QAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IAC1E,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAEM,MAAM,WAAW,GAAG,CAAC,IAI3B,EAAe,EAAE;IAChB,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAEnC,IAAI,IAAI,IAAI,GAAG,EAAE,CAAC;QAChB,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAA;IACrD,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,IAAI,IAAI,CAAA;IAC5B,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC9B,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;QACxC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAA;QACzB,IAAI,GAAG;YAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAA;QACrD,MAAM,IAAI,KAAK,CACb,QAAQ,IAAI,+FAA+F,CAC5G,CAAA;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAA;AACrF,CAAC,CAAA;AAvBY,QAAA,WAAW,eAuBvB;AAEM,MAAM,kBAAkB,GAAG,KAAK,IAA0B,EAAE;IACjE,MAAM,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;IAC1D,MAAM,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAA;IAC3B,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAA;IAChD,MAAM,IAAI,GAAG,MAAM,oBAAU,CAAC,QAAQ,CAAC,KAAK,EAAE;QAC5C,OAAO,EAAE,IAAI;QACb,SAAS,EAAE,QAAQ;QACnB,YAAY,EAAE,QAAQ;KACvB,CAAC,CAAA;IACF,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,CAAA;AAC/C,CAAC,CAAA;AAVY,QAAA,kBAAkB,sBAU9B"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type CliOptions = {
|
|
2
|
+
stdio: boolean;
|
|
3
|
+
http: boolean;
|
|
4
|
+
https: boolean;
|
|
5
|
+
port?: number;
|
|
6
|
+
cert?: string;
|
|
7
|
+
key?: string;
|
|
8
|
+
certKey?: string;
|
|
9
|
+
};
|
|
10
|
+
declare const DEFAULT_HTTP_PORT = 8000;
|
|
11
|
+
declare const DEFAULT_HTTPS_PORT = 8443;
|
|
12
|
+
export declare const parseCli: () => CliOptions;
|
|
13
|
+
export { DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT };
|
|
14
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,OAAO,CAAA;IACd,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,OAAO,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,QAAA,MAAM,iBAAiB,OAAO,CAAA;AAC9B,QAAA,MAAM,kBAAkB,OAAO,CAAA;AAE/B,eAAO,MAAM,QAAQ,QAAO,UAsB3B,CAAA;AAED,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,CAAA"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_HTTPS_PORT = exports.DEFAULT_HTTP_PORT = exports.parseCli = void 0;
|
|
4
|
+
const getArg = (name) => {
|
|
5
|
+
const arg = process.argv.find((a) => a.startsWith(`${name}=`));
|
|
6
|
+
return arg?.slice(name.length + 1);
|
|
7
|
+
};
|
|
8
|
+
const hasArg = (name) => process.argv.includes(name);
|
|
9
|
+
const DEFAULT_HTTP_PORT = 8000;
|
|
10
|
+
exports.DEFAULT_HTTP_PORT = DEFAULT_HTTP_PORT;
|
|
11
|
+
const DEFAULT_HTTPS_PORT = 8443;
|
|
12
|
+
exports.DEFAULT_HTTPS_PORT = DEFAULT_HTTPS_PORT;
|
|
13
|
+
const parseCli = () => {
|
|
14
|
+
const httpMode = hasArg('--http');
|
|
15
|
+
const httpsMode = hasArg('--https');
|
|
16
|
+
const portArg = getArg('--port');
|
|
17
|
+
const port = portArg ? parseInt(portArg, 10) : undefined;
|
|
18
|
+
const cert = getArg('--cert');
|
|
19
|
+
const key = getArg('--key');
|
|
20
|
+
const certKey = getArg('--cert-key');
|
|
21
|
+
const stdio = !httpMode && !httpsMode;
|
|
22
|
+
const http = httpMode || httpsMode;
|
|
23
|
+
const https = httpsMode;
|
|
24
|
+
return {
|
|
25
|
+
stdio,
|
|
26
|
+
http,
|
|
27
|
+
https,
|
|
28
|
+
port,
|
|
29
|
+
cert,
|
|
30
|
+
key,
|
|
31
|
+
certKey,
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
exports.parseCli = parseCli;
|
|
35
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;AAAA,MAAM,MAAM,GAAG,CAAC,IAAY,EAAsB,EAAE;IAClD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAA;IAC9D,OAAO,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AACpC,CAAC,CAAA;AAED,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;AAY5D,MAAM,iBAAiB,GAAG,IAAI,CAAA;AA2BrB,8CAAiB;AA1B1B,MAAM,kBAAkB,GAAG,IAAI,CAAA;AA0BH,gDAAkB;AAxBvC,MAAM,QAAQ,GAAG,GAAe,EAAE;IACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;IACjC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;IACnC,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;IAChC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACxD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,CAAA;IAC3B,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;IAEpC,MAAM,KAAK,GAAG,CAAC,QAAQ,IAAI,CAAC,SAAS,CAAA;IACrC,MAAM,IAAI,GAAG,QAAQ,IAAI,SAAS,CAAA;IAClC,MAAM,KAAK,GAAG,SAAS,CAAA;IAEvB,OAAO;QACL,KAAK;QACL,IAAI;QACJ,KAAK;QACL,IAAI;QACJ,IAAI;QACJ,GAAG;QACH,OAAO;KACR,CAAA;AACH,CAAC,CAAA;AAtBY,QAAA,QAAQ,YAsBpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostValidation.d.ts","sourceRoot":"","sources":["../src/hostValidation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAsB9D,eAAO,MAAM,wBAAwB,GACnC,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,MAAM,YAAY,SA+BnB,CAAA"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hostValidationMiddleware = void 0;
|
|
4
|
+
const LOCALHOST_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
5
|
+
const NGROK_SUFFIXES = ['.ngrok-free.app', '.ngrok.app', '.ngrok.io'];
|
|
6
|
+
const getAllowedHosts = () => {
|
|
7
|
+
const extra = process.env.MCP_ALLOWED_HOSTS;
|
|
8
|
+
if (!extra)
|
|
9
|
+
return LOCALHOST_HOSTS;
|
|
10
|
+
const hosts = new Set(LOCALHOST_HOSTS);
|
|
11
|
+
for (const h of extra.split(',').map((s) => s.trim()).filter(Boolean)) {
|
|
12
|
+
hosts.add(h);
|
|
13
|
+
}
|
|
14
|
+
return hosts;
|
|
15
|
+
};
|
|
16
|
+
const isHostAllowed = (hostname) => {
|
|
17
|
+
if (LOCALHOST_HOSTS.has(hostname))
|
|
18
|
+
return true;
|
|
19
|
+
if (NGROK_SUFFIXES.some((s) => hostname.endsWith(s)))
|
|
20
|
+
return true;
|
|
21
|
+
if (getAllowedHosts().has(hostname))
|
|
22
|
+
return true;
|
|
23
|
+
return false;
|
|
24
|
+
};
|
|
25
|
+
const hostValidationMiddleware = (req, res, next) => {
|
|
26
|
+
const hostHeader = req.headers.host;
|
|
27
|
+
if (!hostHeader) {
|
|
28
|
+
res.status(403).json({
|
|
29
|
+
jsonrpc: '2.0',
|
|
30
|
+
error: { code: -32000, message: 'Missing Host header' },
|
|
31
|
+
id: null,
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
let hostname;
|
|
36
|
+
try {
|
|
37
|
+
hostname = new URL(`http://${hostHeader}`).hostname;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
res.status(403).json({
|
|
41
|
+
jsonrpc: '2.0',
|
|
42
|
+
error: { code: -32000, message: `Invalid Host header: ${hostHeader}` },
|
|
43
|
+
id: null,
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!isHostAllowed(hostname)) {
|
|
48
|
+
res.status(403).json({
|
|
49
|
+
jsonrpc: '2.0',
|
|
50
|
+
error: { code: -32000, message: `Invalid Host: ${hostname}` },
|
|
51
|
+
id: null,
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
next();
|
|
56
|
+
};
|
|
57
|
+
exports.hostValidationMiddleware = hostValidationMiddleware;
|
|
58
|
+
//# sourceMappingURL=hostValidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostValidation.js","sourceRoot":"","sources":["../src/hostValidation.ts"],"names":[],"mappings":";;;AAEA,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAA;AACpE,MAAM,cAAc,GAAG,CAAC,iBAAiB,EAAE,YAAY,EAAE,WAAW,CAAC,CAAA;AAErE,MAAM,eAAe,GAAG,GAAgB,EAAE;IACxC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAA;IAC3C,IAAI,CAAC,KAAK;QAAE,OAAO,eAAe,CAAA;IAClC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC,CAAA;IACtC,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QACtE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IACd,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAED,MAAM,aAAa,GAAG,CAAC,QAAgB,EAAW,EAAE;IAClD,IAAI,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAA;IAC9C,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IACjE,IAAI,eAAe,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAA;IAChD,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAEM,MAAM,wBAAwB,GAAG,CACtC,GAAY,EACZ,GAAa,EACb,IAAkB,EAClB,EAAE;IACF,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAA;IACnC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,qBAAqB,EAAE;YACvD,EAAE,EAAE,IAAI;SACT,CAAC,CAAA;QACF,OAAM;IACR,CAAC;IACD,IAAI,QAAgB,CAAA;IACpB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,GAAG,CAAC,UAAU,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAA;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,wBAAwB,UAAU,EAAE,EAAE;YACtE,EAAE,EAAE,IAAI;SACT,CAAC,CAAA;QACF,OAAM;IACR,CAAC;IACD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,iBAAiB,QAAQ,EAAE,EAAE;YAC7D,EAAE,EAAE,IAAI;SACT,CAAC,CAAA;QACF,OAAM;IACR,CAAC;IACD,IAAI,EAAE,CAAA;AACR,CAAC,CAAA;AAlCY,QAAA,wBAAwB,4BAkCpC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare const DEFAULT_HTTP_PORT = 8000;
|
|
2
|
+
declare const DEFAULT_HTTPS_PORT = 8443;
|
|
3
|
+
declare const findAvailablePort: (startPort: number) => Promise<number>;
|
|
4
|
+
export type HttpServerOptions = {
|
|
5
|
+
port?: number;
|
|
6
|
+
https?: boolean;
|
|
7
|
+
cert?: string;
|
|
8
|
+
key?: string;
|
|
9
|
+
certKey?: string;
|
|
10
|
+
};
|
|
11
|
+
declare const runHttpServer: (opts?: HttpServerOptions) => Promise<void>;
|
|
12
|
+
export { findAvailablePort, runHttpServer, DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT };
|
|
13
|
+
//# 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":"AAaA,QAAA,MAAM,iBAAiB,OAAO,CAAA;AAC9B,QAAA,MAAM,kBAAkB,OAAO,CAAA;AAK/B,QAAA,MAAM,iBAAiB,GAAI,WAAW,MAAM,KAAG,OAAO,CAAC,MAAM,CAyBzD,CAAA;AAOJ,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AASD,QAAA,MAAM,aAAa,GAAU,OAAM,iBAAsB,kBAkJxD,CAAA;AAED,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,CAAA"}
|