@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.
@@ -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}`);
@@ -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', 'dropdown', 'form input', 'arrow icon')"
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 { query, category = "all" } = input;
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 = component.tagName.toLowerCase().includes(queryLower) || component.name.toLowerCase().includes(queryLower) || component.className.toLowerCase().includes(queryLower) || component.description?.toLowerCase().includes(queryLower);
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 = icon.tagName.toLowerCase().includes(queryLower) || icon.name.toLowerCase().includes(queryLower) || icon.className.toLowerCase().includes(queryLower) || icon.description?.toLowerCase().includes(queryLower);
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
- const aExact = a.tagName.toLowerCase() === queryLower;
209
- const bExact = b.tagName.toLowerCase() === queryLower;
210
- if (aExact && !bExact) return -1;
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, 20);
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;