@sebgroup/green-core 2.21.0-rc.20260113130744922 → 2.21.0-rc.20260113135658239
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/bin/mcp-server/tools.js +114 -18
- package/bin/mcp-server/types.d.ts +4 -0
- package/custom-elements.json +9873 -9873
- package/gds-element.js +1 -1
- package/generated/mcp/INSTRUCTIONS.md +5 -5
- package/generated/mcp/components.json +1 -1
- package/generated/mcp/icons.json +1 -1
- package/generated/mcp/index.json +1 -1
- package/generated/react/index.d.ts +7 -7
- package/generated/react/index.js +7 -7
- package/package.json +1 -1
- package/utils/helpers/custom-element-scoping.js +1 -1
package/bin/mcp-server/tools.js
CHANGED
|
@@ -18,19 +18,39 @@ function setupToolHandlers(server) {
|
|
|
18
18
|
tools: [
|
|
19
19
|
{
|
|
20
20
|
name: "search_components",
|
|
21
|
-
description: "Search for Green Design System components by name, description, or functionality. Use this when you don't know the exact component name or want to discover available components.",
|
|
21
|
+
description: "Search for Green Design System components by name, description, or functionality. Use this when you don't know the exact component name or want to discover available components. Supports multi-term searches and regex patterns.",
|
|
22
22
|
inputSchema: {
|
|
23
23
|
type: "object",
|
|
24
24
|
properties: {
|
|
25
25
|
query: {
|
|
26
26
|
type: "string",
|
|
27
|
-
description: "Component name or search term (e.g., 'button'
|
|
27
|
+
description: "Component name or search term. Can be multiple terms separated by spaces/commas (e.g., 'button input radio'). Use regex format (e.g., '^gds-button') if useRegex is true."
|
|
28
28
|
},
|
|
29
29
|
category: {
|
|
30
30
|
type: "string",
|
|
31
31
|
enum: ["component", "icon", "all"],
|
|
32
32
|
description: "Filter by type. Default: 'all'",
|
|
33
33
|
default: "all"
|
|
34
|
+
},
|
|
35
|
+
splitTerms: {
|
|
36
|
+
type: "boolean",
|
|
37
|
+
description: "Split query on spaces and commas to search for multiple terms. Default: true",
|
|
38
|
+
default: true
|
|
39
|
+
},
|
|
40
|
+
matchAll: {
|
|
41
|
+
type: "boolean",
|
|
42
|
+
description: "When splitTerms is true, require ALL terms to match (AND logic). Default: false (OR logic)",
|
|
43
|
+
default: false
|
|
44
|
+
},
|
|
45
|
+
useRegex: {
|
|
46
|
+
type: "boolean",
|
|
47
|
+
description: "Treat query as a regular expression pattern. Default: false",
|
|
48
|
+
default: false
|
|
49
|
+
},
|
|
50
|
+
maxResults: {
|
|
51
|
+
type: "number",
|
|
52
|
+
description: "Maximum number of results to return. Default: 20",
|
|
53
|
+
default: 20
|
|
34
54
|
}
|
|
35
55
|
},
|
|
36
56
|
required: ["query"]
|
|
@@ -155,19 +175,93 @@ function setupToolHandlers(server) {
|
|
|
155
175
|
});
|
|
156
176
|
}
|
|
157
177
|
async function handleSearchComponents(input) {
|
|
158
|
-
const {
|
|
178
|
+
const {
|
|
179
|
+
query,
|
|
180
|
+
category = "all",
|
|
181
|
+
splitTerms = true,
|
|
182
|
+
matchAll = false,
|
|
183
|
+
useRegex = false,
|
|
184
|
+
maxResults = 20
|
|
185
|
+
} = input;
|
|
159
186
|
const results = [];
|
|
160
|
-
const queryLower = query.toLowerCase();
|
|
161
187
|
try {
|
|
188
|
+
let searchTerms;
|
|
189
|
+
let regexPattern = null;
|
|
190
|
+
if (useRegex) {
|
|
191
|
+
try {
|
|
192
|
+
if (query.length > 100) {
|
|
193
|
+
throw new Error("Regex pattern too long (max 100 characters)");
|
|
194
|
+
}
|
|
195
|
+
regexPattern = new RegExp(query, "i");
|
|
196
|
+
searchTerms = [query];
|
|
197
|
+
} catch (error) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Invalid regex pattern: ${error instanceof Error ? error.message : "unknown error"}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
} else if (splitTerms) {
|
|
203
|
+
searchTerms = query.toLowerCase().split(/[\s,]+/).filter((term) => term.length > 0);
|
|
204
|
+
} else {
|
|
205
|
+
searchTerms = [query.toLowerCase()];
|
|
206
|
+
}
|
|
207
|
+
if (searchTerms.length === 0) {
|
|
208
|
+
throw new Error("Query cannot be empty");
|
|
209
|
+
}
|
|
162
210
|
const loadComponents = category === "component" || category === "all";
|
|
163
211
|
const loadIcons = category === "icon" || category === "all";
|
|
164
212
|
const [componentsIndex, iconsIndex] = await Promise.all([
|
|
165
213
|
loadComponents ? loadComponentsIndex() : Promise.resolve(null),
|
|
166
214
|
loadIcons ? loadIconsIndex() : Promise.resolve(null)
|
|
167
215
|
]);
|
|
216
|
+
const checkMatches = (item) => {
|
|
217
|
+
const tagName = item.tagName.toLowerCase();
|
|
218
|
+
const name = item.name.toLowerCase();
|
|
219
|
+
const className = item.className.toLowerCase();
|
|
220
|
+
const description = item.description?.toLowerCase() || "";
|
|
221
|
+
if (regexPattern) {
|
|
222
|
+
const matches2 = regexPattern.test(tagName) || regexPattern.test(name) || regexPattern.test(className) || regexPattern.test(description);
|
|
223
|
+
if (matches2) {
|
|
224
|
+
if (regexPattern.test(tagName)) {
|
|
225
|
+
if (tagName === query.toLowerCase()) return { matches: true, tier: 1, matchedTerms: 1 };
|
|
226
|
+
if (tagName.startsWith(query.toLowerCase())) return { matches: true, tier: 2, matchedTerms: 1 };
|
|
227
|
+
return { matches: true, tier: 3, matchedTerms: 1 };
|
|
228
|
+
}
|
|
229
|
+
return { matches: true, tier: 4, matchedTerms: 1 };
|
|
230
|
+
}
|
|
231
|
+
return { matches: false, tier: 0, matchedTerms: 0 };
|
|
232
|
+
}
|
|
233
|
+
let matchedTerms = 0;
|
|
234
|
+
let bestTier = 999;
|
|
235
|
+
for (const term of searchTerms) {
|
|
236
|
+
let termMatched = false;
|
|
237
|
+
let termTier = 999;
|
|
238
|
+
if (tagName === `gds-${term}` || tagName === term) {
|
|
239
|
+
termMatched = true;
|
|
240
|
+
termTier = Math.min(termTier, 1);
|
|
241
|
+
} else if (tagName.startsWith(term) || tagName.startsWith(`gds-${term}`)) {
|
|
242
|
+
termMatched = true;
|
|
243
|
+
termTier = Math.min(termTier, 2);
|
|
244
|
+
} else if (tagName.includes(term)) {
|
|
245
|
+
termMatched = true;
|
|
246
|
+
termTier = Math.min(termTier, 3);
|
|
247
|
+
} else if (name.includes(term) || className.includes(term)) {
|
|
248
|
+
termMatched = true;
|
|
249
|
+
termTier = Math.min(termTier, 4);
|
|
250
|
+
} else if (description.includes(term)) {
|
|
251
|
+
termMatched = true;
|
|
252
|
+
termTier = Math.min(termTier, 5);
|
|
253
|
+
}
|
|
254
|
+
if (termMatched) {
|
|
255
|
+
matchedTerms++;
|
|
256
|
+
bestTier = Math.min(bestTier, termTier);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const matches = matchAll ? matchedTerms === searchTerms.length : matchedTerms > 0;
|
|
260
|
+
return { matches, tier: matches ? bestTier : 999, matchedTerms };
|
|
261
|
+
};
|
|
168
262
|
if (componentsIndex) {
|
|
169
263
|
for (const component of componentsIndex.components) {
|
|
170
|
-
const matches
|
|
264
|
+
const { matches, tier, matchedTerms } = checkMatches(component);
|
|
171
265
|
if (matches) {
|
|
172
266
|
const shortName = component.tagName.replace(/^gds-/, "");
|
|
173
267
|
const resourceUris = {};
|
|
@@ -185,23 +279,21 @@ async function handleSearchComponents(input) {
|
|
|
185
279
|
description: component.description,
|
|
186
280
|
category: "component",
|
|
187
281
|
availableDocs: component.files,
|
|
188
|
-
resourceUris
|
|
282
|
+
resourceUris,
|
|
283
|
+
tier,
|
|
284
|
+
matchedTerms
|
|
189
285
|
});
|
|
190
286
|
}
|
|
191
287
|
}
|
|
192
288
|
}
|
|
193
289
|
if (iconsIndex) {
|
|
194
290
|
for (const icon of iconsIndex.icons) {
|
|
195
|
-
const matches
|
|
291
|
+
const { matches, tier, matchedTerms } = checkMatches(icon);
|
|
196
292
|
if (matches) {
|
|
197
293
|
const shortName = icon.tagName.replace(/^gds-/, "");
|
|
198
294
|
const resourceUris = {};
|
|
199
295
|
for (const docType of icon.files) {
|
|
200
|
-
resourceUris[docType] = buildResourceUri(
|
|
201
|
-
"icons",
|
|
202
|
-
shortName,
|
|
203
|
-
docType
|
|
204
|
-
);
|
|
296
|
+
resourceUris[docType] = buildResourceUri("icons", shortName, docType);
|
|
205
297
|
}
|
|
206
298
|
results.push({
|
|
207
299
|
name: icon.name,
|
|
@@ -210,19 +302,23 @@ async function handleSearchComponents(input) {
|
|
|
210
302
|
description: icon.description,
|
|
211
303
|
category: "icon",
|
|
212
304
|
availableDocs: icon.files,
|
|
213
|
-
resourceUris
|
|
305
|
+
resourceUris,
|
|
306
|
+
tier,
|
|
307
|
+
matchedTerms
|
|
214
308
|
});
|
|
215
309
|
}
|
|
216
310
|
}
|
|
217
311
|
}
|
|
218
312
|
results.sort((a, b) => {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (!aExact && bExact) return 1;
|
|
313
|
+
if (a.tier !== b.tier) return a.tier - b.tier;
|
|
314
|
+
if (splitTerms && a.matchedTerms !== b.matchedTerms)
|
|
315
|
+
return b.matchedTerms - a.matchedTerms;
|
|
223
316
|
return a.tagName.localeCompare(b.tagName);
|
|
224
317
|
});
|
|
225
|
-
const limitedResults = results.slice(0,
|
|
318
|
+
const limitedResults = results.slice(0, maxResults).map((r) => {
|
|
319
|
+
const { tier, matchedTerms, ...result } = r;
|
|
320
|
+
return result;
|
|
321
|
+
});
|
|
226
322
|
return {
|
|
227
323
|
content: [
|
|
228
324
|
{
|
|
@@ -96,6 +96,10 @@ export interface SearchResult {
|
|
|
96
96
|
export interface SearchComponentsInput {
|
|
97
97
|
query: string;
|
|
98
98
|
category?: Category;
|
|
99
|
+
splitTerms?: boolean;
|
|
100
|
+
matchAll?: boolean;
|
|
101
|
+
useRegex?: boolean;
|
|
102
|
+
maxResults?: number;
|
|
99
103
|
}
|
|
100
104
|
export interface GetComponentDocsInput {
|
|
101
105
|
componentName: string;
|