@revealui/mcp 0.0.1-pre.0 → 0.1.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 (270) hide show
  1. package/.env.example +9 -0
  2. package/MCP_MAINTENANCE.md +265 -0
  3. package/README.md +260 -0
  4. package/__tests__/crdt.integration.test.ts +156 -0
  5. package/configs/README.md +77 -0
  6. package/configs/claude-template.json +54 -0
  7. package/dist/packages/core/src/database/ssl-config.d.ts +9 -0
  8. package/dist/packages/core/src/database/ssl-config.d.ts.map +1 -0
  9. package/dist/packages/core/src/database/ssl-config.js +8 -0
  10. package/dist/packages/core/src/database/ssl-config.js.map +1 -0
  11. package/dist/packages/core/src/features.d.ts +86 -0
  12. package/dist/packages/core/src/features.d.ts.map +1 -0
  13. package/dist/packages/core/src/features.js +93 -0
  14. package/dist/packages/core/src/features.js.map +1 -0
  15. package/dist/packages/core/src/license.d.ts +75 -0
  16. package/dist/packages/core/src/license.d.ts.map +1 -0
  17. package/dist/packages/core/src/license.js +174 -0
  18. package/dist/packages/core/src/license.js.map +1 -0
  19. package/dist/packages/core/src/monitoring/alerts.d.ts +118 -0
  20. package/dist/packages/core/src/monitoring/alerts.d.ts.map +1 -0
  21. package/dist/packages/core/src/monitoring/alerts.js +325 -0
  22. package/dist/packages/core/src/monitoring/alerts.js.map +1 -0
  23. package/dist/packages/core/src/monitoring/cleanup-manager.d.ts +71 -0
  24. package/dist/packages/core/src/monitoring/cleanup-manager.d.ts.map +1 -0
  25. package/dist/packages/core/src/monitoring/cleanup-manager.js +227 -0
  26. package/dist/packages/core/src/monitoring/cleanup-manager.js.map +1 -0
  27. package/dist/packages/core/src/monitoring/health-monitor.d.ts +22 -0
  28. package/dist/packages/core/src/monitoring/health-monitor.d.ts.map +1 -0
  29. package/dist/packages/core/src/monitoring/health-monitor.js +143 -0
  30. package/dist/packages/core/src/monitoring/health-monitor.js.map +1 -0
  31. package/dist/packages/core/src/monitoring/index.d.ts +14 -0
  32. package/dist/packages/core/src/monitoring/index.d.ts.map +1 -0
  33. package/dist/packages/core/src/monitoring/index.js +18 -0
  34. package/dist/packages/core/src/monitoring/index.js.map +1 -0
  35. package/dist/packages/core/src/monitoring/process-registry.d.ts +97 -0
  36. package/dist/packages/core/src/monitoring/process-registry.d.ts.map +1 -0
  37. package/dist/packages/core/src/monitoring/process-registry.js +223 -0
  38. package/dist/packages/core/src/monitoring/process-registry.js.map +1 -0
  39. package/dist/packages/core/src/monitoring/types.d.ts +231 -0
  40. package/dist/packages/core/src/monitoring/types.d.ts.map +1 -0
  41. package/dist/packages/core/src/monitoring/types.js +43 -0
  42. package/dist/packages/core/src/monitoring/types.js.map +1 -0
  43. package/dist/packages/core/src/monitoring/zombie-detector.d.ts +81 -0
  44. package/dist/packages/core/src/monitoring/zombie-detector.d.ts.map +1 -0
  45. package/dist/packages/core/src/monitoring/zombie-detector.js +232 -0
  46. package/dist/packages/core/src/monitoring/zombie-detector.js.map +1 -0
  47. package/dist/packages/core/src/observability/logger.d.ts +47 -0
  48. package/dist/packages/core/src/observability/logger.d.ts.map +1 -0
  49. package/dist/packages/core/src/observability/logger.js +141 -0
  50. package/dist/packages/core/src/observability/logger.js.map +1 -0
  51. package/dist/packages/core/src/utils/logger-server.d.ts +32 -0
  52. package/dist/packages/core/src/utils/logger-server.d.ts.map +1 -0
  53. package/dist/packages/core/src/utils/logger-server.js +69 -0
  54. package/dist/packages/core/src/utils/logger-server.js.map +1 -0
  55. package/dist/packages/core/src/utils/request-context.d.ts +143 -0
  56. package/dist/packages/core/src/utils/request-context.d.ts.map +1 -0
  57. package/dist/packages/core/src/utils/request-context.js +169 -0
  58. package/dist/packages/core/src/utils/request-context.js.map +1 -0
  59. package/dist/packages/dev/src/code-validator/index.d.ts +20 -0
  60. package/dist/packages/dev/src/code-validator/index.d.ts.map +1 -0
  61. package/dist/packages/dev/src/code-validator/index.js +20 -0
  62. package/dist/packages/dev/src/code-validator/index.js.map +1 -0
  63. package/dist/packages/dev/src/code-validator/types.d.ts +67 -0
  64. package/dist/packages/dev/src/code-validator/types.d.ts.map +1 -0
  65. package/dist/packages/dev/src/code-validator/types.js +7 -0
  66. package/dist/packages/dev/src/code-validator/types.js.map +1 -0
  67. package/dist/packages/dev/src/code-validator/validator.d.ts +48 -0
  68. package/dist/packages/dev/src/code-validator/validator.d.ts.map +1 -0
  69. package/dist/packages/dev/src/code-validator/validator.js +176 -0
  70. package/dist/packages/dev/src/code-validator/validator.js.map +1 -0
  71. package/dist/packages/mcp/src/adapters/db.d.ts +46 -0
  72. package/dist/packages/mcp/src/adapters/db.d.ts.map +1 -0
  73. package/dist/packages/mcp/src/adapters/db.js +127 -0
  74. package/dist/packages/mcp/src/adapters/db.js.map +1 -0
  75. package/dist/packages/mcp/src/config/index.d.ts +11 -0
  76. package/dist/packages/mcp/src/config/index.d.ts.map +1 -0
  77. package/dist/packages/mcp/src/config/index.js +18 -0
  78. package/dist/packages/mcp/src/config/index.js.map +1 -0
  79. package/dist/packages/mcp/src/contracts.d.ts +131 -0
  80. package/dist/packages/mcp/src/contracts.d.ts.map +1 -0
  81. package/dist/packages/mcp/src/contracts.js +153 -0
  82. package/dist/packages/mcp/src/contracts.js.map +1 -0
  83. package/dist/packages/mcp/src/hypervisor.d.ts +132 -0
  84. package/dist/packages/mcp/src/hypervisor.d.ts.map +1 -0
  85. package/dist/packages/mcp/src/hypervisor.js +359 -0
  86. package/dist/packages/mcp/src/hypervisor.js.map +1 -0
  87. package/dist/packages/mcp/src/index.d.ts +25 -0
  88. package/dist/packages/mcp/src/index.d.ts.map +1 -0
  89. package/dist/packages/mcp/src/index.js +41 -0
  90. package/dist/packages/mcp/src/index.js.map +1 -0
  91. package/dist/packages/mcp/src/servers/adapter.d.ts +199 -0
  92. package/dist/packages/mcp/src/servers/adapter.d.ts.map +1 -0
  93. package/dist/packages/mcp/src/servers/adapter.js +487 -0
  94. package/dist/packages/mcp/src/servers/adapter.js.map +1 -0
  95. package/dist/packages/mcp/src/servers/code-validator.d.ts +24 -0
  96. package/dist/packages/mcp/src/servers/code-validator.d.ts.map +1 -0
  97. package/dist/packages/mcp/src/servers/code-validator.js +156 -0
  98. package/dist/packages/mcp/src/servers/code-validator.js.map +1 -0
  99. package/dist/packages/mcp/src/servers/neon.d.ts +11 -0
  100. package/dist/packages/mcp/src/servers/neon.d.ts.map +1 -0
  101. package/dist/packages/mcp/src/servers/neon.js +90 -0
  102. package/dist/packages/mcp/src/servers/neon.js.map +1 -0
  103. package/dist/packages/mcp/src/servers/next-devtools.d.ts +11 -0
  104. package/dist/packages/mcp/src/servers/next-devtools.d.ts.map +1 -0
  105. package/dist/packages/mcp/src/servers/next-devtools.js +215 -0
  106. package/dist/packages/mcp/src/servers/next-devtools.js.map +1 -0
  107. package/dist/packages/mcp/src/servers/playwright.d.ts +11 -0
  108. package/dist/packages/mcp/src/servers/playwright.d.ts.map +1 -0
  109. package/dist/packages/mcp/src/servers/playwright.js +68 -0
  110. package/dist/packages/mcp/src/servers/playwright.js.map +1 -0
  111. package/dist/packages/mcp/src/servers/stripe.d.ts +11 -0
  112. package/dist/packages/mcp/src/servers/stripe.d.ts.map +1 -0
  113. package/dist/packages/mcp/src/servers/stripe.js +86 -0
  114. package/dist/packages/mcp/src/servers/stripe.js.map +1 -0
  115. package/dist/packages/mcp/src/servers/supabase.d.ts +11 -0
  116. package/dist/packages/mcp/src/servers/supabase.d.ts.map +1 -0
  117. package/dist/packages/mcp/src/servers/supabase.js +144 -0
  118. package/dist/packages/mcp/src/servers/supabase.js.map +1 -0
  119. package/dist/packages/mcp/src/servers/vercel.d.ts +11 -0
  120. package/dist/packages/mcp/src/servers/vercel.d.ts.map +1 -0
  121. package/dist/packages/mcp/src/servers/vercel.js +87 -0
  122. package/dist/packages/mcp/src/servers/vercel.js.map +1 -0
  123. package/dist/packages/mcp/src/servers/vultr-test.d.ts +3 -0
  124. package/dist/packages/mcp/src/servers/vultr-test.d.ts.map +1 -0
  125. package/dist/packages/mcp/src/servers/vultr-test.js +82 -0
  126. package/dist/packages/mcp/src/servers/vultr-test.js.map +1 -0
  127. package/dist/scripts/lib/analyzers/console-analyzer.d.ts +188 -0
  128. package/dist/scripts/lib/analyzers/console-analyzer.d.ts.map +1 -0
  129. package/dist/scripts/lib/analyzers/console-analyzer.js +432 -0
  130. package/dist/scripts/lib/analyzers/console-analyzer.js.map +1 -0
  131. package/dist/scripts/lib/analyzers/index.d.ts +11 -0
  132. package/dist/scripts/lib/analyzers/index.d.ts.map +1 -0
  133. package/dist/scripts/lib/analyzers/index.js +11 -0
  134. package/dist/scripts/lib/analyzers/index.js.map +1 -0
  135. package/dist/scripts/lib/args.d.ts +104 -0
  136. package/dist/scripts/lib/args.d.ts.map +1 -0
  137. package/dist/scripts/lib/args.js +304 -0
  138. package/dist/scripts/lib/args.js.map +1 -0
  139. package/dist/scripts/lib/cache.d.ts +185 -0
  140. package/dist/scripts/lib/cache.d.ts.map +1 -0
  141. package/dist/scripts/lib/cache.js +390 -0
  142. package/dist/scripts/lib/cache.js.map +1 -0
  143. package/dist/scripts/lib/cli/dispatch.d.ts +116 -0
  144. package/dist/scripts/lib/cli/dispatch.d.ts.map +1 -0
  145. package/dist/scripts/lib/cli/dispatch.js +206 -0
  146. package/dist/scripts/lib/cli/dispatch.js.map +1 -0
  147. package/dist/scripts/lib/cli/index.d.ts +10 -0
  148. package/dist/scripts/lib/cli/index.d.ts.map +1 -0
  149. package/dist/scripts/lib/cli/index.js +10 -0
  150. package/dist/scripts/lib/cli/index.js.map +1 -0
  151. package/dist/scripts/lib/database/ssl-config.d.ts +26 -0
  152. package/dist/scripts/lib/database/ssl-config.d.ts.map +1 -0
  153. package/dist/scripts/lib/database/ssl-config.js +47 -0
  154. package/dist/scripts/lib/database/ssl-config.js.map +1 -0
  155. package/dist/scripts/lib/errors.d.ts +218 -0
  156. package/dist/scripts/lib/errors.d.ts.map +1 -0
  157. package/dist/scripts/lib/errors.js +543 -0
  158. package/dist/scripts/lib/errors.js.map +1 -0
  159. package/dist/scripts/lib/exec.d.ts +107 -0
  160. package/dist/scripts/lib/exec.d.ts.map +1 -0
  161. package/dist/scripts/lib/exec.js +232 -0
  162. package/dist/scripts/lib/exec.js.map +1 -0
  163. package/dist/scripts/lib/index.d.ts +50 -0
  164. package/dist/scripts/lib/index.d.ts.map +1 -0
  165. package/dist/scripts/lib/index.js +65 -0
  166. package/dist/scripts/lib/index.js.map +1 -0
  167. package/dist/scripts/lib/logger.d.ts +50 -0
  168. package/dist/scripts/lib/logger.d.ts.map +1 -0
  169. package/dist/scripts/lib/logger.js +159 -0
  170. package/dist/scripts/lib/logger.js.map +1 -0
  171. package/dist/scripts/lib/output.d.ts +149 -0
  172. package/dist/scripts/lib/output.d.ts.map +1 -0
  173. package/dist/scripts/lib/output.js +263 -0
  174. package/dist/scripts/lib/output.js.map +1 -0
  175. package/dist/scripts/lib/parallel.d.ts +164 -0
  176. package/dist/scripts/lib/parallel.d.ts.map +1 -0
  177. package/dist/scripts/lib/parallel.js +355 -0
  178. package/dist/scripts/lib/parallel.js.map +1 -0
  179. package/dist/scripts/lib/paths.d.ts +92 -0
  180. package/dist/scripts/lib/paths.d.ts.map +1 -0
  181. package/dist/scripts/lib/paths.js +171 -0
  182. package/dist/scripts/lib/paths.js.map +1 -0
  183. package/dist/scripts/lib/state/adapters/memory.d.ts +42 -0
  184. package/dist/scripts/lib/state/adapters/memory.d.ts.map +1 -0
  185. package/dist/scripts/lib/state/adapters/memory.js +110 -0
  186. package/dist/scripts/lib/state/adapters/memory.js.map +1 -0
  187. package/dist/scripts/lib/state/adapters/pglite.d.ts +46 -0
  188. package/dist/scripts/lib/state/adapters/pglite.d.ts.map +1 -0
  189. package/dist/scripts/lib/state/adapters/pglite.js +256 -0
  190. package/dist/scripts/lib/state/adapters/pglite.js.map +1 -0
  191. package/dist/scripts/lib/state/index.d.ts +16 -0
  192. package/dist/scripts/lib/state/index.d.ts.map +1 -0
  193. package/dist/scripts/lib/state/index.js +16 -0
  194. package/dist/scripts/lib/state/index.js.map +1 -0
  195. package/dist/scripts/lib/state/types.d.ts +111 -0
  196. package/dist/scripts/lib/state/types.d.ts.map +1 -0
  197. package/dist/scripts/lib/state/types.js +8 -0
  198. package/dist/scripts/lib/state/types.js.map +1 -0
  199. package/dist/scripts/lib/state/workflow-state.d.ts +110 -0
  200. package/dist/scripts/lib/state/workflow-state.d.ts.map +1 -0
  201. package/dist/scripts/lib/state/workflow-state.js +331 -0
  202. package/dist/scripts/lib/state/workflow-state.js.map +1 -0
  203. package/dist/scripts/lib/telemetry.d.ts +194 -0
  204. package/dist/scripts/lib/telemetry.d.ts.map +1 -0
  205. package/dist/scripts/lib/telemetry.js +394 -0
  206. package/dist/scripts/lib/telemetry.js.map +1 -0
  207. package/dist/scripts/lib/utils.d.ts +270 -0
  208. package/dist/scripts/lib/utils.d.ts.map +1 -0
  209. package/dist/scripts/lib/utils.js +473 -0
  210. package/dist/scripts/lib/utils.js.map +1 -0
  211. package/dist/scripts/lib/validation/database.d.ts +83 -0
  212. package/dist/scripts/lib/validation/database.d.ts.map +1 -0
  213. package/dist/scripts/lib/validation/database.js +199 -0
  214. package/dist/scripts/lib/validation/database.js.map +1 -0
  215. package/dist/scripts/lib/validation/env.d.ts +80 -0
  216. package/dist/scripts/lib/validation/env.d.ts.map +1 -0
  217. package/dist/scripts/lib/validation/env.js +246 -0
  218. package/dist/scripts/lib/validation/env.js.map +1 -0
  219. package/dist/scripts/lib/validation/index.d.ts +16 -0
  220. package/dist/scripts/lib/validation/index.d.ts.map +1 -0
  221. package/dist/scripts/lib/validation/index.js +16 -0
  222. package/dist/scripts/lib/validation/index.js.map +1 -0
  223. package/dist/scripts/lib/validation/post-execution.d.ts +74 -0
  224. package/dist/scripts/lib/validation/post-execution.d.ts.map +1 -0
  225. package/dist/scripts/lib/validation/post-execution.js +110 -0
  226. package/dist/scripts/lib/validation/post-execution.js.map +1 -0
  227. package/dist/scripts/lib/validation/pre-execution.d.ts +165 -0
  228. package/dist/scripts/lib/validation/pre-execution.d.ts.map +1 -0
  229. package/dist/scripts/lib/validation/pre-execution.js +466 -0
  230. package/dist/scripts/lib/validation/pre-execution.js.map +1 -0
  231. package/dist/scripts/lib/validators/documentation-validator.d.ts +242 -0
  232. package/dist/scripts/lib/validators/documentation-validator.d.ts.map +1 -0
  233. package/dist/scripts/lib/validators/documentation-validator.js +584 -0
  234. package/dist/scripts/lib/validators/documentation-validator.js.map +1 -0
  235. package/dist/scripts/lib/validators/index.d.ts +11 -0
  236. package/dist/scripts/lib/validators/index.d.ts.map +1 -0
  237. package/dist/scripts/lib/validators/index.js +11 -0
  238. package/dist/scripts/lib/validators/index.js.map +1 -0
  239. package/docker-compose.yml +46 -0
  240. package/docs/INDEX.md +88 -0
  241. package/docs/README.md +774 -0
  242. package/docs/SETUP.md +264 -0
  243. package/docs/servers/code-validator.md +586 -0
  244. package/eslint.config.js +7 -0
  245. package/migrations/0001_add_crdt_columns.sql +8 -0
  246. package/migrations/0001_rollback.sql +6 -0
  247. package/migrations/005_performance_indexes.sql +190 -0
  248. package/migrations/backfill_crdt_meta.js +45 -0
  249. package/package.json +21 -85
  250. package/src/__tests__/hypervisor.test.ts +212 -0
  251. package/src/adapters/db.ts +180 -0
  252. package/src/config/config.json +49 -0
  253. package/src/config/index.ts +30 -0
  254. package/src/contracts.ts +221 -0
  255. package/src/hypervisor.ts +464 -0
  256. package/src/index.ts +87 -0
  257. package/src/servers/adapter.ts +643 -0
  258. package/src/servers/code-validator.ts +188 -0
  259. package/src/servers/neon.ts +103 -0
  260. package/src/servers/next-devtools.ts +230 -0
  261. package/src/servers/playwright.ts +77 -0
  262. package/src/servers/stripe.ts +99 -0
  263. package/src/servers/supabase.ts +161 -0
  264. package/src/servers/vercel.ts +100 -0
  265. package/src/servers/vultr-test.ts +97 -0
  266. package/tsconfig.json +12 -0
  267. package/vitest.config.ts +22 -0
  268. package/LICENSE +0 -202
  269. package/dist/index.js +0 -10990
  270. package/dist/index.js.map +0 -1
@@ -0,0 +1,464 @@
1
+ /**
2
+ * MCP Hypervisor
3
+ *
4
+ * Manages N running MCP server processes, pings them for liveness, and
5
+ * dynamically exposes their tools at runtime. Inspired by the
6
+ * MCPCompatibilityLayer/MCPHypervisor pattern from AnythingLLM.
7
+ *
8
+ * Architecture:
9
+ * - Singleton: one hypervisor per process manages all MCP servers
10
+ * - Each server is spawned with piped stdio for JSON-RPC communication
11
+ * - Tool names are namespaced: @@mcp_{serverName}_{toolName}
12
+ * - Health check loop: every 60s, process.exitCode check + tools/list probe
13
+ *
14
+ * Wire format: newline-delimited JSON-RPC 2.0 (stdin/stdout)
15
+ */
16
+
17
+ import { type ChildProcess, spawn } from 'node:child_process'
18
+ import { registerCleanupHandler } from '@revealui/core/monitoring'
19
+ import { logger } from '@revealui/core/observability/logger'
20
+
21
+ // =============================================================================
22
+ // Types
23
+ // =============================================================================
24
+
25
+ export interface MCPServerConfig {
26
+ /** Unique name for this server (used in tool namespacing) */
27
+ name: string
28
+ /** Executable to run (e.g. 'node', 'pnpm') */
29
+ command: string
30
+ /** Arguments to the command */
31
+ args: string[]
32
+ /** Additional environment variables */
33
+ env?: Record<string, string>
34
+ }
35
+
36
+ export interface MCPTool {
37
+ name: string
38
+ description: string
39
+ inputSchema: {
40
+ type: 'object'
41
+ properties?: Record<string, unknown>
42
+ required?: string[]
43
+ }
44
+ }
45
+
46
+ export interface NamespacedTool {
47
+ /** Namespaced name: @@mcp_{serverName}_{toolName} */
48
+ namespacedName: string
49
+ serverName: string
50
+ tool: MCPTool
51
+ }
52
+
53
+ interface ServerEntry {
54
+ config: MCPServerConfig
55
+ process: ChildProcess | null
56
+ tools: MCPTool[]
57
+ healthy: boolean
58
+ lastPingAt: number | null
59
+ }
60
+
61
+ interface JsonRpcRequest {
62
+ jsonrpc: '2.0'
63
+ id: number
64
+ method: string
65
+ params?: unknown
66
+ }
67
+
68
+ interface JsonRpcResponse {
69
+ jsonrpc: '2.0'
70
+ id: number
71
+ result?: unknown
72
+ error?: { code: number; message: string }
73
+ }
74
+
75
+ // =============================================================================
76
+ // Constants
77
+ // =============================================================================
78
+
79
+ const HEALTH_CHECK_INTERVAL_MS = 60_000
80
+ const REQUEST_TIMEOUT_MS = 5_000
81
+ const MCP_TOOL_PREFIX = '@@mcp'
82
+
83
+ // =============================================================================
84
+ // MCPHypervisor
85
+ // =============================================================================
86
+
87
+ /**
88
+ * Singleton that manages MCP server processes and their tool registries.
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const hypervisor = MCPHypervisor.getInstance()
93
+ *
94
+ * hypervisor.registerServer({
95
+ * name: 'stripe',
96
+ * command: 'pnpm',
97
+ * args: ['dlx', '@stripe/mcp', '--tools=all', '--api-key=sk_...'],
98
+ * })
99
+ *
100
+ * await hypervisor.startServer('stripe')
101
+ * const tools = hypervisor.getAllTools()
102
+ * // tools[0].namespacedName === '@@mcp_stripe_create_payment_intent'
103
+ * ```
104
+ */
105
+ export class MCPHypervisor {
106
+ private static instance: MCPHypervisor | null = null
107
+
108
+ private servers: Map<string, ServerEntry> = new Map()
109
+ private requestCounter = 0
110
+ private pendingRequests: Map<
111
+ number,
112
+ { resolve: (value: unknown) => void; reject: (error: Error) => void; timer: NodeJS.Timeout }
113
+ > = new Map()
114
+ private healthCheckTimer: NodeJS.Timeout | null = null
115
+
116
+ private constructor() {
117
+ registerCleanupHandler(
118
+ 'mcp-hypervisor',
119
+ async () => this.stopAll(),
120
+ 'Stop all MCP server processes',
121
+ 85,
122
+ )
123
+ this.startHealthCheckLoop()
124
+ }
125
+
126
+ static getInstance(): MCPHypervisor {
127
+ if (!MCPHypervisor.instance) {
128
+ MCPHypervisor.instance = new MCPHypervisor()
129
+ }
130
+ return MCPHypervisor.instance
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // Server registration
135
+ // ---------------------------------------------------------------------------
136
+
137
+ /**
138
+ * Register an MCP server configuration without starting it.
139
+ */
140
+ registerServer(config: MCPServerConfig): void {
141
+ if (this.servers.has(config.name)) {
142
+ logger.warn(`[MCPHypervisor] Server "${config.name}" is already registered`)
143
+ return
144
+ }
145
+ this.servers.set(config.name, {
146
+ config,
147
+ process: null,
148
+ tools: [],
149
+ healthy: false,
150
+ lastPingAt: null,
151
+ })
152
+ logger.info(`[MCPHypervisor] Registered server: ${config.name}`)
153
+ }
154
+
155
+ /**
156
+ * Unregister a server (stops it first if running).
157
+ */
158
+ async unregisterServer(name: string): Promise<void> {
159
+ const entry = this.servers.get(name)
160
+ if (!entry) return
161
+ if (entry.process) await this.stopServer(name)
162
+ this.servers.delete(name)
163
+ }
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // Lifecycle
167
+ // ---------------------------------------------------------------------------
168
+
169
+ /**
170
+ * Spawn the MCP server process with piped stdio.
171
+ * Tools are discovered via `listServerTools()` after startup.
172
+ */
173
+ async startServer(name: string): Promise<void> {
174
+ const entry = this.servers.get(name)
175
+ if (!entry) throw new Error(`[MCPHypervisor] Unknown server: "${name}"`)
176
+ if (entry.process && entry.process.exitCode === null) {
177
+ logger.info(`[MCPHypervisor] Server "${name}" is already running`)
178
+ return
179
+ }
180
+
181
+ const { config } = entry
182
+ const child = spawn(config.command, config.args, {
183
+ stdio: ['pipe', 'pipe', 'pipe'],
184
+ env: { ...process.env, ...config.env },
185
+ })
186
+
187
+ entry.process = child
188
+ entry.healthy = false
189
+ entry.tools = []
190
+
191
+ // Buffer incoming stdout for JSON-RPC response parsing
192
+ let buffer = ''
193
+ child.stdout?.on('data', (chunk: Buffer) => {
194
+ buffer += chunk.toString()
195
+ const lines = buffer.split('\n')
196
+ buffer = lines.pop() ?? '' // keep incomplete line in buffer
197
+ for (const line of lines) {
198
+ const trimmed = line.trim()
199
+ if (!trimmed) continue
200
+ try {
201
+ const msg = JSON.parse(trimmed) as JsonRpcResponse
202
+ this.handleResponse(msg)
203
+ } catch {
204
+ // Non-JSON output from server — ignore
205
+ }
206
+ }
207
+ })
208
+
209
+ child.stderr?.on('data', (chunk: Buffer) => {
210
+ logger.warn(`[MCPHypervisor] ${name} stderr: ${chunk.toString().trim()}`)
211
+ })
212
+
213
+ child.on('exit', (code) => {
214
+ logger.warn(`[MCPHypervisor] Server "${name}" exited with code ${code}`)
215
+ entry.healthy = false
216
+ // Reject all pending requests for this server
217
+ for (const [id, pending] of this.pendingRequests) {
218
+ pending.reject(new Error(`Server "${name}" exited`))
219
+ clearTimeout(pending.timer)
220
+ this.pendingRequests.delete(id)
221
+ }
222
+ })
223
+
224
+ // Allow a brief startup window, then probe tools
225
+ await new Promise<void>((resolve) => setTimeout(resolve, 500))
226
+
227
+ try {
228
+ await this.listServerTools(name)
229
+ entry.healthy = true
230
+ logger.info(`[MCPHypervisor] Server "${name}" started (${entry.tools.length} tools)`)
231
+ } catch (error) {
232
+ logger.warn(
233
+ `[MCPHypervisor] Server "${name}" started but tool discovery failed: ${
234
+ error instanceof Error ? error.message : String(error)
235
+ }`,
236
+ )
237
+ // Still mark healthy if the process is alive (tools may not be supported)
238
+ entry.healthy = entry.process.exitCode === null
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Stop a running MCP server process.
244
+ */
245
+ async stopServer(name: string): Promise<void> {
246
+ const entry = this.servers.get(name)
247
+ if (!entry?.process) return
248
+
249
+ entry.process.kill('SIGTERM')
250
+ await new Promise<void>((resolve) => setTimeout(resolve, 200))
251
+
252
+ if (entry.process.exitCode === null) {
253
+ entry.process.kill('SIGKILL')
254
+ }
255
+
256
+ entry.process = null
257
+ entry.healthy = false
258
+ entry.tools = []
259
+ logger.info(`[MCPHypervisor] Stopped server: ${name}`)
260
+ }
261
+
262
+ /**
263
+ * Stop all running servers.
264
+ */
265
+ async stopAll(): Promise<void> {
266
+ this.stopHealthCheckLoop()
267
+ await Promise.all(Array.from(this.servers.keys()).map((name) => this.stopServer(name)))
268
+ }
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // JSON-RPC communication
272
+ // ---------------------------------------------------------------------------
273
+
274
+ /**
275
+ * Send a JSON-RPC request to a running server and await the response.
276
+ */
277
+ private sendRequest(name: string, method: string, params?: unknown): Promise<unknown> {
278
+ const entry = this.servers.get(name)
279
+ if (!entry?.process || entry.process.exitCode !== null) {
280
+ return Promise.reject(new Error(`Server "${name}" is not running`))
281
+ }
282
+
283
+ const id = ++this.requestCounter
284
+ const request: JsonRpcRequest = {
285
+ jsonrpc: '2.0',
286
+ id,
287
+ method,
288
+ ...(params !== undefined && { params }),
289
+ }
290
+
291
+ return new Promise((resolve, reject) => {
292
+ const timer = setTimeout(() => {
293
+ this.pendingRequests.delete(id)
294
+ reject(new Error(`Request ${method} timed out after ${REQUEST_TIMEOUT_MS}ms`))
295
+ }, REQUEST_TIMEOUT_MS)
296
+
297
+ this.pendingRequests.set(id, { resolve, reject, timer })
298
+
299
+ try {
300
+ entry.process?.stdin?.write(`${JSON.stringify(request)}\n`)
301
+ } catch (error) {
302
+ clearTimeout(timer)
303
+ this.pendingRequests.delete(id)
304
+ reject(error)
305
+ }
306
+ })
307
+ }
308
+
309
+ private handleResponse(msg: JsonRpcResponse): void {
310
+ const pending = this.pendingRequests.get(msg.id)
311
+ if (!pending) return
312
+
313
+ clearTimeout(pending.timer)
314
+ this.pendingRequests.delete(msg.id)
315
+
316
+ if (msg.error) {
317
+ pending.reject(new Error(`JSON-RPC error ${msg.error.code}: ${msg.error.message}`))
318
+ } else {
319
+ pending.resolve(msg.result)
320
+ }
321
+ }
322
+
323
+ // ---------------------------------------------------------------------------
324
+ // Health checks
325
+ // ---------------------------------------------------------------------------
326
+
327
+ /**
328
+ * Ping a server — checks process liveness and sends a JSON-RPC `ping`.
329
+ * Updates `entry.healthy`.
330
+ */
331
+ async pingServer(name: string): Promise<boolean> {
332
+ const entry = this.servers.get(name)
333
+ if (!entry) return false
334
+
335
+ // Process liveness check
336
+ if (!entry.process || entry.process.exitCode !== null) {
337
+ entry.healthy = false
338
+ return false
339
+ }
340
+
341
+ entry.lastPingAt = Date.now()
342
+
343
+ try {
344
+ await this.sendRequest(name, 'ping')
345
+ entry.healthy = true
346
+ return true
347
+ } catch {
348
+ // ping not supported by all servers — still healthy if process is alive
349
+ entry.healthy = entry.process.exitCode === null
350
+ return entry.healthy
351
+ }
352
+ }
353
+
354
+ // ---------------------------------------------------------------------------
355
+ // Tool discovery
356
+ // ---------------------------------------------------------------------------
357
+
358
+ /**
359
+ * Discover tools from a running server via JSON-RPC `tools/list`.
360
+ * Caches the result in the server entry.
361
+ */
362
+ async listServerTools(name: string): Promise<MCPTool[]> {
363
+ const entry = this.servers.get(name)
364
+ if (!entry) throw new Error(`Unknown server: "${name}"`)
365
+
366
+ const result = await this.sendRequest(name, 'tools/list')
367
+
368
+ const tools = (result as { tools?: MCPTool[] })?.tools ?? []
369
+ entry.tools = tools
370
+ return tools
371
+ }
372
+
373
+ /**
374
+ * Return all tools from all healthy servers, namespaced as
375
+ * `@@mcp_{serverName}_{toolName}` to avoid collisions.
376
+ */
377
+ getAllTools(): NamespacedTool[] {
378
+ const tools: NamespacedTool[] = []
379
+
380
+ for (const [serverName, entry] of this.servers) {
381
+ if (!entry.healthy) continue
382
+
383
+ for (const tool of entry.tools) {
384
+ tools.push({
385
+ namespacedName: `${MCP_TOOL_PREFIX}_${serverName}_${tool.name}`,
386
+ serverName,
387
+ tool,
388
+ })
389
+ }
390
+ }
391
+
392
+ return tools
393
+ }
394
+
395
+ /**
396
+ * Call a tool on a running MCP server via JSON-RPC `tools/call`.
397
+ *
398
+ * @param serverName - The registered server name
399
+ * @param toolName - The tool name (without namespace prefix)
400
+ * @param args - Arguments to pass to the tool
401
+ */
402
+ async callTool(serverName: string, toolName: string, args: unknown): Promise<unknown> {
403
+ return this.sendRequest(serverName, 'tools/call', {
404
+ name: toolName,
405
+ arguments: args,
406
+ })
407
+ }
408
+
409
+ /**
410
+ * Return the health status of all registered servers.
411
+ */
412
+ getStatus(): Record<string, { healthy: boolean; toolCount: number; pid: number | null }> {
413
+ const status: Record<string, { healthy: boolean; toolCount: number; pid: number | null }> = {}
414
+ for (const [name, entry] of this.servers) {
415
+ status[name] = {
416
+ healthy: entry.healthy,
417
+ toolCount: entry.tools.length,
418
+ pid: entry.process?.pid ?? null,
419
+ }
420
+ }
421
+ return status
422
+ }
423
+
424
+ // ---------------------------------------------------------------------------
425
+ // Health check loop
426
+ // ---------------------------------------------------------------------------
427
+
428
+ private startHealthCheckLoop(): void {
429
+ this.healthCheckTimer = setInterval(async () => {
430
+ for (const [name] of this.servers) {
431
+ try {
432
+ await this.pingServer(name)
433
+ } catch {
434
+ // Already handled inside pingServer
435
+ }
436
+ }
437
+ }, HEALTH_CHECK_INTERVAL_MS)
438
+
439
+ // Don't prevent process exit
440
+ this.healthCheckTimer.unref?.()
441
+ }
442
+
443
+ private stopHealthCheckLoop(): void {
444
+ if (this.healthCheckTimer) {
445
+ clearInterval(this.healthCheckTimer)
446
+ this.healthCheckTimer = null
447
+ }
448
+ }
449
+
450
+ // ---------------------------------------------------------------------------
451
+ // Testing utilities
452
+ // ---------------------------------------------------------------------------
453
+
454
+ /**
455
+ * Reset the singleton (for testing only).
456
+ * @internal
457
+ */
458
+ static _resetForTests(): void {
459
+ if (MCPHypervisor.instance) {
460
+ MCPHypervisor.instance.stopHealthCheckLoop()
461
+ MCPHypervisor.instance = null
462
+ }
463
+ }
464
+ }
package/src/index.ts ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @revealui/mcp
3
+ *
4
+ * Model Context Protocol integrations for RevealUI.
5
+ *
6
+ * Provides:
7
+ * - MCP adapter framework (Vercel, Stripe, Neon)
8
+ * - Database adapter (PGlite / PostgreSQL) with CRDT support
9
+ * - MCP contracts (Zod schemas for request/response/tool bridging)
10
+ * - MCP server launchers (code-validator, stripe, neon, etc.)
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+
15
+ import { isFeatureEnabled } from '@revealui/core/features'
16
+ import { initializeLicense } from '@revealui/core/license'
17
+ import { logger } from '@revealui/core/observability/logger'
18
+
19
+ /**
20
+ * Check if the MCP package is licensed for use.
21
+ * Initializes the license cache from environment variables, then checks the tier.
22
+ * Returns false with a warning log if no Pro/Enterprise license is active.
23
+ */
24
+ export async function checkMcpLicense(): Promise<boolean> {
25
+ await initializeLicense()
26
+ if (!isFeatureEnabled('mcp')) {
27
+ logger.warn(
28
+ '[@revealui/mcp] MCP server integration requires a Pro or Enterprise license. ' +
29
+ 'Visit https://revealui.com/pricing for details.',
30
+ )
31
+ return false
32
+ }
33
+ return true
34
+ }
35
+
36
+ // Database adapter
37
+ export {
38
+ type CrdtOperationsInsert,
39
+ type CrdtOperationsRow,
40
+ connectPglite,
41
+ connectPostgres,
42
+ createMcpDbClient,
43
+ type McpDbClient,
44
+ type QueryResult,
45
+ } from './adapters/db.js'
46
+ // Configuration
47
+ export {
48
+ getMcpConfig,
49
+ type McpConfig as McpEnvConfig,
50
+ type McpMetricsMode,
51
+ } from './config/index.js'
52
+ // Contracts (Zod schemas + tool bridging)
53
+ export {
54
+ agentDefinitionToAgentCard,
55
+ agentDefinitionToMcpTools,
56
+ contractsToolDefinitionToMcpTool,
57
+ type MCPAdapterConfig,
58
+ MCPAdapterConfigSchema,
59
+ type MCPRequest,
60
+ type MCPRequestOptions,
61
+ MCPRequestOptionsSchema,
62
+ MCPRequestSchema,
63
+ type MCPResponse,
64
+ type MCPResponseMetadata,
65
+ MCPResponseMetadataSchema,
66
+ MCPResponseSchema,
67
+ mcpToolToContractsToolDefinition,
68
+ } from './contracts.js'
69
+ // Hypervisor: process management + dynamic tool discovery
70
+ export {
71
+ MCPHypervisor,
72
+ type MCPServerConfig,
73
+ type MCPTool,
74
+ type NamespacedTool,
75
+ } from './hypervisor.js'
76
+ // Adapter framework
77
+ export {
78
+ createMCPAdapter,
79
+ disposeAllAdapters,
80
+ generateIdempotencyKey,
81
+ generateUniqueIdempotencyKey,
82
+ MCPAdapter,
83
+ type MCPConfig,
84
+ NeonAdapter,
85
+ StripeAdapter,
86
+ VercelAdapter,
87
+ } from './servers/adapter.js'