contextguard 0.1.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/.github/ISSUE_TEMPLATE/bug_report.md +57 -0
- package/CONTRIBUTING.md +532 -0
- package/LICENSE +21 -0
- package/README.md +163 -0
- package/SECURITY.md +254 -0
- package/dist/mcp-security-wrapper.js +398 -0
- package/eslint.config.mts +23 -0
- package/mcp_security.log +2 -0
- package/package.json +46 -0
- package/security.json +12 -0
- package/src/mcp-security-wrapper.ts +527 -0
- package/src/test-server.ts +295 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copyright (c) 2025 Amir Mironi
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { spawn, ChildProcess } from "child_process";
|
|
11
|
+
import * as fs from "fs";
|
|
12
|
+
import { createHash } from "crypto";
|
|
13
|
+
|
|
14
|
+
interface SecurityConfig {
|
|
15
|
+
maxToolCallsPerMinute?: number;
|
|
16
|
+
blockedPatterns?: string[];
|
|
17
|
+
allowedFilePaths?: string[];
|
|
18
|
+
alertThreshold?: number;
|
|
19
|
+
enablePromptInjectionDetection?: boolean;
|
|
20
|
+
enableSensitiveDataDetection?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface SecurityEvent {
|
|
24
|
+
timestamp: string;
|
|
25
|
+
eventType: string;
|
|
26
|
+
severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
|
|
27
|
+
details: Record<string, unknown>;
|
|
28
|
+
sessionId: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface MCPMessage {
|
|
32
|
+
jsonrpc: string;
|
|
33
|
+
id?: string | number;
|
|
34
|
+
method?: string;
|
|
35
|
+
params?: {
|
|
36
|
+
name?: string;
|
|
37
|
+
arguments?: Record<string, string>;
|
|
38
|
+
path?: string;
|
|
39
|
+
filePath?: string;
|
|
40
|
+
[key: string]: unknown;
|
|
41
|
+
};
|
|
42
|
+
result?: unknown;
|
|
43
|
+
error?: unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class SecurityPolicy {
|
|
47
|
+
private config: SecurityConfig;
|
|
48
|
+
private sensitiveDataPatterns: RegExp[];
|
|
49
|
+
private promptInjectionPatterns: RegExp[];
|
|
50
|
+
|
|
51
|
+
constructor(config: SecurityConfig) {
|
|
52
|
+
this.config = {
|
|
53
|
+
maxToolCallsPerMinute: 30,
|
|
54
|
+
blockedPatterns: [],
|
|
55
|
+
allowedFilePaths: [],
|
|
56
|
+
alertThreshold: 5,
|
|
57
|
+
enablePromptInjectionDetection: true,
|
|
58
|
+
enableSensitiveDataDetection: true,
|
|
59
|
+
...config,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Sensitive data patterns
|
|
63
|
+
this.sensitiveDataPatterns = [
|
|
64
|
+
/(?:password|secret|api[_-]?key|token)\s*[:=]\s*['"]?[\w\-.]+['"]?/gi,
|
|
65
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // Email
|
|
66
|
+
/\b\d{3}-\d{2}-\d{4}\b/g, // SSN
|
|
67
|
+
/sk-[a-zA-Z0-9]{48}/g, // OpenAI API keys
|
|
68
|
+
/ghp_[a-zA-Z0-9]{36}/g, // GitHub tokens
|
|
69
|
+
/AKIA[0-9A-Z]{16}/g, // AWS Access Keys
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
// Prompt injection patterns
|
|
73
|
+
this.promptInjectionPatterns = [
|
|
74
|
+
/ignore\s+(previous|all)\s+(instructions|prompts)/gi,
|
|
75
|
+
/system:\s*you\s+are\s+now/gi,
|
|
76
|
+
/forget\s+(everything|all)/gi,
|
|
77
|
+
/new\s+instructions:/gi,
|
|
78
|
+
/\[INST\].*?\[\/INST\]/gs,
|
|
79
|
+
/<\|im_start\|>/g,
|
|
80
|
+
/disregard\s+previous/gi,
|
|
81
|
+
/override\s+previous/gi,
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
checkPromptInjection(text: string): string[] {
|
|
86
|
+
if (!this.config.enablePromptInjectionDetection) return [];
|
|
87
|
+
const violations: string[] = [];
|
|
88
|
+
for (const pattern of this.promptInjectionPatterns) {
|
|
89
|
+
const matches = text.match(pattern);
|
|
90
|
+
if (matches) {
|
|
91
|
+
violations.push(
|
|
92
|
+
`Potential prompt injection detected: "${matches[0].substring(
|
|
93
|
+
0,
|
|
94
|
+
50
|
|
95
|
+
)}..."`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return violations;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
checkSensitiveData(text: string): string[] {
|
|
103
|
+
if (!this.config.enableSensitiveDataDetection) return [];
|
|
104
|
+
const violations: string[] = [];
|
|
105
|
+
for (const pattern of this.sensitiveDataPatterns) {
|
|
106
|
+
const matches = text.match(pattern);
|
|
107
|
+
if (matches) {
|
|
108
|
+
violations.push(
|
|
109
|
+
`Sensitive data pattern detected (redacted): ${pattern.source.substring(
|
|
110
|
+
0,
|
|
111
|
+
30
|
|
112
|
+
)}...`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return violations;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
checkFileAccess(filePath: string): string[] {
|
|
120
|
+
const violations: string[] = [];
|
|
121
|
+
if (filePath.includes("..")) {
|
|
122
|
+
violations.push(`Path traversal attempt detected: ${filePath}`);
|
|
123
|
+
}
|
|
124
|
+
const dangerousPaths = [
|
|
125
|
+
"/etc",
|
|
126
|
+
"/root",
|
|
127
|
+
"/sys",
|
|
128
|
+
"/proc",
|
|
129
|
+
"C:\\Windows\\System32",
|
|
130
|
+
];
|
|
131
|
+
if (dangerousPaths.some((dangerous) => filePath.startsWith(dangerous))) {
|
|
132
|
+
violations.push(`Access to dangerous path detected: ${filePath}`);
|
|
133
|
+
}
|
|
134
|
+
if (
|
|
135
|
+
this.config.allowedFilePaths &&
|
|
136
|
+
this.config.allowedFilePaths.length > 0
|
|
137
|
+
) {
|
|
138
|
+
const isAllowed = this.config.allowedFilePaths.some((allowed) =>
|
|
139
|
+
filePath.startsWith(allowed)
|
|
140
|
+
);
|
|
141
|
+
if (!isAllowed) {
|
|
142
|
+
violations.push(`File path not in allowed list: ${filePath}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return violations;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
checkRateLimit(timestamps: number[]): boolean {
|
|
149
|
+
const oneMinuteAgo = Date.now() - 60000;
|
|
150
|
+
const recentCalls = timestamps.filter((t) => t > oneMinuteAgo);
|
|
151
|
+
return recentCalls.length < (this.config.maxToolCallsPerMinute || 30);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
class SecurityLogger {
|
|
156
|
+
private logFile: string;
|
|
157
|
+
private events: SecurityEvent[] = [];
|
|
158
|
+
|
|
159
|
+
constructor(logFile: string = "mcp_security.log") {
|
|
160
|
+
this.logFile = logFile;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
logEvent(
|
|
164
|
+
eventType: string,
|
|
165
|
+
severity: SecurityEvent["severity"],
|
|
166
|
+
details: Record<string, unknown>,
|
|
167
|
+
sessionId: string
|
|
168
|
+
): void {
|
|
169
|
+
const event: SecurityEvent = {
|
|
170
|
+
timestamp: new Date().toISOString(),
|
|
171
|
+
eventType,
|
|
172
|
+
severity,
|
|
173
|
+
details,
|
|
174
|
+
sessionId,
|
|
175
|
+
};
|
|
176
|
+
this.events.push(event);
|
|
177
|
+
fs.appendFileSync(this.logFile, JSON.stringify(event) + "\n");
|
|
178
|
+
if (severity === "HIGH" || severity === "CRITICAL") {
|
|
179
|
+
console.error(
|
|
180
|
+
`[SECURITY ALERT] ${eventType}: ${JSON.stringify(details)}`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
getStatistics(): Record<string, unknown> {
|
|
186
|
+
return {
|
|
187
|
+
totalEvents: this.events.length,
|
|
188
|
+
eventsByType: this.countByField("eventType"),
|
|
189
|
+
eventsBySeverity: this.countByField("severity"),
|
|
190
|
+
recentEvents: this.events.slice(-10),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private countByField(field: keyof SecurityEvent): Record<string, number> {
|
|
195
|
+
const counts: Record<string, number> = {};
|
|
196
|
+
for (const event of this.events) {
|
|
197
|
+
const value = String(event[field]);
|
|
198
|
+
counts[value] = (counts[value] || 0) + 1;
|
|
199
|
+
}
|
|
200
|
+
return counts;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
class MCPSecurityWrapper {
|
|
205
|
+
private serverCommand: string[];
|
|
206
|
+
private policy: SecurityPolicy;
|
|
207
|
+
private logger: SecurityLogger;
|
|
208
|
+
private process: ChildProcess | null = null;
|
|
209
|
+
private toolCallTimestamps: number[] = [];
|
|
210
|
+
private sessionId: string;
|
|
211
|
+
private clientMessageBuffer: string = "";
|
|
212
|
+
private serverMessageBuffer: string = "";
|
|
213
|
+
|
|
214
|
+
constructor(
|
|
215
|
+
serverCommand: string[],
|
|
216
|
+
policy: SecurityPolicy,
|
|
217
|
+
logger: SecurityLogger
|
|
218
|
+
) {
|
|
219
|
+
this.serverCommand = serverCommand;
|
|
220
|
+
this.policy = policy;
|
|
221
|
+
this.logger = logger;
|
|
222
|
+
this.sessionId = createHash("md5")
|
|
223
|
+
.update(Date.now().toString())
|
|
224
|
+
.digest("hex")
|
|
225
|
+
.substring(0, 8);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async start(): Promise<void> {
|
|
229
|
+
this.process = spawn(this.serverCommand[0], this.serverCommand.slice(1), {
|
|
230
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (!this.process.stdout || !this.process.stdin || !this.process.stderr) {
|
|
234
|
+
throw new Error("Failed to create child process streams");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
this.logger.logEvent(
|
|
238
|
+
"SERVER_START",
|
|
239
|
+
"LOW",
|
|
240
|
+
{
|
|
241
|
+
command: this.serverCommand.join(" "),
|
|
242
|
+
pid: this.process.pid,
|
|
243
|
+
},
|
|
244
|
+
this.sessionId
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
this.process.stderr.pipe(process.stderr);
|
|
248
|
+
|
|
249
|
+
this.process.stdout.on("data", (data: Buffer) => {
|
|
250
|
+
const output = data.toString();
|
|
251
|
+
this.handleServerOutput(output);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
process.stdin.on("data", (data: Buffer) => {
|
|
255
|
+
const input = data.toString();
|
|
256
|
+
this.handleClientInput(input);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
this.process.on("exit", (code) => {
|
|
260
|
+
this.logger.logEvent(
|
|
261
|
+
"SERVER_EXIT",
|
|
262
|
+
"MEDIUM",
|
|
263
|
+
{
|
|
264
|
+
exitCode: code,
|
|
265
|
+
},
|
|
266
|
+
this.sessionId
|
|
267
|
+
);
|
|
268
|
+
console.error("\n=== MCP Security Statistics ===");
|
|
269
|
+
console.error(JSON.stringify(this.logger.getStatistics(), null, 2));
|
|
270
|
+
// Use setImmediate to allow pending I/O to complete
|
|
271
|
+
setImmediate(() => {
|
|
272
|
+
process.exit(code || 0);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
this.process.on("error", (err) => {
|
|
277
|
+
this.logger.logEvent(
|
|
278
|
+
"SERVER_ERROR",
|
|
279
|
+
"HIGH",
|
|
280
|
+
{
|
|
281
|
+
error: err.message,
|
|
282
|
+
},
|
|
283
|
+
this.sessionId
|
|
284
|
+
);
|
|
285
|
+
console.error("Server process error:", err);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private handleClientInput(input: string): void {
|
|
290
|
+
this.clientMessageBuffer += input;
|
|
291
|
+
const lines = this.clientMessageBuffer.split("\n");
|
|
292
|
+
this.clientMessageBuffer = lines.pop() || "";
|
|
293
|
+
for (const line of lines) {
|
|
294
|
+
if (line.trim()) {
|
|
295
|
+
this.processClientMessage(line);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private processClientMessage(line: string): void {
|
|
301
|
+
try {
|
|
302
|
+
const message: MCPMessage = JSON.parse(line);
|
|
303
|
+
this.logger.logEvent(
|
|
304
|
+
"CLIENT_REQUEST",
|
|
305
|
+
"LOW",
|
|
306
|
+
{
|
|
307
|
+
method: message.method,
|
|
308
|
+
id: message.id,
|
|
309
|
+
},
|
|
310
|
+
this.sessionId
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const violations: string[] = [];
|
|
314
|
+
let shouldBlock = false;
|
|
315
|
+
|
|
316
|
+
if (message.method === "tools/call") {
|
|
317
|
+
const now = Date.now();
|
|
318
|
+
this.toolCallTimestamps.push(now);
|
|
319
|
+
|
|
320
|
+
// Clean up old timestamps to prevent memory leak
|
|
321
|
+
const oneMinuteAgo = now - 60000;
|
|
322
|
+
this.toolCallTimestamps = this.toolCallTimestamps.filter(
|
|
323
|
+
(t) => t > oneMinuteAgo
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
if (!this.policy.checkRateLimit(this.toolCallTimestamps)) {
|
|
327
|
+
violations.push("Rate limit exceeded for tool calls");
|
|
328
|
+
shouldBlock = true;
|
|
329
|
+
this.logger.logEvent(
|
|
330
|
+
"RATE_LIMIT_EXCEEDED",
|
|
331
|
+
"HIGH",
|
|
332
|
+
{
|
|
333
|
+
method: message.method,
|
|
334
|
+
toolName: message.params?.name,
|
|
335
|
+
},
|
|
336
|
+
this.sessionId
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const paramsStr = JSON.stringify(message.params);
|
|
341
|
+
const injectionViolations = this.policy.checkPromptInjection(paramsStr);
|
|
342
|
+
violations.push(...injectionViolations);
|
|
343
|
+
const sensitiveViolations = this.policy.checkSensitiveData(paramsStr);
|
|
344
|
+
violations.push(...sensitiveViolations);
|
|
345
|
+
|
|
346
|
+
// Check multiple possible file path parameter locations
|
|
347
|
+
const filePathParams = [
|
|
348
|
+
message.params?.arguments?.path,
|
|
349
|
+
message.params?.arguments?.filePath,
|
|
350
|
+
message.params?.arguments?.file,
|
|
351
|
+
message.params?.arguments?.directory,
|
|
352
|
+
message.params?.path,
|
|
353
|
+
message.params?.filePath,
|
|
354
|
+
].filter((path): path is string => typeof path === 'string');
|
|
355
|
+
|
|
356
|
+
for (const filePath of filePathParams) {
|
|
357
|
+
const fileViolations = this.policy.checkFileAccess(filePath);
|
|
358
|
+
violations.push(...fileViolations);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
this.logger.logEvent(
|
|
362
|
+
"TOOL_CALL",
|
|
363
|
+
violations.length > 0 ? "HIGH" : "LOW",
|
|
364
|
+
{
|
|
365
|
+
toolName: message.params?.name,
|
|
366
|
+
hasViolations: violations.length > 0,
|
|
367
|
+
violations,
|
|
368
|
+
},
|
|
369
|
+
this.sessionId
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (violations.length > 0) {
|
|
374
|
+
this.logger.logEvent(
|
|
375
|
+
"SECURITY_VIOLATION",
|
|
376
|
+
"CRITICAL",
|
|
377
|
+
{
|
|
378
|
+
violations,
|
|
379
|
+
message: message,
|
|
380
|
+
blocked: shouldBlock,
|
|
381
|
+
},
|
|
382
|
+
this.sessionId
|
|
383
|
+
);
|
|
384
|
+
console.error(
|
|
385
|
+
`\n⚠️ SECURITY VIOLATIONS DETECTED:\n${violations.join("\n")}\n`
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
if (shouldBlock) {
|
|
389
|
+
console.error("🚫 REQUEST BLOCKED\n");
|
|
390
|
+
// Send error response back to client
|
|
391
|
+
if (message.id !== undefined) {
|
|
392
|
+
const errorResponse: MCPMessage = {
|
|
393
|
+
jsonrpc: message.jsonrpc,
|
|
394
|
+
id: message.id,
|
|
395
|
+
error: {
|
|
396
|
+
code: -32000,
|
|
397
|
+
message: "Security violation: Request blocked",
|
|
398
|
+
data: { violations },
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
402
|
+
}
|
|
403
|
+
return; // Don't forward to server
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (this.process && this.process.stdin) {
|
|
408
|
+
this.process.stdin.write(line + "\n");
|
|
409
|
+
}
|
|
410
|
+
} catch (err) {
|
|
411
|
+
this.logger.logEvent(
|
|
412
|
+
"PARSE_ERROR",
|
|
413
|
+
"MEDIUM",
|
|
414
|
+
{
|
|
415
|
+
error: err instanceof Error ? err.message : String(err),
|
|
416
|
+
line: line.substring(0, 100),
|
|
417
|
+
},
|
|
418
|
+
this.sessionId
|
|
419
|
+
);
|
|
420
|
+
console.error(`Failed to parse client message: ${err}`);
|
|
421
|
+
// Forward unparseable messages
|
|
422
|
+
if (this.process && this.process.stdin) {
|
|
423
|
+
this.process.stdin.write(line + "\n");
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private handleServerOutput(output: string): void {
|
|
429
|
+
// Forward output immediately
|
|
430
|
+
process.stdout.write(output);
|
|
431
|
+
|
|
432
|
+
// Buffer and parse for logging
|
|
433
|
+
this.serverMessageBuffer += output;
|
|
434
|
+
const lines = this.serverMessageBuffer.split("\n");
|
|
435
|
+
this.serverMessageBuffer = lines.pop() || "";
|
|
436
|
+
|
|
437
|
+
for (const line of lines) {
|
|
438
|
+
if (line.trim()) {
|
|
439
|
+
try {
|
|
440
|
+
const message: MCPMessage = JSON.parse(line);
|
|
441
|
+
this.logger.logEvent(
|
|
442
|
+
"SERVER_RESPONSE",
|
|
443
|
+
"LOW",
|
|
444
|
+
{
|
|
445
|
+
id: message.id,
|
|
446
|
+
hasError: !!message.error,
|
|
447
|
+
},
|
|
448
|
+
this.sessionId
|
|
449
|
+
);
|
|
450
|
+
} catch (err) {
|
|
451
|
+
// Log parse errors for server output
|
|
452
|
+
this.logger.logEvent(
|
|
453
|
+
"SERVER_PARSE_ERROR",
|
|
454
|
+
"LOW",
|
|
455
|
+
{
|
|
456
|
+
error: err instanceof Error ? err.message : String(err),
|
|
457
|
+
line: line.substring(0, 100),
|
|
458
|
+
},
|
|
459
|
+
this.sessionId
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async function main() {
|
|
468
|
+
const args = process.argv.slice(2);
|
|
469
|
+
if (args.length === 0 || args.includes("--help")) {
|
|
470
|
+
console.log(`
|
|
471
|
+
MCP Security Wrapper - MVP
|
|
472
|
+
|
|
473
|
+
Usage:
|
|
474
|
+
npx ts-node mcp-security-wrapper.ts --server "node server.js" [--config security.json]
|
|
475
|
+
|
|
476
|
+
Options:
|
|
477
|
+
--server <command> Command to start the MCP server (required)
|
|
478
|
+
--config <file> Path to security config JSON file (optional)
|
|
479
|
+
--help Show this help message
|
|
480
|
+
|
|
481
|
+
Example:
|
|
482
|
+
npx ts-node mcp-security-wrapper.ts --server "node server.js" --config security.json
|
|
483
|
+
`);
|
|
484
|
+
process.exit(0);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
let serverCommand: string = "";
|
|
488
|
+
let configFile: string = "";
|
|
489
|
+
|
|
490
|
+
for (let i = 0; i < args.length; i++) {
|
|
491
|
+
if (args[i] === "--server" && args[i + 1]) {
|
|
492
|
+
serverCommand = args[i + 1];
|
|
493
|
+
i++;
|
|
494
|
+
} else if (args[i] === "--config" && args[i + 1]) {
|
|
495
|
+
configFile = args[i + 1];
|
|
496
|
+
i++;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (!serverCommand) {
|
|
501
|
+
console.error("Error: --server argument is required");
|
|
502
|
+
process.exit(1);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let config: SecurityConfig = {};
|
|
506
|
+
if (configFile && fs.existsSync(configFile)) {
|
|
507
|
+
config = JSON.parse(fs.readFileSync(configFile, "utf-8"));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const policy = new SecurityPolicy(config);
|
|
511
|
+
const logger = new SecurityLogger();
|
|
512
|
+
const wrapper = new MCPSecurityWrapper(
|
|
513
|
+
serverCommand.split(" "),
|
|
514
|
+
policy,
|
|
515
|
+
logger
|
|
516
|
+
);
|
|
517
|
+
await wrapper.start();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (require.main === module) {
|
|
521
|
+
main().catch((err) => {
|
|
522
|
+
console.error("Fatal error:", err);
|
|
523
|
+
process.exit(1);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export { MCPSecurityWrapper, SecurityPolicy, SecurityLogger };
|