@toolsdk.ai/registry 1.0.116 → 1.0.117
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 +76 -33
- package/dist/domains/sandbox/sandbox-utils.js +16 -1
- 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) {
|
|
@@ -98,22 +108,40 @@ export class SandockSandboxClient {
|
|
|
98
108
|
},
|
|
99
109
|
});
|
|
100
110
|
const output = await this.executeShellCommand(`cd /mcpspace && node ${tempFile}`);
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
query: { path: tempFile },
|
|
107
|
-
},
|
|
108
|
-
})
|
|
109
|
-
.catch((error) => {
|
|
110
|
-
console.warn("[SandockSandboxClient] Warning: Could not delete temp file:", error);
|
|
111
|
+
// Fire-and-forget: cleanup temp file in background without blocking result return
|
|
112
|
+
// The cleanup will complete asynchronously, and logs will appear when it does
|
|
113
|
+
this.cleanupTempFileAsync(tempFile, this.sandboxId).catch((err) => {
|
|
114
|
+
// This should never happen due to internal error handling, but just in case
|
|
115
|
+
console.error("[SandockSandboxClient] Unexpected error in cleanupTempFileAsync:", err);
|
|
111
116
|
});
|
|
112
117
|
return {
|
|
113
118
|
exitCode: 0,
|
|
114
119
|
result: output,
|
|
115
120
|
};
|
|
116
121
|
}
|
|
122
|
+
async cleanupTempFileAsync(tempFile, sandboxId) {
|
|
123
|
+
if (!sandboxId) {
|
|
124
|
+
console.warn("[SandockSandboxClient] Sandbox ID is null, skipping temp file cleanup");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const { error } = await this.client.DELETE("/api/sandbox/{id}/fs", {
|
|
129
|
+
params: {
|
|
130
|
+
path: { id: sandboxId },
|
|
131
|
+
query: { path: tempFile },
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
if (error) {
|
|
135
|
+
console.warn(`[SandockSandboxClient] Warning: Could not delete temp file ${tempFile}:`, error);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
console.log(`[SandockSandboxClient] Temp file ${tempFile} deleted successfully`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (cleanupError) {
|
|
142
|
+
console.warn(`[SandockSandboxClient] Error during temp file cleanup for ${tempFile}:`, cleanupError);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
117
145
|
async listTools(packageKey) {
|
|
118
146
|
const mcpServerConfig = this.packageRepository.getPackageConfig(packageKey);
|
|
119
147
|
const testCode = generateMCPTestCode(mcpServerConfig, "listTools");
|
|
@@ -146,30 +174,45 @@ export class SandockSandboxClient {
|
|
|
146
174
|
}
|
|
147
175
|
const sandboxIdToDelete = this.sandboxId;
|
|
148
176
|
this.sandboxId = null; // Clear immediately to avoid duplicate calls
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
177
|
+
// Fire-and-forget: destroy sandbox in background without blocking
|
|
178
|
+
// The cleanup will complete asynchronously, and logs will appear when it does
|
|
179
|
+
this.destroySandboxAsync(sandboxIdToDelete).catch((err) => {
|
|
180
|
+
// This should never happen due to internal error handling, but just in case
|
|
181
|
+
console.error("[SandockSandboxClient] Unexpected error in destroySandboxAsync:", err);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
async destroySandboxAsync(sandboxId) {
|
|
185
|
+
try {
|
|
186
|
+
// Delete sandbox completely using the fs delete API with root path
|
|
187
|
+
// This removes all files and effectively shuts down the sandbox
|
|
188
|
+
const { error } = await this.client.DELETE("/api/sandbox/{id}/fs", {
|
|
189
|
+
params: {
|
|
190
|
+
path: { id: sandboxId },
|
|
191
|
+
query: { path: "/" },
|
|
192
|
+
},
|
|
193
|
+
});
|
|
158
194
|
if (error) {
|
|
159
|
-
|
|
195
|
+
// Check if error is "not found" (already deleted)
|
|
196
|
+
const errorStr = JSON.stringify(error);
|
|
197
|
+
if (errorStr.includes("not found") || errorStr.includes("404")) {
|
|
198
|
+
console.log(`[SandockSandboxClient] Sandbox ${sandboxId} already deleted (not found on platform)`);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
console.warn(`[SandockSandboxClient] Warning: Could not delete sandbox ${sandboxId}: ${errorStr}`);
|
|
202
|
+
}
|
|
160
203
|
}
|
|
161
204
|
else {
|
|
162
|
-
console.log(
|
|
205
|
+
console.log(`[SandockSandboxClient] Sandbox ${sandboxId} deleted successfully`);
|
|
163
206
|
}
|
|
164
|
-
}
|
|
165
|
-
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
166
209
|
const errorMessage = err.message;
|
|
167
210
|
if (errorMessage.includes("not found") || errorMessage.includes("404")) {
|
|
168
|
-
console.log(
|
|
211
|
+
console.log(`[SandockSandboxClient] Sandbox ${sandboxId} already deleted (not found on platform)`);
|
|
169
212
|
}
|
|
170
213
|
else {
|
|
171
|
-
console.warn(
|
|
214
|
+
console.warn(`[SandockSandboxClient] Warning: Could not delete sandbox ${sandboxId}: ${errorMessage}`);
|
|
172
215
|
}
|
|
173
|
-
}
|
|
216
|
+
}
|
|
174
217
|
}
|
|
175
218
|
}
|
|
@@ -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: {
|
|
@@ -69,6 +70,13 @@ async function runMCP() {
|
|
|
69
70
|
console.error("Error closing MCP client:", closeError);
|
|
70
71
|
}
|
|
71
72
|
}
|
|
73
|
+
if (transport) {
|
|
74
|
+
try {
|
|
75
|
+
await transport.close();
|
|
76
|
+
} catch (transportError) {
|
|
77
|
+
console.error("Error closing transport:", transportError);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
|
|
@@ -100,6 +108,13 @@ runMCP();
|
|
|
100
108
|
console.error("Error closing MCP client:", closeError);
|
|
101
109
|
}
|
|
102
110
|
}
|
|
111
|
+
if (transport) {
|
|
112
|
+
try {
|
|
113
|
+
await transport.close();
|
|
114
|
+
} catch (transportError) {
|
|
115
|
+
console.error("Error closing transport:", transportError);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
103
118
|
}
|
|
104
119
|
}
|
|
105
120
|
|
|
@@ -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
|
}
|