@johpaz/hive-sdk 0.0.14 → 0.0.15

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 (199) hide show
  1. package/.github/CODEOWNERS +9 -0
  2. package/.github/workflows/publish.yml +89 -0
  3. package/.github/workflows/version-bump.yml +102 -0
  4. package/CHANGELOG.md +38 -0
  5. package/README.md +158 -0
  6. package/bun.lock +543 -0
  7. package/bunfig.toml +7 -0
  8. package/docs/API-AGENTS.md +316 -0
  9. package/docs/API-CONTEXT-COMPILER.md +252 -0
  10. package/docs/API-DAG-SCHEDULER.md +273 -0
  11. package/docs/API-TOOLS-SKILLS-CHANNELS.md +293 -0
  12. package/docs/API-WORKERS-EVENTS.md +152 -0
  13. package/docs/INDEX.md +141 -0
  14. package/docs/README.md +68 -0
  15. package/package.json +54 -105
  16. package/packages/cli/package.json +17 -0
  17. package/packages/cli/src/commands/init.ts +56 -0
  18. package/packages/cli/src/commands/run.ts +45 -0
  19. package/packages/cli/src/commands/test.ts +42 -0
  20. package/packages/cli/src/commands/trace.ts +55 -0
  21. package/packages/cli/src/index.ts +43 -0
  22. package/packages/core/package.json +58 -0
  23. package/packages/core/src/ace/Curator.ts +158 -0
  24. package/packages/core/src/ace/Reflector.ts +200 -0
  25. package/packages/core/src/ace/Tracer.ts +100 -0
  26. package/packages/core/src/ace/index.ts +4 -0
  27. package/packages/core/src/agent/AgentRunner.ts +699 -0
  28. package/packages/core/src/agent/Compaction.ts +221 -0
  29. package/packages/core/src/agent/ContextCompiler.ts +567 -0
  30. package/packages/core/src/agent/ContextGuard.ts +91 -0
  31. package/packages/core/src/agent/ConversationStore.ts +244 -0
  32. package/packages/core/src/agent/Hooks.ts +166 -0
  33. package/packages/core/src/agent/NativeTools.ts +31 -0
  34. package/packages/core/src/agent/PromptBuilder.ts +169 -0
  35. package/packages/core/src/agent/Service.ts +267 -0
  36. package/packages/core/src/agent/StuckLoop.ts +133 -0
  37. package/packages/core/src/agent/index.ts +12 -0
  38. package/packages/core/src/agent/providers/LLMClient.ts +149 -0
  39. package/packages/core/src/agent/providers/anthropic.ts +212 -0
  40. package/packages/core/src/agent/providers/gemini.ts +215 -0
  41. package/packages/core/src/agent/providers/index.ts +199 -0
  42. package/packages/core/src/agent/providers/interface.ts +195 -0
  43. package/packages/core/src/agent/providers/ollama.ts +175 -0
  44. package/packages/core/src/agent/providers/openai-compat.ts +231 -0
  45. package/packages/core/src/agent/providers.ts +1 -0
  46. package/packages/core/src/agent/selectors/PlaybookSelector.ts +147 -0
  47. package/packages/core/src/agent/selectors/SkillSelector.ts +478 -0
  48. package/packages/core/src/agent/selectors/ToolSelector.ts +577 -0
  49. package/packages/core/src/agent/selectors/index.ts +6 -0
  50. package/packages/core/src/api/createAgent.test.ts +48 -0
  51. package/packages/core/src/api/createAgent.ts +122 -0
  52. package/packages/core/src/api/index.ts +2 -0
  53. package/packages/core/src/canvas/CanvasManager.ts +390 -0
  54. package/packages/core/src/canvas/a2ui-tools.ts +255 -0
  55. package/packages/core/src/canvas/canvas-tools.ts +448 -0
  56. package/packages/core/src/canvas/emitter.ts +149 -0
  57. package/packages/core/src/canvas/index.ts +6 -0
  58. package/packages/core/src/config/index.ts +2 -0
  59. package/packages/core/src/config/loader.ts +554 -0
  60. package/packages/core/src/ethics/EthicsGuard.test.ts +54 -0
  61. package/packages/core/src/ethics/EthicsGuard.ts +66 -0
  62. package/packages/core/src/ethics/index.ts +2 -0
  63. package/packages/core/src/gateway/channel-notify.test.ts +14 -0
  64. package/packages/core/src/gateway/channel-notify.ts +12 -0
  65. package/packages/core/src/gateway/index.ts +1 -0
  66. package/packages/core/src/index.ts +37 -0
  67. package/packages/core/src/mcp/MCPClient.ts +439 -0
  68. package/packages/core/src/mcp/MCPToolAdapter.ts +176 -0
  69. package/packages/core/src/mcp/config.ts +13 -0
  70. package/packages/core/src/mcp/hot-reload.ts +147 -0
  71. package/packages/core/src/mcp/index.ts +11 -0
  72. package/packages/core/src/mcp/logger.ts +42 -0
  73. package/packages/core/src/mcp/singleton.ts +21 -0
  74. package/packages/core/src/mcp/transports/index.ts +67 -0
  75. package/packages/core/src/mcp/transports/sse.ts +241 -0
  76. package/packages/core/src/mcp/transports/websocket.ts +159 -0
  77. package/packages/core/src/memory/Scratchpad.test.ts +47 -0
  78. package/packages/core/src/memory/Scratchpad.ts +37 -0
  79. package/packages/core/src/memory/Storage.ts +6 -0
  80. package/packages/core/src/memory/index.ts +2 -0
  81. package/packages/core/src/multimodal/VisionService.ts +293 -0
  82. package/packages/core/src/multimodal/index.ts +2 -0
  83. package/packages/core/src/multimodal/types.ts +28 -0
  84. package/packages/core/src/security/Pairing.ts +250 -0
  85. package/packages/core/src/security/RateLimit.ts +270 -0
  86. package/packages/core/src/security/index.ts +4 -0
  87. package/packages/core/src/skills/SkillLoader.ts +388 -0
  88. package/packages/core/src/skills/bundled-data.generated.ts +3332 -0
  89. package/packages/core/src/skills/defineSkill.ts +18 -0
  90. package/packages/core/src/skills/index.ts +4 -0
  91. package/packages/core/src/state/index.ts +2 -0
  92. package/packages/core/src/state/store.ts +312 -0
  93. package/packages/core/src/storage/SQLiteStorage.ts +407 -0
  94. package/packages/core/src/storage/crypto.ts +101 -0
  95. package/packages/core/src/storage/index.ts +10 -0
  96. package/packages/core/src/storage/onboarding.ts +1603 -0
  97. package/packages/core/src/storage/schema.ts +689 -0
  98. package/packages/core/src/storage/seed.ts +740 -0
  99. package/packages/core/src/storage/usage.ts +374 -0
  100. package/packages/core/src/swarm/AgentBus.ts +460 -0
  101. package/packages/core/src/swarm/AgentExecutor.ts +53 -0
  102. package/packages/core/src/swarm/Coordinator.ts +251 -0
  103. package/packages/core/src/swarm/EventBridge.ts +122 -0
  104. package/packages/core/src/swarm/EventBus.ts +169 -0
  105. package/packages/core/src/swarm/TaskGraph.ts +192 -0
  106. package/packages/core/src/swarm/TaskNode.ts +97 -0
  107. package/packages/core/src/swarm/TaskResult.ts +22 -0
  108. package/packages/core/src/swarm/WorkerPool.ts +236 -0
  109. package/packages/core/src/swarm/errors.ts +37 -0
  110. package/packages/core/src/swarm/index.ts +30 -0
  111. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +99 -0
  112. package/packages/core/src/swarm/presets/ResearchPreset.ts +97 -0
  113. package/packages/core/src/swarm/presets/index.ts +4 -0
  114. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +21 -0
  115. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +46 -0
  116. package/packages/core/src/swarm/strategies/index.ts +3 -0
  117. package/packages/core/src/swarm/types.ts +164 -0
  118. package/packages/core/src/tools/ToolExecutor.ts +58 -0
  119. package/packages/core/src/tools/ToolRegistry.test.ts +98 -0
  120. package/packages/core/src/tools/ToolRegistry.ts +61 -0
  121. package/packages/core/src/tools/agents/get-available-models.ts +118 -0
  122. package/packages/core/src/tools/agents/index.ts +715 -0
  123. package/packages/core/src/tools/bridge-events.ts +26 -0
  124. package/packages/core/src/tools/canvas/index.ts +375 -0
  125. package/packages/core/src/tools/cli/index.ts +142 -0
  126. package/packages/core/src/tools/codebridge/index.ts +342 -0
  127. package/packages/core/src/tools/core/index.ts +476 -0
  128. package/packages/core/src/tools/cron/index.ts +626 -0
  129. package/packages/core/src/tools/filesystem/fs-delete.ts +78 -0
  130. package/packages/core/src/tools/filesystem/fs-edit.ts +106 -0
  131. package/packages/core/src/tools/filesystem/fs-exists.ts +63 -0
  132. package/packages/core/src/tools/filesystem/fs-glob.ts +108 -0
  133. package/packages/core/src/tools/filesystem/fs-list.ts +129 -0
  134. package/packages/core/src/tools/filesystem/fs-read.ts +72 -0
  135. package/packages/core/src/tools/filesystem/fs-write.ts +67 -0
  136. package/packages/core/src/tools/filesystem/index.ts +34 -0
  137. package/packages/core/src/tools/filesystem/workspace-guard.ts +62 -0
  138. package/packages/core/src/tools/index.ts +231 -0
  139. package/packages/core/src/tools/meeting/index.ts +363 -0
  140. package/packages/core/src/tools/office/index.ts +47 -0
  141. package/packages/core/src/tools/office/office-escribir-docx.ts +192 -0
  142. package/packages/core/src/tools/office/office-escribir-pdf.ts +172 -0
  143. package/packages/core/src/tools/office/office-escribir-pptx.ts +174 -0
  144. package/packages/core/src/tools/office/office-escribir-xlsx.ts +116 -0
  145. package/packages/core/src/tools/office/office-leer-docx.ts +93 -0
  146. package/packages/core/src/tools/office/office-leer-pdf.ts +114 -0
  147. package/packages/core/src/tools/office/office-leer-pptx.ts +136 -0
  148. package/packages/core/src/tools/office/office-leer-xlsx.ts +124 -0
  149. package/packages/core/src/tools/projects/index.ts +37 -0
  150. package/packages/core/src/tools/projects/project-create.ts +94 -0
  151. package/packages/core/src/tools/projects/project-done.ts +66 -0
  152. package/packages/core/src/tools/projects/project-fail.ts +66 -0
  153. package/packages/core/src/tools/projects/project-list.ts +96 -0
  154. package/packages/core/src/tools/projects/project-update.ts +72 -0
  155. package/packages/core/src/tools/projects/task-create.ts +68 -0
  156. package/packages/core/src/tools/projects/task-evaluate.ts +93 -0
  157. package/packages/core/src/tools/projects/task-update.ts +93 -0
  158. package/packages/core/src/tools/types.ts +39 -0
  159. package/packages/core/src/tools/voice/index.ts +104 -0
  160. package/packages/core/src/tools/web/browser-click.ts +78 -0
  161. package/packages/core/src/tools/web/browser-extract.ts +139 -0
  162. package/packages/core/src/tools/web/browser-navigate.ts +106 -0
  163. package/packages/core/src/tools/web/browser-screenshot.ts +87 -0
  164. package/packages/core/src/tools/web/browser-script.ts +88 -0
  165. package/packages/core/src/tools/web/browser-service.ts +554 -0
  166. package/packages/core/src/tools/web/browser-type.ts +101 -0
  167. package/packages/core/src/tools/web/browser-wait.ts +136 -0
  168. package/packages/core/src/tools/web/index.ts +41 -0
  169. package/packages/core/src/tools/web/web-fetch.ts +78 -0
  170. package/packages/core/src/tools/web/web-search.ts +123 -0
  171. package/packages/core/src/utils/benchmark.ts +80 -0
  172. package/packages/core/src/utils/crypto.ts +73 -0
  173. package/packages/core/src/utils/date.ts +42 -0
  174. package/packages/core/src/utils/index.ts +10 -0
  175. package/packages/core/src/utils/logger.ts +389 -0
  176. package/packages/core/src/utils/retry.ts +70 -0
  177. package/packages/core/src/utils/toon.ts +253 -0
  178. package/packages/core/src/voice/index.ts +656 -0
  179. package/test/setup-db.ts +216 -0
  180. package/tsconfig.json +39 -0
  181. package/src/agents.ts +0 -1
  182. package/src/canvas.ts +0 -1
  183. package/src/channels.ts +0 -1
  184. package/src/config.ts +0 -1
  185. package/src/events.ts +0 -1
  186. package/src/gateway.ts +0 -1
  187. package/src/index.ts +0 -304
  188. package/src/mcp.ts +0 -1
  189. package/src/multimodal.ts +0 -1
  190. package/src/scheduler.ts +0 -1
  191. package/src/security.ts +0 -1
  192. package/src/skills.ts +0 -1
  193. package/src/state.ts +0 -1
  194. package/src/storage.ts +0 -1
  195. package/src/tools.ts +0 -1
  196. package/src/tts.ts +0 -1
  197. package/src/types.ts +0 -82
  198. package/src/utils.ts +0 -1
  199. package/src/voice.ts +0 -1
@@ -0,0 +1,147 @@
1
+ /**
2
+ * MCP Hot Reload
3
+ *
4
+ * Watches for MCP server changes in DB and updates MCP Manager automatically
5
+ *
6
+ * Architecture: Direct Connection
7
+ * - MCP servers are tracked in DB (mcp_servers table)
8
+ * - MCP tools are loaded at runtime from connected servers (not stored in DB)
9
+ */
10
+
11
+ import { getDb } from "../storage/SQLiteStorage.ts";
12
+ import { logger } from "../utils/logger.ts";
13
+ import { decryptConfig } from "../storage/crypto.ts";
14
+ import { syncMCPToolsToDB, syncMCPToolsToFTS, clearMCPToolsFromDB } from "./MCPToolAdapter.ts";
15
+ import type { MCPClientManager } from "../mcp/index.ts";
16
+
17
+ const log = logger.child("mcp:hot-reload");
18
+
19
+ let _watchInterval: Timer | null = null;
20
+ let _lastKnownServers = new Set<string>();
21
+
22
+ /**
23
+ * Start watching for MCP server changes
24
+ * Checks every 2 seconds for new/removed servers
25
+ */
26
+ export function startMCPHotReload(mcpManager: MCPClientManager): void {
27
+ if (_watchInterval) {
28
+ log.warn("MCP Hot Reload already running");
29
+ return;
30
+ }
31
+
32
+ log.info("Starting MCP Hot Reload watcher (2s interval)");
33
+
34
+ // Initial sync - sync all currently connected servers
35
+ syncMCPServers(mcpManager).then(() => {
36
+ log.info("Initial MCP server sync complete");
37
+ }).catch(err => {
38
+ log.error(`Initial MCP server sync failed: ${err.message}`);
39
+ });
40
+
41
+ // Watch for changes
42
+ _watchInterval = setInterval(() => {
43
+ syncMCPServers(mcpManager);
44
+ }, 2000);
45
+ }
46
+
47
+ /**
48
+ * Stop watching
49
+ */
50
+ export function stopMCPHotReload(): void {
51
+ if (_watchInterval) {
52
+ clearInterval(_watchInterval);
53
+ _watchInterval = null;
54
+ log.info("MCP Hot Reload stopped");
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Sync MCP servers from DB to MCP Manager
60
+ * Note: Only server status is tracked, tools are loaded at runtime
61
+ */
62
+ async function syncMCPServers(mcpManager: MCPClientManager): Promise<void> {
63
+ try {
64
+ const db = getDb();
65
+ const dbServers = db.query(`SELECT * FROM mcp_servers WHERE enabled = 1`).all() as Record<string, any>[];
66
+
67
+ const currentServerNames = new Set(dbServers.map(s => s.id || s.name));
68
+
69
+ // Detect new servers
70
+ for (const server of dbServers) {
71
+ const serverName = server.id || server.name;
72
+
73
+ if (!_lastKnownServers.has(serverName)) {
74
+ log.info(`New MCP server detected: ${serverName} - connecting...`);
75
+
76
+ try {
77
+ const mcpServerConfig: any = {
78
+ transport: server.transport,
79
+ command: server.command,
80
+ args: server.args ? JSON.parse(server.args) : [],
81
+ url: server.url,
82
+ enabled: true,
83
+ };
84
+
85
+ if (server.headers_encrypted && server.headers_iv) {
86
+ mcpServerConfig.headers = decryptConfig(server.headers_encrypted, server.headers_iv);
87
+ }
88
+
89
+ // Update MCP Manager config (auto-connects new servers)
90
+ const currentConfig = (mcpManager as any).config || { servers: {} };
91
+ await mcpManager.updateConfig({
92
+ ...currentConfig,
93
+ servers: {
94
+ ...currentConfig.servers,
95
+ [serverName]: mcpServerConfig,
96
+ },
97
+ });
98
+
99
+ // Wait a bit for connection to establish
100
+ await new Promise(resolve => setTimeout(resolve, 500));
101
+
102
+ // Get tools count and update status
103
+ const tools = mcpManager.getServerTools(serverName) || [];
104
+ db.query(`UPDATE mcp_servers SET status = ?, tools_count = ? WHERE id = ?`).run("connected", tools.length, serverName);
105
+
106
+ // Persist MCP tool definitions to DB and FTS5
107
+ // Use server.name (human-readable) for mcpToolId consistency with context-compiler
108
+ syncMCPToolsToDB(server.id || server.name, server.name || serverName, tools);
109
+ await syncMCPToolsToFTS();
110
+
111
+ log.info(`MCP server ${serverName} connected: ${tools.length} tools available`);
112
+ } catch (err) {
113
+ log.error(`Failed to connect MCP server ${serverName}: ${(err as Error).message}`);
114
+ db.query(`UPDATE mcp_servers SET status = ? WHERE id = ?`).run("error", serverName);
115
+ }
116
+ }
117
+ }
118
+
119
+ // Detect removed servers
120
+ for (const oldServerName of _lastKnownServers) {
121
+ if (!currentServerNames.has(oldServerName)) {
122
+ log.info(`MCP server removed: ${oldServerName} - disconnecting...`);
123
+
124
+ try {
125
+ // Remove from MCP Manager
126
+ const currentConfig = (mcpManager as any).config || { servers: {} };
127
+ delete currentConfig.servers[oldServerName];
128
+ await mcpManager.updateConfig(currentConfig);
129
+
130
+ // Delete MCP tool definitions from DB and FTS5
131
+ clearMCPToolsFromDB(oldServerName);
132
+
133
+ // Update DB status
134
+ db.query(`UPDATE mcp_servers SET status = ?, tools_count = 0 WHERE id = ?`).run("disconnected", oldServerName);
135
+
136
+ log.info(`MCP server ${oldServerName} disconnected`);
137
+ } catch (err) {
138
+ log.error(`Failed to disconnect MCP server ${oldServerName}: ${(err as Error).message}`);
139
+ }
140
+ }
141
+ }
142
+
143
+ _lastKnownServers = currentServerNames;
144
+ } catch (err) {
145
+ log.error(`MCP server sync failed: ${(err as Error).message}`);
146
+ }
147
+ }
@@ -0,0 +1,11 @@
1
+ export type { MCPTool, MCPResource, MCPPrompt } from "./MCPClient.ts";
2
+ export { MCPClientManager } from "./MCPClient.ts";
3
+ export type { MCPToolDefinition } from "./MCPToolAdapter.ts";
4
+ export { mcpToolId, syncMCPToolsToDB, syncMCPToolsToFTS, clearMCPToolsFromDB } from "./MCPToolAdapter.ts";
5
+ export type { MCPConfig, MCPServerConfig } from "./config.ts";
6
+ export { setMCPManager, getMCPManager, hasMCPManager } from "./singleton.ts";
7
+ export { startMCPHotReload, stopMCPHotReload } from "./hot-reload.ts";
8
+ export type { LogLevel as MCPLogLevel, LogHandler as MCPLogHandler } from "./logger.ts";
9
+ export { logger as mcpLogger } from "./logger.ts";
10
+ export type { SSETransportConfig, WebSocketTransportConfig, StdioTransportConfig, TransportType, TransportOptions } from "./transports/index.ts";
11
+ export { SSETransport, WebSocketTransport, createTransport } from "./transports/index.ts";
@@ -0,0 +1,42 @@
1
+ export type LogLevel = "debug" | "info" | "warn" | "error";
2
+ export type LogHandler = (level: LogLevel, context: string, message: string, data?: Record<string, unknown>) => void;
3
+
4
+ class Logger {
5
+ private context: string;
6
+ private level: LogLevel = "info";
7
+ private handler: LogHandler | null = null;
8
+
9
+ constructor(context: string, handler: LogHandler | null = null) {
10
+ this.context = context;
11
+ this.handler = handler;
12
+ }
13
+
14
+ setHandler(handler: LogHandler | null): void {
15
+ this.handler = handler;
16
+ }
17
+
18
+ private log(level: LogLevel, message: string, data?: Record<string, unknown>): void {
19
+ if (this.handler) {
20
+ this.handler(level, this.context, message, data);
21
+ return;
22
+ }
23
+
24
+ // Silent by default if no handler is set, to avoid standard console pollution.
25
+ // The consumer (e.g., Hive Agent) should set a handler to bridge these logs.
26
+ }
27
+
28
+ debug(message: string, data?: Record<string, unknown>): void { this.log("debug", message, data); }
29
+ info(message: string, data?: Record<string, unknown>): void { this.log("info", message, data); }
30
+ warn(message: string, data?: Record<string, unknown>): void { this.log("warn", message, data); }
31
+ error(message: string, data?: Record<string, unknown>): void { this.log("error", message, data); }
32
+
33
+ child(context: string): Logger {
34
+ return new Logger(`${this.context}:${context}`, this.handler);
35
+ }
36
+
37
+ setLevel(level: LogLevel): void {
38
+ this.level = level;
39
+ }
40
+ }
41
+
42
+ export const logger = new Logger("mcp");
@@ -0,0 +1,21 @@
1
+ /**
2
+ * MCP Manager Singleton
3
+ *
4
+ * Provides global access to the MCP Manager instance
5
+ */
6
+
7
+ import type { MCPClientManager } from "../mcp/index.ts";
8
+
9
+ let _mcpManager: MCPClientManager | null = null;
10
+
11
+ export function setMCPManager(m: MCPClientManager): void {
12
+ _mcpManager = m;
13
+ }
14
+
15
+ export function getMCPManager(): MCPClientManager | null {
16
+ return _mcpManager;
17
+ }
18
+
19
+ export function hasMCPManager(): boolean {
20
+ return _mcpManager !== null;
21
+ }
@@ -0,0 +1,67 @@
1
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
2
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
3
+
4
+ // CORRECCIÓN 1 — quitar extensión .ts de los imports
5
+ // Bun resuelve los módulos sin extensión correctamente
6
+ // Con .ts puede fallar en algunos contextos de build/bundle
7
+ import { SSETransport, type SSETransportConfig } from "./sse";
8
+ import { WebSocketTransport, type WebSocketTransportConfig } from "./websocket";
9
+
10
+ export { SSETransport, type SSETransportConfig };
11
+ export { WebSocketTransport, type WebSocketTransportConfig };
12
+
13
+ // CORRECCIÓN 2 — exportar StdioTransportConfig
14
+ // Estaba definido pero no exportado — el resto del código no puede importarlo
15
+ export interface StdioTransportConfig {
16
+ command: string;
17
+ args?: string[];
18
+ env?: Record<string, string>;
19
+ }
20
+
21
+ export type TransportType = "stdio" | "sse" | "websocket";
22
+
23
+ export interface TransportOptions {
24
+ type: TransportType;
25
+ stdio?: StdioTransportConfig;
26
+ sse?: SSETransportConfig;
27
+ websocket?: WebSocketTransportConfig;
28
+ }
29
+
30
+ export function createTransport(options: TransportOptions): Transport {
31
+ switch (options.type) {
32
+ case "stdio": {
33
+ if (!options.stdio) {
34
+ throw new Error("stdio config required for stdio transport");
35
+ }
36
+ return new StdioClientTransport({
37
+ command: options.stdio.command,
38
+ args: options.stdio.args ?? [],
39
+ env: options.stdio.env ?? (process.env as Record<string, string>),
40
+ });
41
+ }
42
+
43
+ case "sse": {
44
+ if (!options.sse) {
45
+ throw new Error("sse config required for SSE transport");
46
+ }
47
+ // CORRECCIÓN 3 — sin cast as unknown as Transport
48
+ // SSETransport ahora implementa Transport directamente (implements Transport)
49
+ // el cast doble era señal de que el tipo no estaba bien declarado en la clase
50
+ return new SSETransport(options.sse);
51
+ }
52
+
53
+ case "websocket": {
54
+ if (!options.websocket) {
55
+ throw new Error("websocket config required for WebSocket transport");
56
+ }
57
+ // Igual — WebSocketTransport ahora implementa Transport directamente
58
+ return new WebSocketTransport(options.websocket);
59
+ }
60
+
61
+ default: {
62
+ // exhaustive check — TypeScript avisa si falta un caso
63
+ const _exhaustive: never = options.type;
64
+ throw new Error(`Unknown transport type: ${_exhaustive}`);
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,241 @@
1
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
2
+ import { logger } from "../logger";
3
+
4
+ export interface SSETransportConfig {
5
+ url: string; // URL base
6
+ headers?: Record<string, string>;
7
+ }
8
+
9
+ export class SSETransport implements Transport {
10
+ private baseUrl: string;
11
+ private messagesUrl: string | null = null; // Endpoint recibido del servidor
12
+
13
+ private headers: Record<string, string>;
14
+ private abortController: AbortController | null = null;
15
+ private cookies: string[] = [];
16
+ private startResolve: (() => void) | null = null;
17
+ private startReject: ((err: Error) => void) | null = null;
18
+ sessionId?: string;
19
+
20
+ onmessage: ((message: unknown) => void) | undefined;
21
+ onerror: ((error: Error) => void) | undefined;
22
+ onclose: (() => void) | undefined;
23
+
24
+ constructor(config: SSETransportConfig) {
25
+ this.baseUrl = config.url;
26
+ this.headers = config.headers ?? {};
27
+ }
28
+
29
+ async start(): Promise<void> {
30
+ this.abortController = new AbortController();
31
+
32
+ return new Promise(async (resolve, reject) => {
33
+ // Timeout fallback: if no endpoint received in 5s, continue anyway
34
+ const timeout = setTimeout(() => {
35
+ this.startResolve = null;
36
+ this.startReject = null;
37
+ resolve();
38
+ }, 5000);
39
+
40
+ this.startResolve = () => {
41
+ clearTimeout(timeout);
42
+ this.startResolve = null;
43
+ this.startReject = null;
44
+ resolve();
45
+ };
46
+
47
+ this.startReject = (err) => {
48
+ clearTimeout(timeout);
49
+ this.startResolve = null;
50
+ this.startReject = null;
51
+ reject(err);
52
+ };
53
+
54
+ try {
55
+ logger.debug(`[SSE] Connecting to: ${this.baseUrl}`);
56
+ const response = await fetch(this.baseUrl, {
57
+ method: "GET",
58
+ headers: {
59
+ "Accept": "application/json, text/event-stream",
60
+ "Cache-Control": "no-cache",
61
+ ...this.headers,
62
+ },
63
+ signal: this.abortController!.signal,
64
+ });
65
+
66
+ if (response.status === 405) {
67
+ logger.debug(`[SSE] GET not allowed (405), falling back to Streamable HTTP pattern for ${this.baseUrl}`);
68
+ this.startResolve();
69
+ return;
70
+ }
71
+
72
+ if (!response.ok) {
73
+ this.startReject(new Error(`MCP SSE connection failed: ${response.status} ${response.statusText}`));
74
+ return;
75
+ }
76
+
77
+ this.readSessionId(response);
78
+
79
+ if (response.body) {
80
+ this.startReading(response.body);
81
+ } else {
82
+ this.startResolve();
83
+ }
84
+ } catch (error: any) {
85
+ if (error.name !== "AbortError") {
86
+ this.startReject(error);
87
+ }
88
+ }
89
+ });
90
+ }
91
+
92
+ private readSessionId(response: Response) {
93
+ const sessionId = response.headers.get("x-session-id") ??
94
+ response.headers.get("mcp-session-id");
95
+ if (sessionId) {
96
+ this.sessionId = sessionId;
97
+ }
98
+
99
+ // Track cookies for session affinity (important for n8n/proxies)
100
+ const setCookie = response.headers.get("set-cookie");
101
+ if (setCookie) {
102
+ // Simple cookie extraction: just keep the keys and values
103
+ const newCookies = setCookie.split(',').map(c => c.split(';')[0].trim());
104
+ this.cookies = [...new Set([...this.cookies, ...newCookies])];
105
+ }
106
+ }
107
+
108
+ private startReading(stream: ReadableStream<Uint8Array>) {
109
+ const reader = stream.getReader();
110
+ const decoder = new TextDecoder();
111
+ let buffer = "";
112
+
113
+ this.processStream(reader, decoder, buffer).catch((error) => {
114
+ if (this.onerror && error.name !== "AbortError") {
115
+ this.onerror(error instanceof Error ? error : new Error(String(error)));
116
+ }
117
+ });
118
+ }
119
+
120
+ private async processStream(
121
+ reader: ReadableStreamDefaultReader<Uint8Array>,
122
+ decoder: TextDecoder,
123
+ buffer: string
124
+ ): Promise<void> {
125
+ try {
126
+ let eventType = "message";
127
+ let eventData = "";
128
+
129
+ while (true) {
130
+ const { done, value } = await reader.read();
131
+
132
+ if (done) {
133
+ return;
134
+ }
135
+
136
+ buffer += decoder.decode(value, { stream: true });
137
+
138
+ const lines = buffer.split("\n");
139
+ buffer = lines.pop() ?? "";
140
+
141
+ for (const line of lines) {
142
+ if (line.startsWith("event: ")) {
143
+ eventType = line.slice(7).trim();
144
+ } else if (line.startsWith("data: ")) {
145
+ const data = line.slice(6);
146
+ if (data.trim() === "[DONE]") {
147
+ this.onclose?.();
148
+ return;
149
+ }
150
+ eventData += data + "\n";
151
+ } else if (line === "") {
152
+ if (eventData) {
153
+ eventData = eventData.trim();
154
+ if (eventType === "endpoint") {
155
+ try {
156
+ this.messagesUrl = new URL(eventData, this.baseUrl).href;
157
+ logger.debug(`[SSE] Messages endpoint received: ${this.messagesUrl}`);
158
+ this.startResolve?.();
159
+ } catch (e) {
160
+ logger.warn(`[SSE] Failed to parse endpoint: ${eventData}`);
161
+ }
162
+ } else if (eventType === "message" || eventType === "") {
163
+ try {
164
+ const parsed = JSON.parse(eventData);
165
+ this.onmessage?.(parsed);
166
+ } catch {
167
+ // Ignorar heartbeats o no-JSON
168
+ }
169
+ }
170
+ eventData = "";
171
+ }
172
+ eventType = "message";
173
+ }
174
+ }
175
+ }
176
+ } catch (error: any) {
177
+ if (error.name !== "AbortError") {
178
+ this.onerror?.(error instanceof Error ? error : new Error(String(error)));
179
+ }
180
+ }
181
+ }
182
+
183
+ async close(): Promise<void> {
184
+ this.abortController?.abort();
185
+ this.abortController = null;
186
+ this.sessionId = undefined;
187
+ this.onclose?.();
188
+ }
189
+
190
+ async send(message: unknown): Promise<void> {
191
+ if (!this.abortController) {
192
+ throw new Error("SSE transport not started — llama start() primero");
193
+ }
194
+
195
+ const targetUrl = this.messagesUrl || this.baseUrl;
196
+ let url = targetUrl;
197
+
198
+ if (this.sessionId && !url.includes(`sessionId=${this.sessionId}`)) {
199
+ url = `${targetUrl}${targetUrl.includes('?') ? '&' : '?'}sessionId=${this.sessionId}`;
200
+ }
201
+
202
+ const response = await fetch(url, {
203
+ method: "POST",
204
+ headers: {
205
+ "Content-Type": "application/json",
206
+ "Accept": "application/json, text/event-stream",
207
+ ...this.headers,
208
+ ...(this.cookies.length > 0 ? { "Cookie": this.cookies.join('; ') } : {}),
209
+ },
210
+ body: JSON.stringify(message),
211
+ signal: this.abortController.signal,
212
+ });
213
+
214
+ this.readSessionId(response);
215
+
216
+ if (!response.ok) {
217
+ const body = await response.text().catch(() => "");
218
+ throw new Error(`MCP message failed (${response.status}): ${body || response.statusText}`);
219
+ }
220
+
221
+ const contentType = response.headers.get("content-type") ?? "";
222
+
223
+ if (contentType.includes("text/event-stream") && response.body) {
224
+ this.startReading(response.body);
225
+ }
226
+ else if (contentType.includes("application/json")) {
227
+ const text = await response.text();
228
+ if (text.trim()) {
229
+ try {
230
+ this.onmessage?.(JSON.parse(text));
231
+ } catch {
232
+ // ignored
233
+ }
234
+ }
235
+ }
236
+ }
237
+ }
238
+
239
+ export function createSSETransport(config: SSETransportConfig): Transport {
240
+ return new SSETransport(config) as unknown as Transport;
241
+ }
@@ -0,0 +1,159 @@
1
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
2
+
3
+ export interface WebSocketTransportConfig {
4
+ url: string;
5
+ headers?: Record<string, string>;
6
+ reconnect?: boolean; // reconectar automáticamente si se cae (default: true)
7
+ reconnectDelay?: number; // ms entre intentos de reconexión (default: 3000)
8
+ reconnectMaxAttempts?: number; // máximo de intentos (default: 10)
9
+ }
10
+
11
+ export class WebSocketTransport implements Transport {
12
+ private url: string;
13
+ private ws: WebSocket | null = null;
14
+ private headers?: Record<string, string>;
15
+ private intentionallyClosed = false; // distingue close() manual de caída
16
+ private reconnectAttempts = 0;
17
+ private reconnectDelay: number;
18
+ private reconnectMaxAttempts: number;
19
+ private shouldReconnect: boolean;
20
+
21
+ onmessage: ((message: unknown) => void) | undefined;
22
+ onerror: ((error: Error) => void) | undefined;
23
+ onclose: (() => void) | undefined;
24
+
25
+ constructor(config: WebSocketTransportConfig) {
26
+ this.url = config.url;
27
+ this.headers = config.headers;
28
+ this.shouldReconnect = config.reconnect ?? true;
29
+ this.reconnectDelay = config.reconnectDelay ?? 3000;
30
+ this.reconnectMaxAttempts = config.reconnectMaxAttempts ?? 10;
31
+ }
32
+
33
+ async start(): Promise<void> {
34
+ this.intentionallyClosed = false;
35
+ this.reconnectAttempts = 0;
36
+ return this.connect();
37
+ }
38
+
39
+ private connect(): Promise<void> {
40
+ return new Promise((resolve, reject) => {
41
+
42
+ // CORRECCIÓN 1 — headers en Bun WebSocket
43
+ // Bun acepta las opciones como segundo argumento cuando no hay subprotocols,
44
+ // o como objeto con `headers` dentro de un array de subprotocols vacío.
45
+ // La forma más segura y compatible:
46
+ const ws = this.headers && Object.keys(this.headers).length > 0
47
+ ? new WebSocket(this.url, {
48
+ // @ts-expect-error — Bun extiende la API estándar de WebSocket
49
+ headers: this.headers,
50
+ })
51
+ : new WebSocket(this.url);
52
+
53
+ this.ws = ws;
54
+ let resolved = false;
55
+
56
+ ws.onopen = () => {
57
+ resolved = true;
58
+ this.reconnectAttempts = 0; // reset contador al conectar exitosamente
59
+ resolve();
60
+ };
61
+
62
+ ws.onmessage = (event: MessageEvent) => {
63
+ try {
64
+ const data = JSON.parse(event.data as string);
65
+ this.onmessage?.(data);
66
+ } catch {
67
+ // Ignorar mensajes no-JSON (ping/pong, heartbeats)
68
+ }
69
+ };
70
+
71
+ // CORRECCIÓN 2 — separar el error de conexión del error post-conexión
72
+ ws.onerror = (event: Event) => {
73
+ const error =
74
+ (event as ErrorEvent).error ??
75
+ new Error(`WebSocket error en ${this.url}`);
76
+
77
+ if (!resolved) {
78
+ // Error durante la conexión inicial → rechazar la promesa
79
+ reject(error);
80
+ } else {
81
+ // Error después de conectar → notificar sin rechazar
82
+ this.onerror?.(error instanceof Error ? error : new Error(String(error)));
83
+ }
84
+ };
85
+
86
+ // CORRECCIÓN 3 — reconexión automática en cierre inesperado
87
+ ws.onclose = (event: CloseEvent) => {
88
+ this.ws = null;
89
+
90
+ if (this.intentionallyClosed) {
91
+ // Cierre manual con close() — notificar y no reconectar
92
+ this.onclose?.();
93
+ return;
94
+ }
95
+
96
+ if (!resolved) {
97
+ // Cierre antes de que se abriera → rechazar la promesa
98
+ reject(new Error(
99
+ `WebSocket cerrado antes de conectar — code: ${event.code}, reason: ${event.reason}`
100
+ ));
101
+ return;
102
+ }
103
+
104
+ // Cierre inesperado después de conectar
105
+ if (
106
+ this.shouldReconnect &&
107
+ this.reconnectAttempts < this.reconnectMaxAttempts
108
+ ) {
109
+ this.reconnectAttempts++;
110
+ const delay = this.reconnectDelay * this.reconnectAttempts; // backoff lineal
111
+
112
+ setTimeout(async () => {
113
+ try {
114
+ await this.connect();
115
+ } catch (err) {
116
+ this.onerror?.(err instanceof Error ? err : new Error(String(err)));
117
+ this.onclose?.();
118
+ }
119
+ }, delay);
120
+ } else {
121
+ // Sin más intentos → notificar cierre definitivo
122
+ this.onerror?.(new Error(
123
+ `WebSocket desconectado después de ${this.reconnectAttempts} intentos — ${this.url}`
124
+ ));
125
+ this.onclose?.();
126
+ }
127
+ };
128
+ });
129
+ }
130
+
131
+ async close(): Promise<void> {
132
+ this.intentionallyClosed = true; // marcar como cierre intencional
133
+ if (this.ws) {
134
+ // Código 1000 = cierre normal
135
+ this.ws.close(1000, "Client closed connection");
136
+ this.ws = null;
137
+ }
138
+ }
139
+
140
+ async send(message: unknown): Promise<void> {
141
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
142
+ throw new Error(
143
+ `WebSocket no está conectado — readyState: ${this.ws?.readyState ?? "null"}`
144
+ );
145
+ }
146
+ this.ws.send(JSON.stringify(message));
147
+ }
148
+
149
+ // Útil para health checks desde el MCP client
150
+ get isConnected(): boolean {
151
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
152
+ }
153
+ }
154
+
155
+ export function createWebSocketTransport(
156
+ config: WebSocketTransportConfig
157
+ ): Transport {
158
+ return new WebSocketTransport(config) as unknown as Transport;
159
+ }