@hailer/mcp 1.0.24 → 1.0.26
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/dist/cli.d.ts +8 -1
- package/dist/cli.js +266 -1
- package/dist/mcp/tool-registry.d.ts +15 -0
- package/dist/mcp/tool-registry.js +23 -0
- package/dist/stdio-server.js +22 -9
- package/package.json +2 -1
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,270 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Hailer MCP CLI
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* setup - Interactive setup for Claude Desktop
|
|
8
|
+
* (none) - Start MCP server (auto-detects stdio/http mode)
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
3
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
require("
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const readline = __importStar(require("readline"));
|
|
48
|
+
const CLAUDE_CONFIG_DIR = path.join(os.homedir(), 'Library', 'Application Support', 'Claude');
|
|
49
|
+
const CLAUDE_CONFIG_FILE = path.join(CLAUDE_CONFIG_DIR, 'claude_desktop_config.json');
|
|
50
|
+
/**
|
|
51
|
+
* Prompt user for input
|
|
52
|
+
*/
|
|
53
|
+
function prompt(question, isPassword = false) {
|
|
54
|
+
const rl = readline.createInterface({
|
|
55
|
+
input: process.stdin,
|
|
56
|
+
output: process.stdout,
|
|
57
|
+
});
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
if (isPassword) {
|
|
60
|
+
// For password, we need to handle it differently
|
|
61
|
+
process.stdout.write(question);
|
|
62
|
+
let password = '';
|
|
63
|
+
const stdin = process.stdin;
|
|
64
|
+
const wasRaw = stdin.isRaw;
|
|
65
|
+
if (stdin.isTTY) {
|
|
66
|
+
stdin.setRawMode(true);
|
|
67
|
+
}
|
|
68
|
+
stdin.resume();
|
|
69
|
+
stdin.setEncoding('utf8');
|
|
70
|
+
const onData = (char) => {
|
|
71
|
+
const charCode = char.charCodeAt(0);
|
|
72
|
+
if (charCode === 13 || charCode === 10) {
|
|
73
|
+
// Enter
|
|
74
|
+
stdin.removeListener('data', onData);
|
|
75
|
+
if (stdin.isTTY) {
|
|
76
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
77
|
+
}
|
|
78
|
+
process.stdout.write('\n');
|
|
79
|
+
rl.close();
|
|
80
|
+
resolve(password);
|
|
81
|
+
}
|
|
82
|
+
else if (charCode === 3) {
|
|
83
|
+
// Ctrl+C
|
|
84
|
+
process.exit();
|
|
85
|
+
}
|
|
86
|
+
else if (charCode === 127 || charCode === 8) {
|
|
87
|
+
// Backspace
|
|
88
|
+
if (password.length > 0) {
|
|
89
|
+
password = password.slice(0, -1);
|
|
90
|
+
process.stdout.write('\b \b');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
password += char;
|
|
95
|
+
process.stdout.write('*');
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
stdin.on('data', onData);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
rl.question(question, (answer) => {
|
|
102
|
+
rl.close();
|
|
103
|
+
resolve(answer.trim());
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Find Node.js executable path
|
|
110
|
+
*/
|
|
111
|
+
function findNodePath() {
|
|
112
|
+
// Use current Node.js path (the one running this script)
|
|
113
|
+
return process.execPath;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Find the hailer-mcp package location
|
|
117
|
+
*/
|
|
118
|
+
function findPackagePath() {
|
|
119
|
+
// When installed globally via npm, __dirname will be in the package
|
|
120
|
+
// When running from source, we're in src/
|
|
121
|
+
const distPath = path.resolve(__dirname, '..', 'dist', 'app.js');
|
|
122
|
+
const srcPath = path.resolve(__dirname, 'app.js');
|
|
123
|
+
if (fs.existsSync(distPath)) {
|
|
124
|
+
return distPath;
|
|
125
|
+
}
|
|
126
|
+
if (fs.existsSync(srcPath)) {
|
|
127
|
+
return srcPath;
|
|
128
|
+
}
|
|
129
|
+
// For global npm install, find in node_modules
|
|
130
|
+
try {
|
|
131
|
+
const globalPath = require.resolve('@hailer/mcp/dist/app.js');
|
|
132
|
+
return globalPath;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Fallback to current directory
|
|
136
|
+
return path.resolve(process.cwd(), 'dist', 'app.js');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Setup command - configure Claude Desktop
|
|
141
|
+
*/
|
|
142
|
+
async function runSetup() {
|
|
143
|
+
console.log('\n🔧 Hailer MCP Setup for Claude Desktop\n');
|
|
144
|
+
console.log('This will configure Claude Desktop to use Hailer MCP tools.\n');
|
|
145
|
+
// Check if macOS
|
|
146
|
+
if (process.platform !== 'darwin') {
|
|
147
|
+
console.log('⚠️ This setup is designed for macOS. For other platforms, please configure manually.');
|
|
148
|
+
console.log(' See: https://github.com/hailer/hailer-mcp#claude-desktop-setup\n');
|
|
149
|
+
}
|
|
150
|
+
// Get credentials
|
|
151
|
+
const email = await prompt('Hailer email: ');
|
|
152
|
+
if (!email) {
|
|
153
|
+
console.log('❌ Email is required');
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
const password = await prompt('Hailer password: ', true);
|
|
157
|
+
if (!password) {
|
|
158
|
+
console.log('❌ Password is required');
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
// Get API URL (optional)
|
|
162
|
+
const apiUrlInput = await prompt('Hailer API URL (press Enter for default https://api.hailer.com): ');
|
|
163
|
+
const apiUrl = apiUrlInput || 'https://api.hailer.com';
|
|
164
|
+
// Find paths
|
|
165
|
+
const nodePath = findNodePath();
|
|
166
|
+
const appPath = findPackagePath();
|
|
167
|
+
console.log('\n📍 Detected paths:');
|
|
168
|
+
console.log(` Node.js: ${nodePath}`);
|
|
169
|
+
console.log(` Hailer MCP: ${appPath}`);
|
|
170
|
+
// Check if app.js exists
|
|
171
|
+
if (!fs.existsSync(appPath)) {
|
|
172
|
+
console.log(`\n❌ Could not find app.js at ${appPath}`);
|
|
173
|
+
console.log(' Please run "npm run build" first if using from source.');
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
// Create config
|
|
177
|
+
const hailerConfig = {
|
|
178
|
+
command: nodePath,
|
|
179
|
+
args: [appPath],
|
|
180
|
+
env: {
|
|
181
|
+
MCP_CLIENT_API_KEY: 'claude-desktop',
|
|
182
|
+
HAILER_EMAIL: email,
|
|
183
|
+
HAILER_PASSWORD: password,
|
|
184
|
+
HAILER_API_URL: apiUrl,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
// Read existing config or create new
|
|
188
|
+
let config = { mcpServers: {} };
|
|
189
|
+
if (fs.existsSync(CLAUDE_CONFIG_FILE)) {
|
|
190
|
+
try {
|
|
191
|
+
const existingContent = fs.readFileSync(CLAUDE_CONFIG_FILE, 'utf-8');
|
|
192
|
+
config = JSON.parse(existingContent);
|
|
193
|
+
if (!config.mcpServers) {
|
|
194
|
+
config.mcpServers = {};
|
|
195
|
+
}
|
|
196
|
+
console.log('\n📄 Found existing Claude Desktop config');
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
console.log('\n⚠️ Could not parse existing config, creating new one');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Check if hailer already configured
|
|
203
|
+
if (config.mcpServers.hailer) {
|
|
204
|
+
const overwrite = await prompt('\n⚠️ Hailer MCP is already configured. Overwrite? (y/N): ');
|
|
205
|
+
if (overwrite.toLowerCase() !== 'y') {
|
|
206
|
+
console.log('Setup cancelled.');
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Add hailer config
|
|
211
|
+
config.mcpServers.hailer = hailerConfig;
|
|
212
|
+
// Ensure directory exists
|
|
213
|
+
if (!fs.existsSync(CLAUDE_CONFIG_DIR)) {
|
|
214
|
+
fs.mkdirSync(CLAUDE_CONFIG_DIR, { recursive: true });
|
|
215
|
+
console.log(`\n📁 Created directory: ${CLAUDE_CONFIG_DIR}`);
|
|
216
|
+
}
|
|
217
|
+
// Write config
|
|
218
|
+
fs.writeFileSync(CLAUDE_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
219
|
+
console.log('\n✅ Configuration saved!');
|
|
220
|
+
console.log(` File: ${CLAUDE_CONFIG_FILE}`);
|
|
221
|
+
console.log('\n🚀 Next steps:');
|
|
222
|
+
console.log(' 1. Quit Claude Desktop completely (Cmd+Q)');
|
|
223
|
+
console.log(' 2. Reopen Claude Desktop');
|
|
224
|
+
console.log(' 3. Look for "hailer" in the MCP tools list');
|
|
225
|
+
console.log('\n💡 Tip: Use a dedicated bot account, not your personal account.\n');
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Main entry point
|
|
229
|
+
*/
|
|
230
|
+
async function main() {
|
|
231
|
+
const args = process.argv.slice(2);
|
|
232
|
+
const command = args[0];
|
|
233
|
+
switch (command) {
|
|
234
|
+
case 'setup':
|
|
235
|
+
await runSetup();
|
|
236
|
+
break;
|
|
237
|
+
case 'help':
|
|
238
|
+
case '--help':
|
|
239
|
+
case '-h':
|
|
240
|
+
console.log(`
|
|
241
|
+
Hailer MCP - Model Context Protocol server for Hailer
|
|
242
|
+
|
|
243
|
+
Usage:
|
|
244
|
+
hailer-mcp Start MCP server (auto-detects stdio/http mode)
|
|
245
|
+
hailer-mcp setup Interactive setup for Claude Desktop
|
|
246
|
+
hailer-mcp help Show this help message
|
|
247
|
+
|
|
248
|
+
Environment variables:
|
|
249
|
+
MCP_CLIENT_API_KEY Client identifier (required for stdio mode)
|
|
250
|
+
HAILER_EMAIL Hailer account email
|
|
251
|
+
HAILER_PASSWORD Hailer account password
|
|
252
|
+
HAILER_API_URL API URL (default: https://api.hailer.com)
|
|
253
|
+
MCP_TRANSPORT Force transport: 'stdio' or 'http'
|
|
254
|
+
ENABLE_NUCLEAR_TOOLS Enable destructive operations (default: false)
|
|
255
|
+
|
|
256
|
+
For more information, see:
|
|
257
|
+
https://github.com/hailer/hailer-mcp
|
|
258
|
+
`);
|
|
259
|
+
break;
|
|
260
|
+
default:
|
|
261
|
+
// Start the server (default behavior)
|
|
262
|
+
await Promise.resolve().then(() => __importStar(require('./app')));
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
main().catch((error) => {
|
|
267
|
+
console.error('Error:', error.message);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
});
|
|
5
270
|
//# sourceMappingURL=cli.js.map
|
|
@@ -113,6 +113,21 @@ export declare class ToolRegistry {
|
|
|
113
113
|
* Get tool metadata (for access control checks)
|
|
114
114
|
*/
|
|
115
115
|
getTool(name: string): Tool | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* Get tools with their original Zod schemas for MCP SDK registration.
|
|
118
|
+
* MCP SDK requires Zod schemas, not JSON Schema objects.
|
|
119
|
+
*
|
|
120
|
+
* @param filter - Optional filter configuration
|
|
121
|
+
* @returns Array of tools with name, description, and Zod schema
|
|
122
|
+
*/
|
|
123
|
+
getToolsWithZodSchemas(filter?: {
|
|
124
|
+
allowedGroups?: ToolGroup[];
|
|
125
|
+
allowedTools?: string[];
|
|
126
|
+
}): Array<{
|
|
127
|
+
name: string;
|
|
128
|
+
description: string;
|
|
129
|
+
schema: z.ZodType;
|
|
130
|
+
}>;
|
|
116
131
|
/**
|
|
117
132
|
* Get total tool count
|
|
118
133
|
*/
|
|
@@ -324,6 +324,29 @@ Then retry \`${name}\` with the correct format from the skill examples.` : `💡
|
|
|
324
324
|
getTool(name) {
|
|
325
325
|
return this.tools.get(name);
|
|
326
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Get tools with their original Zod schemas for MCP SDK registration.
|
|
329
|
+
* MCP SDK requires Zod schemas, not JSON Schema objects.
|
|
330
|
+
*
|
|
331
|
+
* @param filter - Optional filter configuration
|
|
332
|
+
* @returns Array of tools with name, description, and Zod schema
|
|
333
|
+
*/
|
|
334
|
+
getToolsWithZodSchemas(filter) {
|
|
335
|
+
let toolsToExpose = Array.from(this.tools.values());
|
|
336
|
+
if (filter) {
|
|
337
|
+
if (filter.allowedTools) {
|
|
338
|
+
toolsToExpose = toolsToExpose.filter(t => filter.allowedTools.includes(t.name));
|
|
339
|
+
}
|
|
340
|
+
else if (filter.allowedGroups) {
|
|
341
|
+
toolsToExpose = toolsToExpose.filter(t => filter.allowedGroups.includes(t.group));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return toolsToExpose.map(tool => ({
|
|
345
|
+
name: tool.name,
|
|
346
|
+
description: tool.description,
|
|
347
|
+
schema: tool.schema
|
|
348
|
+
}));
|
|
349
|
+
}
|
|
327
350
|
/**
|
|
328
351
|
* Get total tool count
|
|
329
352
|
*/
|
package/dist/stdio-server.js
CHANGED
|
@@ -36,23 +36,36 @@ async function startStdioServer(toolRegistry) {
|
|
|
36
36
|
const allowedGroups = config_1.environment.ENABLE_NUCLEAR_TOOLS
|
|
37
37
|
? [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE, tool_registry_1.ToolGroup.PLAYGROUND, tool_registry_1.ToolGroup.NUCLEAR]
|
|
38
38
|
: [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE, tool_registry_1.ToolGroup.PLAYGROUND];
|
|
39
|
-
|
|
39
|
+
// Get tools with their original Zod schemas - MCP SDK requires Zod, not JSON Schema
|
|
40
|
+
const tools = toolRegistry.getToolsWithZodSchemas({ allowedGroups });
|
|
40
41
|
logger.info('Registering tools for stdio server', {
|
|
41
|
-
toolCount:
|
|
42
|
+
toolCount: tools.length,
|
|
42
43
|
allowedGroups,
|
|
43
44
|
});
|
|
44
|
-
// Register each tool with the MCP server
|
|
45
|
-
for (const
|
|
46
|
-
|
|
45
|
+
// Register each tool with the MCP server using Zod schemas
|
|
46
|
+
for (const tool of tools) {
|
|
47
|
+
// Extract shape from ZodObject for MCP SDK compatibility
|
|
48
|
+
// MCP SDK expects a "raw shape" (object with Zod types as values), not a ZodObject
|
|
49
|
+
const zodSchema = tool.schema;
|
|
50
|
+
const shape = zodSchema._def?.typeName === 'ZodObject'
|
|
51
|
+
? zodSchema._def.shape()
|
|
52
|
+
: zodSchema;
|
|
53
|
+
logger.debug('Registering tool with Zod schema', {
|
|
54
|
+
name: tool.name,
|
|
55
|
+
schemaTypeName: zodSchema._def?.typeName || 'unknown',
|
|
56
|
+
shapeKeys: Object.keys(shape || {})
|
|
57
|
+
});
|
|
58
|
+
// Use tool() method with shape object - MCP SDK will recognize Zod types
|
|
59
|
+
server.tool(tool.name, tool.description, shape, async (args) => {
|
|
47
60
|
const startTime = Date.now();
|
|
48
|
-
logger.debug('Tool call received', { toolName:
|
|
61
|
+
logger.debug('Tool call received', { toolName: tool.name, args });
|
|
49
62
|
try {
|
|
50
63
|
// Get user context (cached)
|
|
51
64
|
const userContext = await UserContextCache_1.UserContextCache.getContext(apiKey);
|
|
52
65
|
// Execute the tool
|
|
53
|
-
const result = await toolRegistry.executeTool(
|
|
66
|
+
const result = await toolRegistry.executeTool(tool.name, args, userContext);
|
|
54
67
|
const duration = Date.now() - startTime;
|
|
55
|
-
logger.info('Tool call completed', { toolName:
|
|
68
|
+
logger.info('Tool call completed', { toolName: tool.name, duration });
|
|
56
69
|
// Handle different result formats
|
|
57
70
|
if (result && typeof result === 'object' && 'content' in result) {
|
|
58
71
|
// Result is already in MCP format { content: [...] }
|
|
@@ -69,7 +82,7 @@ async function startStdioServer(toolRegistry) {
|
|
|
69
82
|
catch (error) {
|
|
70
83
|
const duration = Date.now() - startTime;
|
|
71
84
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
72
|
-
logger.error('Tool call failed', error, { toolName:
|
|
85
|
+
logger.error('Tool call failed', error, { toolName: tool.name, duration });
|
|
73
86
|
return {
|
|
74
87
|
content: [{
|
|
75
88
|
type: 'text',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hailer/mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"config": {
|
|
5
5
|
"docker": {
|
|
6
6
|
"registry": "registry.gitlab.com/hailer-repos/hailer-mcp"
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@anthropic-ai/sdk": "^0.54.0",
|
|
30
30
|
"@hailer/cli": "^1.1.10",
|
|
31
|
+
"@hailer/mcp": "^1.0.24",
|
|
31
32
|
"@modelcontextprotocol/sdk": "^1.11.5",
|
|
32
33
|
"@opentelemetry/api-logs": "^0.57.0",
|
|
33
34
|
"@opentelemetry/exporter-logs-otlp-proto": "^0.57.0",
|