@brutalist/mcp 0.5.1 โ 0.6.0
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 +63 -63
- package/dist/brutalist-server.d.ts +10 -0
- package/dist/brutalist-server.d.ts.map +1 -1
- package/dist/brutalist-server.js +177 -293
- package/dist/brutalist-server.js.map +1 -1
- package/dist/cli-agents.d.ts +1 -0
- package/dist/cli-agents.d.ts.map +1 -1
- package/dist/cli-agents.js +45 -2
- package/dist/cli-agents.js.map +1 -1
- package/dist/tool-definitions.d.ts +6 -0
- package/dist/tool-definitions.d.ts.map +1 -0
- package/dist/tool-definitions.js +211 -0
- package/dist/tool-definitions.js.map +1 -0
- package/dist/types/brutalist.d.ts +0 -16
- package/dist/types/brutalist.d.ts.map +1 -1
- package/dist/types/tool-config.d.ts +52 -0
- package/dist/types/tool-config.d.ts.map +1 -0
- package/dist/types/tool-config.js +25 -0
- package/dist/types/tool-config.js.map +1 -0
- package/dist/utils/pagination.d.ts +2 -2
- package/dist/utils/pagination.d.ts.map +1 -1
- package/dist/utils/pagination.js +1 -1
- package/dist/utils/pagination.js.map +1 -1
- package/dist/utils/response-cache.d.ts +89 -0
- package/dist/utils/response-cache.d.ts.map +1 -0
- package/dist/utils/response-cache.js +260 -0
- package/dist/utils/response-cache.js.map +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +22 -3
- package/dist/utils.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { gzip, gunzip } from 'zlib';
|
|
3
|
+
import { promisify } from 'util';
|
|
4
|
+
import { logger } from '../logger.js';
|
|
5
|
+
const gzipAsync = promisify(gzip);
|
|
6
|
+
const gunzipAsync = promisify(gunzip);
|
|
7
|
+
/**
|
|
8
|
+
* LRU Response Cache with TTL and memory management
|
|
9
|
+
*/
|
|
10
|
+
export class ResponseCache {
|
|
11
|
+
cache = new Map();
|
|
12
|
+
accessOrder = [];
|
|
13
|
+
stats = {
|
|
14
|
+
entries: 0,
|
|
15
|
+
totalSize: 0,
|
|
16
|
+
hits: 0,
|
|
17
|
+
misses: 0,
|
|
18
|
+
evictions: 0
|
|
19
|
+
};
|
|
20
|
+
// Configuration
|
|
21
|
+
maxEntries;
|
|
22
|
+
ttlMs;
|
|
23
|
+
maxTotalSizeMB;
|
|
24
|
+
maxEntrySizeMB;
|
|
25
|
+
compressionThresholdMB;
|
|
26
|
+
cleanupTimer;
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.maxEntries = options.maxEntries || 50;
|
|
29
|
+
this.ttlMs = (options.ttlHours || 2) * 60 * 60 * 1000; // Convert hours to ms
|
|
30
|
+
this.maxTotalSizeMB = options.maxTotalSizeMB || 500;
|
|
31
|
+
this.maxEntrySizeMB = options.maxEntrySizeMB || 10;
|
|
32
|
+
this.compressionThresholdMB = options.compressionThresholdMB || 1;
|
|
33
|
+
// Log configuration
|
|
34
|
+
logger.info(`๐ฆ ResponseCache initialized:`, {
|
|
35
|
+
maxEntries: this.maxEntries,
|
|
36
|
+
ttlHours: this.ttlMs / (1000 * 60 * 60),
|
|
37
|
+
maxTotalSizeMB: this.maxTotalSizeMB,
|
|
38
|
+
maxEntrySizeMB: this.maxEntrySizeMB,
|
|
39
|
+
compressionThresholdMB: this.compressionThresholdMB
|
|
40
|
+
});
|
|
41
|
+
// Periodic cleanup
|
|
42
|
+
this.cleanupTimer = setInterval(() => this.cleanupExpired(), 5 * 60 * 1000); // Every 5 minutes
|
|
43
|
+
// Allow Node to exit even if timer is active
|
|
44
|
+
this.cleanupTimer.unref();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Generate cache key from request parameters
|
|
48
|
+
*/
|
|
49
|
+
generateCacheKey(params) {
|
|
50
|
+
// Create deterministic string from params
|
|
51
|
+
const sortedParams = Object.keys(params)
|
|
52
|
+
.sort()
|
|
53
|
+
.reduce((acc, key) => {
|
|
54
|
+
if (params[key] !== undefined && key !== 'analysis_id' && key !== 'offset' && key !== 'limit' && key !== 'cursor' && key !== 'force_refresh') {
|
|
55
|
+
acc[key] = params[key];
|
|
56
|
+
}
|
|
57
|
+
return acc;
|
|
58
|
+
}, {});
|
|
59
|
+
const paramString = JSON.stringify(sortedParams);
|
|
60
|
+
const hash = createHash('sha256').update(paramString).digest('hex');
|
|
61
|
+
return hash;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Generate short analysis ID from cache key
|
|
65
|
+
*/
|
|
66
|
+
generateAnalysisId(cacheKey) {
|
|
67
|
+
return cacheKey.substring(0, 8);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Store response in cache
|
|
71
|
+
*/
|
|
72
|
+
async set(params, content, forceKey) {
|
|
73
|
+
const cacheKey = forceKey || this.generateCacheKey(params);
|
|
74
|
+
const analysisId = this.generateAnalysisId(cacheKey);
|
|
75
|
+
// Check entry size
|
|
76
|
+
const contentSize = Buffer.byteLength(content, 'utf-8');
|
|
77
|
+
const sizeMB = contentSize / (1024 * 1024);
|
|
78
|
+
if (sizeMB > this.maxEntrySizeMB) {
|
|
79
|
+
logger.warn(`โ ๏ธ Response too large for cache: ${sizeMB.toFixed(2)}MB > ${this.maxEntrySizeMB}MB`);
|
|
80
|
+
return { cacheKey, analysisId };
|
|
81
|
+
}
|
|
82
|
+
// Compress if needed
|
|
83
|
+
let finalContent = content;
|
|
84
|
+
let compressed = false;
|
|
85
|
+
if (sizeMB > this.compressionThresholdMB) {
|
|
86
|
+
try {
|
|
87
|
+
const compressedBuffer = await gzipAsync(Buffer.from(content, 'utf-8'));
|
|
88
|
+
const compressedSize = compressedBuffer.length / (1024 * 1024);
|
|
89
|
+
logger.info(`๐๏ธ Compressed response: ${sizeMB.toFixed(2)}MB โ ${compressedSize.toFixed(2)}MB`);
|
|
90
|
+
finalContent = compressedBuffer.toString('base64');
|
|
91
|
+
compressed = true;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
logger.error('Compression failed:', error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Check total cache size
|
|
98
|
+
await this.ensureCapacity(contentSize);
|
|
99
|
+
// Store in cache
|
|
100
|
+
const entry = {
|
|
101
|
+
content: finalContent,
|
|
102
|
+
timestamp: Date.now(),
|
|
103
|
+
analysisId,
|
|
104
|
+
cacheKey,
|
|
105
|
+
requestParams: params,
|
|
106
|
+
compressed,
|
|
107
|
+
size: compressed ? Buffer.byteLength(finalContent, 'base64') : contentSize
|
|
108
|
+
};
|
|
109
|
+
this.cache.set(cacheKey, entry);
|
|
110
|
+
this.cache.set(analysisId, entry); // Also index by short ID
|
|
111
|
+
this.updateAccessOrder(cacheKey); // Only track cache key in LRU order
|
|
112
|
+
this.stats.entries = this.cache.size / 2; // Divided by 2 because we store twice
|
|
113
|
+
this.stats.totalSize += entry.size;
|
|
114
|
+
logger.info(`โ
Cached response: ${analysisId} (${sizeMB.toFixed(2)}MB${compressed ? ' compressed' : ''})`);
|
|
115
|
+
return { cacheKey, analysisId };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Retrieve response from cache
|
|
119
|
+
*/
|
|
120
|
+
async get(keyOrId) {
|
|
121
|
+
const entry = this.cache.get(keyOrId);
|
|
122
|
+
if (!entry) {
|
|
123
|
+
this.stats.misses++;
|
|
124
|
+
logger.debug(`โ Cache miss: ${keyOrId}`);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
// Check TTL
|
|
128
|
+
const age = Date.now() - entry.timestamp;
|
|
129
|
+
if (age > this.ttlMs) {
|
|
130
|
+
logger.info(`โฐ Cache expired: ${keyOrId} (age: ${(age / 1000 / 60).toFixed(0)} minutes)`);
|
|
131
|
+
this.delete(keyOrId);
|
|
132
|
+
this.stats.misses++;
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
// Update access order (always use cache key for consistency)
|
|
136
|
+
this.updateAccessOrder(entry.cacheKey);
|
|
137
|
+
this.stats.hits++;
|
|
138
|
+
logger.info(`โ
Cache hit: ${keyOrId} (age: ${(age / 1000 / 60).toFixed(0)} minutes)`);
|
|
139
|
+
// Decompress if needed
|
|
140
|
+
if (entry.compressed) {
|
|
141
|
+
try {
|
|
142
|
+
const buffer = Buffer.from(entry.content, 'base64');
|
|
143
|
+
const decompressed = await gunzipAsync(buffer);
|
|
144
|
+
return decompressed.toString('utf-8');
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
logger.error('Decompression failed:', error);
|
|
148
|
+
this.delete(keyOrId);
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return entry.content;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Check if key exists in cache
|
|
156
|
+
*/
|
|
157
|
+
has(keyOrId) {
|
|
158
|
+
const entry = this.cache.get(keyOrId);
|
|
159
|
+
if (!entry)
|
|
160
|
+
return false;
|
|
161
|
+
// Check if expired
|
|
162
|
+
const age = Date.now() - entry.timestamp;
|
|
163
|
+
if (age > this.ttlMs) {
|
|
164
|
+
this.delete(keyOrId);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Delete entry from cache
|
|
171
|
+
*/
|
|
172
|
+
delete(keyOrId) {
|
|
173
|
+
const entry = this.cache.get(keyOrId);
|
|
174
|
+
if (entry) {
|
|
175
|
+
this.stats.totalSize -= entry.size;
|
|
176
|
+
// Delete both the cache key and analysis ID
|
|
177
|
+
this.cache.delete(entry.cacheKey);
|
|
178
|
+
this.cache.delete(entry.analysisId);
|
|
179
|
+
// Remove cache key from access order
|
|
180
|
+
this.accessOrder = this.accessOrder.filter(k => k !== entry.cacheKey);
|
|
181
|
+
this.stats.entries = this.cache.size / 2;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Update LRU access order
|
|
186
|
+
*/
|
|
187
|
+
updateAccessOrder(key) {
|
|
188
|
+
this.accessOrder = this.accessOrder.filter(k => k !== key);
|
|
189
|
+
this.accessOrder.push(key);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Ensure cache has capacity for new entry
|
|
193
|
+
*/
|
|
194
|
+
async ensureCapacity(newEntrySize) {
|
|
195
|
+
const maxTotalSize = this.maxTotalSizeMB * 1024 * 1024;
|
|
196
|
+
// Check total size limit
|
|
197
|
+
while (this.stats.totalSize + newEntrySize > maxTotalSize && this.accessOrder.length > 0) {
|
|
198
|
+
const oldestKey = this.accessOrder[0];
|
|
199
|
+
logger.info(`๐๏ธ Evicting for size limit: ${oldestKey}`);
|
|
200
|
+
this.delete(oldestKey);
|
|
201
|
+
this.stats.evictions++;
|
|
202
|
+
}
|
|
203
|
+
// Check entry count limit
|
|
204
|
+
while (this.cache.size / 2 >= this.maxEntries && this.accessOrder.length > 0) {
|
|
205
|
+
const oldestKey = this.accessOrder[0];
|
|
206
|
+
logger.info(`๐๏ธ Evicting for entry limit: ${oldestKey}`);
|
|
207
|
+
this.delete(oldestKey);
|
|
208
|
+
this.stats.evictions++;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Clean up expired entries
|
|
213
|
+
*/
|
|
214
|
+
cleanupExpired() {
|
|
215
|
+
const now = Date.now();
|
|
216
|
+
let cleaned = 0;
|
|
217
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
218
|
+
if (now - entry.timestamp > this.ttlMs) {
|
|
219
|
+
this.delete(key);
|
|
220
|
+
cleaned++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (cleaned > 0) {
|
|
224
|
+
logger.info(`๐งน Cleaned ${cleaned} expired cache entries`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get cache statistics
|
|
229
|
+
*/
|
|
230
|
+
getStats() {
|
|
231
|
+
return { ...this.stats };
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Clear entire cache
|
|
235
|
+
*/
|
|
236
|
+
clear() {
|
|
237
|
+
this.cache.clear();
|
|
238
|
+
this.accessOrder = [];
|
|
239
|
+
this.stats = {
|
|
240
|
+
entries: 0,
|
|
241
|
+
totalSize: 0,
|
|
242
|
+
hits: this.stats.hits,
|
|
243
|
+
misses: this.stats.misses,
|
|
244
|
+
evictions: this.stats.evictions
|
|
245
|
+
};
|
|
246
|
+
logger.info('๐๏ธ Cache cleared');
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Destroy cache and cleanup resources
|
|
250
|
+
*/
|
|
251
|
+
destroy() {
|
|
252
|
+
if (this.cleanupTimer) {
|
|
253
|
+
clearInterval(this.cleanupTimer);
|
|
254
|
+
this.cleanupTimer = undefined;
|
|
255
|
+
}
|
|
256
|
+
this.clear();
|
|
257
|
+
logger.info('๐ Cache destroyed');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=response-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-cache.js","sourceRoot":"","sources":["../../src/utils/response-cache.ts"],"names":[],"mappings":"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"}
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,OAAe,GACzB,MAAM,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,OAAe,GACzB,MAAM,CAsCR"}
|
package/dist/utils.js
CHANGED
|
@@ -10,14 +10,33 @@ import { realpathSync, existsSync } from 'fs';
|
|
|
10
10
|
* @throws Error if the path is outside the project root or does not exist (if mustExist is true).
|
|
11
11
|
*/
|
|
12
12
|
export function resolveAndValidatePath(projectRoot, userPath, mustExist = false) {
|
|
13
|
+
// Check for null byte injection before any path operations
|
|
14
|
+
if (userPath.includes('\0')) {
|
|
15
|
+
throw new Error(`Path traversal detected`);
|
|
16
|
+
}
|
|
13
17
|
const absoluteProjectRoot = realpathSync(projectRoot);
|
|
18
|
+
// For absolute paths, check if they start outside project root immediately
|
|
19
|
+
if (resolve(userPath) === userPath) { // userPath is absolute
|
|
20
|
+
if (!userPath.startsWith(absoluteProjectRoot + sep) && userPath !== absoluteProjectRoot) {
|
|
21
|
+
throw new Error(`Path traversal detected`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
14
24
|
const resolvedPath = resolve(absoluteProjectRoot, userPath);
|
|
15
|
-
|
|
25
|
+
let absoluteResolvedPath;
|
|
26
|
+
const pathExists = existsSync(resolvedPath);
|
|
27
|
+
if (pathExists) {
|
|
28
|
+
// Use realpathSync to resolve symlinks and detect traversal for existing paths
|
|
29
|
+
absoluteResolvedPath = realpathSync(resolvedPath);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// For non-existent paths, use logical resolution for traversal detection
|
|
33
|
+
absoluteResolvedPath = resolvedPath;
|
|
34
|
+
}
|
|
16
35
|
// Ensure the resolved path is a sub-path of the project root
|
|
17
36
|
if (!absoluteResolvedPath.startsWith(absoluteProjectRoot + sep) && absoluteResolvedPath !== absoluteProjectRoot) {
|
|
18
|
-
throw new Error(`Path traversal detected
|
|
37
|
+
throw new Error(`Path traversal detected`);
|
|
19
38
|
}
|
|
20
|
-
if (mustExist && !
|
|
39
|
+
if (mustExist && !pathExists) {
|
|
21
40
|
throw new Error(`Path does not exist: ${userPath}`);
|
|
22
41
|
}
|
|
23
42
|
return absoluteResolvedPath;
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAQ,GAAG,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAE9C;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CACpC,WAAmB,EACnB,QAAgB,EAChB,YAAqB,KAAK;IAE1B,MAAM,mBAAmB,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAQ,GAAG,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAE9C;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CACpC,WAAmB,EACnB,QAAgB,EAChB,YAAqB,KAAK;IAE1B,2DAA2D;IAC3D,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,mBAAmB,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IAEtD,2EAA2E;IAC3E,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC,uBAAuB;QAC3D,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,mBAAmB,GAAG,GAAG,CAAC,IAAI,QAAQ,KAAK,mBAAmB,EAAE,CAAC;YACxF,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAE5D,IAAI,oBAA4B,CAAC;IACjC,MAAM,UAAU,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;IAE5C,IAAI,UAAU,EAAE,CAAC;QACf,+EAA+E;QAC/E,oBAAoB,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IACpD,CAAC;SAAM,CAAC;QACN,yEAAyE;QACzE,oBAAoB,GAAG,YAAY,CAAC;IACtC,CAAC;IAED,6DAA6D;IAC7D,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,mBAAmB,GAAG,GAAG,CAAC,IAAI,oBAAoB,KAAK,mBAAmB,EAAE,CAAC;QAChH,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IAED,IAAI,SAAS,IAAI,CAAC,UAAU,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,wBAAwB,QAAQ,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,oBAAoB,CAAC;AAC9B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brutalist/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Deploy Claude, Codex & Gemini CLI agents to demolish your work before users do. Real file analysis. Brutal honesty. Now with intelligent pagination.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"zod": "^3.24.2"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
|
-
"@types/express": "^
|
|
81
|
+
"@types/express": "^4.17.21",
|
|
82
82
|
"@types/jest": "^30.0.0",
|
|
83
83
|
"@types/node": "^20.17.28",
|
|
84
84
|
"@types/node-fetch": "^2.6.13",
|