@toolsdk.ai/registry 1.0.113 → 1.0.114

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.
@@ -0,0 +1,491 @@
1
+ // npx vitest run src/sandbox/mcp-sandbox-client.test.ts
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { MCPSandboxClient } from "./mcp-sandbox-client";
4
+ // Mock Daytona SDK
5
+ vi.mock("@daytonaio/sdk", () => {
6
+ const mockSandbox = {
7
+ process: {
8
+ codeRun: vi.fn(),
9
+ },
10
+ delete: vi.fn().mockResolvedValue(undefined),
11
+ };
12
+ const mockVolume = {
13
+ id: "mock-volume-id",
14
+ };
15
+ const mockDaytona = {
16
+ create: vi.fn().mockResolvedValue(mockSandbox),
17
+ volume: {
18
+ get: vi.fn().mockResolvedValue(mockVolume),
19
+ },
20
+ };
21
+ return {
22
+ Daytona: vi.fn(() => mockDaytona),
23
+ Image: {
24
+ base: vi.fn().mockReturnValue({
25
+ runCommands: vi.fn().mockReturnThis(),
26
+ workdir: vi.fn().mockReturnThis(),
27
+ }),
28
+ },
29
+ };
30
+ });
31
+ // Mock helper functions
32
+ vi.mock("../helper", () => ({
33
+ extractLastOuterJSON: vi.fn((str) => str),
34
+ getPackageConfigByKey: vi.fn(async (key) => ({
35
+ type: "mcp-server",
36
+ packageName: `@test/${key}`,
37
+ name: "Test Package",
38
+ description: "Test Description",
39
+ runtime: "node",
40
+ env: {
41
+ TEST_API_KEY: {
42
+ description: "Test API Key",
43
+ required: true,
44
+ },
45
+ },
46
+ })),
47
+ }));
48
+ // Helper function to get mocked Daytona instance
49
+ async function getMockedDaytonaInstance() {
50
+ const { Daytona } = await import("@daytonaio/sdk");
51
+ const DaytonaMock = Daytona;
52
+ return DaytonaMock.mock.results[0].value;
53
+ }
54
+ // Helper function to get mocked sandbox
55
+ async function getMockedSandbox() {
56
+ const daytonaInstance = await getMockedDaytonaInstance();
57
+ return daytonaInstance.create.mock.results[0].value;
58
+ }
59
+ // Helper function to mock extractLastOuterJSON
60
+ async function getMockedExtractJSON() {
61
+ const { extractLastOuterJSON } = await import("../helper");
62
+ return extractLastOuterJSON;
63
+ }
64
+ describe("MCPSandboxClient - MCP Sandbox Client Unit Tests", () => {
65
+ let client;
66
+ beforeEach(() => {
67
+ vi.clearAllMocks();
68
+ client = new MCPSandboxClient("node");
69
+ });
70
+ afterEach(async () => {
71
+ if (client) {
72
+ await client.kill();
73
+ }
74
+ });
75
+ describe("Initialization Tests", () => {
76
+ it("should successfully initialize Sandbox", async () => {
77
+ await client.initialize();
78
+ // Verify Daytona is called correctly
79
+ const { Daytona } = await import("@daytonaio/sdk");
80
+ expect(Daytona).toHaveBeenCalledWith({
81
+ apiKey: expect.any(String),
82
+ });
83
+ });
84
+ it("should prevent duplicate initialization", async () => {
85
+ // First initialization
86
+ await client.initialize();
87
+ // Second initialization should return directly without creating new sandbox
88
+ await client.initialize();
89
+ const daytonaInstance = await getMockedDaytonaInstance();
90
+ // create should only be called once
91
+ expect(daytonaInstance.create).toHaveBeenCalledTimes(1);
92
+ });
93
+ it("should support concurrent initialization calls", async () => {
94
+ // Initiate multiple initialization requests simultaneously
95
+ const promises = [client.initialize(), client.initialize(), client.initialize()];
96
+ await Promise.all(promises);
97
+ const daytonaInstance = await getMockedDaytonaInstance();
98
+ // Even with concurrent calls, create should only be called once
99
+ expect(daytonaInstance.create).toHaveBeenCalledTimes(1);
100
+ });
101
+ it("should use correct runtime configuration", () => {
102
+ const nodeClient = new MCPSandboxClient("node");
103
+ const pythonClient = new MCPSandboxClient("python");
104
+ expect(nodeClient).toBeDefined();
105
+ expect(pythonClient).toBeDefined();
106
+ });
107
+ });
108
+ describe("listTools - List Tools Tests", () => {
109
+ it("should successfully list tools", async () => {
110
+ await client.initialize();
111
+ const mockTools = [
112
+ {
113
+ name: "test_tool_1",
114
+ description: "Test Tool 1",
115
+ inputSchema: {
116
+ type: "object",
117
+ properties: {
118
+ param1: { type: "string" },
119
+ },
120
+ },
121
+ },
122
+ {
123
+ name: "test_tool_2",
124
+ description: "Test Tool 2",
125
+ inputSchema: {
126
+ type: "object",
127
+ properties: {
128
+ param2: { type: "number" },
129
+ },
130
+ },
131
+ },
132
+ ];
133
+ const mockResponse = {
134
+ exitCode: 0,
135
+ result: JSON.stringify({
136
+ toolCount: 2,
137
+ tools: mockTools,
138
+ }),
139
+ };
140
+ const mockSandbox = await getMockedSandbox();
141
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
142
+ const extractMock = await getMockedExtractJSON();
143
+ extractMock.mockReturnValue(mockResponse.result);
144
+ const tools = await client.listTools("test-package");
145
+ expect(tools).toEqual(mockTools);
146
+ expect(tools).toHaveLength(2);
147
+ expect(mockSandbox.process.codeRun).toHaveBeenCalledWith(expect.any(String));
148
+ });
149
+ it("should throw error when Sandbox is not initialized", async () => {
150
+ await expect(client.listTools("test-package")).rejects.toThrow("Sandbox not initialized. Call initialize() first.");
151
+ });
152
+ it("should handle execution failure", async () => {
153
+ await client.initialize();
154
+ const mockResponse = {
155
+ exitCode: 1,
156
+ result: "Error: Failed to list tools",
157
+ };
158
+ const mockSandbox = await getMockedSandbox();
159
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
160
+ await expect(client.listTools("test-package")).rejects.toThrow("Failed to list tools");
161
+ });
162
+ it("should handle JSON parsing errors", async () => {
163
+ await client.initialize();
164
+ const mockResponse = {
165
+ exitCode: 0,
166
+ result: "invalid json",
167
+ };
168
+ const mockSandbox = await getMockedSandbox();
169
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
170
+ const extractMock = await getMockedExtractJSON();
171
+ extractMock.mockReturnValue("invalid json");
172
+ await expect(client.listTools("test-package")).rejects.toThrow();
173
+ });
174
+ });
175
+ describe("executeTool - Execute Tool Tests", () => {
176
+ it("should successfully execute tool", async () => {
177
+ await client.initialize();
178
+ const mockResult = {
179
+ result: { message: "Success", data: { value: 42 } },
180
+ isError: false,
181
+ };
182
+ const mockResponse = {
183
+ exitCode: 0,
184
+ result: JSON.stringify(mockResult),
185
+ };
186
+ const mockSandbox = await getMockedSandbox();
187
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
188
+ const extractMock = await getMockedExtractJSON();
189
+ extractMock.mockReturnValue(mockResponse.result);
190
+ const result = await client.executeTool("test-package", "test_tool", {
191
+ param1: "value1",
192
+ });
193
+ expect(result).toEqual(mockResult);
194
+ expect(mockSandbox.process.codeRun).toHaveBeenCalledWith(expect.any(String));
195
+ });
196
+ it("should support passing environment variables", async () => {
197
+ await client.initialize();
198
+ const mockResult = {
199
+ result: { message: "Success with env" },
200
+ isError: false,
201
+ };
202
+ const mockResponse = {
203
+ exitCode: 0,
204
+ result: JSON.stringify(mockResult),
205
+ };
206
+ const mockSandbox = await getMockedSandbox();
207
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
208
+ const extractMock = await getMockedExtractJSON();
209
+ extractMock.mockReturnValue(mockResponse.result);
210
+ const envs = {
211
+ TEST_API_KEY: "real-api-key-123",
212
+ TEST_SECRET: "secret-value",
213
+ };
214
+ const result = await client.executeTool("test-package", "test_tool", { param1: "value1" }, envs);
215
+ expect(result).toEqual(mockResult);
216
+ // Verify generated code contains environment variables
217
+ const generatedCode = mockSandbox.process.codeRun.mock.calls[0][0];
218
+ expect(generatedCode).toContain("TEST_API_KEY");
219
+ });
220
+ it("should handle tool execution errors", async () => {
221
+ await client.initialize();
222
+ const mockResult = {
223
+ result: null,
224
+ isError: true,
225
+ errorMessage: "Tool execution failed: Invalid parameters",
226
+ };
227
+ const mockResponse = {
228
+ exitCode: 0,
229
+ result: JSON.stringify(mockResult),
230
+ };
231
+ const mockSandbox = await getMockedSandbox();
232
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
233
+ const extractMock = await getMockedExtractJSON();
234
+ extractMock.mockReturnValue(mockResponse.result);
235
+ await expect(client.executeTool("test-package", "test_tool", { invalid: "param" })).rejects.toThrow("Tool execution failed: Invalid parameters");
236
+ });
237
+ it("should throw error when Sandbox is not initialized", async () => {
238
+ await expect(client.executeTool("test-package", "test_tool", {})).rejects.toThrow("Sandbox not initialized. Call initialize() first.");
239
+ });
240
+ it("should handle non-zero process exit code", async () => {
241
+ await client.initialize();
242
+ const mockResponse = {
243
+ exitCode: 1,
244
+ result: "Process crashed",
245
+ };
246
+ const mockSandbox = await getMockedSandbox();
247
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
248
+ await expect(client.executeTool("test-package", "test_tool", {})).rejects.toThrow("Failed to execute tool");
249
+ });
250
+ it("should correctly serialize complex argument objects", async () => {
251
+ await client.initialize();
252
+ const complexArgs = {
253
+ stringParam: "test",
254
+ numberParam: 42,
255
+ booleanParam: true,
256
+ arrayParam: [1, 2, 3],
257
+ objectParam: { nested: { value: "deep" } },
258
+ };
259
+ const mockResult = {
260
+ result: { success: true },
261
+ isError: false,
262
+ };
263
+ const mockResponse = {
264
+ exitCode: 0,
265
+ result: JSON.stringify(mockResult),
266
+ };
267
+ const mockSandbox = await getMockedSandbox();
268
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
269
+ const extractMock = await getMockedExtractJSON();
270
+ extractMock.mockReturnValue(mockResponse.result);
271
+ await client.executeTool("test-package", "test_tool", complexArgs);
272
+ const generatedCode = mockSandbox.process.codeRun.mock.calls[0][0];
273
+ expect(generatedCode).toContain(JSON.stringify(complexArgs));
274
+ });
275
+ });
276
+ describe("kill - Cleanup Tests", () => {
277
+ it("should successfully cleanup Sandbox", async () => {
278
+ await client.initialize();
279
+ const mockSandbox = await getMockedSandbox();
280
+ await client.kill();
281
+ expect(mockSandbox.delete).toHaveBeenCalledTimes(1);
282
+ });
283
+ it("should safely return when Sandbox is not initialized", async () => {
284
+ // Call kill directly without initialization
285
+ await expect(client.kill()).resolves.not.toThrow();
286
+ });
287
+ it("should handle deletion failure", async () => {
288
+ await client.initialize();
289
+ const mockSandbox = await getMockedSandbox();
290
+ mockSandbox.delete.mockRejectedValue(new Error("Delete failed"));
291
+ // Should not throw error even if deletion fails
292
+ await expect(client.kill()).resolves.not.toThrow();
293
+ });
294
+ it("should reset Sandbox state after kill", async () => {
295
+ await client.initialize();
296
+ await client.kill();
297
+ // Calling methods that require Sandbox should throw error
298
+ await expect(client.listTools("test-package")).rejects.toThrow("Sandbox not initialized");
299
+ });
300
+ });
301
+ describe("Code Generation Tests", () => {
302
+ it("generated listTools code should contain necessary imports", async () => {
303
+ await client.initialize();
304
+ const mockResponse = {
305
+ exitCode: 0,
306
+ result: JSON.stringify({ toolCount: 0, tools: [] }),
307
+ };
308
+ const mockSandbox = await getMockedSandbox();
309
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
310
+ const extractMock = await getMockedExtractJSON();
311
+ extractMock.mockReturnValue(mockResponse.result);
312
+ await client.listTools("test-package");
313
+ const generatedCode = mockSandbox.process.codeRun.mock.calls[0][0];
314
+ // Verify necessary imports
315
+ expect(generatedCode).toContain("import { Client }");
316
+ expect(generatedCode).toContain("import { StdioClientTransport }");
317
+ expect(generatedCode).toContain("@modelcontextprotocol/sdk");
318
+ });
319
+ it("generated executeTool code should contain tool name and arguments", async () => {
320
+ await client.initialize();
321
+ const mockResponse = {
322
+ exitCode: 0,
323
+ result: JSON.stringify({ result: {}, isError: false }),
324
+ };
325
+ const mockSandbox = await getMockedSandbox();
326
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
327
+ const extractMock = await getMockedExtractJSON();
328
+ extractMock.mockReturnValue(mockResponse.result);
329
+ await client.executeTool("test-package", "my_tool", { key: "value" });
330
+ const generatedCode = mockSandbox.process.codeRun.mock.calls[0][0];
331
+ expect(generatedCode).toContain("my_tool");
332
+ expect(generatedCode).toContain("client.callTool");
333
+ expect(generatedCode).toContain(JSON.stringify({ key: "value" }));
334
+ });
335
+ it("generated code should use pnpx to execute package", async () => {
336
+ await client.initialize();
337
+ const mockResponse = {
338
+ exitCode: 0,
339
+ result: JSON.stringify({ toolCount: 0, tools: [] }),
340
+ };
341
+ const mockSandbox = await getMockedSandbox();
342
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
343
+ const extractMock = await getMockedExtractJSON();
344
+ extractMock.mockReturnValue(mockResponse.result);
345
+ await client.listTools("test-package");
346
+ const generatedCode = mockSandbox.process.codeRun.mock.calls[0][0];
347
+ expect(generatedCode).toContain('command: "pnpx"');
348
+ expect(generatedCode).toContain('"--silent"');
349
+ });
350
+ it("generated code should contain environment variable configuration", async () => {
351
+ await client.initialize();
352
+ const mockResponse = {
353
+ exitCode: 0,
354
+ result: JSON.stringify({ result: {}, isError: false }),
355
+ };
356
+ const mockSandbox = await getMockedSandbox();
357
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
358
+ const extractMock = await getMockedExtractJSON();
359
+ extractMock.mockReturnValue(mockResponse.result);
360
+ await client.executeTool("test-package", "test_tool", {}, { TEST_API_KEY: "real-value" });
361
+ const generatedCode = mockSandbox.process.codeRun.mock.calls[0][0];
362
+ expect(generatedCode).toContain("PNPM_HOME");
363
+ expect(generatedCode).toContain("PNPM_STORE_PATH");
364
+ expect(generatedCode).toContain("TEST_API_KEY");
365
+ });
366
+ it("generated code should correctly handle missing environment variables", async () => {
367
+ await client.initialize();
368
+ const mockResponse = {
369
+ exitCode: 0,
370
+ result: JSON.stringify({ toolCount: 0, tools: [] }),
371
+ };
372
+ const mockSandbox = await getMockedSandbox();
373
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
374
+ const extractMock = await getMockedExtractJSON();
375
+ extractMock.mockReturnValue(mockResponse.result);
376
+ // Do not pass real environment variables
377
+ await client.listTools("test-package");
378
+ const generatedCode = mockSandbox.process.codeRun.mock.calls[0][0];
379
+ // Should use mock_value as default value
380
+ expect(generatedCode).toContain("mock_value");
381
+ });
382
+ });
383
+ describe("Edge Cases and Exception Tests", () => {
384
+ it("should handle empty tool list", async () => {
385
+ await client.initialize();
386
+ const mockResponse = {
387
+ exitCode: 0,
388
+ result: JSON.stringify({ toolCount: 0, tools: [] }),
389
+ };
390
+ const mockSandbox = await getMockedSandbox();
391
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
392
+ const extractMock = await getMockedExtractJSON();
393
+ extractMock.mockReturnValue(mockResponse.result);
394
+ const tools = await client.listTools("empty-package");
395
+ expect(tools).toEqual([]);
396
+ expect(tools).toHaveLength(0);
397
+ });
398
+ it("should handle empty argument object", async () => {
399
+ await client.initialize();
400
+ const mockResult = {
401
+ result: { success: true },
402
+ isError: false,
403
+ };
404
+ const mockResponse = {
405
+ exitCode: 0,
406
+ result: JSON.stringify(mockResult),
407
+ };
408
+ const mockSandbox = await getMockedSandbox();
409
+ mockSandbox.process.codeRun.mockResolvedValue(mockResponse);
410
+ const extractMock = await getMockedExtractJSON();
411
+ extractMock.mockReturnValue(mockResponse.result);
412
+ await expect(client.executeTool("test-package", "test_tool", {})).resolves.toEqual(mockResult);
413
+ });
414
+ it("should handle non-existent package", async () => {
415
+ const { getPackageConfigByKey } = await import("../helper");
416
+ const getPackageMock = getPackageConfigByKey;
417
+ getPackageMock.mockRejectedValueOnce(new Error("Package 'non-existent' not found in packages list."));
418
+ await client.initialize();
419
+ await expect(client.listTools("non-existent")).rejects.toThrow("Package 'non-existent' not found");
420
+ });
421
+ it("should handle network timeout", async () => {
422
+ await client.initialize();
423
+ const mockSandbox = await getMockedSandbox();
424
+ mockSandbox.process.codeRun.mockRejectedValueOnce(new Error("Network timeout"));
425
+ await expect(client.listTools("test-package")).rejects.toThrow("Network timeout");
426
+ });
427
+ });
428
+ describe("Complete Workflow Tests", () => {
429
+ it("should support complete workflow: initialize -> list tools -> execute tool -> cleanup", async () => {
430
+ // 1. Initialize
431
+ await client.initialize();
432
+ const mockSandbox = await getMockedSandbox();
433
+ // 2. List tools
434
+ const mockTools = [
435
+ {
436
+ name: "hello_tool",
437
+ description: "Say hello",
438
+ inputSchema: {
439
+ type: "object",
440
+ properties: { name: { type: "string" } },
441
+ },
442
+ },
443
+ ];
444
+ mockSandbox.process.codeRun.mockResolvedValueOnce({
445
+ exitCode: 0,
446
+ result: JSON.stringify({ toolCount: 1, tools: mockTools }),
447
+ });
448
+ const extractMock = await getMockedExtractJSON();
449
+ extractMock.mockImplementation((str) => str);
450
+ const tools = await client.listTools("test-package");
451
+ expect(tools).toHaveLength(1);
452
+ expect(tools[0].name).toBe("hello_tool");
453
+ // 3. Execute tool
454
+ mockSandbox.process.codeRun.mockResolvedValueOnce({
455
+ exitCode: 0,
456
+ result: JSON.stringify({
457
+ result: { message: "Hello, World!" },
458
+ isError: false,
459
+ }),
460
+ });
461
+ const result = await client.executeTool("test-package", "hello_tool", {
462
+ name: "World",
463
+ });
464
+ expect(result).toHaveProperty("result");
465
+ // 4. Cleanup
466
+ await client.kill();
467
+ expect(mockSandbox.delete).toHaveBeenCalled();
468
+ });
469
+ it("should support multiple tool executions", async () => {
470
+ await client.initialize();
471
+ const mockSandbox = await getMockedSandbox();
472
+ const extractMock = await getMockedExtractJSON();
473
+ extractMock.mockImplementation((str) => str);
474
+ // Execute multiple times
475
+ for (let i = 0; i < 3; i++) {
476
+ mockSandbox.process.codeRun.mockResolvedValueOnce({
477
+ exitCode: 0,
478
+ result: JSON.stringify({
479
+ result: { iteration: i },
480
+ isError: false,
481
+ }),
482
+ });
483
+ const result = await client.executeTool("test-package", "test_tool", {
484
+ index: i,
485
+ });
486
+ expect(result).toHaveProperty("result");
487
+ }
488
+ expect(mockSandbox.process.codeRun).toHaveBeenCalledTimes(3);
489
+ });
490
+ });
491
+ });
package/dist/types.d.ts CHANGED
@@ -6,6 +6,7 @@ export type PackageConfig = z.infer<typeof PackageConfigSchema>;
6
6
  export type CategoryConfig = z.infer<typeof CategoryConfigSchema>;
7
7
  export type PackagesList = z.infer<typeof PackagesListSchema>;
8
8
  export type ToolExecute = z.infer<typeof ToolExecuteSchema>;
9
+ export type MCPSandboxProvider = "LOCAL" | "DAYTONA" | "SANDOCK";
9
10
  export interface Response<T> {
10
11
  success: boolean;
11
12
  code: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolsdk.ai/registry",
3
- "version": "1.0.113",
3
+ "version": "1.0.114",
4
4
  "description": "An Open, Structured, and Standard Registry for MCP Servers and Packages.",
5
5
  "keywords": [],
6
6
  "license": "MIT",
@@ -87,6 +87,7 @@
87
87
  "@cyanheads/pubmed-mcp-server": "1.2.3",
88
88
  "@cyanheads/toolkit-mcp-server": "1.0.1",
89
89
  "@dasheck0/face-generator": "1.0.1",
90
+ "@daytonaio/sdk": "0.109.0",
90
91
  "@debugg-ai/debugg-ai-mcp": "1.0.15",
91
92
  "@delorenj/mcp-server-ticketmaster": "0.2.5",
92
93
  "@deventerprisesoftware/scrapi-mcp": "0.0.3",
@@ -4,7 +4,12 @@
4
4
  "description": "Integrates with Ref.tools documentation search service to provide curated technical documentation access, web search fallback, and URL-to-markdown conversion for efficient developer reference during coding workflows.",
5
5
  "url": "https://github.com/ref-tools/ref-tools-mcp",
6
6
  "runtime": "node",
7
- "license": "unknown",
8
- "env": {},
7
+ "license": "mit",
8
+ "env": {
9
+ "REF_API_KEY": {
10
+ "description": "sign up to get an API key",
11
+ "required": true
12
+ }
13
+ },
9
14
  "name": "Ref Tools MCP"
10
15
  }