@toolsdk.ai/registry 1.0.123 → 1.0.125

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.
Files changed (34) hide show
  1. package/README.md +9 -8
  2. package/dist/api/index.js +4 -0
  3. package/dist/domains/package/package-handler.d.ts +2 -0
  4. package/dist/domains/package/package-handler.js +1 -1
  5. package/dist/domains/package/package-so.js +10 -3
  6. package/dist/domains/package/package-so.test.js +24 -5
  7. package/dist/domains/registry/__tests__/federated-registry-provider.test.d.ts +1 -0
  8. package/dist/domains/registry/__tests__/federated-registry-provider.test.js +125 -0
  9. package/dist/domains/registry/__tests__/local-registry-provider.test.d.ts +1 -0
  10. package/dist/domains/registry/__tests__/local-registry-provider.test.js +80 -0
  11. package/dist/domains/registry/__tests__/official-registry-provider.integration.test.d.ts +1 -0
  12. package/dist/domains/registry/__tests__/official-registry-provider.integration.test.js +70 -0
  13. package/dist/domains/registry/__tests__/registry-utils.test.d.ts +1 -0
  14. package/dist/domains/registry/__tests__/registry-utils.test.js +307 -0
  15. package/dist/domains/registry/index.d.ts +8 -0
  16. package/dist/domains/registry/index.js +9 -0
  17. package/dist/domains/registry/providers/federated-registry-provider.d.ts +25 -0
  18. package/dist/domains/registry/providers/federated-registry-provider.js +49 -0
  19. package/dist/domains/registry/providers/local-registry-provider.d.ts +23 -0
  20. package/dist/domains/registry/providers/local-registry-provider.js +35 -0
  21. package/dist/domains/registry/providers/official-registry-provider.d.ts +34 -0
  22. package/dist/domains/registry/providers/official-registry-provider.js +83 -0
  23. package/dist/domains/registry/registry-factory.d.ts +21 -0
  24. package/dist/domains/registry/registry-factory.js +52 -0
  25. package/dist/domains/registry/registry-schema.d.ts +586 -0
  26. package/dist/domains/registry/registry-schema.js +39 -0
  27. package/dist/domains/registry/registry-types.d.ts +23 -0
  28. package/dist/domains/registry/registry-types.js +1 -0
  29. package/dist/domains/registry/registry-utils.d.ts +14 -0
  30. package/dist/domains/registry/registry-utils.js +50 -0
  31. package/indexes/categories-list.json +1 -0
  32. package/indexes/packages-list.json +11 -0
  33. package/package.json +2 -1
  34. package/packages/code-execution/sandock-mcp.json +15 -0
package/README.md CHANGED
@@ -6,10 +6,10 @@
6
6
 
7
7
  [![Product Hunt](https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=997428&theme=light&period=daily)](https://www.producthunt.com/products/toolsdk-ai)
8
8
 
9
- ![How many MCP Servers in ToolSDK MCP Registry](https://img.shields.io/badge/MCP_Servers-4108-blue)
9
+ ![How many MCP Servers in ToolSDK MCP Registry](https://img.shields.io/badge/MCP_Servers-4109-blue)
10
10
  ![toolsdk-mcp-registry License](https://img.shields.io/badge/LICENSE-MIT-ff69b4)
11
11
 
12
- 🚀 **Open-source**, **production-ready**, and **developer-friendly** registry for 4108+ Model Context Protocol (MCP) servers, plugins, and AI agent tools.
12
+ 🚀 **Open-source**, **production-ready**, and **developer-friendly** registry for 4109+ Model Context Protocol (MCP) servers, plugins, and AI agent tools.
13
13
 
14
14
  Perfect for **AI automation**, **chatbot development**, **LLM integrations**, and **enterprise AI deployments**.
15
15
 
@@ -24,7 +24,7 @@ Perfect for **AI automation**, **chatbot development**, **LLM integrations**, an
24
24
  ### 🎯 Key Features
25
25
 
26
26
  - 🔐 **Private & Self-Hosted** - Deploy your own secure MCP registry with Docker in minutes
27
- - 🤖 **4108+ AI Tools** - Largest curated collection of MCP servers for Claude, LLMs, and AI agents
27
+ - 🤖 **4109+ AI Tools** - Largest curated collection of MCP servers for Claude, LLMs, and AI agents
28
28
  - ⚡ **Remote Execution** - Run MCP tools in isolated sandbox environments via REST API
29
29
  - 🔍 **Powerful Search** - Fast, full-text search powered by Meilisearch
30
30
  - 📦 **NPM Integration** - Use as a TypeScript/Node.js SDK in your projects
@@ -46,7 +46,7 @@ Perfect for **AI automation**, **chatbot development**, **LLM integrations**, an
46
46
 
47
47
  This open-source registry provides:
48
48
 
49
- - 📚 **Structured Database** - 4108+ validated MCP servers with metadata
49
+ - 📚 **Structured Database** - 4109+ validated MCP servers with metadata
50
50
  - 🔗 **Multiple Formats** - JSON, npm package, and generated documentation
51
51
  - 🌐 **REST API** - Query and execute tools remotely
52
52
  - 📖 **Auto-Generated Docs** - Always up-to-date README and API documentation
@@ -134,7 +134,7 @@ That's it! Your self-hosted MCP registry is now running with:
134
134
 
135
135
  - 🌐 **Web Interface**: http://localhost:3003
136
136
  - 📚 **Swagger API Docs**: http://localhost:3003/swagger
137
- - 🔍 **Search & Execute** 4108+ MCP tools remotely
137
+ - 🔍 **Search & Execute** 4109+ MCP tools remotely
138
138
  - 🤖 **Integrate** with your AI agents, chatbots, and LLM applications
139
139
 
140
140
  #### 💻 Remote Tool Execution Example
@@ -241,7 +241,7 @@ Help grow the world's largest open-source MCP registry! Share your AI tools, plu
241
241
 
242
242
  - [Fork this repository](https://github.com/toolsdk-ai/toolsdk-mcp-registry/fork)
243
243
  - Create `your-mcp-server.json` in [packages/uncategorized](./packages/uncategorized)
244
- - Submit a PR and join 4108+ MCP servers!
244
+ - Submit a PR and join 4109+ MCP servers!
245
245
 
246
246
  **3. Get Discovered**
247
247
 
@@ -259,9 +259,9 @@ Your MCP server will be:
259
259
 
260
260
  ## 📋 MCP Servers Directory
261
261
 
262
- **4108+ AI Agent Tools, LLM Integrations & Automation Servers**
262
+ **4109+ AI Agent Tools, LLM Integrations & Automation Servers**
263
263
 
264
- - ✅ **Validated & Tested** (709) - Production-ready MCP servers
264
+ - ✅ **Validated & Tested** (710) - Production-ready MCP servers
265
265
  - ⚙️ **Community Contributed** (3399) - Requires configuration
266
266
 
267
267
  Browse by category: Developer Tools, AI Agents, Databases, Cloud Platforms, APIs, and more!
@@ -970,6 +970,7 @@ Run code securely, perfect for coding agents and AI-driven programming tasks.
970
970
  - [✅ node-code-sandbox-mcp](https://github.com/ssdeanx/node-code-sandbox-mcp): Provides a secure Docker-based environment for executing Node.js code with npm dependencies, shell commands, and file operations while maintaining proper isolation for testing and web development prototyping. (7 tools) (node)
971
971
  - [✅ nrepl-mcp-server](https://github.com/johancodinha/nrepl-mcp-server): Integrates with Clojure nREPL instances to enable code evaluation, namespace listing, and public var inspection for AI-assisted Clojure development. (3 tools) (node)
972
972
  - [✅ python-local](https://github.com/alec2435/python_mcp): Provides an interactive Python REPL environment for executing code within conversations, maintaining separate state for each session and supporting both expressions and statements. (1 tools) (python)
973
+ - [✅ sandock-mcp](https://github.com/sandock-ai/sandock): A Model Context Protocol server for running code in a secure sandbox by Sandock. (1 tools) (node)
973
974
  - [❌ @pydantic/mcp-run-python](https://github.com/pydantic/pydantic-ai/tree/HEAD/mcp-run-python): Provides a browser-compatible Python execution environment with package management capabilities for running code snippets safely without requiring a backend Python installation. (node)
974
975
  - [❌ @yepcode/mcp-server](https://github.com/yepcode/mcp-server-js): Enables secure execution of LLM-generated scripts and processes in isolated environments with environment variable management for teams needing to run code directly from AI assistants. (node)
975
976
  - [❌ ai-meta-tool-creator](https://github.com/alxspiker/ai-meta-mcp-server): Enables AI to dynamically create, execute, and manage custom tools in a secure sandbox environment for JavaScript, Python, or shell code with persistent storage capabilities. (node)
package/dist/api/index.js CHANGED
@@ -4,11 +4,15 @@ import { serve } from "@hono/node-server";
4
4
  import { swaggerUI } from "@hono/swagger-ui";
5
5
  import { OpenAPIHono } from "@hono/zod-openapi";
6
6
  import { configRoutes } from "../domains/config/config-route";
7
+ import { repository } from "../domains/package/package-handler";
7
8
  import { packageRoutes } from "../domains/package/package-route";
9
+ import { initRegistryFactory } from "../domains/registry/registry-factory";
8
10
  import { searchRoutes } from "../domains/search/search-route";
9
11
  import { SearchSO } from "../domains/search/search-so";
10
12
  import { getServerPort, isSearchEnabled } from "../shared/config/environment";
11
13
  import { getDirname } from "../shared/utils";
14
+ // Initialize Registry Factory with the local repository
15
+ initRegistryFactory(repository);
12
16
  const initializeSearchService = async () => {
13
17
  try {
14
18
  await SearchSO.getInstance();
@@ -1,4 +1,6 @@
1
1
  import type { MCPSandboxProvider } from "../sandbox/sandbox-types";
2
+ import { PackageRepository } from "./package-repository";
3
+ export declare const repository: PackageRepository;
2
4
  export declare const packageHandler: {
3
5
  getPackageDetail: (packageName: string, sandboxProvider?: MCPSandboxProvider) => Promise<{
4
6
  success: boolean;
@@ -6,7 +6,7 @@ import { PackageRepository } from "./package-repository";
6
6
  import { PackageSO } from "./package-so";
7
7
  const __dirname = getDirname(import.meta.url);
8
8
  const packagesDir = path.join(__dirname, "../../../packages");
9
- const repository = new PackageRepository(packagesDir);
9
+ export const repository = new PackageRepository(packagesDir);
10
10
  export const packageHandler = {
11
11
  getPackageDetail: async (packageName, sandboxProvider) => {
12
12
  try {
@@ -1,5 +1,6 @@
1
+ import { getRegistryProvider } from "../registry/registry-factory";
1
2
  export class PackageSO {
2
- constructor(_packageName, _config, _packageInfo, _repository, _executor) {
3
+ constructor(_packageName, _config, _packageInfo, _executor) {
3
4
  this._packageName = _packageName;
4
5
  this._config = _config;
5
6
  this._packageInfo = _packageInfo;
@@ -24,10 +25,16 @@ export class PackageSO {
24
25
  return this._config;
25
26
  }
26
27
  static async init(packageName, repository, executor) {
27
- const config = repository.getPackageConfig(packageName);
28
+ // Use FederatedRegistryProvider, which checks for local packages first and falls back to the official registry if not found locally.
29
+ const provider = getRegistryProvider("FEDERATED");
30
+ const config = await provider.getPackageConfig(packageName);
31
+ if (!config) {
32
+ throw new Error(`Package '${packageName}' not found`);
33
+ }
34
+ // Get package metadata (category, validated) from local repository index if available.
28
35
  const allPackages = repository.getAllPackages();
29
36
  const packageInfo = allPackages[packageName] || {};
30
- return new PackageSO(packageName, config, packageInfo, repository, executor);
37
+ return new PackageSO(packageName, config, packageInfo, executor);
31
38
  }
32
39
  async getTools() {
33
40
  return await this._executor.listTools(this.packageName);
@@ -1,15 +1,20 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { initRegistryFactory, resetRegistryFactory } from "../registry/registry-factory";
2
3
  import { PackageSO } from "./package-so";
3
4
  describe("PackageSO", () => {
4
5
  let mockRepository;
5
6
  let mockExecutor;
6
7
  beforeEach(() => {
8
+ // Reset factory before each test
9
+ resetRegistryFactory();
7
10
  // Mock PackageRepository
8
11
  mockRepository = {
9
12
  getPackageConfig: vi.fn(),
10
13
  getAllPackages: vi.fn(),
11
14
  exists: vi.fn(),
12
15
  };
16
+ // Initialize Registry Factory with mock repository
17
+ initRegistryFactory(mockRepository);
13
18
  // Mock ToolExecutor
14
19
  mockExecutor = {
15
20
  listTools: vi.fn(),
@@ -32,6 +37,7 @@ describe("PackageSO", () => {
32
37
  category: "filesystem",
33
38
  validated: true,
34
39
  };
40
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
35
41
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
36
42
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({
37
43
  [packageName]: mockPackageInfo,
@@ -44,6 +50,7 @@ describe("PackageSO", () => {
44
50
  expect(packageSO.description).toBe("A server for filesystem operations");
45
51
  expect(packageSO.category).toBe("filesystem");
46
52
  expect(packageSO.validated).toBe(true);
53
+ expect(mockRepository.exists).toHaveBeenCalledWith(packageName);
47
54
  expect(mockRepository.getPackageConfig).toHaveBeenCalledWith(packageName);
48
55
  expect(mockRepository.getAllPackages).toHaveBeenCalled();
49
56
  });
@@ -57,6 +64,7 @@ describe("PackageSO", () => {
57
64
  name: "New Package",
58
65
  description: "A new package",
59
66
  };
67
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
60
68
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
61
69
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({});
62
70
  // Act
@@ -77,6 +85,7 @@ describe("PackageSO", () => {
77
85
  name: "Test Package",
78
86
  description: null,
79
87
  };
88
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
80
89
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
81
90
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({});
82
91
  // Act
@@ -119,6 +128,7 @@ describe("PackageSO", () => {
119
128
  },
120
129
  },
121
130
  ];
131
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
122
132
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
123
133
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({});
124
134
  vi.spyOn(mockExecutor, "listTools").mockResolvedValue(mockTools);
@@ -142,6 +152,7 @@ describe("PackageSO", () => {
142
152
  description: "Test description",
143
153
  };
144
154
  const errorMessage = "Failed to list tools";
155
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
145
156
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
146
157
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({});
147
158
  vi.spyOn(mockExecutor, "listTools").mockRejectedValue(new Error(errorMessage));
@@ -165,6 +176,7 @@ describe("PackageSO", () => {
165
176
  name: "Test Package",
166
177
  description: "Test description",
167
178
  };
179
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
168
180
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
169
181
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({});
170
182
  vi.spyOn(mockExecutor, "executeTool").mockResolvedValue(mockResult);
@@ -194,6 +206,7 @@ describe("PackageSO", () => {
194
206
  name: "Test Package",
195
207
  description: "Test description",
196
208
  };
209
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
197
210
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
198
211
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({});
199
212
  vi.spyOn(mockExecutor, "executeTool").mockResolvedValue(mockResult);
@@ -222,6 +235,7 @@ describe("PackageSO", () => {
222
235
  name: "Test Package",
223
236
  description: "Test description",
224
237
  };
238
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
225
239
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
226
240
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({});
227
241
  vi.spyOn(mockExecutor, "executeTool").mockRejectedValue(new Error(errorMessage));
@@ -256,6 +270,7 @@ describe("PackageSO", () => {
256
270
  },
257
271
  },
258
272
  ];
273
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
259
274
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
260
275
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({
261
276
  [packageName]: mockPackageInfo,
@@ -266,11 +281,11 @@ describe("PackageSO", () => {
266
281
  const detail = await packageSO.getDetailWithTools();
267
282
  // Assert
268
283
  expect(detail).toEqual({
284
+ type: "mcp-server",
285
+ runtime: "node",
269
286
  name: "Test Package",
270
287
  packageName: "@test/package",
271
288
  description: "A test package for demonstration",
272
- category: "testing",
273
- validated: true,
274
289
  tools: mockTools,
275
290
  });
276
291
  });
@@ -284,6 +299,7 @@ describe("PackageSO", () => {
284
299
  name: "Test Package",
285
300
  description: "Test description",
286
301
  };
302
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
287
303
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
288
304
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({});
289
305
  vi.spyOn(mockExecutor, "listTools").mockRejectedValue(new Error("Failed to get tools"));
@@ -309,6 +325,7 @@ describe("PackageSO", () => {
309
325
  description: "A minimal package",
310
326
  };
311
327
  const mockTools = [];
328
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
312
329
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
313
330
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({});
314
331
  vi.spyOn(mockExecutor, "listTools").mockResolvedValue(mockTools);
@@ -317,12 +334,12 @@ describe("PackageSO", () => {
317
334
  const detail = await packageSO.getDetailWithTools();
318
335
  // Assert
319
336
  expect(detail).toEqual({
337
+ type: "mcp-server",
338
+ runtime: "node",
320
339
  name: "Minimal Package",
321
340
  packageName: "@test/minimal-package",
322
341
  description: "A minimal package",
323
- category: undefined,
324
- validated: undefined,
325
- tools: mockTools,
342
+ tools: [],
326
343
  });
327
344
  });
328
345
  });
@@ -342,6 +359,7 @@ describe("PackageSO", () => {
342
359
  category: "test-category",
343
360
  validated: false,
344
361
  };
362
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
345
363
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
346
364
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({
347
365
  [packageName]: mockPackageInfo,
@@ -366,6 +384,7 @@ describe("PackageSO", () => {
366
384
  name: "Test Package",
367
385
  description: "Test description",
368
386
  };
387
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
369
388
  vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
370
389
  vi.spyOn(mockRepository, "getAllPackages").mockReturnValue({});
371
390
  // Act
@@ -0,0 +1,125 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { FederatedRegistryProvider } from "../providers/federated-registry-provider";
3
+ describe("FederatedRegistryProvider", () => {
4
+ let mockLocalProvider;
5
+ let mockOfficialProvider;
6
+ let provider;
7
+ beforeEach(() => {
8
+ // Mock LocalRegistryProvider
9
+ mockLocalProvider = {
10
+ getPackageConfig: vi.fn(),
11
+ exists: vi.fn(),
12
+ };
13
+ // Mock OfficialRegistryProvider
14
+ mockOfficialProvider = {
15
+ getPackageConfig: vi.fn(),
16
+ exists: vi.fn(),
17
+ search: vi.fn(),
18
+ };
19
+ provider = new FederatedRegistryProvider(mockLocalProvider, mockOfficialProvider);
20
+ });
21
+ describe("getPackageConfig", () => {
22
+ it("should return local config when package exists locally", async () => {
23
+ // Arrange
24
+ const packageName = "@modelcontextprotocol/server-filesystem";
25
+ const mockConfig = {
26
+ type: "mcp-server",
27
+ runtime: "node",
28
+ packageName,
29
+ name: "Filesystem Server",
30
+ description: "A server for filesystem operations",
31
+ };
32
+ vi.spyOn(mockLocalProvider, "getPackageConfig").mockResolvedValue(mockConfig);
33
+ // Act
34
+ const result = await provider.getPackageConfig(packageName);
35
+ // Assert
36
+ expect(result).toEqual(mockConfig);
37
+ expect(mockLocalProvider.getPackageConfig).toHaveBeenCalledWith(packageName);
38
+ expect(mockOfficialProvider.getPackageConfig).not.toHaveBeenCalled();
39
+ });
40
+ it("should query official provider when local returns null", async () => {
41
+ // Arrange
42
+ const packageName = "@toolsdk.ai/tavily-mcp";
43
+ const officialConfig = {
44
+ type: "mcp-server",
45
+ runtime: "node",
46
+ packageName,
47
+ name: "Tavily MCP Server",
48
+ description: "MCP server for Tavily search",
49
+ };
50
+ vi.spyOn(mockLocalProvider, "getPackageConfig").mockResolvedValue(null);
51
+ vi.spyOn(mockOfficialProvider, "getPackageConfig").mockResolvedValue(officialConfig);
52
+ // Act
53
+ const result = await provider.getPackageConfig(packageName);
54
+ // Assert
55
+ expect(result).toEqual(officialConfig);
56
+ expect(mockLocalProvider.getPackageConfig).toHaveBeenCalledWith(packageName);
57
+ expect(mockOfficialProvider.getPackageConfig).toHaveBeenCalledWith(packageName);
58
+ });
59
+ it("should return null when both providers return null", async () => {
60
+ // Arrange
61
+ const packageName = "non-existent-package";
62
+ vi.spyOn(mockLocalProvider, "getPackageConfig").mockResolvedValue(null);
63
+ vi.spyOn(mockOfficialProvider, "getPackageConfig").mockResolvedValue(null);
64
+ // Act
65
+ const result = await provider.getPackageConfig(packageName);
66
+ // Assert
67
+ expect(result).toBeNull();
68
+ });
69
+ it("should return null when official provider throws error", async () => {
70
+ // Arrange
71
+ const packageName = "@toolsdk.ai/tavily-mcp";
72
+ vi.spyOn(mockLocalProvider, "getPackageConfig").mockResolvedValue(null);
73
+ vi.spyOn(mockOfficialProvider, "getPackageConfig").mockRejectedValue(new Error("Network error"));
74
+ // Act
75
+ const result = await provider.getPackageConfig(packageName);
76
+ // Assert
77
+ expect(result).toBeNull();
78
+ });
79
+ });
80
+ describe("exists", () => {
81
+ it("should return true when package exists locally", async () => {
82
+ // Arrange
83
+ const packageName = "@modelcontextprotocol/server-filesystem";
84
+ vi.spyOn(mockLocalProvider, "exists").mockResolvedValue(true);
85
+ // Act
86
+ const result = await provider.exists(packageName);
87
+ // Assert
88
+ expect(result).toBe(true);
89
+ expect(mockLocalProvider.exists).toHaveBeenCalledWith(packageName);
90
+ expect(mockOfficialProvider.exists).not.toHaveBeenCalled();
91
+ });
92
+ it("should check official provider when local returns false", async () => {
93
+ // Arrange
94
+ const packageName = "@toolsdk.ai/tavily-mcp";
95
+ vi.spyOn(mockLocalProvider, "exists").mockResolvedValue(false);
96
+ vi.spyOn(mockOfficialProvider, "exists").mockResolvedValue(true);
97
+ // Act
98
+ const result = await provider.exists(packageName);
99
+ // Assert
100
+ expect(result).toBe(true);
101
+ expect(mockLocalProvider.exists).toHaveBeenCalledWith(packageName);
102
+ expect(mockOfficialProvider.exists).toHaveBeenCalledWith(packageName);
103
+ });
104
+ it("should return false when both providers return false", async () => {
105
+ // Arrange
106
+ const packageName = "non-existent-package";
107
+ vi.spyOn(mockLocalProvider, "exists").mockResolvedValue(false);
108
+ vi.spyOn(mockOfficialProvider, "exists").mockResolvedValue(false);
109
+ // Act
110
+ const result = await provider.exists(packageName);
111
+ // Assert
112
+ expect(result).toBe(false);
113
+ });
114
+ it("should return false when official provider throws error", async () => {
115
+ // Arrange
116
+ const packageName = "@toolsdk.ai/tavily-mcp";
117
+ vi.spyOn(mockLocalProvider, "exists").mockResolvedValue(false);
118
+ vi.spyOn(mockOfficialProvider, "exists").mockRejectedValue(new Error("Network error"));
119
+ // Act
120
+ const result = await provider.exists(packageName);
121
+ // Assert
122
+ expect(result).toBe(false);
123
+ });
124
+ });
125
+ });
@@ -0,0 +1,80 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { LocalRegistryProvider } from "../providers/local-registry-provider";
3
+ describe("LocalRegistryProvider", () => {
4
+ let mockRepository;
5
+ let provider;
6
+ beforeEach(() => {
7
+ // Mock PackageRepository
8
+ mockRepository = {
9
+ getPackageConfig: vi.fn(),
10
+ getAllPackages: vi.fn(),
11
+ exists: vi.fn(),
12
+ };
13
+ provider = new LocalRegistryProvider(mockRepository);
14
+ });
15
+ describe("getPackageConfig", () => {
16
+ it("should return package config when package exists", async () => {
17
+ // Arrange
18
+ const packageName = "@modelcontextprotocol/server-filesystem";
19
+ const mockConfig = {
20
+ type: "mcp-server",
21
+ runtime: "node",
22
+ packageName,
23
+ name: "Filesystem Server",
24
+ description: "A server for filesystem operations",
25
+ };
26
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
27
+ vi.spyOn(mockRepository, "getPackageConfig").mockReturnValue(mockConfig);
28
+ // Act
29
+ const result = await provider.getPackageConfig(packageName);
30
+ // Assert
31
+ expect(result).toEqual(mockConfig);
32
+ expect(mockRepository.exists).toHaveBeenCalledWith(packageName);
33
+ expect(mockRepository.getPackageConfig).toHaveBeenCalledWith(packageName);
34
+ });
35
+ it("should return null when package does not exist", async () => {
36
+ // Arrange
37
+ const packageName = "non-existent-package";
38
+ vi.spyOn(mockRepository, "exists").mockReturnValue(false);
39
+ // Act
40
+ const result = await provider.getPackageConfig(packageName);
41
+ // Assert
42
+ expect(result).toBeNull();
43
+ expect(mockRepository.exists).toHaveBeenCalledWith(packageName);
44
+ expect(mockRepository.getPackageConfig).not.toHaveBeenCalled();
45
+ });
46
+ it("should return null when getPackageConfig throws error", async () => {
47
+ // Arrange
48
+ const packageName = "@modelcontextprotocol/server-filesystem";
49
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
50
+ vi.spyOn(mockRepository, "getPackageConfig").mockImplementation(() => {
51
+ throw new Error("File read error");
52
+ });
53
+ // Act
54
+ const result = await provider.getPackageConfig(packageName);
55
+ // Assert
56
+ expect(result).toBeNull();
57
+ });
58
+ });
59
+ describe("exists", () => {
60
+ it("should return true when package exists", async () => {
61
+ // Arrange
62
+ const packageName = "@modelcontextprotocol/server-filesystem";
63
+ vi.spyOn(mockRepository, "exists").mockReturnValue(true);
64
+ // Act
65
+ const result = await provider.exists(packageName);
66
+ // Assert
67
+ expect(result).toBe(true);
68
+ expect(mockRepository.exists).toHaveBeenCalledWith(packageName);
69
+ });
70
+ it("should return false when package does not exist", async () => {
71
+ // Arrange
72
+ const packageName = "non-existent-package";
73
+ vi.spyOn(mockRepository, "exists").mockReturnValue(false);
74
+ // Act
75
+ const result = await provider.exists(packageName);
76
+ // Assert
77
+ expect(result).toBe(false);
78
+ });
79
+ });
80
+ });
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { OfficialRegistryProvider } from "../providers/official-registry-provider";
3
+ /**
4
+ * Integration tests for OfficialRegistryProvider
5
+ * These tests call the real official API
6
+ */
7
+ describe("OfficialRegistryProvider - Integration", () => {
8
+ const provider = new OfficialRegistryProvider();
9
+ describe("search", () => {
10
+ it("should search and return results from official API", async () => {
11
+ // Act - search for tavily
12
+ const results = await provider.search("tavily");
13
+ // Assert
14
+ expect(results).toBeDefined();
15
+ expect(Array.isArray(results)).toBe(true);
16
+ // Log results for debugging
17
+ console.log(`Found ${results.length} results for 'tavily'`);
18
+ if (results.length > 0) {
19
+ console.log("First result:", JSON.stringify(results[0], null, 2));
20
+ }
21
+ }, 10000); // 10s timeout for network call
22
+ it("should return empty array for non-existent package", async () => {
23
+ // Act
24
+ const results = await provider.search("non-existent-package-xyz-123");
25
+ // Assert
26
+ expect(results).toBeDefined();
27
+ expect(Array.isArray(results)).toBe(true);
28
+ expect(results.length).toBe(0);
29
+ }, 10000);
30
+ });
31
+ describe("getPackageConfig", () => {
32
+ it("should get config for tavily-mcp package", async () => {
33
+ // Act
34
+ const config = await provider.getPackageConfig("tavily");
35
+ // Assert
36
+ if (config) {
37
+ console.log("Tavily package config:", JSON.stringify(config, null, 2));
38
+ expect(config.type).toBe("mcp-server");
39
+ expect(config.runtime).toBe("node");
40
+ expect(config.packageName).toBeDefined();
41
+ expect(config.name).toBeDefined();
42
+ }
43
+ else {
44
+ console.log("No tavily package found");
45
+ }
46
+ }, 10000);
47
+ it("should return null for non-existent package", async () => {
48
+ // Act
49
+ const config = await provider.getPackageConfig("non-existent-package-xyz-123");
50
+ // Assert
51
+ expect(config).toBeNull();
52
+ }, 10000);
53
+ });
54
+ describe("exists", () => {
55
+ it("should return true for existing package", async () => {
56
+ // Act
57
+ const exists = await provider.exists("tavily");
58
+ // Assert
59
+ console.log(`Tavily exists: ${exists}`);
60
+ // We expect it to exist, but don't fail the test if the API changes
61
+ expect(typeof exists).toBe("boolean");
62
+ }, 10000);
63
+ it("should return false for non-existent package", async () => {
64
+ // Act
65
+ const exists = await provider.exists("non-existent-package-xyz-123");
66
+ // Assert
67
+ expect(exists).toBe(false);
68
+ }, 10000);
69
+ });
70
+ });