@massu/core 0.6.1 → 0.6.3
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/commands/_shared-preamble.md +14 -0
- package/commands/_shared-references/verification-table.md +0 -3
- package/commands/massu-ci-fix.md +2 -2
- package/commands/massu-gap-enhancement-analyzer.md +85 -345
- package/commands/massu-golden-path/references/approval-points.md +9 -12
- package/commands/massu-golden-path/references/competitive-mode.md +9 -7
- package/commands/massu-golden-path/references/error-handling.md +4 -2
- package/commands/massu-golden-path/references/phase-0-requirements.md +3 -3
- package/commands/massu-golden-path/references/phase-1-plan-creation.md +41 -52
- package/commands/massu-golden-path/references/phase-2-implementation.md +50 -157
- package/commands/massu-golden-path/references/phase-2.5-gap-analyzer.md +14 -48
- package/commands/massu-golden-path/references/phase-3-simplify.md +5 -5
- package/commands/massu-golden-path/references/phase-4-commit.md +20 -46
- package/commands/massu-golden-path/references/phase-5-push.md +14 -47
- package/commands/massu-golden-path/references/phase-6-completion.md +8 -58
- package/commands/massu-golden-path.md +27 -43
- package/commands/massu-loop/references/checkpoint-audit.md +14 -18
- package/commands/massu-loop/references/guardrails.md +3 -3
- package/commands/massu-loop/references/iteration-structure.md +46 -14
- package/commands/massu-loop/references/loop-controller.md +72 -63
- package/commands/massu-loop/references/plan-extraction.md +19 -11
- package/commands/massu-loop/references/vr-plan-spec.md +20 -28
- package/commands/massu-loop.md +36 -56
- package/commands/massu-review.md +2 -2
- package/dist/cli.js +442 -81
- package/package.json +1 -1
- package/src/mcp-bridge-tools.ts +459 -0
- package/src/tools.ts +8 -0
- package/commands/massu-golden-path/references/phase-3.5-security-audit.md +0 -108
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massu/core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, tiered tooling (12 free / 72 total), 55+ workflow commands, 11 agents, 20+ patterns",
|
|
6
6
|
"main": "src/server.ts",
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
+
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
+
|
|
4
|
+
import { spawn, type ChildProcess } from 'child_process';
|
|
5
|
+
import { readFileSync, existsSync } from 'fs';
|
|
6
|
+
import { resolve } from 'path';
|
|
7
|
+
import { getConfig, getProjectRoot } from './config.ts';
|
|
8
|
+
import type { ToolDefinition, ToolResult } from './tools.ts';
|
|
9
|
+
|
|
10
|
+
/** Prefix a base tool name with the configured tool prefix. */
|
|
11
|
+
function p(baseName: string): string {
|
|
12
|
+
return `${getConfig().toolPrefix}_${baseName}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ============================================================
|
|
16
|
+
// MCP Bridge: Cross-project tool mesh
|
|
17
|
+
// ============================================================
|
|
18
|
+
|
|
19
|
+
interface MCPServerConfig {
|
|
20
|
+
command: string;
|
|
21
|
+
args?: string[];
|
|
22
|
+
cwd?: string;
|
|
23
|
+
type?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface MCPConnection {
|
|
27
|
+
config: MCPServerConfig;
|
|
28
|
+
process: ChildProcess | null;
|
|
29
|
+
connected: boolean;
|
|
30
|
+
tools: MCPToolDef[];
|
|
31
|
+
requestId: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface MCPToolDef {
|
|
35
|
+
name: string;
|
|
36
|
+
description: string;
|
|
37
|
+
inputSchema: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Active MCP connections (module-level to persist across tool calls)
|
|
41
|
+
const connections = new Map<string, MCPConnection>();
|
|
42
|
+
|
|
43
|
+
// Clean up all MCP subprocesses on exit to prevent orphans
|
|
44
|
+
process.on('exit', () => {
|
|
45
|
+
for (const [, conn] of connections) {
|
|
46
|
+
if (conn.process && !conn.process.killed) {
|
|
47
|
+
try { conn.process.kill('SIGTERM'); } catch { /* already dead */ }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
process.on('SIGTERM', () => {
|
|
52
|
+
for (const [name] of connections) disconnectServer(name);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Environment variables safe to forward to MCP subprocesses.
|
|
57
|
+
// Generic for any Massu npm user — no project-specific hardcoding.
|
|
58
|
+
const ENV_ALLOW_LIST = new Set([
|
|
59
|
+
'PATH', 'HOME', 'LANG', 'LC_ALL', 'TERM',
|
|
60
|
+
'PYTHONPATH', 'NODE_PATH',
|
|
61
|
+
]);
|
|
62
|
+
const ENV_DENY_PATTERNS = [
|
|
63
|
+
'PRIVATE_KEY', 'SECRET_KEY', 'API_SECRET',
|
|
64
|
+
'AUTH_DISABLED', 'COLD_STORAGE',
|
|
65
|
+
'PASSWORD', 'TOKEN',
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
function buildSubprocessEnv(): Record<string, string> {
|
|
69
|
+
const env: Record<string, string> = {};
|
|
70
|
+
// Derive safe env prefixes from the project name in massu.config.yaml.
|
|
71
|
+
// e.g., project name "hedge" -> allow HEDGE_CONFIG_, HEDGE_LOG_ etc.
|
|
72
|
+
// This makes the bridge work for any Massu user's project without hardcoding.
|
|
73
|
+
const projectName = getConfig().project?.name?.toUpperCase() || '';
|
|
74
|
+
const safePrefixes = projectName
|
|
75
|
+
? [`${projectName}_CONFIG_`, `${projectName}_LOG_`]
|
|
76
|
+
: [];
|
|
77
|
+
|
|
78
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
79
|
+
if (!value) continue;
|
|
80
|
+
if (ENV_DENY_PATTERNS.some(pat => key.includes(pat))) continue;
|
|
81
|
+
if (ENV_ALLOW_LIST.has(key)) {
|
|
82
|
+
env[key] = value;
|
|
83
|
+
} else if (safePrefixes.length > 0 && safePrefixes.some(pfx => key.startsWith(pfx))) {
|
|
84
|
+
env[key] = value;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return env;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Load MCP server configurations from .mcp.json in project root.
|
|
92
|
+
* Only loads servers that are NOT the massu server itself (avoid self-connection).
|
|
93
|
+
*/
|
|
94
|
+
function loadMcpConfig(): Record<string, MCPServerConfig> {
|
|
95
|
+
const root = getProjectRoot();
|
|
96
|
+
const mcpPath = resolve(root, '.mcp.json');
|
|
97
|
+
if (!existsSync(mcpPath)) return {};
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const raw = JSON.parse(readFileSync(mcpPath, 'utf-8'));
|
|
101
|
+
const servers: Record<string, MCPServerConfig> = {};
|
|
102
|
+
const mcpServers = raw.mcpServers || {};
|
|
103
|
+
const pfx = getConfig().toolPrefix;
|
|
104
|
+
|
|
105
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
106
|
+
// Skip self (the massu server)
|
|
107
|
+
if (name === pfx) continue;
|
|
108
|
+
servers[name] = config as MCPServerConfig;
|
|
109
|
+
}
|
|
110
|
+
return servers;
|
|
111
|
+
} catch (e) {
|
|
112
|
+
console.error('[mcp-bridge] Failed to parse .mcp.json:', e);
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Send a JSON-RPC 2.0 request to an MCP subprocess and wait for response.
|
|
119
|
+
*/
|
|
120
|
+
async function mcpRequest(
|
|
121
|
+
conn: MCPConnection,
|
|
122
|
+
method: string,
|
|
123
|
+
params: Record<string, unknown> = {},
|
|
124
|
+
): Promise<Record<string, unknown>> {
|
|
125
|
+
if (!conn.process || !conn.process.stdin || !conn.process.stdout) {
|
|
126
|
+
throw new Error('MCP process not connected');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
conn.requestId++;
|
|
130
|
+
const request = {
|
|
131
|
+
jsonrpc: '2.0',
|
|
132
|
+
id: conn.requestId,
|
|
133
|
+
method,
|
|
134
|
+
params,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
const timeout = setTimeout(() => {
|
|
139
|
+
conn.process?.stdout?.removeListener('data', onData);
|
|
140
|
+
reject(new Error(`MCP request timed out: ${method}`));
|
|
141
|
+
}, 30000);
|
|
142
|
+
|
|
143
|
+
// Buffer partial chunks until we have a complete newline-delimited response
|
|
144
|
+
let buffer = '';
|
|
145
|
+
const onData = (data: Buffer) => {
|
|
146
|
+
buffer += data.toString('utf-8');
|
|
147
|
+
const lines = buffer.split('\n');
|
|
148
|
+
// Keep the last incomplete chunk in the buffer
|
|
149
|
+
buffer = lines.pop() || '';
|
|
150
|
+
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
if (!line.trim()) continue;
|
|
153
|
+
try {
|
|
154
|
+
const response = JSON.parse(line);
|
|
155
|
+
if (response.id === conn.requestId) {
|
|
156
|
+
clearTimeout(timeout);
|
|
157
|
+
conn.process?.stdout?.removeListener('data', onData);
|
|
158
|
+
if (response.error) {
|
|
159
|
+
reject(new Error(`MCP error ${response.error.code}: ${response.error.message}`));
|
|
160
|
+
} else {
|
|
161
|
+
resolve(response.result || {});
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
} catch (e) {
|
|
166
|
+
clearTimeout(timeout);
|
|
167
|
+
conn.process?.stdout?.removeListener('data', onData);
|
|
168
|
+
reject(new Error(`Failed to parse MCP response: ${e}`));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
conn.process!.stdout!.on('data', onData);
|
|
175
|
+
conn.process!.stdin!.write(JSON.stringify(request) + '\n');
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Connect to an MCP server subprocess and perform handshake.
|
|
181
|
+
*/
|
|
182
|
+
async function connectToServer(name: string, config: MCPServerConfig): Promise<MCPConnection> {
|
|
183
|
+
const existing = connections.get(name);
|
|
184
|
+
if (existing?.connected && existing.process && !existing.process.killed) {
|
|
185
|
+
return existing;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const root = getProjectRoot();
|
|
189
|
+
const cwd = config.cwd ? resolve(root, config.cwd) : root;
|
|
190
|
+
const args = config.args || [];
|
|
191
|
+
|
|
192
|
+
const proc = spawn(config.command, args, {
|
|
193
|
+
cwd,
|
|
194
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
195
|
+
env: buildSubprocessEnv(),
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const conn: MCPConnection = {
|
|
199
|
+
config,
|
|
200
|
+
process: proc,
|
|
201
|
+
connected: false,
|
|
202
|
+
tools: [],
|
|
203
|
+
requestId: 0,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
// Handshake
|
|
208
|
+
await mcpRequest(conn, 'initialize', {
|
|
209
|
+
protocolVersion: '2024-11-05',
|
|
210
|
+
capabilities: {},
|
|
211
|
+
clientInfo: { name: 'massu-mcp-bridge', version: '1.0.0' },
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Send initialized notification
|
|
215
|
+
if (proc.stdin) {
|
|
216
|
+
proc.stdin.write(JSON.stringify({
|
|
217
|
+
jsonrpc: '2.0',
|
|
218
|
+
method: 'notifications/initialized',
|
|
219
|
+
params: {},
|
|
220
|
+
}) + '\n');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
conn.connected = true;
|
|
224
|
+
|
|
225
|
+
// Discover tools
|
|
226
|
+
const toolsResult = await mcpRequest(conn, 'tools/list', {}) as { tools?: MCPToolDef[] };
|
|
227
|
+
conn.tools = toolsResult.tools || [];
|
|
228
|
+
|
|
229
|
+
// Store in map only after successful handshake
|
|
230
|
+
connections.set(name, conn);
|
|
231
|
+
return conn;
|
|
232
|
+
} catch (e) {
|
|
233
|
+
// Clean up on handshake failure — don't leave a broken connection in the map
|
|
234
|
+
if (!proc.killed) proc.kill('SIGTERM');
|
|
235
|
+
throw e;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Disconnect an MCP server.
|
|
241
|
+
*/
|
|
242
|
+
function disconnectServer(name: string): void {
|
|
243
|
+
const conn = connections.get(name);
|
|
244
|
+
if (conn) {
|
|
245
|
+
conn.connected = false;
|
|
246
|
+
if (conn.process && !conn.process.killed) {
|
|
247
|
+
conn.process.kill('SIGTERM');
|
|
248
|
+
// Escalate to SIGKILL if SIGTERM doesn't work within 3 seconds
|
|
249
|
+
const proc = conn.process;
|
|
250
|
+
setTimeout(() => {
|
|
251
|
+
if (!proc.killed) {
|
|
252
|
+
try { proc.kill('SIGKILL'); } catch { /* already dead */ }
|
|
253
|
+
}
|
|
254
|
+
}, 3000);
|
|
255
|
+
}
|
|
256
|
+
connections.delete(name);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function text(s: string): ToolResult {
|
|
261
|
+
return { content: [{ type: 'text', text: s }] };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ============================================================
|
|
265
|
+
// Tool Definitions
|
|
266
|
+
// ============================================================
|
|
267
|
+
|
|
268
|
+
export function getMcpBridgeToolDefinitions(): ToolDefinition[] {
|
|
269
|
+
return [
|
|
270
|
+
{
|
|
271
|
+
name: p('mcp_servers'),
|
|
272
|
+
description: 'List all configured MCP servers from .mcp.json and their connection status.',
|
|
273
|
+
inputSchema: {
|
|
274
|
+
type: 'object',
|
|
275
|
+
properties: {},
|
|
276
|
+
required: [],
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: p('mcp_tools'),
|
|
281
|
+
description: 'List tools available from a specific MCP server. Connects to the server if not already connected.',
|
|
282
|
+
inputSchema: {
|
|
283
|
+
type: 'object',
|
|
284
|
+
properties: {
|
|
285
|
+
server: { type: 'string', description: 'MCP server name from .mcp.json' },
|
|
286
|
+
},
|
|
287
|
+
required: ['server'],
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: p('mcp_call'),
|
|
292
|
+
description: 'Call a tool on a connected MCP server. Connects automatically if needed.',
|
|
293
|
+
inputSchema: {
|
|
294
|
+
type: 'object',
|
|
295
|
+
properties: {
|
|
296
|
+
server: { type: 'string', description: 'MCP server name from .mcp.json' },
|
|
297
|
+
tool: { type: 'string', description: 'Tool name to call on the remote server' },
|
|
298
|
+
arguments: {
|
|
299
|
+
type: 'object',
|
|
300
|
+
description: 'Arguments to pass to the remote tool',
|
|
301
|
+
additionalProperties: true,
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
required: ['server', 'tool'],
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: p('mcp_status'),
|
|
309
|
+
description: 'Health check all MCP server connections. Shows which are connected, disconnected, or errored.',
|
|
310
|
+
inputSchema: {
|
|
311
|
+
type: 'object',
|
|
312
|
+
properties: {},
|
|
313
|
+
required: [],
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function isMcpBridgeTool(name: string): boolean {
|
|
320
|
+
const pfx = getConfig().toolPrefix;
|
|
321
|
+
return name.startsWith(`${pfx}_mcp_`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export async function handleMcpBridgeToolCall(
|
|
325
|
+
name: string,
|
|
326
|
+
args: Record<string, unknown>,
|
|
327
|
+
): Promise<ToolResult> {
|
|
328
|
+
const pfx = getConfig().toolPrefix;
|
|
329
|
+
const baseName = name.startsWith(`${pfx}_`) ? name.slice(pfx.length + 1) : name;
|
|
330
|
+
|
|
331
|
+
switch (baseName) {
|
|
332
|
+
case 'mcp_servers':
|
|
333
|
+
return handleMcpServers();
|
|
334
|
+
case 'mcp_tools':
|
|
335
|
+
return await handleMcpTools(args.server as string);
|
|
336
|
+
case 'mcp_call':
|
|
337
|
+
return await handleMcpCall(
|
|
338
|
+
args.server as string,
|
|
339
|
+
args.tool as string,
|
|
340
|
+
(args.arguments as Record<string, unknown>) || {},
|
|
341
|
+
);
|
|
342
|
+
case 'mcp_status':
|
|
343
|
+
return handleMcpStatus();
|
|
344
|
+
default:
|
|
345
|
+
return text(`Unknown MCP bridge tool: ${name}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ============================================================
|
|
350
|
+
// Tool Handlers
|
|
351
|
+
// ============================================================
|
|
352
|
+
|
|
353
|
+
function handleMcpServers(): ToolResult {
|
|
354
|
+
const configs = loadMcpConfig();
|
|
355
|
+
const servers = Object.entries(configs).map(([name, config]) => {
|
|
356
|
+
const conn = connections.get(name);
|
|
357
|
+
return {
|
|
358
|
+
name,
|
|
359
|
+
command: config.command,
|
|
360
|
+
args: config.args || [],
|
|
361
|
+
cwd: config.cwd || '.',
|
|
362
|
+
status: conn?.connected ? 'connected' : 'disconnected',
|
|
363
|
+
toolCount: conn?.tools.length || 0,
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
if (servers.length === 0) {
|
|
368
|
+
return text('No MCP servers configured in .mcp.json (excluding self).');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const lines = ['# MCP Servers\n'];
|
|
372
|
+
for (const srv of servers) {
|
|
373
|
+
const status = srv.status === 'connected' ? 'CONNECTED' : 'DISCONNECTED';
|
|
374
|
+
lines.push(`- **${srv.name}** [${status}] — \`${srv.command} ${srv.args.join(' ')}\` (${srv.toolCount} tools)`);
|
|
375
|
+
}
|
|
376
|
+
return text(lines.join('\n'));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function handleMcpTools(server: string): Promise<ToolResult> {
|
|
380
|
+
const configs = loadMcpConfig();
|
|
381
|
+
const config = configs[server];
|
|
382
|
+
if (!config) {
|
|
383
|
+
return text(`MCP server "${server}" not found in .mcp.json. Available: ${Object.keys(configs).join(', ') || 'none'}`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
const conn = await connectToServer(server, config);
|
|
388
|
+
if (conn.tools.length === 0) {
|
|
389
|
+
return text(`MCP server "${server}" is connected but has no tools.`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const lines = [`# Tools from ${server} (${conn.tools.length})\n`];
|
|
393
|
+
for (const tool of conn.tools) {
|
|
394
|
+
lines.push(`- **${tool.name}**: ${tool.description}`);
|
|
395
|
+
}
|
|
396
|
+
return text(lines.join('\n'));
|
|
397
|
+
} catch (e) {
|
|
398
|
+
return text(`Failed to connect to MCP server "${server}": ${e}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function handleMcpCall(
|
|
403
|
+
server: string,
|
|
404
|
+
tool: string,
|
|
405
|
+
args: Record<string, unknown>,
|
|
406
|
+
): Promise<ToolResult> {
|
|
407
|
+
const configs = loadMcpConfig();
|
|
408
|
+
const config = configs[server];
|
|
409
|
+
if (!config) {
|
|
410
|
+
return text(`MCP server "${server}" not found in .mcp.json.`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
const conn = await connectToServer(server, config);
|
|
415
|
+
const result = await mcpRequest(conn, 'tools/call', { name: tool, arguments: args });
|
|
416
|
+
|
|
417
|
+
// MCP tools/call returns { content: [...] } or { content: [...], isError: true }
|
|
418
|
+
const content = (result as any).content;
|
|
419
|
+
if (Array.isArray(content)) {
|
|
420
|
+
const texts = content
|
|
421
|
+
.filter((c: any) => c.type === 'text')
|
|
422
|
+
.map((c: any) => c.text);
|
|
423
|
+
if ((result as any).isError) {
|
|
424
|
+
return text(`MCP tool error: ${texts.join('\n')}`);
|
|
425
|
+
}
|
|
426
|
+
return text(texts.join('\n'));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return text(JSON.stringify(result, null, 2));
|
|
430
|
+
} catch (e) {
|
|
431
|
+
// Mark as disconnected but don't delete — operator can see the failure in mcp_status
|
|
432
|
+
const conn = connections.get(server);
|
|
433
|
+
if (conn) conn.connected = false;
|
|
434
|
+
return text(`MCP call failed (${server}/${tool}): ${e}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function handleMcpStatus(): ToolResult {
|
|
439
|
+
const configs = loadMcpConfig();
|
|
440
|
+
const lines = ['# MCP Connection Status\n'];
|
|
441
|
+
|
|
442
|
+
for (const [name] of Object.entries(configs)) {
|
|
443
|
+
const conn = connections.get(name);
|
|
444
|
+
if (!conn) {
|
|
445
|
+
lines.push(`- **${name}**: NOT CONNECTED`);
|
|
446
|
+
} else if (conn.connected && conn.process && !conn.process.killed) {
|
|
447
|
+
lines.push(`- **${name}**: HEALTHY (pid=${conn.process.pid}, ${conn.tools.length} tools)`);
|
|
448
|
+
} else {
|
|
449
|
+
lines.push(`- **${name}**: DISCONNECTED`);
|
|
450
|
+
disconnectServer(name);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (Object.keys(configs).length === 0) {
|
|
455
|
+
lines.push('No MCP servers configured.');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return text(lines.join('\n'));
|
|
459
|
+
}
|
package/src/tools.ts
CHANGED
|
@@ -39,6 +39,7 @@ import { getKnowledgeDb } from './knowledge-db.ts';
|
|
|
39
39
|
import { getPythonToolDefinitions, isPythonTool, handlePythonToolCall } from './python-tools.ts';
|
|
40
40
|
import { getConfig, getProjectRoot, getResolvedPaths } from './config.ts';
|
|
41
41
|
import { getCurrentTier, getToolTier, isToolAllowed, annotateToolDefinitions, getLicenseToolDefinitions, isLicenseTool, handleLicenseToolCall } from './license.ts';
|
|
42
|
+
import { getMcpBridgeToolDefinitions, isMcpBridgeTool, handleMcpBridgeToolCall } from './mcp-bridge-tools.ts';
|
|
42
43
|
|
|
43
44
|
export interface ToolDefinition {
|
|
44
45
|
name: string;
|
|
@@ -166,6 +167,8 @@ export function getToolDefinitions(): ToolDefinition[] {
|
|
|
166
167
|
...getKnowledgeToolDefinitions(),
|
|
167
168
|
// Python code intelligence tools
|
|
168
169
|
...getPythonToolDefinitions(),
|
|
170
|
+
// MCP bridge tools (cross-project tool mesh)
|
|
171
|
+
...getMcpBridgeToolDefinitions(),
|
|
169
172
|
// License tools (always available)
|
|
170
173
|
...getLicenseToolDefinitions(),
|
|
171
174
|
// Core tools
|
|
@@ -380,6 +383,11 @@ export async function handleToolCall(
|
|
|
380
383
|
return handlePythonToolCall(name, args, dataDb);
|
|
381
384
|
}
|
|
382
385
|
|
|
386
|
+
// Route MCP bridge tools (cross-project tool mesh)
|
|
387
|
+
if (isMcpBridgeTool(name)) {
|
|
388
|
+
return await handleMcpBridgeToolCall(name, args);
|
|
389
|
+
}
|
|
390
|
+
|
|
383
391
|
// Route license tools
|
|
384
392
|
if (isLicenseTool(name)) {
|
|
385
393
|
const memDb = getMemoryDb();
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# Phase 3.5: Deep Security Audit
|
|
2
|
-
|
|
3
|
-
> Reference doc for `/massu-golden-path`. Return to main file for overview.
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
[GOLDEN PATH -- PHASE 3.5: DEEP SECURITY AUDIT]
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
## Purpose
|
|
10
|
-
|
|
11
|
-
Run a full adversarial security audit loop against ALL files changed in this golden path run. This is a deep, iterative audit with parallel red-team agents that converges to zero findings. It runs AFTER simplification (Phase 3) so the audit targets the final, cleaned-up code -- and BEFORE pre-commit verification (Phase 4) so all security fixes are included in the verification gates.
|
|
12
|
-
|
|
13
|
-
**This phase is NEVER skipped.** Security is non-negotiable regardless of change size, type, or scope.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 3.5.1 Determine Audit Scope
|
|
18
|
-
|
|
19
|
-
Collect ALL files changed during this golden path run:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
git diff --name-only HEAD
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
If files were already committed in earlier phases, also include:
|
|
26
|
-
```bash
|
|
27
|
-
git diff --name-only main...HEAD
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
The audit scope is the union of all changed files. Do NOT narrow scope -- every changed file gets audited.
|
|
31
|
-
|
|
32
|
-
**Output:**
|
|
33
|
-
```
|
|
34
|
-
SECURITY AUDIT SCOPE:
|
|
35
|
-
Files: [N]
|
|
36
|
-
[list of files]
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## 3.5.2 Execute Deep Security Audit
|
|
42
|
-
|
|
43
|
-
Run the full security audit protocol against the scoped files:
|
|
44
|
-
|
|
45
|
-
1. **Launch 2-4 parallel adversarial reviewer agents** adapted to the codebase area:
|
|
46
|
-
- Backend/API code: 4 agents (Injection, Network/Leakage, DoS/Resources, Red Team Bypass)
|
|
47
|
-
- Frontend code: 3 agents (XSS/Injection, Auth/Data Exposure, Input Validation/Logic)
|
|
48
|
-
- Infrastructure/config: 2 agents (Secrets/Config, Dependencies/Supply Chain)
|
|
49
|
-
|
|
50
|
-
2. **Consolidate findings** -- deduplicate across agents, take higher severity on disagreements
|
|
51
|
-
|
|
52
|
-
3. **Fix ALL findings** -- CRITICAL first, then HIGH, MEDIUM, LOW. INFO documented only.
|
|
53
|
-
|
|
54
|
-
4. **Verify fixes** -- import checks, input validation tests, functionality preserved
|
|
55
|
-
|
|
56
|
-
5. **Loop until zero findings** -- max 5 iterations, escalate to user if still failing after 5
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## 3.5.3 Attack Vector Coverage
|
|
61
|
-
|
|
62
|
-
Every audit iteration MUST verify the complete attack vector checklist:
|
|
63
|
-
|
|
64
|
-
### Universal
|
|
65
|
-
- Hardcoded secrets / API keys / credentials
|
|
66
|
-
- Error messages leaking internal details
|
|
67
|
-
- Dependency vulnerabilities
|
|
68
|
-
- Input validation on ALL external boundaries
|
|
69
|
-
|
|
70
|
-
### Backend / API
|
|
71
|
-
- SQL injection, command injection, path traversal
|
|
72
|
-
- SSRF, authentication bypass, authorization bypass
|
|
73
|
-
- DoS via unbounded inputs, memory leaks, race conditions
|
|
74
|
-
- Response validation, type confusion
|
|
75
|
-
|
|
76
|
-
### Frontend
|
|
77
|
-
- XSS, open redirects, sensitive data in client state
|
|
78
|
-
- CSRF, client-side auth bypass
|
|
79
|
-
|
|
80
|
-
### LLM / AI Specific
|
|
81
|
-
- Prompt injection, model output trust
|
|
82
|
-
- Tool argument injection, vision/multimodal injection
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## 3.5.4 Completion Gate
|
|
87
|
-
|
|
88
|
-
The phase completes ONLY when the audit loop achieves a clean pass with zero findings.
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
SECURITY_AUDIT_GATE: PASS
|
|
92
|
-
Iterations: [N]
|
|
93
|
-
Total findings fixed: [N]
|
|
94
|
-
Breakdown: [X] CRITICAL, [X] HIGH, [X] MEDIUM, [X] LOW fixed
|
|
95
|
-
Clean pass: Iteration [N]
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
**Do NOT proceed to Phase 4 until SECURITY_AUDIT_GATE = PASS.**
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## Rules
|
|
103
|
-
|
|
104
|
-
1. **NEVER skip this phase** -- not for small changes, not for docs, not for config
|
|
105
|
-
2. **NEVER proceed with findings unfixed** -- zero means zero
|
|
106
|
-
3. **ALL severity levels get fixed** -- CRITICAL through LOW
|
|
107
|
-
4. **No commit prompt** -- unlike standalone security audit commands, do NOT offer to commit here (Phase 4 handles commits)
|
|
108
|
-
5. **Findings feed Phase 4** -- security fixes are verified by Phase 4's type check, build, lint, and secrets gates automatically
|