@hailer/mcp 1.0.29 → 1.1.3

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 (233) hide show
  1. package/.claude/.session-checked +1 -0
  2. package/.claude/agents/agent-ada-skill-builder.md +10 -2
  3. package/.claude/agents/agent-alejandro-function-fields.md +104 -37
  4. package/.claude/agents/agent-bjorn-config-audit.md +41 -21
  5. package/.claude/agents/agent-builder-agent-creator.md +13 -3
  6. package/.claude/agents/agent-code-simplifier.md +53 -0
  7. package/.claude/agents/agent-dmitri-activity-crud.md +126 -11
  8. package/.claude/agents/agent-giuseppe-app-builder.md +212 -22
  9. package/.claude/agents/agent-gunther-mcp-tools.md +7 -36
  10. package/.claude/agents/agent-helga-workflow-config.md +75 -10
  11. package/.claude/agents/agent-igor-activity-mover-automation.md +125 -0
  12. package/.claude/agents/agent-ingrid-doc-templates.md +164 -36
  13. package/.claude/agents/agent-ivan-monolith.md +154 -0
  14. package/.claude/agents/agent-kenji-data-reader.md +15 -8
  15. package/.claude/agents/agent-lars-code-inspector.md +56 -8
  16. package/.claude/agents/agent-marco-mockup-builder.md +110 -0
  17. package/.claude/agents/agent-marcus-api-documenter.md +323 -0
  18. package/.claude/agents/agent-marketplace-publisher.md +232 -72
  19. package/.claude/agents/agent-marketplace-reviewer.md +255 -79
  20. package/.claude/agents/agent-permissions-handler.md +208 -0
  21. package/.claude/agents/agent-simple-writer.md +48 -0
  22. package/.claude/agents/agent-svetlana-code-review.md +127 -14
  23. package/.claude/agents/agent-tanya-test-runner.md +333 -0
  24. package/.claude/agents/agent-ui-designer.md +100 -0
  25. package/.claude/agents/agent-viktor-sql-insights.md +19 -6
  26. package/.claude/agents/agent-web-search.md +55 -0
  27. package/.claude/agents/agent-yevgeni-discussions.md +7 -1
  28. package/.claude/agents/agent-zara-zapier.md +159 -0
  29. package/.claude/commands/app-squad.md +135 -0
  30. package/.claude/commands/audit-squad.md +158 -0
  31. package/.claude/commands/autoplan.md +563 -0
  32. package/.claude/commands/cleanup-squad.md +98 -0
  33. package/.claude/commands/config-squad.md +106 -0
  34. package/.claude/commands/crud-squad.md +87 -0
  35. package/.claude/commands/data-squad.md +97 -0
  36. package/.claude/commands/debug-squad.md +303 -0
  37. package/.claude/commands/doc-squad.md +65 -0
  38. package/.claude/commands/handoff.md +137 -0
  39. package/.claude/commands/health.md +49 -0
  40. package/.claude/commands/help.md +2 -1
  41. package/.claude/commands/help:agents.md +96 -16
  42. package/.claude/commands/help:commands.md +55 -11
  43. package/.claude/commands/help:faq.md +16 -1
  44. package/.claude/commands/help:skills.md +93 -0
  45. package/.claude/commands/hotfix-squad.md +112 -0
  46. package/.claude/commands/integration-squad.md +82 -0
  47. package/.claude/commands/janitor-squad.md +167 -0
  48. package/.claude/commands/learn-auto.md +120 -0
  49. package/.claude/commands/learn.md +120 -0
  50. package/.claude/commands/mcp-list.md +27 -0
  51. package/.claude/commands/onboard-squad.md +140 -0
  52. package/.claude/commands/plan-workspace.md +732 -0
  53. package/.claude/commands/prd.md +131 -0
  54. package/.claude/commands/project-status.md +82 -0
  55. package/.claude/commands/publish.md +138 -0
  56. package/.claude/commands/recap.md +69 -0
  57. package/.claude/commands/restore.md +64 -0
  58. package/.claude/commands/review-squad.md +152 -0
  59. package/.claude/commands/save.md +24 -0
  60. package/.claude/commands/stats.md +19 -0
  61. package/.claude/commands/swarm.md +210 -0
  62. package/.claude/commands/tool-builder.md +3 -1
  63. package/.claude/commands/ws-pull.md +1 -1
  64. package/.claude/commands/yolo-off.md +17 -0
  65. package/.claude/commands/yolo.md +82 -0
  66. package/.claude/hooks/_shared-memory.cjs +305 -0
  67. package/.claude/hooks/_utils.cjs +134 -0
  68. package/.claude/hooks/agent-failure-detector.cjs +164 -79
  69. package/.claude/hooks/agent-usage-logger.cjs +204 -0
  70. package/.claude/hooks/app-edit-guard.cjs +20 -4
  71. package/.claude/hooks/auto-learn.cjs +316 -0
  72. package/.claude/hooks/bash-guard.cjs +282 -0
  73. package/.claude/hooks/builder-mode-manager.cjs +183 -54
  74. package/.claude/hooks/bulk-activity-guard.cjs +283 -0
  75. package/.claude/hooks/context-watchdog.cjs +292 -0
  76. package/.claude/hooks/delegation-reminder.cjs +478 -0
  77. package/.claude/hooks/design-system-lint.cjs +283 -0
  78. package/.claude/hooks/post-scaffold-hook.cjs +16 -3
  79. package/.claude/hooks/prompt-guard.cjs +366 -0
  80. package/.claude/hooks/publish-template-guard.cjs +16 -0
  81. package/.claude/hooks/session-start.cjs +35 -0
  82. package/.claude/hooks/shared-memory-writer.cjs +147 -0
  83. package/.claude/hooks/skill-injector.cjs +140 -0
  84. package/.claude/hooks/skill-usage-logger.cjs +258 -0
  85. package/.claude/hooks/src-edit-guard.cjs +16 -1
  86. package/.claude/hooks/sync-marketplace-agents.cjs +53 -8
  87. package/.claude/scripts/yolo-toggle.cjs +142 -0
  88. package/.claude/settings.json +141 -14
  89. package/.claude/skills/SDK-activity-patterns/SKILL.md +428 -0
  90. package/.claude/skills/SDK-document-templates/SKILL.md +1033 -0
  91. package/.claude/skills/SDK-function-fields/SKILL.md +542 -0
  92. package/.claude/skills/SDK-generate-skill/SKILL.md +92 -0
  93. package/.claude/skills/SDK-init-skill/SKILL.md +127 -0
  94. package/.claude/skills/SDK-insight-queries/SKILL.md +787 -0
  95. package/.claude/skills/SDK-ws-config-skill/SKILL.md +1139 -0
  96. package/.claude/skills/agent-structure/SKILL.md +98 -0
  97. package/.claude/skills/api-documentation-patterns/SKILL.md +474 -0
  98. package/.claude/skills/chrome-mcp-reference/SKILL.md +370 -0
  99. package/.claude/skills/delegation-routing/SKILL.md +202 -0
  100. package/.claude/skills/frontend-design/SKILL.md +254 -0
  101. package/.claude/skills/hailer-activity-mover/SKILL.md +213 -0
  102. package/.claude/skills/hailer-api-client/SKILL.md +518 -0
  103. package/.claude/skills/hailer-app-builder/SKILL.md +939 -11
  104. package/.claude/skills/hailer-apps-pictures/SKILL.md +269 -0
  105. package/.claude/skills/hailer-design-system/SKILL.md +235 -0
  106. package/.claude/skills/hailer-monolith-automations/SKILL.md +686 -0
  107. package/.claude/skills/hailer-permissions-system/SKILL.md +121 -0
  108. package/.claude/skills/hailer-project-protocol/SKILL.md +488 -0
  109. package/.claude/skills/hailer-rest-api/SKILL.md +61 -0
  110. package/.claude/skills/hailer-rest-api/hailer-activities.md +184 -0
  111. package/.claude/skills/hailer-rest-api/hailer-admin.md +473 -0
  112. package/.claude/skills/hailer-rest-api/hailer-calendar.md +256 -0
  113. package/.claude/skills/hailer-rest-api/hailer-feed.md +249 -0
  114. package/.claude/skills/hailer-rest-api/hailer-insights.md +195 -0
  115. package/.claude/skills/hailer-rest-api/hailer-messaging.md +276 -0
  116. package/.claude/skills/hailer-rest-api/hailer-workflows.md +283 -0
  117. package/.claude/skills/insight-join-patterns/SKILL.md +3 -0
  118. package/.claude/skills/integration-patterns/SKILL.md +421 -0
  119. package/.claude/skills/json-only-output/SKILL.md +52 -12
  120. package/.claude/skills/lsp-setup/SKILL.md +160 -0
  121. package/.claude/skills/mcp-direct-tools/SKILL.md +153 -0
  122. package/.claude/skills/optional-parameters/SKILL.md +32 -23
  123. package/.claude/skills/publish-hailer-app/SKILL.md +76 -12
  124. package/.claude/skills/testing-patterns/SKILL.md +630 -0
  125. package/.claude/skills/tool-builder/SKILL.md +250 -0
  126. package/.claude/skills/tool-parameter-usage/SKILL.md +59 -45
  127. package/.claude/skills/tool-response-verification/SKILL.md +82 -48
  128. package/.claude/skills/zapier-hailer-patterns/SKILL.md +581 -0
  129. package/.env.example +26 -7
  130. package/CLAUDE.md +290 -224
  131. package/dist/CLAUDE.md +370 -0
  132. package/dist/app.d.ts +1 -1
  133. package/dist/app.js +101 -101
  134. package/dist/bot/bot-config.d.ts +26 -0
  135. package/dist/bot/bot-config.js +135 -0
  136. package/dist/bot/bot-manager.d.ts +40 -0
  137. package/dist/bot/bot-manager.js +137 -0
  138. package/dist/bot/bot.d.ts +127 -0
  139. package/dist/bot/bot.js +1328 -0
  140. package/dist/bot/operation-logger.d.ts +28 -0
  141. package/dist/bot/operation-logger.js +132 -0
  142. package/dist/bot/services/conversation-manager.d.ts +60 -0
  143. package/dist/bot/services/conversation-manager.js +246 -0
  144. package/dist/bot/services/index.d.ts +9 -0
  145. package/dist/bot/services/index.js +18 -0
  146. package/dist/bot/services/message-classifier.d.ts +42 -0
  147. package/dist/bot/services/message-classifier.js +228 -0
  148. package/dist/bot/services/message-formatter.d.ts +88 -0
  149. package/dist/bot/services/message-formatter.js +411 -0
  150. package/dist/bot/services/session-logger.d.ts +162 -0
  151. package/dist/bot/services/session-logger.js +724 -0
  152. package/dist/bot/services/token-billing.d.ts +78 -0
  153. package/dist/bot/services/token-billing.js +233 -0
  154. package/dist/bot/services/types.d.ts +169 -0
  155. package/dist/bot/services/types.js +12 -0
  156. package/dist/bot/services/typing-indicator.d.ts +23 -0
  157. package/dist/bot/services/typing-indicator.js +60 -0
  158. package/dist/bot/services/workspace-schema-cache.d.ts +122 -0
  159. package/dist/bot/services/workspace-schema-cache.js +506 -0
  160. package/dist/bot/tool-executor.d.ts +28 -0
  161. package/dist/bot/tool-executor.js +48 -0
  162. package/dist/bot/workspace-overview.d.ts +12 -0
  163. package/dist/bot/workspace-overview.js +94 -0
  164. package/dist/cli.d.ts +1 -8
  165. package/dist/cli.js +1 -253
  166. package/dist/config.d.ts +96 -3
  167. package/dist/config.js +148 -37
  168. package/dist/core.d.ts +5 -0
  169. package/dist/core.js +61 -8
  170. package/dist/lib/discussion-lock.d.ts +42 -0
  171. package/dist/lib/discussion-lock.js +110 -0
  172. package/dist/lib/logger.d.ts +0 -1
  173. package/dist/lib/logger.js +39 -23
  174. package/dist/lib/request-logger.d.ts +77 -0
  175. package/dist/lib/request-logger.js +147 -0
  176. package/dist/mcp/UserContextCache.js +16 -13
  177. package/dist/mcp/hailer-clients.js +18 -17
  178. package/dist/mcp/signal-handler.js +43 -13
  179. package/dist/mcp/tool-registry.d.ts +4 -15
  180. package/dist/mcp/tool-registry.js +94 -32
  181. package/dist/mcp/tools/activity.js +28 -69
  182. package/dist/mcp/tools/app-core.js +9 -4
  183. package/dist/mcp/tools/app-marketplace.js +22 -12
  184. package/dist/mcp/tools/app-member.js +5 -2
  185. package/dist/mcp/tools/app-scaffold.js +32 -18
  186. package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
  187. package/dist/mcp/tools/bot-config/constants.js +94 -0
  188. package/dist/mcp/tools/bot-config/core.d.ts +253 -0
  189. package/dist/mcp/tools/bot-config/core.js +2456 -0
  190. package/dist/mcp/tools/bot-config/index.d.ts +10 -0
  191. package/dist/mcp/tools/bot-config/index.js +59 -0
  192. package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
  193. package/dist/mcp/tools/bot-config/tools.js +15 -0
  194. package/dist/mcp/tools/bot-config/types.d.ts +50 -0
  195. package/dist/mcp/tools/bot-config/types.js +6 -0
  196. package/dist/mcp/tools/discussion.js +107 -77
  197. package/dist/mcp/tools/document.d.ts +11 -0
  198. package/dist/mcp/tools/document.js +741 -0
  199. package/dist/mcp/tools/file.js +5 -2
  200. package/dist/mcp/tools/insight.js +36 -12
  201. package/dist/mcp/tools/investigate.d.ts +9 -0
  202. package/dist/mcp/tools/investigate.js +254 -0
  203. package/dist/mcp/tools/user.d.ts +2 -4
  204. package/dist/mcp/tools/user.js +9 -50
  205. package/dist/mcp/tools/workflow.d.ts +1 -0
  206. package/dist/mcp/tools/workflow.js +164 -52
  207. package/dist/mcp/utils/hailer-api-client.js +26 -17
  208. package/dist/mcp/webhook-handler.d.ts +64 -3
  209. package/dist/mcp/webhook-handler.js +227 -9
  210. package/dist/mcp-server.d.ts +4 -0
  211. package/dist/mcp-server.js +237 -25
  212. package/dist/plugins/bug-fixer/index.d.ts +2 -0
  213. package/dist/plugins/bug-fixer/index.js +18 -0
  214. package/dist/plugins/bug-fixer/tools.d.ts +45 -0
  215. package/dist/plugins/bug-fixer/tools.js +1096 -0
  216. package/package.json +10 -10
  217. package/scripts/test-hal-tools.ts +154 -0
  218. package/.claude/agents/agent-nora-name-functions.md +0 -123
  219. package/.claude/assistant-knowledge.md +0 -23
  220. package/.claude/commands/install-plugin.md +0 -261
  221. package/.claude/commands/list-plugins.md +0 -42
  222. package/.claude/commands/marketplace-setup.md +0 -33
  223. package/.claude/commands/publish-plugin.md +0 -55
  224. package/.claude/commands/uninstall-plugin.md +0 -87
  225. package/.claude/hooks/interactive-mode.cjs +0 -87
  226. package/.claude/hooks/mcp-server-guard.cjs +0 -108
  227. package/.claude/skills/marketplace-publishing.md +0 -155
  228. package/dist/bot/chat-bot.d.ts +0 -31
  229. package/dist/bot/chat-bot.js +0 -357
  230. package/dist/mcp/tools/metrics.d.ts +0 -13
  231. package/dist/mcp/tools/metrics.js +0 -546
  232. package/dist/stdio-server.d.ts +0 -14
  233. package/dist/stdio-server.js +0 -114
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  /**
3
- * Webhook Handler Utilities
3
+ * Webhook Handler for Bot Config Updates
4
4
  *
5
- * Provides webhook token generation and verification for secure endpoints.
5
+ * Receives activity updates from Hailer workflow webhooks and updates
6
+ * local .bot-config/{workspaceId}.json files.
6
7
  */
7
8
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
9
  if (k2 === undefined) k2 = k;
@@ -42,10 +43,23 @@ exports.generateWebhookSignature = generateWebhookSignature;
42
43
  exports.verifyWebhookSignature = verifyWebhookSignature;
43
44
  exports.getWebhookToken = getWebhookToken;
44
45
  exports.getWebhookPath = getWebhookPath;
46
+ exports.onBotUpdate = onBotUpdate;
47
+ exports.handleBotConfigWebhook = handleBotConfigWebhook;
48
+ exports.getWorkspaceConfig = getWorkspaceConfig;
49
+ exports.listWorkspaceConfigs = listWorkspaceConfigs;
45
50
  const fs = __importStar(require("fs"));
46
51
  const path = __importStar(require("path"));
47
52
  const crypto = __importStar(require("crypto"));
48
53
  const logger_1 = require("../lib/logger");
54
+ const config_1 = require("../config");
55
+ // Optional: bot-config only available in bot server mode, not MCP terminal
56
+ let invalidateConfigCache = null;
57
+ try {
58
+ invalidateConfigCache = require('../bot-config').invalidateConfigCache;
59
+ }
60
+ catch {
61
+ // Not available in MCP-only installs — safe to skip
62
+ }
49
63
  const logger = (0, logger_1.createLogger)({ component: 'webhook-handler' });
50
64
  const BOT_CONFIG_DIR = '.bot-config';
51
65
  const WEBHOOK_SECRET_FILE = 'webhook-secret.txt';
@@ -56,12 +70,15 @@ const WEBHOOK_SECRET_FILE = 'webhook-secret.txt';
56
70
  * Constant-time string comparison to prevent timing attacks
57
71
  */
58
72
  function timingSafeEqual(a, b) {
59
- if (a.length !== b.length) {
60
- // Still perform comparison to maintain constant time
61
- crypto.timingSafeEqual(Buffer.from(a), Buffer.from(a));
73
+ const bufA = Buffer.from(a);
74
+ const bufB = Buffer.from(b);
75
+ if (bufA.length !== bufB.length) {
76
+ // Compare against dummy buffer of same length to maintain constant time
77
+ const dummy = Buffer.alloc(bufA.length);
78
+ crypto.timingSafeEqual(bufA, dummy);
62
79
  return false;
63
80
  }
64
- return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
81
+ return crypto.timingSafeEqual(bufA, bufB);
65
82
  }
66
83
  /**
67
84
  * Generate HMAC-SHA256 signature for webhook payload
@@ -103,7 +120,7 @@ function getWebhookToken() {
103
120
  }
104
121
  // Production without WEBHOOK_TOKEN: webhooks disabled (optional feature)
105
122
  if (process.env.NODE_ENV === 'production') {
106
- logger.info('WEBHOOK_TOKEN not set - webhook endpoint disabled');
123
+ logger.debug('WEBHOOK_TOKEN not set - webhook endpoint disabled');
107
124
  return null;
108
125
  }
109
126
  const configDir = path.join(process.cwd(), BOT_CONFIG_DIR);
@@ -128,14 +145,215 @@ function getWebhookToken() {
128
145
  }
129
146
  fs.writeFileSync(secretPath, token, { mode: 0o600 });
130
147
  const maskedToken = `${token.slice(0, 4)}...${token.slice(-4)}`;
131
- logger.info('Generated dev webhook token', { maskedToken, path: secretPath });
148
+ logger.debug('Generated dev webhook token', { maskedToken, path: secretPath });
132
149
  return token;
133
150
  }
134
151
  /**
135
- * Get the full webhook path with token
152
+ * Get the full webhook path with token (no prefix for security through obscurity)
136
153
  */
137
154
  function getWebhookPath() {
138
155
  const token = getWebhookToken();
139
156
  return token ? `/${token}` : null;
140
157
  }
158
+ let botUpdateCallback = null;
159
+ function onBotUpdate(callback) {
160
+ botUpdateCallback = callback;
161
+ }
162
+ /**
163
+ * Get field value from webhook payload by key
164
+ */
165
+ function getFieldValue(fields, key) {
166
+ const field = fields.find((f) => f.key === key);
167
+ return field?.value ?? null;
168
+ }
169
+ /**
170
+ * Load existing workspace config or create empty one
171
+ */
172
+ function loadWorkspaceConfig(workspaceId) {
173
+ const configDir = path.join(process.cwd(), BOT_CONFIG_DIR);
174
+ const configPath = path.join(configDir, `${workspaceId}.json`);
175
+ if (fs.existsSync(configPath)) {
176
+ try {
177
+ return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
178
+ }
179
+ catch (error) {
180
+ logger.warn('Failed to load workspace config', { workspaceId, error: String(error) });
181
+ }
182
+ }
183
+ return {
184
+ workspaceId,
185
+ workspaceName: workspaceId,
186
+ specialists: [],
187
+ lastSynced: new Date().toISOString(),
188
+ };
189
+ }
190
+ /**
191
+ * Save workspace config to file
192
+ */
193
+ function saveWorkspaceConfig(config) {
194
+ const configDir = path.join(process.cwd(), BOT_CONFIG_DIR);
195
+ // Ensure directory exists
196
+ if (!fs.existsSync(configDir)) {
197
+ fs.mkdirSync(configDir, { recursive: true });
198
+ }
199
+ const configPath = path.join(configDir, `${config.workspaceId}.json`);
200
+ config.lastSynced = new Date().toISOString();
201
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
202
+ logger.info('Saved workspace config', {
203
+ workspaceId: config.workspaceId,
204
+ path: configPath,
205
+ });
206
+ }
207
+ /**
208
+ * Process webhook payload and update workspace config
209
+ */
210
+ function handleBotConfigWebhook(payload) {
211
+ const workspaceId = payload.cid;
212
+ logger.debug('Processing bot config webhook', {
213
+ activityId: payload._id,
214
+ activityName: payload.name,
215
+ workspaceId,
216
+ phase: payload.currentPhase,
217
+ });
218
+ // Extract fields
219
+ const email = getFieldValue(payload.fields, 'agentEmailInHailer');
220
+ const password = getFieldValue(payload.fields, 'password');
221
+ const botType = getFieldValue(payload.fields, 'botType');
222
+ const userId = getFieldValue(payload.fields, 'hailerProfile');
223
+ const schemaConfigStr = getFieldValue(payload.fields, 'schemaConfig');
224
+ // Validate required fields
225
+ if (!email || !password) {
226
+ logger.warn('Webhook missing credentials', {
227
+ activityId: payload._id,
228
+ hasEmail: !!email,
229
+ hasPassword: !!password,
230
+ });
231
+ return {
232
+ success: false,
233
+ action: 'skip',
234
+ workspaceId,
235
+ botType,
236
+ error: 'Missing email or password',
237
+ };
238
+ }
239
+ // Parse schema config to determine if deployed or retired
240
+ let deployedPhaseId = null;
241
+ let retiredPhaseId = null;
242
+ if (schemaConfigStr) {
243
+ try {
244
+ const schemaConfig = JSON.parse(schemaConfigStr);
245
+ deployedPhaseId = schemaConfig.deployedPhaseId;
246
+ retiredPhaseId = schemaConfig.retiredPhaseId;
247
+ }
248
+ catch (e) {
249
+ logger.warn('Failed to parse schemaConfig', { schemaConfigStr });
250
+ }
251
+ }
252
+ const isDeployed = deployedPhaseId ? payload.currentPhase === deployedPhaseId : true;
253
+ const isRetired = retiredPhaseId ? payload.currentPhase === retiredPhaseId : false;
254
+ const enabled = isDeployed && !isRetired;
255
+ // Load existing config
256
+ const config = loadWorkspaceConfig(workspaceId);
257
+ const botEntry = {
258
+ activityId: payload._id,
259
+ userId: userId || null,
260
+ email,
261
+ password,
262
+ botType: botType || 'unknown',
263
+ enabled,
264
+ displayName: payload.name, // Activity name from Agent Directory
265
+ };
266
+ let action;
267
+ // Handle orchestrator
268
+ if (botType === 'orchestrator') {
269
+ if (enabled) {
270
+ config.orchestrator = {
271
+ activityId: payload._id,
272
+ userId: userId || '',
273
+ email,
274
+ password,
275
+ displayName: payload.name,
276
+ };
277
+ action = 'update';
278
+ logger.info('Updated orchestrator', { workspaceId, email: (0, config_1.maskEmail)(email), displayName: payload.name });
279
+ }
280
+ else {
281
+ // Orchestrator disabled - remove it
282
+ if (config.orchestrator?.activityId === payload._id) {
283
+ delete config.orchestrator;
284
+ action = 'remove';
285
+ logger.info('Removed orchestrator', { workspaceId, email: (0, config_1.maskEmail)(email) });
286
+ }
287
+ else {
288
+ action = 'update';
289
+ }
290
+ }
291
+ }
292
+ else {
293
+ // Handle specialist
294
+ const existingIndex = config.specialists.findIndex((s) => s.activityId === payload._id);
295
+ if (existingIndex >= 0) {
296
+ // Update existing
297
+ config.specialists[existingIndex] = botEntry;
298
+ action = enabled ? 'update' : 'remove';
299
+ }
300
+ else if (enabled) {
301
+ // Add new
302
+ config.specialists.push(botEntry);
303
+ action = 'add';
304
+ }
305
+ else {
306
+ action = 'update';
307
+ }
308
+ logger.info('Updated specialist', {
309
+ workspaceId,
310
+ email,
311
+ botType,
312
+ enabled,
313
+ action,
314
+ });
315
+ }
316
+ // Save config
317
+ saveWorkspaceConfig(config);
318
+ // Invalidate config cache so loadBotConfigs() reads fresh data
319
+ if (invalidateConfigCache)
320
+ invalidateConfigCache();
321
+ // Trigger callback for hot reload
322
+ if (botUpdateCallback) {
323
+ botUpdateCallback(workspaceId, botEntry, action);
324
+ }
325
+ return {
326
+ success: true,
327
+ action,
328
+ workspaceId,
329
+ botType,
330
+ };
331
+ }
332
+ /**
333
+ * Get workspace config (for debugging/status)
334
+ */
335
+ function getWorkspaceConfig(workspaceId) {
336
+ const config = loadWorkspaceConfig(workspaceId);
337
+ return config.orchestrator || config.specialists.length > 0 ? config : null;
338
+ }
339
+ /**
340
+ * List all workspace configs
341
+ */
342
+ function listWorkspaceConfigs() {
343
+ const configDir = path.join(process.cwd(), BOT_CONFIG_DIR);
344
+ if (!fs.existsSync(configDir))
345
+ return [];
346
+ const files = fs.readdirSync(configDir).filter((f) => f.endsWith('.json'));
347
+ const configs = [];
348
+ for (const file of files) {
349
+ try {
350
+ const content = fs.readFileSync(path.join(configDir, file), 'utf-8');
351
+ configs.push(JSON.parse(content));
352
+ }
353
+ catch (error) {
354
+ logger.warn('Failed to load workspace config', { file, error: String(error) });
355
+ }
356
+ }
357
+ return configs;
358
+ }
141
359
  //# sourceMappingURL=webhook-handler.js.map
@@ -16,6 +16,10 @@ export interface MCPServerConfig {
16
16
  port: number;
17
17
  corsOrigins: string[];
18
18
  toolRegistry: ToolRegistry;
19
+ getDaemonStatus?: () => Record<string, Array<{
20
+ botId: string;
21
+ state: any;
22
+ }>>;
19
23
  }
20
24
  export declare class MCPServerService {
21
25
  private app;
@@ -5,6 +5,39 @@
5
5
  * Implements JSON-RPC 2.0 MCP protocol over HTTP with Server-Sent Events (SSE)
6
6
  * for LLM clients (Claude Desktop, etc.)
7
7
  */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
8
41
  var __importDefault = (this && this.__importDefault) || function (mod) {
9
42
  return (mod && mod.__esModule) ? mod : { "default": mod };
10
43
  };
@@ -16,12 +49,13 @@ const logger_1 = require("./lib/logger");
16
49
  const config_1 = require("./config");
17
50
  const UserContextCache_1 = require("./mcp/UserContextCache");
18
51
  const tool_registry_1 = require("./mcp/tool-registry");
52
+ const webhook_handler_1 = require("./mcp/webhook-handler");
19
53
  class MCPServerService {
20
54
  app;
21
55
  server;
22
56
  logger;
23
57
  config;
24
- toolRegistry;
58
+ toolRegistry; // ← Injected
25
59
  constructor(config) {
26
60
  this.config = config;
27
61
  this.toolRegistry = config.toolRegistry;
@@ -32,7 +66,7 @@ class MCPServerService {
32
66
  this.app = (0, express_1.default)();
33
67
  this.setupMiddleware();
34
68
  this.setupRoutes();
35
- this.logger.info('MCP Server initialized', {
69
+ this.logger.debug('MCP Server initialized', {
36
70
  port: config.port,
37
71
  corsOrigins: config.corsOrigins
38
72
  });
@@ -48,16 +82,17 @@ class MCPServerService {
48
82
  }));
49
83
  }
50
84
  this.app.use(express_1.default.json());
51
- // Request logging middleware (skip health endpoint to reduce noise)
85
+ // Request logging middleware (skip noise: health checks, OAuth discovery probes)
52
86
  this.app.use((req, res, next) => {
53
87
  const isHealthCheck = req.path === '/health';
88
+ const isOAuthProbe = req.path.startsWith('/.well-known/') || (req.method === 'GET' && req.path === '/api/mcp');
54
89
  const start = Date.now();
55
90
  const requestLogger = this.logger.child({
56
91
  requestId: `req-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
57
92
  method: req.method,
58
93
  path: req.path
59
94
  });
60
- if (!isHealthCheck) {
95
+ if (!isHealthCheck && !isOAuthProbe) {
61
96
  requestLogger.debug('Incoming request', {
62
97
  url: req.url,
63
98
  headers: req.headers['content-type'],
@@ -87,15 +122,31 @@ class MCPServerService {
87
122
  status: 'ok',
88
123
  timestamp: new Date().toISOString(),
89
124
  service: 'hailer-mcp-server',
90
- version: '0.1.0'
125
+ version: config_1.APP_VERSION
91
126
  };
92
127
  res.json(health);
93
128
  });
94
- // MCP Protocol endpoint - JSON-RPC 2.0 over SSE
95
- this.app.post('/api/mcp', async (req, res) => {
96
- req.logger.debug('MCP request received', { method: req.body?.method });
129
+ // Daemon status endpoint - monitor LLM context
130
+ this.app.get('/daemon/status', (_, res) => {
131
+ if (!this.config.getDaemonStatus) {
132
+ res.status(404).json({ error: 'Daemon mode not enabled' });
133
+ return;
134
+ }
135
+ const status = this.config.getDaemonStatus();
136
+ if (!status) {
137
+ res.status(503).json({ error: 'Daemon not running' });
138
+ return;
139
+ }
140
+ res.json({
141
+ timestamp: new Date().toISOString(),
142
+ daemons: status
143
+ });
144
+ });
145
+ // MCP Protocol handler - shared by both routes
146
+ const mcpHandler = async (req, res, apiKeyOverride) => {
147
+ const apiKey = apiKeyOverride || req.query.apiKey;
148
+ req.logger.debug('MCP request received', { method: req.body?.method, apiKey: apiKey?.slice(0, 8) + '...' });
97
149
  try {
98
- const apiKey = req.query.apiKey;
99
150
  const mcpRequest = req.body;
100
151
  let result;
101
152
  if (mcpRequest.method === 'tools/list') {
@@ -118,19 +169,23 @@ class MCPServerService {
118
169
  }
119
170
  // Apply default tool group filtering
120
171
  // - NUCLEAR: Only if ENABLE_NUCLEAR_TOOLS=true
172
+ // - BOT_INTERNAL: Only if explicitly requested via params.includeBotInternal (for daemons)
173
+ const includeBotInternal = mcpRequest.params?.includeBotInternal === true;
121
174
  if (!filterConfig) {
122
- // No filter yet - create default excluding NUCLEAR
175
+ // No filter yet - create default excluding NUCLEAR and BOT_INTERNAL
123
176
  filterConfig = {
124
177
  allowedGroups: [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE, tool_registry_1.ToolGroup.PLAYGROUND]
125
178
  };
126
- req.logger.debug('Using default tool filter (excludes NUCLEAR)');
179
+ req.logger.debug('Using default tool filter (excludes NUCLEAR and BOT_INTERNAL)');
127
180
  }
128
181
  else if (filterConfig.allowedGroups) {
129
- // Filter groups - remove NUCLEAR unless enabled
130
- filterConfig.allowedGroups = filterConfig.allowedGroups.filter(g => (config_1.environment.ENABLE_NUCLEAR_TOOLS || g !== tool_registry_1.ToolGroup.NUCLEAR));
182
+ // Filter groups - remove BOT_INTERNAL unless explicitly requested, remove NUCLEAR unless enabled
183
+ filterConfig.allowedGroups = filterConfig.allowedGroups.filter(g => (includeBotInternal || g !== tool_registry_1.ToolGroup.BOT_INTERNAL) &&
184
+ (config_1.environment.ENABLE_NUCLEAR_TOOLS || g !== tool_registry_1.ToolGroup.NUCLEAR));
131
185
  req.logger.debug('Filtered tool groups', {
132
186
  allowedGroups: filterConfig.allowedGroups,
133
- nuclearEnabled: config_1.environment.ENABLE_NUCLEAR_TOOLS
187
+ nuclearEnabled: config_1.environment.ENABLE_NUCLEAR_TOOLS,
188
+ includeBotInternal
134
189
  });
135
190
  }
136
191
  result = {
@@ -154,7 +209,7 @@ class MCPServerService {
154
209
  return this.sendMcpError(res, mcpRequest.id, -32602, 'API key required for tools/call', 400);
155
210
  }
156
211
  const { name, arguments: args = {} } = mcpRequest.params;
157
- req.logger.debug('Handling tools/call request', { toolName: name });
212
+ req.logger.info('Tool call', { tool: name });
158
213
  // Check access control
159
214
  if (!this.canAccessTool(name, apiKey, appConfig)) {
160
215
  return this.sendMcpError(res, mcpRequest.id, -32603, `Access denied to tool: ${name}`, 403);
@@ -163,20 +218,20 @@ class MCPServerService {
163
218
  result = await this.toolRegistry.executeTool(name, args, userContext);
164
219
  }
165
220
  else if (mcpRequest.method === 'initialize') {
166
- req.logger.info('MCP initialize request received');
167
221
  const sessionId = `session-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
168
222
  res.setHeader('Mcp-Session-Id', sessionId);
223
+ req.logger.info('Client connected', { sessionId, clientInfo: mcpRequest.params?.clientInfo });
169
224
  result = {
170
225
  protocolVersion: '2024-11-05',
171
226
  capabilities: { tools: {} },
172
227
  serverInfo: {
173
228
  name: 'hailer-mcp-server',
174
- version: '1.0.0'
229
+ version: config_1.APP_VERSION
175
230
  }
176
231
  };
177
232
  }
178
233
  else if (mcpRequest.method === 'notifications/initialized') {
179
- req.logger.info('MCP handshake completed - client initialized');
234
+ req.logger.debug('MCP handshake completed - client initialized');
180
235
  res.setHeader('Content-Type', 'text/event-stream');
181
236
  res.status(204).end();
182
237
  return;
@@ -210,13 +265,170 @@ class MCPServerService {
210
265
  this.sendMcpError(res, req.body?.id || null, -32000, `Server error: ${errorMessage}`, 500);
211
266
  }
212
267
  }
268
+ };
269
+ // MCP Protocol endpoint - JSON-RPC 2.0 over SSE
270
+ // Route 1: /api/mcp?apiKey=xxx (standard format)
271
+ this.app.post('/api/mcp', (req, res) => mcpHandler(req, res));
272
+ // Route 2: /:apiKey (simplified format - API key as path)
273
+ // Matches 16-64 char alphanumeric keys, but ONLY for MCP requests (has jsonrpc field)
274
+ // Non-MCP requests (webhooks) pass through to later routes
275
+ this.app.post('/:apiKey([a-zA-Z0-9_-]{16,64})', (req, res, next) => {
276
+ if (req.body?.jsonrpc) {
277
+ // MCP request - handle it
278
+ mcpHandler(req, res, req.params.apiKey);
279
+ }
280
+ else {
281
+ // Not MCP (likely webhook) - pass to next route
282
+ next();
283
+ }
213
284
  });
214
- this.logger.debug('Routes configured', {
215
- routes: [
216
- '/health',
217
- '/api/mcp'
218
- ]
219
- });
285
+ // ===== Bot Configuration API (only when MCP_CLIENT_ENABLED=true) =====
286
+ if (config_1.environment.MCP_CLIENT_ENABLED) {
287
+ // GET /api/bots - List all bots and their status
288
+ this.app.get('/api/bots', async (req, res) => {
289
+ req.logger.debug('List bots requested');
290
+ try {
291
+ const { AVAILABLE_BOTS, getBotState } = await Promise.resolve().then(() => __importStar(require('./bot-config')));
292
+ const state = getBotState();
293
+ const bots = AVAILABLE_BOTS.map(bot => ({
294
+ ...bot,
295
+ enabled: state[bot.id] || false
296
+ }));
297
+ res.json({ bots });
298
+ }
299
+ catch (error) {
300
+ req.logger.error('Failed to list bots', { error });
301
+ res.status(500).json({ error: 'Failed to list bots' });
302
+ }
303
+ });
304
+ // POST /api/bots/:id/enable - Enable a bot
305
+ this.app.post('/api/bots/:id/enable', async (req, res) => {
306
+ const { id } = req.params;
307
+ req.logger.debug('Enable bot requested', { botId: id });
308
+ try {
309
+ const { AVAILABLE_BOTS, setBotEnabled } = await Promise.resolve().then(() => __importStar(require('./bot-config')));
310
+ const bot = AVAILABLE_BOTS.find(b => b.id === id);
311
+ if (!bot) {
312
+ return res.status(404).json({ error: `Unknown bot: ${id}` });
313
+ }
314
+ setBotEnabled(id, true);
315
+ res.json({ success: true, botId: id, enabled: true });
316
+ }
317
+ catch (error) {
318
+ req.logger.error('Failed to enable bot', { botId: id, error });
319
+ res.status(500).json({ error: 'Failed to enable bot' });
320
+ }
321
+ });
322
+ // POST /api/bots/:id/disable - Disable a bot
323
+ this.app.post('/api/bots/:id/disable', async (req, res) => {
324
+ const { id } = req.params;
325
+ req.logger.debug('Disable bot requested', { botId: id });
326
+ try {
327
+ const { AVAILABLE_BOTS, setBotEnabled } = await Promise.resolve().then(() => __importStar(require('./bot-config')));
328
+ const bot = AVAILABLE_BOTS.find(b => b.id === id);
329
+ if (!bot) {
330
+ return res.status(404).json({ error: `Unknown bot: ${id}` });
331
+ }
332
+ setBotEnabled(id, false);
333
+ res.json({ success: true, botId: id, enabled: false });
334
+ }
335
+ catch (error) {
336
+ req.logger.error('Failed to disable bot', { botId: id, error });
337
+ res.status(500).json({ error: 'Failed to disable bot' });
338
+ }
339
+ });
340
+ // POST /api/bots/:id/toggle - Toggle a bot
341
+ this.app.post('/api/bots/:id/toggle', async (req, res) => {
342
+ const { id } = req.params;
343
+ req.logger.debug('Toggle bot requested', { botId: id });
344
+ try {
345
+ const { AVAILABLE_BOTS, getBotState, setBotEnabled } = await Promise.resolve().then(() => __importStar(require('./bot-config')));
346
+ const bot = AVAILABLE_BOTS.find(b => b.id === id);
347
+ if (!bot) {
348
+ return res.status(404).json({ error: `Unknown bot: ${id}` });
349
+ }
350
+ const currentState = getBotState();
351
+ const newState = !currentState[id];
352
+ setBotEnabled(id, newState);
353
+ res.json({ success: true, botId: id, enabled: newState });
354
+ }
355
+ catch (error) {
356
+ req.logger.error('Failed to toggle bot', { botId: id, error });
357
+ res.status(500).json({ error: 'Failed to toggle bot' });
358
+ }
359
+ });
360
+ // ===== Bot Config Webhook API =====
361
+ // Get secure webhook path (auto-generated token) - null if disabled
362
+ const webhookPath = (0, webhook_handler_1.getWebhookPath)();
363
+ if (webhookPath) {
364
+ // POST /webhook/{token} - Receives updates from Hailer workflow webhooks
365
+ this.app.post(webhookPath, (req, res) => {
366
+ req.logger.debug('Bot config webhook received', {
367
+ activityId: req.body?._id,
368
+ activityName: req.body?.name,
369
+ workspaceId: req.body?.cid,
370
+ });
371
+ try {
372
+ const result = (0, webhook_handler_1.handleBotConfigWebhook)(req.body);
373
+ if (result.success) {
374
+ req.logger.debug('Bot config updated via webhook', {
375
+ action: result.action,
376
+ workspaceId: result.workspaceId,
377
+ botType: result.botType,
378
+ });
379
+ res.status(200).json(result);
380
+ }
381
+ else {
382
+ req.logger.warn('Bot config webhook failed', { error: result.error });
383
+ res.status(400).json(result);
384
+ }
385
+ }
386
+ catch (error) {
387
+ req.logger.error('Bot config webhook error', { error });
388
+ res.status(500).json({
389
+ success: false,
390
+ error: error instanceof Error ? error.message : 'Internal error',
391
+ });
392
+ }
393
+ });
394
+ // GET /webhook/{token}/status - Status endpoint to see all workspace configs
395
+ this.app.get(`${webhookPath}/status`, (_req, res) => {
396
+ const configs = (0, webhook_handler_1.listWorkspaceConfigs)();
397
+ res.json({
398
+ timestamp: new Date().toISOString(),
399
+ workspaceCount: configs.length,
400
+ workspaces: configs.map((c) => ({
401
+ workspaceId: c.workspaceId,
402
+ workspaceName: c.workspaceName,
403
+ hasOrchestrator: !!c.orchestrator,
404
+ specialistCount: c.specialists.length,
405
+ enabledSpecialists: c.specialists.filter((s) => s.enabled).length,
406
+ lastSynced: c.lastSynced,
407
+ })),
408
+ });
409
+ });
410
+ }
411
+ else {
412
+ this.logger.debug('Webhook endpoint disabled (no WEBHOOK_TOKEN)');
413
+ }
414
+ this.logger.debug('Routes configured', {
415
+ routes: [
416
+ '/health',
417
+ '/daemon/status',
418
+ '/api/mcp',
419
+ '/api/bots'
420
+ ]
421
+ });
422
+ }
423
+ else {
424
+ this.logger.debug('Routes configured', {
425
+ routes: [
426
+ '/health',
427
+ '/daemon/status',
428
+ '/api/mcp'
429
+ ]
430
+ });
431
+ }
220
432
  }
221
433
  /**
222
434
  * Check if agent has access to a specific tool
@@ -275,7 +487,7 @@ class MCPServerService {
275
487
  const server = this.server;
276
488
  return new Promise((resolve) => {
277
489
  server.close(() => {
278
- this.logger.info('MCP Server stopped gracefully');
490
+ this.logger.debug('MCP Server stopped gracefully');
279
491
  resolve();
280
492
  });
281
493
  });
@@ -0,0 +1,2 @@
1
+ export * from "./tools";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./tools"), exports);
18
+ //# sourceMappingURL=index.js.map