@hailer/mcp 1.1.10 → 1.1.11
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/.context-watchdog.json +1 -0
- package/.claude/.session-checked +1 -0
- package/.hailer-mcp-port +1 -0
- package/.mcp.json +2 -2
- package/.opencode/opencode.json +0 -7
- package/CHANGELOG.md +0 -7
- package/dist/CLAUDE.md +370 -0
- package/dist/cli.js +0 -0
- package/dist/lib/discussion-lock.d.ts +42 -0
- package/dist/lib/discussion-lock.js +110 -0
- package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
- package/dist/mcp/tools/bot-config/constants.js +94 -0
- package/dist/mcp/tools/bot-config/core.d.ts +253 -0
- package/dist/mcp/tools/bot-config/core.js +2456 -0
- package/dist/mcp/tools/bot-config/index.d.ts +10 -0
- package/dist/mcp/tools/bot-config/index.js +59 -0
- package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
- package/dist/mcp/tools/bot-config/tools.js +15 -0
- package/dist/mcp/tools/bot-config/types.d.ts +50 -0
- package/dist/mcp/tools/bot-config/types.js +6 -0
- package/dist/mcp/tools/bug-fixer-tools.d.ts +45 -0
- package/dist/mcp/tools/bug-fixer-tools.js +1096 -0
- package/dist/mcp/tools/document.d.ts +11 -0
- package/dist/mcp/tools/document.js +741 -0
- package/dist/mcp/tools/investigate.d.ts +9 -0
- package/dist/mcp/tools/investigate.js +254 -0
- package/dist/mcp-server.js +0 -16
- package/dist/stdio-server.d.ts +14 -0
- package/dist/stdio-server.js +101 -0
- package/inbox/failures.log +1 -0
- package/inbox/usage.jsonl +4 -0
- package/package.json +3 -2
- package/scripts/postinstall.cjs +64 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Investigation Tool - Spawn Claude Code agents to investigate local repos
|
|
3
|
+
*
|
|
4
|
+
* Uses `claude -p` (headless mode, subscription-based) to autonomously
|
|
5
|
+
* explore codebases, search for bugs, and analyze code.
|
|
6
|
+
*/
|
|
7
|
+
import { Tool } from '../tool-registry';
|
|
8
|
+
export declare const investigateRepoTool: Tool;
|
|
9
|
+
//# sourceMappingURL=investigate.d.ts.map
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Investigation Tool - Spawn Claude Code agents to investigate local repos
|
|
4
|
+
*
|
|
5
|
+
* Uses `claude -p` (headless mode, subscription-based) to autonomously
|
|
6
|
+
* explore codebases, search for bugs, and analyze code.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.investigateRepoTool = void 0;
|
|
10
|
+
const zod_1 = require("zod");
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const tool_registry_1 = require("../tool-registry");
|
|
13
|
+
const logger_1 = require("../../lib/logger");
|
|
14
|
+
const config_1 = require("../../config");
|
|
15
|
+
const logger = (0, logger_1.createLogger)({ component: 'investigate-tool' });
|
|
16
|
+
/**
|
|
17
|
+
* Resolve claude CLI path at module load
|
|
18
|
+
*/
|
|
19
|
+
const CLAUDE_BIN = (() => {
|
|
20
|
+
try {
|
|
21
|
+
return (0, child_process_1.execSync)('which claude', { encoding: 'utf-8' }).trim();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return 'claude'; // fallback to PATH lookup
|
|
25
|
+
}
|
|
26
|
+
})();
|
|
27
|
+
/**
|
|
28
|
+
* Parse DEV_AI_REPOS env var into a name->path map.
|
|
29
|
+
* Format: "name1:/path/one,name2:/path/two"
|
|
30
|
+
*/
|
|
31
|
+
function parseRepoPaths() {
|
|
32
|
+
const raw = config_1.environment.DEV_AI_REPOS;
|
|
33
|
+
const map = new Map();
|
|
34
|
+
if (!raw)
|
|
35
|
+
return map;
|
|
36
|
+
for (const entry of raw.split(',')) {
|
|
37
|
+
const colonIdx = entry.indexOf(':');
|
|
38
|
+
if (colonIdx === -1)
|
|
39
|
+
continue;
|
|
40
|
+
const name = entry.slice(0, colonIdx).trim();
|
|
41
|
+
const repoPath = entry.slice(colonIdx + 1).trim();
|
|
42
|
+
if (name && repoPath) {
|
|
43
|
+
map.set(name, repoPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return map;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve a repo name to its local filesystem path.
|
|
50
|
+
*/
|
|
51
|
+
function resolveRepoPath(repoName) {
|
|
52
|
+
const repos = parseRepoPaths();
|
|
53
|
+
return repos.get(repoName) || null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Spawn `claude -p` in a repo directory and return its output.
|
|
57
|
+
*/
|
|
58
|
+
async function spawnInvestigation(prompt, cwd, maxTurns, repoName) {
|
|
59
|
+
const timeout = config_1.environment.DEV_AI_INVESTIGATION_TIMEOUT;
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const proc = (0, child_process_1.spawn)(CLAUDE_BIN, [
|
|
62
|
+
'-p', prompt,
|
|
63
|
+
'--output-format', 'stream-json',
|
|
64
|
+
'--verbose',
|
|
65
|
+
'--allowedTools', 'Read,Glob,Grep,Bash,Task',
|
|
66
|
+
'--max-turns', String(maxTurns),
|
|
67
|
+
], {
|
|
68
|
+
cwd,
|
|
69
|
+
timeout,
|
|
70
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
71
|
+
env: {
|
|
72
|
+
...process.env,
|
|
73
|
+
// Strip MCP vars so investigation agent doesn't connect to our MCP server
|
|
74
|
+
MCP_SERVER_URL: undefined,
|
|
75
|
+
MCP_CLIENT_API_KEY: undefined,
|
|
76
|
+
MCP_CLIENT_ENABLED: undefined,
|
|
77
|
+
CLIENT_CONFIGS: undefined,
|
|
78
|
+
BOT_EMAIL: undefined,
|
|
79
|
+
BOT_PASSWORD: undefined,
|
|
80
|
+
BOT_API_BASE_URL: undefined,
|
|
81
|
+
ANTHROPIC_API_KEY: undefined,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
let lineBuffer = '';
|
|
85
|
+
let resultText = '';
|
|
86
|
+
let stderr = '';
|
|
87
|
+
proc.stdout.on('data', (data) => {
|
|
88
|
+
lineBuffer += data.toString();
|
|
89
|
+
const lines = lineBuffer.split('\n');
|
|
90
|
+
lineBuffer = lines.pop() || ''; // Keep incomplete last line in buffer
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
const trimmed = line.trim();
|
|
93
|
+
if (!trimmed)
|
|
94
|
+
continue;
|
|
95
|
+
try {
|
|
96
|
+
const event = JSON.parse(trimmed);
|
|
97
|
+
if (event.type === 'system' && event.subtype === 'init') {
|
|
98
|
+
logger.debug(`[investigation:${repoName}] Agent initialized | tools: ${(event.tools || []).length} | model: ${event.model || 'unknown'}`);
|
|
99
|
+
}
|
|
100
|
+
else if (event.type === 'assistant' && event.message?.content) {
|
|
101
|
+
for (const block of event.message.content) {
|
|
102
|
+
if (block.type === 'tool_use') {
|
|
103
|
+
const inputStr = JSON.stringify(block.input || {});
|
|
104
|
+
logger.debug(`[investigation:${repoName}] Tool: ${block.name} ${inputStr.length > 200 ? inputStr.slice(0, 200) + '...' : inputStr}`);
|
|
105
|
+
}
|
|
106
|
+
else if (block.type === 'text') {
|
|
107
|
+
const text = (block.text || '').trim();
|
|
108
|
+
if (text) {
|
|
109
|
+
logger.debug(`[investigation:${repoName}] ${text.length > 300 ? text.slice(0, 300) + '...' : text}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (event.type === 'tool') {
|
|
115
|
+
const content = typeof event.content === 'string' ? event.content : JSON.stringify(event.content || '');
|
|
116
|
+
logger.debug(`[investigation:${repoName}] Tool result (${event.tool_name}): ${content.length > 300 ? content.slice(0, 300) + '...' : content}`);
|
|
117
|
+
}
|
|
118
|
+
else if (event.type === 'result') {
|
|
119
|
+
resultText = event.result || '';
|
|
120
|
+
const cost = event.total_cost_usd ? `$${event.total_cost_usd.toFixed(4)}` : 'unknown';
|
|
121
|
+
const duration = event.duration_ms ? `${(event.duration_ms / 1000).toFixed(1)}s` : 'unknown';
|
|
122
|
+
logger.info(`[investigation:${repoName}] Complete | ${resultText.length} chars | ${duration} | cost: ${cost}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Non-JSON line
|
|
127
|
+
if (trimmed.length > 0) {
|
|
128
|
+
logger.debug(`[investigation:${repoName}] ${trimmed.length > 300 ? trimmed.slice(0, 300) + '...' : trimmed}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
proc.stderr.on('data', (data) => {
|
|
134
|
+
const chunk = data.toString();
|
|
135
|
+
stderr += chunk;
|
|
136
|
+
const trimmed = chunk.trim();
|
|
137
|
+
if (trimmed) {
|
|
138
|
+
logger.warn(`[investigation:${repoName}] ${trimmed.length > 500 ? trimmed.slice(0, 500) + '...' : trimmed}`);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
proc.on('error', (err) => {
|
|
142
|
+
reject(new Error(`Failed to spawn claude: ${err.message}`));
|
|
143
|
+
});
|
|
144
|
+
proc.on('close', (code) => {
|
|
145
|
+
// Process any remaining data in lineBuffer
|
|
146
|
+
if (lineBuffer.trim()) {
|
|
147
|
+
try {
|
|
148
|
+
const event = JSON.parse(lineBuffer.trim());
|
|
149
|
+
if (event.type === 'result') {
|
|
150
|
+
resultText = event.result || '';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// ignore
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (code === 0) {
|
|
158
|
+
resolve(resultText || 'Investigation completed but returned no structured result.');
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const errDetail = stderr.trim() || `exit code ${code}`;
|
|
162
|
+
reject(new Error(`Investigation failed: ${errDetail}`));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const investigateRepoSchema = zod_1.z.object({
|
|
168
|
+
repoName: zod_1.z.string().describe('Target repo name (must match a configured repo). Available repos depend on DEV_AI_REPOS config.'),
|
|
169
|
+
prompt: zod_1.z.string().describe('What to investigate - bug description, search query, code question, etc.'),
|
|
170
|
+
maxTurns: zod_1.z.number().optional().default(30).describe('Max investigation depth (number of agentic turns). Default: 30.'),
|
|
171
|
+
});
|
|
172
|
+
exports.investigateRepoTool = {
|
|
173
|
+
name: 'investigate_repo',
|
|
174
|
+
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
175
|
+
description: 'Spawn a Claude Code agent to investigate code in a local repo. ' +
|
|
176
|
+
'Uses claude -p (headless mode, subscription-based). ' +
|
|
177
|
+
'The agent can explore files, search code, analyze the codebase autonomously, ' +
|
|
178
|
+
'and spawn investigation teams for parallel exploration of complex issues. ' +
|
|
179
|
+
'Returns the investigation findings as text.',
|
|
180
|
+
schema: investigateRepoSchema,
|
|
181
|
+
async execute(args, _context) {
|
|
182
|
+
const repos = parseRepoPaths();
|
|
183
|
+
// If no repos configured, return helpful error
|
|
184
|
+
if (repos.size === 0) {
|
|
185
|
+
return {
|
|
186
|
+
content: [{
|
|
187
|
+
type: 'text',
|
|
188
|
+
text: 'No repos configured. Set DEV_AI_REPOS in .env.local (format: "name:/path,name2:/path2").',
|
|
189
|
+
}],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const repoPath = resolveRepoPath(args.repoName);
|
|
193
|
+
if (!repoPath) {
|
|
194
|
+
const available = Array.from(repos.keys()).join(', ');
|
|
195
|
+
return {
|
|
196
|
+
content: [{
|
|
197
|
+
type: 'text',
|
|
198
|
+
text: `Unknown repo: "${args.repoName}". Available repos: ${available}`,
|
|
199
|
+
}],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const enhancedPrompt = `You are a lead investigator. Use parallel agents to explore the codebase efficiently.
|
|
203
|
+
|
|
204
|
+
HOW TO USE PARALLEL AGENTS:
|
|
205
|
+
- Call multiple Task tools in a SINGLE response to run them in parallel
|
|
206
|
+
- Each Task call blocks until that agent finishes and returns its findings directly to you
|
|
207
|
+
- Use subagent_type: "Explore" with model: "haiku" — these are fast, read-only codebase search agents
|
|
208
|
+
- Do NOT use TeamCreate, SendMessage, or TaskCreate — just call Task directly
|
|
209
|
+
|
|
210
|
+
APPROACH:
|
|
211
|
+
1. In your FIRST response, call 2-3 Task tools simultaneously, each with a focused search prompt
|
|
212
|
+
2. You will receive all their findings in the next turn
|
|
213
|
+
3. If needed, do additional targeted searches yourself with Grep/Read
|
|
214
|
+
4. Write your FINAL REPORT
|
|
215
|
+
|
|
216
|
+
REPORT FORMAT:
|
|
217
|
+
- Root cause of the issue
|
|
218
|
+
- Affected files with paths and line numbers
|
|
219
|
+
- Suggested fix with specific code changes
|
|
220
|
+
|
|
221
|
+
INVESTIGATION TASK:
|
|
222
|
+
${args.prompt}`;
|
|
223
|
+
logger.info('Starting investigation', {
|
|
224
|
+
repo: args.repoName,
|
|
225
|
+
repoPath,
|
|
226
|
+
maxTurns: args.maxTurns,
|
|
227
|
+
promptLength: enhancedPrompt.length,
|
|
228
|
+
});
|
|
229
|
+
try {
|
|
230
|
+
const result = await spawnInvestigation(enhancedPrompt, repoPath, args.maxTurns, args.repoName);
|
|
231
|
+
logger.info('Investigation completed', {
|
|
232
|
+
repo: args.repoName,
|
|
233
|
+
resultLength: result.length,
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
content: [{
|
|
237
|
+
type: 'text',
|
|
238
|
+
text: result || 'Investigation completed but returned no output.',
|
|
239
|
+
}],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
244
|
+
logger.error('Investigation failed', { repo: args.repoName, error: errMsg });
|
|
245
|
+
return {
|
|
246
|
+
content: [{
|
|
247
|
+
type: 'text',
|
|
248
|
+
text: `Investigation failed: ${errMsg}`,
|
|
249
|
+
}],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
//# sourceMappingURL=investigate.js.map
|
package/dist/mcp-server.js
CHANGED
|
@@ -495,22 +495,6 @@ class MCPServerService {
|
|
|
495
495
|
catch (err) {
|
|
496
496
|
this.logger.debug('Could not update .mcp.json', { error: err.message });
|
|
497
497
|
}
|
|
498
|
-
// Update .opencode/opencode.json with the actual port so OpenCode connects to the right server
|
|
499
|
-
const opencodeJsonPath = path.join(process.cwd(), '.opencode', 'opencode.json');
|
|
500
|
-
try {
|
|
501
|
-
if (fs.existsSync(opencodeJsonPath)) {
|
|
502
|
-
const opencodeJson = JSON.parse(fs.readFileSync(opencodeJsonPath, 'utf-8'));
|
|
503
|
-
const hailerMcp = opencodeJson?.mcp?.hailer;
|
|
504
|
-
if (hailerMcp?.url && typeof hailerMcp.url === 'string' && hailerMcp.url.includes('localhost:')) {
|
|
505
|
-
hailerMcp.url = hailerMcp.url.replace(/localhost:\d+/, `localhost:${port}`);
|
|
506
|
-
fs.writeFileSync(opencodeJsonPath, JSON.stringify(opencodeJson, null, 2) + '\n');
|
|
507
|
-
this.logger.debug('.opencode/opencode.json updated with port', { port });
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
catch (err) {
|
|
512
|
-
this.logger.debug('Could not update .opencode/opencode.json', { error: err.message });
|
|
513
|
-
}
|
|
514
498
|
resolve();
|
|
515
499
|
});
|
|
516
500
|
this.server.on('error', (error) => {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdio MCP Server for Claude Desktop
|
|
3
|
+
*
|
|
4
|
+
* Uses @modelcontextprotocol/sdk with StdioServerTransport for
|
|
5
|
+
* JSON-RPC communication via stdin/stdout.
|
|
6
|
+
*
|
|
7
|
+
* This is the entry point when running as Claude Desktop MCP connector.
|
|
8
|
+
*/
|
|
9
|
+
import { ToolRegistry } from './mcp/tool-registry';
|
|
10
|
+
/**
|
|
11
|
+
* Start the stdio MCP server with all registered tools
|
|
12
|
+
*/
|
|
13
|
+
export declare function startStdioServer(toolRegistry: ToolRegistry): Promise<void>;
|
|
14
|
+
//# sourceMappingURL=stdio-server.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Stdio MCP Server for Claude Desktop
|
|
4
|
+
*
|
|
5
|
+
* Uses @modelcontextprotocol/sdk with StdioServerTransport for
|
|
6
|
+
* JSON-RPC communication via stdin/stdout.
|
|
7
|
+
*
|
|
8
|
+
* This is the entry point when running as Claude Desktop MCP connector.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.startStdioServer = startStdioServer;
|
|
12
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
13
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
14
|
+
const logger_1 = require("./lib/logger");
|
|
15
|
+
const tool_registry_1 = require("./mcp/tool-registry");
|
|
16
|
+
const UserContextCache_1 = require("./mcp/UserContextCache");
|
|
17
|
+
const config_1 = require("./config");
|
|
18
|
+
const logger = (0, logger_1.createLogger)({ component: 'stdio-server' });
|
|
19
|
+
/**
|
|
20
|
+
* Start the stdio MCP server with all registered tools
|
|
21
|
+
*/
|
|
22
|
+
async function startStdioServer(toolRegistry) {
|
|
23
|
+
logger.info('Starting stdio MCP server for Claude Desktop');
|
|
24
|
+
// Get API key from environment (set in Claude Desktop config)
|
|
25
|
+
const apiKey = process.env.MCP_CLIENT_API_KEY;
|
|
26
|
+
if (!apiKey) {
|
|
27
|
+
logger.error('MCP_CLIENT_API_KEY environment variable is required for stdio mode');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
// Create MCP server
|
|
31
|
+
const server = new mcp_js_1.McpServer({
|
|
32
|
+
name: 'hailer-mcp-server',
|
|
33
|
+
version: '1.0.0',
|
|
34
|
+
});
|
|
35
|
+
// Get tool definitions (filtered by allowed groups, excluding NUCLEAR unless enabled)
|
|
36
|
+
const allowedGroups = config_1.environment.ENABLE_NUCLEAR_TOOLS
|
|
37
|
+
? [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE, tool_registry_1.ToolGroup.PLAYGROUND, tool_registry_1.ToolGroup.NUCLEAR]
|
|
38
|
+
: [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE, tool_registry_1.ToolGroup.PLAYGROUND];
|
|
39
|
+
const toolDefinitions = toolRegistry.getToolDefinitions({ allowedGroups });
|
|
40
|
+
logger.info('Registering tools for stdio server', {
|
|
41
|
+
toolCount: toolDefinitions.length,
|
|
42
|
+
allowedGroups,
|
|
43
|
+
});
|
|
44
|
+
// Register each tool with the MCP server
|
|
45
|
+
for (const toolDef of toolDefinitions) {
|
|
46
|
+
server.tool(toolDef.name, toolDef.description, toolDef.inputSchema.properties ? toolDef.inputSchema : { type: 'object', properties: {} }, async (args) => {
|
|
47
|
+
const startTime = Date.now();
|
|
48
|
+
logger.debug('Tool call received', { toolName: toolDef.name, args });
|
|
49
|
+
try {
|
|
50
|
+
// Get user context (cached)
|
|
51
|
+
const userContext = await UserContextCache_1.UserContextCache.getContext(apiKey);
|
|
52
|
+
// Execute the tool
|
|
53
|
+
const result = await toolRegistry.executeTool(toolDef.name, args, userContext);
|
|
54
|
+
const duration = Date.now() - startTime;
|
|
55
|
+
logger.info('Tool call completed', { toolName: toolDef.name, duration });
|
|
56
|
+
// Handle different result formats
|
|
57
|
+
if (result && typeof result === 'object' && 'content' in result) {
|
|
58
|
+
// Result is already in MCP format { content: [...] }
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
// Wrap result in MCP format
|
|
62
|
+
return {
|
|
63
|
+
content: [{
|
|
64
|
+
type: 'text',
|
|
65
|
+
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
|
|
66
|
+
}]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
const duration = Date.now() - startTime;
|
|
71
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
72
|
+
logger.error('Tool call failed', error, { toolName: toolDef.name, duration });
|
|
73
|
+
return {
|
|
74
|
+
content: [{
|
|
75
|
+
type: 'text',
|
|
76
|
+
text: `Error: ${errorMessage}`
|
|
77
|
+
}],
|
|
78
|
+
isError: true
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Create stdio transport
|
|
84
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
85
|
+
// Handle graceful shutdown
|
|
86
|
+
process.on('SIGINT', async () => {
|
|
87
|
+
logger.info('Received SIGINT, shutting down stdio server');
|
|
88
|
+
await server.close();
|
|
89
|
+
process.exit(0);
|
|
90
|
+
});
|
|
91
|
+
process.on('SIGTERM', async () => {
|
|
92
|
+
logger.info('Received SIGTERM, shutting down stdio server');
|
|
93
|
+
await server.close();
|
|
94
|
+
process.exit(0);
|
|
95
|
+
});
|
|
96
|
+
// Connect and start serving
|
|
97
|
+
logger.info('Connecting stdio transport');
|
|
98
|
+
await server.connect(transport);
|
|
99
|
+
logger.info('Stdio MCP server is running');
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=stdio-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[2026-02-16T05:32:29.204Z] agent-giuseppe-app-builder | skill | hailer-mcp | \"Field '{fieldLabel}' expects Unix ms timestamp (number), got: '{value}'. Convert the date to milliseconds first.\"\n```\n\n**Important:**\n- Read th
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{"ts":"2026-02-13T08:23:26.264Z","agent":"general-purpose","status":"unknown","project":"hailer-mcp","description":"Convert new agents to OpenCode"}
|
|
2
|
+
{"ts":"2026-02-16T05:26:20.980Z","agent":"agent-svetlana-code-review","status":"unknown","project":"hailer-mcp","description":"Check bot date handling code"}
|
|
3
|
+
{"ts":"2026-02-16T05:32:29.203Z","agent":"agent-giuseppe-app-builder","status":"error","project":"hailer-mcp","description":"Add date validation to bot+tools"}
|
|
4
|
+
{"ts":"2026-02-24T12:50:03.634Z","agent":"agent-gunther-mcp-tools","status":"unknown","project":"hailer-mcp","description":"Find removed MCP tools"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hailer/mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
4
4
|
"config": {
|
|
5
5
|
"docker": {
|
|
6
6
|
"registry": "registry.gitlab.com/hailer-repos/hailer-mcp"
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"release:patch": "npm version patch -m 'chore: release v%s' && git push && git push --tags && npm run build && npm publish --access public",
|
|
26
26
|
"release:minor": "npm version minor -m 'chore: release v%s' && git push && git push --tags && npm run build && npm publish --access public",
|
|
27
27
|
"release:major": "npm version major -m 'chore: release v%s' && git push && git push --tags && npm run build && npm publish --access public",
|
|
28
|
-
"seed-config": "tsx src/commands/seed-config.ts"
|
|
28
|
+
"seed-config": "tsx src/commands/seed-config.ts",
|
|
29
|
+
"postinstall": "node scripts/postinstall.cjs"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@anthropic-ai/sdk": "^0.54.0",
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* postinstall: Copies .claude/ (agents, skills, hooks) to the project root.
|
|
4
|
+
* Runs automatically after `npm install @hailer/mcp`.
|
|
5
|
+
* Skips when installing in the hailer-mcp repo itself (development).
|
|
6
|
+
*/
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
// Find the project root by walking up from node_modules/@hailer/mcp/
|
|
11
|
+
function findProjectRoot() {
|
|
12
|
+
let dir = __dirname;
|
|
13
|
+
// Walk up until we find a package.json that isn't ours
|
|
14
|
+
for (let i = 0; i < 10; i++) {
|
|
15
|
+
dir = path.dirname(dir);
|
|
16
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
17
|
+
if (fs.existsSync(pkgPath)) {
|
|
18
|
+
try {
|
|
19
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
20
|
+
// Skip if this is our own package.json
|
|
21
|
+
if (pkg.name === '@hailer/mcp') continue;
|
|
22
|
+
return dir;
|
|
23
|
+
} catch { continue; }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function copyDir(src, dest) {
|
|
30
|
+
if (!fs.existsSync(src)) return;
|
|
31
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
32
|
+
|
|
33
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
34
|
+
const srcPath = path.join(src, entry.name);
|
|
35
|
+
const destPath = path.join(dest, entry.name);
|
|
36
|
+
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
copyDir(srcPath, destPath);
|
|
39
|
+
} else {
|
|
40
|
+
// Don't overwrite user's local settings
|
|
41
|
+
if (entry.name === 'settings.local.json' && fs.existsSync(destPath)) continue;
|
|
42
|
+
fs.copyFileSync(srcPath, destPath);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const projectRoot = findProjectRoot();
|
|
48
|
+
|
|
49
|
+
// Skip if we can't find a project root or if running in dev (our own repo)
|
|
50
|
+
if (!projectRoot) {
|
|
51
|
+
console.log('@hailer/mcp: skipping agent install (no project root found)');
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const src = path.join(__dirname, '..', '.claude');
|
|
56
|
+
const dest = path.join(projectRoot, '.claude');
|
|
57
|
+
|
|
58
|
+
if (!fs.existsSync(src)) {
|
|
59
|
+
console.log('@hailer/mcp: no .claude/ directory in package, skipping');
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
copyDir(src, dest);
|
|
64
|
+
console.log(`@hailer/mcp: agents installed to ${dest}`);
|