@cooperco/component-library-mcp-server 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/README.md +106 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +55 -0
- package/dist/parsers/passthrough-parser.d.ts +13 -0
- package/dist/parsers/passthrough-parser.js +115 -0
- package/dist/parsers/typescript-parser.d.ts +11 -0
- package/dist/parsers/typescript-parser.js +77 -0
- package/dist/parsers/vue-sfc-parser.d.ts +10 -0
- package/dist/parsers/vue-sfc-parser.js +61 -0
- package/dist/registry/component-descriptions.d.ts +1 -0
- package/dist/registry/component-descriptions.js +43 -0
- package/dist/registry/component-registry.d.ts +23 -0
- package/dist/registry/component-registry.js +256 -0
- package/dist/scaffolding/templates.d.ts +21 -0
- package/dist/scaffolding/templates.js +122 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +61 -0
- package/dist/tools/get-color-palettes.d.ts +14 -0
- package/dist/tools/get-color-palettes.js +68 -0
- package/dist/tools/get-component-props.d.ts +14 -0
- package/dist/tools/get-component-props.js +32 -0
- package/dist/tools/get-component.d.ts +14 -0
- package/dist/tools/get-component.js +109 -0
- package/dist/tools/get-passthrough-config.d.ts +14 -0
- package/dist/tools/get-passthrough-config.js +77 -0
- package/dist/tools/list-components.d.ts +7 -0
- package/dist/tools/list-components.js +21 -0
- package/dist/tools/scaffold-component.d.ts +29 -0
- package/dist/tools/scaffold-component.js +128 -0
- package/dist/tools/search-components.d.ts +7 -0
- package/dist/tools/search-components.js +21 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.js +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
export const handleGetComponent = async (registry, name, projectRoot) => {
|
|
4
|
+
const metadata = await registry.getComponent(name);
|
|
5
|
+
if (!metadata) {
|
|
6
|
+
return {
|
|
7
|
+
content: [
|
|
8
|
+
{
|
|
9
|
+
type: 'text',
|
|
10
|
+
text: `Component "${name}" not found. Use list-components to see all available components.`,
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
isError: true,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
let output = `# ${metadata.name}\n\n`;
|
|
17
|
+
output += `**Category:** ${metadata.category}\n`;
|
|
18
|
+
output += `**GraphQL typename:** ${metadata.typename}\n`;
|
|
19
|
+
output += `**Extends Component:** ${metadata.extendsComponent ? 'Yes' : 'No'}\n`;
|
|
20
|
+
output += `**Uses color palette:** ${metadata.hasColorPalette ? 'Yes' : 'No'}\n`;
|
|
21
|
+
output += `**Directory:** ${metadata.directory}/\n`;
|
|
22
|
+
if (metadata.variants.length > 0) {
|
|
23
|
+
output += `**Variants:** ${metadata.variants.join(', ')}\n`;
|
|
24
|
+
}
|
|
25
|
+
// Props table
|
|
26
|
+
output += '\n## Props\n\n';
|
|
27
|
+
if (metadata.props.length > 0) {
|
|
28
|
+
output += '| Prop | Type | Required | Default | Description |\n';
|
|
29
|
+
output += '|------|------|----------|---------|-------------|\n';
|
|
30
|
+
for (const prop of metadata.props) {
|
|
31
|
+
const typeStr = prop.type.length > 60 ? prop.type.substring(0, 57) + '...' : prop.type;
|
|
32
|
+
output += `| ${prop.name} | \`${typeStr}\` | ${prop.required ? 'Yes' : 'No'} | ${prop.defaultValue ?? '-'} | ${prop.description ?? '-'} |\n`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
output += 'No props defined.\n';
|
|
37
|
+
}
|
|
38
|
+
// Emits
|
|
39
|
+
output += '\n## Events\n\n';
|
|
40
|
+
if (metadata.emits.length > 0) {
|
|
41
|
+
for (const emit of metadata.emits) {
|
|
42
|
+
output += `- \`${emit.name}\`${emit.payload ? ` (payload: ${emit.payload})` : ''}\n`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
output += 'No custom events.\n';
|
|
47
|
+
}
|
|
48
|
+
// Slots
|
|
49
|
+
output += '\n## Slots\n\n';
|
|
50
|
+
if (metadata.slots.length > 0) {
|
|
51
|
+
for (const slot of metadata.slots) {
|
|
52
|
+
output += `- \`${slot.name}\`\n`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
output += 'No named slots.\n';
|
|
57
|
+
}
|
|
58
|
+
// Passthrough
|
|
59
|
+
if (metadata.passthroughInterface) {
|
|
60
|
+
output += `\n## Passthrough Interface: ${metadata.passthroughInterface}\n\n`;
|
|
61
|
+
}
|
|
62
|
+
const pt = await registry.getPassthroughForComponent(metadata.name);
|
|
63
|
+
if (pt) {
|
|
64
|
+
if (pt.variants) {
|
|
65
|
+
for (const [variant, keys] of Object.entries(pt.variants)) {
|
|
66
|
+
output += `\n### Passthrough: ${variant} variant\n\n`;
|
|
67
|
+
if (keys.length > 0) {
|
|
68
|
+
output += '| Key | Default Classes |\n';
|
|
69
|
+
output += '|-----|-----------------|\n';
|
|
70
|
+
for (const key of keys) {
|
|
71
|
+
output += `| ${key.key} | \`${key.defaultClasses}\` |\n`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (pt.keys.length > 0) {
|
|
77
|
+
output += '\n### Passthrough Overrides\n\n';
|
|
78
|
+
output += '| Key | Default Classes |\n';
|
|
79
|
+
output += '|-----|-----------------|\n';
|
|
80
|
+
for (const key of pt.keys) {
|
|
81
|
+
output += `| ${key.key} | \`${key.defaultClasses}\` |\n`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Source files
|
|
86
|
+
output += '\n## Source Files\n\n';
|
|
87
|
+
output += `- **Vue SFC:** ${metadata.vueFile}\n`;
|
|
88
|
+
if (metadata.typeFile) {
|
|
89
|
+
output += `- **Types:** ${metadata.typeFile}\n`;
|
|
90
|
+
}
|
|
91
|
+
// Include raw Vue source for full context
|
|
92
|
+
try {
|
|
93
|
+
const vueSource = await fs.readFile(path.join(projectRoot, metadata.vueFile), 'utf-8');
|
|
94
|
+
output += `\n## Vue Source\n\n\`\`\`vue\n${vueSource}\`\`\`\n`;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// File read failed, skip raw source
|
|
98
|
+
}
|
|
99
|
+
if (metadata.typeFile) {
|
|
100
|
+
try {
|
|
101
|
+
const typeSource = await fs.readFile(path.join(projectRoot, metadata.typeFile), 'utf-8');
|
|
102
|
+
output += `\n## Type Definitions\n\n\`\`\`typescript\n${typeSource}\`\`\`\n`;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// File read failed, skip raw source
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { content: [{ type: 'text', text: output }] };
|
|
109
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ComponentRegistry } from '../registry/component-registry.js';
|
|
2
|
+
export declare const handleGetPassthroughConfig: (registry: ComponentRegistry, name: string) => Promise<{
|
|
3
|
+
content: {
|
|
4
|
+
type: "text";
|
|
5
|
+
text: string;
|
|
6
|
+
}[];
|
|
7
|
+
isError: boolean;
|
|
8
|
+
} | {
|
|
9
|
+
content: {
|
|
10
|
+
type: "text";
|
|
11
|
+
text: string;
|
|
12
|
+
}[];
|
|
13
|
+
isError?: undefined;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const handleGetPassthroughConfig = async (registry, name) => {
|
|
2
|
+
const metadata = await registry.getComponent(name);
|
|
3
|
+
if (!metadata) {
|
|
4
|
+
return {
|
|
5
|
+
content: [
|
|
6
|
+
{
|
|
7
|
+
type: 'text',
|
|
8
|
+
text: `Component "${name}" not found. Use list-components to see all available components.`,
|
|
9
|
+
},
|
|
10
|
+
],
|
|
11
|
+
isError: true,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const pt = await registry.getPassthroughForComponent(metadata.name);
|
|
15
|
+
let output = `# ${metadata.name} Passthrough Configuration\n\n`;
|
|
16
|
+
if (metadata.passthroughInterface) {
|
|
17
|
+
output += `**Interface:** ${metadata.passthroughInterface}\n\n`;
|
|
18
|
+
}
|
|
19
|
+
if (!pt) {
|
|
20
|
+
output += 'No passthrough configuration found for this component.\n';
|
|
21
|
+
}
|
|
22
|
+
else if (pt.variants) {
|
|
23
|
+
for (const [variant, keys] of Object.entries(pt.variants)) {
|
|
24
|
+
output += `## ${variant} Variant\n\n`;
|
|
25
|
+
if (keys.length > 0) {
|
|
26
|
+
output += '| Key | Default Classes |\n';
|
|
27
|
+
output += '|-----|-----------------|\n';
|
|
28
|
+
for (const key of keys) {
|
|
29
|
+
output += `| ${key.key} | \`${key.defaultClasses}\` |\n`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
output += 'No overrides.\n';
|
|
34
|
+
}
|
|
35
|
+
output += '\n';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else if (pt.keys.length > 0) {
|
|
39
|
+
output += '## Overrides\n\n';
|
|
40
|
+
output += '| Key | Default Classes |\n';
|
|
41
|
+
output += '|-----|-----------------|\n';
|
|
42
|
+
for (const key of pt.keys) {
|
|
43
|
+
output += `| ${key.key} | \`${key.defaultClasses}\` |\n`;
|
|
44
|
+
}
|
|
45
|
+
output += '\n';
|
|
46
|
+
}
|
|
47
|
+
output += '## Usage\n\n';
|
|
48
|
+
output += '```typescript\n';
|
|
49
|
+
output +=
|
|
50
|
+
"import { combinePassthroughs } from '@/config/defaultPassthrough'\n";
|
|
51
|
+
output += `import { ${metadata.name}Pt } from '@/config/defaultPassthrough'\n\n`;
|
|
52
|
+
output += 'const pt = computed(() =>\n';
|
|
53
|
+
if (metadata.variants.length > 0) {
|
|
54
|
+
const propName = metadata.variantPropName ?? 'variant';
|
|
55
|
+
output += ` combinePassthroughs(${metadata.name}Pt[props.${propName}], props.pt ?? {})\n`;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
output += ` combinePassthroughs(${metadata.name}Pt, props.pt ?? {})\n`;
|
|
59
|
+
}
|
|
60
|
+
output += ')\n';
|
|
61
|
+
output += '```\n\n';
|
|
62
|
+
const defaultPt = await registry.getDefaultPassthrough();
|
|
63
|
+
output += '## Base DEFAULT_PASSTHROUGH\n\n';
|
|
64
|
+
output += 'All passthroughs extend from:\n';
|
|
65
|
+
if (defaultPt.length > 0) {
|
|
66
|
+
output += '| Key | Default |\n';
|
|
67
|
+
output += '|-----|---------|\n';
|
|
68
|
+
for (const entry of defaultPt) {
|
|
69
|
+
const displayValue = entry.defaultClasses || "''";
|
|
70
|
+
output += `| ${entry.key} | \`${displayValue}\` |\n`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
output += 'Could not parse DEFAULT_PASSTHROUGH from source.\n';
|
|
75
|
+
}
|
|
76
|
+
return { content: [{ type: 'text', text: output }] };
|
|
77
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const handleListComponents = async (registry) => {
|
|
2
|
+
const components = await registry.listComponents();
|
|
3
|
+
const modules = components.filter((c) => c.category === 'module');
|
|
4
|
+
const comps = components.filter((c) => c.category === 'component');
|
|
5
|
+
let output = `# Cooper Component Library (${components.length} components)\n\n`;
|
|
6
|
+
output += '## Modules\n\n';
|
|
7
|
+
output += '| Name | Variants | Description |\n';
|
|
8
|
+
output += '|------|----------|-------------|\n';
|
|
9
|
+
for (const m of modules) {
|
|
10
|
+
const variants = m.variants.length > 0 ? m.variants.join(', ') : '-';
|
|
11
|
+
output += `| ${m.name} | ${variants} | ${m.description} |\n`;
|
|
12
|
+
}
|
|
13
|
+
output += '\n## Components\n\n';
|
|
14
|
+
output += '| Name | Variants | Description |\n';
|
|
15
|
+
output += '|------|----------|-------------|\n';
|
|
16
|
+
for (const c of comps) {
|
|
17
|
+
const variants = c.variants.length > 0 ? c.variants.join(', ') : '-';
|
|
18
|
+
output += `| ${c.name} | ${variants} | ${c.description} |\n`;
|
|
19
|
+
}
|
|
20
|
+
return { content: [{ type: 'text', text: output }] };
|
|
21
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ComponentRegistry } from '../registry/component-registry.js';
|
|
2
|
+
interface ScaffoldArgs {
|
|
3
|
+
name: string;
|
|
4
|
+
category: 'component' | 'module';
|
|
5
|
+
props?: Array<{
|
|
6
|
+
name: string;
|
|
7
|
+
type: string;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
default?: string;
|
|
10
|
+
}>;
|
|
11
|
+
hasColorPalette?: boolean;
|
|
12
|
+
hasPassthrough?: boolean;
|
|
13
|
+
variants?: string[];
|
|
14
|
+
variantPropName?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const handleScaffoldComponent: (registry: ComponentRegistry, args: ScaffoldArgs, projectRoot: string) => Promise<{
|
|
17
|
+
content: {
|
|
18
|
+
type: "text";
|
|
19
|
+
text: string;
|
|
20
|
+
}[];
|
|
21
|
+
isError: boolean;
|
|
22
|
+
} | {
|
|
23
|
+
content: {
|
|
24
|
+
type: "text";
|
|
25
|
+
text: string;
|
|
26
|
+
}[];
|
|
27
|
+
isError?: undefined;
|
|
28
|
+
}>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { generateTypeFile, generateVueFile, generateStoryFile, generateComponentExport, generateTypeExport, } from '../scaffolding/templates.js';
|
|
4
|
+
export const handleScaffoldComponent = async (registry, args, projectRoot) => {
|
|
5
|
+
const { name, category, props, hasColorPalette, hasPassthrough, variants, variantPropName, } = args;
|
|
6
|
+
// Validate name is PascalCase
|
|
7
|
+
if (!/^[A-Z][a-zA-Z0-9]+$/.test(name)) {
|
|
8
|
+
return {
|
|
9
|
+
content: [
|
|
10
|
+
{
|
|
11
|
+
type: 'text',
|
|
12
|
+
text: `Error: Component name "${name}" must be PascalCase (e.g., "BannerModule").`,
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
isError: true,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// Validate prop names are valid TypeScript identifiers
|
|
19
|
+
const TS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
20
|
+
if (props) {
|
|
21
|
+
for (const prop of props) {
|
|
22
|
+
if (!TS_IDENTIFIER.test(prop.name)) {
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
{
|
|
26
|
+
type: 'text',
|
|
27
|
+
text: `Error: Prop name "${prop.name}" is not a valid TypeScript identifier.`,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
isError: true,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Validate variant values are safe string literals
|
|
36
|
+
if (variants) {
|
|
37
|
+
for (const variant of variants) {
|
|
38
|
+
if (!/^[a-zA-Z][a-zA-Z0-9 _-]*$/.test(variant)) {
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: 'text',
|
|
43
|
+
text: `Error: Variant "${variant}" contains invalid characters. Use only letters, numbers, spaces, hyphens, and underscores.`,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Validate variantPropName is a valid TypeScript identifier
|
|
52
|
+
if (variantPropName && !TS_IDENTIFIER.test(variantPropName)) {
|
|
53
|
+
return {
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
56
|
+
type: 'text',
|
|
57
|
+
text: `Error: Variant prop name "${variantPropName}" is not a valid TypeScript identifier.`,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
isError: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// Check if component already exists
|
|
64
|
+
const exists = await registry.componentExists(name);
|
|
65
|
+
if (exists) {
|
|
66
|
+
return {
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: 'text',
|
|
70
|
+
text: `Error: Component "${name}" already exists.`,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
isError: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const options = {
|
|
77
|
+
name,
|
|
78
|
+
category,
|
|
79
|
+
props,
|
|
80
|
+
hasColorPalette: hasColorPalette ?? true,
|
|
81
|
+
hasPassthrough: hasPassthrough ?? true,
|
|
82
|
+
variants,
|
|
83
|
+
variantPropName,
|
|
84
|
+
};
|
|
85
|
+
const componentDir = path.join(projectRoot, 'src/components', name);
|
|
86
|
+
const storyDir = path.join(projectRoot, '.storybook', category === 'module' ? 'modules' : 'components');
|
|
87
|
+
// Generate file contents
|
|
88
|
+
const typeContent = generateTypeFile(options);
|
|
89
|
+
const vueContent = generateVueFile(options);
|
|
90
|
+
const storyContent = generateStoryFile(options);
|
|
91
|
+
const componentExport = generateComponentExport(name);
|
|
92
|
+
const typeExport = generateTypeExport(name);
|
|
93
|
+
// Create directories
|
|
94
|
+
await fs.mkdir(componentDir, { recursive: true });
|
|
95
|
+
await fs.mkdir(storyDir, { recursive: true });
|
|
96
|
+
// Write files
|
|
97
|
+
const filePaths = {
|
|
98
|
+
type: path.join(componentDir, `${name}.ts`),
|
|
99
|
+
vue: path.join(componentDir, `${name}.vue`),
|
|
100
|
+
story: path.join(storyDir, `${name}.stories.ts`),
|
|
101
|
+
};
|
|
102
|
+
await fs.writeFile(filePaths.type, typeContent, 'utf-8');
|
|
103
|
+
await fs.writeFile(filePaths.vue, vueContent, 'utf-8');
|
|
104
|
+
await fs.writeFile(filePaths.story, storyContent, 'utf-8');
|
|
105
|
+
// Update barrel exports
|
|
106
|
+
const componentsPath = path.join(projectRoot, 'src/components/components.ts');
|
|
107
|
+
const typesPath = path.join(projectRoot, 'src/components/types.ts');
|
|
108
|
+
const componentsSource = await fs.readFile(componentsPath, 'utf-8');
|
|
109
|
+
const typesSource = await fs.readFile(typesPath, 'utf-8');
|
|
110
|
+
await fs.writeFile(componentsPath, componentsSource.trimEnd() + '\n' + componentExport + '\n', 'utf-8');
|
|
111
|
+
await fs.writeFile(typesPath, typesSource.trimEnd() + '\n' + typeExport + '\n', 'utf-8');
|
|
112
|
+
// Build output summary
|
|
113
|
+
let output = `# Scaffolded: ${name}\n\n`;
|
|
114
|
+
output += '## Created Files\n\n';
|
|
115
|
+
output += `- \`src/components/${name}/${name}.ts\` — TypeScript interfaces\n`;
|
|
116
|
+
output += `- \`src/components/${name}/${name}.vue\` — Vue component\n`;
|
|
117
|
+
output += `- \`.storybook/${category === 'module' ? 'modules' : 'components'}/${name}.stories.ts\` — Storybook story\n\n`;
|
|
118
|
+
output += '## Updated Files\n\n';
|
|
119
|
+
output += '- `src/components/components.ts` — Added component export\n';
|
|
120
|
+
output += '- `src/components/types.ts` — Added type export\n\n';
|
|
121
|
+
output += '## Generated Type File\n\n';
|
|
122
|
+
output += '```typescript\n' + typeContent + '```\n\n';
|
|
123
|
+
output += '## Generated Vue File\n\n';
|
|
124
|
+
output += '```vue\n' + vueContent + '```\n\n';
|
|
125
|
+
output += '## Generated Story File\n\n';
|
|
126
|
+
output += '```typescript\n' + storyContent + '```\n';
|
|
127
|
+
return { content: [{ type: 'text', text: output }] };
|
|
128
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ComponentRegistry } from '../registry/component-registry.js';
|
|
2
|
+
export declare const handleSearchComponents: (registry: ComponentRegistry, query: string, category?: 'component' | 'module' | 'all') => Promise<{
|
|
3
|
+
content: {
|
|
4
|
+
type: "text";
|
|
5
|
+
text: string;
|
|
6
|
+
}[];
|
|
7
|
+
}>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const handleSearchComponents = async (registry, query, category) => {
|
|
2
|
+
const results = await registry.searchComponents(query, category);
|
|
3
|
+
if (results.length === 0) {
|
|
4
|
+
return {
|
|
5
|
+
content: [
|
|
6
|
+
{
|
|
7
|
+
type: 'text',
|
|
8
|
+
text: `No components found matching "${query}"${category && category !== 'all' ? ` in category "${category}"` : ''}. Use list-components to see all available components.`,
|
|
9
|
+
},
|
|
10
|
+
],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
let output = `# Search Results for "${query}" (${results.length} match${results.length === 1 ? '' : 'es'})\n\n`;
|
|
14
|
+
output += '| Name | Category | Variants | Description |\n';
|
|
15
|
+
output += '|------|----------|----------|-------------|\n';
|
|
16
|
+
for (const c of results) {
|
|
17
|
+
const variants = c.variants.length > 0 ? c.variants.join(', ') : '-';
|
|
18
|
+
output += `| ${c.name} | ${c.category} | ${variants} | ${c.description} |\n`;
|
|
19
|
+
}
|
|
20
|
+
return { content: [{ type: 'text', text: output }] };
|
|
21
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface PropInfo {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
required: boolean;
|
|
5
|
+
description?: string;
|
|
6
|
+
defaultValue?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface EmitInfo {
|
|
9
|
+
name: string;
|
|
10
|
+
payload?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SlotInfo {
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
export interface PassthroughKeyInfo {
|
|
16
|
+
key: string;
|
|
17
|
+
defaultClasses: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ComponentMetadata {
|
|
20
|
+
name: string;
|
|
21
|
+
category: 'component' | 'module';
|
|
22
|
+
typename: string;
|
|
23
|
+
directory: string;
|
|
24
|
+
vueFile: string;
|
|
25
|
+
typeFile: string | null;
|
|
26
|
+
interfaceName: string;
|
|
27
|
+
props: PropInfo[];
|
|
28
|
+
emits: EmitInfo[];
|
|
29
|
+
slots: SlotInfo[];
|
|
30
|
+
variants: string[];
|
|
31
|
+
variantPropName: string | null;
|
|
32
|
+
passthroughInterface: string | null;
|
|
33
|
+
passthroughKeys: PassthroughKeyInfo[];
|
|
34
|
+
extendsComponent: boolean;
|
|
35
|
+
hasColorPalette: boolean;
|
|
36
|
+
description: string;
|
|
37
|
+
}
|
|
38
|
+
export interface ComponentDiscovery {
|
|
39
|
+
exportName: string;
|
|
40
|
+
relativePath: string;
|
|
41
|
+
absoluteVuePath: string;
|
|
42
|
+
absoluteTypePath: string | null;
|
|
43
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cooperco/component-library-mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for the Cooper Component Library. Provides component documentation, prop inspection, color palette info, and scaffolding tools for AI assistants.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"component-library-mcp-server": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"model-context-protocol",
|
|
17
|
+
"vue",
|
|
18
|
+
"component-library",
|
|
19
|
+
"cooper",
|
|
20
|
+
"ai-tools"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/Cryobank/Cordblood-Component-Library.git",
|
|
25
|
+
"directory": "mcp"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
29
|
+
"typescript": "~5.3.3",
|
|
30
|
+
"zod": "^3.23.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.0.0",
|
|
34
|
+
"tsx": "^4.7.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"dev": "tsx src/index.ts",
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"watch": "tsc --watch"
|
|
40
|
+
}
|
|
41
|
+
}
|