@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.
- package/.claude/agents/ada.md +127 -0
- package/.claude/agents/agent-builder.md +151 -0
- package/.claude/agents/alejandro.md +66 -0
- package/.claude/agents/bjorn.md +305 -0
- package/.claude/agents/dmitri.md +61 -0
- package/.claude/agents/giuseppe.md +66 -0
- package/.claude/agents/gunther.md +355 -0
- package/.claude/agents/helga.md +68 -0
- package/.claude/agents/ingrid.md +108 -0
- package/.claude/agents/kenji.md +58 -0
- package/.claude/agents/svetlana.md +394 -0
- package/.claude/agents/viktor.md +63 -0
- package/.claude/agents/yevgeni.md +60 -0
- package/.claude/hooks/agent-failure-detector.cjs +286 -0
- package/.claude/hooks/app-edit-guard.cjs +462 -0
- package/.claude/hooks/interactive-mode.cjs +59 -0
- package/.claude/hooks/mcp-server-guard.cjs +92 -0
- package/.claude/hooks/post-scaffold-hook.cjs +31 -0
- package/.claude/hooks/sdk-delete-guard.cjs +2 -0
- package/.claude/hooks/src-edit-guard.cjs +208 -0
- package/.claude/settings.json +47 -2
- package/.claude/skills/insight-join-patterns/SKILL.md +209 -0
- package/.env.example +13 -1
- package/CLAUDE.md +135 -0
- package/dist/app.js +4 -3
- package/dist/cli.js +0 -0
- package/dist/client/adaptive-documentation-bot.d.ts +0 -2
- package/dist/client/adaptive-documentation-bot.js +5 -16
- package/dist/client/message-processor.js +5 -0
- package/dist/client/providers/anthropic-provider.js +21 -7
- package/dist/mcp/UserContextCache.d.ts +14 -0
- package/dist/mcp/UserContextCache.js +49 -24
- package/dist/mcp/auth.d.ts +7 -0
- package/dist/mcp/auth.js +13 -5
- package/dist/mcp/hailer-clients.d.ts +5 -2
- package/dist/mcp/signal-handler.d.ts +28 -2
- package/dist/mcp/signal-handler.js +4 -2
- package/dist/mcp/tool-registry.d.ts +55 -2
- package/dist/mcp/tool-registry.js +197 -2
- package/dist/mcp/tools/app-core.d.ts +15 -0
- package/dist/mcp/tools/app-core.js +609 -0
- package/dist/mcp/tools/app-marketplace.d.ts +21 -0
- package/dist/mcp/tools/app-marketplace.js +1284 -0
- package/dist/mcp/tools/app-member.d.ts +11 -0
- package/dist/mcp/tools/app-member.js +258 -0
- package/dist/mcp/tools/app-scaffold.d.ts +11 -0
- package/dist/mcp/tools/app-scaffold.js +743 -0
- package/dist/mcp/tools/app.d.ts +13 -22
- package/dist/mcp/tools/app.js +17 -2466
- package/dist/mcp/tools/file.js +6 -6
- package/dist/mcp/tools/insight.d.ts +1 -0
- package/dist/mcp/tools/insight.js +203 -64
- package/dist/mcp/tools/user.js +3 -9
- package/dist/mcp/tools/workflow.js +49 -38
- package/dist/mcp/utils/hailer-api-client.js +4 -13
- package/dist/mcp/utils/tool-helpers.d.ts +102 -0
- package/dist/mcp/utils/tool-helpers.js +179 -0
- package/dist/mcp/utils/types.d.ts +6 -0
- package/dist/mcp/workspace-cache.d.ts +5 -5
- package/dist/mcp/workspace-cache.js +4 -3
- package/package.json +1 -1
- package/.claude/hooks/PreToolUse.sh +0 -52
- package/.claude/hooks/prompt-skill-loader.cjs +0 -553
- package/.claude/hooks/skill-loader.cjs +0 -142
- package/.claude/settings.local.json +0 -49
- package/.claude/skills/MCP-add-app-member-skill/SKILL.md +0 -977
- package/.claude/skills/MCP-build-data-app-skill/SKILL.md +0 -372
- package/.claude/skills/MCP-create-app-skill/SKILL.md +0 -1101
- package/.claude/skills/MCP-create-insight-skill/SKILL.md +0 -1317
- package/.claude/skills/MCP-get-insight-data-skill/SKILL.md +0 -1053
- package/.claude/skills/MCP-insight-api/SKILL.md +0 -185
- package/.claude/skills/MCP-insight-api/references/insight-endpoints.md +0 -514
- package/.claude/skills/MCP-install-workflow-skill/SKILL.md +0 -1056
- package/.claude/skills/MCP-list-apps-skill/SKILL.md +0 -1010
- package/.claude/skills/MCP-list-workflows-minimal-skill/SKILL.md +0 -992
- package/.claude/skills/MCP-local-first-skill/SKILL.md +0 -570
- package/.claude/skills/MCP-populate-workflow-data-skill/SKILL.md +0 -395
- package/.claude/skills/MCP-preview-insight-skill/SKILL.md +0 -1290
- package/.claude/skills/MCP-publish-hailer-app-skill/SKILL.md +0 -453
- package/.claude/skills/MCP-publish-template-skill/SKILL.md +0 -278
- package/.claude/skills/MCP-remove-app-member-skill/SKILL.md +0 -671
- package/.claude/skills/MCP-remove-app-skill/SKILL.md +0 -985
- package/.claude/skills/MCP-remove-insight-skill/SKILL.md +0 -1011
- package/.claude/skills/MCP-remove-workflow-skill/SKILL.md +0 -920
- package/.claude/skills/MCP-scaffold-hailer-app-skill/SKILL.md +0 -1314
- package/.claude/skills/MCP-update-app-skill/SKILL.md +0 -970
- package/.claude/skills/MCP-update-workflow-field-skill/SKILL.md +0 -1098
- package/.claude/skills/SDK-create-function-field-skill/SKILL.md +0 -313
- package/.claude/skills/SDK-generate-skill/SKILL.md +0 -223
- package/.claude/skills/SDK-init-skill/SKILL.md +0 -177
- package/.claude/skills/SDK-workspace-setup-skill/SKILL.md +0 -605
- package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -435
- package/.claude/skills/activity-api/SKILL.md +0 -96
- package/.claude/skills/activity-api/references/activity-endpoints.md +0 -845
- package/.claude/skills/agent-building/SKILL.md +0 -243
- package/.claude/skills/agent-building/references/architecture-patterns.md +0 -446
- package/.claude/skills/agent-building/references/code-examples.md +0 -587
- package/.claude/skills/agent-building/references/implementation-guide.md +0 -619
- package/.claude/skills/app-api/SKILL.md +0 -219
- package/.claude/skills/app-api/references/app-endpoints.md +0 -759
- package/.claude/skills/building-hailer-apps-skill/SKILL.md +0 -813
- package/.claude/skills/hailer-api/SKILL.md +0 -283
- package/.claude/skills/hailer-api/references/activities.md +0 -620
- package/.claude/skills/hailer-api/references/authentication.md +0 -216
- package/.claude/skills/hailer-api/references/datasets.md +0 -437
- package/.claude/skills/hailer-api/references/files.md +0 -301
- package/.claude/skills/hailer-api/references/insights.md +0 -469
- package/.claude/skills/hailer-api/references/workflows.md +0 -720
- package/.claude/skills/hailer-api/references/workspaces-users.md +0 -445
- package/.claude/skills/hailer-app-builder/SKILL.md +0 -340
- package/.claude/skills/mcp-tools/SKILL.md +0 -419
- package/.claude/skills/mcp-tools/references/api-endpoints.md +0 -499
- package/.claude/skills/mcp-tools/references/data-structures.md +0 -554
- package/.claude/skills/mcp-tools/references/implementation-patterns.md +0 -717
- package/.claude/skills/skill-testing/README.md +0 -137
- package/.claude/skills/skill-testing/SKILL.md +0 -348
- package/.claude/skills/skill-testing/references/test-patterns.md +0 -705
- package/.claude/skills/skill-testing/references/testing-guide.md +0 -603
- package/.claude/skills/skill-testing/references/validation-checklist.md +0 -537
- package/.claude/skills/spawn-app-builder/SKILL.md +0 -366
- package/.claude/skills/tool-builder/SKILL.md +0 -328
- 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.
|
|
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:
|
|
462
|
-
wouldCreateSkill:
|
|
463
|
-
wouldUpdateSkill:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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)();
|
|
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
|
-
|
|
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
|
}
|
package/dist/mcp/auth.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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:
|
|
28
|
-
offSignal(eventType: string, handler:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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<
|
|
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
|
-
|
|
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
|
*/
|