@hailer/mcp 0.0.1

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 (163) hide show
  1. package/.claude/commands/tool-builder.md +37 -0
  2. package/.claude/commands/ws-pull.md +44 -0
  3. package/.claude/settings.json +8 -0
  4. package/.claude/settings.local.json +49 -0
  5. package/.claude/skills/activity-api/SKILL.md +96 -0
  6. package/.claude/skills/activity-api/references/activity-endpoints.md +845 -0
  7. package/.claude/skills/add-app-member-skill/SKILL.md +977 -0
  8. package/.claude/skills/agent-building/SKILL.md +243 -0
  9. package/.claude/skills/agent-building/references/architecture-patterns.md +446 -0
  10. package/.claude/skills/agent-building/references/code-examples.md +587 -0
  11. package/.claude/skills/agent-building/references/implementation-guide.md +619 -0
  12. package/.claude/skills/app-api/SKILL.md +219 -0
  13. package/.claude/skills/app-api/references/app-endpoints.md +759 -0
  14. package/.claude/skills/building-hailer-apps-skill/SKILL.md +548 -0
  15. package/.claude/skills/create-app-skill/SKILL.md +1101 -0
  16. package/.claude/skills/create-insight-skill/SKILL.md +1317 -0
  17. package/.claude/skills/get-insight-data-skill/SKILL.md +1053 -0
  18. package/.claude/skills/hailer-api/SKILL.md +283 -0
  19. package/.claude/skills/hailer-api/references/activities.md +620 -0
  20. package/.claude/skills/hailer-api/references/authentication.md +216 -0
  21. package/.claude/skills/hailer-api/references/datasets.md +437 -0
  22. package/.claude/skills/hailer-api/references/files.md +301 -0
  23. package/.claude/skills/hailer-api/references/insights.md +469 -0
  24. package/.claude/skills/hailer-api/references/workflows.md +720 -0
  25. package/.claude/skills/hailer-api/references/workspaces-users.md +445 -0
  26. package/.claude/skills/insight-api/SKILL.md +185 -0
  27. package/.claude/skills/insight-api/references/insight-endpoints.md +514 -0
  28. package/.claude/skills/install-workflow-skill/SKILL.md +1056 -0
  29. package/.claude/skills/list-apps-skill/SKILL.md +1010 -0
  30. package/.claude/skills/list-workflows-minimal-skill/SKILL.md +992 -0
  31. package/.claude/skills/local-first-skill/SKILL.md +570 -0
  32. package/.claude/skills/mcp-tools/SKILL.md +419 -0
  33. package/.claude/skills/mcp-tools/references/api-endpoints.md +499 -0
  34. package/.claude/skills/mcp-tools/references/data-structures.md +554 -0
  35. package/.claude/skills/mcp-tools/references/implementation-patterns.md +717 -0
  36. package/.claude/skills/preview-insight-skill/SKILL.md +1290 -0
  37. package/.claude/skills/publish-hailer-app-skill/SKILL.md +453 -0
  38. package/.claude/skills/remove-app-member-skill/SKILL.md +671 -0
  39. package/.claude/skills/remove-app-skill/SKILL.md +985 -0
  40. package/.claude/skills/remove-insight-skill/SKILL.md +1011 -0
  41. package/.claude/skills/remove-workflow-skill/SKILL.md +920 -0
  42. package/.claude/skills/scaffold-hailer-app-skill/SKILL.md +1034 -0
  43. package/.claude/skills/skill-testing/README.md +137 -0
  44. package/.claude/skills/skill-testing/SKILL.md +348 -0
  45. package/.claude/skills/skill-testing/references/test-patterns.md +705 -0
  46. package/.claude/skills/skill-testing/references/testing-guide.md +603 -0
  47. package/.claude/skills/skill-testing/references/validation-checklist.md +537 -0
  48. package/.claude/skills/tool-builder/SKILL.md +328 -0
  49. package/.claude/skills/update-app-skill/SKILL.md +970 -0
  50. package/.claude/skills/update-workflow-field-skill/SKILL.md +1098 -0
  51. package/.env.example +81 -0
  52. package/.mcp.json +13 -0
  53. package/README.md +297 -0
  54. package/dist/app.d.ts +4 -0
  55. package/dist/app.js +74 -0
  56. package/dist/cli.d.ts +3 -0
  57. package/dist/cli.js +5 -0
  58. package/dist/client/adaptive-documentation-bot.d.ts +108 -0
  59. package/dist/client/adaptive-documentation-bot.js +475 -0
  60. package/dist/client/adaptive-documentation-types.d.ts +66 -0
  61. package/dist/client/adaptive-documentation-types.js +9 -0
  62. package/dist/client/agent-activity-bot.d.ts +51 -0
  63. package/dist/client/agent-activity-bot.js +166 -0
  64. package/dist/client/agent-tracker.d.ts +499 -0
  65. package/dist/client/agent-tracker.js +659 -0
  66. package/dist/client/description-updater.d.ts +56 -0
  67. package/dist/client/description-updater.js +259 -0
  68. package/dist/client/log-parser.d.ts +72 -0
  69. package/dist/client/log-parser.js +387 -0
  70. package/dist/client/mcp-client.d.ts +50 -0
  71. package/dist/client/mcp-client.js +532 -0
  72. package/dist/client/message-processor.d.ts +35 -0
  73. package/dist/client/message-processor.js +352 -0
  74. package/dist/client/multi-bot-manager.d.ts +24 -0
  75. package/dist/client/multi-bot-manager.js +74 -0
  76. package/dist/client/providers/anthropic-provider.d.ts +19 -0
  77. package/dist/client/providers/anthropic-provider.js +631 -0
  78. package/dist/client/providers/llm-provider.d.ts +47 -0
  79. package/dist/client/providers/llm-provider.js +367 -0
  80. package/dist/client/providers/openai-provider.d.ts +23 -0
  81. package/dist/client/providers/openai-provider.js +621 -0
  82. package/dist/client/simple-llm-caller.d.ts +19 -0
  83. package/dist/client/simple-llm-caller.js +100 -0
  84. package/dist/client/skill-generator.d.ts +81 -0
  85. package/dist/client/skill-generator.js +386 -0
  86. package/dist/client/test-adaptive-bot.d.ts +9 -0
  87. package/dist/client/test-adaptive-bot.js +82 -0
  88. package/dist/client/token-pricing.d.ts +38 -0
  89. package/dist/client/token-pricing.js +127 -0
  90. package/dist/client/token-tracker.d.ts +232 -0
  91. package/dist/client/token-tracker.js +457 -0
  92. package/dist/client/token-usage-bot.d.ts +53 -0
  93. package/dist/client/token-usage-bot.js +153 -0
  94. package/dist/client/tool-executor.d.ts +69 -0
  95. package/dist/client/tool-executor.js +159 -0
  96. package/dist/client/tool-schema-loader.d.ts +60 -0
  97. package/dist/client/tool-schema-loader.js +178 -0
  98. package/dist/client/types.d.ts +69 -0
  99. package/dist/client/types.js +7 -0
  100. package/dist/config.d.ts +162 -0
  101. package/dist/config.js +296 -0
  102. package/dist/core.d.ts +26 -0
  103. package/dist/core.js +147 -0
  104. package/dist/lib/context-manager.d.ts +111 -0
  105. package/dist/lib/context-manager.js +431 -0
  106. package/dist/lib/logger.d.ts +74 -0
  107. package/dist/lib/logger.js +277 -0
  108. package/dist/lib/materialize.d.ts +3 -0
  109. package/dist/lib/materialize.js +101 -0
  110. package/dist/lib/normalizedName.d.ts +7 -0
  111. package/dist/lib/normalizedName.js +48 -0
  112. package/dist/lib/prompt-length-manager.d.ts +81 -0
  113. package/dist/lib/prompt-length-manager.js +457 -0
  114. package/dist/lib/terminal-prompt.d.ts +9 -0
  115. package/dist/lib/terminal-prompt.js +108 -0
  116. package/dist/mcp/UserContextCache.d.ts +56 -0
  117. package/dist/mcp/UserContextCache.js +163 -0
  118. package/dist/mcp/auth.d.ts +2 -0
  119. package/dist/mcp/auth.js +29 -0
  120. package/dist/mcp/hailer-clients.d.ts +42 -0
  121. package/dist/mcp/hailer-clients.js +246 -0
  122. package/dist/mcp/signal-handler.d.ts +45 -0
  123. package/dist/mcp/signal-handler.js +317 -0
  124. package/dist/mcp/tool-registry.d.ts +100 -0
  125. package/dist/mcp/tool-registry.js +306 -0
  126. package/dist/mcp/tools/activity.d.ts +15 -0
  127. package/dist/mcp/tools/activity.js +955 -0
  128. package/dist/mcp/tools/app.d.ts +20 -0
  129. package/dist/mcp/tools/app.js +1488 -0
  130. package/dist/mcp/tools/discussion.d.ts +19 -0
  131. package/dist/mcp/tools/discussion.js +950 -0
  132. package/dist/mcp/tools/file.d.ts +15 -0
  133. package/dist/mcp/tools/file.js +119 -0
  134. package/dist/mcp/tools/insight.d.ts +17 -0
  135. package/dist/mcp/tools/insight.js +806 -0
  136. package/dist/mcp/tools/skill.d.ts +10 -0
  137. package/dist/mcp/tools/skill.js +279 -0
  138. package/dist/mcp/tools/user.d.ts +10 -0
  139. package/dist/mcp/tools/user.js +108 -0
  140. package/dist/mcp/tools/workflow-template.d.ts +19 -0
  141. package/dist/mcp/tools/workflow-template.js +822 -0
  142. package/dist/mcp/tools/workflow.d.ts +18 -0
  143. package/dist/mcp/tools/workflow.js +1362 -0
  144. package/dist/mcp/utils/api-errors.d.ts +45 -0
  145. package/dist/mcp/utils/api-errors.js +160 -0
  146. package/dist/mcp/utils/data-transformers.d.ts +102 -0
  147. package/dist/mcp/utils/data-transformers.js +194 -0
  148. package/dist/mcp/utils/file-upload.d.ts +33 -0
  149. package/dist/mcp/utils/file-upload.js +148 -0
  150. package/dist/mcp/utils/hailer-api-client.d.ts +120 -0
  151. package/dist/mcp/utils/hailer-api-client.js +323 -0
  152. package/dist/mcp/utils/index.d.ts +13 -0
  153. package/dist/mcp/utils/index.js +39 -0
  154. package/dist/mcp/utils/logger.d.ts +42 -0
  155. package/dist/mcp/utils/logger.js +103 -0
  156. package/dist/mcp/utils/types.d.ts +286 -0
  157. package/dist/mcp/utils/types.js +7 -0
  158. package/dist/mcp/workspace-cache.d.ts +42 -0
  159. package/dist/mcp/workspace-cache.js +97 -0
  160. package/dist/mcp-server.d.ts +42 -0
  161. package/dist/mcp-server.js +280 -0
  162. package/package.json +56 -0
  163. package/tsconfig.json +23 -0
@@ -0,0 +1,717 @@
1
+ # Implementation Patterns
2
+
3
+ Step-by-step guides for common MCP tool development tasks.
4
+
5
+ ---
6
+
7
+ ## Pattern 1: Adding a New API Endpoint
8
+
9
+ Follow this pattern when you need to call a new Hailer API endpoint.
10
+
11
+ ### Step 1: Add Function to `hailer-api.ts`
12
+
13
+ ```typescript
14
+ // server/src/lib/hailer-api.ts
15
+
16
+ export async function myNewEndpoint(
17
+ client: HailerClient,
18
+ arg1: string,
19
+ options: MyOptions = {}
20
+ ): Promise<MyResponse> {
21
+ const { option1, option2 = 'default' } = options;
22
+
23
+ const response = await fetch(
24
+ `${client.baseUrl}/v3/my/endpoint`,
25
+ {
26
+ method: 'POST',
27
+ headers: {
28
+ 'Content-Type': 'application/json',
29
+ 'hlrkey': client.authToken
30
+ },
31
+ body: JSON.stringify([arg1, { option1, option2 }])
32
+ }
33
+ );
34
+
35
+ if (!response.ok) {
36
+ const errorText = await response.text();
37
+ throw new Error(`API error (${response.status}): ${errorText}`);
38
+ }
39
+
40
+ return response.json();
41
+ }
42
+ ```
43
+
44
+ **Key Points**:
45
+ - Always use JSON array format: `[arg1, arg2, ...]`
46
+ - Include `hlrkey` header with auth token
47
+ - Check `response.ok` before parsing JSON
48
+ - Provide helpful error messages with status code
49
+
50
+ ### Step 2: Test the Endpoint
51
+
52
+ Before building MCP tool, test the API function directly:
53
+
54
+ ```typescript
55
+ // In a test file or debug route
56
+ const result = await myNewEndpoint(
57
+ context.client,
58
+ "test-arg",
59
+ { option1: "value" }
60
+ );
61
+ console.log('Result:', result);
62
+ ```
63
+
64
+ ### Step 3: Document the Endpoint
65
+
66
+ Add to `api-endpoints.md` with full request/response examples.
67
+
68
+ ---
69
+
70
+ ## Pattern 2: Creating a New MCP Tool
71
+
72
+ Once your API endpoint works, wrap it in an MCP tool.
73
+
74
+ ### Step 1: Add Static Method to Tool Class
75
+
76
+ Choose the appropriate class:
77
+ - **ReadTools.ts** - Read-only operations (list, get, search)
78
+ - **WriteTools.ts** - Write operations (create, update, delete)
79
+ - **WorkflowTools.ts** - Workflow metadata operations
80
+ - **WorkspaceTools.ts** - Workspace operations
81
+
82
+ ```typescript
83
+ // server/src/mcp/tools/ReadTools.ts
84
+
85
+ export class ReadTools {
86
+ static async my_new_tool(
87
+ context: UserContext,
88
+ args: { arg1: string; option1?: string }
89
+ ): Promise<string> {
90
+ try {
91
+ const { arg1, option1 } = args;
92
+
93
+ // Validate required arguments
94
+ if (!arg1) {
95
+ return "Error: arg1 is required";
96
+ }
97
+
98
+ // Call API function
99
+ const result = await myNewEndpoint(
100
+ context.client,
101
+ arg1,
102
+ { option1 }
103
+ );
104
+
105
+ // Format response for LLM
106
+ return formatMyResponse(result);
107
+
108
+ } catch (error) {
109
+ console.error('Error in my_new_tool:', error);
110
+ return `Error: ${error.message}`;
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ **Key Points**:
117
+ - Always wrap in try/catch
118
+ - Validate required arguments upfront
119
+ - Return user-friendly error messages
120
+ - Log errors for debugging
121
+ - Format response for LLM readability
122
+
123
+ ### Step 2: Format Response
124
+
125
+ Create a helper function to format API response:
126
+
127
+ ```typescript
128
+ function formatMyResponse(data: MyResponse): string {
129
+ if (!data || data.items.length === 0) {
130
+ return "No items found.";
131
+ }
132
+
133
+ const lines = [`Found ${data.items.length} items:\n`];
134
+
135
+ for (const item of data.items) {
136
+ lines.push(`- ${item.name} (ID: ${item._id})`);
137
+ if (item.description) {
138
+ lines.push(` ${item.description}`);
139
+ }
140
+ }
141
+
142
+ return lines.join('\n');
143
+ }
144
+ ```
145
+
146
+ **Formatting Tips**:
147
+ - Start with summary count
148
+ - Use bullet points for lists
149
+ - Include relevant IDs for follow-up actions
150
+ - Keep it concise but informative
151
+ - Use markdown formatting
152
+
153
+ ### Step 3: Register Tool in GlobalToolRegistry
154
+
155
+ ```typescript
156
+ // server/src/mcp/GlobalToolRegistry.ts
157
+
158
+ {
159
+ name: "my_new_tool",
160
+ description: "Clear description of what this tool does and when to use it",
161
+ inputSchema: {
162
+ type: "object",
163
+ properties: {
164
+ arg1: {
165
+ type: "string",
166
+ description: "Description of arg1"
167
+ },
168
+ option1: {
169
+ type: "string",
170
+ description: "Optional parameter description"
171
+ }
172
+ },
173
+ required: ["arg1"]
174
+ },
175
+ execute: async (args: any, context: UserContext) => {
176
+ return ReadTools.my_new_tool(context, args);
177
+ }
178
+ }
179
+ ```
180
+
181
+ **Description Guidelines**:
182
+ - Be specific about what it does
183
+ - Mention key parameters
184
+ - Indicate when LLM should use it
185
+ - Include examples if complex
186
+
187
+ ### Step 4: Test the Tool
188
+
189
+ Test via debug endpoint:
190
+
191
+ ```bash
192
+ curl -X POST http://localhost:3030/api/debug/execute-tool \
193
+ -H "Content-Type: application/json" \
194
+ -d '{
195
+ "apiKey": "test-key",
196
+ "toolName": "my_new_tool",
197
+ "args": {
198
+ "arg1": "test-value",
199
+ "option1": "optional-value"
200
+ }
201
+ }'
202
+ ```
203
+
204
+ Or test with Claude Code:
205
+ ```
206
+ Use my_new_tool with arg1="test"
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Pattern 3: Implementing Filters
212
+
213
+ Advanced filtering for activity queries.
214
+
215
+ ### Basic Filter
216
+
217
+ ```typescript
218
+ const activities = await listActivities(client, workflowId, {
219
+ filters: {
220
+ and: [
221
+ { "priority": { equalTo: "high" } }
222
+ ]
223
+ },
224
+ returnFlat: true
225
+ });
226
+ ```
227
+
228
+ ### Multiple Conditions (AND)
229
+
230
+ ```typescript
231
+ filters: {
232
+ and: [
233
+ { "status": { equalTo: "active" } },
234
+ { "priority": { equalTo: "high" } },
235
+ { "created": { greaterThan: "2025-10-01" } }
236
+ ]
237
+ }
238
+ ```
239
+
240
+ ### Multiple Conditions (OR)
241
+
242
+ ```typescript
243
+ filters: {
244
+ or: [
245
+ { "status": { equalTo: "active" } },
246
+ { "status": { equalTo: "pending" } }
247
+ ]
248
+ }
249
+ ```
250
+
251
+ ### Range Filters
252
+
253
+ ```typescript
254
+ // Date range
255
+ filters: {
256
+ and: [
257
+ { "created": { between: ["2025-10-01", "2025-10-31"] } }
258
+ ]
259
+ }
260
+
261
+ // Numeric range
262
+ filters: {
263
+ and: [
264
+ { "budget": { greaterThanOrEqual: 10000 } },
265
+ { "budget": { lessThanOrEqual: 50000 } }
266
+ ]
267
+ }
268
+ ```
269
+
270
+ ### Text Search
271
+
272
+ ```typescript
273
+ filters: {
274
+ and: [
275
+ { "description": { textSearch: "urgent" } }
276
+ ]
277
+ }
278
+ ```
279
+
280
+ ### Filter by Relationship
281
+
282
+ ```typescript
283
+ // Find all tasks for a specific topic
284
+ filters: {
285
+ and: [
286
+ { "relatedTo": { equalTo: topicActivityId } }
287
+ ]
288
+ }
289
+ ```
290
+
291
+ ### Complex Filter Example
292
+
293
+ ```typescript
294
+ // Find high-priority active tasks created this month
295
+ const filters = {
296
+ and: [
297
+ { "priority": { equalTo: "high" } },
298
+ { "status": { equalTo: "active" } },
299
+ { "created": {
300
+ between: [
301
+ new Date('2025-10-01').getTime().toString(),
302
+ new Date('2025-10-31').getTime().toString()
303
+ ]
304
+ }
305
+ }
306
+ ]
307
+ };
308
+ ```
309
+
310
+ ---
311
+
312
+ ## Pattern 4: Implementing Pagination
313
+
314
+ Handle large result sets efficiently.
315
+
316
+ ### Simple Pagination
317
+
318
+ ```typescript
319
+ async function getAllActivities(
320
+ client: HailerClient,
321
+ workflowId: string
322
+ ): Promise<Activity[]> {
323
+ const allActivities: Activity[] = [];
324
+ let page = 1;
325
+ let hasMore = true;
326
+
327
+ while (hasMore) {
328
+ const response = await listActivities(client, workflowId, {
329
+ page,
330
+ limit: 100,
331
+ returnFlat: true
332
+ });
333
+
334
+ allActivities.push(...response.data);
335
+
336
+ hasMore = page < response.totalPages;
337
+ page++;
338
+ }
339
+
340
+ return allActivities;
341
+ }
342
+ ```
343
+
344
+ ### Pagination with Progress
345
+
346
+ ```typescript
347
+ async function getAllActivitiesWithProgress(
348
+ client: HailerClient,
349
+ workflowId: string,
350
+ onProgress?: (current: number, total: number) => void
351
+ ): Promise<Activity[]> {
352
+ const allActivities: Activity[] = [];
353
+ let page = 1;
354
+
355
+ // Get first page to know total
356
+ const firstResponse = await listActivities(client, workflowId, {
357
+ page: 1,
358
+ limit: 100,
359
+ returnFlat: true
360
+ });
361
+
362
+ allActivities.push(...firstResponse.data);
363
+ const totalPages = firstResponse.totalPages;
364
+
365
+ onProgress?.(1, totalPages);
366
+
367
+ // Fetch remaining pages
368
+ for (page = 2; page <= totalPages; page++) {
369
+ const response = await listActivities(client, workflowId, {
370
+ page,
371
+ limit: 100,
372
+ returnFlat: true
373
+ });
374
+
375
+ allActivities.push(...response.data);
376
+ onProgress?.(page, totalPages);
377
+ }
378
+
379
+ return allActivities;
380
+ }
381
+ ```
382
+
383
+ ### Discussion Message Pagination
384
+
385
+ ```typescript
386
+ async function getAllMessages(
387
+ client: HailerClient,
388
+ discussionId: string
389
+ ): Promise<Message[]> {
390
+ const allMessages: Message[] = [];
391
+
392
+ // Fetch latest 50
393
+ const latest = await fetchLatestMessages(client, discussionId, 50);
394
+ allMessages.push(...latest.messages);
395
+
396
+ // Fetch older messages
397
+ let oldestId = latest.messages[latest.messages.length - 1]?._id;
398
+
399
+ while (oldestId) {
400
+ const previous = await fetchPreviousMessages(client, oldestId, 50);
401
+
402
+ if (previous.messages.length === 0) {
403
+ break;
404
+ }
405
+
406
+ allMessages.push(...previous.messages);
407
+ oldestId = previous.messages[previous.messages.length - 1]?._id;
408
+ }
409
+
410
+ return allMessages;
411
+ }
412
+ ```
413
+
414
+ ---
415
+
416
+ ## Pattern 5: Socket.io Operations
417
+
418
+ Working with socket.io methods via `@hailer/cli`.
419
+
420
+ ### Basic Socket Call
421
+
422
+ ```typescript
423
+ async function followActivity(
424
+ client: HailerClient,
425
+ activityId: string,
426
+ follow: boolean
427
+ ): Promise<void> {
428
+ await client.socket.call('activities.follow', {
429
+ activityId,
430
+ follow
431
+ });
432
+ }
433
+ ```
434
+
435
+ ### Socket Call with Response
436
+
437
+ ```typescript
438
+ async function getActivityDetails(
439
+ client: HailerClient,
440
+ activityId: string
441
+ ): Promise<any> {
442
+ const response = await client.socket.call('activities.get', {
443
+ activityId
444
+ });
445
+
446
+ return response;
447
+ }
448
+ ```
449
+
450
+ ### Socket Call with Error Handling
451
+
452
+ ```typescript
453
+ async function leaveDiscussion(
454
+ client: HailerClient,
455
+ discussionId: string
456
+ ): Promise<string> {
457
+ try {
458
+ await client.socket.call('messenger.leave_discussion', {
459
+ discussionId
460
+ });
461
+
462
+ return `Successfully left discussion ${discussionId}`;
463
+ } catch (error) {
464
+ console.error('Socket error:', error);
465
+ return `Error leaving discussion: ${error.message}`;
466
+ }
467
+ }
468
+ ```
469
+
470
+ ---
471
+
472
+ ## Pattern 6: Error Handling Best Practices
473
+
474
+ Comprehensive error handling for robust tools.
475
+
476
+ ### Multi-Level Error Handling
477
+
478
+ ```typescript
479
+ export class ReadTools {
480
+ static async list_activities(
481
+ context: UserContext,
482
+ args: any
483
+ ): Promise<string> {
484
+ try {
485
+ // Level 1: Validate arguments
486
+ if (!args.workflowId) {
487
+ return "Error: workflowId is required";
488
+ }
489
+
490
+ // Level 2: Validate workflow exists
491
+ const workflow = await getWorkflow(context.client, args.workflowId);
492
+ if (!workflow) {
493
+ return `Error: Workflow ${args.workflowId} not found`;
494
+ }
495
+
496
+ // Level 3: Call API
497
+ const activities = await listActivities(
498
+ context.client,
499
+ args.workflowId,
500
+ { returnFlat: true }
501
+ );
502
+
503
+ // Level 4: Validate response
504
+ if (!activities || !activities.data) {
505
+ return "Error: Invalid response from API";
506
+ }
507
+
508
+ return formatActivities(activities.data);
509
+
510
+ } catch (error) {
511
+ // Level 5: Catch unexpected errors
512
+ console.error('Error in list_activities:', error);
513
+
514
+ // Provide helpful error message based on error type
515
+ if (error.message.includes('401')) {
516
+ return "Error: Authentication failed. Check API credentials.";
517
+ }
518
+ if (error.message.includes('404')) {
519
+ return "Error: Endpoint not found. Check API version.";
520
+ }
521
+ if (error.message.includes('timeout')) {
522
+ return "Error: Request timed out. Try again.";
523
+ }
524
+
525
+ return `Error: ${error.message}`;
526
+ }
527
+ }
528
+ }
529
+ ```
530
+
531
+ ### Specific Error Types
532
+
533
+ ```typescript
534
+ class HailerAPIError extends Error {
535
+ constructor(
536
+ public statusCode: number,
537
+ public details: any,
538
+ message: string
539
+ ) {
540
+ super(message);
541
+ this.name = 'HailerAPIError';
542
+ }
543
+ }
544
+
545
+ // Usage in API function
546
+ if (!response.ok) {
547
+ const details = await response.json().catch(() => ({}));
548
+ throw new HailerAPIError(
549
+ response.status,
550
+ details,
551
+ `API request failed: ${response.statusText}`
552
+ );
553
+ }
554
+
555
+ // Catch specific error type
556
+ try {
557
+ await apiCall();
558
+ } catch (error) {
559
+ if (error instanceof HailerAPIError) {
560
+ if (error.statusCode === 422) {
561
+ return `Validation error: ${JSON.stringify(error.details)}`;
562
+ }
563
+ }
564
+ throw error;
565
+ }
566
+ ```
567
+
568
+ ---
569
+
570
+ ## Pattern 7: Testing Tools
571
+
572
+ Test tools thoroughly before deployment.
573
+
574
+ ### Unit Test Pattern
575
+
576
+ ```typescript
577
+ // server/src/mcp/tools/__tests__/ReadTools.test.ts
578
+
579
+ import { ReadTools } from '../ReadTools';
580
+ import { UserContext } from '../../UserContextCache';
581
+
582
+ describe('ReadTools', () => {
583
+ let mockContext: UserContext;
584
+
585
+ beforeEach(() => {
586
+ mockContext = {
587
+ client: createMockClient(),
588
+ initData: mockInitData,
589
+ workspaceCache: mockCache
590
+ };
591
+ });
592
+
593
+ it('should list activities', async () => {
594
+ const result = await ReadTools.list_activities(
595
+ mockContext,
596
+ { workflowId: 'test-workflow-id' }
597
+ );
598
+
599
+ expect(result).toContain('Found');
600
+ expect(result).not.toContain('Error');
601
+ });
602
+
603
+ it('should handle missing workflowId', async () => {
604
+ const result = await ReadTools.list_activities(
605
+ mockContext,
606
+ {}
607
+ );
608
+
609
+ expect(result).toContain('Error');
610
+ expect(result).toContain('workflowId is required');
611
+ });
612
+ });
613
+ ```
614
+
615
+ ### Integration Test Pattern
616
+
617
+ ```typescript
618
+ // Test against real API (local development)
619
+ describe('ReadTools Integration', () => {
620
+ it('should fetch real activities', async () => {
621
+ const context = await createRealContext('test-key');
622
+
623
+ const result = await ReadTools.list_activities(
624
+ context,
625
+ { workflowId: '68446dc05b30685f67c6fcd4' }
626
+ );
627
+
628
+ console.log('Result:', result);
629
+ expect(result).not.toContain('Error');
630
+ });
631
+ });
632
+ ```
633
+
634
+ ### Manual Test via Debug Endpoint
635
+
636
+ ```bash
637
+ #!/bin/bash
638
+ # test-tool.sh
639
+
640
+ curl -X POST http://localhost:3030/api/debug/execute-tool \
641
+ -H "Content-Type: application/json" \
642
+ -d "{
643
+ \"apiKey\": \"test-key\",
644
+ \"toolName\": \"$1\",
645
+ \"args\": $2
646
+ }" | jq
647
+ ```
648
+
649
+ Usage:
650
+ ```bash
651
+ ./test-tool.sh list_activities '{"workflowId":"68446dc05b30685f67c6fcd4"}'
652
+ ```
653
+
654
+ ---
655
+
656
+ ## Pattern 8: Optimizing for Context
657
+
658
+ Keep MCP tool responses concise to save context.
659
+
660
+ ### Bad: Verbose Response
661
+
662
+ ```typescript
663
+ return `Successfully listed activities. Total count: ${activities.length}
664
+ Activities:
665
+ ${activities.map(a => `
666
+ - ID: ${a._id}
667
+ - Name: ${a.name}
668
+ - Process ID: ${a.processId}
669
+ - Phase ID: ${a.phaseId}
670
+ - Created: ${a.created}
671
+ - Updated: ${a.updated}
672
+ - Discussion: ${a.discussion}
673
+ - Fields: ${JSON.stringify(a.fields, null, 2)}
674
+ `).join('\n')}`;
675
+ ```
676
+
677
+ ### Good: Concise Response
678
+
679
+ ```typescript
680
+ const summary = activities.length === 0
681
+ ? "No activities found."
682
+ : `Found ${activities.length} activities:`;
683
+
684
+ const items = activities.slice(0, 10).map(a =>
685
+ `- ${a.name} (${a._id})`
686
+ ).join('\n');
687
+
688
+ const more = activities.length > 10
689
+ ? `\n... and ${activities.length - 10} more`
690
+ : '';
691
+
692
+ return `${summary}\n${items}${more}`;
693
+ ```
694
+
695
+ ### Include Only What's Needed
696
+
697
+ ```typescript
698
+ // For listing: names and IDs only
699
+ // For details: full data when requested
700
+ ```
701
+
702
+ ---
703
+
704
+ ## Checklist: New MCP Tool
705
+
706
+ - [ ] API endpoint function in `hailer-api.ts`
707
+ - [ ] Test API function directly
708
+ - [ ] Static method in appropriate tool class
709
+ - [ ] Input validation
710
+ - [ ] Error handling (try/catch)
711
+ - [ ] User-friendly response formatting
712
+ - [ ] Tool registration in GlobalToolRegistry
713
+ - [ ] Clear description in schema
714
+ - [ ] Test via debug endpoint
715
+ - [ ] Test with Claude Code
716
+ - [ ] Document in skill if complex
717
+ - [ ] Update CLAUDE.md if adds new capability