@toolsdk.ai/registry 1.0.112 → 1.0.113

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.
@@ -1,10 +1,13 @@
1
1
  import { createErrorResponse, createResponse } from "../utils";
2
2
  import { PackageSO } from "./package-so";
3
+ const shouldUseSandbox = () => {
4
+ return process.env.USE_MCP_SANDBOX === "true";
5
+ };
3
6
  export const packageHandler = {
4
7
  executeTool: async (c) => {
5
8
  const requestBody = await c.req.json();
6
9
  try {
7
- const toolSO = new PackageSO();
10
+ const toolSO = new PackageSO(shouldUseSandbox());
8
11
  const result = await toolSO.executeTool(requestBody);
9
12
  const response = createResponse(result);
10
13
  return c.json(response, 200);
@@ -33,7 +36,7 @@ export const packageHandler = {
33
36
  return c.json(errorResponse, 200);
34
37
  }
35
38
  try {
36
- const toolSO = new PackageSO();
39
+ const toolSO = new PackageSO(shouldUseSandbox());
37
40
  const result = await toolSO.getPackageDetail(packageName);
38
41
  const response = createResponse(result);
39
42
  return c.json(response, 200);
@@ -53,7 +56,7 @@ export const packageHandler = {
53
56
  return c.json(errorResponse, 200);
54
57
  }
55
58
  try {
56
- const toolSO = new PackageSO();
59
+ const toolSO = new PackageSO(shouldUseSandbox());
57
60
  const result = await toolSO.listTools(packageName);
58
61
  const response = createResponse(result);
59
62
  return c.json(response, 200);
@@ -1,7 +1,19 @@
1
1
  import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ import { MCPSandboxClient } from "../sandbox/mcp-sandbox-client.js";
2
3
  import type { MCPServerPackageConfig, ToolExecute } from "../types";
3
4
  export declare class PackageSO {
5
+ private useSandbox;
6
+ private sandboxClient;
7
+ private static sandboxInstances;
8
+ private static MAX_SANDBOXES;
9
+ private static IDLE_CLOSE_MS;
10
+ constructor(useSandbox?: boolean);
11
+ static acquireSandbox(key?: string): Promise<MCPSandboxClient>;
12
+ static releaseSandbox(key?: string): Promise<void>;
13
+ static cleanupSandboxInstances(): Promise<void>;
4
14
  executeTool(request: ToolExecute): Promise<unknown>;
15
+ private executeToolInSandbox;
5
16
  listTools(packageName: string): Promise<Tool[]>;
17
+ private listToolsInSandbox;
6
18
  getPackageDetail(packageName: string): Promise<MCPServerPackageConfig>;
7
19
  }
@@ -1,11 +1,144 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { getMcpClient, getPackageConfigByKey, typedAllPackagesList } from "../helper.js";
4
+ import { MCPSandboxClient } from "../sandbox/mcp-sandbox-client.js";
4
5
  import { getDirname } from "../utils";
5
6
  const __dirname = getDirname(import.meta.url);
6
7
  export class PackageSO {
8
+ constructor(useSandbox = false) {
9
+ this.useSandbox = false;
10
+ this.sandboxClient = null;
11
+ this.useSandbox = useSandbox;
12
+ if (useSandbox) {
13
+ const sandboxKey = "default";
14
+ const record = PackageSO.sandboxInstances.get(sandboxKey);
15
+ if (record) {
16
+ record.refCount++;
17
+ record.lastUsedAt = Date.now();
18
+ this.sandboxClient = record.client;
19
+ // Cancel all idle close timer
20
+ record.client.touch();
21
+ }
22
+ else {
23
+ // Create a new instance if none exists (subject to max limit)
24
+ this.sandboxClient = new MCPSandboxClient();
25
+ const newRecord = {
26
+ client: this.sandboxClient,
27
+ refCount: 1,
28
+ lastUsedAt: Date.now(),
29
+ idleCloseMs: PackageSO.IDLE_CLOSE_MS,
30
+ };
31
+ // Try to insert, evict LRU idle instance if MAX is reached
32
+ if (PackageSO.sandboxInstances.size >= PackageSO.MAX_SANDBOXES) {
33
+ // Find the oldest idle instance (refCount === 0)
34
+ let lruKey = null;
35
+ let lruTime = Infinity;
36
+ for (const [k, r] of PackageSO.sandboxInstances) {
37
+ if (r.refCount === 0 && r.lastUsedAt < lruTime) {
38
+ lruKey = k;
39
+ lruTime = r.lastUsedAt;
40
+ }
41
+ }
42
+ if (lruKey) {
43
+ const evict = PackageSO.sandboxInstances.get(lruKey);
44
+ // Immediately close and delete
45
+ if (evict) {
46
+ evict.client.kill().catch((e) => {
47
+ console.error("[PackageSO] Error killing evicted sandbox:", e);
48
+ });
49
+ PackageSO.sandboxInstances.delete(lruKey);
50
+ }
51
+ }
52
+ else {
53
+ // No recyclable instances -> throw error to prevent exceeding system limit
54
+ throw new Error("Cannot create new sandbox: max sandboxes reached and none are idle");
55
+ }
56
+ }
57
+ PackageSO.sandboxInstances.set(sandboxKey, newRecord);
58
+ }
59
+ }
60
+ }
61
+ // Acquire / Release explicit APIs (for external or future use)
62
+ static async acquireSandbox(key = "default") {
63
+ const record = PackageSO.sandboxInstances.get(key);
64
+ if (record) {
65
+ record.refCount++;
66
+ record.lastUsedAt = Date.now();
67
+ record.client.touch();
68
+ return record.client;
69
+ }
70
+ // Create new instance (note MAX_SANDBOXES check, consistent with constructor logic)
71
+ if (PackageSO.sandboxInstances.size >= PackageSO.MAX_SANDBOXES) {
72
+ // Try to evict LRU idle instance
73
+ let lruKey = null;
74
+ let lruTime = Infinity;
75
+ for (const [k, r] of PackageSO.sandboxInstances) {
76
+ if (r.refCount === 0 && r.lastUsedAt < lruTime) {
77
+ lruKey = k;
78
+ lruTime = r.lastUsedAt;
79
+ }
80
+ }
81
+ if (lruKey) {
82
+ const evict = PackageSO.sandboxInstances.get(lruKey);
83
+ if (evict) {
84
+ await evict.client
85
+ .kill()
86
+ .catch((e) => console.error("[PackageSO] Error killing evicted sandbox:", e));
87
+ PackageSO.sandboxInstances.delete(lruKey);
88
+ }
89
+ }
90
+ else {
91
+ throw new Error("Cannot create new sandbox: max sandboxes reached and none are idle");
92
+ }
93
+ }
94
+ const client = new MCPSandboxClient();
95
+ const newRecord = {
96
+ client,
97
+ refCount: 1,
98
+ lastUsedAt: Date.now(),
99
+ idleCloseMs: PackageSO.IDLE_CLOSE_MS,
100
+ };
101
+ PackageSO.sandboxInstances.set(key, newRecord);
102
+ return client;
103
+ }
104
+ static async releaseSandbox(key = "default") {
105
+ const record = PackageSO.sandboxInstances.get(key);
106
+ if (!record)
107
+ return;
108
+ record.refCount = Math.max(0, record.refCount - 1);
109
+ record.lastUsedAt = Date.now();
110
+ if (record.refCount === 0) {
111
+ // schedule idle close after IDLE_CLOSE_MS
112
+ record.client.scheduleIdleClose(record.idleCloseMs || PackageSO.IDLE_CLOSE_MS);
113
+ }
114
+ }
115
+ // Cleanup all static sandbox instances safely
116
+ static async cleanupSandboxInstances() {
117
+ const killers = [];
118
+ for (const [key, record] of PackageSO.sandboxInstances) {
119
+ killers.push((async () => {
120
+ try {
121
+ await record.client.kill();
122
+ }
123
+ catch (err) {
124
+ console.error("[PackageSO] cleanup error for sandbox", key, err);
125
+ }
126
+ })());
127
+ PackageSO.sandboxInstances.delete(key);
128
+ }
129
+ await Promise.all(killers);
130
+ }
7
131
  async executeTool(request) {
8
132
  const mcpServerConfig = getPackageConfigByKey(request.packageName);
133
+ if (this.useSandbox && mcpServerConfig.runtime === "node") {
134
+ try {
135
+ const result = await this.executeToolInSandbox(request);
136
+ return result;
137
+ }
138
+ catch (error) {
139
+ console.warn(`[executeTool] Sandbox mode failed for tool ${request.toolKey} in package ${request.packageName}, falling back to local mode:`, error);
140
+ }
141
+ }
9
142
  const { client, closeConnection } = await getMcpClient(mcpServerConfig, request.envs || {});
10
143
  try {
11
144
  const result = await client.callTool({
@@ -19,8 +152,47 @@ export class PackageSO {
19
152
  await closeConnection();
20
153
  }
21
154
  }
155
+ async executeToolInSandbox(request) {
156
+ if (!this.sandboxClient) {
157
+ throw new Error("Sandbox client not initialized");
158
+ }
159
+ // Initialize if not already initialized (concurrency protection via MCPSandboxClient.initialize)
160
+ await this.sandboxClient.initialize();
161
+ // Mark usage
162
+ this.sandboxClient.touch();
163
+ try {
164
+ const result = await this.sandboxClient.executeTool(request.packageName, request.toolKey, request.inputData || {}, request.envs);
165
+ console.log(`Tool ${request.toolKey} executed successfully in sandbox`);
166
+ return result;
167
+ }
168
+ catch (error) {
169
+ // If it's a sandbox not found error, try reinitializing and retrying once
170
+ if (error instanceof Error && error.message.includes("sandbox was not found")) {
171
+ console.log("[PackageSO] Retrying tool execution after sandbox failure");
172
+ await this.sandboxClient.initialize();
173
+ // Retry tool execution
174
+ const result = await this.sandboxClient.executeTool(request.packageName, request.toolKey, request.inputData || {}, request.envs);
175
+ console.log(`Tool ${request.toolKey} executed successfully in sandbox (retry)`);
176
+ return result;
177
+ }
178
+ throw error;
179
+ }
180
+ finally {
181
+ // Release reference count
182
+ await PackageSO.releaseSandbox("default");
183
+ }
184
+ }
22
185
  async listTools(packageName) {
23
186
  const mcpServerConfig = getPackageConfigByKey(packageName);
187
+ if (this.useSandbox && mcpServerConfig.runtime === "node") {
188
+ try {
189
+ const tools = await this.listToolsInSandbox(packageName);
190
+ return tools;
191
+ }
192
+ catch (error) {
193
+ console.warn(`[listTools] Sandbox mode failed for package ${packageName}, falling back to local mode:`, error);
194
+ }
195
+ }
24
196
  const mockEnvs = {};
25
197
  if (mcpServerConfig.env) {
26
198
  Object.keys(mcpServerConfig.env).forEach((key) => {
@@ -37,6 +209,34 @@ export class PackageSO {
37
209
  await closeConnection();
38
210
  }
39
211
  }
212
+ async listToolsInSandbox(packageName) {
213
+ if (!this.sandboxClient) {
214
+ throw new Error("Sandbox client not initialized");
215
+ }
216
+ // Initialize only if sandbox is not initialized
217
+ await this.sandboxClient.initialize();
218
+ this.sandboxClient.touch();
219
+ try {
220
+ const tools = await this.sandboxClient.listTools(packageName);
221
+ console.log(`Tools list retrieved successfully for package ${packageName} in sandbox`);
222
+ return tools;
223
+ }
224
+ catch (error) {
225
+ // If it's a sandbox not found error, try reinitializing and retrying once
226
+ if (error instanceof Error && error.message.includes("sandbox was not found")) {
227
+ console.log("[PackageSO] Retrying tools listing after sandbox failure");
228
+ await this.sandboxClient.initialize();
229
+ // Retry listing tools
230
+ const tools = await this.sandboxClient.listTools(packageName);
231
+ console.log(`Tools list retrieved successfully for package ${packageName} in sandbox (retry)`);
232
+ return tools;
233
+ }
234
+ throw error;
235
+ }
236
+ finally {
237
+ await PackageSO.releaseSandbox("default");
238
+ }
239
+ }
40
240
  async getPackageDetail(packageName) {
41
241
  const packageInfo = typedAllPackagesList[packageName];
42
242
  if (!packageInfo) {
@@ -58,3 +258,6 @@ export class PackageSO {
58
258
  return packageConfigWithTools;
59
259
  }
60
260
  }
261
+ PackageSO.sandboxInstances = new Map();
262
+ PackageSO.MAX_SANDBOXES = 10;
263
+ PackageSO.IDLE_CLOSE_MS = 5 * 60 * 1000;
package/dist/helper.js CHANGED
@@ -101,6 +101,7 @@ export function updatePackageJsonDependencies({ packageDeps, enableValidation =
101
101
  const packageJsonFile = "./package.json";
102
102
  const packageJSONStr = fs.readFileSync(packageJsonFile, "utf-8");
103
103
  const newDeps = {
104
+ "@e2b/code-interpreter": "^2.0.0",
104
105
  "@modelcontextprotocol/sdk": "^1.12.0",
105
106
  "@hono/node-server": "1.15.0",
106
107
  "@hono/swagger-ui": "^0.5.2",
@@ -0,0 +1,37 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare class MCPSandboxClient {
3
+ private sandbox;
4
+ private initializing;
5
+ private readonly apiKey;
6
+ private toolCache;
7
+ private readonly E2B_SANDBOX_TIMEOUT_MS;
8
+ private readonly TOOL_CACHE_TTL;
9
+ TOOL_EXECUTION_TIMEOUT: number;
10
+ private lastTouchTime;
11
+ private readonly THROTTLE_DELAY_MS;
12
+ createdAt: number | null;
13
+ lastUsedAt: number | null;
14
+ private ttlTimer;
15
+ private autoCloseOnIdleTimer;
16
+ private idleCloseMs;
17
+ constructor(apiKey?: string);
18
+ initialize(): Promise<void>;
19
+ private setupTTLTimer;
20
+ /**
21
+ * Update sandbox last used time
22
+ */
23
+ touch(): void;
24
+ /**
25
+ * Actually perform the touch operation
26
+ */
27
+ private performTouch;
28
+ scheduleIdleClose(idleMs: number): void;
29
+ kill(): Promise<void>;
30
+ clearPackageCache(packageKey: string): void;
31
+ clearAllCache(): void;
32
+ listTools(packageKey: string): Promise<Tool[]>;
33
+ executeTool(packageKey: string, toolName: string, argumentsObj: Record<string, unknown>, envs?: Record<string, string>): Promise<unknown>;
34
+ private runCodeWithTimeout;
35
+ private generateMCPTestCode;
36
+ private generateEnvVariables;
37
+ }