@toolsdk.ai/registry 1.0.113 → 1.0.115
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/README.md +66 -11
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +23 -25
- package/dist/domains/config/config-route.d.ts +2 -0
- package/dist/domains/config/config-route.js +31 -0
- package/dist/domains/config/config-schema.d.ts +57 -0
- package/dist/domains/config/config-schema.js +10 -0
- package/dist/domains/config/config-types.d.ts +3 -0
- package/dist/domains/executor/executor-factory.d.ts +9 -0
- package/dist/domains/executor/executor-factory.js +17 -0
- package/dist/domains/executor/executor-types.d.ts +15 -0
- package/dist/domains/executor/local-executor.d.ts +12 -0
- package/dist/domains/executor/local-executor.js +48 -0
- package/dist/domains/executor/sandbox-executor.d.ts +16 -0
- package/dist/domains/executor/sandbox-executor.js +83 -0
- package/dist/domains/package/package-handler.d.ts +17 -0
- package/dist/domains/package/package-handler.js +58 -0
- package/dist/domains/package/package-repository.d.ts +9 -0
- package/dist/domains/package/package-repository.js +26 -0
- package/dist/domains/package/package-route.d.ts +2 -0
- package/dist/{api → domains/package}/package-route.js +38 -52
- package/dist/domains/package/package-schema.d.ts +244 -0
- package/dist/domains/package/package-schema.js +52 -0
- package/dist/domains/package/package-so.d.ts +78 -0
- package/dist/domains/package/package-so.js +61 -0
- package/dist/domains/package/package-so.test.js +378 -0
- package/dist/domains/package/package-types.d.ts +9 -0
- package/dist/domains/package/package-types.js +1 -0
- package/dist/domains/sandbox/clients/daytona-client.d.ts +17 -0
- package/dist/domains/sandbox/clients/daytona-client.js +112 -0
- package/dist/domains/sandbox/clients/sandock-client.d.ts +19 -0
- package/dist/domains/sandbox/clients/sandock-client.js +178 -0
- package/dist/domains/sandbox/sandbox-factory.d.ts +8 -0
- package/dist/domains/sandbox/sandbox-factory.js +23 -0
- package/dist/domains/sandbox/sandbox-pool-so.d.ts +25 -0
- package/dist/domains/sandbox/sandbox-pool-so.js +123 -0
- package/dist/domains/sandbox/sandbox-types.d.ts +25 -0
- package/dist/domains/sandbox/sandbox-types.js +1 -0
- package/dist/domains/sandbox/sandbox-utils.d.ts +3 -0
- package/dist/domains/sandbox/sandbox-utils.js +109 -0
- package/dist/domains/search/search-handler.d.ts +47 -0
- package/dist/domains/search/search-handler.js +113 -0
- package/dist/domains/search/search-route.d.ts +2 -0
- package/dist/domains/search/search-route.js +101 -0
- package/dist/domains/search/search-schema.d.ts +384 -0
- package/dist/domains/search/search-schema.js +99 -0
- package/dist/domains/search/search-so.d.ts +55 -0
- package/dist/{search/search-service.js → domains/search/search-so.js} +200 -297
- package/dist/shared/config/environment.d.ts +16 -0
- package/dist/shared/config/environment.js +41 -0
- package/dist/shared/schemas/common-schema.d.ts +249 -0
- package/dist/{schema.js → shared/schemas/common-schema.js} +37 -80
- package/dist/shared/schemas/index.d.ts +1 -0
- package/dist/shared/schemas/index.js +1 -0
- package/dist/shared/scripts-helpers/index.d.ts +60 -0
- package/dist/shared/scripts-helpers/index.js +61 -0
- package/dist/shared/utils/file-util.d.ts +1 -0
- package/dist/shared/utils/file-util.js +5 -0
- package/dist/shared/utils/index.d.ts +5 -0
- package/dist/shared/utils/index.js +5 -0
- package/dist/shared/utils/mcp-client-util.d.ts +31 -0
- package/dist/shared/utils/mcp-client-util.js +79 -0
- package/dist/shared/utils/package-util.d.ts +6 -0
- package/dist/shared/utils/package-util.js +53 -0
- package/dist/shared/utils/promise-util.d.ts +1 -0
- package/dist/shared/utils/promise-util.js +14 -0
- package/dist/{utils.d.ts → shared/utils/response-util.d.ts} +6 -2
- package/dist/{utils.js → shared/utils/response-util.js} +1 -6
- package/dist/shared/utils/string-util.d.ts +1 -0
- package/dist/shared/utils/string-util.js +25 -0
- package/dist/shared/utils/validation-util.d.ts +12 -0
- package/dist/shared/utils/validation-util.js +99 -0
- package/indexes/categories-list.json +1 -0
- package/indexes/packages-list.json +6 -0
- package/package.json +9 -2
- package/packages/developer-tools/neurolink.json +23 -0
- package/packages/search-data-extraction/ref-tools-mcp.json +7 -2
- package/README.dev.md +0 -195
- package/dist/api/package-handler.d.ts +0 -18
- package/dist/api/package-handler.js +0 -72
- package/dist/api/package-route.d.ts +0 -2
- package/dist/api/package-so.d.ts +0 -19
- package/dist/api/package-so.js +0 -263
- package/dist/api/package.test.js +0 -19
- package/dist/helper.d.ts +0 -72
- package/dist/helper.js +0 -278
- package/dist/sandbox/mcp-sandbox-client.d.ts +0 -37
- package/dist/sandbox/mcp-sandbox-client.js +0 -428
- package/dist/schema.d.ts +0 -806
- package/dist/search/search-route.d.ts +0 -3
- package/dist/search/search-route.js +0 -305
- package/dist/search/search-service.d.ts +0 -120
- package/dist/search/search.test.js +0 -100
- package/dist/types.d.ts +0 -27
- /package/dist/{api/package.test.d.ts → domains/config/config-types.js} +0 -0
- /package/dist/{search/search.test.d.ts → domains/executor/executor-types.js} +0 -0
- /package/dist/{types.js → domains/package/package-so.test.d.ts} +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createSandockClient } from "sandock";
|
|
3
|
+
import { getSandockConfig } from "../../../shared/config/environment";
|
|
4
|
+
import { getDirname } from "../../../shared/utils/file-util";
|
|
5
|
+
import { extractLastOuterJSON } from "../../../shared/utils/string-util";
|
|
6
|
+
import { PackageRepository } from "../../package/package-repository";
|
|
7
|
+
import { generateMCPTestCode } from "../sandbox-utils";
|
|
8
|
+
/**
|
|
9
|
+
* Sandock Sandbox Client
|
|
10
|
+
* Implements SandboxClient interface for Sandock provider
|
|
11
|
+
*/
|
|
12
|
+
export class SandockSandboxClient {
|
|
13
|
+
constructor(_runtime = "node") {
|
|
14
|
+
this.sandboxId = null;
|
|
15
|
+
this.initializing = null;
|
|
16
|
+
const __dirname = getDirname(import.meta.url);
|
|
17
|
+
const packagesDir = path.join(__dirname, "../../../../packages");
|
|
18
|
+
this.packageRepository = new PackageRepository(packagesDir);
|
|
19
|
+
const config = getSandockConfig();
|
|
20
|
+
this.client = createSandockClient({
|
|
21
|
+
baseUrl: config.apiUrl,
|
|
22
|
+
headers: {
|
|
23
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async initialize() {
|
|
28
|
+
if (this.sandboxId) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (this.initializing) {
|
|
32
|
+
await this.initializing;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
this.initializing = (async () => {
|
|
36
|
+
try {
|
|
37
|
+
// Create sandbox with pre-built MCP image
|
|
38
|
+
const createResponse = await this.client.POST("/api/sandbox", {
|
|
39
|
+
body: {
|
|
40
|
+
image: "seey/sandock-mcp:latest",
|
|
41
|
+
workdir: "/mcpspace",
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
if (!createResponse.data) {
|
|
45
|
+
const errorMsg = "error" in createResponse ? JSON.stringify(createResponse.error) : "Unknown error";
|
|
46
|
+
throw new Error(`Failed to create sandbox: ${errorMsg}`);
|
|
47
|
+
}
|
|
48
|
+
this.sandboxId = createResponse.data.data.id;
|
|
49
|
+
console.log(`[SandockSandboxClient] Sandbox created successfully: ${this.sandboxId}`);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
this.sandboxId = null;
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
this.initializing = null;
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
59
|
+
await this.initializing;
|
|
60
|
+
}
|
|
61
|
+
async executeShellCommand(cmd) {
|
|
62
|
+
if (!this.sandboxId) {
|
|
63
|
+
throw new Error("Sandbox not initialized. Call initialize() first.");
|
|
64
|
+
}
|
|
65
|
+
const response = await this.client.POST("/api/sandbox/{id}/shell", {
|
|
66
|
+
params: {
|
|
67
|
+
path: {
|
|
68
|
+
id: this.sandboxId,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
body: {
|
|
72
|
+
cmd,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
if (!response.data) {
|
|
76
|
+
const errorMsg = "error" in response ? JSON.stringify(response.error) : "Unknown error";
|
|
77
|
+
throw new Error(`Shell command failed: ${errorMsg}`);
|
|
78
|
+
}
|
|
79
|
+
const stdout = response.data.data.stdout || "";
|
|
80
|
+
const stderr = response.data.data.stderr || "";
|
|
81
|
+
if (stderr.trim()) {
|
|
82
|
+
console.log(`[SandockSandboxClient] stderr: ${stderr}`);
|
|
83
|
+
}
|
|
84
|
+
return stdout;
|
|
85
|
+
}
|
|
86
|
+
async executeCode(code) {
|
|
87
|
+
if (!this.sandboxId) {
|
|
88
|
+
throw new Error("Sandbox not initialized. Call initialize() first.");
|
|
89
|
+
}
|
|
90
|
+
// Write code to a temporary file in /mcpspace (accessible to node_modules)
|
|
91
|
+
const tempFile = `/mcpspace/mcp_test_${Date.now()}.mjs`;
|
|
92
|
+
// Use Sandock's file write API (more reliable)
|
|
93
|
+
await this.client.POST("/api/sandbox/{id}/fs/write", {
|
|
94
|
+
params: {
|
|
95
|
+
path: { id: this.sandboxId },
|
|
96
|
+
},
|
|
97
|
+
body: {
|
|
98
|
+
path: tempFile,
|
|
99
|
+
content: code,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
const output = await this.executeShellCommand(`cd /mcpspace && node ${tempFile}`);
|
|
103
|
+
// Asynchronously clean up temp file without blocking result return
|
|
104
|
+
this.client
|
|
105
|
+
.DELETE("/api/sandbox/{id}/fs", {
|
|
106
|
+
params: {
|
|
107
|
+
path: { id: this.sandboxId },
|
|
108
|
+
query: { path: tempFile },
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
.catch((error) => {
|
|
112
|
+
console.warn("[SandockSandboxClient] Warning: Could not delete temp file:", error);
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
exitCode: 0,
|
|
116
|
+
result: output,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async listTools(packageKey) {
|
|
120
|
+
const mcpServerConfig = this.packageRepository.getPackageConfig(packageKey);
|
|
121
|
+
const testCode = generateMCPTestCode(mcpServerConfig, "listTools");
|
|
122
|
+
const response = await this.executeCode(testCode);
|
|
123
|
+
if (response.exitCode !== 0) {
|
|
124
|
+
throw new Error(`Failed to list tools: ${response.result}`);
|
|
125
|
+
}
|
|
126
|
+
const parsedResultStr = extractLastOuterJSON(response.result);
|
|
127
|
+
const result = JSON.parse(parsedResultStr);
|
|
128
|
+
return result.tools;
|
|
129
|
+
}
|
|
130
|
+
async executeTool(packageKey, toolName, argumentsObj, envs) {
|
|
131
|
+
const mcpServerConfig = this.packageRepository.getPackageConfig(packageKey);
|
|
132
|
+
const testCode = generateMCPTestCode(mcpServerConfig, "executeTool", toolName, argumentsObj, envs);
|
|
133
|
+
const response = await this.executeCode(testCode);
|
|
134
|
+
if (response.exitCode !== 0) {
|
|
135
|
+
throw new Error(`Failed to execute tool: ${response.result}`);
|
|
136
|
+
}
|
|
137
|
+
const parsedResultStr = extractLastOuterJSON(response.result);
|
|
138
|
+
const result = JSON.parse(parsedResultStr);
|
|
139
|
+
if (result.isError) {
|
|
140
|
+
console.error("[SandockSandboxClient] Tool execution error:", result.errorMessage);
|
|
141
|
+
throw new Error(result.errorMessage);
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
async destroy() {
|
|
146
|
+
if (!this.sandboxId) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const sandboxIdToStop = this.sandboxId;
|
|
150
|
+
this.sandboxId = null; // Clear immediately to avoid duplicate calls
|
|
151
|
+
// Asynchronously clean up sandbox without blocking result return
|
|
152
|
+
this.client
|
|
153
|
+
.POST("/api/sandbox/{id}/stop", {
|
|
154
|
+
params: {
|
|
155
|
+
path: {
|
|
156
|
+
id: sandboxIdToStop,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
.then((response) => {
|
|
161
|
+
if (!response.data && "error" in response) {
|
|
162
|
+
console.warn(`[SandockSandboxClient] Warning: Could not stop sandbox: ${JSON.stringify(response.error)}`);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.log("[SandockSandboxClient] Sandbox stopped successfully");
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
.catch((err) => {
|
|
169
|
+
const errorMessage = err.message;
|
|
170
|
+
if (errorMessage.includes("not found") || errorMessage.includes("404")) {
|
|
171
|
+
console.log("[SandockSandboxClient] Sandbox already stopped (not found on platform)");
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
console.warn("[SandockSandboxClient] Warning: Could not stop sandbox:", errorMessage);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MCPSandboxProvider, SandboxClient } from "./sandbox-types";
|
|
2
|
+
/**
|
|
3
|
+
* Sandbox Factory
|
|
4
|
+
* Creates appropriate sandbox client based on provider
|
|
5
|
+
*/
|
|
6
|
+
export declare class SandboxFactory {
|
|
7
|
+
static create(runtime: "node" | "python" | "java" | "go", provider: MCPSandboxProvider): SandboxClient;
|
|
8
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DaytonaSandboxClient } from "./clients/daytona-client";
|
|
2
|
+
import { SandockSandboxClient } from "./clients/sandock-client";
|
|
3
|
+
/**
|
|
4
|
+
* Sandbox Factory
|
|
5
|
+
* Creates appropriate sandbox client based on provider
|
|
6
|
+
*/
|
|
7
|
+
// biome-ignore lint/complexity/noStaticOnlyClass: Factory pattern
|
|
8
|
+
export class SandboxFactory {
|
|
9
|
+
static create(runtime, provider) {
|
|
10
|
+
switch (provider) {
|
|
11
|
+
case "SANDOCK":
|
|
12
|
+
return new SandockSandboxClient(runtime);
|
|
13
|
+
case "DAYTONA":
|
|
14
|
+
return new DaytonaSandboxClient(runtime);
|
|
15
|
+
case "E2B":
|
|
16
|
+
throw new Error("E2B sandbox provider is not yet implemented");
|
|
17
|
+
case "LOCAL":
|
|
18
|
+
throw new Error("LOCAL provider should not use sandbox client");
|
|
19
|
+
default:
|
|
20
|
+
throw new Error(`Unknown sandbox provider: ${provider}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MCPSandboxProvider, SandboxClient } from "./sandbox-types";
|
|
2
|
+
/**
|
|
3
|
+
* Sandbox Pool Service Object
|
|
4
|
+
* Manages sandbox instance lifecycle with singleton pattern
|
|
5
|
+
*/
|
|
6
|
+
export declare class SandboxPoolSO {
|
|
7
|
+
private static instance;
|
|
8
|
+
private pools;
|
|
9
|
+
private readonly maxSize;
|
|
10
|
+
private constructor();
|
|
11
|
+
static getInstance(): SandboxPoolSO;
|
|
12
|
+
private getSandboxKey;
|
|
13
|
+
acquire(runtime: "node" | "python" | "java" | "go", provider: MCPSandboxProvider): Promise<SandboxClient>;
|
|
14
|
+
release(runtime: "node" | "python" | "java" | "go", provider: MCPSandboxProvider): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* LRU eviction strategy
|
|
17
|
+
* Evicts least recently used sandbox with zero reference count
|
|
18
|
+
*/
|
|
19
|
+
private evictLRU;
|
|
20
|
+
cleanup(): Promise<void>;
|
|
21
|
+
getPoolStatus(): Record<string, {
|
|
22
|
+
refCount: number;
|
|
23
|
+
lastUsedAt: number;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { SandboxFactory } from "./sandbox-factory";
|
|
2
|
+
/**
|
|
3
|
+
* Sandbox Pool Service Object
|
|
4
|
+
* Manages sandbox instance lifecycle with singleton pattern
|
|
5
|
+
*/
|
|
6
|
+
export class SandboxPoolSO {
|
|
7
|
+
constructor(maxSize = 10) {
|
|
8
|
+
this.pools = new Map();
|
|
9
|
+
this.maxSize = maxSize;
|
|
10
|
+
}
|
|
11
|
+
static getInstance() {
|
|
12
|
+
if (!SandboxPoolSO.instance) {
|
|
13
|
+
SandboxPoolSO.instance = new SandboxPoolSO();
|
|
14
|
+
}
|
|
15
|
+
return SandboxPoolSO.instance;
|
|
16
|
+
}
|
|
17
|
+
getSandboxKey(runtime, provider) {
|
|
18
|
+
return `sandbox-${provider}-${runtime}`;
|
|
19
|
+
}
|
|
20
|
+
async acquire(runtime, provider) {
|
|
21
|
+
const sandboxKey = this.getSandboxKey(runtime, provider);
|
|
22
|
+
const record = this.pools.get(sandboxKey);
|
|
23
|
+
if (record) {
|
|
24
|
+
record.refCount++;
|
|
25
|
+
record.lastUsedAt = Date.now();
|
|
26
|
+
console.log(`[SandboxPoolSO] Reusing existing sandbox: ${sandboxKey} (refCount: ${record.refCount})`);
|
|
27
|
+
return record.client;
|
|
28
|
+
}
|
|
29
|
+
if (this.pools.size >= this.maxSize) {
|
|
30
|
+
await this.evictLRU();
|
|
31
|
+
}
|
|
32
|
+
console.log(`[SandboxPoolSO] Creating new sandbox: ${sandboxKey}`);
|
|
33
|
+
const client = SandboxFactory.create(runtime, provider);
|
|
34
|
+
const newRecord = {
|
|
35
|
+
client,
|
|
36
|
+
refCount: 1,
|
|
37
|
+
lastUsedAt: Date.now(),
|
|
38
|
+
provider,
|
|
39
|
+
runtime,
|
|
40
|
+
};
|
|
41
|
+
this.pools.set(sandboxKey, newRecord);
|
|
42
|
+
return client;
|
|
43
|
+
}
|
|
44
|
+
async release(runtime, provider) {
|
|
45
|
+
const sandboxKey = this.getSandboxKey(runtime, provider);
|
|
46
|
+
const record = this.pools.get(sandboxKey);
|
|
47
|
+
if (!record) {
|
|
48
|
+
console.warn(`[SandboxPoolSO] Sandbox ${sandboxKey} not found in pool`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
record.refCount = Math.max(0, record.refCount - 1);
|
|
52
|
+
record.lastUsedAt = Date.now();
|
|
53
|
+
console.log(`[SandboxPoolSO] Released sandbox: ${sandboxKey} (refCount: ${record.refCount})`);
|
|
54
|
+
if (record.refCount === 0) {
|
|
55
|
+
try {
|
|
56
|
+
await record.client.destroy();
|
|
57
|
+
this.pools.delete(sandboxKey);
|
|
58
|
+
console.log(`[SandboxPoolSO] Sandbox ${sandboxKey} destroyed and removed from pool`);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error(`[SandboxPoolSO] Error destroying sandbox ${sandboxKey}:`, error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* LRU eviction strategy
|
|
67
|
+
* Evicts least recently used sandbox with zero reference count
|
|
68
|
+
*/
|
|
69
|
+
async evictLRU() {
|
|
70
|
+
let lruKey = null;
|
|
71
|
+
let lruTime = Infinity;
|
|
72
|
+
for (const [key, record] of this.pools) {
|
|
73
|
+
if (record.refCount === 0 && record.lastUsedAt < lruTime) {
|
|
74
|
+
lruKey = key;
|
|
75
|
+
lruTime = record.lastUsedAt;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (lruKey) {
|
|
79
|
+
const evictRecord = this.pools.get(lruKey);
|
|
80
|
+
if (evictRecord) {
|
|
81
|
+
try {
|
|
82
|
+
await evictRecord.client.destroy();
|
|
83
|
+
this.pools.delete(lruKey);
|
|
84
|
+
console.log(`[SandboxPoolSO] Evicted LRU sandbox: ${lruKey}`);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.error(`[SandboxPoolSO] Error evicting sandbox ${lruKey}:`, error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
throw new Error("Cannot create new sandbox: max sandboxes reached and none are idle");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async cleanup() {
|
|
96
|
+
console.log(`[SandboxPoolSO] Cleaning up ${this.pools.size} sandbox(es)`);
|
|
97
|
+
const cleanupPromises = [];
|
|
98
|
+
for (const [key, record] of this.pools) {
|
|
99
|
+
cleanupPromises.push((async () => {
|
|
100
|
+
try {
|
|
101
|
+
await record.client.destroy();
|
|
102
|
+
console.log(`[SandboxPoolSO] Cleaned up sandbox: ${key}`);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error(`[SandboxPoolSO] Error cleaning up sandbox ${key}:`, error);
|
|
106
|
+
}
|
|
107
|
+
})());
|
|
108
|
+
}
|
|
109
|
+
await Promise.all(cleanupPromises);
|
|
110
|
+
this.pools.clear();
|
|
111
|
+
console.log("[SandboxPoolSO] All sandboxes cleaned up");
|
|
112
|
+
}
|
|
113
|
+
getPoolStatus() {
|
|
114
|
+
const status = {};
|
|
115
|
+
for (const [key, record] of this.pools) {
|
|
116
|
+
status[key] = {
|
|
117
|
+
refCount: record.refCount,
|
|
118
|
+
lastUsedAt: record.lastUsedAt,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return status;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export interface SandboxExecuteResult {
|
|
3
|
+
exitCode: number;
|
|
4
|
+
result: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Sandbox Client Interface
|
|
8
|
+
* Unified abstraction for different sandbox providers (Daytona, E2B, Sandock)
|
|
9
|
+
*/
|
|
10
|
+
export interface SandboxClient {
|
|
11
|
+
initialize(): Promise<void>;
|
|
12
|
+
listTools(packageKey: string): Promise<Tool[]>;
|
|
13
|
+
executeTool(packageKey: string, toolName: string, args: Record<string, unknown>, envs?: Record<string, string>): Promise<unknown>;
|
|
14
|
+
destroy(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export type MCPSandboxProvider = "SANDOCK" | "DAYTONA" | "E2B" | "LOCAL";
|
|
17
|
+
export interface MCPToolResult {
|
|
18
|
+
toolCount: number;
|
|
19
|
+
tools: Tool[];
|
|
20
|
+
}
|
|
21
|
+
export interface MCPExecuteResult {
|
|
22
|
+
result: unknown;
|
|
23
|
+
isError?: boolean;
|
|
24
|
+
errorMessage?: string;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { MCPServerPackageConfig } from "../package/package-types";
|
|
2
|
+
export declare function generateEnvVariables(env: MCPServerPackageConfig["env"], realEnvs?: Record<string, string>): string;
|
|
3
|
+
export declare function generateMCPTestCode(mcpServerConfig: MCPServerPackageConfig, operation: "listTools" | "executeTool", toolName?: string, argumentsObj?: Record<string, unknown>, envs?: Record<string, string>): string;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export function generateEnvVariables(env, realEnvs) {
|
|
2
|
+
if (!env) {
|
|
3
|
+
return "";
|
|
4
|
+
}
|
|
5
|
+
const envEntries = Object.entries(env).map(([key, _]) => {
|
|
6
|
+
if (realEnvs === null || realEnvs === void 0 ? void 0 : realEnvs[key]) {
|
|
7
|
+
return `${JSON.stringify(key)}: ${JSON.stringify(realEnvs[key])}`;
|
|
8
|
+
}
|
|
9
|
+
return `${JSON.stringify(key)}: "mock_value"`;
|
|
10
|
+
});
|
|
11
|
+
return envEntries.join(",\n ");
|
|
12
|
+
}
|
|
13
|
+
export function generateMCPTestCode(mcpServerConfig, operation, toolName, argumentsObj, envs) {
|
|
14
|
+
const commonCode = `
|
|
15
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
16
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
17
|
+
|
|
18
|
+
async function runMCP() {
|
|
19
|
+
let client;
|
|
20
|
+
try {
|
|
21
|
+
const packageName = "${mcpServerConfig.packageName}";
|
|
22
|
+
|
|
23
|
+
const transport = new StdioClientTransport({
|
|
24
|
+
command: "pnpx",
|
|
25
|
+
args: ["--silent", packageName],
|
|
26
|
+
env: {
|
|
27
|
+
...Object.fromEntries(
|
|
28
|
+
Object.entries(process.env).filter(([_, v]) => v !== undefined)
|
|
29
|
+
),
|
|
30
|
+
PNPM_HOME: "/root/.local/share/pnpm",
|
|
31
|
+
PNPM_STORE_PATH: "/pnpm-store",
|
|
32
|
+
${generateEnvVariables(mcpServerConfig.env, envs)}
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
client = new Client(
|
|
37
|
+
{
|
|
38
|
+
name: "mcp-server-${mcpServerConfig.packageName}-client",
|
|
39
|
+
version: "1.0.0",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
capabilities: {
|
|
43
|
+
tools: {},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
await client.connect(transport);
|
|
49
|
+
`;
|
|
50
|
+
if (operation === "listTools") {
|
|
51
|
+
return `${commonCode}
|
|
52
|
+
const toolsObj = await client.listTools();
|
|
53
|
+
|
|
54
|
+
const result = {
|
|
55
|
+
toolCount: toolsObj.tools.length,
|
|
56
|
+
tools: toolsObj.tools
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
process.stdout.write(JSON.stringify(result));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error("Error in MCP test:", error);
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
process.stdout.write(JSON.stringify({ error: error.message || "Unknown error occurred" }));
|
|
64
|
+
} finally {
|
|
65
|
+
if (client) {
|
|
66
|
+
try {
|
|
67
|
+
await client.close();
|
|
68
|
+
} catch (closeError) {
|
|
69
|
+
console.error("Error closing MCP client:", closeError);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
runMCP();
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
return `${commonCode}
|
|
80
|
+
|
|
81
|
+
const result = await client.callTool({
|
|
82
|
+
name: "${toolName}",
|
|
83
|
+
arguments: ${JSON.stringify(argumentsObj)}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
process.stdout.write(JSON.stringify(result));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("Error in MCP test:", error);
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
process.stdout.write(JSON.stringify({
|
|
91
|
+
result: null,
|
|
92
|
+
isError: true,
|
|
93
|
+
errorMessage: error.message
|
|
94
|
+
}));
|
|
95
|
+
} finally {
|
|
96
|
+
if (client) {
|
|
97
|
+
try {
|
|
98
|
+
await client.close();
|
|
99
|
+
} catch (closeError) {
|
|
100
|
+
console.error("Error closing MCP client:", closeError);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
runMCP();
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const searchHandler: {
|
|
2
|
+
search: ({ q, limit, offset, category, }: {
|
|
3
|
+
q: string;
|
|
4
|
+
limit?: number;
|
|
5
|
+
offset?: number;
|
|
6
|
+
category?: string;
|
|
7
|
+
}) => Promise<{
|
|
8
|
+
success: boolean;
|
|
9
|
+
code: number;
|
|
10
|
+
message: string;
|
|
11
|
+
}>;
|
|
12
|
+
suggest: (q: string, limit?: number) => Promise<{
|
|
13
|
+
success: boolean;
|
|
14
|
+
code: number;
|
|
15
|
+
message: string;
|
|
16
|
+
}>;
|
|
17
|
+
getFacets: () => Promise<{
|
|
18
|
+
success: boolean;
|
|
19
|
+
code: number;
|
|
20
|
+
message: string;
|
|
21
|
+
}>;
|
|
22
|
+
healthCheck: () => Promise<{
|
|
23
|
+
success: boolean;
|
|
24
|
+
code: number;
|
|
25
|
+
message: string;
|
|
26
|
+
}>;
|
|
27
|
+
initialize: () => Promise<{
|
|
28
|
+
success: boolean;
|
|
29
|
+
code: number;
|
|
30
|
+
message: string;
|
|
31
|
+
}>;
|
|
32
|
+
indexPackages: () => Promise<{
|
|
33
|
+
success: boolean;
|
|
34
|
+
code: number;
|
|
35
|
+
message: string;
|
|
36
|
+
}>;
|
|
37
|
+
clearIndex: () => Promise<{
|
|
38
|
+
success: boolean;
|
|
39
|
+
code: number;
|
|
40
|
+
message: string;
|
|
41
|
+
}>;
|
|
42
|
+
getStats: () => Promise<{
|
|
43
|
+
success: boolean;
|
|
44
|
+
code: number;
|
|
45
|
+
message: string;
|
|
46
|
+
}>;
|
|
47
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { getDirname } from "../../shared/utils/file-util";
|
|
3
|
+
import { createErrorResponse, createResponse } from "../../shared/utils/response-util";
|
|
4
|
+
import { SearchSO } from "./search-so";
|
|
5
|
+
const __dirname = getDirname(import.meta.url);
|
|
6
|
+
let searchSO = null;
|
|
7
|
+
async function getSearchSO() {
|
|
8
|
+
if (!searchSO) {
|
|
9
|
+
searchSO = await SearchSO.getInstance();
|
|
10
|
+
}
|
|
11
|
+
return searchSO;
|
|
12
|
+
}
|
|
13
|
+
export const searchHandler = {
|
|
14
|
+
search: async ({ q, limit = 20, offset = 0, category, }) => {
|
|
15
|
+
try {
|
|
16
|
+
const filter = category ? `category = '${category}'` : undefined;
|
|
17
|
+
const so = await getSearchSO();
|
|
18
|
+
const results = await so.search(q, { limit, offset, filter });
|
|
19
|
+
return createResponse(results);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error("Search failed:", error.stack);
|
|
23
|
+
return createErrorResponse(error.message || "Search failed", 500);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
suggest: async (q, limit = 10) => {
|
|
27
|
+
try {
|
|
28
|
+
const so = await getSearchSO();
|
|
29
|
+
const suggestions = await so.suggest(q, limit);
|
|
30
|
+
return createResponse({ suggestions });
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error("Failed to get suggestions:", error.stack);
|
|
34
|
+
return createErrorResponse(error.message || "Failed to get suggestions", 400);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
getFacets: async () => {
|
|
38
|
+
try {
|
|
39
|
+
const so = await getSearchSO();
|
|
40
|
+
const facets = await so.getFacets();
|
|
41
|
+
return createResponse({
|
|
42
|
+
categories: facets === null || facets === void 0 ? void 0 : facets.category,
|
|
43
|
+
authors: facets === null || facets === void 0 ? void 0 : facets.author,
|
|
44
|
+
validated: facets === null || facets === void 0 ? void 0 : facets.validated,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error("Failed to get facets:", error.stack);
|
|
49
|
+
return createErrorResponse(error.message || "Failed to get facets", 500);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
healthCheck: async () => {
|
|
53
|
+
try {
|
|
54
|
+
const so = await getSearchSO();
|
|
55
|
+
const health = await so.healthCheck();
|
|
56
|
+
return createResponse(health);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error("Health check failed:", error.stack);
|
|
60
|
+
return createErrorResponse(error.message || "Health check failed", 500);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
initialize: async () => {
|
|
64
|
+
try {
|
|
65
|
+
searchSO = await SearchSO.getInstance();
|
|
66
|
+
return createResponse({ message: "Search service initialized successfully" });
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error("Failed to initialize search service:", error.stack);
|
|
70
|
+
return createErrorResponse(error.message || "Failed to initialize search service", 500);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
indexPackages: async () => {
|
|
74
|
+
try {
|
|
75
|
+
const so = await getSearchSO();
|
|
76
|
+
const packagesPath = path.join(__dirname, "../../../indexes/packages-list.json");
|
|
77
|
+
const stats = await so.indexPackages(packagesPath);
|
|
78
|
+
return createResponse({
|
|
79
|
+
message: `Indexed ${stats.numberOfDocuments} documents`,
|
|
80
|
+
details: stats,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.error("Failed to index packages:", error.stack);
|
|
85
|
+
return createErrorResponse(error.message || "Failed to index packages", 500);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
clearIndex: async () => {
|
|
89
|
+
try {
|
|
90
|
+
const so = await getSearchSO();
|
|
91
|
+
await so.clearIndex();
|
|
92
|
+
return createResponse({ message: "Index cleared successfully" });
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error("Failed to clear index:", error.stack);
|
|
96
|
+
return createErrorResponse(error.message || "Failed to clear index", 500);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
getStats: async () => {
|
|
100
|
+
try {
|
|
101
|
+
const so = await getSearchSO();
|
|
102
|
+
const stats = await so.getStats();
|
|
103
|
+
return createResponse({
|
|
104
|
+
message: "Index statistics retrieved successfully",
|
|
105
|
+
details: stats,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error("Failed to get index statistics:", error.stack);
|
|
110
|
+
return createErrorResponse(error.message || "Failed to get index statistics", 500);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|