mstro-app 0.3.1 → 0.3.5
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/README.md +3 -19
- package/bin/mstro.js +15 -177
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-integration.js +43 -135
- package/dist/server/mcp/bouncer-integration.js.map +1 -1
- package/dist/server/services/platform.d.ts.map +1 -1
- package/dist/server/services/platform.js +2 -13
- package/dist/server/services/platform.js.map +1 -1
- package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-explorer-handlers.js +17 -1
- package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-utils.d.ts.map +1 -1
- package/dist/server/services/websocket/file-utils.js +26 -20
- package/dist/server/services/websocket/file-utils.js.map +1 -1
- package/dist/server/services/websocket/types.d.ts +1 -1
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/dist/server/utils/paths.d.ts +0 -12
- package/dist/server/utils/paths.d.ts.map +1 -1
- package/dist/server/utils/paths.js +0 -12
- package/dist/server/utils/paths.js.map +1 -1
- package/package.json +1 -2
- package/server/README.md +0 -1
- package/server/mcp/README.md +0 -5
- package/server/mcp/bouncer-integration.ts +55 -210
- package/server/services/platform.ts +2 -12
- package/server/services/websocket/file-explorer-handlers.ts +16 -1
- package/server/services/websocket/file-utils.ts +29 -24
- package/server/services/websocket/types.ts +1 -0
- package/server/utils/paths.ts +0 -14
- package/bin/configure-claude.js +0 -298
- package/dist/server/mcp/bouncer-cli.d.ts +0 -3
- package/dist/server/mcp/bouncer-cli.d.ts.map +0 -1
- package/dist/server/mcp/bouncer-cli.js +0 -138
- package/dist/server/mcp/bouncer-cli.js.map +0 -1
- package/hooks/bouncer.sh +0 -159
- package/server/mcp/bouncer-cli.ts +0 -180
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
3
|
-
// Licensed under the MIT License. See LICENSE file for details.
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Bouncer CLI - Shell-callable wrapper for Mstro security bouncer
|
|
7
|
-
*
|
|
8
|
-
* This CLI reads Claude Code hook input from stdin and returns a security decision.
|
|
9
|
-
* It's designed to be called from bouncer.sh.
|
|
10
|
-
*
|
|
11
|
-
* Input (stdin): Claude Code PreToolUse hook JSON payload
|
|
12
|
-
* Output (stdout): JSON decision { decision: "allow"|"deny", reason: string }
|
|
13
|
-
*
|
|
14
|
-
* The hook payload includes conversation context that we pass to the bouncer
|
|
15
|
-
* so it can make context-aware decisions.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { type BouncerReviewRequest, reviewOperation } from './bouncer-integration.js';
|
|
19
|
-
|
|
20
|
-
interface HookInput {
|
|
21
|
-
// Tool identification (mstro: toolName, Claude Code: tool_name)
|
|
22
|
-
tool_name?: string;
|
|
23
|
-
toolName?: string;
|
|
24
|
-
// Tool parameters (mstro: input/toolInput, Claude Code: tool_input)
|
|
25
|
-
input?: Record<string, unknown>;
|
|
26
|
-
toolInput?: Record<string, unknown>;
|
|
27
|
-
tool_input?: Record<string, unknown>;
|
|
28
|
-
// Claude Code hook metadata
|
|
29
|
-
hook_event_name?: string;
|
|
30
|
-
transcript_path?: string;
|
|
31
|
-
permission_mode?: string;
|
|
32
|
-
cwd?: string;
|
|
33
|
-
// Mstro conversation context
|
|
34
|
-
session_id?: string;
|
|
35
|
-
conversation?: {
|
|
36
|
-
messages?: Array<{
|
|
37
|
-
role: 'user' | 'assistant';
|
|
38
|
-
content: string;
|
|
39
|
-
}>;
|
|
40
|
-
last_user_message?: string;
|
|
41
|
-
};
|
|
42
|
-
// Common fields
|
|
43
|
-
tool_use_id?: string;
|
|
44
|
-
working_directory?: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Read all data from stdin (Node.js compatible)
|
|
49
|
-
*/
|
|
50
|
-
async function readStdin(): Promise<string> {
|
|
51
|
-
return new Promise((resolve, reject) => {
|
|
52
|
-
const chunks: Buffer[] = [];
|
|
53
|
-
process.stdin.on('data', (chunk) => chunks.push(chunk));
|
|
54
|
-
process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8').trim()));
|
|
55
|
-
process.stdin.on('error', reject);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function buildOperationString(toolName: string, toolInput: Record<string, unknown>): string {
|
|
60
|
-
if (toolName === 'Bash' && toolInput.command) {
|
|
61
|
-
return `${toolName}: ${toolInput.command}`;
|
|
62
|
-
}
|
|
63
|
-
if (['Write', 'Edit', 'Read'].includes(toolName)) {
|
|
64
|
-
const filePath = toolInput.file_path || toolInput.filePath || toolInput.path;
|
|
65
|
-
return filePath ? `${toolName}: ${filePath}` : `${toolName}: ${JSON.stringify(toolInput)}`;
|
|
66
|
-
}
|
|
67
|
-
return `${toolName}: ${JSON.stringify(toolInput)}`;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Detect whether the caller is Claude Code (vs mstro).
|
|
72
|
-
* Claude Code includes hook_event_name in its payload.
|
|
73
|
-
*/
|
|
74
|
-
function isClaudeCodeHook(hookInput: HookInput): boolean {
|
|
75
|
-
return hookInput.hook_event_name === 'PreToolUse';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Format a bouncer decision for the calling system.
|
|
80
|
-
* Claude Code expects: { hookSpecificOutput: { permissionDecision, ... } }
|
|
81
|
-
* Mstro expects: { decision, reason, confidence, threatLevel, alternative }
|
|
82
|
-
*/
|
|
83
|
-
function formatDecisionOutput(
|
|
84
|
-
decision: { decision: string; reasoning: string; confidence?: number; threatLevel?: string; alternative?: string },
|
|
85
|
-
claudeCode: boolean
|
|
86
|
-
): string {
|
|
87
|
-
const mappedDecision = decision.decision === 'deny' ? 'deny' : 'allow';
|
|
88
|
-
if (claudeCode) {
|
|
89
|
-
return JSON.stringify({
|
|
90
|
-
hookSpecificOutput: {
|
|
91
|
-
hookEventName: 'PreToolUse',
|
|
92
|
-
permissionDecision: mappedDecision,
|
|
93
|
-
permissionDecisionReason: decision.reasoning,
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
return JSON.stringify({
|
|
98
|
-
decision: mappedDecision,
|
|
99
|
-
reason: decision.reasoning,
|
|
100
|
-
confidence: decision.confidence,
|
|
101
|
-
threatLevel: decision.threatLevel,
|
|
102
|
-
alternative: decision.alternative,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function formatSimpleOutput(d: 'allow' | 'deny', reason: string, claudeCode: boolean): string {
|
|
107
|
-
if (claudeCode) {
|
|
108
|
-
return JSON.stringify({
|
|
109
|
-
hookSpecificOutput: {
|
|
110
|
-
hookEventName: 'PreToolUse',
|
|
111
|
-
permissionDecision: d,
|
|
112
|
-
permissionDecisionReason: reason,
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
return JSON.stringify({ decision: d, reason });
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function extractConversationContext(hookInput: HookInput): string | undefined {
|
|
120
|
-
const lastUserMessage = hookInput.conversation?.last_user_message;
|
|
121
|
-
if (lastUserMessage) return `User's request: "${lastUserMessage}"`;
|
|
122
|
-
|
|
123
|
-
const recentMessages = hookInput.conversation?.messages?.slice(-5);
|
|
124
|
-
if (recentMessages?.length) {
|
|
125
|
-
return `Recent conversation:\n${recentMessages.map(m => `${m.role}: ${m.content}`).join('\n')}`;
|
|
126
|
-
}
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async function main() {
|
|
131
|
-
const inputStr = await readStdin();
|
|
132
|
-
|
|
133
|
-
if (!inputStr) {
|
|
134
|
-
// Can't detect caller without input — output both-compatible allow
|
|
135
|
-
console.log(JSON.stringify({ decision: 'allow', reason: 'Empty input, allowing' }));
|
|
136
|
-
process.exit(0);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
let hookInput: HookInput;
|
|
140
|
-
try {
|
|
141
|
-
hookInput = JSON.parse(inputStr);
|
|
142
|
-
} catch (e) {
|
|
143
|
-
console.error('[bouncer-cli] Failed to parse input JSON:', e);
|
|
144
|
-
console.log(JSON.stringify({ decision: 'allow', reason: 'Invalid JSON input, allowing' }));
|
|
145
|
-
process.exit(0);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const claudeCode = isClaudeCodeHook(hookInput);
|
|
149
|
-
const toolName = hookInput.tool_name || hookInput.toolName || 'unknown';
|
|
150
|
-
// Claude Code: tool_input, mstro: input/toolInput
|
|
151
|
-
const toolInput = hookInput.tool_input || hookInput.input || hookInput.toolInput || {};
|
|
152
|
-
const userRequestContext = extractConversationContext(hookInput);
|
|
153
|
-
const lastUserMessage = hookInput.conversation?.last_user_message;
|
|
154
|
-
const recentMessages = hookInput.conversation?.messages?.slice(-5);
|
|
155
|
-
|
|
156
|
-
const bouncerRequest: BouncerReviewRequest = {
|
|
157
|
-
operation: buildOperationString(toolName, toolInput),
|
|
158
|
-
context: {
|
|
159
|
-
purpose: userRequestContext || 'Tool use request from Claude',
|
|
160
|
-
// Claude Code: cwd, mstro: working_directory
|
|
161
|
-
workingDirectory: hookInput.cwd || hookInput.working_directory || process.cwd(),
|
|
162
|
-
toolName,
|
|
163
|
-
toolInput,
|
|
164
|
-
userRequest: lastUserMessage,
|
|
165
|
-
conversationHistory: recentMessages?.map(m => `${m.role}: ${m.content}`),
|
|
166
|
-
sessionId: hookInput.session_id,
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
const decision = await reviewOperation(bouncerRequest);
|
|
172
|
-
console.log(formatDecisionOutput(decision, claudeCode));
|
|
173
|
-
} catch (error: unknown) {
|
|
174
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
175
|
-
console.error('[bouncer-cli] Error:', message);
|
|
176
|
-
console.log(formatSimpleOutput('allow', `Bouncer error: ${message}. Allowing to avoid blocking.`, claudeCode));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
main();
|