@hailer/mcp 1.0.28 ā 1.0.30
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.js +19 -1
- package/dist/mcp/UserContextCache.d.ts +5 -0
- package/dist/mcp/UserContextCache.js +19 -0
- package/dist/mcp/tool-registry.d.ts +16 -0
- package/dist/mcp/tool-registry.js +8 -4
- package/dist/mcp/tools/activity.js +4 -0
- package/dist/mcp/tools/app-core.js +4 -0
- package/dist/mcp/tools/app-marketplace.js +9 -0
- package/dist/mcp/tools/app-member.js +2 -0
- package/dist/mcp/tools/app-scaffold.js +19 -5
- package/dist/mcp/tools/discussion.js +8 -0
- package/dist/mcp/tools/file.js +2 -0
- package/dist/mcp/tools/insight.js +6 -0
- package/dist/mcp/tools/metrics.js +4 -0
- package/dist/mcp/tools/user.js +6 -1
- package/dist/mcp/tools/workflow.js +10 -0
- package/dist/mcp/utils/index.d.ts +1 -0
- package/dist/mcp/utils/index.js +10 -1
- package/dist/mcp/utils/role-utils.d.ts +74 -0
- package/dist/mcp/utils/role-utils.js +151 -0
- package/dist/mcp/utils/types.d.ts +24 -1
- package/dist/stdio-server.js +9 -3
- package/manifest.json +62 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -47,6 +47,9 @@ const os = __importStar(require("os"));
|
|
|
47
47
|
const readline = __importStar(require("readline"));
|
|
48
48
|
const CLAUDE_CONFIG_DIR = path.join(os.homedir(), 'Library', 'Application Support', 'Claude');
|
|
49
49
|
const CLAUDE_CONFIG_FILE = path.join(CLAUDE_CONFIG_DIR, 'claude_desktop_config.json');
|
|
50
|
+
// Credentials file for publish and other tools that need direct auth
|
|
51
|
+
const HAILER_MCP_DIR = path.join(os.homedir(), '.hailer-mcp');
|
|
52
|
+
const CREDENTIALS_FILE = path.join(HAILER_MCP_DIR, 'credentials.json');
|
|
50
53
|
/**
|
|
51
54
|
* Muted output stream for password input
|
|
52
55
|
*/
|
|
@@ -55,6 +58,10 @@ class MutedStream {
|
|
|
55
58
|
muted = false;
|
|
56
59
|
write() { return true; }
|
|
57
60
|
end() { }
|
|
61
|
+
on() { return this; }
|
|
62
|
+
once() { return this; }
|
|
63
|
+
emit() { return true; }
|
|
64
|
+
removeListener() { return this; }
|
|
58
65
|
}
|
|
59
66
|
/**
|
|
60
67
|
* Prompt user for input
|
|
@@ -199,8 +206,19 @@ async function runSetup() {
|
|
|
199
206
|
}
|
|
200
207
|
// Write config
|
|
201
208
|
fs.writeFileSync(CLAUDE_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
209
|
+
// Save credentials to ~/.hailer-mcp/credentials.json for publish tool
|
|
210
|
+
if (!fs.existsSync(HAILER_MCP_DIR)) {
|
|
211
|
+
fs.mkdirSync(HAILER_MCP_DIR, { recursive: true });
|
|
212
|
+
}
|
|
213
|
+
const credentials = {
|
|
214
|
+
email,
|
|
215
|
+
password,
|
|
216
|
+
apiUrl,
|
|
217
|
+
};
|
|
218
|
+
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), { mode: 0o600 });
|
|
202
219
|
console.log('\nā
Configuration saved!');
|
|
203
|
-
console.log(`
|
|
220
|
+
console.log(` Claude Desktop: ${CLAUDE_CONFIG_FILE}`);
|
|
221
|
+
console.log(` Credentials: ${CREDENTIALS_FILE}`);
|
|
204
222
|
console.log('\nš Next steps:');
|
|
205
223
|
console.log(' 1. Quit Claude Desktop completely (Cmd+Q)');
|
|
206
224
|
console.log(' 2. Reopen Claude Desktop');
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { HailerClient } from './hailer-clients';
|
|
2
2
|
import { WorkspaceCache } from './workspace-cache';
|
|
3
3
|
import { HailerV2CoreInitResponse, HailerApiClient } from './utils/index';
|
|
4
|
+
import { ToolGroup } from './tool-registry';
|
|
5
|
+
import { UserRole } from './utils/index';
|
|
4
6
|
export interface UserContext {
|
|
5
7
|
client: HailerClient;
|
|
6
8
|
hailer: HailerApiClient;
|
|
@@ -10,6 +12,9 @@ export interface UserContext {
|
|
|
10
12
|
createdAt: number;
|
|
11
13
|
email: string;
|
|
12
14
|
password: string;
|
|
15
|
+
workspaceRoles: Record<string, UserRole>;
|
|
16
|
+
currentWorkspaceId: string;
|
|
17
|
+
allowedGroups: ToolGroup[];
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* Cache for user-specific data (client connections, init data, workspace cache)
|
|
@@ -6,6 +6,7 @@ const workspace_cache_1 = require("./workspace-cache");
|
|
|
6
6
|
const config_1 = require("../config");
|
|
7
7
|
const logger_1 = require("../lib/logger");
|
|
8
8
|
const index_1 = require("./utils/index");
|
|
9
|
+
const index_2 = require("./utils/index");
|
|
9
10
|
const logger = (0, logger_1.createLogger)({ component: 'user-context-cache' });
|
|
10
11
|
/**
|
|
11
12
|
* Cache for user-specific data (client connections, init data, workspace cache)
|
|
@@ -123,6 +124,21 @@ class UserContextCache {
|
|
|
123
124
|
// Create workspace cache from init data
|
|
124
125
|
const appConfig = (0, config_1.createApplicationConfig)();
|
|
125
126
|
const workspaceCache = (0, workspace_cache_1.createWorkspaceCache)(init, appConfig.mcpConfig);
|
|
127
|
+
// Extract user roles from ALL workspaces
|
|
128
|
+
const currentUserId = await (0, hailer_clients_1.getCurrentUserId)(client);
|
|
129
|
+
const workspaceRoles = (0, index_2.extractWorkspaceRoles)(networks, currentUserId);
|
|
130
|
+
// Get current workspace ID
|
|
131
|
+
const currentWorkspaceId = init.network?._id || Object.keys(networks)[0] || '';
|
|
132
|
+
// Get role for current workspace (for backward compatibility and initial tool filtering)
|
|
133
|
+
const userRole = workspaceRoles[currentWorkspaceId] || 'guest';
|
|
134
|
+
const allowedGroups = (0, index_2.getAllowedGroups)(userRole, config_1.environment.ENABLE_NUCLEAR_TOOLS);
|
|
135
|
+
logger.info('User roles extracted from all workspaces', {
|
|
136
|
+
apiKey: apiKey.substring(0, 8) + '...',
|
|
137
|
+
workspaceCount: Object.keys(workspaceRoles).length,
|
|
138
|
+
currentWorkspaceId,
|
|
139
|
+
currentRole: userRole,
|
|
140
|
+
allRoles: Object.entries(workspaceRoles).map(([id, role]) => `${id.slice(-6)}:${role}`).join(', ')
|
|
141
|
+
});
|
|
126
142
|
// Get credentials from config (for tools like publish_hailer_app that need external auth)
|
|
127
143
|
const accountConfig = appConfig.getClientConfig(apiKey);
|
|
128
144
|
const context = {
|
|
@@ -134,6 +150,9 @@ class UserContextCache {
|
|
|
134
150
|
createdAt: Date.now(),
|
|
135
151
|
email: accountConfig.email,
|
|
136
152
|
password: accountConfig.password,
|
|
153
|
+
workspaceRoles, // NEW: Map of workspaceId ā role
|
|
154
|
+
currentWorkspaceId, // NEW: Current workspace ID
|
|
155
|
+
allowedGroups, // Keep for stdio-server compatibility
|
|
137
156
|
};
|
|
138
157
|
// Calculate and log cache sizes
|
|
139
158
|
const rawInitSize = Buffer.byteLength(JSON.stringify(init), 'utf8');
|
|
@@ -27,6 +27,20 @@ export declare enum ToolGroup {
|
|
|
27
27
|
NUCLEAR = "nuclear",
|
|
28
28
|
BOT_INTERNAL = "bot_internal"
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Tool annotations for MCP safety hints (per MCP spec)
|
|
32
|
+
* These hints help clients understand tool behavior for UI/UX decisions
|
|
33
|
+
*/
|
|
34
|
+
export interface ToolAnnotations {
|
|
35
|
+
/** If true, the tool does not modify any data (safe to run without confirmation) */
|
|
36
|
+
readOnlyHint?: boolean;
|
|
37
|
+
/** If true, the tool may perform destructive updates (delete, overwrite) */
|
|
38
|
+
destructiveHint?: boolean;
|
|
39
|
+
/** If true, the tool may interact with entities outside the user's control */
|
|
40
|
+
openWorldHint?: boolean;
|
|
41
|
+
/** If true, repeated calls with same args produce same result */
|
|
42
|
+
idempotentHint?: boolean;
|
|
43
|
+
}
|
|
30
44
|
/**
|
|
31
45
|
* Tool definition interface
|
|
32
46
|
*/
|
|
@@ -35,6 +49,7 @@ export interface Tool<TSchema extends z.ZodType = z.ZodType> {
|
|
|
35
49
|
group: ToolGroup;
|
|
36
50
|
description: string;
|
|
37
51
|
schema: TSchema;
|
|
52
|
+
annotations?: ToolAnnotations;
|
|
38
53
|
execute: (args: z.infer<TSchema>, context: UserContext) => Promise<McpResponse>;
|
|
39
54
|
}
|
|
40
55
|
/**
|
|
@@ -44,6 +59,7 @@ export interface ToolDefinition {
|
|
|
44
59
|
name: string;
|
|
45
60
|
description: string;
|
|
46
61
|
inputSchema: any;
|
|
62
|
+
annotations?: ToolAnnotations;
|
|
47
63
|
}
|
|
48
64
|
/**
|
|
49
65
|
* ToolRegistry - Clean, testable, dependency-injected tool registry
|
|
@@ -192,7 +192,8 @@ class ToolRegistry {
|
|
|
192
192
|
return toolsToExpose.map(tool => ({
|
|
193
193
|
name: tool.name,
|
|
194
194
|
description: tool.description,
|
|
195
|
-
inputSchema: this.convertZodSchemaToJsonSchema(tool.schema)
|
|
195
|
+
inputSchema: this.convertZodSchemaToJsonSchema(tool.schema),
|
|
196
|
+
...(tool.annotations && { annotations: tool.annotations })
|
|
196
197
|
}));
|
|
197
198
|
}
|
|
198
199
|
/**
|
|
@@ -222,7 +223,8 @@ class ToolRegistry {
|
|
|
222
223
|
return {
|
|
223
224
|
name: tool.name,
|
|
224
225
|
description: `${tool.description} [Enum-constrained to valid workspace IDs]`,
|
|
225
|
-
inputSchema: masterSchema
|
|
226
|
+
inputSchema: masterSchema,
|
|
227
|
+
...(tool.annotations && { annotations: tool.annotations })
|
|
226
228
|
};
|
|
227
229
|
}
|
|
228
230
|
}
|
|
@@ -234,7 +236,8 @@ class ToolRegistry {
|
|
|
234
236
|
return {
|
|
235
237
|
name: tool.name,
|
|
236
238
|
description: `${tool.description} [Schema-constrained to ${workflowName}]`,
|
|
237
|
-
inputSchema: workspaceSchema
|
|
239
|
+
inputSchema: workspaceSchema,
|
|
240
|
+
...(tool.annotations && { annotations: tool.annotations })
|
|
238
241
|
};
|
|
239
242
|
}
|
|
240
243
|
}
|
|
@@ -242,7 +245,8 @@ class ToolRegistry {
|
|
|
242
245
|
return {
|
|
243
246
|
name: tool.name,
|
|
244
247
|
description: tool.description,
|
|
245
|
-
inputSchema: this.convertZodSchemaToJsonSchema(tool.schema)
|
|
248
|
+
inputSchema: this.convertZodSchemaToJsonSchema(tool.schema),
|
|
249
|
+
...(tool.annotations && { annotations: tool.annotations })
|
|
246
250
|
};
|
|
247
251
|
}
|
|
248
252
|
/**
|
|
@@ -436,6 +436,7 @@ exports.listActivitiesTool = {
|
|
|
436
436
|
name: 'list_activities',
|
|
437
437
|
group: tool_registry_1.ToolGroup.READ,
|
|
438
438
|
description: `List activities from workflow phase`,
|
|
439
|
+
annotations: { readOnlyHint: true },
|
|
439
440
|
schema: zod_1.z.object({
|
|
440
441
|
workspaceId: zod_1.z.string().optional().describe("Workspace ID. If not provided, uses current workspace. Use list_my_workspaces to see available workspaces."),
|
|
441
442
|
workflowId: zod_1.z.string().describe("Workflow ID to list activities from"),
|
|
@@ -584,6 +585,7 @@ exports.showActivityByIdTool = {
|
|
|
584
585
|
name: 'show_activity_by_id',
|
|
585
586
|
group: tool_registry_1.ToolGroup.READ,
|
|
586
587
|
description: `Get activity by ID`,
|
|
588
|
+
annotations: { readOnlyHint: true },
|
|
587
589
|
schema: zod_1.z.object({
|
|
588
590
|
workspaceId: zod_1.z.string().optional().describe("Workspace ID. If not provided, uses current workspace. Use list_my_workspaces to see available workspaces."),
|
|
589
591
|
activityId: zod_1.z.string().describe("Activity ID to load"),
|
|
@@ -631,6 +633,7 @@ exports.createActivityTool = {
|
|
|
631
633
|
name: 'create_activity',
|
|
632
634
|
group: tool_registry_1.ToolGroup.WRITE,
|
|
633
635
|
description: createActivityDescription,
|
|
636
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
634
637
|
schema: zod_1.z.object({
|
|
635
638
|
workspaceId: zod_1.z
|
|
636
639
|
.string()
|
|
@@ -970,6 +973,7 @@ exports.updateActivityTool = {
|
|
|
970
973
|
name: 'update_activity',
|
|
971
974
|
group: tool_registry_1.ToolGroup.WRITE,
|
|
972
975
|
description: updateActivityDescription,
|
|
976
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
973
977
|
schema: zod_1.z.object({
|
|
974
978
|
workspaceId: zod_1.z
|
|
975
979
|
.string()
|
|
@@ -23,6 +23,7 @@ exports.createAppTool = {
|
|
|
23
23
|
name: 'create_app',
|
|
24
24
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
25
25
|
description: createAppDescription,
|
|
26
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
26
27
|
schema: zod_1.z.object({
|
|
27
28
|
workspaceId: zod_1.z
|
|
28
29
|
.string()
|
|
@@ -141,6 +142,7 @@ exports.listAppsTool = {
|
|
|
141
142
|
name: 'list_apps',
|
|
142
143
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
143
144
|
description: listAppsDescription,
|
|
145
|
+
annotations: { readOnlyHint: true },
|
|
144
146
|
schema: zod_1.z.object({
|
|
145
147
|
workspaceId: zod_1.z
|
|
146
148
|
.string()
|
|
@@ -234,6 +236,7 @@ exports.updateAppTool = {
|
|
|
234
236
|
name: 'update_app',
|
|
235
237
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
236
238
|
description: updateAppDescription,
|
|
239
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
237
240
|
schema: zod_1.z.object({
|
|
238
241
|
appId: zod_1.z
|
|
239
242
|
.string()
|
|
@@ -347,6 +350,7 @@ exports.removeAppTool = {
|
|
|
347
350
|
name: 'remove_app',
|
|
348
351
|
group: tool_registry_1.ToolGroup.NUCLEAR,
|
|
349
352
|
description: removeAppDescription,
|
|
353
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
350
354
|
schema: zod_1.z.object({
|
|
351
355
|
appId: zod_1.z
|
|
352
356
|
.string()
|
|
@@ -22,6 +22,7 @@ exports.listTemplatesTool = {
|
|
|
22
22
|
name: 'list_templates',
|
|
23
23
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
24
24
|
description: listTemplatesDescription,
|
|
25
|
+
annotations: { readOnlyHint: true },
|
|
25
26
|
schema: zod_1.z.object({
|
|
26
27
|
workspaceId: zod_1.z
|
|
27
28
|
.string()
|
|
@@ -128,6 +129,7 @@ exports.createTemplateTool = {
|
|
|
128
129
|
name: 'create_template',
|
|
129
130
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
130
131
|
description: createTemplateDescription,
|
|
132
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
131
133
|
schema: zod_1.z.object({
|
|
132
134
|
name: zod_1.z
|
|
133
135
|
.string()
|
|
@@ -212,6 +214,7 @@ exports.installTemplateTool = {
|
|
|
212
214
|
name: 'install_template',
|
|
213
215
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
214
216
|
description: installTemplateDescription,
|
|
217
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
215
218
|
schema: zod_1.z.object({
|
|
216
219
|
templateId: zod_1.z
|
|
217
220
|
.string()
|
|
@@ -315,6 +318,7 @@ exports.getTemplateTool = {
|
|
|
315
318
|
name: 'get_template',
|
|
316
319
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
317
320
|
description: getTemplateDescription,
|
|
321
|
+
annotations: { readOnlyHint: true },
|
|
318
322
|
schema: zod_1.z.object({
|
|
319
323
|
templateId: zod_1.z
|
|
320
324
|
.string()
|
|
@@ -422,6 +426,7 @@ exports.publishTemplateTool = {
|
|
|
422
426
|
name: 'publish_template',
|
|
423
427
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
424
428
|
description: publishTemplateDescription,
|
|
429
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
425
430
|
schema: zod_1.z.object({
|
|
426
431
|
productId: zod_1.z
|
|
427
432
|
.string()
|
|
@@ -608,6 +613,7 @@ exports.getProductTool = {
|
|
|
608
613
|
name: 'get_product',
|
|
609
614
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
610
615
|
description: getProductDescription,
|
|
616
|
+
annotations: { readOnlyHint: true },
|
|
611
617
|
schema: zod_1.z.object({
|
|
612
618
|
productId: zod_1.z
|
|
613
619
|
.string()
|
|
@@ -669,6 +675,7 @@ exports.getProductManifestTool = {
|
|
|
669
675
|
name: 'get_product_manifest',
|
|
670
676
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
671
677
|
description: getProductManifestDescription,
|
|
678
|
+
annotations: { readOnlyHint: true },
|
|
672
679
|
schema: zod_1.z.object({
|
|
673
680
|
productId: zod_1.z
|
|
674
681
|
.string()
|
|
@@ -723,6 +730,7 @@ const publishAppDescription = `Publish app to Hailer marketplace`;
|
|
|
723
730
|
exports.publishAppTool = {
|
|
724
731
|
name: 'publish_app',
|
|
725
732
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
733
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
726
734
|
description: publishAppDescription,
|
|
727
735
|
schema: zod_1.z.object({
|
|
728
736
|
appId: zod_1.z
|
|
@@ -912,6 +920,7 @@ exports.installMarketplaceAppTool = {
|
|
|
912
920
|
name: 'install_marketplace_app',
|
|
913
921
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
914
922
|
description: installMarketplaceAppDescription,
|
|
923
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
915
924
|
schema: zod_1.z.object({
|
|
916
925
|
productId: zod_1.z
|
|
917
926
|
.string()
|
|
@@ -18,6 +18,7 @@ exports.addAppMemberTool = {
|
|
|
18
18
|
name: 'add_app_member',
|
|
19
19
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
20
20
|
description: addAppMemberDescription,
|
|
21
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
21
22
|
schema: zod_1.z.object({
|
|
22
23
|
appId: zod_1.z
|
|
23
24
|
.string()
|
|
@@ -110,6 +111,7 @@ exports.removeAppMemberTool = {
|
|
|
110
111
|
name: 'remove_app_member',
|
|
111
112
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
112
113
|
description: removeAppMemberDescription,
|
|
114
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
113
115
|
schema: zod_1.z.object({
|
|
114
116
|
appId: zod_1.z
|
|
115
117
|
.string()
|
|
@@ -125,6 +125,7 @@ exports.scaffoldHailerAppTool = {
|
|
|
125
125
|
name: 'scaffold_hailer_app',
|
|
126
126
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
127
127
|
description: scaffoldHailerAppDescription,
|
|
128
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
128
129
|
schema: zod_1.z.object({
|
|
129
130
|
projectName: zod_1.z.string().min(1).describe("Project folder name"),
|
|
130
131
|
template: zod_1.z.enum(['react-ts-style', 'react-ts-example', 'react-ts', 'vanilla']).describe("Template to use (react-ts-style recommended - includes Hailer theme)"),
|
|
@@ -497,6 +498,7 @@ exports.publishHailerAppTool = {
|
|
|
497
498
|
name: 'publish_hailer_app',
|
|
498
499
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
499
500
|
description: publishHailerAppDescription,
|
|
501
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
500
502
|
schema: zod_1.z.object({
|
|
501
503
|
projectDirectory: zod_1.z.string().optional().describe("Path to app project (defaults to DEV_APPS_PATH or current directory)"),
|
|
502
504
|
appId: zod_1.z.string().optional().describe("App ID to publish to (reads from manifest.json if not provided)"),
|
|
@@ -507,15 +509,28 @@ exports.publishHailerAppTool = {
|
|
|
507
509
|
async execute(args, context) {
|
|
508
510
|
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
509
511
|
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
512
|
+
const os = await Promise.resolve().then(() => __importStar(require('os')));
|
|
513
|
+
// Try to read credentials from ~/.hailer-mcp/credentials.json (saved by hailer-mcp setup)
|
|
514
|
+
let savedCredentials = {};
|
|
515
|
+
const credentialsFile = path.join(os.homedir(), '.hailer-mcp', 'credentials.json');
|
|
516
|
+
try {
|
|
517
|
+
if (fs.existsSync(credentialsFile)) {
|
|
518
|
+
savedCredentials = JSON.parse(fs.readFileSync(credentialsFile, 'utf-8'));
|
|
519
|
+
logger.debug('Loaded credentials from ~/.hailer-mcp/credentials.json');
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
catch (error) {
|
|
523
|
+
logger.warn('Failed to read credentials file', { error });
|
|
524
|
+
}
|
|
525
|
+
// Priority: args > saved credentials > context
|
|
526
|
+
const email = args.email || savedCredentials.email || context.email;
|
|
527
|
+
const password = args.password || savedCredentials.password || context.password;
|
|
513
528
|
const { publishToMarket } = args;
|
|
514
529
|
if (!email || !password) {
|
|
515
530
|
return {
|
|
516
531
|
content: [{
|
|
517
532
|
type: "text",
|
|
518
|
-
text: `ā **Credentials Required**\n\nNo credentials found
|
|
533
|
+
text: `ā **Credentials Required**\n\nNo credentials found.\n\n**Run setup first:**\n\`\`\`bash\nhailer-mcp setup\n\`\`\`\n\nOr provide email/password parameters directly.`,
|
|
519
534
|
}],
|
|
520
535
|
};
|
|
521
536
|
}
|
|
@@ -649,7 +664,6 @@ exports.publishHailerAppTool = {
|
|
|
649
664
|
responseText += `**Project:** ${projectDir}\n`;
|
|
650
665
|
responseText += `**Marketplace:** ${publishToMarket ? 'Yes (will get targetId)' : 'No'}\n\n`;
|
|
651
666
|
// Run the SDK publish script using `expect` for interactive automation
|
|
652
|
-
const os = await Promise.resolve().then(() => __importStar(require('os')));
|
|
653
667
|
const result = await new Promise((resolve) => {
|
|
654
668
|
// Create temp expect script file
|
|
655
669
|
const tmpDir = os.tmpdir();
|
|
@@ -79,6 +79,7 @@ exports.listMyDiscussionsTool = {
|
|
|
79
79
|
name: 'list_my_discussions',
|
|
80
80
|
group: tool_registry_1.ToolGroup.READ,
|
|
81
81
|
description: listMyDiscussionsDescription,
|
|
82
|
+
annotations: { readOnlyHint: true },
|
|
82
83
|
schema: zod_1.z.object({}),
|
|
83
84
|
async execute(args, context) {
|
|
84
85
|
try {
|
|
@@ -241,6 +242,7 @@ exports.fetchDiscussionMessagesTool = {
|
|
|
241
242
|
name: 'fetch_discussion_messages',
|
|
242
243
|
group: tool_registry_1.ToolGroup.READ,
|
|
243
244
|
description: fetchDiscussionMessagesDescription,
|
|
245
|
+
annotations: { readOnlyHint: true },
|
|
244
246
|
schema: zod_1.z.object({
|
|
245
247
|
discussionId: zod_1.z
|
|
246
248
|
.string()
|
|
@@ -403,6 +405,7 @@ exports.fetchPreviousDiscussionMessagesTool = {
|
|
|
403
405
|
name: 'fetch_previous_discussion_messages',
|
|
404
406
|
group: tool_registry_1.ToolGroup.READ,
|
|
405
407
|
description: fetchPreviousDiscussionMessagesDescription,
|
|
408
|
+
annotations: { readOnlyHint: true },
|
|
406
409
|
schema: zod_1.z.object({
|
|
407
410
|
oldestMessageId: zod_1.z
|
|
408
411
|
.string()
|
|
@@ -521,6 +524,7 @@ exports.joinDiscussionTool = {
|
|
|
521
524
|
name: 'join_discussion',
|
|
522
525
|
group: tool_registry_1.ToolGroup.WRITE,
|
|
523
526
|
description: joinDiscussionDescription,
|
|
527
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
524
528
|
schema: zod_1.z.object({
|
|
525
529
|
activityId: zod_1.z
|
|
526
530
|
.string()
|
|
@@ -801,6 +805,7 @@ exports.leaveDiscussionTool = {
|
|
|
801
805
|
name: 'leave_discussion',
|
|
802
806
|
group: tool_registry_1.ToolGroup.WRITE,
|
|
803
807
|
description: leaveDiscussionDescription,
|
|
808
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
804
809
|
schema: zod_1.z.object({
|
|
805
810
|
activityId: zod_1.z
|
|
806
811
|
.string()
|
|
@@ -910,6 +915,7 @@ exports.addDiscussionMessageTool = {
|
|
|
910
915
|
name: 'add_discussion_message',
|
|
911
916
|
group: tool_registry_1.ToolGroup.WRITE,
|
|
912
917
|
description: addDiscussionMessageDescription,
|
|
918
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
913
919
|
schema: zod_1.z.object({
|
|
914
920
|
discussionId: zod_1.z.string().min(24, "Discussion ID must be at least 24 characters").describe("The discussion ID where to post the message"),
|
|
915
921
|
content: zod_1.z.string().min(1, "Message content cannot be empty").describe("The message text to post"),
|
|
@@ -952,6 +958,7 @@ exports.inviteDiscussionMembersTool = {
|
|
|
952
958
|
name: 'invite_discussion_members',
|
|
953
959
|
group: tool_registry_1.ToolGroup.WRITE,
|
|
954
960
|
description: inviteDiscussionMembersDescription,
|
|
961
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
955
962
|
schema: zod_1.z.object({
|
|
956
963
|
discussionId: zod_1.z
|
|
957
964
|
.string()
|
|
@@ -1103,6 +1110,7 @@ exports.getActivityFromDiscussionTool = {
|
|
|
1103
1110
|
name: 'get_activity_from_discussion',
|
|
1104
1111
|
group: tool_registry_1.ToolGroup.READ,
|
|
1105
1112
|
description: getActivityFromDiscussionDescription,
|
|
1113
|
+
annotations: { readOnlyHint: true },
|
|
1106
1114
|
schema: zod_1.z.object({
|
|
1107
1115
|
discussionId: zod_1.z
|
|
1108
1116
|
.string()
|
package/dist/mcp/tools/file.js
CHANGED
|
@@ -62,6 +62,7 @@ exports.uploadFilesTool = {
|
|
|
62
62
|
name: 'upload_files',
|
|
63
63
|
group: tool_registry_1.ToolGroup.WRITE,
|
|
64
64
|
description: uploadFilesDescription,
|
|
65
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
65
66
|
schema: zod_1.z.object({
|
|
66
67
|
files: zod_1.z.union([
|
|
67
68
|
zod_1.z.array(zod_1.z.object({
|
|
@@ -164,6 +165,7 @@ exports.downloadFileTool = {
|
|
|
164
165
|
name: 'download_file',
|
|
165
166
|
group: tool_registry_1.ToolGroup.READ,
|
|
166
167
|
description: downloadFileDescription,
|
|
168
|
+
annotations: { readOnlyHint: true },
|
|
167
169
|
schema: zod_1.z.object({
|
|
168
170
|
fileId: zod_1.z.string().describe("File ID to download"),
|
|
169
171
|
savePath: zod_1.z.string().optional().describe("Optional: local path to save file to disk")
|
|
@@ -96,6 +96,7 @@ exports.createInsightTool = {
|
|
|
96
96
|
name: 'create_insight',
|
|
97
97
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
98
98
|
description: createInsightDescription,
|
|
99
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
99
100
|
schema: zod_1.z.object({
|
|
100
101
|
workspaceId: zod_1.z
|
|
101
102
|
.string()
|
|
@@ -259,6 +260,7 @@ exports.previewInsightTool = {
|
|
|
259
260
|
name: 'preview_insight',
|
|
260
261
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
261
262
|
description: previewInsightDescription,
|
|
263
|
+
annotations: { readOnlyHint: true },
|
|
262
264
|
schema: zod_1.z.object({
|
|
263
265
|
workspaceId: zod_1.z
|
|
264
266
|
.string()
|
|
@@ -376,6 +378,7 @@ exports.getInsightDataTool = {
|
|
|
376
378
|
name: 'get_insight_data',
|
|
377
379
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
378
380
|
description: getInsightDataDescription,
|
|
381
|
+
annotations: { readOnlyHint: true },
|
|
379
382
|
schema: zod_1.z.object({
|
|
380
383
|
insightId: zod_1.z
|
|
381
384
|
.string()
|
|
@@ -472,6 +475,7 @@ exports.updateInsightTool = {
|
|
|
472
475
|
name: 'update_insight',
|
|
473
476
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
474
477
|
description: updateInsightDescription,
|
|
478
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
475
479
|
schema: zod_1.z.object({
|
|
476
480
|
insightId: zod_1.z
|
|
477
481
|
.string()
|
|
@@ -616,6 +620,7 @@ exports.removeInsightTool = {
|
|
|
616
620
|
name: 'remove_insight',
|
|
617
621
|
group: tool_registry_1.ToolGroup.NUCLEAR,
|
|
618
622
|
description: removeInsightDescription,
|
|
623
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
619
624
|
schema: zod_1.z.object({
|
|
620
625
|
insightId: zod_1.z
|
|
621
626
|
.string()
|
|
@@ -743,6 +748,7 @@ const listInsightsDescription = `List all insights in workspace`;
|
|
|
743
748
|
exports.listInsightsTool = {
|
|
744
749
|
name: 'list_insights',
|
|
745
750
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
751
|
+
annotations: { readOnlyHint: true },
|
|
746
752
|
description: listInsightsDescription,
|
|
747
753
|
schema: zod_1.z.object({
|
|
748
754
|
workspaceId: zod_1.z
|
|
@@ -219,6 +219,7 @@ function formatAsChart(data, groupByLabel) {
|
|
|
219
219
|
exports.queryMetricTool = {
|
|
220
220
|
name: 'query_metric',
|
|
221
221
|
group: tool_registry_1.ToolGroup.READ,
|
|
222
|
+
annotations: { readOnlyHint: true },
|
|
222
223
|
description: `Query metrics from Victoria Metrics using PromQL. Available metrics:
|
|
223
224
|
- hailer.activity.create - Activities created (labels: workspace, process, team, userRole)
|
|
224
225
|
- hailer.message.create - Messages sent (labels: workspace, discussion, userRole)
|
|
@@ -329,6 +330,7 @@ exports.listMetricsTool = {
|
|
|
329
330
|
name: 'list_metrics',
|
|
330
331
|
group: tool_registry_1.ToolGroup.READ,
|
|
331
332
|
description: 'List all available Hailer metrics from Victoria Metrics',
|
|
333
|
+
annotations: { readOnlyHint: true },
|
|
332
334
|
schema: zod_1.z.object({}),
|
|
333
335
|
async execute(_args, _context) {
|
|
334
336
|
try {
|
|
@@ -443,6 +445,7 @@ exports.searchWorkspaceForMetricsTool = {
|
|
|
443
445
|
group: tool_registry_1.ToolGroup.READ,
|
|
444
446
|
description: `Search for workspaces by name to get their IDs for metric queries.
|
|
445
447
|
Use this tool when user mentions a workspace by name (e.g. "Sales Team") to find its ID for filtering metrics.`,
|
|
448
|
+
annotations: { readOnlyHint: true },
|
|
446
449
|
schema: zod_1.z.object({
|
|
447
450
|
name: zod_1.z.string().min(3).describe("Workspace name to search (min 3 characters)"),
|
|
448
451
|
limit: zod_1.z.number().min(1).max(100).optional().default(20).describe("Maximum results to return (default 20, max 100)"),
|
|
@@ -495,6 +498,7 @@ exports.searchUserForMetricsTool = {
|
|
|
495
498
|
group: tool_registry_1.ToolGroup.READ,
|
|
496
499
|
description: `Look up user details by ID for metric analysis.
|
|
497
500
|
Use this after getting user IDs from grouped metric results to see who they are.`,
|
|
501
|
+
annotations: { readOnlyHint: true },
|
|
498
502
|
schema: zod_1.z.object({
|
|
499
503
|
userIds: zod_1.z.preprocess((val) => {
|
|
500
504
|
if (typeof val === 'string') {
|
package/dist/mcp/tools/user.js
CHANGED
|
@@ -22,6 +22,7 @@ exports.listMyWorkspacesTool = {
|
|
|
22
22
|
name: 'list_my_workspaces',
|
|
23
23
|
group: tool_registry_1.ToolGroup.READ,
|
|
24
24
|
description: listMyWorkspacesDescription,
|
|
25
|
+
annotations: { readOnlyHint: true },
|
|
25
26
|
schema: zod_1.z.object({}),
|
|
26
27
|
async execute(_args, context) {
|
|
27
28
|
logger.debug('Listing user workspaces', {
|
|
@@ -41,7 +42,9 @@ exports.listMyWorkspacesTool = {
|
|
|
41
42
|
.map(([id, ws]) => {
|
|
42
43
|
const isCurrent = id === currentWorkspaceId;
|
|
43
44
|
const marker = isCurrent ? ' ā current' : '';
|
|
44
|
-
|
|
45
|
+
const role = context.workspaceRoles[id] || 'guest';
|
|
46
|
+
const roleEmoji = role === 'owner' ? 'š' : role === 'admin' ? 'āļø' : role === 'member' ? 'š¤' : 'šļø';
|
|
47
|
+
return `⢠**${ws.name}**${marker}\n - ID: \`${id}\`\n - Role: ${roleEmoji} ${role}`;
|
|
45
48
|
})
|
|
46
49
|
.join('\n\n');
|
|
47
50
|
const hint = workspaceList.length > 1
|
|
@@ -65,6 +68,7 @@ exports.searchWorkspaceUsersTool = {
|
|
|
65
68
|
name: 'search_workspace_users',
|
|
66
69
|
group: tool_registry_1.ToolGroup.READ,
|
|
67
70
|
description: searchWorkspaceUsersDescription,
|
|
71
|
+
annotations: { readOnlyHint: true },
|
|
68
72
|
schema: zod_1.z.object({
|
|
69
73
|
query: zod_1.z
|
|
70
74
|
.string()
|
|
@@ -132,6 +136,7 @@ exports.getWorkspaceBalanceTool = {
|
|
|
132
136
|
name: 'get_workspace_balance',
|
|
133
137
|
group: tool_registry_1.ToolGroup.READ,
|
|
134
138
|
description: getWorkspaceBalanceDescription,
|
|
139
|
+
annotations: { readOnlyHint: true },
|
|
135
140
|
schema: zod_1.z.object({
|
|
136
141
|
workspaceId: zod_1.z
|
|
137
142
|
.string()
|
|
@@ -57,6 +57,7 @@ exports.getWorkflowSchemaTool = {
|
|
|
57
57
|
name: 'get_workflow_schema',
|
|
58
58
|
group: tool_registry_1.ToolGroup.READ,
|
|
59
59
|
description: getWorkflowSchemaDescription,
|
|
60
|
+
annotations: { readOnlyHint: true },
|
|
60
61
|
schema: zod_1.z.object({
|
|
61
62
|
workflowId: zod_1.z.string().describe("Workflow ID to get schema from"),
|
|
62
63
|
phaseId: zod_1.z.string().describe("Phase ID to get schema from (use list_workflow_phases to get available phases)"),
|
|
@@ -181,6 +182,7 @@ exports.listWorkflowPhasesTool = {
|
|
|
181
182
|
name: 'list_workflow_phases',
|
|
182
183
|
group: tool_registry_1.ToolGroup.READ,
|
|
183
184
|
description: listWorkflowPhasesDescription,
|
|
185
|
+
annotations: { readOnlyHint: true },
|
|
184
186
|
schema: zod_1.z.object({
|
|
185
187
|
workflowId: zod_1.z.string().describe("Workflow ID to get phases from"),
|
|
186
188
|
}),
|
|
@@ -259,6 +261,7 @@ exports.listWorkflowsTool = {
|
|
|
259
261
|
name: 'list_workflows',
|
|
260
262
|
group: tool_registry_1.ToolGroup.READ,
|
|
261
263
|
description: listWorkflowsDescription,
|
|
264
|
+
annotations: { readOnlyHint: true },
|
|
262
265
|
schema: zod_1.z.object({
|
|
263
266
|
workspace: zod_1.z.string().optional().describe("Optional workspace ID or name"),
|
|
264
267
|
includeRelationships: zod_1.z.coerce.boolean().optional().default(true).describe("Show ActivityLink relationships between workflows"),
|
|
@@ -421,6 +424,7 @@ exports.installWorkflowTool = {
|
|
|
421
424
|
name: 'install_workflow',
|
|
422
425
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
423
426
|
description: installWorkflowDescription,
|
|
427
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
424
428
|
schema: installWorkflowSchema,
|
|
425
429
|
async execute(args, context) {
|
|
426
430
|
logger.debug('Installing workflow', {
|
|
@@ -560,6 +564,7 @@ exports.removeWorkflowTool = {
|
|
|
560
564
|
name: 'remove_workflow',
|
|
561
565
|
group: tool_registry_1.ToolGroup.NUCLEAR,
|
|
562
566
|
description: removeWorkflowDescription,
|
|
567
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
563
568
|
schema: removeWorkflowSchema,
|
|
564
569
|
async execute(args, context) {
|
|
565
570
|
logger.debug('Removing workflow', {
|
|
@@ -710,6 +715,7 @@ exports.updateWorkflowFieldTool = {
|
|
|
710
715
|
name: 'update_workflow_field',
|
|
711
716
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
712
717
|
description: updateWorkflowFieldDescription,
|
|
718
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
713
719
|
schema: updateWorkflowFieldSchema,
|
|
714
720
|
async execute(args, context) {
|
|
715
721
|
logger.debug('Updating workflow field', {
|
|
@@ -838,6 +844,7 @@ exports.testFunctionFieldTool = {
|
|
|
838
844
|
name: 'test_function_field',
|
|
839
845
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
840
846
|
description: testFunctionFieldDescription,
|
|
847
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
841
848
|
schema: testFunctionFieldSchema,
|
|
842
849
|
async execute(args, context) {
|
|
843
850
|
logger.debug('Testing function field', {
|
|
@@ -1019,6 +1026,7 @@ exports.listWorkflowsMinimalTool = {
|
|
|
1019
1026
|
name: 'list_workflows_minimal',
|
|
1020
1027
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
1021
1028
|
description: listWorkflowsMinimalDescription,
|
|
1029
|
+
annotations: { readOnlyHint: true },
|
|
1022
1030
|
schema: listWorkflowsMinimalSchema,
|
|
1023
1031
|
async execute(args, context) {
|
|
1024
1032
|
logger.debug('Listing workflows (minimal)', {
|
|
@@ -1131,6 +1139,7 @@ exports.countActivitiesTool = {
|
|
|
1131
1139
|
name: 'count_activities',
|
|
1132
1140
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
1133
1141
|
description: countActivitiesDescription,
|
|
1142
|
+
annotations: { readOnlyHint: true },
|
|
1134
1143
|
schema: countActivitiesSchema,
|
|
1135
1144
|
async execute(args, context) {
|
|
1136
1145
|
logger.debug('Counting activities', {
|
|
@@ -1234,6 +1243,7 @@ exports.updateWorkflowPhaseTool = {
|
|
|
1234
1243
|
name: 'update_workflow_phase',
|
|
1235
1244
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
1236
1245
|
description: updateWorkflowPhaseDescription,
|
|
1246
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
1237
1247
|
schema: updateWorkflowPhaseSchema,
|
|
1238
1248
|
async execute(args, context) {
|
|
1239
1249
|
logger.debug('Updating workflow phase', {
|
|
@@ -12,5 +12,6 @@ export { transformActivity, transformActivities, transformFields, transformField
|
|
|
12
12
|
export { textResponse, errorResponse, successResponse, jsonResponse, paginatedResponse, listResponse, getErrorMessage, withErrorHandling, isErrorResponse } from './response-builder';
|
|
13
13
|
export { normalizePagination, calculatePaginationMeta, formatPaginationText } from './pagination';
|
|
14
14
|
export type { PaginationMeta, PaginationOptions } from './pagination';
|
|
15
|
+
export { deriveUserRole, getAllowedGroups, findCurrentUserMember, extractWorkspaceRoles, getAllowedGroupsForWorkspace, getMaxRole, checkWorkspaceAccess } from './role-utils';
|
|
15
16
|
export type * from './types';
|
|
16
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/mcp/utils/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Provides centralized access to all utility functions and types
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.formatPaginationText = exports.calculatePaginationMeta = exports.normalizePagination = exports.isErrorResponse = exports.withErrorHandling = exports.getErrorMessage = exports.listResponse = exports.paginatedResponse = exports.jsonResponse = exports.successResponse = exports.errorResponse = exports.textResponse = exports.formatActivityListResponse = exports.formatUserName = exports.formatTimestamp = exports.transformWorkflowFields = exports.transformPhases = exports.transformWorkflow = exports.findFieldByKey = exports.transformFieldValue = exports.transformFields = exports.transformActivities = exports.transformActivity = exports.HailerApiClient = exports.createSuccessResponse = exports.createErrorResponse = exports.handleApiResponse = exports.makeApiCall = exports.HailerApiError = exports.LogTag = exports.LogLevel = exports.logger = exports.createLogger = void 0;
|
|
7
|
+
exports.checkWorkspaceAccess = exports.getMaxRole = exports.getAllowedGroupsForWorkspace = exports.extractWorkspaceRoles = exports.findCurrentUserMember = exports.getAllowedGroups = exports.deriveUserRole = exports.formatPaginationText = exports.calculatePaginationMeta = exports.normalizePagination = exports.isErrorResponse = exports.withErrorHandling = exports.getErrorMessage = exports.listResponse = exports.paginatedResponse = exports.jsonResponse = exports.successResponse = exports.errorResponse = exports.textResponse = exports.formatActivityListResponse = exports.formatUserName = exports.formatTimestamp = exports.transformWorkflowFields = exports.transformPhases = exports.transformWorkflow = exports.findFieldByKey = exports.transformFieldValue = exports.transformFields = exports.transformActivities = exports.transformActivity = exports.HailerApiClient = exports.createSuccessResponse = exports.createErrorResponse = exports.handleApiResponse = exports.makeApiCall = exports.HailerApiError = exports.LogTag = exports.LogLevel = exports.logger = exports.createLogger = void 0;
|
|
8
8
|
// Logging utilities
|
|
9
9
|
var logger_1 = require("../../lib/logger");
|
|
10
10
|
Object.defineProperty(exports, "createLogger", { enumerable: true, get: function () { return logger_1.createLogger; } });
|
|
@@ -50,4 +50,13 @@ var pagination_1 = require("./pagination");
|
|
|
50
50
|
Object.defineProperty(exports, "normalizePagination", { enumerable: true, get: function () { return pagination_1.normalizePagination; } });
|
|
51
51
|
Object.defineProperty(exports, "calculatePaginationMeta", { enumerable: true, get: function () { return pagination_1.calculatePaginationMeta; } });
|
|
52
52
|
Object.defineProperty(exports, "formatPaginationText", { enumerable: true, get: function () { return pagination_1.formatPaginationText; } });
|
|
53
|
+
// Role-based access control
|
|
54
|
+
var role_utils_1 = require("./role-utils");
|
|
55
|
+
Object.defineProperty(exports, "deriveUserRole", { enumerable: true, get: function () { return role_utils_1.deriveUserRole; } });
|
|
56
|
+
Object.defineProperty(exports, "getAllowedGroups", { enumerable: true, get: function () { return role_utils_1.getAllowedGroups; } });
|
|
57
|
+
Object.defineProperty(exports, "findCurrentUserMember", { enumerable: true, get: function () { return role_utils_1.findCurrentUserMember; } });
|
|
58
|
+
Object.defineProperty(exports, "extractWorkspaceRoles", { enumerable: true, get: function () { return role_utils_1.extractWorkspaceRoles; } });
|
|
59
|
+
Object.defineProperty(exports, "getAllowedGroupsForWorkspace", { enumerable: true, get: function () { return role_utils_1.getAllowedGroupsForWorkspace; } });
|
|
60
|
+
Object.defineProperty(exports, "getMaxRole", { enumerable: true, get: function () { return role_utils_1.getMaxRole; } });
|
|
61
|
+
Object.defineProperty(exports, "checkWorkspaceAccess", { enumerable: true, get: function () { return role_utils_1.checkWorkspaceAccess; } });
|
|
53
62
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role-Based Access Control Utilities
|
|
3
|
+
*
|
|
4
|
+
* Derives user role from workspace member flags and maps roles to ToolGroups.
|
|
5
|
+
* Used by UserContextCache to determine tool access at context creation time.
|
|
6
|
+
*/
|
|
7
|
+
import { ToolGroup } from '../tool-registry';
|
|
8
|
+
import { UserRole, WorkspaceMember, WorkspaceInfo } from './types';
|
|
9
|
+
/**
|
|
10
|
+
* Derive user role from workspace member flags
|
|
11
|
+
* Priority: owner > admin > guest > member
|
|
12
|
+
*
|
|
13
|
+
* @param member - Workspace member from v2.core.init
|
|
14
|
+
* @returns UserRole - 'owner' | 'admin' | 'guest' | 'member'
|
|
15
|
+
*/
|
|
16
|
+
export declare function deriveUserRole(member: WorkspaceMember): UserRole;
|
|
17
|
+
/**
|
|
18
|
+
* Map user role to allowed ToolGroups
|
|
19
|
+
*
|
|
20
|
+
* @param role - User role derived from workspace member
|
|
21
|
+
* @param enableNuclear - Optional override to disable NUCLEAR even for owners
|
|
22
|
+
* @returns Array of ToolGroups the user can access
|
|
23
|
+
*/
|
|
24
|
+
export declare function getAllowedGroups(role: UserRole, enableNuclear?: boolean): ToolGroup[];
|
|
25
|
+
/**
|
|
26
|
+
* Find current user in workspace members array
|
|
27
|
+
*
|
|
28
|
+
* @param members - Array of workspace members from init.network.members
|
|
29
|
+
* @param currentUserId - Current user's ID
|
|
30
|
+
* @returns WorkspaceMember if found, undefined otherwise
|
|
31
|
+
*/
|
|
32
|
+
export declare function findCurrentUserMember(members: WorkspaceMember[], currentUserId: string): WorkspaceMember | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Extract user roles from all workspaces
|
|
35
|
+
* Returns a map of workspaceId ā UserRole
|
|
36
|
+
*
|
|
37
|
+
* @param networks - Record of workspace ID to WorkspaceInfo from init.networks
|
|
38
|
+
* @param currentUserId - Current user's ID
|
|
39
|
+
* @returns Record mapping workspace IDs to UserRoles
|
|
40
|
+
*/
|
|
41
|
+
export declare function extractWorkspaceRoles(networks: Record<string, WorkspaceInfo>, currentUserId: string): Record<string, UserRole>;
|
|
42
|
+
/**
|
|
43
|
+
* Get allowed groups for a specific workspace
|
|
44
|
+
*
|
|
45
|
+
* @param workspaceRoles - Map of workspace IDs to UserRoles
|
|
46
|
+
* @param workspaceId - Target workspace ID
|
|
47
|
+
* @param enableNuclear - Optional override to disable NUCLEAR even for owners
|
|
48
|
+
* @returns Array of ToolGroups the user can access in the specified workspace
|
|
49
|
+
*/
|
|
50
|
+
export declare function getAllowedGroupsForWorkspace(workspaceRoles: Record<string, UserRole>, workspaceId: string, enableNuclear?: boolean): ToolGroup[];
|
|
51
|
+
/**
|
|
52
|
+
* Get the highest role across all workspaces
|
|
53
|
+
* Used to determine which tools to show at startup (max potential access)
|
|
54
|
+
*
|
|
55
|
+
* @param workspaceRoles - Map of workspace IDs to UserRoles
|
|
56
|
+
* @returns Highest UserRole across all workspaces
|
|
57
|
+
*/
|
|
58
|
+
export declare function getMaxRole(workspaceRoles: Record<string, UserRole>): UserRole;
|
|
59
|
+
/**
|
|
60
|
+
* Check if user has access to a specific ToolGroup in a workspace
|
|
61
|
+
* Used for runtime permission validation when tools are called with workspaceId
|
|
62
|
+
*
|
|
63
|
+
* @param workspaceRoles - Map of workspace IDs to UserRoles
|
|
64
|
+
* @param currentWorkspaceId - Current default workspace ID
|
|
65
|
+
* @param targetWorkspaceId - Target workspace ID (or undefined to use current)
|
|
66
|
+
* @param requiredGroup - ToolGroup required for the operation
|
|
67
|
+
* @param enableNuclear - Optional override to disable NUCLEAR even for owners
|
|
68
|
+
* @returns Object with allowed boolean and optional reason string
|
|
69
|
+
*/
|
|
70
|
+
export declare function checkWorkspaceAccess(workspaceRoles: Record<string, UserRole>, currentWorkspaceId: string, targetWorkspaceId: string | undefined, requiredGroup: ToolGroup, enableNuclear?: boolean): {
|
|
71
|
+
allowed: boolean;
|
|
72
|
+
reason?: string;
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=role-utils.d.ts.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Role-Based Access Control Utilities
|
|
4
|
+
*
|
|
5
|
+
* Derives user role from workspace member flags and maps roles to ToolGroups.
|
|
6
|
+
* Used by UserContextCache to determine tool access at context creation time.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.deriveUserRole = deriveUserRole;
|
|
10
|
+
exports.getAllowedGroups = getAllowedGroups;
|
|
11
|
+
exports.findCurrentUserMember = findCurrentUserMember;
|
|
12
|
+
exports.extractWorkspaceRoles = extractWorkspaceRoles;
|
|
13
|
+
exports.getAllowedGroupsForWorkspace = getAllowedGroupsForWorkspace;
|
|
14
|
+
exports.getMaxRole = getMaxRole;
|
|
15
|
+
exports.checkWorkspaceAccess = checkWorkspaceAccess;
|
|
16
|
+
const tool_registry_1 = require("../tool-registry");
|
|
17
|
+
/**
|
|
18
|
+
* Derive user role from workspace member flags
|
|
19
|
+
* Priority: owner > admin > guest > member
|
|
20
|
+
*
|
|
21
|
+
* @param member - Workspace member from v2.core.init
|
|
22
|
+
* @returns UserRole - 'owner' | 'admin' | 'guest' | 'member'
|
|
23
|
+
*/
|
|
24
|
+
function deriveUserRole(member) {
|
|
25
|
+
if (member.owner)
|
|
26
|
+
return 'owner';
|
|
27
|
+
if (member.admin)
|
|
28
|
+
return 'admin';
|
|
29
|
+
if (member.guest)
|
|
30
|
+
return 'guest';
|
|
31
|
+
return 'member';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Map user role to allowed ToolGroups
|
|
35
|
+
*
|
|
36
|
+
* @param role - User role derived from workspace member
|
|
37
|
+
* @param enableNuclear - Optional override to disable NUCLEAR even for owners
|
|
38
|
+
* @returns Array of ToolGroups the user can access
|
|
39
|
+
*/
|
|
40
|
+
function getAllowedGroups(role, enableNuclear = true) {
|
|
41
|
+
switch (role) {
|
|
42
|
+
case 'owner':
|
|
43
|
+
return enableNuclear
|
|
44
|
+
? [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE, tool_registry_1.ToolGroup.PLAYGROUND, tool_registry_1.ToolGroup.NUCLEAR]
|
|
45
|
+
: [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE, tool_registry_1.ToolGroup.PLAYGROUND];
|
|
46
|
+
case 'admin':
|
|
47
|
+
return [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE, tool_registry_1.ToolGroup.PLAYGROUND];
|
|
48
|
+
case 'member':
|
|
49
|
+
return [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE];
|
|
50
|
+
case 'guest':
|
|
51
|
+
return [tool_registry_1.ToolGroup.READ];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Find current user in workspace members array
|
|
56
|
+
*
|
|
57
|
+
* @param members - Array of workspace members from init.network.members
|
|
58
|
+
* @param currentUserId - Current user's ID
|
|
59
|
+
* @returns WorkspaceMember if found, undefined otherwise
|
|
60
|
+
*/
|
|
61
|
+
function findCurrentUserMember(members, currentUserId) {
|
|
62
|
+
return members.find(m => m.uid === currentUserId);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Extract user roles from all workspaces
|
|
66
|
+
* Returns a map of workspaceId ā UserRole
|
|
67
|
+
*
|
|
68
|
+
* @param networks - Record of workspace ID to WorkspaceInfo from init.networks
|
|
69
|
+
* @param currentUserId - Current user's ID
|
|
70
|
+
* @returns Record mapping workspace IDs to UserRoles
|
|
71
|
+
*/
|
|
72
|
+
function extractWorkspaceRoles(networks, currentUserId) {
|
|
73
|
+
const roles = {};
|
|
74
|
+
for (const [wsId, network] of Object.entries(networks)) {
|
|
75
|
+
const members = (network.members || []);
|
|
76
|
+
const member = findCurrentUserMember(members, currentUserId);
|
|
77
|
+
roles[wsId] = member ? deriveUserRole(member) : 'guest';
|
|
78
|
+
}
|
|
79
|
+
return roles;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get allowed groups for a specific workspace
|
|
83
|
+
*
|
|
84
|
+
* @param workspaceRoles - Map of workspace IDs to UserRoles
|
|
85
|
+
* @param workspaceId - Target workspace ID
|
|
86
|
+
* @param enableNuclear - Optional override to disable NUCLEAR even for owners
|
|
87
|
+
* @returns Array of ToolGroups the user can access in the specified workspace
|
|
88
|
+
*/
|
|
89
|
+
function getAllowedGroupsForWorkspace(workspaceRoles, workspaceId, enableNuclear = true) {
|
|
90
|
+
const role = workspaceRoles[workspaceId] || 'guest';
|
|
91
|
+
return getAllowedGroups(role, enableNuclear);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get the highest role across all workspaces
|
|
95
|
+
* Used to determine which tools to show at startup (max potential access)
|
|
96
|
+
*
|
|
97
|
+
* @param workspaceRoles - Map of workspace IDs to UserRoles
|
|
98
|
+
* @returns Highest UserRole across all workspaces
|
|
99
|
+
*/
|
|
100
|
+
function getMaxRole(workspaceRoles) {
|
|
101
|
+
const roleOrder = ['guest', 'member', 'admin', 'owner'];
|
|
102
|
+
let maxRole = 'guest';
|
|
103
|
+
for (const role of Object.values(workspaceRoles)) {
|
|
104
|
+
if (roleOrder.indexOf(role) > roleOrder.indexOf(maxRole)) {
|
|
105
|
+
maxRole = role;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return maxRole;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get minimum role required for a ToolGroup
|
|
112
|
+
* Used for error messages
|
|
113
|
+
*/
|
|
114
|
+
function getRequiredRoleForGroup(group) {
|
|
115
|
+
switch (group) {
|
|
116
|
+
case tool_registry_1.ToolGroup.READ:
|
|
117
|
+
return 'guest';
|
|
118
|
+
case tool_registry_1.ToolGroup.WRITE:
|
|
119
|
+
return 'member';
|
|
120
|
+
case tool_registry_1.ToolGroup.PLAYGROUND:
|
|
121
|
+
return 'admin';
|
|
122
|
+
case tool_registry_1.ToolGroup.NUCLEAR:
|
|
123
|
+
return 'owner';
|
|
124
|
+
default:
|
|
125
|
+
return 'owner';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if user has access to a specific ToolGroup in a workspace
|
|
130
|
+
* Used for runtime permission validation when tools are called with workspaceId
|
|
131
|
+
*
|
|
132
|
+
* @param workspaceRoles - Map of workspace IDs to UserRoles
|
|
133
|
+
* @param currentWorkspaceId - Current default workspace ID
|
|
134
|
+
* @param targetWorkspaceId - Target workspace ID (or undefined to use current)
|
|
135
|
+
* @param requiredGroup - ToolGroup required for the operation
|
|
136
|
+
* @param enableNuclear - Optional override to disable NUCLEAR even for owners
|
|
137
|
+
* @returns Object with allowed boolean and optional reason string
|
|
138
|
+
*/
|
|
139
|
+
function checkWorkspaceAccess(workspaceRoles, currentWorkspaceId, targetWorkspaceId, requiredGroup, enableNuclear = true) {
|
|
140
|
+
const effectiveWsId = targetWorkspaceId || currentWorkspaceId;
|
|
141
|
+
const role = workspaceRoles[effectiveWsId] || 'guest';
|
|
142
|
+
const allowedGroups = getAllowedGroups(role, enableNuclear);
|
|
143
|
+
if (!allowedGroups.includes(requiredGroup)) {
|
|
144
|
+
return {
|
|
145
|
+
allowed: false,
|
|
146
|
+
reason: `Insufficient permissions in workspace '${effectiveWsId.slice(-6)}'. Your role '${role}' doesn't have access to ${requiredGroup} tools. Required: ${getRequiredRoleForGroup(requiredGroup)} or higher.`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return { allowed: true };
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=role-utils.js.map
|
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
* Shared type definitions for Hailer MCP Server
|
|
3
3
|
* Consolidates interfaces used across multiple files
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* User role in workspace (derived from member flags)
|
|
7
|
+
* Used to determine which ToolGroups are available to the user
|
|
8
|
+
*/
|
|
9
|
+
export type UserRole = 'guest' | 'member' | 'admin' | 'owner';
|
|
10
|
+
/**
|
|
11
|
+
* Workspace member from v2.core.init response
|
|
12
|
+
* Contains role flags that determine user permissions
|
|
13
|
+
*
|
|
14
|
+
* Schema reference: hailer-api/src/validation/sharedSchemas.ts (validWorkspaceMemberSchema)
|
|
15
|
+
*/
|
|
16
|
+
export interface WorkspaceMember {
|
|
17
|
+
uid: string;
|
|
18
|
+
title?: string;
|
|
19
|
+
owner?: boolean;
|
|
20
|
+
admin?: boolean;
|
|
21
|
+
guest?: boolean;
|
|
22
|
+
inviter?: boolean;
|
|
23
|
+
feedAdmin?: boolean;
|
|
24
|
+
customRole?: string;
|
|
25
|
+
joined: number;
|
|
26
|
+
fields?: Record<string, string | string[] | null>;
|
|
27
|
+
}
|
|
5
28
|
export type { CleanActivity, FieldValue, WorkflowInfo, PhaseInfo, FieldInfo, UserInfo, } from './data-transformers';
|
|
6
29
|
export interface HailerField {
|
|
7
30
|
data: any[];
|
|
@@ -173,7 +196,7 @@ export interface WorkspaceInfo {
|
|
|
173
196
|
_id: string;
|
|
174
197
|
name: string;
|
|
175
198
|
description?: string;
|
|
176
|
-
members?:
|
|
199
|
+
members?: WorkspaceMember[];
|
|
177
200
|
settings?: Record<string, any>;
|
|
178
201
|
}
|
|
179
202
|
export interface SignalData {
|
package/dist/stdio-server.js
CHANGED
|
@@ -32,15 +32,21 @@ async function startStdioServer(toolRegistry) {
|
|
|
32
32
|
name: 'hailer-mcp-server',
|
|
33
33
|
version: '1.0.0',
|
|
34
34
|
});
|
|
35
|
-
// Get
|
|
35
|
+
// Get user context to determine role-based tool access
|
|
36
|
+
const userContext = await UserContextCache_1.UserContextCache.getContext(apiKey);
|
|
37
|
+
// Show ALL tools - permission checks happen at runtime via checkWorkspaceAccess()
|
|
38
|
+
// ENABLE_NUCLEAR_TOOLS still controls whether NUCLEAR tools are available at all
|
|
36
39
|
const allowedGroups = config_1.environment.ENABLE_NUCLEAR_TOOLS
|
|
37
40
|
? [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE, tool_registry_1.ToolGroup.PLAYGROUND, tool_registry_1.ToolGroup.NUCLEAR]
|
|
38
41
|
: [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE, tool_registry_1.ToolGroup.PLAYGROUND];
|
|
39
42
|
// Get tools with their original Zod schemas - MCP SDK requires Zod, not JSON Schema
|
|
40
43
|
const tools = toolRegistry.getToolsWithZodSchemas({ allowedGroups });
|
|
41
|
-
logger.info('
|
|
44
|
+
logger.info('Tools registered (runtime permission checks apply)', {
|
|
45
|
+
currentWorkspaceId: userContext.currentWorkspaceId,
|
|
46
|
+
currentRole: userContext.workspaceRoles[userContext.currentWorkspaceId] || 'guest',
|
|
42
47
|
toolCount: tools.length,
|
|
43
|
-
|
|
48
|
+
workspaceCount: Object.keys(userContext.workspaceRoles).length,
|
|
49
|
+
nuclearEnabled: config_1.environment.ENABLE_NUCLEAR_TOOLS
|
|
44
50
|
});
|
|
45
51
|
// Register each tool with the MCP server using Zod schemas
|
|
46
52
|
for (const tool of tools) {
|
package/manifest.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpb_version": "0.1",
|
|
3
|
+
"name": "hailer-mcp",
|
|
4
|
+
"version": "1.0.29",
|
|
5
|
+
"display_name": "Hailer MCP",
|
|
6
|
+
"description": "Connect Claude to Hailer workspaces for workflow management, activity tracking, discussions, and insights",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Hailer",
|
|
9
|
+
"url": "https://hailer.com"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/hailer/hailer-mcp"
|
|
14
|
+
},
|
|
15
|
+
"server": {
|
|
16
|
+
"type": "stdio",
|
|
17
|
+
"entry_point": "dist/app.js",
|
|
18
|
+
"mcp_config": {
|
|
19
|
+
"command": "node",
|
|
20
|
+
"args": ["${serverPath}/dist/app.js"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"user_config": [
|
|
24
|
+
{
|
|
25
|
+
"id": "mcp_client_api_key",
|
|
26
|
+
"name": "Client API Key",
|
|
27
|
+
"description": "Identifier for this client (any string, e.g. 'claude-desktop')",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"required": true,
|
|
30
|
+
"default": "claude-desktop",
|
|
31
|
+
"env_var": "MCP_CLIENT_API_KEY"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "hailer_email",
|
|
35
|
+
"name": "Hailer Email",
|
|
36
|
+
"description": "Your Hailer account email",
|
|
37
|
+
"type": "string",
|
|
38
|
+
"required": true,
|
|
39
|
+
"env_var": "HAILER_EMAIL"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "hailer_password",
|
|
43
|
+
"name": "Hailer Password",
|
|
44
|
+
"description": "Your Hailer account password",
|
|
45
|
+
"type": "string",
|
|
46
|
+
"required": true,
|
|
47
|
+
"sensitive": true,
|
|
48
|
+
"env_var": "HAILER_PASSWORD"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "hailer_api_url",
|
|
52
|
+
"name": "API URL",
|
|
53
|
+
"description": "Hailer API URL (default: https://api.hailer.com)",
|
|
54
|
+
"type": "string",
|
|
55
|
+
"required": false,
|
|
56
|
+
"default": "https://api.hailer.com",
|
|
57
|
+
"env_var": "HAILER_API_URL"
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"categories": ["productivity", "workflow", "collaboration"],
|
|
61
|
+
"keywords": ["hailer", "workflow", "activity", "discussion", "insight", "crm"]
|
|
62
|
+
}
|