@splunk/splunk-ui-mcp 0.1.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/LICENSE +202 -0
- package/README.md +54 -0
- package/lib/constants/versions.d.ts +9 -0
- package/lib/constants/versions.js +12 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +43 -0
- package/lib/resources/components.d.ts +26 -0
- package/lib/resources/components.js +133 -0
- package/lib/resources/icons.d.ts +30 -0
- package/lib/resources/icons.js +123 -0
- package/lib/resources/tests/components.unit.d.ts +1 -0
- package/lib/resources/tests/components.unit.js +133 -0
- package/lib/resources/tests/icons.unit.d.ts +1 -0
- package/lib/resources/tests/icons.unit.js +161 -0
- package/lib/tools/find_icons.d.ts +16 -0
- package/lib/tools/find_icons.js +102 -0
- package/lib/tools/get_component_docs.d.ts +19 -0
- package/lib/tools/get_component_docs.js +82 -0
- package/lib/tools/requirements.d.ts +15 -0
- package/lib/tools/requirements.js +25 -0
- package/lib/tools/tests/find_icons.unit.d.ts +1 -0
- package/lib/tools/tests/find_icons.unit.js +149 -0
- package/lib/tools/tests/get_component_docs.unit.d.ts +1 -0
- package/lib/tools/tests/get_component_docs.unit.js +131 -0
- package/lib/tools/tests/requirements.unit.d.ts +1 -0
- package/lib/tools/tests/requirements.unit.js +34 -0
- package/lib/utils/component-catalog.d.ts +26 -0
- package/lib/utils/component-catalog.js +148 -0
- package/lib/utils/tests/component-catalog.unit.d.ts +1 -0
- package/lib/utils/tests/component-catalog.unit.js +144 -0
- package/package.json +59 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
const MOCK_COMPONENT_LIST = [
|
|
3
|
+
{
|
|
4
|
+
name: 'Button',
|
|
5
|
+
filename: 'Button.md',
|
|
6
|
+
category: 'Base',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: 'Card Layout',
|
|
10
|
+
filename: 'Card Layout.md',
|
|
11
|
+
category: 'Base',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'Text',
|
|
15
|
+
filename: 'Text.md',
|
|
16
|
+
category: 'Data Entry',
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
const MOCK_BUTTON_DOCS = `# Button
|
|
20
|
+
|
|
21
|
+
## Overview
|
|
22
|
+
|
|
23
|
+
A button component for triggering actions.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
\`\`\`typescript
|
|
28
|
+
import Button from '@splunk/react-ui/Button';
|
|
29
|
+
\`\`\`
|
|
30
|
+
`;
|
|
31
|
+
// Mock the component catalog module
|
|
32
|
+
vi.mock('../../utils/component-catalog.ts', () => ({
|
|
33
|
+
getComponentList: vi.fn(() => MOCK_COMPONENT_LIST),
|
|
34
|
+
getComponentInfo: vi.fn((name) => MOCK_COMPONENT_LIST.find((c) => c.name === name)),
|
|
35
|
+
getComponentDocs: vi.fn((name) => {
|
|
36
|
+
if (name === 'Button') {
|
|
37
|
+
return MOCK_BUTTON_DOCS;
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`Component "${name}" not found in catalog`);
|
|
40
|
+
}),
|
|
41
|
+
}));
|
|
42
|
+
describe('createComponentResourceUri', () => {
|
|
43
|
+
it('builds an encoded MCP URI for the provided component name', async () => {
|
|
44
|
+
const { createComponentResourceUri, COMPONENT_RESOURCE_URI_BASE } = await import("../components.js");
|
|
45
|
+
expect(createComponentResourceUri('Button')).toBe(`${COMPONENT_RESOURCE_URI_BASE}/Button`);
|
|
46
|
+
expect(createComponentResourceUri('Card Layout')).toBe(`${COMPONENT_RESOURCE_URI_BASE}/Card%20Layout`);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('component resources', () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
vi.resetModules();
|
|
52
|
+
});
|
|
53
|
+
describe('createComponentResourceLink', () => {
|
|
54
|
+
it('includes catalog metadata when the component exists', async () => {
|
|
55
|
+
const { createComponentResourceLink, createComponentResourceUri } = await import("../components.js");
|
|
56
|
+
const link = createComponentResourceLink('Button');
|
|
57
|
+
expect(link).toMatchObject({
|
|
58
|
+
type: 'resource_link',
|
|
59
|
+
name: 'Button',
|
|
60
|
+
title: 'Button',
|
|
61
|
+
uri: createComponentResourceUri('Button'),
|
|
62
|
+
description: 'Base component',
|
|
63
|
+
mimeType: 'text/markdown',
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
it('creates a resource link even if the component is unknown', async () => {
|
|
67
|
+
const { createComponentResourceLink, createComponentResourceUri } = await import("../components.js");
|
|
68
|
+
const link = createComponentResourceLink('Unknown');
|
|
69
|
+
expect(link).toMatchObject({
|
|
70
|
+
type: 'resource_link',
|
|
71
|
+
name: 'Unknown',
|
|
72
|
+
title: 'Unknown',
|
|
73
|
+
uri: createComponentResourceUri('Unknown'),
|
|
74
|
+
mimeType: 'text/markdown',
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('component resource handler', () => {
|
|
79
|
+
it('reads component documentation by name', async () => {
|
|
80
|
+
const componentResource = (await import("../components.js")).default;
|
|
81
|
+
const url = new URL('mcp://splunk-ui/components/Button');
|
|
82
|
+
const result = componentResource.handler(url, {
|
|
83
|
+
componentName: 'Button',
|
|
84
|
+
});
|
|
85
|
+
expect(result.contents).toHaveLength(1);
|
|
86
|
+
expect(result.contents[0]).toMatchObject({
|
|
87
|
+
mimeType: 'text/markdown',
|
|
88
|
+
text: MOCK_BUTTON_DOCS,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
it('extracts component name from URL path if param is missing', async () => {
|
|
92
|
+
const componentResource = (await import("../components.js")).default;
|
|
93
|
+
const url = new URL('mcp://splunk-ui/components/Button');
|
|
94
|
+
const result = componentResource.handler(url, {});
|
|
95
|
+
expect(result.contents).toHaveLength(1);
|
|
96
|
+
expect(result.contents[0]).toMatchObject({
|
|
97
|
+
mimeType: 'text/markdown',
|
|
98
|
+
text: MOCK_BUTTON_DOCS,
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
it('throws error when component name is missing', async () => {
|
|
102
|
+
const componentResource = (await import("../components.js")).default;
|
|
103
|
+
// URL with no component in path
|
|
104
|
+
const url = new URL('mcp://splunk-ui/components');
|
|
105
|
+
expect(() => componentResource.handler(url, {})).toThrow('Component name missing in resource request');
|
|
106
|
+
});
|
|
107
|
+
it('throws error for unknown component', async () => {
|
|
108
|
+
const componentResource = (await import("../components.js")).default;
|
|
109
|
+
const url = new URL('mcp://splunk-ui/components/Unknown');
|
|
110
|
+
expect(() => componentResource.handler(url, { componentName: 'Unknown' })).toThrow('Unknown component "Unknown"');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('component resource template', () => {
|
|
114
|
+
it('autocompletes component names', async () => {
|
|
115
|
+
const componentResource = (await import("../components.js")).default;
|
|
116
|
+
const complete = componentResource.template.completeCallback?.('componentName');
|
|
117
|
+
const completions = (await complete?.('b')) ?? [];
|
|
118
|
+
expect(completions).toEqual(['Button']);
|
|
119
|
+
});
|
|
120
|
+
it('autocompletes with partial matches', async () => {
|
|
121
|
+
const componentResource = (await import("../components.js")).default;
|
|
122
|
+
const complete = componentResource.template.completeCallback?.('componentName');
|
|
123
|
+
const completions = (await complete?.('card')) ?? [];
|
|
124
|
+
expect(completions).toContain('Card Layout');
|
|
125
|
+
});
|
|
126
|
+
it('returns empty array when no matches found for autocomplete', async () => {
|
|
127
|
+
const componentResource = (await import("../components.js")).default;
|
|
128
|
+
const complete = componentResource.template.completeCallback?.('componentName');
|
|
129
|
+
const completions = (await complete?.('xyz')) ?? [];
|
|
130
|
+
expect(completions).toEqual([]);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
const ICON_CATALOG_FIXTURE = vi.hoisted(() => [
|
|
3
|
+
{
|
|
4
|
+
name: 'SearchIcon',
|
|
5
|
+
category: 'Interface',
|
|
6
|
+
description: 'Use for search interactions.',
|
|
7
|
+
keywords: 'search, magnify, find',
|
|
8
|
+
variants: ['default', 'filled'],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: 'AlertIcon',
|
|
12
|
+
keywords: 'alert, warning',
|
|
13
|
+
},
|
|
14
|
+
]);
|
|
15
|
+
vi.mock('@splunk/react-icons/icon-catalog.json', () => ({
|
|
16
|
+
default: ICON_CATALOG_FIXTURE,
|
|
17
|
+
}));
|
|
18
|
+
describe('createIconResourceUri', () => {
|
|
19
|
+
it('builds an encoded MCP URI for the provided icon name', async () => {
|
|
20
|
+
const { createIconResourceUri, ICON_RESOURCE_URI_BASE } = await import("../icons.js");
|
|
21
|
+
expect(createIconResourceUri('SimpleIcon')).toBe(`${ICON_RESOURCE_URI_BASE}/SimpleIcon`);
|
|
22
|
+
expect(createIconResourceUri('Icon With Spaces')).toBe(`${ICON_RESOURCE_URI_BASE}/Icon%20With%20Spaces`);
|
|
23
|
+
expect(createIconResourceUri('Icon/Separators')).toBe(`${ICON_RESOURCE_URI_BASE}/Icon%2FSeparators`);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('icon resources', () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
vi.resetModules();
|
|
29
|
+
});
|
|
30
|
+
describe('createIconResourceLink', () => {
|
|
31
|
+
it('includes catalog metadata when the icon exists', async () => {
|
|
32
|
+
const { createIconResourceLink, createIconResourceUri } = await import("../icons.js");
|
|
33
|
+
const link = createIconResourceLink('SearchIcon');
|
|
34
|
+
expect(link).toMatchObject({
|
|
35
|
+
type: 'resource_link',
|
|
36
|
+
name: 'SearchIcon',
|
|
37
|
+
title: 'SearchIcon',
|
|
38
|
+
uri: createIconResourceUri('SearchIcon'),
|
|
39
|
+
description: 'Use for search interactions.',
|
|
40
|
+
mimeType: 'application/json',
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
it('falls back to minimal metadata when the icon is unknown', async () => {
|
|
44
|
+
const { createIconResourceLink, createIconResourceUri } = await import("../icons.js");
|
|
45
|
+
const link = createIconResourceLink('MissingIcon');
|
|
46
|
+
expect(link).toMatchObject({
|
|
47
|
+
type: 'resource_link',
|
|
48
|
+
name: 'MissingIcon',
|
|
49
|
+
title: 'MissingIcon',
|
|
50
|
+
uri: createIconResourceUri('MissingIcon'),
|
|
51
|
+
description: undefined,
|
|
52
|
+
mimeType: 'application/json',
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe('getIconResourceDetails', () => {
|
|
57
|
+
it('returns structured details for known icons', async () => {
|
|
58
|
+
const { getIconResourceDetails } = await import("../icons.js");
|
|
59
|
+
const details = getIconResourceDetails('SearchIcon');
|
|
60
|
+
expect(details).toMatchObject({
|
|
61
|
+
name: 'SearchIcon',
|
|
62
|
+
importPath: '@splunk/react-icons/SearchIcon',
|
|
63
|
+
category: 'Interface',
|
|
64
|
+
description: 'Use for search interactions.',
|
|
65
|
+
keywords: 'search, magnify, find',
|
|
66
|
+
variants: ['default', 'filled'],
|
|
67
|
+
});
|
|
68
|
+
expect(details?.usageExample).toContain('SearchIcon');
|
|
69
|
+
});
|
|
70
|
+
it('returns undefined when the icon is missing', async () => {
|
|
71
|
+
const { getIconResourceDetails } = await import("../icons.js");
|
|
72
|
+
expect(getIconResourceDetails('MissingIcon')).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
it('accepts catalog entries directly', async () => {
|
|
75
|
+
const { getIconResourceDetails } = await import("../icons.js");
|
|
76
|
+
const details = getIconResourceDetails({
|
|
77
|
+
name: 'AlertIcon',
|
|
78
|
+
key: './AlertIcon',
|
|
79
|
+
keywords: 'alert, warning',
|
|
80
|
+
variants: ['default'],
|
|
81
|
+
description: 'An alert icon.',
|
|
82
|
+
category: '',
|
|
83
|
+
});
|
|
84
|
+
expect(details).toMatchObject({
|
|
85
|
+
name: 'AlertIcon',
|
|
86
|
+
importPath: '@splunk/react-icons/AlertIcon',
|
|
87
|
+
});
|
|
88
|
+
expect(details.usageExample).toContain('AlertIcon');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('iconResource', () => {
|
|
92
|
+
const setup = async () => {
|
|
93
|
+
const { default: iconResource, createIconResourceUri } = await import("../icons.js");
|
|
94
|
+
return {
|
|
95
|
+
iconResource,
|
|
96
|
+
createIconResourceUri,
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
it('provides the expected metadata configuration', async () => {
|
|
100
|
+
const { iconResource } = await setup();
|
|
101
|
+
expect(iconResource.name).toBe('splunk-ui-icons');
|
|
102
|
+
expect(iconResource.config).toMatchObject({
|
|
103
|
+
title: expect.stringContaining('Icon Catalog'),
|
|
104
|
+
mimeType: 'application/json',
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
it('lists icons from the catalog', async () => {
|
|
108
|
+
const { iconResource } = await setup();
|
|
109
|
+
const { template } = iconResource;
|
|
110
|
+
const listResult = await template.listCallback?.();
|
|
111
|
+
expect(listResult).toMatchObject({
|
|
112
|
+
resources: expect.arrayContaining([
|
|
113
|
+
expect.objectContaining({ name: 'SearchIcon' }),
|
|
114
|
+
expect.objectContaining({ name: 'AlertIcon' }),
|
|
115
|
+
]),
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
it('suggests icon names using the completion handler', async () => {
|
|
119
|
+
const { iconResource } = await setup();
|
|
120
|
+
const complete = iconResource.template.completeCallback?.('iconName');
|
|
121
|
+
const completions = (await complete?.('se')) ?? [];
|
|
122
|
+
expect(completions).toContain('SearchIcon');
|
|
123
|
+
expect(completions).not.toContain('AlertIcon');
|
|
124
|
+
});
|
|
125
|
+
it('reads icon details using request variables', async () => {
|
|
126
|
+
const { iconResource, createIconResourceUri } = await setup();
|
|
127
|
+
expect(typeof iconResource.handler).toBe('function');
|
|
128
|
+
const readResult = await iconResource.handler(new URL(createIconResourceUri('AlertIcon')), { iconName: 'AlertIcon' });
|
|
129
|
+
const [content] = readResult.contents;
|
|
130
|
+
expect(content).toMatchObject({
|
|
131
|
+
uri: createIconResourceUri('AlertIcon'),
|
|
132
|
+
mimeType: 'application/json',
|
|
133
|
+
});
|
|
134
|
+
const parsed = JSON.parse(String(content.text ?? '{}'));
|
|
135
|
+
expect(parsed).toMatchObject({
|
|
136
|
+
name: 'AlertIcon',
|
|
137
|
+
importPath: '@splunk/react-icons/AlertIcon',
|
|
138
|
+
keywords: 'alert, warning',
|
|
139
|
+
});
|
|
140
|
+
expect(parsed.usageExample).toContain('AlertIcon');
|
|
141
|
+
// Ensure the template reference is exercised for coverage.
|
|
142
|
+
expect(iconResource.template).toBeDefined();
|
|
143
|
+
});
|
|
144
|
+
it('derives the icon name from the URI when variables are absent', async () => {
|
|
145
|
+
const { iconResource, createIconResourceUri } = await setup();
|
|
146
|
+
const readWithoutVariable = (await iconResource.handler(new URL(createIconResourceUri('SearchIcon')), {}));
|
|
147
|
+
const [content] = readWithoutVariable.contents;
|
|
148
|
+
expect(JSON.parse(String(content.text))).toMatchObject({ name: 'SearchIcon' });
|
|
149
|
+
});
|
|
150
|
+
it('throws when the icon cannot be resolved from variables or the URI', async () => {
|
|
151
|
+
const { iconResource } = await setup();
|
|
152
|
+
expect(() => iconResource.handler(new URL('mcp://splunk-ui'), {})).toThrowError('Icon name missing in resource request: mcp://splunk-ui');
|
|
153
|
+
});
|
|
154
|
+
it('throws when the requested icon is not found in the catalog', async () => {
|
|
155
|
+
const { iconResource, createIconResourceUri } = await setup();
|
|
156
|
+
expect(() => iconResource.handler(new URL(createIconResourceUri('MissingIcon')), {
|
|
157
|
+
iconName: 'MissingIcon',
|
|
158
|
+
})).toThrowError('Unknown icon "MissingIcon"');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
declare const findIconsTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
config: {
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
inputSchema: {
|
|
9
|
+
query: z.ZodString;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
handler: ({ query }: {
|
|
13
|
+
query?: string;
|
|
14
|
+
}) => CallToolResult;
|
|
15
|
+
};
|
|
16
|
+
export default findIconsTool;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import iconCatalog from '@splunk/react-icons/icon-catalog.json' with { type: 'json' };
|
|
2
|
+
import { addSearchData, search } from '@splunk/ui-utils/search.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { createIconResourceLink, getIconResourceDetails } from "../resources/icons.js";
|
|
5
|
+
const iconData = addSearchData(iconCatalog);
|
|
6
|
+
const NO_RESULTS_RESULT = {
|
|
7
|
+
content: [
|
|
8
|
+
{
|
|
9
|
+
type: 'text',
|
|
10
|
+
text: 'No icons found. Try different keywords like "search", "notification", "user", "chart", etc.',
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
};
|
|
14
|
+
const formatIconDetails = (details) => {
|
|
15
|
+
const lines = [];
|
|
16
|
+
if (details.description) {
|
|
17
|
+
lines.push(`- Description: ${details.description}`);
|
|
18
|
+
}
|
|
19
|
+
lines.push(`- Import: \`${details.importPath}\``);
|
|
20
|
+
if (details.category) {
|
|
21
|
+
lines.push(`- Category: ${details.category}`);
|
|
22
|
+
}
|
|
23
|
+
if (details.variants?.length) {
|
|
24
|
+
lines.push(`- Variants: ${details.variants.join(', ')}`);
|
|
25
|
+
}
|
|
26
|
+
if (details.keywords) {
|
|
27
|
+
lines.push(`- Keywords: ${details.keywords}`);
|
|
28
|
+
}
|
|
29
|
+
lines.push('');
|
|
30
|
+
lines.push(details.usageExample);
|
|
31
|
+
return lines.join('\n');
|
|
32
|
+
};
|
|
33
|
+
const findIconsHandler = ({ query }) => {
|
|
34
|
+
if (!query || query.trim().length === 0) {
|
|
35
|
+
return NO_RESULTS_RESULT;
|
|
36
|
+
}
|
|
37
|
+
const results = search(query, iconData);
|
|
38
|
+
if (results.length === 0) {
|
|
39
|
+
return NO_RESULTS_RESULT;
|
|
40
|
+
}
|
|
41
|
+
const [recommended, ...others] = results;
|
|
42
|
+
const recommendedIcon = recommended.name;
|
|
43
|
+
const alternativeIcons = others.slice(0, 4); // recommend up to 4 alternative icons
|
|
44
|
+
const recommendedDetails = getIconResourceDetails(recommendedIcon);
|
|
45
|
+
const alternativeSummaries = alternativeIcons.map((icon) => ({
|
|
46
|
+
name: icon.name,
|
|
47
|
+
details: getIconResourceDetails(icon.name),
|
|
48
|
+
}));
|
|
49
|
+
const sections = [
|
|
50
|
+
`# Icons for "${query}"`,
|
|
51
|
+
'## Recommended Icon',
|
|
52
|
+
`Recommended to use <${recommendedIcon} /> for "${query}"`,
|
|
53
|
+
recommendedDetails
|
|
54
|
+
? formatIconDetails(recommendedDetails)
|
|
55
|
+
: `No catalog details found for ${recommendedIcon}.`,
|
|
56
|
+
];
|
|
57
|
+
sections.push('Follow the resource links below for full metadata and usage snippets.');
|
|
58
|
+
if (alternativeSummaries.length > 0) {
|
|
59
|
+
sections.push('## Alternative Icons', alternativeSummaries
|
|
60
|
+
.map(({ name, details }) => {
|
|
61
|
+
const context = details?.description ?? details?.keywords ?? details?.category;
|
|
62
|
+
return context ? `- ${name}: ${context}` : `- ${name}`;
|
|
63
|
+
})
|
|
64
|
+
.join('\n'));
|
|
65
|
+
}
|
|
66
|
+
const summaryText = sections.join('\n\n');
|
|
67
|
+
const structuredContent = {
|
|
68
|
+
recommended: {
|
|
69
|
+
name: recommendedIcon,
|
|
70
|
+
details: recommendedDetails ?? null,
|
|
71
|
+
},
|
|
72
|
+
alternatives: alternativeSummaries.map(({ name, details }) => ({
|
|
73
|
+
name,
|
|
74
|
+
details: details ?? null,
|
|
75
|
+
})),
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: 'text',
|
|
81
|
+
text: summaryText,
|
|
82
|
+
},
|
|
83
|
+
createIconResourceLink(recommendedIcon),
|
|
84
|
+
...alternativeIcons.map((icon) => createIconResourceLink(icon.name)),
|
|
85
|
+
],
|
|
86
|
+
structuredContent,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const findIconsTool = {
|
|
90
|
+
name: 'find_icons',
|
|
91
|
+
config: {
|
|
92
|
+
title: 'Find Icons',
|
|
93
|
+
description: 'Search @splunk/react-icons for the best match and a few alternatives based on your keyword.',
|
|
94
|
+
inputSchema: {
|
|
95
|
+
query: z
|
|
96
|
+
.string()
|
|
97
|
+
.describe('The search query for the icon, e.g., "search", "notification", "user".'),
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
handler: findIconsHandler,
|
|
101
|
+
};
|
|
102
|
+
export default findIconsTool;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* Tool to get documentation for a specific React UI component
|
|
5
|
+
*/
|
|
6
|
+
declare const getComponentDocsTool: {
|
|
7
|
+
name: string;
|
|
8
|
+
config: {
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
inputSchema: {
|
|
12
|
+
componentName: z.ZodString;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
handler: ({ componentName }: {
|
|
16
|
+
componentName?: string;
|
|
17
|
+
}) => CallToolResult;
|
|
18
|
+
};
|
|
19
|
+
export default getComponentDocsTool;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createComponentResourceLink } from "../resources/components.js";
|
|
3
|
+
import { componentExists, getComponentDocs, getComponentList } from "../utils/component-catalog.js";
|
|
4
|
+
/**
|
|
5
|
+
* Tool to get documentation for a specific React UI component
|
|
6
|
+
*/
|
|
7
|
+
const getComponentDocsTool = {
|
|
8
|
+
name: 'get_component_docs',
|
|
9
|
+
config: {
|
|
10
|
+
title: 'Get Component Documentation',
|
|
11
|
+
description: 'Retrieves the full documentation for a specific @splunk/react-ui component. ' +
|
|
12
|
+
'Includes overview, usage guidelines, examples, API documentation, and accessibility information.',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
componentName: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe('The exact name of the component (e.g., "Button", "Card Layout", "Text"). ' +
|
|
17
|
+
'Case-sensitive. Use the component name as it appears in the catalog.'),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
handler: ({ componentName }) => {
|
|
21
|
+
if (!componentName || componentName.trim().length === 0) {
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: 'text',
|
|
26
|
+
text: 'Component name is required. Please provide a component name.',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Check if component exists
|
|
32
|
+
if (!componentExists(componentName)) {
|
|
33
|
+
const components = getComponentList();
|
|
34
|
+
const availableNames = components.map((c) => c.name).slice(0, 10);
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: 'text',
|
|
39
|
+
text: `Component "${componentName}" not found.\n\n` +
|
|
40
|
+
`Available components include: ${availableNames.join(', ')}, and more.\n\n` +
|
|
41
|
+
'Component names are case-sensitive. Please check the spelling and try again.',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Get the documentation
|
|
47
|
+
try {
|
|
48
|
+
const docs = getComponentDocs(componentName);
|
|
49
|
+
return {
|
|
50
|
+
content: [
|
|
51
|
+
{
|
|
52
|
+
type: 'text',
|
|
53
|
+
text: docs,
|
|
54
|
+
},
|
|
55
|
+
createComponentResourceLink(componentName),
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return {
|
|
61
|
+
content: [
|
|
62
|
+
{
|
|
63
|
+
type: 'text',
|
|
64
|
+
text: `Failed to retrieve documentation for "${componentName}": ${error.message}`,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
isError: true,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
export default getComponentDocsTool;
|
|
73
|
+
// TODO: Future enhancement - add section extraction
|
|
74
|
+
// Allow requesting specific sections of documentation:
|
|
75
|
+
// - Overview
|
|
76
|
+
// - Examples
|
|
77
|
+
// - API/Props
|
|
78
|
+
// - Accessibility
|
|
79
|
+
// - Test Hooks
|
|
80
|
+
//
|
|
81
|
+
// Example:
|
|
82
|
+
// export function getComponentSection(componentName: string, section: string): CallToolResult
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Tool to list design system version requirements (Splunk UI packages + peerDependencies)
|
|
4
|
+
* without requiring an existing project.
|
|
5
|
+
*/
|
|
6
|
+
declare const requirements: {
|
|
7
|
+
name: string;
|
|
8
|
+
config: {
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
inputSchema: {};
|
|
12
|
+
};
|
|
13
|
+
handler: () => CallToolResult;
|
|
14
|
+
};
|
|
15
|
+
export default requirements;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import VERSIONS from "../constants/versions.js";
|
|
2
|
+
/**
|
|
3
|
+
* Tool to list design system version requirements (Splunk UI packages + peerDependencies)
|
|
4
|
+
* without requiring an existing project.
|
|
5
|
+
*/
|
|
6
|
+
const requirements = {
|
|
7
|
+
name: 'list_splunk_ui_requirements',
|
|
8
|
+
config: {
|
|
9
|
+
title: 'List Splunk UI Design System Requirements',
|
|
10
|
+
description: '🔧 **For Agents:** ALWAYS call this tool first to fetch exact version constraints for Splunk UI packages and peer dependencies required by the Splunk UI Design System before scaffolding a new project; after use the check_setup tool to verify.\n\n' +
|
|
11
|
+
'Returns the Splunk UI design system requirements as JSON.',
|
|
12
|
+
inputSchema: {},
|
|
13
|
+
},
|
|
14
|
+
handler: () => {
|
|
15
|
+
return {
|
|
16
|
+
content: [
|
|
17
|
+
{
|
|
18
|
+
type: 'text',
|
|
19
|
+
text: JSON.stringify(VERSIONS, null, 2),
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export default requirements;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|