Skip to content

Commit a1ab8d4

Browse files
committed
adding executor
1 parent 25439ed commit a1ab8d4

File tree

7 files changed

+1004
-701
lines changed

7 files changed

+1004
-701
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { IMCPToolCall, IMCPToolResult } from "../../types/mcp";
2+
3+
export interface IToolCallRecord {
4+
name: string;
5+
arguments: Record<string, any>;
6+
result: IMCPToolResult;
7+
executionTime: number;
8+
}
9+
10+
export interface ICodeExecutionResult {
11+
success: boolean;
12+
output: string;
13+
error?: string;
14+
executionTime: number;
15+
toolsCalled: string[];
16+
toolCallRecords: IToolCallRecord[];
17+
returnValue?: any;
18+
}
19+
20+
export interface IExecutionContext {
21+
executeToolCall: (name: string, args: Record<string, any>) => Promise<IMCPToolResult>;
22+
console: {
23+
log: (...args: any[]) => void;
24+
error: (...args: any[]) => void;
25+
warn: (...args: any[]) => void;
26+
};
27+
}
28+
29+
export class CodeExecutor {
30+
private executionTimeout: number = 30000; // 30 seconds default
31+
private toolsCalled: string[] = [];
32+
private toolCallRecords: IToolCallRecord[] = [];
33+
private consoleOutput: string[] = [];
34+
35+
constructor(
36+
private executeToolCallback: (toolCall: IMCPToolCall) => Promise<IMCPToolResult>,
37+
timeout?: number
38+
) {
39+
if (timeout) {
40+
this.executionTimeout = timeout;
41+
}
42+
}
43+
44+
async execute(code: string): Promise<ICodeExecutionResult> {
45+
const startTime = Date.now();
46+
this.toolsCalled = [];
47+
this.toolCallRecords = [];
48+
this.consoleOutput = [];
49+
50+
try {
51+
this.validateCode(code);
52+
const context = this.createExecutionContext();
53+
const result = await this.executeWithTimeout(code, context);
54+
const executionTime = Date.now() - startTime;
55+
56+
return {
57+
success: true,
58+
output: this.consoleOutput.join('\n'),
59+
executionTime,
60+
toolsCalled: this.toolsCalled,
61+
toolCallRecords: this.toolCallRecords,
62+
returnValue: result
63+
};
64+
65+
} catch (error) {
66+
const executionTime = Date.now() - startTime;
67+
return {
68+
success: false,
69+
output: this.consoleOutput.join('\n'),
70+
error: error.message || String(error),
71+
executionTime,
72+
toolsCalled: this.toolsCalled,
73+
toolCallRecords: this.toolCallRecords
74+
};
75+
}
76+
}
77+
78+
private validateCode(code: string): void {
79+
// Check for dangerous patterns
80+
const dangerousPatterns = [
81+
/\brequire\s*\(/,
82+
/\bimport\s+/,
83+
/\bprocess\./,
84+
/\b__dirname\b/,
85+
/\b__filename\b/,
86+
/\beval\s*\(/,
87+
/\bFunction\s*\(/,
88+
/\bglobal\./,
89+
/\bwindow\./,
90+
/\bdocument\./,
91+
];
92+
93+
for (const pattern of dangerousPatterns) {
94+
if (pattern.test(code)) {
95+
throw new Error(`Code contains prohibited pattern: ${pattern.source}`);
96+
}
97+
}
98+
99+
// Basic syntax validation
100+
if (!code.trim()) {
101+
throw new Error('Code cannot be empty');
102+
}
103+
}
104+
105+
private createExecutionContext(): IExecutionContext {
106+
const self = this;
107+
108+
return {
109+
executeToolCall: async (name: string, args: Record<string, any>) => {
110+
const toolStartTime = Date.now();
111+
self.toolsCalled.push(name);
112+
113+
const result = await self.executeToolCallback({ name, arguments: args });
114+
const toolExecutionTime = Date.now() - toolStartTime;
115+
116+
// Record full tool call details
117+
self.toolCallRecords.push({
118+
name,
119+
arguments: args,
120+
result,
121+
executionTime: toolExecutionTime
122+
});
123+
124+
return result;
125+
},
126+
console: {
127+
log: (...args: any[]) => {
128+
self.consoleOutput.push(args.map(a => String(a)).join(' '));
129+
},
130+
error: (...args: any[]) => {
131+
self.consoleOutput.push('[ERROR] ' + args.map(a => String(a)).join(' '));
132+
},
133+
warn: (...args: any[]) => {
134+
self.consoleOutput.push('[WARN] ' + args.map(a => String(a)).join(' '));
135+
}
136+
}
137+
};
138+
}
139+
140+
private async executeWithTimeout(code: string, context: IExecutionContext): Promise<any> {
141+
return new Promise((resolve, reject) => {
142+
// Set timeout
143+
const timeoutHandle = setTimeout(() => {
144+
reject(new Error(`Code execution timeout after ${this.executionTimeout}ms`));
145+
}, this.executionTimeout);
146+
147+
const helperFunctions = `
148+
async function callMCPTool(name, args) {
149+
return await executeToolCall(name, args || {});
150+
}
151+
`;
152+
153+
const wrappedCode = `
154+
${helperFunctions}
155+
156+
return (async () => {
157+
${code}
158+
})();
159+
`;
160+
161+
try {
162+
const AsyncFunction = async function () {}.constructor as any;
163+
const executor = new AsyncFunction(
164+
'executeToolCall',
165+
'console',
166+
wrappedCode
167+
);
168+
169+
executor(
170+
context.executeToolCall,
171+
context.console
172+
).then((result: any) => {
173+
clearTimeout(timeoutHandle);
174+
resolve(result);
175+
}).catch((error: any) => {
176+
clearTimeout(timeoutHandle);
177+
reject(error);
178+
});
179+
180+
} catch (error) {
181+
clearTimeout(timeoutHandle);
182+
reject(error);
183+
}
184+
});
185+
}
186+
187+
setExecutionTimeout(timeout: number): void {
188+
this.executionTimeout = timeout;
189+
}
190+
}

0 commit comments

Comments
 (0)