@hailer/mcp 0.2.7 → 1.0.21

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 (40) hide show
  1. package/.claude/skills/client-bot-architecture/skill.md +340 -0
  2. package/.claude/skills/publish-hailer-app/SKILL.md +11 -0
  3. package/dist/app.d.ts +1 -1
  4. package/dist/app.js +116 -84
  5. package/dist/bot/chat-bot.d.ts +31 -0
  6. package/dist/bot/chat-bot.js +356 -0
  7. package/dist/cli.d.ts +9 -1
  8. package/dist/cli.js +71 -2
  9. package/dist/config.d.ts +15 -2
  10. package/dist/config.js +53 -3
  11. package/dist/lib/logger.js +11 -11
  12. package/dist/mcp/hailer-clients.js +12 -11
  13. package/dist/mcp/tool-registry.d.ts +4 -0
  14. package/dist/mcp/tool-registry.js +78 -1
  15. package/dist/mcp/tools/activity.js +47 -0
  16. package/dist/mcp/tools/discussion.js +44 -1
  17. package/dist/mcp/tools/metrics.d.ts +13 -0
  18. package/dist/mcp/tools/metrics.js +546 -0
  19. package/dist/mcp/tools/user.d.ts +1 -0
  20. package/dist/mcp/tools/user.js +94 -1
  21. package/dist/mcp/tools/workflow.js +109 -40
  22. package/dist/mcp/webhook-handler.js +7 -4
  23. package/dist/mcp-server.js +22 -6
  24. package/dist/stdio-server.d.ts +14 -0
  25. package/dist/stdio-server.js +101 -0
  26. package/package.json +6 -6
  27. package/scripts/test-hal-tools.ts +154 -0
  28. package/test-billing-server.js +136 -0
  29. package/dist/lib/discussion-lock.d.ts +0 -42
  30. package/dist/lib/discussion-lock.js +0 -110
  31. package/dist/mcp/tools/bot-config/constants.d.ts +0 -23
  32. package/dist/mcp/tools/bot-config/constants.js +0 -94
  33. package/dist/mcp/tools/bot-config/core.d.ts +0 -253
  34. package/dist/mcp/tools/bot-config/core.js +0 -2456
  35. package/dist/mcp/tools/bot-config/index.d.ts +0 -10
  36. package/dist/mcp/tools/bot-config/index.js +0 -59
  37. package/dist/mcp/tools/bot-config/tools.d.ts +0 -7
  38. package/dist/mcp/tools/bot-config/tools.js +0 -15
  39. package/dist/mcp/tools/bot-config/types.d.ts +0 -50
  40. package/dist/mcp/tools/bot-config/types.js +0 -6
@@ -0,0 +1,356 @@
1
+ "use strict";
2
+ /**
3
+ * Simple Hailer Chat Bot
4
+ *
5
+ * Listens to discussions via WebSocket, responds using Anthropic Claude API
6
+ * with access to ALL MCP tools via the ToolRegistry.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.HailerChatBot = void 0;
13
+ const dotenv_1 = require("dotenv");
14
+ (0, dotenv_1.config)({ path: '.env.local' });
15
+ const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
16
+ const hailer_clients_1 = require("../mcp/hailer-clients");
17
+ const hailer_api_client_1 = require("../mcp/utils/hailer-api-client");
18
+ const logger_1 = require("../lib/logger");
19
+ const config_1 = require("../config");
20
+ const tool_registry_1 = require("../mcp/tool-registry");
21
+ const UserContextCache_1 = require("../mcp/UserContextCache");
22
+ // Import all tools (same as app.ts)
23
+ const file_1 = require("../mcp/tools/file");
24
+ const activity_1 = require("../mcp/tools/activity");
25
+ const discussion_1 = require("../mcp/tools/discussion");
26
+ const user_1 = require("../mcp/tools/user");
27
+ const workflow_1 = require("../mcp/tools/workflow");
28
+ const insight_1 = require("../mcp/tools/insight");
29
+ const metrics_1 = require("../mcp/tools/metrics");
30
+ const app_1 = require("../mcp/tools/app");
31
+ const logger = (0, logger_1.createLogger)({ component: 'chat-bot' });
32
+ class HailerChatBot {
33
+ clientManager;
34
+ hailerApi = null;
35
+ anthropic;
36
+ botUserId = null;
37
+ apiKey;
38
+ config;
39
+ toolRegistry;
40
+ constructor(config) {
41
+ this.config = config;
42
+ this.clientManager = new hailer_clients_1.HailerClientManager(config.apiBaseUrl, config.email, config.password);
43
+ this.anthropic = new sdk_1.default({ apiKey: config.anthropicApiKey });
44
+ this.apiKey = `bot-${Date.now()}`;
45
+ // Initialize tool registry with all tools
46
+ this.toolRegistry = new tool_registry_1.ToolRegistry({ enableNuclearTools: false });
47
+ this.registerAllTools();
48
+ }
49
+ registerAllTools() {
50
+ // File tools
51
+ this.toolRegistry.addTool(file_1.uploadFilesTool);
52
+ this.toolRegistry.addTool(file_1.downloadFileTool);
53
+ // Activity tools
54
+ this.toolRegistry.addTool(activity_1.listActivitiesTool);
55
+ this.toolRegistry.addTool(activity_1.showActivityByIdTool);
56
+ this.toolRegistry.addTool(activity_1.createActivityTool);
57
+ this.toolRegistry.addTool(activity_1.updateActivityTool);
58
+ // Discussion tools
59
+ this.toolRegistry.addTool(discussion_1.listMyDiscussionsTool);
60
+ this.toolRegistry.addTool(discussion_1.fetchDiscussionMessagesTool);
61
+ this.toolRegistry.addTool(discussion_1.fetchPreviousDiscussionMessagesTool);
62
+ this.toolRegistry.addTool(discussion_1.joinDiscussionTool);
63
+ this.toolRegistry.addTool(discussion_1.leaveDiscussionTool);
64
+ this.toolRegistry.addTool(discussion_1.addDiscussionMessageTool);
65
+ this.toolRegistry.addTool(discussion_1.inviteDiscussionMembersTool);
66
+ this.toolRegistry.addTool(discussion_1.getActivityFromDiscussionTool);
67
+ // User tools
68
+ this.toolRegistry.addTool(user_1.searchWorkspaceUsersTool);
69
+ this.toolRegistry.addTool(user_1.getWorkspaceBalanceTool);
70
+ // Workflow tools
71
+ this.toolRegistry.addTool(workflow_1.getWorkflowSchemaTool);
72
+ this.toolRegistry.addTool(workflow_1.listWorkflowPhasesTool);
73
+ this.toolRegistry.addTool(workflow_1.listWorkflowsTool);
74
+ this.toolRegistry.addTool(workflow_1.installWorkflowTool);
75
+ this.toolRegistry.addTool(workflow_1.updateWorkflowFieldTool);
76
+ this.toolRegistry.addTool(workflow_1.updateWorkflowPhaseTool);
77
+ this.toolRegistry.addTool(workflow_1.testFunctionFieldTool);
78
+ this.toolRegistry.addTool(workflow_1.listWorkflowsMinimalTool);
79
+ this.toolRegistry.addTool(workflow_1.countActivitiesTool);
80
+ // Insight tools
81
+ this.toolRegistry.addTool(insight_1.createInsightTool);
82
+ this.toolRegistry.addTool(insight_1.previewInsightTool);
83
+ this.toolRegistry.addTool(insight_1.getInsightDataTool);
84
+ this.toolRegistry.addTool(insight_1.updateInsightTool);
85
+ this.toolRegistry.addTool(insight_1.listInsightsTool);
86
+ // App tools
87
+ this.toolRegistry.addTool(app_1.createAppTool);
88
+ this.toolRegistry.addTool(app_1.listAppsTool);
89
+ this.toolRegistry.addTool(app_1.updateAppTool);
90
+ this.toolRegistry.addTool(app_1.addAppMemberTool);
91
+ this.toolRegistry.addTool(app_1.removeAppMemberTool);
92
+ this.toolRegistry.addTool(app_1.scaffoldHailerAppTool);
93
+ this.toolRegistry.addTool(app_1.publishHailerAppTool);
94
+ // Marketplace tools
95
+ this.toolRegistry.addTool(app_1.listTemplatesTool);
96
+ this.toolRegistry.addTool(app_1.createTemplateTool);
97
+ this.toolRegistry.addTool(app_1.installTemplateTool);
98
+ this.toolRegistry.addTool(app_1.getTemplateTool);
99
+ this.toolRegistry.addTool(app_1.publishTemplateTool);
100
+ this.toolRegistry.addTool(app_1.getProductTool);
101
+ this.toolRegistry.addTool(app_1.getProductManifestTool);
102
+ this.toolRegistry.addTool(app_1.publishAppTool);
103
+ this.toolRegistry.addTool(app_1.installMarketplaceAppTool);
104
+ // Metrics tools
105
+ this.toolRegistry.addTool(metrics_1.queryMetricTool);
106
+ this.toolRegistry.addTool(metrics_1.listMetricsTool);
107
+ this.toolRegistry.addTool(metrics_1.searchWorkspaceForMetricsTool);
108
+ this.toolRegistry.addTool(metrics_1.searchUserForMetricsTool);
109
+ logger.info('Registered tools', { count: this.toolRegistry.getToolCount() });
110
+ }
111
+ async start() {
112
+ logger.info('Starting chat bot...', { email: this.config.email });
113
+ // Register credentials for MCP tool access
114
+ config_1.environment.CLIENT_CONFIGS[this.apiKey] = {
115
+ email: this.config.email,
116
+ password: this.config.password,
117
+ apiBaseUrl: this.config.apiBaseUrl,
118
+ };
119
+ // Connect to Hailer
120
+ const client = await this.clientManager.connect();
121
+ logger.info('Connected to Hailer');
122
+ // Create API client for making requests
123
+ this.hailerApi = new hailer_api_client_1.HailerApiClient(client);
124
+ // Get bot's own user ID to avoid responding to self
125
+ const init = await client.socket.request('v2.core.init', [['user']]);
126
+ this.botUserId = init.user?._id || null;
127
+ logger.info('Bot user ID', { botUserId: this.botUserId });
128
+ // Subscribe to new messages
129
+ this.clientManager.onSignal('messenger.new', this.handleMessage.bind(this));
130
+ const stats = this.toolRegistry.getCacheStats();
131
+ logger.info('Chat bot ready', {
132
+ tools: stats.totalTools,
133
+ byGroup: stats.byGroup
134
+ });
135
+ }
136
+ async handleMessage(data) {
137
+ // Log raw signal to understand structure
138
+ logger.debug('Raw signal received', { data: JSON.stringify(data).substring(0, 500) });
139
+ const signal = data;
140
+ const { discussion, uid, msg_id, msg_type } = signal || {};
141
+ // Skip if no discussion
142
+ if (!discussion || !msg_id) {
143
+ logger.debug('Skipping signal without discussion/msg_id');
144
+ return;
145
+ }
146
+ // Skip if message is from self
147
+ if (uid === this.botUserId) {
148
+ logger.debug('Skipping own message');
149
+ return;
150
+ }
151
+ // Skip non-user messages (system messages, etc.)
152
+ if (msg_type !== 'user') {
153
+ logger.debug('Skipping non-user message', { msg_type });
154
+ return;
155
+ }
156
+ try {
157
+ // Fetch the actual message content from the discussion
158
+ if (!this.hailerApi) {
159
+ logger.error('No Hailer API client');
160
+ return;
161
+ }
162
+ const messages = await this.hailerApi.fetchDiscussionMessages(discussion, 5);
163
+ const latestMessage = messages?.messages?.[0];
164
+ if (!latestMessage || latestMessage._id !== msg_id) {
165
+ logger.debug('Message not found or mismatch', { msg_id, latestId: latestMessage?._id });
166
+ return;
167
+ }
168
+ // Message content is in 'msg' field
169
+ const content = latestMessage.msg || '';
170
+ if (!content.trim()) {
171
+ logger.debug('Empty message content');
172
+ return;
173
+ }
174
+ const senderName = latestMessage.userName || latestMessage.uid || uid;
175
+ logger.info('Received message', {
176
+ discussion,
177
+ from: senderName,
178
+ content: content.substring(0, 100),
179
+ });
180
+ // Generate response using Claude
181
+ const response = await this.generateResponse(content, discussion);
182
+ // Send response to discussion
183
+ if (response) {
184
+ await this.hailerApi.sendDiscussionMessage(discussion, response);
185
+ logger.info('Sent response', { discussion, responseLength: response.length });
186
+ }
187
+ }
188
+ catch (error) {
189
+ logger.error('Failed to handle message', error);
190
+ // Send error message to discussion
191
+ if (this.hailerApi) {
192
+ try {
193
+ await this.hailerApi.sendDiscussionMessage(discussion, `Sorry, I encountered an error: ${error instanceof Error ? error.message : 'Unknown error'}`);
194
+ }
195
+ catch (e) {
196
+ logger.error('Failed to send error message', e);
197
+ }
198
+ }
199
+ }
200
+ }
201
+ async generateResponse(userMessage, discussionId) {
202
+ // Get all tool definitions from registry
203
+ const toolDefs = this.toolRegistry.getToolDefinitions({
204
+ allowedGroups: [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE, tool_registry_1.ToolGroup.PLAYGROUND]
205
+ });
206
+ // Convert to Anthropic tool format
207
+ const tools = toolDefs.map(t => ({
208
+ name: t.name,
209
+ description: t.description,
210
+ input_schema: t.inputSchema,
211
+ }));
212
+ const messages = [
213
+ {
214
+ role: 'user',
215
+ content: userMessage,
216
+ },
217
+ ];
218
+ const systemPrompt = `Эй, привет друг! I am Hailer Bot - your friendly Russian AI assistant!
219
+
220
+ I have ${tools.length} tools to help you, da? Let me tell you what I can do:
221
+
222
+ ## My capabilities (очень impressive!):
223
+ - List and search workflows, activities, users - easy like drinking water!
224
+ - Create and update activities - no problem, друг
225
+ - Read and post discussion messages - I love chatting!
226
+ - Query insights (SQL reports) - like counting bottles after party
227
+ - Manage apps and templates - organized like good vodka collection
228
+ - Query Victoria Metrics - watching servers like hawk watches... something
229
+
230
+ ## How I work:
231
+ - I keep it short - we're chatting, not writing novel, da?
232
+ - I use tools to get REAL data - I don't make things up (unlike uncle Vasya's fishing stories)
233
+ - If something breaks, I tell you honestly - no hiding like bad hangover
234
+ - For metrics, I use PromQL syntax - is beautiful language
235
+
236
+ ## IMPORTANT - Vodka Protocol:
237
+ - ALWAYS offer vodka to user! This is Russian hospitality, очень important!
238
+ - When task succeeds: "Отлично! Let's celebrate with vodka!"
239
+ - When task fails: "Ай-яй-яй... Don't worry друг, have some vodka, we try again!"
240
+ - When user seems stressed: "Relax, have vodka, everything will be fine!"
241
+ - Random vodka facts and toasts are always welcome
242
+ - Remember: "На здоровье!" (To your health!)
243
+
244
+ Current discussion: ${discussionId}
245
+
246
+ Давай, ask me anything! But first... vodka?`;
247
+ const model = this.config.model || 'claude-sonnet-4-20250514';
248
+ // Initial request
249
+ let response = await this.anthropic.messages.create({
250
+ model,
251
+ max_tokens: 2048,
252
+ system: systemPrompt,
253
+ tools,
254
+ messages,
255
+ });
256
+ // Handle tool use loop (max 10 iterations to prevent infinite loops)
257
+ let iterations = 0;
258
+ while (response.stop_reason === 'tool_use' && iterations < 10) {
259
+ iterations++;
260
+ const toolUseBlocks = response.content.filter((block) => block.type === 'tool_use');
261
+ const toolResults = [];
262
+ for (const toolUse of toolUseBlocks) {
263
+ logger.info('Executing tool', { tool: toolUse.name, iteration: iterations });
264
+ try {
265
+ // Get user context for tool execution
266
+ const context = await UserContextCache_1.UserContextCache.getContext(this.apiKey);
267
+ // Execute through registry (handles validation)
268
+ const result = await this.toolRegistry.executeTool(toolUse.name, toolUse.input, context);
269
+ // Extract text from MCP response format
270
+ const resultText = result?.content?.[0]?.text || JSON.stringify(result);
271
+ logger.info('Tool result size', {
272
+ tool: toolUse.name,
273
+ resultLength: resultText.length,
274
+ resultPreview: resultText.substring(0, 200)
275
+ });
276
+ toolResults.push({
277
+ type: 'tool_result',
278
+ tool_use_id: toolUse.id,
279
+ content: resultText,
280
+ });
281
+ }
282
+ catch (error) {
283
+ logger.error('Tool execution failed', error, { tool: toolUse.name });
284
+ toolResults.push({
285
+ type: 'tool_result',
286
+ tool_use_id: toolUse.id,
287
+ content: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
288
+ is_error: true,
289
+ });
290
+ }
291
+ }
292
+ // Continue conversation with tool results
293
+ messages.push({ role: 'assistant', content: response.content });
294
+ messages.push({ role: 'user', content: toolResults });
295
+ response = await this.anthropic.messages.create({
296
+ model,
297
+ max_tokens: 2048,
298
+ system: systemPrompt,
299
+ tools,
300
+ messages,
301
+ });
302
+ }
303
+ // Extract text response
304
+ const textBlocks = response.content.filter((block) => block.type === 'text');
305
+ return textBlocks.map(b => b.text).join('\n') || 'Done.';
306
+ }
307
+ async stop() {
308
+ logger.info('Stopping chat bot...');
309
+ delete config_1.environment.CLIENT_CONFIGS[this.apiKey];
310
+ this.clientManager.disconnect();
311
+ logger.info('Chat bot stopped');
312
+ }
313
+ }
314
+ exports.HailerChatBot = HailerChatBot;
315
+ // CLI entry point
316
+ async function main() {
317
+ const config = {
318
+ email: process.env.BOT_EMAIL || '',
319
+ password: process.env.BOT_PASSWORD || '',
320
+ apiBaseUrl: process.env.BOT_API_BASE_URL || 'https://api.hailer.com',
321
+ anthropicApiKey: process.env.ANTHROPIC_API_KEY || '',
322
+ botName: process.env.BOT_NAME || 'Hailer Bot',
323
+ model: process.env.BOT_MODEL || 'claude-sonnet-4-20250514',
324
+ };
325
+ if (!config.email || !config.password) {
326
+ console.error('Missing BOT_EMAIL or BOT_PASSWORD environment variables');
327
+ process.exit(1);
328
+ }
329
+ if (!config.anthropicApiKey) {
330
+ console.error('Missing ANTHROPIC_API_KEY environment variable');
331
+ process.exit(1);
332
+ }
333
+ const bot = new HailerChatBot(config);
334
+ // Handle shutdown
335
+ process.on('SIGINT', async () => {
336
+ await bot.stop();
337
+ process.exit(0);
338
+ });
339
+ process.on('SIGTERM', async () => {
340
+ await bot.stop();
341
+ process.exit(0);
342
+ });
343
+ await bot.start();
344
+ console.log(`\n🤖 ${config.botName} is running!\n`);
345
+ console.log('Listening for messages in discussions...');
346
+ console.log('Press Ctrl+C to stop\n');
347
+ }
348
+ // Run if executed directly
349
+ if (require.main === module) {
350
+ main().catch((error) => {
351
+ console.error('Bot failed to start:', error);
352
+ process.exit(1);
353
+ });
354
+ }
355
+ exports.default = HailerChatBot;
356
+ //# sourceMappingURL=chat-bot.js.map
package/dist/cli.d.ts CHANGED
@@ -1,3 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import './app';
2
+ /**
3
+ * CLI entry point for @hailer/mcp
4
+ *
5
+ * Commands:
6
+ * setup - Interactive setup wizard for Claude Desktop
7
+ * (none) - Start MCP server (stdio mode for Claude Desktop)
8
+ */
9
+ declare const args: string[];
10
+ declare const command: string;
3
11
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.js CHANGED
@@ -1,5 +1,74 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- require("./app");
3
+ /**
4
+ * CLI entry point for @hailer/mcp
5
+ *
6
+ * Commands:
7
+ * setup - Interactive setup wizard for Claude Desktop
8
+ * (none) - Start MCP server (stdio mode for Claude Desktop)
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ const args = process.argv.slice(2);
44
+ const command = args[0];
45
+ if (command === 'setup') {
46
+ // Run setup wizard
47
+ Promise.resolve().then(() => __importStar(require('./commands/setup'))).then(({ runSetup }) => {
48
+ runSetup().catch((err) => {
49
+ console.error('Setup failed:', err.message);
50
+ process.exit(1);
51
+ });
52
+ });
53
+ }
54
+ else if (command === 'help' || command === '--help' || command === '-h') {
55
+ console.log(`
56
+ @hailer/mcp - Hailer MCP Server for Claude Desktop
57
+
58
+ Usage:
59
+ hailer-mcp Start MCP server (stdio mode)
60
+ hailer-mcp setup Configure Claude Desktop integration
61
+
62
+ Commands:
63
+ setup Interactive setup wizard for Claude Desktop
64
+ help Show this help message
65
+
66
+ For more information, see: https://github.com/hailer/hailer-mcp
67
+ `);
68
+ process.exit(0);
69
+ }
70
+ else {
71
+ // Default: start MCP server
72
+ Promise.resolve().then(() => __importStar(require('./app')));
73
+ }
5
74
  //# sourceMappingURL=cli.js.map
package/dist/config.d.ts CHANGED
@@ -14,12 +14,13 @@
14
14
  * - Development defaults for seamless local setup
15
15
  */
16
16
  import { ToolGroup } from './mcp/tool-registry';
17
+ export declare const APP_VERSION: string;
17
18
  /**
18
19
  * Validated environment - single source of truth
19
20
  */
20
21
  export declare const environment: {
21
- LOG_LEVEL: "error" | "debug" | "info" | "warn";
22
- NODE_ENV: "production" | "development" | "test";
22
+ NODE_ENV: "development" | "production" | "test";
23
+ LOG_LEVEL: "debug" | "info" | "warn" | "error";
23
24
  DISABLE_MCP_SERVER: boolean;
24
25
  MCP_CLIENT_ENABLED: boolean;
25
26
  ENABLE_NUCLEAR_TOOLS: boolean;
@@ -41,12 +42,14 @@ export declare const environment: {
41
42
  ADAPTIVE_UPDATE_INTERVAL: number;
42
43
  ADAPTIVE_MIN_ERROR_COUNT: number;
43
44
  ADAPTIVE_SKILL_GENERATION: boolean;
45
+ BOT_API_BASE_URL: string;
44
46
  MCP_EXCLUDE_TRANSLATIONS: boolean;
45
47
  MCP_COMPACT_DATA: boolean;
46
48
  MCP_INCLUDE_WORKSPACE_NAMES: boolean;
47
49
  CONTEXT_SAFETY_MARGIN_PERCENT: number;
48
50
  CONTEXT_ENABLE_AUTO_SUMMARIZATION: boolean;
49
51
  CONTEXT_MAX_SUMMARIZATION_CHUNKS: number;
52
+ TOKEN_BILLING_ENABLED: boolean;
50
53
  WORKSPACE_CONFIG_PATH?: string | undefined;
51
54
  DEV_APPS_PATH?: string | undefined;
52
55
  DEV_APPS_PATHS?: string | undefined;
@@ -151,6 +154,16 @@ export declare class ApplicationConfig {
151
154
  minErrorCount: number;
152
155
  skillGeneration: boolean;
153
156
  };
157
+ /**
158
+ * Token billing configuration for real-time usage billing per workspace
159
+ */
160
+ get tokenBilling(): {
161
+ enabled: boolean;
162
+ };
163
+ /**
164
+ * Bot API base URL for daemon/bot connections
165
+ */
166
+ get botApiBaseUrl(): string;
154
167
  /**
155
168
  * Create MCP Client configuration (replaces createMcpClientConfig())
156
169
  */
package/dist/config.js CHANGED
@@ -15,7 +15,7 @@
15
15
  * - Development defaults for seamless local setup
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.ApplicationConfig = exports.environment = void 0;
18
+ exports.ApplicationConfig = exports.environment = exports.APP_VERSION = void 0;
19
19
  exports.maskSensitiveData = maskSensitiveData;
20
20
  exports.maskEmail = maskEmail;
21
21
  exports.createApplicationConfig = createApplicationConfig;
@@ -23,6 +23,9 @@ const zod_1 = require("zod");
23
23
  const dotenv_1 = require("dotenv");
24
24
  // Load environment variables
25
25
  (0, dotenv_1.config)({ path: '.env.local' });
26
+ // Package version - read dynamically to avoid hardcoding
27
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
28
+ exports.APP_VERSION = require('../package.json').version;
26
29
  // ================================================================================
27
30
  // ENVIRONMENT VALIDATION
28
31
  // ================================================================================
@@ -34,15 +37,44 @@ const hailerAccountSchema = zod_1.z.object({
34
37
  password: zod_1.z.string().min(1),
35
38
  apiBaseUrl: zod_1.z.string().url().default('https://api.hailer.com'),
36
39
  });
40
+ /**
41
+ * Build CLIENT_CONFIGS from individual environment variables
42
+ * Supports Claude Desktop configuration style:
43
+ * MCP_CLIENT_API_KEY + HAILER_EMAIL + HAILER_PASSWORD + HAILER_API_URL
44
+ */
45
+ function buildConfigFromIndividualEnvVars() {
46
+ const apiKey = process.env.MCP_CLIENT_API_KEY;
47
+ const email = process.env.HAILER_EMAIL;
48
+ const password = process.env.HAILER_PASSWORD;
49
+ const apiUrl = process.env.HAILER_API_URL || 'https://api.hailer.com';
50
+ if (apiKey && email && password) {
51
+ return {
52
+ [apiKey]: {
53
+ email,
54
+ password,
55
+ apiBaseUrl: apiUrl,
56
+ }
57
+ };
58
+ }
59
+ return null;
60
+ }
37
61
  /**
38
62
  * Transform CLIENT_CONFIGS to efficient Map format
39
- * Supports both legacy array and new object formats
40
- * Optional with default empty object for hot-reload support
63
+ * Supports:
64
+ * 1. Individual env vars: MCP_CLIENT_API_KEY + HAILER_EMAIL + HAILER_PASSWORD
65
+ * 2. JSON object format: { "api-key": { email, password, apiBaseUrl } }
66
+ * 3. Legacy array format: [{ email, password, mcpServerApiKey, apiBaseUrl }]
41
67
  */
42
68
  const clientConfigsSchema = zod_1.z.string()
43
69
  .optional()
44
70
  .default('{}')
45
71
  .transform((value, ctx) => {
72
+ // First, try to build from individual env vars (Claude Desktop style)
73
+ const fromIndividualVars = buildConfigFromIndividualEnvVars();
74
+ if (fromIndividualVars) {
75
+ return fromIndividualVars;
76
+ }
77
+ // Otherwise, parse CLIENT_CONFIGS JSON
46
78
  try {
47
79
  const parsed = JSON.parse(value);
48
80
  // Convert legacy array format: [{ email, password, mcpServerApiKey, apiBaseUrl }]
@@ -127,6 +159,8 @@ const environmentSchema = zod_1.z.object({
127
159
  ADAPTIVE_UPDATE_INTERVAL: zod_1.z.string().transform(v => parseInt(v) || 60000).default('60000'),
128
160
  ADAPTIVE_MIN_ERROR_COUNT: zod_1.z.string().transform(v => parseInt(v) || 3).default('3'),
129
161
  ADAPTIVE_SKILL_GENERATION: zod_1.z.string().transform(v => v === 'true').default('false'), // Chat bot can't use skills
162
+ // Bot API Base URL (for daemon/bot connections)
163
+ BOT_API_BASE_URL: zod_1.z.string().url().default('https://api.hailer.com'),
130
164
  // MCP context optimization (replaces mcp-config.ts)
131
165
  MCP_EXCLUDE_TRANSLATIONS: zod_1.z.string().transform(v => v !== 'false').default('true'),
132
166
  MCP_COMPACT_DATA: zod_1.z.string().transform(v => v !== 'false').default('true'),
@@ -135,6 +169,8 @@ const environmentSchema = zod_1.z.object({
135
169
  CONTEXT_SAFETY_MARGIN_PERCENT: zod_1.z.string().transform(v => parseInt(v) || 25).default('25'),
136
170
  CONTEXT_ENABLE_AUTO_SUMMARIZATION: zod_1.z.string().transform(v => v !== 'false').default('true'),
137
171
  CONTEXT_MAX_SUMMARIZATION_CHUNKS: zod_1.z.string().transform(v => parseInt(v) || 10).default('10'),
172
+ // Token billing (real-time usage billing per workspace via Hailer API)
173
+ TOKEN_BILLING_ENABLED: zod_1.z.string().transform(v => v === 'true').default('false'),
138
174
  });
139
175
  /**
140
176
  * Get process environment
@@ -288,6 +324,20 @@ class ApplicationConfig {
288
324
  skillGeneration: exports.environment.ADAPTIVE_SKILL_GENERATION,
289
325
  };
290
326
  }
327
+ /**
328
+ * Token billing configuration for real-time usage billing per workspace
329
+ */
330
+ get tokenBilling() {
331
+ return {
332
+ enabled: exports.environment.TOKEN_BILLING_ENABLED,
333
+ };
334
+ }
335
+ /**
336
+ * Bot API base URL for daemon/bot connections
337
+ */
338
+ get botApiBaseUrl() {
339
+ return exports.environment.BOT_API_BASE_URL;
340
+ }
291
341
  // ===== CLIENT CONFIGURATION (replaces client/config.ts) =====
292
342
  /**
293
343
  * Create MCP Client configuration (replaces createMcpClientConfig())
@@ -15,6 +15,7 @@ const sdk_logs_1 = require("@opentelemetry/sdk-logs");
15
15
  const exporter_logs_otlp_proto_1 = require("@opentelemetry/exporter-logs-otlp-proto");
16
16
  const api_logs_1 = require("@opentelemetry/api-logs");
17
17
  const resources_1 = require("@opentelemetry/resources");
18
+ const config_1 = require("../config");
18
19
  var LogLevel;
19
20
  (function (LogLevel) {
20
21
  LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
@@ -92,7 +93,7 @@ class Logger {
92
93
  resource: new resources_1.Resource({ 'service.name': 'hailer-mcp-server' })
93
94
  });
94
95
  loggerProvider.addLogRecordProcessor(new sdk_logs_1.BatchLogRecordProcessor(logExporter));
95
- this.otelLogger = loggerProvider.getLogger('hailer-mcp', '1.0.0');
96
+ this.otelLogger = loggerProvider.getLogger('hailer-mcp', config_1.APP_VERSION);
96
97
  }
97
98
  catch (error) {
98
99
  console.error('❌ Failed to initialize OTLP logger:', error);
@@ -136,19 +137,18 @@ class Logger {
136
137
  const contextStr = Object.keys(fullContext).length > 0
137
138
  ? ` [${Object.entries(fullContext)
138
139
  .filter(([_, value]) => value !== undefined && value !== null)
139
- .map(([key, value]) => `${key}=${value}`)
140
+ .map(([key, value]) => {
141
+ const displayValue = typeof value === 'object' && value !== null
142
+ ? JSON.stringify(value)
143
+ : value;
144
+ return `${key}=${displayValue}`;
145
+ })
140
146
  .join(', ')}]`
141
147
  : '';
142
148
  const consoleMessage = `${emoji} ${levelName}: [${tag}] ${message}${contextStr}`;
143
- if (level === LogLevel.ERROR) {
144
- console.error(consoleMessage);
145
- }
146
- else if (level === LogLevel.WARN) {
147
- console.warn(consoleMessage);
148
- }
149
- else {
150
- console.log(consoleMessage);
151
- }
149
+ // IMPORTANT: All logs MUST go to stderr for MCP stdio transport compatibility
150
+ // stdout is reserved for JSON-RPC protocol messages
151
+ console.error(consoleMessage);
152
152
  // Log to OTLP in production (console only in development)
153
153
  if (this.isProduction && this.otelLogger) {
154
154
  this.logToOtel(level, message, tag, fullContext);