@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
|
@@ -1,117 +1,89 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MeiliSearch Service for Awesome MCP Registry
|
|
3
|
-
* Handles search indexing and querying for MCP packages
|
|
4
|
-
*/
|
|
5
1
|
import fs from "node:fs/promises";
|
|
6
|
-
import path from "node:path";
|
|
7
2
|
import { MeiliSearch } from "meilisearch";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
import { getMeiliSearchConfig } from "../../shared/config/environment";
|
|
4
|
+
/**
|
|
5
|
+
* Search Service Object
|
|
6
|
+
* Manages MeiliSearch connection and indexing with singleton pattern
|
|
7
|
+
*/
|
|
8
|
+
export class SearchSO {
|
|
9
|
+
constructor(client, _index, host, indexName) {
|
|
10
|
+
this.client = client;
|
|
11
|
+
this._index = _index;
|
|
12
|
+
this.host = host;
|
|
15
13
|
this.indexName = indexName;
|
|
16
|
-
// Initialize MeiliSearch client
|
|
17
|
-
this.client = new MeiliSearch({
|
|
18
|
-
host: this.host,
|
|
19
|
-
apiKey: this.apiKey || undefined,
|
|
20
|
-
});
|
|
21
|
-
this._index = null;
|
|
22
|
-
this.isInitialized = false;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Get the initialization status
|
|
26
|
-
*/
|
|
27
|
-
getIsInitialized() {
|
|
28
|
-
return this.isInitialized;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Get the MeiliSearch client
|
|
32
|
-
*/
|
|
33
|
-
getClient() {
|
|
34
|
-
return this.client;
|
|
35
14
|
}
|
|
36
15
|
get index() {
|
|
37
16
|
return this._index;
|
|
38
17
|
}
|
|
39
|
-
|
|
40
|
-
this._index
|
|
18
|
+
get isInitialized() {
|
|
19
|
+
return this._index !== null;
|
|
41
20
|
}
|
|
42
|
-
async
|
|
21
|
+
static async getInstance(host, apiKey, indexName = "mcp-packages") {
|
|
22
|
+
var _a;
|
|
23
|
+
if ((_a = SearchSO.instance) === null || _a === void 0 ? void 0 : _a.isInitialized) {
|
|
24
|
+
return SearchSO.instance;
|
|
25
|
+
}
|
|
26
|
+
const meiliConfig = getMeiliSearchConfig();
|
|
27
|
+
const meiliHost = host || meiliConfig.host;
|
|
28
|
+
const meiliKey = apiKey || meiliConfig.apiKey || undefined;
|
|
29
|
+
const client = new MeiliSearch({
|
|
30
|
+
host: meiliHost,
|
|
31
|
+
apiKey: meiliKey,
|
|
32
|
+
});
|
|
33
|
+
console.log(`Connecting to MeiliSearch at ${meiliHost}...`);
|
|
34
|
+
await client.health();
|
|
35
|
+
console.log("✅ MeiliSearch is healthy");
|
|
36
|
+
let index;
|
|
43
37
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
await
|
|
47
|
-
console.log("✅ MeiliSearch is healthy");
|
|
48
|
-
// Create or get the index
|
|
49
|
-
try {
|
|
50
|
-
await this.client.createIndex(this.indexName, { primaryKey: "id" });
|
|
51
|
-
console.log(`✅ Created new index: ${this.indexName}`);
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
if (error.message.includes("exists")) {
|
|
55
|
-
console.log(`✅ Using existing index: ${this.indexName}`);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// Get the index object
|
|
62
|
-
this.index = await this.client.getIndex(this.indexName);
|
|
63
|
-
// Configure search settings
|
|
64
|
-
await this.configureIndex();
|
|
65
|
-
this.isInitialized = true;
|
|
66
|
-
console.log("✅ Search service initialized successfully");
|
|
38
|
+
await client.createIndex(indexName, { primaryKey: "id" });
|
|
39
|
+
console.log(`✅ Created new index: ${indexName}`);
|
|
40
|
+
index = await client.getIndex(indexName);
|
|
67
41
|
}
|
|
68
42
|
catch (error) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
43
|
+
if (error.message.includes("exists")) {
|
|
44
|
+
console.log(`✅ Using existing index: ${indexName}`);
|
|
45
|
+
}
|
|
46
|
+
index = await client.getIndex(indexName);
|
|
72
47
|
}
|
|
48
|
+
const searchSO = new SearchSO(client, index, meiliHost, indexName);
|
|
49
|
+
await searchSO.configureIndex();
|
|
50
|
+
console.log("✅ Search service initialized successfully");
|
|
51
|
+
SearchSO.instance = searchSO;
|
|
52
|
+
return searchSO;
|
|
73
53
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Configure search index settings for optimal MCP package search
|
|
76
|
-
*/
|
|
77
54
|
async configureIndex() {
|
|
55
|
+
if (!this._index)
|
|
56
|
+
return;
|
|
78
57
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
],
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
db: ["database"],
|
|
109
|
-
api: ["rest", "graphql"],
|
|
110
|
-
auth: ["authentication", "authorization"],
|
|
111
|
-
mcp: ["model context protocol"],
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
}
|
|
58
|
+
await this._index.updateSettings({
|
|
59
|
+
searchableAttributes: [
|
|
60
|
+
"name",
|
|
61
|
+
"packageName",
|
|
62
|
+
"description",
|
|
63
|
+
"tools",
|
|
64
|
+
"category",
|
|
65
|
+
"author",
|
|
66
|
+
"keywords",
|
|
67
|
+
],
|
|
68
|
+
filterableAttributes: ["category", "validated", "author", "hasTools", "popularity"],
|
|
69
|
+
sortableAttributes: ["popularity", "name", "category"],
|
|
70
|
+
rankingRules: [
|
|
71
|
+
"words",
|
|
72
|
+
"typo",
|
|
73
|
+
"proximity",
|
|
74
|
+
"attribute",
|
|
75
|
+
"sort",
|
|
76
|
+
"exactness",
|
|
77
|
+
"popularity:desc",
|
|
78
|
+
],
|
|
79
|
+
synonyms: {
|
|
80
|
+
ai: ["artificial intelligence", "machine learning", "ml"],
|
|
81
|
+
db: ["database"],
|
|
82
|
+
api: ["rest", "graphql"],
|
|
83
|
+
auth: ["authentication", "authorization"],
|
|
84
|
+
mcp: ["model context protocol"],
|
|
85
|
+
},
|
|
86
|
+
});
|
|
115
87
|
console.log("✅ Search index configured");
|
|
116
88
|
}
|
|
117
89
|
catch (error) {
|
|
@@ -119,166 +91,13 @@ class SearchService {
|
|
|
119
91
|
throw error;
|
|
120
92
|
}
|
|
121
93
|
}
|
|
122
|
-
/**
|
|
123
|
-
* Transform package data for search indexing
|
|
124
|
-
*/
|
|
125
|
-
transformPackageForIndex(packageName, packageData) {
|
|
126
|
-
// Extract tools information
|
|
127
|
-
const tools = packageData.tools || {};
|
|
128
|
-
const toolNames = Object.keys(tools);
|
|
129
|
-
const toolDescriptions = Object.values(tools)
|
|
130
|
-
.map((t) => t.description || "")
|
|
131
|
-
.join(" ");
|
|
132
|
-
// Calculate basic popularity score (can be enhanced with real metrics)
|
|
133
|
-
const popularity = this.calculatePopularityScore(packageData);
|
|
134
|
-
// Create a safe ID by encoding special characters
|
|
135
|
-
const safeId = this.createSafeId(packageName);
|
|
136
|
-
return {
|
|
137
|
-
id: safeId,
|
|
138
|
-
name: packageData.name || packageName,
|
|
139
|
-
packageName: packageName,
|
|
140
|
-
description: packageData.description || "",
|
|
141
|
-
category: packageData.category || "uncategorized",
|
|
142
|
-
validated: packageData.validated || false,
|
|
143
|
-
author: this.extractAuthor(packageName),
|
|
144
|
-
tools: `${toolNames.join(" ")} ${toolDescriptions}`,
|
|
145
|
-
toolCount: toolNames.length,
|
|
146
|
-
hasTools: toolNames.length > 0,
|
|
147
|
-
keywords: this.extractKeywords(packageData, packageName),
|
|
148
|
-
popularity: popularity,
|
|
149
|
-
path: packageData.path || "",
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Create a safe ID for MeiliSearch (alphanumeric, hyphens, underscores only)
|
|
154
|
-
*/
|
|
155
|
-
createSafeId(packageName) {
|
|
156
|
-
return packageName
|
|
157
|
-
.replace(/@/g, "at-")
|
|
158
|
-
.replace(/\//g, "-")
|
|
159
|
-
.replace(/\./g, "_")
|
|
160
|
-
.replace(/:/g, "-")
|
|
161
|
-
.replace(/[^a-zA-Z0-9\-_]/g, "_")
|
|
162
|
-
.substring(0, 500); // Keep under 511 byte limit
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Calculate a popularity score for ranking
|
|
166
|
-
*/
|
|
167
|
-
calculatePopularityScore(packageData) {
|
|
168
|
-
let score = 0;
|
|
169
|
-
// Boost for validated packages
|
|
170
|
-
if (packageData.validated)
|
|
171
|
-
score += 10;
|
|
172
|
-
// Boost for packages with tools
|
|
173
|
-
const toolCount = Object.keys(packageData.tools || {}).length;
|
|
174
|
-
score += toolCount * 2;
|
|
175
|
-
// Boost for packages with good descriptions
|
|
176
|
-
if (packageData.description && packageData.description.length > 50)
|
|
177
|
-
score += 5;
|
|
178
|
-
return score;
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Extract author from package name
|
|
182
|
-
*/
|
|
183
|
-
extractAuthor(packageName) {
|
|
184
|
-
if (packageName.startsWith("@")) {
|
|
185
|
-
return packageName.split("/")[0].substring(1);
|
|
186
|
-
}
|
|
187
|
-
return "";
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Extract relevant keywords from package data
|
|
191
|
-
*/
|
|
192
|
-
extractKeywords(packageData, packageName) {
|
|
193
|
-
const keywords = [];
|
|
194
|
-
// Add category as keyword
|
|
195
|
-
if (packageData.category) {
|
|
196
|
-
keywords.push(packageData.category.replace(/-/g, " "));
|
|
197
|
-
}
|
|
198
|
-
// Extract keywords from description
|
|
199
|
-
if (packageData.description) {
|
|
200
|
-
const desc = packageData.description.toLowerCase();
|
|
201
|
-
const commonKeywords = [
|
|
202
|
-
"api",
|
|
203
|
-
"database",
|
|
204
|
-
"auth",
|
|
205
|
-
"search",
|
|
206
|
-
"ai",
|
|
207
|
-
"ml",
|
|
208
|
-
"tool",
|
|
209
|
-
"server",
|
|
210
|
-
"client",
|
|
211
|
-
];
|
|
212
|
-
commonKeywords.forEach((keyword) => {
|
|
213
|
-
if (desc.includes(keyword))
|
|
214
|
-
keywords.push(keyword);
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
// Extract from package name
|
|
218
|
-
const nameWords = packageName
|
|
219
|
-
.replace(/[@/\-_]/g, " ")
|
|
220
|
-
.split(" ")
|
|
221
|
-
.filter((w) => w.length > 2);
|
|
222
|
-
keywords.push(...nameWords);
|
|
223
|
-
return [...new Set(keywords)].join(" ");
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Index all packages from the packages-list.json file
|
|
227
|
-
*/
|
|
228
|
-
async indexPackages() {
|
|
229
|
-
if (!this.isInitialized) {
|
|
230
|
-
throw new Error("Search service not initialized. Call initialize() first.");
|
|
231
|
-
}
|
|
232
|
-
try {
|
|
233
|
-
console.log("📥 Loading packages data...");
|
|
234
|
-
const packagesPath = path.join(__dirname, "..", "..", "indexes", "packages-list.json");
|
|
235
|
-
const packagesData = await fs.readFile(packagesPath, "utf8");
|
|
236
|
-
const packages = JSON.parse(packagesData);
|
|
237
|
-
console.log(`📦 Found ${Object.keys(packages).length} packages to index`);
|
|
238
|
-
// Transform packages for indexing
|
|
239
|
-
const documents = Object.entries(packages).map(([packageName, packageData]) => this.transformPackageForIndex(packageName, packageData));
|
|
240
|
-
console.log("🔄 Indexing packages...");
|
|
241
|
-
// Add documents to index in batches
|
|
242
|
-
const batchSize = 1000;
|
|
243
|
-
const batches = [];
|
|
244
|
-
for (let i = 0; i < documents.length; i += batchSize) {
|
|
245
|
-
batches.push(documents.slice(i, i + batchSize));
|
|
246
|
-
}
|
|
247
|
-
for (let i = 0; i < batches.length; i++) {
|
|
248
|
-
const batch = batches[i];
|
|
249
|
-
if (this.index) {
|
|
250
|
-
const task = await this.index.addDocuments(batch);
|
|
251
|
-
console.log(`📝 Indexed batch ${i + 1}/${batches.length} (${batch.length} documents) - Task ID: ${task.taskUid}`);
|
|
252
|
-
// Wait for task completion
|
|
253
|
-
await this.client.waitForTask(task.taskUid);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
// Get final stats
|
|
257
|
-
if (!this.index) {
|
|
258
|
-
throw new Error("Index is not initialized");
|
|
259
|
-
}
|
|
260
|
-
const stats = await this.index.getStats();
|
|
261
|
-
console.log(`✅ Indexing complete! ${stats.numberOfDocuments} documents indexed`);
|
|
262
|
-
return stats;
|
|
263
|
-
}
|
|
264
|
-
catch (error) {
|
|
265
|
-
console.error("❌ Failed to index packages:", error.message);
|
|
266
|
-
throw error;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Search packages with advanced options
|
|
271
|
-
*/
|
|
272
94
|
async search(query, options = {}) {
|
|
273
|
-
if (!this.
|
|
274
|
-
throw new Error("Search
|
|
95
|
+
if (!this._index) {
|
|
96
|
+
throw new Error("Search index not initialized. Call getInstance() first.");
|
|
275
97
|
}
|
|
276
98
|
try {
|
|
277
99
|
const searchOptions = Object.assign({ limit: options.limit || 20, offset: options.offset || 0, filter: options.filter || undefined, sort: options.sort || ["popularity:desc"], attributesToHighlight: ["name", "description"], attributesToCrop: ["description"], cropLength: 100, highlightPreTag: "<mark>", highlightPostTag: "</mark>", matchingStrategy: options.matchingStrategy || "last" }, options);
|
|
278
|
-
|
|
279
|
-
throw new Error("Index is not initialized");
|
|
280
|
-
}
|
|
281
|
-
const results = await this.index.search(query, searchOptions);
|
|
100
|
+
const results = await this._index.search(query, searchOptions);
|
|
282
101
|
return {
|
|
283
102
|
hits: results.hits,
|
|
284
103
|
query: results.query,
|
|
@@ -293,18 +112,12 @@ class SearchService {
|
|
|
293
112
|
throw error;
|
|
294
113
|
}
|
|
295
114
|
}
|
|
296
|
-
/**
|
|
297
|
-
* Get search suggestions/autocomplete
|
|
298
|
-
*/
|
|
299
115
|
async suggest(query, limit = 10) {
|
|
300
|
-
if (!this.
|
|
301
|
-
throw new Error("Search
|
|
116
|
+
if (!this._index) {
|
|
117
|
+
throw new Error("Search index not initialized");
|
|
302
118
|
}
|
|
303
119
|
try {
|
|
304
|
-
|
|
305
|
-
throw new Error("Index is not initialized");
|
|
306
|
-
}
|
|
307
|
-
const results = await this.index.search(query, {
|
|
120
|
+
const results = await this._index.search(query, {
|
|
308
121
|
limit: limit,
|
|
309
122
|
attributesToRetrieve: ["name", "packageName", "category"],
|
|
310
123
|
attributesToHighlight: ["name"],
|
|
@@ -325,18 +138,46 @@ class SearchService {
|
|
|
325
138
|
throw error;
|
|
326
139
|
}
|
|
327
140
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
async getFacets() {
|
|
332
|
-
if (!this.isInitialized) {
|
|
333
|
-
throw new Error("Search service not initialized. Call initialize() first.");
|
|
141
|
+
async indexPackages(packagesPath) {
|
|
142
|
+
if (!this._index) {
|
|
143
|
+
throw new Error("Search index not initialized");
|
|
334
144
|
}
|
|
335
145
|
try {
|
|
336
|
-
|
|
337
|
-
|
|
146
|
+
console.log("📥 Loading packages data...");
|
|
147
|
+
const packagesData = await fs.readFile(packagesPath, "utf8");
|
|
148
|
+
const packages = JSON.parse(packagesData);
|
|
149
|
+
console.log(`📦 Found ${Object.keys(packages).length} packages to index`);
|
|
150
|
+
// Transform packages for indexing
|
|
151
|
+
const documents = Object.entries(packages).map(([packageName, packageData]) => this.transformPackageForIndex(packageName, packageData));
|
|
152
|
+
console.log("🔄 Indexing packages...");
|
|
153
|
+
// Add documents to index in batches
|
|
154
|
+
const batchSize = 1000;
|
|
155
|
+
const batches = [];
|
|
156
|
+
for (let i = 0; i < documents.length; i += batchSize) {
|
|
157
|
+
batches.push(documents.slice(i, i + batchSize));
|
|
158
|
+
}
|
|
159
|
+
for (let i = 0; i < batches.length; i++) {
|
|
160
|
+
const batch = batches[i];
|
|
161
|
+
const task = await this._index.addDocuments(batch);
|
|
162
|
+
console.log(`📝 Indexed batch ${i + 1}/${batches.length} (${batch.length} documents) - Task ID: ${task.taskUid}`);
|
|
163
|
+
await this.client.waitForTask(task.taskUid);
|
|
338
164
|
}
|
|
339
|
-
|
|
165
|
+
// Get final stats
|
|
166
|
+
const stats = await this._index.getStats();
|
|
167
|
+
console.log(`✅ Indexing complete! ${stats.numberOfDocuments} documents indexed`);
|
|
168
|
+
return stats;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.error("❌ Failed to index packages:", error.message);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async getFacets() {
|
|
176
|
+
if (!this._index) {
|
|
177
|
+
throw new Error("Search index not initialized");
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const results = await this._index.search("", {
|
|
340
181
|
limit: 0,
|
|
341
182
|
facets: ["category", "validated", "author"],
|
|
342
183
|
});
|
|
@@ -347,36 +188,24 @@ class SearchService {
|
|
|
347
188
|
throw error;
|
|
348
189
|
}
|
|
349
190
|
}
|
|
350
|
-
/**
|
|
351
|
-
* Get index statistics
|
|
352
|
-
*/
|
|
353
191
|
async getStats() {
|
|
354
|
-
if (!this.
|
|
355
|
-
throw new Error("Search
|
|
192
|
+
if (!this._index) {
|
|
193
|
+
throw new Error("Search index not initialized");
|
|
356
194
|
}
|
|
357
195
|
try {
|
|
358
|
-
|
|
359
|
-
throw new Error("Index is not initialized");
|
|
360
|
-
}
|
|
361
|
-
return await this.index.getStats();
|
|
196
|
+
return await this._index.getStats();
|
|
362
197
|
}
|
|
363
198
|
catch (error) {
|
|
364
199
|
console.error("❌ Failed to get stats:", error.message);
|
|
365
200
|
throw error;
|
|
366
201
|
}
|
|
367
202
|
}
|
|
368
|
-
/**
|
|
369
|
-
* Clear the index
|
|
370
|
-
*/
|
|
371
203
|
async clearIndex() {
|
|
372
|
-
if (!this.
|
|
373
|
-
throw new Error("Search
|
|
204
|
+
if (!this._index) {
|
|
205
|
+
throw new Error("Search index not initialized");
|
|
374
206
|
}
|
|
375
207
|
try {
|
|
376
|
-
|
|
377
|
-
throw new Error("Index is not initialized");
|
|
378
|
-
}
|
|
379
|
-
const task = await this.index.deleteAllDocuments();
|
|
208
|
+
const task = await this._index.deleteAllDocuments();
|
|
380
209
|
await this.client.waitForTask(task.taskUid);
|
|
381
210
|
console.log("✅ Index cleared");
|
|
382
211
|
}
|
|
@@ -385,9 +214,6 @@ class SearchService {
|
|
|
385
214
|
throw error;
|
|
386
215
|
}
|
|
387
216
|
}
|
|
388
|
-
/**
|
|
389
|
-
* Health check for the search service
|
|
390
|
-
*/
|
|
391
217
|
async healthCheck() {
|
|
392
218
|
try {
|
|
393
219
|
await this.client.health();
|
|
@@ -410,7 +236,84 @@ class SearchService {
|
|
|
410
236
|
};
|
|
411
237
|
}
|
|
412
238
|
}
|
|
239
|
+
transformPackageForIndex(packageName, packageData) {
|
|
240
|
+
const tools = packageData.tools || {};
|
|
241
|
+
const toolNames = Object.keys(tools);
|
|
242
|
+
const toolDescriptions = Object.values(tools)
|
|
243
|
+
.map((t) => t.description || "")
|
|
244
|
+
.join(" ");
|
|
245
|
+
const popularity = this.calculatePopularityScore(packageData);
|
|
246
|
+
const safeId = this.createSafeId(packageName);
|
|
247
|
+
return {
|
|
248
|
+
id: safeId,
|
|
249
|
+
name: packageData.name || packageName,
|
|
250
|
+
packageName: packageName,
|
|
251
|
+
description: packageData.description || "",
|
|
252
|
+
category: packageData.category || "uncategorized",
|
|
253
|
+
validated: packageData.validated || false,
|
|
254
|
+
author: this.extractAuthor(packageName),
|
|
255
|
+
tools: `${toolNames.join(" ")} ${toolDescriptions}`,
|
|
256
|
+
toolCount: toolNames.length,
|
|
257
|
+
hasTools: toolNames.length > 0,
|
|
258
|
+
keywords: this.extractKeywords(packageData, packageName),
|
|
259
|
+
popularity: popularity,
|
|
260
|
+
path: packageData.path || "",
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
createSafeId(packageName) {
|
|
264
|
+
return packageName
|
|
265
|
+
.replace(/@/g, "at-")
|
|
266
|
+
.replace(/\//g, "-")
|
|
267
|
+
.replace(/\./g, "_")
|
|
268
|
+
.replace(/:/g, "-")
|
|
269
|
+
.replace(/[^a-zA-Z0-9\-_]/g, "_")
|
|
270
|
+
.substring(0, 500);
|
|
271
|
+
}
|
|
272
|
+
calculatePopularityScore(packageData) {
|
|
273
|
+
let score = 0;
|
|
274
|
+
if (packageData.validated)
|
|
275
|
+
score += 10;
|
|
276
|
+
const toolCount = Object.keys(packageData.tools || {}).length;
|
|
277
|
+
score += toolCount * 2;
|
|
278
|
+
if (packageData.description && packageData.description.length > 50)
|
|
279
|
+
score += 5;
|
|
280
|
+
return score;
|
|
281
|
+
}
|
|
282
|
+
extractAuthor(packageName) {
|
|
283
|
+
if (packageName.startsWith("@")) {
|
|
284
|
+
return packageName.split("/")[0].substring(1);
|
|
285
|
+
}
|
|
286
|
+
return "";
|
|
287
|
+
}
|
|
288
|
+
extractKeywords(packageData, packageName) {
|
|
289
|
+
const keywords = [];
|
|
290
|
+
if (packageData.category) {
|
|
291
|
+
keywords.push(packageData.category.replace(/-/g, " "));
|
|
292
|
+
}
|
|
293
|
+
if (packageData.description) {
|
|
294
|
+
const desc = packageData.description.toLowerCase();
|
|
295
|
+
const commonKeywords = [
|
|
296
|
+
"api",
|
|
297
|
+
"database",
|
|
298
|
+
"auth",
|
|
299
|
+
"search",
|
|
300
|
+
"ai",
|
|
301
|
+
"ml",
|
|
302
|
+
"tool",
|
|
303
|
+
"server",
|
|
304
|
+
"client",
|
|
305
|
+
];
|
|
306
|
+
commonKeywords.forEach((keyword) => {
|
|
307
|
+
if (desc.includes(keyword))
|
|
308
|
+
keywords.push(keyword);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
const nameWords = packageName
|
|
312
|
+
.replace(/[@/\-_]/g, " ")
|
|
313
|
+
.split(" ")
|
|
314
|
+
.filter((w) => w.length > 2);
|
|
315
|
+
keywords.push(...nameWords);
|
|
316
|
+
return [...new Set(keywords)].join(" ");
|
|
317
|
+
}
|
|
413
318
|
}
|
|
414
|
-
|
|
415
|
-
const searchService = new SearchService();
|
|
416
|
-
export default searchService;
|
|
319
|
+
SearchSO.instance = null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MCPSandboxProvider } from "../../domains/sandbox/sandbox-types";
|
|
2
|
+
export declare function getSandboxProvider(): MCPSandboxProvider;
|
|
3
|
+
export declare function getDaytonaConfig(): {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
apiUrl: string | undefined;
|
|
6
|
+
};
|
|
7
|
+
export declare function getSandockConfig(): {
|
|
8
|
+
apiKey: string;
|
|
9
|
+
apiUrl: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function getMeiliSearchConfig(): {
|
|
12
|
+
host: string;
|
|
13
|
+
apiKey: string | null;
|
|
14
|
+
};
|
|
15
|
+
export declare function getServerPort(): number;
|
|
16
|
+
export declare function isSearchEnabled(): boolean;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
dotenv.config({ path: path.resolve(process.cwd(), ".env.local") });
|
|
4
|
+
dotenv.config({ path: path.resolve(process.cwd(), ".env") });
|
|
5
|
+
export function getSandboxProvider() {
|
|
6
|
+
console.log("process.env.MCP_SANDBOX_PROVIDER", process.env.MCP_SANDBOX_PROVIDER);
|
|
7
|
+
const provider = (process.env.MCP_SANDBOX_PROVIDER || "LOCAL").toUpperCase();
|
|
8
|
+
if (provider === "LOCAL" ||
|
|
9
|
+
provider === "DAYTONA" ||
|
|
10
|
+
provider === "SANDOCK" ||
|
|
11
|
+
provider === "E2B") {
|
|
12
|
+
return provider;
|
|
13
|
+
}
|
|
14
|
+
console.warn(`[Environment] Unsupported MCP_SANDBOX_PROVIDER value '${provider}', falling back to LOCAL mode`);
|
|
15
|
+
return "LOCAL";
|
|
16
|
+
}
|
|
17
|
+
export function getDaytonaConfig() {
|
|
18
|
+
return {
|
|
19
|
+
apiKey: process.env.DAYTONA_API_KEY || "",
|
|
20
|
+
apiUrl: process.env.DAYTONA_API_URL,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function getSandockConfig() {
|
|
24
|
+
return {
|
|
25
|
+
apiKey: process.env.SANDOCK_API_KEY || "",
|
|
26
|
+
apiUrl: process.env.SANDOCK_API_URL || "https://sandock.ai",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function getMeiliSearchConfig() {
|
|
30
|
+
return {
|
|
31
|
+
host: process.env.MEILI_HTTP_ADDR || "http://localhost:7700",
|
|
32
|
+
apiKey: process.env.MEILI_MASTER_KEY || null,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function getServerPort() {
|
|
36
|
+
const port = process.env.PORT || process.env.MCP_SERVER_PORT;
|
|
37
|
+
return port ? parseInt(port, 10) : 3003;
|
|
38
|
+
}
|
|
39
|
+
export function isSearchEnabled() {
|
|
40
|
+
return process.env.ENABLE_SEARCH === "true";
|
|
41
|
+
}
|