@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.
- package/.claude/skills/client-bot-architecture/skill.md +340 -0
- package/.claude/skills/publish-hailer-app/SKILL.md +11 -0
- package/dist/app.d.ts +1 -1
- package/dist/app.js +116 -84
- package/dist/bot/chat-bot.d.ts +31 -0
- package/dist/bot/chat-bot.js +356 -0
- package/dist/cli.d.ts +9 -1
- package/dist/cli.js +71 -2
- package/dist/config.d.ts +15 -2
- package/dist/config.js +53 -3
- package/dist/lib/logger.js +11 -11
- package/dist/mcp/hailer-clients.js +12 -11
- package/dist/mcp/tool-registry.d.ts +4 -0
- package/dist/mcp/tool-registry.js +78 -1
- package/dist/mcp/tools/activity.js +47 -0
- package/dist/mcp/tools/discussion.js +44 -1
- package/dist/mcp/tools/metrics.d.ts +13 -0
- package/dist/mcp/tools/metrics.js +546 -0
- package/dist/mcp/tools/user.d.ts +1 -0
- package/dist/mcp/tools/user.js +94 -1
- package/dist/mcp/tools/workflow.js +109 -40
- package/dist/mcp/webhook-handler.js +7 -4
- package/dist/mcp-server.js +22 -6
- package/dist/stdio-server.d.ts +14 -0
- package/dist/stdio-server.js +101 -0
- package/package.json +6 -6
- package/scripts/test-hal-tools.ts +154 -0
- package/test-billing-server.js +136 -0
- package/dist/lib/discussion-lock.d.ts +0 -42
- package/dist/lib/discussion-lock.js +0 -110
- package/dist/mcp/tools/bot-config/constants.d.ts +0 -23
- package/dist/mcp/tools/bot-config/constants.js +0 -94
- package/dist/mcp/tools/bot-config/core.d.ts +0 -253
- package/dist/mcp/tools/bot-config/core.js +0 -2456
- package/dist/mcp/tools/bot-config/index.d.ts +0 -10
- package/dist/mcp/tools/bot-config/index.js +0 -59
- package/dist/mcp/tools/bot-config/tools.d.ts +0 -7
- package/dist/mcp/tools/bot-config/tools.js +0 -15
- package/dist/mcp/tools/bot-config/types.d.ts +0 -50
- 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
|
-
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
|
40
|
-
*
|
|
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())
|
package/dist/lib/logger.js
CHANGED
|
@@ -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',
|
|
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]) =>
|
|
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
|
-
|
|
144
|
-
|
|
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);
|