@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.
Files changed (247) hide show
  1. package/README.md +423 -0
  2. package/README.zh.md +404 -0
  3. package/assets/logo.svg +138 -0
  4. package/dist/admin-ui/assets/index-BjdBylCY.css +1 -0
  5. package/dist/admin-ui/assets/index-CDwKIMTJ.js +117 -0
  6. package/dist/admin-ui/assets/remixicon-B25hvfAs.eot +0 -0
  7. package/dist/admin-ui/assets/remixicon-BTtOSOPh.svg +9709 -0
  8. package/dist/admin-ui/assets/remixicon-CZw4FkzQ.woff2 +0 -0
  9. package/dist/admin-ui/assets/remixicon-S6an_USy.woff +0 -0
  10. package/dist/admin-ui/assets/remixicon-sqouR8Ox.ttf +0 -0
  11. package/dist/admin-ui/dist/assets/index-BjdBylCY.css +1 -0
  12. package/dist/admin-ui/dist/assets/index-CDwKIMTJ.js +117 -0
  13. package/dist/admin-ui/dist/assets/index-CppEl63e.css +1 -0
  14. package/dist/admin-ui/dist/assets/index-DBkeGfMQ.js +117 -0
  15. package/dist/admin-ui/dist/assets/index-ss5EmsBH.js +117 -0
  16. package/dist/admin-ui/dist/assets/remixicon-B25hvfAs.eot +0 -0
  17. package/dist/admin-ui/dist/assets/remixicon-BTtOSOPh.svg +9709 -0
  18. package/dist/admin-ui/dist/assets/remixicon-CZw4FkzQ.woff2 +0 -0
  19. package/dist/admin-ui/dist/assets/remixicon-S6an_USy.woff +0 -0
  20. package/dist/admin-ui/dist/assets/remixicon-sqouR8Ox.ttf +0 -0
  21. package/dist/admin-ui/dist/index.html +14 -0
  22. package/dist/admin-ui/index.html +14 -0
  23. package/dist/app.module.d.ts +3 -0
  24. package/dist/app.module.d.ts.map +1 -0
  25. package/dist/app.module.js +65 -0
  26. package/dist/app.module.js.map +1 -0
  27. package/dist/auth/admin.guard.d.ts +8 -0
  28. package/dist/auth/admin.guard.d.ts.map +1 -0
  29. package/dist/auth/admin.guard.js +34 -0
  30. package/dist/auth/admin.guard.js.map +1 -0
  31. package/dist/auth/auth.module.d.ts +3 -0
  32. package/dist/auth/auth.module.d.ts.map +1 -0
  33. package/dist/auth/auth.module.js +24 -0
  34. package/dist/auth/auth.module.js.map +1 -0
  35. package/dist/auth/decorators/jwt-payload.decorator.d.ts +2 -0
  36. package/dist/auth/decorators/jwt-payload.decorator.d.ts.map +1 -0
  37. package/dist/auth/decorators/jwt-payload.decorator.js +10 -0
  38. package/dist/auth/decorators/jwt-payload.decorator.js.map +1 -0
  39. package/dist/auth/group-token.guard.d.ts +11 -0
  40. package/dist/auth/group-token.guard.d.ts.map +1 -0
  41. package/dist/auth/group-token.guard.js +53 -0
  42. package/dist/auth/group-token.guard.js.map +1 -0
  43. package/dist/cache/cache.interface.d.ts +8 -0
  44. package/dist/cache/cache.interface.d.ts.map +1 -0
  45. package/dist/cache/cache.interface.js +3 -0
  46. package/dist/cache/cache.interface.js.map +1 -0
  47. package/dist/cache/cache.module.d.ts +5 -0
  48. package/dist/cache/cache.module.d.ts.map +1 -0
  49. package/dist/cache/cache.module.js +56 -0
  50. package/dist/cache/cache.module.js.map +1 -0
  51. package/dist/cache/cache.token.d.ts +2 -0
  52. package/dist/cache/cache.token.d.ts.map +1 -0
  53. package/dist/cache/cache.token.js +5 -0
  54. package/dist/cache/cache.token.js.map +1 -0
  55. package/dist/cache/memory/memory-cache.adapter.d.ts +15 -0
  56. package/dist/cache/memory/memory-cache.adapter.d.ts.map +1 -0
  57. package/dist/cache/memory/memory-cache.adapter.js +52 -0
  58. package/dist/cache/memory/memory-cache.adapter.js.map +1 -0
  59. package/dist/cache/redis/redis-cache.adapter.d.ts +16 -0
  60. package/dist/cache/redis/redis-cache.adapter.d.ts.map +1 -0
  61. package/dist/cache/redis/redis-cache.adapter.js +63 -0
  62. package/dist/cache/redis/redis-cache.adapter.js.map +1 -0
  63. package/dist/config/app-config.module.d.ts +3 -0
  64. package/dist/config/app-config.module.d.ts.map +1 -0
  65. package/dist/config/app-config.module.js +100 -0
  66. package/dist/config/app-config.module.js.map +1 -0
  67. package/dist/config/app-config.service.d.ts +29 -0
  68. package/dist/config/app-config.service.d.ts.map +1 -0
  69. package/dist/config/app-config.service.js +66 -0
  70. package/dist/config/app-config.service.js.map +1 -0
  71. package/dist/crypto/crypto.module.d.ts +3 -0
  72. package/dist/crypto/crypto.module.d.ts.map +1 -0
  73. package/dist/crypto/crypto.module.js +22 -0
  74. package/dist/crypto/crypto.module.js.map +1 -0
  75. package/dist/crypto/encryption.service.d.ts +9 -0
  76. package/dist/crypto/encryption.service.d.ts.map +1 -0
  77. package/dist/crypto/encryption.service.js +59 -0
  78. package/dist/crypto/encryption.service.js.map +1 -0
  79. package/dist/crypto/jwt.service.d.ts +28 -0
  80. package/dist/crypto/jwt.service.d.ts.map +1 -0
  81. package/dist/crypto/jwt.service.js +72 -0
  82. package/dist/crypto/jwt.service.js.map +1 -0
  83. package/dist/groups/dto/create-group.dto.d.ts +5 -0
  84. package/dist/groups/dto/create-group.dto.d.ts.map +1 -0
  85. package/dist/groups/dto/create-group.dto.js +34 -0
  86. package/dist/groups/dto/create-group.dto.js.map +1 -0
  87. package/dist/groups/groups.controller.d.ts +9 -0
  88. package/dist/groups/groups.controller.d.ts.map +1 -0
  89. package/dist/groups/groups.controller.js +60 -0
  90. package/dist/groups/groups.controller.js.map +1 -0
  91. package/dist/groups/groups.module.d.ts +3 -0
  92. package/dist/groups/groups.module.d.ts.map +1 -0
  93. package/dist/groups/groups.module.js +25 -0
  94. package/dist/groups/groups.module.js.map +1 -0
  95. package/dist/groups/groups.service.d.ts +9 -0
  96. package/dist/groups/groups.service.d.ts.map +1 -0
  97. package/dist/groups/groups.service.js +45 -0
  98. package/dist/groups/groups.service.js.map +1 -0
  99. package/dist/health/health.controller.d.ts +13 -0
  100. package/dist/health/health.controller.d.ts.map +1 -0
  101. package/dist/health/health.controller.js +68 -0
  102. package/dist/health/health.controller.js.map +1 -0
  103. package/dist/health/health.module.d.ts +3 -0
  104. package/dist/health/health.module.d.ts.map +1 -0
  105. package/dist/health/health.module.js +22 -0
  106. package/dist/health/health.module.js.map +1 -0
  107. package/dist/main.d.ts +3 -0
  108. package/dist/main.d.ts.map +1 -0
  109. package/dist/main.js +109 -0
  110. package/dist/main.js.map +1 -0
  111. package/dist/mcp/admin-mcp.controller.d.ts +13 -0
  112. package/dist/mcp/admin-mcp.controller.d.ts.map +1 -0
  113. package/dist/mcp/admin-mcp.controller.js +105 -0
  114. package/dist/mcp/admin-mcp.controller.js.map +1 -0
  115. package/dist/mcp/client-mcp.controller.d.ts +9 -0
  116. package/dist/mcp/client-mcp.controller.d.ts.map +1 -0
  117. package/dist/mcp/client-mcp.controller.js +63 -0
  118. package/dist/mcp/client-mcp.controller.js.map +1 -0
  119. package/dist/mcp/dto/create-mcp.dto.d.ts +11 -0
  120. package/dist/mcp/dto/create-mcp.dto.d.ts.map +1 -0
  121. package/dist/mcp/dto/create-mcp.dto.js +74 -0
  122. package/dist/mcp/dto/create-mcp.dto.js.map +1 -0
  123. package/dist/mcp/dto/update-mcp.dto.d.ts +11 -0
  124. package/dist/mcp/dto/update-mcp.dto.d.ts.map +1 -0
  125. package/dist/mcp/dto/update-mcp.dto.js +71 -0
  126. package/dist/mcp/dto/update-mcp.dto.js.map +1 -0
  127. package/dist/mcp/mcp.module.d.ts +3 -0
  128. package/dist/mcp/mcp.module.d.ts.map +1 -0
  129. package/dist/mcp/mcp.module.js +26 -0
  130. package/dist/mcp/mcp.module.js.map +1 -0
  131. package/dist/mcp/mcp.service.d.ts +16 -0
  132. package/dist/mcp/mcp.service.d.ts.map +1 -0
  133. package/dist/mcp/mcp.service.js +78 -0
  134. package/dist/mcp/mcp.service.js.map +1 -0
  135. package/dist/metrics/metrics.controller.d.ts +12 -0
  136. package/dist/metrics/metrics.controller.d.ts.map +1 -0
  137. package/dist/metrics/metrics.controller.js +71 -0
  138. package/dist/metrics/metrics.controller.js.map +1 -0
  139. package/dist/metrics/metrics.module.d.ts +3 -0
  140. package/dist/metrics/metrics.module.d.ts.map +1 -0
  141. package/dist/metrics/metrics.module.js +20 -0
  142. package/dist/metrics/metrics.module.js.map +1 -0
  143. package/dist/oas/admin-oas.controller.d.ts +12 -0
  144. package/dist/oas/admin-oas.controller.d.ts.map +1 -0
  145. package/dist/oas/admin-oas.controller.js +96 -0
  146. package/dist/oas/admin-oas.controller.js.map +1 -0
  147. package/dist/oas/client-oas.controller.d.ts +9 -0
  148. package/dist/oas/client-oas.controller.d.ts.map +1 -0
  149. package/dist/oas/client-oas.controller.js +63 -0
  150. package/dist/oas/client-oas.controller.js.map +1 -0
  151. package/dist/oas/dto/create-oas.dto.d.ts +12 -0
  152. package/dist/oas/dto/create-oas.dto.d.ts.map +1 -0
  153. package/dist/oas/dto/create-oas.dto.js +82 -0
  154. package/dist/oas/dto/create-oas.dto.js.map +1 -0
  155. package/dist/oas/dto/update-oas.dto.d.ts +12 -0
  156. package/dist/oas/dto/update-oas.dto.d.ts.map +1 -0
  157. package/dist/oas/dto/update-oas.dto.js +77 -0
  158. package/dist/oas/dto/update-oas.dto.js.map +1 -0
  159. package/dist/oas/oas.module.d.ts +3 -0
  160. package/dist/oas/oas.module.d.ts.map +1 -0
  161. package/dist/oas/oas.module.js +26 -0
  162. package/dist/oas/oas.module.js.map +1 -0
  163. package/dist/oas/oas.service.d.ts +16 -0
  164. package/dist/oas/oas.service.d.ts.map +1 -0
  165. package/dist/oas/oas.service.js +79 -0
  166. package/dist/oas/oas.service.js.map +1 -0
  167. package/dist/otel/otel.d.ts +25 -0
  168. package/dist/otel/otel.d.ts.map +1 -0
  169. package/dist/otel/otel.js +61 -0
  170. package/dist/otel/otel.js.map +1 -0
  171. package/dist/storage/interfaces/repos.interface.d.ts +158 -0
  172. package/dist/storage/interfaces/repos.interface.d.ts.map +1 -0
  173. package/dist/storage/interfaces/repos.interface.js +4 -0
  174. package/dist/storage/interfaces/repos.interface.js.map +1 -0
  175. package/dist/storage/memory/memory-group.repo.d.ts +9 -0
  176. package/dist/storage/memory/memory-group.repo.d.ts.map +1 -0
  177. package/dist/storage/memory/memory-group.repo.js +33 -0
  178. package/dist/storage/memory/memory-group.repo.js.map +1 -0
  179. package/dist/storage/memory/memory-mcp.repo.d.ts +13 -0
  180. package/dist/storage/memory/memory-mcp.repo.d.ts.map +1 -0
  181. package/dist/storage/memory/memory-mcp.repo.js +65 -0
  182. package/dist/storage/memory/memory-mcp.repo.js.map +1 -0
  183. package/dist/storage/memory/memory-oas.repo.d.ts +13 -0
  184. package/dist/storage/memory/memory-oas.repo.d.ts.map +1 -0
  185. package/dist/storage/memory/memory-oas.repo.js +55 -0
  186. package/dist/storage/memory/memory-oas.repo.js.map +1 -0
  187. package/dist/storage/memory/memory-token.repo.d.ts +11 -0
  188. package/dist/storage/memory/memory-token.repo.d.ts.map +1 -0
  189. package/dist/storage/memory/memory-token.repo.js +39 -0
  190. package/dist/storage/memory/memory-token.repo.js.map +1 -0
  191. package/dist/storage/storage.module.d.ts +5 -0
  192. package/dist/storage/storage.module.d.ts.map +1 -0
  193. package/dist/storage/storage.module.js +84 -0
  194. package/dist/storage/storage.module.js.map +1 -0
  195. package/dist/storage/storage.tokens.d.ts +5 -0
  196. package/dist/storage/storage.tokens.d.ts.map +1 -0
  197. package/dist/storage/storage.tokens.js +8 -0
  198. package/dist/storage/storage.tokens.js.map +1 -0
  199. package/dist/storage/typeorm/entities/group.entity.d.ts +12 -0
  200. package/dist/storage/typeorm/entities/group.entity.d.ts.map +1 -0
  201. package/dist/storage/typeorm/entities/group.entity.js +57 -0
  202. package/dist/storage/typeorm/entities/group.entity.js.map +1 -0
  203. package/dist/storage/typeorm/entities/mcp-entry.entity.d.ts +16 -0
  204. package/dist/storage/typeorm/entities/mcp-entry.entity.d.ts.map +1 -0
  205. package/dist/storage/typeorm/entities/mcp-entry.entity.js +82 -0
  206. package/dist/storage/typeorm/entities/mcp-entry.entity.js.map +1 -0
  207. package/dist/storage/typeorm/entities/oas-entry.entity.d.ts +17 -0
  208. package/dist/storage/typeorm/entities/oas-entry.entity.d.ts.map +1 -0
  209. package/dist/storage/typeorm/entities/oas-entry.entity.js +87 -0
  210. package/dist/storage/typeorm/entities/oas-entry.entity.js.map +1 -0
  211. package/dist/storage/typeorm/entities/token.entity.d.ts +13 -0
  212. package/dist/storage/typeorm/entities/token.entity.d.ts.map +1 -0
  213. package/dist/storage/typeorm/entities/token.entity.js +68 -0
  214. package/dist/storage/typeorm/entities/token.entity.js.map +1 -0
  215. package/dist/storage/typeorm/typeorm-group.repo.d.ts +12 -0
  216. package/dist/storage/typeorm/typeorm-group.repo.d.ts.map +1 -0
  217. package/dist/storage/typeorm/typeorm-group.repo.js +50 -0
  218. package/dist/storage/typeorm/typeorm-group.repo.js.map +1 -0
  219. package/dist/storage/typeorm/typeorm-mcp.repo.d.ts +15 -0
  220. package/dist/storage/typeorm/typeorm-mcp.repo.d.ts.map +1 -0
  221. package/dist/storage/typeorm/typeorm-mcp.repo.js +88 -0
  222. package/dist/storage/typeorm/typeorm-mcp.repo.js.map +1 -0
  223. package/dist/storage/typeorm/typeorm-oas.repo.d.ts +15 -0
  224. package/dist/storage/typeorm/typeorm-oas.repo.d.ts.map +1 -0
  225. package/dist/storage/typeorm/typeorm-oas.repo.js +92 -0
  226. package/dist/storage/typeorm/typeorm-oas.repo.js.map +1 -0
  227. package/dist/storage/typeorm/typeorm-token.repo.d.ts +13 -0
  228. package/dist/storage/typeorm/typeorm-token.repo.d.ts.map +1 -0
  229. package/dist/storage/typeorm/typeorm-token.repo.js +56 -0
  230. package/dist/storage/typeorm/typeorm-token.repo.js.map +1 -0
  231. package/dist/tokens/dto/issue-token.dto.d.ts +6 -0
  232. package/dist/tokens/dto/issue-token.dto.d.ts.map +1 -0
  233. package/dist/tokens/dto/issue-token.dto.js +41 -0
  234. package/dist/tokens/dto/issue-token.dto.js.map +1 -0
  235. package/dist/tokens/tokens.controller.d.ts +12 -0
  236. package/dist/tokens/tokens.controller.d.ts.map +1 -0
  237. package/dist/tokens/tokens.controller.js +89 -0
  238. package/dist/tokens/tokens.controller.js.map +1 -0
  239. package/dist/tokens/tokens.module.d.ts +3 -0
  240. package/dist/tokens/tokens.module.d.ts.map +1 -0
  241. package/dist/tokens/tokens.module.js +27 -0
  242. package/dist/tokens/tokens.module.js.map +1 -0
  243. package/dist/tokens/tokens.service.d.ts +25 -0
  244. package/dist/tokens/tokens.service.d.ts.map +1 -0
  245. package/dist/tokens/tokens.service.js +69 -0
  246. package/dist/tokens/tokens.service.js.map +1 -0
  247. 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