@iress-oss/ids-mcp-server 0.0.1-dev.5 → 0.0.1-dev.7

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.
Files changed (36) hide show
  1. package/dist/componentHandlers.js +241 -0
  2. package/dist/componentHandlers.test.js +380 -0
  3. package/dist/config.js +16 -0
  4. package/dist/index.js +53 -0
  5. package/dist/iressHandlers.js +144 -0
  6. package/dist/iressHandlers.test.js +316 -0
  7. package/dist/resourceHandlers.js +67 -0
  8. package/dist/resourceHandlers.test.js +352 -0
  9. package/dist/searchHandlers.js +287 -0
  10. package/dist/searchHandlers.test.js +524 -0
  11. package/dist/toolHandler.js +31 -0
  12. package/dist/toolHandler.test.js +369 -0
  13. package/dist/tools.js +165 -0
  14. package/dist/types.js +4 -0
  15. package/dist/utils.js +59 -0
  16. package/dist/utils.test.js +286 -0
  17. package/generated/docs/components-autocomplete-docs.md +47 -3
  18. package/generated/docs/components-checkboxgroup-docs.md +17 -17
  19. package/generated/docs/components-col-docs.md +1 -1
  20. package/generated/docs/components-combobox-docs.md +4 -4
  21. package/generated/docs/components-filter-docs.md +3 -3
  22. package/generated/docs/components-form-docs.md +7 -40
  23. package/generated/docs/components-icon-docs.md +4 -4
  24. package/generated/docs/components-inputcurrency-docs.md +4 -47
  25. package/generated/docs/components-radiogroup-docs.md +21 -21
  26. package/generated/docs/components-richselect-docs.md +322 -1
  27. package/generated/docs/components-row-docs.md +4 -4
  28. package/generated/docs/components-skiplink-docs.md +1 -1
  29. package/generated/docs/components-table-ag-grid-docs.md +104 -1696
  30. package/generated/docs/components-table-docs.md +6 -6
  31. package/generated/docs/components-tabset-docs.md +28 -0
  32. package/generated/docs/extensions-editor-docs.md +12 -6
  33. package/generated/docs/introduction-docs.md +1 -1
  34. package/generated/docs/patterns-loading-docs.md +2 -2
  35. package/generated/docs/themes-available-themes-docs.md +29 -29
  36. package/package.json +14 -5
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Tool handlers for component-related operations
3
+ */
4
+ import { z } from 'zod';
5
+ import * as path from 'path';
6
+ import { getMarkdownFiles, mapIressComponentToFile, extractIressComponents, readFileContent, } from './utils.js';
7
+ import { DOCS_DIR } from './config.js';
8
+ function checkIressComponentMatch(query) {
9
+ if (query.startsWith('Iress')) {
10
+ const componentFile = mapIressComponentToFile(query);
11
+ if (componentFile) {
12
+ return {
13
+ content: [
14
+ {
15
+ type: 'text',
16
+ text: `Found exact match for **${query}**:\n\n**${componentFile}**\n ${query} component documentation\n\n*Use \`get_iress_component_info\` with "${query}" for detailed information.*`,
17
+ },
18
+ ],
19
+ };
20
+ }
21
+ }
22
+ return null;
23
+ }
24
+ function calculateRelevanceScore(file, content, query, lines) {
25
+ let relevanceScore = 0;
26
+ const queryLower = query.toLowerCase();
27
+ const filenameLower = file.toLowerCase();
28
+ // Check for Iress component mentions in content
29
+ const iressComponents = extractIressComponents(content);
30
+ const hasMatchingIressComponent = iressComponents.some((comp) => comp.toLowerCase().includes(queryLower) ||
31
+ queryLower.includes(comp.toLowerCase().replace('iress', '')));
32
+ if (hasMatchingIressComponent) {
33
+ relevanceScore += 75;
34
+ }
35
+ // High relevance for filename matches
36
+ if (filenameLower.includes(queryLower)) {
37
+ relevanceScore += 100;
38
+ }
39
+ // Medium relevance for title matches
40
+ const title = lines.find((line) => line.startsWith('#'))?.replace(/^#+\s*/, '') ?? '';
41
+ if (title.toLowerCase().includes(queryLower)) {
42
+ relevanceScore += 50;
43
+ }
44
+ // Lower relevance for content matches
45
+ const contentMatches = content.toLowerCase().split(queryLower).length - 1;
46
+ relevanceScore += contentMatches * 2;
47
+ return relevanceScore;
48
+ }
49
+ function createSearchResult(file, relevanceScore, lines) {
50
+ const componentName = file
51
+ .replace(/^components-/, '')
52
+ .replace('-docs.md', '');
53
+ const description = lines
54
+ .slice(0, 10)
55
+ .find((line) => line.trim() && !line.startsWith('#') && !line.startsWith('<!--'))
56
+ ?.trim() ?? 'IDS component documentation';
57
+ return {
58
+ file,
59
+ relevance: relevanceScore,
60
+ description: `${componentName}: ${description}`,
61
+ };
62
+ }
63
+ export function handleFindComponent(args) {
64
+ const schema = z.object({
65
+ query: z.string(),
66
+ category: z.enum(['components', 'foundations', 'resources']).optional(),
67
+ });
68
+ const { query, category } = schema.parse(args);
69
+ // Check if query is an Iress component name
70
+ const iressMatch = checkIressComponentMatch(query);
71
+ if (iressMatch) {
72
+ return iressMatch;
73
+ }
74
+ const markdownFiles = getMarkdownFiles();
75
+ // Filter files by category if specified
76
+ const filteredFiles = category
77
+ ? markdownFiles.filter((file) => file.startsWith(`${category}-`))
78
+ : markdownFiles;
79
+ const results = [];
80
+ for (const file of filteredFiles) {
81
+ try {
82
+ const filePath = path.join(DOCS_DIR, file);
83
+ const content = readFileContent(filePath);
84
+ const lines = content.split('\n');
85
+ const relevanceScore = calculateRelevanceScore(file, content, query, lines);
86
+ if (relevanceScore > 0) {
87
+ results.push(createSearchResult(file, relevanceScore, lines));
88
+ }
89
+ }
90
+ catch (error) {
91
+ console.error(`Error reading file ${file}:`, error);
92
+ }
93
+ }
94
+ // Sort by relevance
95
+ results.sort((a, b) => b.relevance - a.relevance);
96
+ return {
97
+ content: [
98
+ {
99
+ type: 'text',
100
+ text: results.length > 0
101
+ ? `Found ${results.length} relevant IDS components:\n\n${results
102
+ .slice(0, 10) // Limit to top 10 results
103
+ .map((r, index) => `${index + 1}. **${r.file}**\n ${r.description}`)
104
+ .join('\n\n')}`
105
+ : `No IDS components found matching "${query}". Try searching for common component names like "button", "input", "table", or "modal".`,
106
+ },
107
+ ],
108
+ };
109
+ }
110
+ function findComponentFile(component, markdownFiles) {
111
+ return markdownFiles.find((file) => file.includes(`components-${component.toLowerCase()}`) ||
112
+ file.toLowerCase().includes(component.toLowerCase()));
113
+ }
114
+ function formatAvailableComponents(markdownFiles) {
115
+ return markdownFiles
116
+ .filter((f) => f.startsWith('components-'))
117
+ .map((f) => `- ${f.replace('components-', '').replace('-docs.md', '')}`)
118
+ .join('\n');
119
+ }
120
+ function isPropsRelatedLine(line) {
121
+ return (line.includes('mode') ||
122
+ line.includes('prop') ||
123
+ line.includes('Properties') ||
124
+ line.includes('API') ||
125
+ line.includes('Examples'));
126
+ }
127
+ function extractPropSections(content) {
128
+ const lines = content.split('\n');
129
+ const propSections = [];
130
+ let inPropsSection = false;
131
+ let currentSection = '';
132
+ for (const line of lines) {
133
+ if (isPropsRelatedLine(line)) {
134
+ if (currentSection) {
135
+ propSections.push(currentSection);
136
+ }
137
+ currentSection = line + '\n';
138
+ inPropsSection = true;
139
+ }
140
+ else if (inPropsSection && line.trim()) {
141
+ currentSection += line + '\n';
142
+ }
143
+ else if (inPropsSection && !line.trim()) {
144
+ if (currentSection) {
145
+ propSections.push(currentSection);
146
+ currentSection = '';
147
+ }
148
+ inPropsSection = false;
149
+ }
150
+ }
151
+ if (currentSection) {
152
+ propSections.push(currentSection);
153
+ }
154
+ return propSections;
155
+ }
156
+ function formatPropsResponse(propSections, content, componentFile) {
157
+ return propSections.length > 0
158
+ ? propSections.join('\n---\n\n')
159
+ : `Props information extracted from ${componentFile}:\n\n${content.slice(0, 2000)}...`;
160
+ }
161
+ export function handleGetComponentProps(args) {
162
+ const schema = z.object({
163
+ component: z.string(),
164
+ });
165
+ const { component } = schema.parse(args);
166
+ const markdownFiles = getMarkdownFiles();
167
+ const componentFile = findComponentFile(component, markdownFiles);
168
+ if (!componentFile) {
169
+ return {
170
+ content: [
171
+ {
172
+ type: 'text',
173
+ text: `Component "${component}" not found. Available components:\n${formatAvailableComponents(markdownFiles)}`,
174
+ },
175
+ ],
176
+ };
177
+ }
178
+ try {
179
+ const filePath = path.join(DOCS_DIR, componentFile);
180
+ const content = readFileContent(filePath);
181
+ const propSections = extractPropSections(content);
182
+ return {
183
+ content: [
184
+ {
185
+ type: 'text',
186
+ text: `**${component} Component Props & API**\n\n${formatPropsResponse(propSections, content, componentFile)}`,
187
+ },
188
+ ],
189
+ };
190
+ }
191
+ catch (error) {
192
+ throw new Error(`Failed to read component file ${componentFile}: ${error instanceof Error ? error.message : String(error)}`);
193
+ }
194
+ }
195
+ export function handleListComponents(args) {
196
+ const schema = z.object({
197
+ category: z
198
+ .enum(['components', 'foundations', 'resources', 'all'])
199
+ .default('all'),
200
+ });
201
+ const { category } = schema.parse(args);
202
+ const markdownFiles = getMarkdownFiles();
203
+ const categorized = {
204
+ components: markdownFiles
205
+ .filter((f) => f.startsWith('components-'))
206
+ .map((f) => f.replace('components-', '').replace('-docs.md', '')),
207
+ foundations: markdownFiles
208
+ .filter((f) => f.startsWith('foundations-'))
209
+ .map((f) => f.replace('foundations-', '').replace('-docs.md', '')),
210
+ resources: markdownFiles
211
+ .filter((f) => f.startsWith('resources-'))
212
+ .map((f) => f.replace('resources-', '').replace('-docs.md', '')),
213
+ };
214
+ let output = '**IDS Component Library**\n\n';
215
+ if (category === 'all') {
216
+ const componentsList = categorized.components
217
+ .map((c) => `- ${c}`)
218
+ .join('\n');
219
+ const foundationsList = categorized.foundations
220
+ .map((c) => `- ${c}`)
221
+ .join('\n');
222
+ const resourcesList = categorized.resources.map((c) => `- ${c}`).join('\n');
223
+ output += `**Components (${categorized.components.length})**\n${componentsList}\n\n`;
224
+ output += `**Foundations (${categorized.foundations.length})**\n${foundationsList}\n\n`;
225
+ output += `**Resources (${categorized.resources.length})**\n${resourcesList}`;
226
+ }
227
+ else {
228
+ const items = categorized[category];
229
+ const itemsList = items.map((c) => `- ${c}`).join('\n');
230
+ const categoryTitle = category.charAt(0).toUpperCase() + category.slice(1);
231
+ output += `**${categoryTitle} (${items.length})**\n${itemsList}`;
232
+ }
233
+ return {
234
+ content: [
235
+ {
236
+ type: 'text',
237
+ text: output,
238
+ },
239
+ ],
240
+ };
241
+ }
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Tests for component handlers
3
+ */
4
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
5
+ import { handleFindComponent, handleGetComponentProps, handleListComponents, } from './componentHandlers.js';
6
+ import * as utils from './utils.js';
7
+ // Mock the utils module
8
+ vi.mock('./utils.js', () => ({
9
+ getMarkdownFiles: vi.fn(),
10
+ mapIressComponentToFile: vi.fn(),
11
+ extractIressComponents: vi.fn(),
12
+ readFileContent: vi.fn(),
13
+ }));
14
+ // Mock the config module
15
+ vi.mock('./config.js', () => ({
16
+ DOCS_DIR: '/mocked/docs/path',
17
+ }));
18
+ const mockUtils = vi.mocked(utils);
19
+ describe('componentHandlers', () => {
20
+ beforeEach(() => {
21
+ vi.clearAllMocks();
22
+ });
23
+ afterEach(() => {
24
+ vi.restoreAllMocks();
25
+ });
26
+ describe('handleFindComponent', () => {
27
+ const mockMarkdownFiles = [
28
+ 'components-button-docs.md',
29
+ 'components-input-docs.md',
30
+ 'components-table-docs.md',
31
+ 'foundations-colors-docs.md',
32
+ 'resources-icons-docs.md',
33
+ ];
34
+ const mockButtonContent = `# Button Component
35
+
36
+ A versatile button component for user interactions.
37
+
38
+ ## Props
39
+ - variant: string - Button style variant
40
+ - size: string - Button size
41
+
42
+ ## Examples
43
+ \`\`\`jsx
44
+ <Button variant="primary">Click me</Button>
45
+ \`\`\`
46
+ `;
47
+ it('should find exact match for Iress component', () => {
48
+ const args = {
49
+ query: 'IressButton',
50
+ };
51
+ mockUtils.mapIressComponentToFile.mockReturnValue('components-button-docs.md');
52
+ const result = handleFindComponent(args);
53
+ expect(mockUtils.mapIressComponentToFile).toHaveBeenCalledWith('IressButton');
54
+ expect(result.content).toHaveLength(1);
55
+ expect(result.content[0].type).toBe('text');
56
+ expect(result.content[0].text).toContain('Found exact match for **IressButton**');
57
+ expect(result.content[0].text).toContain('components-button-docs.md');
58
+ expect(result.content[0].text).toContain('Use `get_iress_component_info` with "IressButton"');
59
+ });
60
+ it('should return null for non-matching Iress component', () => {
61
+ const args = {
62
+ query: 'IressNonExistent',
63
+ };
64
+ mockUtils.mapIressComponentToFile.mockReturnValue(null);
65
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
66
+ mockUtils.readFileContent.mockReturnValue('No relevant content');
67
+ mockUtils.extractIressComponents.mockReturnValue([]);
68
+ const result = handleFindComponent(args);
69
+ expect(result.content[0].text).toContain('No IDS components found matching "IressNonExistent"');
70
+ });
71
+ it('should search and rank components by relevance', () => {
72
+ const args = {
73
+ query: 'button',
74
+ };
75
+ mockUtils.mapIressComponentToFile.mockReturnValue(null);
76
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
77
+ mockUtils.extractIressComponents.mockReturnValue([]);
78
+ // Mock file content with different relevance scores
79
+ mockUtils.readFileContent.mockImplementation((filePath) => {
80
+ if (filePath.includes('button')) {
81
+ return mockButtonContent;
82
+ }
83
+ return 'Some other component content';
84
+ });
85
+ const result = handleFindComponent(args);
86
+ expect(result.content[0].text).toContain('Found');
87
+ expect(result.content[0].text).toContain('relevant IDS components');
88
+ expect(result.content[0].text).toContain('button-docs.md');
89
+ });
90
+ it('should filter by category when specified', () => {
91
+ const args = {
92
+ query: 'component',
93
+ category: 'components',
94
+ };
95
+ mockUtils.mapIressComponentToFile.mockReturnValue(null);
96
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
97
+ mockUtils.extractIressComponents.mockReturnValue([]);
98
+ mockUtils.readFileContent.mockReturnValue('Component content');
99
+ handleFindComponent(args);
100
+ // Should only search components category files
101
+ expect(mockUtils.readFileContent).toHaveBeenCalledWith('/mocked/docs/path/components-button-docs.md');
102
+ expect(mockUtils.readFileContent).toHaveBeenCalledWith('/mocked/docs/path/components-input-docs.md');
103
+ expect(mockUtils.readFileContent).not.toHaveBeenCalledWith('/mocked/docs/path/foundations-colors-docs.md');
104
+ });
105
+ it('should handle Iress component mentions in content', () => {
106
+ const args = {
107
+ query: 'button',
108
+ };
109
+ mockUtils.mapIressComponentToFile.mockReturnValue(null);
110
+ mockUtils.getMarkdownFiles.mockReturnValue(['components-form-docs.md']);
111
+ mockUtils.extractIressComponents.mockReturnValue(['IressButton']);
112
+ mockUtils.readFileContent.mockReturnValue('This form uses IressButton component');
113
+ const result = handleFindComponent(args);
114
+ expect(result.content[0].text).toContain('relevant IDS components');
115
+ });
116
+ it('should validate input parameters using zod schema', () => {
117
+ const invalidArgs = {
118
+ query: 123, // Should be string
119
+ };
120
+ expect(() => handleFindComponent(invalidArgs)).toThrow();
121
+ });
122
+ it('should validate category parameter using zod schema', () => {
123
+ const invalidArgs = {
124
+ query: 'button',
125
+ category: 'invalid-category', // Should be enum value
126
+ };
127
+ expect(() => handleFindComponent(invalidArgs)).toThrow();
128
+ });
129
+ it('should handle file reading errors gracefully', () => {
130
+ const args = {
131
+ query: 'button',
132
+ };
133
+ mockUtils.mapIressComponentToFile.mockReturnValue(null);
134
+ mockUtils.getMarkdownFiles.mockReturnValue(['components-button-docs.md']);
135
+ mockUtils.readFileContent.mockImplementation(() => {
136
+ throw new Error('File read error');
137
+ });
138
+ // Should not throw, but handle error gracefully
139
+ const result = handleFindComponent(args);
140
+ expect(result.content[0].text).toContain('No IDS components found matching "button"');
141
+ });
142
+ it('should limit results to top 10', () => {
143
+ const args = {
144
+ query: 'component',
145
+ };
146
+ const manyFiles = Array.from({ length: 15 }, (_, i) => `components-comp${i}-docs.md`);
147
+ mockUtils.mapIressComponentToFile.mockReturnValue(null);
148
+ mockUtils.getMarkdownFiles.mockReturnValue(manyFiles);
149
+ mockUtils.extractIressComponents.mockReturnValue([]);
150
+ mockUtils.readFileContent.mockReturnValue('component content');
151
+ const result = handleFindComponent(args);
152
+ // Count the number of component entries by splitting on numbered items
153
+ const entries = result.content[0].text.split(/\n\d+\. /).length - 1;
154
+ expect(entries).toBeLessThanOrEqual(10);
155
+ });
156
+ });
157
+ describe('handleGetComponentProps', () => {
158
+ const mockMarkdownFiles = [
159
+ 'components-button-docs.md',
160
+ 'components-input-docs.md',
161
+ ];
162
+ const mockComponentContent = `# Button Component
163
+
164
+ ## Overview
165
+ A button component
166
+
167
+ ## Props
168
+ - variant: string - The button variant
169
+ - size: string - The button size
170
+
171
+ Some text after props
172
+
173
+ ## API
174
+ Additional API methods
175
+
176
+ Some text after API
177
+
178
+ ## Examples
179
+ Usage examples here
180
+
181
+ Some text after examples
182
+ `;
183
+ it('should return component props when component exists', () => {
184
+ const args = {
185
+ component: 'button',
186
+ };
187
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
188
+ mockUtils.readFileContent.mockReturnValue(mockComponentContent);
189
+ const result = handleGetComponentProps(args);
190
+ expect(result.content).toHaveLength(1);
191
+ expect(result.content[0].type).toBe('text');
192
+ expect(result.content[0].text).toContain('**button Component Props & API**');
193
+ expect(result.content[0].text).toContain('## API');
194
+ expect(result.content[0].text).toContain('Additional API methods');
195
+ });
196
+ it('should return error when component not found', () => {
197
+ const args = {
198
+ component: 'nonexistent',
199
+ };
200
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
201
+ const result = handleGetComponentProps(args);
202
+ expect(result.content[0].text).toContain('Component "nonexistent" not found');
203
+ expect(result.content[0].text).toContain('Available components:');
204
+ expect(result.content[0].text).toContain('- button');
205
+ expect(result.content[0].text).toContain('- input');
206
+ });
207
+ it('should handle case-insensitive component matching', () => {
208
+ const args = {
209
+ component: 'BUTTON',
210
+ };
211
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
212
+ mockUtils.readFileContent.mockReturnValue(mockComponentContent);
213
+ const result = handleGetComponentProps(args);
214
+ expect(result.content[0].text).toContain('**BUTTON Component Props & API**');
215
+ });
216
+ it('should extract props from content with no specific props sections', () => {
217
+ const args = {
218
+ component: 'button',
219
+ };
220
+ const contentWithoutProps = `# Button Component
221
+ This is a simple button component without specific props sections.
222
+ Here is some general documentation.`;
223
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
224
+ mockUtils.readFileContent.mockReturnValue(contentWithoutProps);
225
+ const result = handleGetComponentProps(args);
226
+ expect(result.content[0].text).toContain('This is a simple button component');
227
+ });
228
+ it('should throw error when file reading fails', () => {
229
+ const args = {
230
+ component: 'button',
231
+ };
232
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
233
+ mockUtils.readFileContent.mockImplementation(() => {
234
+ throw new Error('File access denied');
235
+ });
236
+ expect(() => handleGetComponentProps(args)).toThrow('Failed to read component file components-button-docs.md: File access denied');
237
+ });
238
+ it('should validate input parameters using zod schema', () => {
239
+ const invalidArgs = {
240
+ component: 123, // Should be string
241
+ };
242
+ expect(() => handleGetComponentProps(invalidArgs)).toThrow();
243
+ });
244
+ it('should handle multiple prop-related sections', () => {
245
+ const contentWithMultipleSections = `# Button Component
246
+
247
+ ## Props
248
+ - variant: string
249
+
250
+ ## API
251
+ Methods available
252
+
253
+ ## Properties
254
+ Additional properties
255
+
256
+ ## mode
257
+ Different modes
258
+ `;
259
+ const args = {
260
+ component: 'button',
261
+ };
262
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
263
+ mockUtils.readFileContent.mockReturnValue(contentWithMultipleSections);
264
+ const result = handleGetComponentProps(args);
265
+ expect(result.content[0].text).toContain('Props');
266
+ expect(result.content[0].text).toContain('API');
267
+ expect(result.content[0].text).toContain('Properties');
268
+ expect(result.content[0].text).toContain('mode');
269
+ });
270
+ });
271
+ describe('handleListComponents', () => {
272
+ const mockMarkdownFiles = [
273
+ 'components-button-docs.md',
274
+ 'components-input-docs.md',
275
+ 'components-table-docs.md',
276
+ 'foundations-colors-docs.md',
277
+ 'foundations-typography-docs.md',
278
+ 'resources-icons-docs.md',
279
+ ];
280
+ it('should list all components by default', () => {
281
+ const args = {};
282
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
283
+ const result = handleListComponents(args);
284
+ expect(result.content).toHaveLength(1);
285
+ expect(result.content[0].type).toBe('text');
286
+ expect(result.content[0].text).toContain('**IDS Component Library**');
287
+ expect(result.content[0].text).toContain('**Components (3)**');
288
+ expect(result.content[0].text).toContain('- button');
289
+ expect(result.content[0].text).toContain('- input');
290
+ expect(result.content[0].text).toContain('- table');
291
+ expect(result.content[0].text).toContain('**Foundations (2)**');
292
+ expect(result.content[0].text).toContain('- colors');
293
+ expect(result.content[0].text).toContain('- typography');
294
+ expect(result.content[0].text).toContain('**Resources (1)**');
295
+ expect(result.content[0].text).toContain('- icons');
296
+ });
297
+ it('should list only components when category is "components"', () => {
298
+ const args = {
299
+ category: 'components',
300
+ };
301
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
302
+ const result = handleListComponents(args);
303
+ expect(result.content[0].text).toContain('**Components (3)**');
304
+ expect(result.content[0].text).toContain('- button');
305
+ expect(result.content[0].text).toContain('- input');
306
+ expect(result.content[0].text).toContain('- table');
307
+ expect(result.content[0].text).not.toContain('**Foundations');
308
+ expect(result.content[0].text).not.toContain('**Resources');
309
+ });
310
+ it('should list only foundations when category is "foundations"', () => {
311
+ const args = {
312
+ category: 'foundations',
313
+ };
314
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
315
+ const result = handleListComponents(args);
316
+ expect(result.content[0].text).toContain('**Foundations (2)**');
317
+ expect(result.content[0].text).toContain('- colors');
318
+ expect(result.content[0].text).toContain('- typography');
319
+ expect(result.content[0].text).not.toContain('**Components');
320
+ expect(result.content[0].text).not.toContain('**Resources');
321
+ });
322
+ it('should list only resources when category is "resources"', () => {
323
+ const args = {
324
+ category: 'resources',
325
+ };
326
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
327
+ const result = handleListComponents(args);
328
+ expect(result.content[0].text).toContain('**Resources (1)**');
329
+ expect(result.content[0].text).toContain('- icons');
330
+ expect(result.content[0].text).not.toContain('**Components');
331
+ expect(result.content[0].text).not.toContain('**Foundations');
332
+ });
333
+ it('should list all categories when category is "all"', () => {
334
+ const args = {
335
+ category: 'all',
336
+ };
337
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
338
+ const result = handleListComponents(args);
339
+ expect(result.content[0].text).toContain('**Components (3)**');
340
+ expect(result.content[0].text).toContain('**Foundations (2)**');
341
+ expect(result.content[0].text).toContain('**Resources (1)**');
342
+ });
343
+ it('should handle empty file lists', () => {
344
+ const args = {
345
+ category: 'components',
346
+ };
347
+ mockUtils.getMarkdownFiles.mockReturnValue([]);
348
+ const result = handleListComponents(args);
349
+ expect(result.content[0].text).toContain('**Components (0)**');
350
+ });
351
+ it('should validate category parameter using zod schema', () => {
352
+ const invalidArgs = {
353
+ category: 'invalid-category', // Should be enum value
354
+ };
355
+ expect(() => handleListComponents(invalidArgs)).toThrow();
356
+ });
357
+ it('should use default category when not specified', () => {
358
+ const args = {};
359
+ mockUtils.getMarkdownFiles.mockReturnValue(mockMarkdownFiles);
360
+ const result = handleListComponents(args);
361
+ // Should default to 'all' and show all categories
362
+ expect(result.content[0].text).toContain('**Components');
363
+ expect(result.content[0].text).toContain('**Foundations');
364
+ expect(result.content[0].text).toContain('**Resources');
365
+ });
366
+ it('should properly format component names by removing prefixes and suffixes', () => {
367
+ const customFiles = [
368
+ 'components-complex-button-component-docs.md',
369
+ 'foundations-design-tokens-docs.md',
370
+ 'resources-icon-library-docs.md',
371
+ ];
372
+ const args = {};
373
+ mockUtils.getMarkdownFiles.mockReturnValue(customFiles);
374
+ const result = handleListComponents(args);
375
+ expect(result.content[0].text).toContain('- complex-button-component');
376
+ expect(result.content[0].text).toContain('- design-tokens');
377
+ expect(result.content[0].text).toContain('- icon-library');
378
+ });
379
+ });
380
+ });
package/dist/config.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Configuration constants for the IDS MCP Server
3
+ */
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ export const DOCS_DIR = path.join(__dirname, '..', 'generated', 'docs');
9
+ export const SERVER_CONFIG = {
10
+ name: 'ids-mcp-server',
11
+ version: '1.0.0',
12
+ };
13
+ export const CAPABILITIES = {
14
+ resources: {},
15
+ tools: {},
16
+ };
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { SERVER_CONFIG, CAPABILITIES } from './config.js';
6
+ import { toolDefinitions } from './tools.js';
7
+ import { handleToolCall } from './toolHandler.js';
8
+ import { handleListResources, handleReadResource } from './resourceHandlers.js';
9
+ /**
10
+ * Create MCP server for Iress Design System (IDS) component library context
11
+ */
12
+ const server = new Server(SERVER_CONFIG, { capabilities: CAPABILITIES });
13
+ /**
14
+ * List available IDS component documentation resources
15
+ */
16
+ server.setRequestHandler(ListResourcesRequestSchema, () => {
17
+ const result = handleListResources();
18
+ return result;
19
+ });
20
+ /**
21
+ * Read content from a specific markdown file
22
+ */
23
+ server.setRequestHandler(ReadResourceRequestSchema, (request) => {
24
+ const result = handleReadResource(request);
25
+ return result;
26
+ });
27
+ /**
28
+ * List available IDS development tools
29
+ */
30
+ server.setRequestHandler(ListToolsRequestSchema, () => {
31
+ return {
32
+ tools: toolDefinitions,
33
+ };
34
+ });
35
+ /**
36
+ * Handle IDS component library tool calls
37
+ */
38
+ server.setRequestHandler(CallToolRequestSchema, (request) => {
39
+ const result = handleToolCall(request);
40
+ return result;
41
+ });
42
+ /**
43
+ * Start the server
44
+ */
45
+ async function main() {
46
+ const transport = new StdioServerTransport();
47
+ await server.connect(transport);
48
+ console.error('IDS Component Library MCP Server running on stdio');
49
+ }
50
+ main().catch((error) => {
51
+ console.error('Server error:', error);
52
+ process.exit(1);
53
+ });