@tronsfey/ucli-server 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +423 -0
- package/README.zh.md +404 -0
- package/assets/logo.svg +138 -0
- package/dist/admin-ui/assets/index-BjdBylCY.css +1 -0
- package/dist/admin-ui/assets/index-CDwKIMTJ.js +117 -0
- package/dist/admin-ui/assets/remixicon-B25hvfAs.eot +0 -0
- package/dist/admin-ui/assets/remixicon-BTtOSOPh.svg +9709 -0
- package/dist/admin-ui/assets/remixicon-CZw4FkzQ.woff2 +0 -0
- package/dist/admin-ui/assets/remixicon-S6an_USy.woff +0 -0
- package/dist/admin-ui/assets/remixicon-sqouR8Ox.ttf +0 -0
- package/dist/admin-ui/dist/assets/index-BjdBylCY.css +1 -0
- package/dist/admin-ui/dist/assets/index-CDwKIMTJ.js +117 -0
- package/dist/admin-ui/dist/assets/index-CppEl63e.css +1 -0
- package/dist/admin-ui/dist/assets/index-DBkeGfMQ.js +117 -0
- package/dist/admin-ui/dist/assets/index-ss5EmsBH.js +117 -0
- package/dist/admin-ui/dist/assets/remixicon-B25hvfAs.eot +0 -0
- package/dist/admin-ui/dist/assets/remixicon-BTtOSOPh.svg +9709 -0
- package/dist/admin-ui/dist/assets/remixicon-CZw4FkzQ.woff2 +0 -0
- package/dist/admin-ui/dist/assets/remixicon-S6an_USy.woff +0 -0
- package/dist/admin-ui/dist/assets/remixicon-sqouR8Ox.ttf +0 -0
- package/dist/admin-ui/dist/index.html +14 -0
- package/dist/admin-ui/index.html +14 -0
- package/dist/app.module.d.ts +3 -0
- package/dist/app.module.d.ts.map +1 -0
- package/dist/app.module.js +65 -0
- package/dist/app.module.js.map +1 -0
- package/dist/auth/admin.guard.d.ts +8 -0
- package/dist/auth/admin.guard.d.ts.map +1 -0
- package/dist/auth/admin.guard.js +34 -0
- package/dist/auth/admin.guard.js.map +1 -0
- package/dist/auth/auth.module.d.ts +3 -0
- package/dist/auth/auth.module.d.ts.map +1 -0
- package/dist/auth/auth.module.js +24 -0
- package/dist/auth/auth.module.js.map +1 -0
- package/dist/auth/decorators/jwt-payload.decorator.d.ts +2 -0
- package/dist/auth/decorators/jwt-payload.decorator.d.ts.map +1 -0
- package/dist/auth/decorators/jwt-payload.decorator.js +10 -0
- package/dist/auth/decorators/jwt-payload.decorator.js.map +1 -0
- package/dist/auth/group-token.guard.d.ts +11 -0
- package/dist/auth/group-token.guard.d.ts.map +1 -0
- package/dist/auth/group-token.guard.js +53 -0
- package/dist/auth/group-token.guard.js.map +1 -0
- package/dist/cache/cache.interface.d.ts +8 -0
- package/dist/cache/cache.interface.d.ts.map +1 -0
- package/dist/cache/cache.interface.js +3 -0
- package/dist/cache/cache.interface.js.map +1 -0
- package/dist/cache/cache.module.d.ts +5 -0
- package/dist/cache/cache.module.d.ts.map +1 -0
- package/dist/cache/cache.module.js +56 -0
- package/dist/cache/cache.module.js.map +1 -0
- package/dist/cache/cache.token.d.ts +2 -0
- package/dist/cache/cache.token.d.ts.map +1 -0
- package/dist/cache/cache.token.js +5 -0
- package/dist/cache/cache.token.js.map +1 -0
- package/dist/cache/memory/memory-cache.adapter.d.ts +15 -0
- package/dist/cache/memory/memory-cache.adapter.d.ts.map +1 -0
- package/dist/cache/memory/memory-cache.adapter.js +52 -0
- package/dist/cache/memory/memory-cache.adapter.js.map +1 -0
- package/dist/cache/redis/redis-cache.adapter.d.ts +16 -0
- package/dist/cache/redis/redis-cache.adapter.d.ts.map +1 -0
- package/dist/cache/redis/redis-cache.adapter.js +63 -0
- package/dist/cache/redis/redis-cache.adapter.js.map +1 -0
- package/dist/config/app-config.module.d.ts +3 -0
- package/dist/config/app-config.module.d.ts.map +1 -0
- package/dist/config/app-config.module.js +100 -0
- package/dist/config/app-config.module.js.map +1 -0
- package/dist/config/app-config.service.d.ts +29 -0
- package/dist/config/app-config.service.d.ts.map +1 -0
- package/dist/config/app-config.service.js +66 -0
- package/dist/config/app-config.service.js.map +1 -0
- package/dist/crypto/crypto.module.d.ts +3 -0
- package/dist/crypto/crypto.module.d.ts.map +1 -0
- package/dist/crypto/crypto.module.js +22 -0
- package/dist/crypto/crypto.module.js.map +1 -0
- package/dist/crypto/encryption.service.d.ts +9 -0
- package/dist/crypto/encryption.service.d.ts.map +1 -0
- package/dist/crypto/encryption.service.js +59 -0
- package/dist/crypto/encryption.service.js.map +1 -0
- package/dist/crypto/jwt.service.d.ts +28 -0
- package/dist/crypto/jwt.service.d.ts.map +1 -0
- package/dist/crypto/jwt.service.js +72 -0
- package/dist/crypto/jwt.service.js.map +1 -0
- package/dist/groups/dto/create-group.dto.d.ts +5 -0
- package/dist/groups/dto/create-group.dto.d.ts.map +1 -0
- package/dist/groups/dto/create-group.dto.js +34 -0
- package/dist/groups/dto/create-group.dto.js.map +1 -0
- package/dist/groups/groups.controller.d.ts +9 -0
- package/dist/groups/groups.controller.d.ts.map +1 -0
- package/dist/groups/groups.controller.js +60 -0
- package/dist/groups/groups.controller.js.map +1 -0
- package/dist/groups/groups.module.d.ts +3 -0
- package/dist/groups/groups.module.d.ts.map +1 -0
- package/dist/groups/groups.module.js +25 -0
- package/dist/groups/groups.module.js.map +1 -0
- package/dist/groups/groups.service.d.ts +9 -0
- package/dist/groups/groups.service.d.ts.map +1 -0
- package/dist/groups/groups.service.js +45 -0
- package/dist/groups/groups.service.js.map +1 -0
- package/dist/health/health.controller.d.ts +13 -0
- package/dist/health/health.controller.d.ts.map +1 -0
- package/dist/health/health.controller.js +68 -0
- package/dist/health/health.controller.js.map +1 -0
- package/dist/health/health.module.d.ts +3 -0
- package/dist/health/health.module.d.ts.map +1 -0
- package/dist/health/health.module.js +22 -0
- package/dist/health/health.module.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +109 -0
- package/dist/main.js.map +1 -0
- package/dist/mcp/admin-mcp.controller.d.ts +13 -0
- package/dist/mcp/admin-mcp.controller.d.ts.map +1 -0
- package/dist/mcp/admin-mcp.controller.js +105 -0
- package/dist/mcp/admin-mcp.controller.js.map +1 -0
- package/dist/mcp/client-mcp.controller.d.ts +9 -0
- package/dist/mcp/client-mcp.controller.d.ts.map +1 -0
- package/dist/mcp/client-mcp.controller.js +63 -0
- package/dist/mcp/client-mcp.controller.js.map +1 -0
- package/dist/mcp/dto/create-mcp.dto.d.ts +11 -0
- package/dist/mcp/dto/create-mcp.dto.d.ts.map +1 -0
- package/dist/mcp/dto/create-mcp.dto.js +74 -0
- package/dist/mcp/dto/create-mcp.dto.js.map +1 -0
- package/dist/mcp/dto/update-mcp.dto.d.ts +11 -0
- package/dist/mcp/dto/update-mcp.dto.d.ts.map +1 -0
- package/dist/mcp/dto/update-mcp.dto.js +71 -0
- package/dist/mcp/dto/update-mcp.dto.js.map +1 -0
- package/dist/mcp/mcp.module.d.ts +3 -0
- package/dist/mcp/mcp.module.d.ts.map +1 -0
- package/dist/mcp/mcp.module.js +26 -0
- package/dist/mcp/mcp.module.js.map +1 -0
- package/dist/mcp/mcp.service.d.ts +16 -0
- package/dist/mcp/mcp.service.d.ts.map +1 -0
- package/dist/mcp/mcp.service.js +78 -0
- package/dist/mcp/mcp.service.js.map +1 -0
- package/dist/metrics/metrics.controller.d.ts +12 -0
- package/dist/metrics/metrics.controller.d.ts.map +1 -0
- package/dist/metrics/metrics.controller.js +71 -0
- package/dist/metrics/metrics.controller.js.map +1 -0
- package/dist/metrics/metrics.module.d.ts +3 -0
- package/dist/metrics/metrics.module.d.ts.map +1 -0
- package/dist/metrics/metrics.module.js +20 -0
- package/dist/metrics/metrics.module.js.map +1 -0
- package/dist/oas/admin-oas.controller.d.ts +12 -0
- package/dist/oas/admin-oas.controller.d.ts.map +1 -0
- package/dist/oas/admin-oas.controller.js +96 -0
- package/dist/oas/admin-oas.controller.js.map +1 -0
- package/dist/oas/client-oas.controller.d.ts +9 -0
- package/dist/oas/client-oas.controller.d.ts.map +1 -0
- package/dist/oas/client-oas.controller.js +63 -0
- package/dist/oas/client-oas.controller.js.map +1 -0
- package/dist/oas/dto/create-oas.dto.d.ts +12 -0
- package/dist/oas/dto/create-oas.dto.d.ts.map +1 -0
- package/dist/oas/dto/create-oas.dto.js +82 -0
- package/dist/oas/dto/create-oas.dto.js.map +1 -0
- package/dist/oas/dto/update-oas.dto.d.ts +12 -0
- package/dist/oas/dto/update-oas.dto.d.ts.map +1 -0
- package/dist/oas/dto/update-oas.dto.js +77 -0
- package/dist/oas/dto/update-oas.dto.js.map +1 -0
- package/dist/oas/oas.module.d.ts +3 -0
- package/dist/oas/oas.module.d.ts.map +1 -0
- package/dist/oas/oas.module.js +26 -0
- package/dist/oas/oas.module.js.map +1 -0
- package/dist/oas/oas.service.d.ts +16 -0
- package/dist/oas/oas.service.d.ts.map +1 -0
- package/dist/oas/oas.service.js +79 -0
- package/dist/oas/oas.service.js.map +1 -0
- package/dist/otel/otel.d.ts +25 -0
- package/dist/otel/otel.d.ts.map +1 -0
- package/dist/otel/otel.js +61 -0
- package/dist/otel/otel.js.map +1 -0
- package/dist/storage/interfaces/repos.interface.d.ts +158 -0
- package/dist/storage/interfaces/repos.interface.d.ts.map +1 -0
- package/dist/storage/interfaces/repos.interface.js +4 -0
- package/dist/storage/interfaces/repos.interface.js.map +1 -0
- package/dist/storage/memory/memory-group.repo.d.ts +9 -0
- package/dist/storage/memory/memory-group.repo.d.ts.map +1 -0
- package/dist/storage/memory/memory-group.repo.js +33 -0
- package/dist/storage/memory/memory-group.repo.js.map +1 -0
- package/dist/storage/memory/memory-mcp.repo.d.ts +13 -0
- package/dist/storage/memory/memory-mcp.repo.d.ts.map +1 -0
- package/dist/storage/memory/memory-mcp.repo.js +65 -0
- package/dist/storage/memory/memory-mcp.repo.js.map +1 -0
- package/dist/storage/memory/memory-oas.repo.d.ts +13 -0
- package/dist/storage/memory/memory-oas.repo.d.ts.map +1 -0
- package/dist/storage/memory/memory-oas.repo.js +55 -0
- package/dist/storage/memory/memory-oas.repo.js.map +1 -0
- package/dist/storage/memory/memory-token.repo.d.ts +11 -0
- package/dist/storage/memory/memory-token.repo.d.ts.map +1 -0
- package/dist/storage/memory/memory-token.repo.js +39 -0
- package/dist/storage/memory/memory-token.repo.js.map +1 -0
- package/dist/storage/storage.module.d.ts +5 -0
- package/dist/storage/storage.module.d.ts.map +1 -0
- package/dist/storage/storage.module.js +84 -0
- package/dist/storage/storage.module.js.map +1 -0
- package/dist/storage/storage.tokens.d.ts +5 -0
- package/dist/storage/storage.tokens.d.ts.map +1 -0
- package/dist/storage/storage.tokens.js +8 -0
- package/dist/storage/storage.tokens.js.map +1 -0
- package/dist/storage/typeorm/entities/group.entity.d.ts +12 -0
- package/dist/storage/typeorm/entities/group.entity.d.ts.map +1 -0
- package/dist/storage/typeorm/entities/group.entity.js +57 -0
- package/dist/storage/typeorm/entities/group.entity.js.map +1 -0
- package/dist/storage/typeorm/entities/mcp-entry.entity.d.ts +16 -0
- package/dist/storage/typeorm/entities/mcp-entry.entity.d.ts.map +1 -0
- package/dist/storage/typeorm/entities/mcp-entry.entity.js +82 -0
- package/dist/storage/typeorm/entities/mcp-entry.entity.js.map +1 -0
- package/dist/storage/typeorm/entities/oas-entry.entity.d.ts +17 -0
- package/dist/storage/typeorm/entities/oas-entry.entity.d.ts.map +1 -0
- package/dist/storage/typeorm/entities/oas-entry.entity.js +87 -0
- package/dist/storage/typeorm/entities/oas-entry.entity.js.map +1 -0
- package/dist/storage/typeorm/entities/token.entity.d.ts +13 -0
- package/dist/storage/typeorm/entities/token.entity.d.ts.map +1 -0
- package/dist/storage/typeorm/entities/token.entity.js +68 -0
- package/dist/storage/typeorm/entities/token.entity.js.map +1 -0
- package/dist/storage/typeorm/typeorm-group.repo.d.ts +12 -0
- package/dist/storage/typeorm/typeorm-group.repo.d.ts.map +1 -0
- package/dist/storage/typeorm/typeorm-group.repo.js +50 -0
- package/dist/storage/typeorm/typeorm-group.repo.js.map +1 -0
- package/dist/storage/typeorm/typeorm-mcp.repo.d.ts +15 -0
- package/dist/storage/typeorm/typeorm-mcp.repo.d.ts.map +1 -0
- package/dist/storage/typeorm/typeorm-mcp.repo.js +88 -0
- package/dist/storage/typeorm/typeorm-mcp.repo.js.map +1 -0
- package/dist/storage/typeorm/typeorm-oas.repo.d.ts +15 -0
- package/dist/storage/typeorm/typeorm-oas.repo.d.ts.map +1 -0
- package/dist/storage/typeorm/typeorm-oas.repo.js +92 -0
- package/dist/storage/typeorm/typeorm-oas.repo.js.map +1 -0
- package/dist/storage/typeorm/typeorm-token.repo.d.ts +13 -0
- package/dist/storage/typeorm/typeorm-token.repo.d.ts.map +1 -0
- package/dist/storage/typeorm/typeorm-token.repo.js +56 -0
- package/dist/storage/typeorm/typeorm-token.repo.js.map +1 -0
- package/dist/tokens/dto/issue-token.dto.d.ts +6 -0
- package/dist/tokens/dto/issue-token.dto.d.ts.map +1 -0
- package/dist/tokens/dto/issue-token.dto.js +41 -0
- package/dist/tokens/dto/issue-token.dto.js.map +1 -0
- package/dist/tokens/tokens.controller.d.ts +12 -0
- package/dist/tokens/tokens.controller.d.ts.map +1 -0
- package/dist/tokens/tokens.controller.js +89 -0
- package/dist/tokens/tokens.controller.js.map +1 -0
- package/dist/tokens/tokens.module.d.ts +3 -0
- package/dist/tokens/tokens.module.d.ts.map +1 -0
- package/dist/tokens/tokens.module.js +27 -0
- package/dist/tokens/tokens.module.js.map +1 -0
- package/dist/tokens/tokens.service.d.ts +25 -0
- package/dist/tokens/tokens.service.d.ts.map +1 -0
- package/dist/tokens/tokens.service.js +69 -0
- package/dist/tokens/tokens.service.js.map +1 -0
- package/package.json +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
<h1 align="center">ucli server</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://www.npmjs.com/package/@tronsfey/ucli-server"><img src="https://img.shields.io/npm/v/@tronsfey/ucli-server?color=7c3aed" alt="npm version"/></a>
|
|
5
|
+
<img src="https://img.shields.io/badge/NestJS-v11-e0234e" alt="NestJS"/>
|
|
6
|
+
<img src="https://img.shields.io/badge/node-%3E%3D18-38bdf8" alt="node"/>
|
|
7
|
+
<img src="https://img.shields.io/badge/license-MIT-22c55e" alt="license"/>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
English | <a href="./README.zh.md">中文</a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
`@tronsfey/ucli-server` is the server component of ucli. It provides:
|
|
19
|
+
|
|
20
|
+
- **Encrypted OAS storage** — OpenAPI specs with auth configs encrypted at rest (AES-256-GCM)
|
|
21
|
+
- **Encrypted MCP server storage** — MCP server configs with auth (none / http_headers / env) encrypted at rest
|
|
22
|
+
- **Group-scoped JWT issuance** — RS256-signed tokens that control which specs a client can access
|
|
23
|
+
- **Token revocation** — blacklist via cache (JTI-based)
|
|
24
|
+
- **Pluggable backends** — swap storage (memory / PostgreSQL / MySQL) and cache (memory / Redis) via env vars
|
|
25
|
+
- **Observability** — structured JSON logging (Pino), Prometheus metrics, health/readiness probes
|
|
26
|
+
- **Distributed tracing** — OpenTelemetry auto-instrumentation (HTTP, Express, PG, Redis) enabled by default
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
|
|
30
|
+
```mermaid
|
|
31
|
+
graph TB
|
|
32
|
+
subgraph AppModule["AppModule"]
|
|
33
|
+
direction TB
|
|
34
|
+
Config["ConfigModule\n(Joi-validated env)"]
|
|
35
|
+
Auth["AuthModule\nAdminGuard · GroupTokenGuard"]
|
|
36
|
+
Crypto["CryptoModule\nJwtService (RS256)\nEncryptionService (AES-256-GCM)"]
|
|
37
|
+
|
|
38
|
+
subgraph Storage["StorageModule.forRoot()"]
|
|
39
|
+
Mem1["MemoryGroupRepo\nMemoryTokenRepo\nMemoryOASRepo\nMemoryMCPRepo"]
|
|
40
|
+
DB1["TypeORM repos\n(postgres · mysql)"]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
subgraph Cache["CacheModule.forRoot()"]
|
|
44
|
+
Mem2["MemoryCacheAdapter"]
|
|
45
|
+
Redis["RedisCacheAdapter\n(ioredis)"]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Groups["GroupsModule\nPOST/GET /admin/groups"]
|
|
49
|
+
Tokens["TokensModule\nPOST /admin/groups/:id/tokens\nDELETE /admin/tokens/:id"]
|
|
50
|
+
OAS["OASModule\nAdmin CRUD + Client read"]
|
|
51
|
+
MCP["MCPModule\nAdmin CRUD + Client read"]
|
|
52
|
+
Health["HealthModule\n/api/v1/health · /api/v1/ready"]
|
|
53
|
+
Metrics["MetricsModule\nGET /metrics (Prometheus)"]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
Config --> Auth
|
|
57
|
+
Config --> Storage
|
|
58
|
+
Config --> Cache
|
|
59
|
+
Crypto --> Tokens
|
|
60
|
+
Crypto --> OAS
|
|
61
|
+
Storage --> Groups
|
|
62
|
+
Storage --> Tokens
|
|
63
|
+
Storage --> OAS
|
|
64
|
+
Storage --> MCP
|
|
65
|
+
Cache --> Auth
|
|
66
|
+
Groups --> Tokens
|
|
67
|
+
Groups --> OAS
|
|
68
|
+
Crypto --> MCP
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm install -g @tronsfey/ucli-server
|
|
75
|
+
# or
|
|
76
|
+
pnpm add -g @tronsfey/ucli-server
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Quick Start (memory mode — no DB/Redis required)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# 1. Generate a 32-byte encryption key
|
|
83
|
+
ENCRYPTION_KEY=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
|
|
84
|
+
|
|
85
|
+
# 2. Start the server
|
|
86
|
+
ADMIN_SECRET=my-secret ENCRYPTION_KEY=$ENCRYPTION_KEY ucli-server
|
|
87
|
+
|
|
88
|
+
# Server starts on http://localhost:3000
|
|
89
|
+
# Swagger UI: http://localhost:3000/api/docs
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Environment Variables
|
|
93
|
+
|
|
94
|
+
| Variable | Required | Default | Description |
|
|
95
|
+
|----------|----------|---------|-------------|
|
|
96
|
+
| `ADMIN_SECRET` | **Yes** | — | Secret for `X-Admin-Secret` header (≥ 8 chars) |
|
|
97
|
+
| `ENCRYPTION_KEY` | **Yes** | — | 64-char hex (32 bytes) for AES-256-GCM |
|
|
98
|
+
| `PORT` | No | `3000` | HTTP listen port |
|
|
99
|
+
| `HOST` | No | `0.0.0.0` | HTTP listen host |
|
|
100
|
+
| `DB_TYPE` | No | `memory` | `memory` \| `postgres` \| `mysql` |
|
|
101
|
+
| `DATABASE_URL` | If DB | — | PostgreSQL or MySQL connection URL |
|
|
102
|
+
| `CACHE_TYPE` | No | `memory` | `memory` \| `redis` |
|
|
103
|
+
| `REDIS_URL` | If redis | — | Redis connection URL |
|
|
104
|
+
| `JWT_PRIVATE_KEY` | Prod | auto-gen | Base64-encoded PKCS8 PEM |
|
|
105
|
+
| `JWT_PUBLIC_KEY` | Prod | auto-gen | Base64-encoded SPKI PEM |
|
|
106
|
+
| `JWT_DEFAULT_TTL` | No | `86400` | Token TTL in seconds (`0` = no expiry) |
|
|
107
|
+
| `LOG_LEVEL` | No | `info` | `trace` \| `debug` \| `info` \| `warn` \| `error` \| `fatal` |
|
|
108
|
+
| `SWAGGER_ENABLED` | No | `true` | Set `false` to disable `/api/docs` in production |
|
|
109
|
+
| `OTEL_ENABLED` | No | `true` | Set `false` to disable OpenTelemetry tracing |
|
|
110
|
+
| `OTEL_SERVICE_NAME` | No | `ucli-server` | Service name on all trace spans |
|
|
111
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | No | — | OTLP collector URL; unset = no-op exporter |
|
|
112
|
+
| `ADMIN_UI_PATH` | No | auto | Override path to admin dashboard static files |
|
|
113
|
+
|
|
114
|
+
## Storage Backends
|
|
115
|
+
|
|
116
|
+
| `DB_TYPE` | Driver | Notes |
|
|
117
|
+
|-----------|--------|-------|
|
|
118
|
+
| `memory` | — | Default. No persistence. Data lost on restart. |
|
|
119
|
+
| `postgres` | `pg` | PostgreSQL 12+ |
|
|
120
|
+
| `mysql` | `mysql2` | MySQL 5.7+ / MariaDB 10.3+ |
|
|
121
|
+
|
|
122
|
+
Tables are auto-created on first run.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# PostgreSQL
|
|
126
|
+
DB_TYPE=postgres \
|
|
127
|
+
DATABASE_URL=postgresql://user:pass@host:5432/oas_gateway \
|
|
128
|
+
ADMIN_SECRET=secret ENCRYPTION_KEY=<64-hex> ucli-server
|
|
129
|
+
|
|
130
|
+
# MySQL
|
|
131
|
+
DB_TYPE=mysql \
|
|
132
|
+
DATABASE_URL=mysql://user:pass@host:3306/oas_gateway \
|
|
133
|
+
ADMIN_SECRET=secret ENCRYPTION_KEY=<64-hex> ucli-server
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Cache Backends
|
|
137
|
+
|
|
138
|
+
| `CACHE_TYPE` | Notes |
|
|
139
|
+
|--------------|-------|
|
|
140
|
+
| `memory` | Default. In-process TTL cache. Lost on restart. |
|
|
141
|
+
| `redis` | Redis 6+ via ioredis. Shared across multiple instances. |
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
CACHE_TYPE=redis REDIS_URL=redis://:password@host:6379 \
|
|
145
|
+
ADMIN_SECRET=secret ENCRYPTION_KEY=<64-hex> ucli-server
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Production Deployment
|
|
149
|
+
|
|
150
|
+
Generate persistent RS256 key pair so tokens survive restarts:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
node -e "
|
|
154
|
+
const { generateKeyPairSync } = require('crypto');
|
|
155
|
+
const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
|
|
156
|
+
console.log('JWT_PRIVATE_KEY=' + Buffer.from(privateKey.export({ type:'pkcs8', format:'pem' })).toString('base64'));
|
|
157
|
+
console.log('JWT_PUBLIC_KEY=' + Buffer.from(publicKey.export({ type:'spki', format:'pem' })).toString('base64'));
|
|
158
|
+
"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Using `docker-compose.yml` in the repo root to spin up PostgreSQL + Redis:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
docker-compose up -d
|
|
165
|
+
DB_TYPE=postgres CACHE_TYPE=redis \
|
|
166
|
+
DATABASE_URL=postgresql://oas_gateway:changeme@localhost:5432/oas_gateway \
|
|
167
|
+
REDIS_URL=redis://:changeme@localhost:6379 \
|
|
168
|
+
JWT_PRIVATE_KEY=<base64-pem> JWT_PUBLIC_KEY=<base64-pem> \
|
|
169
|
+
ADMIN_SECRET=<strong-secret> ENCRYPTION_KEY=<64-hex> \
|
|
170
|
+
ucli-server
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Admin API Reference
|
|
174
|
+
|
|
175
|
+
All admin endpoints require the `X-Admin-Secret: <ADMIN_SECRET>` header.
|
|
176
|
+
|
|
177
|
+
### Groups
|
|
178
|
+
|
|
179
|
+
| Method | Path | Description |
|
|
180
|
+
|--------|------|-------------|
|
|
181
|
+
| `POST` | `/admin/groups` | Create a group |
|
|
182
|
+
| `GET` | `/admin/groups` | List all groups |
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# Create group
|
|
186
|
+
curl -X POST http://localhost:3000/admin/groups \
|
|
187
|
+
-H "X-Admin-Secret: my-secret" \
|
|
188
|
+
-H "Content-Type: application/json" \
|
|
189
|
+
-d '{"name":"production","description":"Production agents group"}'
|
|
190
|
+
# → { "id": "uuid", "name": "production", "description": "..." }
|
|
191
|
+
|
|
192
|
+
# List groups
|
|
193
|
+
curl http://localhost:3000/admin/groups \
|
|
194
|
+
-H "X-Admin-Secret: my-secret"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Tokens
|
|
198
|
+
|
|
199
|
+
| Method | Path | Description |
|
|
200
|
+
|--------|------|-------------|
|
|
201
|
+
| `POST` | `/admin/groups/:id/tokens` | Issue a JWT for the group |
|
|
202
|
+
| `DELETE` | `/admin/tokens/:id` | Revoke a token |
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Issue token (save the returned JWT — shown once!)
|
|
206
|
+
curl -X POST http://localhost:3000/admin/groups/<group-id>/tokens \
|
|
207
|
+
-H "X-Admin-Secret: my-secret" \
|
|
208
|
+
-H "Content-Type: application/json" \
|
|
209
|
+
-d '{"name":"agent-token","ttlSec":86400}'
|
|
210
|
+
# → { "id": "jti-uuid", "token": "eyJ..." }
|
|
211
|
+
|
|
212
|
+
# Revoke token
|
|
213
|
+
curl -X DELETE http://localhost:3000/admin/tokens/<jti-uuid> \
|
|
214
|
+
-H "X-Admin-Secret: my-secret"
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### OAS Entries
|
|
218
|
+
|
|
219
|
+
| Method | Path | Description |
|
|
220
|
+
|--------|------|-------------|
|
|
221
|
+
| `POST` | `/admin/oas` | Register an OAS entry |
|
|
222
|
+
| `GET` | `/admin/oas` | List all OAS entries |
|
|
223
|
+
| `PUT` | `/admin/oas/:id` | Update an OAS entry |
|
|
224
|
+
| `DELETE` | `/admin/oas/:id` | Delete an OAS entry |
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
# Register
|
|
228
|
+
curl -X POST http://localhost:3000/admin/oas \
|
|
229
|
+
-H "X-Admin-Secret: my-secret" \
|
|
230
|
+
-H "Content-Type: application/json" \
|
|
231
|
+
-d '{
|
|
232
|
+
"groupId": "<group-id>",
|
|
233
|
+
"name": "payments",
|
|
234
|
+
"remoteUrl": "https://api.example.com/openapi.json",
|
|
235
|
+
"authType": "bearer",
|
|
236
|
+
"authConfig": {"type":"bearer","token":"<api-token>"},
|
|
237
|
+
"cacheTtl": 3600
|
|
238
|
+
}'
|
|
239
|
+
|
|
240
|
+
# List
|
|
241
|
+
curl http://localhost:3000/admin/oas \
|
|
242
|
+
-H "X-Admin-Secret: my-secret"
|
|
243
|
+
|
|
244
|
+
# Update
|
|
245
|
+
curl -X PUT http://localhost:3000/admin/oas/<oas-id> \
|
|
246
|
+
-H "X-Admin-Secret: my-secret" \
|
|
247
|
+
-H "Content-Type: application/json" \
|
|
248
|
+
-d '{"cacheTtl": 7200}'
|
|
249
|
+
|
|
250
|
+
# Delete
|
|
251
|
+
curl -X DELETE http://localhost:3000/admin/oas/<oas-id> \
|
|
252
|
+
-H "X-Admin-Secret: my-secret"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### MCP Servers
|
|
256
|
+
|
|
257
|
+
| Method | Path | Description |
|
|
258
|
+
|--------|------|-------------|
|
|
259
|
+
| `POST` | `/admin/mcp` | Register an MCP server |
|
|
260
|
+
| `GET` | `/admin/mcp` | List all MCP servers |
|
|
261
|
+
| `GET` | `/admin/mcp/:id` | Get a single MCP server |
|
|
262
|
+
| `PUT` | `/admin/mcp/:id` | Update an MCP server |
|
|
263
|
+
| `DELETE` | `/admin/mcp/:id` | Delete an MCP server |
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# Register (http transport + no auth)
|
|
267
|
+
curl -X POST http://localhost:3000/admin/mcp \
|
|
268
|
+
-H "X-Admin-Secret: my-secret" \
|
|
269
|
+
-H "Content-Type: application/json" \
|
|
270
|
+
-d '{
|
|
271
|
+
"groupId": "<group-id>",
|
|
272
|
+
"name": "weather",
|
|
273
|
+
"transport": "http",
|
|
274
|
+
"serverUrl": "https://weather.mcp.example.com/sse",
|
|
275
|
+
"authConfig": {"type":"none"}
|
|
276
|
+
}'
|
|
277
|
+
|
|
278
|
+
# Register (stdio transport + env auth)
|
|
279
|
+
curl -X POST http://localhost:3000/admin/mcp \
|
|
280
|
+
-H "X-Admin-Secret: my-secret" \
|
|
281
|
+
-H "Content-Type: application/json" \
|
|
282
|
+
-d '{
|
|
283
|
+
"groupId": "<group-id>",
|
|
284
|
+
"name": "local-tools",
|
|
285
|
+
"transport": "stdio",
|
|
286
|
+
"command": "npx -y @myorg/mcp-tools",
|
|
287
|
+
"authConfig": {"type":"env","env":{"API_KEY":"<secret>","REGION":"us-east-1"}}
|
|
288
|
+
}'
|
|
289
|
+
|
|
290
|
+
# List
|
|
291
|
+
curl http://localhost:3000/admin/mcp \
|
|
292
|
+
-H "X-Admin-Secret: my-secret"
|
|
293
|
+
|
|
294
|
+
# Update
|
|
295
|
+
curl -X PUT http://localhost:3000/admin/mcp/<mcp-id> \
|
|
296
|
+
-H "X-Admin-Secret: my-secret" \
|
|
297
|
+
-H "Content-Type: application/json" \
|
|
298
|
+
-d '{"description": "Updated description"}'
|
|
299
|
+
|
|
300
|
+
# Delete
|
|
301
|
+
curl -X DELETE http://localhost:3000/admin/mcp/<mcp-id> \
|
|
302
|
+
-H "X-Admin-Secret: my-secret"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Client API Reference
|
|
308
|
+
|
|
309
|
+
Client endpoints require `Authorization: Bearer <group-jwt>`.
|
|
310
|
+
|
|
311
|
+
| Method | Path | Description |
|
|
312
|
+
|--------|------|-------------|
|
|
313
|
+
| `GET` | `/api/v1/oas` | List OAS entries visible to the token's group |
|
|
314
|
+
| `GET` | `/api/v1/oas/:name` | Get a single OAS entry with decrypted auth |
|
|
315
|
+
| `GET` | `/api/v1/mcp` | List MCP servers for the token's group (decrypted auth) |
|
|
316
|
+
| `GET` | `/api/v1/mcp/:name` | Get a single MCP server with decrypted auth |
|
|
317
|
+
|
|
318
|
+
## Auth Types
|
|
319
|
+
|
|
320
|
+
| `authType` | `authConfig` shape |
|
|
321
|
+
|------------|-------------------|
|
|
322
|
+
| `none` | `{ "type": "none" }` |
|
|
323
|
+
| `bearer` | `{ "type": "bearer", "token": "..." }` |
|
|
324
|
+
| `api_key` | `{ "type": "api_key", "key": "...", "in": "header\|query", "name": "X-API-Key" }` |
|
|
325
|
+
| `basic` | `{ "type": "basic", "username": "...", "password": "..." }` |
|
|
326
|
+
| `oauth2_cc` | `{ "type": "oauth2_cc", "tokenUrl": "...", "clientId": "...", "clientSecret": "...", "scopes": [] }` |
|
|
327
|
+
|
|
328
|
+
Auth configs are encrypted with AES-256-GCM before storage. They are decrypted in-memory only at request time.
|
|
329
|
+
|
|
330
|
+
## MCP Auth Types
|
|
331
|
+
|
|
332
|
+
| `authConfig.type` | Shape |
|
|
333
|
+
|--------------------|-------|
|
|
334
|
+
| `none` | `{ "type": "none" }` |
|
|
335
|
+
| `http_headers` | `{ "type": "http_headers", "headers": { "Authorization": "Bearer ..." } }` |
|
|
336
|
+
| `env` | `{ "type": "env", "env": { "API_KEY": "..." } }` (stdio transport) |
|
|
337
|
+
|
|
338
|
+
MCP auth configs follow the same encryption model as OAS auth configs: AES-256-GCM at rest, decrypted in-memory at request time.
|
|
339
|
+
|
|
340
|
+
## OpenTelemetry Tracing
|
|
341
|
+
|
|
342
|
+
Distributed tracing is **enabled by default**. The OTEL SDK auto-instruments HTTP, Express, PostgreSQL, and Redis without any code changes.
|
|
343
|
+
|
|
344
|
+
### How it coexists with Prometheus
|
|
345
|
+
|
|
346
|
+
| Concern | Technology | Endpoint |
|
|
347
|
+
|---------|-----------|---------|
|
|
348
|
+
| Metrics (scraping) | `prom-client` | `GET /metrics` |
|
|
349
|
+
| Distributed traces | OpenTelemetry | OTLP push to collector |
|
|
350
|
+
|
|
351
|
+
They are independent — no conflict. Prometheus scrapes `/metrics` as usual; OTEL exports spans to your collector via OTLP.
|
|
352
|
+
|
|
353
|
+
### OTEL environment variables
|
|
354
|
+
|
|
355
|
+
| Variable | Default | Description |
|
|
356
|
+
|----------|---------|-------------|
|
|
357
|
+
| `OTEL_ENABLED` | `true` | Set `false` to disable entirely |
|
|
358
|
+
| `OTEL_SERVICE_NAME` | `ucli-server` | Service name tag on all spans |
|
|
359
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | — | Collector URL (e.g. `http://otel-collector:4318`). When unset, spans are discarded locally (no-op) |
|
|
360
|
+
| `OTEL_EXPORTER_OTLP_HEADERS` | — | Auth headers for the collector (e.g. `Authorization=Bearer token`) |
|
|
361
|
+
| `OTEL_PROPAGATORS` | `tracecontext,baggage` | W3C trace context propagation (standard) |
|
|
362
|
+
| `OTEL_TRACES_SAMPLER` | `parentbased_always_on` | Sampling strategy |
|
|
363
|
+
|
|
364
|
+
### Quick setup with a local collector (docker)
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# Start Jaeger all-in-one (OTLP + UI)
|
|
368
|
+
docker run -d --name jaeger \
|
|
369
|
+
-p 4318:4318 \
|
|
370
|
+
-p 16686:16686 \
|
|
371
|
+
jaegertracing/all-in-one:latest
|
|
372
|
+
|
|
373
|
+
# Start ucli-server with tracing enabled
|
|
374
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
|
|
375
|
+
ADMIN_SECRET=my-secret ENCRYPTION_KEY=<64-hex> ucli-server
|
|
376
|
+
|
|
377
|
+
# Open Jaeger UI
|
|
378
|
+
open http://localhost:16686
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Disabling OTEL
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
OTEL_ENABLED=false ADMIN_SECRET=my-secret ENCRYPTION_KEY=<64-hex> ucli-server
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Admin Dashboard
|
|
388
|
+
|
|
389
|
+
A built-in web UI is served at `/admin-ui` when the package is installed. It provides:
|
|
390
|
+
|
|
391
|
+
- **Dashboard** — overview stats (groups, OAS entries, MCP servers, active tokens)
|
|
392
|
+
- **Groups** — create and delete groups
|
|
393
|
+
- **OAS Entries** — register, edit, and delete OAS entries with auth configuration
|
|
394
|
+
- **MCP Servers** — register, edit, and delete MCP server configs with auth configuration
|
|
395
|
+
- **Tokens** — issue JWT tokens per group (shown once after creation), view status, revoke
|
|
396
|
+
|
|
397
|
+
The dashboard is auto-served from the `dist/admin-ui/` directory bundled with the npm package.
|
|
398
|
+
No extra configuration is required — just start the server and open `http://localhost:3000/admin-ui`.
|
|
399
|
+
|
|
400
|
+
You can also point it at an existing dist directory:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
ADMIN_UI_PATH=/path/to/custom/dist ADMIN_SECRET=secret ENCRYPTION_KEY=<64-hex> ucli-server
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Health & Observability
|
|
407
|
+
|
|
408
|
+
| Endpoint | Method | Description |
|
|
409
|
+
|----------|--------|-------------|
|
|
410
|
+
| `/api/v1/health` | `GET` | Liveness probe — always `200 OK` |
|
|
411
|
+
| `/api/v1/ready` | `GET` | Readiness probe — checks storage + cache adapters |
|
|
412
|
+
| `/metrics` | `GET` | Prometheus metrics (IP-restricted by default) |
|
|
413
|
+
| `/api/docs` | `GET` | Swagger UI (disable with `SWAGGER_ENABLED=false`) |
|
|
414
|
+
| `/api/openapi.json` | `GET` | OpenAPI 3.0 JSON spec |
|
|
415
|
+
| `/admin-ui` | `GET` | Admin dashboard |
|
|
416
|
+
|
|
417
|
+
## Security Model
|
|
418
|
+
|
|
419
|
+
- **At rest**: `authConfig` fields are encrypted with AES-256-GCM (256-bit key, random IV per record)
|
|
420
|
+
- **In transit**: Decrypted auth is only sent to authenticated CLI clients over TLS
|
|
421
|
+
- **JWT**: RS256-signed, JTI-tracked for revocation via cache blacklist
|
|
422
|
+
- **Admin auth**: `X-Admin-Secret` header checked via constant-time comparison
|
|
423
|
+
- **Never logged**: Auth configs are never written to logs or exposed in error messages
|