@johpaz/hive-sdk 0.0.12 → 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,37 @@
1
+ export { createAgent } from "./api/index.ts";
2
+ export type { AgentConfig, Agent, AgentEvent } from "./api/index.ts";
3
+
4
+ export { defineTool } from "./tools/ToolRegistry.ts";
5
+ export type { ToolDefinition } from "./tools/ToolRegistry.ts";
6
+ export { ToolRegistry } from "./tools/ToolRegistry.ts";
7
+ export { ToolExecutor } from "./tools/ToolExecutor.ts";
8
+ export type { ToolExecutionResult } from "./tools/ToolExecutor.ts";
9
+
10
+ export { defineSkill } from "./skills/defineSkill.ts";
11
+ export type { SkillDefinition } from "./skills/defineSkill.ts";
12
+ export { SkillLoader } from "./skills/index.ts";
13
+ export type { Skill, SkillStep, OutputFormat, SkillsConfig } from "./skills/index.ts";
14
+
15
+ export { AgentLoop, runAgent, runAgentIsolated, buildAgentLoop, getAgentLoop, rebuildAgentLoop } from "./agent/index.ts";
16
+ export type { AgentLoopOptions, StepEvent, StreamChunk } from "./agent/index.ts";
17
+ export type { Tool, ToolParameter, ToolResult } from "./agent/index.ts";
18
+ export { DAGScheduler } from "./swarm/index.ts";
19
+ export type { DAGSchedulerOptions, IAgentExecutor } from "./swarm/index.ts";
20
+ export { TaskGraph, TaskNode } from "./swarm/index.ts";
21
+ export type { TaskNodeConfig, NodeStatus, DAGResult, NodeSummary } from "./swarm/index.ts";
22
+
23
+ export { MCPClientManager } from "./mcp/index.ts";
24
+ export type { MCPTool, MCPResource, MCPPrompt, MCPConfig, MCPServerConfig } from "./mcp/index.ts";
25
+
26
+ export { EthicsGuard } from "./ethics/index.ts";
27
+ export type { EthicsRule } from "./ethics/index.ts";
28
+
29
+ export { Scratchpad } from "./memory/index.ts";
30
+ export type { IStorage } from "./memory/index.ts";
31
+
32
+ export { initializeDatabase, dbService } from "./storage/index.ts";
33
+
34
+ export { loadConfig, loadEnv, getHiveDir } from "./config/index.ts";
35
+ export type { Config } from "./config/index.ts";
36
+
37
+ export { logger } from "./utils/index.ts";
@@ -0,0 +1,439 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
3
+ import type { MCPConfig, MCPServerConfig } from "./config";
4
+ import { logger, type LogHandler } from "./logger";
5
+ import * as path from "node:path";
6
+ import {
7
+ createTransport,
8
+ type TransportType,
9
+ } from "./transports/index";
10
+
11
+ export interface MCPTool {
12
+ name: string;
13
+ description: string;
14
+ inputSchema: Record<string, unknown>;
15
+ }
16
+
17
+ export interface MCPResource {
18
+ uri: string;
19
+ name: string;
20
+ description?: string;
21
+ mimeType?: string;
22
+ }
23
+
24
+ export interface MCPPrompt {
25
+ name: string;
26
+ description?: string;
27
+ arguments?: Array<{
28
+ name: string;
29
+ description?: string;
30
+ required?: boolean;
31
+ }>;
32
+ }
33
+
34
+ interface MCPServerState {
35
+ name: string;
36
+ config: MCPServerConfig;
37
+ client: Client | null;
38
+ transport: Transport | null;
39
+ status: "connected" | "disconnected" | "error" | "connecting";
40
+ tools: MCPTool[];
41
+ resources: MCPResource[];
42
+ prompts: MCPPrompt[];
43
+ reconnectAttempts: number;
44
+ lastError?: string; // CORRECCIÓN 3 — guardar el mensaje de error
45
+ }
46
+
47
+ export class MCPClientManager {
48
+ private servers: Map<string, MCPServerState> = new Map();
49
+ private config: MCPConfig;
50
+ private log = logger.child("mcp");
51
+
52
+ constructor(config: MCPConfig) {
53
+ this.config = config;
54
+ }
55
+
56
+ setLogHandler(handler: LogHandler): void {
57
+ logger.setHandler(handler);
58
+ }
59
+
60
+ async initialize(): Promise<void> {
61
+ const servers = this.config.servers ?? {};
62
+
63
+ for (const [name, serverConfig] of Object.entries(servers)) {
64
+ if (serverConfig.enabled !== false) {
65
+ this.servers.set(name, {
66
+ name,
67
+ config: serverConfig as MCPServerConfig,
68
+ client: null,
69
+ transport: null,
70
+ status: "disconnected",
71
+ tools: [],
72
+ resources: [],
73
+ prompts: [],
74
+ reconnectAttempts: 0,
75
+ });
76
+ }
77
+ }
78
+
79
+ this.log.info(`MCP Client initialized with ${this.servers.size} servers`);
80
+
81
+ // CORRECCIÓN 1 — conectar todos los servers al inicializar
82
+ // initialize() solo registraba los servers pero nunca llamaba connectAll()
83
+ // por eso el log mostraba "initialized with 2 servers" pero nunca conectaba
84
+ await this.connectAll();
85
+ }
86
+
87
+ async updateConfig(config: MCPConfig): Promise<void> {
88
+ this.config = config;
89
+ const newServers = this.config.servers ?? {};
90
+
91
+ // Eliminar servers que ya no están en la config o fueron deshabilitados
92
+ for (const name of this.servers.keys()) {
93
+ if (!newServers[name] || newServers[name].enabled === false) {
94
+ await this.disconnectServer(name);
95
+ this.servers.delete(name);
96
+ }
97
+ }
98
+
99
+ // Añadir o actualizar servers
100
+ for (const [name, serverConfig] of Object.entries(newServers)) {
101
+ if (serverConfig.enabled !== false) {
102
+ const existing = this.servers.get(name);
103
+ if (existing) {
104
+ const configChanged =
105
+ JSON.stringify(existing.config) !== JSON.stringify(serverConfig);
106
+ if (configChanged) {
107
+ const wasConnected = existing.status === "connected";
108
+ await this.disconnectServer(name);
109
+ existing.config = serverConfig as MCPServerConfig;
110
+ if (wasConnected) {
111
+ await this.connectServer(name).catch((err) => {
112
+ this.log.error(
113
+ `Failed to reconnect ${name} after config update: ${err.message}`
114
+ );
115
+ });
116
+ }
117
+ }
118
+ } else {
119
+ // Server nuevo — añadir y conectar inmediatamente
120
+ this.servers.set(name, {
121
+ name,
122
+ config: serverConfig as MCPServerConfig,
123
+ client: null,
124
+ transport: null,
125
+ status: "disconnected",
126
+ tools: [],
127
+ resources: [],
128
+ prompts: [],
129
+ reconnectAttempts: 0,
130
+ });
131
+ // CORRECCIÓN 2 — conectar el server nuevo inmediatamente
132
+ await this.connectServer(name).catch((err) => {
133
+ this.log.error(`Failed to connect new server ${name}: ${err.message}`);
134
+ });
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ private expandPath(p: string): string {
141
+ if (p.startsWith("~")) {
142
+ return path.join(process.env.HOME ?? "", p.slice(1));
143
+ }
144
+ return p;
145
+ }
146
+
147
+ private createTransportForServer(state: MCPServerState): Transport {
148
+ const transportType = state.config.transport as TransportType;
149
+
150
+ switch (transportType) {
151
+ case "stdio": {
152
+ const command = state.config.command ?? "npx";
153
+ const args = state.config.args ?? [];
154
+
155
+ const env: Record<string, string> = {};
156
+ for (const [key, value] of Object.entries(process.env)) {
157
+ if (value !== undefined) env[key] = value;
158
+ }
159
+ if (state.config.env) {
160
+ for (const [key, value] of Object.entries(state.config.env)) {
161
+ env[key] = this.expandPath(value);
162
+ }
163
+ }
164
+
165
+ return createTransport({ type: "stdio", stdio: { command, args, env } });
166
+ }
167
+
168
+ case "sse": {
169
+ const url = state.config.url;
170
+ if (!url) throw new Error("SSE transport requires 'url' config");
171
+ return createTransport({
172
+ type: "sse",
173
+ sse: { url, headers: state.config.headers },
174
+ });
175
+ }
176
+
177
+ case "websocket": {
178
+ const url = state.config.url;
179
+ if (!url) throw new Error("WebSocket transport requires 'url' config");
180
+ return createTransport({
181
+ type: "websocket",
182
+ websocket: { url, headers: state.config.headers },
183
+ });
184
+ }
185
+
186
+ default:
187
+ throw new Error(`Unknown transport type: ${transportType}`);
188
+ }
189
+ }
190
+
191
+ async connectServer(name: string): Promise<void> {
192
+ const state = this.servers.get(name);
193
+ if (!state) throw new Error(`MCP server not found: ${name}`);
194
+ if (state.status === "connected") return;
195
+
196
+ state.status = "connecting";
197
+ state.lastError = undefined; // limpiar error anterior
198
+ this.log.info(`Connecting to MCP server: ${name}`);
199
+
200
+ try {
201
+ const transport = this.createTransportForServer(state);
202
+
203
+ const client = new Client(
204
+ { name: "hive", version: "0.1.0" },
205
+ { capabilities: {} }
206
+ );
207
+
208
+ await client.connect(transport);
209
+
210
+ state.client = client;
211
+ state.transport = transport;
212
+ state.status = "connected";
213
+ state.reconnectAttempts = 0;
214
+
215
+ await this.discoverCapabilities(name);
216
+
217
+ this.log.info(`Connected to MCP server: ${name}`, {
218
+ tools: state.tools.length,
219
+ resources: state.resources.length,
220
+ prompts: state.prompts.length,
221
+ });
222
+ } catch (error) {
223
+ state.status = "error";
224
+ // CORRECCIÓN 3 — guardar el mensaje de error para mostrarlo en el dashboard
225
+ state.lastError = (error as Error).message;
226
+ this.log.error(`Failed to connect to MCP server ${name}: ${state.lastError}`);
227
+ throw error;
228
+ }
229
+ }
230
+
231
+ private async discoverCapabilities(name: string): Promise<void> {
232
+ const state = this.servers.get(name);
233
+ if (!state?.client) return;
234
+
235
+ try {
236
+ const toolsResult = await state.client.listTools();
237
+ state.tools = (toolsResult.tools ?? []).map((t) => ({
238
+ name: t.name,
239
+ description: t.description ?? "",
240
+ inputSchema: t.inputSchema as Record<string, unknown>,
241
+ }));
242
+ } catch {
243
+ this.log.debug(`No tools from MCP server: ${name}`);
244
+ }
245
+
246
+ try {
247
+ const resourcesResult = await state.client.listResources();
248
+ state.resources = (resourcesResult.resources ?? []).map((r) => ({
249
+ uri: r.uri,
250
+ name: r.name,
251
+ description: r.description,
252
+ mimeType: r.mimeType,
253
+ }));
254
+ } catch {
255
+ this.log.debug(`No resources from MCP server: ${name}`);
256
+ }
257
+
258
+ try {
259
+ const promptsResult = await state.client.listPrompts();
260
+ state.prompts = (promptsResult.prompts ?? []).map((p) => ({
261
+ name: p.name,
262
+ description: p.description,
263
+ arguments: p.arguments,
264
+ }));
265
+ } catch {
266
+ this.log.debug(`No prompts from MCP server: ${name}`);
267
+ }
268
+ }
269
+
270
+ async disconnectServer(name: string): Promise<void> {
271
+ const state = this.servers.get(name);
272
+ if (!state) return;
273
+
274
+ if (state.client) {
275
+ try {
276
+ await state.client.close();
277
+ } catch {
278
+ // Ignorar errores al cerrar
279
+ }
280
+ }
281
+
282
+ state.client = null;
283
+ state.transport = null;
284
+ state.status = "disconnected";
285
+ state.lastError = undefined;
286
+
287
+ this.log.info(`Disconnected from MCP server: ${name}`);
288
+ }
289
+
290
+ async callTool(
291
+ serverName: string,
292
+ toolName: string,
293
+ args: Record<string, unknown>
294
+ ): Promise<unknown> {
295
+ const state = this.servers.get(serverName);
296
+ if (!state?.client) {
297
+ throw new Error(`MCP server not connected: ${serverName}`);
298
+ }
299
+
300
+ this.log.debug(`Calling MCP tool: ${serverName}/${toolName}`, { args });
301
+
302
+ const result = await state.client.callTool({
303
+ name: toolName,
304
+ arguments: args,
305
+ });
306
+
307
+ return result.content;
308
+ }
309
+
310
+ async readResource(serverName: string, uri: string): Promise<unknown> {
311
+ const state = this.servers.get(serverName);
312
+ if (!state?.client) {
313
+ throw new Error(`MCP server not connected: ${serverName}`);
314
+ }
315
+
316
+ const result = await state.client.readResource({ uri });
317
+ return result.contents;
318
+ }
319
+
320
+ getServerStatus(name: string): MCPServerState["status"] | undefined {
321
+ return this.servers.get(name)?.status;
322
+ }
323
+
324
+ getServerTools(name: string): MCPTool[] {
325
+ return this.servers.get(name)?.tools ?? [];
326
+ }
327
+
328
+ getServerResources(name: string): MCPResource[] {
329
+ return this.servers.get(name)?.resources ?? [];
330
+ }
331
+
332
+ getAllTools(): Map<string, MCPTool[]> {
333
+ const result = new Map<string, MCPTool[]>();
334
+ for (const [name, state] of this.servers) {
335
+ if (state.status === "connected") {
336
+ result.set(name, state.tools);
337
+ }
338
+ }
339
+ return result;
340
+ }
341
+
342
+ listServers(): Array<{
343
+ name: string;
344
+ status: string;
345
+ tools: MCPTool[];
346
+ resources: MCPResource[];
347
+ prompts: MCPPrompt[];
348
+ url?: string;
349
+ error?: string;
350
+ }> {
351
+ return Array.from(this.servers.values()).map((s) => ({
352
+ name: s.name,
353
+ status: s.status,
354
+ tools: s.tools,
355
+ resources: s.resources,
356
+ prompts: s.prompts,
357
+ url: s.config.transport === "stdio" ? `${s.config.command} ${s.config.args?.join(" ")}` : s.config.url,
358
+ error: s.lastError,
359
+ }));
360
+ }
361
+
362
+ getServerDetails(
363
+ name: string
364
+ ):
365
+ | {
366
+ name: string;
367
+ status: string;
368
+ tools: MCPTool[];
369
+ resources: MCPResource[];
370
+ prompts: MCPPrompt[];
371
+ config: MCPServerConfig;
372
+ error?: string;
373
+ }
374
+ | undefined {
375
+ const s = this.servers.get(name);
376
+ if (!s) return undefined;
377
+
378
+ // CORRECCIÓN 4 — redactar headers con tokens antes de exponer al dashboard
379
+ const safeConfig: MCPServerConfig = {
380
+ ...s.config,
381
+ headers: s.config.headers
382
+ ? Object.fromEntries(
383
+ Object.entries(s.config.headers).map(([k, v]) => [
384
+ k,
385
+ k.toLowerCase().includes("auth") ||
386
+ k.toLowerCase().includes("token") ||
387
+ k.toLowerCase().includes("key")
388
+ ? `${(v as string).slice(0, 4)}••••••••`
389
+ : v,
390
+ ])
391
+ )
392
+ : undefined,
393
+ };
394
+
395
+ return {
396
+ name: s.name,
397
+ status: s.status,
398
+ tools: s.tools,
399
+ resources: s.resources,
400
+ prompts: s.prompts,
401
+ config: safeConfig,
402
+ error: s.lastError,
403
+ };
404
+ }
405
+
406
+ async connectAll(): Promise<void> {
407
+ const promises: Promise<void>[] = [];
408
+
409
+ for (const name of this.servers.keys()) {
410
+ promises.push(
411
+ this.connectServer(name).catch((error) => {
412
+ // No relanzar — el Gateway sigue funcionando sin ese server
413
+ this.log.error(`Failed to connect ${name}: ${error.message}`);
414
+ })
415
+ );
416
+ }
417
+
418
+ await Promise.allSettled(promises);
419
+ }
420
+
421
+ async reconnectAll(): Promise<void> {
422
+ await this.disconnectAll();
423
+ await this.connectAll();
424
+ }
425
+
426
+ async disconnectAll(): Promise<void> {
427
+ const promises: Promise<void>[] = [];
428
+
429
+ for (const name of this.servers.keys()) {
430
+ promises.push(this.disconnectServer(name));
431
+ }
432
+
433
+ await Promise.allSettled(promises);
434
+ }
435
+ }
436
+
437
+ export function createMCPManager(config: MCPConfig): MCPClientManager {
438
+ return new MCPClientManager(config);
439
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * MCP Tool Sync — Persist MCP tool definitions to DB and FTS5
3
+ *
4
+ * When an MCP server connects, its tool definitions are persisted to
5
+ * the mcp_tools table and indexed in mcp_tools_fts for search_knowledge.
6
+ * When the server disconnects, all its tools are deleted from both.
7
+ *
8
+ * This enables:
9
+ * 1. search_knowledge to find MCP tools via FTS5
10
+ * 2. Tool definitions to survive across context compiler invocations
11
+ * 3. Offline visibility of what MCP tools were available
12
+ */
13
+
14
+ import { getDb } from "../storage/SQLiteStorage.ts"
15
+ import { logger } from "../utils/logger.ts"
16
+
17
+ const log = logger.child("mcp:tool-sync")
18
+
19
+ export interface MCPToolDefinition {
20
+ name: string
21
+ description: string
22
+ inputSchema?: Record<string, unknown>
23
+ }
24
+
25
+ /**
26
+ * Generate a stable ID for an MCP tool based on server + tool name.
27
+ * Uses the same sanitization as mcpToolFullName for consistency.
28
+ */
29
+ export function mcpToolId(serverName: string, toolName: string): string {
30
+ const safe = (s: string) => s.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_.\-:]/g, '_')
31
+ const full = `${safe(serverName)}__${safe(toolName)}`
32
+ const trimmed = full.length > 64 ? full.substring(0, 64) : full
33
+ return /^[a-zA-Z_]/.test(trimmed) ? trimmed : `_${trimmed}`.substring(0, 64)
34
+ }
35
+
36
+ /**
37
+ * Persist MCP tool definitions to the mcp_tools table.
38
+ * Called when a server connects or reconnects.
39
+ * Deletes existing tools for the server first, then inserts fresh data.
40
+ */
41
+ export function syncMCPToolsToDB(
42
+ serverId: string,
43
+ serverName: string,
44
+ tools: MCPToolDefinition[]
45
+ ): void {
46
+ const db = getDb()
47
+
48
+ try {
49
+ const deleteExisting = db.prepare("DELETE FROM mcp_tools WHERE server_id = ?")
50
+ deleteExisting.run(serverId)
51
+
52
+ if (tools.length === 0) {
53
+ log.debug(`[mcp:tool-sync] No tools to persist for server ${serverName}`)
54
+ return
55
+ }
56
+
57
+ const insertTool = db.prepare(`
58
+ INSERT INTO mcp_tools(id, server_id, server_name, tool_name, description, category, active, created_at, updated_at)
59
+ VALUES (?, ?, ?, ?, ?, 'mcp', 1, (unixepoch()), (unixepoch()))
60
+ `)
61
+
62
+ let count = 0
63
+ for (const tool of tools) {
64
+ const id = mcpToolId(serverName, tool.name)
65
+ insertTool.run(id, serverId, serverName, tool.name, tool.description || "")
66
+ count++
67
+ }
68
+
69
+ log.info(`[mcp:tool-sync] Persisted ${count} MCP tools for server ${serverName} to mcp_tools`)
70
+ } catch (err) {
71
+ log.error(`[mcp:tool-sync] Failed to persist MCP tools for server ${serverName}:`, err)
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Sync all active MCP tools from mcp_tools table to mcp_tools_fts.
77
+ * Called after syncMCPToolsToDB or after startup to ensure FTS5 is in sync.
78
+ *
79
+ * This does a full clear + re-insert to avoid schema drift.
80
+ */
81
+ export async function syncMCPToolsToFTS(): Promise<void> {
82
+ const db = getDb()
83
+
84
+ try {
85
+ const syncTransaction = db.transaction(() => {
86
+ // Verify table exists
87
+ const tableCheck = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='mcp_tools_fts'").get()
88
+ if (!tableCheck) {
89
+ throw new Error("mcp_tools_fts table does not exist!")
90
+ }
91
+
92
+ // Clear existing FTS data
93
+ db.run("DELETE FROM mcp_tools_fts")
94
+
95
+ // Re-populate from mcp_tools where active
96
+ const mcpTools = db.query(`
97
+ SELECT id, server_name, tool_name, description, category
98
+ FROM mcp_tools
99
+ WHERE active = 1
100
+ `).all() as Array<{ id: string; server_name: string; tool_name: string; description: string; category: string }>
101
+
102
+ if (mcpTools.length === 0) {
103
+ log.debug(`[mcp:tool-sync] No MCP tools to sync to FTS5`)
104
+ return
105
+ }
106
+
107
+ const insert = db.prepare(`
108
+ INSERT INTO mcp_tools_fts(id, server_name, tool_name, description, category)
109
+ VALUES (?, ?, ?, ?, ?)
110
+ `)
111
+
112
+ for (const tool of mcpTools) {
113
+ insert.run(tool.id, tool.server_name, tool.tool_name, tool.description, tool.category)
114
+ }
115
+
116
+ log.info(`[mcp:tool-sync] Synced ${mcpTools.length} MCP tools to mcp_tools_fts`)
117
+ })
118
+
119
+ syncTransaction()
120
+ } catch (err) {
121
+ log.error(`[mcp:tool-sync] Failed to sync MCP tools to FTS5:`, err)
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Delete all MCP tool definitions for a server from both mcp_tools and mcp_tools_fts.
127
+ * Called when a server disconnects or is removed.
128
+ */
129
+ export function clearMCPToolsFromDB(serverId: string): void {
130
+ const db = getDb()
131
+
132
+ try {
133
+ // Delete from mcp_tools (CASCADE will handle FTS5 via trigger or manual sync)
134
+ const result = db.query("DELETE FROM mcp_tools WHERE server_id = ?").run(serverId)
135
+
136
+ log.info(`[mcp:tool-sync] Cleared MCP tools for server_id=${serverId}`)
137
+
138
+ // Re-sync FTS5 to remove stale entries
139
+ syncMCPToolsToFTSSync()
140
+ } catch (err) {
141
+ log.error(`[mcp:tool-sync] Failed to clear MCP tools for server_id=${serverId}:`, err)
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Synchronous version of syncMCPToolsToFTS for use in clearMCPToolsFromDB.
147
+ * Avoids async/await in transaction that was already started.
148
+ */
149
+ function syncMCPToolsToFTSSync(): void {
150
+ const db = getDb()
151
+
152
+ try {
153
+ db.run("DELETE FROM mcp_tools_fts")
154
+
155
+ const mcpTools = db.query(`
156
+ SELECT id, server_name, tool_name, description, category
157
+ FROM mcp_tools
158
+ WHERE active = 1
159
+ `).all() as Array<{ id: string; server_name: string; tool_name: string; description: string; category: string }>
160
+
161
+ if (mcpTools.length === 0) return
162
+
163
+ const insert = db.prepare(`
164
+ INSERT INTO mcp_tools_fts(id, server_name, tool_name, description, category)
165
+ VALUES (?, ?, ?, ?, ?)
166
+ `)
167
+
168
+ for (const tool of mcpTools) {
169
+ insert.run(tool.id, tool.server_name, tool.tool_name, tool.description, tool.category)
170
+ }
171
+
172
+ log.debug(`[mcp:tool-sync] Re-synced ${mcpTools.length} MCP tools to FTS5 after deletion`)
173
+ } catch (err) {
174
+ log.error(`[mcp:tool-sync] Failed to re-sync MCP tools to FTS5:`, err)
175
+ }
176
+ }
@@ -0,0 +1,13 @@
1
+ export interface MCPConfig {
2
+ servers?: Record<string, MCPServerConfig>;
3
+ }
4
+
5
+ export interface MCPServerConfig {
6
+ transport: "stdio" | "sse" | "websocket";
7
+ enabled?: boolean;
8
+ command?: string;
9
+ args?: string[];
10
+ env?: Record<string, string>;
11
+ url?: string;
12
+ headers?: Record<string, string>;
13
+ }