@hailer/mcp 0.0.6 → 0.1.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 (122) hide show
  1. package/.claude/agents/ada.md +127 -0
  2. package/.claude/agents/agent-builder.md +151 -0
  3. package/.claude/agents/alejandro.md +66 -0
  4. package/.claude/agents/bjorn.md +305 -0
  5. package/.claude/agents/dmitri.md +61 -0
  6. package/.claude/agents/giuseppe.md +66 -0
  7. package/.claude/agents/gunther.md +355 -0
  8. package/.claude/agents/helga.md +68 -0
  9. package/.claude/agents/ingrid.md +108 -0
  10. package/.claude/agents/kenji.md +58 -0
  11. package/.claude/agents/svetlana.md +394 -0
  12. package/.claude/agents/viktor.md +63 -0
  13. package/.claude/agents/yevgeni.md +60 -0
  14. package/.claude/hooks/agent-failure-detector.cjs +286 -0
  15. package/.claude/hooks/app-edit-guard.cjs +462 -0
  16. package/.claude/hooks/interactive-mode.cjs +59 -0
  17. package/.claude/hooks/mcp-server-guard.cjs +92 -0
  18. package/.claude/hooks/post-scaffold-hook.cjs +31 -0
  19. package/.claude/hooks/sdk-delete-guard.cjs +2 -0
  20. package/.claude/hooks/src-edit-guard.cjs +208 -0
  21. package/.claude/settings.json +47 -2
  22. package/.claude/skills/insight-join-patterns/SKILL.md +209 -0
  23. package/.env.example +13 -1
  24. package/CLAUDE.md +135 -0
  25. package/dist/app.js +4 -3
  26. package/dist/cli.js +0 -0
  27. package/dist/client/adaptive-documentation-bot.d.ts +0 -2
  28. package/dist/client/adaptive-documentation-bot.js +5 -16
  29. package/dist/client/message-processor.js +5 -0
  30. package/dist/client/providers/anthropic-provider.js +21 -7
  31. package/dist/mcp/UserContextCache.d.ts +14 -0
  32. package/dist/mcp/UserContextCache.js +49 -24
  33. package/dist/mcp/auth.d.ts +7 -0
  34. package/dist/mcp/auth.js +13 -5
  35. package/dist/mcp/hailer-clients.d.ts +5 -2
  36. package/dist/mcp/signal-handler.d.ts +28 -2
  37. package/dist/mcp/signal-handler.js +4 -2
  38. package/dist/mcp/tool-registry.d.ts +55 -2
  39. package/dist/mcp/tool-registry.js +197 -2
  40. package/dist/mcp/tools/app-core.d.ts +15 -0
  41. package/dist/mcp/tools/app-core.js +609 -0
  42. package/dist/mcp/tools/app-marketplace.d.ts +21 -0
  43. package/dist/mcp/tools/app-marketplace.js +1284 -0
  44. package/dist/mcp/tools/app-member.d.ts +11 -0
  45. package/dist/mcp/tools/app-member.js +258 -0
  46. package/dist/mcp/tools/app-scaffold.d.ts +11 -0
  47. package/dist/mcp/tools/app-scaffold.js +743 -0
  48. package/dist/mcp/tools/app.d.ts +13 -22
  49. package/dist/mcp/tools/app.js +17 -2466
  50. package/dist/mcp/tools/file.js +6 -6
  51. package/dist/mcp/tools/insight.d.ts +1 -0
  52. package/dist/mcp/tools/insight.js +203 -64
  53. package/dist/mcp/tools/user.js +3 -9
  54. package/dist/mcp/tools/workflow.js +49 -38
  55. package/dist/mcp/utils/hailer-api-client.js +4 -13
  56. package/dist/mcp/utils/tool-helpers.d.ts +102 -0
  57. package/dist/mcp/utils/tool-helpers.js +179 -0
  58. package/dist/mcp/utils/types.d.ts +6 -0
  59. package/dist/mcp/workspace-cache.d.ts +5 -5
  60. package/dist/mcp/workspace-cache.js +4 -3
  61. package/package.json +1 -1
  62. package/.claude/hooks/PreToolUse.sh +0 -52
  63. package/.claude/hooks/prompt-skill-loader.cjs +0 -553
  64. package/.claude/hooks/skill-loader.cjs +0 -142
  65. package/.claude/settings.local.json +0 -49
  66. package/.claude/skills/MCP-add-app-member-skill/SKILL.md +0 -977
  67. package/.claude/skills/MCP-build-data-app-skill/SKILL.md +0 -372
  68. package/.claude/skills/MCP-create-app-skill/SKILL.md +0 -1101
  69. package/.claude/skills/MCP-create-insight-skill/SKILL.md +0 -1317
  70. package/.claude/skills/MCP-get-insight-data-skill/SKILL.md +0 -1053
  71. package/.claude/skills/MCP-insight-api/SKILL.md +0 -185
  72. package/.claude/skills/MCP-insight-api/references/insight-endpoints.md +0 -514
  73. package/.claude/skills/MCP-install-workflow-skill/SKILL.md +0 -1056
  74. package/.claude/skills/MCP-list-apps-skill/SKILL.md +0 -1010
  75. package/.claude/skills/MCP-list-workflows-minimal-skill/SKILL.md +0 -992
  76. package/.claude/skills/MCP-local-first-skill/SKILL.md +0 -570
  77. package/.claude/skills/MCP-populate-workflow-data-skill/SKILL.md +0 -395
  78. package/.claude/skills/MCP-preview-insight-skill/SKILL.md +0 -1290
  79. package/.claude/skills/MCP-publish-hailer-app-skill/SKILL.md +0 -453
  80. package/.claude/skills/MCP-publish-template-skill/SKILL.md +0 -278
  81. package/.claude/skills/MCP-remove-app-member-skill/SKILL.md +0 -671
  82. package/.claude/skills/MCP-remove-app-skill/SKILL.md +0 -985
  83. package/.claude/skills/MCP-remove-insight-skill/SKILL.md +0 -1011
  84. package/.claude/skills/MCP-remove-workflow-skill/SKILL.md +0 -920
  85. package/.claude/skills/MCP-scaffold-hailer-app-skill/SKILL.md +0 -1314
  86. package/.claude/skills/MCP-update-app-skill/SKILL.md +0 -970
  87. package/.claude/skills/MCP-update-workflow-field-skill/SKILL.md +0 -1098
  88. package/.claude/skills/SDK-create-function-field-skill/SKILL.md +0 -313
  89. package/.claude/skills/SDK-generate-skill/SKILL.md +0 -223
  90. package/.claude/skills/SDK-init-skill/SKILL.md +0 -177
  91. package/.claude/skills/SDK-workspace-setup-skill/SKILL.md +0 -605
  92. package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -435
  93. package/.claude/skills/activity-api/SKILL.md +0 -96
  94. package/.claude/skills/activity-api/references/activity-endpoints.md +0 -845
  95. package/.claude/skills/agent-building/SKILL.md +0 -243
  96. package/.claude/skills/agent-building/references/architecture-patterns.md +0 -446
  97. package/.claude/skills/agent-building/references/code-examples.md +0 -587
  98. package/.claude/skills/agent-building/references/implementation-guide.md +0 -619
  99. package/.claude/skills/app-api/SKILL.md +0 -219
  100. package/.claude/skills/app-api/references/app-endpoints.md +0 -759
  101. package/.claude/skills/building-hailer-apps-skill/SKILL.md +0 -813
  102. package/.claude/skills/hailer-api/SKILL.md +0 -283
  103. package/.claude/skills/hailer-api/references/activities.md +0 -620
  104. package/.claude/skills/hailer-api/references/authentication.md +0 -216
  105. package/.claude/skills/hailer-api/references/datasets.md +0 -437
  106. package/.claude/skills/hailer-api/references/files.md +0 -301
  107. package/.claude/skills/hailer-api/references/insights.md +0 -469
  108. package/.claude/skills/hailer-api/references/workflows.md +0 -720
  109. package/.claude/skills/hailer-api/references/workspaces-users.md +0 -445
  110. package/.claude/skills/hailer-app-builder/SKILL.md +0 -340
  111. package/.claude/skills/mcp-tools/SKILL.md +0 -419
  112. package/.claude/skills/mcp-tools/references/api-endpoints.md +0 -499
  113. package/.claude/skills/mcp-tools/references/data-structures.md +0 -554
  114. package/.claude/skills/mcp-tools/references/implementation-patterns.md +0 -717
  115. package/.claude/skills/skill-testing/README.md +0 -137
  116. package/.claude/skills/skill-testing/SKILL.md +0 -348
  117. package/.claude/skills/skill-testing/references/test-patterns.md +0 -705
  118. package/.claude/skills/skill-testing/references/testing-guide.md +0 -603
  119. package/.claude/skills/skill-testing/references/validation-checklist.md +0 -537
  120. package/.claude/skills/spawn-app-builder/SKILL.md +0 -366
  121. package/.claude/skills/tool-builder/SKILL.md +0 -328
  122. package/tsconfig.json +0 -23
@@ -17,13 +17,11 @@ exports.AdaptiveDocumentationBot = void 0;
17
17
  const logger_1 = require("../lib/logger");
18
18
  const log_parser_1 = require("./log-parser");
19
19
  const description_updater_1 = require("./description-updater");
20
- const skill_generator_1 = require("./skill-generator");
21
20
  const simple_llm_caller_1 = require("./simple-llm-caller");
22
21
  const logger = (0, logger_1.createLogger)({ component: 'AdaptiveDocumentationBot' });
23
22
  class AdaptiveDocumentationBot {
24
23
  logParser;
25
24
  descriptionUpdater;
26
- skillGenerator;
27
25
  llmCaller;
28
26
  config;
29
27
  errorPatterns = new Map();
@@ -41,7 +39,6 @@ class AdaptiveDocumentationBot {
41
39
  };
42
40
  this.logParser = new log_parser_1.LogParser(this.config.logPath);
43
41
  this.descriptionUpdater = new description_updater_1.DescriptionUpdater();
44
- this.skillGenerator = new skill_generator_1.SkillGenerator();
45
42
  logger.info('Adaptive Documentation Bot initialized', {
46
43
  autoUpdate: this.config.autoUpdate,
47
44
  updateInterval: this.config.updateInterval,
@@ -314,12 +311,7 @@ Return ONLY valid JSON, no other text.`;
314
311
  await this.descriptionUpdater.updateParameterDescription(toolName, analysis.affectedParameter, paramImproved, analysis.problem);
315
312
  }
316
313
  }
317
- // 3. Create/update skill if needed
318
- if (this.config.skillGeneration && analysis.needsSkill) {
319
- await this.skillGenerator.updateOrCreateSkill(toolName, pattern, analysis.incorrectUsage || pattern.examples[0].attemptedCall, analysis.correctUsage || {});
320
- // Update skill summary
321
- await this.skillGenerator.updateSkillSummary(toolName);
322
- }
314
+ // 3. Skill generation disabled (skills moved to docs/)
323
315
  logger.info('✅ Improvements applied', {
324
316
  tool: toolName,
325
317
  descriptionUpdated: analysis.isDescriptionIssue,
@@ -426,8 +418,7 @@ Return ONLY the improved description text.`;
426
418
  return {
427
419
  monitoring: this.isMonitoring,
428
420
  errorPatterns: this.errorPatterns.size,
429
- improvements: this.descriptionUpdater.getStats(),
430
- skills: this.skillGenerator.getStats()
421
+ improvements: this.descriptionUpdater.getStats()
431
422
  };
432
423
  }
433
424
  /**
@@ -452,15 +443,13 @@ Return ONLY the improved description text.`;
452
443
  for (const pattern of this.errorPatterns.values()) {
453
444
  if (pattern.count >= this.config.minErrorCount && !pattern.suggestedFix) {
454
445
  const toolName = pattern.examples[0].toolName;
455
- // Check if existing skill exists
456
- const existingSkill = await this.skillGenerator['findExistingSkill'](toolName);
457
446
  suggestions.push({
458
447
  toolName,
459
448
  errorCount: pattern.count,
460
449
  errorMessage: pattern.examples[0].errorMessage,
461
- existingSkill: existingSkill?.name,
462
- wouldCreateSkill: !existingSkill,
463
- wouldUpdateSkill: !!existingSkill
450
+ existingSkill: undefined,
451
+ wouldCreateSkill: false,
452
+ wouldUpdateSkill: false
464
453
  });
465
454
  }
466
455
  }
@@ -201,6 +201,11 @@ class HailerMessageProcessor {
201
201
  async extractFromMessengerNew(signal) {
202
202
  try {
203
203
  const { discussion, msg_id, uid } = signal.data;
204
+ // Validate required fields
205
+ if (!uid || !discussion) {
206
+ console.warn("❌ MCP Client: Missing uid or discussion in messenger.new signal");
207
+ return [];
208
+ }
204
209
  // IMPORTANT: Ignore messages from our own bots to prevent loops
205
210
  if (this.mcpAgentIds.includes(uid)) {
206
211
  console.log(`🔇 MCP Client: Ignoring message from bot ${uid} (preventing self-trigger loop)`);
@@ -118,10 +118,16 @@ class AnthropicProvider extends llm_provider_1.LlmProvider {
118
118
  error: `Initial prompt too long: ${initialTokenCount.totalTokens} tokens exceeds safe limit of ${initialTokenCount.limit.safeTokens} tokens`,
119
119
  };
120
120
  }
121
- let response = await this.client.messages.create({
121
+ // Enable structured outputs with strict mode for schema validation
122
+ const strictTools = minimalTools.map(tool => ({
123
+ ...tool,
124
+ strict: true
125
+ }));
126
+ let response = await this.client.beta.messages.create({
122
127
  model: this.config.model || "claude-sonnet-4-20250514",
123
128
  max_tokens: this.config.maxTokens || 2000,
124
129
  temperature: this.config.temperature || 0.7,
130
+ betas: ['structured-outputs-2025-11-13'],
125
131
  system: [
126
132
  {
127
133
  type: "text",
@@ -135,7 +141,7 @@ class AnthropicProvider extends llm_provider_1.LlmProvider {
135
141
  content: finalUserContent,
136
142
  },
137
143
  ],
138
- tools: minimalTools,
144
+ tools: strictTools,
139
145
  });
140
146
  const toolCalls = [];
141
147
  // No conversation history - process each message independently
@@ -376,10 +382,16 @@ class AnthropicProvider extends llm_provider_1.LlmProvider {
376
382
  }
377
383
  }
378
384
  // Make API call with potentially summarized messages and optimized tools
379
- response = await this.client.messages.create({
385
+ // Add strict mode for structured outputs
386
+ const strictOptimizedTools = optimizedTools.map(tool => ({
387
+ ...tool,
388
+ strict: true
389
+ }));
390
+ response = await this.client.beta.messages.create({
380
391
  model: this.config.model || "claude-sonnet-4-20250514",
381
392
  max_tokens: this.config.maxTokens || 2000,
382
393
  temperature: this.config.temperature || 0.7,
394
+ betas: ['structured-outputs-2025-11-13'],
383
395
  system: [
384
396
  {
385
397
  type: "text",
@@ -388,7 +400,7 @@ class AnthropicProvider extends llm_provider_1.LlmProvider {
388
400
  }
389
401
  ],
390
402
  messages: messagesToSend,
391
- tools: optimizedTools,
403
+ tools: strictOptimizedTools,
392
404
  });
393
405
  // Accumulate tokens from this API call
394
406
  if (response.usage) {
@@ -445,11 +457,12 @@ class AnthropicProvider extends llm_provider_1.LlmProvider {
445
457
  // Continue with truncated messages as fallback
446
458
  }
447
459
  }
448
- // Make final call without tools
449
- response = await this.client.messages.create({
460
+ // Make final call without tools (no strict mode needed - no tools)
461
+ response = await this.client.beta.messages.create({
450
462
  model: this.config.model || "claude-sonnet-4-20250514",
451
463
  max_tokens: this.config.maxTokens || 2000,
452
464
  temperature: this.config.temperature || 0.7,
465
+ betas: ['structured-outputs-2025-11-13'],
453
466
  system: [
454
467
  {
455
468
  type: "text",
@@ -567,7 +580,7 @@ class AnthropicProvider extends llm_provider_1.LlmProvider {
567
580
  if (jsonData.error) {
568
581
  throw new Error(`MCP tool schema error: ${jsonData.error.message || jsonData.error}`);
569
582
  }
570
- // Convert MCP tool format to Anthropic tool format
583
+ // Convert MCP tool format to Anthropic tool format with strict mode
571
584
  const toolSchema = {
572
585
  name: jsonData.result.name,
573
586
  description: jsonData.result.description || `Tool: ${jsonData.result.name}`,
@@ -576,6 +589,7 @@ class AnthropicProvider extends llm_provider_1.LlmProvider {
576
589
  properties: {},
577
590
  required: [],
578
591
  },
592
+ strict: true, // Enable structured outputs validation
579
593
  };
580
594
  // Cache the schema
581
595
  this.toolSchemaCache.set(cacheKey, toolSchema);
@@ -8,6 +8,8 @@ export interface UserContext {
8
8
  workspaceCache: WorkspaceCache;
9
9
  apiKey: string;
10
10
  createdAt: number;
11
+ email: string;
12
+ password: string;
11
13
  }
12
14
  /**
13
15
  * Cache for user-specific data (client connections, init data, workspace cache)
@@ -16,20 +18,32 @@ export interface UserContext {
16
18
  * - Reuses shared connection pool from hailer-clients.ts
17
19
  * - Caches user-specific data (init, workspaceCache) separately from tool logic
18
20
  * - Used by both MCP Server and Client for consistent user context
21
+ * - Uses Promise memoization to prevent duplicate context creation on concurrent requests
19
22
  */
20
23
  export declare class UserContextCache {
21
24
  private static cache;
25
+ /** Pending context creation promises - prevents race conditions on concurrent requests */
26
+ private static pending;
22
27
  /** Life of each cache unit. */
23
28
  private static readonly DEFAULT_TTL_MS;
24
29
  /**
25
30
  * Get or create user context (client, init, workspaceCache) for an API key
26
31
  * This data is user-specific and should be cached per user
27
32
  *
33
+ * Uses Promise memoization to prevent race conditions: if multiple requests
34
+ * come in for the same API key simultaneously, they all wait for the same
35
+ * Promise instead of creating duplicate connections.
36
+ *
28
37
  * @param apiKey - Hailer API key for authentication
29
38
  * @param forceRefresh - If true, bypasses cache and creates fresh context (useful for testing or after permission changes)
30
39
  * @param ttlMs - Custom TTL in milliseconds, defaults to 15 minutes
31
40
  */
32
41
  static getContext(apiKey: string, forceRefresh?: boolean, ttlMs?: number): Promise<UserContext>;
42
+ /**
43
+ * Internal method that actually creates the context.
44
+ * Separated from getContext to support Promise memoization.
45
+ */
46
+ private static createContextInternal;
33
47
  /**
34
48
  * Clear cache entry for specific API key (for cleanup/testing)
35
49
  */
@@ -14,21 +14,33 @@ const logger = (0, logger_1.createLogger)({ component: 'user-context-cache' });
14
14
  * - Reuses shared connection pool from hailer-clients.ts
15
15
  * - Caches user-specific data (init, workspaceCache) separately from tool logic
16
16
  * - Used by both MCP Server and Client for consistent user context
17
+ * - Uses Promise memoization to prevent duplicate context creation on concurrent requests
17
18
  */
18
19
  class UserContextCache {
19
20
  static cache = new Map();
21
+ /** Pending context creation promises - prevents race conditions on concurrent requests */
22
+ static pending = new Map();
20
23
  /** Life of each cache unit. */
21
24
  static DEFAULT_TTL_MS = 15 * 60 * 1000; // 15 minutes
22
25
  /**
23
26
  * Get or create user context (client, init, workspaceCache) for an API key
24
27
  * This data is user-specific and should be cached per user
25
28
  *
29
+ * Uses Promise memoization to prevent race conditions: if multiple requests
30
+ * come in for the same API key simultaneously, they all wait for the same
31
+ * Promise instead of creating duplicate connections.
32
+ *
26
33
  * @param apiKey - Hailer API key for authentication
27
34
  * @param forceRefresh - If true, bypasses cache and creates fresh context (useful for testing or after permission changes)
28
35
  * @param ttlMs - Custom TTL in milliseconds, defaults to 15 minutes
29
36
  */
30
37
  static async getContext(apiKey, forceRefresh = false, ttlMs = this.DEFAULT_TTL_MS) {
31
- console.log(`🔍 getContext called: apiKey=${apiKey.substring(0, 8)}..., forceRefresh=${forceRefresh}, cacheHas=${this.cache.has(apiKey)}`);
38
+ logger.debug('getContext called', {
39
+ apiKey: apiKey.substring(0, 8) + '...',
40
+ forceRefresh,
41
+ inCache: this.cache.has(apiKey),
42
+ isPending: this.pending.has(apiKey)
43
+ });
32
44
  // Check cache first (unless forceRefresh is requested)
33
45
  if (!forceRefresh && this.cache.has(apiKey)) {
34
46
  const cachedContext = this.cache.get(apiKey);
@@ -58,6 +70,31 @@ class UserContextCache {
58
70
  apiKey: apiKey.substring(0, 8) + '...'
59
71
  });
60
72
  }
73
+ // Check if context creation is already in progress (prevents race condition)
74
+ if (!forceRefresh && this.pending.has(apiKey)) {
75
+ logger.debug('Waiting for pending context creation', {
76
+ apiKey: apiKey.substring(0, 8) + '...'
77
+ });
78
+ return this.pending.get(apiKey);
79
+ }
80
+ // Create the context and store the Promise immediately to prevent duplicates
81
+ const contextPromise = this.createContextInternal(apiKey);
82
+ this.pending.set(apiKey, contextPromise);
83
+ try {
84
+ const context = await contextPromise;
85
+ this.cache.set(apiKey, context);
86
+ return context;
87
+ }
88
+ finally {
89
+ // Always clean up pending entry
90
+ this.pending.delete(apiKey);
91
+ }
92
+ }
93
+ /**
94
+ * Internal method that actually creates the context.
95
+ * Separated from getContext to support Promise memoization.
96
+ */
97
+ static async createContextInternal(apiKey) {
61
98
  logger.info('Creating new user context', { apiKey: apiKey.substring(0, 8) + '...' });
62
99
  try {
63
100
  // Create client connection (uses existing connection pool)
@@ -69,45 +106,33 @@ class UserContextCache {
69
106
  ['processes', 'users', 'network', 'networks']
70
107
  ]);
71
108
  // Create workspace cache from init data
72
- const appConfig = (0, config_1.createApplicationConfig)(); // TODO: small concern here: it creates a new instance of config instaed of reusing it, is it wise?
109
+ const appConfig = (0, config_1.createApplicationConfig)();
73
110
  const workspaceCache = (0, workspace_cache_1.createWorkspaceCache)(init, appConfig.mcpConfig);
111
+ // Get credentials from config (for tools like publish_hailer_app that need external auth)
112
+ const accountConfig = appConfig.getClientConfig(apiKey);
74
113
  const context = {
75
114
  client,
76
115
  hailer,
77
116
  init,
78
117
  workspaceCache,
79
118
  apiKey,
80
- createdAt: Date.now()
119
+ createdAt: Date.now(),
120
+ email: accountConfig.email,
121
+ password: accountConfig.password,
81
122
  };
82
- // Calculate cache sizes
123
+ // Calculate and log cache sizes
83
124
  const rawInitSize = Buffer.byteLength(JSON.stringify(init), 'utf8');
84
125
  const workspaceCacheSize = Buffer.byteLength(JSON.stringify(workspaceCache), 'utf8');
85
- const totalContextSize = Buffer.byteLength(JSON.stringify({
86
- init,
87
- workspaceCache
88
- }), 'utf8');
89
- // Log cache sizes to console
90
- console.log('\n📊 === WORKSPACE CACHE SIZE ANALYSIS ===');
91
- console.log(`Raw init response: ${rawInitSize.toLocaleString()} bytes (${(rawInitSize / 1024).toFixed(2)} KB)`);
92
- console.log(`Workspace cache: ${workspaceCacheSize.toLocaleString()} bytes (${(workspaceCacheSize / 1024).toFixed(2)} KB)`);
93
- console.log(`Total context: ${totalContextSize.toLocaleString()} bytes (${(totalContextSize / 1024).toFixed(2)} KB)`);
94
- console.log(`Reduction: ${((1 - workspaceCacheSize / rawInitSize) * 100).toFixed(1)}%`);
95
- console.log(`Workflows: ${init.processes?.length || 0}`);
96
- console.log(`Users: ${workspaceCache.users.length}`);
97
- console.log(`Workspaces: ${Object.keys(workspaceCache.allWorkspaces).length}`);
98
- console.log('=====================================\n');
99
- // Cache for future requests
100
- this.cache.set(apiKey, context);
126
+ const totalContextSize = Buffer.byteLength(JSON.stringify({ init, workspaceCache }), 'utf8');
101
127
  logger.info('User context created and cached', {
102
128
  apiKey: apiKey.substring(0, 8) + '...',
103
129
  workflowCount: init.processes?.length || 0,
104
130
  userCount: workspaceCache.users.length,
105
- rawInitBytes: rawInitSize,
106
- workspaceCacheBytes: workspaceCacheSize,
107
- totalContextBytes: totalContextSize,
131
+ workspaceCount: Object.keys(workspaceCache.allWorkspaces).length,
108
132
  rawInitKB: (rawInitSize / 1024).toFixed(2),
109
133
  workspaceCacheKB: (workspaceCacheSize / 1024).toFixed(2),
110
- totalContextKB: (totalContextSize / 1024).toFixed(2)
134
+ totalContextKB: (totalContextSize / 1024).toFixed(2),
135
+ reductionPercent: ((1 - workspaceCacheSize / rawInitSize) * 100).toFixed(1)
111
136
  });
112
137
  return context;
113
138
  }
@@ -1,2 +1,9 @@
1
+ /**
2
+ * Custom fetch function that handles self-signed certificates in development.
3
+ *
4
+ * SECURITY: TLS bypass is only enabled when BOTH conditions are met:
5
+ * 1. NODE_ENV is explicitly set to 'development'
6
+ * 2. URL is a local development URL (localhost, 127.0.0.1, or *.local.gd)
7
+ */
1
8
  export declare const safeFetch: (url: string, options?: RequestInit) => Promise<Response>;
2
9
  //# sourceMappingURL=auth.d.ts.map
package/dist/mcp/auth.js CHANGED
@@ -1,12 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.safeFetch = void 0;
4
- // Custom fetch function that handles self-signed certificates
4
+ /**
5
+ * Custom fetch function that handles self-signed certificates in development.
6
+ *
7
+ * SECURITY: TLS bypass is only enabled when BOTH conditions are met:
8
+ * 1. NODE_ENV is explicitly set to 'development'
9
+ * 2. URL is a local development URL (localhost, 127.0.0.1, or *.local.gd)
10
+ */
5
11
  const safeFetch = async (url, options = {}) => {
6
- const isLocalDev = url.includes('local.gd') || url.includes('localhost');
7
- if (isLocalDev) {
8
- // For Next.js/Node.js environment with self-signed certs
9
- // We need to set the environment variable before making the request
12
+ const isDevelopment = process.env.NODE_ENV === 'development';
13
+ const isLocalUrl = url.startsWith('https://localhost') ||
14
+ url.startsWith('https://127.0.0.1') ||
15
+ url.includes('.local.gd');
16
+ if (isDevelopment && isLocalUrl) {
17
+ // For Node.js environment with self-signed certs in local development
10
18
  const originalValue = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
11
19
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
12
20
  try {
@@ -1,4 +1,5 @@
1
1
  import { Client } from "@hailer/cli";
2
+ import { SignalType } from './utils/types';
2
3
  export interface HailerRestClient {
3
4
  fetch: (url: string, options?: RequestInit) => Promise<Response>;
4
5
  sessionKey: string;
@@ -13,6 +14,8 @@ export interface HailerClient {
13
14
  * This is called after authentication to retrieve the user ID automatically
14
15
  */
15
16
  export declare function getCurrentUserId(client: HailerClient): Promise<string>;
17
+ /** Signal handler callback type */
18
+ export type SignalHandler = (data: Record<string, unknown>) => void;
16
19
  /** Hailer Client for a specific account with listeners and basic handling logic */
17
20
  export declare class HailerClientManager {
18
21
  private host;
@@ -24,8 +27,8 @@ export declare class HailerClientManager {
24
27
  constructor(host: string, username: string, password: string);
25
28
  connect(): Promise<HailerClient>;
26
29
  private setupSignalHandling;
27
- onSignal(eventType: string, handler: (data: any) => void): void;
28
- offSignal(eventType: string, handler: (data: any) => void): void;
30
+ onSignal(eventType: SignalType | string, handler: SignalHandler): void;
31
+ offSignal(eventType: SignalType | string, handler: SignalHandler): void;
29
32
  disconnect(): void;
30
33
  isConnected(): boolean;
31
34
  getClient(): HailerClient | null;
@@ -4,9 +4,33 @@ import { HailerClient } from './hailer-clients';
4
4
  * Signal types that Hailer emits via socket.io
5
5
  */
6
6
  export type HailerSignalType = 'activities.updated' | 'activities.created' | 'activities.deleted' | 'discussion.message' | 'messenger.new' | 'user.joined' | 'user.left' | 'workspace.updated' | 'process.updated' | 'cache.invalidate';
7
+ /** Raw signal from Hailer socket - tuple of [eventType, eventData] */
8
+ export type HailerSocketSignal = [string, Record<string, unknown>];
9
+ /** Message object nested in signal data */
10
+ export interface HailerSignalMessage {
11
+ _id?: string;
12
+ content?: string;
13
+ discussion?: string;
14
+ user?: string;
15
+ userName?: string;
16
+ }
17
+ /** Signal data varies by type - common fields for type narrowing */
18
+ export interface HailerSignalData {
19
+ _id?: string;
20
+ id?: string;
21
+ discussionId?: string;
22
+ msg_id?: string;
23
+ content?: string;
24
+ user?: string;
25
+ userName?: string;
26
+ discussion?: string;
27
+ uid?: string;
28
+ message?: HailerSignalMessage;
29
+ [key: string]: unknown;
30
+ }
7
31
  export interface HailerSignal {
8
32
  type: HailerSignalType;
9
- data: any;
33
+ data: HailerSignalData;
10
34
  timestamp: number;
11
35
  workspaceId?: string;
12
36
  }
@@ -39,7 +63,9 @@ export declare class SignalHandler {
39
63
  getSignalHistory(types?: HailerSignalType[], limit?: number, workspaceId?: string): HailerSignal[];
40
64
  getActiveSubscriptions(): SignalSubscription[];
41
65
  clearHistory(): void;
42
- createMcpSignalTools(server: any): void;
66
+ createMcpSignalTools(server: {
67
+ tool: (...args: unknown[]) => void;
68
+ }): void;
43
69
  }
44
70
  export declare const createSignalHandler: (clients: HailerClient, workspaceCache?: WorkspaceCache) => SignalHandler;
45
71
  //# sourceMappingURL=signal-handler.d.ts.map
@@ -150,9 +150,10 @@ class SignalHandler {
150
150
  });
151
151
  }
152
152
  if (init.users) {
153
- this.workspaceCache.users = init.users;
153
+ // Store raw users in rawInit - the users array is derived from this
154
+ this.workspaceCache.rawInit.users = init.users;
154
155
  logger.info('Users cache refreshed', {
155
- userCount: init.users.length
156
+ userCount: Object.keys(init.users).length
156
157
  });
157
158
  }
158
159
  if (init.network) {
@@ -218,6 +219,7 @@ class SignalHandler {
218
219
  logger.info('Signal history cleared', { previousHistorySize: previousCount });
219
220
  }
220
221
  // Utility methods for MCP integration
222
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- MCP server type from @modelcontextprotocol/sdk
221
223
  createMcpSignalTools(server) {
222
224
  // Add MCP tools for signal subscription management
223
225
  server.tool("subscribe_to_signals", "🔔 Subscribe to real-time Hailer signals - Get notified of activity updates, new messages, and workspace changes", {
@@ -3,9 +3,15 @@
3
3
  *
4
4
  * No singletons, no statics - just clean, explicit tool registration
5
5
  * with per-agent access control via dependency injection.
6
+ *
7
+ * Supports workspace-specific schema overrides for structured outputs:
8
+ * - Load schemas from WORKSPACE_SCHEMAS_PATH environment variable
9
+ * - Schemas contain enum constraints (valid IDs, phase options, etc.)
10
+ * - Enables Claude's strict mode validation with actual workspace values
6
11
  */
7
12
  import { z } from 'zod';
8
13
  import { UserContext } from './UserContextCache';
14
+ import { McpResponse } from './utils/types';
9
15
  /**
10
16
  * Tool groups for access control
11
17
  * READ: Safe read operations (workflows, activities, schemas)
@@ -27,7 +33,7 @@ export interface Tool<TSchema extends z.ZodType = z.ZodType> {
27
33
  group: ToolGroup;
28
34
  description: string;
29
35
  schema: TSchema;
30
- execute: (args: z.infer<TSchema>, context: UserContext) => Promise<any>;
36
+ execute: (args: z.infer<TSchema>, context: UserContext) => Promise<McpResponse>;
31
37
  }
32
38
  /**
33
39
  * Tool definition for MCP protocol (JSON-RPC)
@@ -41,13 +47,33 @@ export interface ToolDefinition {
41
47
  * ToolRegistry - Clean, testable, dependency-injected tool registry
42
48
  *
43
49
  * NO SINGLETON PATTERN - instantiated explicitly and passed through DI
50
+ *
51
+ * Workspace Schema Support:
52
+ * Set WORKSPACE_SCHEMAS_PATH env var to load Claude-compatible schemas
53
+ * with enum constraints for workflow IDs, phase IDs, field options, etc.
44
54
  */
45
55
  export declare class ToolRegistry {
46
56
  private tools;
47
57
  private enableNuclearTools;
58
+ private workspaceSchemas;
59
+ private workspaceSchemasLoaded;
60
+ private static readonly TOOL_TO_SCHEMA_MAP;
48
61
  constructor(options?: {
49
62
  enableNuclearTools?: boolean;
50
63
  });
64
+ /**
65
+ * Load workspace-specific schemas from configured path
66
+ * Schemas provide enum constraints for Claude's strict mode
67
+ */
68
+ private loadWorkspaceSchemas;
69
+ /**
70
+ * Get workspace schema for a workflow (if available)
71
+ */
72
+ getWorkspaceSchema(workflowName: string): any | null;
73
+ /**
74
+ * Check if workspace schemas are available
75
+ */
76
+ hasWorkspaceSchemas(): boolean;
51
77
  /**
52
78
  * Add a tool to the registry
53
79
  */
@@ -66,8 +92,17 @@ export declare class ToolRegistry {
66
92
  /**
67
93
  * Get specific tool schema on-demand
68
94
  * Token-efficient: ~200 tokens per tool vs loading all tools upfront
95
+ *
96
+ * For activity tools (create_activity, update_activity), returns the master
97
+ * workspace schema with ALL valid IDs as enums. This enables Claude's strict
98
+ * mode to constrain outputs to valid values without needing them in the prompt.
99
+ */
100
+ getToolSchema(toolName: string, workflowName?: string): ToolDefinition | null;
101
+ /**
102
+ * Get all workflow-specific tool schemas
103
+ * Returns schemas with enum constraints for each workflow
69
104
  */
70
- getToolSchema(toolName: string): ToolDefinition | null;
105
+ getAllWorkflowSchemas(): Map<string, ToolDefinition>;
71
106
  /**
72
107
  * Execute tool with validation
73
108
  */
@@ -87,7 +122,25 @@ export declare class ToolRegistry {
87
122
  totalTools: number;
88
123
  toolNames: string[];
89
124
  byGroup: Record<ToolGroup, number>;
125
+ workspaceSchemas: {
126
+ loaded: boolean;
127
+ count: number;
128
+ workflows: string[];
129
+ };
90
130
  };
131
+ /**
132
+ * Get all available workspace schemas (for listing available workflows)
133
+ */
134
+ getAvailableWorkflowSchemas(): Array<{
135
+ name: string;
136
+ workflowId: string;
137
+ description: string;
138
+ }>;
139
+ /**
140
+ * Get workflow-specific create tool definition
141
+ * Returns schema with enum constraints for valid phase IDs, field options, etc.
142
+ */
143
+ getWorkflowCreateToolDefinition(workflowName: string): ToolDefinition | null;
91
144
  /**
92
145
  * Convert Zod schema to JSON Schema format (required by MCP protocol)
93
146
  */