@sebgroup/green-core 2.33.0 → 2.34.0
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 +24 -0
- package/bin/context-cli/framework.d.ts +3 -0
- package/bin/context-cli/framework.js +23 -0
- package/bin/context-cli/index.d.ts +2 -0
- package/bin/context-cli/index.js +363 -0
- package/bin/context-cli/parse-args.d.ts +22 -0
- package/bin/context-cli/parse-args.js +46 -0
- package/bin/mcp-server/errors.d.ts +2 -7
- package/bin/mcp-server/handlers.d.ts +86 -0
- package/bin/mcp-server/handlers.js +356 -0
- package/bin/mcp-server/search.js +8 -1
- package/bin/mcp-server/tools.d.ts +13 -1
- package/bin/mcp-server/tools.js +8 -303
- package/components/table/table.component.js +2 -2
- package/custom-elements.json +3182 -2916
- package/gds-element.js +1 -1
- 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 +3 -2
- package/utils/helpers/custom-element-scoping.js +1 -1
- package/utils/testing/index.js +6 -2
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import "../../chunks/chunk.QU3DSPNU.js";
|
|
2
|
+
import { capitalize } from "../../utils/helpers/casing.js";
|
|
3
|
+
import { DOC_TYPES, PATHS, SEARCH_CONFIG, URI_SCHEME } from "./constants.js";
|
|
4
|
+
import { NotFoundError } from "./errors.js";
|
|
5
|
+
import { parseSearchQuery, performSearch } from "./search.js";
|
|
6
|
+
import {
|
|
7
|
+
buildResourceUri,
|
|
8
|
+
findComponent,
|
|
9
|
+
findIcon,
|
|
10
|
+
loadComponentsIndex,
|
|
11
|
+
loadGlobalIndex,
|
|
12
|
+
loadIconsIndex,
|
|
13
|
+
parseResourceUri,
|
|
14
|
+
readMcpFile
|
|
15
|
+
} from "./utils.js";
|
|
16
|
+
import {
|
|
17
|
+
validateGetComponentDocsInput,
|
|
18
|
+
validateGetGuideInput,
|
|
19
|
+
validateListGuidesInput,
|
|
20
|
+
validateSearchComponentsInput
|
|
21
|
+
} from "./validation.js";
|
|
22
|
+
async function handleSearchComponents(input) {
|
|
23
|
+
const validatedInput = validateSearchComponentsInput(input);
|
|
24
|
+
const {
|
|
25
|
+
query,
|
|
26
|
+
category = "all",
|
|
27
|
+
splitTerms = true,
|
|
28
|
+
matchAll = false,
|
|
29
|
+
useRegex = false,
|
|
30
|
+
maxResults = SEARCH_CONFIG.DEFAULT_MAX_RESULTS
|
|
31
|
+
} = validatedInput;
|
|
32
|
+
const loadComponents = category === "component" || category === "all";
|
|
33
|
+
const loadIcons = category === "icon" || category === "all";
|
|
34
|
+
const [componentsIndex, iconsIndex] = await Promise.all([
|
|
35
|
+
loadComponents ? loadComponentsIndex() : Promise.resolve(null),
|
|
36
|
+
loadIcons ? loadIconsIndex() : Promise.resolve(null)
|
|
37
|
+
]);
|
|
38
|
+
const components = componentsIndex?.components || [];
|
|
39
|
+
const icons = iconsIndex?.icons || [];
|
|
40
|
+
const { searchTerms, regexPattern } = parseSearchQuery(
|
|
41
|
+
query,
|
|
42
|
+
splitTerms,
|
|
43
|
+
useRegex
|
|
44
|
+
);
|
|
45
|
+
const buildUris = (item, cat) => {
|
|
46
|
+
const shortName = item.tagName.replace(/^gds-/, "");
|
|
47
|
+
const resourceCategory = cat === "component" ? "components" : "icons";
|
|
48
|
+
const uris = {};
|
|
49
|
+
for (const docType of item.files) {
|
|
50
|
+
uris[docType] = buildResourceUri(resourceCategory, shortName, docType);
|
|
51
|
+
}
|
|
52
|
+
return uris;
|
|
53
|
+
};
|
|
54
|
+
const results = performSearch(
|
|
55
|
+
components,
|
|
56
|
+
icons,
|
|
57
|
+
query,
|
|
58
|
+
searchTerms,
|
|
59
|
+
regexPattern,
|
|
60
|
+
matchAll,
|
|
61
|
+
splitTerms,
|
|
62
|
+
maxResults,
|
|
63
|
+
buildUris
|
|
64
|
+
);
|
|
65
|
+
return {
|
|
66
|
+
content: [
|
|
67
|
+
{
|
|
68
|
+
type: "text",
|
|
69
|
+
text: JSON.stringify(
|
|
70
|
+
{
|
|
71
|
+
query,
|
|
72
|
+
resultCount: results.length,
|
|
73
|
+
results
|
|
74
|
+
},
|
|
75
|
+
null,
|
|
76
|
+
2
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function handleGetComponentDocs(input) {
|
|
83
|
+
const validatedInput = validateGetComponentDocsInput(input);
|
|
84
|
+
const {
|
|
85
|
+
componentName,
|
|
86
|
+
framework,
|
|
87
|
+
includeGuidelines = true,
|
|
88
|
+
includeInstructions = true
|
|
89
|
+
} = validatedInput;
|
|
90
|
+
const [componentsIndex, iconsIndex] = await Promise.all([
|
|
91
|
+
loadComponentsIndex(),
|
|
92
|
+
loadIconsIndex()
|
|
93
|
+
]);
|
|
94
|
+
if (!componentsIndex || !iconsIndex) {
|
|
95
|
+
throw new NotFoundError(
|
|
96
|
+
"Failed to load component indexes",
|
|
97
|
+
"index",
|
|
98
|
+
"components/icons"
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const component = findComponent(componentName, componentsIndex.components);
|
|
102
|
+
const icon = component ? null : findIcon(componentName, iconsIndex.icons);
|
|
103
|
+
const found = component || icon;
|
|
104
|
+
if (!found) {
|
|
105
|
+
throw new NotFoundError(
|
|
106
|
+
`Component not found: ${componentName}. Try using the search_components tool to find available components.`,
|
|
107
|
+
"component",
|
|
108
|
+
componentName
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
const shortName = found.tagName.replace(/^gds-/, "");
|
|
112
|
+
const sections = [];
|
|
113
|
+
let primaryDoc;
|
|
114
|
+
if (framework === "angular") {
|
|
115
|
+
primaryDoc = DOC_TYPES.ANGULAR;
|
|
116
|
+
} else if (framework === "react") {
|
|
117
|
+
primaryDoc = DOC_TYPES.REACT;
|
|
118
|
+
} else {
|
|
119
|
+
primaryDoc = DOC_TYPES.API;
|
|
120
|
+
}
|
|
121
|
+
sections.push(`# ${found.tagName} - ${capitalize(framework)}`);
|
|
122
|
+
sections.push("");
|
|
123
|
+
if (framework === "angular" || framework === "react") {
|
|
124
|
+
sections.push(`\u26A0\uFE0F **${capitalize(framework)}-Specific Documentation**`);
|
|
125
|
+
sections.push(
|
|
126
|
+
`The import paths and syntax below are for ${capitalize(framework)} applications.`
|
|
127
|
+
);
|
|
128
|
+
sections.push("");
|
|
129
|
+
}
|
|
130
|
+
if (found.files.includes(primaryDoc)) {
|
|
131
|
+
const content = await readMcpFile(`${shortName}/${primaryDoc}.md`);
|
|
132
|
+
if (content) {
|
|
133
|
+
const contentWithoutTitle = content.replace(/^#\s+.*?\n/, "");
|
|
134
|
+
sections.push(contentWithoutTitle);
|
|
135
|
+
sections.push("");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if ((framework === "angular" || framework === "react") && found.files.includes(DOC_TYPES.API)) {
|
|
139
|
+
const apiContent = await readMcpFile(`${shortName}/${DOC_TYPES.API}.md`);
|
|
140
|
+
if (apiContent) {
|
|
141
|
+
sections.push("---");
|
|
142
|
+
sections.push("");
|
|
143
|
+
sections.push("## Component API Reference");
|
|
144
|
+
sections.push("");
|
|
145
|
+
sections.push(
|
|
146
|
+
"The following properties, events, slots, and methods are available:"
|
|
147
|
+
);
|
|
148
|
+
sections.push("");
|
|
149
|
+
const apiWithoutHeader = apiContent.replace(/^#\s+.*?\n/, "").replace(/\*\*Class\*\*:.*?\n/, "").replace(/\*\*Tag\*\*:.*?\n/, "").trim();
|
|
150
|
+
sections.push(apiWithoutHeader);
|
|
151
|
+
sections.push("");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (includeGuidelines && found.files.includes(DOC_TYPES.GUIDELINES)) {
|
|
155
|
+
const guidelines = await readMcpFile(
|
|
156
|
+
`${shortName}/${DOC_TYPES.GUIDELINES}.md`
|
|
157
|
+
);
|
|
158
|
+
if (guidelines) {
|
|
159
|
+
sections.push("---");
|
|
160
|
+
sections.push("");
|
|
161
|
+
sections.push("## Design Guidelines");
|
|
162
|
+
sections.push("");
|
|
163
|
+
sections.push(guidelines);
|
|
164
|
+
sections.push("");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (includeInstructions && found.files.includes(DOC_TYPES.INSTRUCTIONS)) {
|
|
168
|
+
const instructions = await readMcpFile(
|
|
169
|
+
`${shortName}/${DOC_TYPES.INSTRUCTIONS}.md`
|
|
170
|
+
);
|
|
171
|
+
if (instructions) {
|
|
172
|
+
sections.push("---");
|
|
173
|
+
sections.push("");
|
|
174
|
+
sections.push("## Usage Instructions");
|
|
175
|
+
sections.push("");
|
|
176
|
+
sections.push(instructions);
|
|
177
|
+
sections.push("");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
sections.push("---");
|
|
181
|
+
sections.push("");
|
|
182
|
+
sections.push("\u{1F4A1} **Using a different framework?**");
|
|
183
|
+
sections.push("Call this tool again with:");
|
|
184
|
+
if (framework !== "angular")
|
|
185
|
+
sections.push('- `framework: "angular"` for Angular documentation');
|
|
186
|
+
if (framework !== "react")
|
|
187
|
+
sections.push('- `framework: "react"` for React documentation');
|
|
188
|
+
if (framework !== "web-component")
|
|
189
|
+
sections.push('- `framework: "web-component"` for vanilla JS usage');
|
|
190
|
+
return {
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: "text",
|
|
194
|
+
text: sections.join("\n")
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async function handleListGuides(input) {
|
|
200
|
+
const validatedInput = validateListGuidesInput(input);
|
|
201
|
+
const { category = "all", framework } = validatedInput;
|
|
202
|
+
const globalIndex = await loadGlobalIndex();
|
|
203
|
+
if (!globalIndex) {
|
|
204
|
+
throw new NotFoundError("Failed to load global index", "index", "global");
|
|
205
|
+
}
|
|
206
|
+
let guides = globalIndex.guides;
|
|
207
|
+
if (category !== "all") {
|
|
208
|
+
guides = guides.filter((g) => g.category === category);
|
|
209
|
+
}
|
|
210
|
+
if (framework && framework !== "all") {
|
|
211
|
+
guides = guides.filter((g) => g.tags.includes(framework));
|
|
212
|
+
}
|
|
213
|
+
const guidesWithUris = guides.map((guide) => {
|
|
214
|
+
const name = guide.path.replace(/^(guides|concepts)\//, "").replace(/\.md$/, "");
|
|
215
|
+
const guideCategory = guide.path.startsWith("guides/") ? "guides" : "concepts";
|
|
216
|
+
const uri = buildResourceUri(guideCategory, name);
|
|
217
|
+
return {
|
|
218
|
+
title: guide.title,
|
|
219
|
+
category: guide.category,
|
|
220
|
+
description: guide.description,
|
|
221
|
+
tags: guide.tags,
|
|
222
|
+
resourceUri: uri
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
return {
|
|
226
|
+
content: [
|
|
227
|
+
{
|
|
228
|
+
type: "text",
|
|
229
|
+
text: JSON.stringify(
|
|
230
|
+
{
|
|
231
|
+
guideCount: guidesWithUris.length,
|
|
232
|
+
guides: guidesWithUris
|
|
233
|
+
},
|
|
234
|
+
null,
|
|
235
|
+
2
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
]
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
async function handleGetGuide(input) {
|
|
242
|
+
const validatedInput = validateGetGuideInput(input);
|
|
243
|
+
const { name } = validatedInput;
|
|
244
|
+
const globalIndex = await loadGlobalIndex();
|
|
245
|
+
if (!globalIndex) {
|
|
246
|
+
throw new NotFoundError("Failed to load global index", "index", "global");
|
|
247
|
+
}
|
|
248
|
+
const guide = globalIndex.guides.find((g) => {
|
|
249
|
+
const guideName = g.path.replace(/^(guides|concepts)\//, "").replace(/\.md$/, "");
|
|
250
|
+
return guideName === name;
|
|
251
|
+
});
|
|
252
|
+
if (!guide) {
|
|
253
|
+
throw new NotFoundError(
|
|
254
|
+
`Guide not found: ${name}. Use list_guides to see available guides.`,
|
|
255
|
+
"guide",
|
|
256
|
+
name
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const content = await readMcpFile(guide.path);
|
|
260
|
+
if (!content) {
|
|
261
|
+
throw new NotFoundError(
|
|
262
|
+
`Guide file not found: ${guide.path}`,
|
|
263
|
+
"file",
|
|
264
|
+
guide.path
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
type: "text",
|
|
271
|
+
text: `# ${guide.title}
|
|
272
|
+
|
|
273
|
+
${content}`
|
|
274
|
+
}
|
|
275
|
+
]
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
async function handleGetInstructions() {
|
|
279
|
+
const globalIndex = await loadGlobalIndex();
|
|
280
|
+
if (!globalIndex) {
|
|
281
|
+
throw new NotFoundError("Failed to load global index", "index", "global");
|
|
282
|
+
}
|
|
283
|
+
if (!globalIndex.instructions) {
|
|
284
|
+
throw new NotFoundError(
|
|
285
|
+
"Instructions not available. The MCP may not have been generated with instructions support.",
|
|
286
|
+
"file",
|
|
287
|
+
"INSTRUCTIONS.md"
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
const content = await readMcpFile("INSTRUCTIONS.md");
|
|
291
|
+
if (!content) {
|
|
292
|
+
throw new NotFoundError(
|
|
293
|
+
"Instructions file not found",
|
|
294
|
+
"file",
|
|
295
|
+
"INSTRUCTIONS.md"
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
content: [
|
|
300
|
+
{
|
|
301
|
+
type: "text",
|
|
302
|
+
text: content
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
async function handleResolveUri(uri) {
|
|
308
|
+
if (uri === URI_SCHEME.INSTRUCTIONS) {
|
|
309
|
+
const content2 = await readMcpFile(PATHS.INSTRUCTIONS_FILE);
|
|
310
|
+
if (!content2) {
|
|
311
|
+
throw new NotFoundError(
|
|
312
|
+
"Instructions file not found",
|
|
313
|
+
"file",
|
|
314
|
+
PATHS.INSTRUCTIONS_FILE
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
content: [{ type: "text", text: content2 }]
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const parsed = parseResourceUri(uri);
|
|
322
|
+
if (!parsed) {
|
|
323
|
+
throw new NotFoundError(`Invalid resource URI format: ${uri}`, "uri", uri);
|
|
324
|
+
}
|
|
325
|
+
const { category, name, docType } = parsed;
|
|
326
|
+
let filePath;
|
|
327
|
+
if (category === "components" || category === "icons") {
|
|
328
|
+
if (!docType) {
|
|
329
|
+
throw new NotFoundError(
|
|
330
|
+
`Document type required for ${category} URIs (e.g. green://${category}/${name}/api)`,
|
|
331
|
+
"docType",
|
|
332
|
+
uri
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
filePath = `${name}/${docType}.md`;
|
|
336
|
+
} else if (category === "guides" || category === "concepts") {
|
|
337
|
+
filePath = `${category}/${name}.md`;
|
|
338
|
+
} else {
|
|
339
|
+
throw new NotFoundError(`Unknown category: ${category}`, "category", uri);
|
|
340
|
+
}
|
|
341
|
+
const content = await readMcpFile(filePath);
|
|
342
|
+
if (!content) {
|
|
343
|
+
throw new NotFoundError(`Resource not found: ${uri}`, "file", filePath);
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
content: [{ type: "text", text: content }]
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
export {
|
|
350
|
+
handleGetComponentDocs,
|
|
351
|
+
handleGetGuide,
|
|
352
|
+
handleGetInstructions,
|
|
353
|
+
handleListGuides,
|
|
354
|
+
handleResolveUri,
|
|
355
|
+
handleSearchComponents
|
|
356
|
+
};
|
package/bin/mcp-server/search.js
CHANGED
|
@@ -9,6 +9,12 @@ function compileRegexPattern(pattern) {
|
|
|
9
9
|
{ maxLength: SEARCH_CONFIG.MAX_REGEX_LENGTH }
|
|
10
10
|
);
|
|
11
11
|
}
|
|
12
|
+
if (/([+*?]|\{\d+,?\d*\})\)*([+*?]|\{\d+,?\d*\})/.test(pattern)) {
|
|
13
|
+
throw new RegexError(
|
|
14
|
+
"Regex pattern contains nested quantifiers which could cause catastrophic backtracking",
|
|
15
|
+
pattern
|
|
16
|
+
);
|
|
17
|
+
}
|
|
12
18
|
try {
|
|
13
19
|
return new RegExp(pattern, "i");
|
|
14
20
|
} catch (error) {
|
|
@@ -75,7 +81,8 @@ function checkMultiTermMatch(item, searchTerms, matchAll) {
|
|
|
75
81
|
}
|
|
76
82
|
function parseSearchQuery(query, splitTerms, useRegex) {
|
|
77
83
|
if (useRegex) {
|
|
78
|
-
const
|
|
84
|
+
const pattern = query.startsWith("/") && query.endsWith("/") ? query.slice(1, -1) : query;
|
|
85
|
+
const regexPattern = compileRegexPattern(pattern);
|
|
79
86
|
return { searchTerms: [query], regexPattern };
|
|
80
87
|
}
|
|
81
88
|
if (splitTerms) {
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool registration for the Green Design System MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* This module wires MCP protocol schemas to shared handler functions.
|
|
5
|
+
* The actual business logic lives in ./handlers.ts so it can be reused
|
|
6
|
+
* by other consumers (e.g. the context CLI).
|
|
7
|
+
*
|
|
8
|
+
* @module tools
|
|
9
|
+
*/
|
|
1
10
|
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
11
|
/**
|
|
3
|
-
* Register tool handlers on the MCP server
|
|
12
|
+
* Register tool handlers on the MCP server.
|
|
13
|
+
*
|
|
14
|
+
* Defines the MCP tool schemas (names, descriptions, input schemas) and
|
|
15
|
+
* delegates each tool call to the corresponding shared handler function.
|
|
4
16
|
*/
|
|
5
17
|
export declare function setupToolHandlers(server: Server): void;
|