@sebgroup/green-core 2.21.0-rc.20260112150926042 → 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/resources.js +23 -0
- package/bin/mcp-server/tools.js +152 -18
- package/bin/mcp-server/types.d.ts +5 -0
- package/custom-elements.json +4934 -4943
- package/gds-element.js +1 -1
- package/generated/mcp/INSTRUCTIONS.md +64 -0
- package/generated/mcp/components.json +1 -1
- package/generated/mcp/icons.json +1 -1
- package/generated/mcp/index.json +2 -1
- package/generated/react/index.d.ts +3 -3
- package/generated/react/index.js +3 -3
- package/package.json +1 -1
- package/utils/helpers/custom-element-scoping.js +1 -1
|
@@ -61,6 +61,14 @@ function setupResourceHandlers(server) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
if (globalIndex) {
|
|
64
|
+
if (globalIndex.instructions) {
|
|
65
|
+
resources.push({
|
|
66
|
+
uri: "green://instructions",
|
|
67
|
+
name: "Green Design System Instructions",
|
|
68
|
+
description: "General instructions and guidelines for agents using the Green Design System MCP",
|
|
69
|
+
mimeType: "text/markdown"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
64
72
|
for (const guide of globalIndex.guides) {
|
|
65
73
|
const name = guide.path.replace(/^(guides|concepts)\//, "").replace(/\.md$/, "");
|
|
66
74
|
const category = guide.path.startsWith("guides/") ? "guides" : "concepts";
|
|
@@ -82,6 +90,21 @@ function setupResourceHandlers(server) {
|
|
|
82
90
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
83
91
|
const { uri } = request.params;
|
|
84
92
|
try {
|
|
93
|
+
if (uri === "green://instructions") {
|
|
94
|
+
const content2 = await readMcpFile("INSTRUCTIONS.md");
|
|
95
|
+
if (!content2) {
|
|
96
|
+
throw new Error("Instructions file not found");
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
contents: [
|
|
100
|
+
{
|
|
101
|
+
uri,
|
|
102
|
+
mimeType: "text/markdown",
|
|
103
|
+
text: content2
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
};
|
|
107
|
+
}
|
|
85
108
|
const parsed = parseResourceUri(uri);
|
|
86
109
|
if (!parsed) {
|
|
87
110
|
throw new Error(`Invalid resource URI: ${uri}`);
|
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"]
|
|
@@ -106,6 +126,15 @@ function setupToolHandlers(server) {
|
|
|
106
126
|
},
|
|
107
127
|
required: ["name"]
|
|
108
128
|
}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "get_instructions",
|
|
132
|
+
description: "Get the base instructions for using the Green Design System MCP. These instructions contain critical rules, typography guidelines, layout system requirements, and general best practices that should be read before implementing any Green components.",
|
|
133
|
+
inputSchema: {
|
|
134
|
+
type: "object",
|
|
135
|
+
properties: {},
|
|
136
|
+
required: []
|
|
137
|
+
}
|
|
109
138
|
}
|
|
110
139
|
]
|
|
111
140
|
};
|
|
@@ -126,6 +155,8 @@ function setupToolHandlers(server) {
|
|
|
126
155
|
return await handleListGuides(args);
|
|
127
156
|
case "get_guide":
|
|
128
157
|
return await handleGetGuide(args);
|
|
158
|
+
case "get_instructions":
|
|
159
|
+
return await handleGetInstructions();
|
|
129
160
|
default:
|
|
130
161
|
throw new Error(`Unknown tool: ${name}`);
|
|
131
162
|
}
|
|
@@ -144,19 +175,93 @@ function setupToolHandlers(server) {
|
|
|
144
175
|
});
|
|
145
176
|
}
|
|
146
177
|
async function handleSearchComponents(input) {
|
|
147
|
-
const {
|
|
178
|
+
const {
|
|
179
|
+
query,
|
|
180
|
+
category = "all",
|
|
181
|
+
splitTerms = true,
|
|
182
|
+
matchAll = false,
|
|
183
|
+
useRegex = false,
|
|
184
|
+
maxResults = 20
|
|
185
|
+
} = input;
|
|
148
186
|
const results = [];
|
|
149
|
-
const queryLower = query.toLowerCase();
|
|
150
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
|
+
}
|
|
151
210
|
const loadComponents = category === "component" || category === "all";
|
|
152
211
|
const loadIcons = category === "icon" || category === "all";
|
|
153
212
|
const [componentsIndex, iconsIndex] = await Promise.all([
|
|
154
213
|
loadComponents ? loadComponentsIndex() : Promise.resolve(null),
|
|
155
214
|
loadIcons ? loadIconsIndex() : Promise.resolve(null)
|
|
156
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
|
+
};
|
|
157
262
|
if (componentsIndex) {
|
|
158
263
|
for (const component of componentsIndex.components) {
|
|
159
|
-
const matches
|
|
264
|
+
const { matches, tier, matchedTerms } = checkMatches(component);
|
|
160
265
|
if (matches) {
|
|
161
266
|
const shortName = component.tagName.replace(/^gds-/, "");
|
|
162
267
|
const resourceUris = {};
|
|
@@ -174,23 +279,21 @@ async function handleSearchComponents(input) {
|
|
|
174
279
|
description: component.description,
|
|
175
280
|
category: "component",
|
|
176
281
|
availableDocs: component.files,
|
|
177
|
-
resourceUris
|
|
282
|
+
resourceUris,
|
|
283
|
+
tier,
|
|
284
|
+
matchedTerms
|
|
178
285
|
});
|
|
179
286
|
}
|
|
180
287
|
}
|
|
181
288
|
}
|
|
182
289
|
if (iconsIndex) {
|
|
183
290
|
for (const icon of iconsIndex.icons) {
|
|
184
|
-
const matches
|
|
291
|
+
const { matches, tier, matchedTerms } = checkMatches(icon);
|
|
185
292
|
if (matches) {
|
|
186
293
|
const shortName = icon.tagName.replace(/^gds-/, "");
|
|
187
294
|
const resourceUris = {};
|
|
188
295
|
for (const docType of icon.files) {
|
|
189
|
-
resourceUris[docType] = buildResourceUri(
|
|
190
|
-
"icons",
|
|
191
|
-
shortName,
|
|
192
|
-
docType
|
|
193
|
-
);
|
|
296
|
+
resourceUris[docType] = buildResourceUri("icons", shortName, docType);
|
|
194
297
|
}
|
|
195
298
|
results.push({
|
|
196
299
|
name: icon.name,
|
|
@@ -199,19 +302,23 @@ async function handleSearchComponents(input) {
|
|
|
199
302
|
description: icon.description,
|
|
200
303
|
category: "icon",
|
|
201
304
|
availableDocs: icon.files,
|
|
202
|
-
resourceUris
|
|
305
|
+
resourceUris,
|
|
306
|
+
tier,
|
|
307
|
+
matchedTerms
|
|
203
308
|
});
|
|
204
309
|
}
|
|
205
310
|
}
|
|
206
311
|
}
|
|
207
312
|
results.sort((a, b) => {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
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;
|
|
212
316
|
return a.tagName.localeCompare(b.tagName);
|
|
213
317
|
});
|
|
214
|
-
const limitedResults = results.slice(0,
|
|
318
|
+
const limitedResults = results.slice(0, maxResults).map((r) => {
|
|
319
|
+
const { tier, matchedTerms, ...result } = r;
|
|
320
|
+
return result;
|
|
321
|
+
});
|
|
215
322
|
return {
|
|
216
323
|
content: [
|
|
217
324
|
{
|
|
@@ -408,6 +515,33 @@ ${content}`
|
|
|
408
515
|
throw new Error(`Failed to get guide: ${error}`);
|
|
409
516
|
}
|
|
410
517
|
}
|
|
518
|
+
async function handleGetInstructions() {
|
|
519
|
+
try {
|
|
520
|
+
const globalIndex = await loadGlobalIndex();
|
|
521
|
+
if (!globalIndex) {
|
|
522
|
+
throw new Error("Failed to load global index");
|
|
523
|
+
}
|
|
524
|
+
if (!globalIndex.instructions) {
|
|
525
|
+
throw new Error(
|
|
526
|
+
"Instructions not available. The MCP may not have been generated with instructions support."
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const content = await readMcpFile("INSTRUCTIONS.md");
|
|
530
|
+
if (!content) {
|
|
531
|
+
throw new Error("Instructions file not found");
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
content: [
|
|
535
|
+
{
|
|
536
|
+
type: "text",
|
|
537
|
+
text: content
|
|
538
|
+
}
|
|
539
|
+
]
|
|
540
|
+
};
|
|
541
|
+
} catch (error) {
|
|
542
|
+
throw new Error(`Failed to get instructions: ${error}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
411
545
|
export {
|
|
412
546
|
setupToolHandlers
|
|
413
547
|
};
|
|
@@ -59,6 +59,7 @@ export interface IconsIndex {
|
|
|
59
59
|
export interface GlobalIndex {
|
|
60
60
|
version: string;
|
|
61
61
|
generatedAt: string;
|
|
62
|
+
instructions?: string;
|
|
62
63
|
components: string;
|
|
63
64
|
icons: string;
|
|
64
65
|
guides: GuideEntry[];
|
|
@@ -95,6 +96,10 @@ export interface SearchResult {
|
|
|
95
96
|
export interface SearchComponentsInput {
|
|
96
97
|
query: string;
|
|
97
98
|
category?: Category;
|
|
99
|
+
splitTerms?: boolean;
|
|
100
|
+
matchAll?: boolean;
|
|
101
|
+
useRegex?: boolean;
|
|
102
|
+
maxResults?: number;
|
|
98
103
|
}
|
|
99
104
|
export interface GetComponentDocsInput {
|
|
100
105
|
componentName: string;
|