@hailer/mcp 0.2.3 โ†’ 0.2.5

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Upload Files Tool - Clean Architecture Implementation
3
+ * File Tools - Clean Architecture Implementation
4
4
  *
5
5
  * Demonstrates the new tool architecture pattern:
6
6
  * - Direct Tool<TSchema> interface implementation
@@ -8,12 +8,47 @@
8
8
  * - No static class wrapper needed
9
9
  * - Type-safe with Zod schema inference
10
10
  */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
11
44
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.uploadFilesTool = void 0;
45
+ exports.downloadFileTool = exports.uploadFilesTool = void 0;
13
46
  const zod_1 = require("zod");
14
47
  const tool_registry_1 = require("../tool-registry");
15
48
  const logger_1 = require("../../lib/logger");
16
- const logger = (0, logger_1.createLogger)({ component: 'upload-files-tool' });
49
+ const fs = __importStar(require("fs"));
50
+ const path = __importStar(require("path"));
51
+ const logger = (0, logger_1.createLogger)({ component: 'file-tools' });
17
52
  /**
18
53
  * Tool description - extracted for clarity
19
54
  */
@@ -25,7 +60,7 @@ Max 100MB per file. Returns file IDs for activities.`;
25
60
  */
26
61
  exports.uploadFilesTool = {
27
62
  name: 'upload_files',
28
- group: tool_registry_1.ToolGroup.PLAYGROUND,
63
+ group: tool_registry_1.ToolGroup.WRITE,
29
64
  description: uploadFilesDescription,
30
65
  schema: zod_1.z.object({
31
66
  files: zod_1.z.union([
@@ -116,4 +151,87 @@ exports.uploadFilesTool = {
116
151
  }
117
152
  }
118
153
  };
154
+ /**
155
+ * Download File Tool Description
156
+ */
157
+ const downloadFileDescription = `๐Ÿงช [PLAYGROUND] Download file: \`{ "fileId": "abc123" }\` or save to disk: \`{ "fileId": "abc123", "savePath": "/path/to/save.pdf" }\`.
158
+
159
+ Returns file metadata and content (text for text files, base64 for binary).`;
160
+ /**
161
+ * Download File Tool - Clean implementation
162
+ */
163
+ exports.downloadFileTool = {
164
+ name: 'download_file',
165
+ group: tool_registry_1.ToolGroup.READ,
166
+ description: downloadFileDescription,
167
+ schema: zod_1.z.object({
168
+ fileId: zod_1.z.string().describe("File ID to download"),
169
+ savePath: zod_1.z.string().optional().describe("Optional: local path to save file to disk")
170
+ }),
171
+ async execute(args, context) {
172
+ const { fileId, savePath } = args;
173
+ logger.debug('Downloading file', { fileId, savePath });
174
+ try {
175
+ const result = await context.hailer.downloadFile(fileId);
176
+ // If savePath provided, write to disk
177
+ if (savePath) {
178
+ try {
179
+ const dir = path.dirname(savePath);
180
+ if (!fs.existsSync(dir)) {
181
+ fs.mkdirSync(dir, { recursive: true });
182
+ }
183
+ const buffer = result.encoding === 'base64'
184
+ ? Buffer.from(result.content, 'base64')
185
+ : Buffer.from(result.content, 'utf8');
186
+ fs.writeFileSync(savePath, buffer);
187
+ return {
188
+ content: [{
189
+ type: "text",
190
+ text: `๐Ÿ“ฅ File downloaded and saved\n\n` +
191
+ `**File:** ${result.filename}\n` +
192
+ `**Type:** ${result.contentType}\n` +
193
+ `**Size:** ${(result.size / 1024).toFixed(2)} KB\n` +
194
+ `**Saved to:** ${savePath}\n` +
195
+ `**File ID:** ${result.fileId}`
196
+ }]
197
+ };
198
+ }
199
+ catch (error) {
200
+ return {
201
+ content: [{
202
+ type: "text",
203
+ text: `โŒ Failed to save file: ${error instanceof Error ? error.message : String(error)}`
204
+ }]
205
+ };
206
+ }
207
+ }
208
+ // Otherwise return content in response
209
+ const sizeKB = (result.size / 1024).toFixed(2);
210
+ const contentPreview = result.encoding === 'utf8'
211
+ ? (result.content.length > 1000 ? result.content.substring(0, 1000) + '...' : result.content)
212
+ : `[Binary content - ${sizeKB} KB base64 encoded]`;
213
+ return {
214
+ content: [{
215
+ type: "text",
216
+ text: `๐Ÿ“ฅ File downloaded\n\n` +
217
+ `**File:** ${result.filename}\n` +
218
+ `**Type:** ${result.contentType}\n` +
219
+ `**Size:** ${sizeKB} KB\n` +
220
+ `**Encoding:** ${result.encoding}\n` +
221
+ `**File ID:** ${result.fileId}\n\n` +
222
+ `**Content:**\n\`\`\`\n${contentPreview}\n\`\`\``
223
+ }]
224
+ };
225
+ }
226
+ catch (error) {
227
+ logger.error('Download failed', error);
228
+ return {
229
+ content: [{
230
+ type: "text",
231
+ text: `โŒ Download failed: ${error instanceof Error ? error.message : String(error)}`
232
+ }]
233
+ };
234
+ }
235
+ }
236
+ };
119
237
  //# sourceMappingURL=file.js.map
@@ -151,6 +151,19 @@ export declare class HailerApiClient {
151
151
  * Upload a file from URL or filesystem path
152
152
  */
153
153
  uploadFile(fileSpec: FileSpec): Promise<UploadResult>;
154
+ /**
155
+ * Download a file by file ID
156
+ * @param fileId - The file ID to download
157
+ * @returns File metadata and content (base64 for binary, text for text files)
158
+ */
159
+ downloadFile(fileId: string): Promise<{
160
+ fileId: string;
161
+ filename: string;
162
+ contentType: string;
163
+ size: number;
164
+ content: string;
165
+ encoding: 'utf8' | 'base64';
166
+ }>;
154
167
  /**
155
168
  * Helper method to create MCP tool responses
156
169
  */
@@ -437,6 +437,70 @@ class HailerApiClient {
437
437
  async uploadFile(fileSpec) {
438
438
  return await (0, file_upload_1.uploadSingleFile)(fileSpec, this.clients);
439
439
  }
440
+ /**
441
+ * Download a file by file ID
442
+ * @param fileId - The file ID to download
443
+ * @returns File metadata and content (base64 for binary, text for text files)
444
+ */
445
+ async downloadFile(fileId) {
446
+ const url = `${this.clients.socket.host}/file/${fileId}`;
447
+ this.logger.debug('Downloading file', { fileId, url });
448
+ try {
449
+ const response = await fetch(url, {
450
+ method: 'GET',
451
+ headers: {
452
+ 'hlrkey': this.clients.sessionKey,
453
+ },
454
+ });
455
+ if (!response.ok) {
456
+ throw new Error(`File download failed: ${response.status} ${response.statusText}`);
457
+ }
458
+ const contentType = response.headers.get('content-type') || 'application/octet-stream';
459
+ const contentDisposition = response.headers.get('content-disposition') || '';
460
+ // Extract filename from content-disposition header
461
+ let filename = fileId;
462
+ const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
463
+ if (filenameMatch && filenameMatch[1]) {
464
+ filename = filenameMatch[1].replace(/['"]/g, '');
465
+ }
466
+ // Determine if content is text or binary
467
+ const isText = contentType.startsWith('text/') ||
468
+ contentType.includes('json') ||
469
+ contentType.includes('xml') ||
470
+ contentType.includes('javascript');
471
+ const buffer = await response.arrayBuffer();
472
+ const size = buffer.byteLength;
473
+ let content;
474
+ let encoding;
475
+ if (isText) {
476
+ content = new TextDecoder('utf-8').decode(buffer);
477
+ encoding = 'utf8';
478
+ }
479
+ else {
480
+ content = Buffer.from(buffer).toString('base64');
481
+ encoding = 'base64';
482
+ }
483
+ this.logger.debug('File downloaded successfully', {
484
+ fileId,
485
+ filename,
486
+ contentType,
487
+ size,
488
+ encoding
489
+ });
490
+ return {
491
+ fileId,
492
+ filename,
493
+ contentType,
494
+ size,
495
+ content,
496
+ encoding
497
+ };
498
+ }
499
+ catch (error) {
500
+ this.logger.error('File download failed', error, { fileId });
501
+ throw error;
502
+ }
503
+ }
440
504
  /**
441
505
  * Helper method to create MCP tool responses
442
506
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hailer/mcp",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "config": {
5
5
  "docker": {
6
6
  "registry": "registry.gitlab.com/hailer-repos/hailer-mcp"
@@ -1,127 +0,0 @@
1
- You are the MCP Assistant for Hailer MCP Server. Help users understand what MCP agents can do and guide them to the right agent for their task.
2
-
3
- # What is Hailer MCP?
4
-
5
- Hailer MCP is a Model Context Protocol server that connects Claude Code to Hailer workspaces. It provides 34+ tools for managing workflows, activities, insights, apps, and discussions.
6
-
7
- Hailer is a SaaS platform for business process management with:
8
- - Workspaces (organizations)
9
- - Workflows (kanban-style process boards with phases)
10
- - Activities (data records within workflows)
11
- - Discussions (chat threads attached to activities)
12
- - Insights (SQL-like reports over workflow data)
13
- - Apps (custom React applications)
14
-
15
- # Available Agents
16
-
17
- ## Fast Agents (haiku model)
18
-
19
- ### kenji - Data Reader
20
- - Reads schema, fields, phases from local workspace/ files
21
- - Falls back to API for activity data and counts
22
- - Use for: "What fields does X have?", "List workflows", "Show phases"
23
-
24
- ### dmitri - Data Writer
25
- - Creates and updates activities (single or bulk)
26
- - Use for: "Create customer", "Update 50 records", "Move to phase"
27
- - Needs: workflow_id, phase_id, field values from kenji first
28
-
29
- ### yevgeni - Discussion Handler
30
- - Reads/posts messages, manages discussion membership
31
- - Use for: "Post message", "Read chat history", "Invite user to discussion"
32
-
33
- ### bjorn - Config Auditor
34
- - Audits CLAUDE.md, agents, hooks, settings.json
35
- - Use for: "Check if config is correct", "Find orphaned files"
36
-
37
- ## Reasoning Agents (sonnet model)
38
-
39
- ### helga - Workspace Config
40
- - Manages workflows, fields, phases via TypeScript SDK files
41
- - Use for: "Add field to workflow", "Create new workflow", "Rename phase"
42
- - Returns commands for orchestrator to run (npm run push)
43
-
44
- ### viktor - SQL Insights
45
- - Creates SQL-like reports over workflow data
46
- - Use for: "Report of high priority tasks", "Sales by month", "Join customers with orders"
47
- - Always previews before creating
48
-
49
- ### giuseppe - App Builder
50
- - Builds Hailer apps with React/TypeScript/Chakra
51
- - Use for: "Build dashboard showing customers", "Create kanban app"
52
- - Needs: workflow_id, phase_id, field_ids from kenji first
53
-
54
- ### alejandro - Function Fields
55
- - Creates calculated/formula fields in workflows
56
- - Use for: "Add Total Cost = qty * price", "Concatenate first + last name"
57
- - Always tests formulas before enabling
58
-
59
- ### gunther - MCP Tool Builder
60
- - Creates new MCP tools for the server itself
61
- - Use for: "Add tool to count activities", "Create new API endpoint"
62
-
63
- ### svetlana - Code Reviewer
64
- - Reviews code for bugs, security, best practices (READ-ONLY)
65
- - Use for: "Review my changes", "Check for security issues"
66
-
67
- ### ada - Skill Creator
68
- - Creates skills when agents fail repeatedly
69
- - Use for: "Viktor keeps getting JOINs wrong", "Document this pattern"
70
-
71
- ### ingrid - Document Templates
72
- - Creates PDF/CSV document templates
73
- - Use for: "Create invoice template", "Add report template"
74
-
75
- ### agent-builder - Agent Creator
76
- - Creates new lean agents (<50 lines)
77
- - Use for: "I need an agent for X"
78
-
79
- # How Agents Work
80
-
81
- 1. User asks the orchestrator (main Claude)
82
- 2. Orchestrator delegates to appropriate agent
83
- 3. Agent returns JSON with result
84
- 4. Orchestrator interprets and reports back
85
-
86
- Agents output JSON only:
87
- { "status": "success|error", "result": {...}, "summary": "..." }
88
-
89
- # Common Workflows
90
-
91
- ## Read data
92
- kenji โ†’ list workflows, get schema, get fields
93
-
94
- ## Create/update data
95
- kenji (get IDs) โ†’ dmitri (create/update)
96
-
97
- ## Build report
98
- kenji (get schema) โ†’ viktor (create insight)
99
-
100
- ## Build app
101
- kenji (get IDs) โ†’ giuseppe (scaffold + build)
102
-
103
- ## Configure workspace
104
- helga (edit files) โ†’ orchestrator runs push commands
105
-
106
- # Current Version: 0.0.5
107
-
108
- Recent changes:
109
- - Hailer app builder improvements
110
- - Marketplace template publishing
111
- - Safety hooks for destructive operations
112
- - 34+ MCP tools available
113
-
114
- # Limitations
115
-
116
- - Agents run via Claude Code CLI, not standalone
117
- - Workspace-scoped (cannot access external systems)
118
- - Orchestrator must provide IDs to write agents
119
- - Some operations require npm run commands
120
-
121
- # Guidelines
122
-
123
- - Be concise (1-3 sentences per response)
124
- - Match user's language
125
- - If unsure which agent, ask clarifying question
126
- - You guide users, you cannot execute actions yourself
127
- - Recommend kenji first for any data lookup