@brutalist/mcp 0.7.0 → 0.9.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/README.md +22 -1
- package/dist/brutalist-server.d.ts +46 -16
- package/dist/brutalist-server.d.ts.map +1 -1
- package/dist/brutalist-server.js +223 -611
- package/dist/brutalist-server.js.map +1 -1
- package/dist/cli-agents.d.ts +11 -9
- package/dist/cli-agents.d.ts.map +1 -1
- package/dist/cli-agents.js +239 -155
- package/dist/cli-agents.js.map +1 -1
- package/dist/domains/argument-space.d.ts +9 -0
- package/dist/domains/argument-space.d.ts.map +1 -1
- package/dist/domains/argument-space.js +27 -20
- package/dist/domains/argument-space.js.map +1 -1
- package/dist/formatting/response-formatter.d.ts +43 -0
- package/dist/formatting/response-formatter.d.ts.map +1 -0
- package/dist/formatting/response-formatter.js +277 -0
- package/dist/formatting/response-formatter.js.map +1 -0
- package/dist/generators/tool-generator.d.ts.map +1 -1
- package/dist/generators/tool-generator.js +3 -1
- package/dist/generators/tool-generator.js.map +1 -1
- package/dist/handlers/tool-handler.d.ts +33 -0
- package/dist/handlers/tool-handler.d.ts.map +1 -0
- package/dist/handlers/tool-handler.js +299 -0
- package/dist/handlers/tool-handler.js.map +1 -0
- package/dist/registry/argument-spaces.js +17 -17
- package/dist/registry/argument-spaces.js.map +1 -1
- package/dist/transport/http-transport.d.ts +40 -0
- package/dist/transport/http-transport.d.ts.map +1 -0
- package/dist/transport/http-transport.js +182 -0
- package/dist/transport/http-transport.js.map +1 -0
- package/dist/types/tool-config.d.ts +3 -3
- package/dist/types/tool-config.js +2 -2
- package/dist/types/tool-config.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +13 -6
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { logger } from '../logger.js';
|
|
2
|
+
import { extractPaginationParams, parseCursor } from '../utils/pagination.js';
|
|
3
|
+
/**
|
|
4
|
+
* ToolHandler - Handles roast tool execution with caching and pagination
|
|
5
|
+
* Extracted from BrutalistServer to follow Single Responsibility Principle
|
|
6
|
+
*/
|
|
7
|
+
export class ToolHandler {
|
|
8
|
+
cliOrchestrator;
|
|
9
|
+
responseCache;
|
|
10
|
+
formatter;
|
|
11
|
+
config;
|
|
12
|
+
activeSessions;
|
|
13
|
+
handleStreamingEvent;
|
|
14
|
+
handleProgressUpdate;
|
|
15
|
+
ensureSessionCapacity;
|
|
16
|
+
constructor(cliOrchestrator, responseCache, formatter, config, activeSessions, handleStreamingEvent, handleProgressUpdate, ensureSessionCapacity) {
|
|
17
|
+
this.cliOrchestrator = cliOrchestrator;
|
|
18
|
+
this.responseCache = responseCache;
|
|
19
|
+
this.formatter = formatter;
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.activeSessions = activeSessions;
|
|
22
|
+
this.handleStreamingEvent = handleStreamingEvent;
|
|
23
|
+
this.handleProgressUpdate = handleProgressUpdate;
|
|
24
|
+
this.ensureSessionCapacity = ensureSessionCapacity;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Unified handler for all roast tools - DRY principle
|
|
28
|
+
*/
|
|
29
|
+
async handleRoastTool(config, args, extra) {
|
|
30
|
+
try {
|
|
31
|
+
// CRITICAL: Prevent recursion - reject tool calls from brutalist-spawned subprocesses
|
|
32
|
+
if (process.env.BRUTALIST_SUBPROCESS === '1') {
|
|
33
|
+
logger.warn(`🚫 Rejecting tool call from brutalist subprocess (recursion prevented)`);
|
|
34
|
+
return {
|
|
35
|
+
content: [{
|
|
36
|
+
type: "text",
|
|
37
|
+
text: `ERROR: Brutalist MCP tools cannot be used from within a brutalist-spawned CLI subprocess (recursion prevented)`
|
|
38
|
+
}]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const progressToken = extra._meta?.progressToken;
|
|
42
|
+
// Extract session context for security
|
|
43
|
+
// IMPORTANT: Use consistent "anonymous" for all anonymous users to enable cache sharing
|
|
44
|
+
const sessionId = extra?.sessionId ||
|
|
45
|
+
extra?._meta?.sessionId ||
|
|
46
|
+
extra?.headers?.['mcp-session-id'] ||
|
|
47
|
+
'anonymous'; // Consistent for cache sharing across pagination requests
|
|
48
|
+
const requestId = `${sessionId}-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
49
|
+
logger.debug(`🔐 Processing request with session: ${sessionId.substring(0, 8)}..., request: ${requestId.substring(0, 12)}...`);
|
|
50
|
+
// Track session activity
|
|
51
|
+
if (!this.activeSessions.has(sessionId)) {
|
|
52
|
+
this.ensureSessionCapacity(); // Ensure capacity before adding new session
|
|
53
|
+
this.activeSessions.set(sessionId, {
|
|
54
|
+
startTime: Date.now(),
|
|
55
|
+
requestCount: 0,
|
|
56
|
+
lastActivity: Date.now()
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const sessionInfo = this.activeSessions.get(sessionId);
|
|
60
|
+
sessionInfo.requestCount++;
|
|
61
|
+
sessionInfo.lastActivity = Date.now();
|
|
62
|
+
logger.debug(`Tool execution: ${config.name}, primaryArgField=${config.primaryArgField}`);
|
|
63
|
+
logger.debug(`Args: ${JSON.stringify(args, null, 2)}`);
|
|
64
|
+
// Extract pagination parameters
|
|
65
|
+
const paginationParams = extractPaginationParams(args);
|
|
66
|
+
if (args.cursor) {
|
|
67
|
+
const cursorParams = parseCursor(args.cursor);
|
|
68
|
+
Object.assign(paginationParams, cursorParams);
|
|
69
|
+
}
|
|
70
|
+
// Determine if pagination was explicitly requested by the user
|
|
71
|
+
const explicitPaginationRequested = args.offset !== undefined ||
|
|
72
|
+
args.limit !== undefined ||
|
|
73
|
+
args.cursor !== undefined ||
|
|
74
|
+
args.context_id !== undefined;
|
|
75
|
+
logger.info(`🔧 DEBUG: explicitPaginationRequested=${explicitPaginationRequested}, offset=${args.offset}, limit=${args.limit}, cursor=${args.cursor}, context_id=${args.context_id}, resume=${args.resume}`);
|
|
76
|
+
// Validate resume flag requires context_id
|
|
77
|
+
if (args.resume && !args.context_id) {
|
|
78
|
+
throw new Error(`The 'resume' flag requires a 'context_id' from a previous response. ` +
|
|
79
|
+
`Run an initial analysis first, then use the returned context_id with resume: true.`);
|
|
80
|
+
}
|
|
81
|
+
// Check cache if context_id provided
|
|
82
|
+
// Two modes: PAGINATION (context_id alone) vs CONTINUATION (context_id + resume: true)
|
|
83
|
+
let conversationHistory;
|
|
84
|
+
let resumeFollowUpQuestion; // Store follow-up for conversation history
|
|
85
|
+
let resumeOriginalParams; // Original params for filesystem tools
|
|
86
|
+
if (args.context_id && !args.force_refresh) {
|
|
87
|
+
const cachedResponse = await this.responseCache.getByContextId(args.context_id, sessionId);
|
|
88
|
+
if (cachedResponse) {
|
|
89
|
+
logger.info(`🎯 Cache HIT for context_id: ${args.context_id}`);
|
|
90
|
+
if (args.resume === true) {
|
|
91
|
+
// CONVERSATION CONTINUATION: User explicitly wants to continue with history injection
|
|
92
|
+
const textContent = args.content || args.idea || args.architecture || args.research || args.product || args.security || args.infrastructure;
|
|
93
|
+
const primaryArg = textContent || args[config.primaryArgField];
|
|
94
|
+
if (!primaryArg || primaryArg.trim() === '') {
|
|
95
|
+
throw new Error(`Conversation continuation (resume: true) requires new content/prompt. ` +
|
|
96
|
+
`Provide your follow-up question or comment in the content field.`);
|
|
97
|
+
}
|
|
98
|
+
// Store the follow-up question for conversation history
|
|
99
|
+
resumeFollowUpQuestion = primaryArg;
|
|
100
|
+
// Store original request params (for filesystem tools that need original targetPath)
|
|
101
|
+
resumeOriginalParams = cachedResponse.requestParams;
|
|
102
|
+
logger.info(`💬 Conversation continuation - new prompt: "${primaryArg.substring(0, 50)}..."`);
|
|
103
|
+
conversationHistory = cachedResponse.conversationHistory || [];
|
|
104
|
+
// Fall through to execute new analysis with history
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// PAGINATION: Just retrieving previous response (no resume flag)
|
|
108
|
+
logger.info(`📖 Pagination request - returning cached response`);
|
|
109
|
+
const cachedResult = {
|
|
110
|
+
success: true,
|
|
111
|
+
responses: [{
|
|
112
|
+
agent: 'cached',
|
|
113
|
+
success: true,
|
|
114
|
+
output: cachedResponse.content,
|
|
115
|
+
executionTime: 0
|
|
116
|
+
}]
|
|
117
|
+
};
|
|
118
|
+
return this.formatter.formatToolResponse(cachedResult, args.verbose, paginationParams, args.context_id, explicitPaginationRequested);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
logger.warn(`❌ Cache MISS for context_id: ${args.context_id}, session: ${sessionId}`);
|
|
123
|
+
throw new Error(`Context ID "${args.context_id}" not found in cache. ` +
|
|
124
|
+
`It may have expired (2 hour TTL) or belong to a different session. ` +
|
|
125
|
+
`Remove context_id parameter to run a new analysis.`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Generate cache key for this request
|
|
129
|
+
const cacheKey = this.responseCache.generateCacheKey(config.cacheKeyFields.reduce((acc, field) => {
|
|
130
|
+
acc.tool = config.name;
|
|
131
|
+
if (args[field] !== undefined)
|
|
132
|
+
acc[field] = args[field];
|
|
133
|
+
return acc;
|
|
134
|
+
}, {}));
|
|
135
|
+
// Check if we have a cached result (unless forcing refresh)
|
|
136
|
+
if (!args.force_refresh) {
|
|
137
|
+
const cachedContent = await this.responseCache.get(cacheKey, sessionId);
|
|
138
|
+
if (cachedContent) {
|
|
139
|
+
// Get existing context_id or create new alias
|
|
140
|
+
const existingContextId = this.responseCache.findContextIdForKey(cacheKey);
|
|
141
|
+
const contextId = existingContextId
|
|
142
|
+
? this.responseCache.createAlias(existingContextId, cacheKey)
|
|
143
|
+
: this.responseCache.generateContextId(cacheKey);
|
|
144
|
+
logger.info(`🎯 Cache hit for new request, using context_id: ${contextId}`);
|
|
145
|
+
const cachedResult = {
|
|
146
|
+
success: true,
|
|
147
|
+
responses: [{
|
|
148
|
+
agent: 'cached',
|
|
149
|
+
success: true,
|
|
150
|
+
output: cachedContent,
|
|
151
|
+
executionTime: 0
|
|
152
|
+
}]
|
|
153
|
+
};
|
|
154
|
+
return this.formatter.formatToolResponse(cachedResult, args.verbose, paginationParams, contextId, explicitPaginationRequested);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Build context with custom builder if available
|
|
158
|
+
let context = config.contextBuilder ? config.contextBuilder(args) : args.context;
|
|
159
|
+
// Get the primary argument (targetPath, idea, architecture, etc.)
|
|
160
|
+
// For text-based tools, use content field; for filesystem tools, use primaryArgField
|
|
161
|
+
const textContent = args.content || args.idea || args.architecture || args.research || args.product || args.security || args.infrastructure;
|
|
162
|
+
let primaryArg = textContent || args[config.primaryArgField];
|
|
163
|
+
// For resume mode with filesystem tools, use original targetPath from cached params
|
|
164
|
+
// and inject the follow-up question into context instead
|
|
165
|
+
const filesystemTools = ['codebase', 'fileStructure', 'dependencies', 'gitHistory', 'testCoverage'];
|
|
166
|
+
if (resumeOriginalParams && filesystemTools.includes(config.analysisType)) {
|
|
167
|
+
// Use original targetPath for the CLI execution (needed for path validation)
|
|
168
|
+
const originalTargetPath = resumeOriginalParams.targetPath;
|
|
169
|
+
if (originalTargetPath) {
|
|
170
|
+
logger.info(`🔄 Resume mode: Using original targetPath="${originalTargetPath}" for filesystem tool`);
|
|
171
|
+
primaryArg = originalTargetPath;
|
|
172
|
+
// Also restore workingDirectory if available
|
|
173
|
+
if (resumeOriginalParams.workingDirectory) {
|
|
174
|
+
args.workingDirectory = resumeOriginalParams.workingDirectory;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// If we have conversation history, inject it into the context
|
|
179
|
+
if (conversationHistory && conversationHistory.length > 0) {
|
|
180
|
+
const conversationContext = conversationHistory.map(msg => {
|
|
181
|
+
const role = msg.role === 'user' ? 'User' : 'Assistant';
|
|
182
|
+
return `${role}: ${msg.content}`;
|
|
183
|
+
}).join('\n\n---\n\n');
|
|
184
|
+
// For resume mode, inject the follow-up question into the context
|
|
185
|
+
const followUpContent = resumeFollowUpQuestion || '';
|
|
186
|
+
const contextPrefix = `## Previous Conversation\n\n${conversationContext}\n\n---\n\n## New User Prompt\n\n${followUpContent}\n\n`;
|
|
187
|
+
context = contextPrefix + (context || '');
|
|
188
|
+
logger.info(`💬 Injected ${conversationHistory.length} previous messages into context`);
|
|
189
|
+
}
|
|
190
|
+
logger.debug(`Primary arg: ${config.primaryArgField}="${primaryArg}", analysisType="${config.analysisType}"`);
|
|
191
|
+
// Run the analysis
|
|
192
|
+
const result = await this.executeBrutalistAnalysis(config.analysisType, primaryArg, config.systemPrompt, context, args.workingDirectory, args.preferredCLI, args.verbose, args.models, progressToken, sessionId, requestId);
|
|
193
|
+
// Cache the result if successful
|
|
194
|
+
let contextId;
|
|
195
|
+
if (result.success && result.responses.length > 0) {
|
|
196
|
+
const fullContent = this.formatter.extractFullContent(result);
|
|
197
|
+
if (fullContent) {
|
|
198
|
+
const cacheData = config.cacheKeyFields.reduce((acc, field) => {
|
|
199
|
+
acc.tool = config.name;
|
|
200
|
+
if (args[field] !== undefined)
|
|
201
|
+
acc[field] = args[field];
|
|
202
|
+
return acc;
|
|
203
|
+
}, {});
|
|
204
|
+
// Build updated conversation history
|
|
205
|
+
// For resume mode, use the follow-up question; otherwise use primaryArg
|
|
206
|
+
const now = Date.now();
|
|
207
|
+
const userMessageContent = resumeFollowUpQuestion || primaryArg;
|
|
208
|
+
const updatedConversation = [
|
|
209
|
+
...(conversationHistory || []),
|
|
210
|
+
{ role: 'user', content: userMessageContent, timestamp: now },
|
|
211
|
+
{ role: 'assistant', content: fullContent, timestamp: now }
|
|
212
|
+
];
|
|
213
|
+
// If continuing a conversation (resume: true), update existing context_id
|
|
214
|
+
if (args.resume && args.context_id && conversationHistory) {
|
|
215
|
+
// Update existing cache entry with extended conversation
|
|
216
|
+
contextId = args.context_id;
|
|
217
|
+
await this.responseCache.updateByContextId(contextId, fullContent, updatedConversation, sessionId || 'anonymous');
|
|
218
|
+
logger.info(`✅ Updated conversation ${contextId} (now ${updatedConversation.length} messages)`);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
// New conversation - create new context_id
|
|
222
|
+
const { contextId: newId } = await this.responseCache.set(cacheData, fullContent, cacheKey, sessionId, requestId, updatedConversation);
|
|
223
|
+
contextId = newId;
|
|
224
|
+
logger.info(`✅ Cached new conversation with context ID: ${contextId} for session: ${sessionId?.substring(0, 8)}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return this.formatter.formatToolResponse(result, args.verbose, paginationParams, contextId, explicitPaginationRequested);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
return this.formatter.formatErrorResponse(error);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Execute brutalist analysis with CLI orchestrator
|
|
236
|
+
*/
|
|
237
|
+
async executeBrutalistAnalysis(analysisType, primaryContent, systemPromptSpec, context, workingDirectory, preferredCLI, verbose, models, progressToken, sessionId, requestId) {
|
|
238
|
+
logger.info(`🏢 Starting brutalist analysis: ${analysisType}`);
|
|
239
|
+
logger.info(`🔧 DEBUG: preferredCLI=${preferredCLI}, primaryContent=${primaryContent}`);
|
|
240
|
+
logger.debug("Executing brutalist analysis", {
|
|
241
|
+
primaryContent,
|
|
242
|
+
analysisType,
|
|
243
|
+
systemPromptSpec,
|
|
244
|
+
workingDirectory,
|
|
245
|
+
preferredCLI
|
|
246
|
+
});
|
|
247
|
+
try {
|
|
248
|
+
// Get CLI context for execution summary
|
|
249
|
+
logger.info(`🔧 DEBUG: About to detect CLI context`);
|
|
250
|
+
await this.cliOrchestrator.detectCLIContext();
|
|
251
|
+
logger.info(`🔧 DEBUG: CLI context detected successfully`);
|
|
252
|
+
// Execute CLI agent analysis (single or multi-CLI based on preferences)
|
|
253
|
+
logger.info(`🔍 Executing brutalist analysis with timeout: ${this.config.defaultTimeout}ms`);
|
|
254
|
+
logger.info(`🔧 DEBUG: About to call cliOrchestrator.executeBrutalistAnalysis`);
|
|
255
|
+
const responses = await this.cliOrchestrator.executeBrutalistAnalysis(analysisType, primaryContent, systemPromptSpec, context, {
|
|
256
|
+
workingDirectory: workingDirectory || this.config.workingDirectory,
|
|
257
|
+
timeout: this.config.defaultTimeout,
|
|
258
|
+
preferredCLI,
|
|
259
|
+
analysisType: analysisType,
|
|
260
|
+
models,
|
|
261
|
+
onStreamingEvent: this.handleStreamingEvent,
|
|
262
|
+
progressToken,
|
|
263
|
+
onProgress: progressToken && sessionId ?
|
|
264
|
+
(progress, total, message) => this.handleProgressUpdate(progressToken, progress, total, message, sessionId) : undefined,
|
|
265
|
+
sessionId,
|
|
266
|
+
requestId
|
|
267
|
+
});
|
|
268
|
+
logger.info(`🔧 DEBUG: cliOrchestrator.executeBrutalistAnalysis returned ${responses.length} responses`);
|
|
269
|
+
const successfulResponses = responses.filter(r => r.success);
|
|
270
|
+
const totalExecutionTime = responses.reduce((sum, r) => sum + r.executionTime, 0);
|
|
271
|
+
logger.info(`📊 Analysis complete: ${successfulResponses.length}/${responses.length} CLIs successful (${totalExecutionTime}ms total)`);
|
|
272
|
+
logger.info(`🔧 DEBUG: About to synthesize feedback`);
|
|
273
|
+
const synthesis = this.cliOrchestrator.synthesizeBrutalistFeedback(responses, analysisType);
|
|
274
|
+
logger.info(`🔧 DEBUG: Synthesis length: ${synthesis.length} characters`);
|
|
275
|
+
const result = {
|
|
276
|
+
success: successfulResponses.length > 0,
|
|
277
|
+
responses,
|
|
278
|
+
synthesis,
|
|
279
|
+
analysisType,
|
|
280
|
+
targetPath: primaryContent,
|
|
281
|
+
executionSummary: {
|
|
282
|
+
totalCLIs: responses.length,
|
|
283
|
+
successfulCLIs: successfulResponses.length,
|
|
284
|
+
failedCLIs: responses.length - successfulResponses.length,
|
|
285
|
+
totalExecutionTime,
|
|
286
|
+
selectedCLI: responses.length === 1 ? responses[0].agent : undefined,
|
|
287
|
+
selectionMethod: responses.length === 1 ? responses[0].selectionMethod : 'multi-cli'
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
logger.info(`🔧 DEBUG: Returning result with success=${result.success}`);
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
logger.error("Brutalist analysis execution failed", error);
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=tool-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-handler.js","sourceRoot":"","sources":["../../src/handlers/tool-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,8 +27,8 @@ export const ARGUMENT_SPACES = {
|
|
|
27
27
|
name: 'Extended Text Input',
|
|
28
28
|
base: FILESYSTEM_ARGUMENT_SPACE.base,
|
|
29
29
|
domain: TEXT_INPUT_ARGUMENT_SPACE.domain.extend({
|
|
30
|
-
resources: z.string().optional().describe("Available resources (budget, team, time
|
|
31
|
-
timeline: z.string().optional().describe("Expected timeline
|
|
30
|
+
resources: z.string().optional().describe("Available resources (budget, team, time)"),
|
|
31
|
+
timeline: z.string().optional().describe("Expected timeline")
|
|
32
32
|
}),
|
|
33
33
|
computed: (args) => ({
|
|
34
34
|
workingDirectory: args.targetPath || '.',
|
|
@@ -41,9 +41,9 @@ export const ARGUMENT_SPACES = {
|
|
|
41
41
|
name: 'Architecture Specific',
|
|
42
42
|
base: FILESYSTEM_ARGUMENT_SPACE.base,
|
|
43
43
|
domain: TEXT_INPUT_ARGUMENT_SPACE.domain.extend({
|
|
44
|
-
scale: z.string().optional().describe("Expected scale/load
|
|
45
|
-
constraints: z.string().optional().describe("
|
|
46
|
-
deployment: z.string().optional().describe("Deployment
|
|
44
|
+
scale: z.string().optional().describe("Expected scale/load"),
|
|
45
|
+
constraints: z.string().optional().describe("Technical/budget constraints"),
|
|
46
|
+
deployment: z.string().optional().describe("Deployment strategy")
|
|
47
47
|
}),
|
|
48
48
|
computed: (args) => ({
|
|
49
49
|
workingDirectory: args.targetPath || '.',
|
|
@@ -57,9 +57,9 @@ export const ARGUMENT_SPACES = {
|
|
|
57
57
|
name: 'Research Specific',
|
|
58
58
|
base: FILESYSTEM_ARGUMENT_SPACE.base,
|
|
59
59
|
domain: TEXT_INPUT_ARGUMENT_SPACE.domain.extend({
|
|
60
|
-
field: z.string().optional().describe("Research field
|
|
61
|
-
claims: z.string().optional().describe("Main claims
|
|
62
|
-
data: z.string().optional().describe("Data sources
|
|
60
|
+
field: z.string().optional().describe("Research field"),
|
|
61
|
+
claims: z.string().optional().describe("Main claims"),
|
|
62
|
+
data: z.string().optional().describe("Data sources/setup")
|
|
63
63
|
}),
|
|
64
64
|
computed: (args) => ({
|
|
65
65
|
workingDirectory: args.targetPath || '.',
|
|
@@ -73,9 +73,9 @@ export const ARGUMENT_SPACES = {
|
|
|
73
73
|
name: 'Security Specific',
|
|
74
74
|
base: FILESYSTEM_ARGUMENT_SPACE.base,
|
|
75
75
|
domain: TEXT_INPUT_ARGUMENT_SPACE.domain.extend({
|
|
76
|
-
assets: z.string().optional().describe("Critical assets
|
|
77
|
-
threatModel: z.string().optional().describe("Known threats
|
|
78
|
-
compliance: z.string().optional().describe("Compliance requirements
|
|
76
|
+
assets: z.string().optional().describe("Critical assets to protect"),
|
|
77
|
+
threatModel: z.string().optional().describe("Known threats"),
|
|
78
|
+
compliance: z.string().optional().describe("Compliance requirements")
|
|
79
79
|
}),
|
|
80
80
|
computed: (args) => ({
|
|
81
81
|
workingDirectory: args.targetPath || '.',
|
|
@@ -89,9 +89,9 @@ export const ARGUMENT_SPACES = {
|
|
|
89
89
|
name: 'Product Specific',
|
|
90
90
|
base: FILESYSTEM_ARGUMENT_SPACE.base,
|
|
91
91
|
domain: TEXT_INPUT_ARGUMENT_SPACE.domain.extend({
|
|
92
|
-
users: z.string().optional().describe("Target users
|
|
93
|
-
competition: z.string().optional().describe("
|
|
94
|
-
metrics: z.string().optional().describe("Success metrics
|
|
92
|
+
users: z.string().optional().describe("Target users"),
|
|
93
|
+
competition: z.string().optional().describe("Competitors"),
|
|
94
|
+
metrics: z.string().optional().describe("Success metrics")
|
|
95
95
|
}),
|
|
96
96
|
computed: (args) => ({
|
|
97
97
|
workingDirectory: args.targetPath || '.',
|
|
@@ -105,9 +105,9 @@ export const ARGUMENT_SPACES = {
|
|
|
105
105
|
name: 'Infrastructure Specific',
|
|
106
106
|
base: FILESYSTEM_ARGUMENT_SPACE.base,
|
|
107
107
|
domain: TEXT_INPUT_ARGUMENT_SPACE.domain.extend({
|
|
108
|
-
scale: z.string().optional().describe("Expected scale
|
|
109
|
-
sla: z.string().optional().describe("SLA
|
|
110
|
-
budget: z.string().optional().describe("
|
|
108
|
+
scale: z.string().optional().describe("Expected scale"),
|
|
109
|
+
sla: z.string().optional().describe("SLA/uptime targets"),
|
|
110
|
+
budget: z.string().optional().describe("Cost constraints")
|
|
111
111
|
}),
|
|
112
112
|
computed: (args) => ({
|
|
113
113
|
workingDirectory: args.targetPath || '.',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"argument-spaces.js","sourceRoot":"","sources":["../../src/registry/argument-spaces.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAEL,yBAAyB,EACzB,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AAEtC;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAkC;IAC5D,+BAA+B;IAC/B,UAAU,EAAE,yBAAyB;IAErC,gEAAgE;IAChE,UAAU,EAAE,yBAAyB;IAErC,mEAAmE;IACnE,gBAAgB,EAAE,qBAAqB;IAEvC,4BAA4B;IAC5B,gBAAgB,EAAE,sBAAsB;IAExC,0BAA0B;IAC1B,cAAc,EAAE,oBAAoB;IAEpC,sBAAsB;IACtB,UAAU,EAAE,gBAAgB;IAE5B,qDAAqD;IACrD,mBAAmB,EAAE;QACnB,EAAE,EAAE,qBAAqB;QACzB,IAAI,EAAE,qBAAqB;QAC3B,IAAI,EAAE,yBAAyB,CAAC,IAAI;QACpC,MAAM,EAAE,yBAAyB,CAAC,MAAM,CAAC,MAAM,CAAC;YAC9C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"argument-spaces.js","sourceRoot":"","sources":["../../src/registry/argument-spaces.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
2
|
+
import { BrutalistServerConfig } from '../types/brutalist.js';
|
|
3
|
+
/**
|
|
4
|
+
* HttpTransport - Manages HTTP server and MCP transport
|
|
5
|
+
* Extracted from BrutalistServer to follow Single Responsibility Principle
|
|
6
|
+
*/
|
|
7
|
+
export declare class HttpTransport {
|
|
8
|
+
private config;
|
|
9
|
+
private mcpRequestHandler;
|
|
10
|
+
private httpServer?;
|
|
11
|
+
private httpTransport?;
|
|
12
|
+
private actualPort?;
|
|
13
|
+
private shutdownHandler?;
|
|
14
|
+
constructor(config: BrutalistServerConfig, mcpRequestHandler: (transport: StreamableHTTPServerTransport) => void);
|
|
15
|
+
/**
|
|
16
|
+
* Start HTTP server with MCP transport
|
|
17
|
+
*/
|
|
18
|
+
start(packageVersion: string): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Stop the HTTP server gracefully
|
|
21
|
+
*/
|
|
22
|
+
stop(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Get actual listening port (useful for tests)
|
|
25
|
+
*/
|
|
26
|
+
getActualPort(): number | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Get HTTP transport instance
|
|
29
|
+
*/
|
|
30
|
+
getTransport(): StreamableHTTPServerTransport | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Cleanup method for tests - remove event listeners
|
|
33
|
+
*/
|
|
34
|
+
cleanup(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Secure CORS implementation
|
|
37
|
+
*/
|
|
38
|
+
private handleCORS;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=http-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-transport.d.ts","sourceRoot":"","sources":["../../src/transport/http-transport.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAGnG,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;GAGG;AACH,qBAAa,aAAa;IAOtB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,iBAAiB;IAP3B,OAAO,CAAC,UAAU,CAAC,CAAM;IACzB,OAAO,CAAC,aAAa,CAAC,CAAgC;IACtD,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAC,CAAa;gBAG3B,MAAM,EAAE,qBAAqB,EAC7B,iBAAiB,EAAE,CAAC,SAAS,EAAE,6BAA6B,KAAK,IAAI;IAG/E;;OAEG;IACU,KAAK,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4EzD;;OAEG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAalC;;OAEG;IACI,aAAa,IAAI,MAAM,GAAG,SAAS;IAI1C;;OAEG;IACI,YAAY,IAAI,6BAA6B,GAAG,SAAS;IAIhE;;OAEG;IACI,OAAO,IAAI,IAAI;IAOtB;;OAEG;IACH,OAAO,CAAC,UAAU;CAwDnB"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
import { logger } from '../logger.js';
|
|
5
|
+
/**
|
|
6
|
+
* HttpTransport - Manages HTTP server and MCP transport
|
|
7
|
+
* Extracted from BrutalistServer to follow Single Responsibility Principle
|
|
8
|
+
*/
|
|
9
|
+
export class HttpTransport {
|
|
10
|
+
config;
|
|
11
|
+
mcpRequestHandler;
|
|
12
|
+
httpServer;
|
|
13
|
+
httpTransport;
|
|
14
|
+
actualPort;
|
|
15
|
+
shutdownHandler;
|
|
16
|
+
constructor(config, mcpRequestHandler) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.mcpRequestHandler = mcpRequestHandler;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Start HTTP server with MCP transport
|
|
22
|
+
*/
|
|
23
|
+
async start(packageVersion) {
|
|
24
|
+
logger.info(`Starting with HTTP streaming transport on port ${this.config.httpPort}`);
|
|
25
|
+
// Create HTTP transport with streaming support
|
|
26
|
+
this.httpTransport = new StreamableHTTPServerTransport({
|
|
27
|
+
sessionIdGenerator: () => randomUUID(),
|
|
28
|
+
enableJsonResponse: false, // Force SSE streaming
|
|
29
|
+
onsessioninitialized: (sessionId) => {
|
|
30
|
+
logger.info(`New session initialized: ${sessionId}`);
|
|
31
|
+
},
|
|
32
|
+
onsessionclosed: (sessionId) => {
|
|
33
|
+
logger.info(`Session closed: ${sessionId}`);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
// Notify caller to connect MCP server to transport
|
|
37
|
+
this.mcpRequestHandler(this.httpTransport);
|
|
38
|
+
// Create Express app for HTTP handling
|
|
39
|
+
const app = express();
|
|
40
|
+
app.use(express.json({ limit: '10mb' })); // Add JSON size limit for security
|
|
41
|
+
// Apply CORS middleware
|
|
42
|
+
app.use((req, res, next) => {
|
|
43
|
+
this.handleCORS(req, res, next);
|
|
44
|
+
});
|
|
45
|
+
// Route all MCP requests through the transport
|
|
46
|
+
app.all('/mcp', async (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
await this.httpTransport.handleRequest(req, res, req.body);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
logger.error("HTTP request handling failed", error);
|
|
52
|
+
if (!res.headersSent) {
|
|
53
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
// Health check endpoint
|
|
58
|
+
app.get('/health', (req, res) => {
|
|
59
|
+
res.json({ status: 'ok', transport: 'http-streaming', version: packageVersion });
|
|
60
|
+
});
|
|
61
|
+
// Start the HTTP server - bind to localhost only for security
|
|
62
|
+
const port = this.config.httpPort ?? 3000;
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
this.httpServer = app.listen(port, '127.0.0.1', () => {
|
|
65
|
+
const actualPort = this.httpServer.address()?.port || port;
|
|
66
|
+
this.actualPort = actualPort;
|
|
67
|
+
logger.info(`HTTP server listening on port ${actualPort}`);
|
|
68
|
+
logger.info(`MCP endpoint: http://localhost:${actualPort}/mcp`);
|
|
69
|
+
logger.info(`Health check: http://localhost:${actualPort}/health`);
|
|
70
|
+
resolve();
|
|
71
|
+
});
|
|
72
|
+
this.httpServer.on('error', (error) => {
|
|
73
|
+
logger.error('HTTP server failed to start', error);
|
|
74
|
+
reject(error);
|
|
75
|
+
});
|
|
76
|
+
// Handle graceful shutdown - avoid duplicate listeners
|
|
77
|
+
if (!this.shutdownHandler) {
|
|
78
|
+
this.shutdownHandler = () => {
|
|
79
|
+
logger.info('Received SIGTERM, shutting down gracefully');
|
|
80
|
+
this.httpServer?.close(() => {
|
|
81
|
+
logger.info('HTTP server closed');
|
|
82
|
+
process.exit(0);
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
process.on('SIGTERM', this.shutdownHandler);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Stop the HTTP server gracefully
|
|
91
|
+
*/
|
|
92
|
+
async stop() {
|
|
93
|
+
if (this.httpServer) {
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
this.httpServer.close(() => {
|
|
96
|
+
logger.info('HTTP server stopped');
|
|
97
|
+
this.httpServer = undefined;
|
|
98
|
+
this.actualPort = undefined;
|
|
99
|
+
resolve();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get actual listening port (useful for tests)
|
|
106
|
+
*/
|
|
107
|
+
getActualPort() {
|
|
108
|
+
return this.actualPort;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get HTTP transport instance
|
|
112
|
+
*/
|
|
113
|
+
getTransport() {
|
|
114
|
+
return this.httpTransport;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Cleanup method for tests - remove event listeners
|
|
118
|
+
*/
|
|
119
|
+
cleanup() {
|
|
120
|
+
if (this.shutdownHandler) {
|
|
121
|
+
process.removeListener('SIGTERM', this.shutdownHandler);
|
|
122
|
+
this.shutdownHandler = undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Secure CORS implementation
|
|
127
|
+
*/
|
|
128
|
+
handleCORS(req, res, next) {
|
|
129
|
+
const origin = req.headers.origin;
|
|
130
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
131
|
+
// Define safe default origins for development
|
|
132
|
+
const defaultDevOrigins = [
|
|
133
|
+
'http://localhost:3000',
|
|
134
|
+
'http://127.0.0.1:3000',
|
|
135
|
+
'http://localhost:8080',
|
|
136
|
+
'http://127.0.0.1:8080',
|
|
137
|
+
'http://localhost:3001',
|
|
138
|
+
'http://127.0.0.1:3001'
|
|
139
|
+
];
|
|
140
|
+
// Get allowed origins from config or use defaults
|
|
141
|
+
const allowedOrigins = this.config.corsOrigins || defaultDevOrigins;
|
|
142
|
+
const allowWildcard = this.config.allowCORSWildcard === true && !isProduction;
|
|
143
|
+
// Determine if origin is allowed
|
|
144
|
+
let allowedOrigin = null;
|
|
145
|
+
if (allowWildcard) {
|
|
146
|
+
// Only in development with explicit opt-in
|
|
147
|
+
allowedOrigin = '*';
|
|
148
|
+
logger.warn("⚠️ Using wildcard CORS - only safe in development!");
|
|
149
|
+
}
|
|
150
|
+
else if (!origin) {
|
|
151
|
+
// No origin header (same-origin or direct server access)
|
|
152
|
+
allowedOrigin = defaultDevOrigins[0]; // Default fallback
|
|
153
|
+
}
|
|
154
|
+
else if (allowedOrigins.includes(origin)) {
|
|
155
|
+
// Explicitly allowed origin
|
|
156
|
+
allowedOrigin = origin;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// Rejected origin
|
|
160
|
+
logger.warn(`🚫 CORS rejected origin: ${origin}`);
|
|
161
|
+
allowedOrigin = null;
|
|
162
|
+
}
|
|
163
|
+
// Set headers only if origin is allowed
|
|
164
|
+
if (allowedOrigin) {
|
|
165
|
+
res.header('Access-Control-Allow-Origin', allowedOrigin);
|
|
166
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
167
|
+
res.header('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');
|
|
168
|
+
res.header('Access-Control-Allow-Credentials', 'false'); // Explicit false
|
|
169
|
+
}
|
|
170
|
+
if (req.method === 'OPTIONS') {
|
|
171
|
+
if (allowedOrigin) {
|
|
172
|
+
res.sendStatus(200);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
res.sendStatus(403); // Forbidden for disallowed origins
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
next();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=http-transport.js.map
|