@toolsdk.ai/registry 1.0.124 → 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.
- package/dist/api/index.js +4 -0
- package/dist/domains/package/package-handler.d.ts +2 -0
- package/dist/domains/package/package-handler.js +1 -1
- package/dist/domains/package/package-so.js +10 -3
- package/dist/domains/package/package-so.test.js +24 -5
- package/dist/domains/registry/__tests__/federated-registry-provider.test.d.ts +1 -0
- package/dist/domains/registry/__tests__/federated-registry-provider.test.js +125 -0
- package/dist/domains/registry/__tests__/local-registry-provider.test.d.ts +1 -0
- package/dist/domains/registry/__tests__/local-registry-provider.test.js +80 -0
- package/dist/domains/registry/__tests__/official-registry-provider.integration.test.d.ts +1 -0
- package/dist/domains/registry/__tests__/official-registry-provider.integration.test.js +70 -0
- package/dist/domains/registry/__tests__/registry-utils.test.d.ts +1 -0
- package/dist/domains/registry/__tests__/registry-utils.test.js +307 -0
- package/dist/domains/registry/index.d.ts +8 -0
- package/dist/domains/registry/index.js +9 -0
- package/dist/domains/registry/providers/federated-registry-provider.d.ts +25 -0
- package/dist/domains/registry/providers/federated-registry-provider.js +49 -0
- package/dist/domains/registry/providers/local-registry-provider.d.ts +23 -0
- package/dist/domains/registry/providers/local-registry-provider.js +35 -0
- package/dist/domains/registry/providers/official-registry-provider.d.ts +34 -0
- package/dist/domains/registry/providers/official-registry-provider.js +83 -0
- package/dist/domains/registry/registry-factory.d.ts +21 -0
- package/dist/domains/registry/registry-factory.js +52 -0
- package/dist/domains/registry/registry-schema.d.ts +586 -0
- package/dist/domains/registry/registry-schema.js +39 -0
- package/dist/domains/registry/registry-types.d.ts +23 -0
- package/dist/domains/registry/registry-types.js +1 -0
- package/dist/domains/registry/registry-utils.d.ts +14 -0
- package/dist/domains/registry/registry-utils.js +50 -0
- package/package.json +1 -1
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { transformAndFilterServers, transformServer } from "../registry-utils";
|
|
3
|
+
describe("registry-utils", () => {
|
|
4
|
+
describe("transformServer", () => {
|
|
5
|
+
it("should transform npm+stdio package correctly", () => {
|
|
6
|
+
// Arrange
|
|
7
|
+
const officialServer = {
|
|
8
|
+
name: "io.github.Seey215/tavily-mcp",
|
|
9
|
+
title: "Tavily MCP Server",
|
|
10
|
+
description: "MCP server for advanced web search using Tavily API.",
|
|
11
|
+
repository: {
|
|
12
|
+
url: "https://github.com/Seey215/tavily-mcp",
|
|
13
|
+
},
|
|
14
|
+
version: "0.2.9",
|
|
15
|
+
packages: [
|
|
16
|
+
{
|
|
17
|
+
registryType: "npm",
|
|
18
|
+
identifier: "@toolsdk.ai/tavily-mcp",
|
|
19
|
+
version: "0.2.9",
|
|
20
|
+
transport: {
|
|
21
|
+
type: "stdio",
|
|
22
|
+
},
|
|
23
|
+
environmentVariables: [
|
|
24
|
+
{
|
|
25
|
+
name: "TAVILY_API_KEY",
|
|
26
|
+
description: "Your TAVILY_API_KEY",
|
|
27
|
+
isRequired: true,
|
|
28
|
+
isSecret: true,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
// Act
|
|
35
|
+
const result = transformServer(officialServer);
|
|
36
|
+
// Assert
|
|
37
|
+
expect(result).not.toBeNull();
|
|
38
|
+
expect(result).toMatchObject({
|
|
39
|
+
type: "mcp-server",
|
|
40
|
+
runtime: "node",
|
|
41
|
+
packageName: "@toolsdk.ai/tavily-mcp",
|
|
42
|
+
packageVersion: "0.2.9",
|
|
43
|
+
name: "Tavily MCP Server",
|
|
44
|
+
description: "MCP server for advanced web search using Tavily API.",
|
|
45
|
+
url: "https://github.com/Seey215/tavily-mcp",
|
|
46
|
+
env: {
|
|
47
|
+
TAVILY_API_KEY: {
|
|
48
|
+
description: "Your TAVILY_API_KEY",
|
|
49
|
+
required: true,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
it("should use title as name if available", () => {
|
|
55
|
+
// Arrange
|
|
56
|
+
const officialServer = {
|
|
57
|
+
name: "io.github.test/package",
|
|
58
|
+
title: "Test Package Title",
|
|
59
|
+
packages: [
|
|
60
|
+
{
|
|
61
|
+
registryType: "npm",
|
|
62
|
+
identifier: "@test/package",
|
|
63
|
+
transport: {
|
|
64
|
+
type: "stdio",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
// Act
|
|
70
|
+
const result = transformServer(officialServer);
|
|
71
|
+
// Assert
|
|
72
|
+
expect(result === null || result === void 0 ? void 0 : result.name).toBe("Test Package Title");
|
|
73
|
+
});
|
|
74
|
+
it("should use name if title is not available", () => {
|
|
75
|
+
// Arrange
|
|
76
|
+
const officialServer = {
|
|
77
|
+
name: "io.github.test/package",
|
|
78
|
+
packages: [
|
|
79
|
+
{
|
|
80
|
+
registryType: "npm",
|
|
81
|
+
identifier: "@test/package",
|
|
82
|
+
transport: {
|
|
83
|
+
type: "stdio",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
// Act
|
|
89
|
+
const result = transformServer(officialServer);
|
|
90
|
+
// Assert
|
|
91
|
+
expect(result === null || result === void 0 ? void 0 : result.name).toBe("io.github.test/package");
|
|
92
|
+
});
|
|
93
|
+
it("should return null for non-npm packages", () => {
|
|
94
|
+
// Arrange
|
|
95
|
+
const officialServer = {
|
|
96
|
+
name: "io.github.test/docker-package",
|
|
97
|
+
packages: [
|
|
98
|
+
{
|
|
99
|
+
registryType: "docker",
|
|
100
|
+
identifier: "test/package",
|
|
101
|
+
transport: {
|
|
102
|
+
type: "stdio",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
// Act
|
|
108
|
+
const result = transformServer(officialServer);
|
|
109
|
+
// Assert
|
|
110
|
+
expect(result).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
it("should return null for non-stdio transport", () => {
|
|
113
|
+
// Arrange
|
|
114
|
+
const officialServer = {
|
|
115
|
+
name: "io.github.test/sse-package",
|
|
116
|
+
packages: [
|
|
117
|
+
{
|
|
118
|
+
registryType: "npm",
|
|
119
|
+
identifier: "@test/package",
|
|
120
|
+
transport: {
|
|
121
|
+
type: "sse",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
// Act
|
|
127
|
+
const result = transformServer(officialServer);
|
|
128
|
+
// Assert
|
|
129
|
+
expect(result).toBeNull();
|
|
130
|
+
});
|
|
131
|
+
it("should select first npm+stdio package when multiple packages exist", () => {
|
|
132
|
+
// Arrange
|
|
133
|
+
const officialServer = {
|
|
134
|
+
name: "io.github.test/multi-package",
|
|
135
|
+
packages: [
|
|
136
|
+
{
|
|
137
|
+
registryType: "docker",
|
|
138
|
+
identifier: "test/docker",
|
|
139
|
+
transport: {
|
|
140
|
+
type: "stdio",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
registryType: "npm",
|
|
145
|
+
identifier: "@test/first-npm",
|
|
146
|
+
transport: {
|
|
147
|
+
type: "stdio",
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
registryType: "npm",
|
|
152
|
+
identifier: "@test/second-npm",
|
|
153
|
+
transport: {
|
|
154
|
+
type: "stdio",
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
// Act
|
|
160
|
+
const result = transformServer(officialServer);
|
|
161
|
+
// Assert
|
|
162
|
+
expect(result).not.toBeNull();
|
|
163
|
+
expect(result === null || result === void 0 ? void 0 : result.packageName).toBe("@test/first-npm");
|
|
164
|
+
});
|
|
165
|
+
it("should handle empty environment variables", () => {
|
|
166
|
+
// Arrange
|
|
167
|
+
const officialServer = {
|
|
168
|
+
name: "io.github.test/no-env",
|
|
169
|
+
packages: [
|
|
170
|
+
{
|
|
171
|
+
registryType: "npm",
|
|
172
|
+
identifier: "@test/package",
|
|
173
|
+
transport: {
|
|
174
|
+
type: "stdio",
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
// Act
|
|
180
|
+
const result = transformServer(officialServer);
|
|
181
|
+
// Assert
|
|
182
|
+
expect(result).not.toBeNull();
|
|
183
|
+
expect(result === null || result === void 0 ? void 0 : result.env).toBeUndefined();
|
|
184
|
+
});
|
|
185
|
+
it("should handle optional environment variable fields", () => {
|
|
186
|
+
// Arrange
|
|
187
|
+
const officialServer = {
|
|
188
|
+
name: "io.github.test/package",
|
|
189
|
+
packages: [
|
|
190
|
+
{
|
|
191
|
+
registryType: "npm",
|
|
192
|
+
identifier: "@test/package",
|
|
193
|
+
transport: {
|
|
194
|
+
type: "stdio",
|
|
195
|
+
},
|
|
196
|
+
environmentVariables: [
|
|
197
|
+
{
|
|
198
|
+
name: "API_KEY",
|
|
199
|
+
// description and isRequired are optional
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
};
|
|
205
|
+
// Act
|
|
206
|
+
const result = transformServer(officialServer);
|
|
207
|
+
// Assert
|
|
208
|
+
expect(result).not.toBeNull();
|
|
209
|
+
expect(result === null || result === void 0 ? void 0 : result.env).toEqual({
|
|
210
|
+
API_KEY: {
|
|
211
|
+
description: "",
|
|
212
|
+
required: false,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
describe("transformAndFilterServers", () => {
|
|
218
|
+
it("should transform and filter multiple servers", () => {
|
|
219
|
+
// Arrange
|
|
220
|
+
const servers = [
|
|
221
|
+
{
|
|
222
|
+
name: "io.github.test/npm-stdio",
|
|
223
|
+
packages: [
|
|
224
|
+
{
|
|
225
|
+
registryType: "npm",
|
|
226
|
+
identifier: "@test/npm-stdio",
|
|
227
|
+
transport: { type: "stdio" },
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: "io.github.test/docker",
|
|
233
|
+
packages: [
|
|
234
|
+
{
|
|
235
|
+
registryType: "docker",
|
|
236
|
+
identifier: "test/docker",
|
|
237
|
+
transport: { type: "stdio" },
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: "io.github.test/npm-sse",
|
|
243
|
+
packages: [
|
|
244
|
+
{
|
|
245
|
+
registryType: "npm",
|
|
246
|
+
identifier: "@test/npm-sse",
|
|
247
|
+
transport: { type: "sse" },
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: "io.github.test/another-npm-stdio",
|
|
253
|
+
packages: [
|
|
254
|
+
{
|
|
255
|
+
registryType: "npm",
|
|
256
|
+
identifier: "@test/another-npm-stdio",
|
|
257
|
+
transport: { type: "stdio" },
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
];
|
|
262
|
+
// Act
|
|
263
|
+
const result = transformAndFilterServers(servers);
|
|
264
|
+
// Assert
|
|
265
|
+
expect(result).toHaveLength(2);
|
|
266
|
+
expect(result[0].packageName).toBe("@test/npm-stdio");
|
|
267
|
+
expect(result[1].packageName).toBe("@test/another-npm-stdio");
|
|
268
|
+
});
|
|
269
|
+
it("should return empty array when no servers match criteria", () => {
|
|
270
|
+
// Arrange
|
|
271
|
+
const servers = [
|
|
272
|
+
{
|
|
273
|
+
name: "io.github.test/docker",
|
|
274
|
+
packages: [
|
|
275
|
+
{
|
|
276
|
+
registryType: "docker",
|
|
277
|
+
identifier: "test/docker",
|
|
278
|
+
transport: { type: "stdio" },
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "io.github.test/npm-sse",
|
|
284
|
+
packages: [
|
|
285
|
+
{
|
|
286
|
+
registryType: "npm",
|
|
287
|
+
identifier: "@test/npm-sse",
|
|
288
|
+
transport: { type: "sse" },
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
];
|
|
293
|
+
// Act
|
|
294
|
+
const result = transformAndFilterServers(servers);
|
|
295
|
+
// Assert
|
|
296
|
+
expect(result).toHaveLength(0);
|
|
297
|
+
});
|
|
298
|
+
it("should handle empty array", () => {
|
|
299
|
+
// Arrange
|
|
300
|
+
const servers = [];
|
|
301
|
+
// Act
|
|
302
|
+
const result = transformAndFilterServers(servers);
|
|
303
|
+
// Assert
|
|
304
|
+
expect(result).toHaveLength(0);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { FederatedRegistryProvider } from "./providers/federated-registry-provider";
|
|
2
|
+
export { LocalRegistryProvider } from "./providers/local-registry-provider";
|
|
3
|
+
export { OfficialRegistryProvider } from "./providers/official-registry-provider";
|
|
4
|
+
export type { RegistryProviderType } from "./registry-factory";
|
|
5
|
+
export { getRegistryProvider, initRegistryFactory, resetRegistryFactory, } from "./registry-factory";
|
|
6
|
+
export type { OfficialEnvironmentVariable, OfficialPackage, OfficialRepository, OfficialSearchResponse, OfficialServer, OfficialServerItem, OfficialTransport, } from "./registry-schema";
|
|
7
|
+
export type { IRegistryProvider, RegistrySource } from "./registry-types";
|
|
8
|
+
export { transformAndFilterServers, transformServer } from "./registry-utils";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Types and interfaces
|
|
2
|
+
export { FederatedRegistryProvider } from "./providers/federated-registry-provider";
|
|
3
|
+
// Providers
|
|
4
|
+
export { LocalRegistryProvider } from "./providers/local-registry-provider";
|
|
5
|
+
export { OfficialRegistryProvider } from "./providers/official-registry-provider";
|
|
6
|
+
// Factory
|
|
7
|
+
export { getRegistryProvider, initRegistryFactory, resetRegistryFactory, } from "./registry-factory";
|
|
8
|
+
// Utils
|
|
9
|
+
export { transformAndFilterServers, transformServer } from "./registry-utils";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MCPServerPackageConfig } from "../../package/package-types";
|
|
2
|
+
import type { IRegistryProvider } from "../registry-types";
|
|
3
|
+
import type { LocalRegistryProvider } from "./local-registry-provider";
|
|
4
|
+
import type { OfficialRegistryProvider } from "./official-registry-provider";
|
|
5
|
+
/**
|
|
6
|
+
* Federated Registry Provider
|
|
7
|
+
* Implements local-first federated query strategy
|
|
8
|
+
*/
|
|
9
|
+
export declare class FederatedRegistryProvider implements IRegistryProvider {
|
|
10
|
+
private readonly localProvider;
|
|
11
|
+
private readonly officialProvider;
|
|
12
|
+
constructor(localProvider: LocalRegistryProvider, officialProvider: OfficialRegistryProvider);
|
|
13
|
+
/**
|
|
14
|
+
* Get package configuration (local first)
|
|
15
|
+
* @param packageName - Package name
|
|
16
|
+
* @returns Package configuration, null if not found
|
|
17
|
+
*/
|
|
18
|
+
getPackageConfig(packageName: string): Promise<MCPServerPackageConfig | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Check if package exists
|
|
21
|
+
* @param packageName - Package name
|
|
22
|
+
* @returns Whether the package exists
|
|
23
|
+
*/
|
|
24
|
+
exists(packageName: string): Promise<boolean>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federated Registry Provider
|
|
3
|
+
* Implements local-first federated query strategy
|
|
4
|
+
*/
|
|
5
|
+
export class FederatedRegistryProvider {
|
|
6
|
+
constructor(localProvider, officialProvider) {
|
|
7
|
+
this.localProvider = localProvider;
|
|
8
|
+
this.officialProvider = officialProvider;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get package configuration (local first)
|
|
12
|
+
* @param packageName - Package name
|
|
13
|
+
* @returns Package configuration, null if not found
|
|
14
|
+
*/
|
|
15
|
+
async getPackageConfig(packageName) {
|
|
16
|
+
// 1. Query local first
|
|
17
|
+
const localConfig = await this.localProvider.getPackageConfig(packageName);
|
|
18
|
+
if (localConfig) {
|
|
19
|
+
return localConfig;
|
|
20
|
+
}
|
|
21
|
+
// 2. If not found locally, query official
|
|
22
|
+
try {
|
|
23
|
+
const officialConfig = await this.officialProvider.getPackageConfig(packageName);
|
|
24
|
+
return officialConfig;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
console.warn(`[FederatedRegistry] Failed to fetch from official: ${error}`);
|
|
28
|
+
return null; // Official API failed, return null
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check if package exists
|
|
33
|
+
* @param packageName - Package name
|
|
34
|
+
* @returns Whether the package exists
|
|
35
|
+
*/
|
|
36
|
+
async exists(packageName) {
|
|
37
|
+
// 1. Check local first
|
|
38
|
+
if (await this.localProvider.exists(packageName)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
// 2. Then check official
|
|
42
|
+
try {
|
|
43
|
+
return await this.officialProvider.exists(packageName);
|
|
44
|
+
}
|
|
45
|
+
catch (_a) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PackageRepository } from "../../package/package-repository";
|
|
2
|
+
import type { MCPServerPackageConfig } from "../../package/package-types";
|
|
3
|
+
import type { IRegistryProvider } from "../registry-types";
|
|
4
|
+
/**
|
|
5
|
+
* Local Registry Provider adapter
|
|
6
|
+
* Wraps PackageRepository as an async interface
|
|
7
|
+
*/
|
|
8
|
+
export declare class LocalRegistryProvider implements IRegistryProvider {
|
|
9
|
+
private readonly packageRepository;
|
|
10
|
+
constructor(packageRepository: PackageRepository);
|
|
11
|
+
/**
|
|
12
|
+
* Get package configuration
|
|
13
|
+
* @param packageName - Package name
|
|
14
|
+
* @returns Package configuration, null if not found
|
|
15
|
+
*/
|
|
16
|
+
getPackageConfig(packageName: string): Promise<MCPServerPackageConfig | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Check if package exists
|
|
19
|
+
* @param packageName - Package name
|
|
20
|
+
* @returns Whether the package exists
|
|
21
|
+
*/
|
|
22
|
+
exists(packageName: string): Promise<boolean>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Registry Provider adapter
|
|
3
|
+
* Wraps PackageRepository as an async interface
|
|
4
|
+
*/
|
|
5
|
+
export class LocalRegistryProvider {
|
|
6
|
+
constructor(packageRepository) {
|
|
7
|
+
this.packageRepository = packageRepository;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get package configuration
|
|
11
|
+
* @param packageName - Package name
|
|
12
|
+
* @returns Package configuration, null if not found
|
|
13
|
+
*/
|
|
14
|
+
async getPackageConfig(packageName) {
|
|
15
|
+
if (!this.packageRepository.exists(packageName)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const config = this.packageRepository.getPackageConfig(packageName);
|
|
20
|
+
return config;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error(`[LocalRegistry] Failed to get package config for '${packageName}':`, error);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if package exists
|
|
29
|
+
* @param packageName - Package name
|
|
30
|
+
* @returns Whether the package exists
|
|
31
|
+
*/
|
|
32
|
+
async exists(packageName) {
|
|
33
|
+
return this.packageRepository.exists(packageName);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { MCPServerPackageConfig } from "../../package/package-types";
|
|
2
|
+
import type { IRegistryProvider } from "../registry-types";
|
|
3
|
+
/**
|
|
4
|
+
* Official Registry Provider
|
|
5
|
+
* Responsible for calling official API and transforming data
|
|
6
|
+
*/
|
|
7
|
+
export declare class OfficialRegistryProvider implements IRegistryProvider {
|
|
8
|
+
private readonly baseUrl;
|
|
9
|
+
private readonly timeout;
|
|
10
|
+
/**
|
|
11
|
+
* Get package configuration
|
|
12
|
+
* @param packageName - Package name (official Registry ID)
|
|
13
|
+
* @returns Package configuration, null if not found
|
|
14
|
+
*/
|
|
15
|
+
getPackageConfig(packageName: string): Promise<MCPServerPackageConfig | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Check if package exists
|
|
18
|
+
* @param packageName - Package name
|
|
19
|
+
* @returns Whether the package exists
|
|
20
|
+
*/
|
|
21
|
+
exists(packageName: string): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Search packages
|
|
24
|
+
* @param query - Search keyword
|
|
25
|
+
* @returns List of package configurations
|
|
26
|
+
*/
|
|
27
|
+
search(query: string): Promise<MCPServerPackageConfig[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Fetch data from official API
|
|
30
|
+
* @param endpoint - API endpoint
|
|
31
|
+
* @returns Response data
|
|
32
|
+
*/
|
|
33
|
+
private fetchFromOfficial;
|
|
34
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { OfficialSearchResponseSchema, } from "../registry-schema";
|
|
2
|
+
import { transformAndFilterServers } from "../registry-utils";
|
|
3
|
+
/**
|
|
4
|
+
* Official Registry Provider
|
|
5
|
+
* Responsible for calling official API and transforming data
|
|
6
|
+
*/
|
|
7
|
+
export class OfficialRegistryProvider {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.baseUrl = "https://registry.modelcontextprotocol.io/v0.1";
|
|
10
|
+
this.timeout = 5000; // 5 second timeout
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get package configuration
|
|
14
|
+
* @param packageName - Package name (official Registry ID)
|
|
15
|
+
* @returns Package configuration, null if not found
|
|
16
|
+
*/
|
|
17
|
+
async getPackageConfig(packageName) {
|
|
18
|
+
try {
|
|
19
|
+
// 1. Call search API
|
|
20
|
+
const searchResults = await this.search(packageName);
|
|
21
|
+
// 2. Return first result (if exists)
|
|
22
|
+
if (searchResults.length > 0) {
|
|
23
|
+
return searchResults[0];
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.warn(`[OfficialRegistry] Failed to get package config for '${packageName}':`, error);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if package exists
|
|
34
|
+
* @param packageName - Package name
|
|
35
|
+
* @returns Whether the package exists
|
|
36
|
+
*/
|
|
37
|
+
async exists(packageName) {
|
|
38
|
+
const config = await this.getPackageConfig(packageName);
|
|
39
|
+
return config !== null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Search packages
|
|
43
|
+
* @param query - Search keyword
|
|
44
|
+
* @returns List of package configurations
|
|
45
|
+
*/
|
|
46
|
+
async search(query) {
|
|
47
|
+
try {
|
|
48
|
+
const response = await this.fetchFromOfficial(`/servers?search=${encodeURIComponent(query)}`);
|
|
49
|
+
const servers = response.servers.map((item) => item.server);
|
|
50
|
+
return transformAndFilterServers(servers);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error(`[OfficialRegistry] Search error for '${query}':`, error);
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Fetch data from official API
|
|
59
|
+
* @param endpoint - API endpoint
|
|
60
|
+
* @returns Response data
|
|
61
|
+
*/
|
|
62
|
+
async fetchFromOfficial(endpoint) {
|
|
63
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(url, {
|
|
68
|
+
signal: controller.signal,
|
|
69
|
+
headers: {
|
|
70
|
+
Accept: "application/json",
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
75
|
+
}
|
|
76
|
+
const data = await response.json();
|
|
77
|
+
return OfficialSearchResponseSchema.parse(data);
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
clearTimeout(timeoutId);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PackageRepository } from "../package/package-repository";
|
|
2
|
+
import type { IRegistryProvider } from "./registry-types";
|
|
3
|
+
/**
|
|
4
|
+
* Registry Provider type
|
|
5
|
+
*/
|
|
6
|
+
export type RegistryProviderType = "LOCAL" | "OFFICIAL" | "FEDERATED";
|
|
7
|
+
/**
|
|
8
|
+
* Initialize Registry Factory
|
|
9
|
+
* @param packageRepository - Local package repository
|
|
10
|
+
*/
|
|
11
|
+
export declare function initRegistryFactory(packageRepository: PackageRepository): void;
|
|
12
|
+
/**
|
|
13
|
+
* Get Registry Provider
|
|
14
|
+
* @param type - Provider type, defaults to FEDERATED
|
|
15
|
+
* @returns Registry Provider instance
|
|
16
|
+
*/
|
|
17
|
+
export declare function getRegistryProvider(type?: RegistryProviderType): IRegistryProvider;
|
|
18
|
+
/**
|
|
19
|
+
* Reset factory (mainly used for testing)
|
|
20
|
+
*/
|
|
21
|
+
export declare function resetRegistryFactory(): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { FederatedRegistryProvider } from "./providers/federated-registry-provider";
|
|
2
|
+
import { LocalRegistryProvider } from "./providers/local-registry-provider";
|
|
3
|
+
import { OfficialRegistryProvider } from "./providers/official-registry-provider";
|
|
4
|
+
/**
|
|
5
|
+
* Registry Provider instance container
|
|
6
|
+
*/
|
|
7
|
+
let localProvider = null;
|
|
8
|
+
let officialProvider = null;
|
|
9
|
+
let federatedProvider = null;
|
|
10
|
+
let initialized = false;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize Registry Factory
|
|
13
|
+
* @param packageRepository - Local package repository
|
|
14
|
+
*/
|
|
15
|
+
export function initRegistryFactory(packageRepository) {
|
|
16
|
+
if (initialized) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
localProvider = new LocalRegistryProvider(packageRepository);
|
|
20
|
+
officialProvider = new OfficialRegistryProvider();
|
|
21
|
+
federatedProvider = new FederatedRegistryProvider(localProvider, officialProvider);
|
|
22
|
+
initialized = true;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get Registry Provider
|
|
26
|
+
* @param type - Provider type, defaults to FEDERATED
|
|
27
|
+
* @returns Registry Provider instance
|
|
28
|
+
*/
|
|
29
|
+
export function getRegistryProvider(type = "FEDERATED") {
|
|
30
|
+
if (!initialized || !localProvider || !officialProvider || !federatedProvider) {
|
|
31
|
+
throw new Error("RegistryFactory not initialized. Call initRegistryFactory() first.");
|
|
32
|
+
}
|
|
33
|
+
switch (type) {
|
|
34
|
+
case "LOCAL":
|
|
35
|
+
return localProvider;
|
|
36
|
+
case "OFFICIAL":
|
|
37
|
+
return officialProvider;
|
|
38
|
+
case "FEDERATED":
|
|
39
|
+
return federatedProvider;
|
|
40
|
+
default:
|
|
41
|
+
throw new Error(`Unknown provider type: ${type}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Reset factory (mainly used for testing)
|
|
46
|
+
*/
|
|
47
|
+
export function resetRegistryFactory() {
|
|
48
|
+
localProvider = null;
|
|
49
|
+
officialProvider = null;
|
|
50
|
+
federatedProvider = null;
|
|
51
|
+
initialized = false;
|
|
52
|
+
}
|