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,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Amir Mironi
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import * as readline from "readline";
|
|
11
|
+
import { execSync } from "child_process";
|
|
12
|
+
|
|
13
|
+
interface MCPRequest {
|
|
14
|
+
jsonrpc: string;
|
|
15
|
+
id: string | number;
|
|
16
|
+
method: string;
|
|
17
|
+
params?: {
|
|
18
|
+
name?: string;
|
|
19
|
+
arguments?: Record<string, string>;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface MCPResponse {
|
|
24
|
+
jsonrpc: string;
|
|
25
|
+
id: string | number;
|
|
26
|
+
result?: unknown;
|
|
27
|
+
error?: {
|
|
28
|
+
code: number;
|
|
29
|
+
message: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class TestMCPServer {
|
|
34
|
+
private rl: readline.Interface;
|
|
35
|
+
|
|
36
|
+
constructor() {
|
|
37
|
+
this.rl = readline.createInterface({
|
|
38
|
+
input: process.stdin,
|
|
39
|
+
output: process.stdout,
|
|
40
|
+
terminal: false,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
start(): void {
|
|
45
|
+
console.error("Test MCP Server started");
|
|
46
|
+
this.rl.on("line", (line) => {
|
|
47
|
+
try {
|
|
48
|
+
const request: MCPRequest = JSON.parse(line);
|
|
49
|
+
this.handleRequest(request);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error("Error parsing request:", err);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
this.rl.on("close", () => {
|
|
55
|
+
console.error("Test MCP Server stopped");
|
|
56
|
+
process.exit(0);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private handleRequest(request: MCPRequest): void {
|
|
61
|
+
let response: MCPResponse;
|
|
62
|
+
switch (request.method) {
|
|
63
|
+
case "initialize":
|
|
64
|
+
response = this.handleInitialize(request);
|
|
65
|
+
break;
|
|
66
|
+
case "tools/list":
|
|
67
|
+
response = this.handleToolsList(request);
|
|
68
|
+
break;
|
|
69
|
+
case "tools/call":
|
|
70
|
+
response = this.handleToolCall(request);
|
|
71
|
+
break;
|
|
72
|
+
default:
|
|
73
|
+
response = {
|
|
74
|
+
jsonrpc: "2.0",
|
|
75
|
+
id: request.id,
|
|
76
|
+
error: {
|
|
77
|
+
code: -32601,
|
|
78
|
+
message: `Method not found: ${request.method}`,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
console.log(JSON.stringify(response));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private handleInitialize(request: MCPRequest): MCPResponse {
|
|
86
|
+
return {
|
|
87
|
+
jsonrpc: "2.0",
|
|
88
|
+
id: request.id,
|
|
89
|
+
result: {
|
|
90
|
+
protocolVersion: "2024-11-05",
|
|
91
|
+
serverInfo: {
|
|
92
|
+
name: "test-mcp-server",
|
|
93
|
+
version: "0.1.0",
|
|
94
|
+
},
|
|
95
|
+
capabilities: {
|
|
96
|
+
tools: {},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private handleToolsList(request: MCPRequest): MCPResponse {
|
|
103
|
+
return {
|
|
104
|
+
jsonrpc: "2.0",
|
|
105
|
+
id: request.id,
|
|
106
|
+
result: {
|
|
107
|
+
tools: [
|
|
108
|
+
{
|
|
109
|
+
name: "read_file",
|
|
110
|
+
description: "Read contents of a file",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {
|
|
114
|
+
path: {
|
|
115
|
+
type: "string",
|
|
116
|
+
description: "Path to the file to read",
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
required: ["path"],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "write_file",
|
|
124
|
+
description: "Write content to a file",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
path: {
|
|
129
|
+
type: "string",
|
|
130
|
+
description: "Path to the file to write",
|
|
131
|
+
},
|
|
132
|
+
content: {
|
|
133
|
+
type: "string",
|
|
134
|
+
description: "Content to write to the file",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
required: ["path", "content"],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "list_directory",
|
|
142
|
+
description: "List contents of a directory",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
path: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "Path to the directory to list",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
required: ["path"],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "execute_command",
|
|
156
|
+
description:
|
|
157
|
+
"Execute a shell command (DANGEROUS - for testing only)",
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: "object",
|
|
160
|
+
properties: {
|
|
161
|
+
command: {
|
|
162
|
+
type: "string",
|
|
163
|
+
description: "Command to execute",
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
required: ["command"],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private handleToolCall(request: MCPRequest): MCPResponse {
|
|
175
|
+
const { name, arguments: args } = request.params || {};
|
|
176
|
+
try {
|
|
177
|
+
let result: unknown;
|
|
178
|
+
switch (name) {
|
|
179
|
+
case "read_file":
|
|
180
|
+
result = this.readFile(args?.path || "");
|
|
181
|
+
break;
|
|
182
|
+
case "write_file":
|
|
183
|
+
result = this.writeFile(args?.path || "", args?.content || "");
|
|
184
|
+
break;
|
|
185
|
+
case "list_directory":
|
|
186
|
+
result = this.listDirectory(args?.path || "");
|
|
187
|
+
break;
|
|
188
|
+
case "execute_command":
|
|
189
|
+
result = this.executeCommand(args?.command || "");
|
|
190
|
+
break;
|
|
191
|
+
default:
|
|
192
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
jsonrpc: "2.0",
|
|
196
|
+
id: request.id,
|
|
197
|
+
result: {
|
|
198
|
+
content: [
|
|
199
|
+
{
|
|
200
|
+
type: "text",
|
|
201
|
+
text: JSON.stringify(result, null, 2),
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
} catch (err) {
|
|
207
|
+
const error = err as Error;
|
|
208
|
+
return {
|
|
209
|
+
jsonrpc: "2.0",
|
|
210
|
+
id: request.id,
|
|
211
|
+
error: {
|
|
212
|
+
code: -32000,
|
|
213
|
+
message: error.message,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private readFile(filePath: string): Record<string, unknown> {
|
|
220
|
+
console.error(`Reading file: ${filePath}`);
|
|
221
|
+
const resolvedPath = path.resolve(filePath);
|
|
222
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
223
|
+
throw new Error(`File not found: ${filePath}`);
|
|
224
|
+
}
|
|
225
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
226
|
+
return {
|
|
227
|
+
path: resolvedPath,
|
|
228
|
+
size: content.length,
|
|
229
|
+
content: content.substring(0, 1000),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private writeFile(
|
|
234
|
+
filePath: string,
|
|
235
|
+
content: string
|
|
236
|
+
): Record<string, unknown> {
|
|
237
|
+
console.error(`Writing file: ${filePath}`);
|
|
238
|
+
const resolvedPath = path.resolve(filePath);
|
|
239
|
+
fs.writeFileSync(resolvedPath, content, "utf-8");
|
|
240
|
+
return {
|
|
241
|
+
path: resolvedPath,
|
|
242
|
+
size: content.length,
|
|
243
|
+
message: "File written successfully",
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private listDirectory(dirPath: string): Record<string, unknown> {
|
|
248
|
+
console.error(`Listing directory: ${dirPath}`);
|
|
249
|
+
const resolvedPath = path.resolve(dirPath);
|
|
250
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
251
|
+
throw new Error(`Directory not found: ${dirPath}`);
|
|
252
|
+
}
|
|
253
|
+
const files = fs.readdirSync(resolvedPath);
|
|
254
|
+
const fileList = files.map((file) => {
|
|
255
|
+
const filePath = path.join(resolvedPath, file);
|
|
256
|
+
const stats = fs.statSync(filePath);
|
|
257
|
+
return {
|
|
258
|
+
name: file,
|
|
259
|
+
type: stats.isDirectory() ? "directory" : "file",
|
|
260
|
+
size: stats.size,
|
|
261
|
+
modified: stats.mtime.toISOString(),
|
|
262
|
+
};
|
|
263
|
+
});
|
|
264
|
+
return {
|
|
265
|
+
path: resolvedPath,
|
|
266
|
+
count: fileList.length,
|
|
267
|
+
files: fileList,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private executeCommand(command: string): Record<string, unknown> {
|
|
272
|
+
console.error(`⚠️ DANGEROUS: Executing command: ${command}`);
|
|
273
|
+
try {
|
|
274
|
+
const output = execSync(command, {
|
|
275
|
+
encoding: "utf-8",
|
|
276
|
+
timeout: 5000,
|
|
277
|
+
});
|
|
278
|
+
return {
|
|
279
|
+
command,
|
|
280
|
+
output,
|
|
281
|
+
exitCode: 0,
|
|
282
|
+
};
|
|
283
|
+
} catch (err) {
|
|
284
|
+
const error = err as Error & { status?: number };
|
|
285
|
+
return {
|
|
286
|
+
command,
|
|
287
|
+
error: error.message,
|
|
288
|
+
exitCode: error.status || 1,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const server = new TestMCPServer();
|
|
295
|
+
server.start();
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"outDir": "dist",
|
|
6
|
+
"rootDir": "src",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"resolveJsonModule": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|