@toolsdk.ai/registry 1.0.116 → 1.0.118
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/dist/domains/sandbox/clients/daytona-client.js +18 -8
- package/dist/domains/sandbox/clients/sandock-client.d.ts +2 -0
- package/dist/domains/sandbox/clients/sandock-client.js +79 -34
- package/dist/domains/sandbox/sandbox-utils.js +16 -2
- package/dist/shared/utils/mcp-client-util.js +7 -0
- package/package.json +1 -1
|
@@ -5,6 +5,22 @@ import { getDirname } from "../../../shared/utils/file-util";
|
|
|
5
5
|
import { extractLastOuterJSON } from "../../../shared/utils/string-util";
|
|
6
6
|
import { PackageRepository } from "../../package/package-repository";
|
|
7
7
|
import { generateMCPTestCode } from "../sandbox-utils";
|
|
8
|
+
// Singleton Daytona client shared across all instances to prevent memory leaks
|
|
9
|
+
let sharedDaytonaClient = null;
|
|
10
|
+
function getDaytonaClient() {
|
|
11
|
+
if (!sharedDaytonaClient) {
|
|
12
|
+
const config = getDaytonaConfig();
|
|
13
|
+
const daytonaConfig = {
|
|
14
|
+
apiKey: config.apiKey,
|
|
15
|
+
};
|
|
16
|
+
if (config.apiUrl) {
|
|
17
|
+
daytonaConfig.apiUrl = config.apiUrl;
|
|
18
|
+
}
|
|
19
|
+
sharedDaytonaClient = new Daytona(daytonaConfig);
|
|
20
|
+
console.log("[DaytonaSandboxClient] Shared Daytona client initialized");
|
|
21
|
+
}
|
|
22
|
+
return sharedDaytonaClient;
|
|
23
|
+
}
|
|
8
24
|
/**
|
|
9
25
|
* Daytona Sandbox Client
|
|
10
26
|
* Implements SandboxClient interface for Daytona provider
|
|
@@ -27,14 +43,8 @@ export class DaytonaSandboxClient {
|
|
|
27
43
|
}
|
|
28
44
|
this.initializing = (async () => {
|
|
29
45
|
try {
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
apiKey: config.apiKey,
|
|
33
|
-
};
|
|
34
|
-
if (config.apiUrl) {
|
|
35
|
-
daytonaConfig.apiUrl = config.apiUrl;
|
|
36
|
-
}
|
|
37
|
-
const daytona = new Daytona(daytonaConfig);
|
|
46
|
+
// Use shared singleton Daytona client instead of creating new one per initialization
|
|
47
|
+
const daytona = getDaytonaClient();
|
|
38
48
|
const declarativeImage = Image.base("node:20")
|
|
39
49
|
.runCommands("npm install -g pnpm", "mkdir -p /workspace", "cd /workspace && npm init -y", "cd /workspace && pnpm add @modelcontextprotocol/sdk")
|
|
40
50
|
.workdir("/workspace");
|
|
@@ -13,7 +13,9 @@ export declare class SandockSandboxClient implements SandboxClient {
|
|
|
13
13
|
initialize(): Promise<void>;
|
|
14
14
|
private executeShellCommand;
|
|
15
15
|
private executeCode;
|
|
16
|
+
private cleanupTempFileAsync;
|
|
16
17
|
listTools(packageKey: string): Promise<Tool[]>;
|
|
17
18
|
executeTool(packageKey: string, toolName: string, argumentsObj: Record<string, unknown>, envs?: Record<string, string>): Promise<unknown>;
|
|
18
19
|
destroy(): Promise<void>;
|
|
20
|
+
private destroySandboxAsync;
|
|
19
21
|
}
|
|
@@ -5,6 +5,21 @@ import { getDirname } from "../../../shared/utils/file-util";
|
|
|
5
5
|
import { extractLastOuterJSON } from "../../../shared/utils/string-util";
|
|
6
6
|
import { PackageRepository } from "../../package/package-repository";
|
|
7
7
|
import { generateMCPTestCode } from "../sandbox-utils";
|
|
8
|
+
// Singleton HTTP client shared across all instances to prevent memory leaks
|
|
9
|
+
let sharedSandockClient = null;
|
|
10
|
+
function getSandockClient() {
|
|
11
|
+
if (!sharedSandockClient) {
|
|
12
|
+
const config = getSandockConfig();
|
|
13
|
+
sharedSandockClient = createSandockClient({
|
|
14
|
+
baseUrl: config.apiUrl,
|
|
15
|
+
headers: {
|
|
16
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
console.log("[SandockSandboxClient] Shared HTTP client initialized");
|
|
20
|
+
}
|
|
21
|
+
return sharedSandockClient;
|
|
22
|
+
}
|
|
8
23
|
/**
|
|
9
24
|
* Sandock Sandbox Client
|
|
10
25
|
* Implements SandboxClient interface for Sandock provider
|
|
@@ -16,13 +31,8 @@ export class SandockSandboxClient {
|
|
|
16
31
|
const __dirname = getDirname(import.meta.url);
|
|
17
32
|
const packagesDir = path.join(__dirname, "../../../../packages");
|
|
18
33
|
this.packageRepository = new PackageRepository(packagesDir);
|
|
19
|
-
|
|
20
|
-
this.client =
|
|
21
|
-
baseUrl: config.apiUrl,
|
|
22
|
-
headers: {
|
|
23
|
-
Authorization: `Bearer ${config.apiKey}`,
|
|
24
|
-
},
|
|
25
|
-
});
|
|
34
|
+
// Use shared singleton client instead of creating new one per instance
|
|
35
|
+
this.client = getSandockClient();
|
|
26
36
|
}
|
|
27
37
|
async initialize() {
|
|
28
38
|
if (this.sandboxId) {
|
|
@@ -39,6 +49,7 @@ export class SandockSandboxClient {
|
|
|
39
49
|
body: {
|
|
40
50
|
image: "seey/sandock-mcp:latest",
|
|
41
51
|
workdir: "/mcpspace",
|
|
52
|
+
usePersistentCache: true,
|
|
42
53
|
},
|
|
43
54
|
});
|
|
44
55
|
if (error) {
|
|
@@ -97,23 +108,42 @@ export class SandockSandboxClient {
|
|
|
97
108
|
content: code,
|
|
98
109
|
},
|
|
99
110
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
},
|
|
108
|
-
})
|
|
109
|
-
.catch((error) => {
|
|
110
|
-
console.warn("[SandockSandboxClient] Warning: Could not delete temp file:", error);
|
|
111
|
+
// Set custom pnpm store directory (volume-mounted cache directory) before executing code
|
|
112
|
+
const output = await this.executeShellCommand(`cd /mcpspace && pnpm config set store-dir /data/pnpm-store && node ${tempFile}`);
|
|
113
|
+
// Fire-and-forget: cleanup temp file in background without blocking result return
|
|
114
|
+
// The cleanup will complete asynchronously, and logs will appear when it does
|
|
115
|
+
this.cleanupTempFileAsync(tempFile, this.sandboxId).catch((err) => {
|
|
116
|
+
// This should never happen due to internal error handling, but just in case
|
|
117
|
+
console.error("[SandockSandboxClient] Unexpected error in cleanupTempFileAsync:", err);
|
|
111
118
|
});
|
|
112
119
|
return {
|
|
113
120
|
exitCode: 0,
|
|
114
121
|
result: output,
|
|
115
122
|
};
|
|
116
123
|
}
|
|
124
|
+
async cleanupTempFileAsync(tempFile, sandboxId) {
|
|
125
|
+
if (!sandboxId) {
|
|
126
|
+
console.warn("[SandockSandboxClient] Sandbox ID is null, skipping temp file cleanup");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const { error } = await this.client.DELETE("/api/sandbox/{id}/fs", {
|
|
131
|
+
params: {
|
|
132
|
+
path: { id: sandboxId },
|
|
133
|
+
query: { path: tempFile },
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
if (error) {
|
|
137
|
+
console.warn(`[SandockSandboxClient] Warning: Could not delete temp file ${tempFile}:`, error);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.log(`[SandockSandboxClient] Temp file ${tempFile} deleted successfully`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (cleanupError) {
|
|
144
|
+
console.warn(`[SandockSandboxClient] Error during temp file cleanup for ${tempFile}:`, cleanupError);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
117
147
|
async listTools(packageKey) {
|
|
118
148
|
const mcpServerConfig = this.packageRepository.getPackageConfig(packageKey);
|
|
119
149
|
const testCode = generateMCPTestCode(mcpServerConfig, "listTools");
|
|
@@ -146,30 +176,45 @@ export class SandockSandboxClient {
|
|
|
146
176
|
}
|
|
147
177
|
const sandboxIdToDelete = this.sandboxId;
|
|
148
178
|
this.sandboxId = null; // Clear immediately to avoid duplicate calls
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
179
|
+
// Fire-and-forget: destroy sandbox in background without blocking
|
|
180
|
+
// The cleanup will complete asynchronously, and logs will appear when it does
|
|
181
|
+
this.destroySandboxAsync(sandboxIdToDelete).catch((err) => {
|
|
182
|
+
// This should never happen due to internal error handling, but just in case
|
|
183
|
+
console.error("[SandockSandboxClient] Unexpected error in destroySandboxAsync:", err);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
async destroySandboxAsync(sandboxId) {
|
|
187
|
+
try {
|
|
188
|
+
// Delete sandbox completely using the fs delete API with root path
|
|
189
|
+
// This removes all files and effectively shuts down the sandbox
|
|
190
|
+
const { error } = await this.client.DELETE("/api/sandbox/{id}/fs", {
|
|
191
|
+
params: {
|
|
192
|
+
path: { id: sandboxId },
|
|
193
|
+
query: { path: "/" },
|
|
194
|
+
},
|
|
195
|
+
});
|
|
158
196
|
if (error) {
|
|
159
|
-
|
|
197
|
+
// Check if error is "not found" (already deleted)
|
|
198
|
+
const errorStr = JSON.stringify(error);
|
|
199
|
+
if (errorStr.includes("not found") || errorStr.includes("404")) {
|
|
200
|
+
console.log(`[SandockSandboxClient] Sandbox ${sandboxId} already deleted (not found on platform)`);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
console.warn(`[SandockSandboxClient] Warning: Could not delete sandbox ${sandboxId}: ${errorStr}`);
|
|
204
|
+
}
|
|
160
205
|
}
|
|
161
206
|
else {
|
|
162
|
-
console.log(
|
|
207
|
+
console.log(`[SandockSandboxClient] Sandbox ${sandboxId} deleted successfully`);
|
|
163
208
|
}
|
|
164
|
-
}
|
|
165
|
-
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
166
211
|
const errorMessage = err.message;
|
|
167
212
|
if (errorMessage.includes("not found") || errorMessage.includes("404")) {
|
|
168
|
-
console.log(
|
|
213
|
+
console.log(`[SandockSandboxClient] Sandbox ${sandboxId} already deleted (not found on platform)`);
|
|
169
214
|
}
|
|
170
215
|
else {
|
|
171
|
-
console.warn(
|
|
216
|
+
console.warn(`[SandockSandboxClient] Warning: Could not delete sandbox ${sandboxId}: ${errorMessage}`);
|
|
172
217
|
}
|
|
173
|
-
}
|
|
218
|
+
}
|
|
174
219
|
}
|
|
175
220
|
}
|
|
@@ -17,10 +17,11 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
|
17
17
|
|
|
18
18
|
async function runMCP() {
|
|
19
19
|
let client;
|
|
20
|
+
let transport;
|
|
20
21
|
try {
|
|
21
22
|
const packageName = "${mcpServerConfig.packageName}";
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
transport = new StdioClientTransport({
|
|
24
25
|
command: "pnpx",
|
|
25
26
|
args: ["--silent", packageName],
|
|
26
27
|
env: {
|
|
@@ -28,7 +29,6 @@ async function runMCP() {
|
|
|
28
29
|
Object.entries(process.env).filter(([_, v]) => v !== undefined)
|
|
29
30
|
),
|
|
30
31
|
PNPM_HOME: "/root/.local/share/pnpm",
|
|
31
|
-
PNPM_STORE_PATH: "/pnpm-store",
|
|
32
32
|
${generateEnvVariables(mcpServerConfig.env, envs)}
|
|
33
33
|
},
|
|
34
34
|
});
|
|
@@ -69,6 +69,13 @@ async function runMCP() {
|
|
|
69
69
|
console.error("Error closing MCP client:", closeError);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
+
if (transport) {
|
|
73
|
+
try {
|
|
74
|
+
await transport.close();
|
|
75
|
+
} catch (transportError) {
|
|
76
|
+
console.error("Error closing transport:", transportError);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
72
79
|
}
|
|
73
80
|
}
|
|
74
81
|
|
|
@@ -100,6 +107,13 @@ runMCP();
|
|
|
100
107
|
console.error("Error closing MCP client:", closeError);
|
|
101
108
|
}
|
|
102
109
|
}
|
|
110
|
+
if (transport) {
|
|
111
|
+
try {
|
|
112
|
+
await transport.close();
|
|
113
|
+
} catch (transportError) {
|
|
114
|
+
console.error("Error closing transport:", transportError);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
103
117
|
}
|
|
104
118
|
}
|
|
105
119
|
|
|
@@ -32,6 +32,13 @@ async function createMcpClient(mcpServerConfig, transport) {
|
|
|
32
32
|
catch (e) {
|
|
33
33
|
console.warn(`${packageName} mcp client close failure.`, e);
|
|
34
34
|
}
|
|
35
|
+
// Close transport to release child process and file descriptors
|
|
36
|
+
try {
|
|
37
|
+
await transport.close();
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
console.warn(`${packageName} mcp transport close failure.`, e);
|
|
41
|
+
}
|
|
35
42
|
};
|
|
36
43
|
return { client, transport, closeConnection };
|
|
37
44
|
}
|